. << PupMore.pub -- continuation of PupPackage.pub >>
.sec Rendezvous/Termination Protocol Interface

The RTP module (file PupRTP) contains primitives for establishing and breaking connections with foreign processes according to the Rendezvous/Termination Protocol.

The local end of a connection is maintained within the confines of an RTPSoc, an RTP socket structure (defined in PupRTP.decl).  This begins with a level 1 Pup socket (PupSoc), but includes the following additional information:

.begin indent 4,24 tabs 25
ctx\A pointer to the background context maintaining the connection.

state\The state of the connection (see below).

connID\The connection ID (see "Pup Specifications").

rtpOtherPupProc\A procedure called upon receipt of any Pup that is not part of the Rendezvous/Termination Protocol.

rtpOtherTimer\A timer for use by higher levels of protocol.

rtpOtherTimerProc\A procedure called when rtpOtherTimer expires.

There is some other information (wasListening, rtpTimer) used by the RTP module but not of interest to external programs.

At a given moment, an RTPSoc may be in one of a number of "states".  A detailed explanation of the meanings of these states may be found in the memo "Pup Connection State Diagram" (file RTPStates.press).

.begin indent 4,19 tabs 20
stateClosed\No connection exists:  either none has ever been created or a previously existing connection has terminated.

stateRFCOut\The local process has initiated a request for connection (RFC) to some foreign process.  A reply is expected from the remote process.

stateListening\The local process is "listening" for an RFC from any foreign process.

stateOpen\The connection is considered by both parties to have been established.  What the cooperating processes do with this connection is a matter of higher-level protocol (e.g., BSP).

stateEndIn\The foreign process has requested that the connection be terminated, and is awaiting a confirmation from the local process.

stateEndOut\The local process has requested that the connection be terminated, and is awaiting a confirmation from the foreign process.

stateDally\A transitory state having to do with the termination handshake (see "Pup Specifications").

stateAbort\The connection has been aborted abnormally by the foreign process.

An RTPSoc is created by calling OpenRTPSocket, which performs various initialization, creates a background process to manage the connection, and interacts with some foreign process in one of three ways (see below) to open a connection.  Once the connection is open, the RTP background process monitors the socket for arrival of Pups requesting that the connection be closed or aborted, and updates the state of the socket appropriately.  The local process may also request explicitly that the connection be terminated, by calling CloseRTPSocket.

The procedures defined in the RTP module are the following:

.begin indent 4,4
.once indent 0,10 nojust
OpenRTPSocket(soc, ctxQ [pupCtxQ], openMode [modeInitAndWait], connID [random], otherProc [DefaultOtherPupProc], timeout [defaultTimeout], zone [pupZone]) = true or false
Causes an RTP socket to be created and optional interactions with a foreign process to be initiated.  "soc" is a block of length lenRTPSoc which must already have been initialized as a level 1 socket (PupSoc) by a prior call to OpenLevel1Socket.  (An external static "lRTPSoc" exists whose value is the length of an RTPSoc in words.)  Both the local and foreign port addresses (the "lclPort" and "frnPort" fields in the PupSoc) must be completely established, unless "openMode" is "listenAndWait" or "listenAndReturn", in which case only the local socket number (soc>>PupSoc.lclPort.socket) need be established.

"ctxQ" is a context queue to which a context created by this procedure may be appended.  It defaults to pupCtxQ (the "ctxQ" passed to InitPupLevel1).

"openMode" specifies the manner in which the connection is to be opened.  If it is "modeInitAndWait", a request for connection to the foreign process is initiated, and OpenRTPSocket then blocks until either the answering RFC is received and the connection's state becomes open (in which case it returns true) or an error occurs (in which case the RTPSoc is closed and OpenRTPSocket returns false).  If it is "modeInitAndReturn", the request is initiated in a similar manner, but then OpenRTPSocket returns true immediately and it is the caller's responsibility to monitor the subsequent state of the connection.

If "openMode" is "modeListenAndWait", the socket is placed in a "listening" state.  When a request for connection is received from some foreign process, a reply is generated and the connection becomes open, and OpenRTPSocket returns true.  If the mode is "modeListenAndReturn", OpenRTPSocket returns true immediately and it is the caller's responsibility to monitor the subsequent state of the connection.

If "openMode" is "modeImmediateOpen", the socket is immediately placed in the open state (it is assumed that the caller has already performed a rendezvous with the foreign process in some other manner) and OpenRTPSocket returns true.

"connID" is a pointer to a two-word vector specifying the connection ID (see "Pup Specifications").  If not specified, a connection ID is chosen at random.  "connID" need never be specified if "openMode" is one of the listening modes.

