summaryrefslogtreecommitdiff
path: root/appl/cmd/palm/palmsrv.b
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/palm/palmsrv.b')
-rw-r--r--appl/cmd/palm/palmsrv.b901
1 files changed, 901 insertions, 0 deletions
diff --git a/appl/cmd/palm/palmsrv.b b/appl/cmd/palm/palmsrv.b
new file mode 100644
index 00000000..f878be03
--- /dev/null
+++ b/appl/cmd/palm/palmsrv.b
@@ -0,0 +1,901 @@
+implement Palmsrv;
+
+#
+# serve up a Palm using SLP and PADP
+#
+# Copyright © 2003 Vita Nuova Holdings Limited. All rights reserved.
+#
+# forsyth@vitanuova.com
+#
+# TO DO
+# USB and possibly other transports
+# tickle
+
+include "sys.m";
+ sys: Sys;
+
+include "draw.m";
+
+include "timers.m";
+ timers: Timers;
+ Timer, Sec: import timers;
+
+include "palm.m";
+
+include "arg.m";
+
+Palmsrv: module
+{
+ init: fn(nil: ref Draw->Context, nil: list of string);
+};
+
+debug := 0;
+
+usage()
+{
+ sys->fprint(sys->fildes(2), "usage: palm/palmsrv [-d /dev/eia0] [-s 57600]\n");
+ raise "fail:usage";
+}
+
+init(nil: ref Draw->Context, args: list of string)
+{
+ sys = load Sys Sys->PATH;
+ sys->pctl(Sys->NEWPGRP|Sys->FORKFD, nil);
+
+ device, speed: string;
+
+ arg := load Arg Arg->PATH;
+ if(arg == nil)
+ error(sys->sprint("can't load %s: %r", Arg->PATH));
+ arg->init(args);
+ while((c := arg->opt()) != 0)
+ case c {
+ 'D' =>
+ debug++;
+ 'd' =>
+ device = arg->arg();
+ 's' =>
+ speed = arg->arg();
+ * =>
+ usage();
+ }
+ args = arg->argv();
+ arg = nil;
+
+ if(device == nil)
+ device = "/dev/eia0";
+ if(speed == nil)
+ speed = "57600";
+
+ dfd := sys->open(device, Sys->ORDWR);
+ if(dfd == nil)
+ error(sys->sprint("can't open %s: %r", device));
+ cfd := sys->open(device+"ctl", Sys->OWRITE);
+
+ timers = load Timers Timers->PATH;
+ if(timers == nil)
+ error(sys->sprint("can't load %s: %r", Timers->PATH));
+ srvio := sys->file2chan("/chan", "palmsrv");
+ if(srvio == nil)
+ error(sys->sprint("can't create channel /chan/palmsrv: %r"));
+ timers->init(Sec/100);
+ p := Pchan.init(dfd, cfd);
+ spawn server(srvio, p);
+}
+
+error(s: string)
+{
+ sys->fprint(sys->fildes(2), "palmsrv: %s\n", s);
+ raise "fail:error";
+}
+
+Xact: adt
+{
+ fid: int;
+ reply: array of byte;
+ error: string;
+};
+
+server(srv: ref Sys->FileIO, p: ref Pchan)
+{
+ actions: list of ref Xact;
+ nuser := 0;
+ for(;;)alt{
+ (nil, nbytes, fid, rc) := <-srv.read =>
+ if(rc == nil){
+ actions = delact(actions, fid);
+ break;
+ }
+ act := findact(actions, fid);
+ if(act == nil){
+ rc <-= (nil, "no transaction in progress");
+ break;
+ }
+ actions = delact(actions, fid);
+ if(p.shutdown)
+ rc <-= (nil, "link shut down");
+ else if(act.error != nil)
+ rc <-= (nil, act.error);
+ else if(act.reply != nil)
+ rc <-= (act.reply, nil);
+ else
+ rc <-= (nil, "no reply"); # probably shouldn't happen
+
+ (nil, data, fid, wc) := <-srv.write =>
+ actions = delact(actions, fid); # discard result of any previous transaction
+ if(wc == nil){
+ if(--nuser <= 0){
+ nuser = 0;
+ p.stop();
+ }
+ break;
+ }
+ if(len data == 4 && string data == "exit"){
+ p.close();
+ wc <-= (len data, nil);
+ exit;
+ }
+ if(p.shutdown){
+ wc <-= (0, "link shut down"); # must close then reopen
+ break;
+ }
+ if(!p.started){
+ err := p.start();
+ if(err != nil){
+ wc <-= (0, sys->sprint("can't start protocol: %s", err));
+ break;
+ }
+ nuser++;
+ }
+ (result, err) := p.padp_xchg(data, 20*1000);
+ if(err != nil){
+ wc <-= (0, err);
+ break;
+ }
+ actions = ref Xact(fid, result, err) :: actions;
+ wc <-= (len data, nil);
+ }
+}
+
+findact(l: list of ref Xact, fid: int): ref Xact
+{
+ for(; l != nil; l = tl l)
+ if((a := hd l).fid == fid)
+ return a;
+ return nil;
+}
+
+delact(l: list of ref Xact, fid: int): list of ref Xact
+{
+ ol := l;
+ l = nil;
+ for(; ol != nil; ol = tl ol)
+ if((a := hd ol).fid != fid)
+ l = a :: l;
+ return l;
+}
+
+killpid(pid: int)
+{
+ if(pid != 0){
+ fd := sys->open("/prog/"+string pid+"/ctl", sys->OWRITE);
+ if(fd != nil)
+ sys->fprint(fd, "kill");
+ }
+}
+
+#
+# protocol implementation
+# Serial Link Protocol (framing)
+# Connection Management Protocol (wakeup, negotiation)
+# Packet Assembly/Disassembly Protocol (reliable delivery fragmented datagram)
+#
+
+DATALIM: con 1024;
+
+# SLP packet types
+SLP_System, SLP_Unused, SLP_PAD, SLP_Loop: con iota;
+
+# SLP block content, without framing
+Sblock: adt {
+ src: int; # socket ID
+ dst: int; # socket ID
+ proto: int; # packet type
+ xid: int; # transaction ID
+ data: array of byte;
+
+ new: fn(): ref Sblock;
+ print: fn(sb: self ref Sblock, dir: string);
+};
+
+#
+# Palm channel
+#
+Pchan: adt {
+ started: int;
+ shutdown: int;
+
+ protocol: int;
+ lport: byte;
+ rport: byte;
+
+ fd: ref Sys->FD;
+ cfd: ref Sys->FD;
+ baud: int;
+
+ rpid: int;
+ lastid: int;
+ rd: chan of ref Sblock;
+ reply: ref Sblock; # data replacing lost ack
+
+ init: fn(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan;
+ start: fn(p: self ref Pchan): string;
+ stop: fn(p: self ref Pchan);
+ close: fn(p: self ref Pchan): int;
+ slp_read: fn(p: self ref Pchan, nil: int): (ref Sblock, string);
+ slp_write: fn(p: self ref Pchan, xid: int, nil: array of byte): string;
+
+ setbaud: fn(p: self ref Pchan, nil: int);
+
+ padp_read: fn(p: self ref Pchan, xid: int, timeout: int): (array of byte, string);
+ padp_write: fn(p: self ref Pchan, msg: array of byte, xid: int): string;
+ padp_xchg: fn(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string);
+ tickle: fn(p: self ref Pchan);
+
+ connect: fn(p: self ref Pchan): string;
+ accept: fn(p: self ref Pchan, baud: int): string;
+
+ nextseq: fn(p: self ref Pchan): int;
+};
+
+Pchan.init(dfd: ref Sys->FD, cfd: ref Sys->FD): ref Pchan
+{
+ p := ref Pchan;
+ p.fd = dfd;
+ p.cfd = cfd;
+ p.baud = InitBaud;
+ p.protocol = SLP_PAD;
+ p.rport = byte 3;
+ p.lport = byte 3;
+ p.rd = chan of ref Sblock;
+ p.lastid = 0;
+ p.rpid = 0;
+ p.started = 0;
+ p.shutdown = 0;
+ return p;
+}
+
+Pchan.start(p: self ref Pchan): string
+{
+ if(p.started)
+ return nil;
+ p.shutdown = 0;
+ p.baud = InitBaud;
+ p.reply = nil;
+ ctl(p, "f");
+ ctl(p, "d1");
+ ctl(p, "r1");
+ ctl(p, "i8");
+ ctl(p, "q8192");
+ ctl(p, sys->sprint("b%d", InitBaud));
+ pidc := chan of int;
+ spawn slp_recv(p, pidc);
+ p.started = 1;
+ p.rpid = <-pidc;
+ err := p.accept(57600);
+ if(err != nil)
+ p.stop();
+ return err;
+}
+
+ctl(p: ref Pchan, s: string)
+{
+ if(p.cfd != nil)
+ sys->fprint(p.cfd, "%s", s);
+}
+
+Pchan.setbaud(p: self ref Pchan, baud: int)
+{
+ if(p.baud != baud){
+ p.baud = baud;
+ ctl(p, sys->sprint("b%d", baud));
+ sys->sleep(200);
+ }
+}
+
+Pchan.stop(p: self ref Pchan)
+{
+ p.shutdown = 0;
+ if(!p.started)
+ return;
+ killpid(p.rpid);
+ p.rpid = 0;
+ p.reply = nil;
+# ctl(p, "f");
+# ctl(p, "d0");
+# ctl(p, "r0");
+# ctl(p, sys->sprint("b%d", InitBaud));
+ p.started = 0;
+}
+
+Pchan.close(p: self ref Pchan): int
+{
+ if(p.started)
+ p.stop();
+ p.reply = nil;
+ p.cfd = nil;
+ p.fd = nil;
+ timers->shutdown();
+ return 0;
+}
+
+# CMP protocol for connection management
+# See include/Core/System/CMCommon.h, Palm SDK
+# There are two major versions: the original V1, still always used in wakeup messsages;
+# and V2, which is completely different (similar structure to Desklink) and used by newer devices, but the headers
+# are the same length. Start off in V1 announcing version 2.x, then switch to that.
+# My device supports only V1, so I use that.
+
+CMPHDRLEN: con 10; # V1: type[1] flags[1] vermajor[1] verminor[1] mbz[2] baud[4]
+ # V2: type[1] cmd[1] error[2] argc[1] mbz[1] mbz[4]
+
+# CMP V1
+Cmajor: con 1;
+Cminor: con 2;
+
+InitBaud: con 9600;
+
+# type
+Cwake, Cinit, Cabort, Cextended: con 1+iota;
+
+# Cinit flags
+ChangeBaud: con 16r80;
+RcvTimeout1: con 16r40; # tell Palm to set receive timeout to 1 minute (CMP v1.1)
+RcvTimeout2: con 16r20; # tell Palm to set receive timeout to 2 minutes (v1.1)
+
+# Cinit and Cwake flag
+LongPacketEnable: con 16r10; # enable long packet support (v1.2)
+
+# Cabort flags
+WrongVersion: con 16r80; # incompatible com versions
+
+# CMP V2
+Carg1: con Palm->ArgIDbase;
+Cresponse: con 16r80;
+Cxchgprefs, Chandshake: con 16r10+iota;
+
+Pchan.connect(p: self ref Pchan): string
+{
+ (nil, e1) := cmp_write(p, Cwake, 0, Cmajor, Cminor, 57600);
+ if(e1 != nil)
+ return e1;
+ (op, flag, nil, nil, baud, e2) := cmp_read(p, 0);
+ if(e2 != nil)
+ return e2;
+ case op {
+ Cinit=>
+ if(flag & ChangeBaud)
+ p.setbaud(baud);
+ return nil;
+
+ Cabort=>
+ return "Palm rejected connect";
+
+ * =>
+ return sys->sprint("Palm connect: reply %d", op);
+ }
+ return nil;
+}
+
+Pchan.accept(p: self ref Pchan, maxbaud: int): string
+{
+ (op, nil, major, minor, baud, err) := cmp_read(p, 0);
+ if(err != nil)
+ return err;
+ if(major != 1){
+ sys->fprint(sys->fildes(2), "palmsrv: comm version mismatch: %d.%d\n", major, minor);
+ cmp_write(p, Cabort, WrongVersion, Cmajor, 0, 0);
+ return sys->sprint("comm version mismatch: %d.%d", major, minor);
+ }
+ if(baud > maxbaud)
+ baud = maxbaud;
+ flag := 0;
+ if(baud != InitBaud)
+ flag = ChangeBaud;
+ (nil, err) = cmp_write(p, Cinit, flag, Cmajor, Cminor, baud);
+ if(err != nil)
+ return err;
+ p.setbaud(baud);
+ return nil;
+}
+
+cmp_write(p: ref Pchan, op: int, flag: int, major: int, minor: int, baud: int): (int, string)
+{
+ cmpbuf := array[CMPHDRLEN] of byte;
+ cmpbuf[0] = byte op;
+ cmpbuf[1] = byte flag;
+ cmpbuf[2] = byte major;
+ cmpbuf[3] = byte minor;
+ cmpbuf[4] = byte 0;
+ cmpbuf[5] = byte 0;
+ put4(cmpbuf[6:], baud);
+
+ if(op == Cwake)
+ return (16rFF, p.padp_write(cmpbuf, 16rFF));
+ xid := p.nextseq();
+ return (xid, p.padp_write(cmpbuf, xid));
+}
+
+cmp_read(p: ref Pchan, xid: int): (int, int, int, int, int, string)
+{
+ (c, err) := p.padp_read(xid, 20*Sec);
+ if(err != nil)
+ return (0, 0, 0, 0, 0, err);
+ if(len c != CMPHDRLEN)
+ return (0, 0, 0, 0, 0, "CMP: bad response");
+ return (int c[0], int c[1], int c[2], int c[3], get4(c[6:]), nil);
+}
+
+#
+# Palm PADP protocol
+# ``The Packet Assembly/Disassembly Protocol'' in
+# Developing Palm OS Communications, US Robotics, 1996, pp. 53-68.
+#
+# forsyth@caldo.demon.co.uk, 1997
+#
+
+FIRST: con 16r80;
+LAST: con 16r40;
+MEMERROR: con 16r20;
+
+# packet types
+Pdata: con 1;
+Pack: con 2;
+Ptickle: con 4;
+Pabort: con 8;
+
+PADPHDRLEN: con 4; # type[1] flags[1] size[2]
+
+RetryInterval: con 4*Sec;
+MaxRetries: con 14; # they say 14 `seconds', but later state they might need 20 for heap mgmt, so i'll assume 14 attempts (at 4sec ea)
+
+Pchan.padp_xchg(p: self ref Pchan, msg: array of byte, timeout: int): (array of byte, string)
+{
+ xid := p.nextseq();
+ err := p.padp_write(msg, xid);
+ if(err != nil)
+ return (nil, err);
+ return p.padp_read(xid, timeout);
+}
+
+#
+# PADP header
+# type[1] flags[2] size[2], high byte first for size
+#
+# max block size is 2^16-1
+# must ack within 2 seconds
+# wait at most 10 seconds for next chunk
+# 10 retries
+#
+
+Pchan.padp_write(p: self ref Pchan, buf: array of byte, xid: int): string
+{
+ count := len buf;
+ if(count >= 1<<16)
+ return "padp: write too big";
+ p.reply = nil;
+ flags := FIRST;
+ mem := buf[0:];
+ offset := 0;
+ while(count > 0){
+ n := count;
+ if(n > DATALIM)
+ n = DATALIM;
+ else
+ flags |= LAST;
+ ob := array[PADPHDRLEN+n] of byte;
+ ob[0] = byte Pdata;
+ ob[1] = byte flags;
+ l: int;
+ if(flags & FIRST)
+ l = count; # total size in first segment
+ else
+ l = offset; # offset in rest
+ put2(ob[2:], l);
+ ob[PADPHDRLEN:] = mem[0:n];
+ if(debug)
+ padp_dump(ob, "Tx");
+ p.slp_write(xid, ob);
+ retries := 0;
+ for(;;){
+ (ib, nil) := p.slp_read(RetryInterval);
+ if(ib == nil){
+ sys->print("padp write: ack timeout\n");
+ retries++;
+ if(retries > MaxRetries){
+ # USR says not to give up if (flags&LAST)!=0; giving up seems safer
+ sys->print("padp write: give up\n");
+ return "PADP: no response";
+ }
+ p.slp_write(xid, ob);
+ continue;
+ }
+ if(ib.proto != SLP_PAD || len ib.data < PADPHDRLEN || ib.xid != xid && ib.xid != 16rFF){
+ sys->print("padp write: ack wrong type(%d) or xid(%d,%d), or len %d\n", ib.proto, ib.xid, xid, len ib.data);
+ continue;
+ }
+ if(ib.xid == 16rFF){ # connection management
+ if(int ib.data[0] == Ptickle)
+ continue;
+ if(int ib.data[0] == Pabort){
+ sys->print("padp write: device abort\n");
+ p.shutdown = 1;
+ return "device cancelled operation";
+ }
+ }
+ if(int ib.data[0] != Pack){
+ if(int ib.data[0] == Ptickle)
+ continue;
+ # right transaction ... if it's acceptable data, USR says to save it & treat as ack
+ sys->print("padp write: type %d, not ack\n", int ib.data[0]);
+ if(int ib.data[0] == Pdata && flags & LAST && int ib.data[1] & FIRST){
+ p.reply = ib;
+ break;
+ }
+ continue;
+ }
+ if(int ib.data[1] & MEMERROR)
+ return "padp: pilot out of memory";
+ if((flags&(FIRST|LAST)) != (int ib.data[1]&(FIRST|LAST)) ||
+ get2(ib.data[2:]) != get2(ob[2:])){
+ sys->print("padp write: ack, wrong flags (#%x,#%x) or offset (%d,%d)\n", int ib.data[1], flags, get2(ib.data[2:]), get2(ob[2:]));
+ continue;
+ }
+ if(debug)
+ sys->print("padp write: ack %d %d\n", xid, get2(ob[2:]));
+ break;
+ }
+ mem = mem[n:];
+ count -= n;
+ offset += n;
+ flags &= ~FIRST;
+ }
+ return nil;
+}
+
+Pchan.padp_read(p: self ref Pchan, xid, timeout: int): (array of byte, string)
+{
+ buf, mem: array of byte;
+
+ offset := 0;
+ ready := 0;
+ retries := 0;
+ ack := array[PADPHDRLEN] of byte;
+ for(;;){
+ b := p.reply;
+ if(b == nil){
+ err: string;
+ (b, err) = p.slp_read(timeout);
+ if(b == nil){
+ sys->print("padp read: timeout %d\n", retries);
+ if(++retries <= 5)
+ continue;
+ sys->print("padp read: gave up\n");
+ return (nil, err);
+ }
+ retries = 0;
+ } else
+ p.reply = nil;
+ if(debug)
+ padp_dump(b.data, "Rx");
+ if(len b.data < PADPHDRLEN){
+ sys->print("padp read: length\n");
+ continue;
+ }
+ if(b.proto != SLP_PAD){
+ sys->print("padp read: bad proto (%d)\n", b.proto);
+ continue;
+ }
+ if(int b.data[0] == Pabort && b.xid == 16rFF){
+ p.shutdown = 1;
+ return (nil, "device cancelled transaction");
+ }
+ if(int b.data[0] != Pdata || xid != 0 && b.xid != xid){
+ sys->print("padp read mismatch: type (%d) or xid(%d::%d)\n", int b.data[0], b.xid, xid);
+ continue;
+ }
+ f := int b.data[1];
+ o := get2(b.data[2:]);
+ if(f & FIRST){
+ buf = array[o] of byte;
+ ready = 1;
+ offset = 0;
+ o = 0;
+ mem = buf;
+ timeout = 4*Sec;
+ }
+ if(!ready || o != offset){
+ sys->print("padp read: offset %d, expected %d\n", o, offset);
+ continue;
+ }
+ n := len b.data - PADPHDRLEN;
+ if(n > len mem){
+ sys->print("padp read: record too long (%d/%d)\n", n, len mem);
+ # it's probably fatal, but retrying does no harm
+ continue;
+ }
+ mem[0:] = b.data[PADPHDRLEN:PADPHDRLEN+n];
+ mem = mem[n:];
+ offset += n;
+ ack[0:] = b.data[0:PADPHDRLEN];
+ ack[0] = byte Pack;
+ p.slp_write(xid, ack);
+ if(f & LAST)
+ break;
+ }
+ if(offset != len buf)
+ return (buf[0:offset], nil);
+ return (buf, nil);
+}
+
+Pchan.nextseq(p: self ref Pchan): int
+{
+ n := p.lastid + 1;
+ if(n >= 16rFF)
+ n = 1;
+ p.lastid = n;
+ return n;
+}
+
+Pchan.tickle(p: self ref Pchan)
+{
+ xid := p.nextseq();
+ data := array[PADPHDRLEN] of byte;
+ data[0] = byte Ptickle;
+ data[1] = byte (FIRST|LAST);
+ put2(data[2:], 0);
+ if(debug)
+ sys->print("PADP: tickle\n");
+ p.slp_write(xid, data);
+}
+
+padp_dump(data: array of byte, dir: string)
+{
+ stype: string;
+
+ case int data[0] {
+ Pdata => stype = "Data";
+ Pack => stype = "Ack";
+ Ptickle => stype = "Tickle";
+ Pabort => stype = "Abort";
+ * => stype = sys->sprint("#%x", int data[0]);
+ }
+
+ sys->print("PADP %s %s flags=#%x len=%d\n", stype, dir, int data[1], get2(data[2:]));
+
+ if(debug > 1 && (data[0] != byte Pack || len data > 4)){
+ data = data[4:];
+ for(i := 0; i < len data;){
+ sys->print(" %.2x", int data[i]);
+ if(++i%16 == 0)
+ sys->print("\n");
+ }
+ sys->print("\n");
+ }
+}
+
+#
+# Palm's Serial Link Protocol
+# See include/Core/System/SerialLinkMgr.h in Palm SDK
+# and the description in the USR document mentioned above.
+#
+
+SLPHDRLEN: con 10; # BE[1] EF[1] ED[1] dest[1] src[1] type[1] size[2] xid[1] check[1] body[size] crc[2]
+SLP_MTU: con SLPHDRLEN+PADPHDRLEN+DATALIM;
+
+Sblock.new(): ref Sblock
+{
+ return ref Sblock(0, 0, 0, 16rFF, nil);
+}
+
+#
+# format and write an SLP frame
+#
+Pchan.slp_write(p: self ref Pchan, xid: int, b: array of byte): string
+{
+ d := array[SLPHDRLEN] of byte;
+ cb := array[2] of byte;
+
+ nb := len b;
+ d[0] = byte 16rBE;
+ d[1] = byte 16rEF;
+ d[2] = byte 16rED;
+ d[3] = byte p.rport;
+ d[4] = byte p.lport;
+ d[5] = byte p.protocol;
+ d[6] = byte (nb >> 8);
+ d[7] = byte (nb & 16rFF);
+ d[8] = byte xid;
+ d[9] = byte 0;
+ n := 0;
+ for(i:=0; i<len d; i++)
+ n += int d[i];
+ d[9] = byte (n & 16rFF);
+ if(debug)
+ printbytes(d, "SLP Tx hdr");
+ crc := crc16(d, 0);
+ put2(cb, crc16(b, crc));
+
+ if(sys->write(p.fd, d, SLPHDRLEN) != SLPHDRLEN ||
+ sys->write(p.fd, b, nb) != len b ||
+ sys->write(p.fd, cb, 2) != 2)
+ return sys->sprint("%r");
+ return nil;
+}
+
+Pchan.slp_read(p: self ref Pchan, timeout: int): (ref Sblock, string)
+{
+ clock := Timer.start(timeout);
+ alt {
+ <-clock.timeout =>
+ if(debug)
+ sys->print("SLP: timeout\n");
+ return (nil, "SLP: timeout");
+ b := <-p.rd =>
+ clock.stop();
+ return (b, nil);
+ }
+}
+
+slp_recv(p: ref Pchan, pidc: chan of int)
+{
+ n: int;
+
+ pidc <-= sys->pctl(0, nil);
+ buf := array[2*SLP_MTU] of byte;
+ sb := Sblock.new();
+ rd := wr := 0;
+Work:
+ for(;;){
+
+ if(wr != rd){
+ # data already in buffer might start a new frame
+ if(rd != 0){
+ buf[0:] = buf[rd:wr];
+ wr -= rd;
+ rd = 0;
+ }
+ }else
+ rd = wr = 0;
+
+ # header
+ while(wr < SLPHDRLEN){
+ n = sys->read(p.fd, buf[wr:], SLPHDRLEN-wr);
+ if(n <= 0)
+ break Work;
+ wr += n;
+ }
+# {for(i:=0; i<wr;i++)sys->print("%.2x", int buf[i]);sys->print("\n");}
+ if(buf[0] != byte 16rBE || buf[1] != byte 16rEF || buf[2] != byte 16rED){
+ rd++;
+ continue;
+ }
+ if(debug)
+ printbytes(buf[0:wr], "SLP Rx hdr");
+ n = 0;
+ for(i:=0; i<SLPHDRLEN-1; i++)
+ n += int buf[i];
+ if((n & 16rFF) != int buf[9]){
+ rd += 3;
+ continue;
+ }
+ hdr := buf[0:SLPHDRLEN];
+ sb.dst = int hdr[3];
+ sb.src = int hdr[4];
+ sb.proto = int hdr[5];
+ size := (int hdr[6]<<8) | int hdr[7];
+ sb.xid = int hdr[8];
+ sb.data = array[size] of byte;
+ crc := crc16(hdr, 0);
+ rd += SLPHDRLEN;
+ if(rd == wr)
+ rd = wr = 0;
+
+ # data and CRC
+ while(wr-rd < size+2){
+ n = sys->read(p.fd, buf[wr:], size+2-(wr-rd));
+ if(n <= 0)
+ break Work;
+ wr += n;
+ }
+ crc = crc16(buf[rd:rd+size], crc);
+ if(crc != get2(buf[rd+size:])){
+ if(debug)
+ sys->print("CRC error: local=#%.4ux pilot=#%.4ux\n", crc, get2(buf[rd+size:]));
+ for(; rd < wr && buf[rd] != byte 16rBE; rd++)
+ ; # hunt for next header
+ continue;
+ }
+ if(sb.proto != SLP_Loop){
+ sb.data[0:] = buf[rd:rd+size];
+ if(debug)
+ sb.print("Rx");
+ rd += size+2;
+ p.rd <-= sb;
+ sb = Sblock.new();
+ } else {
+ # should we reflect these?
+ if(debug)
+ sb.print("Loop");
+ rd += size+2;
+ }
+ }
+ p.rd <-= nil;
+}
+
+Sblock.print(b: self ref Sblock, dir: string)
+{
+ sys->print("SLP %s %d->%d len=%d proto=%d xid=#%.2x\n",
+ dir, int b.src, int b.dst, len b.data, int b.proto, int b.xid);
+}
+
+printbytes(d: array of byte, what: string)
+{
+ buf := sys->sprint("%s[", what);
+ for(i:=0; i<len d; i++)
+ buf += sys->sprint(" #%.2x", int d[i]);
+ buf += "]";
+ sys->print("%s\n", buf);
+}
+
+get4(p: array of byte): int
+{
+ return (int p[0]<<24) | (int p[1]<<16) | (int p[2]<<8) | int p[3];
+}
+
+get3(p: array of byte): int
+{
+ return (int p[1]<<16) | (int p[2]<<8) | int p[3];
+}
+
+get2(p: array of byte): int
+{
+ return (int p[0]<<8) | int p[1];
+}
+
+put4(p: array of byte, v: int)
+{
+ p[0] = byte (v>>24);
+ p[1] = byte (v>>16);
+ p[2] = byte (v>>8);
+ p[3] = byte (v & 16rFF);
+}
+
+put3(p: array of byte, v: int)
+{
+ p[0] = byte (v>>16);
+ p[1] = byte (v>>8);
+ p[2] = byte (v & 16rFF);
+}
+
+put2(p: array of byte, v: int)
+{
+ p[0] = byte (v>>8);
+ p[1] = byte (v & 16rFF);
+}
+
+# this will be done by table look up;
+# polynomial is xⁱ⁶+xⁱ⁲+x⁵+1
+
+crc16(buf: array of byte, crc: int): int
+{
+ for(j := 0; j < len buf; j++){
+ crc = crc ^ (int buf[j]) << 8;
+ for(i := 0; i < 8; i++)
+ if(crc & 16r8000)
+ crc = (crc << 1) ^ 16r1021;
+ else
+ crc = crc << 1;
+ }
+ return crc & 16rffff;
+}