/****************************************************************
 *                                                              *
 *  LIBDIST V1.0						*
 *                                                              *
 *  ns.c -- naming service                                      *
 *                                                              *
 *  Last changed: 17.02.96                                      *
 *  Author: Frank Kargl (frank.kargl@informatik.uni-ulm.de)     *
 *                                                              *
 *  Restrictions: works only for Solaris 2.6 or above           *
 *                                                              *
 ****************************************************************/

#include "libdist.h"
#include "config.h"

#include <search.h>				/* for btree routines */
#include <arpa/inet.h>				/* inet_ntoa */

/* item structure */
struct dbentry {
    char    key[DL_NS_KEYLENGTH];		/* key */
    char    data[DL_NS_DATALENGTH];		/* data*/
};

static struct dbentry *dl_ns_root = NULL;		/* begin of data tree */
static rwlock_t	dl_ns_lock;				/* read/write lock */

/***
 *** int dl_ns_dbcompare(void *entry1, void *entry2)
 ***
 *** Function: compare two tree entries
 *** Return: 0  if equal
 ***         >0 if greater
 ***         <0 if smaller
 ***/

int dl_ns_dbcompare(const void *entry1, const void *entry2) {
    return strcmp(((struct dbentry *)entry1)->key,
		    ((struct dbentry *)entry2)->key);
}

/***
 *** char *parsereq(char *mesg);
 ***
 *** Function: parse a request to the nameserver
 ***           and generate an according response
 *** Return: response string
 *** 
 *** Nameserver protocol:
 *** Requests:
 *** I <key> <data><CR><LF>      insert data into nameserver
 *** D <key><CR><LF>             delete data from nameserver
 *** R <key><CR><LF>             retrieve data from nameserver
 *** P<CR><LF>                   print the namespace
 *** Q                           quit nameserver
 *** Replies:
 *** ERROR<CR><LF>               general error
 *** OK<CR><LF>                  OK reply
 *** R <key> <data><CR><LF>		 reply to request
 *** NOTFOUND<CR><LF>            key not found
 ***/

char *parsereq(char *mesg) {

    struct dbentry *tmpptr=NULL,*tmpptr2=NULL;	/* temporary pointer */
    char    **anyptr=NULL;		/* I hate all this conversion ;-) */
    struct dbentry dummydb;		/* dummy for inserts */
    static char returnmsg[DL_MAXLINE];	/* return string */
    char arg1[DL_NS_KEYLENGTH];		/* the key */
    char arg2[DL_NS_DATALENGTH];	/* the data */
    void dl_ns_printit();		/* for twalk */

    /* what command is it ? */
    switch (mesg[0]) {

	/*** Insert 'I key data' ***/

	case 'I':

		/* parse input line */
		if (sscanf(mesg,"I %s %s\r\n",arg1,arg2) != 2) {
		    strcpy(returnmsg,ERRORMSG);
		    break;
		}

		/* insert data into tree */
		tmpptr=(struct dbentry *)malloc(sizeof(struct dbentry));
		if (tmpptr==NULL) {
		    strcpy(returnmsg,ERRORMSG);
		    break;
		}
		    
		strcpy(tmpptr->key,arg1);
		strcpy(tmpptr->data,arg2);

		/* lock the tree */
		if ( !rw_wrlock(&dl_ns_lock)) {

		    /* find the right place */
		    anyptr=tsearch((void *)tmpptr,(void **) &dl_ns_root, \
				    dl_ns_dbcompare);

		    tmpptr2=(struct dbentry *)(*anyptr);

		    /* key already in table ? */
		    if(tmpptr2 != tmpptr) {
			/* yes, copy new data over old one */
			strcpy(tmpptr2->data,tmpptr->data);
			free(tmpptr);
		    }

		    /* free lock */
		    rw_unlock(&dl_ns_lock);
		}

		strcpy(returnmsg,OKMSG);
		break;

	/*** Delete 'D key' ***/

	case 'D':

		/* parse input line */
		if (sscanf(mesg,"D %s\r\n", arg1) != 1) {
		    strcpy(returnmsg,ERRORMSG);
		    break;
		}
				
		/* lock the tree and delete data */
		if ( !rw_wrlock(&dl_ns_lock)) {
		    tmpptr=tdelete((void *)arg1,(void **) &dl_ns_root, \
				    dl_ns_dbcompare);

		    /* free lock */
		    rw_unlock(&dl_ns_lock);
		}

		if (tmpptr==NULL) {
		    strcpy(returnmsg,NOTFOUNDMSG);
		    break;
		}

		strcpy(returnmsg,OKMSG);
		break;

	/*** Retrieve 'R key' ***/

	case 'R':

		/* parse input line */
		if (sscanf(mesg,"R %s\r\n", arg1) != 1) {
		    strcpy(returnmsg,ERRORMSG);
		    break;
		}
				
		/* create db entry for request */
		strcpy(dummydb.key,arg1);

		/* lock the tree */
		if ( !rw_rdlock(&dl_ns_lock)) {
		    anyptr=tfind((void *)&dummydb,(void **) &dl_ns_root, \
				    dl_ns_dbcompare);

		    /* free lock */
		    rw_unlock(&dl_ns_lock);
		}	
		
		/* we didn't find the entry */
		if (anyptr==NULL) {
			strcpy(returnmsg,NOTFOUNDMSG);
			break;
		}
 
		/* now some little conversion tricks */
		tmpptr=(struct dbentry *)(*anyptr);

		sprintf(returnmsg,REPLYMSG,tmpptr->key,tmpptr->data);
		break;

        /*** Print namespace 'P' ***/
        /*** Just for debugging  ***/
		 
	case 'P':

#ifdef DEBUG
		fprintf(stderr,"LIBDIST-ns: dumping nametable\n");
#endif

		if ( !rw_rdlock(&dl_ns_lock)) {
		    twalk((void *) dl_ns_root, dl_ns_printit);
		    rw_unlock(&dl_ns_lock);
		}

		strcpy(returnmsg,OKMSG);
		break;

	/*** Quit 'Q' ***/

	case 'Q':

#ifdef DEBUG
		fprintf(stderr,"LIBDIST-ns: nameserver shutting down\n");
#endif

		dl_thr_exit();

	/*** Default -> Protocol error */

	default:
		
		strcpy(returnmsg,ERRORMSG);
		break;
    }

    return returnmsg;
}

