/*<pre>

Relay Software
--------------

This code runs on a Rabbit Semiconductor embedded ethernet module, any of the
models RCM2200, RCM3200, RCM4200, or RCM6700. It provides a TCP/IP server of
a particular type we call a LWDAQ Relay.

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program.  If not, see <https://www.gnu.org/licenses/>.

NOTE: The PLATFORM constant selects the target system.

NOTE: This code compiles on Dynamic C 9.62 for RCM4200 and RCM3200 and on
Dynamic C 10.64 for RCM4200 and RCM6700. In Dynamic C 10.72 add the following
line to your project define list: MAX_FIRMWARE_BINSIZE=0x80000.

NOTE: For RCM2200 and RCM3200, after you install Dynamic C and before you
compile this code, edit \lib\bioslib\memconfig.lib and change the compiler macro
XMEM_RESERVE_SIZE to 0x4000L or greater.

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 8 adds the MAC_READ instruction, to which the Relay responds by
transmitting its MAC address. Version 8 corrects a bug in the buffered TCP/IP
read routine, and guarantees that closing the TCP/IP socket will always cause
the Relay to return to its listening state. Verion 8 corrects a bug in the
security level, so that trying to execute any instruction other than a LOGIN
when the security level is 2 and you have not yet logged in results in an
immediate closing of the socket, and a return to the listening state.

Version 9 slows down the controller interface by switching to seven wait states
instead of 3. This allows it to work in the A2064A and A2064X boards without
modification. We add the STREAM_DELETE job, which writes a constant byte value
repeatedly to the same address.

Version 10 cleans up TCPIP error handling and socket closure. In particular,
when the relay receives an invalid message start code, it closes the existing
socket immediately, instead of flushing the socket and waiting for more input.

Version 11 is compatible with Dynamic C Version 9.21, and backwards-compatible
with Dynamic C Version 7.33. To get the code to work on DC9.21, we had to
declare as static variables all main() variable that we pass to our in-line
assembler code. The compiled code is marginally faster at TCPIP data transfer
with DC9.21 than with DC7.33.

Version 12 begins by improving the implementation of carriage returns in the
debug reporting. We move the controller interface configuration into a separate
routine, which we comment more heavily. We speed up the stream read assembler
loop slightly by using ld a,(hl) instead of ld a,0xE000.

Version 12 continues with the addition of the zero-length call to our buffered
socket read routine. The zero-length call clears the TCPIP socket buffer. Only
when the buffer is clear will tcp_tick inform us that a socket has been closed
or broken. We use the zero-length call in BYTE_POLL to abort when the client
closes the socket. Until now, all versions of this code would fail to abort from
a BYTE_POLL whenever there was incoming data still to be read out of the TCPIP
socket.

Version 12 continues with the addition of a ping to the default router at
start-up. This ping serves to advertise the RCM's MAC address to the router, and
so bring the router up to date if we have just switched one RCM for another on
the same physical socket, and assigned both the same IP address. As part of this
addition to the code, we eliminate tcp_config and replace it with calls to
ifconfig.

Version 12 continues with multi-platform support through the PLATFORM constant.

Version 12 continues with improvements to the response to RAM buffer overflow
and messages whose content length exceeds the input buffer length.

Version 13 adds support for the RCM4200 with the interface defined by the A2101A
schematic. We compile for RCM4200 using Dynamic C Version 10.23, and for RCM2200
and RCM3200 with 9.21. It turns out that printf commands are ignored by all RCMs
at run-time when the programming cable is not connected. So we clean up our code
by removing the REPORT flag.

Version 13 We no longer need to add a null character at the end of the
configuration data we send to the Rabbit Control Module. The CONFIG_WRITE code
adds the null character for us.

Version 13 We replace the message-parsing and message-construction code in the
main event loop with subroutine calls. This slows down the message response time
a little, but greatly simplifies the code and makes it possible for us to
implement new message protocols by enhancing only the subroutines rather than
our main loop.

Version 13 We add support for the Simple Instruction-Answer Protocol, SIAP,
which we activate when the port number is greater than MIN_SIAP_PORT and less
than MAX_SIAP_PORT.

Version 13 We add the ECHO message. The server takes the contents of the ECHO
message and returns them un-modified in a DATA_RETURN message. The contents can
be a string or binary data. We add the STREAM_WRITE message to write data to a
memory portal. We add the REBOOT message, which forces the module to re-boot and
re-load its configuration from EEPROM. Clients are restricted by the security
level from forcing the re-boot in the same way they are restricted from
re-writing the EEPROM.

Version 13 We change the way the server handles unrecognized messages and
efforts to execute instructions forbidden without a log-in. The server now
closes the socket.

Version 14 We remove SIAP support in order to simplify the code, and we migrate
to DC10.64 for RCM4200 and later, and to DC9.62 for RCM3200 and earlier. We
change the way we handle errors from an attempt to format the EEPROM disk during
a configuration reset. We do not abort the configuration reading when we
encounter an error, because we will get an error when trying to force a
re-format of an already-formatted disk. We report the state of the re-format
attempt to the console. Another problem that arose when we moved to DC10.64 was
that printf commands are active even when the programming cable is not
connected, so we restore the REPORT flag, and set it to zero by default. This
turns off all printing during operation, but leaves the start-up printing
intact. The REPORT flag must be set to zero for production code, or else the
instruction handling speed slows down by an order of magnitude.

Version 14 We add support for RCM6700.

Version 14 We remove the "lwdaq" variable that selects lwdaq or siap. This
variable was not being initialized so that the code was working only when it by
chance was set.

Version 14 We correct the manner in which the ETH0 interface is shut down and
started again during the configuration read. Until we corrected this problem,
the RCM6700 would not ping the local router on power-up.

Version 15 We call tcp_reserveport to create a queue for socket connections.
Multiple clients can connect to the LWDAQ server, but their sockets will be kept
waiting until the first one has closed. Thus multiple clients can share the same
LWDAQ driver with no collisions.

(C) 2003-2021 Kevan Hashemi, Brandeis University
(C) 2021-2025 Kevan Hashemi, Open Source Instruments Inc.
*/

