// NetExec1.bcpl -- all the network-related stuff
// Copyright Xerox Corporation 1979, 1980, 1982, 1983
// Last modified July 1, 1983  4:23 PM by Boggs

get "PupEftp.decl"
get "AltoDefs.d"

external
[
// outgoing procedures
PrintName; PrintPort; LoadMicrocode; Where; LoadKT
GetTime; GetDir; GetName; NetBoot; MyEtherBoot

// incoming procedures
UNPACKDT; WRITEUDT
DisableInterrupts; StartIO
MoveBlock; Zero; Dequeue; Allocate; Free; Block
SetTimer; TimerHasExpired; Dismiss; SetCalendar
PutTemplate; Wss; Closes; Resets
StringCompare; GetKeyword; GetString
EnumerateKeywordTable; InsertKeyword; LookupKeyword; DeleteKeyword

GetPartner; OpenLevel1Socket; CloseLevel1Socket
GetPBI; ReleasePBI; CompletePup; LocateNet
OpenEFTPSoc; ReceiveEFTPBlock; CloseEFTPSoc

// incoming statics
sysZone
kbdCS; cmdDsp; cmdKT; ebKT; buf; eng
timeRequest; dirRequest
lenPBI; ndbQ
mayDayPacket; bootLoaderPacket
]

static ourName

manifest
[
psMiscServ = 4
ptAltoTimeReq = 206b
ptAltoTimeReply = 207b
ptBootFileReq = 244b
ptBootDirReq = 257b
ptBootDirReply = 260b
ptAddressLookup = 223b
ptAddressReply = 224b
ptAddressError = 222b
]

structure KTE:	//must match definition in NetExec.bcpl
[
bfn word	//boot file number
local word	//true => local procedure; false => boot file
port @Port	//internet address of boot server
date word 2	//used as a version number
]

structure BFD: [ bfn word; date word 2; name @String ]

//-----------------------------------------------------------------------------------------
let GetTime() be  //a context
//-----------------------------------------------------------------------------------------
[
Block() repeatuntil timeRequest
let pbi = Allocate(sysZone, lenPBI); Zero(pbi, lenPBI)
pbi>>PBI.pup.type = ptAltoTimeReq
pbi>>PBI.pup.length = pupOvBytes
pbi>>PBI.pup.dPort.socket↑2 = psMiscServ
SimplePup(pbi, 50, 1000, InstallTime)
Free(sysZone, pbi)
timeRequest = 0
] repeat

//-----------------------------------------------------------------------------------------
and InstallTime(pbi, nil) = valof
//-----------------------------------------------------------------------------------------
[
structure NTime:
   [
   time word 2
   zone:
      [
      sign bit 1
      hour bit 7
      minute bit 8
      ]
   beginDST word
   endDST word
   ]

if pbi>>PBI.pup.type ne ptAltoTimeReply resultis true  // keep trying
let nTime = lv pbi>>PBI.pup.words
SetCalendar(lv nTime>>NTime.time)
timeParams>>LTP.sign = nTime>>NTime.zone.sign
timeParams>>LTP.zoneH = nTime>>NTime.zone.hour
timeParams>>LTP.zoneM = nTime>>NTime.zone.minute
timeParams>>LTP.beginDST = nTime>>NTime.beginDST
timeParams>>LTP.endDST = nTime>>NTime.endDST
resultis false
]

