-- Transport Mechanism - Location of server by client --

-- [Juniper]<DMS>MS>Locate.mesa

-- Andrew Birrell  18-Mar-81 17:55:30 --

DIRECTORY
BodyDefs	USING [Connect, maxRNameLength, RName],
LocateDefs	USING [FoundServerInfo, FoundState],
NameInfoDefs	USING [Close, Enumerate, GetConnect, GetMembers,
		       MemberInfo, NameType, RListHandle],
ProtocolDefs	USING [Connect, Init, IsLocal, maxConnectLength,
		       RegServerEnquirySocket, RegServerPollingSocket],
PupDefs		USING [EnumeratePupAddresses, GetFreePupBuffer,
		       GetPupAddress, PupAddress, PupBuffer,
		       PupNameTrouble, PupRouterSendThis, PupSocket,
		       PupSocketDestroy, PupSocketID, PupSocketMake,
		       ReturnFreePupBuffer, SecondsToTocks,
		       SetPupContentsWords],
PupTypes	USING [fillInSocketID, PupSocketID],
Storage		USING[ Node, Free ],
String		USING [AppendString, EquivalentString];

Locate: MONITOR
   IMPORTS NameInfoDefs, ProtocolDefs, PupDefs, Storage, String
   EXPORTS LocateDefs =

BEGIN

ReplyInfo: TYPE = RECORD[ found: BOOLEAN, where: PupDefs.PupAddress ];

AcceptReplies: PROCEDURE[ socket: PupDefs.PupSocket, sent: CARDINAL,
                    accept: PROCEDURE[PupDefs.PupAddress]RETURNS[BOOLEAN] ]
                 RETURNS[ info: ReplyInfo ] =
   BEGIN
   bestHops: CARDINAL ← LAST[CARDINAL];
   info.found ← FALSE;
   UNTIL info.found
   OR sent = 0 -- we may finish early or late, but it will be near enough--
   DO BEGIN
      b: PupDefs.PupBuffer = socket.get[];
      IF b = NIL
      THEN EXIT
      ELSE BEGIN
           sent ← sent-1;
           SELECT b.pupType FROM
             iAmEcho =>
               BEGIN
               info.where ← b.source;
               IF accept[info.where] THEN info.found ← TRUE
               END;
           ENDCASE;
           PupDefs.ReturnFreePupBuffer[b];
           END;
      END;
   ENDLOOP;
   END;
   
SendEnquiry: PROCEDURE[addr: PupDefs.PupAddress, from: PupDefs.PupAddress] =
   BEGIN
   b: PupDefs.PupBuffer;
   b ← PupDefs.GetFreePupBuffer[];
   b.source ← from;
   b.pupType ← echoMe;
   b.dest ← addr;
   -- force socket number, because database may not have GV test-mode socket numbers --
   b.dest.socket ← ProtocolDefs.RegServerPollingSocket;
   PupDefs.SetPupContentsWords[b, 0];
   PupDefs.PupRouterSendThis[b];
   END;


EnquiryInfo: TYPE = RECORD[ outcome:{ done, down, badName },
                            local: BOOLEAN, sent: CARDINAL];

gvName: STRING = "gv.gv"; -- NA for registration servers--
NoGVRS: SIGNAL = CODE; -- debugging; may be resumed --

GetGVRegServer: PROCEDURE[ from: PupDefs.PupAddress ]
                  RETURNS[ info: EnquiryInfo ] =
   BEGIN
   Work: PROCEDURE[addr: PupDefs.PupAddress] RETURNS[ BOOLEAN ] =
      BEGIN
      -- don't trust socket number given by NLS (?) --
      addr.socket ← ProtocolDefs.RegServerEnquirySocket;
      SendEnquiry[addr, from]; info.sent ← info.sent+1;
      RETURN[FALSE]
      END;
   GVRS: STRING = "GrapevineRServer"L;
   info ← [done, FALSE, 0];
   -- hack to allow test servers anywhere on my net --
   [] ← Work[ [net:[0], host:[0],
               socket: ProtocolDefs.RegServerEnquirySocket ] ];
   -- end hack --
   BEGIN
      ENABLE PupDefs.PupNameTrouble =>
         IF code = errorFromServer
         THEN BEGIN SIGNAL NoGVRS[]; GOTO bad END
         ELSE GOTO down;
      [] ← PupDefs.EnumeratePupAddresses[GVRS, Work ];
      EXITS
         down => info.outcome ← down;
         bad => info.outcome ← badName;
   END;
   END;


