DIRECTORY
BluejayUtils USING [ DescribeInterval ],
DESFace USING [ GetRandomKey, Key ],
IO,
Lark USING [ ConnectionSpecRec, KeyTableBody, VoiceSocket ],
RedBlackTree,
Pup USING [ Address, Socket ],
PupSocket USING [ Create, GetLocalAddress, Socket ],
RefID USING [ Seal ],
SafeStorage USING [ GetCanonicalType, Type ],
BasicTime USING [ Update, Now ],
Thrush USING [
AlertKind, CallUrgency, ConversationHandle, ConvEvent, ConvEventBody, Credentials, Dehandle, EncryptionKey, epoch, EventSequence, EventSequenceBody, H, IntervalSpec, IntervalSpecs, NB, nullConvHandle, nullHandle, nullKey, PartyHandle, PartyType, ProseSpec, ProseSpecs, Reason, ROPE, SHHH, SmartsHandle, StateID, StateInConv, ThHandle, unencrypted, VoiceTime ],
ThNet USING [ pd ],
ThParty,
ThPartyPrivate USING [
CFRec, CFRef, ConversationBody, ConversationData, DoDescribeParty, logSizeIncrement, PartyBody, PartyData, SmartsBody, SmartsData, Supervise ],
ThPartyMonitorImpl,
ThSmartsRpcControl USING [ InterfaceRecord ],
Triples USING [ Any, Foreach, ForeachProc, Make, Select ],
TU USING [ RefAddr ],
VoiceUtils USING [ Problem, ProblemFR ]
;

ThPartyOpsImpl: CEDAR MONITOR LOCKS root
IMPORTS
BluejayUtils,
DESFace,
root: ThPartyMonitorImpl,
RefID,
SafeStorage,
BasicTime,
IO,
PupSocket,
RedBlackTree,
ThNet,
ThPartyPrivate,
Thrush,
Triples,
TU,
VoiceUtils
EXPORTS ThParty, ThPartyPrivate
SHARES ThPartyMonitorImpl = {

AlertKind: TYPE = Thrush.AlertKind;
CallUrgency: TYPE = Thrush.CallUrgency;
CFRef: TYPE = ThPartyPrivate.CFRef;
RTConvType: SafeStorage.Type = SafeStorage.GetCanonicalType[CODE[ThPartyPrivate.ConversationBody]];
ConvEvent: TYPE = Thrush.ConvEvent;
ConversationData: TYPE = ThPartyPrivate.ConversationData;
ConversationHandle: TYPE = Thrush.ConversationHandle;
Credentials: TYPE = Thrush.Credentials;
H: PROC[r: REF] RETURNS[Thrush.ThHandle] = INLINE {RETURN[Thrush.H[r]]; };
NB: TYPE = Thrush.NB;
none: SHHH = Thrush.unencrypted;
nullConvHandle: Thrush.ConversationHandle = Thrush.nullConvHandle;
nullHandle: Thrush.PartyHandle = Thrush.nullHandle;
PartyBody: TYPE = ThPartyPrivate.PartyBody;	-- Concrete
RTPartyType: SafeStorage.Type = SafeStorage.GetCanonicalType[CODE[PartyBody]];
PartyData: TYPE = ThPartyPrivate.PartyData;	-- REF Concrete
PartyHandle: TYPE = Thrush.PartyHandle;	-- Handle
Reason: TYPE = Thrush.Reason;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Thrush.SHHH;
SmartsBody: TYPE = ThPartyPrivate.SmartsBody;	-- Concrete
SmartsData: TYPE = ThPartyPrivate.SmartsData;	-- REF Concrete
RTSmartsType: SafeStorage.Type =SafeStorage.GetCanonicalType[CODE[SmartsBody]];
SmartsHandle: TYPE = Thrush.SmartsHandle;	-- Handle
StateID: TYPE = Thrush.StateID;
StateInConv: TYPE = Thrush.StateInConv;


nextConv: ConversationHandle _ Thrush.epoch;
convClassIncrement: INT _ 100000000B; -- members of same class are identical in low 24 bits.


Alert: PUBLIC ENTRY PROC[
shhh: SHHH,
credentials: Credentials,
state: StateInConv_initiating,	 -- IF reserved, reason specifies reason for cancelling prev.
reason: Reason_wontSay, -- IF reserving, specifies reason for cancelling last one.
calledPartyID: PartyHandle,
urgency: CallUrgency,
alertKind: AlertKind,
newConv: BOOL_FALSE,
comment: ROPE
] RETURNS [ nb: NB, convID: ConversationHandle ] = {
ENABLE UNWIND => NULL;
cfRef: CFRef;
conv: ConversationData;
callingParty: PartyData;
calledParty: PartyData;
IF newConv OR credentials.convID=nullConvHandle THEN {
credentials.convID _ CreateConv[
partyID: credentials.partyID, convID: credentials.convID,
urgency: urgency, alertKind: alertKind].convID;
newConv_TRUE;
};
[conv, callingParty, cfRef, nb] _ Verify[credentials];
convID _ credentials.convID;
IF nb#success THEN RETURN;
IF cfRef#NIL AND cfRef.event.state=idle THEN {
nb _ AllIdle[conv];
IF nb#success THEN RETURN; };
conv.urgency _ urgency;
conv.alertKind _ alertKind;
IF calledPartyID # nullHandle OR state = initiating THEN {
calledParty _ DehandleParty[calledPartyID];
IF calledParty=NIL THEN {nb_noSuchParty2; RETURN}; 
TRUSTED {
SELECT calledParty.type FROM
trunk => IF calledParty.reservedBy # credentials.partyID THEN {nb_noSuchParty2; RETURN};
ENDCASE;
};
IF calledPartyID=credentials.partyID THEN {nb_narcissism; RETURN};
[] _ PostConvEvent[conv: conv, party: calledParty, state: pending, reason: wontSay,
comment: comment ];
};
[] _ PostConvEvent[conv: conv, party: callingParty, state: state, reason: reason,
smartsID: credentials.smartsID, comment: comment ];
};

