// ImpFrontEnd.bcpl
// Last modified August 22, 1983  11:08 AM by Taft

get "Pup0.decl"
get "PupAlImp.decl"
get "AltoDefs.d"
get "SysDefs.d"
get "Streams.d"
get "ImpFENames.d"
get "MenuDefs.d"

external
[
// outgoing procedures
Wss

// incoming procedures
InitPupLevel1; LoadRam; InitBcplRuntime
Enqueue; Dequeue
InitializeContext; CallContextList; Block
SetTimer; TimerHasExpired; Dismiss
CreateStringStream; Puts; Closes; PutTemplate
ShowDisplayStream; CreateDisplayStream; CreateKeyboardStream
InitializeZone; AddToZone; Allocate; Free
MoveBlock; SetBlock; Zero; MyFrame; Junta; DoubleIncrement; Umin; CallSwat; SysErr

// incoming statics
impNDB; ndbQ; lenPBI; pbiFreeQ; sysZone; RamImage; keys; dsp; gacha12; sysFont
]

static
[
version
impIQ; etherIQ
etherIPHost = 0  // host to send IP packets to
etherNDB
ctxQ
stats
beginStorage; endStorage
]

//----------------------------------------------------------------------------
structure ArpanetAddress:  // IP address of an Arpanet host
//----------------------------------------------------------------------------
[
net byte
host byte
imp word
]
manifest lenArpanetAddress = size ArpanetAddress/16

//----------------------------------------------------------------------------
structure IPPBI:  // IP encapsulation in Ethernet packet to/from front-end
//----------------------------------------------------------------------------
[
blank word offset PBI.encapsulation/16
dest byte		// standard Ethernet header
src byte
type word
arpaAdr @ArpanetAddress	// Arpanet source or destination
data word 0 = ip @IPHeader  // encapsulated IP packet
]
manifest lenIPEncap = (offset IPPBI.data - offset PBI.encapsulation)/16

//----------------------------------------------------------------------------
structure Stats:  // Forwarding statistics
//----------------------------------------------------------------------------
[
fromImp word 2
toImp word 2
discard word 2
hostDown word
impDown word
]
manifest lenStats = size Stats/16

structure String: [ length byte; char↑1,1 byte ]

manifest
[
// Ethernet types for encapsulating IP packet between IP host and front-end
etIPtoFE = 1004B	// IP host to front-end (arpaAdr is destination)
etFEtoIP = 1005B	// front-end to IP host (arpaAdr is source)

netArpa = 10		// Arpanet IP net number

minLenIPPkt = 10	// words in minimum-length IP packet
maxLenIPPkt = 576/2	// words in maximum-length IP packet
maxLenEtherIPPkt = lenIPEncap+maxLenIPPkt // max-length Ethernet-encapsulated IP packet

lenForwarderCtx = 150
lenDisplayCtx = 400
endCode = 335B
]

//----------------------------------------------------------------------------
let Main() be
//----------------------------------------------------------------------------
[
version = "ImpFrontEnd of August 22, 1983"
Junta(levAlloc, Top)
]

//----------------------------------------------------------------------------
and Top() be
//----------------------------------------------------------------------------
[
Init()

// Give leftover storage to sysZone and turn it into packet buffers
AddToZone(sysZone, InitPupLevel1, beginStorage-InitPupLevel1)
@endCode = MyFrame()-50
AddToZone(sysZone, endStorage, @endCode-endStorage)
let slop = Allocate(sysZone, 500)

   [ // repeat
   let pbi = Allocate(sysZone, lenPBI, true)
   if pbi eq 0 then break
   Enqueue(pbiFreeQ, pbi)
   ] repeat

Free(sysZone, slop)

CallContextList(ctxQ!0) repeat
]

