-- File: PupRouterOut.mesa,  Last Edit:
--   HGM  October 16, 1979  11:08 PM
--   Taft  May 29, 1983  12:36 PM

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  StatsDefs: FROM "StatsDefs" USING [StatIncr],
  CommUtilDefs: FROM "CommUtilDefs" USING [GetTicks],
  PupRouterDefs: FROM "PupRouterDefs" USING [
    routerLock, maxHop, emptyCacheEntry, OutThings, routingCacheHead, routingCacheTail,
    pupRouterIsActive, probesLeftToDo, probeRetransmitTimeout, pleaseProbe,
    BeSurePupIsOn, SetPupChecksum, RoutingCacheEntry, GetRoutingCacheEntry],
  DriverDefs: FROM "DriverDefs" USING [
    firstNetwork, doDebug, doShow, doStats, doStorms,
    Glitch, Network, PutOnGlobalDoneQueue],
  PupDefs: FROM "PupDefs" USING [
    outgoingPup, zappedOutgoingPup, PupBuffer, GetFreePupBuffer,
    SetPupContentsWords, PupRouterBroadcastThis],
  BufferDefs: FROM "BufferDefs",
  PupTypes: FROM "PupTypes" USING [
    allNets, allHosts, gatewaySoc, PupType,
    PupAddress, PupHostID, PupNetID],
  DriverTypes: FROM "DriverTypes" USING [bufferSeal];

PupRouterOut: MONITOR LOCKS PupRouterDefs.routerLock
  IMPORTS StatsDefs, CommUtilDefs, PupRouterDefs, DriverDefs, PupDefs
  EXPORTS PupRouterDefs, PupDefs
  SHARES BufferDefs, DriverTypes =
BEGIN OPEN StatsDefs, PupRouterDefs, DriverDefs, PupDefs, PupTypes;

dataWordsPerPup: PUBLIC CARDINAL;

outThings: PUBLIC OutThings ← [
  outStormy: FALSE,
  showOut: FALSE,
  outShower: ];

-- parameters for killing packets
lightning: INTEGER ← 30;
bolt: INTEGER ← 10;

BufferSealBroken: PUBLIC ERROR = CODE;
IllegalPupLength: PUBLIC ERROR = CODE;

PupRouterSendThis: PUBLIC ENTRY PROCEDURE [b: PupBuffer] =
  BEGIN
  IF b.dest.net=allNets AND b.dest.host=allHosts THEN
    BEGIN
    PupRouterBroadcastThis[b];
    RETURN;
    END;
  SendUnlocked[b];
  END;

SneakySendUnlocked: PUBLIC PROCEDURE [b: PupBuffer] = JustSendUnlocked;
JustSendUnlocked: INTERNAL PROCEDURE [b: PupBuffer] =
  BEGIN
  SendUnlocked[b];
  END;

