/*  DEMOSERV.C	18-Sep-00 12:33:19 by Kevin D. Quitt

    Demonstration price-check server.

    Listen on the specified port for price requests from a ScanVue.  If the
    number is found in the specified data file, return information
    accordingly, otherwise return the scanned code and a sequence number.

 9-Feb-01 14:42, [KDQ]\kqpos\demoserv.c, Edit by KDQ
    Added handling (to some extent) of nominal-mode packets.

 9-Feb-01 12:11, [KDQ]\kqpos\demoserv.c, Edit by KDQ
    Added logging of barcodes to an output file.

31-Jan-01  8:59, [KDQ]\iee\socket\listen.c, Edit by KDQ
    Supports different responses to same barcode when rescanned in a timely
    manner.

19-Jan-01 19:44, [KDQ]m:\iee\socket\listen.c, Edit by KDQ
    Added ability to send files in-line.

15-Jan-01 18:01, [KDQ]m:\iee\socket\listen.c, Edit by KDQ
    Changed to send NUL-terminated strings including the termination.

 5-Oct-00 14:26, [KDQ]m:\iee\socket\listen.c, Edit by KDQ
     Changed default port to IANA-approved ProductInfo port.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>

#define	VERSION	"V3.14"

#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

#ifndef	LINUX
typedef unsigned long	ulong;
#endif

#if WIN32
#define	CloseSocket(s)	closesocket(s)
#else
#define	CloseSocket(s)	close(s)
#endif


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

#include    "sloppidef.h"

#define	MAXHOSTNAME	(128)

#ifndef	TRUE
#define	TRUE	(1==1)
#define	FALSE	(1!=1)
#endif
#ifndef NUL
#define	NUL	(0)
#endif

#define	mkstr(t)    # t
#define	mkstrstr(t) mkstr(t)

#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	useconsole  = FALSE;		/* I/O to display/keyboard	    */
time_t	threshhold  = 10;		/* Time limit for same-items scans  */
int	verbose	    = 0;		/* How much do we spew?		    */
int	trivial	    = TRUE;		/* Output comm mode		    */
char	host[ MAXHOSTNAME ];		/* Name of this host		    */
char	datafile[ 128 ]	= "data.dat";	/* Default data file name	    */
char    logfilename[ 128 ];		/* No default log file		    */
FILE   *logfile;
char	regfilename[ 128 ];		/* Logging of registrations	    */
FILE   *regfile;
unsigned short	port	= SLOPPIPORT;	/* Communications port number	    */

ulong	NewDn, NewUp;


/*  These track what barcode we've seen recently and responses we've made.
*/
char	lastFound[ 4096 ]    = {0};
char	lastRecord[ 4096 ]   = {0};
time_t   foundWhen	    = 0;
int	timesSeen	    = 0;

char    rcv[ 2048 ];			/* General receive buffer	    */
char    xmit[ 8192 ];			/* General transmit buffer	    */


/*  Remove trailing NULs and whitespace from a string, return the length.
*/
int  strip( char *string )
  {
    int	c, ch;

    if ( !string )
	return	0;

    c	= strlen( string );

    while ( c  >  0 )			/* Strip trailing nulls, whitespace */
      {
	ch  = string[ c - 1 ];
	if ( !ch  ||  isspace( ch ))
	    string[ --c ]   = NUL;	/* NUL fill			    */
	else
	    break;
      }

    return  c;
  }


/*		General purpose socket utilities, BSD-style.		    */

/*  Wait for a connection to occur on a socket created with establish()
*/
int get_connection( int s )
  {
    int t;				/* socket of connection		    */

    if ( verbose  >  1 )
	fprintf( stdout, "Waiting for connection on %d\n", s );
    t	= accept( s, NULL, NULL );
    if ( verbose  >  1)
	fprintf( stdout, "get_connection: accept returned %d\n", t );

    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 ( verbose  >  1 )
	fprintf( stdout, "establish: socket is %d\n", s );
    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, 1 );			/* max # of queued connects	    */
    if ( verbose  >  1 )fprintf( stdout,"establish: listening on %d for %d\n", s, 1 );
    return  s;
  }


