-- File: RealForwarder.mesa,  Last Edit: HGM  March 17, 1981  8:27 PM

DIRECTORY
  Process USING [Detach, SetPriority, SetTimeout, MsecToTicks],
  Runtime USING [IsBound],
  Storage USING [Node],
  StatsDefs USING [StatIncr],
  CommFlags USING [doStats],
  ForwarderDefs USING [
    SetupForwarderThings, ForwarderStats, PrintBadPup,
    statGateInfoReplies, statRoutingTableChanges, statGateInfoBC,
    statGateLowOnBuffers, statGarbageSourceOrDest, statNoRouteToNet,
    statTooManyHops, forwarderStatsRequest],
  PupRouterDefs USING [
    BuildErrorPup, EnumerateRoutingTable, GetRoutingTableEntry, maxHop,
    PupGateInfo, PupGatewaySee, RejectPupWithBadChecksum, RoutingTableEntry,
    SetBadPupProc, SetPupChecksum],
  PupDefs,
  DriverDefs USING [Network, GetDeviceChain, MaybeGetFreePupBuffer],
  BufferDefs USING [PupBuffer, ReturnFreeBuffer, BuffersLeft, QueueInitialize],
  PupTypes USING [
    PupNetID, PupHostID, PupErrorCode, allNets, allHosts, gatewaySoc,
    maxDataWordsPerRoutingPup];

