// IFsNameLookup.bcpl -- Network directory lookup routines.
// Adapted from PupNetDirLookup.bcpl, but making use of the IFS
// name server facilities
// Copyright Xerox Corporation 1979, 1980, 1981, 1983

// Last modified September 18, 1983  2:18 PM by Taft

get "Pup0.decl"
get "Pup1.decl"
get "IfsMisc.decl"
get "IfsName.decl"

external
[
// outgoing procedures
EnumeratePupAddresses; PrintPort

// incoming procedures
NameLookup; AddressLookup; PutTemplate
OpenLevel1Socket; CloseLevel1Socket; CompletePup
GetPBI; ReleasePBI; AppendStringToPup; LocateNet
SetTimer; TimerHasExpired; Block; Dismiss; Dequeue; Min
Wss; SetBlock; MoveBlock; Zero; DefaultArgs; Allocate; Free

// incoming statics
sysZone
]

manifest maxNPorts = 35  // maximum number of ports we'll consider in answer

//----------------------------------------------------------------------------
let EnumeratePupAddresses(name, filter, arg, dontCheckRT; numargs na) = valof
//----------------------------------------------------------------------------
// An exact replacement for the Pup package procedure of the same name.
// Evaluates name to one or more addresses.  For each address, calls
// filter(port, arg), where port is that address and arg is the arg passed
// to EnumeratePupAddresses.  If filter returns true, enumeration stops; if
// false, enumeration continues.  Ordinarily the addresses are enumerated in
// order of increasing distance away from here (hop count), and inaccessible
// addresses are omitted; however, if dontCheckRT is supplied and true,
// the addresses are not sorted and are all returned.
// Filter defaults to TakeTheFirst, which treats arg as a pointer to a Port
// and copies the first Pup address to that port, discarding the rest.
// Returns zero if successful and an error code (from IfsName.decl)
// if unsuccessful.
[
DefaultArgs(lv na, -1, TakeTheFirst, 0, false)

let ports = Allocate(sysZone, maxNPorts*lenPort)
let result = valof
   [
   let nPorts = NameLookup(name, ports, maxNPorts*lenPort)
   if nPorts ge ecNameFirst then
      [ // failed, see why
      unless nPorts eq ecNameServerDisabled resultis nPorts
   
      // Reason for failure was that the local name server was locked.
      // Try asking some other name server before giving up.
      nPorts = ecNoServerResponded
      let soc = vec lenPupSoc-1
      // Default transient local socket, zero foreign socket
      OpenLevel1Socket(soc, 0, 0, true)
      soc>>PupSoc.frnPort.socket↑2 = socketMiscServices
      for try = 1 to 20 do
         [
         let pbi = GetPBI(soc, true)  // try to avoid pbi deadlock
         if pbi ne 0 then
            [
            AppendStringToPup(pbi, 1, name)
            pbi>>PBI.allNets = true  // send on all directly connected nets
            CompletePup(pbi, ptNameLookup)
            ]
         let wait = nil; SetTimer(lv wait, 100)  // 1 sec
            [ // repeat
            Block()
            pbi = Dequeue(lv soc>>PupSoc.iQ)
            if pbi ne 0 then
               [
               switchon pbi>>PBI.pup.type into
                  [
                  case ptNetDirError:
                     nPorts = ecNameNotFound
                     endcase
                  case ptNameReply:
                     nPorts = Min((pbi>>PBI.pup.length-pupOvBytes)/6,
                      maxNPorts)
                     MoveBlock(ports, lv pbi>>PBI.pup.words, nPorts*lenPort)
                     endcase
                  ]
               ReleasePBI(pbi)
               if nPorts ne ecNoServerResponded break
               ]
            ] repeatuntil TimerHasExpired(lv wait)
         if nPorts ne ecNoServerResponded break
         ]
      CloseLevel1Socket(soc)
      if nPorts ge ecNameFirst resultis nPorts
      ]

// EnumeratePupAddresses (cont'd)

   // Obtain hop counts for sorting
   let hopsVec = vec maxNPorts-1
   SetBlock(hopsVec, (dontCheckRT? 0, maxHops+1), nPorts)
   let nRoutesFound = 0
   test dontCheckRT
      ifso  // Return all the ports that we got
         nRoutesFound = nPorts
      ifnot  // Look up route for each port
         for try = 0 to 20 do
            [
            // Even tries just look in the routing table; odd tries initiate probes.
            // The purpose of this dance is to get all the information we can from the
            // existing routing table before initiating probes that might displace
            // useful entries already there.  This can happen if the name we are looking
            // up maps to more networks than there are entries in the routing cache.
            let dontProbe = (try & 1) eq 0
            for i = 0 to nPorts-1 do
               if hopsVec!i gr maxHops then
                  [
                  let rte = LocateNet((ports+i*lenPort)>>Port.net, dontProbe)
                  if rte ne 0 then
                     [
                     hopsVec!i = rte>>RTE.hops
                     if hopsVec!i le maxHops then nRoutesFound = nRoutesFound+1
                     ]
                  ]
            if nRoutesFound eq nPorts % (nRoutesFound gr 0 & try ge 4) break
            unless dontProbe do Dismiss(100)
            ]
   
   // Emit answers in increasing order of hop count, being careful to maintain
   // the existing order of addresses at equal distances.
   // This is effectively an n↑2 sort, but n is small, right?
   let hops = 0
   while hops le maxHops do
      [
      let nextHops = maxHops+1
      for i = 0 to nPorts-1 do
         [
         if hopsVec!i eq hops & filter(ports+i*lenPort, arg) then
            [ nextHops = maxHops+1; break ]
         if hopsVec!i gr hops then nextHops = Min(nextHops, hopsVec!i)
         ]
      hops = nextHops
      ]
   
   resultis nRoutesFound eq 0? ecCantGetThere, 0
   ]

Free(sysZone, ports)
resultis result
]

//----------------------------------------------------------------------------
and TakeTheFirst(argPort, resultPort) = valof
//----------------------------------------------------------------------------
[
MoveBlock(resultPort, argPort, lenPort)
resultis true
]

//----------------------------------------------------------------------------
and PrintPort(stream, port) be
//----------------------------------------------------------------------------
// Prints a port as a name expression if possible, else as net#host#socket.
[
if AddressLookup(stream, port) ne 0 then
   [
   PutTemplate(stream, "$O#$O#", port>>Port.net, port>>Port.host)
   if port>>Port.socket↑1 ne 0 % port>>Port.socket↑2 ne 0 then
      PutTemplate(stream, "$UEO", lv port>>Port.socket)
   ]
]