/*
 * dctpLprocessUApi.c -- User-side API for Lprocess 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include "dctpCore.h"
#include "dctpSupport.h"
#include "dctpRawApi.h"
#include "dctpLprocessPApi.h"
#include "dctpLprocessApi.h"

static int rspsd = -1;   /* Socket descriptor for response socket */

static struct sockaddr_un caddr = {  /* Address of command socket */
    sun_family: AF_UNIX,
    sun_path:   DCTPLP_CMDSOCKNAME
};

static struct sockaddr_un raddr;     /* Address of our response socket */

/*
 * Send a command followed by variable-length data
 */
static int writeCmdMsg(void *cmd, int csize, void *data, int dsize) {
    struct msghdr mhdr;
    struct iovec iov[2];

    ((dctpLpGenCmd *)cmd)->pid = getpid();
    iov[0].iov_base = cmd;
    iov[0].iov_len = csize;
    iov[1].iov_base = data;
    iov[1].iov_len = dsize;
    mhdr.msg_name = &caddr;
    mhdr.msg_namelen = sizeof(caddr);
    mhdr.msg_iov = iov;
    mhdr.msg_iovlen = 2;
    mhdr.msg_control = NULL;
    mhdr.msg_controllen = 0;
    mhdr.msg_flags = 0;
    return(sendmsg(rspsd, &mhdr, 0));
}

/*
 * Send a fixed-length command
 */
static int writeCmd(void *cmd, int size) {
    ((dctpLpGenCmd *)cmd)->pid = getpid();
    return(sendto(rspsd, cmd, size, 0, (struct sockaddr *)&caddr, sizeof(caddr)));
}

/*
 * Read a response that contains variable-length data
 */
static int readRspMsg(void *cmd, int csize, void *data, int dsize) {
    struct msghdr mhdr;
    struct iovec iov[2];

    iov[0].iov_base = cmd;
    iov[0].iov_len = csize;
    iov[1].iov_base = data;
    iov[1].iov_len = dsize;
    mhdr.msg_name = &raddr;
    mhdr.msg_namelen = sizeof(raddr);
    mhdr.msg_iov = iov;
    mhdr.msg_iovlen = 2;
    mhdr.msg_control = NULL;
    mhdr.msg_controllen = 0;
    mhdr.msg_flags = 0;
    return(recvmsg(rspsd, &mhdr, 0));
}

/*
 * Read a fixed-length response
 */
static int readRsp(void *rsp, int size) {
    return(recv(rspsd, rsp, size, 0));
}

/*
 * Open our response socket
 */
