DIRECTORY Ascii USING[ CR, SP ], BasicTime USING[ Pulses, PulsesToMicroseconds ], BufferDefs USING[ Buffer, PupBuffer ], DriverDefs USING[ ChangeNumberOfInputBuffers, GetDeviceChain ], IO USING[ PutFR ], LookerDefs USING[ Clear, DiskChar, DiskCommit, DiskMultiple, DiskPos, GetLength, InputAction, NotePausing, NoteSlow, ScreenLines, SendNow, SetPos, DisplayChar, DisplayMultiple, WriteTitle ], PrincOpsUtils USING[ BITAND, IsBound, LongCOPY ], Process USING[ Abort, EnableAborts, Priority, priorityBackground, SetPriority ], PupDefs USING[ AnyLocalPupAddress, GetPupAddress, PupAddress, PupNameTrouble, PupPackageMake ], PupTypes, Rope USING[ Length, ROPE ], RPCWatch USING[ SetSpyProc ], SpecialCommunication USING[ SetEthernetOneListener, SetSpyProc ], TeledebugProtocol, VM USING[ AddressForPageNumber, Allocate, Free, Interval, PagesForWords ]; Looker: MONITOR IMPORTS BasicTime, DriverDefs, IO, LookerDefs, PrincOpsUtils, Process, PupDefs, Rope, RPCWatch, SpecialCommunication, VM EXPORTS LookerDefs SHARES BufferDefs = { MSec: PROC[rawClock: BasicTime.Pulses] RETURNS[ LONG CARDINAL ] = INLINE { RETURN[BasicTime.PulsesToMicroseconds[rawClock]/1000] }; bufferSize: CARDINAL; maxPupLength: CARDINAL; bigPupLength: CARDINAL = PupTypes.maxDataBytesPerGatewayPup; smallPupLength: CARDINAL = 54; BufferData: TYPE = MACHINE DEPENDENT RECORD[ pupLength: CARDINAL, pupTransportControl: [0..256), pupType: PupTypes.PupType, pupID: PupTypes.Pair, dest: PupDefs.PupAddress, source: PupDefs.PupAddress, pupBody: SELECT OVERLAID * FROM big => [bigChars: PACKED ARRAY[0..bigPupLength) OF CHARACTER], small => [smallChars: PACKED ARRAY[0..smallPupLength) OF CHARACTER], pupChars => [pupChars: PACKED ARRAY[0..0) OF CHARACTER], pupBytes => [pupBytes: PACKED ARRAY[0..0) OF [0..377B]], pupWords => [pupWords: ARRAY[0..0) OF CARDINAL], pupString => [pupString: StringBody], rfc => [address: PupDefs.PupAddress ], ack => [maximumBytesPerPup, numberOfPupsAhead, numberOfBytesAhead: CARDINAL ], abort => [abortCode: CARDINAL, abortText: PACKED ARRAY [0..0) OF CHARACTER ], error => [errorHeader: ARRAY [0..9] OF WORD, errorCode: PupTypes.PupErrorCode, errorOptions: WORD, errorText: PACKED ARRAY [0..0) OF CHARACTER], nameIs => [nameIs: ARRAY [0..0) OF PupDefs.PupAddress ], ENDCASE ]; Buffer: TYPE = LONG POINTER TO BufferData; ContentsBytes: PROC[b: Buffer] RETURNS[CARDINAL] = INLINE { RETURN[ b.pupLength - 22 ] }; nBuffers: CARDINAL = 300; BufferIndex: TYPE = [0..nBuffers); bufferSpace: VM.Interval; buffers: LONG POINTER TO --ARRAY BufferIndex OF-- BufferData _ NIL; times: REF ARRAY BufferIndex OF BasicTime.Pulses _ NIL; lost: PACKED ARRAY BufferIndex OF BOOL _ ALL[FALSE]; logged: PACKED ARRAY BufferIndex OF BOOL _ ALL[FALSE]; wBuffer: BufferIndex _ 0; -- buffer being written by ethernet driver -- rBuffer: BufferIndex _ 0; -- buffer Looker wants to read -- fullBuffers: [0..nBuffers] _ 0; bufferChange: CONDITION; lookerWaiting: BOOL _ FALSE; allUsed: BOOL _ FALSE; -- whether all buffers have been written -- AllocBuffers: ENTRY PROC[big: BOOL] = { IF buffers # NIL THEN FreeBuffers[]; bufferSize _ IF big THEN SIZE[big BufferData] ELSE SIZE[small BufferData]; bufferSpace _ VM.Allocate[VM.PagesForWords[LONG[nBuffers] * bufferSize]]; buffers _ VM.AddressForPageNumber[bufferSpace.page]; maxPupLength _ IF big THEN bigPupLength ELSE smallPupLength; times _ NEW[ARRAY BufferIndex OF BasicTime.Pulses]; InnerFlush[]; }; FreeBuffers: PROC = { VM.Free[bufferSpace]; times _ NIL; }; GetBuffer: ENTRY PROC RETURNS[BufferIndex] = { ENABLE UNWIND => lookerWaiting _ FALSE; WHILE fullBuffers = 0 DO lookerWaiting _ TRUE; WAIT bufferChange ENDLOOP; lookerWaiting _ FALSE; RETURN[ rBuffer ] }; ReturnBuffer: ENTRY PROC = { rBuffer _ IF rBuffer = LAST[BufferIndex] THEN 0 ELSE SUCC[rBuffer]; fullBuffers _ fullBuffers-1; NOTIFY bufferChange; }; ReplayBuffers: ENTRY PROC = { rBuffer _ IF allUsed THEN IF wBuffer = LAST[BufferIndex] THEN 0 ELSE SUCC[wBuffer] ELSE 0; fullBuffers _ (wBuffer+nBuffers-rBuffer) MOD nBuffers; IF lookerWaiting THEN NOTIFY bufferChange; }; FlushBuffers: ENTRY PROC = { InnerFlush[] }; InnerFlush: INTERNAL PROC = { rBuffer _ wBuffer _ 0; fullBuffers _ 0; allUsed _ FALSE; lost _ ALL[FALSE]; logged _ ALL[FALSE]; waitingForBuffers _ FALSE; NOTIFY bufferChange; }; waitingForBuffers: BOOL _ FALSE; NextBuffer: INTERNAL PROC = { IF fullBuffers >= nBuffers/2 OR ( waitingForBuffers AND fullBuffers+50 >= nBuffers/2 --make sure we have 50 free--) THEN { waitingForBuffers _ TRUE; lost[wBuffer] _ TRUE; RETURN }; waitingForBuffers _ FALSE; IF wBuffer = LAST[BufferIndex] THEN { allUsed _ TRUE; wBuffer _ 0 } ELSE wBuffer _ SUCC[wBuffer]; fullBuffers _ fullBuffers+1; IF lookerWaiting THEN NOTIFY bufferChange; logged[wBuffer] _ lost[wBuffer] _ FALSE; }; wanted: PupDefs.PupAddress; myNet: CARDINAL _ 0; broadcast: BOOL _ FALSE; ShowBroadcast: PUBLIC --ENTRY-- PROC[wanted: BOOL] = { broadcast _ wanted; }; Driver: ENTRY PROC[b: BufferDefs.Buffer] RETURNS[BOOL] = { IF b.encapsulation.ethernetType = pup AND ( ( (b.dest.host = wanted.host OR (broadcast AND b.dest.host = 0) OR wanted.host=0) AND ( b.dest.net = wanted.net OR ( b.dest.net = 0 AND wanted.net = myNet ) OR wanted.net = 0 ) ) OR ( ( b.source.host = wanted.host OR wanted.host = 0 ) AND ( b.source.net = wanted.net OR ( b.source.net = 0 AND wanted.net = myNet ) OR wanted.net = 0 ) ) ) THEN { myBuffer: BufferIndex = wBuffer; times[myBuffer] _ b.time; PrincOpsUtils.LongCOPY[from: @b.bufferBody, to: buffers + myBuffer * bufferSize, nwords: MIN[ (b.pupLength+1)/2, bufferSize ] ]; NextBuffer[]; }; RETURN[TRUE]--pkt still belongs to dispatcher-- }; RPCDriver: SAFE PROC[b: BufferDefs.PupBuffer] = TRUSTED { [] _ Driver[LOOPHOLE[b]]; IF b.dest.host # 0 AND b.dest.host # realHost THEN b.pupType _ LOOPHOLE[0]; }; realHost: CARDINAL; TakeEthernet: ENTRY PROC = { realHost _ DriverDefs.GetDeviceChain[].hostNumber; DriverDefs.ChangeNumberOfInputBuffers[TRUE]; IF NOT SpecialCommunication.SetEthernetOneListener[ physicalOrder: 1, newHostNumber: 0--promiscuous--] THEN ERROR; SpecialCommunication.SetSpyProc[Driver]; IF PrincOpsUtils.IsBound[RPCWatch.SetSpyProc] THEN RPCWatch.SetSpyProc[RPCDriver]; }; GiveEthernet: ENTRY PROC = { DriverDefs.ChangeNumberOfInputBuffers[FALSE]; IF NOT SpecialCommunication.SetEthernetOneListener[ physicalOrder: 1, newHostNumber: realHost] THEN ERROR; SpecialCommunication.SetSpyProc[NIL]; IF PrincOpsUtils.IsBound[RPCWatch.SetSpyProc] THEN RPCWatch.SetSpyProc[NIL]; }; inputActive: BOOL _ FALSE; lookerActive: BOOL _ FALSE; pauseWanted: BOOL _ FALSE; inputChange: CONDITION; lookerChange: CONDITION; wantedPriority: Process.Priority _ Process.priorityBackground; lookerPriority: Process.Priority; ActivateLooker: ENTRY PROC RETURNS[ BOOL ] = { ENABLE UNWIND => NULL; IF lookerPriority # wantedPriority THEN Process.SetPriority[lookerPriority _ wantedPriority]; IF inputActive OR pauseWanted THEN { WHILE inputActive OR pauseWanted DO WAIT inputChange ENDLOOP; RETURN[FALSE] } ELSE { lookerActive _ TRUE; RETURN[TRUE] }; }; DeactivateLooker: ENTRY PROC = { lookerActive _ FALSE; IF inputActive THEN NOTIFY lookerChange }; ActivateInput: ENTRY PROC = { ENABLE UNWIND => NULL; WHILE inputActive DO WAIT inputChange ENDLOOP; inputActive _ TRUE; WHILE lookerActive DO WAIT lookerChange ENDLOOP; }; DeactivateInput: ENTRY PROC = { inputActive _ FALSE; BROADCAST inputChange; }; NewPriority: PUBLIC ENTRY PROC[new: Process.Priority] = { wantedPriority _ new; }; mode: { display, disk} _ display; tabFrom: REAL; tabTo: REAL; tabAddr: REAL; tabPkt: REAL; tabData: REAL; diskFrom: CARDINAL; diskPkt: CARDINAL; diskDetails: CARDINAL; InitTabs: PROC = { tabFrom _ LookerDefs.GetLength["7777: "]; tabTo _ tabFrom + (LookerDefs.GetLength["from"]-LookerDefs.GetLength["to"]); tabAddr _ tabFrom + LookerDefs.GetLength["from "]; tabPkt _ tabAddr + LookerDefs.GetLength["77#777#777 "]; tabData _ tabPkt + LookerDefs.GetLength["[aData,L:777,to:177777]"]; diskFrom _ 6; -- "7777: " -- diskPkt _ diskFrom + 17; -- "from 377#377#377 " -- diskDetails _ diskPkt + 25; -- "[aData,L:1024,to:177777] " -- }; WriteChar: PROC[CHARACTER] _ LookerDefs.DisplayChar; WriteDigit: PROC[card: CARDINAL] = INLINE {WriteChar['0+card]}; WriteMultiple: PROC[LONG DESCRIPTOR FOR PACKED ARRAY OF CHARACTER] _ LookerDefs.DisplayMultiple; WriteString: PROC[s: LONG STRING] = { WriteMultiple[DESCRIPTOR[@(s.text),s.length]]; }; WritePup: PROC[b: Buffer] = { WriteMultiple[DESCRIPTOR[@(b.pupChars), MIN[ContentsBytes[b],maxPupLength]]]; }; WriteOctal: PROC[n: CARDINAL] = { SELECT n FROM < 10B => GO TO final; < 100B => { WriteDigit[(n / 10B)]; GO TO final}; < 1000B => { WriteDigit[(n / 100B)]; WriteDigit[(n / 10B) MOD 10B]; GO TO final}; >= 10000B => { IF n >= 100000B THEN WriteChar['1]; WriteDigit[(n / 10000B) MOD 10B]}; ENDCASE; WriteDigit[(n / 1000B) MOD 10B]; WriteDigit[(n / 100B) MOD 10B]; WriteDigit[(n / 10B) MOD 10B]; GO TO final; EXITS final => WriteDigit[(n MOD 10B)]; }; WriteLongOctal: PROC[n: LONG CARDINAL] = { SELECT n FROM < 200000B => WriteOctal[n]; ENDCASE => { radix: CARDINAL = 8; radixPower: LONG CARDINAL _ 1; lwb: LONG CARDINAL _ n/radix; WHILE radixPower <= lwb DO radixPower _ radixPower*radix ENDLOOP; WHILE radixPower > 0 DO x: CARDINAL = n/radixPower; WriteDigit[x]; n _ n - x*radixPower; radixPower _ radixPower/radix; ENDLOOP; }; }; WritePair: PROC[p: PupTypes.Pair] = { WriteOctal[p.a]; WriteChar[',]; WriteOctal[p.b] }; WriteDecimal: PROC[n: CARDINAL] = { tenPower: CARDINAL _ 1; n10: CARDINAL _ n/10; WHILE tenPower <= n10 DO tenPower _ tenPower*10 ENDLOOP; WHILE tenPower > 0 DO x: CARDINAL = n/tenPower; WriteDigit[x]; n _ n - x*tenPower; tenPower _ tenPower/10; ENDLOOP; }; WriteAddr: PROC[addr: PupDefs.PupAddress] = INLINE { WriteOctal[addr.net]; WriteChar['#]; WriteOctal[addr.host]; WriteChar['#]; WriteOctal[PrincOpsUtils.BITAND[addr.socket.b,777B]]; }; WriteID: PROC[n: CARDINAL] = INLINE { WriteString["to:"]; WriteOctal[n]; }; prevMS: LONG CARDINAL _ 0; SequinID: TYPE = MACHINE DEPENDENT RECORD[ allocate: Byte, receiveSequence: Byte, control: SequinControl, sendSequence: Byte ]; Byte: TYPE = [0..256); SequinControl: TYPE = MACHINE DEPENDENT { data(0), ack(1), nop(2), restart(3), check(4), open(5), break(6), close(7), closed(8), destroy(9), dallying(10), quit(11), broken(12), retransmit(13), stifle(14), openclose(15), opendestroy(16), (255) }; leafSocket: PupTypes.PupSocketID = [a:0,b:43B]; lookupFileSocket: PupTypes.PupSocketID = [a:0,b:61B]; teleSwatSocket: PupTypes.PupSocketID = TeledebugProtocol.teleSwatSocket; Watch: PROC[ b: Buffer, time: BasicTime.Pulses ] = { DefaultBodyPrintout: PROC[b: Buffer] = INLINE { WriteString["L:"]; WriteOctal[ContentsBytes[b]] }; { mscSrv: BOOL = b.source.socket = PupTypes.miscSrvSoc OR b.dest.socket = PupTypes.miscSrvSoc; leaf: BOOL = b.source.socket = leafSocket OR b.dest.socket = leafSocket; lookupFile: BOOL = b.source.socket = lookupFileSocket OR b.dest.socket = lookupFileSocket; teleSwat: BOOL = b.source.socket = teleSwatSocket OR b.dest.socket = teleSwatSocket; newMS: LONG CARDINAL = MSec[time]; IF newMS >= prevMS + 10000 THEN IF newMS >= prevMS + 1000*LONG[1000] -- 1000 seconds -- THEN WriteString[IF prevMS = 0 THEN "first"L ELSE "long"L] ELSE { WriteDecimal[(newMS-prevMS)/1000]; WriteChar['s]; } ELSE WriteDecimal[(newMS-prevMS) MOD 10000 ]; prevMS _ newMS; WriteChar[':]; IF mode = display THEN { IF b.source.host = wanted.host AND b.source.net = wanted.net THEN { LookerDefs.SetPos[tabTo]; WriteString["to"]; LookerDefs.SetPos[tabAddr]; WriteAddr[b.dest] } ELSE { LookerDefs.SetPos[tabFrom]; WriteString["from"]; LookerDefs.SetPos[tabAddr]; WriteAddr[b.source] }; LookerDefs.SetPos[tabPkt]; } ELSE { LookerDefs.DiskPos[diskFrom]; IF b.source.host = wanted.host AND b.source.net = wanted.net THEN { WriteString[" to "]; WriteAddr[b.dest] } ELSE { WriteString["from "]; WriteAddr[b.source] }; LookerDefs.DiskPos[diskPkt]; }; WriteChar['[]; { type: STRING = SELECT b.pupType FROM echoMe => "echoMe", iAmEcho => "iAmEcho", badEcho => "badEcho", error => "error", rfc => "rfc", abort => "abort", end => "end", endRep => "endRep", data => "data", aData => "aData", ack => "ack", mark => "mark", int => "int", intRep => "intRep", aMark => "aMark", eData => "eData", eAck => "eAck", eEnd => "eEnd", eAbort => "eAbort", rpp => "rpp", LOOPHOLE[140B] => "RPC-call-end", LOOPHOLE[141B] => "RPC-data-end", LOOPHOLE[142B] => "RPC-ack", LOOPHOLE[143B] => "RPC-spare", LOOPHOLE[144B] => "RPC-RFA", LOOPHOLE[150B] => "RPC-a-call-end", LOOPHOLE[151B] => "RPC-a-data-end", LOOPHOLE[152B] => "RPC-ping", LOOPHOLE[153B] => "RPC-a-spare", LOOPHOLE[154B] => "RPC-a-RFA", LOOPHOLE[160B] => "RPC-call-more", LOOPHOLE[161B] => "RPC-data-more", LOOPHOLE[170B] => "RPC-a-call-more", LOOPHOLE[171B] => "RPC-a-data-more", gatewayRequest--200-- => IF lookupFile THEN "fileLookup" ELSE "gatewayRequest", gatewayInfo--201-- => IF lookupFile THEN "fileInfo" ELSE "gatewayInfo", dateTenexRequest--202-- => IF lookupFile THEN "fileError" ELSE IF teleSwat THEN "go" ELSE "dateTenexRequest", dateTenexIs--203-- => IF teleSwat THEN "goReply" ELSE "dateTenexIs", LOOPHOLE[204B] => IF teleSwat THEN "ack" ELSE "pt204", dateAltoRequest--206-- => "dateAltoRequest", dateAltoIs--207-- => "dateAltoIs", mailCheck--210-- => "mailCheck", mailIsNew--211-- => "mailIsNew", mailNotNew--212-- => "mailNotNew", mailError--213-- => "mailError", mailCheckLaurel--214-- => "mailCheckL", nameLookup--220-- => "nameLookup", nameIs--221-- => "nameIs", nameError--222-- => "nameError", addressLookup--223-- => "addrLookup", addressIs--224-- => "addrIs", whereIsUser--230-- => "whereIsUser", userIs--231-- => "userIs", userError--232-- => "userError", netDirVersion--240-- => "netDirVersion", sendNetDir--241-- => "sendNetDir", bootFileSend--244-- => "bootFileSend", kissOfDeath--247-- => "kissOfDeath", request--250-- => IF mscSrv THEN "userAuthReq" ELSE "request", result--251-- => IF mscSrv THEN "userAuthOk" ELSE "result", unsolicited--252-- => IF mscSrv THEN "userAuthBad" ELSE "unsolicited", custodian--253-- => "custodian", sync--254-- => "sync", pineAck--255-- => "pineAck", noop--256-- => "noop", bootDirReq--257-- => "bootDirReq", bootDirReply--260-- => IF NOT leaf THEN "bootDirReply" ELSE SELECT LOOPHOLE[b.pupID,SequinID].control FROM data=> "lData", ack=> "lAck", nop=> "nop", restart=> "restart", check=> "check", open=> "open", break=> "break", close=> "close", closed=>"closed", destroy=> "destroy", dallying=>"dallying", quit=> "quit", broken=>"broken", retransmit=> "retransmit", stifle=>"stifle", openclose=> "openClose", opendestroy=>"opendestroy", ENDCASE => "leaf?", TeledebugProtocol.coreStoreRequest--300-- => "wCore", TeledebugProtocol.coreFetchRequest--301-- => "rCore", TeledebugProtocol.diskAddressSetRequest--302-- => "diskAddr", TeledebugProtocol.diskStoreRequest--303-- => "wDisk", TeledebugProtocol.diskFetchRequest--304-- => "rDisk", ENDCASE => NIL; IF type = NIL THEN { WriteString["pt"]; WriteOctal[LOOPHOLE[b.pupType, CARDINAL]]; } ELSE WriteString[type]; }; WriteChar[',]; SELECT b.pupType FROM error => SELECT b.errorCode FROM badChecksumPupErrorCode => WriteString["badChecksum"L]; noProcessPupErrorCode => WriteString["noSuchPort"L]; resourceLimitsPupErrorCode => WriteString["resourceLimits"L]; inconsistentPupErrorCode => WriteString["inconsistentPup"L]; cantGetTherePupErrorCode => WriteString["cantGetThere"L]; hostDownPupErrorCode => WriteString["hostDown"L]; eightHopsPupErrorCode => WriteString["sixteenHops"L]; tooBigPupErrorCode => WriteString["tooBigPup"L]; iAmNotAGatewayPupErrorCode => WriteString["iAmNotAGateway"L]; gatewayResourceLimitsPupErrorCode => WriteString["gatewayResources"L]; ENDCASE => WriteOctal[LOOPHOLE[b.errorCode]]; mailCheck, mailCheckLaurel, nameLookup => IF mode # disk THEN WritePup[b]; nameIs => WriteAddr[b.nameIs[0]]; abort => WriteOctal[b.abortCode]; data, aData => { l: CARDINAL = ContentsBytes[b]; WriteID[b.pupID.b+l]; WriteString[",L:"]; WriteOctal[l]; }; ack => { WriteID[b.pupID.b]; WriteString[",pups:"]; WriteOctal[b.numberOfPupsAhead]; }; mark, aMark => { WriteID[b.pupID.b+1]; WriteString[",mk:"]; WriteOctal[b.pupBytes[0]]; }; rfc, echoMe => { wellKnown: STRING = SELECT b.dest.socket FROM PupTypes.telnetSoc => "telnet", PupTypes.gatewaySoc => "gateway", PupTypes.ftpSoc => "ftp", PupTypes.miscSrvSoc => "miscSrv", PupTypes.echoSoc => "echo", PupTypes.bspTestSoc => "bspTest", PupTypes.mailSoc => "mail", PupTypes.eftpReceiveSoc => "eftp", PupTypes.copyDiskSoc => "copyDisk", PupTypes.rpcpSoc => "rpcp", PupTypes.librarianSoc => "librarian", PupTypes.pineSoc => "pine", ENDCASE => NIL; IF wellKnown # NIL THEN WriteString[wellKnown] ELSE { IF b.dest.socket.b IN [50B..57B] AND b.dest.socket.a IN [0..1] THEN { -- RFC or EchoMe on a Grapevine socket -- gvSoc: STRING = SELECT b.dest.socket.b FROM 50B => "RS-enquire", 51B => "RS-update", 52B => "RS-poll", 53B => "GV-Lily", 54B => "MS-poll", 55B => "MS-forward", 56B => "MS-send", 57B => "MS-retrieve", ENDCASE => ERROR; IF b.dest.socket.a = 1 THEN WriteString["Test-"]; WriteString[gvSoc]; } ELSE IF b.dest.socket.a = 0 AND b.dest.socket.b IN [0 .. 256) THEN WriteOctal[b.dest.socket.b]; }; }; end, endRep, int, intRep => NULL; IN [ LOOPHOLE[140B]..LOOPHOLE[171B] ] => --Cedar RPC packet-- { RPCHeader: TYPE = MACHINE DEPENDENT RECORD[ length (0:0..14): [0..77777B], oddByte (0:15..15): { no(0), yes(1) }, type (1): RPCPktType, destPSB (2): CARDINAL--PSB.PsbIndex--,-- field has 6 extra bits srcePSB (3): CARDINAL--PSB.PsbIndex--,-- field has 6 extra bits destHost (4): CARDINAL--Machine--, destSoc (5): PupTypes.PupSocketID, srceHost (7): CARDINAL--Machine--, srceSoc (8): PupTypes.PupSocketID, convLS (10): CARDINAL--PktConversationID--, convMS (11): CARDINAL, pktID (12): RPCPktID, dispatcher (16): DispatcherDetails ]; RPCPktType: TYPE = MACHINE DEPENDENT RECORD[ -- "type" word of a Pup -- transport (0:0..7): [0..255], -- should be zero before sending -- subType (0:8..10): { rpc(3B), (7B) }, eom (0:11..11): { end(0), notEnd(1) }, ack (0:12..12): { dontAck(0), pleaseAck(1) }, class (0:13..15): { call(0), data(1), ack(2), rfa(4), (7) } ]; RPCPktID: TYPE = MACHINE DEPENDENT RECORD[ activity(0): CARDINAL--PSB.PsbIndex--,-- field has 6 extra bits-- callSeqLS(1): CARDINAL--CallCount--, callSeqMS(2): CARDINAL, pktSeq(3): CARDINAL ]; DispatcherDetails: TYPE = MACHINE DEPENDENT RECORD[ mds: CARDINAL, -- top half of dispatcher's MDS base address -- dispatcherID: LONG CARDINAL--DispatcherID--, dispatcherHint: CARDINAL--ExportHandle-- -- hint to exporter host's export table -- ]; h: LONG POINTER TO RPCHeader = LOOPHOLE[b]; overhead: CARDINAL = SIZE[RPCHeader]+1--checksum--; WriteString["Len:"]; IF h.length < overhead THEN { WriteString["?-"L]; WriteOctal[overhead-h.length] } ELSE WriteOctal[h.length-overhead]; WriteString[",Cnv:"]; WriteOctal[h.convLS MOD 1000B]; WriteString[",Call:"]; WriteOctal[h.pktID.callSeqLS MOD 1000B]; WriteString[",Pkt:"]; WriteOctal[h.pktID.pktSeq MOD 1000B]; IF h.type.class = call THEN { WriteString[",Disp:"]; WriteOctal[h.dispatcher.dispatcherHint MOD 1000B] }; }; addressLookup => WriteAddr[b.nameIs[0]]; gatewayRequest => IF lookupFile AND mode # disk THEN WritePup[b]; bootDirReply => IF leaf THEN { id: SequinID = LOOPHOLE[b.pupID]; IF b.dest.socket = leafSocket THEN { WriteString["rec:"]; WriteOctal[id.receiveSequence]; WriteString[",send:"]; WriteOctal[id.sendSequence]; } ELSE { WriteString["send:"]; WriteOctal[id.sendSequence]; WriteString[",rec:"]; WriteOctal[id.receiveSequence]; }; IF id.control = data THEN { l: CARDINAL = ContentsBytes[b]; WriteString[",L:"]; WriteOctal[l]; }; }; request => IF mscSrv THEN WriteString[@(b.pupString)] --userAuthReq-- ELSE WriteString["Pine-request"]; result => IF mscSrv THEN DefaultBodyPrintout[b] --userAuthOk-- ELSE WriteString["Pine-result"]; unsolicited => IF mscSrv THEN DefaultBodyPrintout[b] --userAuthBad-- ELSE WriteString["Pine-unsolicited"]; custodian => WriteString["Pine-unsolicited"]; sync => NULL; pineAck => NULL; noop => NULL; TeledebugProtocol.coreStoreRequest, TeledebugProtocol.coreFetchRequest => IF teleSwat THEN WriteLongOctal[LOOPHOLE[@b.pupBody, LONG POINTER TO TeledebugProtocol.CoreStoreRequest].page]; TeledebugProtocol.diskAddressSetRequest, TeledebugProtocol.diskFetchRequest => IF teleSwat AND ContentsBytes[b] = 2*SIZE[TeledebugProtocol.DiskAddressSetRequest] THEN WriteLongOctal[LOOPHOLE[@b.pupBody, LONG POINTER TO TeledebugProtocol.DiskAddressSetRequest].page]; ENDCASE => DefaultBodyPrintout[b]; WriteChar[']]; IF mode = disk THEN { LookerDefs.DiskPos[diskDetails]; WriteString["ID="]; WritePair[b.pupID]; WriteString[" from="]; WritePair[LOOPHOLE[b.source.socket]]; WriteString[" to="]; WritePair[LOOPHOLE[b.dest.socket]]; { l: CARDINAL = MIN[ContentsBytes[b],maxPupLength]; WriteChar[Ascii.CR]; LookerDefs.DiskPos[diskFrom]; LookerDefs.DiskPos[diskPkt]; IF l = 0 THEN WriteString["{empty}"] ELSE WritePup[b]; WriteChar[Ascii.CR]; LookerDefs.DiskPos[diskFrom]; LookerDefs.DiskPos[diskPkt]; WriteString["Bytes="]; IF l = 0 THEN WriteString["{empty}"] ELSE FOR i: CARDINAL IN [0..l) DO WriteOctal[b.pupBytes[i]]; WriteChar[Ascii.SP] ENDLOOP; WriteChar[Ascii.CR]; LookerDefs.DiskPos[diskFrom]; LookerDefs.DiskPos[diskPkt]; WriteString["Words="]; IF l = 0 THEN WriteString["{empty}"] ELSE FOR i: CARDINAL IN [0..l/2) DO WriteOctal[b.pupWords[i]]; WriteChar[Ascii.SP] ENDLOOP; }; } ELSE IF b.pupType = data OR b.pupType = aData THEN { LookerDefs.SetPos[tabData]; WritePup[b] }; WriteChar[Ascii.CR]; }; }; speed: {slow, fast} _ fast; lookerCount: CARDINAL; StartPause: PROC = { WriteString["Pausing ..."L]; LookerDefs.SendNow[]; LookerDefs.NotePausing[TRUE]; pauseWanted _ TRUE; }; LookerMain: PROC = { countLimit: CARDINAL = 40; Process.SetPriority[lookerPriority _ wantedPriority]; DO ENABLE ABORTED => EXIT; this: BufferIndex = GetBuffer[]; IF ActivateLooker[] THEN { IF lost[this] THEN { WriteString["Lost packet(s)"L]; WriteChar[Ascii.CR] }; Watch[buffers + this * bufferSize, times[this]]; ReturnBuffer[]; IF speed = slow AND (lookerCount _ lookerCount+1) >= (LookerDefs.ScreenLines[] * 3) / 4 THEN StartPause[]; DeactivateLooker[]; }; ENDLOOP; }; WriteDiskLog: PROC = { WriteChar _ LookerDefs.DiskChar; WriteMultiple _ LookerDefs.DiskMultiple; mode _ disk; WriteChar[Ascii.CR]; ReplayBuffers[]; IF logged[GetBuffer[]] THEN { WriteString["{ continued from previous logged packets }"L]; WriteChar[Ascii.CR]; THROUGH [1..fullBuffers] DO this: BufferIndex = GetBuffer[]; IF logged[this] THEN prevMS _ MSec[times[this]] ELSE EXIT; ReturnBuffer[]; ENDLOOP; } ELSE { WriteString["Watching host "L]; WriteOctal[wanted.net]; WriteChar['#]; WriteOctal[wanted.host]; WriteChar['#]; WriteChar[Ascii.CR]; }; THROUGH [1..fullBuffers] DO this: BufferIndex = GetBuffer[]; IF lost[this] THEN { WriteString["Lost packet(s)"L]; WriteChar[Ascii.CR] }; Watch[buffers + this * bufferSize, times[this]]; logged[this] _ TRUE; ReturnBuffer[]; ENDLOOP; WriteChar _ LookerDefs.DisplayChar; WriteMultiple _ LookerDefs.DisplayMultiple; mode _ display; LookerDefs.DiskCommit[]; }; WriteTitle: PROC = { LookerDefs.WriteTitle[IO.PutFR[ "PUPWatch: watching host %b#%b#", [integer[wanted.net]], [integer[wanted.host]] ] ]; }; DoAction: PUBLIC PROC[act: LookerDefs.InputAction] = { alreadyPaused: BOOL; ActivateInput[]; alreadyPaused _ pauseWanted; IF pauseWanted AND act.act # pauseContinue THEN WriteChar[Ascii.CR]; lookerCount _ 0; WITH act: act SELECT FROM fast => { WriteString["Fast"L]; speed _ fast; LookerDefs.NoteSlow[FALSE]; pauseWanted _ FALSE; }; newHost => { WriteString["Host ... "]; LookerDefs.SendNow[]; GiveEthernet[]; -- stops driver -- { outcome: LookupOutcome; net, host: [0..255]; [outcome, net, host] _ Lookup[act.name]; SELECT outcome FROM ok => { WriteString["ok"L]; LookerDefs.SendNow[]; wanted.net _ [net]; wanted.host _ [host]; LookerDefs.Clear[]; FlushBuffers[]; WriteTitle[]; pauseWanted _ FALSE; }; noResponse => WriteString["no name-lookup response"L]; badName => WriteString["name not found"L]; noRoute => WriteString["no route to that host"L]; ENDCASE => ERROR; }; TakeEthernet[]; -- starts driver -- }; pktSize => AllocBuffers[big: act.big]; replay => { LookerDefs.Clear[]; WriteString["Replay (Slow)"L]; ReplayBuffers[]; speed _ slow; LookerDefs.NoteSlow[TRUE]; pauseWanted _ FALSE; }; slow => { WriteString["Slow"L]; speed _ slow; LookerDefs.NoteSlow[TRUE]; pauseWanted _ FALSE; }; start => { InitTabs[]; AllocBuffers[big: FALSE]; PupDefs.PupPackageMake[]; wanted _ PupDefs.AnyLocalPupAddress[PupTypes.fillInSocketID]; myNet _ wanted.net; WriteTitle[]; pauseWanted _ FALSE; lookerProcess _ FORK LookerMain[]; TakeEthernet[]; }; stop => { Process.Abort[lookerProcess]; JOIN lookerProcess; GiveEthernet[]; FreeBuffers[]; }; writeLog => IF version = boot THEN WriteString["""Write log"" isn't implemented in this version"L] ELSE { WriteString["Writing log file ... "L]; LookerDefs.SendNow[]; WriteDiskLog[]; WriteString["ok"L]; }; pauseContinue => IF pauseWanted THEN { WriteString[" continuing"L]; pauseWanted _ FALSE; } ELSE StartPause[]; ENDCASE => ERROR; IF NOT pauseWanted OR alreadyPaused THEN WriteChar[Ascii.CR]; IF NOT pauseWanted AND act.act # stop THEN LookerDefs.NotePausing[FALSE]; DeactivateInput[]; }; LookupOutcome: TYPE = { ok, badName, noResponse, noRoute }; Lookup: PROC [name: Rope.ROPE] RETURNS[outcome: LookupOutcome, net, host: [0..255]] = { addr: PupDefs.PupAddress; outcome _ ok; addr _ PupDefs.GetPupAddress[, IF name.Length[] = 0 THEN "ME" ELSE name ! PupDefs.PupNameTrouble => { outcome _ SELECT code FROM noRoute => noRoute, noResponse => noResponse, ENDCASE => badName; CONTINUE } ]; net _ addr.net; host _ addr.host; }; version: { bcd, image, boot } _ bcd; lookerProcess: PROCESS; SELECT version FROM image => { STOP }; bcd => { NULL }; boot => { NULL }; ENDCASE => ERROR; Process.EnableAborts[@bufferChange]; Process.EnableAborts[@inputChange]; Process.EnableAborts[@lookerChange]; }. ðLooker.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Russ Atkinson, July 25, 1984 11:33:35 pm PDT Andrew Birrell October 25, 1983 11:29 am Pup level -- TEMP: patch to prevent RPCRuntime getting confused -- Synchronization with user type-in -- Output subroutines -- Display line layout is: 7777: from 77#377#777 [aData,L:777,to:177777]abcdefghijklmn 7777: to 77#377#777 [aData,L:777,to:177777]abcdefghijklmn Disk log line layout is: 7777: from 377#377#377 [aData,L:1024,to:177777] abcdefghijklmn 7777: to 377#377#377 [aData,L:1024,to:177777] abcdefghijklmn Fastest case, probably most common, too. Second fastest case, probably second most common, too. layout of Pup id's for Sequin/Leaf protocol -- registered pup types Cedar RPC pup types. Bits are: 8..10: 3 (=> start at pt140) 11: {end(0),notEnd(1)} 12: {dontAck(0),pleaseAck(1)} 13..15: {call,data,ack,spare,rfa} unregistered pup types (possible overlap) also dateTextRequest (socket 4), statisticsRequest (socket 22) also dateTextIs (socket 4), statisticsAre (socket 22) also eventReport (socket 30) also eventReportReply (socket 30) *** RPC header layout, stolen from RPCPkt.mesa -- end of standard Pup header -- For secure conversations, the remainder of the packet must be encrypted -- [ConversationID,PktID] uniquely identifies pkt for all hosts and time -- *** -- WriteString[",alloc:"]; WriteOctal[id.allocate]; user command input -- InputAction: TYPE = RECORD[SELECT act: * FROM fast => NULL, newHost => [name: Rope.ROPE], pauseContinue => NULL, quit => NULL, replay => NULL, slow => NULL, writeLog => NULL, pktSize => [big: BOOL], ENDCASE]; Address Lookup -- Initialisation -- Our display module hasn't been started yet -- We're now fully running, possibly after ether-booting -- Ê!ᘚœ ™ Jšœ Ïmœ1™Jš œžœžœžœž œ˜DJš œžœžœžœž œ˜8Jšœžœžœžœ ˜8Jšœžœžœžœ˜0J˜%J˜(˜0Jšœžœ˜—šœžœ˜Jš œ žœžœžœž œ˜.—šœžœžœžœ˜,J˜!Jšœžœ˜Jš œ žœžœžœž œ˜-—Jšœžœžœ˜8Jšžœ˜ J˜——Jš œžœžœžœžœ ˜*J˜š Ÿ œžœ žœžœž˜9Jšœžœ˜J˜—Jšœ žœ˜Jšœ žœ˜"J˜Jšœ žœ ˜Jš œ žœžœžœÏcœžœ˜CJ˜Jš œžœžœ žœžœ˜7J˜Jš œžœžœ žœžœžœžœ˜4Jš œžœžœ žœžœžœžœ˜6J˜Jšœ -˜GJšœ !˜;J˜Jšœž œ˜Jšœžœžœ˜Jšœ žœžœ +˜BJ˜šŸ œžœžœžœ˜%Jšœ˜Jšžœ žœžœ˜$Jš œ žœžœžœžœžœ˜JJšœžœ žœžœ˜IJšœ žœ(˜4Jšœžœžœžœ˜J˜!J˜—š Ÿœžœžœžœžœ˜.Jšžœžœžœ˜šžœ ˜"Jšžœ6˜:—šžœ žœ ˜šžœ˜Jš žœ žœ žœžœ žœ˜=Jšžœžœ˜ Jšœ˜—šžœ˜Jšœžœžœžœ˜!Jšœ˜——Jšœ˜J˜—šŸœžœžœ˜ Jšœžœ˜Jšžœ žœžœ˜*J˜—šŸ œžœžœ˜Jšžœžœžœ˜Jšžœ žœžœ žœ˜.Jšœžœ˜Jšžœžœžœžœ˜0Jšœ˜J˜—šŸœžœžœ˜Jšœžœž œ ˜+Jšœ˜J˜—šŸ œžœžœžœ˜9J˜Jšœ˜J˜—Jšœ™J˜J˜!J˜Jšœ™Jšœ;™;Jšœ;™;Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜J˜Jšœ™Jšœ>™>Jšœ>™>Jšœ žœ˜Jšœ žœ˜Jšœ žœ˜J˜šŸœžœ˜J˜*˜J˜:—J˜3J˜9J˜DJšœ ˜(Jšœ ˜3Jšœ !˜>Jšœ˜J˜—JšŸ œžœž œ˜4J˜JšŸ œžœžœžœ˜?J˜šŸ œžœžœž œžœžœžœžœž œ˜DJ˜J˜—šŸ œžœžœžœ˜%Jšœž œ˜.Jšœ˜J˜—šŸœžœ˜Jšœž œžœ"˜Mšœ˜J˜——šŸ œžœžœ˜!šžœž˜ šœ˜Jšœ(™(Jšžœžœ˜ —šœ ˜ Jšœ6™6Jšœžœžœ˜$—šœ ˜ Jšœ-žœžœžœ˜D—šœ˜Jšžœžœ˜#Jšœžœ˜"—Jšžœ˜—Jšœžœ˜ Jšœžœ˜Jšœžœ˜Jšžœžœ˜ šžœ ˜Jšœžœ˜—Jšœ˜J˜—šŸœžœžœžœ˜*šžœž˜ Jšœ˜šžœ˜ Jšœžœ˜Jšœ žœžœ˜Jšœžœžœ ˜Jšžœžœžœ˜Ašžœž˜Jšœžœ˜Jšœ˜J˜J˜Jšžœ˜—J˜——Jšœ˜J˜—šŸ œžœ˜#J˜4J˜—šŸ œžœžœ˜#Jšœ žœ˜Jšœžœ˜Jšžœžœžœ˜8Jšžœ ˜šžœžœ˜Jšœ˜J˜J˜—Jšžœ˜Jšœ˜J˜—šŸ œžœžœ˜4Jšœ˜J˜Jšœ˜J˜Jšœžœ˜5Jšœ˜J˜—šŸœžœžœžœ˜%J˜J˜Jšœ˜J˜—Jšœžœžœ˜J˜Jšœ.™.J˜š œ žœžœž œžœ˜*J˜&J˜-J˜—Jšœžœ ˜J˜šœžœžœž œ˜)J˜$J˜&J˜.J˜6J˜J˜—J˜/J˜5J˜HJ˜šŸœžœ)˜4šŸœžœž˜-J˜4—šœ˜šœžœ(˜4Jšžœ%˜'—šœžœ˜)Jšžœ˜—šœ žœ%˜5Jšžœ"˜$—šœ žœ#˜1Jšžœ ˜"—Jšœžœžœ˜"Jšžœ˜šžœžœžœ ˜™>šœ  œ˜Jšžœ žœ žœ˜1—Jšœ5™5šœ œ˜Jšžœ žœ ˜Jšžœžœ žœžœ˜3—šœ  œ˜Jšžœ žœ žœ˜.—šžœ ˜Jšžœ žœžœ ˜$—Jšœ œ˜-Jšœ  œ˜(Jšœ  œ˜'Jšœ  œ˜'Jšœ  œ˜(Jšœ  œ˜'Jšœ œ˜(Jšœ  œ˜(Jšœ œ˜$Jšœ  œ˜'Jšœ  œ˜(Jšœ  œ˜$Jšœ  œ˜)Jšœ œ˜$Jšœ  œ˜'Jšœ  œ˜+Jšœ™Jšœ  œ˜(Jšœ!™!Jšœ  œ˜*Jšœ  œ˜)šœ œ˜Jšžœžœžœ ˜,—šœ œ˜Jšžœžœžœ ˜*—šœ  œ˜Jšžœžœžœ˜0—Jšœ  œ˜ Jšœ œ˜"Jšœ œ˜%Jšœ œ˜"Jšœ  œ˜(šœ  œ˜Jšžœžœžœ˜šžœžœžœž˜3J˜.J˜1J˜.J˜/J˜1J˜.J˜4J˜3J˜Jšžœ ˜——Jšœ" œ ˜5Jšœ" œ ˜5Jšœ' œ˜=Jšœ" œ ˜5Jšœ" œ ˜5Jšžœžœ˜—Jšžœž˜ šžœ˜J˜Jšœ žœ žœ˜*Jšœ˜—Jšžœ˜—Jšœ˜J˜J˜J˜šžœ ž˜˜šžœ ž˜˜J˜—˜J˜—˜J˜—˜J˜ —˜J˜—˜J˜—˜J˜—˜J˜—˜J˜—˜$J˜!——Jšžœžœ˜-—˜)Jšžœ žœ ˜ —˜ J˜—˜J˜—šœ˜Jšœžœ˜J˜J˜J˜Jšœ˜—šœ˜J˜J˜Jšœ ˜ Jšœ˜—šœ˜J˜J˜Jšœ˜Jšœ˜—šœ˜šœ žœžœž˜-J˜$J˜%J˜!J˜%J˜"J˜%J˜"J˜"J˜&J˜"J˜'J˜"Jšžœžœ˜—Jšžœ ž˜Jšžœ˜šžœ˜Jšžœžœ ˜ Jšžœžœ˜šžœ )˜0šœžœžœž˜+J˜J˜J˜J˜J˜J˜J˜J˜Jšžœžœ˜—Jšžœžœ˜1J˜Jšœ˜—šžœžœ˜Jšžœžœ ˜!Jšžœ˜!—Jšœ˜—Jšœ˜—Jšœžœ˜!šžœžœžœ  ˜=Jšœ˜————Jšœ1™1š œ žœžœž œžœ˜+J˜J˜&J˜Jšœž œ ˜@Jšœž œ ˜@Jšœž  œ˜#J˜#Jšœž  œ˜#J˜#Jšœ™Jšœž œ˜,Jšœžœ˜JšœJ™JJ˜J˜%—š œžœžœž œžœ ˜IJšœ #˜AJ˜%J˜&J˜-J˜>—š œžœžœž œžœ˜.JšœH™HJšœ ž œ ˜AJšœž  œ˜%Jšœžœ˜Jšœ žœ˜—š œžœžœž œžœ˜3Jšœžœ /˜>Jšœžœž œ˜,Jšœž œ *œ˜V—šœ™Jš œžœžœžœ žœ˜+Jšœ žœžœ   œ˜3J˜Jšžœ˜Jšžœ6˜:Jšžœ˜#Jšœ*žœ˜5Jšœ4žœ˜?Jšœ0žœ˜;Jšžœ˜JšžœAžœ ˜RJšœ˜˜J˜—˜Jšžœ žœ žœ ˜/—˜Jšžœ˜šžœ˜Jšœžœ ˜!Jšžœ˜šžœ˜J˜Jšœ˜J˜Jšœ˜Jšœ˜—šžœ˜J˜Jšœ˜J˜Jšœ˜Jšœ˜————Jšœ™šœ™Jšžœ˜šžœ˜Jšœžœ˜J˜J˜Jšœ˜—Jšœ˜˜ Jšžœžœ ˜:Jšžœ˜!—˜ Jšžœžœ ˜4Jšžœ˜ —˜Jšžœžœ ˜5Jšžœ!˜%—J˜-Jšœžœ˜ Jšœ žœ˜Jšœžœ˜ ˜IJšžœ ˜ šžœžœ ˜(Jšžœžœžœ+˜:——˜NJšžœ žœžœ)˜Ršžœžœ ˜(Jšžœžœžœ0˜?——Jšžœ˜"J˜Jšžœ ˜šžœ˜J˜ J˜J˜J˜Jšœ žœ˜%J˜Jšœ žœ˜#šœ˜Jšœžœžœ ˜1Jšœžœ˜J˜J˜Jšžœžœžœ ˜6Jšœžœ˜J˜J˜J˜Jšžœ˜Jšžœ˜šžœžœžœžœ˜šžœ˜Jšœžœ˜—Jšžœ˜—Jšœžœ˜J˜J˜J˜Jšžœ˜Jšžœ˜šžœžœžœžœ ˜ šžœ˜Jšœžœ˜—Jšžœ˜——Jšœ˜Jšœ˜—šžœžœžœ˜-Jšžœ-˜1—Jšœžœ˜Jšœ˜Jšœ˜J˜J˜—J˜Jšœ žœ˜J˜šŸ œžœ˜J˜2Jšœžœžœ˜1Jšœ˜J˜—šŸ œžœ˜Jšœ žœ˜J˜5šžœžœžœžœ˜J˜ Jšžœ˜šžœ˜Jšžœ ˜ Jšžœ3žœ˜=J˜0J˜Jšžœ ˜JšžœD˜GJšžœ˜J˜Jšœ˜——Jšžœ˜Jšœ˜J˜—šŸ œžœ˜J˜ J˜(J˜ Jšœžœ˜J˜Jšžœ˜šžœ˜J˜;Jšœžœ˜Jšžœ˜šžœ!˜#Jšžœ ˜Jšžœ˜Jšžœžœ˜ J˜—Jšžœ˜Jšœ˜—šžœ˜J˜Jšœ&˜&Jšœ'˜'Jšœžœ˜Jšœ˜—Jšžœ˜šžœ!˜#Jšžœ ˜ Jšžœ3žœ˜=J˜0Jšœžœ˜J˜—Jšžœ˜J˜#J˜+J˜J˜Jšœ˜J˜J˜J˜—Jšœ™J˜šŸ œžœ˜šœžœ)˜AJ˜3—Jšœ˜J˜—JšŸœžœžœ˜4Jšœ-™-Jšœ ™ Jšœ™Jšœ™Jšœ ™ Jšœ™Jšœ ™ Jšœ™Jšœ™šœ ™ Jšœ˜Jšœžœ˜J˜J˜Jšžœ žœžœžœ˜DJ˜šžœ žœž˜šœ ˜ J˜Jšœ"žœžœ˜>Jšœ˜—šœ ˜ J˜/Jšœ ˜"šœ˜J˜J˜J˜(šžœ ž˜˜Jšœ˜J˜)J˜)J˜J˜J˜ Jšœžœ˜Jšœ˜—J˜6J˜*J˜1—Jšžœžœ˜—Jšœ˜Jšœ ˜#Jšœ˜—˜ J˜—šœ ˜ J˜J˜Jšœ3žœ˜9Jšœžœ˜Jšœ˜—šœ ˜ J˜Jšœ"žœ˜(Jšœžœ˜Jšœ˜—šœ ˜ J˜ Jšœžœ˜J˜J˜QJ˜ Jšœžœ˜Jšœžœ˜"J˜Jšœ˜—šœ ˜ J˜Jšžœ˜J˜J˜Jšœ˜—˜ Jšžœ˜Jšžœ@˜Dšžœ˜J˜