-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- TestBootServer.mesa, HGM,  4-Nov-84  8:56:53

DIRECTORY
  Ascii USING [CR],
  Checksum USING [ComputeChecksum],
  Environment USING [Byte, bytesPerPage],
  Format USING [NetworkAddress, StringProc],
  FormSW USING [
    AllocateItemDescriptor, BooleanItem, ClientItemsProcType, CommandItem,
    ItemHandle, newLine, ProcType, StringItem],
  Heap USING [systemZone],
  MsgSW USING [Post],
  Put USING [Char, CR, Line, Text],
  Runtime USING [GetBcdTime],
  Stream USING [CompletionCode, Delete, GetBlock, Handle],
  String USING [AppendChar, AppendDecimal, AppendLongDecimal, AppendString],
  System USING [
    GetClockPulses, Microseconds, NetworkAddress,
    nullNetworkAddress, nullSocketNumber, Pulses, PulsesToMicroseconds, SocketNumber],
  Time USING [Append, Unpack],
  Tool USING [
    Create, UnusedLogName, MakeFormSW, MakeFileSW, MakeMsgSW, MakeSWsProc],
  ToolWindow USING [TransitionProcType],
  Unformat USING [Error, NetworkAddress],
  Window USING [Handle],

  AddressTranslation USING [Error, PrintError, StringToNetworkAddress],
  BootServer USING [StringToBFN],
  BootServerBasics USING [BootFileNumber],
  BootServerTypes USING [BootFileRequest],
  Buffer USING [NSBuffer],
  NetworkStream USING [
    CloseReply, ConnectionID, ConnectionSuspended, CreateTransducer,
    GetUniqueConnectionID],
  NSConstants USING [bootServerSocket],
  NSTypes USING [bytesPerSppHeader, bytesPerIDPHeader, ConnectionID],
  Socket USING [
    AssignNetworkAddress, ChannelHandle, Create, Delete, GetSendBuffer, GetPacket,
    PutPacket, ReturnBuffer, SetDestination, SetPacketWords, SetWaitTime,
    TimeOut];

