/*
 * dctpSockSupport.c -- Internal support routines for socket operations.  For
 * dccp-tp internal use only.
 *
 * Copyright (C) 2008 Tom Phelan
 *
 * This file is part of dccp-tp.
 *
 * Dccp-tp is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * Dccp-tp is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with dccp-tp.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Documentation and source code for dccp-tp is available at
 * http://www.phelan-4.com/dccp-tp/.
 */

#include "dctpCore.h"
#include "dctpInternal.h"
#include "dctpSupport.h"
#include "dctpRawApi.h"

/*
 * Private function prototypes
 */
static void dataDropped(dctpSocket *sock, dctpPacket *pkt);
static dctpInitCookie *getInitCookie(dctpPacket *pkt);

#define DCTPSOCK_LISTENHASHSIZE         97
#define DCTPSOCK_HALFCONNECTEDHASHSIZE  97
#define DCTPSOCK_CONNECTEDHASHSIZE      1021

/*
 * The listen hash holds all sockets in the listen state.  Each socket
 * is unique in local port, service code and encapsulation.
 */
static dctpSocket *listenHash[DCTPSOCK_LISTENHASHSIZE];

/*
 * The half-connected hash holds sockets in the REQUEST that haven't
 * figured out their source addresses yet.  Once we get the DCCP-Respond
 * we'll learn that, and then they'll move to the connected hash.
 */
static dctpSocket *halfConnectedHash[DCTPSOCK_HALFCONNECTEDHASHSIZE];

/*
 * The connected hash holds all sockets in the various connected states.
 * Each socket is unique in source and destination port, source and
 * destination address and encapsulation.  Since address is part of the
 * key, there are encap-specific routines for dealing with the
 * connected hash.
 */
static dctpSocket *connectedHash[DCTPSOCK_CONNECTEDHASHSIZE];

/*
 * dctpiGetListenSock -- see if another socket is already listening.
 *
 * Returns a pointer to a socket already listening on the same
 * port/scode/encap as the passed socket, or NULL.
 */
dctpSocket *dctpiGetListenSock(dctpSocket *sock) {
    uint_t hash = (sock->localAddr.sa_port ^ sock->scode ^ sock->encap) %
	DCTPSOCK_LISTENHASHSIZE;
    dctpSocket *nsock;

    nsock = listenHash[hash];
    while (nsock) {
	if ((sock->scode == nsock->scode) &&
	    (sock->encap == nsock->encap) &&
	    (dctpoMemcmp(&(sock->localAddr), &(nsock->localAddr), sizeof(dctpSockaddr)) == 0)) {
	    return(nsock);
	}
	nsock = nsock->nextHash;
    }
    return(NULL);
}

/*
 * dctpiAddListenSock -- add a socket to the listen hash
 */
void dctpiAddListenSock(dctpSocket *sock) {
    uint_t hash = (sock->localAddr.sa_port ^ sock->scode ^ sock->encap) %
	DCTPSOCK_LISTENHASHSIZE;

    sock->nextHash = listenHash[hash];
    listenHash[hash] = sock;
}

static void rmListenSock(dctpSocket *sock) {
    uint_t hash = (sock->localAddr.sa_port ^ sock->scode ^ sock->encap) %
	DCTPSOCK_LISTENHASHSIZE;
    dctpSocket *ps = NULL, *sk;

    for (sk = listenHash[hash]; sk; sk = sk->nextHash) {
	if (sk == sock) {
	    if (ps) {
		ps->nextHash = sk->nextHash;
	    } else {
		listenHash[hash] = sk->nextHash;
	    }
	    return;
	}
	ps = sk;
    }
}

/*
 * dctpiGetWaitingSock -- get the first waiting connection on a listening socket
 */
dctpSocket *dctpiGetWaitingSock(dctpSocket *sock) {
    dctpSocket *nsock = sock->firstWaiting;

    if (nsock) {
	if ((sock->firstWaiting = nsock->nextWaiting) == NULL) {
	    sock->lastWaiting = NULL;
	}
	++sock->backlog;
    }
    return(nsock);
}

/*
 * dctpiAddWaitingSocket -- add a connected socket derived from a listening
 * socket to that socket's waiting queue.
 */
