home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fred Fish Collection 1.5
/
ffcollection-1-5-1992-11.iso
/
ff_disks
/
200-299
/
ff226.lzh
/
Vlt
/
xprlib
/
xprotocol.doc
< prev
next >
Wrap
Text File
|
1989-06-25
|
56KB
|
1,624 lines
+----------------------------------------------------------------------+
| |
| D I S C L A I M E R N O T I C E |
| |
| This document and/or portions of the material and data furnished |
| herewith, was developed under sponsorship of the U.S. Government. |
| Neither the U.S. nor the U.S.D.O.E., nor the Leland Stanford |
| Junior University, nor their employees, nor their respective con- |
| tractors, subcontractors, or their employees, makes any warranty, |
| express or implied, or assumes any liability or responsibility for |
| accuracy, completeness or usefulness of any information, appara- |
| tus, product or process disclosed, or represents that its use will |
| not infringe privately-owned rights. Mention of any product, its |
| manufacturer, or suppliers shall not, nor is it intended to, imply |
| approval, disapproval, or fitness for any particular use. The U.S. |
| and the University at all times retain the right to use and dis- |
| seminate same for any purpose whatsoever. |
| |
+----------------------------------------------------------------------+
XPR: External File Transfer Protocols as Amiga Libraries.
=========================================================
Version - 4 June 1989
(C) Copyright 1989 by
W.G.J. Langeveld
Stanford Linear Accelerator Center
ABSTRACT
========
This document describes a standard method
of using Amiga shared libraries for the
implementation of external file transfer
protocols, as (partially) implemented in
the Amiga terminal emulator VLT.
1. Introduction.
================
One of the most frequently asked questions of the author of a
communications program is "Why don't you implement this wonderful file transfer
protocol in addition to the 25 you already have?". Clearly, implementing more
FTP's leads to larger code size and to increased product development time and
customer support requirements, unless there is a way to have the additional
protocols available as separate entities. One obvious way is to put the
additional FTP's in overlays, but that only mitigates the code size problem and
does not allow protocols to be used with communications programs of different
vendors. Better is to open the serial device as a shared port and to have a
completely separate program access it at the same time. However, this method has
the disadvantage that shared use of a single serial port can lead to
unpredictable results unless there is a well-established priority system
enforcing which program is allowed to write to the device at which time. The
advantage is that the FTP can now be developed separately and even by someone
other than the author of the communications program. There are variations
involving inter-process communication to add access control to the latter
system, but I will not go into further detail.
The system described here is based on Amiga shared libraries. The
library implements a small number of primary functions, such as "Send File(s)"
and "Receive File(s)". These functions are called with a single argument, a
pointer to an XPR_IO structure. This structure contains a number of things, the
more obvious one being a pointer to a null terminated string indicating which
files are to be sent or received and addresses of "call-back" functions inside
the code of the communications program to access the serial device, which is
opened typically in exclusive access. The scheme described here opens the
possibility for the Amiga community to write a multitude of file transfer
protocols, all rather small in size because they don't contain any overhead,
that work with any communications program following the rules outlined in this
document.
Possible problems with shared libraries are that they should be
reentrant and that they should, if possible, not open dos.library [1]. On the
other hand, these problems can easily be turned into advantages: for one,
reentrancy is not hard to accomplish and in addition when there are multiple
serial ports in use all of them can use the FTP with a single copy of the code.
Not having to open dos.library can be accomplished by having call-back functions
that provide all the DOS access needed in the original communications program.
Typically these DOS functions are already linked into the original code anyway,
and call-backs have to be provided for serial port access in any case.
For the sake of reentrancy across calls to the external protocol library
(XPR), a field for storing a pointer to a data area is added for use by the XPR
internally.
Section 2 explains the library structure itself. Section 3 covers the
XPR_IO structure and defines all the call-back functions. Section 4 describes an
example library for a simple ASCII transfer without bells or whistles and will
show how to code the library part of the call-backs. Section 5 shows how to set
up the interface on the communications program side.
Note: the examples are all for Manx C and assembler but should be easily
modifyable for Lattice or any other language. Not all source files are given in
this document. This archive, however, contains the example library plus all
files needed to link it and interface to it, for Manx. Specifically, the
routines that interface to XPR from VLT are in the "comm-program" subdirectory,
and the sources to the library are in the "library" subdirectory.
I would like to thank Marco Papa of Felsina Software for his help in
working out some of the details of the XPR standard.
Neither this document, nor the XPR standard, nor the other files in this
archive are in the public domain, but they may be freely distributed and used
for any purpose bearing in mind the stipulations given in the disclaimer above,
and with the proviso that in case of further distribution all files of this
archive must remain together and unchanged.
Reference:
[1] Jim Mackraz says that opening dos.library inside a library is not a good
idea.
2. XPR libraries.
=================
Each external FTP is implemented as a separate library which lives in
the libs: directory. It is mandatory that the names of XPR libraries start with
the three letters "xpr" so that they are easily identified. The template for the
name is xpr<protocol-name>.library, where <protocol-name> is a descriptive name
of the protocol that is implemented. Obvious examples would be xprkermit.library
and xprxmodem.library, but xprmykermit.library would be fine for a
user-customized kermit implementation. When thinking of a name, the implementer
of an XPR library should keep in mind that communication programs will likely
use the <protocol-name> part in their XPR requester.
Each XPR library in turn has four public functions. The functions are:
XProtocolCleanup()
XprotocolSetup()
XprotocolSend() and
XprotocolReceive()
in addition to the usual open, close expunge and reserved vectors. The library
skeleton is given in Appendix A.
Typically, a session with a terminal emulator using external protocols
would consist of
1. Selecting an external protocol (Using e.g. a file requester
showing only those files in libs: starting with "xpr").
2. Retrieving the library base XProtocolBase of the selected protocol
using OpenLibrary().
3. (Allocating and) initializing an XPR_IO structure.
4. Optionally calling XProtocolSetup() with the initialized structure.
5. Optionally Calling XProtocolSend() and/or XprotocolReceive() once or
multiple times to transfer files.
6. Optionally calling XProtocolSetup() to change parameters or to send
special commands. Perhaps repeat 5.
7. Calling XprotocolCleanup() to deallocate any resources allocated by
XProtocolSetup(). (Deallocate the XPR_IO structure if needed).
8. Closing the library using CloseLibrary().
9. Repeat the process, or
10. Exit.
All four XPR functions take a single argument, a pointer to an XPR_IO
structure, properly initialized as described in section 5. After
XProtocolSetup() has been called, the same XPR_IO structure should be used for
calls to any of the other functions. Only the xpr_filename field is allowed to
be changed between calls. In particular, the xpr_data field is for internal use
by the XPR library only! It should be initialized to NULL before calling
XProtocolSetup() and should not be changed by the communications program.
XProtocolSetup() should only be called at the request of the user.
XProtocolCleanup() should always be called before the library is closed.
In the form of a sample program, the rules above look like this:
/** MyWonderFullCommProgram.c
*
* Just an example. An actual implementation would likely look different.
*
**/
#include <stdio.h>
#include <functions.h>
#include "xproto.h"
struct Library *XProtocolBase = NULL;
#define SEND 1
#define RECEIVE 2
#define INITIALIZE 3
main()
{
struct XPR_IO io;
int user_said, Waiting_for_user_input();
XProtocolBase = OpenLibrary("xprascii.library", 0L);
if (XProtocolBase == NULL) {
printf("protocol not found\n");
exit(10);
}
/*
* Initialize structure (see later).
*/
xpr_setup(io);
/*
* Retrieve the initalization string
*/
Get_init_string_from_user_or_wherever(buffer);
io->xpr_filename = buffer;
XProtocolSetup(io);
while (user_said = Waiting_for_user_input(filename)) {
if (user_said == SEND) {
io->xpr_filename = filename;
XProtocolSend(io);
}
else if (user_said == RECEIVE) {
io->xpr_filename = filename;
XProtocolReceive(io);
}
else if (user_said == INITIALIZE) {
io->xpr_filename = NULL;
XProtocolSetup(io);
}
}
XProtocolCleanup(io);
CloseLibrary(XProtocolBase);
exit(0);
}
Clearly, only one FTP can be active at any particular instant in the
life of the session of the communications program. However, this is not really a
limitation in practice, and can be worked around at the cost of some amount of
programming effort.
XProtocolSetup(), XProtocolSend(), XProtocolReceive() and
XProtocolCleanup() return 0L on failure, non-zero on success.
3. The XPR_IO structure.
========================
The XPR_IO structure definition is given in Appendix B. The reader
should keep in mind that the callback functions are to be implemented by the
author of the communications program, not by the author of the external
protocol. However, most communications programs already have functions that
perform the operations listed here, so the implementation should not be too
difficult. Also, the communications program author is not required, strictly
speaking, to implement any of the functions: functions that are not implemented
should be indicated by initializing the corresponding XPR_IO field to NULL.
Obviously, a minimum set of functions must be implemented in order to be useful.
On the other hand, it is up to the implementer of the external protocol to
determine if the given set of functions is sufficient to perform the protocol
transfer. In case of missing functions (indicated by NULL fields in the XPR_IO
structure) suitable default actions should be taken.
We will now examine all the fields of XPR_IO in detail.
3.1 char *xpr_filename;
----------------------------
The xpr_filename field is used primarily to pass null-terminated strings
containing a file name (or file names specified by wild cards) to the functions
XProtocolSend() or XProtocolReceive(). The XPR implementer may elect to support
wild cards in the file name. Call-backs for finding the first and next filename
matching the pattern are provided in the XPR_IO structure, but on the other hand
XPR implementers should take care to check that these call-backs are implemented
by the communications program by testing the corresponding XPR_IO fields for
NULL. Never assume that all call-backs are implemented! If a particular
call-back without which the XPR cannot function is not implemented, the XPR
should fail gracefully.
The xpr_filename field can also be used to pass an initialization string
to XProtocolSetup(). Typically, if this field is left NULL in a call to
XProtocolSetup(), it would be the duty of XProtocolSetup() to query the user for
initialization information, using the xpr_gets function (see later). If an
initialization string is present, XProtocolSetup() should NOT query the user,
but this is left to the discretion of the implementer of the protocol, as is the
precise form of the initialization string. It is the duty of the communications
program to determine any default initialization strings for the protocol in
question. Suggested is the use of environment variables named for the protocols
they refer to, containing the initialization string. For the simple Ascii
protocol shown later, the user might have a statement like
set xprascii=50
in his startup sequence, or with AmigaDOS 1.3, a file called xprascii in his
env: directory containing the letters "50" (50 referring here to the number of
ticks delay between 80-character packets - obviously more extensive
initialization might be needed).
Given the presence of such default information, XProtocolSetup() should
always be called using the default initialization string right after opening the
library. Conversely, a mechanism (menu option) should be present in the
communications program to change the settings by calling XProtocolSetup() with a
NULL value for this field. On the other hand, if no default initialization
string is present, the legal situation can arise that XProtocolSetup() is never
called.
It should be noted that XProtocolSetup() can be used to implement any
commands not directly related to sending or receiving files. Examples that come
to mind are Kermit Bye and Finish. One should keep in mind, that typically the
communications program does not know what protocol it is running, much less what
commands that protocol might support. When the user asks to "setup" the external
protocol, XProtocolSetup() should be called with a NULL xpr_filename field,
and the external protocol should request a command, as stated before. In the
case of an external Kermit protocol, the user might type a Bye or Finish, and
the external protocol could act accordingly.
The xpr_filename field is ignored by the XProtocolCleanup() function.
3.2 long (*xpr_fopen)();
---------------------------
The xpr_fopen() call-back function works in most respects identically to
the stdio function fopen(). Calling sequence:
long fp = (*xpr_fopen)(char *filename, char *accessmode)
D0 A0 A1
The result is a FILE structure, but one should not count on it being a
particular one, since it may be compiler dependent. The return value should only
be used in calls to other stdio functions.
An error return is indicated when the function returns NULL.
Note that the arguments must be passed in registers A0 and A1
respectively. See also section 4.
3.3 long (*xpr_fclose)();
-----------------------------
The xpr_fclose() call-back function works in most respects identically
to the stdio function fclose(). Calling sequence:
(*xpr_fclose)(long filepointer)
A0
Note that the argument must be passed in register A0.
3.4 long (*xpr_fread)();
----------------------------
The xpr_fread() call-back function works in most respects identically to
the stdio function fread(). Calling sequence:
long count = (*xpr_fread)(char *buffer, long size, long count,
D0 A0 D0 D1
long fileptr)
A1
The function returns the actual number items read. The size argument is in bytes.
The function returns 0 on error or end of file.
3.5 long (*xpr_fwrite)();
-----------------------------
The xpr_fwrite() call-back function works in most respects identically
to the stdio function fwrite(). Calling sequence:
long count = (*xpr_fwrite)(char *buffer, long size, long count,
D0 A0 D0 D1
long fileptr)
A1
The function returns the actual number items written. The size argument is in
bytes. The function returns 0 on failure.
3.6 long (*xpr_sread)();
----------------------------
The xpr_sread() call-back function has the following calling sequence:
long count = (*xpr_sread)(char *buffer, long size, long timeout)
D0 A0 D0 D1
The first argument is a pointer to a buffer to receive the characters from the
serial port, with a size specified in the second argument. The third item is a
timeout in microseconds. The timeout may be set to 0L if the objective is to
just read any characters that may currently be available. When this argument is
non-zero, the function will not return until either the timeout period has
expired, or the buffer has filled up. The function returns the actual number of
characters put into the buffer, or -1L on error or timeout.
Note: the value 0L for the timeout argument is a special case. Remember
that AmigaDOS 1.3 may have problems with small non-zero values for timeouts.
3.7 long (*xpr_swrite)();
-----------------------------
The xpr_swrite() call-back function has the following calling sequence:
long status = (*xpr_swrite)(char *buffer, long size)
D0 A0 D0
This function writes a buffer with the given size to the serial port. It returns
0L on success, non-zero on failure.
3.8 long (*xpr_sflush)();
----------------------------
The xpr_sflush call-back function has the following calling sequence:
long status = (*xpr_sflush)()
D0
This function flushes all the data in the serial port input buffer. It is
typically used to recover after a protocol error. The function returns 0L on
success, non-zero on failure.
3.9 long (*xpr_update)();
-----------------------------
The xpr_update() call-back function has the following calling sequence:
(*xpr_update)(struct XPR_UPDATE *updatestruct)
A0
where:
struct XPR_UPDATE { long xpru_updatemask;
char *xpru_protocol;
char *xpru_filename;
long xpru_filesize;
char *xpru_msg;
char *xpru_errormsg;
long xpru_blocks;
long xpru_blocksize;
long xpru_bytes;
long xpru_errors;
long xpru_timeouts;
long xpru_packettype;
long xpru_packetdelay;
long xpru_chardelay;
char *xpru_blockcheck;
char *xpru_expecttime;
char *xpru_elapsedtime;
long xpru_datarate;
long xpru_reserved1;
long xpru_reserved2;
long xpru_reserved3;
long xpru_reserved4;
long xpru_reserved5;
}
This function is intended to communicate a variety of values and strings from
the external protocol to the communications program for display. Hence, the
display format itself (requester, text-I/O) is left to the implementer of the
communications program.
The mask xpru_updatemask indicates which of the other fields are valid,
i.e. have had their value updated. It is possible to update a single or multiple
values. Values that the external protocol does not use can be indicated by a
NULL for pointers and -1L for longs.
The possible bit values for the xpru_updatemask are:
#define XPRU_PROTOCOL 0x00000001L
#define XPRU_FILENAME 0x00000002L
#define XPRU_FILESIZE 0x00000004L
#define XPRU_MSG 0x00000008L
#define XPRU_ERRORMSG 0x00000010L
#define XPRU_BLOCKS 0x00000020L
#define XPRU_BLOCKSIZE 0x00000040L
#define XPRU_BYTES 0x00000080L
#define XPRU_ERRORS 0x00000100L
#define XPRU_TIMEOUTS 0x00000200L
#define XPRU_PACKETTYPE 0x00000400L
#define XPRU_PACKETDELAY 0x00000800L
#define XPRU_CHARDELAY 0x00001000L
#define XPRU_BLOCKCHECK 0x00002000L
#define XPRU_EXPECTTIME 0x00004000L
#define XPRU_ELAPSEDTIME 0x00008000L
#define XPRU_DATARATE 0x00010000L
The other fields of the XPR_UPDATE structure have the following
meaning:
xpru_protocol -- a string that indicates the name of the protocol used
xpru_filename -- the name of the file currently sent or received
xpru_filesize -- the size of the file
xpru_msg -- a "generic" message (50 characters or less)
xpru_errormsg -- an "error" message (50 characters or less)
xpru_blocks -- number of transferred blocks
xpru_blocksize -- size of most recently transferred block (bytes)
xpru_bytes -- number of transferred bytes
xpru_errors -- number of errors
xpru_timeouts -- number of timeouts
xpru_packettype -- type of packet (e.g. Kermit 'D'-packet)
xpru_packetdelay -- delay between packets in msec
xpru_chardelay -- delay between characters in msec
xpru_blockcheck -- block check type (e.g. "Checksum", "CRC-16", "CRC-32")
xpru_expecttime -- expected transfer time (e.g. "5 min 20 sec", "00:05:30")
xpru_elapsedtime -- elapsed time from start of transfer (see xpru_expecttime)
xpru_datarate -- rate of data transfer expressed in characters per second.
xpru_reserved1 -- for further expansion
... . -- ...
xpru_reserved5 -- for further expansion
The communications program is free to ignore any field and to only update
the ones it can handle.
If xpru_updatemask is equal to -1L, then ALL fields are either valid or
are unambiguously valued to indicate they are unused: NULL for pointers and -1L
for longs.
When writing an external protocol, it is advisable to keep any strings
as short as possible, and not longer than about 50 characters. Remember, if your
strings are too long, they may overflow whatever display mechanism the
communications program has chosen. It is also advisable to fill in as many
fields as you can, since the communications program may not choose to display
the ones you favor. When writing a communications program interface to XPR, on
the other hand, remember that strings can be as much as 50 characters long. If
you don't receive your favorite variables, it may be possible to compute them
from those that are given. It is good practice for the external protocol to call
xpr_update before starting the transfer with a message in the xpru_msg field
indicating whether the protocol is sending or receiving a file.
The XPR_UPDATE structure must be provided by the external protocol, and
must, of course be allocated either on the stack (as a local variable) or using
AllocMem or malloc(). This is needed to ensure reentrancy. In general, it is a
good idea to keep the entire library reentrant, since more than one
communications program may be using the same code simultaneously.
3.10 long (*xpr_chkabort)();
-------------------------------
The xpr_chkabort() call-back function has no arguments:
long status = (*xpr_chkabort)()
D0
When it returns non-zero, it means that the user has requested an abort. It is
possible to implement levels of abort by returning 1L, 2L, 3L, etc, depending on
the user's actions. The highest level of abort is -1L, which should be
interpreted to mean stop all actions and return. The chkabort function should be
called reasonably frequently.
3.11 long (*xpr_chkmisc)();
------------------------------
The xpr_chkmisc() call-back function has no arguments and returns
nothing.
(*xpr_chkmisc)()
It is intended to give the communications program that is currently executing
the external protocol transfer a chance to service its various message ports and
to respond to user actions. It should be called on a regular basis.
3.12 long (*xpr_gets)();
---------------------------
The xpr_gets() call-back function works somewhat like the stdio function
gets(). Calling sequence:
long status = (*xpr_gets)(char *prompt, char *buffer)
D0 A0 A1
The first argument is a pointer to a string containing a prompt, to be displayed
by the communications program in any manner it sees fit. The second argument
should be a pointer to a buffer to receive the user's response. It should have a
size of at least 256 bytes. The function returns 0L on failure or user
cancellation, non-zero on success. The buffer has to be supplied by the XPR.
3.13 long (*xpr_setserial)();
--------------------------------
The xpr_setserial() call-back function has the following calling
sequence:
long oldstatus = (*xpr_setserial)(long newstatus)
D0 D0
This function returns the current serial device status in encoded form. If the
newstatus argument is -1L, the serial device status will not be changed.
Otherwise the serial device status will be changed to newstatus. If oldstatus
is returned as -1L, the call failed and the serial status was not changed.
Note: if the serial device status is changed with this function, the
external protocol must change the status back to oldstatus before returning.
serial status longword:
.......................
byte 0: as the SerFlags field in IOExtSer structure.
bit 0: - parity on if set
bit 1: - parity odd if set
bit 2: - 7-wire protocol enabled if set
bit 3: - queued break if set
bit 4: - rad-boogie if set
bit 5: - shared if set
bit 6: - EOF mode if set
bit 7: - Xon/Xoff disabled if set
byte 1: summary of other settings
bit 0: - enable mark/space parity if set
bit 1: - parity mark if set, space otherwise
bit 2: - 2 stop bits if set, 1 otherwise
bit 3: - read wordlength is 7 if set, 8 otherwise
bit 4: - write wordlength is 7 if set, 8 otherwise
bit 5: - not used
bit 6: - not used
bit 7: - not used
byte 2: specifies one of a limited set of baud rates, as in
preferences.h.
- 110 baud = 0
- 300 baud = 1
- 1200 baud = 2
- 2400 baud = 3
- 4800 baud = 4
- 9600 baud = 5
- 19200 baud = 6
- midi = 7
- 38400 baud = 8
- 57600 baud = 9
- 76800 baud = 10
- 115200 baud = 11
byte 3: not used
3.14 long (*xpr_ffirst)();
-----------------------------
The xpr_ffirst() call-back function has the calling sequence:
long stateinfo = (*xpr_ffirst)(char *buffer, char *pattern)
D0 A0 A1
The first argument is a buffer to receive the first filename that matches the
pattern in the second argument. The function returns 0L if no file matching the
pattern was found, non-zero otherwise. The buffer should have a size of at least
256 bytes and is provided by the XPR. See also 3.14.
3.15 long (*xpr_fnext)();
----------------------------
The xpr_fnext() call-back function has the calling sequence:
long stateinfo = (*xpr_fnext)(long oldstate, char *buffer, char *pattern)
D0 D0 A0 A1
The first argument is a buffer to receive the next filename that matches the
pattern in the second argument. The function returns 0L if no further file
matching the pattern was found, non-zero otherwise. The buffer should have a
size of at least 256 bytes and is provided by the XPR.
Note: the value returned by xpr_ffirst and xpr_fnext may be used by the
implementing communications program to maintain state information, but the
mechanism is up to the implementer. If reentrancy is not required, state
information may be kept in global variables by the implementer, and the oldstate
argument can be ignored. However, the external protocol implementation must pass
the stateinfo variable returned by ffirst or fnext to the next invocation of
fnext.
3.16 long (*xpr_finfo)();
----------------------------
The xpr_finfo() call-back function has the calling sequence:
long info = (*xpr_finfo)(char *filename, long typeofinfo)
D0 A0 D0
This function returns information about a file given its name and the type of
information requested.
typeofinfo value: resulting info:
.......................................
1L file size
(other values) (to be determined)
3.17 long *xpr_reserved1;
-----------------------------
This field is reserved for future expansion. It should be initialized to
NULL by the communications program.
3.18 long *xpr_reserved2;
-----------------------------
This field is reserved for future expansion. It should be initialized to
NULL by the communications program.
3.19 long *xpr_data;
------------------------
This field is for internal use by the external protocol. Typically the
field is initialized to point to a structure containing information extracted
from the initialization string handed to or retrieved by the XProtocolSetup()
function, see section 2. The structure should be deallocated and the field
restored to NULL by the XProtocolCleanup() function. The communications program
should never access this field, except when initializing the XPR_IO structure:
the field should be initialized to NULL.
4. An example protocol.
=======================
The following is an annotated listing of an ascii upload protocol.
/** xprascii.c
*
* These are the protocol transfer routines for a simple ASCII upload.
*
**/
#include <exec/exec.h>
#include <functions.h>
#include <stdio.h>
/*
* xproto.h is the include file given in Appendix B.
*/
#include "xproto.h"
/*
* The following two strings must exist.
*/
char XPRname[] = "xprascii.library";
char XPRid[] = "xprascii 0.9 (May 89)\r\n";
UWORD XPRrevision = 9;
long atol();
/*
* The callxx...() routines are described later. They provide the
* assembler interface from the XPR library to the call-back routines.
*/
long calla(), callaa(), callad(), calladd(), calladda();
char *malloc();
/**
*
* Send a file
*
**/
long XProtocolSend(IO)
struct XPR_IO *IO;
{
long fp, r, i;
long brkflag = 0, fl = 0L, sd = 0L;
long (*xupdate)(), (*xswrite)(), (*xfopen)(), (*xfclose)(), (*xfread)(),
(*xsread)(), (*xchkabort)();
unsigned char *buff = NULL, *serbuff = NULL;
struct XPR_UPDATE xpru;
/*
* These are the call-backs we need. If any of them isn't provided, quit.
* Could do some error reporting if at least xupdate is there.
*/
if ((xupdate = IO->xpr_update) == NULL) return(0L);
if ((xswrite = IO->xpr_swrite) == NULL) return(0L);
if ((xfopen = IO->xpr_fopen) == NULL) return(0L);
if ((xfclose = IO->xpr_fclose) == NULL) return(0L);
if ((xfread = IO->xpr_fread) == NULL) return(0L);
if ((xsread = IO->xpr_sread) == NULL) return(0L);
if ((xchkabort = IO->xpr_chkabort) == NULL) return(0L);
/*
* Allocate a few buffers.
*/
buff = (unsigned char *) malloc(80);
serbuff = (unsigned char *) malloc(80);
/*
* If we ran out of memory, print a message.
* The argument needs to go in A0: calla does this for us.
*/
if (buff == NULL || serbuff == NULL) {
xpru.xpru_updatemask = XPRU_ERRORMSG;
xpru.xpru_errormsg = "Ran out of memory!";
calla(xupdate, &xpru);
return(0L);
}
/*
* Read the send delay, if a XProtocolSetup() was done before.
* If send delay is too large, cut it off at 10 seconds.
* In this example, the xpr_data field contains a null terminated string
* containing the number of ticks to delay each 80 characters.
*/
if (IO->xpr_data) {
sd = atol(IO->xpr_data);
if (sd > 500L) sd == 500L;
}
/*
* Open the file. One could do wild card detection here.
* xfopen requires two arguments, in a0 and a1 respectively.
* Again, this must be done in assembler, and callaa does it.
*/
fp = callaa(xfopen, IO->xpr_filename, "r");
if (fp == NULL) {
free(buff);
free(serbuff);
xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_FILENAME;
xpru.xpru_errormsg = "Failed to open input file";
xpru.xpru_filename = IO->xpr_filename;
calla(xupdate, &xpru);
return(0L);
}
/*
* Start the transfer. See 3.8 for a discussion on how to implement
* xupdate.
*/
xpru.xpru_updatemask = XPRU_MSG | XPRU_FILENAME;
xpru.xpru_msg = "Starting ASCII Send";
xpru.xpru_filename = IO->xpr_filename;
calla(xupdate, &xpru);
/*
* Now read 80 byte chunks from the file using xfread.
* xfread requires four arguments, a0, d0, d1 and a1.
*/
xpru.xpru_blocks = 0L;
while (r = calladda(xfread, buff, 1L, 80L, fp)) {
/*
* Convert line feeds to carriage returns before sending to host.
* fl counts the characters. Display how many characters are sent.
*/
for (i = 0L; i < r; i++) if (buff[i] == '\n') buff[i] = '\r';
fl += r;
xpru.xpru_updatemask = XPRU_BYTES | XPRU_BLOCKS | XPRU_BLOCKSIZE;
xpru.xpru_bytes = fl;
xpru.xpru_blocks++;
xpru.xpru_blocksize = r;
calla(xupdate, &xpru);
callad(xswrite, buff, r);
/*
* Every 80 bytes, put out a message and delay if requested.
*/
xpru.xpru_updatemask = XPRU_PACKETDELAY;
xpru.xpru_packetdelay = sd * 20L; /* msec! */
calla(xupdate, &xpru);
/*
* Can't use Delay() here, because Delay() is in dos.library!
* However writing an equivalent function using the timer.device is
* trivial.
*/
TimeOut(sd);
/*
* Eat any characters that might arrive from the serial port.
* calladd stores arg1 in a0, arg2 in d0, arg3 in d1.
* We're not really waiting for any characters: use a timeout of 0L.
*/
while (calladd(xsread, serbuff, 80L, 0L) > 0L) ;
/*
* Check for "abort" here. Perhaps should call chkmisc() as well.
*/
if (brkflag = xchkabort()) break;
}
/*
* Close the file
*/
calla(xfclose, fp);
free(buff);
free(serbuff);
/*
* If we got here through chkabort() say Aborted.
*/
xpru.xpru_updatemask = XPRU_MSG;
if (brkflag) xpru.xpru_msg = "Aborted";
else xpru.xpru_msg = "Done";
calla(xupdate, &xpru);
if (brkflag) return(0L);
else return(1L);
}
/**
*
* Receive a file.
*
**/
long XProtocolReceive(IO)
struct XPR_IO *IO;
{
long (*xupdate)();
struct XPR_UPDATE xpru;
if (xupdate = IO->xpr_update) {
xpru.xpru_updatemask = XPRU_MSG;
xpru.xpru_msg = "ASCII receive is not implemented. Use capture?";
calla(xupdate, &xpru);
}
return(0L);
}
/**
*
* Setup
*
**/
long XProtocolSetup(IO)
struct XPR_IO *IO;
{
long (*xupdate)(), (*xgets)();
struct XPR_UPDATE xpru;
if ((xupdate = IO->xpr_update) == NULL) return(0L);
if ((xgets = IO->xpr_gets) == NULL) return(0L);
/*
* Allocate a bit of memory for a data buffer
*/
if (IO->xpr_data == NULL) {
if ((IO->xpr_data = (long *) malloc(256)) == NULL) {
xpru.xpru_updatemask = XPRU_ERRORMSG;
xpru.xpru_errormsg = "ASCII - Out of memory!";
calla(xupdate, &xpru);
return(0L);
}
}
/*
* If setup string isn't handed to us, ask questions
*/
if (IO->xpr_filename == NULL) {
/*
* Get the value for the send dealy
*/
callaa(xgets, "Enter ASCII send delay (ticks, 1 tick = 20 msec)",
IO->xpr_data);
}
else {
strcpy(IO->xpr_data, IO->xpr_filename);
}
return(1L);
}
/**
*
* Cleanup
*
**/
long XProtocolCleanup(IO)
struct XPR_IO *IO;
{
if (IO->xpr_data) free(IO->xpr_data);
IO->xpr_data = NULL;
return(1L);
}
/**
*
* The following functions setup the proper registers for the call-back
* functions.
*
**/
#asm
public _callad
_callad:
movea.l 4(sp),a2 ; First argument is function
movea.l 8(sp),a0 ; Second argument goes in a0
move.l 12(sp),d0 ; Third argument goes in d0
jsr (a2)
rts
public _calladda
_calladda:
movea.l 4(sp),a2 ; First argument is function
movea.l 8(sp),a0 ; Second argument goes in a0
move.l 12(sp),d0 ; Third argument goes in d0
move.l 16(sp),d1 ; Fourth argument goes in d1
movea.l 20(sp),a1 ; Fifth argument goes in a1
jsr (a2)
rts
public _calla
_calla:
movea.l 4(sp),a2 ; First argument is function
movea.l 8(sp),a0 ; Second argument goes in a0
jsr (a2)
rts
public _callaa
_callaa:
movea.l 4(sp),a2 ; First argument is function
movea.l 8(sp),a0 ; Second argument goes in a0
movea.l 12(sp),a1 ; Third argument goes in a1
jsr (a2)
rts
public _calladd
_calladd:
movea.l 4(sp),a2 ; First argument is function
move.l 8(sp),a0 ; Second argument goes in a0
move.l 12(sp),d0 ; Third argument goes in d0
move.l 16(sp),d1 ; Fourth argument goes in d1
jsr (a2)
rts
#endasm
/*
* Could have added any other functions needed for other call-backs.
* Could have written a fancier single one... Could've...
*/
__
/ \ o /
-----/----\----/-----
/ o \__/
Clearly it isn't very hard to implement a simple protocol. More
elaborate protocols are straightforward extensions to the above example. Of
course, there are a few more standard files needed to make the above example
into a complete library (like Open, Close and Expunge functions and a ROM-Tag
structure) but those parts are the same for any library and aren't given here.
5. The interface to the communications program.
===============================================
The following is an annotated listing of a few call-back functions
as they are implemented in VLT. Also, it is shown how to initialize the
XPR_IO structure.
/** xprfuncs.c
*
* Call-back functions for eXternal PRotocol support
*
**/
#include <functions.h>
#include <exec/exec.h>
#include <stdio.h>
/*
* xproto.h is given in Appendix B
*/
#include "xproto.h"
/*
* xfer.h is a VLT private header file containing some information for
* file transfer protocols
*/
#include "xfer.h"
/*
* These are the C versions of the interface
*/
long vlt_update(), vlt_swrite(), vlt_fread(), vlt_fopen(),
vlt_fclose(), vlt_gets(), vlt_sread(), vlt_chkabort();
/*
* These are the assembly level glue functions, see vltface.asm
*/
extern long avlt_update(), avlt_swrite(), avlt_fread(), avlt_fopen(),
avlt_fclose(), avlt_gets(), avlt_sread(), avlt_chkabort();
/**
*
* This function initializes an XPR_IO structure.
*
**/
xpr_setup(IO)
struct XPR_IO *IO;
{
/*
* NULL out all the functions we don't do yet.
* Fill the other ones with the addresses to the assembler glue version
* of the interface routines. See vltface.asm
*/
IO->xpr_filename = NULL;
IO->xpr_fopen = avlt_fopen;
IO->xpr_fclose = avlt_fclose;
IO->xpr_fread = avlt_fread;
IO->xpr_fwrite = NULL;
IO->xpr_sread = avlt_sread;
IO->xpr_swrite = avlt_swrite;
IO->xpr_sflush = NULL;
IO->xpr_update = avlt_update;
IO->xpr_chkabort = avlt_chkabort;
IO->xpr_chkmisc = NULL;
IO->xpr_gets = avlt_gets;
IO->xpr_setserial = NULL;
IO->xpr_ffirst = NULL;
IO->xpr_fnext = NULL;
IO->xpr_finfo = NULL;
IO->xpr_reserved1 = NULL;
IO->xpr_reserved2 = NULL;
/*
* Especially, NULL out the XPR private data field.
*/
IO->xpr_data = NULL;
return;
}
/**
*
* Interface to VLT's MsgDisplay() function.
*
**/
/*
* These are formats for VLT's requester
*/
static char *xprnamfmt = "%s\n%s\n\n\n\n";
static char *filnamfmt = "\n\n%s\n\n\n";
static char *blksizfmt = "\n\n\n\nBlock: %6ld -- Block Size: %6ld\n";
static char *errtimfmt = "\n\n\n\n\nErrors: %6ld -- Timeouts: %6ld";
static char *delayfmt = "\n\n\n\n\nPacket delay %ld";
/*
* Below are some VLT globals to orchestrate the display
*/
long xpr_blocks = 0L, xpr_blocksize = 0L, xpr_errors = 0L, xpr_timeouts = 0L;
/*
* The function
*/
long vlt_update(x)
struct XPR_UPDATE *x;
{
extern struct Window *mywindow;
extern char *XPR_Name;
/*
* First time, determine the window size (50 chars wide, 5 lines tall).
*/
SetMsgWindow(mywindow, 50, 6);
/*
* Use VLT's PostMsg function to display all the information.
*/
if (x->xpru_updatemask & XPRU_PROTOCOL) {
PostMsg(mywindow, xprnamfmt, XPR_Name, x->xpru_protocol);
}
if (x->xpru_updatemask & XPRU_MSG) {
PostMsg(mywindow, xprnamfmt, XPR_Name, x->xpru_msg);
}
if (x->xpru_updatemask & XPRU_ERRORMSG) {
PostMsg(mywindow, xprnamfmt, XPR_Name, x->xpru_errormsg);
}
if (x->xpru_updatemask & XPRU_FILENAME) {
PostMsg(mywindow, filnamfmt, x->xpru_filename);
}
if (x->xpru_updatemask & XPRU_PACKETDELAY) {
PostMsg(mywindow, delayfmt, x->xpru_packetdelay);
}
if (x->xpru_updatemask & (XPRU_BLOCKS | XPRU_BLOCKSIZE)) {
if (x->xpru_updatemask & XPRU_BLOCKS) xpr_blocks = x->xpru_blocks;
if (x->xpru_updatemask & XPRU_BLOCKSIZE) xpr_blocksize = x->xpru_blocksize;
PostMsg(mywindow, blksizfmt, xpr_blocks, xpr_blocksize);
}
if (x->xpru_updatemask & (XPRU_ERRORS | XPRU_TIMEOUTS)) {
if (x->xpru_updatemask & XPRU_ERRORS) xpr_errors = x->xpru_errors;
if (x->xpru_updatemask & XPRU_TIMEOUTS) xpr_timeouts = x->xpru_timeouts;
PostMsg(mywindow, errtimfmt, xpr_errors, xpr_timeouts);
}
return(0L);
}
/**
*
* Prompt the user for input
*
**/
long vlt_gets(s, t)
char *s, *t;
{
/*
* Use VLT's DoRequest() function
*/
return((long) DoRequest(mywindow, t, s, NULL, " Cancel "));
}
/**
*
* Write a string to the serial port
*
**/
long vlt_swrite(s, n)
char *s;
long n;
{
/*
* Use VLT's SendString() function
*/
SendString(s, (int) n);
return(0L);
}
/**
*
* Read characters from the serial port
*
**/
long vlt_sread(buff, length, micros)
unsigned char *buff;
long length, micros;
{
extern int timeout;
long secs = 0L;
if (buff == NULL) return(-1L);
/*
* Convert timeout to seconds and micros if necessary
*/
if (micros) {
if (micros > 1000000L) {
secs = micros / 1000000L;
micros = micros % 1000000L;
}
}
/*
* Cheat! Only return a single character since we have such a nice
* readchar() function in VLT. One day I'll have to modify this to
* save the odd microsecond...
*/
buff[0] = (unsigned char) readchar(secs, micros);
/*
* VLT has a global called timeout. This comes in xfer.h.
* If the read was successful, return having read a single character.
*/
if (timeout == GOODREAD) return(1L);
/*
* Else return error condition
*/
return(-1L);
}
/**
*
* Interfaces to stdio
*
**/
long vlt_fopen(s, t)
char *s, *t;
{
return((long) fopen(s, t));
}
long vlt_fclose(fp)
FILE *fp;
{
return((long) fclose(fp));
}
long vlt_fread(buff, size, count, fp)
char *buff;
long size, count;
FILE *fp;
{
int res;
res = fread(buff, (int) size, (int) count, fp);
return((long) res);
}
/**
*
* Check for Abort
*
**/
long vlt_chkabort()
{
/*
* VLT aborts its protocols when the escape key is pressed.
* CheckForKey loops over the UserPort messages looking for an escape.
*/
return((long) CheckForKey(69));
}
__
/ \ o /
-----/----\----/-----
/ o \__/
Clearly, this part of the implementation isn't hard either. The only
thing left is the assembly level glue on the communications program side. You
may wonder at this point why all this assembly level stuff is necessary. It is
necessary because many programs and libraries are written in small code/small
data. This means that both the communications program and the library address
their code/data off of some register, in the case of Manx usually A4. The
problem is that the communications program and the library are loaded in
different parts of memory, while startup code takes care of setting up the
proper value for A4. And the values of A4 are different for the the
communications program and the library! Now, if you just call a library
function, the assembly level glue does a few things, among which are: (1) saving
the caller's A4 somewhere safe and (2) retrieving the A4 it stored somewhere
when the library was loaded. Then the library function is executed, and the
function returns to the glue. The glue then restores A4 to the state it was in
before the library call.
In the case of these call-back functions, we have to do the reverse.
After all, when a function like xpr_update is called, the current value of A4 is
the one that goes with the library's code. If the call-back function tries to
access any data back in the communications program, we're in big trouble.
So what the assembly part of the call-backs has to do is (1) save the
library's A4 (on the stack) and (2) get the value of A4 appropriate for the
communications program. Then we can push the various registers onto the stack,
call the C version of the call-back and then restore the value of A4 to what the
library wants.
For the above call-backs, the assembly level glue is listed below. This
concludes the documentation on external protocols using Amiga shared libraries.
If you have any questions, comments or suggestions, contact me on BIX.
Meanwhile, have fun!
;;; vltface.asm
;
; DESCRIPTION:
; ===========
;
; This is an interface to VLT callback functions from
; external protocol libraries.
;
; AUTHOR/DATE: W.G.J. Langeveld, March 1989.
; ============
;
;;;
public _geta4
setup macro
movem.l d2/d3/d4-d7/a2-a6,-(sp)
jsr _geta4 ; Get a4.
endm
push macro
move.l \1,-(sp)
endm
fix macro
ifc '\1',''
mexit
endc
ifle \1-8
addq.l #\1,sp
endc
ifgt \1-8
lea \1(sp),sp
endc
endm
restore macro
fix \1
movem.l (sp)+,d2/d3/d4-d7/a2-a6
rts
endm
public _avlt_fopen
public _vlt_fopen
public _avlt_fclose
public _vlt_fclose
public _avlt_fread
public _vlt_fread
public _avlt_sread
public _vlt_sread
public _avlt_swrite
public _vlt_swrite
public _avlt_update
public _vlt_update
public _avlt_chkabort
public _vlt_chkabort
public _avlt_gets
public _vlt_gets
_avlt_fopen:
setup
push a1
push a0
jsr _vlt_fopen
restore 8
_avlt_fclose:
setup
push a0
jsr _vlt_fclose
restore 4
_avlt_fread:
setup
push a1
push d1
push d0
push a0
jsr _vlt_fread
restore 16
_avlt_sread:
setup
push d1
push d0
push a0
jsr _vlt_sread
restore 12
_avlt_swrite:
setup
push d0
push a0
jsr _vlt_swrite
restore 8
_avlt_update:
setup
push a0
jsr _vlt_update
restore 4
_avlt_chkabort:
setup
jsr _vlt_chkabort
restore
_avlt_gets:
setup
push a1
push a0
jsr _vlt_gets
restore 8
__
/ \ o /
-----/----\----/-----
/ o \__/
Appendix A: XPR library skeleton.
=================================
;;; libface.asm
;
; DESCRIPTION:
; ===========
;
; This is the skeleton for an Amiga Exec library.
; This version is written for Aztec C. It is based on the example
; library by Jim Mackraz who got some stuff from Neil Katin.
; This library implements a protocol transfer library.
; All changes and additions by me.
;
; AUTHOR/DATE: W.G.J. Langeveld, February 1989.
; ============
;
;;;
include 'exec/types.i'
setup macro
movem.l d2/d3/d4-d7/a2-a6,-(sp)
jsr _geta4 ;set up a4 for small model
endm
push macro
move.l \1,-(sp)
endm
fix macro
ifc '\1',''
mexit
endc
ifle \1-8
addq.l #\1,sp
endc
ifgt \1-8
lea \1(sp),sp
endc
endm
restore macro
fix \1
movem.l (sp)+,d2/d3/d4-d7/a2-a6
rts
endm
dseg
public _libfunctab
_libfunctab:
dc.l XPRopen
dc.l XPRclose
dc.l XPRexpunge
dc.l $0000
dc.l XPRXProtocolCleanup
dc.l XPRXProtocolSetup
dc.l XPRXProtocolSend
dc.l XPRXProtocolReceive
dc.l $ffffffff
cseg
;=== library functions
public _XPROpen
public _XPRClose
public _XPRExpunge
public _XProtocolCleanup
public _XProtocolSetup
public _XProtocolSend
public _XProtocolReceive
public _geta4
XPRopen:
setup
push a6
jsr _XPROpen
restore 4
XPRclose:
setup
push a6
jsr _XPRClose
restore 4
XPRexpunge:
setup
push a6
jsr _XPRExpunge
restore 4
XPRXProtocolCleanup:
setup
push a0
jsr _XProtocolCleanup
restore 4
XPRXProtocolSetup:
setup
push a0
jsr _XProtocolSetup
restore 4
XPRXProtocolSend:
setup
push a0
jsr _XProtocolSend
restore 4
XPRXProtocolReceive:
setup
push a0
jsr _XProtocolReceive
restore 4
end
Appendix B: The xproto.h include file
=====================================
/** xproto.h
*
* Include file for External Protocol Handling
*
**/
/*
* The structure
*/
struct XPR_IO {
char *xpr_filename; /* File name(s) */
long (*xpr_fopen)(); /* Open file */
long (*xpr_fclose)(); /* Close file */
long (*xpr_fread)(); /* Get char from file */
long (*xpr_fwrite)(); /* Put string to file */
long (*xpr_sread)(); /* Get char from serial */
long (*xpr_swrite)(); /* Put string to serial */
long (*xpr_sflush)(); /* Flush serial input buffer*/
long (*xpr_update)(); /* Print stuff */
long (*xpr_chkabort)(); /* Check for abort */
long (*xpr_chkmisc)(); /* Check misc. stuff */
long (*xpr_gets)(); /* Get string interactively */
long (*xpr_setserial)(); /* Set and Get serial info */
long (*xpr_ffirst)(); /* Find first file name */
long (*xpr_fnext)(); /* Find next file name */
long (*xpr_finfo)(); /* Return file info */
long *xpr_reserved1; /* Reserved */
long *xpr_reserved2; /* Reserved */
long *xpr_data; /* Initialized by Setup. */
};
/*
* The functions
*/
extern long XProtocolSend(), XProtocolReceive(),
XProtocolSetup(), XProtocolCleanup();
/*
* The update structure
*/
struct XPR_UPDATE { long xpru_updatemask;
char *xpru_protocol;
char *xpru_filename;
long xpru_filesize;
char *xpru_msg;
char *xpru_errormsg;
long xpru_blocks;
long xpru_blocksize;
long xpru_bytes;
long xpru_errors;
long xpru_timeouts;
long xpru_packettype;
long xpru_packetdelay;
long xpru_chardelay;
char *xpru_blockcheck;
char *xpru_expecttime;
char *xpru_elapsedtime;
long xpru_datarate;
long xpru_reserved1;
long xpru_reserved2;
long xpru_reserved3;
long xpru_reserved4;
long xpru_reserved5;
};
/*
* The possible bit values for the xpru_updatemask are:
*/
#define XPRU_PROTOCOL 0x00000001L
#define XPRU_FILENAME 0x00000002L
#define XPRU_FILESIZE 0x00000004L
#define XPRU_MSG 0x00000008L
#define XPRU_ERRORMSG 0x00000010L
#define XPRU_BLOCKS 0x00000020L
#define XPRU_BLOCKSIZE 0x00000040L
#define XPRU_BYTES 0x00000080L
#define XPRU_ERRORS 0x00000100L
#define XPRU_TIMEOUTS 0x00000200L
#define XPRU_PACKETTYPE 0x00000400L
#define XPRU_PACKETDELAY 0x00000800L
#define XPRU_CHARDELAY 0x00001000L
#define XPRU_BLOCKCHECK 0x00002000L
#define XPRU_EXPECTTIME 0x00004000L
#define XPRU_ELAPSEDTIME 0x00008000L
#define XPRU_DATARATE 0x00010000L