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