-- N.Wirth June 1, 1977
-- S.Andler August 24, 1977 10:49 PM
-- C.Geschke August 22, 1977 5:11 PM
DIRECTORY
InlineDefs: FROM "InlineDefs",
IODefs: FROM "IODefs",
PupDefs: FROM "PupDefs",
StringDefs: FROM "StringDefs",
SystemDefs: FROM "SystemDefs",
TeleSilDefs: FROM "TeleSilDefs";

DEFINITIONS FROM TeleSilDefs;

TeleSilPup: PROGRAM=
BEGIN
-- External Procedures --
-- From KeyStreams --
SetIdleProc: EXTERNAL PROCEDURE[p: PROCEDURE];
ResetIdleProc: EXTERNAL PROCEDURE;
-- From TeleSilResident --
BitMapDisplay: EXTERNAL PROCEDURE;
Mark: EXTERNAL PROCEDURE[point: Coord, icon: CursorIcon];
SetCursorIcon: EXTERNAL PROCEDURE[icon: CursorIcon] RETURNS[oldIcon: CursorIcon];
SetMousePosition: EXTERNAL PROCEDURE[point: Coord];
TypeScriptWindow: EXTERNAL PROCEDURE;
-- From TeleSilDisplay --
Confirm: EXTERNAL PROCEDURE RETURNS[BOOLEAN];
Error: EXTERNAL PROCEDURE[s: STRING];
-- From TeleSilIO --
InputFile: EXTERNAL PROCEDURE[
EndOfFile: PROCEDURE RETURNS[BOOLEAN],
GetWord: PROCEDURE RETURNS[WORD],
GetBlock: PROCEDURE[address: POINTER, words: INTEGER]];
OutputFile: EXTERNAL PROCEDURE[
PutWord: PROCEDURE[word: WORD],
PutBlock: PROCEDURE[address: POINTER, words: INTEGER]];
-- From TeleSilMain --
InputStatus: EXTERNAL PROCEDURE[GetWord: PROCEDURE RETURNS[WORD]];
NewPicture: EXTERNAL PROCEDURE;
OutputStatus: EXTERNAL PROCEDURE[ PutWord: PROCEDURE[word: WORD]];
SetEtherOn: EXTERNAL PROCEDURE[newValue: BOOLEAN];

-- Constants --
BOF: PupDefs.Byte= 0; -- Mark byte to indicate beginning of file
EOF: PupDefs.Byte= 1; -- Mark byte to indicate end of file

-- Types --
Connection: TYPE= RECORD[
next: ConnPtr,
id: ConnId,
output: {active, closing, closed, finished},
input: {active, holding, closed, finished},
closeReason: PupDefs.CloseReason,
outputWaiting: BOOLEAN,
bs: PupDefs.ByteStream,
name: STRING];
ConnPtr: TYPE= POINTER TO Connection;
ConnSet: TYPE= WORD; -- Simulation of SET
InQueueEl: TYPE= RECORD[
next: InQueuePtr,
originator: ConnId,
pCmd: CmdPtr];
OutQueueEl: TYPE= RECORD[
next: OutQueuePtr,
recipients: ConnSet,
pCmd: CmdPtr];
InQueuePtr: TYPE= POINTER TO InQueueEl;
OutQueuePtr: TYPE= POINTER TO OutQueueEl;

-- Variables --
pupVector: PupDefs.PupInterface ← NIL; --pointer to giant dispatch vector
listenBs: PupDefs.ByteStream; -- for listening to anybody

listenSocket: PupDefs.Pair ← PupDefs.Pair[0,6];
sendHim: PupDefs.Address ← [PupDefs.ThisNet, PupDefs.ThisHost, listenSocket];
-- listenSocket is the agreed upon listening socket
sendMe: PupDefs.Address ← [PupDefs.ThisNet, PupDefs.ThisHost, PupDefs.Pair[0,0]];
-- Socket Pair[0,0] gives unique socket

