LupineUsersGuide.tioga
Last Edited by:
Andrew Birrell, 08-Jul-82 11:56:52 PDT
Subhana, June 4, 1984 9:59:55 am PDT
Bob Hagmann October 9, 1985 3:02:33 pm PDT
LUPINE USER'S GUIDEVERSION 1.2
LUPINE USER'S GUIDEVERSION 1.2
CEDAR 6.0 — FOR INTERNAL XEROX USE ONLY
CEDAR 6.0 — FOR INTERNAL XEROX USE ONLY
Lupine User's Guide
An Introduction to Remote Procedure Calls in Cedar
Version 1.2
Bruce Nelson and Andrew Birrell
Release as [Cedar]<Cedar6.0>Documentation>LupineUsersGuide.tioga
© Copyright 1985 Xerox Corporation. All rights reserved.
This document describes research software for Xerox internal use only.
XEROX Xerox Corporation
Palo Alto Research Center
3333 Coyote Hill Road
Palo Alto, California 94304
For Internal Xerox Use Only
Contents
1Introduction
2Translator Operation
3Conversations, Encryption, and Secure Communication
4Binding and Configuration
5Crash Detection and Recovery
6Parameter Passing
7Interface and Parameter Restrictions
8References
Appendix A: Public RPC Interfaces
Appendix B: Example Remote Interface and Stubs
1 Introduction
Lupine is the translator for Cedar's [Cedar] remote procedure call mechanism. A thorough understanding of remote procedure call (RPC) principles is assumed throughout this guideindeed, once-skeptical readers have assured us it is foolish to continue without adequate background. Nelson's dissertation [RPC] provides more than the necessary foundation, as Lupine, not surprisingly, resembles that thesis's Emissary mechanism closely (Lupine, however, includes no orphan algorithms). Be particularly conversant with chapters 1 and 2, and section 5.4.
Relationship to Courier
Xerox's Office Products Division has its own RPC mechanism, Courier [CourierFS, CourierPS]. Courier is for use in standard Pilot [Pilot] environments and has its own Lupine-like translator, Diplomat [Diplomat]. The Courier system is supported by OPD and is released with Pilot; it is the product software RPC mechanism. Lupine, in contrast, is supported by CSL for use primarily within CSL and PARC. Lupine's remote calls execute in both the Cedar and Pilot environments, are about ten times faster than Courier's, and are significantly easier to program.
Operational Overview
Lupine reads a compiled Mesa interface and mechanically writes four Mesa source modules that implement the same interface remotely. For a given
DEFINITIONS interface named
Target, Lupine's stub control interface and three stub modules are:
TargetRpcControl.mesa. This public definitions module contains declarations for the remote binding of Target.
TargetRpcClientImpl.mesa. This program module executes in hosts that want to import Target. TargetRpcClientImpl is the client stub module. It exports Target (when translated using the ImportSingle[TRUE] option) and TargetRpcControl.
TargetRpcBinderImpl.mesa. This optional program module is used to instantiate and import a dynamically varying number of remote Target interfaces. It exports TargetRpcControl but not Target. It depends on module TargetRpcClientImpl.
TargetRpcServerImpl.mesa. This program module executes in hosts that want to export Target. TargetRpcServerImpl is the server stub module. It imports Target and exports TargetRpcControl.
Appendix B contains an example of each of these generated modules. As you use this guide, refer to appendix B as necessary for concrete examples.
To use the stub modules compiled from the generated programs, clients include them in configurations on their client and server hosts. Aside from these configuration modifications, the only required program changes are two procedure calls to routines in TargetRpcControl. These binding calls, discussed in detail later, cause the serving machine to export Target and the client machine to import it. This scenario is slightly more complicated with secure calls or multiple instantiation, and much more complicated with dynamic crash recovery.
To establish bindings, make secure calls, and handle exceptions, Cedar RPC programmers use an RPCRuntime package interface called RPC. There is a parallel interface, MesaRPC, for standard Pilot programmers. Appendix A reproduces both of these interfaces.
Acknowledgments
Cedar's RPC mechanism is the work of two people: Andrew Birrell designed and wrote the RPCRuntime package with its transport, binding, and secure communication subsystems; Bruce Nelson designed the stub format and wrote the Lupine translator and diagnostic tools. In addition, members of the Alpine and Voice projectsMark Brown, Dan Swinehart, and Ed Taft, in particularmade numerous good suggestions about parameter marshaling and multiple interface instances. Dan was also a revealing alpha tester.
This guide is primarily Nelson's venture, with Birrell contributing material on secure communication and remote binding.
2 Translator Operation
Installation
The Lupine translator runs in the Cedar environment (but a Tajo version would be trivial if needed). To install Lupine and all the necessary RPCRuntime underpinnings, use Bringover to retrieve the public components of [Indigo]<Cedar>Top>Lupine.df.
Translation
Lupine reads the BCD of a compiled interface and writes four output files. At present, Lupine is used from the Executive, although eventually it will be under the control of the Cedar system modeller. Establishing a command file to translate each remote interface, or package of interfaces, is recommended for now.
The basic procedure is this:
When your target interface is ready for remote use, compile it:
% Compile Target
Feed the resulting interface to Lupine:
% Lupine TranslateInterface[Target]
Compile the four stub modules, in this order:
% Compile TargetRpcControl TargetRpcClientImpl,TargetRpcBinderImpl
TargetRpcServerImpl
Lupine records its translation progress in the Executive's window and log. The source and object files for the stubs will be many times larger than the original interface, so be sure you have sufficient space for them.
To abort a translation just click "STOP!" in the CommandTool window.
Commands and Syntax
Lupine can be invoked from the command line or used interactively from the keyboard. There is one real command,
TranslateInterface, and a number of option-setting commands. They are presented here in procedural keyboard style. Consult a Lupine wizard before using a nondefault value for
DefaultParameterPassing, FreeServerArguments or
DynamicHeapNews.
Help
Print a string that describes most of the options
TranslateInterface [fileName: STRING]
Lupine processes fileName.bcd, e.g., Target.bcd.
TargetLanguage [language: STRING ← Cedar]
TargetLanguage sets the programming language used to write the output files (do not confuse the TargetLanguage command with the use of Target as an example interface). The acceptable languages are Cedar and Mesa; Cedar is the default.
DefaultParameterPassing [method: STRING ← VALUE]
This command sets the default parameter passing method to be VAR, VALUE, RESULT, or HANDLE. (These are explained in a later section on Parameter Passing.) VALUE is the default, and take heed that this is different from Mesa's default semantics for address-containing values.
InterMdsCalls [yes: BOOLEAN ← FALSE]
When InterMdsCalls is true, the importers and exporters of Target must operate within the same host (the remote binder insures this), but can execute in different MDSs. This tighter binding yields much faster parameter marshaling for address-containing parameters. Note, however, that intraMDS addresses such as POINTERs and STRINGs are necessarily illegal in interMDS calls, and Lupine issues warnings if they are used. The default is false.
InlineDispatcherStubs [yes: BOOLEAN ← TRUE]
Lupine usually writes a single large procedure in the server stub program. For large or complicated remote interfaces, however, the compiler can overflow when it compiles this huge dispatcher procedure. If this happens, setting InlineDispatcherStubs to false may cure the overflow problem by writing many small procedures instead. If not, there two distasteful alternatives: either simplify the troublesome interface, or split it into two smaller interfaces. The default is true.
FreeServerArguments [yes: BOOLEAN ← TRUE]
When FreeServerArguments is true, Lupine automatically deallocates any noncollected storage that it allocates to unmarshal arguments in the server stubs. When false, it does not deallocate this storage, presumably because the underlying server implementation is going to assume responsibility for deallocation itself. This unSAFE feature is only for Mesanot CedarRPC interfaces that use this contorted allocation strategy. It applies only to arguments: storage for results is never deallocated in either the client or the server. The default is true.
DynamicHeapNews [heap, mds: INTEGER ← 50, 50]
A Mesa remote interface can have parameters with a dynamic number of noncollected objects (i.e., objects NEWed from the heap, such as a descriptor of strings or a sequence of pointers). Lupine imposes a translation-time upper bound on the total number of these dynamic objects allowed in the argument listnot result listof a single call. The default is 50 heap (long pointer) objects and 50 mds (short pointer) objects.
LineLength [length: INTEGER ← 82]
LineLength specifies the approximate maximum length of source lines in the generated output files. The default, 82, is a good choice for Viewers and Tajo.
DeclareSignals [yes: BOOLEAN ← TRUE]
Lupine normally expects that the client and server will run on different machines. Hence, both the server and client code have to define any SIGNAL or ERROR in the interface to be translated. If the client and server will run on same machine, then "DeclareSignals" can be set to FALSE, and the user must declare all SIGNAL or ERROR and export them to the proper interfaces.
ImportSingle [yes: BOOLEAN ← FALSE]
Lupine will either translate an interface for single import, or multiple import (see "Importing One Remote Instance" and "Importing Multiple Remote Instances" below). The default is multiple import.
InlinePacketAllocate [yes: BOOLEAN ← TRUE | INT]
Lupine needs to write code that allocates packet buffers. The default is to use inline code that is fast and only does procedure calls in special cases. This code can make the module large, and this option allows the user to trade space for speed. If an INT is given, only this many procedures will be translated using inline allocate.
InlineMarshal [yes: BOOLEAN ← TRUE ]
FALSE produces a marshalling procedure per type. This can save code size at the expense of speed if the same types are used in many procedures.
InlineRopeMarshal [yes: BOOLEAN ← TRUE ]
FALSE produces prodedure calls for ROPE marshalling. This can save code size at the expense of speed if ROPEs are used.
There are additional debugging commands for wizards only. Type ? at any point for a list of possible commands or responses.
Command Files
The following example illustrates option-setting and translation in command files:
>Lupine DefaultParameterPassing[VAR] InlineDispatcherStubs[FALSE]
LineLength[60] TranslateInterface[Target]
Lupine Errors
Fortunately, because Lupine's input is compiler output, few translation errors are possible. Most errors are caused by parameter passing restrictions. Lupine reports errors both in the typescript and in the output file itself. Error and warning messages in the typescript include the output file name and character position of the error, and messages in the output file are preceded with "#####" to make them easy to spot. Many errors cause an ERROR statement to be inserted in the output file. This prevents a bogus stub from executing when the result would be chaos. If you can't understand a particular error message, see a Lupine wizard.
As mentioned above, if a set of stubs is too big to compile, the InlineDispatcherStubs option may remedy the problem.
Compiler Warnings and Errors
The compiler will occasionally issue warning messages about referenced but unused DIRECTORY items as it compiles stub programs. Lupine is a mechanical translator, and eliminating the spurious warnings would be difficult. Live with them.
The compiler may give errors like "must come from an imported interface" or "is an unmatched implicit import". Edit the IMPORTS statement in the program to overcome this.
Translated interfaces tend to be quite large. If you overflow the compiler, try translating with the options above that trade space for speed.
3 Conversations, Encryption, and Secure Communication
Lupine and the RPC runtime provide facilities for making secure remote calls. Such calls are encrypted using the DES encryption algorithm [DES]. The protocols used provide secure two-way authentication without any passwords being passed in the clear [Needham-Schroeder]. Secure RPC calls prevent an intruder from seeing the procedure names, parameters, or results of the calls; from modifying calls in transit; from replaying previously valid calls; from observing passwords (encryption keys) in transit; from observing the cipher text of known (or chosen) plain text.
Secure RPC communication is based on the concept of a
conversation. A conversation takes place between two authenticated Grapevine individuals, known as the
originator and the
responder of the conversation.
RPC.Principal: TYPE = Rope.ROPE;
RPC.EncryptionKey: TYPE = BodyDefs.Password;
RPC.Conversation:TYPE = . . . ;
RPC.SecurityLevel = {
none,
authOnly,
ECB,
CBC,
CBCCheck};
RPC.GenerateConversation: PROC RETURNS[Conversation];
RPC.StartConversation: PROC [
caller: Principal,
key: EncryptionKey,
callee: Principal,
level: SecurityLevel[authOnly..CBCCheck] ]
RETURNS[Conversation];
RPC.EndConversation: PROC [conversation: Conversation];
RPC.GetCaller: PROC [conversation: Conversation] RETURNS [Principal];
RPC.GetLevel: PROC [conversation: Conversation] RETURNS [SecurityLevel];
RPC.ConversationID: TYPE [3];
RPC.GetConversationID: PROC[conversation: Conversation]
RETURNS [id: ConversationID];
RPC.AuthenticateFailed: ERROR [why: RPC.AuthenticateFailure];
A conversation is created by the originator calling RPC.StartConversation and is destroyed by the originator calling RPC.EndConversation. The originator provides his own name and password and the name of the responder. StartConversation communicates with Grapevine to verify the validity of these arguments; at this time there is no communication with the responder. See below for a description of the SecurityLevel of a conversation. At present, the communication with Grapevine does not use secure protocols.
Having created the conversation, the originator may now make secure calls to the responder by passing the
Conversation as the first argument of any remote call to a procedure in an interface that is exported by the responder. (The same conversation may be used to make calls in multiple interfaces.) The RPC runtime guarantees that the call can be understood only inside a host containing the authenticated responder and that the call will be received by the responder at most once. The responder can determine the authenticated identity of the originator by calling
RPC.GetCaller when he receives a call having the
Conversation as its first argument. Every conversation is uniquely identified for all time by an identifier that either party may obtain by calling
RPC.GetConversationID. Calling
RPC.StartConversation may raise the exception
AuthenticateFailed, with the following values for its argument:
communications Unable to contact Grapevine.
badCaller The originator's name is not a Grapevine individual.
badKey The originator's password was incorrect.
badCallee The responder's name is not a Grapevine individual.
The procedure GenerateConversation may be called to obtain a Conversation to be used solely for calls within a single machine; such a Conversation must not be used for remote calls.
The SecurityLevel passed to StartConversation indicates how much security the originator wants for this conversation. The SecurityLevel of any conversation may be obtained by calling GetLevel. The meanings of the conversation levels are as follows. none indicates that the conversation was created by calling GenerateConversation, and that no authentication or encryption has been employed. authOnly will perform the authentication, but calls are not encrypted, and modification, manufacture or replay of calls or results is not detected. ECB uses the ECB mode of DES [DES], which protects calls and results from eavesdropping (and makes modifying, manufacturing or replaying calls or results somewhat more difficult). ECB mode does not hide patterns of repeated data within or between calls. CBC uses the CBC mode of DES [DES],, which behaves like ECB except that it hides repeated data patterns. CBCCheck is like CBC, with the addition of a checksum on each call and result; this prevents any eavesdropping, modification, manufacture or replay of a call or result. CBCCheck should normally be used for secure communication, and authOnly should normally be used for applications where only authantication is desired; the other values of SecurityLevel are advisable only where hardware or performance constraints dictate. At present the DES algorithm is replaced by a trivial exclusive-or algorithm, because of the absence of encryption hardware.
4 Binding and Configuration
Lupine does not perform remote binding by using remote versions of the modeller or C/Mesa binder [Mesa]. Instead, the modeller or binder are used to bindlocallyto an instance of TargetRpcControl. A remote interface, say Target, is then bound remotely by calling binding procedures in the TargetRpcControl interface. These procedures use the RPCRuntime, in cooperation with Grapevine [Grapevine], to perform remote binding. This is elaborated below, after a discussion of remote interface naming.
Interface Names, Instances, and Versions
Naming remote interfaces is much more complicated than naming local-machine interfaces because remote interfaces must be uniquely identifiable in a distributed environment. Furthermore, ability to control the degree of typechecking is required for widespread systems such as Laurel (see sections 4.1.3.1 and 4.1.3.4 in Nelson's thesis [RPC]). Lupine addresses this problem with InterfaceNames, defined below. (ShortROPEs are discussed in the Parameter Passing section.)
RPC.ShortROPE: TYPE = Rope.ROPE;
RPC.VersionRange: TYPE = RECORD [first, last: CARDINAL];
RPC.matchAllVersions: VersionRange = ...;
RPC.InterfaceName: RECORD [
type: ShortROPE ← NIL,
instance: ShortROPE ← NIL,
version: VersionRange ← matchAllVersions ];
RPC.defaultInterfaceName:
InterfaceName = [];
InterfaceName.type
The type in an interface name specifies which interface is being exported or imported. This is not a precisely defined concept, since it is up to the implementors of interfaces to separate their interfaces into types, but it is intended to correspond to a single Mesa definitions module. Only the implementor can decide at what stage in the incremental development of an interface it should be considered as a new type. In the distributed environment, there may be multiple exporters of a given type of interface. For example, if the interface is for access to a printer, each printer would export the same type of interface. These multiple interfaces are distinguished by their InterfaceName.instance, described below.
The type of an interface may be a Grapevine R-Name, or an arbitrary string chosen by the implementor. One approach is to use the corresponding definitions module name as the type. The effects of using these forms of type are described below.
InterfaceName.instance
The instance in an interface name specifies which particular one of the (potentially) many instances of this interface is being named. For example, if the interface is for access to a printer and is exported by each printer, each printer would export the same interface type but a different interface instance.
The instance of an interface may be a Grapevine R-Name, or a host name (for example, "Ivy"), or a Pup network address of the exporting host (for example, "3#17#"). The effects of using these forms of instance are described below.
Multiple remotely exported interfaces may have the same instance, provided that they each have a different type and they are exported from the same host.
InterfaceName.version
The version in an interface name may be used to control the extent to which the remote binder will check compatibility of versions between the exporter and importer. If the exporter of a remote interface specifies [a,b] as the InterfaceName.version and the importer specifies [c,d], then the remote binding will be allowed to succeed only if the ranges [a..b] and [c..d] have a non-empty intersection. However, RPC.matchAllVersions is assumed to intersect with every version range. That is, if RPC.matchAllVersions is specified by either the importer or the exporter, then the remote binding will not fail because of version mis-match.
By using this facility, the implementors are asserting that they support, at a bit-wise compatible level, multiple versions of an interface. This freedom is in marked contrast to the Mesa binder (and loader), which insist on identity of versions for binding to succeed. This extra freedom seems necessary in the distributed environment of RPC, but may well be a fertile source of bugs.
To remain upward compatible with previous versions of an interface, you may add procedures (or signals or errors) only at the end of the interface. You must not make any significant changes to any types (for example, changing the order of fields in a record or of parameters to a procedure is significant, as is adding fields or parameters). You may change the names of things (like procedures, fields, parameters). You may also use this version field to indicate semantic changes that do not involve changes to the definitions file.
Exporting
For interface
Target, calling
TargetRpcControl.ExportInterface causes
Target (as specified by
interfaceName) to be exported for immediate remote use. Any attempts to import
Target before
ExportInterface completes, or after
UnexportInterface completes, will raise an exception (see the description of importing, below). These two routines must be called in the strict sequence
ExportInterface,
UnexportInterface,
ExportInterface, .... Calling
UnexportInterface has no effect on calls in progress.
TargetRpcControl.ExportInterface: PROCEDURE [
interfaceName: InterfaceName ← defaultInterfaceName,
user: RPC.Principal,
password: RPC.EncryptionKey,
parameterStorage: Zones ← standardZones ];
TargetRpcControl.UnexportInterface: PROCEDURE;
RPC.ExportFailed: ERROR [why: RPC.ExportFailure];
The precise behavior of ExportInterface depends on the value of interfaceName. If the interfaceName.instance is a Grapevine R-Name, the Grapevine registration service is consulted to ensure that the instance is an individual whose connect-site is a Pup net address for the exporting machine. If necessary, the database is updated, using the given user and password as credentials. If the instance is not an R-Name (that is, it does not contain a dot), it should be either a Pup Name Lookup Server name that maps to the exporting machine, or a Pup net address constant for the exporting machine. If the interfaceName.type is an R-Name, the Grapevine database is consulted (and updated if necessary) to ensure that the type is a group, one of whose members is the interfaceName.instance. At present, these actions on an interfaceName.type which is an R-Name are not implemented; every type is treated as just a string.
If you call ExportInterface and default the interfaceName.type, Lupine will use for the type a combination of the interface name (for example, "Target") and the compiler's version stamp for the interface. The default type is thus a unique name for the remote interface, and this uniqueness permits the default version to be matchAllVersions of which there can be just one.
Calling
ExportInterface with a defaulted
interfaceName.instance is not yet supported; exporters and importers must give an explicit
instance each time. For simple experiments, it is convenient to use your own Grapevine
R-Name as the
instance, and your own credentials as parameters to
ExportInterface. For example:
myInterface: InterfaceName = [instance: "Nelson.pa"]; -- Use default type & version.
TargetRpcControl.ExportInterface [
interfaceName: myInterface,
user: "Nelson.pa",
password: Rpc.MakeKey[ "Nelson's password"] ];
If an attempt to export a remote interface fails, the exception
ExportFailed is raised. No monitors are locked when this signal is raised, so further calls into the RPC runtime may be made from catch-phrases. The parameter of the signal is one of the following:
communications Exporting failed because of inability to contact Grapevine or a Pup Name Lookup server.
badType The interfaceName.type was illegal (a Grapevine R-Name, but not a group).
badInstance The interfaceName.instance was illegal (NIL, not a Grapevine R-Name, not an individual, not a Name Lookup server name for this host, not a net address constant for this host).
badVersion The interfaceName.version was illegal (upper bound less than lower bound).
tooMany Too many current exports for local tables consult a Lupine wizard.
badCredentials The credentials were inadequate for updating the Grapevine database.
Importing
Remote importing of an interface Target may be caused by calling TargetRPCControl.ImportInterface or TargetRPCControl.ImportNewInterface, as described below. In either case, an interfaceName is provided, which is interpreted as follows.
If interfaceName.instance is not NIL, then it must be: a Grapevine R-Name that is an individual whose connect-site is a Pup net address, a Pup Name Lookup server host name, or a Pup net address constant. Each of these maps to a net address of a machine, which is assumed to be the machine that has previously exported the desired interface. If that machine is running the Cedar RPC package and has exported an interface whose InterfaceName has the same type and instance, and whose version is compatible with the importer's interfaceName.version, then the import succeeds and the importer's stub is now bound to the exporter's stub.
If interfaceName.instance is NIL, then the remote binder will attempt to find any exported interface whose type and version match those given in interfaceName. If the type is a Grapevine R-Name, then it should specify a Grapevine group. The binder enumerates the members of that group, obtains the network addresses that are the connect-sites of the members, and tries to bind to them in turn until one succeeds. The order of these binding attempts is undefined, but is closely related to hop-count and responsiveness. The algorithm usually chooses the nearest instance of the type that is operational. If the type is a Pup Name Lookup server name string, then this algorithm is performed with the network addresses that are the value of that name. Importing with interfaceName.instance = NIL is not yet implemented.
If an attempt to import a remote interface fails, the
ERROR
ImportFailed is raised. No monitors are locked when this signal is raised, so further calls into the RPC runtime may be made from catch-phrases. The parameter of the signal is one of the following:
communications Importing failed because of inability to contact one of: Grapevine, a Pup Name Lookup server, the exporting host.
badType The interfaceName.type was illegal (interfaceName.instance is NIL and type is not a Grapevine R-Name of a group, and not a Name Lookup server name).
badInstance The interfaceName.instance was illegal (not a Grapevine R-Name, not an individual, not a Name Lookup server name, not a net address constant).
badVersion The interfaceName.version was illegal (upper bound less than lower bound).
wrongVersion The exporter and importer version ranges don't overlap.
unbound No currently running host has exported the appropriate type and instance.
stubProtocol The exporter and importer are using stub modules produced by incompatible verisons of Lupine.
Importing One Remote Instance (translation with ImportSingle[TRUE])
For interface
Target, calling
TargetRpcControl.ImportInterface causes
Target (as specified by
interfaceName) to be remotely imported for immediate use. In the C/Mesa sense,
Target was bound when
TargetRpcClientImpl, which exports
Target locally, was bound. But
remote binding is not complete until
ImportInterface is called. As a consequence, any attempts to use operations in
Target before
ImportInterface completes, or after
UnimportInterface completes, will fail unrecoverably. Calling
UnimportInterface has no effect on calls in progress. These two routines should normally be called in the strict sequence
ImportInterface,
UnimportInterface,
ImportInterface, .... However, calling
ImportInterface without an intervening call to
UnimportInterface will just remotely import the interface as if
UnimportInterface had been called. Note that
ImportInterface[
someInterface] will raise the exception
RPC.ImportFailed[
unbound] unless some host has previously called
ExportInterface[
someInterface].
TargetRpcControl.ImportInterface: PROCEDURE [
interfaceName: InterfaceName ← defaultInterfaceName,
parameterStorage: Zones ← standardZones ];
TargetRpcControl.UnimportInterface: PROCEDURE;
RPC.ImportFailed: ERROR [why: {...}];
Importing Multiple Remote Instances (translation with the default ImportSingle[FALSE])
For interface
Target, calling
TargetRpcControl.ImportNewInterface causes a new instance of
Target (as specified by
interfaceName) to be created and remotely imported for immediate use. Typically, each call of
ImportNewInterface will specify a different value for
interfaceName.instance, thus binding to a different, distributed instance of the
Target interface. The operations in this new instance must be accessed through the
interfaceRecord that
ImportNewInterface returns,
not through
Target itself, as in the static importing case discussed above. (The
Target interface is useless to RPC programmers using multiple instances and should not even be referenced.) For example, if
Target contains procedure
WritePages, a call to
WritePages must be written
interfaceRecord.WritePages[...], not
Target.WritePages[...]. Any attempts to use operations in
interfaceRecord before
ImportNewInterface completes, or after
UnimportNewInterface completes, will fail unrecoverably. Calling
UnimportNewInterface has no effect on calls in progress.
UnimportNewInterface is available only for the Mesa target environment. In Cedar, discarded remote interfaces are garbage collected. Note that
ImportNewInterface[
someInterface] will fail unless some host has previously called
ExportInterface[
someInterface].
TargetRpcControl.InterfaceRecord: TYPE = REF RECORD [...];
TargetRpcControl.ImportNewInterface: PROCEDURE [
interfaceName: InterfaceName ← defaultInterfaceName,
parameterStorage: Zones ← standardZones ]
RETURNS [interfaceRecord: InterfaceRecord];
TargetRpcControl.UnimportNewInterface: PROCEDURE [
interfaceRecord: InterfaceRecord ]
RETURNS [nilInterface: InterfaceRecord ← NIL]; -- Only for Mesa, not Cedar.
RPC.ImportFailed: ERROR [why: {...}];
Importing Local Instances
When a distributed program deals with multiple remote instances, it is often true that one (or more) of the instances will actually reside on the local host. In this case, performing remote calls to the local instance can have an undesirable performance impact. RPC programmers who want to optimize "remote" calls to local dynamic instances can do so in the following fashion:
When importing locally, first get an TargetRpcControl.InterfaceRecord by calling TargetRpcControl.NewInterfaceRecord. (You may be able to copy the code from TargetRpcBinderImpl directly into your program: replace the call with ``NEW[TargetRpcControl.InterfaceRecordObject]'').
Fill in the InterfaceRecord's fields with the procedures to be called. Note that the procedures differ from the normal exported procedures in that their argument list is preceeded by an "interface: TargetRpcControl.InterfaceRecord" argument.
Parameter Storage Zones
To unmarshal certain parameters, Lupine's stubs must allocate private storage. This storage is taken from zones that RPC programmers can specify at binding time. For simple interfaces a set of standard zones is adequate, and thus the binding operations have default
standardZones (below). For production interfaces that use address-containing (AC) parameters frequently, however, establishing special-purpose zones can increase performance significantly. If a remote interface uses a large number of small AC objects (except for ropes, which get special treatment from the Rope package), or it uses any large AC objects, consult a Lupine wizard.
RPC.Zones: RECORD [
gc: ZONE ← NIL,
heap: UNCOUNTED ZONE ← NIL,
mds: MDSZone ← NIL ];
RPC.standardZones: Zones = [];
Module Dependencies
The compile-time relationships of
Target modules, Lupine's stub modules, and the RPCRuntime packages are shown below. If module
B is to the right of module
A, then
B depends on
A.
RPC or MesaRPC
RPCLupine
LupineRuntime
RPCRuntime package
Target
Target's clients
TargetImpl
TargetRpcControl
Target's remote binding modules
LupineRuntime (repeated from above)
TargetRpcClientImpl
TargetRpcBinderImpl
TargetRpcServerImpl
DF Files for Remote Interfaces
As a general rule, Lupine.df exports all the Lupine, LupineRuntime, RPCRuntime, and Cedar runtime components needed by remote programs. Thus DF files describing remote applications should say [Exports] Imports Lupine.df to get all required components, although tailoring with a USING list may be worthwhile.
Configurations Using Remote Interfaces
Remote calls typically take place between independent hosts, and thus, for a given Target interface, Target's client and implementation modules, along with the necessary stubs, must be split between the cooperating hosts. A simple division of the Target service into remote TargetClient and TargetServer C/Mesa configurations is illustrated below. Constructing corresponding Cedar models would be similar.
TargetClient: CONFIGURATION
IMPORTS
-- For Lupine-generated modules:
Atom, ConvertUnsafe, Heap, Inline, LupineRuntime, Rope, RopeInline,
RPCLupine, SafeStorage, UnsafeStorage,
-- For ClientRpcImportImpl and TargetClientImpl:
. . .
CONTROL ClientRpcImportImpl, TargetClientImpl
= BEGIN
TargetRpcClientImpl; -- EXPORTS Target, TargetRpcControl.
TargetRpcBinderImpl; -- Optional, for multiple instances via ImportNewInterface.
ClientRpcImportImpl; -- IMPORTS TargetRpcControl, calls Import(New)Interface.
TargetClientImpl; -- IMPORTS Target (one instance) or
-- IMPORTS TargetRpcControl (multiple instances) and uses interfaceRecord.
END;
TargetServer: CONFIGURATION
IMPORTS
-- For Lupine-generated modules:
Atom, ConvertUnsafe, Heap, Inline, LupineRuntime, Rope, RopeInline,
RPCLupine, SafeStorage, UnsafeStorage,
-- For ServerRpcExportImpl and TargetImpl:
. . .
CONTROL ServerRpcExportImpl
= BEGIN
TargetRpcServerImpl; -- IMPORTS Target, EXPORTS TargetRpcControl.
ServerRpcExportImpl; -- IMPORTS TargetRpcControl, calls ExportInterface.
TargetImpl; -- EXPORTS Target.
END;
The purpose of the user-written ClientRpcImportImpl and ServerRpcExportImpl modules is to import and export the remote Target interface by making calls on TargetRpcControl's Import(New)Interface and ExportInterface routines. These trivial binding operations could just as well be placed in TargetClientImpl and TargetImpl, or combined with the binding operations of other remote interfaces into one master binding module. Note that these configurations do not include the RPC runtime package, RPCRuntime.bcd. This package should normally be loaded separately, because it is essential that only one copy of this package is loaded into any machine. Check with current Cedar release documentation to see whether RPCRuntime.bcd is included in the Cedar boot file.
It is sometimes advantageous to have both client and server configurations loaded into one host, as for single-machine debugging. Such configurations must be specified cautiously, however, so that remote imports and exports are not misconnected locally, short circuiting all remote calls. The easiest way to do this is to use nested configurations, as in the following example.
TargetTestCombined: CONFIGURATION
IMPORTS
-- For Lupine:
Atom, ConvertUnsafe, Heap, Inline, LupineRuntime, Rope, RopeInline,
RPCLupine, SafeStorage, UnsafeStorage,
-- For TargetImpl, ClientImpl:
. . .
CONTROL TargetServer, TargetClient
= BEGIN
TargetClient;
TargetServer;
END;
These TargetClient and TargetServer examples are simple; more elaborate configurations will be commonly required. See appendix A of the Diplomat document [Diplomat] for further, more complicated, examples, or consult a Lupine wizard.
5 Crash Detection and Recovery
A general discussion of crashes and crash recovery in the context of RPC is outside the scope of this guide. The main reason is insufficient experience with distributed systems: there is no widespread agreement on an RPC-level crash recovery methodology, or, indeed, if such a methodology is even needed or possible at this level. In currently operational distributed systems, those with the best crash behavior appear to use highly application-specific techniques. See Nelson's thesis [RPC] for more discussion (sections 4.1.1, 4.2.3, 5.1, 7.2).
RPC.CallFailed: SIGNAL [why: RPC.CallFailure];
Lupine's RPCRuntime uses the
RPC.CallFailed exception to report crashes and other remote call execution problems. This exception can be reported by
any invocation of a remote procedure, or on any signal of a remote signal or error. RPC programmers interested in continuous service must therefore be prepared to catch
CallFailed on each remote operation. Recovery actions usually consist of restoring some invariants before trying to rebind to another instance of the remote interface. No monitors are locked when
RPC.CallFailed is raised, so further calls into the RPC runtime may be made from catch-phrases. The parameter of the signal is one of the following:
timeout The other participant in the call is not responding to attempts to communicate. The remote call may or may not have taken place. Note that this is not raised merely because the remote call takes a long time to execute. The RPC runtime places no limit on the execution time of a remote call, provided the server still responds to probes. A client can terminate a call that is taking too long by using the Abort mechanism of the Mesa process machinery. RPC.CallFailed may be resumed iff its parameter is timeout.
unbound The server has un3exported this interface, or has been restarted, since the interface was imported. The call did not take place.
busy After repeated attempts, the server is too busy to accept the call. The call did not take place.
protocol The caller and server runtimes disagree about the communication protocol; consult a Lupine wizard.
stubProtocol The caller and server stubs disagree about the communication protocol; consult a Lupine wizard.
Note for experts: In servers, the RPCRuntime is prepared to catch CallFailed itself and abort the incoming call that caused it. Thus if CallFailed is signalled out of a remote exception or a callback (procedure parameter), and the exception or procedure was invoked by a remote call executing in a server, the RPC programmer's server code does not have to catch CallFailure. The RPCRuntime will automatically clean up and ignore the original call, if desired.
6 Parameter Passing
Semantics
For parameters that do not contain addresses, Lupine's parameter passing semantics are exactly those of Cedar and Mesa: call-by-value. For address-containing (AC) parameters, however, Lupine has four passing semantics: for arguments,
VAR,
VALUE,
RESULT, and
HANDLE; for results, just
VALUE. Each of these is a copy semantics; Lupine does not support reference semantics (call-by-reference) between disjoint address spaces. Here is a brief description of each; the next secion has examples:
VALUE. Value semantics cause the actual argument to be evaluated and assigned (copied) to the formal argument at the time of invocation. For AC types used as RPC parameters, the referent and not the address is used. For example, if p is a REFINT, then passing p by VALUE uses the integer p^, not the pointer p (HANDLE passing would use just p). NIL semantics are preservede.g., if p is NIL, then NIL is passed. This dereferencing behavior also applies to POINTERs (same as REFs), DESCRIPTORs (the underlying array is copied), LISTs (the individual elements are copied to a new list), and strings (ropes, atoms, and strings).
RESULT. Result semantics cause the value of the formal AC argument to be assigned to the actual argument at the completion of the call. Arguments are dereferenced as described above. The initial value of the actual argument is unspecified.
VAR. VAR means VALUE-and-RESULT: the actual argument is evaluated and assigned to the formal argument at the start of the call, and the final value of the formal argument is assigned back to the actual argument when the call finishes. In the absence of aliasing, VAR semantics are identical to reference semantics.
HANDLE. An AC argument passed as a handle is treated as a numeric quantity, not an address. There is no interpretation or dereferencing of the address itself. Lupine does not consider a handle type to be an AC type. The compiler, however, does, and this can cause serious safety breeches for REF-containing types used as RPC handles. Basically, Cedar RPC programmers cannot use REF-containing handles; see a Lupine wizard.
Syntax
The specification of parameter passing semantics is somewhat awkward because Mesa and Cedar support only call-by-value semantics. There are two methods:
Explicit. To explicitly declare a passing method, RPC programmers include
VAR,
VALUE,
RESULT, or
HANDLE as a prefix to a parameter's type name. For example,
Buffer: TYPE = DESCRIPTOR FOR ARRAY OF DiskPage;
VALUEBuffer: TYPE = Buffer;
RESULTBuffer: TYPE = Buffer;
VARBuffer: TYPE = Buffer;
ReadPages: PROCEDURE [..., inputPages: RESULTBuffer];
WritePages: PROCEDURE [..., outputPages: VALUEBuffer];
XORPages: PROCEDURE [previousPages: VARBuffer, nextPage: VALUEBuffer];
Implicit. To implicitly establish a passing method RPC programmers use the DefaultParameterPassing option at translation time. Such blanket declarations are not recommended, and you should see a Lupine wizard before using DefaultParameterPassing.
Applicability
The following table indicates which passing methods are applicable to each type (a blank entry means
No). Value semantics is the default in all cases. Further restrictions are listed below.
The types below can be passed by VAR VALUE RESULT HANDLE
INT, BOOL, CHAR, REAL, ... Yes
Subrange, Enumeration Yes
RECORD, variant RECORD Yes
ARRAY, SEQUENCE Yes
OPAQUE (with assignment) Yes
ANY, OPAQUE (without assignment) No
CONDITION, MONITORLOCK No
ZONE No Yes
PROCEDURE, SIGNAL, ERROR Not yet Yes
REF, POINTER, DESCRIPTOR Yes1 Yes Yes1 Yes
RELATIVE POINTER2 Yes
LIST, ROPE, ATOM, ShortROPE, ShortATOM3 Yes Yes
STRING, ShortSTRING3 Yes Yes Yes Yes
TEXT, STRINGBODY Yes
1. These AC types can be called by VAR and RESULT, but their referents may not in turn contain any AC types. Restated, VAR and RESULT are only one level deep.
2. Lupine does not consider relative pointers to be AC, and always treats them as numeric values.
3. ShortROPE, ShortSTRING, and ShortATOM are string types that should be used by RPC programmers only when a string's length has a guaranteed limit. Grapevine's RNames are a primary example because they will frequently be used in remote calls to specify users, principals, servers, and so forth. The use of short strings is always optional; when used, Lupine can usually optimize stubs by guaranteeing that a call always fits in one packet. The short string length limit, defined by RPC.maxShortStringLength, is currently 64. The other properties of these types are identical to those of their longer brothers.
Guarantees
Lupine's typechecking is no stronger than the compiler's. In generating code, Lupine assumes that the value of a parameter is always a legal value for a parameter of that typethere is no special checking. If a client programmer passes a REF which has been loopholed to an invalid address, or passes an out-of-bounds enumerated or subrange value, the results are unspecified. For example, if Lupine must dereference the bogus REF during marshaling, either an address fault will occur or an equally bogus referent will be handed to an unsuspecting server. For an invalid enumerated or subrange value, a fault will occur only if the stub modules were compiled with bounds checking enabled.
The upshot is that RPC programmers must be just as suspicious of invalid parameters as they would be in the absence of RPC. Furthermore, programmers of servers must be especially careful because RPC client code need not be generated by Lupine, or even written in Mesa.
7 Interface and Parameter Restrictions
Lupine tries hard to make local and remote procedure semantics identical, and as a result there are very few restrictions. Here are the details.
Interface Remarks and Restrictions
PROGRAMs, PROCESSes, and PORTs. Lupine supports only remote procedures, signals, and errors. Remote programs, processes, and ports are not permitted.
Interface variables. Lupine does not support global variables in interfaces, including procedures and other items accessed through POINTER TO FRAME.
INLINE procedures. Lupine ignores inline procedures in remote interfaces, since inline procedure bodies appear in the interface itself and are compiled directly into clients.
Monitors. A procedure's ENTRY attribute is invisible in interfaces, and thus stub procedures never have the ENTRY attribute. This is exactly the desired behavior, for the actual implementation is the only module that may need to be a monitor.
Multiple module names. Lupine does not support multiple module names for remote interfaces (e.g., A, B, C: DEFINITIONS).
Default parameters and dynamic interface instances. Dynamic interface instantiation via ImportNewInterface uses an explicit interface record declared in TargetRpcControl. Because the parameter declarations in this record do not duplicate any explicit defaults declared with the formal parameters in Target (a symbol table shortcoming), explicit parameter defaulting cannot occur. (Implicit default values from the parameter's type declaration, however, continue to work properly.) This is in contrast to remote calls through a single Target instance, where the defaults, if any, are available.
Identifier conflicts. While unlikely, it's possible that an internal stub-module identifier used by Lupine (e.g., temporary variable) will conflict with an external identifier used by a remote interface (e.g., procedure or parameter name). The compiler will discover and complain about such conflicts, which should be reported to Lupine's implementors straightaway. The short-term repair, however, is renaming the conflicting identifiers in the remote interface.
Parameter Marshaling Restrictions
Lists, trees, and graphs. Lupine does not automatically marshal these recursive data structures when they are constructed from explicit REFs or POINTERs. Only noncyclic LIST structures are supported.
Callback parameters. Procedure, signal, and error parameters are on the verge of working. If you need them, tell a Lupine wizard, who might finish the job just for you.
Computed sequences and variant records. Lupine cannot marshal any computed sequences, nor can Lupine marshal computed or overlaid variant records with AC components.
Sequences inside variant records. Lupine cannot marshal a sequence contained directly within a variant record. However, Lupine happily accepts references to sequences Thus the first line below is illegal, but the second is acceptable:
RECORD [SELECT color: * FROM red => [SEQUENCE . . . OF Intensity]];
RECORD [SELECT color: * FROM red => [REF RECORD[SEQUENCE . . . OF Intensity]]];
Anonymous record components and single-component records. Lupine cannot cope with anonymous record components. (Anonymous parameters, however, are fine, except for signal and error arguments.) Therefore unnamed, single- or multi-component, address-containing (AC) records will cause a translation error. To recover, just give the components names.
Default values for variant records. Sometimes, to marshal a variant record, Lupine must NEW storage for it. Safety requires that the new record have an initial tag value, but Lupine is not in a position to choose the correct one. An RPC programmer must therefore specify a default value for the record in the record's declaration, even if Lupine's stubs are the only place it is used. You needn't worry about this, however, until the compiler first complains about it.
Record type declarations. To marshal certain records, Lupine must NEW each of the embedded components. If one of these components is an explicit record specification (e.g., RECORD [red, green, ...]), and not a record type identifier (e.g., ColorRecord), the compiler will report a type clash. If this happens, RPC programmers must redeclare all such records to be record types.
Packed arrays of AC types. Lupine will properly translate these ill-used types (e.g., PACKED ARRAY OF STRING), but the compiler will not accept the result because of an @ applied to the packed elements.
8 References
[Cedar] Cedar Language Committee.
Cedar MesaVersion 6T5.
File [Indigo]<CedarLang>Doc>Cedar6T5.press, 16 January 1981.
Cedar documentation is evolving, so make current inquiries; this is a founding document.
[CourierFS] Remote procedure calls.
Pilot Programmer's Manual [Pilot], chapter 6.3.
The Courier Functional Specification is now an integral part of the PPM.
[CourierPS] Courier: The Remote Procedure Call Protocol.
Xerox System Integration Standard XSIS 038112, December 1981.
[DES] National Bureau of Standards.
Data Encryption Standard.
FIPS Publication 46, January, 1977.
[Diplomat] Bruce Nelson.
Diplomat, Attache to Envoy.
File [Idun]<Nelson>Diplomat>Diplomat.press, version 5.0, 8 August 1980.
Envoy is an ancestor of Courier, so this document is somewhat dated.
[Grapevine] Andrew Birrell, Roy Levin, Roger Needham, and Michael Schroeder.
Grapevine, an Exercise in Distributed Computing.
Communications of the ACM 25(4): 26074, April 1982.
[Mesa] James G. Mitchell, William Maybury, Richard Sweet.
Mesa Language Manual.
Xerox PARC technical report CSL-79-3, version 5.0, April 1979.
[Needham-Schroeder] Roger Needham and Michael Schroeder.
Using encryption for authentication in large networks of computers.
Communications of the ACM 21(12):99399, December, 1978.
[Pilot] Pilot Programmer's Manual.
Xerox Office Products Division, version 8.0, March 1982.
[RPC] Bruce Nelson.
Remote Procedure Call.
Xerox PARC technical report CSL-81-9, May 1981.
Appendix A: Public RPC Interfaces
The following are the public interfaces to Lupine's RPC runtime, RPC.mesa and MesaRPC.mesa. Small details are likely to have changed since this document was last edited, so remember that Lupine.df always refers to the latest version.
RPC.mesa
RPC is the Cedar interface to the RPC runtime.
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by:
Birrell, September 7, 1983 3:20 pm
BZM 29-Oct-81 11:46:31
Taft 6-Oct-81 18:35:28
Bob Hagmann February 8, 1985 3:30:34 pm PST
DIRECTORY
GVBasics USING[ maxRNameLength, Password ],
Rope USING [ROPE];
RPC: CEDAR DEFINITIONS =
BEGIN
Short string/rope/atom types. Used only by Lupine clients.
maxShortStringLength: CARDINAL = 64;
ShortSTRING: TYPE = STRING;
ShortROPE: TYPE = Rope.ROPE;
ShortATOM: TYPE = ATOM;
Types for Import and Export calls.
InterfaceName:
TYPE =
RECORD [
type: ShortROPE ← NIL, -- e.g., "AlpineAccess.Alpine"
instance: ShortROPE ← NIL, -- e.g., "MontBlanc.Alpine"
version: VersionRange ← matchAllVersions ];
defaultInterfaceName: InterfaceName = [];
VersionRange:
TYPE =
MACHINE
DEPENDENT
RECORD[first, last:
CARDINAL];
client-defined, closed interval
matchAllVersions: VersionRange = [1,0];
importer: use any version; exporter: no versioning implied
Parameter storage zones. Used only by Lupine clients.
Zones:
TYPE =
RECORD [
gc: ZONE ← NIL,
heap: UNCOUNTED ZONE ← NIL,
mds: MDSZone ← NIL ];
standardZones: Zones = [];
Encryption and Authentication facilities.
maxPrincipalLength:
CARDINAL =
MIN[maxShortStringLength, GVBasics.maxRNameLength];
Limit on length of ropes used for Principal names
Principal:
TYPE = ShortROPE;
Name of authentication principal
EncryptionKey:
TYPE = GVBasics.Password;
DES key
MakeKey: PROCEDURE [text: Rope.ROPE] RETURNS[EncryptionKey];
Conversation: TYPE = REF ConversationObject;
ConversationObject: PRIVATE TYPE;
SecurityLevel:
TYPE =
MACHINE
DEPENDENT {
none(0), -- unauthenticated, insecure; used for "unencrypted"
authOnly(1), -- authenticated, but unencrypted calls
ECB(2), -- authenticated, encrypt with ECB mode of DES
CBC(3), -- authenticated, encrypt with CBC mode of DES
CBCCheck(4) -- authenticated, encrypt with CBC mode of DES + checksum
};
ConversationLevel: TYPE = SecurityLevel[authOnly..CBCCheck];
unencrypted: Conversation =
NIL;
Dummy conversation; may be passed to RPC runtime.
GetConversationID[unencrypted] = ERROR;
GetCaller[unencrypted] = NIL;
GetLevel[unencrypted] = none;
GenerateConversation:
PROC
RETURNS[Conversation];
Returns a handle for a previously unused Conversation. This conversation is only for local use, it must not be passed to the RPC runtime.
GetConversationID[GenerateConversation[]] = unique ID;
GetCaller[GenerateConversation[]] = NIL;
GetLevel[GenerateConversation[]] = "none";
StartConversation:
PROCEDURE[
caller: Principal,
key: EncryptionKey,
callee: Principal,
level: ConversationLevel ]
RETURNS[conversation: Conversation];
Obtains authenticator for conversation, registers it with runtime, and allocates ConversationID
EndConversation:
PROCEDURE [conversation: Conversation];
Terminates use of this conversation
GetCaller:
PROCEDURE [conversation: Conversation]
RETURNS [caller: Principal];
Returns the caller name for a current call. Result is NIL if conversation's security level is "none" (including conversation = "unencrypted").
GetLevel: PROCEDURE [conversation: Conversation] RETURNS [level: SecurityLevel];
ConversationID:
TYPE[3];
UID allocated by initiator host
GetConversationID:
PROC[conversation: Conversation]
RETURNS[id: ConversationID];
Returns permanently unique ID of this conversation
Public signals:
AuthenticateFailure:
TYPE = {
communications, -- couldn't contact authentication server(s) --
badCaller, -- invalid caller name --
badKey, -- incorrect caller password --
badCallee -- invalid callee name --
};
ExportFailure:
TYPE = {
communications, -- couldn't access binding database --
badType, -- unacceptable interface type name --
badInstance, -- unacceptable interface instance name --
badVersion, -- statically silly version range --
tooMany, -- too many exports for local tables --
badCredentials -- not allowed to change the database --
};
ImportFailure:
TYPE = {
communications, -- couldn't access binding database --
badType, -- unacceptable interface type name --
badInstance, -- unacceptable interface instance name --
badVersion, -- statically silly version range --
wrongVersion, -- exported version not in req'd range --
unbound, -- this instance not exported --
stubProtocol -- exporter protocol incompatible with importer --
};
CallFailure:
TYPE = {
timeout, -- no acknowledgement within reasonable time --
unbound, -- server no longer exports the interface --
busy, -- server says it's too busy --
runtimeProtocol, -- user/server runtimes don't understand each other --
stubProtocol -- user/server stubs don't understand each other --
};
AuthenticateFailed:
ERROR[why: AuthenticateFailure];
Raised by StartConversation
ExportFailed:
ERROR[why: ExportFailure];
Raised by ExportInterface
ImportFailed:
ERROR[why: ImportFailure];
Raised by ImportInterface
CallFailed:
SIGNAL[why: CallFailure];
Raised by any remote call; only why=timeout is resumable
END.
MesaRPC.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
RPC: Pilot compatible interface to the RPC runtime.
Last Edited by:
Andrew Birrell, September 13, 1983 1:33 pm
Bob Hagmann February 11, 1985 9:16:41 am PST
DIRECTORY
GVBasics USING[ maxRNameLength, Password ];
MesaRPC: CEDAR DEFINITIONS =
BEGIN
Short string types. Used only by Lupine clients.
maxShortStringLength: CARDINAL = 64;
ShortSTRING: TYPE = STRING;
Types for Import and Export calls.
InterfaceName:
TYPE =
RECORD [
type: LONG STRING ← NIL, -- e.g., "AlpineAccess.Alpine"
instance: LONG STRING ← NIL, -- e.g., "MontBlanc.Alpine"
version: VersionRange ← matchAllVersions ];
defaultInterfaceName: InterfaceName = [];
VersionRange:
TYPE =
MACHINE
DEPENDENT
RECORD[first, last:
CARDINAL];
client-defined, closed interval
matchAllVersions: VersionRange = [1,0];
importer: use any version; exporter: no versioning implied
Parameter storage zones. Used only by Lupine clients.
Zones:
TYPE =
RECORD [
gc: ZONE ← NIL,
heap: UNCOUNTED ZONE ← NIL,
mds: MDSZone ← NIL ];
standardZones: Zones = [];
Encryption and Authentication facilities.
maxPrincipalLength:
CARDINAL =
MIN[maxShortStringLength, GVBasics.maxRNameLength];
Limit on length of ropes used for Principal names
Principal:
TYPE =
LONG
STRING;
Name of authentication principal
EncryptionKey:
TYPE = GVBasics.Password;
DES key
MakeKey: PROCEDURE [text: LONG STRING] RETURNS[EncryptionKey];
Conversation: TYPE = LONG POINTER TO ConversationObject;
ConversationObject: PRIVATE TYPE;
SecurityLevel:
TYPE =
MACHINE
DEPENDENT {
none(0), -- unauthenticated, insecure; used for "unencrypted"
authOnly(1), -- authenticated, but unencrypted calls
ECB(2), -- authenticated, encrypt with ECB mode of DES
CBC(3), -- authenticated, encrypt with CBC mode of DES
CBCCheck(4) -- authenticated, encrypt with CBC mode of DES + checksum
};
ConversationLevel: TYPE = SecurityLevel[authOnly..CBCCheck];
unencrypted: Conversation =
NIL;
Dummy conversation; may be passed to RPC runtime.
GetConversationID[unencrypted] = ERROR;
GetCaller[unencrypted] = NIL;
GetLevel[unencrypted] = none;
GenerateConversation:
PROC
RETURNS[Conversation];
Returns a handle for a previously unused Conversation. This conversation is only for local use, it must not be passed to the RPC runtime.
GetConversationID[GenerateConversation[]] = unique ID;
GetCaller[GenerateConversation[]] = NIL;
GetLevel[GenerateConversation[]] = "none";
StartConversation:
PROCEDURE[
caller: Principal,
key: EncryptionKey,
callee: Principal,
level: ConversationLevel ]
RETURNS[conversation: Conversation];
Obtains authenticator for conversation, registers it with runtime, and allocates ConversationID
EndConversation:
PROCEDURE [conversation: Conversation];
Terminates use of this conversation
GetCaller:
PROCEDURE [conversation: Conversation]
RETURNS [caller: Principal];
Returns the caller name for a current call. Result is NIL if conversation's security level is "none" (including conversation = "unencrypted").
GetLevel: PROCEDURE [conversation: Conversation] RETURNS [level: SecurityLevel];
ConversationID:
TYPE[3];
UID allocated by initiator host
GetConversationID:
PROC[conversation: Conversation]
RETURNS[id: ConversationID];
Returns permanently unique ID of this conversation
Public signals:
AuthenticateFailure:
TYPE = {
communications, -- couldn't contact authentication server(s) --
badCaller, -- invalid caller name --
badKey, -- incorrect caller password --
badCallee -- invalid callee name --
};
ExportFailure:
TYPE = {
communications, -- couldn't access binding database --
badType, -- unacceptable interface type name --
badInstance, -- unacceptable interface instance name --
badVersion, -- statically silly version range --
tooMany, -- too many exports for local tables --
badCredentials -- not allowed to change the database --
};
ImportFailure:
TYPE = {
communications, -- couldn't access binding database --
badType, -- unacceptable interface type name --
badInstance, -- unacceptable interface instance name --
badVersion, -- statically silly version range --
wrongVersion, -- exported version not in req'd range --
unbound, -- this instance not exported --
stubProtocol -- exporter protocol incompatible with importer --
};
CallFailure:
TYPE = {
timeout, -- no acknowledgement within reasonable time --
unbound, -- server no longer exports the interface --
busy, -- server says it's too busy --
runtimeProtocol, -- user/server runtimes don't understand each other --
stubProtocol -- user/server stubs don't understand each other --
};
AuthenticateFailed:
ERROR[why: AuthenticateFailure];
Raised by StartConversation
ExportFailed:
ERROR[why: ExportFailure];
Raised by ExportInterface
ImportFailed:
ERROR[why: ImportFailure];
Raised by ImportInterface
CallFailed:
SIGNAL[why: CallFailure];
Raised by any remote call; only why=timeout is resumable
END.
Appendix B: Example Remote Interface and Stubs
There follows a very simple interface, Target.mesa, and its stub modules as an example. Once again, small details may have changed, but this is unimportant since RPC programmers need not be concerned with stub implementations. These modules are un-retouched output from the Lupine translator.
Target.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Lupine: example interface
Andrew Birrell September 14, 1983 9:06 am
Bob Hagmann February 11, 1985 9:18:37 am PST
DIRECTORY
Rope USING[ ROPE ];
Target: CEDAR DEFINITIONS =
BEGIN
Basic: PROC;
Simple: PROC[first: INT, second: REF INT] RETURNS[a: Rope.ROPE, b: ATOM];
Reason: TYPE = { x, y, z };
Exception: ERROR[why: Reason];
Consultation: SIGNAL;
END.
-- Copyright (C) 1985 by Xerox Corporation. All rights reserved.
-- Stub file was translated on March 7, 1985 11:44:56 am PST by Lupine of March 6, 1985 8:13:46 am PST
-- Source interface Target came from file Target.bcd, which was created on March 7, 1985 11:44:45 am PST with version stamp 234#313#31370206177 from source of February 11, 1985 9:18:37 am PST.
-- The RPC stub modules for Target are:
-- TargetRpcControl.mesa;
-- TargetRpcClientImpl.mesa;
-- TargetRpcBinderImpl.mesa;
-- TargetRpcServerImpl.mesa.
-- The parameters for this translation are:
-- Target language = Cedar
-- Default parameter passing = VALUE
-- Deallocate server heap arguments = TRUE
-- Inline RpcServerImpl dispatcher stubs = TRUE
-- Declare signals = TRUE
-- Warn about short POINTER ("MDS") allocations = TRUE
-- Maximum number of dynamic heap NEWs = 50, MDS NEWs = 50
-- Acceptable parameter protocols = VersionRange[1..1].
DIRECTORY
Rope,
Target,
RPC USING [defaultInterfaceName, EncryptionKey, InterfaceName, Principal,
standardZones, VersionRange, Zones];
TargetRpcControl: DEFINITIONS
SHARES Target
= BEGIN OPEN Target, RpcPublic: RPC;
-- Public RPC types and constants.
InterfaceName: TYPE = RpcPublic.InterfaceName;
VersionRange: TYPE = RpcPublic.VersionRange;
Principal: TYPE = RpcPublic.Principal;
EncryptionKey: TYPE = RpcPublic.EncryptionKey;
Zones: TYPE = RpcPublic.Zones;
defaultInterfaceName: InterfaceName = RpcPublic.defaultInterfaceName;
standardZones: Zones = RpcPublic.standardZones;
-- Standard remote binding routines.
ImportInterface: SAFE PROCEDURE [
interfaceName: InterfaceName ← defaultInterfaceName,
parameterStorage: Zones ← standardZones ];
ExportInterface: SAFE PROCEDURE [
interfaceName: InterfaceName ← defaultInterfaceName,
user: Principal,
password: EncryptionKey,
parameterStorage: Zones ← standardZones ];
UnexportInterface: SAFE PROCEDURE;
-- Dynamic instantiation and binding.
ImportNewInterface: SAFE PROCEDURE [
interfaceName: InterfaceName ← defaultInterfaceName,
parameterStorage: Zones ← standardZones ]
RETURNS [interfaceRecord: InterfaceRecord];
-- NewInterfaceRecord is necessary for Cedar clients who want to
-- manufacture a private interface instance, because
-- RpcBindingImpl has finalization on type InterfaceRecord.
NewInterfaceRecord: SAFE PROCEDURE
RETURNS [interfaceRecord: InterfaceRecord];
InterfaceRecord: TYPE = REF InterfaceRecordObject;
InterfaceRecordObject: TYPE = RECORD [
Basic: SAFE PROCEDURE,
Simple: SAFE PROCEDURE [first: INT, second: REF INT] RETURNS
[a: Rope.ROPE, b: ATOM],
Exception: ERROR [why: Reason],
Consultation: SAFE SIGNAL,
lupineDetails: PRIVATE REF LupineDetailsObject←NIL];
LupineDetailsObject: PRIVATE TYPE;
-- Definitions for the stubs.
LupineProtocolVersion: PUBLIC VersionRange = [first: 1, last: 1];
InterMdsCallsOnly: PUBLIC BOOLEAN = FALSE;
ProcedureIndex: PRIVATE TYPE = MACHINE DEPENDENT {
LupineUnusedIndex (0), LupineLastIndex (3),
Basic (4), Simple (5)};
SignalIndex: PRIVATE TYPE = MACHINE DEPENDENT {
LupineUnusedIndex (0), LupineLastIndex (3),
Exception (4), Consultation (5)};
END. -- TargetRpcControl.
-- Copyright (C) 1985 by Xerox Corporation. All rights reserved.
-- Stub file was translated on March 7, 1985 11:44:57 am PST by Lupine of March 6, 1985 8:13:46 am PST
-- Source interface Target came from file Target.bcd, which was created on March 7, 1985 11:44:45 am PST with version stamp 234#313#31370206177 from source of February 11, 1985 9:18:37 am PST.
-- The RPC stub modules for Target are:
-- TargetRpcControl.mesa;
-- TargetRpcClientImpl.mesa;
-- TargetRpcBinderImpl.mesa;
-- TargetRpcServerImpl.mesa.
-- The parameters for this translation are:
-- Target language = Cedar
-- Default parameter passing = VALUE
-- Deallocate server heap arguments = TRUE
-- Inline RpcServerImpl dispatcher stubs = TRUE
-- Declare signals = TRUE
-- Warn about short POINTER ("MDS") allocations = TRUE
-- Maximum number of dynamic heap NEWs = 50, MDS NEWs = 50
-- Acceptable parameter protocols = VersionRange[1..1].
DIRECTORY
Rope,
Target,
TargetRpcControl USING [InterfaceRecord, InterMdsCallsOnly, LupineProtocolVersion,
ProcedureIndex, SignalIndex],
RPC --USING SOME OF [InterfaceName, standardZones, Zones]--,
RPCLupine --USING SOME OF [Alloc, Call, DataLength, DeAlloc, Dispatcher,
-- GetPkt, GetStubPkt, ImportHandle, ImportInterface, maxDataLength,
-- maxPrincipalLength, maxShortStringLength, pktOverhead, ReceiveExtraPkt,
-- SendPrelimPkt, StartCall, StartSignal, StubPkt, UnimportInterface]--,
LupineRuntime --USING SOME OF [BindingError, CheckPktLength, CopyFromPkt,
-- CopyFromMultiplePkts, CopyToPkt, CopyToMultiplePkts, defaultZones,
-- DispatchingError, FinishThisPkt, ListHeader, MarshalingError,
-- MarshalingExprError, MarshalAtom, MarshalRope, NilHeader, ProtocolError,
-- RopeHeader, RpcPktDoubleWord, RuntimeError, SequenceHeader, SHORT,
-- StartNextPkt, StringHeader, StubPktDoubleWord, TranslationError,
-- UnmarshalingError, UnmarshalingExprError, UnmarshalAtom, UnmarshalRope,
-- WordsForChars]--,
Atom --USING SOME OF [GetPName, MakeAtom]--,
PrincOpsUtils --USING SOME OF [Enter, Exit]--,
VM --USING SOME OF [AddressForPageNumber, PageCount, PageNumber,
-- PageNumberForAddress, PagesForWords]--;
TargetRpcClientImpl: MONITOR
IMPORTS RpcPrivate: RPCLupine, Lupine: LupineRuntime, Atom, PrincOpsUtils,
Rope
EXPORTS Target, TargetRpcControl
SHARES Target, TargetRpcControl, Rope
= BEGIN OPEN Target, RpcControl: TargetRpcControl, RpcPublic: RPC;
-- Standard remote binding routines.
bound: BOOLEAN ← FALSE;
myInterface: RpcPrivate.ImportHandle;
paramZones: RpcPublic.Zones ← RpcPublic.standardZones;
ImportInterface: PUBLIC ENTRY SAFE PROCEDURE [
interfaceName: RpcPublic.InterfaceName,
parameterStorage: RpcPublic.Zones ] =
TRUSTED BEGIN ENABLE UNWIND => NULL;
IsNull: PROCEDURE [string: Rope.ROPE] RETURNS [BOOLEAN] =
INLINE {RETURN[string.Length[] = 0]};
IF bound THEN Lupine.BindingError;
myInterface ← RpcPrivate.ImportInterface [
interface: [
type: IF ~IsNull[interfaceName.type]
THEN interfaceName.type ELSE "Target~234#313#31370206177",
instance: interfaceName.instance,
version: interfaceName.version ],
localOnly: RpcControl.InterMdsCallsOnly,
stubProtocol: RpcControl.LupineProtocolVersion ];
paramZones ← [
gc: IF parameterStorage.gc # NIL
THEN parameterStorage.gc ELSE Lupine.defaultZones.gc,
heap: IF parameterStorage.heap # NIL
THEN parameterStorage.heap ELSE Lupine.defaultZones.heap,
mds: IF parameterStorage.mds # NIL
THEN parameterStorage.mds ELSE Lupine.defaultZones.mds ];
bound ← TRUE;
END;
UnimportInterface: PUBLIC ENTRY SAFE PROCEDURE =
TRUSTED BEGIN ENABLE UNWIND => NULL;
IF ~bound THEN Lupine.BindingError;
myInterface ← RpcPrivate.UnimportInterface[myInterface];
paramZones ← RpcPublic.standardZones;
bound ← FALSE;
END;
-- Remote public procedure stubs.
Basic: PUBLIC SAFE PROCEDURE =
TRUSTED BEGIN
ArgumentOverlay: TYPE = MACHINE DEPENDENT RECORD [
transferIndex (0): RpcControl.ProcedureIndex ← Basic];
pktBuffer: ARRAY [1..RpcPrivate.pktOverhead+1] OF WORD;
pkt: RpcPrivate.StubPkt = RpcPrivate.GetStubPkt[space: @pktBuffer];
argPkt: LONG POINTER TO ArgumentOverlay = @pkt.data[0];
pktLength: RpcPrivate.DataLength ← 1;
lastPkt: BOOLEAN;
RpcPrivate.StartCall[callPkt: pkt, interface: myInterface];
argPkt.transferIndex ← Basic;
[returnLength: , lastPkt: lastPkt] ←
RpcPrivate.Call[ pkt: pkt, callLength: pktLength,
maxReturnLength: 0, signalHandler: ClientDispatcher];
Lupine.CheckPktLength[pkt: pkt, pktLength: 0];
RETURN[];
END; -- Basic.
Simple: PUBLIC SAFE PROCEDURE [first: INT, second: REF INT] RETURNS
[a: Rope.ROPE, b: ATOM] =
TRUSTED BEGIN
ArgumentOverlay: TYPE = MACHINE DEPENDENT RECORD [
transferIndex (0): RpcControl.ProcedureIndex ← Simple, first (1):
INT];
pkt: RpcPrivate.RPCPkt = RpcPrivate.GetPkt[space: RpcPrivate.AllocInline[RpcPrivate.pktOverhead+254]];
argPkt: LONG POINTER TO ArgumentOverlay = @pkt.data[0];
pktLength: RpcPrivate.DataLength ← 3;
lastPkt: BOOLEAN;
BEGIN ENABLE UNWIND => RpcPrivate.DeAlloc[LOOPHOLE[pkt], RpcPrivate.pktOverhead+254];
RpcPrivate.StartCall[callPkt: pkt, interface: myInterface];
argPkt^ ← [first: first];
BEGIN -- Marshal second: REF INT to pkt.data[pktLength].
pkt.data[pktLength] ← second=NIL; pktLength ← pktLength+1;
IF second # NIL THEN
BEGIN
Lupine.RpcPktDoubleWord[pkt, pktLength]^ ← second^;
pktLength ← pktLength + 2;
END;
END; -- Marshal second.
[returnLength: , lastPkt: lastPkt] ←
RpcPrivate.Call[ pkt: pkt, callLength: pktLength,
maxReturnLength: 254, signalHandler: ClientDispatcher];
pktLength ← 0;
BEGIN -- Unmarshal a: Rope.ROPE from pkt.data[pktLength].
ropeIsNIL: Lupine.NilHeader;
IF pktLength+2 > RpcPrivate.maxDataLength
THEN pktLength ← Lupine.FinishThisPkt[pkt: pkt, pktLength:
pktLength];
ropeIsNIL ← pkt.data[pktLength]; pktLength ← pktLength+1;
IF ropeIsNIL
THEN a ← NIL
ELSE BEGIN
ropeLength: Lupine.RopeHeader;
textRope: Rope.Text;
ropeLength ← pkt.data[pktLength]; pktLength ← pktLength+1;
IF ropeLength > LAST[NAT]
THEN Lupine.UnmarshalingError;
a ← textRope ← Rope.NewText[size: ropeLength];
pktLength ← Lupine.CopyFromPkt[pkt: pkt, pktLength: pktLength,
dataAdr: BASE[DESCRIPTOR[textRope.text]], dataLength: Lupine.WordsForChars[ropeLength],
alwaysOnePkt: FALSE];
END; -- IF ropeIsNIL.
END; -- Unmarshal a.
BEGIN -- Unmarshal b: ATOM from pkt.data[pktLength].
pNameOfAtom: Rope.ROPE;
ropeIsNIL: Lupine.NilHeader;
IF pktLength+2 > RpcPrivate.maxDataLength
THEN pktLength ← Lupine.FinishThisPkt[pkt: pkt, pktLength:
pktLength];
ropeIsNIL ← pkt.data[pktLength]; pktLength ← pktLength+1;
IF ropeIsNIL
THEN pNameOfAtom ← NIL
ELSE BEGIN
ropeLength: Lupine.RopeHeader;
textRope: Rope.Text;
ropeLength ← pkt.data[pktLength]; pktLength ← pktLength+1;
IF ropeLength > LAST[NAT]
THEN Lupine.UnmarshalingError;
pNameOfAtom ← textRope ← Rope.NewText[size: ropeLength];
pktLength ← Lupine.CopyFromPkt[pkt: pkt, pktLength: pktLength,
dataAdr: BASE[DESCRIPTOR[textRope.text]], dataLength: Lupine.WordsForChars[ropeLength],
alwaysOnePkt: FALSE];
END; -- IF ropeIsNIL.
b ← Atom.MakeAtom[--pName:-- pNameOfAtom];
END; -- Unmarshal b.
Lupine.CheckPktLength[pkt: pkt, pktLength: pktLength];
RpcPrivate.DeAllocInline[LOOPHOLE[pkt], RpcPrivate.pktOverhead+254];
RETURN[a, b];
END; -- UNWIND.
END; -- Simple.
-- Remote public signals and errors.
Exception: PUBLIC ERROR [why: Reason] = CODE;
Consultation: PUBLIC SAFE SIGNAL = CODE;
-- Public signal and error dispatcher.
ClientDispatcher: --PROCEDURE [pkt: RPCPkt, callLength: DataLength,
-- lastPkt: BOOLEAN, localConversation: Conversation] RETURNS [returnLength:
-- DataLength]-- RpcPrivate.Dispatcher =
BEGIN
SELECT LOOPHOLE[pkt.data[0], RpcControl.SignalIndex] FROM
Exception => RETURN[
ExceptionStub[pkt: pkt, callLength: callLength, lastPkt: lastPkt,
localConversation: localConversation]];
Consultation => RETURN[
ConsultationStub[pkt: pkt, callLength: callLength, lastPkt:
lastPkt, localConversation: localConversation]];
ENDCASE => RETURN[Lupine.DispatchingError[]];
END; -- ClientDispatcher
-- Public signal and error dispatcher stubs.
ExceptionStub: --ERROR [why: Reason]-- RpcPrivate.Dispatcher =
INLINE BEGIN
ArgumentOverlay: TYPE = MACHINE DEPENDENT RECORD [
transferIndex (0): RpcControl.SignalIndex, why (1): Reason];
argPkt: LONG POINTER TO ArgumentOverlay = @pkt.data[0];
Lupine.CheckPktLength[pkt: pkt, pktLength: 2];
ERROR Exception[argPkt.why];
END; -- ExceptionStub.
ConsultationStub: --SIGNAL-- RpcPrivate.Dispatcher =
INLINE BEGIN
pktLength: RpcPrivate.DataLength;
Lupine.CheckPktLength[pkt: pkt, pktLength: 1];
SIGNAL Consultation[];
pktLength ← 0;
RETURN[returnLength: pktLength];
END; -- ConsultationStub.
-- No module initialization.
END. -- TargetRpcClientImpl.
-- Copyright (C) 1985 by Xerox Corporation. All rights reserved.
-- Stub file was translated on March 7, 1985 11:44:59 am PST by Lupine of March 6, 1985 8:13:46 am PST
-- Source interface Target came from file Target.bcd, which was created on March 7, 1985 11:44:45 am PST with version stamp 234#313#31370206177 from source of February 11, 1985 9:18:37 am PST.
-- The RPC stub modules for Target are:
-- TargetRpcControl.mesa;
-- TargetRpcClientImpl.mesa;
-- TargetRpcBinderImpl.mesa;
-- TargetRpcServerImpl.mesa.
-- The parameters for this translation are:
-- Target language = Cedar
-- Default parameter passing = VALUE
-- Deallocate server heap arguments = TRUE
-- Inline RpcServerImpl dispatcher stubs = TRUE
-- Declare signals = TRUE
-- Warn about short POINTER ("MDS") allocations = TRUE
-- Maximum number of dynamic heap NEWs = 50, MDS NEWs = 50
-- Acceptable parameter protocols = VersionRange[1..1].
DIRECTORY
Rope,
Target,
TargetRpcControl USING [InterMdsCallsOnly, LupineProtocolVersion,
ProcedureIndex, SignalIndex],
RPC USING [EncryptionKey, InterfaceName, Principal, standardZones,
Zones],
RPCLupine --USING SOME OF [Call, DataLength, Dispatcher, ExportHandle,
-- ExportInterface, GetStubPkt, maxDataLength, maxPrincipalLength,
-- maxShortStringLength, pktOverhead, ReceiveExtraPkt, SendPrelimPkt,
-- StartCall, StartSignal, StubPkt, UnexportInterface]--,
LupineRuntime --USING SOME OF [BindingError, CheckPktLength, CopyFromPkt,
-- CopyFromMultiplePkts, CopyToPkt, CopyToMultiplePkts, defaultZones,
-- DispatchingError, FinishThisPkt, ListHeader, MarshalingError,
-- MarshalingExprError, MarshalAtom, MarshalRope, NilHeader, ProtocolError,
-- RopeHeader, RpcPktDoubleWord, RuntimeError, SequenceHeader, SHORT,
-- StartNextPkt, StringHeader, StubPktDoubleWord, TranslationError,
-- UnmarshalingError, UnmarshalingExprError, UnmarshalAtom, UnmarshalRope,
-- WordsForChars]--,
Atom --USING SOME OF [GetPName, MakeAtom]--;
TargetRpcServerImpl: MONITOR
IMPORTS Target, RpcPrivate: RPCLupine, Lupine: LupineRuntime, Atom,
Rope
EXPORTS TargetRpcControl
SHARES Target, TargetRpcControl, Rope
= BEGIN OPEN Target, RpcControl: TargetRpcControl, RpcPublic: RPC;
-- Standard remote binding routines.
bound: BOOLEAN ← FALSE;
myInterface: RpcPrivate.ExportHandle;
paramZones: RpcPublic.Zones ← RpcPublic.standardZones;
ExportInterface: PUBLIC ENTRY SAFE PROCEDURE [
interfaceName: RpcPublic.InterfaceName,
user: RpcPublic.Principal,
password: RpcPublic.EncryptionKey,
parameterStorage: RpcPublic.Zones ] =
TRUSTED BEGIN ENABLE UNWIND => NULL;
IsNull: PROCEDURE [string: Rope.ROPE] RETURNS [BOOLEAN] =
INLINE {RETURN[string.Length[] = 0]};
IF bound THEN Lupine.BindingError;
myInterface ← RpcPrivate.ExportInterface [
interface: [
type: IF ~IsNull[interfaceName.type]
THEN interfaceName.type ELSE "Target~234#313#31370206177",
instance: interfaceName.instance,
version: interfaceName.version ],
user: user, password: password,
dispatcher: ServerDispatcher,
localOnly: RpcControl.InterMdsCallsOnly,
stubProtocol: RpcControl.LupineProtocolVersion ];
paramZones ← [
gc: IF parameterStorage.gc # NIL
THEN parameterStorage.gc ELSE Lupine.defaultZones.gc,
heap: IF parameterStorage.heap # NIL
THEN parameterStorage.heap ELSE Lupine.defaultZones.heap,
mds: IF parameterStorage.mds # NIL
THEN parameterStorage.mds ELSE Lupine.defaultZones.mds ];
bound ← TRUE;
END;
UnexportInterface: PUBLIC ENTRY SAFE PROCEDURE =
TRUSTED BEGIN ENABLE UNWIND => NULL;
IF ~bound THEN Lupine.BindingError;
myInterface ← RpcPrivate.UnexportInterface[myInterface];
paramZones ← RpcPublic.standardZones;
bound ← FALSE;
END;
-- Public procedure dispatcher and public signal and error catcher.
ServerDispatcher: --PROCEDURE [pkt: RPCPkt, callLength: DataLength,
-- lastPkt: BOOLEAN, localConversation: Conversation] RETURNS [returnLength:
-- DataLength]-- RpcPrivate.Dispatcher =
BEGIN
-- Catch public signals.
ENABLE BEGIN
Exception --ERROR [why: Reason]-- =>
BEGIN
ArgumentOverlay: TYPE = MACHINE DEPENDENT RECORD [
transferIndex (0): RpcControl.SignalIndex ← Exception, why
(1): Reason];
argPkt: LONG POINTER TO ArgumentOverlay = @pkt.data[0];
pktLength: RpcPrivate.DataLength ← 2;
lastPkt: BOOLEAN;
RpcPrivate.StartSignal[signalPkt: pkt];
argPkt^ ← [why: why];
[returnLength: , lastPkt: lastPkt] ←
RpcPrivate.Call[ pkt: pkt, callLength: pktLength,
maxReturnLength: 0];
Lupine.RuntimeError; -- Impossible to RESUME an ERROR.
END; -- Exception.
Consultation --SIGNAL-- =>
TRUSTED BEGIN
ArgumentOverlay: TYPE = MACHINE DEPENDENT RECORD [
transferIndex (0): RpcControl.SignalIndex ← Consultation];
argPkt: LONG POINTER TO ArgumentOverlay = @pkt.data[0];
pktLength: RpcPrivate.DataLength ← 1;
lastPkt: BOOLEAN;
RpcPrivate.StartSignal[signalPkt: pkt];
argPkt.transferIndex ← Consultation;
[returnLength: , lastPkt: lastPkt] ←
RpcPrivate.Call[ pkt: pkt, callLength: pktLength,
maxReturnLength: 0];
Lupine.CheckPktLength[pkt: pkt, pktLength: 0];
RESUME[];
END; -- Consultation.
END; -- Catch public signals.
-- Call public procedures (still in dispatcher).
SELECT LOOPHOLE[pkt.data[0], RpcControl.ProcedureIndex] FROM
Basic => RETURN[
BasicStub[pkt: pkt, callLength: callLength, lastPkt: lastPkt,
localConversation: localConversation]];
Simple => RETURN[
SimpleStub[pkt: pkt, callLength: callLength, lastPkt: lastPkt,
localConversation: localConversation]];
ENDCASE => RETURN[Lupine.DispatchingError[]];
END; -- ServerDispatcher
-- Public procedure dispatcher stubs.
BasicStub: --PROCEDURE-- RpcPrivate.Dispatcher =
INLINE BEGIN
pktLength: RpcPrivate.DataLength;
Lupine.CheckPktLength[pkt: pkt, pktLength: 1];
Basic[];
pktLength ← 0;
RETURN[returnLength: pktLength];
END; -- BasicStub.
SimpleStub: --PROCEDURE [first: INT, second: REF INT] RETURNS [a:
-- Rope.ROPE, b: ATOM]-- RpcPrivate.Dispatcher =
INLINE BEGIN
second: REF INT;
a: Rope.ROPE;
b: ATOM;
ArgumentOverlay: TYPE = MACHINE DEPENDENT RECORD [
transferIndex (0): RpcControl.ProcedureIndex, first (1): INT];
argPkt: LONG POINTER TO ArgumentOverlay = @pkt.data[0];
pktLength: RpcPrivate.DataLength ← 3;
BEGIN -- Unmarshal second: REF INT from pkt.data[pktLength].
isNIL: Lupine.NilHeader;
isNIL ← pkt.data[pktLength]; pktLength ← pktLength+1;
IF isNIL
THEN second ← NIL
ELSE BEGIN
second ← (paramZones.gc.NEW[INT]);
BEGIN
second^ ← Lupine.RpcPktDoubleWord[pkt, pktLength]^;
pktLength ← pktLength + 2;
END;
END; -- IF isNIL.
END; -- Unmarshal second.
Lupine.CheckPktLength[pkt: pkt, pktLength: pktLength];
[a, b] ←
Simple[argPkt.first, second];
pktLength ← 0;
BEGIN -- Marshal a: Rope.ROPE to pkt.data[pktLength].
IF pktLength+2 > RpcPrivate.maxDataLength
THEN pktLength ← Lupine.StartNextPkt[pkt: pkt, pktLength: pktLength];
pkt.data[pktLength] ← a=NIL; pktLength ← pktLength+1;
IF a # NIL
THEN BEGIN
textRope: Rope.Text = Rope.InlineFlatten[r: a];
pkt.data[pktLength] ← textRope.length; pktLength ← pktLength+1;
pktLength ← Lupine.CopyToPkt[pkt: pkt, pktLength: pktLength,
dataAdr: BASE[DESCRIPTOR[textRope.text]], dataLength: Lupine.WordsForChars[textRope.length],
alwaysOnePkt: FALSE];
END; -- IF a # NIL.
END; -- Marshal a.
BEGIN -- Marshal b: ATOM to pkt.data[pktLength].
pNameOfAtom: Rope.Text = Atom.GetPName[atom: b];
IF pktLength+2 > RpcPrivate.maxDataLength
THEN pktLength ← Lupine.StartNextPkt[pkt: pkt, pktLength: pktLength];
pkt.data[pktLength] ← pNameOfAtom=NIL; pktLength ← pktLength+1;
IF pNameOfAtom # NIL
THEN BEGIN
textRope: Rope.Text = Rope.InlineFlatten[r: pNameOfAtom];
pkt.data[pktLength] ← textRope.length; pktLength ← pktLength+1;
pktLength ← Lupine.CopyToPkt[pkt: pkt, pktLength: pktLength,
dataAdr: BASE[DESCRIPTOR[textRope.text]], dataLength: Lupine.WordsForChars[textRope.length],
alwaysOnePkt: FALSE];
END; -- IF pNameOfAtom # NIL.
END; -- Marshal b.
RETURN[returnLength: pktLength];
END; -- SimpleStub.
-- No module initialization.
END. -- TargetRpcServerImpl.
-- Copyright (C) 1985 by Xerox Corporation. All rights reserved.
-- Stub file was translated on March 7, 1985 11:44:58 am PST by Lupine of March 6, 1985 8:13:46 am PST
-- Source interface Target came from file Target.bcd, which was created on March 7, 1985 11:44:45 am PST with version stamp 234#313#31370206177 from source of February 11, 1985 9:18:37 am PST.
-- The RPC stub modules for Target are:
-- TargetRpcControl.mesa;
-- TargetRpcClientImpl.mesa;
-- TargetRpcBinderImpl.mesa;
-- TargetRpcServerImpl.mesa.
-- The parameters for this translation are:
-- Target language = Cedar
-- Default parameter passing = VALUE
-- Deallocate server heap arguments = TRUE
-- Inline RpcServerImpl dispatcher stubs = TRUE
-- Declare signals = TRUE
-- Warn about short POINTER ("MDS") allocations = TRUE
-- Maximum number of dynamic heap NEWs = 50, MDS NEWs = 50
-- Acceptable parameter protocols = VersionRange[1..1].
-- NOTE: Discard this module unless you use dynamic client binding.
DIRECTORY
Target,
TargetRpcControl USING [ImportInterface, InterfaceRecord, InterfaceRecordObject],
TargetRpcClientImpl,
RPC USING [InterfaceName, Zones],
SafeStorage USING [EnableFinalization, EstablishFinalization, FinalizationQueue,
FQEmpty, FQNext, NewFQ];
TargetRpcBinderImpl: MONITOR
IMPORTS RpcControl: TargetRpcControl, ClientPrototype: TargetRpcClientImpl,
RTT: SafeStorage
EXPORTS TargetRpcControl
SHARES Target, TargetRpcControl
= BEGIN OPEN RpcPublic: RPC;
-- Dynamic instantiation and binding routines.
ImportNewInterface: PUBLIC SAFE PROCEDURE [
interfaceName: RpcPublic.InterfaceName,
parameterStorage: RpcPublic.Zones ]
RETURNS [interfaceRecord: RpcControl.InterfaceRecord] =
TRUSTED BEGIN
interfaceRecord ← NewInterface[];
LupineDetails[interfaceRecord].module.ImportInterface [
interfaceName: interfaceName,
parameterStorage: parameterStorage
! UNWIND => FreeInterface[interfaceRecord] ];
END;
UnimportNewInterface: SAFE PROCEDURE [
interfaceRecord: RpcControl.InterfaceRecord ] =
TRUSTED BEGIN
LupineDetails[interfaceRecord].module.UnimportInterface[];
FreeInterface[interfaceRecord];
END;
-- Utility routines for interface instantiation and caching.
ConcreteLupineDetails: TYPE = REF LupineDetailsObject;
LupineDetailsObject: PUBLIC TYPE = RECORD [
module: ClientModule←NIL,
list: RpcControl.InterfaceRecord←NIL, --package reference
next: RpcControl.InterfaceRecord←NIL --free list -- ];
LupineDetails: PROCEDURE [abstractInterface: RpcControl.InterfaceRecord]
RETURNS [ConcreteLupineDetails] =
INLINE {RETURN[abstractInterface.lupineDetails]};
ClientModule: TYPE = POINTER TO FRAME[TargetRpcClientImpl];
clientInterfaceCache: RpcControl.InterfaceRecord ← NIL; -- free interface
-- records
clientInterfaceList: RpcControl.InterfaceRecord ← NIL; -- all interface
-- records
NewInterfaceRecord: PUBLIC SAFE PROCEDURE
RETURNS [interfaceRecord: RpcControl.InterfaceRecord] =
TRUSTED BEGIN
interfaceRecord ← NEW[RpcControl.InterfaceRecordObject];
END;
NewInterface: PROCEDURE RETURNS [interface: RpcControl.InterfaceRecord]=
BEGIN
GetCachedInterface: ENTRY PROCEDURE
RETURNS [cachedIR: RpcControl.InterfaceRecord] =
INLINE BEGIN ENABLE UNWIND => NULL;
IF (cachedIR𡤌lientInterfaceCache) # NIL
THEN clientInterfaceCache ← LupineDetails[clientInterfaceCache].next;
END;
ReclaimInterfaces[];
IF (interface ← GetCachedInterface[]) = NIL
THEN BEGIN
ChainNewInterface: ENTRY PROCEDURE =
INLINE BEGIN ENABLE UNWIND => NULL;
interface.lupineDetails ← NEW[
LupineDetailsObject ← [module: module, list: clientInterfaceList]];
clientInterfaceList ← interface;
END; -- ChainNewInterface.
module: ClientModule = NEW ClientPrototype;
interface ← NewInterfaceRecord[];
interface^ ← [
Basic: module.Basic, Simple: module.Simple, Exception: module.Exception,
Consultation: module.Consultation];
ChainNewInterface[];
END;
RTT.EnableFinalization[interface];
END;
FreeInterface: ENTRY PROCEDURE [interface: RpcControl.InterfaceRecord]=
INLINE BEGIN ENABLE UNWIND => NULL;
LupineDetails[interface].next ← clientInterfaceCache;
clientInterfaceCache ← interface;
END;
-- Finalization for dynamic interfaces. Just cache and reuse for now.
freedInterfaces: RTT.FinalizationQueue = RTT.NewFQ[20];
ReclaimInterfaces: PROCEDURE =
INLINE BEGIN
WHILE ~RTT.FQEmpty[freedInterfaces] DO
interface: RpcControl.InterfaceRecord =
NARROW[RTT.FQNext[freedInterfaces]];
IF interface.lupineDetails # NIL THEN
UnimportNewInterface[interface];
ENDLOOP;
END;
-- Module initialization.
RTT.EstablishFinalization[
type: CODE[RpcControl.InterfaceRecordObject],
npr: 1, fq: freedInterfaces ];
END. -- TargetRpcBinderImpl.