\" $Header: courier.tbl.me,v 1.8 87/03/17 09:32:52 ed Exp $
\" $Header: courier.tbl.me,v 1.8 87/03/17 09:32:52 ed Exp $
\" $Log:	courier.tbl.me,v $
\" Revision 1.8  87/03/17  09:32:52  ed
\" Added -I switch to establish search path for DEPENDS UPON files.
\" 
\" Revision 1.7  86/11/22  07:12:05  jqj
\" small changes reflecting differences between 4.3bsd and 4.2 Maryland XNS.
\" 
\" Revision 1.6  86/05/12  09:02:43  jqj
\" minor revisions
\" 
\" Revision 1.5  85/03/26  06:30:58  jqj
\" Revised public alpha-test version, released 26 March 1985
\" 
\" Revision 1.4  85/03/11  16:46:01  jqj
\" Public alpha-test version, released 11 March 1985
\" 
.lg 0
.fo ''%''
.(l C
.ps +4
.b
XNS Courier under UNIX (4.3BSD)
.ps -4
.sp 2
.r
.he $ XNS Courier $ $Date: 87/03/17 09:32:52 $
J.Q. Johnson
Computer Science Department
Cornell University
405 Upson Hall
Ithaca, NY  14853
.)l
.sh 1 "Introduction"
.pp
This document describes the implementation and use
of the Courier remote procedure call protocol for Berkeley UNIX
(version 4.3), with XNS.
.sp 1
.nf
.b
.(c
********************************************************
Warning:  this document is a DRAFT design specification.
It has not yet been fully implemented, and the details of
the interface it specifies (particularly with respect to 
Courier servers) are subject to change.  Send design
comments to jqj@cornell.ARPA or jqj@cornell.UUCP.
********************************************************
.)c
.r
.fi
.sp 1
.uh "Changes since previous revision:"
.np
Added discussion of encapsulated protocols.
.np
Added discussion of Clearinghouse library calls for finding a connection,
and relationship of connections to outstanding RPCs.
.uh "Earlier changes:"
.np
Changed names of generated file names to include version number.
Added discussion on scope of names vs. DEPENDS UPON modules.
.np
Users must now link Courier program ``Foo'' with Foo1_support.o.
.np
Added some discussion of freeing dynamic storage via clear_Foo.
.np
Changed
.i CourierOpen()
to take ns_addr structure rather than a string host name.  Routines for
converting from name to address (using Clearinghouse) have not yet been
designed. 
.np
Changed ERROR discussion to indicate that an ERROR is mapped to a long
with value > 65535.
.np
Changed description of BDT to reflect use of 
.i BDTread()
and
.i BDTwrite() .
.np
Added SPPMAXDATA.
.np
Changed discussion of BulkData constants to indicate that things now
work correctly.
.sh 2 "General Description"
.pp
Courier is both a protocol and a specification language.
The protocol describes how remote procedures are invoked and
how parameters and results of various data types are transmitted.
The specification language, somewhat reminiscent of Mesa,
provides a simple way of defining the remote interfaces of distributed
programs.
.pp
This implementation is an attempt to allow UNIX C programmers to
construct distributed Courier programs to communicate with other
Courier implementations layered on XNS.  It thus requires kernel
support for XNS Sequenced Packet Protocol sockets.
.pp
The simplest form of distributed program using Courier
consists of three parts.
The first is the specification,
written in the Courier language.
The specification must declare all procedures of
the program that will be called remotely (the
.i "server procedures" ).
The next part is the implementation, in C, of the server procedures.
Finally, there is the client portion of the program, also in C, which calls
the server procedures.
Note that either the client or server portion of the program may be
omitted if the UNIX program is designed to act with a non-UNIX
partner; for example, a simple printing client under UNIX might send
files to a Xerox laser printer using the Printing protocol published
and supported by Xerox.
.sh 2 "Acknowledgements"
.pp
Contributors to the current implementation have included
JQ Johnson, the primary implementor of the current version of the
Courier compiler under UNIX,
Eric Cooper, the author of the original UNIX Courier/TCP compiler,
Jeff Mogul, the author of the exception signalling mechanism used
here to handle abort and reject messages,
James O'Toole, Chris Torek, and Mark Weiser,
the authors of the 4.2BSD version of UNIX kernel support for XNS,
Keith Sklower, the author of the present (4.3BSD) version of UNIX kernel support,
Lee Moore, and Bill Nesheim.
.pp
Some of the software is copyright 1984 by Eric Cooper, and is for
research use only; it may be freely distributed but may not be
sold as a commercial product.
.sh 2 "References"
.pp
This document assumes familiarity with the Courier language,
details of which may be found in
the Courier protocol definition,
.i "Courier: The Remote Procedure Call Protocol" ,
Xerox System Integration Standard 038112,
December 1981, and ``Appendix F, Bulk Data Transfer,'' Xerox System
Integration Standard 038112 Add. 1, October 1982.
However, for reference purposes, a grammar for the Courier language
may be found in the appendix to this document.
.pp
Users of Bulk Data Transfer will need to interact with SPP 
(Sequenced Packet Protocol)
streams, and may wish to see
.i "Internet Transport Protocols" ,
Xerox System Integration Standard 028112, December 1981.
.pp
Those interested in the workings of UNIX networking might consult
``A 4.2bsd Interprocess Communications Primer,''
(DRAFT of March 11, 1983), by Samuel J. Leffler, Robert S. Fabry, and
William N. Joy, and the 
``4.2BSD System Manual,''
(Revised July, 1983), by William Joy 
.i "et al" .
.pp
This document is also supplemented by various UNIX manual pages, notably
.i "xnscourier(3) " ,
and
.i "except(3)" .
.sh 2 "Software and Hardware Dependencies"
.pp
This package requires the following software to operate:
.(l
4.3BSD (currently tested only for VAX and Gould versions)
PCC, 4.3BSD version (probably\-limited testing has been done with Tartan C)
.)l
.pp
To communicate with Xerox products you will need an Ethernet
interface.  Most of the existing example applications depend heavily on
the existence of a Xerox Clearinghouse somewhere on the local network.
.sh 1 "The Courier Specification Language"
.pp
The Courier specification language is documented (loosely) in the Xerox
``Courier: The Remote Procedure Call Protocol.''  Our compiler
implements a large subset of the specification.  Note that the embedding
of Courier constructs in a message or bit stream is usually irrelevant to the
applications programmer, who need only be concerned with the semantics
of the Courier datatypes.  The courier compiler and runtimes translate
Courier datatypes into more easily managed C constructs and handle the
protocol of communication with the remote system.
.pp
This implementation places several restrictions on the specifications
it will accept:
.np
No forward references are permitted in the courier
specification.  The user must reorder type specifications so that all
types and constants are defined before they are used.
Special provisions are made for recursive type declarations of the form
typically found in Bulk Data ``StreamOf'' declarations; see the
discussion of BDT below for details.  General recursive declarations,
e.g. that of the Filing ``Filter,'' are not supported.
.np
The Courier specification is unclear as to lexical issues, 
name scope, and name overloading.
In the current compiler, names should contain only upper and lower case
letters or digits.  Also, all constants, types, and
enumeration tags within a module should have distinct names.  Enumeration
tag names are currently global; thus, different enumeration types, even
in different modules, may not have the same tags.
.np
Constants of type CHOICE are not fully supported.  In particular, they
are allowed only if the active arm of the choice is a nill record.
.np
Constants of type SEQUENCE are not supported.  Constants of type ARRAY
(which have the same syntax as SEQUENCE constants) are supported, but are
not rangechecked; ``Vect: ARRAY 10 OF INTEGER = {1,2,3}'' is accepted,
though illegal.
.np
This implementation supports DEPENDS UPON and
referenced types and constants.  However, the semantics of referenced
enumeration constants is unclear.  Also, this implementation
does not support recursive dependencies.
.lp
We think we've eliminated all other restrictions, but we're probably wrong.
.sh 2 "Predefined types"
.pp
The following typedefs correspond to predefined Courier types:
.(b
.TS
c c c
l l l.
Courier type	C typedef	Implemented (on VAX) as

BOOLEAN	Boolean	char (0==TRUE, 1==FALSE)
CARDINAL	Cardinal	unsigned short
LONG CARDINAL	LongCardinal	unsigned long
INTEGER	Integer	short
LONG INTEGER	LongInteger	long
STRING	String	char*
UNSPECIFIED	Unspecified	unsigned short
LONG UNSPECIFIED	LongUnspecified	unsigned long
.TE
.)b
Note that the representations of these types as seen by the applications
programmer are quite different from those sent over a Courier SPP connection.
For example, on the VAX a Cardinal's bytes are swapped before being
sent out in network byte order.  A UNIX String corresponds to a pointer to
a null-terminated array of characters while a Courier serialized STRING
contains a count directly followed by an array of characters.  Note
in particular that this implementation does
not support Courier STRINGs containing the ascii character NUL.
.sh 2 "Constructed types"
.pp
The Courier enumeration, array, and (non-null) record types
correspond to C enumerations, arrays, and structures respectively.
The Courier null record corresponds to an int.
.pp
The Courier sequence and choice types
pose some problems when they are mapped into C.
This is because an object of one of these types
must contain run-time information (the length of the sequence
or which choice is present)
that is implicit in Courier, but must be made explicit
in C.
Furthermore, the C programmer must bear the responsibility of keeping
this information consistent.
.pp
A sequence type is mapped into a structure consisting
of a Cardinal called
.i "length" ,
and a pointer to the sequence elements called
.i "sequence" .
The consistency requirement is that
.i "length"
indicate the number of elements in the array pointed to
by
.i "sequence" .
.pp
A choice type is mapped into a structure consisting
of an enumeration element called
.i "designator" ,
and a union of all the possible choices.
The designator is of the enumeration type defined
(implicitly or explicitly)
in the declaration of the choice type.
If this enumeration consists of elements
.i A ,
.i B ,
and
.i C ,
then the choices are accessible as
.i A_case ,
.i B_case ,
and
.i C_case .
The consistency requirement is that
.i "designator"
contain the enumeration value corresponding to
which choice currently occupies the union.
.sh 2 "Constants"
.pp
The Courier specification is silent on the precise representations of
numbers and strings.  In this implementation, numbers may be represented
as an optional minus sign followed by a
sequence of decimal digits or a sequence of octal digits followed
by ``B''.  A constant of type STRING 
is represented by a sequence of characters enclosed in
double quotation marks; it has the same restrictions as a C literal string (no
embedded newlines, use ``\e'' as a prefix character, etc.) except that double
double quotes are permitted to indicate quotations inside the string.
.sp 1
.nf
minInt: INTEGER = -2147483648;		-- or 20000000000B --
lastCard: CARDINAL = 177777B;		-- or 65535 --
quotedName: STRING = "my name is \"jqj""\en";
.fi
.sp 1
.pp
Although the Courier language allows constants of
any type to be declared,
it is difficult to define constants of constructed types
in C programs.
This implementation normally handles constants by creating
static variables initialized to the values of the (possibly structured)
constants.  The technique is not in general possible for structured 
constants of type CHOICE
since they are implemented as C ``unions'', so this implementation does not
support constants which contain a non-null CHOICE.  As a special case,
constants whose selected variant is a null record are allowed, e.g.
.sp 1
	myImmediate: BulkData.Immediate = immediate [];