RealForwarder: MONITOR
  IMPORTS
    Process, Runtime, Storage, StatsDefs, ForwarderDefs, PupRouterDefs, DriverDefs,
    PupDefs, BufferDefs
  EXPORTS BufferDefs, ForwarderDefs, PupRouterDefs
  SHARES BufferDefs =
  BEGIN OPEN StatsDefs, PupRouterDefs, PupDefs, BufferDefs, PupTypes;

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

  tellEverybody: BOOLEAN ← TRUE; -- debugging flag to avoid poluting the world

  packets: POINTER TO ARRAY [0..0) OF LONG CARDINAL;
  bytes: POINTER TO ARRAY [0..0) OF LONG CARDINAL;
  nets: CARDINAL;
  stop: BOOLEAN ← FALSE;
  pupGateSoc: PupSocket;


  GetPointerToPupGateStats: PUBLIC PROCEDURE
    RETURNS [
      POINTER TO ARRAY [0..0) OF LONG CARDINAL, POINTER TO ARRAY [0..0) OF LONG
      CARDINAL, CARDINAL] = BEGIN RETURN[packets, bytes, nets]; END;

  PupForwarderOn: PUBLIC PROCEDURE =
    BEGIN
    network: Network ← DriverDefs.GetDeviceChain[];
    IF CommFlags.doStats THEN
      BEGIN
      finger: Network;
      size: CARDINAL;
      nets ← 1; -- discard
      FOR finger ← network, finger.next UNTIL finger = NIL DO
	nets ← nets + 1; ENDLOOP;
      size ← nets*nets;
      packets ← Storage.Node[2*size];
      bytes ← Storage.Node[2*size];
      FOR i: CARDINAL IN [0..size) DO packets[i] ← bytes[i] ← 0; ENDLOOP;
      END;
    Process.Detach[FORK PupForwarderOn2[]];
    END;

  PupForwarderOn2: ENTRY PROCEDURE =
    BEGIN
    network: Network ← DriverDefs.GetDeviceChain[];
    world: PupAddress ← [[0], [0], gatewaySoc];
    spin: CONDITION;
    Process.SetTimeout[@spin, Process.MsecToTicks[100]];
    -- As a hack, we can run without knowing our network number at startup time.
    -- Wait here to be sure we don't polute things if we don't know it yet.
    Process.Detach[FORK LookAtBadPups[]];
    Process.Detach[FORK TransferForwarderStats[]];
    UNTIL network.netNumber # [0, 0] DO IF stop THEN RETURN; WAIT spin; ENDLOOP;
    Process.Detach[FORK PupTalk[]];
    pupGateSoc ← PupSocketMake[gatewaySoc, world, veryLongWait];
    Process.Detach[FORK PupListen[]];
    END;

  PupForwarderOff: PUBLIC ENTRY PROCEDURE =
    BEGIN
    spin: CONDITION;
    Process.SetTimeout[@spin, Process.MsecToTicks[100]];
    stop ← TRUE;
    THROUGH [0..5) DO
      -- 5 cycles of 100 ms each
      NOTIFY talker;
      WAIT spin;
      ENDLOOP;
    END;

  PupListen: PUBLIC PROCEDURE =
    BEGIN
    b: PupBuffer;
    DO
      b ← pupGateSoc.get[];
      IF b = NIL THEN LOOP;
      SELECT b.pupType FROM
	gatewayRequest =>
	  BEGIN
	  IF ~tellEverybody AND b.dest.host = allHosts THEN
	    BEGIN -- don't answer broadcast requests yet
	    ReturnFreePupBuffer[b];
	    END
	  ELSE
	    BEGIN
	    SwapPupSourceAndDest[b];
	    SendPupRoutingPacket[b];
	    IF CommFlags.doStats THEN StatIncr[ForwarderDefs.statGateInfoReplies];
	    END
	  END;
	gatewayInfo =>
	  BEGIN -- RoutingTable packet from another Gateway
	  IF PupGatewaySee[b] THEN
	    BEGIN
	    KickTalker[];
	    IF CommFlags.doStats THEN StatIncr[ForwarderDefs.statRoutingTableChanges];
	    END;
	  ReturnFreePupBuffer[b];
	  END;
	ForwarderDefs.forwarderStatsRequest => PutStats[b];
	ENDCASE => ReturnFreePupBuffer[b];
      ENDLOOP;
    END;

  talker: CONDITION;

  KickTalker: PUBLIC ENTRY PROCEDURE = BEGIN NOTIFY talker; END;

  PupTalk: PUBLIC ENTRY PROCEDURE =
    BEGIN
    spin: CONDITION;
    b: PupBuffer;
    i: CARDINAL ← 0;
    Process.SetTimeout[@talker, Process.MsecToTicks[30000]];
    Process.SetTimeout[@spin, Process.MsecToTicks[100]];
    DO
      -- forever
      IF tellEverybody THEN
	BEGIN
	i ← i + 1;
	UNTIL (b ← DriverDefs.MaybeGetFreePupBuffer[]) # NIL DO
	  WAIT spin; ENDLOOP;
	b.pupID ← [0, i];
	b.dest ← [allNets, allHosts, gatewaySoc];
	b.source ← [, , gatewaySoc];
	SendPupRoutingPacket[b];
	IF CommFlags.doStats THEN StatIncr[ForwarderDefs.statGateInfoBC];
	END;
      WAIT talker;
      ENDLOOP;
    END;

  SendPupRoutingPacket: PROCEDURE [b: PupBuffer] =
    BEGIN
    data: LONG POINTER TO PupGateInfo ← LOOPHOLE[@b.pupWords[0]];
    AddOne: PROCEDURE [rte: RoutingTableEntry] =
      BEGIN
      IF rte.net = 0 OR rte.network = NIL THEN RETURN;
      IF n = maxDataWordsPerRoutingPup/SIZE[PupGateInfo] THEN
	BEGIN
	b2: PupBuffer ← DriverDefs.MaybeGetFreePupBuffer[];
	IF b2 # NIL THEN
	  BEGIN
	  b2.pupID ← b.pupID;
	  b2.source ← b.source;
	  b2.dest ← b.dest;
	  b2.pupType ← b.pupType;
	  SetPupContentsWords[b, n*SIZE[PupGateInfo]];
	  PupRouterSendThis[b];
	  b ← b2;
	  END;
	data ← LOOPHOLE[@b.pupWords[0]];
	n ← 0;
	END;
      IF rte.hop = 0 THEN
	data↑ ←
	  [net: rte.net, viaNet: rte.net, viaHost: [rte.network.hostNumber],
	    hop: 0]
      ELSE
	data↑ ←
	  [net: rte.net, viaNet: [rte.network.netNumber.b], viaHost: rte.route,
	    hop: rte.hop];
      IF stop THEN data.hop ← maxHop + 1;
      data ← data + SIZE[PupGateInfo];
      n ← n + 1;
      END;
    n: CARDINAL ← 0;
    b.pupType ← gatewayInfo;
    EnumerateRoutingTable[AddOne];
    SetPupContentsWords[b, n*SIZE[PupGateInfo]];
    PupRouterSendThis[b];
    END;

  magicOne: INTEGER = -9; -- offset for pupTransportControl
  magicTwo: CARDINAL = 1;

  DoForwardPupBuffer: PUBLIC PROCEDURE [b: PupBuffer] =
    BEGIN
    fromNetwork: Network ← b.network;
    toNetwork: Network;
    sourceNet: PupNetID ← b.source.net;
    destNet: PupNetID ← b.dest.net;
    route: PupHostID;
    rte: RoutingTableEntry;
    old: WORD;
    errorCode: PupTypes.PupErrorCode;
    -- Allow directed broadcasts, but avoid nonsense and loops
    IF sourceNet = 0 OR destNet = 0 OR b.source.host = allHosts OR
      (destNet = fromNetwork.netNumber.b AND b.dest.host = allHosts) THEN
      BEGIN -- Don't forward this Pup
      ReturnFreePupBuffer[b];
      IF CommFlags.doStats THEN StatIncr[statPupNotForwarded];
      IF CommFlags.doStats THEN StatIncr[ForwarderDefs.statGarbageSourceOrDest];
      RETURN;
      END;
    old ← (@b.pupWords[0] + magicOne)↑;
    IF b.pupTransportControl = maxHop*20B THEN
      BEGIN
      DoErrorPup[b, eightHopsPupErrorCode, "Discarded by 16th Gateway"];
      IF CommFlags.doStats THEN BumpPupStats[fromNetwork.index, 0, 0];
      IF CommFlags.doStats THEN StatIncr[ForwarderDefs.statTooManyHops];
      RETURN;
      END;
    b.pupTransportControl ← b.pupTransportControl + 20B;
    rte ← GetRoutingTableEntry[destNet];
    IF rte = NIL OR rte.hop > maxHop OR (toNetwork ← rte.network) = NIL THEN
      BEGIN -- don't know how to get there
      DoErrorPup[b, cantGetTherePupErrorCode, "No route to that Net"];
      IF CommFlags.doStats THEN BumpPupStats[fromNetwork.index, 0, 0];
      IF CommFlags.doStats THEN StatIncr[ForwarderDefs.statNoRouteToNet];
      RETURN;
      END;
    IF BuffersLeft[] < 3 THEN
      BEGIN
      IF CommFlags.doStats THEN StatIncr[statPupNotForwarded];
      ReturnFreeBuffer[b];
      IF CommFlags.doStats THEN BumpPupStats[fromNetwork.index, 0, 0];
      IF CommFlags.doStats THEN StatIncr[ForwarderDefs.statGateLowOnBuffers];
      RETURN;
      END;
    b.network ← toNetwork;
    UpdatePupChecksum[b, magicTwo, old];
    IF (route ← rte.route) = 0 THEN route ← b.dest.host;
    toNetwork.encapsulatePup[b, route];
    errorCode ← toNetwork.forwardBuffer[b];
    SELECT errorCode FROM
      noErrorPupErrorCode =>
	BEGIN
	IF CommFlags.doStats THEN
	  BEGIN
	  StatIncr[statPupForwarded];
	  BumpPupStats[fromNetwork.index, toNetwork.index, b.pupLength];
	  END;
	END;
      ENDCASE =>
	BEGIN
	IF errorCode = cantGetTherePupErrorCode AND rte.hop # 0 THEN
	  rte.hop ← maxHop + 1;
	DoErrorPup[b, errorCode, NIL];
	IF CommFlags.doStats THEN BumpPupStats[fromNetwork.index, 0, 0];
	END;
    END;

  -- This is the routine that defines the layout of the statistics counters.
  -- Note that a dest of 0 is used for discard.

  BumpPupStats: PROCEDURE [sourceIndex, destIndex, length: CARDINAL] =
    BEGIN
    index: CARDINAL = sourceIndex + destIndex*nets;
    packets[index] ← packets[index] + 1;
    bytes[index] ← bytes[index] + length;
    END;

  DoErrorPup: PROCEDURE [
    b: PupBuffer, code: PupTypes.PupErrorCode, text: STRING] =
    BEGIN
    destNet: PupNetID;
    toNetwork: Network;
    route: PupHostID;
    rte: RoutingTableEntry;
    IF CommFlags.doStats THEN StatIncr[statPupNotForwarded];
    IF text = NIL THEN
      SELECT code FROM
	connectionLimitPupErrorCode =>
	  BEGIN
	  text ← "Gateway Output Queue connection limit exceeded";
	  code ← gatewayResourceLimitsPupErrorCode;
	  END;
	gatewayResourceLimitsPupErrorCode => text ← "Gateway Output Queue full";
	cantGetTherePupErrorCode => text ← "No route to host";
	ENDCASE;
    IF ~PupRouterDefs.BuildErrorPup[b, code, text] THEN RETURN;
    destNet ← b.dest.net;
    b.source.socket ← [0, 0];
    rte ← GetRoutingTableEntry[destNet];
    IF rte = NIL OR rte.hop > maxHop OR (toNetwork ← rte.network) = NIL THEN
      BEGIN ReturnFreePupBuffer[b]; RETURN; END;
    b.network ← toNetwork;
    IF (route ← rte.route) = 0 THEN route ← b.dest.host;
    SetPupChecksum[b];
    toNetwork.encapsulatePup[b, route];
    toNetwork.sendBuffer[b];
    END;

  UpdatePupChecksum: PUBLIC PROCEDURE [
    b: PupBuffer, offset: INTEGER, oldValue: WORD] =
    BEGIN
    len: CARDINAL ← (b.pupLength - 1)/2;
    checksumLoc: LONG POINTER ← @b.pupLength + len;
    diff: WORD;
    IF checksumLoc↑ = 177777B THEN RETURN;
    diff ← OnesSub[b.pupWords[offset + (magicOne - magicTwo)], oldValue];
    checksumLoc↑ ← OnesAdd[checksumLoc↑, LeftCycle[diff, len - offset]];
    END;

  OnesAdd: PROCEDURE [a, b: CARDINAL] RETURNS [c: CARDINAL] = INLINE
    BEGIN c ← a + b; IF c < a THEN c ← c + 1; IF c = 177777B THEN c ← 0; END;

  OnesSub: PROCEDURE [a, b: CARDINAL] RETURNS [c: CARDINAL] = INLINE
    BEGIN
    c ← a + (-b - 1);
    IF c < a THEN c ← c + 1;
    IF c = 177777B THEN c ← 0;
    END;

  LeftCycle: PROCEDURE [a, b: CARDINAL] RETURNS [c: CARDINAL] = INLINE
    BEGIN
    c ← a;
    THROUGH [0..(b MOD 16)) DO
      IF c < 100000B THEN c ← c*2 ELSE c ← c*2 + 1; ENDLOOP;
    END;

  badPupArrived: CONDITION;
  badPupQueue: QueueObject;

  PutBadPup: ENTRY PROCEDURE [b: PupBuffer] =
    BEGIN
    IF badPupQueue.length > 5 THEN
      BEGIN PupRouterDefs.RejectPupWithBadChecksum[b]; RETURN; END;
    EnqueuePup[@badPupQueue, b];
    NOTIFY badPupArrived;
    END;

  GetBadPup: ENTRY PROCEDURE RETURNS [b: PupBuffer] =
    BEGIN
    IF badPupQueue.length = 0 THEN WAIT badPupArrived;
    b ← DequeuePup[@badPupQueue];
    END;

  LookAtBadPups: PROCEDURE =
    BEGIN
    IF ~Runtime.IsBound[ForwarderDefs.PrintBadPup] THEN RETURN;
    Process.SetPriority[1];
    PupRouterDefs.SetBadPupProc[PutBadPup];
    DO ForwarderDefs.PrintBadPup[GetBadPup[]]; ENDLOOP;
    END;

  -- UGH, we can't just call somebody because we don't want to clutter up core with stuff that isn't normally needed, so we go through this horrible process switch to change priorities.

  statsArrived: CONDITION;
  statsQueue: QueueObject;

  PutStats: ENTRY PROCEDURE [b: PupBuffer] =
    BEGIN
    IF statsQueue.length > 5 THEN BEGIN ReturnFreePupBuffer[b]; RETURN; END;
    EnqueuePup[@statsQueue, b];
    NOTIFY statsArrived;
    END;

  GetStats: ENTRY PROCEDURE RETURNS [b: PupBuffer] =
    BEGIN
    WHILE statsQueue.length = 0 DO WAIT statsArrived; ENDLOOP;
    b ← DequeuePup[@statsQueue];
    END;

  TransferForwarderStats: PROCEDURE =
    BEGIN
    Process.SetPriority[1];
    DO ForwarderDefs.ForwarderStats[GetStats[]]; ENDLOOP;
    END;

  -- Initialization

  ForwarderDefs.SetupForwarderThings[];
  BufferDefs.QueueInitialize[@badPupQueue];
  Process.SetTimeout[@badPupArrived, Process.MsecToTicks[60000]];
  BufferDefs.QueueInitialize[@statsQueue];
  Process.SetTimeout[@statsArrived, Process.MsecToTicks[60000]];
  END.