/*  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;
    if ( verbose )
	fprintf( stdout, "sock read %d\n", len );
    while ( len )
      {
	got = recv( sock, buf, len, 0 );
	if ( verbose )
	    fprintf( stdout, "%d ", got );
	if ( got  <=  0 )
	    break;

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

    if ( verbose )
	fprintf( stdout, "total %d\n", total );
    return  total;
  }



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

/*  Dump what's being sent in various formats, as specified.
*/
    if ( useconsole  ||  ( verbose  &&  debug ))
      {
	if ( !debug )
	  {
	    if ( len  ==  4 )
	      {
		fprintf( stdout, "\nFile length: %lu\n",
			 (ulong)ntohl( *(long *)buf));
		return;
	      }
	    fprintf( stdout, "(%d bytes of the file)\n", len );
	    return;
	  }

	fprintf( stdout, "%d >", len );
	for ( ch = 0; ch < len; ch++ )
	  {
	    if ((debug  ==  1)  &&  isprint( buf[ ch ]))
		fputc( buf[ ch ], stdout );
	    else
		fprintf( stdout, "\\x%02X", buf[ ch ] & 0xFF );
	  }
	fprintf( stdout, ">\n" );
      }

/*  If console mode has been selected, we don't actually send anything.
*/
    if ( !useconsole )
      {
	while ( len )
	  {
	    if( verbose  >  1 )
		fprintf( stdout, "socksend %d\n", len );
	    ch  = send( sock, buf, len, 0 );
	    if ( ch  >=  0 )
	      {
		if ( verbose  &&  !debug )
		    fprintf( stdout, "Sent %d\n", ch );
		len	-= ch;
		buf	+= ch;
	      }
	    else
	      {
		if ( verbose )
		    fprintf( stdout, "Error %d\n", ch );
		len	= 0;
	      }
	  }
      }
  }


/*  Send a file directly in the stream for the response.
*/
int sendfile( int sock, char *fnamebuf )
  {
    FILE   *imm;
    size_t  fsize;
    char   *fbuff;
    size_t  xfer    = 0;

    if ( !fnamebuf )
	return	0;

    imm = fopen( fnamebuf + 1, "rb" );
    if ( imm )
      {
	fseek( imm, 0, SEEK_END );
	fsize   = ftell( imm );		/* How big is the file?		    */
	if ( !fsize )
	  {
	      forgetit:
	    fclose( imm );
	    return  0;
	  }
	fseek( imm, 0, SEEK_SET );

	fbuff   = malloc( fsize );
	if ( !fbuff )
	    goto    forgetit;

/*  Read in the file.  If it works, send the file name, the four-byte file
    size, then the file.
*/
	xfer	= fread( fbuff, 1, fsize, imm );
	if ( xfer  ==  fsize )
	  {
	    socksend( sock, fnamebuf, strlen( fnamebuf ) + 1, 1 );

	    xfer    = htonl( fsize );
	    socksend( sock, (char *)&xfer, sizeof xfer, 2 );

	    socksend( sock, fbuff, fsize, 0 );

	    xfer    = 1;
	  }
	else
	  {
	    if ( verbose )
		fprintf( stdout, "Read failed\n" );
	    xfer    = 0;
	  }

	fclose( imm );
	free( fbuff );
      }
    else if ( verbose )
	fprintf( stdout, "Can't open [%s]\n", fnamebuf + 1 );

    return  xfer;
  }




/*		   Sample database and utility functions.		    */

void clearHistory( void )
  {
    if ( verbose  >  1 )
	fprintf( stdout, "clear history\n" );

    memset( lastFound,  0, sizeof lastFound  );
    memset( lastRecord, 0, sizeof lastRecord );

    foundWhen	= 0;
    timesSeen	= 0;
  }

void	recordHistory( char *record )
  {
    if ( verbose  >  1 )
	fprintf( stdout, "Save data: [%s]\n", record );
    strncpy( lastRecord, record, sizeof lastRecord );
  }