// Set PLATFORM to select the target system for compilation.
// 1: A2037E (RCM2200 on LWDAQ Driver with Ethernet Interface)
// 1: A2064A (RCM2200 on TCPIP-VME Interface)
// 2: A2064F (RCM3200 on TCPIP-VME Interface)
// 3: A2101A (RCM4200 on Timing and Control Module)
// 4: A2071E (RCM6700 on LWDAQ Driver with Ethernet Interface)
#define PLATFORM 4

// Current version number
#define VERSION_NUM 15

// 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 RCM3200 and RCM4200 interfaces.
#if PLATFORM >= 2
	#define PORTA_AUX_IO
#endif

// 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
#if PLATFORM == 1 || PLATFORM == 2
	// For RCM2200 and RCM3200 we use the fs2 file system because the fat file
	// system library does not support these devices, while the older fs2 system
	// does.
	#define FS2_USE_PROGRAM_FLASH 16 // kbytes for file system
	#define CONFIG_FILE_NAME 10 // a numerical name for the config file
	#use "fs2.lib" // the fs2 file system library
#endif
#if PLATFORM == 3 || PLATFORM == 4
	// For RCM4200 and RCM6700 we use the FAT file system. This system uses the
	// on-board 8-MByte serial EEPROM, and so stays away from 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
#endif

