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