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

DIRECTORY
  Basics USING [LongNumber],
  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
    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[LOOPHOLE[mumble, Basics.LongNumber].lowbits 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.