.alone←declaration(expurgate)=0;
.if alone then start
.require "ttydefs.pub" source!file
.end
.every heading (|Grapevine Package|,|September 20, 1983|,{page})
.once center
Grapevine Package
.skip 3

This package implements a subset of the client protocols for accessing the Grapevine registration services.  The top-level facilities included are user name/password authentication, group membership determination, and resource location.  The package does not include operations for updating the Grapevine data base or for transporting electronic mail.  However, lower-level primitives are accessible, out of which those operations could be constructed.

For information on the organization and use of the Grapevine registration services and the protocols used to access them, consult "The Grapevine Interface", by Andrew Birrell, file [Indigo]Docs>Interface.press.

.sec Organization and requirements

The Grapevine package is distributed as file Grapevine.dm, which contains the following binary files: GrapevineEnquire.br, GrapevineLocate.br, GrapevineNameInfo.br, and GrapevineProtocol.br.  In normal use, all four modules are required.

All modules of the Grapevine package may be loaded into overlays (managed by the BCPL Overlays package); such overlays will swap out when calls to Grapevine are not in progress.  The initialization module GrapevineOEP.br declares Overlay Entry Points (OEPs) required by the package.

The Grapevine package depends on the Pup package and all its supporting packages.  The following is a complete list of the modules required on a normal Alto:

.begin nojust indent 0,24 tabs 25
Grapevine package\GrapevineEnquire, GrapevineLocate, GrapevineNameInfo, GrapevineProtocol (from Grapevine.dm)

Pup package\PupBSPOpenClose, PupBSPProt, PupBSPStreams, PupBSPBlock, PupBSPa, PupRTPOpenClose, PupRTP, PupNetDirLookup, Pup1OpenClose, Pup1b, PupAl1a, PupRoute, PupAlEthb, PupAlEtha, Pup1Init, PupDummyGate, PupAlEthInit (from PupPackage.dm)

Context package\Context, ContextInit (from Context.dm)

Interrupt package\Interrupt, InterruptInit (from Interrupt.dm)

Queue package\AltoQueue

Timer package\AltoTimer

Strings package\StringUtil (from Strings.dm)

ByteBlt package\AltoByteBlt
.end

File Grapevine.decl is a "get" file containing definitions needed by clients of the package.  These definitions consist of various data structures and error codes, including the following:

An RName is a Grapevine registered name, in the form "simpleName.registry".  It has the same representation as a BCPL String.

A Password is a 64-bit encryption key, straightforwardly derived from a text password.  A procedure exists to convert a String to a Password.

A TimeStamp consists of a net address and a standard Alto packed time.  All objects in the Grapevine data base are marked with TimeStamps, which are used to determine the validity of cached information and to control the propagation of updates.

An RList represents the contents of a Grapevine group (or other list maintained by Grapevine).  It contains a TimeStamp and is the head of a queue of RItems, each of which contains an RName.

.sec Initialization and general operation

The Grapevine package must be initialized by calling:

.begin indent 4,4
.once indent 0
InitGrapevine(zone, testing [false])
.continue
Initializes the global state of the package.  "zone" is a zone to be used for all storage allocations by the package.  InitGrapevine permanently extracts only a four-word block from zone; but other operations make heavy temporary use of zone.

If testing is true, the Grapevine package communicates only with servers that are running in "testing" mode.  InitGrapevine may be called multiple times to change testing; the zone argument is ignored during all but the first call.
.end

The Pup package must be initialized (InitPupLevel1) before the Grapevine package may be called; and calls to Grapevine procedures must be from within the confines of a Context (as managed by the BCPL Context package).

The Grapevine package caches some global state between calls.  Most significantly, a BSP stream to the most recently accessed Grapevine server is usually left open; in most cases this substantially speeds up a subsequent request if it is made within the Grapevine server's connection timeout interval (currently one minute).  If desired, this global state may be flushed by calling GVDestroyStream().

Procedures in the Grapevine package may be called from multiple contexts (processes).  The package maintains an internal interlock so that only one Grapevine protocol interaction will be in progress at a time.

