<> <> <> DIRECTORY BluejayUtils USING [ DescribeInterval ], DESFace USING [ GetRandomKey, Key ], IO, Lark USING [ ConnectionSpecRec, KeyTableBody, VoiceSocket ], RedBlackTree, Pup USING [ Address, nullAddress, Socket ], PupSocket USING [ CreateEphemeral, GetLocalAddress, Socket ], RefID USING [ Seal ], SafeStorage USING [ GetCanonicalType, Type ], BasicTime USING [ Update, Now ], Thrush USING [ AlertKind, CallUrgency, ConversationHandle, ConvEvent, ConvEventBody, Credentials, Dehandle, EncryptionKey, epoch, EventSequence, EventSequenceBody, H, IntervalSpec, IntervalSpecs, NB, nullConvHandle, nullHandle, nullKey, PartyHandle, PartyType, ProseSpec, ProseSpecs, Reason, ROPE, SHHH, SmartsHandle, StateID, StateInConv, ThHandle, unencrypted, VoiceTime ], 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 ], TU USING [ RefAddr ], VoiceUtils USING [ Problem, ProblemFR ] ; ThPartyOpsImpl: CEDAR MONITOR LOCKS root IMPORTS BluejayUtils, DESFace, root: ThPartyMonitorImpl, RefID, SafeStorage, BasicTime, IO, PupSocket, RedBlackTree, ThNet, ThPartyPrivate, Thrush, Triples, TU, VoiceUtils 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; 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; IF newConv OR credentials.convID=nullConvHandle THEN { credentials.convID _ CreateConv[ partyID: credentials.partyID, convID: credentials.convID, urgency: urgency, alertKind: alertKind].convID; newConv_TRUE; }; [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 OR state = initiating THEN { calledParty _ DehandleParty[calledPartyID]; IF calledParty=NIL THEN {nb_noSuchParty2; RETURN}; <<<< Not satisfied with this, yet. If trunkParty info has been changed, don't make the call.>>>> TRUSTED { SELECT calledParty.type FROM trunk, service => IF calledParty.reservedBy # credentials.partyID THEN {nb_noSuchParty2; RETURN}; ENDCASE; }; IF calledPartyID=credentials.partyID THEN {nb_narcissism; RETURN}; [] _ PostConvEvent[conv: conv, party: calledParty, state: pending, reason: wontSay, comment: comment ]; }; <<<< State must be initiating if calledPartyID#nullHandle. Nobody's checking. >>>> [] _ 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; [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 => VoiceUtils.ProblemFR["%g, %g: Impossible", $System, NIL, TU.RefAddr[party], TU.RefAddr[cfRef]]; 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 => { VoiceUtils.Problem[IO.PutFR["%g, %g, %g: State mismatch among parties", TU.RefAddr[party], TU.RefAddr[otherParty], TU.RefAddr[cfRef]], $System]; RETURN; }; <> cantReq, valid => NULL; noop => RETURN; ENDCASE => { VoiceUtils.ProblemFR["%g, %g: Impossible", $System, NIL, TU.RefAddr[otherParty], TU.RefAddr[cfRef]]; 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, conference: BOOL_FALSE ] = { conv: ConversationData; [conv, , , nb] _ Verify[credentials]; IF nb=stateMismatch THEN nb_success; IF nb#success THEN RETURN; [partyID, conference] _ FindOtherParty[credentials.partyID, conv]; description _ SELECT TRUE FROM conference => "Group of parties", partyID#nullHandle => ThPartyPrivate.DoDescribeParty[partyID], ENDCASE => NIL; }; <<>> FindOtherParty: PUBLIC PROC[myPartyID: PartyHandle, conv: ConversationData] RETURNS[partyID: PartyHandle_nullHandle, conference: BOOL_FALSE ] = { FOR i: NAT IN [1..conv.currentStateID] DO ce: ConvEvent = conv.log[i]; IF myPartyID=ce.credentials.partyID THEN LOOP; partyID _ ce.credentials.partyID; IF conv.numParties>2 THEN { conference_TRUE; 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 { 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 VoiceUtils.ProblemFR["%g: Conversation still has parties", $System, NIL, TU.RefAddr[conv]]; KillHandleConv[conv.convID]; }; MergeConversations: PUBLIC PROC[ shhh: SHHH, credentials: Credentials, -- of surviving conversation otherStateID: StateID, -- of dissolving conversation otherConvID: ConversationHandle ] RETURNS [ nb: NB ] = {NULL}; SetIntervals: PUBLIC ENTRY PROC[ shhh: SHHH _ none, credentials: Credentials, intervalSpecs: Thrush.IntervalSpecs ] 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]; [] _ PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , ,intervalSpecs]; FOR iSL: Thrush.IntervalSpecs _ intervalSpecs, iSL.rest WHILE iSL#NIL DO iS: Thrush.IntervalSpec = iSL.first; IF ~ThNet.pd.encryptVoice THEN iS.keyIndex_0; IF iS.type=request THEN iS.intID.stateID _ conv.currentStateID; ENDLOOP; Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf]; <> }; SetProse: PUBLIC ENTRY PROC[ shhh: SHHH _ none, credentials: Credentials, proseSpecs: Thrush.ProseSpecs ] 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]; [] _ PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , , , proseSpecs]; FOR pSL: Thrush.ProseSpecs _ proseSpecs, pSL.rest WHILE pSL#NIL DO pS: Thrush.ProseSpec = pSL.first; IF pS.type=request THEN pS.intID.stateID _ conv.currentStateID; ENDLOOP; Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf]; <> }; DescribeInterval: PUBLIC PROC[ shhh: SHHH _ none, credentials: Credentials, targetInterval: Thrush.IntervalSpec, minSilence: Thrush.VoiceTime _ 1 -- Smallest silent interval that will be considered silence. ] RETURNS [ nb: NB, exists: BOOL_FALSE, intervals: Thrush.IntervalSpecs_NIL ] = { conv: ConversationData; party: PartyData; cfRef: CFRef; [conv, party, cfRef, nb] _ VerifyEnt[credentials]; IF nb#success AND nb#stateMismatch THEN RETURN; [exists, intervals] _ BluejayUtils.DescribeInterval[targetInterval.tune, targetInterval.interval, minSilence]; }; 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 OR nb=stateMismatch 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]; }; <> VerifyEnt: PUBLIC ENTRY PROC[ credentials: Credentials ] RETURNS [ conv: ConversationData, party: PartyData, cfRef: CFRef, nb: Thrush.NB ] = { [conv, party, cfRef, nb] _ Verify[credentials]; }; 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, intervalSpecs: Thrush.IntervalSpecs_NIL, proseSpecs: Thrush.ProseSpecs_NIL ] RETURNS [cfRef: CFRef_NIL] = { 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: (SELECT party.type FROM trunk => party.outgoing, ENDCASE => NIL), comment: comment, intervalSpecs: intervalSpecs, proseSpecs: proseSpecs ]]; }; 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[] ]]; <<In Cedar 6.1 and thereafter, socket numbers cannot be invented but must be assigned by the implementation of PupSocket. For this version of Thrush/Bluejay, only Bluejay uses a Cedar socket, and only Bluejay provide parties of type $service, so only it needs to be dealt with, and we can tell if that's what we have by looking at the type. We create the PupSocket.Socket here (without specifying the remote address), jam it into a RefID, and store the RefID in cfRef (RefID because theoretically Jay may not be local, and because we may have to pass it as a long cardinal into Bluejay somewhere). We record the assigned socket ID in cfRef.sockID. BluejaySmarts knows how to take all this apart. We will destroy the RefID when the cfRef.>> IF party.type = $service THEN { pa: Pup.Address; socket: REF PupSocket.Socket = NEW[PupSocket.Socket _ PupSocket.CreateEphemeral[remote: Pup.nullAddress, getTimeout: 100]]; -- See BluejaySmartsImpl cfRef.socket _ RefID.Seal[socket]; pa _ PupSocket.GetLocalAddress[socket^]; cfRef.sockID _ LOOPHOLE[pa.socket]; }; 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: Pup.Socket; party: PartyData = NARROW[trip.val]; 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[ IF party=myParty AND party.type=$service THEN cfRef.socket ELSE cfRef.sockID]; <<See earlier comments about Cedar 6.1 communications accommodation.>> WITH smarts.properties SELECT FROM voiceTerminal => sockets[specIndex] _ [ [ netAddress.net], [netAddress.host], sockID ]; ENDCASE => VoiceUtils.Problem["Non-voice terminal trying to be voice smarts", $System]; parties[specIndex] _ party; 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 => VoiceUtils.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 VoiceUtils.ProblemFR["%g: No such conversation", $System, NIL, TU.RefAddr[conv]] ELSE IF stateID=0 THEN RETURN[NIL] ELSE IF stateID>=conv.log.size THEN VoiceUtils.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]; }; <> convTable: RedBlackTree.Table = RedBlackTree.Create[ConvGet, ConvCompare]; ConvTableEntry: TYPE = REF ConvTableRecord; ConvTableRecord: TYPE = RECORD [ convID: ConversationHandle, conv: ConversationData ]; cTE: ConvTableEntry _ NEW[ConvTableRecord _ [nullConvHandle, NIL]]; <> DehandleConv: PUBLIC INTERNAL PROC[ convID: ConversationHandle] <> RETURNS [conv: ConversationData_NIL ] = { convEntry: ConvTableEntry; cTE.convID _ convID; convEntry _ NARROW[convTable.Lookup[cTE]]; RETURN[IF convEntry#NIL THEN convEntry.conv ELSE NIL]; }; EnhandleConv: INTERNAL PROC[ conv: ConversationData, convClassID: ConversationHandle ] <> RETURNS [ convID: ConversationHandle_nextConv ] = { entry: ConvTableEntry; IF convClassID#nullConvHandle THEN convID _ LOOPHOLE[LOOPHOLE[convClassID, LONG CARDINAL] + convClassIncrement] <> ELSE nextConv _ BasicTime.Update[nextConv, 1]; cTE.convID _ convID; IF convTable.Lookup[cTE]#NIL THEN { IF convClassID#nullConvHandle THEN RETURN EnhandleConv[conv,convID]; VoiceUtils.Problem["Struct. broken", $System]; RETURN[Thrush.nullConvHandle]; }; entry _ NEW[ConvTableRecord _ [convID, conv]]; convTable.Insert[entry, entry]; }; KillHandleConv: INTERNAL PROC[ convID: ConversationHandle ] = { <> cTE.convID _ convID; []_convTable.Delete[cTE]; }; ConvCompare: RedBlackTree.Compare = { keyRec: ConvTableEntry = NARROW[k]; convRec: ConvTableEntry = NARROW[data]; c1: LONG CARDINAL = LOOPHOLE[keyRec.convID, LONG CARDINAL]; c2: LONG CARDINAL = LOOPHOLE[convRec.convID, LONG CARDINAL]; RETURN[SELECT c1 FROM < c2 => less, > c2 => greater, ENDCASE => equal ]; }; ConvGet: RedBlackTree.GetKey = { RETURN[data]; }; DehandleParty: PUBLIC PROC[partyID: Thrush.PartyHandle, insist: BOOLEAN_FALSE] RETURNS[party: PartyData] = TRUSTED { RETURN[LOOPHOLE[Thrush.Dehandle[partyID, RTPartyType, insist]]]; }; DehandleSmarts: PUBLIC PROC[smartsID: Thrush.SmartsHandle, insist: BOOLEAN_FALSE] RETURNS[smarts: SmartsData] = TRUSTED { RETURN[LOOPHOLE[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 ]; }. <<>> <> <> <<>> <> <> <> <> <> <> <> <> <> <<>>