ThPartyOpsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, July 21, 1985 10:40:59 pm PDT
DIRECTORY
BluejayUtils USING [ DescribeInterval ],
DESFace USING [ GetRandomKey, Key ],
IO,
Lark USING [ ConnectionSpecRec, KeyTableBody, VoiceSocket ],
Log USING [ Problem, ProblemFR ],
RedBlackTree,
PupTypes USING [ PupSocketID ],
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 ]
;
ThPartyOpsImpl: CEDAR MONITOR LOCKS root
IMPORTS
BluejayUtils,
DESFace,
root: ThPartyMonitorImpl,
SafeStorage,
BasicTime,
IO,
Log,
RedBlackTree,
ThNet,
ThPartyPrivate,
Thrush,
Triples,
TU
EXPORTS ThParty, ThPartyPrivate
SHARES ThPartyMonitorImpl = {
Copies 
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;
Conversation ID stuff
nextConv: ConversationHandle ← Thrush.epoch;
convClassIncrement: INT ← 100000000B; -- members of same class are identical in low 24 bits.
Implementation of ThParty
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: BOOLFALSE,
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};
<< Not satisfied with this, yet. If trunkParty info has been changed, don't make the call.>>
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 ];
};
<< State must be initiating if calledPartyID#nullHandle. Nobody's checking. >>
[] ← 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 = {
If there is exactly one remaining party that is not idle . . . now it will be.
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 = {
If callee is answering or threatening to answer, advance caller, too.
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 =>
Log.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 => { Log.Problem[IO.PutFR["%g, %g, %g: State mismatch among parties",
TU.RefAddr[party], TU.RefAddr[otherParty], TU.RefAddr[cfRef]], $System]; RETURN; };
Must match the other, valid, one.
cantReq, valid => NULL;
noop => RETURN;
ENDCASE => { Log.ProblemFR["%g, %g: Impossible", $System, NIL, TU.RefAddr[otherParty], TU.RefAddr[cfRef]]; RETURN; };
[]←PostConvEvent[
If another party to be changed was found; do it.
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: BOOLFALSE ] = {
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: BOOLFALSE ] = {
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]];
[]𡤎nterKey[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 Log.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𡤀
IF iS.type=request THEN iS.intID.stateID ← conv.currentStateID;
ENDLOOP;
Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf];
Wake all parties to conversation, now. Potential new key.
};
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];
Wake all parties to conversation, now. Potential new key.
};
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: BOOLFALSE, 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,
stateID: StateID,
partyID: PartyHandle,
smartsID: SmartsHandle,
convID: ConversationHandle,
key: Thrush.EncryptionKey ]
RETURNS [ nb: NB, keyIndex: [0..17B] ← 0 ] = {
ENABLE UNWIND => NULL;
conv: ConversationData;
cfRef: CFRef;
[conv, , cfRef, nb] ← Verify[credentials];
Don't care about stateMismatch here.
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];
};
Utilities
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 ] = {
Validate parameters: existence, in conversation, state match; produce dehandled values.
Validation for smarts is only partial; verifies that it's a smarts.
smarts: SmartsData ← DehandleSmarts[credentials.smartsID];
conv�handleConv[credentials.convID];
party�handleParty[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[] ]];
Triples.Make[cfRef, conv, party];
conv.numParties ← conv.numParties+1;
party.numConvs ← party.numConvs+1; };
Ferret out voice smarts, if need be.
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;
Maintain active party counts, generate connection specifications.
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]
Generate a Lark.ConnectionSpec for this conversant.
RETURNS[isSpec: BOOLFALSE] = {
sockets: ARRAY[0..2) OF Lark.VoiceSocket;
parties: ARRAY[0..2) OF PartyData;
specIndex: NAT𡤀
GetOneActive: INTERNAL Triples.ForeachProc = {
cfRef: CFRef;
smarts: SmartsData;
sockID: PupTypes.PupSocketID;
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[cfRef.sockID];
WITH smarts.properties SELECT FROM
voiceTerminal => sockets[specIndex] ← [ [ machine.net], [machine.host], sockID ];
ENDCASE => Log.Problem["Non-voice terminal trying to be voice smarts", $System];
parties[specIndex] ← NARROW[trip.val];
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 => Log.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 Log.ProblemFR["%g: No such conversation", $System, NIL, TU.RefAddr[conv]]
ELSE IF stateID=0 THEN RETURN[NIL]
ELSE IF stateID>=conv.log.size THEN Log.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: BOOLTRUE;
OneIdle: Triples.ForeachProc = {
Complains unless all parties in conversation are presently idle.
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]; };
Conversation Handle management
convTable: RedBlackTree.Table = RedBlackTree.Create[ConvGet, ConvCompare];
ConvTableEntry: TYPE = REF ConvTableRecord;
ConvTableRecord: TYPE = RECORD [
convID: ConversationHandle,
conv: ConversationData
];
cTE: ConvTableEntry ← NEW[ConvTableRecord ← [nullConvHandle, NIL]];
NB. Above works as long as procedures below remain locked by SINGLE monitor.
DehandleConv: PUBLIC INTERNAL PROC[ convID: ConversationHandle]
At present, called only from Verify
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 ]
Called only from CreateConv.
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];
Log.Problem["Struct. broken", $System]; RETURN[Thrush.nullConvHandle]; };
entry ← NEW[ConvTableRecord ← [convID, conv]];
convTable.Insert[entry, entry];
};
KillHandleConv: INTERNAL PROC[ convID: ConversationHandle ] = {
Called only from DestroyConversation
cTE.convID ← convID;
[]𡤌onvTable.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: BOOLEANFALSE]
RETURNS[party: PartyData] = TRUSTED {
RETURN[LOOPHOLE[Thrush.Dehandle[partyID, RTPartyType, insist]]];
};
DehandleSmarts: PUBLIC PROC[smartsID: Thrush.SmartsHandle, insist: BOOLEANFALSE]
RETURNS[smarts: SmartsData] = TRUSTED {
RETURN[LOOPHOLE[Thrush.Dehandle[smartsID, RTSmartsType, insist]]];
};
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;
conv.newKeyTable ← TRUE;
RETURN[keyIndex, new];
};
Initialization
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 = [
Columns represent present state: rows represent proposed new states
[ 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
];
}.
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