// 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,count_lb,count_hb;
	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. If the message is mal-formed, in 
			// particular if it has the wrong start code byte, the receive
			// message routine will close the socket.
			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
				// RCM2200	400 us		510 us
				// RCM3200	210 us		270 us
				// RCM4200	110 us		150 us
				// 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];
					write_controller_byte(register_addr,value);
					if (REPORT) printf("BYTE_WRITE to %d of %d.\n",register_addr,value);
					break;
				}

				case BYTE_READ:{
					register_addr=in_buffer[3];
					value=read_controller_byte(register_addr);
					if (REPORT) printf("BYTE_READ from %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 %d for %d...\n",register_addr,value);
					while (read_controller_byte(register_addr) != value) {
						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;
				}

				// We measured the byte-transfer speed during STREAM_READ with
				// an oscilloscope on the controller data strobe.
				//
				// Platform	Byte Time		Clock Cycles
				// RCM2200	1400 ns	  30 @ 22 MHz
				// RCM3200	870 ns	  38 @ 44 MHz
				// RCM4200	440 ns	  26 @ 58 MHz
				// RCM6700	230 ns	  39 @ 187 MHz
				//
				// For each platform we give the number of clock cycles used by
				// the inner loop of our STREAM_READ assembler code. When we
				// divide by the clock speed, we get the theoretical byte
				// transfer time in microseconds.
				//
				// The number of clock cycles used by the inner loop varies
				// between the platforms because of the different numbers of
				// wait states we insert in the I/O read cycle. The RCM2200 and
				// RCM3200 insert wait states to allow time for A2037As in a VME
				// crate to respond. But the RCM4200 has no need to wait for VME
				// boards, and adds only three wait states to the I/O read. The
				// RCM6700 is so fast that we must insert 15 wait states even to
				// communicate with a controller running on a 40-MHz clock.
				case STREAM_READ:{
 					// The register address will be the lower six bits
 					// specified in the stream read register address byte.
					register_addr=in_buffer[3] & 0x3F;

					// The content length is in the input buffer.
					long_ptr=(long*)&in_buffer[4];
					content_length=flip_bytes(*long_ptr);

					// We can now print a status message.
					if (REPORT) printf("STREAM_READ from %d of length %lu...\n",
		 			register_addr,content_length);

					// Send the data return message header.
					return_header(&socket,DATA_RETURN,content_length);

					// Set up the stream read according to PLATFORM. See
					// controller interface configuration routine for more
					// information.
					#if PLATFORM == 1
					io_addr=0xE000;
					WrPortI(PADR,NULL,(0x40 | register_addr));
					#endif
					#if PLATFORM == 2 || PLATFORM == 3 || PLATFORM == 4
					io_addr=(0xC000 | register_addr);
					WrPortI(PEB3R,NULL,0xFF);
					#endif

					// 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 de,out_buffer
						ld hl,count_hb
						ld c,(hl)
						ld a,c
						cp a,0
						jr z,remainder_sr

						ld b,0x00
						ld hl,(io_addr)
						outer_loop_sr:
							inner_loop_sr:		; clocks for RCM6700 after slash
								ioe ld a,(hl)	; 7 + wait states
								ld (de),a		; 7
								inc de			; 2
								dec b			; 2
							jr nz,inner_loop_sr	; 5/6 total 23/24 + wait states
							dec c
						jr nz,outer_loop_sr

						remainder_sr:

						ld hl,count_lb
						ld b,(hl)
						ld a,b
						cp a,0
						jr z,done_sr

						ld hl,(io_addr)
						loop_sr:
							ioe ld a,(hl)
							ld (de),a
							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 lower six bits specified
					// in the stream read register address byte.
					register_addr=in_buffer[3] & 0x3F;

					// Set up the stream read according to PLATFORM. See
					// controller interface configuration routine for more
					// information.
					#if PLATFORM == 1
					io_addr=0xE000;
					WrPortI(PADR,NULL,register_addr);
					#endif
					#if PLATFORM == 2 || PLATFORM == 3 || PLATFORM == 4
					io_addr=(0xC000 | register_addr);
					WrPortI(PEB3R,NULL,0x00);
					#endif

					// 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 %d of length %lu...\n",
		 				register_addr,content_length);

					// 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 de,in_buffer
						inc de
						inc de
						inc de
						inc de
						
						ld hl,count_hb
						ld c,(hl)
						ld a,c
						cp a,0
						jr z,remainder_sw
						
						ld b,0x00
						ld hl,(io_addr)
						
						outer_loop_sw:
							inner_loop_sw:		; clocks for RCM6700 after slash
								ld a,(de)		; 6
								ioe ld (hl),a	; 6 + wait states
								dec b			; 2
								inc de			; 2
							jr nz,inner_loop_sw	; 5/6 total 21/22 + wait states
							dec c
						jr nz,outer_loop_sw
						
						remainder_sw:
						ld hl,count_lb
						ld b,(hl)
						ld a,b
						cp a,0
						jr z,done_sw
						
						ld hl,(io_addr)
						
						loop_sw:
						ld a,(de)
						ioe ld (hl),a
						dec b
						inc de
						jr nz,loop_sw
						
						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 lower six bits specified
					// in the stream read register address byte.
					register_addr=in_buffer[3] & 0x3F;

					// Set up the stream delete according to PLATFORM. See
					// controller interface configuration routine for more
					// information.
					#if PLATFORM == 1
					io_addr=0xE000;
					WrPortI(PADR,NULL,register_addr);
					#endif
					#if PLATFORM == 2 || PLATFORM == 3 || PLATFORM == 4
					io_addr=(0xC000 | register_addr);
					WrPortI(PEB3R,NULL,0x00);
					#endif
	
					// 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);
	
					// 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 hl,count_hb
						ld c,(hl)
						ld a,c
						cp a,0
						jr z,remainder_sd

						ld hl,value
						ld a,(hl)
						ld b,0x00
						ld hl,(io_addr)

						outer_loop_sd:
							inner_loop_sd:
								ioe ld (hl),a	; 6 + wait states
								dec b			; 2
							jr nz,inner_loop_sd	; 5/6 total 13/14 + wait states.
							dec c
						jr nz,outer_loop_sd

						remainder_sd:
						ld hl,count_lb
						ld b,(hl)
						ld a,b
						cp a,0
						jr z,done_sd

						ld hl,value
						ld a,(hl)

						ld hl,(io_addr)
						loop_sd:
							ioe ld (hl),a
							dec b
						jr nz,loop_sd

						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_length<CONFIG_LENGTH) {
							if (REPORT) printf("Accepted %d characters.\n",in_length);
						in_buffer[(int) in_length]=0x00;
							write_relay_configuration(in_buffer);
						} else {
							if (REPORT) printf(
								"Rejected %d characters (too long).\n",
								in_length);
							sock_close(&socket);
							goto sock_err;
						}
					} else {
						if (REPORT) printf("Rejected: not logged in.\n");
						sock_close(&socket);
						goto sock_err;
					}
					break;
				}

				case MAC_READ:{
					if (REPORT) printf("MAC_READ\n");
					pd_getaddress(0,&out_buffer);
		 			return_data(&socket,out_buffer,MAC_LENGTH);
					break;
				}

	 			case ECHO:{
			 		if (REPORT) printf("ECHO\n");
					return_data(&socket,in_buffer,in_length);
					in_buffer[(int) in_length]=0x00;
					if (REPORT) printf("%s\n",in_buffer);
					break;
				}

				case REBOOT:{
					if (REPORT) printf("REBOOT\n");
					if ((logged_in==1) || (security_level==0)) {
						if (REPORT) printf("Closing socket...\n");
						sock_close(&socket);
						if (REPORT) printf("Rebooting...\n");
						forceSoftReset();
						break;
					} else {
						if (REPORT) printf(
						"Rejected: not logged in, closing socket.\n");
						sock_close(&socket);
						break;
					}
					break;
				}

				default: {
					if (REPORT) printf("BAD MESSAGE, closing socket.\n");
						sock_close(&socket);
					break;
				}
			}

			continue;

			sock_err: {
				sock_abort(&socket);
				if (REPORT) printf("Connection closed.\n");
				break;
			}
		}
	}
}

