/*
 * 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.
 *
 * You can obtain a copy of the licence at RISC OS path @.^.LICENCE
 * or http://www.riscosdev.com/lanman98/LICENCE.CDDL
 * See the Licence for the specific language governing permissions
 * and limitations under the Licence.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the Licence file.
 *
 * CDDL HEADER END
 *
 * Portions Copyright 2026 RISC OS Developments
 * SPDX-License-Identifier: CDDL-1.0
 */
/* Native HTTP plus TLSManager-owned HTTPS I/O. */
#include <string.h>
#include "kernel.h"
#include "swis.h"
#include "LanMan98BaseLib/error.h"
#include "LanMan98BaseLib/tcp.h"
#include "LanMan98BaseLib/thread.h"
#include "webio.h"

#define TLSMANAGER_OPEN 0xDFA41
#define TLSMANAGER_STEP 0xDFA42
#define TLSMANAGER_READ 0xDFA43
#define TLSMANAGER_WRITE 0xDFA44
#define TLSMANAGER_CLOSE 0xDFA45
#define TLSMANAGER_ABI_VERSION 1
#define TLSMANAGER_STATUS_COMPLETE 0
#define TLSMANAGER_STATUS_EOF 4
#define WEB_TIMEOUT 3500
#define TLS_CONNECT_TIMEOUT 3000

typedef struct {
    unsigned int size;
    unsigned int abi;
    const char *hostname;
    unsigned int hostname_length;
    unsigned int port;
    unsigned int connect_timeout_cs;
    unsigned int io_timeout_cs;
    unsigned int minimum_tls;
    unsigned int trust_policy;
} tlsmanager_open_block;

typedef struct {
    int swi;
    int handle;
    void *buffer;
    int size;
    int result;
    int status;
} tls_operation;

static int tls_ready(void *argument)
{
    tls_operation *operation;
    _kernel_swi_regs in, out;
    _kernel_oserror *error;

    operation = argument;
    memset(&in, 0, sizeof(in));
    in.r[1] = operation->handle;
    if(operation->swi == TLSMANAGER_READ ||
       operation->swi == TLSMANAGER_WRITE)
    {
        in.r[2] = (int)operation->buffer;
        in.r[3] = operation->size;
    }
    error = _kernel_swi(operation->swi, &in, &out);
    if(error) ErrorErr(error);
    operation->result = out.r[0];
    operation->status = operation->swi == TLSMANAGER_STEP ?
        out.r[0] : out.r[1];
    if(operation->swi == TLSMANAGER_STEP)
        return operation->status == TLSMANAGER_STATUS_COMPLETE;
    if(operation->result > 0) return 1;
    return operation->swi == TLSMANAGER_READ &&
           operation->status == TLSMANAGER_STATUS_EOF;
}

static int tls_call(int swi, int handle, void *buffer, int size)
{
    tls_operation *operation;
    int result;

    /* ThreadWait can reuse the caller's saved stack, so its argument must be
       allocated outside that stack. */
    operation = Malloc(sizeof(*operation));
    memset(operation, 0, sizeof(*operation));
    operation->swi = swi;
    operation->handle = handle;
    operation->buffer = buffer;
    operation->size = size;
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry
    {
        if(!tls_ready(operation)) ThreadWait(tls_ready, operation);
    }
    ExceptCatch
    {
        ThreadResetTimeout();
        Free(operation);
        ExceptRethrow();
    }
    ThreadResetTimeout();
    result = operation->result;
    Free(operation);
    return result;
}

void WebIOInit(web_io *io)
{
    memset(io, 0, sizeof(*io));
    io->tls = -1;
}

void WebIOClose(web_io *io)
{
    if(io->tls != -1)
    {
        _swix(TLSMANAGER_CLOSE, _INR(0,1), 1, io->tls);
        io->tls = -1;
    }
    if(io->tcp)
    {
        TcpConnDestruct(io->tcp);
        io->tcp = NULL;
    }
}

void WebIOOpen(web_io *io, const web_url *url)
{
    tcp_addr_t address;
    tlsmanager_open_block block;
    _kernel_oserror *error;

    address = NULL;
    if(!url->secure)
    {
        ThreadSetTimeout(WEB_TIMEOUT);
        ExceptTry
        {
            address = TcpResolve((char *)url->host);
            if(!address) Error("WebDAV server name could not be resolved");
            io->tcp = TcpCall(0, url->port, address);
        }
        ExceptCatch
        {
            ThreadResetTimeout();
            if(address) TcpAddrDestruct(address);
            WebIOClose(io);
            ExceptRethrow();
        }
        ThreadResetTimeout();
        TcpAddrDestruct(address);
        return;
    }

    error = _swix(OS_Module, _INR(0,1), 18, "TLSManager");
    if(error) Error("HTTPS WebDAV requires !TLSManager");
    memset(&block, 0, sizeof(block));
    block.size = sizeof(block);
    block.abi = TLSMANAGER_ABI_VERSION;
    block.hostname = url->host;
    block.hostname_length = strlen(url->host);
    block.port = url->port;
    block.connect_timeout_cs = TLS_CONNECT_TIMEOUT;
    block.io_timeout_cs = WEB_TIMEOUT;
    error = _swix(TLSMANAGER_OPEN, _INR(0,2) | _OUT(0), 0, &block,
                  sizeof(block), &io->tls);
    if(error) ErrorErr(error);
    ExceptTry
    {
        tls_call(TLSMANAGER_STEP, io->tls, NULL, 0);
    }
    ExceptCatch
    {
        WebIOClose(io);
        ExceptRethrow();
    }
}

int WebIORead(web_io *io, void *buffer, int size)
{
    int got;

    if(io->tls != -1)
        return tls_call(TLSMANAGER_READ, io->tls, buffer, size);
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry { got = TcpReadSome(buffer, size, io->tcp); }
    ExceptCatch { ThreadResetTimeout(); ExceptRethrow(); }
    ThreadResetTimeout();
    return got;
}

void WebIOWrite(web_io *io, const void *buffer, int size)
{
    const char *position;
    int used;

    position = buffer;
    if(io->tls != -1)
    {
        while(size)
        {
            used = tls_call(TLSMANAGER_WRITE, io->tls,
                            (void *)position, size);
            if(used <= 0 || used > size)
                Error("WebDAV TLS connection stopped sending");
            position += used;
            size -= used;
        }
        return;
    }
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry { TcpWrite((void *)buffer, size, io->tcp); }
    ExceptCatch { ThreadResetTimeout(); ExceptRethrow(); }
    ThreadResetTimeout();
}