"otherProc" is a procedure to be called when a non-RTP Pup is received by the socket.  This will be described in more detail later.  If not specified, "otherProc" defaults to DefaultOtherPupProc, a procedure that simply releases any PBI it is passed (one may change the default by clobbering the DefaultOtherPupProc static with something else).

"timeout" specifies the maximum time OpenRTPSocket will wait (if "openMode" is "modeInitAndWait" or "modeListenAndWait") before timing out and returning false.  It (and all other "timeout" arguments in the Pup package) is in units of 10 milliseconds, with a maximum legal value of 2↑15 (a little over 5 minutes), according to the conventions established in the Timer Package.  If unspecified, "timeout" defaults to "defaultTimeout", a static defined in this module, whose value in the released package is 6000 (i.e., 60 seconds;  this is set by the parameter "defaultDefaultTimeout" in PupParams.decl).

"zone" is a free-storage zone from which a context block (of size rtpStackSize) may be allocated.  If it is not specified, pupZone (the "zone" passed to InitPupLevel1) is used.  Note:  OpenRTPSocket calls InitializeContext, so the ContextInit module must be resident (despite what the Context Package writeup says).

.once indent 0
CloseRTPSocket(soc, timeout [...defaultTimeout]) = true or false
Requests that the connection rooted in the RTPSoc "soc" be terminated.  If "timeout" is nonzero, a normal termination is attempted if possible;  if zero (or the attempted normal termination times out), the connection is aborted (terminated abnormally).  When the connection has been closed, the context created by OpenRTPSocket is destroyed and returned to the zone from which it was allocated.  CloseRTPSocket then returns true if the connection was terminated normally and false if abnormally.  The level 1 PupSoc pointed to by "soc" still exists, and it is the caller's responsibility to dispose of it appropriately (generally by calling CloseLevel1Socket).

The process created by OpenRTPSocket (called RTPSocketProcess) has several responsibilities.  First, all Pups arriving on the socket's iQ are dequeued and inspected.  Ones whose types are part of the Rendezvous/Termination protocol are processed internally.  All protocol interactions (including replies, retransmissions, and local state changes) are handled automatically.

Received Pups that are not part of the RTP are passed to the "rtpOtherPupProc" procedure, which is initialized to the "otherProc" argument in OpenRTPSocket.  More specifically, the statement

.skip 1
.once nofill indent 4

