-- File: ArpaTelnetStreamImpl.mesa - last edit:
-- JAV                 25-Nov-87 10:30:31

-- Copyright (C) 1985, 1986, 1987 by Xerox Corporation. All rights reserved.

DIRECTORY
  Ascii USING [CR, LF],
  Environment USING [Block, Byte],
  Heap USING [Create, Delete],
  Inline USING [LowHalf],
  Process USING [Abort, Detach, DisableTimeout, EnableAborts, InitializeCondition, SetTimeout, Ticks, TicksToMsec],
  Space USING [wordsPerPage],
  String USING [AppendChar, MakeString],
  TcpStream USING [Closed, CompletionCode, Failed, Handle, Suspended, WaitTime],
  ArpaTelnetConstants,
  ArpaTelnetStream;

ArpaTelnetStreamImpl: MONITOR LOCKS userData USING userData: Data
  IMPORTS Heap, Inline, Process, String, TcpStream
  EXPORTS ArpaTelnetStream =

  {
  
  OPEN ArpaTelnetConstants, ArpaTelnetStream;
  
  UserDataEntry: PUBLIC TYPE = LONG POINTER TO UserDataRecord;
  UserDataRecord: TYPE = RECORD [
    clientData: TcpStream.Handle ← NIL,
    partner: LONG POINTER TO HostStatusRecord ← NIL,
    user: LONG POINTER TO StatusRecord ← NIL,
    userData: Data ← NIL,
    output: OutputString,
    crSeen: BOOLEAN ← FALSE,
    userZone: UNCOUNTED ZONE ← NIL];   
    
  StatusRecord: TYPE =  RECORD [
    optionsPossible: PACKED ARRAY OptionsEnnum OF BOOLEAN ← ALL[TRUE],
    optionsRecord: PACKED ARRAY OptionsEnnum OF BOOLEAN ← ALL[FALSE],
    Terminal: LONG STRING];
    
  OutputString: TYPE = RECORD [
   bytes: LONG POINTER TO PACKED ARRAY OF Environment.Byte,
   length: CARDINAL ← 0,
   maxlength: CARDINAL ← 0];
  
  Data: TYPE = LONG POINTER TO DataRecord;
  DataRecord: TYPE = MONITORED RECORD [
    ring: LONG POINTER TO DataBlock,
    takePoint: INTEGER ← -1,
    addPoint: INTEGER ← 0,
    haveData: BOOLEAN ← FALSE,
    RingHasData: CONDITION ← [timeout: 0],
    RingHasSpace: CONDITION ← [timeout: 0],
    awaitUrgentProcess: PROCESS ← NIL,
    charGetterProcess: PROCESS ← NIL,
    streamAborted: BOOLEAN ← FALSE,
    abortReason: AbortReason,
    crSeen: BOOLEAN ← FALSE,
    addLFToCR: BOOLEAN ← TRUE,
    urgentProcessing: BOOLEAN ← FALSE];
  
  DataBlock: TYPE = PACKED ARRAY [0..bufferSize) OF Environment.Byte ← ALL[0];
  
  bufferSize: CARDINAL =  128;
  
  outputBuffLen: CARDINAL =  128;
  size: CARDINAL = SIZE[Object] + outputBuffLen + SIZE[UserDataEntry];
  pages: CARDINAL = ((size/Space.wordsPerPage) + 2);
  
  WeAreHereString: LONG STRING ← "[YES]\n"L;
  
  Bytes: TYPE = RECORD [PACKED SEQUENCE COMPUTED CARDINAL OF Environment.Byte];
  
  GoAhead: PUBLIC SIGNAL = CODE;
  eraseLine: PUBLIC SIGNAL = CODE;
  eraseChar: PUBLIC SIGNAL = CODE;
  Abort: PUBLIC SIGNAL = CODE;
  Interrupt: PUBLIC SIGNAL = CODE;
  break: PUBLIC SIGNAL = CODE;
  
  Error: PUBLIC ERROR [reason: TelnetErrorReason] = CODE;
  StreamAborted: PUBLIC ERROR [abortReason: AbortReason] = CODE;
  
  -- PROCESSES

  AwaitUrgent: PROC [tty: Handle] =
    { 						 -- AwaitUrgent
    data: UserDataEntry ← tty.clientData;
    tcpStream: TcpStream.Handle ← data.clientData;
    dataBlock: PACKED ARRAY [0..256) OF Environment.Byte ← ALL[0];
    block: Environment.Block ← [@dataBlock, 0, 256];
    BEGIN ENABLE {
           TcpStream.Suspended => {
	     DestroyConnection[data.userData, 
	       SELECT why FROM
		 transmissionTimeout => transmissionTimeout,
		 noRouteToDestination => noRouteToDestination,
		 remoteServiceDisappeared => remoteServiceDisappeared,
		 reset => reset,
		 securityMismatch => securityMismatch,
		 precedenceMismatch => precedenceMismatch,
	       ENDCASE => other]; GOTO exit};
	   TcpStream.Failed => {
	     DestroyConnection[data.userData, 
	       SELECT why FROM
		 timeout => timeout,
		 noRouteToDestination => noRouteToDestination,
		 noServiceAtDestination => noServiceAtDestination,
		 remoteReject => remoteReject,
		 precedenceMismatch => precedenceMismatch,
		 securityMismatch => securityMismatch,
		 optionMismatch => optionMismatch,
	       ENDCASE => other]; GOTO exit};
	   ABORTED => GOTO exit};
    DO
      tcpStream.waitForUrgent[block];
      SetUrgent[data.userData, TRUE];
    ENDLOOP;
    END
    EXITS
      exit => RETURN;
    }; 						-- END AwaitUrgent
  
  SetUrgent: ENTRY PROCEDURE [userData: Data, boolean: BOOLEAN] = {userData.urgentProcessing ← boolean};

  CharGetter: PROC [tty: Handle] =
    { 						 -- CharGetter
    index: CARDINAL ← 0;
    dataBlock: Environment.Block ← [NIL, 0, 0];
    numChars: CARDINAL ← 0;
    why: TcpStream.CompletionCode;
    blockSize: CARDINAL ← 0;
    push: BOOLEAN ← FALSE;
    data: UserDataEntry ← tty.clientData;
    stream: TcpStream.Handle ← data.clientData;
    BEGIN ENABLE {
           TcpStream.Suspended => {Process.Detach[FORK DestroyConnection[data.userData, 
	       SELECT why FROM
		 transmissionTimeout => transmissionTimeout,
		 noRouteToDestination => noRouteToDestination,
		 remoteServiceDisappeared => remoteServiceDisappeared,
		 reset => reset,
		 securityMismatch => securityMismatch,
		 precedenceMismatch => precedenceMismatch,
	       ENDCASE => other]]; GOTO exit};
	   TcpStream.Failed => {Process.Detach[FORK DestroyConnection[data.userData, 
	       SELECT why FROM
		 timeout => timeout,
		 noRouteToDestination => noRouteToDestination,
		 noServiceAtDestination => noServiceAtDestination,
		 remoteReject => remoteReject,
		 precedenceMismatch => precedenceMismatch,
		 securityMismatch => securityMismatch,
		 optionMismatch => optionMismatch,
	       ENDCASE => other]]; GOTO exit};
           ABORTED => GOTO exit};
	   
    
    ProcessBlock: ENTRY PROC [charsAdded: CARDINAL ← 0, userData: Data] =
    {
    OPEN userData;
    ENABLE UNWIND => NULL;
      IF takePoint = -1 THEN takePoint ← addPoint;
      addPoint ← addPoint + charsAdded;
      IF addPoint >= bufferSize THEN
        addPoint ← addPoint - bufferSize;
      haveData ← TRUE;
      BROADCAST RingHasData;
    };
    
    WaitOnSpace: ENTRY PROCEDURE [userData: Data] =
    {
    ENABLE UNWIND => NULL;
      WAIT userData.RingHasSpace
    };
    
    {
    OPEN data.userData;
    stream.setWaitTime[500];
      DO
	IF takePoint > addPoint THEN
	  blockSize ← takePoint - addPoint
	ELSE IF addPoint > takePoint THEN
	  blockSize ← bufferSize - addPoint
	ELSE {
	  WaitOnSpace[data.userData];
	  LOOP;
	};
	blockSize ← MIN[128, blockSize]; 	-- 128 default get size
	dataBlock ← [LOOPHOLE[ring], addPoint, (blockSize + addPoint)];
	[numChars, why] ← stream.get[dataBlock];
	IF why = closing THEN {
	  Process.Detach[FORK DestroyConnection[data.userData, closing]];
	  EXIT};
	IF numChars # 0 THEN ProcessBlock[numChars, data.userData];
      ENDLOOP;
    }
    END
    EXITS 
      exit => RETURN;
    }; 						 -- END CharGetter


  -- PROCEDURES
  
  DestroyConnection: ENTRY PROCEDURE [userData: Data, reason: AbortReason] = {
    ENABLE UNWIND => NULL;
    userData.streamAborted ← TRUE;
    userData.abortReason ← reason;
    IF userData = NIL THEN RETURN;	-- connection already deleted some other way.
    IF userData.awaitUrgentProcess = NIL OR userData.charGetterProcess = NIL THEN RETURN;
    Process.Abort[userData.charGetterProcess];
    JOIN userData.charGetterProcess;
    Process.Abort[userData.awaitUrgentProcess];
    JOIN userData.awaitUrgentProcess;
    userData.awaitUrgentProcess ← userData.charGetterProcess ← NIL;
    BROADCAST userData.RingHasData;
    BROADCAST userData.RingHasSpace;
   };

  Terminate: ENTRY PROC [userData: Data] =
    { 						-- Terminate
    ENABLE UNWIND => NULL;
    BROADCAST userData.RingHasData;
    BROADCAST userData.RingHasSpace;
    };						-- END Terminate

 
  Create: PUBLIC PROC [input: TcpStream.Handle, options: Options, addLFToCR: BOOLEAN] RETURNS [telnetStream: Handle] = {
    StreamZone: UNCOUNTED ZONE ← Heap.Create[initial: pages];
    data: UserDataEntry ← NIL;
    telnetStream ← StreamZone.NEW[Object ← [
      	options: options,
        getByte: GetByte,
        putByte: PutByte,
        get: GetBlock,
        put: PutBlock,
	push: Push,
        delete: Delete,
	clientData: data,
	getTimeout: GetTimeout,
	setTimeout: SetTimeout,
	setInputOptions: SetInputOptions,
	flushDataLine: FlushDataLine]
	];
    data ← telnetStream.clientData ← StreamZone.NEW[UserDataRecord];
    data.userZone ← StreamZone;
    data.userData ← StreamZone.NEW[DataRecord];
    data.userData.addLFToCR ← addLFToCR;
    data.userData.ring ← StreamZone.NEW[DataBlock];
    data.output ← [bytes: LOOPHOLE[StreamZone.NEW[Bytes [bufferSize]]], length: 0, maxlength: bufferSize];
    data.clientData ← input;
    data.partner ← StreamZone.NEW[HostStatusRecord];
    data.user ← StreamZone.NEW[StatusRecord];
    data.user.Terminal ← String.MakeString[StreamZone, 40];
    Process.InitializeCondition[@data.userData.RingHasData, 0];
    Process.EnableAborts[@data.userData.RingHasData];
    Process.DisableTimeout[@data.userData.RingHasData];
    Process.InitializeCondition[@data.userData.RingHasSpace, 0];
    Process.EnableAborts[@data.userData.RingHasSpace];
    Process.DisableTimeout[@data.userData.RingHasSpace];
    data.userData.awaitUrgentProcess ← FORK AwaitUrgent[telnetStream];
    data.userData.charGetterProcess ← FORK CharGetter[telnetStream];
    data.user.optionsPossible[Binary] ← options.willBinary;
    data.user.optionsPossible[Echo] ← options.willEcho;
    data.user.optionsPossible[SupGA] ← FALSE;
    data.user.optionsPossible[Status] ← options.willStatus;
    data.user.optionsPossible[TimeMark] ← FALSE;
    data.user.optionsPossible[TerminalType] ← FALSE;
    data.user.optionsPossible[EOR] ← FALSE;
    data.user.optionsPossible[ExtendedOptionsList] ← FALSE;
    RETURN[telnetStream]};
    
  RemoveChars: ENTRY PROCEDURE [charsRemoved: CARDINAL ← 0, userData: Data] =
  {
  OPEN userData;
  ENABLE UNWIND => NULL;
      takePoint ← takePoint + charsRemoved;
    IF takePoint >= bufferSize THEN
      takePoint ← bufferSize - takePoint;
    IF takePoint = addPoint THEN {
      takePoint ← -1;
      haveData ← FALSE
    };
    BROADCAST RingHasSpace;
  };
  
  FlushLine: ENTRY PROCEDURE [userData: Data] =
  {
    ENABLE UNWIND => NULL;
    IF userData = NIL THEN ERROR ABORTED;
    IF userData.streamAborted THEN ERROR StreamAborted[userData.abortReason];
    userData.takePoint ← -1;  -- empty input buffer
    userData.haveData ← FALSE;
    userData.crSeen ← FALSE;
    BROADCAST userData.RingHasSpace;
  };
   
  WaitOnData: ENTRY PROCEDURE [userData: Data] =
  {
  ENABLE UNWIND => NULL;
    WAIT userData.RingHasData
  }; 
  
  GetByte: PUBLIC PROC [sH: Handle] RETURNS [byte: Environment.Byte ← 0, code: ReturnRecord] =
    BEGIN
  
    GetInternalByte: PROCEDURE [tty: Handle] RETURNS [byte: Environment.Byte ← 0] =
    BEGIN     
     
      Get: PROCEDURE = {
        UNTIL data.userData.takePoint # -1 DO
          WaitOnData[data.userData];
	  IF data = NIL OR data.userData = NIL THEN ERROR ABORTED;
          IF ~data.userData.haveData THEN ERROR ABORTED; 
        ENDLOOP;
	byte ← data.userData.ring[data.userData.takePoint];
	RemoveChars[1, data.userData]};

      Dont: PROCEDURE RETURNS[return: BOOLEAN ← TRUE] = {
      option: OptionsEnnum;
	Get[];
	SELECT byte FROM
	   BinaryOp => {
	     code.returnCode ← binary;
	     code.on ← FALSE;
	     option ← Binary};
	   EchoOp => {
	     code.returnCode ← echo;
	     code.on ← FALSE;
	     option ← Echo};
	   SupGA => option ← SupGA;
	   StatusOp => option ← Status;
	   TimeMark => option ← TimeMark;
 	   TerminalType => option ← TerminalType;
	   EOR => option ← EOR;
	   ExtendedOptionsList => option ← ExtendedOptionsList;
	ENDCASE => RETURN[FALSE];
	IF data.user.optionsRecord[option] THEN
	  data.user.optionsRecord[option] ← FALSE};
	  
      DoIT: PROCEDURE RETURNS[return: BOOLEAN ← TRUE] = {
      option: OptionsEnnum;
         Get[];
	SELECT byte FROM
	   BinaryOp => {
	     code.returnCode ← binary;
	     code.on ← TRUE;
	     option ← Binary};
	   EchoOp => {
	     code.returnCode ← echo;
	     code.on ← TRUE;
	     option ← Echo};
	   SupGA => {option ← SupGA; return ← FALSE};
	   StatusOp => option ← Status;
	   TimeMark => option ← TimeMark;
 	   TerminalType => option ← TerminalType;
	   EOR => option ← EOR;
	   ExtendedOptionsList => option ← ExtendedOptionsList;
	ENDCASE => RETURN[FALSE];
	IF data.user.optionsPossible[option] THEN
	  data.user.optionsRecord[option] ← TRUE
	ELSE {
	  PutByteInternal[sH, IAC, FALSE];
	  PutByteInternal[sH, WONT, FALSE];
	  PutByteInternal[sH, byte, FALSE];
	  SendNow[sH, TRUE, FALSE]}
      };
      
      Will: PROCEDURE = {
        OPEN data.partner;
        Get[];
	SELECT byte FROM
	   BinaryOp => {optionsPossible[Binary] ← TRUE; optionsVerified[Binary] ← TRUE};
	   EchoOp => {optionsPossible[Echo] ← TRUE; optionsVerified[Echo] ← TRUE};
	   SupGA => {optionsPossible[SupGA] ← TRUE; optionsVerified[SupGA] ← TRUE};
	   StatusOp => {optionsPossible[Status] ← TRUE; optionsVerified[Status] ← TRUE};
	   TimeMark => {optionsPossible[TimeMark] ← TRUE; optionsVerified[TimeMark] ← TRUE};
 	   TerminalType => {optionsPossible[TerminalType] ← TRUE; optionsVerified[TerminalType] ← TRUE};
	   EOR => {optionsPossible[EOR] ← TRUE; optionsVerified[EOR] ← TRUE};
	   ExtendedOptionsList => {optionsPossible[ExtendedOptionsList] ← TRUE; optionsVerified[ExtendedOptionsList] ← TRUE};
	ENDCASE;
      };
      
      Wont: PROCEDURE = {
        OPEN data.partner;
        Get[];
	SELECT byte FROM
	   BinaryOp => {optionsPossible[Binary] ← FALSE; optionsVerified[Binary] ← TRUE};
	   EchoOp => {optionsPossible[Echo] ← FALSE; optionsVerified[Echo] ← TRUE; ERROR Error[doesntEcho]};
	   SupGA => {optionsPossible[SupGA] ← FALSE; optionsVerified[SupGA] ← TRUE};
	   StatusOp => {optionsPossible[Status] ← FALSE; optionsVerified[Status] ← TRUE};
	   TimeMark => {optionsPossible[TimeMark] ← FALSE; optionsVerified[TimeMark] ← TRUE};
 	   TerminalType => {optionsPossible[TerminalType] ← FALSE; optionsVerified[TerminalType] ← TRUE};
	   EOR => {optionsPossible[EOR] ← FALSE; optionsVerified[EOR] ← TRUE};
	   ExtendedOptionsList => {optionsPossible[ExtendedOptionsList] ← FALSE; optionsVerified[ExtendedOptionsList] ← TRUE};
	ENDCASE;
      };
    
      DoSubNegotiation: PROCEDURE = 
      BEGIN
      
        StatusSugNego: PROCEDURE = 
	BEGIN
	  Get[];
	  IF byte = IS THEN {
	    Get[];
	    SELECT byte FROM
	      WILL => {
	        DO
		  OPEN data.partner;
		  Get[];
		  SELECT byte FROM
		    BinaryOp => {optionsPossible[Binary] ← TRUE; optionsVerified[Binary] ← TRUE};
		    EchoOp => {optionsPossible[Echo] ← TRUE; optionsVerified[Echo] ← TRUE};
		    SupGA => {optionsPossible[SupGA] ← TRUE; optionsVerified[SupGA] ← TRUE};
		    StatusOp => {optionsPossible[Status] ← TRUE; optionsVerified[Status] ← TRUE};
		    TimeMark => {optionsPossible[TimeMark] ← TRUE; optionsVerified[TimeMark] ← TRUE};
		    TerminalType => {optionsPossible[TerminalType] ← TRUE; optionsVerified[TerminalType] ← TRUE};
		    EOR => {optionsPossible[EOR] ← TRUE; optionsVerified[EOR] ← TRUE};
		    ExtendedOptionsList => {optionsPossible[ExtendedOptionsList] ← TRUE; optionsVerified[ExtendedOptionsList] ← TRUE};
		  ENDCASE;
		ENDLOOP};
	      Do => {
	        DO
		  OPEN data.partner;
		  Get[];
		  SELECT byte FROM
		    BinaryOp => {optionsRecord[Binary] ← TRUE; optionsVerified[Binary] ← TRUE};
		    EchoOp => {optionsRecord[Echo] ← TRUE; optionsVerified[Echo] ← TRUE};
		    SupGA => {optionsRecord[SupGA] ← TRUE; optionsVerified[SupGA] ← TRUE};
		    StatusOp => {optionsRecord[Status] ← TRUE; optionsVerified[Status] ← TRUE};
		    TimeMark => {optionsRecord[TimeMark] ← TRUE; optionsVerified[TimeMark] ← TRUE};
		    TerminalType => {optionsRecord[TerminalType] ← TRUE; optionsVerified[TerminalType] ← TRUE};
		    EOR => {optionsRecord[EOR] ← TRUE; optionsVerified[EOR] ← TRUE};
		    ExtendedOptionsList => {optionsRecord[ExtendedOptionsList] ← TRUE; optionsVerified[ExtendedOptionsList] ← TRUE};
		  ENDCASE;
		ENDLOOP};
	    ENDCASE}
	  ELSE IF byte = Send THEN {
	    OPEN data.user;
	    codeRecord: PACKED ARRAY OptionsEnnum OF Environment.Byte ← [BinaryOp, EchoOp, SupGA, StatusOp, TimeMark, TerminalType, EOR, ExtendedOptionsList];
	    PutByteInternal[sH, IAC, FALSE];
	    PutByteInternal[sH, SB, FALSE];
	    FOR i: OptionsEnnum IN [Binary..ExtendedOptionsList] DO
	      IF optionsRecord[i] THEN {
	        PutByteInternal[sH, WILL, FALSE];
	        PutByteInternal[sH, codeRecord[i], FALSE]};
	      IF optionsPossible[i] THEN {
	        PutByteInternal[sH, Do, FALSE];
	        PutByteInternal[sH, codeRecord[i], FALSE]}
	    ENDLOOP;
	    PutByteInternal[sH, IAC, FALSE];
	    PutByteInternal[sH, SE, FALSE]}
	  ELSE RETURN
	END;
	  
	TerminalTypeSugNego: PROCEDURE = {
	OPEN data.user;
	iacSeen: BOOLEAN ← FALSE;
	seSeen: BOOLEAN ← FALSE;
	  Get[];
	  IF byte = IS THEN {
	    OPEN data.partner;
	    DO
	      Get[];
	      IF byte = IAC THEN IF ~iacSeen THEN {iacSeen ← TRUE; LOOP} ELSE iacSeen ← FALSE;
	      IF byte = SE AND iacSeen THEN EXIT ELSE IF seSeen THEN seSeen ← FALSE ELSE {seSeen ←  TRUE; LOOP};
	      String.AppendChar[Terminal, LOOPHOLE[byte]];
	    ENDLOOP}
	  ELSE IF byte = Send THEN {
	  OPEN data.user;
	    PutByteInternal[sH, IAC, FALSE];
	    PutByteInternal[sH, SB, FALSE];
	    PutByteInternal[sH, TerminalType, FALSE];
	    PutByteInternal[sH, IS, FALSE];
	    FOR i: CARDINAL IN [0..Terminal.length] DO
	      PutByteInternal[sH, LOOPHOLE[Terminal.text[i]], FALSE] ENDLOOP;
	    PutByteInternal[sH, IAC, FALSE];
	    PutByteInternal[sH, SE, FALSE]}
	  ELSE RETURN};
	  
	ExtededOPSugNego: PROCEDURE = {<<
	  Get[];
	  IF byte = IS THEN
	  ELSE IF byte = Send THEN
	  ELSE RETURN>>};
	  
	  
        Get[];
	SELECT byte FROM
	  StatusOp => StatusSugNego[];
	  TerminalType => TerminalTypeSugNego[];
	  ExtendedOptionsList => ExtededOPSugNego[];
	ENDCASE => UNTIL byte = SE DO Get[] ENDLOOP
      END;
      
      DO
	Get[];
	IF byte = IAC THEN {
	  Get[];
	  SELECT byte FROM
	     IAC => IF data.userData.urgentProcessing THEN LOOP ELSE RETURN[byte];
	     DONT => IF Dont[] THEN RETURN;
	     Do => IF DoIT[] THEN RETURN;
	     WONT => Wont[];
	     WILL => Will[];
	     SB => DoSubNegotiation[];
	     ArpaTelnetConstants.GA => IF sH.options.signalOnGoAhead THEN SIGNAL GoAhead ELSE code.returnCode ← goAhead;
	     EL => IF data.userData.urgentProcessing THEN LOOP ELSE IF sH.options.signalOnEraseLine THEN SIGNAL eraseLine ELSE code.returnCode ← eraseLine;
	     EC => IF data.userData.urgentProcessing THEN LOOP ELSE IF sH.options.signalOnEraseChar THEN SIGNAL eraseChar ELSE code.returnCode ← eraseChar;
	     AYT => PutBlock[sH, [LOOPHOLE[@WeAreHereString.text], 0, WeAreHereString.length], TRUE];
	     AO => {FlushOutput[data]; IF sH.options.signalOnAbort THEN SIGNAL Abort ELSE code.returnCode ← abort};
	     IP => IF sH.options.signalOnInterrupt THEN SIGNAL Interrupt ELSE code.returnCode ← interrupt;
	     BREAK => IF sH.options.signalOnBreak THEN SIGNAL break ELSE code.returnCode ← break;
	     DM => SetUrgent[data.userData, FALSE];
	     NOP => LOOP;
	     SE => NULL;	-- should not be seen without SB
	   ENDCASE}
	 ELSE
	   RETURN[byte];
	 IF code.returnCode # normal THEN RETURN;
       ENDLOOP
     END;	-- GetByteInternal
     
    data: UserDataEntry ← sH.clientData;  
    IF data = NIL OR data.userData = NIL OR data.userData.streamAborted THEN ERROR StreamAborted[data.userData.abortReason];
    byte ← GetInternalByte[sH];
    IF data.userData.crSeen AND ~data.user.optionsRecord[Binary] AND data.userData.addLFToCR THEN
      {data.userData.crSeen ← FALSE; byte ← GetInternalByte[sH]};	-- drop LFs
    IF byte = LOOPHOLE[Ascii.CR] AND ~data.user.optionsRecord[Binary] AND data.userData.addLFToCR THEN 
      data.userData.crSeen ← TRUE ELSE data.userData.crSeen ← FALSE;
    RETURN[byte, code];
  END;
  
  FlushOutput: PROC [data: UserDataEntry] =
  BEGIN
    data.output.length ← 0
  END;
  
  PutByteInternal: PROCEDURE [sH: Handle, byte: Environment.Byte, push: BOOLEAN] =
  {
  data: UserDataEntry ← sH.clientData;
    IF data = NIL OR data.userData = NIL OR data.userData.streamAborted THEN ERROR StreamAborted[data.userData.abortReason];
    data.output.bytes[data.output.length] ← byte;
    data.output.length ← data.output.length + 1;
    IF data.userData.addLFToCR AND byte = LOOPHOLE[Ascii.CR] THEN data.crSeen ← TRUE;
    IF data.crSeen THEN {
      IF byte # LOOPHOLE[Ascii.LF] THEN PutByteInternal[sH, LOOPHOLE[Ascii.LF], FALSE]; data.crSeen ← FALSE};
    IF data.user.optionsRecord[Echo] OR push THEN SendNow[sH, TRUE, FALSE]	-- remote Echo or pushing
    ELSE IF data.output.length >= bufferSize THEN SendNow[sH, FALSE, FALSE];
  };

  PutByte: PUBLIC PROC [sH: Handle, byte: Environment.Byte, push: BOOLEAN] =
  {
  data: UserDataEntry ← sH.clientData;
    IF data = NIL OR data.userData = NIL OR data.userData.streamAborted THEN ERROR StreamAborted[data.userData.abortReason];
    PutByteInternal[sH, byte, FALSE];
    IF byte = IAC THEN PutByteInternal[sH, IAC, FALSE];
  };
  
  GetBlock: PUBLIC PROCEDURE [sH: Handle, block: Environment.Block] RETURNS [bytesTransferred: CARDINAL ← 0, code: ReturnRecord] = {
    [block.blockPointer[block.startIndex], code] ← GetByte[sH];
    IF code.returnCode = normal THEN bytesTransferred ← 1 ELSE bytesTransferred ← 0
  };
  
  PutBlock: PUBLIC PROCEDURE [sH: Handle, block: Environment.Block, push: BOOLEAN] = {
  data: UserDataEntry ← sH.clientData;
    FOR i: CARDINAL IN [block.startIndex..block.stopIndexPlusOne) DO
      data.output.bytes[data.output.length] ← block.blockPointer[i];
      data.output.length ← data.output.length + 1;
      IF data.userData.addLFToCR AND block.blockPointer[i] = LOOPHOLE[Ascii.CR] THEN data.crSeen ← TRUE;
      IF data.crSeen THEN {
        IF block.blockPointer[i] # LOOPHOLE[Ascii.LF] THEN {
          data.output.bytes[data.output.length] ← LOOPHOLE[Ascii.LF];
          data.output.length ← data.output.length + 1};
	data.crSeen ← FALSE};
      IF block.blockPointer[i] = IAC THEN {
	data.output.bytes[data.output.length] ← IAC;
	data.output.length ← data.output.length + 1};
      IF data.output.length >= bufferSize THEN SendNow[sH, FALSE, FALSE];
    ENDLOOP;
    IF push THEN SendNow[sH, TRUE, FALSE]};
   
  Push: PUBLIC PROCEDURE [sH: Handle] = {SendNow[sH, TRUE, FALSE]}; 
  
  Delete: PUBLIC PROCEDURE [sH: Handle] = {
  data: UserDataEntry ← sH.clientData;
  tempZone: UNCOUNTED ZONE ← data.userZone;
  byte: PACKED ARRAY [0..256) OF Environment.Byte ← ALL[0];
  why: TcpStream.CompletionCode ← normal;
  BEGIN ENABLE {
    TcpStream.Suspended => CONTINUE;
    TcpStream.Closed => CONTINUE};
    
    IF data = NIL THEN RETURN;
    IF data.userData # NIL THEN {
      InternalDelete[tempZone, data.userData, sH];
      IF data.userData # NIL THEN {
	JoinProcesses[data.userData];
	IF data.userData.ring # NIL THEN tempZone.FREE[@data.userData.ring];
	tempZone.FREE[@data.userData]}};
    data.userData ← NIL;
    IF data.clientData # NIL THEN {
      data.clientData.close[ !
	TcpStream.Suspended => CONTINUE;
	TcpStream.Closed => CONTINUE];	-- no get since no more data is expected or wanted.
      data.clientData.destroy[data.clientData !
	TcpStream.Suspended => CONTINUE;
	TcpStream.Closed => CONTINUE]};
    data.clientData ← NIL;
    IF data # NIL THEN {
      IF data.output.bytes # NIL THEN {
        tempZone.FREE[@data.output.bytes];
	data.output.bytes ← NIL};
      IF data.partner # NIL THEN {
        tempZone.FREE[@data.partner];
	data.partner ← NIL};
      IF data.user # NIL THEN {
        tempZone.FREE[@data.user];
	data.user ← NIL};
      IF data # NIL THEN {
        tempZone.FREE[@data];
	data ← NIL}};
    sH.clientData ← LOOPHOLE[LONG[0]];	-- NIL
    tempZone.FREE[@sH];
    Heap.Delete[tempZone];
  END;
  };
    
  InternalDelete: ENTRY PROCEDURE [zone: UNCOUNTED ZONE, userData: Data, sH: Handle] =
  {
  ENABLE UNWIND => NULL;
  data: UserDataEntry ← sH.clientData;
    IF userData = NIL THEN RETURN;
    userData.streamAborted ← TRUE;   
     IF userData.awaitUrgentProcess # NIL THEN Process.Abort[userData.awaitUrgentProcess];
    IF userData.charGetterProcess # NIL THEN Process.Abort[userData.charGetterProcess];
    BROADCAST userData.RingHasData;
    BROADCAST userData.RingHasSpace;
  };
  
  JoinProcesses: ENTRY PROCEDURE [userData: Data] =
  {
  ENABLE UNWIND => NULL;
    IF userData.charGetterProcess # NIL THEN {
      JOIN userData.charGetterProcess;
      userData.charGetterProcess ← NIL};
    IF userData.awaitUrgentProcess # NIL THEN {
      JOIN userData.awaitUrgentProcess;
      userData.awaitUrgentProcess ← NIL};
  };
    
  SendNow: PROCEDURE [sH: Handle, push, urgent: BOOLEAN ← FALSE] =
    {  						-- { SendNow
    data: UserDataEntry ← sH.clientData;
    block: Environment.Block ← [
      blockPointer: LOOPHOLE[data.output.bytes],
      startIndex: 0,
      stopIndexPlusOne: data.output.length];
      
  BEGIN ENABLE TcpStream.Suspended => CONTINUE;
      
    IF data.output.length # 0 THEN {
      data.clientData.put[block, push, urgent];
      data.output.length ← 0};
    IF data.crSeen THEN {PutByteInternal[sH, LOOPHOLE[Ascii.LF], push]; data.crSeen ← FALSE};
    END;
    }; 						-- END SendNow

  GetTimeout: PUBLIC PROC [sH: Handle] RETURNS [waitTime: TcpStream.WaitTime] =
    {
    data: UserDataEntry ← sH.clientData;
      IF data = NIL THEN ERROR ABORTED;
      RETURN[Process.TicksToMsec[data.userData.RingHasData.timeout]];
    };
 
  SetTimeout: PUBLIC PROC [sH: Handle, timeOut: TcpStream.WaitTime] =
    {
    data: UserDataEntry ← sH.clientData;
    IF data = NIL THEN ERROR ABORTED;
    IF timeOut > LAST[CARDINAL] THEN timeOut ← LAST[CARDINAL];
    Process.SetTimeout[@data.userData.RingHasData, Inline.LowHalf[timeOut]];
    };
  
  SetInputOptions: PUBLIC PROCEDURE [sH: Handle, options: Options] = {
    sH.options ← options};
  
  FlushDataLine: PUBLIC PROCEDURE [sH: Handle] = {
    data: UserDataEntry ← sH.clientData;
    IF data = NIL OR data.userData = NIL THEN ERROR ABORTED;
    FlushLine[data.userData]
    };
    
  GA: PUBLIC PROCEDURE [sH: Handle] = 
    {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, ArpaTelnetConstants.GA, FALSE];
      SendNow[sH, TRUE, FALSE];
    };
    
  AbortOutput: PUBLIC PROCEDURE [sH: Handle] = 
    {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, AO, FALSE];
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, DM, FALSE];
      SendNow[sH, TRUE, TRUE];
    };
	
  InterruptProcess: PUBLIC PROCEDURE [sH: Handle] = 
    {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, IP, FALSE];
      SendNow[sH, TRUE, FALSE];
    };
	
  AreYouThere: PUBLIC PROCEDURE [sH: Handle] = 
    {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, AYT, FALSE];
      SendNow[sH, TRUE, FALSE];
    };
	
  EraseLine: PUBLIC PROCEDURE [sH: Handle] = 
    {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, EL, FALSE];
      SendNow[sH, TRUE, FALSE];
    };
  
  EraseChar: PUBLIC PROCEDURE [sH: Handle] = 
    {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, EC, FALSE];
      SendNow[sH, TRUE, FALSE];
    };
  
  Echo: PUBLIC PROCEDURE [sH: Handle, on: BOOLEAN] = 
    {
    data: UserDataEntry ← sH.clientData;
      IF on THEN 
	IF data.partner.optionsPossible[Echo] THEN {
	  PutByteInternal[sH, IAC, FALSE];
	  PutByteInternal[sH, Do, FALSE];
	  PutByteInternal[sH, EchoOp, FALSE]}
	ELSE ERROR Error[doesntEcho]
      ELSE {
        PutByteInternal[sH, IAC, FALSE];
	PutByteInternal[sH, DONT, FALSE];
        PutByteInternal[sH, EchoOp, FALSE]; 
	};
      SendNow[sH, TRUE, FALSE];
    };
  
  Binary: PUBLIC PROCEDURE [sH: Handle, on: BOOLEAN] = 
    {   
    data: UserDataEntry ← sH.clientData;
      IF data = NIL THEN ERROR ABORTED;
      IF on THEN 
	IF data.partner.optionsPossible[Binary] THEN {
	  PutByteInternal[sH, IAC, FALSE];
	  PutByteInternal[sH, Do, FALSE];
	  PutByteInternal[sH, BinaryOp, FALSE]}
	ELSE ERROR Error[doesntBinary]
      ELSE {
        PutByteInternal[sH, IAC, FALSE];
	PutByteInternal[sH, DONT, FALSE];
        PutByteInternal[sH, BinaryOp, FALSE]; 
	};
      SendNow[sH, TRUE, FALSE];
    };
    
  Break: PUBLIC PROCEDURE [sH: Handle] = 
    {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, BREAK, FALSE];
      SendNow[sH, TRUE, FALSE];
    };

  
  Status: PUBLIC PROCEDURE [sH: Handle] RETURNS [status: LONG POINTER TO HostStatusRecord] = 
    {
    data: UserDataEntry ← sH.clientData;
    IF data = NIL THEN ERROR ABORTED;
    IF data.partner.optionsPossible[Status] THEN {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, SB, FALSE];
      PutByteInternal[sH, StatusOp, FALSE];
      PutByteInternal[sH, Send, FALSE];
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, SE, FALSE];
      SendNow[sH, TRUE, FALSE]}
    ELSE ERROR Error[doesntStatus];
    RETURN[data.partner];
    };
  
  SetTerminalType: PUBLIC PROCEDURE [sH: Handle, terminalType: LONG STRING] RETURNS [success: BOOLEAN ← TRUE] = 
    {
    userData: UserDataEntry ← sH.clientData;
    IF userData = NIL THEN ERROR ABORTED;
    IF userData.partner.optionsPossible[TerminalType] THEN {
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, SB, FALSE];
      PutByteInternal[sH, IS, FALSE];
      userData.user.Terminal.length ← 0;
      FOR i: CARDINAL IN [0..terminalType.length) DO
        PutByteInternal[sH, LOOPHOLE[terminalType.text[i]], FALSE];
        String.AppendChar[userData.user.Terminal, terminalType.text[i]];
      ENDLOOP;
      PutByteInternal[sH, IAC, FALSE];
      PutByteInternal[sH, SE, FALSE];
      SendNow[sH, TRUE, FALSE]}
    ELSE ERROR Error[doesntTermType];
    };
  


}.								-- END ArpaTelnetStreamImpl


LOG
10-Apr-85 10:34:06 JAV  Created.