/*
 * urusaiserver.c
 *
 * UDP notificator server implementing simple messaging.
 *
 * Author: Dario Rodriguez dario@softhome.net
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <time.h>
#include <errno.h>

#define SERVERPORT 20245
#define BUFLEN 2048
#define MAXID 32
#define CLIENTBLOCK 128
#define MAXPACKETSIZE 1300

typedef struct client {
        struct sockaddr_in addr;
        time_t lastrecv;
        char id[MAXID];
} client;

typedef struct urusai {
        int fd;
        struct sockaddr_in myaddr;
        int numclients;
        int sizeclients;
        client *clients;
} urusai;

typedef enum seenaction {
	seen_error=0,
	seen_new,
	seen_updated,
	seen_nochange
} seenaction;

static int signal_init(int signum, void (*fn)(int));
static void sigint(int signum);
volatile int sigint_flag=0;

urusai *urusai_init(void);
void urusai_free(urusai *u);
client *urusai_clientseen(urusai *u, char *id, struct sockaddr_in *addr, seenaction *seen);
int urusai_clientdeleteold(urusai *u, int timeout);
client *urusai_clientget(urusai *u, char *id);
int urusai_send(urusai *u, char *outbuf, int outbuf_used, client *destclient);


int msg_parse(char *inbuf, int inbuf_used,char **serial,char **origid,char **destid,char **cmd, char **contents);
int msg_compose(char *outbuf, int bufsize, char *serial, char *origid, char *destid, char *cmd, char *contents);

int
main(int argc, char *argv[])
{
        char inbuf[BUFLEN],outbuf[BUFLEN];;
        int inbuf_used,outbuf_used;
        struct sockaddr_in clientaddr;
        socklen_t clientaddr_len;
        urusai *u;
        client *origclient, *destclient;
        char *serial,*origid,*destid,*cmd,*contents;
	seenaction seen;	
        if((u=urusai_init())==NULL) {
                printf("ERROR: Couldn'n get/bind socket or insufficient memory\n");
                return(1);
        }
        sigint_flag=0;
        signal_init(SIGINT,sigint);
        while(!sigint_flag) {
		memset(&clientaddr,0,sizeof(clientaddr));
                clientaddr_len=sizeof(clientaddr);
                if((inbuf_used=recvfrom(u->fd,inbuf,sizeof(inbuf),0,(struct sockaddr *)&clientaddr,&clientaddr_len))==-1)
                        continue; /* interrupted by signal */
                if(msg_parse(inbuf,inbuf_used,&serial,&origid,&destid,&cmd,&contents)!=0)
                        continue;
                if((origclient=urusai_clientseen(u,origid,&clientaddr,&seen))==NULL)
                        continue;
                if(strcmp(cmd,"ping")==0) {
                        outbuf_used=msg_compose(outbuf, sizeof(outbuf), serial, NULL, origid,"pong",(seen==seen_new)?"new":(seen==seen_updated)?"updated":(seen==seen_nochange)?"nochange":NULL);
                        urusai_send(u,outbuf,outbuf_used,origclient);
                } else if(strcmp(cmd,"message")==0) {
                        if((destclient=urusai_clientget(u,destid))==NULL) {
                                outbuf_used=msg_compose(outbuf, sizeof(outbuf), serial, NULL, origid,"messageerror","destid_unknown");
                                urusai_send(u,outbuf,outbuf_used,origclient);
                        } else {
                                outbuf_used=msg_compose(outbuf, sizeof(outbuf), serial, origid, destid,"message",contents);
                                urusai_send(u,outbuf,outbuf_used,destclient);
                        }
                } else if(strcmp(cmd,"receipt")==0) {
                        if((destclient=urusai_clientget(u,destid))==NULL) {
                                outbuf_used=msg_compose(outbuf, sizeof(outbuf), serial, NULL, origid,"receipterror","destid_unknown");
                                urusai_send(u,outbuf,outbuf_used,origclient);
                        } else {
                                outbuf_used=msg_compose(outbuf, sizeof(outbuf), serial, origid, destid,"receipt",contents);
                                urusai_send(u,outbuf,outbuf_used,destclient);
                        }
                }
        }
        urusai_free(u),u=NULL;
        return(0);
}

static int
signal_init(int signum, void (*fn)(int))
{
        struct sigaction sa;
        sa.sa_handler=fn;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags=0;
        return(sigaction(signum,&sa,0));
}

static void
sigint(int signum)
{
        sigint_flag=1;
}

