DIRECTORY BasicTime USING [ Update, Now ], CardTab USING [ Create, Delete, EachPairAction, Fetch, Pairs, Ref, Store ], DESFace USING [ GetRandomKey, Key ], IV USING [ KeyTableBody ], MBQueue USING [ QueueClientAction ], Process USING [ Detach, Pause, 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, PollSpec, PollSpecBody, SetPoaching, 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 ], ThSmarts USING [ noneScheduled ], ThSmartsRpcControl USING [ InterfaceRecord ], Triples USING [ Any, Erase, Foreach, ForeachProc, Is, Make, Select ], TU USING [ MakeUnique ], VoiceUtils USING [ OwnNetAddress, Problem ] ; ThPartyOpsImpl: CEDAR MONITOR LOCKS root IMPORTS BasicTime, CardTab, 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, checkConflict: BOOL ] RETURNS [ nb: NB _ $success, convEvent: ConvEvent_NIL ] = { 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, checkConflict: checkConflict]; 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; visitedParty: PartyData; nb2: NB _ $wuddYaTalkinAbout; calledParty _ UnsealParty[calledPartyID]; visitedParty _ IF calledParty=NIL THEN NIL ELSE NARROW[Triples.Select[$Visiting, calledParty, --visitee--]]; IF visitedParty#NIL THEN nb2 _ AlertOne[credentials, Reseal[visitedParty], calledPartyID]; nb _ AlertOne[credentials, calledPartyID]; IF nb#$success AND nb2=$success THEN nb_$success; }; AlertOne: PUBLIC INTERNAL PROC[ credentials: Credentials, calledPartyID: PartyID, intendedPartyID: PartyID _ nullID ] RETURNS [ nb: NB ] = { ENABLE UNWIND => NULL; convState: ConvState; conv: ConversationData; callingParty: PartyData; calledParty: PartyData; partyID: PartyID = credentials.partyID; [, callingParty,, nb] _ Verify[credentials]; IF nb # $success THEN RETURN; credentials.partyID _ calledPartyID; [conv, calledParty, convState, nb] _ Verify[credentials]; SELECT nb FROM $notInConv, $success => NULL; $success => IF conv#NIL THEN RETURN[$alreadyInConv]; -- already in the conversation! $noSuchParty => RETURN[$noSuchParty2]; ENDCASE=> RETURN; ThPartyPrivate.SetPoaching[calledParty, callingParty]; IF VoiceParty[calledParty] = VoiceParty[UnsealParty[partyID]] THEN RETURN[$narcissism]; IF nb = $success AND conv#NIL THEN RETURN[$alreadyInConv]; 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, checkConflict: FALSE].nb; }; Advance: PUBLIC ENTRY PROC[ shhh: SHHH, credentials: Credentials, state: Thrush.StateInConv, reportToAll: BOOL, reason: Thrush.Reason, comment: ROPE, bilateral: BOOL, checkConflict: BOOL _ FALSE ] RETURNS [nb: NB, convEvent: ConvEvent] = { ENABLE UNWIND => NULL; [nb, convEvent] _ DoAdvance[ credentials: credentials, state: state, reportToAll: reportToAll, reason: reason, comment: comment, newInConv: FALSE, bilateral: bilateral, checkConflict: checkConflict ]; }; 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 bilateral: BOOL_FALSE, checkConflict: BOOL _ FALSE ] RETURNS [nb: NB_$success, convEvent: ConvEvent_NIL] = { convState: ConvState; conv: ConversationData; party: PartyData; IF state = $idle THEN checkConflict _ FALSE; -- force to unengaged state on idle! [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; nb _ EngageParty[party:party, convID:conv.convID, doEngage:checkConflict, test:TRUE]; IF nb # $success THEN { IF convState.state=$neverWas THEN party.numConvs _ party.numConvs-1; RETURN[nb: nb]; }; 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, $interfaceError => credentials.state _ convState.state; -- Don't change state, but return current one as report. ENDCASE => RETURN; IF nb=$success AND state = $active AND ~party.partyActive THEN { voiceP: PartyData; voiceS: SmartsData; IF (voiceP_VoiceParty[party])=NIL OR (voiceS _ NARROW[Triples.Select[$Smarts, voiceP, NIL]])=NIL OR voiceS.properties.role#$voiceTerminal THEN RETURN[nb: $voiceTerminalUnavailable]; IF IsBilateralConv[conv] AND conv.cStateBilateral#convState THEN RETURN[nb: $bilateralConv]; IF bilateral THEN { IF conv.numActive>1 THEN RETURN[nb: $conferenceConv]; conv.cStateBilateral _ convState; }; }; IF nb=$success THEN [] _ EngageParty[party:party, convID:conv.convID, doEngage:checkConflict, test:FALSE]; convEvent _ PostConvEvent[ credentials: credentials, convState: convState, reason: reason, comment: comment, reportToAll: reportToAll]; }; IsBilateralConv: INTERNAL PROC[conv: ConversationData] RETURNS [is: BOOL] = { cStateB: ConvState _ conv.cStateBilateral; RETURN[cStateB#NIL AND cStateB.state=$active AND Triples.Select[cStateB, conv, NIL]#NIL AND conv.numActive>1]; }; GetConversationInfo: PUBLIC ENTRY PROC [ shh: SHHH_none, convID: ConversationID ] RETURNS [ nb: NB_$success, cInfo: ThParty.ConversationInfo_[] ] = { [nb, cInfo] _ GetConversationInfoInt[UnsealConv[convID]]; }; GetConversationInfoInt: INTERNAL PROC[conv: ConversationData ] RETURNS [ nb: NB_$success, cInfo: ThParty.ConversationInfo_[] ] = { 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.bilateralConv _ IsBilateralConv[conv]; 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; intendedName: Rope.ROPE; intendedPartyID: PartyID; activeIndex: NAT_0; -- when incremented, the index of the next active party description inactiveIndex: NAT; -- when incremented, the index of the next non-active party description GetParties: INTERNAL FinishProcType = { index: NAT; HowToReport _ NIL; IF Triples.Select[$Poaching, party, -- poachee party -- ]#NIL AND smarts.properties.role#$voiceTerminal THEN RETURN; -- Wait for voice terminal index _ SELECT TRUE FROM party=ownParty => 0, convState.state=$active => (activeIndex_activeIndex+1), ENDCASE => (inactiveIndex_inactiveIndex+1); pInfo.numParties _ MAX[index+1, pInfo.numParties]; 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 IF (intendedName _ThPartyPrivate.DoDescribeParty[UnsealParty[intendedPartyID], nameReq])#NIL THEN intendedName ELSE name, type: party.type, state: convState.state, numConvs: party.numConvs, enabled: party.enabled, partyActive: party.partyActive, voicePath: smarts.properties.role=$voiceTerminal, partyEngaged: party.partyEngaged#nullConvID, -- if anyone needs it. 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; inactiveIndex _ conv.numActive-(IF convState.state=$active THEN 1 ELSE 0); pInfo _ NEW[ThParty.PartyInfoSeq[IF allParties THEN conv.numParties ELSE 1]]; pInfo.conversationInfo _ GetConversationInfoInt[conv].cInfo; 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 conv#NIL THEN keyTable _ conv.keyTable; -- always answer the question if possible IF nb=$stateMismatch THEN nb_$success; }; 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 => nb _ $success; 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]; IF nb = $noSuchParty AND party = NIL THEN RETURN; -- else just not yet enabled nb _ $success; interface _ FindServiceInterface[party, type]; IF interface = NIL THEN { interface _ NEW[Thrush.InterfaceSpec _ [interfaceName: [type: type], serviceID: interfaceSpecPattern.serviceID, interfaceID: interfaceSpecPattern.interfaceID]]; 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; 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; }; }; interfaceSpec _ interface^; }; CheckIn: PUBLIC ENTRY PROC[shh: SHHH_none, credentials: ThParty.Credentials] RETURNS [nb: NB] = { credentials.convID _ nullConvID; -- Not concerned with conversation nb _ Verify[credentials, FALSE].nb; }; 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; }; 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 = { 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 = { 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] = { IF conv=NIL THEN RETURN; -- Nothing can be done. 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; }; VoiceParty: PUBLIC INTERNAL PROC[party: PartyData] RETURNS [voiceParty: PartyData ] = { IF party=NIL THEN RETURN; IF party.type#$individual THEN RETURN[party]; voiceParty _ NARROW[Triples.Select[$Poaching, party, -- poachee --]]; IF voiceParty = NIL THEN voiceParty _ party; }; EngageParty: PUBLIC INTERNAL PROC[ party: PartyData, convID: ConversationID, doEngage: BOOL, test: BOOL_FALSE] RETURNS [nb: NB_$success] = { vParty: PartyData _ VoiceParty[party]; newEngagement: ConversationID; IF vParty=NIL THEN RETURN; newEngagement _ vParty.partyEngaged; SELECT doEngage FROM FALSE => IF newEngagement = convID THEN newEngagement _ nullConvID; TRUE => { otherParty: PartyData _ NARROW[Triples.Select[$Other, vParty, NIL]]; IF otherParty=NIL THEN otherParty _ NARROW[Triples.Select[$Other, NIL, vParty]]; IF otherParty#NIL AND otherParty.partyEngaged#nullConvID THEN { conv: ConversationData _ UnsealConv[convID]; oPartyPoacher: PartyData _ NARROW[Triples.Select[$Poaching, NIL, otherParty]]; IF conv=NIL THEN RETURN[$voiceTerminalBusy]; -- Best we can do. IF ~(Triples.Is[$Party, conv, otherParty] OR (oPartyPoacher#NIL AND Triples.Is[$Party, conv, oPartyPoacher])) THEN RETURN[$voiceTerminalBusy]; -- There's a true front/back conflict }; SELECT newEngagement FROM convID, nullConvID => newEngagement _ convID; ENDCASE => RETURN[$voiceTerminalBusy]; }; ENDCASE => ERROR; IF test THEN RETURN; party _ NARROW[Triples.Select[$Poaching, NIL, vParty]]; vParty.partyEngaged _ newEngagement; IF party#NIL THEN party.partyEngaged _ newEngagement; }; 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]; whatToReport: SubstReport; IF convState=NIL THEN ERROR; 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 ]]; whatToReport _ NEW[SubstReportRec_[ convEvent: convEvent, oldPartyID: Reseal[oldParty], newPartyID: Reseal[newParty]]]; IF newParty.type=$individual AND ThPartyPrivate.GetRnameFromParty[oldParty, $owner].rAtom # ThPartyPrivate.GetRnameFromParty[newParty, $owner].rAtom THEN RETURN; IF oldParty.type=$individual THEN Report[ FinishProc: FinishSubstitution, reportToAll: FALSE, conv: conv, party: oldParty, whatToReport: whatToReport ]; newParty.partyActive _ oldParty.partyActive; newParty.partyEngaged _ oldParty.partyEngaged; oldParty.partyActive _ FALSE; oldParty.partyEngaged _ nullConvID; IF convState.state#idle THEN { newParty.numConvs _ newParty.numConvs+1; oldParty.numConvs _ oldParty.numConvs-1; }; Triples.Foreach[Triples.Any, conv, oldParty, SpliceNewPartyIntoConv]; Report[ FinishProc: FinishSubstitution, reportToAll: TRUE, conv: conv, party: NIL, whatToReport: whatToReport ]; }; 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; }; conv: ThPartyPrivate.ConversationData _ UnsealConvE[substReport.convEvent.self.convID]; IF conv=NIL THEN RETURN; -- has effervesced since report was scheduled. IF ~report.smarts.failed AND conv#NIL THEN report.smarts.interface.clientStubSubstitution[ interface: report.smarts.interface, shh: report.smarts.shh, convEvent: substReport.convEvent, oldPartyID: substReport.oldPartyID, newPartyID: substReport.newPartyID]; CheckIdle[conv]; }; }; PartyPoller: PROC = { Pause: PROC[seconds: INT] _ { Process.Pause[Process.SecondsToTicks[seconds]]; }; numPartiesInPrevPass: INT_0; DO nextScheduledCheck: INT_IF numPartiesInPrevPass=0 THEN ThSmarts.noneScheduled ELSE numPartiesInPrevPass*ThNet.pd.pollPartiesInterval*2; numPartiesInThisPass: INT_0; PollOneParty: Triples.ForeachProc = { party: PartyData _ NARROW[trip.val]; smarts: SmartsData _ NARROW[Triples.Select[$Smarts, party, NIL]]; IF ~ThNet.pd.pollParties THEN RETURN[FALSE]; -- quit! IF smarts=NIL OR smarts.failed OR ~party.enabled THEN RETURN; numPartiesInThisPass_numPartiesInThisPass+1; smarts.notifications.QueueClientAction[DoPoll, NEW[ThPartyPrivate.PollSpecBody_[smarts, Reseal[party], $hello, nextScheduledCheck]]]; Pause[ThNet.pd.pollPartiesInterval]; }; IF ThNet.pd.pollParties THEN FOR types: LIST OF ATOM _ ThNet.pd.pollPartyTypes, types.rest WHILE types#NIL DO [] _ Triples.Foreach[types.first, NIL, NIL, PollOneParty]; ENDLOOP; Pause[ThNet.pd.pollPartiesInterval]; numPartiesInPrevPass _ numPartiesInThisPass; ENDLOOP; }; DoPoll: PUBLIC PROC[r: REF] = { pollSpec: ThPartyPrivate.PollSpec _ NARROW[r]; smarts: SmartsData _ pollSpec.smarts; { ENABLE RPC.CallFailed => { IF ~smarts.failed THEN { VoiceUtils.Problem["Communications to smarts failed", $System]; []_ThParty.Deregister[smartsID: Reseal[smarts]]; }; smarts.failed_TRUE; CONTINUE; }; IF smarts.failed THEN RETURN; smarts.interface.clientStubCheckIn[ interface: smarts.interface, shh: smarts.shh, credentials: [partyID: pollSpec.partyID, smartsID: Reseal[smarts]], reason: pollSpec.reason, nextScheduledCheck: pollSpec.nextScheduledCheck ]; }; }; 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 = { 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: CardTab.Ref = CardTab.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: CardTab.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]]; }; 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] = { 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; }; TRUSTED { Process.Detach[FORK PartyPoller[]]; ThNet.pd.pollPartyTypes _ LIST[$telephone, $individual]; -- To begin with }; }. 6zThPartyOpsImpl.mesa Copyright Σ 1985, 1986, 1987 by Xerox Corporation. All rights reserved. Last modified by D. Swinehart, July 8, 1987 1:13:36 pm PDT Doug Terry, October 28, 1986 2:23:55 pm PST Copies Conversation ID stuff Implementation of ThParty This conv's key, will be key index 1. CreateConversation.nb~$success CreateConversation.nb~$voiceTerminalBusy -- checkConflict only CreateConversation.Verify.nb~~$noSuchSmarts CreateConversation.Verify.nb~~$noSuchParty CreateConversation.nb~$ Alert.nb~$success Alert.nb~$narcissism Alert.nb~$alreadyInConv Alert.nb~$noSuchParty2 Alert.Verify.nb~~$noSuchSmarts Alert.Verify.nb~~$noSuchParty Alert.Verify.nb~~$noSuchConv Alert.Verify.nb~~$notInConv Alert.Verify.nb~~$stateMismatch Alert.Verify.nb~~$interfaceError Alert.DoAdvance.nb~~$convIdle Alert.nb~$ Alert alerts a party and whoever it's visiting. This routine is responsible for alerting just one party. It's used by Alert and by the Visit function Be sure $individual parties are associated with the right voice terminals. It is not permitted to call yourself. This is the only check in the system for this case. Already in the conversation! 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. AlertOne.nb~$success AlertOne.nb~$narcissism AlertOne.nb~$alreadyInConv AlertOne.nb~$noSuchParty2 AlertOne.Verify.nb~~$noSuchSmarts AlertOne.Verify.nb~~$noSuchParty AlertOne.Verify.nb~~$noSuchConv AlertOne.Verify.nb~~$notInConv AlertOne.Verify.nb~~$stateMismatch AlertOne.Verify.nb~~$interfaceError AlertOne.DoAdvance.nb~~$convIdle AlertOne.nb~$ Advance.nb~$success Advance.nb~$convIdle Advance.nb~$bilateralConv -- $active only Advance.nb~$conferenceConv -- $active only Advance.nb~$voiceTerminalUnavailable -- $active only Advance.nb~$voiceTerminalBusy -- hardware-requiring states only Advance.Verify.nb~~$noSuchSmarts Advance.Verify.nb~~$noSuchParty Advance.Verify.nb~~$noSuchConv Advance.Verify.nb~~$notInConv Advance.Verify.nb~~$stateMismatch Advance.Verify.nb~~$interfaceError It is asserted that this happens only from CreateConversation, which will subsequently delete the conversation and all its other connections; the only value that will not thereby vanish is party.numConvs, which we deal with here. A party is not allowed to be $active unless it can support a voice connection. Some parties, due to hardware limitations, cannot participate in conference calls. They must assert bilateral as they attempt to enter the active state. Here we check all that, and deal with it. DoAdvance.nb~$success DoAdvance.nb~$convIdle DoAdvance.nb~$bilateralConv -- $active only DoAdvance.nb~$conferenceConv -- $active only DoAdvance.nb~$voiceTerminalUnavailable -- $active only DoAdvance.nb~$voiceTerminalBusy -- hardware-requiring states only DoAdvance.Verify.nb~~$noSuchSmarts DoAdvance.Verify.nb~~$noSuchParty DoAdvance.Verify.nb~~$noSuchConv DoAdvance.Verify.nb~~$notInConv DoAdvance.Verify.nb~~$stateMismatch DoAdvance.Verify.nb~~$interfaceError GetConversationInfo.nb~$success GetConversationInfo.nb~$noSuchConv GetConversationInfoInt.nb~$success GetConversationInfoInt.nb~$noSuchConv Computes the socket values (and corresponding party/smarts ident) for each voice terminal participant in a conversation, when invoked by Report. A party that does not have an associated voice terminal will not be allowed to enter the Active state. GetPartyInfo.nb~$success GetPartyInfo.nb~$noSuchConv GetPartyInfo.nb~$noSuchParty GetPartyInfo.nb~$notInConv GetKeyTable.nb~$success GetKeyTable.Verify.nb~~$noSuchSmarts GetKeyTable.Verify.nb~~$noSuchParty GetKeyTable.Verify.nb~~$noSuchConv GetKeyTable.Verify.nb~~$notInConv GetKeyTable.Verify.nb~~$interfaceError Don't care about stateMismatch here (maps to $success). RegisterKey.nb~$success RegisterKey.nb~$newKeys RegisterKey.Verify.nb~~$noSuchSmarts RegisterKey.Verify.nb~~$noSuchParty RegisterKey.Verify.nb~~$noSuchConv RegisterKey.Verify.nb~~$notInConv RegisterKey.Verify.nb~~$interfaceError UnregisterKey.nb~$success RegisterKey.Verify.nb~~$noSuchSmarts RegisterKey.Verify.nb~~$noSuchParty RegisterKey.Verify.nb~~$noSuchConv RegisterKey.Verify.nb~~$notInConv RegisterKey.Verify.nb~~$interfaceError Produce hostHint value RegisterServiceInterface.nb~$success Verify.nb~$noSuchParty [nb: nb] _ Verify[credentials]; -- There's no strong reason for checking credentials. SELECT nb FROM $success, $stateMismatch => NULL; ENDCASE => RETURN; LookupServiceInterface.nb~$success LookupServiceInterface.nb~$noSuchParty2 LookupServiceInterface.nb~$noSuchInterface CheckIn.nb~$success LookupServiceInterface.nb~~$noSuchParty LookupServiceInterface.nb~~$noSuchSmarts Utilities Validate parameters: existence, in conversation, state match; produce dehandled values. Validation for smarts is only partial; verifies that it's a smarts. This is dangerous. If caller inadvertently omitted convID, kablooey! Not interested in any conversation, or it doesn't exist IF newInConv is TRUE and convState already existed, it's a logical error, but we're not checking it. Verify.nb~$success Verify.nb~$noSuchSmarts Verify.nb~$noSuchParty Verify.nb~$noSuchConv Verify.nb~$notInConv Verify.nb~$stateMismatch Verify.nb~$interfaceError Prev. line is for protection: you're not supposed to leave idle state, or enter it twice, but this will keep the counts correct if it happens. party.numConvs becomes 0 once a party goes idle. Report Progress Generic reporting routine, synchronous with ThPartyOps procedure, and protected by its monitor. Depending on reportToAll, queues up the report to be distributed to the smarts' for all parties in the conversation (TRUE), or just to the requesting party (FALSE). Reports are made to the indicated parties and their poachees. $visiting will be dealt with later. Having identified a party and smarts to which to report, calls FinishProc to complete the report (to indicate in the report the addressee of the report, for instance); FinishProc may veto the report by returning FALSE. [trip: Triples.TripleRec] RETURNS [continue: BOOLEAN _ TRUE] party is the one we're reporting to; it might be a poachee of the participant. participant is the party that's actually a member of the conversation. Called as report is being prepared to fill in addressee information Two cases: 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. Called to issue state-change progress report Specific reporting routine, dispatched from a smarts notification queue, for reporting party state changes. If all parties are idle in conversation, conversation can go away. If all parties are idle in conversation, conversation can go away. If all parties are idle in conversation, conversation can go away. Find the associated party that actually carries the voice connection. If none, return party. So it doesn't guarantee that there really can be a voice connection. Now test for direct conflict. EngageParty.nb~$success EngageParty.nb~$voiceTerminalBusy Substitution of one party for another in a conversation If both parties exist, either a poacher is arriving or leaving. In either case, the oldParty might be involved in conversations that the new party should inherit. Arrange for that here. 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. When oldParty is the poacher, issue a report to it explicitly, so it can record the change. 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.  Otherwise, conv is dead for this party, but make the splice anyway, since non-dead parties still have to be able to understand what's what. Prepare and issue a report to all parties (report will go to the new, not the old, party.) PROC[party: PartyData, smarts: SmartsData, convState: ConvState, whatToReport: REF] RETURNS [HowToReport: PROC[r: REF]_NIL, whatToReallyReport: REF_NIL]; The original SubstReport and its ConvEvent are not used in any final report; there needs to be a separate report for each party, and it's easier this way; infrequency argument. Specific reporting routine, dispatched from a smarts notification queue, for reporting substitutions of poachers for poachees and the reverse. Necessary, it is believed, only to keep the report count correct Polling loop to issue occasional CheckIn calls to every Party. [trip: Triples.TripleRec] RETURNS [continue: BOOLEAN _ TRUE] att=type: $telephone, $individual, $service; obj=$rName; val=party Reporting of actions generated by voice services ReportAction.nb~$success ReportAction.Verify.nb~~$noSuchSmarts ReportAction.Verify.nb~~$noSuchParty ReportAction.Verify.nb~~$noSuchConv ReportAction.Verify.nb~~$notInConv ReportAction.Verify.nb~~$stateMismatch ReportAction.Verify.nb~~$interfaceError If report.reportToAll is FALSE, the actual reportee will be detected in due time; it isn't the caller of ReportAction. See ReportAction for nb's Three cases: 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. Specific reporting routine, dispatched from a smarts notification queue, for reporting party state changes. To original reporter, when all others have been delivered! Conversation ID management At present, called only from Verify Called only from CreateConv. Must be called before new conversation's ID is entered in table. Returns an available multicast host number. KillConv: INTERNAL PROC[ convID: ConversationID ] = INLINE { []_convTable.Delete[LOOPHOLE[convID]]; }; Key Table management Presently returns full if table is full; user should hang up and restart. (Key-suppliers can improve things by calling UnregisterKey when done with them.) Presently returns full if table is full; user should hang up and restart. Move-to-front would work nicely here!! Runs forever, possibly polling all Parties to hasten detection of failures. Swinehart, May 15, 1985 10:46:39 am PDT changes to: SetProse, DescribeInterval, PostConvEvent Swinehart, May 16, 1985 10:09:48 am PDT Switch to RedBlackTree for conversation handle stuff changes to: DIRECTORY, ThPartyOpsImpl, nextConv, convTable, cID, ConvTableEntry, ConvTableRecord, UnsealConv, EnhandleConv, KillHandleConv, ConvCompare, ConvGet, EnterKey Swinehart, October 25, 1985 6:24:50 pm PDT Handle => ID, eliminate conversation class stuff! changes to: DIRECTORY, ConversationID, Reseal, nullConvID, nullID, PartyID, SmartsID, nextConv, Alert, DoAdvance, OtherParty, FindOtherParty, CreateConversation, CreateConv, DestroyConversation, MergeConversations, RegisterKey, SetSubject, GetSubject, ConversationsForParty, PostConvEvent, GetOneActive, ConvTableRecord, cTE, UnsealConv, EnhandleConv, KillIDConv, UnsealParty, UnsealSmarts, convTable, ThPartyOpsImpl, Activate, GetEvent Swinehart, November 4, 1985 9:59:14 pm PST Tracking changes to Thrush types, Get/Create code changes to: Alert, DoAdvance, r, r, OtherParty, CreateConv, SetIntervals, SetProse, DescribeInterval, RegisterKey, Verify, PostConvEvent, GetOneActive, Activate, AllIdle, EKResults, EnterKey, Validity, Advance Swinehart, November 7, 1985 4:22:59 pm PST Major change to basic switching methods, simplifying ThParty's role in managing them. changes to: DIRECTORY, Alert, CreateConv, SetSubject, GetSubject, ConversationsForParty, Verify, PostConvEvent, GetOneActive, UnsealConv, UnsealParty, UnsealSmarts, ConvEvent, NB, nullConvID, PartyBody, Reason, SmartsBody, SmartsData, SmartsID, CreateConversation, Advance, DoAdvance, FindOtherParty, MergeConversations, NewId, DestroyConversation, EnterKey, VerifyEnt, GCS, PostConvEvent, RegisterKey, DeleteConversation Swinehart, November 8, 1985 8:44:36 am PST More of above, merge the supervisor stuff, eliminating ThPartySupervisorImpl. changes to: PostConvEvent, PackagedReport, ReportBody, Report, ReportToOne, ReportToParty, Progress Swinehart, April 24, 1986 3:15:04 pm PST When new poacher's owner is same as new poachee's owner, allow poacher to inherit ongoing calls involving poachee. changes to: DIRECTORY, MakeSubstitution Swinehart, May 17, 1986 5:29:46 pm PDT Cedar 6.1 changes to: DIRECTORY, NewId Doug Terry, October 28, 1986 2:23:55 pm PST changes to: RegisterKey Swinehart, December 22, 1986 1:44:41 pm PST AlertOne now totally responsible for narcissism check: if the associated voice party for the originator and the new party are the same, disallow the addition of the new party to the conversation. changes to: AlertOne, VoiceParty, Advance Swinehart, January 21, 1987 6:32:13 pm PST Catalog nb usages. changes to: CreateConversation, Alert, AlertOne, Advance, DoAdvance, GetConversationInfo, GetConversationInfoInt, GetPartyInfo, GetKeyTable, RegisterKey, UnregisterKey, RegisterServiceInterface, LookupServiceInterface, Verify, ReportAction, ReportActionInt Swinehart, June 15, 1987 11:23:13 am PDT Get posting of cInfo.bilateralConv correct in all cases. changes to: DoAdvance, IsBilateralConv, GetConversationInfo, GetConversationInfoInt, GetParties Swinehart, June 21, 1987 10:25:51 pm PDT Install PartyPoller, enabling detection of Smarts failure shortly after it occurs. changes to: DIRECTORY, PartyPoller, Pause (local of PartyPoller), PollOneParty (local of PartyPoller), RemoveKey, TRUSTED Κ*Š˜šœ™IcodešœH™HJšœ:™:K™+—J˜šΟk ˜ Jšœ œ˜ Jšœœ>˜KJšœœ˜$Jšœœ˜Jšœœ˜$Jšœœ/˜™>Jšœ+™+Jšœ*™*Jšœ)™)J˜—šžœœ œ˜Jšœœ˜ J˜J˜Jšœ ˜ Jšœœœ˜Jšœœœ˜Jšœ˜J˜Jšœœ˜J˜)šœœ œœ˜*Jšœœ(Ÿ œ˜A—JšœœœB˜ZJšœ*˜*Jšœ œœ ˜1J˜Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ ™ Jšœ™Jšœ™J˜—šžœœ˜J˜J˜J˜!Jšœœœ˜J™—Jšœœœ˜Jšœ˜Jšœ˜Jšœ˜J˜J˜'Jšœ,˜,Jšœœœ˜J˜$Jšœ9˜9šœ˜Jšœœ˜Jš œ œœœœŸ˜TJšœœ˜&Jšœœ˜—šœ6˜6J™J—šœ<œœ˜WJ™Z—š œœœœœ˜:Jšœ™—JšΟtœΌ ™ΎJ˜šœ œ˜Jšœœ0˜RJšœ˜—Jšœhœœ˜‡J˜Jšœ™Jšœ™Jšœ™Jšœ™Jšœ!™!Jšœ ™ Jšœ™Jšœ™Jšœ"™"Jšœ#™#Jšœ ™ Jšœ™J˜—šžœœœœ˜Jšœœ˜ J˜J˜Jšœ œ˜J˜Jšœ ˜Jšœ ˜Jšœ ˜Jšœœœ˜,Jšœœœ˜šœ˜Jšœ˜Jšœ ˜ Jšœ˜Jšœ˜Jšœ˜Jšœ ˜J˜J˜Jšœ˜—Jšœ˜Jšœ™Jšœ™Jšœ)™)Jšœ*™*Jšœ4™4Jšœ?™?Jšœ ™ Jšœ™Jšœ™Jšœ™Jšœ!™!Jšœ"™"—J˜šž œœœœ˜!J˜J˜"J˜Jšœ œœ˜Jšœœ˜Jšœ œ˜Jšœ œŸΠckŸ@˜[Jšœ œœ˜Jšœ ˜Jšœœœ œ˜9Jšœ˜J˜J˜JšœœœŸ$˜QJšœ>˜>šœ˜Jš œ$œœŸΠctŸ’˜OJšœ.Ÿ˜Q—šœ˜˜ Jšœ œœœ˜Jš œΟsœ£œ £ œ£œ˜Ušœœ˜šœœ#˜DJ™ε—Jšœ ˜J˜—Jšœœœœ˜NJšœ œœœ˜OJ˜J˜—JšœHŸ8˜€Jšœœ˜—šœ œœœ˜@J˜J˜J™Nšœœ˜$Jšœ œ!œœ˜>Jšœ&œœ ˜Q—J™Δšœ˜Jšœœœ˜?—šœ œ˜Jšœœœ˜5J˜!J˜—J˜—šœ ˜Jš œ£œ£œ £ œ£œ˜V—šœ˜šœ/˜/Jšœ<˜<——Jšœ˜Jšœ™Jšœ™Jšœ+™+Jšœ,™,Jšœ6™6JšœA™AJšœ"™"Jšœ!™!Jšœ ™ Jšœ™Jšœ#™#Jšœ$™$J˜—š žœœœœœ˜MJ˜*šœ œœ˜,Jšœœ˜*Jšœ˜—J˜J˜—šžœœœ˜Qšœ˜ Jšœœ ˜Jšœ"˜"JšœB˜BJšœ™Jšœ"™"—J˜—šžœœœ˜>šœ˜ Jšœœ ˜Jšœ"˜"J˜—Jšœœœœ˜)J˜J˜J˜!J˜!J˜+Jšœ#˜#Jšœ!˜!Jšœ˜Jšœ,˜,Jšœ<Ÿœ˜OJ˜Jšœ"™"Jšœ%™%J˜—šž œœ˜!JšœœGœ˜Všœ˜ Jšœœ˜Jšœ˜J˜—J˜8J˜J˜7Jšœ œ˜Jšœœ˜J˜Jšœ œŸC˜WJšœœŸG˜[šΟb œœ˜'J™ψJšœœ˜ Jšœœ˜šœ"Ÿœœ˜>Jšœ'œœŸ˜Q—šœ˜Jšœœ˜Jšœœ˜7Jšœ$˜+—Jšœœ˜2šœœœ˜ Jšœ0˜4—J˜,šœ˜J˜Jšœ ˜ J˜!šœœœ˜'Jšœœœ˜*Jš œœWœœœ˜~—J˜J˜J˜J˜J˜Jšœ1˜1J˜CJ˜$J˜—J˜—šœœœœ ˜ Jš œœ œœœ ˜5—šœ œ˜J˜)Jšœ œœ˜%J˜—Jšœ œœ˜Jšœ œœœ˜JJš œœœ œœ˜MJ˜Jšœœœ˜6Jšœœœ˜J˜J˜ Jšœ%˜%J™7Jš œœœœœ˜CJšœ,˜,Jšœœœ œ ˜3Jšœ˜Jšœ™Jšœ™Jšœ$™$Jšœ#™#Jšœ"™"Jšœ!™!Jšœ&™&J˜—šž œœ˜!Jšœœ˜Jšœ˜J˜J˜Jšœœ˜!J˜Jšœ%˜%Jšœœ,œœ˜LJ˜J˜Jšœ™Jšœ$™$Jšœ#™#Jšœ"™"Jšœ!™!Jšœ&™&J˜—šžœœœœ˜,Kšœœ˜K˜ Kšœ*˜*Kšœœœ*˜:Kšœ œ˜$Kšœ œ œ˜K˜K˜Kšœœ5˜>Kšœ-˜-Kš œœ œœœŸ˜NK˜Kšœ.˜.šœ œ˜Kšœ œ‘˜ Kšœœ$˜AK˜—K™šœ2˜8Kšœ(˜(—Kšœœœœ&˜;šœ˜Kšœœœ˜>K˜-K˜—K˜K˜7šœ œœ˜Kšœ˜K˜+K˜—K˜,K˜MK˜5K˜K˜Jšœ$™$Jšœ™—K˜šžœœ˜*Kšœœ˜K˜ K˜Kšœœ ˜Kšœœœ5˜EKšœ œ˜$K˜KšœU™UKš œœœœœ™CK˜"Kšœœœœ˜2Kšœ.˜.šœ œœ˜Kšœœ"œ˜6Kšœœœ0˜AKšœ œœœ˜;K˜—K˜K˜Jšœ"™"Jšœ'™'Jšœ*™*J˜—š žœœœœœ(˜LJšœœ˜Jšœ!Ÿ"˜CJšœœ˜#J˜Jšœ™Jšœ'™'Jšœ(™(J˜——™ J˜š žœœœ'œœ˜Hšœ˜ Jšœœ˜Jšœœ˜Jšœœ˜Jšœ œ˜—J™WJ™CJšœ8˜8Jšœ$˜$Jšœ'˜'šœœ œ˜J˜J˜šœœœ œ ˜HJš œE ™G—Jšœ ˜—Jšœ œœ˜9š œ œœœœ˜'Jšœ7™7—Jšœ1˜1šœ œœœ˜3J™f—šœ˜Jšœ)˜)Jšœ+Ÿ˜EJšœ˜—J˜Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™Jšœ™J˜—š ž œœœ6œœ˜cJšœœ˜&•StartOfExpansion@ -- [trip: Triples.TripleRec] RETURNS [continue: BOOLEAN _ TRUE]šœ˜Jš œ œœ"œœœ˜UJ˜—Jšœ*œ˜/Jš œ œœ œœ˜*Jšœ œ$˜3J˜%J˜"Jšœœ)˜BJ˜"J˜$J˜J˜—šž œ œ˜J˜J˜Jšœœ˜Jšœ ˜Jšœ ˜Jšœœ˜%J˜8J˜4šœœŸ)˜E˜OJ™ΐ—JšœEœ˜NJšœ˜—šœœŸ˜4J˜OJšœDœ˜LJšœ˜—J˜>J˜$J˜1šœ œ˜)J˜ J˜J˜J˜J˜J˜—J™Jšœ;ŸE˜€Jšœ˜J˜—Jšœœœ ˜&šœ œœ˜J˜Jšœ˜J˜J˜—šžœœ˜šœLœ˜TJš œœœœœœ˜E——J˜Jš œoΟo œ]œ$œΎœ™ΘJ™šžœœ˜JšœŸ3˜OJšœ œ˜J˜J˜Jšœ˜J˜–@ -- [trip: Triples.TripleRec] RETURNS [continue: BOOLEAN _ TRUE]šœ œ˜-Jš‘<™šœ œœ œ˜BJšœP˜P—šœ œœ˜Jšœœ<˜PJ˜,JšœE˜EJ˜—Jšœœ"Ÿ œ˜@Jšœœœ#˜4J˜—Jšœœ˜0Jšœ+Ÿ œ˜GJ˜—J˜J™Cšž œœ˜(™ J™ŒJ™i—Jšœœ˜,šœœ-œ ˜VJšœœ$˜,—šœ˜Jšœ]˜]—šœœ˜ Jšœ5Ÿ˜T—Jšœœ˜)J˜J˜—J™,šžœœœ˜ J™kJšœœ˜#Jšœœ˜3˜šœœ˜šœœ˜J˜?Jšœ7˜7J˜—Jšœœ˜Jšœ˜Jšœ˜—šœ˜šœ+˜+JšœR˜R——J˜.J˜—Jšœ˜—J˜šžœœœ˜!J™BJšœœ˜#Jšœ œ˜'J˜J˜—šž œœœ˜1J™BKš œœœœŸ˜0Kšœ,˜,šœ œœœ˜JKšœœ˜.—J˜J˜—šžœœœœ˜?J™BJšœ œ˜K˜MKšœ˜ Kšœ˜J˜J˜—J˜šžœœœ˜?J˜.JšœœŸ˜EJšœ˜J˜—š žœœœœœ˜5Jšœ-Ÿ˜Aš œœœœœœ ˜1Jšœœ ˜—Jšœ ˜Jšœ˜J˜—šžœœœ˜$K˜Kšœœ ˜Kšœœ œœ˜6š œœœœ8œœ˜ZKšœ)œœœ ˜HKšœ˜—K˜K˜—šž œœœœ˜WJ™EJ™\Jšœœœœ˜Jšœœœ˜-Jšœ œ"Ÿ œ˜EJšœœœ˜,J˜K˜—šž œœœœ˜"Jšœ4œœœ˜KJšœœ˜J˜&J˜Jšœœœœ˜J˜$šœ ˜Jšœœœ˜Cšœ˜ Jšœœ œ˜DJš œ œœœœ ˜Pšœ œœ$œ˜?J˜,Jšœœœ˜OJš œœœœŸ˜?šœ(˜,Jšœœœ+˜EJšœŸ%˜A—J˜—J™šœ˜Jšœ-˜-Jšœœ˜&—J˜—Jšœœ˜—Jšœœœ˜Jšœœœ ˜7J˜$Jšœœœ$˜5J˜Jšœ™Jšœ!™!K˜——™7J™Jšœ œœ˜'šœœœ˜J˜J˜J˜J˜J˜—šžœœœ.˜TJ™»šžœœŸ˜IJšŸ(œŸ˜?Jšœ,Ÿ ˜LJšœ-Ÿ1˜^J˜—šž œœ‘˜?Jš‘(œ˜,Jšœ(œ ˜9Jšœ˜JšœCœ˜JJšœ˜Jšœ œœœ˜šœ œ˜(J˜ ˜J˜J˜AJ˜J˜Jšœ3Ÿ˜FJ˜—J˜7J˜J˜—šœœ˜#Jšœ˜Jšœ=˜=—šœ˜ JšœΔ™Δšœ:˜:Jšœ9œ˜E——šœ˜!Jšœ[™[˜Jšœ˜Jšœ œ˜Jšœ ˜ Jšœ˜Jšœ˜J˜——Jš ’œ œ™–J˜,J˜.Jšœœ˜Jšœ#˜#šœœ˜Jšœ‹™‹J˜(J˜(J˜—JšœE˜EJ™Zšœ˜Jšœ˜Jšœ œ˜Jšœ ˜ Jšœœ˜ Jšœ˜J˜—J˜—Jšœ œœ œœœœ˜AJ˜=J˜J˜—šžœœ˜/šœLœ™TJš œœœœœœ™E—J™°Jšœœ˜0J˜!Jšœœ ˜1J˜!Jšœœ0˜Kšœ˜Jšœg˜g—J˜J˜—šžœœœ˜$J™ŽJšœœ˜#Jšœœ˜7˜šœœ˜šœœ˜J˜?Jšœ7˜7J˜—Jšœœ˜Jšœ˜Jšœ˜—JšœW˜WJš œœœœŸ.˜Gšœœœ˜*šœ/˜/Jšœ;˜;Jšœ!˜!JšœH˜H——šœ˜J™@—J˜—J˜J˜J˜——J™>™šž œœ˜šžœœ œ˜J˜/J˜—Jšœœ˜š˜šœœœœ˜MJšœ5˜9—Jšœœ˜–@ -- [trip: Triples.TripleRec] RETURNS [continue: BOOLEAN _ TRUE]šž œ˜%Jšœœ œœ™