-- File: NameResImpl.mesa - last edit:
-- JAV                 26-Aug-87 11:43:20
-- AOF                  1-Apr-87 12:39:11

-- Copyright (C) 1987 by Xerox Corporation. All rights reserved.

-- This software was produced by the University of Southern California (USC)
-- Information Sciences Institute (ISI).

-- USC-ISI does not assume any responsibility for the correctness,
-- performance, or use of this sotfware.  

-- Distribution of this software is limited by agreement between USC-ISI
-- and the XEROX Corporation.

DIRECTORY
  ArpaAddressTranslation USING [
    AddressTableEntry, AddressTableLength, AppendINetAddress,
    AppendINetAddressAndGrow, AppendProtocol, DebugProc, defaultTimeout,
    HostTableEntry, HostTableLength, INetAddr, INetAddress,
    MailboxTableEntry, mailboxTableLength, NamesPerEntry,
    nullAddressDesc, nullHostDesc, nullINetAddress, nullMailboxDesc,
    nullServerDesc, PortType, QClass, QType, QueryHeader, ResponseType,
    ServerTableEntry, serverTableLength, ServerType],
  ArpaBuffer USING [Body, Buffer, ReturnBuffer],
  ArpaPort USING [
    AssignPort, Create, Delete, GetPacket, GetSendBuffer, Handle,
    PutPacket, SetIPLengths, SetUDPLength, SetWaitTime, Timeout, UDPHeaderBytes],
  ArpaPortInternal USING [
    AddrMatch, BuildMasks, GetArpaAddr, GetDomainNameServer],
  ArpaRouter USING [InternetAddress, Port, unknownInternetAddress],
  Environment USING [Byte],
  Format USING [Number, StringProc],
  Heap USING [systemZone],
  Inline USING [BITSHIFT, DBITSHIFT, HighByte, LowByte],
  Process USING [Detach],
  String USING [
    AppendChar, AppendDecimal, AppendString, AppendStringAndGrow,
    CopyToNewString, Empty, Equivalent, EquivalentSubString, FreeString,
    MakeString, Replace, SubStringDescriptor],
  System USING [
    AdjustGreenwichMeanTime, GetGreenwichMeanTime, GreenwichMeanTime,
    SecondsSinceEpoch];
  
NameResImpl: MONITOR 
  IMPORTS ArpaAddressTranslation, ArpaBuffer, ArpaPort, ArpaPortInternal, 
          ArpaRouter, Format, Heap, Inline, Process, String, System
  EXPORTS ArpaAddressTranslation =

  BEGIN
  OPEN ArpaAddressTranslation;

  MaxQuerySize: CARDINAL = 512;
  maxTries: CARDINAL = 4;
  maxReferrals: CARDINAL = 4;
  
  defaultNameServer: INetAddr ← nullINetAddress;
  myINetAddress: ArpaRouter.InternetAddress ← LOOPHOLE[nullINetAddress];
  
  --cache tables
  hostTable: PUBLIC ARRAY [0..HostTableLength) OF HostTableEntry ← ALL[nullHostDesc];
  serverTable: PUBLIC ARRAY [0..serverTableLength) OF ServerTableEntry ← ALL[nullServerDesc];
  addressTable: PUBLIC ARRAY [0..AddressTableLength) OF AddressTableEntry ← ALL[nullAddressDesc];
  mailboxTable: PUBLIC ARRAY [0..mailboxTableLength) OF MailboxTableEntry ← ALL[nullMailboxDesc];
  NoAnswer: PUBLIC SIGNAL = CODE;
  ErrorInReply: PUBLIC SIGNAL = CODE;
  
  requestCount: CARDINAL ← 0;  -- used for making up unique id  

  UDPBuffer: TYPE = LONG POINTER TO UDPBufferObject;
  UDPBufferObject: TYPE = MACHINE DEPENDENT RECORD [
    source(0:0..15):  PortType ← null,
    dest(1:0..15):  PortType ← null,
    length(2:0..15):  [0..177777B]   ← 0,
    checksum(3:0..15):  [0..177777B]   ← 0,
    data(4):  PACKED ARRAY [0..0) OF Environment.Byte ← NULL
    ];

