/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "Licence").
 * You may not use this file except in compliance with the Licence.
 *
 * CDDL HEADER END
 *
 * Copyright (c) RISC OS Developments 2026. All rights reserved.
 * SPDX-License-Identifier: CDDL-1.0
 */
/* Non-blocking OpenSSL transport owned by TLSManager. */
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

#include "kernel.h"
#include "swis.h"
#include <openssl/err.h>
#include <openssl/provider.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>

#include "transport.h"

#define TLSMANAGER_MAX_CONNECTIONS 8
#define TLSMANAGER_HOST_LIMIT 255u
#define TLSMANAGER_DEFAULT_CONNECT_TIMEOUT 3000u
#define TLSMANAGER_DEFAULT_IO_TIMEOUT 3000u
#ifndef TLSMANAGER_CA_FILE
#define TLSMANAGER_CA_FILE "<TLSManager$Dir>.OpenSSL.cacert"
#endif
#define TLSMANAGER_CA_VERSION "Mozilla 2026-08-13"
#define RESOLVER_GETHOST 0x46001
#define CRYPTRANDOM_BLOCK 0x51983
#define TLSMANAGER_ERROR 0x80fd00

enum connection_state {
    CONNECTION_FREE,
    CONNECTION_RESOLVING,
    CONNECTION_CONNECTING,
    CONNECTION_PROXY_HANDSHAKE,
    CONNECTION_PROXY_SEND,
    CONNECTION_PROXY_READ,
    CONNECTION_HANDSHAKE,
    CONNECTION_READY,
    CONNECTION_FAILED
};

typedef struct connection {
    unsigned int generation;
    enum connection_state state;
    int socket;
    SSL *ssl;
    SSL *proxy_ssl;
    char hostname[TLSMANAGER_HOST_LIMIT + 1u];
    char proxy_hostname[TLSMANAGER_HOST_LIMIT + 1u];
    unsigned int port;
    unsigned int proxy_port;
    int proxy_secure;
    int target_secure;
    unsigned int connect_timeout;
    unsigned int io_timeout;
    int deadline;
    int wait_status;
    unsigned int handshake_steps;
    char proxy_buffer[768];
    size_t proxy_sent;
    size_t proxy_used;
} connection;

static SSL_CTX *client_context;
static OSSL_PROVIDER *default_provider;
static connection connections[TLSMANAGER_MAX_CONNECTIONS];
static _kernel_oserror transport_error;
static int last_ssl_error;
static int last_socket_error;

static _kernel_oserror *error_text(const char *text)
{
    transport_error.errnum = TLSMANAGER_ERROR;
    strncpy(transport_error.errmess, text, sizeof(transport_error.errmess)-1u);
    transport_error.errmess[sizeof(transport_error.errmess)-1u] = '\0';
    return &transport_error;
}

static _kernel_oserror *openssl_error(const char *operation)
{
    unsigned long code;
    const char *reason;
    char text[200];

    code = ERR_peek_error();
    reason = code ? ERR_reason_error_string(code) : NULL;
    if(reason)
        sprintf(text, "TLSManager %s SSL %d errno %d &%08lx: %.80s",
                operation, last_ssl_error, last_socket_error, code, reason);
    else if(code)
        sprintf(text, "TLSManager %s SSL %d errno %d (OpenSSL &%08lx)",
                operation, last_ssl_error, last_socket_error, code);
    else
        sprintf(text, "TLSManager %s SSL %d errno %d (no OpenSSL error)",
                operation, last_ssl_error, last_socket_error);
    return error_text(text);
}

static int monotonic_time(void)
{
    int now;
    _swix(OS_ReadMonotonicTime, _OUT(0), &now);
    return now;
}

static int expired(const connection *item)
{
    return (int)(monotonic_time() - item->deadline) >= 0;
}