.sp 1
.sh 2 "Procedures"
.pp
PROCEDURE constants correspond to C functions plus a numeric jump table
index used in serialization of procedure calls on the network.
Procedure declarations may include argument lists, result lists, and
error lists.  The argument list of the corresponding C function 
contains one parameter for each parameter specified in the declaration,
but is
expanded by the addition of 2 parameters (see below).  The result list
specifies a C structure to be returned by the C function containing the
results; for a procedure, Foo, this result list is of type FooResults.
Thus, a procedure declared
.sp 1
	Example: PROCEDURE [ ] RETURNS [ a: Foo, b: Baz ] = 0;
.sp 1
would be implemented by a C function with no arguments
returning a structure of type ExampleResults with definition
something like
.sp 1
.nf
	typedef struct {
		Foo a;
		Baz b;
	} ExampleResults
.fi
.sp 1
Note that Foo and Baz might be arbitrarily complex structures;
see ``Constructed Types'' above.
.pp
Importing a PROCEDURE from a DEPENDS UPON module imports only its jump
table index.
.sh 2 "ERROR constants"
.pp
An ERROR constant corresponds to a defined numeric value, and possibly
to a C structure describing its argument list.  For example,
.sp 1
	OtherError: ERROR [ errorstring: STRING ] = 1;
.sp 1
corresponds approximately to the C declarations:
.sp 1
.nf
	#define OtherError (1+ERROR_OFFSET)
	typedef struct {
		String errorstring;
	} OtherErrorArgs;
.fi
.sp 1
Note that ERROR values must be specified in the range 0 to 65535, but
that they produce a symbol which appears to the C programmer
with values of type int
in the range 65536 to 131171 (in some contexts, the user may wish to
treat C error values in the range 0 to 65535 as Unix error numbers).
The structure corresponding to the error
may be useful in creating and referencing error arguments
in C functions.
.sh 2 "Scope of Names"
.pp
Given a courier program beginning
.sp
	Example : PROGRAM 537 VERSION 1 =