--****************************************************************************--

  GetWord: PROCEDURE [data: LONG POINTER TO PACKED ARRAY [0..0) OF Environment.Byte, index: LONG POINTER TO CARDINAL] RETURNS [i: CARDINAL] =
    BEGIN
      i ← Inline.BITSHIFT[data[index↑],8] + data[index↑+1];
      index↑ ← index↑ + 2;
    END; -- GetWord

  GetLong: PROCEDURE [data: LONG POINTER TO PACKED ARRAY [0..0) OF Environment.Byte, index: LONG POINTER TO CARDINAL] RETURNS [i: LONG UNSPECIFIED] =
    BEGIN
      i ← Inline.DBITSHIFT[data[index↑], 24] + Inline.DBITSHIFT[data[index↑+1],16] + Inline.DBITSHIFT[data[index↑+2],8] + data[index↑+3];
      index↑ ← index↑ + 4;
    END; -- GetLong
    
  BuildRequest: PROCEDURE [domain: LONG STRING, type: QType, dest: INetAddr, destPort: PortType, b: ArpaBuffer.Buffer] = 
    BEGIN
    -- UDP Data
    bufferBody: ArpaBuffer.Body = b.arpa;
    {
    OPEN ip: bufferBody.ipHeader, udp: bufferBody.user;
    
    hPtr: LONG POINTER TO QueryHeader ←  LOOPHOLE[@udp.bytes];
    index, countIndex, count: CARDINAL ← 0;
    
      udp.destinationPort ← LOOPHOLE[destPort];
      
      ip.destination ← LOOPHOLE[dest];
      ip.protocol ← userDatagram;
      ip.service ← LOOPHOLE[0];
      ip.identification ← 0;
      ip.lifetime ← 15B;
      
      -- make up request
      hPtr↑ ← [requestCount, query, query, FALSE, FALSE, FALSE, FALSE, 0, okay, 1, 0, 0, 0];
      index ← 2 * SIZE[QueryHeader];
      countIndex ← index; -- fill in count later
      FOR i:CARDINAL IN [0..domain.length) DO
	IF domain.text[i] = '. THEN {
	  udp.bytes[countIndex] ← count;
	  countIndex ← index ← index + 1;
	  count ← 0}
	ELSE {
	  index ← index + 1;
	  udp.bytes[index] ← LOOPHOLE[domain.text[i]];
	  count ← count + 1};
      ENDLOOP;
      IF count # 0 THEN {
	udp.bytes[countIndex] ← count;
	index ← index + 1;
	udp.bytes[index] ← 0;
	index ← index + 1}
      ELSE
        IF udp.bytes[index-1] # 0 THEN index ← index + 1;
      udp.bytes[index] ← Inline.HighByte[type];
      udp.bytes[index+1] ← Inline.LowByte[type];
      udp.bytes[index+2] ← Inline.HighByte[QClass.internet];
      udp.bytes[index+3] ← Inline.LowByte[QClass.internet];
      index ← index + 4;
      
      ArpaPort.SetUDPLength[bufferBody, index];  --Sets the udp length.
      ArpaPort.SetIPLengths[bufferBody, 0, index + ArpaPort.UDPHeaderBytes];
      };
    END; -- BuildRequest

  RemoteRequest: PROCEDURE [domain: LONG STRING, type: QType, server: ServerType, udpH: ArpaPort.Handle, debugProc: DebugProc ← NIL] RETURNS [b: ArpaBuffer.Buffer ← NIL] =
  BEGIN 
    IF debugProc # NIL THEN
      BEGIN
	tempString: LONG STRING ← [80];
	String.AppendString[tempString, " Sending "L];
	String.AppendString[tempString, 
	SELECT type FROM
	  address => "ADDRESS"L,
	  mb => "MAILBOX"L,
	  ns => "NAME SERVER"L,
	  ptr => "POINTER"L,
	  wks => "WELL KNOWN SERVICE"L,
	ENDCASE => "UNKNOWN"L];
	String.AppendString[tempString, " query to "L];
	AppendINetAddress[tempString, server.host];
	String.AppendString[tempString, ", port = "L];
	String.AppendDecimal[tempString, LOOPHOLE[server.port]];
	debugProc[tempString];
      END;
    
    -- send request
    FOR i: CARDINAL IN [0..maxTries) WHILE b = NIL DO
    sendBuf: ArpaBuffer.Buffer ← ArpaPort.GetSendBuffer[udpH];
      BEGIN ENABLE UNWIND => IF sendBuf # NIL THEN ArpaBuffer.ReturnBuffer[sendBuf];
	IF debugProc # NIL THEN debugProc["."L];
	BuildRequest[domain, type, server.host, server.port, sendBuf];	
	ArpaPort.PutPacket[udpH, sendBuf];
	sendBuf ← NIL;
	b ← ArpaPort.GetPacket[udpH ! ArpaPort.Timeout => LOOP];
      END;
    ENDLOOP;
    requestCount ← requestCount + 1;
    IF debugProc # NIL THEN debugProc["\n"L];
    IF b = NIL THEN SIGNAL NoAnswer; 
  END; --RemoteRequest
  