.sec High-level operations

These operations are sufficient for authentication, access control, and resource location.

.begin indent 4,4
.once indent 0
Authenticate(name, password) = code
.continue
Checks "name" (an RName) and "password" (a 64-bit Password key) in the Grapevine data base, and returns a code describing the outcome (from Grapevine.decl):
.begin nofill tabs 20

ecIndividual\name is an individual and password is correct.
ecBadPassword\incorrect password.
ecBadRName\name does not exist or is not an individual.
ecAllDown\can't contact any R-Server for name's registry.
.end

.once indent 0
MakeKey(string, key)
.continue
Converts a text password pointed to by "string" to a 64-bit encryption key pointed to by "key".  The result is suitable as the "password" argument of Authenticate.

.once indent 0
IsMemberClosure(group, name) = code
.continue
Determines whether "name" appears anywhere in the tree that results from expanding "group"; both arguments are RNames.  Returns a code describing the outcome:
.begin nofill tabs 20

ecIsMember\name is a member of group.
ecIsNotMember\name is not a member of group.
ecBadRName\name or group does not exist or is of wrong type.
ecAllDown\can't contact any R-Server for group's registry.
.end

.once indent 0
FindServer(serverName, pollingSocket, proc, arg)
.continue
Attempts to find an instance of a service identified by the specified name. If serviceName contains a ".", treats it as an RName (which must be a Grapevine group) and attempts to locate the nearest functioning instance of the service among the group's members (which must be individuals whose connect sites are valid Pup address constants).  If serviceName does not contain a ".", treats it as a Name Lookup Server (NLS) name and attempts to locate the nearest functioning instance of the service without consulting Grapevine (a local broadcast is also issued).

In either case, for each potential instance of the service, calls proc(port, arg), which should attempt to open a connection to the given port and return true if successful and false if not (arg may be used to communicate additional parameters and/or results).  Proc should at least default and perhaps unconditionally set the port's socket field to the appropriate well-known socket number before using it, since in general the service socket is distinct from the polling socket.  Note that proc will be called repeatedly until either it returns true or the list of potential instances is exhausted.