TestBootServer: PROGRAM
  IMPORTS
    Checksum, Format, FormSW, Heap, MsgSW, Put, Runtime,
    Stream, String, System, Time, Tool, Unformat,
    AddressTranslation, BootServer, NetworkStream, Socket
  EXPORTS NetworkStream =
  BEGIN
  
  ConnectionID: PUBLIC TYPE = NSTypes.ConnectionID;

  z: UNCOUNTED ZONE = Heap.systemZone;

  msg, log, form: Window.Handle ← NIL;
  remoteAddress, bfn: LONG STRING ← NIL;
  silent: BOOLEAN ← FALSE;
  remoteAddr, localAddr: System.NetworkAddress;
  bootFileNumber: BootServerBasics.BootFileNumber;

  Init: PROCEDURE =
    BEGIN
    herald: STRING = [100];
    String.AppendString[herald, "TestBootServer of  "L];
    Time.Append[herald, Time.Unpack[Runtime.GetBcdTime[]]];
    [] ← Tool.Create[
      name: herald, makeSWsProc: MakeSWs, clientTransition: ClientTransition];
    END;

  MakeSWs: Tool.MakeSWsProc =
    BEGIN
    logFileName: STRING = [40];
    msg ← Tool.MakeMsgSW[window: window, lines: 1];
    form ← Tool.MakeFormSW[window: window, formProc: MakeItemArray];
    Tool.UnusedLogName[logFileName, "TestBootServer.log$"L];
    log ← Tool.MakeFileSW[window: window, name: logFileName];
    END;

  MakeItemArray: FormSW.ClientItemsProcType =
    BEGIN
    nItems: CARDINAL = 6;
    i: INTEGER ← -1;
    items ← FormSW.AllocateItemDescriptor[nItems];
    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "Microcode"L, place: FormSW.newLine, proc: Microcode];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "FastStream"L, proc: FastStream];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "SlowStream"L, proc: SlowStream];
    items[i ← i + 1] ← FormSW.BooleanItem[tag: "Silent"L, switch: @silent];
    items[i ← i + 1] ← FormSW.StringItem[tag: "BFN"L, string: @bfn, inHeap: TRUE];
    items[i ← i + 1] ← FormSW.StringItem[
      tag: "RemoteAddress"L, string: @remoteAddress, inHeap: TRUE,
      place: FormSW.newLine];
    IF (i + 1) # nItems THEN ERROR;
    RETURN[items, TRUE];
    END;

  ClientTransition: ToolWindow.TransitionProcType =
    BEGIN
    SELECT TRUE FROM
      old = inactive =>
        BEGIN
        localAddr ← Socket.AssignNetworkAddress[];
        remoteAddress ← z.NEW[StringBody[40]];
        String.AppendString[remoteAddress, "0#*#"L];
        bfn ← z.NEW[StringBody[40]];
        String.AppendString[bfn, "25200000000"L];
        END;
      new = inactive =>
        BEGIN
        z.FREE[@remoteAddress];
        z.FREE[@bfn];
        END;
      ENDCASE;
    END;

  FindParameters: PROCEDURE RETURNS [ok: BOOLEAN] =
    BEGIN
    ok ← TRUE;
    MsgSW.Post[msg, ""L];
    remoteAddr ← GetAddress[remoteAddress, NSConstants.bootServerSocket !
      Trouble =>
        BEGIN
	Put.Line[msg, reason];
        ok ← FALSE;
        CONTINUE;
        END];
    bootFileNumber ← BootServer.StringToBFN[
      bfn !
      ANY =>
        BEGIN
        MsgSW.Post[msg, "BFN is incorrectly specified; try again."L];
        ok ← FALSE;
        CONTINUE;
        END];
    END;

  Microcode: FormSW.ProcType =
    BEGIN
    target: System.NetworkAddress ← System.nullNetworkAddress;
    checksum: WORD ← 0;
    elapsed: System.Microseconds;
    words: LONG CARDINAL ← 0;
    missed, strays: CARDINAL ← 0;
    soc: Socket.ChannelHandle;
    b: Buffer.NSBuffer;
    hit, tail: BOOLEAN ← FALSE;
    packetNumber: CARDINAL ← 0;  -- first packet will be 1
    overheadBytes: CARDINAL =
      NSTypes.bytesPerIDPHeader +
        2*SIZE[simpleData BootServerTypes.BootFileRequest];
    beginTime, stopTime: System.Pulses;

    GotOne: PROCEDURE [b: Buffer.NSBuffer] =
      BEGIN
      answer: LONG POINTER TO simpleData BootServerTypes.BootFileRequest;
      answer ← LOOPHOLE[@b.ns.nsWords];
      packetNumber ← packetNumber + 1;
      SELECT answer.packetNumber FROM
        packetNumber =>
          BEGIN
          clump: CARDINAL = (b.ns.pktLength - overheadBytes)/2;
          checksum ← Checksum.ComputeChecksum[checksum, clump, @answer.data];
          words ← words + clump;
          IF ~silent THEN Put.Char[log, '+];
          END;
        ENDCASE =>
          BEGIN
          FOR i: CARDINAL IN [0..50) UNTIL answer.packetNumber = packetNumber DO
            packetNumber ← packetNumber + 1;
            missed ← missed + 1;
            IF ~silent THEN Put.Char[log, '-];
            ENDLOOP;
          END;
      IF b.ns.pktLength = overheadBytes THEN
        BEGIN IF ~silent THEN Put.Char[log, '!]; tail ← TRUE; END;
      END;

    IF ~FindParameters[] THEN RETURN;

    soc ← Socket.Create[local: localAddr, receive: 10];
    Socket.SetWaitTime[soc, 1500];  -- milli-seconds

    beginTime ← System.GetClockPulses[];
    FOR i: CARDINAL IN [0..10) UNTIL hit DO
      request: LONG POINTER TO simpleRequest BootServerTypes.BootFileRequest;
      requestSize: CARDINAL = SIZE[simpleRequest BootServerTypes.BootFileRequest];
      b ← Socket.GetSendBuffer[soc];
      Socket.SetDestination[b, remoteAddr];
      b.ns.packetType ← bootServerPacket;
      request ← LOOPHOLE[@b.ns.nsWords];
      request↑ ← [simpleRequest[bootFileNumber]];
      Socket.SetPacketWords[b, requestSize];
      Socket.PutPacket[soc, b];
      UNTIL tail DO
        answer: LONG POINTER TO simpleData BootServerTypes.BootFileRequest;
        b ← Socket.GetPacket[
          soc !
          Socket.TimeOut => BEGIN IF ~hit THEN Put.Char[log, '?]; EXIT; END];
        answer ← LOOPHOLE[@b.ns.nsWords];
        SELECT TRUE FROM
          b.ns.packetType = error => Put.Char[log, 'E];
          answer.etherBootPacketType # simpleData => BEGIN Put.Char[log, '#]; END;
          (hit = FALSE) AND (answer.packetNumber = 1) =>
            BEGIN hit ← TRUE; target ← b.ns.source; GotOne[b]; END;
          target = b.ns.source => BEGIN GotOne[b]; END;
          ENDCASE => BEGIN strays ← strays + 1; END;
        Socket.ReturnBuffer[b];
        ENDLOOP;
      ENDLOOP;
    stopTime ← System.GetClockPulses[];

    Put.CR[log];
    Socket.Delete[soc];
    elapsed ← System.PulsesToMicroseconds[[stopTime - beginTime]];
    TailMessage[target, checksum, elapsed, words, missed, strays];
    END;

  FastStream: FormSW.ProcType =
    BEGIN
    target: System.NetworkAddress ← System.nullNetworkAddress;
    checksum: WORD ← 0;
    elapsed: System.Microseconds;
    words: LONG CARDINAL ← 0;
    strays: CARDINAL ← 0;
    soc: Socket.ChannelHandle;
    latched, end: BOOLEAN ← FALSE;
    b: Buffer.NSBuffer;
    myConnection, hisConnection: NSTypes.ConnectionID;
    recvSequence: CARDINAL ← 0;
    beginTime, stopTime: System.Pulses;

    IF ~FindParameters[] THEN RETURN;

    myConnection ← LOOPHOLE[NetworkStream.GetUniqueConnectionID[]];
    soc ← Socket.Create[local: localAddr, receive: 10];
    Socket.SetWaitTime[soc, 1500];  -- milli-seconds

    beginTime ← System.GetClockPulses[];
    FOR i: CARDINAL IN [0..10) UNTIL latched DO
      request: LONG POINTER TO sppRequest BootServerTypes.BootFileRequest;
      requestSize: CARDINAL = SIZE[sppRequest BootServerTypes.BootFileRequest];
      b ← Socket.GetSendBuffer[soc];
      Socket.SetDestination[b, remoteAddr];
      b.ns.packetType ← bootServerPacket;
      request ← LOOPHOLE[@b.ns.nsWords];
      request↑ ← [sppRequest[
        bootFileNumber: bootFileNumber, connectionID: myConnection]];
      Socket.SetPacketWords[b, requestSize];
      Socket.PutPacket[soc, b];
      UNTIL end DO
        b ← Socket.GetPacket[
          soc ! Socket.TimeOut => BEGIN Put.Char[log, '?]; EXIT; END];
        SELECT TRUE FROM
          b.ns.packetType = error => Put.Char[log, 'E];
          b.ns.packetType = sequencedPacket =>
            BEGIN
            overhead: CARDINAL =
              NSTypes.bytesPerIDPHeader + NSTypes.bytesPerSppHeader;
            clump: CARDINAL ← (b.ns.pktLength - overhead)/2;
            IF b.ns.destinationConnectionID # myConnection THEN GOTO Reject;
            IF ~latched THEN
              BEGIN
              latched ← TRUE;
              target ← b.ns.source;
              hisConnection ← b.ns.sourceConnectionID;
              Socket.SetWaitTime[soc, 10000];  -- milli-seconds
              END;
            IF b.ns.sourceConnectionID # hisConnection THEN GOTO Reject;
            IF b.ns.source # target THEN GOTO Reject;
            IF b.ns.sequenceNumber # recvSequence THEN GOTO Reject;
            IF ~b.ns.systemPacket AND b.ns.sequenceNumber = recvSequence THEN
              BEGIN
              recvSequence ← recvSequence + 1;
              SELECT b.ns.subtype FROM
                0 =>
                  BEGIN
                  checksum ← Checksum.ComputeChecksum[
                    checksum, clump, @b.ns.sppWords];
                  words ← words + clump;
                  END;
                376B =>
                  BEGIN
                  b.ns ← [
                    checksum:, pktLength: overhead,
                    transportControl: [FALSE, 0, 0], packetType: sequencedPacket,
                    destination: target, source: localAddr,
                    nsBody: spp[
                      systemPacket: FALSE, sendAck: FALSE, attention: FALSE,
                      endOfMessage: FALSE, unusedType: 0, subtype: 377B,
                      sourceConnectionID: myConnection,
                      destinationConnectionID: hisConnection, sequenceNumber: 0,
                      acknowledgeNumber: recvSequence,
                      allocationNumber: recvSequence + 100, sppBody:]];
                  Socket.PutPacket[soc, b];
                  LOOP;  -- avoid ReturnBuffer below
                  END;
                377B => BEGIN end ← TRUE; END;
                ENDCASE => NULL;
              END;
            IF b.ns.sendAck THEN
              BEGIN
              b.ns ← [
                checksum:, pktLength: overhead,
                transportControl: [FALSE, 0, 0], packetType: sequencedPacket,
                destination: target, source: localAddr,
                nsBody: spp[
                  systemPacket: TRUE, sendAck: FALSE, attention: FALSE,
                  endOfMessage: FALSE, unusedType: 0, subtype: 0,
                  sourceConnectionID: myConnection,
                  destinationConnectionID: hisConnection, sequenceNumber: 0,
                  acknowledgeNumber: recvSequence,
                  allocationNumber: recvSequence + 100, sppBody:]];
              Socket.PutPacket[soc, b];
              LOOP;  -- avoid ReturnBuffer below
              END;
            EXITS Reject => BEGIN strays ← strays + 1; END;
            END;
          ENDCASE => BEGIN strays ← strays + 1; END;
        Socket.ReturnBuffer[b];
        ENDLOOP;
      ENDLOOP;
    stopTime ← System.GetClockPulses[];

    Put.CR[log];
    Socket.Delete[soc];
    elapsed ← System.PulsesToMicroseconds[[stopTime - beginTime]];
    TailMessage[target, checksum, elapsed, words, 0, strays];
    END;


  SlowStream: FormSW.ProcType =
    BEGIN
    target: System.NetworkAddress ← System.nullNetworkAddress;
    checksum: WORD ← 0;
    elapsed: System.Microseconds;
    words: LONG CARDINAL ← 0;
    strays: CARDINAL ← 0;
    soc: Socket.ChannelHandle;
    b: Buffer.NSBuffer;
    connection: NetworkStream.ConnectionID = NetworkStream.GetUniqueConnectionID[];
    stream: Stream.Handle ← NIL;
    beginTime, stopTime: System.Pulses;

    IF ~FindParameters[] THEN RETURN;

    soc ← Socket.Create[local: localAddr, receive: 10];
    Socket.SetWaitTime[soc, 1500];  -- milli-seconds

    beginTime ← System.GetClockPulses[];
    FOR i: CARDINAL IN [0..10) UNTIL stream # NIL DO
      request: LONG POINTER TO sppRequest BootServerTypes.BootFileRequest;
      requestSize: CARDINAL = SIZE[sppRequest BootServerTypes.BootFileRequest];
      b ← Socket.GetSendBuffer[soc];
      Socket.SetDestination[b, remoteAddr];
      b.ns.packetType ← bootServerPacket;
      request ← LOOPHOLE[@b.ns.nsWords];
      request↑ ← [sppRequest[
        bootFileNumber: bootFileNumber, connectionID: connection]];
      Socket.SetPacketWords[b, requestSize];
      Socket.PutPacket[soc, b];
      UNTIL stream # NIL DO
        b ← Socket.GetPacket[
          soc ! Socket.TimeOut => BEGIN Put.Char[log, '?]; EXIT; END];
        SELECT TRUE FROM
          b.ns.packetType = error => Put.Char[log, 'E];
          b.ns.packetType = sequencedPacket =>
            BEGIN
            him: NetworkStream.ConnectionID = LOOPHOLE[b.ns.sourceConnectionID];
            target ← b.ns.source;
            stream ← NetworkStream.CreateTransducer[
              local: localAddr, remote: target, localConnID: connection,
              remoteConnID: him, activelyEstablish: FALSE, classOfService: bulk];
            -- discard this packet, and hope the retransmission works
            END;
          ENDCASE => BEGIN strays ← strays + 1; END;
        Socket.ReturnBuffer[b];
        ENDLOOP;
      ENDLOOP;
    IF stream # NIL THEN
      BEGIN
      bufferSize: CARDINAL = Environment.bytesPerPage;
      buffer: PACKED ARRAY [0..bufferSize) OF Environment.Byte;
      FOR page: CARDINAL ← 0, page + 1 DO
        bytes: CARDINAL;
        why: Stream.CompletionCode;
        clump: CARDINAL;
        [bytes, why] ← Stream.GetBlock[
          stream, [@buffer, 0, bufferSize] !
          NetworkStream.ConnectionSuspended => EXIT];
        IF (bytes MOD 2) # 0 THEN Put.Char[log, '$];
        clump ← bytes/2;
        words ← words + clump;
        checksum ← Checksum.ComputeChecksum[checksum, clump, @buffer];
        IF bytes # 0 AND ~silent THEN Put.Char[log, '+];
        IF why = sstChange THEN
          BEGIN
          IF ~silent THEN Put.Char[log, '!];
          [] ← NetworkStream.CloseReply[stream];
          EXIT;
          END;
        ENDLOOP;
      Stream.Delete[stream];
      END;
    stopTime ← System.GetClockPulses[];

    Put.CR[log];
    Socket.Delete[soc];
    elapsed ← System.PulsesToMicroseconds[[stopTime - beginTime]];
    TailMessage[target, checksum, elapsed, words, 0, strays];
    END;

  TailMessage: PROCEDURE [
    target: System.NetworkAddress, checksum: WORD, elapsed: System.Microseconds,
    words: LONG CARDINAL, missed, strays: CARDINAL] =
    BEGIN
    text: STRING = [500];
    IF TRUE THEN
      BEGIN
      ms: LONG CARDINAL = elapsed/1000;
      String.AppendString[text, "This run took "L];
      String.AppendLongDecimal[text, ms];
      String.AppendString[text, " ms, "L];
      String.AppendLongDecimal[text, words*16*1000/ms];
      String.AppendString[text, " bits/sec."L];
      String.AppendChar[text, Ascii.CR];
      END;
    IF target # System.nullNetworkAddress THEN
      BEGIN
      Append: PROCEDURE [s: LONG STRING, clientData: LONG POINTER] =
        BEGIN String.AppendString[text, s]; END;
      String.AppendString[text, "Response from "L];
      Format.NetworkAddress[Append, target, productSoftware];
      String.AppendChar[text, '.];
      String.AppendChar[text, Ascii.CR];
      END;
    IF missed > 0 THEN
      BEGIN
      String.AppendString[text, "We missed at least "L];
      String.AppendDecimal[text, missed];
      String.AppendString[text, " packets."L];
      String.AppendChar[text, Ascii.CR];
      END;
    IF strays > 0 THEN
      BEGIN
      String.AppendString[text, "We also encountered "L];
      String.AppendDecimal[text, strays];
      String.AppendString[
        text, " stray packets (maybe from other boot servers)."L];
      String.AppendChar[text, Ascii.CR];
      END;
    IF checksum # 0 THEN
      BEGIN
      String.AppendString[text, "The checksum was flakey"L];
      String.AppendChar[text, '.];
      String.AppendChar[text, Ascii.CR];
      END;
    Put.Text[log, text];
    MsgSW.Post[msg, ""L];
    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;

  Init[];
  END...