/*
 * $Header: /san01/cvs/ashpool/csrc/continuity/mecha/Attic/net.c,v 1.14 2004/05/27 13:32:22 aleigh Exp $ 
 */

/*
 * Copyright (c) 2004 Alex Leigh, All Rights Reserved
 * 
 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF ALEX LEIGH The copyright
 * notice above does not evidence any actual or intended publication of such
 * source code.
 * 
 * Use, disclosure, copying, distribution, or transmission of this code without
 * express permission is prohibited.
 */

#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>
#include <netdb.h>

#include "mecha.h"
#include "net.h"

#define EAGAIN_TRYCOUNT 28
#define EAGAIN_WAIT 15

ssize_t netFconn_writev(netTconn * conn, const struct iovec * iov,int iovcnt);

int netFset_reuse(int sd)
{
    int reuse = 1;

    if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) !=
        0) {
        return -1;
    }
    return 0;
}

int netFset_nodelay(int sd)
{
    int opt = 1;
//    if (setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)) != 0)
//        return -1;
    return 0;
}

int netFipv4_listen_tcp(int port, unsigned int ip, int maxconnect)
{
    int newport;
    struct sockaddr_in dest;
    int on = 1;

    newport = socket(AF_INET, SOCK_STREAM, 6);
    if (newport == -1) {
        logFmsg(2, "netFipv4_listen_tcp: socket failed [e: %s]",
                strerror(errno));
        return -1;
    }
    dest.sin_family = AF_INET;
    dest.sin_port = htons(port);
    dest.sin_addr.s_addr = htonl(ip);

    fleFset_close_exec(newport);

    setsockopt(newport, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    if (bind(newport, (struct sockaddr *) &dest, sizeof(dest)) == -1) {
        logFmsg(2, "netFipv4_listen_tcp: bind failed: %s",
                strerror(errno));
        return (-1);
    }
    listen(newport, maxconnect);

    return (newport);
}

static ssize_t netFconn_reader_read(netTconn * conn, netTreader * reader)
{
    ssize_t ret;

    bzero(reader->buf, READER_CHUNK_SZ);

    thrFtrigger_set(10, conn->sd);

    ret = conn->io_read(conn, reader->buf, READER_CHUNK_SZ);

    thrFtrigger_clear();

    if (ret == -1)
        return -1;

    reader->len = ret;

    reader->pos = 0;

    return reader->len;
}

/*
 * Reads and writes into buf until either len-1 occurs, or a newline is found
 * in the input sd. Always null terminates and returns the number of bytes
 * written including the null.
 */

ssize_t netFconn_read_line(netTconn * conn, netTreader * reader,
                           char *buf, ssize_t len)
{
    char *p;
    ssize_t i, ret;

    assert(conn != NULL);
    assert(reader != NULL);
    assert(buf != NULL);
    assert(len > 1);

    p = reader->buf + reader->pos;

    for (i = 0; i < len; i++) {
        if ((reader->len == 0) || (reader->pos > (reader->len - 1))) {
            /* No data in the buffer; read. */
            ret = netFconn_reader_read(conn, reader);

            /* error trap / null read trap */
            if (ret == -1 || ret == 0) {
                return i;
            }
            p = reader->buf + reader->pos;
        }
        if (*p == '\n') {
            reader->pos++;

            /* lets go back one to the newline */
            i--;
            buf[i] = '\0';      /* Null terminate the string */
            return i;
        }
        if (*p != '\r')
            buf[i] = *p;

        p++;
        reader->pos++;

    }
    return i;
}

int netFisready(int fd)
{
    int rc;
    fd_set fds;
    struct timeval tv;

    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    rc = select(fd + 1, &fds, NULL, NULL, &tv);
    if (rc < 0)
        return -1;

    return FD_ISSET(fd, &fds) ? 1 : 0;
}

int netFtcp_connect(char *addr, unsigned int port)
{
    int sock;
    struct sockaddr_in sa;
    struct hostent *hp;

    bzero((char *) &sa, sizeof(struct sockaddr_in));
    if (!(hp = gethostbyname(addr))) {
        logFmsg(1, "gethostbyname failed.");
        return -1;
    }
    sa.sin_family = AF_INET;
    memcpy((char *) &sa.sin_addr, hp->h_addr_list[0], hp->h_length);
    sa.sin_port = htons(port);

    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        logFmsg(1, "socket [a: %s] [e: %s]",
                addr, strerror(errno));
        return -1;
    }
    fleFset_close_exec(sock);

    if (connect(sock, (struct sockaddr *) &sa,
                sizeof(struct sockaddr_in)) < 0) {
        close(sock);
        logFmsg(1, "connect failed: %s", strerror(errno));

        return -1;
    }
    return sock;
}

