-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- BootServerNoDisk.mesa, HGM,  4-Nov-84  8:22:25

DIRECTORY
  AddressTranslation USING [Error, PrintError, StringToNetworkAddress],
  Ascii USING [CR],
  CmFile USING [Handle, TableError],
  Format USING [NetworkAddress, NetworkNumber, StringProc],
  Heap USING [systemZone],
  Process USING [Detach],
  Put USING [Text],
  String USING [AppendChar, AppendString],
  StringLookUp USING [noMatch],
  System USING [
    NetworkAddress, NetworkNumber, nullNetworkAddress, nullSocketNumber, SocketNumber],
  Token USING [FreeTokenString, Item, NetworkNumber],
  Unformat USING [Error, NetworkAddress],
  BootServer USING [Counters],
  BootServerTypes USING [BootFileRequest],
  Buffer USING [NSBuffer, ReturnBuffer],
  Driver USING [Network],
  Indirect USING [Close, OpenSection, NextValue],
  NSConstants USING [bootServerSocket],
  RouterInternal USING [SendPacket],
  Socket USING [
    ChannelHandle, Create,
    GetPacket, LocalAddressFromSocket,
    SetDestination, SetWaitTime, TimeOut],
  Stats USING [StatCounterIndex, StatIncr, StatsStringToIndex];

