// Pup1b.bcpl -- Pup level 1
// This module contains 'main line' code.
// Copyright Xerox Corporation 1979, 1981, 1982

// Last modified September 27, 1982  12:40 PM by Taft

get "pup0.decl"
get "pup1.decl"

external
[
// outgoing procedures
PupLevel1; CompletePup; RoutePup; SocketNotFound
GetPBI; ReleasePBI; PupError; ExchangePorts
SetPupSPort; SetPupDPort; SetPupID

// incoming procedures
AppendStringToPup; BroadcastNextNet
Zero; MoveBlock; CallSwat; SysErr
Enqueue; Dequeue; Block; SetTimer; TimerHasExpired
PupChecksum; MultEq; HInsert; HLookup; LocateNet

// outgoing statics
ndbQ; pbiFreeQ; pbiIQ; pbiTQ; socketQ; gatewayIQ; dPSIB; numNets
pupZone; pupCtxQ; lenPBI; lenPup; maxPupDataBytes; pupLevel1Ctx

// incoming statics
pupRT
]

static  //level 1 statics
[
ndbQ; pbiFreeQ; pbiIQ; pbiTQ; socketQ; gatewayIQ; dPSIB; numNets
pupZone; pupCtxQ; lenPBI; lenPup; maxPupDataBytes; pupLevel1Ctx
]
compileif pupDebug then [ static [ lastFreeQTail; freeQTimer ]]