/*  If the last barcode we saw is too old, or if the new one doesn't match
    the old one, start fresh.

    If it's not too old and matches, we bump the timesSeen count
*/
void	registerHistory( char *barcode )
  {
    time_t  now;

    now	= time( NULL );

    if ( verbose  >  1 )
      {
	fprintf( stdout, "barcode is %sa match.\n",
		 strcmp( barcode, lastFound ) ? "not " : "" );
	fprintf( stdout, "threshhold %sexceeded.\n",
		 ((now - foundWhen) > threshhold) ? "" : "not " );
      }

    if ( strcmp( barcode, lastFound )  ||  ((now - foundWhen) > threshhold))
      {
	clearHistory();
	if ( verbose  >  1 )
	    fprintf( stdout, "New barcode [%s] at %d.\n", barcode, (int)now );
	strncpy( lastFound, barcode, sizeof lastFound );
      }

    foundWhen	= now;			/* Same code, and timely	    */
    timesSeen++;
  }
 


/*  If the current count and the count on the record are a match, or if
    there is no count on the record, save the record data for the response.
*/
void	setNthRecord( char *record, int len )
  {
    char   *pastNumber;
    int	    number;

    if ( !record  ||  (len  <  1))
	return;

    if ( verbose  >  1 )
	fprintf( stdout, "Checking record [%s].\n", record );


/*  A numbered record starts with the number surrounded by hash marks.
*/
    if ((len  ==  1)  ||  (record[ 0 ]  !=  '#'))
      {
	if ( verbose  >  1 )
	  {
	    if ( len  ==  1 )
		fprintf( stdout, "Too small.\n" );
	    else
		fprintf( stdout, "Not numbered.\n" );
	  }
	recordHistory( record );	/* Too small or not numbered	    */
	return;
      }

    if ( record[ 1 ]  ==  '#' )		/* Convert ## to #		    */
      {
	if ( verbose  >  1 )
	    fprintf( stdout, "Starts with ##.\n" );
	recordHistory( record + 1 );
	return;
      }

    pastNumber	= strchr( record + 1, '#' );
    if ( !pastNumber )			/* No number terminator		    */
      {
	if ( verbose  >  1 )
	    fprintf( stdout, "No number terminator.\n" );
	recordHistory( record );
	return;
      }

    number  = atoi( record + 1 );
    if ( number ==  timesSeen )
      {
	if ( verbose  >  1 )
	    fprintf( stdout, "Number match!\n" );
	recordHistory( ++pastNumber );
      }
  }





/*  If the invocation includes a data file (presumably for demo purposes),
    we search it for the barcode and return the specified text.  The format
    of the data file is:
    <Exact Barcode text><TAB character><Exact text to be displayed>

    The text to be displayed must be pre-formatted to fit into 20-column
    rows. 
*/
int foundCode( char *code, char *outline, size_t outsize )
  {
    FILE   *in;
    char    tcode[ 128 ];
    char    data[ 4096 ];
    int	    len, rlen;
    int	    found   = FALSE;
    char   *record;


/*  Check to see if a data file was specified.  Try to open it if so.
*/
    if ( !datafile[ 0 ]  ||  !outline )
	return	0;
    in	= fopen( datafile, "r" );
    if ( !in )
      {
	if ( verbose  >  1 )
	    fprintf(stdout, "can't open datafile %s\n", datafile );
	return	0;
      }


/*  Get the passed barcode, remove trailing whitespace, then append a tab.
*/
    strcpy( tcode, code );
    len	= strip( tcode );
    if ( !len )
      {
	if ( verbose  >  1 )
	    fputs( "filename stripped to null string", stdout);
	return	0;
      }

    registerHistory( tcode );	    /* Dejau Vu?			    */

    strcat( tcode, "\t" );
    len++;


/*  Compare the barcode (including tab character) to start of lines in the
    file.
*/
    while ( !found  &&  !feof( in )  &&  !ferror( in ))
      {
	fgets( data, sizeof data, in );
	if ( feof( in )  ||  ferror( in ))
	    break;

	if (isspace( data[ 0 ]))
	    continue;

	rlen	= strip( data );
	if ( !rlen )
	    continue;

	if ( !strncmp( tcode, data, len ))
	  {
	    record  = data + len;
	    rlen    -= len;
	    if ( rlen  <=  0 )		/* Anything in the data record?	    */
		continue;

	    setNthRecord( record, rlen );	/* Records correct hit	    */
	  }
      }

    fclose( in );

    if ( lastRecord[ 0 ] )		/* Anything appropriate found?	    */
      {
	strncpy( outline, lastRecord, outsize );
	if ( verbose  >  1 )
	    fprintf( stdout, "Data Record [%s]\n", outline );
	return	strlen( outline ) + 1;	/* Including terminating NUL	    */
      }

    *outline    = 0;
    return  0;
  }


