/*
Relay Software -------------- NOTE: The PLATFORM constant selects the target system as follows: NOTE: For RCM2200 and RCM3200, after you install Dynamic C, and before you compile this code, edit BIOS\RABBITBIOS.C and change 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. */ // 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) #define PLATFORM 1 // Current version number #define VERSION_NUM 13 // The TCP_REPORT flag turns on additional TCPIP diagnostic messages. #define TCP_REPORT 1 // 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 1 // 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 // SIAP messages #define MIN_SIAP_PORT 30000 // min range for SIAP communication #define MAX_SIAP_PORT 40000 // max range for SIAP communication #define SIAP_ID_LENGTH 4 #define SIAP_FRAME_SIZE 8 #define SIAP_OPEN_STRING "DONE" // 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. In // Bios\RabbitBios.c set XMEM_RESERVE_SIZE to 0x4000L. This // reserves 64KBytes of space on the EEPROM for the file system. #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" #endif #if PLATFORM == 3 // For RCM4200 we use the FAT file system. The file 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 "fat.lib" #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); int soar_transmit_string(tcp_Socket* s, char* contents); // 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; char lwdaq; /* 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 router_addr_string[20]; // 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. Determine if the message protocol // is siap or lwdaq based upon the port number. read_relay_configuration(); lwdaq = ((ip_portMAX_SIAP_PORT)); // Ping the router to register MAC address. ifconfig(IF_ANY,IFG_ROUTER_DEFAULT,&router_addr,IFS_END); inet_ntoa(router_addr_string,router_addr); if (TCP_REPORT) printf("Attempting to ping router at %s.\n",router_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"); while (1) { // Reset LWDAQ interface variables. logged_in=0; tcp_first=-1; tcp_available=0; status=0; 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); printf("Connection established...\n"); if (!lwdaq) { strcpy(out_buffer,SIAP_OPEN_STRING); soar_transmit_string(&socket,out_buffer); } while (1) { // If there is data available, we proceed to read the next // message. Otherwise we wait. if (tcp_available==0) { 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) { 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 repeadetly 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 // // 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); 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); 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]; 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:{ 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 29 @ 22 MHz // RCM3200 870 ns 37 @ 44 MHz // RCM4200 440 ns 25 @ 58 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 calculation comes out very close to our // observations. // // 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. 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. 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 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: ioe ld a,(hl) ;6 + wait states ld (de),a ;7 inc de ;2 dec b ;2 jr nz,inner_loop_sr ;5 total 22 + 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; // In the A2037E and A2064A, the IO Address lies within the // range 0xE000 to 0xFFFF so that we provoke a pulse on PE7, // which we are use as our read and write strobe. In the // A2064F, the IO Address lies within 0xC000 and we use PE6 as // the strobe. In the A2037E and A2064A, we write the register // address to Parallel Port A, as well as a 1 in bit 6 to // indicate a read cycle. In the A2064F we Set !CW, or PE3, // to 0 to indicate a read cycle, but don't have to write // the register address to the parallel port. #if PLATFORM == 1 io_addr=0xE000; WrPortI(PADR,NULL,register_addr); #endif #if PLATFORM == 2 || PLATFORM == 3 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. 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: ld a,(de) ;6 ioe ld (hl),a ;6 + 3 or 15 wait states dec b ;2 inc de ;2 jr nz,inner_loop_sw ;5 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) ;6 ioe ld (hl),a ;6 + 3 or 15 wait states dec b ;2 inc de ;2 jr nz,loop_sw ;5 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; // In the A2037E and A2064A, the IO Address lies within the // range 0xE000 to 0xFFFF so that we provoke a pulse on PE7, // which we are use as our read and write strobe. In the // A2064F, the IO Address lies within 0xC000 and we use PE6 as // the strobe. In the A2037E and A2064A, we write the register // address to Parallel Port A, as well as a 1 in bit 6 to // indicate a read cycle. In the A2064F we Set !CW, or PE3, // to 0 to indicate a read cycle, but don't have to write // the register address to the parallel port. #if PLATFORM == 1 io_addr=0xE000; WrPortI(PADR,NULL,register_addr); #endif #if PLATFORM == 2 || PLATFORM == 3 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. 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 + 3 or 15 wait states dec b ;2 jr nz,inner_loop_sd ;5 total 16 or 22 clocks/byte. 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:{ printf("LOGIN\n"); if (in_length<1) { logged_in=0; printf("Failed with empty password.\n"); } if (!strcmp(password,in_buffer)) { logged_in=1; printf("Logged in with password: %s.\n",in_buffer); } else { logged_in=0; printf("Failed with password: %s.\n",in_buffer); } return_byte(&socket,logged_in); break; } case CONFIG_READ:{ printf("CONFIG_READ\n"); if ((logged_in==1) || (security_level==0)) { return_data(&socket,configuration,strlen(configuration)); printf("Transmitted configuration of %d characters.\n", strlen(configuration)); } else { printf("Rejected: not logged in.\n"); sock_close(&socket); goto sock_err; } break; } case CONFIG_WRITE:{ printf("CONFIG_WRITE\n"); if ((logged_in==1) || (security_level==0)) { if (in_length 0) { tcp_available=tcp_available+num_read; if (TCP_REPORT) printf("Read %d bytes into RAM buffer, now %d available.\n", num_read,tcp_available); } if (!tcp_tick(s)) { if (TCP_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 (TCP_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 (TCP_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 (TCP_REPORT) printf("Connection broken.\n"); return -1; } } if (TCP_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 for port numbers less than MIN_SIAP_PORT, and Simple Instruction-Answer Protocol (SIAP) for higher port numbers. */ int receive_message(tcp_Socket* s, long* id, long* len, char* content) { char code; if (lwdaq) { // If this is a LWDAQ Message, we start by reading 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) { printf("Close code received, closing socket.\n"); return -1; } else { printf("Invalid start code, closing socket.\n"); } sock_close(s); return -1; } } if (lwdaq) { // For LWDAQ messages, we read the message identifier // then the content length. buffered_socket_read(s,(char*)id,sizeof(*id)); buffered_socket_read(s,(char*)len,sizeof(*len)); } else { // For SIAP messages, we read the content length then the // message identifier. buffered_socket_read(s,(char*)len,sizeof(*len)); buffered_socket_read(s,(char*)id,sizeof(*id)); } // We flip the big-endian long integer bytes around to make them // little-endian. *id=flip_bytes(*id); *len=flip_bytes(*len); // The SIAP content length includes the message id, so we must // correct it. if (!lwdaq) {*len=*len-SIAP_ID_LENGTH;} if (*len>BUFF_SIZE) { 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; } // If this is a LWDAQ message, read the end code. if (lwdaq) { buffered_socket_read(s,&code,sizeof(code)); if (code!=END_CODE) { 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; if (lwdaq) { 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); } else { lp=(long*)&buff[0]; *lp=flip_bytes(len+SIAP_ID_LENGTH); lp=(long*)&buff[SIAP_ID_LENGTH]; *lp=flip_bytes(id); sock_write(s,buff,SIAP_FRAME_SIZE); } return 0; } /* return_footer sends a terminating sequence through a socket. */ int return_footer(tcp_Socket* s) { char buff[100]; if (lwdaq) { 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; } /* soar_transmit_string sends a soar message containing a string of characters. The first four bytes sent over the socket are the length of the string. The string follows, without its terminating null character. */ int soar_transmit_string(tcp_Socket* s, char* contents) { char buff[100]; long* lp; lp=(long*)&buff[0]; *lp=flip_bytes(strlen(contents)); sock_write(s,buff,sizeof(long)); sock_write(s,contents,strlen(contents)); 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 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"); rc=fopen_wr(&config_file,CONFIG_FILE_NAME); if (rc) { printf("Failed with error %d.\n",errno); return -1; } 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 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) { printf("ERROR: in fat_AutoMount, %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_MUST_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); #endif strcpy(scratch,configuration); parameter=strtok(scratch,SEPCHARS); if (strcmp(parameter,"lwdaq_relay_configuration")) { printf("Error: contents start with <%s>.\n",parameter); return -1; } 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")) { tcp_config("MY_IP_ADDRESS",value); ifconfig(IF_ETH0,IFS_DOWN,IFS_IPADDR,aton(value),IFS_UP,IFS_END); printf("Setting %s to %s\n",parameter,value); } if (!strcmp(parameter,"gateway_addr")) { ifconfig(IF_ETH0,IFS_DOWN,IFS_ROUTER_SET,aton(value),IFS_UP,IFS_END); printf("Setting %s to %s\n",parameter,value); } if (!strcmp(parameter,"subnet_mask")) { ifconfig(IF_ETH0,IFS_DOWN,IFS_NETMASK,aton(value),IFS_UP,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); } 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. */ 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 printf("Writing new configuration to disk...\n"); rc=fopen_wr(&config_file,CONFIG_FILE_NAME); if (rc) { printf("Failed with error %d.\n",errno); return -1; } fseek(&config_file,0,SEEK_SET); fwrite(&config_file,contents,strlen(contents)+1); fclose(&config_file); #endif #if PLATFORM == 3 long prealloc; // bytes pre-allocated to the configuration file FATfile config_file; // configuration file printf("Writing new configuration to disk...\n"); prealloc = 0; rc = fat_Open(fat_part_mounted[0], CONFIG_FILE_NAME,FAT_FILE,FAT_OPEN,&config_file,&prealloc); if (rc<0) { printf("ERROR: in fat_Open, %ls.\n", error_message(rc)); return -1; } fat_Write(&config_file,contents,strlen(contents)+1); fat_Close(&config_file); #endif 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; }