SendUnlocked: INTERNAL PROCEDURE [b: PupBuffer] =
  BEGIN
  d: PupAddress ← b.dest;
  n: PupNetID ← d.net;
  routing: POINTER TO RoutingCacheEntry;
  network: Network;
  h: PupHostID;
  IF doStats THEN StatIncr[statPupSent];

  IF doDebug THEN BeSurePupIsOn[];
  IF doDebug AND b.seal#DriverTypes.bufferSeal THEN Glitch[BufferSealBroken];

  IF doStorms AND outThings.outStormy  -- for debugging only
  AND (lightning←lightning+1)>bolt OR lightning<0 THEN
    BEGIN
    IF lightning>bolt THEN
      BEGIN
      IF bolt>100 THEN
        BEGIN
        lightning←-INTEGER[CommUtilDefs.GetTicks[] MOD 20B];
        bolt←10;
        END
      ELSE BEGIN lightning←0; bolt←bolt+1; END;
      END;
    IF doShow AND outThings.showOut THEN outThings.outShower[zappedOutgoingPup,b];
    PutOnGlobalDoneQueue[b];
    IF doStats THEN StatIncr[statZappedP];
    RETURN;
    END;

  -- Maybe we should do something for the local case?

  b.pupTransportControl ← 0;
  routing ← GetRoutingCacheEntry[net: n, ifMissing: probeAndReturn];
  IF routing=NIL OR routing.entry.hop>maxHop OR routing.entry.network=NIL THEN
    BEGIN  -- don't know where to send it
    IF doStats THEN StatIncr[statPupsSentNowhere];
    IF routing=NIL OR routing.entry.network=NIL THEN
      BEGIN -- Send him an error Pup, but beware of RequeueProc-- END;
    PutOnGlobalDoneQueue[b];
    RETURN;  -- wait to see if there is an alternate path
    END;
  network ← routing.entry.network;
  b.source.host ← [network.hostNumber];  -- tell the truth
  b.source.net ← [network.netNumber];
  IF doShow AND outThings.showOut THEN outThings.outShower[outgoingPup,b];
  h ← routing.entry.route;
  IF h=0 THEN h ← b.dest.host;  -- we are on the same net
  IF doDebug AND ((b.pupLength+1)/2)>dataWordsPerPup+wordsPerPupHeader THEN
    DriverDefs.Glitch[IllegalPupLength];
  SetPupChecksum[b];
  network.encapsulatePup[b,h];
  network.sendBuffer[b];
  END;

PupRouterBroadcastThis: PUBLIC PROCEDURE [b: PupBuffer] =
  BEGIN
  IF doStats THEN StatIncr[statPupBroadcast];
  IF doDebug THEN BeSurePupIsOn[];
  IF doDebug AND b.seal#DriverTypes.bufferSeal THEN Glitch[BufferSealBroken];
  IF DriverDefs.firstNetwork=NIL THEN BEGIN PutOnGlobalDoneQueue[b]; RETURN; END;
  b.allNets ← TRUE;  -- this is where it gets turned on
  b.network ← DriverDefs.firstNetwork;
  b.dest.host ← allHosts;
  PupBroadcaster[b];
  END;

-- b.network is already set up to the desired network
PupBroadcaster: PUBLIC PROCEDURE [b: PupBuffer] =
  BEGIN
  network: Network ← b.network;
  IF b.bypassZeroNet AND network.netNumber=0 THEN
    BEGIN PutOnGlobalDoneQueue[b]; RETURN; END;  -- goes (slowly) around in circles
  b.pupTransportControl ← 0;
  b.dest.net ← b.source.net ← [network.netNumber];
  b.source.host ← [network.hostNumber];
  network.encapsulatePup[b,PupTypes.allHosts];
  SetPupChecksum[b];
  network.sendBuffer[b];
  END;

-- Pup is assumed to have arrived from somewhere else.  We fixup defaults here.
SwapPupSourceAndDest: PUBLIC PROCEDURE [b: PupBuffer] =
  BEGIN
  network: DriverDefs.Network ← b.network;
  temp: PupAddress;
  IF doDebug AND b.seal#DriverTypes.bufferSeal THEN Glitch[BufferSealBroken];
  temp ← b.source;
  b.source ← b.dest;
  b.dest ← temp;
  -- in case we are returning a broadcast packet
  IF b.dest.net=0 THEN b.dest.net ← [network.netNumber];
  IF b.source.net=0 THEN b.source.net ← [network.netNumber];
  IF b.source.host=PupTypes.allHosts THEN b.source.host ← [network.hostNumber];
  END;

ReturnPup: PUBLIC PROCEDURE [b: PupBuffer, type: PupTypes.PupType, bytes: CARDINAL] =
  BEGIN
  SwapPupSourceAndDest[b];
  SendPup[b,type,bytes];
  END;


wordsPerPupHeader: CARDINAL = 11;
bytesPerPupHeader: CARDINAL = wordsPerPupHeader*2;

