DIRECTORY Atom USING [GetPName], Basics USING [bytesPerWord, DivMod], BasicTime USING [GetClockPulses, GMT, Now, Period, Pulses, PulsesToMicroseconds], Buttons USING [Button, ButtonProc, Create, SetDisplayStyle], Commander USING [CommandProc, Register], Containers USING [ChildXBound, ChildYBound, Create], Convert USING [Error, IntFromRope, RopeFromInt], CommDriver USING [GetNetworkChain, Network], Endian USING [bytesPerFWord, FFromCard, FFromInt, FWORD, IntFromF], EthernetDriverStats USING [EtherStats, MaxTries], IO USING [Close, Flush, PutF, PutRope, STREAM, Value], Labels USING [Create], Loader USING [BCDBuildTime], PrincOpsUtils USING [LongCopy], Process USING [MsecToTicks, Pause, priorityForeground, SetPriority, Ticks, TicksToMsec], Pup USING [Address, nullAddress], PupBuffer USING [Buffer, RoutingInfoResponse, echoStatsVersion, maxDataBytes], PupHop USING [GetRouting, Hop, RoutingTableEntry, unreachable], PupName USING [AddressToRope, Error, HisName, NameLookup], PupSocket USING [AllocBuffer, CreateEphemeral, CreateServer, Destroy, ExtractErrorRope, FreeBuffer, Get, GetUniqueID, GetUserBytes, Kick, Put, ReturnToSender, SetNoErrors, SetUserBytes, SetUserSize, Socket, waitForever], PupSocketBackdoor USING [PutAgain, PutFirst, Resend, ReturnToSenderNoFree, SetDirectReceive, UseNormalPath], PupType USING [echoStatsRequest, echoStatsReply], PupWKS USING [echo], Rope USING [ROPE], Rules USING [Create], TypeScript USING [ChangeLooks, Create], VFonts USING [FontHeight, StringWidth], ViewerClasses USING [Viewer], ViewerEvents USING [EventProc, RegisterEventProc], ViewerIO USING [CreateViewerStreams], ViewerOps USING [AddProp, ComputeColumn, CreateViewer, FetchProp, MoveViewer, OpenIcon, SetOpenHeight], ViewerTools USING [GetContents, MakeNewTextViewer, SetContents, SetSelection]; PupEchoTool: CEDAR MONITOR IMPORTS Atom, Basics, BasicTime, Buttons, Commander, CommDriver, Containers, Convert, Endian, IO, Labels, Loader, PrincOpsUtils, Process, PupHop, PupName, PupSocket, PupSocketBackdoor, Rules, TypeScript, VFonts, ViewerEvents, ViewerIO, ViewerOps, ViewerTools = { BYTE: TYPE = [0..100H); Buffer: TYPE = PupBuffer.Buffer; Socket: TYPE = PupSocket.Socket; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Viewer: TYPE = ViewerClasses.Viewer; bytesPerFWord: NAT = Endian.bytesPerFWord; bytesPerWord: NAT = Basics.bytesPerWord; buttonHeight: INT _ VFonts.FontHeight[] + 3; buttonWidth: INT _ VFonts.StringWidth["Route"] + 2*3; pupsEchoed: INT _ 0; serverLog: STREAM _ NIL; pleaseStopServer: BOOLEAN _ FALSE; server: PROCESS _ NIL; showDollar: REF BOOL _ NEW [BOOL _ FALSE]; serverSoc: Socket _ NIL; ClientData: TYPE = REF ClientDataRep; ClientDataRep: TYPE = RECORD [ log: STREAM _ NIL, in: STREAM _ NIL, pleaseStop: BOOLEAN _ FALSE, user: PROCESS _ NIL, where: Pup.Address _ Pup.nullAddress, echo: REF BOOL _ NEW [BOOL _ TRUE], dot: REF BOOL _ NEW [BOOL _ TRUE], miss: REF BOOL _ NEW [BOOL _ TRUE], late: REF BOOL _ NEW [BOOL _ TRUE], dataChecking: REF BOOL _ NEW [BOOL _ TRUE], fixedLength: REF BOOL _ NEW [BOOL _ FALSE], target, length, timeout, dally: Viewer _ NIL, good, error, funny: 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]]; global: ClientData _ NIL; -- debugging Create: Commander.CommandProc = { viewer, buttons, log: Viewer _ NIL; data: ClientData _ NEW[ClientDataRep _ []]; global _ data; viewer _ ViewerOps.CreateViewer [ flavor: $Container, info: [name: "PupEchoTool", column: right, iconic: TRUE, scrollable: FALSE]]; [] _ ViewerEvents.RegisterEventProc[Poof, destroy, viewer, TRUE]; ViewerOps.AddProp[viewer, $PupEchoTool, data]; log _ TypeScript.Create[ [name: "PupEchoTool.log", wy: 27+4, parent: viewer, border: FALSE], FALSE]; [data.in, data.log] _ ViewerIO.CreateViewerStreams [ name: "PupEchoTool.log", backingFile: "PupEchoTool.log", viewer: log, editedStream: FALSE]; IF serverLog = NIL THEN { serverLog _ data.log; StartServer[]; }; Containers.ChildXBound[viewer, log]; Containers.ChildYBound[viewer, log]; CreateButtons[data, viewer, log]; TypeScript.ChangeLooks[log, 'f]; IO.PutF[data.log, "PupEchoTool 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.dot, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy]; 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: "$", init: showDollar, clientData: data, parent: kids, x: child.wx + child.ww + 10, y: child.wy]; child _ MakeBool[name: "D-ck", init: data.dataChecking, 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[PupBuffer.maxDataBytes], prev: data.length, width: VFonts.StringWidth["10000"], newline: FALSE ]; child _ data.timeout _ MakeLabeledText[ parent: kids, sibling: child, name: "Timeout:", data: "2500", prev: data.timeout, width: VFonts.StringWidth["10000"], newline: FALSE ]; child _ data.dally _ MakeLabeledText[ parent: kids, sibling: child, name: "Dally:", data: "0", prev: data.dally, width: VFonts.StringWidth["10000"], newline: FALSE ]; child _ MakeRule[kids, child]; child _ data.target _ MakeLabeledText[ parent: kids, sibling: child, name: "Target:", data: "Target", width: VFonts.StringWidth["Big long name ................................."], prev: data.target ]; child _ MakeRule[kids, child]; child _ MakeLabel[kids, child, "What: "]; child _ MakeButton[kids, child, data, "Poke", PokeProc]; child _ MakeButton[kids, child, data, "Start", StartProc]; child _ MakeButton[kids, child, data, "Blast", BlastProc, TRUE]; child _ MakeButton[kids, child, data, "Hist", HistProc]; child _ MakeButton[kids, child, data, "Stop", StopProc]; child _ MakeButton[kids, child, data, "Route", RouteProc]; child _ MakeButton[kids, child, data, "Stats", StatsProc]; 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]; }; }; Poof: ViewerEvents.EventProc = { data: ClientData _ NARROW[ViewerOps.FetchProp[viewer, $PupEchoTool]]; IF event # destroy OR before # TRUE THEN ERROR; Stop[data]; IF data.log = serverLog THEN { StopServer[]; serverLog _ NIL; }; IO.Close[data.log]; IO.Close[data.in]; }; StartServer: ENTRY PROC = { IF server # NIL THEN RETURN; pleaseStopServer _ FALSE; server _ FORK Server[]; }; StopServer: ENTRY PROC = { IF server = NIL THEN RETURN; pleaseStopServer _ TRUE; PupSocket.Kick[serverSoc]; TRUSTED { JOIN server; }; server _ NIL; serverSoc _ NIL; }; Server: PROC = { serverSoc _ PupSocket.CreateServer[ local: PupWKS.echo, recvBuffers: 99, getTimeout: PupSocket.waitForever]; PupSocketBackdoor.SetDirectReceive[serverSoc, Mirror, NIL]; UNTIL pleaseStopServer DO b: Buffer _ PupSocket.Get[serverSoc]; IF b = NIL THEN LOOP; SELECT b.type FROM echoMe => { b.type _ iAmEcho; PupSocket.ReturnToSender[b]; BumpEchoed[]; IF showDollar^ THEN IO.PutRope[serverLog, "$"]; }; PupType.echoStatsRequest => { b.echoStats _ [ version: PupBuffer.echoStatsVersion, pupsEchoed: Endian.FFromCard[pupsEchoed]]; b.type _ PupType.echoStatsReply; PupSocket.SetUserSize[b, SIZE[PupBuffer.RoutingInfoResponse]]; PupSocket.ReturnToSender[b]; }; ENDCASE => PupSocket.FreeBuffer[b]; ENDLOOP; PupSocket.SetNoErrors[serverSoc]; PupSocket.Destroy[serverSoc]; }; BumpEchoed: ENTRY PROC = { pupsEchoed _ pupsEchoed.SUCC; }; Mirror: PROC [socket: Socket, b: Buffer, user: REF ANY] RETURNS [Buffer] = { SELECT b.type FROM echoMe => { IF showDollar^ THEN {PupSocketBackdoor.UseNormalPath[b]; RETURN[NIL]; }; b.type _ iAmEcho; PupSocketBackdoor.ReturnToSenderNoFree[b]; BumpEchoed[]; }; PupType.echoStatsRequest => { b.echoStats _ [ version: PupBuffer.echoStatsVersion, pupsEchoed: Endian.FFromCard[pupsEchoed]]; b.type _ PupType.echoStatsReply; PupSocket.SetUserSize[b, SIZE[PupBuffer.RoutingInfoResponse]]; PupSocketBackdoor.ReturnToSenderNoFree[b]; }; ENDCASE => NULL; RETURN[b]; }; StartProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; overlap: NAT; SELECT TRUE FROM shift AND control => overlap _ 5; control => overlap _ 2; shift => overlap _ 1; ENDCASE => overlap _ 0; Start[data, overlap]; }; Start: PROC [data: ClientData, overlap: NAT] = { target: ROPE = ViewerTools.GetContents[data.target]; IF data.user # NIL THEN Stop[data]; IO.PutF[data.log, "\nEchoing to %G", [rope[target]]]; IF ~FindPath[data, target] THEN RETURN; IF overlap # 0 THEN IO.PutF[data.log, "%g packets will be kept in flight.\n", [integer[overlap+1]]]; data.pleaseStop _ FALSE; data.user _ FORK User[data, overlap]; }; BlastProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; overlap: NAT; SELECT TRUE FROM shift AND control => overlap _ 6; control => overlap _ 3; shift => overlap _ 2; ENDCASE => overlap _ 1; Blast[data, overlap]; }; Blast: PROC [data: ClientData, overlap: NAT] = { target: ROPE = ViewerTools.GetContents[data.target]; IF data.user # NIL THEN Stop[data]; IO.PutF[data.log, "\nBlasting Pups to %G", [rope[target]]]; IF ~FindPath[data, target] THEN RETURN; IF overlap # 0 THEN IO.PutF[data.log, "%g packets will be kept in flight.\n", [integer[overlap]]]; data.pleaseStop _ FALSE; data.user _ FORK Blaster[data, overlap]; }; 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; }; RouteProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Route[data]; }; Route: PROC [data: ClientData] = { PrintOne: PROC [net: BYTE] = { rte: PupHop.RoutingTableEntry _ PupHop.GetRouting[[net]]; IF rte.hop = PupHop.unreachable THEN RETURN; nets _ nets + 1; IF k = 0 THEN IO.PutF[data.log, "|"]; IO.PutF[data.log, "%3B%4B#%3B#%4D |", [integer[net]], [integer[rte.immediate.net]], [integer[rte.immediate.host]], [integer[rte.hop]] ]; IF (k _ k + 1) = 3 THEN { IO.PutF[data.log, "\n"]; k _ 0; }; }; k, nets: INT _ 0; IO.PutF[data.log, "\n%G\n", [time[BasicTime.Now[]]] ]; IO.PutF[data.log, "Local Pup Routing Table.\n"]; IO.PutF[data.log, "| Net Via Hops | Net Via Hops | Net Via Hops |\n"]; IO.PutF[data.log, "|-----------------|-----------------|-----------------|\n"]; FOR net: BYTE IN BYTE DO PrintOne[net]; ENDLOOP; IF k # 0 THEN IO.PutF[data.log, "\n"]; IF nets > 1 THEN IO.PutF[data.log, "There are %D active networks.\n", [integer[nets]]]; }; StatsProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; Stats[data]; }; Stats: PROC [data: ClientData] = { first: CommDriver.Network _ CommDriver.GetNetworkChain[]; IO.PutF[data.log, "\n%G\n", [time[BasicTime.Now[]]] ]; FOR network: CommDriver.Network _ first, network.next UNTIL network = NIL DO SELECT network.type FROM ethernet, ethernetOne => { stats: EthernetDriverStats.EtherStats _ NARROW[network.stats]; IO.PutF[data.log, "Ethernet"]; IF network.type = ethernetOne THEN IO.PutF[data.log, "One"]; IO.PutF[data.log, " Statistics from %B#%B#.\n", [integer[network.pup.net]], [integer[network.pup.host]] ]; IO.PutF[data.log, " Rcv: pkts %G, words %G, bad %G, missed %G\n", [integer[stats.packetsRecv]], [integer[stats.wordsRecv]], [integer[stats.badRecvStatus]], [integer[stats.inputOff]]]; IO.PutF[data.log, " Xmit: pkts %G, words %G, bad %G\n", [integer[stats.packetsSent]], [integer[stats.wordsSent]], [integer[stats.badSendStatus]]]; IO.PutF[data.log, " Lds:"]; FOR i: NAT IN [0..EthernetDriverStats.MaxTries] DO IO.PutF[data.log, " %G", [integer[stats.loadTable[i]]]]; ENDLOOP; IO.PutF[data.log, "\n"]; }; ENDCASE => NULL; ENDLOOP; }; User: PROC [data: ClientData, overlap: NAT] = { ENABLE UNWIND => NULL; start, finish: BasicTime.GMT; sent, mashed, missed, late, error, funny: INT _ 0; bytes: INT _ 0; sendPacketNumber: INT _ 0; recvPacketNumber: INT _ 0; dataChecking: BOOL = data.dataChecking^; fixedLength: BOOL = data.fixedLength^; lengthText: ROPE = ViewerTools.GetContents[data.length]; timeoutText: ROPE = ViewerTools.GetContents[data.timeout]; dallyText: ROPE = ViewerTools.GetContents[data.dally]; lengthCard: CARDINAL _ 0; timeoutCard: CARDINAL _ 3000; dallyCard: CARDINAL _ 0; dallyTicks: Process.Ticks; socket: Socket; sizeOfStartArray: NAT = 64; packetStart: ARRAY [0..sizeOfStartArray) OF BasicTime.Pulses; cond: CONDITION; grabber: PROCESS; longest: CARDINAL _ PupBuffer.maxDataBytes; first: BOOL _ TRUE; Kick: ENTRY PROC = TRUSTED {NOTIFY cond; }; Wait: ENTRY PROC = TRUSTED {WAIT cond; }; Grabber: PROC = TRUSTED { DO b: Buffer _ PupSocket.Get[socket]; start, packetStop: BasicTime.Pulses; recvStartIndex: NAT; length, pupLength: CARDINAL; arrivedPacketNumber: INT; IF b = NIL THEN { IF data.pleaseStop THEN EXIT; -- Wait for last packet to keep stats clean first _ TRUE; missed _ missed + 1; IF data.miss^ THEN IO.PutRope[data.log, "?"]; recvPacketNumber _ recvPacketNumber + 1; Kick[]; LOOP; }; IF b.type = error THEN { error _ error + 1; PrintErrorPup[data, b]; PupSocket.FreeBuffer[b]; Kick[]; LOOP;}; IF b.type # iAmEcho THEN { funny _ funny + 1; IO.PutRope[data.log, "%"]; PupSocket.FreeBuffer[b]; Kick[]; LOOP;}; arrivedPacketNumber _ Endian.IntFromF[b.id]; IF (recvPacketNumber-arrivedPacketNumber) > 0 THEN { late _ late + 1; IF data.late^ THEN IO.PutRope[data.log, "#"]; PupSocket.FreeBuffer[b]; Kick[]; LOOP; }; UNTIL arrivedPacketNumber = recvPacketNumber DO missed _ missed + 1; IF data.miss^ THEN IO.PutRope[data.log, "?"]; recvPacketNumber _ recvPacketNumber + 1; ENDLOOP; length _ lengthCard; IF ~fixedLength AND longest > 0 THEN length _ CARDINAL[recvPacketNumber MOD longest]; pupLength _ PupSocket.GetUserBytes[b]; IF pupLength # length THEN { funny _ funny + 1; IO.PutRope[data.log, "%"]; PupSocket.FreeBuffer[b]; Kick[]; LOOP; }; packetStop _ BasicTime.GetClockPulses[]; recvStartIndex _ LOOPHOLE[recvPacketNumber, LONG CARDINAL] MOD sizeOfStartArray; start _ packetStart[recvStartIndex]; AddToDelayHist[data, BasicTime.PulsesToMicroseconds[packetStop-start]]; TRUSTED { found: LONG POINTER TO FWordPattern = LOOPHOLE[@b.body]; fwords: NAT; bytes: NAT; IF ~dataChecking THEN GOTO Good; [quotient: fwords, remainder: bytes] _ Basics.DivMod[num: length, den: bytesPerFWord]; FOR k: NAT IN [0..fwords) DO IF found[k] # fWordPattern[k] THEN GOTO Mashed; ENDLOOP; FOR k: NAT IN [length-bytes..length) DO IF b.byte[k] # bytePattern[k] THEN GOTO Mashed; REPEAT FINISHED => GOTO Good; ENDLOOP; EXITS Mashed => { mashed _ mashed + 1; IO.PutRope[data.log, "~"]; }; Good => { data.good _ data.good + 1; bytes _ bytes + length; recvPacketNumber _ recvPacketNumber + 1; IF data.echo^ THEN IO.PutRope[data.log, "!"]; IF recvPacketNumber MOD 1000 = 0 AND data.dot^ THEN { IF data.echo^ THEN IO.PutRope[data.log, "\n"] ELSE IO.PutRope[data.log, "."]; }; }; }; PupSocket.FreeBuffer[b]; Kick[]; IF data.pleaseStop AND recvPacketNumber = sendPacketNumber THEN EXIT; ENDLOOP; Kick[]; }; b: Buffer; IF TRUE 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, 0]; temp _ MIN[temp, longest]; longest _ lengthCard _ temp; IF fixedLength THEN IO.PutF[data.log, "Packet length is %G bytes.\n", [integer[lengthCard]]]; }; IF dallyText # NIL THEN { temp: INT; temp _ Convert.IntFromRope[dallyText ! Convert.Error => { IO.PutF[data.log, "Can't parse Dally field.\n"]; CONTINUE; } ]; temp _ MAX[temp, 0]; temp _ MIN[temp, 60000]; dallyCard _ temp; IF dallyCard # 0 THEN { dallyTicks _ Process.MsecToTicks[dallyCard]; dallyCard _ Process.TicksToMsec[dallyTicks]; IO.PutF[data.log, "Will dally %G ms after sending each packet.\n", [integer[dallyCard]]]; }; }; IF timeoutText # NIL THEN { temp: INT; temp _ Convert.IntFromRope[timeoutText ! Convert.Error => { IO.PutF[data.log, "Can't parse Timeout field.\n"]; CONTINUE; } ]; temp _ MAX[temp, 0]; temp _ MIN[temp, 60000]; timeoutCard _ temp; }; timeoutCard _ timeoutCard + dallyCard; InitDelayHist[data]; data.good _ 0; socket _ PupSocket.CreateEphemeral[ remote: data.where, sendBuffers: 25, recvBuffers: 100, getTimeout: timeoutCard]; TRUSTED { grabber _ FORK Grabber[]; }; start _ BasicTime.Now[]; b _ PupSocket.AllocBuffer[socket]; UNTIL data.pleaseStop DO length: CARDINAL _ lengthCard; sendStartIndex: NAT; IF ~fixedLength AND longest > 0 THEN length _ CARDINAL[sendPacketNumber MOD longest]; b.id _ Endian.FFromInt[sendPacketNumber]; b.type _ echoMe; TRUSTED { words: CARDINAL = (length+bytesPerWord-1)/bytesPerWord; PrincOpsUtils.LongCopy[to: @b.body, nwords: words, from: LOOPHOLE[bytePattern]]; }; PupSocket.SetUserBytes[b, length]; sendStartIndex _ LOOPHOLE[sendPacketNumber, LONG CARDINAL] MOD sizeOfStartArray; packetStart[sendStartIndex] _ BasicTime.GetClockPulses[]; IF first THEN PupSocketBackdoor.PutFirst[socket, b] ELSE { first _ FALSE; PupSocketBackdoor.PutAgain[socket, b]; }; sent _ sent + 1; sendPacketNumber _ sendPacketNumber + 1; IF dallyCard # 0 THEN Process.Pause[dallyTicks]; WHILE (sendPacketNumber-recvPacketNumber-overlap) > 0 DO IF data.pleaseStop THEN EXIT; Wait[]; ENDLOOP; ENDLOOP; IO.PutRope[data.log, "\n"]; IF recvPacketNumber = sendPacketNumber THEN PupSocket.Kick[socket]; PupSocket.FreeBuffer[b]; finish _ BasicTime.Now[]; TRUSTED { JOIN grabber; }; PupSocket.SetNoErrors[serverSoc]; PupSocket.Destroy[socket]; { 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, "%8G packets sent in %G seconds.\n", [integer[sent]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F packets/second.\n", [real[packetsPerSecond]] ]; IO.PutF[data.log, "%8G bytes received in %G seconds.\n", [integer[bytes]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.0F bits/second.\n", [real[8.0*bytes/seconds]] ]; IO.PutF[data.log, "%8.2F 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"]; PrintPercent[data, error, sent, "Error Packets"]; PrintPercent[data, funny, sent, "Funny Packets"]; IO.Flush[data.log]; }; }; Grabber: PROC [socket: Socket, b: Buffer, user: REF] RETURNS [Buffer] = TRUSTED { data: ClientData = NARROW[user]; SELECT b.type FROM iAmEcho => IF GotAnother[data] MOD 1000 = 0 AND data.echo^ THEN { PupSocketBackdoor.UseNormalPath[b]; RETURN[NIL]; }; ENDCASE => { PupSocketBackdoor.UseNormalPath[b]; RETURN[NIL]; }; RETURN[b]; }; GotAnother: ENTRY PROC [data: ClientData] RETURNS [INT] = INLINE { RETURN[data.good _ data.good + 1]; }; Blaster: PROC [data: ClientData, overlap: NAT] = { ENABLE UNWIND => NULL; start, finish: BasicTime.GMT; sent: INT _ 0; id: INT _ 0; lengthText: ROPE = ViewerTools.GetContents[data.length]; socket: Socket; maxFlingers: NAT = 6; flinger: ARRAY [0..maxFlingers) OF PROCESS; length: NAT _ PupBuffer.maxDataBytes; NextId: ENTRY PROC RETURNS [next: INT] = INLINE { next _ id; id _ id + 1; }; SentAnother: ENTRY PROC RETURNS [print: BOOL] = INLINE { sent _ sent + 1; IF sent MOD 1000 = 0 THEN RETURN[TRUE]; RETURN[FALSE]; }; Flinger: PROC = TRUSTED { b: Buffer _ PupSocket.AllocBuffer[socket]; UNTIL data.pleaseStop DO id: INT _ NextId[]; b.id _ Endian.FFromInt[id]; b.type _ echoMe; TRUSTED { words: CARDINAL = (length+bytesPerWord-1)/bytesPerWord; PrincOpsUtils.LongCopy[to: @b.body, nwords: words, from: LOOPHOLE[bytePattern]]; }; PupSocket.SetUserBytes[b, length]; IF id = 0 THEN PupSocketBackdoor.PutFirst[socket, b] ELSE PupSocketBackdoor.Resend[b]; IF SentAnother[] AND data.echo^ THEN IO.PutRope[data.log, "s"]; ENDLOOP; Process.Pause[2]; PupSocket.Kick[socket]; PupSocket.FreeBuffer[b]; }; IF TRUE THEN { temp: INT; temp _ Convert.IntFromRope[lengthText ! Convert.Error => { IO.PutF[data.log, "Can't parse Length field.\n"]; length _ 0; CONTINUE; } ]; temp _ MAX[temp, 0]; temp _ MIN[temp, length]; length _ temp; IO.PutF[data.log, "Packet length is %G bytes.\n", [integer[length]]]; }; InitDelayHist[data]; data.good _ data.error _ data.funny _ 0; socket _ PupSocket.CreateEphemeral[ remote: data.where, sendBuffers: 25, recvBuffers: 100, getTimeout: 1000]; PupSocketBackdoor.SetDirectReceive[socket, Grabber, data]; start _ BasicTime.Now[]; FOR i: NAT IN [0..overlap) DO TRUSTED { flinger[i] _ FORK Flinger[]; }; ENDLOOP; UNTIL data.pleaseStop DO b: Buffer _ PupSocket.Get[socket]; IF b = NIL THEN LOOP; SELECT b.type FROM error => { data.error _ data.error + 1; PrintErrorPup[data, b]; }; iAmEcho => IO.PutRope[data.log, "r"]; ENDCASE => { data.funny _ data.funny + 1; IO.PutRope[data.log, "%"]; }; PupSocket.FreeBuffer[b]; ENDLOOP; IO.PutRope[data.log, "\n"]; FOR i: NAT IN [0..overlap) DO TRUSTED { JOIN flinger[i]; }; ENDLOOP; finish _ BasicTime.Now[]; PupSocket.SetNoErrors[serverSoc]; PupSocket.Destroy[socket]; { 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, "%8G packets sent in %G seconds.\n", [integer[sent]], [cardinal[seconds]]]; IO.PutF[data.log, "%8.2F packets/second.\n", [real[packetsPerSecond]] ]; IO.PutF[data.log, "%8.2F ms/packet.\n", [real[msPerPacket]] ]; PrintPercent[data, data.good, sent, "Packets echoed OK"]; PrintPercent[data, data.error, sent, "Error Packets"]; IO.Flush[data.log]; }; }; 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]]]; }; PokeProc: Buttons.ButtonProc = { data: ClientData _ NARROW[clientData]; target: ROPE = ViewerTools.GetContents[data.target]; Poke[data, target]; }; Poke: PROC [data: ClientData, target: ROPE] = TRUSTED { id: Endian.FWORD = PupSocket.GetUniqueID[]; b: Buffer; socket: Socket; hit: INT _ 0; start: BasicTime.Pulses; buffersToCollect: NAT = 100; buffers: ARRAY [0..buffersToCollect) OF Buffer _ ALL[NIL]; stop: ARRAY [0..buffersToCollect) OF BasicTime.Pulses; lengthText: ROPE = ViewerTools.GetContents[data.length]; bytes: INT _ 0; IO.PutF[data.log, "\nPoking %G", [rope[target]]]; IF ~FindPath[data, target] THEN RETURN; IF data.fixedLength^ THEN { temp: INT; temp _ Convert.IntFromRope[lengthText ! Convert.Error => { IO.PutF[data.log, "Can't parse Length field.\n"]; temp _ 0; CONTINUE; } ]; temp _ MAX[temp, 0]; bytes _ MIN[temp, PupBuffer.maxDataBytes]; IO.PutF[data.log, "Packet length is %G bytes.\n", [integer[bytes]]]; }; Process.SetPriority[Process.priorityForeground]; socket _ PupSocket.CreateEphemeral[ remote: data.where, recvBuffers: buffersToCollect, getTimeout: 5000]; b _ PupSocket.AllocBuffer[socket]; b.id _ id; b.type _ echoMe; PupSocket.SetUserBytes[b, bytes]; start _ BasicTime.GetClockPulses[]; PupSocket.Put[socket, b]; FOR i: NAT IN [0..buffersToCollect) DO b _ PupSocket.Get[socket]; IF b = NIL THEN EXIT; buffers[i] _ b; stop[i] _ BasicTime.GetClockPulses[]; ENDLOOP; FOR i: NAT IN [0..buffersToCollect) DO b _ buffers[i]; IF b = NIL THEN EXIT; SELECT TRUE FROM (b.type = error) => PrintErrorPup[data, b]; (b.type # iAmEcho) => IO.PutRope[data.log, "%"]; (b.id # id) => IO.PutRope[data.log, "#"]; ENDCASE => { microseconds: LONG CARDINAL _ BasicTime.PulsesToMicroseconds[stop[i]-start]; hit _ hit.SUCC; IO.PutF[data.log, "Response %G from %G = %G in %G microseconds.\n", [integer[hit]], [rope[PupName.AddressToRope[b.source]]], [rope[PupName.HisName[b.source]]], [integer[microseconds]] ]; }; PupSocket.FreeBuffer[b]; ENDLOOP; PupSocket.SetNoErrors[serverSoc]; PupSocket.Destroy[socket]; }; 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]] ]; }; PrintErrorPup: PROC [data: ClientData, b: Buffer] = { length: CARDINAL _ PupSocket.GetUserBytes[b]; IO.PutRope[data.log, "Error Pup from "]; IO.PutRope[data.log, PupName.HisName[b.source]]; IO.PutRope[data.log, ": "]; IO.PutRope[data.log, PupSocket.ExtractErrorRope[b]]; IO.PutRope[data.log, "\n"]; }; FindPath: PROC [data: ClientData, target: ROPE] RETURNS [BOOLEAN] = { rte: PupHop.RoutingTableEntry; data.where _ PupName.NameLookup[target, PupWKS.echo ! PupName.Error => { IO.PutF[data.log, " Oops: %G.\n", [rope[text]]]; GOTO Trouble; }]; IO.PutF[data.log, " = %G", [rope[PupName.AddressToRope[data.where]]]]; rte _ PupHop.GetRouting[[data.where.net]]; IF rte.hop # 0 THEN IO.PutF[data.log, " which is %G hops via %G", [integer[rte.hop]], [rope[PupName.AddressToRope[rte.immediate]]] ]; IO.PutRope[data.log, ".\n"]; RETURN[TRUE]; EXITS Trouble => RETURN[FALSE]; }; 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, guarded: BOOL _ FALSE] RETURNS[child: Viewer] = { child _ Buttons.Create[ info: [name: name, parent: parent, border: TRUE, wy: sibling.wy, wx: sibling.wx + sibling.ww - 1, ww: buttonWidth], proc: proc, clientData: data, fork: TRUE, guarded: guarded, 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, ww: VFonts.StringWidth[name] + 2*3 + 2], paint: FALSE ]; }; MakeLabeledText: PROC [ parent, sibling: Viewer, name, data: ROPE, prev: Viewer, width: INT, newline: BOOL _ TRUE] RETURNS [child: Viewer] = { buttonWidth: INT _ VFonts.StringWidth[name] + 2*3; 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: width+10, data: IF prev = NIL THEN data ELSE ViewerTools.GetContents[prev], border: FALSE, wx: x + buttonWidth + 2, wy: y, scrollable: FALSE ], paint: FALSE ]; [] _ 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; }; patternBytes: NAT = 5000; patternFWords: NAT = patternBytes/bytesPerFWord; BytePattern: TYPE = PACKED ARRAY [0..patternBytes) OF BYTE; FWordPattern: TYPE = ARRAY [0..patternFWords) OF Endian.FWORD; bytePattern: REF BytePattern _ NEW [BytePattern]; fWordPattern: LONG POINTER TO FWordPattern; FOR i: NAT IN [0..patternBytes) DO bytePattern[i] _ i MOD 100H; ENDLOOP; TRUSTED {fWordPattern _ LOOPHOLE[bytePattern]; }; Commander.Register["PupEchoTool", Create, "Echo Pups to another machine."]; }. LPupEchoTool.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Hal Murray, December 10, 1986 10:57:11 pm PST Viewer layout parameters Server data (global) Note: This server goes in on top of the builtin one [viewer: ViewerClasses.Viewer, event: ViewerEvent, before: BOOL] RETURNS[abort: BOOL _ FALSE] 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 parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL Κ&"˜šœ™Icodešœ Οmœ7™BK™-—J˜šΟk ˜ Jšœžœ ˜Jšœžœ˜$Jšœ žœžœ-˜QJšœžœ/˜JšœR˜R—J˜—š œ˜ Jšœ;žœ™@Jšžœžœžœ™Jšœžœ,˜EJš žœžœ žœžœžœ˜/Jšœ ˜ Jšžœžœžœ˜@Jšžœ˜Jšžœ˜J˜J˜—š‘ œžœžœ˜Jšžœ žœžœžœ˜Jšœžœ˜Jšœ žœ ˜Jšœ˜J˜—š‘ œžœžœ˜Jšžœ žœžœžœ˜Jšœžœ˜Jšœ˜Jšžœžœ ˜Jšœ žœ˜ Jšœ žœ˜Jšœ˜J˜—š‘œžœ˜šœ#˜#Jšœ˜Jšœ˜Jšœ#˜#—Jšœ6žœ˜;šžœž˜Jšœ%˜%Jšžœžœžœžœ˜šžœž˜˜ J˜Jšœ˜Jšœ ˜ Jšžœ žœžœ˜2—˜˜Jšœ$˜$J˜*—Jšœ ˜ Jšœžœ!˜>Jšœ˜—Jšžœ˜#—Jšžœ˜—Jšœ!˜!Jšœ˜Jšœ˜J˜—š‘ œžœžœ˜Jšœžœ˜Jšœ˜J˜—š ‘œžœ#žœžœžœ ˜Lšžœž˜˜ Jšžœ žœ&žœžœ˜HJšœ˜Jšœ*˜*Jšœ˜—˜˜Jšœ$˜$J˜*—Jšœ ˜ Jšœžœ!˜>Jšœ-˜-—Jšžœžœ˜—Jšžœ˜ Jšœ˜J˜—š  œ˜!JšœL™LJšœžœ ˜&Jšœ žœ˜ šžœžœž˜Jšœžœ˜!Jšœ˜Jšœ˜Jšžœ˜—Jšœ˜J˜—š‘œžœžœ˜0Jšœžœ(˜4Jšžœ žœžœ ˜#Jšžœ3˜5Jšžœžœžœ˜'Jšžœ žœžœN˜dJšœžœ˜Jšœ žœ˜(J˜—š  œ˜!JšœL™LJšœžœ ˜&Jšœ žœ˜ šžœžœž˜Jšœžœ˜!Jšœ˜Jšœ˜Jšžœ˜—Jšœ˜J˜—š‘œžœžœ˜0Jšœžœ(˜4Jšžœ žœžœ ˜#Jšžœ9˜;Jšžœžœžœ˜'Jšžœ žœžœL˜bJšœžœ˜Jšœ žœ˜+J˜—š œ˜ JšœL™LJšœžœ ˜&Jšœ ˜ Jšœ˜J˜—š œ˜ JšœL™LJšœžœ ˜&Jšœ˜J˜—š‘œžœžœ˜)Jšœžœ˜Jšžœ žœžœžœ ˜'Jšœ žœ˜J˜—š  œ˜!JšœL™LJšœžœ ˜&Jšœ˜J˜—š‘œžœ˜"š‘œžœžœ˜Jšœ9˜9Jšžœžœžœ˜,Jšœ˜Jšžœžœžœ˜%Jšžœ†˜ˆJšžœžœžœ#˜?—Jšœ žœ˜Jšžœ4˜6Jšžœ.˜0JšžœM˜OJšžœM˜Oš žœžœžœžœž˜Jšœ˜Jšžœ˜—Jšžœžœžœ˜&Jšžœ žœžœD˜WJšœ˜—J˜š  œ˜!JšœL™LJšœžœ ˜&Jšœ˜J˜—š‘œžœ˜"Jšœ9˜9Jšžœ4˜6šžœ3žœ žœž˜Lšžœž˜šœ˜Jšœ(žœ˜>Jšžœ˜Jšžœžœžœ˜Jšœ9˜9J˜:J˜3J˜/J˜1J˜1Jšžœ˜Jšœ˜J˜—š ‘œžœ#žœžœ žœ˜QJšœžœ˜ šžœž˜šœ ˜ šžœžœ žœ žœ˜6Jšœ#˜#Jšžœžœ˜——Jšžœ*žœžœ˜@—Jšžœ˜ Jšœ˜J˜—š ‘ œžœžœžœžœžœ˜BJšžœ˜%J˜—š‘œžœžœ˜2Jšžœžœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜ Jšœ žœ(˜8Jšœ˜Jšœ žœ˜Jšœ žœžœžœ˜+Jšœžœ˜%š ‘œžœžœžœžœžœ˜1Jšœ ˜ Jšœ˜—š ‘ œžœžœžœ žœžœ˜8J˜Jš žœžœ žœžœžœ˜'Jšžœžœ˜—š‘œžœžœ˜Jšœ*˜*šžœž˜Jšœžœ ˜Jšœ˜J˜šžœ˜ Jšœžœ(˜7Jšœ9žœ˜S—Jšœ"˜"Jšžœžœ&˜4Jšžœ˜!Jšžœžœ žœžœ˜?Jšžœ˜—J˜Jšœ˜Jšœ˜—šžœžœžœ˜Jšœžœ˜ šœ:˜:Jšžœ/˜1Jšœ ˜ Jšžœ˜—Jšœžœ ˜Jšœžœ˜Jšœ˜JšžœF˜H—J˜J˜(šœ#˜#JšœI˜I—Jšœ:˜:J˜šžœžœžœž˜Jšžœžœ˜)Jšžœ˜—šžœž˜J˜"Jšžœžœžœžœ˜šžœž˜šœ ˜ Jšœ˜Jšœ˜—Jšœ žœ˜%šžœ˜ Jšœ˜Jšžœ˜——Jšœ˜Jšžœ˜—Jšžœ˜šžœžœžœž˜Jšžœžœ˜Jšžœ˜—Jšœ˜Jšœ!˜!J˜Jšœ˜Jšœžœ˜$Jšœ žœžœ-˜CJšžœ žœ ˜ Jšœžœžœ ˜,Jšžœžœ˜%Jšžœ(˜,Jšžœ[˜]JšžœF˜HJšžœ<˜>Jšœ9˜9J˜6Jšžœ˜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˜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˜š‘œžœ˜Jš œ%žœžœ žœžœžœ˜vJšœ žœ"˜2Jš œžœžœ žœžœ˜AJš œžœžœ žœžœ ˜I˜&šœ˜Jšœ/˜/Jš œžœžœžœžœ˜AJšœžœ˜J˜Jšœ žœ˜—Jšœžœ˜—˜Jšœ=žœ˜RJšœ0žœ žœ˜E—Jšžœ ˜J˜—š œ˜'JšœL™LJšœžœ ˜"šžœ ž˜Jšœ&žœ˜+Jšœ žœ˜Jšœ(žœ"žœ˜TJšžœžœ˜——J˜Kšœžœ˜Kšœžœ˜0Kš œ žœžœžœžœžœ˜;Kš œžœžœžœžœ˜>Kšœ žœžœ˜1Jšœžœžœžœ˜+Kš žœžœžœžœžœžœ˜HKšžœžœ˜1K˜šœK˜KJ˜—Jšœ˜J˜J˜J˜——…—~«μ