Advance: PUBLIC ENTRY PROC[
shhh: SHHH,
credentials: Credentials,
state: StateInConv,
reason: Thrush.Reason,
comment: ROPE
] RETURNS [nb: NB] = {
ENABLE UNWIND => NULL;
conv: ConversationData; party: PartyData; cfRef: CFRef;
[conv, party, cfRef, nb] _ Verify[credentials];
IF nb#success THEN RETURN;
nb _ DoAdvance[credentials.smartsID, party, conv, cfRef, state, reason, comment];
};

DoAdvance: PUBLIC INTERNAL PROC [
smartsID: SmartsHandle,
party: PartyData,
conv: ConversationData,
cfRef: CFRef,
state: StateInConv,
reason: Thrush.Reason,
comment: ROPE
] RETURNS [nb: NB] = {
otherCfRef: CFRef;
otherParty: PartyData _ NIL;
otherState: Thrush.StateInConv;
OtherProc: Triples.ForeachProc _ SELECT state FROM
idle => IdleLast,
ringing, active => FindCaller,
ENDCASE => NIL;
IdleLast: Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => {
otherState _ idle;
SELECT r.event.state FROM
idle => RETURN[TRUE];
ENDCASE=>NULL;
IF otherParty#NIL THEN { otherParty_NIL; RETURN[FALSE]; };
otherParty_NARROW[trip.val];
otherCfRef _ r; };
ENDCASE; };
FindCaller: Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => {
otherState _ IF state = ringing THEN maybe ELSE active;
SELECT r.event.state FROM
initiating, maybe => NULL;
ENDCASE => RETURN[TRUE];
otherParty _ NARROW[trip.val];
otherCfRef _ r;
RETURN[FALSE]; };
ENDCASE; };
SELECT validTransitions[state][cfRef.event.state] FROM
no, cantReq => RETURN[invalidTransition];
noop => RETURN; -- success
valid => NULL;
ENDCASE =>
VoiceUtils.ProblemFR["%g, %g: Impossible", $System, NIL, TU.RefAddr[party], TU.RefAddr[cfRef]];
cfRef_PostConvEvent[
conv: conv, party: party, smartsID: smartsID,
state: state, reason: reason, comment: comment];
nb _ success;
IF OtherProc#NIL THEN Triples.Foreach[Triples.Any, conv, Triples.Any, OtherProc];
IF otherParty # NIL THEN {
SELECT validTransitions[otherState][otherCfRef.event.state] FROM
no => { VoiceUtils.Problem[IO.PutFR["%g, %g, %g: State mismatch among parties",
TU.RefAddr[party], TU.RefAddr[otherParty], TU.RefAddr[cfRef]], $System]; RETURN; };
cantReq, valid => NULL;
noop => RETURN;
ENDCASE => { VoiceUtils.ProblemFR["%g, %g: Impossible", $System, NIL, TU.RefAddr[otherParty], TU.RefAddr[cfRef]]; RETURN; };
[]_PostConvEvent[
conv: conv, party: otherParty, state: otherState, reason: reason, comment: comment];
};
};

OtherParty: PUBLIC ENTRY PROC[
shhh: SHHH_none,
credentials: Credentials
] RETURNS[
nb: NB,
partyID: PartyHandle_nullHandle,
description: Thrush.ROPE,
conference: BOOL_FALSE ] = {
conv: ConversationData;
[conv, , , nb] _ Verify[credentials];
IF nb=stateMismatch THEN nb_success;
IF nb#success THEN RETURN;
[partyID, conference] _ FindOtherParty[credentials.partyID, conv];
description _ SELECT TRUE FROM
conference => "Group of parties",
partyID#nullHandle => ThPartyPrivate.DoDescribeParty[partyID],
ENDCASE => NIL;
};
FindOtherParty: PUBLIC PROC[myPartyID: PartyHandle, conv: ConversationData]
RETURNS[partyID: PartyHandle_nullHandle, conference: BOOL_FALSE ] = {
FOR i: NAT IN [1..conv.currentStateID] DO
ce: ConvEvent = conv.log[i];
IF myPartyID=ce.credentials.partyID THEN LOOP;
partyID _ ce.credentials.partyID;
IF conv.numParties>2 THEN { conference_TRUE; RETURN; };
EXIT; ENDLOOP;
};
CreateConversation: PUBLIC ENTRY PROC[
shhh: SHHH,
credentials: Credentials,
urgency: CallUrgency,
alertKind: AlertKind
] RETURNS [ nb: NB, convID: ConversationHandle ] = {
ENABLE UNWIND => NULL;
conv: ConversationData =
CreateConv[credentials.partyID, Thrush.nullConvHandle, urgency, alertKind];
RETURN[success, conv.convID];
};

CreateConv: INTERNAL PROC[
partyID: PartyHandle,
convID: ConversationHandle, -- non-null if want a related converation.
urgency: CallUrgency_normal,
alertKind: AlertKind_standard]
RETURNS [ conv: ConversationData_NIL ] = TRUSTED {
conv _ NEW[ThPartyPrivate.ConversationBody _ [
creatorPartyID: partyID,
timeOfID: BasicTime.Now[],
urgency: urgency, alertKind: alertKind,
log: NEW[Thrush.EventSequenceBody[ThPartyPrivate.logSizeIncrement]]]];
conv.keyTable _ NEW[Lark.KeyTableBody[20B]];
[]_EnterKey[conv, DESFace.GetRandomKey[]]; -- = this conv's key, will be key index 1.
conv.convID _ EnhandleConv[conv, convID];
};

DestroyConversation: PUBLIC INTERNAL PROC[ conv: ConversationData ] = {
IF conv.numParties#0 THEN VoiceUtils.ProblemFR["%g: Conversation still has parties", $System, NIL, TU.RefAddr[conv]];
KillHandleConv[conv.convID];
};

