-- File: RouterImpl.mesa - last edit:
-- AOF                 16-Dec-87 19:53:41
-- MI                   1-Aug-86 15:22:18
-- SMA                 23-May-86 10:25:42
-- Copyright (C) 1984, 1985, 1986, 1987 by Xerox Corporation. All rights reserved. 
--Function: The implementation module for the Pilot NS Router.

DIRECTORY
  Buffer USING [ReturnBuffer],
  ByteBlt USING [ByteBlt],
  Checksums USING [SetChecksum, TestChecksum],
  CommunicationInternal USING [PacketStreamsGo],
  CommHeap USING [zone],
  CommFlags USING [doStats, doStorms],
  Driver USING [
    ChangeNumberOfInputBuffers, GetDeviceChain, NetworkNonExistent,
    Device, nilDevice, PutOnGlobalDoneQueue, GetNthDevice],
  EchoServer USING [CreateServer, DeleteServer],
  Environment USING [bytesPerWord],
  HostNumbers USING [IsMulticastID],
  IEEE8023 USING [EncapObject, Encapsulation],
  Inline USING [LongCOPY, LowHalf],
  NetworkStream USING [],
  NSBuffer USING [
    Body, CreditReceiveBuffer, GetBuffer, Buffer, Enqueue, ReturnBuffer],
  NSConstants USING [
    <<maxWellKnownSocket, >>routingInformationSocket, errorSocket],
  NSConstantsExtras USING [maxWellKnownSocket],
  NSTypes USING [
    maxIDPBytesPerPacket, bytesPerIDPHeader, ErrorCode, maxIDPDataBytes],
  Process USING [Pause, Yield],
  Protocol1 USING [
    MatrixRecord, FamilyUnit, DecapsulatorProc, EncapsulatorProc,
    RegisterFamily, EvictFamily, EncapsulateAndTransmit, Family,
    GetContext, Action, AddFamilyMember, RemoveFamilyMember,
    SetMaximumBufferSize],
  Router USING [PhysicalMedium, RoutersFunction],
  RouterInternal USING [defaultRth],
  RoutingTable USING [Handle, Object, NetworkContext, ContextObject],
  Socket USING [SetPacketBytes, SwapSourceAndDestination],
  SocketInternal USING [SocketHandle, SocketTable],
  SpecialSystem USING [ 
    GetProcessorID, NetworkNumber, HostNumber, SocketNumber, ProcessorID],
  Stats USING [StatIncr],
  System USING [
    broadcastHostNumber, GetClockPulses, NetworkAddress, NetworkNumber,
    nullNetworkNumber];

