DIRECTORY Atom USING [GetPName], BasicTime USING [GetClockPulses, GMT, Now, Period, Pulses, PulsesToMicroseconds, Update], Buttons USING [Button, ButtonProc, Create, Destroy, SetDisplayStyle], Commander USING [CommandProc, Register], Containers USING [ChildXBound, ChildYBound, Create], Convert USING [Error, IntFromRope, RopeFromInt, TimeFromRope], IO USING [Flush, PutChar, PutF, PutRope, STREAM, Value], Labels USING [Create], Loader USING [BCDBuildTime], PrincOps USING [zEXCH], Rope USING [ROPE], Rules USING [Create], TypeScript USING [ChangeLooks, Create], ViewerClasses USING [Viewer], ViewerIO USING [CreateViewerStreams], ViewerOps USING [AddProp, ComputeColumn, CreateViewer, MoveViewer, OpenIcon, SetOpenHeight], ViewerTools USING [GetContents, MakeNewTextViewer, SetContents, SetSelection], IPDefs USING [Byte, DataBuffer, Datagram, DatagramRec, DByte, Address, InternetHeader, nullAddress], IPName USING [AddressToName, AddressToRope, LoadCacheFromName, NameToAddress], UDP USING [BodyRec, Create, date, default, Destroy, echo, Handle, maxLength, minLength, Receive, Send, time]; UDPEchoTool: CEDAR MONITOR IMPORTS Atom, BasicTime, Buttons, Commander, Containers, Convert, IO, Labels, Loader, Rules, TypeScript, ViewerIO, ViewerOps, ViewerTools, IPName, UDP = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Viewer: TYPE = ViewerClasses.Viewer; buttonHeight: INTEGER _ 0; buttonWidth: INTEGER _ 0; ClientData: TYPE = REF ClientDataRep; ClientDataRep: TYPE = RECORD [ log: STREAM _ NIL, in: STREAM _ NIL, pleaseStop: BOOLEAN _ FALSE, user: PROCESS _ NIL, where: IPDefs.Address _ IPDefs.nullAddress, echo: REF BOOL _ NEW [BOOL _ TRUE], miss: REF BOOL _ NEW [BOOL _ TRUE], late: REF BOOL _ NEW [BOOL _ TRUE], fixedLength: REF BOOL _ NEW [BOOL _ FALSE], target, length, delay, pause: Viewer _ NIL, good: INT _ 0, minDelay, maxDelay: INT _ 0, delayHist: ARRAY DelayRange OF INT _ ALL[0] ]; DelayRange: TYPE = { d1, d1a, d2, d2a, d3, d3a, d4, d4a, d5, d5a, d6, d6a, d7, d7a, d8, d8a, d9, d9a, d10, d14, d20, d28, d50, d70, d100, d140, d200, d280, d500, d700, d1000, d1400, d2000, d2800, d5000, d7000, d10000, d14000, d20000, d28000, more}; delayTime: ARRAY DelayRange OF INT = [ d1: 1000, d1a: 1500, d2: 2000, d2a: 2500, d3: 3000, d3a: 3500, d4: 4000, d4a: 4500, d5: 5000, d5a: 5500, d6: 6000, d6a: 6500, d7: 7000, d7a: 7500, d8: 8000, d8a: 8500, d9: 9000, d9a: 9500, d10: 10000, d14: 14000, d20: 20000, d28: 28000, d50: 50000, d70: 70000, d100: 100000, d140: 140000, d200: 200000, d280: 280000, d500: 500000, d700: 700000, d1000: 1000000, d1400: 1400000, d2000: 2000000, d2800: 2800000, d5000: 5000000, d7000: 7000000, d10000: 10000000, d14000: 14000000, d20000: 20000000, d28000: 28000000, more: LAST[INT]]; Create: Commander.CommandProc = TRUSTED { data: ClientData _ NEW[ClientDataRep _ []]; viewer, buttons, log: Viewer _ NIL; viewer _ ViewerOps.CreateViewer [ flavor: $Container, info: [name: "UDP EchoTool", column: right, iconic: TRUE, scrollable: FALSE]]; ViewerOps.AddProp[viewer, $UDPEcho, data]; { -- Kludge to find Button size temp: Buttons.Button = Buttons.Create[ info: [name: "Length:", parent: viewer, border: FALSE, wx: 0, wy: 0], proc: NIL, clientData: NIL, fork: FALSE, paint: FALSE]; buttonWidth _ temp.ww; buttonHeight _ temp.wh; Buttons.Destroy[temp]; }; log _ TypeScript.Create[ [name: "UDPEchoTool.log", wy: 27+4, parent: viewer, border: FALSE], FALSE]; [data.in, data.log] _ ViewerIO.CreateViewerStreams [ name: "UDPEchoTool.log", backingFile: "UDPEchoTool.log", viewer: log, editedStream: FALSE]; Containers.ChildXBound[viewer, log]; Containers.ChildYBound[viewer, log]; CreateButtons[data, viewer, log]; TypeScript.ChangeLooks[log, 'f]; IO.PutF[data.log, "UDP EchoTool of %G.\n", [time[Loader.BCDBuildTime[Create]]]]; ViewerOps.OpenIcon[viewer]; }; CreateButtons: ENTRY PROC[data: ClientData, parent, log: Viewer] = { child: Viewer _ NIL; kids: Viewer = Containers.Create[ info: [parent: parent, border: FALSE, scrollable: FALSE, wx: 0, wy: -9999, ww: 9999, wh: 0] ]; Containers.ChildXBound[parent, kids]; child _ MakeBool[name: "!", init: data.echo, clientData: data, parent: kids, x: 2, y: 1]; child _ MakeBool[name: "?", init: data.miss, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy]; child _ MakeBool[name: "#", init: data.late, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy]; child _ MakeBool[name: "FixedLength", init: data.fixedLength, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy]; child _ data.length _ MakeLabeledText[ parent: kids, sibling: child, name: "Length:", data: Convert.RopeFromInt[UDP.maxLength], prev: data.length, newline: FALSE ]; child _ MakeRule[kids, child]; child _ data.target _ MakeLabeledText[ parent: kids, sibling: child, name: "Target:", data: "Target", prev: data.target ]; child _ MakeRule[kids, child]; child _ MakeLabel[kids, child, "What:"]; child _ MakeButton[kids, child, data, "Date", DateProc]; child _ MakeButton[kids, child, data, "Time", TimeProc]; child _ MakeButton[kids, child, data, "Start", StartProc]; child _ MakeButton[kids, child, data, "Hist", HistProc]; child _ MakeButton[kids, child, data, "Stop", StopProc]; child _ MakeRule[kids, child]; { kidsY: INTEGER = 2; kidsH: INTEGER = child.wy + child.wh + 2; ViewerOps.MoveViewer[viewer: log, x: 0, y: kidsY + kidsH, w: log.ww, h: parent.ch - (kids.wy + kidsH), paint: FALSE]; ViewerOps.SetOpenHeight[parent, kidsY + kidsH + 12 * buttonHeight]; IF ~parent.iconic THEN ViewerOps.ComputeColumn[parent.column]; ViewerOps.MoveViewer[viewer: kids, x: kids.wx, y: kidsY, w: kids.ww, h: kidsH]; }; }; StartProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Start[data]; }; Start: PROC [data: ClientData] = { target: ROPE = ViewerTools.GetContents[data.target]; IF data.user # NIL THEN Stop[data]; Report[data, "\nEchoing to ", target, " "]; IF ~FindPath[data, target] THEN RETURN; data.pleaseStop _ FALSE; data.user _ FORK User[data]; }; HistProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Stop[data]; PrintDelayHist[data]; }; StopProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Stop[data]; }; Stop: PROC [data: ClientData] = TRUSTED { data.pleaseStop _ TRUE; IF data.user # NIL THEN JOIN data.user; data.user _ NIL; }; User: PROC [data: ClientData] = TRUSTED { start, finish: BasicTime.GMT; sent, mashed, missed, late: INT _ 0; bytes: INT _ 0; packetNumber: CARDINAL _ 0; fixedLength: BOOL = data.fixedLength^; lengthText: ROPE = ViewerTools.GetContents[data.length]; bytesInSeqNo: CARDINAL _ 2; minLength: CARDINAL _ UDP.minLength+bytesInSeqNo; maxLength: CARDINAL _ UDP.maxLength; lengthCard: CARDINAL _ UDP.maxLength; timeout: INT _ 5000; handle: UDP.Handle _ UDP.Create[him: data.where, local: UDP.default, remote: UDP.echo]; IF handle = NIL THEN {IO.PutF[data.log, "Can't make handle.\n"]; RETURN[]; }; IF fixedLength THEN { temp: INT; temp _ Convert.IntFromRope[lengthText ! Convert.Error => { IO.PutF[data.log, "Can't parse Length field.\n"]; lengthCard _ 0; CONTINUE; } ]; temp _ MAX[temp, minLength]; temp _ MIN[temp, maxLength]; lengthCard _ temp; IO.PutF[data.log, "Packet length is %G bytes.\n", [integer[lengthCard]]]; }; InitDelayHist[data]; data.good _ 0; start _ BasicTime.Now[]; UNTIL data.pleaseStop DO FOR cycle: CARDINAL IN [minLength..maxLength] UNTIL data.pleaseStop DO packetStart, packetStop: BasicTime.Pulses; dg: IPDefs.Datagram _ NEW [IPDefs.DatagramRec]; length: CARDINAL _ IF fixedLength THEN lengthCard ELSE cycle; body: LONG POINTER TO UDP.BodyRec _ LOOPHOLE[@dg.data]; seqNo: LONG POINTER TO CARDINAL _ LOOPHOLE[@body.data]; seqNo^ _ (packetNumber _ packetNumber.SUCC); FOR k: CARDINAL IN [minLength..length) DO body.data[k] _ k MOD 100H; ENDLOOP; packetStart _ BasicTime.GetClockPulses[]; UDP.Send[handle, dg, length]; sent _ sent + 1; UNTIL (dg _ UDP.Receive[handle, timeout]) = NIL DO body _ LOOPHOLE[@dg.data]; seqNo _ LOOPHOLE[@body.data]; SELECT TRUE FROM (seqNo^ # packetNumber) OR (body.length # length) => BEGIN late _ late + 1; IF data.late^ THEN IO.PutRope[data.log, "#"]; END; ENDCASE => { packetStop _ BasicTime.GetClockPulses[]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[packetStop-packetStart]]; FOR k: CARDINAL IN [minLength..length) DO IF body.data[k] # k MOD 100H THEN { IO.PutRope[data.log, "~"]; mashed _ mashed + 1; EXIT; }; ENDLOOP; IF data.echo^ THEN IO.PutRope[data.log, "!"]; data.good _ data.good + 1; bytes _ bytes + length-minLength; EXIT; }; dg _ NIL; ENDLOOP; IF dg # NIL THEN dg _ NIL ELSE { missed _ missed + 1; IF data.miss^ THEN IO.PutRope[data.log, "?"]; }; ENDLOOP; IF ~data.echo^ THEN IO.PutRope[data.log, "."] ELSE IO.PutRope[data.log, "\n"]; ENDLOOP; IO.PutRope[data.log, "\n"]; finish _ BasicTime.Now[]; UDP.Destroy[handle]; BEGIN packetsPerSecond, msPerPacket: REAL; seconds: LONG CARDINAL _ BasicTime.Period[from: start, to: finish]; IF seconds = 0 THEN seconds _ 1; packetsPerSecond _ REAL[sent]/REAL[seconds]; IF data.good = 0 THEN msPerPacket _ 0 ELSE msPerPacket _ 1000.0*seconds/data.good; IO.PutF[data.log, "%G packets sent in %G seconds =>\n\t", [integer[sent]], [cardinal[seconds]]]; IO.PutF[data.log, "%1.2F packets/second.\n", [real[packetsPerSecond]] ]; IO.PutF[data.log, "%G bytes received in %G seconds =>\n\t", [integer[bytes]], [cardinal[seconds]]]; IO.PutF[data.log, "%1.0F bits/second => ", [real[8.0*bytes/seconds]] ]; IO.PutF[data.log, "%G ms/packet.\n", [real[msPerPacket]] ]; PrintPercent[data, data.good, sent, "Packets echoed OK"]; PrintPercent[data, mashed, sent, "Packets with bad data"]; PrintPercent[data, missed, sent, "Packets missed"]; PrintPercent[data, late, sent, "Late Packets"]; IO.Flush[data.log]; END; }; InitDelayHist: PROC [data: ClientData] = { data.minDelay _ INT.LAST; data.maxDelay _ INT.FIRST; data.delayHist _ ALL[0]; }; AddToDelayHist: PROC [data: ClientData, micro: INT] = { FOR d: DelayRange IN DelayRange DO -- Slow but clean IF delayTime[d] < micro THEN LOOP; data.delayHist[d] _ data.delayHist[d] + 1; EXIT; REPEAT FINISHED => ERROR; ENDLOOP; data.minDelay _ MIN[data.minDelay, micro]; data.maxDelay _ MAX[data.maxDelay, micro]; }; PrintDelayHist: PROC [data: ClientData] = { total: INT _ 0; IF data.good = 0 THEN RETURN; IO.PutF[data.log, " Incremental Cumulative Microseconds\n"]; FOR d: DelayRange IN DelayRange DO IF data.delayHist[d] = 0 THEN LOOP; total _ total + data.delayHist[d]; IO.PutF[data.log, "%8G", [integer[data.delayHist[d]]]]; IO.PutF[data.log, "%7.2F%%", [real[100.0*data.delayHist[d]/data.good]]]; IO.PutF[data.log, "%8G", [integer[total]]]; IO.PutF[data.log, "%7.2F%%", [real[100.0*total/data.good]]]; IO.PutF[data.log, "%9G\n", [integer[delayTime[d]]]]; ENDLOOP; IO.PutF[data.log, "The min delay was %G microseconds.\n", [integer[data.minDelay]]]; IO.PutF[data.log, "The max delay was %G microseconds.\n", [integer[data.maxDelay]]]; }; DateProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; target: ROPE = ViewerTools.GetContents[data.target]; Date[data, target]; }; Date: PROC [data: ClientData, target: ROPE] = TRUSTED { handle: UDP.Handle; dg: IPDefs.Datagram; body: LONG POINTER TO UDP.BodyRec; packetStart: BasicTime.Pulses; Report[data, "\nGetting Date from ", target, " "]; IF ~FindPath[data, target] THEN RETURN; handle _ UDP.Create[him: data.where, local: UDP.default, remote: UDP.date]; IF handle = NIL THEN {IO.PutF[data.log, "Can't make handle.\n"]; RETURN[]; }; dg _ NEW [IPDefs.DatagramRec]; body _ LOOPHOLE[@dg.data]; packetStart _ BasicTime.GetClockPulses[]; UDP.Send[handle, dg, UDP.minLength]; UNTIL (dg _ UDP.Receive[handle, 5000]) = NIL DO body _ LOOPHOLE[@dg.data]; { length: CARDINAL _ body.length-UDP.minLength; packetStop: BasicTime.Pulses _ BasicTime.GetClockPulses[]; microseconds: LONG CARDINAL _ BasicTime.PulsesToMicroseconds[packetStop-packetStart]; FOR i: CARDINAL IN [0..length) DO char: CHAR _ LOOPHOLE[body.data[UDP.minLength+i]]; IF char = '\l THEN LOOP; IO.PutChar[data.log, char]; ENDLOOP; IO.PutF[data.log, "\nThe flight time was %G ms.\n", [integer[microseconds/1000]]]; EXIT; }; ENDLOOP; UDP.Destroy[handle]; }; TimeProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; target: ROPE = ViewerTools.GetContents[data.target]; Time[data, target]; }; baseGmt: BasicTime.GMT; baseRaw: LONG CARDINAL; Time: PROC [data: ClientData, target: ROPE] = TRUSTED { handle: UDP.Handle; dg: IPDefs.Datagram; body: LONG POINTER TO UDP.BodyRec; packetStart: BasicTime.Pulses; Report[data, "\nGetting Time from ", target, " "]; IF ~FindPath[data, target] THEN RETURN; handle _ UDP.Create[him: data.where, local: UDP.default, remote: UDP.time]; IF handle = NIL THEN {IO.PutF[data.log, "Can't make handle.\n"]; RETURN[]; }; dg _ NEW [IPDefs.DatagramRec]; body _ LOOPHOLE[@dg.data]; packetStart _ BasicTime.GetClockPulses[]; UDP.Send[handle, dg, UDP.minLength]; UNTIL (dg _ UDP.Receive[handle, 5000]) = NIL DO body _ LOOPHOLE[@dg.data]; SELECT TRUE FROM (body.length # UDP.minLength+4) => IO.PutRope[data.log, "Wrong Length"]; ENDCASE => { now: BasicTime.GMT _ BasicTime.Now[]; packetStop: BasicTime.Pulses _ BasicTime.GetClockPulses[]; Flip: PROC [Pair] RETURNS [LONG CARDINAL] = TRUSTED MACHINE CODE BEGIN PrincOps.zEXCH; END; Pair: TYPE = MACHINE DEPENDENT RECORD [a, b: CARDINAL]; -- used for word swap flopped: LONG POINTER TO Pair _ LOOPHOLE[@body.data]; raw: LONG CARDINAL _ Flip[flopped^]; period: INT _ raw-baseRaw; him: BasicTime.GMT _ BasicTime.Update[base: baseGmt, period: period]; diff: INT _ BasicTime.Period[from: now, to: him]; microseconds: LONG CARDINAL _ BasicTime.PulsesToMicroseconds[packetStop-packetStart]; IO.PutF[data.log, "His time is %G.\n", [time[him]]]; IO.PutF[data.log, "Our time is %G.\n", [time[now]]]; IO.PutF[data.log, "The flight time was %G ms.\n", [integer[microseconds/1000]]]; IO.PutF[data.log, "His clock is %G seconds faster than ours.\n", [integer[diff]]]; EXIT; }; dg _ NIL; ENDLOOP; UDP.Destroy[handle]; }; PrintPercent: PROC [data: ClientData, x, sent: INT, name: ROPE] = { IF x = 0 THEN RETURN; IO.PutF[data.log, "%8G %7.2F%% %G\n", [integer[x]], [real[100.0*x/sent]], [rope[name]] ]; }; FindPath: PROC [data: ClientData, target: ROPE] RETURNS [BOOLEAN] = TRUSTED { where: LIST OF IPDefs.Address; IF IPName.LoadCacheFromName[target, TRUE, TRUE] = down THEN { IO.PutF[data.log, "Can't load name Cache."]; RETURN[FALSE]; }; where _ IPName.NameToAddress[target]; IF where = NIL THEN {IO.PutF[data.log, "Name not found."]; RETURN[FALSE]; }; data.where _ where.first; Report[data, "= ", IPName.AddressToName[data.where]]; Report[data, " = ", IPName.AddressToRope[data.where], ".\n"]; RETURN[TRUE]; }; Report: PROC [data: ClientData, r1,r2,r3,r4: ROPE _ NIL] = TRUSTED { IF r1 # NIL THEN {IO.PutRope[data.log, r1]}; IF r2 # NIL THEN {IO.PutRope[data.log, r2]}; IF r3 # NIL THEN {IO.PutRope[data.log, r3]}; IF r4 # NIL THEN {IO.PutRope[data.log, r4]}; }; MakeRule: PROC [parent, sibling: Viewer] RETURNS [child: Viewer] = { child _ Rules.Create[ info: [parent: parent, border: FALSE, wy: IF sibling = NIL THEN 0 ELSE sibling.wy + sibling.wh + 2, wx: 0, ww: parent.ww, wh: 1], paint: FALSE ]; Containers.ChildXBound[parent, child]; }; MakeButton: PROC [parent, sibling: Viewer, data: REF ANY, name: ROPE, proc: Buttons.ButtonProc] RETURNS[child: Viewer] = { child _ Buttons.Create[ info: [name: name, parent: parent, border: TRUE, wy: sibling.wy, wx: sibling.wx + buttonWidth - 1, ww: buttonWidth], proc: proc, clientData: data, fork: TRUE, paint: FALSE]; }; SelectorProc: TYPE = PROC [parent: Viewer, clientData: REF, value: ATOM]; Selector: TYPE = REF SelectorRec; SelectorRec: TYPE = RECORD [ value: REF ATOM, change: PROC [parent: Viewer, clientData: REF, value: ATOM], clientData: REF, buttons: LIST OF Buttons.Button, values: LIST OF ATOM ]; MakeSelector: PROC [name: ROPE, values: LIST OF ATOM, init: REF ATOM _ NIL, change: SelectorProc _ NIL, clientData: REF _ NIL, parent: Viewer, x, y: INTEGER] RETURNS [child: Viewer] = { selector: Selector _ NEW [SelectorRec _ [ value: IF init # NIL THEN init ELSE NEW [ATOM _ values.first], change: change, clientData: clientData, buttons: NIL, values: values ] ]; last: LIST OF Buttons.Button _ NIL; child _ Labels.Create[info: [name: name, parent: parent, border: FALSE, wx: x, wy: y] ]; FOR a: LIST OF ATOM _ values, a.rest UNTIL a = NIL DO child _ Buttons.Create[ info: [name: Atom.GetPName[a.first], parent: parent, border: TRUE, wx: child.wx + child.ww + 2, wy: child.wy], proc: SelectorHelper, clientData: selector, fork: TRUE, paint: TRUE]; IF last = NIL THEN last _ selector.buttons _ CONS[first: child, rest: NIL] ELSE { last.rest _ CONS[first: child, rest: NIL]; last _ last.rest }; IF a.first = selector.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack]; ENDLOOP; }; SelectorHelper: Buttons.ButtonProc = { self: Buttons.Button = NARROW[parent]; selector: Selector = NARROW[clientData]; buttons: LIST OF Buttons.Button _ selector.buttons; FOR a: LIST OF ATOM _ selector.values, a.rest UNTIL a = NIL DO IF self = buttons.first THEN { selector.value^ _ a.first; IF selector.change # NIL THEN selector.change[self.parent, selector.clientData, a.first]; Buttons.SetDisplayStyle[buttons.first, $WhiteOnBlack]; } ELSE Buttons.SetDisplayStyle[buttons.first, $BlackOnWhite]; buttons _ buttons.rest; ENDLOOP; }; BoolProc: TYPE = PROC [parent: Viewer, clientData: REF, value: BOOL]; Bool: TYPE = REF BoolRec; BoolRec: TYPE = RECORD [ value: REF BOOL, change: BoolProc, clientData: REF, button: Viewer ]; MakeBool: PROC [name: ROPE, init: REF BOOL, change: BoolProc _ NIL, clientData: REF _ NIL, parent: Viewer, x, y: INTEGER] RETURNS [child: Viewer] = { bool: Bool _ NEW [BoolRec _ [ value: IF init # NIL THEN init ELSE NEW [BOOL _ TRUE], change: change, clientData: clientData, button: NIL ] ]; child _ Buttons.Create[ info: [name: name, parent: parent, border: TRUE, wx: x, wy: y], proc: BoolHelper, clientData: bool, fork: TRUE, paint: TRUE]; bool.button _ child; IF bool.value^ THEN Buttons.SetDisplayStyle[child, $WhiteOnBlack]; }; BoolHelper: Buttons.ButtonProc = { self: Buttons.Button = NARROW[parent]; bool: Bool = NARROW[clientData]; bool.value^ _ ~bool.value^; IF bool.value^ THEN Buttons.SetDisplayStyle[bool.button, $WhiteOnBlack] ELSE Buttons.SetDisplayStyle[bool.button, $BlackOnWhite]; IF bool.change # NIL THEN bool.change[self.parent, bool.clientData, bool.value^]; }; MakeLabel: PROC [parent, sibling: Viewer, name: ROPE] RETURNS [child: Viewer] = { child _ Labels.Create[ info: [name: name, parent: parent, border: FALSE, wy: sibling.wy + sibling.wh + (IF sibling.class.flavor = $Button THEN -1 ELSE 2), wx: 2], paint: FALSE ]; }; MakeLabeledText: PROC [ parent, sibling: Viewer, name, data: ROPE, prev: Viewer, newline: BOOL _ TRUE] RETURNS [child: Viewer] = { x: INTEGER = IF newline THEN 2 ELSE sibling.wx + sibling.ww + 10; y: INTEGER = IF newline THEN sibling.wy + sibling.wh + 1 ELSE sibling.wy; child _ ViewerTools.MakeNewTextViewer[ info: [parent: parent, wh: buttonHeight, ww: 999, scrollable: TRUE, data: IF prev = NIL THEN data ELSE ViewerTools.GetContents[prev], border: FALSE, wx: x + buttonWidth + 2, wy: y], paint: FALSE ]; Containers.ChildXBound[parent, child]; [] _ Buttons.Create[ info: [name: name, parent: parent, wh: buttonHeight, border: FALSE, wx: x, wy: y], proc: LabeledTextProc, clientData: child, fork: FALSE, paint: FALSE]; RETURN[child]; }; LabeledTextProc: Buttons.ButtonProc = { text: Viewer = NARROW[clientData]; SELECT mouseButton FROM red => ViewerTools.SetSelection[text, NIL]; yellow => NULL; blue => { ViewerTools.SetContents[text, NIL]; ViewerTools.SetSelection[text, NIL] }; ENDCASE => ERROR; }; Commander.Register["UDPEchoTool", Create, "Echo UDP packets to the Arpa Internet."]; baseGmt _ Convert.TimeFromRope["1-Jan-70 00:00:00 GMT"]; baseRaw _ 2208988800; END. μUDPEchoTool.mesa Hal Murray June 26, 1985 11:19:07 pm PDT John Larson, July 12, 1987 2:23:52 am PDT Viewer layout parameters parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL ΚΈ˜šœ™Icode™(K™)—J˜šΟk ˜ Jšœœ ˜Jšœ œœ5˜YJšœœ8˜EJšœ œ˜(Jšœ œ$˜4Jšœœ1˜>Jšœœ!œ ˜8Jšœœ ˜Jšœœ˜Jšœ œ ˜Jšœœœ˜Jšœœ ˜Jšœ œ˜'Jšœœ ˜Jšœ œ˜%Jšœ œM˜\Jšœ œ=˜NJšœœX˜dJšœœB˜Nšœœd˜mJ˜——šΟn œœ˜š˜Jšœ:œOœ˜—Jš˜J˜Jšœœœ˜Jšœœœœ˜Jšœœ˜%™Jšœœ˜Jšœ œ˜—J˜Jšœ œœ˜%šœœœ˜Jšœœœ˜Jšœœœ˜Jšœ œœ˜Jšœœœ˜Jšœ+˜+Jš œœœœœœ˜#Jš œœœœœœ˜#Jš œœœœœœ˜#Jš œ œœœœœ˜+Jšœ'œ˜+Jšœœ˜Jšœœ˜Jš œ œ œœœ˜.—J˜šœ œ˜JšœQ˜QJšœA˜AJšœP˜P—J˜šœ œ œœ˜&Jšœ)˜)Jšœ)˜)Jšœ)˜)Jšœ)˜)Jšœ˜JšœG˜GJšœS˜SJšœ_˜_JšœNœœ˜Y—J˜šΠbnœœ˜)Jšœœ˜+Jšœœ˜#šœ!˜!J˜Jšœ4œœ˜O—Jšœ*˜*J˜šœΟc˜˜&šœ0œ˜6J˜—Jš œœœœ œ˜7—J˜J˜Jšœ˜—šœ˜Jšœ<œœ˜K—šœ4˜4JšœTœ˜[—Jšœ$˜$Jšœ$˜$Jšœ!˜!Jšœ ˜ JšœN˜PJšœ˜J˜—šž œœœ+˜DJšœœ˜šœ!˜!Jšœœœ'˜^—J˜%JšœY˜YJšœw˜wJšœw˜wJšœˆ˜ˆšœ&˜&J˜ J˜J˜Jšœœ ˜)Jšœ˜Jšœ œ˜—Jšœ˜šœ&˜&J˜ J˜J˜J˜Jšœ˜—J˜J˜(Jšœ8˜8Jšœ8˜8Jšœ:˜:Jšœ8˜8Jšœ8˜8J˜šœ˜Jšœœ˜Jšœœ˜)Kšœnœ˜uJ˜CJšœœ(˜>JšœR˜R—J˜J˜—šŸ œ˜!JšœL™LJšœœ ˜&Jšœ˜J˜—šžœœ˜"Jšœœ(˜4Jšœ œœ ˜#Jšœ+˜+Jšœœœ˜'Jšœœ˜Jšœ œ˜J˜—šŸœ˜ JšœL™LJšœœ ˜&Jšœ ˜ Jšœ˜J˜—šŸœ˜ JšœL™LJšœœ ˜&Jšœ˜J˜—šžœœœ˜)Jšœœ˜Jšœ œœœ ˜'Jšœ œ˜J˜—šžœœœ˜)Jšœœ˜Jšœœ˜$Jšœœ˜Jšœœ˜Jšœ œ˜&Jšœ œ(˜8Jšœœ˜Jšœ œœ˜1Jšœ œœ ˜$Jšœ œœ ˜%Jšœ œ˜Jš œœ œ œœ˜WJš œ œœœ)œ˜Mšœ œ˜Jšœœ˜ šœ:˜:Jšœ/˜1Jšœ˜Jšœ˜—Jšœœ˜Jšœœ˜Jšœ˜JšœJ˜L—J˜J˜J˜šœ˜š œœœœ˜FJšœ*˜*Jšœœ˜/Jš œœœ œ œ˜=Icode2š œœœœœ œ ˜7Lš œœœœœœ ˜7Jšœ&œ˜,šœœœ˜)Jšœœœ˜#—Jšœ)˜)Lšœ˜J˜šœœœ˜2Lšœœ ˜Lšœœ ˜šœœ˜šœœ˜4Jš œœ œœœ˜I—šœ˜ Jšœ(˜(JšœM˜Mšœœœ˜)šœœœ˜#Jšœ˜Jšœ˜Jšœ˜—Jšœ˜—Jšœ œœ˜-Jšœ˜Jšœ!˜!Jšœ˜——Jšœœ˜ Jšœ˜—Jšœœœ˜Jšœœ œœ˜LJšœ˜—Jš œ œœœœ˜NJšœ˜—Jšœ˜Jšœ˜Jšœ˜Jš˜Jšœœ˜$Jšœ œœ-˜CJšœ œ ˜ Jšœœœ ˜,Jšœœ˜%Jšœ(˜,Jšœ^˜`JšœF˜HJšœa˜cJšœE˜GJšœ9˜;Jšœ9˜9J˜:J˜3J˜/Jšœ˜Jšœ˜J˜—šž œœ˜*Jšœœœ˜Jšœœœ˜Jšœœ˜J˜—šžœœœ˜7šœœ œ ˜5Jšœœœ˜"Jšœ*˜*Jšœ˜Jšœœœ˜Jšœ˜Jšœœ˜*Jšœœ˜-J˜——šžœœ˜+Jšœœ˜Jšœœœ˜JšœE˜Gšœœ ˜"Jšœœœ˜#Jšœ"˜"Jšœ5˜7JšœF˜HJšœ)˜+Jšœ:˜—Jšœ%˜%Jš œ œœœ$œœ˜LJ˜Jšœ5˜5Jšœ=˜=Jšœœ˜J˜—š žœœ!œœœ˜DJšœœœœ˜,Jšœœœœ˜,Jšœœœœ˜,Jšœœœœ˜/—J˜šžœœœ˜D˜šœœ˜%Jš œœ œœœ;˜[—Jšœœ˜—Jšœ)˜)J˜—š ž œœ!œœœœ˜z˜šœ+œ˜0J˜C—J˜ J˜Jšœœ˜ Jšœœ˜—J˜—Jš œœœœ œ˜IJšœ œœ ˜!šœ œœ˜Jšœœœ˜Jšœœœ œ˜J˜J˜Jšœ œ˜ J˜—Jšœœœœ˜#JšœAœ˜Xš œœœœœœ˜5šœ˜Kšœ=œ-˜nJšœ2œ œ˜E—Jš œœœœœ˜JJšœœœ˜EJšœœ/˜PJšœ˜ ——J˜šŸœ˜&JšœL™LJšœœ ˜&Jšœœ ˜(Jšœ œœ#˜3š œœœœœœ˜>šœœ˜J˜Jšœœœ<˜YJšœ8˜8—Jšœ7˜;J˜Jšœ˜ ——J˜Jš œ œœœ œ˜EJšœœœ ˜šœ œœ˜Jšœœœ˜Jšœ˜Jšœ œ˜Jšœ˜—J˜šžœ˜Kšœœœœœœœœ˜jKšœ˜šœ œ ˜Jšœœœœœœœœ˜6J˜Jšœ˜Jšœœ˜—šœ˜Kšœ+œ˜?Jšœ*œ œ˜=—Jšœ˜Jšœ œ2˜E—J˜šŸ œ˜"JšœL™LJšœœ ˜&Jšœ œ ˜ Jšœ˜Jšœ œ4˜GJšœ5˜9Jšœœœ<˜U—šž œœ!œœ˜Q˜šœ+œ˜1Jšœœ œœ˜QJ˜—Jšœœ˜——J˜šžœœ˜Jš œ%œœœœ˜jJš œœœ œœ˜AJš œœœ œœ ˜I˜&šœ>œ˜CJš œœœœœ˜AJšœœ˜J˜ —Jšœœ˜—Jšœ&˜&˜Jšœ=œ˜RJšœ0œ œ˜E—Jšœ ˜J˜—šŸœ˜'JšœL™LJšœœ ˜"šœ ˜Jšœ&œ˜+Jšœ œ˜Jšœ(œ"œ˜TJšœœ˜——J˜šœT˜TJšœ8˜8Jšœ˜J˜—Jšœ˜J˜J˜J˜—J˜—…—MžgB