MergeConversations: PUBLIC PROC[
shhh: SHHH,
credentials: Credentials, -- of surviving conversation
otherStateID: StateID, -- of dissolving conversation
otherConvID: ConversationHandle
] RETURNS [ nb: NB ] = {NULL};

SetIntervals: PUBLIC ENTRY PROC[
shhh: SHHH _ none,
credentials: Credentials,
intervalSpecs: Thrush.IntervalSpecs
] RETURNS [ nb: NB ] = {
ENABLE UNWIND => NULL;
conv: ConversationData;
party: PartyData;
cfRef: CFRef;
WachetAuf: INTERNAL Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => { party_NARROW[trip.val]; ThPartyPrivate.Supervise[party]; }; ENDCASE; };
[conv, party, cfRef, nb] _ Verify[credentials];
IF nb#success THEN RETURN;
IF cfRef=NIL OR cfRef.event=NIL OR cfRef.event.state#active THEN RETURN[convNotActive];
[] _ PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , ,intervalSpecs];
FOR iSL: Thrush.IntervalSpecs _ intervalSpecs, iSL.rest WHILE iSL#NIL DO
iS: Thrush.IntervalSpec = iSL.first;
IF ~ThNet.pd.encryptVoice THEN iS.keyIndex_0;
IF iS.type=request THEN iS.intID.stateID _ conv.currentStateID;
ENDLOOP;
Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf];
};

SetProse: PUBLIC ENTRY PROC[
shhh: SHHH _ none,
credentials: Credentials,
proseSpecs: Thrush.ProseSpecs
] RETURNS [ nb: NB ] = {
ENABLE UNWIND => NULL;
conv: ConversationData;
party: PartyData;
cfRef: CFRef;
WachetAuf: INTERNAL Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => { party_NARROW[trip.val]; ThPartyPrivate.Supervise[party]; }; ENDCASE; };
[conv, party, cfRef, nb] _ Verify[credentials];
IF nb#success THEN RETURN;
IF cfRef=NIL OR cfRef.event=NIL OR cfRef.event.state#active THEN RETURN[convNotActive];
[] _ PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , , , proseSpecs];
FOR pSL: Thrush.ProseSpecs _ proseSpecs, pSL.rest WHILE pSL#NIL DO
pS: Thrush.ProseSpec = pSL.first;
IF pS.type=request THEN pS.intID.stateID _ conv.currentStateID;
ENDLOOP;
Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf];
};

DescribeInterval: PUBLIC PROC[
shhh: SHHH _ none,
credentials: Credentials,
targetInterval: Thrush.IntervalSpec,
minSilence: Thrush.VoiceTime _ 1 -- Smallest silent interval that will be considered silence.
]
RETURNS [ nb: NB, exists: BOOL_FALSE, intervals: Thrush.IntervalSpecs_NIL ] = {
conv: ConversationData;
party: PartyData;
cfRef: CFRef;
[conv, party, cfRef, nb] _ VerifyEnt[credentials];
IF nb#success AND nb#stateMismatch THEN RETURN;
[exists, intervals] _ BluejayUtils.DescribeInterval[targetInterval.tune, targetInterval.interval, minSilence];
};

RegisterKey: PUBLIC ENTRY PROC [
shh: SHHH _ none,
credentials: Credentials,
key: Thrush.EncryptionKey ]
 RETURNS [ nb: NB, keyIndex: [0..17B] _ 0 ] = {
ENABLE UNWIND => NULL;
conv: ConversationData;
cfRef: CFRef;
[conv, , cfRef, nb] _ Verify[credentials];
IF nb=success OR nb=stateMismatch THEN keyIndex _ EnterKey[conv, key].keyIndex;
};

SetSubject: PUBLIC ENTRY PROC[ shh: SHHH, convID: ConversationHandle, subject: ROPE ] = {
ENABLE UNWIND => NULL;
conv: ConversationData = DehandleConv[convID];
IF conv=NIL THEN RETURN ELSE conv.subject _ subject; };

GetSubject: PUBLIC ENTRY PROC[ shh: SHHH, convID: ConversationHandle ] RETURNS [subject: ROPE] = {
ENABLE UNWIND => NULL;
conv: ConversationData = DehandleConv[convID];
RETURN[IF conv=NIL THEN NIL ELSE conv.subject]; };

ConversationsForParty: PUBLIC ENTRY PROC [ shh: SHHH_none, partyID: PartyHandle ] = {
party: PartyData _ DehandleParty[partyID];
ReallyWachetAuf: INTERNAL Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => {
conv: ConversationData _ NARROW[trip.obj];
r.lastNotedID _ 0;
ThPartyPrivate.Supervise[party];
};
ENDCASE;
};
IF party#NIL THEN Triples.Foreach[Triples.Any, Triples.Any, party, ReallyWachetAuf];
};


VerifyEnt: PUBLIC ENTRY PROC[ credentials: Credentials 
] RETURNS [
conv: ConversationData,
party: PartyData,
cfRef: CFRef,
nb: Thrush.NB ] = {
[conv, party, cfRef, nb] _ Verify[credentials];
};

Verify: PUBLIC INTERNAL PROC[ credentials: Credentials 
] RETURNS [
conv: ConversationData,
party: PartyData,
cfRef: CFRef,
nb: Thrush.NB ] = {
smarts: SmartsData _ DehandleSmarts[credentials.smartsID];
conv_DehandleConv[credentials.convID];
party_DehandleParty[credentials.partyID];
nb _ SELECT NIL[REF ANY] FROM
smarts=> noSuchSmarts,
party=> noSuchParty,
conv=> noSuchConv,
ENDCASE=> success;
IF nb=success AND party#NIL THEN
IF party.partyFailed THEN nb_noSuchParty
ELSE IF party.numEnabled=0 THEN nb _ partyNotEnabled;
IF conv#NIL AND party#NIL THEN
cfRef _ NARROW[Triples.Select[--cfRef--, conv, party]];
IF nb=success THEN
IF cfRef#NIL THEN { IF credentials.stateID<cfRef.lastPostedID THEN nb_stateMismatch }
ELSE IF credentials.stateID#conv.currentStateID THEN nb_stateMismatch;
IF cfRef=NIL THEN SELECT nb FROM
success => IF credentials.stateID#0 THEN nb _ notInConv;
stateMismatch => nb _ notInConv;
ENDCASE;
};