urusai *
urusai_init(void)
{
        urusai *u;
        if((u=malloc(sizeof(urusai)))==NULL)
                return(NULL);
        memset(u,0,sizeof(urusai));
        if((u->fd=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1) {
                urusai_free(u),u=NULL;
                return(NULL);
        }
        u->myaddr.sin_family=AF_INET;
        u->myaddr.sin_port=htons(SERVERPORT);
        u->myaddr.sin_addr.s_addr=htonl(INADDR_ANY);
        if(bind(u->fd,(struct sockaddr *)&(u->myaddr),sizeof(u->myaddr))==-1) {
                urusai_free(u),u=NULL;
                return(NULL);
        }
        return(u);
}

void
urusai_free(urusai *u)
{
        if(u==NULL)
                return;
        if(u->fd!=-1)
                close(u->fd),u->fd=-1;
        if(u->clients) {
                free(u->clients),u->clients=NULL;
                u->numclients=0;
                u->sizeclients=0;
        }
        free(u),u=NULL;
}

client *
urusai_clientseen(urusai *u, char *id, struct sockaddr_in *addr,seenaction *seen)
{
        int i,ifree;
        client *c;
        if(u==NULL || id==NULL || addr==NULL || seen==NULL) {
		if(seen!=NULL)
			*seen=seen_error;
                return(NULL);
	}
        if(u->numclients==u->sizeclients) {
                client *newc;
                if((newc=realloc(u->clients,sizeof(client)*(u->sizeclients+CLIENTBLOCK)))==NULL)
                        return(NULL);
                u->clients=newc;
                memset(u->clients+u->sizeclients,0,CLIENTBLOCK*sizeof(client));
                u->sizeclients+=CLIENTBLOCK;
        }
        for(i=0,ifree=-1;i<u->sizeclients;i++) {
                c=u->clients+i;
                if(c->id[0]=='\0' && ifree==-1)
                        ifree=i;
                if(strcmp(id,c->id)==0)
                        break;
        }
        if(i==u->sizeclients) {
                if(ifree==-1) {
			*seen=seen_error;
                        return(NULL);
		}
                i=ifree;
                c=u->clients+i;
                strncpy(c->id,id,sizeof(c->id));
                c->id[sizeof(c->id)-1]='\0';
		*seen=seen_new;
        } else {
		if(memcmp(&(c->addr),addr,sizeof(c->addr))==0)
			*seen=seen_nochange;
		else
			*seen=seen_updated;
	}
        c->lastrecv=time(NULL);
        memcpy(&(c->addr),addr,sizeof(c->addr));
        return(c);
}

int
urusai_clientdeleteold(urusai *u, int timeout)
{
        time_t cuttime;
        int i;
        client *c;
        if(u==NULL)
                return(-1);
        cuttime=time(NULL)-timeout;
        for(i=0;i<u->sizeclients;i++) {
                c=u->clients+i;
                if(c->id[0]=='\0' || c->lastrecv>cuttime)
                        continue;
                memset(c,0,sizeof(client));
        }
        return(0);
}

client *
urusai_clientget(urusai *u, char *id)
{
        int i;
        client *c;
        if(u==NULL || id[0]=='\0')
                return(NULL);
        for(i=0;i<u->sizeclients;i++) {
                c=u->clients+i;
                if(strcmp(id,c->id)==0)
                        return(c);
        }
        return(NULL);
}

int
urusai_send(urusai *u, char *outbuf, int outbuf_used, client *destclient)
{
        struct sockaddr_in to;
        if(u==NULL || outbuf==NULL || outbuf_used<=0 || destclient==NULL)
                return(-1);
        memcpy(&to,&(destclient->addr),sizeof(to));
        if(sendto(u->fd,outbuf,outbuf_used,0,(struct sockaddr *)&to,sizeof(to))==-1)
                return(-1);
        return(0);
}

int
msg_parse(char *inbuf, int inbuf_used,char **serial,char **origid,char **destid,char **cmd, char **contents)
{
        char *ptr;
        if(inbuf==NULL || inbuf_used<=0)
                return(-1);
        *serial=inbuf;
        if((ptr=memchr(*serial,0,inbuf_used-(*serial-inbuf)))==NULL)
                return(-1);
        *origid=ptr+1;
        if((ptr=memchr(*origid,0,inbuf_used-(*origid-inbuf)))==NULL)
                return(-1);
        *destid=ptr+1;
        if((ptr=memchr(*destid,0,inbuf_used-(*destid-inbuf)))==NULL)
                return(-1);
        *cmd=ptr+1;
        if((ptr=memchr(*cmd,0,inbuf_used-(*cmd-inbuf)))==NULL)
                return(-1);
        *contents=ptr+1;
        if((ptr=memchr(*contents,0,inbuf_used-(*contents-inbuf)))==NULL)
                return(-1);
        return(0);
}

int msg_compose(char *outbuf, int bufsize, char *serial, char *origid, char *destid, char *cmd, char *contents)
{
        int totallen,len;
        char *ptr;
        if(outbuf==NULL || bufsize<=0 || serial==NULL)
                return(-1);
        totallen=strlen(serial)+1+
                ((origid==NULL)?0:strlen(origid))+1+
                ((destid==NULL)?0:strlen(destid))+1+
                ((cmd==NULL)?0:strlen(cmd))+1+
                ((contents==NULL)?0:strlen(contents))+1;
        if(totallen>bufsize || totallen>MAXPACKETSIZE)
                return(-1);
        ptr=outbuf;
        len=strlen(serial);
        memcpy(ptr,serial,len),ptr+=len;
        *(ptr++)=0;
        len=(origid==NULL)?0:strlen(origid);
        memcpy(ptr,origid,len),ptr+=len;
        *(ptr++)=0;
        len=(destid==NULL)?0:strlen(destid);
        memcpy(ptr,destid,len),ptr+=len;
        *(ptr++)=0;
        len=(cmd==NULL)?0:strlen(cmd);
        memcpy(ptr,cmd,len),ptr+=len;
        *(ptr++)=0;
        len=(contents==NULL)?0:strlen(contents);
        memcpy(ptr,contents,len),ptr+=len;
        *(ptr++)=0;
        return(ptr-outbuf);
}