>>>> HELPER.TEXT unit helper; interface procedure help; implementation uses {$U kermglob.code} kermglob; procedure keypress; var ch: char; begin writeln('---------------Press any key to continue---------------'); read( keyboard, ch ); page(output); {SP} end; (* keypress *) procedure help1; var ch: char; begin { help1 } if (noun = nullsym) then begin writeln('KERMIT is a family of programs that do reliable file transfer'); writeln('between computers over TTY lines.', ' KERMIT can also be used to make the '); writeln('microcomputer behave as a terminal', ' for a mainframe. These are the '); writeln('commands for the UCSD p-system version, KERMIT-UCSD:'); writeln end; (* if *) if (noun = nullsym) or (noun = consym) then begin writeln(' CONNECT To make a "virutual terminal" connection to a remote'); writeln(' ':14, 'system.'); writeln; writeln(' ':14, 'To break the connection and "escape" back to the micro,'); writeln(' ':14, 'type the escape sequence (CTRL-] C, that is Control '); writeln(' ':14, 'rightbracket followed immediately by the letter C.)'); writeln; end; (* if *) if (noun = nullsym) or (noun = exitsym) then begin writeln(' EXIT To return back to main command level of the p-system.'); writeln; end; (* if *) if (noun = nullsym) or (noun = helpsym) then begin writeln(' HELP To get a list of KERMIT commands.'); writeln; end; (* if *) if (noun = nullsym) or (noun = quitsym) then begin writeln(' QUIT Same as EXIT.'); writeln; end; (* if *) if (noun = nullsym) or (noun = recsym) then begin writeln(' RECEIVE To accept a file from the remote system.'); writeln; end; (* if *) end; (* help1 *) procedure help2; var ch: char; begin { help2 } if (noun = nullsym) or (noun = sendsym) then begin writeln(' SEND To send a file or group of files to the remote system.'); writeln; end; (* if *) if (noun = nullsym) then keypress; if (noun = nullsym) or (noun = setsym) then begin writeln(' SET To establish system-dependent parameters. The '); writeln(' SET options are as follows: '); writeln; if (adj = nullsym) or (adj = debugsym) then begin writeln(' DEBUG To set debug mode ON or OFF '); writeln(' ':31, '(default is OFF).'); writeln; end; (* if *) if (adj = nullsym) or (adj = escsym) then begin writeln(' ':14, 'ESCAPE To change the escape sequence that '); writeln(' ':31, 'lets you return to the PC Kermit from'); writeln(' ':31, 'the remote host. The default is CTRL-] c.'); writeln; end; (* if *) if (adj = nullsym) or (adj = filewarnsym) then begin writeln(' ':14, 'FILE-WARNING ON/OFF, default is OFF. If ON, '); writeln(' ':31, 'Kermit will warn you and rename an '); writeln(' ':31, 'incoming file so as not to write over'); writeln(' ':31, 'a file that currently exists with the'); writeln(' ':31, 'same name'); writeln; end; (* if *) if (adj = nullsym) or (adj = baudsym) then begin writeln(' ':14, 'BAUD To set the serial baud rate.' ); writeln(' ':31, 'Choices are: 110/300/1200/2400/4800/9600.' ); writeln(' ':31, 'The default is 1200.'); writeln end; (* if *) if (adj = nullsym) then keypress; end; (* if *) end; (* help2 *) procedure help3; begin if (noun = nullsym) or (noun = setsym) then begin if (adj = nullsym) or (adj = ibmsym) then begin writeln(' ':14, 'IBM ON/OFF, default is OFF. This flag '); writeln(' ':31, 'should be ON only when transfering files'); writeln(' ':31, 'between the micro and an IBM VM/CMS'); writeln(' ':31, 'system. It also causes the parity to'); writeln(' ':31, 'be set appropriately (mark) and activates'); writeln(' ':31, 'local echoing'); writeln; end; (* if *) if (adj = nullsym) or (adj = localsym) then begin writeln(' ':14, 'LOCAL-ECHO ON/OFF, default is OFF. This sets the'); writeln(' ':31, 'duplex. It should be ON when using '); writeln(' ':31, 'the IBM and OFF for the DEC-20.'); writeln; end; (* if *) if (adj = nullsym) or (adj = emulatesym) then begin writeln(' ':14, 'EMULATE ON/OFF, default is OFF. This sets the'); writeln(' ':31, 'DataMedia 1520A terminal emulation on or off.'); writeln; end; (* if *) end; (* if *) end; (* help3 *) procedure help4; begin if (noun = setsym) or (noun = nullsym) then begin if (adj = nullsym) or (adj = paritysym) then begin writeln(' ':14, 'PARITY EVEN, ODD, MARK, SPACE, or NONE.'); writeln(' ':31, 'NONE is the default but if the IBM '); writeln(' ':31, 'flag is set, parity is set to MARK. '); writeln(' ':31, 'This flag selects the parity for '); writeln(' ':31, 'outgoing and incoming characters during'); writeln(' ':31, 'CONNECT and file transfer to match the'); writeln(' ':31, 'requirements of the host.'); writeln; end; (* if *) end; (* if *) if (noun = nullsym) or (noun = showsym) then begin writeln(' SHOW To see the values of parameters that can be modified'); writeln(' via the SET command.'); end; (* if *) end; (* help4 *) procedure help; begin help1; help2; help3; help4 end; (* help *) end. { unit helper } >>>> KERMGLOB.TEXT unit kermglob; interface const blksize = 512; oport = 8; (* output port # *) inport = 7; keyport = 2; bell = 7; (* ASCII bell *) maxpack = 93; (* maximum packet size minus 1 *) soh = 1; (* start of header *) sp = 32; (* ASCII space *) cr = 13; (* ASCII CR *) lf = 10; (* ASCII line feed *) xdle = 16; (* ASCII DLE (space compression prefix for psystem) *) del = 127; (* delete *) my_esc = 29; (* default esc char for connect (^]) *) maxtry = 5; (* number of times to retry sending packet *) my_quote = '#'; (* quote character I'll use *) my_pad = 0; (* number of padding chars I need *) my_pchar = 0; (* padding character I need *) my_eol = 13; (* end of line character i need *) my_time = 5; (* seconds after which I should be timed out *) maxtim = 20; (* maximum timeout interval *) mintim = 2; (* minimum time out interval *) at_eof = -1; (* value to return if at eof *) rqsize = 5000; (* input queue size *) qsize1 = 5001; (* qsize + 1 *) eoln_sym = 13; (* pascal eoln sym *) back_space = 8; (* pascal backspace sym *) defaultbaud = 1200; (* default baud rate *) (* screen control information *) (* console line on which to put specified info *) title_line = 1; statusline = 2; packet_line = 3; retry_line = 4; file_line = 5; error_line = 6; debug_line = 7; prompt_line = 8; (* position on line to put info *) statuspos = 70; packet_pos = 19; retry_pos = 17; file_pos = 11; type packettype = packed array[0..maxpack] of char; parity_type = (evenpar, oddpar, markpar, spacepar, nopar); char_int_rec = record (* allows character to be treated as integer... *) (* is system dependent *) case boolean of true: (i: integer); false: (ch: char) end; (* record *) int_bool_rec = record (* allows integer to be treated as boolean... *) (* used for numeric and, or, xor...system dependent *) case boolean of true: (i: integer); false: (b: boolean) end; (* record *) string255 = string[255]; statustype = (null, at_eol, unconfirmed, parm_expected, ambiguous, unrec, fn_expected, ch_expected, num_expected); vocab = (nullsym, allsym, baudsym, consym, debugsym, emulatesym, escsym, evensym, exitsym, filewarnsym,helpsym, ibmsym, localsym, marksym, nonesym, oddsym, offsym, onsym, paritysym, quitsym, recsym, sendsym, setsym, showsym, spacesym); scrcommands = (sc_up, sc_right, sc_clreol, sc_clreos, sc_home, sc_escape, sc_left, sc_clrall, scr_clrline); var noun, verb, adj: vocab; status: statustype; vocablist: array[vocab] of string255; xfilename, line: string255; newescchar: char; expected: set of vocab; newbaud: integer; currstate: char; (* current state *) f: file of char; (* file to be received *) oldf: file; (* file to be sent *) s: string255; xeol, quote, esc_char: char; fwarn, ibm, half_duplex, debug: boolean; i, size, rpsiz, spsiz, pad, n, num_try, oldtry, timint: integer; recpkt, packet: packettype; padchar, ch: char; debf: text; (* file for debug output *) parity: parity_type; xon: char; filebuf: packed array[1..1024] of char; bufpos, bufend: integer; parity_array: packed array[char] of char; ctlset: set of char; rec_ok, send_ok: boolean; baud: integer; emulating: boolean; implementation end. { kermglob } >>>> KERMIT.TEXT program kermit; (* $R-*) (* turn range checking off *) (* $L+*) USES {$u kermglob.code} kermglob, {$U kermutil.code} kermutil, (* {$U kermpack.code} kermpack, *) {$U parser.code} parser, {$U helper.code} helper, {$U sender.code} sender, {$U receiver.code} receiver; { Modifications by SP, 25 Oct 1983: adapt to IBM Version IV.1 Delete keyboard and serial buffering: provided by system already. Additional mods by SP, 18 Mar 1984: make all strings 255 chars long 13 May 84: Incorporate screen control through syscom record entries for portability } procedure showparms; forward; procedure connect; (* connect to remote host and transceive *) var ch: char; close: boolean; procedure read_esc; (* read character after esc char and interpret it *) begin repeat until read_ch(keyport,ch); (* wait until they've typed something in *) if (ch in ['a'..'z']) then (* uppercase it *) ch := chr(ord(ch) - ord('a') + ord('A')); if ch in ['B','C','S','?'] then case ch of 'B': sendbrk; (* B: send a break to the IBM *) 'C': close := true; (* C: end connection *) 'S': begin (* S: show status *) noun := allsym; showparms end; (* S *) '?': begin (* ?: show options *) writeln('B Send a BREAK signal.'); writeln('C Close Connection, return to KERMIT-UCSD command level.'); writeln('Q Query Status of connection'); writeln('F Send Control-F character to remote host.' ); writeln('S Send Control-S character to remote host.' ); writeln('? Print this list'); writeln('^',esc_char,' send the escape character itself to the'); writeln(' remote host.') end; (* ? *) end (* case *) else if ch = esc_char then (* ESC-char: send it out *) begin if half_duplex then begin write(ch); { changed from echo() by SP } write_ch(oport,ch) end (* if *) end (* else if *) else (* anything else: ignore *) write(chr(bell)) end; (* read_esc *) begin (* connect *) clear_buf(keyport); (* empty keyboard buffer *) clear_buf(inport); (* empty remote input buffer *) writeln('Connecting to host...type CTRL-',ctl(esc_char),' C to exit'); close := false; repeat if read_ch(inport,ch) then (* if char from host then *) echo(ch); (* echo it *) if read_ch(keyport,ch) then (* if char from keyboard then *) if ch <> esc_char then (* if not ESC-char then *) begin if half_duplex then (* echo it if half-duplex *) write(ch); { changed from echo() by sp } write_ch(oport,ch) (* send it out the port *) end (* if *) else (* ch = esc_char *) (* else is ESC-char so *) read_esc; (* interpret next char *) until close; (* if still connected, get more *) writeln('Disconnected') end; (* connect *) procedure fill_parity_array; (* parity value table for even parity...not(entry) = odd parity *) const min = 0; max = 126; var i, shifter, counter: integer; minch, maxch, ch: char; r: char_int_rec; begin minch := chr(min); maxch := chr(max); case parity of evenpar: for ch := minch to maxch do begin r.ch := ch; (* put char into variant record *) shifter := aand(r.i,255); (* mask off parity bit *) counter := 0; for i := 1 to 7 do begin (* count the 1's *) if odd(shifter) then counter := counter + 1; shifter := shifter div 2 end; (* for i *) if odd(counter) then (* stick a 1 on if necessary *) parity_array[ch] := chr(aor(ord(ch),128)) else parity_array[ch] := chr(aand(ord(ch),127)) end; (* for ch *) (* case even *) oddpar: for ch := minch to maxch do begin r.ch := ch; (* put char into variant record *) shifter := aand(r.i,255); (* mask off parity bit *) counter := 0; for i := 1 to 7 do begin (* count the 1's *) if odd(shifter) then counter := counter + 1; shifter := shifter div 2 end; (* for i *) if odd(counter) then (* stick a 1 on if necessary *) parity_array[ch] := chr(aand(ord(ch),127)) else parity_array[ch] := chr(aor(ord(ch),128)) end; (* for ch *) (* case odd *) markpar: for ch := minch to maxch do (* stick a 1 on all chars *) parity_array[ch] := chr(aor(ord(ch),128)); spacepar: for ch := minch to maxch do (* mask off parity on all chars *) parity_array[ch] := chr(aand(ord(ch),127)); nopar: for ch := minch to maxch do (* don't mess with parity bit at all *) parity_array[ch] := ch; end; (* case *) end; (* fill_parity_array *) {$I setshow.text} procedure initialize; var ch: char; begin pad := mypad; padchar := chr(mypchar); xeol := chr(my_eol); esc_char := chr(my_esc); quote := my_quote; ctlset := [chr(1)..chr(31),chr(del),quote]; half_duplex := false; debug := false; emulating := false; fwarn := false; spsiz := max_pack; rpsiz := max_pack; n := 0; parity := nopar; initvocab; fill_parity_array; ibm := false; xon := chr(17); bufpos := 1; bufend := 0; baud := defaultbaud; setup_comm end; (* initialize *) procedure closeup; begin page( output ) end; (* closeup *) begin (* main kermit program *) initialize; repeat write('Kermit-UCSD> '); readstr(keyport,line); case parse of unconfirmed: writeln('Unconfirmed'); parm_expected: writeln('Parameter expected'); ambiguous: writeln('Ambiguous'); unrec: writeln('Unrecognized command'); fn_expected: writeln('File name expected'); ch_expected: writeln('Single character expected'); null: case verb of consym: connect; helpsym: help; recsym: begin recsw(rec_ok); gotoxy(0,debugline); write(chr(bell)); if rec_ok then writeln('successful receive') else writeln('unsuccessful receive'); (*$I-*) (* set i/o checking off *) close(oldf); { why??? } if not rec_ok then close(f); { added by SP } (*$I+*) (* set i/o checking back on *) gotoxy(0,promptline); end; (* recsym *) sendsym: begin uppercase(xfilename); sendsw(send_ok); gotoxy(0,debugline); write(chr(bell)); if send_ok then writeln('successful send') else writeln('unsuccessful send'); (*$I-*) (* set i/o checking off *) close(oldf); (*$I+*) (* set i/o checking back on *) gotoxy(0,promptline); end; (* sendsym *) setsym: set_parms; show_sym: show_parms; end; (* case verb *) end; (* case parse *) until (verb = exitsym) or (verb = quitsym); closeup end. (* kermit *) >>>> KERMPACK.TEXT unit kermpack; interface uses {$U kermglob.code} kermglob; procedure spar(var packet: packettype); procedure rpar(var packet: packettype); procedure spack(ptype: char; num:integer; len: integer; data: packettype); function rpack(var len, num: integer; var data: packettype): char; procedure bufemp(buffer: packettype; var f: text; len: integer); function bufill(var buffer: packettype): integer; implementation uses {$U kermutil.code} kermutil; procedure bufemp(*buffer: packettype; var f: text; len: integer*); (* empties a packet into a file *) { Note: this strips out ALL linefeed characters! } var i,ls: integer; r: char_int_rec; s: string255; begin s := copy('',0,0); ls := 0; i := 0; while i < len do begin r.ch := buffer[i]; (* get a character *) if (r.ch = myquote) then begin (* if character is control quote *) i := i + 1; (* skip over quote and *) r.ch := buffer[i]; (* get quoted character *) if (aand(r.i,127) <> ord(myquote)) then r.ch := ctl(r.ch); (* controllify it *) end; (* if *) if (r.i = lf) then { skip linefeeds SP } i := i + 1 else if (r.i = cr) then begin (* else if a carriage return then *) i := i + 1; { i := i + 3; } (* skip over that and line feed *) (*$I-*) (* turn i/o checking off *) writeln(f,s); (* and write out line to file *) s := copy('',0,0); (* empty the string var *) ls := 0; if (io_result <> 0) then begin (* if io_error *) io_error(ioresult); (* tell them and *) currstate := 'a'; (* abort *) end (* if *) end (*$I+*) (* turn i/o checking back on *) else begin (* else, is a regular char, so *) r.i := aand(r.i,127); (* mask off parity bit *) s := concat(s,' '); (* and add character to out string *) ls := ls + 1; s[ls] := r.ch; i := i + 1 (* increase buffer pointer *) end; (* else *) end; (* while *) (* and get another char *) (*$I-*) (* turn i/o checking off *) write(f,s); (* and write out line to file *) if (io_result <> 0) then begin (* if io_error *) io_error(ioresult); (* tell them and *) currstate := 'a'; (* abort *) end (* if *) (*$I+*) (* turn i/o checking back on *) end; (* bufemp *) function bufill(*var buffer: packettype): integer*); (* fill a packet with data from a file...manages a 2 block buffer *) var i, j, k, t7, count: integer; r: char_int_rec; begin i := 0; (* while file has some data & packet has some room we'll keep going *) while ((bufpos <= bufend) or (not eof(oldf))) and (i < spsiz-12) do begin (* if we need more data from disk then *) if (bufpos > bufend) and (not eof(oldf)) then begin (* read a couple of blocks *) bufend := blockread(oldf,filebuf[1],2) * blksize; (* and adjust buffer pointer *) bufpos := 1 end; (* if *) if (bufpos <= bufend) then (* if we're within buffer bounds *) begin r.ch := filebuf[bufpos]; (* get a character *) bufpos := bufpos + 1; (* increase buffer pointer *) if (r.i = xdle) then (* if it's space compression char, *) begin count := ord(unchar(filebuf[bufpos])); (* get # of spaces *) bufpos := bufpos + 1; (* read past # *) r.ch := ' '; (* and make current char a space *) end (* else if *) else (* otherwise, it's just a char *) count := 1; (* so only 1 copy of it *) if (r.ch in ctlset) then (* if a control char *) begin if (r.i = cr) then (* if a carriage return *) begin buffer[i] := quote; (* put (quoted) CR in buffer *) i := i + 1; buffer[i] := ctl(chr(cr)); i := i + 1; r.i := lf; (* and we'll stick a LF after *) end; (* if *) if r.i <> 0 then (* if not a NUL then *) begin buffer[i] := quote; (* put the quote in buffer *) i := i + 1; if r.ch <> quote then r.ch := ctl(r.ch); (* and un-controllify char *) end (* if *) end; (* if *) end; (* if *) j := 1; while (j <= count) and (i <= spsiz - 8) do begin (* put all the chars in buffer *) if (r.i <> 0) then (* so long as not a NUL *) begin buffer[i] := r.ch; i := i + 1; end (* if *) else (* if is a NUL so *) if (bufpos > blksize) then (* skip to end of block *) bufpos := bufend + 1 (* since rest will be NULs *) else bufpos := blksize + 1; j := j + 1 end; (* while *) end; (* while *) if (i = 0) then (* if we're at end of file, *) bufill := (at_eof) (* indicate it *) else (* else *) begin if (j <= count) then (* if didn't all fit in packet *) begin bufpos := bufpos - 2; (* put buf pointer at DLE *) (* and update compress count *) filebuf[bufpos + 1] := tochar(chr(count-j+1)); end; (* if *) bufill := i (* return # of chars in packet *) end; (* else *) end; (* bufill *) procedure spar(*var packet: packettype*); (* fills data array with my send-init parameters *) begin packet[0] := tochar(chr(maxpack)); (* biggest packet i can receive *) packet[1] := tochar(chr(mytime)); (* when i want to be timed out *) packet[2] := tochar(chr(mypad)); (* how much padding i need *) packet[3] := ctl(chr(mypchar)); (* padding char i want *) packet[4] := tochar(chr(myeol)); (* end of line character i want *) packet[5] := myquote; (* control-quote char i want *) packet[6] := 'N'; (* I won't do 8-bit quoting *) end; (* spar *) procedure rpar(*var packet: packettype*); (* gets their init params *) begin spsiz := ord(unchar(packet[0])); (* max send packet size *) timint := ord(unchar(packet[1])); (* when i should time out *) pad := ord(unchar(packet[2])); (* number of pads to send *) padchar := ctl(packet[3]); (* padding char to send *) xeol := unchar(packet[4]); (* eol char i must send *) quote := packet[5]; (* incoming data quote char *) end; (* rpar *) procedure packetwrite(p: packettype; len: integer); (* writes out all of a packet for debugging purposes *) var i: integer; begin gotoxy(0,debugline); for i := 0 to len+3 do write(p[i]) end; (* packetwrite *) procedure spack(*ptype: char; num: integer; len: integer; data: packettype*); (* send a packet *) const maxtry = 10000; var bufp, i, count: integer; chksum: char; buffer: packettype; ch: char; begin if ibm and (currstate <> 's') then (* if ibm and not SINIT then *) begin count := 0; repeat (* wait for an xon *) repeat count := count + 1 until (readch(inport, ch)) or (count > maxtry ); until (ch = xon) or (count > maxtry); if count > maxtry then (* if wait too long then *) begin exit(spack) (* get out *) end; (* if *) end; (* if *) bufp := 0; for i := 1 to pad do write_ch(oport,padchar); (* write out any padding chars *) buffer[bufp] := chr(soh); (* packet sync character *) bufp := bufp + 1; chksum := tochar(chr(len + 3)); (* init chksum *) buffer[bufp] := tochar(chr(len + 3)); (* character count *) bufp := bufp + 1; chksum := chr(ord(chksum) + ord(tochar(chr(num)))); buffer[bufp] := tochar(chr(num)); bufp := bufp + 1; chksum := chr(ord(chksum) + ord(ptype)); buffer[bufp] := ptype; (* packet type *) bufp := bufp + 1; for i := 0 to len - 1 do (* loop through data chars *) begin buffer[bufp] := data[i]; (* store char *) bufp := bufp + 1; chksum := chr(ord(chksum) + ord(data[i])) end; (* for i *) (* compute final chksum *) chksum := chr(aand(ord(chksum) + (aand(ord(chksum),192) div 64), 63)); buffer[bufp] := tochar(chksum); bufp := bufp + 1; buffer[bufp] := xeol; if (parity <> nopar) then for i := 0 to bufp do (* set correct parity on buffer *) buffer[i] := parity_array[buffer[i]]; {unitwrite(oport,buffer[0],bufp+1,,12);} (* send the packet out *) for i := 0 to bufp do write_ch(oport, buffer[i]); if debug then packetwrite(buffer,len); end; (* spack *) (*$G+*) (* turn on goto option...need it for next routine *) function rpack(*var len, num: integer; var data: packettype): char*); (* read a packet *) label 1; (* used to emulate C's CONTINUE statement *) const maxtry = 10000; var count, i, ichksum: integer; chksum, ptype: char; r: char_int_rec; begin count := 0; if not getsoh and (currstate<>'r') then (*if don't get synch char then *) begin rpack := 'N'; (* treat as a NAK *) num := n mod 64; exit(rpack) (* and get out of here *) end; 1: count := count + 1; if (count>maxtry)and(currstate<>'r') then (* if we've tried too many times *) begin (* and aren't waiting for init *) rpack := 'N'; (* treat as NAK *) exit(rpack) (* and get out of here *) end; (* if *) if not getch(r) then (* get a char and *) goto 1; (* resynch if soh *) ichksum := r.i; (* start checksum *) len := ord(unchar(r.ch)) - 3; (* character count *) if not getch(r) then (* get a char and *) goto 1; (* resynch if soh *) ichksum := ichksum + r.i; num := ord(unchar(r.ch)); (* packet number *) if not getch(r) then (* get a char and *) goto 1; (* resynch if soh *) ichksum := ichksum + r.i; ptype := r.ch; (* packet type *) for i := 0 to len-1 do (* get any data *) begin if not getch(r) then (* get a char and *) goto 1; (* resynch if soh *) ichksum := ichksum + r.i; data[i] := r.ch; end; (* for i *) data[len] := chr(0); (* mark end of data *) if not getch(r) then (* get a char and *) goto 1; (* resynch if soh *) (* compute final checksum *) chksum := chr(aand(ichksum + (aand(ichksum,192) div 64), 63)); if (chksum <> unchar(r.ch)) then (* if checksum bad *) rpack := chr(0) (* return 'false' indicator *) else (* else *) rpack := ptype; (* return packet type *) if debug then begin gotoxy(0,debugline); write(len,num,ptype); for i := 1 to 1000 do ; end; (* if *) end; (* rpack *) (*$G-*) (* turn off goto option...don't need it anymore *) end. { kermpack } >>>> KERMUTIL.TEXT unit kermutil; { Change log: 13 May 84: Use KERNEL's syscom record for screen control -sp- } interface uses {$U kermglob.code} kermglob; function read_ch(unitno: integer; var ch: char): boolean; procedure read_str(unitno:integer; var s: string255); procedure echo(ch: char); procedure clear_buf(unitno:integer); function aand(x,y: integer): integer; function aor(x,y: integer): integer; function xor(x,y: integer): integer; procedure uppercase(var s: string255); procedure error(p: packettype; len: integer); procedure io_error(i: integer); procedure debugwrite(s: string255); procedure debugint(s: string255; i: integer); function min(x,y: integer): integer; function tochar(ch: char): char; function unchar(ch: char): char; function ctl(ch: char): char; function getch(var r: char_int_rec): boolean; function getsoh: boolean; function getfil(filename: string255): boolean; procedure send_brk; procedure setup_comm; procedure write_ch(unitno: integer; ch: char ); procedure screen( scrcmd: scrcommands ); procedure writescreen(s: string255); procedure refresh_screen(numtry, num: integer); implementation uses {$U remunit.code} remunit, {SP, 1/14/84} {$U kernel.code} kernel; procedure uppercase(*var s: string255*); var i: integer; begin for i := 1 to length(s) do if s[i] in ['a'..'z'] then s[i] := chr(ord(s[i]) - ord('a') + ord('A')) end; (* uppercase *) { screen -- perform screen operations } procedure screen{( scrcmd: scrcommands )}; begin { for portability, peek in at syscom vector to get control chars } with syscom^ do begin if crtctrl.prefixed[ord(scrcmd)] then write( crtinfo.prefix ); with crtctrl do case scrcmd of sc_up: write( rlf ); sc_right: write( ndfs ); sc_clreol: write( eraseeol ); sc_clreos: write( eraseeos ); sc_home: write( home ); sc_escape: write( escape ); sc_left: write( backspace ); sc_clrall: write( clearscreen ); scr_clrline: write( clearline ) end end end; { screen } function read_ch(*unitno:integer; var ch: char): boolean*); (* read a character from an input queue *) var ready: boolean; begin if unitno=keyport then ready := cr_kbstat else if unitno=inport then ready := cr_remstat else ready := false; if ready then (* if a char there *) if unitno=keyport then begin ch := ' '; unitread( keyport, ch, 1,, 12 ) end else ch := cr_getrem; read_ch := ready end; (* read_ch *) procedure write_ch(*unitno: integer; ch: char*); begin if unitno=oport then cr_putrem( ch ) end; procedure read_str(*unitno:integer; var s: string255*); (* acts like readln(s) but takes input from input queue *) var i: integer; begin i := 0; s := copy('',0,0); repeat repeat (* get a character *) until read_ch(unitno,ch); if (ord(ch) = backspace) then (* if it's a backspace then *) begin if (i > 0) then (* if not at beginning of line *) begin write(ch); (* go back a space on screen *) write(' '); (* erase char on screen *) write(ch); (* go back a space again *) i := i - 1; (* adjust string counter *) s := copy(s,1,i) (* adjust string *) end (* if *) end (* if *) else if (ord(ch) <> eoln_sym) then (* otherwise if not at eoln then *) begin write(ch); (* echo char on screen *) i := i + 1; (* inc string counter *) s := concat(s,' '); s[i] := ch; (* put char in string *) end; (* if *) until (ord(ch) = eoln_sym); (* if not eoln, get another char *) s := copy(s,1,i); (* correct string length *) writeln (* write a line on the screen *) end; (* read_str *) procedure clear_buf(*unitno:integer*); { modified by SP } begin if unitno=keyport then unitclear( unitno ) end; procedure send_brk; begin cr_break end; procedure setup_comm; { SP, 14 Jan 84 } var result: cr_baud_result; begin cr_setcommunications(false, false, baud, 8, 1, cr_orig, 'IBM PC', result ); end; function aand(*x,y: integer): integer*); (* arithmetic and--takes 2 integers and ands them, yeilding an integer *) var xrec, yrec, temp: int_bool_rec; begin xrec.i := x; (* put the two numbers in variant record *) yrec.i := y; temp.b := xrec.b and yrec.b; (* use as booleans to 'and' them *) aand := temp.i (* return integer result *) end; (* aand *) function aor(*x,y: integer): integer*); (* arithmetic or *) var xrec, yrec, temp: int_bool_rec; begin xrec.i := x; (* put two numbers in variant record *) yrec.i := y; temp.b := xrec.b or yrec.b; (* use as booleans to 'or' them *) aor := temp.i (* return integer result *) end; (* aor *) function xor(*x,y: integer): integer*); (* exclusive or *) var xrec, yrec, temp: int_bool_rec; begin xrec.i := x; (* put two numbers in variant record *) yrec.i := y; (* use as booleans to 'xor' them *) temp.b := (xrec.b or yrec.b) and (not(xrec.b and yrec.b)); xor := temp.i (* return integer result *) end; (* xor *) procedure error(*p: packettype; len: integer*); (* writes error message sent by remote host *) var i: integer; begin gotoxy(0,errorline); for i := 0 to len-1 do write(p[i]); gotoxy(0,promptline); end; (* error *) procedure io_error(*i: integer*); begin gotoxy( 0, errorline ); screen( sc_clreol ); case i of 0: writeln('No error'); 1: writeln('Bad Block, Parity error (CRC)'); 2: writeln('Bad Unit Number'); 3: writeln('Bad Mode, Illegal operation'); 4: writeln('Undefined hardware error'); 5: writeln('Lost unit, Unit is no longer on-line'); 6: writeln('Lost file, File is no longer in directory'); 7: writeln('Bad Title, Illegal file name'); 8: writeln('No room, insufficient space'); 9: writeln('No unit, No such volume on line'); 10: writeln('No file, No such file on volume'); 11: writeln('Duplicate file'); 12: writeln('Not closed, attempt to open an open file'); 13: writeln('Not open, attempt to close a closed file'); 14: writeln('Bad format, error in reading real or integer'); 15: writeln('Ring buffer overflow') end; (* case *) gotoxy(0,promptline) end; (* io_error *) procedure debugwrite(*s: string255*); (* writes a debugging message *) var i: integer; begin if debug then begin gotoxy(0,debugline); screen( sc_clreol ); write(s); for i := 1 to 2000 do ; (* write debugging message *) end (* if debug *) end; (* debugwrite *) procedure debugint(*s: string255; i: integer*); (* write a debugging message and an integer *) begin if debug then begin debugwrite(s); write(i) end (* if debug *) end; (* debugint *) function min(*x,y: integer): integer*); (* returns smaller of two integers *) begin if x < y then min := x else min := y end; (* min *) function tochar(*ch: char): char*); (* tochar converts a control character to a printable one by adding space *) begin tochar := chr(ord(ch) + ord(' ')) end; (* tochar *) function unchar(*ch: char): char*); (* unchar undoes tochar *) begin unchar := chr(ord(ch) - ord(' ')) end; (* unchar *) function ctl(*ch: char): char*); (* ctl toggles control bit: ^A becomes A, A becomes ^A *) begin ctl := chr(xor(ord(ch),64)) end; (* ctl *) procedure echo(*ch: char*); (* echos a character on the screen *) const maxtry = 30000; var count, cursorx, cursory:integer; { The DataMedia emulation is by John Socha. } begin ch := chr(aand(ord(ch),127)); (* mask off parity bit *) if emulating and (ord(ch) in [30,25,28,31,29,11]) then case ord(ch) of { Datamedia 1520 emulation } { rs }30: begin { allow timeout while waiting for coordinates so computer doesn't freeze } count := 0; repeat count := count + 1 until read_ch( inport, ch ) or (count>maxtry); if count<=maxtry then begin cursorx:=ord(ch)-32; count := 0; repeat count := count + 1 until read_ch( inport, ch ) or (count>maxtry); if count<=maxtry then begin cursory:=ord(ch)-32; gotoxy(cursorx,cursory) end end end; { em }25: screen( sc_home ); { fs }28: screen( sc_right ); { us }31: screen( sc_up ); { gs }29: screen( sc_clreol ); { vt }11: screen( sc_clreos ) end else unitwrite(1,ch,1,,12) { the 12 eliminates DLE & CR expansion } end; (* echo *) function getch(*var r: char_int_rec): boolean*); (* gets a character, strips parity, returns true if it got a char which *) (* isn't Kermit SOH, false if it gets SOH or nothing after maxtry *) const maxtry = 10000; var count: integer; begin count := 0; getch := false; repeat count := count + 1; until (read_ch(inport,r.ch)) or (count>maxtry); (* wait for a character *) if (count > maxtry) then (* if wait too long then *) exit(getch); (* get out of here *) r.i := aand(r.i,127); (* strip parity from char *) getch := (r.ch <> chr(soh)); (* return true if not SOH *) end; (* getch *) function getsoh(*: boolean*); (* reads characters until it finds an SOH; returns false if has to read more *) (* than maxtry chars *) { modified by SP } const maxtry = 10000; var ch: char; count: integer; begin count := 0; getsoh := true; repeat repeat count := count + 1 until (read_ch(inport,ch)) or (count > maxtry); (* wait for a character *) if (count > maxtry) then begin getsoh := false; exit(getsoh) end; (* if *) ch := chr(aand(ord(ch),127)); (* strip parity of char *) until (ch = chr(SOH)) (* if not SOH, get more *) end; (* getsoh *) function getfil(*filename: string255): boolean*); (* opens a file for writing *) begin (*$I-*) (* turn i/o checking off *) rewrite(f,filename); (*$I-*) (* turn i/o checking on *) getfil := (ioresult = 0) end; (* getfil *) procedure writescreen(*s: string255*); (* sets up the screen for receiving or sending files *) begin page(output); gotoxy(0,titleline); write(' Kermit UCSD p-system, Version ', version ); gotoxy(statuspos,statusline); write(s); gotoxy(0,packetline); write('Number of Packets: '); gotoxy(0,retryline); write('Number of Tries: '); gotoxy(0,fileline); write('File Name: '); end; (* writescreen *) procedure refresh_screen(*numtry, num: integer*); (* keeps track of packet count on screen *) begin gotoxy(retrypos,retryline); write(numtry: 5); gotoxy(packetpos,packetline); write(num: 5) end; (* refresh_screen *) begin { body of unit kermutil } { initialization code } syscom^.crtinfo.flush := chr(255); { effectively turning flush off } syscom^.crtinfo.stop := chr(254); { effectively turning stop off } ***; { <-- would you believe that this is Pascal? } { termination code } syscom^.crtinfo.flush := chr(6); { turn flush back on } syscom^.crtinfo.stop := chr(19) { effectively turning stop off } end. { kermutil } >>>> PARSER.TEXT (*$S+*) unit parser; INTERFACE uses {$U kermglob.code} kermglob; function parse: statustype; procedure initvocab; IMPLEMENTATION uses {$U kermutil.code} kermutil; procedure eatspaces(var s: string255); var done: boolean; i: integer; begin done := (length(s) = 0); while not done do begin if s[1] = ' ' then begin i := length(s) - 1; s := copy(s,2,i); done := length(s) = 0 end (* if *) else done := true end (* while *) end; (* eatspaces *) procedure isolate_word(var line, s: string255); var i: integer; done: boolean; begin done := false; i := 1; s := copy(' ',0,0); while (i <= length(line)) and not done do begin if line[i] = ' ' then done := true else s := concat(s,copy(line,i,1)); i := i + 1; end; (* while *) line := copy(line,i,length(line)-i+1); end; (* isolate_word *) function get_fn(var line, fn: string255): boolean; var i, l: integer; begin get_fn := true; isolate_word(line, fn); l := length(fn); (* Watch out, the set below had an ASCII null (0) in quotes as its 5th *) (* member, between '_' and '/'. The null character has been deleted to *) (* allow tape and network distribution of this program. *) if (l > 15) or (l < 1) then get_fn := false else for i := 1 to l do if not (fn[i] in ['0'..'9','A'..'Z', '-', '_', '', '/', '.']) then get_fn := false end; (* get_fn *) function get_num( var line: string255; var n: integer ): boolean; var numstr: string255; i, l: integer; begin get_num := true; isolate_word( line, numstr ); l := length(numstr); if (l>5) or (l<1) then begin n := 0; get_num := false end else begin n := 0; i := 1; numstr := concat( numstr, ' ' ); while (numstr[i] in ['0'..'9']) do begin if n<(maxint div 10) then n := n*10 + ord( numstr[i] ) - ord( '0' ); i := i + 1 end end end; { get_num } function nextch(var ch: char): boolean; var s: string255; begin isolate_word(line,s); if length(s) <> 1 then nextch := false else begin ch := s[1]; nextch := true end (* else *) end; (* nextch *) function parse(*: statustype*); type states = (start, fin, get_filename, get_set_parm, get_parity, get_on_off, get_char, get_show_parm, get_help_show, get_help_parm, exitstate, get_baud); var status: statustype; word: vocab; state: states; function get_sym(var word: vocab): statustype; var i: vocab; s: string255; stat: statustype; done: boolean; matches: integer; begin eat_spaces(line); if length(line) = 0 then getsym := ateol else begin stat := null; done := false; isolate_word(line,s); i := allsym; matches := 0; repeat if (pos(s,vocablist[i]) = 1) and (i in expected) then begin matches := matches + 1; word := i end else if (s[1] < vocablist[i,1]) then done := true; if (i = spacesym) then done := true else i := succ(i) until (matches > 1) or done; if matches > 1 then stat := ambiguous else if (matches = 0) then stat := unrec; getsym := stat end (* else *) end; (* getsym *) begin state := start; parse := null; noun := nullsym; verb := nullsym; adj := nullsym; uppercase(line); repeat case state of start: begin expected := [consym, exitsym, helpsym, quitsym, recsym, sendsym, setsym, showsym]; status := getsym(verb); if status = ateol then begin parse := null; exit(parse) end (* if *) else if (status <> unrec) and (status <> ambiguous) then case verb of consym: state := fin; exitsym, quitsym: state := fin; helpsym: state := get_help_parm; recsym: state := fin; sendsym: state := getfilename; setsym: state := get_set_parm; showsym: state := get_show_parm; end (* case *) end; (* case start *) fin: begin expected := []; status := getsym(verb); if status = ateol then begin parse := null; exit(parse) end (* if status *) else status := unconfirmed end; (* case fin *) getfilename: begin expected := []; if getfn(line,xfilename) then begin status := null; state := fin end (* if *) else status := fnexpected end; (* case get file name *) get_set_parm: begin expected := [paritysym, localsym, ibmsym, emulatesym, escsym, debugsym, filewarnsym, baudsym]; status := getsym(noun); if status = ateol then status := parm_expected else if (status <> unrec) and (status <> ambiguous) then case noun of paritysym: state := get_parity; localsym: state := get_on_off; ibmsym: state := get_on_off; emulatesym: state := get_on_off; escsym: state := getchar; debugsym: state := get_on_off; filewarnsym: state := get_on_off; baudsym: state := get_baud end (* case *) end; (* case get_set_parm *) get_parity: begin expected := [marksym, spacesym, nonesym, evensym, oddsym]; status := getsym(adj); if status = ateol then status := parm_expected else if (status <> unrec) and (status <> ambiguous) then state := fin end; (* case get_parity *) get_baud: begin expected := []; if get_num( line, newbaud ) then begin status := null; state := fin end else begin newbaud := 0; status := parm_expected end end; (* case get_baud *) get_on_off: begin expected := [onsym, offsym]; status := getsym(adj); if status = ateol then status := parm_expected else if (status <> unrec) and (status <> ambiguous) then state := fin end; (* get_on_off *) get_char: if nextch(newescchar) then state := fin else status := ch_expected; get_show_parm: begin expected := [allsym, paritysym, localsym, ibmsym, escsym, debugsym, filewarnsym, baudsym]; status := getsym(noun); if status = ateol then status := parm_expected else if (status <> unrec) and (status <> ambiguous) then state := fin end; (* case get_show_parm *) get_help_show: begin expected := [paritysym, localsym, ibmsym, escsym, debugsym, filewarnsym, baudsym, emulatesym]; status := getsym(adj); if (status = at_eol) then begin status := null; state := fin end else if (status <> unrec) and (status <> ambiguous) then state := fin end; (* case get_help_show *) get_help_parm: begin expected := [consym, exitsym, helpsym, quitsym, recsym, sendsym, setsym, showsym]; status := getsym(noun); if status = ateol then begin parse := null; exit(parse) end; if (status <> unrec) and (status <> ambiguous) then case noun of consym: state := fin; sendsym: state := fin; recsym: state := fin; setsym: state := get_help_show; showsym: state := fin; helpsym: state := fin; exitsym, quitsym: state := fin; end (* case *) end; (* case get_help_show *) end (* case *) until (status <> null); parse := status end; (* parse *) procedure initvocab; var i: integer; begin vocablist[allsym] := 'ALL'; vocablist[baudsym] := 'BAUD'; vocablist[consym] := 'CONNECT'; vocablist[debugsym] := 'DEBUG'; vocablist[emulatesym] := 'EMULATE'; vocablist[escsym] := 'ESCAPE'; vocablist[evensym] := 'EVEN'; vocablist[exitsym] := 'EXIT'; vocablist[filewarnsym] := 'FILE-WARNING'; vocablist[helpsym] := 'HELP'; vocablist[ibmsym] := 'IBM'; vocablist[localsym] := 'LOCAL-ECHO'; vocablist[marksym] := 'MARK'; vocablist[nonesym] := 'NONE'; vocablist[oddsym] := 'ODD'; vocablist[offsym] := 'OFF'; vocablist[onsym] := 'ON'; vocablist[paritysym] := 'PARITY'; vocablist[quitsym] := 'QUIT'; vocablist[recsym] := 'RECEIVE'; vocablist[sendsym] := 'SEND'; vocablist[setsym] := 'SET'; vocablist[showsym] := 'SHOW'; vocablist[spacesym] := 'SPACE'; end; (* initvocab *) end. (* end of unit *) >>>> RECEIVER.TEXT unit receiver; interface procedure recsw(var rec_ok: boolean); implementation uses {$U kermglob.code} kermglob, {$U kermutil.code} kermutil, {$U kermpack.code} kermpack; procedure recsw{(var rec_ok: boolean)}; function rdata: char; (* send file data *) var num, len: integer; ch: char; i: integer; begin repeat if numtry > maxtry then begin currstate := 'a'; exit(rdata) end; num_try := num_try + 1; ch := rpack(len,num,recpkt); (* receive a packet *) refresh_screen(numtry,n); if (ch = 'D') then (* got data packet *) begin if (num <> (n mod 64)) then (* wrong packet *) begin if (oldtry > maxtry) then begin rdata := 'a'; (* too many tries, abort *) exit(rdata) end; (* if *) n := n - 1; if (num = (n mod 64)) then (* previous packet again *) begin (* so re-ACK it *) spack('Y',num,6,packet); numtry := 0; (* reset try counter *) (* stay in same state *) end (* if *) else (* wrong number *) currstate := 'a' (* so abort *) end (* if *) else (* right packet *) begin bufemp(recpkt,f,len); (* write data to file *) spack('Y',(n mod 64),0,packet); (* ACK packet *) oldtry := numtry; (* reset try counters *) numtry := 0; n := n + 1 (* bump packet number *) (* stay in data send state *) end (* else *) end (* if 'D' *) else if (ch = 'F') then (* file header *) begin if (oldtry > maxtry) then begin rdata := 'a'; (* too many tries, abort *) exit(rdata) end; (* if *) n := n - 1; if (num = (n mod 64)) then (* previous packet again *) begin (* so re-ACK it *) spack('Y',num,0,packet); numtry := 0; (* reset try counter *) currstate := currstate; (* stay in same state *) end (* if *) else currstate := 'a' (* not previous packet, abort *) end (* if 'F' *) else if (ch = 'Z') then (* end of file *) begin if (num <> (n mod 64)) then(* wrong packet, abort *) begin rdata := 'a'; exit(rdata) end; (* if *) spack('Y',n mod 64,0,packet); (* ok, ACK it *) close(f,lock); (* close up the file *) n := n + 1; (* bump packet counter *) currstate := 'f'; (* go to complete state *) end (* else if 'Z' *) else if (ch = 'E') then (* error packet *) begin error(recpkt,len); (* display error *) currstate := 'a' (* and abort *) end (* if 'E' *) else if (ch <> chr(0)) then (* some other packet type, *) currstate := 'a' (* abort *) until (currstate <> 'd'); rdata := currstate end; (* rdata *) function rfile: char; (* receive file header *) var num, len: integer; ch: char; oldfn: string255; i: integer; procedure makename(recpkt: packettype; var fn: string255; l: integer); function exist(fn: string255): boolean; (* returns true if file named fn exists *) var f: file; isthere: boolean; begin (*$I-*) (* turn off i/o checking *) reset(f,fn); isthere := (ioresult = 0); if isthere then { added by SP } close( f ); (*$I+*) exist := isthere end; (* exist *) procedure checkname(var fn: string255); (* if file fn exists, makes a new name which doesn't *) (* does this by changing letters in file name until it *) (* finds some combination which doesn't exitst *) var ch: char; i: integer; begin i := 1; while (i <= length(fn)) and exist(fn) do begin ch := 'A'; while (ch in ['A'..'Z']) and exist(fn) do begin fn[i] := ch; ch := succ(ch); end; (* while *) i := i + 1 end; (* while *) end; (* checkname *) begin (* makename *) fn := copy(' ',1,15); (* stretch length *) moveleft(recpkt[0],fn[1],l); (* get filename from packet *) oldfn := copy(fn, 1,l); (* save fn sent to show user *) fn := copy(fn,1,min(15,l)); (* set length of filename *) (* and make sure <= 15 *) uppercase(fn); if pos('.TEXT',fn) <> length(fn)-4 then begin if length(fn) > 10 then fn := copy(fn,1,10); (* can only be 15 long in all *) fn := concat(fn,'.TEXT'); (* and we'll add .TEXT *) end; (* if *) if fwarn then (* if file warning is on *) checkname(fn); (* must check that name unique *) end; (* makename *) begin (* rfile *) if debug then debugwrite('rfile'); if (numtry > maxtry) then (* if too many tries, give up *) begin rfile := 'a'; exit(rfile) end; numtry := numtry + 1; ch := rpack(len,num,recpkt); (* receive a packet *) refresh_screen(numtry,n); if ch = 'S' then (* send init, maybe our ACK lost *) begin if (oldtry > maxtry) then (* too many tries, abort *) begin rfile := 'a'; exit(rfile) end; (* if *) n := n - 1; if num = (n mod 64) then (* previous packet mod 64? *) begin (* yes, ACK it again *) spar(packet); (* with our send init params *) spack('Y',num,6,packet); numtry := 0; (* reset try counter *) rfile := currstate; (* stay in same state *) end (* if *) else (* not previous packet, abort *) currstate := 'a' end (* if 'S' *) else if (ch = 'Z') then (* end of file *) begin if (oldtry > maxtry) then (* too many tries, abort *) begin rfile := 'a'; exit(rfile) end; (* if *) n := n - 1; if num = (n mod 64) then (* previous packet mod 64? *) begin (* yes, ACK it again *) spack('Y',num,0,packet); numtry := 0; rfile := currstate (* stay in same state *) end (* if *) else rfile := 'a' (* no, abort *) end (* else if *) else if (ch = 'F') then (* file header *) begin (* which is what we really want *) if (num <> (n mod 64)) then (* if wrong packet, abort *) begin rfile := 'a'; exit(rfile) end; makename(recpkt,xfilename,len); (* get filename, make unique if filew *) gotoxy(filepos,fileline); write(oldfn,' ==> ',xfilename); if not getfil(xfilename) then (* try to open new file *) begin ioerror(ioresult); (* if unsuccessful, tell them *) rfile := 'a'; (* and abort *) exit(rfile) end; (* if *) spack('Y',n mod 64,0,packet); (* ACK file header *) oldtry := numtry; (* reset try counters *) numtry := 0; n := n + 1; (* bump packet number *) rfile := 'd'; (* switch to data state *) end (* else if *) else if ch = 'B' then (* break transmission *) begin if (num <> (n mod 64)) then (* wrong packet, abort *) begin rfile := 'a'; exit(rfile) end; spack('Y',n mod 64,0,packet); (* say ok *) rfile := 'c' (* go to complete state *) end (* else if *) else if (ch = 'E') then begin error(recpkt,len); rfile := 'a' end else if (ch = chr(0)) then (* returned false *) rfile := currstate (* so stay in same state *) else (* some weird state, so abort *) rfile := 'a' end; (* rfile *) function rinit: char; (* receive initialization *) var num, len: integer; (* packet number and length *) ch: char; begin if debug then debugwrite('rinit'); numtry := numtry + 1; ch := rpack(len,num,recpkt); (* receive a packet *) refresh_screen(num_try,n); if (ch = 'S') then (* send init packet *) begin rpar(recpkt); (* get other side's init data *) spar(packet); (* fill packet with my init data *) ctl_set := [chr(1)..chr(31),chr(del),quote]; spack('Y',n mod 64,6,packet); (* ACK with my params *) oldtry := numtry; (* save old try count *) numtry := 0; (* start a new counter *) n := n + 1; (* bump packet number *) rinit := 'f'; (* enter file send state *) end (* if 'S' *) else if (ch = 'E') then begin rinit := 'a'; error(recpkt,len) end (* if 'E' *) else if (ch = chr(0)) then rinit := 'r' (* stay in same state *) else rinit := 'a' (* abort *) end; (* rinit *) (* state table switcher for receiving packets *) begin (* recswok *) writescreen('Receiving'); currstate := 'r'; (* initial state is send *) n := 0; (* set packet # *) numtry := 0; (* no tries yet *) while true do if currstate in ['d', 'f', 'r', 'c', 'a'] then case currstate of 'd': currstate := rdata; 'f': currstate := rfile; 'r': currstate := rinit; 'c': begin rec_ok := true; exit(recsw) end; (* case c *) 'a': begin rec_ok := false; exit(recsw) end (* case a *) end (* case *) else (* state not in legal states *) begin rec_ok := false; exit(recsw) end (* else *) end; (* recsw *) end. { receiver } >>>> SENDER.TEXT unit sender; interface procedure sendsw(var send_ok: boolean); implementation uses {$U kermglob.code} kermglob, {$U kermutil.code} kermutil, {$U kermpack.code} kermpack; procedure sendsw{(var send_ok: boolean)}; var io_status: integer; procedure openfile; (* resets file & gets past first 2 blocks *) begin (*$I-*) (* turn off compiler i/o checking temporarily *) reset(oldf,xfilename); (*$I+*) (* turn compiler i/o checking back on *) io_status := io_result; if (iostatus = 0) then if (pos('.TEXT',xfilename) = length(xfilename) - 4) then begin (* is a text file, so *) i := blockread(oldf,filebuf,1); (* skip past 2 block header *) i := blockread(oldf,filebuf,1); end; (* if *) end; (* openfile *) function sinit: char; (* send init packet & receive other side's *) var num, len, i: integer; (* packet number and length *) ch: char; begin if debug then debugwrite('sinit'); if numtry > maxtry then begin sinit := 'a'; exit(sinit) end; num_try := num_try + 1; spar(packet); clear_buf(inport); refresh_screen(numtry,n); spack('S',n mod 64,6,packet); ch := rpack(len,num,recpkt); if (ch = 'N') then begin sinit := 's'; exit(sinit) end (* if 'N' *) else if (ch = 'Y') then begin if ((n mod 64) <> num) then (* not the right ack *) begin sinit := currstate; exit(sinit) end; rpar(recpkt); if (xeol = chr(0)) then (* if they didn't spec eol *) xeol := chr(my_eol); (* use mine *) if (quote = chr(0)) then (* if they didn't spec quote *) quote := my_quote; (* use mine *) ctl_set := [chr(1)..chr(31),chr(del),quote]; numtry := 0; n := n + 1; (* increase packet number *) sinit := 'f'; exit(sinit) end (* else if 'Y' *) else if (ch = 'E') then begin error(recpkt,len); sinit := 'a' end (* if 'E' *) else if (ch = chr(0)) then sinit := currstate else if (ch <> 'N') then sinit := 'a' end; (* sinit *) function sdata: char; (* send file data *) var num, len: integer; ch: char; packarray: array[false..true] of packettype; sizearray: array[false..true] of integer; current: boolean; b: boolean; function other(b: boolean): boolean; (* complements a boolean which is used as array index *) begin if b then other := false else other := true end; (* other *) begin current := true; packarray[current] := packet; sizearray[current] := size; while (currstate = 'd') do begin if (numtry > maxtry) then (* if too many tries, give up *) currstate := 'a'; b := other(current); numtry := numtry + 1; (* send a data packet *) spack('D',n mod 64,sizearray[current],packarray[current]); refresh_screen(numtry,n); (* set up next packet *) sizearray[b] := bufill(packarray[b]); ch := rpack(len,num,recpkt); (* receive a packet *) if ch = 'N' then (* NAK, so just stay in this state *) if ((n+1) mod 64 <> num) then (* unless NAK for next packet, which *) sdata := currstate else (* is just like ACK for this packet *) begin if num > 0 then num := (num - 1) (* in which case, decrement num *) else num := 63; ch := 'Y'; (* and indicate an ACK *) end; (* else *) if (ch = 'Y') then begin if ((n mod 64) <> num) then (* if wrong ACK *) begin sdata := currstate; (* stay in same state *) exit(sdata); (* get out of here *) end; (* if *) numtry := 0; n := n + 1; current := b; if sizearray[current] = ateof then currstate := 'z' (* set state to eof *) else currstate := 'd' (* else stay in data state *) end (* if *) else if (ch = 'E') then begin error(recpkt,len); currstate := 'a' end (* if 'E' *) else if (ch = chr(0)) then (* receive failure, so stay in d *) begin end else if (ch <> 'N') then currstate := 'a' (* on anything else goto abort state *) end; (* while *) size := sizearray[current]; packet := packarray[current]; sdata := currstate end; (* sdata *) function sfile: char; (* send file header *) var num, len, i: integer; ch: char; fn: packettype; oldfn: string255; procedure legalize(var fn: string255); (* make sure we send only 1 '.' in filename *) var count, i, j, l: integer; begin count := 0; l := length(fn); for i := 1 to l do (* count '.'s in fn *) if fn[i] = '.' then count := count + 1; for i := 1 to count-1 do (* remove all but 1 *) begin j := 1; while (j < l) and (fn[j] <> '.') do j := j + 1; (* by finding it *) fn := concat(copy(fn,1,j-1),copy(fn,j+1,l-j)); (* and copying around it *) l := l - 1 end (* for i *) end; (* legalize *) begin if debug then debugwrite('sfile'); if (numtry > maxtry) then (* if too many tries, give up *) begin sfile := 'a'; exit(sfile) end; numtry := numtry + 1; oldfn := xfilename; legalize(xfilename); (* make filename acceptable to remote *) len := length(xfilename); moveleft(xfilename[1],fn[0],len); (* move filename into a packettype *) gotoxy(filepos,fileline); write(oldfn,' ==> ',xfilename); refresh_screen(numtry,n); spack('F',n mod 64,len,fn); (* send file header packet *) size := bufill(packet); (* get first data from file *) (* while waiting for response *) ch := rpack(len,num,recpkt); if ch = 'N' then (* NAK, so just stay in this state *) if ((n+1) mod 64 <> num) then (* unless NAK for next packet, which *) exit(sfile) (* is just like ACK for this packet *) else begin if (num > 0) then num := (num - 1) (* in which case, decrement num *) else num := 63; ch := 'Y'; (* and indicate an ACK *) end; (* else *) if (ch = 'Y') then begin if ((n mod 64) <> num) then (* if wrong ACK, stay in F state *) exit(sfile); numtry := 0; n := n + 1; sfile := 'd'; end (* if *) else if (ch = 'E') then begin error(recpkt,len); sfile := 'a' end (* if 'E' *) else if (ch <> chr(0)) and (ch <> 'N') then (* don't recognize it *) sfile := 'a' end; (* sfile *) function seof: char; (* send end of file *) var num, len: integer; ch: char; begin if debug then debugwrite('seof'); if (numtry > maxtry) then (* if too many tries, give up *) begin seof := 'a'; exit(seof) end; numtry := numtry + 1; refresh_screen(numtry,n); spack('Z',(n mod 64),0,packet); (* send end of file packet *) if debug then debugwrite('seof1'); ch := rpack(len,num,recpkt); if ch = 'N' then (* NAK, so just stay in this state *) if ((n+1) mod 64 <> num) then (* unless NAK for next packet, which *) exit(seof) (* is just like ACK for this packet *) else begin if num > 0 then num := (num - 1) (* in which case, decrement num *) else num := 63; ch := 'Y'; (* and indicate an ACK *) end; (* else *) if (ch = 'Y') then begin if debug then debugwrite('seof2'); if ((n mod 64) <> num) then (* if wrong ACK, stay in F state *) exit(seof); numtry := 0; n := n + 1; if debug then debugwrite(concat('closing ',s)); close(oldf); seof := 'b' end (* if *) else if (ch = 'E') then begin error(recpkt,len); seof := 'a' end (* if 'E' *) else if (ch = chr(0)) then (* receive failed, so stay in z state *) begin end else if (ch <> 'N') then (* other error, just abort *) seof := 'a' end; (* seof *) function sbreak: char; var num, len: integer; ch: char; (* send break (end of transmission) *) begin if debug then debugwrite('sbreak'); if (numtry > maxtry) then (* if too many tries, give up *) begin sbreak := 'a'; exit(sbreak) end; numtry := numtry + 1; refresh_screen(numtry,n); spack('B',(n mod 64),0,packet); (* send end of file packet *) ch := rpack(len,num,recpkt); if ch = 'N' then (* NAK, so just stay in this state *) if ((n+1) mod 64 <> num) then (* unless NAK for next packet, which *) exit(sbreak) (* is just like ACK for this packet *) else begin if num > 0 then num := (num - 1) (* in which case, decrement num *) else num := 63; ch := 'Y'; (* and indicate an ACK *) end; (* else *) if (ch = 'Y') then begin if ((n mod 64) <> num) then (* if wrong ACK, stay in B state *) exit(sbreak); numtry := 0; n := n + 1; sbreak := 'c' (* else, switch state to complete *) end (* if *) else if (ch = 'E') then begin error(recpkt,len); sbreak := 'a' end (* if 'E' *) else if (ch = chr(0)) then (* receive failed, so stay in z state *) begin end else if (ch <> 'N') then (* other error, just abort *) sbreak := 'a' end; (* sbreak *) (* state table switcher for sending *) begin (* sendsw *) if debug then debugwrite(concat('Opening ',xfilename)); openfile; if io_status <> 0 then begin io_error(io_status); send_ok := false; exit(sendsw) end; write_screen('Sending'); currstate := 's'; n := 0; (* set packet # *) numtry := 0; while true do if currstate in ['d', 'f', 'z', 's', 'b', 'c', 'a'] then case currstate of 'd': currstate := sdata; 'f': currstate := sfile; 'z': currstate := seof; 's': currstate := sinit; 'b': currstate := sbreak; 'c': begin send_ok := true; exit(sendsw) end; (* case c *) 'a': begin send_ok := false; exit(sendsw) end (* case a *) end (* case *) else (* state not in legal states *) begin send_ok := false; exit(sendsw) end (* else *) end; (* sendsw *) end. { sender } >>>> SETSHOW.TEXT procedure write_bool(s: string255; b: boolean); (* writes message & 'on' if b, 'off' if not b *) begin write(s); case b of true: writeln('on'); false: writeln('off'); end; (* case *) end; (* write_bool *) procedure show_parms; (* shows the various settable parameters *) begin case noun of allsym: begin write_bool('Debugging is ',debug); writeln('Escape character is ^',ctl(esc_char)); write_bool('File warning is ',fwarn); write_bool('IBM is ',ibm); write_bool('Local echo is ',halfduplex); write_bool('Emulate DataMedia is ', emulating ); case parity of evenpar: write('Even'); markpar: write('Mark'); nopar: write('No'); oddpar: write('Odd'); spacepar: write('Space'); end; (* case *) writeln(' parity'); writeln( 'Baud rate is ', baud:5 ); end; (* allsym *) debugsym: write_bool('Debugging is ',debug); escsym: writeln('Escape character is ^',ctl(esc_char)); filewarnsym: write_bool('File warning is ',fwarn); ibmsym: write_bool('IBM is ',ibm); localsym: write_bool('Local echo is ',halfduplex); emulatesym: write_bool('Emulate DataMedia is ', emulating ); baudsym: writeln( 'Baud rate is ', baud:5 ); paritysym: begin case parity of evenpar: write('Even'); markpar: write('Mark'); nopar: write('No'); oddpar: write('Odd'); spacepar: write('Space'); end; (* case *) writeln(' parity'); end; (* paritysym *) end; (* case *) end; (* show_sym *) procedure set_parms; (* sets the parameters *) begin case noun of debugsym: case adj of onsym: begin debug := true; (*$I-*) rewrite(debf,'CONSOLE:') (*I+*) end; (* onsym *) offsym: debug := false end; (* case adj *) escsym: escchar := newescchar; filewarnsym: fwarn := (adj = onsym); ibmsym: case adj of onsym: begin ibm := true; parity := markpar; half_duplex := true; fillparityarray end; (* onsym *) offsym: begin ibm := false; parity := nopar; half_duplex := false; fillparityarray end; (* onsym *) end; (* case adj *) localsym: halfduplex := (adj = onsym); emulatesym: emulating := (adj = onsym); paritysym: begin case adj of evensym: parity := evenpar; marksym: parity := markpar; nonesym: parity := nopar; oddsym: parity := oddpar; spacesym: parity := spacepar; end; (* case *) fill_parity_array; end; (* paritysym *) baudsym: begin if newbaud=110 then baud := 110 else if newbaud=300 then baud := 300 else if newbaud=1200 then baud := 1200 else if newbaud=2400 then baud := 2400 else if newbaud=4800 then baud := 4800 else if newbaud=9600 then baud := 9600; setup_comm end { baudsym } end; (* case *) end; (* set_parms *)