.sp
types and constants declared in this program belong to the module ``Example1'';
Their full names have prefixed to them the program name and version number.
Given the declaration of ``minInt'' above, the XNS Courier compiler actually
produces a header file (here
.i Example1.h )
containing the C declaration:
.sp 1
.nf
static Integer Example1_minInt = {-2147483648};
.fi
.sp 1
.pp
This prefixing implies that a C programmer may refer to several different
remote Courier programs without risk of name collision.
For programmer convenience a second header file (here
.i Example1_defs.h )
is also created by the compiler containing macro definitions which widen
the scope to ``Example1'' and allow the programmer to refer to types and
constants without explicit qualification.  The programmer may include
whichever header file he prefers.
.pp
Note that types and constants obtained from a DEPENDS UPON inclusion must
be referenced using fully qualified form.  Note also that structure members,
procedure arguments, and enum tags are not currently qualified; beware
potential name conflicts!
.sh 1 "Running the Courier Compiler"
.pp
The specification file is expected to have the extension
.i ".cr" .
Consider the following skeletal Courier program definition:
.sp
	Example : PROGRAM 537 VERSION 1 = BEGIN ... END.
.sp
The name of this Courier program is ``Example''; it is version 1.
By convention, this specification would stored in the file
.i "Example1.cr" .
The ``537'' in this example is illustrative only;
the program number is uniquely allocated from the range of LongCardinals
by Xerox.  See Appendix B of the Courier standard for program number
assignment procedures.
.pp
The first step is to use the Courier compiler on the specification.
Assuming
.i "xnscourier"
is in your path (it is 
.i "/usr/new/xnscourier"
on the Berkeley distribution), type
.sp 1
	xnscourier Example1.cr
.sp 1
If there are no errors in compilation,
the following files will be produced:
.(b
.TS
c c
l l.
File	Contents

Example1_defs.h	scope widening macros (includes Example1.h)
Example1.h	definitions, typedefs, and constant declarations
Example1_support.c	routines to map between C and Courier
Example1_server.c	server main program and support routines
Example1_client.c	client routines to support applications calling Example
.TE
.)b
.pp
The header file
.i "Example1_defs.h"
should be included via
.sp
	#include ``Example1_defs.h''
.sp
in all user-written parts of the Courier program
(i.e., the implementations of the client program and server procedures.)
.pp
The 
.i "\fB-I\fIinclude-directory"
switch may be used on the command line of
.i xnscourier
to establish a search path for Courier specification files named in a
DEPENDS UPON clause. By default, the compiler looks first in the current
directory followed by the courier-description-file named in the 
.i /etc/Courierservices
file.
.sh 2 "Calling Remote Procedures \- Client Implementation"
.pp
A remote procedure appears to the C programmer to look almost like
a local procedure (although it typically makes use of resources not
available on the local machine or else runs many times slower
than would a local procedure).  A C client program (i.e. the caller
of a remote procedure) actually calls a stub function generated by
the courier compiler, which in turn executes the code necessary to
invoke the corresponding procedure on the remote machine.  
.pp
Before calling the remote procedure it is necessary to establish a
Courier SPP connection (in the process binding a local socket to
a remote machine).  This may be done by the calling the function
CourierOpen(), passing it as argument a structure identifying the remote
host.  This structure
is of type ``ns_addr''
as defined in the include file
.i "#include <netns/ns.h>" .
CourierOpen() returns an anonymous pointer
(of type CourierConnection*) to a structure containing
the socket to be used for this courier call, which should be passed
to each remote procedure to be executed on the particular remote host.
CourierOpen() returns NULL if there is a problem opening an SPP connection
to the host specified.
.pp
For low-level access to ns_addr structures,
the procedure getXNSaddr() is available, taking a string in the form
``network#a1.a2.a3.a4.a5.a6#socket'' (where all numbers are hex) or 
``networkhigh-networklow#d1-d2-d3-d4#socket'' (where all numbers are
3-digit decimal numbers)
and returning a pointer to the
corresponding ns_addr structure.  This result may then be passed as the
argument to CourierOpen().
More normally, the user will have a string in the form of an NS address,
e.g. ``jqj:computer\ science:cornell-univ''.  This string can be
coerced into a Clearinghouse ObjectName, then used in a Clearinghouse-based
lookup to find the corresponding address:
.sp 1
.nf
	Clearinghouse2_ObjectName hobj, hdefault;
	struct ns_addr * host;
	char * string;

	CH_NameDefault(&hdefault);	/* get defaults */
	hobj = CH_StringToName(string, &hdefault);
	host = CH_LookupAddr(hobj, 0);	
.fi
.sp 1
.pp
The SPP connection may be reused by multiple RPCs destined for the same
host, even if the procedures are part of different remote programs.  However,
the SPP connection may not be reused while an RPC is outstanding; thus,
if you need to issue a Courier call during a BDT transfer, you will need
to obtain a second CourierConnection.
.pp
The client then calls the compiler-generated stub function to invoke
the remote procedure.  This stub
function takes at least 2 arguments:
.ip  (1)
The pointer returned from CourierOpen().
If the SPP connection does not
currently exist (i.e. has been closed by the server)
it is reopened for this call.
.ip (2)
A pointer to a user-supplied function which will be used if a Bulk Data
Transfer is required (see the section on BDT below); if this remote
procedure does not involve Bulk Data Transfer, then the function pointer
should be 0 or NULL.
.ip (3...)
One additional argument corresponding to each parameter in the Courier
language description of this procedure.
.pp
The remote procedure returns in one of 2 ways:  either it returns
normally, passing back a structure corresponding to the RETURNS parameters
specified in the procedure description, or it signals an error which
may be caught using the DURING ... HANDLER mechanism described in
.i except(1) .
.pp
A Courier procedure is allowed to return multiple results, a language feature
not easily mapped into C.  To provide this functionality, all UNIX
courier remote procedures actually return a structure
containing the procedure results.  Thus, for a procedure named
Foo, declared
.sp 1
	Foo: PROCEDURE [ ] RETURNS [str: STRING];
.sp 1
the C function Foo would return a structure of type FooResults, declared
.sp 1
.nf
	typedef struct {
		String str;
	} FooResults;
.fi
.fi
.pp
Instead of returning a value of type determined by the RESULTS clause
of a PROCEDURE declaration, a Courier remote procedure call may return
a reject message indicating the remote system's inability to even
attempt a remote operation, or an abort message raising a remote error,
i.e. reporting the operation's failure.  Reject and abort messages are
handled by a signalling mechanism  or if uncaught cause termination of
the client program.  For example, a client calling the remote procedure
defined by
.sp 1
.nf
	NoSuchFile: ERROR = 0;
	OtherError: ERROR [ errorstring: STRING ] = 1;
	Example: PROCEDURE [ ] REPORTS [ NoSuchFile, OtherError ];
.fi
.sp 1
might be coded as
.sp 1
.nf
	conn = CourierOpen(destaddr);
	DURING Example(conn,NULL)
	HANDLER switch(Exception.Code) {
	case REJECT_ERROR:
		fprintf(stderr,"Remote reject.\en");
		exit(1);
	case NoSuchFile:
		fprintf(stderr,"No such file\en");
		break;
	case OtherError:
		fprintf(stderr,"Remote error %s\en",
			CourierErrArgs(OtherErrorArgs,errorstring) );
		break;
	case PROTOCOL_VIOLATION:
	case INTERNAL_ERROR:
		fprintf(stderr,"Local internal error %s\en",
			Exception.Message);
		break;
	default:
		fprintf(stderr,"Unknown error, type %d\en",
			Exception.Code);
	}	
	END_HANDLER
