Introduction
Courier client / server interface intended primarily for Sirocco-generated stubs.
The "class" stuff determines what kind of transport is used — currently:
$SPP gets you "official" Courier. This uses SPP streams for its transport and supports bulk data transfer, but does not allow broadcast calls.
$EXCHANGE gets you "expedited" Courier. This uses (unreliable) packet exchange for transport, requires that each argument or result record fit in a single packet, and has no support for bulk data transfer, but does allow broadcast calls.
Other classes could be implemented — see the CrRPCFriends interface. To allow non-XNS transport, though, we'd probably have to change all the XNS.address parameters in this interface to Rope.ROPE ...
For client stubs: It is the client stub's responsibility to ensure that only one call is outstanding on a handle at any time. Sirocco takes care of this.
Create / Set Parameters / Destroy for Client Handles
CreateClientHandle:
PROC [
class: HandleClass,
remote: XNS.Address,
timeoutMsec: INT ← 0
] RETURNS [Handle];
Create a client handle connected to the specified remote address.
For restrictions on remote see SetRemote[...] below.
DestroyClientHandle:
PROC [h: Handle];
All the SetXXX procs return a handle with the desired parameter values. This handle is not guaranteed to the same as the original argument handle.
SetRemote:
PROC [h: Handle, remote:
XNS.Address]
RETURNS [Handle];
Redirect a client handle to the given remote address.
The socket component of the address may be defaulted to XNS.unknownSocket, in which case a well-known socket will be used if it exists for the transport class, otherwise Error[notImplemented].
SetTimeout:
PROC [h: Handle, timeoutMsec:
INT ← 0]
RETURNS [Handle];
Reset the timeout associated with a handle.
Treatment of the timeout value is class-specific. Specifying 0 gives a default that is reasonable for the transport class.
SetHops:
PROC [h: Handle, low, high:
NAT]
RETURNS [Handle];
Only implemented for transport supporting broadcast ...
Calling a Remote Procedure
PutArgsProc:
TYPE ~
PROC [h: Handle];
Called to put arguments to handle using PutBYTE, ..., defined below.
A PutArgsProc may propagate errors raised from underlying transport mechanism (e.g. XNSStream.ConnectionClosed[...]), in which case it will eventually be unwound by the invoking CallProc.
GetResultsProc:
TYPE ~
PROC [h: Handle];
Called to get results from handle using GetBYTE, ..., defined below.
A GetResultsProc may propagate errors raised from underlying transport mechanism (e.g. XNSStream.ConnectionClosed[...], IO.EndOfStream[...]), in which case it will eventually be unwound by the invoking CallProc.
GetErrorProc:
TYPE ~
PROC [h: Handle, errNum:
CARDINAL];
Called to get arguments to a remote ERROR from handle using GetBYTE, ..., defined below.
A GetErrorProc may propagate errors raised from underlying transport mechanism (e.g. XNSStream.ConnectionClosed[...], IO.EndOfStream[...]), in which case it will eventually be unwound by the invoking CallProc.
CallProc:
TYPE ~
PROC [
h: Handle,
remotePgm: CARD,
remotePgmVersion: CARDINAL,
remoteProc: CARDINAL,
putArgs: PutArgsProc ← NIL,
getResults: GetResultsProc ← NIL,
getError: GetErrorProc ← NIL];
! Error[Handle, errorReason, text]
A handle's CallProc invokes a remote procedure. It works by:
(1) call putArgs to stuff the arguments.
(2) send the call message.
(4) get the reply; if it's a normal return then call getResults, otherwise ERROR Error[...].
Network and stream errors raised by putArgs and getResults will be caught, unwound and converted to Error[...]. Unwinding after Error[...] cleans up correctly.
It is reasonable for getResults to do substantial processing, but (obviously) it can't do another remote procedure call using the same handle.
Call: CallProc;
Servers for Remote Calls
ServerProc:
TYPE ~
PROC [
h: Handle,
pgm: CARD,
pgmVersion: CARDINAL,
proc: CARDINAL,
beginReturn: BeginReturnProc,
beginError: BeginErrorProc,
beginReject: BeginRejectProc
];
Server for all procedures of a version of a remote program.
BeginReturnProc:
TYPE ~
PROC [h: Handle];
Notifies Courier that we're about to construct the return record.
BeginErrorProc:
TYPE ~
PROC [h: Handle, errNum:
CARDINAL];
Notifies Courier that we're about to construct an abort record.
Reject Reasons
noSuchProgram: CARDINAL ~ 0;
noSuchVersion: CARDINAL ~ 1;
noSuchProcedure: CARDINAL ~ 2;
invalidArgument: CARDINAL ~ 3;
unspecifiedReject: CARDINAL ~ 0ffffH;
BeginRejectProc:
TYPE ~
PROC [h: Handle, rejectReason:
CARDINAL ← noSuchProcedure];
Notifies Courier that we're about to construct a reject record. Use of noSuchVersion or noSuchProgram as the rejectReason here would be questionable, since these errors are caught before the server proc is called.
RegisterServerProc:
PROC [
pgm: CARD,
pgmVersion: CARDINAL,
serverProc: ServerProc];
Register a remote program ServerProc. Specifying NIL for serverProc will undo any existing registration for specified pgm, pgmVersion.
EnsureListener:
PROC [
class: HandleClass,
socket: XNS.Socket ← XNS.unknownSocket];
Ensure that there's a listener for the given transport class listening at the specified socket. This is in some sense a CROCK — there should be a Courier well-known socket for each transport class, but they didn't design it that way. For transport with a well-known socket (e.g. SPP) the default is to use it; for transport without a well-known socket, the default will raise Error[notImplemented]. In any case, an explicitly-specified socket might make sense for debugging ...
GetRemote:
PROC [h: Handle]
RETURNS [remote:
XNS.Address];
Determine the remote address of a given handle. Used by server to determine client's address.
Marshalling Primitives
GetBYTE:
PROC [h: Handle]
RETURNS [
BYTE] ~
INLINE {
RETURN [h.marshallProcs.getB[h]] };
GetCHAR:
PROC [h: Handle]
RETURNS [
CHAR] ~
TRUSTED
INLINE {
RETURN [LOOPHOLE[h.marshallProcs.getB[h]]] };
GetHWORD:
PROC [h: Handle]
RETURNS [
HWORD] ~
INLINE {
RETURN [h.marshallProcs.getH[h]] };
GetBOOL:
PROC [h: Handle]
RETURNS [
BOOL] ~
INLINE {
RETURN [Endian.CardFromH[h.marshallProcs.getH[h]] # 0] };
GetCARDINAL:
PROC [h: Handle]
RETURNS [
CARDINAL] ~
INLINE {
RETURN [Endian.CardFromH[h.marshallProcs.getH[h]]] };
GetINTEGER:
PROC [h: Handle]
RETURNS [
INTEGER] ~
TRUSTED
INLINE {
RETURN [LOOPHOLE[Endian.CardFromH[h.marshallProcs.getH[h]]]] };
GetFWORD:
PROC [h: Handle]
RETURNS [
FWORD] ~
INLINE {
RETURN [h.marshallProcs.getF[h]] };
GetCARD:
PROC [h: Handle]
RETURNS [
CARD] ~
INLINE {
RETURN [Endian.CardFromF[h.marshallProcs.getF[h]]] };
GetINT:
PROC [h: Handle]
RETURNS [
INT] ~
TRUSTED INLINE {
RETURN [LOOPHOLE[Endian.CardFromF[h.marshallProcs.getF[h]]]] };
UnsafeGetBlock:
UNSAFE
PROC [h: Handle, block:
IO.UnsafeBlock]
RETURNS [nBytesRead:
INT] ~
TRUSTED
INLINE {
RETURN [h.marshallProcs.unsafeGetBlock[h, block]] };
GetHAlign:
PROC [h: Handle] ~
INLINE {
h.marshallProcs.getHAlign[h] };
PutBYTE:
PROC [h: Handle, byte:
BYTE] ~
INLINE {
h.marshallProcs.putB[h, byte] };
PutCHAR:
PROC [h: Handle, byte:
CHAR] ~
TRUSTED
INLINE {
h.marshallProcs.putB[h, LOOPHOLE[byte]] };
PutHWORD:
PROC [h: Handle, hWord:
HWORD] ~
INLINE {
h.marshallProcs.putH[h, hWord] };
PutBOOL:
PROC [h: Handle, bool:
BOOL] ~
INLINE {
h.marshallProcs.putH[h, Endian.HFromCard[IF bool THEN 1 ELSE 0]] };
PutCARDINAL:
PROC [h: Handle, cardinal:
CARDINAL] ~
INLINE {
h.marshallProcs.putH[h, Endian.HFromCard[cardinal]] };
PutINTEGER:
PROC [h: Handle, integer:
INTEGER] ~
TRUSTED
INLINE {
h.marshallProcs.putH[h, Endian.HFromCard[LOOPHOLE[integer]]] };
PutFWORD:
PROC [h: Handle, fWord:
FWORD] ~
INLINE {
h.marshallProcs.putF[h, fWord] };
PutCARD:
PROC [h: Handle, card:
CARD] ~
INLINE {
h.marshallProcs.putF[h, Endian.FFromCard[card]] };
PutINT:
PROC [h: Handle, int:
INT] ~
TRUSTED
INLINE {
h.marshallProcs.putF[h, Endian.FFromCard[LOOPHOLE[int]]] };
UnsafePutBlock:
UNSAFE
PROC [h: Handle, block:
IO.UnsafeBlock] ~
TRUSTED
INLINE {
h.marshallProcs.unsafePutBlock[h, block] };
PutHAlign:
PROC [h: Handle] ~
INLINE {
h.marshallProcs.putHAlign[h] };
SeqHWORD: TYPE ~ REF SeqHWORDObject;
SeqHWORDObject:
TYPE ~
MACHINE
DEPENDENT
RECORD [
body: PACKED SEQUENCE length: CARDINAL OF HWORD ];
GetSeqHWORD: PROC [h: Handle] RETURNS [seqHWORD: SeqHWORD];
PutSeqHWORD: PROC [h: Handle, seqHWORD: SeqHWORD];
SkipSeqHWORD:
PROC [h: Handle];
SeqCARDINAL: TYPE ~ REF SeqCARDINALObject;
SeqCARDINALObject:
TYPE ~
MACHINE
DEPENDENT
RECORD [
body: PACKED SEQUENCE length: CARDINAL OF HWORD ];
GetSeqCARDINAL: PROC [h: Handle] RETURNS [seqCARDINAL: SeqCARDINAL];
PutSeqCARDINAL: PROC [h: Handle, seqCARDINAL: SeqCARDINAL];
SkipSeqCARDINAL:
PROC [h: Handle];
GetROPE: PROC [h: Handle] RETURNS [rope: ROPE];
PutROPE: PROC [h: Handle, rope: ROPE];
SkipROPE: PROC [h: Handle];
MarshalledROPEHWords: PROC [rope: ROPE] RETURNS [hWords: CARDINAL];
Bulk Data
BulkDataCheckAbortProc: TYPE ~ PROC [h: Handle] RETURNS [abort: BOOL];
BulkDataXferProc:
TYPE ~
PROC [h: Handle, stream:
IO.
STREAM, checkAbort: BulkDataCheckAbortProc]
RETURNS [abort:
BOOL];
Think of this as a callback proc passed from client to server. The server calls the BulkDataXferProc, passing an IO.STREAM, for the data, and the checkAbort proc, which will be polled to determine whether the server wants to abort the transfer. The result of the BulkDataXferProc indicates whether the client aborted the transfer.
BulkDataSource: TYPE ~ BulkDataXferProc;
BulkDataSink:
TYPE ~ BulkDataXferProc;
NOTE (Nov 1986): GetBulkDataXXXX are not INLINE to avoid compiler error in pass 4.
GetBulkDataSource:
PROC [h: Handle]
RETURNS [BulkDataSource];
~ INLINE { RETURN [h.marshallProcs.getBulkDataSource[h] };
GetBulkDataSink:
PROC [h: Handle]
RETURNS [BulkDataSink];
~ INLINE { RETURN [h.marshallProcs.getBulkDataSink[h] };
PutBulkDataSource:
PROC [h: Handle, bulkDataSource: BulkDataSource] ~
INLINE {
h.marshallProcs.putBulkDataSource[h, bulkDataSource] };
PutBulkDataSink:
PROC [h: Handle, bulkDataSink: BulkDataSink] ~
INLINE {
h.marshallProcs.putBulkDataSink[h, bulkDataSink] };
Private Details
Object:
TYPE ~
RECORD [
class: HandleClass,
kind: HandleKind,
marshallProcs: REF MarshallProcs,
procs: REF ANY, -- really (REF ClientProcs) * (REF ServerProcs)
data: REF ANY, -- private, for use by implementor
clientData: REF ANY -- public, for use by client
];
MarshallProcs:
TYPE ~
RECORD [
putB: PROC [Handle, BYTE],
putH: PROC [Handle, HWORD],
putF: PROC [Handle, FWORD],
unsafePutBlock: UNSAFE PROC [Handle, IO.UnsafeBlock],
putBulkDataSource: PROC [Handle, BulkDataSource],
putBulkDataSink: PROC [Handle, BulkDataSink],
putHAlign: PROC [Handle],
getB: PROC [Handle] RETURNS [BYTE],
getH: PROC [Handle] RETURNS [HWORD],
getF: PROC [Handle] RETURNS [FWORD],
unsafeGetBlock: UNSAFE PROC [Handle, IO.UnsafeBlock] RETURNS [INT],
getBulkDataSource: PROC [Handle] RETURNS [BulkDataSource],
getBulkDataSink: PROC [Handle] RETURNS [BulkDataSink],
getHAlign: PROC [Handle]
];
ClientProcs:
TYPE ~
RECORD [
setRemote: PROC [Handle, XNS.Address] RETURNS [Handle],
setTimeout: PROC [Handle, INT] RETURNS [Handle],
setHops: PROC [Handle, NAT, NAT] RETURNS [Handle],
destroy: PROC [Handle],
finalize: PROC [Handle] ← NIL,
call: CallProc
];
ServerProcs:
TYPE ~
RECORD [
getRemote: PROC [Handle] RETURNS [XNS.Address]
];
}.