/*			  SLOPPI utility functions			    */

/*  Return the name of the specified token as a string.
*/
char   *tokenName( SLOPPITOKEN tkn )
  {
    int	    i;

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

    return  "(unknown)";
  }

char   *tokenString( SLOPPITOKEN tkn )
  {
    static char	tstr[ 5 ];

    memmove( tstr, &tkn, 4 );
    tstr[ 4 ]	= 0;

    return  tstr;
  }



/*  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;
  }


/*  Formatted dump of a nominal packet's content.
*/
void	dumpNominalPacket( char *intro, SLPACKET *pkt )
  {
    SLOPPITOKEN	slt;

    fprintf( stdout, "\n%s nominal mode packet, length %lu\n",
	     intro, (ulong)ntohl( pkt->header.length ));

    slt	= pkt->header.token;
    fprintf( stdout, "Type   %s (%s)\n", tokenName( slt ), tokenString( slt ));

    switch ( slt )
      {
	case	SLT_TERMINATE:
	  {
	    fprintf( stdout, "(no data)\n" );
	    break;
	  }

	case    SLT_CAPQUERY:
	case    SLT_CAPRESPONSE:
	case    SLT_MODESETQUERY:
	case    SLT_MODERESPONSE:
	case    SLT_PRODUCTQUERY:
	  {
	    fprintf( stdout, "%s\n", (char *)&pkt->body );
	    break;
	  }

	case    SLT_ERRREPORT:
	  {
	    FILE   *elog;
	    time_t    theTime;
	    theTime = time( NULL );

	    elog    = fopen( "error.log", "a" );
	    if ( elog )
	      {
		fprintf( elog, "%s%s\n", ctime( &theTime ), (char *)&pkt->body );
		fclose( elog );
	      }
	    fprintf( stdout, "%s\n", (char *)&pkt->body );
	    break;
	  }

	case    SLT_DATA:
	  {
	    SLOPPIDATA	dtyp;

	    dtyp    = (SLOPPIDATA)pkt->body.data.type;
	    switch ( dtyp )
	      {
		case	SLD_NOP:
		  {
		    fprintf( stdout, "NOP\n" );
		    break;
		  }

		case    SLD_DISPLAYTEXT:
		  {
		    fprintf( stdout, "DISPLAYTEXT: %s\n", pkt->body.data.data.info );
		    break;
		  }

		case    SLD_SPECIALTEXT:
		  {
		    fprintf( stdout, "SPECIALTEXT: %s\n", pkt->body.data.data.info );
		    break;
		  }

		case    SLD_UNC:
		  {
		    fprintf( stdout, "UNC: %s\n", pkt->body.data.data.info );
		    break;
		  }

		case    SLD_URL:
		  {
		    fprintf( stdout, "URL: %s\n", pkt->body.data.data.info );
		    break;
		  }

		default:
		  {
		    fprintf( stdout, "(unknown type %d)\n", dtyp );
		    break;
		  }
	      }
	    break;
	  }

	case    SLT_REGISTRATION:
	  {
	    char   *str;
	    time_t    theTime;
	    theTime = time( NULL );

	    str	= pkt->body.registration.reginfo;
	    fprintf( stdout, "unitID:    %s\n", str );
	    str	+= strlen( str ) + 1;
	    fprintf( stdout, "productID: %s\n", str );
	    str	+= strlen( str ) + 1;
	    fprintf( stdout, "IPaddress: %s\n", str );

	    if ( *regfilename )
	      {
		regfile	= fopen( regfilename, "a" );
		if ( regfile )
		  {
		    str	= pkt->body.registration.reginfo;
		    fprintf( regfile, "%s\t", str );
		    str	+= strlen( str ) + 1;
		    fprintf( regfile, "%s\t", str );
		    str	+= strlen( str ) + 1;
		    fprintf( regfile, "%s\t%s", str, ctime( &theTime ));
		    fclose( regfile );
		  }
	      }
	    break;
	  }

	case	SLT_EVENT:
	  {
	    SLEVENT    *evnt	= &pkt->body.events;

	    NewDn   = ntohl( evnt->starting );
	    NewUp   = ntohl( evnt->ending );
	    fprintf( stdout, "%s New Starts: %08lX  New Stops: %08lX  at %ld\n",
		     evnt->unitID, NewDn, NewUp, ntohl( evnt->tickTime ));
	    break;
	  }

	default:
	  {
	    break;
	  }
      }
    fputs( "\n", stdout ); fflush( stdout );
  }