.fi
.sp 1
The error value, Exception.code, will be one of (1) ``REJECT_ERROR'',
which indicates a REJECT message from the remote server, (2) a program-defined
ERROR value (offset by ERROR_OFFSET), (3) or ``INTERNAL_ERROR'' 
or ``PROTOCOL_VIOLATION''
indicating
a serious internal error in the Unix courier implementation; in the third
case, a string is available as Exception.Message.
Both errors and rejections may have arguments, which can be accessed
within a handler as a struct pointed to by Exception.Message.  For
programmer convenience a macro, CourierErrArgs, is defined, taking
as arguments the typedef defining the current error's arguments 
and the name of the argument to be accessed; this macro may be used
only within an error handler.
For rejection messages, the type should be ``rejectionDetails,''
as defined on page 25 of the Courier spec.
.pp
When done with a particular remote connection, the client should call
CourierClose() with argument the pointer returned from CourierOpen()
to free the socket and cleanly close the connection.
.pp
The client program should be loaded with 
.i "Example1_client.o" ,
.i "Example1_support.o" ,
and -lcourier (the XNS Courier library)
to produce an executable program.
.sh 2 "Writing a Courier Server"
.pp
The Courier protocol specifies a standard for communicating
parameters and results which has been adhered to in this
implementation.
It also specifies an initial connection protocol
and a format for messages.
.pp
There is a single Courier Daemon per UNIX machine whose function
is to listen on a well-known XNS port for Courier interface activation
requests.
A request contains the number of the Courier program
whose server is to be activated, and its version number.  These two
numbers are used as a key in the file 
.i /etc/Courierservices
to identify
the executable file associated with the particular courier program.
.i /etc/Courierservices
consists of a sequence of lines of the form
.sp 1
	program-name  program-number  version  courier-description-file  server-binary-file
.sp 1
The daemon
either spawns the executable file
or replies with a reject message.  Note that each incoming remote procedure
call may be serviced by a separate UNIX process.
This implies that connection-oriented services (services which consist of a
sequence of related procedure calls) must be implemented by maintaining
the state of connections in a file.
.pp
The executable file containing the server for a particular remote
program consists of a main program generated by the courier compiler
which interprets CALL messages from the remote client and calls
one or more user-written functions to implement the
courier procedures.
.pp
Given a courier specification for the remote program Example containing
remote procedure Example,
the server functions (including the C function Example and any support
routines needed)
should be loaded with 
.i "Example1_server.o" ,
.i "Example1_support.o" ,
and -lcourier
to produce the server process that will be invoked whenever
an activation request arrives.
.pp
A server function receives an argument list similar to that passed by
a client to a remote procedure.  The first two arguments in the
parameter list should be ignored; each succeeding argument corresponds
to one parameter in the courier declaration.
.pp
To return from a server function you must return a structure containing
the results of the procedure.  If this structure contains pointers (e.g.
Strings) you should insure that the data pointed to is allocated
staticly or on the heap rather than on the stack.
You must not call exit(3) from within the server function; doing so will
cause the client (remote caller) to hang indefinitely waiting for a
reply.
.pp
To abort a server function, returning an error code, use the
``raise(code, msg)''
library function, with arguments the error code and either NULL (if the
error takes no arguments) or a pointer to a structure containing the
arguments to the error.  Raise causes a longjump out of the function
directly to the handler, and thence to the remote procedure caller.
For example, to return the OtherError message defined above one might code
.sp 1
.nf
	OtherErrorArg randomerr;
	. . .
	randomerr.errorstring = sys_errlist[errno]);
	raise(OtherError,(char*) &randomerr);
.fi
.sp 1
.pp
When a server function returns to its main program caller the main()
sends the appropriate RETURN or ABORT message to the remote client,
and does an exit(0).  The server then holds the connection for up to 90 
seconds waiting for another Courier call to arrive on the same SPP
stream.
.sh 2 "Dynamic Allocation"
.pp
One additional 
.i caveat 
is necessary when using Courier remote procedure calls from C:  C does not
have garbage collection, so you should free any dynamically allocated storage
when you are done with it.  In general, top level Courier objects are
always allocated from the stack or statically; however, strings and sequences
within an object are generally allocated from the heap using malloc().
.pp
A client program which calls the remote procedure Foo defined above,
returning a STRING,
actually has returned to it a C struct containing a pointer to an array
of malloced characters.  When done with this string, the program may free
it by calling clear_FooResults() with argument the address of the
result returned by the call to Foo.  Thus:
.sp 1
.nf
	FooResults result;
	. . .
	result = Foo(conn,NULL);
	. . .
	printf("result was %s\n",result.str);
	clear_FooResults(&result);
.fi
.sp 1
.sh 1 "Using Bulk Data Transfer"
.pp
.sh 2 "Overview"
.pp
When a Courier program needs to transfer an arbitrary amount of
information as an argument or result of a Courier procedure, the
procedure is usually defined to have an argument of type ``BulkData.Sink''
or ``BulkData.Source'' (and a ``DEPENDS UPON BulkData''
is included in the program).
The argument is a ``source'' if it is information transferred from caller
to server (as though a procedure argument), a ``sink'' if it is
information transferred from server to caller (as though a procedure
result).  In this
implementation, a Courier procedure may have at most one such argument,
which must have the value null or immediate.
In a Courier call, the bulk data is transmitted in a special way,
multiplexed into the same data path as control messages
between the arguments and the results.
.pp
The BDT user should include in the 
.i ".cr"
file a ``DEPENDS UPON BulkData (0) VERSION 1'', then reference these
arguments as BulkData.Source and BulkData.Sink in PROCEDURE
declarations.
The declaration of a Courier PROCEDURE can then indicate a bulk
data transfer by including in its formal argument list a variable
of type BulkData.Source or BulkData.Sink
as appropriate.
The corresponding parameter to the C procedure will be of type
BulkData1_Descriptor.
.sh 2 "Coding a Client"
.pp
To use Bulk Data in a client program, the Courier definition of the
procedure must include one BulkData.Source or BulkData.Sink parameter.
As the actual argument to the call, the C client passes a constant,
either BulkData1_immediateSource, BulkData1_immediateSink, BulkData1_nullSource, 
or BulkData1_nullSink as appropriate.  The client
also specifies a pointer to a user-supplied function as the second
argument to the remote procedure.
Courier sets up the transaction, then calls the supplied function with
one argument, the pointer returned by CourierOpen() describing the
SPP socket on which to write (if a source argument) or read
(if a sink) the bulk data. 
.pp
To complete the transaction normally, the
function should send packets of SPP data terminated by
an end-of-message (if writing) or read up to but not beyond an
end-of-message (if reading), and should return normally to its caller.
To do so, it should call the procedures BDTread(), BDTwrite(), or 
BDTclosewrite()
as appropriate.  BDTread() and BDTwrite() take arguments analogous to read()
and write() except that instead of a file descriptor they accept the SPP
descriptor returned by CourierOpen(); their arguments are thus
(1) the pointer to the SPP data structure,
(2) a pointer to an array of characters (the IO buffer),
and (3) a count;
they return the number of characters actually read or written.
.pp
The UNIX program can finish a write transfer by calling BDTclosewrite() or
BDTabort(), each with the SPP descriptor as argument.
BDTclosewrite() produces an SPP end-of-message, while BDTabort() instructs
Courier to discard any buffered data and
send a Bulk Data Abort to abort the transaction.  A read transfer may also be
prematurely ended by calling BDTabort().
.sh 2 "Coding a Server"
.pp
Writing a server that uses BDT
is similar to writing the BDT function in a caller.
The server is passed a BulkData.Source or BulkData.Sink (the two are
indistinguishable at run time).
The server routine should check to make sure that the source or sink
is of type null or immediate (active and passive descriptors are not
supported in this implementation) by means of code such as:
.sp 1
.nf
Foo(bdtconnection,ignoredarg,..., s, ...)
	CourierConnection *bdtconnection;
	BulkData1_Sink s;
	. . .
	switch (s.designator) {
	case active:
	case passive:
		/* can't raise BulkData1_InvalidDescriptor, so */
		raise(someerror);
	case null:
		/* handle null transfer */
		break;
	case immediate:
		/* handle normal, i.e. immediate, transfer */
		break;
	}
