DIRECTORY Basics USING [BytePair, HighByte, LongNumber, LowByte], BasicTime USING [GetClockPulses, GMT, Now, Pulses, PulsesToMicroseconds], DFUtilities USING [DateToRope], GVSend USING [AddRecipient, AddToItem, CheckValidity, Create, Handle, Send, StartSend, StartSendInfo, StartText], IO USING [Flush, PutF, PutFR, PutRope, STREAM, time, Value], IPNameUdp USING [Class, DomainHeader, Type], Process USING [Detach, SecondsToTicks], Rope USING [Cat, Concat, Equal, Fetch, Find, FromChar, Length, ROPE, Substr], IPDefs USING [Byte, DataBuffer, Datagram, DatagramRec, DByte, Address, InternetHeader], IPName USING [AddressToName, AddressToRope, LoadCacheFromName, NameToAddress], IPRouter USING [BestAddress], UDP USING [BodyRec, Create, default, Destroy, domain, Handle, minLength, Receive, Send], UserCredentials USING [Get]; NameWatcher: CEDAR MONITOR IMPORTS Basics, BasicTime, DFUtilities, GVSend, IO, Process, Rope, IPName, IPRouter, UDP, UserCredentials = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; started: BasicTime.GMT _ BasicTime.Now[]; senderPwd: ROPE = UserCredentials.Get[].password; sender: ROPE = UserCredentials.Get[].name; recipient: ROPE _ "ArpanetSupport^.pa"; log: STREAM _ NIL; interval: CARDINAL _ 1*10*60; -- interval between queries (10 mins) abort: BOOLEAN _ FALSE; debug: BOOLEAN _ FALSE; stats: BOOLEAN _ FALSE; TestRecord: TYPE = RECORD[ server: ROPE, query: ROPE, expectAddr: IPDefs.Address, failed: BOOLEAN, sendMessage: BOOLEAN, timeOut: INT, histogram: Histogram]; -- in milliSecs tests: LIST OF TestRecord _ LIST[ [ server: "parcvax.Xerox.COM", query: "Xerox.COM", expectAddr: [10,2,0,32], failed: FALSE, sendMessage: TRUE, timeOut: 60000, histogram: NEW[HistogramRecord]], [ server: "sonora.dec.com", query: "Xerox.COM", expectAddr: [10,2,0,32], failed: FALSE, sendMessage: FALSE, timeOut: 60000, histogram: NEW[HistogramRecord]] ]; sequenceNumber: CARDINAL _ 0; histogramSlots: INT = 300; histogramSlotSize: INT = 100; -- ms/slot Histogram: TYPE = REF HistogramRecord; HistogramRecord: TYPE = RECORD [ probes: INT _ 0, lost: INT _ 0, counters: ARRAY [0..histogramSlots) OF INT _ ALL[0] ]; Query: PROC [server: ROPE, query: ROPE, expectAddr: IPDefs.Address, timeOut: INT, histogram: Histogram] RETURNS[failed: BOOLEAN_ FALSE, how: ROPE _ NIL] = TRUSTED { packetStart: BasicTime.Pulses; type: IPNameUdp.Type _ a; id: CARDINAL _ (sequenceNumber _ sequenceNumber + 1); handle: UDP.Handle; badReply: BOOLEAN _ FALSE; gotAddress: BOOLEAN _ FALSE; gotResponse: BOOLEAN _ FALSE; where: IPDefs.Address; Report[log, "\nQuerying ", server]; [gotAddress, where] _ FindPath[server]; IF ~gotAddress THEN {Report[log, "Can't load address for server."]; RETURN[TRUE, "Can't load address for server."]}; handle _ UDP.Create[him: where, local: UDP.default, remote: UDP.domain]; IF handle = NIL THEN { Report[log, "Can't create UDP handle.\n"]; RETURN[FALSE, "Can't create UDP handle.\n"]; }; FOR i: CARDINAL IN [0..2) DO -- 1 retry packetStop: BasicTime.Pulses; milliseconds: LONG CARDINAL; dg: IPDefs.Datagram _ NEW [IPDefs.DatagramRec]; udp: LONG POINTER TO UDP.BodyRec _ LOOPHOLE[@dg.data]; domain: LONG POINTER TO IPNameUdp.DomainHeader _ LOOPHOLE[@udp.data]; domain^ _ [id: id]; udp.length _ UDP.minLength + IPNameUdp.DomainHeader.SIZE*2; AppendQuery[udp, domain, query, type, in]; UDP.Send[handle, dg, udp.length]; packetStart _ BasicTime.GetClockPulses[]; histogram.probes _ histogram.probes + 1; dg _ UDP.Receive[handle, timeOut]; -- timeout in milliSeconds packetStop _ BasicTime.GetClockPulses[]; IF dg # NIL THEN { milliseconds _ BasicTime.PulsesToMicroseconds[packetStop-packetStart]/1000; udp _ LOOPHOLE[@dg.data]; domain _ LOOPHOLE[@udp.data]; IF dg.inHdr.source # where THEN { IF log ~= NIL THEN IO.PutF[log, "Strange source address: expected %G, found %G = %G: \n", [rope[IPName.AddressToRope[where]]], [rope[IPName.AddressToRope[dg.inHdr.source]]], [rope[IPName.AddressToName[dg.inHdr.source]]]]; UDP.Destroy[handle]; RETURN[FALSE, Rope.Cat["Strange source address: ", IPName.AddressToRope[dg.inHdr.source], " = ", IPName.AddressToName[dg.inHdr.source]]]}; [badReply, how] _ CheckDomainPacket[query, expectAddr, udp, domain]; IF badReply THEN { IF log ~= NIL THEN { Report[log, "Incorrect Reply: ", how, "\n"]; IO.PutF[log, "Response time was %G ms.\n", [integer[milliseconds]]]}; UDP.Destroy[handle]; RETURN[TRUE, how]} ELSE Report[log, "Reply: ok, "]; IF log ~= NIL THEN IO.PutF[log, "The response time was %G ms.\n", [integer[milliseconds]]]; AddToCounter[histogram, milliseconds]; dg _ NIL; gotResponse _ TRUE; EXIT; } ELSE histogram.lost _ histogram.lost + 1; ENDLOOP; IF ~gotResponse THEN {Report[log, "No response.", "\n"]; RETURN[TRUE]}; Report[log, "\n"]; UDP.Destroy[handle]; RETURN[FALSE] }; AddToCounter: PROC [histogram: Histogram, ms: INT] = { index: INT; ms _ ms + histogramSlotSize - 1; index _ ms/histogramSlotSize; IF index >= histogramSlots THEN index _ histogramSlots-1; histogram.counters[index] _ histogram.counters[index] + 1; }; PrintStats: PROC [log: IO.STREAM, histogram: Histogram] = { running: INT _ 0; now: BasicTime.GMT _ BasicTime.Now[]; nowRope: ROPE _ DFUtilities.DateToRope[[format: explicit, gmt: now]]; startedRope: ROPE _ DFUtilities.DateToRope[[format: explicit, gmt: started]]; Report[log, "Started: ", startedRope, "\n"]; Report[log, "Ended: ", nowRope, "\n"]; IO.PutF[log, " Queries: %G.\n", [integer[histogram.probes]] ]; IF histogram.probes = 0 THEN RETURN; IO.PutF[log, " No reply: %G, %1.2F%%.\n", [integer[histogram.lost]], [real[100.0*histogram.lost/histogram.probes]] ]; IO.PutRope[log, " Response time histogram:\n"]; FOR i: INT IN [0..histogramSlots) DO counter: INT _ histogram.counters[i]; milliseconds: INT _ i*histogramSlotSize; counterPerCent, runningPerCent: REAL; IF counter = 0 THEN LOOP; running _ running + counter; counterPerCent _ 100.0*counter/histogram.probes; runningPerCent _ 100.0*running/histogram.probes; IO.PutF[log, "%7G %7.2F %7.2F %7G\n", [integer[counter]], [real[counterPerCent]], [real[runningPerCent]], [integer[milliseconds]] ]; ENDLOOP; IO.PutRope[log, "\n"]; IO.Flush[log]; }; PrintServerStats: PROC [log: IO.STREAM, server: Rope.ROPE] = { FOR list: LIST OF TestRecord _ tests, list.rest UNTIL list = NIL DO this: ROPE _ list.first.server; IF Rope.Equal[this, server, FALSE] OR Rope.Equal[server, "*", FALSE] THEN {Report[log, "Server: ", this, "\n"]; PrintStats[log, list.first.histogram]; Report[log, "\n"]; IO.Flush[log]}; ENDLOOP; }; FindPath: PROC [target: ROPE] RETURNS [ok: BOOLEAN_FALSE, where: IPDefs.Address] = { whereList: LIST OF IPDefs.Address; IF IPName.LoadCacheFromName[target, TRUE, TRUE] = down THEN { IF log # NIL THEN IO.PutF[log, "Can't load name Cache."]; RETURN; }; whereList _ IPName.NameToAddress[target]; IF whereList = NIL THEN {IF log # NIL THEN IO.PutF[log, "Name not found."]; RETURN; }; where _ IPRouter.BestAddress[whereList]; Report[log, " = ", IPName.AddressToRope[where], ".\n"]; RETURN[TRUE, where]; }; CheckDomainPacket: PROC [query: ROPE, expectAddr: IPDefs.Address, udp: LONG POINTER TO UDP.BodyRec, domain: LONG POINTER TO IPNameUdp.DomainHeader] RETURNS[failed: BOOLEAN _ FALSE, how: ROPE _ NIL] = TRUSTED { length: INT _ udp.length; type: IPNameUdp.Type; class: IPNameUdp.Class; ttl: INT; rDataLength: CARDINAL; name: ROPE; udp.length _ UDP.minLength + IPNameUdp.DomainHeader.SIZE*2; SELECT domain.qr FROM response => IF debug THEN Report[log, "qr: response, "]; ENDCASE => RETURN[TRUE, "qr # response."]; SELECT domain.opcode FROM query => IF debug THEN Report[log, "op: query"]; ENDCASE => RETURN[TRUE, "op # query."]; IF debug AND log ~= NIL THEN IO.PutF[log, ", length: %G bytes.\n", [integer[length]]]; IF debug AND log ~= NIL THEN IO.PutF[log, "aa: %G, tc: %G, rd: %G, ra: %G\n", [boolean[domain.aa]], [boolean[domain.tc]], [boolean[domain.rd]], [boolean[domain.ra]]]; SELECT domain.rcode FROM ok => IF debug THEN Report[log, "op: ok"]; format => RETURN[TRUE, "Format failure."]; serverFailed => RETURN[TRUE, "Server failed."]; nameNotFound => RETURN[TRUE, "nameNotFound."]; notImplemented => RETURN[TRUE, "notImplemented."]; refused => RETURN[TRUE, "refused."]; ENDCASE => RETURN[TRUE, "unknown."]; IF domain.tc THEN {Report[log, " ** TRUNCATED **"]; RETURN[TRUE, "Truncated."];}; IF debug THEN Report[log, "\n"]; IF debug AND log ~= NIL THEN IO.PutF[log, "qdCount: %G, anCount: %G, nsCount: %G, arCount: %G\n", [integer[domain.qdCount]], [integer[domain.anCount]], [integer[domain.nsCount]], [integer[domain.arCount]]]; IF domain.qdCount # 1 THEN RETURN[TRUE, "qdCount # 1"]; name _ GetName[udp]; IF debug AND log ~= NIL THEN IO.PutF[log, "Query Name: \"%G\", ", [rope[name]]]; type _ GetTwoBytes[udp]; IF type # a THEN RETURN[TRUE, "Incorrect query type."]; class _ GetTwoBytes[udp]; IF class # in THEN RETURN[TRUE, "Incorrect query class."]; name _ GetName[udp]; IF ~Rope.Equal[name, query, FALSE] THEN RETURN[TRUE, "Incorrect response name."]; type _ GetTwoBytes[udp]; IF type # a THEN RETURN[TRUE, "Incorrect response type."]; class _ GetTwoBytes[udp]; IF class # in THEN RETURN[TRUE, "Incorrect response class."]; ttl _ GetTtl[udp]; rDataLength _ GetTwoBytes[udp]; SELECT type FROM a => { -- This is it! IF rDataLength = 4 THEN { addr: IPDefs.Address _ GetIPAddress[udp]; IF addr # expectAddr THEN RETURN[TRUE, "Incorrect address."];} ELSE RETURN[TRUE, "Funny Length for address response."]; }; ENDCASE => { -- Something bogus RETURN[TRUE, "Bogus RR."]; }; IF domain.nsCount # 0 THEN RETURN[TRUE, "nsCount # 0"]; IF domain.arCount # 0 THEN RETURN[TRUE, "arCount # 0"]; }; GetName: PROC [udp: LONG POINTER TO UDP.BodyRec] RETURNS [rope: ROPE] = TRUSTED { length: INT _ udp.length; indirect: INT _ 300B; rope _ NIL; DO bytes: INT _ udp.data[length]; length _ length + 1; IF bytes = 0 THEN EXIT; IF bytes >= indirect THEN { -- Indirect link, keep length we have temp: INT _ (bytes-indirect)*256 + udp.data[length]; temp _ temp + UDP.minLength; length _ length + 1; udp.length _ temp; rope _ Rope.Concat[rope, GetName[udp]]; EXIT; }; FOR i: INT IN [0..bytes) DO rope _ Rope.Concat[rope, Rope.FromChar[LOOPHOLE[udp.data[length]]]]; length _ length + 1; ENDLOOP; IF udp.data[length] # 0 THEN rope _ Rope.Concat[rope, "."]; ENDLOOP; udp.length _ length; }; longTime: LONG CARDINAL = INT.LAST; GetTtl: PUBLIC PROC [ udp: LONG POINTER TO UDP.BodyRec] RETURNS [INT] = { card: LONG CARDINAL _ GetCard[udp]; IF card > longTime THEN card _ longTime; RETURN[card]; }; GetCard: PROC [ udp: LONG POINTER TO UDP.BodyRec] RETURNS [LONG CARDINAL] = TRUSTED { ln: Basics.LongNumber; ln.hi _ GetCardinal[udp]; ln.lo _ GetCardinal[udp]; RETURN[ln.lc]; }; GetCardinal: PROC [ udp: LONG POINTER TO UDP.BodyRec] RETURNS [CARDINAL] = TRUSTED { RETURN[GetTwoBytes[udp]]; }; GetTwoBytes: PROC [ udp: LONG POINTER TO UDP.BodyRec] RETURNS [UNSPECIFIED] = TRUSTED { length: INT _ udp.length; temp: Basics.BytePair; temp.high _ udp.data[length]; temp.low _ udp.data[length+1]; udp.length _ udp.length + 2; RETURN[temp]; }; GetIPAddress: PROC [ udp: LONG POINTER TO UDP.BodyRec] RETURNS [a: IPDefs.Address] = TRUSTED { length: INT _ udp.length; a[0] _ udp.data[length+0]; a[1] _ udp.data[length+1]; a[2] _ udp.data[length+2]; a[3] _ udp.data[length+3]; udp.length _ udp.length + 4; }; AppendQuery: PROC [ udp: LONG POINTER TO UDP.BodyRec, domain: LONG POINTER TO IPNameUdp.DomainHeader, query: ROPE, type: IPNameUdp.Type, class: IPNameUdp.Class] = TRUSTED { AppendName[udp, query]; AppendTwoBytes[udp, type]; AppendTwoBytes[udp, class]; domain.qdCount _ domain.qdCount + 1; }; AppendName: PROC [ udp: LONG POINTER TO UDP.BodyRec, name: ROPE] = TRUSTED { DO dot: INT _ Rope.Find[name, "."]; IF dot = -1 THEN EXIT; IF dot = 0 THEN EXIT; -- Bounds fault AppendFragment[udp, Rope.Substr[name, 0, dot]]; name _ Rope.Substr[name, dot+1] ENDLOOP; IF Rope.Length[name] # 0 THEN AppendFragment[udp, name]; AppendFragment[udp, NIL]; }; AppendFragment: PROC [ udp: LONG POINTER TO UDP.BodyRec, rope: ROPE] = TRUSTED { length: INT _ udp.length; chars: INT _ Rope.Length[rope]; udp.data[length] _ chars; FOR i: INT IN [0..chars) DO udp.data[length+i+1] _ LOOPHOLE[Rope.Fetch[rope, i]]; ENDLOOP; udp.length _ udp.length + chars + 1; }; AppendTwoBytes: PROC [ udp: LONG POINTER TO UDP.BodyRec, data: UNSPECIFIED] = TRUSTED { length: INT _ udp.length; udp.data[length] _ Basics.HighByte[data]; udp.data[length+1] _ Basics.LowByte[data]; udp.length _ udp.length + 2; }; Report: PROC [log: IO.STREAM, r1, r2, r3, r4: ROPE _ NIL] = TRUSTED { IF log = NIL THEN RETURN; IF r1 # NIL THEN {IO.PutRope[log, r1]}; IF r2 # NIL THEN {IO.PutRope[log, r2]}; IF r3 # NIL THEN {IO.PutRope[log, r3]}; IF r4 # NIL THEN {IO.PutRope[log, r4]}; }; SendTheMessage: PROC [server: ROPE, justFailed: BOOLEAN, how: ROPE_ NIL] = { msg: ROPE; handle: GVSend.Handle; ssi: GVSend.StartSendInfo; msg _ Rope.Cat[msg, "Date: ", IO.PutFR["%G", IO.time[]], "\n"]; msg _ Rope.Cat[msg, "From: Mailer.pa (ArpaGateway NameWatcher)\n"]; msg _ Rope.Cat[msg, "Subject: ", server]; msg _ Rope.Cat[msg, SELECT TRUE FROM ~justFailed => " name server back up.\n", how # NIL => " name server problem.\n", ENDCASE => " name server not responding.\n" ]; msg _ Rope.Cat[msg, "To: ", recipient, "\n"]; msg _ Rope.Cat[msg, "\n"]; IF how ~= NIL THEN msg _ Rope.Cat[msg, "Problem: ", how, "\n"] ELSE {IF justFailed THEN msg _ Rope.Cat[msg, "Problem could be with the ArpaGateway, Gandalf (Alto connection to IMP), the IMP, or the name server running on Vaxc.\n\nTo restart the name server, kill off the old one and type \"/etc/named\"\n"]}; handle _ GVSend.Create[]; ssi _ GVSend.StartSend[ handle: handle, senderPwd: senderPwd, sender: sender, returnTo: NIL, validate: TRUE ]; IF ssi ~= ok THEN RETURN; GVSend.AddRecipient[handle, recipient]; IF GVSend.CheckValidity[handle, NIL] # 1 THEN ERROR; GVSend.StartText[handle]; GVSend.AddToItem[handle, msg]; GVSend.Send[handle]; }; CheckNameServer: PROC = { how: ROPE _ NIL; FOR list: LIST OF TestRecord _ tests, list.rest UNTIL list = NIL DO server: ROPE _ list.first.server; wasFailed: BOOLEAN _ list.first.failed; IF server = NIL THEN EXIT; [list.first.failed, how] _ Query[server, list.first.query, list.first.expectAddr, list.first.timeOut, list.first.histogram]; IF list.first.sendMessage THEN { IF list.first.failed AND ~wasFailed THEN { IF log # NIL THEN Report[log, "Sending failure message to: ", recipient, "\n"]; SendTheMessage[server, TRUE, how]} ELSE IF wasFailed AND ~list.first.failed THEN { IF log # NIL THEN Report[log, "Sending up message to: ", recipient, "\n"]; SendTheMessage[server, FALSE]}}; IF log ~= NIL AND stats THEN {PrintStats[log, list.first.histogram]; IO.Flush[log]}; ENDLOOP; }; Background: ENTRY PROC = { DO snooz: CONDITION _ [timeout: Process.SecondsToTicks[interval]]; WAIT snooz; IF abort THEN {Report[log, "\n", "Aborted.", "\n"]; EXIT}; CheckNameServer[]; ENDLOOP; }; TRUSTED {Process.Detach[FORK Background[]]; }; END. NameWatcher.mesa John Larson, July 13, 1987 5:16:49 pm PDT _ NameWatcher.log _ ViewerIO.CreateViewerStreams["NameWatcher"].out; _ NameWatcher.PrintServerStats[ViewerIO.CreateViewerStreams["Stats"].out, "*"] IF domain.anCount # 1 THEN RETURN[TRUE, "anCount # 1"]; Κa˜šœ™Icode™)—J˜šΟk ˜ Jšœœ+˜7Jšœ œœ%˜IJšœ œ˜Jšœœe˜qJšœœœ˜Jšœ(˜(J˜šœœœ˜JšœK˜KLšœœ ˜Lšœ œ ˜šœœ˜!šœœœœD˜YJšœ%˜%Jšœ.˜.Jšœ/˜/—Jšœ˜Jšœœ˜‹J˜—LšœD˜DL˜šœ œ˜šœœœ˜L˜-LšœC˜E—Lšœœœ˜'—Lšœ˜"L˜JšœœœœF˜[Jšœ&˜&Jšœœ˜ Jšœœ˜šœ˜Jšœ˜—Jšœ%˜)—Jšœ˜J˜—šœœ%œœ˜GJ˜—Jšœ˜Jšœ˜Jšœœ˜J˜J˜—šΟn œœœ˜6Jšœœ˜ Jšœ ˜ Jšœ˜Jšœœ˜9Jšœ=˜=J˜J˜—šž œœœœ˜;Jšœ œ˜Jšœœ˜%Jšœ œ8˜EJšœ œ<˜M—˜Jšœ-˜-Jšœ'˜'J˜J˜Jšœ?˜AJšœœœ˜$Jšœw˜yJ˜J˜Jšœ0˜2šœœœ˜$Jšœ œ˜%Jšœœ˜(Jšœ œ˜%Jšœ œœ˜Jšœ˜Jšœ0˜0Jšœ0˜0Jšœ‚˜„šœ˜J˜——Jšœ˜Jšœ˜J˜J˜—š  œœœœœ˜>š œœœœœ˜CJšœœ˜Jš œœœœœaœ ˜ΉJšœ˜—J˜Jšœ˜J˜J˜J˜—š  œœ œœœœ˜TJšœ œœ˜"šœ"œœ œ˜=Jš œœœœ&œ˜D—Jšœ)˜)Jšœ œœœœœœœ˜VJšœ(˜(Jšœ7˜7Jšœœ ˜J˜J˜—š œœ œ˜AJšœœœœœœœœœ œœœœœ˜Jšœœ˜Jšœ˜Jšœ˜Jšœœ˜ Jšœ œ˜Jšœœ˜ J˜Jšœ œ$œ˜;J˜šœ ˜Jšœ œœ˜8Jšœœœ˜*—šœ˜Jšœ œœ˜0Jšœœœ˜'J˜—Jš œœœœœ7˜Vš œœœœœ.˜MJšœX˜XJ˜—šœ˜Jšœœœ˜+Jšœ œœ˜*Jšœœœ˜/Jšœœœ˜.Jšœœœ˜2Jšœ œœ˜$Jšœœœ˜$J˜—Jšœ œ$œœ˜RJ˜Jšœœ˜ š œœœœœB˜aJšœl˜lJ˜—Jšœœœœ˜7J˜Jšœ˜Jš œœœœœ1˜PJšœ˜Jšœ œœœ˜8Jšœ˜Jšœ œœœ˜:J˜Jšœœœœ™7J˜Jšœ˜Jš œœœœœ˜QJ˜Jšœ˜Jšœ œœœ˜;J˜Jšœ˜Jšœ œœœ˜=J˜Jšœ˜Jšœ˜J˜šœ˜šœŸ˜šœœ˜Jšœ)˜)Jšœœœœ˜>—Jšœœœ+˜;—šœŸ˜Jšœœ˜——J˜Jšœœœœ˜7Jšœœœœ˜7Jšœ˜J˜J˜—š œœœœœœ œœœ˜QJšœœ˜Jšœ œ˜Jšœœ˜ š˜Jšœœ˜J˜Jšœ œœ˜šœœŸ%˜AJšœœ+˜4Jšœœ ˜J˜Jšœ˜Jšœ'˜'Jšœ˜—šœœœ ˜Jšœ'œ˜DJ˜Jšœ˜—Jšœœ˜;Jšœ˜—Jšœ˜J˜—Jš œ œœœœ˜#š œœœ˜Jš œœœœœ œœ˜3Jšœœœ˜#Jšœœ˜(Jšœ ˜J˜—š œœ˜Jšœœœœœ œœœœ˜EJšœ˜Jšœ˜Jšœ˜Jšœ ˜J˜—š  œœ˜Jšœœœœœ œœœ˜@Jšœ˜J˜—š  œœ˜Jšœœœœœ œ œœ˜CJšœœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ ˜J˜—š  œœ˜Jš œœœœœ œœ˜IJšœœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜J˜—š  œœ˜Jšœœœœœœœœ˜QJšœœ2œ˜FJšœ˜Jšœ˜Jšœ˜Jšœ'˜'J˜—š  œœ˜Jš œœœœœœœ˜9š˜Jšœœ˜ Jšœ œœ˜Jšœ œœŸ˜%Jšœ/˜/Jšœ˜Jšœ˜—Jšœœ˜8Jšœœ˜J˜—š œœ˜Jš œœœœœœœ˜9Jšœœ˜Jšœœ˜Jšœ˜šœœœ ˜Jšœœ˜5Jšœ˜—Jšœ'˜'J˜—š œœ˜Jš œœœœœ œœ˜@Jšœœ˜Jšœ)˜)Jšœ*˜*Jšœ˜J˜—š œœœœœœœ˜EJšœœœœ˜Jšœœœœ˜'Jšœœœœ˜'Jšœœœœ˜'Jšœœœœ˜*—J˜J˜J˜J˜J˜š  œœ œœœœ˜LJšœœ˜ J˜Jšœ˜Jšœœ œ˜?J˜CJšœ)˜)J˜šœ˜šœœ˜Jšœ*˜*Jšœœ˜(Jšœ&˜-Jšœ˜J˜——Jšœ-˜-J˜J˜Jšœœœ,˜>Jšœœ œέ˜υJ˜J˜šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ œ˜Jšœ œ˜—Jšœ œœ˜Jšœ'˜'Jšœœœœ˜4Jšœ˜Jšœ˜Jšœ˜Jšœ˜J˜J˜—š œœ˜Jšœœœ˜J˜š œœœœœ˜CJšœœ˜!Jšœ œ˜'Jšœ œœœ˜Jšœ|˜|J˜šœœ˜ šœœ œ˜*Jšœœœ>˜OJšœœ˜"—šœœ œœ˜/Jšœœœ9˜JJšœœ˜ —J˜—Jš œœœœ)œ ˜TJšœ˜—J˜Jšœ˜——˜š  œœœ˜Lš˜Lšœ œ0˜@Lšœ˜ Lšœœ'œ˜:L˜Lšœ˜ —J˜Jšœœ˜.J˜J˜šœ˜J˜J˜J˜——J˜—…—:ΌP+