--****************************************************************************--

  UnpackName: PROCEDURE [p: UDPBuffer, i: CARDINAL, s: LONG POINTER TO LONG STRING] RETURNS [newIndex: CARDINAL] =
    BEGIN
      UNTIL p.data[i] = 0 OR p.data[i] >= 300B OR i >= MaxQuerySize DO
	FOR j: CARDINAL IN [i+1..i+p.data[i]] DO
	  s↑.text[s↑.length] ← LOOPHOLE[p.data[j]];
	  s↑.length ← s↑.length + 1;
	ENDLOOP;
	i ← i + p.data[i] + 1;
	s↑.text[s↑.length] ← '. ;
	s↑.length ← s↑.length + 1;
      ENDLOOP;
      IF i < MaxQuerySize THEN 
	IF p.data[i] >= 300B THEN
	  BEGIN
	    tempIndex: CARDINAL;
	    tempIndex ← GetWord[@p.data, @i];
	    tempIndex ← tempIndex - 140000B;
	    IF (s↑.maxlength - s↑.length) > p.data[tempIndex] THEN
	    tempIndex ← UnpackName[p, tempIndex, s];
	  END
	ELSE
	  BEGIN
	    i ← i + 1;  -- 1 byte for the 0 (end of name)
	    IF s↑.length =0 THEN {
	      s↑.text[s↑.length] ← '.;
	      s↑.length ← s↑.length + 1 };
	  END;
      RETURN[i];
    END; -- UnpackName

  UpdateTime: PROCEDURE [timePtr: LONG POINTER TO System.GreenwichMeanTime, newTime: System.GreenwichMeanTime] =
    BEGIN
    IF timePtr↑ = 0 OR System.SecondsSinceEpoch[timePtr↑] < System.SecondsSinceEpoch[newTime] THEN
      timePtr↑ ← newTime;
    END; -- UpdateTime

  AddStringEntry: PROCEDURE [arrayPtr: LONG POINTER TO ARRAY [0..NamesPerEntry) OF LONG STRING, countPtr: LONG POINTER TO CARDINAL, str: LONG STRING] =
  BEGIN
    IF countPtr↑ < NamesPerEntry THEN
    BEGIN
      FOR i: CARDINAL IN [0..countPtr↑) DO
	IF String.Equivalent[str, arrayPtr[i]] THEN EXIT;
	REPEAT FINISHED => 
	BEGIN  -- no matching entry
	  arrayPtr[countPtr↑] ← String.CopyToNewString[str, Heap.systemZone];
	  countPtr↑ ← countPtr↑ + 1;
	END;
      ENDLOOP;
    END;
  END; -- AddStringEntry

  AddAddressEntry: PROCEDURE [arrayPtr: LONG POINTER TO ARRAY [0..NamesPerEntry) OF INetAddr, countPtr: LONG POINTER TO CARDINAL, addr: INetAddr] =
    BEGIN
      IF countPtr↑ < NamesPerEntry THEN
	BEGIN
	  FOR i: CARDINAL IN [0..countPtr↑) DO
	    IF addr = arrayPtr[i] THEN EXIT;
	    REPEAT
	    FINISHED => BEGIN  -- no matching entry
	      arrayPtr[countPtr↑] ← addr; 
	      countPtr↑ ← countPtr↑ + 1;
	    END;
	  ENDLOOP;
	END;
    END; -- AddAddressEntry

  ProcessReply: PROCEDURE [b: ArpaBuffer.Buffer, domain: LONG STRING, currentTime: System.GreenwichMeanTime, debugProc: DebugProc ← NIL] RETURNS [result: ResponseType] =
    BEGIN 
    OPEN udp: b.arpa.user;
    
    pUDP: UDPBuffer ← LOOPHOLE[@udp];
    hPtr: LONG POINTER TO QueryHeader ← LOOPHOLE[@udp.data];
  
      -- check rcode and counts in header
      IF hPtr.qr = response AND hPtr.rcode = okay THEN
	BEGIN ENABLE UNWIND => ArpaBuffer.ReturnBuffer[b];
	domainAddress: INetAddr;
	queryType: QType;
	out: LONG STRING ← [120];
	index: CARDINAL ← 2 * SIZE[QueryHeader];
	
	-- check the query in the response
	FOR i:CARDINAL IN [0..hPtr.queryCount) DO
	  index ← UnpackName[pUDP, index, @out];
	  IF NOT String.Equivalent[domain, out] THEN SIGNAL ErrorInReply;
	  queryType ← LOOPHOLE[GetWord[@pUDP.data, @index]];
	  IF queryType = ptr THEN
	    BEGIN
	      temp: INetAddr;
		out.length ← out.length -9; --cut off .IN-ADDR.
		temp ← INetAddress[out];
		domainAddress.d ← temp.a;
		domainAddress.c ← temp.b;
		domainAddress.b ← temp.c;
		domainAddress.a ← temp.d;
	    END;		     
	  index ← index + 2;  -- 2 bytes for the class
	ENDLOOP;
	   
	FOR i:CARDINAL IN [0..(hPtr.answerCount+hPtr.nsCount+hPtr.arCount)) DO
	  rdlen: CARDINAL ← 0;
	  type: QType;
	  ttl: System.GreenwichMeanTime ← [0];
	  time: LONG CARDINAL;
	  
	  out.length ← 0;
	  index ← UnpackName[pUDP, index, @out];
	  type ← LOOPHOLE[GetWord[@pUDP.data, @index]];
	  index ← index + 2;  -- 2 bytes for the class
	  time ← GetLong[@pUDP.data, @index];
	  ttl ← System.AdjustGreenwichMeanTime[currentTime, MIN[17777777777B, time]];
	  rdlen ← GetWord[@pUDP.data, @index];
  
	  SELECT type FROM
	    ns =>	-- reply to ns queryType, or referral 
	      BEGIN
	      server: LONG POINTER TO ServerTableEntry ← InitServerEntry[out, currentTime];
	      out.length ← 0;
	      index ← UnpackName[pUDP, index, @out];
	      IF debugProc # NIL AND queryType # ns THEN
		BEGIN
		  debugProc["   Received referral to "L];
		  debugProc[out];
		  debugProc["\n"];
		END;
	      AddStringEntry[@server.host, @server.hostCount, out]; 
	      UpdateTime[@server.time, ttl];
	      END;
  
	    cName => 
	      BEGIN
		fullName: LONG STRING ← [120];
		index ← UnpackName[pUDP, index, @fullName];
		SELECT queryType FROM
		  address, wks => 
		    BEGIN
		      host: LONG POINTER TO HostTableEntry ← InitHostEntry[@fullName, currentTime];
			AddStringEntry[@host.name, @host.nameCount, out]; 
			UpdateTime[@host.time, ttl];
		    END;
		  mb => 
		    BEGIN
		      mailbox: LONG POINTER TO MailboxTableEntry ← InitMailboxEntry[@fullName, currentTime];
			AddStringEntry[@mailbox.name, @mailbox.nameCount, out]; 
			UpdateTime[@mailbox.time, ttl];
		    END;
		ENDCASE; 
	      END;
  
	    mb => 
	      BEGIN
		mailbox: LONG POINTER TO MailboxTableEntry ← InitMailboxEntry[@out, currentTime];
		out.length ← 0;
		index ← UnpackName[pUDP, index, @out];
		String.Replace[@mailbox.host, out, Heap.systemZone];
		UpdateTime[@mailbox.time, ttl];
	      END;
	      
	    address => 
	      BEGIN
		addr: LONG POINTER TO AddressTableEntry;
		host: LONG POINTER TO HostTableEntry  ← InitHostEntry[@out, currentTime];
		address: INetAddr ← [pUDP.data[index], pUDP.data[index+1], pUDP.data[index+2], pUDP.data[index+3]];
		index ← index + 4;  -- 4 bytes for the internet address
		AddAddressEntry[@host.addr, @host.addrCount, address];
		UpdateTime[@host.time, ttl];
  
		addr ← InitAddressEntry[address, currentTime]; 
		FOR i: CARDINAL IN [0..host.nameCount) DO AddStringEntry[@addr.name, @addr.nameCount, host.name[i]]; ENDLOOP;
		UpdateTime[@addr.time, ttl];
	      END;
	      
	    ptr => 
	      IF String.Equivalent[domain, out] THEN
		BEGIN
		  addr: LONG POINTER TO AddressTableEntry ← InitAddressEntry[domainAddress, currentTime];
		  out.length ← 0;
		  index ← UnpackName[pUDP, index, @out];
		  AddStringEntry[@addr.name, @addr.nameCount, out];
		  UpdateTime[@addr.time, ttl];
		END;
	      
	    wks => BEGIN
	      addr: LONG POINTER TO AddressTableEntry;
	      host: LONG POINTER TO HostTableEntry ← InitHostEntry[@out, currentTime];
	      address: INetAddr ← [pUDP.data[index], pUDP.data[index+1], pUDP.data[index+2], pUDP.data[index+3]];
	      AddAddressEntry[@host.addr, @host.addrCount, address];
	      UpdateTime[@host.time, ttl];
  
	      addr ← InitAddressEntry[address, currentTime]; 
	      FOR i: CARDINAL IN [0..host.nameCount) DO AddStringEntry[@addr.name, @addr.nameCount, host.name[i]]; ENDLOOP;
	      UpdateTime[@addr.time, ttl];
  
	      IF debugProc # NIL THEN
		BEGIN
		stringProc: Format.StringProc = {String.AppendString[out, s]};
		out.length ← 0;
		String.AppendString[out, " For: "L];
		AppendINetAddress[out, address];
		String.AppendString[out, " ("L];
		AppendProtocol[out, LOOPHOLE[pUDP.data[index+4]]];
		String.AppendString[out, "), WKS bitmap: "L];
		debugProc[out];
		FOR i: CARDINAL IN [index+5..index+rdlen) DO
		  IF ((i-index+5) MOD 10) = 0 THEN
		    BEGIN
		      debugProc["\n"];
		      debugProc["                        "L];	-- 24 blanks
		    END;
		  out.length ← 0;
		  Format.Number[stringProc, pUDP.data[i], [8,TRUE,TRUE,3]];
		  String.AppendString[out,"B "L];
		  debugProc[out];
		  ENDLOOP;
		debugProc["\n"]; 
		END;
	      index ← index + rdlen;
	      END;
	    ENDCASE => index ← index + rdlen;
	    
	ENDLOOP;
	END;
    ArpaBuffer.ReturnBuffer[b];
    RETURN[hPtr.rcode];
    END;  --ProcessReply
  
