/*
 * 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. If applicable, add the
 * following below this CDDL HEADER, with the fields enclosed by
 * brackets "[]" replaced with your own identifying information:
 * Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
 
/*
 *   Copyright 1996 Warm Silence Software Ltd.  All rights reserved.
 *   Use is subject to license terms.
 */

/*
 *   PHBG: 20/12/96: Initial (termite) version
 *   RJW: Completly rewritten for BSD
 *   PHBG: Fix a bug or three and add listening sockets.
 *   PHBG 5/2/97:   Added UDP support
 *   PHBG 27/7/98:  Threaded version
 */

#include "memcheckcompat.h"
#include <string.h>
#include "TCPIPLibs.errno.h"
#include "socklib.h"
#include "sys/ioctl.h"
#include "netinet.tcp.h"
#include "netdb.h"
#include "unixlib.h"
#include "inetlib.h"
#include "gethost.h"

#include "swis.h"
#include "kernel.h"
#include "debug.h"
#include "error.h"
#include "memory.h"
#include "thread.h"

#include "tcp.h"


struct tcp_conn_s
{
    int                bleep;
    int                socket;
    int		       ready;
};

struct tcp_bleeper_s
{
    int socket;
    int close_on_destruct;
};

struct tcp_addr_s
{
    int            family;
    int            length;
    struct in_addr addr;
    char           zeropad[8];
};

struct udp_conn_s
{
    int                socket;
    struct sockaddr_in *peer;
};

void TcpInitialise(void)
{
}

void TcpFinalise(void)
{
}

char *TcpHostName(void)
{
    static char buf[65];

    gethostname(buf, 65);
    return buf;
}

typedef struct
{
    char *name;
    tcp_addr_t addr;
    int done;
} *resolution_t;

static int resolve(resolution_t res)
{
    struct hostent *host;
    int error;

    if(!res->done)
    {
        host = gethost(res->name, &error);
        switch(error)
        {
            case 0:
                res->done = 1;
                if(host->h_length > 12)
                    Error("Oversized address returned by gethost");
                res->addr = Malloc(sizeof(*res->addr));
                res->addr->length = host->h_length;
                res->addr->family = host->h_addrtype;
                memcpy((char *) &res->addr->addr, host->h_addr, res->addr->length);
                break;
            case EINPROGRESS:
                break;
            default:
                res->done = 1;
                break;
        }
    }
    return res->done;
}

tcp_addr_t TcpResolve(char *name)
{
    resolution_t res;
    tcp_addr_t addr;

    res = Malloc(sizeof(*res) + strlen(name) + 1);
    res->name = (char *)(res + 1);
    strcpy(res->name, name);
    res->addr = NULL;
    res->done = 0;
    ExceptTry
    {
        while(!resolve(res))
            ThreadWait((int (*)(void *)) resolve, res);
    }
    ExceptCatch
    {
        Free(res->addr);
        Free(res);
        ExceptRethrow();
    }
    addr = res->addr;
    Free(res);
    return addr;
}

tcp_addr_t TcpAddrFromQuad(char *quad)
{
    tcp_addr_t addr;

    addr = Malloc(sizeof(*addr));
    memset(addr, 0, sizeof(*addr));
    addr->family = AF_INET;
    addr->length = 4;
    memcpy(&addr->addr, quad, 4);
    return addr;
}

void TcpAddrToQuad(tcp_addr_t addr, char *quad)
{
    memcpy(quad, &addr->addr, 4);
}

tcp_addr_t TcpPeer(tcp_conn_t conn)
{
    tcp_addr_t result;
    struct sockaddr_in sockaddr;
    int len;

    len = sizeof(sockaddr);
    if(getpeername(conn->socket, (struct sockaddr *) &sockaddr, &len))
        ErrorErr(&_inet_error);
    len -= 4;
    result = Malloc(sizeof(*result));
    result->length = len;
    result->family = sockaddr.sin_family;
    if(len > 12)
        Error("Oversized address returned from getpeername");
    memcpy((char *) &result->addr, (char *) &sockaddr.sin_addr, len);
    return result;
}