.fi
.sp 1
Note that the BDT error InvalidDescriptor cannot be used unless the
procedure declaration explicitly notes that this is a valid REPORTS error
type.  Normally, each courier program defines its own error to be reported
on BDT problems.
.pp
Use the first argument to the server procedure as the handle on the
BDT connection.
As with a client BDTabort() may
be used to send abort messages.
.sh 2 "Sending Bulk Data"
.pp
Sending bulk data, either as client or server, is quite
straightforward.  Use the supplied procedure BDTwrite(), whose
semantics are similar to write().  BDTwrite() will return -1
if an error occurs or an abort from the listener is received; in this
case, the sender should immediately cease transmitting BDT data, and
should instead call BDTabort(). 
Typical code to send data on ``bdtconnection'',
in this case read from a buffered file open
as ``source,'' might appear as:
.sp 1
.nf
	CourierConnection *bdtconnection;
	FILE *source;
	int count;
	char buffer[SPPMAXDATA];
	...
	while ( (count = fread(buffer,1,SPPMAXDATA,source)) > 0 &&
		BDTwrite(bdtconnection,buffer,count) >= 0 )
		;
	if (count <= 0)	/* succsfull transfer */
		BDTclosewrite(bdtconnection);
	else
		BDTabort(bdtconnection);
