-- Copyright (C) 1984, 1985  by Xerox Corporation. All rights reserved. 
-- NameInfo: communication with registration server

-- HGM, 14-Sep-85 22:23:40
-- Ted Wobber	  29-Aug-84 13:40:37
-- Andrew Birrell  August 30, 1982 12:00 pm

DIRECTORY
  BodyDefs USING [Connect, oldestTime, Password, Remark, RName, Timestamp],
  LocateDefs USING [FindRegServer, FoundServerInfo],
  NameInfoDefs USING [
    AuthenticateInfo, ConnectInfo, ExpandInfo, ListType, MembershipGrade,
    MemberInfo, MembershipLevel, Membership, NameType, Outcome, RemarkInfo,
    RListHandle, StampInfo],
  NameInfoPrivate USING [],
  NameInfoSpecialDefs USING [],
  NameUpdateDefs USING [],
  Process USING [GetCurrent],
  ProtocolDefs USING [
    CreateStream, DestroyStream, Enquire, Failed, Handle, IsLocal, MakeKey,
    ReceiveBoolean, ReceiveConnect, ReceiveRC, ReceiveRemark,
    RegServerEnquirySocket, ReturnCode, RSOperation, SendByte, SendNow,
    SendPassword, SendRName, SendString, SendRSOperation],
  PupDefs USING [AppendPupAddress, PupAddress],
  RListDefs USING [Close, Enumerate, Receive, RListHandle];

