DIRECTORY BluejaySmarts, GList USING [ DRemove, Nconc ], IO, Jukebox USING [ Handle, IntervalSpec, VoiceDirection ], MBQueue USING [ QueueClientAction ], Process USING [ Detach, SecondsToTicks, SetTimeout ], PupSocket USING [ SetGetTimeout, SetRemoteAddress, Socket ], RecordingServiceRegister USING [ jayShh, jukebox, numParties ], RefID USING [ ID ], ThParty USING [ Advance, ConversationInfo, GetConversationInfo, GetKeyTable, GetPartyInfo, PartyInfo, RegisterKey, ReportAction ], ThPartyPrivate USING [ GetPupSocket ], Thrush USING [ ActionID, ActionReport, ConversationID, ConvEvent, Credentials, EncryptionKey, NB, NetAddress, notReallyInConv, nullConvID, PartyID, ROPE, SHHH, SmartsID, StateInConv ], ThSmarts, VoiceStream USING [ Close, VoiceStreamEvent, Handle, NotifyProc, Open, SetSocket, WaitEmpty, wholeTune ], VoiceUtils USING [ Problem, ReportFR ] ; BluejaySmartsImpl: CEDAR MONITOR IMPORTS GList, IO, MBQueue, Process, PupSocket, RecordingServiceRegister, ThParty, ThPartyPrivate, VoiceStream, VoiceUtils EXPORTS BluejaySmarts, ThSmarts = { OPEN IO; ConversationID: TYPE = Thrush.ConversationID; nullConvID: ConversationID = Thrush.nullConvID; PartyID: TYPE = Thrush.PartyID; SHHH: TYPE = Thrush.SHHH; SmartsID: TYPE = Thrush.SmartsID; StateInConv: TYPE = Thrush.StateInConv; JayInfo: TYPE = BluejaySmarts.JayInfo; NB: TYPE = Thrush.NB; IntID: TYPE = BluejaySmarts.IntID; nullIntID: IntID = BluejaySmarts.nullIntID; IntervalSpec: TYPE = Jukebox.IntervalSpec; IntervalReq: TYPE = BluejaySmarts.IntervalReq; infos: PUBLIC ARRAY[0..100) OF BluejaySmarts.JayInfo_ALL[NIL]; keyDistributionTimeoutInSeconds: INT _ 300; ConvDesc: TYPE = BluejaySmarts.ConvDesc; ConvDescBody: TYPE = BluejaySmarts.ConvDescBody; NconcIntervals: PROC[l1, l2: LIST OF IntervalReq] RETURNS[LIST OF IntervalReq] = INLINE { RETURN[NARROW[GList.Nconc[l1, l2]]]; }; Progress: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, convEvent: Thrush.ConvEvent ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; info: JayInfo = InfoForSmarts[smartsID: convEvent.self.smartsID]; convID: Thrush.ConversationID = convEvent.self.convID; cDesc: ConvDesc _ GetConv[info, convID]; whatNeedsDoing: WhatNeedsDoing; IF cDesc=NIL THEN { VoiceUtils.Problem["No info at Progress", $Bluejay]; RETURN; }; IF convEvent.self.partyID = convEvent.other.partyID THEN { -- own state changed NoteNewState[cDesc, convEvent]; SELECT info.currentConvID FROM convID, nullConvID => NULL; ENDCASE => []_ChangeState[cDesc, $idle, $busy, "One conversation at a time, please"]; RETURN; }; IF convID#info.currentConvID THEN { VoiceUtils.Problem["Strange progress report", $Bluejay]; ForgetConv[cDesc]; RETURN; }; whatNeedsDoing _ whatNeedsDoingIf[cDesc.situation.self.state][convEvent.other.state]; SELECT whatNeedsDoing FROM $noop, $ntiy, $reac, $deac => NULL; -- No action is needed, or we don't yet know what one to take. $imp => ERROR; -- This is supposed to be impossible! $xrep => VoiceUtils.Problem["Didn't expect state change report", $Bluejay]; $frgt => ForgetConv[cDesc]; $invl => { VoiceUtils.Problem["Invalid state transition", $Bluejay]; []_ChangeState[cDesc: cDesc, state: $idle, reason: $error, comment: "System Error: Invalid state transition"]; }; $idle => { nb2: Thrush.NB; cInfo: ThParty.ConversationInfo; [nb2, cInfo] _ ThParty.GetConversationInfo[shh: RecordingServiceRegister.jayShh, convID: convID]; IF nb2 # $success OR (cInfo.numParties-cInfo.numIdle) <= 1 THEN []_ChangeState[cDesc, $idle, IF nb2#$success THEN $error ELSE $terminating]; }; ENDCASE => ERROR; }; Substitution: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, convEvent: Thrush.ConvEvent, oldPartyID: Thrush.PartyID, newPartyID: Thrush.PartyID ] = { ENABLE UNWIND => NULL; -- RestoreInvariant; info: JayInfo = InfoForSmarts[smartsID: convEvent.self.smartsID]; cDesc: ConvDesc _ GetConv[info, convEvent.self.convID]; IF cDesc=NIL THEN { VoiceUtils.Problem["No info at Substitution", $Bluejay]; RETURN; }; NoteNewState[cDesc, convEvent]; }; ReportAction: PUBLIC ENTRY PROC[ shh: SHHH, report: Thrush.ActionReport ] = { ENABLE UNWIND => NULL; info: JayInfo = InfoForSmarts[smartsID: report.self.smartsID]; convID: Thrush.ConversationID = report.self.convID; cDesc: ConvDesc _ GetConv[info, convID]; IF cDesc=NIL THEN { VoiceUtils.Problem["can't find conversation for report", $Bluejay]; RETURN; }; SELECT report.actionClass FROM $keyDistribution => { BROADCAST cDesc.keysMightBeDistributed; cDesc.keysDistributed _ TRUE; RETURN; }; ENDCASE=> shh _ shh; -- a place to stand during debugging }; NoteNewState: INTERNAL PROC[cDesc: ConvDesc, convEvent: Thrush.ConvEvent] = { OPEN now: cDesc.situation.self; nb: Thrush.NB; state: StateInConv _ convEvent.self.state; previousState: StateInConv = cDesc.situation.self.state; info: JayInfo = cDesc.info; cDesc.situation _ convEvent^; IF state = previousState THEN RETURN; -- No conceivable value in acting. IF info.currentConvID=nullConvID THEN info.currentConvID _ cDesc.situation.self.convID; SELECT state FROM $notified => { nb _ OpenConnection[cDesc]; -- Set up Bluejay streams, socket stuff nb _ ChangeState[cDesc, IF nb=$success THEN $active ELSE $idle]; IF nb#$success THEN ERROR; -- should work regardless of everything. }; $idle, $neverWas => ForgetConv[cDesc]; $active => NULL; -- Need take no action until recording or playback requested. ENDCASE; }; GetConversation: PUBLIC ENTRY PROC [ -- exported to BluejaySmarts smartsID: Thrush.SmartsID, conv: Thrush.ConversationID ] RETURNS [nb: Thrush.NB_$success, cDesc: ConvDesc] = { ENABLE UNWIND => NULL; info: JayInfo _ InfoForSmarts[smartsID]; cDesc _ GetConv[info, conv]; nb _ SELECT TRUE FROM info=NIL => $noSuchSmarts2, conv=nullConvID OR cDesc=NIL => $noSuchConv, conv#info.currentConvID => $notInConv, cDesc.situation.self.state#$active => $convNotActive, ENDCASE => $success; IF nb=$success AND cDesc.keyTable=NIL THEN [nb, cDesc.keyTable] _ ThParty.GetKeyTable[info.shh, cDesc.situation.self]; }; DistributeKey: PUBLIC ENTRY PROC [ -- exported to BluejaySmarts cDesc: ConvDesc, key: Thrush.EncryptionKey, wait: BOOL_FALSE ] RETURNS [nb: Thrush.NB, keyIndex: [0..17B]] = TRUSTED { ENABLE UNWIND => NULL; keyID: LONG POINTER TO Thrush.ActionID = LOOPHOLE[LONG[@key]]; num: NAT; [nb, keyIndex] _ ThParty.RegisterKey[credentials: cDesc.situation.self, key: key]; SELECT nb FROM $success, $newKeys, $stateMismatch => NULL; ENDCASE => GOTO Fail; IF wait THEN { [nb, num] _ ThParty.ReportAction[report: [other: cDesc.situation.self, requestingParty: cDesc.situation.self.partyID, actionID: keyID^, actionClass: $keyDistribution, actionType: $newKeys], reportToAll: TRUE, selfOnCompletion: TRUE]; SELECT nb FROM $success, $stateMismatch => nb _ $success; ENDCASE => GOTO Fail; IF num=0 THEN RETURN; nb _ $newKeys; cDesc.keysDistributed _ FALSE; Process.SetTimeout[@cDesc.keysMightBeDistributed, Process.SecondsToTicks[keyDistributionTimeoutInSeconds]]; WAIT cDesc.keysMightBeDistributed; IF ~cDesc.keysDistributed THEN GOTO Fail; }; EXITS Fail => nb _ ChangeState[cDesc, $idle, $error, "Could not distribute encryption key"]; }; SetInterval: PUBLIC ENTRY SAFE PROC [ir: IntervalReq] -- exported to BluejaySmarts RETURNS [nb: Thrush.NB_$success] = CHECKED { ENABLE UNWIND => NULL; IF ir.iSpec.tuneID <0 THEN RETURN[nb: $noTuneSpecified]; IF ir.iSpec.length = -1 THEN ir.iSpec.length _ VoiceStream.wholeTune; IF ir.intID # nullIntID THEN { ir.reportRequested _ TRUE; IssueReport[ir, $scheduled]; }; ir.cDesc.info.intervalReqs _ NconcIntervals[ir.cDesc.info.intervalReqs, LIST[ir]]; }; IssueReport: INTERNAL PROC[req: IntervalReq, event: VoiceStream.VoiceStreamEvent] = { cDesc: ConvDesc _ req.cDesc; info: JayInfo _ cDesc.info; nb: NB; SELECT event FROM $finished, $flushed => { IF event=$finished AND ~req.lastInterval THEN RETURN; IF info.intervalReqs=NIL THEN TRUSTED {VoiceStream.WaitEmpty[info.stream]} ELSE WHILE info.numNotifications<=0 DO WAIT info.reportsExist; ENDLOOP; }; $started, $scheduled => IF ~req.firstInterval THEN RETURN; ENDCASE; nb _ ThParty.ReportAction[ shhh: RecordingServiceRegister.jayShh, report: [ self: cDesc.situation.self, -- placeholder, will be filled in by ThParty other: cDesc.situation.self, requestingParty: req.requestingParty, actionID: req.intID, actionClass: IF req.direction = $record THEN $recording ELSE $playback, actionType: event ], reportToAll: TRUE ].nb; IF nb#$success THEN VoiceUtils.Problem["Bluejay report failed", $Bluejay]; }; IREvent: TYPE = REF IREventRec; IREventRec: TYPE = RECORD [ ir: IntervalReq, event: VoiceStream.VoiceStreamEvent ]; EP: ENTRY PROC[info: JayInfo] = TRUSTED { info.numNotifications _ info.numNotifications + 1; BROADCAST info.reportsExist; }; ReportFromBluejay: VoiceStream.NotifyProc = TRUSTED { ir: IntervalReq _ NARROW[clientData]; irE: IREvent; IF ir=NIL OR ir.cDesc=NIL THEN RETURN; -- We don't report actions that do not affect our requests irE _ NEW[IREventRec _ [ir, event]]; ir.cDesc.info.notifications.QueueClientAction[QdReportFromBluejay, irE]; Process.Detach[FORK EP[ir.cDesc.info]]; -- could be a separate MBQueue }; QdReportFromBluejay: ENTRY PROC[r: REF] = { irE: IREvent _ NARROW[r]; ir: IntervalReq _ irE.ir; event: VoiceStream.VoiceStreamEvent _ irE.event; cDesc: ConvDesc; info: JayInfo; rqst: BOOL_FALSE; irFlush: IntervalReq _ NIL; cDesc _ ir.cDesc; IF cDesc.situation.self.state <= Thrush.notReallyInConv THEN RETURN; info _ cDesc.info; info.numNotifications _ info.numNotifications-1; FOR irS: LIST OF IntervalReq _ info.intervalReqs, irS.rest WHILE irS#NIL DO ir1: IntervalReq _ irS.first; match: BOOL; rqst _ ir1.reportRequested; match _ ir=ir1; IF match AND rqst AND event=$flushed AND ir.direction=record THEN event_$finished; IF rqst AND (~match OR event=$flushed) THEN irFlush _ ir1; IF ~(match AND event=$started) THEN info.intervalReqs _ irS.rest; -- Shorten list IF match THEN EXIT; REPEAT FINISHED => rqst _ FALSE; ENDLOOP; IF irFlush#NIL THEN IssueReport[irFlush, $flushed]; IF rqst AND event#$flushed THEN IssueReport[ir, event]; }; GetConv: --PUBLIC-- INTERNAL PROC[info: JayInfo, convID: ConversationID _ nullConvID] RETURNS [ cDesc: ConvDesc ] = { IF info = NIL THEN RETURN[NIL]; IF convID = nullConvID THEN convID _ info.currentConvID; IF convID = nullConvID THEN RETURN[NIL]; FOR convs: LIST OF BluejaySmarts.ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO IF convs.first.situation.self.convID = convID THEN RETURN[convs.first]; ENDLOOP; cDesc _ NEW[ConvDescBody_[]]; cDesc.situation.self.convID _ convID; cDesc.info _ info; info.conversations _ CONS[cDesc, info.conversations]; }; InfoForSmarts: INTERNAL PROC [ smartsID: SmartsID ] RETURNS [ info: JayInfo_NIL ] = { FOR i: NAT IN [0..RecordingServiceRegister.numParties) DO IF smartsID=infos[i].credentials.smartsID THEN RETURN [ infos[i] ]; ENDLOOP; }; ChangeState: INTERNAL PROC[ cDesc: ConvDesc, state: StateInConv, reason: ATOM_NIL, comment: ROPE_NIL] RETURNS [nb: NB] = { convEvent: Thrush.ConvEvent; [nb, convEvent] _ ThParty.Advance[ shhh: RecordingServiceRegister.jayShh, credentials: cDesc.situation.self, state: state, reason: reason, comment: comment, reportToAll: TRUE -- parameterize if other states than $idle and $active become possible ]; SELECT nb FROM $success => NoteNewState[cDesc, convEvent]; $stateMismatch => { VoiceUtils.Problem["Statemismatch for recording party", $Bluejay]; NoteNewState[cDesc, convEvent]; nb _ ChangeState[cDesc, $idle, $error, "State Mismatch bug"]; }; $noSuchConv => VoiceUtils.Problem["Conv doesn't exist at Advance", $Bluejay]; $partyAlreadyActive => NULL; -- Deal with $canActivate here some time ENDCASE =>ERROR; --=> AssessDamage[nb, cDesc, convEvent] Error management needed }; ForgetConv: INTERNAL PROC[cDesc: ConvDesc ] = { info: JayInfo _ cDesc.info; CloseConnection[cDesc]; IF cDesc.situation.self.convID = info.currentConvID THEN info.currentConvID _ nullConvID; info.conversations _ NARROW[GList.DRemove[cDesc, info.conversations]]; }; OpenConnection: INTERNAL PROC[cDesc: ConvDesc] RETURNS[nb: NB] = TRUSTED { info: JayInfo = cDesc.info; pInfo: ThParty.PartyInfo; socket: PupSocket.Socket; remoteAddress: Thrush.NetAddress; IF info.stream=NIL THEN info.stream _ VoiceStream.Open[jukebox: RecordingServiceRegister.jukebox, proc: ReportFromBluejay]; [nb, pInfo] _ ThParty.GetPartyInfo[credentials: cDesc.situation.self, nameReq: $none, allParties: TRUE]; IF nb # $success OR pInfo[0].partyID=0 THEN { VoiceUtils.Problem["No conversation info, or incomplete", $Bluejay]; RETURN; }; -- This is really bad! socket _ ThPartyPrivate.GetPupSocket[pInfo[1].partyID]; IF socket=NIL THEN ERROR; remoteAddress _ pInfo[1].socket; -- Transmit to remote net and host remoteAddress.socket _ pInfo[0].socket.socket; -- Transmit to own assigned socket ID PupSocket.SetRemoteAddress[socket, remoteAddress]; PupSocket.SetGetTimeout[socket, 100]; VoiceStream.SetSocket[socket: socket, handle: info.stream]; VoiceUtils.ReportFR["C %d ", $Bluejay, NIL, card[info.credentials.smartsID]]; }; CloseConnection: INTERNAL PROC[cDesc: ConvDesc] = TRUSTED { info: JayInfo = cDesc.info; IF info.stream#NIL THEN VoiceStream.Close[info.stream]; info.stream _ NIL; VoiceUtils.ReportFR["D %d ", $Bluejay, NIL, card[info.credentials.smartsID]]; }; WhatNeedsDoing: TYPE = ATOM; -- { whatNeedsDoingIf: ARRAY StateInConv OF ARRAY StateInConv OF WhatNeedsDoing _ [ [ $imp, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt], --neverWas [ $imp, $noop, $frgt, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $ntiy ], -- idle [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- failed [ $imp, $imp, $imp , $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- reserved [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- parsing [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp , $imp, $imp, $imp ], -- initiating [ $imp, $idle,$idle, $invl, $invl, $invl, $xrep, $noop, $noop, $ntiy, $noop, $ntiy ], -- notified [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- ringback [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- ringing [ $imp, $idle,$idle,$invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $noop, $ntiy ], -- canActivate [ $imp, $idle,$idle,$invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $reac, $deac ], -- active [ $imp, $idle,$idle,$invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ntiy, $ntiy ] -- inactive (current ^) ]; }. BluejaySmartsImpl.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Last modified by D. Swinehart, October 19, 1986 4:17:20 pm PDT Doug Terry, November 18, 1986 3:55:55 pm PST Needing this is a travesty. See comments about sockets in ThPartyPrivate. Types, Definitions Call Supervision Some party has changed state in a conversation presumably relevant. (new conversation and we're idle) or (this is the one we like) We have to reject this, since we're already dealing with another conv. We are (still) willing to seriously consider only one conversation at a time. A change in a conversation we haven't previously heard about?? Someone else's state changed in a conv. we're interested in; see if it means anything to us! A poacher has substituted for a poachee, or the other way around. Update state so that if it's us, we know who we are. Tunes: control of recording and playback We don't want to schedule new voice until the encryption keys for it have been distributed. An ActionReport is issued to tell everybody about the keys and then tell us that it's told everybody. We wait one time around a timeout for a special condition variable (ugh) and then give up. If the event is a completion event ($finished or $flushed), the activity might have been a write. If it was a write, the tune will still be locked, and actions the client might take referring to the tune may fail. To avoid this, we want to wait until Bluejay has finished everything up (if there's no further activity scheduled), or  when there is activity  until Bluejay has moved on to the next request (indicated by the next $started report). ReportFromBluejay queues its reports, but it also nudges a condition variable and associated count in real time, so that one can wait in one report for the next one to come in. Since they're serialized, there will be at most one of these waiters-for-report pending per Bluejay Smarts. Reports are queued because they can be generated directly by procedures called from entry procedures in this module. Perhaps the queueing should be done by Bluejay? Any interval prior to the one being reported was not finished normally. We treat it as if it were flushed. We remember the last one that requested a report. We extend that to include the interval being reported if the event is $flushed and it wasn't a record request (which we convert to $finished.) Once we reach the reported interval in our saved list of requests, we report on any intervals that were flushed, then on the reported intervals, if a report was requested for it and the event is $started or $finished. Recording is usually stopped by a flush event; we treat it as finished. Utilities We and some other smarts acting on our behalf requested something at the same time. The other one won. This should not have happened. The important thing is not to get hung up in weird loops. Unusual, but possible if idle report took a real long time to get here. Neither this nor Bluejay deals at all properly with multicasting. Worse yet on recording! See also ThPartyPrivate discussion about sockets, and discussion of socket assignments in LarkSmartsSupImpl.ComputeConnection. This is a value that includes our own net/host, but the remote party's assigned socket ID. State Transition Tables Just codes to dispatch on in Supervisor; explained there $noop, $idle, $frgt, $reac, $deac, -- cases explained in code $invl, -- considered an invalid request $xrep, -- we got a report we feel we shouldn't have got $ntiy, -- not implemented yet $imp -- this situation should not arise even in the face of invalid requests }; If we're in the state identified by the row, and someone else in the conversation reports a transition onto the state identified by the column, what should we do? never idle failed resrv pars init notif rback ring canAc activ inact -- _ (other) (Clip this table to view without these comments.) This situation arises when we've forgotten about the conversation that somebody else is still reporting on. We don't expect to hear from others while we're deciding whether to play failed column and possibly idle column is bogus! Swinehart, May 15, 1985 10:30:42 am PDT Cedar 6.0 changes to: InitJay Swinehart, May 22, 1985 12:07:31 pm PDT recording => service, Jay => Recording changes to: InitJay Swinehart, May 17, 1986 3:53:47 pm PDT Cedar6.1 changes to: DIRECTORY, BluejaySmartsImpl, Report, Supervise, OpenConnection, CloseConnection, ReportDoneEntry, InitJay, ReportBluejay Doug Terry, July 29, 1986 11:41:35 am PDT Removed operations for recording and playing voice ropes; their implementations are included in VoiceRopeImpl. changes to: DIRECTORY, BluejaySmartsImpl, IntID, nullIntID, IntervalSpec, NoteNewState, SetInterval, IssueReport, GetConv, InitJay Doug Terry, July 30, 1986 12:07:36 pm PDT Moved JayInit to RecordingServiceRegister. changes to: DIRECTORY, BluejaySmartsImpl, CloseConnection, ConvDescBody Doug Terry, August 18, 1986 2:16:07 pm PDT Tracked change to BluejaySmarts.IntervalReqBody. changes to: DIRECTORY, IntID, nullIntID, SetInterval, IssueReport Êl˜šœ™Icodešœ Ïmœ7™BJšœ>™>K™,—J™šÏk ˜ J˜Jšœžœ˜!Jšžœ˜Jšœ žœ*˜8Jšœ žœ˜(Jšœžœ(˜5Jšœ žœ-˜=Jšœžœ!˜?Jšœžœžœ˜Jšœ žœu˜ƒšœžœ˜&JšÏtœJŸ™L—Jš œžœRžœ4žœžœ˜¹J˜ Jšœ žœX˜iJšœ žœ˜&J˜J˜—šœž ˜ Jšžœžœi˜zJšžœ˜#Jšžœžœ˜J˜—™J˜šœžœ˜-Jšœ/˜/—Jšœ žœ˜Jšžœžœ žœ˜Jšœ žœ˜!J˜'Jšœ žœ˜&J˜Jšžœžœ žœ˜Jšœžœ˜"Jšœ+˜+Jšœžœ˜*Jšœ žœ˜.J˜Jš œž œ žœžœžœ˜>Jšœ!žœ˜+J˜Jšœ žœ˜(šœžœ˜0J™—šÏnœžœ žœžœžœžœžœ˜PJšžœžœžœ˜0—J˜—™J˜š œž œžœ˜Jšœ žœ˜Jšœ˜Jšœ˜KšžœžœžœÏc˜-J™CJšœA˜AJšœ6˜6Jšœ(˜(J˜Jšžœžœžœ8žœ˜SJ˜šžœ2žœ¡˜OKšœ˜šžœž˜šœžœ˜Jšœ>™>—šžœN˜UJšœF™FK™M——Kšžœ˜K˜—šžœžœ˜#J™>JšœLžœ˜V—J˜Jšœ\™\KšœU˜Ušžœž˜šœžœ˜#Kš¡>˜>—Kšœžœ¡%˜4KšœK˜KK˜šœ ˜ Kšœ9˜9šœ*˜*KšœC˜C—Kšœ˜—šœ ˜ Kšœ žœ"˜0Kšœa˜ašžœžœ'ž˜?šœ˜Kšžœžœžœ˜/——K˜—Kšžœžœ˜—J˜J˜—š  œž œžœ˜ Jšœ žœ˜Jšœ˜J˜J˜Jšœ˜Kšžœžœžœ¡˜-J™wJšœA˜AJšœ8˜8Jšžœžœžœ<žœ˜WJ˜J˜J˜—š  œž œžœ˜ Jšœžœ˜ Jšœ˜Jšœ˜Kšžœžœžœ˜Jšœ>˜>Jšœ3˜3Jšœ(˜(KšžœžœžœGžœ˜bšžœž˜šœ˜Kšž œ˜(Kšœžœ˜Kšžœ˜Kšœ˜—Kšžœ¡$˜9—J˜J˜—š  œžœžœ2˜MKšžœ˜Kšœ žœ˜K˜*K˜8K˜K˜Kšžœžœžœ¡"˜HKšžœžœ2˜Wšžœž˜šœ˜Kšœ¡'˜CKšœžœ žœ žœ˜@Kš žœ žœžœ¡Ÿ¡%Ÿ˜EK˜—K˜&Kšœ žœ¡=˜NKšžœ˜—K˜K˜——Jšœ(™(™š  œžœžœžœ¡˜BJšœ˜Jšœ˜Jšœ˜Jšžœ žœ˜5Jšžœžœžœ˜Jšœ(˜(Jšœ˜šœžœžœž˜Jšœžœ˜Jšœžœžœžœ ˜,Jšœžœ ˜&Jšœ5˜5Jšžœ ˜—šžœ žœžœž˜*JšœK˜K—J˜J˜—š   œžœžœžœ¡˜@Jšœ˜Jšœ˜Jšœžœž˜J˜Jšžœ žœžœ˜7Jšžœžœžœ˜Jš œžœžœžœžœžœ˜>Jšœžœ˜ JšœR˜RJš žœžœ'žœžœžœ˜Pšžœžœ˜Jšœž™žKšœËžœžœ˜éJšžœžœ,žœžœ˜OJšžœžœžœ˜Jšœ˜Jšœžœ˜Kšœl˜lJšžœ˜"Jšžœžœžœ˜)K˜—šž˜JšœV˜V—J˜—J˜š   œžœžœžœžœ¡˜SJšžœ žœ žœ˜,Jšžœžœžœ˜Jšžœžœžœ˜8Jšžœžœ)˜Ešžœžœ˜Jšœžœ˜Jšœ˜J˜—JšœHžœ˜RJ˜J˜—š  œž œ;˜UJ˜J˜Jšœžœ˜J™àšžœž˜šœ˜Jšžœžœžœžœ˜5Jšžœžœžœžœ%˜JJš žœžœžœžœžœ˜GJ˜—Jšœžœžœžœ˜:Jšžœ˜—šœ˜JšœŸ˜&šœŸ˜ Jšœ¡,˜HJ˜J˜%J˜Jšœ žœžœ žœ ˜GJ˜JšœŸ˜—Jšœ ž˜JšœŸ˜—Jšžœ žœ7˜JJ˜J˜—JšœvŸœ/Ÿ™§J™Jšœ žœžœ ˜šœ žœžœ˜J˜J˜#J˜J˜—šÐbkœžœžœžœ˜)J˜2Jšž œ˜J˜—˜J˜—š œžœ˜5Jšœžœ ˜%J˜ Jš žœžœžœ žœžœžœ¡:˜aJšœžœ˜$J˜HJšœžœžœ¡˜FJšœ˜J˜—š œ œžœžœžœ˜,J™®J™ÙJ™Jšœžœ˜Jšœ˜J˜0J˜J˜Jšœžœžœ˜Jšœžœ˜J˜Jšžœ6žœžœ˜DJ˜J˜0š žœžœžœ+žœžœž˜KJ˜Jšœžœ˜ Jšœ˜Jšœ˜š žœžœžœžœžœ˜RJ™G—Jšžœžœ žœžœ˜:Jšžœ žœžœ¡˜QJšžœžœžœ˜Jšžœžœ žœ˜ Jšžœ˜—Jšžœ žœžœ ˜3Jšžœžœžœ˜7J˜—˜J™——™ J™š  œ¡Ðck¡œžœžœ4˜UJšžœ˜Jš žœžœžœžœžœ˜Jšžœžœ˜8Jšžœžœžœžœ˜(š žœžœžœ9žœžœž˜]Jšžœ,žœžœ˜GJšžœ˜—Jšœžœ˜Jšœ%˜%J˜Jšœžœ˜5J˜—J˜š   œžœžœžœžœ˜Ušžœžœžœ*ž˜9Jšžœ(žœžœ˜CJšžœ˜—Jšœ˜J˜—š  œžœžœ˜Jš œ-žœžœ žœžœ˜IJšžœžœ˜J˜šœ"˜"Jšœ&˜&J˜"Jšœ ˜ Jšœ˜Jšœ˜Jšœ žœ¡F˜XJ˜—šžœž˜K˜+˜K™ÂKšœB˜BK˜Kšœ=˜=K˜—šœM˜MKšœG™G—Kšœžœ¡*˜GKšžœ¡(Ðct¡¤˜S—˜J™——š  œžœžœ˜/J˜J˜Jšžœ2žœ!˜YJšœžœ+˜FJ˜—J˜š  œžœžœžœžœžœ˜JJ˜J˜J˜J˜!Jšžœ žœžœd˜{šœ ˜ JšœTžœ˜Z—šžœžœžœ˜-JšœEžœ¡˜f—JšŸœ¶¡#œŸ™Ü˜7JšœWžœ™\—Jšžœžœžœžœ˜Jšœ!¡"˜CJšœ/¡%˜TJšœ2˜2Jšœ%˜%Jšœ;˜;Jšœ'žœ#˜MJšœ˜—J˜š œžœžœžœ˜;J˜Jšžœ žœžœ ˜7Jšœžœ˜Jšœ'žœ#˜MJšœ˜J˜——J™™J˜J˜šœžœžœ¡˜!J™8Jšœ#¡™=Jšœ¡ ™'Jšœ¡0™7Jšœ¡™Jšœ¡H™MJ™J˜—š œžœ žœžœ žœ˜NJ™¢J™JšÏfÐfsb™cš¦ZÑcfs ˜dJšœ1™1Jšœk™k—Jš¦[§˜bJš¦Y§ ˜cJš¦Z§ ˜eJš¦Z§ ˜dJš¦Z§ ˜gš¦Ñbfs¦¨¦G§ ˜eJ™H—Jš¦Z§ ˜eJš¦Z§ ˜dJš¦¨¦¨¦F§˜gJš ¦¨¦¨¦6¨¦¨¦§ ˜bš¦¨¦¨¦F§˜pJšÐstÏs0©™2—J˜——J˜J˜™'K™ Kšœ Ïr™—™'K™&Kšœ «™—™&K™Kšœ «y™…—™)K™nKšœ «v™‚—™)Kšœ*™*Kšœ «;™G—™*Kšœ0™0Kšœ «5™A——…—8î]t