-- File: PupNameServer.mesa,  Last Edit: HGM March 17, 1981  1:06 AM

DIRECTORY
  InlineDefs USING [BcplLongNumber, MesaToBcplLongNumber],
  Mopcodes USING [zEXCH],
  Process USING [Detach, Yield],
  String USING [AppendChar, AppendLongNumber],

  AddressTranslation USING [AppendNetworkAddress],
  StatsDefs USING [StatCounterIndex, StatIncr, StatGetCounter],
  BufferDefs USING [OisBuffer],
  OISCP USING [ReturnFreeOisBuffer, GetOisPacketTextLength, SetOisPacketTextLength],
  Socket USING [ChannelAborted, ChannelHandle, PutPacket],
  SpecialSystem USING [HostNumber, NetworkAddress],
  System USING [NetworkAddress],
  NameServerDefs USING [
    nameStatsRequest, nameStatsReply, whoAmI, CacheEntry, NameStatsEntry,
    nameVersion, statSend, statHits, statNone, ForceNameIntoCache,
    SearchCacheForName, ForceAddressIntoCache, SearchCacheForAddress],
  NetDirDefs USING [maxAddrsPerEntry],
  PupDefs USING [
    GetPupContentsBytes, ReturnPup, ParsePupAddressConstant, PupAddress,
    PupSocketID, PupBuffer, PupRouterSendThis, ReturnFreePupBuffer,
    SwapPupSourceAndDest, MoveStringBodyToPupBuffer, AppendStringBodyToPupBuffer];