/***
 *** void *dl_ns_server(void *);
 ***
 *** Function: act as a nameserver on UDP broadcast basis
 *** Return: ­ (endless loop)
 ***/

static void *dl_ns_server(void *dummy) {

    int servsock;			/* serversocket */
    struct sockaddr_in client;		/* the IEA of the client */
    int client_len;			/* client address length */
	
    char buffer[DL_MAXLINE];		/* the request buffer */
    char *response;			/* the reply */
    int on = 1;				/* for setsockopt */

    /* create the server socket */
    if ((servsock=dl_sck_server(DL_NS_PORT,DL_SCK_UDP,NULL)) == DL_ERROR ) {
	/* error starting server ? -> die silently */

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: error starting nameserver\n");
#endif

	dl_thr_exit();
    }

    /* allow broadcasts */
    setsockopt(servsock,SOL_SOCKET,SO_BROADCAST,(char *)&on,sizeof(on));

    /* endless loop */
    while(1) {

	/* get request */
	client_len=sizeof(client);
	if (recvfrom(servsock,buffer,sizeof(buffer),0, \
		    (struct sockaddr *)&client,&client_len) < 0) {
	    /* error reading ? */

#ifdef DEBUG
	    fprintf(stderr,"LIBDIST-ns: error reading in nameserver\n");
#endif

	    continue;
	}

	/* parse request */
	response=parsereq(buffer);

	/* send reply */
	sendto(servsock,response,strlen(response),0, \
		(struct sockaddr *)&client,sizeof(client));
    }
}

/***
 *** thread_t dl_ns_start(void)
 ***
 *** Function: start a name server on this machine
 *** Return  : the thread id of the nameserver
 ***	       DL_ERROR if error
 ***/

thread_t dl_ns_start(void) {

	thread_t mythread;	/* thread identifier returned */

	/* init lock */
	rwlock_init(&dl_ns_lock, USYNC_THREAD, NULL);

	/* start nameserver */
	mythread=dl_thr_create(dl_ns_server,NULL);
	
	return(mythread);
}

/***
 *** int dl_ns_enter(char *key, char *data)
 ***
 *** Function: send data identified by key to all nameservers
 *** Return  : DL_OK if successfull
 ***           DL_ERROR if error
 ***/

int dl_ns_enter(char *key, char *data) {

    char buffer[DL_MAXLINE];	/* buffer for request string */
    struct sockaddr_in serv;	/* IEA of server */
    int serv_len;		/* server IEA length */
    int sock;			/* well, a socket ;-) */
    char *ptr;			/* ptr to receive string */

    /* assemble request string */
    sprintf(buffer,"I %s %s\r\n",key,data);

    /* open broadcast socket */
    sock=dl_sck_sockb();

    /* send request as broadcast */
    if (dl_sck_sendb(sock,DL_NS_PORT,buffer) == DL_ERROR) {
    /* error ? how could this happen ? */

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: error sending text\n");
#endif

	return DL_ERROR;
    }

    serv_len=sizeof(serv);

    /* receive response */
    ptr=dl_sck_recvtio(sock,DL_NS_TIMEOUT);
    if (ptr == NULL) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: timeout\n");
#endif

	return DL_NS_TIMEDOUT;
    }

    strcpy(buffer,ptr);

    if (!strcmp(buffer,OKMSG)) {
	return DL_OK;
    } else {
	return DL_ERROR;
    }
}

