// PupNameLookup.bcpl  - Network directory lookup routines.
// requires functions up to and including Pup level 1
// Copyright Xerox Corporation 1979, 1981

// Last modified November 21, 1981  11:17 AM by Taft

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

external
[
// outgoing procedures
GetPartner; RequestNameLookup; ParseAddressConst

// incoming procedures
OpenLevel1Socket; CloseLevel1Socket; CompletePup
GetPBI; ReleasePBI; AppendStringToPup; LocateNet
SetTimer; TimerHasExpired; Block; Dismiss; Dequeue; Min
Puts; Wss; MoveBlock; Zero

// incoming statics
pupRT
]

manifest
[
psMiscServ = 4		// well-known socket
ptNetDirLookup = 220b
ptNetDirReply = 221b
ptNetDirError = 222b
]

//----------------------------------------------------------------------------
let GetPartner(name, stream, port, s1, s2; numargs na) = valof
//----------------------------------------------------------------------------
// stream is an output character stream.  zero if not there.
// name is the name to lookup - a bcpl string.
// port is a pointer to a 3 word area where the answer goes.
// s1 and s2 are put into the socket field of port if the
//  answer has zero there.  These may be omitted.
[
let portVec = vec 10*lenPort
let nPorts = 1
unless ParseAddressConst(name, portVec) do
   [
   nPorts = RequestNameLookup(name, stream, portVec, 10*lenPort)
   if nPorts eq 0 resultis false
   ]

// Attempt to select the address for which we have the best route,
// and initiate attempts to locate a route if we don't have any.
for try = 1 to 10 do
   [
   let bestHops = maxHops+1
   let curPort = portVec
   let cacheOK = true
   for i = 1 to nPorts do
      [
      let rte = LocateNet(curPort>>Port.net)  // initiates probe if not found
      test rte eq 0
         ifso cacheOK = false  // don't have all required entries in RT yet
         ifnot if rte>>RTE.hops ls bestHops then
            [
            bestHops = rte>>RTE.hops
            MoveBlock(port, curPort, lenPort)
            ]
      curPort = curPort+lenPort
      ]
   if bestHops le maxHops & (cacheOK % try gr 1) then
      [
      // Found a route.  Now default socket number if necessary
      if na ge 5 & port>>Port.socket↑1 eq 0 & port>>Port.socket↑2 eq 0 then
         [  // no socket number in result, substitute default
         port>>Port.socket↑1 = s1
         port>>Port.socket↑2 = s2
         ]
      resultis true
      ]
   Dismiss(100)
   ]

if stream ne 0 then Wss(stream, "Can't get there from here")
resultis false
]

//----------------------------------------------------------------------------
and RequestNameLookup(name, stream, resultVec, lenResultVec) = valof
//----------------------------------------------------------------------------
// lenResultVec should be the length of resultVec in words.
// Stores the result ports in resultVec and returns the number of ports
// if successful.  Outputs an error message to stream and returns zero
// if unsuccessful.
[
let soc = vec lenPupSoc-1
// Default transient local port, zero foreign port
OpenLevel1Socket(soc, 0, 0, true)
soc>>PupSoc.frnPort.socket↑2 = psMiscServ
for try = 1 to 30 do
   [
   let pbi = GetPBI(soc)
   AppendStringToPup(pbi, 1, name)
   pbi>>PBI.allNets = true  //send on all directly connected nets
   CompletePup(pbi, ptNetDirLookup)
   let wait = nil; SetTimer(lv wait, 100)  //1 sec
   until TimerHasExpired(lv wait) do
      [
      Block()
      pbi = Dequeue(lv soc>>PupSoc.iQ)
      if pbi ne 0 then
         [
         let res = -1
         switchon pbi>>PBI.pup.type into
            [
            case ptNetDirError:
               [
               if stream ne 0 then
                  for i = 1 to pbi>>PBI.pup.length-pupOvBytes do
                     Puts(stream, pbi>>PBI.pup.bytes↑i)
               res = 0
               endcase
               ]
            case ptNetDirReply:
               [
               let nWords =
                Min((pbi>>PBI.pup.length-pupOvBytes) rshift 1, lenResultVec)
               MoveBlock(resultVec, lv pbi>>PBI.pup.words, nWords)
               res = nWords/3
               endcase
               ]
            ]
         ReleasePBI(pbi)
         if res ne -1 then
            [ CloseLevel1Socket(soc); resultis res ]
         ]
      ]
   ]
if stream ne 0 then Wss(stream, "No name lookup server responded")
CloseLevel1Socket(soc)
resultis false
]

//----------------------------------------------------------------------------
and ParseAddressConst(string, port) = valof
//----------------------------------------------------------------------------
// Parses a network address constant of the form
//	net#host#sock and sets port accordingly
// Returns:
//	true if string had correct syntax
//	false otherwise
[
Zero(port, lenPort)
let c, len = 0, string>>String.length
   [
   let char = nil
      [
      c = c+1; if c gr len resultis true
      char = string>>String.char↑c
      ] repeatwhile char eq $*s
   while char ge $0 & char le $7 do
      [
      if (port>>Port.socket↑1 & #160000) ne 0 resultis false
      port>>Port.socket↑1 = port>>Port.socket↑1 lshift 3 +
         port>>Port.socket↑2 rshift 13
      port>>Port.socket↑2 = port>>Port.socket↑2 lshift 3 + char-$0
      c = c+1; if c gr len resultis true
      char = string>>String.char↑c
      ]
   while char eq $*s do
      [
      c = c+1; if c gr len resultis true
      char = string>>String.char↑c
      ]
   if char ne $# % port>>Port.net ne 0 % port>>Port.socket↑1 ne 0 %
    (port>>Port.socket↑2 & #177400) ne 0 resultis false
   port>>Port.net = port>>Port.host
   port>>Port.host = port>>Port.socket↑2
   port>>Port.socket↑1 = 0; port>>Port.socket↑2 = 0
   ] repeat
]