void TcpAddrDestruct(tcp_addr_t ip)
{
    Free(ip);
}

static int conn_ready(tcp_conn_t conn)
{
    static fd_set write_fds;
    struct timeval timeout;

    conn->ready = 0;
    FD_ZERO(&write_fds);
    FD_SET(conn->socket, &write_fds);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    if(select(FD_SETSIZE, NULL, &write_fds, NULL, &timeout) < 0)
        ErrorErr(&_inet_error);
    if(FD_ISSET(conn->socket, &write_fds))
    {
        int error, size;

        size = sizeof(int);
        if(getsockopt(conn->socket, SOL_SOCKET, SO_ERROR, &error, &size) < 0)
            ErrorErr(&_inet_error);
        if(error)
            Error("Connection failed (%d)\n", error);
        conn->ready = 1;
    }
    return conn->ready;
}

tcp_conn_t TcpCall(tcp_port_t sp, tcp_port_t dp, tcp_addr_t dip)
{
    tcp_conn_t conn;
    int on = 1;
    struct sockaddr_in server;

    conn = Malloc(sizeof(*conn));
    conn->socket = -1;
    ExceptTry
    {
        conn->socket = socket(AF_INET, SOCK_STREAM, 0);
        if(conn->socket < 0)
            ErrorErr(&_inet_error);

        /* Mark Socket non-blocking */
        if(socketioctl(conn->socket,FIONBIO, &on) < 0)
            ErrorErr(&_inet_error);

        /* First lets bind the socket to the right local port */
        memset(&server, 0, sizeof(server));
        server.sin_addr.s_addr = (unsigned int)htonl(INADDR_ANY);
        server.sin_family = AF_INET;
        server.sin_port = htons((u_short)sp);
        if(bind(conn->socket, (struct sockaddr *) &server, sizeof(server)))
            ErrorErr(&_inet_error);

        /* Now prepare the remote port details */
        if(dip->length > 12)
            Error("Oversized address in TcpCall");
        memset(&server, 0, sizeof(server));
        memcpy((char *)&server.sin_addr,&dip->addr,dip->length);
        server.sin_family = dip->family;
        server.sin_port = htons((u_short)dp);

        /* Now we do the connect */
        if(connect(conn->socket, (struct sockaddr *) &server, sizeof(server)) == -1)
        {
            switch(errno)
            {
                case EINPROGRESS:
                    break;
                default:
                    ErrorErr(&_inet_error);
                    break;
            }
        }
        conn_ready(conn);
        while(!conn->ready)
            ThreadWait((int (*)(void *)) conn_ready, conn);
    }
    ExceptCatch
    {
        TcpConnDestruct(conn);
        ExceptRethrow();
    }
    return conn;
}

tcp_addr_t TcpLocalAddr(tcp_conn_t conn)
{
    tcp_addr_t result;
    struct sockaddr_in sockaddr;
    int len;

    len = sizeof(sockaddr);
    if(getsockname(conn->socket, (struct sockaddr *) &sockaddr, &len))
        ErrorErr(&_inet_error);
    len -= 4;
    result = Malloc(sizeof(*result));
    result->length = len;
    result->family = sockaddr.sin_family;
    if(len > 12)
        Error("Oversized address returned from getpeername");
    memcpy((char *) &result->addr, (char *) &sockaddr.sin_addr, len);
    return result;
}

tcp_port_t TcpLocalPort(tcp_conn_t conn)
{
    int len;
    struct sockaddr_in name;

    len = sizeof(name);
    if(getsockname(conn->socket, (struct sockaddr *) &name, &len))
        ErrorErr(&_inet_error);
    return htons((u_short)name.sin_port);
}

