-- File: OISCPTesterSink.mesa 
-- Last edit: BLyon, January 16, 1981  5:42 PM    
-- Last edit: HGM, January 27, 1981  9:03 PM    

DIRECTORY
  FormSW USING [
    AllocateItemDescriptor, BooleanItem, ClientItemsProcType, CommandItem,
    NumberItem, line0, line1, ProcType, StringItem],
  Inline USING [BITAND, BITSHIFT],
  MsgSW USING [Post],
  NetworkStream USING [
    Close, CreateListener, defaultWaitTime, DeleteListener, FindAddresses, Listen,
    ListenerHandle, CloseReply, closeSST, CloseStatus, ConnectionSuspended],
  OISCP USING [OiscpPackageDestroy, OiscpPackageMake],
  Process USING [Yield, Detach],
  Put USING [Char, CR, Decimal, Line, Octal, Text],
  Router USING [FindMyHostID, FindDestinationRelativeNetID],
  SpecialSystem USING [
    HostNumber, NetworkAddress, NetworkNumber, nullNetworkNumber, SocketNumber],
  Storage USING [FreeString, FreeNodeNil, Node, String],
  Stream,
  String USING [AppendChar, AppendNumber, AppendString],
  System USING [],
  Tool USING [Create, MakeFileSW, MakeFormSW, MakeMsgSW, MakeSWsProc],
  ToolWindow USING [TransitionProcType],
  Window USING [Handle, Place];

