ThPartyOpsImpl.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
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 = {
Copies 
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
Conversation ID stuff
nextConv: ConversationID ← Thrush.epoch;
nullishKey: Thrush.EncryptionKey ← ALL[[b:0, p:1]]; -- Thrush.nullKey, parity-corrected.
Implementation of ThParty
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 { []𡤎nterKey[conv, DESFace.GetRandomKey[]]; };
This conv's key, will be key index 1.
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 ];
};
CreateConversation.nb~$success
CreateConversation.nb~$voiceTerminalBusy -- checkConflict only
CreateConversation.Verify.nb~~$noSuchSmarts
CreateConversation.Verify.nb~~$noSuchParty
CreateConversation.nb~$<unexpectedErrors>
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;
};
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~$<unexpectedErrors>
AlertOne: PUBLIC INTERNAL PROC[
credentials: Credentials,
calledPartyID: PartyID,
intendedPartyID: PartyID ← nullID
] RETURNS [ nb: 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
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];
Be sure $individual parties are associated with the right voice terminals.
IF VoiceParty[calledParty] = VoiceParty[UnsealParty[partyID]] THEN RETURN[$narcissism];
It is not permitted to call yourself. This is the only check in the system for this case.
IF nb = $success AND conv#NIL THEN RETURN[$alreadyInConv];
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.
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;
};
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~$<unexpectedErrors>
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
];
};
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
DoAdvance: PUBLIC INTERNAL PROC [
credentials: Credentials,
intendedPartyID: PartyID ← nullID,
state: Thrush.StateInConv,
reportToAll: BOOLFALSE,
reason: Thrush.Reason←NIL,
comment: ROPENIL,
newInConv: BOOL, -- if TRUE, party must not be in the conversation yet . . . and vice versa
bilateral: BOOLFALSE,
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;
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.
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;
A party is not allowed to be $active unless it can support a voice connection.
IF (voiceP←VoiceParty[party])=NIL OR
(voiceS ← NARROW[Triples.Select[$Smarts, voiceP, NIL]])=NIL OR
voiceS.properties.role#$voiceTerminal THEN RETURN[nb: $voiceTerminalUnavailable];
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.
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];
};
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
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]]; };
GetConversationInfo.nb~$success
GetConversationInfo.nb~$noSuchConv
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 --]];
};
GetConversationInfoInt.nb~$success
GetConversationInfoInt.nb~$noSuchConv
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𡤀 -- 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 = {
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.
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�tiveIndex+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];
};
GetPartyInfo.nb~$success
GetPartyInfo.nb~$noSuchConv
GetPartyInfo.nb~$noSuchParty
GetPartyInfo.nb~$notInConv
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;
};
GetKeyTable.nb~$success
GetKeyTable.Verify.nb~~$noSuchSmarts
GetKeyTable.Verify.nb~~$noSuchParty
GetKeyTable.Verify.nb~~$noSuchConv
GetKeyTable.Verify.nb~~$notInConv
GetKeyTable.Verify.nb~~$interfaceError
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];
Don't care about stateMismatch here (maps to $success).
SELECT nb FROM $success, $stateMismatch => NULL; ENDCASE => RETURN;
[keyIndex, ekResults] ← EnterKey[conv, key];
nb ← IF ekResults#$new THEN $success ELSE $newKeys;
};
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: 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];
};
UnregisterKey.nb~$success
RegisterKey.Verify.nb~~$noSuchSmarts
RegisterKey.Verify.nb~~$noSuchParty
RegisterKey.Verify.nb~~$noSuchConv
RegisterKey.Verify.nb~~$notInConv
RegisterKey.Verify.nb~~$interfaceError
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];
};
Produce hostHint value
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^;
};
RegisterServiceInterface.nb~$success
Verify.nb~$noSuchParty
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]; -- There's no strong reason for checking 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; };
};
interfaceSpec ← interface^;
};
LookupServiceInterface.nb~$success
LookupServiceInterface.nb~$noSuchParty2
LookupServiceInterface.nb~$noSuchInterface
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;
};
CheckIn.nb~$success
LookupServiceInterface.nb~~$noSuchParty
LookupServiceInterface.nb~~$noSuchSmarts
Utilities
Verify: INTERNAL PROC[ credentials: Credentials, newInConv: BOOLFALSE ]
RETURNS [
conv: ConversationData←NIL,
party: PartyData←NIL,
convState: ConvState←NIL,
nb: Thrush.NB ] = {
Validate parameters: existence, in conversation, state match; produce dehandled values.
Validation for smarts is only partial; verifies that it's a smarts.
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,
This is dangerous. If caller inadvertently omitted convID, kablooey!
ENDCASE=> $success;
IF nb=$success AND ~party.enabled THEN nb ← $noSuchParty;
IF nb#$success OR conv=NIL THEN RETURN;
Not interested in any conversation, or it doesn't exist
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;
};
Verify.nb~$success
Verify.nb~$noSuchSmarts
Verify.nb~$noSuchParty
Verify.nb~$noSuchConv
Verify.nb~$notInConv
Verify.nb~$stateMismatch
Verify.nb~$interfaceError
GetConvState: PUBLIC INTERNAL PROC[ conv: ConversationData, party: PartyData, createOK: BOOLFALSE]
RETURNS [convState: ConvState←NIL] = {
GCS: Triples.ForeachProc = {
WITH trip.att SELECT FROM cS: ConvState => { convState𡤌S; 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; };
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.
$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 Progress
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: REFNIL];
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.
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] = {
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.
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];
};
Called as report is being prepared to fill in addressee information
FinishEvent: INTERNAL FinishProcType = {
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.
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];
};
Called to issue state-change progress report
ReportProgress: PROC[r: REF] = {
Specific reporting routine, dispatched from a smarts notification queue, for reporting party state changes.
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] = {
If all parties are idle in conversation, conversation can go away.
report: PackagedReport = NARROW[r];
CheckIdle[NARROW[report.whatToReport]];
};
CheckIdle: ENTRY PROC[conv: ConversationData] = {
If all parties are idle in conversation, conversation can go away.
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 {
If all parties are idle in conversation, conversation can go away.
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];
[]𡤌onvTable.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 ] = {
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.
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: BOOLFALSE]
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
};
Now test for direct 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;
};
EngageParty.nb~$success
EngageParty.nb~$voiceTerminalBusy
Substitution of one party for another in a conversation
SubstReport: TYPE = REF SubstReportRec;
SubstReportRec: TYPE = RECORD [
convEvent: Thrush.ConvEvent,
oldPartyID: PartyID,
newPartyID: PartyID
];
MakeSubstitution: PUBLIC INTERNAL PROC[oldParty: PartyData, newParty: PartyData] = {
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.
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
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.
ThPartyPrivate.GetRnameFromParty[oldParty, $owner].rAtom #
ThPartyPrivate.GetRnameFromParty[newParty, $owner].rAtom THEN RETURN;
IF oldParty.type=$individual THEN
When oldParty is the poacher, issue a report to it explicitly, so it can record the change.
Report[
FinishProc: FinishSubstitution,
reportToAll: FALSE,
conv: conv,
party: oldParty,
whatToReport: whatToReport
];
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. 
newParty.partyActive ← oldParty.partyActive;
newParty.partyEngaged ← oldParty.partyEngaged;
oldParty.partyActive ← FALSE;
oldParty.partyEngaged ← nullConvID;
IF convState.state#idle THEN {
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.
newParty.numConvs ← newParty.numConvs+1;
oldParty.numConvs ← oldParty.numConvs-1;
};
Triples.Foreach[Triples.Any, conv, oldParty, SpliceNewPartyIntoConv];
Prepare and issue a report to all parties (report will go to the new, not the old, party.)
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 = {
PROC[party: PartyData, smarts: SmartsData, convState: ConvState, whatToReport: REF]
RETURNS [HowToReport: PROC[r: REF]←NIL, whatToReallyReport: REFNIL];
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.
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] = {
Specific reporting routine, dispatched from a smarts notification queue, for reporting substitutions of poachers for poachees and the reverse.
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];
Necessary, it is believed, only to keep the report count correct
};
};
Polling loop to issue occasional CheckIn calls to every Party.
PartyPoller: PROC = {
Pause: PROC[seconds: INT] ← {
Process.Pause[Process.SecondsToTicks[seconds]];
};
numPartiesInPrevPass: INT𡤀
DO
nextScheduledCheck: INTIF numPartiesInPrevPass=0 THEN ThSmarts.noneScheduled
ELSE numPartiesInPrevPass*ThNet.pd.pollPartiesInterval*2;
numPartiesInThisPass: INT𡤀
PollOneParty: Triples.ForeachProc = {
[trip: Triples.TripleRec] RETURNS [continue: BOOLEANTRUE]
att=type: $telephone, $individual, $service; obj=$rName; val=party
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
];
};
};
Reporting of actions generated by voice services
AReport: TYPE = REF AReportRec;
AReportRec: TYPE = RECORD [
report: Thrush.ActionReport,
numReportsOut: INTEGER𡤀, -- 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: BOOLFALSE,
selfOnCompletion: BOOLFALSE
] RETURNS [nb: NB, numReportsIssued: NAT𡤀] = {
ENABLE UNWIND => NULL;
[nb, numReportsIssued] ← ReportActionInt[report, reportToAll, selfOnCompletion];
};
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
ReportActionInt: INTERNAL PROC[
report: Thrush.ActionReport, -- includes credentials (in other field) identifying the service party.
reportToAll: BOOLFALSE,
selfOnCompletion: BOOLFALSE
] RETURNS [nb: NB, numReportsIssued: NAT𡤀] = {
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];
If report.reportToAll is FALSE, the actual reportee will be detected in due time; it isn't the caller of ReportAction.
numReportsIssued ← rReport.numReportsOut;
};
See ReportAction for nb's
FinishReportAction: INTERNAL FinishProcType = {
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.
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] = {
Specific reporting routine, dispatched from a smarts notification queue, for reporting party state changes.
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= {
To original reporter, when all others have been delivered!
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];
};
};
Conversation ID management
convTable: CardTab.Ref = CardTab.Create[];
UnsealConv: PUBLIC INTERNAL PROC[ convID: ConversationID]
At present, called only from Verify
RETURNS [conv: ConversationData←NIL ] = {
RETURN[NARROW[convTable.Fetch[LOOPHOLE[convID]].val]]; };
EnhandleConv: INTERNAL PROC[ conv: ConversationData ]
Called only from CreateConv.
RETURNS [ convID: ConversationID←nextConv ] = {
nextConv ← BasicTime.Update[nextConv, 1];
IF ~convTable.Store[LOOPHOLE[convID], conv] THEN ERROR;
};
ConferenceHost: INTERNAL PROC RETURNS [conferenceHost: Thrush.NetAddress] = {
Must be called before new conversation's ID is entered in table. Returns an available
multicast host number.
inUse: PACKED ARRAY [0..100B) OF BOOLALL[FALSE];
MarkInUse: CardTab.EachPairAction = {
conv: ConversationData = NARROW[val];
inUse[conv.conferenceHost.host] ← TRUE;
RETURN[FALSE];
};
[]𡤌onvTable.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]];
};
KillConv: INTERNAL PROC[ convID: ConversationID ] = INLINE {
[]𡤌onvTable.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]]]; };
Key Table management
EKResults: TYPE = { new, duplicate, disagreement, full };
EnterKey: INTERNAL PROC[conv: ConversationData,
key: Thrush.EncryptionKey]
RETURNS [ keyIndex: [0..17B], results: EKResults] = {
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.)
keyIndex𡤀
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] = {
Presently returns full if table is full; user should hang up and restart.
Move-to-front would work nicely here!!
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
};
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