//----------------------------------------------------------------------------
and ImpForwarderCtx(ctx) be  // Handles forwarding in both directions
//----------------------------------------------------------------------------
[ // repeat
while impIQ!0 ne 0 do
   [
   let ipbi = Dequeue(impIQ)
   if etherIPHost eq 0 then
      [ Discard(ipbi); loop ]  // no IP host to send to yet
   let opbi = Dequeue(pbiFreeQ)  // copying packet is faster than shifting up in-place
   if opbi eq 0 then
      [ Discard(ipbi); loop ]  // can't get PBI to copy into
   let lenIPPkt = ipbi>>PBI.packetLength
   if offset IPPBI.data/16+lenIPPkt gr lenPBI then
      [ Discard(opbi); loop ]  // too large to be encapsulated in local PBI
   MoveBlock(lv opbi>>IPPBI.data, lv ipbi>>ImpPBI.data, lenIPPkt)
   opbi>>IPPBI.arpaAdr.net = netArpa
   opbi>>IPPBI.arpaAdr.host = ipbi>>ImpPBI.host
   opbi>>IPPBI.arpaAdr.imp = ipbi>>ImpPBI.imp
   opbi>>PBI.packetLength = lenIPEncap+lenIPPkt
   opbi>>IPPBI.dest = etherIPHost
   opbi>>IPPBI.src = etherNDB>>NDB.localHost
   opbi>>IPPBI.type = etFEtoIP
   opbi>>PBI.ndb = etherNDB
   opbi>>PBI.queue = pbiFreeQ
   Enqueue(pbiFreeQ, ipbi)
   (etherNDB>>NDB.level0Transmit)(opbi)
   DoubleIncrement(lv stats>>Stats.fromImp)
   ]

while etherIQ!0 ne 0 do
   [
   let pbi = Dequeue(etherIQ)
   if pbi>>IPPBI.dest eq 0 then
      [ Discard(pbi); loop ]  // ignore broadcasts
   etherIPHost = pbi>>IPPBI.src  // partner is most recent guy who sent to us
   let lenIPPkt = pbi>>PBI.packetLength - lenIPEncap
   pbi>>ImpPBI.host = pbi>>IPPBI.arpaAdr.host
   pbi>>ImpPBI.imp = pbi>>IPPBI.arpaAdr.imp
   pbi>>ImpPBI.link = linkInternet
   MoveBlock(lv pbi>>ImpPBI.data, lv pbi>>IPPBI.data, lenIPPkt)
   pbi>>PBI.packetLength = lenIPPkt
   pbi>>PBI.ndb = impNDB
   pbi>>PBI.queue = pbiFreeQ
   (impNDB>>NDB.level0Transmit)(pbi)
   DoubleIncrement(lv stats>>Stats.toImp)
   ]

Block()
] repeat

//----------------------------------------------------------------------------
and Discard(pbi) be
//----------------------------------------------------------------------------
[
Enqueue(pbiFreeQ, pbi)
DoubleIncrement(lv stats>>Stats.discard)
]

//----------------------------------------------------------------------------
and ImpIPFilter(pbi) = valof
//----------------------------------------------------------------------------
[
let lenIPPkt = (pbi>>ImpPBI.ip.length+1) rshift 1
test pbi>>ImpPBI.link eq linkInternet & pbi>>ImpPBI.ip.version eq versionIP &
 pbi>>PBI.packetLength ge minLenIPPkt & lenIPPkt le pbi>>PBI.packetLength
   ifso [ pbi>>PBI.packetLength = lenIPPkt; resultis true ] // flush Imp padding
   ifnot resultis false
]

//----------------------------------------------------------------------------
and EtherIPFilter(pbi) =
//----------------------------------------------------------------------------
 pbi>>PBI.packetLength ge lenIPEncap+minLenIPPkt & pbi>>IPPBI.type eq etIPtoFE

//----------------------------------------------------------------------------
and DisplayCtx(ctx) be
//----------------------------------------------------------------------------
// Handles interactions with display and menu
[
let updateTimer = nil
SetTimer(lv updateTimer, 0)
let menu = MenuData>>DATA.menu
let oldStats = vec lenStats
SetBlock(oldStats, -1, lenStats)

   [ // repeat
   let selection = ScanMenu(menu, false)
   switchon selection into
      [
      case bQuit:
         @displayListHead = 0
         Dismiss(1)
         finish
      default:
         if selection ne 0 then DeSelect(menu!selection)
      ]

   if TimerHasExpired(lv updateTimer) then
      [
      DisplayNumber(bFromImp, lv stats>>Stats.fromImp, lv oldStats>>Stats.fromImp)
      DisplayNumber(bToImp, lv stats>>Stats.toImp, lv oldStats>>Stats.toImp)
      DisplayNumber(bDiscarded, lv stats>>Stats.discard, lv oldStats>>Stats.discard)
      stats>>Stats.hostDown = impNDB>>ImpNDB.status.hostNotReady
      stats>>Stats.impDown = impNDB>>ImpNDB.status.impNotReady
      DisplayUpDown(bHostUpDown, lv stats>>Stats.hostDown, lv oldStats>>Stats.hostDown,
       "Host")
      DisplayUpDown(bImpUpDown, lv stats>>Stats.impDown, lv oldStats>>Stats.impDown,
       "Imp")
      SetTimer(lv updateTimer, 100)  // 1 second
      ]

   Dismiss(1)
   ] repeat
]

