Date: Thu, 04 Oct 84 14:51:26 EDT From: Edgar B. Butt To: sy.fdc@cu20b Subject: Oh no, another Kermit! Here is a Kermit implementation for the Sperry 1100 systems written in Pascal. It has been run successfully here at the University of Maryland, College Park, and at SUNY, Albany. Please add it to your selection of Kermits. I would appreciate feedback from anyone who tries it. The first page of code consists of comments explaining how to use and generate Kermit1100. Hop someone finds it useful, Edgar Butt (Butt@umd2.arpa) Computer Science Center University of Maryland College Park, Maryland 20742 (301) 454-2946 The source for Kermit1100 version 2.0 begins on the next line. {Kermit1100 - see first executable line in main block for version KERMIT1100 is yet another Kermit written to run on the Sperry (Univac) 1100 series of computers. It is written in Pascal to be compiled on the NOSC Pascal Compiler, version 2.2 or later. This compiler is available from the Computer Science Center of the University of Maryland, College Park, for a nominal service charge. Kermit aficianodos may notice that the structure of this version differs from other versions in that packets are read and sequence checked in the main program loop and are then dispatched to the proper input or output state with a single case statement. This structure has allowed the various state processes to be relatively uncluttered. While doing this implementation I discovered that NAK's are like tadpole tails. They seem like a neat idea at first, but as the frog emerges, they serve no useful purpose. Likewise, I have been unable to find a case in which NAK's are necessary. Sending an ACK for the last good packet received is just as good. If I'm wrong, I am sure that some swamp dweller out there will let me know. (Not to worry, I handle incoming NAK's even though they are not necessary.) By way of a quick synopsys of features, this version of Kermit has: Simple server mode - processes S and R packets 8-bit quoting (Turned on by Q-option) Repeat count prefixes Error packet generation and processing Kermit 1100 is called as a processor with the following control card: @Q*F.KERMIT,OPTIONS 1100SPEC,REMOTESPEC Q*F. is the file in which the processor resides. 1100SPEC is the 1100 file or element on which Kermit will operate. REMOTESPEC is the file name sent to the remote Kermit(a fib of sorts) OPTIONS: B - big buffers. Kermit1100 normally tells the remote Kermit to send packets that will fit in 84 characters. B-option causes it to request the maximum size Kermit packets (which ain't as big as you might wish) Make sure that your communications hardware and software will let the long packets get through. C - assume for sending or receiving that records are to be separated by CR instead of CR-LF L - log in the element KERMITLOG.MDSSS all file reads and writes and all communication sends and receives. MDSSS is the month, day and seconds/4 encoded base 32 (0,...,9,A,...,V). If a catalogued file 'KERMITLOG' is assignable, it is used. Otherwise a temporary file is created. Q - allow eight-bit quoting for sending or receiving. If the file being sent or received has 8-bit data and if the remote kermit is capable of 8-bit quoting, then all 8-bits of data can be sent or received. R - expect to receive data. Put the data in 1100SPEC if specified or in the file or element name sent from the remote Kermit. No transformation on the incoming name is done at present so it had better be good. S - send 1100SPEC to the remote Kermit. If REMOTESPEC is specified, put it in the file header packet. Otherwise put 1100SPEC in the packet. T - test mode. Send (actually print on a terminal) packets as if an S-option had been specified without reading ACK's. W - If the S-option is used, wait 30 seconds before starting to send Kermit1100 tries not to exit until an EOF is received in order to process multiple requests from the remote Kermit. Happy hopping, Edgar Butt (BUTT@UMD2.ARPA) Computer Science Center University of Maryland College Park, Maryland 20742 Phone (301) 454-2946 } {$F Here we go.....} PROCESSOR Kermit (input, output); CONST maxtry = 5; maxbuf = 200 ; maxlin = 80; maxwrt = 132; ascnul = 0; ascsoh = 1; asclf = 10; asccr = 13; ascsp = 32; { } ascns = 35; {#} ascamp = 38; {&} ascast = 42; {*} ascper = 46; {.} ascb = 66; {B} ascd = 68; {D} asce = 69; {E} ascf = 70; {F} ascn = 78; {N} ascr = 82; {R} ascs = 83; {S} asct = 84; {T} ascy = 89; {Y} ascz = 90; {Z} asctil = 126; {~} ascdel = 127; {rubout} mark = ascsoh; TYPE kermitstates = (kcommand, kexit, wexit, sinitiate, sheader, sdata, sbreak, rinitiate, rheader, rdata); filestatus = (closed, open, endfile); ascval = 0..255 ; ascbuf = RECORD ln: INTEGER; ch: ARRAY[1..maxbuf] OF ascval; END; line = PACKED ARRAY [1..maxlin] OF CHAR; {System dependent TYPE} ident= PACKED ARRAY[1..12] OF CHAR; sbits = SET of 0..35; VAR version: string; iniflg: boolean; {Set true after first initialization} server: boolean; {If true, Kermit1100 waits for packets from remote} state: kermitstates; filbuf,wrtbuf,redbuf,sndbuf,rcvbuf: ascbuf; redix: integer; rfile,wfile,lfile: text; fname,rfname,lname: line; fnlen,rfnlen: INTEGER; rstatus, wstatus,lstatus: filestatus; seq,rcvseq: INTEGER; rlen: INTEGER; stype,rcvtyp: ascval; numtry: INTEGER; numcserr: INTEGER; ineoln: boolean; sndonly: boolean; sndlog, rcvlog, wrtlog, redlog: boolean; bstrip: boolean; creol: boolean; lfeol: boolean; crlfeol: boolean; gotcr: boolean; locbsiz: ascval; loctout: ascval; locnpad: ascval; locpad: ascval; loceol: ascval; locquo: ascval; optqu8: ascval; locqu8: ascval; locrep: ascval; rembsiz: ascval; remdsiz: ascval; {Maximum number of data characters to send (remdsiz-3)} remtout: ascval; remnpad: ascval; rempad: ascval; remeol: ascval; remquo: ascval; remqu8: ascval; remrep: ascval; {System dependent VAR} ruse,wuse,luse: ident; a0,a1,a2: integer; {Forward reference procedures } PROCEDURE error(msg:string);FORWARD; {System dependent procedures to read and write files} PROCEDURE readelt1(VAR f:text; filename:ident; name:line; VAR ok:boolean); EXTERN; PROCEDURE openelt1(VAR f:text; filename:ident; name:line; VAR ok:boolean); EXTERN; PROCEDURE closeelt1(VAR f:text; filename:ident; name:line); EXTERN; PROCEDURE param_string(field:INTEGER; VAR param:STRING); EXTERN; PROCEDURE csf(image:line; VAR status:sbits);EXTERN; PROCEDURE write_now(VAR f:text);EXTERN; { System dependent procedure to get a file name from the procedure call card. } PROCEDURE getspec(field: INTEGER; VAR l: line; VAR len: INTEGER); VAR s: string[80]; i: INTEGER; BEGIN param_string(field,s); len:=LENGTH(s); FOR i:=1 TO len DO l[i]:=s[i]; FOR i:=len+1 TO 80 DO l[i]:=' '; END; {$F Character manipulation routines} {System dependent: It is assumed that the function ord(c) where c is of type char will return the ASCII code for the character c.} {System dependent: It is assumed that the function chr(i) where i is an integer ASCII code from 0 to 255 will return the appropriate character} FUNCTION makechar (i: INTEGER): ascval; BEGIN makechar:=ascsp+i; END; FUNCTION unchar (a: ascval): INTEGER; BEGIN unchar:=a-ascsp; END; FUNCTION tog64(a: ascval): ascval; BEGIN tog64:=bxor(64,a); {System dependent} END; FUNCTION tog128(a: ascval): ascval; BEGIN tog128:=bxor(128,a); {System dependent} END; FUNCTION checksum (sum: INTEGER): ascval; BEGIN checksum := (((sum MOD 256) DIV 64) + sum) MOD 64; END; {$F Open and close log file} PROCEDURE logopn; {System dependent} VAR i,t: INTEGER; lstat: boolean; csfsta: sbits; BEGIN csf('@asg,az kermitlog. ',csfsta); IF 35 IN csfsta THEN BEGIN csf('@asg,t kermitlog.,///256 . ',csfsta); END; IF 35 IN csfsta THEN BEGIN writeln(lfile,'Error assigning logfile: KERMITLOG'); END ELSE BEGIN lname:='KERMITLOG.mdttt . '; er(44{TDATE$},a0); a1:=bshr(band(170000000000b,a0),10)+bshr(band(3700000000b,a0),9) +band(77777b,bshr(a0,2)); FOR i:=1 TO 5 DO BEGIN t:=band(31,bshlc(a1,11+5*i))+48; IF t>57 THEN t:=t+7; lname[10+i]:=chr(t); END; luse:='L$F$I$L$E$$$'; openelt1(lfile,luse,lname,lstat); IF lstat=false THEN BEGIN writeln('Error opening log element: ',lname); END ELSE BEGIN lstatus:=open; write(lfile,'Kermit1100 ',version,' Logfile '); write_now(lfile); {Write date and time into logfile} writeln(lfile); writeln(output,'Logging to ',lname); END; END; END; PROCEDURE logcls; {System dependent} BEGIN IF lstatus=open THEN BEGIN closeelt1(lfile,luse,lname); END; END; {$F Buffer routines} PROCEDURE bufinit(VAR buf:ascbuf); BEGIN buf.ln:=0; END; PROCEDURE putbuf(VAR buf: ascbuf; a:ascval); BEGIN IF NOT (buf.lnmaxlin THEN len:=maxlin; FOR i:=1 TO len DO BEGIN a:=buf.ch[i]; IF a>127 THEN a:=a-127; l[i]:=chr(a); END; FOR i:=len+1 to maxlin DO l[i]:=' '; END; {$F Process parameters to and from remote Kermit} PROCEDURE putpar; VAR temp: ascval; BEGIN bufinit(filbuf); putbuf(filbuf,makechar(locbsiz)); putbuf(filbuf,makechar(loctout)); putbuf(filbuf,makechar(locnpad)); putbuf(filbuf,tog64(locpad)); putbuf(filbuf,makechar(loceol)); putbuf(filbuf,locquo); temp:=ascsp; IF locqu8<>0 THEN temp:=locqu8; putbuf(filbuf,temp); putbuf(filbuf,ascsp); {Only know how do to 1 character checksum} temp:=ascsp; IF locrep<>0 THEN temp:=locrep; putbuf(filbuf,temp); END; PROCEDURE getpar; BEGIN IF rcvbuf.ln > 0 THEN rembsiz:=unchar(rcvbuf.ch[1]); IF rcvbuf.ln > 1 THEN remtout:=unchar(rcvbuf.ch[2]); IF rcvbuf.ln > 2 THEN remnpad:=unchar(rcvbuf.ch[3]); IF rcvbuf.ln > 3 THEN rempad:=tog64(rcvbuf.ch[4]); IF rcvbuf.ln > 4 THEN remeol:=unchar(rcvbuf.ch[5]); IF rcvbuf.ln > 5 THEN remquo:=rcvbuf.ch[6]; IF rcvbuf.ln > 6 THEN remqu8:=rcvbuf.ch[7]; IF rcvbuf.ln > 8 THEN remrep:=rcvbuf.ch[9]; remdsiz:=rembsiz-3; IF state=rinitiate THEN {Our parameters have not been sent} BEGIN IF locqu8=0 THEN remqu8:=0; IF ((32remquo) THEN BEGIN locqu8:=ascy; {Remote Kermit specified 8-bit quote character} END ELSE IF remqu8=ascy THEN BEGIN locqu8:=ascamp; IF (locqu8=remquo) OR (locqu8=remrep) THEN locqu8:=asctil; IF (locqu8=remquo) OR (locqu8=remrep) THEN locqu8:=ascns; remqu8:=locqu8; END ELSE BEGIN locqu8:=0; {Don't do 8-bit quoting} remqu8:=0; END; IF ((32remquo) AND (remrep<>remqu8) AND (locrep<>0) THEN BEGIN locrep:=remrep; {Agree to do repeat counts} END ELSE BEGIN remrep:=0; locrep:=0; END; END ELSE {Our parameters have already been sent} BEGIN IF (remqu8<>ascy) AND (remqu8<>locqu8) THEN BEGIN locqu8:=0; {Don't do 8-bit quoting} END; IF remrep<>locrep THEN locrep:=0; {Don't do repeat counts} END; END; {$F Input a packet or a command} PROCEDURE rcvpkt; { This procedure reads all terminal input to Kermit, both packets and command lines. On exit, the following global parameters are set: rcvtyp = 0 - No SOH encountered, could be command line 1 - SOH encountered, but packet incomplete 2 - Checksum error Other - ASCII value of packet type from good packet rcvseq = -1 - Not a valid packet -2 - End of input file encountered 0...63 - Sequence number from valid packet rcvbuf.ln - number of ascii values input since last SOH or if no SOH, from beginning of line rcvbuf.ch - array of ascii values input } VAR c: CHAR; av,rt: ascval; rst,rsq,cs:INTEGER; BEGIN IF rcvlog THEN write(lfile,'rcv <'); IF ineoln THEN BEGIN readln(input); END; rcvtyp:=0; IF eof(input) THEN BEGIN rcvseq:=-2; IF rcvlog THEN write(lfile,'@'); END ELSE BEGIN rcvseq:=-1; rst:=0; ineoln:=eoln(input); bufinit(rcvbuf); WHILE NOT ineoln DO BEGIN IF eoln(input) THEN BEGIN { The 1100 EXEC truncates some trailing spaces. Since a valid packet can end in one or more spaces, we will assume that short packets should end in spaces and hope that the checksum filters out errors. } av:=ascsp; END ELSE BEGIN read(input,c); IF rcvlog THEN write(lfile,c); av:=ord(c); END; IF av=mark THEN rst:=1; CASE rst OF 0: {Mark character never encountered.} BEGIN putbuf(rcvbuf,av); ineoln:=eoln(input); END; 1: {Mark character.} BEGIN rcvtyp:=1; rcvseq:=-1; bufinit(rcvbuf); ineoln:=eoln(input); rst:=2; END; 2: {Length of the packet.} BEGIN cs:=av; {Initialize checksum} rlen:=unchar(av)-3; rst:=3; END; 3: {Packet number.} BEGIN cs:=cs+av; rsq:=unchar(av); rst:=4; END; 4: {Packet type.} BEGIN cs:=cs+av; rt:=av; {remember the packet type} rst:=5; IF rlen=0 THEN rst:=6; END; 5: {Data portion.} BEGIN cs:=cs+av; putbuf(rcvbuf,av); IF rcvbuf.ln = rlen THEN rst:=6; END; 6: {Checksum.} BEGIN IF checksum(cs)=unchar(av) THEN BEGIN rcvtyp:=rt; rcvseq:=rsq; ineoln:=true; {Ignore the rest of the line} END ELSE BEGIN numcserr:=numcserr+1; rst:=0; {Look for another mark} rcvtyp:=2; {Indicate checksum error} ineoln:=eoln(input); END; END; END; END; END; IF rcvlog THEN writeln(lfile,'>'); END; {$F Build and send packets} PROCEDURE makepacket(ptype: ascval; seq, len: INTEGER); VAR i: INTEGER; c: ascval; cs: INTEGER; BEGIN bufinit(sndbuf); FOR i:=1 TO remnpad DO BEGIN putbuf(sndbuf,rempad); END; putbuf(sndbuf,mark); c:=makechar(len+3); cs:=c; {Initialize checksum} putbuf(sndbuf,c); c:=makechar(seq); cs:=cs+c; putbuf(sndbuf,c); c:=ptype; cs:=cs+c; putbuf(sndbuf,c); FOR i:=1 to len DO BEGIN c:=filbuf.ch[i]; cs:=cs+c; putbuf(sndbuf,c); END; c:=makechar(checksum(cs)); putbuf(sndbuf,c); { The 1100 EXEC may strip trailing spaces from the end of output images. This can cause a problem if the checksum is a space. To eliminate this problem, a period will be inserted in the output image after the checksum whenever the checksum is a space. } putbuf(sndbuf,ascper); { The 1100 O/S puts a CR LF on the end of each output line. If the remote EOL character is not CR or LF, then it must be added to the packet. } IF (remeol<>asccr) AND (remeol<>asclf) THEN BEGIN putbuf(sndbuf,remeol); END; END; PROCEDURE sndpkt; VAR i:INTEGER; BEGIN IF sndlog THEN write(lfile,'snd <'); FOR i:=1 TO sndbuf.ln DO BEGIN write(output,chr(sndbuf.ch[i])); IF sndlog THEN write(lfile,chr(sndbuf.ch[i])); END; writeln(output); IF sndlog THEN writeln(lfile,'>'); END; {$F File output} PROCEDURE wrtrec; VAR i:INTEGER; c:char; BEGIN IF wrtlog THEN write(lfile,'wrt ['); FOR i:=1 TO wrtbuf.ln DO BEGIN {$A- Turn off range checking, ASCII value may be >127} c:=chr(wrtbuf.ch[i]); {$A+ Turn on range checking} write(wfile,c) ; IF wrtlog THEN write(lfile,c); END; writeln(wfile); IF wrtlog THEN writeln(lfile,']'); bufinit(wrtbuf); END; PROCEDURE wrtcls; {System dependent} BEGIN IF wstatus=open THEN BEGIN IF wrtbuf.ln>0 THEN wrtrec; closeelt1(wfile,wuse,fname); END; wstatus:=closed; END; PROCEDURE wrtopn; {System dependent} VAR wstat: boolean; BEGIN wrtcls; wuse:='W$F$I$L$E$$$'; openelt1(wfile,wuse,fname,wstat); IF wstat THEN wstatus:=open; bufinit(wrtbuf); END; PROCEDURE wrtasc(a:ascval); BEGIN IF wrtbuf.ln >=maxwrt THEN wrtrec; putbuf(wrtbuf,a); END; {$F Process data portion of data packet} PROCEDURE putrec(buf: ascbuf); VAR i,j,repcnt:INTEGER; a:ascval; qflag: boolean; BEGIN i:=1; WHILE i<= buf.ln DO BEGIN a:=buf.ch[i]; i:=i+1; repcnt:=1; IF a=remrep THEN BEGIN repcnt:=unchar(buf.ch[i]); i:=i+1; a:=buf.ch[i]; i:=i+1; END; qflag:= a=remqu8; IF qflag THEN BEGIN a:=buf.ch[i]; i:=i+1; END; IF a=remquo THEN BEGIN a:=buf.ch[i]; i:=i+1; IF (a<>remquo) AND (a<>remqu8) AND (a<>remrep) THEN a:=tog64(a); END; IF qflag THEN a:=tog128(a); FOR j:=1 to repcnt DO BEGIN IF a=asclf THEN BEGIN IF lfeol OR gotcr THEN BEGIN wrtrec; gotcr:=false; END ELSE BEGIN wrtasc(a); END; END ELSE BEGIN IF gotcr THEN BEGIN wrtasc(asccr); gotcr:=false; END; IF a=asccr THEN BEGIN IF creol THEN BEGIN wrtrec; END ELSE IF crlfeol THEN BEGIN gotcr:=true; END ELSE BEGIN wrtasc(a); END; END ELSE BEGIN wrtasc(a); END; END; END; END; END; {$F File input} PROCEDURE redrec; VAR c: CHAR; a: ascval; nonblank: INTEGER; BEGIN bufinit(redbuf); IF redix >= 0 THEN readln(rfile); redix:=0; IF NOT eof(rfile) THEN BEGIN nonblank:=0; IF redlog THEN write(lfile,'red ['); WHILE NOT eoln(rfile) DO BEGIN read(rfile,c); IF redlog THEN write(lfile,c); a:=ord(c); putbuf(redbuf,a); IF a <> ascsp THEN nonblank := redbuf.ln; END; IF redlog THEN writeln(lfile,']'); IF bstrip THEN redbuf.ln := nonblank; IF creol OR crlfeol THEN putbuf(redbuf,asccr); IF lfeol OR crlfeol THEN putbuf(redbuf,asclf); END; END; PROCEDURE redopn; {System dependent} VAR rstat: boolean; BEGIN rstatus:=closed; ruse:='R$F$I$L$E$$$'; readelt1(rfile,ruse,fname,rstat); IF rstat THEN rstatus:=open; redix:=-1; redbuf.ln:=-1; END; PROCEDURE redcls; BEGIN rstatus:=closed; END; {$F Build data portion of data packet} PROCEDURE getrec; VAR a: ascval; exit: BOOLEAN; prevln,previx,tix: INTEGER; BEGIN bufinit(filbuf); IF eof(rfile) THEN BEGIN rstatus:=endfile; END ELSE BEGIN exit:=false; REPEAT IF redix >= redbuf.ln THEN BEGIN redrec; {get another record and strip spaces} IF eof(rfile) THEN BEGIN exit:=true; IF filbuf.ln=0 THEN rstatus:=endfile; END; END; IF redix < redbuf.ln THEN BEGIN prevln:=filbuf.ln; previx:=redix; redix:=redix+1; a:=redbuf.ch[redix]; IF locrep<>0 THEN BEGIN tix:=redix+1; WHILE (a=redbuf.ch[tix]) AND (tix<=redbuf.ln) DO tix:=tix+1; tix:=tix-redix; {tix is now the repeat count} IF tix>3 THEN BEGIN IF tix>94 THEN tix:=94; putbuf(filbuf,locrep); putbuf(filbuf,makechar(tix)); redix:=redix-1+tix; END; END; IF (a>127) THEN BEGIN IF locqu8<>0 THEN putbuf(filbuf,locqu8); a:=tog128(a); END; IF (a<32) OR (a=ascdel) THEN BEGIN putbuf(filbuf,locquo); a:=tog64(a); END; IF (a=locquo) OR (a=locqu8) OR (a=locrep) THEN BEGIN putbuf(filbuf,locquo); END; putbuf(filbuf,a); IF filbuf.ln >= remdsiz THEN BEGIN exit:=true; IF filbuf.ln>remdsiz then BEGIN {Character expansion caused buffer length to be exceeded. Back up.} filbuf.ln:=prevln; redix:=previx; END; END; END; UNTIL exit; END; END; {$F Send states} PROCEDURE sendinitiate; BEGIN IF fnlen>0 THEN BEGIN redopn; IF rstatus=open THEN BEGIN putpar; {Put parameters into buffer} makepacket(ascs,seq,filbuf.ln); {Make packet with our parameters} numtry:=0; state:=sheader; END ELSE BEGIN error('Error opening read file'); state:=kexit; END; END ELSE BEGIN error('No read file specified'); state:=kexit; END; END; PROCEDURE sendheader; BEGIN IF rcvtyp=ascy THEN BEGIN IF not sndonly THEN getpar; {Get parameters from ACK of 'S' packet} IF rfnlen>0 THEN BEGIN lintobuf(rfname,rfnlen,filbuf); {Send remote file name.} END ELSE BEGIN lintobuf(fname,fnlen,filbuf); {Send local file name.} END; numtry:=0; seq:=(seq+1) mod 64; makepacket(ascf,seq,filbuf.ln); state:=sdata END; END; PROCEDURE senddata; BEGIN IF rcvtyp=ascy THEN BEGIN getrec; numtry:=0; seq:=(seq+1) mod 64; IF rstatus = open THEN BEGIN makepacket(ascd,seq,filbuf.ln); END ELSE BEGIN makepacket(ascz,seq,0); state:=sbreak; fnlen:=0; END; END; END; PROCEDURE sendbreak; BEGIN IF rcvtyp=ascy THEN BEGIN numtry:=0; seq:=(seq+1) mod 64; makepacket(ascb,seq,0); END; state:=wexit; END; {$F Receive states} PROCEDURE receiveinitiate; BEGIN IF rcvtyp=ascs THEN BEGIN getpar; {Get parameters from packet} putpar; {Put parameters into buffer} makepacket(ascy,seq,filbuf.ln); {Make ACK packet with our parameters} seq:=rcvseq; numtry:=0; seq:=(seq+1) mod 64; state:=rheader; END ELSE BEGIN error('Wrong packet in receive initiation'); state:=kexit; END; END; PROCEDURE receiveheader; BEGIN IF rcvtyp=ascf THEN BEGIN IF fnlen=0 THEN BEGIN buftolin(rcvbuf,fname,fnlen); END; IF fnlen>0 THEN BEGIN wrtopn; IF wstatus=open THEN BEGIN makepacket(ascy,seq,0); numtry:=0; seq:=(seq+1) mod 64; state:=rdata; END ELSE BEGIN error('Error opening write file'); state:=kexit; END; END ELSE BEGIN error('No output file specified'); state:=kexit; END; END ELSE IF rcvtyp=ascb THEN BEGIN makepacket(ascy,seq,0); sndpkt; state:=kexit; END ELSE BEGIN error('Wrong packet receiveing file header'); state:=kexit; END; END; PROCEDURE receivedata; BEGIN IF rcvtyp=ascd THEN BEGIN putrec(rcvbuf); makepacket(ascy,seq,0); numtry:=0; seq:=(seq+1) mod 64; END ELSE IF rcvtyp=ascz THEN BEGIN wrtcls; fnlen:=0; makepacket(ascy,seq,0); numtry:=0; seq:=(seq+1) mod 64; state:=rheader; END ELSE BEGIN error('Unexpected packet receiving data'); state:=kexit; END; END; {$F Error processing} {Process fatal errors} PROCEDURE error; {parameters appear above in forward reference} VAR i,l:integer; BEGIN l:=length(msg); IF l>maxbuf-6 THEN l:=maxbuf-6; bufinit(filbuf); FOR i:=1 to 3 DO putbuf(filbuf,ascsp); {Make message readable in packet} FOR i:=1 to l DO putbuf(filbuf,ord(msg[i])); FOR i:=1 to 3 DO putbuf(filbuf,ascsp); {Make message readable in packet} makepacket(asce,seq,filbuf.ln); sndpkt; state:=kexit; END; {$F Command state} PROCEDURE kermitcommand; BEGIN REPEAT rcvpkt; IF rcvseq>-1 THEN BEGIN IF rcvtyp=ascs THEN BEGIN state:=rinitiate; END ELSE IF rcvtyp=ascr THEN BEGIN IF fnlen=0 THEN BEGIN buftolin(rcvbuf,fname,fnlen); END; state:=sinitiate; END ELSE BEGIN error('Unexpected packet type'); END; END ELSE IF rcvseq=-1 THEN BEGIN writeln('No commands implemented'); END ELSE IF rcvseq=-2 THEN BEGIN state:=kexit; server:=false; END; UNTIL state<>kcommand; END; {$F Get processor call options and file specifications} PROCEDURE getoptions; {System dependent} BEGIN getspec(1,fname,fnlen); {Get local file name, if any.} getspec(2,rfname,rfnlen); {Get remote file name, if any.} IF 'S' IN options THEN state:=sinitiate; IF 'R' IN options THEN state:=rinitiate; IF 'T' IN options THEN BEGIN sndonly:=true; state:=sinitiate; server:=false; END; IF 'B' IN options THEN BEGIN locbsiz:=94; END; IF 'C' IN options THEN BEGIN crlfeol:=false; creol:=true; lfeol:=false; END; IF 'L' IN options THEN BEGIN sndlog:=true; rcvlog:=true; wrtlog:=true; redlog:=true; END; optqu8:=0; {Assume no eight-bit quoting will be done} IF 'Q' IN options THEN BEGIN optqu8:=ascamp; {Eight-bit quoting may be done} END; IF ('W' IN options) AND ('S' IN options) THEN BEGIN a1:=30000; er(48{TWAIT$},a0,a1); END; END; {$F Initialization state} PROCEDURE kermitinitialize; VAR lstat: boolean; BEGIN state:=kcommand; numtry:=0; seq:=0; fnlen:=0; {Indicate no file name yet} bstrip:=true; locbsiz:=78; loctout:=12; locnpad:=0; locpad:=0; loceol:=asccr; locquo:=ascns; { locqu8 will be set after options are processed. } locrep:=asctil; {Initialize to 0 to turn off repeat counts} rembsiz:=78; remdsiz:=rembsiz-3; remtout:=12; remnpad:=0; rempad:=0; remeol:=asccr; remqu8:=0; remrep:=0; bufinit(sndbuf); {The following should only be done on the first call to initialize} IF iniflg=false THEN BEGIN sndonly:=false; sndlog:=false; rcvlog:=false; wrtlog:=false; redlog:=false; crlfeol:=true; creol:=false; lfeol:=false; rstatus:=closed; wstatus:=closed; lstatus:=closed; {System dependent initialization} ineoln:=false; {Indicate no readln necessary for first line} getoptions; {Process options and file specifications} IF sndlog OR rcvlog OR wrtlog OR redlog THEN logopn END; locqu8:=optqu8; {Eight-bit quoting done only with Q-option} iniflg:=true; END; {$F Main block} BEGIN version:= '2.0'; writeln(output,'Kermit 1100 ',version); iniflg:=false; server:=true; WHILE server DO BEGIN kermitinitialize; IF state=kcommand THEN kermitcommand; IF state=sinitiate THEN sendinitiate; IF state=rinitiate THEN receiveinitiate; WHILE state<>kexit DO BEGIN REPEAT sndpkt; numtry:=numtry+1; IF sndonly THEN BEGIN rcvseq:=seq; rcvtyp:=ascy; rcvbuf.ln:=0; END ELSE BEGIN rcvpkt; END; IF rcvtyp=ascn THEN BEGIN {We have just received a NAK. The Kermit protocol would be much simpler and no less effective if the NAK had never been included. However, since this is not universally appreciated, one has to deal with them. To do so, we will convert a NAK into an ACK with the previous sequence number.} rcvseq:=(rcvseq-1) mod 64; rcvtyp:=ascy; END ELSE IF rcvseq=-2 THEN {End of file on input} BEGIN error('End of file on input data'); state:=kexit; server:=false; END; UNTIL (rcvseq=seq) OR (numtry>=maxtry) OR (state=kexit); IF (rcvseq<>seq) AND (state<>kexit) THEN BEGIN error('Failed to receive expected packet'); state:=kexit; END ELSE IF rcvtyp=asce THEN {Just received error packet} BEGIN state:=kexit END ELSE BEGIN CASE state OF sheader :sendheader; sdata :senddata; sbreak :sendbreak; rheader :receiveheader; rdata :receivedata; wexit :state:=kexit; {Go around one more time, then exit} kexit :; END; END END; wrtcls; END; logcls; writeln('Kermit End'); END .