-- Grapevine: Name Lookup Server cache --

-- [Juniper]<Grapevine>MS>NameLookup.mesa

-- Andrew Birrell,   4-Mar-82 15:37:12 

DIRECTORY
BTreeDefs	USING[ BTreeHandle, CreateAndInitializeBTree, Insert,
		       KeyNotFound, Lookup, ReleaseBTree, TestKeys ],
Inline		USING[ COPY ],
LogDefs		USING[ WriteChar, WriteLogEntry ],
Process		USING[ Detach, Pause, SecondsToTicks ],
PupDefs		USING[ GetBufferParms, GetFreePupBuffer,
		       GetPupContentsBytes, PupAddress, PupBuffer,
		       PupRouterBroadcastThis, PupSocket,
		       PupSocketDestroy, PupSocketMake,
		       ReturnFreePupBuffer, ReturnPup, SecondsToTocks,
		       SetPupContentsWords ],
PupTypes	USING[ fillInSocketID, mailSoc, miscSrvSoc ],
String		USING[ AppendString, AppendDecimal ],
Time		USING[ Current, Packed ],
VMDefs		USING[ AbandonFile, FileHandle, GetFileLength,
		       OpenFile, SetFileLength];

NameLookup: MONITOR
   IMPORTS BTreeDefs, Inline, LogDefs, Process, PupDefs, String, Time,
           VMDefs =

BEGIN

LogChar: PROC[c: CHARACTER] =
   { LogDefs.WriteChar['n]; LogDefs.WriteChar[c] };


LowerCase: PROCEDURE[c: CHARACTER] RETURNS[CHARACTER] = INLINE
   { RETURN[IF c IN ['A..'Z] THEN c - 'A + 'a ELSE c] };

IsFirstGE: BTreeDefs.TestKeys =
   BEGIN
   -- parameters a,b: DESC FOR ARRAY OF WORD returns[ BOOLEAN]--
   aC: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[a]];
   bC: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[b]];
   FOR i:CARDINAL IN [0..2*MIN[LENGTH[a],LENGTH[b]])
   DO IF LowerCase[aC[i]] < LowerCase[bC[i]] THEN RETURN[FALSE];
      IF LowerCase[aC[i]] > LowerCase[bC[i]] THEN RETURN[TRUE];
   ENDLOOP;
   RETURN[LENGTH[a] >= LENGTH[b]];
   END;

AreTheyEq: BTreeDefs.TestKeys =
   BEGIN
   aC: POINTER TO PACKED ARRAY OF CHARACTER =LOOPHOLE[BASE[a]];
   bC: POINTER TO PACKED ARRAY OF CHARACTER = LOOPHOLE[BASE[b]];
   IF LENGTH[a] = LENGTH[b]
   THEN FOR i:CARDINAL IN [0..2*LENGTH[a])
        DO IF LowerCase[aC[i]] # LowerCase[bC[i]] THEN EXIT;
        REPEAT FINISHED => RETURN[TRUE];
        ENDLOOP;
   RETURN[FALSE];
   END;

Key: TYPE = DESCRIPTOR FOR PACKED ARRAY OF CHARACTER;

Desc: PROCEDURE[ name: Key ]
        RETURNS[ DESCRIPTOR FOR ARRAY OF WORD ] = INLINE
   BEGIN
   IF LENGTH[name] MOD 2 # 0 THEN name[LENGTH[name]] ← '@;
   RETURN[ DESCRIPTOR[ BASE[name], (1+LENGTH[name])/2 ] ]
   END;

tree: BTreeDefs.BTreeHandle;
treeFile: VMDefs.FileHandle;

hitCount:   LONG CARDINAL ← 0;
missCount:  LONG CARDINAL ← 0;
entryCount: CARDINAL ← 0;

roysBuggingFlag: BOOLEAN ← FALSE; -- TRUE to provoke a VM bug --

OpenTree: INTERNAL PROC =
   BEGIN
   treeFile ← VMDefs.OpenFile[name: "NameLookup.BTree$"L,
                             options: oldOrNew,
                             cacheFraction: 5];
   IF roysBuggingFlag THEN VMDefs.SetFileLength[treeFile, [0,0]];
   tree ← BTreeDefs.CreateAndInitializeBTree [
        fileH: LOOPHOLE[treeFile],
        initializeFile: TRUE, useDefaultOrderingRoutines: FALSE,
        isFirstGreaterOrEqual: IsFirstGE,
        areTheyEqual: AreTheyEq ];
   entryCount ← 0;
   hitCount ← missCount ← 0;
   END;

CloseTree: INTERNAL PROC =
   { VMDefs.AbandonFile[LOOPHOLE[BTreeDefs.ReleaseBTree[tree]]] };

