/*  InputDemo.C	14-Mar-03 11:57:56 by Kevin D. Quitt

    Touch and other input demonstration server.  Based on Demoserv.

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

			===========================
			V E R Y   I M P O R T A N T
			===========================
    
    DO NOT USE THIS PROGRAM IN A PRODUCTION ENVIRONMENT.  THIS IS SAMPLE
    CODE ONLY; WHILE THERE ARE NO KNOWN PROBLEMS, NO CLAIMS ARE MADE THAT
    THIS PROPGRAM IS SUITABLE FOR ANY USE OTHER THAN BEING SAMPLE CODE.

    THIS CODE HAS BEEN SUCCESSFULLY COMPILED AND RUN UNDER SEVERAL FLAVORS
    OF UNIX, LINUX, VAX/VMS, AND WINDOWS (BOTH VISUAL C++ AND THE GNU
    SUITE OF TOOLS).  NUMEROUS CHANGES HAVE BEEN MADE SINCE THIS PROGRAM
    HAS BEEN VERIFIED ON ANY SYSTEM BUT WINDOWS; OPERATION ON OTHER
    PLATFORMS MAY REQUIRE SOME CHANGES.  PLEASE ADVISE US OF THOSE
    REQUIRED CHANGES FOR THESE OR OTHER PLATFORMS.

    Demoserv was originally written to debug and test the ProductInfo
    client.  It was later expanded and cleaned up to be usable as sample
    code for programmers who are knowledgeable about database access but
    not necessarily familiar with network programming and/or the Product
    Info protocol.

    Due to continued expansion of the Client's functionality, more code has
    been added - to the point where this program is no longer really an
    example of good coding practices.  The network functions
    (get_connection, establish, makeConnection, sockread, socksend) are
    rock-solid and taken (slightly modified) from _UNIX Network
    Programming_, Vol 1, by W. Richard Stevens.

    This program performs four functions:

    1. Create a listening socket for the ProductInfo server.

	This uses code straight from the book.  It is also why this
	program is unsuitable for a production environment: only a
	single connection at a time is accepted.  In an environment
	with multiple clients, it is guaranteed that some queries
	will fail because this server is busy.  To change this, see
	the function establish(). 

    2. Wait for a connection from a client and receive the barcode or
       other ProductInfo message.

	Again, only a single connection at a time.  To handle
	multiple simultaneous requests, a new process must be created
	in main to handle the request.  Because of the way this
	program processes data lookups, it is not practical to have
	simultaneous requests. 

    3. Perform a lookup for barcodes or based on other data.

	This is the main reason this program is not suitable for
	production: the data table is opened and searched linearly
	for a match.  This is fine for a demo, but performance is
	poor when there are more than a few dozen items.

	*** REPLACE THE MARKED SECTION WITH ACCESS TO YOUR DATABASE ***

    4. Send the response to the client.

	Again, code straight from the book for TCP/IP and ProductInfo
	protocol.


========== Modification History


 5-Jan-05 11:20, [ROCKYROAD]d:\isiarm\Sample Utilities\DemoServ\inputdemo.c, Edit by kdq
    Added dump of double-byte strings for verbosity > 2

15-Jun-04 15:06, [ROCKYROAD]d:\isiarm\Sample Utilities\InputDemo\inputdemo.c, Edit by kdq
    Added '@' to get text response from a file.

 7-Oct-03 18:54, [ROCKYROAD]d:\isiarm\Sample Utilities\InputDemo\inputdemo.c, Edit by kdq
    Merged in latest demoserv.c to make this program a proper superset.

22-Jan-03 15:47, [IEE]\isiarm\Sample Utilities\DemoServ\demoserv.c, Edit by kdq
    Added -f option to send multiple images in response to a single query.

 7-Jan-03 13:40, [IEE]\isiarm\Sample Utilities\DemoServ\demoserv.c, Edit by kdq
    Add compile-time option to generate TextReset test rather than mixed-
    text test.

31-Dec-02 10:42, [IEE]\isiarm\Sample Utilities\DemoServ\demoserv.c, Edit by kdq
    Modified processEvent to correctly report Keyboard, Button, and Touch
    events separately.

28-Aug-02 15:49, [IEE]\isiarm\Sample Utilities\DemoServ\demoserv.c, Edit by kdq
    Added double-byte handling.

 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	"V7.02"

#define	INIT	"INIT"
#define	EXIT	"EXIT"
#define	TIMEOUT	"TIMEOUT"
#define	MSR	"MSR"
#define	SCAN	"SCAN"

#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>
#include <process.h>    /* _beginthread, _endthread */
#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;
#else
#define	CloseSocket(s)	close(s)
#endif

#ifndef	TEXTRESETTEST
#define	TEXTRESETTEST	( 0 )
#endif

#include    "inputdemo.h"
#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"

/*  Event definitions come from IEElib.h
*/
#define	PIEVENT_PB0	(0x00000001)	/* Push button 0		    */
#define	PIEVENT_PB1	(0x00000002)	/* Push button 1		    */
#define	PIEVENT_PB2	(0x00000004)	/* Push button 2		    */
#define	PIEVENT_PB3	(0x00000008)	/* Push button 3		    */
#define	PIEVENT_DOWN	(0x00000010)	/* Touch screen down		    */
#define	PIEVENT_POINTER	(0x00000010)	/* Mouse/Touch screen up/down	    */
#define	PIEVENT_KEYBD	(0x00000020)	/* Keyboard press		    */
#define	PI_BUTTONS  (PIEVENT_PB0 | PIEVENT_PB1 | PIEVENT_PB2 | PIEVENT_PB3)



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

static INIFILE	IniFile;
static INIFILE *IFP = &IniFile;

const uchar FE	= 0xFE;
const uchar FF	= 0xFF;

#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		    */
int	useShow	    = FALSE;		/* Use ShowMessage, not DisplayText */
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;
ushort	port	    = SLOPPIPORT;	/* Communications port number	    */

ulong	NewDn, NewUp, keyChar;
int	cnt = 0;

int	MixedTextTest;
int	MixedTextGraphic    = TRUE;
char	TestBarcode[ 1024 ] = {0};
int	InLineGraphic;
int	SingleResponse	    = TRUE;
int	showKeyCode	    = FALSE;
int	AddOut, IgnoreIn;
int	SendMultiple	    = 0;
int	eventsArePrinted    = FALSE;

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

/*  Controls for special demo - handles touch, msr, scan
*/
#define	INPUT_TOUCH	( 1 )
#define	INPUT_MSR	( 2 )
#define	INPUT_SCAN	( 3 )
#define	INPUT_TIMEOUT	( 4 )

int	    useTouchDemo    = FALSE;
SECTION	   *currentState    = NULL;
int	    touchSock	    = 0;	/* If using passed socket	    */
ulong	    touchIP	    = 0;	/* If being passed an IP	    */
char	   *touchMSRScan    = NULL;	/* Data from MSR/Scanner	    */
uint	    touchPoint	    = 0;
int	    touchTimeout    = 0;	/* Infinite			    */
char	    touchRootDir[ 256 ]	= {0};
char	   *touchLastImage  = NULL;
time_t	    touchNextTouch  = 0;

//  Touch state controls
int	touchProcessTouch   = FALSE;	/* Touch input starts script	    */
int	touchProcessMSR	    = FALSE;	/* MSR input starts script	    */
int	touchProcessScan    = FALSE;	/* Scanner input starts script	    */

char	vcoord[]    = "ABCDEF";
char	hcoord[]    = "01234567";

