<> <> <> DIRECTORY BasicTime USING [ Update, Now ], CardTable USING [ Create, Delete, EachPairAction, Fetch, Pairs, Ref, Store ], DESFace USING [ GetRandomKey, Key ], Lark USING [ KeyTable, KeyTableBody, VoiceSocket ], MBQueue USING [ Create, QueueClientAction ], Process USING [ Detach, SecondsToTicks, SetTimeout ], PupTypes USING [ PupSocketID ], RefID USING [ ID, Reseal, Unseal ], RPC USING [ CallFailed ], ThNet USING [ pd ], ThParty, ThPartyPrivate USING [ ConversationBody, ConversationData, ConvState, ConvStateBody, DoDescribeParty, PartyBody, PartyData, SmartsBody, SmartsData ], ThPartyMonitorImpl, Thrush USING [ AlertKind, CallUrgency, ConversationID, ConvEvent, ConvEventBody, Credentials, EncryptionKey, epoch, Machine, NB, nullConvID, nullID, nullKey, PartyID, Reason, ROPE, SHHH, SmartsID, StateID, StateInConv, unencrypted ], ThSmartsRpcControl USING [ InterfaceRecord ], Triples USING [ Any, Erase, Foreach, ForeachProc, Make, Select ], TU USING [ MakeUnique ], VoiceUtils USING [ Problem ] ; ThPartyOpsImpl: CEDAR MONITOR LOCKS root IMPORTS BasicTime, CardTable, DESFace, MBQueue, Process, RefID, root: ThPartyMonitorImpl, RPC, ThNet, ThParty, ThPartyPrivate, Thrush, Triples, TU, VoiceUtils EXPORTS ThParty, ThPartyPrivate SHARES ThPartyMonitorImpl = { <> ConvEvent: TYPE = Thrush.ConvEvent; ConvState: TYPE = ThPartyPrivate.ConvState; ConversationData: TYPE = ThPartyPrivate.ConversationData; ConversationID: TYPE = Thrush.ConversationID; Credentials: TYPE = Thrush.Credentials; Reseal: PROC[r: REF] RETURNS[RefID.ID] = INLINE {RETURN[RefID.Reseal[r]]; }; NB: TYPE = Thrush.NB; nullConvID: Thrush.ConversationID = Thrush.nullConvID; PartyBody: TYPE = ThPartyPrivate.PartyBody; -- Concrete PartyData: TYPE = ThPartyPrivate.PartyData; -- REF Concrete PartyID: TYPE = Thrush.PartyID; -- ID nullID: Thrush.PartyID = Thrush.nullID; Reason: TYPE = Thrush.Reason; ROPE: TYPE = Thrush.ROPE; SHHH: TYPE = Thrush.SHHH; none: SHHH = Thrush.unencrypted; SmartsBody: TYPE = ThPartyPrivate.SmartsBody; -- Concrete SmartsData: TYPE = ThPartyPrivate.SmartsData; -- REF Concrete SmartsID: TYPE = Thrush.SmartsID; -- ID <> nextConv: ConversationID _ Thrush.epoch; <> CreateConversation: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, state: Thrush.StateInConv, urgency: Thrush.CallUrgency, alertKind: Thrush.AlertKind, subject: ROPE ] RETURNS [ nb: NB, convEvent: ConvEvent ] = { ENABLE UNWIND => NULL; conv: ConversationData = NEW[ThPartyPrivate.ConversationBody _ [ timeOfID: BasicTime.Now[], startTime: BasicTime.Now[], subject: subject, urgency: urgency, alertKind: alertKind, conferenceHost: ConferenceHost[], keyTable: NEW[Lark.KeyTableBody[20B]] ] ]; TRUSTED { []_EnterKey[conv, DESFace.GetRandomKey[]]; }; <> conv.convID _ EnhandleConv[conv]; credentials.stateID _ 0; credentials.state _ $neverWas; credentials.convID _ conv.convID; [nb, convEvent] _ DoAdvance[credentials: credentials, state: state, newInConv: TRUE]; IF nb # $success THEN DeleteConversation[ conv ]; }; Alert: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, calledPartyID: PartyID, comment: ROPE ] RETURNS [ nb: NB ] = { ENABLE UNWIND => NULL; convState: ConvState; conv: ConversationData; callingParty: PartyData; calledParty: PartyData; IF calledPartyID=credentials.partyID THEN RETURN[nb: $narcissism]; [, callingParty,, nb] _ Verify[credentials]; IF nb # $success THEN RETURN; credentials.partyID _ calledPartyID; [conv, calledParty, convState, nb] _ Verify[credentials]; SELECT nb FROM $notInConv => NULL; $success => IF conv#NIL THEN RETURN[$convStillActive]; -- already in the conversation! $noSuchParty => RETURN[$noSuchParty2]; ENDCASE=> RETURN; <> credentials.stateID _ 0; IF calledParty=NIL OR (calledParty.type = $trunk AND calledParty.reservedBy # Reseal[callingParty]) THEN RETURN[nb: $noSuchParty2]; nb _ DoAdvance[credentials: credentials, state: $notified, newInConv: TRUE].nb; }; Advance: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, state: Thrush.StateInConv, reportToAll: BOOL, reason: Thrush.Reason, comment: ROPE ] RETURNS [nb: NB, convEvent: ConvEvent] = { ENABLE UNWIND => NULL; [nb, convEvent] _ DoAdvance[ credentials: credentials, state: state, reportToAll: reportToAll, reason: reason, comment: comment, newInConv: FALSE ]; }; DoAdvance: PUBLIC INTERNAL PROC [ credentials: Credentials, state: Thrush.StateInConv, reportToAll: BOOL_FALSE, reason: Thrush.Reason_NIL, comment: ROPE_NIL, newInConv: BOOL -- if TRUE, party must not be in the conversation yet . . . and vice versa ] RETURNS [nb: NB_$success, convEvent: ConvEvent_NIL] = { convState: ConvState; conv: ConversationData; party: PartyData; [conv, party, convState, nb] _ Verify[credentials, newInConv]; SELECT nb FROM $success => { IF convState=NIL THEN RETURN; IF state = $active AND party.partyActive THEN RETURN[nb: $partyAlreadyActive]; IF conv.numIdle = conv.numParties AND state # $idle THEN RETURN[nb: $convIdle]; credentials.state _ state; }; $stateMismatch => credentials.state _ convState.state; ENDCASE => RETURN; convEvent _ PostConvEvent[ credentials: credentials, convState: convState, reason: reason, comment: comment, reportToAll: reportToAll]; }; GetConversationInfo: PUBLIC ENTRY PROC [ shh: SHHH_none, convID: ConversationID ] RETURNS [ nb: NB_$success, cInfo: ThParty.ConversationInfo_[] ] = { conv: ConversationData = UnsealConv[convID]; IF conv=NIL THEN RETURN[nb: $noSuchConv]; cInfo.subject _ conv.subject; cInfo.urgency _ conv.urgency; cInfo.alertKind _ conv.alertKind; cInfo.startTime _ conv.startTime; <> cInfo.conferenceHost _ conv.conferenceHost; cInfo.numParties _ conv.numParties; cInfo.numActive _ conv.numActive; cInfo.numIdle _ conv.numIdle; cInfo.originator _ Reseal[Triples.Select[$Originator, conv, -- originator --]]; }; GetPartyInfo: PUBLIC ENTRY PROC [ shh: SHHH_none, credentials: Credentials, nameReq: ThParty.NameReq, allParties: BOOL ] RETURNS [ nb: NB, pInfo: ThParty.PartyInfo_NIL ] = { conv: ConversationData = UnsealConv[credentials.convID]; convState: ConvState; ownParty: PartyData = UnsealParty[credentials.partyID]; GetParties: INTERNAL FinishProcType = { <> index: NAT; HowToReport _ NIL; IF smarts.properties.role # $voiceTerminal THEN RETURN; IF party=ownParty THEN { index _ 0; pInfo.numParties_MAX[1, pInfo.numParties]; } ELSE { index _ MAX[1, pInfo.numParties]; pInfo.numParties _ index+1; }; pInfo[index] _ [ partyID: Reseal[party], type: party.type, state: convState.state, <> <> name: IF nameReq=$none THEN NIL ELSE ThPartyPrivate.DoDescribeParty[party, nameReq], numConvs: party.numConvs, enabled: party.enabled, partyActive: party.partyActive, socket: [smarts.properties.machine.net, smarts.properties.machine.host, convState.sockID]]; }; nb _IF conv=NIL THEN $noSuchConv ELSE IF ownParty=NIL THEN $noSuchParty ELSE $success; IF nb=$success THEN { convState _ GetConvState[conv, ownParty]; IF convState=NIL THEN nb _ $notInConv }; IF nb#$success THEN RETURN; pInfo _ NEW[ThParty.PartyInfoSeq[IF allParties THEN conv.numParties ELSE 1]]; pInfo.conferenceHost _ conv.conferenceHost; pInfo[0] _ []; Report[ FinishProc: GetParties, reportToAll: allParties, conv: conv, party: ownParty, whatToReport: NIL]; }; GetKeyTable: PUBLIC ENTRY PROC [ shh: SHHH_none, credentials: Credentials ] RETURNS [ nb: NB, keyTable: Lark.KeyTable_NIL ] = { conv: ConversationData; [conv,,,nb] _ Verify[credentials]; IF nb # $success THEN RETURN; keyTable _ conv.keyTable; }; RegisterKey: PUBLIC ENTRY PROC [ shh: SHHH _ none, credentials: Credentials, key: Thrush.EncryptionKey ] RETURNS [ nb: NB, keyIndex: [0..17B] _ 0 ] = { ENABLE UNWIND => NULL; conv: ConversationData; [conv, , , nb] _ Verify[credentials]; <> IF nb=$success OR nb=$stateMismatch THEN keyIndex _ EnterKey[conv, key].keyIndex; }; <> VerifyEnt: PUBLIC ENTRY PROC[ credentials: Credentials ] RETURNS [ conv: ConversationData, party: PartyData, convState: ConvState, nb: Thrush.NB ] = { [conv, party, convState, nb] _ Verify[credentials]; }; Verify: PUBLIC INTERNAL PROC[ credentials: Credentials, newInConv: BOOL_FALSE ] RETURNS [ conv: ConversationData_NIL, party: PartyData_NIL, convState: ConvState_NIL, nb: Thrush.NB ] = { <> <> smarts: SmartsData _ UnsealSmarts[credentials.smartsID]; conv_UnsealConv[credentials.convID]; party_UnsealParty[credentials.partyID]; nb _ SELECT NIL[REF ANY] FROM smarts => $noSuchSmarts, party => $noSuchParty, conv => IF credentials.convID#nullConvID THEN $noSuchConv ELSE $success, ENDCASE=> $success; IF nb=$success AND ~party.enabled THEN nb _ $noSuchParty; IF nb#$success OR conv=NIL THEN RETURN; <> convState _ GetConvState[conv, party, newInConv]; IF convState=NIL THEN { nb _ $notInConv; RETURN; }; <<IF newInConv is TRUE and convState already existed, it's a logical error, but we're not checking it.>> SELECT credentials.stateID FROM < convState.stateID => nb_$stateMismatch; > convState.stateID => nb_$interfaceError; -- really bogus behaviour! ENDCASE; }; GetConvState: INTERNAL PROC[ conv: ConversationData, party: PartyData, createOK: BOOL_FALSE] RETURNS [convState: ConvState_NIL] = { GCS: Triples.ForeachProc = { WITH trip.att SELECT FROM cS: ConvState => { convState_cS; RETURN[FALSE]; }; ENDCASE; }; Triples.Foreach[Triples.Any, conv, party, GCS]; IF convState#NIL OR ~createOK THEN RETURN; convState _ NEW[ThPartyPrivate.ConvStateBody _ [sockID: NewId[]]]; Triples.Make[convState, conv, party]; Triples.Make[$Party, conv, party]; IF conv.numParties=0 THEN TU.MakeUnique[$Originator, conv, party]; party.numConvs _ party.numConvs+1; conv.numParties _ conv.numParties+1; }; PostConvEvent: INTERNAL PROC[ credentials: Credentials, convState: ConvState, reason: Thrush.Reason_NIL, comment: Thrush.ROPE_NIL, reportToAll: BOOL ] RETURNS [convEvent: ConvEvent ] = { conv: ConversationData _ UnsealConv[credentials.convID]; party: PartyData _ UnsealParty[credentials.partyID]; SELECT convState.state FROM -- states that was, but will no longer be $idle => { conv.numIdle _ conv.numIdle-1; party.numConvs _ party.numConvs+1; }; <> $active => { conv.numActive _ conv.numActive -1; party.partyActive _ FALSE; }; ENDCASE; SELECT credentials.state FROM -- states that will be $idle => { conv.numIdle _ conv.numIdle+1; party.numConvs _ party.numConvs-1; }; $active => { conv.numActive _ conv.numActive+1; party.partyActive _ TRUE; }; ENDCASE; convState.stateID _ credentials.stateID _ convState.stateID+1; convState.state _ credentials.state; conv.timeOfID _ convState.time _ BasicTime.Now[]; convEvent _ NEW[ Thrush.ConvEventBody _ [ self: [ ], other: credentials, time: convState.time, reason: reason, comment: comment ] ]; <> Report[FinishEvent, reportToAll, conv, party, convEvent ]; -- will queue up either a report or an idle check for each recipient. }; PackagedReport: TYPE = REF ReportBody; ReportBody: TYPE = RECORD [ smarts: SmartsData, whatToReport: REF ]; FinishProcType: TYPE = PROC[party: PartyData, smarts: SmartsData, convState: ConvState, whatToReport: REF] RETURNS [HowToReport: PROC[r: REF]_NIL, whatToReallyReport: REF_NIL]; <> <<>> Report: INTERNAL PROC[ FinishProc: FinishProcType, -- provides Report proc as one of its return values reportToAll: BOOL, conv: ConversationData, party: PartyData, whatToReport: REF ] = { ReportToOne: INTERNAL Triples.ForeachProc = { <<[trip: Triples.TripleRec] RETURNS [continue: BOOLEAN _ TRUE]>> party: PartyData = NARROW[trip.val]; ReportToParty[party, party]; }; ReportToParty: INTERNAL PROC[party: PartyData, participant: PartyData] = { <> pSmarts: SmartsData; packagedReport: PackagedReport; realReport: REF _ whatToReport; HowToReport: PROC[r: REF]_NIL; IF party=NIL OR ~party.enabled THEN RETURN; pSmarts _ NARROW[Triples.Select[$Smarts, party, --pSmarts--]]; IF pSmarts#NIL AND FinishProc#NIL THEN [HowToReport, realReport] _ FinishProc[participant, pSmarts, GetConvState[conv, participant], whatToReport]; IF HowToReport#NIL THEN { packagedReport _ NEW[ReportBody _ [smarts: pSmarts, whatToReport: realReport] ]; IF pSmarts.notifications=NIL THEN pSmarts.notifications _ MBQueue.Create[]; conv.numReportsOut _ conv.numReportsOut + 1; pSmarts.notifications.QueueClientAction[HowToReport, packagedReport]; }; party _ NARROW[Triples.Select[$Poaching, party, -- poachee --]]; IF party#NIL THEN ReportToParty[party, participant]; }; IF ~reportToAll THEN ReportToParty[party, party] ELSE Triples.Foreach[$Party, conv, Triples.Any --party--, ReportToOne]; }; <> FinishEvent: INTERNAL FinishProcType = { <> <<1) reporting to originating smarts: don't copy convEvent, and use report only to do CheckIdle asynchronously with the originating operation.>> <<2) reporting to some other smarts: copy convEvent before filling in self field, request a ReportProgress.>> convEvent: ConvEvent = NARROW[whatToReport]; reportEvent: ConvEvent = IF (convEvent.other.smartsID = Reseal[smarts]) THEN convEvent ELSE NEW[Thrush.ConvEventBody _ convEvent^]; reportEvent.self _ [ Reseal[party], Reseal[smarts], reportEvent.other.convID, convState.state, convState.stateID]; IF convEvent = reportEvent THEN RETURN[ReportCheckIdle, UnsealConv[convEvent.self.convID]] -- no report to initiator ELSE RETURN[ReportProgress, reportEvent]; }; <> ReportProgress: PROC[r: REF] = { <> report: PackagedReport = NARROW[r]; convEvent: ConvEvent = NARROW[report.whatToReport]; { ENABLE RPC.CallFailed => { IF ~report.smarts.failed THEN { VoiceUtils.Problem["Communications to smarts failed", $System]; []_ThParty.Deregister[smartsID: Reseal[report.smarts]]; }; report.smarts.failed_TRUE; CONTINUE; }; IF report.smarts.failed THEN RETURN; report.smarts.interface.clientStubProgress[ interface: report.smarts.interface, shh: report.smarts.shh, convEvent: convEvent]; CheckIdle[UnsealConvE[convEvent.self.convID]]; }; }; ReportCheckIdle: PROC[r: REF] = { <> report: PackagedReport = NARROW[r]; CheckIdle[NARROW[report.whatToReport]]; }; CheckIdle: ENTRY PROC[conv: ConversationData] = { <> conv.numReportsOut _ conv.numReportsOut - 1; IF conv.numIdle = conv.numParties AND conv.numReportsOut<=0 THEN TRUSTED { Process.Detach[FORK IdleAfterAWhile[conv]]; }; }; IdleAfterAWhile: ENTRY PROC[conv: ConversationData] = TRUSTED { <> idler: CONDITION; Process.SetTimeout[@idler, Process.SecondsToTicks[ThNet.pd.postIdleTimeout]]; WAIT idler; DeleteConversation[conv]; }; DeleteConversation: INTERNAL PROC[ conv: ConversationData ] = { Triples.Erase[Triples.Any, conv, Triples.Any]; []_convTable.Delete[LOOPHOLE[conv.convID]]; -- KillConv[conv.convID]; }; NewId: PROC RETURNS [ PupTypes.PupSocketID ] = TRUSTED { idKey: DESFace.Key _ DESFace.GetRandomKey[]; -- connection specs. keys: LONG POINTER TO ARRAY[0..2) OF PupTypes.PupSocketID _ LOOPHOLE[LONG[@idKey]]; RETURN[keys[0]]; }; <> convTable: CardTable.Ref = CardTable.Create[]; UnsealConv: PUBLIC INTERNAL PROC[ convID: ConversationID] <> RETURNS [conv: ConversationData_NIL ] = { RETURN[NARROW[convTable.Fetch[LOOPHOLE[convID]].val]]; }; EnhandleConv: INTERNAL PROC[ conv: ConversationData ] <> RETURNS [ convID: ConversationID_nextConv ] = { nextConv _ BasicTime.Update[nextConv, 1]; IF ~convTable.Store[LOOPHOLE[convID], conv] THEN ERROR; }; ConferenceHost: INTERNAL PROC RETURNS [conferenceHost: Thrush.Machine] = { <> <> inUse: PACKED ARRAY [0..100B) OF BOOL _ ALL[FALSE]; MarkInUse: CardTable.EachPairAction = { conv: ConversationData = NARROW[val]; inUse[conv.conferenceHost.host] _ TRUE; RETURN[FALSE]; }; []_convTable.Pairs[MarkInUse]; FOR hIndex: [0..100B) IN [2..100B) DO IF ~inUse[hIndex] THEN RETURN [[[173B], [hIndex]]]; ENDLOOP; VoiceUtils.Problem["Out of conference hosts!", $System]; RETURN[[[173B],[1]]]; }; <> <<[]_convTable.Delete[LOOPHOLE[convID]]; };>> <<>> UnsealConvE: ENTRY PROC[convID: ConversationID] RETURNS [conv: ConversationData_NIL ] = { RETURN[UnsealConv[convID]]; }; UnsealParty: PUBLIC PROC[partyID: Thrush.PartyID] RETURNS[party: PartyData] = { RETURN[NARROW[RefID.Unseal[partyID]]]; }; UnsealSmarts: PUBLIC PROC[smartsID: Thrush.SmartsID] RETURNS[smarts: SmartsData] = { RETURN[NARROW[RefID.Unseal[smartsID]]]; }; <> <<>> 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; RETURN[keyIndex, new]; }; }. <<>> <> <<>> <<>> <> <> <<>> <> <> <> <> < ID, eliminate conversation class stuff!>> <> <<>> <<>> <> <> <> <<>> <> <> <> <<>> <> <> <> <<>>