/*
flip_bytes reverses the order of four bytes in a thirty-two bit
variable, so as to convert little-endian to big-endian byte order,
and visa-versa.
*/
long flip_bytes(long original)
{
	char *a,*b;
	long result;
	a=((char *) &original);
	b=((char *) &result)+3;
	*b=*a;
	a=a+1;b=b+(-1);*b=*a;
	a=a+1;b=b+(-1);*b=*a;
	a=a+1;b=b+(-1);*b=*a;
	return result;
}

/*
	configure_controller_interface sets up the I/O ports on the Rabbit Core
	Module to communicate with the controller logic chip. The set-up depends
	upon the target platform.

	PLATFORM=1, A2037E, A2064X and A2064A.

	The connections between the RCM2200 and Controller are as follows:
	
	Controller		RCM3200
	CA0..CA5		PA0..PA5
	!CW			PA6
	RCD0..RCD7	 D0..D7
	!LNK			!LNK
	!ACT			!ACT
	!IOWR		  !IOWR
	!IORD		  !IORD
	!RESET		 !RESET_IN
	!CDS			PE7
	
	We use PE7 from IO port E as the data strobe, PA6 from IO port A as the
	read/write line, PA0..PA5 from IO Port A as the address bus, and we use the
	Rabbit 2000 data bus directly for the controller data.

	PLATFORM=2, A2064F

	The connections between the RCM3200 and Controller are as follows:
	
	Controller		RCM3200
	CA0..CA5		PB2..PB7
	!CW			PE3
	RCD0..RCD7	 PA0..PA7
	!LNK			!LNK
	!ACT			!ACT
	!IOWR		  !IOWR
	!IORD		  !IORD
	!RESET		 !RESET_IN
	!CDS			PE6
	
	We use PE6 as the data strobe, PE3 as the read/write line, IO Port A as the
	data bus, and IO Port B as the address bus.

	PLATFORM=3, A2101A

	The connections between the RCM4200 and Controller are as follows:
	
	Controller		RCM4200 Signal
	CA0..CA5		PB2..PB7
	!CW			PE3
	!CDS			PE6
	RCD0..RCD7	 PA0..PA7
	!ETH			!ETH
	!RESET		 !RESET_IN
	
	PLATFORM=4, A2071E
	
	The connections between the RCM6700 and Controller are as follows:
	
	Controller		RCM6700 Signal
	CA0..CA5		PB2..PB7
	!CW			PE3
	!CDS			PE6
	RCD0..RCD7	 PA0..PA7
	ETH			LNK
	!RESET		 !RESET_IN
*/
void configure_controller_interface() {

// Set PLATFORM to select the target system for compilation.
// 1: A2037E (RCM2200 on LWDAQ Driver with Ethernet Interface)
// 1: A2064A (RCM2200 on TCPIP-VME Interface)
// 2: A2064F (RCM3200 on TCPIP-VME Interface)
// 3: A2101A (RCM4200 on Timing and Control Module)
// 3: A2071E (RCM6700 on LWDAQ Driver with Ethernet Interface)

#if PLATFORM == 1
  printf("Configured for A2037E or A2064A.\n");

	// IO Port A can act as a special communications port called
	// a "slave port", which we don't want, or as an input, or as
	// an output. We want it to be an output, so we can use it to
	// drive CA0..CA5 and !CW. We write 10000100 to the Slave Port
	// Control Register. Now Port A is an output register.
	WrPortI(SPCR,&SPCRShadow,0x84);

	// We set Port A to all ones just to be sure of the initial
	// state of the address lines and !CW.
	WrPortI(PADR,&PADRShadow,0xff);

	// We want to use PE7 as our data strobe. We set PE7 to act
	// as a strobe for I/O cycles in address range 0xE000 to
	// 0xFFFF by setting bit 7 in the Parallel Port E Function
	// Register.
	WrPortI(PEFR,&PEFRShadow,(PEFRShadow|0x80));

	// Now that PE7 has been configured as a data strobe, we can
	// activate it by writing to bit 7 of Parallel Port E Data
	// Direction Register.
	WrPortI(PEDDR,&PEDDRShadow,(PEDDRShadow|0x80));

	// We set the length of the PE7 strobe using the I/O Bank
	// Seven Control Register. Bits 7 and 6 set the number of wait
	// states inserted in the strobe. Bits 5 and 4 we set to 11 to
	// make PE7 a read and write strobe. We set bit 3 to enable
	// write cycles to I/O addresses 0xE000 to 0xFFFF. We write
	// 0x38 for 15 wait states, 0x78 for seven, 0xB8 for three,
	// and 0xF8 for one.
	WrPortI(IB7CR,&IB7CRShadow,0x78);// 7 wait states
#endif

#if PLATFORM == 2 || PLATFORM == 3  || PLATFORM == 4
	if (PLATFORM==2) printf("Configured for A2064F.\n");
	if (PLATFORM==3) printf("Configured for A2101A.\n");
	if (PLATFORM==4) printf("Configured for A2071E.\n");

	// IO Port A can act as a special communications port called
	// a "slave port", which we don't want, or as an input, or as
	// an output, or as an auxilliary data bus. We want it to be an
	// auxilliary data bus. We write 10001100 to the Slave Port
	// Control Register. Now Port A will carry data to the processor
	// on an I/O read, and carry data out of the processor on an
	// I/O write. Another consequence of enabling the auxilliary
	// data bus is that Parallel Port B will be used as the
	// auxilliary address bus (see below).
	WrPortI(SPCR,&SPCRShadow,0x8C);

	// We set up PE6 to act as a strobe for I/O cycles in address range
	// 0xC000 to 0xDFFF by setting bit 6 in the Parallel Port E
	// Function Register.
	BitWrPortI(PEFR,&PEFRShadow,1,6);

	// We are using PE6, which is bit 6 on Parallel Port E, as our
	// data strobe. We make PE6 an ouput bit by writing to
	// the Parallel Port E Data Direction Register.
	BitWrPortI(PEDDR,&PEDDRShadow,1,6);

	// We set the length of the PE6 strobe using the I/O Bank
	// Six Control Register. The top two bits set the number of wait
	// states inserted in the strobe. We must also set bit 3 of this
	// same register to enable write cycles to I/O addresses 0xC000
	// to 0xDFFF. And we must configure the strobe as a read/write
	// data strobe instead of a chip select. We write 0x38 for 15 wait
	// states, 0x78 for seven, 0xB8 for three, and 0xF8 for one.
	#if PLATFORM == 2
	WrPortI(IB6CR,&IB6CRShadow,0x38); // 15 wait states
	#endif
	#if PLATFORM == 3
	WrPortI(IB6CR,&IB6CRShadow,0xB8); // 3 wait states
	#endif
	#if PLATFORM == 4
	WrPortI(IB6CR,&IB6CRShadow,0x38); // 15 wait states
	#endif

	// We want to use PE3 as our read/write line. We set PE3 as an
	// output bit by setting PEDDR bit 3.
	BitWrPortI(PEDDR,&PEDDRShadow,1,3);

	// We set PE3 to 1 to indicate a default READ state. We use the
	// dedicated PE3 control register to set its value to one.
	WrPortI(PEB3R,NULL,0xFF);

	// We set Parallel Port B to be an output register by setting
	// every bit in the Parallel Port B Data Direction Register. Because
	// we have already enabled the auxilliary data bus with our initial
	// write to SPCR (see above), Parallel Port B will now carry the
	// least significant bits of the I/O address.
	WrPortI(PBDDR,&PBDDRShadow,0xFF);
#endif
}

