-- File: NameConversion.mesa,  Last Edit:
  -- MAS  Apr 18, 1980 12:38 PM
  -- HGM  August 3, 1980  5:20 PM
  -- Taft  June 1, 1983  10:01 AM

-- Copyright  Xerox Corporation 1979, 1980

DIRECTORY
  Mopcodes: FROM "Mopcodes" USING [zEXCH],
  StatsDefs: FROM "StatsDefs" USING [StatIncr],
  CommUtilDefs: FROM "CommUtilDefs" USING [
    AppendChar, AppendLongNumber, MsecToTicks, SetTimeout],
  PupRouterDefs: FROM "PupRouterDefs" USING [
    maxHop, PupRoutingTableEntry, GetPupRoutingTableEntry, IfMissing,
    probesLeftToDo, probeResponse, routerLock],
  DriverDefs: FROM "DriverDefs" USING [doStats, Glitch],
  PupStream: FROM "PupStream",  -- EXPORTs
  PupDefs: FROM "PupDefs" USING [
    GetFreePupBuffer, ReturnFreePupBuffer,
    DataWordsPerPupBuffer, GetPupContentsBytes,
    SetPupContentsWords, SetPupContentsBytes,
    UniqueLocalPupAddress,
    NameLookupErrorCode, PupAddress, PupBuffer, PupSocket,
    PupRouterBroadcastThis,
    PupSocketDestroy, PupSocketMake, SecondsToTocks],
  PupTypes: FROM "PupTypes" USING [
    fillInPupAddress, fillInSocketID, miscSrvSoc, PupSocketID];

NameConversion: MONITOR LOCKS PupRouterDefs.routerLock
  IMPORTS  CommUtilDefs, StatsDefs, DriverDefs, PupRouterDefs, PupDefs
  EXPORTS PupStream, PupDefs =
BEGIN OPEN PupDefs;

PupNameTrouble: PUBLIC ERROR [e: STRING, code: NameLookupErrorCode] = CODE;

StringIsNIL: PUBLIC ERROR = CODE;
StringTooLong: PUBLIC ERROR = CODE;

GetPupAddress: PUBLIC PROCEDURE [a: POINTER TO PupAddress, s: STRING] =
  BEGIN
  IF ~ParsePupAddressConstant[a,s] THEN PupNameLookup[a,s];
  END;

-- Lookup Text string as Name Via Gateways

PupNameLookup: PUBLIC PROCEDURE [a: POINTER TO PupAddress, s: STRING] =
  BEGIN
  TakeTheFirst: PROCEDURE [him: PupAddress] RETURNS [BOOLEAN] =
    BEGIN
    rte: PupRouterDefs.PupRoutingTableEntry ←
      PupRouterDefs.GetPupRoutingTableEntry[net: him.net, ifMissing: return];
    IF rte.network=NIL OR rte.hop>PupRouterDefs.maxHop THEN
      RETURN[FALSE];  -- skip unreachable ones
    a.net ← him.net;
    a.host ← him.host;
    IF him.socket#[0,0] THEN a.socket ← him.socket;
    RETURN[TRUE];
    END;
  IF s.length=2 AND s[0]='M AND s[1]='E THEN
    BEGIN  -- special case hack
    me: PupAddress ← UniqueLocalPupAddress[NIL];
    a.net ← me.net;
    a.host ← me.host;
    RETURN;
    END;
  IF EnumeratePupAddresses[s,TakeTheFirst] THEN RETURN;
  ERROR PupNameTrouble["No Route to that Host"L,noRoute];
  END;

