-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- PupNameServerHot.mesa, HGM, 22-Jan-84  7:34:51

DIRECTORY
  Format USING [HostNumber],
  Inline USING [LongCOPY],
  Mopcodes USING [zEXCH],
  Process USING [Detach, Yield],
  String USING [AppendLongNumber, AppendString, WordsForString],
  System USING [HostNumber],

  Stats USING [StatCounterIndex, StatIncr, StatGetCounter],
  Buffer USING [NSBuffer, ReturnBuffer],
  Socket USING [
    ChannelHandle, GetPacketBytes, PutPacket, ReturnBuffer, SetPacketBytes,
    SetPacketWords],
  NameServerDefs USING [
    nameStatsRequest, nameStatsReply,
    nameToCacheRequest, addressToCacheRequest, hereIsCacheEntry,
    CacheEntry, NameStatsEntry,
    nameVersion, statSend, statHits, statNone,
    ForceNameIntoCache, ForceAddressIntoCache,
    SearchCacheForName, SearchCacheForAddress],
  NetDirDefs USING [maxAddrsPerEntry],
  PupWireFormat USING [BcplLongNumber, MesaToBcplLongNumber],
  PupDefs USING [
    AppendStringBodyToPupBuffer, GetPupContentsBytes, MoveStringBodyToPupBuffer,
    ParsePupAddressConstant, PupAddress, PupBuffer, PupRouterSendThis,
    ReturnPup, PupSocketID,
    SendPup, SetPupContentsWords, SwapPupSourceAndDest];

