DIRECTORY DESFace USING [ GetRandomKey, Key ], Lark USING [ ConnectionSpecRec, KeyTableBody, VoiceSocket ], Log USING [ Problem, SLOG ], OrderedSymbolTable, PupTypes USING [ PupSocketID ], SafeStorage USING [ GetCanonicalType, Type ], BasicTime USING [ Update, Now ], Thrush USING [ AlertKind, CallUrgency, ConversationHandle, ConvEvent, ConvEventBody, Credentials, Dehandle, EncryptionKey, epoch, EventSequence, EventSequenceBody, H, IntervalSpec, NB, nullConvHandle, nullHandle, nullKey, PartyHandle, PartyType, PhoneNumber, Reason, ROPE, SHHH, SmartsHandle, StateID, StateInConv, ThHandle, unencrypted ], ThNet USING [ pd ], ThParty, ThPartyPrivate USING [ CFRec, CFRef, ConversationBody, ConversationData, DoDescribeParty, logSizeIncrement, PartyBody, PartyData, SmartsBody, SmartsData, Supervise ], ThPartyMonitorImpl, ThSmartsRpcControl USING [ InterfaceRecord ], Triples USING [ Any, Foreach, ForeachProc, Make, Select ] ; ThPartyOpsImpl: CEDAR MONITOR LOCKS root IMPORTS DESFace, root: ThPartyMonitorImpl, SafeStorage, BasicTime, Log, OrderedSymbolTable, ThNet, ThPartyPrivate, Thrush, Triples EXPORTS ThParty, ThPartyPrivate SHARES ThPartyMonitorImpl = { AlertKind: TYPE = Thrush.AlertKind; CallUrgency: TYPE = Thrush.CallUrgency; CFRef: TYPE = ThPartyPrivate.CFRef; RTConvType: SafeStorage.Type = SafeStorage.GetCanonicalType[CODE[ThPartyPrivate.ConversationBody]]; ConvEvent: TYPE = Thrush.ConvEvent; ConversationData: TYPE = ThPartyPrivate.ConversationData; ConversationHandle: TYPE = Thrush.ConversationHandle; Credentials: TYPE = Thrush.Credentials; H: PROC[r: REF] RETURNS[Thrush.ThHandle] = INLINE {RETURN[Thrush.H[r]]; }; NB: TYPE = Thrush.NB; none: SHHH = Thrush.unencrypted; nullConvHandle: Thrush.ConversationHandle = Thrush.nullConvHandle; nullHandle: Thrush.PartyHandle = Thrush.nullHandle; PartyBody: TYPE = ThPartyPrivate.PartyBody; -- Concrete RTPartyType: SafeStorage.Type = SafeStorage.GetCanonicalType[CODE[PartyBody]]; PartyData: TYPE = ThPartyPrivate.PartyData; -- REF Concrete PartyHandle: TYPE = Thrush.PartyHandle; -- Handle Reason: TYPE = Thrush.Reason; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Thrush.SHHH; SmartsBody: TYPE = ThPartyPrivate.SmartsBody; -- Concrete SmartsData: TYPE = ThPartyPrivate.SmartsData; -- REF Concrete RTSmartsType: SafeStorage.Type =SafeStorage.GetCanonicalType[CODE[SmartsBody]]; SmartsHandle: TYPE = Thrush.SmartsHandle; -- Handle StateID: TYPE = Thrush.StateID; StateInConv: TYPE = Thrush.StateInConv; nextConv: ConversationHandle _ Thrush.epoch; convTable: OrderedSymbolTable.Table; convClassIncrement: INT _ 100000000B; -- members of same class are identical in low 24 bits. Alert: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, state: StateInConv_initiating, -- IF reserved, reason specifies reason for cancelling prev. reason: Reason_wontSay, -- IF reserving, specifies reason for cancelling last one. calledPartyID: PartyHandle, urgency: CallUrgency, alertKind: AlertKind, newConv: BOOL_FALSE, comment: ROPE ] RETURNS [ nb: NB, convID: ConversationHandle ] = { ENABLE UNWIND => NULL; cfRef: CFRef; conv: ConversationData; callingParty: PartyData; calledParty: PartyData; Log.SLOG[]; IF newConv OR credentials.convID=nullConvHandle THEN credentials.convID _ CreateConv[ partyID: credentials.partyID, convID: credentials.convID, urgency: urgency, alertKind: alertKind].convID; [conv, callingParty, cfRef, nb] _ Verify[credentials]; convID _ credentials.convID; IF nb#success THEN RETURN; IF cfRef#NIL AND cfRef.event.state=idle THEN { nb _ AllIdle[conv]; IF nb#success THEN RETURN; }; conv.urgency _ urgency; conv.alertKind _ alertKind; IF calledPartyID # nullHandle THEN { calledParty _ DehandleParty[calledPartyID]; IF calledParty=NIL THEN {nb_noSuchParty2; RETURN}; IF calledPartyID=credentials.partyID THEN {nb_narcissism; RETURN}; [] _ PostConvEvent[conv: conv, party: calledParty, state: pending, reason: wontSay, comment: comment ]; }; [] _ PostConvEvent[conv: conv, party: callingParty, state: state, reason: reason, smartsID: credentials.smartsID, comment: comment ]; }; Advance: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, state: StateInConv, reason: Thrush.Reason, comment: ROPE ] RETURNS [nb: NB] = { ENABLE UNWIND => NULL; conv: ConversationData; party: PartyData; cfRef: CFRef; Log.SLOG[]; [conv, party, cfRef, nb] _ Verify[credentials]; IF nb#success THEN RETURN; nb _ DoAdvance[credentials.smartsID, party, conv, cfRef, state, reason, comment]; }; DoAdvance: PUBLIC INTERNAL PROC [ smartsID: SmartsHandle, party: PartyData, conv: ConversationData, cfRef: CFRef, state: StateInConv, reason: Thrush.Reason, comment: ROPE ] RETURNS [nb: NB] = { otherCfRef: CFRef; otherParty: PartyData _ NIL; otherState: Thrush.StateInConv; OtherProc: Triples.ForeachProc _ SELECT state FROM idle => IdleLast, ringing, active => FindCaller, ENDCASE => NIL; IdleLast: Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { otherState _ idle; SELECT r.event.state FROM idle => RETURN[TRUE]; ENDCASE=>NULL; IF otherParty#NIL THEN { otherParty_NIL; RETURN[FALSE]; }; otherParty_NARROW[trip.val]; otherCfRef _ r; }; ENDCASE; }; FindCaller: Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { otherState _ IF state = ringing THEN maybe ELSE active; SELECT r.event.state FROM initiating, maybe => NULL; ENDCASE => RETURN[TRUE]; otherParty _ NARROW[trip.val]; otherCfRef _ r; RETURN[FALSE]; }; ENDCASE; }; SELECT validTransitions[state][cfRef.event.state] FROM no, cantReq => RETURN[invalidTransition]; noop => RETURN; -- success valid => NULL; ENDCASE => Log.Problem[,$System]; cfRef_PostConvEvent[ conv: conv, party: party, smartsID: smartsID, state: state, reason: reason, comment: comment]; nb _ success; IF OtherProc#NIL THEN Triples.Foreach[Triples.Any, conv, Triples.Any, OtherProc]; IF otherParty # NIL THEN { SELECT validTransitions[otherState][otherCfRef.event.state] FROM no => { Log.Problem["State mismatch among parties", $System]; RETURN; }; cantReq, valid => NULL; noop => RETURN; ENDCASE => { Log.Problem["Impossible", $System]; RETURN; }; []_PostConvEvent[ conv: conv, party: otherParty, state: otherState, reason: reason, comment: comment]; }; }; OtherParty: PUBLIC ENTRY PROC[ shhh: SHHH_none, credentials: Credentials ] RETURNS[ nb: NB, partyID: PartyHandle_nullHandle, description: Thrush.ROPE_NIL, conference: BOOL_FALSE ] = { conv: ConversationData; party: PartyData; cfRef: CFRef; [conv, party, cfRef, nb] _ Verify[credentials]; IF nb=stateMismatch THEN nb_success; IF nb#success THEN RETURN; FOR i: NAT IN [1..conv.currentStateID] DO ce: ConvEvent = conv.log[i]; IF credentials.partyID=ce.credentials.partyID THEN LOOP; partyID _ ce.credentials.partyID; description _ ThPartyPrivate.DoDescribeParty[partyID]; IF conv.numParties>2 THEN { conference_TRUE; description _ "Group of parties"; RETURN; }; EXIT; ENDLOOP; }; CreateConversation: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, urgency: CallUrgency, alertKind: AlertKind ] RETURNS [ nb: NB, convID: ConversationHandle ] = { ENABLE UNWIND => NULL; conv: ConversationData = CreateConv[credentials.partyID, Thrush.nullConvHandle, urgency, alertKind]; RETURN[success, conv.convID]; }; CreateConv: INTERNAL PROC[ partyID: PartyHandle, convID: ConversationHandle, -- non-null if want a related converation. urgency: CallUrgency_normal, alertKind: AlertKind_standard] RETURNS [ conv: ConversationData_NIL ] = TRUSTED { ENABLE ANY => { Log.Problem[, $System]; conv_NIL; CONTINUE; }; conv _ NEW[ThPartyPrivate.ConversationBody _ [ creatorPartyID: partyID, timeOfID: BasicTime.Now[], urgency: urgency, alertKind: alertKind, log: NEW[Thrush.EventSequenceBody[ThPartyPrivate.logSizeIncrement]]]]; conv.keyTable _ NEW[Lark.KeyTableBody[20B]]; []_EnterKey[conv, DESFace.GetRandomKey[]]; -- = this conv's key, will be key index 1. conv.convID _ EnhandleConv[conv, convID]; }; DestroyConversation: PUBLIC INTERNAL PROC[ conv: ConversationData ] = { IF conv.numParties#0 THEN Log.Problem["Conversation still has parties", $System]; KillHandleConv[conv.convID]; }; MergeConversations: PUBLIC PROC[ shhh: SHHH, credentials: Credentials, -- of surviving conversation otherStateID: StateID, -- of dissolving conversation otherConvID: ConversationHandle ] RETURNS [ nb: NB ] = {NULL}; SetInterval: PUBLIC ENTRY PROC[ shhh: SHHH _ none, credentials: Credentials, intervalSpec: Thrush.IntervalSpec ] RETURNS [ nb: NB ] = { ENABLE UNWIND => NULL; conv: ConversationData; party: PartyData; cfRef: CFRef; WachetAuf: INTERNAL Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { party_NARROW[trip.val]; ThPartyPrivate.Supervise[party]; }; ENDCASE; }; [conv, party, cfRef, nb] _ Verify[credentials]; IF nb#success THEN RETURN; IF cfRef=NIL OR cfRef.event=NIL OR cfRef.event.state#active THEN RETURN[convNotActive]; IF ~ThNet.pd.encryptVoice THEN intervalSpec.keyIndex_0; [] _ PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , ,intervalSpec]; IF intervalSpec.type=request THEN intervalSpec.intID _ conv.currentStateID; Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf]; }; RegisterKey: PUBLIC ENTRY PROC [ shh: SHHH _ none, credentials: Credentials, key: Thrush.EncryptionKey ] RETURNS [ nb: NB, keyIndex: [0..17B] _ 0 ] = { ENABLE UNWIND => NULL; conv: ConversationData; cfRef: CFRef; [conv, , cfRef, nb] _ Verify[credentials]; IF nb=success THEN keyIndex _ EnterKey[conv, key].keyIndex; }; SetSubject: PUBLIC ENTRY PROC[ shh: SHHH, convID: ConversationHandle, subject: ROPE ] = { ENABLE UNWIND => NULL; conv: ConversationData = DehandleConv[convID]; IF conv=NIL THEN RETURN ELSE conv.subject _ subject; }; GetSubject: PUBLIC ENTRY PROC[ shh: SHHH, convID: ConversationHandle ] RETURNS [subject: ROPE] = { ENABLE UNWIND => NULL; conv: ConversationData = DehandleConv[convID]; RETURN[IF conv=NIL THEN NIL ELSE conv.subject]; }; ConversationsForParty: PUBLIC ENTRY PROC [ shh: SHHH_none, partyID: PartyHandle ] = { party: PartyData _ DehandleParty[partyID]; ReallyWachetAuf: INTERNAL Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => { conv: ConversationData _ NARROW[trip.obj]; r.lastNotedID _ 0; ThPartyPrivate.Supervise[party]; }; ENDCASE; }; IF party#NIL THEN Triples.Foreach[Triples.Any, Triples.Any, party, ReallyWachetAuf]; }; Verify: PUBLIC INTERNAL PROC[ credentials: Credentials ] RETURNS [ conv: ConversationData, party: PartyData, cfRef: CFRef, nb: Thrush.NB ] = { smarts: SmartsData _ DehandleSmarts[credentials.smartsID]; conv_DehandleConv[credentials.convID]; party_DehandleParty[credentials.partyID]; nb _ SELECT NIL[REF ANY] FROM smarts=> noSuchSmarts, party=> noSuchParty, conv=> noSuchConv, ENDCASE=> success; IF nb=success AND party#NIL THEN IF party.partyFailed THEN nb_noSuchParty ELSE IF party.numEnabled=0 THEN nb _ partyNotEnabled; IF conv#NIL AND party#NIL THEN cfRef _ NARROW[Triples.Select[--cfRef--, conv, party]]; IF nb=success THEN IF cfRef#NIL THEN { IF credentials.stateID IF credentials.stateID#0 THEN nb _ notInConv; stateMismatch => nb _ notInConv; ENDCASE; }; PostConvEvent: INTERNAL PROC[ conv: ConversationData, party: PartyData, smartsID: SmartsHandle _ nullHandle, state: Thrush.StateInConv, reason: Thrush.Reason_wontSay, comment: Thrush.ROPE_NIL, intervalSpec: Thrush.IntervalSpec_NIL ] RETURNS [cfRef: CFRef_NIL] = { ENABLE ANY => { Log.Problem["PostConvEvent failure", $System]; CONTINUE; }; event, lastEvent: ConvEvent; lastState: Thrush.StateInConv; conv.currentStateID _ conv.currentStateID+1; conv.timeOfID _ BasicTime.Now[]; TRUSTED { event _ NEW[Thrush.ConvEventBody _ [ credentials: [ H[party], smartsID, conv.convID, conv.currentStateID ], time: conv.timeOfID, state: state, reason: reason, urgency: conv.urgency, alertKind: conv.alertKind, address: (WITH p: party SELECT FROM trunk => p.outgoing, ENDCASE => NIL), comment: comment, intervalSpec: intervalSpec ]]; }; InsertEvent[conv, event]; IF conv.newKeyTable THEN event.keyTable _ conv.keyTable; conv.newKeyTable_FALSE; cfRef _ NARROW[Triples.Select[--cfRef--, conv, party]]; IF cfRef = NIL THEN { cfRef _ NEW[ThPartyPrivate.CFRec _ [ event: NIL, sockID: NewId[] ]]; Triples.Make[cfRef, conv, party]; conv.numParties _ conv.numParties+1; party.numConvs _ party.numConvs+1; }; SELECT state FROM initiating, active, canActivate => IF cfRef.voiceSmartsID=nullHandle THEN { smarts: SmartsData _ DehandleSmarts[smartsID]; IF smarts#NIL THEN WITH smarts.properties SELECT FROM backstop, supervisor => NULL; -- << ERROR? >> voiceTerminal => cfRef.voiceSmartsID _ smartsID; manager => { smarts _ NARROW[Triples.Select[$AdjacentTerminal, party, -- smarts --]]; IF smarts # NIL THEN cfRef.voiceSmartsID _ H[smarts]; }; ENDCASE; }; ENDCASE; lastEvent _ cfRef.event; lastState _ IF lastEvent=NIL THEN idle ELSE lastEvent.state; cfRef.event _ event; cfRef.lastPostedID _ conv.currentStateID; IF state=active THEN { IF lastState#active THEN IF party.partyActive THEN event.state _ canActivate ELSE [] _ Activate[conv, cfRef, party] } --<> ELSE IF lastState = active THEN { party.partyActive _ FALSE; conv.numActive _ conv.numActive - 1; }; ThPartyPrivate.Supervise[party]; }; Activate: INTERNAL PROC[conv: ConversationData, myCfRef: CFRef, myParty: PartyData] RETURNS[isSpec: BOOL_FALSE] = { sockets: ARRAY[0..2) OF Lark.VoiceSocket; parties: ARRAY[0..2) OF PartyData; specIndex: NAT_0; GetOneActive: INTERNAL Triples.ForeachProc = { cfRef: CFRef; smarts: SmartsData; sockID: PupTypes.PupSocketID; IF specIndex>=2 THEN RETURN[FALSE]; WITH trip.att SELECT FROM r: CFRef => cfRef _ r; ENDCASE=> RETURN[TRUE]; IF cfRef.voiceSmartsID = nullHandle THEN RETURN[TRUE]; smarts _ DehandleSmarts[cfRef.voiceSmartsID]; IF smarts=NIL THEN RETURN--[FALSE]--; -- fail back sockID _ LOOPHOLE[cfRef.sockID]; WITH smarts.properties SELECT FROM voiceTerminal => sockets[specIndex] _ [ [ machine.net], [machine.host], sockID ]; ENDCASE => Log.Problem["Non-voice terminal trying to be voice smarts", $System]; parties[specIndex] _ NARROW[trip.val]; specIndex _ specIndex+1; }; myParty.partyActive_TRUE; conv.numActive _ conv.numActive + 1; Triples.Foreach[Triples.Any, conv, Triples.Any, GetOneActive]; IF specIndex#2 THEN RETURN[FALSE]; SELECT myParty FROM =parties[0] => specIndex _ 0; =parties[1] => specIndex _ 1; ENDCASE => Log.Problem["Impossible", $System]; myCfRef.event.spec _ NEW[Lark.ConnectionSpecRec _ [ protocol: interactive, encoding: muLaw, sampleRate: 8000, packetSize: 160, -- sample bytes, that is. buffer: out1, keyIndex: IF ThNet.pd.encryptVoice THEN 1 ELSE 0, localSocket: sockets[specIndex], remoteSocket: sockets[1-specIndex], blankA: 0 ]]; RETURN[TRUE]; }; GetEvent: PUBLIC INTERNAL PROC[conv: ConversationData, stateID: Thrush.StateID] RETURNS [ Thrush.ConvEvent_NIL ] = { IF conv=NIL THEN Log.Problem["No such conversation", $System] ELSE IF stateID=0 THEN RETURN[NIL] ELSE IF stateID>=conv.log.size THEN Log.Problem["State ID out of range", $System] ELSE RETURN[conv.log[stateID]]; }; InsertEvent: INTERNAL PROC[conv: ConversationData, entry: ConvEvent] = { IF conv.currentStateID >= conv.log.size THEN { oldLog: Thrush.EventSequence = conv.log; newLog: Thrush.EventSequence = NEW[Thrush.EventSequenceBody[oldLog.size+ThPartyPrivate.logSizeIncrement]]; FOR i: NAT IN [0..oldLog.size) DO newLog[i]_oldLog[i]; ENDLOOP; conv.log _ newLog; }; conv.log[conv.currentStateID] _ entry; }; NewId: PROC RETURNS [ LONG CARDINAL ] = TRUSTED { idKey: DESFace.Key _ DESFace.GetRandomKey[]; -- connection specs. keys: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL _ LOOPHOLE[LONG[@idKey]]; RETURN[keys[0]]; }; AllIdle: INTERNAL PROC[conv: ConversationData] RETURNS [nb: Thrush.NB] = { allAreIdle: BOOL_TRUE; OneIdle: Triples.ForeachProc = { WITH trip.att SELECT FROM r: CFRef => IF r.event.state#idle THEN { allAreIdle_FALSE; RETURN[FALSE]; }; ENDCASE;}; Triples.Foreach[Triples.Any, conv, Triples.Any, OneIdle]; RETURN[IF allAreIdle THEN success ELSE invalidTransition]; }; DehandleConv: PUBLIC INTERNAL PROC[ convID: ConversationHandle] RETURNS [conv: ConversationData_NIL ] = { node: OrderedSymbolTable.Node = convTable.Lookup[convID]; RETURN[IF node#NIL THEN node.conv ELSE NIL]; }; EnhandleConv: INTERNAL PROC[ conv: ConversationData, convClassID: ConversationHandle ] RETURNS [ convID: ConversationHandle_nextConv ] = { IF convClassID#nullConvHandle THEN convID _ BasicTime.Update[convClassID, convClassIncrement] ELSE nextConv _ BasicTime.Update[nextConv, 1]; IF convTable.Lookup[convID]#NIL THEN { IF convClassID#nullConvHandle THEN RETURN EnhandleConv[conv,convID]; Log.Problem["Struct. broken", $System]; RETURN[Thrush.nullConvHandle]; }; convTable.Insert[ NEW[OrderedSymbolTable.NodeRecord_[conv:conv]], convID ]; }; KillHandleConv: INTERNAL PROC[ convID: ConversationHandle ] = { []_convTable.Delete[convID]; }; DehandleParty: PUBLIC PROC[partyID: Thrush.PartyHandle, insist: BOOLEAN_FALSE] RETURNS[party: PartyData] = TRUSTED { RETURN[Thrush.Dehandle[partyID, RTPartyType, insist]]; }; DehandleSmarts: PUBLIC PROC[smartsID: Thrush.SmartsHandle, insist: BOOLEAN_FALSE] RETURNS[smarts: SmartsData] = TRUSTED { RETURN[Thrush.Dehandle[smartsID, RTSmartsType, insist]]; }; EKResults: TYPE = { new, duplicate, disagreement, full }; EnterKey: INTERNAL PROC[conv: ConversationData, key: Thrush.EncryptionKey] RETURNS [ keyIndex: [0..17B], results: EKResults] = { IF key=Thrush.nullKey THEN RETURN[0, duplicate]; FOR keyIndex IN [1..17B] DO IF conv.keyTable[keyIndex]=key THEN RETURN[keyIndex, duplicate] ELSE IF conv.keyTable[keyIndex][0].b=0 AND conv.keyTable[keyIndex] = Thrush.nullKey THEN EXIT; REPEAT FINISHED => RETURN[0, full]; ENDLOOP; conv.keyTable[keyIndex] _ key; conv.newKeyTable _ TRUE; RETURN[keyIndex, new]; }; Validity: TYPE = { noop, -- Already in this state; no transition action required valid, -- This transition is all right if other tests check out. no, -- This transition is not allowed, nohow. cantReq -- This transition may not be requested by the Party-level client. }; validTransitions: ARRAY Thrush.StateInConv OF ARRAY Thrush.StateInConv OF Validity = [ [ noop, valid, valid, valid, valid, valid, valid, valid, valid, valid, no ], -- idle [ valid, noop, valid, no, no, no, no, no, no, no, no ], -- reserved [ valid, valid, noop, no, no, no, no, no, no, no, no ], -- parsing [ valid, valid, valid, noop, no, no, no, no, no, no, no ], -- initiating [ cantReq, cantReq, cantReq, no, noop, no, no, no, no, no, no ], -- pending [ no, no, no, cantReq, no, noop, no, no, no, no, no ], -- maybe [ no, no, no, no, valid, no, noop, no, no, no, no ], -- ringing [ cantReq, cantReq, cantReq, cantReq, cantReq, cantReq, cantReq, noop, no, cantReq, no ], -- canAct [ valid, valid, valid, cantReq, valid, cantReq, valid, valid, noop, valid, no ], -- active [ no, no, no, no, no, no, no, valid, valid, noop, no ], -- inactive [ no, no, no, no, no, no, no, no, no, no, no ] -- any ]; OrderedSymbolTable.Initialize[ NEW[OrderedSymbolTable.NodeRecord_[]],NEW[OrderedSymbolTable.NodeRecord_[]]]; convTable _ OrderedSymbolTable.CreateTable[NEW[OrderedSymbolTable.NodeRecord_[]]]; }. öThPartyOpsImpl.mesa Last modified by D. Swinehart, December 28, 1983 9:29 pm Copies Conversation ID stuff Implementation of ThParty << State must be initiating if calledPartyID#nullHandle. Nobody's checking. >> If there is exactly one remaining party that is not idle . . . now it will be. If callee is answering or threatening to answer, advance caller, too. Must match the other, valid, one. If another party to be changed was found; do it. Wake all parties to conversation, now. Potential new key. stateID: StateID, partyID: PartyHandle, smartsID: SmartsHandle, convID: ConversationHandle, Utilities Validate parameters: existence, in conversation, state match; produce dehandled values. Validation for smarts is only partial; verifies that it's a smarts. Ferret out voice smarts, if need be. Maintain active party counts, generate connection specifications. Generate a Lark.ConnectionSpec for this conversant. Complains unless all parties in conversation are presently idle. At present, called only from Verify Called only from CreateConv. Called only from DestroyConversation Key Table management Presently returns full if table is full; user should hang up and restart. Initialization Columns represent present state: rows represent proposed new states Êb˜Jšœ™Jšœ8™8J˜šÏk ˜ Jšœœ˜$Jšœœ2˜˜Ä—Jšœœ˜J˜šœœ˜Jšœ˜—J˜Jšœœ˜-Jšœœ,˜9J˜J˜—šœœœœ˜(š˜J˜J˜J˜ Jšœ ˜ J˜J˜J˜J˜J˜J˜—Jšœ˜Jšœ˜J˜—šœ™Jšœ œ˜#Jšœ œ˜'šœœ˜#Jšœ<œ#˜c—Jšœ œ˜#Jšœœ#˜9Jšœœ˜5Jšœ œ˜'Jšœœœœœœœ˜JJšœœ œ˜Jšœœ˜ J˜BJ˜3šœ œÏc ˜7Jšœ=œ ˜N—Jšœ œž˜;Jšœ œž ˜1Jšœœ˜Jšœœ œ˜Jšœœ œ˜Jšœ œž ˜9šœ œž˜=Jšœ=œ˜O—Jšœœž ˜3Jšœ œ˜Jšœ œ˜'J˜—™J˜J˜,J˜$J˜\J˜—šœ™J˜šÏnœœ œ˜Jšœœ˜ J˜Jšœ ž<˜\Jšœž:˜RJ˜J˜Jšœ˜Jšœ œœ˜Jšœ ˜ Jšœœœ"˜4Jšœœœ˜Jšœ ˜ Jšœ˜Jšœ˜Jšœ˜Jšœœ˜ šœ œ#˜4šœ ˜ Jšœ9˜9Jšœ/˜/——J˜6J˜Jšœ œœ˜šœœœœ˜.Jšœ˜Jšœ œœ˜—J˜J˜šœœ˜$J˜+Jšœ œœœ˜3Jšœ#œœ˜BšœS˜SJšœ˜—Jšœ˜J™O—šœQ˜QJšœ3˜3—J˜—J˜šŸœœœœ˜Jšœœ˜ J˜J˜J˜Jšœ ˜ šœœœ˜Jšœœœ˜J˜7Jšœœ˜ J˜/Jšœ œœ˜JšœQ˜QJšœ˜——J˜šŸ œœœœ˜!J˜Jšœ˜Jšœ˜J˜ J˜J˜Jšœ ˜ Jšœœœ˜Jšœ˜Jšœœ˜J˜šœ!œ˜2J˜J˜Jšœœ˜—J˜!™Nšœ œ˜˜ J˜šœ˜Jšœœœ˜Jšœœ˜—Jš œ œœœœœ˜:Jšœ œ ˜Jšœ˜—Jšœ˜ ——J˜#™Ešœ œ˜˜ Jšœ œœœ˜7šœ˜Jšœœ˜Jšœœœ˜—Jšœ œ ˜Jšœ˜Jšœœ˜—Jšœ˜ ——šœ,˜6Jšœœ˜)Jšœœž ˜Jšœ œ˜Jšœ˜!—˜J˜-Jšœ0˜0—Jšœ ˜ Jšœ œœ<˜Qšœœœ˜šœ6˜@šœ>œ˜HJšœž ™!—Jšœœ˜Jšœœ˜Jšœ*œ˜;—˜J™0JšœT˜T—Jšœ˜—Jšœ˜—J˜šŸ œœœœ˜Jšœœ˜J˜šœœ˜ Jšœœ˜Jšœ ˜ Jšœœœ˜Jšœ œœ˜—J˜7J˜/Jšœœ ˜$Jšœ œœ˜šœœœ˜)J˜Jšœ,œœ˜8Jšœ!˜!J˜6šœœ˜Jšœ œ$œ˜=—Jšœœ˜—J˜J™—šŸœœœœ˜&Jšœœ˜ J˜Jšœ˜Jšœ˜Jšœœ+˜4Jšœœœ˜šœ˜JšœK˜K—Jšœ˜Jšœ˜J˜—šŸ œœœ˜Jšœ˜Jšœž*˜FJšœ˜Jšœ˜Jšœœœ˜2Jšœœ#œœ˜>šœœ$˜.Jšœ˜J˜J˜'Jšœœ>˜F—Jšœœ˜,J˜UJšœ)˜)Jšœ˜—J˜šŸœœœœ˜GJšœœ8˜QJšœ˜Jšœ˜—J˜šŸœ œ˜ Jšœœ˜ Jšœž˜6Jšœž˜4J˜Jšœœœœ˜J˜—šŸ œœ˜Jšœœ˜Jšœ˜˜!Jšœœœ˜—Jšœœœ˜J˜J˜J˜ šœ œ˜+šœ œ˜Jšœœ0œ˜U——Jšœ/˜/Jšœ œœ˜Jšœœœ œœœœ˜WJšœœ˜7JšœZ˜ZJšœœ*˜K˜;J™9—J˜J˜—šŸ œœ˜ Jšœœ˜Jšœ˜J™Jšœ™J™J™J˜Jšœœœ˜/Jšœœœ˜J˜J˜ Jšœ*˜*Jšœ œ)˜;Jšœ˜—J˜š Ÿ œœœœœ'œ˜YJšœœœ˜J˜.Jš œœœœœ˜7—J˜š Ÿ œœœœ œ˜bJšœœœ˜J˜.Jš œœœœœœ˜2—J˜š Ÿœœœœœ!˜UJ˜*šœœ˜1šœ œ˜šœ ˜ Jšœœ ˜*Jšœ˜Jšœ ˜ Jšœ˜—Jšœ˜Jšœ˜——JšœœœC˜TJ˜—J˜—™ J˜šŸœœœ˜7šœœ˜ J˜J˜J˜ Jšœ œ˜—J™WJ™CJšœ:˜:Jšœ&˜&Jšœ)˜)šœœ œ˜J˜J˜J˜Jšœ ˜—šœ œœ˜ Jšœœ˜(Jšœ5˜5—š œœœœ˜Jšœœž œ˜7—šœ ˜Jš œœœœ(œ˜UJšœ)œ˜F—š œœœœ˜ Jšœ œœ˜8J˜ Jšœ˜—J˜J˜—šŸ œ œ˜J˜J˜J˜$J˜J˜Jšœ ˜Jšœ"˜%Jšœœœ˜ Jšœœ5œ˜KJ˜J˜J˜,J˜ Jšœ˜ šœœ˜$J˜FJ˜J˜Ošœ œ œ˜#Jšœœœ˜%—J˜J˜J˜J˜—J˜Jšœœ ˜8Jšœœ˜Jšœœž œ˜7šœ œœ˜šœœ˜$Jšœœ˜ J˜—Jšœ!˜!Jšœ$˜$Jšœ%˜%—J™$šœ˜šœ#œ œ˜KJšœ.˜.š œœœœœ˜5Jšœœž˜-J˜0˜ Jšœ œ*ž œ˜HJšœ œœ$˜8—Jšœ˜ ——Jšœ˜—J™AJ˜Jš œ œ œœœ˜Jšœ œœœ˜"šœ ˜Jšœ˜Jšœ˜Jšœ'˜.—šœœ˜3J˜J˜Jšœ˜Jšœž˜*J˜ Jšœ œœœ˜1J˜ J˜#J˜ Jšœ˜—Jšœœ˜ J˜J˜—šŸœœœœ1˜OJšœœ˜$Jšœœœ-˜=Jšœ œœœ˜"Jšœœ.˜QJšœœ˜"—J˜šŸ œœœ.˜Hšœ&œ˜.J˜(šœ˜JšœH˜K—Jš œœœœœ˜?J˜—J˜)J˜—š Ÿœœœœœœ˜1Jšœ-ž˜Ašœœœœœœœœ˜4Jšœœ ˜—Jšœ ˜J˜—š Ÿœœœœ œ˜JJšœ œœ˜˜ J™@šœ œ˜šœ œœ˜(Jšœ œœœ˜#—Jšœ˜ ——J˜9šœœ œ œ˜=J˜——šŸ œœœœ˜?J™#Jšœœ˜)J˜9Jš œœœœ œœ˜,J˜—J˜šŸ œœœ;˜VJ™Jšœ,˜3šœ˜"Jšœ:˜:—Jšœ*˜.šœœœ˜&Jšœœœ˜DJšœ(œ˜I—Jšœœ:˜PJ˜—J˜šŸœœœ"˜?J™$J˜J˜—J˜š Ÿ œœœ&œœ˜NJšœœ˜%Jšœ0˜6J˜J˜—š Ÿœœœ(œœ˜QJšœœ˜'Jšœ2˜8J˜J˜——J™™J˜9J˜šŸœœœ˜/Jšœ˜Jšœ.˜5J™IJšœœœ˜0šœ œ ˜Jšœœœ˜?Jš œœ œ*œœ˜^Jšœœœ œ˜,—Jšœ˜Jšœœ˜Jšœ˜J˜—J˜—šœ™J™šœ œ˜Jšœž7˜?Jšœ ž9˜BJšœž)˜/Jšœ žB˜KJ˜—J˜Jš œœœœœ ˜VšžC™CJšMÐck˜TJš7  ˜CJš7  ˜BJš;  ˜HJšA  ˜KJš7 ˜?Jš5  ˜?JšZ  ˜cJšQ  ˜ZJš8  ˜CJš/ ˜5J˜J˜——šœ˜Jšœ#œ$˜M—Jšœ+œ$˜RJ˜J˜—…—K0eˆ