PupTelnet:
CEDAR
MONITOR
LOCKS pc USING pc: PupConversation
Locking Order: PupConversation, then display stuff (i.e. Viewers & Tioga).
IMPORTS FS, IO, MessageWindow, Process, Protocols, PupName, PupStream, Rope, UserCredentials
= {OPEN Protocols;
pupTelnet: Protocol ←
NEW [ProtocolRep ← [
name: "PupTelnet",
Instantiate: Instantiate,
StartConnect: StartConnect,
StartDisconnect: StartDisconnect,
GiveUp: GiveUp,
SetOnLine: SetOnLine
]];
PupConversation: TYPE = REF PupConversationRep;
PupConversationRep:
TYPE =
MONITORED
RECORD [
fromClient, toClient, serverStream, logStream: IO.STREAM ← NIL,
charsSentSinceFlush: NAT ← 0,
changer: PROCESS ← NIL,
change: CONDITION,
procs: ARRAY Who OF PROCESS ← ALL[NIL],
phase: Phase ← running,
serverToUsering: BOOL ← TRUE,
gettingClientChar: BOOL ← FALSE];
Phase: TYPE = {running, destroying, destroyed};
Who: TYPE = {uToS, sToU};
whoBar: ARRAY Who OF Who ← [uToS: sToU, sToU: uToS];
MarkByte: TYPE = PupStream.MARK;
setLineWidth: MarkByte = 2;
setPageLength: MarkByte = 3;
timingMark: MarkByte = 5;
timingMarkReply: MarkByte = 6;
doSetLineWidth: BOOL ← TRUE;
minLogKeep: CARDINAL ← 10;
Instantiate:
PROC [client: Client, logFileName:
ROPE ←
NIL]
RETURNS [c: Conversant] = {
pc: PupConversation ←
NEW [PupConversationRep ← [
fromClient: client.fromClient,
toClient: client.toClient
]];
IF logFileName # NIL THEN SetLog[pc, logFileName];
TRUSTED {
Process.InitializeCondition[@pc.change, Process.SecondsToTicks[60]];
Process.EnableAborts[@pc.change];
};
c ←
NEW [ConversantPrivate ← [
protocol: pupTelnet,
client: client,
conversantData: pc,
driver: client.fromClient
]];
TRUSTED {
Process.Detach[pc.procs[uToS] ← FORK UserToServer[c, pc]];
Process.Detach[pc.procs[sToU] ← FORK ServerToUser[c, pc]];
};
};
SetLog:
PROC [pc: PupConversation, logFileName:
ROPE] = {
oldKeep: CARDINAL ← 1;
pc.logStream ← FS.StreamOpen[fileName: logFileName, accessOptions: create];
oldKeep ← FS.FileInfo[logFileName !FS.Error => CONTINUE].keep;
IF oldKeep < minLogKeep
THEN FS.SetKeep[logFileName, minLogKeep !FS.Error => CONTINUE];
};
NoteDestruction:
ENTRY
PROC [c: Conversant, pc: PupConversation] = {
ENABLE UNWIND => {};
InternalNoteDestruction[c, pc];
};
InternalNoteDestruction:
INTERNAL
PROC [c: Conversant, pc: PupConversation] = {
SELECT pc.phase
FROM
running => pc.phase ← destroying;
destroying, destroyed => NULL;
ENDCASE => ERROR;
BROADCAST pc.change;
SELECT c.state.connectivity
FROM
connected => InternalReallyStartDisconnect[c, pc, NIL, FALSE];
connecting, disconnecting => InternalGiveUp[c, pc, NIL];
disconnected => NULL;
ENDCASE => ERROR;
pc.phase ← destroyed;
BROADCAST pc.change;
};
Destroy: PROC [c: Conversant] = {
pc: PupConversation = NARROW[c.conversantData];
Start: ENTRY PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
SELECT pc.phase FROM
running => pc.phase ← destroying;
destroying, destroyed => ERROR;
ENDCASE => ERROR;
BROADCAST pc.change;
WHILE pc.serverToUsering DO WAIT pc.change ENDLOOP;
};
Really: ENTRY PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
pc.phase ← destroyed;
BROADCAST pc.change;
IF pc.gettingClientChar THEN TRUSTED {Process.Abort[pc.procs[uToS]]};
};
Start[pc];
SELECT c.state.connectivity FROM
connected => StartDisconnect[c, FALSE];
connecting, disconnecting => GiveUp[c];
disconnected => NULL;
ENDCASE => ERROR;
Really[pc];
};
StartConnect
:
PROC [
c: Conversant,
serverName: ROPE,
login: BOOL ← FALSE]
= {
pc: PupConversation = NARROW[c.conversantData];
Really:
ENTRY
PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
IF pc.phase >= destroying
THEN {
Scream["Can't connect a destroyed conversant!"];
RETURN};
SELECT c.state.connectivity
FROM
disconnected => {
c.serverName ← serverName;
c.state.connectivity ← connecting;
c.state.onLine ← TRUE;
c.driver ← IO.noInputStream;
BROADCAST pc.change;
c.client.type.NoteState[c !
UNWIND => {
c.state.connectivity ← disconnected;
BROADCAST pc.change;
};
];
TRUSTED {Process.Detach[
pc.changer ← FORK Connect[c, pc, serverName, login]
]};
};
connecting, connected, disconnecting => {
Scream["Already connected to %g!", [rope[c.serverName]]];
c.client.type.NoteState[c];
};
ENDCASE => ERROR;
};
Really[pc];
};
SetConnectivity:
ENTRY
PROC [c: Conversant, pc: PupConversation, cy: Connectivity, cond:
UNSAFE
PROCESS ←
NIL, serverStream:
IO.
STREAM ←
NIL]
RETURNS [ok:
BOOL] = {
IF ok ← (cond =
NIL
OR cond = pc.changer)
THEN {
c.state.connectivity ← cy;
c.state.onLine ← cy = connected;
pc.serverStream ← serverStream;
c.driver ← IF NOT c.state.onLine THEN c.client.fromClient ELSE IF serverStream # NIL THEN serverStream ELSE IO.noInputStream;
BROADCAST pc.change;
c.client.type.NoteState[c];
};
};
Connect:
PROC [c: Conversant, pc: PupConversation, serverName:
ROPE, login:
BOOL] = {
self: UNSAFE PROCESS = Process.GetCurrent[];
toClient: IO.STREAM = c.client.toClient;
{
ENABLE
{
IO.Error => IF ec = StreamClosed AND stream = toClient THEN GOTO Destroyed;
IO.EndOfStream => IF stream = toClient THEN GOTO Destroyed;
UNWIND => [] ← SetConnectivity[c, pc, disconnected, self];
};
addr: Pup.Address;
serverStream: IO.STREAM ← NIL;
toClient.PutF["\015\012Opening connection to %g ... ", IO.rope[serverName]];
toClient.Flush[];
addr ← PupName.NameLookup[serverName, PupWKS.telnet
! PupName.Error =>
{
Process.CheckForAbort[];
toClient.PutRope["PUP name lookup error"];
FinishStringWithErrorMsg[toClient, text];
GOTO Fail;
}
];
Process.CheckForAbort[];
serverStream ← PupStream.Create[addr, 10000, 10000
! PupStream.StreamClosing =>
{
Process.CheckForAbort[];
toClient.PutRope["Can't connect"];
FinishStringWithErrorMsg[toClient, text];
GOTO Fail;
}
];
Process.CheckForAbort[];
toClient.PutF["open.\015\012"];
toClient.Flush[];
IF doSetLineWidth
THEN {
PupStream.SendMark[serverStream, setLineWidth];
serverStream.PutChar[0C];
serverStream.Flush[!PupStream.Timeout => RESUME];
};
IF login
THEN {
name, password: Rope.ROPE;
[name: name, password: password] ← UserCredentials.Get[];
serverStream.PutRope["Login "];
serverStream.PutRope[name];
IF Rope.Find[s1: name, s2: "."] = -1 THEN serverStream.PutRope[".PA"];
serverStream.PutRope[" "];
serverStream.PutRope[password];
serverStream.PutRope[" \n"];
serverStream.Flush[!PupStream.Timeout => RESUME];
};
IF NOT SetConnectivity[c, pc, connected, self, serverStream] THEN serverStream.Close[TRUE !IO.Error => CONTINUE];
EXITS
Fail => {
[] ← SetConnectivity[c, pc, disconnected, self];
};
Destroyed => NoteDestruction[c, pc];
}};
FinishStringWithErrorMsg:
PROC [toClient:
IO.
STREAM, errorMsg:
ROPE, ending:
ROPE ← ".\015\012"] = {
IF errorMsg # NIL THEN toClient.PutF[":\t%s", IO.rope[errorMsg]];
toClient.PutRope[ending];
toClient.Flush[];
};
StartDisconnect:
PROC [c: Conversant, verbosely:
BOOL ←
TRUE] = {
ReallyStartDisconnect[c, IO.PutFR["Closing connection to %g", [rope[c.serverName]]], verbosely];
};
ReallyStartDisconnect:
PROC [c: Conversant, msg:
ROPE, verbosely:
BOOL ←
TRUE] = {
pc: PupConversation = NARROW[c.conversantData];
Really:
ENTRY
PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
InternalReallyStartDisconnect[c, pc, msg, verbosely];
};
Really[pc];
};
InternalReallyStartDisconnect:
INTERNAL
PROC [c: Conversant, pc: PupConversation, msg:
ROPE, verbosely:
BOOL] = {
IF pc.logStream # NIL THEN pc.logStream.Flush[!FS.Error => CONTINUE];
SELECT c.state.connectivity
FROM
disconnected => {
Scream["Already disconnected ?%g?", [rope[msg]]];
c.client.type.NoteState[c];
};
connecting => {
Scream["Can't disconnect because still connecting ?%g?", [rope[msg]]];
c.client.type.NoteState[c];
};
connected => {
c.state.connectivity ← disconnecting;
c.state.onLine ← FALSE;
c.driver ← IO.noInputStream;
BROADCAST pc.change;
c.client.type.NoteState[c !
UNWIND => {
c.state.connectivity ← disconnected;
BROADCAST pc.change;
};
];
TRUSTED {Process.Detach[
pc.changer ← FORK Disconnect[c, pc, msg, verbosely]
]};
};
disconnecting => {
Scream["Already disconnecting from %g!", [rope[c.serverName]]];
c.client.type.NoteState[c];
};
ENDCASE => ERROR;
};
Disconnect:
PROC [c: Conversant, pc: PupConversation, msg:
ROPE, verbosely:
BOOL] = {
self: UNSAFE PROCESS = Process.GetCurrent[];
{
ENABLE
{
IO.Error => IF ec = StreamClosed AND stream = c.client.toClient THEN GOTO Destroyed;
IO.EndOfStream => IF stream = c.client.toClient THEN GOTO Destroyed;
};
ok: BOOL;
serverStream: IO.STREAM = pc.serverStream;
IF verbosely
THEN {
c.client.toClient.PutF["\015\012%g ... ", [rope[msg]]];
c.client.toClient.Flush[]};
serverStream.Close[TRUE !IO.Error => CONTINUE];
ok ← SetConnectivity[c, pc, disconnected, self, NIL];
IF ok
AND verbosely
THEN {
c.client.toClient.PutRope[" ... closed\015\012"];
c.client.toClient.Flush[]};
EXITS
Destroyed => NoteDestruction[c, pc];
}};
GiveUp:
PROC [c: Conversant, verbosely:
BOOL ←
TRUE] = {
pc: PupConversation = NARROW[c.conversantData];
Really:
ENTRY
PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
InternalGiveUp[c, pc, IF verbosely THEN "... gave up!\015\012" ELSE NIL];
};
Really[pc];
};
InternalGiveUp:
INTERNAL
PROC [c: Conversant, pc: PupConversation, msg:
ROPE ←
NIL] = {
SELECT c.state.connectivity
FROM
connecting, disconnecting => {
oldChanger: PROCESS ← pc.changer;
pc.changer ← NIL;
IF c.state.connectivity = connecting THEN TRUSTED {Process.Abort[oldChanger]};
c.state.connectivity ← disconnected;
c.state.onLine ← FALSE;
c.driver ← c.client.fromClient;
BROADCAST pc.change;
c.client.type.NoteState[c];
IF msg #
NIL
THEN {
ENABLE IO.Error => IF ec = StreamClosed AND stream = c.client.toClient THEN {InternalNoteDestruction[c, pc]; CONTINUE};
c.client.toClient.PutRope[msg];
c.client.toClient.Flush[];
};
};
disconnected, connected => {
Scream["Not connecting or disconnecting!"];
c.client.type.NoteState[c];
};
ENDCASE => ERROR;
};
SetOnLine:
PROC [c: Conversant, onLine:
BOOL] = {
pc: PupConversation = NARROW[c.conversantData];
Really:
ENTRY
PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
c.state.onLine ← onLine;
c.driver ← IF NOT onLine THEN c.client.fromClient ELSE IF pc.serverStream # NIL THEN pc.serverStream ELSE IO.noInputStream;
BROADCAST pc.change;
c.client.type.NoteState[c];
};
Really[pc];
};
flushPeriod: NAT ← 50;
UserToServer:
PROC [c: Conversant, pc: PupConversation] = {
PreChar:
ENTRY
PROC [pc: PupConversation]
RETURNS [do: {proceed, exit}] = {
IF pc.phase >= destroying THEN RETURN [exit];
pc.gettingClientChar ← TRUE;
do ← proceed;
};
Stuff:
ENTRY
PROC [pc: PupConversation]
RETURNS [do: {disconnect, proceed, exit}] = {
ENABLE UNWIND => {};
pc.gettingClientChar ← FALSE;
UNTIL c.state.connectivity=connected OR (NOT c.state.onLine) OR pc.phase >= destroying DO WAIT pc.change ENDLOOP;
do ← proceed;
SELECT
TRUE
FROM
pc.phase >= destroying => do ← exit;
NOT c.state.onLine => pc.toClient.PutChar[char];
ENDCASE => {
ENABLE PupStream.StreamClosing =>
SELECT c.state.connectivity
FROM
connected => {
do ← disconnect;
whyDisconnect ← text;
CONTINUE};
disconnected, connecting, disconnecting => {
};
ENDCASE => ERROR;
pc.serverStream.PutChar[char];
IF pc.charsSentSinceFlush >= flushPeriod
OR pc.fromClient.CharsAvail[] = 0
THEN {
pc.serverStream.Flush[!PupStream.Timeout => RESUME];
pc.charsSentSinceFlush ← 0;
}
ELSE pc.charsSentSinceFlush ← pc.charsSentSinceFlush + 1;
};
};
char: CHAR;
whyDisconnect: ROPE ← NIL;
DO
ENABLE IO.EndOfStream, IO.Error, ABORTED => EXIT;
SELECT PreChar[pc]
FROM
proceed => NULL;
exit => EXIT;
ENDCASE => ERROR;
char ← pc.fromClient.GetChar[!
IO.Error => IF ec = StreamClosed AND stream = pc.fromClient THEN GOTO Destroyed;
ABORTED => RETRY
];
DO
SELECT Stuff[pc]
FROM
exit => GOTO Exit;
proceed => EXIT;
disconnect => ReallyStartDisconnect[
c,
IO.PutFR[
"Connection being closed by %s %g",
[rope[c.serverName]],
[rope[whyDisconnect]]],
TRUE
];
ENDCASE => ERROR;
ENDLOOP;
ENDLOOP;
EXITS
Exit => NULL;
Destroyed => NoteDestruction[c, pc];
};
ServerToUser:
PROC [c: Conversant, pc: PupConversation] = {
serverStream: IO.STREAM ← NIL;
serverName: ROPE ← "??";
char: CHAR;
Stuff:
ENTRY
PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
IF pc.logStream # NIL THEN pc.logStream.PutChar[char];
UNTIL (c.state.connectivity=connected AND c.state.onLine) OR pc.phase >= destroying DO WAIT pc.change ENDLOOP;
IF pc.phase >= destroying THEN RETURN;
pc.toClient.PutChar[char !
IO.Error => IF ec = StreamClosed AND stream = pc.toClient THEN {InternalNoteDestruction[c, pc]; CONTINUE};
];
serverStream ← pc.serverStream;
serverName ← c.serverName;
};
Acquire:
ENTRY
PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
UNTIL c.state.connectivity=connected OR pc.phase >= destroying DO WAIT pc.change ENDLOOP;
serverStream ← pc.serverStream;
serverName ← c.serverName;
};
NeedToClose:
ENTRY
PROC [pc: PupConversation]
RETURNS [need:
BOOL] = {
need ←
SELECT c.state.connectivity
FROM
connected => TRUE,
disconnected, disconnecting, connecting => FALSE,
ENDCASE => ERROR;
};
StopServing:
ENTRY
PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
pc.serverToUsering ← FALSE;
BROADCAST pc.change;
};
Acquire[pc];
{ENABLE UNWIND => StopServing[pc];
DO
whyDisconnect: ROPE ← NIL;
{
ENABLE {
PupStream.StreamClosing => {whyDisconnect ← text; GOTO Disc};
IO.EndOfStream, IO.Error, ABORTED => EXIT;
UNWIND => {
IF pc.logStream # NIL THEN pc.logStream.Close[!FS.Error => CONTINUE];
};
};
IF pc.phase >= destroying THEN EXIT;
IF serverStream.EndOf[]
THEN {
mySST: MarkByte;
mySST ← PupStream.ConsumeMark[serverStream];
IF pc.phase >= destroying THEN EXIT;
IF mySST = timingMark
THEN {
Reply:
ENTRY
PROC [pc: PupConversation] = {
ENABLE UNWIND => {};
PupStream.SendMark[serverStream, timingMarkReply]};
Reply[pc];
};
};
IF pc.phase >= destroying THEN EXIT;
char ← serverStream.GetChar[!
PupStream.Timeout, IO.EndOfStream => GOTO ReAcquire;
];
Stuff[pc];
EXITS
Disc => {
IF NeedToClose[pc] THEN ReallyStartDisconnect[c, IO.PutFR["Connection being closed by %s %g", [rope[serverName]], [rope[whyDisconnect]]], TRUE];
Acquire[pc];
};
ReAcquire => Acquire[pc];
}ENDLOOP;
};
StopServing[pc];
IF pc.logStream # NIL THEN pc.logStream.Close[!FS.Error => CONTINUE];
};
Scream:
PROC [fmt:
ROPE, v1, v2:
IO.Value ← [null[]]] = {
MessageWindow.Append[
message: IO.PutFR[fmt, v1, v2],
clearFirst: TRUE
];
MessageWindow.Blink[];
};
Protocols.RegProtocol[pupTelnet];
}.