static int ipv4_literal(const char *text, struct in_addr *address)
{
    unsigned char octets[4];
    unsigned int value;
    int octet;
    int digits;

    for(octet = 0; octet < 4; octet++)
    {
        value = 0;
        digits = 0;
        while(*text >= '0' && *text <= '9')
        {
            value = value * 10u + (unsigned int)(*text++ - '0');
            if(value > 255u) return 0;
            digits++;
        }
        if(!digits) return 0;
        octets[octet] = (unsigned char)value;
        if(octet < 3)
        {
            if(*text++ != '.') return 0;
        }
        else if(*text != '\0') return 0;
    }
    memcpy(&address->s_addr, octets, sizeof(octets));
    return 1;
}

static void release_connection(connection *item)
{
    if(item->ssl)
    {
        SSL_free(item->ssl);
        item->ssl = NULL;
    }
    if(item->proxy_ssl)
    {
        SSL_free(item->proxy_ssl);
        item->proxy_ssl = NULL;
    }
    if(item->socket >= 0)
    {
        close(item->socket);
        item->socket = -1;
    }
    memset(item->hostname, 0, sizeof(item->hostname));
    memset(item->proxy_hostname, 0, sizeof(item->proxy_hostname));
    memset(item->proxy_buffer, 0, sizeof(item->proxy_buffer));
    item->proxy_port = 0;
    item->proxy_secure = 0;
    item->target_secure = 1;
    item->proxy_sent = 0;
    item->proxy_used = 0;
    item->state = CONNECTION_FREE;
}

static connection *connection_for(unsigned int handle)
{
    unsigned int slot;
    unsigned int generation;
    connection *item;

    slot = (handle & 255u);
    generation = handle >> 8;
    if(slot == 0 || slot > TLSMANAGER_MAX_CONNECTIONS || generation == 0)
        return NULL;
    item = &connections[slot-1u];
    if(item->state == CONNECTION_FREE || item->generation != generation)
        return NULL;
    return item;
}

static unsigned int handle_for(unsigned int slot, connection *item)
{
    return (item->generation << 8) | (slot + 1u);
}

static int ssl_status(connection *item, int result)
{
    int code;
    unsigned long err;

    code = SSL_get_error(item->ssl, result);
    last_ssl_error = code;
    last_socket_error = errno;
    if(code == SSL_ERROR_WANT_READ) return TLSMANAGER_STATUS_READABLE;
    if(code == SSL_ERROR_WANT_WRITE) return TLSMANAGER_STATUS_WRITABLE;
    /* The RISC OS socket veneer can leave the BIO retry flag clear even
       though its errno is one of the normal non-blocking states. */
    if(code == SSL_ERROR_SYSCALL &&
       (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINPROGRESS ||
        errno == EALREADY || errno == ENOTCONN))
    {
        code = SSL_want(item->ssl);
        if(code == SSL_READING) return TLSMANAGER_STATUS_READABLE;
        if(code == SSL_WRITING) return TLSMANAGER_STATUS_WRITABLE;
        return TLSMANAGER_STATUS_YIELD;
    }
    if(code == SSL_ERROR_ZERO_RETURN) return TLSMANAGER_STATUS_EOF;
    /* HTTP/1.1 Connection: close peers often FIN without a TLS close_notify.
       SSL_OP_IGNORE_UNEXPECTED_EOF normally turns that into ZERO_RETURN.
       Keep an explicit-reason fallback only — an empty error queue on
       SSL_ERROR_SYSCALL must not become a false EOF on non-blocking RISC OS
       sockets. */
    if(code == SSL_ERROR_SSL || (code == SSL_ERROR_SYSCALL && errno == 0))
    {
        err = ERR_peek_error();
        if(err && ERR_GET_REASON(err) == SSL_R_UNEXPECTED_EOF_WHILE_READING)
            return TLSMANAGER_STATUS_EOF;
    }
    return -1;
}