void	processCommandLine( int argc, char *argv[] )
  {
    int	    setdata = FALSE;
    int	    setport = FALSE;
    int	    settime = FALSE;
    int	    arg, c, p, balaTest;

    for ( arg = 1; arg < argc; arg++ )
      {
	c   = argv[ arg ][ 0 ];
	if ((c  ==  '-')  ||  (c  ==  '/'))
	  {
	    c   = toupper( argv[ arg ][ 1 ]);

	    switch ( c )
	      {
		case	'B':
		  {
		    balaTest	= TRUE;
		    break;
		  }

		case	'C':
		  {
		    useconsole	= TRUE;
		    fprintf( stdout, "Using console for input\n" );
		    break;
		  }


		case    'D':
		  {
		    strncpy( datafile, argv[ arg ] + 2, sizeof datafile );
		    fprintf( stdout, "Using data file %s\n", datafile );
		    break;
		  }

		case    'L':
		  {
		    strncpy( logfilename, argv[ arg ] + 2, sizeof logfilename );
		    fprintf( stdout, "Logging to %s\n", logfilename );
		    break;
		  }

		case	'H':
		  {
		    strncpy( host, argv[ arg ] + 2, sizeof host );
		    fprintf( stdout, "Identifying myself as host %s\n", host );
		    break;
		  }

		case	'P':
		  {
		    c   = toupper( argv[ arg ][ 2 ]);

		    switch ( c )
		      {
			case	'T':
			case	'N':
			  {
			    trivial = (c == 'T');
			    break;
			  }
			default:
			  {
			    fprintf( stdout, "Unknown protocol mode: %s\n",
				     argv[ arg ] + 2 );
			    break;
			  }
		      }
		    break;
		  }

		case    'R':
		  {
		    strncpy( regfilename, argv[ arg ] + 2, sizeof regfilename );
		    fprintf( stdout, "Registrations to %s\n", regfilename );
		    break;
		  }

		case    'T':
		  {
		    threshhold	= atoi( &argv[ arg ][2] );
		    fprintf( stdout, "Threshhold is %d seconds\n",
			     (int)threshhold );
		    break;
		  }

		case	'V':
		  {
		    if ( verbose++ )
			fprintf( stdout, "Even more verbose\n" );
		    else
			fprintf( stdout, "Verbose\n" );
		    break;
		  }

		case    '?':
		  {
		      help:
		    fputs( "\nSample ScanVue server, " VERSION "\n"
			   "\ndemoserv [options] [port]\n\n"
			   "Options\n"
			   "-console     Communicate with console, not network.\n"
			   "-ddatafile   'datafile' is the name of the file to use\n"
			   "             for barcode lookups.  Default is DATA.DAT\n"
			   "-hhost       'host' is used as the host name.  Usually\n"
			   "             an IP address for multi-homed systems\n"
			   "-llogfile    'logfile' is the name of the file used to\n"
			   "             record all incoming barcodes.\n"
			   "-rregfile	 'regfile' is the name of the file used to\n"
			   "             record unit registrations.\n"
			   "-pprotocol   'protocol' is either 'TRIVIAL' or 'NOMINAL'\n"
			   "             Sets the format of responses to queries.\n"
			   "-tthreshold  The time, in seconds, that another scan of\n"
			   "             the same barcode is treated as a follow-up.\n"
			   "-v           Print extra information about transactions.\n"
			   "             May be used more than once for even more info.\n"
			   "port         The TCP/IP port number to monitor for product\n"
			   "             information queries.  The range is 1024-65535\n"
			   "             and the default is 1283\n",
			   stdout );
		    exit( EXIT_SUCCESS );
		  }

		default:
		  {
		    fprintf( stdout, "Unknown: %s\n", argv[ arg ]);
		    break;
		  }
	      }
	  }
	else
	  {
	    p   = atoi( argv[ arg ]);
	    if ((p < 1024)  ||  (p > 65535))
	      {
		fputs( "\nPort out of range", stdout );
		goto    help;
	      }

	    port    = p;
	  }
      }


    fprintf( stdout, "Using %s format packets for responses.\n",
	     trivial ? "trivial" : "nominal" );
    if ( !*logfilename )
	fprintf( stdout, "Not logging received barcodes\n" );
    if ( !*regfilename )
	fprintf( stdout, "Not logging Registrations\n" );
    if ( !setdata )
	fprintf( stdout, "Using data file %s\n", datafile );
    if ( !setport  &&  !useconsole )
	fprintf( stdout, "Using port %d\n", port );
    if ( !settime )
	fprintf( stdout, "Threshhold time in seconds: %d\n", (int)threshhold );


  }