--****************************************************************************--

  ClearHostEntry: PROCEDURE [host: LONG POINTER TO HostTableEntry] =    
    BEGIN
    IF host # NIL THEN
      BEGIN
      FOR n: CARDINAL IN [0..host.nameCount) DO
        IF host.name[n] # NIL THEN String.FreeString[Heap.systemZone, host.name[n]];
        ENDLOOP;
      host↑ ← nullHostDesc;
      END;
    END; -- ClearHostEntry

  ClearMailboxEntry: PROCEDURE [desc: LONG POINTER TO MailboxTableEntry] =    
    BEGIN
    IF desc # NIL THEN
      BEGIN
      FOR n: CARDINAL IN [0..desc.nameCount) DO
        IF desc.name[n] # NIL THEN String.FreeString[Heap.systemZone, desc.name[n]];
        ENDLOOP;
      String.FreeString[Heap.systemZone, desc.host];
      desc↑ ← nullMailboxDesc;
      END;
    END; -- ClearMailboxEntry

  ClearServerEntry: PROCEDURE [server: LONG POINTER TO ServerTableEntry] =    
    BEGIN
    IF server # NIL THEN
      BEGIN
      String.FreeString[Heap.systemZone, server.domain];
      FOR i: CARDINAL IN [0..server.hostCount) DO
        IF server.host[i] # NIL THEN String.FreeString[Heap.systemZone, server.host[i]];
        ENDLOOP;
      server↑ ← nullServerDesc;
      END;
    END; -- ClearServerEntry
    
  ClearAddressEntry: PROCEDURE [address: LONG POINTER TO AddressTableEntry] =    
    BEGIN
      IF address # NIL THEN
	BEGIN
	  FOR i: CARDINAL IN [0..address.nameCount) DO
	    IF address.name[i] # NIL THEN String.FreeString[Heap.systemZone, address.name[i]];
	  ENDLOOP;
	  address↑ ← nullAddressDesc;
	END;
    END; -- ClearAddressEntry
    