tcp_bleeper_t TcpListen(tcp_port_t port)
{
    tcp_bleeper_t bleep;
    int on = 1;
    struct sockaddr_in server;

    bleep = Malloc(sizeof(*bleep));
    bleep->socket = -1;
    bleep->close_on_destruct = 1;
    ExceptTry
    {
        bleep->socket = socket(AF_INET, SOCK_STREAM, 0);
        if(bleep->socket < 0)
            ErrorErr(&_inet_error);

        /* Mark Socket non-blocking */
        if(socketioctl(bleep->socket ,FIONBIO, &on) < 0)
            ErrorErr(&_inet_error);

        /* First lets bind the socket to the right local port */
        memset(&server, 0, sizeof(server));
        server.sin_addr.s_addr = (unsigned int)htonl(INADDR_ANY);
        server.sin_family = AF_INET;
        server.sin_port = htons((u_short)port);
        if(bind(bleep->socket, (struct sockaddr *) &server, sizeof(server)))
            ErrorErr(&_inet_error);
        if(listen(bleep->socket, 5))
            ErrorErr(&_inet_error);
    }
    ExceptCatch
    {
        TcpBleeperDestruct(bleep);
        ExceptRethrow();
    }
    return bleep;
}

tcp_bleeper_t TcpListenToConn(tcp_conn_t conn)
{
    tcp_bleeper_t bleep;

    bleep = Malloc(sizeof(*bleep));
    bleep->socket = conn->socket;
    bleep->close_on_destruct = 0;
    if(listen(bleep->socket, 5))
        ErrorErr(&_inet_error);

    return bleep;
}

tcp_port_t TcpBleeperPort(tcp_bleeper_t bleeper)
{
    int len;
    struct sockaddr_in name;

    len = sizeof(name);
    if(getsockname(bleeper->socket, (struct sockaddr *) &name, &len))
        ErrorErr(&_inet_error);
    return htons((u_short)name.sin_port);
}

void TcpBleeperDestruct(tcp_bleeper_t bleep)
{
    if(bleep)
    {
        if(bleep->socket != -1 && bleep->close_on_destruct)
            socketclose(bleep->socket);
        Free(bleep);
    }
}

static int answer_bleep(tcp_conn_t conn)
{
    conn->socket = accept(conn->bleep, NULL, NULL);
    if(conn->socket < 0)
    {
        switch(errno)
        {
            case EWOULDBLOCK:
                return NULL;
            default:
                ErrorErr(&_inet_error);
        }
    }
    return conn->socket >= 0;
}

tcp_conn_t TcpAnswer(tcp_bleeper_t bleep)
{
    tcp_conn_t conn;

    conn = Malloc(sizeof(*conn));
    conn->bleep = bleep->socket;
    conn->socket = -1;
    conn->ready = 0;
    ExceptTry
    {
        answer_bleep(conn);
        while(conn->socket < 0)
            ThreadWait((int (*)(void *)) answer_bleep, conn);
    }
    ExceptCatch
    {
        TcpConnDestruct(conn);
        ExceptRethrow();
    }
    return conn;
}

void TcpSetNoDelay(tcp_conn_t conn)
{
    int flag;

    flag = 1;
    if(setsockopt(conn->socket, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag)) == -1)
        ErrorErr(&_inet_error);
}

void TcpConnDestruct(tcp_conn_t conn)
{
    if(conn)
    {
        if(conn->socket >= 0)
        {
            socketclose(conn->socket);
        }
        Free(conn);
    }
}

static int conn_readable(tcp_conn_t conn)
{
    static fd_set read_fds;
    struct timeval timeout;

    FD_ZERO(&read_fds);
    FD_SET(conn->socket, &read_fds);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    if(select(FD_SETSIZE, &read_fds, NULL, NULL, &timeout) < 0)
        ErrorErr(&_inet_error);
    return (FD_ISSET(conn->socket, &read_fds) != 0);
}