PostConvEvent: INTERNAL PROC[
conv: ConversationData,
party: PartyData,
smartsID: SmartsHandle _ nullHandle,
state: Thrush.StateInConv,
reason: Thrush.Reason_wontSay,
comment: Thrush.ROPE_NIL,
intervalSpecs: Thrush.IntervalSpecs_NIL,
proseSpecs: Thrush.ProseSpecs_NIL
] RETURNS [cfRef: CFRef_NIL] = {
event, lastEvent: ConvEvent;
lastState: Thrush.StateInConv;
conv.currentStateID _ conv.currentStateID+1;
conv.timeOfID _ BasicTime.Now[];
TRUSTED {
event _ NEW[Thrush.ConvEventBody _ [
credentials: [ H[party], smartsID, conv.convID, conv.currentStateID ],
time: conv.timeOfID,
state: state, reason: reason, urgency: conv.urgency, alertKind: conv.alertKind,
address: (SELECT party.type FROM
trunk => party.outgoing, ENDCASE => NIL),
comment: comment,
intervalSpecs: intervalSpecs,
proseSpecs: proseSpecs
]];
};
InsertEvent[conv, event];
IF conv.newKeyTable THEN event.keyTable _ conv.keyTable;
conv.newKeyTable_FALSE;
cfRef _ NARROW[Triples.Select[--cfRef--, conv, party]];
IF cfRef = NIL THEN {
cfRef _ NEW[ThPartyPrivate.CFRec _ [
event: NIL,
sockID: NewId[] ]];
IF party.type = $service THEN {
pa: Pup.Address;
socket: REF PupSocket.Socket = NEW[PupSocket.Socket _ PupSocket.Create[getTimeout: 100]]; -- See BluejaySmartsImpl
cfRef.socket _ RefID.Seal[socket];
pa _ PupSocket.GetLocalAddress[socket^];
cfRef.sockID _ LOOPHOLE[pa.socket];
};
Triples.Make[cfRef, conv, party];
conv.numParties _ conv.numParties+1;
party.numConvs _ party.numConvs+1; };
SELECT state FROM
initiating, active, canActivate => IF cfRef.voiceSmartsID=nullHandle THEN {
smarts: SmartsData _ DehandleSmarts[smartsID];
IF smarts#NIL THEN WITH smarts.properties SELECT FROM
backstop, supervisor => NULL; -- << ERROR? >>
voiceTerminal => cfRef.voiceSmartsID _ smartsID;
manager => {
smarts _ NARROW[Triples.Select[$AdjacentTerminal, party, -- smarts --]];
IF smarts # NIL THEN cfRef.voiceSmartsID _ H[smarts]; };
ENDCASE; };
ENDCASE;
lastEvent _ cfRef.event;
lastState _ IF lastEvent=NIL THEN idle ELSE lastEvent.state;
cfRef.event _ event;
cfRef.lastPostedID _ conv.currentStateID;
IF state=active THEN {
IF lastState#active THEN
IF party.partyActive THEN event.state _ canActivate
ELSE [] _ Activate[conv, cfRef, party] } --<<Should complain if result is false, here?>>
ELSE IF lastState = active THEN {
party.partyActive _ FALSE; conv.numActive _ conv.numActive - 1; };
ThPartyPrivate.Supervise[party];
};

Activate: INTERNAL PROC[conv: ConversationData, myCfRef: CFRef, myParty: PartyData]
RETURNS[isSpec: BOOL_FALSE] = {
sockets: ARRAY[0..2) OF Lark.VoiceSocket;
parties: ARRAY[0..2) OF PartyData;
specIndex: NAT_0;

GetOneActive: INTERNAL Triples.ForeachProc = {
cfRef: CFRef;
smarts: SmartsData;
sockID: Pup.Socket;
party: PartyData = NARROW[trip.val];
IF specIndex>=2 THEN RETURN[FALSE];
WITH trip.att SELECT FROM r: CFRef => cfRef _ r; ENDCASE=> RETURN[TRUE];
IF cfRef.voiceSmartsID = nullHandle THEN RETURN[TRUE];
smarts _ DehandleSmarts[cfRef.voiceSmartsID];
IF smarts=NIL THEN RETURN--[FALSE]--; -- fail back
sockID _LOOPHOLE[
IF party=myParty AND party.type=$service THEN cfRef.socket ELSE cfRef.sockID];
WITH smarts.properties SELECT FROM
voiceTerminal => sockets[specIndex] _ [ [ netAddress.net], [netAddress.host], sockID ];
ENDCASE => VoiceUtils.Problem["Non-voice terminal trying to be voice smarts", $System];
parties[specIndex] _ party;
specIndex _ specIndex+1; };

myParty.partyActive_TRUE;
conv.numActive _ conv.numActive + 1;
Triples.Foreach[Triples.Any, conv, Triples.Any, GetOneActive];
IF specIndex#2 THEN RETURN[FALSE];
SELECT myParty FROM
=parties[0] => specIndex _ 0;
=parties[1] => specIndex _ 1;
ENDCASE => VoiceUtils.Problem["Impossible", $System];
myCfRef.event.spec _ NEW[Lark.ConnectionSpecRec _ [
protocol: interactive,
encoding: muLaw,
sampleRate: 8000,
packetSize: 160, -- sample bytes, that is.
buffer: out1,
keyIndex: IF ThNet.pd.encryptVoice THEN 1 ELSE 0,
localSocket: sockets[specIndex],
remoteSocket: sockets[1-specIndex],
blankA: 0
]];
RETURN[TRUE];
};

GetEvent: PUBLIC INTERNAL PROC[conv: ConversationData, stateID: Thrush.StateID]
RETURNS [ Thrush.ConvEvent_NIL ] = {
IF conv=NIL THEN VoiceUtils.ProblemFR["%g: No such conversation", $System, NIL, TU.RefAddr[conv]]
ELSE IF stateID=0 THEN RETURN[NIL]
ELSE IF stateID>=conv.log.size THEN VoiceUtils.Problem["State ID out of range", $System]
ELSE RETURN[conv.log[stateID]]; };

InsertEvent: INTERNAL PROC[conv: ConversationData, entry: ConvEvent] = {
IF conv.currentStateID >= conv.log.size THEN {
oldLog: Thrush.EventSequence = conv.log;
newLog: Thrush.EventSequence =
NEW[Thrush.EventSequenceBody[oldLog.size+ThPartyPrivate.logSizeIncrement]];
FOR i: NAT IN [0..oldLog.size) DO newLog[i]_oldLog[i]; ENDLOOP;
conv.log _ newLog; };
conv.log[conv.currentStateID] _ entry; };

NewId: PROC RETURNS [ LONG CARDINAL ] = TRUSTED {
idKey: DESFace.Key _ DESFace.GetRandomKey[]; -- connection specs.
keys: LONG POINTER TO ARRAY[0..2) OF LONG CARDINAL _
LOOPHOLE[LONG[@idKey]];
RETURN[keys[0]]; };

AllIdle: INTERNAL PROC[conv: ConversationData] RETURNS [nb: Thrush.NB] = {
allAreIdle: BOOL_TRUE;
OneIdle: Triples.ForeachProc = {
WITH trip.att SELECT FROM
r: CFRef => IF r.event.state#idle THEN {
allAreIdle_FALSE; RETURN[FALSE]; };
ENDCASE;};
Triples.Foreach[Triples.Any, conv, Triples.Any, OneIdle];
RETURN[IF allAreIdle THEN success ELSE invalidTransition]; };