//----------------------------------------------------------------------------
and DisplayNumber(boxName, lvNum, lvOldNum) be
//----------------------------------------------------------------------------
[
if lvNum!0 ne lvOldNum!0 % lvNum!1 ne lvOldNum!1 then
   [
   lvOldNum!0 = lvNum!0
   lvOldNum!1 = lvNum!1
   let box = (MenuData>>DATA.menu)!boxName
   FillBox(box, white)
   let string = vec 10
   let ss = CreateStringStream(string, 20)
   PutTemplate(ss, "$8ED", lvNum)
   Closes(ss)
   WriteBox(box, string, 0, sysFont)
   Dismiss(1)
   ]
]

//----------------------------------------------------------------------------
and DisplayUpDown(boxName, lvDown, lvOldDown, who) be
//----------------------------------------------------------------------------
[
if lvDown!0 ne lvOldDown!0 then
   [
   lvOldDown!0 = lvDown!0
   let box = (MenuData>>DATA.menu)!boxName
   FillBox(box, white)
   let string = vec 10
   let ss = CreateStringStream(string, 20)
   PutTemplate(ss, "$S $S", who, (lvDown!0? "down", "up"))
   Closes(ss)
   WriteBox(box, string, 0, sysFont)
   Dismiss(1)
   ]
]

//----------------------------------------------------------------------------
and Wss(s, str) be  // needed because we Junta it and PutTemplate calls it
//----------------------------------------------------------------------------
 for i=1 to str>>String.length do Puts(s, str>>String.char↑i)

//----------------------------------------------------------------------------
and Init() be
//----------------------------------------------------------------------------
// This code could be discarded at the end of initialization if we felt like it.
[
// Storage initialization
endStorage = MyFrame() - 1000
let lenStorage = Umin(endStorage-@endCode, 77777B)
beginStorage = endStorage-lenStorage
@endCode = endStorage
sysZone = InitializeZone(beginStorage, lenStorage, SysErr, 0)

// Context initialization
ctxQ = Allocate(sysZone, 2); Zero(ctxQ, 2)
Enqueue(ctxQ, InitializeContext(Allocate(sysZone, lenForwarderCtx),
 lenForwarderCtx, ImpForwarderCtx))
Enqueue(ctxQ, InitializeContext(Allocate(sysZone, lenDisplayCtx),
 lenDisplayCtx, DisplayCtx))

// Microcode initialization
test LoadRam(RamImage, true) eq 0
   ifso InitBcplRuntime()
   ifnot CallSwat("Can't load microcode")

// Pup package initialization
// Packet buffers must be larger than normal so as to accomodate Ethernet-encapsulated
// IP packets with perhaps one extra word of Imp padding.  Computation is tricky
// since the number we pass to InitPupLevel1 is based on number of Pup data bytes
// whereas we are dealing in raw packets.
manifest bytes = 2*maxLenEtherIPPkt - pupOvBytes - size PBI.encapsulation/8 + 2
InitPupLevel1(sysZone, ctxQ, 10, bytes)

// Make sure we have an Imp interface, and find the Ethernet NDB
let ndb = ndbQ!0
let haveImp = false
while ndb ne 0 do
   [
   if ndb>>NDB.netType eq netTypeArpa then haveImp = true
   if ndb>>NDB.netType eq netTypeEther then etherNDB = ndb
   ndb = ndb!0
   ]
unless haveImp do CallSwat("Imp interface not installed")
if etherNDB eq 0 then CallSwat("Ethernet interface not installed")

// Set up IP packet filters for Imp and Ethernet
let pf = Allocate(sysZone, lenPF+2); Zero(pf, lenPF+2)
impIQ = pf+lenPF
pf>>PF.predicate = ImpIPFilter
pf>>PF.queue = impIQ
if haveImp then Enqueue(lv impNDB>>NDB.pfQ, pf)
pf = Allocate(sysZone, lenPF+2); Zero(pf, lenPF+2)
etherIQ = pf+lenPF
pf>>PF.predicate = EtherIPFilter
pf>>PF.queue = etherIQ
if etherNDB ne 0 then Enqueue(lv etherNDB>>NDB.pfQ, pf)

// Display and menu initialization
// Make a DisplayStream window at the top of the screen, both to
// space the Menu display down and to give the Menu package
// a real dsp to work from (it gets the default font by calling
// GetFont(dsp)!!!!).
sysFont = gacha12+2
dsp = CreateDisplayStream(6, Allocate(sysZone, 200), 200, sysFont, 2)
ShowDisplayStream(dsp, DSalone)
let lenBitMap = MenuSize()
let bitMap = Allocate(sysZone, lenBitMap)
let stream = CreateMenuDisplayStream(bitMap, lenBitMap)
ShowDisplayStream(stream)
MenuIdle = Block
FillBox((MenuData>>DATA.menu)!bTitle, white)
WriteBox((MenuData>>DATA.menu)!bTitle, version, 0, sysFont)

// Misc other initialization
keys = CreateKeyboardStream()
stats = Allocate(sysZone, lenStats); Zero(stats, lenStats)
]