int TcpRead(void *buf, int size, tcp_conn_t conn)
{
    char *cbuf;
    int read, rcvd;

#ifdef MemCheck_MEMCHECK
    memmove(buf, buf, size);
#endif
    cbuf = buf;
    read = 0;
    rcvd = -1;
    while(size && rcvd)
    {
        while((rcvd = recv(conn->socket, cbuf, size, 0)) < 0)
        {
            switch(errno)
            {
                case EWOULDBLOCK:
                    ThreadWait((int (*)(void *))conn_readable, conn);
                    break;
                default:
                    ErrorErr(&_inet_error);
                    break;
            }
        }
        cbuf += rcvd;
        read += rcvd;
        size -= rcvd;
        if(size < 0) Error("Overflow in TCPRead");
    }
    return read;
}

int TcpReadSome(void *buf, int size, tcp_conn_t conn)
{
    int rcvd;

#ifdef MemCheck_MEMCHECK
    memmove(buf, buf, size);
#endif
    while((rcvd = recv(conn->socket, buf, size, 0)) < 0)
    {
        switch(errno)
        {
            case EWOULDBLOCK:
                ThreadWait((int (*)(void *))conn_readable, conn);
                break;
            default:
                ErrorErr(&_inet_error);
                break;
        }
    }
    return rcvd;
}

static int conn_writeable(tcp_conn_t conn)
{
    static fd_set write_fds;
    struct timeval timeout;

    FD_ZERO(&write_fds);
    FD_SET(conn->socket, &write_fds);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    if(select(FD_SETSIZE, NULL, &write_fds, NULL, &timeout) < 0)
        ErrorErr(&_inet_error);
    return (FD_ISSET(conn->socket, &write_fds) != 0);
}

void TcpWrite(void *buf, int size, tcp_conn_t conn)
{
    char *cbuf;
    int sent;

#ifdef MemCheck_MEMCHECK
    memmove(buf, buf, size);
#endif
    cbuf = buf;
    sent = -1;
    while(size && sent)
    {
        while((sent = send(conn->socket, cbuf, size, 0)) < 0)
        {
            switch(errno)
            {
                case EWOULDBLOCK:
                    ThreadWait((int (*)(void *))conn_writeable, conn);
                    break;
                default:
                    ErrorErr(&_inet_error);
                    break;
            }
        }
        cbuf += sent;
        size -= sent;
        if(size < 0) Error("Overflow in TCPWrite");
    }
    if(size) Error("Incomplete write to TCP socket");
}

void TcpClose(tcp_conn_t conn)
{
    if(shutdown(conn->socket, 1) != 0)
        ErrorErr(&_inet_error);
}

udp_conn_t UdpCreate(tcp_port_t local)
{
    udp_conn_t conn;
    int on = 1;
    struct sockaddr_in server;

    conn = Malloc(sizeof(*conn));
    conn->peer   = NULL;
    conn->socket = -1;
    ExceptTry
    {
        conn->socket = socket(AF_INET, SOCK_DGRAM, 0);
        if(conn->socket < 0)
            ErrorErr(&_inet_error);

        /* Mark Socket non-blocking */
        if(socketioctl(conn->socket, FIONBIO, &on) < 0)
            ErrorErr(&_inet_error);

        /* First lets bind the socket to the right local port */
        memset(&server, 0, sizeof(server));
        server.sin_addr.s_addr = (unsigned int)htonl(INADDR_ANY);
        server.sin_family = AF_INET;
        server.sin_port = htons((u_short)local);
        if(bind(conn->socket, (struct sockaddr *) &server, sizeof(server)))
            ErrorErr(&_inet_error);
    }
    ExceptCatch
    {
        UdpDestruct(conn);
        ExceptRethrow();
    }
    return conn;
}

void UdpSetPeer(tcp_port_t port, tcp_addr_t addr, udp_conn_t conn)
{
    int on = 1;

    conn->peer = Malloc(sizeof(struct sockaddr_in));
    memset(conn->peer, 0, sizeof(struct sockaddr_in));
    if(addr)
    {
        memcpy((char *)&conn->peer->sin_addr, &addr->addr, addr->length);
        conn->peer->sin_family = addr->family;
    }
    else
    {
        if(setsockopt(conn->socket, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) == -1)
            ErrorErr(&_inet_error);
        conn->peer->sin_addr.s_addr = (unsigned int)htonl(INADDR_BROADCAST);
        conn->peer->sin_family = AF_INET;
    }
    conn->peer->sin_port = htons((u_short)port);
}

