-- File: PupRouterIn.mesa, Last Edit: HGM February 25, 1981 3:17 PM
-- Last edited by Andrew Birrell June 20, 1983 11:54 am

DIRECTORY
PrincOpsUtils USING [LowHalf],
ProcessorFace USING [GetClockPulses],
StatsDefs USING [StatIncr],
PupRouterDefs USING [
bytesPerPupHeader, routerLock, routingTableUpdateTimeout, probeResponse,
InThings, PupGateInfo, PupRouterSocket, RoutingTableEntry, RoutingTable,
maxHop, TestPupChecksum, RejectPupWithBadChecksum, DontForwardPupBuffer,
Reject],
CommFlags USING [doShow, doStats, doStorms],
DriverDefs USING [Network, Glitch],
PupDefs USING [
EnqueuePup, ReturnFreePupBuffer, PupBuffer, incomingPup, zappedIncomingPup],
BufferDefs USING [BuffersLeft],
PupTypes USING [allHosts, gatewaySoc, PupAddress, PupHostID, PupNetID],
DriverTypes;

PupRouterIn: MONITOR LOCKS PupRouterDefs.routerLock
IMPORTS
PrincOpsUtils, ProcessorFace, StatsDefs, PupRouterDefs, DriverDefs, PupDefs, BufferDefs
EXPORTS BufferDefs, PupRouterDefs
SHARES BufferDefs, DriverTypes =
BEGIN OPEN StatsDefs, PupRouterDefs, DriverDefs, PupDefs, PupTypes;

-- EXPORTed TYPEs
Network: PUBLIC TYPE = DriverDefs.Network;

-- SemiPublic things for PupRouterCold and friends
routerIsActive: PUBLIC BOOLEAN ← FALSE;
firstSocket: PUBLIC PupRouterSocket ← NIL;
routingTable: PUBLIC RoutingTable;
pupForwarder: PUBLIC PROCEDURE [PupBuffer] ← DontForwardPupBuffer;

inThings: PUBLIC InThings ←
[inStormy: FALSE, watcherIsWatching: FALSE, watcherSeesBroadcast: FALSE,
watcherCallsThis:, badChecksumProc: PupRouterDefs.RejectPupWithBadChecksum,
showIn: FALSE, inShower:];

-- parameters for killing packets
lightning: INTEGER ← 30;
bolt: INTEGER ← 10;

PupRouterNotActive: PUBLIC ERROR = CODE;

BeSurePupIsOn: PUBLIC PROCEDURE =
BEGIN IF ~routerIsActive THEN DriverDefs.Glitch[PupRouterNotActive]; END;

-- Only called by dispatcher when a Pup arrives.

PupInputer: PUBLIC ENTRY PROCEDURE [b: PupBuffer] =
BEGIN
so: PupRouterSocket;
d: PupAddress ← b.dest;
targetNet: PupNetID ← d.net;
network: Network ← b.network;
rte: RoutingTableEntry;
IF CommFlags.doStats THEN StatIncr[statPupReceived];

IF b.pupLength < bytesPerPupHeader THEN
BEGIN
IF CommFlags.doStats THEN StatIncr[pupTooShort];
ReturnFreePupBuffer[b];
RETURN;
END;

IF ~TestPupChecksum[b] THEN
BEGIN
IF CommFlags.doStats THEN StatIncr[statReceivedBadPupChecksum];
inThings.badChecksumProc[b];
RETURN;
END;

IF CommFlags.doStorms AND inThings.inStormy -- for debugging only
AND (lightning ← lightning + 1) > bolt OR lightning < 0 THEN
BEGIN
IF lightning > bolt THEN
BEGIN
IF bolt > 100 THEN
BEGIN
mumble: LONG CARDINAL ← ProcessorFace.GetClockPulses[];
-- Alto Pulses are short
lightning ← -INTEGER[PrincOpsUtils.LowHalf[mumble] MOD 20B];
bolt ← 10;
END
ELSE BEGIN lightning ← 0; bolt ← bolt + 1; END;
END;
IF CommFlags.doShow AND inThings.showIn THEN
inThings.inShower[zappedIncomingPup, b];
ReturnFreePupBuffer[b];
IF CommFlags.doStats THEN StatIncr[statZappedP];
RETURN
END;
IF CommFlags.doShow AND inThings.showIn THEN
inThings.inShower[incomingPup, b];