PupNameServerHot: MONITOR
  IMPORTS
    InlineDefs, Process, String,
    AddressTranslation, StatsDefs, OISCP, Socket, NameServerDefs, PupDefs
  EXPORTS System, NameServerDefs =
  BEGIN OPEN StatsDefs, NameServerDefs, PupDefs;


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

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


  PupNameServer: PUBLIC ENTRY PROCEDURE [b: PupBuffer] =
    BEGIN
    SELECT b.pupType FROM
      nameLookup =>
	BEGIN
	StatIncr[statName];
	IF ~busy THEN
	  BEGIN
	  busy ← TRUE;
	  Process.Detach[FORK PupNameLookup[b]];
	  Process.Yield[];
	  END
	ELSE BEGIN StatIncr[statBusy]; ReturnFreePupBuffer[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]; ReturnFreePupBuffer[b]; END;
	END;
      NameServerDefs.whoAmI =>
	BEGIN
	StatIncr[statWhoAmI];
	IF ~busy THEN
	  BEGIN
	  busy ← TRUE;
	  Process.Detach[FORK PupWhoAmI[b]];
	  Process.Yield[];
	  END
	ELSE BEGIN StatIncr[statBusy]; ReturnFreePupBuffer[b]; END;
	END;
      NameServerDefs.nameStatsRequest => NameServerStats[b];
      ENDCASE => BEGIN StatIncr[statMouseTrap]; ReturnFreePupBuffer[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;

    ce: CacheEntry;
    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];
    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.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]];
      SELECT TRUE FROM
	ParsePupAddressConstant[@a, s] =>
	  BEGIN CrossPort[a]; StatIncr[statConst]; END;
	((ce ← SearchCacheForName[s]) # NIL OR (ce ← ForceNameIntoCache[s]) # NIL)
	  =>
	  BEGIN
	  k: CARDINAL;
	  IF LENGTH[ce.addrs] = 0 THEN
	    BEGIN ReturnPupError[b, nameNotFound]; RETURN; END;
	  FOR k IN [0..LENGTH[ce.addrs]) DO CrossPort[ce.addrs[k]]; ENDLOOP;
	  END;
	ENDCASE =>
	  BEGIN -- disk locked out or something
	  ReturnFreePupBuffer[b];
	  busy ← FALSE;
	  RETURN;
	  END;
      ResetAddrLists[];
      ENDLOOP;
    IF old = 0 THEN BEGIN ReturnPupError[b, empty]; RETURN; END;
    target ← LOOPHOLE[@b.pupBody];
    FOR i IN [0..old) DO target[i] ← oldAddrs[i]; ENDLOOP;
    ReturnPup[b, nameIs, 2*old*SIZE[PupAddress]];
    busy ← FALSE;
    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.pupType ← nameError;
    MoveStringBodyToPupBuffer[b, s];
    SwapPupSourceAndDest[b];
    PupRouterSendThis[b];
    busy ← FALSE;
    END;

  ReturnOiscpError: PROCEDURE [cH: Socket.ChannelHandle, b: BufferDefs.OisBuffer, e: ErrorCode] =
    BEGIN
    s: STRING;
    text: LONG POINTER TO PACKED ARRAY [0..0) OF CHARACTER;
    text ← LOOPHOLE[@b.ois.oisWords[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.ois.oisWords[2] ← 3;  -- translationError
    FOR i: CARDINAL IN [0..s.length) DO text[i] ← s[i]; ENDLOOP;
    OISCP.SetOisPacketTextLength[b, 2*3+s.length];
    b.ois.destination ← b.ois.source;
    Socket.PutPacket[cH, b ! Socket.ChannelAborted => CONTINUE];
    busy ← FALSE;
    END;

  PupWhoAmI: PROCEDURE [b: PupBuffer] =
    BEGIN
    target: LONG POINTER TO ARRAY [0..0) OF PupAddress;
    s: STRING = [50];
    who: LONG POINTER TO SpecialSystem.HostNumber = LOOPHOLE[@b.pupWords[0]];
    ce: CacheEntry;
    IF GetPupContentsBytes[b] # 6 THEN
      BEGIN ReturnPupError[b, illegalLength]; RETURN; END;
    AppendHostNumber[s,who↑];
    SELECT TRUE FROM
      (ce ← SearchCacheForName[s]) # NIL => NULL;
      (ce ← ForceNameIntoCache[s]) # NIL => NULL;
      ENDCASE =>
	BEGIN -- disk locked out or something
	ReturnFreePupBuffer[b];
	busy ← FALSE;
	RETURN;
	END;
    IF LENGTH[ce.addrs] = 0 THEN BEGIN ReturnPupError[b, nameNotFound]; RETURN; END;
    target ← LOOPHOLE[@b.pupBody];
    FOR i: CARDINAL IN [0..LENGTH[ce.addrs]) DO target[i] ← ce.addrs[i]; ENDLOOP;
    ReturnPup[b, nameIs, 2*LENGTH[ce.addrs]*SIZE[PupAddress]];
    busy ← FALSE;
    END;

  OISCPWhoAmI: PROCEDURE [cH: Socket.ChannelHandle, b: BufferDefs.OisBuffer] =
    BEGIN
    target: LONG POINTER TO ARRAY [0..0) OF PupAddress;
    s: STRING = [50];
    who: LONG POINTER TO SpecialSystem.HostNumber = LOOPHOLE[@b.ois.oisWords[3]];
    ce: CacheEntry;
    IF OISCP.GetOisPacketTextLength[b] # 6*2 THEN
      BEGIN ReturnOiscpError[cH, b, illegalLength]; RETURN; END;
    AppendHostNumber[s,who↑];
    SELECT TRUE FROM
      (ce ← SearchCacheForName[s]) # NIL => NULL;
      (ce ← ForceNameIntoCache[s]) # NIL => NULL;
      ENDCASE =>
	BEGIN -- disk locked out or something
	OISCP.ReturnFreeOisBuffer[b];
	busy ← FALSE;
	RETURN;
	END;
    IF LENGTH[ce.addrs] = 0 THEN
      BEGIN ReturnOiscpError[cH, b, nameNotFound]; RETURN; END;
    b.ois.oisWords[2] ← 2;  -- translationResponse
    target ← LOOPHOLE[@b.ois.oisWords[3]];
    FOR i: CARDINAL IN [0..LENGTH[ce.addrs]) DO target[i] ← ce.addrs[i]; ENDLOOP;
    OISCP.SetOisPacketTextLength[b, 2*(3+3)];
    b.ois.destination ← b.ois.source;
    Socket.PutPacket[cH, b ! Socket.ChannelAborted => CONTINUE];
    busy ← FALSE;
    END;

  NetworkAddress: PUBLIC TYPE = SpecialSystem.NetworkAddress;
  AppendHostNumber: PROCEDURE [s: STRING, h: SpecialSystem.HostNumber] =
    BEGIN
    temp: STRING = [50];
    na: SpecialSystem.NetworkAddress = [[0,0],h,[0]];
    first: CARDINAL;
    AddressTranslation.AppendNetworkAddress[temp,na];
    FOR i: CARDINAL IN [0..temp.length) DO
      first ← i+1;
      IF temp[i] = '# THEN EXIT;
      ENDLOOP;
    FOR i: CARDINAL IN [first..temp.length) DO
      IF temp[i] = '# THEN EXIT;
      String.AppendChar[s,temp[i]];
      ENDLOOP;
    END;

  PupAddressLookup: PROCEDURE [b: PupBuffer] =
    BEGIN
    ce: CacheEntry;
    host, socket: PupAddress ← b.address;
    host.socket ← [0, 0];
    socket.net ← [0];
    socket.host ← [0];
    IF GetPupContentsBytes[b] # 2*SIZE[PupAddress] THEN
      BEGIN ReturnPupError[b, illegalLength]; RETURN; END;
    IF (ce ← FindAddress[b.address]) = NIL THEN GOTO DiskBusy;
    IF LENGTH[ce.names] # 0 THEN
      BEGIN
      -- Check for ambigious name
      PupDefs.MoveStringBodyToPupBuffer[b, ce.names[0]];
      GOTO SendThis;
      END;
    IF (ce ← FindAddress[host]) = NIL THEN GOTO DiskBusy;
    IF LENGTH[ce.names] = 0 THEN BEGIN ReturnPupError[b, nameNotFound]; RETURN; END;
    PupDefs.MoveStringBodyToPupBuffer[b, ce.names[0]];
    -- Check for ambigious name
    IF socket.socket # [0, 0] THEN
      BEGIN
      IF (ce ← FindAddress[socket]) = NIL THEN GOTO DiskBusy;
      AppendStringBodyToPupBuffer[b, "+"L];
      IF LENGTH[ce.names] = 0 THEN
	BEGIN
	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
      ELSE AppendStringBodyToPupBuffer[b, ce.names[0]];
      END;
    GOTO SendThis;
    EXITS
      DiskBusy => BEGIN ReturnFreePupBuffer[b]; busy ← FALSE; END;
      SendThis =>
	BEGIN
	b.pupType ← addressIs;
	SwapPupSourceAndDest[b];
	PupRouterSendThis[b];
	busy ← FALSE;
	END;
    END;

  FindAddress: PROCEDURE [a: PupAddress] RETURNS [ce: CacheEntry] =
    BEGIN
    ce ← SearchCacheForAddress[a];
    IF ce # NIL THEN RETURN;
    ce ← ForceAddressIntoCache[a];
    END;

  NameServerStats: PUBLIC PROCEDURE [b: PupBuffer] =
    BEGIN
    Stat: PROCEDURE [stat: StatsDefs.StatCounterIndex]
      RETURNS [InlineDefs.BcplLongNumber] =
      BEGIN
      RETURN[InlineDefs.MesaToBcplLongNumber[StatsDefs.StatGetCounter[stat]]];
      END;
    nse: LONG POINTER TO NameStatsEntry ← LOOPHOLE[@b.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.
  OISCPNameServer: PUBLIC ENTRY PROCEDURE [cH: Socket.ChannelHandle, b: BufferDefs.OisBuffer] =
    BEGIN
    StatIncr[statXlation];
    IF ~busy THEN
      BEGIN
      busy ← TRUE;
      Process.Detach[FORK OISCPWhoAmI[cH, b]];
      Process.Yield[];
      END
    ELSE BEGIN StatIncr[statBusy]; OISCP.ReturnFreeOisBuffer[b]; END;
    END;


  END.