{Begin SubSec Higher-level NS Protocol Functions} {Title Higher-level NS Protocol Functions} {Text The following is a description of the Interlisp-D facilities for using Xerox SPP and Courier protocols and the services based on them. The sections on naming conventions, Printing, and Filing are of general interest to users of Network Systems servers. The remaining sections describe interfaces of interest to those who wish to program other applications on top of either Courier or SPP. {begin subsec Name and Address Conventions} {title Name and Address Conventions} {text Addresses of hosts in the NS world consist of three parts, a network number, a machine number, and a socket number. These three parts are embodied in the Interlisp-D datatype {term {Lisp NSADDRESS}}. Objects of type {Lisp NSADDRESS} print as "net#h1.h2.h3#socket", where all the numbers are printed in octal radix, and the 48-bit host number is broken into three 16-bit fields. Most functions that accept an address argument will accept either an {Lisp NSADDRESS} object or a string that is the printed representation of the address. Higher-level functions accept host arguments in the form of a symbolic name for the host. The NS world has a hierarchical name space. Each object name is in three parts: the {it Organization}, the {it Domain}, and the {it Object} parts. There can be many domains in a single organization, and many objects in a single domain. The name space is maintained by the {term {it Clearinghouse}}, a distributed network database service. A Clearinghouse name is standardly notated as {it object}:{it domain}:{it organization}. The parts {it organization} or {it domain}:{it organization} may be omitted if they are the default (see below). Alphabetic case is not significant. Internally, names are represented as objects of datatype {term {lisp NSNAME}}, but most functions accept the textual representation as well, either as a litatom or a string. Objects of type {lisp NSNAME} print as {it object}:{it domain}:{it organization}, with fields omitted when they are equal to the default. A {it Domain} is standardly represented as an {lisp NSNAME} in which the object part is null. If frequent use is to be made of an NS name, it is generally preferable to convert it to an {lisp NSNAME} once, by calling {fn PARSE.NSNAME}, then passing the resultant object to all functions desiring it. {vardef {name CH.DEFAULT.ORGANIZATION} {text This is a string specifying the default Clearinghouse organization. }} {vardef {name CH.DEFAULT.DOMAIN} {text This is a string specifying the default Clearinghouse domain. If it or the variable {var CH.DEFAULT.ORGANIZATION} is {lisp NIL}, they are set by Lisp system code (when they are needed) to be the first domain served by the nearest Clearinghouse server. }} In small organizations with just one domain, it is reasonable to just leave these variables {lisp NIL} and have the system set them appropriately. In organizations with more than one domain, it is wise to set them in the site initialization file, so as not to be dependent on exactly which Clearinghouse servers are up at any time. {fnDef {name PARSE.NSNAME}{args NAME #PARTS DEFAULTDOMAIN} {text When {arg #PARTS} is 3 (or {lisp NIL}), parses {arg NAME}, a litatom or string, into its three parts, returning an object of type {lisp NSNAME}. If the domain or organization is omitted, defaults are supplied, either from {arg DEFAULTDOMAIN} (an {lisp NSNAME} whose domain and organization fields only are used) or from the variables {var CH.DEFAULT.DOMAIN} and {var CH.DEFAULT.ORGANIZATION}. If {arg #PARTS} is 2, {arg NAME} is interpreted as a domain name, and an {lisp NSNAME} with null object is returned. In this case, if {arg NAME} is a full 3-part name, the object part is stripped off. If {arg #PARTS} is 1, {arg NAME} is interpreted as an organization name, and a simple string is returned. In this case, if {arg NAME} is a 2- or 3-part name, the organization is extracted from it. If {arg NAME} is already an object of type {lisp NSNAME}, then it is returned as is (if {arg #PARTS} is 3), or its domain and/or organization parts are extracted (if {arg #PARTS} is 1 or 2). }} {fndef {name NSNAME.TO.STRING} {args NSNAME FULLNAMEFLG} {text Converts {arg NSNAME}, an object of type {lisp NSNAME}, to its string representation. If {arg FULLNAMEFLG} is true, the full printed name is returned; otherwise, fields that are equal to the default are omitted. }} Programmers who wish to manipulate {lisp NSADDRESS} and {lisp NSNAME} objects directly should load the Library package {lisp ETHERRECORDS}. }{end subsec Name and Address Conventions} {begin subsec Clearinghouse Functions} {title Clearinghouse Functions} {text This section describes functions that may be used to access information in the Clearinghouse. {fndef {name START.CLEARINGHOUSE} {args RESTARTFLG} {text Performs an expanding ring broadcast in order to find the nearest Clearinghouse server, whose address it returns. If a Clearinghouse has already been located, this function simply returns its address immediately, unless {arg RESTARTFLG} is true, in which case the cache of Clearinghouse information is invalidated and a new broadcast is performed. {fn START.CLEARINGHOUSE} is normally performed automatically by the system the first time it needs Clearinghouse information; however, it may be necessary to call it explicitly (with {arg RESTARTFLG} set) if the local Clearinghouse server goes down. }} {vardef {name CH.NET.HINT} {text A number or list of numbers, giving a hint as to which network the nearest Clearinghouse server is on. When {fn START.CLEARINGHOUSE} looks for a Clearinghouse server, it probes the network(s) given by {var CH.NET.HINT} first, performing the expanding ring broadcast only if it fails there. If the nearest Clearinghouse server is not on the directly connected network, setting {var CH.NET.HINT} to the proper network number in the local {lisp INIT} file can speed up {fn START.CLEARINGHOUSE} considerably. }} {fndef {name SHOW.CLEARINGHOUSE} {args ENTIRE.CLEARINGHOUSE? DONT.GRAPH} {text This function displays the structure of the cached Clearinghouse information in a window. Once created, it will be redisplayed whenever the cache is updated, until the window is closed. The structure is shown using the Library package {lisp GRAPHER}. If {arg ENTIRE.CLEARINGHOUSE?} is true, then this function probes the Clearinghouse to discover the entire domain:organization structure of the Internet, and graphs the result. If {arg DONT.GRAPH} is true, the structure is not graphed, but rather the results are returned as a nested list indicating the structure. }} {fndef {name LOOKUP.NS.SERVER} {args NAME TYPE FULLFLG} {text Returns the address, as an {lisp NSADDRESS}, for the object {arg NAME}. {arg TYPE} is the property under which the address is stored, which defaults to {lisp ADDRESS.LIST}. The information is cached so that it need not be recomputed on each call; the cache is cleared by restarting the Clearinghouse. If {arg FULLFLG} is true, returns a list whose first element is the canonical name of {arg NAME} and whose tail is the address list. }} The following functions perform various sorts of retrieval operations on database entries in the Clearinghouse. Here, "The Clearinghouse" refers to the collective service offered by all the Clearinghouse servers on an internet; Lisp internally deals with which actual server(s) it needs to contact to obtain the desried information. The argument(s) describing the objects under consideration can be strings or {lisp NSNAME}'s, and in most cases can contain the wild card "{lisp *}", which matches a subsequence of zero or more characters. Wildcards are permitted only in the most specific field of a name (e.g., in the object part of a full three-part name). When an operation intended for a single object is instead given a pattern, the operation is usually performed on the first matching object in the database, which may or may not be interesting. {fndef {name CH.LOOKUP.OBJECT} {args OBJECTPATTERN} {text Looks up {arg OBJECTPATTERN} in the Clearinghouse database, returning its canonical name (as an {lisp NSNAME}) if found, {lisp NIL} otherwise. If {arg OBJECTPATTERN} contains a "{lisp *}", returns the first matching name. }} {fndef {name CH.LIST.ORGANIZATIONS} {args ORGANIZATIONPATTERN} {text Returns a list of organization names in the Clearinghouse database matching {arg ORGANIZATIONPATTERN}. The default pattern is "{lisp *}", which matches anything. }} {fndef {name CH.LIST.DOMAINS} {args DOMAINPATTERN} {text Returns a list of domain names (two-part {lisp NSNAME}'s) in the Clearinghouse database matching {arg DOMAINPATTERN}. The default pattern is "{lisp *}", which matches anything in the default organization. }} {fndef {name CH.LIST.OBJECTS} {args OBJECTPATTERN PROPERTY} {text Returns a list of object names matching {arg OBJECTPATTERN} and having the property {arg PROPERTY}. {arg PROPERTY} is a number or a symbolic name for a Clearinghouse property; the latter include {lisp USER}, {lisp PRINT.SERVICE}, {lisp FILE.SERVICE}, {lisp MEMBERS}, {lisp ADDRESS.LIST} and {lisp ALL}. For example, {lisp (CH.LIST.OBJECTS "*:PARC:Xerox" (QUOTE USER))} returns a list of the names of users in the domain PARC:Xerox. {lisp (CH.LIST.OBJECTS "*lisp*:PARC:Xerox" (QUOTE MEMBERS))} returns a list of all group names in PARC:Xerox containing the substring "lisp". }} {fndef {name CH.LIST.ALIASES} {args OBJECTNAMEPATTERN} {text Returns a list of all objects in the Clearinghouse database that are aliases and match {arg OBJECTNAMEPATTERN}. }} {fndef {name CH.LIST.ALIASES.OF} {args OBJECT} {text Returns a list of all objects in the Clearinghouse database that are aliases of {arg OBJECT}. }} {fndef {name CH.RETRIEVE.ITEM} {args OBJECT PROPERTY INTERPRETATION} {text Retrieves the value of the {arg PROPERTY} property of {arg OBJECT}. Returns a list of two elements, the canonical name of the object and the value. If {arg INTERPRETATION} is given, it is a Clearinghouse type (see {SectionRef Mac COURIERDEF}) with which to interpret the bits that come back; otherwise, the value is simply of the form {lisp (SEQUENCE UNSPECIFIED)}, a list of 16-bit integers representing the value. }} {fndef {name CH.RETRIEVE.MEMBERS} {args OBJECT PROPERTY {anonarg}} {text Retrieves the members of the group {arg OBJECT}, as a list of {lisp NSNAME}'s. {arg PROPERTY} is Clearinghouse Group property under which the members are stored; the usual property used for this purpose is {lisp MEMBERS}. }} {fndef {name CH.ISMEMBER} {args GROUPNAME PROPERTY SECONDARYPROPERTY NAME} {text Tests whether {arg NAME} is a member of {arg GROUPNAME}'s {arg PROPERTY} property. This is a potentially complex operation; see the description of procedure {lisp IsMember} in the Clearinghouse Protocol documentation for details. }} }{end subsec Clearinghouse Functions} {Begin Note} This section [NS Printing] no longer needed. [bvm: I disagree, is still interesting] {End Note} {begin subsec NS Printing} {title NS Printing} {text This section describes the facilities that are available for printing Interpress masters on NS Print servers. {Begin Note} [bvm: Let's dedocument this.] {vardef {name NS.DEFAULT.PRINTER} {text The value of this variable is used whenever no printserver is specified for the functions described below. Its value is a Clearinghouse name, as an atom, string or {lisp NSNAME}. If its value is {lisp NIL}, it will be set automatically to some printserver in the local Clearinghouse domain. In environments where there is no Clearinghouse, the value of {var NS.DEFAULT.PRINTER} must be an appropriate {lisp NSADDRESS} record. }} {End Note} {fndef {name NSPRINT} {args PRINTER FILE OPTIONS} {text This function prints an Interpress master on {arg PRINTER}, which is a Clearinghouse name represented as a string or {lisp NSNAME}. If {arg PRINTER} is {lisp NIL}, {fn NSPRINT} uses the first print server registered in the default domain. {arg FILE} is the name of an Interpress file to be printed. {arg OPTIONS} is a list in property list format that controls details of the printing. Possible properties are as follows: {begin labeledList NSPRINT options} {label {lisp DOCUMENT.NAME}} {text The document name to appear on the header page (a string). Default is the full name of the file.} {label {lisp DOCUMENT.CREATION.DATE}} {text The creation date to appear on the header page (a Lisp integer date, such as returned by {fn IDATE}). The default value is the creation date of the file.} {label {lisp SENDER.NAME}} {text The name of the sender to appear on the header page (a string). The default value is the name of the user.} {label {lisp RECIPIENT.NAME}} {text The name of the recipient to appear on the header page (a string). The default is none.} {label {lisp #COPIES}} {text The number of copies to be printed. The default value is 1.} {label {lisp MEDIUM}} {text The medium on which the master is to be printed. If omitted, this defaults to the value of {var NSPRINT.DEFAULT.MEDIUM}{index NSPRINT.DEFAULT.MEDIUM Var}, as follows: {lisp NIL} means to use the printer's default; {lisp T} means to use the first medium reported available by the printer; any other value must be a Courier value of type {lisp MEDIUM}. The format of this type is a list {lisp (PAPER (KNOWN.SIZE {arg TYPE}))} or {lisp (PAPER (OTHER.SIZE ({arg WIDTH LENGTH})))}. The paper {arg TYPE} is one of {lisp US.LETTER}, {lisp US.LEGAL}, {lisp A0} through {lisp A10}, {lisp ISO.B0} through {lisp ISO.B10}, and {lisp JIS.B0} through {lisp JIS.B10}.} {label {lisp STAPLE?}} {text True if the document should be stapled.} {label {lisp #SIDES}} {text 1 or 2 to indicate that the document should be printed on one or two sides, respectively. The default is the value of {var EMPRESS#SIDES}{index EMPRESS#SIDES var}.} {label {lisp PRIORITY}} {text The priority of this print request, one of {lisp LOW}, {lisp NORMAL}, or {lisp HIGH}. The default is the printer's default.} {end labeledList NSPRINT options} }} {fndef {name NSPRINTER.STATUS} {args PRINTER} {text This function returns a list describing the printer's current status{emdash}whether it is available or busy, and what kind of paper is loaded. }} {fndef {name NSPRINTER.PROPERTIES} {args PRINTER} {text This function returns a list describing the printer's capabilities at the moment{emdash}type of paper loaded, whether it can print two-sided, etc. }} }{end subsec NS Printing} {begin subsec NS Filing} {title NS Filing} {text Lisp accesses Xerox NS fileservers using the NS Filing Protocol. For most operations, the programmer simply treats the NS fileserver as any other host or device. {fn OPENFILE}, {fn GETFILEINFO}, {fn DIRECTORY} all work appropriately when the filename specifies an NS file server host name, e.g., {lisp {bracket PHYLEX:PARC:XEROX}<LISP>LIBRARY>GRAPHER.DCOM;1}. Note that all NS File Server host names must contain a colon, even if the domain and organization fields are defaulted, in order that they be distinguishable from other types of host names (e.g., Pup server names). Note also that spaces are allowable characters in NS names. Thus, if an NS file name contains spaces (in the file name or the server name) and is presented as a litatom, the spaces must be quoted with {lisp %}. However, all the standard file operations also work when the name is presented as a string, in which case there is no problem with spaces. The following are features specific to NS fileservers. {fndef {name BREAK.NSFILING.CONNECTION}{args HOST} {text Closes any open connections to NS file server {arg HOST}. }} {vardef {name FILING.ENUMERATION.DEPTH} {text The full NS Filing Protocol supports a true hierarchical name space for files. This leaves some ambiguity about how deep the function {fn DIRECTORY} should enumerate files. The depth is controlled by the variable {var FILING.ENUMERATION.DEPTH}, which is either a number, specifying the number of levels deep to enumerate, or {lisp T}, meaning enumerate to all levels. In the former case, when the enumeration reaches the specified depth, only the subdirectory name rooted at that level is listed, and none of its descendants is listed. When {var FILING.ENUMERATION.DEPTH} is {lisp T}, all files are listed, and no subdirectory names are listed. {var FILING.ENUMERATION.DEPTH} is initially {lisp T}. Independent of {var FILING.ENUMERATION.DEPTH}, a request to enumerate the top-level of a file server's hierarchy lists only the top level, i.e., assumes a depth of 1. For example, {lisp (DIRECTORY '{bracket PHYLEX:})} lists exactly the top-level directories of the server {lisp PHYLEX:}. }} }{end subsec NS Filing} {begin subsec SPP Stream Interface} {title SPP Stream Interface} {text This section describes the stream interface to the Sequenced Packet Protocol. SPP is the transport protocol for Courier, which in turn is the transport layer for Filing and Printing. {fndef {name SPP.OPEN} {args HOST SOCKET PROBEP NAME WHENCLOSEDFN} {text This function is used to open a bidirectional SPP stream. There are two cases: user and server. User: If {arg HOST} is specified, an SPP connection is initiated to {arg HOST}, an {lisp NSADDRESS} or string representing an NS address. If the socket part of the address is null (zero), it is defaulted to {arg SOCKET}. If both {arg HOST} and {arg PROBEP} are specified, then the connection is probed for a response before returning the stream; {lisp NIL} is returned if {arg HOST} doesn't respond. Server: If {arg HOST} is {lisp NIL}, a passive connection is created which listens for an incoming connection to local socket {arg SOCKET}. {fn SPP.OPEN} returns the input side of the bidirectional stream; the function {fn SPPOUTPUTSTREAM} is used to obtain the output side. The standard stream operations {fn BIN}, {fn READP}, {fn EOFP} (on the input side), and {fn BOUT}, {fn FORCEOUTPUT} (on the output side), are defined on these streams, as is {fn CLOSEF}, which can be applied to either stream to close the connection. {arg NAME} is a mnemonic name for the connection process, mainly useful for debugging. {arg WHENCLOSEDFN} is an optional function or list of functions to call when the stream is closed, either by the user or the server. }} {fndef {name SPPOUTPUTSTREAM} {args STREAM} {text Applied to the input stream of an SPP connection, this function returns the corresponding output stream. }} {vardef {name SPP.USER.TIMEOUT} {text Specifies the time, in milliseconds, to wait before deciding that a host isn't responding. }} {fndef {name SPP.SENDEOM} {args STREAM} {text Transmits the data buffered so far on this output stream, if any, with the End of Message bit set. If there is nothing buffered, sends a zero-length packet with End of Message bit set. }} {fndef {name SPP.DSTYPE} {args STREAM DSTYPE} {text Accesses the current datastream type of the connection. If {arg DSTYPE} is {lisp NIL}, returns the datastream type of the current packet being read. If {arg DSTYPE} is non-{lisp NIL}, sets the datastream type of all subsequent packets sent on this connection, until the next call to {fn SPP.DSTYPE}. Since this affects the current partially-filled packet, the stream should probably be flushed (via {fn FORCEOUTPUT}) before this function is called. }} {fndef {name SPP.EOMP} {args STREAM} {text This function returns {lisp T} or {lisp NIL} depending on whether or not an End of Message indication has been reached. This is only true after the last byte of data in the message has been read. By convention, {fn EOFP} is true of a stream that is at EOM. }} {fndef {name SPP.CLEAREOM} {args STREAM NOERRORFLG} {text Clears the End of Message indication on {arg STREAM}. This is necessary in order to read beyond the EOM. Causes an error if the stream is not currently at the End of Message, unless {arg NOERRORFLG} is true. }} {fndef {name SPP.SENDATTENTION} {args STREAM ATTENTIONBYTE} {text Sends an SPP "attention" packet, one with the Attention bit set and containing the single byte of data {arg ATTENTIONBYTE}. }} }{end subsec SPP Stream Interface} {begin subsec Courier Remote Procedure Call Protocol} {title Courier Remote Procedure Call Protocol} {text Courier{index Courier *Primary*} is the Xerox Network Systems Remote Procedure Call protocol. It uses the Sequenced Packet Protocol for reliable transport. Courier uses procedure call as a metaphor for the exchange of a request from a user process and its positive reply from a server process; exceptions or error conditions are the metaphor for a negative reply. A family of remote procedures and the errors they can raise constitute a remote program. A remote program generally represents a complete service, such as the Filing or Printing programs described earlier in this chapter. For more detail about Courier, the reader is referred to the published specification of the Courier protocol. The following documentation assumes some familiarity with the protocol. It describes how to define a Courier program and use it to communicate with a remote system element that implements a server for that program. This section does not discuss how to construct such a server. {begin subsec Defining Courier Programs} {title Defining Courier Programs} {text A Courier program definition is a file package type and command, {filecom COURIERPROGRAMS}{index COURIERPROGRAMS *primary* FileCom}. Thus, you can use {fn GETDEF}, {fn PUTDEF}, and {fn EDITDEF} to manipulate them, or use the file package command {lisp (COURIERPROGRAMS name1 name2 ...)} to save them. The function {fn COURIERPROGRAM} can be used to define a Courier program initially. {fndef {name COURIERPROGRAM} {args NAME {ellipsis}} {type NLAMBDA NOSPREAD} {text This function is used to define Courier programs. The syntax is {lispcode (COURIERPROGRAM {arg NAME} ({arg PROGRAMNUMBER} {arg VERSIONNUMBER}) . {arg DEFINITIONS})} The tail {arg DEFINITIONS} is a property list where the properties are selected from {lisp TYPES}, {lisp PROCEDURES}, {lisp ERRORS} and {lisp INHERITS}; the values are lists of pairs of the form ({arg LABEL} . {arg DEFINITION}). These are described in more detail as follows: The {lisp TYPES} section lists the symbolically-defined types used to represent the arguments and results of procedures and errors in this Courier program. Each element in this section is of the form ({arg TYPENAME} {arg TYPEDEFINITION}), e.g., {lisp (PRIORITY INTEGER)}. The {arg TYPEDEFINITION} can be a predefined type (see next section), another type defined in this {lisp TYPES} section, or a qualified typename taken from another Courier program; these latter are written as a dotted pair ({arg PROGRAMNAME} . {arg TYPENAME}). The {lisp PROCEDURES} section lists the remote procedures defined by this Courier program. A procedure definition is a stylized reduction of the Courier definition syntax defined in the Courier Protocol specification: {lispcode ({arg PROCEDURENAME} {arg NUMBER} {arg ARGUMENTS} RETURNS {arg RESULTTYPES} REPORTS {arg ERRORNAMES})} {arg ARGUMENTS} is a list of type names, one per argument to the remote procedure, or {lisp NIL} if the procedure takes no arguments. {arg RESULTTYPES} is a list of type names, one for each value to be returned. {arg ERRORNAMES} is a list of names of errors that can be raised by this procedure; each such error must be listed in the program's {lisp ERRORS} section. The atoms {lisp RETURNS} and {lisp REPORTS} are noise words to aid readability. The {lisp ERRORS} section lists the errors that can be raised by procedures in this program. An error definition is of the form {lisp ({arg ERRORNAME} {arg NUMBER} {arg ARGUMENTS})}, where {arg ARGUMENTS} is a list of type names, one for each argument, if any, reported by the error. The {lisp INHERITS} section is an optional list of other Courier programs, some of whose definitions are "inherited" by this program. More specifically, if a type, procedure or error referenced in the current program definition is not defined in this program, the system searches for a definition of it in each of the inherited programs in turn, and uses the first such definition found. The {lisp INHERITS} section is useful when defining variants of a given Courier program. For example, if one wanted to try out version 4 of Courier program {lisp BAR}, and version 4 differed from version 3 of program {lisp BAR} only in a small number of procedure or type definitions, one could define a program {Lisp NEWBAR} with an {lisp INHERITS} section of {lisp (BAR)} and only need to list the few changed definitions inside {Lisp NEWBAR}. }} }{end subsec Defining Courier Programs} {begin subsec Courier Type Definitions} {title Courier Type Definitions} {text This section describes how the Courier types described in the Courier Protocol document are expressed in a Lisp Courier program definition, and how values of each type are represented. Each type in a Courier program's {lisp TYPES} section must ultimately be defined in terms of one of the following "base" types, although the definition can be indirect through arbitrarily many levels. That is, a type can be defined in terms of any other type known by an extant Courier definition. The names of the base types are "global"; they need no qualification, nor do type names mentioned in the same Courier program. To refer to a type not defined in the same Courier program (or to any non-base type when there is no program context), one writes a {it Qualified name}, in the form ({arg PROGRAM . TYPE}). In general, a Qualified name is legal in any place that calls for a Courier type. {begin subsec Pre-defined Types} {title Pre-defined Types} {text Pre-defined (atomic) types are expressed as uppercase litatoms from the following set: {labeledlist {name {lisp BOOLEAN}} {item Values are represented by {lisp T} and {lisp NIL}.} {name {lisp INTEGER}} {item Values are represented as small integers in the range [-32768..32767].} {name {lisp CARDINAL}} {item Values are represented as small integers in the range [0..65535].} {name {lisp UNSPECIFIED}} {item Same as {lisp CARDINAL}.} {name {lisp LONGINTEGER}} {item Values are represented as {lisp FIXP}'s.} {name {lisp LONGCARDINAL}} {item Same as {lisp LONGINTEGER}. Note that Interlisp-D does not (currently) have a datatype that truly represents a 32-bit {it unsigned} integer.} {name {lisp STRING}} {item Values are represented as Lisp strings.} } In addition, the following types not in the document have been added for convenience: {labeledlist {name {lisp TIME}} {item Represents a date and time in accordance with the Network Time Standard. The value is a {lisp FIXP} such as returned by the function {fn IDATE}, and is encoded as a {lisp LONGCARDINAL}.} {name {lisp NSADDRESS}} {item Represents a network address. The value is an object of type {Lisp NSADDRESS}{index NSADDRESS} ({SectionRef Term NSADDRESS}), and is encoded as six items of type {lisp UNSPECIFIED}.} {name {lisp NSNAME}} {item Represents a three-part Clearinghouse name. The value is an object of type {Lisp NSNAME}{index NSNAME} ({SectionRef Term NSNAME}), and is encoded as three items of type {lisp STRING}.} {name {lisp NSNAME2}} {item Represents a two-part Clearinghouse name, i.e., a domain. The value is an object of type {Lisp NSNAME}{index NSNAME} ({SectionRef Term NSNAME}), and is encoded as two items of type {lisp STRING}.} } }{end subsec Pre-defined Types} {begin subsec Constructed Types} {title Constructed Types} {text Constructed Types are composite objects made up of elements of other types. They are all expressed as a list whose {fn CAR} names the type and whose remaining elements give details. The following are available: {begin labeledlist Enumeration of Constructed Types} {name {lisp (ENUMERATION ({arg NAME INDEX}) ... ({arg NAME INDEX}))}} {item Each {arg NAME} is an arbitrary litatom or string; the corresponding {arg INDEX} is its Courier encoding (a {lisp CARDINAL}). Values of type {lisp ENUMERATION} are represented as a {arg NAME} from the list of choices. For example, a value of type {lisp (ENUMERATION (UNKNOWN 0) (RED 1) (BLUE 2))} might be the litatom {lisp RED}.} {name {lisp (SEQUENCE {arg TYPE})}} {item A {lisp SEQUENCE} value is represented as a list, each element being of type {arg TYPE}. A {lisp SEQUENCE} of length zero is represented as {lisp NIL}. Note that there is no maximum length for a {lisp SEQUENCE} in the Lisp implementation of Courier.} {name {lisp (ARRAY {arg LENGTH TYPE})}} {item An {lisp ARRAY} value is represented as a list of {arg LENGTH} elements, each of type {arg TYPE}.} {name {lisp (CHOICE ({arg NAME INDEX TYPE}) ... ({arg NAME INDEX TYPE}))}} {item The {lisp CHOICE} type allows one to select among several different types at runtime; the {arg INDEX} is used in the encoding to distinguish the value types. A value of type {lisp CHOICE} is represented in Lisp as a list of two elements, ({arg NAME} {arg VALUE}). For example, a value of type {lispcode (CHOICE (STATUS 0 (ENUMERATION (BUSY 0) (COMPLETE 1))) (MESSAGE 1 STRING))} could be {lisp (STATUS COMPLETE)} or {lisp (MESSAGE "Out of paper.")}.} {name {lisp (RECORD ({arg FIELDNAME TYPE}) ... ({arg FIELDNAME TYPE}))}} {item Values of type {lisp RECORD} are represented as lists, with one element for each field of the record. The field names are not part of the value, but are included for documentation purposes.} {end labeledlist Enumeration of Constructed Types} For programmer convenience, there are two macros that allow Courier records to be constructed and dissected in a manner similar to Lisp records. These compile into the appropriate composites of {fn CONS}, {fn CAR} and {fn CDR}. {MacDef {name COURIER.CREATE} {args TYPE FIELDNAME ← VALUE {ellipsis} FIELDNAME ← VALUE} {text Creates a value of type {arg TYPE}, which should be a fully-qualified type name that designates a {lisp RECORD} type, e.g., {lisp (MAILTRANSPORT . POSTMARK)}. Each {arg FIELDNAME} should correspond to a field of the record, and all fields must be included. Each {arg VALUE} is evaluated; all other arguments are not. The assignment arrows are for readability, and are optional. }} {MacDef {name COURIER.FETCH} {args TYPE FIELD OBJECT} {text Analogous to the Record Package operator {lisp fetch}. Argument {arg TYPE} is as with {mac COURIER.CREATE}; {arg FIELD} is the name of one of its fields. {mac COURIER.FETCH} extracts the indicated field from {arg OBJECT}. For readability, the noiseword "{lisp of}" may be inserted between {arg FIELD} and {arg OBJECT}. Only the argument {arg OBJECT} is evaluated. }} For example, if the program {lisp CLEARINGHOUSE} has a type declaration {lispcode (USERDATA.VALUE (RECORD (LAST.NAME.INDEX CARDINAL) (FILE.SERVICE STRING))),} then the expression {lispcode (SETQ INFO (COURIER.CREATE (CLEARINGHOUSE . USERDATA.VALUE) LAST.NAME.INDEX ← 12 FILE.SERVICE ← "Phylex:PARC:Xerox")} would set the variable {lisp INFO} to the list {lisp (12 "Phylex:PARC:Xerox")}. The expression {lispcode (COURIER.FETCH (CLEARINGHOUSE . USERDATA.VALUE) FILE.SERVICE of INFO)} would produce {lisp "Phylex:PARC:Xerox"}. }{end subsec Constructed Types} {begin subsec User Extensions to the Type Language} {title User Extensions to the Type Language} {text The programmer can add new base types to the Courier language by telling the system how to read and write values of that type. The programmer chooses a name for the type, and gives the name a {prop COURIERDEF} property. The new name can then be used anywhere that the type names listed in the previous sections, such as {lisp CARDINAL}, can be used. Such extensions are useful for user-defined objects, such as datatypes, that are not naturally represented by any predefined or constructed type. The {lisp NSADDRESS} and {lisp NSNAME} Courier types are defined by this mechanism. {PropDef {name COURIERDEF} {text The format of the {prop COURIERDEF} property is a list of up to four elements, ({arg READFN WRITEFN LENGTHFN WRITEREPFN}). The first two elements are required; if the latter two are omitted, the system will simulate them as needed. The elements are as follows: {begin labeledlist COURIERDEF fields} {label {arg READFN}} {text This is a function of three arguments, ({arg STREAM PROGRAM TYPE}). The function is called by Courier when it needs to read a value of this type from {arg STREAM} as part of a Courier transaction. The function reads and returns the value from {arg STREAM}, possibly using some of the functions described in {SectionRef fn COURIER.READ}. {arg PROGRAM} and {arg TYPE} are the name of the Courier program and the type. In the case of atomic types, {arg TYPE} is a litatom, and is provided for type discrimination in case the programmer has supplied a single reading function for several different types. In the case of constructed types, {arg TYPE} is a list, {fn CAR} of which is the type name.} {label {arg WRITEFN}} {text This is a function of four arguments, ({arg STREAM VALUE PROGRAM TYPE}). The function is called by Courier when it needs to write {arg VALUE} to {arg STREAM}. {arg PROGRAM} and {arg TYPE} are as with the reading function. The function should write {arg VALUE} on {arg STREAM}. The result returned from this function is ignored.} {label {arg LENGTHFN}} {text This function is called when Courier wants to write a value of this type in the form {lisp (SEQUENCE UNSPECIFIED)}, and then only if the {arg WRITEREPFN} is omitted. The function is of three arguments, ({arg VALUE PROGRAM TYPE}). It should return, as an integer, the number of 16-bit words that the {arg WRITEFN} would require to write out this value. If values of this type are all the same length, the {arg LENGTHFN} can be a simple integer instead of a function. See discussion of {fn COURIER.WRITE.SEQUENCE.UNSPECIFIED} ({SectionRef fn COURIER.WRITE.SEQUENCE.UNSPECIFIED}.} {label {arg WRITEREPFN}} {Text This function is called when Courier wants to write a value of this type in the form {lisp (SEQUENCE UNSPECIFIED)}. The function takes the same arguments as the {arg WRITEFN}, but must write the value to the stream preceded by its length. If this function is omitted, Courier invokes the {arg LENGTHFN} to find out how long the value is, and then invokes the {arg WRITEFN}. If the {arg LENGTHFN} is omitted, Courier invokes the {arg WRITEFN} on a scratch stream to find out how long the value is.} {end labeledlist COURIERDEF fields} }} }{end subsec User Extensions to the Type Language} }{end subsec Courier Type Definitions} {begin subsec Performing Courier Transactions} {title Performing Courier Transactions} {text The normal use of Courier is to open a connection with a remote system element using {fn COURIER.OPEN}, perform one or more remote procedure calls using {fn COURIER.CALL}, then close the connection with {fn CLOSEF}. {fndef {name COURIER.OPEN} {args HOSTNAME SERVERTYPE NOERRORFLG NAME WHENCLOSEDFN} {text Opens a Courier connection to the Courier socket on {arg HOST}, and returns an SPP stream that can be passed to {fn COURIER.CALL}. {arg HOST} can be an NS address, or a symbolic Clearinghouse name in the form of a string, litatom or {lisp NSNAME}. In the case of a symbolic name, {arg SERVERTYPE} specifies the Clearinghouse property under which the server's address may be found; normally, this is {lisp NIL}, in which case the {lisp ADDRESS.LIST} property is used. If {arg NOERRORFLG} is true, {lisp NIL} is returned if a connection cannot be made, or the server supports the wrong version of Courier. The Courier connection process is named {arg NAME}, if specified. {arg WHENCLOSEDFN} is a function of one argument, the Courier stream, that will be called when the connection is closed, either by user or server. }} {fndef {name COURIER.CALL} {args STREAM PROGRAM PROCEDURE ARG{sub 1} {ellipsis} ARG{sub N} NOERRORFLG} {type NOSPREAD} {text This function calls the remote procedure {arg PROCEDURE} of the Courier program {arg PROGRAM}. {arg STREAM} is the stream returned by {fn COURIER.OPEN}. The arguments should be Lisp values appropriate for the Courier types of the corresponding formal parameters of the procedure. There must be the same number of actual and formal arguments. If the procedure call is successful, Courier returns the result(s) of the call as specified in the {lisp RETURNS} section of the procedure definition. If there is only a single result, it is returned directly, otherwise a list of results is returned. Procedures that take a Bulk Data argument (source or sink) are treated specially; see {SectionRef subsec Using Bulk Data Transfer}. If the procedure call results in an error, one of three possible courses is available. The default behavior is to cause a Lisp error. To suppress the error, an optional keyword can be appended to the argument list, as if an extra argument. This {arg NOERRORFLG} argument can be the atom {lisp NOERROR}, in which case {lisp NIL} is returned as the result of the call. If {arg NOERRORFLG} is {lisp RETURNERRORS}, the result of the call is a list {lisp (ERROR {arg ERRORNAME . ERRORARGS})}. If the failure was a Courier Reject, rather than Error, then {arg ERRORNAME} is the atom {lisp REJECT}. }} Examples: {lispcode (COURIERPROGRAM PERSONNEL (17 1) TYPES ((PERSON.NAME (RECORD (FIRST.NAME STRING) (MIDDLE MIDDLE.PART) (LAST.NAME STRING))) (MIDDLE.PART (CHOICE (NAME 0 STRING) (INITIAL 1 STRING))) (BIRTHDAY (RECORD (YEAR CARDINAL) (MONTH STRING) (DAY CARDINAL)))) PROCEDURES ((GETBIRTHDAY 3 (PERSON.NAME) RETURNS (BIRTHDAY) REPORTS (NO.SUCH.PERSON))) ERRORS ((NO.SUCH.PERSON 1)) )} This expression defines {lisp PERSONNEL} to be Courier program number 17, version number 1. The example defines three types, {lisp PERSON.NAME}, {lisp MIDDLE.PART} and {lisp BIRTHDAY}, and one procedure, {lisp GETBIRTHDAY}, whose procedure number is 3. The following code could be used to call the remote {lisp GETBIRTHDAY} procedure on the host with address {lisp HOSTADDRESS}. {lispcode (SETQ STREAM (COURIER.OPEN HOSTADDRESS)) (PROG1 (COURIER.CALL STREAM 'PERSONNEL 'GETBIRTHDAY (COURIER.CREATE (PERSONNEL . BIRTHDAY) FIRST.NAME ← "Eric" MIDDLE ← '(INITIAL "C") LAST.NAME ← "Cooper")) (CLOSEF STREAM)) } {fn COURIER.CALL} in this example might return a value such as {lisp (1959 "January" 10)}. {begin subsec Expedited Procedure Call} {title Expedited Procedure Call} {text Some Courier servers support "Expedited Procedure Call", which is a way of performing a single Courier transaction by a Packet Exchange protocol, rather than going to the expense of setting up a full Courier connection. Expedited calls must have no bulk data arguments, and their arguments and results must each fit into a single packet. {fndef {name COURIER.EXPEDITED.CALL} {args ADDRESS SOCKET# PROGRAM PROCEDURE ARG{sub 1} {ellipsis} ARG{sub N} NOERRORFLG} {type NOSPREAD} {text Attempts to perform a Courier call using the Expedited Procedure Call. {arg ADDRESS} is the NS address of the remote host and {arg SOCKET#} is the socket on which it is known to listen for expedited calls. The remaining arguments are exactly as with {fn COURIER.CALL}. If the arguments to the procedure do not fit in one packet, or if there is no response to the call, or if the call returns the error {lisp USE.COURIER} (which must be defined by exactly that name in {arg PROGRAM}), then the call is attempted instead by the normal, non-expedited method{emdash}a Courier connection is opened with {arg ADDRESS}, and {fn COURIER.CALL} is invoked on the arguments given. }} }{end subsec Expedited Procedure Call} {begin subsec Expanding Ring Broadcast} {title Expanding Ring Broadcast} {text "Expanding Ring Broadcast" is a method of locating a server of a particular type whose address is not known in advance. The system broadcasts some sort of request packet on the directly-connected network, then on networks one hop away, then on networks two hops away, etc., until a positive response is received. For use in locating a server for a particular Courier program, a stylized form of Expanding Ring Broadcast is defined. The request packet is essentially the call portion of an Expedited Procedure Call for some procedure defined in the program. The response packet is a Courier response, and typically contains at least the server's address as the result of the call. The designer of the protocol must, of course, specify which procedure to use in the broadcast (usually it is procedure number zero) and on what socket the server should listen for broadcasts. {fn START.CLEARINGHOUSE} uses this procedure to locate the nearest Clearinghouse server. {fndef {name COURIER.BROADCAST.CALL} {args DESTSOCKET# PROGRAM PROCEDURE ARGS RESULTFN NETHINT MESSAGE} {text Performs an expanding ring broadcast for servers willing to implement {arg PROCEDURE} in Courier program {arg PROGRAM}. {arg DESTSOCKET#} is the socket on which such servers of this type are known to listen for broadcasts, typically the same socket on which they listen for expedited calls. {arg ARGS} is the argument list, if any, to the procedure (note that it is not spread, unlike with {fn COURIER.CALL}). If a host responds positively, then the function {arg RESULTFN} is called with one argument, the Courier results of the procedure call. If {arg RESULTFN} returns a non-null value, the value is returned as the value of {fn COURIER.BROADCAST.CALL} and the search stops there; otherwise, the search for a responsive host continues. If {arg RESULTFN} is not supplied (or is {lisp NIL}), then the results of the procedure call are returned directly from {fn COURIER.BROADCAST.CALL}; i.e., {arg RESULTFN} defaults to the identity function. {arg NETHINT}, if supplied, is a net number or list of net numbers as a hint concerning which net(s) to try first before performing a pure expanding-ring broadcast. If {arg MESSAGE} is non-NIL, it is a description (string) of what the broadcast is looking for, to be printed in the prompt window to inform the user of what is happening. For example, {fn START.CLEARINGHOUSE} passes in the message {lisp "Clearinghouse servers"} and the hint {var CH.NET.HINT}. }} }{end subsec Expanding Ring Broadcast} {begin subsec Using Bulk Data Transfer} {title Using Bulk Data Transfer} {text When a Courier program needs to transfer an arbitrary amount of information as an argument or result of a Courier procedure, the procedure is usually defined to have one argument of type "Bulk Data". The argument is a "source" if it is information transferred from caller to server (as though a procedure argument), a "sink" if it is information transferred from server to caller (as though a procedure result). These two "types" are indicated in a Courier procedure's formal argument list as {lisp BULK.DATA.SOURCE} and {lisp BULK.DATA.SINK}, respectively. A Courier procedure may have at most one such argument. In a Courier call, the bulk data is transmitted in a special way, between the arguments and the results. There are two basic ways to handle this in the call. The caller can specify how the bulk data is to be interpreted (how to read or write it), or the caller can request to be given a bulk data stream as the result of the Courier call. The former is the preferred way; both are described below. In the first method, the caller passes as the actual argument to the Courier call (i.e., in the position in the argument list occupied by {lisp BULK.DATA.SOURCE} or {lisp BULK.DATA.SINK}) a function to perform the transfer. Courier sets up the transaction, then calls the supplied function with one argument, a stream on which to write (if a source argument) or read (if a sink) the bulk data. If the function returns normally, the Courier transaction proceeds as usual; if it errors out, Courier sends a Bulk Data Abort to abort the transaction. In the case of a sink argument, if the value returned from the sink function is non-{lisp NIL}, it is returned as the result of {fn COURIER.CALL}; otherwise, the result of {fn COURIER.CALL} is the usual procedure result, as declared in the Courier program. For convenience, a Bulk Data sink argument to a Courier call can be specified as a fully qualified Courier type, e.g., {lisp (CLEARINGHOUSE . NAME)}, in which case the Bulk Data stream is read as a "stream of" that type (see {fn COURIER.READ.BULKDATA}, below). The second method for handling bulk data is to pass {lisp NIL} as the bulk data "argument" to {fn COURIER.CALL}. In this case, Courier sets up the call, then returns a stream that is open for {lisp OUTPUT} (if a source argument) or {lisp INPUT} (if a sink). The caller is responsible for transferring the bulk data on the stream, then closing the stream to complete the transaction. The value returned from {fn CLOSEF} is the Courier result. This method is required if the caller's control structure is open-ended in a way such that the bulk data cannot be transferred within the scope of the call to {fn COURIER.CALL}. In either method, the stream on which the bulk data is transferred is a standard Interlisp stream, so {fn BIN}, {fn BOUT}, {fn COPYBYTES} are all appropriate. Many Courier programs define a "Stream of <type>" as a means of transferring an arbitrary number of objects, all of the same type. Although this is typically specified formally in the printed Courier documentation as a recursive definition, the recursion is in practice unnecessary and unwieldy; instead, the following function should be used. {fndef {name COURIER.READ.BULKDATA} {args STREAM PROGRAM TYPE} {text Reads from {arg STREAM} a "Stream of {arg TYPE}" for Courier program {arg PROGRAM}, and returns a list of the objects read. Passing {lisp (X . Y)} as the bulk argument to a Courier call is thus equivalent to passing the function {lisp (LAMBDA (STREAM) (COURIER.READ.BULKDATA STREAM X Y))}. }} }{end subsec Using Bulk Data Transfer} {begin subsec Courier Subfunctions for Data Transfer} {title Courier Subfunctions for Data Transfer} {text The following functions are of interest to those who transfer data in Courier representations, e.g., as part of a function to implement a user-defined Courier type. {fndef {name COURIER.READ} {args STREAM PROGRAM TYPE} {text Reads from the stream {arg STREAM} a Courier value of type {arg TYPE} for program {arg PROGRAM}. If {arg TYPE} is a predefined type, then {arg PROGRAM} is irrelevant; otherwise, it is required in order to qualify {arg TYPE}. }} {fndef {name COURIER.WRITE} {args STREAM ITEM PROGRAM TYPE} {text Writes {arg ITEM} to the stream {arg STREAM} as a Courier value of type {arg TYPE} for program {arg PROGRAM}. }} {fndef {name COURIER.READ.SEQUENCE} {args STREAM PROGRAM BASETYPE} {text Reads from the stream {arg STREAM} a Courier value {lisp SEQUENCE} of values of type {arg TYPE} for program {arg PROGRAM}. Equivalent to {lisp (COURIER.READ {arg STREAM} {arg PROGRAM} (SEQUENCE {arg BASETYPE}))}. }} {fndef {name COURIER.WRITE.SEQUENCE} {args STREAM ITEM PROGRAM BASETYPE} {text Equivalent to {lisp (COURIER.WRITE {arg STREAM} {arg ITEM} {arg PROGRAM} (SEQUENCE {arg BASETYPE}))}. }} Some Courier programs traffic in values whose interpretation is left up to the clients of the program; the values are transferred in Courier transactions as values of type {lisp (SEQUENCE UNSPECIFIED)}. For example, the Clearinghouse program transfers the value of a database property as an uninterpreted sequence, leaving it up to the caller, who knows what type of value the particular property takes, to interpret the sequence of raw bits as some other Courier representation. The following functions are useful when dealing with such values. {fndef {name COURIER.WRITE.REP} {args VALUE PROGRAM TYPE} {text Produces a list of 16-bit integers, i.e., a value of type {lisp (SEQUENCE UNSPECIFIED)}, that represents {arg VALUE} when interpreted as a Courier value of type {arg TYPE} in {arg PROGRAM}. Examples: {lispcode (COURIER.WRITE.REP T NIL 'BOOLEAN) {rm =>} (1)} {lispcode (COURIER.WRITE.REP "Thing" NIL 'STRING) {rm =>} (5 52150Q 64556Q 63400Q)} {lispcode (COURIER.WRITE.REP '(10 25) NIL '(SEQUENCE INTEGER)) {rm =>} (2 10 25)} }} {fndef {name COURIER.READ.REP} {args LIST.OF.WORDS PROGRAM TYPE} {text Interprets {arg LIST.OF.WORDS}, a list of 16-bit integers, as a Courier object of type {arg TYPE} in the Courier program {arg PROGRAM}. }} {fndef {name COURIER.WRITE.SEQUENCE.UNSPECIFIED} {args STREAM ITEM PROGRAM TYPE} {text Writes to the stream {arg STREAM} in the form {lisp (SEQUENCE UNSPECIFIED)} the object {arg ITEM}, whose value is really a Courier value of type {arg TYPE} for program {arg PROGRAM}. Equivalent to, but usually much more efficient than, {lisp (COURIER.WRITE {arg STREAM} (COURIER.WRITE.REP {arg ITEM} {arg PROGRAM} {arg TYPE}) NIL '(SEQUENCE UNSPECIFIED))}. }} }{end subsec Courier Subfunctions for Data Transfer} }{end subsec Performing Courier Transactions} {Begin Note} will not document this because of possible sensitivity: {fndef {name COURIERTRACE} {args FLG REGION} {text This function controls the tracing of Courier remote procedure calls. It is similar to {fn PUPTRACE} and {fn XIPTRACE}, but operates at the call/return level rather than the packet level. }} {End Note} }{end subsec Courier Remote Procedure Call Protocol} }{End SubSec Higher-level NS Protocol Functions}