PROGRAM KERMIT;
(* AN IMPLEMENTATION OF KERMIT FOR THE IBM 9000, THIS IS A BASIC SEND ONLY *)
(* KERMIT DESIGNED FOR SHORT TRANSFERS OVER A NULL MODEM LINE, NO ATTEMPT AT *)
(* TERMINAL EMULATION WAS ATTEMPTED. --- WARNING, THIS SOURCE CODE WAS TYPED IN *)
(* BY HAND AS THE ORIGINAL SOURCE FILE WAS UNAVAILABLE, THEIR MIGHT BE TYPOS *)
(* 2ND WARNING, THIS IS THE FIRST PASCAL PROGRAM I EVER WROTE *)
(* 3RD WARNING, WHEN LINKING THIS PROGRAM BE SURE TO ALLOCATE LESS THEN THE *)
(* DEFAULT STACK SPACE 28K IS FINE, THIS WILL ENABLE THIS PROGRAM TO RUN ON *)
(* COMPUTERS WITH SMALLER RAM SIZES *)

(* AUTHOR: GLENN R. HOWES --> HOWES@BERT.CHEM.WISC.EDU *)
(* DATE: MAY, 1990 *)
	USES
		SYSTEM_LIBRARY;

	TYPE
		PACKET = STRING[82];
		PKTPNT = ^PACKET;
		BUFFER = PACKED ARRAY[1..512] OF CHAR;
		BUFFPNT = ^BUFFER;
		SMPACKET = STRING[1];
	VAR