void dctpiAddWaitingSocket(dctpSocket *sock) {
    if (sock->backlog > 0) {
	sock->nextWaiting = NULL;
	if ((sock->nextWaiting = sock->parent->lastWaiting) == NULL) {
	    sock->parent->firstWaiting = sock;
	}
	sock->parent->lastWaiting = sock;
	--sock->backlog;
    } else {
	/* Too big backlog, get rid of connection */
	dctpiCloseConnection(sock, NULL);
    }
}

static void rmNatV4ConnectedSocket(dctpSocket *sock) {
    uint32_t hash, raddr, laddr;
    dctpSocket *ps = NULL, *sk;

    raddr = ((dctpSockaddrV4 *)&(sock->remoteAddr))->sin_addr.s_addr;
    laddr = ((dctpSockaddrV4 *)&(sock->localAddr))->sin_addr.s_addr;
    hash = (raddr ^ laddr ^ sock->remoteAddr.sa_port ^ sock->localAddr.sa_port ^
	    sock->encap) % DCTPSOCK_CONNECTEDHASHSIZE;
    for (sk = connectedHash[hash]; sk; sk = sk->nextHash) {
	if (sk == sock) {
	    if (ps) {
		ps->nextHash = sk->nextHash;
	    } else {
		connectedHash[hash] = sk->nextHash;
	    }
	    return;
	}
    }
}

static void rmConnectedSock(dctpSocket *sock) {
    switch (sock->encap) {
    case DCTPENCAP_NAT:
	rmNatV4ConnectedSocket(sock);
	return;
    default:
	dctpoLog(DCTPLOG_ERR, "rmConnectedSock: bad encap %d\n", sock->encap);
	return;
    }
}

static void rmNatV4HalfConnectedSocket(dctpSocket *sock) {
    uint32_t hash, raddr;
    dctpSocket *ps = NULL, *sk;

    raddr = ((dctpSockaddrV4 *)&(sock->remoteAddr))->sin_addr.s_addr;
    hash = (raddr ^ sock->remoteAddr.sa_port ^ sock->localAddr.sa_port ^
	    sock->encap) % DCTPSOCK_CONNECTEDHASHSIZE;
    for (sk = halfConnectedHash[hash]; sk; sk = sk->nextHash) {
	if (sk == sock) {
	    if (ps) {
		ps->nextHash = sk->nextHash;
	    } else {
		halfConnectedHash[hash] = sk->nextHash;
	    }
	    return;
	}
    }
}

static void rmHalfConnectedSock(dctpSocket *sock) {
    switch (sock->encap) {
    case DCTPENCAP_NAT:
	rmNatV4HalfConnectedSocket(sock);
	return;
    default:
	dctpoLog(DCTPLOG_ERR, "rmHalfConnectedSock: bad encap %d\n", sock->encap);
	return;
    }
}

/*
 * Add a NATv4 connected socket to the connected hash
 */
static void addNatV4ConnectedSocket(dctpSocket *sock) {
    uint32_t hash, raddr, laddr;

    raddr = ((dctpSockaddrV4 *)&(sock->remoteAddr))->sin_addr.s_addr;
    laddr = ((dctpSockaddrV4 *)&(sock->localAddr))->sin_addr.s_addr;
    if (laddr != 0) {
	hash = (raddr ^ laddr ^ sock->remoteAddr.sa_port ^ sock->localAddr.sa_port ^
		sock->encap) % DCTPSOCK_CONNECTEDHASHSIZE;
	sock->nextHash = connectedHash[hash];
	connectedHash[hash] = sock;
    } else {
	hash = (raddr ^ sock->remoteAddr.sa_port ^ sock->localAddr.sa_port ^
		sock->encap) % DCTPSOCK_HALFCONNECTEDHASHSIZE;
	sock->nextHash = halfConnectedHash[hash];
	halfConnectedHash[hash] = sock;
    }
}

/*
 * dctpiAddConnectedSocket -- add a connected socket to the connected hash
 */
void dctpiAddConnectedSocket(dctpSocket *sock) {
    switch (sock->encap) {
    case DCTPENCAP_NAT:
	addNatV4ConnectedSocket(sock);
	return;
    default:
	dctpoLog(DCTPLOG_ERR, "dctpiAddConnectedSocket: bad encap: %d\n", sock->encap);
	return;
    }
}