int	vtouch, htouch;

char   *HelpText
   =	"\nSample ProductInfo server, " VERSION "\n"
	"\ndemoserv [options] [port]\n\n"
	"Options\n"
	"-acount      Add specified count of bytes to the beginning\n"
	"             of every response.\n"
	"-b           Use SLD_SHOWMESSAGE instead of SLD_DISPLAYTEXT\n"
	"             to support double-byte fonts.\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"
	"-e           Touch and button events cause printing/\n"
	"-fcount      Send multiple images in response to the barcode\n"
	"             specified by the -m option.  Mixed text testing\n"
	"             is overridden.\n"
	"-g           Send graphics in-line.\n"
	"-hhost       'host' is used as the host name.  Usually\n"
	"             an IP address for multi-homed systems\n"
	"-icount      Ignore the first count bytes from each request.\n"
	"-k           The keycode received from keypresses are omitted\n"
	"             from the code used to lookup in data.dat; only\n"
	"             \"EV6\" is used instead of (e.g.) \"EV6A\" for\n"
	"             a keycode that has a hex value of 41.\n"
	"-llogfile    'logfile' is the name of the file used to\n"
	"             record all incoming barcodes.\n"
	"-mbarcode    Send mixed graphics and text, using indicated\n"
	"             barcode as the trigger.  If the barcode starts\n"
	"             with a hypen, the graphic is not sent.\n"
	"-pprotocol   'protocol' is either 'TRIVIAL' or 'NOMINAL'\n"
	"             Sets the format of responses to queries.\n"
	"-rregfile    'regfile' is the name of the file used to\n"
	"             record unit registrations.\n"
	"-s           Send multiple text responses to -m barcode\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"
	"-z           Special input demo for touch/msr.  Use TOUCH.DAT.\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"
	;


/*  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 ] & 0xFF;	/* Not sign-extended		    */
	if ( !ch  ||  isspace( ch ))
	    string[ --c ]   = NUL;	/* NUL fill			    */
	else
	    break;
      }

    return  c;
  }


/*  Convert a 32-bit IP value to its string representation.
*/
char	*IPtoQuad( char *theQuad, ulong ip )
  {
    static char	quad[ 16 ];

    if ( !theQuad )
	theQuad	= quad;

    sprintf( theQuad, "%lu.%lu.%lu.%lu",
	     (ip >> 24) & 0xFF,
	     (ip >> 16) & 0xFF,
	     (ip >>  8) & 0xFF,
	     (ip >>  0) & 0xFF );

    return  theQuad;
  }



//		General purpose socket utilities, BSD-style.

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

    if ( verbose  >  1 )
      {
	fprintf( stdout, "Waiting for connection on %d\n", s );
	fflush( stdout );
      }

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


    if ( verbose  >  1)
      {
	fprintf( stdout, "get_connection: accept returned %d\n", t );
	if ( t  >  0 )
	    fprintf( stdout, "Connection from %d.%d.%d.%d\n",
		     (unsigned char)addr.sa_data[ 2 ],
		     (unsigned char)addr.sa_data[ 3 ],
		     (unsigned char)addr.sa_data[ 4 ],
		     (unsigned char)addr.sa_data[ 5 ] );
	fflush( stdout );
      }

    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 ( verbose  >  1 )
      {
	fprintf( stdout, "establish: socket is %d\n", s );
	fflush( stdout );
      }
    if ( s  <  0 )
      {
	fputs( "socket failed", stdout );
	return	-1;
      }

/*	THIS LINE LIMITS THE NUMBER OF SIMULTANEOUS CONNECTION REQUESTS TO
  	JUST ONE.  IF A SECOND CLIENT TRIES TO CONNECT IT WILL BE REFUSED.
  	IF YOU INCREASE THIS NUMBER, MORE REQUESTS WILL QUEUE UP, BUT THEY
  	WILL NOT BE PROCESSED UNTIL ALL PREVIOUS REQUESTS ARE COMPLETE.
*/
    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	    */
    if ( verbose  >  1 )
      {
	fprintf( stdout,"establish: listening on %d for %d\n", s, MAXCONNECT );
	fflush( stdout );
      }
    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

/*  Create socket
*/
    touchSock	= socket( AF_INET, SOCK_STREAM, 0 );
    if ( touchSock  ==  -1 )
      {
	touchSock   = 0;
	return	FALSE;
      }

#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

    IPtoQuad( remoteHost, touchIP );
    if ( verbose  >  1 )
      {
	printf( "makeConnection to %08lx: %s\n", touchIP, remoteHost );
	fflush( stdout );
      }

    phe	= gethostbyname( remoteHost );

    if ( !phe )
      {
	CloseSocket( touchSock );
	touchSock   = 0;
	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;

/*  connect
*/
    status  = connect( touchSock, (struct sockaddr *)&remote_sin,
		       sizeof remote_sin );
    if ( status  ==  -1 )
      {
	CloseSocket( touchSock );
	touchSock   = 0;
	return	FALSE;
      }

    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;
    if ( verbose )
      {
	fprintf( stdout, "sock read %d\n", len );
	fflush( stdout );
      }

    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 );
	fflush( stdout );
      }

    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 ));
		fflush( stdout );
		return;
	      }
	    fprintf( stdout, "(%d bytes of the file)\n", len );
	    fflush( stdout );
	    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" );
	fflush( stdout );
      }

/*  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 );
		fflush( stdout );
	      }
	    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 );
		    fflush( stdout );
		  }
		len	= 0;
	      }
	  }
      }
  }


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

    if ( !fnam )
	return	0;

    switch ( fnam[ 0 ] )
      {
	case	'<':
	  {
	    isText  = FALSE;
	    break;
	  }

	case    '@':
	  {
	    isText  = TRUE;
	    break;
	  }

	default:
	  {
	    return  0;
	  }
      }

    strncpy( fnamebuf, fnam + 1, sizeof fnamebuf );
    fbuff   = strchr( fnamebuf, ';' );
    if ( fbuff )
	*fbuff	= 0;

    imm = fopen( fnamebuf, "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 )
	  {
	    if ( verbose )
	      {
		fprintf( stdout, "Read failed\n" );
		fflush( stdout );
	      }
	    xfer    = 0;
	  }
	else
	  {
	    if ( isText )
	      {
		fbuff[ fsize++ ]    = 0;	// Double-Byte font
		fbuff[ fsize++ ]    = 0;
	      }
	    else
	      {
		socksend( sock, fnam, strlen( fnam ) + 1, 1 );

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

	    socksend( sock, fbuff, fsize, 0 );
	    xfer    = 1;
	  }

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

    return  xfer;
  }


/*  Sample database and utility functions.
*/
int	getPair( char *spec, int *v, int *h )
  {
    int	cv, ch;

    if ( !spec  ||  !*spec )
	return	FALSE;

    cv	= toupper( spec[ 0 ]);
    ch	= spec[ 1 ];

    if ( !strchr( vcoord, cv )  ||  !strchr( hcoord, ch ))
	return	FALSE;

    if ( v )
	*v  = cv - 'A';
    if ( h )
	*h  = ch - '0';

    return  TRUE;
  }

int	getRectangle( char *spec, int *t, int *l, int *b, int *r )
  {
    if ( !spec )
	return	FALSE;

    if ( !getPair( spec, t, l ))
	return	FALSE;

    if ( spec[ 2 ]  ==  '-' )
      {
	if ( !getPair( spec + 3, b, r ))
	    return	FALSE;
      }
    else
      {
	if ( t  &&  b )
	    *b	= *t;
	if ( l  &&  r )
	    *r	= *l;
      }

    return  TRUE;
  }


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

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

    foundWhen	= 0;
    timesSeen	= 0;
    okToPrint	= TRUE;
  }