If FindServer is successful, it returns zero; if unsuccessful, it returns one of ecBadRName (there is no such service) or ecAllDown (can't contact any instance of the service).

The service to be located must respond to EchoMe requests on a well-known socket, the low 16 bits of which are given as pollingSocket and the high 16 bits of which are zero (except when the Grapevine package is in testing mode).  Any service (Grapevine or non-Grapevine) obeying this convention may be located, regardless of whether it is named by an RName or an NLS name.

.end

.sec Higher-level primitives

These procedures may be used to perform most of the querying (as opposed to updating) operations provided by Grapevine.  Calling them requires definitions from GrapevineProtocol.decl, which may be obtained from GrapevineSources.dm.

.begin indent 4,4
.once indent 0
IsInACL(name, member, descriptor) = code
.continue
Determines membership in an access control list.  "name" is the RName of a group with which the access control list is associated, "member" is the RName to be tested for membership in that list, and "descriptor" encodes one of a large collection of access control list checking options.  "descriptor" is composed by adding together three constants, one from each of the following groups:

The R-Name with which the access control list is actually associated:
.begin nofill indent 4 tabs 24
dItself\the group "name" itself.
dItsRegistry\"name"s registry (i.e., "reg.GV").
.end

What access control list (associated with "name") is to be checked:
.begin nofill indent 4 tabs 24
dMembers\the membership list of the group.
dOwners\the owners list of the group.
dFriends\the friends list of the group.
.end

How the list is to be enumerated:
.begin fill preface 0 indent 4,23 tabs 24 crbreak
dDirect\just check for membership in the list itself.
dClosure\expand any names that are groups, and check for membership in those groups as well (do this recursively to as many levels as necessary).
dUpArrow\as for dClosure, but expand only those groups whose names contain "↑"; in many cases this is considerably faster than dClosure.
.end

For example, the IsMemberClosure procedure, described earlier, is implemented as IsInACL(name, member, dItself+dMember+dClosure).

IsInACL returns one of the codes described under IsMemberClosure (above).

.once indent 0
ReadRList(name, op, lvEC []) = rList or 0
.continue
Reads a list associated with "name" (an RName) as specified by "op":
.begin nofill tabs 25

opReadMembers\the (direct) membership of the group "name".
opReadOwners\the (direct) Owner list for "name".
opReadFriends\the (direct) Friends list for "name".
.end

If this is successful, ReadRList returns a pointer to an RList, which heads a queue of RItems each of which contains an RName.  This RList is allocated from the zone that was passed to InitGrapevine; note that an indefinite amount of storage is required, depending on the size of the list.

If ReadRList is unsuccessful, it returns zero after storing into @lvEC (if specified) one of the following codes:
.begin nofill tabs 20

ecBadRName\name does not exist or is not a group.
ecAllDown\can't contact any R-Server for group's registry.
.end

.once indent 0
DestroyRList(rList)
.continue
Destroys (i.e., frees the storage occupied by) an RList returned by ReadRList.

.once indent 0
ReadRString(name, op, lvEC []) = string or 0
.continue
Reads a string associated with "name" (an RName) as specified by "op":
.begin nofill tabs 20

opReadConnect\the Connect site for the individual "name".
opReadRemark\the Remark for the group "name".
.end

If this is successful, ReadRString returns a BCPL String allocated from the zone that was passed to InitGrapevine.  The caller must Free the string to that zone when done with it.
.end

.sec Lower-level primitives

These are the procedures out of which the higher-level operations are composed; they may be used to compose other Grapevine operations (both queries and updates) not provided by the package.  If you use these, you should read the code in GrapevineNameInfo.bcpl to understand how they are intended to be called.  Calling these procedures may require definitions from GrapevineInternal.decl as well as GrapevineProtocol.decl and Grapevine.decl.

.begin indent 4,4
.once indent 0
Enquire(name, proc, arg) = returnCode
.continue
Attempts to establish a BSP stream to a registration server that has a copy of the registry for "name" (an RName), and then calls proc(stream, name, arg).  Proc should return a ReturnCode describing the outcome; any additional results may be communicated via storage pointed to by "arg".  Enquire returns the ReturnCode returned by proc if proc was actually called, or a locally-manufactured ReturnCode otherwise.  See GrapevineProtocol.decl and the "Grapevine Interface" document for the interpretation of ReturnCodes.

Enquire's function is simply to establish contact with the correct registration server; it is proc's responsibility to perform the actual desired query, update, or whatever.  That is, proc must send the protocol command over stream and interpret the result.  Auxiliary procedures that facilitate this include SendWord, SendRName, ReceiveWord, ReceiveRName, and ReceiveRList.

The first word of a Grapevine server's response is always a ReturnCode; and it is this ReturnCode that proc is expected to return.  Additionally, if ReturnCode.code equals rcDone, proc is expected to consume any additional results that follow the ReturnCode before returning.

Proc may be called more than once, if the first call results in a ReturnCode indicating that the wrong registration server has been contacted.  Also, any I/O to the BSP stream may result in a stream error, which causes control to be ripped away from proc and returned to Enquire.  Consequently, proc must not acquire any resources (e.g., allocated storage) not known to the caller of Enquire, else those resources may be lost.

.once indent 0
EnquireWithStamp(stream, op, name, stamp [nullStamp]) = returnCode
.continue
Sends a protocol command of the form [name, op, stamp] and returns the resulting ReturnCode.  This constitutes the major protocol interaction of many Grapevine commands; it should be called only from within a "proc" passed to Enquire.
.end

.sec Revision history

January 6, 1982

The third argument of IsInACL is now a "descriptor" encoding all possible combinations of ACL checking options.  InitGrapevine has a "testing" option.

September 20, 1983

A procedure FindServer has been added for locating servers by means of the Grapevine resource location algorithm.  A few minor bugs are fixed.