/*
 * dctpiAddRcvPacket -- add a received packet to a socket's receive queue
 */
int dctpiAddRcvPacket(dctpSocket *sock, dctpPacket *pkt) {
    if ((sock->rpktqlen + 1) > sock->mrpktqlen) {
	dataDropped(sock, pkt);
	return(-1);
    }
    ++sock->rpktqlen;
    pkt->nextPkt = NULL;
    if (sock->lastRpkt) {
	sock->lastRpkt->nextPkt = pkt;
    } else {
	sock->firstRpkt = pkt;
    }
    sock->lastRpkt = pkt;
    dctpoSocketSignal(sock);
    return(0);
}

/*
 * dctpiGetNextPacket -- get the next received packet from a socket's queue
 */
dctpPacket *dctpiGetNextPacket(dctpSocket *sock) {
    dctpPacket *pkt;

    if ((pkt = sock->firstRpkt) != NULL) {
	if ((sock->firstRpkt = pkt->nextPkt) == NULL) {
	    sock->lastRpkt = NULL;
	}
	--sock->rpktqlen;
    }
    return(pkt);
}

/*
 * dctpiGetNatV4IncomingSocket -- get the connected or listening socket
 * associated with a NATv4 incoming packet.
 */
dctpSocket *dctpiGetNatV4IncomingSocket(dctpPacket *pkt) {
    uint32_t saddr, daddr, hash;
    dctpSocket *sock;
    dctpInitCookie *cookie;

    saddr = *((uint32_t *)&(pkt->iphdr[16]));
    daddr = *((uint32_t *)&(pkt->iphdr[20]));
    /* Look for connected socket first */
    hash = (saddr ^ daddr ^ pkt->chdr.sport ^ pkt->chdr.dport ^ DCTPENCAP_NAT)
	% DCTPSOCK_CONNECTEDHASHSIZE;
    for (sock = connectedHash[hash]; sock; sock = sock->nextHash) {
	if ((((dctpSockaddrV4 *)&(sock->localAddr))->sin_addr.s_addr == daddr) &&
	    (((dctpSockaddrV4 *)&(sock->localAddr))->sin_port == pkt->chdr.dport) &&
	    (((dctpSockaddrV4 *)&(sock->remoteAddr))->sin_addr.s_addr == saddr) &&
	    (((dctpSockaddrV4 *)&(sock->remoteAddr))->sin_port == pkt->chdr.sport) &&
	    (sock->encap == DCTPENCAP_NAT)) {
	    /* Found it */
	    return(sock);
	}
    }
    /* Try half-connected */
    if (pkt->chdr.type == DCTPPKT_RESPONSE) {
	hash = (daddr ^ pkt->chdr.sport ^ pkt->chdr.dport ^ DCTPENCAP_NAT)
	    % DCTPSOCK_HALFCONNECTEDHASHSIZE;
	for (sock = halfConnectedHash[hash]; sock; sock = sock->nextHash) {
	    if ((((dctpSockaddrV4 *)&(sock->localAddr))->sin_port == pkt->chdr.dport) &&
		(((dctpSockaddrV4 *)&(sock->remoteAddr))->sin_addr.s_addr == saddr) &&
		(((dctpSockaddrV4 *)&(sock->remoteAddr))->sin_port == pkt->chdr.sport) &&
		(sock->encap == DCTPENCAP_NAT)) {
		/* Found it, remove from half connected and add to connected */
		rmHalfConnectedSock(sock);
		((dctpSockaddrV4 *)&(sock->localAddr))->sin_addr.s_addr = daddr;
		dctpiAddConnectedSocket(sock);
		return(sock);
	    }
	}
    }
    /* No connected socket, try listening */
    if (pkt->chdr.type == DCTPPKT_REQUEST) {
	hash = (pkt->chdr.dport ^ pkt->chdr.scode ^ DCTPENCAP_NAT) % DCTPSOCK_LISTENHASHSIZE;
	for (sock = listenHash[hash]; sock; sock = sock->nextHash) {
	    saddr = ((dctpSockaddrV4 *)&(sock->localAddr))->sin_addr.s_addr;
	    if ((pkt->chdr.scode == sock->scode) &&
		((saddr == 0) ||(saddr == daddr)) &&
		(pkt->chdr.dport == sock->localAddr.sa_port) &&
		(sock->encap == DCTPENCAP_NAT)) {
		/* Found it */
		return(sock);
	    }
	}
    } else if ((cookie = getInitCookie(pkt)) != NULL) {
	sock = cookie->sock;
	sock->cookie = cookie;
	return(sock);
    }
    return(NULL);
}

