;;; -*- Mode:LISP; Package:KERMIT; Syntax:COMMON-LISP; Base:10 -*- ;;; ITS KERMIT - a Rule-Based Expert System ; Authorship Information: ; Mark David (LMI) Original version, using KERMIT.C as a guide ; George Carrette (LMI) Various enhancements ; Mark Ahlstrom (Honeywell) Port to 3600 (edits marked with ";1;") ; Jonathan Rees (MIT) Almost total rewrite for ITS ;;; [It ain't really an expert system. That was an attempt at humor.] ;;; Don't make any attempt to understand this code without having access ;;; to the Kermit Protocol Manual, Frank Da Cruz, Columbia University, ;;; 5th edition. ;;; In an attempt to allow this code to outlive its usefulness on the ;;; ITS machines, I've tried to make the code as close to Common Lisp as ;;; seems reasonable, without going overboard. So a port to some ;;; Common Lisp implementation should be relatively straightforward. ;;;+ Bugs ;;; What to do about the control-C's at the end of ITS files? ;;; There's apparently no way to distinguish them from legitimate control-C's. ;;; Right now they're interpreted exactly the same as end of file. ;;; ;;; The receive packet size rightly ought to be about 93 or so, but ;;; for some reason can't RECEIVE packets bigger than about 64 bytes. ;;; For the life of me I can't figure out why. Debug this some day. ;;; [And due to a bug in Kermit-9000, I have set the packet size down ;;; to 50 (probably should be 32) to try to make SURE...] ;;;- #+maclisp (EVAL-WHEN (EVAL LOAD COMPILE) (OR (GET 'COMMON 'LOADEDP) (LOAD '((DSK MATH) COMMON)))) #+maclisp (FORMAT NIL "T") Make sure FORMAT is loaded #+maclisp (DECLARE (*LEXPR SEND-ERROR-PACKET INIT-SERIAL-I/O STRING-APPEND MERGE-PATHNAMES)) #+maclisp (DECLARE (FIXNUM (SERIAL-WRITE-CHAR FIXNUM)) (FIXNUM (SERIAL-READ-CHAR))) #+maclisp (SETQ GC-OVERFLOW NIL) ;;; Fundamental constants (DEFCONSTANT *SOH* 1 "start of header") (DEFCONSTANT *MAX-PACKET-SIZE* (- 128. 32.) "maximum packet size") ;;; The following three values merely need to answer true to FAILP ;;; and not be legal packet types. (DEFCONSTANT *TIMED-OUT* 0) (DEFCONSTANT *LENGTH-ERROR* 1) (DEFCONSTANT *CHECKSUM-ERROR* 2) ;;; Global parameters (DEFVAR *IMAGE?* NIL "T means 8-bit mode") (DEFVAR *DEBUG?* NIL "T means supply debugging info as you run") (DEFVAR *VERSION-NUMBERS?* T "T means include version numbers in file names") (DEFVAR *MAX-RETRY-COUNT* 10. "times to retry a packet") (DEFVAR *MIN-TIMEOUT* 2 "minimum timeout interval in seconds") (DEFVAR *SERVER-TIMEOUT* 30 "timeout interval when in server mode") (DEFVAR *SERIAL-INPUT* NIL "input stream for serial line") (DEFVAR *SERIAL-OUTPUT* NIL "output stream for serial line") (DEFVAR *INTERACTION-STREAM* *TERMINAL-IO* "stream for messages to user") ;;; State machine variables (DEFVAR *SERVER-MODE* NIL "true if in server mode") (DEFVAR *FILE-TRANSFER-LIST* '() "list of source/dest filename pairs") (DEFVAR *COMMAND* NIL "generic or other command to execute") (DEFVAR *SEQUENCE-NUMBER* 0 "the packet number") (DEFVAR *RETRY-COUNT* 0 "times this packet retried") (DEFVAR *STREAM* NIL "file pointer for current disk file") (DEFVAR *DISCARD* NIL "T if current file is to be discarded") ;;; Packet reception parameters (see KPM page 26): (DEFVAR *RECEIVE-PACKET-SIZE* 50 "maximum packet size") ;see bug remark, above (DEFVAR *MY-TIMEOUT* 10 "seconds after which I time out") (DEFVAR *MY-PAD* 0 "number of padding characters I need") (DEFVAR *MY-PAD-CHAR* 0 "char I want as a padding char") (DEFVAR *MY-EOL* #o15 "my kind of return char") (DEFVAR *MY-QUOTE* #\# "my quote character") ;;; Packet transmission parameters: (DEFVAR *SEND-PACKET-SIZE* 80 "maximum send packet size") ;MAXL (DEFVAR *TIMEOUT* 15 "timeout for remote host on sends") ;TIME (DEFVAR *PAD* 0 "how much padding to send") ;NPAD (DEFVAR *PAD-CHAR* 0 "padding character to send") ;PADC (DEFVAR *EOL* #o15 "end-of-line character to send") ;EOL (DEFVAR *QUOTE* #\# "quote character in outgoing data") ;QCTL ;;; Packet buffers, globally allocated for no very good reason (DEFVAR *RECEIVE-PACKET* (MAKE-ARRAY *MAX-PACKET-SIZE* :ELEMENT-TYPE 'CHARACTER :FILL-POINTER 0) "receive packet buffer") (DEFVAR *SEND-PACKET* (MAKE-ARRAY *MAX-PACKET-SIZE* :ELEMENT-TYPE 'CHARACTER :FILL-POINTER 0) "send packet buffer") ;;; Utility routines & macros (DEFMACRO MESSAGE (&REST CRUFT) `(IF (OR (NOT *SERVER-MODE*) *DEBUG?*) (FORMAT *INTERACTION-STREAM* ,@CRUFT))) (DEFMACRO DEBUG-MESSAGE (&REST CRUFT) `(IF *DEBUG?* (FORMAT *DEBUG-IO* ,@CRUFT))) ;;; TOCHAR: converts a control character to a printable one by adding a space. ;;; UNCHAR: undoes tochar. ;;; CTL: converts between control characters and printable characters by ;;; toggling the control bit (i.e. ^a becomes a and a becomes ^a). (DEFSUBST TOCHAR (CH) (+ CH #\SPACE)) (DEFSUBST UNCHAR (CH) (- CH #\SPACE)) (DEFSUBST CTL (CH) (LOGXOR CH #o100)) (DEFSUBST COMPUTE-FINAL-CHECKSUM (CHKSUM) (LOGAND (+ (ASH (LOGAND CHKSUM #o0300) -6) CHKSUM) #o077)) ;;; ACKP, NACKP, ERRP, FAILP ;;; Predicates applied to packet type returned by RECEIVE-PACKET: ;;; an ACK (Y), a NACK (N), an ERRORMESSAGE (E), or a failed ;;; packet transmission (timeout, bad checksum, or illegal length). (DEFSUBST ACKP (TYPE) (CHAR= TYPE #\Y)) (DEFSUBST NACKP (TYPE) (CHAR= TYPE #\N)) (DEFSUBST ERRP (TYPE) (CHAR= TYPE #\E)) (DEFSUBST FAILP (TYPE) (CHAR< TYPE #\SPACE)) ;;; Top-level entry routines ;;; Server mode: (DEFUN SERVE () ;See LIBDOC; TTY > (FORMAT *INTERACTION-STREAM* "~&Entering KERMIT server mode.~%") (LET ((STATUS (RUN-STATE-MACHINE #'RECEIVE-SERVER-IDLE T '() NIL))) (IF (EQ STATUS 'LOGOUT) (QUIT) STATUS))) ;;; Send a file: (DEFUN SEND-FILE (LOCAL-FILESPEC &OPTIONAL REMOTE-FILESPEC) (LET ((LOCAL-FILE-LIST (FILES-MATCHING LOCAL-FILESPEC))) (COND ((NULL LOCAL-FILE-LIST) (MESSAGE "~&No files match this specification: ~S" LOCAL-FILESPEC)) (T (RUN-STATE-MACHINE #'SEND-INIT NIL (MAKE-TRANSFER-LIST LOCAL-FILE-LIST REMOTE-FILESPEC) NIL))))) ;;; Get a file: ;;; --- This routine is untested. Use at your own risk. --- ;;; [What about sending parameters? We ought to do an I / Y packet exchange!] (DEFUN GET-FILE (REMOTE-SPEC &OPTIONAL LOCAL-SPEC) (RUN-STATE-MACHINE #'SEND-MISC NIL (LIST (CONS REMOTE-SPEC (OR LOCAL-SPEC (LOCALIZE REMOTE-SPEC)))) 'GET)) ;;; Tell remote server to go away: (DEFUN FINISH () (RUN-STATE-MACHINE #'SEND-MISC NIL '() 'FINISH)) (DEFUN BYE () (RUN-STATE-MACHINE #'SEND-MISC NIL '() 'BYE)) ;;; The KERMIT state machine (DEFUN RUN-STATE-MACHINE (STATE *SERVER-MODE* *FILE-TRANSFER-LIST* *COMMAND*) (IF (NULL *SERIAL-INPUT*) (INIT-SERIAL-I/O)) (LET ((TEMP (SERIAL-STATE))) (UNWIND-PROTECT (PROGN (DISABLE-ECHOING TEMP) (FLUSH-INPUT) (*CATCH 'DONE (DO () (NIL) (*CATCH 'ABORT (UNWIND-PROTECT (LET ((*SEQUENCE-NUMBER* 0) (*RETRY-COUNT* 0) (*DISCARD* NIL) (*TIMEOUT* *TIMEOUT*)) (DO ((STATE STATE (FUNCALL STATE))) (NIL) (DEBUG-MESSAGE "~&State = ~S~&" STATE))) (COND (*STREAM* (CLOSE *STREAM*) (SETQ *STREAM* NIL))))) (IF (NOT *SERVER-MODE*) (RETURN 'ABORTED))))) (SET-SERIAL-STATE TEMP)))) (DEFMACRO DEFSTATE (NAME &REST BODY) `(DEFUN ,NAME () ,@BODY)) (DEFMACRO GOTO-STATE (STATE) `#',STATE) (DEFUN ABORT () (DEBUG-MESSAGE "~&Aborting.") (*THROW 'ABORT NIL)) (DEFUN COMPLETE () (IF *SERVER-MODE* (GOTO-STATE RECEIVE-SERVER-IDLE) (*THROW 'DONE 'COMPLETE))) (DEFSUBST NEXT-SEQUENCE-NUMBER () (LOGAND (1+ *SEQUENCE-NUMBER*) #o77)) (DEFSUBST PREVIOUS-SEQUENCE-NUMBER () (LOGAND (+ *SEQUENCE-NUMBER* #o77) #o77)) (DEFUN BUMP-SEQUENCE-NUMBER () (SETQ *SEQUENCE-NUMBER* (NEXT-SEQUENCE-NUMBER)) (SETQ *RETRY-COUNT* 0)) (DEFUN BUMP-RETRY-COUNT () (INCF *RETRY-COUNT*) (COND ((> *RETRY-COUNT* *MAX-RETRY-COUNT*) (SEND-ERROR-PACKET "aborting after ~D retries" *MAX-RETRY-COUNT*) (ABORT)))) ;;; Rec_Server_Idle -- Server idle, waiting for a message (DEFSTATE RECEIVE-SERVER-IDLE (SETQ *SEQUENCE-NUMBER* 0) (SETQ *TIMEOUT* *SERVER-TIMEOUT*) ;will get reset later (MULTIPLE-VALUE-BIND (TYPE NUM DATA) (RECEIVE-PACKET) (COND ((FAILP TYPE) (SEND-NACK 0) (GOTO-STATE RECEIVE-SERVER-IDLE)) ((NOT (= NUM 0)) (SEND-ERROR-PACKET "bad sequence number - ~S" NUM)) ((CHAR= TYPE #\I) ;; Initialize & send ACK (INITIALIZE-PARAMETERS DATA) (GOTO-STATE RECEIVE-SERVER-IDLE)) ((CHAR= TYPE #\S) ;; Remote host wants to send a file; we should prepare to receive. (INITIALIZE-PARAMETERS DATA) (BUMP-SEQUENCE-NUMBER) (GOTO-STATE RECEIVE-FILE-HEADER)) ((CHAR= TYPE #\R) ;; Remote host wants to receive a file, i.e. we should send it. (LET* ((FILESPEC (UNKLUDGIFY-STRING DATA)) (XFER-LIST (FILES-MATCHING FILESPEC))) (COND ((NULL XFER-LIST) (SEND-ERROR-PACKET "no files matching this specification") (GOTO-STATE RECEIVE-SERVER-IDLE)) (T (SETQ *FILE-TRANSFER-LIST* (MAKE-TRANSFER-LIST XFER-LIST FILESPEC)) (DEBUG-MESSAGE "~&Files to send: ~S" *FILE-TRANSFER-LIST*) (GOTO-STATE SEND-INIT))))) ((CHAR= TYPE #\G) (COND ((< (STRING-LENGTH DATA) 1) (SEND-ERROR-PACKET "ill-formed generic command packet") (GOTO-STATE RECEIVE-SERVER-IDLE)) ((CHAR= (STRING-ELT DATA 0) #\F) (SEND-ACK *SEQUENCE-NUMBER*) (*THROW 'DONE 'FINISHED)) ((CHAR= (STRING-ELT DATA 0) #\L) (SEND-ACK *SEQUENCE-NUMBER*) (*THROW 'DONE 'LOGOUT)) ;; Unimplemented: Cwd, Directory, diskUsage, ;; dEletefile, Type, Rename, Kopy, Whoisloggedin, ;; sendMessage, runProgram, Journal, Variable (T (SEND-ERROR-PACKET "unimplemented generic command - ~A" (UNKLUDGIFY-STRING DATA)) (GOTO-STATE RECEIVE-SERVER-IDLE)))) (T (SEND-ERROR-PACKET "unimplemented server command - ~C" TYPE) (GOTO-STATE RECEIVE-SERVER-IDLE))))) ;;; Rec_Init - entry point for non-server RECEIVE command (pretty useless) (DEFSTATE RECEIVE-INIT (SETQ *SEQUENCE-NUMBER* 0) (MULTIPLE-VALUE-BIND (TYPE NUM DATA) (RECEIVE-PACKET) (COND ((AND (CHAR= TYPE #\S) (= NUM 0)) (INITIALIZE-PARAMETERS DATA) (BUMP-SEQUENCE-NUMBER) (GOTO-STATE RECEIVE-FILE-HEADER)) ((FAILP TYPE) (SEND-NACK 0) (BUMP-RETRY-COUNT) (GOTO-STATE RECEIVE-INIT)) (T (SEND-NACK 0) (ABORT))))) ;;; Rec_File -- look for a file header or EOT message (DEFSTATE RECEIVE-FILE-HEADER (MULTIPLE-VALUE-BIND (TYPE NUM PACKET) (RECEIVE-PACKET) (COND ((AND (CHAR= TYPE #\F) (= NUM *SEQUENCE-NUMBER*)) ;; File header; ignore it if this is a GET command. (IF (NULL *FILE-TRANSFER-LIST*) (LET ((REMOTE (UNKLUDGIFY-STRING PACKET))) (PUSH (CONS REMOTE (PATHNAME REMOTE)) *FILE-TRANSFER-LIST*))) (LET ((LOCAL-PATHNAME (CDAR *FILE-TRANSFER-LIST*))) (COND ((SETQ *STREAM* (MAYBE-OPEN-FOR-OUTPUT LOCAL-PATHNAME)) (MESSAGE "~&Receiving ~A as ~A" (CAAR *FILE-TRANSFER-LIST*) (NAMESTRING (PATHNAME->NAMELIST LOCAL-PATHNAME))) (SEND-ACK NUM) (BUMP-SEQUENCE-NUMBER) (GOTO-STATE RECEIVE-DATA)) (T (MESSAGE "~&Can't create ~S" LOCAL-PATHNAME) (SEND-ERROR-PACKET "Can't create ~S" LOCAL-PATHNAME) (ABORT))))) ((AND (CHAR= TYPE #\B) (= NUM *SEQUENCE-NUMBER*)) ;; End of transmission (SEND-ACK NUM) (COMPLETE)) ((AND (CHAR= TYPE #\S) (= NUM (PREVIOUS-SEQUENCE-NUMBER))) ;; Send init (SEND-PARAMETERS #\Y NUM) (BUMP-RETRY-COUNT) (GOTO-STATE RECEIVE-FILE-HEADER)) ((AND (CHAR= TYPE #\Z) (= NUM (PREVIOUS-SEQUENCE-NUMBER))) ;; End of file (SEND-ACK NUM) (BUMP-RETRY-COUNT) (GOTO-STATE RECEIVE-FILE-HEADER)) ((FAILP TYPE) ;; Timeout (SEND-NACK *SEQUENCE-NUMBER*) (GOTO-STATE RECEIVE-FILE-HEADER)) (T (SEND-ERROR-PACKET "error receiving file header") (ABORT))))) ;;; Rec_Data - Receive data up to end of file (DEFSTATE RECEIVE-DATA (MULTIPLE-VALUE-BIND (TYPE NUM DATA) (RECEIVE-PACKET) (COND ((AND (CHAR= TYPE #\D) (= NUM *SEQUENCE-NUMBER*)) (EMPTY-BUFFER DATA) (SEND-ACK NUM) (BUMP-SEQUENCE-NUMBER) (GOTO-STATE RECEIVE-DATA)) ((AND (OR (CHAR= TYPE #\D) (CHAR= TYPE #\F)) (= NUM (PREVIOUS-SEQUENCE-NUMBER))) (SEND-ACK NUM) (BUMP-RETRY-COUNT) (GOTO-STATE RECEIVE-DATA)) ((AND (CHAR= TYPE #\Z) (= NUM *SEQUENCE-NUMBER*)) (LET ((FILENAME (PATHNAME->NAMELIST (CDAR *FILE-TRANSFER-LIST*))) (IO-LOSSAGE #'(LAMBDA IGNORE (SEND-ERROR-PACKET "error closing file") (ABORT)))) (COND ((OR (< (STRING-LENGTH DATA) 1) (NOT (CHAR= (STRING-ELT DATA 0) #\D))) (RENAMEF *STREAM* FILENAME) (MESSAGE "~&File received successfully: ~A" (NAMESTRING FILENAME))) (T ;; Discard (DELETEF *STREAM*) (MESSAGE "~&File discarded: ~A" (NAMESTRING FILENAME)))) (SEND-ACK NUM) (SETQ *STREAM* NIL) (POP *FILE-TRANSFER-LIST*) (BUMP-SEQUENCE-NUMBER) (GOTO-STATE RECEIVE-FILE-HEADER))) ((FAILP TYPE) (SEND-NACK *SEQUENCE-NUMBER*) (BUMP-RETRY-COUNT) (GOTO-STATE RECEIVE-DATA)) (T (IF (NOT (ERRP TYPE)) (SEND-ERROR-PACKET "error during data reception")) (ABORT))))) ;;; Send_Init - entry for SEND command (DEFSTATE SEND-INIT (SETQ *SEQUENCE-NUMBER* 0) (SEND-PARAMETERS #\S 0) (MULTIPLE-VALUE-BIND (REPLY NUM PACKET) (RECEIVE-PACKET) (COND ((AND (ACKP REPLY) (= *SEQUENCE-NUMBER* NUM)) (SETQ *EOL* 0 *QUOTE* 0) ;Initialize questionable params. (DECODE-PARAMETERS PACKET) ;Check and set defaults (IF (ZEROP *EOL*) (SETQ *EOL* *MY-EOL*)) (IF (ZEROP *QUOTE*) (SETQ *QUOTE* *MY-QUOTE*)) (BUMP-SEQUENCE-NUMBER) (GOTO-STATE OPEN-FILE)) ((ERRP REPLY) (ABORT)) (T (GOTO-STATE SEND-INIT))))) ;;; Open_File (DEFSTATE OPEN-FILE (COND ((NULL *FILE-TRANSFER-LIST*) (GOTO-STATE SEND-BREAK)) (T (LET ((LOCAL-FILENAME (CAAR *FILE-TRANSFER-LIST*))) (COND ((NOT (SETQ *STREAM* (MAYBE-OPEN-FOR-INPUT LOCAL-FILENAME))) ;; This ain't right, but what's to do for it?? (MESSAGE "~&Can't open file ~A" LOCAL-FILENAME) (POP *FILE-TRANSFER-LIST*) (SETQ *DISCARD* T) (GOTO-STATE SEND-EOF)) (T (GOTO-STATE SEND-FILE-HEADER))))))) ;;; Send_File (DEFUN NORMAL-ACKP (TYPE NUM) (OR (AND (ACKP TYPE) (= NUM *SEQUENCE-NUMBER*)) (AND (NACKP TYPE) (= NUM (NEXT-SEQUENCE-NUMBER))))) (DEFUN NORMAL-NACKP (TYPE) (OR (FAILP TYPE) (NACKP TYPE))) (DEFSTATE SEND-FILE-HEADER (SEND-PACKET #\F *SEQUENCE-NUMBER* (KERMITIFY-PATHNAME (CDAR *FILE-TRANSFER-LIST*) *VERSION-NUMBERS?*)) (MULTIPLE-VALUE-BIND (REPLY NUM IGNORE) (RECEIVE-PACKET) (COND ((NORMAL-ACKP REPLY NUM) (BUMP-SEQUENCE-NUMBER) (GOTO-STATE SEND-DATA)) ((NORMAL-NACKP REPLY) (BUMP-RETRY-COUNT) (GOTO-STATE SEND-FILE-HEADER)) (T (IF (NOT (ERRP REPLY)) (SEND-ERROR-PACKET "error sending file header")) (ABORT))))) ;;; Send_Data -- Send contents of file (DEFSTATE SEND-DATA (IF (= *RETRY-COUNT* 0) (FILL-BUFFER *SEND-PACKET*)) (COND ((= (STRING-LENGTH *SEND-PACKET*) 0) (GOTO-STATE SEND-EOF)) (T (SEND-PACKET #\D *SEQUENCE-NUMBER* *SEND-PACKET*) (MULTIPLE-VALUE-BIND (REPLY NUM PACKET) (RECEIVE-PACKET) (COND ((NORMAL-ACKP REPLY NUM) (BUMP-SEQUENCE-NUMBER) (COND ((AND (> (STRING-LENGTH PACKET) 0) (OR (CHAR= (STRING-ELT PACKET 0) #\X) (CHAR= (STRING-ELT PACKET 0) #\Z))) (SETQ *DISCARD* T) (GOTO-STATE SEND-EOF)) (T (GOTO-STATE SEND-DATA)))) ((NORMAL-NACKP REPLY) (BUMP-RETRY-COUNT) (GOTO-STATE SEND-DATA)) (T (IF (NOT (ERRP REPLY)) (SEND-ERROR-PACKET "error during data transmission")) (ABORT))))))) ;;; Send_EOF -- Send end of file indicator (DEFSTATE SEND-EOF (SEND-PACKET #\Z *SEQUENCE-NUMBER* (IF *DISCARD* "Z" NIL)) (MULTIPLE-VALUE-BIND (REPLY NUM IGNORE) (RECEIVE-PACKET) (COND ((NORMAL-ACKP REPLY NUM) (MESSAGE "~&File sent successfully: ~A~%" (TRUENAME *STREAM*)) (CLOSE *STREAM*) (SETQ *STREAM* NIL) (POP *FILE-TRANSFER-LIST*) (BUMP-SEQUENCE-NUMBER) (SETQ *DISCARD* NIL) (GOTO-STATE OPEN-FILE)) ((NORMAL-NACKP REPLY) (GOTO-STATE SEND-EOF)) (T (SETQ *DISCARD* NIL) (IF (NOT (ERRP REPLY)) (SEND-ERROR-PACKET "error during EOF transmission")) (ABORT))))) ;;; Send_Break - End of transaction (DEFSTATE SEND-BREAK (SEND-PACKET #\B *SEQUENCE-NUMBER* NIL) (MULTIPLE-VALUE-BIND (REPLY NUM IGNORE) (RECEIVE-PACKET) (COND ((OR (AND (ACKP REPLY) (= NUM *SEQUENCE-NUMBER*)) (AND (NACKP REPLY) (= NUM 0))) (COMPLETE)) ((OR (AND (NACKP REPLY) (= NUM *SEQUENCE-NUMBER*)) (FAILP REPLY)) (GOTO-STATE SEND-BREAK)) (T (IF (NOT (ERRP TYPE)) (SEND-ERROR-PACKET "error during break transmission")) (ABORT))))) ;;; Send_Gen_Cmd - Command to server (DEFSTATE SEND-MISC (CASEQ *COMMAND* ((FINISH) (SEND-PACKET #\G 0 "F")) ((LOGOUT) (SEND-PACKET #\G 0 "L")) ((GET) (SEND-PACKET #\R 0 (KERMITIFY-PATHNAME (CDAR *FILE-TRANSFER-LIST*) *VERSION-NUMBERS?*))) (T (ERROR "weird command in SEND-MISC" *COMMAND* 'FAIL-ACT))) (MULTIPLE-VALUE-BIND (TYPE NUM DATA) (RECEIVE-PACKET) (COND ((AND (ACKP TYPE) (= NUM 0)) (MESSAGE "~&OK") (COMPLETE)) ((AND (CHAR= TYPE #\S) (= NUM 0) (>= (STRING-LENGTH DATA) 6)) (DECODE-PARAMETERS DATA) (SEND-PARAMETERS #\Y 0) (BUMP-SEQUENCE-NUMBER) (GOTO-STATE RECEIVE-FILE-HEADER)) ((NORMAL-NACKP TYPE) (BUMP-RETRY-COUNT) (GOTO-STATE SEND-MISC)) (T (IF (NOT (ERRP TYPE)) (SEND-ERROR-PACKET "error during command transmission")) (ABORT))))) ;;; Packet sending and receiving (DEFUN SEND-ERROR-PACKET (&REST CRUFT) (LET* ((FROB (LEXPR-FUNCALL #'FORMAT NIL CRUFT)) (FROB (FORMAT NIL "Kermit-ITS: ~A" FROB))) (DEBUG-MESSAGE "~&Sending error packet: ~A" FROB) (SEND-PACKET #\E *SEQUENCE-NUMBER* FROB))) (DEFUN SEND-ACK (NUM) (SEND-PACKET #\Y NUM NIL)) (DEFUN SEND-NACK (NUM) (SEND-PACKET #\N NUM NIL)) ;;; Remember remote parameters and ackowledge with ours (DEFUN INITIALIZE-PARAMETERS (DATA) (DECODE-PARAMETERS DATA) (SEND-PARAMETERS #\Y *SEQUENCE-NUMBER*)) (DEFUN DECODE-PARAMETERS (DATA) (SETQ *SEND-PACKET-SIZE* (UNCHAR (STRING-ELT DATA 0))) (SETQ *TIMEOUT* (UNCHAR (STRING-ELT DATA 1))) (SETQ *PAD* (UNCHAR (STRING-ELT DATA 2))) (SETQ *PAD-CHAR* (CTL (STRING-ELT DATA 3))) (SETQ *EOL* (UNCHAR (STRING-ELT DATA 4))) (SETQ *QUOTE* (STRING-ELT DATA 5))) (DEFUN SEND-PARAMETERS (TYPE NUM) (LET ((PACKET *SEND-PACKET*)) (SET-STRING-ELT PACKET 0 (TOCHAR *RECEIVE-PACKET-SIZE*)) (SET-STRING-ELT PACKET 1 (TOCHAR *MY-TIMEOUT*)) (SET-STRING-ELT PACKET 2 (TOCHAR *MY-PAD*)) (SET-STRING-ELT PACKET 3 (CTL *MY-PAD-CHAR*)) (SET-STRING-ELT PACKET 4 (TOCHAR *MY-EOL*)) (SET-STRING-ELT PACKET 5 *MY-QUOTE*) (SETF (FILL-POINTER PACKET) 6) (SEND-PACKET TYPE NUM PACKET))) ;;; SEND-PACKET ;;; TYPE -- a number, the type of packet this is. ;;; NUM -- a number, the packet-number of this packet. ;;; DATA -- a string, i.e. an art-string type of array, the data of this pkt. (DEFUN SEND-PACKET (TYPE NUM DATA) (LET ((CHKSUM 0) (LEN (IF DATA (STRING-LENGTH DATA) 0))) (DECLARE (FIXNUM CHKSUM LEN)) (DEBUG-MESSAGE "~&Sent packet: type = ~3C, num = ~3D~& Data = ~A" TYPE NUM (IF DATA (UNKLUDGIFY-STRING DATA) "")) (DO ((I 0 (1+ I))) ((>= I *PAD*)) (SERIAL-WRITE-CHAR *PAD-CHAR*)) (SERIAL-WRITE-CHAR *SOH*) (INCF CHKSUM (SERIAL-WRITE-CHAR (TOCHAR (+ LEN 3)))) (INCF CHKSUM (SERIAL-WRITE-CHAR (TOCHAR NUM))) (INCF CHKSUM (SERIAL-WRITE-CHAR TYPE)) (IF DATA (DO ((I 0 (1+ I))) ((>= I LEN)) (DECLARE (FIXNUM I)) (INCF CHKSUM (SERIAL-WRITE-CHAR (STRING-ELT DATA I))))) (SERIAL-WRITE-CHAR (TOCHAR (COMPUTE-FINAL-CHECKSUM CHKSUM))) (SERIAL-WRITE-CHAR *EOL*) (FORCE-OUTPUT *SERIAL-OUTPUT*))) ;;; RECEIVE-PACKET ;;; Values returned are, in order: ;;; TYPE -- a character (fixnum), in {#\A, #\S, ...}. E.g. #\A means "abort". ;;; NUM -- a number, the packet-number of this packet. ;;; DATA -- a string, the data of this packet, which is as many ;;; characters as appropriate/desired for this type of packet. ;;; Many callers need only one (usually the type) value. (DEFUN RECEIVE-PACKET () (*CATCH 'TIMEOUT (UNWIND-PROTECT (PROGN (ALARMCLOCK 'TIME (MAX *TIMEOUT* *MIN-TIMEOUT*)) (REALLY-RECEIVE-PACKET)) (ALARMCLOCK 'TIME NIL)))) (DEFUN ALARMCLOCK-HANDLER (IGNORE) (DEBUG-MESSAGE "~&RECEIVE-PACKET timed out") (*THROW 'TIMEOUT *TIMED-OUT*)) (SETQ ALARMCLOCK #'ALARMCLOCK-HANDLER) (DEFUN REALLY-RECEIVE-PACKET () (LET ((CH 0) (TYPE 0) (I 0) (CCHKSUM 0) (RCHKSUM 0) (LEN 0) (NUM 0) (DATA *RECEIVE-PACKET*)) (DECLARE (FIXNUM CH TYPE I CCHKSUM RCHKSUM LEN NUM)) (PROG () CONTINUE (SETQ CH (SERIAL-READ-CHAR)) (COND ((NOT (CHAR= CH *SOH*)) (GO CONTINUE))) (SETQ CH (SERIAL-READ-CHAR)) (IF (CHAR= CH *SOH*) (GO CONTINUE)) (SETQ CCHKSUM CH) ;OK, start checksum (SETQ LEN (- (UNCHAR CH) 3)) ;Get character count ;; (if *debug?* (format t "<~d>" len)) (COND ((OR (< LEN 0) (> LEN (- *MAX-PACKET-SIZE* 3))) (DEBUG-MESSAGE "~&RECEIVE-PACKET got illegal packet length: ~D" LEN) (RETURN (VALUES *LENGTH-ERROR* NUM DATA)))) (SETQ CH (SERIAL-READ-CHAR)) (IF (CHAR= CH *SOH*) (GO CONTINUE)) (INCF CCHKSUM CH) ;OK, update checksum (SETQ NUM (UNCHAR CH)) ;Get packet number (SETQ CH (SERIAL-READ-CHAR)) (IF (CHAR= CH *SOH*) (GO CONTINUE)) (INCF CCHKSUM CH) ;OK, update checksum (SETQ TYPE CH) ;Get packet type (SETQ I 0) READ-THE-PACKET (COND ((< I LEN) (SETQ CH (SERIAL-READ-CHAR)) (IF (CHAR= CH *SOH*) (GO CONTINUE)) (INCF CCHKSUM CH) ;OK, update checksum (SET-STRING-ELT DATA I CH) (INCF I) (GO READ-THE-PACKET))) (SET-STRING-ELT DATA LEN 0) ;Mark end of data (SETF (FILL-POINTER DATA) LEN) (SETQ CH (SERIAL-READ-CHAR)) (SETQ RCHKSUM (UNCHAR CH)) ;OK, get checksum (SETQ CH (SERIAL-READ-CHAR)) (IF (CHAR= CH *SOH*) (GO CONTINUE)) ;OK, get eol char and toss it ;Safe! (DEBUG-MESSAGE "~&Received packet: type = ~3C, num = ~3D, len = ~3D~% Data = ~A" TYPE NUM LEN (UNKLUDGIFY-STRING DATA)) (SETQ CCHKSUM (COMPUTE-FINAL-CHECKSUM CCHKSUM)) (COND ((NOT (EQUAL CCHKSUM RCHKSUM)) (DEBUG-MESSAGE "RECEIVE-PACKET received bad checksum (expected ~A, got ~A)" RCHKSUM CCHKSUM) ;; Corruption, oh no! (RETURN (VALUES *CHECKSUM-ERROR* NUM DATA))) (T ;; Else checksum ok, 'uncorrupted'. (IF (ERRP TYPE) (MESSAGE "~&Aborting with following error from remote host:~% ~S~%" (UNKLUDGIFY-STRING DATA))) (RETURN (VALUES TYPE NUM DATA))))))) ;;; Serial I/O (DEFUN INIT-SERIAL-I/O (&OPTIONAL (TTYNAME 'TTY)) (IF *SERIAL-INPUT* (TERMINATE-SERIAL-I/O)) (SETQ *SERIAL-INPUT* (OPEN `((,TTYNAME)) '(TTY SINGLE IMAGE IN))) (SETQ *SERIAL-OUTPUT* (OPEN `((,TTYNAME)) '(TTY BLOCK IMAGE OUT)))) (DEFUN TERMINATE-SERIAL-I/O () (CLOSE *SERIAL-INPUT*) (CLOSE *SERIAL-OUTPUT*) (SETQ *SERIAL-INPUT* NIL *SERIAL-OUTPUT* NIL)) (DEFUN SERIAL-STATE () (SYSCALL 3. 'TTYGET *SERIAL-INPUT*)) (DEFUN SET-SERIAL-STATE (TEMP) (SYSCALL 0. 'TTYSET *SERIAL-INPUT* (CAR TEMP) (CADR TEMP))) (DEFUN DISABLE-ECHOING (TEMP) (SYSCALL 0. 'TTYSET *SERIAL-INPUT* (LOGAND (CAR TEMP) #o070707070707) (LOGAND (CADR TEMP) #o070707070707))) ;(DEFUN ENABLE-ECHOING (TEMP) ; (SYSCALL 0. 'TTYSET *SERIAL-INPUT* ; (LOGIOR (CAR TEMP) #o202020202020) ; (LOGIOR (CADR TEMP) #o202020200020))) (DEFUN SERIAL-READ-CHAR () (LET ((CH (+TYI *SERIAL-INPUT*))) (IF *IMAGE?* CH (LOGAND CH #o177)))) (DEFUN SERIAL-WRITE-CHAR (CH) (+TYO CH *SERIAL-OUTPUT*) CH) (DEFUN FLUSH-INPUT () (CLEAR-INPUT *SERIAL-INPUT*)) ;;; File I/O ;;; Put a pathname into Kermit standard form. (DEFUN KERMITIFY-PATHNAME (PATHNAME VERSIONP) (LET* ((NAME (STRINGIFY-COMPONENT (PATHNAME-NAME PATHNAME))) (TYPE (STRINGIFY-COMPONENT (PATHNAME-TYPE PATHNAME))) (VERSION (STRINGIFY-COMPONENT (PATHNAME-VERSION PATHNAME)))) (COND ((AND VERSIONP VERSION) (STRING-APPEND NAME "." (OR TYPE "") "." VERSION)) (TYPE (STRING-APPEND NAME "." TYPE)) (T NAME)))) (DEFUN STRINGIFY-COMPONENT (X) (COND ((EQ X ':WILD) "*") ((EQ X ':NEWEST) "0") ((EQ X ':OLDEST) "-1") ((NUMBERP X) (COERCE (EXPLODEN X) 'STRING)) (T X))) (DEFUN MAKE-TRANSFER-LIST (LOCAL-FILE-LIST REMOTE-FILESPEC) (DECLARE (SPECIAL REMOTE-FILESPEC)) (MAPCAR #'(LAMBDA (F) (CONS F (MERGE-PATHNAMES REMOTE-FILESPEC F (PATHNAME-VERSION F)))) LOCAL-FILE-LIST)) ;;; Opening files... (DEFUN MAYBE-OPEN-FOR-INPUT (FILESPEC) (*CATCH 'FNF (LET ((IO-LOSSAGE #'(LAMBDA IGNORE (*THROW 'FNF NIL)))) (OPEN (PATHNAME->NAMELIST FILESPEC) '(IN IMAGE BLOCK))))) (DEFUN MAYBE-OPEN-FOR-OUTPUT (FILESPEC) (*CATCH 'FNF (LET ((IO-LOSSAGE #'(LAMBDA IGNORE (*THROW 'FNF NIL)))) (OPEN (MERGEF '((* *) _KERM_ >) (PATHNAME->NAMELIST FILESPEC)) '(OUT IMAGE BLOCK))))) ;;; Write a packet buffer into the currently open local output file. (DEFUN EMPTY-BUFFER (BUFFER) "Put data from an incoming packet into a local disk file." (LET ((LEN (STRING-LENGTH BUFFER))) (DO ((I 0 (1+ I))) ((>= I LEN) BUFFER) (LET ((CH (STRING-ELT BUFFER I))) (DECLARE (FIXNUM CH)) (COND ((CHAR= CH *MY-QUOTE*) (SETQ CH (STRING-ELT BUFFER (INCF I))) (IF (NOT (CHAR= (LOGAND CH #o177) *MY-QUOTE*)) (SETQ CH (CTL CH))))) (IF (NOT *IMAGE?*) (SETQ CH (LOGAND CH #o177))) (+TYO CH *STREAM*))))) ;;; Fill a packet buffer from the currently open local input file. ;;; Returns the number of characters stored into the packet buffer. ;;; A return value of zero means that the end of the file has been reached. ;;; There are four ways to fill a buffer: ;;; 1. kermit default: 7-bit, quote all control characters. ;;; 3. image mode: send everything through with no conversion, except for ;;; quoting the quote character. ;;; 2, 4: excised by JAR. (DEFUN FILL-BUFFER (BUFFER) "fill buffer with the outgoing data from the file *STREAM* points to. Only control quoting is done; 8-bit and repeat count prefixes are not handled." ;;1; Changed 6 to 7!! See lmbugs.doc file item #14. (LET ((INDEX 0) (LIMIT (- *SEND-PACKET-SIZE* 7))) (DECLARE (FIXNUM LIMIT INDEX)) (DO () ((>= INDEX LIMIT)) (LET ((C (+TYI *STREAM*))) (DECLARE (FIXNUM C)) (COND ((OR (CHAR< C 0) (AND (NOT *IMAGE?*) (CHAR= C #^C))) ;??? ;; ITS end-of-file (RETURN NIL)) ((AND (CHAR>= C #\SPACE) (CHAR< C (CODE-CHAR #o177)) (NOT (CHAR= C *QUOTE*))) ;; Regular character (SET-STRING-ELT BUFFER INDEX C) (INCF INDEX)) ((AND (NOT *IMAGE?*) (CHAR>= C (CODE-CHAR #o177))) ;; Weird character. Don't send anything for it. (DEBUG-MESSAGE "~&The character ~C [~O octal] couldn't be translated to ASCII." C C)) (T (SET-STRING-ELT BUFFER INDEX *QUOTE*) (INCF INDEX) (SET-STRING-ELT BUFFER INDEX (IF (OR *IMAGE?* (CHAR>= C #\SPACE)) C (CTL C))) (INCF INDEX))))) (SETF (FILL-POINTER BUFFER) INDEX) INDEX))