maxDirVersion: CARDINAL ← 0;

InitTree: ENTRY PROC =
   { OpenTree[] };

FlushTree: ENTRY PROC =
   BEGIN
   log: STRING = [40];
   String.AppendString[log, "Found NLS version "L];
   String.AppendDecimal[log, maxDirVersion];
   LogDefs.WriteLogEntry[log];
   LogDefs.WriteChar['V];
   CloseTree[]; OpenTree[];
   END;

Insert: ENTRY PROC[key: Key,
                   value: DESCRIPTOR FOR ARRAY OF WORD] = INLINE
  BEGIN
  keyDesc: DESCRIPTOR FOR ARRAY OF WORD = Desc[key];
  IF LENGTH[keyDesc] < 32 AND (LENGTH[keyDesc]+LENGTH[value]) <= 80
  THEN BEGIN
       BTreeDefs.Insert[tree, keyDesc, value];
       entryCount ← entryCount+1;
       END;
  END;

Lookup: ENTRY PROC[key: Key, value: DESCRIPTOR FOR ARRAY OF WORD]
           RETURNS[ length: CARDINAL ] = INLINE
   BEGIN
   length ← BTreeDefs.Lookup[tree, Desc[key], value];
   IF length = BTreeDefs.KeyNotFound
   THEN missCount ← missCount + 1
   ELSE hitCount ← hitCount + 1;
   END;



Interesting: PROC[key: Key,
                  local: PupDefs.PupAddress,
                  value: DESCRIPTOR FOR ARRAY OF PupDefs.PupAddress]
          RETURNS[BOOLEAN] =
   BEGIN
   -- In order to limit the size of the cache (or, to avoid designing a
   -- replacement algorithm), not all names are cached.
   -- Names which are composite ones (with "+"), or are really just address
   -- constants are never cached.  Names which have a socket number which
   -- is MTP or one of the Grapevine sockets, or map to this host are
   -- cached.  Other names are cached if the BTree is small.
   -- I believe that in all normal environments an 8 page BTree will cache
   -- (almost) all names that occur.
   -- There is also a restriction on BTree key lengths
   FOR i: CARDINAL IN [0..LENGTH[key])
   DO SELECT key[i] FROM
        IN ['a..'z], IN ['A..'Z], '- => NULL;
      ENDCASE => RETURN[FALSE];
   ENDLOOP;
   IF VMDefs.GetFileLength[treeFile].page < 8 THEN RETURN[TRUE];
   FOR j: CARDINAL IN [0..LENGTH[value])
   DO SELECT value[j].socket.b FROM
        PupTypes.mailSoc.b, IN [50B..57B] => RETURN[TRUE];
      ENDCASE =>
        IF value[j].host = local.host
        AND (value[j].net = local.net OR value[j].net = 0)
        THEN RETURN[TRUE];
   ENDLOOP;
   RETURN[FALSE]
   END;

LoadCache: PROC[ keyBuffer: PupDefs.PupBuffer, keyLength: CARDINAL ] =
   BEGIN
   nlsSoc: PupDefs.PupSocket = PupDefs.PupSocketMake[
      PupTypes.fillInSocketID,, PupDefs.SecondsToTocks[2]];
   local: PupDefs.PupAddress = nlsSoc.getLocalAddress[];
   b: PupDefs.PupBuffer ← NIL;
   newVersion: BOOLEAN ← FALSE;
   SameVersion: PROC[new: CARDINAL] RETURNS[ BOOLEAN ] =
      BEGIN
      IF new > maxDirVersion
      THEN BEGIN
           maxDirVersion ← new; newVersion ← TRUE;
           LogDefs.WriteLogEntry["Waiting for new NLS version"L];
           RETURN[FALSE]
           END
      ELSE RETURN[TRUE];
      END;
   -- The protocol is:
   --   us -> broadcast: NetDirVersion[0]
   --   nls -> us: NetDirVersion[his]
   --   us -> nls: NameLookup[key, pupID=hisVersion]
   --   nls -> us: NameIs[array of PupAddress, pupID=hisVersion]
   -- Because a particular nls has monotonic netDir versions, we know
   -- that the name value came from a netDir at least as recent as the
   -- version number he gave us.
   THROUGH [1..1] -- re-tries for lost packets --
   DO b ← PupDefs.GetFreePupBuffer[];
      b.pupWords[0] ← 0;
      PupDefs.SetPupContentsWords[b, 1];
      b.source ← local;
      b.dest.socket ← PupTypes.miscSrvSoc;
      b.pupType ← netDirVersion;
      b.pupID ← [0,0];
      PupDefs.PupRouterBroadcastThis[b];
      UNTIL (b ← nlsSoc.get[]) = NIL -- UNTIL timeout --
      DO SELECT b.pupType FROM
           netDirVersion =>
             IF PupDefs.GetPupContentsBytes[b] = 2
             AND b.pupWords[0] >= maxDirVersion
             THEN BEGIN
                  b.pupID ← [a:0, b:b.pupWords[0]];
                  Inline.COPY[from: @(keyBuffer.pupChars),
                              to: @(b.pupWords),
                              nwords: (1+keyLength)/2];
                  PupDefs.ReturnPup[b, nameLookup, keyLength];
                  END
             ELSE PupDefs.ReturnFreePupBuffer[b];
           nameIs =>
             IF b.pupID.b >= maxDirVersion
             THEN BEGIN
                  length: CARDINAL=PupDefs.GetPupContentsBytes[b]/2;
                  key: Key = DESCRIPTOR[@(keyBuffer.pupChars),
                                        keyLength];
                  IF SameVersion[b.pupID.b]
                  AND keyBuffer # NIL
                  AND Interesting[key, local,
                                 DESCRIPTOR[@(b.pupWords),
                                          length/SIZE[PupDefs.PupAddress]]]
                  THEN BEGIN
                       Insert[key, DESCRIPTOR[@(b.pupWords),length]];
                       LogChar['C];
                       END
                  ELSE LogChar['I];
                  GOTO done
                  END
             ELSE PupDefs.ReturnFreePupBuffer[b];
           nameError =>
             IF b.pupID.b >= maxDirVersion
             THEN BEGIN
                  IF SameVersion[b.pupID.b]
                  AND keyBuffer # NIL
                  THEN LogChar['E] --bad name--;
                  GOTO done
                  END
             ELSE PupDefs.ReturnFreePupBuffer[b];
         ENDCASE => PupDefs.ReturnFreePupBuffer[b] -- ignore --;
      ENDLOOP;
      LogChar['?] --timeout: re-try--;
   REPEAT
      done =>
        DO PupDefs.ReturnFreePupBuffer[b];
           -- be polite: swallow extra replies! --
           IF (b ← nlsSoc.get[]) = NIL THEN EXIT;
        ENDLOOP;
      FINISHED => LogChar['X] --failed--;
   ENDLOOP; 
   PupDefs.PupSocketDestroy[nlsSoc];
   IF keyBuffer # NIL THEN PupDefs.ReturnFreePupBuffer[keyBuffer];
   IF newVersion -- don't flush until NLS has settled down again! --
   THEN { Process.Pause[Process.SecondsToTicks[600]]; FlushTree[] };
   FinderEnded[];
   END;

finderRunning: BOOLEAN ← FALSE;
lastTime: Time.Packed ← 0; -- time of last forced poll --

PossibleLoadCache: ENTRY PROC[b: PupDefs.PupBuffer, length: CARDINAL] =
   BEGIN
   IF NOT finderRunning
   THEN { finderRunning ← TRUE; Process.Detach[FORK LoadCache[b,length]];
          lastTime ← Time.Current[] }
   ELSE IF b # NIL THEN PupDefs.ReturnFreePupBuffer[b];
   END;

FinderEnded: ENTRY PROC = INLINE
   { finderRunning ← FALSE };


NameLookupMain: PROC =
   BEGIN
   soc: PupDefs.PupSocket = PupDefs.PupSocketMake[
          PupTypes.miscSrvSoc, , PupDefs.SecondsToTocks[120]];
   maxWords: CARDINAL = PupDefs.GetBufferParms[].bufferSize;
   InitTree[];
   DO b: PupDefs.PupBuffer = soc.get[];
      IF b # NIL
      THEN SELECT b.pupType FROM
             nameLookup =>
               BEGIN
               key: Key = DESCRIPTOR[@(b.pupChars),
                                PupDefs.GetPupContentsBytes[b]];
               length: CARDINAL =
                          Lookup[key, DESCRIPTOR[@(b.pupWords),maxWords]];
               IF length # BTreeDefs.KeyNotFound
               THEN { LogChar['H]; PupDefs.ReturnPup[b, nameIs, length*2] }
               ELSE { LogChar['M]; PossibleLoadCache[b, LENGTH[key]] };
               END;
             whereIsUser => PupDefs.ReturnPup[b, userIs, 0];
           ENDCASE => PupDefs.ReturnFreePupBuffer[b];
      IF lastTime + 120 < Time.Current[]
      THEN -- check current net directory version --
           { PossibleLoadCache[NIL, 0] };
   ENDLOOP;
   END;

NameLookupProcess: PROCESS = FORK NameLookupMain[];

END.