--****************************************************************************--
     
  FindHostEntry: PROCEDURE [domain: LONG POINTER TO LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO HostTableEntry ← NIL] = 
    BEGIN
      FOR i: CARDINAL IN [0..HostTableLength) DO
	FOR j: CARDINAL IN [0..hostTable[i].nameCount) DO
	  IF String.Equivalent[domain↑,hostTable[i].name[j]] THEN
	    IF hostTable[i].time # 0 AND (System.SecondsSinceEpoch[hostTable[i].time] >= System.SecondsSinceEpoch[currentTime]) THEN
	      RETURN[@hostTable[i]]
	    ELSE
	      EXIT;
	ENDLOOP;
      ENDLOOP;
    END; -- FindHostEntry
   
  FindMailboxEntry: PROCEDURE [domain: LONG POINTER TO LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO MailboxTableEntry ← NIL] = 
    BEGIN
    FOR i: CARDINAL IN [0..mailboxTableLength) DO
      FOR j: CARDINAL IN [0..mailboxTable[i].nameCount) DO
	IF String.Equivalent[domain↑,mailboxTable[i].name[j]] THEN
	  IF mailboxTable[i].time # 0 AND (System.SecondsSinceEpoch[mailboxTable[i].time] >= System.SecondsSinceEpoch[currentTime]) THEN
	    RETURN[@mailboxTable[i]]
	ELSE
	  EXIT;
      ENDLOOP;
    ENDLOOP;
    END; -- FindMailboxEntry

  FindAddress: PROCEDURE [address: INetAddr, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO AddressTableEntry ← NIL] =
    BEGIN
      FOR i: CARDINAL IN [0..AddressTableLength) DO
	IF addressTable[i].addr = address AND addressTable[i].time # 0 AND System.SecondsSinceEpoch[addressTable[i].time] >= System.SecondsSinceEpoch[currentTime] THEN
	RETURN[@addressTable[i]];
      ENDLOOP;
    END; -- FindAddress

  MatchingServer: PROCEDURE [domain: LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO ServerTableEntry ← NIL] =
    BEGIN
    FOR i: CARDINAL IN [0..serverTableLength) DO
      IF String.Equivalent[serverTable[i].domain, domain] 
	AND serverTable[i].time # 0 
	AND System.SecondsSinceEpoch[serverTable[i].time] >= System.SecondsSinceEpoch[currentTime] THEN
	  RETURN[@serverTable[i]];
    ENDLOOP;
    END; -- MatchingServer

  BestServer: PROCEDURE [domain: LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO ServerTableEntry ← NIL] =
  -- find the server from the cache (serverTable) for the longest domain name
  --   that matches the domain in question (Domain)
  BEGIN
  bestIndex, strLength: CARDINAL ← 0;
  FOR i: CARDINAL IN [0..serverTableLength) DO
    IF serverTable[i].domain # NIL AND serverTable[i].domain.length > strLength AND NOT (serverTable[i].time = 0 OR System.SecondsSinceEpoch[serverTable[i].time] < System.SecondsSinceEpoch[currentTime]) THEN 
    IF (serverTable[i].domain.length < domain.length AND domain.text[domain.length-serverTable[i].domain.length-1] = '.) OR serverTable[i].domain.length = domain.length THEN
      BEGIN
	domainSubStr: String.SubStringDescriptor ← [serverTable[i].domain, 0, serverTable[i].domain.length];
	tail: String.SubStringDescriptor ← [domain, (domain.length - serverTable[i].domain.length), serverTable[i].domain.length];
	IF String.EquivalentSubString[@tail, @domainSubStr] THEN
	  BEGIN
	    bestIndex ← i;
	    strLength ← serverTable[i].domain.length;
	  END;
      END;
  ENDLOOP;    
  IF strLength # 0 THEN
  desc ← @serverTable[bestIndex];
  END; -- BestServer

--****************************************************************************--

  InitMailboxEntry: PROCEDURE [domain: LONG POINTER TO LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO MailboxTableEntry ← NIL] = 
    BEGIN
      IF (desc ← FindMailboxEntry[domain, currentTime]) # NIL THEN RETURN;
      FOR j: CARDINAL IN [0..mailboxTableLength) DO
	IF mailboxTable[j].time = 0 OR System.SecondsSinceEpoch[mailboxTable[j].time] < System.SecondsSinceEpoch[currentTime] THEN 
	  BEGIN  -- found an empty entry
	    desc ← @mailboxTable[j];
	    ClearMailboxEntry[desc];
	    EXIT;
	  END;
	REPEAT
	FINISHED =>
	  BEGIN -- clear out the whole mailboxTable
	    desc ← @mailboxTable[0];
	    FOR k: CARDINAL IN [0..mailboxTableLength) DO ClearMailboxEntry[@mailboxTable[k]]; ENDLOOP;
	  END;
      ENDLOOP;
      -- fill in name
      desc.name[0] ← String.CopyToNewString[domain↑, Heap.systemZone];
      desc.nameCount ← 1;
    END; -- InitMailboxEntry

  InitHostEntry: PROCEDURE [domain: LONG POINTER TO LONG STRING,  currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO HostTableEntry ← NIL] = 
    BEGIN
      IF (desc ← FindHostEntry[domain, currentTime]) # NIL THEN  RETURN;
      FOR j: CARDINAL IN [0..HostTableLength) DO
	IF hostTable[j].time = 0 OR System.SecondsSinceEpoch[hostTable[j].time] < System.SecondsSinceEpoch[currentTime] THEN 
	  BEGIN  -- found an empty entry
	    desc ← @hostTable[j];
	    ClearHostEntry[desc];
	    EXIT;
	  END;
	REPEAT
	FINISHED =>
	  BEGIN -- clear out the whole hostTable
	    desc ← @hostTable[0];
	    FOR k: CARDINAL IN [0..HostTableLength) DO ClearHostEntry[@hostTable[k]]; ENDLOOP;
	  END;
      ENDLOOP;
      -- fill in name
      desc.name[0] ← String.CopyToNewString[domain↑, Heap.systemZone];
      desc.nameCount ← 1;
    END; --InitHostEntry

  InitAddressEntry: PROCEDURE [address: INetAddr, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO AddressTableEntry ← NIL] = 
    BEGIN
    -- find matching domain
    IF (desc ← FindAddress[address, currentTime]) # NIL THEN RETURN[desc];
    -- find unused entry
    FOR i: CARDINAL IN [0..serverTableLength) DO
      IF addressTable[i].time = 0 OR System.SecondsSinceEpoch[addressTable[i].time] < System.SecondsSinceEpoch[currentTime] THEN 
	BEGIN  -- found an empty entry
	  ClearAddressEntry[@addressTable[i]];
	  addressTable[i].addr ← address;
	  RETURN[@addressTable[i]];
	END;
      REPEAT
      FINISHED =>
	BEGIN -- clear out the whole serverTable
	FOR j: CARDINAL IN [0..AddressTableLength) DO ClearAddressEntry[@addressTable[j]]; ENDLOOP;
	addressTable[0].addr ← address;
	RETURN[@addressTable[0]];
	END;
    ENDLOOP;
    END; --InitAddressEntry

  InitServerEntry: PROCEDURE [domain: LONG STRING, currentTime: System.GreenwichMeanTime] RETURNS [desc: LONG POINTER TO ServerTableEntry ← NIL] = 
    BEGIN
    -- find matching domain
    IF (desc ← MatchingServer[domain, currentTime]) # NIL THEN RETURN[desc];
    -- find unused entry
    FOR i: CARDINAL IN [0..serverTableLength) DO
      IF serverTable[i].time = 0 OR System.SecondsSinceEpoch[serverTable[i].time] < System.SecondsSinceEpoch[currentTime] THEN 
	BEGIN  -- found an empty entry
	  ClearServerEntry[@serverTable[i]];
	  serverTable[i].domain ← String.CopyToNewString[domain, Heap.systemZone];
	  RETURN[@serverTable[i]];
	END;
      REPEAT
      FINISHED =>
	BEGIN -- clear out the whole serverTable
	  FOR j: CARDINAL IN [0..serverTableLength) DO ClearServerEntry[@serverTable[j]]; ENDLOOP;
	  serverTable[0].domain ← String.CopyToNewString[domain, Heap.systemZone];
	  RETURN[@serverTable[0]];
	END;
    ENDLOOP;
    END; --InitServerEntry

--****************************************************************************--

  Resolve: PROCEDURE [LookupEntry: PROCEDURE RETURNS [done: BOOLEAN], SendQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN], domain: LONG STRING, server: ServerType ← [nullINetAddress, domainNS], debugProc: DebugProc ← NIL, currentTime: System.GreenwichMeanTime] =
  BEGIN
  done: BOOLEAN ← FALSE;
  tries: CARDINAL ← 0;

  TryServerFromCache: PROCEDURE =
  BEGIN
  serverHost: LONG POINTER TO HostTableEntry ← NIL;
  previousServer, bestServer: LONG POINTER TO ServerTableEntry ← NIL;
    WHILE NOT done DO
      IF (bestServer ← BestServer[domain, currentTime]) = NIL OR (previousServer = bestServer) THEN 
        RETURN
      ELSE
	FOR i: CARDINAL IN [0..bestServer.hostCount) WHILE NOT done DO
	  IF (serverHost ← FindHostEntry[@bestServer.host[i], currentTime]) # NIL AND (tries ← tries + 1) <= maxReferrals THEN
	  BEGIN
	    IF debugProc # NIL THEN debugProc[" Cache:"L];
	    done ← SendQuery[[SelectLocalAddress[serverHost↑], domainNS] ! NoAnswer => CONTINUE];
	  END;
	ENDLOOP;
    previousServer ← bestServer;
    ENDLOOP;
  END; -- TryServerFromCache

  TryServerFromParam: PROCEDURE =
  BEGIN
    IF server.host # nullINetAddress AND (tries ← tries + 1) <= maxReferrals THEN
    BEGIN
      IF debugProc # NIL THEN debugProc[" Param:"L];
      done ← SendQuery[server];
    END;
  END; -- TryServerFromParam
  
  TryServerFromInit: PROCEDURE =
  BEGIN
    IF defaultNameServer = nullINetAddress THEN defaultNameServer ← LOOPHOLE[ArpaPortInternal.GetDomainNameServer[]]; 
    IF defaultNameServer # nullINetAddress AND (tries ← tries + 1) <= maxReferrals THEN
    BEGIN
      IF debugProc # NIL THEN debugProc[" Init:"L];
      done ← SendQuery[[defaultNameServer, domainNS] ! NoAnswer => CONTINUE];
    END;
  END; -- TryServerFromInit

  IF LookupEntry[] THEN RETURN;	-- is information already in cache
  TryServerFromParam[];	-- if server was specified, try it
  IF done OR tries >= maxReferrals THEN RETURN;
  IF domain # NIL THEN	-- try any applicable servers from cache
    BEGIN
      TryServerFromCache[];
      IF done OR tries >= maxReferrals THEN RETURN;
    END;
  TryServerFromInit[];	-- try default servers
  IF done OR tries >= maxReferrals THEN RETURN;
  IF domain # NIL THEN TryServerFromCache[];	-- in case default server made referral
  END; --Resolve

