-- N.Wirth June 1, 1977
-- S.Andler August 24, 1977 10:49 PM
-- C.Geschke August 31, 1977 9:36 AM
-- E.Satterthwaite September 13, 1977 3:35 PM
-- R.Johnsson September 19, 1977 8:18 AM
-- J.Sandman October 18, 1977 12:40 PM
-- E.Satterthwaite October 20, 1977 10:47 AM
DIRECTORY
InlineDefs: FROM "InlineDefs",
IODefs: FROM "IODefs",
PupDefs: FROM "PupDefs",
SchedDefs: FROM "SchedDefs",
StreamDefs: FROM "StreamDefs",
StringDefs: FROM "StringDefs",
SystemDefs: FROM "SystemDefs",
TeleSilDefs: FROM "TeleSilDefs",
TeleSilProcDefs: FROM "TeleSilProcDefs";
TeleSilPup: PROGRAM
IMPORTS
IODefs, PupDefs, SchedDefs, StreamDefs, StringDefs, SystemDefs,
TeleSilProcDefs
EXPORTS TeleSilProcDefs =
BEGIN
OPEN
TeleSilProcDefs,
-- TeleSilResident:
-- BitMapDisplay, Mark, SetCursorIcon, SetMousePosition, TypeScriptWindow
-- TeleSilDisplay: Confirm, Error
-- TeleSilIO: InputFile, OutputFile
-- TeleSilMain: InputStatus, NewPicture, OutputStatus, SetEtherOn
TeleSilDefs;
-- 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 state vector
listener: PupDefs.Listener; -- for listening to anybody
WordsPerPup: CARDINAL = 28;
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: STRING ← [80];
errorText: STRING ← buffer; -- 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 RETURN[InlineDefs.BITSHIFT[1, id]] END;
InSet: PROCEDURE[set: ConnSet, id: ConnId] RETURNS[BOOLEAN]=
BEGIN RETURN[InlineDefs.BITAND[set, Set[id]]#emptySet] END;
SetUnion: PROCEDURE[s1, s2: ConnSet] RETURNS[ConnSet]=
BEGIN RETURN[InlineDefs.BITOR[s1, s2]] END;
SetDiff: PROCEDURE[s1, s2: ConnSet] RETURNS[ConnSet]=
BEGIN RETURN[InlineDefs.BITAND[s1, InlineDefs.BITNOT[s2]]] END;
--------------------------------------------------------------------
-- STRINGS
--------------------------------------------------------------------
GetString: PROCEDURE[bs: PupDefs.ByteStream, to: STRING]=
BEGIN
i: [0..80);
to.length ← PupDefs.ByteStreamGet[bs];
-- puppackage should be fixed:
FOR i IN [0..to.length) DO to[i] ← PupDefs.ByteStreamGetChar[bs] ENDLOOP;
--FOR i IN [0..to.length) DO to[i] ← LOOPHOLE[PupDefs.ByteStreamGet[bs]] ENDLOOP;
IF InlineDefs.BITAND[to.length,1]=0 THEN [] ← PupDefs.ByteStreamGet[bs]
END;
PutString: PROCEDURE[bs: PupDefs.ByteStream, from: STRING]=
BEGIN
i: [0..80);
PupDefs.ByteStreamPut[bs, from.length];
FOR i IN [0..from.length) DO PupDefs.ByteStreamPutChar[bs, from[i]] ENDLOOP;
IF InlineDefs.BITAND[from.length,1]=0 THEN PupDefs.ByteStreamPut[bs, 0]
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 SchedDefs.ScheduleeYields ENDLOOP;
PupDefs.ByteStreamPutMark[pConn.bs, BOF]; -- 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 =>
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;
ENDCASE => BEGIN SelectiveSendCmd[pCmd, allOthers]; RETURN[pCmd] END;
FreeCmd[pCmd];
SchedDefs.ScheduleeYields -- 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.GetPupVector[];
pupVector.dataWordsPerPup ← WordsPerPup;
[] ← PupDefs.PupPackageReset[];
StreamDefs.SetIdleProc[SchedDefs.ScheduleeYields]; -- Idle process for keyboard input
IF action=connect THEN
WHILE CheckAddress[] DO
OpenConnection;
IF setConn = emptySet THEN ReadAddress ELSE EXIT
ENDLOOP;
IODefs.WriteLine["Making Listener"];
listener ← PupDefs.ListenerMakeByteStreams[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"];
PupDefs.ListenerDestroy[listener];
IODefs.WriteLine["Destroying Pup Package"];
StreamDefs.ResetIdleProc; -- Reset idle process for keyboard input
PupDefs.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 IODefs; -- Write..., Read...
DO -- until a host hostName has been parsed or no name given
IF hostName.length=0 THEN RETURN[FALSE];
IF PupDefs.GetAddress[@sendHim, hostName, errorText] THEN
BEGIN -- hostName parsed correctly
WriteString["Address parsed: "];
PupDefs.PrintAddress[@sendHim]; WriteChar[IODefs.CR];
RETURN[TRUE]
END;
Error[errorText];
ReadAddress
ENDLOOP
END;
OpenConnection: PROCEDURE=
BEGIN OPEN IODefs; -- Write..., Read...
bs: PupDefs.ByteStream;
WriteLine["Opening connection"];
bs ← PupDefs.ByteStreamMake[@sendConn];
BEGIN ENABLE PupDefs.StreamClosing => GOTO streamClosing;
PupDefs.ByteStreamOpen[bs];
WriteString["Connected to "];
PupDefs.PrintAddress[@bs.myPacketStream.remote];
WriteChar[CR];
-- Exchange names
PutString[bs,myName];
PupDefs.ByteStreamSendNow[bs];
GetString[bs,buffer];
WriteString["Connection accepted by "]; WriteString[buffer]; WriteChar[CR];
EtherInput[bs];
CreateIOProcesses[IdToConn[MakeConn[bs]]]
EXITS
streamClosing =>
BEGIN
PupDefs.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[unused: UNSPECIFIED, bs: PupDefs.ByteStream, remAddr: PupDefs.PAddress]=
BEGIN
IF setConn=fullSet THEN ERROR
ELSE
BEGIN ENABLE PupDefs.StreamClosing => GOTO streamClosing;
GetString[bs,buffer]; -- Get name
EtherReport[connecting, MakeConn[bs]]
EXITS
streamClosing => PupDefs.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↑ ← [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;
SchedDefs.ScheduleeCreate[InputServer, pConn];
SchedDefs.ScheduleeCreate[OutputServer, pConn];
END;
DestroyConn: PROCEDURE[pConn: ConnPtr]=
BEGIN
previousConn: ConnPtr;
WHILE pConn.output#finished OR pConn.input#finished DO SchedDefs.ScheduleeYields 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;
PupDefs.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 PupDefs.StreamClosing => GOTO streamClosing;
WriteString["Connection from "]; WriteString[pConn.name]; WriteString[" - "];
PupDefs.PrintAddress[@pConn.bs.myPacketStream.remote];
IF NOT Confirm[] THEN GOTO streamClosing;
-- Send name
PutString[pConn.bs,myName];
PupDefs.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 ← PupDefs.ByteStreamGet[bs];
bytePair.right ← PupDefs.ByteStreamGet[bs];
RETURN[LOOPHOLE[bytePair]]
END;
GetBlock: PROCEDURE[address: POINTER, words: INTEGER]=
BEGIN
mark: PupDefs.Byte;
finger: PupDefs.BlockFinger ← DESCRIPTOR[address, words];
PupDefs.ByteStreamGetWholeBlock[bs, @finger
!PupDefs.MarkInStream =>
BEGIN
-- A mark=EOF in the stream means end of file.
mark ← PupDefs.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] ← LOOPHOLE[word];
PupDefs.ByteStreamPut[bs, bytePair.left];
PupDefs.ByteStreamPut[bs, bytePair.right]
END;
PutBlock: PROCEDURE[address: POINTER, words: INTEGER]=
BEGIN
finger: PupDefs.BlockFinger ← DESCRIPTOR[address, words];
PupDefs.ByteStreamPutBlock[bs, @finger]
END;
WriteLine["Sending picture"];
OutputStatus[PutWord];
OutputFile[PutWord, PutBlock];
PupDefs.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]];
PupDefs.ByteStreamPutBlock[pConn.bs, @finger -- potential Yield
!PupDefs.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
PupDefs.ByteStreamSendNow[pConn.bs
!PupDefs.StreamClosing =>
BEGIN pConn.output ← closed; CONTINUE END]
END;
IF pConn.output#active THEN EXIT; -- no more messages for this connection
SchedDefs.ScheduleeYields
ENDLOOP;
IF pConn.output=closing THEN PupDefs.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
PupDefs.ByteStreamGetWholeBlock[pConn.bs, @finger -- potential Yield
!PupDefs.MarkInStream =>
BEGIN -- A mark=BOF in the stream means beginning of file.
mark ← PupDefs.ByteStreamGet[pConn.bs];
IF mark=BOF THEN
BEGIN pConn.input ← holding; cmd ← [ether[input]]; CONTINUE END
END;
PupDefs.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 SchedDefs.ScheduleeYields 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.