$Debug off$ $UCSD ON$ $SYSPROG$ $SEARCH '*INTERFACE.', '*IO.', 'KRMIO', 'KRMWNDW', 'KRMRPT'$ $PAGE$ { Module KRMGUTS contains the heart of Kermit - the procedures, variables, etc., that actually implement the Kermit protocol. } module krmguts; import ascii_defs, byte_str, byte_io, err_codes, krmrpt, terminal, iodeclarations, general_1, general_3; export const MAXFILES = 10; { maximum number of files that can be sent } type filename_list = array[1..MAXFILES] of filename; var RunType : Transfer_type; { type of transfer currently in effect } Kermit_error : integer; { Error and status conditions left here } Kermit_error_string : string [80]; { operational parameters } Local : boolean; { local/remote flag } OneWayOnly : boolean; { used for testing } Verbosity: boolean; { true to print verbose messages } Debug : boolean; { true to print really verbose debugging msgs } PROCEDURE KermitInit; { initialize various parameters & defaults } PROCEDURE SYSInit; { system dependent initialization } PROCEDURE SYSFinish; { system dependent cleanup } PROCEDURE ParmInit; { initialize operating parameters } { Command entry points } procedure RecvSwitch( files : filename_list ); { Receive file group entry point } procedure SendSwitch( files : filename_list ); { Send file group entry point } procedure TN; { invokes terminal emulator } implement CONST {-%- System Dependent -%-} DEFPARMFILE = 'KERMIT.PRM'; TEMPFILE = 'TEMP.K'; abort_file_key = #X; { ^X aborts single file send } abort_group_key = #Z; { ^Z aborts file group send } { Default transmission parameter definitions. These are assigned to } { the transmission parameter variables by ParmInit when Kermit is } { first started. } DEFTRY = 10; { default for number of retries } DEFTIMEOUT = 12; { default time out } MAXPACK = 94; { max is 94 ~ - ' ' } DEFDELAY = 5; { delay before sending first init for send } NUMPARAM = 6; { number of parameters in init packet } DEFMARK = SOH; { packet start mark } DEFQUOTE = SHARP; { default quote character } DEFPAD = 0; { default number of padding chars } DEFPADCHAR = 0; { default padding character } DEFEOL = CR; { default end of line sequence } DEFEOLTYPE = 2; { 1 = LineFeed 2 = CrLf 3 = Just Cr } NUMBUFFERS = 5; { Number of buffers } { packet types } TYPEB = 66; { ord('B') break packet } TYPEC = 67; { ord('C') Host command packet } TYPED = 68; { ord('D') data packet } TYPEE = 69; { ord('E') error packet } TYPEF = 70; { ord('F') file header packet } TYPEG = 71; { ord('G') generic kermit command packet } TYPEN = 78; { ord('N') NAK packet } TYPER = 82; { ord('R') Receive init packet } TYPES = 83; { ord('S') send init packet } TYPET = 84; { ord('T') ? } TYPEX = 88; { ord('X') Text packet } TYPEY = 89; { ord('Y') ACK packet } TYPEZ = 90; { ord('Z') EOF packet } $PAGE$ TYPE { Data Types for Kermit } Packet = RECORD mark : byte; { SOH character } count: byte; { # of bytes following this field } seq : byte; { sequence number modulo 64 } ptype: byte; { d,y,n,s,b,f,z,e,t packet type } data : ByteString; { the actual data } { chksum is last valid char in data array } { eol is added, not considered part of packet proper } END; EOLtype = (LineFeed,CrLf,JustCr); Ppack = 1..NUMBUFFERS; CType = RECORD check : integer; { checksum summation counter } PacketPtr : integer; { points to next "raw" byte in data field } i : integer; { points to next cooked byte in data field } fld : integer; { packet field counter } t : byte; { raw byte received from remote } finished : boolean; { true if packet completely received } restart : boolean; good : boolean; END; $PAGE$ VAR keyboard : text; { non-echoing standard input file } ior : integer; { error recovery routine saves ioresult } { here } breakchar : byte; { break character for TN mode } ch : char; { scratch character } report : string[120]; { status report string } rpos : integer; { status report string position } { Variables for Kermit } ParmFile : filename; { parameter file name } DiskFile : filedesc; { file being sent or received } EOLforFile : EOLtype; { EOL sequence used for Kermit data } State : kermitstates; { current state of the automaton } SaveState : kermitstates; { holds old state for retries } n,J : integer; { packet sequence number } MaxTry : integer; { maximum number of retries allowed } NumTry : integer; { times this packet retried } OldTry : integer; { times last packet retried } { packet transmission parameters } LocalMark : integer; { packet start mark } RemoteMark : integer; LocalPad : integer; { number of padding characters I need } RemotePad : integer; { number of padding chars to send } LocalPadChar : byte; { padding character I need } RemotePadChar : byte; { padding character to use } LocalTimeOut : integer; { our timeout interval in seconds } RemoteTimeOut : integer; { their timeout interval in seconds } LocalDelay : integer; { delay before sending first init } LocalEOL,LocalQuote : byte; { parms. for us } RemoteEOL, RemoteQuote : byte; { parms. the remote wants } SizeRecv, SizeSend : integer; { buffer sizes for receive and send } { statistics variables } stats : kermit_statistics; { Packet buffers. These are used to hold packets being built as } { received, or assembled for transmission. } Buf : ARRAY [1..NUMBUFFERS] OF packet; ThisPacket : Ppack; { current packet being sent } LastPacket : Ppack; { last packet sent } CurrentPacket : Ppack; { current packet received } NextPacket : Ppack; { next packet being received } DebugPacket : Ppack; { save input to do debug } TOPacket : packet; { Time_Out Packet } TimeLeft : integer; { until Time_Out } PackControl : CType; { variables for receive packet routine } $PAGE$ PROCEDURE Verbose ( c : cstring ); { Print string c if verbosity Called by Field1 Field2 Field3 Field5 SendFile SendEOF SendBreak SendOurInit GetTheirInit ReceiveData } BEGIN IF Verbosity THEN begin setstrlen(report,0); strwrite(report, 1,rpos, c); report_log( report ); end; END; { procedure verbosity } $PAGE$ PROCEDURE PutErr( c : cstring ); { Print error messages. } BEGIN IF Local THEN begin setstrlen(report,0); strwrite(report,1,rpos,c); report_status(report); report_log(report); end; END; { procedure PutErr } $PAGE$ PROCEDURE OverHead ( p , f : integer; VAR o : integer ); { Calculate OverHead as % OverHead := (p-f)*100/f Called by DisplayStatistics } BEGIN IF f <> 0 then o := trunc((p-f)*100/f) else o := 0; END; $PAGE$ PROCEDURE CalRat ( f : integer; t : integer; VAR r : integer ); { Calculate Effective Baud Rate Rate = f*10/t Called by DisplayStatistics } BEGIN r := 0; END; $PAGE$ PROCEDURE Sleep ( t : integer); { pause for t seconds } { Called by SendSwitch } BEGIN END; $PAGE$ PROCEDURE StartTimer; { Called by ReceivePacket } BEGIN TimeLeft := RemoteTimeOut; END; $PAGE$ PROCEDURE StopTimer; { Called by ReceivePacket } BEGIN TimeLeft := MaxInt; END; $PAGE$ FUNCTION MakeChar ( c : byte ) : byte; { Convert integer to printable character. } BEGIN MakeChar := c+BLANK; END; $PAGE$ FUNCTION UnChar ( c : byte ) : byte; { Reverse of MakeChar } BEGIN UnChar := c-BLANK END; $PAGE$ FUNCTION Ctl ( c : byte ) : byte; { Does c XOR 100. } BEGIN IF IsControl(c) THEN c := c+64 ELSE c := c-64; Ctl := c; END; $PAGE$ FUNCTION IsValidPType ( c : byte ) : boolean; { True if c is a valid packet type. Called by Field3 } BEGIN IsValidPType := c in [TYPEB, TYPEC, TYPED, TYPEE, TYPEF, TYPEG, TYPEN, TYPER, TYPES, TYPET, TYPEX, TYPEY, TYPEZ] END; $PAGE$ FUNCTION CheckFunction ( c : integer ) : byte; { Calculate checksum Called by SendPacket Field5 } VAR x: integer; BEGIN { CheckFunction := (c + ( c AND 300 ) /100 ) AND 77; } x := (c MOD 256 ) DIV 64; x := x+c; CheckFunction := x MOD 64; END; $PAGE$ PROCEDURE EnCodeParm ( VAR data : ByteString ); { encode parameters } { Encodes the global parameter variables and places them into the given parameter ByteString. References : SizeRecv LocalTimeOut LocalPad LocalPadChar LocalEOL LocalQuote Called by SendOurInit GetTheirInit DoInitLast } VAR i: integer; BEGIN FOR i:=1 TO NUMPARAM DO data[i] := BLANK; data[NUMPARAM+1] := ENDSTR; data[1] := MakeChar(SizeRecv); { my biggest packet } data[2] := MakeChar(LocalTimeOut); { when I want timeout} data[3] := MakeChar(LocalPad); { how much padding } data[4] := Ctl(LocalPadChar); { my padding character } data[5] := MakeChar(LocalEOL); { my EOL } data[6] := LocalQuote; { my quote char } END; $PAGE$ PROCEDURE DeCodeParm ( VAR data : ByteString ); { decode parameters } { Accepts a parameter string, decodes the values, and places them in the global parameter variables. Modifies : SizeSend RemoteTimeOut RemotePad RemotePadChar RemoteEOL RemoteQuote Called by GetTheirInit } BEGIN SizeSend := UnChar(data[1]); RemoteTimeOut := UnChar(data[2]); { when I should time out } RemotePad := UnChar(data[3]); { padding characters to send } RemotePadChar := Ctl(data[4]); { padding character } RemoteEOL := UnChar(data[5]); { EOL to send } RemoteQuote := data[6]; { quote to send } END; $PAGE$ PROCEDURE ReadParm ( VAR Parms : ByteString ; ParmFile : filename ); { Opens the parameter file, if any, and reads a single line from it into the parameter Parms. If no parameter file exists, returns a null string (i.e., just ENDSTR in the first position). Inputs : ParmFile filename of parameter file Calls Exists Sopen GetLine Called by GetParm } VAR dummy : boolean; fd : filedesc; BEGIN; Parms[1]:=ENDSTR; IF Exists(ParmFile) THEN BEGIN fd := Sopen(ParmFile,IOREAD); dummy := GetLine(Parms,fd,MAXSTR); Sclose(fd); END; END; $PAGE$ PROCEDURE GetParm( ParmFile : filename ); { get parameters from file } { Reads a line from the parameter file via ReadParm and sets the global parameter variables according to the values in the file. Inputs : ParmFile filename of parameter file Modifies SizeRecv LocalTimeOut LocalPad LocalPadChar LocalEOL LocalQuote Calls ReadParm Called by ParmInit SetParameters } VAR data : ByteString; BEGIN; ReadParm(data, ParmFile); IF (length(data) > 0) THEN { get parameters } BEGIN SizeRecv := UnChar(data[1]); LocalTimeOut := UnChar(data[2]); { when I should time out } LocalPad := UnChar(data[3]); { padding characters to send } LocalPadChar := Ctl(data[4]); { padding character } LocalEOL := UnChar(data[5]); { EOL to send } LocalQuote := data[6]; { quote to send } END; END; $PAGE$ PROCEDURE ParmInit; { Initializes transmission parameters (pad character, timeout, etc.) to their default values as defined by the default parameter constants, then reads any new values from the parameter file. Parameter file values thus override the initial 'hardwired' defaults. Calls GetParm Called by Main Program } BEGIN breakchar:=CTRLY; { Set the initial default values } RemotePad := DEFPAD; LocalPad := DEFPAD; RemotePadChar := DEFPADCHAR; LocalPadChar := DEFPADCHAR; LocalMark := DEFMARK; RemoteTimeOut := DEFTIMEOUT; LocalTimeOut := DEFTIMEOUT; LocalDelay := DEFDELAY; SizeRecv := MAXPACK; SizeSend := MAXPACK; RemoteEOL := DEFEOL; LocalEOL := DEFEOL; RemoteQuote := DEFQUOTE; LocalQuote := DEFQUOTE; MaxTry := DEFTRY; CASE DEFEOLTYPE OF 1: EOLforFile := LineFeed; 2: EOLforFile := CrLf; 3: EOLforFile := JustCR; END { case }; { Now read the new defaults from the parameter file } ParmFile := DEFPARMFILE; GetParm( ParmFile ); Local := true; { default to local } END; $PAGE$ {-%- System Dependent -%-} PROCEDURE SYSInit; { Performs system dependent initialization, for example setting the mode of the console terminal. Called once by the main program when Kermit is started. Called by Main Program } begin { procedure SYSInit } ioinitialize; initio; { initialize the byte I/O module } init_data_comm; end; { procedure SYSInit } $PAGE$ {-%- System Dependent -%-} PROCEDURE SYSFinish; { Performs any system dependent cleanup operations, for example resetting the mode of the console terminal to normal. Called once by main program just before Kermit exits. Called by Main Program } BEGIN iouninitialize; END; $PAGE$ PROCEDURE StartRun; { initialization for transaction } { Called just before a transaction is started. Modifies stats.RunTime Calls SerialFlush Called by SendSwitch RecvSwitch } BEGIN SerialFlush; stats.RunTime := 0; END; $PAGE$ { Function DoBreakchar is the break character action routine passed to the procedure emulator when in TN mode. The break character command (i.e., the character typed immediately after the break character) is passed as the argument. If it returns true, the emulator will exit back to its caller. } function DoBreakchar ( c : char ) : boolean; begin DoBreakchar := false; case c of 'c','C': DoBreakchar := true; otherwise begin writeln('Break character commands:'); writeln(' C Break connection'); writeln(' ? This message'); end; end; { case } end; { procedure DoBreakchar } PROCEDURE TN; { This procedure implements the 'terminal emulator' to connect to the host. Calls emulator Called by Main program } BEGIN { procedure TN } write(#12); writeln('Connecting to host'); emulator( chr(CTRLY), DoBreakchar ); write(#12); END; { procedure TN } $PAGE$ PROCEDURE SetParameters( arg : filename ); { Sets new parameter file name, loads new parameters via GetParm. Implicit inputs : Arg filename of file from which to read new parameters Calls GetParm Called by Main Program (invoked by load new parameters command) } var fnm : filename; BEGIN IF (strlen(Arg) > 2) THEN BEGIN ParmFile := arg; { get the new parameter file } { name from the command line } { into ParmFile } GetParm( ParmFile ); { read new parameters } END; END; $PAGE$ PROCEDURE KermitInit; { initialize various parameters & defaults } { Initializes the KERMIT protocol machine and sets the option variables to default values. Calls Called by Main program } BEGIN n := 0; stats.NumSendPacks := 0; stats.NumRecvPacks := 0; stats.NumACKsent := 0; stats.NumNAKsent := 0; stats.NumACKrecv := 0; stats.NumNAKrecv := 0; stats.NumBADrecv := 0; stats.ChInPack := 0; stats.ChInFile := 0; RunType := invalid; DiskFile := IOERROR; { to indicate not open yet } ThisPacket := 1; LastPacket := 2; CurrentPacket := 3; NextPacket := 4; DebugPacket := 5; WITH TOPacket DO BEGIN count := 3; seq := 0; ptype := TYPEN; data[1] := ENDSTR; END; { with } END; { procedure KermitInit } $PAGE$ {-%- System Dependent -%-} procedure FinishUpFile; { clean up the open file } { Called by ErrorPack BuildPacket ReceivePacket } begin Sclose(DiskFile); end; { procedure FinishUpFile } $PAGE$ PROCEDURE DisplayStatistics; { Calls OverHead CalRat Called by ErrorPack BuildPacket ReceivePacket } BEGIN IF ((RunType <> Invalid) AND Local ) THEN with stats do BEGIN OverHead(ChInPack,ChInFile,packet_overhead); CalRat(ChInFile,RunTime,effrate); report_packet_statistics( stats, runtype ); END; { with } END; { procedure DisplayStatistics } $PAGE$ PROCEDURE DisplayPacket ( mes : cstring; VAR p : Ppack ); { where mes = string to be printed preceding packet contents p = index into buf of packet to be displayed Print Debugging Info. Prints the given message on the standard error device, followed by the contents of the given packet as follows: Called by ReSendPacket SendPacket BuildPacket } BEGIN WITH Buf[p] DO BEGIN setstrlen(report,0); strwrite(report,1,rpos, mes, UnChar(count):3, UnChar(seq):3, chr(ptype):3); report_log( report ); BtoS(data, report); report_log( report ); END; { with } END; { procedure DisplayPacket } $PAGE$ PROCEDURE PutOut ( p : Ppack ); { Output Packet } { where p = index into buf of packet to be sent Outputs the given packet, preceded by RemotePad padding characters, to the serial line. Calls Putcf PutCon PutStr Called by ReSendPacket SendPacket } VAR i : integer; BEGIN IF (RemotePad > 0) THEN FOR i := 1 TO RemotePad DO Putcf(RemotePadChar,LineOut); WITH Buf[p] DO BEGIN report_send_packet(UnChar(seq)); { report which packet we're sending } Putcf(mark,LineOut); Putcf(count,LineOut); Putcf(seq,LineOut); Putcf(ptype,LineOut); PutStr(data,LineOut); END; { with } END; { procedure PutOut } $PAGE$ PROCEDURE ReSendPacket; { Re-sends previous packet, which had been renamed to Buf[LastPacket] by SendPacket just after that routine sent it. Modifies stats.ChInPack stats.NumSendPacks Calls PutOut Called by SendPacket } BEGIN stats.NumSendPacks := stats.NumSendPacks+1; stats.ChInPack := stats.ChInPack + RemotePad + UnChar(Buf[LastPacket].count) + 3; IF Debug THEN DisplayPacket('Re-Sending ... ',LastPacket); PutOut(LastPacket); END; $PAGE$ PROCEDURE SendPacket; { sends ThisPacket; leaves it in LastPacket } { Accepts "raw" packet in Buf[ThisPacket]. Encodes count (which is initially the length of the data field), sequence number, and calculates the checksum. After packet is sent, exchanges ThisPacket and LastPacket by swapping pointers, so that ReSendPacket can send it again if necessary. Modifies stats.ChInPack Calls PutOut ReSendPacket CheckFunction DisplayPacket Called by SendACK SendNAK ErrorPack SendFile SendData SendEOF SendBreak SendOurInit GetTheirInit DoInitLast } VAR i,len,chksum : integer; temp : Ppack; BEGIN IF (NumTry <> 1) AND (RunType = Transmit ) THEN ReSendPacket ELSE BEGIN { send fresh packet } WITH Buf[ThisPacket] DO BEGIN mark := LocalMark; { mark } len := count; { save length } count := MakeChar(len+3); { count = 3+length of data } seq := MakeChar(seq); { seq number } chksum := count + seq + ptype; IF ( len > 0) { is there data ? } THEN FOR i:= 1 TO len DO chksum := chksum + data[i]; { loop for data } chksum := CheckFunction(chksum); { calculate checksum } data[len+1] := MakeChar(chksum); { make printable & output } data[len+2] := RemoteEOL; { EOL } data[len+3] := ENDSTR; END; { WITH } stats.NumSendPacks := stats.NumSendPacks+1; IF Debug THEN DisplayPacket('Sending ... ',ThisPacket); PutOut(ThisPacket); IF RunType = Transmit THEN BEGIN stats.ChInPack := stats.ChInPack + RemotePad + len + 6; temp := LastPacket; LastPacket := ThisPacket; ThisPacket := temp; END; END; { send fresh packet } END; { procedure SendPacket } $PAGE$ PROCEDURE SendACK ( n : integer ); { send ACK packet } { Builds an ACK packet for the given sequence number in Buf[ThisPacket] and sends it. Modifies stats.NumACKsent Buf[ThisPacket] Calls SendPacket Called by BuildPacket DoData DoEOF DoBreak DoFile DoEOFLast } BEGIN WITH Buf[ThisPacket] DO BEGIN count := 0; seq := n; ptype := TYPEY; END; SendPacket; stats.NumACKsent := stats.NumACKsent+1; END; $PAGE$ PROCEDURE SendNAK ( n : integer ); { send NAK packet } { Builds a NAK packet for the given sequence number into Buf[ThisPacket] and sends it. Modifies stats.NumNAKsent Buf[ThisPacket] Calls SendPacket Called by GetTheirInit DoData DoFileLast DoEOF DoBreak DoFile DoEOFLast DoInitLast ReceiveFile } BEGIN WITH Buf[ThisPacket] DO BEGIN count := 0; seq := n; ptype := TYPEN; END; SendPacket; stats.NumNAKsent := stats.NumNAKsent+1; END; $PAGE$ PROCEDURE ErrorPack ( c : cstring ); { where c = Error description string to be printed or sent in data field of packet Sends an error packet to the other Kermit with the error string in the data field. Calls PutErr SendPacket Called by GetFile ReceivePacket } BEGIN WITH Buf[ThisPacket] DO BEGIN seq := n; ptype := TYPEE; CtoB(c,data); count := length(data); END; { with } SendPacket; FinishUpFile; DisplayStatistics; END; $PAGE$ PROCEDURE Field1; { Count } { Checks the count field assumed to be in PackControl.t, sets the count field in Buf[DebugPacket] to t itself, and the count field in Buf[NextPacket] to UnChar(t). If the count is not within the proper range, a message will be printed via Verbose and PackControl.good will be set FALSE; otherwise, PackControl.good will be unchanged. References SizeRecv Modifies Buf[NextPacket] Buf[DebugPacket] PackControl Calls Verbose Called by BuildPacket } VAR test: boolean; BEGIN WITH Buf[NextPacket] DO BEGIN WITH PackControl DO BEGIN Buf[DebugPacket].count := t; count := UnChar(t); test := (count >= 3) OR (count <= SizeRecv-2); IF NOT test THEN Verbose('Bad count '); good := good AND test; END; { with PackControl } END; { with NextPacket } END; { procedure Field1 } $PAGE$ PROCEDURE Field2; { Packet Number } { Checks the sequence number field assumed to be in PackControl.t, sets the sequence number field in Buf[DebugPacket] to t itself, and the sequence number field in Buf[NextPacket] to UnChar(t). If the sequence number is not within the proper range, a message will be printed via Verbose and PackControl.good will be set FALSE; otherwise, PackControl.good will be unchanged. Modifies Buf[NextPacket] Buf[DebugPacket] PackControl Calls Verbose Called by BuildPacket } VAR test : boolean; BEGIN WITH Buf[NextPacket] DO BEGIN WITH PackControl DO BEGIN Buf[DebugPacket].seq := t; seq := UnChar(t); test := (seq >= 0) OR (seq <= 63); IF NOT test THEN Verbose('Bad seq number '); good := test AND good; END; END; END; $PAGE$ PROCEDURE Field3; { Packet Type } { Checks the type field assumed to be in PackControl.t, sets the type field in Buf[DebugPacket] and in Buf[NextPacket] to PackControl.t. If the type is not a valid packet type, a message will be printed via Verbose and PackControl.good will be set FALSE; otherwise, PackControl.good will be unchanged. Modifies Buf[NextPacket] Buf[DebugPacket] PackControl Calls Verbose IsValidPType Called by BuildPacket } VAR test : boolean; BEGIN WITH Buf[NextPacket] DO BEGIN WITH PackControl DO BEGIN ptype := t; Buf[DebugPacket].ptype := t; test := IsValidPType(ptype); IF NOT test THEN Verbose('Bad Packet Type '); good := test AND good; END; END; END; $PAGE$ PROCEDURE Field4; { Data } { Places the data character, assumed to be in PackControl.t, into the next position in Buf[DebugPacket].data. This position is assumed to be in PackControl.PacketPtr. Does the proper unquoting, and leaves the unquoted character in the next position of Buf[NextPacket].data. Modifies Buf[NextPacket] Buf[DebugPacket] PackControl Calls -nothing- Called by BuildPacket } BEGIN WITH PackControl DO BEGIN PacketPtr := PacketPtr+1; Buf[DebugPacket].data[PacketPtr] := t; Buf[NextPacket].data[i] := t; i := i + 1; END; { with PackControl } END; { procedure Field4 } $PAGE$ PROCEDURE Field5; { Check Sum } { Places the checksum character, assumed to be in PackControl.t, followed by a terminator, into the next position of Buf[DebugPacket].data. Calls CheckFunction to verify the checksum; if the checksum accumulated for the data does not match the one sent, then outputs an error message via Verbose and sets Good to FALSE, otherwise Good is unchanged. Sets the PackControl.finished. Modifies Buf[NextPacket] Buf[DebugPacket] PackControl Calls Verbose CheckFunction Called by BuildPacket } VAR test : boolean; BEGIN WITH PackControl DO BEGIN PacketPtr := PacketPtr +1; Buf[DebugPacket].data[PacketPtr] := t; Buf[DebugPacket].data[PacketPtr + 1] := ENDSTR; check := CheckFunction(check); check := MakeChar(check); test := (t=check); IF NOT test THEN Verbose('Bad CheckSum '); good := test AND good; Buf[NextPacket].data[i] := ENDSTR; finished := true; { set finished } END; END; $PAGE$ PROCEDURE BuildPacket; { Process received character } { Processes received character, assumed to be in PackControl.t, and adds it to the packet in Buf[NextPacket] according to the state information in PackControl. When the packet is completely received, the packet is checked to see if it is an error packet. If the packet is an error packet, Kermit_error_string will be set to the error packet text, and kermit_error will be set to abort_errpack. FinishUpFile and DisplayStatistics will be called. Returns one of the following error codes in Kermit_error: success Character successfully processed abort_errpack Error packet received from remote Modifies PackControl Buf[NextPacket] CurrentPacket NextPacket stats.NumRecvPacks Calls Field1 Field2 Field3 Field4 Field5 SendACK DisplayPacket Called by ReceivePacket } VAR temp : Ppack; BEGIN kermit_error := success; WITH PackControl DO BEGIN WITH Buf[NextPacket] DO BEGIN IF (t<>ENDSTR) { if a character was read } THEN IF restart THEN BEGIN { read until we get SOH marker } IF (t = SOH) THEN BEGIN { is packet mark } finished := false; { set variables } good := true; seq := -1; { set return values to bad packet } ptype := QUESTION; data[1] := ENDSTR; data[MAXSTR] := ENDSTR; restart := false; fld := 0; i := 1; PacketPtr := 0; check := 0; END; { is packet mark } END { read until we get SOH marker } ELSE BEGIN { have started packet } IF (t=SOH) { check for restart or EOL } THEN restart := true ELSE IF (t=LocalEOL) THEN BEGIN finished := true; good := false; END ELSE BEGIN { not mark or EOL } CASE fld OF { increment field number } 0: fld := 1; 1: fld := 2; 2: fld := 3; 3: { no data } IF (count=3) THEN fld := 5 ELSE fld := 4; 4: { end of data } IF (PacketPtr>=count-3) THEN fld := 5; END { case }; IF (fld<>5) THEN { add into checksum } check := check+t; CASE fld OF 1: Field1; 2: Field2; 3: Field3; 4: Field4; 5: Field5; END; { case } END; { not mark or EOL } END; { have started packet } IF finished THEN BEGIN IF Debug THEN BEGIN DisplayPacket('Received ... ',DebugPacket); IF good THEN report := 'Packet is Good' ELSE report := 'Packet is BAD'; report_log(report); END; { debug } IF (ptype=TYPEE) AND good THEN BEGIN { was error packet } Kermit_error := abort_errpack; BtoS(data, Kermit_error_string); SendACK(n); { send ACK } END; { was error packet } stats.NumRecvPacks := stats.NumRecvPacks+1; temp := CurrentPacket; CurrentPacket := NextPacket; NextPacket := temp; END; { if finished } END; { with Buf[NextPacket] } END; { with PackControl } END; { procedure BuildPacket } $PAGE$ procedure ReceivePacket; { Receives a packet into Buf[NextPacket], which is then renamed to Buf[CurrentPacket] when complete. If the packet is not successfully received, then FinishUpFile will be called. Returns one of the following codes in Kermit_error : success Packet successfully received timeout Timeout while waiting for complete packet abort_file Abort file key typed by user abort_group Abort file group typed by user abort_errpack Error packet received from remote References PackControl Modifies stats.ChInPack Calls SerialIn ConsoleStatus ConsoleIn BuildPacket Called by ReceiveACK GetTheirInit ReceiveData ReceiveFile } label 1000; { go to this when error occurs } var c : char; BEGIN kermit_error := success; { assume success for now } WITH PackControl DO BEGIN StartTimer; finished := false; restart := true; REPEAT t := SerialIn; IF (RunType = Receive) AND (t <> ENDSTR) THEN stats.ChInPack := stats.ChInPack + 1; IF Local { see if character typed on console } THEN if consolestatus then begin { if a character was typed } c := consolein; { read it } if c in [abort_file_key, abort_group_key] then begin { abort file } if c = abort_file_key then kermit_error := abort_file else kermit_error := abort_group; good := false; goto 1000; end { abort file } else t := LocalEOL; END; { if a character was typed } BuildPacket; if Kermit_error <> success then goto 1000; { return this error to caller } UNTIL finished OR (TimeLeft = 0); IF (TimeLeft = 0) { if timed out waiting for packet } THEN BEGIN Buf[CurrentPacket] := TOPacket; restart := true; IF NOT ((RunType=Transmit) AND (State=RecvInit)) THEN BEGIN Kermit_error := timeout; END; END; 1000: If kermit_error <> success then FinishUpFile; if (Kermit_error = abort_file) or (Kermit_error = abort_group) then ErrorPack('Transfer aborted '); StopTimer; DisplayStatistics; END; { with PackControl } END; { procedure ReceivePacket } $PAGE$ FUNCTION ReceiveACK : boolean; { Receive ACK with correct number } { If OneWayOnly is set, then returns TRUE immediately. Receives a packet into CurrentPacket. If it is not received correctly, will return FALSE and the NumXXXRecv counters will be invalid (!?). Otherwise, if it is an ACK packet, increments stats.NumACKrecv. If it is an ACK packet, increments stats.NumNAKrecv. If it is any other type, increments stats.NumBADrecv. If it is an ACK packet and the sequence number number matches the one expected, then will return TRUE. Errors errors returned by ReceivePacket Modifies stats.NumACKrecv stats.NumNAKrecv stats.NumBADrecv Calls ReceivePacket Called by SendFile SendData SendEOF SendBreak SendOurInit } VAR Ok: boolean; BEGIN kermit_error := success; if onewayonly then ReceiveACK := true else begin { look for ACK from remote } ReceivePacket; if not odd(Kermit_error) { if ReceivePacket returned error } then ReceiveACK := false { error receiving packet } else WITH Buf[CurrentPacket] DO BEGIN { packet received ok } IF (ptype=TYPEY) THEN stats.NumACKrecv := stats.NumACKrecv+1 ELSE IF (ptype=TYPEN) THEN stats.NumNAKrecv := stats.NumNAKrecv+1 ELSE stats.NumBADrecv := stats.NumBADrecv +1; { was this packet the one we expected? } ReceiveACK := (ptype=TYPEY) AND (n=seq); END; { packet received ok } end; { look for ACK from remote } END; { function ReceiveACK } $PAGE$ PROCEDURE DataFromFile ( VAR newstate : KermitStates ); { Get data from file into ThisPacket } { Fills the data field of Buf[ThisPacket] with characters from DiskFile, which is assumed to be opened. Characters are read from file via Getcf. The field is terminated by ENDSTR, and the count, sequence and packet type fields are set. If EOF is reached, the file is closed, and newstate and SaveState are set to FileData. Otherwise, newstate is set to whatever SaveState is, and SaveState is left unchanged. References Diskfile Modifies SaveState Buf[ThisPacket] stats.ChInFile Calls Sclose Getcf Called by SendData } VAR x,c : byte; i: integer; BEGIN IF (NumTry=1) { if first time packet sent } THEN BEGIN i := 1; x := ENDSTR; WITH Buf[ThisPacket] DO BEGIN { leave room for quote & NEWLINE } WHILE (i< SizeSend - 8 ) AND (x <> ENDFILE) DO begin x := Getcf(c,DiskFile); { get character and quote if necessary } IF (x<>ENDFILE) THEN IF (IsControl(x)) OR (x=RemoteQuote) THEN BEGIN { control char -- quote } IF (x=NEWLINE) THEN CASE EOLforFile OF { use proper EOL } LineFeed: { ok as is }; CrLf: BEGIN data[i] := RemoteQuote; i := i+1; data[i] := Ctl(CR); i := i+1; { LF will be put in below } END; { CrLf } JustCR: x := CR; END { case }; data[i] := RemoteQuote; i := i+1; IF (x<>RemoteQuote) THEN data[i] := Ctl(x) ELSE data[i] := RemoteQuote; END { control char } ELSE data[i] := x; { it's regular char } IF (x<>ENDFILE) THEN BEGIN i := i+1; { increase count for next char } stats.ChInFile := stats.ChInFile + 1; END; END; { get character and quote if necessary } data[i] := ENDSTR; { terminate ByteString } count := i-1; { set data fieldlength } seq := n; { set sequence number } ptype := TYPED; { set packet type } IF (x=ENDFILE) THEN BEGIN newstate := EOFile; Sclose(DiskFile); DiskFile := ioerror; END ELSE newstate := FileData; SaveState := newstate; { save state } END { with Buf[ThisPacket] do } END { if first time packet sent } ELSE newstate := SaveState; { get old state } END; { procedure DataFromFile } $PAGE$ PROCEDURE SendFile( name : filename ); { send file name packet } { Sends file header packet for the named file. If file does not exist, returns cant_find_file. If file cannot be opened, returns cant_read_file. If attempt to send header fails, leaves state set to FileHeader. If the attempt fails more than MaxTry times, sets state to Abort and returns retry_exhausted. If the file header is succesfully sent (ACKed by other side), sets state to FileData and returns success. Errors Retry Count Exhausted cant_find_file cant_read_file References MaxTry Modifies Buf[ThisPacket] NumTry State n Calls PutErr Verbose SendPacket ReceiveACK Called by SendSwitch } var num : integer; BEGIN Kermit_error := success; IF NumTry > MaxTry THEN BEGIN { retry count exhausted } PutErr ('Send file - Too Many'); Kermit_error := retry_exhausted; State := Abort; { too many tries, abort } END { retry count exhausted } ELSE BEGIN { Open the file and send file header } IF Exists(name) THEN with Buf[ThisPacket] do begin { File already exists. Open it, set up ThisPacket with name of file in data field. Show filename in file status display, send error packet if can't open file. } DiskFile := Sopen(name,IOREAD); count := strlen(name); { set packet length } StoB(name, data); { convert name to Bytestring } { in data field of packet } report_send_file(name); stats.ChInFile := stats.ChInFile + count; seq := n; ptype := TYPEF; IF DiskFile <= IOERROR THEN Kermit_error := cant_read_file; END { file already exists (with) } ELSE begin { file does not exist } kermit_error := cant_find_file; end; { file does not exist } NumTry := NumTry+1; IF Verbosity THEN begin { report sending file header } IF (NumTry = 1) { If first time we're sending file header } THEN num := Buf[ThisPacket].seq ELSE num := Buf[LastPacket].seq; setstrlen(report,0); strwrite(report,1,rpos,'Sending file header packet #', num:1,' for ',name:1); report_log(report); end; { report sending file header } SendPacket; { send this packet } IF ReceiveACK THEN BEGIN NumTry := 0; { reset packet retry count } State := FileData; n := (n+1) MOD 64; END END; { send file header } END; { procedure SendFile } $PAGE$ PROCEDURE SendData; { send file data packets } { Errors Retry Count Exhausted References MaxTry Modifies NumTry State n Calls PutCon PutNum PutErr DataFromFile SendPacket ReceiveACK Called by SendSwitch } VAR newstate: KermitStates; BEGIN IF Verbosity THEN BEGIN setstrlen(report,0); strwrite(report,1,rpos,'Sending data packet #',n:1); report_log(report); END; IF NumTry > MaxTry THEN BEGIN State := Abort; { too many tries, abort } PutErr ('Send data - Too many'); END ELSE BEGIN { send data packet } NumTry := NumTry+1; DataFromFile(newstate); SendPacket; IF ReceiveACK THEN BEGIN { got acknowledgement } State := newstate; NumTry := 0; n := (n+1) MOD 64; END; { got acknowledgement } END; { send data packet } END; { procedure SendData } $PAGE$ PROCEDURE SendEOF; { send EOF packet } { References MaxTry Modifies Buf[ThisPacket] NumTry State n Calls Verbose SendPacket ReceiveACK Called by SendSwitch } BEGIN Verbose ('Sending EOF '); IF NumTry > MaxTry THEN BEGIN State := Abort; { too many tries, abort } PutErr('Send EOF - Too Many '); END ELSE BEGIN { send EOF packet } NumTry := NumTry+1; IF (NumTry = 1) THEN BEGIN { if first time packet sent } WITH Buf[ThisPacket] DO BEGIN ptype := TYPEZ; seq := n; count := 0; END { with } END; { if first time packet sent } SendPacket; IF ReceiveACK THEN BEGIN { got acknowledgement } State := FileHeader; NumTry := 0; n := (n+1) MOD 64; END; { got acknowledgement } END; { send EOF packet } END; { procedure SendEOF } $PAGE$ PROCEDURE SendBreak; { send break packet } { Sends a break packet. If ACKed by other side, sets state to Complete. If not, leaves state set to Break, returns success. However, if the failure exhausted the retry count, sets state to Abort and returns retry_exhausted. Errors retry_exhausted References MaxTry Modifies Buf[ThisPacket] NumTry State n Calls Verbose PutErr SendPacket ReceiveACK Called by SendSwitch } BEGIN Kermit_error := success; Verbose ('Sending break '); IF NumTry > MaxTry THEN BEGIN State := Abort; { too many tries, abort } PutErr('Send break -Too Many'); Kermit_error := retry_exhausted; END ELSE BEGIN { send break packet } NumTry := NumTry+1; { make up packet } IF NumTry = 1 THEN BEGIN WITH Buf[ThisPacket] DO BEGIN ptype := TYPEB; seq := n; count := 0; END END; { with } SendPacket; { send this packet } IF ReceiveACK THEN State := Complete; END; { send break packet } END; { procedure SendBreak } $PAGE$ PROCEDURE SendOurInit; { send init packet } { Send our init packet to the remote, get its init packet, set the remotexxxx parameters from it. References MaxTry OneWayOnly Modifies Buf[ThisPacket] Buf[CurrentPacket] NumTry State n SizeSend RemoteTimeOut RemotePad RemotePadChar RemoteEOL RemoteQuote Calls Verbose PutErr EnCodeParm SendPacket ReceiveACK Called by SendSwitch } BEGIN Verbose ('Sending init '); IF NumTry > MaxTry THEN BEGIN State := Abort; { too many tries, abort } PutErr('Cannot Initialize '); END ELSE BEGIN { send our send init packet } NumTry := NumTry+1; IF (NumTry = 1) THEN BEGIN { if first time packet sent } WITH Buf[ThisPacket] DO BEGIN EnCodeParm(data); count := NUMPARAM; seq := n; ptype := TYPES; END { with } END; { if first time packet sent } SendPacket; { send this packet } IF ReceiveACK THEN BEGIN { got acknowledgment } WITH Buf[CurrentPacket] DO BEGIN IF OneWayOnly { use same data if test mode } THEN data := Buf[LastPacket].data; SizeSend := UnChar(data[1]); RemoteTimeOut := UnChar(data[2]); RemotePad := UnChar(data[3]); RemotePadChar := Ctl(data[4]); RemoteEOL := CR; { default to CR } IF (length(data) >= 5) THEN IF (data[5] <> 0) THEN RemoteEOL := UnChar(data[5]); RemoteQuote := DEFQUOTE; IF (length(data) >= 6) THEN IF (data[6] <> 0) THEN RemoteQuote := data[6]; END; { with } State := FileHeader; NumTry := 0; n := (n+1) MOD 64; END; { got acknowledgement } END; { send our send init packet } END; { procedure SendOurInit } $PAGE$ PROCEDURE SendSwitch( files : filename_list); { Send-switch is the state table switcher for sending files. It loops until either it is finished or a fault is encountered. Routines called by SendSwitch are responsible for changing the state. If an error does occur, Kermit_error is left set to the value put there by the routine that detected the error. References OneWayOnly Modifies State NumTry Calls Sleep StartRun SendData SendFile SendEOF SendOurInit SendBreak Called by Main Program } var nf : integer; done : boolean; BEGIN RunType := Transmit; State := SendInit; { send initiate is the start state } NumTry := 0; { say no tries yet } init_packet_display(runtype); IF (NOT OneWayOnly) THEN Sleep(LocalDelay); nf := 1; { point to first filename } StartRun; done := false; while (not done) do begin CASE State OF FileData: SendData; { data-send state } FileHeader: if strlen(files[nf]) = 0 { if no more files to send } then state := Break else SendFile(files[nf]); { send file name in header } EOFile: begin nf := nf + 1; { point to next file name } SendEOF; { send end-of-file } end; SendInit: SendOurInit; { send initialize } Break: SendBreak; { send break } Complete: { nothing }; Abort: { nothing }; END { case }; done := (State = Abort) OR (State=Complete) or not odd(kermit_error); end; { while } clean_packet_display(runtype); END; $PAGE$ PROCEDURE GetFile ( data : bytestring ); { Creates file with name given by the bytestring data. Assigns it to file descriptor diskfile. References Verbosity Modifies DiskFile Calls Exists ErrorPack Called by DoFile } VAR name : FileName; npos : integer; BEGIN IF DiskFile = IOERROR { if we don't already have a file } THEN begin { create a file } BtoS(data, name); { get the filename from the given ByteString } IF Verbosity THEN begin setstrlen(report,0); strwrite(report,1,rpos,'Creating file ',name); report_log(report); end; { check Max length } IF strlen(name) > FILENAME_LENGTH THEN setstrlen(name, FILENAME_LENGTH); IF Exists(name) THEN BEGIN { if file exists already } setstrlen(report,0); strwrite(report,1,rpos,'File already exists - ',name); setstrlen(name,0); strwrite(name, 1, npos, TEMPFILE:1, n:1); strwrite(report,rpos,rpos, '. Calling new file ',name,' instead.'); report_status(report); END; { if file exists already } DiskFile := Sopen(name,IOWRITE); END; { create a file } IF (Diskfile <= IOERROR) THEN begin { could not create output file } Kermit_error := cant_create_file; ErrorPack('Couldn''t create file'); end; END; { procedure GetFile } $PAGE$ PROCEDURE GetTheirInit; { Receive init packet. Respond with ACK and our parameters. Errors retry_exhausted Retry count exhausted rcvd_bad_init Received Bad Init packet References MaxTry Debug Modifies Buf[ThisPacket] Buf[CurrentPacket] State NumTry n stats.NumACKsent OldTry Calls Verbose ReceivePacket DeCodeParm EnCodeParm SendPacket SendNAK Called by RecvSwitch } VAR rs : boolean; BEGIN IF NumTry > MaxTry THEN BEGIN State := Abort; Kermit_error := retry_exhausted; END ELSE BEGIN { Receive the Send init from remote } Verbose ( 'Receiving Init '); NumTry := NumTry+1; ReceivePacket; IF odd(kermit_error) AND (Buf[CurrentPacket].ptype = TYPES) THEN BEGIN { Good send init packet received } WITH Buf[CurrentPacket] DO BEGIN n := seq; DeCodeParm(data); END; { with } { now send mine } WITH Buf[ThisPacket] DO BEGIN count := NUMPARAM; seq := n; Ptype := TYPEY; EnCodeParm(data); END; SendPacket; stats.NumACKsent := stats.NumACKsent+1; State := FileHeader; OldTry := NumTry; NumTry := 0; n := (n+1) MOD 64 END { good send init packet received } ELSE BEGIN { ReceivePacket returned an error } if (Kermit_error <> abort_file) and (Kermit_error <> abort_group) then kermit_error := rcvd_bad_init; SendNAK(n); END; END; { Receive the Send init from remote } END; $PAGE$ PROCEDURE DataToFile; { output to file } { Writes the data field of Buf[CurrentPacket] to DiskFile, modifiying the end of line sequence (dictated by EOLforFile) to be a single NEWLINE, as required by Putcf. Updates the file character counter stats.ChInFile. Implicit Inputs Buf[CurrentPacket] References EOLForFile DiskFile Modifies stats.ChInFile Calls Putcf Called by DoData } VAR i : integer; { packet data field index } control : boolean; { TRUE if last byte was control prefix } procedure bytetofile( b : byte ); begin { Putcf wants the line terminator to be only a NEWLINE character. If the character is the current Kermit line terminator (depending on EOLforFile) then write a NEWLINE to the file. NB: Here we assume that the NEWLINE character is actually a LF. } CASE EOLforFile OF LineFeed: Putcf(b,DiskFile); { terminator is already a NEWLINE } CrLf: IF b <> CR { don't output CR } THEN Putcf(b,DiskFile); JustCR: IF b = CR { change CR to NEWLINE } THEN Putcf(NEWLINE,DiskFile) ELSE Putcf(b,DiskFile); END; { case } stats.ChInFile := stats.ChInFile + 1; end; { procedure bytetofile } BEGIN { procedure DataToFile } WITH Buf[CurrentPacket] DO BEGIN control := FALSE; for i := 1 to length(data) do begin IF data[i] = LocalQuote THEN IF control { character is quote } THEN begin { quote, quote } bytetofile(LocalQuote); control := FALSE; END { quote, quote } ELSE control := TRUE { set control on } ELSE IF control { not quote } THEN begin { convert to control } bytetofile(Ctl(data[i])); control := FALSE; END ELSE bytetofile(data[i]); end; { for } END; { with CurrentPacket } END; { procedure DataToFile } $PAGE$ PROCEDURE DoData; { Process Data packet } { Processes received data packet, assumed to be in CurrentPacket. If the packet is the expected one, writes the data to the destination file via DataToFile. If it is the previous packet (i.e. the ACK for that packet got lost), ACKs that packet again if the retry count has not reached maximum. If it is any other packet number, the a NAK will be sent for the expected packet. Implicit Inputs Buf[CurrentPacket] Errors Retry count exhausted References MaxTry OldTry Modifies OldTry NumTry n State Calls DataToFile PutErr SendACK SendNAK Called by ReceiveData } BEGIN WITH Buf[CurrentPacket] DO BEGIN IF seq = ((n + 63) MOD 64) THEN BEGIN { it's the previous data packet } IF OldTry>MaxTry { if retried too many times } THEN BEGIN State := Abort; kermit_error := retry_exhausted; END ELSE BEGIN SendACK(seq); NumTry := 0; END; END { it's the previous packet } ELSE BEGIN { it's not the previous one } IF (n<>seq) { if it's not the expected one } THEN SendNAK(n) { NAK the expected one } ELSE BEGIN SendACK(n); { ACK } DataToFile; OldTry := NumTry; NumTry := 0; n := (n+1) MOD 64; END; END; { it's not the previous one } END; { with } END; { procedure DoData } $PAGE$ PROCEDURE DoFileLast; { Process File Packet } { Called by ReceiveData when file header packet received when a data packet expected (ie the sender never got the ACK for the file header). Errors Retry count exhausted References Buf[CurrentPacket] MaxTry Modifies State OldTry NumTry Calls PutErr SendACK SendNAK Called by ReceiveData } BEGIN { File header - last one } IF OldTry > MaxTry { tries ? } THEN BEGIN State := Abort; PutErr('Old file - Too many '); END ELSE BEGIN OldTry := OldTry+1; WITH Buf[CurrentPacket] DO BEGIN IF seq = ((n + 63) MOD 64) { packet number } THEN BEGIN { send ACK } SendACK(seq); NumTry := 0 END ELSE BEGIN SendNAK(n); { NAK } END; END; { with } END; { retry not exhausted } END; { procedure DoFileLast } $PAGE$ PROCEDURE DoEOF; { Process EOF packet } { Called by ReceiveData to process received EOF packets. If not the expected sequence number, NAKs the expected packet, otherwise ACKs it and closes the file. References Buf[CurrentPacket] DiskFile Modifies DiskFile OldTry NumTry State n Calls SendNAK SendACK Sclose Called by ReceiveData } BEGIN { EOF - this one } IF Buf[CurrentPacket].seq<>n { packet number ? } THEN SendNAK(n) { NAK the expected packet } ELSE BEGIN { ACK this one } SendACK(n); Sclose(DiskFile); { close file } DiskFile := IOERROR; OldTry := NumTry; NumTry := 0; n := (n+1) MOD 64; { next packet } State := FileHeader; { change state } END; { ACK this one } END; { procedure DoEOF } $PAGE$ PROCEDURE ReceiveData; { Receive data packets } { Reads packet, dispatches to proper routine if data, EOF, or file header packet. If it is any other type, NAKs the expected data packet. Returns one of the following codes in Kermit_error: success Data packet successfully received retry_ehausted Retry Count Exhausted inv_packet_type Invalid Packet Type errors returned by ReceivePacket References MaxTry Verbosity Local Buf[CurrentPacket] Modifies NumTry Calls ReceivePacket DoData DoFileLast DoEOF Verbose SendNAK Called by RecvSwitch } VAR strend : integer; packetnum : ByteString; good : boolean; BEGIN kermit_error := success; IF NumTry > MaxTry { check number of tries } THEN BEGIN State := Abort; Kermit_error := retry_exhausted; END ELSE BEGIN { retry not exhausted } NumTry := NumTry+1; { increase number of tries } ReceivePacket; { get packet } WITH Buf[CurrentPacket] DO BEGIN IF Verbosity THEN BEGIN PutCon('Receiving (Data) ',STDERR); PutNum(seq,STDERR); END; IF (ptype in [TYPED, TYPEZ, TYPEF]) { check type } AND odd(kermit_error) { and ReceivePacket status } THEN CASE ptype OF TYPED: DoData; TYPEF: DoFileLast; TYPEZ: DoEOF; END { case } ELSE BEGIN { not a good type } Verbose('Expected data pack '); if odd(kermit_error) { if ReceivePacket was successful } then kermit_error := inv_packet_type; SendNAK(n); END; END; { with } END; { retry not exhausted } END; { procedure ReceiveData } $PAGE$ PROCEDURE DoBreak; { Process Break packet } { Called by ReceiveFile to process a break packet. Errors None References Buf[CurrentPacket] n Modifies State Calls SendNAK SendACK Called by ReceiveFile } BEGIN { Break transmission } IF Buf[CurrentPacket].seq<>n { packet number ? } THEN SendNAK(n) { NAK } ELSE BEGIN { send ACK } SendACK(n) ; State := Complete { change state } END END; $PAGE$ PROCEDURE DoFile; { Process file packet } { Called by ReceiveFile to process file header packet. Errors None References Buf[CurrentPacket] Modifies stats.ChInFile OldTry NumTry n State Calls SendNAK SendACK GetFile Called by ReceiveFile } BEGIN WITH Buf[CurrentPacket] DO BEGIN IF seq<>n { packet number ? } THEN SendNAK(n) { NAK } ELSE BEGIN { send ACK } SendACK(n); stats.ChInFile := stats.ChInFile + length(data); GetFile(data); { get file name } OldTry := NumTry; NumTry := 0; n := (n+1) MOD 64; { next packet } State := FileData; { change state } END; { send ACK } END; { with } END; { procedure DoFile } $PAGE$ PROCEDURE DoEOFLast; { Process EOF Packet } { Called by ReceiveFile to process an EOF for the last file (i.e., the ACK for the last EOF was lost). Resends the ACK for the EOF. Errors Retry count exhausted References Buf[CurrentPacket] MaxTry n Modifies State OldTry NumTry Calls PutErr SendACK SendNAK Called by ReceiveFile } BEGIN { End Of File Last One} IF OldTry > MaxTry { tries ? } THEN BEGIN State := Abort; PutErr('Old EOF - Too many '); END ELSE BEGIN { process last EOF packet } OldTry := OldTry+1; WITH Buf[CurrentPacket] DO BEGIN IF seq =((n + 63 ) MOD 64) { packet number } THEN BEGIN { send ACK } SendACK(seq); Numtry := 0; END ELSE SendNAK(n); { NAK } END; { with } END; { process last EOF packet } END; { procedure DoEOFLast } $PAGE$ PROCEDURE DoInitLast; { Called by ReceiveFile when a Send-Init packet was received (i.e. when the ACK for the last Send-Init was lost). Resends the Send-Init. Errors Retry count exhausted References MaxTry Buf[CurrentPacket] NUMPARAM Modifies Buf[ThisPacket] State OldTry NumTry stats.NumACKsent Calls PutErr EnCodeParm SendPacket SendNAK Called by ReceiveFile } BEGIN { Init Packet - last one } IF OldTry>MaxTry { number of tries? } THEN BEGIN State := Abort; PutErr('Old init - Too many '); END ELSE BEGIN { process last init packet } OldTry := OldTry+1; IF Buf[CurrentPacket].seq = ((n + 63) MOD 64) { packet number } THEN BEGIN { send ACK } WITH Buf[ThisPacket] DO BEGIN count := NUMPARAM; seq := Buf[CurrentPacket].seq; ptype := TYPEY; EnCodeParm(data); END; SendPacket; stats.NumACKsent := stats.NumACKsent+1; NumTry := 0; END { send ACK } ELSE SendNAK(n); { NAK } END; { process last init packet } END; { procedure DoInitLast } $PAGE$ PROCEDURE ReceiveFile; { receive file packet } { Receives file header packet from host. Returns one of the following codes in Kermit_error: success file header packet successfully received retry_exhausted Retry count exhausted inv_packet_type Invalid Packet Type errors returned by ReceivePacket References MaxTry Verbosity Debug Modifies Buf[CurrentPacket] NumTry Calls ReceivePacket DoInitLast DoEOFLast DoFile DoBreak SendNAK Called by RecvSwitch } VAR good: boolean; rpos : integer; report, fnm : string[80]; BEGIN kermit_error := success; IF NumTry > MaxTry { check number of tries } THEN BEGIN { retry count exhausted } State := Abort; kermit_error := retry_exhausted; END { retry count exhausted } ELSE BEGIN { get the file header packet } NumTry := NumTry+1; { increase number of tries } ReceivePacket; { get packet } WITH Buf[CurrentPacket] DO BEGIN IF VERBOSITY THEN BEGIN setstrlen(report,0); strwrite(report,1,rpos, 'Receiving file header packet #', seq:1); report_log(report); END; IF (ptype in [TYPES, TYPEZ, TYPEF, TYPEB]) AND odd(kermit_error) THEN CASE ptype OF TYPES: DoInitLast; { ACK to Init packet lost } TYPEZ: DoEOFLast; { ACK to EOF lost } TYPEF: begin { File header } BtoS(data, fnm); report_receive_file(fnm); DoFile; end; { TYPEF } TYPEB: DoBreak; { finished receiving file group } END { case } ELSE BEGIN IF Debug THEN PutErr('Expected File Packet'); if odd(Kermit_error) { if ReceivePacket successful } then kermit_error := inv_packet_type; SendNAK(n); END; END; { with } END; { get the file header packet } END; { procedure ReceiveFile } $PAGE$ procedure SendRecvInit( fnm : filename ); { Sends receive initiate packet with the given filename to the remote server. Called by RecvSwitch } begin { build the Receive Init packet in ThisPacket } with Buf[ThisPacket] do begin StoB(fnm, data); { convert filename into bytestring in data field } count := strlen(fnm); seq := n; ptype := TYPER; { type is Receive Init } end; { with } SendPacket; { send ThisPacket } end; { procedure SendRecvInit } $PAGE$ procedure RecvSwitch( files : filename_list ); { Receive file group state switcher. If filename_list is non-empty, sends receive init packet for the files in it. Modifies State NumTry Calls StartRun ReceiveData GetTheirInit ReceiveFile Called by Main program } var i : integer; fnm : filename; BEGIN RunType := Receive; State := RecvInit; init_packet_display(runtype); NumTry := 0; StartRun; i := 1; while strlen(files[i]) <> 0 do begin fnm := files[i]; i := i + 1; SendRecvInit( fnm ); REPEAT if debug or verbosity then begin { print blank line to separate packet info } report := ''; report_log(report); end; CASE State OF FileData: ReceiveData; RecvInit: GetTheirInit; Break: { nothing }; FileHeader: ReceiveFile; EOFile: { nothing }; Complete: { nothing }; Abort: { nothing }; END; { case } UNTIL (State=Abort) OR (State=Complete ) or (not odd(kermit_error)); end; { while } clean_packet_display(runtype); END; { procedure recvswitch } end. { module krmguts }