//-----------------------------------------------------------------------------------------
and GetDir() be  //a context
//-----------------------------------------------------------------------------------------
[
Block() repeatuntil dirRequest
let target = dirRequest; dirRequest = 0
if target>>String.char↑1 eq $[ then
   [
   for i = 2 to target>>String.length-1 do
      target>>String.char↑(i-1) = target>>String.char↑i
   target>>String.length = target>>String.length -2
   ]
let port = vec lenPort; Zero(port, lenPort)
GetPartner(target, kbdCS, port, 0, psMiscServ)
Free(sysZone, target)

let pbi = Allocate(sysZone, lenPBI); Zero(pbi, lenPBI)
pbi>>PBI.pup.type = ptBootDirReq
pbi>>PBI.pup.length = pupOvBytes
MoveBlock(lv pbi>>PBI.pup.dPort, port, lenPort)
SimplePup(pbi, 100, 300, InstallDir)
] repeat

//-----------------------------------------------------------------------------------------
and InstallDir(pbi, nil) = valof
//-----------------------------------------------------------------------------------------
[
if pbi>>PBI.pup.type eq ptBootDirReply then
   [
   let ptr, maxPtr = 1, (pbi>>PBI.pup.length-pupOvBytes)/2
   while ptr ls maxPtr do
      [
      let bfd = lv pbi>>PBI.pup.words↑ptr
      let name = lv bfd>>BFD.name
      let length = name>>String.length
      if StringCompare(name, ".boot", length-4) eq 0 then
         LoadKT(cmdKT, name, bfd>>BFD.bfn, lv pbi>>PBI.pup.sPort, lv bfd>>BFD.date)
      if StringCompare(name, ".eb", length-2) eq 0 then
         LoadKT(ebKT, name, bfd>>BFD.bfn, lv pbi>>PBI.pup.sPort, lv bfd>>BFD.date)
      ptr = ptr + 4 + length/2
      ]
   ]
resultis true
]

//-----------------------------------------------------------------------------------------
and LoadKT(kt, name, bfn,  port, date) be
//-----------------------------------------------------------------------------------------
[
let kte = LookupKeyword(kt, name)
if kte ne 0 & DoubleUsc(date, lv kte>>KTE.date) gr 0 then
   [ DeleteKeyword(kt, name); kte = 0 ]
if kte eq 0 then
   [
   kte = InsertKeyword(kt, name)
   kte>>KTE.bfn = bfn
   kte>>KTE.local = port eq 0 & date eq 0
   MoveBlock(lv kte>>KTE.port, port, 3)
   MoveBlock(lv kte>>KTE.date, date, 2)
   ]
]

//-----------------------------------------------------------------------------------------
and DoubleUsc(lvA, lvB) =
//-----------------------------------------------------------------------------------------
// lvA and lvB are the addresses of two 32-bit operands Returns:
//	-1 if A < B
//	 0 if A = B
//	 1 if A > B
(table
   [
    41003b	//	sta 0 3 2	; lvA
    45002b	//	sta 1 2 2	; lvB
    23003b	//	lda 0 @3 2	; A high part
    27002b	//	lda 1 @2 2	; B high part
   106414b	//	se 0 1		; A, B
      405b	//	 jmp dusc1
    11003b	//	isz 3 2		; lvA
    11002b	//	isz 2 2		; lvB
    23003b	//	lda 0 @3 2	; A low part
    27002b	//	lda 1 @2 2	; B low part
   106433b	// dusc1: sleu 0 1	; A, B
      405b	//	 jmp gr		; A > B
   106414b	//	se 0 1		; A, B
   102001b	//	 mkminusone 0 0 skp	; A < B
   102460b	//	mkzero 0 0	; A = B
     1401b	//	jmp 1 3
   102520b	// gr:	mkone 0 0
     1401b	//	jmp 1 3
   ])(lvA, lvB)

//-----------------------------------------------------------------------------------------
and GetName() be  //a context
//-----------------------------------------------------------------------------------------
[
let ndb = ndbQ!0; Block() repeatwhile ourName ne 0 % ndb>>NDB.localNet eq 0
let pbi = Allocate(sysZone, lenPBI); Zero(pbi, lenPBI)
pbi>>PBI.pup.type = ptAddressLookup
pbi>>PBI.pup.length = pupOvBytes+6
pbi>>PBI.pup.dPort.socket↑2 = psMiscServ
let port = lv pbi>>PBI.pup.bytes↑1
port>>Port.net = ndb>>NDB.localNet
port>>Port.host = ndb>>NDB.localHost
SimplePup(pbi, 100, 500, InstallName)
Free(sysZone, pbi)
] repeat

//-----------------------------------------------------------------------------------------
and InstallName(pbi, nil) = valof
//-----------------------------------------------------------------------------------------
[
switchon pbi>>PBI.pup.type into
   [
   case ptAddressReply:
      [
      let numChars = pbi>>PBI.pup.length - pupOvBytes
      ourName = Allocate(sysZone, numChars/2+1)
      for i = 1 to numChars do
         ourName>>String.char↑i = pbi>>PBI.pup.bytes↑i
      ourName>>String.length = numChars
      resultis false
      ]
   case ptAddressError:
      [
      ourName = -1
      resultis false
      ]
   default: resultis true
   ]
]

//-----------------------------------------------------------------------------------------
and PrintName(stream) be
//-----------------------------------------------------------------------------------------
[
test ourName eq 0 % ourName eq -1
   ifso PutTemplate(stream, "[$O#$O#]", (ndbQ!0)>>NDB.localNet, (ndbQ!0)>>NDB.localHost)
   ifnot PutTemplate(stream, "[$S]", ourName)
]

//-----------------------------------------------------------------------------------------
and PrintPort(stream, port) be
//-----------------------------------------------------------------------------------------
[
PutTemplate(stream, "$UO#$UO#", port>>Port.net, port>>Port.host)
if port>>Port.socket↑1 ne 0 % port>>Port.socket↑2 ne 0 then
   PutTemplate(stream, "$EUO", lv port>>Port.socket)
]

//-----------------------------------------------------------------------------------------
and LoadMicrocode() be
//-----------------------------------------------------------------------------------------
[
Wss(kbdCS, " from file ")
let kte = GetKeyword(kbdCS, ebKT)
Closes(cmdDsp)

let soc = vec lenEFTPSoc
OpenEFTPSoc(soc, 0, lv kte>>KTE.port)

// ought to do some buffer bounds checking, and check file checksum
let bytePtr = nil
   [
   let pbi = GetPBI(soc)
   pbi>>PBI.pup.id↑2 = kte>>KTE.bfn
   CompletePup(pbi, ptBootFileReq, pupOvBytes)
   bytePtr = ReceiveEFTPBlock(soc, buf, 100) // 1 sec
   ] repeatuntil bytePtr gr 0

let bytes = nil
   [
   bytes = ReceiveEFTPBlock(soc, buf+bytePtr rshift 1, 1000)  // 10 sec
   bytePtr = bytePtr + bytes
   ] repeatuntil bytes le 0

CloseEFTPSoc(soc)

let ptr = buf + 256
if eng eq 4 then
   [  // magic incantation from HGM
   ptr = ptr -1
   ptr!0 = 0
   ]
(table [ 61036b; 1401b ])(ptr, true)  // load ram and boot
]

//-----------------------------------------------------------------------------------------
and Where() be
//-----------------------------------------------------------------------------------------
[
Wss(kbdCS, " (is file) ")
let kte = GetKeyword(kbdCS, cmdKT, true)
if kte eq 0 then [ Resets(kbdCS); kte = GetKeyword(kbdCS, ebKT) ]
let pbi = Allocate(sysZone, lenPBI); Zero(pbi, lenPBI)
pbi>>PBI.pup.type = ptBootDirReq
pbi>>PBI.pup.length = pupOvBytes
pbi>>PBI.pup.dPort.socket↑2 = psMiscServ
SimplePup(pbi, 1000, 250, WhereMatch, kte!-1)
Free(sysZone, pbi)
]

//-----------------------------------------------------------------------------------------
and WhereMatch(pbi, string) = valof
//-----------------------------------------------------------------------------------------
[
if pbi>>PBI.pup.type eq ptBootDirReply then
   [
   let ptr, maxPtr = 1, (pbi>>PBI.pup.length-pupOvBytes)/2
   while ptr ls maxPtr do
      [
      let bfd = lv pbi>>PBI.pup.words↑ptr
      let name = lv bfd>>BFD.name
      let length = name>>String.length
      if StringCompare(string, name) eq 0 then
         [
         let utv = vec 7; UNPACKDT(lv bfd>>BFD.date, utv)
         PutTemplate(cmdDsp, "*N[$P] boot file number $UO, created $P",
          PrintPort, lv pbi>>PBI.pup.sPort, bfd>>BFD.bfn, WRITEUDT, utv)
         ]
      ptr = ptr + 4 + length/2
      ]
   ]
resultis true
]

//-----------------------------------------------------------------------------------------
and SimplePup(outPbi, retransTO, totalTO, proc, arg) be
//-----------------------------------------------------------------------------------------
[
let soc = vec lenPupSoc; OpenLevel1Socket(soc)
let q = vec 1; q!0 = 0; outPbi>>PBI.queue = q
let rTimer = nil; SetTimer(lv rTimer, 0)
let tTimer = nil; SetTimer(lv tTimer, totalTO)
let continue = true
   [
   if TimerHasExpired(lv rTimer) then
      [
      outPbi>>PBI.socket = soc
      CompletePup(outPbi)
      SetTimer(lv rTimer, retransTO)
      ]
   Block() repeatuntil soc>>PupSoc.iQ.head ne 0 %
    TimerHasExpired(lv rTimer) % TimerHasExpired(lv tTimer)
   if TimerHasExpired(lv tTimer) break
   let inPbi = Dequeue(lv soc>>PupSoc.iQ); if inPbi ne 0 then
      [
      continue = proc(inPbi, arg)
      ReleasePBI(inPbi)
      ]
   ] repeatwhile continue
CloseLevel1Socket(soc)
]

//-----------------------------------------------------------------------------------------
and NetBoot() be
//-----------------------------------------------------------------------------------------
[
Wss(kbdCS, " (file named) ")
let kte = GetKeyword(kbdCS, cmdKT)
if kte>>KTE.local then [ Wss(kbdCS, " - NetExec command; not a boot file"); return ]
Wss(kbdCS, " (from server) ")
let server = GetString(kbdCS)
if server>>String.char↑1 eq $[ then
   [
   for i = 2 to server>>String.length-1 do
      server>>String.char↑(i-1) = server>>String.char↑i
   server>>String.length = server>>String.length -2
   ]
let dPort = vec lenPort
if GetPartner(server, kbdCS, dPort, 0, 4) then MyEtherBoot(kte>>KTE.bfn, dPort)
Free(sysZone, server)
]

//-----------------------------------------------------------------------------------------
and MyEtherBoot(bfn, dPort; numargs na) be
//-----------------------------------------------------------------------------------------
[
let mayDayPup = mayDayPacket+2
mayDayPup>>Pup.id↑2 = bfn
if na gr 1 then
   [
   let rte = LocateNet(dPort>>Port.net)
   if rte eq 0 then [ Dismiss(150); rte = LocateNet(dPort>>Port.net) ]
   let pdh = rte eq 0? 0, (rte>>RTE.hops eq 0? dPort>>Port.host, rte>>RTE.host)
   mayDayPacket!0 = pdh lshift 8
   MoveBlock(lv mayDayPup>>Pup.dPort, dPort, lenPort)
   mayDayPup>>Pup.sPort.net = (ndbQ!0)>>NDB.localNet
   ]
DisableInterrupts()
StartIO(3)
for i = 0 to 255 do 1!i = bootLoaderPacket!i  //can't call a procedure!
goto 6
]