GetGroupInfo: PROC[ who, local: BodyDefs.RName,
                          from: PupDefs.PupAddress]
                 RETURNS[ info: EnquiryInfo ] =
   BEGIN
   mInfo: NameInfoDefs.MemberInfo =  NameInfoDefs.GetMembers[who];
   WITH m: mInfo SELECT FROM
     notFound => info ← [badName,FALSE,0];
     allDown => info ← [down,FALSE,0];
     individual => info ← [badName,FALSE,0];
     group =>
       BEGIN
       apparent: CARDINAL ← 0;
       actual: CARDINAL ← 0;
       Count: PROC[member:BodyDefs.RName]RETURNS[done:BOOLEAN] =
          { apparent ← apparent + 1; done ← FALSE };
       addresses: DESCRIPTOR FOR ARRAY OF PupDefs.PupAddress;
       Access: PROC[member:BodyDefs.RName]RETURNS[done:BOOLEAN] =
          BEGIN
          cInfo: NameInfoDefs.NameType;
          connect: ProtocolDefs.Connect = [ProtocolDefs.maxConnectLength];
          done ← FALSE;
          cInfo ← NameInfoDefs.GetConnect[member, connect];
          SELECT cInfo FROM
            individual =>
              BEGIN
              PupDefs.GetPupAddress[@(addresses[actual]), connect !
                             PupDefs.PupNameTrouble => GOTO cant];
              actual ← actual + 1;
              EXITS cant => NULL;
              END;
          ENDCASE => NULL -- ignore others --;
          END;
       NameInfoDefs.Enumerate[m.members, Count];
       addresses ← DESCRIPTOR[Storage.Node[apparent*
                                           SIZE[PupDefs.PupAddress]],
                              apparent];
       NameInfoDefs.Enumerate[m.members, Access];
       NameInfoDefs.Close[m.members];
       info ← [done,FALSE,0];
       FOR i: CARDINAL IN [0..actual)
       DO IF local = NIL
          THEN { SendEnquiry[addresses[i], from]; info.sent ← info.sent+1 }
          ELSE BEGIN
               IF ProtocolDefs.IsLocal[addresses[i]]
               THEN BEGIN
                    info.local ← TRUE;
                    local.length ← 0; String.AppendString[local, who];
                    END;
               END;
       ENDLOOP;
       Storage.Free[BASE[addresses]];
       END;
   ENDCASE => ERROR;
   END;


FindRegServer: PUBLIC PROCEDURE[ who: BodyDefs.RName,
                  accept: PROCEDURE[PupDefs.PupAddress]RETURNS[BOOLEAN] ]
               RETURNS[ foundInfo: LocateDefs.FoundServerInfo ] =
   BEGIN
   -- find a registration server for given R-Name --
   sep: CHARACTER = '.; -- SN sep NA --
   rPtr: CARDINAL;
   NA: BodyDefs.RName = [BodyDefs.maxRNameLength];

   -- parse to find registry name --
   rPtr ← who.length;
   DO IF rPtr = 0 THEN RETURN[ [notFound[]] ];
      rPtr←rPtr-1;
      IF who[rPtr] = sep THEN EXIT;
   ENDLOOP;
   NA.length ← 0;
   FOR rPtr←rPtr+1, rPtr+1 WHILE rPtr # who.length
   DO NA[NA.length] ← who[rPtr]; NA.length←NA.length+1 ENDLOOP;

   String.AppendString[NA, ".GV"L];

   foundInfo ← FindNearestServer[NA, accept];

   END; --FindRegServer--


FindNearestServer: PUBLIC PROCEDURE[list: BodyDefs.RName,
                     accept: PROCEDURE[PupDefs.PupAddress]RETURNS[BOOLEAN] ]
                   RETURNS[info: LocateDefs.FoundServerInfo] =
   BEGIN
   socket: PupDefs.PupSocket = PupDefs.PupSocketMake[
              local: PupTypes.fillInSocketID,
              remote:, ticks: PupDefs.SecondsToTocks[1] ];
   from: PupDefs.PupAddress = socket.getLocalAddress[];
   THROUGH [1..3] -- re-tries for lost packets --
   DO BEGIN
      sendInfo: EnquiryInfo = IF String.EquivalentString[list, gvName]
             THEN GetGVRegServer[from]
             ELSE GetGroupInfo[list,NIL,from];
      SELECT sendInfo.outcome FROM
        badName => { info ← [notFound[]]; EXIT };
        down => { info ← [allDown[]]; EXIT };
        done =>
          BEGIN
          reply: ReplyInfo = AcceptReplies[socket, sendInfo.sent, accept];
          IF reply.found
          THEN { info ←  [found[reply.where]]; EXIT }
          ELSE NULL --continue round loop--
          END;
      ENDCASE => ERROR;
      END;
      REPEAT
   FINISHED => info ← [allDown[]]
   ENDLOOP;
   PupDefs.PupSocketDestroy[socket];
   END --FindNearestServer--;

FindLocalServer: PUBLIC PROCEDURE[list, local: BodyDefs.RName]
                 RETURNS[ LocateDefs.FoundState ] =
   BEGIN
   sendInfo: EnquiryInfo = GetGroupInfo[list, local, ];
   SELECT sendInfo.outcome FROM
     badName => RETURN[ notFound ];
     down => RETURN[ allDown ];
     done => RETURN[ IF sendInfo.local THEN found ELSE notFound ];
   ENDCASE => ERROR;
   END;

AcceptFirst: PUBLIC PROCEDURE[PupDefs.PupAddress]RETURNS[BOOLEAN] =
   BEGIN
   RETURN[TRUE]
   END;

ProtocolDefs.Init[];

END.