//----------------------------------------------------------------------------
let PupLevel1() be
//----------------------------------------------------------------------------
// process to distribute input Pups and release used output pbis.
[PupLevel1
let pbi = nil
   [
   compileif pupDebug then
      [  //for debugging, test for lockup due to free queue empty
      test pbiFreeQ!0 eq 0
         ifnot lastFreeQTail = 0
         ifso test pbiFreeQ!1 eq lastFreeQTail
            ifnot
               [
               SetTimer(lv freeQTimer, 2000)  //20 seconds
               lastFreeQTail = pbiFreeQ!1
               ]
            ifso if TimerHasExpired(lv freeQTimer) then
               CallSwat("pbiFreeQ empty for 20 sec. - deadlock?")
      ]
   while pbiTQ!0 ne 0 do
      [
      pbi = Dequeue(pbiTQ)
      compileif multipleNets then
         [
         if pbi>>PBI.allNets then
            [  //handle pbi going out on all nets
            BroadcastNextNet(pbi, pbi>>PBI.ndb>>NDB.link)
            loop
            ]
         ]
      ReleasePBI(pbi)
      ]
   pbi = Dequeue(pbiIQ)
   if pbi ne 0 then break
   Block()
   ] repeat

// We now have an input pbi to process.
pbi>>PBI.queue = pbiTQ  //in case pbi is re-used for output
pbi>>PBI.socket = 0  //pbi not yet assigned to a socket
pbi>>PBI.status = 0  //in particular, allNets = false

// Don't ever allow a Pup from host zero to be processed, because
// if we turn it around it will become a broadcast!
if pbi>>PBI.pup.sPort.host eq 0 then [ ReleasePBI(pbi); loop ]

// still in PupLevel1

// PupLevel1 (cont'd)

// If the destination net is different from that on which the pbi arrived then
//   if we know the identity of the net on which the pbi arrived
//     then if we have a valid RT entry for the Pup destination net
//        then if we are directly connected to that net &
//		(it isn't a broadcast % its immed. src net ne dest net)
//           then setup ndb so that we later compare the destination
//              host with our local host address on that net
//           else it can't be for us so forward it via gateway
//        else we don't know how it got here so discard
//     else assume it arrived on the correct destination net

let dNet = pbi>>PBI.pup.dPort.net
let dHost = pbi>>PBI.pup.dPort.host
let ndb = pbi>>PBI.ndb  //ndb for net it came in on
if dNet ne ndb>>NDB.localNet then
   if dNet ne 0 & ndb>>NDB.localNet ne 0 then
      [
      let rte = HLookup(pupRT, dNet)  //rte for destination net
      test rte ne 0
         ifso test rte>>RTE.hops eq 0 &
          (multipleNets? (dHost ne 0 % ndb eq rte>>RTE.ndb), true)
            ifso ndb = rte>>RTE.ndb
            ifnot [ Enqueue(gatewayIQ, pbi); loop ]
         ifnot  //routing error.  Don't know why it got to us.
            [ PupError(pbi, #1002, "Can't get there from here", true); loop ]
      ]

// Now we know the dest net is one to which we are connected.
// ndb points to the NDB for it.
unless dHost eq ndb>>NDB.localHost % dHost eq 0 do
   [ Enqueue(gatewayIQ, pbi); loop ]

// pbi is for us.  Search local socket queue for recipient.
let soc = socketQ!0
   [
   if soc eq 0 then [ SocketNotFound(pbi); break ]
   if pbi>>PBI.pup.dPort.socket↑1 eq soc>>PupSoc.lclPort.socket↑1 &
    pbi>>PBI.pup.dPort.socket↑2 eq soc>>PupSoc.lclPort.socket↑2 then
      [ // socket found
      if soc>>PupSoc.doChecksum then  //check Pup checksum
         [
         let c = pbi!(lenPBIOverhead+(pbi>>PBI.pup.length-1) rshift 1)
         unless c eq -1 % c eq PupChecksum(lv pbi>>PBI.pup) do 
            [ PupError(pbi, 1, "Bad Checksum"); break ]
         ]

      // if we don't know the identity of the net on which the
      // packet arrived, but it was addressed to a specific
      // net, and to an existing socket in this host,
      // then believe the destination net number in the packet
      // and establish the identity of the connected net in the RT.
      if ndb>>NDB.localNet eq 0 & dNet ne 0 then
         [
         pbi>>PBI.ndb>>NDB.localNet = dNet
         let rte = HInsert(pupRT, dNet)  //zeroes hop count
         rte>>RTE.ndb = ndb
         rte>>RTE.host = ndb>>NDB.localHost
         ]
      FillInNets(pbi)  // default zero source and dest net numbers
      if soc>>PupSoc.numIPBI eq 0 % soc>>PupSoc.numTPBI eq 0 then
         [ PupError(pbi, 3, "Port IQ full"); break ]
      soc>>PupSoc.iAll = soc>>PupSoc.iAll -1  // see comment in GetPBI
      soc>>PupSoc.tAll = soc>>PupSoc.tAll -1
      pbi>>PBI.usage = true  // this is an input packet
      pbi>>PBI.socket = soc
      Enqueue(lv soc>>PupSoc.iQ, pbi)
      break
      ]
   soc = soc!0
   ] repeat
]PupLevel1 repeat

//----------------------------------------------------------------------------
and SocketNotFound(pbi) be  // default handling for unwanted pbi's
//----------------------------------------------------------------------------
   PupError(pbi, 2, "No such port")

//----------------------------------------------------------------------------
and FillInNets(pbi) be
//----------------------------------------------------------------------------
[
if pbi>>PBI.pup.dPort.net eq 0 then
   pbi>>PBI.pup.dPort.net = pbi>>PBI.ndb>>NDB.localNet
if pbi>>PBI.pup.sPort.net eq 0 then
   pbi>>PBI.pup.sPort.net = pbi>>PBI.ndb>>NDB.localNet
]

//----------------------------------------------------------------------------
and PupError(pbi, subtype, string, setSourcePort; numargs na) be
//----------------------------------------------------------------------------
[
manifest numHeaderWords = (offset Pup.words)/16
test pbi>>PBI.pup.type eq typeError %  // incoming type Error?
 pbi>>PBI.pup.dPort.host eq 0 %  // broadcast?
 pbi>>PBI.pup.dPort.socket↑1 eq socTransient  // to transient socket?
   ifso ReleasePBI(pbi)  // yes, forget it
   ifnot
      [
      MoveBlock(lv pbi>>PBI.pup.words↑1,lv pbi>>PBI.pup, numHeaderWords)
      FillInNets(pbi)
      pbi>>PBI.pup.words↑(numHeaderWords+1) = subtype
      pbi>>PBI.pup.words↑(numHeaderWords+2) = 0
      AppendStringToPup(pbi, 2*(numHeaderWords+2)+1, string)
      ExchangePorts(pbi)
      if na ge 4 & setSourcePort then
         [
         pbi>>PBI.pup.sPort.net = pbi>>PBI.ndb>>NDB.localNet
         pbi>>PBI.pup.sPort.host = pbi>>PBI.ndb>>NDB.localHost
         pbi>>PBI.pup.sPort.socket↑1 = 0
         pbi>>PBI.pup.sPort.socket↑2 = 0
         ]
      pbi>>PBI.queue = pbiTQ
      CompletePup(pbi, typeError)
      ]
]

//----------------------------------------------------------------------------
and CompletePup(pbi, type, length; numargs na) be
//----------------------------------------------------------------------------
[
if na ge 2 then pbi>>PBI.pup.type = type
if na ge 3 then pbi>>PBI.pup.length = length
pbi>>PBI.pup.transport = 0
let soc = pbi>>PBI.socket
if soc ne 0 then  // error and trace pups have no lcl socket
   [  // default any zero address fields in header
   DefaultPort(lv pbi>>PBI.pup.dPort, lv soc>>PupSoc.frnPort)
   DefaultPort(lv pbi>>PBI.pup.sPort, lv soc>>PupSoc.lclPort)
   compileif pupDebug then
      [
      if pbi>>PBI.pup.sPort.socket↑1 eq 0 &
       pbi>>PBI.pup.sPort.socket↑2 eq 0 then
         CallSwat("[CompletePup] Zero source socket")
      ]
   ]

// Check for request to broadcast on all directly-connected networks
if pbi>>PBI.allNets then
   [
   compiletest multipleNets
      ifso [ BroadcastNextNet(pbi, ndbQ!0); return ]
      ifnot
         [ // if bypassZeroNet, discard if net's identity is not known
         if (ndbQ!0)>>NDB.localNet eq 0 & pbi>>PBI.bypassZeroNet then
            [ ReleasePBI(pbi); return ]
         ]
   ]

// Set Pup checksum
pbi!(lenPBIOverhead + (pbi>>PBI.pup.length-1) rshift 1) =
 soc eq 0 % soc>>PupSoc.doChecksum? PupChecksum(lv pbi>>PBI.pup), -1

// Route, encapsulate, and transmit
let pdh = RoutePup(pbi)
test pbi>>PBI.ndb ne 0
   ifso
      [  // destination net is in RT.  Encapsulate and transmit
      (pbi>>PBI.ndb>>NDB.encapsulatePup)(pbi, pdh)
      (pbi>>PBI.ndb>>NDB.level0Transmit)(pbi)
      ]
   ifnot
      [  // destination net not in RT.  Dispose of pbi as if it had
         // been transmitted, and initiate a probe to locate the net
      LocateNet(pbi>>PBI.pup.dPort.net)
      Enqueue(pbi>>PBI.queue, pbi)
      ]
]

//----------------------------------------------------------------------------
and RoutePup(pbi) = valof
//----------------------------------------------------------------------------
// Sets ndb pointer in pbi, 0 if can't route there
// Returns physical destination host
[
compileif pupDebug then
   [
   if pbi>>PBI.pup.sPort.host eq 0 then
      CallSwat("[RoutePup] Zero source host")
   ]
let rte = HLookup(pupRT, pbi>>PBI.pup.dPort.net)
pbi>>PBI.ndb = rte ne 0 & rte>>RTE.hops le maxHops ? rte>>RTE.ndb, 0
resultis rte>>RTE.hops eq 0? pbi>>PBI.pup.dPort.host, rte>>RTE.host
]

//----------------------------------------------------------------------------
and DefaultPort(pupPort, socPort) be
//----------------------------------------------------------------------------
[
if pupPort>>Port.net eq 0 then
   [  // if port in socket refers to net zero but to a specific
      // host, then update the net number in the socket with
      // the actual net number of the default NDB, assuming that
      // is now known.
   if socPort>>Port.net eq 0 & socPort>>Port.host ne 0 then
      socPort>>Port.net = (ndbQ!0)>>NDB.localNet
   pupPort>>Port.net = socPort>>Port.net
   ]
if pupPort>>Port.host eq 0 then
   pupPort>>Port.host = socPort>>Port.host
if pupPort>>Port.socket↑1 eq 0 & pupPort>>Port.socket↑2 eq 0 then
   [
   pupPort>>Port.socket↑1 = socPort>>Port.socket↑1
   pupPort>>Port.socket↑2 = socPort>>Port.socket↑2
   ]
]

//----------------------------------------------------------------------------
and ExchangePorts(pbi) be
//----------------------------------------------------------------------------
// Exchange source and destination ports.
[
let d = lv pbi>>PBI.pup.dPort
let s = lv pbi>>PBI.pup.sPort
for i = 0 to lenPort-1 do
   [ let temp = d!i; d!i = s!i; s!i = temp ]

// If the original pup had a destination net of zero, fill in the actual net
// number (if we know it) so the reply will appear to arise from the
// net the original pup arrived on.
// If the original pup was broadcast, when the exchange is done
// sPort.host will be zero.  CompletePup will default this to
// the lclPort.host in the level1 socket, which may not be the
// correct host corresponding to sPort.net if this host has multiple
// nets, so force it by setting sPort.host here.
compileif multipleNets then
   [
   if pbi>>PBI.pup.sPort.net eq 0 then
      pbi>>PBI.pup.sPort.net = pbi>>PBI.ndb>>NDB.localNet
   if pbi>>PBI.pup.sPort.host eq 0 then
      // the source net here was the destination before the swap, so
      // it is guaranteed to be in the routing table.
      pbi>>PBI.pup.sPort.host =
       HLookup(pupRT, pbi>>PBI.pup.sPort.net)>>RTE.ndb>>NDB.localHost
   ]
]

//----------------------------------------------------------------------------
and SetPupDPort(pbi, port) be
//----------------------------------------------------------------------------
   MoveBlock(lv pbi>>PBI.pup.dPort, port, lenPort)

//----------------------------------------------------------------------------
and SetPupSPort(pbi, port) be
//----------------------------------------------------------------------------
   MoveBlock(lv pbi>>PBI.pup.sPort, port, lenPort)

//----------------------------------------------------------------------------
and SetPupID(pbi, pupID) be
//----------------------------------------------------------------------------
[
pbi>>PBI.pup.id↑1 = pupID!0
pbi>>PBI.pup.id↑2 = pupID!1
]

//----------------------------------------------------------------------------
and GetPBI(soc, returnOnFail; numargs na) = valof
//----------------------------------------------------------------------------
// Gets an OUTPUT pbi charged to the socket.  The Pup header is cleared.
// If returnOnFail is true, return zero on failure;
// if false or omitted, block until a pbi is available.
[
let pbi = nil
   [
   if soc>>PupSoc.numTPBI gr 0 & soc>>PupSoc.numOPBI gr 0 then
      [
      pbi = Dequeue(pbiFreeQ)
      if pbi ne 0 then break
      ]
   if na ge 2 & returnOnFail then resultis 0
   Block()
   ] repeat

// Adjust socket allocations.
// Do full-word arithmetic because structure references are expensive.
// Know that numTPBI and numOPBI are the right-hand bytes of the words
// and that the computation will not underflow.
soc>>PupSoc.tAll = soc>>PupSoc.tAll-1
soc>>PupSoc.oAll = soc>>PupSoc.oAll-1

Zero(pbi, lenPBIOverhead+pupOvWords)  // clear pbi and pup header
pbi>>PBI.socket = soc
pbi>>PBI.queue = pbiTQ  // default disposition after output done
resultis pbi
]

//----------------------------------------------------------------------------
and ReleasePBI(pbi) be
//----------------------------------------------------------------------------
[
let soc = pbi>>PBI.socket
if soc ne 0 then
   [
   soc>>PupSoc.tAll = soc>>PupSoc.tAll+1
   test pbi>>PBI.usage  // input or output PBI?
      ifso soc>>PupSoc.iAll = soc>>PupSoc.iAll+1
      ifnot soc>>PupSoc.oAll = soc>>PupSoc.oAll+1
   ]
Enqueue(pbiFreeQ, pbi)
]