PupSocket.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Hal Murray, June 3, 1986 9:55:56 pm PDT
This is the interface for flinging/catching Pup packets to/from the net. See the comments at the end.
DIRECTORY
Endian USING [FWORD],
Pup USING [Address, Socket],
PupBuffer USING [Buffer, ByteIndex, FWordIndex, HWordIndex],
PupType USING [ErrorCode],
Rope USING [ROPE];
PupSocket: CEDAR DEFINITIONS = {
Buffer: TYPE = PupBuffer.Buffer;
ROPE: TYPE = Rope.ROPE;
Socket: TYPE = REF Object;
Object: TYPE;
Creation and parameter setting
Milliseconds: TYPE = INT;
dontWait: Milliseconds = 0;
waitForever: Milliseconds = INT.LAST;
Get timeout units are milliseconds.
The process machinery has a limit of an hour or so.
CreateServer: PROC [
local: Pup.Socket, -- Must be WellKnown
sendBuffers: NAT ← 1,
recvBuffers: NAT ← 5,
getTimeout: Milliseconds ← 10000 ]
RETURNS [socket: Socket];
Note: Local must be a small number. All the common values are included in PupWKS. All the values defined in PupWKS are WellKnown. PupName.IsWellKnown can be used to test other values.
Note: There is no way to create a Socket and specify an arbitrary local socket. If you don't want to use a well known socket, you should call PupSocket.CreateEphemeral and then use PupSocket.GetLocalAddress to find out what socket number was assigned by the PupPackage.
Creating a socket with a local socket number that is already in use simply pushes the previous socket. Both can be used to send packets, but the newer socket will receive all incoming packets. This allows you to temporarily capture a running server's socket, for example to test some new code.
Errors: SocketNotWellKnown if local is too big.
CreateEphemeral: PROC [
remote: Pup.Address,
sendBuffers: NAT ← 1,
recvBuffers: NAT ← 5,
getTimeout: Milliseconds ← 10000 ]
RETURNS [socket: Socket];
The PupPackage assigns the local socket number.
Errors: None.
SetRemoteAddress: PROC [Socket, Pup.Address];
GetRemoteAddress: PROC [Socket] RETURNS [Pup.Address];
Errors: None.
GetLocalAddress: PROC [Socket] RETURNS [Pup.Address];
Reminder: The local net+host may not be what you expect if the machine has more than one interface. This is called the back door problem. Buffers received via PupSocket.Get may have a destination address that doesn't match the local address. The socket number will match. Also, the net may be null (0) and the host may be broadcast (0).
Errors: None.
SetGetTimeout: PROC [Socket, Milliseconds];
Errors: None.
SetNoErrors: PROC [socket: Socket];
Don't send any error Pups if this socket runs out of recv buffers and/or the this socket gets deleted.
Errors: None.
Kick: PROC [socket: Socket];
Wakeup any processes waiting in Get. Normally only used just before Destroy. Destroy includes an automatic Kick so an explicit call is not required to keep the PupPackage happy.
Errors: None.
Destroy: PROC [socket: Socket];
Dropping on the floor is safe, but may waste resources. This recycles the socket slot sooner.
Errors: None.
Sending
AllocBuffer: PROC [socket: Socket] RETURNS [Buffer];
Allocates a send buffer. Will wait if the number of buffers specified in Create* are already in use. Use Process.Abort if you don't want to wait any longer.
Errors: None.
SetUserBytes: PROC [b: Buffer, bytes: PupBuffer.ByteIndex];
SetUserHWords: PROC [b: Buffer, hWords: PupBuffer.HWordIndex];
SetUserFWords: PROC [b: Buffer, fWords: PupBuffer.FWordIndex];
SetUserSize: PROC [b: Buffer, sizeUnits: NAT];
The PupPackage will set buffer.length, rounding up to include the header.
Use SetUserHWords or SetUserFWords if you know you are dealing with 16 or 32 bit quantities.
Beware: On a 32 bit machine, SIZE may round up an extra half word. SetUserSize is a stopgap until the Compiler supports BYTES[Type]. Use it so Grep can easily find all the places that need changing.
Errors: None. Bounds fault if the length is too big for a buffer.
Broadcast: PROC [socket: Socket, b: Buffer];
Broadcasts Pup on all directly connected nets. Dest socket number is taken from socket.remote. Dest net number will be filled in if it is known.
Errors: None.
Put: PROC [socket: Socket, b: Buffer];
Does Send[socket, b, socket.remote].
Send: PROC [socket: Socket, b: Buffer, dest: Pup.Address];
Client should fill in length (using SetUser*), type, and id. The PupPackage will fill in source, dest, hop count, and software checksum.
Normally, on the local Ethernet, this operation takes only a few milliseconds. If the network is wedged, it may take several seconds. If you are sending out a phone line, it may take a minute or two. Process.Abort is unlikely to do anything.
Errors: None. Pups sent to funny places may fall into a black hole or may generate error Pups.
Receiving
Get: PROC [socket: Socket] RETURNS [Buffer];
Returns NIL if no buffer arrives before timeout or socket gets Kicked or Destroyed.
Use Kick or Process.Abort if you get impatient.
Errors: None.
GetUserBytes: PROC [Buffer] RETURNS [bytes: PupBuffer.ByteIndex];
GetUserHWords: PROC [Buffer] RETURNS [hWords: PupBuffer.HWordIndex];
GetUserFWords: PROC [Buffer] RETURNS [fWords: PupBuffer.FWordIndex];
GetUserSize: PROC [Buffer] RETURNS [sizeUnits: NAT];
Multibyte sizes get truncated without complaint.
Use SetUserHWords or SetUserFWords if you know you are dealing with 16 or 32 bit quantities.
Beware: On a 32 bit machine, SIZE may round up an extra half word and/or GetUserSize may unexpectedly round down a half word. GetUserSize is a stopgap until the Compiler supports BYTES[Type]. Use it so Grep can easily find all the places that need changing.
Errors: None. (Maybe BoundsFault if the buffer didn't come from PupSocket.Get.)
FreeBuffer: PROC [b: Buffer];
Dropping on the floor is safe, but very risky/evil/antisocial. Your quota won't get updated until the collector finds it.
You can also call FreeBuffer if you change your mind after calling AllocBuffer.
Errors: None.
Beware: Using a buffer after you have freed it will cause all sorts of trouble.
FixupSourceAndDest: PROC [Buffer];
Fills in source net number if it was unknown.
Call this before copying the source field if you want to remember the sender.
Also fixes up dest address.
Fills in dest host if it was broadcast.
Fills in dest net if it was unknown.
Errors: None. (NIL fault if the buffer didn't come from PupSocket.Get.)
ReturnToSender: PROC [b: Buffer];
This is the normal way for a server to answer a single packet querry.
The client fills in the new type, sets the length, and leaves the id unchanged.
ReturnToSender does FixupSourceAndDest, then swaps source and dest, then sends packet back where it came from.
Errors: None. (NIL fault if the buffer didn't come from PupSocket.Get.)
ReturnError: PROC [b: Buffer, code: PupType.ErrorCode, rope: ROPE];
Like ReturnToSender, but it first constructs an error Pup in the buffer.
NIL for rope will use builtin strings for known codes.
Errors: None. (NIL fault if the buffer didn't come from PupSocket.Get.)
Rope Utilities
CopyRope: PROC [b: PupBuffer.Buffer, rope: Rope.ROPE];
AppendRope: PROC [b: PupBuffer.Buffer, rope: Rope.ROPE];
CopyRope zaps the length in the buffer, appends as many characters as it can, then sets the length. AppendRope is similar, but it doesn't zero the length to start with. Both truncate (with no feedback) if it won't fit.
Errors: None. (Maybe BoundsFault if the buffer didn't come from PupSocket.Get.)
ExtractRope: PROC [b: PupBuffer.Buffer] RETURNS [rope: Rope.ROPE];
Copy the whole body of the pup into a Rope. (It's probably an error message.)
ExtractErrorRope: PROC [b: PupBuffer.Buffer] RETURNS [rope: Rope.ROPE];
ExtractAbortRope: PROC [b: PupBuffer.Buffer] RETURNS [rope: Rope.ROPE];
Copy the appropiate portion of the buffer into a ROPE.
Errors: None. (Maybe BoundsFault if the buffer didn't come from PupSocket.Get.)
Utilities
GetUniqueID: PROC RETURNS [Endian.FWORD];
Unique within a session. May get reused due to rollback or reboot.
Errors: None.
IsThisMe: PROC [Pup.Address] RETURNS [yes: BOOL];
Returns TRUE IFF the address corresponds to some driver connected to this machine.
Errors: None.
GetMyAddress: PROC RETURNS [Pup.Address];
RPC thinks this way. It's really bogus since a machine can have more than one address. The socket will be null.
Errors: None. Will return Pup.nullAddress if there are no active drivers.
Errors
SocketNotWellKnown: ERROR;
}.
Buffers:
The hard part of communications is buffer management. Buffers are large and pined into real memory. If you get a buffer from the PupPackage via AllocBuffer or Get, you should give it back via FreeBuffer, ReturnToSender, ReturnError, Put* or Send. Once you have handed a buffer to the PupPackage, don't touch it again. ("b ← NIL;" is suggested as a reminder.)
The PupPackage attempts to protect itself against gross client errors by limiting the number of buffers a client can have at any time. Send and receive buffers are counted/limited separately.
Sockets and Buffers are reference counted, so it's OK to drop them on the floor in funny cases.
Sample Server:
socket: PupSocket.Socket ← PupSocket.CreateServer[local: PupWKS.xxx, getTimeout: INT.LAST];
UNTIL quit DO
b: Buffer ← PupSocket.Get[socket ! ABORTED => { b ← NIL; CONTINUE; } ]
IF b = NIL THEN LOOP;
...
process the request
...
IF reject THEN { PupSocket.FreeBuffer[b]; LOOP; };
PupSocket.FixupSourceAndDest[b];
lastClient ← b.source;
b.type ← xxx;
b.response ← [...];
PupSocket.SetUserSize[b, SIZE[Response]];
PupSocket.ReturnToSender[socket, b];
b ← NIL;
ENDLOOP;
PupSocket.Destroy[socket];
Sample User:
server: ROPE ← xxx.
him: Pup.Address ← PupName.NameLookup[server, PupWKS.xxx ! ...];
socket: PupSocket.Socket ← PupSocket.CreateEphemeral[remote: him, getTimeout: 5000];
hit: BOOLFALSE;
id: Endian.FWORD ← ...
FOR i: NAT IN [0..10) UNTIL hit DO
b: PupBuffer.Buffer ← PupSocket.AllocBuffer[socket]
b.type ← xxx;
b.id ← id;
b.request ← [xxx];
PupSocket.SetUserSize[socket, b, SIZE[Request]];
PupSocket.Put[socket, b];
b ← PupSocket.Get[socket]
IF b = NIL THEN LOOP;
IF id # b.id THEN { PupSocket.FreeBuffer[socket, b]; LOOP; };
...
process the answer
...
PupSocket.FreeBuffer[socket, b];
b ← NIL;
hit ← TRUE;
ENDLOOP;
PupSocket.SetNoErrors[socket, TRUE];
PupSocket.Destroy[socket];
IF ~hit THEN ERROR NoResponse
Process structure:
The above examples only work if "process the request" is fast relative to the users retransmission rate. If some requests might take a long time, consider passing them to another process so that the main process can continue to take packets from the PupPackage.
It's OK to have several processes sending and/or receiving with the same socket at the same time. (That's unlikely for anything reasonably simple.)
Sending:
There are 3 ways to send a packet.
Put is the simplest. It just sends the pup to the address saved from Create.
Send is more convient if you are sending single packets to various machines.
ReturnToSender is the normal way for a server to send a single packet response back to a user.
The sending process will block until the packet has been sent by the hardware. (In the old days, the packet was queued in the driver, but the sending process didn't have to wait for it.)
Receiving:
The PupPackage doesn't do any filtering on the source address. (You might expect it to check since you specified a remote address when you created the socket.) Normally, this works out OK since: 1) if you are a server (using a WKS), you will probably take packets from anybody, or 2) if you are a user (using a default system assigned socket), nobody but the server(s) you are talking to knows your socket number. If it matters, you should use and verify the id field.