<> <> <> <> DIRECTORY BasicTime USING [ Update, Now ], CardTable USING [ Create, Delete, EachPairAction, Fetch, Pairs, Ref, Store ], DESFace USING [ GetRandomKey, Key ], IV USING [ KeyTableBody ], MBQueue USING [ QueueClientAction ], Process USING [ Detach, SecondsToTicks, SetTimeout ], Pup USING [ nullSocket, Socket ], PupName USING [ AddressToRope ], RefID USING [ ID, Reseal, Unseal ], Rope USING [ Equal, ROPE ], RPC USING [ CallFailed, GetConversationID, ShortROPE ], ThNet USING [ pd ], ThParty, ThPartyPrivate USING [ ConversationBody, ConversationData, ConvState, ConvStateBody, DoDescribeParty, GetRnameFromParty, PartyBody, PartyData, SmartsBody, SmartsData ], ThPartyMonitorImpl, Thrush USING [ ActionID, ActionReport, AlertKind, CallUrgency, ConversationID, ConvEvent, ConvEventBody, Credentials, EncryptionKey, epoch, InterfaceSpec, KeyTable, NB, NetAddress, noAddress, 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 [ OwnNetAddress, Problem ] ; ThPartyOpsImpl: CEDAR MONITOR LOCKS root IMPORTS BasicTime, CardTable, DESFace, MBQueue, Process, PupName, RefID, root: ThPartyMonitorImpl, Rope, 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; nullishKey: Thrush.EncryptionKey _ ALL[[b:0, p:1]]; -- Thrush.nullKey, parity-corrected. <> CreateConversation: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, state: Thrush.StateInConv, urgency: Thrush.CallUrgency, alertKind: Thrush.AlertKind, reason: Thrush.Reason, comment: ROPE, 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[IV.KeyTableBody[20B]] ] ]; IF ThNet.pd.nullKeyCorrectParity THEN FOR i: [0..16) IN [0..16) DO conv.keyTable[i] _ ALL[[b: 0, p: 1]]; ENDLOOP; 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, reason: reason, comment: comment]; IF nb # $success THEN DeleteConversation[ conv ]; }; Alert: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, calledPartyID: PartyID, comment: ROPE ] RETURNS [ nb: NB ] = { ENABLE UNWIND => NULL; calledParty: PartyData; nb2: NB; [nb, calledParty] _ AlertOne[credentials, calledPartyID]; IF calledParty = NIL THEN RETURN[nb]; calledParty _ NARROW[Triples.Select[$Visiting, calledParty, --visitee--]]; IF calledParty=NIL THEN RETURN[nb]; [nb2, ] _ AlertOne[credentials, Reseal[calledParty], calledPartyID]; IF nb#$success OR nb2=$success THEN nb_$success; }; AlertOne: PUBLIC INTERNAL PROC[ credentials: Credentials, calledPartyID: PartyID, intendedPartyID: PartyID _ nullID ] RETURNS [ nb: NB, calledParty: PartyData_NIL ] = { <> ENABLE UNWIND => NULL; convState: ConvState; conv: ConversationData; callingParty: PartyData; partyID: PartyID = credentials.partyID; [, callingParty,, nb] _ Verify[credentials]; IF nb # $success THEN RETURN; credentials.partyID _ calledPartyID; [conv, calledParty, convState, nb] _ Verify[credentials]; IF calledPartyID=partyID THEN RETURN[nb: $narcissism]; SELECT nb FROM $notInConv => NULL; $success => IF conv#NIL THEN RETURN[$convStillActive]; -- already in the conversation! $noSuchParty => RETURN[$noSuchParty2]; ENDCASE=> RETURN; <<credentials.smartsID remains the originator's  this produces a strange value for convEvent.situation.other.smartsID; at present, no one uses that  if they do, they'll have to understand.>> credentials.stateID _ 0; IF calledParty=NIL OR (calledParty.type = $trunk AND calledParty.reservedBy # Reseal[callingParty]) THEN RETURN[nb: $noSuchParty2]; nb _ DoAdvance[credentials: credentials, intendedPartyID: intendedPartyID, 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, intendedPartyID: PartyID _ nullID, 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]; IF intendedPartyID#nullID THEN IF convState.intendedPartyID # nullID THEN ERROR -- Why does this case arise? ELSE convState.intendedPartyID _ intendedPartyID; -- See ThPartyPrivate.ConvState 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]; name: Rope.ROPE; intendedPartyID: 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; }; name _ IF nameReq=$none THEN NIL ELSE ThPartyPrivate.DoDescribeParty[party, nameReq]; intendedPartyID _ convState.intendedPartyID; pInfo[index] _ [ partyID: Reseal[party], name: name, intendedPartyID: intendedPartyID, intendedName: IF nameReq=$none THEN NIL ELSE IF intendedPartyID = nullID THEN name ELSE ThPartyPrivate.DoDescribeParty[UnsealParty[intendedPartyID], nameReq], type: party.type, state: convState.state, <> <> numConvs: party.numConvs, enabled: party.enabled, partyActive: party.partyActive, socket: smarts.properties.netAddress ]; }; 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: Thrush.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, reportNewKeys: BOOL] -- note: reportNewKeys is no longer used RETURNS [ nb: NB, keyIndex: [0..17B] _ 0 ] = TRUSTED { ENABLE UNWIND => NULL; conv: ConversationData; ekResults: EKResults_$duplicate; [conv, , , nb] _ Verify[credentials]; <> SELECT nb FROM $success, $stateMismatch => NULL; ENDCASE => RETURN; [keyIndex, ekResults] _ EnterKey[conv, key]; nb _ IF ekResults#$new THEN $success ELSE $newKeys; }; UnregisterKey: PUBLIC ENTRY PROC[ shh: SHHH _ none, credentials: Credentials, key: Thrush.EncryptionKey ] RETURNS [ nb: NB _ $success ] = { conv: ConversationData; [conv, , , nb] _ Verify[credentials]; SELECT nb FROM $success, $stateMismatch => NULL; ENDCASE => RETURN; RemoveKey[conv, key]; }; RegisterServiceInterface: PUBLIC ENTRY PROC[ shhh: SHHH _ none, credentials: Thrush.Credentials, interfaceSpecPattern: Thrush.InterfaceSpec ] RETURNS[nb: NB, interfaceSpec: Thrush.InterfaceSpec] = { interface: REF Thrush.InterfaceSpec; instance: RPC.ShortROPE_NIL; hostHint: Thrush.NetAddress; party: PartyData; type: RPC.ShortROPE = interfaceSpecPattern.interfaceName.type; [party: party, nb: nb] _ Verify[credentials]; SELECT nb FROM $success, $stateMismatch, $noSuchConv => NULL; $noSuchParty => IF party = NIL THEN RETURN ; -- else just not yet enabled ENDCASE => RETURN; nb _ $success; interface _ FindServiceInterface[party, type]; IF interface = NIL THEN { interface _ NEW[Thrush.InterfaceSpec _ [interfaceName: [type: type], serviceID: interfaceSpecPattern.serviceID]]; party.actionInterfaces _ CONS[interface, party.actionInterfaces]; }; <> IF interfaceSpecPattern.hostHint # Thrush.noAddress THEN hostHint _ interfaceSpecPattern.hostHint ELSE IF shhh=NIL THEN hostHint _ VoiceUtils.OwnNetAddress[] ELSE { na: Thrush.NetAddress = LOOPHOLE[RPC.GetConversationID[shhh]]; hostHint _ [na.net, na.host, Pup.nullSocket]; }; interface.hostHint _ hostHint; instance _ interfaceSpecPattern.interfaceName.instance; IF instance = NIL THEN { hostHint.socket _ NewId[]; instance _ PupName.AddressToRope[hostHint]; }; interface.interfaceName.instance _ instance; interface.interfaceName.version _ interfaceSpecPattern.interfaceName.version; interface.serviceID _ interfaceSpecPattern.serviceID; interfaceSpec _ interface^; }; LookupServiceInterface: PUBLIC ENTRY PROC[ shhh: SHHH _ none, credentials: Thrush.Credentials, serviceParty: PartyID, type: RPC.ShortROPE ] RETURNS[nb: NB _ $success, interfaceSpec: Thrush.InterfaceSpec] = { interface: REF Thrush.InterfaceSpec; party: PartyData; [nb: nb] _ Verify[credentials]; SELECT nb FROM $success, $stateMismatch => NULL; ENDCASE => RETURN; party _ UnsealParty[serviceParty]; IF party=NIL THEN { nb _ $noSuchParty2; RETURN; }; interface _ FindServiceInterface[party, type]; IF interface=NIL THEN { party _ NARROW[Triples.Select[$Poaching, party, NIL]]; IF party#NIL THEN interface _ FindServiceInterface[party, type]; IF interface = NIL THEN { nb _ $noSuchInterface; RETURN; }; }; nb _ $success; interfaceSpec _ interface^; }; <> Verify: 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: PUBLIC 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 _ []]; 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: PUBLIC 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] ]; 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; RETRY; }; IF ~report.smarts.failed THEN 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: PUBLIC PROC RETURNS [ Pup.Socket ] = TRUSTED { idKey: DESFace.Key _ DESFace.GetRandomKey[]; -- connection specs. keys: LONG POINTER TO ARRAY[0..2) OF Pup.Socket _ LOOPHOLE[LONG[@idKey]]; RETURN[keys[0]]; }; FindServiceInterface: INTERNAL PROC[ party: PartyData, type: RPC.ShortROPE ] RETURNS[interface: REF Thrush.InterfaceSpec_NIL] = { FOR iF: LIST OF REF Thrush.InterfaceSpec _ party.actionInterfaces, iF.rest WHILE iF#NIL DO IF type.Equal[iF.first.interfaceName.type, FALSE] THEN RETURN[iF.first]; ENDLOOP; }; <> <<>> SubstReport: TYPE = REF SubstReportRec; SubstReportRec: TYPE = RECORD [ convEvent: Thrush.ConvEvent, oldPartyID: PartyID, newPartyID: PartyID ]; MakeSubstitution: PUBLIC INTERNAL PROC[oldParty: PartyData, newParty: PartyData] = { <> SpliceNewPartyIntoConv: INTERNAL Triples.ForeachProc -- [trip: TripleRec] -- RETURNS [continue: BOOLEAN _ TRUE] -- ={ -- copy attributes Triples.Make[trip.att, trip.obj, newParty]; -- newParty # oldParty asserted. Triples.Erase[trip.att, trip.obj, trip.val]; -- Make before break, although it doesn't matter. }; SubInOneConv: INTERNAL Triples.ForeachProc -- [trip: TripleRec] -- RETURNS [continue: BOOLEAN _ TRUE] -- ={ conv: ThPartyPrivate.ConversationData = NARROW[trip.obj]; convEvent: ConvEvent; convState: ThPartyPrivate.ConvState = GetConvState[conv, oldParty, FALSE]; IF convState=NIL THEN ERROR; <<Here we actually move to newParty all the relations between the conversation and the oldParty. This feels too drastic to work, but we'll try it. When newParty is the poacher, splice only occurs if the owner rName of oldParty and newParty are the same. Otherwise, the poaching relationship is OK, but it doesn't affect ongoing conversations. >> IF newParty.type#$telephone AND ThPartyPrivate.GetRnameFromParty[oldParty, $owner].rAtom # ThPartyPrivate.GetRnameFromParty[newParty, $owner].rAtom THEN RETURN; newParty.partyActive _ oldParty.partyActive; oldParty.partyActive _ FALSE; IF convState.state#idle THEN { <> newParty.numConvs _ newParty.numConvs+1; oldParty.numConvs _ oldParty.numConvs-1; }; Triples.Foreach[Triples.Any, conv, oldParty, SpliceNewPartyIntoConv]; <> convEvent _ NEW[Thrush.ConvEventBody _ [ self: [], other: [ partyID: Reseal[newParty], smartsID: Reseal[Triples.Select[$Smarts, newParty, Triples.Any]], convID: conv.convID, state: convState.state, stateID: convState.stateID _ convState.stateID + 1 -- Clock must tick! ], time: conv.timeOfID _ convState.time _ BasicTime.Now[], reason: $substituting ]]; Report[ FinishProc: FinishSubstitution, reportToAll: TRUE, conv: conv, party: NIL, whatToReport: NEW[SubstReportRec_[ convEvent: convEvent, oldPartyID: Reseal[oldParty], newPartyID: Reseal[newParty]]] ]; }; IF oldParty=NIL OR newParty=NIL OR oldParty=newParty THEN RETURN; Triples.Foreach[$Party, Triples.Any, oldParty, SubInOneConv]; }; FinishSubstitution: INTERNAL FinishProcType = { <> <> <> substReport: SubstReport _ NARROW[whatToReport]; HowToReport _ ReportSubstitution; substReport _ NEW[SubstReportRec _ substReport^]; whatToReallyReport _ substReport; substReport.convEvent _ NEW[Thrush.ConvEventBody _ substReport.convEvent^]; substReport.convEvent.self _ [ Reseal[party], Reseal[smarts], substReport.convEvent.other.convID, convState.state, convState.stateID]; }; ReportSubstitution: PROC[r: REF] = { <> report: PackagedReport = NARROW[r]; substReport: SubstReport = 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; RETRY; }; IF ~report.smarts.failed THEN report.smarts.interface.clientStubSubstitution[ interface: report.smarts.interface, shh: report.smarts.shh, convEvent: substReport.convEvent, oldPartyID: substReport.oldPartyID, newPartyID: substReport.newPartyID]; CheckIdle[UnsealConvE[substReport.convEvent.self.convID]]; <> }; }; <> AReport: TYPE = REF AReportRec; AReportRec: TYPE = RECORD [ report: Thrush.ActionReport, numReportsOut: INTEGER_0, -- for selfOnCompletion feature finalReport: AReport_NIL, -- for selfOnCompletion feature reportToAll: BOOL, selfOnCompletion: BOOL ]; ReportAction: PUBLIC ENTRY 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] = { ENABLE UNWIND => NULL; [nb, numReportsIssued] _ ReportActionInt[report, reportToAll, selfOnCompletion]; }; ReportActionInt: INTERNAL PROC[ report: Thrush.ActionReport, -- includes credentials (in other field) identifying the service party. reportToAll: BOOL _ FALSE, selfOnCompletion: BOOL _ FALSE ] RETURNS [nb: NB, numReportsIssued: NAT_0] = { rReport: AReport; conv: ConversationData; [nb: nb, conv: conv] _ Verify[report.other]; IF nb # $success THEN RETURN; -- Reports are not that important. rReport _ NEW[AReportRec _ [report, 0, NIL, reportToAll, selfOnCompletion]]; rReport.finalReport _ rReport; -- for selfOnCompletion feature. Report[FinishProc: FinishReportAction, reportToAll: TRUE, conv: conv, party: NIL, whatToReport: rReport]; <> numReportsIssued _ rReport.numReportsOut; }; FinishReportAction: INTERNAL FinishProcType = { <> <<1) reporting to caller of ThParty.ReportAction. Don't report (at least wait, if selfOnCompletion).>> <<2) reporting to the originator of the service request, as defined by the action report, and this party isn't the originator. Don't report.>> <<3 reporting to all or reporting to the originator of the service request, and this is it. Generate a report.>> aReport: AReport _ NARROW[whatToReport]; aReportCopy: AReport _ aReport; IF aReport.report.other.smartsID = Reseal[smarts] THEN IF aReport.selfOnCompletion THEN NULL ELSE RETURN -- requestingParty ELSE IF ~aReport.reportToAll AND Reseal[party] # aReport.report.requestingParty THEN RETURN -- no report to non-requesting party. ELSE aReportCopy _ NEW[AReportRec _ aReport^]; -- must be a copy, because . . . aReportCopy.report.self _ [ Reseal[party], Reseal[smarts], aReportCopy.report.other.convID, convState.state, convState.stateID]; IF aReportCopy = aReport THEN RETURN; -- Will be used indirectly through finalReport link of other reports if selfOnCompletion aReport.numReportsOut _ aReport.numReportsOut+1; -- aReport = aReportCopy.finalReport RETURN[DoReportAction, aReportCopy]; }; DoReportAction: PROC[r: REF] = { <> report: PackagedReport = NARROW[r]; aReport: AReport _ NARROW[report.whatToReport]; finalReport: AReport _ aReport.finalReport; { 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; RETRY; }; conv: ConversationData _ UnsealConvE[aReport.report.self.convID]; IF report.smarts.failed THEN { CheckIdle[conv]; RETURN; }; report.smarts.interface.clientStubReportAction[ interface: report.smarts.interface, shh: report.smarts.shh, report: aReport.report]; IF aReport.selfOnCompletion THEN { FinalReport: ENTRY PROC= { <> ENABLE UNWIND => NULL; pSmarts: SmartsData; finalReport.numReportsOut _ finalReport.numReportsOut-1; IF finalReport.numReportsOut#0 THEN RETURN; pSmarts _ UnsealSmarts[finalReport.report.self.smartsID]; conv.numReportsOut _ conv.numReportsOut + 1; report.smarts _ pSmarts; report.whatToReport _ finalReport; pSmarts.notifications.QueueClientAction[DoReportAction, report]; }; FinalReport[]; }; CheckIdle[conv]; }; }; <> 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.NetAddress] = { <> <> 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], Pup.nullSocket]]; ENDLOOP; VoiceUtils.Problem["Out of conference hosts!", $System]; RETURN[[[173B],[1], Pup.nullSocket]]; }; <> <<[]_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] = { <> <<(Key-suppliers can improve things by calling UnregisterKey when done with them.)>> keyIndex_0; IF key=Thrush.nullKey OR key=nullishKey THEN RETURN[0, $duplicate]; FOR kI: [0..17B] IN [1..17B] DO IF conv.keyTable[kI]=key THEN { conv.keyUses[kI] _ conv.keyUses[kI] + 1; RETURN[kI, $duplicate]; } ELSE IF conv.keyTable[kI][0].b=0 AND (conv.keyTable[kI] = Thrush.nullKey OR conv.keyTable[kI] = nullishKey) AND keyIndex=0 THEN keyIndex_kI; ENDLOOP; IF keyIndex=0 THEN RETURN [0, $full]; conv.keyTable[keyIndex] _ key; conv.keyUses[keyIndex] _ 1; RETURN[keyIndex, $new]; }; RemoveKey: INTERNAL PROC[conv: ConversationData, key: Thrush.EncryptionKey] = { <> <> IF key=Thrush.nullKey OR key=nullishKey THEN RETURN; FOR keyIndex: [0..20B) IN [1..20B) DO IF conv.keyTable[keyIndex]#key THEN LOOP; conv.keyUses[keyIndex] _ conv.keyUses[keyIndex] - 1; IF conv.keyUses[keyIndex] < 0 THEN ERROR; IF conv.keyUses[keyIndex] = 0 THEN conv.keyTable[keyIndex] _ IF ThNet.pd.nullKeyCorrectParity THEN nullishKey ELSE Thrush.nullKey; EXIT; ENDLOOP; }; }. <<>> <> <<>> <<>> <> <> <<>> <> <> <> <> < ID, eliminate conversation class stuff!>> <> <<>> <<>> <> <> <> <<>> <> <> <> <<>> <> <> <> <> <> <> <> <> <> <> <> <<>>