--****************************************************************************--

  SelectLocalAddress: PROCEDURE [host: HostTableEntry] RETURNS [address: INetAddr ← nullINetAddress] =
    BEGIN
    IF myINetAddress = LOOPHOLE[nullINetAddress] THEN
      myINetAddress ← ArpaPortInternal.GetArpaAddr[]; 
    IF host.addrCount # 0 THEN
      BEGIN
      FOR i: CARDINAL IN [0..host.addrCount) DO
        mask: ArpaRouter.InternetAddress ← ArpaPortInternal.BuildMasks[myINetAddress].netMask;
        IF ArpaPortInternal.AddrMatch[mask, myINetAddress, LOOPHOLE[host.addr[i]]] THEN
          RETURN[host.addr[i]];
      ENDLOOP;
      RETURN[host.addr[0]];
      END;
    END; --SelectLocalAddress

  INetAddressOrName: PUBLIC PROCEDURE [s: LONG STRING] RETURNS [address: INetAddr ← nullINetAddress] =
    BEGIN
    Later: PROCEDURE [domain: LONG STRING] ={
      [] ← SelectLocalAddress[ResolveHostName[domain]]};

    IF ~String.Empty[s] THEN
      BEGIN
      address ← INetAddress[s];
      IF ArpaPortInternal.GetDomainNameServer[] # ArpaRouter.unknownInternetAddress THEN {
        IF (address ← INetAddress[s]) = nullINetAddress THEN
          address ← SelectLocalAddress[ResolveHostName[domain: s<<, time: 2>>]];
	IF address = nullINetAddress THEN
	  Process.Detach[FORK Later[s]]};
      END;
    END; -- INetAddressOrName

  ResolveHostName: PUBLIC PROCEDURE [domain: LONG STRING, WKSFlag: BOOLEAN ← FALSE, useCache: BOOLEAN ← TRUE, server: ServerType ← [nullINetAddress, domainNS], time: CARDINAL ← defaultTimeout, debugProc: DebugProc ← NIL] RETURNS [desc: HostTableEntry ← nullHostDesc] =
    BEGIN
    domainStr: LONG STRING ← String.CopyToNewString[domain, Heap.systemZone, 1];
    currentTime: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    
      LookupHost: ENTRY PROCEDURE RETURNS [done: BOOLEAN ← FALSE] =
        BEGIN
        hostPtr: LONG POINTER TO HostTableEntry ← FindHostEntry[@domainStr, currentTime];
        IF done ← (hostPtr # NIL) THEN
          IF (done ← useCache) THEN
            desc ← hostPtr↑
          ELSE
	    ClearHostEntry[hostPtr]
        END; -- LookupHost
       
      SendHostQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN ← FALSE] =
        BEGIN
	port: ArpaRouter.Port ← ArpaPort.AssignPort[];
        udpHandle: ArpaPort.Handle ← ArpaPort.Create[port, 1, 1, normal];
        b: ArpaBuffer.Buffer ← NIL;
 
        UpdateTables: ENTRY PROCEDURE =
          BEGIN 
          hostPtr: LONG POINTER TO HostTableEntry;
          done ← (ProcessReply[b, domainStr, currentTime, debugProc] = nameError);
          IF NOT done THEN
	    IF (done ← ((hostPtr ← FindHostEntry[@domainStr, currentTime]) # NIL)) THEN
	      desc ← hostPtr↑;
          END; -- UpdateTables
       
	BEGIN ENABLE UNWIND =>
	  {IF b # NIL THEN ArpaBuffer.ReturnBuffer[b];
	   ArpaPort.Delete[udpHandle]};

        ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)];
        b ← RemoteRequest[domainStr, IF WKSFlag THEN wks ELSE address, server, udpHandle, debugProc];
        UpdateTables[];
	END;
	
	ArpaPort.Delete[udpHandle];
        END; -- SendHostQuery

    -- main
    IF domainStr.text[domainStr.length-1] # '. THEN String.AppendChar[domainStr, '.];
    Resolve[LookupHost, SendHostQuery, domainStr, server, debugProc, currentTime];
    String.FreeString[Heap.systemZone, domainStr];
    END; -- ResolveHostName