int	maybePrintEvent( char *code )
  {
    return  !strncmp( code, "T-", 2 )	||
	    (!strncmp( code, "EV", 2 )  &&  ('-'  !=  code[ 3 ]));

  }


void	recordHistory( char *record )
  {
    if ( verbose  >  1 )
      {
	printf( "recordHistory\n" );
	fflush( stdout );
      }

    if ( maybePrintEvent( record ))
	return;

    if ( verbose  >  1 )
      {
	fprintf( stdout, "Save data: [%s]\n", record );
	fflush( stdout );
      }
    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;

    if ( maybePrintEvent( barcode ))
	return;

    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 " );
	fflush( stdout );
      }

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


/*  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" );
	    fflush( stdout );
	  }
	recordHistory( record );	/* Too small or not numbered	    */
	return;
      }

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

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

    number  = atoi( record + 1 );
    if ( number ==  timesSeen )
      {
	if ( verbose  >  1 )
	  {
	    fprintf( stdout, "Number match!\n" );
	    fflush( stdout );
	  }
	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 (default), or whatever the row count is set to in the INI file.
*/
int foundCode( char *code, char *outline, size_t outsize, int okToRecord )
  {
    FILE   *in;
    char    tcode[ 128 ];
    char    data[ 512 ];
    int	    len, rlen;
    int	    found   = FALSE;
    char   *record;
    int	    isTouch = FALSE;


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

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

    lastRecord[ 0 ]	= 0;

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

    isTouch = !strncmp( tcode, "T-", 2 );	// Touch-screen?
	 


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

	    if ( isTouch )
	      {
		int t, l, b, r;

		if ( getRectangle( record,&t, &l, &b, &r ))
		  {
		    if ((vtouch  >=  t)  &&  (vtouch  <=  b)  &&
			(htouch  >=  l)  &&  (htouch  <=  r))
		      {
			record	= pastNextWhite( record );
			if ( !record )
			    continue;
			if ( verbose  >  2 )
			  {
			    printf( "Touch %c%c found within %c%c-%c%c\n",
				    vcoord[ vtouch ], hcoord[ htouch ],
				    vcoord[ t ], hcoord[ l ],
				    vcoord[ b ], hcoord[ r ] );
			    fflush( stdout );
			  }
		      }
		    else		// Not in this record's rectangle
		      {
			if ( verbose  >  2 )
			  {
			    printf( "Outside of rectangle\n" );
			    fflush( stdout );
			  }
			continue;
		      }
		  }
		else
		  {
		    if ( verbose  >  1 )
		      {
			printf( "Can't get rectangle\n" );
			fflush( stdout );
		      }
		    continue;
		  }
	      }

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

    fclose( in );

    if ( lastRecord[ 0 ] )		/* Anything appropriate found?	    */
      {
	if ( !strncmp( tcode, "EV6", 3 ))
	    sprintf( outline, lastRecord, keyChar, keyChar );
	else
	    strncpy( outline, lastRecord, outsize );
	if ( verbose  >  1 )
	  {
	    fprintf( stdout, "Data Record [%s]\n", outline );
	    fflush( stdout );
	  }
	len = strlen( outline ) + 1;	/* Including terminating NUL	    */

	if ((lastRecord[ 0 ]  ==  '|')  &&
	    ((lastRecord[ 1 ]  ==  '>')  ||  (lastRecord[ 1 ]  ==  '<')))
	    len++;			/* Double NUL for double-byte text  */
	return	len;
      }

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


/*  Send a Nominal-mode SLOPPI packet.
*/
void	sendNominalPacket( int sock, SLOPPITOKEN token, char *buf, int len )
  {
    char	xmit[ XMTBUFFSIZE ];
    SLPACKET   *pkt	= (SLPACKET *)xmit;
    int		plen	= sizeof pkt->header;

    memmove( xmit + plen, buf, len );
    plen    += len;

    pkt->header.token	= token;
    pkt->header.length	= htonl( plen );

    socksend( sock, xmit, plen, FALSE );
  }


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

		case	SLD_SHOWMESSAGE:
		  {
		    fprintf( stdout, "SHOWMESSAGE: %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 );

/*  Keyboard, Touch Screen, and Button events are never mixed; they each
    use the extra area differently.
    Touch Screen and Button hardware is mutually exclusive.  Exclusivity
    with a keyboard is not guaranteed, but likely.
*/
	    if ( NewDn  &  PIEVENT_KEYBD )
	      {
		fprintf( stdout, "Got key x%02X (%c) at %lu\n",
			 ntohl( evnt->extras[ 0 ] ),
			 ntohl( evnt->extras[ 0 ] ),
			 (ulong)ntohl( evnt->tickTime ));
	      }

	    if ( NewDn  &  PI_BUTTONS )
	      {
		fprintf( stdout,
			 "%s New Starts: %08lX  New Stops: %08lX  at %lu\n",
			 evnt->unitID, NewDn, NewUp,
			 (ulong)ntohl( evnt->tickTime ));
	      }

	    if ( (NewDn  &  PIEVENT_POINTER)  ||  (NewUp  &  PIEVENT_POINTER))
	      {
		fprintf( stdout, "%s at %lu %lu\n",
			 (ntohl( evnt->extras[0] )  &  PIEVENT_DOWN) ?
			 "Touch" : "Release",
			 (ulong)ntohl( evnt->extras[1] ),
			 (ulong)ntohl( evnt->extras[2] ));
	      }
	    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;

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

	    switch ( c )
	      {
		case	'A':
		  {
		    if ( argv[ arg ][ 2 ] )
		      {
			AddOut   = atoi( &argv[ arg ][ 2 ] );
			fprintf( stdout, "Adding %d chars out\n", AddOut );
		      }
		    break;
		  }

		case	'B':
		  {
		    useShow = 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	'E':
		  {
		    eventsArePrinted	= TRUE;
		    break;
		  }

		case	'F':
		  {
		    if ( argv[ arg ][ 2 ] )
			SendMultiple	= atoi( &argv[ arg ][ 2 ] );
		    else
			SendMultiple	= 1;

		    fprintf( stdout, "Send %d image%s per barcode\n",
			     SendMultiple, SendMultiple  ==  1 ? "" : "s" );
		    break;
		  }

		case	'G':
		  {
		    InLineGraphic   = TRUE;
		    fprintf( stdout, "Using In-Line graphics\n" );
		    break;
		  }

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

		case	'I':
		  {
		    if ( argv[ arg ][ 2 ] )
		      {
			IgnoreIn    = atoi( &argv[ arg ][ 2 ] );
			fprintf( stdout, "Ignoring %d chars on input\n",
				 IgnoreIn );
		      }
		    break;
		  }

		case	'K':
		  {
		    showKeyCode	= TRUE;
		    fprintf( stdout, "Showing any key press as EV6\n" );
		    break;
		  }


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

		case	'M':
		  {
		    int	p   = 2;
		    if ( argv[ arg ][ p ]  ==  '-' )
		      {
			p++;
			MixedTextGraphic    = FALSE;
			fprintf( stdout, "No graphic\n" );
		      }

		    if ( argv[ arg ][ p ] )
		      {
			MixedTextTest	= TRUE;
			strncpy( TestBarcode, &argv[ arg ][ p ],
				 sizeof TestBarcode );
#if TEXTRESETTEST
			fprintf( stdout, "Text Reset test, barcode is [%s]\n",
				 TestBarcode );
#else
			fprintf( stdout, "Using Mixed text, barcode is [%s]\n",
				 TestBarcode );
#endif
		      }
		    else
		      {
			MixedTextTest	= FALSE;
			fprintf( stdout, "Not mixed text\n" );
		      }
		    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	'S':
		  {
		    SingleResponse  = FALSE;
		    fprintf( stdout, "Multiple text responses\n" );
		    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	'Z':
		  {
		    useTouchDemo    = !useTouchDemo;
		    break;
		  }

		case    '?':
		  {
		      help:
		    fputs( HelpText, stdout );
		    exit( EXIT_SUCCESS );
		  }

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

	    port    = (ushort)p;
	  }
      }


    if ( verbose )
      {
	fprintf( stdout, "Using %s format packets for responses.\n",
		 trivial ? "trivial" : "nominal" );
	if ( useShow )		fprintf( stdout, "Using SLD_SHOWMESSAGE.\n" );
	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 );
	fflush( stdout );
      }

    if ( SendMultiple )
      {
	if ( MixedTextTest )
	  {
	    fprintf( stdout, "Sending %d image%s in response to [%s]%s.\n",
		     SendMultiple, SendMultiple  ==  1 ? "" : "s",
		     TestBarcode,
		     MixedTextTest ? ", overriding Mixed text test" : "" );
	    MixedTextTest   = FALSE;
	  }
      }
  }


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 ) + 1;
  }


int hexVal( char ch )
  {
    int	    val = 0;
    char    chs[ 2 ];


    if ( isxdigit( ch ))
      {
	chs[ 0 ]    = (char)toupper( ch );
	chs[ 1 ]    = (char)0;
	val = strcspn( "0123456789ABCDEF", chs );
      }

    return  val;
  }

size_t	unHexify( uchar *buf, size_t len )
  {
    uchar  *ip	= buf;
    uchar  *op	= buf;
    uchar   ch;
    size_t  count   = 0;;

    while ( len )
      {
	ch  = *(ip++);
	if ( len  >  3 )
	  {
	    if ((ch     ==  '\\')   &&
		(ip[0]  == 'x')	    &&
		(isxdigit(ip[1]))   &&
		(isxdigit(ip[2])))
	      {
		ch  = (uchar)((hexVal( ip[ 1 ] ) << 4) + hexVal( ip[ 2 ]));
		ip  += 3;
		len -= 3;
	      }

	  }
	*(op++)	= ch;
	count++;
	len--;
      }

    *op	= 0;
    return  count;
  }

void	sendQuotedText( char *xmit, size_t len, int sock )
  {
    SLPACKET   *opkt	= (SLPACKET *)xmit;
    int		isdbt	= FALSE;
    char       *endian	= NULL;

    xmit    += DATAHEADERSIZE;
    if ( xmit[0]  ==  '|' )		/* Double byte text?		    */
      {
	if ( xmit[1]  ==  '>' )		/* Big endian?			    */
	  {
	    endian  = "Big";
	    xmit[0] = FE;
	    xmit[1] = FF;
	    isdbt   = TRUE;
	  }
	else if ( xmit[1]  ==  '<')	/* Little Endian?		    */
	  {
	    endian  = "Little";
	    xmit[0] = FF;
	    xmit[1] = FE;
	    isdbt   = TRUE;
	  }
      }

    if ( isdbt )			/* Double-byte text?		    */
      {
	if ( verbose  >  1 )
	  {
	    printf( "%s Endian Double-byte packet\n", endian );
	    fflush( stdout );
	  }

	len = unHexify( (uchar *)xmit + 2, len - 2 ) + 2;
      }
    else
      {
	len = unHexify( (uchar *)xmit + 1, len - 1 );
	memmove( xmit, xmit + 1, len );
      }

    if ( !trivial )
      {
	len	+= DATAHEADERSIZE;

	opkt->header.length	    = htonl(  len );
	opkt->header.token	    = SLT_DATA;
	if ( useShow )
	    opkt->body.data.type    = SLD_SHOWMESSAGE;
	else
	    opkt->body.data.type    = SLD_DISPLAYTEXT;
	xmit	= (char *)opkt;
	if ( verbose  >  1 )
	  {
	    dumpNominalPacket( "Sending", opkt );
	    fflush( stdout );
	  }
      }


    if ( trivial  &&  verbose > 2  &&  isdbt )
      {
	size_t	i;

	printf( "%d: ", len );
	for ( i = 0; i < len; i++ )
	    printf( "\\x%02X ", xmit[ i ] & 0xFF );
	printf( "\n" );
	fflush( stdout );
      }

    socksend( sock, xmit, len, 0 );
  }


/*============================ TOUCH DEMO ================================*/

void	initTouchDemo( void )
  {
    if ( verbose  >  1)
      {
	printf( "initTouchDemo\n" );
	fflush( stdout );
      }

    if ( touchSock )
      {
	close( touchSock );
	touchSock   = 0;
      }

    currentState    = NULL;
    touchMSRScan    = NULL;
    touchTimeout    = 0;

    touchProcessTouch	= FALSE;
    touchProcessMSR	= FALSE;
    touchProcessScan    = FALSE;
  }

void	disableTouchDemo( void )
  {
    if ( verbose  >  1)
      {
	printf( "disableTouchDemo\n" );
	fflush( stdout );
      }

    initTouchDemo();
    useTouchDemo    = FALSE;
  }

#define	TDSIZE	(128 * 1024)
int	loadTouchDemo( void )
  {
    FILE   *TDAT    = NULL;
    size_t  got	    = 0;
    char   *script  = NULL;
    int	    loaded  = FALSE;

    initTouchDemo();
    script  = malloc( TDSIZE );
    if ( !script )
      {
	disableTouchDemo();
	return	FALSE;
      }

    TDAT    = fopen( "touch.dat", "r" );
    if ( TDAT )
      {
	got = fread( script, 1, TDSIZE - 1, TDAT );
	fclose( TDAT );
      }

    if ( !TDAT  ||  !got )
      {
	free( script );
	initTouchDemo();
	return	FALSE;
      }

    loaded  = LoadIniFile( IFP, script, got );
    free( script );

    loaded  &= !!getINISection( IFP, INIT );

    return  loaded;
  }


int	tdMaybeOpen( void )
  {
    if ( touchSock )
	return	TRUE;
    return  makeConnection();
  }

void	tdSendMode( char *string, int keepOpen )
  {
    if ( verbose  >  1)
      {
	printf( "tdSendMode\n" );
	fflush( stdout );
      }


    if ( !string  ||  !tdMaybeOpen())
	return;

    sendNominalPacket( touchSock, SLT_MODESETQUERY, string, strlen( string )+1);

    if ( !keepOpen )
      {
	if ( verbose  >  1)
	  {
	    printf( "...closing socket\n" );
	    fflush( stdout );
	  }

	CloseSocket( touchSock );
	touchSock   = 0;
      }
    else
      {
	if ( verbose  >  1)
	  {
	    printf( "...leaving socket open\n" );
	    fflush( stdout );
	  }

      }
  }

void	tdSendFile( char *string, int keepOpen )
  {
    char    fnam[ 512 ];

    if ( !string )
	return;
    if ( !tdMaybeOpen())
      {
	if ( verbose  >  1 )
	  {
	    printf( "tdMaybeOpen failed\n" );
	    fflush( stdout );
	  }
	return;
      }

    touchLastImage  = string;

    if ( verbose  >  1)
      {
	printf( "tdSendFile\n" );
	fflush( stdout );
      }

    sprintf( fnam, "%s%s", touchRootDir, string );

    sendfile( touchSock, fnam );

    if ( !keepOpen )
      {
	if ( verbose  >  1)
	  {
	    printf( "...closing socket\n" );
	    fflush( stdout );
	  }

	CloseSocket( touchSock );
	touchSock   = 0;
      }
    else
      {
	if ( verbose  >  1)
	  {
	    printf( "...leaving socket open\n" );
	    fflush( stdout );
	  }
      }
  }

void	tdSendText( char *string, int keepOpen )
  {
    int	len;

    if ( !string  ||  !tdMaybeOpen())
	return;

    if ( verbose  >  1)
      {
	printf( "tdSendText\n" );
	fflush( stdout );
      }


    len	= strlen( string ) + 1;
    socksend( touchSock, string, len, FALSE );

    if ( !keepOpen )
      {
	if ( verbose  >  1)
	  {
	    printf( "...closing socket\n" );
	    fflush( stdout );
	  }

	CloseSocket( touchSock );
	touchSock   = 0;
      }
    else
      {
	if ( verbose  >  1)
	  {
	    printf( "...leaving socket open\n" );
	    fflush( stdout );
	  }
      }
  }


/*  The Touch demo's over.  Set the POS timeout on the unit to one second,
    wait that second, then set it to 10 seconds, and we're done.
*/
void	exitDemoProcessing( void )
  {
    if ( verbose  >  1)
      {
	printf( "exitDemoProcessing\n" );
	fflush( stdout );
      }

    tdSendMode( "POSTimeout=0", TRUE );
    tdSendFile( touchLastImage, TRUE );
    sleep( 1 );
    tdSendMode( "POSTimeout=10", FALSE );
    initTouchDemo();
  }


int	tpTouch( char *val )
  {
    touchProcessTouch	= boolVal( val, TRUE );

    if ( verbose  >  1)
      {
	printf( "Setting touchProcessTouch to %s\n", touchProcessTouch ? "TRUE" : "FALSE" );
	fflush( stdout );
      }

    return  TRUE;
  }

int	tpMSR( char *val )
  {
    touchProcessMSR	= boolVal( val, TRUE );

    if ( verbose  >  1)
      {
	printf( "Setting touchProcessMSR to %s\n", touchProcessMSR ? "TRUE" : "FALSE" );
	fflush( stdout );
      }

    return  TRUE;
  }

int	tpScan( char *val )
  {
    touchProcessScan	= boolVal( val, TRUE );

    if ( verbose  >  1)
      {
	printf( "Setting touchProcessScan to %s\n", touchProcessScan ? "TRUE" : "FALSE" );
	fflush( stdout );
      }

    return  TRUE;
  }

int	tpTimeout( char *val )
  {
    int	ret, tm;

    if ( !val )
	return	FALSE;

    ret	= sscanf( val, "%d", &tm );

    if ( verbose  >  1)
      {
	printf( "Setting touchTimeout to %d\n", tm );
	fflush( stdout );
      }

    touchTimeout    = tm;

    return  TRUE;
  }

int	tpImage( char *val )
  {
    if ( verbose  >  1)
      {
	printf( "Displaying image: %s\n", val );
	fflush( stdout );
      }
    if ( !val )
	return	FALSE;

    tdSendFile( val, FALSE );

    return  TRUE;
  }

int tpRootDir( char *val )
  {
    int	len;
    memset( touchRootDir, 0, sizeof touchRootDir );
    strcpy( touchRootDir, "<" );
    if ( val )
      {
	strcat( touchRootDir, val );
	len	= strlen( touchRootDir );
	if ( touchRootDir[ len - 1 ]  !=  '/' )
	    strcat( touchRootDir, "/" );
      }
    return  TRUE;
  }

int	tpExit( char *val )
  {
    if ( verbose  >  1)
      {
	printf( "tpExit\n" );
	fflush( stdout );
      }
    exitDemoProcessing();
    return  TRUE;
    (void)val;
  }


struct
  {
    char   *op;
    int	  (*func)(char *);
  } OPTABLE[]	=
  {
      { "Fake",	    NULL	},
      { "TOUCH",    tpTouch	},
      { "MSR",	    tpMSR	},
      { "SCAN",	    tpScan	},
      { "TIMEOUT",  tpTimeout	},
      { "IMAGE",    tpImage	},
      { "ROOT_DIR", tpRootDir	},
      { "EXIT",	    tpExit	},
      { NULL,	    NULL	}
  };

int isOP( char *op )
  {
    int	    i;

    for ( i = 1; OPTABLE[ i ].op; i++ )
      {
	if ( !stricmp( op, OPTABLE[ i ].op )	&&
	     OPTABLE[ i ].func )
	    return  i;
      }
    return  0;
  }


int processOP( char *op, char *parm )
  {
    int	    thisop;

    if ( !op )
	return	FALSE;

    thisop  = isOP( op );
    if ( !thisop )
	return	FALSE;

    if ( verbose  >  1)
      {
	printf( "processOp %s\n", strVal( op ));
	fflush( stdout );
      }

    return  OPTABLE[ thisop ].func( parm );
  }


int initializeState( SECTION *sec )
  {
    int	    tvp;

    if ( !sec )
	return	FALSE;

    if ( verbose  >  1)
      {
	printf( "initializeState %s\n", sec->Name );
	fflush( stdout );
      }

    if ( !stricmp( sec->Name, INIT ))
	tdSendMode( "POSTimeout=999999", FALSE );

    for ( tvp = 0; tvp < sec->TVCount; tvp++ )
      {
	if ( isOP( sec->TVPair[ tvp ].Token ))
	  {
	    if ( !processOP( sec->TVPair[ tvp ].Token,
			     sec->TVPair[ tvp ].Value ))
	    return  FALSE;
	  }
      }

    return  TRUE;
  }

int	touchEnterState( char *newState )
  {
/*  If no state specified, or the state is the same, do nothing.
*/
    if ( !newState )
	return	TRUE;

    if (currentState  &&  !stricmp( currentState->Name, newState ))
      {
	if ( verbose  >  1)
	  {
	    printf( "touchEnterState no change\n" );
	    fflush( stdout );
	  }
	return	TRUE;
      }

    if ( !stricmp( newState, EXIT ))
      {
	if ( verbose  >  1)
	  {
	    printf( "touchEnterState exit\n" );
	    fflush( stdout );
	  }
	exitDemoProcessing();
	return	TRUE;
      }

    if ( verbose  >  1)
      {
	printf( "touchEnterState try to enter %s ", newState );
	fflush( stdout );
      }
    currentState    = getINISection( IFP, newState );
    if ( !currentState )
      {
	if ( verbose  >  1)
	  {
	    printf( "failed\n" );
	    fflush( stdout );
	  }
	return	FALSE;			/* Force an exit		    */
      }
    if ( verbose  >  1)
      {
	printf( "succeeded\n" );
	fflush( stdout );
      }

    return  initializeState( currentState );
  }

int	touchInputTypeAllowed( int ityp )
  {
    int	    ok	= FALSE;

    switch ( ityp )
      {
	case	INPUT_TOUCH:
	  {
	    ok	= !!touchProcessTouch;
	    break;
	  }

	case	INPUT_MSR:
	  {
	    ok	= !!touchProcessMSR;
	    break;
	  }

	case	INPUT_SCAN:
	  {
	    ok	= !!touchProcessScan;
	    break;
	  }

	case	INPUT_TIMEOUT:
	  {
	    ok	= !!touchTimeout;
	    break;
	  }
      }

    return  ok;
  }


/*  Processing input changes us from one state to the next.
*/
char   *processTouchInput( SECTION *state, uint point )
  {
    int	    h, v, t, l, b, r;
    int	tvp;

    if ( !state )
	return	NULL;

    h	= point & 0xF;
    v	= (point >> 4) & 0xF;

    for ( tvp = 0; tvp < state->TVCount; tvp++ )
      {
	if ( isOP( state->TVPair[ tvp ].Token )	    &&
	     ( state->TVPair[ tvp ].Value   &&
	       isBoolVal( state->TVPair[ tvp ].Value )))
	    continue;

	if ( !getRectangle( state->TVPair[ tvp ].Token, &t, &l, &b, &r ))
	    continue;

	if ((v  <  t)  ||  (v  >  b))
	    continue;

	if ((h  <  l)  ||  (h  >  r))
	    continue;

	return	state->TVPair[ tvp ].Value;
      }

    return  NULL;
  }

/*  Process MSR and Scanner input.  We ignore the actual content for now.
    We also allow there to be more than one of a particular command, such
    as enabling the device and specifying the new state when it happens.
*/
char   *processMSInput( SECTION *state, char *intype )
  {
    int		tvp;
    SECTION    *newstate    = NULL;

    for ( tvp = 0; tvp < state->TVCount; tvp++ )
      {
	if ( stricmp( intype, state->TVPair[ tvp ].Token ))
	    continue;

	if ( state->TVPair[ tvp ].Value	    &&
	     !isBoolVal( state->TVPair[ tvp ].Value ))
	    break;
      }

    if ( tvp  >= state->TVCount )	/* Found it?			    */
      {
	if ( verbose  >  1 )
	  {
	    printf( "processMSInput fail\n" );
	    fflush( stdout );
	  }
	return	NULL;			/* Nope				    */
      }

    newstate	= getINISection( IFP, state->TVPair[ tvp ].Value );
    if ( verbose  >  1 )
      {
	printf( "processMSInput newstate is %s\n", strVal( newstate->Name ));
	fflush( stdout );
      }
    if ( !newstate )
	return	NULL;

    return  newstate->Name;
  }


char   *processMSRInput( SECTION *state, char *data )
  {
    if ( verbose  >  1)
      {
	printf( "processMSRInput\n" );
	fflush( stdout );
      }
    return  processMSInput( state, MSR );
    (void)data;
  }

char   *processScanInput( SECTION *state, char *data )
  {
    if ( verbose  >  1)
      {
	printf( "processScanInput\n" );
	fflush( stdout );
      }
    return  processMSInput( state, SCAN );
    (void)data;
  }


/*  Timeout input means we switch to the timeout state.  If not defined, we
    exit.
*/
char   *processTimeoutInput( void )
  {
    SECTION *state;

    if ( verbose  >  1)
      {
	printf( "processTimeoutInput\n" );
	fflush( stdout );
      }
    state   = getINISection( IFP, TIMEOUT );

    if ( state )
	return	TIMEOUT;

    return  EXIT;
  }


char   *touchProcessInput( int from, SECTION *state )
  {
    char   *ns  = NULL;

    if ( !touchInputTypeAllowed( from ))
	return	NULL;

    switch  ( from )
      {
	case	INPUT_TOUCH:
	  {
	    ns	= processTouchInput( state, touchPoint );
	    break;
	  }
	case    INPUT_MSR:
	  {
	    ns	= processMSRInput( state, touchMSRScan );
	    break;
	  }
	case    INPUT_SCAN:
	  {
	    ns	= processScanInput( state, touchMSRScan );
	    break;
	  }
	case    INPUT_TIMEOUT:
	  {
	    ns	= processTimeoutInput();
	    break;
	  }
      }

    return  ns;
  }


void	doTouchDemo( int from )
  {
    char   *newState;

    if ( currentState  ==  NULL )	/* Then we initialize everything    */
      {
	if ( !loadTouchDemo())
	    return;

/*  INIT State is special.  It defines which devices participate in the
    demo and which state actually starts the demo.  When we enter the INIT
    state, we do the necessary setup.  We then fall through to the normal
    handling of a state, so we are in INIT only in a transitory condition.
*/
	if ( !touchEnterState( INIT ))	/* Enter init state		    */
	    return;
      }

    newState	= touchProcessInput( from, currentState );
    if ( !touchEnterState( newState ))
	touchEnterState( EXIT );
  }


/*============================ TOUCH DEMO ================================*/


/*  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,
    5, 1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1,
    6, 1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1
  };

char	pktbuf[ XMTBUFFSIZE ];

/*  Process an event.
*/
void	processEvent( SLPACKET *ipkt, ulong theIP, char *xmit )
  {
    ulong   es;				/* Event start			    */
    size_t  len, plen;
    int	    status, sock, h, v;
    char    button[ 128 ];
    time_t  now;
    char   *data    = NULL;

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

    es   = ntohl( evnt->starting );	/* Get the new starts		    */
    if ( !es )				/* If none, we're done		    */
	return;

    if ( es  &  PIEVENT_KEYBD )
	keyChar	= ntohl( evnt->extras[0] );

    if ( verbose  >  1 )
      {
	printf( "processEvent\n" );
	fflush( stdout );
      }


/*  Synthesize a barcode and check for it.
*/
    es	    = newEvents[ es & 0x3F ];
    len	    = 0;
    plen    = 0;
    now	    = time( NULL );

    switch ( es )
      {
	case	5:			/* Touch screen?		    */
	  {
	    if ( useTouchDemo )
	      {
		if ( now  <=  touchNextTouch )
		  {
		    if ( verbose  >  1 )
		      {
			printf( "Touch discarded\n" );
			fflush( stdout );
		      }
		    return;
		  }
		touchNextTouch  = now + 1;

		touchIP	= theIP;
		touchPoint	= ntohl( evnt->extras[2] )  |
				  ntohl( evnt->extras[1] ) << 4;
		doTouchDemo( INPUT_TOUCH );
		return;
	      }
	    else
	      {
		h   = ntohl( evnt->extras[2] ) & 0xF;
		v   = ntohl( evnt->extras[1] ) & 0xF;

		if ( !eventsArePrinted )
		  {
#if 1
		    sprintf( button, "EV5%02d%02d", v, h );
#else
		    len = 1 + sprintf( xmit, "Touched %02d,%02d",
				       ntohl( evnt->extras[1] ),
				       ntohl( evnt->extras[2] ));
#endif
		    break;
		  }

		if ( !okToPrint  ||  !xmit )
		    return;


		sprintf( button, "T-%s", lastFound );

		if ( verbose  >  1 )
		  {
		    printf( "Looking up %s\n", button );
		    fflush( stdout );
		  }
		htouch	= h;
		vtouch	= v;
		len = foundCode( button, xmit, XMTBUFFSIZE, FALSE );
		htouch	= -1;
		vtouch	= -1;
		if ( 0  ==  len )
		  {
		    if ( verbose  >  1 )
		      {
			printf( "Not found\n" );
			fflush( stdout );
		      }
		    return;			// Not found, no retry
		  }

		if ((now - foundWhen)  >  threshhold )
		  {
		    if ( verbose  >  1 )
		      {
			printf( "Too old\n" );
			fflush( stdout );
		      }
		    return;
		  }

		data	= xmit;
		if ( '<'  !=  *data )
		  {
		    ppkt    = (SLDATA *)pktbuf;
		    okToPrint   = FALSE;	// Once per slide, please
		  }
		else
		    ppkt    = NULL;
	      }
	    break;
	  }

	case	6:			// Keyboard press
	  {
	    len = 0;
	    if ( !showKeyCode )
	      {
		sprintf( button, "EV6%c", (int)keyChar );
		len = foundCode( button, xmit, XMTBUFFSIZE, TRUE );
	      }
	    if ( 0  ==  len )
		sprintf( button, "EV%ld", es );
	    break;
	  }

	default:			// It's one of the buttons
	  {
	    if ( okToPrint  &&  xmit  &&  eventsArePrinted )
	      {
		sprintf( button, "EV%ld-%s", es, lastFound );
		if ( verbose  >  1 )
		  {
		    printf( "Check out button %s\n", button );
		    fflush( stdout );
		  }

		len = foundCode( button, xmit, XMTBUFFSIZE, FALSE );
		if ( 0  ==  len )
		  {
		    return;
		    break;
		  }

		if ( verbose  >  1 )
		  {
		    printf( "Button found\n" );
		    fflush( stdout );
		  }

		if ((now - foundWhen)  >  threshhold )
		  {
		    if ( verbose  >  1 )
		      {
			printf( "Too old\n" );
			fflush( stdout );
		      }
		    return;
		  }

		ppkt	    = (SLDATA *)pktbuf;
		data	    = xmit;
		okToPrint   = FALSE;	// Once per slide, please
	      }
	    else
		sprintf( button, "EV%ld", es );

	    break;
	  }
      }

    if ( 0  ==  len )
	len = foundCode( button, xmit, XMTBUFFSIZE, TRUE );

/*  If something was found, open a new socket to the specified IP and send
    it.
*/
    if ( len )
      {
	char		   *quad;
	struct hostent     *phe;
	struct sockaddr_in  remote_sin;

	quad	= IPtoQuad( NULL, theIP );
	if ( verbose  >  1 )
	  {
	    if ( ppkt )
		printf( "Print to %s\n", quad );
	    else
		printf( "Send [%s] to %s\n", xmit, quad );
	    fflush( stdout );
	  }

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

/*  Create socket
*/
	sock	= socket( AF_INET, SOCK_STREAM, 0 );
	if ( sock == -1 )
	  {
	    printf( "Error creating socket\n");
	    exit( -1 );
	  }

	phe	= gethostbyname( (const char *)quad );

	if ( !phe )
	  {
	    printf( "DNS lookup failed for host :%s\n", quad );
	    CloseSocket( sock );
	    exit( -1 );
	  }

	memcpy((void *)&(remote_sin.sin_addr), phe->h_addr, phe->h_length );
	remote_sin.sin_port	= htons( SLOPPIPORT );
	remote_sin.sin_family	= AF_INET;

	status	= connect( sock, (struct sockaddr *)&remote_sin,
			   sizeof remote_sin );
	if ( status  ==  -1 )
	  {
	    printf( "Error connecting to node %s\n", quad );
	    CloseSocket( sock );
	    exit( EXIT_FAILURE );
	  }

	if ( ppkt )			// Special text to send?
	  {
	    if ( verbose  >  1 )
	      {
		printf( "Sending message to printer\n" );
		fflush( stdout );
	      }

	    len = unHexify( (uchar *)data, strlen( data ));
	    if ( 0  ==  len )
		return;			// Bad data, no retry

	    memset( pktbuf, 0, XMTBUFFSIZE );

	    plen    = (sizeof *ppkt) - 4 + len;

	    ppkt->type  = SLD_SPECIALTEXT;
	    memmove( ppkt->data.info, data, len );
	    len	= plen;
	    if ( verbose  >  1 )
	      {
		printf( "Ready to print %d\n", len );
		fflush( stdout );
	      }

	    sendNominalPacket( sock, SLT_DATA, (char *)ppkt, plen );
	    ppkt    = NULL;
	  }
	else
	  {
	    if ( xmit[ 0 ]  ==  '<' )	/* Send the file?		    */
		sendfile( sock, xmit );
	    else
		socksend( sock, xmit, len, 0 );
	  }

	CloseSocket( sock );
      }
  }


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;
    SLPACKET   *opkt	= (SLPACKET *)&xmit;
    SLDATA     *ppkt	= NULL;

    if ( !rcv )
	return;


/*  If we are supposed to ignore some of the incoming packet, move the
    important stuff to the start of the packet.
*/
    if ( IgnoreIn  !=  0 )
	memmove( rcv, rcv + IgnoreIn, RCVBUFFSIZE - IgnoreIn );


/*  Product queries are handles by making them look like a tirival mode
    query.  Events are special.  Others are generally just dumped.
*/
    if ( !rcv[ 0 ] )		/* A Nominal mode packet?	    */
      {
	if ( verbose  == 1 )
	  {
	    fprintf( stdout, "Received a Nominal-mode packet\n" );
	    fflush( stdout );
	  }
	if ( verbose > 1 )
	  {
	    time_t	now;
	    now = time( NULL );
	    fprintf( stdout, "%s", ctime( &now ));
	    dumpNominalPacket( "Received", ipkt );
	    fflush( stdout );
	  }

	if ( packetToken( ipkt )  ==  SLT_EVENT )
	  {
	    processEvent( ipkt, theIP, xmit );
	    return;
	  }

	if ( packetToken( ipkt )  !=  SLT_PRODUCTQUERY )
	    return;

	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" );
	    fflush( stdout );
	  }
	return;
      }

    memset( xmit, 0, XMTBUFFSIZE );

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


/*  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 );
	fflush( stdout );
      }


    strcpy( xmit, "abcdefghij" );
    if ( AddOut )
	socksend( sock, xmit, AddOut, FALSE );

    if ( useTouchDemo  &&  !currentState )
      {
	touchSock	= sock;
	touchMSRScan    = rep;
	doTouchDemo( (*rep  ==  '%') ? INPUT_MSR : INPUT_SCAN );
	return;
      }

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

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

/*  Special treatment for testing.
*/
    if ( MixedTextTest )
      {
	if ( !strcmp( rep, TestBarcode ))
	  {
	    tosend  = xmit;

	    if ( MixedTextGraphic )
	      {
		if ( InLineGraphic )
		  {
		    x   = sprintf( tosend, "%s", "<images/sample.gif" ) + 1;
		    sendfile( sock, tosend );
		  }
		else
		  {
		    x   = sprintf( tosend, "%s", "/images/sample.gif" ) + 1;
		    socksend( sock, tosend, x, TRUE );
		  }
	      }

	    if  ( SingleResponse )
	      {
#if 1
		x   = sprintf( tosend, "%s", "Mixed Text" ) + 1;
#else
		sleep( 1 );
		x   = sprintf( tosend, "%s", "Delayed Mixed Text" ) + 1;
#endif
		socksend( sock, tosend, x, TRUE );
	      }
	    else
	      {
#if !TEXTRESETTEST
		x   = sprintf( tosend, "%s", "Wait one second" ) + 1;
		socksend( sock, tosend, x, TRUE );
		sleep( 1 );

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

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

		x   = sprintf( tosend, "%s", "Stay up PTO seconds" ) + 1;
		socksend( sock, tosend, x, TRUE );
#else
		x   = sprintf( tosend, "%s", "sendResponse=TRUE" ) + 1;
		sendNominalPacket( sock, SLT_MODESETQUERY, tosend, x );
		recv( sock, tosend, XMTBUFFSIZE, 0 );

		x   = sprintf( tosend, "%s", "TextVPos=96" ) + 1;
		sendNominalPacket( sock, SLT_MODESETQUERY, tosend, x );
		recv( sock, tosend, XMTBUFFSIZE, 0 );

		x   = sprintf( tosend, "%s", "TextRows=2" ) + 1;
		sendNominalPacket( sock, SLT_MODESETQUERY, tosend, x );
		recv( sock, tosend, XMTBUFFSIZE, 0 );

		x   = sprintf( tosend, "%s", "TextBackgnd=0" ) + 1;
		sendNominalPacket( sock, SLT_MODESETQUERY, tosend, x );
		recv( sock, tosend, XMTBUFFSIZE, 0 );

		x   = sprintf( tosend, "%s", "TextForegnd=16" ) + 1;
		sendNominalPacket( sock, SLT_MODESETQUERY, tosend, x );
		recv( sock, tosend, XMTBUFFSIZE, 0 );

		x   = sprintf( tosend, "%s", "TextReset=TRUE" ) + 1;
		sendNominalPacket( sock, SLT_MODESETQUERY, tosend, x );
		recv( sock, tosend, XMTBUFFSIZE, 0 );

		x   = sprintf( tosend, "%s", "sendResponse=FALSE" ) + 1;
		sendNominalPacket( sock, SLT_MODESETQUERY, tosend, x );

		x   = sprintf( tosend, "%s", "Text Message" ) + 1;
		socksend( sock, tosend, x, TRUE );
#endif
	      }
	    return;
	  }
      }


    if ( SendMultiple )
      {
	if ( !strcmp( rep, TestBarcode ))
	  {
	    int	i;

	    tosend  = xmit;
	    for ( i = 0; i < SendMultiple; i++ )
	      {
		if ( verbose  >  1 )
		  {
		    printf( "[%d]", i );
		    fflush( stdout );
		  }
		if ( i & 1 )
		    x	= sprintf( tosend, "%s", "<images/flip1.gif" );
		else
		    x	= sprintf( tosend, "%s", "<images/flip0.gif" );

		sendfile( sock, tosend );
	      }

	    return;
	  }
      }

/*  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, TRUE );
    if ( verbose  >  1 )
      {
	fprintf( stdout, "foundCode returned %d\n", x );
	fflush( stdout );
      }


/*^^^								      ^^^*/
/*****************  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 the text we're given starts with a vertical bar, the text is
    double-byte text, either big-endian or little-endian, as indicated by
    the second character, a '>' or '<', respectively.

    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 )
      {
	switch ( tosend[ 0 ] )
	  {
	    case    '|':		// Quote the rest of the text?
	      {
		if ( verbose  >  1 )
		  {
		    fputs( "sending quoted text\n", stdout );
		    fflush( stdout );
		  }
		sendQuotedText( xmit, x, sock );
		return;
	      }

	    case    '<':		// Send the file?
	    case    '@':		// Send contents of file as text?
	      {
		if ( verbose  >  1 )
		  {
		    fputs( "sending file\n", stdout );
		    fflush( stdout );
		  }
		if ( sendfile( sock, tosend ))
		    return;

		break;
	      }

	    case  '{':
	      {
		tosend    = pastNextWhite( tosend + 1 );
		if ( !tosend )
		    return;			// Bad data, no retry

		x = unHexify( (uchar *)tosend, strlen( tosend ));
		if ( 0  ==  x )
		    return;			// Bad data, no retry

		ppkt    = (SLDATA *)pktbuf;
		memset( pktbuf, 0, XMTBUFFSIZE );

		plen    = (sizeof *ppkt) - 4 + x;

		ppkt->type  = SLD_SPECIALTEXT;
		memmove( ppkt->data.info, tosend, x );
		x   = plen;

		if ( verbose  >  1 )
		  {
		    printf( "Sending message to printer\n" );
		    fflush( stdout );
		  }
		sendNominalPacket( sock, SLT_DATA, (char *)ppkt, plen );
		ppkt    = NULL;

		return;
		break;
	      }
	  }
      }
    else
      {
	x   = sprintf( tosend, "%s\r\n  %d", rep, cnt ) + 1;
	if ( ('/'  ==  tosend[ 0 ])  ||  ('<'  ==  tosend[ 0 ]))
	    tosend[ 0 ]	= ' ';
      }


/*  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 );
	x   = unHexify( tosend, x );

	if ( verbose  >  1 )
	  {
	    fprintf( stdout, "trivial %d, [%s], %d\n", sock, tosend, x );
	    fflush( stdout );
	  }
	socksend( sock, tosend, x, TRUE );
      }
    else
      {
	x			+= DATAHEADERSIZE;
	opkt->header.length	= htonl( x );
	opkt->header.token	= SLT_DATA;

	if ( useShow )
	    opkt->body.data.type    = SLD_SHOWMESSAGE;
	else
	    opkt->body.data.type    = SLD_DISPLAYTEXT;

	if ( verbose  >  1 )
	  {
	    dumpNominalPacket( "Sending", opkt );
	    fflush( stdout );
	  }

	socksend( sock, 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 );
	  }
      }
  }

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 ( verbose  >  1 )
      {
	fprintf(stdout, "read returned %d\n", c);
	fflush( stdout );
      }
    if ( c  >=  0 )
	ProcessPacket( rcv, sock, theIP );
    else if ( verbose )
      {
	fprintf( stdout, "Error on read: %d\n", c );
	fflush( stdout );
      }

    touchSock	= 0;
    CloseSocket( sock );
  }

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

    unsigned long   theIP   = 0;

    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 );
	    fflush( stdout );
	  }
	if ( listenSocket  <=  0 )
	    exit( EXIT_FAILURE );
      }

    sock    = 0;
    while (  TRUE )
      {
	if ( useconsole )		// For debugging this program
	  {
	    if ( !rcv )
	      {
		rcv = malloc( RCVBUFFSIZE );
		if ( !rcv )
		  {
		    perror( "malloc receive" );
		    exit( EXIT_FAILURE );
		  }
	      }
	    memset( rcv, 0, RCVBUFFSIZE );
	    fprintf( stdout, "listening: " );
	    fflush( stdout );
	    fgets( rcv, RCVBUFFSIZE, stdin );
	    c   = strlen( rcv );
	    ProcessPacket( rcv, sock, theIP );
	  }
	else				// Monitor a network connection.
	  {
	    if ( verbose )
	      {
		fprintf( stdout, "\nWaiting for connection\n" );
		fflush( stdout );
	      }
	    sock   = get_connection( listenSocket, &theIP );
	    if ( verbose )
	      {
		fprintf( stdout, "Got connection %d\n", sock );
		fflush( stdout );
	      }
	    if ( sock  <=  0 )
	      {
		if ( errno  ==  EINTR )	/* EINTR might happen on accept(),  */
		  {
		    if ( verbose )
		      {
			fprintf( stdout, "Got EINTR\n" );
			fflush( stdout );
		      }
		    sock   = get_connection( listenSocket, &theIP );
		  }
	      }
	    if ( sock  <=  0 )
	      {
		perror( "accept" );	/* bad				    */
		exit( 1 );
	      }

/*	FOR A MULTI-THREADED SERVER, THE FOLLOWING FUNCTION CALL MUST BE
  	ASSIGNED TO A NEWLY-CREATED TASK (FORK OR THREAD, PER THE OPERATING
  	SYSTEM) RATHER THAN EXECUTED BY THE MAIN TASK.
*/
	    ProcessConnection( sock, theIP );
	  }

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

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