<> <> <> <> <<>> <> <<>> DIRECTORY Arpa USING [Address, nullAddress], ArpaBuf USING [Buffer, FragmentOffset, maxBodyHWords, maxBytes, minIHL], ArpaExtras USING [IsBroadcast, MyAddress], ArpaICMPBuf USING [Body, Buffer, hdrBytes, maxEchoDataBytes], ArpaIP USING [GetUserBytes], ArpaName USING [ReplyStatus, NameToAddress], ArpaUDP USING [GetUserBytes], ArpaUDPBuf USING [Buffer, maxBodyHWords], Basics USING [Card32FromF, Card16FromH, FFromCard32, FWORD, HFromCard16, HWORD], BasicTime USING [GMT, Now], Commander USING [CommandProc, Register], CommBuffer USING [Overhead], CommDriver USING [Buffer, BufferObject, CreateInterceptor, DestroyInterceptor, GetNetworkChain, Interceptor, Network, RecvInterceptor, SendInterceptor], Convert USING [RopeFromCard], ConvertExtras USING [RopeFromArpaAddress], GenericTool USING [ButtonProc, CreateInstance, PutMsgRope, ToolHandle], IO USING [Error, PutChar, PutF, PutFR, STREAM], Process USING [ConditionPointer, EnableAborts, Pause, SecondsToTicks, SetTimeout], Rope USING [Concat, FromChar, Length, ROPE], ViewerClasses USING [Viewer]; OfflineArpaSpyTool: CEDAR MONITOR IMPORTS ArpaBuf, ArpaExtras, ArpaIP, ArpaName, ArpaUDP, Basics, BasicTime, Commander, CommDriver, Convert, ConvertExtras, GenericTool, IO, Process, Rope ~ BEGIN ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; Viewer: TYPE ~ ViewerClasses.Viewer; FWORD: TYPE ~ Basics.FWORD; HWORD: TYPE ~ Basics.HWORD; ToolHandle: TYPE ~ GenericTool.ToolHandle; ButtonProc: TYPE ~ GenericTool.ButtonProc; thisHost: Arpa.Address _ ArpaExtras.MyAddress[]; <> RopeFromNetTime: PROC [netTime: FWORD] RETURNS [rope: ROPE] = { seconds, fraction, ms: CARD; ms _ CfF[netTime]; IF ms < 0 THEN RETURN["Unknown"]; seconds _ ms/1000; fraction _ ms MOD 1000; RETURN[IO.PutFR["%R.%03G", [integer[seconds]], [integer[fraction]] ]]; }; <> toolName: ROPE ~ "OfflineArpaSpyTool"; globalToolHandle: ToolHandle; RunningType: TYPE ~ { none, catch, show, stats }; ControlHandle: TYPE ~ REF ControlObject; ControlObject: TYPE ~ RECORD [ running: RunningType _ none, stop: ButtonProc _ Stop, catch: ButtonProc _ Catch, show: ButtonProc _ Show, stats: ButtonProc _ Stats, putICMP: BOOL _ FALSE, putUDP: BOOL _ FALSE, putTCP: BOOL _ FALSE, putBcst: BOOL _ FALSE, -- packets sent to IP broadcast address putOther: BOOL _ FALSE, shortFormat: BOOL _ FALSE, maxLength: CARDINAL _ ArpaBuf.maxBytes, buffers: CARDINAL _ 32, thisMachine: ROPE, to: ROPE, from: ROPE]; DataHandle: TYPE ~ REF DataObject; DataObject: TYPE ~ RECORD [ interceptor: CommDriver.Interceptor _ NIL, printQHead, printQTail, printQFree: PrintBuf, printQNonempty: CONDITION, pleaseStop: BOOL _ FALSE, promiscuous: BOOL _ FALSE, fromHost: Arpa.Address _ Arpa.nullAddress, toHost: Arpa.Address _ Arpa.nullAddress, nTooLong: INT _ 0, nICMP: INT _ 0, nUDP: INT _ 0, nTCP: INT _ 0, nBcst: INT _ 0, nOther: INT _ 0 ]; Create: ENTRY PROC RETURNS [created: BOOL] ~ { cH: ControlHandle; dH: DataHandle; IF globalToolHandle # NIL THEN RETURN [FALSE]; cH _ NEW[ControlObject _ [thisMachine~ConvertExtras.RopeFromArpaAddress[thisHost]]]; dH _ NEW[DataObject _ []]; TRUSTED { InitCond[@dH.printQNonempty] }; globalToolHandle _ GenericTool.CreateInstance[ toolName~toolName, control~cH, options~LIST[ ["thisMachine", readonly[]], ["to", notify[GetFilterAddresses]], ["from", notify[GetFilterAddresses]] ], 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] RETURNS [entered: BOOL] ~ { cH: ControlHandle ~ NARROW[tH.control]; IF cH.running # none THEN { GenericTool.PutMsgRope[tH, "Tool is busy", TRUE]; RETURN [FALSE] }; cH.running _ me; RETURN [TRUE]; }; Exit: PROC [tH: ToolHandle] ~ { cH: ControlHandle ~ NARROW[tH.control]; 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 ~ { Stop[tH]; globalToolHandle _ NIL }; GetFilterAddresses: ButtonProc ~ { dH: DataHandle ~ NARROW[tH.data]; cH: ControlHandle ~ NARROW[tH.control]; gotTo, gotFrom: BOOL; status: ArpaName.ReplyStatus; source: Arpa.Address; gotTo _ gotFrom _ FALSE; dH.toHost _ dH.fromHost _ thisHost; IF Rope.Length[cH.to] > 0 THEN { [dH.toHost, status, source] _ ArpaName.NameToAddress[cH.to]; SELECT status FROM bogus => GenericTool.PutMsgRope[tH,"Invalid name in To field.\n", TRUE]; down => GenericTool.PutMsgRope[tH,"Name servers not responding.\n", TRUE]; other => GenericTool.PutMsgRope[tH,"No address for To field name.\n", TRUE]; ENDCASE; IF dH.toHost # Arpa.nullAddress THEN gotTo _ TRUE ELSE GenericTool.PutMsgRope[tH, "Unable to load address for To field name.\n", TRUE]; }; IF Rope.Length[cH.from] > 0 THEN { [dH.fromHost, status, source] _ ArpaName.NameToAddress[cH.from]; SELECT status FROM bogus => GenericTool.PutMsgRope[tH, "Invalid name in From field.\n", TRUE]; down => GenericTool.PutMsgRope[tH, "Name servers not responding.\n", TRUE]; other => GenericTool.PutMsgRope[tH, "No address for From field name.\n", TRUE]; ENDCASE; IF dH.fromHost # Arpa.nullAddress THEN gotFrom _ TRUE ELSE GenericTool.PutMsgRope[tH, "Unable to load address for From field name.\n", TRUE]}; dH.promiscuous _ (gotTo OR gotFrom); }; 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, "\n%7g ICMP\n%7g UDP\n%7g TCP\n%7g Other\n", [integer[dH.nICMP]], [integer[dH.nUDP]], [integer[dH.nTCP]], [integer[dH.nOther]] ]; IO.PutF[tH.out, "\n%7g dropped (too long)\n", [integer[dH.nTooLong]] ]; IO.PutF[tH.out, "\n%7g total\n\n", [integer[ dH.nICMP+dH.nUDP+dH.nTCP+dH.nOther]] ]; }; Exit[tH]; }; <> HfC: PROC [c: CARDINAL] RETURNS [HWORD] ~ INLINE { RETURN [Basics.HFromCard16[c]] }; FfC: PROC [c: LONG CARDINAL] RETURNS [FWORD] ~ INLINE { RETURN [Basics.FFromCard32[c]] }; CfH: PROC [h: HWORD] RETURNS [CARDINAL] ~ INLINE { RETURN [Basics.Card16FromH[h]] }; CfF: PROC [f: FWORD] RETURNS [LONG CARDINAL] ~ INLINE { RETURN [Basics.Card32FromF[f]] }; <> DisplayArpaBuf: PROC [s: STREAM, b: ArpaBuf.Buffer, heading: ROPE, bufBytes: NAT, when: BasicTime.GMT, short: BOOL] ~ { <> PutHdr: PROC [typeName: ROPE] ~ { IO.PutF[s, "Arpa %g (%g) %g:\n", [rope[heading]], [cardinal[bufBytes]], [time[when]] ]; IO.PutF[s, "%g->%g, ihl %g, len %g ttl %g", [rope[ConvertExtras.RopeFromArpaAddress[b.hdr1.source]]], [rope[ConvertExtras.RopeFromArpaAddress[b.hdr1.dest]]], [cardinal[b.hdr1.ihl]], [cardinal[CfH[b.hdr1.length]]], [cardinal[b.hdr1.timeToLive]] ]; IO.PutF[s, ", id %x, frag %x\n", [cardinal[CfH[b.hdr1.fragmentId]]], [cardinal[CfH[b.hdr1.fragmentCtl]]] ]; }; bodyBytes, optionsBytes: CARDINAL; firstFragment, optionsPresent, simple: BOOL; [bodyBytes, optionsBytes] _ ArpaIP.GetUserBytes[b]; firstFragment _ (ArpaBuf.FragmentOffset[b] = 0); optionsPresent _ (optionsBytes # 0); simple _ firstFragment AND (NOT optionsPresent); SELECT b.hdr1.protocol FROM icmp => { PutHdr["ICMP"]; IF simple THEN TRUSTED { PutICMPBuf[s, LOOPHOLE[b], bodyBytes, short] } ELSE PutOtherBuf[s, b, bodyBytes, short]; }; udp => { PutHdr["UDP"]; IF simple THEN TRUSTED { PutUDPBuf[s, LOOPHOLE[b], bodyBytes, short] } ELSE PutOtherBuf[s, b, bodyBytes, short]; }; tcp => { PutHdr["TCP"]; IF simple THEN TRUSTED { PutTCPBuf[s, LOOPHOLE[b], bodyBytes, short] } ELSE PutOtherBuf[s, b, bodyBytes, short]; }; ENDCASE => { PutHdr[Convert.RopeFromCard[ORD[b.hdr1.protocol], 16]]; PutOtherBuf[s, b, bodyBytes, short]; }; }; <<>> PutICMPBuf: PROC [s: STREAM, b: ArpaICMPBuf.Buffer, bytes: CARDINAL, short: BOOL] ~ { SELECT b.hdr2.icmpType FROM echoReply => PutEchoRequestOrReply[s, b, bytes, short, "echoReply"]; destUnreachable => PutReturnedData[s, b, bytes, short, "destUnreachable"]; sourceQuench => PutReturnedData[s, b, bytes, short, "sourceQuench"]; redirect => PutReturnedDataAndAddress[s, b, bytes, short, "redirect"]; echo => PutEchoRequestOrReply[s, b, bytes, short, "echo"]; timeExceeded => PutReturnedData[s, b, bytes, short, "timeExceeded"]; parameterProblem => PutReturnedDataAndPointer[s, b, bytes, short, "parameterProblem"]; timestamp => PutTimes[s, b, bytes, short, "timestamp"]; timestampReply => PutTimes[s, b, bytes, short, "timestampReply"]; infoRequest => PutInfoRequestOrReply[s, b, bytes, short, "infoRequest"]; infoReply => PutInfoRequestOrReply[s, b, bytes, short, "infoReply"]; ENDCASE => { IO.PutF[s, "type %g code %g\n", [cardinal[b.hdr2.icmpType.ORD]], [cardinal[b.hdr2.icmpCode]] ]; }; }; PutEchoRequestOrReply: PROC [s: STREAM, b: ArpaICMPBuf.Buffer, bytes: CARDINAL, short: BOOL, title: ROPE] ~ { i: CARDINAL; IO.PutF[s, "%g code %g id %g seq %g\n", [rope[title]], [cardinal[b.hdr2.icmpCode]], [cardinal[CfH[b.body.echo.identifier]]], [cardinal[CfH[b.body.echo.sequenceNum]]] ]; bytes _ bytes - ArpaICMPBuf.hdrBytes - (BYTES[ArpaICMPBuf.Body.echo] - ArpaICMPBuf.maxEchoDataBytes); i _ 0; WHILE i < bytes DO THROUGH [1..10] DO IF i < ArpaICMPBuf.maxEchoDataBytes THEN IO.PutF[s, "%4x ", [cardinal[b.body.echo.data[i]]] ]; i _ i + 1; ENDLOOP; IO.PutChar[s, '\n]; IF short THEN EXIT; ENDLOOP; }; PutOrigHdr: PROC [s: STREAM, b: ArpaICMPBuf.Buffer] ~ { IO.PutF[s, "%g->%g, ihl %g, len %g ttl %g", [rope[ConvertExtras.RopeFromArpaAddress[b.body.destUnreachable.origHdr.source]]], [rope[ConvertExtras.RopeFromArpaAddress[b.body.destUnreachable.origHdr.dest]]], [cardinal[b.body.destUnreachable.origHdr.ihl]], [cardinal[CfH[b.body.destUnreachable.origHdr.length]]], [cardinal[b.body.destUnreachable.origHdr.timeToLive]] ]; IO.PutF[s, ", id %x, frag %x\n", [cardinal[CfH[b.body.destUnreachable.origHdr.fragmentId]]], [cardinal[CfH[b.body.destUnreachable.origHdr.fragmentCtl]]] ]; }; PutReturnedData: PROC [s: STREAM, b: ArpaICMPBuf.Buffer, bytes: CARDINAL, short: BOOL, title: ROPE] ~ { IO.PutF[s, "%g code %g\n", [rope[title]], [cardinal[b.hdr2.icmpCode]] ]; PutOrigHdr[s, b]; }; PutReturnedDataAndAddress: PROC [s: STREAM, b: ArpaICMPBuf.Buffer, bytes: CARDINAL, short: BOOL, title: ROPE] ~ { IO.PutF[s, "%g code %g adr %g\n", [rope[title]], [cardinal[b.hdr2.icmpCode]], [rope[ConvertExtras.RopeFromArpaAddress[b.body.redirect.address]]] ]; PutOrigHdr[s, b]; }; PutReturnedDataAndPointer: PROC [s: STREAM, b: ArpaICMPBuf.Buffer, bytes: CARDINAL, short: BOOL, title: ROPE] ~ { IO.PutF[s, "%g code %g ptr %g\n", [rope[title]], [cardinal[b.hdr2.icmpCode]], [cardinal[b.body.parameterProblem.pointer]] ]; PutOrigHdr[s, b]; }; PutTimes: PROC [s: STREAM, b: ArpaICMPBuf.Buffer, bytes: CARDINAL, short: BOOL, title: ROPE] ~ { IO.PutF[s, "%g code %g id %g seq %g\n", [rope[title]], [cardinal[b.hdr2.icmpCode]], [cardinal[CfH[b.body.timestamp.identifier]]], [cardinal[CfH[b.body.timestamp.sequenceNum]]] ]; IO.PutF[s, "orig %g revc %g send %g\n", [rope[RopeFromNetTime[b.body.timestamp.originateTimestamp]]], [rope[RopeFromNetTime[b.body.timestamp.receiveTimestamp]]], [rope[RopeFromNetTime[b.body.timestamp.transmitTimestamp]]] ]; }; PutInfoRequestOrReply: PROC [s: STREAM, b: ArpaICMPBuf.Buffer, bytes: CARDINAL, short: BOOL, title: ROPE] ~ { IO.PutF[s, "%g code %g id %g seq %g\n", [rope[title]], [cardinal[b.hdr2.icmpCode]], [cardinal[CfH[b.body.infoRequest.identifier]]], [cardinal[CfH[b.body.infoRequest.sequenceNum]]] ]; }; PutUDPBuf: PROC [s: STREAM, b: ArpaUDPBuf.Buffer, bytes: CARDINAL, short: BOOL] ~ { i, words: CARDINAL; words _ MIN[((ArpaUDP.GetUserBytes[b]+BYTES[HWORD]-1) / BYTES[HWORD]), ArpaUDPBuf.maxBodyHWords]; IO.PutF[s, "ports %g->%g len %g\n", [cardinal[CfH[b.hdr2.sourcePort]]], [cardinal[CfH[b.hdr2.destPort]]], [cardinal[CfH[b.hdr2.length]]] ]; i _ 0; WHILE i < words DO THROUGH [1..10] WHILE i < words DO IO.PutF[s, "%4x ", [cardinal[CfH[b.body.hWords[i]]]] ]; i _ i + 1; ENDLOOP; IO.PutChar[s, '\n]; IF short THEN EXIT; ENDLOOP; }; PutTCPBuf: PROC [s: STREAM, b: ArpaBuf.Buffer, bytes: CARDINAL, short: BOOL] ~ { <> <> PutOtherBuf[s, b, bytes, short]; }; PutOtherBuf: PROC [s: STREAM, b: ArpaBuf.Buffer, bytes: CARDINAL, short: BOOL] ~ { i: CARDINAL _ 0; words: CARDINAL _ MIN[((bytes+BYTES[HWORD]-1) / BYTES[HWORD]), ArpaBuf.maxBodyHWords]; WHILE i < words DO THROUGH [1..10] WHILE i < words DO IO.PutF[s, "%4x ", [cardinal[CfH[b.body.hWords[i]]]] ]; i _ i + 1; ENDLOOP; IO.PutChar[s, '\n]; IF short THEN EXIT; ENDLOOP; }; <> Recv: CommDriver.RecvInterceptor <<[recv: RecvType, data: REF ANY, network: Network, buffer: Buffer, bytes: NAT] RETURNS [kill: BOOL _ FALSE]>> ~ { tH: ToolHandle; dH: DataHandle; cH: ControlHandle; tH _ NARROW[data]; dH _ NARROW[tH.data]; cH _ NARROW[tH.control]; IF bytes > cH.maxLength THEN { dH.nTooLong _ dH.nTooLong.SUCC; RETURN }; SELECT recv FROM arpa, arpaTranslate => { b: ArpaBuf.Buffer; TRUSTED { b _ LOOPHOLE[buffer] }; IF dH.promiscuous THEN { SELECT TRUE FROM (b.hdr1.source = dH.fromHost) => { PostPrintRequest[tH~tH, kind~arpa, buffer~buffer, direction~send, bytes~bytes] }; (b.hdr1.dest = dH.toHost) => { PostPrintRequest[tH~tH, kind~arpa, buffer~buffer, direction~recv, bytes~bytes] }; (ArpaExtras.IsBroadcast[b.hdr1.dest]) => { PostPrintRequest[tH~tH, kind~arpa, buffer~buffer, direction~recv, bytes~bytes] }; ENDCASE; } ELSE { PostPrintRequest[tH~tH, kind~arpa, buffer~buffer, direction~recv, bytes~bytes]; }; }; ENDCASE => NULL; }; Send: CommDriver.SendInterceptor <<[send: SendType, data: REF ANY, network: Network, buffer: Buffer, bytes: NAT] RETURNS [kill: BOOL _ FALSE]]>> ~ { tH: ToolHandle; dH: DataHandle; cH: ControlHandle; tH _ NARROW[data]; dH _ NARROW[tH.data]; cH _ NARROW[tH.control]; IF dH.promiscuous THEN RETURN; IF bytes > cH.maxLength THEN { dH.nTooLong _ dH.nTooLong.SUCC; RETURN }; SELECT send FROM arpa, arpaReturn => { b: ArpaBuf.Buffer; TRUSTED { b _ LOOPHOLE[buffer] }; PostPrintRequest[tH~tH, kind~arpa, buffer~buffer, direction~send, bytes~bytes]; }; ENDCASE => NULL; }; Install: ENTRY PROC [tH: ToolHandle] ~ { dH: DataHandle ~ NARROW[tH.data]; network: CommDriver.Network ~ CommDriver.GetNetworkChain[]; IF dH.interceptor # NIL THEN RETURN; IF network = NIL THEN RETURN; dH.interceptor _ CommDriver.CreateInterceptor[ network~network, sendMask~[arpa~TRUE, arpaReturn~TRUE, arpaTranslate~FALSE, xns~FALSE, xnsReturn~FALSE, xnsTranslate~FALSE, pup~FALSE, pupReturn~FALSE, pupTranslate~FALSE, other~FALSE, otherReturn~FALSE, otherTranslate~FALSE, raw~FALSE], sendProc~Send, recvMask~[arpa~TRUE, arpaTranslate~FALSE, xns~FALSE, xnsTranslate~FALSE, pup~FALSE, pupTranslate~FALSE, other~FALSE, otherTranslate~FALSE, error~FALSE], recvProc~Recv, data~tH, promiscuous~dH.promiscuous]; }; UnInstall: ENTRY PROC [tH: ToolHandle] ~ { dH: DataHandle ~ NARROW[tH.data]; IF dH.interceptor = NIL THEN RETURN; CommDriver.DestroyInterceptor[dH.interceptor]; dH.interceptor _ NIL }; <> <> BufKind: TYPE ~ { arpa, unknown }; BufDirection: TYPE ~ { send, recv }; PrintBuf: TYPE ~ REF PrintBufObject; PrintBufObject: TYPE ~ RECORD [ bufObject: CommDriver.BufferObject, bytes: NAT, time: BasicTime.GMT, kind: BufKind, direction: BufDirection, next: PrintBuf]; FlushPrintQueue: ENTRY PROC [cH: ControlHandle, dH: DataHandle] ~ { dH.printQHead _ dH.printQTail _ NIL; THROUGH [1 .. cH.buffers] DO temp: PrintBuf _ NEW[PrintBufObject]; temp.next _ dH.printQFree; dH.printQFree _ temp; ENDLOOP; }; EnqueuePrintBuf: ENTRY PROC [dH: DataHandle, b: PrintBuf] ~ INLINE { IF dH.printQHead = NIL THEN { dH.printQHead _ dH.printQTail _ b; } ELSE { dH.printQTail.next _ b; dH.printQTail _ b }; b.next _ NIL }; AllocPrintBuf: ENTRY PROC [dH: DataHandle] RETURNS [b: PrintBuf] ~ INLINE { IF (b _ dH.printQFree) # NIL THEN { dH.printQFree _ b.next; b.next _ NIL } ELSE { b _ dH.printQHead; IF b = NIL THEN ERROR; dH.printQHead _ b.next; b.next _ NIL; }; }; PostPrintRequest: PROC [tH: ToolHandle, kind: BufKind, buffer: CommDriver.Buffer, direction: BufDirection, bytes: NAT] ~ { cH: ControlHandle ~ NARROW[tH.control]; dH: DataHandle _ NARROW[tH.data]; b: PrintBuf; SELECT kind FROM arpa => { ab: ArpaBuf.Buffer; TRUSTED { ab _ LOOPHOLE[buffer] }; IF ArpaExtras.IsBroadcast[ab.hdr1.dest] THEN { dH.nBcst _ dH.nBcst + 1; IF NOT cH.putBcst THEN RETURN }; SELECT ab.hdr1.protocol FROM icmp => { dH.nICMP _ dH.nICMP + 1; IF NOT cH.putICMP THEN RETURN }; udp => { dH.nUDP _ dH.nUDP + 1; IF NOT cH.putUDP THEN RETURN }; tcp => { dH.nTCP _ dH.nTCP + 1; IF NOT cH.putTCP THEN RETURN }; ENDCASE => { dH.nOther _ dH.nOther + 1; IF NOT cH.putOther THEN RETURN }; }; ENDCASE => ERROR; b _ AllocPrintBuf[dH]; b.bufObject _ buffer^; b.bytes _ bytes; b.bufObject.ovh.next _ NIL; b.bufObject.ovh.network _ NIL; b.time _ BasicTime.Now[]; b.kind _ kind; b.direction _ direction; EnqueuePrintBuf[dH, b]; }; Catch: PROC [tH: ToolHandle] ~ { cH: ControlHandle ~ NARROW[tH.control]; dH: DataHandle ~ NARROW[tH.data]; IF NOT Enter[tH, catch] THEN RETURN; FlushPrintQueue[cH, dH]; Install[tH]; IO.PutF[tH.out, "\nCatching at %g\n\n", [time[BasicTime.Now[]]] ]; DO ENABLE ABORTED => EXIT; IF dH.pleaseStop THEN EXIT; Process.Pause[ Process.SecondsToTicks[1] ]; -- ugh! ENDLOOP; IO.PutF[tH.out, "\n\nStopped %g\n", [time[BasicTime.Now[]]] ]; UnInstall[tH]; Exit[tH]; }; Show: PROC [tH: ToolHandle] ~ { cH: ControlHandle ~ NARROW[tH.control]; dH: DataHandle ~ NARROW[tH.data]; IF NOT Enter[tH, show] THEN RETURN; IO.PutF[tH.out, "\nShowing at %g\n\n", [time[BasicTime.Now[]]] ]; FOR b: PrintBuf _ dH.printQHead, b.next WHILE b # NIL DO ENABLE IO.Error, ABORTED => EXIT; IF dH.pleaseStop THEN EXIT; SELECT b.direction FROM send => SELECT b.kind FROM arpa => TRUSTED { DisplayArpaBuf[tH.out, LOOPHOLE[b], "Send", b.bytes, b.time, cH.shortFormat] }; ENDCASE => ERROR; recv => SELECT b.kind FROM arpa => TRUSTED { DisplayArpaBuf[tH.out, LOOPHOLE[b], "Recv", b.bytes, b.time, cH.shortFormat] }; ENDCASE => ERROR; ENDCASE; IO.PutChar[tH.out, '\n]; ENDLOOP; IO.PutF[tH.out, "\n\nDone showing at %g\n", [time[BasicTime.Now[]]] ]; Exit[tH]; }; <> CvtRopeFromCardinals: PROC [args: LIST OF CARDINAL] RETURNS [result: ROPE _ NIL] ~ { WHILE args # NIL DO n: CARDINAL ~ args.first; result _ Rope.Concat[result, Rope.FromChar[VAL[n/256]]]; result _ Rope.Concat[result, Rope.FromChar[VAL[n MOD 256]]]; args _ args.rest; ENDLOOP; }; <> 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.