-- Patch to recveive broadcasts on anonymous nets from old Gateways
-- (or replies to broadcasts)
IF network.netNumber.b > 377B AND d.net = b.source.net AND
(d.host = allHosts OR d.host = network.hostNumber) THEN
targetNet ← d.net ← b.dest.net ← [0];

rte ← GetRoutingTableEntry[targetNet];
IF targetNet # 0 AND network.netNumber.b # 0 THEN
BEGIN
-- Patch in case network.netNumber isn't reflected in routingTable. This can happen if the NS router finds the network number and doesn't tell us.
IF routingTable[network.netNumber.b].network # network
THEN routingTable[network.netNumber.b] ←
[net: [network.netNumber.b], hop: 0, time: 0, route: [0], network: network];
-- End patch --
IF rte = NIL OR rte.network = NIL OR rte.hop # 0 OR
(d.host = allHosts AND rte.network # network) THEN
BEGIN pupForwarder[b]; RETURN; END;
IF FALSE THEN -- It is more complicated than that.....
b.pupTransportControl ← b.pupTransportControl + 20B;
-- Hack for Gateway init
network ← rte.network; -- fixup backdoor problems
END;

IF (d.host = network.hostNumber OR d.host = allHosts) THEN
BEGIN -- packet for us - incomming or local
FOR so ← firstSocket, so.next UNTIL so = NIL DO
IF so.local.socket = d.socket THEN
BEGIN
IF network.netNumber.b = 0 AND targetNet # 0 THEN
BEGIN -- packet for us, believe network number
network.netNumber ← [0, targetNet];
IF targetNet < routingTable.length THEN
rte^ ←
[net: targetNet, hop: 0, time: 0, route: [0], network: network];
END;
IF so.input.length > 1 AND BufferDefs.BuffersLeft[] < 2 THEN
BEGIN
IF CommFlags.doStats THEN StatIncr[statPupInputQueueOverflow];
Reject[b, resourceLimitsPupErrorCode];
EXIT;
END;
EnqueuePup[so.input, b];
NOTIFY so.ready;
EXIT;
END;
REPEAT
FINISHED =>
BEGIN -- not in socket table
IF b.pupType = gatewayInfo AND d.socket = gatewaySoc THEN
[] ← GatewaySee[b]
ELSE
BEGIN -- non gateway packet for unknown socket
IF CommFlags.doStats THEN
IF d.host # allHosts THEN StatIncr[statJunkPupsForUsNoLocalSocket]
ELSE StatIncr[statJunkBroadcastPups];
IF ~inThings.watcherIsWatching THEN GOTO RejectThisPup;
IF (d.host # allHosts OR inThings.watcherSeesBroadcast) AND
inThings.watcherCallsThis[b] THEN GOTO RejectThisPup;
END;
ReturnFreePupBuffer[b];
EXITS
RejectThisPup =>
BEGIN
-- Hack special case check to avoid touching another module
IF b.dest.host = allHosts THEN ReturnFreePupBuffer[b]
ELSE Reject[b, noProcessPupErrorCode];
END;
END;
ENDLOOP;
END
ELSE pupForwarder[b];
END;

Timeout: PUBLIC ENTRY PROCEDURE =
BEGIN
WHILE routerIsActive DO
-- There isn't any need to probe the Gateways if we don't know our network number, since they broadcast every 30 seconds or so.
EnumerateRoutingTable[FlushDeadNets];
WAIT routingTableUpdateTimeout; -- 30 seconds
ENDLOOP;
END;

FlushDeadNets: PROCEDURE [rte: RoutingTableEntry] =
BEGIN
IF rte.hop = 0 THEN RETURN; -- directly connected
IF rte.network = NIL THEN RETURN; -- no way to get there
IF (rte.time ← rte.time + 30) > 180 THEN rte.network ← NIL
END;

PupGatewaySee: PUBLIC ENTRY PROCEDURE [b: PupBuffer] RETURNS [BOOLEAN] =
BEGIN RETURN[GatewaySee[b]]; END;

GatewaySee: INTERNAL PROCEDURE [b: PupBuffer] RETURNS [new: BOOLEAN] =
BEGIN
newRoute: PupHostID = b.source.host;
network: Network = b.network;
length: CARDINAL = b.pupLength - 22;
new ← FALSE; -- no changes yet
IF b.pupType # gatewayInfo
-- Patch for Anonymous networks
-- OR b.source.net ~IN[1..routingTable.length)
OR newRoute = 0 OR (length MOD 2*SIZE[PupGateInfo]) # 0 THEN
BEGIN IF CommFlags.doStats THEN StatIncr[statMouseTrap]; RETURN; END;
IF newRoute = network.hostNumber THEN RETURN; -- from self
IF CommFlags.doStats THEN StatIncr[statPupGatewayPacketsRecv];
IF network.netNumber = [0, 0] THEN
BEGIN -- we don't know our network number on this device yet
net: PupNetID ← b.source.net;
network.netNumber ← [0, net];
IF net < routingTable.length THEN
routingTable[net] ←
[net: net, hop: 0, time: 0, route: [0], network: network];
-- Note: If we are not a gateway, we have probably just learned the network number of the standard Ethernet device. The users process that called PupRouterOn will normally be waiting so kick him loose.
NOTIFY probeResponse;
END;
-- What should we do if the network number from this Pup doesn't match the one we know in network.netNumber?

-- This is where we actually update the routing table. For all the details, see Taft's memo stored on: [MAXC]<Pup>GatewayInformation.bravo
BEGIN
data: LONG POINTER TO PupGateInfo ← LOOPHOLE[@b.pupWords[0]];
THROUGH [0..length/(2*SIZE[PupGateInfo])) DO
newHop, newTime: CARDINAL;
net: PupNetID ← [data.net];
rte: RoutingTableEntry;
newHop ← data.hop + 1;
data ← data + SIZE[PupGateInfo];
IF net >= routingTable.length THEN LOOP; -- too big, skip it
rte ← GetRoutingTableEntry[net];
IF rte.hop = 0 THEN LOOP; -- directly connected
-- This is a bit tricky. We want to keep entrys with hop>maxHop until they timeout so that Gateways will do the right things about propagating changes, but we don't want to learn new paths to nowhere.
IF ((rte.network = NIL OR rte.hop > maxHop) AND newHop > maxHop) THEN LOOP;
IF rte.network = NIL OR (rte.route = newRoute AND rte.network = network) OR
rte.time > 90 OR newHop < rte.hop OR
(newHop = rte.hop AND network.speed > rte.network.speed) THEN
BEGIN
IF newHop > maxHop THEN
BEGIN
newHop ← maxHop + 1; -- dangling entry, don't rejuvenate timer
newTime ← rte.time;
END
ELSE newTime ← 0;
IF rte.hop # newHop THEN new ← TRUE;
-- rejuvenate timer if nothing else
rte^ ←
[net: net, hop: newHop, time: newTime, route: newRoute,
network: network];
END;
ENDLOOP;
END;
END;

GetRoutingTableEntry: PUBLIC PROCEDURE [net: PupNetID]
RETURNS [rte: RoutingTableEntry] =
BEGIN
IF net >= routingTable.length THEN RETURN[NIL];
RETURN[@routingTable[net]];
END;

EnumerateRoutingTable: PUBLIC PROCEDURE [proc: PROCEDURE [RoutingTableEntry]] =
BEGIN
FOR n: CARDINAL IN [0..routingTable.length) DO
proc[@routingTable[n]]; ENDLOOP;
END;

SetPupForwarder: PUBLIC PROCEDURE [proc: PROCEDURE [PupBuffer]] =
BEGIN
pupForwarder ← proc;
END;

END.