// BFSEvent.bcpl -- monitor a BFS using the Pup Event Report Protocol
// Copyright Xerox Corporation 1979
// Last modified May 29, 1979  11:52 PM by Boggs

get "AltoFileSys.d"
get "AltoDefs.d"
get "Disks.d"
get "BFS.d"

external
[
// outgoing procedures
BFSEvent

// incoming procedures
Timer; Zero; MoveBlock; StartIO; Idle
]

static
[
// a very degenerate routing table:
lclNet			// our net number, last time we got a routing packet
lclHost			// our host number, last time we reset the interface
frnNet			// net number of our partner
gateHost		// gateway host to get to frnNet
BFSEventHost = 0	// non-zero => generate Event Reports to this address
]

//----------------------------------------------------------------------------
structure Port:		// Internetwork address
//----------------------------------------------------------------------------
[
net byte
host byte
socket↑1,2 word
]
manifest lenPort = size Port/16

//----------------------------------------------------------------------------
structure PBI:		// A Pup in an Ethernet packet
//----------------------------------------------------------------------------
[
eDest byte		// destination on this Ethernet
eSrc byte		// my address on this Ethernet
eType word		// 1000B => Pup

// the Pup begins here, stuff above is Ethernet encapsulation
length word		// Bytes of pup contents + header
transport byte		// zero it on sending
type byte		// pup type
id word 2		// generally used for duplicate detection
dPort: @Port		// internet destination address of this packet
sPort: @Port		// internet source address of this packet
bytes↑0,531 byte	// the data
checksum word		// position varies -- follows contents
]
manifest lenPBI = size PBI/16

//----------------------------------------------------------------------------
structure RTE:		// Routing table entry
//----------------------------------------------------------------------------
[
dNet byte		// to get to this net...
viaNet byte		// if zero I'm directly connected; send them to me
viaHost byte		// if non zero, I'll send them here
hops byte		// your goal is this many internet hops away
]
manifest lenRTE = size RTE/16

//----------------------------------------------------------------------------
structure RTPBI:	// Routing table PBI
//----------------------------------------------------------------------------
[
blank word offset PBI.bytes/16
rte↑0,0 @RTE
]

//----------------------------------------------------------------------------
structure BFSPBI:	// BFSEvent PBI
//----------------------------------------------------------------------------
[
blank word offset PBI.bytes/16
kcb @KCB
label @DL
]
manifest lenBFSPBI = size BFSPBI/16

manifest
[
pupOvBytes = 22		// Pup header overhead

ptEventReport = 240B	// Pup Types
ptEventReply = 241B
ptRouteRequest = 200B
ptRouteReply = 201B

socRouteInfo = 2	// Well known sockets

// Ether definitions
etherPup = 1000B	// Ethernet type = Pup

ePLoc = 600B		// Ending status
eBLoc = 601B		// Interrupt channel bits
eELoc = 602B		// Ending count
eLLoc = 603B		// Initial load
eICLoc = 604B		// Input count
eIPLoc = 605B		// Input pointer
eOCLoc = 606B		// Output count
eOPLoc = 607B		// Output pointer
eHLoc = 610B		// Host address

etherReset = 3		// interface SIO command bits
etherInput = 2
etherOutput = 1
]

//----------------------------------------------------------------------------
let BFSEvent(cb) be
//----------------------------------------------------------------------------
[
if BFSEventHost eq 0 return
let origTim = vec 2; Timer(origTim)
let sPort = table [ 0; 0; 123456b ]
let dPort = table [ 0; 0; 123456b ]
dPort>>Port.host = BFSEventHost
let pbi = vec lenPBI
for try = 1 to 3 do
   [
   MoveBlock(lv pbi>>BFSPBI.kcb, lv cb>>CB.link, lKCB)
   MoveBlock(lv pbi>>BFSPBI.label, cb>>CB.labelAddress, lDL)
   SendPup(pbi, sPort, dPort, origTim, ptEventReport, lenBFSPBI*2)
   if ReceivePup(pbi, dPort, sPort, origTim, ptEventReply, 27) break
   ]
]

//----------------------------------------------------------------------------
and InitNet() = valof
//----------------------------------------------------------------------------
[
@eHLoc = StartIO(etherReset)
if @eHLoc & 77777b eq 0 resultis false
@eHLoc = @eHLoc & 377b
if @eHLoc eq 0 resultis false
Zero(ePLoc, 7)
if @eHLoc ne lclHost then lclNet = 0
lclHost = @eHLoc
resultis true
]

