|
|
1.1 root 1: /*
2: * New Datakit protocol
3: */
4:
5: #include "../h/param.h"
6: #include "../h/stream.h"
7: #include "../h/conf.h"
8: #include "../h/ioctl.h"
9: #include "../h/dkstat.h"
10: #include "../h/ttyld.h"
11: #include "dkp.h"
12:
13: #if NDKP>0
14: #ifdef CAREFUL
15: #define TRC(c) *dkptrp++ = c; if (dkptrp>=&dkptrb[1024]) dkptrp=dkptrb
16: char dkptrb[1024]; char *dkptrp = dkptrb;
17: #else
18: #define TRC(c)
19: #endif
20:
21: struct dkp {
22: struct queue *rdq; /* associated read queue */
23: struct block *inp; /* msg being collected */
24: struct block *inpe; /* end of msg */
25: short state; /* flags */
26: short trx; /* # bytes in trailer being collected */
27: short indata; /* # bytes in message being collected */
28: u_char iseq; /* last good input sequence number */
29: u_char lastecho; /* last echo/rej sent */
30: char WS; /* first non-consumed message */
31: char WACK; /* first non-acknowledged message */
32: char WNX; /* next message to be sent */
33: u_char XW; /* size of xmit window */
34: u_char timer; /* timeout for xmit */
35: u_char outcnt; /* count output chars for char mode */
36: u_char trbuf[3]; /* trailer being collected */
37: struct block *xb[8]; /* the xmit window buffer */
38: };
39:
40: extern struct dkstat dkstat;
41:
42: /*
43: * Protocol control bytes
44: */
45: #define SEQ 0010 /* sequence number, ends trailers */
46: #undef ECHO
47: #define ECHO 0020 /* echos, data given to next queue */
48: #define REJ 0030 /* rejections, transmission error */
49: #define ACK 0040 /* acknowledgments */
50: #define BOT 0050 /* beginning of trailer */
51: #define BOTM 0051 /* beginning of trailer, more data follows */
52: #define BOTS 0052 /* seq update algorithm on this trailer */
53: #define SOU 0053 /* start of unsequenced trailer */
54: #define EOU 0054 /* end of unsequenced trailer */
55: #define ENQ 0055 /* xmitter requests flow/error status */
56: #define CHECK 0056 /* xmitter requests error status */
57: #define INITREQ 0057 /* request initialization */
58: #define INIT0 0060 /* disable trailer processing */
59: #define INIT1 0061 /* enable trailer procesing */
60: #define AINIT 0062 /* response to INIT0/INIT1 */
61: #undef DELAY
62: #define DELAY 0100 /* real-time printing delay */
63: #define BREAK 0110 /* Send/receive break (new style) */
64:
65: #define OPEN 01
66: #define LCLOSE 02
67: #define RCLOSE 04
68: #define XCHARMODE 010
69: #define OPENING 020
70: #define RJING 040
71: #define STOPPED 0100
72: #define RCHARMODE 0200
73:
74: #define DKPPRI 28
75: #define DKPTIME 2
76:
77: struct dkp dkp[NDKP];
78:
79: int dkpiput(), dkpisrv(), dkpoput(), dkposrv(), dkpopen(), cdkpopen();
80: int dkpclose();
81: static struct qinit cdkprinit = { dkpiput,dkpisrv,cdkpopen,dkpclose,512,64 };
82: static struct qinit dkprinit = { dkpiput,dkpisrv,dkpopen,dkpclose,512,64 };
83: static struct qinit dkpwinit = { dkpoput,dkposrv,dkpopen,dkpclose,128,65 };
84: struct streamtab dkpinfo = { &dkprinit, &dkpwinit };
85: struct streamtab cdkpinfo = { &cdkprinit, &dkpwinit };
86:
87: dkpopen(q)
88: {
89: return(rdkpopen(q, !XCHARMODE));
90: }
91:
92: cdkpopen(q)
93: {
94: return(rdkpopen(q, XCHARMODE));
95: }
96:
97: rdkpopen(q, mode)
98: register struct queue *q;
99: {
100: register struct dkp *dkpp;
101: static timer = 0;
102: int dkptimer();
103:
104: if (timer == 0) {
105: timer = 1;
106: timeout(dkptimer, (caddr_t)NULL, 60);
107: }
108: if (q->ptr)
109: dkpp = (struct dkp *)q->ptr;
110: else {
111: for (dkpp = dkp; dkpp->state!=0; dkpp++)
112: if (dkpp >= &dkp[NDKP])
113: return(0);
114: dkpp->rdq = q;
115: q->ptr = (caddr_t)dkpp;
116: WR(q)->ptr = (caddr_t)dkpp;
117: WR(q)->flag |= QNOENB;
118: putctl(q->next, M_FLUSH);
119: dkpp->timer = DKPTIME;
120: dkpp->trx = 0;
121: dkpp->iseq = 0;
122: dkpp->lastecho = ECHO+0;
123: dkpp->WS = 1;
124: dkpp->WACK = 1;
125: dkpp->WNX = 1;
126: dkpp->XW = 3;
127: if (mode!=XCHARMODE) {
128: WR(q)->flag |= QDELIM;
129: dkpp->state = OPENING | RCHARMODE;
130: putctl1(WR(q)->next, M_CTL, INIT1);
131: } else {
132: dkpp->XW = 1;
133: dkpp->state = RCHARMODE | XCHARMODE | OPEN;
134: putctl1(WR(q)->next, M_CTL, INIT0);
135: }
136: }
137: return(1);
138: }
139:
140: /*
141: * Shut it down.
142: * The problem is to dispose of unacked stuff in the window.
143: * -- no real solution; the receiver might hang on for hours.
144: * Give it 15 seconds.
145: */
146: dkpclose(q)
147: register struct queue *q;
148: {
149: register struct dkp *dkpp;
150: register s = spl5();
151: register i;
152: register struct block *bp;
153:
154: dkpp = (struct dkp *)q->ptr;
155: dkpp->state |= LCLOSE;
156: flushq(q, 1);
157: for (i=0; dkpp->WACK < dkpp->WNX && i<15; i++)
158: tsleep((caddr_t)dkpp, DKPPRI, 1);
159: if (dkpp->WACK < dkpp->WNX)
160: dkprack(dkpp, ACK+((dkpp->WNX-1) & 07));
161: dkpinflush(dkpp);
162: splx(s);
163: dkpp->state = 0;
164: flushq(WR(q), 1);
165: }
166:
167:
168: /*
169: * Process a bunch of input
170: * -- for now, ignore strange control bytes
171: */
172: dkpisrv(q)
173: register struct queue *q;
174: {
175: register struct dkp *dkpp = (struct dkp *)q->ptr;
176: register struct block *bp;
177: register c;
178:
179: while (bp = getq(q)) {
180: if (bp->type == M_CTL) {
181: c = *bp->rptr & 0370;
182: if (c==REJ || c==ECHO) {
183: dkpp->lastecho = *bp->rptr;
184: (*WR(q)->next->qinfo->putp)(WR(q)->next, bp);
185: } else
186: freeb(bp);
187: continue;
188: }
189: if ((q->next->flag&QFULL)==0 || bp->type>=QPCTL
190: || dkpp->state&RCLOSE) {
191: TRC('G'); TRC(*bp->rptr);
192: (*q->next->qinfo->putp)(q->next, bp);
193: } else {
194: putbq(q, bp);
195: return;
196: }
197: }
198: }
199:
200: /*
201: * Packet arrives.
202: */
203: dkpiput(q, bp)
204: struct queue *q;
205: register struct block *bp;
206: {
207: register struct dkp *dkpp;
208: register i;
209: register struct block *nbp;
210:
211: if ((dkpp = (struct dkp *)q->ptr)==NULL) {
212: freeb(bp);
213: return;
214: }
215: switch (bp->type) {
216:
217: moredata:
218: bp->rptr++;
219: bp->type = M_DATA;
220: case M_DATA:
221: if (bp->rptr >= bp->wptr||q->flag&QFULL||dkpp->state&LCLOSE) {
222: freeb(bp);
223: return;
224: }
225: if (dkpp->state & RCHARMODE) {
226: putq(q, bp);
227: return;
228: }
229: switch (dkpp->trx) {
230:
231: case 1:
232: case 2:
233: dkpp->trbuf[dkpp->trx++] = *bp->rptr;
234: goto moredata;
235:
236: default:
237: dkpp->trx = 0;
238: case 0:
239: break;
240: }
241: bp->next = NULL;
242: if (dkpp->indata > 256) { /* protect against garbage */
243: freeb(bp);
244: return;
245: }
246: if (dkpp->inp) {
247: dkpp->inpe->next = bp;
248: dkpp->inpe = bp;
249: } else {
250: dkpp->inp = bp;
251: dkpp->inpe = bp;
252: }
253: dkpp->indata += bp->wptr - bp->rptr;
254: return;
255:
256: case M_CTL:
257: switch (*bp->rptr) {
258:
259: case ENQ:
260: putctl1(WR(q)->next, M_CTL, dkpp->lastecho);
261: case CHECK:
262: putctl1(WR(q)->next, M_CTL, ACK+dkpp->iseq);
263: dkpinflush(dkpp);
264: goto moredata;
265:
266: case AINIT:
267: dkpp->state &= ~OPENING;
268: dkpp->state |= OPEN;
269: qenable(WR(q));
270: dkpinflush(dkpp);
271: goto moredata;
272:
273: case INIT0:
274: case INIT1:
275: putctl1(WR(q)->next, M_CTL, AINIT);
276: if (*bp->rptr==INIT0 && (dkpp->state&RCHARMODE)==0) {
277: dkpp->state |= RCHARMODE;
278: dkpp->XW = 1;
279: q->flag &= ~QDELIM;
280: } else if (*bp->rptr==INIT1 && (dkpp->state&RCHARMODE)){
281: dkpp->state &= ~RCHARMODE;
282: dkpp->XW = 3;
283: q->flag |= QDELIM;
284: }
285: dkpinflush(dkpp);
286: dkpp->iseq = 0;
287: wakeup(dkpp);
288: goto moredata;
289:
290: case INITREQ:
291: if (dkpp->state&XCHARMODE)
292: putctl1(WR(q)->next, M_CTL, INIT0);
293: else {
294: if (dkpp->WS < dkpp->WNX)
295: dkprack(dkpp, ECHO+((dkpp->WNX-1)&07));
296: dkpp->WS = 1;
297: dkpp->WACK = 1;
298: dkpp->WNX = 1;
299: putctl1(WR(q)->next, M_CTL, INIT1);
300: }
301: dkpinflush(dkpp);
302: goto moredata;
303:
304: case BREAK:
305: qpctl(q, M_BREAK);
306: dkpp->indata++;
307: goto moredata;
308:
309: case BOT:
310: case BOTS:
311: case BOTM:
312: dkpp->trx = 1;
313: dkpp->trbuf[0] = *bp->rptr;
314: goto moredata;
315:
316: case REJ+0: case REJ+1: case REJ+2: case REJ+3:
317: case REJ+4: case REJ+5: case REJ+6: case REJ+7:
318: if (dkpp->state&RCHARMODE)
319: goto moredata;
320: TRC('r');
321: if (((*bp->rptr+1)&07) == (dkpp->WACK&07)
322: && (dkpp->state&RJING) == 0) {
323: dkstat.dkprxmit++;
324: for (i=dkpp->WACK; i<dkpp->WNX; i++) {
325: TRC('Z');
326: TRC('0' + (i&07));
327: dkpp->state |= RJING;
328: dkpxmit(WR(q), dkpp->xb[i&07], i);
329: }
330: }
331: goto moredata;
332:
333: case ACK+0: case ACK+1: case ACK+2: case ACK+3:
334: case ACK+4: case ACK+5: case ACK+6: case ACK+7:
335: case ECHO+0: case ECHO+1: case ECHO+2: case ECHO+3:
336: case ECHO+4: case ECHO+5: case ECHO+6: case ECHO+7:
337: dkprack(dkpp, *bp->rptr);
338: goto moredata;
339:
340: case SEQ+0: case SEQ+1: case SEQ+2: case SEQ+3:
341: case SEQ+4: case SEQ+5: case SEQ+6: case SEQ+7:
342: i = *bp->rptr & 07;
343: if (dkpp->state & RCHARMODE) {
344: TRC('e');
345: qpctl1(q, M_CTL, ECHO+i);
346: goto moredata;
347: }
348: if (dkpp->trx !=3
349: || dkpp->indata != dkpp->trbuf[1] + (dkpp->trbuf[2]<<8)
350: || i != ((dkpp->iseq+1)&07)) { /* reject? */
351: if (dkpp->trx != 3)
352: dkstat.dkprjtrs++;
353: else if (i != ((dkpp->iseq+1)&07))
354: dkstat.dkprjseq++;
355: else
356: dkstat.dkprjpks++;
357: dkpinflush(dkpp);
358: if (dkpp->trbuf[0]==BOTS)
359: dkpp->iseq = i;
360: TRC('R'); TRC('0'+dkpp->iseq);
361: TRC(dkpp->trx!=3?'t':(i!=(dkpp->iseq+1)&07?'s':'c'));
362: qpctl1(q, M_CTL, REJ+dkpp->iseq);
363: goto moredata;
364: }
365: /* accept */
366: while (nbp = dkpp->inp) {
367: dkpp->inp = nbp->next;
368: putq(q, nbp);
369: }
370: TRC('A'); TRC('0'+i);
371: dkpp->inpe = NULL;
372: dkpp->trx = 0;
373: dkpp->indata = 0;
374: dkpp->iseq = i;
375: qpctl1(q, M_CTL, ECHO+i);
376: if (dkpp->trbuf[0] != BOTM)
377: qpctl(q, M_DELIM);
378: goto moredata;
379:
380: default:
381: if (*bp->rptr < 0200) /* non-supervisory */
382: dkpp->indata++;
383: qpctl1(q, M_CTL, *bp->rptr);
384: goto moredata;
385: }
386:
387: case M_HANGUP:
388: dkpp->state |= RCLOSE;
389: flushq(WR(q), 1);
390: dkprack(dkpp, ECHO+((dkpp->WNX-1) & 07));
391: putq(q, bp);
392: return;
393:
394: case M_IOCACK:
395: case M_IOCNAK:
396: case M_CLOSE:
397: (*q->next->qinfo->putp)(q->next, bp);
398: return;
399:
400: default:
401: freeb(bp);
402: return;
403: }
404: }
405:
406: /*
407: * --- Output processor
408: */
409:
410: /*
411: * accept data from writer
412: * -- handle most non-data messages
413: */
414: dkpoput(q, bp)
415: register struct queue *q;
416: register struct block *bp;
417: {
418: register struct dkp *dkpp = (struct dkp *)q->ptr;
419: register union stmsg *sp;
420: register x;
421:
422: if (dkpp->state & RCLOSE) {
423: freeb(bp);
424: return;
425: }
426: switch (bp->type) {
427:
428: case M_STOP:
429: dkpp->state |= STOPPED;
430: freeb(bp);
431: return;
432:
433: case M_START:
434: dkpp->state &= ~STOPPED;
435: freeb(bp);
436: qenable(q);
437: return;
438:
439: case M_FLUSH:
440: flushq(q, 0);
441: freeb(bp);
442: return;
443:
444: case M_IOCTL:
445: sp = (union stmsg *)bp->rptr;
446: switch (sp->ioc0.com) {
447:
448: case TIOCSETP:
449: case TIOCSETN:
450: x = sp->ioc1.sb.sg_ispeed;
451: bp->wptr = bp->rptr;
452: bp->type = M_IOCACK;
453: qreply(q, bp);
454: if (x==0)
455: putctl(OTHERQ(q), M_HANGUP);
456: return;
457:
458: case TIOCGETP:
459: sp->ioc1.sb.sg_ispeed =
460: sp->ioc1.sb.sg_ospeed = B9600;
461: bp->type = M_IOCACK;
462: qreply(q, bp);
463: return;
464:
465: case DIOCSTREAM:
466: RD(q)->flag &= ~QDELIM;
467: bp->wptr = bp->rptr;
468: bp->type = M_IOCACK;
469: qreply(q, bp);
470: return;
471:
472: case DIOCRECORD:
473: if ((dkpp->state&RCHARMODE) == 0) {
474: RD(q)->flag | = QDELIM;
475: bp->wptr = bp->rptr;
476: bp->type = M_IOCACK;
477: } else
478: bp->type = M_IOCNAK;
479: qreply(q, bp);
480: return;
481:
482: case KIOCINIT:
483: if (dkpp->state&XCHARMODE)
484: putctl1(q->next, M_CTL, INIT0);
485: else {
486: if (dkpp->WS < dkpp->WNX)
487: dkprack(dkpp, ECHO+((dkpp->WNX-1)&07));
488: dkpp->WS = 1;
489: dkpp->WACK = 1;
490: dkpp->WNX = 1;
491: putctl1(q->next, M_CTL, INIT1);
492: }
493: bp->wptr = bp->rptr;
494: bp->type = M_IOCACK;
495: qreply(q, bp);
496: return;
497:
498: case KIOCISURP:
499: bp->wptr = bp->rptr;
500: bp->type = M_IOCACK;
501: qreply(q, bp);
502: return;
503:
504: default:
505: (*q->next->qinfo->putp)(q->next, bp);
506: return;
507: }
508:
509: case M_DELAY:
510: x = *bp->rptr;
511: *bp->rptr = DELAY;
512: bp->type = M_CTL;
513: while (x) {
514: (*bp->rptr)++;
515: x >>= 1;
516: }
517: goto putonq;
518:
519: case M_CLOSE:
520: (*q->next->qinfo->putp)(q->next, bp);
521: return;
522:
523: default:
524: freeb(bp);
525: return;
526:
527: case M_BREAK:
528: bp->type = M_CTL;
529: *bp->wptr++ = BREAK;
530: case M_DELIM:
531: case M_DATA:
532: putonq:
533: putq(q, bp);
534: if (dkpp->WNX < dkpp->WS+dkpp->XW)
535: qenable(q);
536: return;
537: }
538: }
539:
540: /*
541: * Out server:
542: * if space in window, process queue
543: */
544: dkposrv(q)
545: register struct queue *q;
546: {
547: register struct dkp *dkpp = (struct dkp *)q->ptr;
548: register struct block *bp, *xbp;
549: int c;
550:
551: if (dkpp->state & (STOPPED|OPENING))
552: return;
553: while (dkpp->WNX < dkpp->WS+dkpp->XW) {
554: if ((bp = getq(q)) == NULL)
555: break;
556: if (dkpp->state & XCHARMODE) {
557: switch (bp->type) {
558:
559: case M_DELIM:
560: freeb(bp);
561: continue;
562:
563: default:
564: dkpp->outcnt += bp->wptr - bp->rptr;
565: (*q->next->qinfo->putp)(q->next, bp);
566: if (dkpp->outcnt >= 64) {
567: putctl1(q->next, M_CTL,
568: SEQ+(dkpp->WNX&07));
569: dkpp->WNX++;
570: dkpp->WACK = dkpp->WNX;
571: dkpp->outcnt = 0;
572: }
573: continue;
574: }
575: }
576: /*
577: * look ahead for delimiters.
578: * Don't transmit a single data block,
579: * to avoid 0-len block next time
580: */
581: if (bp->type==M_DATA && (xbp = q->first) && xbp->type==M_DELIM){
582: xbp = getq(q);
583: if (xbp)
584: freeb(xbp); /* toss delimiter */
585: bp->type = M_DELIM;
586: }
587: if (bp->type==M_DATA && q->first==NULL) {
588: putbq(q, bp);
589: return;
590: }
591: TRC('x'); TRC('0'+dkpp->WS/10); TRC('0'+dkpp->WS%10);
592: TRC('.'); TRC('0'+dkpp->WNX/10); TRC('0'+dkpp->WNX%10);
593: if (dkpp->xb[dkpp->WNX&07]) {
594: freeb(dkpp->xb[dkpp->WNX&07]);
595: printf("dkp losing block");
596: }
597: dkpp->xb[dkpp->WNX & 07] = bp;
598: dkpxmit(q, bp, dkpp->WNX);
599: dkpp->WNX++;
600: }
601: }
602:
603: /*
604: * Send out a message, with trailer.
605: */
606: dkpxmit(q, bp, seqno)
607: struct queue *q;
608: register struct block *bp;
609: {
610: register struct dkp *dkpp = (struct dkp *)q->ptr;
611: register type;
612: register size;
613: register struct block *xbp;
614:
615: if (bp==NULL) {
616: printf("null bp in dkpxmit\n");
617: return;
618: }
619: type = bp->type;
620: size = bp->wptr - bp->rptr;
621: seqno &= 07;
622: /* send ptr to block, if non-empty */
623: if (size) {
624: if ((xbp = allocb(0)) == NULL)
625: return;
626: TRC('X'); TRC('0'+seqno);
627: xbp->rptr = bp->rptr;
628: xbp->wptr = bp->wptr;
629: if (type!=M_DELIM)
630: xbp->type = type;
631: (*q->next->qinfo->putp)(q->next, xbp);
632: }
633: /* send trailer */
634: if ((xbp = allocb(3)) == NULL)
635: return;
636: xbp->type = M_CTL;
637: *xbp->wptr++ = type==M_DATA? BOTM: BOT;
638: *xbp->wptr++ = size;
639: *xbp->wptr++ = size >> 8;
640: (*q->next->qinfo->putp)(q->next, xbp);
641: putctl1(q->next, M_CTL, SEQ + seqno);
642: dkpp->timer = DKPTIME;
643: }
644:
645: /*
646: * Receive an ack of some sort for a transmitted message.
647: * Advance various windows.
648: */
649: dkprack(dkpp, msg)
650: register struct dkp *dkpp;
651: {
652: register struct block **bpp;
653: register seqno, i;
654:
655: seqno = msg & 07;
656: msg &= 0370;
657: /* invariants: 0 <= WS <= WACK <= WNX; seqno maximal < WNX; WS < 8 */
658: if (seqno >= dkpp->WNX)
659: seqno -= 8;
660: else if (seqno+8 < dkpp->WNX)
661: seqno += 8;
662: dkpp->state &= ~RJING;
663: for (i=dkpp->WS; i<=seqno; i++) {
664: bpp = &dkpp->xb[i&07];
665: if (*bpp) {
666: freeb(*bpp);
667: *bpp = NULL;
668: }
669: }
670: if ((int)dkpp->WACK <= seqno)
671: dkpp->WACK = seqno+1;
672: if (msg==ECHO) {
673: TRC('E'); TRC('0'+(seqno&07));
674: if (dkpp->WS <= seqno) {
675: dkpp->timer = DKPTIME; /* push off timeout */
676: dkpp->WS = seqno+1;
677: if (dkpp->WNX<dkpp->WS+dkpp->XW && WR(dkpp->rdq)->count)
678: qenable(WR(dkpp->rdq));
679: }
680: } else {
681: for (i=dkpp->WACK; i<dkpp->WNX; i++) {
682: if (dkpp->xb[i&07]==0)
683: printf("WS %d WACK %d WNX %d i %d seqno %d\n",
684: dkpp->WS, dkpp->WACK, dkpp->WNX, i, seqno);
685: dkpxmit(WR(dkpp->rdq), dkpp->xb[i&07], i);
686: dkstat.dkprxmit++;
687: }
688: }
689: if (dkpp->WS >= 8) {
690: dkpp->WS -= 8;
691: dkpp->WACK -= 8;
692: dkpp->WNX -= 8;
693: }
694: }
695:
696: dkptimer()
697: {
698: register struct dkp *dkpp;
699: register struct queue *q;
700:
701: for (dkpp = dkp; dkpp < &dkp[NDKP]; dkpp++) {
702: if ((dkpp->state&(OPEN|OPENING)) == 0)
703: continue;
704: if (--dkpp->timer>0)
705: continue;
706: q = WR(dkpp->rdq)->next;
707: if (q->flag&QFULL)
708: continue;
709: if (dkpp->state & XCHARMODE) {
710: if (dkpp->WS < dkpp->WNX)
711: putctl1(q, M_CTL, SEQ+((dkpp->WNX-1)&07));
712: dkpp->timer = 10;
713: continue;
714: }
715: if (dkpp->state&OPENING)
716: putctl1(q, M_CTL, INIT1);
717: if (dkpp->WS != dkpp->WNX)
718: putctl1(q, M_CTL, ENQ);
719: dkpp->timer = DKPTIME;
720: }
721: timeout(dkptimer, (caddr_t)NULL, 60);
722: }
723:
724: /*
725: * throw away data in front of the barrier, and clear the trailer buffer
726: */
727: dkpinflush(dkpp)
728: register struct dkp *dkpp;
729: {
730: register struct block *bp;
731:
732: while (bp = dkpp->inp) {
733: dkpp->inp = bp->next;
734: freeb(bp);
735: }
736: dkpp->inpe = NULL;
737: dkpp->trx = 0;
738: dkpp->indata = 0;
739: }
740: #endif
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.