/*
	write_controller_byte sets the controller address, then writes a byte.
*/
void write_controller_byte(char register_addr, char value){

#if PLATFORM == 1
	// We write the lower six bits of register_addr to
	// Parallel Port A. These lines drive CA0..CA5 on the
	// controller. We make sure PA6, the sixth bit on Prallel
	// Port A, is 0, to indicate a write cycle. PA6 drives
	// !CW on the controller.
	WrPortI(PADR,NULL,(register_addr & 0x3F));

	// We write "value" to address 0xE000, which causes "value"
	// to appear on D0..D7, and provokes a data strobe on
	// PE7, which we configured as the data strobe for all
	// addresses in the range 0xE000 to 0xFFFF.
	WrPortE(0xE000,NULL,value);
#endif

#if PLATFORM == 2  || PLATFORM == 3 || PLATFORM == 4
	// Set !CW, carried on PE3, to 0 to indicate a write cycle.
 	WrPortI(PEB3R,NULL,0x00);

	// Write to 0xC000 + register_address to perform a write
	// cycle with Port B used for the address and PE6 used as
	// the data strobe.
	WrPortE((0xC000 | register_addr),NULL,value);
#endif
}

/*
	read_controller_byte sets the controller address, then reads a byte.
*/
char read_controller_byte(char register_addr){
	char value;

#if PLATFORM == 1
	// We write the lower six bits of register_addr to Parallel Port A. These
	// lines drive CA0..CA5 on the controller. We make sure PA6, the sixth bit
	// on Prallel Port A, is 1, to indicate a read cycle. PA6 drives !CW on the
	// controller.
	WrPortI(PADR,NULL,(0x40 | (register_addr & 0x3F)));

	// We read a byte from address 0xE000, which a data strobe on PE7. The
	// controller drives D0..D7 with its register value, and the processor
	// clocks this value at the end of the data strobe.
	value=RdPortE(0xE000);
#endif

#if PLATFORM == 2 || PLATFORM == 3 || PLATFORM == 4
	// Set !CW, carried on PE3, to 1 to indicate a read cycle.
 	WrPortI(PEB3R,NULL,0xFF);

	// Read from 0xC000 + register_address to perform a read
	// cycle with Port B used for the address and PE6 used as
	// the data strobe.
	value=RdPortE((0xC000 | (register_addr)));
#endif
	return value;
}

