XNSEchoTool.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Demers, November 28, 1986 3:24:01 pm PST
DIRECTORY
BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds],
Commander USING [CommandProc, Register],
Endian USING[BYTE, bytesPerHWord, FWORD, HFromCard, HWORD],
GenericTool USING [ButtonProc, CreateInstance, PutMsgRope, ToolHandle],
IO USING [Error, PutChar, PutF, PutRope, STREAM],
Process USING [ConditionPointer, EnableAborts, Pause, SecondsToTicks, SetTimeout],
Rope USING [Length, ROPE],
ViewerClasses USING [Viewer],
XNS USING [Address, GetThisHost, Host, unknownAddress],
XNSAddressParsing USING [AddressFromRope, Error, MyRope],
XNSBuf USING [Buffer],
XNSEchoBuf USING [Buffer, hdrBytes, maxBodyHWords],
XNSSocket USING [AllocBuffer, Create, Destroy, dontWait, FreeBuffer, Get, GetUserBytes, Handle, Milliseconds, Send, SetGetTimeout, SetUserBytes],
XNSWKS USING [echo];
XNSEchoTool: CEDAR MONITOR
IMPORTS BasicTime, Commander, Endian, GenericTool, IO, Process, Rope, XNS, XNSAddressParsing, XNSSocket
~ BEGIN
ROPE: TYPE ~ Rope.ROPE;
STREAM: TYPE ~ IO.STREAM;
Viewer: TYPE ~ ViewerClasses.Viewer;
BYTE: TYPE ~ Endian.BYTE;
FWORD: TYPE ~ Endian.FWORD;
HWORD: TYPE ~ Endian.HWORD;
ToolHandle: TYPE ~ GenericTool.ToolHandle;
ButtonProc: TYPE ~ GenericTool.ButtonProc;
thisHost: XNS.Host ← XNS.GetThisHost[];
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: BOOLFALSE,
sH: XNSSocket.Handle ← NIL,
remoteAddress: XNS.Address ← XNS.unknownAddress,
sent: LONG CARDINAL ← 0,
recvd: ARRAY PokeResult OF LONG CARDINALALL[0],
delay: ARRAY PokeResult OF LONG CARDINALALL[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: BOOLFALSE] 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: BOOLFALSE, 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];
EXITS
Out => NULL;
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];
EXITS
Out => NULL;
END;
Exit[tH];
};
Initialization
Go: Commander.CommandProc
[cmd: Handle] RETURNS [result: REFNIL, msg: ROPENIL]
~ {
IF Create[]
THEN RETURN [result~$Done]
ELSE RETURN [result~$Failure]
};
Commander.Register[key~toolName, proc~Go];
END.