-- File: PupRouterIn.mesa,  Last Edit: HGM  February 25, 1981  3:17 PM

DIRECTORY
  Inline USING [LowHalf],
  System 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
    Inline, System, 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 ← System.GetClockPulses[];
	  -- Alto Pulses are short
	  lightning ← -INTEGER[Inline.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
      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 < LENGTH[routingTable] 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..LENGTH[routingTable])
    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 < LENGTH[routingTable] 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 >= LENGTH[routingTable] 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 >= LENGTH[routingTable] THEN RETURN[NIL];
    RETURN[@routingTable[net]];
    END;

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

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

  END.