-- File: EchoTool.mesa 
-- BLyon, January 16, 1981  5:58 PM
-- HGM, March 14, 1981  12:25 PM
-- Please don't forget to update the herald...


DIRECTORY
  AddressTranslation USING [
    AppendMyHostNumber, AppendNetworkAddress, StringToNetworkAddress],
  BufferDefs USING [BufferAccessHandle, OisBuffer],
  Echo USING [echoRequest, echoResponse],
  FormSW USING [
    AllocateItemDescriptor, BooleanItem, ClientItemsProcType, CommandItem,
    ItemHandle, line0, line3, line4, line5, line6, newLine, NotifyProcType,
    NumberItem, ProcType, StringItem],
  Inline USING [BITAND, BITNOT],
  MsgSW USING [Post],
  OISCP USING [
    OiscpPackageDestroy, OiscpPackageMake,
    GetFreeSendOisBufferFromPool, ReturnFreeOisBuffer],
  OISCPTypes USING [bytesPerPktHeader, bytesPerPktText],
  OISCPConstants USING [unknownSocketID, echoerSocket],
  Process USING [Yield, Detach],
  Put USING [Char, CR, Line, Text],
  Router USING [FindMyHostID, SetOisCheckit, SetOisDriverLoopback],
  SpecialSystem USING [HostNumber, NetworkAddress],
  Socket USING [
    Abort, AssignNetworkAddress, Create, Delete, GetPacket, PutPacket,
    SetWaitTime, TimeOut, TransferStatus],
  SocketInternal USING [GetBufferPool, SocketHandle],
  StatsDefs USING [StatPrintCurrent, StatReady, StatSince],
  Storage USING [Free, FreeStringNil, String],
  String USING [AppendNumber, AppendLongNumber, AppendString],
  System USING [],
  Time USING [AppendCurrent],
  Tool USING [
    Create, UnusedLogName, MakeFormSW, MakeFileSW, MakeMsgSW, MakeSWsProc],
  ToolWindow USING [TransitionProcType],
  Window USING [Handle, Place];

