home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Columbia Kermit
/
kermit.zip
/
archives
/
pfkerm.zip
/
PFKERM.TXT
< prev
Wrap
Text File
|
1997-11-08
|
13KB
|
439 lines
file pfkerm.txt
This is a conversion of the Forth block file KERMIT.DOW
so as to be readable as a plain text file. The complete
package, including the actual block files, is available
from my web site as
http://www.eskimo.com/~pygmy/pfkerm.zip
This pfkerm.txt and KERMIT.DOW provide the "shadow"
comments to match the source code in the file pfkerm.src
and KERMIT.SCR.
KERMIT.SCR
Contains a simple implementation of the Kermit
file transfer protocol.
copyright 1997 Frank Sergeant pygmy@pobox.com
809 W. San Antonio St.
San Marcos, TX 78666
This source code is not Public Domain or Shareware.
You may use it freely for any private or commercial purpose
provided you do so at your own risk.
( load screen Kermit file transfer protocol)
For the algorithm, see pp 98-113 of
_C Programmer's Guide to Serial Communications_
by Joe Campbell, Howard W. Sams & Company, 1987,
ISBN 0-672-22584-0.
Note, there are some errors in Campbell's examples.
( KERMIT)
GET-Y/N Wait for the user to press a y, n, Y, or N key.
Return true if y or Y. Return false if n or N.
TRY-AGAIN?
Display a message and ask whether the user wants
to try again. E.g.
" Drive not ready" TRY-AGAIN? IF ... THEN
.MSG clears the top 2 lines of the screen and displays a
message, leaving the cursor positioned just past the
message. E.g. " Starting the transfer ..." .MSG
( KERMIT)
MYMAXL maximum "len" we are willing to handle.
The transmitted LEN field includes SEQ, TYPE, DATA, CKSUM
fields. 94 maximum allowed under basic Kermit. Our
buffers must be 1 byte longer to hold the LEN field also.
OUT-BUF & IN-BUF buffers for building outgoing or receiving
incoming frames. We store LEN, SEQ, TYPE, DATA, CKSUM
fields, but not the SOH nor the ending CR.
OUTLEN & INLEN count bytes currently in the buffers
MAXL holds agreed-upon maximum "len" value, which is
the MIN of receiver's and sender's preferences.
a "character-ized" number is produced by adding a "space." The
result must be <= $7E, thus the original number must be
<= $5E (ie 94 decimal).
( KERMIT)
MAXL, QCTL, etc are the agreed-upon protocol parameters for
the session. INIT-LIMITS initializes these to the values
we would prefer to use. The sender and receiver exchange
an S-frame and an ACK-frame listing their preferences. We
then compromise by taking the MIN between them.
( KERMIT)
We make >LEN, >TYPE, etc relative to the start of the buffer
so we can use the same definitions for both the receiving and
sending buffers. >CKSUM assumes the LEN byte has been
initialized.
( KERMIT - compromise on the parameters)
COMPROMISE assumes we have an S frame in one buffer and its
ACK frame in the other buffer. We don't care whether we are
the sender or receiver. The compromise takes the more
conservative setting from each buffer as the actual protocol
parameter to use.
For now, we will ignore all the settings except for MAXL and
TIMEOUT, taking the MIN of MAXL and the MAX of TIMEOUT.
MYMENU cheap error handling in the case where the user
chooses to abort the file transfer process. Set up
your own menu ( ' MYREALMENU IS MYMENU ) or allow the
default 'no vector' error to occur.
KSER-IN gets a serial character and tests whether it is SOH,
all the while checking for a time-out. Returns
character and SOH-flag (true if character is SOH).
In case of time out, return up an extra level,
putting a 'V on the stack as the dummy frame type
indicating a time out followed by a true flag
indicating a 'good' check sum.
Note, KSER-IN is only called by GETFRAME and so is
always called with the correct stack depth. To test
it standalone, nest it once in a test word, as shown
in TEST-IN.
( KERMIT)
We "controlify" a control code (0-$1F, $7F) by flipping bit 6
and preceding it with the QCTL character (usually '#). The
QCTL character itself is escaped. We count QCTL as a control
character in CTRL? so we can escape it, but we only flip bit
6 for actual control characters. Also, consider $7E (~) to
be a control character, as it is used for repeat counts
(KEMIT puts a character into OUT-BUF and increments the count
KEMIT writes a character into OUT-BUF, escaping it if necessary.
ROOM? says whether there is room in the buffer for another
character. We require 2 bytes available in case the
next character needs to be escaped. If we allowed
high-bit escpaping we would require 3 bytes instead.
( KERMIT)
CK%% converts the raw checksum of all the bytes
after SOH into a checksum character by wrapping
and characterifying it according to the KERMIT algorithm.
CKSUM calculates a checksum on a buffer by adding the bytes
in the LEN SEQ TYPE & DATA fields and applying CK%%.
The LEN field must include the cksum byte.
CKSUM? Calculate the checksum character for the input frame
and compare it to the transmitted checksum character.
Return true if the checksum is good.
MODEM! sends a character to the modem. We defer it to make
testing easy.
DATA! builds an entire data field, stopping either when out
of source characters or out of room in OUT-BUF.
BUILD-FRAME Given the address and length of data to be
transferred and the type of the frame, put as much of
the data as will fit into a frame and return the address
and length of the remaining (i.e. unsent) data.
( KERMIT - debugging aids)
.FRAME .INB .OUTB are used for testing to dump the contents
of the buffers to the screen.
TEST1 TEST2 provide some test data
( KERMIT)
SENDFRAME sends an entire header, from SOH through 1-byte
checksum and ending carriage return, to the "modem."
It sends SOH, sends LEN+1 characters in the OUT-BUF,
and then sends a carriage return.
( KERMIT)
LIMITS provides data for use in building either an S-frame
or its ACK frame for purposes of negotiating
the protocol as to maximum frame length, etc.
Note that PADC is controlified, but seems not to
be "escaped" -- after all, we haven't agreed upon
the escape character at the time of sending the
S-frame. We build this frame directly into OUT-BUF
to prevent DATA! from escaping any characters.
We say we'll use (~) as the repeat character, but we
will _not_ use repeat counts when we transmit, but we
_will_ handle them when we receive. If the sender does
not escape actual tildes, then we will have a problem.
( KERMIT)
KINIT sends the 'send-init' frame. It must have sequence zero.
This is the 'S' frame sent by sender in response to the
receiver's initial NAKs.
KINITACK sends a reply to a 'send-init' frame. Before sending
KINITACK (if we are receiving) or after receiving
KINITACK (if we are sending), we must adjust our settings
to the minimum of the sender's and the receiver's requests
Note complex handling of COMPROMISE.
FILEHEADER sends the file name of the file to be transmitted.
EOF is sent at the end of each file we send. EOT is sent after
we finish sending all the files. Reciever sends ACK or NAK
after each frame is received, depending on whether chksum is
ok. ERROR is sent to abandon the session. I think we will
ignore an ATTRIB frame.
( KERMIT)
EXPECTED holds the count of bytes we expect to receive
following the length byte.
SETLENGTH handles the length count for an incoming frame,
initializing EXPECTED and INLEN and putting the
length byte into the input buffer.
PUT-IN-BUFFER puts input bytes into the buffer and returns
a flag that is true when all the expected bytes
have arrived.
GETFRAME is closely tied to KSER-IN and is the only word that
should ever call KSER-IN, as KSER-IN returns upward an extra
level in case of a timeout, supplying the type and cksum flag
(ie 'V -1). So, GETFRAME always succeeds, returning a type
and flag. It watches for an SOH in the middle of a frame and
starts over. What makes GETFRAME tricky is it needs to handle
the usual case as well as a timeout at any time as well as an
unexpected SOH at any time. What makes it simpler is pushing
some of the logic down to the word KSER-IN and letting KSER-IN
terminate not only itself but also GETFRAME in the case of a
timeout, thus producing a dummy V-frame. After that we no
longer have a timeout as a special case, we simply have an
additional "frame" type (i.e. a timeout frame).
( KERMIT)
GET-GOOD-FRAME continues to try to get a frame until one
arrives with a good checksum. It will try
forever unless the user aborts the transfer.
(See KSER-IN for test for user abort.)
IN-SEQ sequence number of the frame in the input buffer
GOOD-SEQ? true if the input frame's sequence number is the
expected sequence number.
( KERMIT)
(GETACK keeps getting frames until one comes in with a good
checksum. V-frames are ok.
GETACK keeps getting ack frames, handling or ignoring each, as
appropriate. It re-sends the data frame in case of a
V-frame (timeout) or a NAK with the correct sequence
number. It is used only by the sender. Later, it
could bail out if too many NAKs or timeouts occur in a
row, etc.
READ load up the buffer from the file in preparation for
transmitting it via the serial port
( KERMIT)
GET-FIRST-NAK ignores timeouts and sequence numbers and waits
for a NAK from the receiver.
SEND wait for the prompting NAK frame from receiver
send S-frame ( ie KINIT)
reset serial in to throw away any extra prompting NAKs
get S-frame ACK for SEQ 0
send the entire file, one D-frame at a time
close the file
send end of file and end of transmission
( KERMIT)
IN-DATA is a buffer for holding the UNCTRL'd data field. Make
it big in case lots of repeat counts are present.
C!+ stores a character and bumps the address (similar to C@+)
C@+- gets a character from the 'from' address, increments
the 'from' address and decrements the count of remaining
characters.
UNCTRL'd if the current character is the QCTL escape character,
get another character and unescape it.
REPEAT'd The most recent character was the tilde (~), indicating
the beginning of a 3 or 4 character repeat sequence.
Get the next character as the count and then the next 1
or 2 (if escaped) to find the value to be repeated, &
expand that repeated character into destination buffer.
UNCTRL copy the escaped and repeated source buffer,
unescaping and expanding as appropriate, to the
destination buffer.
>IN-DATA copies IN-BUF's data field, which may contain
escaped characters, to IN-DATA with escaped characters
converted to their actual values (and repeated counts
expanded).
( KERMIT)
BUILDFNAME extracts name of file to be received from an
input F frame and stores it in our KFNAME buffer
as a counted string (and an asciiz string suitable
for passing to DOS for creating the file).
RCVNAME this is what we do in response to an F-frame:
save the file name in the KFNAME buffer as
a counted, asciiz string, then create the file and
save the handle.
( KERMIT)
GET-NEXT Get the next frame we are expecting, ACKing or NAKing
as appropriate.
Always ack with the seq number we received, even if
it wasn't the seq number we expected, thus allowing
sender to continue. But, throw away frames that
do not have the expected seq number. Except, if
V-frame (ie timeout) or if a bad checksum, then
NAK with our expected sequence number.
It is possible a D-frame should not be ACK'd until
after we have written it to disk, in case disk writes
interfere with servicing the serial port.
WRITE Append input data to the file.
( KERMIT)
RECEIVE send NAK every second until we see SOH, then get
the rest of that first frame -- until we get the
S-frame. Then compromise on settings and send
an ack for the S-frame. Then, handle the frame
types, getting file name and opening it for an
F-frame, writing D-frames to the file, closing
the file upon getting a Z-frame, and exiting upon
getting a B-frame (EOT).