convTable: RedBlackTree.Table = RedBlackTree.Create[ConvGet, ConvCompare];
ConvTableEntry: TYPE = REF ConvTableRecord;
ConvTableRecord: TYPE = RECORD [
convID: ConversationHandle,
conv: ConversationData
];
cTE: ConvTableEntry _ NEW[ConvTableRecord _ [nullConvHandle, NIL]];

DehandleConv: PUBLIC INTERNAL PROC[ convID: ConversationHandle]
RETURNS [conv: ConversationData_NIL ] = {
convEntry: ConvTableEntry;
cTE.convID _ convID;
convEntry _ NARROW[convTable.Lookup[cTE]];
RETURN[IF convEntry#NIL THEN convEntry.conv ELSE NIL];
};

EnhandleConv: INTERNAL PROC[ conv: ConversationData, convClassID: ConversationHandle ]
RETURNS [ convID: ConversationHandle_nextConv ] = {
entry: ConvTableEntry;
IF convClassID#nullConvHandle THEN
convID _ BasicTime.Update[convClassID, convClassIncrement]
ELSE nextConv _ BasicTime.Update[nextConv, 1];
cTE.convID _ convID;
IF convTable.Lookup[cTE]#NIL THEN {
IF convClassID#nullConvHandle THEN RETURN EnhandleConv[conv,convID];
VoiceUtils.Problem["Struct. broken", $System]; RETURN[Thrush.nullConvHandle]; };
entry _ NEW[ConvTableRecord _ [convID, conv]];
convTable.Insert[entry, entry];
};

KillHandleConv: INTERNAL PROC[ convID: ConversationHandle ] = {
cTE.convID _ convID;
[]_convTable.Delete[cTE];
};

ConvCompare: RedBlackTree.Compare = {
keyRec: ConvTableEntry = NARROW[k];
convRec: ConvTableEntry = NARROW[data];
c1: LONG CARDINAL = LOOPHOLE[keyRec.convID, LONG CARDINAL];
c2: LONG CARDINAL = LOOPHOLE[convRec.convID, LONG CARDINAL];
RETURN[SELECT c1 FROM
< c2 => less,
> c2 => greater,
ENDCASE => equal
];
};

ConvGet: RedBlackTree.GetKey = { RETURN[data]; };

DehandleParty: PUBLIC PROC[partyID: Thrush.PartyHandle, insist: BOOLEAN_FALSE]
RETURNS[party: PartyData] = TRUSTED {
RETURN[LOOPHOLE[Thrush.Dehandle[partyID, RTPartyType, insist]]];
};

DehandleSmarts: PUBLIC PROC[smartsID: Thrush.SmartsHandle, insist: BOOLEAN_FALSE]
RETURNS[smarts: SmartsData] = TRUSTED {
RETURN[LOOPHOLE[Thrush.Dehandle[smartsID, RTSmartsType, insist]]];
};

EKResults: TYPE = { new, duplicate, disagreement, full };

EnterKey: INTERNAL PROC[conv: ConversationData,
key: Thrush.EncryptionKey]
RETURNS [ keyIndex: [0..17B], results: EKResults] = {
IF key=Thrush.nullKey THEN RETURN[0, duplicate];
FOR keyIndex IN [1..17B] DO
IF conv.keyTable[keyIndex]=key THEN RETURN[keyIndex, duplicate]
ELSE IF conv.keyTable[keyIndex][0].b=0 AND conv.keyTable[keyIndex] = Thrush.nullKey THEN EXIT;
REPEAT FINISHED => RETURN[0, full]; ENDLOOP;
conv.keyTable[keyIndex] _ key;
conv.newKeyTable _ TRUE;
RETURN[keyIndex, new];
};


