XNSSocket.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Demers, December 20, 1986 12:22:14 pm PST
This is the interface for flinging/catching XNS packets to/from the net. See the comments at the end.
DIRECTORY
XNS USING [Address, unknownAddress, unknownSocket, Socket],
XNSBuf USING [Buffer, maxBodyBytes, maxBodyFWords, maxBodyHWords],
XNSErrorTypes USING [ErrorType];
XNSSocket:
CEDAR
DEFINITIONS ~ {
Address: TYPE ~ XNS.Address;
Socket: TYPE ~ XNS.Socket;
Buffer: TYPE ~ XNSBuf.Buffer;
Handle: TYPE ~ REF Object;
Object: TYPE;
Creation and parameter setting
Milliseconds: TYPE = INT;
dontWait: Milliseconds = 0;
waitForever: Milliseconds =
INT.
LAST;
Get timeout units are milliseconds.
Process machinery has a limit of an hour or so.
Create:
PROC [
remote: Address ← XNS.unknownAddress,
sendBuffers: NAT ← 1,
recvBuffers: NAT ← 5,
getTimeout: Milliseconds ← 10000,
local: Socket ← XNS.unknownSocket ] -- unknownSocket => XNSPackage will assign a free one
RETURNS [handle: Handle];
Creating a socket with a local socket number that is already in use simply pushes the previous handle. It can still be used to send packets, but the newer handle will receive all incoming packets. This would be pretty frustrating for a user but you are unlikely to know his socket number anyway. For a server, this allows you to capture the server's socket temporarily, for example to test some new code.
GetLocalAddress:
PROC [Handle]
RETURNS [Address];
GetRemoteAddress:
PROC [Handle]
RETURNS [Address];
SetRemoteAddress:
PROC [Handle, Address];
Don't call SetRemoteAddress while any other send activity is in progress.
SetGetTimeout:
PROC [Handle, Milliseconds];
SetSoftwareChecksumming:
PROC [handle: Handle, send, recv:
BOOL];
FALSE => Faster
BEWARE: Hardware and Gateways occasionally mash packets.
Don't turn these off if you are transferring valuable data.
SetNoErrors:
PROC [handle: Handle, noErrors:
BOOL ←
TRUE];
Don't send any error packets if this socket runs out of recv buffers and/or the this socket gets deleted.
Kick:
PROC [handle: Handle];
Wakeup any processes waiting in Get. Normally only used just before Destroy.
Destroy:
PROC [handle: Handle];
Dropping on the floor is safe, but may waste resources. This recycles the socket slot sooner.
Sending
AllocBuffer:
PROC [handle: Handle]
RETURNS [Buffer];
SetUserBits: PROC [b: Buffer, bits: [0 .. XNSBuf.maxBodyBytes*BITS[BYTE]]];
SetUserBytes: PROC [b: Buffer, bytes: [0 .. XNSBuf.maxBodyBytes]];
SetUserHWords: PROC [b: Buffer, hWords: [0 .. XNSBuf.maxBodyHWords]];
SetUserFWords:
PROC [b: Buffer, fWords: [0 .. XNSBuf.maxBodyFWords]];
The Package will set buffer.length, rounding up to include the header.
Put:
PROC [b: Buffer];
Client should fill in length (using SetUser*) and type.
XNSPackage fills in source and dest (from handle from which buffer was allocated), 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.
Packets sent to funny places may fall into a black hole or may generate error packets.
Send:
PROC [b: Buffer, dest: Address];
Like Put, but sends to the given dest address rather than to the remote address specified when the socket was created.
Broadcast:
PROC [b: Buffer, socket: Socket];
Like Put/Send, but broadcast the packet to the given socket on all directly-connected networks.
Receiving
Get:
PROC [handle: Handle]
RETURNS [Buffer];
NIL if none before timeout
Use Kick or Process.Abort if you get impatient
GetUserBits: PROC [Buffer] RETURNS [bits: [0 .. XNSBuf.maxBodyBytes*BITS[BYTE]]];
GetUserBytes: PROC [Buffer] RETURNS [bytes: [0 .. XNSBuf.maxBodyBytes]];
GetUserHWords: PROC [Buffer] RETURNS [hWords: [0 .. XNSBuf.maxBodyHWords]];
GetUserFWords:
PROC [Buffer]
RETURNS [fWords: [0 .. XNSBuf.maxBodyFWords]];
Multibyte sizes get truncated without complaint.
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.
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.
ReturnToSender:
PROC [b: Buffer];
This is the normal way for a server to answer a single packet query.
The client fills in the new type and sets the length.
ReturnToSender does FixupSourceAndDest, then swaps source and dest, then sends packet back where it came from.
ReturnError:
PROC [b: Buffer, type: XNSErrorTypes.ErrorType];
Like ReturnToSender, but it first constructs an error packet in the buffer.
Unique IDs
For packet exchange, etc.
GetUnique16: PROC RETURNS [CARD16];
GetUnique32: PROC RETURNS [CARD32];
}.
Buffers:
The hard part of communications is buffer management. Buffers are large and pinned into real memory. It hurts to be sloppy. If you get a buffer from the XNSPackage via AllocBuffer or Get, you should give it back via FreeBuffer, ReturnToSender, ReturnError, Put* or Send. Once you have handed a buffer to the XNSPackage, don't touch it again. ("b ← NIL;" is suggested as a reminder.)
The XNSPackage 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.
Buffers are reference counted, so it's OK to drop one on the floor in very weird cases (like getting ABORTed from the debugger). They should be explicitly freed on any reasonably likely error path, especially one that can be induced by a client error or an error on another machine.
Sample Server:
handle: XNSSocket.Handle ← XNSSocket.Create[local: XNSWKS.xxx, getTimeout: XNSSocket.waitForever];
UNTIL quit
DO
b: Buffer ← XNSSocket.Get[handle ! ABORTED => { b ← NIL; CONTINUE; } ]
IF b = NIL THEN LOOP;
...
process the request
...
IF reject THEN { XNSSocket.FreeBuffer[b]; LOOP; };
XNSSocket.FixupSourceAndDest[b];
lastClient ← b.source;
b.type ← xxx;
b.response ← [...];
XNSSocket.SetUserSize[b, SIZE[Response]];
XNSSocket.ReturnToSender[b];
b ← NIL;
ENDLOOP;
XNSSocket.Destroy[handle];
Sample User:
server: ROPE ← xxx.
him: XNS.Address ← ...
handle: XNSSocket.Handle ← XNSSocket.Create[remote: him, getTimeout: 5000];
hit: BOOL ← FALSE;
FOR i:
NAT
IN [0..10)
UNTIL hit
DO
b: XNSBuffer.Buffer ← XNSSocket.AllocBuffer[handle]
b.type ← xxx;
b.request ← [xxx];
XNSSocket.SetUserSize[b, SIZE[Request]];
XNSSocket.Put[b];
b ← XNSSocket.Get[handle]
IF b = NIL THEN LOOP;
...
IF notMyAnswer THEN { XNSSocket.FreeBuffer[b]; LOOP; };
...
process the answer
...
XNSSocket.FreeBuffer[b];
b ← NIL;
hit ← TRUE;
ENDLOOP;
XNSSocket.SetNoErrors[handle, TRUE];
XNSSocket.Destroy[handle];
IF ~hit THEN ERROR NoResponse
PacketExchange:
The XNS Packet Exchange protocol is supposed to automate a certain amount of the above, and might be worth considering.
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 handle at the same time. (That's unlikely for anything reasonably simple.) The client must provide the ML to make sure that Put uses the desired SetRemoteAddress.
Sending:
There are 3 ways to send a packet.
Put is the simplest. It just sends the pup to the address saved from Create or SetRemoteAddress.
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 XNSPackage 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 handle.) 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.