is executed, and it is up to the called procedure to appropriately process and dispose of the PBI.  Since this call is made within the context of the RTPSocketProcess, which has only "rtpStackSize" (130 as released) words of stack space, the called procedure cannot make heavy demands on the stack without risk of stack overflow.  One might increase rtpStackSize (a static defined in this module, whose initial value is given in PupParams.decl as "defaultRTPStackSize"), but the safest course of action is for the called procedure simply to enqueue the PBI on some queue looked at by another process with more stack space available to it.  (One should note, however, that the "rtpOtherPupProc" procedure defined by the BSP module, to be described in the next section, manages to do all its work--a significant amount--without overflowing the RTP process's stack.  The main potential pitfall is in calling system procedures such as Ws that require very large amounts of stack space in some cases.)

"Abort" and "Error" Pups, while handled by RTPSocketProcess (for their effects on the socket's state), are also passed on to the "rtpOtherPupProc" procedure, for purposes such as displaying the Pup's text to the user.  The RTP module distinguishes between "fatal" and "non-fatal" sub-types of Errors, treating the former the same as an Abort (thereby placing the connection in the "Abort" state) and ignoring the latter;  both kinds, however, are passed to "rtpOtherPupProc".

Additionally, the RTPSocketProcess checks for expiration of a timer called "rtpOtherTimer" in the RTPSoc.  If it expires, the procedure given in "rtpOtherTimerProc" is called, with the socket as its argument.  This facility is used in the BSP module, which also requires the ability to do asynchronous processing.  "rtpOtherTimerProc" is initialized to Noop when OpenRTPSocket is called.

The following miscellaneous procedures defined in the RTP module are of possible interest to callers:

.begin indent 4,4
.once indent 0
RTPFilter(pbi, checkFrnPort, checkID) = true or false
Does selective filtering of "pbi" against parameters in the socket to which the PBI is assigned, and returns true if the PBI is accepted and false if rejected.  First, broadcast Pups (destination host zero) are always rejected.  Then, if checkFrnPort is true, the source port address of the PBI is checked for equality with the foreign port address given in the socket.  Finally, if checkID is true, the Pup ID in the PBI is checked for equality with the connection ID in the socket.

.once indent 0
CompleteRTPPup(pbi, type, length)
Stores "type" and "length" in the respective fields of the Pup, copies the connection ID from the socket to the Pup, and finally calls CompletePup(pbi) to send it on its way.

.sec Byte Stream Protocol Interface

The BSP module (files PupBSPStreams, PupBSPProt, and PupBSPa) contains procedures for sending and receiving error-free, flow-controlled byte streams to and from a foreign process, and for dealing with the other primitives defined by the BSP (namely Marks and Interrupts).

A process's interface to the BSP module is by way of a BSPSoc, a BSP socket structure, which is a further extension of an RTPSoc (which, it will be recalled, is an extension of a PupSoc).  The BSPSoc contains a large amount of additional information, most of which fortunately is not of interest to external programs.  The items that are of interest are the following:

.begin indent 4,24 tabs 25
bspStatus\A word containing various status bits, including the following three:

markPending\A Mark has been encountered while reading the incoming byte stream.  Further attempts at input (via Gets or BSPReadBlock) will fail until this bit is cleared (either explicitly or by calling BSPGetMark).

interruptIn\An Interrupt has been received.  If the caller depends on this bit for noticing the arrival of Interrupts, then it must clear the bit explicitly after doing so.  Interrupts arriving in close succession will not be distinguishable as separate events unless they are intercepted via the "bspOtherPupProc" mechanism, described later.

noInactivityTimeout\This flag, normally false, may be set to true to disable an automatic timeout mechanism that aborts the BSP connection if the foreign process does not respond to any BSP protocol requests for two minutes.  The purpose of this is to detect that a connection has died (due to network failure or the foreign process crashing).  Being able to disable this timeout mechanism is handy during debugging.

bspOtherPupProc\A procedure called upon receipt of any Pup not part of the BSP (or RTP).

bspStr\A block containing a BSPStr, a BSP stream structure.  This contains the dispatches for interfacing to the operating system's generic stream-handling procedures (Gets, Puts), plus some information specific to the BSP stream.

A BSP stream is created by first opening a connection to a foreign process (by means of the RTP), then calling the following procedure:

.begin indent 4,4
.once indent 0
CreateBSPStream(soc) = str
Creates and initializes a BSP socket, and returns a pointer to the stream block within it.  "soc" must point to a region of length lenBSPSoc (which is the value of an external static lBSPSoc), and it must already support one end of an open RTP connection (by having been passed to OpenLevel1Socket and then OpenRTPSocket).  If the state of the connection is not stateOpen or stateEndIn, CreateBSPStream returns zero.  Otherwise, the stream is completely initialized and the pointer to it is returned.  See the sample program at the end of this document for an example of the proper sequence of operations for opening a BSP stream from scratch.

All the generic stream procedures (Gets, Puts, etc.) must be passed "str" as an argument, as should the procedures BSPReadBlock and BSPWriteBlock.  However, all other operations on the socket (including specialized BSP functions such as BSPGetMark) must be passed "soc".  When necessary, "str" and "soc" may be computed from each other by the following statements:

.skip 1
.begin nofill indent 4
str = soc+offsetBSPStr
soc = str-offsetBSPStr

where offsetBSPStr is an external static defined in the BSP package.

The defined generic stream procedures are as follows.  The descriptions of Gets and Puts assume that the default stream error-handling procedure (invoked by Errors(str, ec)) is in use;  the real truth appears in the description of Errors.

.begin indent 4,4
.once indent 0
Gets(str, timeout [...-1]) = byte or -1
Attempts to return the next byte from the BSP stream "str";  returns -1 on any failure.  A failure will result if the connection has become closed or a Mark has been encountered in the incoming stream.  If "timeout" is -1 (the default), Gets waits indefinitely for data to arrive (or some failure condition to arise);  if other than -1, it waits up to "timeout" (units of 10 milliseconds) and then gives the failure return.

Note that occurrence of the timeout condition does not imply anything about the health of the connection; the timeout feature is provided entirely for the caller's convenience, and has nothing to do with the internal connection inactivity timeout.  If the connection fails, the connection state (soc>>RTPSoc.state) will change to something other than stateOpen.

.once indent 0
Puts(str, byte, timeout [...-1]) = true or false
Attempts to output "byte" to the BSP stream "str";  returns true on success and false on failure.  A failure will result if the connection has become closed or the operation times out.  The "timeout" is defined as for Gets, with -1 meaning wait indefinitely.  Note that in general, outputting a byte to a BSP stream merely causes that byte to be appended to a partially-constructed Pup in memory;  only when a Pup is filled up is any packet actually sent over the net.  BSPForceOutput (described below) must be called to cause a partially-filled Pup to be closed out and transmitted immediately.

.once indent 0
Endofs(str) = true or false
Returns true if there is not presently any data to be read from the BSP stream "str" or a Mark has been encountered.  Note that this definition of Endofs is analogous to that for "keys" as opposed to that for disk files;  i.e., so long as the connection is still open, Endofs(str) being true says only that there is not now any data to be read, not that there won't be data at some time in the future.

.once indent 0
Closes(str) = true or false
Closes the BSP stream "str" and destroys the associated socket, as detailed in the description of CloseBSPSocket (below).

.once indent 0
Errors(str, ec) = value
The stream error procedure (which is initialized to BSPErrors by CreateBSPStream) is called under various error conditions arising in Gets and Puts.  The error code "ec" will be one of the following:

.begin indent 4,24 tabs 25
ecBadStateForGets\Gets has failed because the connection is no longer open.  This can occur either because an Abort or fatal Error is received or because the connection's inactivity timeout (2 minutes) expires.  (The timeout may be disabled for debugging purposes by setting soc>>BSPSoc.noInactivityTimeout to true.)

ecGetsTimeout\Gets has failed because no data became available for reading within the timeout specified in the call to Gets.

ecMarkEncountered\Gets has failed because it has encountered a Mark in the stream.

ecBadStateForPuts\Puts has failed because the connection is no longer open.

ecPutsTimeout\Puts has failed because it was not possible to output the byte within the timeout specified in the call to Puts.

In each case, the Gets or Puts returns the result of calling Errors with the corresponding error code.  The default Errors procedure returns -1 when passed any of the Gets error codes and false when passed one of the Puts error codes, thereby obtaining the failure behavior presented earlier in the descriptions of Gets and Puts.

The remaining procedures operate on a "soc" (BSPSoc) rather than a "str", since they are peculiar to BSP.

.begin indent 4,4
.once indent 0
CloseBSPSocket(soc, timeout [...defaultTimeout]) = true or false
Closes the connection and destroys the BSPSoc pointed to by "soc".  First, if the connection is still in a reasonable state, any pending output is transmitted;  CloseBSPSocket will wait up to "timeout" for successful acknowledgment of this data.  Next, the connection is terminated by a call to CloseRTPSocket (the description of which includes the interpretation of "timeout").  Then all PBIs still residing on the BSPSoc's various queues are released.  Finally, the socket is destroyed by a call to CloseLevel1Socket.  The result returned is true if the connection was closed normally, false if abnormally.

.once indent 0
BSPGetMark(soc) = byte
Returns the value of the pending Mark byte in the incoming stream, and clears the markPending flag so as to permit future calls to Gets to read data past the Mark in the stream.  This procedure will call SysErr(soc, ecBadBSPGetMark) if a Mark has not in fact been encountered.

.once indent 0,10 nojust
BSPPutMark(soc, markByte, timeout [...-1], sendNow [false]) = true or false
Inserts the specified "markByte" into the outgoing stream.  Calling this procedure causes all data up to and including the Mark byte to actually be transmitted immediately.  The interpretation of "timeout" and the result returned by the procedure are the same as for Puts; "sendNow" is described under BSPForceOutput (below).

.once indent 0
BSPForceOutput(soc, sendNow [false])
Forces any partially-filled output Pup to be transmitted immediately.  This procedure will never block.  If "sendNow" is true, the BSP package will elicit an immediate acknowledgment, thereby expediting the process of flushing the local output queue of unacknowledged Pups.  The caller should set this argument to true when it expects not to send more data for a while, particularly if it is about to turn around and receive some data over the same stream.

.once indent 0,10 nojust
BSPPutInterrupt(soc, code, string, timeout [...-1]) = true or false
Generates a BSP Interrupt Pup (see "Pup Specifications") using the specified "code" for the Interrupt Code and "string" for the Interrupt Text.  The procedure returns true unless it failed to send the Interrupt due either to the connection no longer being open or to exhausting the specified "timeout".

The BSP module accomplishes much of its work as a result of being given control by the socket's RTPSocketProcess context through two paths:  the "rtpOtherPupProc" procedure, called when a non-RTP Pup is encountered, and the "rtpOtherTimerProc" procedure, called when the "rtpOtherTimer" expires.  These three cells in the RTPSoc structure are renamed "bspPupProc", "bspTimer", and "bspTimerProc" within the BSP module.  By this means, the management of both incoming and outgoing byte streams is accomplished automatically (including the generation of acknowledgments and retransmissions).

Received Pups that are not part of either the RTP or the BSP are handed to the procedure given in the "bspOtherPupProc" cell in the socket.  This is initialized to the previous contents of the socket's "rtpOtherPupProc" by CreateBSPStream (which then stores a pointer to the BSP module's own BSPPupProc into the latter cell).  The earlier description of "rtpOtherPupProc" (in the section on the RTP module) applies to "bspOtherPupProc".

Received Interrupt packets are also passed to "bspOtherPupProc" after being processed by the BSP module.  Note that an Interrupt passed in this manner has been verified to conform to protocol (this is the case also for Abort and Error packets passed up from the RTP module) and may therefore be "believed".  Any other type of packet, on the other hand, has had no checking done on it beyond the level 1 interface (where the destination port and checksum were verified).

A note on allocations:  this BSP implementation probably will not work at all unless the socket's PBI allocations are at least 3, 2, and 2 for "total", "input", and "output" respectively.  High throughput will be gained only by giving the socket somewhat larger allocations (say, 6 to 10 PBIs) for the direction(s) in which high throughput is desired.

In a program with at most one active BSP connection, that socket should be allocated all of the PBIs in the system except one per directly-connected network (there must always be one extra PBI available for receiving incoming packets on each network);  this is the default allocation established in dPSIB by InitPupLevel1.  In a program with several active connections, one should adjust individual socket allocations appropriately (though probably not simply by dividing the total PBIs by the number of sockets, since doing so typically leads to underutilization of PBIs).  Assuming there are plenty of PBIs in the system, it is generally safe to overcommit the system resources (relying on the statistical unlikelihood that all sockets will simultaneously tie up all the PBIs to which they are individually entitled).  One should be aware, however, that the higher-level protocols can get into deadlock conditions if the system pbiFreeQ becomes exhausted.  For the same reason, a PBI passed to an external program via the "bspOtherPupProc" entry in the socket must be released as quickly as possible, since it is charged against the socket's allocation.

The BSP module includes a static "bspVersion" whose value is (protocol version * 1000) + package version.

.sec BSP Block Transfer Procedures

The BSP stream mechanism just presented, while being a "fast stream" in the sense defined by the operating system, is still relatively slow and is therefore not well suited to transferring large volumes of data (such as file transfers between disk and net).  A separate module (PupBSPBlock) is provided for accomplishing block transfers at least an order of magnitude faster than by iterated calls on Gets or Puts.  This module requires that the AltoByteBlt module (released as a separate package) be loaded as well.

Two procedures are defined in this module:

.begin indent 4,4
.once indent 0,10 nojust
BSPReadBlock(str, wordP, byteP, count, timeout [...-1]) = count
Reads a maximum of "count" bytes from the BSP stream "str", storing them in memory starting at byte position "byteP" relative to word address "wordP" (for example, byteP = 0 means the left byte of the word referenced by "wordP").  The transfer terminates under any of the conditions that would cause Gets(soc,timeout) to return -1.  The procedure returns the actual number of bytes transferred.

.once indent 0,10 nojust
BSPWriteBlock(str, wordP, byteP, count, timeout [...-1]) = count
Writes a maximum of "count" bytes to the BSP stream "str", obtaining them from memory starting at byte position "byteP" relative to word address "wordP".  The transfer terminates under any of the conditions that would cause Puts(soc,byte,timeout) to return false.  The procedure returns the actual number of bytes transferred.

.sec Network Directory Lookup Module

This module (file PupNetDirLookup) contains procedures to convert between a string consisting of any legal inter-network name/address expression and a Port structure containing the corresponding address.  See the memo "Naming and Addressing Conventions for Pup" (file PupName.press) for information on legal expressions.

The PupNetDirLookup module contains all the procedures described below.  The alternative PupNameLookup module, included in the Pup package for historical reasons, contains only GetPartner and ParseAddressConst; it is somewhat smaller than PupNetDirLookup, and it may be used in situations where the other functions are not needed.

.begin indent 4,4
.once indent 0,10 nojust
GetPartner(name, stream [none], port, s1 [...none], s2 [...none]) = true or false
Parses the BCPL string "name" and stores the resulting address value in the Port structure "port", returning true if successful and false otherwise.  "stream", if nonzero, is used for publishing an error message if the conversion is unsuccessful.  "s1" and "s2", if supplied, specify the high- and low-order parts of the default socket number, which is substituted into the "port" if the socket number is unspecified in the "name".

If the "name" consists entirely of address constants (in the form "net#host#socket" or some subset thereof, where the components are octal numbers), then it is parsed locally.  Otherwise, GetPartner attempts to establish contact with a Network Directory Lookup server, to which it passes the "name" for evaluation.  If the reply consists of several alternative addresses, the "best" one is chosen on the basis of information in the local routing table.  Regardless of whether or not the string is an address constant, GetPartner will return false (with the message "Can't get there from here") if no routing table entry exists for the resulting network and several calls to LocateNet discover no way of reaching that network.

.once indent 0,10 nojust
ParseAddressConst(name, port) = true or false
Attempts to parse the BCPL string "name" as an address constant of the form "net#host#socket".  Stores the result in "port" and returns true if successful; returns false if unsuccessful.

.once indent 0,10 nojust
EnumeratePupAddresses(name, filter [TakeTheFirst], arg [], dontCheckRT [false]) = 0 or error code
Evaluates "name" to one or more addresses.  For each address, calls filter(port, arg), where port is a pointer to a Port structure containing that address and arg is the "arg" passed to EnumeratePupAddresses.  If filter returns true, enumeration stops; if false, enumeration continues.

Ordinarily the addresses are enumerated in order of increasing distance away from here (hop count), and inaccessible addresses are omitted; however, if "dontCheckRT" is supplied and true, the addresses are not sorted and are all returned.  Filter defaults to an internal procedure TakeTheFirst, which treats "arg" as a pointer to a Port and copies the first Pup address to that port, discarding the rest.

EnumeratePupAddresses returns zero if successful and an error code (from PupNetDir.decl) if unsuccessful.  The error codes are ecNameNotFound, meaning that the name is not registered in the Network Directory server data base; ecCantGetThere, meaning that the name exists but none of the addresses are currently accessible in the internet; and ecNoServerResponded, meaning that no Network Directory server could be located.

.once indent 0,10 nojust
PupAddressLookup(port, lvEC [], zone [pupZone]) = string or 0
Interacts with a network directory server to find a name string corresponding to the Pup address pointed to by "port".  If successful, returns a string allocated from "zone" (which defaults to pupZone, the zone passed to InitPupLevel1).  If unsuccessful, stores an error code (from PupNetDir.decl) at @lvEC (which may be omitted) and returns zero.  The error codes are ecAddressNotFound, meaning that the address is not registered in the Network Directory server data base; and ecNoServerResponded, meaning that no Network Directory server could be located.

.sec Example

The following example program makes use of most of the facilities provided in the Pup package.  It is basically a rock-bottom minimal user Telnet (like Chat) with no redeeming features whatsoever.

The main procedure PupExample performs initialization, which consists of creating a large zone, initializing the Pup package, creating a large display window, and creating and starting a context running the procedure TopLevel.

TopLevel first requests the user to type in a foreign port name, which it parses by calling GetPartner (note that the socket number is defaulted to 1, the server Telnet socket).  Then a socket is created and a connection is opened.  Two new contexts are now created, running the procedures KeysToNet and NetToDsp.  TopLevel then blocks until either the connection is no longer open or the second blank key on the right of the keyboard is pressed, at which point it destroys the two contexts it created, closes the connection, and loops back to the beginning.

The KeysToNet procedure blocks waiting for keyboard input, then outputs the typed-in character to the BSP stream and calls BSPForceOutput to force immediate transmission.  If the Puts fails, KeysToNet simply blocks forever, in the expectation that TopLevel will detect that the connection is no longer open and take appropriate action.

The NetToDsp procedure blocks waiting for input from the BSP stream.  When a normal character is received, it is output to the display.  If Gets returns -1, then either a Mark is pending or the connection has ended;  if the former, a message is printed and BSPGetMark is called to clear the Mark pending status;  if the latter, NetToDsp blocks indefinitely.

.skip 2
.begin verbatim
// PupExample.bcpl

// Bldr PupExample PupBSPStreams PupBSPProt PupBSPa PupBSPOpenClose ↑
//  PupRTP PupRTPOpenClose PupNameLookup ↑
//  Pup1b PupAl1a Pup1OpenClose PupRoute ↑
//  PupAlEthb PupAlEtha ↑
//  Context ContextInit Interrupt AltoQueue AltoTimer ↑
//  Pup1Init PupDummyGate PupAlEthInit InterruptInit

get "Pup.decl"

InitPupLevel1; OpenLevel1Socket; CloseLevel1Socket; SetAllocation
OpenRTPSocket; CreateBSPStream; GetPartner
BSPForceOutput; BSPGetMark
InitializeContext; CallContextList; Block; Enqueue; Unqueue
InitializeZone; CreateDisplayStream; ShowDisplayStream
Gets; Puts; Closes; Endofs; Ws
keys; dsp
static [ ctxQ; myDsp; bspSoc; bspStr ]

let PupExample() be  // initialization
let myZone = vec 10000; InitializeZone(myZone, 10000)
let q = vec 1; ctxQ = q; ctxQ!0 = 0
InitPupLevel1(myZone, ctxQ, 20)
let v = vec 10000
myDsp = CreateDisplayStream(40, v, 10000)
let v = vec 3000
Enqueue(ctxQ, InitializeContext(v, 3000, TopLevel))
CallContextList(ctxQ!0) repeat

and TopLevel() be  // top-level process
Ws("*nConnect to: ")
let name = vec 127; GetString(name)
if name>>String.length eq 0 then finish
let frnPort = vec lenPort
unless GetPartner(name, dsp, frnPort, 0, 1) do loop
let v = vec lenBSPSoc; bspSoc = v
OpenLevel1Socket(bspSoc, 0, frnPort)
unless OpenRTPSocket(bspSoc, ctxQ) do
   [ Ws("*nFailed to connect"); CloseLevel1Socket(bspSoc); loop]
bspStr = CreateBSPStream(bspSoc)
let keysToNetCtx, netToDspCtx = vec 1000, vec 1000
Enqueue(ctxQ, InitializeContext(keysToNetCtx, 1000, KeysToNet))
Enqueue(ctxQ, InitializeContext(netToDspCtx, 1000, NetToDsp))
Block() repeatuntil bspSoc>>BSPSoc.state ne stateOpen %
   @#177035 eq #177775  //second blank key pressed
Unqueue(ctxQ, keysToNetCtx); Unqueue(ctxQ, netToDspCtx)
] repeat