NameInfo: MONITOR
  IMPORTS LocateDefs, Process, ProtocolDefs, PupDefs, RListDefs
  EXPORTS NameInfoDefs, NameInfoPrivate, NameInfoSpecialDefs, NameUpdateDefs =

  BEGIN

  -- monitor FindRegServer calls
  lockedProcess: PROCESS ← NIL;
  canSearch: CONDITION;
  recursions: CARDINAL ← 0;


  -- Re-export RListDefs into NameInfoDefs --

  RListHandle: PUBLIC TYPE = RECORD [l: RListDefs.RListHandle];

  Enumerate: PUBLIC PROC [
    list: RListHandle, work: PROC [BodyDefs.RName] RETURNS [done: BOOLEAN]] = {
    RListDefs.Enumerate[list.l, work]};

  Close: PUBLIC PROC [list: RListHandle] = {RListDefs.Close[list.l]};



  Expand: PUBLIC PROC [
    name: BodyDefs.RName, oldStamp: BodyDefs.Timestamp ← BodyDefs.oldestTime]
    RETURNS [NameInfoDefs.ExpandInfo] =
    BEGIN
    info: NameInfoDefs.NameType;
    list: RListHandle;
    stamp: BodyDefs.Timestamp;
    Receive: PROC [str: ProtocolDefs.Handle] = {
      list ← [l: RListDefs.Receive[str]]};
    [info, stamp] ← GetCompound[name, oldStamp, Expand, Receive];
    SELECT info FROM
      noChange => RETURN[[noChange[]]];
      group => RETURN[[group[list, stamp]]];
      individual => RETURN[[individual[list, stamp]]];
      notFound => RETURN[[notFound[]]];
      protocolError => RETURN[[protocolError[]]];
      wrongServer => RETURN[[wrongServer[]]];
      allDown => RETURN[[allDown[]]];
      ENDCASE => RETURN[[protocolError[]]];
    END;


  GetMembers: PUBLIC PROC [
    name: BodyDefs.RName, oldStamp: BodyDefs.Timestamp ← BodyDefs.oldestTime]
    RETURNS [NameInfoDefs.MemberInfo] = {
    RETURN[GetGroupList[name, oldStamp, ReadMembers]]};

  GetOwners: PUBLIC PROC [
    name: BodyDefs.RName, oldStamp: BodyDefs.Timestamp ← BodyDefs.oldestTime]
    RETURNS [NameInfoDefs.MemberInfo] = {
    RETURN[GetGroupList[name, oldStamp, ReadOwners]]};

  GetFriends: PUBLIC PROC [
    name: BodyDefs.RName, oldStamp: BodyDefs.Timestamp ← BodyDefs.oldestTime]
    RETURNS [NameInfoDefs.MemberInfo] = {
    RETURN[GetGroupList[name, oldStamp, ReadFriends]]};


  GetGroupList: PUBLIC PROC [
    name: BodyDefs.RName, oldStamp: BodyDefs.Timestamp,
    op: ProtocolDefs.RSOperation] RETURNS [NameInfoDefs.MemberInfo] =
    BEGIN
    info: NameInfoDefs.NameType;
    list: RListHandle;
    stamp: BodyDefs.Timestamp;
    Receive: PROC [str: ProtocolDefs.Handle] = {
      list ← [l: RListDefs.Receive[str]]};
    [info, stamp] ← GetCompound[name, oldStamp, op, Receive];
    SELECT info FROM
      noChange => RETURN[[noChange[]]];
      group => RETURN[[group[list, stamp]]];
      individual => RETURN[[individual[]]];
      notFound => RETURN[[notFound[]]];
      protocolError => RETURN[[protocolError[]]];
      wrongServer => RETURN[[wrongServer[]]];
      allDown => RETURN[[allDown[]]];
      ENDCASE => RETURN[[protocolError[]]];
    END;


  GetCompound: PUBLIC PROC [
    name: BodyDefs.RName, stamp: BodyDefs.Timestamp, op: ProtocolDefs.RSOperation,
    receive: PROC [ProtocolDefs.Handle], reporter: PROC [LONG STRING] ← NIL]
    RETURNS [info: NameInfoDefs.NameType, newStamp: BodyDefs.Timestamp] =
    BEGIN
    rcCopy: ProtocolDefs.ReturnCode;
    GetListWork: PROC [str: ProtocolDefs.Handle]
      RETURNS [rc: ProtocolDefs.ReturnCode] =
      BEGIN
      [rc, newStamp] ← ProtocolDefs.Enquire[str, op, name, stamp];
      rcCopy ← rc;
      END;
    info ← Enquire[name, GetListWork, reporter];
    IF
      (info = individual
        AND (op = Expand OR op = ReadEntry OR op = ReadMailboxes)) OR info = group
      OR (op = ReadEntry AND rcCopy = [done, dead]) THEN
      receive[str ! ProtocolDefs.Failed => GOTO notQuite];
    ReleaseStream[];
    EXITS notQuite => {CloseStream[]; ReleaseStream[]; info ← allDown; }
    END;



  GetConnect: PUBLIC PROC [name: BodyDefs.RName, connect: BodyDefs.Connect]
    RETURNS [NameInfoDefs.ConnectInfo] =
    BEGIN
    ConnectWork: PROC [str: ProtocolDefs.Handle]
      RETURNS [rc: ProtocolDefs.ReturnCode] =
      BEGIN [rc, ] ← ProtocolDefs.Enquire[str, ReadConnect, name]; END;
    info: NameInfoDefs.NameType ← Enquire[name, ConnectWork];
    IF info = individual THEN
      ProtocolDefs.ReceiveConnect[
        str, connect ! ProtocolDefs.Failed => GOTO notQuite];
    ReleaseStream[];
    SELECT info FROM
      IN NameInfoDefs.ConnectInfo => RETURN[info];
      ENDCASE => RETURN[protocolError];
    EXITS notQuite => {CloseStream[]; ReleaseStream[]; RETURN[allDown]}
    END;



  GetRemark: PUBLIC PROC [name: BodyDefs.RName, remark: BodyDefs.Remark]
    RETURNS [NameInfoDefs.RemarkInfo] =
    BEGIN
    RemarkWork: PROC [str: ProtocolDefs.Handle]
      RETURNS [rc: ProtocolDefs.ReturnCode] =
      BEGIN [rc, ] ← ProtocolDefs.Enquire[str, ReadRemark, name]; END;
    info: NameInfoDefs.NameType = Enquire[name, RemarkWork];
    IF info = group THEN
      ProtocolDefs.ReceiveRemark[
        str, remark ! ProtocolDefs.Failed => GOTO notQuite];
    ReleaseStream[];
    SELECT info FROM
      IN NameInfoDefs.RemarkInfo => RETURN[info];
      ENDCASE => RETURN[protocolError];
    EXITS notQuite => {CloseStream[]; ReleaseStream[]; RETURN[allDown]}
    END;



  CheckStamp: PUBLIC PROC [
    name: BodyDefs.RName, oldStamp: BodyDefs.Timestamp ← BodyDefs.oldestTime]
    RETURNS [NameInfoDefs.StampInfo] =
    BEGIN
    info: NameInfoDefs.NameType;
    CheckStampWork: PROC [str: ProtocolDefs.Handle]
      RETURNS [rc: ProtocolDefs.ReturnCode] =
      BEGIN [rc, ] ← ProtocolDefs.Enquire[str, CheckStamp, name, oldStamp]; END;
    info ← Enquire[name, CheckStampWork];
    ReleaseStream[];
    SELECT info FROM
      IN NameInfoDefs.StampInfo => RETURN[info];
      ENDCASE => RETURN[protocolError];
    END;


  Authenticate: PUBLIC PROC [name: BodyDefs.RName, password: LONG STRING]
    RETURNS [NameInfoDefs.AuthenticateInfo] = {
    RETURN[AuthenticateKey[name, ProtocolDefs.MakeKey[password]]]};


  AuthenticateKey: PUBLIC PROC [name: BodyDefs.RName, key: BodyDefs.Password]
    RETURNS [NameInfoDefs.AuthenticateInfo] =
    BEGIN
    AuthWork: PROC [str: ProtocolDefs.Handle]
      RETURNS [rc: ProtocolDefs.ReturnCode] =
      BEGIN
      ProtocolDefs.SendRSOperation[str, Authenticate];
      ProtocolDefs.SendRName[str, name];
      ProtocolDefs.SendPassword[str: str, key: [0, 0, 0, 0], pw: key];
      ProtocolDefs.SendNow[str];
      rc ← ProtocolDefs.ReceiveRC[str];
      END;
    info: NameInfoDefs.NameType = Enquire[name, AuthWork];
    ReleaseStream[];
    SELECT info FROM
      IN NameInfoDefs.AuthenticateInfo => RETURN[info];
      ENDCASE => RETURN[protocolError];
    END;



  IsInList: PUBLIC PROC [
    name, member: BodyDefs.RName, level: NameInfoDefs.MembershipLevel,
    grade: NameInfoDefs.MembershipGrade, acl: NameInfoDefs.ListType]
    RETURNS [res: NameInfoDefs.Membership] =
    BEGIN
    IsInListWork: PROC [str: ProtocolDefs.Handle]
      RETURNS [rc: ProtocolDefs.ReturnCode] =
      BEGIN
      ProtocolDefs.SendRSOperation[str, IsInList];
      ProtocolDefs.SendRName[str, name];
      ProtocolDefs.SendRName[str, member];
      ProtocolDefs.SendByte[str, LOOPHOLE[grade]];
      ProtocolDefs.SendByte[str, LOOPHOLE[acl]];
      ProtocolDefs.SendByte[str, LOOPHOLE[level]];
      ProtocolDefs.SendNow[str];
      rc ← ProtocolDefs.ReceiveRC[str];
      END;
    info: NameInfoDefs.NameType = Enquire[name, IsInListWork];
    SELECT info FROM
      group =>
        res ←
          IF ProtocolDefs.ReceiveBoolean[
          str ! ProtocolDefs.Failed => GOTO notQuite] THEN yes ELSE no;
      individual, notFound => res ← notGroup;
      ENDCASE => res ← allDown;  -- includes various protocol errors --
    ReleaseStream[];
    EXITS notQuite => {CloseStream[]; ReleaseStream[]; res ← allDown; }
    END;



  Update: PUBLIC PROC [
    user: BodyDefs.RName, password: BodyDefs.Password,
    op: ProtocolDefs.RSOperation, target: BodyDefs.RName, value: LONG STRING ← NIL,
    newPwd: BodyDefs.Password ← [, , , ], reporter: PROC [LONG STRING] ← NIL]
    RETURNS [info: NameInfoDefs.Outcome] =
    BEGIN
    UpdateWork: PROC [str: ProtocolDefs.Handle]
      RETURNS [rc: ProtocolDefs.ReturnCode] =
      BEGIN
      ProtocolDefs.SendRSOperation[str, IdentifyCaller];
      ProtocolDefs.SendRName[str, user];
      ProtocolDefs.SendPassword[str, [0, 0, 0, 0], password];
      ProtocolDefs.SendNow[str];
      rc ← ProtocolDefs.ReceiveRC[str];
      IF rc = [BadRName, group] THEN rc ← [BadRName, notFound];
      IF rc = [done, individual] THEN
        BEGIN
        ProtocolDefs.SendRSOperation[str, op];
        ProtocolDefs.SendRName[str, target];
        SELECT op FROM
          CreateGroup, DeleteIndividual, DeleteGroup, AddSelf, DeleteSelf => NULL;
          ChangePassword, CreateIndividual =>
            ProtocolDefs.SendPassword[str, [0, 0, 0, 0], newPwd];
          ENDCASE => ProtocolDefs.SendString[str, value];
        ProtocolDefs.SendNow[str];
        rc ← ProtocolDefs.ReceiveRC[str];
        END;
      END;
    info ← Enquire[target, UpdateWork, reporter];
    ReleaseStream[];
    END;



  -- There is a cache of one stream and one address. --
  -- Access to this is under mutual exclusion, enforced by calls
  -- on ClaimStream and ReleaseStream --

  released: CONDITION;
  free: BOOLEAN ← TRUE;
  str: ProtocolDefs.Handle ← NIL;  -- cached R-Server stream --
  cacheAddr: PupDefs.PupAddress;  -- address of cached stream --
  addrHint: BOOLEAN ← FALSE;  -- whether to use cached address as a hint --

  ClaimStream: ENTRY PROC = {UNTIL free DO WAIT released ENDLOOP; free ← FALSE; };

  ReleaseStream: ENTRY PROC = {free ← TRUE; NOTIFY released};

  GetSearchLock: ENTRY PROC =
    BEGIN
    myProcess: PROCESS = Process.GetCurrent[];
    DO
      SELECT lockedProcess FROM
        NIL => {lockedProcess ← myProcess; EXIT};
        myProcess => {recursions ← recursions + 1; EXIT};  -- recursion
        ENDCASE => WAIT canSearch;
      ENDLOOP;
    END;

  ReleaseSearchLock: ENTRY PROC = {
    IF recursions # 0 THEN recursions ← recursions - 1
    ELSE {lockedProcess ← NIL; NOTIFY canSearch}};

  CleanUp: PUBLIC --NameInfoSpecialDefs-- PROC =
    BEGIN
    ClaimStream[];
    -- optimize for R-Server/M-Server internal stream --
    IF NOT ProtocolDefs.IsLocal[cacheAddr] THEN CloseStream[];
    ReleaseStream[];
    END;


  -- "SetServer" allows a client (typically Maintain) to provide
  -- a server address as a hint.

  SetServer: PUBLIC PROC [addr: PupDefs.PupAddress] =
    BEGIN ClaimStream[]; cacheAddr ← addr; addrHint ← TRUE; ReleaseStream[]; END;


  Enquire: PROC [
    name: BodyDefs.RName,
    EnquiryWork: PROC [ProtocolDefs.Handle] RETURNS [ProtocolDefs.ReturnCode],
    reporter: PROC [LONG STRING] ← NIL] RETURNS [info: NameInfoDefs.Outcome] =
    BEGIN
    -- slightly subtle optimisation: If we don't have a stream, or the
    -- stream we have times out,  we'll need to get one to an R-Server for
    -- GV.  Getting one early might save us from having to call
    -- FindRegServer later --
    Create: PROC =
      BEGIN
      cacheAddr.socket ← ProtocolDefs.RegServerEnquirySocket;
      IF reporter # NIL THEN
        BEGIN
        s: STRING = [64];
        PupDefs.AppendPupAddress[s, cacheAddr];
        reporter[s];
        END;
      str ← ProtocolDefs.CreateStream[cacheAddr];
      END;
    rc: ProtocolDefs.ReturnCode;
    oldBad: BOOLEAN ← FALSE;
    ClaimStream[];
    BEGIN
    AcceptGV: PROC [addr: PupDefs.PupAddress] RETURNS [BOOLEAN] =
      BEGIN
      IF str # NIL THEN ERROR;
      cacheAddr ← addr;
      Create[ ! ProtocolDefs.Failed => GOTO failed];
      RETURN[TRUE];
      EXITS failed => {CloseStream[]; RETURN[FALSE]}
      END;
    IF addrHint THEN  -- cached address hint set by client, so try it --
      {addrHint ← FALSE; CloseStream[]; [] ← AcceptGV[cacheAddr]};
    IF str # NIL  -- try cached stream first --
      THEN
      BEGIN
      rc ← EnquiryWork[str ! ProtocolDefs.Failed => GOTO streamGone];
      EXITS streamGone => CloseStream[];
      END;
    IF str = NIL THEN
      BEGIN
      -- Don't use cached address, so that we return to a near server--
      -- Note: The following call of FindRegServer doesn't call us
      -- back recursively!
      [] ← LocateDefs.FindRegServer["x.GV"L, AcceptGV];
      IF str = NIL THEN {info ← allDown; RETURN};
      rc ← EnquiryWork[str ! ProtocolDefs.Failed => GOTO notThere];
      END;
    IF rc.code = WrongServer THEN oldBad ← TRUE ELSE oldBad ← FALSE;
    EXITS notThere => {CloseStream[]; oldBad ← TRUE; };
    END;
    IF oldBad THEN
      BEGIN  -- need to find the correct R-Server --
      Accept: PROC [addr: PupDefs.PupAddress] RETURNS [BOOLEAN] =
        BEGIN
        ClaimStream[];
        addr.socket ← ProtocolDefs.RegServerEnquirySocket;
        IF str # NIL AND cacheAddr # addr THEN CloseStream[];
        IF str = NIL THEN
          BEGIN
          cacheAddr ← addr;
          Create[ ! ProtocolDefs.Failed => GOTO failed];
          END;
        RETURN[TRUE];
        EXITS failed => {CloseStream[]; ReleaseStream[]; RETURN[FALSE]}
        END;
      foundInfo: LocateDefs.FoundServerInfo;
      ReleaseStream[] -- for FindRegServer -- ;
      GetSearchLock[];  -- serialize calls to FindRegServer (to save processes)
      foundInfo ← LocateDefs.FindRegServer[name, Accept];
      ReleaseSearchLock[];
      WITH foundInfo SELECT FROM
        notFound => {info ← notFound; ClaimStream[]; RETURN};
        allDown => {info ← allDown; ClaimStream[]; RETURN};
        found =>
          BEGIN
          -- stream was claimed inside "Accept" --
          rc ← EnquiryWork[str ! ProtocolDefs.Failed => GOTO down];
          EXITS down => {CloseStream[]; info ← allDown; RETURN}
          END;
        ENDCASE => ERROR;
      END;
    info ←
      SELECT rc.code FROM
        noChange => noChange,
        done, BadRName =>
          SELECT rc.type FROM
            individual => individual,
            group => group,
            ENDCASE => notFound -- includes dead -- ,
        BadOperation, BadProtocol => protocolError,
        WrongServer => wrongServer,
        AllDown => allDown,
        BadPassword => badPwd,
        outOfDate => outOfDate,
        NotAllowed => notAllowed,
        ENDCASE => allDown;
    END;

  CloseStream: PROC = {
    IF str # NIL THEN {ProtocolDefs.DestroyStream[str]; str ← NIL}};


  END.