XModem Transfer
Volume Number: 2
Issue Number: 12
Column Tag: Forth Forum
Batch Text File Transfer by XMODEM
By Jörg Langowski, MacTutor Editorial Board, Grenoble, France
If you don't happen to have a tape backup unit for your Macintosh, but access to
some larger computer installation over a terminal line (with, usually, lots of disk
space), there is a slow but secure way to keep backups of your files by uploading them
to that machine. There are all sorts of communication utilities that help in doing so,
most of them using the XMODEM protocol for up- and downloading files.
The telecommunications programs I am aware of at this moment are excellent
tools for transferring single files to and from the Macintosh. If one wants to back up
one full disk by such a file transfer, however, the task becomes a little tedious since
one has to select one file at a time; particularly time-consuming if your disk has 327
short text files on it. The XMODEM standard does offer a batch transfer mode in which
the filename is transferred followed by the file, and if anyone out there knows of a
terminal program that has this mode implemented, please let me know.
For the time being, the transfer of one whole volume on a HFS disk from the Mac
to another system by XMODEM batch transfer is a neat little project to implement in
Forth. Besides being useful, it'll help us gain some insight into HFS file handling.
We will limit ourselves to transferring text files here. Since you all are aware
that files on the Macintosh consist of the data and the resource fork, applications and
other resource-containing files have to be changed into text files using a utility like
BINHEX first. However based on our example, it is rather easy to implement the
MacBinary format [a special standard combining both forks together with the Finder
information into one structure: see MacTutor V2#1], and transfer files of any type
automatically. At the present time, a text file backup system is fine for me, since the
documents I most often have to back up, like manuscripts or current versions of Mach
2 and NEON source files, are text files anyway.
The XMODEM Batch Transfer Protocol
How does an XMODEM file transfer proceed? Each file is split up into sectors of
128 bytes each (a relic of old CP/M times), each sector starting at one. A simple file
transfer (not batch mode) has the following protocol:
Simple File Transfer Protocol
Sender Receiver
waits for NAK,
80 sec timeout sends NAK at
10 sec intervals
Loop n times for n sectors
send header:
SOH ($01)
sector no. (0-255)
sector no. complement (255-0)
send sector data, 128 bytes
send checksum:
(header bytes + sector data ) mod 255
compute checksum and
compare with checksum
byte
send ACK ($06) if OK and
NAK if not
If ACK received, increment
sector no.
otherwise resend sector
End Loop
send EOT ($04)
send ACK
The transfer may be cancelled at any time by the sender or receiver transmitting
a Ctrl-X ($24) to the other end.
In batch mode, there is an additional protocol defined to transmit the filename
before the file transfer starts:
Batch Mode Protocol
Sender Receiver
waits for NAK,
80 sec timeout sends NAK at
10 sec intervals
sends ACK
awaits filename characters,
1 sec timeout
on error -> above NAK
Loop 11 times
send filename char
(bit 7=0, upper case)
add to checksum receive filename char
await ACK 1 sec timeout add to checksum
ACK
End Loop
send Ctrl-Z
add to checksum receive Ctrl-Z
add to checksum
send checksum
receive checksum +
verify
ACK if OK and "u" if not await ACK
- Normal file transfer starts -
NAK
SOH
.
.
.
EOT
ACK
expect NAK to start new
filename NAK
ACK for new file name
or EOT if finished
The routines necessary to implement this protocol are printed in Listing 1. [For
the single file transfer protocol, I adapted some routines from an article by Robert
Taylor in Dr. Dobbs Journal 83 (9/1983) p.66.]
The program in Listing 1 is best used by having some terminal emulation like
Mockterminal running on the Macintosh at the same time. Mach2 users may append this
code directly to the "Terminal Emulator" example on the Mach2 demo disk and then
start the terminal emulator before doing the transfer. For other Forths, the program
should be easily adaptable, you'll have to change the routines for setting the baud rate
and for doing the modem input and output.
Look at the definition of the words send and send-filename and the inner loop of
send for the implementation of the XMODEM protocol in Forth.
In order to be able to transfer one whole volume automatically, we must have
some means for accessing the files on a volume one by one. The example contains some
file and directory handling words for this purpose. I have also left in there some words
I needed in testing (like $openWD) which are not used in the actual implementation,
but could be useful at times.
The Mach2 word $open opens a file on the default volume, given a name string,
and returns a file ID number. In order to access files in any arbitrary folder (= HFS
volume), we have to get a volume ID number first and set the default volume to this ID.
The word promptvolID calls the standard file package to prompt the user to select a file
in the folder that is to be transferred. Then the volume ID of this folder is returned.
Calling setvol with a volume ID sets the default volume. getidxfile takes as its input a
volume ID and an index n and will look for the n-th file in the volume. The filename is
returned in a global parameter block, parblock, and can be used to open the file with
$open after setting the correct default volume. send-batch simply scans the default
volume for all files and transfers them one by one. So far, for text files only. Have fun
implementing the MacBinary standard.
Reader Feedback
I got several comments on the floating point routines that we published lately. Ed
Moskowitz from Winfield, IL points out correctly that the routines will not work with
the new Mach2 since they use D5 and D6. This is correct, and D5 and D6 have to be
added to the MOVEM.L save and restore list at the routine entry and exits to fix this bug.
Mike Morton from Cambridge, MA had some useful comments to make about
improving the speed of those routines. Excerpts from his letter:
"• If you're doing lots of _Pack4 calls, you can do a GetTrapAddress and call the
package directly, saving 30 to 50 microseconds ()
• Instead of BTST #7, D0, you can do TST.B D0 and use BMI to see if the bit is on.
...
when you AND #$7FFFFF to both D3 and D1 in the division routine, it's faster
to move the mask to a register just once and use it twice. ()
• When you're doing multiplications [and divisions, JL], you might optimize for
cases where some of the number is zero. So your four MULUs might test whether
the operands are zero and handle that case explicitly. I spent some time writing
32-bit fixed-point multiply routines a while ago, and this helped a lot because
many numbers don't have any bits on the bottom of the longword. If you expect
many of the f.p. numbers to not have many significant bits, this helps some.
()
Thanks, Ed and Mike, for your comments.
NEON News
One letter came clear from Tasmania, from Phil Barnard:
I am suffering from the new ROM syndrome at the moment, although the faster
and more spacious drives are very pleasant after the original units. Having to replace
the interrupt vectors for the interrupt button was a nuisance. As you are no doubt
aware the new ROM/System does not present the familiar bomb-box, but an 'empty'
dialog.
It may be that the new dialog can be used to resume in the same fashion as the old
one, but I don't yet know how. G sends it back into any loop that you may wish
to exit. Looking at the register contents by typing A0 through D7 is of limited debugging
use. Can it do anything else?
[Why, I even installed a control panel with flashing LEDs and toggle switches on
the top of my Mac. Why do you want anything else? ... Well, G 40F6D8, of course, you
probably know by now that that is the address of _Exittoshell and will (sometimes)
send you straight back to the Finder. I don't use it for many other things. JL]
"The following routine brings back the old familiar bomb-box on interrupt.
\ installation of custom interrupt vectors; Phil Barnard
\ ** INTERRUPT VECTORS **
\ prints out the seven vectors from the interrupt table
: get.ints ( -- ) cr
$ 64 -base @ . cr \ VIA
$ 68 -base @ . cr \ SCC
$ 6C -base @ . cr \ VIA & SCC
$ 70 -base @ . cr \ Debugging Button
$ 74 -base @ . cr \
$ 78 -base @ . cr \
$ 7C -base @ . cr \
CREATE RTS $ 4E75 NEXT,
\ installs RTS in the last four (debugging) vectors in the interrupt
table
: put.ints ( -- )
$ 80 $ 70 do 'c RTS +base i -base ! 4 +loop get.ints ;
Thanks, Phil. I'll deal with more NEON stuff in the next column where I'll also
have some time to write about the new release of NEON, version 2.0. Here only a few
headlines:
• NEON 2.0 is fully HFS and Switcher compatible; a new class pathlist has been
added to specify HFS search paths. The assembler supports the new ROM calls.
• Applications install as a one piece file, no need to keep a copy of the installed
NEON kernel on the disk.
• A decompiler has been added that will handle simple definitions, classes, objects
and methods.
• A number of bugs, including the famous IC! have been fixed.
More details about NEON 2.0 and other NEON things later. Till then, good
holidays.
Listing 1: XMODEM batch file downloader
( Implementation of Modem7 file transfer protocol,
© 9/86 J. Langowski for MacTutor )
only forth also mac also i/o also assembler
decimal
60 user fileID
10 constant maxerr 0 constant nul 24 constant can
4 constant eot 6 constant ack 21 constant nak
1 constant soh 26 constant ctrl-z
117 constant bdnmch
" Select file on volume to upload:" constant seldirtext
variable time-ct variable nak-ct variable cksum
variable filename 128 vallot
variable xbuffer 128 vallot
variable replyrecord 74 vallot
header parblock 12 allot ( 12 bytes junk )
header iocompl 4 allot ( at PB + 12 )
header iores 8 allot ( at PB + 16 )
header ioref# 4 allot ( at PB + 24 )
header iomisc 4 allot ( at PB + 28 )
100 allot ( some elbow room )
: long-timeout 1000 time-ct ! ; ( 17 sec timeout )
: short-timeout 100 time-ct ! ; ( 1.7 sec timeout )
: baud-rate ( - )
1CC0A COMM1 mode ( 9600 BAUD Xon/Xoff )
ABORT" Serial Driver Error" ; (8 bit, No Par, 2Stop)
: modout comm1 output emit console output ;