/*
 * 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 "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
#define TLSMANAGER_CA_FILE "<TLSManager$Dir>.OpenSSL.cacert"
#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_HANDSHAKE,
    CONNECTION_READY,
    CONNECTION_FAILED
};

typedef struct connection {
    unsigned int generation;
    enum connection_state state;
    int socket;
    SSL *ssl;
    char hostname[TLSMANAGER_HOST_LIMIT + 1u];
    unsigned int port;
    unsigned int connect_timeout;
    unsigned int io_timeout;
    int deadline;
    int wait_status;
    unsigned int handshake_steps;
} 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 void release_connection(connection *item)
{
    if(item->ssl)
    {
        SSL_free(item->ssl);
        item->ssl = NULL;
    }
    if(item->socket >= 0)
    {
        close(item->socket);
        item->socket = -1;
    }
    memset(item->hostname, 0, sizeof(item->hostname));
    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;

    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;
    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);
    SSL_CTX_set_options(client_context, SSL_OP_NO_COMPRESSION);
    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 < sizeof(*block) || block->size < sizeof(*block) ||
       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->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");
    memset(&remote, 0, sizeof(remote));
    remote.sin_family = AF_INET;
    remote.sin_port = htons((unsigned short)item->port);
    remote.sin_addr = address;
    result = connect(item->socket, (struct sockaddr *)&remote, sizeof(remote));
    if(result == 0 || errno == EISCONN)
        item->state = 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 ?
        TLSMANAGER_STATUS_YIELD : TLSMANAGER_STATUS_WRITABLE;
    *socket_number = item->socket;
    return NULL;
}

static _kernel_oserror *start_tls(connection *item)
{
    X509_VERIFY_PARAM *parameters;

    item->ssl = SSL_new(client_context);
    if(!item->ssl) return openssl_error("connection allocation");
    if(SSL_set_fd(item->ssl, item->socket) != 1 ||
       SSL_set_tlsext_host_name(item->ssl, item->hostname) != 1 ||
       SSL_set1_host(item->ssl, item->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);
    item->state = CONNECTION_HANDSHAKE;
    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)
    {
        host = NULL;
        resolver_error = 0;
        error = _swix(RESOLVER_GETHOST, _IN(0)|_OUT(0)|_OUT(1),
                      item->hostname, &resolver_error, &host);
        if(error)
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager requires Resolver 0.48 or later");
        }
        if(resolver_error == EINPROGRESS || !host)
        {
            *status = TLSMANAGER_STATUS_YIELD;
            return NULL;
        }
        if(resolver_error < 0 || 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");
        }
        error = start_tls(item);
        if(error)
        {
            item->state = CONNECTION_FAILED;
            return error;
        }
    }
    else if(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)
    {
        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 TLS readiness check failed");
        }
        if(result == 0)
        {
            *status = item->wait_status;
            return NULL;
        }
        item->wait_status = TLSMANAGER_STATUS_YIELD;
    }
    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)
        {
            item->state = CONNECTION_FAILED;
            return error_text("TLSManager certificate verification failed");
        }
        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)
    {
        item->state = CONNECTION_FAILED;
        return openssl_error("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;
        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("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;
}
