{ Software Tools in PASCAL -- Procedures used by KERMIT } { Also Externals called by Send & Receive Switch } {$E+} PROCEDURE stiphalt; { used by external procedures for halt } EXTERNAL; PROCEDURE FinishUp(t:boolean); EXTERNAL; PROCEDURE PutBin(c : character); { Output Binary } BEGIN IF (c = ENDFILE) THEN { flush buffer} { fill with NULLS -- will be written below } WHILE (bptr <= BLKSIZE) DO BEGIN binbuffer[bptr] := chr(NULLCHAR); bptr := bptr + 1; END; IF bptr > BLKSIZE THEN BEGIN bfile^ := binbuffer; put(bfile); bptr := 1; IF c <> ENDFILE THEN putbin(c); END ELSE BEGIN binbuffer[bptr] := chr(c); bptr := bptr + 1; END END; { close (omsi) -- close a file } PROCEDURE Sclose (fd : filedesc); BEGIN IF (fd >= STDERR) AND (fd <= MAXOPEN) THEN BEGIN WITH openlist[fd] DO BEGIN IF (mode <= -IOREAD) THEN BEGIN IF (mode = -IOWRITE) THEN putbin(ENDFILE); { flush buffer } close(bfile); mode := IOERROR; END ELSE BEGIN close(filevar); mode := IOAVAIL; END END; END END; PROCEDURE ResetLine; { Reset DL11 Line } EXTERNAL; PROCEDURE ConUP; { Console upper case only } EXTERNAL; { close all files on exit } PROCEDURE closeall; VAR fd : filedesc; BEGIN FOR fd := STDERR TO MAXOPEN DO Sclose(fd); ResetLine; ConUP; END; { Open file in Binary Mode } FUNCTION Obinary (VAR intname : string100; omode : integer) : filedesc; VAR len : integer; BEGIN IF (omode = -IOREAD) THEN BEGIN reset(bfile, intname,'',len); binbuffer := bfile^; bptr := 1; END ELSE BEGIN rewrite(bfile, intname); bptr := 1; END; IF (omode = -IOREAD) AND (len <= 0) THEN BEGIN sclose(BINARYFILE); Obinary := IOERROR; END ELSE BEGIN Obinary := BINARYFILE; openlist[BINARYFILE].mode := omode; END; END; { open (RT-11) -- open a file for reading or writing } FUNCTION Sopen (VAR name : string; omode : integer) : filedesc; VAR i ,len: integer; intname : string100; found : boolean; BEGIN i := 1; WHILE (name[i] <> ENDSTR) AND (name[i] <> NEWLINE) DO BEGIN intname[i] := chr(name[i]); i := i + 1 END; FOR i := i TO MAXSTR DO intname[i] := ' '; { pad name with blanks } IF (omode < IOERROR) THEN Sopen := obinary(intname,omode) ELSE BEGIN { find a free slot in openlist } Sopen := IOERROR; found := false; i := 1; WHILE (i <= MAXOPEN) AND (NOT found) DO BEGIN IF (openlist[i].mode = IOAVAIL) THEN WITH openlist[i] DO BEGIN mode := omode; IF (mode = IOREAD) THEN reset(filevar, intname,'',len) ELSE IF (mode = IOWRITE) THEN rewrite(filevar, intname); IF (len <= 0) AND (mode=IOREAD) THEN BEGIN Sclose(i); Sopen := IOERROR END ELSE Sopen:=i; found := true END; i := i + 1 END END END; { getcf (UCB) -- get one character from file } FUNCTION getcf (VAR c: character; fd : filedesc) : character; FORWARD; { getc (UCB) -- get one character from standard input } FUNCTION getc (VAR c : character) : character; VAR ch : char; BEGIN IF (redirect[STDIN] = STDIN ) THEN BEGIN IF eof THEN c := ENDFILE ELSE IF eoln THEN BEGIN readln; c := NEWLINE END ELSE BEGIN read(ch); c := ord(ch) END; getc := c END ELSE getc := getcf(c,redirect[STDIN]) END; PROCEDURE GETCL(VAR c : character;VAR t :integer); { Get Character from DL11 Line } { TimeLeft is also used } EXTERNAL; PROCEDURE GetBin(VAR c: character); { Get Binary character } BEGIN IF bptr > BLKSIZE THEN BEGIN get(bfile); binbuffer := bfile^; IF eof(bfile) THEN c := ENDFILE ELSE BEGIN bptr := 1; getbin(c); END; END ELSE BEGIN c := ord(binbuffer[bptr]); bptr := bptr + 1; END END; FUNCTION getcf; { Get Character from file } VAR ch : char; BEGIN IF (fd = STDIN) THEN getcf := getc(c) ELSE WITH openlist[fd] DO IF (mode = IOLINE) THEN BEGIN GETCL(c,TimeLeft); { strip parity } IF (parity <> oNONE) THEN c := c AND 177B; END ELSE IF (mode = -IOREAD) THEN GETBIN(c) ELSE IF eof(filevar) THEN c := ENDFILE ELSE IF eoln(filevar) THEN BEGIN readln(filevar); c := NEWLINE END ELSE BEGIN read(filevar, ch); c := ord(ch) END; getcf := c END; { getline (UCB) -- get a line from file } FUNCTION getline (VAR s : string; fd : filedesc; maxsize : integer) : boolean; VAR i : integer; c : character; BEGIN i := 1; REPEAT s[i] := getcf(c, fd); i := i + 1 UNTIL (c = ENDFILE) OR (c = NEWLINE) OR (i >= maxsize); IF (c = ENDFILE) THEN { went one too far } i := i - 1; s[i] := ENDSTR; getline := (c <> ENDFILE) END; { putcf (UCB) -- put a single character on file fd } PROCEDURE putcf (c : character; fd : filedesc); FORWARD; { putc (UCB) -- put one character on standard output } PROCEDURE putc (c : character); BEGIN IF (redirect[STDOUT] = STDOUT) THEN IF c = NEWLINE THEN writeln ELSE write(chr(c)) ELSE putcf(c,redirect[STDOUT]); END; PROCEDURE PUTCL(VAR c : character); { Output character to DL11 Line } EXTERNAL; PROCEDURE putcf; { Put character to file } BEGIN IF (fd = STDOUT) THEN putc(c) ELSE WITH openlist[fd] DO IF (mode = IOLINE) THEN PUTCL(c) ELSE IF (mode = -IOWRITE) THEN PUTBIN(c) ELSE IF c = NEWLINE THEN writeln(filevar) ELSE write(filevar, chr(c)) END; { putstr (UCB) -- put out string on file } PROCEDURE putstr (VAR s : string; f : filedesc); VAR i : integer; BEGIN i := 1; WHILE (s[i] <> ENDSTR) AND (i < MAXSTR) DO BEGIN putcf(s[i], f); i := i + 1 END END; PROCEDURE Xbreak(VAR f : text); { As External since break is already defined } EXTERNAL; PROCEDURE Obreak(fd : filedesc); BEGIN IF (fd = STDOUT) THEN Xbreak(output) ELSE Xbreak(openlist[fd].filevar); END; PROCEDURE GTLINE(var commandLine : string80); BEGIN write('KERMIT-RT> '); Obreak(STDOUT); readln(commandLine); END; { itoc - convert integer n to char string in s[i]... } FUNCTION itoc (n : integer; VAR s : string; i : integer) : integer; { returns end of s } BEGIN IF (n < 0) THEN BEGIN s[i] := ord('-'); itoc := itoc(-n, s, i+1) END ELSE BEGIN IF (n >= 10) THEN i := itoc(n DIV 10, s, i); s[i] := n MOD 10 + ord('0'); s[i+1] := ENDSTR; itoc := i + 1 END END; { length -- compute length of string } FUNCTION length (VAR s : string) : integer; VAR n : integer; BEGIN n := 1; WHILE (s[n] <> ENDSTR) DO n := n + 1; length := n - 1 END; { scopy -- copy string at src[i] to dest[j] } PROCEDURE scopy (VAR src : string; i : integer; VAR dest : string; j : integer); BEGIN WHILE (src[i] <> ENDSTR) DO BEGIN dest[j] := src[i]; i := i + 1; j := j + 1 END; dest[j] := ENDSTR END; { index -- find position of character c in string s } FUNCTION index (VAR s : string; c : character) : integer; VAR i : integer; BEGIN i := 1; WHILE (s[i] <> c) AND (s[i] <> ENDSTR) DO i := i + 1; IF (s[i] = ENDSTR) THEN index := 0 ELSE index := i END; PROCEDURE CtoS({ Using } x:cstring; { Returning } VAR s:string); { convert constant to STIP string } VAR i : integer; BEGIN FOR i:=1 TO CONLENGTH DO s[i] := ord(x[i]); s[CONLENGTH+1] := ENDSTR; END; FUNCTION Exists({ Using }VAR s:string): { Returning } boolean; { returns true if file exists } VAR fd: filedesc; result: boolean; BEGIN fd := Sopen(s,IOREAD); result := (fd <> IOERROR); Sclose(fd); Exists := result; END; FUNCTION nargs: integer; { returns number arguments } { for RT - 11 } BEGIN nargs := cmdargs END; FUNCTION getarg(n:integer;VAR s:string;maxsize:integer): BOOLEAN; { return the nth argument } { RT - 11 } BEGIN IF ((n<1) OR (cmdargs0) THEN FOR i := 1 TO Pad DO putcf(PadChar,LineOut); WITH p^ DO BEGIN putcf(mark,LineOut); putcf(count,LineOut); putcf(seq,LineOut); putcf(ptype,LineOut); putstr(data,LineOut); END; END; FUNCTION GetIn { Returning } :character; { get character } { Should return NULL ) if no characters } VAR c :character; BEGIN c := getcf(c,LineIn); GetIn := c; IF (RunType = Receive) AND (c <> NULLCHAR) THEN AddTo(ChInPackRecv,1); END; PROCEDURE StartTimer; BEGIN TimeLeft := TheirTimeOut * 60; { in ticks } END; PROCEDURE StopTimer; BEGIN TimeLeft := MaxInt; { * 60 } END; FUNCTION MakeChar({ Using } c:character): { Returning } character; { convert integer to printable } BEGIN MakeChar := c + BLANK; END; FUNCTION UnChar({ Using } c:character): { Returning } character; { reverse of makechar } BEGIN UnChar := c - BLANK END; FUNCTION IsControl( c:character): boolean; { true if control } BEGIN { assume -128 .. 127 for characters } IF (c >= NULLCHAR) THEN IsControl := (c=DEL ) OR (c < BLANK ) ELSE IsControl := IsControl(c + 128); END; FUNCTION Ctl( c:character): character; { c XOR 100 } BEGIN { assume -128 .. 127 for characters } IF (c >= NULLCHAR) THEN IF (c < 64) THEN c := c + 64 ELSE c := c - 64 ELSE c := Ctl(c + 128) - 128; Ctl := c; END; FUNCTION CheckFunction({ Using } c:integer): { Returning } character; { calculate checksum } 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; PROCEDURE EnCodeParm({ Updating } VAR data:string); { encode parameters } 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(MyTimeOut); { when I want timeout} data[3] := MakeChar(MyPad); { how much padding } data[4] := Ctl(MyPadChar); { my padding character } data[5] := MakeChar(myEOL); { my EOL } data[6] := MyQuote; { my quote char } { Handle 8 Bit Quoting - for transmit use our default } IF RunType = Transmit THEN data[7] := Def8QuoteMode { Default mode } ELSE { For receive -- these may have to be changed } IF (QuoteForBinary = TYPEY) THEN IF (Def8QuoteMode <> TYPEY) THEN BEGIN BinaryMode := Quoted; data[7] := DEF8CHAR; QuoteForBinary := DEF8CHAR; END ELSE BEGIN BinaryMode := FullBinary; data[7] := TYPEY; END ELSE IF (QuoteForBinary = TYPEN) THEN data[7] := TYPEY ELSE IF (QuoteForBinary = BLANK) THEN data[7] := BLANK ELSE data[7] := TYPEY; { Make sure that Quote Character is OK } IF (RunType = Receive) AND (BinaryMode <> Quoted) THEN QuoteForBinary := ENDSTR; END; PROCEDURE DeCodeParm({ Using } VAR data:string); { decode parameters } VAR i,l : integer; BEGIN l := length(data); IF l < NUMPARAM THEN FOR i := l + 1 TO NUMPARAM DO data[i] := BLANK; data[NUMPARAM+1] := ENDSTR; SizeSend := UnChar(data[1]); { Packet Size } TheirTimeOut := UnChar(data[2]); { when I should time out } Pad := UnChar(data[3]); { padding characters to send } PadChar := Ctl(data[4]); { padding character } IF data[5] = BLANK THEN SendEOL := CR ELSE SendEOL := UnChar(data[5]);{ EOL to send } IF data[6] = BLANK THEN SendQuote := SHARP ELSE SendQuote := data[6]; { quote to send } QuoteForBinary := data[7]; { 8 Bit Quote Character } { Change these if Full Binary not available } { Use NotSupported if 'N' received } IF QuoteForBinary = TYPEY THEN BinaryMode := FullBinary ELSE IF QuoteForBinary = BLANK THEN BinaryMode := FullBinary ELSE IF QuoteForBinary = TYPEN THEN BinaryMode := NotSupported ELSE BinaryMode := Quoted; { Set it to quoted if we asked for it } IF (RunType = Transmit) AND (QuoteForBinary = TYPEY) AND (Def8QuoteMode <> TYPEY) THEN BEGIN BinaryMode := Quoted; QuoteForBinary := Def8QuoteMode; END; { Make sure that Quote Character is OK } IF (RunType = Transmit) AND (BinaryMode <> Quoted) THEN QuoteForBinary := ENDSTR; END; { Externals for RT-11 } PROCEDURE ICON; { set up console } EXTERNAL; PROCEDURE ITIME; { set up timer } EXTERNAL; PROCEDURE RCON; { Reset console } EXTERNAL; PROCEDURE RTIME; { Reset Timer } EXTERNAL; PROCEDURE Virtual; { Virtual terminal } EXTERNAL; PROCEDURE SetLine; { Set up DL11 line } EXTERNAL; PROCEDURE SYSinit; { special initialization } BEGIN END; PROCEDURE SYSfinish; { System dependent } BEGIN RTIME; RCON; END; PROCEDURE StartRun; { initialization as necessary } BEGIN State := Init; { send initiate is the start state } NumTry := 0; { say no tries yet } RunTime := 0; NumSendPacks := 0; NumRecvPacks := 0; NumACK := 0; NumNAK := 0; NumACKrecv := 0; NumNAKrecv := 0; NumBADrecv := 0; ChInFileSend := 0.0; ChInPackSend := 0.0; ChInFileRecv := 0.0; ChInFileRecv := 0.0; ITIME; ICON; END; PROCEDURE OpenPort; BEGIN IF InvalidConnection THEN BEGIN InvalidConnection := false; LineIn := DL11LINE; LineOut := DL11LINE; SetLine; END; END; PROCEDURE BadVTerminalConnect; BEGIN; writeln('Bad Terminal Connection'); END; PROCEDURE MakeConnection; { connect to remote } BEGIN writeln('[Connecting to remote host, Type CTRL-]C to return]'); Virtual; writeln('[Connection closed, back at RT-11]'); END; PROCEDURE DebugPacket({ Using } mes : cstring; { Using } VAR p : Ppack); { Print Debugging Info } BEGIN PutCon(mes,STDERR); WITH p^ DO BEGIN PutNum(Unchar(count),STDERR); PutNum(Unchar(seq),STDERR); putcf(BLANK,STDERR); putcf(ptype,STDERR); putcf(NEWLINE,STDERR); putstr(data,STDERR); putcf(NEWLINE,STDERR); END; END; PROCEDURE ReSendPacket; { re -sends previous packet } BEGIN NumSendPacks := NumSendPacks+1; AddTo(ChInPackSend,Pad + UnChar(LastPacket^.count) + 3); IF Debug THEN DebugPacket('Re-Sending ... ',LastPacket); PutPacket(LastPacket); END; PROCEDURE SendPacket; { expects count as length of data portion } { and seq as number of packet } { builds & sends packet } VAR i,len,chksum : integer; temp : Ppack; BEGIN IF (NumTry <> 1) AND (RunType = Transmit ) THEN ReSendPacket ELSE BEGIN WITH ThisPacket^ DO BEGIN mark :=SOH; { 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) THEN { is there data ? } FOR i:= 1 TO len DO IF (data[i] >= 0) THEN chksum := chksum + data[i] ELSE chksum := chksum + data[i] + 256; { assume -128 .. 127 for characters } chksum := CheckFunction(chksum); { calculate checksum } data[len+1] := MakeChar(chksum); { make printable & output } IF OneWayOnly THEN BEGIN data[len+2] := CR; { Use CRLF } data[len+3] := NEWLINE; data[len+4] := ENDSTR; END ELSE BEGIN data[len+2] := SendEOL; { EOL } data[len+3] := ENDSTR; END; END; NumSendPacks := NumSendPacks+1; IF Debug THEN DebugPacket('Sending ... ',ThisPacket); PutPacket(ThisPacket); IF RunType = Transmit THEN BEGIN AddTo(ChInPackSend,Pad + len + 6); temp := LastPacket; LastPacket := ThisPacket; ThisPacket := temp; END; END END; PROCEDURE SendACK({ Using } n:integer); { send ACK packet } BEGIN WITH ThisPacket^ DO BEGIN count := 0; seq := n; ptype := TYPEY; END; SendPacket; NumACK := NumACK+1; END; PROCEDURE SendNAK({ Using } n:integer); { send NAK packet } BEGIN WITH ThisPacket^ DO BEGIN count := 0; seq := n; ptype := TYPEN; END; SendPacket; NumNAK := NumNAK+1; END; PROCEDURE ErrorPack({ Using } c:cstring); { output Error packet if necessary -- then exit } BEGIN IF Local THEN Putcln(c,STDERR); WITH ThisPacket^ DO BEGIN seq := n; ptype := TYPEE; CtoS(c,data); count := length(data); END; SendPacket; FinishUp(false); StipHalt; END; PROCEDURE Verbose({ Using } c:cstring); { Print message if verbosity } BEGIN IF Verbosity THEN Putcln(c,STDERR); END; PROCEDURE PutErr({ Using } c:cstring); { Print error_messages } BEGIN IF Local THEN Putcln(c,STDERR); END; {$E-} { Turn off Externals here } PROCEDURE Field1; { Count } VAR test: boolean; BEGIN WITH NextPacket^ DO BEGIN count := UnChar(t); test := (count >= 3) OR (count <= SizeRecv-2); InputPacket^.count := t; IF NOT test THEN Verbose('Bad count '); isgood := isgood AND test; END; END; PROCEDURE Field2; { Packet Number } VAR test : boolean; BEGIN WITH NextPacket^ DO BEGIN seq := UnChar(t); test := (seq >= 0) OR (seq <= 63); InputPacket^.seq := t; IF NOT test THEN Verbose('Bad seq number '); isgood := isgood AND test; END; END; PROCEDURE Field3; { Packet Type } VAR test : boolean; BEGIN WITH NextPacket^ DO BEGIN ptype := t; test := (t =TYPEB) OR (t=TYPED) OR (t=TYPEE) OR (t=TYPEF) OR (t=TYPEN) OR (t=TYPES) OR (t=TYPEY) OR (t=TYPEZ); InputPacket^.ptype := t; IF NOT test THEN Verbose('Bad Packet Type '); isgood := isgood AND test; END; END; PROCEDURE ProcessQuoted; { for Data } BEGIN WITH NextPacket^ DO BEGIN IF (t=MyQuote) OR (t=QuoteForBinary) THEN { character is quote } BEGIN IF control THEN { quote ,quote } BEGIN data[dataptr] := t + ishigh; dataptr := dataptr+1; control := false; ishigh := 0; END ELSE IF (t=MyQuote) THEN { set control on } control := true END ELSE { not quote } IF control THEN { convert to control } BEGIN data[dataptr] := ctl(t) + ishigh; dataptr := dataptr+1; control := false; ishigh := 0; END ELSE { regular data } BEGIN data[dataptr] := t + ishigh; dataptr := dataptr+1; ishigh := 0; END; END; END; PROCEDURE Field4; { Data } BEGIN PacketPtr := PacketPtr+1; InputPacket^.data[PacketPtr] := t; WITH NextPacket^ DO BEGIN IF ((ptype = TYPES) or (ptype = TYPEY)) THEN BEGIN data[dataptr] := t; dataptr := dataptr+1; END ELSE BEGIN IF (BinaryMode = Quoted) THEN BEGIN { has it been quited ?} IF (NOT control) AND (t = QuoteForBinary) THEN ishigh := 128 ELSE ProcessQuoted; END ELSE ProcessQuoted; { do regular quoting } END; END; END; PROCEDURE Field5; { Check Sum } VAR test : boolean; BEGIN WITH InputPacket^ DO BEGIN PacketPtr := PacketPtr +1; data[PacketPtr] := t; PacketPtr := PacketPtr +1; data[PacketPtr] := ENDSTR; END; { end of input string } check := CheckFunction(check); check := MakeChar(check); test := (t=check); isgood := isgood AND test; NextPacket^.data[dataptr] := ENDSTR; { end of data string } finished := true; { set finished } END; PROCEDURE BuildPacket; { receive packet & validate checksum } VAR temp : Ppack; BEGIN WITH NextPacket^ DO BEGIN IF restart THEN BEGIN { read until get SOH marker } IF (t = SOH) THEN BEGIN finished := false; { set varibles } control := false; ishigh := 0; { no shift } isgood := true; seq := -1; { set return values to bad packet } ptype := QUESTION; data[1] := ENDSTR; data[MAXSTR] := ENDSTR; restart := false; fld := 0; dataptr := 1; PacketPtr := 0; check := 0; END; END ELSE { have started packet } BEGIN IF (t=SOH) { check for restart or EOL } THEN restart := true ELSE IF (t=myEOL) THEN BEGIN finished := true; isgood := false; END ELSE BEGIN CASE fld OF { increment field number } 0: fld := 1; 1: fld := 2; 2: fld := 3; 3: IF (count=3) { no data } THEN fld := 5 ELSE fld := 4; 4: IF (PacketPtr>=count-3) { end of data } THEN fld := 5; END { case }; IF (fld<>5) THEN check := check+t; { add into checksum } CASE fld OF 1: Field1; 2: Field2; 3: Field3; 4: Field4; 5: Field5; END; { case } END; END; IF finished THEN BEGIN IF (ptype=TYPEE) AND isgood THEN { error_packets } BEGIN IF Local THEN putstr(data,STDERR); putcf(NEWLINE,STDERR); FinishUp(false); StipHalt; END; NumRecvPacks := NumRecvPacks+1; IF Debug THEN BEGIN DebugPacket('Received ... ',InputPacket); IF isgood THEN PutCln('Is Good ',STDERR); END; temp := CurrentPacket; CurrentPacket := NextPacket; NextPacket := temp; END; END; END; {$E+} { Turn on Externals here } FUNCTION RecvPacket: boolean; BEGIN StartTimer; finished := false; restart := true; FromConsole := nothing; { No Interupt } REPEAT t := GetIn; IF Local { check Interupt } THEN CASE FromConsole OF abortnow: BEGIN ErrorPack('Aborting Transfer '); END; nothing: { nothing }; CRin: BEGIN t := MyEOL; FromConsole := nothing; END; END; { case } IF (t <> NULLCHAR) THEN BuildPacket; UNTIL finished OR (TimeLeft <= 0); IF (TimeLeft <= 0) THEN BEGIN CurrentPacket^.ptype := TYPET; restart := true; isgood := true; Verbose('Timed Out ') END; StopTimer; RecvPacket := isgood; END; FUNCTION RecvACK : { Returning } boolean; { receive ACK with correct number } VAR Ok: boolean; BEGIN IF (NOT OneWayOnly ) THEN Ok := RecvPacket; WITH CurrentPacket^ DO BEGIN IF (ptype=TYPEY) THEN NumACKrecv := NumACKrecv+1 ELSE IF (ptype=TYPEN) THEN NumNAKrecv := NumNAKrecv+1 ELSE IF NOT OneWayOnly THEN NumBadrecv := NumBadrecv +1; { got right one ? } RecvACK := ( Ok AND (ptype=TYPEY) AND (n=seq)) OR ( OneWayOnly) END; END;