-- File: NameConversion.mesa,  Last Edit: HGM  March 22, 1981  9:54 PM

DIRECTORY
  Mopcodes USING [zEXCH],
  String USING [AppendChar, AppendString, AppendOctal, AppendLongNumber],
  StatsDefs USING [StatIncr],
  PupRouterDefs USING [GetRoutingTableEntry, RoutingTableEntry, maxHop],
  CommFlags USING [doStats],
  DriverDefs USING [Glitch],
  PupStream USING [],
  PupDefs USING [
    GetFreePupBuffer, ReturnFreePupBuffer, DataWordsPerPupBuffer,
    GetPupContentsBytes, SetPupContentsWords, SetPupContentsBytes,
    UniqueLocalPupAddress, NameLookupErrorCode, PupAddress, PupBuffer, PupSocket,
    PupRouterBroadcastThis, PupSocketDestroy, PupSocketMake, SecondsToTocks],
  PupTypes USING [fillInPupAddress, fillInSocketID, miscSrvSoc, PupSocketID];

NameConversion: PROGRAM
  IMPORTS String, StatsDefs, DriverDefs, PupRouterDefs, PupDefs
  EXPORTS PupDefs, PupStream =
  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.RoutingTableEntry;
      rte ← PupRouterDefs.GetRoutingTableEntry[him.net];
      IF rte = NIL OR rte.network = NIL OR rte.hop > PupRouterDefs.maxHop THEN
        RETURN[FALSE];  -- but 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;
    FOR try: CARDINAL IN [0..10) DO
      b ← GetFreePupBuffer[];
      b.pupType ← nameLookup;
      b.pupID ← [try, try];
      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: LONG POINTER TO ARRAY OF PupAddress ←
              LOOPHOLE[@b.pupWords];
            distance: ARRAY [0..maxAnswers) OF CARDINAL ← ALL[longHop];
            howMany: CARDINAL;
            howMany ← MIN[
              maxAnswers, GetPupContentsBytes[b]/(2*SIZE[PupAddress])];
            FOR i: CARDINAL IN [0..howMany) DO
              rte: PupRouterDefs.RoutingTableEntry;
              rte ← PupRouterDefs.GetRoutingTableEntry[trialAddress[i].net];
              IF rte # NIL AND rte.network # NIL THEN distance[i] ← rte.hop;
              ENDLOOP;
            hit ← FALSE;
            FOR j: CARDINAL IN [0..longHop] UNTIL hit DO
              FOR i: CARDINAL 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 => SaveFrameSpace[b];
          ENDCASE =>
            BEGIN
            IF CommFlags.doStats THEN StatsDefs.StatIncr[statMouseTrap];
            ReturnFreePupBuffer[b];
            b ← NIL;
            END;
      ENDLOOP;
    ERROR PupNameTrouble["No name lookup server responded"L, noResponse];
    END;  -- of ENABLE
    END;

  SaveFrameSpace: PROCEDURE [b: PupBuffer] =
    BEGIN
    error: STRING = [50];
    AppendPseudoString[error, DESCRIPTOR[@b.pupBytes, GetPupContentsBytes[b]]];
    ERROR PupNameTrouble[error, errorFromServer];
    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;
    bar: BOOLEAN ← FALSE;
    IF s = NIL OR s.length = 0 THEN RETURN[FALSE];
    FOR i: CARDINAL ← 0, i + 1 UNTIL i = s.length DO
      c: CHARACTER;
      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;
    FOR try: CARDINAL IN [0..10) DO
      b ← GetFreePupBuffer[];
      b.pupType ← addressLookup;
      b.pupID ← [try, try];
      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 => SaveFrameSpace[b];
          ENDCASE =>
            BEGIN
            IF CommFlags.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;

  AppendOctal: PROCEDURE [s: STRING, n: CARDINAL] =
    BEGIN
    IF n > 7 THEN AppendOctal[s, n/8];
    String.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
    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: CARDINAL 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
    FOR i: CARDINAL IN [0..MIN[e.maxlength - e.length, LENGTH[p]]) DO
      String.AppendChar[e, p[i]]; ENDLOOP;
    END;

  AppendPupAddress: PUBLIC PROCEDURE [s: STRING, a: PupAddress] =
    BEGIN
    Flip: PROCEDURE [PupTypes.PupSocketID] RETURNS [LONG INTEGER] = MACHINE CODE
      BEGIN Mopcodes.zEXCH END;
    AppendOctal[s, a.net];
    String.AppendChar[s, '#];
    AppendOctal[s, a.host];
    String.AppendChar[s, '#];
    String.AppendLongNumber[s, Flip[a.socket], 8];
    END;

  AppendErrorPup: PUBLIC PROCEDURE [s: STRING, b: PupDefs.PupBuffer] =
    BEGIN
    IF b.pupType = error THEN
      BEGIN
      String.AppendString[s, "[Error Pup, code="L];
      String.AppendOctal[s, b.errorCode];
      String.AppendString[s, ", from: "L];
      AppendPupAddress[s, b.source];
      String.AppendString[s, "] "L];
      FOR i: CARDINAL IN [0..PupDefs.GetPupContentsBytes[b] - 2*(10 + 1 + 1)) DO
        String.AppendChar[s, b.errorText[i]]; ENDLOOP;
      END
    ELSE
      BEGIN
      String.AppendString[s, "[Funny PupType = "L];
      String.AppendOctal[s, b.pupType];
      String.AppendString[s, ", from: "L];
      AppendPupAddress[s, b.source];
      String.AppendString[s, "]"L];
      END;
    END;

  END.