/*

Relay Software
--------------

NOTE: This code compiles on Dynamic C 10.64+ for RCM6700. In Dynamic C 10.72
add the following line to your project define list: MAX_FIRMWARE_BINSIZE=0x80000.

The Relay accepts connections on a single IP port, only one connection at a
time. Clients can connect, exercise the driver, and read and write to the
configuration file in EEPROM. Clients cannot change the currently-active
configuration. To activate the configuration stored in EEPROM, you must REBOOT
the driver with the hardware reset switch. If you want to return the EEPROM
configuration file to the factory default, then you hold the configuration
switch down and reset the driver. Hold the configuration switch down for at
least three seconds after you release the hardware reset switch. The factory
default configuration is defined by the constants beginning with the
prefix "MY_".

The Relay allows clients to set the global variable logged_in with
a LOGIN message accompanied by the a string matching the global password string.
The global variable security_level, which is also a configuration parameter,
determines when the client is required to log in. With security level zero, the
client is never required to log in. With security level one or higher, the client
must log in to read from or write to the configuration files. With security
level two or higher, the client must log in to execute any message other than
a LOGIN message.

Version 1 [24-SEP-19] Based upon C2037E15 for the A2071E with RCM6700 module.
Eliminate PLATFORM variable on the assumption that we are compiling only for
the RCM6700. Adapt BYTE_WRITE and BYTE_READ for use with the A2087A. The
BYTE_WRITE instruction we hijack and direct into a local VME Controller
register when the BYTE_WRITE is to one of the four VME base address locations
that in earlier versions of the TCPIP-VME Interface resided in VME backplane
address space. We adapt the STREAM_READ, adding assembler code that sets up
the local registers that control VME backplane single-byte read cycles.

Version 2 [26-SEP-19] We implement STREAM_WRITE and STREAM_DELETE. We
accelerate by using the IX and IY to store the locations of the !CW (control write)
and the VME Control Register. 

*/

// Current version number
#define VERSION_NUM 2

// The REPORT flag turns on reporting during active message reception
// and processing. Set to zero for production code. When zero, the only
// messages that are printed are boot-time configuration messages and
// notifications of a socket opening and closing.
#define REPORT 0

// Default configuration
#define TCPCONFIG 0 // we define our own configuration
#define USE_ETHERNET 1 // use ethernet, not wireless
#define MY_IP_ADDRESS "10.0.0.37" // IP of driver
#define MY_GATEWAY "10.0.0.1" // IP of gateway
#define MY_NETMASK "255.255.255.0" // IP mask
#define MY_PORT 90 // IP connection port
#define MY_PASSWORD "LWDAQ" // password for access
#define MY_SECURITY 0 // security level
#define MY_TIMEOUT 0 // tcpip timeout seconds (0=infinite)
#define MY_ID "not_assigned" // driver serial number
#define MY_TIMESTAMP "00000000000000"// null time stamp

// Ethernet configuration
#define ETH_MAXBUFS 3
#define ETH_MTU 1514
#define BUFF_SIZE (ETH_MTU-40)
#define RAM_BUFF_SIZE (6*BUFF_SIZE)
#define TCP_BUF_SIZE (6*BUFF_SIZE)
#define CHECK_TCP_COUNT 1000

// LWDAQ messages
#define START_CODE 0xA5 // correct value for start code
#define CLOSE_CODE 0x04 // close connection character
#define END_CODE 0x5A // correct value for end code
#define START_OFFSET 0
#define ID_OFFSET 1
#define CLEN_OFFSET 5
#define CONTENT_OFFSET 9
#define FRAME_SIZE 10

// Message Identifiers
#define VERSION_READ	0
#define BYTE_READ	   1
#define BYTE_WRITE	  2
#define STREAM_READ	 3
#define DATA_RETURN	 4
#define BYTE_POLL	   5
#define LOGIN		   6
#define CONFIG_READ	 7
#define CONFIG_WRITE	8
#define MAC_READ		9
#define STREAM_DELETE   10
#define ECHO			11
#define STREAM_WRITE	12
#define REBOOT		  13

// Set up the auxilliary IO bus for RCM67600 interfaces.
#define PORTA_AUX_IO

// Controller registers
#define CS_ADDR 40 // configuration switch address

// General
#define BITS_PER_BYTE 8 // number of buts per byte
#define MAC_LENGTH 6 // bytes per MAC address

// Select memory map.
#memmap xmem

// TCP library.
#use "dcrtcp.lib"

// EEPROM file system .
#define CONFIG_LENGTH 1024 // bytes for config file buffer
#define SEPCHARS " :\n,;=" // separator characters in config file
// We use the FAT file system. The on-board 8-MByte
// serial EEPROM holds the file systen, so we do not use the
// the FLASH memory used for program instructions.
#define FAT_BLOCK  // sets all FAT operations to blocking mode
#define CONFIG_FILE_NAME "Config.txt" // a name for the cofig file
#use "fat16.lib" // the fat file system library