RouterImpl: MONITOR LOCKS socketRouterLock
  IMPORTS
    Buffer, NSBuffer, ByteBlt, Checksums, CommHeap, Driver, Inline, HostNumbers,
    Process, Protocol1, RouterInternal, Socket, SpecialSystem, Stats, System,
    CommunicationInternal, EchoServer
  EXPORTS
    Buffer, CommunicationInternal, NetworkStream, RoutingTable, Router,
    RouterInternal, Socket, SocketInternal, System =
  BEGIN

  --EXPORTed TYPEs
  Device: PUBLIC <<Buffer>> TYPE = Driver.Device;
  NetworkNumber: PUBLIC <<System>> TYPE = SpecialSystem.NetworkNumber;
  HostNumber: PUBLIC <<System>> TYPE = SpecialSystem.HostNumber;
  ProcessorID: PUBLIC TYPE = SpecialSystem.ProcessorID;
  SocketNumber: PUBLIC <<System>> TYPE = SpecialSystem.SocketNumber;
  ChannelHandle: PUBLIC TYPE = SocketInternal.SocketHandle;
  
  --Router.--NetworkNonExistent: PUBLIC ERROR = CODE;
  --Router.--NoTableEntryForNet: PUBLIC ERROR = CODE;
  --Socket.--DuplicateSocket: PUBLIC ERROR = CODE;

  bpw: NATURAL = Environment.bytesPerWord;

  socketRouterLock: PUBLIC <<RouterInternal>> MONITORLOCK;
  socketTable: PUBLIC <<SocketInternal>> SocketInternal.SocketTable;

  spareSocketID: SocketNumber;
  useCount: INTEGER ← 0;  --to detect multiple clients

  lastWKS: SocketNumber = NSConstantsExtras.maxWellKnownSocket;
  
  myProcID: ProcessorID;  --processID of this machine

  nsProtocolFamily: Protocol1.FamilyUnit ← [
    name: ns, status: dead, spy: NIL, state: NIL,
    maxBufferSize: NSTypes.maxIDPBytesPerPacket, 
    receive: LOOPHOLE[ReceivePacket], broadcast: LOOPHOLE[Broadcast],
    stateChanged: NSStateChange];

  rto: RoutingTable.Object;
  checkIt: PUBLIC <<RouterInternal>> BOOLEAN ← TRUE;
  routersFunction: PUBLIC <<RouterInternal>> Router.RoutersFunction;
  startEnumeration: PUBLIC <<Router>> NetworkNumber;  --set to start enumeration
  endEnumeration: PUBLIC <<Router>> NetworkNumber;  --implies end of enumeration

  --PARAMETERS FOR KILLING PACKETS FOR DEBUGGING AND
  --SWITCHES THAT DON'T CHANGE DURING EXECUTION UNLESS FOR DIAGNOSTICS
  debugging: RECORD[
    bolt: INTEGER ← 10, lightning: INTEGER ← 30,
    stormy, driverLoopback: BOOLEAN ← FALSE,
    allowedToSendErrorPackets: BOOLEAN ← TRUE] ← [];

  PutOnGlobalDoneQueue: PROC[b: NSBuffer.Buffer] =
    LOOPHOLE[Driver.PutOnGlobalDoneQueue];
  <<
  The drivers may have to be told what its network numbers are.
  This is certainly true if this is the first machine running on a network.
  Physical order is the location of the network on the network device chain.
  >>
  SetNetworkID: PUBLIC ENTRY PROC[
    physicalOrder: CARDINAL,
    medium: Router.PhysicalMedium,
    newNetID: System.NetworkNumber]
    RETURNS [oldNetID: System.NetworkNumber] =
    BEGIN
    ENABLE UNWIND => NULL;
    context: RoutingTable.NetworkContext;
    network: Driver.Device ← Driver.GetNthDevice[
      physicalOrder, SELECT medium FROM
        ethernet => ethernet, ethernetOne => ethernetOne, ENDCASE => phonenet !
	Driver.NetworkNonExistent => GOTO noNet];
    context ← Protocol1.GetContext[network, ns];
    oldNetID ← context.netNumber; context.netNumber ← newNetID;
    rto.stateChanged[context];  --see if routing table cares
    EXITS noNet => ERROR NetworkNonExistent;
    END;

  GetNetworkID: PUBLIC ENTRY PROC[
    physicalOrder: CARDINAL, medium: Router.PhysicalMedium]
    RETURNS [System.NetworkNumber] =
    BEGIN
    ENABLE UNWIND => NULL;
    context: RoutingTable.NetworkContext;
    network: Driver.Device ← Driver.GetNthDevice[
      physicalOrder, SELECT medium FROM
        ethernet => ethernet, ethernetOne => ethernetOne, ENDCASE => phonenet];
    context ← Protocol1.GetContext[network, ns];
    RETURN[context.netNumber];
    END;

  <<
  The following two routines know something of the level-0 machinery.
  Consequently, they really shouldn't be housed in this module, but for
  the sake of saving a module (aka global frame), they are.  The question
  then arises, "Is it better to look into level-1 from level-0 or into
  level-0 from level-1?"  If I decide what the answer is, I'll include it
  here sometime.  But for now....
  >>

  DecapsulateEthernet: Protocol1.DecapsulatorProc =
    BEGIN
    e: IEEE8023.Encapsulation = LOOPHOLE[b.linkLayer.blockPointer];
    IF HostNumbers.IsMulticastID[LOOPHOLE[@e.ethernetSource]] THEN
      BEGIN
      IF CommFlags.doStats THEN Stats.StatIncr[statPacketsDiscarded];
      RETURN[orphan];
      END;
    IF e.ethernetType = ns THEN
      BEGIN
      bytes: CARDINAL = b.fo.driver.length;
      body: NSBuffer.Body = LOOPHOLE[e + SIZE[ethernet IEEE8023.EncapObject]];
      IF bytes < body.pktLength THEN RETURN[orphan];  --is packet valid?
      b.linkLayer.stopIndexPlusOne ← SIZE[ethernet IEEE8023.EncapObject] * bpw;
      b.highLayer ← [LOOPHOLE[body], 0, body.pktLength]; --Compute higher layer
      RETURN[ns];
      END
    ELSE RETURN[vagrant];
    END;  --Decapsulate
    

  EncapsulateEthernet: Protocol1.EncapsulatorProc =
    BEGIN
    e: IEEE8023.Encapsulation;
    body: NSBuffer.Body = LOOPHOLE[b.highLayer.blockPointer];
    e ← LOOPHOLE[body - SIZE[ethernet IEEE8023.EncapObject]];
    e↑ ← [
      ethernetDest: NARROW[immediate, LONG POINTER TO HostNumber]↑,
      ethernetSource: myProcID, var: ethernet[ethernetType: ns]];
    b.fo.driver.length ← body.pktLength +
      (SIZE[ethernet IEEE8023.EncapObject] * bpw);
    b.linkLayer ← [LOOPHOLE[e], 0, SIZE[ethernet IEEE8023.EncapObject] * bpw];
    END;  --Encapsulate

  NSStateChange: PROC[
    driver: Device, context: LONG POINTER, why: Protocol1.Action] =
    BEGIN
    SELECT why FROM
      add => IF rto.addNetwork # NIL THEN rto.addNetwork[context];
      remove => rto.removeNetwork[context];
      modify => rto.stateChanged[context];
      ENDCASE;
    END;  --StateChangedEthernet

  Register: PUBLIC PROC[h: RoutingTable.Handle ← NIL] =
    BEGIN
    ExchangeObjects: ENTRY PROC =
      BEGIN
      IF h # NIL THEN rto ← h↑  --copy in new object
      ELSE rto ← RouterInternal.defaultRth↑;  --default to old
      routersFunction ← rto.type;
      startEnumeration ← rto.startEnumeration;
      endEnumeration ← rto.endEnumeration;
      END;  --ExchangeObjects
    nsProtocolFamily.status ← dead;  --slow thing up a while
    Process.Pause[1];  --wait for traffic to clear
    IF rto.stop # NIL THEN rto.stop[];  --stop current
    ExchangeObjects[];  --reregister
    IF rto.start # NIL THEN rto.start[];  --start new
    nsProtocolFamily.status ← alive;  --and let the good times role
    END;  --Register

  EnumerateRoutingTable: PUBLIC PROC[previous: NetworkNumber, delay: CARDINAL]
    RETURNS [net: NetworkNumber] = {net ← rto.enumerate[previous, delay]}; 

  FillRoutingTable: PUBLIC PROC[maxDelay: CARDINAL] =
    {rto.fillTable[maxDelay]};  --FillRoutingTable

  GetDelayToNet: PUBLIC PROC[net: NetworkNumber] RETURNS [delay: CARDINAL] =
    {delay ← rto.getDelay[net]};

  GetRouterFunction: PUBLIC PROC [] RETURNS [Router.RoutersFunction] =
    {RETURN[rto.type]};  --GetRouterFunction

  FindDestinationRelativeNetID: PUBLIC PROC[net: NetworkNumber]
    RETURNS [NetworkNumber] =
    {RETURN[rto.findNetwork[net]]};  --FindDestinationRelativeNetID

  FlushCacheEntry: PUBLIC PROC[net: System.NetworkNumber] =
    {rto.flushCache[net]};

  <<
  This procedure assigns a temporary socket number in an System.NetworkAddress.
  Some day the active socket numbers in use will be kept around, so that on wrap
  around an unused number will be assigned.  When the system element is
  connected to more than one network, this procedure must return a list of
  System.NetworkAddress.
  >>
  AssignAddress, AssignNetworkAddress: PUBLIC ENTRY PROC
    RETURNS [localAddr: System.NetworkAddress] =
    BEGIN
    ReassignUniqueSocket[];  --get the unique socket
    localAddr ← [
      net: rto.findNetwork[System.nullNetworkNumber],
      host: myProcID, socket: LOOPHOLE[spareSocketID]];
    END;  --AssignAddress

  <<
  This procedure is just like the previous one except that the network number
  is relative to the destination network.  That is, we pick that one of our
  locally connected networks that is the best way to get to destNet, with
  the hope that it is the best way to get from destNet to us.
  >>
  AssignDestinationRelativeAddress: PUBLIC ENTRY PROC[
    destNet: NetworkNumber] RETURNS [localAddr: System.NetworkAddress] =
    BEGIN
    ReassignUniqueSocket[];  --get the unique socket
    localAddr ← [
      net: rto.findNetwork[destNet], host: myProcID,
      socket: LOOPHOLE[spareSocketID]];
    END;  --AssignDestinationRelativeAddress

  ReassignUniqueSocket: INTERNAL PROC =
    BEGIN
    --RETURNs from inner loop--DO
      IF (spareSocketID ← [spareSocketID + 1]) = SocketNumber[0] THEN
	spareSocketID ← [LOOPHOLE[lastWKS, CARDINAL] + 1];

      FOR cH: ChannelHandle ← socketTable.first, cH.next UNTIL cH = NIL DO
	socket: SocketNumber = cH.address.socket;
	IF (spareSocketID = socket) THEN EXIT;
	REPEAT FINISHED => RETURN;  --that socket will work
	ENDLOOP;

      ENDLOOP;
    END;  --AssignUniqueSocket

  AddSocket: PUBLIC ENTRY PROC[sH: ChannelHandle] =
    BEGIN
    FOR cH: ChannelHandle ← socketTable.first, cH.next UNTIL cH = NIL DO
      IF (sH.address.socket = cH.address.socket) THEN
	RETURN WITH ERROR DuplicateSocket;
      ENDLOOP;

    --add new socket to the head of the table
    sH.next ← socketTable.first;
    socketTable.first ← sH;
    socketTable.length ← socketTable.length + 1;

    --Is this the first "real" client?  Should we tell the driver to open up?
    SELECT TRUE FROM
      (sH.pool.seal = listenPool) => NULL;  --nope, he's a fake
      (sH.pool.receive > 0) => Driver.ChangeNumberOfInputBuffers[TRUE];
      ENDCASE;
    END;  --AddSocket

  RemoveSocket: PUBLIC ENTRY PROC[sH: ChannelHandle] =
    BEGIN
    previousSH: ChannelHandle;
    IF socketTable.first = sH THEN socketTable.first ← sH.next
    ELSE
      BEGIN
      previousSH ← socketTable.first;
      UNTIL previousSH = NIL DO
        IF previousSH.next = sH THEN {previousSH.next ← sH.next; EXIT};
        previousSH ← previousSH.next;
        ENDLOOP;
      END;
    socketTable.length ← socketTable.length - 1;

    --Is it time to "close" the drivers down again?
    SELECT TRUE FROM
      (sH.pool.seal = listenPool) => NULL;  --that's a fake
      (sH.pool.receive > 0) => Driver.ChangeNumberOfInputBuffers[FALSE];
      ENDCASE;
    END;  --RemoveSocket

  --DEBUGGING HOOKS

  SetNSStormy: PUBLIC <<RouterInternal>> PROC[new: BOOLEAN] =
    {debugging.stormy ← new};
  SetNSDriverLoopback: PUBLIC <<RouterInternal>> PROC[new: BOOLEAN] =
    {debugging.driverLoopback ← new};

  --UTILITY ROUTINES
  SetNSCheckit: PUBLIC <<RouterInternal>> PROC[new: BOOLEAN] = {checkIt ← new};
  FindMyHostID: PUBLIC PROC RETURNS [HostNumber] = {RETURN[myProcID]};

  <<
  This procedure transmits a packet over a locally connected network.
  The procedure assumes that all fields of the buffer have been filled
  in appropriately, except the encapsulation and buffer length which depend
  on the network.  If the packet is destined for a local socket and we do
  NOT want to do loopback at the driver or at the network, then it gets
  looped back here.
  Packets are no longer copied into system buffers. The caller owns the
  buffer.  The buffer is asynchronously sent and returned to the  caller
  process by the dispatcher using b.requeueProcedure.
  
  Broadcast packets are NOT delivered to the source socket here, they are 
  delivered to the correct driver.  The driver then has the responsibility to 
  get the packet onto the incoming packet queue.  How that is done is driver
  and hardware dependent.  E.g., the Dolphin can hear Ethernet packets it has
  sent, so the dolphin just broadcasts the pkt, and hears it at the same time.
  The DLion can't do that, so it's Ethernet driver explicitly copies broadcast
  pkts being sent, and puts them on the input queue.
  >>

  SendPacket: PUBLIC PROC[b: NSBuffer.Buffer] =
    BEGIN
    body: NSBuffer.Body = b.ns;
    body.transportControl ← [trace: FALSE, filler: 0, hopCount: 0];
    SELECT TRUE FROM
      --debugging mode
      (CommFlags.doStorms AND debugging.stormy AND PacketHit[]) =>
        {PutOnGlobalDoneQueue[b]; Process.Yield[]};
      --broadcasts and local w/loopback included here
      ((body.destination.host # myProcID) OR debugging.driverLoopback) =>
        BEGIN
	--If the destination net is inaccessible then we will have suffered
	--the overhead of computing a checksum, but we don't expect inaccessible
	--nets that often.  Set the checksum if appropriate
	IF checkIt THEN Checksums.SetChecksum[body] ELSE body.checksum ← 177777B;
	[] ← rto.transmit[b];
	END;
      --(body.destination.host = myProcID) =>
      --packet is for a local socket; broadcast packets are not delivered locally
      (~DeliveredToLocalSocket[b, TRUE --copy this buffer--]) => 
        BEGIN
        b.fo.network ← Driver.nilDevice;
        SendErrorPacket[b, noSocket, 0];  --back to local socket
        IF CommFlags.doStats THEN Stats.StatIncr[statJunkNSForUsNoLocalSocket];
        Process.Yield[];
        END;
      --we requeue here because we shorted the drivers and dispatcher.
      ENDCASE => {PutOnGlobalDoneQueue[b]; Process.Yield[]};
    END;  --SendPacket

  <<
  This procedure is called by the Dispatcher when it sees a valid ns packet in
  a system buffer.  The procedure checks the checksum field and then routes
  the packet to a local socket.  If this is an internetwork router, then
  the packets are forwarded over a suitable network.  We now own this packet.
  >>
  ReceivePacket: PUBLIC PROC[b: NSBuffer.Buffer] =
    BEGIN
    SpecialCase: PROC RETURNS[BOOLEAN] = INLINE
      BEGIN
      OPEN c: NARROW[b.fo.context, RoutingTable.NetworkContext];
      RETURN[
        (c.netNumber = body.destination.net)
	OR (body.destination.net = System.nullNetworkNumber)
	OR (body.source.socket = NSConstants.routingInformationSocket)];
      END;  --SpecialCase
	
    body: NSBuffer.Body = b.ns;
    destHost: HostNumber ← body.destination.host;

    <<
    If to my host | broadcast to my net | boadcast to unknown net id
    | broadcast routing info packet (from socket #1). Insures we see packets
    when we don't yet know our net number (we think its zero).

    We use broadcast packets if a) the pkt's dest. net is the net number of
    the driver that the pkt came in on, or b) the pkt's dest. net is
    System.nullNetworkNumber
    
    If we use this broadcast packet, we do NOT forward it (we may have 
    previously forwarded it, and then the driver "heard" it, though)
    >> 
    SELECT TRUE FROM
      --is packet at least minimum length
      (body.pktLength < NSTypes.bytesPerIDPHeader) =>
        BEGIN
        IF CommFlags.doStats THEN Stats.StatIncr[statNSDiscarded];
        NSBuffer.ReturnBuffer[b];  --done with this packet
	END;
      --if testing checksums, do they check
      (checkIt AND ~Checksums.TestChecksum[body]) =>
        BEGIN
	IF CommFlags.doStats THEN Stats.StatIncr[statReceivedBadNSChecksum];
	NSBuffer.ReturnBuffer[b];  --done with this packet
	END;
      --debugging mode only - wipes out reception
      (CommFlags.doStorms AND debugging.stormy AND PacketHit[]) =>
        NSBuffer.ReturnBuffer[b];  --done with this packet
      --packet directed to me
      (destHost = myProcID) =>
        BEGIN
	--do I have a socket to deliver it to
	IF ~DeliveredToLocalSocket[b, FALSE] THEN
	  BEGIN
	  SendErrorPacket[b, noSocket, 0];
	  IF CommFlags.doStats THEN Stats.StatIncr[statJunkNSForUsNoLocalSocket];
	  END;
	END;
      --is packet a broadcast to some well-known (even not so well known) socket
      (HostNumbers.IsMulticastID[@destHost] AND SpecialCase[]) =>
        BEGIN
	IF ~DeliveredToLocalSocket[b, FALSE] THEN
	  BEGIN
	  IF CommFlags.doStats THEN Stats.StatIncr[statJunkBroadcastNS];
	  NSBuffer.ReturnBuffer[b];
	  END;
	END;
      (rto.type = interNetworkRouting) =>
        rto.forward[b];  --dispatcher returns b to system pool
      ENDCASE =>
        BEGIN
	IF CommFlags.doStats THEN Stats.StatIncr[statNSDiscarded];
	NSBuffer.ReturnBuffer[b];
	END;
    
    END;  --ReceivePacket

  <<
  This procedure attempts to hit a packet with a bolt of lightening, and
  if it succeeds, then it returns true else false.  The caller dispenses
  with the buffer.
  >>
  PacketHit: --ENTRY-- PROC RETURNS [BOOLEAN] =
    BEGIN
    SELECT TRUE FROM
      (~CommFlags.doStorms) => NULL;
      ((debugging.lightning ← debugging.lightning + 1) > debugging.bolt) =>
        BEGIN
        IF debugging.bolt > 100 THEN
          BEGIN
          randLong: LONG CARDINAL ← System.GetClockPulses[];
          rand: CARDINAL ← Inline.LowHalf[randLong];
          debugging.lightning ← -INTEGER[rand MOD 20B];
          debugging.bolt ← 10;
	  IF CommFlags.doStats THEN Stats.StatIncr[statZappedP];
          END;
	RETURN[TRUE];
	END;
      (debugging.lightning < 0) =>
        BEGIN
	debugging.lightning ← 0; debugging.bolt ← debugging.bolt + 1;
	IF CommFlags.doStats THEN Stats.StatIncr[statZappedP];
	RETURN[TRUE];
	END;
      ENDCASE;
    RETURN[FALSE];
    END;  --PacketHit

  <<
  This procedure finds a local socket object to deliver the packet to, and if
  it succeeds, then it returns true else false.  Caller  retains ownership
  of buffer if useCopy is true or return is false;  caller loses buffer
  ownership if NOT useCopy and return is true.
  >>

  DeliveredToLocalSocket: ENTRY PROC[b: NSBuffer.Buffer, useCopy: BOOLEAN]
    RETURNS [BOOLEAN] =
    BEGIN
    OPEN c: NARROW[b.fo.context, RoutingTable.NetworkContext];
    ENABLE UNWIND => NULL;
    sH, prevSH: ChannelHandle;
    body: NSBuffer.Body = b.ns;

    <<
    In case the sender of this buffer doesn't know his own network number,
    try to help him along by reaching back into the input device's network
    object and getting the network from there.  It is also possible that we
    don't know any more than the sender, and in fact, we may be the sender.
    >>
    SELECT TRUE FROM
      (body.source.net # System.nullNetworkNumber) => NULL;  --already set
      (body.transportControl.hopCount # 0) => NULL;  --too late to help
      (LOOPHOLE[b.fo.network, Device] = Driver.nilDevice) => NULL;  --we're sender
      ENDCASE => body.source.net ← c.netNumber;

    --find the correct socket for this packet
    FOR sH ← (prevSH ← socketTable.first), sH.next UNTIL sH = NIL DO
      SELECT TRUE FROM
        (sH.address.socket # body.destination.socket) => {prevSH ← sH; LOOP};
	(useCopy) => EnqueueCopyOfNewInput[sH, body];
	(NSBuffer.CreditReceiveBuffer[sH.pool, b]) =>
	  BEGIN
	  b.fo.status ← goodCompletion;
	  WITH cH: sH SELECT FROM
	    normal =>
	      BEGIN
	      BROADCAST cH.newUserInput;  --wake up the client
	      NSBuffer.Enqueue[@cH.queue, b];  --make sure he has the buffer
	      END;
	    listen=>
	      BEGIN
	      b.requeueData ← sH;  --copy the socket handle into buffer
	      NOTIFY cH.arrival↑;  --wake up the central process
	      NSBuffer.Enqueue[cH.queue, b];  --and make sure he has the buffer
	      END;
	    ENDCASE;
	  END;
	ENDCASE =>
	  BEGIN
	  IF CommFlags.doStats THEN Stats.StatIncr[statNSInputQueueOverflow];
	  NSBuffer.ReturnBuffer[b];
	  END;
        --do some dynamic socket ordering before returning;
	--rearange the list only if sH was not in the first
	--two entries of the list.
        IF prevSH # socketTable.first THEN
          BEGIN
          prevSH.next ← sH.next;  --this removes sH from the list
          sH.next ← socketTable.first;
          socketTable.first ← sH;  --this puts sH at head of list
          END;
        RETURN[TRUE];
      ENDLOOP;
    RETURN[FALSE];
    END;  --DeliveredToLocalSocket

  <<
  This procedure enqueues a new input packet at the socket object.
  The incoming buffer is from a local socket.
  The caller of this routine retains ownership of this buffer.  Since there
  is a possibility of using more buffers than anticipated, we check to see
  if there is a spare input buffer before trying to get a new buffer.
  >>
  EnqueueCopyOfNewInput: INTERNAL PROC[sH: ChannelHandle, body: NSBuffer.Body] =
    BEGIN
    getBuffer: NSBuffer.Buffer;
    IF sH.pool.receiveInUse < sH.pool.receive THEN
      BEGIN
      getBuffer ← NSBuffer.GetBuffer[
        sH.pool, receive, FALSE, nsProtocolFamily.maxBufferSize];
      IF getBuffer = NIL THEN RETURN;  --can't do it this time
      getBuffer.fo.status ← goodCompletion;
      Inline.LongCOPY[
        from: @body.checksum, to: @getBuffer.ns.checksum,
        nwords: (body.pktLength + 1) / bpw];
      WITH cH: sH SELECT FROM
	normal =>
	  BEGIN
	  BROADCAST cH.newUserInput;  --wake up the client
	  NSBuffer.Enqueue[@cH.queue, getBuffer];  --make sure he has the buffer
	  END;
	listen=>
	  BEGIN
	  getBuffer.requeueData ← sH;  --copy the socket handle into buffer
	  NOTIFY cH.arrival↑;  --wake up the central process
	  NSBuffer.Enqueue[cH.queue, getBuffer];  --and make sure he has buffer
	  END;
	ENDCASE;
      END
    ELSE IF CommFlags.doStats THEN Stats.StatIncr[statNSInputQueueOverflow];
    END;  --EnqueueCopyOfNewInput

  --This procedure causes a broadcast packet to be sent over all networks.
  BroadcastThisPacket: PUBLIC <<RouterInternal>> PROC[b: NSBuffer.Buffer] =
    BEGIN
    network: Device;
    body: NSBuffer.Body = b.ns;
    b.fo.allNets ← TRUE;  --this is where it gets turned on
    b.fo.network ← network ← Driver.GetDeviceChain[];
    IF network = NIL THEN
      BEGIN
      b.fo.status ← noRouteToNetwork;
      PutOnGlobalDoneQueue[b];
      IF CommFlags.doStats THEN Stats.StatIncr[statNSSentNowhere];
      END
    ELSE
      BEGIN
      b.fo.context ← Protocol1.GetContext[network, ns];
      Broadcast[b];
      IF CommFlags.doStats THEN Stats.StatIncr[statNSBroadcast];
      END;
    END;  --BroadcastThisPacket

  <<
  This procedure causes a broadcast packet to be sent out over the right network.
  This procedure is directly used by the Dispatcher when sending an allNets pkt
  on to the next network. Among others, (I suppose).
  >>
  Broadcast: PROC[b: NSBuffer.Buffer] =
    BEGIN
    OPEN context: NARROW[b.fo.context, RoutingTable.NetworkContext];
    body: NSBuffer.Body = b.ns;
    network: Device = b.fo.network;
    allHosts: HostNumber ← System.broadcastHostNumber;
    --NB: Putting this on the GlobalDoneQueue causes the first driver in chain
    --to be skipped, but progresses to the next in the chain.
    SELECT TRUE FROM
      (~network.alive) => PutOnGlobalDoneQueue[b];  --is the driver alive?
      (b.fo.context = NIL) => PutOnGlobalDoneQueue[b];  --does it support family?
      (b.fo.bypassZeroNet) AND (context.netNumber = System.nullNetworkNumber) =>
	PutOnGlobalDoneQueue[b];  --we're skipping null nets and it is
      ENDCASE =>
        BEGIN
	--assumes caller has set both destination and sourse socket fields
	b.fo.status ← goodCompletion;  --rash assumption
	body.transportControl ← [FALSE, 0, 0];  --just starting 
	body.destination.host ← allHosts; body.source.host ← myProcID;
	body.destination.net ← body.source.net ← context.netNumber;
	IF ~checkIt THEN body.checksum ← 177777B ELSE Checksums.SetChecksum[body];
	Protocol1.EncapsulateAndTransmit[LOOPHOLE[b], @allHosts];
	IF CommFlags.doStats THEN Stats.StatIncr[statBroadcastRequest];
	END;
    END;  --SendBroadcastPacketToCorrectNet

  --This procedure generates and sends an error packet.
  SendErrorPacket: PUBLIC PROC[
    offendingPkt: NSBuffer.Buffer,
    errCode: NSTypes.ErrorCode, errParm: CARDINAL] =
    BEGIN
    body: NSBuffer.Body = offendingPkt.ns;

    <<
    "aborted" is a status that ULP should RETRY.  Other status is probably
    treated with a more fatal attitude.  This means that we won't be able
    to slow down SPP's retransmitter in cases when the local machine is
    congested.  Do we care?
    >>
    offendingPkt.fo.status ← SELECT errCode FROM
      noSocket => invalidDestAddr,
      unspecified => aborted,
      badChecksum => aborted,
      inconsistent => aborted,
      congestionWarning => aborted,
      congestionDiscard => aborted,
      unspecifiedInRoute => aborted,
      noError => aborted,
      resourceLimits => rejected,
      listenerReject => rejected,
      tooBig => rejected,
      connectionLimit => rejected,
      invalidPacketType => protocolViolation,
      protocolViolation => protocolViolation,
      cantGetThere => noRouteToNetwork,
      excessHops => noRouteToNetwork,
      ENDCASE => aborted;  --least fatal of the bunch

    <<
    offendingPkt.requeueProcedure may be == Buffer.ReturnBuffer or
    it may really be a client requeue procedure; we'll never know.
    >>
    SELECT TRUE FROM
      --don't send errors back to multicast
      (HostNumbers.IsMulticastID[@body.destination.host]) =>
        PutOnGlobalDoneQueue[offendingPkt];
      --don't error errors
      (body.packetType = error) => PutOnGlobalDoneQueue[offendingPkt];
      --if there is a client requeue, then it's a local packet
      (offendingPkt.requeueProcedure # LOOPHOLE[Buffer.ReturnBuffer]) =>
        PutOnGlobalDoneQueue[offendingPkt];
      --might be inhibited by clients
      (debugging.allowedToSendErrorPackets) =>  --can we send errors now?
	BEGIN
	<<
	This only adheres the the protocol externally.  Internally, we just
	send the errorType and errorParameter, none of the offending packet.
	This gets us around the problem with rippling the error packet into
	itself when the packet may be a short buffer.
	>>
	moved: CARDINAL ← IF body.source.host = myProcID THEN 0
	ELSE ByteBlt.ByteBlt[
	  [LOOPHOLE[@body.errorBody], 0, NSTypes.maxIDPDataBytes - 4],
	  [LOOPHOLE[body], 0, body.pktLength], move];
	Socket.SetPacketBytes[offendingPkt, moved + 4];
	Socket.SwapSourceAndDestination[offendingPkt];

	--IF not from socket, then from router.  Any MLs here?
	IF errCode ~IN[listenerReject..protocolViolation] THEN
	  body.source ← [
	    net: rto.findNetwork[body.destination.net],
	    host: myProcID, socket: NSConstants.errorSocket];

	body.packetType ← error;
	body.errorType ← errCode;
	body.errorParameter ← errParm;
	SendPacket[offendingPkt];
	IF CommFlags.doStats THEN Stats.StatIncr[statNSErrorPacketsSent];
	END;
      ENDCASE => PutOnGlobalDoneQueue[offendingPkt];  --can't send errors now
    END;  --SendErrorPacket

  <<
  This procedure starts XNS.  The Communication software is written
  with the idea of eventually turning the code on and off during execution,
  and so we should get the latest values of netID and hostID when being
  turned back on.
  >>

  NSPackageMake: PUBLIC PROC[] RETURNS[Protocol1.Family] =
    BEGIN
    AlreadyStarted: ENTRY PROC[] RETURNS[BOOLEAN] = INLINE
      {RETURN[(useCount ← useCount + 1) > 1]};
    IF ~AlreadyStarted[] THEN
      BEGIN
      NSRouterOn[];  --XNS protocol support
      CommunicationInternal.PacketStreamsGo[];  --miscellaneous
      EchoServer.CreateServer[];  --default echo server
      END;
    RETURN[@nsProtocolFamily];
    END;

  NSPackageDestroy: PUBLIC PROC =
    BEGIN
    Users: ENTRY PROC RETURNS[BOOLEAN] = INLINE
      {RETURN[(useCount = 0) OR ((useCount ← useCount - 1) > 0)]}; 
    IF Users[] THEN RETURN;  --either inactive or too many users
    EchoServer.DeleteServer[];  --default echo server
    NSRouterOff[];  --shut the router off
    END;

  NSRouterOn: PUBLIC PROC =
    BEGIN
    driver: Device;
    matrix: Protocol1.MatrixRecord;
    NSRouterActivate[];
    rto.stop ← NIL; rto.addNetwork ← NIL;  --don't call those just yet
    Protocol1.RegisterFamily[@nsProtocolFamily];  --make us known
    matrix ← [
      family: @nsProtocolFamily, context: NIL,
      encapsulator: EncapsulateEthernet,
      decapsulator: DecapsulateEthernet];
    FOR driver ← Driver.GetDeviceChain[], driver.next UNTIL driver = NIL DO
      IF driver.device # ethernet THEN LOOP;
      matrix.context ← CommHeap.zone.NEW[RoutingTable.ContextObject ← [
        netNumber: System.nullNetworkNumber, network: driver, stats: NIL]];
      Protocol1.AddFamilyMember[driver, @matrix];
      Protocol1.SetMaximumBufferSize[
        driver, @nsProtocolFamily, NSTypes.maxIDPBytesPerPacket];
      ENDLOOP;
    Register[];  --register default table impl
    END;  --NSRouterOn

  NSRouterActivate: ENTRY PROC = INLINE
    BEGIN
    myProcID ← SpecialSystem.GetProcessorID[];
    UNTIL LOOPHOLE[spareSocketID, CARDINAL] > LOOPHOLE[lastWKS, CARDINAL] DO
      spareSocketID ← Inline.LowHalf[System.GetClockPulses[]];
      ENDLOOP;
    socketTable ← [length: 0, first: NIL];
    END;  --NSRouterActivate

  NSRouterOff: PUBLIC PROC =
    BEGIN
    driver: Device;
    context: RoutingTable.NetworkContext;
    nsProtocolFamily.status ← dead;  --just pretending
    IF rto.stop # NIL THEN rto.stop[];  --convey plans to router
    FOR driver ← Driver.GetDeviceChain[], driver.next UNTIL driver = NIL DO
      IF driver.device # ethernet THEN LOOP;  --questionable logic
      context ← Protocol1.GetContext[driver, ns];  --find the context
      Protocol1.RemoveFamilyMember[driver, @nsProtocolFamily];
      CommHeap.zone.FREE[@context];  --free the dude up
      ENDLOOP;
    Protocol1.EvictFamily[ns];  --goin' south for the winter
    END;  --NSRouterOff
 
 --Send error packets in normal manner 
  EnableNSErrorPackets: PUBLIC <<RouterInternal>> PROC =
    {debugging.allowedToSendErrorPackets ← TRUE};
  
  --Suppress all error packets (probably used in debugging)
  DisableNSErrorPackets:PUBLIC <<RouterInternal>> PROC =
    {debugging.allowedToSendErrorPackets ← FALSE};

  --mainline code
  
  END.  --RouterImpl module.

LOG

17-May-84 10:05:24  AOF  Post Klamath
 5-Mar-85 17:57:17  AOF  Treat broadcast as special case of multicast
 4-Apr-85 14:22:34  AOF  Monitor deadlock trying to stop XNS
25-Apr-85  9:53:57  AOF  Remove LOOPHOLEs around .TransferStatus
 3-May-85 11:01:31  AOF  Error packet to use overLap ← move
23-Aug-85  8:12:35  AOF  Delete context object when shutting off router
 5-Nov-85  9:15:30  AOF  Removing more LOOPHOLEs
10-Dec-85 14:21:30  AOF  Restructuring listeners
18-Dec-85 12:17:18  AOF  Opening/closing driver's input queues
18-Apr-86 10:15:26  AOF  Condition PacketHit to with CommFlags.doStorms
22-May-86 18:07:33  SMA  No more Buffer dependencies and added NetworkNonExistent.
 6-Jun-86 17:42:15  AOF  More twiddles in encal/decap routines
17-Jun-86 19:39:41  AOF  Setting MaximumBufferSize for the family
 1-Aug-86 15:21:46  MI   Changed degree of driver.length from word to byte.
18-Aug-86 16:05:39  AOF  Changed some (most) NSBuffer.Buffer => LP TO .BufferBody
15-Oct-86 15:57:36  AOF  Better translation of error codes in SendErrorPacket
26-Oct-86 18:52:23  AOF  Tracking maxBufferSize when getting copies
16-Nov-86 15:54:10  AOF  More Error packet stuff
21-Dec-86 13:25:54  AOF  (Re)EXPORT the variable "checkIt"
17-Apr-87 18:36:58  AOF  Fix BroadcastToAllNets
 5-May-87 13:19:06  AOF  Move default control setting in SendPacket
18-Jul-87 10:52:37  AOF  Moving from EtherMAC to IEEE8023
28-Jul-87  9:13:16  AOF  AR#11506 (Can't * to null network - James L.)
30-Aug-87 10:50:45  AOF  Better instrumentation
30-Aug-87 12:15:15  AOF  Broadcast wasn't setting '* as destination
15-Oct-87 12:40:14  AOF  RemoveSocket was increasing buffers too