ThPartyOpsImpl.mesa
Last modified by D. Swinehart, June 19, 1984 3:41:36 pm PDT
DIRECTORY
DESFace USING [ GetRandomKey, Key ],
IO,
Lark USING [ ConnectionSpecRec, KeyTableBody, VoiceSocket ],
Log USING [ Problem, ProblemFR, SLOG ],
OrderedSymbolTable,
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, NB, nullConvHandle, nullHandle, nullKey, PartyHandle, PartyType, PhoneNumber, Reason, ROPE, SHHH, SmartsHandle, StateID, StateInConv, ThHandle, unencrypted ],
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
DESFace,
root: ThPartyMonitorImpl,
SafeStorage,
BasicTime,
IO,
Log,
OrderedSymbolTable,
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;
convTable: OrderedSymbolTable.Table;
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;
Log.SLOG[];
IF newConv OR credentials.convID=nullConvHandle THEN
credentials.convID ← CreateConv[
partyID: credentials.partyID, convID: credentials.convID,
urgency: urgency, alertKind: alertKind].convID;
[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 THEN {
calledParty ← DehandleParty[calledPartyID];
IF calledParty=NIL THEN {nb←noSuchParty2; RETURN};
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;
Log.SLOG[];
[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.ROPENIL,
conference: BOOLFALSE ] = {
conv: ConversationData; party: PartyData; cfRef: CFRef;
[conv, party, cfRef, nb] ← Verify[credentials];
IF nb=stateMismatch THEN nb←success;
IF nb#success THEN RETURN;
FOR i: NAT IN [1..conv.currentStateID] DO
ce: ConvEvent = conv.log[i];
IF credentials.partyID=ce.credentials.partyID THEN LOOP;
partyID ← ce.credentials.partyID;
description ← ThPartyPrivate.DoDescribeParty[partyID];
IF conv.numParties>2 THEN {
conference←TRUE; description ← "Group of parties"; 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};
SetInterval: PUBLIC ENTRY PROC[
shhh: SHHH ← none,
credentials: Credentials,
intervalSpec: Thrush.IntervalSpec
] 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];
IF ~ThNet.pd.encryptVoice THEN intervalSpec.keyIndex𡤀
[] ← PostConvEvent[conv, party, credentials.smartsID, cfRef.event.state, , ,intervalSpec];
IF intervalSpec.type=request THEN intervalSpec.intID ← conv.currentStateID;
Triples.Foreach[Triples.Any, conv, Triples.Any, WachetAuf];
Wake all parties to conversation, now. Potential new key.
};
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];
IF nb=success 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
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,
intervalSpec: Thrush.IntervalSpec←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: (WITH p: party SELECT FROM
trunk => p.outgoing, ENDCASE => NIL),
comment: comment,
intervalSpec: intervalSpec
]];
};
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]; };
DehandleConv: PUBLIC INTERNAL PROC[ convID: ConversationHandle]
At present, called only from Verify
RETURNS [conv: ConversationData←NIL ] = {
node: OrderedSymbolTable.Node = convTable.Lookup[convID];
RETURN[IF node#NIL THEN node.conv ELSE NIL];
};
EnhandleConv: INTERNAL PROC[ conv: ConversationData, convClassID: ConversationHandle ]
Called only from CreateConv.
RETURNS [ convID: ConversationHandle←nextConv ] = {
IF convClassID#nullConvHandle THEN
convID ← BasicTime.Update[convClassID, convClassIncrement]
ELSE nextConv ← BasicTime.Update[nextConv, 1];
IF convTable.Lookup[convID]#NIL THEN {
IF convClassID#nullConvHandle THEN RETURN EnhandleConv[conv,convID];
Log.Problem["Struct. broken", $System]; RETURN[Thrush.nullConvHandle]; };
convTable.Insert[ NEW[OrderedSymbolTable.NodeRecord←[conv:conv]], convID ];
};
KillHandleConv: INTERNAL PROC[ convID: ConversationHandle ] = {
Called only from DestroyConversation
[]𡤌onvTable.Delete[convID];
};
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.
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
];
OrderedSymbolTable.Initialize[
NEW[OrderedSymbolTable.NodeRecord←[]],NEW[OrderedSymbolTable.NodeRecord←[]]];
convTable ← OrderedSymbolTable.CreateTable[NEW[OrderedSymbolTable.NodeRecord←[]]];
}.