Validity: TYPE = {
noop,			-- Already in this state; no transition action required
valid,			-- This transition is all right if other tests check out.
no,			-- This transition is not allowed, nohow.
cantReq		-- This transition may not be requested by the Party-level client.
};

validTransitions: ARRAY Thrush.StateInConv OF ARRAY Thrush.StateInConv OF Validity = [
[ noop, valid, valid, valid, valid, valid, valid, valid, valid, valid, no ],	-- idle
[ valid, noop, valid, no, no, no, no, no, no, no, no ],	-- reserved
[ valid, valid, noop, no, no, no, no, no, no, no, no ],	-- parsing
[ valid, valid, valid, noop, no, no, no, no, no, no, no ],	-- initiating
[ cantReq, cantReq, cantReq, no, noop, no, no, no, no, no, no ],	-- pending
[ no, no, no, cantReq, no, noop, no, no, no, no, no ],	-- maybe
[ no, no, no, no, valid, no, noop, no, no, no, no ],	-- ringing
[ cantReq, cantReq, cantReq, cantReq, cantReq, cantReq, cantReq, noop, no, cantReq, no ], -- canAct
[ valid, valid, valid, cantReq, valid, cantReq, valid, valid, noop, valid, no ],	-- active
[ no, no, no, no, no, no, no, valid, valid, noop, no ],	-- inactive
[ no, no, no, no, no, no, no, no, no, no, no ]	-- any
];

}.
��x��ThPartyOpsImpl.mesa
Copyright c 1985, 1986 by Xerox Corporation.  All rights reserved.
Last modified by D. Swinehart, May 22, 1986 4:23:06 pm PDT
Copies	
Conversation ID stuff
Implementation of ThParty
<< Not satisfied with this, yet.  If trunkParty info has been changed, don't make the call.>>
<< State must be initiating if calledPartyID#nullHandle.  Nobody's checking. >>
If there is exactly one remaining party that is not idle . . . now it will be.
If callee is answering or threatening to answer, advance caller, too.
Must match the other, valid, one.
If another party to be changed was found; do it.


Wake all parties to conversation, now. Potential new key.
Wake all parties to conversation, now. Potential new key.
stateID: StateID,
partyID: PartyHandle,
smartsID: SmartsHandle,
convID: ConversationHandle,
Don't care about stateMismatch here.
Utilities
Validate parameters: existence, in conversation, state match; produce dehandled values.
Validation for smarts is only partial; verifies that it's a smarts.
In Cedar 6.1 and thereafter, socket numbers cannot be invented but must be assigned by the implementation of PupSocket.  For this version of Thrush/Bluejay, only Bluejay uses a Cedar socket, and only Bluejay provide parties of type $service, so only it needs to be dealt with, and we can tell if that's what we have by looking at the type.  We create the PupSocket.Socket here (without specifying the remote address), jam it into a RefID, and store the RefID in cfRef (RefID because theoretically Jay may not be local, and because we may have to pass it as a long cardinal into Bluejay somewhere).  We record the assigned socket ID in cfRef.sockID.  BluejaySmarts knows how to take all this apart.  We will destroy the RefID when the cfRef.
Ferret out voice smarts, if need be.
Maintain active party counts, generate connection specifications.
Generate a Lark.ConnectionSpec for this conversant.
See earlier comments about Cedar 6.1 communications accommodation.
Complains unless all parties in conversation are presently idle.
Conversation Handle management
NB. Above works as long as procedures below remain locked by SINGLE monitor.
At present, called only from Verify
Called only from CreateConv.
Called only from DestroyConversation
Key Table management

Presently returns full if table is full; user should hang up and restart.
Move-to-front would work nicely here!!
Initialization

Columns represent present state: rows represent proposed new states

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, DehandleConv, EnhandleConv, KillHandleConv, ConvCompare, ConvGet, EnterKey
Swinehart, May 17, 1986 4:06:14 pm PDT
Cedar 6.1
changes to: DIRECTORY, GetOneActive