_kernel_oserror *TLSManagerTransportInit(void)
{
    unsigned char entropy[48];
    _kernel_oserror *error;
    unsigned int i;

    memset(connections, 0, sizeof(connections));
    for(i=0; i<TLSMANAGER_MAX_CONNECTIONS; i++)
        connections[i].socket = -1;
    error = _swix(CRYPTRANDOM_BLOCK, _INR(0,1), entropy, sizeof(entropy));
    if(error) return error_text("TLSManager requires CryptRandom");
    if(!OPENSSL_init_ssl(OPENSSL_INIT_NO_LOAD_CONFIG, NULL))
        return openssl_error("initialisation");
    default_provider = OSSL_PROVIDER_load(NULL, "default");
    if(!default_provider)
    {
        memset(entropy, 0, sizeof(entropy));
        return openssl_error("default provider load");
    }
    RAND_seed(entropy, sizeof(entropy));
    if(RAND_status() != 1)
    {
        memset(entropy, 0, sizeof(entropy));
        error = openssl_error("CryptRandom seeding");
        OSSL_PROVIDER_unload(default_provider);
        default_provider = NULL;
        return error;
    }
    memset(entropy, 0, sizeof(entropy));
    client_context = SSL_CTX_new(TLS_client_method());
    if(!client_context)
    {
        error = openssl_error("context creation");
        memset(entropy, 0, sizeof(entropy));
        OSSL_PROVIDER_unload(default_provider);
        default_provider = NULL;
        return error;
    }
    SSL_CTX_set_verify(client_context, SSL_VERIFY_PEER, NULL);
    SSL_CTX_set_min_proto_version(client_context, TLS1_2_VERSION);
    /* WebDAV uses Connection: close. Many servers end the TCP stream without
       a TLS close_notify; OpenSSL 3 otherwise reports UNEXPECTED_EOF on the
       final read and aborts a completed HTTP response. */
    SSL_CTX_set_options(client_context,
                        SSL_OP_NO_COMPRESSION | SSL_OP_IGNORE_UNEXPECTED_EOF);
    if(SSL_CTX_load_verify_locations(client_context, TLSMANAGER_CA_FILE, NULL) != 1)
    {
        error = openssl_error("CA bundle load");
        SSL_CTX_free(client_context);
        client_context = NULL;
        OSSL_PROVIDER_unload(default_provider);
        default_provider = NULL;
        return error;
    }
    return NULL;
}

void TLSManagerTransportFinal(void)
{
    unsigned int i;

    for(i=0; i<TLSMANAGER_MAX_CONNECTIONS; i++)
        release_connection(&connections[i]);
    if(client_context)
    {
        SSL_CTX_free(client_context);
        client_context = NULL;
    }
    if(default_provider)
    {
        OSSL_PROVIDER_unload(default_provider);
        default_provider = NULL;
    }
}

unsigned int TLSManagerTransportCount(void)
{
    unsigned int i;
    unsigned int count;

    count = 0;
    for(i=0; i<TLSMANAGER_MAX_CONNECTIONS; i++)
        if(connections[i].state != CONNECTION_FREE) count++;
    return count;
}

const char *TLSManagerOpenSSLVersion(void)
{
    return OpenSSL_version(OPENSSL_VERSION);
}

const char *TLSManagerCABundleVersion(void)
{
    return TLSMANAGER_CA_VERSION;
}