//----------------------------------------------------------------------------
and SendPup(pbi, sPort, dPort, id, type, length) = valof
//----------------------------------------------------------------------------
// Assume data already in pbi.
// Returns true if packet seemed to go out OK.
[
unless InitNet() resultis false
if dPort ne 0 then MoveBlock(lv pbi>>PBI.dPort, dPort, lenPort)
if sPort ne 0 then MoveBlock(lv pbi>>PBI.sPort, sPort, lenPort)
if id ne 0 then MoveBlock(lv pbi>>PBI.id, id, 2)

let dNet = pbi>>PBI.dPort.net
test dNet eq 0 % dNet eq lclNet
   ifso pbi>>PBI.eDest = pbi>>PBI.dPort.host
   ifnot test dNet eq frnNet & gateHost ne 0
      ifso pbi>>PBI.eDest = gateHost
      ifnot
         [
         frnNet, gateHost = dNet, 0
         let rPort = table [ 0; 0; socRouteInfo ]
         SendPup(pbi, rPort, rPort, 0, ptRouteRequest, pupOvBytes)
         resultis true  //Liar!  And you dirtied his packet too!
         ]

pbi>>PBI.eSrc = @eHLoc
pbi>>PBI.eType = etherPup	// I'm a Pup in an Ether packet
pbi>>PBI.length = length
pbi>>PBI.transport = 0
pbi>>PBI.type = type
pbi>>PBI.sPort.net = lclNet
pbi>>PBI.sPort.host = @eHLoc
pbi!((length+3) rshift 1) = -1  // No checksum

@eOCLoc = (length+5) rshift 1	// Pup bytes + Ethernet encapsulation
@eOPLoc = pbi
@ePLoc = 0
StartIO(etherOutput)	// Turn on transmitter
for i = 1 to 30000 if @ePLoc ne 0 break
let status = @ePLoc
StartIO(etherReset)	// Reset interface
resultis status eq 777b  // 777b is good xmtr status
]

//----------------------------------------------------------------------------
and ReceivePup(pbi, sPort, dPort, id, type, timeout) = valof
//----------------------------------------------------------------------------
// Filter by sockets, id, and type.
// Returns pup length or 0 if timeout.
[
unless InitNet() resultis false
let tim = @realTimeClock + timeout
   [
   @eICLoc = lenPBI
   @eIPLoc = pbi
   @ePLoc = 0
   StartIO(etherInput)	// Turn on receiver
   while @ePLoc eq 0 & (tim - @realTimeClock) gr 0 do Idle()
   let lastEPLoc = @ePLoc
   let lastEELoc = @eELoc
   StartIO(etherReset)	// Reset interface
   if lastEPLoc eq 0 resultis 0  // timeout

   // reject obviously bad packets
   if (@eICLoc-lastEELoc) ne (pbi>>PBI.length+5) rshift 1 %
    lastEPLoc ne 377b % pbi>>PBI.eType ne etherPup % pbi>>PBI.eSrc eq 0 loop

   if EqV(lv pbi>>PBI.dPort.socket, table [ 0; socRouteInfo ], 2) &
    pbi>>PBI.type eq ptRouteReply then
      [  // sort of a 'built-in' socket...
      lclNet = pbi>>PBI.dPort.net
      for i = 0 to (pbi>>PBI.length-pupOvBytes)/lenRTE do
         if pbi>>RTPBI.rte↑i.dNet eq frnNet then
            [ gateHost = pbi>>PBI.sPort.host; break ]
      loop
      ]
   if (type eq 0? true, pbi>>PBI.type eq type) &
    EqV(lv pbi>>PBI.sPort.socket, lv sPort>>Port.socket, 2) &
    EqV(lv pbi>>PBI.dPort.socket, lv dPort>>Port.socket, 2) &
    EqV(lv pbi>>PBI.id, id, 2) resultis pbi>>PBI.length
   ] repeat	// Until good Pup received or timeout
]

//----------------------------------------------------------------------------
and EqV(a, b, len) = valof
//----------------------------------------------------------------------------
[
if (b & 177770B) eq 0 resultis true	// Don't compare
for i = 0 to len-1 do if a!i ne b!i resultis false
resultis true
]