ÊÅ��˜�šœ™Icodešœ
Ïmœ7™BJšœ:™:—J˜�šÏk	˜	Jšœ
žœ˜(Jšœžœ˜$Jšžœ˜Jšœžœ2˜<J˜
Jšœžœ˜Jšœ
žœ%˜4Jšœžœ
˜Jšœžœ˜-Jšœ
žœ˜ šœžœ˜Jšœ•žœžœI˜è—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šœ<žœ#˜c—Jšœžœ˜#Jšœžœ#˜9Jšœžœ˜5Jšœ
žœ˜'Jšžœžœžœžœžœžœžœ˜JJšžœžœ
žœ˜Jšœžœ˜ J˜BJ˜3šœžœÏc˜7Jšœ=žœ
˜N—JšœžœŸ˜;Jšœ
žœŸ	˜1Jšœžœ˜Jšžœžœ
žœ˜Jšžœžœ
žœ˜JšœžœŸ˜9šœžœŸ˜=Jšœ=žœ˜O—JšœžœŸ	˜3Jšœ	žœ˜Jšœ
žœ˜'J˜�—™J˜�J˜,J˜\J˜�—šœ™J˜�šÏnœžœž
œ˜Jšœžœ˜J˜Jšœ Ÿ<˜\JšœŸ:˜RJ˜J˜Jšœ˜Jšœ	žœžœ˜Jšœ	ž˜
Jšœžœžœ"˜4Jšžœžœžœ˜Jšœ
˜
Jšœ˜Jšœ˜Jšœ˜šžœ	žœ#žœ˜6šœ ˜ Jšœ9˜9Jšœ/˜/—Jšœžœ˜
J˜—J˜6J˜Jšžœžœžœ˜šžœžœžœžœ˜.Jšœ˜Jšžœžœžœ˜—J˜J˜šžœžœžœ˜:J˜+Jšžœ
žœžœžœ˜3™]Jšž	˜	šžœž˜Jšœ	žœEžœ˜XJšžœ˜J˜——Jšžœ#žœžœ˜BšœS˜SJšœ˜—Jšœ˜J™O—šœQ˜QJšœ3˜3—J˜—J˜�š œžœžœžœ˜Jšœžœ˜J˜J˜J˜Jšœ	ž˜
šœžœžœ˜Jšžœžœžœ˜J˜7J˜/Jšžœžœžœ˜JšœQ˜QJšœ˜——J˜�š 	œžœžœžœ˜!J˜Jšœ˜Jšœ˜J˜
J˜J˜Jšœ	ž˜
Jšœžœžœ˜Jšœ˜Jšœžœ˜J˜šœ!žœž˜2J˜J˜Jšžœžœ˜—J˜!™Nšžœ
žœž˜˜
J˜šžœž˜Jšœžœžœ˜Jšžœžœ˜—Jšžœžœžœžœžœžœ˜:Jšœžœ˜Jšœ˜—Jšžœ˜——J˜#™Ešžœ
žœž˜˜
Jšœ
žœžœžœ˜7šžœž˜Jšœžœ˜Jšžœžœžœ˜—Jšœ
žœ˜Jšœ˜Jšžœžœ˜—Jšžœ˜——šžœ,ž˜6Jšœžœ˜)JšœžœŸ
˜Jšœ	žœ˜šžœ˜
Jšœ4žœžœžœ˜_——˜J˜-Jšœ0˜0—Jšœ
˜
Jšžœžœžœ<˜Qšžœžœžœ˜šžœ6ž˜@šœO˜OJšœIžœ˜SJšœŸ ™!—Jšœžœ˜Jšœžœ˜Jš
žœ:žœžœžœžœ˜|—˜J™0JšœT˜T—Jšœ˜—Jšœ˜—J˜�š 
œžœžœžœ˜Jšœžœ˜J˜šœžœ˜
Jšœžœ˜Jšœ ˜ Jšœžœ˜Jšœžœžœ˜—J˜J˜%Jšžœžœ˜$Jšžœžœžœ˜J˜Bšœžœžœž˜Jšœ!˜!J˜>Jšžœžœ˜—J˜J™�—š œžœžœ0˜KJšžœ.žœžœ˜Ešžœžœžœž˜)J˜Jšžœ"žœžœ˜.Jšœ!˜!Jšžœžœžœžœ˜7Jšžœžœ˜—J˜J™�—š œžœžœžœ˜&Jšœžœ˜J˜Jšœ˜Jšœ˜Jšœžœ+˜4Jšžœžœžœ˜šœ˜JšœK˜K—Jšžœ˜Jšœ˜J˜�—š 
œžœžœ˜Jšœ˜JšœŸ*˜FJšœ˜Jšœ˜Jšžœžœžœ˜2šœžœ$˜.Jšœ˜J˜J˜'Jšœžœ>˜F—Jšœžœ˜,J˜UJšœ)˜)Jšœ˜—J˜�š œžœžœžœ˜GJšžœžœ\˜uJšœ˜Jšœ˜—J˜�š œžœ˜ Jšœžœ˜JšœŸ˜6JšœŸ˜4J˜Jšœžœžœžœ˜J˜�—š œžœ˜ Jšœžœ˜Jšœ˜šœ#˜#Jšœžœžœ˜—Jšžœžœžœ˜J˜J˜J˜
šœžœ˜+šžœ
žœž˜Jšœžœ0žœ˜U——Jšœ/˜/Jšžœžœžœ˜Jšžœžœžœ
žœžœžœžœ˜WJšœ[˜[šžœ5žœžœž˜HJ˜$Jšžœžœ˜-Jšžœžœ(˜?Jšžœ˜—˜;J™9—J˜J˜�—š œžœ˜Jšœžœ˜Jšœ˜šœ˜Jšœžœžœ˜—Jšžœžœžœ˜J˜J˜J˜
šœžœ˜+šžœ
žœž˜Jšœžœ0žœ˜U——Jšœ/˜/Jšžœžœžœ˜Jšžœžœžœ
žœžœžœžœ˜WJšœ[˜[šžœ/žœžœž˜BJšœ!˜!Jšžœžœ(˜?Jšžœ˜—˜;J™9—J˜J˜�—š œžœžœ˜Jšœžœ˜Jšœ˜J˜$J˜]Jšœ˜Jš
žœžœ
žœžœ"žœ˜OJ˜J˜J˜
Jšœ2˜2Jšžœžœžœžœ˜/J˜nJ˜J˜�—š œžœ˜ Jšœžœ˜Jšœ˜J™Jšœ™J™J™J˜Jšœžœžœ˜/Jšžœžœžœ˜J˜J˜
Jšœ*˜*J™$Jšžœžœžœ)˜OJšœ˜—J˜�š 
œžœžœžœžœ'žœ˜YJšžœžœžœ˜J˜.Jš
žœžœžœžœžœ˜7—J˜�š
 
œžœžœžœžœ˜bJšžœžœžœ˜J˜.Jšžœžœžœžœžœžœ˜2—J˜�š
 œžœžœžœžœ!˜UJ˜*šœžœ˜1šžœ
žœž˜šœ
˜
Jšœžœ˜*Jšœ˜Jšœ ˜ Jšœ˜—Jšžœ˜Jšœ˜——JšžœžœžœC˜TJ˜—J˜�—™	J˜�š 	œžœžœ˜7šœžœ˜J˜J˜J˜
Jšœžœ˜—J˜/J˜J˜�—š œžœžœ˜7šœžœ˜J˜J˜J˜
Jšœžœ˜—J™WJ™CJšœ:˜:Jšœ&˜&Jšœ)˜)šœžœžœž˜J˜J˜J˜Jšžœ˜—šžœžœžœž˜ Jšžœžœ˜(Jšœ5˜5—š	žœžœžœžœž˜JšœžœŸ	œ˜7—šžœž˜Jš
žœžœžœžœ(žœ˜UJšžœ)žœ˜F—š	žœžœžœžœž˜ Jšœžœžœ˜8J˜ Jšžœ˜—J˜J˜�—š 
œž
œ˜J˜J˜J˜$J˜J˜Jšœž	˜Jšœ$ž˜(Jšœž˜!Jšœžœžœ˜ J˜J˜J˜,J˜ Jšžœ˜	šœžœ˜$J˜FJ˜J˜Ošœ
žœž˜ Jšœžœžœ˜)—J˜J˜Jšœ˜J˜J˜—J˜Jšžœžœ ˜8Jšœžœ˜JšœžœŸ	œ˜7šžœ	žœžœ˜šœžœ˜$Jšœžœ˜J˜—JšÏtœä¡™æšžœžœ˜J˜Jš	œžœžœ8ŸÐctŸ¢˜tJ˜"Jšœ(˜(Jšœžœ˜#J˜—Jšœ!˜!Jšœ$˜$Jšœ%˜%—J™$šžœž˜šœ#žœ žœ˜KJšœ.˜.šžœžœžœžœžœž˜5JšœžœŸ˜-J˜0˜Jšœ	žœ*Ÿœ˜HJšžœ
žœžœ$˜8—Jšžœ˜——Jšžœ˜—J™AJ˜Jš	œžœžœžœžœ˜<Jšœ˜J˜)šžœžœ˜šžœž˜Jšžœžœ˜3Jšžœ%Ÿ/˜X——šžœžœžœ˜!Jšœžœ)˜B—J˜ Jšœ˜J˜�—š œžœžœ<˜SJ™3Jšžœ	žœžœ˜Jšœ	žœžœ˜)Jšœ	žœžœ˜"Jšœžœ˜J˜�šœžœ˜.J˜
J˜J˜Jšœžœ˜$Jšžœžœžœžœ˜#Jšžœ
žœžœžœžœžœ˜HJšžœ"žœžœžœ˜6Jšœ-˜-Jš
žœžœžœžŸœŸ˜2šœžœ˜Jšžœžœžœžœ˜NJš¡œB¡™D—šžœžœž˜"JšœW˜WJšžœP˜W—Jšœ˜J˜—J˜�Jšœžœ˜Jšœ$˜$J˜>Jšžœ
žœžœžœ˜"šžœ	ž˜Jšœ˜Jšœ˜Jšžœ.˜5—šœžœ˜3J˜J˜Jšœ˜JšœŸ˜*J˜
Jšœ
žœžœžœ˜1J˜ J˜#J˜	Jšœ˜—Jšžœžœ˜
J˜J˜�—š œžœžœžœ1˜OJšžœžœ˜$JšžœžœžœQ˜aJšžœžœžœžœ˜"Jšžœžœ5˜XJšžœžœ˜"—J˜�š œžœžœ.˜Hšžœ&žœ˜.J˜(šœ˜JšžœH˜K—Jš
žœžœžœžœžœ˜?J˜—J˜)J˜�—š œžœžœžœžœžœ˜1Jšœ-Ÿ˜Ašœžœžœžœžœžœžœžœ˜4Jšžœžœ
˜—Jšžœ
˜J˜�—š
 œžœžœžœ
