-- File: NetDirFileAlto.mesa,  Last Edit: HGM March 8, 1981  1:21 AM

DIRECTORY
  AltoFileDefs USING [FA],
  Inline USING [COPY, LowHalf],
  Process USING [Yield],
  Put USING [Line],
  Storage USING [Node, String],
  String USING [
    AppendChar, AppendDecimal, AppendString, UpperCase, WordsForString],
  StreamDefs USING [GetFA, JumpToFA],
  StringDefs USING [BcplSTRING],
  System USING [Pulses, GetClockPulses, PulsesToMicroseconds],
  Time USING [AppendCurrent],

  File USING [Capability, nullCapability],
  George USING [
    CreateInputStream, Destroy, GetLength, GetWord, GetWords, Handle,
    LookupExistingFile, SetIndex],

  NetDirDefs USING [
    Addr, AddrOffset, Attribute, Entry, EntryOffset, Header, last, Name,
    NameOffset, Offset, maxAddrsPerEntry, maxAddrsInFile, maxCharsPerName,
    maxEntryBufferLength, maxNamesInFile, maxNamesPerEntry, sizeOfBasicName],
  StatsDefs USING [StatCounterIndex, StatBump],
  Lock USING [LockDisk, UnlockDisk],
  NameServerDefs USING [CacheEntry, msg],
  PupTypes USING [PupAddress];