EnumeratePupAddresses: PUBLIC PROCEDURE [
    s: STRING, filter: PROCEDURE [PupAddress] RETURNS [BOOLEAN] ]
  RETURNS [hit: BOOLEAN] =
  BEGIN
  soc: PupSocket;
  b: PupBuffer ← NIL;
  soc ← PupSocketMake[
	PupTypes.fillInSocketID,
	PupTypes.fillInPupAddress,
	SecondsToTocks[2]];
  BEGIN ENABLE UNWIND => -- this is the error exit
      BEGIN IF b#NIL THEN ReturnFreePupBuffer[b]; PupSocketDestroy[soc]; END;
  THROUGH [0..10) DO  -- try a few times
    b ← GetFreePupBuffer[];
    b.pupType ← nameLookup;
    b.dest.socket ← PupTypes.miscSrvSoc;
    b.source ← soc.getLocalAddress[];
    MoveStringBodyToPupBuffer[b,s];
    PupRouterBroadcastThis[b];
    b ← NIL;  -- In case we get Aborted and then UNWIND
    b ← soc.get[];  -- 2 sec wait
    IF b#NIL THEN 
      SELECT b.pupType FROM
	nameIs =>
	  BEGIN
	  maxAnswers: CARDINAL = 35;
          longHop: CARDINAL = PupRouterDefs.maxHop+1;
	  trialAddress: POINTER TO ARRAY OF PupAddress ← LOOPHOLE[@b.pupWords];
	  distance: ARRAY [0..maxAnswers) OF CARDINAL ← ALL[longHop];
	  i, j, howMany, try: CARDINAL;
          atLeastOne: BOOLEAN ← FALSE;
	  howMany ← MIN[maxAnswers,GetPupContentsBytes[b]/(2*SIZE[PupAddress])];
          FOR try IN [0..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.
            whatToDo: PupRouterDefs.IfMissing ←
              IF try MOD 2 = 0 THEN return ELSE probeAndReturn;
            WaitForRouter: ENTRY PROCEDURE =
              BEGIN
              ENABLE UNWIND => NULL;
              WHILE PupRouterDefs.probesLeftToDo#0 DO
                WAIT PupRouterDefs.probeResponse; ENDLOOP;
              -- Wait some more for trailing packets in multi-packet response
              WAIT halfSecond;
              END;
            allInCache: BOOLEAN ← TRUE;
	    FOR i IN [0..howMany) DO
              IF distance[i]=longHop THEN
                BEGIN
	        route: PupRouterDefs.PupRoutingTableEntry ←
                  PupRouterDefs.GetPupRoutingTableEntry[
                    net: trialAddress[i].net, ifMissing: whatToDo];
	        IF route.network#NIL AND route.hop<longHop
                THEN { distance[i] ← route.hop; atLeastOne ← TRUE }
                ELSE allInCache ← FALSE;
                END;
	      ENDLOOP;
            IF atLeastOne AND (allInCache OR try>=4) THEN EXIT;
            IF whatToDo=probeAndReturn THEN WaitForRouter[];
            ENDLOOP;
	  hit ← FALSE;
	  FOR j IN [0..longHop] UNTIL hit DO
	    FOR i IN [0..howMany) UNTIL hit DO
	      IF distance[i]#j THEN LOOP;
	      hit ← filter[trialAddress[i]];
	      ENDLOOP;
	    ENDLOOP;
	  ReturnFreePupBuffer[b];
	  PupSocketDestroy[soc];
	  RETURN;
	  END;
	nameError =>
	  BEGIN
	  error: STRING = [50];
	  AppendPseudoString[error,DESCRIPTOR[@b.pupBytes,GetPupContentsBytes[b]]];
	  ERROR PupNameTrouble[error,errorFromServer];
	  END;
	ENDCASE=>
	  BEGIN
	  IF DriverDefs.doStats THEN StatsDefs.StatIncr[statMouseTrap];
	  ReturnFreePupBuffer[b];
	  b ← NIL;
	  END;
    ENDLOOP;
  ERROR PupNameTrouble["No name lookup server responded"L,noResponse];
  END;  -- of ENABLE
  END;

-- Parse a string of the form net#host#socket
--  Setup net and host appropiately,  Don't change socket if not specified
-- RETURNs TRUE unless can't parse it.
ParsePupAddressConstant: PUBLIC PROCEDURE [a: POINTER TO PupAddress, s: STRING]
    RETURNS [BOOLEAN] =
  BEGIN
  n, h, s1, s2: CARDINAL ← 0;
  i: CARDINAL;
  c: CHARACTER;
  bar: BOOLEAN ← FALSE;
  IF s=NIL OR s.length=0 THEN RETURN[FALSE];
  FOR i←0,i+1 UNTIL i=s.length DO
    SELECT (c←s[i]) FROM
      '| =>
	BEGIN
	IF bar THEN RETURN[FALSE];
	bar ← TRUE;
	s1←s2; s2←0;
	END;
      '# =>
	BEGIN
	IF bar THEN RETURN[FALSE];
	IF n#0 OR s1#0 THEN RETURN[FALSE];
	n←h; h←s2; s1←s2←0;
	END;
      IN ['0..'9] =>
	BEGIN
	IF ~bar THEN s1←s1*8+s2/17777B  -- 32 bit number
	ELSE IF s2>17777B THEN RETURN[FALSE];
	s2 ← s2*8+CARDINAL[c-'0];
	END;
      ENDCASE => RETURN [FALSE];
    ENDLOOP;
  IF n ~IN [0..377B] THEN RETURN [FALSE];
  IF h ~IN [0..377B] THEN RETURN [FALSE];
  a.net ← [n];
  a.host ← [h];
  IF s1#0 OR s2#0 THEN a.socket ← [s1,s2];
  RETURN[TRUE];
  END;

-- Inverse of PupNameLookup

PupAddressLookup: PUBLIC PROCEDURE [s: STRING, a: PupAddress] =
  BEGIN
  soc: PupSocket;
  b: PupBuffer ← NIL;
  soc ← PupSocketMake[
	PupTypes.fillInSocketID,
	PupTypes.fillInPupAddress,
	SecondsToTocks[2]];
  BEGIN ENABLE UNWIND => -- this is the error exit
      BEGIN IF b#NIL THEN ReturnFreePupBuffer[b]; PupSocketDestroy[soc]; END;
  THROUGH [0..10) DO  -- try a few times
    b ← GetFreePupBuffer[];
    b.pupType ← addressLookup;
    b.dest.socket ← PupTypes.miscSrvSoc;
    b.source ← soc.getLocalAddress[];
    b.address ← a;
    SetPupContentsWords[b,SIZE[PupAddress]];
    PupRouterBroadcastThis[b];
    b ← NIL;  -- In case we get Aborted and then UNWIND
    b ← soc.get[];  -- 2 sec wait
    IF b#NIL THEN 
      SELECT b.pupType FROM
	addressIs =>
	  BEGIN
	  AppendPseudoString[s,DESCRIPTOR[@b.pupBytes,GetPupContentsBytes[b]]];
	  ReturnFreePupBuffer[b];
	  PupSocketDestroy[soc];
	  RETURN;
	  END;
	nameError =>
	  BEGIN
	  error: STRING = [50];
	  AppendPseudoString[error,DESCRIPTOR[@b.pupBytes,GetPupContentsBytes[b]]];
	  ERROR PupNameTrouble[error,errorFromServer];
	  END;
	ENDCASE=>
	  BEGIN
	  IF DriverDefs.doStats THEN StatsDefs.StatIncr[statMouseTrap];
	  ReturnFreePupBuffer[b];
	  b ← NIL;
	  END;
    ENDLOOP;
  ERROR PupNameTrouble["No name lookup server responded"L,noResponse];
  END;  -- of ENABLE
  END;

AppendMyName: PUBLIC PROCEDURE [name: STRING] =
  BEGIN
  AppendHostName[name,UniqueLocalPupAddress[NIL]];
  END;

AppendHostName: PUBLIC PROCEDURE [s: STRING, who: PupAddress] =
  BEGIN
  who.socket ← [0,0];
  PupAddressLookup[s,who !
    PupNameTrouble =>
      BEGIN
      AppendPupAddress[s,who];
      CONTINUE;
      END ];
  END;

Flip: PROCEDURE [PupTypes.PupSocketID] RETURNS [LONG INTEGER] =
  MACHINE CODE BEGIN Mopcodes.zEXCH END;

AppendPupAddress: PUBLIC PROCEDURE [s: STRING, a: PupAddress] =
  BEGIN
  AppendOctal[s,a.net];
  CommUtilDefs.AppendChar[s,'#];
  AppendOctal[s,a.host];
  CommUtilDefs.AppendChar[s,'#];
  CommUtilDefs.AppendLongNumber[s,Flip[a.socket],8];
  END;

AppendOctal: PROCEDURE [s: STRING, n: CARDINAL] =
  BEGIN
  IF n>7 THEN AppendOctal[s,n/8];
  CommUtilDefs.AppendChar[s,'0+(n MOD 8)];
  END;

-- move the body of a mesa STRING into the body of a packet and set its length
MoveStringBodyToPupBuffer: PUBLIC PROCEDURE [b: PupBuffer, s: STRING] =
  BEGIN
  dataBytesPerPup: CARDINAL ← 2*DataWordsPerPupBuffer[];
  i: CARDINAL;
  IF s=NIL THEN DriverDefs.Glitch[StringIsNIL];
  IF s.length>dataBytesPerPup THEN DriverDefs.Glitch[StringTooLong];
  FOR i IN [0..s.length) DO b.pupChars[i] ← s[i]; ENDLOOP;
  SetPupContentsBytes[b,s.length];
  END;

-- append the body of a mesa STRING into the body of a packet and set its length
AppendStringBodyToPupBuffer: PUBLIC PROCEDURE [b: PupBuffer, s: STRING] =
  BEGIN
  i, length: CARDINAL;
  dataBytesPerPup: CARDINAL ← 2*DataWordsPerPupBuffer[];
  IF s=NIL THEN DriverDefs.Glitch[StringIsNIL];
  length ← GetPupContentsBytes[b];
  IF (s.length+length)>dataBytesPerPup THEN DriverDefs.Glitch[StringTooLong];
  FOR i IN [0..s.length) DO
    b.pupChars[length+i] ← s[i];
    ENDLOOP;
  SetPupContentsBytes[b,(s.length+length)];
  END;

-- APPEND a pseudo string to a STRING
AppendPseudoString: PROCEDURE [e: STRING, p: LONG DESCRIPTOR FOR PACKED ARRAY OF CHARACTER] =
  BEGIN
  i: CARDINAL;
  FOR i IN [0..MIN[e.maxlength-e.length,LENGTH[p]]) DO
    CommUtilDefs.AppendChar[e,p[i]];
    ENDLOOP;
  END;

halfSecond: CONDITION;

CommUtilDefs.SetTimeout[@halfSecond, CommUtilDefs.MsecToTicks[500]];

END.