-- File: RealForwarder.mesa - last edit:
-- AOF                  3-Feb-88 15:42:23
-- WIrish               5-Jun-86 10:15:00
-- Copyright (C) 1984, 1985, 1988 by Xerox Corporation. All rights reserved. 

DIRECTORY
  Buffer USING [ReturnBuffer],
  Heap USING [systemZone],
  Inline USING [LowHalf],
  Process USING [Detach, MsecToTicks, SetPriority, SetTimeout, SecondsToTicks],
  Runtime USING [IsBound],
  System USING [
    GetGreenwichMeanTime, GreenwichMeanTime, Pulses, GetClockPulses, PulsesToMicroseconds],
  CommFlags USING [doStats],
  Driver USING [Device, GetDeviceChain, PutOnGlobalDoneQueue],
  ForwarderDefs USING [
    Counters, SetupForwarderThings, ForwarderStats, PrintBadPup,
    statPupGatewayPacketMs, statGateInfoReplies,
    statRoutingTableChanges, statGateInfoBC, statGateLowOnBuffers,
    statGarbageSourceOrDest, statNoRouteToNet, statTooManyHops,
    forwarderStatsRequest],
  Protocol1 USING [GetContext, EncapsulateAndTransmit],
  PupDefs,
  PupRouterDefs USING [
    BuildErrorPup, EnumerateRoutingTable, GetRoutingTableEntry, maxHop, NetworkContext,
    PupGateInfo, PupGatewaySee, RejectPupWithBadChecksum, RoutingTableEntry,
    SetBadPupProc, SetPupChecksum],
  PupTypes USING [
    Pair, PupAddress, PupNetID, PupHostID, PupErrorCode, allNets, allHosts, gatewaySoc,
    maxDataWordsPerRoutingPup],
  RoutingFudges USING [],
  Stats USING [StatBump, StatIncr];

