-- File: InrRoutingTableImpl.mesa - last edit:
-- AOF                  5-Feb-88 13:48:34
-- Leong               16-Sep-87 13:42:15
-- Kluger              22-Oct-85  8:05:50

-- Copyright (C) 1984, 1985, 1986, 1988 by Xerox Corporation. All rights reserved.

-- Function: The implementation module for the NS INR's routing table.

<<
IMPLEMENTATION NOTE:

Note that opaque types System.NetworkNumber and System.HostNumber are EXPORTed here as well as in module INRImpl. This is to allow referencing of the type without LOOPHOLEs.
>>

DIRECTORY
  Buffer USING [],
  CommHeap USING [zone],
  CommFlags USING [doDebug],
  CourierInternal USING [ExchWords],
  Driver USING [GetDeviceChain, Glitch, Device],
  InrFriends USING [
    DriverDetails, HopCount, Method, RoutingTableEntry, RoutingTableObject],
  Inline USING [BITAND],
  InrInternal USING [
    defaultNumberOfHashBits, deadEntryHopCount, infinityHopCount, inrLock,
    localHop, maxInternetrouterHops, pleaseStop, INRRoutingTable,
    SendGratuitousResponse, stats, updateCycles],
  InrStats USING [RoutingTableSearch, tupleIndexTableSize],
  NSConstants USING [routingInformationSocket],
  Process USING [EnableAborts, Pause, SecondsToTicks, SetTimeout],
  Protocol1 USING [GetContext],
  Router USING [NoTableEntryForNet],
  RoutingTable USING [NetworkContext],
  Space USING [ScratchMap, Unmap, wordsPerPage],
  SpecialSystem USING [HostNumber, NetworkNumber],
  System USING [
    broadcastHostNumber, GetClockPulses, NetworkAddress, NetworkNumber,
    nullHostNumber, nullNetworkAddress, nullNetworkNumber, nullSocketNumber,
    Pulses];