--****************************************************************************--

  ResolveMailboxName: PUBLIC PROCEDURE [domain: LONG STRING, useCache: BOOLEAN ← TRUE, server: ServerType ← [nullINetAddress, domainNS], time: CARDINAL ← defaultTimeout, debugProc: DebugProc ← NIL] RETURNS [desc: MailboxTableEntry ← nullMailboxDesc] =
    BEGIN
    domainStr: LONG STRING ← String.CopyToNewString[domain, Heap.systemZone, 1];
    currentTime: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    
      LookupMailbox: ENTRY PROCEDURE RETURNS [done: BOOLEAN ← FALSE] =
        BEGIN
        mbPtr: LONG POINTER TO MailboxTableEntry ← FindMailboxEntry[@domainStr, currentTime];
        IF done ← (mbPtr # NIL) THEN
          IF (done ← useCache) THEN
            desc ← mbPtr↑
          ELSE
	    ClearMailboxEntry[mbPtr]
        END; -- LookupMailbox

      SendMailboxQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN ← FALSE] =
        BEGIN
	port: ArpaRouter.Port ← ArpaPort.AssignPort[];
        udpHandle: ArpaPort.Handle ← ArpaPort.Create[port, 1, 1, normal];
        b: ArpaBuffer.Buffer ← NIL;
    
        UpdateTables: ENTRY PROCEDURE =
          BEGIN
          mbPtr: LONG POINTER TO MailboxTableEntry;
          done ← (ProcessReply[b, domainStr, currentTime, debugProc] = nameError);
          IF NOT done THEN
	    IF (done ← ((mbPtr ← FindMailboxEntry[@domainStr, currentTime]) # NIL)) THEN
	      desc ← mbPtr↑;
          END; -- UpdateTables
       
	BEGIN ENABLE UNWIND =>
	  {IF b # NIL THEN ArpaBuffer.ReturnBuffer[b];
	  ArpaPort.Delete[udpHandle]};

        ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)];
        b ← RemoteRequest[domainStr, mb, server, udpHandle, debugProc];
        UpdateTables[];
	END;
	
	ArpaPort.Delete[udpHandle];
        END; -- SendMailboxQuery

    -- main
    IF domainStr.text[domainStr.length-1] # '. THEN String.AppendChar[domainStr, '.];
    Resolve[LookupMailbox, SendMailboxQuery, domainStr, server, debugProc, currentTime];
    String.FreeString[Heap.systemZone, domainStr];
    END; -- ResolveMailboxName
    