EchoTool: PROGRAM
  IMPORTS
    FormSW, Inline, MsgSW, OISCP, Process, Put,
    AddressTranslation, Router, Socket, SocketInternal,
    StatsDefs, Storage, String, Time, Tool
  EXPORTS Socket, System
  SHARES BufferDefs =
  BEGIN

  -- EXPORTED TYPE(S)
  NetworkAddress: PUBLIC TYPE = SpecialSystem.NetworkAddress;
  ChannelHandle: PUBLIC TYPE = SocketInternal.SocketHandle;

  -- global variable declarations
  msg, log, form: Window.Handle ← NIL;
  thisMachineID, localAddress, remoteAddress: STRING ← NIL;
  localAddr: SpecialSystem.NetworkAddress;
  maxLength, maxEchos: CARDINAL ← 256;
  running, pleaseStop: BOOLEAN ← FALSE;
  driverLoopBack: BOOLEAN ← TRUE;
  useMax: BOOLEAN ← FALSE;
  fixedLength: BOOLEAN ← FALSE;
  verbose: BOOLEAN ← TRUE;
  checkIt: BOOLEAN ← TRUE;
  checksums: BOOLEAN ← FALSE;
  doStats: BOOLEAN ← FALSE;

  Init: PROCEDURE =
    BEGIN
    [] ← Tool.Create[
      name: "Echo Tool of March 14, 1981"L, makeSWsProc: MakeThisTool,
      clientTransition: Transition];
    END;

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

  MakeItemArray: FormSW.ClientItemsProcType =
    BEGIN
    echoStartPlace: Window.Place = [x: 2, y: FormSW.line0];
    stopPlace: Window.Place = [x: 10*7, y: FormSW.line0];
    useMaxPlace: Window.Place = [x: 20*7, y: FormSW.line0];
    maxEchosPlace: Window.Place = [x: 30*7, y: FormSW.line0];
    thisMachineIDPlace: Window.Place = [x: 2, y: FormSW.line3];
    localAddressPlace: Window.Place = [x: 2, y: FormSW.line4];
    remoteAddressPlace: Window.Place = [x: 2, y: FormSW.line5];
    checkItPlace: Window.Place = [x: 2, y: FormSW.line6];
    verbosePlace: Window.Place = [x: 15*7, y: FormSW.line6];
    checksumsPlace: Window.Place = [x: 30*7, y: FormSW.line6];
    driverLoopBackPlace: Window.Place = [x: 45*7, y: FormSW.line6];
    nItems: CARDINAL = 17;
    i: INTEGER ← -1;
    items ← FormSW.AllocateItemDescriptor[nItems];

    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "Echo"L, place: echoStartPlace, proc: EchoProc];
    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "Stop"L, place: stopPlace, proc: Stop];
    items[i ← i + 1] ← FormSW.BooleanItem[
      tag: "UseMax"L, place: useMaxPlace, switch: @useMax];
    items[i ← i + 1] ← FormSW.NumberItem[
      tag: "MaxEchos"L, place: maxEchosPlace, value: @maxEchos];
    items[i ← i + 1] ← FormSW.BooleanItem[
      tag: "FixedLength"L, place: FormSW.newLine, switch: @fixedLength];
    items[i ← i + 1] ← FormSW.NumberItem[tag: "(Max)Length"L, value: @maxLength];
    items[i ← i + 1] ← FormSW.BooleanItem[
      tag: "DoStats"L, switch: @doStats, place: FormSW.newLine];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "Ready"L, proc: Ready];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "Recent"L, proc: Since];
    items[i ← i + 1] ← FormSW.CommandItem[tag: "Totals"L, proc: Totals];
    items[i ← i + 1] ← FormSW.StringItem[
      tag: "ThisMachineID"L, place: thisMachineIDPlace, string: @thisMachineID,
      readOnly: TRUE];
    items[i ← i + 1] ← FormSW.StringItem[
      tag: "LocalSocket"L, place: localAddressPlace, string: @localAddress,
      inHeap: TRUE, readOnly: TRUE];
    items[i ← i + 1] ← FormSW.StringItem[
      tag: "RemoteAddress"L, place: remoteAddressPlace, string: @remoteAddress,
      inHeap: TRUE];
    items[i ← i + 1] ← FormSW.BooleanItem[
      tag: "CheckIt"L, place: checkItPlace, switch: @checkIt];
    items[i ← i + 1] ← FormSW.BooleanItem[
      tag: "Verbose"L, place: verbosePlace, switch: @verbose];
    items[i ← i + 1] ← FormSW.BooleanItem[
      tag: "Checksums"L, place: checksumsPlace, proc: ToggleChecksums,
      switch: @checksums];
    items[i ← i + 1] ← FormSW.BooleanItem[
      tag: "DriverLoopBack"L, place: driverLoopBackPlace,
      proc: ToggleDriverLoopBack, switch: @driverLoopBack];
    IF (i + 1) # nItems THEN ERROR;
    RETURN[items, TRUE];
    END;

  Transition: ToolWindow.TransitionProcType =
    BEGIN
    IF old = inactive THEN -- tool is becomming active
      BEGIN
      myHostID: SpecialSystem.HostNumber;
      OISCP.OiscpPackageMake[];
      myHostID ← Router.FindMyHostID[];
      Router.SetOisDriverLoopback[driverLoopBack ← TRUE];
      Router.SetOisCheckit[checksums ← TRUE];
      useMax ← FALSE;
      verbose ← checkIt ← TRUE;
      thisMachineID ← Storage.String[1 + 6*SIZE[SpecialSystem.HostNumber]];
      AddressTranslation.AppendMyHostNumber[thisMachineID];
      localAddr ← Socket.AssignNetworkAddress[];
      localAddress ← Storage.String[2 + 6*SIZE[SpecialSystem.NetworkAddress]];
      AddressTranslation.AppendNetworkAddress[localAddress, localAddr];
      END
    ELSE
      IF new = inactive THEN
	BEGIN
	thisMachineID ← Storage.FreeStringNil[thisMachineID];
	localAddress ← Storage.FreeStringNil[localAddress];
	remoteAddress ← Storage.FreeStringNil[remoteAddress];
        OISCP.OiscpPackageDestroy[];
	END;
    END;

  ToggleDriverLoopBack: FormSW.NotifyProcType =
    BEGIN
    Router.SetOisDriverLoopback[driverLoopBack];
    Put.Text[log, "DriverLoopBack is now "L];
    Put.Line[log, IF driverLoopBack THEN "ON."L ELSE "OFF."L];
    END;

  ToggleChecksums: FormSW.NotifyProcType =
    BEGIN
    Router.SetOisCheckit[checksums];
    Put.Text[log, "Checksums are now "L];
    Put.Line[log, IF checksums THEN "ON."L ELSE "OFF."L];
    END;

  Stop: FormSW.ProcType =
    BEGIN pleaseStop ← TRUE; WHILE running DO Process.Yield[]; ENDLOOP; END;

  Ready: FormSW.ProcType = BEGIN StatsDefs.StatReady[]; END;

  Since: FormSW.ProcType = BEGIN StatsDefs.StatSince[log]; END;

  Totals: FormSW.ProcType = BEGIN StatsDefs.StatPrintCurrent[log]; END;

  EchoProc: FormSW.ProcType =
    BEGIN
    p: PROCESS;
    useLimit, fixed: BOOLEAN;
    limit, size: CARDINAL;
    errFlag: BOOLEAN ← FALSE;
    myHostID: SpecialSystem.HostNumber ← Router.FindMyHostID[];
    remoteAddr: SpecialSystem.NetworkAddress;
    cH: ChannelHandle;
    MsgSW.Post[msg, ""L];
    IF running THEN
      BEGIN
      MsgSW.Post[msg, "An echoer is already running; use Stop to quit it."L];
      errFlag ← TRUE;
      END;
    useLimit ← useMax;
    limit ← maxEchos;
    fixed ← fixedLength;
    size ← maxLength;
    remoteAddr ← AddressTranslation.StringToNetworkAddress[remoteAddress !
      ANY =>
	BEGIN
	MsgSW.Post[msg, "RemoteAddress is incorrectly specified; try again."L];
	errFlag ← TRUE;
	CONTINUE;
	END];
    IF errFlag THEN RETURN;
    IF remoteAddr.socket=OISCPConstants.unknownSocketID THEN
      remoteAddr.socket ← OISCPConstants.echoerSocket;
    pleaseStop ← FALSE;
    running ← TRUE;
    cH ← Socket.Create[localAddr];
    Socket.SetWaitTime[cH, 1500]; -- milli-seconds
    p ← FORK Echoer[cH, remoteAddr, useLimit, limit, fixed, size];
    Process.Detach[p];
    MsgSW.Post[msg, "Echoer successfully started."L];
    Process.Yield[];
    END;

  Echoer: PROCEDURE [
    cH: ChannelHandle, remoteAddr: SpecialSystem.NetworkAddress,
    useMaxEcho: BOOLEAN, maxEcho: CARDINAL, fixed: BOOLEAN, size: CARDINAL] =
    BEGIN
    length: CARDINAL = MIN[OISCPTypes.bytesPerPktText/2, 400B, size];
    picks: ARRAY [0..16] OF CARDINAL;
    drops: ARRAY [0..16] OF CARDINAL;
    sent, good, missed, late, bad, horrible, error: LONG CARDINAL;

    ClearCounters: PROCEDURE =
      BEGIN
      sent ← good ← missed ← late ← bad ← horrible ← error ← 0;
      picks ← ALL[0];
      drops ← ALL[0];
      END;

    AddToHist: PROCEDURE [
      hist: POINTER TO ARRAY [0..16] OF CARDINAL, bits: WORD] =
      BEGIN
      i: CARDINAL;
      IF bits = 0 THEN RETURN;
      SELECT bits FROM
	1 => i ← 15;
	2 => i ← 14;
	4 => i ← 13;
	10B => i ← 12;
	20B => i ← 11;
	40B => i ← 10;
	100B => i ← 9;
	200B => i ← 8;
	400B => i ← 7;
	1000B => i ← 6;
	2000B => i ← 5;
	4000B => i ← 4;
	10000B => i ← 3;
	20000B => i ← 2;
	40000B => i ← 1;
	100000B => i ← 0;
	ENDCASE => i ← 16;
      hist[i] ← hist[i] + 1;
      END;

    PrintSummary: PROCEDURE =
      BEGIN
      Put.CR[log];
      WriteLongDecimal[sent];
      Put.Line[log, " packets sent."L];
      ShowPercent[good, "good packets received."L];
      ShowPercent[missed, "packets missed."L];
      ShowPercent[late, "late (or??) packets received."L];
      ShowPercent[bad, "bad packets received."L];
      ShowPercent[horrible, "packets received with more than 10 words wrong."L];
      ShowPercent[error, "error packets received."L];
      IF bad # 0 THEN
	BEGIN
	x: WORD ← 100000B;
	Put.CR[log];
	Put.Line[log, "     Bit    Picked Dropped"L];
	FOR i: CARDINAL IN [0..16] DO
	  IF picks[i] # 0 OR drops[i] # 0 THEN
	    BEGIN
	    IF i = 16 THEN Put.Text[log, " Other"L] ELSE O6[x];
	    D8[picks[i]];
	    D8[drops[i]];
	    Put.CR[log];
	    END;
	  x ← x/2;
	  ENDLOOP;
	END;
      END;

    ShowPercent: PROCEDURE [n: LONG CARDINAL, s: STRING] =
      BEGIN
      IF n = 0 THEN RETURN;
      WriteLongDecimal[n];
      Put.Text[log, " ("L];
      WriteLongDecimal[n*100/sent];
      Put.Text[log, "%) "L];
      Put.Line[log, s];
      END;

    WriteNumber: PROCEDURE [n, radix, width: CARDINAL] = INLINE
      BEGIN
      temp: STRING = [25];
      String.AppendNumber[temp, n, radix];
      THROUGH [temp.length..width) DO Put.Char[log, ' ]; ENDLOOP;
      Put.Text[log, temp];
      END;

    WriteLongDecimal: PROCEDURE [n: LONG UNSPECIFIED] =
      BEGIN
      temp: STRING = [32];
      temp.length ← 0;
      String.AppendLongNumber[temp, n, 10];
      Put.Text[log, temp];
      END;

    D8: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 10, 8]; END;

    O3: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END;

    O6: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 3]; END;

    O9: PROCEDURE [n: CARDINAL] = BEGIN WriteNumber[n, 8, 9]; END;

    WriteCurrentDateAndTime: PROCEDURE =
      BEGIN
      time: STRING = [32];
      Time.AppendCurrent[time, ];
      Put.Text[log, time];
      END;

    PrintSocketEchoError: PROCEDURE [
      b: BufferDefs.OisBuffer, status: Socket.TransferStatus] =
      BEGIN
      temp: STRING = [100];
      String.AppendString[temp, "Error OIS, code="L];
      String.AppendString[
	temp,
	SELECT status FROM
	  pending => "pending"L,
	  aborted => "aborted"L,
	  noRouteToNetwork => "noRouteToNetwork"L,
	  hardwareProblem => "hardwareProblem"L,
	  invalidDestAddr => "invalidDestinationAddr"L,
	  goodCompletion => "goodCompletion (?!)"L,
	  ENDCASE => "?????"L];
      String.AppendString[temp, ", from: "L];
      AddressTranslation.AppendNetworkAddress[temp, b.ois.source];
      Put.Line[log, temp];
      END;

    -- sequence number
    counter: CARDINAL ← 0;
    -- send/ receive stuff
    myBufferAccessHandle: BufferDefs.BufferAccessHandle ←
      SocketInternal.GetBufferPool[cH];
    sendBuf, recBuf: BufferDefs.OisBuffer;
    pktBody: LONG POINTER TO ARRAY [0..0) OF WORD;
    status: Socket.TransferStatus;
    ClearCounters[];
    IF doStats THEN StatsDefs.StatReady[];
    -- note tht are two outstanding Get's before entering this loop
    UNTIL pleaseStop OR ((counter >= maxEcho) AND useMaxEcho) DO
      cycle: CARDINAL;
      IF (((counter ← counter + 1) MOD 5) = 0) THEN Process.Yield[];
      cycle ← IF fixed THEN length ELSE counter MOD length;
      sendBuf ← OISCP.GetFreeSendOisBufferFromPool[myBufferAccessHandle];
      sendBuf.ois.pktLength ← OISCPTypes.bytesPerPktHeader + 2*(cycle + 1);
      sendBuf.ois.transCntlAndPktTp.packetType ← echo;
      sendBuf.ois.destination ← remoteAddr;
      pktBody ← @sendBuf.ois.oisWords;
      pktBody[0] ← Echo.echoRequest;
      FOR k: CARDINAL IN [0..cycle) DO pktBody[k+1] ← (k*400B + counter); ENDLOOP;
      Socket.PutPacket[cH, sendBuf];
      sent ← sent + 1;
      -- now receive the echo or any back logged echos
      DO
	recBuf ← Socket.GetPacket[
	  cH !
	  Socket.TimeOut =>
	    BEGIN missed ← missed + 1; Put.Char[log, '?]; EXIT; END];
	status ← LOOPHOLE[recBuf.status];
	pktBody ← @recBuf.ois.oisWords;
	SELECT TRUE FROM
	  (status # goodCompletion) =>
	    BEGIN -- some kind of error occurred
	    error ← error + 1;
	    PrintSocketEchoError[recBuf, status];
	    OISCP.ReturnFreeOisBuffer[recBuf];
	    LOOP;
	    END;
	  (recBuf.ois.pktLength # OISCPTypes.bytesPerPktHeader + 2*(cycle + 1))
          OR (pktBody[0] # Echo.echoResponse)
          OR ((cycle # 0) AND (pktBody[0+1] # (0*400B + counter))) =>
	    BEGIN -- probably a late packet, but could be trash, or error
	    late ← late + 1;
	    Put.Char[log, '#];
	    OISCP.ReturnFreeOisBuffer[recBuf];
	    LOOP;
	    END;
	  ENDCASE =>
	    BEGIN -- the echo we were looking for
	    hits: CARDINAL ← 0;
	    IF checkIt THEN
	      FOR k: CARDINAL IN [0..cycle) DO
		IF pktBody[k+1] # (k*400B + counter) THEN
		  BEGIN OPEN Inline;
		  expected, found, picked, dropped: WORD;
		  IF hits = 0 THEN
		    BEGIN
		    Put.CR[log];
		    WriteCurrentDateAndTime[];
		    Put.Text[log, "    Data compare error(s) on packet number "L];
		    WriteLongDecimal[sent];
		    Put.Line[log, "."L];
		    Put.Line[log, "Idx Expected    Found   Picked  Dropped"L];
		    END;
		  expected ← k + cycle*400B;
		  found ← pktBody↑[k];
		  picked ← BITAND[found, BITNOT[expected]];
		  dropped ← BITAND[expected, BITNOT[found]];
		  AddToHist[@picks, picked];
		  AddToHist[@drops, dropped];
		  IF hits < 10 THEN
		    BEGIN
		    O3[k];
		    O9[expected];
		    O9[found];
		    O9[picked];
		    O9[dropped];
		    Put.CR[log];
		    END;
		  hits ← hits + 1;
		  END;
		ENDLOOP;
	    IF hits = 0 THEN good ← good + 1 ELSE bad ← bad + 1;
	    IF hits = 0 AND verbose THEN Put.Char[log, '!];
	    IF hits > 10 THEN
	      BEGIN horrible ← horrible + 1; Put.Line[log, "...."L]; END;
	    OISCP.ReturnFreeOisBuffer[recBuf];
	    EXIT;
	    END;
	ENDLOOP;
      ENDLOOP;
    -- get back the two recieve buffers
    Put.CR[log];
    Socket.Abort[cH];
    IF doStats THEN StatsDefs.StatSince[log];
    Socket.Delete[cH];
    PrintSummary[];
    MsgSW.Post[msg, ""L];
    running ← FALSE;
    END;


  Init[]; -- this gets string out of global frame
  END...