PupNameServerHot: MONITOR
  IMPORTS
    Format, Inline, Process, String, Stats, Socket, NameServerDefs,
    Buffer, PupWireFormat, PupDefs
  EXPORTS NameServerDefs =
  BEGIN OPEN Stats, NameServerDefs, PupDefs;


  busy: PUBLIC BOOLEAN ← FALSE;
  -- Before OISCPNameServer was added, this didn't need to be a monitor because only one process called into this module.  busy is the only monitor data.

  statName, statAddress, statXlation: PUBLIC StatCounterIndex;
  statConst, statBusy: PUBLIC StatCounterIndex;


  PupNameServer: PUBLIC ENTRY PROCEDURE [b: PupBuffer] =
    BEGIN
    SELECT b.pup.pupType FROM
      nameLookup =>
        BEGIN
        StatIncr[statName];
        IF ~busy THEN
          BEGIN
          busy ← TRUE;
          Process.Detach[FORK PupNameLookup[b]];
          Process.Yield[];
          END
        ELSE BEGIN StatIncr[statBusy]; Buffer.ReturnBuffer[b]; END;
        END;
      addressLookup =>
        BEGIN
        StatIncr[statAddress];
        IF ~busy THEN
          BEGIN
          busy ← TRUE;
          Process.Detach[FORK PupAddressLookup[b]];
          Process.Yield[];
          END
        ELSE BEGIN StatIncr[statBusy]; Buffer.ReturnBuffer[b]; END;
        END;
      NameServerDefs.nameToCacheRequest =>
        BEGIN
        IF ~busy THEN
          BEGIN
          busy ← TRUE;
          Process.Detach[FORK NameCacheLookup[b]];
          Process.Yield[];
          END
        ELSE BEGIN StatIncr[statBusy]; Buffer.ReturnBuffer[b]; END;
        END;
      NameServerDefs.addressToCacheRequest =>
        BEGIN
        IF ~busy THEN
          BEGIN
          busy ← TRUE;
          Process.Detach[FORK AddressCacheLookup[b]];
          Process.Yield[];
          END
        ELSE BEGIN StatIncr[statBusy]; Buffer.ReturnBuffer[b]; END;
        END;
      NameServerDefs.nameStatsRequest => NameServerStats[b];
      ENDCASE => BEGIN StatIncr[statMouseTrap]; Buffer.ReturnBuffer[b]; END;
    END;

  PupNameLookup: PROCEDURE [b: PupBuffer] =
    BEGIN

    -- This kludgy looking structure is just an easy way of moving what would otherwise be global data into our local frame.
    oldAddrs, newAddrs: ARRAY [0..NetDirDefs.maxAddrsPerEntry) OF PupAddress;
    old, new: CARDINAL;
    InitAddrLists: PROCEDURE =
      BEGIN  -- initialize to a single empty item
      oldAddrs[0] ← [[0], [0], [0, 0]];
      old ← 1;
      new ← 0;
      END;
    CrossPort: PROCEDURE [a: PupAddress] =
      BEGIN
      FOR i: CARDINAL IN [0..old) DO
        b: PupAddress ← oldAddrs[i];
        IF ~(a.net = b.net OR a.net = 0 OR b.net = 0) THEN LOOP;
        IF ~(a.host = b.host OR a.host = 0 OR b.host = 0) THEN LOOP;
        IF ~(a.socket = b.socket OR a.socket = [0, 0] OR b.socket = [0, 0]) THEN
          LOOP;
        -- it got past the filter, add it to the list
        IF new = NetDirDefs.maxAddrsPerEntry THEN ERROR;
        IF b.net = 0 THEN b.net ← a.net;
        IF b.host = 0 THEN b.host ← a.host;
        IF b.socket = [0, 0] THEN b.socket ← a.socket;
        newAddrs[new] ← b;
        new ← new + 1;
        ENDLOOP;
      END;
    ResetAddrLists: PROCEDURE =
      BEGIN  -- flush old, move new to old
      FOR i: CARDINAL IN [0..new) DO oldAddrs[i] ← newAddrs[i]; ENDLOOP;
      old ← new;
      new ← 0;
      END;

    target: LONG POINTER TO ARRAY [0..0) OF PupAddress;
    length: CARDINAL;
    i, j: CARDINAL;
    c: CHARACTER;
    s: STRING = [50];
    a: PupAddress;
    InitAddrLists[];
    length ← GetPupContentsBytes[b];
    SwapPupSourceAndDest[b];  -- Fixup source net number 
    IF length ~IN (0..200) THEN
      BEGIN ReturnPupError[b, illegalLength]; RETURN; END;
    i ← 0;
    UNTIL i = length DO
      s.length ← j ← 0;
      UNTIL i = length DO
        -- collect a string until we come to a + or the end
        c ← b.pup.pupChars[i];
        i ← i + 1;
        SELECT c FROM
          '  => LOOP;  -- skip blanks (how did they get this far?)
          '+ => EXIT;
          '#, '|, IN ['0..'9] => NULL;  -- as in 5#30#123|456
          '-, '/, IN ['A..'Z], IN ['a..'z] => NULL;  -- MAXC, Maxc, maxc
          ENDCASE => BEGIN ReturnPupError[b, illegalCharacter]; RETURN; END;
        IF j = s.maxlength THEN BEGIN ReturnPupError[b, nameTooLong]; RETURN; END;
        s[j] ← c;
        j ← j + 1;
        ENDLOOP;
      IF j = 0 THEN BEGIN ReturnPupError[b, empty]; RETURN; END;
      s.length ← j;
      a ← [[0], [0], [0, 0]];
      BEGIN
      found, notFound: BOOLEAN ← FALSE;
      CrossAddrs: PROCEDURE [ce: CacheEntry] =
        BEGIN
        IF LENGTH[ce.addrs] = 0 THEN BEGIN notFound ← TRUE; RETURN; END;
        FOR k: CARDINAL IN [0..LENGTH[ce.addrs]) DO CrossPort[ce.addrs[k]]; ENDLOOP;
	found ← TRUE;
	END;
      SELECT TRUE FROM
        ParsePupAddressConstant[@a, s] => GOTO Constant;
        SearchCacheForName[s, CrossAddrs] AND found => GOTO CacheHit;
	notFound => GOTO NotFound;
        ForceNameIntoCache[s, CrossAddrs] AND found => GOTO CacheHit;
	notFound => GOTO NotFound;
        ENDCASE => GOTO Ignore;
      EXITS
        Constant => BEGIN CrossPort[a]; StatIncr[statConst]; END;
        CacheHit => NULL;
        NotFound => BEGIN ReturnPupError[b, nameNotFound]; RETURN; END;
	Ignore =>
          BEGIN  -- disk locked out or something
          Buffer.ReturnBuffer[b];
          busy ← FALSE;
          RETURN;
          END;
      END;
      ResetAddrLists[];
      ENDLOOP;
    IF old = 0 THEN BEGIN ReturnPupError[b, empty]; RETURN; END;
    target ← LOOPHOLE[@b.pup.pupBody];
    FOR i: CARDINAL IN [0..old) DO target[i] ← oldAddrs[i]; ENDLOOP;
    SendPup[b, nameIs, 2*old*SIZE[PupAddress]];
    busy ← FALSE;
    END;

  NameCacheLookup: PROCEDURE [b: PupBuffer] =
    BEGIN
    CopyCacheEntry: PROCEDURE [ce: CacheEntry] =
      BEGIN
      MoveCacheEntryToPupBuffer[b, ce];
      END;
    s: STRING = [50];
    length: CARDINAL ← GetPupContentsBytes[b];
    SwapPupSourceAndDest[b];  -- Fixup source net number 
    IF length ~IN (0..200) THEN
      BEGIN ReturnPupError[b, illegalLength]; RETURN; END;
    FOR i: CARDINAL IN [0..length) DO
      c: CHARACTER ← b.pup.pupChars[i];
      SELECT c FROM
        '#, '|, IN ['0..'9] => NULL;  -- as in 5#30#123|456
        '-, '/, IN ['A..'Z], IN ['a..'z] => NULL;  -- MAXC, Maxc, maxc
        ENDCASE => BEGIN ReturnPupError[b, illegalCharacter]; RETURN; END;
      IF s.length = s.maxlength THEN BEGIN ReturnPupError[b, nameTooLong]; RETURN; END;
      s[i] ← c;
      s.length ← i + 1;
      ENDLOOP;
    SELECT TRUE FROM
      SearchCacheForName[s, CopyCacheEntry] => NULL;
      ForceNameIntoCache[s, CopyCacheEntry] => NULL;
      ENDCASE => BEGIN Buffer.ReturnBuffer[b]; busy ← FALSE; RETURN; END;
    b.pup.pupType ← hereIsCacheEntry;
    PupRouterSendThis[b];
    busy ← FALSE;
    END;


  MoveCacheEntryToPupBuffer: PROCEDURE [b: PupBuffer, ce: CacheEntry] =
    BEGIN
    p: LONG POINTER ← @b.pup.pupWords;
    n: CARDINAL ← 0;
    (p+n)↑ ← 0;  -- File version number
    n ← n + SIZE[CARDINAL];
    (p+n)↑ ← LENGTH[ce.names];
    n ← n + SIZE[CARDINAL];
    FOR i: CARDINAL IN [0..LENGTH[ce.names]) DO
      words: CARDINAL ← String.WordsForString[ce.names[i].length];
      Inline.LongCOPY[to: (p+n), from: ce.names[i], nwords: words];
      n ← n + words;
      ENDLOOP;
    (p+n)↑ ← LENGTH[ce.addrs];
    n ← n + SIZE[CARDINAL];
    FOR i: CARDINAL IN [0..LENGTH[ce.addrs]) DO
      Inline.LongCOPY[to: (p+n), from: @ce.addrs[i], nwords: SIZE[PupAddress]];
      n ← n + SIZE[PupAddress];
      ENDLOOP;
    PupDefs.SetPupContentsWords[b, n];
    END;

  ErrorCode: TYPE = {
    illegalLength, illegalCharacter, nameTooLong, empty, nameNotFound, noName};

  ReturnPupError: PROCEDURE [b: PupBuffer, e: ErrorCode] =
    BEGIN
    s: STRING;
    SELECT e FROM
      illegalLength => s ← "Illegal Length"L;
      illegalCharacter => s ← "Illegal Character"L;
      nameTooLong => s ← "Name too long"L;
      nameNotFound => s ← "Name not found"L;
      noName => s ← "Host not in directory"L;
      empty => s ← "Inconsistent expression"L;
      ENDCASE => ERROR;
    b.pup.pupType ← nameError;
    MoveStringBodyToPupBuffer[b, s];
    PupRouterSendThis[b];
    busy ← FALSE;
    END;

  ReturnNSError: PROCEDURE [
    cH: Socket.ChannelHandle, b: Buffer.NSBuffer, e: ErrorCode] =
    BEGIN
    s: STRING;
    text: LONG POINTER TO PACKED ARRAY [0..0) OF CHARACTER;
    text ← LOOPHOLE[@b.ns.nsWords[3]];
    SELECT e FROM
      illegalLength => s ← "Illegal Length"L;
      illegalCharacter => s ← "Illegal Character"L;
      nameTooLong => s ← "Name too long"L;
      nameNotFound => s ← "Name not found"L;
      noName => s ← "Host not in directory"L;
      empty => s ← "Inconsistent expression"L;
      ENDCASE => ERROR;
    b.ns.nsWords[2] ← 3;  -- translationError
    FOR i: CARDINAL IN [0..s.length) DO text[i] ← s[i]; ENDLOOP;
    Socket.SetPacketBytes[b, 2*3 + s.length];
    Socket.PutPacket[cH, b];
    busy ← FALSE;
    END;

  WhoAmI: PROCEDURE [cH: Socket.ChannelHandle, b: Buffer.NSBuffer] =
    BEGIN
    target: LONG POINTER TO ARRAY [0..0) OF PupAddress = LOOPHOLE[@b.ns.nsWords[3]];
    s: STRING = [50];
    who: LONG POINTER TO System.HostNumber = LOOPHOLE[@b.ns.nsWords[3]];
    notFound: BOOLEAN ← FALSE;
    CopyAddrs: PROCEDURE [ce: CacheEntry] =
      BEGIN
      IF LENGTH[ce.addrs] = 0 THEN BEGIN notFound ← TRUE; RETURN; END;
      FOR i: CARDINAL IN [0..LENGTH[ce.addrs]) DO target[i] ← ce.addrs[i]; ENDLOOP;
      Socket.SetPacketWords[b, 3 + LENGTH[ce.addrs]*SIZE[PupAddress]];
      END;
    IF Socket.GetPacketBytes[b] # 6*2 THEN
      BEGIN ReturnNSError[cH, b, illegalLength]; RETURN; END;
    AppendHostNumber[s, who↑];
    SELECT TRUE FROM
      SearchCacheForName[s, CopyAddrs] => NULL;
      notFound => NULL;
      ForceNameIntoCache[s, CopyAddrs] => NULL;
      notFound => NULL;
      ENDCASE =>
        BEGIN  -- disk locked out or something
        Socket.ReturnBuffer[b];
        busy ← FALSE;
        RETURN;
        END;
    IF notFound THEN
      BEGIN ReturnNSError[cH, b, nameNotFound]; busy ← FALSE; RETURN; END;
    b.ns.nsWords[2] ← 2;  -- translationResponse
    Socket.PutPacket[cH, b];
    busy ← FALSE;
    END;

  PupAddressLookup: PROCEDURE [b: PupBuffer] =
    BEGIN
    CopyCacheInfo: PROCEDURE [ce: CacheEntry] =
      BEGIN
      IF LENGTH[ce.names] = 0 THEN RETURN;
      hit ← TRUE;
      PupDefs.MoveStringBodyToPupBuffer[b, ce.names[0]];
      END;
    AppendCacheInfo: PROCEDURE [ce: CacheEntry] =
      BEGIN
      IF LENGTH[ce.names] = 0 THEN RETURN;
      hit ← TRUE;
      AppendStringBodyToPupBuffer[b, ce.names[0]];
      END;
    hit: BOOLEAN ← FALSE;
    host, socket: PupAddress ← b.pup.address;
    host.socket ← [0, 0];
    socket.net ← [0];
    socket.host ← [0];
    SwapPupSourceAndDest[b];  -- Fixup source net number
    IF GetPupContentsBytes[b] # 2*SIZE[PupAddress] THEN
      BEGIN ReturnPupError[b, illegalLength]; RETURN; END;
    SELECT TRUE FROM
      SearchCacheForAddress[b.pup.address, CopyCacheInfo] => NULL;
      ForceAddressIntoCache[b.pup.address, CopyCacheInfo] => NULL;
      ENDCASE => GOTO DiskBusy;
    IF hit THEN GOTO SendThis;
    -- Raw address not found, try for something like Maxc+FTP
    SELECT TRUE FROM
      SearchCacheForAddress[host, CopyCacheInfo] => NULL;
      ForceAddressIntoCache[host, CopyCacheInfo] => NULL;
      ENDCASE => GOTO DiskBusy;
    IF ~hit THEN BEGIN ReturnPupError[b, nameNotFound]; busy ← FALSE; RETURN; END;
    AppendStringBodyToPupBuffer[b, "+"L];
    hit ← FALSE;
    SELECT TRUE FROM
      SearchCacheForAddress[socket, AppendCacheInfo] => NULL;
      ForceAddressIntoCache[socket, AppendCacheInfo] => NULL;
      ENDCASE => GOTO DiskBusy;
    IF ~hit THEN
      BEGIN -- socket not in table, use number
      Flip: PROCEDURE [PupDefs.PupSocketID] RETURNS [LONG INTEGER] = MACHINE
        CODE BEGIN Mopcodes.zEXCH END;
      s: STRING = [20];
      String.AppendLongNumber[s, Flip[socket.socket], 8];
      AppendStringBodyToPupBuffer[b, s];
      END;
    GOTO SendThis;
    EXITS
      DiskBusy => BEGIN Buffer.ReturnBuffer[b]; busy ← FALSE; END;
      SendThis =>
        BEGIN
        b.pup.pupType ← addressIs;
        PupRouterSendThis[b];
        busy ← FALSE;
        END;
    END;

  AddressCacheLookup: PROCEDURE [b: PupBuffer] =
    BEGIN
    CopyCacheEntry: PROCEDURE [ce: CacheEntry] =
      BEGIN
      MoveCacheEntryToPupBuffer[b, ce];
      END;
    SwapPupSourceAndDest[b];  -- Fixup source net number
    IF GetPupContentsBytes[b] # 2*SIZE[PupAddress] THEN
      BEGIN ReturnPupError[b, illegalLength]; RETURN; END;
    SELECT TRUE FROM
      SearchCacheForAddress[b.pup.address, CopyCacheEntry] => NULL;
      ForceAddressIntoCache[b.pup.address, CopyCacheEntry] => NULL;
      ENDCASE => BEGIN Buffer.ReturnBuffer[b]; busy ← FALSE; RETURN; END;
    b.pup.pupType ← hereIsCacheEntry;
    PupRouterSendThis[b];
    busy ← FALSE;
    END;

  AppendHostNumber: PROCEDURE [string: LONG STRING, host: System.HostNumber] =
    BEGIN
    Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN String.AppendString[string, s]; END;
    Format.HostNumber[Append, host, octal];
    END;

  NameServerStats: PUBLIC PROCEDURE [b: PupBuffer] =
    BEGIN
    Stat: PROCEDURE [stat: Stats.StatCounterIndex]
      RETURNS [PupWireFormat.BcplLongNumber] =
      BEGIN
      RETURN[PupWireFormat.MesaToBcplLongNumber[Stats.StatGetCounter[stat]]];
      END;
    nse: LONG POINTER TO NameStatsEntry ← LOOPHOLE[@b.pup.pupWords];
    nse↑ ← [
      version: nameVersion, nameRequests: Stat[statName],
      directoriesSend: Stat[statSend], cacheHits: Stat[statHits],
      cacheMisses: Stat[statNone]];
    ReturnPup[b, nameStatsReply, 2*SIZE[NameStatsEntry]];
    END;

  -- This lives here so that it can access busy.
  NSNameServer: PUBLIC ENTRY PROCEDURE [
    cH: Socket.ChannelHandle, b: Buffer.NSBuffer] =
    BEGIN
    StatIncr[statXlation];
    IF ~busy THEN
      BEGIN
      busy ← TRUE;
      Process.Detach[FORK WhoAmI[cH, b]];
      Process.Yield[];
      END
    ELSE BEGIN StatIncr[statBusy]; Socket.ReturnBuffer[b]; END;
    END;


  END.