_kernel_oserror *TLSManagerTransportOpen(const tlsmanager_open_block *block,
                                         size_t size, unsigned int *handle)
{
    unsigned int i;
    connection *item;

    if(!client_context) return error_text("TLSManager TLS engine is inactive");
    if(!block || size < offsetof(tlsmanager_open_block, proxy_hostname) ||
       block->size < offsetof(tlsmanager_open_block, proxy_hostname) ||
       block->abi != TLSMANAGER_ABI_VERSION || !block->hostname ||
       block->hostname_length == 0 ||
       block->hostname_length > TLSMANAGER_HOST_LIMIT ||
       block->port == 0 || block->port > 65535u || block->trust_policy != 0)
        return error_text("TLSManager received invalid open parameters");
    item = NULL;
    for(i=0; i<TLSMANAGER_MAX_CONNECTIONS; i++)
        if(connections[i].state == CONNECTION_FREE)
        {
            item = &connections[i];
            break;
        }
    if(!item) return error_text("TLSManager connection limit reached");
    item->generation++;
    if(item->generation == 0 || item->generation > 0x00ffffffu)
        item->generation = 1;
    memcpy(item->hostname, block->hostname, block->hostname_length);
    item->hostname[block->hostname_length] = '\0';
    if(strlen(item->hostname) != block->hostname_length)
    {
        release_connection(item);
        return error_text("TLSManager hostname contains a zero byte");
    }
    item->port = block->port;
    item->proxy_port = 0;
    item->proxy_secure = 0;
    item->target_secure = 1;
    item->proxy_sent = 0;
    item->proxy_used = 0;
    if(size >= offsetof(tlsmanager_open_block, proxy_secure) &&
       block->size >= offsetof(tlsmanager_open_block, proxy_secure) &&
       block->proxy_hostname)
    {
        if(block->proxy_hostname_length == 0 ||
           block->proxy_hostname_length > TLSMANAGER_HOST_LIMIT ||
           block->proxy_port == 0 || block->proxy_port > 65535u)
        {
            release_connection(item);
            return error_text("TLSManager received invalid proxy parameters");
        }
        memcpy(item->proxy_hostname, block->proxy_hostname,
               block->proxy_hostname_length);
        item->proxy_hostname[block->proxy_hostname_length] = '\0';
        if(strlen(item->proxy_hostname) != block->proxy_hostname_length)
        {
            release_connection(item);
            return error_text("TLSManager proxy hostname contains a zero byte");
        }
        item->proxy_port = block->proxy_port;
    }
    if(size >= sizeof(*block) && block->size >= sizeof(*block))
    {
        if(block->proxy_secure > 1u || block->target_secure > 1u ||
           (block->proxy_secure && !item->proxy_port) ||
           (!block->proxy_secure && !block->target_secure))
        {
            release_connection(item);
            return error_text("TLSManager received invalid proxy TLS settings");
        }
        item->proxy_secure = block->proxy_secure;
        item->target_secure = block->target_secure;
    }
    item->connect_timeout = block->connect_timeout_cs ?
        block->connect_timeout_cs : TLSMANAGER_DEFAULT_CONNECT_TIMEOUT;
    item->io_timeout = block->io_timeout_cs ?
        block->io_timeout_cs : TLSMANAGER_DEFAULT_IO_TIMEOUT;
    item->deadline = monotonic_time() + (int)item->connect_timeout;
    item->wait_status = TLSMANAGER_STATUS_YIELD;
    item->state = CONNECTION_RESOLVING;
    *handle = handle_for(i, item);
    return NULL;
}