// function prototypes
long flip_bytes(long);
void write_controller_byte(char,char);
char read_controller_byte(char);
int read_relay_configuration();
void configure_controller_interface();
int write_relay_configuration(char* contents);
int buffered_socket_read(tcp_Socket* s, byte* dp, int len);
int receive_message(tcp_Socket* s, long* id, long* len, char* content);
int return_long(tcp_Socket* s, long data);
int return_byte(tcp_Socket* s, char data);
int return_header(tcp_Socket* s, long id, long len);
int return_footer(tcp_Socket* s);
int return_data(tcp_Socket* s, char* block, long len);

// global variables
char logged_in;
int ip_port,tcp_timeout,security_level;
char password[32];
char configuration[CONFIG_LENGTH];
char tcp_buffer[RAM_BUFF_SIZE];
int tcp_first,tcp_available;


/*
main listens for a TCPIP connection, and then attends to that
connection while rejecting all others. When the socket closes,
main goes back to listening.
*/
main()
{
	// These variables we will use in assembly code, so they must be
	// static. The compiler must be able to translate the name into
	// a number for the assembly instructions.
	static char value,poll_value,count_lb,count_hb,vcr_value;
	static char out_buffer[BUFF_SIZE];
	static char in_buffer[BUFF_SIZE];
	static int io_addr;

	// tcpip socket variables must be static.
	static tcp_Socket socket;

	// These variables will not be used in assembler, so they need
	// not be static.
	char code,register_addr;
	char message[255];
	int socket_open,byte_num,status;
	long in_length,content_length,in_message_id;
	long *long_ptr;
	longword router_addr;
	char addr_string[20];
	struct sockaddr client_info;

	// Set up the I/O registers to communicate with the controller
	// logic chip.
	configure_controller_interface();

	// Start up and initialize the TCPIP stack.
	sock_init();

	// Configure the LWDAQ TCP/IP interface.
	strcpy(password,MY_PASSWORD);
	ip_port=MY_PORT;
	security_level=MY_SECURITY;
	tcp_timeout=MY_TIMEOUT;

	// Read relay configuration file.
	read_relay_configuration();

	// Ping the router to register MAC address.
	ifconfig(IF_ETH0,IFG_ROUTER_DEFAULT,&router_addr,IFS_END);
	inet_ntoa(addr_string,router_addr);
	printf("Attempting to ping router at %s.\n",addr_string);
	tcp_tick(NULL);
	status=_ping(router_addr,0);
	tcp_tick(NULL);
	if (status==-1) printf("Could not resolve router hardware address.\n");
	if (status==0) printf("Ping succeeded.\n");
	if (status==1) printf("Failed sending packet to router.\n");

	// Allow multiple clients to queue up for connection to the
	// LWDAQ server on the same socket.
	tcp_reserveport(ip_port);

	while (1) {
		status=0;

		if (REPORT) printf("Listening for connection on port %d...\n",ip_port);
		tcp_listen(&socket,ip_port,0,0,NULL,0);
		sock_wait_established(&socket,0,NULL,&status);
		getpeername((sock_type *)&socket,&client_info,NULL);
		inet_ntoa(addr_string,client_info.s_ip);
		if (REPORT) printf("Connection established with client %s.\n",addr_string);

		logged_in=0;
		tcp_first=-1;
		tcp_available=0;

		while (1) {
			// If there is data available, we proceed to read the next
			// message. Otherwise we wait.
			if (tcp_available==0) {
				if (REPORT) printf("Waiting for input...\n");
				sock_wait_input(&socket,tcp_timeout,NULL,&status);
				if (status<0) goto sock_err;
			}

			// Get the latest message and parse it into the id, length, and
			// the contents themselves, if any.
			status=receive_message(&socket,&in_message_id,&in_length,in_buffer);
			if (status<0) goto sock_err;

			// We check for security clearance. We do this as efficiently as
			// we can, to reduce the over-head on message processing.
			if (security_level>=2) {
				if (logged_in==0) {
					if ((int) in_message_id != LOGIN) {
						if (REPORT) printf(
							"LOGIN required for security level %d, closing socket.\n",
							security_level);
						sock_close(&socket);
						goto sock_err;
					}
				}
			}

			// Here we decode the incoming message id.
			switch ((int) in_message_id) {

				// We need decoding speed most for BYTE_WRITE instructions, which
				// we use repeatedly to set up the driver for data acquisition and
				// control. That's why the BYTE_WRITE instruction comes first in
				// the list of possible instructions. We measured the BYTE_WRITE
				// execution time by sending 100,000 BYTE_WRITE messages in 100 sets
				// of 1000 to the driver. By this means, we obtained both a minimum
				// execution time and an average execution time.
				//
				// Platform   Minimum	 Average
				// RCM6700	40 us	   95 us
				//
				// The minimum execution time is what we see on the oscillocsope when
				// we look at the controller data strobe. This does not include the
				// time taken by TCPIP communication because our buffered socket read
				// routine reads in many messages at a time, which we subsequently
				// execute. The average time is the total time for 100k byte writes
				// divided by 100k.
				//
				case BYTE_WRITE:{
					register_addr=in_buffer[3];
					value=in_buffer[4];
					if ((register_addr >= 42) && (register_addr <= 45)) {
						if (REPORT) printf("BYTE_WRITE to base address register %d of %d\n",
						register_addr,value);
						write_controller_byte(register_addr,value);
						break;
					}
					if (REPORT) printf("BYTE_WRITE to VME location %d of %d\n",
					register_addr,value);
					write_controller_byte(45,register_addr);
					write_controller_byte(46,0xA7);
					if (register_addr % 2 == 0) {
						write_controller_byte(50,value);
						write_controller_byte(47,6);
					} else {
						write_controller_byte(51,value);
						write_controller_byte(47,5);
					}
					while (read_controller_byte(47) == 0) {
						status=buffered_socket_read(&socket,NULL,0);
						if (status<0) goto sock_err;
					}
					write_controller_byte(47,0);
					break;
				}

				case BYTE_READ:{
					register_addr=in_buffer[3];
					write_controller_byte(45,register_addr);
					write_controller_byte(46,0xE7);
					if (register_addr % 2 == 0) {
						write_controller_byte(47,6);
					} else {
						write_controller_byte(47,5);
					}
					while (read_controller_byte(47) == 0) {
						status=buffered_socket_read(&socket,NULL,0);
						if (status<0) goto sock_err;
					}
					if (register_addr % 2 == 0) {
						value=read_controller_byte(50);
					} else {
						value=read_controller_byte(51);
					}
					write_controller_byte(47,0);
					if (REPORT) printf("BYTE_READ from VME location %d of %d.\n",
						register_addr,value);
					return_byte(&socket,value);
					break;
				}

				case BYTE_POLL:{
					register_addr=in_buffer[3];
					value=in_buffer[4];
					if (REPORT) printf("POLL_BYTE of VME location %d for %d...\n",
						register_addr,value);
					poll_value=value+1;
					while (poll_value != value) {
 						write_controller_byte(45,register_addr);
				   		write_controller_byte(46,0xE7);
						if (register_addr % 2 == 0) {
							write_controller_byte(47,6);
						} else {
							write_controller_byte(47,5);
						}
						while (read_controller_byte(47) == 0) {
							status=buffered_socket_read(&socket,NULL,0);
							if (status<0) goto sock_err;
						}
						if (register_addr % 2 == 0) {
					   		poll_value=read_controller_byte(50);
						} else {
							poll_value=read_controller_byte(51);
						}
						write_controller_byte(47,0);
 						status=buffered_socket_read(&socket,NULL,0);
						if (status<0) goto sock_err;
					}
					break;
				}

				case VERSION_READ:{
					if (REPORT) printf("VERSION_READ\n");
					return_long(&socket,VERSION_NUM);
					break;
				}

				case STREAM_READ:{
					// The register address will be byte 3 of the input buffer.
					register_addr=in_buffer[3];

					// The content length is a four-byte integer in bytes 4-7
					// of the input buffer.
					long_ptr=(long*)&in_buffer[4];
					content_length=flip_bytes(*long_ptr);

					// We print a status message.
					if (REPORT) printf("STREAM_READ from VME location %d of length %lu...\n",
						register_addr,content_length);

					// Send the data return message header.
					return_header(&socket,DATA_RETURN,content_length);

					// Write the register address to the least significant byte of
					// the four-byte VME address, which is location 0x3D or 45 decimal.
					write_controller_byte(45,register_addr);

					// Write !LWORD = 1, !WRITE = 1 to the address control register.
					// The stream read is exclusively a read operation on the VME
					// backplane. Write 0x39 to the VME Address Modifier its in the
					// same register, but these have to be written in reverse order.
					// They specify non-privileged 24-bit addressing.
					write_controller_byte(46,0xE7);

					// Select which data strobe we will be using, which is vcr_value,
					// and select the data register we will write data to.
					if (register_addr % 2 == 0) {
						io_addr=(0xC000 | 50);
						vcr_value=6;
					} else {
						io_addr=(0xC000 | 51);
						vcr_value=5;
					}

					// Enter the stream read loop, which uses assembler code at its core.
					while (content_length>0) {
						if (content_length>BUFF_SIZE) {
							count_hb=(char) ((BUFF_SIZE & 0x0000FF00) >> BITS_PER_BYTE);
							count_lb=(char) (BUFF_SIZE & 0x000000FF);
						} else {
							count_hb=(char) ((content_length & 0x0000FF00) >> BITS_PER_BYTE);
							count_lb=(char) (content_length & 0x000000FF);
						}

						#asm
						ld ix,0xC02F	 				; The VME control register
						ld iy,0x007B					; The Control Write bit
						ld de,out_buffer
						ld a,(count_hb)
						ld c,a
						cp 0
						jr z,remainder_sr

						ld b,0x00
						ld hl,(io_addr)
						ioi ld (iy),0x00
						outer_loop_sr:
							inner_loop_sr:	 			;0
						   		ld a,(vcr_value)		;7
						   		ioe ld (ix),a			;11 + w
						  		ioi ld (iy),0xFF		;12
							 	dtack_ilsr:				;0
									ioe ld a,(ix)		;9 + w
									cp 0				;4
								jr z,dtack_ilsr   		;6
								ioe ld a,(hl)			;7 + w
								ld (de),a				;7
								ioi ld (iy),0x00		;12
						   		ioe ld (ix),0x00		;12 + w
								inc de					;2
								dec b					;2
							jr nz,inner_loop_sr			;6 Total 99 + 4w = 127
							dec c
						jr nz,outer_loop_sr

						remainder_sr:
						ld a,(count_lb)
						ld b,a
						cp 0
						jr z,done_sr

						ld hl,(io_addr)
						ioi ld (iy),0x00
						loop_sr:
						   	ld a,(vcr_value)
						   	ioe ld (ix),a
						   	ioi ld (iy),0xFF
						   	dtack_lsr:
							   	ioe ld a,(ix)
							   	cp 0
						   	jr z,dtack_lsr
							ioe ld a,(hl)
							ld (de),a
							ioi ld (iy),0x00
						   	ioe ld (ix),0x00
							inc de
							dec b
						jr nz,loop_sr

						done_sr:
						#endasm

						if (content_length>BUFF_SIZE) {
							sock_write(&socket,out_buffer,BUFF_SIZE);
							content_length=content_length-BUFF_SIZE;
						}
						else {
							sock_write(&socket,out_buffer,(int) content_length);
							content_length=0;
						}
					}

					return_footer(&socket);
					break;
				}

				case STREAM_WRITE:{
					// The register address will be the eight bits specified
					// in the stream read register address byte.
					register_addr=in_buffer[3];

					// The number of bytes we are going to write is the number
					// of bytes in the message content minus the number of bytes
					// used by the register address.
					content_length=in_length-sizeof(long);

					// Print status message.
					if (REPORT) printf("STREAM_WRITE to VME %d of length %lu...\n",
						register_addr,content_length);

					// Write the register address to the least significant byte of
					// the four-byte VME address, which is location 0x3D or 45 decimal.
					write_controller_byte(45,register_addr);

					// Write !LWORD = 1, !WRITE = 0 to the address control register.
					// The stream write is exclusively a write operation on the VME
					// backplane. Write 0x39 to the VME Address Modifier its in the
					// same register, but these have to be written in reverse order.
					// They specify non-privileged 24-bit addressing.
					write_controller_byte(46,0xA7);

					// Select which data strobe we will be using, which is vcr_value,
					// and select the data register we will write data to.
					if (register_addr % 2 == 0) {
						io_addr=(0xC000 | 50);
						vcr_value=6;
					} else {
						io_addr=(0xC000 | 51);
						vcr_value=5;
					}

					// Enter the stream write loop, which uses assembler code at its core.
					while (content_length>0) {
						if (content_length>BUFF_SIZE) {
							count_hb=(char) ((BUFF_SIZE & 0x0000FF00) >> BITS_PER_BYTE);
							count_lb=(char) (BUFF_SIZE & 0x000000FF);
						}
						else {
							count_hb=(char) ((content_length & 0x0000FF00) >> BITS_PER_BYTE);
							count_lb=(char) (content_length & 0x000000FF);
						}

						#asm
						ld ix,0xC02F	 				; The VME control register
						ld iy,0x007B					; The Control Write bit
						ld de,in_buffer
						inc de
						inc de
						inc de
						inc de

						ld a,(count_hb)
						ld c,a
						cp 0
						jr z,remainder_sw

						ld b,0x00
						ld hl,(io_addr)
						ioi ld (iy),0x00				;Assert !CW
						outer_loop_sw:
							inner_loop_sw:	 			;0
								ld a,(de)				;7 Read input buffer byte into A
								ioe ld (hl),a			;7 + w Write A to data register
						   		ld a,(vcr_value)		;7 Read vcr_value into A
						   		ioe ld (ix),a			;11 + w Write to VME Control Register
						  		ioi ld (iy),0xFF		;12 Un-assert !CW
							 	dtack_ilsw:				;0
									ioe ld a,(ix)		;9 + w Read VME Control Register
									cp 0				;4 Compare to zero
								jr z,dtack_ilsw   		;6 If not DTACK or BERR, repeat
								ioi ld (iy),0x00		;12 Assert !CW
						   		ioe ld (ix),0x00		;12 + w Write 0 to VME Control Register
								inc de					;2 Increment input buffer pointer
								dec b					;2 Decrement inner loop counter
							jr nz,inner_loop_sw			;6 Total 99 + 4w = 127
							dec c						;Decrement outer loop counter.
						jr nz,outer_loop_sw

						remainder_sw:
						ld a,(count_lb)
						ld b,a
						cp 0
						jr z,done_sw

						ld hl,(io_addr)
						ioi ld (iy),0x00
						loop_sw:	 				;0
							ld a,(de)				;7
							ioe ld (hl),a			;7 + w
						  	ld a,(vcr_value)		;7
							ioe ld (ix),a			;11 + w
							ioi ld (iy),0xFF		;12
							dtack_sw:				;0
								ioe ld a,(ix)		;9 + w
								cp 0				;4
							jr z,dtack_sw   		;6
						   	ioi ld (iy),0x00		;12
						   	ioe ld (ix),0x00		;12 + w
							inc de					;2
							dec b					;2
						jr nz,loop_sw				;6 Total 99 + 4w = 127

						done_sw:
						#endasm

						if (content_length>BUFF_SIZE) {
							content_length=content_length-BUFF_SIZE;
						}
						else {
							content_length=0;
						}
					}

					break;
				}

				case STREAM_DELETE:{
					// The register address will be the eight bits specified
					// in the stream read register address byte.
					register_addr=in_buffer[3];

				   // Obtain number of bytes to delete from the input buffer. We have to
				   // flip the byte order. We use the content_length variable to hold the
				   // number of bytes we wish to over-write.
				   long_ptr=(long*)&in_buffer[4];
				   content_length=flip_bytes(*long_ptr);

				   // Read the delete value from the input buffer. We will write this
				   // repeatedly to the memory portal specified by register_addr.
				   value=in_buffer[8];

				   // Print status message.
				   if (REPORT) printf(
					   "STREAM_DELETE to %d of length %lu, over-writing with %d...\n",
					   register_addr,content_length,value);

					// Write the register address to the least significant byte of
					// the four-byte VME address, which is location 0x3D or 45 decimal.
					write_controller_byte(45,register_addr);

					// Write !LWORD = 1, !WRITE = 0 to the address control register.
					// The stream write is exclusively a write operation on the VME
					// backplane. Write 0x39 to the VME Address Modifier its in the
					// same register, but these have to be written in reverse order.
					// They specify non-privileged 24-bit addressing.
					write_controller_byte(46,0xA7);

					// Select which data strobe we will be suing, which is vcr_value,
					// and select the data register we will write data to.
					if (register_addr % 2 == 0) {
						io_addr=(0xC000 | 50);
						vcr_value=6;
					} else {
						io_addr=(0xC000 | 51);
						vcr_value=5;
					}

				   // Enter the stream delete loop, which uses assembler
				   // code at its core.
				   while (content_length>0) {
						if (content_length>BUFF_SIZE) {
							count_hb=(char) ((BUFF_SIZE & 0x0000FF00) >> BITS_PER_BYTE);
							count_lb=(char) (BUFF_SIZE & 0x000000FF);
						}
						else {
							count_hb=(char) ((content_length & 0x0000FF00) >> BITS_PER_BYTE);
							count_lb=(char) (content_length & 0x000000FF);
						}

						#asm
						ld ix,0xC02F	 				; The VME control register
						ld iy,0x007B					; The Control Write bit
						ld a,(count_hb)
						ld c,a
						cp 0
						jr z,remainder_sd

						ld b,0x00
						ld hl,(io_addr)
						ioi ld (iy),0x00
						outer_loop_sd:
							inner_loop_sd:	 			;0
								ld a,(value)			;7 Read value into A
								ioe ld (hl),a			;7 + w Write A to data register
						   		ld a,(vcr_value)		;7 Read vcr_value into A
						   		ioe ld (ix),a			;11 + w Write A into VME control register
						  		ioi ld (iy),0xFF		;12 Un-assert !CW
							 	dtack_ilsd:				;0
									ioe ld a,(ix)		;9 + w Read VME control register
									cp 0				;4 Compare to zero
								jr z,dtack_ilsd   		;6 If zero, no DTACK or BERR yet, so repeat
								ioi ld (iy),0x00		;12 Assert !CW
						   		ioe ld (ix),0x00		;12 + w Write zero to VME control register
								dec b					;2 Decrement inner loop counter
							jr nz,inner_loop_sd			;6 Total 99 + 4w = 127
							dec c						; Decrement outer loop counter
						jr nz,outer_loop_sd

						remainder_sd:
						ld a,(count_lb)
						ld b,a
						cp 0
						jr z,done_sd

						ld hl,(io_addr)
						ioi ld (iy),0x00			;Assert !CW
						loop_sd:	 				;0
							ld a,(value)			;7 Read value into A
							ioe ld (hl),a			;7 + w Write A to data register
							ld a,(vcr_value)		;7 Read vcr_value into A
							ioe ld (ix),a			;11 + w Write A into VME control register
							ioi ld (iy),0xFF		;12 Un-assert !CW
							dtack_sd:				;0
								ioe ld a,(ix)		;9 + w Read VME control register
								cp 0				;4 Compare to zero
							jr z,dtack_sd   		;6 If zero, no DTACK or BERR yet, so repeat
							ioi ld (iy),0x00		;12 Assert !CW
							ioe ld (ix),0x00		;12 + w Write zero to VME control register
							dec b					;2 decrement loop counter
						jr nz,loop_sd				;6 Total 99 + 4w = 127

						done_sd:
						#endasm

						if (content_length>BUFF_SIZE) {
							content_length=content_length-BUFF_SIZE;
						}
						else {
							content_length=0;
						}
					}

					break;
				}

				case LOGIN:{
					if (REPORT) printf("LOGIN\n");
					if (in_length<1) {
					  logged_in=0;
						if (REPORT) printf("Failed with empty password.\n");
					}
					if (!strcmp(password,in_buffer)) {
						logged_in=1;
						if (REPORT) printf("Logged in with password: %s.\n",in_buffer);
					} else {
					  logged_in=0;
						if (REPORT) printf("Failed with password: %s.\n",in_buffer);
					}
				  return_byte(&socket,logged_in);
					break;
				}

				case CONFIG_READ:{
					if (REPORT) printf("CONFIG_READ\n");
					if ((logged_in==1) || (security_level==0)) {
					  return_data(&socket,configuration,strlen(configuration));
						if (REPORT) printf(
					  "Transmitted configuration of %d characters.\n",
					strlen(configuration));
					} else {
						if (REPORT) printf(
					  "Rejected: not logged in.\n");
						sock_close(&socket);
						goto sock_err;
					}
					break;
				}

				case CONFIG_WRITE:{
					if (REPORT) printf("CONFIG_WRITE\n");
					if ((logged_in==1) || (security_level==0)) {
						if (in_length0) {
		tcp_available=tcp_available+num_read;
			if (REPORT) printf("Read %d bytes into RAM buffer, now %d available.\n",
			num_read,tcp_available);
		}
		if (!tcp_tick(s)) {
			if (REPORT) printf("Connection broken.\n");
			return -1;
		}
		return 0;
	}

	// The following code handles requests for one or more bytes.
	if (tcp_available>=len) {
		memcpy(dp,&tcp_buffer[tcp_first],len);
		tcp_first=tcp_first+len;
		tcp_available=tcp_available-len;
	} else {
		tcp_remaining=len;
		tcp_destination=dp;
		if (tcp_available>0) {
			memcpy(tcp_destination,&tcp_buffer[tcp_first],tcp_available);
			tcp_remaining=tcp_remaining-tcp_available;
			tcp_destination=tcp_destination+tcp_available;
			if (REPORT) printf("Read last %d available, need %d more.\n",
				tcp_available,tcp_remaining);
		}
		tcp_available=0;
		tcp_first=-1;
		while (tcp_remaining>0) {
			if (REPORT) printf("Waiting for %d bytes...\n",tcp_remaining);
			while (tcp_available==0) {
				tcp_available=sock_fastread(s,&tcp_buffer[0],BUFF_SIZE);
				if (!tcp_tick(s)) {
					if (REPORT) printf("Connection broken.\n");
					return -1;
				}
			}
			if (REPORT) printf("Received %d bytes.\n",tcp_available);
			if (tcp_available>=tcp_remaining) {
				memcpy(tcp_destination,&tcp_buffer[0],tcp_remaining);
				tcp_first=tcp_remaining;
				tcp_available=tcp_available-tcp_remaining;
				tcp_remaining=0;
			} else {
				memcpy(tcp_destination,&tcp_buffer[0],tcp_available);
				tcp_destination=tcp_destination+tcp_available;
				tcp_remaining=tcp_remaining-tcp_available;
				tcp_available=0;
			}
		}
	}
	return len;
}

/*
   receive_message reads data from a socket until an entire message
   has been received. It saves the message id to *id and the content
   to *content. It reports the length of the content in *len. The
   routine assumes the LWDAQ Message Protocol.
*/
int receive_message(tcp_Socket* s, long* id, long* len, char* content) {
	char code;

	// We read the start code. If it's incorrect, we close the
	// socket and return with an error code.
	buffered_socket_read(s,&code,sizeof(code));
	if (code!=START_CODE) {
		if (code==CLOSE_CODE) {
		   if (REPORT) printf("Close code received, closing socket.\n");
		   return -1;
		} else {
		   if (REPORT) printf("Invalid start code, closing socket.\n");
		}
		sock_close(s);
		return -1;
	}

	// We read the message identifier and content length.
	buffered_socket_read(s,(char*)id,sizeof(*id));
	buffered_socket_read(s,(char*)len,sizeof(*len));

	// We flip the big-endian long integer bytes around to make them
	// little-endian.
	*id=flip_bytes(*id);
	*len=flip_bytes(*len);

	if (*len>BUFF_SIZE) {
		if (REPORT) printf("Message content too long, closing socket.\n");
		sock_close(s);
		return -1;
	}

	// We read the content itself into a buffer. At the end
	// of the content we write a null character so we can
	// pass the content buffer to string-handling routines.
	if (*len>0) {
		buffered_socket_read(s,content,(int) *len);
		content[(int) *len]=0x00;
	} else {
		content[0]=0x00;
	}

	// Read the end code.
	buffered_socket_read(s,&code,sizeof(code));
	if (code!=END_CODE) {
		if (REPORT) printf("Invalid end code, closing socket.\n");
		sock_close(s);
		return -1;
	}

	return 0;
}

/*
	return_header sends a data return message header through
	a socket.
*/
int return_header(tcp_Socket* s, long id, long len) {
	char buff[100];
	long* lp;

	buff[START_OFFSET]=START_CODE;
	lp=(long*)&buff[ID_OFFSET];
	*lp=flip_bytes(id);
	lp=(long*)&buff[CLEN_OFFSET];
	*lp=flip_bytes(len);
	sock_write(s,buff,CONTENT_OFFSET);
	return 0;
}

/*
	return_footer sends a terminating sequence through a socket.
*/
int return_footer(tcp_Socket* s) {
	char buff[100];

	buff[0]=END_CODE;
	sock_write(s,buff,1);

	return 0;
}

/*
   return_long sends a data return message through a socket with
   a four-byte integer as its content.
*/
int return_long(tcp_Socket* s, long data) {
	char buff[100];
	long* lp;

	return_header(s,DATA_RETURN,sizeof(data));
	lp=(long*)&buff[0];
	*lp=flip_bytes(data);
	sock_write(s,buff,sizeof(data));
	return_footer(s);
	return 0;
}

/*
   return_byte sends a data return message through a socket with
   a single-byte integer as its content.
*/
int return_byte(tcp_Socket* s, char data) {
	char buff[100];

	return_header(s,DATA_RETURN,sizeof(data));
	buff[0]=data;
	sock_write(s,buff,sizeof(data));
	return_footer(s);
	return 0;
}

/*
	return_data sends a block of data through a socket.
*/
int return_data(tcp_Socket* s, char* block, long len) {
	return_header(s,DATA_RETURN,len);
	sock_write(s,block,(int) len);
	return_footer(s);
	return 0;
}

/*
	read_relay_configuration reads the configuration parameters
	out of EEPROM, assuming the EEPROM has been initialized and
	written already. If the EEPROM file system has not been created,
	this routine creates it. If there is no configuration file
	existing, it uses default values for all parameters, which you
	will find declared above as global tcp_firsts. If the user is
	pressing the driver's configuration switch, the routine detects
	this and creates the default configuration file.
*/
int read_relay_configuration() {
	long prealloc; // bytes pre-allocated to the configuration file
	FATfile config_file; // configuration file

	int rc; // return code
	static char scratch[CONFIG_LENGTH]; // scratch string
	char* parameter;
	char* value;

	// Create the default configuration file contents.
	sprintf(configuration,"lwdaq_relay_configuration:\n");
	sprintf(scratch,"operator: relay_version_%d\n",VERSION_NUM);
	strcat(configuration,scratch);
	sprintf(scratch,"configuration_time: %s\n",MY_TIMESTAMP);
	strcat(configuration,scratch);
	sprintf(scratch,"password: %s\n",MY_PASSWORD);
	strcat(configuration,scratch);
	sprintf(scratch,"driver_id: %s\n",MY_ID);
	strcat(configuration,scratch);
	sprintf(scratch,"ip_addr: %s\n",MY_IP_ADDRESS);
	strcat(configuration,scratch);
	sprintf(scratch,"ip_port: %d\n",MY_PORT);
	strcat(configuration,scratch);
	sprintf(scratch,"tcp_timeout: %d\n",MY_TIMEOUT);
	strcat(configuration,scratch);
	sprintf(scratch,"security_level: %d\n",MY_SECURITY);
	strcat(configuration,scratch);
	sprintf(scratch,"gateway_addr: %s\n",MY_GATEWAY);
	strcat(configuration,scratch);
	sprintf(scratch,"subnet_mask: %s\n",MY_NETMASK);
	strcat(configuration,scratch);

	if (!read_controller_byte(CS_ADDR)) {
		printf("Formatting EEPROM disk...\n");
		rc = fat_AutoMount(FDDF_UNCOND_DEV_FORMAT
			| FDDF_UNCOND_PART_FORMAT
			| FDDF_MOUNT_DEV_0
			| FDDF_MOUNT_PART_0);
		if (rc<0) {
			if (rc == -22)  {
				printf("Disk is already formatted.\n");
			} else {
				printf("WARNING: Attempt to format failed, %ls.\n",
				error_message(rc));
			}
		} else {
			printf("Formatted EEPROM disk for the first time.\n");
		}
		rc = fat_AutoMount(FDDF_USE_DEFAULT);
		printf("Mounting EEPROM disk...\n");
		if (rc<0) {
			printf("ERROR: Disk mount failed, %ls.\n", error_message(rc));
			return -1;
		}
		printf("Creating configuration file...\n");
		prealloc = 0;
		rc = fat_Open(fat_part_mounted[0],
			CONFIG_FILE_NAME,FAT_FILE,FAT_CREATE,&config_file,&prealloc);
		if (rc<0) {
			printf("ERROR: in fat_Open, %ls.\n", error_message(rc));
			return -1;
		}
		printf("Writing default configuration to file...\n");
		rc = fat_Write(&config_file,configuration,strlen(configuration)+1);
		if (rc<0) {
			printf("ERROR: in fat_Write, %ls.\n", error_message(rc));
			return -1;
		}
		printf("Closing file and unmounting disk...\n");
		fat_Close(&config_file);
		fat_UnmountDevice(fat_part_mounted[0]->dev );
	}

	printf("Mounting EEPROM disk...\n");
	rc = fat_AutoMount(FDDF_USE_DEFAULT);
	if (rc<0) {
		printf("ERROR: in fat_AutoMount, %ls.\n", error_message(rc));
		return -1;
	}
	printf("Disk capacity is %lu bytes.\n",
	  fat_part_mounted[0]->totcluster * fat_part_mounted[0]->clustlen);

	printf("Reading configuration file...\n");
	rc = fat_Open(fat_part_mounted[0],
		CONFIG_FILE_NAME,FAT_FILE,FAT_OPEN,&config_file, NULL);
	if (rc<0) {
		printf("ERROR: in fat_Open, %ls.\n", error_message(rc));
		return -1;
	}
	fat_Read(&config_file,configuration,CONFIG_LENGTH);
	fat_Close(&config_file);
	fat_UnmountDevice(fat_part_mounted[0]->dev );

	strcpy(scratch,configuration);

	parameter=strtok(scratch,SEPCHARS);
	if (strcmp(parameter,"lwdaq_relay_configuration")) {
		printf("Error: contents start with <%s>.\n",parameter);
		return -1;
	}

	ifdown(IF_ETH0);
	while (ifpending(IF_ETH0) == IF_COMING_DOWN) {tcp_tick(NULL);}

	parameter=strtok(NULL,SEPCHARS);
	value=strtok(NULL,SEPCHARS);
	while ((parameter != NULL) && (value != NULL)) {
		if (!strcmp(parameter,"operator")) {
			printf("Configuration supplied by %s\n",value);
		}
		if (!strcmp(parameter,"configuration_time")) {
			printf("Configuration time stamp is %s\n",value);
		}
		if (!strcmp(parameter,"driver_id")) {
			printf("Driver identification is %s\n",value);
		}
		if (!strcmp(parameter,"password")) {
			strcpy(password,value);
			printf("Setting %s to %s\n",parameter,value);
		}
		if (!strcmp(parameter,"ip_port")) {
			ip_port=atoi(value);
			printf("Setting %s to %d\n",parameter,ip_port);
		}
		if (!strcmp(parameter,"ip_addr")) {
			ifconfig(IF_ETH0,IFS_IPADDR,aton(value),IFS_END);
			printf("Setting %s to %s\n",parameter,value);
		}
		if (!strcmp(parameter,"gateway_addr")) {
			ifconfig(IF_ETH0,IFS_ROUTER_SET,aton(value),IFS_END);
			printf("Setting %s to %s\n",parameter,value);
		}
		if (!strcmp(parameter,"subnet_mask")) {
			ifconfig(IF_ETH0,IFS_NETMASK,aton(value),IFS_END);
			printf("Setting %s to %s\n",parameter,value);
		}
		if (!strcmp(parameter,"security_level")) {
			security_level=atoi(value);
			printf("Setting %s to %d\n",parameter,security_level);
		}
		if (!strcmp(parameter,"tcp_timeout")) {
			tcp_timeout=atoi(value);
			printf("Setting %s to %d\n",parameter,tcp_timeout);
		}
		parameter=strtok(NULL,SEPCHARS);
		value=strtok(NULL,SEPCHARS);
	}

	ifup(IF_ETH0);
	while (ifpending(IF_ETH0) == IF_COMING_UP) {tcp_tick(NULL);}

	return 0;
}

/*
	write_relay_configuration writes a string to the relay
	configuration file. It does not check the string to see
	if it is in the correct format. If the format is not
	correct, a subsequent read_relay_configuration will fail.
	This routine performs a read_relay_configuration so as
	to implement the new configuration immediately.
*/
int write_relay_configuration(char* contents) {
	int rc; // return code
	static char scratch[CONFIG_LENGTH]; // scratch string
	char* parameter;
	char* value;

	long prealloc; // bytes pre-allocated to the configuration file
	FATfile config_file; // configuration file

	if (REPORT) printf("Writing new configuration to disk...\n");
	rc=fat_AutoMount(FDDF_USE_DEFAULT);
	if (rc<0) {
		printf("ERROR: in fat_AutoMount, %ls.\n", error_message(rc));
		return -1;
	}
	rc=fat_Delete(fat_part_mounted[0],FAT_FILE,CONFIG_FILE_NAME);
	prealloc = 0;
	rc = fat_Open(fat_part_mounted[0],
		CONFIG_FILE_NAME,FAT_FILE,FAT_CREATE,&config_file,&prealloc);
	if (rc<0) {
		if (REPORT) printf("ERROR: in fat_Open, %ls.\n", error_message(rc));
		return -1;
	}
	fat_Write(&config_file,contents,strlen(contents)+1);
	fat_Close(&config_file);
	fat_UnmountDevice(fat_part_mounted[0]->dev );

	if (REPORT) {
		strcpy(scratch,contents);
		parameter=strtok(scratch,SEPCHARS);
		printf("%s:\n",parameter);
		parameter=strtok(NULL,SEPCHARS);
		value=strtok(NULL,SEPCHARS);
		while ((parameter != NULL) && (value != NULL)) {
			printf("%s: %s\n",parameter,value);
			parameter=strtok(NULL,SEPCHARS);
			value=strtok(NULL,SEPCHARS);
		}
	}

	return 0;
}