OISCPTesterSink: PROGRAM
  IMPORTS
    FormSW, Inline, MsgSW, NetworkStream, OISCP, Process, Put, Router, Storage,
    Stream, String, Tool
  EXPORTS System =
  BEGIN

  NetworkAddress: PUBLIC TYPE = SpecialSystem.NetworkAddress;

  -- global variable declarations

  pleaseStop, networkStreamRunning: BOOLEAN ← FALSE;
  handle, msgSW, fileSW, formSW: Window.Handle ← NIL;
  localSocket: SpecialSystem.SocketNumber ← [400B];
  useLoopCount: BOOLEAN ← TRUE;
  loopCount: CARDINAL ← 100;
  machineIDString: STRING ← NIL;


  -- UserProc needed routines

  Init: PROCEDURE =
    BEGIN
    handle ← Tool.Create[
      name: "OISCP Tester Sink of September 23, 1980"L,
      makeSWsProc: DoUserProcSpecificSetup, clientTransition: Transition,
      initialState: inactive];
    END;

  DoUserProcSpecificSetup: Tool.MakeSWsProc =
    BEGIN
    IF handle = NIL THEN ERROR;
    msgSW ← Tool.MakeMsgSW[window: handle, lines: 1];
    formSW ← Tool.MakeFormSW[window: handle, formProc: MakeParameterArray];
    fileSW ← Tool.MakeFileSW[window: handle, name: "OISCPTesterSink.log"];
    END;

  -- in this tool tiny and active are the same state.

  Transition: ToolWindow.TransitionProcType =
    BEGIN
    IF old = inactive AND new # inactive THEN
      BEGIN
      primaryNetID: SpecialSystem.NetworkNumber;
      myHostID: SpecialSystem.HostNumber;
      OISCP.OiscpPackageMake[];
      primaryNetID ← Router.FindDestinationRelativeNetID[
	SpecialSystem.nullNetworkNumber];
      myHostID ← Router.FindMyHostID[];
      -- set the machine id string
      machineIDString ← Storage.String[6*SIZE[NetworkAddress] + 2];
      machineIDString.length ← 0;
      AppendSuperLongToString[
	machineIDString, @primaryNetID, SIZE[SpecialSystem.NetworkNumber]];
      String.AppendChar[machineIDString, '#];
      AppendSuperLongToString[
	machineIDString, @myHostID, SIZE[SpecialSystem.HostNumber]];
      String.AppendChar[machineIDString, '#];
      loopCount ← 100;
      END
    ELSE
      IF new = inactive AND old # inactive THEN
	BEGIN
	Storage.FreeString[machineIDString];
	OISCP.OiscpPackageDestroy[];
	END;
    END;

  AppendSuperLongToString: PUBLIC PROCEDURE [
    s: STRING, num: POINTER, length: CARDINAL] =
    BEGIN
    number: POINTER TO ARRAY [0..0) OF CARDINAL = LOOPHOLE[num];
    myCopy: POINTER TO ARRAY [0..0) OF CARDINAL;
    i: CARDINAL;

    AppendSuperLong: PROCEDURE [recurring: BOOLEAN] =
      BEGIN
      finished: BOOLEAN ← recurring;
      lastRem, rem: CARDINAL ← 0;
      i: CARDINAL;
      FOR i IN [0..length) DO
	IF myCopy[i] # 0 THEN finished ← FALSE;
	rem ← Inline.BITAND[myCopy[i], 7B];
	myCopy[i] ← Inline.BITSHIFT[lastRem, 13] + Inline.BITSHIFT[myCopy[i], -3];
	lastRem ← rem;
	ENDLOOP;
      IF finished THEN RETURN;
      AppendSuperLong[TRUE];
      String.AppendNumber[s, rem, 8];
      END; -- AppendSuperLong

    myCopy ← Storage.Node[length];
    FOR i IN [0..length) DO myCopy[i] ← number[i]; ENDLOOP;
    AppendSuperLong[FALSE];
    myCopy ← Storage.FreeNodeNil[myCopy];
    END; -- AppendSuperLongToString

  MakeParameterArray: FormSW.ClientItemsProcType =
    BEGIN
    sinkTestReceivePlace: Window.Place = [x: 2, y: FormSW.line0];
    sinkTestSendPlace: Window.Place = [x: 25*7, y: FormSW.line0];
    stopPlace: Window.Place = [x: 50*7, y: FormSW.line0];
    useCountPlace: Window.Place = [x: 2, y: FormSW.line1];
    countPlace: Window.Place = [x: 9*7, y: FormSW.line1];
    machineIDPlace: Window.Place = [x: 25*7, y: FormSW.line1];
    localSocketPlace: Window.Place = [x: 50*7, y: FormSW.line1];
    nParams: CARDINAL = 7;
    i: INTEGER ← -1;
    freeDesc ← TRUE; -- ????
    items ← FormSW.AllocateItemDescriptor[nParams];

    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "SinkTestReceive"L, place: sinkTestReceivePlace,
      proc: ListenerTestSinkCmd];
    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "SinkTestSend"L, place: sinkTestSendPlace, proc: ListenerTestSendCmd];
    items[i ← i + 1] ← FormSW.CommandItem[
      tag: "STOP"L, place: stopPlace, proc: Stop];
    items[i ← i + 1] ← FormSW.BooleanItem[
      tag: "UseCount"L, place: useCountPlace, switch: @useLoopCount];
    items[i ← i + 1] ← FormSW.NumberItem[
      tag: "LoopCount"L, place: countPlace, value: @loopCount, radix: decimal,
      notNegative: TRUE, signed: FALSE, default: 100];
    items[i ← i + 1] ← FormSW.StringItem[
      tag: "NetAddr"L, place: machineIDPlace, string: @machineIDString,
      readOnly: TRUE, drawBox: TRUE];
    items[i ← i + 1] ← FormSW.NumberItem[
      tag: "LocalSocket"L, place: localSocketPlace, value: @localSocket,
      radix: octal, notNegative: TRUE, signed: FALSE, default: 400B];
    IF i # nParams - 1 THEN ERROR;
    END;


  -- utility tools specific routines

  GetLoopCount: PROCEDURE RETURNS [useCount: BOOLEAN, count: CARDINAL] =
    BEGIN RETURN[useLoopCount, loopCount]; END;

  FindLocalSocketID: PROCEDURE
    RETURNS [localSocketID: SpecialSystem.SocketNumber] =
    BEGIN RETURN[localSocket]; END;

  PrintPoop: PROCEDURE [who: STRING, loopCountUsed: BOOLEAN] =
    BEGIN
    Put.CR[fileSW];
    Put.Line[fileSW, who];
    Put.Text[fileSW, "LoopCount: "L];
    IF loopCountUsed THEN
      IF useLoopCount THEN Put.Decimal[fileSW, loopCount]
      ELSE Put.Text[fileSW, "infinite"L]
    ELSE Put.Text[fileSW, "Not Used"L];
    Put.Text[fileSW, "	LocalSocket: "L];
    Put.Octal[fileSW, LOOPHOLE[localSocket, UNSPECIFIED]];
    Put.CR[fileSW];
    END;


  -- tool command routines

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

  ListenerTestSinkCmd: PUBLIC FormSW.ProcType =
    BEGIN
    primaryNetID: SpecialSystem.NetworkNumber ←
      Router.FindDestinationRelativeNetID[SpecialSystem.nullNetworkNumber];
    myHostID: SpecialSystem.HostNumber ← Router.FindMyHostID[];
    localSocketID: SpecialSystem.SocketNumber ← FindLocalSocketID[];
    localAddr: NetworkAddress ← [primaryNetID, myHostID, localSocketID];
    IF networkStreamRunning THEN
      BEGIN
      MsgSW.Post[msgSW, "A test is already running. Use STOP to abort it."];
      RETURN;
      END;
    PrintPoop["SinkTestReceive", FALSE];
    networkStreamRunning ← TRUE;
    pleaseStop ← FALSE;
    Process.Detach[FORK ListenerTestSink[localAddr]];
    MsgSW.Post[msgSW, "SinkTestReceive successfully started"L];
    Process.Yield[];
    END;

  ListenerTestSink: PROCEDURE [localAddr: NetworkAddress] =
    BEGIN
    attnFork: PROCESS;
    listenerH: NetworkStream.ListenerHandle ← NetworkStream.CreateListener[
      localAddr];
    stH: Stream.Handle;
    DO
      stH ← NetworkStream.Listen[
	listenerH, NetworkStream.defaultWaitTime, NetworkStream.defaultWaitTime];
      IF stH # NIL THEN
	BEGIN
	connectionMsg: STRING ← Storage.String[80];
	remoteGuy: NetworkAddress ← NetworkStream.FindAddresses[stH].remote;
	String.AppendString[
	  connectionMsg, "SinkTestReceive Connection established with "L];
	AppendSuperLongToString[
	  connectionMsg, @remoteGuy.net, SIZE[SpecialSystem.NetworkNumber]];
	String.AppendChar[connectionMsg, '#];
	AppendSuperLongToString[
	  connectionMsg, @remoteGuy.host, SIZE[SpecialSystem.HostNumber]];
	String.AppendChar[connectionMsg, '#];
	AppendSuperLongToString[
	  connectionMsg, @remoteGuy.socket, SIZE[SpecialSystem.SocketNumber]];
	MsgSW.Post[msgSW, connectionMsg];
	Put.Line[fileSW, connectionMsg];
	Storage.FreeString[connectionMsg];
	attnFork ← FORK AttentionListener[stH];
	StreamTestSinkAgent[stH];
	JOIN attnFork;
	Stream.Delete[stH];
	IF pleaseStop THEN EXIT ELSE LOOP;
	END
      ELSE
	BEGIN
	IF pleaseStop THEN
	  BEGIN
	  msg: STRING ←
	    "SinkTestReceive: No requests for service within the default time."L;
	  MsgSW.Post[msgSW, msg];
	  Put.Line[fileSW, msg];
	  EXIT;
	  END
	ELSE BEGIN Put.Char[fileSW, 't]; LOOP; END;
	END;
      ENDLOOP;
    NetworkStream.DeleteListener[listenerH];
    networkStreamRunning ← FALSE;
    END;

  StreamTestSinkAgent: PROCEDURE [stH: Stream.Handle] =
    BEGIN
    c: CHARACTER;
    msg: STRING;
    counter: CARDINAL ← 0;
    dataByte: Stream.Byte ← 1;
    dataSpace: PACKED ARRAY [0..542) OF Stream.Byte;
    bl: Stream.Block ← [@dataSpace, 0, 542];
    options: Stream.InputOptions ← [TRUE, FALSE, FALSE, TRUE, FALSE];
    i: CARDINAL;
    bytesTransferred: CARDINAL;
    why: Stream.CompletionCode;
    inSST: Stream.SubSequenceType;
    closeStatus: NetworkStream.CloseStatus;
    suspended: BOOLEAN ← FALSE;
    Stream.SetInputOptions[stH, options];
    [bytesTransferred, why, inSST] ← Stream.GetBlock[stH, bl];
    FOR i IN [0..bytesTransferred) DO
      IF (dataSpace[i] # 1) AND (dataSpace[i] # 100) THEN
	BEGIN
	Put.Text[fileSW, "SinkTestReceive error: "L];
	Put.Decimal[fileSW, i];
	Put.Text[fileSW, ", "L];
	END;
      ENDLOOP;
    Put.Char[fileSW, '!];
    DO
      c ← '!;
      IF ((counter ← counter + 1) MOD 4) = 0 THEN Process.Yield[];
      [bytesTransferred, why, inSST] ← Stream.GetBlock[
	stH, bl !
	Stream.SSTChange =>
	  BEGIN
	  IF sst = NetworkStream.closeSST THEN EXIT;
	  Put.Char[fileSW, 'c];
	  RESUME
	  ;
	  END;
	NetworkStream.ConnectionSuspended =>
	  BEGIN
	  msg ← "SinkTestReceive: Connection suspended"L;
	  suspended ← TRUE;
	  EXIT;
	  END;
	Stream.TimeOut =>
	  BEGIN
	  msg ← "SinkTestReceive: GetBlock timed out.";
	  suspended ← TRUE;
	  EXIT;
	  END];
      FOR i IN [0..bytesTransferred) DO
	IF (dataSpace[i] # 1) AND (dataSpace[i] # 100) THEN
	  BEGIN
	  Put.Text[fileSW, "SinkTestReceive error: "L];
	  Put.Decimal[fileSW, i];
	  Put.Text[fileSW, ", "];
	  END;
	ENDLOOP;
      Put.Char[fileSW, '!];
      ENDLOOP;
    IF ~suspended THEN
      BEGIN
      -- conform to closing protocol
      -- first get any remaining bytes
      [bytesTransferred, why, inSST] ← Stream.GetBlock[stH, bl ! ANY => NULL];
      closeStatus ← NetworkStream.CloseReply[stH];
      msg ←
	IF closeStatus = good THEN
	"SinkTestReceive: stream connection cleanly closed."L
	ELSE "SinkTestReceive: stream connection closed with some doubt."L;
      END;
    Put.Line[fileSW, msg];
    MsgSW.Post[msgSW, msg];
    END;

  -- This simple attention listener waits for 4 attentions as we never send any more

  AttentionListener: PROCEDURE [stH: Stream.Handle] =
    BEGIN
    i: INTEGER ← 0;
    attnByte: Stream.Byte ← 0;
    FOR i IN [0..4) DO
      attnByte ← Stream.WaitForAttention[stH ! Stream.TimeOut => RETRY];
      Put.Text[fileSW, "attn: "];
      Put.Decimal[fileSW, attnByte];
      Put.CR[fileSW];
      ENDLOOP;
    END;

  ListenerTestSendCmd: PUBLIC FormSW.ProcType =
    BEGIN
    primaryNetID: SpecialSystem.NetworkNumber ←
      Router.FindDestinationRelativeNetID[SpecialSystem.nullNetworkNumber];
    myHostID: SpecialSystem.HostNumber ← Router.FindMyHostID[];
    localSocketID: SpecialSystem.SocketNumber ← FindLocalSocketID[];
    localAddr: NetworkAddress ← [primaryNetID, myHostID, localSocketID];
    IF networkStreamRunning THEN
      BEGIN
      MsgSW.Post[msgSW, "A test is already running. Use STOP to abort it."];
      RETURN;
      END;
    PrintPoop["SinkTestSend", TRUE];
    networkStreamRunning ← TRUE;
    pleaseStop ← FALSE;
    Process.Detach[FORK ListenerTestSend[localAddr]];
    MsgSW.Post[msgSW, "SinkTestSend successfully started"L];
    Process.Yield[];
    END;

  ListenerTestSend: PROCEDURE [localAddr: NetworkAddress] =
    BEGIN
    listenerH: NetworkStream.ListenerHandle ← NetworkStream.CreateListener[
      localAddr];
    stH: Stream.Handle;
    DO
      stH ← NetworkStream.Listen[
	listenerH, NetworkStream.defaultWaitTime, NetworkStream.defaultWaitTime];
      IF stH # NIL THEN
	BEGIN
	connectionMsg: STRING ← Storage.String[80];
	remoteGuy: NetworkAddress ← NetworkStream.FindAddresses[stH].remote;
	String.AppendString[
	  connectionMsg, "SinkTestSend Connection established with "L];
	AppendSuperLongToString[
	  connectionMsg, @remoteGuy.net, SIZE[SpecialSystem.NetworkNumber]];
	String.AppendChar[connectionMsg, '#];
	AppendSuperLongToString[
	  connectionMsg, @remoteGuy.host, SIZE[SpecialSystem.HostNumber]];
	String.AppendChar[connectionMsg, '#];
	AppendSuperLongToString[
	  connectionMsg, @remoteGuy.socket, SIZE[SpecialSystem.SocketNumber]];
	MsgSW.Post[msgSW, connectionMsg];
	Put.Line[fileSW, connectionMsg];
	Storage.FreeString[connectionMsg];
	NetworkStreamSendTest[stH];
	Stream.Delete[stH];
	IF pleaseStop THEN EXIT ELSE LOOP;
	END
      ELSE
	BEGIN
	IF pleaseStop THEN
	  BEGIN
	  msg: STRING ←
	    "SinkTestSend: No requests for service within the default time."L;
	  MsgSW.Post[msgSW, msg];
	  Put.Line[fileSW, msg];
	  EXIT;
	  END
	ELSE BEGIN Put.Char[fileSW, 't]; LOOP; END;
	END;
      ENDLOOP;
    NetworkStream.DeleteListener[listenerH];
    networkStreamRunning ← FALSE;
    END;

  NetworkStreamSendTest: PROCEDURE [streamH: Stream.Handle] =
    BEGIN
    msg: STRING;
    maxPush: CARDINAL;
    useMaxPush: BOOLEAN;
    suspended: BOOLEAN ← FALSE;
    counter: CARDINAL ← 0;
    putDataSpace: PACKED ARRAY [0..542) OF Stream.Byte;
    bl: Stream.Block ← [@putDataSpace, 0, 542];
    closeStatus: NetworkStream.CloseStatus;
    i: CARDINAL;
    outSST: Stream.SubSequenceType ← 0;
    dataByte: Stream.Byte ← 1;
    attnByte: Stream.Byte ← 100;
    [useMaxPush, maxPush] ← GetLoopCount[];
    FOR i IN [0..542) DO putDataSpace[i] ← dataByte; ENDLOOP;
    BEGIN
    ENABLE
      NetworkStream.ConnectionSuspended =>
	BEGIN
	suspended ← TRUE;
	msg ← "NetworkTester.TestSend: Connection suspended.";
	GOTO close;
	END;
    UNTIL pleaseStop OR (counter >= maxPush AND useMaxPush) DO
      Stream.PutByte[streamH, dataByte];
      Stream.PutByte[streamH, dataByte];
      Stream.SendNow[streamH];
      outSST ← 1;
      Stream.SetSST[streamH, outSST];
      outSST ← 2;
      Stream.SetSST[streamH, outSST];
      Stream.SendNow[streamH];
      bl ← [@putDataSpace, 0, 200];
      Stream.PutBlock[streamH, bl, FALSE];
      bl ← [@putDataSpace, 0, 201];
      Stream.PutBlock[streamH, bl, FALSE];
      bl ← [@putDataSpace, 0, 542];
      Stream.PutBlock[streamH, bl, TRUE];
      outSST ← 0;
      Stream.SetSST[streamH, outSST];
      IF ((counter ← counter + 1) MOD 4) = 0 THEN Process.Yield[];
      Put.Char[fileSW, '!];
      ENDLOOP;
    THROUGH [0..4) DO
      Stream.SendAttention[streamH, attnByte]; Put.Char[fileSW, 'a]; ENDLOOP;
    EXITS close => NULL;
    END; -- ENABLE
    IF ~suspended THEN
      BEGIN
      -- close the communication stream
      closeStatus ← NetworkStream.Close[streamH];
      IF closeStatus = good THEN
	msg ← "SinkTestSend: stream connection cleanly closed."L
      ELSE msg ← "SinkTestSend: stream connection closed with some doubt."L;
      END;
    Put.Line[fileSW, msg];
    MsgSW.Post[msgSW, msg];
    END;

  -- Mainline code

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

  END...