GetPupContentsBytes: PUBLIC PROCEDURE [b: PupBuffer] RETURNS [bytes: CARDINAL] =
  BEGIN
  RETURN[b.pupLength-bytesPerPupHeader];
  END;

SetPupContentsBytes: PUBLIC PROCEDURE [b: PupBuffer, bytes: CARDINAL] =
  BEGIN
  IF bytes>dataWordsPerPup*2 THEN Glitch[IllegalPupLength];
  b.pupLength ← bytes+bytesPerPupHeader;
  END;

SetPupContentsWords: PUBLIC PROCEDURE [b: PupBuffer, words: CARDINAL] =
  BEGIN
  IF words>dataWordsPerPup THEN Glitch[IllegalPupLength];
  b.pupLength ← words*2+bytesPerPupHeader;
  END;

SendPup: PUBLIC PROCEDURE [b: PupBuffer, type: PupType, bytes: CARDINAL] =
  BEGIN
  b.pupType ← type;
  SetPupContentsBytes[b,bytes];
  PupRouterSendThis[b];
  END;

InsertRoutingCacheEntry: PUBLIC PROCEDURE [net: PupNetID]
  RETURNS [rte: POINTER TO RoutingCacheEntry] =
  BEGIN
  rte ← GetRoutingCacheEntry[net: net, ifMissing: return, promote: TRUE];
  IF rte=NIL THEN
    DO  -- Repeatedly grab tail cache entry and put it at the head until we
        -- find one that is NOT for a directly-connected network.
      rte ← GetRoutingCacheEntry[
        net: [routingCacheTail.net], ifMissing: return, promote: TRUE];
      IF rte.net=emptyCacheEntry OR rte.entry.hop#0 THEN EXIT;
      ENDLOOP;
  rte.net ← net;
  rte.entry ← [hop: maxHop+1, time: 90, route: [0], network: NIL];
  END;

DeleteRoutingCacheEntry: PUBLIC PROCEDURE [net: PupNetID] =
  BEGIN
  rte: POINTER TO RoutingCacheEntry ← GetRoutingCacheEntry[
    net: net, ifMissing: return, promote: TRUE];
  IF rte#NIL THEN
    BEGIN  -- make entry empty and put it at the tail of the queue.
    -- We know it is at the head now, because GetRoutingCacheEntry put it there!
    rte.net ← emptyCacheEntry;
    rte.entry.network ← NIL;
    rte.entry.hop ← maxHop+1;
    routingCacheHead ← rte.next;
    rte.next ← NIL;
    routingCacheTail.next ← rte;
    routingCacheTail ← rte;
    END;
  END;

Prober: PUBLIC PROCEDURE =
  BEGIN
  -- This peculiar organization is to avoid calling GetFreePupBuffer from
  -- within the monitor, which could cause a deadlock.
  AwaitRequest: ENTRY PROCEDURE RETURNS [action: {probe, quit}] =
    BEGIN
    WHILE pupRouterIsActive DO
      IF probesLeftToDo#0 THEN WAIT probeRetransmitTimeout ELSE WAIT pleaseProbe;
      IF probesLeftToDo#0 THEN
        {probesLeftToDo ← probesLeftToDo-1; RETURN [probe]};
      ENDLOOP;
    RETURN [quit];
    END;
  DO
    SELECT AwaitRequest[] FROM
      probe =>
        BEGIN
        b: BufferDefs.PupBuffer ← PupDefs.GetFreePupBuffer[];
        b.bypassZeroNet ← FALSE;
        b.pupType ← gatewayRequest;
        b.pupID ← [0, probesLeftToDo];
        b.dest.socket ← b.source.socket ← PupTypes.gatewaySoc;
        PupDefs.SetPupContentsWords[b, 0];
        PupDefs.PupRouterBroadcastThis[b];
        END;
      quit => RETURN;
      ENDCASE;
    ENDLOOP;
  END;


END.  -- PupRouterOut