static _kernel_oserror *start_socket(connection *item, struct in_addr address,
                                     int *status, int *socket_number)
{
    struct sockaddr_in remote;
    int nonblocking;
    int result;

    item->socket = socket(AF_INET, SOCK_STREAM, 0);
    if(item->socket < 0) return error_text("TLSManager could not create a socket");
    nonblocking = 1;
    if(ioctl(item->socket, FIONBIO, &nonblocking) < 0)
        return error_text("TLSManager could not make the socket non-blocking");
    {
        int bufsize = 256 * 1024;
        int nodelay = 1;
        setsockopt(item->socket, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
        setsockopt(item->socket, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
        setsockopt(item->socket, IPPROTO_TCP, TCP_NODELAY, &nodelay, sizeof(nodelay));
    }
    memset(&remote, 0, sizeof(remote));
    remote.sin_family = AF_INET;
    remote.sin_port = htons((unsigned short)(item->proxy_port ?
                                             item->proxy_port : item->port));
    remote.sin_addr = address;
    result = connect(item->socket, (struct sockaddr *)&remote, sizeof(remote));
    if(result == 0 || errno == EISCONN)
        item->state = item->proxy_secure ? CONNECTION_PROXY_HANDSHAKE :
                      item->proxy_port ? CONNECTION_PROXY_SEND :
                                         CONNECTION_HANDSHAKE;
    else if(errno == EINPROGRESS || errno == EWOULDBLOCK || errno == EALREADY)
        item->state = CONNECTION_CONNECTING;
    else
        return error_text("TLSManager TCP connection failed");
    *status = (item->state == CONNECTION_HANDSHAKE ||
               item->state == CONNECTION_PROXY_HANDSHAKE ||
               item->state == CONNECTION_PROXY_SEND) ?
        TLSMANAGER_STATUS_YIELD : TLSMANAGER_STATUS_WRITABLE;
    *socket_number = item->socket;
    return NULL;
}

static _kernel_oserror *start_tls(connection *item)
{
    X509_VERIFY_PARAM *parameters;
    BIO *proxy_bio;
    const char *hostname;

    hostname = item->state == CONNECTION_PROXY_HANDSHAKE ?
               item->proxy_hostname : item->hostname;
    item->ssl = SSL_new(client_context);
    if(!item->ssl) return openssl_error("connection allocation");
    if(item->proxy_ssl)
    {
        /* The inner TLS session owns this BIO; the outer SSL remains owned
           by the connection and is released after the inner session. */
        proxy_bio = BIO_new(BIO_f_ssl());
        if(!proxy_bio) return openssl_error("proxy BIO allocation");
        if(BIO_set_ssl(proxy_bio, item->proxy_ssl, BIO_NOCLOSE) <= 0)
        {
            BIO_free(proxy_bio);
            return openssl_error("proxy BIO setup");
        }
        SSL_set_bio(item->ssl, proxy_bio, proxy_bio);
    }
    else if(SSL_set_fd(item->ssl, item->socket) != 1)
        return openssl_error("socket setup");
    if(SSL_set_tlsext_host_name(item->ssl, hostname) != 1 ||
       SSL_set1_host(item->ssl, hostname) != 1)
        return openssl_error("hostname setup");
    parameters = SSL_get0_param(item->ssl);
    X509_VERIFY_PARAM_set_hostflags(parameters, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
    SSL_set_mode(item->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE |
                 SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
    SSL_set_connect_state(item->ssl);
    return NULL;
}

_kernel_oserror *TLSManagerTransportStep(unsigned int handle, int *status,
                                         int *socket_number)
{
    connection *item;
    _kernel_oserror *error;
    struct hostent *host;
    struct in_addr address;
    int resolver_error;
    int socket_error;
    socklen_t socket_error_size;
    fd_set write_fds;
    fd_set read_fds;
    struct timeval timeout;
    int result;

    item = connection_for(handle);
    if(!item) return error_text("TLSManager connection handle is stale");
    *socket_number = item->socket;
    if(item->state == CONNECTION_FAILED)
        return error_text("TLSManager connection has failed");
    if(item->state == CONNECTION_READY)
    {
        *status = TLSMANAGER_STATUS_COMPLETE;
        return NULL;
    }
    if(expired(item))
    {
        char detail[160];

        sprintf(detail,
                "TLSManager connection timed out: state %d wait %d SSL %d errno %d steps %u",
                item->state, item->wait_status, last_ssl_error,
                last_socket_error, item->handshake_steps);
        item->state = CONNECTION_FAILED;
        return error_text(detail);
    }
    if(item->state == CONNECTION_RESOLVING)
    {
        /* RISC OS Resolver_GetHost is for names; use the supplied address
           directly when a proxy or origin was configured as dotted IPv4. */
        if(ipv4_literal(item->proxy_port ? item->proxy_hostname :
                        item->hostname, &address))
        {
            error = start_socket(item, address, status, socket_number);
            if(error) item->state = CONNECTION_FAILED;
            return error;
        }
        host = NULL;
        resolver_error = 0;
        error = _swix(RESOLVER_GETHOST, _IN(0)|_OUT(0)|_OUT(1),
                      item->proxy_port ? item->proxy_hostname : item->hostname,
                      &resolver_error, &host);
        if(error)
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager requires Resolver 0.48 or later");
        }
        if(resolver_error < 0)
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager could not resolve the server name");
        }
        if(resolver_error == EINPROGRESS || !host)
        {
            *status = TLSMANAGER_STATUS_YIELD;
            return NULL;
        }
        if(host->h_length != sizeof(address) ||
           !host->h_addr_list || !host->h_addr_list[0])
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager could not resolve the server name");
        }
        memcpy(&address, host->h_addr_list[0], sizeof(address));
        error = start_socket(item, address, status, socket_number);
        if(error) item->state = CONNECTION_FAILED;
        return error;
    }
    if(item->state == CONNECTION_CONNECTING)
    {
        FD_ZERO(&write_fds);
        FD_SET(item->socket, &write_fds);
        timeout.tv_sec = 0;
        timeout.tv_usec = 0;
        result = select(FD_SETSIZE, NULL, &write_fds, NULL, &timeout);
        if(result < 0)
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager TCP readiness check failed");
        }
        if(result == 0 || !FD_ISSET(item->socket, &write_fds))
        {
            *status = TLSMANAGER_STATUS_WRITABLE;
            return NULL;
        }
        socket_error = 0;
        socket_error_size = sizeof(socket_error);
        if(getsockopt(item->socket, SOL_SOCKET, SO_ERROR, &socket_error,
                      &socket_error_size) < 0 || socket_error)
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager TCP connection failed");
        }
        item->state = item->proxy_secure ? CONNECTION_PROXY_HANDSHAKE :
                      item->proxy_port ? CONNECTION_PROXY_SEND :
                                         CONNECTION_HANDSHAKE;
    }
    if((item->state == CONNECTION_PROXY_HANDSHAKE ||
        item->state == CONNECTION_HANDSHAKE) && !item->ssl)
    {
        error = start_tls(item);
        if(error)
        {
            item->state = CONNECTION_FAILED;
            return error;
        }
    }
    if(item->wait_status == TLSMANAGER_STATUS_READABLE ||
       item->wait_status == TLSMANAGER_STATUS_WRITABLE)
    {
        /* TLS records can already be decrypted in the outer session even
           when the socket has no new readable bytes. */
        if(item->wait_status != TLSMANAGER_STATUS_READABLE ||
           ((!item->ssl || SSL_pending(item->ssl) == 0) &&
            (!item->proxy_ssl || SSL_pending(item->proxy_ssl) == 0)))
        {
            FD_ZERO(&read_fds);
            FD_ZERO(&write_fds);
            if(item->wait_status == TLSMANAGER_STATUS_READABLE)
                FD_SET(item->socket, &read_fds);
            else
                FD_SET(item->socket, &write_fds);
            timeout.tv_sec = 0;
            timeout.tv_usec = 0;
            result = select(FD_SETSIZE,
                            item->wait_status == TLSMANAGER_STATUS_READABLE ?
                                &read_fds : NULL,
                            item->wait_status == TLSMANAGER_STATUS_WRITABLE ?
                                &write_fds : NULL,
                            NULL, &timeout);
            if(result < 0)
            {
                item->state = CONNECTION_FAILED;
                return error_text("TLSManager socket readiness check failed");
            }
            if(result == 0)
            {
                *status = item->wait_status;
                return NULL;
            }
        }
        item->wait_status = TLSMANAGER_STATUS_YIELD;
    }
    if(item->state == CONNECTION_PROXY_SEND)
    {
        if(item->proxy_used == 0)
            item->proxy_used = (size_t)sprintf(item->proxy_buffer,
                "CONNECT %s:%u HTTP/1.1\r\nHost: %s:%u\r\n"
                "User-Agent: LanMan98-TLSManager\r\n"
                "Proxy-Connection: keep-alive\r\n\r\n",
                item->hostname, item->port, item->hostname, item->port);
        ERR_clear_error();
        errno = 0;
        result = item->proxy_secure ?
            SSL_write(item->ssl, item->proxy_buffer + item->proxy_sent,
                      (int)(item->proxy_used - item->proxy_sent)) :
            send(item->socket, item->proxy_buffer + item->proxy_sent,
                 item->proxy_used - item->proxy_sent, 0);
        if(result > 0)
            item->proxy_sent += (size_t)result;
        else if(item->proxy_secure)
        {
            *status = ssl_status(item, result);
            if(*status != TLSMANAGER_STATUS_READABLE &&
               *status != TLSMANAGER_STATUS_WRITABLE &&
               *status != TLSMANAGER_STATUS_YIELD)
            {
                item->state = CONNECTION_FAILED;
                return openssl_error("proxy CONNECT write");
            }
            item->wait_status = *status;
            return NULL;
        }
        else if(result == 0 || (errno != EAGAIN && errno != EWOULDBLOCK))
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager could not send the proxy CONNECT request");
        }
        if(item->proxy_sent < item->proxy_used)
        {
            item->wait_status = *status = TLSMANAGER_STATUS_WRITABLE;
            return NULL;
        }
        item->state = CONNECTION_PROXY_READ;
        item->proxy_used = 0;
        item->wait_status = *status = TLSMANAGER_STATUS_READABLE;
        return NULL;
    }
    if(item->state == CONNECTION_PROXY_READ)
    {
        ERR_clear_error();
        errno = 0;
        result = item->proxy_secure ?
            SSL_read(item->ssl, item->proxy_buffer + item->proxy_used,
                     (int)(sizeof(item->proxy_buffer) - 1u - item->proxy_used)) :
            recv(item->socket, item->proxy_buffer + item->proxy_used,
                 sizeof(item->proxy_buffer) - 1u - item->proxy_used, 0);
        if(result > 0)
        {
            item->proxy_used += (size_t)result;
            item->proxy_buffer[item->proxy_used] = '\0';
        }
        else if(result == 0)
        {
            item->state = CONNECTION_FAILED;
            return error_text("WebDAV proxy closed the CONNECT response");
        }
        else if(item->proxy_secure)
        {
            *status = ssl_status(item, result);
            if(*status != TLSMANAGER_STATUS_READABLE &&
               *status != TLSMANAGER_STATUS_WRITABLE &&
               *status != TLSMANAGER_STATUS_YIELD)
            {
                item->state = CONNECTION_FAILED;
                return openssl_error("proxy CONNECT read");
            }
            item->wait_status = *status;
            return NULL;
        }
        else if(errno == EAGAIN || errno == EWOULDBLOCK)
        {
            item->wait_status = *status = TLSMANAGER_STATUS_READABLE;
            return NULL;
        }
        else
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager could not read the proxy CONNECT response");
        }
        if(strstr(item->proxy_buffer, "\r\n\r\n") == NULL)
        {
            if(item->proxy_used == sizeof(item->proxy_buffer) - 1u)
            {
                item->state = CONNECTION_FAILED;
                return error_text("WebDAV proxy CONNECT response is too large");
            }
            item->wait_status = *status = TLSMANAGER_STATUS_READABLE;
            return NULL;
        }
        if((strncmp(item->proxy_buffer, "HTTP/1.1 200 ", 13) != 0 &&
            strncmp(item->proxy_buffer, "HTTP/1.0 200 ", 13) != 0))
        {
            item->state = CONNECTION_FAILED;
            if(!strncmp(item->proxy_buffer, "HTTP/1.1 407 ", 13) ||
               !strncmp(item->proxy_buffer, "HTTP/1.0 407 ", 13))
                return error_text("WebDAV proxy authentication is required");
            return error_text("WebDAV proxy rejected the CONNECT request");
        }
        memset(item->proxy_buffer, 0, sizeof(item->proxy_buffer));
        item->proxy_used = item->proxy_sent = 0;
        item->wait_status = TLSMANAGER_STATUS_YIELD;
        if(!item->target_secure)
        {
            item->state = CONNECTION_READY;
            item->deadline = monotonic_time() + (int)item->io_timeout;
            *status = TLSMANAGER_STATUS_COMPLETE;
            return NULL;
        }
        if(item->proxy_secure)
        {
            item->proxy_ssl = item->ssl;
            item->ssl = NULL;
        }
        item->state = CONNECTION_HANDSHAKE;
    }
    if(item->state == CONNECTION_HANDSHAKE && !item->ssl)
    {
        error = start_tls(item);
        if(error)
        {
            item->state = CONNECTION_FAILED;
            return error;
        }
    }
    ERR_clear_error();
    errno = 0;
    item->handshake_steps++;
    result = SSL_connect(item->ssl);
    if(result == 1)
    {
        if(SSL_get_verify_result(item->ssl) != X509_V_OK)
        {
            int proxy_handshake = item->state == CONNECTION_PROXY_HANDSHAKE;
            item->state = CONNECTION_FAILED;
            return error_text(proxy_handshake ?
                              "TLSManager proxy certificate verification failed" :
                              "TLSManager certificate verification failed");
        }
        if(item->state == CONNECTION_PROXY_HANDSHAKE)
        {
            item->state = CONNECTION_PROXY_SEND;
            item->wait_status = TLSMANAGER_STATUS_YIELD;
            *status = TLSMANAGER_STATUS_YIELD;
            return NULL;
        }
        item->state = CONNECTION_READY;
        item->deadline = monotonic_time() + (int)item->io_timeout;
        *status = TLSMANAGER_STATUS_COMPLETE;
        *socket_number = item->socket;
        return NULL;
    }
    *status = ssl_status(item, result);
    item->wait_status = *status;
    if(*status < 0)
    {
        int proxy_handshake = item->state == CONNECTION_PROXY_HANDSHAKE;
        item->state = CONNECTION_FAILED;
        return openssl_error(proxy_handshake ? "proxy handshake" : "handshake");
    }
    *socket_number = item->socket;
    return NULL;
}

