/*
 * socklib.c
 *
 * Handy functions for IPv4 socket connections.
 *
 * History:
 *      28/01/2014 Creation
 *      27/05/2014 New API.
 *      28/05/2014 Implement the ipv4_* functions.
 *
 * Author: Dario Rodriguez dario@softhome.net
 * This file is licensed under the terms of the GNU LGPL v2+
 */

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/ioctl.h>

#include "socklib.h"

typedef struct _sselect {
        int dummy;
} _sselect;


#ifndef FILLTV
#define FILLTV(tv,sec,usec) tv.tv_sec=sec,tv.tv_usec=usec
#endif

#ifndef FILLIPV4ADDR
#define FILLIPV4ADDR(a,family,addr,port) \
                memset(&(a),0,sizeof(struct sockaddr_in)),\
                s.sin_family=family,\
                s.sin_addr.s_addr=addr,\
                s.sin_port=htons(port)
#endif

char *
ipv4_genip(char *hostname, long *resulthostsize)
{
        struct addrinfo hints,*ai;
        struct sockaddr_in *in;
        char *host;
        memset(&hints,0,sizeof(hints));
        hints.ai_family=AF_INET;
        if(getaddrinfo(hostname,NULL,&hints,&ai)!=0)
                return(NULL);
        in=(struct sockaddr_in *)ai->ai_addr;
        if(in==NULL || (host=malloc(ai->ai_addrlen))==NULL) {
                freeaddrinfo(ai),ai=NULL;
                return(NULL);
        }
        memcpy(host,ai->ai_addr,ai->ai_addrlen);
        *resulthostsize=ai->ai_addrlen;
        freeaddrinfo(ai),ai=NULL;
        return(host);
}

int
ipv4_genport(char *portname, int fallback)
{
        struct addrinfo hints,*ai;
        struct sockaddr_in *in;
        char port;
        if(*portname>='0' && *portname<='9')
                return(atoi(portname));
        memset(&hints,0,sizeof(hints));
        hints.ai_family=AF_INET;
        if(getaddrinfo(NULL,portname,&hints,&ai)!=0)
                return(fallback);
        if((in=((struct sockaddr_in *)ai->ai_addr))==NULL) {
                freeaddrinfo(ai),ai=NULL;
                return(fallback);
        }
        port=ntohs(in->sin_port);
        freeaddrinfo(ai),ai=NULL;
        return(port);
}

int
ipv4_preconnect(char *host, long hostsize, int port) /* setup socket, set non-blocking, connect(2) call */
{
        struct sockaddr_in c,s;
        int fd;
        int res,err;
        FILLIPV4ADDR(c,AF_INET,htonl(INADDR_ANY),0);
        FILLIPV4ADDR(c,AF_INET,htonl(INADDR_ANY),port);
        memcpy(&(s.sin_addr.s_addr),host,hostsize);
        if((fd=socket(AF_INET,SOCK_STREAM,0 /* any protocol */))==-1)
                return(-1);
        if(bind(fd,(struct sockaddr *)&c,sizeof(c))!=0) {
                close(fd),fd=-1;
                return(-1);
        }
        sock_setblocking(fd,0);
        res=connect(fd,(struct sockaddr *)&s,sizeof(s));
        err=errno;
        if(res==-1 && err!=EINPROGRESS) {
                close(fd),fd=-1;
                return(-1);
        }
        return(fd);
}

int
ipv4_connect(int fd, long timeoutmsec) /* tests writeability */
{
        struct timeval tv;
        fd_set wset;
        FILLTV(tv,timeoutmsec/1000L,(timeoutmsec%(1000L))*1000L);
        FD_ZERO(&wset);
        FD_SET(fd,&wset);
        if(select(fd+1,NULL,&wset,NULL,&tv)>0)
                return(0);
        return(-1);
}

int
ipv4_postconnect(int fd) /* hopefully connect(2) suceeded, set blocking */
{
        sock_setblocking(fd,1);
        return(0);
}

int
ipv4_server(int port)
{
        return(ipv4_serverbinded(NULL,0,port));
}

int
ipv4_serverbinded(char *host, long hostsize, int port)
{
        struct sockaddr_in s;
        int fd;
        if((fd=socket(AF_INET,SOCK_STREAM,0 /* any protocol */))==-1)
                return(-1);
        FILLIPV4ADDR(s,AF_INET,htonl(INADDR_ANY),port);
        if(host!=NULL)
                memcpy(&(s.sin_addr.s_addr),host,hostsize);
        if(bind(fd,(struct sockaddr *)&s,sizeof(s))!=0) {
                close(fd),fd=-1;
                return(-1);
        }
        if(listen(fd,4)==-1) {
                close(fd),fd=-1;
                return(-1);
        }
        return(fd);
}

int
sock_accept(int fd)
{
        int newfd;
        struct sockaddr_in s;
        socklen_t slen;
        slen=sizeof(s);
        if(fd==-1 || (newfd=accept(fd,(struct sockaddr *)&s,&slen))==-1)
                return(-1);
        return(newfd);
}

int
sock_getinfo(int fd, int *iplen, char *ip, int *port) /* ip must be at least 16 bytes to have room for an ipv6 address */
{
        struct sockaddr c;
        struct sockaddr_in *c4;
        struct sockaddr_in6 *c6;
        socklen_t clen;
        if(fd==-1)
                return(-1);
        clen=sizeof(c);
        if(getsockname(fd,(struct sockaddr *)&c,&clen)==-1)
                return(-1);
        if(c.sa_family!=AF_INET && c.sa_family!=AF_INET6)
                return(-1);
        if(c.sa_family==AF_INET) {
                c4=(struct sockaddr_in *) &c;
                *iplen=sizeof(c4->sin_addr.s_addr);
                memcpy(ip,&(c4->sin_addr.s_addr),*iplen);
                *port=ntohs(c4->sin_port);
        } else {
                c6=(struct sockaddr_in6 *) &c;
                *iplen=sizeof(c6->sin6_addr);
                memcpy(ip,&(c6->sin6_addr),*iplen);
                *port=ntohs(c6->sin6_port);
        }
        return(0);
}

int
sock_queued(int fd)
{
        int n;
        if(ioctl(fd,FIONREAD,&n)!=0)
                return(-1);
        return(n);
}

int
sock_setblocking(int fd, int block)
{
        int fl;
        if((fl=fcntl(fd,F_GETFL,0))==-1)
                return(-1);
        fl=(block)?(fl&(~O_NONBLOCK)):(fl|O_NONBLOCK);
        return(fcntl(fd,F_SETFL,fl));
}

sselect *sselect_init(void);
int sselect_reset(sselect *ssel);
int sselect_addread(sselect *ssel, int fd);
int sselect_addwrite(sselect *ssel, int fd);
int sselect_delread(sselect *ssel, int fd);
int sselect_delwrite(sselect *ssel, int fd);
int sselect_wait(sselect *ssel, int ms);
int sselect_getread(sselect *ssel, int *fds, int sizefds);
int sselect_getwrite(sselect *ssel, int *fds, int sizefds);


/* aux functions */
void
sock_setfast(int fd)
{
        int val;
        val=1;
        setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(void *)&val,sizeof(val));
}

void
sock_setunsafe(int fd)
{
        int val;
        struct linger ltime;
        val=1;
        setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(void *)&val,sizeof(val));
        ltime.l_onoff=1;
        ltime.l_linger=0;
        setsockopt(fd,SOL_SOCKET,SO_LINGER,(void *)&ltime,sizeof(ltime));
}