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

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

-- Andrew Birrell  20-Mar-81 16:56:49 --
-- Mike Schroeder March 21, 1981  10:59 AM --

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

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

BEGIN

HopCount:   TYPE = [0..37777B];

AddressRec: TYPE = RECORD[addr: PupDefs.PupAddress,
                          hops: HopCount,
                          reply: BOOLEAN];

AddressTable: TYPE = DESCRIPTOR FOR ARRAY OF AddressRec;


AddressInfo: TYPE = RECORD[ outcome:{ done, down, badName },
                            preSorted: BOOLEAN,
                            found: CARDINAL,
                            addresses: AddressTable];

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

Prod: PROCEDURE[ socket: PupDefs.PupSocket,
                 addrInfo: AddressInfo,
                 accept: PROCEDURE[PupDefs.PupAddress]RETURNS[BOOLEAN] ]
        RETURNS[ info: ReplyInfo ] =
   BEGIN
   -- This is complicated!
   -- The parameters are a table of candidate addresses, and a socket.
   -- The prodder process sends out "echoMe" pups to the addresses;
   -- the slurper process accepts "iAmEcho" replies, and marks the table.
   -- The original process reads the marks from the table, and calls the
   -- client's "accept" procedure to see if this address is ok.
   -- This satisfies various constraints about not hogging pup buffers.
   -- The "echoMe" packets are sent out to closer addresses first; they're
   -- not sent after the client has accepted an address.
   -- Everywhere, the Monitor is not locked while doing long waits.
   from: PupDefs.PupAddress = socket.getLocalAddress[];
   replyRecvd: BOOLEAN ← FALSE;-- "reply-waiting" flag --
   cond: CONDITION; -- notified when reply comes in --
   pleaseDie: BOOLEAN ← FALSE; -- for killing auxiliary processes --
   DeathWish: ENTRY PROC RETURNS[BOOLEAN] = INLINE
      { RETURN[pleaseDie] };
   KillSiblings: ENTRY PROC = INLINE
      { pleaseDie ← TRUE; PupDefs.PupSocketKick[socket] };
   NoteReply: ENTRY PROC[i: CARDINAL, addr: PupDefs.PupAddress] =
      BEGIN
      -- caller promises that "i" is in range --
      IF addrInfo.addresses[i].addr.host = 0
      THEN { addrInfo.addresses[i].addr.host ← addr.host;
             addrInfo.addresses[i].addr.net ← addr.net };
      addrInfo.addresses[i].reply ← TRUE;
      IF NOT replyRecvd THEN NOTIFY cond;
      replyRecvd ← TRUE;
      END;
   TestReply: ENTRY PROC[i: CARDINAL] RETURNS[yes: BOOLEAN] = INLINE
      { yes ← addrInfo.addresses[i].reply;
        addrInfo.addresses[i].reply ← FALSE };
   More: ENTRY PROC RETURNS[yes: BOOLEAN] =
      { IF NOT replyRecvd THEN WAIT cond;
        yes ← replyRecvd; replyRecvd ← FALSE };
   SendProd: PROCEDURE =
      BEGIN
      sent: CARDINAL ← 0;
      lastHops:  CARDINAL = 3; --sorting limit--
      FOR wantedHops: CARDINAL IN [0..lastHops]
      DO lastPass: BOOLEAN = (wantedHops=lastHops);
         FOR i: CARDINAL IN [0..addrInfo.found)
         DO IF DeathWish[] OR sent >= addrInfo.found
            THEN GOTO getUsOutOfHere;
            IF addrInfo.preSorted
            OR (lastPass AND addrInfo.addresses[i].hops>=wantedHops)
            OR addrInfo.addresses[i].hops = wantedHops
            THEN BEGIN
                 b: PupDefs.PupBuffer = PupDefs.GetFreePupBuffer[];
                 b.source ← from;
                 b.pupType ← echoMe;
                 b.dest ← addrInfo.addresses[i].addr;
                 -- force socket number, because database may not have GV test-mode socket numbers --
                 b.dest.socket ← ProtocolDefs.RegServerPollingSocket;
                 b.pupWords[0] ← i;
                 PupDefs.SetPupContentsWords[b, 1];
                 PupDefs.PupRouterSendThis[b];
                 sent ← sent + 1;
                 END;
         REPEAT
            getUsOutOfHere => EXIT -- from outer loop! --
         ENDLOOP;
      ENDLOOP;
      END;
   Slurp: PROCEDURE =
      BEGIN
      UNTIL DeathWish[]
      DO b: PupDefs.PupBuffer = socket.get[];
         IF b # NIL
         THEN BEGIN
              SELECT b.pupType FROM
                iAmEcho =>
                  IF b.pupWords[0] < addrInfo.found
                  THEN NoteReply[b.pupWords[0], b.source];
              ENDCASE => NULL;
              PupDefs.ReturnFreePupBuffer[b];
              END;
      ENDLOOP;
      END;
   prodder: PROCESS = FORK SendProd[];
   slurper: PROCESS = FORK Slurp[];
   Process.SetTimeout[@cond, Process.MsecToTicks[1500]];
   info.found ← FALSE;
   UNTIL info.found
   DO IF NOT More[] THEN EXIT-- time-out on "cond" --;
      FOR i: CARDINAL IN [0..addrInfo.found)
      UNTIL info.found
      DO IF TestReply[i]
         THEN BEGIN
              info.where ← addrInfo.addresses[i].addr;
              IF accept[info.where] THEN info.found ← TRUE;
              END;
      ENDLOOP;
   ENDLOOP;
   KillSiblings[];
   JOIN prodder;
   JOIN slurper;
   END;
   