and KeysToNet() be
test Puts(bspStr, GetKeys())
   ifso BSPForceOutput(bspSoc)
   ifnot Block() repeat
] repeat

and NetToDsp() be
let char = Gets(bspStr)
if char eq -1 then
   test bspSoc>>BSPSoc.markPending
         Ws("*nI saw a Mark!")
      ifnot Block() repeat
Puts(myDsp, char)
] repeat

and GetKeys() = valof
while Endofs(keys) do Block()
resultis Gets(keys)

and GetString(string) be
for i = 1 to 255 do
   let char = GetKeys(); Puts(dsp, char)
   test char eq $*n
      ifnot string>>String.char↑i = char
      ifso [ string>>String.length = i-1; return ]

.sec Revision History

March 25, 1976

Various minor bugs in both code and documentation were fixed.  One serious error in the documentation was in the description of CreateBSPStream, where "lenBSPStr" should have been "lenBSPSoc".  The level 1, RTP, and BSP modules each became slightly smaller.  Various calls to CallSwat were changed to SysErr with registered error codes.

Level 0: External change: file PupAlEth.bcpl replaced by PupAlEthb.bcpl and PupAlEtha.asm.  Internal change: fast (~20-instruction) Ethernet receiver turnaround implemented.

Level 1: External changes: statics pupZone and pupCtxQ added; procedures SetPupDPort, SetPupSPort, SetPupSPort, and FlushQueue added; RT structure definition changed; default pupErrSt is now a "nil" stream rather than "dsp".

