// GateForward.bcpl -- Level 1 code implementing gateway functions

// Last modified February 8, 1979  3:33 PM by Boggs

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

external
[
// outgoing procedures
Forwarder; ForwarderCtx; TMLookup

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

// outgoing statics
@gf; gatewayGoingDown

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

static [ @gf; gatewayGoingDown = false ]

//----------------------------------------------------------------------------
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) % pupRTChanged then
   [
   let pbi = GetPBI(gatewayListenerSoc, true)
   if pbi ne 0 then
      [
      pupRTChanged = false
      pbi>>PBI.allNets = true
      pbi>>PBI.bypassZeroNet = true
      SendRouteInfo(pbi)
      SetTimer(lv gf>>GF.routeBcstTimer, routeBcstInterval)
      ]
   ]

// 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

//We are experimentally allowing 'directed broadcasts': broadcast packets
// destined for a net OTHER THAN THE ONE ON WHICH THEY ARRIVE.
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 8th 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
                  unknownNet = 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 in 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
         [
         SendRouteInfo(pbi)
         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 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 SendRouteInfo(pbi) be
//----------------------------------------------------------------------------
// Build and send a routing info packet in pbi.
// Caller is assumed to have fixed up the ports properly.
[
pbi>>PBI.pup.length = 0
HEnumerate(pupRT, PerRTE, pbi)
CompletePup(pbi, ptRouteReply, pbi>>PBI.pup.length+pupOvBytes)
]

//----------------------------------------------------------------------------
and PerRTE(rte, pbi) be
//----------------------------------------------------------------------------
[
if rte>>RTE.net ne 0 then
   [
   let c = pbi>>PBI.pup.length
   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 = c+4
   ]
]

//----------------------------------------------------------------------------
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)
]