Mercurial > hg > index.cgi
view src/lwwire.c @ 0:bef2801ac83e
Initial checkin with reference implementation of core protocol
Initial checkin. Has the initial version of the protocol documentation along
with a reference implementation of the core protocol.
author | William Astle <lost@l-w.ca> |
---|---|
date | Sun, 08 May 2016 12:56:39 -0600 |
parents | |
children | 2f2cbd2d2561 |
line wrap: on
line source
/* This program implements the lwwire protocol. It expects STDIN and STDOUT to be connected to an appropriately configured communication channel. The following timeouts are specified by the protocol and are listed here for convenience: Between bytes in a request: 10 milliseconds Between bytes reading a response (client): 10ms <= timeout <= 1000ms Server receiving bad request: >= 1100 milliseconds Implementation notes: This implementation uses low level I/O calls (read()/write()) on STDIN and STDOUT because we MUST have fully unbuffered I/O for the protocol to function properly and we really do not want the stdio overhead for that. The complexity of the lwwire_readdata() and lwwire_writedata() functions is required to handle some possible corner cases that would otherwise completely bollix everything up. Command line options: drive=N,PATH Specify that drive #N should reference the file at PATH. Note that the file at PATH will be created if it doesn't exist, but only if it is actually accessed. N is a decimal number from 0 to 255. If N is prefixed with "C", the drive is treated as read-only. By default, no drives are associated with files. Also, it is unspecified what happens if multiple protocol instances access the same drive. */ // for nanosleep #define _POSIX_C_SOURCE 199309L #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> #include <time.h> #include <unistd.h> #define LWERR_NONE 0 #define LWERR_CHECKSUM 0xF3 #define LWERR_READ 0xF4 #define LWERR_WRITE 0xF5 #define LWERR_NOTREADY 0xF6 struct lwwire_driveinfo { char *path; FILE *fp; int isconst; }; struct lwwire_driveinfo drivedata[256]; void lwwire_protoerror(void); int lwwire_readdata(void *, int, int); int lwwire_writedata(void *, int); void lwwire_write(void *, int); int lwwire_read(void *, int); int lwwire_read2(void *, int, int); void lwwire_reset(void); int lwwire_fetch_sector(int dn, int lsn, void *); int lwwire_save_sector(int dn, int lsn, void *); int nonblock(int); void lwwire_proto_read(void); void lwwire_proto_write(void); void lwwire_proto_readex(void); void lwwire_proto_requestextension(void); void lwwire_proto_disableextension(void); void lwwire_proto_extensionop(void); int main(int argc, char **argv) { unsigned char buf[32]; time_t curtime; struct tm *tmval; int rv; int i; // make stdin and stdout non-blocking if (nonblock(0) < 0) { fprintf(stderr, "Cannot make stdin non-blocking: %s\n", strerror(errno)); exit(1); } if (nonblock(1) < 0) { fprintf(stderr, "Cannot make stdout non-blocking: %s\n", strerror(errno)); exit(1); } memset(&drivedata, 0, sizeof(drivedata)); for (i = 1; i < argc; i++) { if (strncmp("drive=", argv[i], 6) == 0) { int dn=0; int isconst = 0; char *ptr; ptr = argv[i] + 6; if (*ptr == 'C') { isconst = 1; ptr++; } while (*ptr >= '0' && *ptr <= '9') { dn = dn * 10 + (*ptr - '0'); ptr++; } if (*ptr != ',' || dn > 255) { fprintf(stderr, "Ignoring invalid drive specification: %s\n", argv[i]); continue; } ptr++; drivedata[dn].path = ptr; drivedata[dn].isconst = isconst; } } fprintf(stderr, "Running with the following disk images:\n"); for (i = 0; i < 256; i++) { if (drivedata[i].path) { fprintf(stderr, "[%d%s] %s\n", i, drivedata[i].isconst ? "C" : "", drivedata[i].path); } } // main loop reading operations and dispatching for (;;) { rv = lwwire_readdata(buf, 1, 0); if (rv < 0) { fprintf(stderr, "Error or timeout reading operation code.\n"); lwwire_protoerror(); continue; } if (rv == 0) { fprintf(stderr, "EOF on comm channel. Exiting.\n"); exit(0); } fprintf(stderr, "Handling opcode %02X\n", buf[0]); // we have an opcode here switch (buf[0]) { case 0x00: // NOOP break; case 0x23: // TIME curtime = time(NULL); tmval = localtime(&curtime); buf[0] = tmval -> tm_year; buf[1] = tmval -> tm_mon; buf[2] = tmval -> tm_mday; buf[3] = tmval -> tm_hour; buf[4] = tmval -> tm_min; buf[5] = tmval -> tm_sec; buf[6] = tmval -> tm_wday; lwwire_write(buf, 7); break; case 0x46: // PRINTFLUSH // no printer is supported by this implemention so NO-OP break; case 0x47: // GETSTAT (useless dw3 operation) case 0x53: // SETSTAT (useless dw3 operation) // burn two bytes from the client and do nothing lwwire_read(buf, 2); break; case 0x49: // INIT (old style INIT call) case 0x54: // TERM (old DW3 op treated same as INIT) case 0xF8: // RESET3 (junk on the line during reset) case 0xFE: // RESET1 (junk on the line during reset) case 0xFF: // RESET2 (junk on the line during reset) lwwire_reset(); break; case 0x50: // PRINT // burn a byte because we don't support any printers lwwire_read(buf, 1); break; case 0x52: // READ case 0x72: // REREAD (same semantics as READ) fprintf(stderr, "DWPROTO: read()\n"); lwwire_proto_read(); break; case 0x57: // WRITE case 0x77: // REWRITE (same semantics as WRITE) fprintf(stderr, "DWPROTO: write()\n"); lwwire_proto_write(); break; case 0x5A: // DWINIT (new style init) lwwire_reset(); if (lwwire_read(buf, 1) < 0) break; fprintf(stderr, "DWINIT: client drive code %02X\n", buf[0]); // tell the client we speak lwwire protocol buf[0] = 0x80; lwwire_write(buf, 1); break; case 0xD2: // READEX (improved reading operation) case 0xF2: // REREADEX (same semantics as READEX) fprintf(stderr, "DWPROTO: readex()\n"); lwwire_proto_readex(); break; case 0xF0: // REQUESTEXTENSION lwwire_proto_requestextension(); break; case 0xF1: // DISABLEEXTENSION lwwire_proto_disableextension(); break; case 0xF3: // EXTENSIONOP lwwire_proto_extensionop(); break; default: fprintf(stderr, "Unrecognized operation code %02X. Doing error state.\n", buf[0]); lwwire_protoerror(); break; } } } // protocol handling functions void lwwire_proto_read(void) { unsigned char buf[259]; int ec; int lsn; int i; if (lwwire_read(buf, 4) < 0) return; lsn = (buf[1] << 16) | (buf[2] << 8) | buf[3]; ec = lwwire_fetch_sector(buf[0], lsn, buf + 1); buf[0] = ec; lwwire_write(buf, 1); if (ec) return; // all this futzing around here is probably a long enough // delay but testing on real hardware is needed here ec = 0; for (i = 1; i < 257; i++) ec += buf[i]; buf[257] = (ec >> 8) & 0xff; buf[258] = ec & 0xff; lwwire_write(buf + 1, 258); } void lwwire_proto_write(void) { unsigned char buf[262]; int lsn; int ec; int i; if (lwwire_read(buf, 262) < 0) return; lsn = (buf[1] << 16) | (buf[2] << 8) | buf[3]; for (ec = 0, i = 4; i < 260; i++) ec += buf[i]; if (ec != ((buf[260] << 8) | buf[261])) { buf[0] = LWERR_CHECKSUM; } else { ec = lwwire_save_sector(buf[0], lsn, buf + 4); buf[0] = ec; } lwwire_write(buf, 1); } void lwwire_proto_readex(void) { unsigned char buf[256]; int lsn; int ec; int i; int csum; if (lwwire_read(buf, 4) < 0) return; lsn = (buf[1] << 16) | (buf[2] << 8) | buf[3]; ec = lwwire_fetch_sector(buf[0], lsn, buf); if (ec) memset(buf, 0, 256); for (i = 0, csum = 0; i < 256; i++) csum += buf[i]; lwwire_write(buf, 256); if ((i = lwwire_read2(buf, 2, 5)) < 0) { fprintf(stderr, "Error reading protocol bytes: %d, %s\n", i, strerror(errno)); return; } i = (buf[0] << 8) | buf[1]; if (i != csum) ec = LWERR_CHECKSUM; buf[0] = ec; lwwire_write(buf, 1); } void lwwire_proto_requestextension(void) { unsigned char buf[1]; if (lwwire_read(buf, 1) < 0) return; // NAK the request buf[1] = 0x55; lwwire_write(buf, 1); } void lwwire_proto_disableextension(void) { unsigned char buf[1]; if (lwwire_read(buf, 1) < 0) return; // ACK disabling any unsupported extensions buf[1] = 0x42; lwwire_write(buf, 1); } void lwwire_proto_extensionop(void) { unsigned char buf[1]; if (lwwire_read(buf, 1) < 0) return; // we don't currently support any extensions so treat as unknown lwwire_protoerror(); } // Various infrastructure things follow here. int nonblock(int fd) { int flags; flags = fcntl(fd, F_GETFL); if (flags < 0) return -1; flags |= O_NONBLOCK; return fcntl(fd, F_SETFL, flags); } /* Read len bytes from the input. If no bytes are available after 10 ms, return error. This *may* allow a timeout longer than 10ms. However, it will eventually time out. In the worse case, it is more permissive than the specification. It will not time out before 10ms elapses. If "itimeout" is 0, then it will wait forever for the first byte. Otherwise, it will time out even on the first one. */ int lwwire_readdata(void *buf, int len, int itimeout) { int toread = len; int rv; fd_set fdset; struct timeval timeout; if (itimeout == 0) { for (;;) { // now wait for the descriptor to be readable FD_ZERO(&fdset); FD_SET(0, &fdset); rv = select(1, &fdset, NULL, NULL, NULL); if (rv < 0) { // this is a last ditch effort to not break completely // in the face of a signal; it should occur only rarely // and it is not clear what the correct behaviour should // be. if (errno == EINTR) continue; return -1; } // if we actually have something to read, move on if (rv > 0) break; } } while (toread > 0) { rv = read(0, buf, toread); if (rv == toread) break; if (rv == 0) { // flag EOF so the caller knows to bail return 0; } if (rv < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { return -1; } rv = 0; } // now rv is the number of bytes read buf += rv; toread -= rv; // now wait for the descriptor to be readable FD_ZERO(&fdset); FD_SET(0, &fdset); timeout.tv_sec = 0; timeout.tv_usec = 10000 * itimeout; rv = select(1, &fdset, NULL, NULL, &timeout); if (rv < 0) { // this is a last ditch effort to not break completely // in the face of a signal; it should occur only rarely // and it is not clear what the correct behaviour should // be. if (errno == EINTR) continue; return -1; } // timeout condition if (rv == 0) { errno = ETIMEDOUT; return -1; } // anything else here means we have more bytes to read } fprintf(stderr, "Protocol bytes read (%d):", len); for (rv = 0; rv < len; rv++) fprintf(stderr, " %02X ", ((char *)(buf))[rv] & 0xff); fprintf(stderr, "\n"); return len; } /* Write data to the output. This will time out after 10 seconds. The timeout is only there in case the underlying communication channel goes out to lunch. It returns -1 on error or len on success. The timeout requires the file descriptor to be non-blocking. */ int lwwire_writedata(void *buf, int len) { int towrite = len; int rv; fd_set fdset; struct timeval timeout; while (towrite > 0) { rv = write(0, buf, towrite); if (rv == towrite) break; if (rv < 0) { if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { return -1; } rv = 0; } // now rv is the number of bytes read buf += rv; towrite -= rv; // now wait for the descriptor to be writable FD_ZERO(&fdset); FD_SET(1, &fdset); timeout.tv_sec = 10; timeout.tv_usec = 0; rv = select(2, NULL, &fdset, NULL, &timeout); if (rv < 0) { // this is a last ditch effort to not break completely // in the face of a signal; it should occur only rarely // and it is not clear what the correct behaviour should // be. if (errno == EINTR) continue; return -1; } // timeout condition if (rv == 0) { errno = ETIMEDOUT; return -1; } // anything else here means we have more bytes to write } fprintf(stderr, "Protocol bytes written (%d):", len); for (rv = 0; rv < len; rv++) fprintf(stderr, " %02X ", ((char *)(buf))[rv] & 0xff); fprintf(stderr, "\n"); return len; } // like lwwire_writedata() except it bails the program on error. void lwwire_write(void *buf, int len) { if (lwwire_writedata(buf, len) < 0) { fprintf(stderr, "Error writing %d bytes to client: %s\n", len, strerror(errno)); exit(1); } } // like lwwire_readdata() except it bails on EOF and bounces to // error state on errors, and always does the timeout for initial // bytes. It returns -1 on a timeout. It does not return on EOF. // It does not return on random errors. int lwwire_read2(void *buf, int len, int toscale) { int rv; rv = lwwire_readdata(buf, len, toscale); if (rv < 0) { if (errno == ETIMEDOUT) { lwwire_protoerror(); return -1; } fprintf(stderr, "Error reading %d bytes from client: %s\n", len, strerror(errno)); exit(1); } if (rv == 0) { fprintf(stderr, "EOF reading %d bytes from client.", len); exit(0); } return 0; } int lwwire_read(void *buf, int len) { return lwwire_read2(buf, len, 1); } /* Handle a protocol error by maintaining radio silence for at least 1100 ms. The pause must be *at least* 1100ms so it's no problem if the messing about takes longer. Do a state reset after the error. */ void lwwire_protoerror(void) { struct timespec sltime; struct timespec rtime; sltime.tv_sec = 1; sltime.tv_nsec = 100000000; while (nanosleep(&sltime, &rtime) < 0) { // anything other than EINTR indicates something seriously messed up if (errno != EINTR) break; sltime = rtime; } lwwire_reset(); } /* fetch a file pointer for the specified drive number */ FILE *lwwire_fetch_drive_fp(int dn) { if (drivedata[dn].path == NULL) return NULL; if (drivedata[dn].fp) { if (ferror(drivedata[dn].fp)) fclose(drivedata[dn].fp); else return drivedata[dn].fp; } drivedata[dn].fp = fopen(drivedata[dn].path, "r+"); if (!drivedata[dn].fp) { if (errno == ENOENT && !drivedata[dn].isconst) { drivedata[dn].fp = fopen(drivedata[dn].path, "w+"); } } return drivedata[dn].fp; } /* read a sector from a disk image */ int lwwire_fetch_sector(int dn, int lsn, void *buf) { FILE *fp; int rc; fp = lwwire_fetch_drive_fp(dn); if (!fp) return LWERR_NOTREADY; if (fseek(fp, lsn * 256, SEEK_SET) < 0) return LWERR_READ; rc = fread(buf, 1, 256, fp); if (rc < 256) { memset(buf + rc, 0, 256 - rc); } return 0; } int lwwire_save_sector(int dn, int lsn, void *buf) { FILE *fp; int rc; fp = lwwire_fetch_drive_fp(dn); if (!fp) return LWERR_NOTREADY; if (drivedata[dn].isconst) return LWERR_WRITE; if (fseek(fp, lsn * 256, SEEK_SET) < 0) return LWERR_WRITE; rc = fwrite(buf, 1, 256, fp); if (rc < 256) return LWERR_WRITE; return 0; } /* Reset the protocol state to "base protocol" mode. */ void lwwire_reset(void) { }