RTP: External changes: defaultTimeout and rtpStackSize changed from manifests to statics (with default values defaultDefaultTimeout and defaultRTPStackSize); DefaultOtherPupProc added.

BSP: External change: static bspVersion added.  Internal change: the transmission strategy was modified to elicit an acknowledgment before allocation is completely exhausted, hence reducing lost throughput due to round-trip delay.

April 16, 1976

The released package Pup.dm was renamed PupPackage.dm, and a debugging version of the package released as PupDebug.dm.  A number of bugs (particularly in level 1) were uncovered while bringing up the software on the Nova.

Level 0: External change: lenPup and lenPBI changed from manifests to statics (defined in level 1) to permit changing PBI size without recompiling the package.  Internal change: 100-millisecond transmit timeout and discard added (eliminating deadlocks caused by things like disconnecting the Alto from the Ethernet).

Level 1: External changes: gateway code split out into separate files PupGateway and PupDummyGate, one of which must be loaded (usually the latter); optional extra argument "pupDataBytes" added to InitPupLevel1; default allocations in dPSIB changed to permit a socket to assign all but one of the PBIs in the system; OpenLevel1Socket defaults the foreign net in some circumstances.  Internal change: if "pupDebug" is on, PupLevel1 checks for the pbiFreeQ being exhausted for more than 20 seconds and calls Swat (this usually indicates a deadlock).

