<> <> <> <<>> 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[]; <> 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]; 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]; }; <> Go: Commander.CommandProc <<[cmd: Handle] RETURNS [result: REF _ NIL, msg: ROPE _ NIL]>> ~ { IF Create[] THEN RETURN [result~$Done] ELSE RETURN [result~$Failure] }; Commander.Register[key~toolName, proc~Go]; END.