int netFconn_close(netTconn *conn) {
	return conn->io_close(conn);
}

ssize_t netFconn_read(netTconn * conn, netTreader * reader,
                      char *buf, ssize_t len)
{
    char *p;
    ssize_t i, ret;

    p = reader->buf + reader->pos;

    for (i = 0; i < len; i++) {
        if ((reader->len == 0) || (reader->pos > (reader->len - 1))) {
            /* No data in the buffer; read. */
            ret = netFconn_reader_read(conn, reader);

            /* error trap / null read trap */
            if (ret == -1 || ret == 0) {
                return i;
            }
            p = reader->buf + reader->pos;
        }
        buf[i] = *p;
        p++;
        reader->pos++;
    }
    return i;
}

ssize_t netFconn_write_two(netTconn * conn, const char *buf1,
                           size_t buf1_len, const char *buf2,
                           size_t buf2_len)
{
    struct iovec io[2];
    ssize_t ret;

    io[0].iov_base = (char *) buf1;
    io[0].iov_len = buf1_len;
    io[1].iov_base = (char *) buf2;
    io[1].iov_len = buf2_len;

    ret = conn->io_writev(conn, io, 2);

    if (ret > (ssize_t)buf1_len)
        return ret - buf1_len;
    else
        return ret;
}

ssize_t netFconn_writev(netTconn * conn, const struct iovec * iov,
                        int iovcnt)
{
    ssize_t ret = conn->io_writev(conn, iov, iovcnt);
    return ret;
}

ssize_t netFconn_write(netTconn * conn, const char *buf, ssize_t len)
{
    const char *bp = buf;
    size_t blen = len;
    ssize_t ret;
    int tc = 0;
    ssize_t written = 0;

    while (blen != 0) {
        while ((ret = conn->io_write(conn, bp, blen)) == -1) {
            if (errno == EAGAIN || errno == EINTR) {
                if (errno == EPIPE) {
                    return -1;
                }
                if (tc == EAGAIN_TRYCOUNT) {
                    /*
                     * We have tried as many times as we are going to for
                     * this connection, so pass out an error and our caller
                     * can decide what to do. Register this as an EAGAIN
                     * failure.
                     */
                    return -1;
                }
                tc++;
                thrFsleep(EAGAIN_WAIT);
                continue;
            } else {
                return -1;
            }
        }
        blen -= ret;
        bp += ret;
        written += ret;
    }
    return written;
}

int netFudp_send(int sd, const void *msg, size_t msg_len, unsigned int port, unsigned int ip)
{
    struct sockaddr_in sa;
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = htonl(ip);
    sa.sin_port = htons(port);
    
    return sendto(sd, msg, msg_len, 0, (struct sockaddr *) &sa, sizeof sa);
}

int netFudp_recv(int sd, char *msg, size_t msg_len, struct sockaddr_in *sa, socklen_t * sa_len)
{
    return recvfrom(sd, msg, msg_len, 0, (struct sockaddr *) sa, sa_len);
}

/*
 * port - Writes back the port that is actually used. Must be 0. defport - A
 * default port in case it's not in services service - The service name to
 * look in services
 */
int netFudp_open(uint16_t * port, int defport, char *service)
{
    int fd;
    int result;
    struct servent *svp;
    struct sockaddr_in *sin_local;
    struct sockaddr_in salocal;
    uint16_t lport;

    /*
     * If port already has an assignment, use it. This provides a kind of
     * caching but requires that port be initialized to zero previous to this
     * call
     */

    if (*port > 5) {
        lport = htons(*port);
    } else {
        svp = getservbyname(service, "udp");
        if (svp != (struct servent *) NULL) {
            lport = (unsigned short) svp->s_port;
        } else {
            lport = htons(defport);
        }
        *port = ntohs(lport);
    }

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        logFmsg(2, "netFudp_open: socket error. [e: %d] [s: %s]", errno,
                service);
        return -1;
    }
    fleFset_close_exec(fd);

    sin_local = (struct sockaddr_in *) &salocal;
    memset((char *) sin_local, '\0', sizeof(salocal));
    sin_local->sin_family = AF_INET;
    sin_local->sin_addr.s_addr = INADDR_ANY;
    sin_local->sin_port = lport;

    result = bind(fd, (struct sockaddr *) &salocal, sizeof(*sin_local));
    if (result < 0) {
        logFmsg(2, "netFudp_open: bind error. [e: %d] [s: %s]", errno,
                service);
        return -1;
    }
    return fd;
}

