PupEthernetTranslation.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Demers, February 5, 1986 5:04:32 pm PST
Hal Murray, May 29, 1986 2:39:13 am PDT
Adapted from NSEthernetOneTranslation.mesa
which in turn was
Very freely adapted from translation code in: Cedar6.0>OISCP>EthernetOneDriver.mesa
Birrell on: 9-Oct-81 16:43:32
BLyon on: March 13, 1981 10:47 PM
Levin, August 9, 1983 9:28 am
Russ Atkinson (RRA) February 19, 1985 7:44:43 pm PST
DIRECTORY
Basics USING [bytesPerWord],
BasicTime USING [GetClockPulses, MicrosecondsToPulses, Pulses],
CommBuffer USING [Overhead],
CommDriver USING [AllocBuffer, Buffer, FreeBuffer, GetNetworkChain, InsertReceiveProc, Network, RecvProc],
CommDriverType USING [Encapsulation, ethernetOneBroadcastHost],
Process USING [SecondsToTicks, SetPriority, SetTimeout, Ticks],
Pup USING [allHosts, Host],
XNS USING [broadcastHost, GetThisHost, Host, unknownHost];
PupEthernetTranslation: CEDAR MONITOR
LOCKS cH USING cH: Cache
IMPORTS BasicTime, CommDriver, Process, XNS
EXPORTS CommBuffer
~ {
BYTE: TYPE ~ [0..100H);
bytesPerWord: NAT ~ Basics.bytesPerWord;
Buffer: TYPE ~ CommDriver.Buffer;
Network: TYPE ~ CommDriver.Network;
Encapsulation: PUBLIC TYPE ~ CommDriverType.Encapsulation; -- exported to CommBuffer
thisHost: XNS.Host ~ XNS.GetThisHost[];
Time
Pulses: TYPE ~ BasicTime.Pulses;
MSecsToPulses: PROC [n: LONG CARDINAL] RETURNS [Pulses] ~ INLINE {
RETURN [BasicTime.MicrosecondsToPulses[1000*n]] };
PulsesSince: PROC [then: Pulses] RETURNS [Pulses] ~ INLINE {
RETURN [BasicTime.GetClockPulses[] - then] };
Translation Request / Reply Packets
TranslationType: TYPE ~ MACHINE DEPENDENT RECORD [a, b: BYTE];
requestType: TranslationType ~ [010H, 041H];
replyType: TranslationType ~ [00eH, 038H];
HostPair: TYPE ~ MACHINE DEPENDENT RECORD [
nsHost: XNS.Host,
pupHost: Pup.Host,
filler: BYTE];
hostPairBytes: CARDINAL ~ SIZE[HostPair, bytesPerWord]; -- Should be BYTES[HostPair]
TranslationPacketObject: TYPE ~ MACHINE DEPENDENT RECORD [
translationType: TranslationType,
replier: HostPair,
requestor: HostPair];
translationPacketBytes: CARDINAL ~ SIZE[TranslationPacketObject, bytesPerWord]; -- Should be BYTES[TranslationPacketObject]; Must be even!
translationShortPacketBytes: CARDINAL ~ translationPacketBytes - hostPairBytes; -- CROCK: some old implementations send reply packets without the requestor field
TranslationBuffer: TYPE ~ REF TranslationBufferObject;
TranslationBufferObject: TYPE ~ MACHINE DEPENDENT RECORD [
ovh: CommBuffer.Overhead,
translationType: TranslationType,
replier: HostPair,
requestor: HostPair];
Translation Entry Cache
For the Pup world, the number of hash headers has been set to the size of the Pup Host space. The hash table chained overflow code is not exercised, and could be discarded. If you want to reduce the number of hash headers, replace the Identity hash function with something like the MOD function in the comment below.
numHashHeaders: CARDINAL ~ 256;
HashIndex: TYPE ~ [0..numHashHeaders);
Hash: PROC [pupHost: Pup.Host] RETURNS [HashIndex] ~ INLINE {
RETURN [pupHost] };
Hash: PROC [pupHost: Pup.Host] RETURNS [HashIndex] ~ INLINE {
RETURN [ pupHost MOD numHashHeaders ] };
Cache: TYPE ~ REF CacheObject;
CacheObject: TYPE ~ MONITORED RECORD [
daemon: PROCESS,
event: CONDITION,
newPendingEntry: BOOLFALSE,
sweepCheckTime: Pulses ← 0,
sweepChecksUntilSweep: CARDINAL ← sweepChecksPerSweep,
sendHead, sendTail: Buffer,
broadcastHostEntry: CacheEntry,
thisHostEntry: CacheEntry,
pendingEntries: CacheEntry,
validEntries: ARRAY HashIndex OF CacheEntry];
CacheEntry: TYPE ~ REF CacheEntryObject;
CacheEntryObject: TYPE ~ RECORD [
next: CacheEntry,
hosts: HostPair,
referenced: BOOL,
timeStamp: Pulses,
tries: CARDINAL
];
Timeouts
pulsesPerSweepCheck: Pulses ~ MSecsToPulses[59500];
sweepCheckTimeout: Process.Ticks ~ Process.SecondsToTicks[60];
sweepChecksPerSweep: CARDINAL ~ 5;
pulsesPerResend: Pulses ~ MSecsToPulses[1500];
resendTimeout: Process.Ticks ← Process.SecondsToTicks[1];
maxTries: CARDINAL ~ 8;
Encapsulating Pup Packets
Statistics
noTranslation: INT ← 0;
notQuick: INT ← 0;
GetEncapsulation: PROC [network: Network, pupHost: Pup.Host] RETURNS [Encapsulation] ~ {
cH: Cache ~ NARROW[network.pup.translation];
eH: CacheEntry ← NIL;
hashIndex: HashIndex ~ Hash[pupHost];
BEGIN
Quick check of first couple of entries without acquiring ML.
IF (eH ← cH.validEntries[hashIndex]) # NIL THEN {
IF eH.hosts.pupHost = pupHost THEN GOTO Found;
IF (eH ← eH.next) # NIL THEN {
IF eH.hosts.pupHost = pupHost THEN GOTO Found;
NULL; -- more checks would go here ...
};
};
IF pupHost = Pup.allHosts THEN { eH ← cH.broadcastHostEntry; GOTO Found };
IF pupHost = network.pup.host THEN { eH ← cH.thisHostEntry; GOTO Found };
notQuick ← notQuick.SUCC;
IF (eH ← GetCacheEntry[cH, hashIndex, pupHost]) # NIL THEN GOTO Found;
GOTO NotFound;
EXITS
Found => {
eH.referenced ← TRUE;
TRUSTED { RETURN[ [ethernet[ethernetDest~eH.hosts.nsHost, ethernetSource~thisHost, ethernetType~oldPup]] ] }
};
NotFound => {
noTranslation ← noTranslation.SUCC;
TRUSTED { RETURN[ [ethernet[ethernetDest~XNS.unknownHost, ethernetSource~thisHost, ethernetType~translationFailed]] ] }
};
END;
};
GetCacheEntry: ENTRY PROC [cH: Cache, hashIndex: HashIndex, pupHost: Pup.Host] RETURNS [CacheEntry] ~ {
Search for a valid cache entry for the given nsHost. If a valid entry is found, return it; otherwise return NIL and arrange for an entry to be added.
eH, prevH: CacheEntry;
eH ← cH.validEntries[hashIndex]; prevH ← NIL;
WHILE eH # NIL DO
IF eH.hosts.pupHost = pupHost THEN {
Move entry to head of list.
IF prevH # NIL THEN {
prevH.next ← eH.next;
eH.next ← cH.validEntries[hashIndex];
cH.validEntries[hashIndex] ← eH };
RETURN[eH] };
prevH ← eH; eH ← eH.next
ENDLOOP;
FOR eH ← cH.pendingEntries, eH.next WHILE eH # NIL DO
IF eH.hosts.pupHost = pupHost THEN RETURN[NIL];
ENDLOOP;
TRUSTED { cH.pendingEntries ← NEW[ CacheEntryObject ← [next~cH.pendingEntries, hosts~[nsHost~XNS.unknownHost, pupHost~pupHost, filler~], referenced~TRUE, timeStamp~BasicTime.GetClockPulses[], tries~0] ] };
cH.newPendingEntry ← TRUE; NOTIFY cH.event;
RETURN[NIL] };
Building Request / Reply Packets
MakeRequest: PROC [cH: Cache, pupHost: Pup.Host, sendTo: XNS.Host ← XNS.broadcastHost] RETURNS [b: Buffer] ~ {
Allocate a buffer, build a request packet in it, and return it.
The sendTo parameter is the XNS Host to which the request packet will be sent. It should be broadcastHost for a normal request.
bH: TranslationBuffer;
b ← CommDriver.AllocBuffer[];
TRUSTED { bH ← LOOPHOLE[b] };
bH.translationType ← requestType;
bH.replier ← [nsHost~XNS.unknownHost, pupHost~pupHost, filler~0];
bH.requestor ← cH.thisHostEntry.hosts;
TRUSTED { bH.ovh.encap ← Encapsulation[ethernet[ethernetDest~sendTo, ethernetSource~thisHost, ethernetType~oldPupTranslation]] };
};
ConvertToReply: PROC [cH: Cache, bH: TranslationBuffer] ~ {
Given a request buffer, convert it to the corresponding reply.
Fill in the encapsulation part here, so the buffer can be sent using network.sendTranslate rather than network.return.
bH.translationType ← replyType;
bH.replier ← cH.thisHostEntry.hosts;
TRUSTED { bH.ovh.encap ← Encapsulation[ethernet[ethernetDest~bH.requestor.nsHost, ethernetSource~thisHost, ethernetType~oldPupTranslation]] };
};
Processing Received Translation Packets
AddTranslation: ENTRY PROC [cH: Cache, hosts: HostPair] ~ {
eH, prevH: CacheEntry;
i: HashIndex ~ Hash[hosts.pupHost];
Look for a pending entry.
eH ← cH.pendingEntries; prevH ← NIL;
WHILE eH # NIL DO
IF eH.hosts.pupHost = hosts.pupHost THEN {
IF prevH = NIL THEN cH.pendingEntries ← eH.next ELSE prevH.next ← eH.next;
eH.hosts.nsHost ← hosts.nsHost;
EXIT };
prevH ← eH; eH ← eH.next
ENDLOOP;
If no pending entry, look for a valid one.
IF eH = NIL THEN {
eH ← cH.validEntries[i]; prevH ← NIL;
WHILE eH # NIL DO
IF eH.hosts.pupHost = hosts.pupHost THEN {
IF prevH = NIL THEN cH.validEntries[i] ← eH.next ELSE prevH.next ← eH.next;
If existing entry is incorrect, drop it on the floor ...
IF eH.hosts.nsHost # hosts.nsHost THEN eH ← NIL;
EXIT };
prevH ← eH; eH ← eH.next
ENDLOOP;
};
IF eH = NIL
THEN cH.validEntries[i] ← NEW[ CacheEntryObject ← [next~cH.validEntries[i], hosts~hosts, referenced~TRUE, timeStamp~BasicTime.GetClockPulses[], tries~0] ]
ELSE { eH.next ← cH.validEntries[i]; cH.validEntries[i] ← eH };
};
Receive Statistics
requestsReceived: INT ← 0;
repliesReceived: INT ← 0;
tooShort: INT ← 0;
badProtocol: INT ← 0;
RecvTranslation: CommDriver.RecvProc
[network: Network, buffer: Buffer, bytes: NAT] RETURNS [Buffer]
~ {
cH: Cache ~ NARROW[network.pup.translation];
bH: TranslationBuffer;
CROCK: the following test should be "< translationPacketBytes", but some old implementations send reply packets without the requestor field. Eventually, when the old implementations go away, fix it by moving the other CROCK (below) up to this position.
IF bytes < translationShortPacketBytes THEN {
tooShort ← tooShort.SUCC;
RETURN [buffer] };
TRUSTED { bH ← LOOPHOLE[buffer] };
SELECT TRUE FROM
bH.translationType = requestType => {
CROCK: the following test should be moved up to replace the previous CROCK.
IF bytes < translationPacketBytes THEN {
tooShort ← tooShort.SUCC;
RETURN [buffer] };
IF bH.replier.pupHost = network.pup.host THEN {
requestsReceived ← requestsReceived.SUCC;
AddTranslation[cH, bH.requestor];
ConvertToReply[cH, bH];
EnqueueForSending[cH, buffer];
buffer ← NIL;
};
};
bH.translationType = replyType => {
repliesReceived ← repliesReceived.SUCC;
AddTranslation[cH, bH.replier];
};
ENDCASE => {
badProtocol ← badProtocol.SUCC };
RETURN[buffer];
};
Daemon Process
EnqueueForSending: ENTRY PROC [cH: Cache, b: Buffer] ~ {
IF cH.sendHead = NIL THEN cH.sendHead ← b ELSE cH.sendTail.ovh.next ← b;
cH.sendTail ← b;
b.ovh.next ← NIL;
NOTIFY cH.event };
InternalEnqueueForSending: INTERNAL PROC [cH: Cache, b: Buffer] ~ {
IF cH.sendHead = NIL THEN cH.sendHead ← b ELSE cH.sendTail.ovh.next ← b;
cH.sendTail ← b;
b.ovh.next ← NIL;
NOTIFY cH.event };
DequeueForSending: ENTRY PROC [cH: Cache] RETURNS [b: Buffer] ~ {
IF (b ← cH.sendHead) = NIL THEN RETURN;
IF (cH.sendHead ← NARROW[b.ovh.next]) = NIL THEN cH.sendTail ← NIL;
};
InternalSendQueueIsEmpty: INTERNAL PROC [cH: Cache] RETURNS [BOOL] ~ INLINE {
RETURN [cH.sendHead = NIL] };
WaitAndScanCache: ENTRY PROC [cH: Cache] ~ {
eH, prevH: CacheEntry;
IF InternalSendQueueIsEmpty[cH] AND NOT cH.newPendingEntry THEN {
TRUSTED { IF cH.pendingEntries # NIL
THEN Process.SetTimeout[@cH.event, resendTimeout]
ELSE Process.SetTimeout[@cH.event, sweepCheckTimeout] };
WAIT cH.event };
prevH ← NIL; eH ← cH.pendingEntries;
WHILE eH # NIL DO
IF PulsesSince[eH.timeStamp] >= pulsesPerResend THEN {
IF eH.tries >= maxTries THEN {
Delete the entry.
eH ← eH.next;
IF prevH = NIL THEN cH.pendingEntries ← eH ELSE prevH.next ← eH;
LOOP };
eH.tries ← eH.tries + 1;
eH.timeStamp ← BasicTime.GetClockPulses[];
{ buffer: Buffer ~ MakeRequest[cH, eH.hosts.pupHost];
InternalEnqueueForSending[cH, buffer] };
};
prevH ← eH; eH ← eH.next;
ENDLOOP;
cH.newPendingEntry ← FALSE;
IF PulsesSince[cH.sweepCheckTime] >= pulsesPerSweepCheck THEN {
Do a sweep check ...
cH.sweepCheckTime ← BasicTime.GetClockPulses[];
IF cH.sweepChecksUntilSweep = 0
THEN {
Do a sweep ...
FOR i: HashIndex IN [0..numHashHeaders) DO
eH ← cH.validEntries[i]; prevH ← NIL;
WHILE eH # NIL DO
IF eH.referenced
THEN {
eH.referenced ← FALSE;
prevH ← eH; eH ← eH.next }
ELSE {
Delete the entry.
eH ← eH.next;
IF prevH = NIL THEN cH.validEntries[i] ← eH ELSE prevH.next ← eH };
ENDLOOP;
ENDLOOP;
cH.sweepChecksUntilSweep ← sweepChecksPerSweep;
}
ELSE {
cH.sweepChecksUntilSweep ← cH.sweepChecksUntilSweep - 1;
};
};
};
Daemon: PROC [network: Network] ~ {
cH: Cache ~ NARROW[ network.pup.translation ];
buffer: Buffer;
Process.SetPriority[3]; -- ???
DO
WaitAndScanCache[cH];
WHILE (buffer ← DequeueForSending[cH]) # NIL DO
network.pup.sendTranslate[network, buffer, translationPacketBytes];
CommDriver.FreeBuffer[buffer];
ENDLOOP;
ENDLOOP;
};
Initialization
Init: PROC = {
Install a cache (and start a daemon) for each ethernet on the chain.
cH: Cache;
FOR network: Network ← CommDriver.GetNetworkChain[], network.next UNTIL network = NIL DO
IF network.type # ethernet THEN LOOP;
cH ← NEW[ CacheObject ← [] ];
cH.broadcastHostEntry ← NEW[ CacheEntryObject ← [hosts~[nsHost~XNS.broadcastHost, pupHost~CommDriverType.ethernetOneBroadcastHost, filler~], referenced~, timeStamp~, tries~] ];
cH.thisHostEntry ← NEW[ CacheEntryObject ← [hosts~[nsHost~thisHost, pupHost~network.pup.host, filler~], referenced~, timeStamp~, tries~] ];
network.pup.translation ← cH;
network.pup.getEncapsulation ← GetEncapsulation;
CommDriver.InsertReceiveProc[network~network, type~pupTranslate, proc~RecvTranslation];
cH.daemon ← FORK Daemon[network];
ENDLOOP;
};
Hack for debugging use from the Interpreter:
TryHost: PROC [pupHost: Pup.Host] ~ {
FOR network: Network ← CommDriver.GetNetworkChain[], network.next UNTIL network = NIL DO
IF network.type = ethernet THEN {
[] ← GetEncapsulation[network, pupHost]; EXIT };
ENDLOOP;
};
Init[];
}.