/*
	buffered_socket_read takes available bytes from the socket buffer and places
	them in a ram buffer so that we can execute commands consecutively without
	having to call the sock_fastread or sock_read routines. Each of these takes a
	couple of milliseconds to return, whether we read one byte or a thousand bytes.
	The routine returns the number of bytes it read. If the routine cannot supply
	the requested number of bytes, perhaps because the socket is closed or broken,
	it returns value -1. The routine treats len=0 as a special case. When len=0,
	it reads all available bytes from the socket and puts them in the ram buffer.
	With the socket buffer empty, the routine calls tcp_tick. If the socket is still
	open, the routine returns 0. If the socket is closed, broken, or if the ram
	buffer is overflowing with un-used data, the routine returns a value less than
	0.
	
	[15-NOV-25] Reviewing the code, we see a potential problem with tcp_first=-1.
	The first byte accessed in tcp_buffer might be the byte before the start of the
	buffer when we call the routine with len=0. But it's not clear the program
	ever calls the routine with len=0;
*/
int buffered_socket_read(tcp_Socket* s, byte* dp, int len) {
	int num_read,tcp_remaining;
	char* tcp_destination;

	// The following code handles requests for zero bytes, which have the effect
	// of reading available bytes from the tcpip socket.
	if (len==0) {
		if (RAM_BUFF_SIZE-tcp_first-tcp_available<BUFF_SIZE) {
			if (REPORT) printf("RAM buffer overflow.\n");
			return -1;
		}
		num_read=sock_fastread(s,&tcp_buffer[tcp_first+tcp_available],BUFF_SIZE);
		if (num_read>0) {
		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);
	
	// We are limited in the size of a single message by the length of our input
	// buffer, so we check to see if the contents length specified in the
	// message header exceeds the length of our buffer. If so, we close the
	// socket.
	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() {
#if PLATFORM == 1 || PLATFORM == 2
	File config_file; // configuration file
	int lx; // FS2 file system code
#endif
#if PLATFORM == 3 || PLATFORM == 4
	long prealloc; // bytes pre-allocated to the configuration file
	FATfile config_file; // configuration file
#endif
	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 PLATFORM == 1 || PLATFORM == 2
	printf ("Starting EEPROM file system...\n");
	lx=fs_get_flash_lx();
 	rc=fs_init(0,0);
	if (rc) {
		printf("Failed with error %d.\n",errno);
		if (errno==EINVAL) printf("Reserveblocks was non-zero.\n");
		if (errno==EIO) printf("Hardware error.\n");
		if (errno==ENOMEM) printf("Insufficient memory for buffers.\n");
		if (errno==ENOSPC) printf("No memory device available.\n");
		return -1;
	}
	if (!read_controller_byte(CS_ADDR)) {
		printf("Formatting EEPROM disk...\n");
		rc=lx_format(lx,0);
		if (rc) {
			printf("Failed with error %d.\n",errno);
			return -1;
		}
	}
	printf("Disk capacity is %ld bytes.\n",fs_get_lx_size(lx,1,0));
	printf("Available space is %ld bytes.\n",fs_get_lx_size(lx,0,0));
	fs_set_lx(lx,lx);
	if (!read_controller_byte(CS_ADDR)) {
		printf("Creating configuration file...\n");
		rc=fcreate(&config_file,CONFIG_FILE_NAME);
		if (rc) {
  	 		printf("Failed with error %d.\n",errno);
 			return -1;
		}
		printf("Writing configuration file...\n");
		fwrite(&config_file,configuration,strlen(configuration)+1);
		fclose(&config_file);
	}
	printf("Reading configuration file...\n");
	rc=fopen_rd(&config_file,CONFIG_FILE_NAME);
	if (rc) {
		printf("Failed with error %d.\n",errno);
		return -1;
	}
	fseek(&config_file,0,SEEK_SET);
	rc=fread(&config_file,configuration,CONFIG_LENGTH);
	fclose(&config_file);
#endif

#if PLATFORM == 3 || PLATFORM == 4
	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 );
#endif

	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;

#if PLATFORM == 1 || PLATFORM == 2
	File config_file; // configuration file

	if (REPORT) printf("Writing new configuration to disk...\n");
	rc=fopen_wr(&config_file,CONFIG_FILE_NAME);
	if (rc) {
		if (REPORT) printf("ERROR: in fopen_wr, %d.\n",errno);
		return -1;
	}
	fwrite(&config_file,contents,strlen(contents)+1);
	fclose(&config_file);
#endif

#if PLATFORM == 3 || PLATFORM == 4
	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 );
#endif

	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;
}