(* 12/14/83 - Time out on first packet added *) (* 12/14/83 - MTS system calls silenced *) (* 12/05/83 - Carriage control option implemented *) (* 12/03/83 - Tape mode and IBM mode established *) (* 11/21/83 - Program commented *) (* 11/19/83 - History line begun *) (* 11/16/83 - complete working version in place *) PROGRAM kermit; (* KERMIT file transfer utility for the Michigan Terminal System (MTS). Version 1.0 written by William S. Hall, Mathematical Reviews, Ann Arbor, MI in PASCAL/VS. For program usage and limitations see SJ1K:kermit.doc *) %page CONST (* Ordinal values of control characters. Where values differ between the EBCDEC and ASCII control characters, then are so noted. *) NUL = 00; SOH = 01; STX = 2; ETX = 03; EOT = 55; (* A/E = 04/55 *) ENQ = 45; (* A/E = 05/45 *) ACK = 46; (* A/E = 06/46 *) BEL = 47; (* A/E = 07/47 *) BS = 22; (* A/E = 08/22 *) HT = 05; (* A/E = 09/05 *) LF = 37; (* A/E = 10/37 *) VT = 11; FF = 12; CR = 13; SO = 14; SI = 15; DLE = 16; DC1 = 17; DC2 = 18; DC3 = 19; DC4 = 60; (* A/E = 20/60 *) NAK = 61; (* A/E = 21/61 *) SYN = 50; (* A/E = 22/50 *) ETB = 38; (* A/E = 23/38 *) CAN = 24; EM = 25; SUB = 63; (* A/E = 26/63 *) ESC = 39; (* A/E = 27/39 *) FS = 28; GS = 29; RS = 30; US = 31; SP = 64; (* A/E = 32/64 *) DEL = 7; (* A/E = 127/7 *) (* Other program constants needed in the program *) MAXPACK = 94; (* Maximum packet size *) MAXTRY = 5; (* Times to retry a packet *) MYQUOTE = '#'; (* Quote character I will use *) MYPAD = 0; (* Number of padding characters I need *) MYPCHAR = NUL; (* Ordinal value of padding character I need *) MYEOL = CR; (* Ordinal value of end of line char I need *) MYTIME = 5; (* Seconds after which I should be timed out *) NAMESIZE = 40; (* Maximum size of file name *) MAXFILES = 20; (* Maximum number of files to send *) SNDINIT_DLY = 8000000; (* Delay in microseconds before first packet *) %page TYPE (* These types are used to call MTS procedures *) char255 = packed array[1..255] of char; halfword = packed -32768..32767; (* This type holds a packet being received or sent *) packet_type = packed array[1..MAXPACK] of char; (* This points to a packet *) packet_ptr = @packet_type; (* Timeout variable for system time-out call *) intpair = array[1..2] of integer; VAR date : alfa; (* used for running date and time call *) time : alfa; delay : intpair; (* used for calling twait procedure *) cc : boolean; (* Carriage control char in column 1? *) ccinfo : char; (* used to set value of cc from input *) col : integer; (* Marks column position *) cmdstr : char255; (* used to issue commands to MTS *) ascii : boolean; (* ascii char set in use *) i : integer; (* Utility integer *) size : integer; (* Size of present data *) n : integer; (* Message number *) rpsiz : integer; (* Maximum receive packet size *) spsiz : integer; (* Maximum send packet size *) pad : integer; (* How much padding to send *) timint : integer; (* Timeout for foreign host on sends *) numtry : integer; (* Times this packet tried *) oldtry : integer; (* Times previous packet retried *) debug : boolean; (* true means debugging *) state : char; (* Present state of the automaton *) padchar : char; (* Padding character to send *) eol : char; (* End of line character to send *) quote : char; (* Quote character in incoming data *) recpkt : packet_ptr; (* Receive packet buffer pointer *) packet : packet_ptr; (* Send packet buffer pointer *) command : char; (* Command - receive or send *) filnam : array[1..MAXFILES] of string(NAMESIZE); (* holds file names *) nfiles : integer; (* number of files to send *) numsent : integer; (* number already send *) bugfil : text; (* debug file *) sndfil : text; (* file to be sent *) rcvfil : text; (* file to be received *) %page PROCEDURE cmdnoe(const cmd : char255; const len : halfword); fortran; (* Makes MTS calls *) PROCEDURE twait(const code : integer; const val : intpair); fortran; (* Executes delays *) PROCEDURE setsys; (* Set the terminal for file transfer so that no packets are wrapped and the terminal is not paged. Also MTS must not echo characters during the transfer, and control characters, especially control A, must be allowed to pass unintercepted by the front end (Hermes). Finally, reader mode allows XON-XOFF flow control. *) BEGIN cmdnoe('$control *msink* width=255', 26); cmdnoe('$control *msink* outlen=255', 27); cmdnoe('$control *msink* reader=on', 26); cmdnoe('$control *msink* echo=off', 25); cmdnoe('$control *msink* npc=off', 24); cmdnoe('$control *msink* pagewait=off', 29); END; {setsys} PROCEDURE resetsys; (* Restore the user's system after completion of run *) BEGIN cmdnoe('$control *msink* reset', 22); END; {resetsys} FUNCTION toupper(c : char) : char; (* Convert lower to upper case *) BEGIN if ((c >= 'a') and (c <= 'i')) or ((c >= 'j') and (c <= 'r')) or ((c >= 's') and (c <= 'z')) then BEGIN if ascii then toupper := chr(ord(c) - 32) else toupper := chr(ord(c) + 64) END else toupper := c; END; {toupper} FUNCTION checksum(c : INTEGER) : INTEGER; (* checksum based on ASCII sum *) (* Compute a checksum in the range 0 to 63. This is a Pascal version of the formula (sum + (sum & 192) div 64) & 63, where & is bitwise 'and' *) VAR x : INTEGER; BEGIN x := (c MOD 256) DIV 64; x := x + c; checksum := x MOD 64; END; {checksum} %page FUNCTION tochar(ch : integer) : char; (* Converts an integer in the range 0 to 94 to a printing character. If ASCII is the underlying character set, this is trivial. For EBCDEC, the internal representation of characters in Pascal/VS, a case statement is appropriate. Note that three characters, namely, "^", "`", and "\" cannot be represented in quotes and chr(ordinal value) is used instead. This seems to be a pecularity of the MTS operating system and not EBCDEC in general. *) BEGIN if ascii then tochar := chr(ch + 32) else case ch of 0 : tochar := ' '; 1 : tochar := '!'; 2 : tochar := '"'; 3 : tochar := '#'; 4 : tochar := '$'; 5 : tochar := '%'; 6 : tochar := '&'; 7 : tochar := ''''; 8 : tochar := '('; 9 : tochar := ')'; 10 : tochar := '*'; 11 : tochar := '+'; 12 : tochar := ','; 13 : tochar := '-'; 14 : tochar := '.'; 15 : tochar := '/'; 16 : tochar := '0'; 17 : tochar := '1'; 18 : tochar := '2'; 19 : tochar := '3'; 20 : tochar := '4'; 21 : tochar := '5'; 22 : tochar := '6'; 23 : tochar := '7'; 24 : tochar := '8'; 25 : tochar := '9'; 26 : tochar := ':'; 27 : tochar := ';'; 28 : tochar := '<'; 29 : tochar := '='; 30 : tochar := '>'; 31 : tochar := '?'; 32 : tochar := '@'; 33 : tochar := 'A'; 34 : tochar := 'B'; 35 : tochar := 'C'; 36 : tochar := 'D'; 37 : tochar := 'E'; 38 : tochar := 'F'; 39 : tochar := 'G'; 40 : tochar := 'H'; 41 : tochar := 'I'; 42 : tochar := 'J'; 43 : tochar := 'K'; 44 : tochar := 'L'; 45 : tochar := 'M'; 46 : tochar := 'N'; 47 : tochar := 'O'; 48 : tochar := 'P'; 49 : tochar := 'Q'; 50 : tochar := 'R'; 51 : tochar := 'S'; 52 : tochar := 'T'; 53 : tochar := 'U'; 54 : tochar := 'V'; 55 : tochar := 'W'; 56 : tochar := 'X'; 57 : tochar := 'Y'; 58 : tochar := 'Z'; 59 : tochar := '['; 60 : tochar := chr(186); 61 : tochar := ']'; 62 : tochar := chr(170); 63 : tochar := '_'; 64 : tochar := chr(154); 65 : tochar := 'a'; 66 : tochar := 'b'; 67 : tochar := 'c'; 68 : tochar := 'd'; 69 : tochar := 'e'; 70 : tochar := 'f'; 71 : tochar := 'g'; 72 : tochar := 'h'; 73 : tochar := 'i'; 74 : tochar := 'j'; 75 : tochar := 'k'; 76 : tochar := 'l'; 77 : tochar := 'm'; 78 : tochar := 'n'; 79 : tochar := 'o'; 80 : tochar := 'p'; 81 : tochar := 'q'; 82 : tochar := 'r'; 83 : tochar := 's'; 84 : tochar := 't'; 85 : tochar := 'u'; 86 : tochar := 'v'; 87 : tochar := 'w'; 88 : tochar := 'x'; 89 : tochar := 'y'; 90 : tochar := 'z'; 91 : tochar := '{'; 92 : tochar := '|'; 93 : tochar := '}'; 94 : tochar := '~'; otherwise if debug then writeln(bugfil, 'tochar error'); END; {case} END; {tochar} %page FUNCTION unchar(ch : char) : integer; (* Undoes tochar *) (* Converts a printing character to an integer in the range 0-94. This procedure undoes the action of "tochar". *) BEGIN if ascii then unchar := ord(ch) - 32 else case ch of ' ' : unchar := 0; '!' : unchar := 1; '"' : unchar := 2; '#' : unchar := 3; '$' : unchar := 4; '%' : unchar := 5; '&' : unchar := 6; '''': unchar := 7; '(' : unchar := 8; ')' : unchar := 9; '*' : unchar := 10; '+' : unchar := 11; ',' : unchar := 12; '-' : unchar := 13; '.' : unchar := 14; '/' : unchar := 15; '0' : unchar := 16; '1' : unchar := 17; '2' : unchar := 18; '3' : unchar := 19; '4' : unchar := 20; '5' : unchar := 21; '6' : unchar := 22; '7' : unchar := 23; '8' : unchar := 24; '9' : unchar := 25; ':' : unchar := 26; ';' : unchar := 27; '<' : unchar := 28; '=' : unchar := 29; '>' : unchar := 30; '?' : unchar := 31; '@' : unchar := 32; 'A' : unchar := 33; 'B' : unchar := 34; 'C' : unchar := 35; 'D' : unchar := 36; 'E' : unchar := 37; 'F' : unchar := 38; 'G' : unchar := 39; 'H' : unchar := 40; 'I' : unchar := 41; 'J' : unchar := 42; 'K' : unchar := 43; 'L' : unchar := 44; 'M' : unchar := 45; 'N' : unchar := 46; 'O' : unchar := 47; 'P' : unchar := 48; 'Q' : unchar := 49; 'R' : unchar := 50; 'S' : unchar := 51; 'T' : unchar := 52; 'U' : unchar := 53; 'V' : unchar := 54; 'W' : unchar := 55; 'X' : unchar := 56; 'Y' : unchar := 57; 'Z' : unchar := 58; '[' : unchar := 59; chr(186) : unchar := 60; ']' : unchar := 61; chr(170) : unchar := 62; '_' : unchar := 63; chr(154) : unchar := 64; 'a' : unchar := 65; 'b' : unchar := 66; 'c' : unchar := 67; 'd' : unchar := 68; 'e' : unchar := 69; 'f' : unchar := 70; 'g' : unchar := 71; 'h' : unchar := 72; 'i' : unchar := 73; 'j' : unchar := 74; 'k' : unchar := 75; 'l' : unchar := 76; 'm' : unchar := 77; 'n' : unchar := 78; 'o' : unchar := 79; 'p' : unchar := 80; 'q' : unchar := 81; 'r' : unchar := 82; 's' : unchar := 83; 't' : unchar := 84; 'u' : unchar := 85; 'v' : unchar := 86; 'w' : unchar := 87; 'x' : unchar := 88; 'y' : unchar := 89; 'z' : unchar := 90; '{' : unchar := 91; '|' : unchar := 92; '}' : unchar := 93; '~' : unchar := 94; otherwise if debug then writeln(bugfil, 'unchar error'); END; {case} END; {unchar} %page FUNCTION ctl(ch : char) : char; (* Changes the printing characters shown below to control characters. Used to unquote a quoted control character in a packet. *) BEGIN if ascii then ctl := chr(ord(ch) - 64) else case ch of '@' : ctl := chr(NUL); 'A' : ctl := chr(SOH); 'B' : ctl := chr(STX); 'C' : ctl := chr(ETX); 'D' : ctl := chr(EOT); 'E' : ctl := chr(ENQ); 'F' : ctl := chr(ACK); 'G' : ctl := chr(BEL); 'H' : ctl := chr(BS); 'I' : ctl := chr(HT); 'J' : ctl := chr(LF); 'K' : ctl := chr(VT); 'L' : ctl := chr(FF); 'M' : ctl := chr(CR); 'N' : ctl := chr(SO); 'O' : ctl := chr(SI); 'P' : ctl := chr(DLE); 'Q' : ctl := chr(DC1); 'R' : ctl := chr(DC2); 'S' : ctl := chr(DC3); 'T' : ctl := chr(DC4); 'U' : ctl := chr(NAK); 'V' : ctl := chr(SYN); 'W' : ctl := chr(ETB); 'X' : ctl := chr(CAN); 'Y' : ctl := chr(EM); 'Z' : ctl := chr(SUB); '[' : ctl := chr(ESC); chr(186) : ctl := chr(FS); ']' : ctl := chr(GS); chr(170) : ctl := chr(RS); '_' : ctl := chr(US); '?' : ctl := chr(DEL); otherwise if debug then writeln(bugfil, 'ctl error'); END; {case} END; {ctl} %page FUNCTION unctl(ch : char) : char; (* Changes a control character to its corresponding printing form *) VAR i : integer; BEGIN i := ord(ch); if ascii then unctl := chr(i + 64) else case i of NUL : unctl := '@'; SOH : unctl := 'A'; STX : unctl := 'B'; ETX : unctl := 'C'; EOT : unctl := 'D'; ENQ : unctl := 'E'; ACK : unctl := 'F'; BEL : unctl := 'G'; BS : unctl := 'H'; HT : unctl := 'I'; LF : unctl := 'J'; VT : unctl := 'K'; FF : unctl := 'L'; CR : unctl := 'M'; SO : unctl := 'N'; SI : unctl := 'O'; DLE : unctl := 'P'; DC1 : unctl := 'Q'; DC2 : unctl := 'R'; DC3 : unctl := 'S'; DC4 : unctl := 'T'; NAK : unctl := 'U'; SYN : unctl := 'V'; ETB : unctl := 'W'; CAN : unctl := 'X'; EM : unctl := 'Y'; SUB : unctl := 'Z'; ESC : unctl := '['; FS : unctl := chr(186); GS : unctl := ']'; RS : unctl := chr(170); US : unctl := '_'; DEL : unctl := '?'; otherwise if debug then writeln(bugfil, 'unctl error'); END; {case} END; {unctl} %page FUNCTION aord(ch : char) : integer; (* Convert a character to its ASCII ordinal value *) BEGIN if ascii then aord := ord(ch) else aord := unchar(ch) + 32; END; {aord} FUNCTION writeopn(nampkt : packet_ptr; len : integer) : boolean; (* Open a file for writing during receive mode. The filename itself is obtained from the sending Kermit in a file name packet. The name is extracted and concatenated to dynamically create and open it. Pascal/VS does not presently return error codes, but by declaring the function as boolean, this feature can be readily implemented when return codes become available. Use of column 1 for carriage control is an option. *) VAR filnam : string(NAMESIZE); crname : string(NAMESIZE + 20); BEGIN filnam := substr(str(nampkt@), 1, len); crname := '$create '||filnam; cmdnoe(crname, length(crname)); if debug then writeln(bugfil, 'Opening ', filnam); if cc then rewrite(rcvfil, 'FILE='||filnam|| ' MAXLEN=255 ') else rewrite(rcvfil, 'FILE='||filnam|| ' MAXLEN=255 NOCC'); col := 1; writeopn := true; END; {writeopn} FUNCTION getnxt : boolean; (* Gen next file for reading when in send mode. No error codes are returned by Pascal/VS at present, but the function returns a boolean value, allowing implementation of such when available. *) BEGIN if debug then writeln(bugfil, 'Opening ', filnam[numsent]); reset(sndfil, 'FILE='||filnam[numsent]||' MAXLEN=255'); col := 1; getnxt := true; END; {getnxt} %page PROCEDURE rpar(data : packet_ptr); (* Get the other side's sent-init packet. The time-out is N/A *) BEGIN spsiz := unchar(data@[1]); (* Maximum send packet size *) timint := unchar(data@[2]); (* When I should time out *) pad := unchar(data@[3]); (* Number of pads to send *) padchar := ctl(data@[4]); (* padding char to send *) eol := chr(unchar(data@[5])); (* end-of-line char to send *) quote := data@[6]; (* incoming data quote char *) if debug then (* write this to trace file *) writeln(bugfil, 'sendinit data from other side - ', spsiz:3, timint:3, pad:3, ord(padchar):3, ord(eol):3, quote); END; {rpar} PROCEDURE spar(data : packet_ptr); (* Fill data array with my send-init parameters *) BEGIN data@[1] := tochar(MAXPACK); (* my max packet size *) data@[2] := tochar(MYTIME); (* when I should be timed out *) data@[3] := tochar(MYPAD); (* how much padding I need *) data@[4] := unctl(chr(MYPCHAR)); (* my pad char *) data@[5] := tochar(MYEOL); (* my end of line *) data@[6] := MYQUOTE; (* quote char I send *) END; {spar} %page FUNCTION bufill(bufptr : packet_ptr) : integer; (* Get a buffer full of data from the file that is being sent. Control characters are quoted (preceded by a '#'). *) VAR i : integer; (* loop index *) t : char; (* utility character *) BEGIN i := 1; while (not eof(sndfil)) and ( i < spsiz - 8) do (* spsiz - 8 keeps the buffer from overflowing *) BEGIN if eoln(sndfil) then (* end of line. Quote CR and LF *) BEGIN (* quote the char *) bufptr@[i] := quote; (* uncontrollify it *) bufptr@[i + 1] := unctl(chr(CR)); (* do the same for *) bufptr@[i + 2] := quote; (* the line feed *) bufptr@[i + 3] := unctl(chr(LF)); (* bump loop ctr *) i := i + 4; readln(sndfil); (* reset file pointer *) col := 1; (* reset column position *) END {if} else BEGIN read(sndfil,t); (* get the next char *) if ((col = 1) and cc) then BEGIN if t = '1' then (* ignore unless FF *) BEGIN (* quote the form feed *) bufptr@[i] := quote; (* put char in buffer *) bufptr@[i + 1] := unctl(chr(FF)); (* bump counter *) i := i + 2; END END {col = 1} (* control char or *) else if (ord(t) < SP) or (t = chr(DEL)) or (t = quote) then (* quote? *) BEGIN (* yes, so quote it *) bufptr@[i] := quote; (* uncontrollify it *) if t <> quote then t := unctl(t); (* put char in buffer *) bufptr@[i + 1] := t; (* bump counter *) i := i + 2; END else BEGIN bufptr@[i] := t; (* put char in buffer *) i := i + 1; (* bump counter *) END; col := col + 1; (* advance column counter *) END; {else} END; {while} bufill := i - 1; (* return count *) END; {bufill} %page PROCEDURE bufemp(buffer : packet_ptr; len : integer); (* Get data from incoming packet into a file *) VAR i : integer; (* counter *) t : char; (* utility character *) BEGIN i := 1; while i <= len do (* loop thru character field *) BEGIN t := buffer@[i]; (* get character *) if t = MYQUOTE then (* next char must be unquoted *) BEGIN i := i + 1; (* bump counter *) t := buffer@[i]; (* get quoted char *) case t of (* it was a real quote *) MYQUOTE : write(rcvfil, t); (* CR, so assume newline *) 'M' : begin writeln(rcvfil); (* reset column marker *) col := 1; end; (* LF, don't pass *) 'J' : ; (* FF, so make new page *) 'L' : begin page(rcvfil); col := col + 1; end; (* expand the tabs *) 'I' : repeat (* assume stops at 1, 9, 17, etc. *) write(rcvfil, ' '); col := col + 1; until (col mod 8 = 1); otherwise (* make a control character *) begin write(rcvfil, ctl(t)); (* increment column marker *) col := col + 1; end; END; {case} END {if} else begin write(rcvfil, t); (* put character into file *) col := col + 1; (* increment column marker *) end; i := i + 1; END; {while} END; {bufemp} %page FUNCTION rpack(var len, num : integer; data : packet_ptr) : char; (* Read a packet being sent. Compute check sum, return packet type *) LABEL 10; (* Heavens! a GOTO - for resynchronization *) VAR i, chksum : integer; (* counter, check sum *) done : boolean; (* packet read if true *) t, class : char; (* utility char, packet type *) BEGIN if debug then writeln(bugfil, 'rpack'); (* debug, trace file *) while t <> chr(SOH) do read(t); (* look for synch char SOH *) if debug then write(bugfil, t); (* save in debugging file *) done := false; (* not yet done *) 10: while not done do BEGIN read(t); (* get char *) if debug then write(bugfil, t); (* save in trace file *) if t = chr(SOH) then goto 10; (* if synch, start again *) chksum := aord(t); (* accumulate check sum *) len := unchar(t) - 3; (* get length of packet *) read(t); (* get char *) if debug then write(bugfil, t); (* save in trace file *) if t = chr(SOH) then goto 10; (* resynchronize *) chksum := chksum + aord(t); (* accumulate check sum *) num := unchar(t); (* get packet number *) read(t); (* get char *) if debug then write(bugfil, t); (* save in trace file *) if t = chr(SOH) then goto 10; (* resynchronize *) chksum := chksum + aord(t); (* accumulate sum *) class := t; (* get packet type *) for i := 1 to len do (* get the actual data *) BEGIN (* get char *) read(t); (* save in trace file *) if debug then write(bugfil, t); (* resynchronize *) if t = chr(SOH) then goto 10; (* accumulate check sum *) chksum := chksum + aord(t); (* store data *) data@[i] := t; END; read(t); (* get sender's check sum *) (* resynchronize *) if t = chr(SOH) then goto 10; (* save in trace *) if debug then write(bugfil, t); done := true; (* end of packet *) END; {while} if t = tochar(checksum(chksum)) then rpack := class else rpack := 'E'; (* compare check sums, return 'E' if bad *) if debug then writeln(bugfil); (* flush line to trace file *) END; {rpack} %page PROCEDURE spack(class : char; num, len : integer; data : packet_ptr); (* Send a packet to the other side *) TYPE buffer = packed array[1..100] of char; VAR i : integer; (* counter *) chksum : integer; (* packet checksum *) bufp : @buffer; (* pointer to buffer *) BEGIN if debug then writeln(bugfil, 'spack'); (* save in trace *) if pad > 0 then (* send padding if needed *) for i := 1 to pad do write(padchar); new(bufp); (* make space *) bufp@[1] := chr(SOH); (* synch character *) bufp@[2] := tochar(len + 3); (* char representation of length *) chksum := aord(bufp@[2]); (* char representation of check sum *) bufp@[3] := tochar(num); (* char representation of packet number *) chksum := chksum + aord(bufp@[3]); (* accumulate check sum *) bufp@[4] := class; (* packet type *) chksum := chksum + aord(class); (* accumulate check sum *) for i := 1 to len do (* accumulate data and check sum *) BEGIN bufp@[4 + i] := data@[i]; chksum := chksum + aord(data@[i]); END; bufp@[len + 4 + 1] := tochar(checksum(chksum)); (* char representation of check sum *) bufp@[len + 4 + 2] := eol; (* end of line wanted by other end *) for i := 1 to (len+4+1) do write(bufp@[i]); (* send it out to other side *) writeln(bufp@[len+4+2]); (* IMPORTANT! Must flush output in MTS *) if debug then (* save the packet in the trace file *) BEGIN for i := 1 to (len+4+2) do write(bugfil, bufp@[i]); writeln(bugfil); (* flush to file *) END; END; {spack} %page FUNCTION recsw : boolean; (* State table switcher for receiving files *) VAR done : boolean; (* no more files to receive if true *) FUNCTION rinit : char; (* Receive initialization from sender *) VAR len, num : integer; (* packet length, number *) BEGIN if debug then writeln(bugfil, 'rinit'); if numtry > MAXTRY then (* too many tries, so abort *) rinit := 'A' else BEGIN (* bump try count *) numtry := numtry + 1; (* get a packet *) case rpack(len, num, recpkt) of (* got a send-init *) 'S' : BEGIN (* retrieve parameters from sender *) rpar(recpkt); (* fill up packet with my info *) spar(packet); (* ACK with my packet *) spack('Y', n, 6, packet); (* save old try count *) oldtry := numtry; (* start a new counter *) numtry := 0; (* bump count, mod 64 *) n := (n + 1) mod 64; (* return file-send state *) rinit := 'F'; END; {S} (* didn't get packet *) 'E' : rinit := state; (* keep waiting *) (* some other type, abort *) otherwise rinit := 'A'; END; {case} END; {else} END; {rinit} %page FUNCTION rfile : char; (* Receive file name *) VAR num, len : integer; (* packet number, length *) k : integer; (* utility integer *) BEGIN if debug then writeln(bugfil, 'rfile'); if numtry > MAXTRY then (* abort if too many tries *) rfile := 'A' else BEGIN (* bump count *) numtry := numtry + 1; (* get a packet *) case rpack(len, num, recpkt) of (* send-init, maybe ACK *) 'S' : BEGIN (* has been lost *) if oldtry > MAXTRY then (* if too many tries, abort *) rfile := 'A' else BEGIN (* bump oldtry count as well *) oldtry := oldtry + 1; (* previous packet mod 64 ? *) k := n - 1; if k < 0 then k := 63; (* yes, so ACK it again *) if num = k then BEGIN (* send our send-init packet *) spar(packet); spack('Y', num, 6, packet); (* reset try counter *) numtry := 0; (* stay in this state *) rfile := state; END else (* not previous packet, abort *) rfile := 'A'; END; {else} END; {S} (* end-of-file *) 'Z' : BEGIN if oldtry > MAXTRY then rfile := 'A' else BEGIN oldtry := oldtry + 1; (* previous packet, mod 64 ? *) k := n - 1; if k < 0 then k := 63; (* yes, so ACK it again *) if num = k then BEGIN spack('Y', num, 0, packet); numtry := 0; (* stay in this state *) rfile := state; END else (* not previous packet, abort *) rfile := 'A'; END END; {Z} (* file-header *) 'F' : BEGIN (* what we really want so the *) if num <> n then (* packet number must be correct *) rfile := 'A' else BEGIN (* try to open a new file *) if not writeopn(recpkt, len) then rfile := 'A' else (* if OK then *) BEGIN (* ACK the file header *) spack('Y', n, 0, packet); (* reset counters *) oldtry := numtry; numtry := 0; (* bump packet number mod 64 *) n := (n + 1) mod 64; (* switch to data packet *) rfile := 'D'; END; END; END; {F} (* break transmission *) 'B' : BEGIN (* need correct packet number *) if num <> n then rfile := 'A' else BEGIN (* say OK *) spack('Y', n, 0, packet); (* switch to complete state *) rfile := 'C'; END; END; {B} (* souldn't get packet *) 'E' : rfile := state; (* keep trying *) (* something else, abort *) otherwise rfile := 'A'; END; {case} END; END; {rfile} FUNCTION rdata : char; (* Receive data *) VAR num, len : integer; (* packet number, length *) k : integer; (* utility integer *) BEGIN if debug then writeln(bugfil, 'rdata'); if numtry > MAXTRY then (* abort if too many tries *) rdata := 'A' else BEGIN numtry := numtry + 1; (* bump try counter *) (* get packet *) case rpack(len, num, recpkt) of (* got a data packet *) 'D' : BEGIN (* looks like wrong number *) if num <> n then BEGIN (* if too many tries, then quit *) if oldtry > MAXTRY then rdata := 'A' else BEGIN (* bump oldtry counter *) oldtry := oldtry + 1; (* see if we have previous packet again *) k := n - 1; if k < 0 then k := 63; (* yes, got previous one *) if num = k then BEGIN (* re-ACK the packet *) spack('Y', num, 0, packet); (* reset try counter *) numtry := 0; (* stay in D, don't write out data *) rdata := state; END else (* Sorry, wrong number *) rdata := 'A'; END; END; { num <> n } (* write the packet to file *) bufemp(recpkt, len); (* acknowledge the packet *) spack('Y', n, 0, packet); (* reset the counters *) oldtry := numtry; numtry := 0; (* count packets, mod 64 *) n := (n + 1) mod 64; (* stay in this state *) rdata := 'D'; END; {D} (* got a file header *) 'F' : BEGIN (* too many, so quit *) if oldtry > MAXTRY then rdata := 'A' else BEGIN (* bump try counter *) oldtry := oldtry + 1; (* see if previous packet *) k := n - 1; if k < 0 then k := 63; (* yes, so ACK it again *) if num = k then BEGIN spack('Y', num, 0, packet); numtry := 0; (* stay in data state *) rdata := state; END else (* not previous packet so abort *) rdata := 'A'; END; END; {Z} 'Z' : BEGIN (* must have right packet *) if num <> n then rdata := 'A' else BEGIN (* OK, so ACK it *) spack('Y', n, 0, packet); (* close the file *) close(rcvfil); (* bump packet counter *) n := (n + 1) mod 64; (* go back to receive file state *) rdata := 'F'; END; END; (* nothing, keep waiting *) 'E' : rdata := state; (* some other type, *) otherwise (* so abort *) rdata := 'A'; END; {case} END; END; {rdata} BEGIN {recsw} done := false; (* initialize *) state := 'R'; (* always start in receive state *) n := 0; (* initialize message number *) numtry := 0; (* no tries yet *) while not done do (* do until done *) case state of 'D' : state := rdata; (* data receive state *) 'F' : state := rfile; (* file receive state *) 'R' : state := rinit; (* send initiate state *) 'C' : BEGIN (* completed state *) recsw := true; done := true; END; 'A' : BEGIN (* abort state *) recsw := false; done := true; END; END; {case} END; {recsw} %page FUNCTION sendsw : boolean; (* State table switcher for sending files *) VAR done : boolean; (* indicates that sending is finished *) FUNCTION sinit : char; (* Send my parameters and get other side's back *) VAR num, len : integer; (* packet number, length *) BEGIN {function sinit} if debug then writeln(bugfil, 'sinit'); if numtry > MAXTRY then sinit := 'A' (* too many tries *) else BEGIN numtry := numtry + 1; (* bump try counter *) spar(packet); (* fill up with init info *) spack('S', n, 6, packet); (* send it out *) case rpack(len, num, recpkt) of (* get reply *) (* NAK packet *) 'N', 'E' : sinit := state; (* just stay in state *) (* ACK packet *) 'Y' : BEGIN (* wrong ACK, stay in state *) if n <> num then sinit := state else BEGIN (* get other side's init info *) rpar(recpkt); (* check and set defaults *) if eol = chr(NUL) then eol := chr(CR); if quote = chr(NUL) then quote := MYQUOTE; (* reset try counter *) numtry := 0; (* bump packet count *) n := (n + 1) mod 64; (* open file to be sent *) if getnxt then (* if open OK go to next state *) sinit := 'F' (* no good, so give up *) else sinit := 'A'; END; {else} END; {'Y'} (* unknown, abort *) otherwise sinit := 'A'; END; {case} END; {else} END; {sinit} %page FUNCTION sfile : char; (* Send file name *) VAR num, len, l : integer; (* packet number, len, stringlength *) c : char; (* utility character *) BEGIN if debug then writeln(bugfil, 'sfile'); if numtry > MAXTRY (* too many tries, give up *) then sfile := 'A' else BEGIN numtry := numtry + 1; (* bump try counter *) len := 0; (* set packet length to zero *) l := length(filnam[numsent]); (* length of filename *) while (len < l) and (len < NAMESIZE) do BEGIN len := len + 1; (* accumulate length *) (* stash away the name itself *) packet@[len] := (* in upper case *) toupper(filnam[numsent][len]); END; (* send it out *) spack('F', n, len, packet); (* get reply *) c := rpack(len, num, recpkt); case c of (* NAK or ACK *) 'N', 'Y' : BEGIN if c = 'N' then (* as before, stay in this state *) BEGIN (* unless NAK for next packet *) num := num - 1; (* which is like an ACK for this packet *) if num < 0 then num := 63; END; (* wrong count so stay in this state *) if n <> num then sfile := state else BEGIN (* reset counters *) numtry := 0; (* bump packet count *) n := (n + 1) mod 64; (* get first data from file *) size := bufill(packet); (* switch to data state *) sfile := 'D'; END; END; (* receive failure *) 'E' : sfile := state; (* just stay here *) otherwise (* unknown, abort *) sfile := 'A'; END; {case} END; {else} END; {sinit} %page FUNCTION sdata : char; VAR num, len : integer; (* packet number, length *) c : char; (* utility character *) BEGIN if debug then writeln(bugfil, 'sdata'); if numtry > MAXTRY then sdata := 'A' (* abort if too many *) else BEGIN numtry := numtry + 1; (* bump try counter *) spack('D', n, size, packet); (* send a data packet *) c := rpack(len, num, recpkt); (* get the reply *) case c of 'N', 'Y' : BEGIN (* NAK or ACK *) (* respond to NAK *) if c = 'N' then BEGIN num := num - 1; if num < 0 then num := 63; END; (* just stay in this state *) if n <> num then sdata := state (* unless NAK is for next packet *) else (* which is like an ACK for this one *) BEGIN (* reset try counter *) numtry := 0; (* bump packet count *) n := (n + 1) mod 64; if not eof(sndfil) then BEGIN (* get data from file if not at end *) size := bufill(packet); (* stay in data state *) sdata := 'D'; END else (* EOF, so switch to that state *) sdata := 'Z'; END; END; (* receive failure *) 'E' : sdata := state; (* stay in state *) otherwise (* anything else, abort *) sdata := 'A'; END; {case} END; {else} END; {sdata} %page FUNCTION seof : char; (* Send enf-of-file *) VAR num, len : integer; (* packet number, length *) c : char; (* utility char *) BEGIN if debug then writeln(bugfil, 'seof'); if numtry > MAXTRY then (* too many, quit *) seof := 'A' else BEGIN numtry := numtry + 1; (* bump counter *) spack('Z', n, 0, packet); (* send Z packet *) c := rpack(len, num, recpkt); (* get reply *) case c of (* ACK or NAK *) 'N', 'Y' : BEGIN (* NAK, fail unless for *) if c = 'N' then (* previous packet *) BEGIN (* then fall thru *) num := num - 1; if num < 0 then num := 63; END; (* wrong, so stay in state *) if n <> num then seof := state else BEGIN (* reset counter *) numtry := 0; (* increment count *) n := (n + 1) mod 64; if debug then writeln(bugfil, 'closing - ', filnam[numsent]); (* close the file *) close(sndfil); (* increment number of files sent *) numsent := numsent + 1; (* get new one if more to go *) if numsent < nfiles then BEGIN (* and go back to filename state *) if getnxt then seof := 'F' else (* unless failure in file open *) seof := 'B' END (* no more files, so set break state *) else seof := 'B'; END; {else} END; {N, Y} (* error, stay in state *) 'E' : seof := state; (* unknown, abort *) otherwise seof := 'A'; END; {case} END; { else } END; {seof} %page FUNCTION sbreak : char; (* send a break *) VAR num, len : integer; (* packet number, length *) c : char; (* utility char *) BEGIN if debug then writeln(bugfil, 'sbreak'); if numtry > MAXTRY then sbreak := 'A' (* abort if too many *) else BEGIN (* bump counter *) numtry := numtry + 1; (* send a break *) spack('B', n, 0, packet); (* look at reply *) c := rpack(len, num, recpkt); case c of (* see if ACK for this *) 'N', 'Y' : BEGIN (* packet or NAK for previous *) if c = 'N' then BEGIN num := num - 1; if num < 0 then num := 63; END; (* if wrong, then stay in state *) if n <> num then sbreak := state else BEGIN (* reset counter *) numtry := 0; (* bump packet count *) n := (n + 1) mod 64; (* switch to complete state *) sbreak := 'C'; END; END; (* receive failure *) 'E' : sbreak := state; (* stay in state *) otherwise (* unknown, abort *) sbreak := 'A'; END; {case} END; { else } END; {sbreak} %page BEGIN {sendsw} done := false; (* not done yet *) state := 'S'; (* send initiate is the start state *) n := 0; (* initialize message number *) numtry := 0; (* no tries yet *) while not done do case state of 'D' : state := sdata; (* data send state *) 'F' : state := sfile; (* send file name *) 'Z' : state := seof; (* end of file *) 'S' : state := sinit; (* send-init *) 'B' : state := sbreak; (* break-send *) 'C' : BEGIN sendsw := true; done := true END; (* complete *) 'A' : BEGIN sendsw := false; done := true END; (* abort *) otherwise BEGIN sendsw := false; done := true END; (* unknown, so fail *) END; {case} END; {sendsw} %page PROCEDURE init; (* Initialize parameters *) BEGIN delay[1] := 0; (* set up initial packet delay *) delay[2] := SNDINIT_DLY; ascii := false; (* We are using ASCII if true *) debug := false; (* For program development *) if debug then (* creating temporary debug file *) BEGIN (* cmdnoe('$create -debug', 14); *) rewrite(bugfil, 'FILE=-debug'); END; reset(input, 'FILE=*msource* Interactive MAXLEN=255'); rewrite(output, 'FILE=*msink* MAXLEN=255'); (* make wide as possible *) new(packet); (* Point to packet *) new(recpkt); (* make the space needed *) eol := chr(CR); (* EOL for outgoing packets *) quote := MYQUOTE; (* Standard control-quote char *) pad := 0; (* No padding *) padchar := chr(NUL); (* Use null if any padding wanted *) END; %page BEGIN {main} datetime(date, time); writeln('Mathematical Reviews - Kermit on MTS.'); writeln('The date is ', date, '. The time is ', time, '.'); writeln; writeln('For help see the file SJ1K:KERMIT.DOC.'); writeln; init; (* initialize all parameters *) writeln('Enter command - (r)eceive/(s)end:'); readln(command); (* get the command *) command := toupper(command); (* convert to upper case *) writeln('Is column 1 reserved for carriage control (y/n)?'); readln(ccinfo); cc := (toupper(ccinfo) = 'Y'); if command = 'S' then (* get the files to send *) BEGIN nfiles := 0; writeln('Enter file names one at a time.'); writeln('Terminate list with carriage return.'); writeln; repeat writeln('File to send:'); nfiles := nfiles + 1; readln(filnam[nfiles]); until (nfiles >= MAXFILES) or (filnam[nfiles] = '') END; setsys; (* set the terminal so Kermit will work *) case command of 'S' : BEGIN (* send files *) writeln; write('Exit to your system, set IBM mode ON,'); writeln(' and initiate RECEIVE-FILE mode.'); writeln(chr(DC1)); (* write an XON *) twait(0, delay); (* wait a while *) numsent := 1; (* none sent yet *) if sendsw = false then (* now go to send switcher *) if debug then writeln(bugfil, 'Send failed at - ', filnam[numsent]) else if debug then writeln(bugfil, 'Send OK'); END; 'R' : BEGIN (* receive files *) writeln; write('Exit to your system, set IBM mode ON,'); writeln(' and initiate SEND-FILE mode.'); if recsw = false then (* go to receive state switcher *) if debug then writeln(bugfil, 'Receive failed.') else if debug then writeln(bugfil, 'Receive OK.'); END; otherwise (* not a valid command *) writeln('Invalid command given.'); END; {case} close(bugfil); resetsys; (* return terminal to original state *) END. {Kermit}