Mercurial > hg > index.cgi
comparison 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 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:bef2801ac83e |
---|---|
1 /* | |
2 This program implements the lwwire protocol. It expects STDIN and STDOUT | |
3 to be connected to an appropriately configured communication channel. | |
4 | |
5 The following timeouts are specified by the protocol and are listed here | |
6 for convenience: | |
7 | |
8 Between bytes in a request: 10 milliseconds | |
9 Between bytes reading a response (client): 10ms <= timeout <= 1000ms | |
10 | |
11 Server receiving bad request: >= 1100 milliseconds | |
12 | |
13 Implementation notes: | |
14 | |
15 This implementation uses low level I/O calls (read()/write()) on STDIN | |
16 and STDOUT because we MUST have fully unbuffered I/O for the protocol | |
17 to function properly and we really do not want the stdio overhead for | |
18 that. | |
19 | |
20 The complexity of the lwwire_readdata() and lwwire_writedata() functions | |
21 is required to handle some possible corner cases that would otherwise | |
22 completely bollix everything up. | |
23 | |
24 Command line options: | |
25 | |
26 drive=N,PATH | |
27 | |
28 Specify that drive #N should reference the file at PATH. Note that the | |
29 file at PATH will be created if it doesn't exist, but only if it is actually | |
30 accessed. N is a decimal number from 0 to 255. If N is prefixed with "C", | |
31 the drive is treated as read-only. | |
32 | |
33 | |
34 By default, no drives are associated with files. Also, it is unspecified | |
35 what happens if multiple protocol instances access the same drive. | |
36 | |
37 */ | |
38 | |
39 // for nanosleep | |
40 #define _POSIX_C_SOURCE 199309L | |
41 | |
42 #include <errno.h> | |
43 #include <fcntl.h> | |
44 #include <stdio.h> | |
45 #include <stdlib.h> | |
46 #include <string.h> | |
47 #include <sys/select.h> | |
48 #include <time.h> | |
49 #include <unistd.h> | |
50 | |
51 #define LWERR_NONE 0 | |
52 #define LWERR_CHECKSUM 0xF3 | |
53 #define LWERR_READ 0xF4 | |
54 #define LWERR_WRITE 0xF5 | |
55 #define LWERR_NOTREADY 0xF6 | |
56 | |
57 struct lwwire_driveinfo | |
58 { | |
59 char *path; | |
60 FILE *fp; | |
61 int isconst; | |
62 }; | |
63 | |
64 struct lwwire_driveinfo drivedata[256]; | |
65 | |
66 void lwwire_protoerror(void); | |
67 int lwwire_readdata(void *, int, int); | |
68 int lwwire_writedata(void *, int); | |
69 void lwwire_write(void *, int); | |
70 int lwwire_read(void *, int); | |
71 int lwwire_read2(void *, int, int); | |
72 void lwwire_reset(void); | |
73 int lwwire_fetch_sector(int dn, int lsn, void *); | |
74 int lwwire_save_sector(int dn, int lsn, void *); | |
75 int nonblock(int); | |
76 | |
77 void lwwire_proto_read(void); | |
78 void lwwire_proto_write(void); | |
79 void lwwire_proto_readex(void); | |
80 void lwwire_proto_requestextension(void); | |
81 void lwwire_proto_disableextension(void); | |
82 void lwwire_proto_extensionop(void); | |
83 | |
84 int main(int argc, char **argv) | |
85 { | |
86 unsigned char buf[32]; | |
87 time_t curtime; | |
88 struct tm *tmval; | |
89 int rv; | |
90 int i; | |
91 | |
92 // make stdin and stdout non-blocking | |
93 if (nonblock(0) < 0) | |
94 { | |
95 fprintf(stderr, "Cannot make stdin non-blocking: %s\n", strerror(errno)); | |
96 exit(1); | |
97 } | |
98 if (nonblock(1) < 0) | |
99 { | |
100 fprintf(stderr, "Cannot make stdout non-blocking: %s\n", strerror(errno)); | |
101 exit(1); | |
102 } | |
103 | |
104 memset(&drivedata, 0, sizeof(drivedata)); | |
105 | |
106 for (i = 1; i < argc; i++) | |
107 { | |
108 if (strncmp("drive=", argv[i], 6) == 0) | |
109 { | |
110 int dn=0; | |
111 int isconst = 0; | |
112 char *ptr; | |
113 ptr = argv[i] + 6; | |
114 if (*ptr == 'C') | |
115 { | |
116 isconst = 1; | |
117 ptr++; | |
118 } | |
119 while (*ptr >= '0' && *ptr <= '9') | |
120 { | |
121 dn = dn * 10 + (*ptr - '0'); | |
122 ptr++; | |
123 } | |
124 if (*ptr != ',' || dn > 255) | |
125 { | |
126 fprintf(stderr, "Ignoring invalid drive specification: %s\n", argv[i]); | |
127 continue; | |
128 } | |
129 ptr++; | |
130 drivedata[dn].path = ptr; | |
131 drivedata[dn].isconst = isconst; | |
132 } | |
133 } | |
134 | |
135 fprintf(stderr, "Running with the following disk images:\n"); | |
136 for (i = 0; i < 256; i++) | |
137 { | |
138 if (drivedata[i].path) | |
139 { | |
140 fprintf(stderr, "[%d%s] %s\n", i, drivedata[i].isconst ? "C" : "", drivedata[i].path); | |
141 } | |
142 } | |
143 | |
144 // main loop reading operations and dispatching | |
145 for (;;) | |
146 { | |
147 rv = lwwire_readdata(buf, 1, 0); | |
148 if (rv < 0) | |
149 { | |
150 fprintf(stderr, "Error or timeout reading operation code.\n"); | |
151 lwwire_protoerror(); | |
152 continue; | |
153 } | |
154 if (rv == 0) | |
155 { | |
156 fprintf(stderr, "EOF on comm channel. Exiting.\n"); | |
157 exit(0); | |
158 } | |
159 fprintf(stderr, "Handling opcode %02X\n", buf[0]); | |
160 | |
161 // we have an opcode here | |
162 switch (buf[0]) | |
163 { | |
164 case 0x00: // NOOP | |
165 break; | |
166 | |
167 case 0x23: // TIME | |
168 curtime = time(NULL); | |
169 tmval = localtime(&curtime); | |
170 buf[0] = tmval -> tm_year; | |
171 buf[1] = tmval -> tm_mon; | |
172 buf[2] = tmval -> tm_mday; | |
173 buf[3] = tmval -> tm_hour; | |
174 buf[4] = tmval -> tm_min; | |
175 buf[5] = tmval -> tm_sec; | |
176 buf[6] = tmval -> tm_wday; | |
177 lwwire_write(buf, 7); | |
178 break; | |
179 | |
180 case 0x46: // PRINTFLUSH | |
181 // no printer is supported by this implemention so NO-OP | |
182 break; | |
183 | |
184 case 0x47: // GETSTAT (useless dw3 operation) | |
185 case 0x53: // SETSTAT (useless dw3 operation) | |
186 // burn two bytes from the client and do nothing | |
187 lwwire_read(buf, 2); | |
188 break; | |
189 | |
190 case 0x49: // INIT (old style INIT call) | |
191 case 0x54: // TERM (old DW3 op treated same as INIT) | |
192 case 0xF8: // RESET3 (junk on the line during reset) | |
193 case 0xFE: // RESET1 (junk on the line during reset) | |
194 case 0xFF: // RESET2 (junk on the line during reset) | |
195 lwwire_reset(); | |
196 break; | |
197 | |
198 case 0x50: // PRINT | |
199 // burn a byte because we don't support any printers | |
200 lwwire_read(buf, 1); | |
201 break; | |
202 | |
203 case 0x52: // READ | |
204 case 0x72: // REREAD (same semantics as READ) | |
205 fprintf(stderr, "DWPROTO: read()\n"); | |
206 lwwire_proto_read(); | |
207 break; | |
208 | |
209 case 0x57: // WRITE | |
210 case 0x77: // REWRITE (same semantics as WRITE) | |
211 fprintf(stderr, "DWPROTO: write()\n"); | |
212 lwwire_proto_write(); | |
213 break; | |
214 | |
215 case 0x5A: // DWINIT (new style init) | |
216 lwwire_reset(); | |
217 if (lwwire_read(buf, 1) < 0) | |
218 break; | |
219 fprintf(stderr, "DWINIT: client drive code %02X\n", buf[0]); | |
220 // tell the client we speak lwwire protocol | |
221 buf[0] = 0x80; | |
222 lwwire_write(buf, 1); | |
223 break; | |
224 | |
225 case 0xD2: // READEX (improved reading operation) | |
226 case 0xF2: // REREADEX (same semantics as READEX) | |
227 fprintf(stderr, "DWPROTO: readex()\n"); | |
228 lwwire_proto_readex(); | |
229 break; | |
230 | |
231 case 0xF0: // REQUESTEXTENSION | |
232 lwwire_proto_requestextension(); | |
233 break; | |
234 | |
235 case 0xF1: // DISABLEEXTENSION | |
236 lwwire_proto_disableextension(); | |
237 break; | |
238 | |
239 case 0xF3: // EXTENSIONOP | |
240 lwwire_proto_extensionop(); | |
241 break; | |
242 | |
243 default: | |
244 fprintf(stderr, "Unrecognized operation code %02X. Doing error state.\n", buf[0]); | |
245 lwwire_protoerror(); | |
246 break; | |
247 } | |
248 } | |
249 } | |
250 | |
251 // protocol handling functions | |
252 void lwwire_proto_read(void) | |
253 { | |
254 unsigned char buf[259]; | |
255 int ec; | |
256 int lsn; | |
257 int i; | |
258 | |
259 if (lwwire_read(buf, 4) < 0) | |
260 return; | |
261 | |
262 lsn = (buf[1] << 16) | (buf[2] << 8) | buf[3]; | |
263 | |
264 ec = lwwire_fetch_sector(buf[0], lsn, buf + 1); | |
265 buf[0] = ec; | |
266 lwwire_write(buf, 1); | |
267 if (ec) | |
268 return; | |
269 // all this futzing around here is probably a long enough | |
270 // delay but testing on real hardware is needed here | |
271 ec = 0; | |
272 for (i = 1; i < 257; i++) | |
273 ec += buf[i]; | |
274 buf[257] = (ec >> 8) & 0xff; | |
275 buf[258] = ec & 0xff; | |
276 lwwire_write(buf + 1, 258); | |
277 } | |
278 | |
279 void lwwire_proto_write(void) | |
280 { | |
281 unsigned char buf[262]; | |
282 int lsn; | |
283 int ec; | |
284 int i; | |
285 if (lwwire_read(buf, 262) < 0) | |
286 return; | |
287 | |
288 lsn = (buf[1] << 16) | (buf[2] << 8) | buf[3]; | |
289 for (ec = 0, i = 4; i < 260; i++) | |
290 ec += buf[i]; | |
291 if (ec != ((buf[260] << 8) | buf[261])) | |
292 { | |
293 buf[0] = LWERR_CHECKSUM; | |
294 } | |
295 else | |
296 { | |
297 ec = lwwire_save_sector(buf[0], lsn, buf + 4); | |
298 buf[0] = ec; | |
299 } | |
300 lwwire_write(buf, 1); | |
301 } | |
302 | |
303 void lwwire_proto_readex(void) | |
304 { | |
305 unsigned char buf[256]; | |
306 int lsn; | |
307 int ec; | |
308 int i; | |
309 int csum; | |
310 if (lwwire_read(buf, 4) < 0) | |
311 return; | |
312 lsn = (buf[1] << 16) | (buf[2] << 8) | buf[3]; | |
313 ec = lwwire_fetch_sector(buf[0], lsn, buf); | |
314 if (ec) | |
315 memset(buf, 0, 256); | |
316 for (i = 0, csum = 0; i < 256; i++) | |
317 csum += buf[i]; | |
318 lwwire_write(buf, 256); | |
319 if ((i = lwwire_read2(buf, 2, 5)) < 0) | |
320 { | |
321 fprintf(stderr, "Error reading protocol bytes: %d, %s\n", i, strerror(errno)); | |
322 return; | |
323 } | |
324 i = (buf[0] << 8) | buf[1]; | |
325 if (i != csum) | |
326 ec = LWERR_CHECKSUM; | |
327 buf[0] = ec; | |
328 lwwire_write(buf, 1); | |
329 } | |
330 | |
331 void lwwire_proto_requestextension(void) | |
332 { | |
333 unsigned char buf[1]; | |
334 | |
335 if (lwwire_read(buf, 1) < 0) | |
336 return; | |
337 // NAK the request | |
338 buf[1] = 0x55; | |
339 lwwire_write(buf, 1); | |
340 } | |
341 | |
342 void lwwire_proto_disableextension(void) | |
343 { | |
344 unsigned char buf[1]; | |
345 | |
346 if (lwwire_read(buf, 1) < 0) | |
347 return; | |
348 // ACK disabling any unsupported extensions | |
349 buf[1] = 0x42; | |
350 lwwire_write(buf, 1); | |
351 } | |
352 | |
353 void lwwire_proto_extensionop(void) | |
354 { | |
355 unsigned char buf[1]; | |
356 if (lwwire_read(buf, 1) < 0) | |
357 return; | |
358 // we don't currently support any extensions so treat as unknown | |
359 lwwire_protoerror(); | |
360 } | |
361 | |
362 // Various infrastructure things follow here. | |
363 int nonblock(int fd) | |
364 { | |
365 int flags; | |
366 | |
367 flags = fcntl(fd, F_GETFL); | |
368 if (flags < 0) | |
369 return -1; | |
370 flags |= O_NONBLOCK; | |
371 return fcntl(fd, F_SETFL, flags); | |
372 } | |
373 | |
374 /* | |
375 Read len bytes from the input. If no bytes are available after | |
376 10 ms, return error. | |
377 | |
378 This *may* allow a timeout longer than 10ms. However, it will | |
379 eventually time out. In the worse case, it is more permissive | |
380 than the specification. It will not time out before 10ms elapses. | |
381 | |
382 If "itimeout" is 0, then it will wait forever for the first | |
383 byte. Otherwise, it will time out even on the first one. | |
384 | |
385 */ | |
386 int lwwire_readdata(void *buf, int len, int itimeout) | |
387 { | |
388 int toread = len; | |
389 int rv; | |
390 fd_set fdset; | |
391 struct timeval timeout; | |
392 | |
393 if (itimeout == 0) | |
394 { | |
395 for (;;) | |
396 { | |
397 // now wait for the descriptor to be readable | |
398 FD_ZERO(&fdset); | |
399 FD_SET(0, &fdset); | |
400 | |
401 rv = select(1, &fdset, NULL, NULL, NULL); | |
402 if (rv < 0) | |
403 { | |
404 // this is a last ditch effort to not break completely | |
405 // in the face of a signal; it should occur only rarely | |
406 // and it is not clear what the correct behaviour should | |
407 // be. | |
408 if (errno == EINTR) | |
409 continue; | |
410 return -1; | |
411 } | |
412 // if we actually have something to read, move on | |
413 if (rv > 0) | |
414 break; | |
415 } | |
416 } | |
417 while (toread > 0) | |
418 { | |
419 rv = read(0, buf, toread); | |
420 if (rv == toread) | |
421 break; | |
422 if (rv == 0) | |
423 { | |
424 // flag EOF so the caller knows to bail | |
425 return 0; | |
426 } | |
427 if (rv < 0) | |
428 { | |
429 if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) | |
430 { | |
431 return -1; | |
432 } | |
433 rv = 0; | |
434 } | |
435 // now rv is the number of bytes read | |
436 buf += rv; | |
437 toread -= rv; | |
438 | |
439 // now wait for the descriptor to be readable | |
440 FD_ZERO(&fdset); | |
441 FD_SET(0, &fdset); | |
442 timeout.tv_sec = 0; | |
443 timeout.tv_usec = 10000 * itimeout; | |
444 | |
445 rv = select(1, &fdset, NULL, NULL, &timeout); | |
446 if (rv < 0) | |
447 { | |
448 // this is a last ditch effort to not break completely | |
449 // in the face of a signal; it should occur only rarely | |
450 // and it is not clear what the correct behaviour should | |
451 // be. | |
452 if (errno == EINTR) | |
453 continue; | |
454 return -1; | |
455 } | |
456 // timeout condition | |
457 if (rv == 0) | |
458 { | |
459 errno = ETIMEDOUT; | |
460 return -1; | |
461 } | |
462 // anything else here means we have more bytes to read | |
463 } | |
464 fprintf(stderr, "Protocol bytes read (%d):", len); | |
465 for (rv = 0; rv < len; rv++) | |
466 fprintf(stderr, " %02X ", ((char *)(buf))[rv] & 0xff); | |
467 fprintf(stderr, "\n"); | |
468 return len; | |
469 } | |
470 | |
471 /* | |
472 Write data to the output. This will time out after 10 seconds. The timeout | |
473 is only there in case the underlying communication channel goes out to lunch. | |
474 | |
475 It returns -1 on error or len on success. | |
476 | |
477 The timeout requires the file descriptor to be non-blocking. | |
478 | |
479 */ | |
480 int lwwire_writedata(void *buf, int len) | |
481 { | |
482 int towrite = len; | |
483 int rv; | |
484 fd_set fdset; | |
485 struct timeval timeout; | |
486 | |
487 while (towrite > 0) | |
488 { | |
489 rv = write(0, buf, towrite); | |
490 if (rv == towrite) | |
491 break; | |
492 if (rv < 0) | |
493 { | |
494 if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) | |
495 { | |
496 return -1; | |
497 } | |
498 rv = 0; | |
499 } | |
500 // now rv is the number of bytes read | |
501 buf += rv; | |
502 towrite -= rv; | |
503 | |
504 // now wait for the descriptor to be writable | |
505 FD_ZERO(&fdset); | |
506 FD_SET(1, &fdset); | |
507 timeout.tv_sec = 10; | |
508 timeout.tv_usec = 0; | |
509 | |
510 rv = select(2, NULL, &fdset, NULL, &timeout); | |
511 if (rv < 0) | |
512 { | |
513 // this is a last ditch effort to not break completely | |
514 // in the face of a signal; it should occur only rarely | |
515 // and it is not clear what the correct behaviour should | |
516 // be. | |
517 if (errno == EINTR) | |
518 continue; | |
519 return -1; | |
520 } | |
521 // timeout condition | |
522 if (rv == 0) | |
523 { | |
524 errno = ETIMEDOUT; | |
525 return -1; | |
526 } | |
527 // anything else here means we have more bytes to write | |
528 } | |
529 fprintf(stderr, "Protocol bytes written (%d):", len); | |
530 for (rv = 0; rv < len; rv++) | |
531 fprintf(stderr, " %02X ", ((char *)(buf))[rv] & 0xff); | |
532 fprintf(stderr, "\n"); | |
533 return len; | |
534 } | |
535 | |
536 // like lwwire_writedata() except it bails the program on error. | |
537 void lwwire_write(void *buf, int len) | |
538 { | |
539 if (lwwire_writedata(buf, len) < 0) | |
540 { | |
541 fprintf(stderr, "Error writing %d bytes to client: %s\n", len, strerror(errno)); | |
542 exit(1); | |
543 } | |
544 } | |
545 | |
546 // like lwwire_readdata() except it bails on EOF and bounces to | |
547 // error state on errors, and always does the timeout for initial | |
548 // bytes. It returns -1 on a timeout. It does not return on EOF. | |
549 // It does not return on random errors. | |
550 int lwwire_read2(void *buf, int len, int toscale) | |
551 { | |
552 int rv; | |
553 rv = lwwire_readdata(buf, len, toscale); | |
554 if (rv < 0) | |
555 { | |
556 if (errno == ETIMEDOUT) | |
557 { | |
558 lwwire_protoerror(); | |
559 return -1; | |
560 } | |
561 fprintf(stderr, "Error reading %d bytes from client: %s\n", len, strerror(errno)); | |
562 exit(1); | |
563 } | |
564 if (rv == 0) | |
565 { | |
566 fprintf(stderr, "EOF reading %d bytes from client.", len); | |
567 exit(0); | |
568 } | |
569 return 0; | |
570 } | |
571 | |
572 int lwwire_read(void *buf, int len) | |
573 { | |
574 return lwwire_read2(buf, len, 1); | |
575 } | |
576 | |
577 /* | |
578 Handle a protocol error by maintaining radio silence for | |
579 at least 1100 ms. The pause must be *at least* 1100ms so | |
580 it's no problem if the messing about takes longer. Do a | |
581 state reset after the error. | |
582 */ | |
583 void lwwire_protoerror(void) | |
584 { | |
585 struct timespec sltime; | |
586 struct timespec rtime; | |
587 | |
588 sltime.tv_sec = 1; | |
589 sltime.tv_nsec = 100000000; | |
590 | |
591 while (nanosleep(&sltime, &rtime) < 0) | |
592 { | |
593 // anything other than EINTR indicates something seriously messed up | |
594 if (errno != EINTR) | |
595 break; | |
596 sltime = rtime; | |
597 } | |
598 lwwire_reset(); | |
599 } | |
600 | |
601 /* fetch a file pointer for the specified drive number */ | |
602 FILE *lwwire_fetch_drive_fp(int dn) | |
603 { | |
604 if (drivedata[dn].path == NULL) | |
605 return NULL; | |
606 if (drivedata[dn].fp) | |
607 { | |
608 if (ferror(drivedata[dn].fp)) | |
609 fclose(drivedata[dn].fp); | |
610 else | |
611 return drivedata[dn].fp; | |
612 } | |
613 | |
614 drivedata[dn].fp = fopen(drivedata[dn].path, "r+"); | |
615 if (!drivedata[dn].fp) | |
616 { | |
617 if (errno == ENOENT && !drivedata[dn].isconst) | |
618 { | |
619 drivedata[dn].fp = fopen(drivedata[dn].path, "w+"); | |
620 } | |
621 } | |
622 return drivedata[dn].fp; | |
623 } | |
624 | |
625 /* read a sector from a disk image */ | |
626 int lwwire_fetch_sector(int dn, int lsn, void *buf) | |
627 { | |
628 FILE *fp; | |
629 int rc; | |
630 | |
631 fp = lwwire_fetch_drive_fp(dn); | |
632 if (!fp) | |
633 return LWERR_NOTREADY; | |
634 | |
635 if (fseek(fp, lsn * 256, SEEK_SET) < 0) | |
636 return LWERR_READ; | |
637 rc = fread(buf, 1, 256, fp); | |
638 if (rc < 256) | |
639 { | |
640 memset(buf + rc, 0, 256 - rc); | |
641 } | |
642 return 0; | |
643 } | |
644 | |
645 int lwwire_save_sector(int dn, int lsn, void *buf) | |
646 { | |
647 FILE *fp; | |
648 int rc; | |
649 | |
650 fp = lwwire_fetch_drive_fp(dn); | |
651 if (!fp) | |
652 return LWERR_NOTREADY; | |
653 if (drivedata[dn].isconst) | |
654 return LWERR_WRITE; | |
655 if (fseek(fp, lsn * 256, SEEK_SET) < 0) | |
656 return LWERR_WRITE; | |
657 rc = fwrite(buf, 1, 256, fp); | |
658 if (rc < 256) | |
659 return LWERR_WRITE; | |
660 return 0; | |
661 } | |
662 | |
663 | |
664 /* | |
665 Reset the protocol state to "base protocol" mode. | |
666 */ | |
667 void lwwire_reset(void) | |
668 { | |
669 } |