/***
 *** char *dl_ns_retrieve(char *key)
 ***
 *** Function: retrieve the data associated with key from a nameserver
 *** Return  : nameserver entry if found
 ***           DL_ERROR if error
 ***	       DL_NS_NOTFOUND if not found
 ***/

char *dl_ns_retrieve(char *key) {

    char buffer[DL_MAXLINE];	/* buffer for request string */
    int sock;			/* well, a socket ;-) */
    char key2[DL_NS_KEYLENGTH];	/* buffer for reply */
    static char data[DL_NS_DATALENGTH];	/* buffer for reply */
    char *ptr;			/* ptr to receive string */

    /* assemble request string */
    sprintf(buffer,"R %s\r\n",key);

    /* create the communication socket */
    sock=dl_sck_sockb();

    if (dl_sck_sendb(sock,DL_NS_PORT,buffer) == DL_ERROR) {
	/* error ? how could this happen ? */

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: error sending text\n");
#endif

	return (char *)DL_ERROR;
    }

    /* receive response */
    ptr=dl_sck_recvtio(sock,DL_NS_TIMEOUT);
    if (ptr == NULL) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: timeout\n");
#endif

	return (char *)DL_NS_TIMEDOUT;
    }

    strcpy(buffer,ptr);

    /* entry not found */
    if (!strcmp(buffer,NOTFOUNDMSG)) {
	return (char *)DL_NS_NOTFOUND;
    }

    /* reply format ok ? */
    if (sscanf(buffer,REPLYMSG,key2,data) != 2) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: wrong format\n");
#endif

	return (char *)DL_ERROR;
    }

    return data;
}

/***
 *** void dl_ns_dump(void)
 ***
 *** Function: dump the namespace
 *** Return  : -
 *** This is for debugging purposes only
 ***/

void dl_ns_dump(void) {

    char buffer[DL_MAXLINE];	/* buffer for request string */
    int sock;			/* well, a socket ;-) */
    char *ptr;			/* ptr to receive string */

    /* assemble request string */
    sprintf(buffer,"P\r\n");

    /* create communication socket */
    sock=dl_sck_sockb();

    if (dl_sck_sendb(sock,DL_NS_PORT,buffer) == -1) {
	/* error ? how could this happen ? */

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: error sending text\n");
#endif

	return;
    }
	
    /* receive response */
    ptr=dl_sck_recvtio(sock,DL_NS_TIMEOUT);
    if (ptr == NULL) {

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: timeout\n");
#endif
	return;
    }

    strcpy(buffer,ptr);

#ifdef DEBUG
    if (!strcmp(buffer,OKMSG)) {
	fprintf(stderr,"LIBDIST: everything is ok\n");
    } else {
	fprintf(stderr,"LIBDIST: dump problems\n");
    }
#endif
}

/***
 *** int dl_ns_delete(char *key)
 ***
 *** Function: delete the data associated with key from a nameserver
 *** Return  : DL_OK if sucessfull
 ***           DL_ERROR if error
 ***	       DL_NS_NOTFOUND if not found
 ***/

int dl_ns_delete(char *key) {

    char buffer[DL_MAXLINE];	/* buffer for request string */
    struct sockaddr_in server;	/* IEA of server */
    int server_len;		/* server IEA length */
    int sock;			/* well, a socket ;-) */
    int numbytes;		/* number of bytes read */

    /* assemble request string */
    sprintf(buffer,"D %s\r\n",key);

    /* create communication socket */
    sock=dl_sck_sockb();

    /* send request as broadcast */
    if (dl_sck_sendb(sock,DL_NS_PORT,buffer) == -1) {
	/* error ? how could this happen ? */

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: error sending text\n");
#endif

	return DL_ERROR;
    }

    numbytes=recvfrom(sock,buffer,sizeof(buffer),0, \
			(struct sockaddr *)&server,&server_len);
    
    if (numbytes < 0) {
	/* error reading */

#ifdef DEBUG
	fprintf(stderr,"LIBDIST-ns: error reading from nameserver\n");
#endif

	return DL_ERROR;
    }

    buffer[numbytes]='\0';

    /* react to response */
    if (!strcmp(buffer,OKMSG)) {
	return DL_OK;
    } else if (!strcmp(buffer,NOTFOUNDMSG)) {
	return DL_NS_NOTFOUND;
    } else {
	return DL_ERROR;
    }

}

/***
 *** void dl_ns_printit(void **node, VISIT order, int level)
 ***
 *** Function: print out the namespace
 *** Return  : -
 ***/

void dl_ns_printit(void **node, VISIT order, int level) {

#ifdef DEBUG
    char *key,*data;
 
    if (order == postorder || order == leaf) {
	key = (*((struct dbentry **)node))->key;
	data = (*((struct dbentry **)node))->data;
	fprintf(stderr,"%s\t%s\n",key,data);
    }
#endif
}
