/*
 * dctpPthreadsApi.c -- User-level API for Pthreads port of dccp-tp
 *
 * 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 <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>
#include "dctpCore.h"
#include "dctpRawApi.h"
#include "dctpApi.h"
#include "dctpSupport.h"

#define DCTP_PTHREADS_MAXSOCKETS 32768

static dctpSocket *sockptrs[DCTP_PTHREADS_MAXSOCKETS];
static int sdallocs[DCTP_PTHREADS_MAXSOCKETS];
static int nextSockdes = 1;
pthread_mutex_t sockdesMutex = PTHREAD_MUTEX_INITIALIZER;

static int getSocketDescriptor(void) {
    int sd;

    pthread_mutex_lock(&sockdesMutex);
    sd = nextSockdes;
    if (sd < 0) {
	pthread_mutex_unlock(&sockdesMutex);
	return(-1);
    }
    sdallocs[sd] = 1;
    do {
	if (++nextSockdes >= DCTP_PTHREADS_MAXSOCKETS) nextSockdes = 1;
	if (nextSockdes == sd) {
	    nextSockdes = -1;
	    break;
	}
    } while (sdallocs[nextSockdes] == 1);
    pthread_mutex_unlock(&sockdesMutex);
    return(sd);
}

static void freeSocketDescriptor(int sd) {
    pthread_mutex_lock(&sockdesMutex);
    if (sockptrs[sd] != NULL) {
	dctpaClose(sockptrs[sd], &errno);
	sockptrs[sd] = NULL;
    }
    sdallocs[sd] = 0;
    if (nextSockdes < 0) nextSockdes = sd;
    pthread_mutex_unlock(&sockdesMutex);
}

static int badsd(int sd) {
    if ((sd <= 0) || (sd >= DCTP_PTHREADS_MAXSOCKETS) || (sockptrs[sd] == NULL)) {
	errno = EBADF;
	return(-1);
    }
    return(0);
}

static int badaddr(dctpSocket *sock, dctpSockaddr *addr, int addrlen) {
    switch (sock->encap) {
    case DCTPENCAP_RAW:
    case DCTPENCAP_NAT:
	if ((addrlen < sizeof(dctpSockaddrV4)) || (addr->sa_family != DCTP_AF_INET)) {
	    errno = EINVAL;
	    return(-1);
	}
	break;
    case DCTPENCAP_RAW6:
    case DCTPENCAP_NAT6:
	if ((addrlen < sizeof(dctpSockaddrV6)) || (addr->sa_family != DCTP_AF_INET6)) {
	    errno = EINVAL;
	    return(-1);
	}
	break;
    default:
	errno = EINVAL;
	return(-1);
    }
    return(0);
}

static uint32_t convertScodetxt(char *scodetxt) {
    uint_t codelen = strlen(scodetxt);
    uint32_t scode;
    uint_t i;
    char *endptr;

    if ((codelen < DCTP_MINSCODETXTLEN) || (codelen > DCTP_MAXSCODETXTLEN)) {
	return(DCTP_INVALIDSCODE);
    } else if ((scodetxt[0] != 'S') || (scodetxt[1] != 'C')) {
	return(DCTP_INVALIDSCODE);
    } else if (scodetxt[2] == ':') {
	if (codelen > DCTP_MAXASCIISCODELEN) {
	    return(DCTP_INVALIDSCODE);
	}
	scode = 0;
	for (i = 3; i < DCTP_MAXASCIISCODELEN; ++i) {
	    if (i >= codelen) {
		scode = (scode << 8) | ' ';
	    } else if ((scodetxt[i] == 42) || (scodetxt[i] == 43) ||
		       ((scodetxt[i] >= 45) && (scodetxt[i] <= 57)) ||
		       ((scodetxt[i] >= 63) && (scodetxt[i] <= 90)) ||
		       (scodetxt[i] == 95) ||
		       ((scodetxt[i] >= 97) && (scodetxt[i] <= 122))) {
		scode = (scode << 8) | scodetxt[i];
	    } else {
		return(DCTP_INVALIDSCODE);
	    }
	}
	return(dctpoHtonl(scode));
    } else if (scodetxt[2] == '=') {
	if ((scodetxt[3] == 'x') || (scodetxt[3] == 'X')) {
	    scode = strtol(&scodetxt[4], &endptr, 16);
	    if ((scodetxt[4] != 0) && (*endptr == 0)) {
		return(dctpoHtonl(scode));
	    } else {
		return(DCTP_INVALIDSCODE);
	    }
	} else {
	    scode = strtol(&scodetxt[3], &endptr, 10);
	    if ((scodetxt[3] != 0) && (*endptr == 0)) {
		return(dctpoHtonl(scode));
	    } else {
		return(DCTP_INVALIDSCODE);
	    }
	}
    } else {
	return(DCTP_INVALIDSCODE);
    }
}

int dccpSocket(int domain, int type, char *scodetxt) {
    int sd;
    dctpSocket *sock;
    int encap;
    uint32_t scode;

    if ((domain != DCTP_PF_INET) && (domain != DCTP_PF_INET6)) {
	errno = EINVAL;
	return(-1);
    }
    if ((type != SOCK_DCCP_RAW) && (type != SOCK_DCCP_NAT)) {
	errno = EPROTONOSUPPORT;
	return(-1);
    }
    scode = convertScodetxt(scodetxt);
    if (scode == 0xffffffff) {
	errno = EINVAL;
	return(-1);
    }
    if ((sd = getSocketDescriptor()) < 0) {
	errno = ENFILE;
	return(-1);
    }
    if (domain == DCTP_PF_INET) encap = (type == SOCK_DCCP_RAW) ? DCTPENCAP_RAW : DCTPENCAP_NAT;
    else encap = (type == SOCK_DCCP_RAW) ? DCTPENCAP_RAW6 : DCTPENCAP_NAT6;
    if ((sock = dctpaSocket(encap, scode, &errno)) == NULL) {
	freeSocketDescriptor(sd);
	return(-1);
    }
    sockptrs[sd] = sock;
    return(sd);
}

int dccpBind(int sd, dctpSockaddr *addr, int addrlen) {
    if (badsd(sd)) return(-1);
    if (badaddr(sockptrs[sd], addr, addrlen)) return(-1);
    return(dctpaBind(sockptrs[sd], addr, &errno));
}

int dccpListen(int sd, int backlog) {
    if (badsd(sd)) return(-1);
    return(dctpaListen(sockptrs[sd], backlog, &errno));
}

int dccpAccept(int sd, dctpSockaddr *addr, int *addrlen) {
    int nsd;
    dctpSocket *nsock;

    if (badsd(sd)) return(-1);
    if ((nsock = dctpaAccept(sockptrs[sd], addr, *addrlen, &errno)) == NULL) {
	return(-1);
    }
    if ((nsd = getSocketDescriptor()) < 0) {
	dctpaClose(nsock, &errno);
	errno = ENFILE;
	return(-1);
    }
    sockptrs[nsd] = nsock;
    return(nsd);
}

int dccpConnect(int sd, dctpSockaddr *addr, int addrlen, void *buf, int blen) {
    dctpPacket *data = NULL;

    if (badsd(sd)) return(-1);
    if (badaddr(sockptrs[sd], addr, addrlen)) return(-1);
    if (buf && blen) {
	if ((data = dctpoPacketMalloc(blen)) != NULL) {
	    memcpy(data->appdata, buf, blen);
	    data->appdatalen = blen;
	} else {
	    errno = ENOMEM;
	    return(-1);
	}
    }
    if (dctpaConnect(sockptrs[sd], addr, data, &errno) < 0) {
	if (data) dctpoPacketFree(data);
	return(-1);
    }
    return(0);
}

int dccpSend(int sd, void *buf, int blen) {
    dctpPacket *data;

    if (badsd(sd)) return(-1);
    if ((data = dctpoPacketMalloc(blen)) == NULL) {
	errno = ENOMEM;
	return(-1);
    }
    memcpy(data->appdata, buf, blen);
    data->appdatalen = blen;
    if (dctpaSend(sockptrs[sd], data, &errno) < 0) {
	dctpoPacketFree(data);
	return(-1);
    }
    return(0);
}

int dccpRecv(int sd, void *buf, uint_t blen) {
    dctpPacket *pkt;
    uint_t len;

    if (badsd(sd)) return(-1);
    if (dctpaRecv(sockptrs[sd], &pkt, &errno) < 0) return(-1);
    len = (pkt->appdatalen > blen) ? blen : pkt->appdatalen;
    dctpoPacketCopyFrom(pkt, 0, (uint8_t *)buf, len);
    dctpoPacketFree(pkt);
    return(len);
}

int dccpClose(int sd) {
    if (badsd(sd)) return(-1);
    pthread_mutex_lock(&sockdesMutex);
    sdallocs[sd] = 0;
    pthread_mutex_unlock(&sockdesMutex);
    return(dctpaClose(sockptrs[sd], &errno));
}

int dccpSetSockopt(int sd, int opt, void *opval, uint_t oplen) {
    if (badsd(sd)) return(-1);
    return(dctpaSetSockopt(sockptrs[sd], opt, 0, opval, oplen, &errno));
}

