-- Copyright (C) 1981, 1984, 1985  by Xerox Corporation. All rights reserved. 
-- Locate.mesa - Location of server by client --

-- HGM, 15-Sep-85  2:38:33
-- Andrew Birrell  20-Mar-81 16:56:49 --
-- Mike Schroeder March 21, 1981  10:59 AM --
-- Hankins:	 13-Jun-84 17:22:48	Klamath update (pup changes)

DIRECTORY
  BodyDefs USING [Connect, maxRNameLength, RName],
  Buffer USING [AccessHandle, GetBuffer, MakePool, ReturnBuffer],
  Heap USING [MakeNode, systemZone],
  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, GetHopsToNetwork, GetPupAddress, PupAddress, PupBuffer,
    PupNameTrouble, PupRouterSendThis, PupSocket, PupSocketDestroy, PupSocketID,
    PupSocketKick, PupSocketMake, SetPupContentsWords, veryLongWait],
  PupTypes USING [fillInSocketID, PupSocketID],
  String USING [AppendString, EquivalentString];

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

  BEGIN

  HopCount: TYPE = [0..37777B];
  AddressRec: TYPE = RECORD [
    addr: PupDefs.PupAddress, hops: HopCount, reply: BOOLEAN];
  AddressTable: TYPE = LONG 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];

  bufferPool: Buffer.AccessHandle;

  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 = Buffer.GetBuffer[
              type: pup, aH: bufferPool, function: send, size: smallBuffer];
            b.pup.source ← from;
            b.pup.pupType ← echoMe;
            b.pup.dest ← addrInfo.addresses[i].addr;
            -- force socket number, because database may not have GV test-mode socket numbers --
            b.pup.dest.socket ← ProtocolDefs.RegServerPollingSocket;
            b.pup.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.pup.pupType FROM
            iAmEcho =>
              IF b.pup.pupWords[0] < addrInfo.found THEN
                NoteReply[b.pup.pupWords[0], b.pup.source];
            ENDCASE => NULL;
          Buffer.ReturnBuffer[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[
      Heap.MakeNode[n: 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[
          Heap.MakeNode[n: 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;
        Heap.systemZone.FREE[@addrInfo.addresses.BASE];
        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 => {
        Heap.systemZone.FREE[@addrInfo.addresses.BASE];
        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] = {
    RETURN[TRUE]};

  ProtocolDefs.Init[];
  bufferPool ← Buffer.MakePool[send: 3, receive: 0];

  END.