BSP: External change: PupBSPb.bcpl replaced by PupBSPStreams.bcpl and PupBSPProt.bcpl (necessitated by Nova BCPL's inability to compile PupBSPb in one gulp).

May 18, 1976

Mostly bug fixes and performance improvements.  Some structure definitions were changed, so recompilation of user programs is advised.

Level 0: Internal changes: more assembly code included to reduce packet loss rate; performance statistics gathered if pupDebug on.

Level 1: External change: optional "type" and "length" arguments added to CompletePup.

October 6, 1976

Significant internal changes were made at levels 0 and 1, and several new capabilities were added.  However, for the most part the changes are upward-compatible.  Many structure declarations changed, so recompilation of programs that "get" any Pup .decl files is required.

Level 0: External changes: SendEtherPup removed; EncapsulateEtherPup and SendEtherPacket added; ability to send and receive non-Pups implemented.

Level 1: External changes: PupRoute file added; PupGateway module deleted from public Pup package release; routing table completely reorganized; new procedures HLookup, HInsert, HDelete, HEnumerate, HHash added; pupErrSt removed; mechanism added for broadcasting to all connected networks; procedures DoubleIncrement, DoubleDifference, Double subtract included (formerly in BSP module).  Internal change: GatewayListener dynamically maintains the best path to each network and purges RTEs of networks for which no routing information has been received recently.

BSP: Internal change: adaptive retransmission timeout implemented to reduce packet loss rate when sending through slow networks or to slow destinations (e.g., Maxc).

March 21, 1977

Mostly bug fixes.  Some structure definitions at level 0 were changed, so recompilation of user programs is advised.

Level 0: SendStats operation added to the NDB object.

July 11, 1977

No external changes.  The Ethernet driver was rewritten to eliminate several low-probability race conditions and improve performance slightly.  The driver now uses the "input under output" feature unconditionally, so problems may be encountered on Alto-Is running old microcode.

March 20, 1978

External changes: Several source files have been broken into smaller pieces to permit much of the code to be included in overlays.  The added modules are Pup1OpenClose, PupRTPOpenClose, and PupBSPOpenClose (see section 1.2 for revised packaging information).  Recompilation is required of any programs that get Pup.decl or PupBSP.decl.

Internal changes: BSP performance through slow links and gateways has been improved.  GetPartner's timeout has been increased.  A few minor bugs have been fixed.

November 6, 1978

Level 0: External changes: The Ethernet driver can now control multiple interfaces connected to the same Alto.  InitAltoEther is called differently.  Drivers are now available for the XEOS EIA interface and the ASD Communication Processor, though they aren't documented here.

Level 1: Internal change: The routing module data structures and algorithms have geen modified to conform to some minor Pup protocol changes.

BSP: External change: An inactivity timeout has been added to automatically abort connections that have died; this may be disabled by setting the noInactivityTimeout bit in the BSPSoc.  Recompilation is required of any programs that get Pup.decl or PupBSP.decl.

February 19, 1979

Level 0: Internal change: The format of the statistics collected by drivers changed.

Level 1: Internal change: The interface between PupRoute and the forwarder changed.  This is only of interest to gateways.  External change: The definitions of pup types and well-known sockets have been removed from Pup1.decl.  We now feel that these belong in less global declaration files closer to the code implementing the various protocols.  Recompilation of user programs will probably cause undefined symbols which you will have to add to your declaration files.

May 27, 1979

Level 1: External changes: InitPupLevel1 takes an additional optional argument "numRTE"; LocateNet procedure added; HHash removed; PupDummyGate module moved from resident to initialization; lPupSoc static added.  Internal changes: the routing table is now a cache of routing information rather than a hash table of all accessible networks in the internet; RTEs may be "invalid" (hops greater than maxHops and ndb equal to zero); new PupRoute.decl includes definitions internal to the routing module.

RTP: External change: lRTPSoc static added.  Internal change: more code moved from resident (PupRTP) to swappable (PupRTPOpenClose) modules.

BSP: External change: lBSPSoc and offsetBSPStr statics added.  Internal changes: minor bugs fixed; more code moved from resident (PupBSPStreams and PupBSPProt) to swappable (PupBSPOpenClose) modules.

NameLookup: External changes: RequestNameLookup and ParseAddressConst procedures now exported.

March 9, 1980

Levels 0 & 1: External change: a "destroy" operation has been added to the NDB, and the procedure DestroyPupLevel1 is included to shut down the Pup package.  A few bugs have been fixed.

December 30, 1980

BSP: External change: The default timeouts for Puts, PutMark, PutInterrupt and BSPWriteBlock were changed from 'defaultTimeout' to '-1' (infinity).  This makes the 'put' operations symmetrical with the 'get' operations: so long as the other end of the connection is alive, the BSP will wait indefinitely to put or get a byte.  Recompilation of client programs is not necessary.

January 25, 1981

BSP: External change: optional 'sendNow' argument added to BSPForceOutput and BSPPutMark.  Recompilation of client programs is not necessary.

November 21, 1981

Level 1: External change: optional "transient" argument added to OpenLevel1Socket.

NameLookup: External change: a new module PupNetDirLookup provides the same functions as PupNameLookup (which still exists), and additionally contains new procedures EnumeratePupAddresses and PupAddressLookup.  Recompilation of client programs is not necessary.