.fi
.sp 1
.pp
Each BDTwrite() transmits one packet after checking for an abort message
from the receiver.
A sender which gets an abort from a remote receiver must immediately
stop transferring data and instead must echo an abort via BDTabort()
to acknowledge the end of the transfer.
.pp
Also, for efficiency's
sake it is desirable to write packets as near as possible to the
nominal maximum length of an SPP packet; this suggests a buffer length of
534 bytes.  For programmer convenience, the file
.i courier.h
defines the symbol SPPMAXDATA as 534.
.sh 2 "Receiving Bulk Data"
.pp
To make receiving bulk data simple, the routine BDTread() performs checks for
various conditions on receipt of each packet.  BDTread() will return the
count of the number of bytes actually read in each packet (skipping empty
packets), or 0 to indicate end of data.  If an error or BDT abort message
occurs, BDTread() will return -1; if this occurs, the receiver should 
immediately stop reading BDT data.
.pp
Typical code for receiving bulk data on the socket described by 
``bdtconnection'' and
depositing it in the file opened for buffered ouput as ``destfile''
might be:
.sp 1
.nf
	CourierConnection *bdtconnection;	/* BDT source descriptor */
	FILE *destfile;				/* output file */
	char sppdata[SPPMAXDATA];
	...
		count = BDTread(bdtconnection, sppdata, SPPMAXDATA);
		while (count > 0) {	/* actually read data */
			if (fwrite(sppdata, 1, count, destfile) == 0) {
					/* error while writing */
				BDTabort(bdtconnection);
				break;
			}
			count = BDTread(bdtconnection, sppdata, SPPMAXDATA);
		}
		if (count == 0) {
			/* successful */
			...
.fi
.sp 1
.sh 2 "StreamOf declarations"
.pp
Frequently, data to be sent on a Bulk Data connection will be described
by a StreamOfSomething declaration in the courier specification.  For
example, in Clearinghouse several routines are documented as returning
a StreamOfObjectName; unfortunately, a StreamOf declaration is recursive,
and so is not immediately translatable into automatic packing and unpacking
routines which need to know in advance how much space to allocate for the
result.  As a kludge, we translate such types as if the recursive
part of the declaration were a null record.  
Consider:
.sp 1
.nf
StreamOfObjectName: TYPE = CHOICE OF {
	nextSegment (0) => RECORD [
		segment: SEQUENCE OF ObjectName,
		restOfStream: StreamOfObjectName ],
	lastSegment (1) => SEQUENCE OF ObjectName};
.fi
.sp 1
.lp
In the above declaration, the ``restOfStream is effectively ignored.
Given this declaration, the following routine may be passed to a
remote procedure as the Bulk Data actor.
.sp 1
.nf
#include "Clearinghouse_support.c"	/* get internalize_* routines */
#define MAXPACKS 5

GetData(conn)
	CourierConnection *conn;
{
	int count, i;
	Unspecified buffer[MAXWORDS*MAXPACKS], *bp, *bufend;
	StreamOfObjectName obnames;
	
	bufend = buffer;
	bp = buffer+MAXWORDS*(MAXPACKS-1);    /* end of available space */
	while (count = BDTread(conn, (char*)bufend, 
				MAXWORDS*sizeof(Unspecified))
		) {
		bufend += count/sizeof(Unspecified);
		if (bufend > bp) {
			fprintf(stderr,"BDT read too big to fit\en");
			BDTabort(conn);
			/* should clear out stuff here if we knew how much */
		}
	}
	bp = buffer;
	while (bp < bufend) {
		bp += internalize_StreamOfObjectName(&obnames,bp);
		if (0 == (int) obnames.designator)
		   for (i = 0; i < obnames.nextSegment_case.segment.length; i++)
			ProcessObjectName(
				obnames.nextSegment_case.segment.sequence[i]);
		else {
		   for (i = 0; i < obnames.lastSegment_case.length; i++)
			ProcessObjectName(
				obnames.lastSegment_case.sequence[i]);
		   return;
		}
	}
}
.fi
.sp 1
.lp
Note that this code is very awkward, and that it requires that the whole
bulk data transfer be stored in memory before it is unpacked.  Stay
tuned; we intend to modify the compiler to make the handling of such
streams much easier, at the cost of incompatibility with the existing
scheme, of course!
.sh 2 "Encapsulated Protocols"
.pp
Some courier programs use what might be termed ``encapsulated protocols''
as a method of type punning to escape from the restrictions of the Courier
language.  For example, in Filing
.sp 1
.nf
	Attribute: RECORD [
		type: AttributeType, value: SEQUENCE OF UNSPECIFIED ];
	checksum: AttributeType = 0;
	Checksum: TYPE = CARDINAL;
	createdBy: AttributeType = 1;
	CreatedBy: TYPE = User;
.fi
.sp 1
Depending on the value of the type subfield, the data in the value field
is intended to be interpreted as either a CARDINAL or a User name.
.pp
Such encapsulation is in general bad design unless a very compelling
need for it exists.  It is also awkward to support in a C program.  The
best way to handle such types is to write functions to code and decode
the encapsulated value using the low level packing and unpacking routines
provided by the Courier compiler.  For example:
.sp 1
.nf
	Filing4_User
	AttrToUser(attr)
		Filing4_Attribute *attr;
	{
		Unspecified buf[2049], *bp; /* space for biggest attribute */
		Cardinal len;
		Filing4_User retval;

		/* useful fact:   Item: TYPE = SEQUENCE OF UNSPECIFIED; */
		externalize_Clearinghouse2_Item(&(attr->value), buf);
		bp = buf;
		bp += internalize_Cardinal(&len, bp);
		bp += internalize_Filing4_User(&retval, bp);  
		return(retval);
	}

	UserToAttr(id, attr)
		Filing4_User id;
		Filing4_Attribute *attr;
	{
		Unspecified buf[2049], *bp;
		Cardinal len;

		/* don't know length yet, so leave space for it */
		bp = buf + sizeof_Cardinal(len);
		len = externalize_Filing4_User(&id, bp);
		(void) externalize_Cardinal(&len, buf);
		internalize_Clearinghouse2_Item(&(attr->value), buf);
		return;
	}

.fi
.sp 1
Since the SEQUENCE OF UNSPECIFIED may have undergone arbitrary
transformations
during deserialization, translating it to a User record consists of first
reserializing it into an array of char, then deserializing it using the
low-level routine appropriate to the encapsulated type.  Translating to
an encapsulated type is an almost precise inverse.
.sh 1 "Example I:  Passwd.cr"
.pp
This section contains a Courier program which implements remote lookup in
.i "/etc/passwd"
(the UNIX database of user names, passwords, home directories, and so
on).
It does not utilize Bulk Data Transfer, but does illustrate most other
features of this courier implementation.
The applications programmer would first write 
.i PasswordLookup.cr ,
the description of the courier interface for the service, then might
write
.i PasswordLookup.c ,
containing the routines needed to implement a server for this courier
program, or might write
.i lookup.c ,
a typical client of this service.
.bp
.sh 2 "The specification (PasswordLookup.cr)"
.sp 1
.nf
PasswordLookup : PROGRAM 754 VERSION 1 =

BEGIN

    -- This is a translation of the passwd structure in <pwd.h>

    Passwd : TYPE = RECORD [
	pw_name, pw_passwd : STRING,
	pw_uid, pw_gid, pw_quota : LONG CARDINAL,
	pw_comment, pw_gecos, pw_dir, pw_shell : STRING
    ];

    -- Remote Errors

    NoSuchUser : ERROR = 0;
    OtherError : ERROR [ errorstring: STRING ] = 1;

    -- Remote entry points.

    LookupUid : PROCEDURE [ uid : CARDINAL ] RETURNS [ passwd : Passwd ]
		    REPORTS [ NoSuchUser ]
		    = 0;

    LookupUser : PROCEDURE [ user : STRING ]
		    RETURNS [ passwd : Passwd, forward : STRING ]
		    REPORTS [ NoSuchUser, OtherError ]
		    = 1;

END.
.fi
.sp 2
.sh 2 "PasswordLookup_defs.h"
.sp 1
.nf
/*
 * Declarations for Courier program PasswordLookup.
 */
#include <courier.h>
#include <except.h>

typedef struct {
	String pw_name;
	String pw_passwd;
	LongCardinal pw_uid;
	LongCardinal pw_gid;
	LongCardinal pw_quota;
	String pw_comment;
	String pw_gecos;
	String pw_dir;
	String pw_shell;
} Passwd;

#define NoSuchUser 0

#define OtherError 1
typedef struct {
	String errorstring;
} OtherErrorArg;

typedef struct {
	Passwd passwd;
} LookupUidResult;

typedef struct {
	Passwd passwd;
	String forward;
} LookupUserResult;

extern LookupUidResult LookupUid();

extern LookupUserResult LookupUser();
.fi
.sp 2
.sh 2 "Makefile"
.sp 1
.nf
CFLAGS = -O
USEROBJS = lookup.o PasswordLookup_client.o
SRVROBJS = PasswordLookup.o PasswordLookup_server.o
LIBS = -lcr
DESTDIR = /usr/lib/courier

all:	lookup PasswordLookup

lookup:	$(USEROBJS)
	cc -o lookup $(USEROBJS) $(LIBS)

PasswordLookup:	$(SRVROBJS)
	cc -o PasswordLookup $(SRVROBJS) $(LIBS)

$(USEROBJS) $(SRVROBJS):	PasswordLookup_defs.h

PasswordLookup_defs.h \e
PasswordLookup_server.c \e
PasswordLookup_client.c:	PasswordLookup.cr
	courier PasswordLookup.cr

install:	all
	install -s PasswordLookup $(DESTDIR)

clean:
	-rm -f *.o PasswordLookup_*.c PasswordLookup_defs.h
.fi
.bp
.sh 2 "The server procedures (PasswordLookup.c)"
.sp 1
.nf
#include <stdio.h>
#include "PasswordLookup_defs.h"

extern Passwd *getpwnam(), *getpwuid();

LookupUidResult
LookupUid(CourierConnection,CourierBDTProc,uid)
	CourierConnection *CourierConnection, CourierBDTProc;
	Cardinal uid;
{
	Passwd *pw;

	pw = getpwuid(uid);
	if (pw == NULL)
		raise(NoSuchUser,NULL);
	else {
		return (*(LookupUidResult*) &pw);
	}
}

LookupUserResult
LookupUser(CourierConnection,CourierBDTProc,user)
	CourierConnection *CourierConnection, CourierBDTProc;
	String user;
{
	LookupUserResult result;
	Passwd *pw;
	FILE *fwdfd;
	static char forward[100];

	pw = getpwnam(user);
	if (pw == NULL)
		raise (NoSuchUser,NULL);
	else {
		sprintf(forward,"%s/.forward",pw->pw_dir);
		if ((fwdfd = fopen(forward,"r")) == NULL) {
			forward[0] = '\e0';
		else {
			fgets(forward,100,fwdfd);
			fclose(fwdfd);
			if (strlen(forward) < 2) (
				static OtherErrorArg randomerr = {
					"invalid forwarding file"};
				raise(OtherError,&randomerr));
		}
		result.password = *pw;
		result.forward = forward;
		return (result);
	}
}
.fi
.bp
.sh 2 "The user program (lookup.c)"
.sp 1
.nf
/*
 * Sample program to access remote password lookup.
 * Usage: lookup machine username
 */
#include <stdio.h>
#include "PasswordLookup_defs.h"

main(argc, argv)
	int argc;  char **argv;
{
	LookupUserResult result;
	Passwd passwd;
	CourierConnection *connection;

	if (argc < 3 || (destaddr = getXNSaddr(argv[1])) == NULL)) {
		fprintf(stderr,"Usage:  %s machine file ...\en",argv[0]);
		exit(1);
	}
	if ((connection = CourierOpen(destaddr)) == NULL) {
		fprintf(stderr,"Can't open connection to %s\en",argv[1]);
		exit(1);
	}
	DURING
		result = LookupUser(connection,NULL,argv[2]);
	HANDLER
		switch(Exception.Code) {
	case Courier_reject:
		fprintf("Connection rejected, code = %d\en",
			CourierErrArgs(rejectionDetails,designator) );
		exit(1);
	case NoSuchUser:
		printf("User %s unknown on %s.\en", argv[2], argv[1]);
		exit(0);
	case OtherError:
		fprintf(stderr,"Remote error %s\en",
			CourierErrArgs(OtherErrorArg,errorstring) );
		exit(1);
	}
	END_HANDLER;

	displaypwd(& result.passwd);
	displayfwd(result.forward);
	CourierClose(connection);
}

displaypwd(p)
	Passwd *p;
{
	printf("%s:%s:%d:%d:%s:%s:%s\en",
		p->pw_name, p->pw_passwd, p->pw_uid, p->pw_gid,
		p->pw_gecos, p->pw_dir, p->pw_shell);
}

displayfwd(s)
	String s;
{
	if (*s) printf("Mail forwarding to %s\en",s);
	else	printf("Mail is not forwarded\en");
}
.fi
.bp
.sh 1 "Example II: PrintFile"
.pp
This example is a very simple application of Bulk Data Transfer.
.sh 2 "PrintFile.cr"
.sp 1
.nf
PrintFile: PROGRAM 756 VERSION 1 =
BEGIN

    DEPENDS UPON BulkData (0) VERSION 1;

    -- Remote errors.

    CantPrint: ERROR = 0;

    -- Remote entry points.

    RPrint: PROCEDURE [ s: BulkData.Source ]
		REPORTS [ CantPrint ];

END.
.fi
.sp 1
.sh 2 "A typical client (remoteprint.c)"
.lp
.sp 1
.nf
/*
 * Sample proram to print a file remotely using trivial remote print
 * protocol.
 * Usage:  remoteprint filename
 *
 */
#include <stdio.h>
#include <sys/types.h>
#include <netns/ns.h>		/* for XNS addresses */
#include "PrintFile_defs.h"

static FILE * source;	/* communicate from main to SendSource */

main(argc, argv)
	int argc;
	char *argv[];
{
	CourierConnection *connection;
	struct ns_addr *destaddr;

	if (argc < 3 || (destaddr = getXNSaddr(argv[1])) == NULL)) {
		fprintf(stderr,"Usage:  %s machine file ...\en",argv[0]);
		exit(1);
	}
	if ((connection = CourierOpen(destaddr)) == NULL) {
		fprintf(stderr,"Can't open connection to %s\en",argv[1]);
		exit(1);
	}
	argv++;
	while (argc-- > 2) {
		argv++;
		if (strcmp(argv[0],"-") == 0) source = stdin;
		else source = fopen(argv[0],"r");
		if (source == NULL)
			fprintf(stderr,"Can't open %s\en",argv[0]);
		else DURING
			RPrint(connection,SendSource,immediateSource);
		    HANDLER
			fprintf(stderr,"Call to RPrint failed.\en");
		    END_HANDLER;
		fclose(source);
	}
	CourierClose(connection);
}