static int openService(void) {
    if ((rspsd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) {
	return(-1);
    }
    raddr.sun_family = AF_UNIX;
    sprintf(raddr.sun_path, DCTPLP_RSPSOCKNAME, getpid());
    if (bind(rspsd, (struct sockaddr *)&raddr, sizeof(raddr)) < 0) {
	close(rspsd);
	rspsd = -1;
	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(htonl(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(htonl(scode));
	    } else {
		return(DCTP_INVALIDSCODE);
	    }
	} else {
	    scode = strtol(&scodetxt[3], &endptr, 10);
	    if ((scodetxt[3] != 0) && (*endptr == 0)) {
		return(htonl(scode));
	    } else {
		return(DCTP_INVALIDSCODE);
	    }
	}
    } else {
	return(DCTP_INVALIDSCODE);
    }
}

int dctpMkSocket(int domain, int type, char *scodetxt) {
    dctpLpSocketCmd cmd;
    dctpLpSocketRsp rsp;
    uint32_t scode;

    if ((domain != DCTP_PF_INET) && (domain != DCTP_PF_INET6)) {
	errno = EINVAL;
	return(-1);
    }
    if ((type != DCTPLP_SOCKRAW) && (type != DCTPLP_SOCKNAT)) {
	errno = EPROTONOSUPPORT;
	return(-1);
    }
    scode = convertScodetxt(scodetxt);
    if (scode == 0xffffffff) {
	errno = EINVAL;
	return(-1);
    }
    if ((rspsd < 0) && (openService() < 0)) {
	/* errno set by openService */
	return(-1);
    }
    cmd.cmd = DCTPLP_OPENCMD;
    cmd.domain = domain;
    cmd.type = type;
    cmd.scode = scode;
    if (writeCmd(&cmd, sizeof(cmd)) < 0) {
	return(-1);
    }
    if (readRsp(&rsp, sizeof(rsp)) < 0) {
	return(-1);
    }
    if (rsp.cmd == DCTPLP_NACKRSP) {
	errno = rsp.rsperrno;
	return(-1);
    }
    return(rsp.sd);
}

int dctpClose(int sd) {
    dctpLpGenCmd cmd;
    dctpLpAckRsp rsp;

    cmd.cmd = DCTPLP_CLOSECMD;
    cmd.sd = sd;
    writeCmd(&cmd, sizeof(cmd));
    readRsp(&rsp, sizeof(rsp));
    return(0);
}

int dctpBind(int sd, dctpSockaddr *addr) {
    dctpLpBindCmd cmd;
    dctpLpAckRsp rsp;

    cmd.cmd = DCTPLP_BINDCMD;
    cmd.sd = sd;
    memcpy(&(cmd.addr), addr, sizeof(dctpSockaddr));
    if (writeCmd(&cmd, sizeof(cmd)) < 0) {
	return(-1);
    }
    if (readRsp(&rsp, sizeof(rsp)) < 0) {
	return(-1);
    }
    if (rsp.cmd == DCTPLP_NACKRSP) {
	errno = rsp.rsperrno;
	return(-1);
    }
    return(0);
}

int dctpListen(int sd,int backlog) {
    dctpLpListenCmd cmd;
    dctpLpAckRsp rsp;

    cmd.cmd = DCTPLP_LISTENCMD;
    cmd.sd = sd;
    cmd.backlog = backlog;
    if (writeCmd(&cmd, sizeof(cmd)) < 0) {
	return(-1);
    }
    if (readRsp(&rsp, sizeof(rsp)) < 0) {
	return(-1);
    }
    if (rsp.cmd == DCTPLP_NACKRSP) {
	errno = rsp.rsperrno;
	return(-1);
    }
    return(0);
}

int dctpAccept(int sd, dctpSockaddr *addr) {
    dctpLpGenCmd cmd;
    dctpLpAcceptRsp rsp;

    cmd.cmd = DCTPLP_ACCEPTCMD;
    cmd.sd = sd;
    if (writeCmd(&cmd, sizeof(cmd)) < 0) {
	return(-1);
    }
    if (readRsp(&rsp, sizeof(rsp)) < 0) {
	return(-1);
    }
    if (rsp.cmd == DCTPLP_NACKRSP) {
	errno = rsp.rsperrno;
	return(-1);
    }
    memcpy(addr, &(rsp.addr), sizeof(dctpSockaddr));
    return(rsp.nsd);
}

int dctpConnect(int sd, dctpSockaddr *addr, void *buf, int blen) {
    dctpLpConnectCmd cmd;
    dctpLpAckRsp rsp;

    if (blen > DCTPLP_MAXPKTSIZE) {
	errno = E2BIG;
	return(-1);
    }
    cmd.cmd = DCTPLP_CONNECTCMD;
    cmd.sd = sd;
    memcpy(&(cmd.addr), addr, sizeof(dctpSockaddr));
    if (blen == 0) {
	if (writeCmd(&cmd, sizeof(cmd)) < 0) {
	    return(-1);
	}
    } else if (writeCmdMsg(&cmd, sizeof(cmd), buf, blen) < 0) {
	return(-1);
    }
    if (readRsp(&rsp, sizeof(rsp)) < 0) {
	return(-1);
    }
    if (rsp.cmd == DCTPLP_NACKRSP) {
	errno = rsp.rsperrno;
	return(-1);
    }
    return(0);
}

int dctpSend(int sd, void *msg, int mlen) {
    dctpLpGenCmd cmd;
    dctpLpAckRsp rsp;

    if (mlen > DCTPLP_MAXPKTSIZE) {
	errno = E2BIG;
	return(-1);
    }
    cmd.cmd = DCTPLP_SENDCMD;
    cmd.sd = sd;
    if (writeCmdMsg(&cmd, sizeof(cmd), msg, mlen) < 0) {
	return(-1);
    }
    if (readRsp(&rsp, sizeof(rsp)) < 0) {
	return(-1);
    }
    if (rsp.cmd == DCTPLP_NACKRSP) {
	errno = rsp.rsperrno;
	return(-1);
    }
    return(mlen);
}

int dctpRecv(int sd, void *buf, int blen) {
    dctpLpGenCmd cmd;
    dctpLpRecvRsp rsp;

    cmd.cmd = DCTPLP_RCVCMD;
    cmd.sd = sd;
    if (writeCmd(&cmd, sizeof(cmd)) < 0) {
	return(-1);
    }
    if (readRspMsg(&rsp, sizeof(rsp), buf, blen) < 0) {
	return(-1);
    }
    if (rsp.cmd == DCTPLP_NACKRSP) {
	errno = rsp.rsperrno;
	return(-1);
    }
    return(rsp.mlen);
}

int dctpSetSockopt(int sd, int optname, void *optval, int optlen, int mandatory) {
    dctpLpSetOptCmd cmd;
    dctpLpAckRsp rsp;

    cmd.cmd = DCTPLP_SETOPTCMD;
    cmd.sd = sd;
    cmd.optname = optname;
    cmd.optlen = (optlen > DCTPLP_SETOPTMAX) ? DCTPLP_SETOPTMAX : optlen;
    cmd.mandatory = mandatory;
    memcpy(cmd.optval, optval, cmd.optlen);
    if (writeCmd(&cmd, sizeof(cmd) - (DCTPLP_SETOPTMAX - cmd.optlen)) < 0) {
	return(-1);
    }
    if (readRsp(&rsp, sizeof(rsp)) < 0) {
	return(-1);
    }
    if (rsp.cmd == DCTPLP_NACKRSP) {
	errno = rsp.rsperrno;
	return(-1);
    }
    return(0);
}

