DIRECTORY BasicTime USING [ GMT ], GVBasics USING [ Password ], IV, LoganBerry USING [ Entry ], RefID USING [ ID ], RPC USING [ InterfaceName, ShortROPE ], Thrush USING [ ActionReport, ConversationID, ConvEvent, Credentials, EncryptionKey, InterfaceSpec, KeyTable, NetAddress, NB, nullID, noAddress, PartyID, PartyType, Reason, ROPE, SHHH, SmartsID, StateInConv, unencrypted, VoiceSocket ], ThSmartsRpcControl ; ThParty: CEDAR DEFINITIONS = { ConversationID: TYPE = Thrush.ConversationID; PartyID: TYPE = Thrush.PartyID; SmartsID: TYPE = Thrush.SmartsID; nullID: RefID.ID = Thrush.nullID; Credentials: TYPE = Thrush.Credentials; NB: TYPE = Thrush.NB; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Thrush.SHHH; none: SHHH = Thrush.unencrypted; SmartsInterfaceName: TYPE = RPC.InterfaceName; SmartsInterfaceRecord: TYPE = ThSmartsRpcControl.InterfaceRecord; SmartsProperties: TYPE=RECORD[ role: SmartsRole_NIL, netAddress: Thrush.NetAddress_Thrush.noAddress ]; ConvType: TYPE = { none, interactive, meeting }; SmartsRole: TYPE= ATOM; -- { $voiceTerminal, $controller, ? }; AccessList: TYPE = LIST OF ROPE; -- access control list for conversations Attributes: TYPE = LoganBerry.Entry; -- Property list CreateConversation: PROC[ shhh: SHHH _ none, credentials: Credentials, state: Thrush.StateInConv_$initiating, reason: Thrush.Reason _ NIL, -- if present, state is probably $failed (to get error signals when no conversation was ever established.) comment: ROPE _ NIL, convAttributes: Attributes_NIL, -- added to conversation list partyAttributes: Attributes_NIL, -- added to own party list checkConflict: BOOL _ FALSE ] RETURNS [ nb: NB, convEvent: Thrush.ConvEvent ]; Alert: PROC[ shhh: SHHH _ none, credentials: Credentials, calledPartyID: PartyID_nullID, comment: ROPE_NIL, convAttributes: Attributes_NIL, partyAttributes: Attributes_NIL -- initializes alerted party's list! ] RETURNS [nb: NB ]; Advance: PROC[ shhh: SHHH _ none, credentials: Credentials, state: Thrush.StateInConv, reportToAll: BOOL_FALSE, reason: Thrush.Reason_NIL, -- wontSay comment: ROPE_NIL, convAttributes: Attributes_NIL, partyAttributes: Attributes_NIL, -- own bilateral: BOOL _ FALSE, -- no more than two active parties in this conversation, please, while this party is an active participant. checkConflict: BOOL _ FALSE -- The state we are advancing to will require that the associated voice hardware be in use; disallow transitions that would make this true of two different conversations for the same party (apply to poachees, inherit by poachers?) ] RETURNS [nb: NB, convEvent: Thrush.ConvEvent ]; ReportAction: PROC[ shhh: SHHH _ none, report: Thrush.ActionReport, -- includes credentials (in other field) identifying the service party. reportToAll: BOOL _ FALSE, selfOnCompletion: BOOL_FALSE ] RETURNS [nb: NB, numReportsIssued: NAT_0]; ConversationInfo: TYPE = REF ConversationInfoRec; ConversationInfoRec: TYPE = RECORD [ convID: Thrush.ConversationID_NULL, numParties: NAT_TRASH, numActive: NAT_TRASH, numIdle: NAT_TRASH, originator: Thrush.PartyID _ TRASH, conferenceHost: Thrush.NetAddress_TRASH, convType: ConvType_$none, -- meeting or interactive bilateralConv: BOOL _ FALSE, -- conversation restricted to two parties startTime: BasicTime.GMT_TRASH, convAttributes: Attributes_NIL, -- conversation-wide attributes moderator: Thrush.PartyID _ 0, -- not always the originator, for named conversations commentator: Thrush.PartyID _ 0 -- feedback channel party, for named conversations ]; GetConversationInfo: PROC [ shh: SHHH_none, convID: ConversationID ] RETURNS [ nb: NB, -- one of $success, $noSuchConv cInfo: ConversationInfo ]; RegisterConversation: PROC [ shhh: SHHH _ none, credentials: Credentials, -- partyID is owner, convID identifies conversation name: ROPE, -- Should be unique, but this is not yet enforced; may be some use convType: ConvType _ $meeting, -- default unregistered is $interactive convAttributes: Attributes _ NIL, -- Permits changing the subject, etc. accessList: AccessList _ NIL ] RETURNS [ nb: NB ]; GetConversationFromName: PROC [ shhh: SHHH _ none, partyID: PartyID, name: ROPE ] RETURNS [ nb: NB, cInfo: ConversationInfo ]; EnumerateNamedConversations: PROC [ shhh: SHHH _ none, partyID: PartyID ] RETURNS [ nb: NB, candidates: LIST OF ConversationInfo ]; PartyInfo: TYPE = REF PartyInfoSeq; nullIx: NAT = 0; PartyInfoSeq: TYPE = RECORD [ numParties: NAT_0, conversationInfo: ConversationInfoRec, -- description of overall conversation ixSelf: NAT_nullIx, -- index of own party in parties sequence ixOther: NAT _ nullIx, -- index of some other (active, if any) party, if any ixModerator: NAT_nullIx, -- index of moderator party ixCommentator: NAT_nullIx, -- index of talkBack (commentator) party ixCommSock: NAT_nullIx, -- index of party whose socket is in use as the commentator socket. ixOriginator: NAT_nullIx, -- index of originator party in parties sequence (needed?) parties: SEQUENCE len: NAT OF PartyInfoSpec -- and of individual parties ]; PartyInfoSpec: TYPE = RECORD [ partyID: PartyID_nullID, name: Thrush.ROPE_NIL, -- owner, current name, or description, see below intendedPartyID: PartyID_nullID, intendedName: Thrush.ROPE_NIL, type: Thrush.PartyType_NIL, state: Thrush.StateInConv_$neverWas, -- of this party in the conversation numConvs: NAT_0, enabled: BOOL_FALSE, voicePath: BOOL _ FALSE, -- TRUE iff this party can participate actively in conversations partyActive: BOOL_FALSE, -- actively involved in n-way conversation partyEngaged: BOOL_FALSE, -- hardware in use socket: Thrush.VoiceSocket_Thrush.noAddress, -- in1 (tx1) socket template this party's voice terminal partyAttributes: Attributes _ NIL ]; NameReq: TYPE = ATOM; -- { $owner, $current, $description, $address, $none }; GetPartyInfo: PROC [ shh: SHHH_none, credentials: Credentials, nameReq: NameReq_$current, allParties: BOOL_FALSE -- IF FALSE, just calling party; else all parties, calling party first ] RETURNS [ nb: NB, -- one of $success, $noSuchConv, $noSuchParty, $notInConv pInfo: PartyInfo ]; DescribeParty: PROC[partyID: Thrush.PartyID, nameReq: NameReq] RETURNS[nb: NB, description: Thrush.ROPE, type: Thrush.PartyType, partner: Thrush.PartyID_nullID, visitee: Thrush.PartyID_nullID, visitors: LIST OF Thrush.PartyID_NIL ]; AddAttributes: PROC[ credentials: Credentials, convAttributes: Attributes_NIL, partyAttributes: Attributes_NIL -- own ] RETURNS[nb: NB]; RegisterServiceInterface: PROC[ shhh: SHHH _ none, credentials: Credentials, interfaceSpecPattern: Thrush.InterfaceSpec ] RETURNS[nb: NB, interfaceSpec: Thrush.InterfaceSpec]; LookupServiceInterface: PROC[ shhh: SHHH _ none, credentials: Credentials, serviceParty: PartyID, type: RPC.ShortROPE ] RETURNS[nb: NB, interfaceSpec: Thrush.InterfaceSpec]; RegisterKey: PROC[ shh: SHHH _ none, credentials: Credentials, key: Thrush.EncryptionKey, reportNewKeys: BOOL_FALSE ] RETURNS [ nb: NB, keyIndex: [0..17B] ]; GetKeyTable: PROC [ shh: SHHH_none, credentials: Credentials ] RETURNS [ nb: NB, -- one of $success, $noSuchConv, $noSuchParty, $notInConv keyTable: Thrush.KeyTable ]; UnregisterKey: PROC[ shh: SHHH _ none, credentials: Credentials, key: Thrush.EncryptionKey ] RETURNS [ nb: NB ]; GetParty: PROC[ shh: SHHH_none, partyID: PartyID, rName: ROPE_NIL, type: Thrush.PartyType_NIL ] RETURNS [nb: NB, newPartyID: PartyID]; GetPartyFromNumber: PROC[ shh: SHHH_none, partyID: PartyID, phoneNumber: Thrush.ROPE_NIL, description: ROPE_NIL ] RETURNS [nb: NB, newPartyID: PartyID]; GetPartyFromFeepNum: PROC[ shh: SHHH_none, partyID: PartyID, feepNum: Thrush.ROPE_NIL ] RETURNS [nb: NB, newPartyID: PartyID]; GetCurrentParty: PROC[shh: SHHH_none, smartsID: SmartsID] RETURNS [nb: NB, partyID: Thrush.PartyID]; ReleaseParty: PROC[shh: SHHH_none, partyID: PartyID, targetPartyID: PartyID] RETURNS [nb: NB]; GetNumbersForRName: PROC[shh: SHHH_none, rName: ROPE] RETURNS [fullRName: ROPE, number: ROPE, homeNumber: ROPE]; Register: PROC[ shh: SHHH_none, rName: ROPE_NIL, type: Thrush.PartyType_ $individual, clonePartyID: PartyID _ nullID, interface: SmartsInterfaceName, properties: SmartsProperties ] RETURNS [ nb: NB, credentials: Credentials ]; CheckIn: PROC[shh: SHHH_none, credentials: Credentials] RETURNS [nb: NB]; Deregister: PROC[shh: SHHH_none, smartsID: SmartsID] RETURNS [nb: NB]; Enable: PROC[shh: SHHH_none, smartsID: SmartsID] RETURNS [nb: Thrush.NB]; Disable: PROC[shh: SHHH_none, smartsID: SmartsID] RETURNS [nb: Thrush.NB]; Visit: PROC[ shh: SHHH_none, hostPartyID: PartyID, guestPartyID: PartyID, guestPassword: GVBasics.Password ] RETURNS [nb: Thrush.NB]; Unvisit: PROC[ shh: SHHH_none, hostPartyID: PartyID, guestPartyID: PartyID, guestPassword: GVBasics.Password ] RETURNS [nb: Thrush.NB]; UnvisitSelf: PROC[ shh: SHHH_none, guestPartyID: PartyID ] RETURNS [nb: Thrush.NB]; }. ELThParty.mesa Copyright Ó 1985, 1986, 1988 by Xerox Corporation. All rights reserved. Last modified by Swinehart, February 20, 1988 6:50:23 pm PST Parties and Conversations Definitions The netAddress is the address to use for establishing voice connections, including a socket number that does not change for the life of the Smarts. A smarts must not be active in more than one conversation at a time, so this unique assignment works. $none is used by some software to indicate that no type has yet been accommodated. See LarkOutImpl. $interactive is ordinary "full-duplex" conversation among 2 or more parties; $meeting is conversation involving one moderator, possibly one commentator, and n-c-1 auditors. voiceTerminal represents a role corresponding to Lark Smarts, front or back door, or BluejaySmarts; an actual source/sink of audio information, as well as the smarts control functions. controller represents an entity that can perform control functions, but cannot supply or accept the voice transmission protocol. Standard and only present example: FinchSmarts. Controllers generally get to hear about changes in conversations before their associated voice terminals do. Conversation, Party Attributes Conversation attributes: Subject, name, urgency, ... -- name is for named, meeting-style conversations. CallUrgency: TYPE= ATOM; -- { $junk, $ifConvenient, $normal=NIL, $important, $urgent, $fire}; Party attributes: Priority, alertKind, ... AlertKind: TYPE = ATOM; -- { $queryOnly, $standard=NIL, $intercom, $background, ... }; As parameters to ThParty.Alert and ThSmarts.Alert: queryOnly: is it likely that Alert would succeed? conversation argument can be null. standard: ring the phone, or whatever recipient would like. intercom: try to get through without ringing. Any party can contribute/replace conversation attributes to the conversation, or party attributes to its own entry. Removing attributes is not yet supported, although a NIL value effectively cancels an attribute. Conversation Management Discussion A Smarts must relay all call-placement requests through a Party it is associated with. This is because we don't trust all Smarts as much as we trust all Parties; the Parties should validate the requests. The party level, however, only concerns itself with maintaining the "truth" about the current state of all parties in a conversation, reporting state transitions to interested smarts, and storing/computing some values that smarts may be interested in. Smarts are responsible for initiating their own state transitions and in interpreting the meaning of being in one state or another. Exceptions: 1) One smarts may advance another party into the $notified state in order to initiate a call. 2) When all parties in a conversation are in the $idle state, the party code can eliminate all the data structures associated with the conversation. CreateConversation produces a new conversation and puts the calling party in it, in the indicated state; one can destroy the conversation only by idling onesself with respect to it, and expecting others to do the same. CreateConversation accepts other call parameters as a convenience. The conversation's creator is henceforth known to the system as the $originator of the conversation. checkConflict causes a test for front-door/back-door conflict to be made as the conversation is initiated. See PartyConflict. Alert adds another party to the conversation, in $notified state -- this is always legal, assuming the party's not in the conversation already! The notified party is expected to advance to a further state and report it to the $originator (caller of Alert). The originator can not subsequently influence the behavior of the notified party, except by suggestion. Advance is called by smarts to progress its own party through a call. See Thrush.StateInConv for a description of the states and their meanings. The reason/comment fields are most useful when going idle, to indicate whether it was due to some problem or just because the call is over. If bilateral is requested, an Advance to $active will fail if >1 parties are already active in the conversation, else will enter a state where additional attempts to become active will fail until the requesting party leaves active participation. Most ThParty functions return an NB (nota bene) code. If this value is not $success, usually any other return values will be NIL, nullID, or meaningless, and the requested operation will not have been performed. The NB values indicate generally what went wrong. $stateMismatch means that some other party has changed state and the caller has not yet been informed of that. This will only be returned from functions that operate on the conversation state. ConvEvent will be reported to alerted party, not us; if this fails, give up on the call. IF TRUE, advance is reported to all parties; if FALSE, to calling party only (smarts other than the calling smarts; in no case is an advance reported back to the caller). See discussion with the definition of Thrush.ActionReport. See also Register/ LookupServiceInterface, below, and ThSmarts.ReportAction. ActionReport's other field should be the credentials of the caller; self will be filled in during the reporting process. IF TRUE, a copy of the report is delivered to all parties (except the caller); if FALSE, to originating party only (and its poachee, if any). IF TRUE, a copy of the report is delivered to the reporting party, after all other progress reports have been delivered (no such copy is delivered if numReportsIssued is 0). See the discusson in RegisterKey for use of this feature. This could lead back to Finch-caused delays and such, but at least does not prevent anybody from hanging up. Enumerations, registrations, queries Information about the conversation as a whole A bilateral conversation is one with two active parties in it, one of which has demanded that the number not exceed two. N.B. This condition will not be set unless both parties are currently in the active state. N.B. This condition will not be set unless the ConversationInfoRec is a component of a PartyInfo returned by GetPartyInfo. Readily-available (and not too big) information about the conversation Named conversations Party information Socket numbers and host names for each of the voice terminals in this conversation. All active parties are arrayed ahead of any others. ixSelf value is meaningless if numParties = 0. ixOther value, along with all other indices # ixSelf are meaningless if numParties < 2. ixCommentator, ixCommSock values are meaningless when conversation type is not $meeting. Call may have been intended for someone else. If not, same as partyID and name $owner: RName of creator of this party; same as $current for telephone and individual parties; RName of trunk's owner for trunks; Idle service RName for services. $current: Current assignment; identity of party on the other end of a $trunk, or modified service name identifying user. $address: meaningless but same as $current for all but $trunk, where it's the phone number $description: dolled-up version of $current, suitable for framing, including all known names and numbers, for Finch and so on. Information about the parties (or just one party) in a conversation Credentials should have either a ConversationID or PartyID or both; other fields irrelevant. GetPartyInfo[nameReq: $current, allParties: FALSE] replaces GetRname. GetPartyInfo[nameReq: $description, allParties: FALSE] replaces DescribeParty partner is poachee if party type is $individual, poacher if party type is $telephone, else NA visitee and visitors are usually nonexistent, so there's no option for suppressing their values. This should probably just get subsumed into GetPartyInfo. Service action registration, lookup. See also ReportAction, above. Having registered it, the caller (service provider) must also XXXRpcControl.ExportInterface[...] the interface, under the specified instance name. interfaceSpecPattern.type should be a simple rope, not a Grapevine RName, since we don't really want Grapevine involved at all. The client can either supply an instance value or let RegisterService invent one (present BluejaySmarts produces 5 parties, but each has the same behavior, so it could use the instance invented during the registration of the first one for the rest of them.) There can/will be only one instance/version of an interface with a given type name registered for a party at a time. New ones override old ones. Caller should supply interfaceSpecPattern.hostHint. If it's null, an attempt will be made to produce a decent value, no guarantees (shhh=none implies call is from own host.) NB can complain about the credentials. The client can now import the interface directly. Interface procedures should traffic in Thrush.Credentials and the like, providing enough information to identify the relevant conversation and so on. The implementations are responsible for issuing the necessary progress reports, and dealing with the other ThParty rules. NB is $noSuchInterface if one cannot be found. NB can also complain about caller's credentials or the validity of the serviceParty (see Thrush.NB). LookupService tries the identified serviceParty and its poachee, if there is one, in that order. Version checking is not done by this function; if the versions don't match the Import attempt will discover it. Encryption key registration. The voice protocol requires each voice terminal to maintain a table of active encryption keys, whose indices appear in individual voice packets. RegisterKey enters a new key, failing if it would displace an old one. The result is the key's index location. If reportNewKeys, a Thrush.ActionReport is issued to all but the originator of the request (see ReportAction, above), with actionClass=$KeyDistribution and actionType=$newKeys, uniqueID=(high order half of key). Once all these reports have been delivered, the originator gets one, too. At this point, it is certain that all participants have been given an opportunity to call GetKeyTable and record the new keys. RegisterKey returns nb=$newKeys if reports are being generated, $keyOverflow if all indices are in use. Thus ends the infamous and long-standing key synchronization problem. UnregisterKey permits the use of a key to be released. If everyone plays the game, keyIndices can be re-used during a conversation. UnregisterKey does not inform anyone else of the change. It should not be issued until all parties have stopped using the old key. It is believed that additional synchronization is unnecessary. Names to Parties, Numbers to Parties Discussion Parties represent participants in conversations. Depending on type, they represent: service: a recording service, synthesizer, and the like. trunk: the party at the other end of a "back door" telephone line. telephone: the user of a Lark (Etherphone) in an office. individual: an authenticated user of a Lark and/or a client program registered with the server. GetParty and friends map names into parties. (Register creates new parties.) The rName is either the name of an individual (including registry), a service (e.g., "recording"), or (in the case of trunks) NIL. partyID is the ID for one's own party -- the "self" of object-style references. This form is followed throughout this interface. IF nb#success, it gives some idea of what went wrong. Thrush.NB can be extended with specialized error values in cases where the caller is expected to understand more about it (when obtaining services, for instance) IF type=NIL, it's a request for an $individual (preferably) or $telephone (otherwise). GetParty will return one's own trunk, properly configured, if type=$telephone and no Etherphone party can be found. phoneNumber is a valid extension or outside telephone number. Description is probably a name, but may not be an RName; we can use it to help the user, but not to look up anything. Unless trunkOK, number must be an internal extension representing an Etherphone user. FeepNum is a string of the form "*nnnnn", where "nnnnn" is a prefix of some rName, mapped with information loss into the corresponding DTMF buttons. The prefix should be long enough to be guaranteed unique within the system. This isn't at all good yet. Who do we represent when we initiate an outgoing call? After obtaining this party, it may become invalid (due to the party dropping out), so be prepared for $noSuchParty problems all along the line. Irrelevant except for service party. Optional function to release reservation of targetPartyID. After calling GetParty, a client is expected to engage the targetParty in a conversation fairly quickly, in human terms. The client has the exclusive right to do this for a moderate time interval, after which it's fair game again. This procedure releases that right early. Information about people and parties fullRName is NIL if nothing found. Party and Smarts creation, initialization Discussion A Smarts implementation uses Register to create the server's identifier for the Smarts, to create the corresponding party, and to establish the necessary links between these objects and the reset of the system. Since the server needs to be able to deal with more than one Smarts implementation, they are implemented as objects and must use RPC's object-oriented option. Options include: Local or remote registration: in the local case, the Smarts implementation is on the same machine and can supply the interface record directly. Cloning: a service can request additional registrations based on an initial prototype. This allows the service to support a number of simultaneous conversations. Calling Register with identical party identification results in the immediate destruction (via Deregister) of the previous version of the party and creation of a new one. CheckIn can be used to determine whether such a step is necessary. Identification of party -- requires either: Description of party Or ID of party to replicate (used to provide multiple instances of services) Identification of smarts implementation -- requires either: RPC Interface allowing an instance to be imported Or Local Interface record matching that produced by RPC -- see ThPartyPrivate.RegisterLocal Kind of Smarts being registered, and from what host nb will indicate something about what went wrong if anything did; if nb=$success, credentials contains the resulting PartyID and SmartsID. Verifies that the credentials are still valid, (conv can be null), or if not, which problem is the most pronounced. Can be used as basis for occasional Smarts to party polling. nb will be noSuchSmarts or success, or something else (which implies things are confused) nb=success unless something went wrong. Any parties connected to this smarts will now be available to GetParty; this smarts is an active voice smarts. This smarts can no longer supply a voice connection to its parties. noPassword: GVBasics.Password _ ALL[0]; Compiler won't let me define this here; client has to do it in an implementation! visitedParty is permitting visitingParty to visit. Visited party must be of type $individual or this function is not permitted. Returns $passwordNotValid either if visitingPassword = noPassword and password protection is required, or if an invalid password is supplied. A GVBasics.Password may be obtained from VoiceUtils.CurrentPassword (not CurrentPasskey!) Visitor has returned home. Password is required, under same circumstances as above, so that no one can maliciously cancel visiting. Visit implementation may choose to retain the key for this reason, although it is not strictly speaking safe to do so. Allows the visitor (the guest) to cancel the visiting arrangement. Swinehart, May 15, 1985 10:01:11 am PDT Cedar 6.0 changes to: GetParty stuff simplified (from client standpoint), Prose functions added, some unused procedures removed. Swinehart, October 31, 1985 5:18:48 pm PST Major changes to argument structure, Smarts/Party structure changes to: Alert, Advance, CreateConversation, GetParty, SetRingEnable, DIRECTORY, LocalSmartsInterface, SmartsProperties, SmartsRole, Register, GetPartyFromFeepNum Swinehart, November 7, 1985 9:50:41 am PST Revamping ThParty, (lean, mean Thrush) changes to: CreateConversation, Alert, Advance Swinehart, May 30, 1986 10:45:17 am PDT Cedar 6.1. Add Action Registration, Lookup, Reporting. Clean out some old Interval/Prose stuff. changes to: DIRECTORY, RegisterServiceInterface Swinehart, August 3, 1986 6:15:00 pm PDT Functions to enable and disable "visiting"; UnregisterKey changes to: GetKeyTable, UnregisterKey, Disable, Visit, Unvisit Swinehart, September 15, 1987 4:47:36 pm PDT Changed Visit, Unvisit definitions; added UnvisitSelf changes to: Visit, Unvisit, UnvisitSelf Swinehart, January 14, 1988 4:26:26 pm PST Add named conversation capabilities; reorganize and extend ConversationInfo. Add access lists to conversations (named only at present). changes to: SmartsRole, AccessList, ConversationInfo, ConversationInfoRec, GetConversationInfo, RegisterConversation, GetConversationFromName, EnumerateNamedConversations, PartyInfo, PartyInfoSeq Swinehart, February 20, 1988 4:12:02 pm PST Remove subject, name, callUrgency, alertKind; add partyAttributes, convAttributes to replace all of the above. Add attributes-changing function. changes to: DIRECTORY, AccessList, Attributes, CreateConversation, Alert, Advance, ConversationInfoRec, RegisterConversation, PartyInfoSpec, DescribeParty ÊK˜Jšœ ™ JšœH™HJšœ<™JšÏb œ«™¸JšŸ œ•™ŸJ™—Jš œ œœœœž(˜IJ˜—™Jšœ œž˜5J™™J™Nšœ œœž™Jšž?™?——™J™šœ œœž>™Všœ2™2J™UJ™;J™-———J™ÕJ˜—™™ JšœÍ™ÍJ™€J™JšŸœÊŸœvŸ œŸ œr™…J™JšŸœ,Ÿ œ©Ÿ œ}™ëJ™JšŸœœŸ œÐbnœÅ™•J™Jš œ!œ[œYœ-Ÿœ³™ÊJ˜—šÏnœœ˜Jšœœ˜J˜J˜&Jšœœžj˜‡Jšœ œœ˜Jšœœž˜=Jšœœž˜;Jšœœ˜Jšœœœ ˜2J˜—š¡œœ˜ Jšœœ˜J˜J˜Jšœ œ˜Jšœœ˜Jšœœž$˜Dšœœœ˜JšœX™X—J˜—š¡œœ˜Jšœœ˜J˜J˜šœ œœ˜Jšœœ)œu™ª—Jšœœž ˜%Jšœ œ˜Jšœœ˜Jšœœž˜'Jšœ œœžk˜„Jšœœœžæ˜‚Jšœœœ ˜1J˜—Icodešœ‚™‚š¡ œœ˜Jšœœ˜JšœžG˜dšœ œ˜JšœœKœ6™—šœœ˜JšœœÏoœCÏtœl£™×—Kšœœœœ˜,—K˜—šœ$™$J™Jšœœœ˜1šœœœ˜$J™-Jšœœ˜#J˜Jšœ œœ˜Jšœ œœ˜Jšœ œœ˜Jšœœ˜#Jšœ"œ˜(Jšœž˜3Jšœœœž)˜FJ˜Jšœ œ˜Jšœœž˜?J˜Jšœž5˜TJšœ ž2˜RJ˜Jšœ)Ÿœ§Ÿz™ÐJ˜—š¡œœœ˜Dšœ˜ J™FJšœœž˜(Jšœ˜J˜J˜——™J™—š¡œœ˜Jšœœ˜Jšœž3˜MJšœœžB˜NJšœž'˜FJšœœž%˜GJšœ˜Jšœœœ˜J˜—š¡œœ˜Jšœœ˜J˜Jšœ˜ Jšœœœ˜.J˜—š¡œœ˜#Jšœœ˜J˜Jš œœœœœ˜;—˜J˜——™J™šœ œœ˜#J™ˆ—J˜Jšœœ˜J˜šœœœ˜Jšœ œ˜Jšœ'ž&˜MJšœœ ž)˜=Jšœ œ ž5˜MJšœ œ ž˜4Jšœœ ž(˜CJšœ œ žC˜[Jšœœ ž:˜TJšœ œœœž˜HJ˜Jš¢œ¢ œ¢œ'¢œ¢ œ¢ œ¢ œ?™à—šœœœ˜Jšœ˜Jšœ œœž1˜H™OJšœ ˜ Jšœœœ˜—Jšœœ˜Jšœ%ž$˜IJšœ œ˜Jšœ œ˜Jšœ œœž@˜YJšœ œœž*˜CJšœœœž˜,Jšœ-ž8˜eJšœ˜!J˜—šœ œœž7˜MJ™¢J™xJšœZ™ZJ™~J˜—š¡ œœ˜Jšœœ˜Jšœ˜Jšœ˜Jšœ œœžF˜]Jšœ˜šœ˜ J™CJ™\Jšœœž9˜BJšœ˜J˜—J™Ešœ0œ™MJ˜——š¡ œœ+˜>šœœœ˜AJšœ?˜?Jšœ œœ˜$Jšœ˜—J™]J™›J˜—š¡ œœ˜Jšœ˜Jšœœ˜Jšœœž˜&Jšœœœ˜J˜——™CJ™š¡œœ˜Kšœœ˜K˜Kšœ*˜*Kšœœœ'˜7Kšœ>¢"œ4¢œŸ¢œ»™—K™‘Kšœ®™®Kš¢œ$™&—K˜š¡œœ˜Kšœœ˜K˜K˜Kšœœ ˜Kšœœœ'˜7—KšœZ¢œÉ¢œ™ÃKš¢œ¢œ¢œd¢ œÄ™çK™—J™™JšœÚ£'™Jš œ¢ œ¢œT¢œ¢9œÉ™žJš¢ œ ¢ œH™gJ™EJ™ÉJ™š¡ œœ˜Jšœœ˜Jšœ˜J˜Jšœœ˜J˜Jšœœ˜'J˜—š¡ œœœ!˜>šœ˜ Jšœœž9˜BJšœ˜J˜J˜——š¡ œœ˜Jšœœ˜Jšœ˜J˜J˜Jšœœ˜—J™—™$J˜™ ™TJ™8J™BJ™8J™_—J™ÑJ™—J˜š¡œœ˜Jšœœ œœ˜2Jšœ˜Jšœœœ˜(J™ØJšœœK™VJ™s—J˜š¡œœ˜Jšœœ˜Jšœ˜Jšœœœœ˜3Jšœœœ˜(J™‹J˜—š¡œœ˜Jšœœ˜Jšœ˜Jšœœ˜Jšœœœ˜(Jšœþ™þJ˜—š¡œœœ˜9Jšœœ˜*J™ÇJ˜—š¡ œœœ0˜LJšœœ˜J™ôJ™——™$J™š¡œœœœ˜5Jšœ œ œœ˜:Jšœ œ™"—J™—™)J™™ JšœÕ™ÕJšœž™ž™J™J™¢J™î——J˜š¡œœ˜Jšœœ˜™+™Jšœœœ˜J˜$—™LJ˜——™;™1Jšœ˜—J™[—™3J˜—Jšœœ˜ šœœ˜#J™QJ™8——J˜š ¡œœœ!œœ˜IJ™±J˜—š ¡ œœœœœ˜FJ™Y—J˜š¡œœœ˜0Jšœ œ˜J™——J˜š¡œœœ˜1Jšœ œ˜J™CJ™—Jšœ œW™zJ™š¡œœ˜ Jšœœ˜Jšœ,˜,Jšœ ˜ Jšœ˜Jšœ œ˜J™êJ™—š¡œœ˜Jšœœ˜Jšœ,˜,Jšœ ˜ Jšœ˜Jšœ œ˜J™üJ™—š¡ œœ˜Jšœœ˜Jšœ˜Jšœ˜Jšœ œ˜J™B—J˜—J˜J˜J˜™'K™ K™v—™*K™;Kšœ Ïr™™¥—K™K™™*K™&Kšœ ¤"™.—™'K™aKšœ ¤#™/—™(K™9Kšœ ¤3™?—™,K™5Kšœ ¤™'—™*K™ˆKšœ ¤·™Ã—™+K™‘Kšœ ¤Ž™š—K™—…—"nw