/*-=-=-=-=-=-=-=-=-=-=-=- RCCL/RCI Notice of Copyright -=-=-=-=-=-=-=-=-=-=-*
 *									    *
 *	Copyright (C) 1991 by John E. Lloyd.				    *
 *									    *
 *	Information on copyright permissions, as well as a complete	    *
 *	disclaimer of warranty, is given in the file COPYRIGHT.		    *
 *									    *
 *-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "../rci/link.h"
#include "../h/cdefs.h"

#include "RCISimMuxd.h"

#define DEBUG 0
#define DISCONNECT 1

struct client_ {
  struct client_ *next;
  struct client_ *prev;
  int fd;
  char *simname;
  short int size;
  short int port;
  int cnt;
  char cmd;
  int state;
#define CLIENT_STATE_NEW       1
#define CLIENT_STATE_READPORT1 2
#define CLIENT_STATE_READPORT2 3
#define CLIENT_STATE_READSIZE1 4
#define CLIENT_STATE_READSIZE2 5
#define CLIENT_STATE_READNAME  6
#define CLIENT_STATE_WAITING   7
} ;
typedef struct client_ CLIENT;

main(argc, argv)
int argc;
char **argv;
{
	CLIENT *clients;
	int i;
	int slen;
	struct sockaddr_in sin;
	int regsock;

#if DEBUG
    printf ("mux: spawned\n");
#endif
	slen = sizeof(sin);
	regsock = atoi(*++argv);

	bzero((char *)&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = 0;
	signal(SIGPIPE,SIG_IGN);

	clients = NULL;	/* initialize list of clients to empty */
#if DISCONNECT
#if DEBUG
    printf ("mux: disconnecting\n");
#endif
	for (i=getdtablesize()-1;i>=0;i--) if (i != regsock) close(i);
	i = open("/dev/tty",O_RDWR,0);
	if (i >= 0)
	 { ioctl(i,TIOCNOTTY,0);
	   close(i);
	 }
	setpgrp(0,getpid());
#endif
	listen(regsock,10);
	if (fork() != 0)
	 { exit(0);
	 }
#if DEBUG
    printf ("mux: listening\n");
#endif
	while (1)
	 { int s;
	   char cmd;
	   CLIENT *c, *c_next;
	   fd_set fds;
	   FD_ZERO(&fds);
	   FD_SET(regsock,&fds);
	   for (c=clients; c; c=c->next)
	    { FD_SET(c->fd,&fds);
	    }
	   if (select(FD_SETSIZE, &fds, (fd_set *)0, (fd_set *)0,
				  (struct timeval *)0) < 0)
	    { printf ("mux: error in select\n");
	      return(-1);
	    }
	   if (FD_ISSET(regsock,&fds))
	    { slen = sizeof(sin);
	      s = accept(regsock,(struct sockaddr *)&sin,&slen);
#if DEBUG
    printf ("mux: accepted new client\n");
#endif
	      if (s < 0)
	       { printf ("mux: odd ... accept returned -1\n");
		 continue;
	       }
	      c = ALLOC(CLIENT);
	      c->fd = s;
	      c->state = CLIENT_STATE_NEW;
	      c->simname = NULL;
	      c->port = 0;
	      LINK_H(clients, c);
	      continue; /* important: list has changed */
	    }
	   for (c=clients; c; c=c_next)
	    { c_next = c->next;
	      /* here is the protocol:

		 Registering clients send an 'R' command followed
		 by the port number, the length of the simname, the
		 simname, and the port #. 

		 Requesting clients send a 'P' command followed by the 
		 length of the simname, and the simname. The port number is
		 then written to them.

		 After the port number is written to a requesting
		 client, it is marked 'X', indicating that it is
		 no longer available.

	       */
	      if (FD_ISSET(c->fd,&fds))
	       { char ch;
		 if (read(c->fd,&ch,1) != 1)
		  { close(c->fd);
		    if (c->simname)
		     { free(c->simname);
		     }
		    UNLINK_H(clients,c);
		    FREE(c);
		  }
		 else
		  { switch (c->state)
		     { case CLIENT_STATE_NEW:
			{ if ((c->cmd = ch) == 'R')
			   { c->state = CLIENT_STATE_READPORT1;
#if DEBUG
    printf ("mux: got 'R' command\n");
#endif
			   }
			  else
			   { c->state = CLIENT_STATE_READSIZE1;
#if DEBUG
    printf ("mux: got 'P' command\n");
#endif
			   }
			  break;
			}
		       case CLIENT_STATE_READPORT1:
			{ ((char *)&c->port)[0] = ch;
			  c->state = CLIENT_STATE_READPORT2;
			  break;
			}
		       case CLIENT_STATE_READPORT2:
			{ ((char *)&c->port)[1] = ch;
			  c->port = ntohs(c->port);
			  c->state = CLIENT_STATE_READSIZE1;
			  break;
			}
		       case CLIENT_STATE_READSIZE1:
			{ ((char *)&c->size)[0] = ch;
			  c->state = CLIENT_STATE_READSIZE2;
			  break;
			}
		       case CLIENT_STATE_READSIZE2:
			{ ((char *)&c->size)[1] = ch;
			  c->size = ntohs(c->size);
			  c->simname = (char*)malloc(c->size+1);
			  c->cnt = 0;
			  c->state = CLIENT_STATE_READNAME;
			  break;
			}
		       case CLIENT_STATE_READNAME:
			{ c->simname[c->cnt++] = ch;
			  if (c->cnt == c->size)
			   { c->simname[c->cnt] = '\0';
			     c->state = CLIENT_STATE_WAITING;
#if DEBUG
    printf ("mux: read name %s (cnt=%d) for cmd %c\n", c->simname, c->cnt,c->cmd);
#endif
			     if (c->cmd == 'P')
			      { short int p;
				CLIENT *oc;
				c->state = CLIENT_STATE_NEW;
				p = htons((short int)0);
				for (oc=clients; oc; oc=oc->next)
				 { 
#if DEBUG
    printf ("mux: search %c %s\n", oc->cmd, oc->simname);
#endif
				   if (oc->cmd == 'R' &&
				       oc->state == CLIENT_STATE_WAITING &&
				       strcmp(oc->simname,c->simname)==0)
				    { p = htons(oc->port);
				      oc->cmd = 'X';
				      break;
				    }
				 }
#if DEBUG
    printf("mux: found port %d\n", ntohs(p));
#endif
				write(c->fd,(char *)&p,sizeof(p));
			      }
			   }
			  break;
			}
		       case CLIENT_STATE_WAITING:
			{ /* what?  someone writing in WAITING state? */
			  /* ignore it */
			  break;
			}
		     }
		  }
	       }
	    }
	 }
}
