/*  nanoserv.c	14-Mar-03 11:57:56 by Kevin D. Quitt

    Tiny demonstration server.  Based on Demoserv.

*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#define	VERSION	"V1.00"


#ifdef	WIN32
#undef	WIN32
#define	WIN32	1
#endif

#ifdef	VMS
#undef	VMS
#define	VMS	1
#endif

#ifdef	LINUX
#undef	LINUX
#define	LINUX	1
#endif

#if	WIN32
#include <fcntl.h>
#include <io.h>
#include <conio.h>
#include <Winsock2.h>
#include <time.h>
#elif	VMS
#include <in.h>
#include <inet.h>
#include <netdb.h>
#include <types.h>
#include <socket.h>
#include <tcp.h>
#include <unixio.h>
#include <ucx$inetdef.h>
#include <iodef.h>
#include <unixlib.h>
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <time.h>
#endif

#if WIN32
#define	CloseSocket(s)	closesocket(s)
#define	sleep(s)	Sleep((s)*1000)
typedef unsigned int	uint;
typedef unsigned long	ulong;
typedef unsigned char	uchar;
#else
#define	CloseSocket(s)	close(s)
#endif


#include    "strutil.h"
#include    "inifile.h"

/*  See SLOPPIDEF.H for the correct TKNAME macro.
*/
#define	TKNAME(a,b,c,d) (((d)<<24)|((c)<<16)|((b)<<8)|(a))

#include    "sloppidef.h"
#include    "dbxface.h"
#include    "nanoserv.h"


#define	MAXHOSTNAME	(128)
#define	RCVBUFFSIZE	(8192)
#define	XMTBUFFSIZE	(8192)
#define	MAXCONNECT	(10)

#define SLOPPIPORT 1283			/* default port number		    */

#define	DATAHEADERSIZE	(sizeof (SLOPPIHEADER) + sizeof (SLOPPIDATA))

/*  Tie the name of the token to its value.
*/
struct
  {
    SLOPPITOKEN	    token;
    char	   *tname;
  } nameToken[] =
  {
      { SLT_TERMINATE,	    mkstr( SLT_TERMINATE )    },
      { SLT_CAPQUERY,	    mkstr( SLT_CAPQUERY )     },
      { SLT_CAPRESPONSE,    mkstr( SLT_CAPRESPONSE )  },
      { SLT_MODESETQUERY,   mkstr( SLT_MODESETQUERY ) },
      { SLT_MODERESPONSE,   mkstr( SLT_MODERESPONSE ) },
      { SLT_ERRREPORT,	    mkstr( SLT_ERRREPORT )    },
      { SLT_PRODUCTQUERY,   mkstr( SLT_PRODUCTQUERY ) },
      { SLT_DATA,	    mkstr( SLT_DATA )	      },
      { SLT_REGISTRATION,   mkstr( SLT_REGISTRATION ) },
      { SLT_EVENT,	    mkstr( SLT_EVENT )	      },
      { 0, NULL }
  };


/*  Operational controls.
*/
int	trivial	    = FALSE;		/* Output comm mode		    */
char	host[ MAXHOSTNAME ];		/* Name of this host		    */
ushort	port	    = SLOPPIPORT;	/* Communications port number	    */

int	cnt = 0;


//		General purpose socket utilities, BSD-style.
//  See UNIX Network Programming by Stevens.

/*  Wait for a connection to occur on a socket created with establish()
*/
int get_connection( int s, unsigned long *caller_ip )
  {
    int		    t;			/* socket of connection		    */
    int		    addrlen;
    struct sockaddr addr    = {0};

    addrlen = sizeof addr;

    t	= accept( s, &addr, &addrlen );
    if ( t  <=  0 )
      {
	if ( errno  ==  EINTR )		/* EINTR might happen on accept(),  */
	    t	= accept( s, &addr, &addrlen );
      }
    if ( t <= 0 )
      {
	perror( "accept" );		/* bad				    */
	exit( 1 );
      }

    if ( caller_ip )
      {
	*caller_ip  = ((unsigned char)addr.sa_data[ 2 ]  <<  24)    +
		      ((unsigned char)addr.sa_data[ 3 ]  <<  16)    +
		      ((unsigned char)addr.sa_data[ 4 ]  <<   8)    +
		      ((unsigned char)addr.sa_data[ 5 ]);
      }

    if ( t  <  0 )			/* accept connection if it's there  */
	t   = -1;
    return  t;
  }