NetDirFileAlto: MONITOR
  IMPORTS
    Inline, Process, Put, Storage, StreamDefs, String, System, Time,
    George, StatsDefs, Lock, NameServerDefs
  EXPORTS NameServerDefs =
  BEGIN OPEN NetDirDefs;

  PupAddress: TYPE = PupTypes.PupAddress;
  CacheEntry: TYPE = NameServerDefs.CacheEntry;

  verbose: BOOLEAN = TRUE;
  fast: BOOLEAN = TRUE;

  diskBufferSize: CARDINAL = 350;
  maxStringWords: CARDINAL = (maxCharsPerName + 1 + 1)/2;
  maxNameSize: CARDINAL = sizeOfBasicName + maxStringWords;
  -- assuming that we don't want to look at the attributes
  maxNodeSize: CARDINAL = MAX[maxNameSize, SIZE[Entry], SIZE[Addr]];

  dir: File.Capability ← File.nullCapability;
  dirStream: George.Handle ← NIL; -- eats up a page for a buffer too
  version: CARDINAL ← 0; -- 0 if none/unknown
  numberOfNames, numberOfAddrs: CARDINAL;
  startOfAddrs, startOfNames, startOfEntrys: AltoFileDefs.FA;
  dirLength: LONG CARDINAL;

  statMsScanningFile: PUBLIC StatsDefs.StatCounterIndex;

  fileName: STRING = "Pup-Network.directory";

  CruftyNetworkDirectoryFile: ERROR = CODE;

  GetDirectoryFile: PUBLIC PROCEDURE RETURNS [File.Capability] =
    BEGIN RETURN[dir]; END;

  SearchNetDirForName: PUBLIC ENTRY PROCEDURE [key: STRING, ce: CacheEntry]
    RETURNS [BOOLEAN] =
    BEGIN
    diskBuffer: ARRAY [0..diskBufferSize) OF WORD;
    name: POINTER TO Name;
    inc, disp, remainder: CARDINAL;
    pulses: System.Pulses ← System.GetClockPulses[];
    IF dirStream = NIL THEN RETURN[FALSE];
    IF ~Lock.LockDisk[fileName, read, fast] THEN RETURN[FALSE];
    StreamDefs.JumpToFA[dirStream, @startOfNames];
    disp ← diskBufferSize;
    THROUGH [0..numberOfNames) DO
      IF disp > (diskBufferSize - maxNameSize) THEN
	BEGIN -- slide the buffer down and refill the tail
	Process.Yield[];
	remainder ← diskBufferSize - disp;
	Inline.COPY[to: @diskBuffer, from: @diskBuffer + disp, nwords: remainder];
	[] ← George.GetWords[dirStream, @diskBuffer + remainder, disp];
	Process.Yield[];
	disp ← 0;
	END;
      name ← LOOPHOLE[@diskBuffer[disp]];
      IF CheckStrings[key, @name.string] THEN
        BEGIN
        CopyThingsFromFile[ce, name.entry];
        EXIT;
        END;
      inc ← 2 + (name.string.length + 2)/2; -- Kludge, but ....
      IF (inc MOD 2) # 0 THEN inc ← inc + 1; -- MAXC requires this
      IF inc > 30 THEN ERROR CruftyNetworkDirectoryFile;
      disp ← disp + inc;
      REPEAT FINISHED => CopyMissedName[key, ce];
      ENDLOOP;
    pulses ← System.Pulses[System.GetClockPulses[] - pulses];
    StatsDefs.StatBump[  -- This might overflow 60 serconds
      statMsScanningFile,Inline.LowHalf[System.PulsesToMicroseconds[pulses]/1000]];
    RETURN[TRUE];
    END;

  SearchNetDirForAddress: PUBLIC ENTRY PROCEDURE [key: PupAddress, ce: CacheEntry]
    RETURNS [BOOLEAN] =
    BEGIN
    diskBuffer: ARRAY [0..diskBufferSize) OF WORD;
    addr: POINTER TO Addr;
    pulses: System.Pulses ← System.GetClockPulses[];
    inc, disp, remainder: CARDINAL;
    IF dirStream = NIL THEN RETURN[FALSE];
    IF ~Lock.LockDisk[fileName, read, fast] THEN RETURN[FALSE];
    StreamDefs.JumpToFA[dirStream, @startOfAddrs];
    disp ← diskBufferSize;
    THROUGH [0..numberOfAddrs) DO
      IF disp > (diskBufferSize - SIZE[Addr]) THEN
	BEGIN -- slide the buffer down and refill the tail
	Process.Yield[];
	remainder ← diskBufferSize - disp;
	Inline.COPY[to: @diskBuffer, from: @diskBuffer + disp, nwords: remainder];
	[] ← George.GetWords[dirStream, @diskBuffer + remainder, disp];
	Process.Yield[];
	disp ← 0;
	END;
      addr ← LOOPHOLE[@diskBuffer[disp]];
      IF key = addr.port THEN
        BEGIN
        CopyThingsFromFile[ce, addr.entry];
        EXIT;
        END;
      inc ← SIZE[Addr] + addr.numberOfAttributes*SIZE[Attribute];
      IF inc > 30 THEN ERROR CruftyNetworkDirectoryFile;
      disp ← disp + inc;
      REPEAT FINISHED => CopyMissedAddress[key, ce];
      ENDLOOP;
    pulses ← System.Pulses[System.GetClockPulses[] - pulses];
    StatsDefs.StatBump[  -- This might overflow 60 serconds
      statMsScanningFile,Inline.LowHalf[System.PulsesToMicroseconds[pulses]/1000]];
    RETURN[TRUE];
    END;

  CopyThingsFromFile: PROCEDURE [ce: CacheEntry, entOff: EntryOffset] =
    BEGIN
    addOff: AddrOffset;
    nameOff: NameOffset;
    diskBuffer: ARRAY [0..maxNodeSize) OF WORD;
    entry: POINTER TO Entry = LOOPHOLE[@diskBuffer];
    name: POINTER TO Name = LOOPHOLE[@diskBuffer];
    addr: POINTER TO Addr = LOOPHOLE[@diskBuffer];
    names: ARRAY [0..maxNamesPerEntry) OF STRING;
    addrs: ARRAY [0..maxAddrsPerEntry) OF PupAddress;
    n, words: CARDINAL;
    StreamDefs.JumpToFA[dirStream, @startOfEntrys];
    SetIndex[entOff];
    [] ← George.GetWords[dirStream, @diskBuffer, SIZE[Entry]];
    addOff ← entry.addr;
    nameOff ← entry.name;
    StreamDefs.JumpToFA[dirStream, @startOfAddrs];
    FOR n ← 0, n + 1 UNTIL addOff = last OR n = maxAddrsPerEntry DO
      SetIndex[addOff];
      [] ← George.GetWords[dirStream, addr, SIZE[Addr]];
      addrs[n] ← addr.port;
      addOff ← addr.next;
      ENDLOOP;
    words ← n*SIZE[PupAddress];
    ce.size ← ce.size + words;
    ce.addrs ← DESCRIPTOR[Storage.Node[words], n];
    StreamDefs.JumpToFA[dirStream, @startOfNames];
    Inline.COPY[to: BASE[ce.addrs], from: @addrs, nwords: words];
    FOR n ← 0, n + 1 UNTIL nameOff = last OR n = maxNamesPerEntry DO
      SetIndex[nameOff];
      [] ← George.GetWords[dirStream, addr, maxNodeSize];
      words ← String.WordsForString[name.string.length];
      ce.size ← ce.size + words;
      names[n] ← Storage.String[name.string.length];
      CopyString[names[n], @name.string];
      nameOff ← name.next;
      ENDLOOP;
    ce.size ← ce.size + n;
    ce.names ← DESCRIPTOR[Storage.Node[n], n];
    Inline.COPY[to: BASE[ce.names], from: @names, nwords: n];
    Lock.UnlockDisk[fileName, fast];
    END;

  CopyMissedName: PROCEDURE [key: STRING, ce: CacheEntry] =
    BEGIN
    ce.names ← DESCRIPTOR[Storage.Node[1], 1];
    ce.names[0] ← Storage.String[key.length];
    String.AppendString[ce.names[0], key];
    ce.size ← ce.size + 1 + String.WordsForString[key.length];
    Lock.UnlockDisk[fileName, fast];
    END;

  CopyMissedAddress: PROCEDURE [key: PupAddress, ce: CacheEntry] =
    BEGIN
    ce.addrs ← DESCRIPTOR[Storage.Node[SIZE[PupAddress]], 1];
    ce.addrs[0] ← key;
    ce.size ← ce.size + SIZE[PupAddress];
    Lock.UnlockDisk[fileName, fast];
    END;

  CheckStrings: PROCEDURE [key: STRING, name: POINTER TO StringDefs.BcplSTRING]
    RETURNS [BOOLEAN] =
    BEGIN OPEN String;
    i: CARDINAL;
    IF key.length # name.length THEN RETURN[FALSE];
    FOR i IN [0..key.length) DO
      IF UpperCase[key[i]] # UpperCase[name.char[i]] THEN RETURN[FALSE]; ENDLOOP;
    RETURN[TRUE]
    END;

  CopyString: PROCEDURE [key: STRING, name: POINTER TO StringDefs.BcplSTRING] =
    BEGIN
    key.length ← 0;
    FOR i: CARDINAL IN [0..name.length) DO
      String.AppendChar[key, name.char[i]]; ENDLOOP;
    END;

  SetIndex: PROCEDURE [offset: Offset] =
    BEGIN
    off: LONG CARDINAL = LONG[LOOPHOLE[offset, CARDINAL]];
    IF off > dirLength THEN ERROR CruftyNetworkDirectoryFile;
    George.SetIndex[dirStream, 2*off];
    END;


  GetDirectoryVersion: PUBLIC PROCEDURE RETURNS [CARDINAL] =
    BEGIN RETURN[version]; END;

  OpenDirectoryFile: PUBLIC PROCEDURE =
    BEGIN IF dirStream = NIL THEN [] ← ResetDirectoryFile[]; END;

  CloseDirectoryFile: PUBLIC ENTRY PROCEDURE =
    BEGIN IF dirStream # NIL THEN George.Destroy[dirStream]; dirStream ← NIL; END;

  ResetDirectoryFile: PUBLIC ENTRY PROCEDURE RETURNS [CARDINAL] =
    BEGIN
    header: Header;
    IF dirStream # NIL THEN George.Destroy[dirStream];
    dirStream ← NIL;
    IF dir = File.nullCapability THEN
      BEGIN
      dir ← George.LookupExistingFile[fileName];
      IF dir = File.nullCapability THEN RETURN[0];
      END;
    IF ~BlessDirectoryFile[dir, FALSE] THEN RETURN[0];
    dirStream ← George.CreateInputStream[dir];
    [] ← George.GetWords[dirStream, @header, SIZE[Header]];
    dirLength ← George.GetLength[dirStream];
    SetIndex[header.nameLookupTable];
    SetIndex[George.GetWord[dirStream]];
    StreamDefs.GetFA[dirStream, @startOfNames];
    SetIndex[header.addrLookupTable];
    SetIndex[George.GetWord[dirStream]];
    StreamDefs.GetFA[dirStream, @startOfAddrs];
    SetIndex[header.firstEntry];
    StreamDefs.GetFA[dirStream, @startOfEntrys];
    IF verbose THEN
      BEGIN
      text: STRING = [75];
      Time.AppendCurrent[text];
      String.AppendString[text, "  Current PupNameLookup Directory version is "L];
      String.AppendDecimal[text, header.version];
      String.AppendChar[text, '.];
      LogString[text];
      END;
    version ← header.version;
    numberOfNames ← header.numberOfNames;
    numberOfAddrs ← header.numberOfAddrs;
    RETURN[version];
    END;

  LogString: PROCEDURE [text: STRING] =
    BEGIN
    IF NameServerDefs.msg # NIL THEN Put.Line[NameServerDefs.msg, text];
    Put.Line[NIL, text];
    END;

  CheckDirectoryFile: PUBLIC PROCEDURE [file: File.Capability]
    RETURNS [ok: BOOLEAN] = BEGIN RETURN[BlessDirectoryFile[file, TRUE]]; END;

  BlessDirectoryFile: PROCEDURE [file: File.Capability, new: BOOLEAN]
    RETURNS [ok: BOOLEAN] =
    BEGIN
    header: Header;
    temp: George.Handle;
    length: LONG CARDINAL;
    message: STRING ← NIL;
    SetIndex: PROCEDURE [offset: Offset] =
      BEGIN
      off: LONG CARDINAL = LONG[LOOPHOLE[offset, CARDINAL]];
      IF off > length THEN ERROR CruftyNetworkDirectoryFile;
      George.SetIndex[temp, 2*off];
      END;
    CheckPosition: PROCEDURE [offset: Offset] =
      BEGIN
      off: LONG CARDINAL = LONG[LOOPHOLE[offset, CARDINAL]];
      IF off > length THEN ERROR CruftyNetworkDirectoryFile;
      END;
    ok ← TRUE;
    temp ← George.CreateInputStream[file];
    BEGIN
    ENABLE CruftyNetworkDirectoryFile => GOTO Bad;
    length ← George.GetLength[temp];
    message ← "Bad Checksum"L;
    TestFileChecksum[temp];
    George.SetIndex[temp, 0];
    IF George.GetWords[temp, @header, SIZE[Header]] # SIZE[Header] THEN
      BEGIN message ← "File is way too short"L; GOTO Bad; END;
    -- crude checks to see if file looks ok
    message ← "Bad internal pointer"L;
    IF header.numberOfNames > maxNamesInFile THEN GOTO Bad;
    SetIndex[header.nameLookupTable]; -- check name offsets
    THROUGH [0..header.numberOfNames) DO
      CheckPosition[George.GetWord[temp]]; ENDLOOP;
    IF header.numberOfAddrs > maxAddrsInFile THEN GOTO Bad;
    SetIndex[header.addrLookupTable]; -- check address offsets
    THROUGH [0..header.numberOfAddrs) DO
      CheckPosition[George.GetWord[temp]]; ENDLOOP;
    IF header.lengthOfEntries > maxEntryBufferLength THEN GOTO Bad;
    SetIndex[header.firstEntry]; -- check another word in the header
    EXITS
      Bad =>
	BEGIN
	ok ← FALSE;
	IF verbose THEN
	  BEGIN
	  text: STRING = [100];
	  Time.AppendCurrent[text];
	  String.AppendString[text, "  "L];
	  String.AppendString[text, IF new THEN "New"L ELSE "Old"L];
	  String.AppendString[text, " PupNameLookup Directory is crufty: "L];
	  String.AppendString[text, message];
	  String.AppendChar[text, '.];
	  LogString[text];
	  END;
	END;
    END;
    George.Destroy[temp];
    END;

  TestFileChecksum: PROCEDURE [stream: George.Handle] =
    BEGIN
    bufferSize: CARDINAL = 100;
    buffer: ARRAY [0..bufferSize) OF WORD;
    cs: WORD ← 0;
    hunk: CARDINAL ← bufferSize;
    left, length: LONG CARDINAL;
    length ← George.GetLength[stream];
    left ← length/2 - 1;
    George.SetIndex[stream, 0];
    UNTIL left = 0 DO
      IF left < bufferSize THEN hunk ← Inline.LowHalf[left];
      IF George.GetWords[stream, @buffer, hunk] # hunk THEN
	ERROR CruftyNetworkDirectoryFile;
      cs ← MoreChecksum[cs, @buffer, hunk];
      left ← left - hunk;
      Process.Yield[];
      ENDLOOP;
    IF cs # George.GetWord[stream] THEN ERROR CruftyNetworkDirectoryFile;
    END;

  MoreChecksum: PROCEDURE [cs: WORD, q: POINTER, size: CARDINAL] RETURNS [WORD] =
    BEGIN
    p: POINTER TO ARRAY [0..0) OF WORD = q;
    i, t: CARDINAL;
    FOR i IN [0..size) DO
      t ← cs + p[i];
      cs ← (IF cs > t THEN t + 1 ELSE t);
      cs ← (IF cs >= 100000B THEN cs*2 + 1 ELSE cs*2);
      ENDLOOP;
    IF cs = 177777B THEN cs ← 0;
    RETURN[cs];
    END;

  END.