void UdpDestruct(udp_conn_t conn)
{
    if(conn)
    {
        if(conn->socket != -1)
            socketclose(conn->socket);
        if(conn->peer)
            Free(conn->peer);
        Free(conn);
    }
}

tcp_port_t UdpLocalPort(udp_conn_t conn)
{
    int len;
    struct sockaddr_in name;

    len = sizeof(name);
    if(getsockname(conn->socket, (struct sockaddr *) &name, &len))
        ErrorErr(&_inet_error);
    return htons((u_short)name.sin_port);
}

static int udp_conn_readable(udp_conn_t conn)
{
    static fd_set read_fds;
    struct timeval timeout;

    FD_ZERO(&read_fds);
    FD_SET(conn->socket, &read_fds);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    if(select(FD_SETSIZE, &read_fds, NULL, NULL, &timeout) < 0)
        ErrorErr(&_inet_error);
    return (FD_ISSET(conn->socket, &read_fds) != 0);
}

int UdpRead(void *buf, int size, udp_conn_t conn)
{
    int read;

#ifdef MemCheck_MEMCHECK
    memmove(buf, buf, size);
#endif
    while((read = recv(conn->socket, buf, size, 0)) < 0)
    {
        switch(errno)
        {
            case EWOULDBLOCK:
                ThreadWait((int (*)(void *))udp_conn_readable, conn);
                break;
            default:
                ErrorErr(&_inet_error);
                break;
        }
    }
    return read;
}

/*
    As UdpRead, and says who sent it.  A machine that answers a multicast
    question does not always say where it is in the answer, but the datagram
    came from somewhere, and that is as good an address as any it might have
    named.
*/
int UdpReadFrom(void *buf, int size, udp_conn_t conn, char *from)
{
    struct sockaddr_in sender;
    int len, read;
    unsigned char *q;

    from[0] = 0;
    for(;;)
    {
        len = sizeof(sender);
        read = recvfrom(conn->socket, buf, size, 0,
                        (struct sockaddr *) &sender, &len);
        if(read >= 0)
            break;
        switch(errno)
        {
            case EWOULDBLOCK:
                ThreadWait((int (*)(void *))udp_conn_readable, conn);
                break;
            default:
                ErrorErr(&_inet_error);
                break;
        }
    }
    if(len >= (int) sizeof(sender))
    {
        q = (unsigned char *) &sender.sin_addr;
        sprintf(from, "%d.%d.%d.%d", q[0], q[1], q[2], q[3]);
    }
    return read;
}

static int udp_conn_writeable(udp_conn_t conn)
{
    static fd_set write_fds;
    struct timeval timeout;

    FD_ZERO(&write_fds);
    FD_SET(conn->socket, &write_fds);
    timeout.tv_sec = 0;
    timeout.tv_usec = 0;
    if(select(FD_SETSIZE, NULL, &write_fds, NULL, &timeout) < 0)
        ErrorErr(&_inet_error);
    return (FD_ISSET(conn->socket, &write_fds) != 0);
}

void UdpWrite(void *buf, int size, udp_conn_t conn)
{
#ifdef MemCheck_MEMCHECK
    memmove(buf, buf, size);
#endif
    if(conn->peer == NULL)
        Error("Write to unconnected UDP socket");
    while(sendto(conn->socket, buf, size, 0, (struct sockaddr *) conn->peer, sizeof(struct sockaddr_in)) < 0)
    {
        switch(errno)
        {
            case EWOULDBLOCK:
                ThreadWait((int (*)(void *))udp_conn_writeable, conn);
                break;
            default:
                ErrorErr(&_inet_error);
                break;
        }
    }
}