/*  Code to establish a socket; originally from bzs@bu-cs.bu.edu
*/
int establish( unsigned short portnum )
  {
    char    myname[ MAXHOSTNAME + 1 ];
    int	    s;
    struct hostent     *hp;
    struct sockaddr_in	sa   = {0};

    int	    one	    = 1;
    char   *onep    = (char *)&one;

#if WIN32
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested	= MAKEWORD( 2, 0 );
    err = WSAStartup( wVersionRequested, &wsaData );

    if ( err != 0 )
      {
	fputs( "A useable WinSock DLL not fount", stdout );
	return -1;
      }
#endif

    if ( host[ 0 ] )			/* User-specified host name?	    */
	strncpy( myname, host, sizeof myname );
    else
	gethostname( myname, MAXHOSTNAME );

    hp= gethostbyname( myname );	/* get our address info		    */
    if ( hp  ==  NULL )			/* we don't exist !?		    */
      {
	fputs( "gethostbyname failed", stdout );
	return	-1;
      }
    sa.sin_family   = hp->h_addrtype;	/* this is our host address	    */
    sa.sin_port	    = htons( portnum );	/* this is our port number	    */

    s	= socket( AF_INET, SOCK_STREAM, 0 );
    if ( s  <  0 )
      {
	fputs( "socket failed", stdout );
	return	-1;
      }

    setsockopt( s, IPPROTO_TCP, TCP_NODELAY, onep, sizeof one );

    if ( bind( s, (struct sockaddr *)&sa, sizeof (struct sockaddr_in))  <  0 )
      {
	fputs( "bind failed", stdout );
	CloseSocket( s );
	return	-1;
      }

    listen( s, MAXCONNECT );		/* max # of queued connects	    */
    return  s;
  }


int	makeConnection( void )
  {
    char    remoteHost[ MAXHOSTNAME ];
    int	    status;
    struct hostent     *phe;
    struct sockaddr_in  remote_sin;

#ifdef WIN32
    {
      WSADATA	WSAData;
      status  = WSAStartup( MAKEWORD( 2,0 ), &WSAData );
      if ( status )
	{
	  printf( "Requested socket version not supported: %d\n", status );
	  exit( -1 );
	}
    }
#endif


#ifdef REQD
/*
    Set Socket Options
*/
    status  = setsockopt( touchSock, IPPROTO_TCP,TCP_NODELAY,
			  (char *)&opt, optlen );
    if ( status == -1 )
      {
	CloseSocket( touchSock );
	touchSock   = 0;
	return	FALSE;
      }

    opt	    = 4096;
    status  = setsockopt( touchSock, SOL_SOCKET, SO_SNDBUF,
			  (char *)&opt, optlen );
    if ( status == -1)
	printf( "Error setting socket option(SO_SNDBUF)\n" );

    status  = setsockopt( touchSock, SOL_SOCKET, SO_RCVBUF,
			  (char *)&opt, optlen );
    if ( status == -1 )
	printf( "Error setting socket option(SO_RCVBUF)\n" );
#endif

    phe	= gethostbyname( remoteHost );

    if ( !phe )
	return	FALSE;

    memcpy((void *)&(remote_sin.sin_addr), phe->h_addr, phe->h_length );

/*  talk to user provided port value
*/
    remote_sin.sin_port	    = htons( SLOPPIPORT );
    remote_sin.sin_family =  AF_INET;


    return  TRUE;
  }


/*  Read specified amount of data from the socket.
*/
int	sockread( int sock, char *buf, int len )
  {
    int	got, total;

    if ( !buf  ||  (len < 1))
	return	0;

    total   = 0;

    while ( len )
      {
	got = recv( sock, buf, len, 0 );
	if ( got  <=  0 )
	    break;

	total	+= got;
	len	-= got;
	buf	+= got;
      }

    return  total;
  }



/*  Send data through the socket.
*/
void	socksend( int sock, char *buf, int len, int debug )
  {
    int	ch;

/*  If console mode has been selected, we don't actually send anything.
*/
    while ( len )
      {
	ch  = send( sock, buf, len, 0 );
	if ( ch  >=  0 )
	  {
	    len	-= ch;
	    buf	+= ch;
	  }
	else
	    len	= 0;
      }
  }


/*			  SLOPPI utility functions			    */

