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