// GateForward.bcpl -- Level 1 code implementing gateway functions
// Last modified June 7, 1983  9:57 AM by Taft

get "Pup0.decl"
get "Pup1.decl"
get "PupRoute.decl"
get "GateForward.decl"

external
[
// outgoing procedures
Forwarder; ForwarderCtx; TMLookup

// incoming procedures
GetPBI; ReleasePBI; PupError; ExchangePorts; CompletePup; RoutePup
OnesComplementAdd; OnesComplementSubtract; LeftCycle; LocateNet
SetTimer; TimerHasExpired; DoubleIncrement; MoveBlock; ReturnFrom
CallSwat; Block; Enqueue; Dequeue; HEnumerate; HHash

// outgoing statics
@gf; gatewayGoingDown

// incoming statics
pupRT; pbiFreeQ
ndbQ; gatewayListenerSoc
maxPupDataBytes
]

static [ @gf; gatewayGoingDown = false ]

// The following must parallel the ID and Ports in a Pup
structure IDAndPorts: [ id word 2; dPort @Port; sPort @Port ]
manifest lenIDAndPorts = size IDAndPorts/16

//----------------------------------------------------------------------------
let ForwarderCtx() be
//----------------------------------------------------------------------------
[
Block()

while gf>>GF.tQ.head ne 0 do
   [
   let pbi = Dequeue(lv gf>>GF.tQ)
   pbi>>PBI.ndb>>NDB.numGPBI = pbi>>PBI.ndb>>NDB.numGPBI +1
   ReleasePBI(pbi)
   ]

// time to broadcast routing information?
if (TimerHasExpired(lv gf>>GF.routeBcstTimer) % pupRT>>RT.changed) &
 // only if my IQ is empty, else we may exceed its allocation!
 gatewayListenerSoc>>PupSoc.iQ.head eq 0 then
   [
   let ok = SendRouteInfo(0)
   pupRT>>RT.changed = false
   // if failed to send (all of) route info because we ran out of
   // packet buffers, try again after a relatively short interval (10 sec).
   SetTimer(lv gf>>GF.routeBcstTimer, (ok? routeBcstInterval, 1000))
   ]

// are there any packets to forward?
let pbi = Dequeue(lv gf>>GF.iQ); if pbi eq 0 return

// Pup destination and source nets
let dNet, sNet = pbi>>PBI.pup.dPort.net, pbi>>PBI.pup.sPort.net
let psn = pbi>>PBI.ndb>>NDB.localNet  //Physical source net

test dNet eq 0 % sNet eq 0 %  //dest or source net unspecified?
 (pbi>>PBI.pup.dPort.host eq 0 & dNet eq pbi>>PBI.ndb>>NDB.localNet)
   ifso ReleasePBI(pbi)  //don't forward
   ifnot
      [
      let v = pbi!(offset PBI.pup.transport/16)
      pbi>>PBI.pup.hopCnt = pbi>>PBI.pup.hopCnt+1
      test pbi>>PBI.pup.hopCnt eq 0
         ifso PupError(pbi, 1004b, "Discarded by 16th gateway")
         ifnot
            [
            UpdatePupChecksum(lv pbi>>PBI.pup, offset Pup.transport/16, v)
            pbi>>PBI.queue = lv gf>>GF.tQ  //where to queue when done
            let pdh = RoutePup(pbi)  //try to route it
            let ndb = pbi>>PBI.ndb
            test ndb ne 0  //do we know how to route it?
               ifso test ndb>>NDB.numGPBI gr 0  //is there room?
                  ifnot PupError(pbi, 1007b, "Gateway OQ overflow")
                  ifso
                     [
                     ndb>>NDB.numGPBI = ndb>>NDB.numGPBI -1
                     (ndb>>NDB.encapsulatePup)(pbi, pdh)
                     (ndb>>NDB.level0Transmit)(pbi)
                     TMIncrement(psn, ndb>>NDB.localNet)
                     loop
                     ]
               ifnot
                  [  //don't know how to route to dNet
                  LocateNet(dNet)  //initiate a probe
                  ReleasePBI(pbi)  //discard
                  ]
            ]
      ]
TMIncrement(psn, 0)  //increment discarded count for psn
] repeat

//----------------------------------------------------------------------------
and Forwarder(pbi) be
//----------------------------------------------------------------------------
// PupRoute passes on PBIs (from Socket 2) which it doesn't handle.
[
ExchangePorts(pbi)  // do this here rather than inside each case
switchon pbi>>PBI.pup.type into
   [
   case ptRouteRequest:
      [
      //don't answer probes from networks whose identity we don't know
      if pbi>>PBI.pup.dPort.net ne 0 then
         [
         let idAndPorts = vec lenIDAndPorts
         MoveBlock(idAndPorts, lv pbi>>PBI.pup.id, lenIDAndPorts)
         ReleasePBI(pbi)
         SendRouteInfo(idAndPorts)
         DoubleIncrement(lv gf>>GF.stats.routeReqs)
         return
         ]
      endcase
      ]
   case ptStatsRequest:
      [
      let stats = lv pbi>>PBI.pup.words
      MoveBlock(lv pbi>>PBI.pup.words, lv gf>>GF.stats, lenStats)
      let ptr = lenStats +1

      let ndb = ndbQ!0; while ndb ne 0 do
         [
         //skip nets whose identity we don't know
         if ndb>>NDB.localNet ne 0 then
            [
            stats>>Stats.numNets = stats>>Stats.numNets +1
            pbi>>PBI.pup.words↑ptr = ndb>>NDB.localNet
            ptr = ptr +1
            ]
         ndb = ndb>>NDB.link
         ]

      //if anybody else needs TMEnumerate, rewrite this procedure
      for i = 0 to (1 lshift gf>>GF.tm>>TM.logSize)-1 do
         if gf>>GF.tm>>TM.TME↑i.sdNet ne -1 then
            [
            if (ptr+lenTME-1) gr (maxPupDataBytes rshift 1) break
            stats>>Stats.numTMEs = stats>>Stats.numTMEs +1
            MoveBlock(lv pbi>>PBI.pup.words↑ptr,
             lv gf>>GF.tm>>TM.TME↑i.sdNet, lenTME)
            ptr = ptr + lenTME
            ]

      CompletePup(pbi, ptStatsReply, pupOvBytes + (ptr-1) lshift 1)
      return
      ]
   ]
ReleasePBI(pbi)
]

