/*
 * 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 RISC OS HTTP and HTTPS I/O. AcornSSL owns the TLS socket.
 * All network waits cooperate with LanMan98's existing scheduler. */
#include <string.h>
#include "kernel.h"
#include "swis.h"
#include "socklib.h"
#include "netinet/in.h"
#include "inetlib.h"
#include "sys/ioctl.h"
#include "LanMan98BaseLib/error.h"
#include "LanMan98BaseLib/tcp.h"
#include "LanMan98BaseLib/thread.h"
#include "webio.h"
#define SSL_CREAT 0x50f80
#define SSL_IOCTL 0x50f81
#define SSL_CONNECT 0x50f82
#define SSL_CLOSE 0x50f84
#define SSL_WRITE 0x50f86
#define SSL_SETOPT 0x50f8b
#define SSL_READ 0x50f8e
#define SSL_HOSTNAME 0x11e0
#define SSL_PROMPTTIME 0x11e1
#define WEB_TIMEOUT 3000
/* Values from the Internet module's BSD socket interface. */
#define WEB_SOL_SOCKET SOL_SOCKET
#define WEB_FIONBIO FIONBIO

typedef struct {
    int swi;
    _kernel_swi_regs in, out;
} ssl_operation;

static int ssl_ready(void *arg)
{
    ssl_operation *op = arg;
    _kernel_oserror *e;
    int code;
    e = _kernel_swi(op->swi, &op->in, &op->out);
    if(!e) return 1;
    code = e->errnum & 255;
    /* Internet's allocated error range, rather than arbitrary TLS errors
       whose low byte might happen to match an errno. */
    if(op->swi == SSL_CONNECT && e->errnum == 0x20e24) return 1;
    if((e->errnum & ~255) == 0x20e00 &&
       (code == 35 || code == 36 || code == 37 || code == 57)) return 0;
    ErrorErr(e);
    return 0;
}

static int ssl_call(int swi, int handle, void *buf, int n)
{
    ssl_operation op;
    memset(&op,0,sizeof(op));
    op.swi = swi;
    op.in.r[0] = handle; op.in.r[1] = (int)buf; op.in.r[2] = n;
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry
    {
        if(!ssl_ready(&op)) ThreadWait(ssl_ready,&op);
    }
    ExceptCatch
    {
        ThreadResetTimeout(); ExceptRethrow();
    }
    ThreadResetTimeout();
    return op.out.r[0];
}

static void option(int handle, int name, void *value)
{
    _kernel_oserror *e;
    e = _swix(SSL_SETOPT, _INR(0,4), handle, WEB_SOL_SOCKET, name, value, 4);
    if(e) ErrorErr(e);
}

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

void WebIOClose(web_io *io)
{
    if(io->ssl != -1) { _swix(SSL_CLOSE,_IN(0),io->ssl); io->ssl = -1; }
    if(io->tcp) { TcpConnDestruct(io->tcp); io->tcp = NULL; }
}

void WebIOOpen(web_io *io, const web_url *url)
{
    tcp_addr_t addr;
    struct sockaddr_in socket_addr;
    _kernel_oserror *e;
    int on, prompt;
    const char *host;
    addr = NULL;
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry
    {
        addr = TcpResolve((char *)url->host);
        if(!addr) Error("WebDAV server name could not be resolved");
        if(!url->secure) io->tcp = TcpCall(0,url->port,addr);
        else
        {
            e = _swix(SSL_CREAT,_INR(0,2)|_OUT(0),2,1,0,&io->ssl);
            if(e) ErrorErr(e);
            host = url->host; prompt = 0;
            option(io->ssl,SSL_HOSTNAME,(void *)host);
            option(io->ssl,SSL_PROMPTTIME,&prompt);
            on = 1;
            e = _swix(SSL_IOCTL,_INR(0,2),io->ssl,WEB_FIONBIO,&on);
            if(e) ErrorErr(e);
            memset(&socket_addr,0,sizeof(socket_addr));
            socket_addr.sin_len = sizeof(socket_addr);
            socket_addr.sin_family = AF_INET;
            socket_addr.sin_port = htons((unsigned short)url->port);
            TcpAddrToQuad(addr,(char *)&socket_addr.sin_addr);
            ssl_call(SSL_CONNECT,io->ssl,&socket_addr,sizeof(socket_addr));
        }
    }
    ExceptCatch
    {
        ThreadResetTimeout();
        if(addr) TcpAddrDestruct(addr);
        WebIOClose(io); ExceptRethrow();
    }
    ThreadResetTimeout();
    TcpAddrDestruct(addr);
}

int WebIORead(web_io *io, void *buf, int n)
{
    int got;
    if(io->ssl != -1) return ssl_call(SSL_READ,io->ssl,buf,n);
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry { got = TcpReadSome(buf,n,io->tcp); }
    ExceptCatch { ThreadResetTimeout(); ExceptRethrow(); }
    ThreadResetTimeout();
    return got;
}

void WebIOWrite(web_io *io, const void *buf, int n)
{
    const char *p = buf;
    int got;
    if(io->ssl != -1)
    {
        while(n)
        {
            got = ssl_call(SSL_WRITE,io->ssl,(void *)p,n);
            if(got <= 0 || got > n) Error("WebDAV TLS connection stopped sending");
            p += got; n -= got;
        }
        return;
    }
    ThreadSetTimeout(WEB_TIMEOUT);
    ExceptTry { TcpWrite((void *)buf,n,io->tcp); }
    ExceptCatch { ThreadResetTimeout(); ExceptRethrow(); }
    ThreadResetTimeout();
}
