Tool Creation
toolName: ROPE ~ "XNSEchoTool";
RunningType: TYPE ~ { none, pokeOnce, echo, stats };
ControlHandle: TYPE ~ REF ControlObject;
ControlObject:
TYPE ~
RECORD [
running: RunningType ← none,
stop: ButtonProc ← Stop,
pokeOnce: ButtonProc ← PokeOnce,
echo: ButtonProc ← Echo,
stats: ButtonProc ← Stats,
timeoutMsecs: XNSSocket.Milliseconds ← 5000,
packetWords: CARDINAL ← 100,
thisMachine: ROPE,
remoteMachine: ROPE];
DataHandle: TYPE ~ REF DataObject;
DataObject:
TYPE ~
RECORD [
runningCount: INT ← 0,
pleaseStop: BOOL ← FALSE,
sH: XNSSocket.Handle ← NIL,
remoteAddress: XNS.Address ← XNS.unknownAddress,
sent: LONG CARDINAL ← 0,
recvd: ARRAY PokeResult OF LONG CARDINAL ← ALL[0],
delay: ARRAY PokeResult OF LONG CARDINAL ← ALL[0]
];
PokeResult: TYPE ~ { good, dropped, bad };
Create:
ENTRY
PROC
RETURNS [created:
BOOL] ~ {
cH: ControlHandle;
dH: DataHandle;
cH ← NEW[ControlObject ← [thisMachine~XNSAddressParsing.MyRope[]]];
dH ← NEW[DataObject ← [sH~XNSSocket.Create[]]];
[] ← GenericTool.CreateInstance[
toolName~toolName,
control~cH,
options~
LIST[
["thisMachine", readonly[]],
["timeoutMsecs", notify[GetGetTimeout]]
],
data~dH,
preDestroy~PreDestroy
];
RETURN [TRUE];
};
InitCond:
UNSAFE
PROC [cP: Process.ConditionPointer] ~
UNCHECKED {
Process.EnableAborts[cP];
Process.SetTimeout[cP, Process.SecondsToTicks[1]];
};
Enter:
ENTRY
PROC [tH: ToolHandle, me: RunningType, shared:
BOOL ←
FALSE]
RETURNS [entered:
BOOL] ~ {
dH: DataHandle ~ NARROW[tH.data];
cH: ControlHandle ~ NARROW[tH.control];
IF (cH.running = none)
OR (shared
AND (cH.running = me))
THEN {
cH.running ← me;
dH.runningCount ← dH.runningCount + 1;
RETURN [TRUE] };
GenericTool.PutMsgRope[tH, "Tool is busy", TRUE];
RETURN [FALSE];
};
Exit:
ENTRY PROC [tH: ToolHandle] ~ {
cH: ControlHandle ~ NARROW[tH.control];
dH: DataHandle ~ NARROW[tH.data];
IF (dH.runningCount ← dH.runningCount - 1) = 0 THEN cH.running ← none;
};
Stop: ButtonProc ~ {
cH: ControlHandle ~ NARROW[tH.control];
dH: DataHandle ~ NARROW[tH.data];
WHILE cH.running # none
DO
dH.pleaseStop ← TRUE;
Process.Pause[ Process.SecondsToTicks[1] ];
ENDLOOP;
dH.pleaseStop ← FALSE;
};
PreDestroy: ButtonProc ~ {
dH: DataHandle ~ NARROW[tH.data];
Stop[tH];
XNSSocket.Destroy[dH.sH];
};
GetRemoteAddress:
PROC [tH: ToolHandle]
RETURNS [valid:
BOOL ←
FALSE, address:
XNS.Address] ~ {
cH: ControlHandle ~ NARROW[tH.control];
IF Rope.Length[cH.remoteMachine] = 0 THEN RETURN;
BEGIN
ENABLE XNSAddressParsing.Error => {
GenericTool.PutMsgRope[tH, text, TRUE]; CONTINUE };
address ← XNSAddressParsing.AddressFromRope[cH.remoteMachine];
address.socket ← XNSWKS.echo;
valid ← TRUE;
END;
};
GetPacketWords:
PROC [tH: ToolHandle]
RETURNS [words:
CARDINAL] ~ {
cH: ControlHandle ~ NARROW[tH.control];
SELECT (words ← cH.packetWords)
FROM
< 1 => {
GenericTool.PutMsgRope[tH, "packet size too small", TRUE];
words ← 1 };
> XNSEchoBuf.maxBodyHWords => {
GenericTool.PutMsgRope[tH, "packet size too large", TRUE];
words ← XNSEchoBuf.maxBodyHWords };
ENDCASE => NULL;
};
GetGetTimeout: ButtonProc ~ {
dH: DataHandle ~ NARROW[tH.data];
cH: ControlHandle ~ NARROW[tH.control];
temp: XNSSocket.Milliseconds ← cH.timeoutMsecs;
temp ← cH.timeoutMsecs;
IF (temp ← cH.timeoutMsecs) < 0 THEN temp ← XNSSocket.dontWait;
XNSSocket.SetGetTimeout[dH.sH, temp];
};
Stats: ButtonProc ~ {
dH: DataHandle ~ NARROW[tH.data];
cH: ControlHandle ~ NARROW[tH.control];
IF NOT Enter[tH, stats] THEN RETURN;
{
ENABLE
IO.Error,
ABORTED =>
CONTINUE;
IO.PutF[tH.out, "\nStatistics:\n\n%g sent\n", [cardinal[dH.sent]] ];
IO.PutF[tH.out, "%g good\n%g dropped\n%g bad\n", [cardinal[dH.recvd[good]]], [cardinal[dH.recvd[dropped]]], [cardinal[dH.recvd[bad]]] ];
IF dH.recvd[good] # 0 THEN IO.PutF[tH.out, "\n%g uSecs avg delay\n", [cardinal[dH.delay[good]/dH.recvd[good]]] ];
dH.sent ← 0;
dH.recvd ← ALL[0];
dH.delay ← ALL[0];
};
Exit[tH];
};
PokeOnce: ButtonProc ~ {
dH: DataHandle ~ NARROW[tH.data];
cH: ControlHandle ~ NARROW[tH.control];
IF NOT Enter[tH, pokeOnce] THEN RETURN;
BEGIN
valid: BOOL;
remote: XNS.Address;
nHWords: CARDINAL;
b: XNSBuf.Buffer;
r: PokeResult;
uSecs: LONG CARDINAL;
[valid, remote] ← GetRemoteAddress[tH];
IF NOT valid THEN GOTO Out;
nHWords ← GetPacketWords[tH];
IO.PutF[tH.out, "\nSending to %g ... ", [rope[cH.remoteMachine]] ];
[b, r, uSecs] ← DoPoke[sH~dH.sH, remote~remote, b~NIL, seqNum~17, nHWords~nHWords];
IO.PutF[tH.out, "%g, delay=%g uSec\n",
[rope[(SELECT r FROM good => "echoed", dropped => "dropped", ENDCASE => "lost")]],
[cardinal[uSecs]] ];
IF b # NIL THEN XNSSocket.FreeBuffer[b];
END;
Exit[tH];
};
DoPoke:
PROC [sH: XNSSocket.Handle, remote: XNS.Address, b: XNSBuf.Buffer, seqNum:
CARDINAL, nHWords:
CARDINAL]
RETURNS [newB: XNSBuf.Buffer, r: PokeResult, uSecs:
LONG
CARDINAL] ~ {
eB: XNSEchoBuf.Buffer;
seqNumH: HWORD ~ Endian.HFromCard[seqNum];
pulses: BasicTime.Pulses;
userBytes: CARDINAL;
userBytes ← nHWords * Endian.bytesPerHWord + XNSEchoBuf.hdrBytes;
IF b = NIL THEN b ← XNSSocket.AllocBuffer[sH];
TRUSTED { eB ← LOOPHOLE[b] };
eB.hdr1.type ← echo;
eB.hdr2 ← [filler~0, type~request];
FOR i:
CARDINAL
IN [0 .. nHWords)
DO
eB.body.hWords[i] ← seqNumH;
ENDLOOP;
XNSSocket.SetUserBytes[b, userBytes];
pulses ← BasicTime.GetClockPulses[];
XNSSocket.Send[b~b, dest~remote]; b ← NIL;
newB ← XNSSocket.Get[sH];
uSecs ← BasicTime.PulsesToMicroseconds[ BasicTime.GetClockPulses[] - pulses ];
IF newB = NIL THEN { r ← dropped; RETURN };
IF XNSSocket.GetUserBytes[newB] # userBytes THEN { r ← bad; RETURN };
TRUSTED { eB ← LOOPHOLE[newB] };
IF (eB.hdr1.type # echo) OR (eB.hdr2.type # reply) THEN { r ← bad; RETURN };
FOR i:
CARDINAL
IN [0 .. nHWords)
DO
IF eB.body.hWords[i] # seqNumH THEN { r ← bad; RETURN };
ENDLOOP;
r ← good; RETURN;
};
Echo: ButtonProc ~ {
cH: ControlHandle ~ NARROW[tH.control];
dH: DataHandle ~ NARROW[tH.data];
IF NOT Enter[tH, echo] THEN RETURN;
BEGIN
ENABLE ABORTED => GOTO Out;
valid: BOOL;
remote: XNS.Address;
nHWords: CARDINAL;
b: XNSBuf.Buffer ← NIL;
r: PokeResult;
uSecs: LONG CARDINAL;
[valid, remote] ← GetRemoteAddress[tH];
IF NOT valid THEN GOTO Out;
nHWords ← GetPacketWords[tH];
IO.PutF[tH.out, "\nEchoing to %g ...\n\n", [rope[cH.remoteMachine]] ];
FOR seqNum:
CARDINAL ← 1, seqNum+1
WHILE
NOT dH.pleaseStop
DO
[b, r, uSecs] ← DoPoke[dH.sH, remote, b, seqNum, nHWords];
dH.sent ← dH.sent + 1;
SELECT r
FROM
good => IO.PutChar[tH.out, '!];
dropped => IO.PutChar[tH.out, '?];
ENDCASE => IO.PutChar[tH.out, '#];
dH.recvd[r] ← dH.recvd[r] + 1;
dH.delay[r] ← dH.delay[r] + uSecs;
ENDLOOP;
IO.PutRope[tH.out, "\n\n ... stopped.\n"];
IF b # NIL THEN XNSSocket.FreeBuffer[b];
END;
Exit[tH];
};