BootServerNoDisk: MONITOR
  IMPORTS
    CmFile, Format, Heap, Indirect, Process, Put,
    String, Token, Unformat,
    AddressTranslation, Buffer, RouterInternal, Socket, Stats
  EXPORTS Buffer =
  BEGIN

  Network: PUBLIC TYPE = Driver.Network;

  counters: BootServer.Counters ← [0, 0, 0, 0, 0, 0];
  simpleBootRequests: Stats.StatCounterIndex;
  streamBootRequests: Stats.StatCounterIndex;

  pleaseStop: BOOLEAN ← FALSE;
  remote: Chain ← NIL;
  
  z: UNCOUNTED ZONE = Heap.systemZone;

  Chain: TYPE = LONG POINTER TO ChainSlot;
  ChainSlot: TYPE = RECORD [
    next: Chain,
    source: System.NetworkNumber,
    dest: System.NetworkAddress,
    target: LONG STRING];


  ActivateServer: PUBLIC ENTRY PROCEDURE =
    BEGIN
    ScanParameterFile[];
    IF remote = NIL THEN RETURN;
    pleaseStop ← FALSE;
    Process.Detach[FORK Server[]];
    END;

  Server: PROCEDURE =
    BEGIN
    soc: Socket.ChannelHandle;
    localAddr: System.NetworkAddress;
    b: Buffer.NSBuffer;
    localAddr ← Socket.LocalAddressFromSocket[NSConstants.bootServerSocket];
    soc ← Socket.Create[localAddr, 0, 2];
    Socket.SetWaitTime[soc, 10000--ms--];
    DO
      b ← NIL;
      b ← Socket.GetPacket[soc ! Socket.TimeOut => CONTINUE];
      IF b = NIL THEN LOOP;
      IF b.ns.packetType = bootServerPacket THEN
        BEGIN
        in: Driver.Network = b.network;
        header: LONG POINTER TO BootServerTypes.BootFileRequest =
          LOOPHOLE[@b.ns.nsWords[0]];
        FOR finger: Chain ← remote, finger.next UNTIL finger = NIL DO
          IF in.netNumber = finger.source THEN
            BEGIN
	    IF finger.dest = System.nullNetworkAddress THEN GOTO Reject;
	    Socket.SetDestination[b, finger.dest];
	    EXIT;
	    END;
	  REPEAT FINISHED => GOTO Reject;
          ENDLOOP;
        SELECT header.etherBootPacketType FROM
          simpleRequest =>
            BEGIN
            Stats.StatIncr[simpleBootRequests];
            counters.microcodeBootFilesRequested ←
              counters.microcodeBootFilesRequested + 1;
            END;
          sppRequest =>
            BEGIN
            Stats.StatIncr[streamBootRequests];
            counters.bootFilesRequested ← counters.bootFilesRequested + 1;
            END;
          ENDCASE => GOTO Reject;
	RouterInternal.SendPacket[b];  -- Socket.PutPacket smashes b.ns.source
	b ← NIL;
	EXITS Reject => NULL;
        END;
      IF b # NIL THEN Buffer.ReturnBuffer[b];
      ENDLOOP;
    END;

  ScanParameterFile: PROCEDURE =
    BEGIN
    cmFile: CmFile.Handle;
    Option: TYPE = {remote};
    NextValue: PROCEDURE [
      h: CmFile.Handle, table: LONG DESCRIPTOR FOR ARRAY Option OF LONG STRING]
      RETURNS [Option] = LOOPHOLE[Indirect.NextValue];
    optionTable: ARRAY Option OF LONG STRING ← [remote: "Remote"L];
    cmFile ← Indirect.OpenSection["BootServer"L];
    IF cmFile = NIL THEN RETURN;
    DO
      option: Option;
      option ← NextValue[
        cmFile, DESCRIPTOR[optionTable] !
        CmFile.TableError =>
          BEGIN
	  IF name[0] # '; THEN Message["Unrecognized parameter: ", name];
	  RETRY;
	  END];
      SELECT option FROM
        LOOPHOLE[StringLookUp.noMatch] => EXIT;
        remote =>
	  BEGIN
	  source: System.NetworkNumber = Token.NetworkNumber[cmFile];
	  temp: LONG STRING ← Token.Item[cmFile, FALSE];
	  new: Chain ← z.NEW[ChainSlot];
	  new↑ ← [NIL, source, System.nullNetworkAddress, z.NEW[StringBody[temp.length]]];
	  String.AppendString[new.target, temp];
	  [] ← Token.FreeTokenString[temp];
	  new.dest ← GetAddress[new.target, NSConstants.bootServerSocket ! Trouble => CONTINUE];
	  IF remote = NIL THEN remote ← new
	  ELSE
	    BEGIN
	    FOR finger: Chain ← remote, finger.next DO
	      IF finger.next = NIL THEN
	        BEGIN
		finger.next ← new;
		EXIT;
		END;
	      ENDLOOP;
	    END;
          MessageNet[new];
	  END;
        ENDCASE => ERROR;
      ENDLOOP;
    Indirect.Close[cmFile];
    END;

  ForgetParameters: PROCEDURE =
    BEGIN
    UNTIL remote = NIL DO
      temp: Chain ← remote;
      remote ← remote.next;
      z.FREE[@temp.target];
      z.FREE[@temp];
      ENDLOOP;
    END;

  MessageNet: PROCEDURE [new: Chain] =
    BEGIN
    text: STRING = [200];
    String.AppendString[text, "Forwarding NS Boot requests from "L];
    AppendNetNumber[text, new.source];
    String.AppendString[text, " to "L];
    String.AppendString[text, new.target];
    String.AppendString[text, " ("L];
    AppendNetworkAddress[text, new.dest];
    String.AppendString[text, ")"L];
    LogString[text];
    END;

  Message: PROCEDURE [one, two, three, four: LONG STRING ← NIL] =
    BEGIN
    text: STRING = [200];
    String.AppendString[text, one];
    IF two # NIL THEN String.AppendString[text, two];
    IF three # NIL THEN String.AppendString[text, three];
    IF four # NIL THEN String.AppendString[text, four];
    LogString[text];
    END;

  AppendNetNumber: PROCEDURE [string: LONG STRING, net: System.NetworkNumber] =
    BEGIN
    Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN String.AppendString[string, s]; END;
    Format.NetworkNumber[Append, net, productSoftware];
    END;

  AppendNetworkAddress: PROCEDURE [string: LONG STRING, addr: System.NetworkAddress] =
    BEGIN
    Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
      BEGIN String.AppendString[string, s]; END;
    Format.NetworkAddress[Append, addr, productSoftware];
    END;

  LogString: PROCEDURE [text: LONG STRING] =
    BEGIN
    String.AppendChar[text, '.];
    String.AppendChar[text, Ascii.CR];
    Put.Text[NIL, text];
    END;

	
  Trouble: ERROR [reason: LONG STRING] = CODE;
  GetAddress: PROCEDURE [host: LONG STRING, socket: System.SocketNumber]
    RETURNS [addr: System.NetworkAddress] =
    BEGIN
    localFailed: BOOLEAN ← FALSE;
    IF host = NIL THEN ERROR Trouble ["NIL => Address Fault"L];
    addr ← Unformat.NetworkAddress[host, octal !
      Unformat.Error => BEGIN localFailed ← TRUE; CONTINUE; END ];
    IF localFailed THEN
      BEGIN
      addr ← AddressTranslation.StringToNetworkAddress[host !
        AddressTranslation.Error =>
	  BEGIN
	  temp: STRING = [200];
	  proc: Format.StringProc = {String.AppendString[temp, s]};
	  AddressTranslation.PrintError[errorRecord, proc];
	  ERROR Trouble[temp];
	  END].addr;
        addr.socket ← socket;  -- CH returns trash in socket
      END;
    IF addr.socket = System.nullSocketNumber THEN addr.socket ← socket;
    END;

  simpleBootRequests ← Stats.StatsStringToIndex["Simple Boot Requests"];
  streamBootRequests ← Stats.StatsStringToIndex["Stream Boot Requests"];
  ActivateServer[];
  END.