InrRoutingTableImpl: MONITOR LOCKS InrInternal.inrLock
  IMPORTS InrInternal,
    CommHeap, CourierInternal, Driver,
    Inline, Process, Protocol1, Router, Space, System
  EXPORTS
    Buffer, InrFriends, InrInternal, InrStats, System =
  
  BEGIN
  
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
  -- TYPEs locally and externally declared
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----

  NetworkContext: TYPE = RoutingTable.NetworkContext;
  Device: PUBLIC TYPE = Driver.Device; -- clear the opaque type in Buffer
  NetworkNumber: PUBLIC TYPE = SpecialSystem.NetworkNumber;
    -- NetworkNumber is also EXPORTed here (in addition to INRImpl) because
    -- System defines NetworkNumber to an opaque type, TYPE[2], and we want
    -- to be able to work with the internals
    
  HostNumber: PUBLIC TYPE = SpecialSystem.HostNumber; -- also EXPORTed by INRImpl

  
  HopCount: TYPE = InrFriends.HopCount;
  RoutingTableEntry : TYPE = InrFriends.RoutingTableEntry;
  RoutingTableObject: TYPE = InrFriends.RoutingTableObject;
  
  INRRoutingTable: TYPE = InrInternal.INRRoutingTable;
  
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
  -- CONSTANTS that make it all happen
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
  unknownHostID: HostNumber = System.nullHostNumber;
  nullNetworkNumber: NetworkNumber = System.nullNetworkNumber;
     -- routing info request network number that indicates all networks
  localHop: InrFriends.HopCount = InrInternal.localHop;
     -- rte delay for attached network(s) is zero hops.
     
  allLocalInternetRouters: System.NetworkAddress = [
    net: nullNetworkNumber, host: System.broadcastHostNumber,
    socket: NSConstants.routingInformationSocket];
     
  updateCycles: CARDINAL = InrInternal.updateCycles;
    -- timeUnits gets reset to this; number of routing table update cycles.
    
  maxInternetrouterHops: CARDINAL = InrInternal.maxInternetrouterHops;
  infinityHopCount: CARDINAL = InrInternal.infinityHopCount;
  deadEntryHopCount: CARDINAL = InrInternal.deadEntryHopCount;
  
  decrementTO: CARDINAL = 40; -- seconds for decrementing table entries
  
  emptyINRRoutingTable: INRRoutingTable = DESCRIPTOR[NIL, 0];
  
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
  -- VARIABLES or semi-Variables
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
  hashingMask: CARDINAL ← 0;
  hashTableSize: CARDINAL ← 0; -- the number of buckets, determined by
    -- the number of bits used for accessing the table
  markDeadEntries: BOOLEAN ← TRUE; -- flag to indicate if an invalid routing
    -- table entry should be just marked as dead or actually removed from the
    -- routing table
  
  --various Glitches generated by the Router
  INRRoutingTableScrambled: ERROR = CODE;
  
  nullEnum: NetworkNumber ← nullNetworkNumber;

  routingTableChanged: PUBLIC CONDITION; << mucked with in INRImpl >>
    -- notified to send info on routes that have changed
  
  routingTable: PUBLIC INRRoutingTable ← emptyINRRoutingTable;
    -- hash table (fixed number of buckets) of routing table entries
  routingTableSize: CARDINAL ← 0;
  numSequencedList: PUBLIC RoutingTableEntry ← NIL;
  
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
  -- routines to the Internetwork Router's routing table
  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
  
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This procedure adds an entry to the inr's routing table. The entry is
  -- also properly inserted into the numerically sequenced list. Be careful
  -- that the entry does not already exist in the table (check should have
  -- been made before calling this routine - FindNetworkNumber[]).
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  -- Only the added entry's two links fields are changed in this routine.
  
  AddEntry: PUBLIC INTERNAL PROCEDURE [e: RoutingTableEntry] =
    BEGIN
    bucket: CARDINAL;
    nse, prev: RoutingTableEntry;
    
    -- FIRST, add entry to routing table (at beginning of collision chain)
    bucket ← HashNetworkNumber[e.destNetwork];
    e.nextRTE ← routingTable[bucket];
    routingTable[bucket] ← e;
    
    -- SECOND, add entry to the numerically sequenced network number list
    prev ← nse ← numSequencedList;
    UNTIL (nse = NIL) DO --find point of insertion
      IF CommFlags.doDebug AND (nse.destNetwork = e.destNetwork) THEN
        Driver.Glitch[INRRoutingTableScrambled];
      IF NetAgtNetB[a: nse.destNetwork, b: e.destNetwork] THEN EXIT;
        -- the given entry belongs just before the entry with the next
	-- greatest network number
      prev ← nse;
      nse ← nse.nextNSE;
      ENDLOOP;
    -- and insert into the list
    IF (nse = numSequencedList) THEN
      BEGIN --insert into the very beginning of list (first entry)
      temp: RoutingTableEntry ← numSequencedList;
      numSequencedList ← e;
      e.nextNSE ← temp;
      END
    ELSE
      BEGIN --is not the first entry in list
      prev.nextNSE ← e;
      e.nextNSE ← nse;
      END;
      
    -- now that an entry has been added increment size
    routingTableSize ← routingTableSize + 1;
    
    StatIncr [@InrInternal.stats.destNetsAdded]; 
    END; --of AddEntry
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This routine acquires the monitor lock and adds a network to the routing
  -- table. After the addition, a NOTIFY is done allow a broadcast of the
  -- changes. AddNet is passed to Pilot Router
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  AddNet: PUBLIC ENTRY PROCEDURE [net: NetworkContext] =
    BEGIN ENABLE UNWIND => NULL;
    AddNetDeviceInternal[net];  --AddNet
    -- send out gratuitous routing response so inrs learn about each other
    NOTIFY routingTableChanged;
    END; --of AddNet
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This procedure tells the NS Router about a new network (device).
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  AddNetDeviceInternal: PUBLIC INTERNAL PROCEDURE [newNetwork: NetworkContext] =
    BEGIN
    IF NOT newNetwork.network.alive THEN RETURN;
    IF (newNetwork.netNumber # nullNetworkNumber) THEN
      BEGIN
      -- the driver already knows the number of its net, add or modify a rte
      e: RoutingTableEntry ← FindNetworkNumber[newNetwork.netNumber, FALSE];
      IF (e = NIL) THEN
        BEGIN -- add a new routing table entry
        e ← CommHeap.zone.NEW[RoutingTableObject];
	e↑ ← RoutingTableObject[
	  nextRTE: NIL, nextNSE: NIL, destNetwork: newNetwork.netNumber,
	  delay: localHop, timeUnits: updateCycles, route: unknownHostID,
	  context: newNetwork, changed: TRUE];
        AddEntry[e];
        END
      ELSE -- modify an existing routing table entry if better hop
	IF e.delay > localHop THEN e↑ ← RoutingTableObject[
	  nextRTE: e.nextRTE, nextNSE: e.nextNSE,
	  destNetwork: newNetwork.netNumber, delay: localHop,
	  timeUnits: updateCycles, route: unknownHostID,
	  context: newNetwork, changed: TRUE];
      END;
      
    IF (FindNetworkNumber[nullNetworkNumber, FALSE] = NIL) THEN
      BEGIN
      -- there is no "default" rte, add one
      e: RoutingTableEntry ← CommHeap.zone.NEW[RoutingTableObject];
      e↑ ← RoutingTableObject[
        nextRTE: NIL, nextNSE: NIL, destNetwork: nullNetworkNumber,
	delay: localHop, timeUnits: updateCycles, route: unknownHostID,
        context: newNetwork, changed: FALSE];
      AddEntry[e];
      END;
    END;  --AddNetDeviceInternal
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- CleanUpRoutingTable free the entries in the routing table and then the
  -- table itself. Global variables are reset.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  --Question: Can/Should this be made into an INLINE?
  
  CleanUpRoutingTable: PUBLIC ENTRY PROCEDURE =
    BEGIN ENABLE UNWIND => NULL;
    e, temp: RoutingTableEntry;
    -- go through linked numerically sequenced list to deallocate all entries
    e ← numSequencedList;
    WHILE (e # NIL) DO
      temp ← e;
      e ← e.nextNSE;
      CommHeap.zone.FREE[@temp];
      ENDLOOP;
    
    -- free the hash routing table
    [] ← Space.Unmap[pointer: BASE[routingTable]];
    
    -- reinitialize the global variables
    numSequencedList ← NIL;
    routingTable ← emptyINRRoutingTable;
    routingTableSize ← 0;
    END; --of CleanUpRoutingTable
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- FORKED INTERNETWORK ROUTER PROCESS
  -- This routine, waking up once every interval, acquires the monitor lock
  -- and decrements the age count of each routing table entry. If an entry's
  -- age is already zero before the decrementation, meaning it may be a bad
  -- entry, the entry is deleted.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  -- Invalid entry detection: minimum of ((updateCycles + 1)*decrementTO) seconds
  
  DecrementRoutingTableEntries: PUBLIC ENTRY PROCEDURE =
    BEGIN ENABLE UNWIND => NULL;
    rte: RoutingTableEntry;
    timer: CONDITION;
    Process.SetTimeout [@timer, Process.SecondsToTicks [decrementTO]];
    Process.EnableAborts [@timer];
    UNTIL InrInternal.pleaseStop DO
      ENABLE ABORTED => EXIT; --exit the until loop
      WAIT timer;
      rte ← numSequencedList;
      
      WHILE (rte # NIL) DO
        -- local network's entries' <timeUnits> should not be decremented
	IF ~((rte.delay=localHop) OR (rte.delay=deadEntryHopCount)) THEN
	  BEGIN
	  IF (rte.timeUnits = 0) THEN
	    BEGIN -- remove the dead entry
	    temp: RoutingTableEntry ← rte;
	    rte ← rte.nextNSE; --first increment pointer
	    IF markDeadEntries THEN MarkEntryDead[temp]
	      ELSE {RemoveEntry[temp]; CommHeap.zone.FREE[@temp]};
	    LOOP; --go to next entry
	    END
	    ELSE rte.timeUnits ← rte.timeUnits - 1;
	  END;
	rte ← rte.nextNSE;
	ENDLOOP; --while not of list loop
	
      -- now go back and wait awhile
      ENDLOOP; --until InrInternal.pleaseStop loop
    END; --of DecrementRoutingTableEntries
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This routine enumerates the routing table by distance first and network
  -- number second. Start the enumeration with nullNetworkNumber and the hop
  -- count of choice. The enumeration is finished when the returned net is
  -- nullNetworkNumber. The table can change between calls.
  -- EnumerateByDistance is passed to Pilot Router.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  -- Dead routing table entries can be enumerated with this routine passing
  -- a <delay> value of InrInternal.deadEntryHopCount
  
  EnumerateByDistance: PUBLIC ENTRY PROCEDURE [
    previous: NetworkNumber, delay: CARDINAL]
    RETURNS [net: NetworkNumber] =
    --Start enumeration with nullNetworkNumber and finish with lastNetworkNumber
    --enumeration happens in order of net #, so table can change between calls
    BEGIN ENABLE UNWIND => NULL;
    foundContextInList: BOOLEAN ← FALSE;
    rte: RoutingTableEntry;
    pulses: System.Pulses ← System.GetClockPulses[]; --for time duration
    
    IF (numSequencedList = NIL) THEN RETURN [net ← nullEnum];
    
    rte ← FindNetworkNumber[previous]; -- try to find context
    IF (rte # NIL) THEN
      BEGIN
      -- found context in list, start enumeration from next entry
      foundContextInList ← TRUE;
      rte ← rte.nextNSE;
      END
    ELSE
      BEGIN
      -- didn't find context in list, must start enumeration from
      -- beginning of numerically sequenced network number list
      foundContextInList ← FALSE;
      rte ← numSequencedList;
      END;
    
    UNTIL (rte = NIL) DO
      IF (rte.delay = delay) --within the desired hop count
	AND (foundContextInList OR NetAgtNetB[rte.destNetwork, previous])
	AND ((rte.context # NIL) OR (rte.delay = infinityHopCount)
	  OR (rte.delay = deadEntryHopCount)) THEN EXIT; -- found it!
	-- Note: the check for infinityHopCount and deadEntryHopCount allows
	-- for the enumeration of unreachable/dead routing table entries
      rte ← rte.nextNSE; -- continue looking for the next one
      ENDLOOP;
    -- At this point, rte should be pointing to the next entry
    net ← IF (rte = NIL) THEN nullEnum ELSE rte.destNetwork;
      
    SearchDurationBump[pulses, enumerateByDistance]; --increments duration
    END; --of EnumerateByDistance
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This routine enumerates the routing table by network number. Start the
  -- enumeration with nullNetworkNumber. The enumeration is finished when the
  -- returned net is nullNetworkNumber. The table can change between calls.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  EnumerateByNetNumber: PUBLIC ENTRY PROCEDURE [previous: NetworkNumber]
    RETURNS [net: NetworkNumber, delay: HopCount] =
    BEGIN ENABLE UNWIND => NULL;
    foundContextInList: BOOLEAN ← FALSE;
    rte: RoutingTableEntry;
    pulses: System.Pulses ← System.GetClockPulses[]; --for time duration
    
    IF (numSequencedList = NIL) THEN
      RETURN [net ← nullEnum, delay ← infinityHopCount];
    
    rte ← FindNetworkNumber[previous]; -- try to find context
    IF (rte # NIL) THEN
      BEGIN
      -- found context in list, start enumeration from next entry
      foundContextInList ← TRUE;
      rte ← rte.nextNSE;
      END
    ELSE
      BEGIN
      -- didn't find context in list, must start enumeration from
      -- beginning of numerically sequenced network number list
      foundContextInList ← FALSE;
      rte ← numSequencedList;
      END;
    
    UNTIL (rte = NIL) DO
      IF (foundContextInList OR NetAgtNetB[rte.destNetwork, previous])
	AND (rte.context # NIL) THEN EXIT; --found it!
      rte ← rte.nextNSE; -- continue looking for the next one
      ENDLOOP;
    -- At this point, rte should be pointing to the next entry
    IF (rte = NIL) THEN {net ← nullEnum; delay ← infinityHopCount;}
      ELSE {net ← rte.destNetwork; delay ← rte.delay;};
      
    SearchDurationBump[pulses, enumerateByNetNumber]; --increments duration
    END; --of EnumerateByNetNumber
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This routine enumerates the routing table by the given method. Only
  -- valid routing table entries will be enumerated.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  nullDriverDetails: InrFriends.DriverDetails = [
    driverType: other, driverNetwork: System.nullNetworkNumber,
    via: System.nullNetworkAddress, statsPtr: NIL];
  
  -- Dead entries will not be enumerated.
  
  EnumerateRoutes: PUBLIC PROCEDURE [previousNet: NetworkNumber,
    previousDelay: HopCount, method: InrFriends.Method,
    alwaysDetermineViaNet: BOOLEAN ← FALSE] 
    RETURNS [net: NetworkNumber,
    delay: HopCount, details: InrFriends.DriverDetails] =
    BEGIN
    
    --Stateless enumerator starts with nullNetworkNumber and zeroHopCount.
    --Ends with nullNetworkNumber.
    
    SELECT method FROM
      byNetNumber =>
        BEGIN
	ENABLE Router.NoTableEntryForNet => RETRY; --start here and do it again
	  -- the entry that was just found (which raised the error) is a dead
	  -- entry, but repeat to find the next valid entry
	[net, delay] ← EnumerateByNetNumber [previousNet];
	IF (net = nullEnum) THEN --finished table enumeration
	  RETURN [nullEnum, infinityHopCount, nullDriverDetails];
       	details ← GetRouteDetails[net, alwaysDetermineViaNet].details;
	END; --byNetNumber
      byDistance =>
        BEGIN
        ENABLE Router.NoTableEntryForNet => RETRY;
	net ← EnumerateByDistance [previousNet, previousDelay];
	UNTIL (net # nullEnum) OR (previousDelay = maxInternetrouterHops) DO
	  -- enumeration for the previous hop count has been completed,
	  -- now it is time to start enumeration for the next hop count
	  previousDelay ← previousDelay + 1;
	  previousNet ← nullEnum; -- start over
	  net ← EnumerateByDistance [previousNet, previousDelay];
	  ENDLOOP;
	IF (net = nullEnum) THEN --finished table enumeration
	  RETURN [nullEnum, infinityHopCount, nullDriverDetails];
	delay ← previousDelay;
	previousNet ← net;
	details ← GetRouteDetails[net, alwaysDetermineViaNet].details;
	END; --byDistance
      ENDCASE => NULL;  -- we handled all of the enumerated type's values
    END; --of EnumerateRoutes
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- FindFarNet is an internal procedure and thus requires the monitor lock.
  -- A non-null netork number is returned for the given medium driver.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
    
  FindFarNet: PUBLIC INTERNAL PROCEDURE [net: NetworkContext] 
    RETURNS [farNet: NetworkNumber] = 
    BEGIN ENABLE UNWIND => NULL;
    << net is a network driver that is anonyomous--it isn't assigned
      its own network number.  Point to Point connections between INRs
      are anonymous E.g. a phoneline between two INRs.
      
      This procedure searches to find the rte in the table 
      that uses <net> and has the lowest delay to a destination net of
      all the rtes that use <net>.  That rte's destNetwork should be
      a viable network for reaching the INR at the far end of the anonymous
      driver.
      
      We make only one pass through the rtes. >>
      
    bestHopsSoFar: HopCount ← infinityHopCount;
    oneAway: HopCount ← localHop + 1;
    
    farNet ← nullNetworkNumber;
    FOR e: RoutingTableEntry ← numSequencedList, e.nextNSE
      UNTIL (e = NIL) OR (bestHopsSoFar = oneAway) DO
      IF (e.context # net) OR (e.destNetwork = nullNetworkNumber)
	THEN LOOP;  -- ignore
      IF (e.delay < bestHopsSoFar) THEN
	BEGIN
	farNet ← e.destNetwork;
	bestHopsSoFar ← e.delay;
	END;
      ENDLOOP;
    END; --of FindFarNet
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- FindNetID returns the ID of the locally connected network most suitable
  -- to get to the destination network.
  -- This routine is passed to Pilot Router
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  -- CALLED FROM RoutingInformationSupplier[]
  
  FindNetID: PUBLIC ENTRY PROCEDURE [net: NetworkNumber]
    RETURNS [NetworkNumber] =
    BEGIN ENABLE UNWIND => NULL;
    RETURN[FindNetIDInternal[net]];
    END; -- of FindNetID
    
  -- CALLED FROM RoutAndSendRoutingInfoResponse[]
    
  FindNetIDInternal: PUBLIC INTERNAL PROCEDURE [
    destNet: NetworkNumber] RETURNS [netNumber: NetworkNumber] =
    BEGIN
    e: RoutingTableEntry;
    netNumber ←
      IF (destNet = nullNetworkNumber) OR -- accelerator so we don't search
      -- for the net zero rte
      ((e ← FindNetworkNumber[destNet]) = NIL) OR (e.context = NIL) THEN
      nullNetworkNumber ELSE e.context.netNumber;
    IF (netNumber = nullNetworkNumber) THEN
      BEGIN
      --since nullNetworkNumber is uninformative find the first network
      --with a known network Number and use it.
      FOR n: Driver.Device ← Driver.GetDeviceChain[], n.next UNTIL n = NIL DO
        c: NetworkContext ← Protocol1.GetContext[n, ns];
        IF (netNumber ← c.netNumber) # nullNetworkNumber THEN EXIT;
        ENDLOOP;
      END;  --find non - nullNetworkNumber network clause
    END;  --FindNetIDInternal
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- FindNetwork searches for an entry with network field equal to <net> and
  -- returns that entry. If the entry cannot be found then e is NIL. If the
  -- <startFromNum> is provided, then the search begins from the corresponding
  -- routing table entry.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  -- CALLED ONLY BY RemoveNetworkLocked[]
  -- Hopefully, providing the <startFromNum> feature will minimize the total
  -- number of searches required to remove a network.
  
  -- Reordering of the collision chain will be done by MarkEntryDead[].
    
  FindNetwork: INTERNAL PROCEDURE [
    net: NetworkContext, startFromNum: NetworkNumber ← nullNetworkNumber]
    RETURNS [e: RoutingTableEntry ← NIL] =
    BEGIN
    pulses: System.Pulses ← System.GetClockPulses[]; --for time duration
    
    IF (startFromNum = nullNetworkNumber)
      THEN e ← numSequencedList
      ELSE BEGIN
        rte: RoutingTableEntry ← FindNetworkNumber[startFromNum];
	e ← IF (rte = NIL) THEN numSequencedList ELSE rte.nextNSE; --redundant ck
        END;
    UNTIL (e = NIL) DO
      IF (e.context = net) THEN EXIT; -- found it!
      e ← e.nextNSE;
      ENDLOOP;
    SearchDurationBump[pulses, findByNetworkDriver]; --increments duration
    END; --of FindNetwork
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This hot procedure searches for an entry with destNetwork field equal to
  -- num and returns that entry.  If the entry cannot be found then e is NIL.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
    
  FindNetworkNumber: PUBLIC INTERNAL PROCEDURE [
    num: NetworkNumber, advanceEntry: BOOLEAN ← TRUE]
    RETURNS [e: RoutingTableEntry] =
    BEGIN
    prev: RoutingTableEntry;
    i: CARDINAL ← 0;
    bucket: CARDINAL;
    pulses: System.Pulses ← System.GetClockPulses[]; --for time duration
    
    IF (numSequencedList = NIL) OR (routingTable.BASE = NIL) THEN
      RETURN [NIL]; -- not started
    
    bucket ← HashNetworkNumber[num];
    e ← prev ← routingTable[bucket];
    UNTIL (e = NIL) DO
      IF (e.destNetwork = num) THEN -- found it!
        BEGIN
        IF advanceEntry AND (i > 4) THEN
          BEGIN
	  --advance entry to the beginning of the list
          prev.nextRTE ← e.nextRTE;
          e.nextRTE ← routingTable[bucket];
          routingTable[bucket] ← e;
          END;
	IF advanceEntry THEN StatIncr [@InrInternal.stats.tupleIndexTable[
	  MIN[i, (InrStats.tupleIndexTableSize-1)]]];
	EXIT; -- exit the until-do loop
	END;
      prev ← e;
      e ← e.nextRTE;
      i ← i + 1;
      ENDLOOP;
      
    SearchDurationBump[pulses, findByNetNumber]; --increments duration
    END; --of FindNetworkNumber
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This routine gets the details for a route. If <alwaysDetermineViaNet> is
  -- TRUE, then it will try to determine the network number of the immediate
  -- host.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  -- CALLED BY EnumerateRoutes[] and GetRouteInfo[]
  
  GetRouteDetails: PROCEDURE [
    destNet: NetworkNumber, alwaysDetermineViaNet: BOOLEAN]
    RETURNS [details: InrFriends.DriverDetails, delay: HopCount] =
    BEGIN
    e: RoutingTableEntry;
    net: NetworkContext;
    host: HostNumber;
    
    LockedFindNet: ENTRY PROC = INLINE
      {ENABLE UNWIND => NULL; e ← FindNetworkNumber[destNet, FALSE]};
      
    LockedFindFarNet: ENTRY PROCEDURE [net: NetworkContext] 
      RETURNS [farNet: NetworkNumber] = 
      BEGIN ENABLE UNWIND => NULL;
      farNet ← FindFarNet[net];
      END; --of LockedFindFarNet
      
      
    -- Mainline of the procedure ...
    
    LockedFindNet[];  --try searching first time
    IF (e = NIL) OR (e.context = NIL) THEN
      ERROR Router.NoTableEntryForNet; --no such entry in table
    net ← e.context;
    delay ← e.delay;
    host ← e.route;
    details.driverType ← net.network.device;
    details.driverNetwork ← net.netNumber;
    details.statsPtr ← net.network.stats;
    IF (delay # localHop) THEN
      BEGIN
      details.via.socket ← System.nullSocketNumber;
      details.via.host ← host;
      details.via.net ←
        IF alwaysDetermineViaNet AND (net.netNumber = nullNetworkNumber)
	  THEN LockedFindFarNet [net] ELSE net.netNumber; 
      END
      ELSE details.via ← System.nullNetworkAddress;
    END; --of GetRouteDetails
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This statistics routine just returns a READONLY pointer to the counter
  -- which keeps track of the number entries in the routing table.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  GetHashTableSize: PUBLIC PROCEDURE
    RETURNS [CARDINAL] = { RETURN [hashTableSize] };
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This routine retreive route information.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  GetRouteInfo: PUBLIC PROCEDURE [net: System.NetworkNumber]
    RETURNS [delay: HopCount, details: InrFriends.DriverDetails] =
    BEGIN
    [delay: delay, details: details] ← GetRouteDetails [
      destNet: net, alwaysDetermineViaNet: TRUE];
    END;
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This statistics routine just returns a READONLY pointer to the counter
  -- which keeps track of the number entries in the routing table.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  GetRoutingTableSize: PUBLIC PROCEDURE RETURNS [LONG POINTER TO
    READONLY CARDINAL] = {RETURN [@routingTableSize]};
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- HashNetworkNumber returns the hashed value of the given network number.
  -- This number is the index to the routing table to find/insert the network
  -- number's corresponding routing table entry.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
    
  HashNetworkNumber: PROCEDURE [networkNumber: NetworkNumber]
    RETURNS [hashValve: CARDINAL] = INLINE
    BEGIN
    hashValve ← Inline.BITAND[hashingMask, networkNumber.b];
    END; --of HashNetworkNumber
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This routine marks a routing table entry as being dead. <delay> is set
  -- to deadEntryHopCount, and the entry is reordered to be last in its
  -- respective hash table bucket's link list.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  MarkEntryDead: INTERNAL PROCEDURE [e: RoutingTableEntry] =
    BEGIN
    first: BOOLEAN ← FALSE; --first in collision chain
    prev: RoutingTableEntry ← NIL;
    bucket: CARDINAL ← HashNetworkNumber[e.destNetwork];
    last: RoutingTableEntry ← routingTable[bucket];
    
    e↑ ← [destNetwork: e.destNetwork, delay: deadEntryHopCount,
      timeUnits: 0, nextRTE: e.nextRTE, nextNSE: e.nextNSE,
      route: unknownHostID, context: NIL, changed: TRUE];
    
    -- reorder entry in the routing table's collision chain
    SELECT TRUE FROM
      (e.nextRTE = NIL) => RETURN; --already last in list
      (last = NIL) => --the bucket's collision chain is nil
        IF CommFlags.doDebug THEN Driver.Glitch[INRRoutingTableScrambled]
	  ELSE RETURN;
      (last = e) => first ← TRUE; --first in list of size > 1
      ENDCASE;
    UNTIL (last.nextRTE = NIL) DO
      IF (last.nextRTE = e) THEN prev ← last;
      last ← last.nextRTE;
      ENDLOOP;
    IF CommFlags.doDebug AND ~first AND (prev = NIL) THEN
      Driver.Glitch[INRRoutingTableScrambled];
    IF first THEN routingTable[bucket] ← e.nextRTE
      ELSE prev.nextRTE ← e.nextRTE;
    last.nextRTE ← e;
    e.nextRTE ← NIL;
    END; --of MarkEntryDead
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This inline procedure compare the two given network number and returns
  -- TRUE if the first is greater than the second. Note that the two network
  -- number should not be equal.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  NetAgtNetB: PROC [a, b: NetworkNumber] RETURNS [BOOLEAN] = INLINE
    BEGIN --RETURNS[a > b];
    RETURN[LOOPHOLE[CourierInternal.ExchWords[LOOPHOLE[a]], LONG CARDINAL] >
      LOOPHOLE[CourierInternal.ExchWords[LOOPHOLE[b]], LONG CARDINAL] ];
    END;
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This procedure removes the given entry from the inr's routing table. The
  -- routing table entry, however, is not deallocated. It is up to the calling
  -- procedure to free it. The routing table entry's correponding numerically
  -- ordered list entry is also removed and freed.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  -- ONLY CALLED FROM DecrementRoutingTableEntries[]
    
  RemoveEntry: INTERNAL PROCEDURE [e: RoutingTableEntry] =
    BEGIN
    prev: RoutingTableEntry ← NIL;
    temp: RoutingTableEntry ← NIL;
    bucket: CARDINAL ← HashNetworkNumber[e.destNetwork];
    
    -- remove entry from the routing table's collision chain
    temp ← routingTable[bucket];
    IF CommFlags.doDebug AND (temp = NIL) THEN
      Driver.Glitch[INRRoutingTableScrambled];
    UNTIL (temp = NIL) OR (temp = e) DO
      prev ← temp;
      temp ← temp.nextRTE;
      ENDLOOP;
    IF CommFlags.doDebug AND (temp = NIL) THEN
      Driver.Glitch[INRRoutingTableScrambled];
    IF (prev = NIL) THEN routingTable[bucket] ← e.nextRTE
    ELSE
      BEGIN
      IF CommFlags.doDebug
        AND ((prev.nextRTE # e) OR (prev.nextRTE = NIL))
        THEN Driver.Glitch[INRRoutingTableScrambled];
      prev.nextRTE ← e.nextRTE;
      END;
    e.nextRTE ← NIL;
    
    -- remove entry from the numerically sequenced network number list
    prev ← NIL;
    temp ← numSequencedList;
    UNTIL (temp = NIL) OR (temp = e) DO
      prev ← temp;
      temp ← temp.nextNSE;
      ENDLOOP;
    IF CommFlags.doDebug AND (temp = NIL) THEN
      Driver.Glitch[INRRoutingTableScrambled];
    IF (prev = NIL) THEN numSequencedList ← e.nextNSE
    ELSE
      BEGIN
      IF CommFlags.doDebug
        AND ((prev.nextNSE # e) OR (prev.nextNSE = NIL))
        THEN Driver.Glitch[INRRoutingTableScrambled];
      prev.nextNSE ← e.nextNSE;
      END;
    e.nextNSE ← NIL;
    
    routingTableSize ← routingTableSize - 1;
    StatIncr [@InrInternal.stats.destNetsDeleted];
    END; --of RemoveEntry
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This procedure removes a network from the NS Router's tables.
  -- RemoveNet is passed to Pilot Router
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  RemoveNet: PUBLIC PROCEDURE [net: NetworkContext] =
    BEGIN
    unknownNetwork: Driver.Device;
    
    FindNetworkNumberLocked: ENTRY PROCEDURE [n: NetworkNumber]
      RETURNS [e: RoutingTableEntry] = INLINE
      BEGIN ENABLE UNWIND => NULL;
      e ← FindNetworkNumber[n, FALSE-- don't reorder chain--]; END;
      
    RemoveNetDeviceLocked[net];
    --we may have removed the nullNetworkNumber network;  if so replace it
    IF (FindNetworkNumberLocked[nullNetworkNumber] = NIL) THEN
      BEGIN
      unknownNetwork ← Driver.GetDeviceChain[];
      UNTIL (unknownNetwork # net.network) OR (unknownNetwork = NIL) DO
        unknownNetwork ← unknownNetwork.next;
       	ENDLOOP; --try to find another network
      IF (unknownNetwork # NIL) THEN
        AddNet[Protocol1.GetContext[unknownNetwork, ns]];
      END; -- of replacing the net 0 rte
    END; --of RemoveNet
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- This procedure removes a network (device) from the NS Router's tables.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  RemoveNetDeviceLocked: PUBLIC ENTRY PROCEDURE [oldNetwork: NetworkContext] =
    BEGIN ENABLE UNWIND => NULL;
    e: RoutingTableEntry;
    anyChanges: BOOLEAN ← FALSE;
    DO
      IF (e ← FindNetwork[oldNetwork]) = NIL THEN EXIT;
      -- mark the rte as pointing to an obsolete network driver
      e↑ ← [destNetwork: e.destNetwork, delay: infinityHopCount,
        timeUnits: updateCycles, -- so we keep the rte entry around for awhile
	nextRTE: e.nextRTE, nextNSE: e.nextNSE, route: unknownHostID,
        context: NIL, changed: TRUE];
      anyChanges ← TRUE;
      ENDLOOP;
    IF anyChanges THEN
      -- send out gratuitous routing response for the new info
      NOTIFY routingTableChanged;
    END;  --of RemoveNetDeviceLocked
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- RoutingTableActivate create and initialize routing table.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  
  RoutingTableActivate: PUBLIC ENTRY PROCEDURE [
    numberOfHashBits: CARDINAL ← InrInternal.defaultNumberOfHashBits,
    markInvalidEntriesDead: BOOLEAN ← TRUE] =
    BEGIN ENABLE UNWIND => NULL;
    
    pageCount, tableLength: CARDINAL;
    
    markDeadEntries ← markInvalidEntriesDead;
    
    -- calculate the length of the routing table
    tableLength ← 1;
    FOR i: CARDINAL IN [0..numberOfHashBits) DO
      tableLength ← tableLength * 2;
      ENDLOOP;
    hashingMask ← tableLength - 1;
    hashTableSize ← tableLength;
    
    pageCount ← (SIZE[RoutingTableEntry]*tableLength) / Space.wordsPerPage;
    IF ((SIZE[RoutingTableEntry]*tableLength) MOD Space.wordsPerPage) > 0
      THEN pageCount ← pageCount + 1;
    
    routingTable ← DESCRIPTOR [Space.ScratchMap[count: pageCount], tableLength];
    FOR i:CARDINAL IN [0..tableLength) DO routingTable[i] ← NIL; ENDLOOP;
    numSequencedList ← NIL;
    routingTableSize ← 0;
    END; --of RoutingTableActivate
    
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
  -- FORKED INTERNETWORK ROUTER PROCESS
  -- This process sends out gratuitous routing response packets for tuples
  -- that have changed. It will not send out packets faster than once per
  -- second.
  -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --
   
  RoutingTableChanged: PUBLIC PROCEDURE =
    BEGIN
    
    Wait: ENTRY PROCEDURE = INLINE {
      ENABLE UNWIND => NULL;
      WAIT routingTableChanged;
      }; --end of Wait
      
    UNTIL InrInternal.pleaseStop DO
      ENABLE ABORTED => EXIT;
      Wait []; --wait for a routing change to occur
      Process.Pause [Process.SecondsToTicks [1]]; --pause to possibly make
        -- other routing table changes thus possibly minimazing the amount
	-- of traffic
      -- announce to everyone the routing changes
      InrInternal.SendGratuitousResponse [to: allLocalInternetRouters,
	b: NIL, allAreUnreachable: FALSE, onlyChangedEntries: TRUE];
      StatIncr [@InrInternal.stats.gratuitousRoutingResponseSent];
      ENDLOOP;
    END; -- of INRRoutingTableChanged
    
    
  --**************** Statistics ****************
    
  PulsesBump: PROCEDURE [
    counter: LONG POINTER TO System.Pulses, bumpAmount: System.Pulses] = INLINE
    BEGIN
    overflow: System.Pulses = LOOPHOLE[LAST[LONG CARDINAL]];
    counter↑ ← System.Pulses[MIN[(counter↑ + bumpAmount), overflow]];
    END;
    
  SearchDurationBump: PROCEDURE [
    pulses: System.Pulses, search: InrStats.RoutingTableSearch] = INLINE
    BEGIN
    IF (InrInternal.stats = NIL) THEN RETURN;
    pulses ← System.Pulses[System.GetClockPulses[] - pulses];
    StatBump [@InrInternal.stats.searchTable[search].totalSearches, 1];
    PulsesBump [@InrInternal.stats.searchTable[search].pulsesSearching, pulses];
    END; --of SearchDurationBump

  StatBump: PROCEDURE [
    counter: LONG POINTER TO LONG CARDINAL, bumpAmount: CARDINAL] = INLINE
    BEGIN
    --locals
    <<
    --add bumpAmount to counter; if bumpAmount < 10000, there will never
    --be overflow
    counter↑ ← (counter↑ + bumpAmount) MOD (LAST[LONG CARDINAL] - 10000);
    >>
    counter↑ ← MIN[(counter↑ + bumpAmount), (LAST[LONG CARDINAL] - 10000)];
    END;
  
  StatIncr: PROCEDURE [counter: LONG POINTER TO LONG CARDINAL] = INLINE
    --add one to counter
    BEGIN
    --locals
    counter↑ ← (counter↑ + 1) MOD (LAST[LONG CARDINAL] - 1);
    END;


  -- MAINLINE CODE --
  
  -- no action
  
  END... --of InrINRRoutingTableImpl
  
  
<<

LOG

 5-Feb-88 13:48:21  AOF  Trimmed for PupGateway
>>