žœ˜JJšœžœžœ˜˜ J™@šžœ
žœž˜šœžœžœ˜(Jšœžœžœžœ˜#—Jšžœ˜
——J˜9šžœžœžœ	žœ˜=J˜�———™J˜�J˜JJšœžœžœ˜+šœžœžœ˜ Jšœ˜J˜J˜—šœžœ$žœ˜CJ™L—J˜�š œžœžœžœ˜?J™#Jšžœžœ˜)Jšœ˜Jšœ˜Jšœžœ˜*Jšžœžœžœžœžœžœ˜6J˜—J˜�š œžœžœ;˜VJ™Jšžœ,˜3J˜šžœž˜"Jšœ:˜:—Jšžœ*˜.Jšœ˜šžœžœžœ˜#Jšžœžœžœ˜DJšœ/žœ˜P—Jšœžœ#˜.Jšœ˜J˜—J˜�š œžœžœ"˜?J™$Jšœ˜Jšœ˜J˜J˜�—š œ˜%Jšœžœ˜#Jšœžœ˜'Jšœžœžœžœžœžœ˜;Jšœžœžœžœžœžœ˜<šžœžœž˜J˜
J˜Jšžœ	˜Jšœ˜—J˜J˜�—š œžœ
˜1J˜�—š
 
œžœžœ&žœžœ˜NJšžœžœ˜%Jšžœžœ1˜@J˜J˜�—š
 œžœžœ(žœžœ˜QJšžœžœ˜'Jšžœžœ3˜BJ˜J˜�——J™™�J˜9J˜�š œžœžœ˜/Jšœ˜Jšžœ.˜5J™IJ™&Jšžœžœžœ˜0šžœ
žœ
ž˜Jšžœžœžœ˜?Jš
žœžœ žœ*žœžœ˜^Jšžœžœžœžœ˜,—Jšœ˜Jšœžœ˜Jšžœ˜J˜J˜�—J˜�—šœ™J™�šœ
žœ˜JšœŸ7˜?Jšœ	Ÿ9˜BJšœŸ)˜/Jšœ	ŸB˜KJ˜—J˜�Jš	œžœžœžœžœ
˜VšŸC™CJšžMÐck˜TJšž7£˜CJšž7£˜BJšž;£
˜HJšžA£
˜KJšž7£˜?Jšž5£
˜?JšžZ£	˜cJšžQ£	˜ZJšž8£˜CJšž/£˜5J˜J˜�——J˜K™�™'KšœÏr)™5—K™�™'K™4Kšœ¤ ™¬—™&K™	Kšœ¤™#—K™�—�…—����XÈ��~��