_kernel_oserror *TLSManagerTransportRead(unsigned int handle, void *buffer,
                                         size_t length, size_t *used,
                                         int *status)
{
    connection *item;
    int result;

    item = connection_for(handle);
    if(!item || item->state != CONNECTION_READY)
        return error_text("TLSManager connection is not ready");
    if(!buffer || length == 0 || length > 0x7fffffffu)
        return error_text("TLSManager received invalid read parameters");
    if(expired(item)) return error_text("TLSManager read timed out");
    ERR_clear_error();
    result = SSL_read(item->ssl, buffer, (int)length);
    if(result > 0)
    {
        *used = (size_t)result;
        *status = TLSMANAGER_STATUS_COMPLETE;
        item->deadline = monotonic_time() + (int)item->io_timeout;
        return NULL;
    }
    *used = 0;
    *status = ssl_status(item, result);
    if(*status < 0)
    {
        item->state = CONNECTION_FAILED;
        return openssl_error("read");
    }
    return NULL;
}

_kernel_oserror *TLSManagerTransportWrite(unsigned int handle,
                                          const void *buffer, size_t length,
                                          size_t *used, int *status)
{
    connection *item;
    int result;

    item = connection_for(handle);
    if(!item || item->state != CONNECTION_READY)
        return error_text("TLSManager connection is not ready");
    if(!buffer || length == 0 || length > 0x7fffffffu)
        return error_text("TLSManager received invalid write parameters");
    if(expired(item)) return error_text("TLSManager write timed out");
    ERR_clear_error();
    result = SSL_write(item->ssl, buffer, (int)length);
    if(result > 0)
    {
        *used = (size_t)result;
        *status = TLSMANAGER_STATUS_COMPLETE;
        /* Arm a fresh read window once the request bytes are on the wire. */
        item->deadline = monotonic_time() + (int)item->io_timeout;
        return NULL;
    }
    *used = 0;
    *status = ssl_status(item, result);
    if(*status == TLSMANAGER_STATUS_READABLE ||
       *status == TLSMANAGER_STATUS_WRITABLE ||
       *status == TLSMANAGER_STATUS_YIELD)
        item->deadline = monotonic_time() + (int)item->io_timeout;
    if(*status < 0)
    {
        item->state = CONNECTION_FAILED;
        return openssl_error("write");
    }
    return NULL;
}

_kernel_oserror *TLSManagerTransportClose(unsigned int handle,
                                          unsigned int flags)
{
    connection *item;

    if(flags & ~1u) return error_text("TLSManager close flags are invalid");
    item = connection_for(handle);
    if(!item) return error_text("TLSManager connection handle is stale");
    if((flags & 1u) && item->ssl && item->state == CONNECTION_READY)
        SSL_shutdown(item->ssl);
    release_connection(item);
    return NULL;
}