int convertControl( char *s )
  {
    int i, l, ch;

    l	= strlen( s );
    for ( i = 0; i < l - 1; i++ )
      {
	if ( s[i]  ==  '\\' )
	  {
	    ch	= s[ i+1 ];
	    if ((ch != 'r')  &&  (ch != 't')  &&  (ch != 'n'))
		continue;
	    if ( ch  ==  'r' )	s[i] = '\r';
	    if ( ch  ==  't' )	s[i] = '\t';
	    if ( ch  ==  'n' )	s[i] = '\n';
	    memmove( s + i + 1, s + i + 2, l - i - 2 );
	    s[ --l ]	= 0;
	  }
      }

    return  strlen( s );
  }



int main( int argc, char* argv[])
  {
    int	    t, c, x;
    int	    listenSocket    = 0;
    int	    cnt = -1;
    char   *rep;
    char   *tosend;

    SLPACKET *ipkt   = (SLPACKET *)&rcv;
    SLPACKET *opkt   = (SLPACKET *)&xmit;

    fprintf( stdout, "Version " VERSION "\n" );

    processCommandLine( argc, argv );

/*  If not using the console for I/O, create a listening socket.
*/
    if ( !useconsole )
      {
	listenSocket   = establish( port );
	if( verbose  >  1 )fprintf(stdout, "establish returned %d\n", listenSocket );
	if ( listenSocket  <=  0 )
	    exit( EXIT_FAILURE );
      }

    t	= 0;
    while ( TRUE )
      {
	memset( rcv, 0, sizeof rcv );	/* Setwardship			    */
	if ( useconsole )
	  {
	    fprintf( stdout, "listening: " );
	    fflush( stdout );
	    fgets( rcv, sizeof rcv, stdin );
	    c   = strlen( rcv );
	  }
	else
	  {
	    if ( t )			/* Close last comm socket	    */
	      {
		if ( verbose )
		    fprintf( stdout, "Closing connection %d\n", t );
		CloseSocket( t );
		t	= 0;
	      }
	    if ( verbose )
		fprintf( stdout, "\nWaiting for connection\n" );
	    t   = get_connection( listenSocket );
	    if ( verbose )
		fprintf( stdout, "Got connection %d\n", t );
	    if ( t  <=  0 )
	      {
		if ( errno  ==  EINTR )	/* EINTR might happen on accept(),  */
		  {
		    if ( verbose )
			fprintf( stdout, "Got EINTR\n" );
		    continue;		/*  try again			    */
		  }
		perror( "accept" );	/* bad				    */
		exit( 1 );
	      }
	    cnt++;			/* Count the number of connects	    */

/*  If we were handling multiple simultaneous connections, we would fork()
    and let the child process do the work.
*/
	    c   = recv( t, rcv, sizeof rcv, 0 );
	    if (verbose  >  1 )
		fprintf(stdout, "read returned %d\n", c);
	    if ( c  <  0 )
	      {
		if ( verbose )
		    fprintf( stdout, "Error on read: %d\n", c );
		continue;
	      }
	  }


/*  Right now, we don't handle nominal-mode packets except for product
    queries.  We handle those by making it look like a tirival mode query.
*/
	if ( !rcv[ 0 ] )		/* A Nominal mode packet?	    */
	  {
	    if ( verbose )
		fprintf( stdout, "Received a Nominal-mode packet\n" );
	    if ( verbose > 1 )
	      {
		time_t	now;
		now = time( NULL );
		fprintf( stdout, "%s", ctime( &now ));
		dumpNominalPacket( "Received", ipkt );
	      }
#if 0
	    if ( packetToken( ipkt )  !=  SLT_EVENT )
	      {
		char   *swx[]	=
		  {
		    "    ", "   1", "  2 ", "  21",
		    " 4  ", " 4 1", " 42 ", " 421",
		    "8   ", "8  1", "8 2 ", "8 21",
		    "84  ", "84 1", "842 ", "8421"
		  };

		x   = sprintf( tosend, "NewDn %s\r\nNewUp %s",
			       swx[ NewDn ], swx[ NewUp ] );
		socksend( t, tosend, x, TRUE );
		continue;
	      }
#endif
	    if ( packetToken( ipkt )  !=  SLT_PRODUCTQUERY )
		continue;

	    c   = strlen( ipkt->body.prodquery.product );
	    memmove( rcv, ipkt->body.prodquery.product, c + 1 );
	  }


/*  Clear off trailing NULs and whitespace.
*/
	c   = strip( rcv );
	if ( c  <=  0 )			/* Anything left?		    */
	  {
	    if ( verbose )
		fprintf( stdout, "Stripped input to nothing\n" );
	    continue;
	  }

	if ( verbose )
	  {
	    time_t	now;
	    now = time( NULL );
	    fprintf( stdout, "%s<%s<\n", ctime( &now ), rcv );
	  }


/*  Input format is: UnitID<TAB>BarcodeData.  Find the first character
    after the tab, else use the entire string.
*/
	if  ( useconsole )		/* Input from console is used raw   */
	    rep = rcv;
	else
	  {
	    rep = strchr( rcv, '\t' );	/* Skip past the tab		    */
	    if ( rep )
		rep++;
	    else
		rep = rcv;
	  }
	if ( verbose )
	    fprintf( stdout, "(%s)\n", rep );


/****************************************************************************
*									    *
*   Here, rep points to the barcode data.  Whatever database lookup is to   *
*   be done to determine the response is done now.  If a history of         *
*   activity is kept by the server, it must know the unitID unless there    *
*   is only a single unit.  This information comes on the request line      *
*   unless the host has configured the unit otherwise			    *
*									    *
****************************************************************************/


/****************************************************************************

    The following code is for testing mixed text and graphics mode.  Define
    these macros with values 0 or 1 when compiling:

	MIXEDTEXTTEST	1 enables the mixed-mode test for a specific
			barcode.  The default is 0; only the data file
			content is used to determine what to send.

	GRAPHICINLINE	1 enables the server to send the file back along
			the already-established connection to the ScanVue.
			The default 0 makes the ScanVue fetch the file from
			its file server on another connection.

	SINGLEMESSAGE	The default 1 sends a single text message to
			overlay the graphic. 0 causes the server to send
			several messages with various timing.

****************************************************************************/

#ifndef	MIXEDTEXTTEST
#define	MIXEDTEXTTEST	1
#endif

#if	MIXEDTEXTTEST

#ifndef	GRAPHICINLINE
#define	GRAPHICINLINE	1
#endif
#ifndef	SINGLEMESSAGE
#define	SINGLEMESSAGE	1
#endif

/*  Special treatment for testing.
*/
	if ( !strcmp( rep, "B11111116" )    ||	/* Special magic	    */
	     !strcmp( rep,   "1111116" )    ||
	     !strcmp( rep, "mix"       ))
	  {
	    tosend  = xmit;


#if	GRAPHICINLINE
	    x	= sprintf( tosend, "%s", "<Images/sample.gif" ) + 1;
	    sendfile( t, tosend );
#else
	    x	= sprintf( tosend, "%s", "/Images/sample.gif" ) + 1;
	    socksend( t, tosend, x, TRUE );
#endif
#if SINGLEMESSAGE
	    x	= sprintf( tosend, "%s", "Mixed Text" ) + 1;
	    socksend( t, tosend, x, TRUE );
#else
	    x	= sprintf( tosend, "%s", "Wait one second" ) + 1;
	    socksend( t, tosend, x, TRUE );
	    sleep( 1 );

	    x	= sprintf( tosend, "%s", "Wait five seconds" ) + 1;
	    socksend( t, tosend, x, TRUE );
	    sleep( 5 );

	    x	= sprintf( tosend, "%s", "Wait ten seconds" ) + 1;
	    socksend( t, tosend, x, TRUE );
	    sleep( 10 );

	    x	= sprintf( tosend, "%s", "Stay up PTO seconds" ) + 1;
	    socksend( t, tosend, x, TRUE );
#endif
	    continue;
	  }
#endif


/*  Look in the data file if we have one, and return the indicated text.
*/

/*  tosend is pointed past where a nominal-mode packet header ends.  We set
    up the data first, then decide how to send it.
*/
	memset( xmit, 0, sizeof xmit );	/* Stewardship			    */
	tosend	= xmit + DATAHEADERSIZE;

	x   = foundCode( rep, tosend, sizeof xmit - DATAHEADERSIZE );
	if ( verbose  >  1 )
	    fprintf( stdout, "foundCode returned %d\n", x );

/*  x is the length of the string in tosend if the code was found, and zero
    if nothing was found.

    If the text we're given starts with a left angle bracket, we send the
    file out along this socket to avoid FTP and SMB setup overhead.

    If nothing is found, send back the barcode and a sequence number.
*/
	if ( x )
	  {
	    if ( tosend[ 0 ]  ==  '<' )	/* Send the file?		    */
	      {
		if ( verbose  >  1 )
		    fputs("sending file\n", stdout);
		if ( sendfile( t, tosend ))
		    continue;
	      }
	  }
	else
	    x   = sprintf( tosend, "%s\r\n  %d", rep, cnt ) + 1;


/*  In trivial mode, we just send the data we got.

    In nominal mode we have to initialize the packet header before we send
    the packet.
*/
	if ( trivial )
	  {
	    x	= convertControl( tosend );
	    if ( verbose  >  1 )
		fprintf(stdout, "trivial %d, [%s], %d\n", t, tosend, x);
	    socksend( t, tosend, x, TRUE );
	  }
	else
	  {
	    x	+= DATAHEADERSIZE;
	    opkt->header.length	= htonl(  x );
	    opkt->header.token	= SLT_DATA;

	    opkt->body.data.type    = SLD_DISPLAYTEXT;

	    if ( verbose  >  1 )
		dumpNominalPacket( "Sending", opkt );

	    socksend( t, xmit, x, trivial ? 1 : 0 );
	  }


/*  Log the input as indicated by the command line.
*/
	if ( *logfilename )
	  {
	    logfile = fopen( logfilename, "a" );
	    if ( logfile )
	      {
		fprintf( logfile, "%s\n", rep );
		fclose( logfile );
	      }
	  }
      }

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