SendSource(bdtconnection)
CourierConnection *bdtconnection;
{
	int count;
	char buffer[SPPMAXDATA];

	while ( (count = fread(buffer,1,SPPMAXDATA,source)) > 0 &&
		BDTwrite(bdtconnection,buffer,count) >= 0 )
		;
	if (count <= 0)
		BDTclosewrite(bdtconnection);	/* last packet with EOM set */
	else
		BDTabort(bdtconnection);
}
.fi
.sp 2
.sh 2 "Server (PrintFile.c)"
.sp 1
.nf
#include <stdio.h>
#include <sys/types.h>
#include <netxns/ns.h>
#include "PrintFile_defs.h"

RPrintResult
Rprint(source,CourierBDTProc,s)
	CourierConnection *source, CourierBDTProc;
	BulkData1_Source s;
{
	FILE *printpipe;
	char sppdata[SPPMAXDATA];

	switch (s.designator)
	case active:
	case passive:
		raise(CantPrint);
		/*NOTREACHED*/
	case null:
		system("print /dev/null");	/* print a null file */
		return;
	case immediate:
		if ((printpipe = popen("print","w")) == NULL) {
			raise(CantPrint);
			/*NOTREACHED*/
		}
		count = BDTread(source, sppdata, SPPMAXDATA);
		while (count > 0) {	/* actually read data */
			if (fwrite(sppdata, 1, count, printpipe) == 0) {
				BDTabort(source);
				break;
			}
			count = BDTread(source, sppdata, SPPMAXDATA);
		}
		if (pclose(printpipe) == 0 && count == 0)
			return;
		else raise(CantPrint);
}
.fi
.sh 1 "One Final Example"
.pp
Finally, we present a slightly 
more useful example, a program to print an Interpress
file on a Services-8 printer.  It depends on the standard Xerox Printing
specification, program 4 version 3.  For an even better example, look
at the printing client, xnsprint, provided with the XNS Courier distribution
as
.i "./examples/print/xnsprint.c" .
.sp 2
.nf
#include <stdio.h>
#include <sys/types.h>
#include <netns/ns.h>
#include "Printing_defs.h"
#include <except.h>

static FILE *ipfile = NULL;

SendSource(bdtconnection)
CourierConnection *bdtconnection;
{
	int count;
	char buffer[SPPMAXDATA];

	while ( (count = fread(buffer,1,SPPMAXDATA,ipfile)) > 0 &&
		BDTwrite(bdtconnection,buffer,count) >= 0 )
		;
	if (count <= 0)
		BDTclosewrite(bdtconnection);	/* last packet with EOM set */
	else
		BDTabort(bdtconnection);
}