/*
 * dctpiCloneSocket -- make a new socket from a listening socket
 */
dctpSocket *dctpiCloneSocket(dctpSocket *sock, dctpPacket *pkt) {
    dctpSocket *nsock;
    dctpSockaddrV4 *v4addr;

    if ((nsock = dctpoMalloc(sizeof(dctpSocket))) == NULL) {
	return(NULL);
    }
    dctpoMemcpy(nsock, sock, sizeof(dctpSocket));
    switch (nsock->encap) {
    case DCTPENCAP_NAT:
	v4addr = (dctpSockaddrV4 *)&(nsock->localAddr);
	v4addr->sin_addr.s_addr = *((uint32_t *)&(pkt->iphdr[20]));
	v4addr = (dctpSockaddrV4 *)&(nsock->remoteAddr);
	v4addr->sin_family = DCTP_AF_INET;
	v4addr->sin_port = pkt->chdr.sport;
	v4addr->sin_addr.s_addr = *((uint32_t *)&(pkt->iphdr[16]));
	break;
    case DCTPENCAP_RAW:
    case DCTPENCAP_NAT6:
    case DCTPENCAP_RAW6:
	/* Do these encaps later */
    default:
	dctpoLog(DCTPLOG_ERR, "dctpiCloneSocket: invalid encap: %d\n", nsock->encap);
	dctpoFree(nsock);
	return(NULL);
    }
    nsock->nextHash = NULL;
    nsock->nextWaiting = NULL;
    nsock->firstWaiting = NULL;
    nsock->lastWaiting = NULL;
    nsock->cookie = NULL;
    nsock->parent = sock;
    dctpoSocketLockInit(nsock);
    dctpiAddConnectedSocket(nsock);
    return(nsock);
}

/*
 * dctpiDestroySock -- destroy (free) a socket
 */
void dctpiDestroySock(dctpSocket *sock) {
    dctpPacket *pkt;
    dctpSocket *sk, *nxt;

    dctpoLog(DCTPLOG_DEBUG, "dctpiDestroySock: destroying sock 0x%x, server %d\n",
	     (uint_t)sock, sock->server);
    /* Remove socket from listening or connected hashes */
    if (sock->state == DCTPSOCK_LISTEN) {
	rmListenSock(sock);
    } else {
	rmConnectedSock(sock);
    }
    for (pkt = sock->firstXpkt; pkt; pkt = pkt->nextPkt) {
	dctpoPacketFree(pkt);
    }
    for (pkt = sock->firstRpkt; pkt; pkt = pkt->nextPkt) {
	dctpoPacketFree(pkt);
    }
    for (sk = sock->firstWaiting; sk; sk = nxt) {
	nxt = sk->nextWaiting;
	dctpaClose(sk, NULL);
    }
    if (sock->cookie) {
	dctpoFree(sock->cookie);
    }
    dctpoStopTimer(&(sock->timer));
    dctpoStopTimer(&(sock->featTimer));
    if (!sock->server && (sock->localAddr.sa_port != 0)) {
	dctpiFreePort(sock->encap, sock->localAddr.sa_port);
    }
    sock->destroyed = 1;
    dctpiDelayedFree(&(sock->fdelay), sock, sock->fdelayTime);
}

static void dataDropped(dctpSocket *sock, dctpPacket *pkt) {
    dctpoLog(DCTPLOG_DEBUG, "dataDropped: packet dropped, sock->server = %d\n",
	     sock->server);
    /* Fill in later */
}

static dctpInitCookie *getInitCookie(dctpPacket *pkt) {
    /* Fill in later */
    return(NULL);
}

static void delayedFree(void *arg) {
    dctpoFree(arg);
}

/*
 * dctpiDelayedFree -- free memory after a delay (in seconds)
 */
void dctpiDelayedFree(dctpTimer *t, void *arg, uint32_t delay) {
    if (delay == 0) delay = 1;
    delay *= 1000000;   /* Convert to microseconds */
    dctpoStartTimer(t, delay, delayedFree, arg);
}

