-- 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.