-- File: RoutingTableImpl.mesa - last edit:
-- AOF 7-Jan-88 18:48:32
-- WIrish 26-Jan-87 11:38:03
-- SMA 23-May-86 8:56:17
-- Copyright (C) 1984, 1985, 1986, 1987, 1988 by Xerox Corporation. All rights reserved.
-- Function: The implementation module for the Pilot NS Router's routing table.
DIRECTORY
Buffer,
CommFlags USING [doDebug, doStats],
CommPriorities USING [receiver],
Driver USING [GetDeviceChain, Glitch, Device, PutOnGlobalDoneQueue],
Environment USING [bytesPerWord, wordsPerPage],
Inline USING [DBITSHIFT, BITAND, LongCOPY],
NSConstants USING [routingInformationSocket],
NSTypes USING [
BufferBody, bytesPerIDPHeader, RoutingInfoTuple, RoutingInfoType,
TransportControl, bytesPerRoutingHeader],
Mopcodes USING [zEXCH],
NSBuffer USING [
AccessHandle, Body, Dequeue, Enqueue, GetBuffer, Buffer, QueueCleanup,
QueueInitialize, QueueObject, ReturnBuffer],
Process USING [
Abort, Detach, EnableAborts, GetPriority, MsecToTicks, Priority,
SetPriority, SetTimeout, Yield],
Protocol1 USING [EncapsulateAndTransmit, GetContext],
RoutingTable USING [Handle, FlushCacheProc, NetworkContext, Object],
Router USING [FindMyHostID, infinity, NoTableEntryForNet, RoutersFunction],
RouterInternal USING [
BroadcastThisPacket, SendErrorPacket, XmitStatus],
RouterOps USING [RouteDetails, DetailSequence, DetailList],
Socket USING [
Create, ChannelHandle, Delete, GetBufferPool, GetPacket, GetSendBuffer,
SetPacketBytes, SetWaitTime, PutPacket, TimeOut],
SocketInternal USING [ListenerProcType, CreateListen],
Space USING [GetMapUnitAttributes, Interval, ScratchMap, Unmap, Usage],
SpaceUsage USING [CommunicationUsage],
Stats USING [StatIncr],
SpecialSystem USING [HostNumber, NetworkNumber, SocketNumber],
System USING [
GetClockPulses, HostNumber, MicrosecondsToPulses, NetworkAddress,
NetworkNumber, nullHostNumber, nullNetworkNumber, broadcastHostNumber,
nullSocketNumber, nullNetworkAddress];
RoutingTableImpl: MONITOR
IMPORTS
NSBuffer, Driver, Inline, Process, Protocol1, Router,
RouterInternal, Socket, SocketInternal, Space, Stats, System
EXPORTS Buffer, RouterInternal, RouterOps, System =
BEGIN
--EXPORTed TYPEs
Device: PUBLIC <<Buffer>> TYPE = Driver.Device;
NetworkNumber: PUBLIC <<System>> TYPE = SpecialSystem.NetworkNumber;
HostNumber: PUBLIC <<System>> TYPE = SpecialSystem.HostNumber;
SocketNumber: PUBLIC <<System>> TYPE = SpecialSystem.SocketNumber;
PutOnGlobalDoneQueue: PROC[b: NSBuffer.Buffer] =
LOOPHOLE[Driver.PutOnGlobalDoneQueue]; --just to save n LOOPHOLEs
TableBase: TYPE = LONG BASE POINTER;
TableRelative: TYPE = TableBase RELATIVE ORDERED POINTER
[0..LAST[CARDINAL]] TO TableElement;
TableElement: TYPE = RECORD[
net: NetworkNumber ← System.nullNetworkNumber, --ultimate destination net
delay: CARDINAL[0..256) ← FIRST[CARDINAL], --measured in hops to remote net
age: CARDINAL[0..256) ← youngster, --age without new routing info
lastAccess: LONG CARDINAL ← System.GetClockPulses[], --when last used
context: RoutingTable.NetworkContext ← NIL, --access to driver
route: HostNumber ← System.nullHostNumber, --immediate host along the way
rlink: TableRelative ← endLink]; --link to overflow element
Entry: TYPE = LONG POINTER TO TableElement; --what's passed around inside
table: RECORD[
base: TableBase ← NIL, --base of routing table
nEntries: NATURAL ← 0, --currently in the table
fill: NATURAL ← 0, --radius of table to maintain
users: NATURAL ← 0, --number of requests with non-zero fill requests
maxLink: TableRelative ← nullLink, --number overflow will hold
cache: TableRelative ← cacheHint, --emtpy slot hint
me: TableElement ← []]; --primary net number
endLink: TableRelative = LAST[TableRelative];
nullLink: TableRelative = FIRST[TableRelative];
defaultFirstPages: NATURAL = 2; --starting off point
defaultGrowPages: NATURAL = 8; --how much we add each time
firstTablePages: NATURAL ← defaultFirstPages; --getting off the ground
growTablePages: NATURAL ← defaultGrowPages; --how much we add each time
maxTableSize: NATURAL ← defaultFirstPages + (4 * defaultGrowPages);
hashLog: NATURAL = 16; --number of entries in basic table
hashMask: NATURAL = hashLog - 1; --used as bitand mask for hashing
cacheHint: TableRelative = LOOPHOLE[hashLog * SIZE[TableElement]];
tableUsage: Space.Usage = FIRST[SpaceUsage.CommunicationUsage] + 2;
youngster: CARDINAL[0..256) = 0; --age of fresh entries
longerAge: CARDINAL[0..256) = 3; --age at which we accept longer routes
flushAge: CARDINAL[0..256) = 6; --age at which we flush old entries
routerProcess: PROCESS; -- does cache fault and sends buffer.
mask: CARDINAL = 31; -- for masking out the high eleven bits of the incoming
-- delay, as we (vanilla routing tables) don't use them.
startEnum: NetworkNumber = System.nullNetworkNumber;
endEnum, allNets: NetworkNumber = [177777B, 177777B];
myHostID: HostNumber; -- host ID of this system element.
tableLife: LONG CARDINAL ←
System.MicrosecondsToPulses[90D6]; -- 90 sec, life in table w/o accesses.
timeToCheck: LONG CARDINAL ←
System.MicrosecondsToPulses[25D6]; -- 25 sec, scan interval through table
faultDally: LONG CARDINAL ←
System.MicrosecondsToPulses[25D5]; -- 2.5 sec, to gather cache faults
initialTransportControl: NSTypes.TransportControl = [
trace: FALSE, filler: 0, hopCount: 0]; -- for sending broadcasts.
localHop: CARDINAL = 0; -- delay for attached network(s).
myAccessHandle: NSBuffer.AccessHandle; -- for obtaining buffers when sending
-- via driver.
cH: Socket.ChannelHandle; -- routing information socket handle.
pleaseStop: BOOLEAN; -- switch to tell processes to stop.
infoArrival: CONDITION; -- requested routing info arrived.
requestedInfoQueue: NSBuffer.QueueObject; -- queue of directed responses.
waitingDirectResp: CARDINAL; -- do we want to keep direct resp's?
clientFills: CARDINAL; -- number of non zero fills.
routerCondition: CONDITION; --buffer to find route for and send.
outQueue: NSBuffer.QueueObject; --send buffers that need routing information.
-- Routing Table Object
defaultRth: PUBLIC RoutingTable.Handle ← @rto; --so router can find us
rto: RoutingTable.Object ← [
type: vanillaRouting, start: Start, stop: Stop,
startEnumeration: startEnum, endEnumeration: endEnum,
enumerate: Enumerate, fillTable: Fill,
getDelay: GetDelay, transmit: Transmit,
forward: Forward, findNetwork: FindLocalNetID,
addNetwork: AddDriver, removeNetwork: RemoveDriver,
flushCache: FlushCache, stateChanged: ChangedState];
maxListLength: NATURAL ← 100; --max length of table lists
badEntry: NATURAL ← 0; --for counting bogus entries added to table
RoutingTableScrambled: ERROR = CODE; --mass confusion, etc
DriverDidntNoteNet: ERROR = CODE; --buffer came from busted driver
NoBufferInQueue: ERROR = CODE; --queue length > 0 but no buffers
CacheFaultFailure: ERROR = CODE; --supposed to spend 2.5 seconds faulting
timeOfLastCheck: LONG CARDINAL ← System.GetClockPulses[];
SanityCheck: <<DEBUGGING>> INTERNAL PROC[] = INLINE
BEGIN
SELECT TRUE FROM
(table.base = NIL) => RETURN; --not ready
(Inline.BITAND[table.cache, 8000H] # 0) =>
Driver.Glitch[RoutingTableScrambled];
(Inline.BITAND[table.maxLink, 8000H] # 0) =>
Driver.Glitch[RoutingTableScrambled];
(table.cache > table.maxLink) =>
Driver.Glitch[RoutingTableScrambled];
ENDCASE;
END; --SanityCheck
AddElement: INTERNAL PROC[net: NetworkNumber] RETURNS[entry: Entry] =
BEGIN
hash, link: TableRelative;
IF CommFlags.doDebug AND (net.a # 0) THEN badEntry ← SUCC[badEntry];
SELECT TRUE FROM
(net = System.nullNetworkNumber) => RETURN[NIL]; --I ain't add'n that
(net = table.me.net) => RETURN[@table.me]; --that one's special
(table.base = NIL) => {Expand[]; RETURN[AddElement[net]]};
(table.base[hash ← Hash[net.b]].net # System.nullNetworkNumber) =>
BEGIN
--start a serial search at table.cache looking for an empty slot
FOR link ← table.cache, link + SIZE[TableElement]
WHILE link < table.maxLink DO
IF table.base[link].net = System.nullNetworkNumber THEN
BEGIN
table.cache ← link + SIZE[TableElement]; --update cache
(entry ← @table.base[link])↑ ← [ --set net and copy old link
net: net, rlink: table.base[hash].rlink, age: youngster];
table.base[hash].rlink ← link; --link base entry to new
EXIT;
END;
REPEAT FINISHED => {Expand[]; RETURN[AddElement[net]]};
ENDLOOP;
END;
ENDCASE => (entry ← @table.base[hash])↑ ← [net: net]; --primary slot
table.nEntries ← SUCC[table.nEntries]; --and count the entry
IF CommFlags.doDebug THEN SanityCheck[];
END; --AddElement
AddDriver: <<PUBLIC>> PROC [context: RoutingTable.NetworkContext] =
BEGIN
-- If the net number of the new attached network is known, calls
-- AddDriverLocked to tell the routing table about it, else tries
-- to find out net number by probing an INR.
IF context.netNumber = System.nullNetworkNumber THEN ProbeINRs[]
ELSE AddDriverLocked[context];
END; --AddDriver
AddDriverInternal: INTERNAL PROC [context: RoutingTable.NetworkContext] =
BEGIN
<<
Tells the routing table about a new attached network. The driver
must know the network number at this point. Called by
ExamineResponse and Locked, both of which hold the monitor.
>>
entry: Entry;
SELECT TRUE FROM
(~context.network.alive) => RETURN;
((entry ← FindElement[context.netNumber, FALSE]) # NIL) => RETURN;
((entry ← AddElement[context.netNumber]) = NIL) => RETURN; --null net
ENDCASE;
entry.context ← context; --finish loading the entry
IF CommFlags.doDebug AND (entry.net = System.nullNetworkNumber) THEN
Driver.Glitch[RoutingTableScrambled];
END; --AddDriverInternal
AddDriverLocked: ENTRY PROC [context: RoutingTable.NetworkContext] = INLINE
{AddDriverInternal[context]};
CheckRoutingTableEntries: INTERNAL PROC[] =
BEGIN
<<
Scan the routing table aging entries as we go.
Delete any entries that are so old that they should be flushed.
Start the scan at each of the hashLog base entries, chasing the links
until we hit an endLink. If deleting causes the table to collapse, take
an early return.
>>
prev, this, next: TableRelative;
hashLink: TableRelative ← nullLink; --equivalent to base slot==0;
IF table.base = NIL THEN RETURN; --doesn't take long to figure that out
THROUGH[0..hashLog) DO
prev ← endLink; --just a known value to compare with
IF CommFlags.doDebug THEN SanityCheck[];
FOR this ← hashLink, next UNTIL this = endLink DO
entry: Entry ← @table.base[this]; --for faster access
IF entry.net = System.nullNetworkNumber THEN EXIT; --empty|broken chain
next ← entry.rlink; --'next' is after entry to empty
SELECT TRUE FROM
(entry.context.network.device > ethernetOne) => prev ← this;
((entry.age ← entry.age.SUCC) >= flushAge), --the entry is old
((entry.delay > table.fill) AND --not filling this full
((System.GetClockPulses[] - entry.lastAccess) > tableLife)) =>
BEGIN
IF (table.nEntries ← PRED[table.nEntries]) = 0 THEN
BEGIN
table.base ← Space.Unmap[table.base];
table.maxLink ← nullLink; table.cache ← cacheHint;
RETURN; --that's about the end of it
END;
SELECT TRUE FROM
(prev # endLink) => --in the middle or at the end
BEGIN
table.base[prev].rlink ← next; --'prev' ← 'next'; empty 'this'
END;
(next # endLink) => --at the beginning and not a lone entry
BEGIN
entry↑ ← table.base[next]; --copy 'next' on top of 'this'
this ← next; --set up so that 'this'='next' gets emptied
next ← hashLink; --back to beginning of the list
END;
ENDCASE; --the one and only entry, and it's from the base
table.base[this] ← []; --empty 'this' entry
table.cache ← MAX[cacheHint, MIN[table.cache, this]]; --adjust cache
END;
ENDCASE => prev ← this; --chase the chain
ENDLOOP;
hashLink ← hashLink + SIZE[TableElement]; --go to next bucket of base
Process.Yield[]; --share the candy a little bit
ENDLOOP;
IF CommFlags.doDebug THEN SanityCheck[];
END; -- of CheckRoutingTableEntries
ChangedState: PROC[context: RoutingTable.NetworkContext] =
BEGIN
RemoveDriver[context];
AddDriver[context];
END; --ChangedState
CleanUpRoutingTable: ENTRY PROC = INLINE
{IF table.base # NIL THEN [] ← Space.Unmap[table.base]; table ← []};
Enumerate: <<PUBLIC>> ENTRY PROCEDURE[
previous: System.NetworkNumber, delay: CARDINAL]
RETURNS [net: System.NetworkNumber ← endEnum] =
BEGIN
LongSwap: PROC[net: System.NetworkNumber] RETURNS[LONG CARDINAL] =
MACHINE CODE {Mopcodes.zEXCH};
NetAgtNetB: PROC [a, b: System.NetworkNumber] RETURNS [BOOLEAN] = INLINE
{RETURN[LongSwap[a] > LongSwap[b]]};
TestThisNet: PROC[] RETURNS [] =
BEGIN
SELECT TRUE FROM
(tuple.objectNetID = System.nullNetworkNumber) => NULL;
(tuple.interrouterDelay = delay)
AND NetAgtNetB[tuple.objectNetID, previous]
AND ~NetAgtNetB[tuple.objectNetID, net] => net ← tuple.objectNetID;
ENDCASE;
END; --TestThisNet
<<
The first entry processed is always the local net. This makes the
loading of 'tuple' and seem strange. Have faith.
>>
hashLink: TableRelative ← nullLink; --equivalent to base slot==0;
tuple: NSTypes.RoutingInfoTuple ← [table.me.net, table.me.delay];
TestThisNet[]; --try the local net first
THROUGH[0..hashLog) WHILE (table.base # NIL) DO
rlink: RoutingTableImpl.TableRelative;
FOR rlink ← hashLink, rlink ← table.base[rlink].rlink
UNTIL rlink = endLink DO
tuple ← [table.base[rlink].net, table.base[rlink].delay];
TestThisNet[]; --is this one any better?
ENDLOOP;
hashLink ← hashLink + SIZE[TableElement];
ENDLOOP;
END; --Enumerate
Expand: INTERNAL PROC[] =
BEGIN
interval: Space.Interval;
stretch, original: CARDINAL;
IF CommFlags.doDebug THEN SanityCheck[];
original ← LOOPHOLE[table.maxLink];
IF table.base = NIL THEN interval ← [NIL, firstTablePages]
ELSE
BEGIN
interval ← Space.GetMapUnitAttributes[table.base].interval;
interval.count ← interval.count + growTablePages;
IF CommFlags.doDebug AND (interval.count > maxTableSize) THEN
Driver.Glitch[RoutingTableScrambled];
END;
table.base ← Space.ScratchMap[interval.count, tableUsage]; --allocate new
IF original # 0 THEN
Inline.LongCOPY[to: table.base, from: interval.pointer, nwords: original];
IF interval.pointer # NIL THEN [] ← Space.Unmap[interval.pointer, return];
table.base[table.maxLink] ← []; --default first entry of new segment
table.maxLink ← LOOPHOLE[ --compute new biggest value
((CARDINAL[interval.count] * Environment.wordsPerPage) /
SIZE[TableElement]) * SIZE[TableElement]];
stretch ← LOOPHOLE[table.maxLink, CARDINAL] - original; --we added this much
Inline.LongCOPY[ --ripple those suckers in there
from: table.base + original,
to: table.base + original + SIZE[TableElement],
nwords: stretch - SIZE[TableElement]];
IF CommFlags.doDebug THEN SanityCheck[];
END; --Expand
Fill: PROC[maxDelay: CARDINAL ← Router.infinity] =
BEGIN
InsertIntoTable: ENTRY PROC[b: NSBuffer.Buffer] = INLINE
{[] ← UpdateRoutingTable[b]};
SendOneRequest: PROC[destination: System.NetworkNumber] =
BEGIN
nsb: NSBuffer.Body;
b: NSBuffer.Buffer;
timeIn: LONG CARDINAL;
cH: Socket.ChannelHandle ← Socket.Create[
System.nullSocketNumber, 1, 10, routingInformation];
Socket.SetWaitTime[cH, 1000]; --only want to wait short times
BEGIN
ENABLE UNWIND => Socket.Delete[cH]; --client tired of waiting?
b ← Socket.GetSendBuffer[cH]; --just one buffer, please
nsb ← b.ns; --and then get a local cache of b.ns
nsb.destination ← him; --send it to him
nsb.packetType ← routingInformation; --we want information
nsb.routingType ← routingInfoRequest; --so we ask nicely
nsb.routingTuple[0] ← [destination, Router.infinity]; --just for grins
Socket.SetPacketBytes[
b, NSTypes.bytesPerRoutingHeader +
Environment.bytesPerWord * SIZE[NSTypes.RoutingInfoTuple]];
<<
Thre is no reason this should be a Router.Broadcast when the destination
is a multicast. If there's no ethernet, then it will go on the phone line
anyhow. If there is an ethernet, then it will certainly beat the phone
line for response time, won't it?
>>
Socket.PutPacket[cH, b]; b ← NIL; --it's on its way
timeIn ← System.GetClockPulses[]; --this is when we start
WHILE (System.GetClockPulses[] - timeIn) < dawdle DO
b ← Socket.GetPacket[cH ! Socket.TimeOut => LOOP];
him ← b.ns.source; --next time we'll be using just him
IF destination = System.nullNetworkNumber THEN
{NSBuffer.ReturnBuffer[b]; EXIT}; --get rid of buffer and bail out
InsertIntoTable[b]; --stick it in the real table
NSBuffer.ReturnBuffer[b]; --remember to free the buffer
ENDLOOP; --WHILE (System.GetClockPulses[] - timeIn) < dawdle DO
END;
Socket.Delete[cH]; --gun the socket - clean up the queue
END;
dawdle: LONG CARDINAL;
him: System.NetworkAddress;
SELECT TRUE FROM
(maxDelay = 0) => IF (TestAndSet[-1] = 0) THEN table.fill ← 0;
(maxDelay > table.fill) =>
BEGIN
[] ← TestAndSet[+1]; --count new user
dawdle ← System.MicrosecondsToPulses[2D6]; --waiting for answers
table.fill ← maxDelay; --record the max ever requested
him ← [
table.me.net,
System.broadcastHostNumber,
NSConstants.routingInformationSocket];
SendOneRequest[table.me.net]; --will reset 'him'
SendOneRequest[endEnum]; --talks only to 'him'
END;
ENDCASE => [] ← TestAndSet[+1]; --count new user
END; --Fill
FindLocalNetID: <<PUBLIC>> ENTRY PROC [destNetNum: System.NetworkNumber]
RETURNS [localNet: System.NetworkNumber] =
BEGIN
e: Entry;
localNet ← IF (e ← FindElement[destNetNum, FALSE]) = NIL THEN
System.nullNetworkNumber ELSE e.context.netNumber;
IF localNet = System.nullNetworkNumber THEN
--since System.nullNetworkNumber isn't helpful, find the first
--network with a known network number and use it.
FOR n: Device ← Driver.GetDeviceChain[], n.next UNTIL n = NIL DO
c: RoutingTable.NetworkContext ← Protocol1.GetContext[n, ns];
SELECT TRUE FROM
(c = NIL) => NULL; --not even close
(~c.network.alive) => NULL; --no better
(c.netNumber = System.nullNetworkNumber) => NULL; --still bad
ENDCASE => {localNet ← c.netNumber; EXIT}; --reasonable substitute
ENDLOOP;
END; --FindLocalNetID
FindElement: INTERNAL PROC[net: NetworkNumber, sort: BOOLEAN]
RETURNS[entry: Entry] =
BEGIN
prev, hash: TableRelative;
SELECT TRUE FROM
(net = System.nullNetworkNumber) => RETURN[NIL];
(net = table.me.net) => RETURN[@table.me];
(table.base = NIL) => RETURN[NIL];
ENDCASE;
IF CommFlags.doDebug THEN SanityCheck[];
hash ← prev ← Hash[net.b]; --remember this value
FOR link: TableRelative ← hash, table.base[link].rlink
UNTIL link = endLink DO
IF CommFlags.doDebug AND (link # hash) AND
(table.base[link].net = System.nullNetworkNumber) THEN
Driver.Glitch[RoutingTableScrambled];
IF table.base[link].net = net THEN
BEGIN
entry ← @table.base[link]; --compute (maybe) return value
SELECT TRUE FROM
(link = hash) => RETURN; --#1 of list
(link = table.base[hash].rlink) => RETURN; --#2 in list
(~sort) => RETURN; --requested that we NOT sort the table
ENDCASE; --needs sorting
table.base[prev].rlink ← entry.rlink; --remove 'link'
entry.rlink ← table.base[hash].rlink; --stick just after hash
table.base[hash].rlink ← link; --so it will be #2 next time
IF CommFlags.doDebug THEN --just check'n
BEGIN
SanityCheck[];
SELECT System.nullNetworkNumber FROM
entry.net => Driver.Glitch[RoutingTableScrambled];
table.base[hash].net => Driver.Glitch[RoutingTableScrambled];
table.base[prev].net => Driver.Glitch[RoutingTableScrambled];
ENDCASE;
END;
RETURN; --don't forget to get out
END;
prev ← link; --remember last value
ENDLOOP;
RETURN[NIL]; --didn't find it
END; --FindElement
FlushCache: <<PUBLIC>> ENTRY RoutingTable.FlushCacheProc =
-- PROC [net: System.NetworkNumber]
BEGIN
-- Removes the entry with the specified net number from the routing
-- table upon client request. The request is ignored if there is no
-- entry or if the entry is an attached net.
RemoveElement[net];
IF CommFlags.doStats THEN Stats.StatIncr[statCacheFlushed];
END; -- FlushCache
Forward: PROC [b: NSBuffer.Buffer] =
BEGIN
IF b.ns.source.host # myHostID THEN
RouterInternal.SendErrorPacket[b, cantGetThere, 0]
ELSE
{b.fo.status ← RouterInternal.XmitStatus[noRouteToNetwork];
PutOnGlobalDoneQueue[b]}; --return b to the system buffer pool
IF CommFlags.doStats THEN Stats.StatIncr[statNSNotForwarded];
END; --Forward
Hash: PROC[n: CARDINAL] RETURNS[TableRelative] = INLINE
{RETURN[LOOPHOLE[Inline.BITAND[n, hashMask] * SIZE[TableElement]]]};
GetCurrentFill: PUBLIC <<RouterOps>> PROC RETURNS[NATURAL, NATURAL] =
{RETURN[table.fill, table.users]};
GetDelay: PROC [net: NetworkNumber] RETURNS [delay: CARDINAL] =
BEGIN
-- Gets the delay to specified net.
LockedFindNet: ENTRY PROC RETURNS[BOOLEAN] = INLINE
BEGIN
entry: Entry = FindElement[net, TRUE];
IF entry = NIL THEN RETURN[FALSE]
ELSE {delay ← entry.delay; RETURN[TRUE]};
END; --LockedFindNet
IF net = System.nullNetworkNumber THEN RETURN[0]; --this is easy
IF ~LockedFindNet[] THEN
BEGIN
--Try a cache fault to get the information.
IF CommFlags.doDebug THEN
BEGIN
timein: LONG CARDINAL = System.GetClockPulses[]; --that's when it starts
RoutingTableCacheFault[net]; --this could take time
SELECT TRUE FROM
(LockedFindNet[]) => NULL; --we got the answer
((System.GetClockPulses[] - timein) < faultDally) =>
Driver.Glitch[CacheFaultFailure];
ENDCASE => ERROR Router.NoTableEntryForNet;
END
ELSE
BEGIN
RoutingTableCacheFault[net]; --this could take time (< faultDally).
IF ~LockedFindNet[] THEN ERROR Router.NoTableEntryForNet;
END;
END;
END; --GetDelay
GetRouteInfo: PUBLIC <<RouterOps>> ENTRY PROC[net: NetworkNumber]
RETURNS[delay: CARDINAL, details: RouterOps.RouteDetails] =
BEGIN
device: Device;
entry: Entry ← FindElement[net, FALSE];
IF entry = NIL THEN RETURN WITH ERROR Router.NoTableEntryForNet;
device ← entry.context.network;
delay ← entry.delay; --copy that one out
details ← [
driverType: device.device,
driverNetwork: entry.net, statsPtr: device.stats,
via: [entry.context.netNumber, entry.route, System.nullSocketNumber]];
END; --GetRouteInfo
ProbeINRs: PROC =
BEGIN
-- Does multiple BroadcastRoutingRequests to find out the network
-- numbers, called at start time.
b: NSBuffer.Buffer ← NIL;
NullNetNum: ENTRY PROC RETURNS [BOOLEAN] =
BEGIN
--checks if any networks on device chain have null net numbers.
FOR n: Device ← Driver.GetDeviceChain[], n.next UNTIL n = NIL DO
c: RoutingTable.NetworkContext ← Protocol1.GetContext[n, ns];
SELECT TRUE FROM
(c = NIL) => LOOP; --what! a driver that doesn't support us!
(~c.network.alive) => LOOP; --he would but he croaked
(c.netNumber = System.nullNetworkNumber) => RETURN[TRUE];
ENDCASE;
ENDLOOP;
RETURN [FALSE];
END; -- NullNetNum
<<
This could take a long time if we had trouble getting responses. (10 sec)
This may be over-kill for one net that is still unnumbered, since
a cache fault will force a broadcast on all nets.
>>
THROUGH [0..4) UNTIL pleaseStop DO
IF NullNetNum[] THEN RoutingTableCacheFault[allNets] ELSE EXIT;
ENDLOOP;
END; --ProbeINRs
ProcessRoutingInfoPacket: PROC [b: NSBuffer.Buffer] =
BEGIN
ExamineResponsePacket: ENTRY PROC =
BEGIN
SELECT TRUE FROM
(nsb.destination.host = myHostID) =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statDirectedReceived];
IF (waitingDirectResp = 0) THEN NSBuffer.ReturnBuffer[b]
ELSE {NSBuffer.Enqueue[@requestedInfoQueue, b]; NOTIFY infoArrival};
END;
ENDCASE => -- process gratuitous packet.
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statGratReceived];
[] ← UpdateRoutingTable[b];
NSBuffer.ReturnBuffer[b];
END;
END; -- of ExamineResponsePacket
-- start of procedure
nsb: NSBuffer.Body = b.ns;
incomingContext: RoutingTable.NetworkContext ← b.fo.context;
SELECT TRUE FROM
--interested only in routing protocol packets
(nsb.packetType # routingInformation) => NULL;
--can't learn anything new from ourselves
(nsb.source.host = myHostID) => NULL;
--sender should know himself
(nsb.source.host = System.nullHostNumber) => NULL;
--buffer didn't arrive through one of our drivers
(incomingContext = NIL) => IF CommFlags.doDebug THEN
Driver.Glitch[DriverDidntNoteNet];
--we are not INR, so ignore routing requests
(nsb.routingType = routingInfoRequest) => NULL;
--not a request, not a response - so what is it?
(nsb.routingType # routingInfoResponse) => NULL;
--is the network it came in on alive?
(~incomingContext.network.alive) => NULL;
--did this come from our local net?
(nsb.transportControl.hopCount = 0) =>
BEGIN
IF CommFlags.doStats THEN Stats.StatIncr[statNSGatewayPacketsRecv];
ExamineResponsePacket[];
RETURN;
END;
ENDCASE;
IF CommFlags.doStats THEN Stats.StatIncr[statJunkBroadcastNS];
NSBuffer.ReturnBuffer[b];
END; -- ProcessRoutingInfoPacket
RemoveElement: INTERNAL PROC[net: NetworkNumber] =
BEGIN
<<
The entry we want deleted gets replaced by the .rlink entry and the .rlink
entry gets marked empty. That covers us in cases where the entry to be
deleted is the one that's in the hash base, but it pushes the problem to
the case removing from the end of the list. For that case we have to
keep track of the previous entry so we can change his .rlink to endLink.
The ENDCASE is removing the one and only entry, and its the base.
No notification is given of entries not found.
>>
prev, this, next: TableRelative;
IF CommFlags.doDebug THEN SanityCheck[];
IF table.base = NIL THEN RETURN; --I claim that's as good as removed
prev ← endLink; --well known starting value
FOR this ← Hash[net.b], table.base[this].rlink UNTIL this = endLink DO
IF table.base[this].net # net THEN {prev ← this; LOOP}; --wrong entry
IF (table.nEntries ← PRED[table.nEntries]) = 0 THEN
BEGIN
table.base ← Space.Unmap[table.base]; --we just gun the whole space
table.maxLink ← nullLink; table.cache ← cacheHint; --reset these
EXIT; --that's the quick and easy way to clean up
END;
next ← table.base[this].rlink; --'next' is after entry to empty
SELECT TRUE FROM
(prev # endLink) => --in the middle or at the end
BEGIN
table.base[prev].rlink ← next; --link 'prev' to 'next'; empty 'this'
END;
(next # endLink) => --at the beginning and not a lone entry
BEGIN
table.base[this] ← table.base[next]; --copy 'next' on top of 'this'
this ← next; --and then set up so that 'this' = 'next' gets emptied
END;
ENDCASE; --the one and only entry, and it's from the base
table.base[this] ← []; --emtpy 'this' entry
table.cache ← MAX[cacheHint, MIN[table.cache, this]];
EXIT; --and, since we found what we're looking for....
ENDLOOP;
IF CommFlags.doDebug THEN SanityCheck[];
END; --RemoveElement
RemoveDriver: ENTRY PROC [context: RoutingTable.NetworkContext] =
BEGIN
<<
This procedure removes the specified attached network and all entries
referencing it from the routing table. Multi entries may use this net.
The loop doesn't increment the count on elements removed because the
number of entries will be decremented by the "RemoveElement[entry.net]"
statement. In effect the number of entries is coming back to equal the
number of elements counted.
>>
entry: Entry;
count: NATURAL ← 0;
FOR entry ← @table.base[FIRST[TableRelative]], entry + SIZE[TableElement]
UNTIL count = table.nEntries DO
SELECT TRUE FROM
(entry.context = NIL) => LOOP; --don't increment count
(entry.context = context) => RemoveElement[entry.net];
ENDCASE => count ← SUCC[count];
ENDLOOP;
END; --RemoveDriver
RoutingTableActivate: ENTRY PROC = INLINE
BEGIN
myHostID ← Router.FindMyHostID[];
table ← []; --set initial values
NSBuffer.QueueInitialize[@requestedInfoQueue];
NSBuffer.QueueInitialize[@outQueue];
END; --RoutingTableActivate
RoutingTableCacheFault: PROC [net: NetworkNumber] =
BEGIN
<<
Broadcasts for info on specified network(s), waits for a directed response
to be queued by ProcessRoutingInfoPacket, then searches for the desired
tuple(s). Called by RouterProcess, GetDelay or Fill.
>>
CacheFaultStart: ENTRY PROC = INLINE
{waitingDirectResp ← SUCC[waitingDirectResp]};
CacheFaultDone: ENTRY PROC = INLINE
{IF (waitingDirectResp ← PRED[waitingDirectResp]) = 0 THEN
NSBuffer.QueueCleanup[@requestedInfoQueue]}; --CacheFaultDone
GatherDirectedResponses: ENTRY PROC RETURNS[done: BOOLEAN ← FALSE] =
BEGIN
<<
Unlike the timer on the caller, this one is used to fix the amount of
time between broadcasts requesting information.
>>
startBroadcast: LONG CARDINAL = System.GetClockPulses[];
UNTIL done --OR timeout-- DO
SELECT TRUE FROM
((b ← NSBuffer.Dequeue[@requestedInfoQueue]) # NIL) =>
done ← UpdateRoutingTable[b, net]; --see if this fixes it
((System.GetClockPulses[] - startBroadcast) > broadcastDally) =>
RETURN<<[FALSE]>>; --and we had no buffer
ENDCASE => {WAIT infoArrival; LOOP}; --wait for some response
NSBuffer.ReturnBuffer[b]; --get rid of buffer we dequeued
ENDLOOP;
END; --GatherDirectedResponses
b: NSBuffer.Buffer;
nsb: NSBuffer.Body;
startFault: LONG CARDINAL = System.GetClockPulses[];
broadcastDally: LONG CARDINAL = Inline.DBITSHIFT[faultDally, -2];
timesRequested, noBuffer: NATURAL ← 0; --how many times did we try/fail?
sizeRoutingRequest: NATURAL = Environment.bytesPerWord *
(SIZE[routingInformation NSTypes.BufferBody] +
SIZE[NSTypes.RoutingInfoTuple]);
CacheFaultStart[]; --keep these packets around
IF CommFlags.doStats THEN Stats.StatIncr[statCacheFaults];
<<
This loop will last until we find the answer, for some known period of
time and we've asked the question at least twice.
It is not dependent on the number of uninteresting responses received.
This should ask the question no more than 4 times.
>>
UNTIL (timesRequested > 1) AND
((System.GetClockPulses[] - startFault) > faultDally) DO
<<
Broadcasts a routing information request on all attached networks.
If <net> is supplied, the request will be for info on that net only, else
information will be requested on all networks the routing
information suppliers know about.
>>
b ← NSBuffer.GetBuffer[myAccessHandle, send, FALSE, sizeRoutingRequest];
IF b # NIL THEN
BEGIN
nsb ← b.ns; --make a local copy
nsb.packetType ← routingInformation;
nsb.transportControl ← initialTransportControl;
nsb.destination.socket ← nsb.source.socket ←
NSConstants.routingInformationSocket;
nsb.pktLength ← sizeRoutingRequest;
nsb.routingType ← routingInfoRequest;
nsb.routingTuple[0] ← [net, Router.infinity];
RouterInternal.BroadcastThisPacket[b];
timesRequested ← timesRequested.SUCC;
END
ELSE IF CommFlags.doDebug THEN noBuffer ← noBuffer.SUCC;
IF pleaseStop OR GatherDirectedResponses[] THEN EXIT;
ENDLOOP;
CacheFaultDone[]; --decrements count and flushes queue
END; --RoutingTableCacheFault
RoutingTableDeactivate: ENTRY PROC = INLINE
BEGIN
pleaseStop ← TRUE;
NSBuffer.QueueCleanup[@requestedInfoQueue];
NSBuffer.QueueCleanup[@outQueue];
END; --RoutingTableDeactivate
RoutingTableFork: SocketInternal.ListenerProcType =
{ProcessRoutingInfoPacket[b]}; -- of RoutingTableFork
RouterProcess: <<FORKED>> PROC =
BEGIN
<<
This is the process that is poked by Transmit when a cache fault
must be done before sending the buffer. It also runs the period check
of the routing table to see if we can toss some old information out.
>>
DequeueLocked: ENTRY PROC RETURNS [NSBuffer.Buffer] = INLINE
{RETURN[NSBuffer.Dequeue[@outQueue]]};
b: NSBuffer.Buffer;
UNTIL pleaseStop DO
ENABLE ABORTED => EXIT;
b ← WaitReqOrCheckTime[]; --wait for some work to do
ProcessUsersRequest[b]; --then go off and do it
ENDLOOP;
UNTIL (b ← DequeueLocked[]) = NIL DO
b.fo.status ← RouterInternal.XmitStatus[aborted];
PutOnGlobalDoneQueue[b]; --requeue it
IF CommFlags.doStats THEN Stats.StatIncr[statNSSentNowhere];
ENDLOOP;
END; --RouterProcess
WaitReqOrCheckTime: ENTRY PROC[] RETURNS[b: NSBuffer.Buffer] =
BEGIN
ENABLE UNWIND => NULL;
<<
This process services client transmit requests and also runs periodically
to check the routing table for aged entries.
>>
now: LONG CARDINAL;
WHILE outQueue.length = 0 DO
WAIT routerCondition; --wait for something to do
IF table.base = NIL THEN LOOP; --why bother?
now ← System.GetClockPulses[]; --get the current time
IF (now - timeOfLastCheck) > timeToCheck THEN
{CheckRoutingTableEntries[]; timeOfLastCheck ← now};
ENDLOOP;
b ← NSBuffer.Dequeue[@outQueue];
IF CommFlags.doDebug AND (b = NIL) THEN Driver.Glitch[NoBufferInQueue];
END; -- WaitReqOrCheckTime
ProcessUsersRequest: PROC[b: NSBuffer.Buffer] =
BEGIN
MonitoredFindNet: ENTRY PROC RETURNS[BOOLEAN] = INLINE
BEGIN
entry: Entry = FindElement[net, TRUE];
SELECT TRUE FROM
(entry = NIL) => RETURN[FALSE]; --there's no such table entry
(entry.delay = Router.infinity) => RETURN[FALSE]; --too far away
ENDCASE;
element ← entry↑; --copy it out so we can use it safely
entry.lastAccess ← System.GetClockPulses[]; --mark time last used
RETURN[TRUE];
END; -- of MonitoredFindNet
-- start of procedure
net: NetworkNumber; --that's where he wants to go
element: TableElement; --our copy of the table element
nextHost: HostNumber; -- immediate destination to get him there.
net ← b.ns.destination.net; --that's where he's going
RoutingTableCacheFault[net]; --crank up a fault
IF CommFlags.doDebug THEN
BEGIN
timein: LONG CARDINAL = System.GetClockPulses[]; --that's when it starts
RoutingTableCacheFault[net]; --crank up a fault
SELECT TRUE FROM
(MonitoredFindNet[]) => NULL; --we got the answer
((System.GetClockPulses[] - timein) < faultDally) =>
Driver.Glitch[CacheFaultFailure];
ENDCASE =>
BEGIN
-- outgoing packet for unknown net - drop it.
b.fo.status ← RouterInternal.XmitStatus[noRouteToNetwork];
PutOnGlobalDoneQueue[b]; --get it back to caller | free
IF CommFlags.doStats THEN Stats.StatIncr[statNSSentNowhere];
RETURN; --go back to caller
END;
END
ELSE
BEGIN
RoutingTableCacheFault[net]; --crank up a fault
IF ~MonitoredFindNet[] THEN
BEGIN
-- outgoing packet for unknown net - drop it.
b.fo.status ← RouterInternal.XmitStatus[noRouteToNetwork];
PutOnGlobalDoneQueue[b]; --get it back to caller | free
IF CommFlags.doStats THEN Stats.StatIncr[statNSSentNowhere];
RETURN; --go back to caller
END;
END;
-- outgoing packet to be transmitted over the correct network
nextHost ← IF element.route # System.nullHostNumber THEN element.route
ELSE b.ns.destination.host; -- we are attached to the destination net
b.fo.status ← RouterInternal.XmitStatus[goodCompletion];
b.fo.context ← element.context; --mark the context being used
b.fo.network ← element.context.network; --mark the network used
Protocol1.EncapsulateAndTransmit[LOOPHOLE[b], @nextHost];
END; -- ProcessUsersRequest
Start: PROC =
BEGIN --This procedure turns the router on.
priority: Process.Priority = Process.GetPriority[]; --save current
pleaseStop ← FALSE;
waitingDirectResp ← clientFills ← 0;
RoutingTableActivate[];
cH ← SocketInternal.CreateListen[
socket: NSConstants.routingInformationSocket,
callback: RoutingTableFork, clientData: NIL,
send: 1, receive: 10, type: routingInformation];
myAccessHandle ← Socket.GetBufferPool[cH];
Process.SetPriority[CommPriorities.receiver]; --let's get some cycles
routerProcess ← FORK RouterProcess[]; --the guy that does cache faults
Process.Detach[FORK ProbeINRs[]]; -- find out the network numbers.
Process.SetPriority[priority]; --then back to whatever we started with
END; --Start
Stop: PROC =
BEGIN
RoutingTableDeactivate[];
Process.Abort[routerProcess];
JOIN routerProcess[];
Socket.Delete[cH];
CleanUpRoutingTable[];
END; --Stop
TableList: PUBLIC <<RouterOps>> ENTRY PROC[zone: UNCOUNTED ZONE]
RETURNS[list: RouterOps.DetailList ← NIL] =
BEGIN
ENABLE UNWIND => NULL; --incase the zone.NEW blows up
entry: Entry; --for a local copy of the table's entry
device: Device; --to get the opaque type where we can look
index: NATURAL ← 0; --to get into the sequence
hashLink: TableRelative ← nullLink; --to loop through the table
IF table.base = NIL THEN RETURN; --no table, no sequence, no worry
<<
This violates the rule of asking PILOT to do something for us while we
hold are own monitor. But I don't want to worry about the table being
a different size when we copy it than when we created the node to hold
the copy. Guess we should warn users that this reduces some of the
parallelism of the router.
>>
list ← zone.NEW[RouterOps.DetailSequence[table.nEntries + 1]];
device ← table.me.context.network;
list[index] ← [ --we're always first
driverType: device.device, driverNetwork: table.me.context.netNumber,
statsPtr: device.stats, via: System.nullNetworkAddress];
THROUGH[0..hashLog) DO
FOR rlink: TableRelative ← hashLink, entry.rlink
UNTIL rlink = endLink DO
entry ← @table.base[rlink];
device ← entry.context.network;
list[(index ← SUCC[index])] ← [driverType: device.device,
driverNetwork: entry.net, statsPtr: device.stats,
via: [entry.context.netNumber, entry.route, System.nullSocketNumber]];
ENDLOOP;
hashLink ← hashLink + SIZE[TableElement]; --get to next base
ENDLOOP;
END; --TableList
Transmit: ENTRY PROC [b: NSBuffer.Buffer] =
BEGIN
<<
Called to transmit a packet, this proc searches the routing table
for the destination net. If found, the proc sends the packet. Because
the client's process should not be used to do a cache fault, if the info
is not found, the buffer is put on the outQueue and the sender process is
notified (which does the cache fault and sends the packet when the routing
info is obtained. If the destination net is null, we simply shove it out
on the first network on the chain. In case of an abort, the caller owns
the buffer.
>>
ENABLE UNWIND => NULL;
entry: Entry;
nextHost: HostNumber;
nsb: NSBuffer.Body = b.ns;
SELECT TRUE FROM
(nsb.destination.net = System.nullNetworkNumber) =>
BEGIN
--running on net with no INR (using null net numbers).
n: Device ← b.fo.network ← Driver.GetDeviceChain[];
IF n = NIL OR ~n.alive THEN
BEGIN
--no network present, drop the packet.
b.fo.status ← RouterInternal.XmitStatus[noRouteToNetwork];
PutOnGlobalDoneQueue[b]; --give it back to system
IF CommFlags.doStats THEN Stats.StatIncr[statNSSentNowhere];
RETURN;
END;
nextHost ← nsb.destination.host;
END;
(entry ← FindElement[nsb.destination.net, TRUE]) = NIL =>
BEGIN
<<
No entry in table; tell RouterProcess to find one.
But...
If this isn't a buffer being sent sourced from here AND
the number of buffers already hanging on the outQueue is
greater that 1, then forget it. Theory is that the buffers
on the queue are ones we're responding to (error or echo or
some variation of a swap source and destination).
>>
IF (b.fo.function # send) AND (outQueue.length > 1) THEN
NSBuffer.ReturnBuffer[b] ELSE NSBuffer.Enqueue[@outQueue, b];
NOTIFY routerCondition;
RETURN;
END;
(entry.delay < Router.infinity) =>
BEGIN
-- entry exists in table already.
entry.lastAccess ← System.GetClockPulses[]; --mark the entry hot
b.fo.context ← entry.context; --mark the context being used
b.fo.network ← entry.context.network; --mark the network being used
-- outgoing packet to be transmitted over the correct network
nextHost ← IF entry.route # System.nullHostNumber THEN entry.route
ELSE nsb.destination.host; -- we are attached to the destination net
END;
ENDCASE =>
BEGIN
--entry exists, but the destination net is not reachable.
b.fo.status ← RouterInternal.XmitStatus[noRouteToNetwork];
PutOnGlobalDoneQueue[b]; --client may get it back
IF CommFlags.doStats THEN Stats.StatIncr[statNSSentNowhere];
RETURN;
END;
b.fo.status ← RouterInternal.XmitStatus[goodCompletion];
Protocol1.EncapsulateAndTransmit[LOOPHOLE[b], @nextHost];
END; -- Transmit
TestAndSet: ENTRY PROC[delta: INTEGER] RETURNS[users: NATURAL] =
BEGIN
RETURN[IF delta > 0 THEN (table.users ← table.users + 1)
ELSE IF table.users > 0 THEN (table.users ← table.users - 1) ELSE 0];
END;
UpdateRoutingTable: INTERNAL PROC [
b: NSBuffer.Buffer,
destNetNum: System.NetworkNumber ← System.nullNetworkNumber]
RETURNS [success: BOOLEAN ← FALSE] =
<<
Digs though tuples in packet and adds the interesting ones to the
routing table. "Interesting" tuples are ones with the desired
objectNetID. Receivers of gratuitous info let it
default to nullNetworkNumber. <success>, used only by RoutingTable to
find out if its info arrived, is set only if a new route is added (not
if existing info is updated).
>>
BEGIN
i: CARDINAL;
tuples: CARDINAL;
newDelay: CARDINAL;
entry: Entry ← NIL;
nsb: NSBuffer.Body = b.ns;
tuple: NSTypes.RoutingInfoTuple;
c: RoutingTable.NetworkContext = NARROW[b.fo.context];
IF c.netNumber = System.nullNetworkNumber THEN --do we understand life?
c.netNumber ← nsb.source.net; --copy net number to context
IF table.me.net = System.nullNetworkNumber THEN
table.me ← [net: nsb.source.net, context: b.fo.context]; --I'm special
IF destNetNum = allNets THEN RETURN[TRUE]; --just wanted to know about me
IF (table.fill = 0) AND (table.base = NIL) --nothing's going to succeed
AND (destNetNum = System.nullNetworkNumber) THEN RETURN; --wasted time
tuples ← (nsb.pktLength - NSTypes.bytesPerIDPHeader -
(Environment.bytesPerWord * SIZE[NSTypes.RoutingInfoType])) /
(Environment.bytesPerWord * SIZE[NSTypes.RoutingInfoTuple]);
FOR i IN [0..tuples) DO
tuple ← nsb.routingTuple[i]; --pick the tuple our for easy access
newDelay ← Inline.BITAND[mask, tuple.interrouterDelay]; --strip high bits.
SELECT TRUE FROM
(tuple.objectNetID = allNets),
(tuple.objectNetID = System.nullNetworkNumber) => NULL;
((entry ← FindElement[tuple.objectNetID, FALSE]) # NIL) =>
BEGIN
SELECT TRUE FROM
--Update the entry if this tuple has better/newer info.
(newDelay < entry.delay), --it's better than what we know now
(entry.route = nsb.source.host), --same router, longer path
(entry.age >= longerAge) => --it's worse, but newer
BEGIN
entry.delay ← newDelay;
entry.route ← nsb.source.host;
entry.context ← b.fo.context;
END;
ENDCASE;
entry.age ← youngster; --the entry is young again
END;
(tuple.objectNetID = destNetNum) => --trying to find new route
BEGIN
success ← newDelay < Router.infinity; --maybe it was dead
entry ← AddElement[tuple.objectNetID]; --its never "me"
entry.delay ← newDelay;
entry.route ← nsb.source.host;
entry.context ← b.fo.context;
END;
(newDelay <= table.fill) => --trying to fill
BEGIN
entry ← AddElement[tuple.objectNetID]; --its never "me"
entry.delay ← newDelay;
entry.route ← nsb.source.host;
entry.context ← b.fo.context;
END;
ENDCASE;
Process.Yield[]; --some to use, some to share
ENDLOOP;
END; -- UpdateRoutingTable
--initialization
Process.EnableAborts[@routerCondition];
Process.SetTimeout[@routerCondition, Process.MsecToTicks[26000]];
Process.SetTimeout[@infoArrival, Process.MsecToTicks[350]];
END. --RoutingTableImpl module.
LOG
17-May-84 11:10:42 AOF Post Klamath.
2-Apr-85 16:40:38 AOF Restarting vanilla routing after doing something else.
25-Jul-85 9:24:16 AOF NIL context check in NullNetNum.
26-Jul-85 13:14:57 AOF Don't WAIT on buffers in BroadcastRoutingRequest.
23-Oct-85 18:34:55 AOF Make CacheFaults take fixed period of time.
3-Nov-85 11:14:49 AOF Rewrite of Fill and Enumerate.
10-Dec-85 17:21:15 AOF Listener rework.
20-Dec-85 10:54:59 AOF Forgot to monitor the new Enumerate code.
23-Jan-86 15:21:44 AOF Decision to use Main or Aside table backwards.
3-Feb-86 17:46:31 SMA Don't remove entries for attached nets, sanity checking.
4-Feb-86 11:30:53 AOF Don't build aside table, use real one
6-Feb-86 18:25:01 AOF Allocate router object from heap, not frame
25-Feb-86 13:30:15 AOF Rewrite table access method and fix GetDelay
22-May-86 18:24:54 SMA No dependencies on Buffer, Driver.Network => Device.
8-Jun-86 13:17:01 AOF 40 just wasn't big enough for testing list lengths
18-Jun-86 15:37:10 AOF Sort table's links in FindElement
18-Aug-86 18:05:51 AOF Local caching of b.ns in nsb.
10-Sep-86 14:23:19 AOF Check for table.base = NIL when stopping router.
16-Nov-86 16:02:15 AOF Add bool to NOT sort table entries in FindElement.
12-Jan-87 14:17:48 AOF Debugging code for smashed table entry.
21-Jan-87 16:53:14 AOF Aging of entries w/o new routing information.
26-Jan-87 11:54:19 WIrish More tweeking of aging of entries w/o new routing information.
26-Jan-87 11:54:19 AOF Using internal process to age tables
29-Jan-87 20:40:18 AOF Gunned "CountThemSuckers"
10-Feb-87 15:48:54 AOF Don't Glitch when we receive a null network answer.
6-Mar-87 11:52:52 AOF More of the same as above
11-Jun-87 16:40:16 AOF Move rto back into global frame
28-Aug-87 9:17:30 AOF Chasing CHS's table munge and "noRoute" problems
1-Oct-87 17:54:37 AOF Up priority of cache fault process
9-Oct-87 11:53:18 AOF ...but to "receiver" priority, not "realtime"
18-Oct-87 10:35:27 AOF Better computation of max table size
20-Oct-87 19:35:53 AOF AR#12150: Cache fault missing answers
21-Oct-87 19:16:06 AOF Forgetting to delete socket on UNWIND in $Fill (CHS)
6-Nov-87 17:37:53 AOF No more mr. nice guy. Keep priority up and get job done
7-Jan-88 18:48:30 AOF Don't delete entries across transient circuits