ThPartyOpsImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, June 3, 1986 1:41:42 am PDT
DIRECTORY
BasicTime USING [ Update, Now ],
CardTable USING [ Create, Delete, EachPairAction, Fetch, Pairs, Ref, Store ],
DESFace USING [ GetRandomKey, Key ],
IV USING [ KeyTableBody ],
MBQueue USING [ QueueClientAction ],
Process USING [ Detach, SecondsToTicks, SetTimeout ],
Pup USING [ nullSocket, Socket ],
PupName USING [ AddressToRope ],
RefID USING [ ID, Reseal, Unseal ],
Rope USING [ Equal ],
RPC USING [ CallFailed, GetConversationID, ShortROPE ],
ThNet USING [ pd ],
ThParty,
ThPartyPrivate
USING [
ConversationBody, ConversationData, ConvState, ConvStateBody, DoDescribeParty, GetRnameFromParty, PartyAvailable, PartyBody, PartyData, SmartsBody, SmartsData ],
ThPartyMonitorImpl,
Thrush
USING [
ActionReport, AlertKind, CallUrgency, ConversationID, ConvEvent, ConvEventBody, Credentials, EncryptionKey, epoch, InterfaceSpec, KeyTable, NB, NetAddress, noAddress, nullConvID, nullID, nullKey, PartyID, Reason, ROPE, SHHH, SmartsID, StateID, StateInConv, unencrypted ],
ThSmartsRpcControl USING [ InterfaceRecord ],
Triples USING [ Any, Erase, Foreach, ForeachProc, Make, Select ],
TU USING [ MakeUnique ],
VoiceUtils USING [ OwnNetAddress, Problem ]
;
ThPartyOpsImpl:
CEDAR
MONITOR
LOCKS root
IMPORTS
BasicTime,
CardTable,
DESFace,
MBQueue,
Process,
PupName,
RefID,
root: ThPartyMonitorImpl,
Rope,
RPC,
ThNet,
ThParty,
ThPartyPrivate,
Thrush,
Triples,
TU,
VoiceUtils
EXPORTS ThParty, ThPartyPrivate
SHARES ThPartyMonitorImpl = {
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;
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
] RETURNS [ nb: NB, convEvent: ConvEvent ] = {
ENABLE UNWIND => NULL;
conv: ConversationData =
NEW[ThPartyPrivate.ConversationBody ← [
timeOfID: BasicTime.Now[],
startTime: BasicTime.Now[],
subject: subject,
urgency: urgency,
alertKind: alertKind,
conferenceHost: ConferenceHost[],
keyTable: NEW[IV.KeyTableBody[20B]]
] ];
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];
IF nb # $success THEN DeleteConversation[ conv ];
};
Alert:
PUBLIC
ENTRY PROC[
shhh: SHHH,
credentials: Credentials,
calledPartyID: PartyID,
comment: ROPE
] RETURNS [ nb: NB ] = {
ENABLE UNWIND => NULL;
convState: ConvState;
conv: ConversationData;
callingParty: PartyData;
calledParty: PartyData;
IF calledPartyID=credentials.partyID THEN RETURN[nb: $narcissism];
[, callingParty,, nb] ← Verify[credentials];
IF nb # $success THEN RETURN;
credentials.partyID ← calledPartyID;
[conv, calledParty, convState, nb] ← Verify[credentials];
SELECT nb
FROM
$notInConv => NULL;
$success => IF conv#NIL THEN RETURN[$convStillActive]; -- already in the conversation!
$noSuchParty => RETURN[$noSuchParty2];
ENDCASE=> RETURN;
credentials.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, state: $notified, newInConv: TRUE, originatingParty: callingParty].nb;
};
Advance:
PUBLIC
ENTRY
PROC[
shhh: SHHH,
credentials: Credentials,
state: Thrush.StateInConv,
reportToAll: BOOL,
reason: Thrush.Reason,
comment: ROPE
] RETURNS [nb: NB, convEvent: ConvEvent] = {
ENABLE UNWIND => NULL;
[nb, convEvent] ← DoAdvance[
credentials: credentials,
state: state,
reportToAll: reportToAll,
reason: reason,
comment: comment,
newInConv: FALSE
];
};
DoAdvance:
PUBLIC
INTERNAL
PROC [
credentials: Credentials,
state: Thrush.StateInConv,
reportToAll: BOOL←FALSE,
reason: Thrush.Reason←NIL,
comment: ROPE←NIL,
newInConv: BOOL, -- if TRUE, party must not be in the conversation yet . . . and vice versa
originatingParty: PartyData←NIL
] RETURNS [nb: NB←$success, convEvent: ConvEvent←NIL] = {
convState: ConvState;
conv: ConversationData;
party: PartyData;
[conv, party, convState, nb] ← Verify[credentials, newInConv, originatingParty];
SELECT nb
FROM
$success => {
IF convState=NIL THEN RETURN;
IF state = $active AND party.partyActive THEN RETURN[nb: $partyAlreadyActive];
IF conv.numIdle = conv.numParties AND state # $idle THEN RETURN[nb: $convIdle];
credentials.state ← state;
};
$stateMismatch => credentials.state ← convState.state;
ENDCASE => RETURN;
convEvent ← PostConvEvent[
credentials: credentials, convState: convState,
reason: reason, comment: comment, reportToAll: reportToAll];
};
GetConversationInfo:
PUBLIC ENTRY PROC [ shh:
SHHH←none, convID: ConversationID ]
RETURNS [
nb: NB←$success,
cInfo: ThParty.ConversationInfo←[]
] = {
conv: ConversationData = UnsealConv[convID];
IF conv=NIL THEN RETURN[nb: $noSuchConv];
cInfo.subject ← conv.subject;
cInfo.urgency ← conv.urgency;
cInfo.alertKind ← conv.alertKind;
cInfo.startTime ← conv.startTime;
cInfo.lastTime ← conv.timeOfID;
cInfo.conferenceHost ← conv.conferenceHost;
cInfo.numParties ← conv.numParties;
cInfo.numActive ← conv.numActive;
cInfo.numIdle ← conv.numIdle;
cInfo.originator ← Reseal[Triples.Select[$Originator, conv, -- originator --]];
};
GetPartyInfo:
PUBLIC ENTRY PROC [
shh: SHHH←none, credentials: Credentials, nameReq: ThParty.NameReq, allParties: BOOL ]
RETURNS [
nb: NB,
pInfo: ThParty.PartyInfo←NIL
] = {
conv: ConversationData = UnsealConv[credentials.convID];
convState: ConvState;
ownParty: PartyData = UnsealParty[credentials.partyID];
GetParties:
INTERNAL FinishProcType = {
Computes the socket values (and corresponding party/smarts ident) for each voice terminal participant in a conversation, when invoked by Report.
index: NAT;
HowToReport ← NIL;
IF smarts.properties.role # $voiceTerminal THEN RETURN;
IF party=ownParty
THEN {
index ← 0;
pInfo.numParties←MAX[1, pInfo.numParties];
}
ELSE {
index ← MAX[1, pInfo.numParties];
pInfo.numParties ← index+1;
};
pInfo[index] ← [
partyID: Reseal[party],
type: party.type,
state: convState.state,
stateID: convState.stateID,
lastTime: convState.time,
name:
IF nameReq=$none
THEN
NIL
ELSE ThPartyPrivate.DoDescribeParty[party, nameReq],
numConvs: party.numConvs,
enabled: party.enabled,
partyActive: party.partyActive,
socket:
[smarts.properties.netAddress.net, smarts.properties.netAddress.host, convState.sockID]];
};
nb ←
IF conv=
NIL
THEN $noSuchConv
ELSE IF ownParty=NIL THEN $noSuchParty ELSE $success;
IF nb=$success
THEN {
convState ← GetConvState[conv, ownParty];
IF convState=NIL THEN nb ← $notInConv
};
IF nb#$success THEN RETURN;
pInfo ← NEW[ThParty.PartyInfoSeq[IF allParties THEN conv.numParties ELSE 1]];
pInfo.conferenceHost ← conv.conferenceHost;
pInfo[0] ← [];
Report[
FinishProc: GetParties, reportToAll: allParties,
conv: conv, party: ownParty, whatToReport: NIL];
};
GetKeyTable:
PUBLIC
ENTRY
PROC [ shh:
SHHH←none, credentials: Credentials ]
RETURNS [ nb: NB, keyTable: Thrush.KeyTable←NIL ] = {
conv: ConversationData;
[conv,,,nb] ← Verify[credentials];
IF nb # $success THEN RETURN;
keyTable ← conv.keyTable;
};
RegisterKey:
PUBLIC ENTRY PROC [
shh: SHHH ← none,
credentials: Credentials,
key: Thrush.EncryptionKey,
reportNewKeys: BOOL]
RETURNS [ nb: NB, keyIndex: [0..17B] ← 0 ] = {
ENABLE UNWIND => NULL;
conv: ConversationData;
[conv, , , nb] ← Verify[credentials];
Don't care about stateMismatch here.
IF nb=$success OR nb=$stateMismatch THEN keyIndex ← EnterKey[conv, key].keyIndex;
};
RegisterServiceInterface:
PUBLIC
ENTRY
PROC[
shhh: SHHH ← none,
credentials: Thrush.Credentials,
interfaceSpecPattern: Thrush.InterfaceSpec
] RETURNS[nb: NB, interfaceSpec: Thrush.InterfaceSpec] = {
interface: REF Thrush.InterfaceSpec;
instance: RPC.ShortROPE←NIL;
hostHint: Thrush.NetAddress;
party: PartyData;
type: RPC.ShortROPE = interfaceSpecPattern.interfaceName.type;
[party: party, nb: nb] ← Verify[credentials];
SELECT nb
FROM
$success, $stateMismatch => NULL;
$noSuchParty => IF party = NIL THEN RETURN ; -- else just not yet enabled
ENDCASE => RETURN;
nb ← $success;
interface ← FindServiceInterface[party, type];
IF interface =
NIL
THEN {
interface ← NEW[Thrush.InterfaceSpec ← [interfaceName: [type: type], serviceID: interfaceSpecPattern.serviceID]];
party.actionInterfaces ← CONS[interface, party.actionInterfaces];
};
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^;
};
LookupServiceInterface:
PUBLIC ENTRY PROC[
shhh: SHHH ← none,
credentials: Thrush.Credentials,
serviceParty: PartyID,
type: RPC.ShortROPE
] RETURNS[nb: NB ← $success, interfaceSpec: Thrush.InterfaceSpec] = {
interface: REF Thrush.InterfaceSpec;
party: PartyData;
[nb: nb] ← Verify[credentials];
SELECT nb FROM $success, $stateMismatch => NULL; ENDCASE => RETURN;
party ← UnsealParty[serviceParty];
IF party=NIL THEN { nb ← $noSuchParty2; RETURN; };
interface ← FindServiceInterface[party, type];
IF interface=
NIL
THEN {
party ← NARROW[Triples.Select[$Poaching, party, NIL]];
IF party#NIL THEN interface ← FindServiceInterface[party, type];
IF interface = NIL THEN { nb ← $noSuchInterface; RETURN; };
};
nb ← $success;
interfaceSpec ← interface^;
};
Utilities
Verify:
INTERNAL
PROC[ credentials: Credentials, newInConv:
BOOL←
FALSE, originatingParty: PartyData←
NIL
]
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.
originatingParty is only for use in PartyAvailable. See Poachnotes, it's too hairy to describe here.
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
AND newInConv
THEN
nb ← ThPartyPrivate.PartyAvailable[originatingParty, party];
Party will not be available if some party related-by-hardware to this one is busy in a way that would make the hardware for this party fail to work or interfere with the other's operation. We are only interested in this situation when we are contemplating beginning another conversation.
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;
};
GetConvState:
INTERNAL
PROC[ conv: ConversationData, party: PartyData, createOK:
BOOL←
FALSE]
RETURNS [convState: ConvState←NIL] = {
GCS: Triples.ForeachProc = {
WITH trip.att SELECT FROM cS: ConvState => { convState𡤌S; RETURN[FALSE]; }; ENDCASE;
};
Triples.Foreach[Triples.Any, conv, party, GCS];
IF convState#NIL OR ~createOK THEN RETURN;
convState ← NEW[ThPartyPrivate.ConvStateBody ← [sockID: NewId[]]];
Triples.Make[convState, conv, party];
Triples.Make[$Party, conv, party];
IF conv.numParties=0 THEN TU.MakeUnique[$Originator, conv, party];
party.numConvs ← party.numConvs+1;
conv.numParties ← conv.numParties+1;
};
PostConvEvent:
INTERNAL PROC[
credentials: Credentials,
convState: ConvState,
reason: Thrush.Reason←NIL,
comment: Thrush.ROPE←NIL,
reportToAll: BOOL
] RETURNS [convEvent: ConvEvent ] = {
conv: ConversationData ← UnsealConv[credentials.convID];
party: PartyData ← UnsealParty[credentials.partyID];
SELECT convState.state
FROM
-- states that was, but will no longer be
$idle => { conv.numIdle ← conv.numIdle-1; party.numConvs ← party.numConvs+1; };
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: REF←NIL];
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;
CONTINUE;
};
IF report.smarts.failed THEN RETURN;
report.smarts.interface.clientStubProgress[
interface: report.smarts.interface, shh: report.smarts.shh, convEvent: convEvent];
CheckIdle[UnsealConvE[convEvent.self.convID]];
};
};
ReportCheckIdle:
PROC[r:
REF] = {
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.
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:
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;
};
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];
IF convState=NIL THEN ERROR;
Here we actually move to newParty all the relations between the conversation and the oldParty. This feels too drastic to work, but we'll try it. When newParty is the poacher, splice only occurs if the owner rName of oldParty and newParty are the same. Otherwise, the poaching relationship is OK, but it doesn't affect ongoing conversations.
IF newParty.type#$telephone
AND ThPartyPrivate.GetRnameFromParty[oldParty, $owner].rAtom #
ThPartyPrivate.GetRnameFromParty[newParty, $owner].rAtom THEN RETURN;
newParty.partyActive ← oldParty.partyActive;
oldParty.partyActive ← FALSE;
IF convState.state#idle
THEN {
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.)
convEvent ←
NEW[Thrush.ConvEventBody ← [
self: [],
other: [
partyID: Reseal[newParty],
smartsID: Reseal[Triples.Select[$Smarts, newParty, Triples.Any]],
convID: conv.convID,
state: convState.state,
stateID: convState.stateID ← convState.stateID + 1 -- Clock must tick!
],
time: conv.timeOfID ← convState.time ← BasicTime.Now[],
reason: $substituting
]];
Report[
FinishProc: FinishSubstitution,
reportToAll: TRUE,
conv: conv,
party: NIL,
whatToReport:
NEW[SubstReportRec←[
convEvent: convEvent,
oldPartyID: Reseal[oldParty], newPartyID: Reseal[newParty]]]
];
};
IF oldParty=NIL OR newParty=NIL OR oldParty=newParty THEN RETURN;
Triples.Foreach[$Party, Triples.Any, oldParty, SubInOneConv];
};
FinishSubstitution:
INTERNAL FinishProcType = {
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.
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;
CONTINUE;
};
IF report.smarts.failed THEN RETURN;
report.smarts.interface.clientStubSubstitution[
interface: report.smarts.interface, shh: report.smarts.shh,
convEvent: substReport.convEvent,
oldPartyID: substReport.oldPartyID, newPartyID: substReport.newPartyID];
CheckIdle[UnsealConvE[substReport.convEvent.self.convID]];
Necessary, it is believed, only to keep the report count correct
};
};
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: BOOL ← FALSE,
selfOnCompletion: BOOL ← FALSE
] 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;
};
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;
CONTINUE;
};
conv: ConversationData;
IF report.smarts.failed THEN RETURN; -- What about numReportsOut counts?
report.smarts.interface.clientStubReportAction[
interface: report.smarts.interface, shh: report.smarts.shh, report: aReport.report];
conv ← UnsealConvE[aReport.report.self.convID];
IF aReport.selfOnCompletion
THEN {
FinalReport:
ENTRY
PROC= {
To original reporter, when all others have been delivered!
pSmarts: SmartsData;
finalReport.numReportsOut ← finalReport.numReportsOut-1;
IF finalReport.numReportsOut#0 THEN RETURN;
pSmarts ← UnsealSmarts[finalReport.report.self.smartsID];
conv.numReportsOut ← conv.numReportsOut + 1;
pSmarts.notifications.QueueClientAction[DoReportAction, finalReport];
};
FinalReport[];
};
CheckIdle[conv];
};
};
Conversation ID management
convTable: CardTable.Ref = CardTable.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 BOOL ← ALL[FALSE];
MarkInUse: CardTable.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.
Move-to-front would work nicely here!!
IF key=Thrush.nullKey THEN RETURN[0, $duplicate];
FOR keyIndex
IN [1..17B]
DO
IF conv.keyTable[keyIndex]=key THEN RETURN[keyIndex, $duplicate]
ELSE IF conv.keyTable[keyIndex][0].b=0 AND conv.keyTable[keyIndex] = Thrush.nullKey THEN EXIT;
REPEAT FINISHED => RETURN[0, full]; ENDLOOP;
conv.keyTable[keyIndex] ← key;
RETURN[keyIndex, new];
};
}.
Temporarily out of service at this time
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