//----------------------------------------------------------------------------
and SendRouteInfo(idAndPorts) = valof
//----------------------------------------------------------------------------
// Send routing info Pup(s) as specified by idAndPorts, or broadcast
// to all nets if idAndPorts=0.
// Returns true normally, false if failed due to running out of PBIs.
[
// pbi must be the word after idAndPorts in the frame
let pbi = InitRouteInfoPup(idAndPorts)
if pbi ne 0 then
   [
   HEnumerate(pupRT, PerRTE, lv idAndPorts)
   CompletePup(pbi)
   ]
resultis pbi ne 0
]

//----------------------------------------------------------------------------
and PerRTE(rte, lvIDAndPorts) be
//----------------------------------------------------------------------------
[ // repeat
if rte>>RTE.net ne 0 then
   [
   let pbi = lvIDAndPorts!1
   let c = pbi>>PBI.pup.length - pupOvBytes
   if c gr maxRouteInfoDataBytes-4 then
      [
      CompletePup(pbi)
      lvIDAndPorts!1 = InitRouteInfoPup(lvIDAndPorts!0)
      if lvIDAndPorts!1 eq 0 then ReturnFrom(SendRouteInfo, false)
      loop
      ]
   pbi>>PBI.pup.bytes↑(c+1) = rte>>RTE.net
   pbi>>PBI.pup.bytes↑(c+2) = rte>>RTE.ndb>>NDB.localNet
   pbi>>PBI.pup.bytes↑(c+3) = rte>>RTE.host
   // If we are about to go down, say that we are a terrible route to anywhere
   pbi>>PBI.pup.bytes↑(c+4) = gatewayGoingDown? maxHops+1, rte>>RTE.hops
   pbi>>PBI.pup.length = pbi>>PBI.pup.length+4
   ]
return
] repeat

//----------------------------------------------------------------------------
and InitRouteInfoPup(idAndPorts) = valof
//----------------------------------------------------------------------------
[
let pbi = GetPBI(gatewayListenerSoc, true)
if pbi ne 0 then
   [
   pbi>>PBI.pup.type = ptRouteReply
   pbi>>PBI.pup.length = pupOvBytes
   test idAndPorts eq 0
      ifso [ pbi>>PBI.allNets = true; pbi>>PBI.bypassZeroNet = true ]
      ifnot MoveBlock(lv pbi>>PBI.pup.id, idAndPorts, lenIDAndPorts)
   ]
resultis pbi
]

//----------------------------------------------------------------------------
and UpdatePupChecksum(pup, index, oldValue) be
//----------------------------------------------------------------------------
// Update Pup checksum to account for changing one word in the Pup
// Typical call (changing Transport byte):
//	let oldWord = pup!(offset Pup.transport/16)
//	pup>>Pup.transport = newValue
//	UpdatePupChecksum(pup, offset Pup.transport/16, oldWord)
[
let len = (pup>>Pup.length-1) rshift 1
if pup!len ne -1 then
   pup!len = OnesComplementAdd(pup!len,
    LeftCycle(OnesComplementSubtract(pup!index, oldValue), len-index))
]

//----------------------------------------------------------------------------
and TMLookup(tm, sNet, dNet, findFree; numargs na) = valof
//----------------------------------------------------------------------------
[
let key = sNet lshift 8 + dNet
let iProbe = nil
let increment = HHash(tm>>TM.logSize, key, lv iProbe)
let probe = iProbe
   [
   let tme = lv tm>>TM.TME↑probe
   if tme>>TME.sdNet eq key then resultis tme
   if tme>>TME.sdNet eq -1 then
      resultis na ge 4 & findFree? tme, 0  //fail
   probe = (probe+increment) & ((1 lshift tm>>TM.logSize)-1)
   ] repeatuntil probe eq iProbe
resultis 0  //fail.  Searched the whole table
]

//----------------------------------------------------------------------------
and TMIncrement(sNet, dNet) be
//----------------------------------------------------------------------------
// Looks up sNet,,dNet in the transit matrix hash table.
// Creates an entry if not found then increments the count.
[
let tme = TMLookup(gf>>GF.tm, sNet, dNet, true)
if tme eq 0 then return  // table full!
if tme>>TME.sdNet eq -1 then  // empty tme
   tme>>TME.sdNet = sNet lshift 8 + dNet
DoubleIncrement(lv tme>>TME.count)
]