GetGVRegServer: PROCEDURE RETURNS[ info: AddressInfo ] =
   BEGIN
   addressesInGVRS: CARDINAL = 10; -- we take the 10 closest --
   Work: PROCEDURE[addr: PupDefs.PupAddress] RETURNS[ BOOLEAN ] =
      BEGIN
      -- terminate if we have enough addresses --
      -- Note: EnumeratePupAddresses gives them closest first --
      IF info.found = LENGTH[info.addresses] THEN RETURN[TRUE];
      info.addresses[info.found] ← [
         addr:  [ net: addr.net, host: addr.host,
                  socket: ProtocolDefs.RegServerEnquirySocket],
         hops:  0 -- ignored, because of "preSorted" flag --,
         reply: FALSE ];
      info.found ← info.found + 1;
      RETURN[FALSE]
      END;
   -- Top-level contacting R-Servers.  Any method is valid!
   -- Try local broadcast, and try Name Lookup Server.
   -- Failing only means R-Servers are inaccessible; name might be ok.
   BEGIN
      info ← [done, TRUE, 0,
              DESCRIPTOR[ Storage.Node[addressesInGVRS*SIZE[AddressRec]],
                          addressesInGVRS ] ];
      [] ← Work[ [net:[0], host:[0], -- local broadcast --
                 socket: ProtocolDefs.RegServerEnquirySocket ] ];
      [] ← PupDefs.EnumeratePupAddresses["GrapevineRServer"L, Work !
               PupDefs.PupNameTrouble => GOTO no ];
      EXITS no => NULL;
   END;
   END;


GetGroupInfo: PROC[ who, local: BodyDefs.RName]
                 RETURNS[ info: AddressInfo ] =
   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;
       Count: PROC[member:BodyDefs.RName]RETURNS[done:BOOLEAN] =
          { apparent ← apparent + 1; done ← FALSE };
       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
              addr: PupDefs.PupAddress;
              addr.socket ← [0,0];
              PupDefs.GetPupAddress[@addr,
                                    connect !
                                     PupDefs.PupNameTrouble => GOTO cant];
              info.addresses[info.found] ← [
                 addr:  addr,
                 hops:  MIN[PupDefs.GetHopsToNetwork[addr.net],
                            LAST[HopCount]],
                 reply: FALSE ];
              info.found ← info.found + 1;
              IF local # NIL AND ProtocolDefs.IsLocal[addr]
              THEN { local.length ← 0; String.AppendString[local, member] };
              EXITS cant => NULL;
              END;
          ENDCASE => NULL -- ignore others --;
          END;
       NameInfoDefs.Enumerate[m.members, Count];
       info ← [done, FALSE, 0,
               DESCRIPTOR[Storage.Node[apparent*SIZE[AddressRec]],
                          apparent] ];
       NameInfoDefs.Enumerate[m.members, Access];
       NameInfoDefs.Close[m.members];
       END;
   ENDCASE => ERROR;
   END;


FindNearestServer: PUBLIC PROCEDURE[list: BodyDefs.RName,
                     accept: PROCEDURE[PupDefs.PupAddress]RETURNS[BOOLEAN] ]
                   RETURNS[info: LocateDefs.FoundServerInfo] =
   BEGIN
   gvName: STRING = "gv.gv"L;
   socket: PupDefs.PupSocket = PupDefs.PupSocketMake[
              local: PupTypes.fillInSocketID,
              remote:, ticks: PupDefs.veryLongWait ];
   addrInfo: AddressInfo = IF String.EquivalentString[list, gvName]
             THEN GetGVRegServer[]
             ELSE GetGroupInfo[list,NIL];
   SELECT addrInfo.outcome FROM
     badName => info ← [notFound[]];
     down => info ← [allDown[]];
     done =>
       BEGIN
       THROUGH [1..4] -- re-tries for lost packets --
       DO reply: ReplyInfo = Prod[socket, addrInfo, accept];
          IF reply.found THEN { info ←  [found[reply.where]]; EXIT }
       REPEAT
       FINISHED => info ← [allDown[]]
       ENDLOOP;
       Storage.Free[BASE[addrInfo.addresses]];
       END;
   ENDCASE => ERROR;
   PupDefs.PupSocketDestroy[socket];
   END --FindNearestServer--;

FindLocalServer: PUBLIC PROCEDURE[list, local: BodyDefs.RName]
                 RETURNS[ LocateDefs.FoundState ] =
   BEGIN
   addrInfo: AddressInfo = GetGroupInfo[list, local];
   SELECT addrInfo.outcome FROM
     badName => RETURN[ notFound ];
     down => RETURN[ allDown ];
     done => { Storage.Free[BASE[addrInfo.addresses]];
               RETURN[ IF local.length # 0 THEN found ELSE notFound ] };
   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--


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

ProtocolDefs.Init[];

END.