RealForwarder: MONITOR
  IMPORTS
    Heap, Inline, Process, Runtime, System, Stats, ForwarderDefs, PupRouterDefs,
    Driver, Protocol1, PupDefs, HonestToGod: Buffer
  EXPORTS Buffer, ForwarderDefs, PupRouterDefs, RoutingFudges =
  BEGIN OPEN Stats, PupRouterDefs, PupDefs, PupTypes;

  -- EXPORTed TYPEs
  Device: PUBLIC TYPE = Driver.Device;

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

  packets: LONG POINTER TO ForwarderDefs.Counters;
  bytes: LONG POINTER TO ForwarderDefs.Counters;
  bad: LONG POINTER TO ForwarderDefs.Counters;
  nets: CARDINAL;
  stop: BOOLEAN ← FALSE;
  pupGateSoc: PupSocket;
  lake: PupDefs.AccessHandle ← PupDefs.MakePool[send: 20, receive: 0];
  pond: PupDefs.AccessHandle ← PupDefs.MakePool[send: 1, receive: 0];
  extraHops: CARDINAL ← 0;


  GetPointerToPupGateStats: PUBLIC PROCEDURE
    RETURNS [
      packets, bytes: LONG POINTER TO ForwarderDefs.Counters, nets: CARDINAL] =
    BEGIN RETURN[RealForwarder.packets, RealForwarder.bytes, RealForwarder.nets]; END;

  GetPointerToBadPupStats: PUBLIC PROCEDURE
    RETURNS [
      bad: LONG POINTER TO ForwarderDefs.Counters] =
    BEGIN RETURN[RealForwarder.bad]; END;

  SetPupFudge: PUBLIC PROCEDURE [hops: CARDINAL] =
    BEGIN
    extraHops ← hops;
    END;

  PupForwarderOn: PUBLIC PROCEDURE =
    BEGIN
    network: Device ← Driver.GetDeviceChain[];
    IF doStats THEN
      BEGIN
      finger: Device;
      size: CARDINAL;
      nets ← 1;  -- discard
      FOR finger ← network, finger.next UNTIL finger = NIL DO
        nets ← nets + 1; ENDLOOP;
      nets ← nets + 4;  -- Allocate room for more drivers.
      size ← nets*nets;
      packets ← Heap.systemZone.NEW[ForwarderDefs.Counters[size]];
      bytes ← Heap.systemZone.NEW[ForwarderDefs.Counters[size]];
      FOR i: CARDINAL IN [0..size) DO packets[i] ← bytes[i] ← 0; ENDLOOP;
      bad ← Heap.systemZone.NEW[ForwarderDefs.Counters[nets]];
      FOR i: CARDINAL IN [0..nets) DO bad[i] ← 0; ENDLOOP;
      END;
    Process.Detach[FORK PupForwarderOn2[]];
    END;

  PupForwarderOn2: ENTRY PROCEDURE =
    BEGIN
    network: Device ← Driver.GetDeviceChain[];
    context: PupRouterDefs.NetworkContext ← Protocol1.GetContext[network, pup];
    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 context.pupNetNumber # 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;
    body: PupDefs.Body;
    DO
      b ← pupGateSoc.get[];
      IF b = NIL THEN LOOP;
      body ← b.pup;
      SELECT body.pupType FROM
        gatewayRequest =>
          BEGIN
          IF ~tellEverybody AND body.dest.host = allHosts THEN
            BEGIN  -- don't answer broadcast requests yet
            PupDefs.ReturnBuffer[b];
            END
          ELSE
            BEGIN
            SwapPupSourceAndDest[b];
            SendPupRoutingPacket[b, FALSE, lake, FALSE];
            IF doStats THEN StatIncr[ForwarderDefs.statGateInfoReplies];
            END
          END;
        gatewayInfo =>
          BEGIN  -- RoutingTable packet from another Gateway
	  pulses: System.Pulses ← System.GetClockPulses[];
	  ms: LONG CARDINAL;
          IF doStats AND ~CommFlags.doStats THEN StatIncr[statPupGatewayPacketsRecv];
          IF body.pupTransportControl = 0 AND PupGatewaySee[b] THEN
            BEGIN
            KickTalker[];
            IF doStats THEN StatIncr[ForwarderDefs.statRoutingTableChanges];
            END;
          PupDefs.ReturnBuffer[b];
          pulses ← System.Pulses[System.GetClockPulses[] - pulses];
	  ms ← System.PulsesToMicroseconds[pulses]/1000;
          IF doStats THEN Stats.StatBump[ForwarderDefs.statPupGatewayPacketMs, Inline.LowHalf[ms]];
          END;
        ForwarderDefs.forwarderStatsRequest => PutStats[b];
        ENDCASE => PupDefs.ReturnBuffer[b];
      ENDLOOP;
    END;

  talker, pause: CONDITION;

  KickTalker: PUBLIC ENTRY PROCEDURE = BEGIN NOTIFY talker; END;
  
  broadcastSeconds: CARDINAL = 30;
  minDallySeconds: CARDINAL = 2;

  -- pond has only 1 send buffer, so we must wait until it has been sent to all nets
  -- before sending another packet
  PupTalk: PUBLIC PROCEDURE =
    BEGIN
    Pause: ENTRY PROCEDURE [seconds: LONG CARDINAL] = -- Beware of ML if GetBuffer waits
      BEGIN
      IF seconds < (broadcastSeconds-minDallySeconds) THEN {
        sec: CARDINAL ← CARDINAL[seconds];
	dally: CARDINAL ← broadcastSeconds-minDallySeconds-sec;
        Process.SetTimeout[@talker, Process.SecondsToTicks[dally]];
        WAIT talker; };
      WAIT pause;  -- Don't broadcast too often
      END;
    i: CARDINAL ← 0;
    Process.SetTimeout[@pause, Process.SecondsToTicks[minDallySeconds]];
    DO
      start: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
      IF tellEverybody THEN
        BEGIN
        b: PupBuffer ← PupDefs.GetBuffer[pond, send];
	body: PupDefs.Body = b.pup;
        body.pupID ← [0, i];
        body.dest ← [allNets, allHosts, gatewaySoc];
        body.source ← [, , gatewaySoc];
        SendPupRoutingPacket[b, TRUE, pond, TRUE];
        IF doStats THEN StatIncr[ForwarderDefs.statGateInfoBC];
        i ← i + 1;
        END;
      Pause[System.GetGreenwichMeanTime[]-start];
      ENDLOOP;
    END;

  SendPupRoutingPacket: PROCEDURE [
    b: PupBuffer, kick: BOOLEAN, pool: PupDefs.AccessHandle, wait: BOOLEAN] =
    BEGIN
    body: PupDefs.Body ← b.pup;
    source: PupTypes.PupAddress ← body.source;
    dest: PupTypes.PupAddress ← body.dest;
    id: PupTypes.Pair ← body.pupID;
    packetNumber: CARDINAL ← 1;
    data: LONG POINTER TO PupGateInfo ← LOOPHOLE[@body.pupWords[0]];
    AddOne: PROCEDURE [rte: RoutingTableEntry] =
      BEGIN
      context: PupRouterDefs.NetworkContext ← rte.context; -- rte.context might change
      IF rte.net = 0 OR context = NIL THEN RETURN;
      IF b = NIL THEN RETURN;
      IF n = maxDataWordsPerRoutingPup/SIZE[PupGateInfo] THEN
        BEGIN
        body.pupType ← gatewayInfo;
        SetPupContentsWords[b, n*SIZE[PupGateInfo]];
        PupRouterSendThis[b];
        packetNumber ← packetNumber + 1;
        IF kick THEN id.a ← packetNumber;
        b ← PupDefs.GetBuffer[pool, send, wait];
        IF b = NIL THEN RETURN;
	body ← b.pup;
        body.pupID ← id;
        body.source ← source;
        body.dest ← dest;
        data ← LOOPHOLE[@body.pupWords[0]];
        n ← 0;
        END;
      IF rte.hop = 0 THEN
        data↑ ← [
          net: rte.net, viaNet: rte.net, viaHost: [context.pupHostNumber],
          hop: 0 + extraHops]
      ELSE
        data↑ ← [
          net: rte.net, viaNet: [context.pupNetNumber], viaHost: rte.route,
          hop: rte.hop + extraHops];
      IF stop THEN data.hop ← maxHop + 1;
      data ← data + SIZE[PupGateInfo];
      n ← n + 1;
      END;
    n: CARDINAL ← 0;
    IF kick THEN body.pupID.a ← packetNumber;
    EnumerateRoutingTable[AddOne];  --AddOne will reassign 'body' if b # NIL
    IF b = NIL THEN RETURN;
    body.pupType ← gatewayInfo;
    SetPupContentsWords[b, n*SIZE[PupGateInfo]];
    PupRouterSendThis[b];
    END;

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

  DoForwardPupBuffer: PUBLIC PROCEDURE [b: PupBuffer] =
    BEGIN
    body: PupDefs.Body ← b.pup;
    fromContext: PupRouterDefs.NetworkContext ← b.fo.context;
    fromNetwork: Device ← b.fo.network;
    toContext: PupRouterDefs.NetworkContext;
    sourceNet: PupNetID ← body.source.net;
    destNet: PupNetID ← body.dest.net;
    length: CARDINAL = body.pupLength;
    route: PupHostID;
    rte: RoutingTableEntry;
    old: WORD;
    errorCode: PupTypes.PupErrorCode;
    -- Allow directed broadcasts, but avoid nonsense and loops
    globalFromContext ← fromContext;
    globalFromNetwork ← fromNetwork;
    IF sourceNet = 0 OR destNet = 0 OR body.source.host = allHosts
      OR (destNet = fromContext.pupNetNumber AND body.dest.host = allHosts) THEN
      BEGIN  -- Don't forward this Pup
      PupDefs.ReturnBuffer[b];
      IF doStats THEN StatIncr[statPupNotForwarded];
      IF doStats THEN StatIncr[ForwarderDefs.statGarbageSourceOrDest];
      RETURN;
      END;
    old ← (@body.pupWords[0] + magicOne)↑;
    IF body.pupTransportControl = maxHop*20B THEN
      BEGIN
      DoErrorPup[b, eightHopsPupErrorCode, "Discarded by 16th Gateway"];
      IF doStats THEN StatIncr[ForwarderDefs.statTooManyHops];
      RETURN;
      END;
    body.pupTransportControl ← body.pupTransportControl + 20B;
    rte ← GetRoutingTableEntry[destNet];
    IF rte = NIL OR rte.hop > maxHop OR (toContext ← rte.context) = NIL THEN
      BEGIN  -- don't know how to get there
      DoErrorPup[b, cantGetTherePupErrorCode, "No route to that Net"];
      IF doStats THEN StatIncr[ForwarderDefs.statNoRouteToNet];
      RETURN;
      END;
