ref: 02ac617541ca1a7bf82b1615fb5a58235469b5d3
dir: /appl/examples/minitel/modem.b/
# # Copyright © 1998 Vita Nuova Limited. All rights reserved. # #modem states for direct connection MSstart, MSdialing, MSconnected, MSdisconnecting, # special features Ecp # error correction : con (1 << iota); Ecplen: con 17; # error correction block length: data[15], crc, validation (=0) Modem: adt { m: ref Module; # common attributes in: chan of ref Event; connect: int; # None, Direct, Network state: int; # modem dialing state saved: string; # response, so far (direct dial) initstr: string; # softmodem init string (direct dial) dialstr: string; # softmodem dial string (direct dial) lastdialstr: string; spec: int; # special features fd: ref Sys->FD; # modem data file, if != nil cfd: ref Sys->FD; # modem ctl file, if != nil (direct dial only) devpath: string; # path to the modem; avail: array of byte; # already read rd: chan of array of byte; # reader -> rd pid: int; # reader pid if != 0 seq: int; # ECP block sequence number waitsyn: int; # awaiting restart SYN SYN ... sequence errforce: int; addparity: int; # must add parity to outgoing data init: fn(m: self ref Modem, connect: int, initstr, dialstr: string); reset: fn(m: self ref Modem); run: fn(m: self ref Modem); quit: fn(m: self ref Modem); runstate: fn(m: self ref Modem, data: array of byte); write: fn(m: self ref Modem, data: array of byte):int; # to network reader: fn(m: self ref Modem, pidc: chan of int); }; partab: array of byte; dump(a: array of byte, n: int): string { s := sys->sprint("[%d]", n); for(i := 0; i < n; i++) s += sys->sprint(" %.2x", int a[i]); return s; } Modem.init(m: self ref Modem, connect: int, initstr, dialstr: string) { partab = array[128] of byte; for(c := 0; c < 128; c++) if(parity(c)) partab[c] = byte (c | 16r80); else partab[c] = byte c; m.in = chan of ref Event; m.connect = connect; m.state = MSstart; m.initstr = initstr; m.dialstr = dialstr; m.pid = 0; m.spec = 0; m.seq = 0; m.waitsyn = 0; m.errforce = 0; m.addparity = 0; m.avail = array[0] of byte; m.rd = chan of array of byte; m.reset(); } Modem.reset(m: self ref Modem) { m.m = ref Module(Pscreen, 0); } Modem.run(m: self ref Modem) { if(m.dialstr != nil) send(ref Event.Eproto(Pmodem, Mmodem, Cconnect, "", 0,0,0)); Runloop: for(;;){ alt { ev := <- m.in => pick e := ev { Equit => break Runloop; Edata => if(debug['m'] > 0) fprint(stderr, "Modem <- %s\n", e.str()); m.write(e.data); if(T.state == Local || T.spec & Echo) { # loopback if(e.from == Mkeyb) { send(ref Event.Eproto(Pscreen, Mkeyb, Ccursor, "", 0,0,0)); send(ref Event.Edata(Pscreen, Mkeyb, e.data)); } } Eproto => case e.cmd { Creset => m.reset(); Cconnect => if(m.pid != 0) break; m.addparity = 1; T.state = Connecting; send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0)); case m.connect { Direct => S.msg("Appel "+m.dialstr+" ..."); dev := "/dev/modem"; if(openmodem(m, dev) < 0) { S.msg("Modem non prêt"); T.state = Local; send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0)); break; } m.state = MSdialing; m.saved = ""; dialout(m); T.terminalid = TERMINALID2; Network => S.msg("Connexion au serveur ..."); if(debug['m'] > 0 || debug['M'] > 0) sys->print("dial(%s)\n", m.dialstr); cx := dial->dial(m.dialstr, ""); if (cx == nil){ S.msg("Echec de la connexion"); T.state = Local; send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0)); if(debug['m'] > 0) sys->print("can't dial %s: %r\n", m.dialstr); break; } m.fd = cx.dfd; m.cfd = cx.cfd; if(len m.dialstr >= 3 && m.dialstr[0:3] == "tcp") m.addparity = 0; # Internet gateway apparently doesn't require parity if(m.fd != nil) { S.msg(nil); m.state = MSconnected; T.state = Online; send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0)); } T.terminalid = TERMINALID1; } if(m.fd != nil) { pidc := chan of int; spawn m.reader(pidc); m.pid = <-pidc; } Cdisconnect => if(m.pid != 0) { S.msg("Déconnexion ..."); m.state = MSdisconnecting; } if(m.connect == Direct) hangup(m); else nethangup(m); Cplay => # for testing case e.s { "play" => replay(m); } Crequestecp => if(m.spec & Ecp){ # for testing: if already active, force an error m.errforce = 1; break; } m.write(array[] of {byte SEP, byte 16r4A}); sys->print("sending request for ecp\n"); Cstartecp => m.spec |= Ecp; m.seq = 0; # not in spec m.waitsyn = 0; # not in spec Cstopecp => m.spec &= ~Ecp; * => break; } } b := <- m.rd => if(debug['m'] > 0){ fprint(stderr, "Modem -> %s\n", dump(b,len b)); } if(b == nil) { m.pid = 0; case m.state { MSdialing => S.msg("Echec appel"); MSdisconnecting => S.msg(nil); } m.state = MSstart; T.state = Local; send(ref Event.Eproto(Pscreen, Mmodem, Cscreenon, "",0,0,0)); send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0)); break; } m.runstate(b); } } if(m.pid != 0) kill(m.pid); send(nil); } Modem.quit(nil: self ref Modem) { } Modem.runstate(m: self ref Modem, data: array of byte) { if(debug['m']>0) sys->print("runstate %d %s\n", m.state, dump(data, len data)); case m.state { MSstart => ; MSdialing => for(i:=0; i<len data; i++) { ch := int data[i]; if(ch != '\n' && ch != '\r') { m.saved[len m.saved] = ch; continue; } (code, str) := seenreply(m.saved); case code { Noise or Ok => ; Success => S.msg(nil); m.state = MSconnected; T.state = Online; send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0)); Failure => hangup(m); S.msg(str); m.state = MSstart; T.state = Local; send(ref Event.Eproto(Pscreen, Mmodem, Cindicators, "",0,0,0)); } m.saved = ""; } MSconnected => send(ref Event.Edata(m.m.path, Mmodem, data)); MSdisconnecting => ; } } Modem.write(m: self ref Modem, data: array of byte): int { if(m.fd == nil) return -1; if(len data == 0) return 0; if(m.addparity){ # unfortunately must copy data to add parity for direct modem connection pa := array[len data] of byte; for(i := 0; i<len data; i++) pa[i] = partab[int data[i] & 16r7F]; data = pa; } if(debug['m']>0) sys->print("WRITE %s\n", dump(data, len data)); return sys->write(m.fd, data, len data); } # # minitel error correction protocol # # SYN, SYN, block number start of retransmission # NUL ignored # DLE escapes {DLE, SYN, NACK, NUL} # NACK, block restart request # crctab: array of int; Crcpoly: con 16r9; # crc7 = x^7+x^3+1 # precalculate the CRC7 remainder for all bytes mktabs() { crctab = array[256] of int; for(c := 0; c < 256; c++){ v := c; crc := 0; for(i := 0; i < 8; i++){ crc <<= 1; # align remainder's MSB with value's if((v^crc) & 16r80) crc ^= Crcpoly; v <<= 1; } crctab[c] = (crc<<1) & 16rFE; # pre-align the result to save <<1 later } } # return the index of the first non-NUL character (the start of a block) nextblock(a: array of byte, i: int, n: int): int { for(; i < n; i++) if(a[i] != byte NUL) break; return i; } # return the data in the ecp block in a[0:Ecplen] (return nil for bad format) decode(a: array of byte): array of byte { if(debug['M']>0) sys->print("DECODE: %s\n", dump(a, Ecplen)); badpar := 0; oldcrc := int a[Ecplen-2]; crc := 0; op := 0; dle := 0; for(i:=0; i<Ecplen-2; i++){ # first byte is high-order byte of polynomial (MSB first) c := int a[i]; nc := c & 16r7F; # strip parity if((c^int partab[nc]) & 16r80) badpar++; crc = crctab[crc ^ c]; # collapse DLE sequences if(!dle){ if(nc == DLE && i+1 < Ecplen-2){ dle = 1; continue; } if(nc == NUL) continue; # strip non-escaped NULs } dle = 0; a[op++] = byte nc; } if(badpar){ if(debug['E'] > 0) sys->print("bad parity\n"); return nil; } crc = (crc>>1)&16r7F; if(int partab[crc] != oldcrc){ if(debug['E'] > 0) sys->print("bad crc: in %ux got %ux\n", oldcrc, int partab[crc]); return nil; } b := array[op] of byte; b[0:] = a[0:op]; if(debug['M'] > 0) sys->print("OUT: %s [%x :: %x]\n", dump(b,op), crc, oldcrc); return b; } Modem.reader(m: self ref Modem, pidc: chan of int) { pidc <-= sys->pctl(0, nil); if(crctab == nil) mktabs(); a := array[Sys->ATOMICIO] of byte; inbuf := 0; while(m.fd != nil) { while((n := read(m.fd, a[inbuf:], len a-inbuf)) > 0){ n += inbuf; inbuf = 0; if((m.spec & Ecp) == 0){ b := array[n] of byte; for(i := 0; i<n; i++) b[i] = byte (int a[i] & 16r7F); # strip parity m.rd <-= b; }else{ #sys->print("IN: %s\n", dump(a,n)); i := 0; if(m.waitsyn){ sys->print("seeking SYN #%x\n", m.seq); syn := byte (SYN | 16r80); lim := n-3; for(; i <= lim; i++) if(a[i] == syn && a[i+1] == syn && (int a[i+2]&16r0F) == m.seq){ i += 3; m.waitsyn = 0; sys->print("found SYN #%x@%d\n", m.seq, i-3); break; } } lim := n-Ecplen; for(; (i = nextblock(a, i, n)) <= lim; i += Ecplen){ b := decode(a[i:]); if(m.errforce || b == nil){ m.errforce = 0; b = array[2] of byte; b[0] = byte NACK; b[1] = byte (m.seq | 16r40); sys->print("NACK #%x\n", m.seq); m.write(b); m.waitsyn = 1; i = n; # discard rest of block break; } m.seq = (m.seq+1) & 16rF; # mod 16 counter m.rd <-= b; } if(i < n){ a[0:] = a[i:n]; inbuf = n-i; } } } if(n <= 0) break; } # m.fd = nil; m.rd <-= nil; } playfd: ref Sys->FD; in_code, in_char: con iota; replay(m: ref Modem) { buf := array[8192] of byte; DMAX: con 10; d := 0; da := array[DMAX] of byte; playfd = nil; if(playfd == nil) playfd = sys->open("minitel.txt", Sys->OREAD); if(playfd == nil) return; nl := 1; discard := 1; state := in_code; hs := ""; start := 0; mainloop: for(;;) { n := sys->read(playfd, buf, len buf); if(n <= 0) break; for(i:=0; i<n; i++) { ch := int buf[i]; if(nl) case ch { '>' => discard = 0; '<' => discard = 1; if(start) sys->sleep(1000); '{' => start = 1; '}' => break mainloop; } if(ch == '\n') nl = 1; else nl = 0; if(discard) continue; if(!start) continue; if(state == in_code && ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z'))) hs[len hs] = ch; else if(ch == '(') { state = in_char; (v, nil) := toint(hs, 16); da[d++] = byte v; if(d == DMAX) { send(ref Event.Edata(m.m.path, Mmodem, da)); d = 0; da = array[DMAX] of byte; sys->sleep(50); } hs = ""; }else if(ch == ')') state = in_code; } } playfd = nil; } kill(pid : int) { prog := "#p/" + string pid + "/ctl"; fd := sys->open(prog, Sys->OWRITE); if (fd != nil) { cmd := array of byte "kill"; sys->write(fd, cmd, len cmd); } } # Modem stuff # modem return codes Ok, Success, Failure, Noise, Found: con iota; # # modem return messages # Msg: adt { text: string; trans: string; code: int; }; msgs: array of Msg = array [] of { ("OK", "Ok", Ok), ("NO CARRIER", "No carrier", Failure), ("ERROR", "Bad modem command", Failure), ("NO DIALTONE", "No dial tone", Failure), ("BUSY", "Busy tone", Failure), ("NO ANSWER", "No answer", Failure), ("CONNECT", "", Success), }; msend(m: ref Modem, x: string): int { a := array of byte x; return sys->write(m.fd, a, len a); } # # apply a string of commands to modem # apply(m: ref Modem, s: string): int { buf := ""; for(i := 0; i < len s; i++){ c := s[i]; buf[len buf] = c; # assume no Unicode if(c == '\r' || i == (len s -1)){ if(c != '\r') buf[len buf] = '\r'; if(msend(m, buf) < 0) return Failure; buf = ""; } } return Ok; } openmodem(m: ref Modem, dev: string): int { m.fd = sys->open(dev, Sys->ORDWR); m.cfd = sys->open(dev+"ctl", Sys->ORDWR); if(m.fd == nil || m.cfd == nil) return -1; # hangup(m); # m.fd = sys->open(dev, Sys->ORDWR); # m.cfd = sys->open(dev+"ctl", Sys->ORDWR); # if(m.fd == nil || m.cfd == nil) # return -1; return 0; } hangup(m: ref Modem) { sys->sleep(1020); msend(m, "+++"); sys->sleep(1020); apply(m, "ATH0"); m.fd = nil; # sys->write(m.cfd, array of byte "f", 1); sys->write(m.cfd, array of byte "h", 1); m.cfd = nil; # HACK: shannon softmodem "off-hook" bug fix sys->open("/dev/modem", Sys->OWRITE); } nethangup(m: ref Modem) { m.fd = nil; sys->write(m.cfd, array of byte "hangup", 6); m.cfd = nil; } # # check `s' for a known reply or `substr' # seenreply(s: string): (int, string) { for(k := 0; k < len msgs; k++) if(len s >= len msgs[k].text && s[0:len msgs[k].text] == msgs[k].text) { return (msgs[k].code, msgs[k].trans); } return (Noise, s); } contains(s, t: string): int { if(t == nil) return 1; if(s == nil) return 0; n := len t; for(i := 0; i+n <= len s; i++) if(s[i:i+n] == t) return 1; return 0; } dialout(m: ref Modem) { if(m.initstr != nil) apply(m, "AT"+m.initstr); if(m.dialstr != nil) { apply(m, "ATD"+m.dialstr); m.lastdialstr = m.dialstr; m.dialstr = nil; } }