main(argc, argv)
	int argc;
	char *argv[];
{
	PrintResults result;
	struct ns_addr *destaddr;
	CourierConnection *conn;
	extern struct ns_addr *getXNSaddr();
	PrintAttributes attributes;
	PrintOptions options;

	/* use Cornell print server, CornellS1 (slander) */
	destaddr = getXNSaddr("2-273#2-852-159-207");
	attributes.length = 0;
	options.length = 0;
	if (argc != 2 || ((ipfile = fopen(argv[1],"r")) == NULL)) {
		fprintf(stderr,"Usage: %s file\en",argv[0]);
		exit(1);
	}
	if ((conn = CourierOpen(destaddr)) == NULL) {
		fprintf(stderr,"Can't open connection to %s\en",xnshost);
		exit(1);
	}

	DURING
		result = Print(conn, SendSource, BulkData1_immediateSource,
					attributes, options);
	HANDLER {
		switch (Exception.Code) {
		case Busy:
			fprintf(stderr,"Busy\en");
			break;
		case ConnectionError:
			fprintf(stderr,"Connection error, %d\en",
				CourierErrArgs(ConnectionErrorArgs,problem));
			break;
		case InsufficientSpoolSpace:
		case SpoolingQueueFull:
			fprintf(stderr,"Insufficient spool space\en");
			break;
		case SpoolingDisabled:
			fprintf(stderr,"Spooling disabled\en");
			break;
		case MasterTooLarge:
		case TooManyClients:
		case ServiceUnavailable:
		case SystemError:
		case InvalidPrintParameters:
		case MediumUnavailable:
		case TransferError:
			fprintf(stderr,"Some Printing error, number %d\en",
				Exception.code-ERROR_OFFSET);
			break;
		case Undefined:
			fprintf(stderr,"Undefined error, number %d\en",
				CourierErrArgs(UndefinedArgs,problem));
			break;
		case REJECT_ERROR:
			fprintf(stderr,"REJECT:  type = %d\en",
				CourierErrArgs(rejectionDetails, designator));
			break;
		default:
			fprintf(stderr,"Some random error, code %d\en",
				Exception.Code);
			break;
		}
	exit(1);
	} END_HANDLER;

	CourierClose(conn);
	printf("Done.  Request ID %x %x %x %x %x\en",
		result.printRequestID[0], result.printRequestID[1],
		result.printRequestID[2], result.printRequestID[3],
		result.printRequestID[4]);
}
.fi
.bp
.sh 1 "Final Notes"
.pp
The issues of authentication and protection are
difficult.
They are only touched upon in this implementation.
Currently, each Courier program must perform any authentication
(presumably using the Authentication protocol)
if this is desired.  A few routines exist to allow cleints and servers
to perform simple Authentication; no support for strong Authentication
has yet been written.
.pp
This implementation is fairly inefficient, especially in
the implementation of servers.  Courier calls require substantial
extra copying of courier arguments and results.  More significantly,
the requirement that each call spawn a unique process is very expensive
in UNIX, and should be reconsidered.
A sequence of RPCs on the same SPP stream that specifies the same program
and version number uses the same process, but in all other cases a new
process must be spawned for each individual RPC.
.pp
This implementation defines a number of reserved identifiers.
.b ">>>should list them here<<<"
.sh 1 "Appendix"
.pp
This appendix contains the grammar for the Courier language.
It is similar to the YACC specification used
by the Courier compiler.
.sp
.ps -2p
.vs -2p
.nf
.TS
l l l l l.
%token	identifier	number	string	
	ARRAY	BEGIN	BOOLEAN	CARDINAL
	CHOICE	DEPENDS	END	ERROR
	FALSE	INTEGER	LONG	OF
	PROCEDURE	PROGRAM	RECORD	REPORTS
	RETURNS	SEQUENCE	STRING	TRUE
	TYPE	UNSPECIFIED	UPON	VERSION
.TE
%%

Program :
		identifier ':' PROGRAM number VERSION number '='
		BEGIN DependencyList DeclarationList END '.'
	|
		identifier ':' PROGRAM '='
		BEGIN DependencyList DeclarationList END '.'
	;

DependencyList :
		/* empty */
	|	DEPENDS UPON ReferencedProgramList ';'
	;

ReferencedProgramList :
		ReferencedProgram
	|	ReferencedProgramList ',' ReferencedProgram
	;

ReferencedProgram :
		identifier '(' number ')' VERSION number
	;

DeclarationList :
		/* empty */
	|	DeclarationList Declaration
	;

Declaration :
		identifier ':' TYPE '=' Type ';'
	|	identifier ':' Type '=' Constant ';'
	;

Type :
		PredefinedType
	|	ConstructedType
	|	ReferencedType
	;

PredefinedType :
		BOOLEAN
	|	CARDINAL
	|	LONG CARDINAL
	|	INTEGER
	|	LONG INTEGER
	|	STRING
	|	UNSPECIFIED
	|	LONG UNSPECIFIED
	;

ConstructedType :
		'{' CorrespondenceList '}'
	|	ARRAY NumericValue OF Type
	|	SEQUENCE MaximumNumber OF Type
	|	RECORD '[' FieldList ']'
	|	RECORD '[' ']'
	|	CHOICE DesignatorType OF '{' CandidateList '}'
	|	PROCEDURE ArgumentList ResultList ErrorList
	|	ERROR ArgumentList
	;

ReferencedType :
		identifier
	|	identifier '.' identifier
	;

CorrespondenceList :
		Correspondence
	|	CorrespondenceList ',' Correspondence
	;

Correspondence :
		identifier '(' NumericValue ')'
	;

MaximumNumber :
		NumericValue
	|	/* empty */
	;

NumericValue :
		number
	|	ReferencedConstant
	;

DesignatorType :
		/* empty */
	|	ReferencedType
	;

CandidateList :
		Candidate
	|	CandidateList ',' Candidate
	;

Candidate :
		DesignatorList '=''>' Type
	;

DesignatorList :
		Designator
	|	DesignatorList ',' Designator
	;

Designator :
		identifier
	|	Correspondence
	;

ArgumentList :
		/* empty */
	|	'[' FieldList ']'
	;

ResultList :
		/* empty */
	|	RETURNS '[' FieldList ']'
	;

ErrorList :
		/* empty */
	|	REPORTS '[' NameList ']'
	;

FieldList :
		Field
	|	FieldList ',' Field
	;

Field :
		NameList ':' Type
	;

Constant :
		PredefinedConstant
	|	ConstructedConstant
	|	ReferencedConstant
	;

PredefinedConstant :
		TRUE
	|	FALSE
	|	number
	|	'-' number
	|	'"' string '"'
	;

ConstructedConstant :
		identifier
	|	'[' ElementList ']'
	|	'[' ComponentList ']'
	|	'['']'
	|	identifier Constant
	|	number
	;

ReferencedConstant :
		identifier
	|	identifier '.' identifier
	;

ElementList :
		Constant
	|	ElementList ',' Constant
	;

ComponentList :
		Component
	|	ComponentList ',' Component
	;

Component :
		NameList ':' Constant
	;

NameList :
		identifier
	|	NameList ',' identifier
	;
.fi
.vs
.ps