/*
 * Create a socket for UDP Broadcast use.  Returns the socket or -1/errno on
 * error.
 */
int netFudp_bcast_connect(void)
{
    int sock;
    struct sockaddr_in sa;
    int on = 1;
    int fd_flags;

    bzero((char *) &sa, sizeof(struct sockaddr_in));

    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = htonl(INADDR_ANY);
    /* sa.sin_port=htons(port); random source port */

    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
        return -1;

    fleFset_close_exec(sock);

    if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &on, sizeof on) == -1)
        return -1;

    if ((fd_flags = fcntl(sock, F_GETFL)) == -1)
        return -1;
    fd_flags |= O_NONBLOCK;
    if (fcntl(sock, F_SETFL, fd_flags) == -1)
        return -1;

    if (bind(sock, (struct sockaddr *) &sa, sizeof sa) == -1)
        return -1;

    return sock;
}

int netFconn_writef(netTconn * conn, const char *fmt, ...)
{
    va_list ap;
    char *buf;
    int size = 1024;
    int e;

    buf = (char *) malloc(size);

    va_start(ap, fmt);

    while (1) {
        int nchars = vsnprintf(buf, size, fmt, ap);

        if (nchars > -1 && nchars < size)
            break;
        if (nchars > -1)
            size = nchars + 1;
        else
            size *= 2;
        buf = (char *) realloc(buf, size);
    }

    va_end(ap);

    if ((e = netFconn_write(conn, buf, strlen(buf))) == -1)
        e = -errno;

    free(buf);

    return e;
}

int _netFconn_writef(netTconn * conn, const char *fmt, va_list * ap)
{
    char *buf;
    int size = 1024;
    int e;

    buf = (char *) malloc(size);

    while (1) {
        int nchars = vsnprintf(buf, size, fmt, *ap);

        if (nchars > -1 && nchars < size)
            break;
        if (nchars > -1) {
            size = nchars + 1;
        } else {
            size *= 2;
        }

        buf = (char *) realloc(buf, size);
    }

    if ((e = netFconn_write(conn, buf, strlen(buf))) == -1) {
        e = -errno;
    }

    free(buf);

    return e;
}

netTreader *netFreader_init(void)
{
    netTreader *r;

    r = (netTreader *) malloc(sizeof(netTreader));
    bzero(r, sizeof(netTreader));

    return r;
}

void netFreader_free(netTreader * reader)
{
    free(reader);
}

ssize_t netFconn_os_read(netTconn * conn, void *buf, size_t len)
{
    return read(conn->sd, buf, len);
}

ssize_t netFconn_os_write(netTconn * conn, const void *buf, size_t len)
{
    size_t ret;

    thrFtrigger_set(10,conn->sd);
    ret=write(conn->sd, buf, len);
    thrFtrigger_clear();

    return ret;
}

int netFconn_os_close(netTconn * conn)
{
    return close(conn->sd);
}

ssize_t netFconn_os_writev(netTconn * conn, const struct iovec * iov,
                           int iovcnt)
{
    return writev(conn->sd, iov, iovcnt);
}

void netFconn_free(netTconn * conn)
{
    if (conn->data_free != NULL && conn->data != NULL)
        conn->data_free(conn->data);
    free(conn);
}

netTconn *netFconn_make(int sd)
{
    netTconn *conn = memFalloc(sizeof(netTconn));

    conn->sd = sd;
    conn->io_read = &netFconn_os_read;
    conn->io_write = &netFconn_os_write;
    conn->io_close = &netFconn_os_close;
    conn->io_writev = &netFconn_os_writev;
    conn->data_free = NULL;

    return conn;
}

/** IPv4 Multicast **/

#define MSGBUFSIZE 256

int netFipv4_mcast_create(unsigned int port, char *group)
{
    struct sockaddr_in addr;
    int sd;
    struct ip_mreq mreq;

    if((sd=socket(AF_INET,SOCK_DGRAM,0))<0) {
        logFmsg(1,"socket() failed. [e: %d]", errno);
        return -1;
    }
    
    netFset_reuse(sd);

    memFclear(&addr,sizeof(addr));
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=htonl(INADDR_ANY);
    addr.sin_port=htons(port);

    if(bind(sd,(struct sockaddr *)&addr,sizeof(addr))<0) {
        logFmsg(1,"bind() failed. [e: %d]", errno);
        return -1;
    }

    mreq.imr_multiaddr.s_addr=inet_addr(group);
    mreq.imr_interface.s_addr=htonl(INADDR_ANY);
    if (setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) {
        logFmsg(1,"setsockopt() 2 failed. [e: %d]", errno);
        return -1;
    }

    return sd;
}