(******************** GLOBAL VARIABLES ***********************)
		IRFILE: TEXT; (* UNTYPED (NON-TEXT) FILE DESCRIPTOR FOR INTERNAL USE *)
		IRBUFFER: BUFFER; (* READ 512 BYTES FROM FILE AT ONCE *)
		IRPNT: BUFFPNT;
		BLOCK: INTEGER; (* INDEX TO KEEP TRACK OF WHICH FILE BLOCK IS BEING ACCESSED *)
		ENDOFBLOCK: BOOLEAN; (* FLAG TO INDICATE ALL 512 BYTES OF A BLOCK HAVE BEEN USED *)
		ENDFILE: BOOLEAN; (* FLAG TO INDICATE THE END OF THE FILE HAS BEEN REACHED *)
		BLOCKLENGTH: INTEGER;
		IRINDEX, BUFFINDEX: INTEGER;
		PACKETNUM: INTEGER; (* INDEX TO KEEP TRACK OF HOW MANY PACKETS HAVE BEEN SENT *)
		FILENAME: STRING[100];
		S, F, D, Z, B, Y, N, E: CHAR; (* ALL THE DIFFERENT KERMIT PACKET TYPES *)
		QUIT: BOOLEAN;
		GSPACKET, GRPACKET: PACKET; (* GLOBAL SEND AND RECEIVE PACKETS *)
		SERIAL0: INTEGER; (* DEVICE LUN # FOR SERIAL PORT 1 *)
		SERIALTEXT:FILE; (* USED IN INITIALIZANG SERIAL DRIVER *)

(******************* ENCODING ROUTINES *********************)
	FUNCTION TOCHAR (X: INTEGER): CHAR;
		VAR
			MYCHAR: CHAR;
	BEGIN
		X := X + 32;
		MYCHAR := CHR(X);
		TOCHAR := MYCHAR;
	END;
	FUNCTION UNCHAR (MYCHAR: CHAR): INTEGER;
		VAR
			X: INTEGER;
	BEGIN
		X := ORD(MYCHAR);
		X := X - 32;
		UNCHAR := X;
	END;
	FUNCTION CTL (MYCHAR: CHAR): CHAR; (* THIS IS A HACK VERSION OF ORD(CHAR) X0R 64 *)
		VAR
			X: INTEGER;
			I: INTEGER;
			J: INTEGER;
	BEGIN
		X := ORD(MYCHAR);
		I := X OR 64;
		J := X AND 64;
		X := I - J;
		CTL := CHR(X);
	END;
	FUNCTION FIND_CHECK_SUM (MYPACKET: PACKET; MYLENGTH: INTEGER): CHAR;
		VAR
			SUM, I, RAWCHECK: INTEGER;
	BEGIN
		SUM := 0;
		FOR I := 1 TO (MYLENGTH) DO (* SUM OF FIELD 2 THROUGH FIELD CHECK -1*)
			BEGIN
				SUM := SUM + ORD(MYPACKET[I]);
			END;
		RAWCHECK := (SUM + ((SUM AND 192) DIV 64)) AND 63;
		FIND_CHECK_SUM := TOCHAR(RAWCHECK);
	END;
	FUNCTION CONTROL_ENCODE (MYCHAR: CHAR): BOOLEAN;
		VAR
			TEMPBYTE: CHAR;
			CHARINT: INTEGER;
			TEMPINT: INTEGER;
	BEGIN
		CHARINT := ORD(MYCHAR);
		TEMPINT := CHARINT AND 127;
		IF ((TEMPINT < 32) OR (TEMPINT = 127)) THEN
			CONTROL_ENCODE := TRUE;
	END;
(******************* FILE ROUTINES **********************)
	FUNCTION OPEN_FILE: BOOLEAN;
	BEGIN
(*$I-*)
		RESET(IRFILE, FILENAME);
(*$I+*)
		IF IORESULT = 0 THEN
			OPEN_FILE := TRUE
		ELSE
			BEGIN
				WRITELN('BAD FILENAME, OR OTHER ERROR: TRY AGAIN');
				OPEN_FILE := FALSE;
			END;
	END;
	PROCEDURE GET_FILE_NAME;
	BEGIN
		IF ARGC > 0 THEN
			BEGIN
				FILENAME := ARGV[1]^;
				ARGC := 0;
			END
		ELSE
			BEGIN
				WRITE('FILENAME (OR Q TO QUIT):');
				READLN(FILENAME);
			END;
	END;
	PROCEDURE GET_N_CHECK_FILE;
		VAR
			GOODFILE: BOOLEAN;
	BEGIN
		GOODFILE := FALSE;
		REPEAT
			GET_FILE_NAME;
			IF FILENAME[1] = 'Q' THEN
				BEGIN
					QUIT := TRUE;
					GOODFILE := TRUE;
				END
			ELSE
				GOODFILE := OPEN_FILE;
		UNTIL GOODFILE = TRUE;
	END;

(********************** SERIAL  PORT INTERACTION ROUTINES **************)
	PROCEDURE OPEN_SERIAL0;
		VAR
			CTLPACKET: ARRAY[1..15] OF INTEGER;
			ERROR: INTEGER;
	BEGIN
		RESET(SERIALTEXT, '#SER00');
		SERIAL0 := GETLUN(@SERIALTEXT);
		CTLPACKET[1] := 4;
		CTLPACKET[2] := $0064; (* 5 SECOND TIMEOUT *)
		CTLPACKET[3] := 6;
		CTLPACKET[4] := $00C8; (* 10 SECOND RECEIVE TIMEOUT *)
		CTLPACKET[5] := 20;
		CTLPACKET[6] := 13; (* 9600 BAUD *)
		CTLPACKET[7] := 0;
		SYSFUNC(SERIAL0, @CTLPACKET, ERROR);
		IF ERROR <> 0 THEN
			WRITELN('ERROR NUMBER ', ERROR);
	END;
	PROCEDURE CLOSE_SERIAL0;
	BEGIN
		CLOSE(SERIALTEXT);
	END
	PROCEDURE SEND_PACKET;
		VAR
			ERROR: INTEGER;
			PAKSIZE: INTEGER;
	BEGIN
		PAKSIZE: 
		UNCHAR(GSPACKET[1]) + 3;
		SWRITE(SERIAL0, @GSPACKET, PAKSIZE, 0, 0, 0, ERROR);
		IF ERROR <> 0 THEN
			WRITELN('ERROR IN SERIAL PORT: ', ERROR);
	END;
	FUNCTION PACKET_RECEIVE: BOOLEAN;
		VAR
			ERROR: INTEGER;
			TEMPC: SMPACKET;
			I: INTEGER;
			LENGTH: INTEGER;
	BEGIN
		REPEAT
			SREAD(SERIAL0, @TEMPC, 1, 0, 0, 0, ERROR);
			GRPACKET[0] := TEMPC[0];
		UNTIL GRPACKET[0] = CHR(1); (* UNTIL WE SEE THE START OF PACKET SYMBOL *)
		SREAD(SERIAL0, @TEMPC, 1, 0, 0, 0, ERROR);
		GRPACKET[1] := TEMPC[0];
		LENGTH := UNCHAR(TEMPC[0]) + 2;
		FOR I := 2 TO LENGTH DO
			BEGIN
				SREAD(SERIAL0, @TEMPC, 1, 0, 0, 0, ERROR);
				GRPACKET[I] := TEMPC[0];
			END;
		IF ERROR <> 0 THEN
			BEGIN
				WRITELN('ERROR IN RECEIVING: ', ERROR);
				PACKET_RECEIVE := FALSE;
			END
		ELSE
			PACKET_RECEIVE := TRUE;
	END;

(****************** MAKE PACKET ROUTINES ************* *)
	PROCEDURE MAKE_INIT_PACKET;
	BEGIN
		GSPACKET[1] := TOCHAR(9); (* LENGTH OF REMAINING PACKET *)
		GSPACKET[2] := TOCHAR(0); (* THIS IS THE FIRST PACKET *)
		GSPACKET[3] := S; (* THIS IS TYPE S *)
		GSPACKET[4] := TOCHAR(80); (* MAX PACKET LENGTH IS 80 *)
		GSPACKET[5] := TOCHAR(5); (* 5 SECOND TIMEOUT *)
		GSPACKET[6] := TOCHAR(0); (* NO PADDING USED *)
		GSPACKET[7] := '@'; (* PADDING SYMBOL, DOESN'T MATTER ANYWAY *)
		GSPACKET[8] := TOCHAR(13); (* END OF LINE CHARACTER *)
		GSPACKET[9] := '#'; (* THE CONTROL PREFIX FOR CONTROL CHARACTER ENCODING *)
		GSPACKET[10] := FIND_CHECK_SUM(GSPACKET, 9);
		GSPACKET[11] := CHR(13); (* END OF LINE IS A CARRIAGE RETURN *)
	END;
	PROCEDURE MAKE_FILE_HEADER;
		VAR
			STLENGTH: INTEGER;
			PKLENGTH: INTEGER;
			I: INTEGER;
			SEQUENCE: INTEGER;
	BEGIN
		STLENGTH := LENGTH(FILENAME);
		PKLENGTH := STLENGTH + 3;
		GSPACKET[1] := TOCHAR(PKLENGTH);
		GSPACKET[3] := F;
		SEQUENCE := PACKETNUM MOD 64;
		GSPACKET[2] := TOCHAR(SEQUENCE);
		FOR I := 1 TO (STLENGTH) DO
			BEGIN
				GSPACKET[(I + 3)] := FILENAME[I];
			END;
		GSPACKET[(PKLENGTH + 1)] := FIND_CHECK_SUM(GSPACKET, PKLENGTH);
		GSPACKET[PKLENGTH + 2] := CHR(13);
		WRITELN('MADE HEADER');
	END;

	PROCEDURE MAKE_DATA_PACKET;
		VAR
			PAKSIZE: INTEGER;
			TEMPCHAR: CHAR;
			DONE: BOOLEAN;
			SEQUENCE: INTEGER;
			INDEX: INTEGER;
			SUM: INTEGER;
			RAWCHECK: INTEGER;
	BEGIN
		PAKSIZE := 5;
		SEQUENCE := PACKETNUM MOD 64;
		GSPACKET[2] := TOCHAR(SEQUENCE);
		SUM := ORD(GSPACKET[2]);
		INDEX := 4;
		REPEAT
			TEMPCHAR := IRBUFFER[IRINDEX];
			IF CONTROL_ENCODE(TEMPCHAR) = TRUE THEN
				BEGIN
					TEMPCHAR := CTL(TEMPCHAR);
					GSPACKET[INDEX] := '#';
					INDEX := INDEX + 1;
					PAKSIZE := PAKSIZE + 1;
					SUM := SUM + 35; (* ASCII NUMBER OF '#' SIGN *)
				END
			ELSE IF TEMPCHAR = '#' THEN
				BEGIN
					GSPACKET[INDEX] := '#';
					INDEX := INDEX + 1;
					PAKSIZE := PAKSIZE + 1;
					SUM := SUM + 35;
				END;
			GSPACKET[INDEX] := TEMPCHAR;
			INDEX := INDEX + 1;
			PAKSIZE := PAKSIZE + 1;
			IRINDEX := IRINDEX + 1;
			SUM := SUM + ORD(TEMPCHAR);
			IF IRINDEX = (BLOCKLENGTH + 1) THEN
				ENDOFBLOCK := TRUE;
		UNTIL ((ENDOFBLOCK = TRUE) OR (PAKSIZE >= 80));
		GSPACKET[1] := TOCHAR((INDEX - 1));
		SUM := SUM + ORD(GSPACKET[1]) + ORD(D); (* ADDING THE LENGTH AND THE TYPE *)
		RAWCHECK := (SUM + ((SUM AND 192) DIV 64)) AND 63;
		GSPACKET[INDEX] := TOCHAR(RAWCHECK);
		GSPACKET[(INDEX + 1)] := CHR(13);
	END;
	PROCEDURE MAKE_EOF;
		VAR
			SEQUENCE: INTEGER;
	BEGIN
		GSPACKET[3] := Z;
		GSPACKET[1] := TOCHAR(3);
		SEQUENCE := PACKETNUM MOD 64;
		GSPACKET[2] := TOCHAR(SEQUENCE);
		GSPACKET[4] := FIND_CHECK_SUM(GSPACKET, 3);
		GSPACKET[5] := CHR(13);
	END;
	PROCEDURE MAKE_END_OF_TRANS;
		VAR
			SEQUENCE: INTEGER;
	BEGIN
		GSPACKET[3] := B;
		GSPACKET[1] := TOCHAR(3);
		SEQUENCE := PACKETNUM MOD 64;
		GSPACKET[2] := TOCHAR(SEQUENCE);
		GSPACKET[4] := FIND_CHECK_SUM(GSPACKET, 3);
		GSPACKET[5] := CHR(13);
	END;
(********************* INITIALIZATION ROUTINES ***************)
	PROCEDURE INITPACKTYPES;

	BEGIN
		S := 'S';
		F := 'F';
		D := 'D';
		Z := 'Z';
		B := 'B';
		Y := 'Y';
		N := 'N';
		E := 'E';
		GRPACKET := '                       ';
		GSPACKET := '                                                             ';
	END;
(********************** MISCELANEOUS ROUTINES *************)
	PROCEDURE DISPLAY_INSTRUCTIONS;
		VAR
			TEMPSTRING: STRING[25];
	BEGIN
		WRITELN('MAKE SURE THE OTHER COMPUTER IS READY TO RECEIVE. ');
		WRITELN('HIT RETURN TO PROCEED');
		READLN(TEMPSTRING);
	END;
	PROCEDURE READ_FILE_BLOCK;
		VAR
			TEMPCHAR: CHR;
	BEGIN
		BLOCKLENGTH := 0;
		REPEAT
			ENDFILE := EOF(IRFILE);
			IF ENDFILE = FALSE THEN
				BEGIN

					IF EOLN(IRFILE) = FALSE THEN
						BEGIN
							BLOCKLENGTH := BLOCKLENGTH + 1;
							READ(IRFILE, TEMPCHAR);
							IRBUFFER[BLOCKLENGTH] := TEMPCHAR;
						END
					ELSE
						BEGIN
							BLOCKLENGTH := BLOCKLENGTH + 1;
							READ(IRFILE, TEMPCHAR);
							IRBUFFER[BLOCKLENGTH] := CHR(13);
						END;
				END;
		UNTIL ((ENDFILE = TRUE) OR (BLOCKLENGTH = 512))
	END;
(************************* DECISION MAKING ROUTINES ************)
	PROCEDURE RECEIVE_AND_CONFIRM;
		VAR
			CHECKCHAR: CHAR;
			PAKLENGTH: INTEGER;
			SEQUENCE: INTEGER;
			SEQCHAR: CHAR;
			CONFIRMED: BOOLEAN;
	BEGIN
		CONFIRMED := TRUE;
		REPEAT
			SEND_PACKET;
			IF ((PACKET_RECEIVE = TRUE) AND (GRPACKET[3] = Y)) THEN
				BEGIN
					PAKLENGTH := UNCHAR(GRPACKET[1]);
					CHECKCHAR := FIND_CHECK_SUM(GRPACKET, PAKLENGTH);
					SEQUENCE := PACKETNUM MOD 64;
					SEQCHAR := TOCHAR(SEQUENCE);
					IF ((CHECKCHR <> GRPACKET[PAKLENGTH + 1]) OR (SEQCHAR <> GRPACKT[2])) THEN
						BEGIN
							CONFIRMED := FALSE;
						END
					ELSE
						CONFIRMED := TRUE;
				END
			ELSE
				BEGIN
					WRITELN('FALSE');
					CONFIRMED := FALSE;
					IF GRPACKET[3] = E THEN
						WRITELN('FATAL ERROR');
				END;
		UNTIL CONFIRMED = TRUE;
	END;
	PROCEDURE INITIATE_TRANSFER;
	BEGIN
		BLOCK := 0; (* WE ARE STARTING TO READ THE FILE FROM DISK *)
		READ_FILE_BLOCK;
		IF BLOCKLENGTH > 0 THEN
			BEGIN
				MAKE_INIT_PACKET;
				RECEIVE_AND_CONFIRM;
				PACKETNUM := 1;
				MAKE_FILE_HEADER;
				RECEIVE_AND_CONFIRM;
				PACKETNUM := 2;
				IRPNT := @IRBUFFER;
				REPEAT
					ENDOFBLOCK := FALSE;
					IRINDEX := 1;
					GSPACKET[3] := D;
					REPEAT
						MAKE_DATA_PACKET;
						WRITE('.');
						RECEIVE_AND_CONFIRM;
						PACKETNUM := PAKETNUM + 1;

					UNTIL ENDOFBLOCK = TRUE;
					WRITELN('+');
					BLOCK := BLOCK + 1;
					READ_FILE_BLOCK;
				UNTIL BLOCKLENGTH = 0; (* END OF FILE *)
				MAKE_EOF;
				WRITELN('END OF FILE SENT');
				RECEIVE_AND_CONFIRM;
			END
		ELSE
			WRITELN('NO APPARENT FILE TO READ');
	END;
(******************** MAIN PROGRAM ******************)
BEGIN
	INITPACKTYPES;
	GSPACKET[0] := CHR(1);
	OPEN_SERIAL0;
	QUIT := FALSE;
	GET_N_CHECK_FILE;
	WHILE QUIT = FALSE DO
		BEGIN
			DISPLAY_INSTRUCTIONS;
			PACKETNUM := 0;
			INITIATE_TRANSFER;
			GET_N_CHECK_FILE;
		END;
	CLOSE_SERIAL0;
END.