IF FALSE THEN
    IF BuffersLeft[NIL] < 3 THEN
      BEGIN
      IF doStats THEN StatIncr[statPupNotForwarded];
      PupDefs.ReturnBuffer[b];
      IF doStats THEN StatIncr[ForwarderDefs.statGateLowOnBuffers];
      RETURN;
      END;
    b.fo.context ← toContext;
    b.fo.network ← toContext.network;
    UpdatePupChecksum[b, magicTwo, old];
    IF (route ← rte.route) = 0 THEN route ← body.dest.host;
    errorCode ← noErrorPupErrorCode; 				-- ***********************
    Protocol1.EncapsulateAndTransmit[LOOPHOLE[b], @route];
    SELECT errorCode FROM
      noErrorPupErrorCode =>
        BEGIN
        IF doStats THEN
          BEGIN
          StatIncr[statPupForwarded];
          BumpPupStats[fromNetwork.index, toContext.network.index, length];
          END;
        END;
      ENDCASE =>
        BEGIN
        IF errorCode = cantGetTherePupErrorCode AND rte.hop # 0 THEN
          rte.hop ← maxHop + 1;
        DoErrorPup[b, errorCode, NIL];
        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;

  
  globalFromNetwork: Device; -- Krock because interfaces are screwy
  
  -- Krock: PhoneNetDriver doesn't copy it ***********
  globalFromContext: PupRouterDefs.NetworkContext ← NIL;

  DoErrorPup: PUBLIC PROCEDURE [
    b: PupBuffer, code: PupTypes.PupErrorCode, text: LONG STRING] =
    BEGIN
    route: PupHostID;
    destNet: PupNetID;
    rte: RoutingTableEntry;
    body: PupDefs.Body ← b.pup;
    context: PupRouterDefs.NetworkContext ← b.fo.context;
    forwarding: BOOL = body.pupTransportControl > 0FH; -- 4 bits of hop, 4 spares
    IF b.requeueProcedure # LOOPHOLE[HonestToGod.ReturnBuffer] THEN 
      -- Don't mash a packet we might retransmit
      Driver.PutOnGlobalDoneQueue[LOOPHOLE[b]];
    IF forwarding THEN { -- Beware of address faults
      IF doStats THEN BumpPupStats[globalFromNetwork.index, 0, 0];
      IF 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;
    IF globalFromContext # NIL THEN
      -- ARGH. NIL FAULT IF PROBLEMS FROM LOCAL TRAFFIC TOO EARLY
      body.source ← [
        [globalFromContext.pupNetNumber],
        [globalFromContext.pupHostNumber], [0, 0]];
    destNet ← body.dest.net;
    rte ← GetRoutingTableEntry[destNet];
    IF rte = NIL OR rte.hop > maxHop OR (context ← rte.context) = NIL THEN
      BEGIN PupDefs.ReturnBuffer[b]; RETURN; END;
    b.fo.context ← context;
    b.fo.network ← context.network;
    IF (route ← rte.route) = 0 THEN route ← body.dest.host;
    SetPupChecksum[b];
    Protocol1.EncapsulateAndTransmit[LOOPHOLE[b], @route];
    END;

  UpdatePupChecksum: PUBLIC PROCEDURE [
    b: PupBuffer, offset: INTEGER, oldValue: WORD] =
    BEGIN
    len: CARDINAL ← (b.pup.pupLength - 1)/2;
    checksumLoc: LONG POINTER ← @b.pup.pupLength + len;
    diff: WORD;
    IF checksumLoc↑ = 177777B THEN RETURN;
    diff ← OnesSub[
      (@b.pup.pupWords[0] + (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
    network: Device ← b.fo.network;
    index: CARDINAL ← network.index;
    bad[index] ← bad[index] + 1;
    IF badPupQueue.length > 5 THEN
      BEGIN PupRouterDefs.RejectPupWithBadChecksum[b]; RETURN; END;
    Enqueue[@badPupQueue, b];
    NOTIFY badPupArrived;
    END;

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

  LookAtBadPups: PROCEDURE =
    BEGIN
    IF ~Runtime.IsBound[LOOPHOLE[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 PupDefs.ReturnBuffer[b]; RETURN; END;
    Enqueue[@statsQueue, b];
    NOTIFY statsArrived;
    END;

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

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

  -- Initialization

  ForwarderDefs.SetupForwarderThings[];
  PupDefs.QueueInitialize[@badPupQueue];
  Process.SetTimeout[@badPupArrived, Process.SecondsToTicks[60]];
  PupDefs.QueueInitialize[@statsQueue];
  Process.SetTimeout[@statsArrived, Process.SecondsToTicks[60]];
  END.