--****************************************************************************--

  ClearServerCache: PUBLIC ENTRY PROCEDURE  =
    BEGIN
    FOR i: CARDINAL IN [0..serverTableLength) DO
      ClearServerEntry[@serverTable[i]];
      ENDLOOP;
    END; -- ClearServerCache

  ResolveServerName: PUBLIC PROCEDURE [domain: LONG STRING, useCache: BOOLEAN ← TRUE, server: ServerType ← [nullINetAddress, domainNS], time: CARDINAL ← defaultTimeout, debugProc: DebugProc ← NIL] RETURNS [desc: ServerTableEntry ← nullServerDesc] =
    BEGIN
    domainStr: LONG STRING ← String.CopyToNewString[domain, Heap.systemZone, 1];
    currentTime: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    
      LookupServer: ENTRY PROCEDURE RETURNS [done: BOOLEAN ← FALSE] =
        BEGIN
        serverPtr: LONG POINTER TO ServerTableEntry ← MatchingServer[domainStr, currentTime];
        IF done ← (serverPtr # NIL) THEN
          IF (done ← useCache) THEN
            desc ← serverPtr↑
          ELSE
	    ClearServerEntry[serverPtr]
        END; -- LookupServer

      SendServerQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN ← FALSE] =
        BEGIN
	port: ArpaRouter.Port ← ArpaPort.AssignPort[];
        udpHandle: ArpaPort.Handle ← ArpaPort.Create[port, 1, 1, normal];
	b: ArpaBuffer.Buffer ← NIL;
    
        UpdateTables: ENTRY PROCEDURE =
          BEGIN
          serverPtr: LONG POINTER TO ServerTableEntry;
	  done ← (ProcessReply[b, domainStr, currentTime, debugProc] = nameError);
          IF NOT done THEN
	    IF (done ← ((serverPtr ← MatchingServer[domainStr, currentTime]) # NIL)) THEN
	      desc ← serverPtr↑;
          END; -- UpdateTables
       
	BEGIN ENABLE UNWIND =>
	  {IF b # NIL THEN ArpaBuffer.ReturnBuffer[b];
	  ArpaPort.Delete[udpHandle]};

        ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)];
        b ← RemoteRequest[domainStr, ns, server, udpHandle, debugProc];
        UpdateTables[];
	END;
	
	ArpaPort.Delete[udpHandle];
        END; -- SendServerQuery

    -- main
    IF domainStr.text[domainStr.length-1] # '. THEN
       String.AppendChar[domainStr, '.];
    Resolve[LookupServer, SendServerQuery, domainStr, server, debugProc, currentTime];
    String.FreeString[Heap.systemZone, domainStr];
    END; --ResolveServerName

--****************************************************************************--

  AppendNameOrINetAddress: PUBLIC PROCEDURE [to: LONG STRING, address: INetAddr] =
    BEGIN
    currentTime: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    addrPtr: LONG POINTER TO AddressTableEntry ← FindAddress[address, currentTime];

    Later: PROCEDURE = {
      [] ← ResolveHostAddress[host: address, time: 2*defaultTimeout]
      };

    IF addrPtr = NIL THEN
      BEGIN
        Process.Detach[FORK Later];
        AppendINetAddress[to, address];
      END
    ELSE
      String.AppendString[to: to, from: addrPtr↑.name[0]];
    END; -- AppendNameOrINetAddress

 
  AppendNameOrINetAddressAndGrow: PUBLIC PROCEDURE [to: LONG POINTER TO LONG STRING, address: INetAddr, z: UNCOUNTED ZONE] =
    BEGIN
    currentTime: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    addrPtr: LONG POINTER TO AddressTableEntry ← FindAddress[address, currentTime];

    Later: PROCEDURE = { 
      [] ← ResolveHostAddress[host: address, time: 2*defaultTimeout]
      };

    IF addrPtr = NIL THEN
      BEGIN
        Process.Detach[FORK Later];
        AppendINetAddressAndGrow[to, address, z];
      END
    ELSE
      String.AppendStringAndGrow[to, addrPtr↑.name[0], z];
    END; -- AppendNameOrINetAddress

   ResolveHostAddress: PUBLIC PROCEDURE [host: INetAddr, useCache: BOOLEAN ← TRUE, server: ServerType ← [nullINetAddress, domainNS], time: CARDINAL ← defaultTimeout, debugProc: DebugProc ← NIL] RETURNS [desc: AddressTableEntry ← nullAddressDesc] =
    BEGIN
    currentTime: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    
      LookupAddress: ENTRY PROCEDURE RETURNS [done: BOOLEAN ← FALSE] =
        BEGIN
	addrPtr: LONG POINTER TO AddressTableEntry ← FindAddress[host, currentTime];
        IF done ← (addrPtr # NIL) THEN
          IF (done ← useCache) THEN
	    desc ← addrPtr↑
	  ELSE
	    ClearAddressEntry[addrPtr];
        END; -- LookupAddress
       
      SendInAddrQuery: PROCEDURE [server: ServerType] RETURNS [done: BOOLEAN ← FALSE] =
        BEGIN
	temp: LONG STRING ← [40];
	port: ArpaRouter.Port ← ArpaPort.AssignPort[];
        udpHandle: ArpaPort.Handle ← ArpaPort.Create[port, 1, 1, normal];
        b: ArpaBuffer.Buffer ← NIL;
    
        UpdateTables: ENTRY PROCEDURE =
          BEGIN
	  addrPtr: LONG POINTER TO AddressTableEntry;
	  done ← (ProcessReply[b, temp, currentTime, debugProc] # okay);
          IF NOT done THEN
	    IF (done ← ((addrPtr ← FindAddress[host, currentTime]) # NIL)) THEN
	      desc ← addrPtr↑;
          END; -- UpdateTables
       
	BEGIN ENABLE UNWIND =>
	  {IF b # NIL THEN ArpaBuffer.ReturnBuffer[b];
	  ArpaPort.Delete[udpHandle]};

--      build string to pass to RemoteRequest
	String.AppendDecimal[temp, host.d];
        String.AppendChar[temp, '.];
        String.AppendDecimal[temp, host.c];
        String.AppendChar[temp, '.];
        String.AppendDecimal[temp, host.b];
        String.AppendChar[temp, '.];
        String.AppendDecimal[temp, host.a];
	String.AppendString[temp, ".IN-ADDR.ARPA."L];

        ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)];
	b ← RemoteRequest[temp, ptr, server, udpHandle, debugProc];
        UpdateTables[];
	END;
	
	ArpaPort.Delete[udpHandle];
        END; -- SendInAddrQuery

    -- main
    Resolve[LookupAddress, SendInAddrQuery, NIL, server, debugProc, currentTime];
    END; -- ResolveHostAddress

--****************************************************************************--

  MakePointerQuery: PUBLIC PROCEDURE [pointer: LONG STRING, server: ServerType ← [nullINetAddress, domainNS], time: CARDINAL ← defaultTimeout, debugProc: DebugProc ← NIL, z: UNCOUNTED ZONE] RETURNS [s: LONG STRING ← NIL] =
    BEGIN
    currentTime: System.GreenwichMeanTime ← System.GetGreenwichMeanTime[];
    strPointer: LONG STRING ← String.CopyToNewString[pointer, z, 1];
    port: ArpaRouter.Port ← ArpaPort.AssignPort[];
    udpHandle: ArpaPort.Handle ← ArpaPort.Create[port, 1, 1, normal];
    b: ArpaBuffer.Buffer;

    BEGIN ENABLE UNWIND => {
      String.FreeString[z, strPointer];
      IF b # NIL THEN ArpaBuffer.ReturnBuffer[b];
      ArpaPort.Delete[udpHandle]};
    IF strPointer.text[strPointer.length-1] # '. THEN
       String.AppendChar[strPointer, '.];
    ArpaPort.SetWaitTime[udpHandle, time * (1000/maxTries)];
    b ← RemoteRequest[strPointer, ptr, server, udpHandle, debugProc];
    
    BEGIN OPEN udp: b.arpa.user;
    pUDP: UDPBuffer ← LOOPHOLE[@udp];
    hPtr: LONG POINTER TO QueryHeader ← LOOPHOLE[@udp.data];    
    
    -- check rcode and counts in header
    IF hPtr.qr = response AND hPtr.rcode = okay THEN
       BEGIN ENABLE UNWIND => ArpaBuffer.ReturnBuffer[b];
       temp: LONG STRING ← [120];
       index: CARDINAL ← 2 * SIZE[QueryHeader];
	  
       -- check the query in the response
       FOR i:CARDINAL IN [0..hPtr.queryCount) DO
	 index ← UnpackName[pUDP, index, @temp];
	 IF NOT String.Equivalent[strPointer, temp] THEN SIGNAL ErrorInReply;
	 index ← index + 4;  -- 2 bytes for the type, 2 bytes for the class
	 ENDLOOP;
	     
       s ← String.MakeString[z, 40];
       FOR i:CARDINAL IN [0..(hPtr.answerCount+hPtr.nsCount+hPtr.arCount)) DO
	 type: QType;
	 time: LONG CARDINAL;
	     
	 temp.length ← 0;
	 index ← UnpackName[pUDP, index, @temp];
	 type ← LOOPHOLE[GetWord[@pUDP.data, @index]];
	 index ← index + 2;  -- 2 bytes for the class
	 time ← GetLong[@pUDP.data, @index];
	 index ← index + 2;  --2 bytes for the rdlength

	 SELECT type FROM
	   ns =>	-- referral 
	     BEGIN
	     server: LONG POINTER TO ServerTableEntry ← InitServerEntry[temp, currentTime];
	     ttl: System.GreenwichMeanTime ← System.AdjustGreenwichMeanTime[currentTime, MIN[17777777777B, time]];
	     temp.length ← 0;
	     index ← UnpackName[pUDP, index, @temp];
	     IF debugProc # NIL THEN
		BEGIN
		debugProc["   Received referral to "L];
		debugProc[temp];
		debugProc["\n"];
		END;
	     AddStringEntry[@server.host, @server.hostCount, temp]; 
	     UpdateTime[@server.time, ttl];
	     END;
	   ptr =>
	     BEGIN
	     temp.length ← 0;
	     index ← UnpackName[pUDP, index, @temp];
	     String.AppendStringAndGrow[@s, temp, z];
	     String.AppendStringAndGrow[@s, ", "L, z];
	     END
	 ENDCASE => SIGNAL ErrorInReply;
	    
	 ENDLOOP;
       IF s.length > 2 THEN s.length ← s.length -2;
       END;
    END; -- OPEN
    END; -- ENABLE
       
    ArpaBuffer.ReturnBuffer[b];
    ArpaPort.Delete[udpHandle];
    String.FreeString[z, strPointer];
    END; -- MakePointerQuery

  END.