/*  Return the token from the packet.  When in doubt, return TERMINATE.
*/
static SLOPPITOKEN packetToken( SLPACKET *pkt )
  {
    long	len;
    SLOPPITOKEN	tkn;
    int		i;

    if ( !pkt )				/* If nothing passed		    */
	return	SLT_TERMINATE;

    len	= ntohl( pkt->header.length );	/* Make sure it has a token	    */
    if ( len  <  sizeof (SLOPPIHEADER))
	return	SLT_TERMINATE;

    tkn	= pkt->header.token;

    for ( i = 0; nameToken[ i ].token  !=  SLT_NOTATOKEN; i++ )
      {
	if ( nameToken[ i ].token  ==  tkn )
	    return  tkn;
      }

    return  SLT_TERMINATE;
  }


/*  Map bit-mapped values to a single selection.  The lowest numbered event
    is selected.
*/
char	newEvents[ 64 ]	=
  {
    0, 1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1,
    0, 1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1,
    0, 1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1
  };


int	processEvent( SLPACKET *ipkt, ulong theIP, char *xmit )
  {
    ulong   es;				/* Event start			    */

    SLDATA     *ppkt	= NULL;
    SLEVENT    *evnt	= &ipkt->body.events;

    es   = ntohl( evnt->starting ) & 0xF;/* Get the new starts		    */
    es	= newEvents[ es ];
    if ( !es )				/* If none, we're done		    */
	return	FALSE;;

    return  databaseEvent( es, evnt->unitID );
  }


void	ProcessPacket( char *rcv, int sock, unsigned long theIP )
  {
    int	    c, x;
    char   *rep	    = NULL;
    char   *tosend  = NULL;
    int	    plen    = 0;

    char    xmit[ XMTBUFFSIZE ];

    SLPACKET   *ipkt	= (SLPACKET *)rcv;

    if ( !rcv )
	return;


/*  Product queries are handles by making them look like a trivial mode
    query.  Events are special; push-button starts are converted to the
    scan code BUTTONX, where X is the hex digit representing new button
    presses.
*/
    if ( !rcv[ 0 ] )			/* A Nominal mode packet?	    */
      {
	switch ( packetToken( ipkt ))
	  {
	    case    SLT_EVENT:
	      {
		if ( !processEvent( ipkt, theIP, xmit ))
		    return;
		strcpy( rcv, xmit );
		break;
	      }

	    case    SLT_PRODUCTQUERY:
	      {
		c   = strlen( ipkt->body.prodquery.product );
		memmove( rcv, ipkt->body.prodquery.product, c + 1 );
		break;
	      }

	    default:
		return;
	  }
      }


/*  Clear off trailing NULs and whitespace.
*/
    c   = strip( rcv );
    if ( c  <=  0 )			/* Anything left?		    */
	return;

    memset( xmit, 0, XMTBUFFSIZE );


    rep = strchr( rcv, '\t' );		/* Skip past the tab		    */
    if ( rep )
	rep++;
    else
	rep = rcv;


/***************  B E G I N  D A T A B A S E  L O O K U P   ****************/
/*VVV								      VVV*/

    memset( xmit, 0, sizeof xmit );	/* Stewardship			    */

    x	= databaseLookup( rep, xmit, sizeof xmit );

/*^^^								      ^^^*/
/*****************  E N D  D A T A B A S E  L O O K U P   ****************/


/*  x is the length of the string in tosend if the code was found, and
    zero if nothing was found.
*/
    if ( x  ==  0 )
	x   = sprintf( xmit, "%s\r\n  %d", rep, cnt ) + 1;
	socksend( sock, xmit, x, 0 );
  }

void	ProcessConnection( int sock, unsigned long theIP )
  {
    int	    c;
    char    rcv[ RCVBUFFSIZE ];

    memset( rcv, 0, sizeof rcv );	/* Setwardship			    */

    c   = recv( sock, rcv, RCVBUFFSIZE, 0 );

    if ( c  >=  0 )
	    ProcessPacket( rcv, sock, theIP );

    CloseSocket( sock );
  }

int main( int argc, char* argv[])
  {
    int	    sock;
    int	    listenSocket    = 0;
    char   *rcv		    = NULL;

    unsigned long   theIP   = 0;

    listenSocket   = establish( port );
    if ( listenSocket  <=  0 )
	exit( EXIT_FAILURE );


    sock    = 0;
    while (  TRUE )
      {
	sock   = get_connection( listenSocket, &theIP );
	if ( sock  <=  0 )
	  {
	    if ( errno  ==  EINTR )	/* EINTR might happen on accept(),  */
		sock   = get_connection( listenSocket, &theIP );
	  }
	if ( sock  <=  0 )
	  {
	    perror( "accept" );	/* bad				    */
	    exit( 1 );
	  }

	ProcessConnection( sock, theIP );

	cnt++;				/* Count the number of connects	    */
      }

    return 0;				/* To keep lint happy		    */
  }