sendConn: PupDefs.ConnObject ← [@sendMe, @sendHim];

firstConn: ConnPtr ← NIL;
setConn: ConnSet ← emptySet;
firstIn, lastIn: InQueuePtr ← NIL; -- List of CmdObj:s coming in
firstOut, lastOut: OutQueuePtr ← NIL; -- List of CmdObj:s to be sent out
buffer, errorText: STRING ← [80]; -- sharing the same space!
hostName, myName: STRING ← NIL;

controlState: {passive, requested, granted} ← granted;

--------------------------------------------------------------------
-- SETS
--------------------------------------------------------------------
emptySet: WORD= 0; -- Simulation of the empty set
fullSet: WORD= 177777B; -- Simulation of the full set

Set: PROCEDURE[id: ConnId] RETURNS[ConnSet]=
BEGIN OPEN InlineDefs; --
BITSHIFT
RETURN[BITSHIFT[1, id]]
END;

InSet: PROCEDURE[set: ConnSet, id: ConnId] RETURNS[BOOLEAN]=
BEGIN OPEN InlineDefs; --
BITAND
RETURN[BITAND[set, Set[id]]#emptySet]
END;

SetUnion: PROCEDURE[s1, s2: ConnSet] RETURNS[ConnSet]=
BEGIN OPEN InlineDefs; --
BITOR
RETURN[BITOR[s1, s2]]
END;

SetDiff: PROCEDURE[s1, s2: ConnSet] RETURNS[ConnSet]=
BEGIN OPEN InlineDefs; --
BITAND, BITNOT
RETURN[BITAND[s1, BITNOT[s2]]]
END;

--------------------------------------------------------------------
-- STRINGS
--------------------------------------------------------------------
GetString: PROCEDURE[bs: PupDefs.ByteStream, to: STRING]=
BEGIN i: [0..80);
to.length ← pupVector.ByteStreamGet[bs];
FOR i IN [0..to.length) DO
to[i] ← pupVector.ByteStreamGetChar[bs]
ENDLOOP;
IF InlineDefs.BITAND[to.length,1]=0 THEN [] ← pupVector.ByteStreamGet[bs]
END;

PutString: PROCEDURE[bs: PupDefs.ByteStream, from: STRING]=
BEGIN i: [0..80);
pupVector.ByteStreamPut[bs, from.length];
FOR i IN [0..from.length) DO
pupVector.ByteStreamPutChar[bs, from[i]]
ENDLOOP;
IF InlineDefs.BITAND[from.length,1]=0 THEN pupVector.ByteStreamPut[bs, 0]
END;
--------------------------------------------------------------------

Yield: PUBLIC PROCEDURE= BEGIN pupVector.ScheduleeYields END;

SendCmd: PUBLIC PROCEDURE[pCmd: CmdPtr]=
BEGIN SelectiveSendCmd[pCmd, setConn] END;

SelectiveSendCmd: PROCEDURE[pCmd: CmdPtr, recipients: ConnSet]=
BEGIN -- This procedure copies the command if it has recipients
p: OutQueuePtr; pConn: ConnPtr;
CursorCmdPtr: TYPE = POINTER TO cursor CmdObj;
FOR pConn ← firstConn, pConn.next UNTIL pConn=NIL DO
IF InSet[recipients, pConn.id] THEN
IF pConn.output=active THEN pConn.outputWaiting ← TRUE
ELSE recipients ← SetDiff[recipients, Set[pConn.id]]
ENDLOOP;
IF recipients#emptySet THEN
IF firstOut#NIL AND pCmd.cmdType=cursor AND lastOut.pCmd.cmdType=cursor THEN BEGIN
lastOut.recipients ← recipients;
LOOPHOLE[lastOut.pCmd, CursorCmdPtr].point ← LOOPHOLE[pCmd, CursorCmdPtr].point
END ELSE BEGIN
p ← SystemDefs.AllocateHeapNode[SIZE[OutQueueEl]];
p↑ ← OutQueueEl[NIL, recipients, CopyCmd[pCmd]];
IF firstOut=NIL THEN firstOut ← p ELSE lastOut.next ← p;
lastOut ← p
END
END;

Refresh: PUBLIC PROCEDURE=
BEGIN SelectiveRefresh[setConn] END;

SelectiveRefresh: PROCEDURE[recipients: ConnSet]=
BEGIN OPEN IODefs; --
Write...
pConn: ConnPtr;
FOR pConn ← firstConn, pConn.next UNTIL pConn=NIL DO
IF InSet[recipients, pConn.id] AND pConn.output=active THEN
BEGIN
WriteString["Refreshing "]; WriteLine[pConn.name];
WriteLine["Waiting for output process to finish"];
WHILE pConn.outputWaiting DO Yield ENDLOOP;
pupVector.ByteStreamPutMark[pConn.bs, BOF]; -- indicate beginning of file
EtherOutput[pConn.bs] -- send refresh
END;
ENDLOOP
END;

RequestControl: PUBLIC PROCEDURE RETURNS[BOOLEAN]=
BEGIN cmd: CmdObj;
IF controlState=granted THEN RETURN[TRUE];
IF controlState=requested THEN RETURN[FALSE];
controlState ← requested;
cmd ← [control[request, [pupVector.myNetwork, pupVector.myHost]]];
SendCmd[@cmd];
RETURN[FALSE]
END;

CmdWaiting: PUBLIC PROCEDURE RETURNS[BOOLEAN]=
BEGIN RETURN[firstIn#NIL] END;

NextCmd: PUBLIC PROCEDURE RETURNS[CmdPtr]=
BEGIN
tmp: InQueuePtr; pCmd: CmdPtr; originator: ConnId; thisGuy, allOthers: ConnSet;
DO
IF firstIn=NIL THEN RETURN[NIL];
pCmd ← firstIn.pCmd; tmp ← firstIn.next;
originator ← firstIn.originator;
thisGuy ← Set[originator];
allOthers ← SetDiff[setConn, thisGuy];
SystemDefs.FreeHeapNode[firstIn];
firstIn ← tmp;
WITH pCmd SELECT FROM
ether => EtherCmd[etherCmd, originator];
cursor => BEGIN SetMousePosition[point]; SelectiveSendCmd[pCmd, allOthers] END;
mark => BEGIN Mark[point, icon]; SelectiveSendCmd[pCmd, allOthers] END;
setCursorIcon => BEGIN [] ← SetCursorIcon[icon]; SelectiveSendCmd[pCmd, allOthers] END;
control =>
BEGIN
SELECT action FROM
request =>
SELECT controlState FROM
passive => SelectiveSendCmd[pCmd, allOthers];
requested =>
BEGIN action ← reject;
SelectiveSendCmd[pCmd, thisGuy]
END;
granted =>
BEGIN controlState ← passive; action ← grant;
SelectiveSendCmd[pCmd, thisGuy]
END;
ENDCASE;
grant =>
IF address=[pupVector.myNetwork, pupVector.myHost]
OR address=[-1,0] THEN controlState ← granted
ELSE SelectiveSendCmd[pCmd, allOthers];
reject =>
IF address=[pupVector.myNetwork, pupVector.myHost] THEN
BEGIN IF controlState=requested THEN controlState ← passive END
ELSE SelectiveSendCmd[pCmd, allOthers];
ENDCASE;
END;
ENDCASE => BEGIN SelectiveSendCmd[pCmd, allOthers]; RETURN[pCmd] END;
FreeCmd[pCmd];
Yield -- let Pup Package do its thing
ENDLOOP
END;

AllocateCmd: PUBLIC PROCEDURE RETURNS[CmdPtr]=
BEGIN RETURN[SystemDefs.AllocateHeapNode[SIZE[CmdObj]]] END;

FreeCmd: PUBLIC PROCEDURE[pCmd: CmdPtr]=
BEGIN SystemDefs.FreeHeapNode[pCmd];
WITH pCmd SELECT FROM text => SystemDefs.FreeHeapString[s]; ENDCASE;
END;

CopyCmd: PROCEDURE[oldCmd: CmdPtr] RETURNS[newCmd: CmdPtr]=
BEGIN newCmd ← AllocateCmd[]; newCmd↑ ← oldCmd↑ END;

Connect: PUBLIC PROCEDURE=
BEGIN ReadAddress; ConnectAndListen[connect];
IF setConn#emptySet THEN controlState ← passive
END;

Listen: PUBLIC PROCEDURE=
BEGIN ConnectAndListen[listen] END;

ConnectAndListen: PROCEDURE[action: {connect, listen}]=
BEGIN
IF myName=NIL THEN
BEGIN
IODefs.WriteString["Your name: "];
buffer.length ← 0;
IODefs.ReadID[buffer]; IODefs.WriteChar[IODefs.CR];
myName ← SystemDefs.AllocateHeapString[buffer.length];
StringDefs.AppendString[myName,buffer];
END;
IODefs.WriteLine["Making Pup Package"];
PupDefs.SetSandBarOK[sandBarOK];
pupVector ← PupDefs.PupPackageMake[];
SetIdleProc[pupVector.ScheduleeYields]; -- Idle process for keyboard input
IF action=connect THEN WHILE CheckAddress[] DO
OpenConnection;
IF setConn#emptySet THEN EXIT
ELSE ReadAddress
ENDLOOP;
IODefs.WriteLine["Making Listener"];
listenBs ← pupVector.ByteStreamMakeListener[listenSocket, Notify, NIL];
SetEtherOn[TRUE]
END;

Disconnect: PUBLIC PROCEDURE=
BEGIN cmd: CmdObj;
IF setConn#emptySet THEN
BEGIN
IF controlState = granted THEN
BEGIN cmd ← [control[grant, [-1,0]]];
SendCmd[@cmd]
END;
CloseConnections
END;
IODefs.WriteLine["Destroying Listener"];
pupVector.ByteStreamDestroy[listenBs];
IODefs.WriteLine["Destroying Pup Package"];
ResetIdleProc; -- Reset idle process for keyboard input
pupVector.PupPackageDestroy;
controlState ← granted;
SetEtherOn[FALSE]
END;

ReadAddress: PROCEDURE=
BEGIN OPEN IODefs; --
Write..., Read...
WriteString["Connect to host: "];
buffer.length ← 0;
IF hostName#NIL THEN
BEGIN
StringDefs.AppendString[buffer,hostName];
SystemDefs.FreeHeapString[hostName]
END;
IODefs.ReadID[buffer]; WriteChar[IODefs.CR];
hostName ← SystemDefs.AllocateHeapString[buffer.length];
StringDefs.AppendString[hostName,buffer]
END;

CheckAddress: PROCEDURE RETURNS[BOOLEAN]=
BEGIN
OPEN pupVector, --
GetAddress, PrintAddress
IODefs; --
Write..., Read...
DO -- until a host hostName has been parsed or no name given
IF hostName.length=0 THEN RETURN[FALSE];
IF GetAddress[@sendHim, hostName, errorText]
THEN BEGIN -- hostName parsed correctly
WriteString["Address parsed: "]; PrintAddress[@sendHim]; WriteChar[IODefs.CR];
RETURN[TRUE]
END;
Error[errorText];
ReadAddress
ENDLOOP
END;

OpenConnection: PROCEDURE=
BEGIN
OPEN pupVector, --
ByteStream...
IODefs; --
Write..., Read...
bs: PupDefs.ByteStream;
WriteLine["Opening connection"];
bs ← ByteStreamMake[@sendConn];
BEGIN ENABLE StreamClosing => GOTO streamClosing;
ByteStreamOpen[bs];
WriteString["Connected to "]; PrintAddress[@bs.myPacketStream.remote]; WriteChar[IODefs.CR];
-- Exchange names
PutString[bs,myName];
ByteStreamSendNow[bs];
GetString[bs,buffer];
WriteString["Connection accepted by "]; WriteString[buffer]; WriteChar[IODefs.CR];
EtherInput[bs];
CreateIOProcesses[IdToConn[MakeConn[bs]]]
EXITS
streamClosing =>
BEGIN
ByteStreamDestroy[bs];
Error["Remote end not responding"]
END
END
END;

CloseConnections: PROCEDURE=
BEGIN -- This routine finishes output. The connections will be closed
-- by OutputServer and destroyed by InputServer
pConn: ConnPtr;
IODefs.WriteLine["Closing connections"];
FOR pConn ← firstConn, pConn.next UNTIL pConn=NIL DO
IF pConn.output=active THEN pConn.output ← closing
ENDLOOP;
UNTIL firstConn=NIL DO DestroyConn[firstConn] ENDLOOP
END;

Notify: PROCEDURE[UNSPECIFIED, bs: PupDefs.ByteStream, remAddr: PupDefs.PAddress]=
BEGIN OPEN pupVector; --
ByteStream...
IF setConn=fullSet THEN ERROR
ELSE BEGIN ENABLE StreamClosing => GOTO streamClosing;
-- Get name
GetString[bs,buffer];
EtherReport[connecting, MakeConn[bs]]
EXITS
streamClosing => ByteStreamDestroy[bs]
END
END;

MakeConn: PROCEDURE[bs: PupDefs.ByteStream] RETURNS[ConnId]=
BEGIN
pConn: ConnPtr ← SystemDefs.AllocateHeapNode[SIZE[Connection]];
id: ConnId;
FOR id IN ConnId UNTIL NOT InSet[setConn, id] DO ENDLOOP;
setConn ← SetUnion[setConn, Set[id]];
pConn↑ ← Connection[next: firstConn, id: id, output: finished, input: finished,
closeReason:, outputWaiting: FALSE, bs: bs, name:];
pConn.name ← SystemDefs.AllocateHeapString[buffer.length];
StringDefs.AppendString[pConn.name,buffer];
firstConn ← pConn;
RETURN[id]
END;

CreateIOProcesses: PROCEDURE[pConn: ConnPtr]=
BEGIN
pConn.input ← active; pConn.output ← active;
pupVector.ScheduleeCreate[InputServer, pConn];
pupVector.ScheduleeCreate[OutputServer, pConn];
END;

DestroyConn: PROCEDURE[pConn: ConnPtr]=
BEGIN previousConn: ConnPtr;
WHILE pConn.output#finished OR pConn.input#finished DO Yield ENDLOOP;
IF pConn=firstConn THEN firstConn ← pConn.next
ELSE FOR previousConn ← firstConn, previousConn.next DO
IF previousConn.next=pConn THEN
BEGIN previousConn.next ← pConn.next; EXIT END
ENDLOOP;
pupVector.ByteStreamDestroy[pConn.bs];
SystemDefs.FreeHeapString[pConn.name];
setConn ← SetDiff[setConn, Set[pConn.id]];
SystemDefs.FreeHeapNode[pConn]
END;

EtherCmd: PROCEDURE[etherCmd: EtherCommand, sender: ConnId]=
BEGIN OPEN IODefs; --
Write...
pConn: ConnPtr ← IdToConn[sender];
TypeScriptWindow;
SELECT etherCmd FROM
input =>
BEGIN
EtherInput[pConn.bs]; -- sender is sending picture
pConn.input ← active;
SelectiveRefresh[SetDiff[setConn, Set[sender]]]
END;
closing =>
BEGIN
WriteString["Disconnected from "]; WriteString[pConn.name]; WriteString[" ... "];
SELECT pConn.closeReason FROM
remoteClose => WriteString["remoteClose"];
transmissionTimeout => WriteString["transmissionTimeout"];
noRouteToNetwork => WriteString["noRouteToNetwork"];
ENDCASE => ERROR;
DestroyConn[pConn];
IF controlState#granted THEN Disconnect
END;
connecting =>
BEGIN ENABLE pupVector.StreamClosing => GOTO streamClosing;
WriteString["Connection from "]; WriteString[pConn.name]; WriteString[" - "];
pupVector.PrintAddress[@pConn.bs.myPacketStream.remote];
IF NOT Confirm[] THEN GOTO streamClosing;
-- Send name
PutString[pConn.bs,myName];
pupVector.ByteStreamSendNow[pConn.bs];
-- Send picture
EtherOutput[pConn.bs];
CreateIOProcesses[pConn]
EXITS
streamClosing => DestroyConn[pConn]
END;
ENDCASE;
BitMapDisplay
END;

IdToConn: PROCEDURE[id: ConnId] RETURNS[pConn: ConnPtr]=
BEGIN
FOR pConn ← firstConn, pConn.next DO
IF pConn=NIL THEN ERROR;
IF pConn.id=id THEN EXIT
ENDLOOP
END;

EtherInput: PROCEDURE[bs: PupDefs.ByteStream]=
BEGIN OPEN IODefs;
eof: BOOLEAN ← FALSE;

EndOfFile: PROCEDURE RETURNS[BOOLEAN]= BEGIN RETURN[eof] END;
GetWord: PROCEDURE RETURNS[WORD]=
BEGIN bytePair: RECORD[left, right: PupDefs.Byte];
bytePair.left ← pupVector.ByteStreamGet[bs];
bytePair.right ← pupVector.ByteStreamGet[bs];
RETURN[LOOPHOLE[bytePair]]
END;
GetBlock: PROCEDURE[address: POINTER, words: INTEGER]=
BEGIN mark: PupDefs.Byte;
finger: PupDefs.BlockFinger ← DESCRIPTOR[address, words];
pupVector.ByteStreamGetWholeBlock[bs, @finger
! pupVector.MarkInStream =>
BEGIN
-- A mark=EOF in the stream means end of file.
mark ← pupVector.ByteStreamGet[bs];
IF mark=EOF THEN BEGIN eof ← TRUE; CONTINUE END
END]
END;

WriteLine["Receiving picture"];
NewPicture;
InputStatus[GetWord];
InputFile[EndOfFile, GetWord, GetBlock];
WriteLine["Finished receiving picture"]
END;

EtherOutput: PROCEDURE[bs: PupDefs.ByteStream]=
BEGIN OPEN IODefs;

PutWord: PROCEDURE[word: WORD]=
BEGIN bytePair: RECORD[left, right: PupDefs.Byte];
bytePair ← LOOPHOLE[word];
pupVector.ByteStreamPut[bs, bytePair.left];
pupVector.ByteStreamPut[bs, bytePair.right]
END;
PutBlock: PROCEDURE[address: POINTER, words: INTEGER]=
BEGIN
finger: PupDefs.BlockFinger ← DESCRIPTOR[address, words];
pupVector.ByteStreamPutBlock[bs, @finger]
END;

WriteLine["Sending picture"];
OutputStatus[PutWord];
OutputFile[PutWord, PutBlock];
pupVector.ByteStreamPutMark[bs, EOF]; -- indicate end of file
WriteLine["Finished sending picture"];
END;

OutputServer: PROCEDURE[pConn: ConnPtr]=
BEGIN
p:OutQueuePtr; finger: PupDefs.BlockFinger;
DO
IF pConn.outputWaiting THEN
BEGIN
DO -- delete messages that have no recipients left
p ← firstOut;
IF p.recipients#emptySet THEN EXIT;
firstOut ← p.next;
FreeCmd[p.pCmd];
SystemDefs.FreeHeapNode[p]
ENDLOOP;
DO -- send messages intended for this connection
IF InSet[p.recipients, pConn.id] THEN
BEGIN
IF pConn.output=active OR pConn.output=closing THEN
BEGIN
finger ← DESCRIPTOR[p.pCmd, SIZE[CmdObj]];
pupVector.ByteStreamPutBlock[pConn.bs, @finger -- potential Yield
!pupVector.StreamClosing =>
BEGIN pConn.output ← closed; CONTINUE END];
WITH p.pCmd SELECT FROM text => PutString[pConn.bs, s]; ENDCASE;
END;
p.recipients ← SetDiff[p.recipients, Set[pConn.id]]
END;
p ← p.next; IF p=NIL THEN EXIT
ENDLOOP;
pConn.outputWaiting ← FALSE;
IF pConn.output=active OR pConn.output=closing THEN
pupVector.ByteStreamSendNow[pConn.bs
!pupVector.StreamClosing =>
BEGIN pConn.output ← closed; CONTINUE END]
END;
IF pConn.output#active THEN EXIT; -- no more messages for this connection
Yield
ENDLOOP;
IF pConn.output=closing THEN pupVector.ByteStreamClose[pConn.bs];
pConn.output ← finished
END;

InputServer: PROCEDURE[pConn: ConnPtr]=
BEGIN
cmd: CmdObj; mark: PupDefs.Byte;
finger: PupDefs.BlockFinger ← DESCRIPTOR[@cmd, SIZE[CmdObj]];
DO
pupVector.ByteStreamGetWholeBlock[pConn.bs, @finger -- potential Yield
!pupVector.MarkInStream =>
BEGIN
-- A mark=BOF in the stream means beginning of file.
mark ← pupVector.ByteStreamGet[pConn.bs];
IF mark=BOF THEN BEGIN
pConn.input ← holding;
cmd ← [ether[input]];
CONTINUE
END
END;
pupVector.StreamClosing => BEGIN pConn.closeReason ← why; GOTO closing END];
WITH cmd SELECT FROM text =>
BEGIN GetString[pConn.bs, buffer];
s ← SystemDefs.AllocateHeapString[buffer.length];
StringDefs.AppendString[s, buffer]
END;
ENDCASE;
QueueCmd[@cmd, pConn.id];
WHILE pConn.input=holding DO Yield ENDLOOP; -- wait
REPEAT
closing =>
BEGIN
pConn.input ← closed;
IF pConn.output#finished THEN pConn.output ← closed;
IF pConn.closeReason#localClose THEN EtherReport[closing, pConn.id]
END
ENDLOOP;
pConn.input ← finished
END;

QueueCmd: PROCEDURE[pCmd: CmdPtr, id: ConnId]=
BEGIN
p: InQueuePtr;
CursorCmdPtr: TYPE = POINTER TO cursor CmdObj;
IF firstIn#NIL AND pCmd.cmdType=cursor AND lastIn.pCmd.cmdType=cursor THEN BEGIN
lastIn.originator ← id;
LOOPHOLE[lastIn.pCmd, CursorCmdPtr].point ← LOOPHOLE[pCmd, CursorCmdPtr].point
END ELSE BEGIN
p ← SystemDefs.AllocateHeapNode[SIZE[InQueueEl]];
p↑ ← InQueueEl[NIL, id, CopyCmd[pCmd]];
IF firstIn=NIL THEN firstIn ← p ELSE lastIn.next ← p;
lastIn ← p
END
END;

EtherReport: PROCEDURE[etherCmd: EtherCommand, id: ConnId]=
BEGIN
cmd: CmdObj ← [ether[etherCmd]];
QueueCmd[@cmd, id]
END;
END.