LarkSmartsSupImpl.mesa
Last modified by D. Swinehart, January 13, 1985 2:16:52 pm PST
DIRECTORY
Commander USING [ CommandProc, Register ],
IO,
Lark,
LarkSmartsMonitorImpl,
Nice,
Process USING [ Detach, SetTimeout, SecondsToTicks ],
Rope USING [ Concat ],
Log USING [ Problem, ProblemFR, Report, ReportFR, SLOG ],
ThParty USING [ Advance, Alert ],
ThPartyPrivate USING [ DehandleSmarts, GetCurrentParty, SmartsBody, SmartsData ],
Thrush USING [
CallUrgency, ConvEvent, ConversationHandle, Disposition, H, IntervalSpec, IntervalSpecs, PartyHandle, NB, nullConvHandle, nullHandle, Reason, ROPE, SmartsHandle, StateID, StateInConv, ThHandle ],
ThSmartsPrivate USING [
ConvDesc, ConvDescBody, Deregister, EnterLarkState, LarkFailed, LarkState, OpenConversations, ParseState, SmartsInfo, SmartsInfoBody ],
Triples USING [ Select ],
TU
;
LarkSmartsSupImpl: CEDAR MONITOR LOCKS root
IMPORTS
Commander,
IO,
Process,
root: LarkSmartsMonitorImpl,
Log,
Nice,
Rope,
ThParty,
ThPartyPrivate,
Thrush,
ThSmartsPrivate,
Triples,
TU
EXPORTS ThSmartsPrivate
SHARES LarkSmartsMonitorImpl = {
OPEN IO;
Copies
CallUrgency: TYPE = Thrush.CallUrgency;
CommandEvents: TYPE = Lark.CommandEvents;
ConversationHandle: TYPE = Thrush.ConversationHandle;
nullConvHandle: ConversationHandle=Thrush.nullConvHandle;
ConvDesc: TYPE = ThSmartsPrivate.ConvDesc;
ConvEvent: TYPE = Thrush.ConvEvent;
disabled: Lark.Event = Lark.disabled;
enabled: Lark.Event = Lark.enabled;
endNum: Lark.Event = Lark.endNum;
H: PROC[r: REF] RETURNS[Thrush.ThHandle] = INLINE {RETURN[Thrush.H[r]]; };
IntervalSpec: TYPE = Thrush.IntervalSpec;
IntervalSpecs: TYPE = Thrush.IntervalSpecs;
LarkState: TYPE = ThSmartsPrivate.LarkState;
NB: TYPE = Thrush.NB;
nullHandle: Thrush.ThHandle = Thrush.nullHandle;
OpenConversations: TYPE = ThSmartsPrivate.OpenConversations;
PartyHandle: TYPE = Thrush.PartyHandle;
Reason: TYPE = Thrush.Reason;
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Lark.SHHH; -- Encrypts conv. if first arg to RPC PROC
SmartsHandle: TYPE = Thrush.SmartsHandle;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
SmartsInfoBody: TYPE = ThSmartsPrivate.SmartsInfoBody;
StateInConv: TYPE = Thrush.StateInConv;
StatusEvents: TYPE = Lark.StatusEvents;
Ctr: TYPE = RECORD[
count: INT,
stop: INT
];
PD: TYPE = RECORD [
defaultRecordLength: INT ← -1,
timeoutNoAction: INTEGER ← 5,
cPr: REF Ctr←NEW[Ctr←[0,1000000]],
cSp: REF Ctr←NEW[Ctr←[0,1000000]],
cRs: REF Ctr←NEW[Ctr←[0,1000000]],
cZp: REF Ctr←NEW[Ctr←[0,1000000]],
cNw: REF Ctr←NEW[Ctr←[0,1000000]],
doReports: BOOLFALSE,
doNice: BOOLFALSE
];
pd: REF PDNEW[PD←[]];
Report: PROC[what: ROPE, info: SmartsInfo, ctr: REF Ctr] = {
IF NOT pd.doReports THEN RETURN;
Log.Report[what, $Lark, info.larkInfo];
ctr.count𡤌tr.count+1;
IF ctr.count>ctr.stop THEN { Log.Problem["Report overflow", $Lark, info.larkInfo]; };
};
ReportFR: PROC[what: ROPE, info: SmartsInfo, ctr: REF Ctr, a1, a2: IO.Value←rope[NIL]] = {
IF NOT pd.doReports THEN RETURN;
Log.ReportFR[what, $Lark, info.larkInfo, a1, a2];
ctr.count𡤌tr.count+1;
IF ctr.count>ctr.stop THEN { Log.Problem["Report overflow", $Lark, info.larkInfo]; };
};
BeNice: PROC[r: REF, d: INT, info: SmartsInfo] = {
IF NOT pd.doNice THEN RETURN;
Nice.BeNice[r, d, $Lark, info.larkInfo];
};
Party-invoked actions
LarkProgress: PUBLIC ENTRY PROC[
shh: SHHH,
smartsID: Thrush.SmartsHandle,
event: Thrush.ConvEvent,
yourParty: BOOL,
latestEvent: BOOL,
informationOnly: BOOL
] RETURNS [ d: Thrush.Disposition ] = {
info: SmartsInfo ← GetSmartsInfo[smartsID: smartsID];
cDesc: ThSmartsPrivate.ConvDesc;
IF info=NIL THEN RETURN[pass];
d�tedAndStop;
Update local state information
Log.SLOG[];
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["---- LkProg: %t(%d) %g %g yr=%g ",
time[event.credentials.convID], int[event.credentials.stateID],
refAny[NEW[StateInConv𡤎vent.state]], TU.RefAddr[info], bool[yourParty]],
IO.PutFR["lt=%g in=%g, ky=%g\n",
bool[latestEvent], bool[event.intervalSpecs#NIL], bool[event.keyTable#NIL]]],
info, pd.cPr];
cDesc ← GetConv[info, event.credentials.convID, FALSE];
IF event.credentials.stateID <= cDesc.cState.credentials.stateID THEN RETURN[pass]; -- Old news!
Fields always extracted
cDesc.cState.credentials.smartsID ← smartsID;
cDesc.cState.credentials.stateID ← event.credentials.stateID;
<<Need to check for increasing stateID here, and reject old ones? If so, enable
test in alrt case below.>>
Extracted if present
IF event.keyTable#NIL THEN {
cDesc.cState.keyTable ← event.keyTable; cDesc.newKeys←TRUE; };
IF event.intervalSpecs#NIL THEN
May accept more later.
EnqueueIntervals[cDesc, event.intervalSpecs];
IF event.address#NIL THEN { cDesc.cState.address𡤎vent.address; cDesc.newAddress←TRUE; };
Extracted if the event changes our state.
IF yourParty THEN {
cDesc.descValid ← TRUE;
cDesc.cState.credentials.partyID ← event.credentials.partyID;
cDesc.cState.state ← event.state;
cDesc.cState.comment ← event.comment;
cDesc.cState.reason ← event.reason;
<< When Conferencing is added, put this back in.>>
cDesc.cState.conferenceHost ← event.conferenceHost;
IF event.spec#NIL THEN { cDesc.cState.spec ← event.spec; cDesc.newSpec←TRUE; };
Extracted if non-standard?
IF event.comment#NIL THEN cDesc.cState.comment ← event.comment;
IF event.urgency#normal THEN cDesc.cState.urgency ← event.urgency;
IF event.alertKind#standard THEN cDesc.cState.alertKind ← event.alertKind;
SELECT event.state FROM
reserved, parsing, initiating => cDesc.originator ← us;
maybe => {
cDesc.originator ← us;
Yucchh! Progress is shared between Trunk and Lark smarts. Want to do it only in the Lark case. This is the only way to tell, at present!
IF info.Supervise = LarkSupervise THEN info.larkInfo.ringTune ← event.ringTune;
};
pending => cDesc.originator ← them;
ringing => { cDesc.originator ← them; info.larkInfo.ringTune ← event.ringTune; };
ENDCASE;
};
BeNice[event, 4, info];
BeNice[cDesc, 6, info];
cDesc.newEvent ← TRUE;
IF latestEvent THEN Apprise[info]; -- Wait for the last report to wake process.
};
Th(e) Process
LarkSupervise: PUBLIC ENTRY PROC[info: SmartsInfo ] = {
TRUSTED {
Process.SetTimeout[@info.thAction, Process.SecondsToTicks[pd.timeoutNoAction]]; };
IF info.apprise THEN DO
ENABLE {
UNWIND => NULL;
ThSmartsPrivate.LarkFailed => GOTO Failing;
};
prevL: OpenConversations ← NIL;
nb: NB←success;
convL: OpenConversations;
trans: Transition;
info.apprise ← FALSE;
FOR convL ← info.conversations,
convL.rest WHILE convL#NIL DO
cDesc: ConvDesc = convL.first;
stateNow: StateInConv = cDesc.cState.state;
ours: BOOL ← ( cDesc.cState.credentials.convID = info.currentConvID );
Log.SLOG[];
IF pd.doReports THEN Report[
Rope.Concat[
IO.PutFR["**** LkSup: %t(%d) %g %g->%g",
time[cDesc.cState.credentials.convID], int[cDesc.cState.credentials.stateID],
TU.RefAddr[info], refAny[NEW[StateInConv←stateNow]], refAny[NEW[StateInConv�sc.desiredState]]],
IO.PutFR[" %g%g\n",
refAny[NEW[Transition←transForStates[stateNow][cDesc.desiredState]]],
rope[IF ours THEN " (ours)" ELSE ""]]],
info, pd.cSp];
BeNice[cDesc, 6, info];
IF NOT cDesc.descValid THEN LOOP;
IF info.currentConvID = nullConvHandle --AND stateNow#idle-- THEN {
<<This stuff feels like it's in the wrong place. Will feel more so when multi-calls
are dealt with. >>
ours←TRUE; info.currentConvID ← cDesc.cState.credentials.convID;
};
IF stateNow=idle THEN {
Conv. is now idle: forget about it or re-use it, sort of.
Remove dead conversation from list.
IF pd.doReports THEN
ReportFR[" ** Zap %t\n", info, pd.cZp, time[cDesc.cState.credentials.convID]];
IF prevL#NIL THEN {
prevL.rest ← convL.rest; convL ← prevL; }
ELSE info.conversations𡤌onvL.rest;
IF ours THEN {
reason: Reason = cDesc.cState.reason;
newConvID: ConversationHandle�sc.cState.credentials.convID;
newDesc: ConvDesc;
info.currentConvID ← nullConvHandle;
SELECT info.larkInfo.hookState FROM
onhook, spkr =>
SELECT reason FROM
terminating, wontSay => GOTO Exit;
ENDCASE => IF cDesc.originator#us THEN GOTO Exit;
ENDCASE =>
SELECT TRUE FROM
reason=terminating, reason=wontSay, cDesc.originator#us => {
Set to look like reserving brand new conversation
newConvID ← nullConvHandle;
info.currentConvID ← nullConvHandle;
};
ENDCASE;
newDesc ← GetConv[info, newConvID, TRUE];
ChangeState[info, newDesc, reserved, cDesc.cState.reason, cDesc.cState.comment];
-- Now go around the loop once, if only to coerce Lark into idle state.
EXITS Exit => NULL;
};
}
ELSE IF NOT ours THEN
Conv isn't the one we're interested in, and doesn't look like it's going idle: idle it.
Should probably consult transForStates for validity.
nb ← ThParty.Advance[
credentials: cDesc.cState.credentials,
state: idle,
reason: busy,
comment: "One conversation at a time, please."
]
ELSE {
State and substate (below) and desired state indicate there's something to do. Do it:
SELECT (trans←transForStates[stateNow][cDesc.desiredState]) FROM
noop => NULL;
elim => ERROR; -- handled in stateNow=idle case above.
rsrv, rers, prsg, alrt => { -- placing call, or reserving conversation
convID: ConversationHandle;
calledPartyID: PartyHandle =
IF trans#alrt THEN nullHandle ELSE cDesc.desiredPartyID;
nb ← IF trans=alrt AND calledPartyID=nullHandle
THEN noSuchParty2 ELSE success;
IF NOT ours THEN Problem["What to do in a race?", info];
IF nb=success THEN [nb, convID] ← ThParty.Alert [
credentials: cDesc.cState.credentials,
calledPartyID: calledPartyID,
state: IF trans = alrt THEN initiating ELSE cDesc.desiredState,
reason: cDesc.desiredReason,
newConv: trans=rsrv,
comment: cDesc.desiredComment
];
IF nb=success THEN {
cDesc.cState.credentials.convID ← info.currentConvID ← convID;
ours←TRUE;
};
};
idle, actv => -- Simple transitions
nb ← ThParty.Advance [
credentials: cDesc.cState.credentials,
state: cDesc.desiredState,
reason: cDesc.desiredReason,
comment: cDesc.desiredComment
];
spvs => { -- Supervision while active.
cDesc.desiredState�tive; -- Reflect reality
FOR iL: IntervalSpecs ← cDesc.newIntervals, iL.rest WHILE iL#NIL DO
May do more later. For now, record the intervals that start; when you see the last one that started end, it's time to shut down the connection.
iS: IntervalSpec = iL.first;
IF iS.interval.length#0 OR iS.queueIt THEN -- else flush: ignore
SELECT iS.type FROM
started => cDesc.desiredIntID ← iS.intID;
finished =>
IF iL.rest=NIL AND iS.intID = cDesc.desiredIntID THEN
SELECT info.larkInfo.hookState FROM
spkr, spKr => ChangeState[info, cDesc, idle]; ENDCASE;
ENDCASE;
ENDLOOP;
cDesc.newIntervals ← NIL;
};
EnterLarkState, below, may cause new keys to be distributed.
When voice messages again supported, will need to initiate intervals here.
ring => -- This is how you say you'll ring an incoming call
IF info.larkInfo.hotLine THEN {
info.apprise←TRUE;
cDesc.desiredState ← active;
}
ELSE nb ← ThParty.Advance [
credentials: cDesc.cState.credentials,
state: ringing
];
invl => {
comment: ROPE="Invalid state transition";
Problem[comment, info];
ChangeState[info, cDesc, idle, error, comment];
};
ntiy => {
comment: ROPE= "Unimplemented state transition";
Problem[comment, info];
ChangeState[info, cDesc, idle, error, comment];
};
ENDCASE => ERROR;
};
Analyze any results of trying to reach a different state.
IF nb#success THEN
ReportFR[" ** Results: nb=%g\n", info, pd.cRs, refAny[NEW[Thrush.NB←nb]]];
SELECT nb FROM
success, stateMismatch => NULL;
noSuchParty2, narcissism => -- Have to get to error tone here. No such party.
ChangeState[info, cDesc, idle, notFound, "Called party not found, or calling self"];
partyNotEnabled => ours←FALSE;
invalidTransition, convNotActive, convStillActive => {
Complain and transition to idle
comment: ROPE="Party-level detected invalid state transition request";
Problem[comment, info];
ChangeState[info, cDesc, idle, error, comment];
};
notInConv, noSuchConv => { -- Complain, zap, go idle and repeat.
comment: ROPE="NotInConv or NoSuchConv";
Problem[comment, info];
IF ours THEN info.currentConvID ← nullConvHandle;
cDesc.cState.credentials.convID ← nullConvHandle;
cDesc.cState.credentials.stateID ← 0;
ChangeState[info, cDesc, idle, error, comment];
};
noSuchParty, noSuchSmarts => { -- Complain, deregister, we gone! Needs tuning.
Problem[
"LkSmts: NoSuchParty or NoSuchSmarts reported, must try to go away", info];
GOTO Failing;
};
ENDCASE => ERROR;
Set up switching and tones to match state.
IF ours THEN ThSmartsPrivate.EnterLarkState[
info.larkInfo, LarkStateForState[cDesc, stateNow], info];
prevL ← convL;
ENDLOOP;
IF NOT info.apprise THEN WAIT info.thAction;
IF info.conversations = NIL THEN EXIT;
REPEAT Failing => ThSmartsPrivate.Deregister[info]; -- now Failed
ENDLOOP;
info.thProcess ← NIL;
};
Connection Management Utilities
GetConv: PUBLIC INTERNAL PROC[info: SmartsInfo, convID: ConversationHandle, validIfNew: BOOL
] RETURNS [ cDesc: ConvDesc←NIL ] = --INLINE-- {
FOR convs: OpenConversations ← info.conversations, convs.rest WHILE convs#NIL DO
IF convs.first.cState.credentials.convID = convID THEN RETURN[convs.first];
ENDLOOP;
cDesc ← NEW[ThSmartsPrivate.ConvDescBody←[]];
cDesc.descValid ← validIfNew;
cDesc.cState.state ← any;
cDesc.cState.credentials ← [ convID: convID, smartsID: H[info.smarts],
partyID: ThPartyPrivate.GetCurrentParty[smartsID: H[info.smarts]], stateID: 0 ];
info.conversations ← CONS[cDesc, info.conversations];
IF pd.doReports THEN
Report[IO.PutFR[" ** NewConv %t %g, vl=%g\n", time[convID], TU.RefAddr[info], bool[validIfNew]], info, pd.cNw];
};
ChangeState: PUBLIC INTERNAL PROC[
info: SmartsInfo,
cDesc: ConvDesc,
state: StateInConv ← idle,
reason: Reason ← wontSay,
comment: ROPENIL
] = {
IF info=NIL OR cDesc=NIL THEN RETURN;
cDesc.desiredState ← state;
cDesc.desiredReason ← reason;
cDesc.desiredComment ← comment;
Apprise[info];
};
Apprise: PUBLIC INTERNAL PROC[info: SmartsInfo] = TRUSTED --INLINE-- {
IF info.thProcess=NIL THEN
Process.Detach[info.thProcess ← FORK info.Supervise[info]];
info.apprise ← TRUE;
NOTIFY info.thAction;
};
EnqueueIntervals: INTERNAL PROC[cDesc: ConvDesc, int: IntervalSpecs] = INLINE {
IF cDesc.newIntervals#NIL THEN cDesc.iTail.rest ← int ELSE cDesc.newIntervals ← int;
FOR iL: IntervalSpecs ← int, iL.rest WHILE iL#NIL DO cDesc.iTail ← iL; ENDLOOP;
};
Other Utilities
GetSmartsInfo: PUBLIC PROC[smartsID: SmartsHandle←nullHandle,
smarts: ThPartyPrivate.SmartsData←NIL]
RETURNS [info: SmartsInfo←NIL] = {
r: REF←smarts;
IF smartsID#nullHandle THEN
r←ThPartyPrivate.DehandleSmarts[smartsID];
IF r#NIL THEN RETURN[NARROW[Triples.Select[$SmartsData, r, --info--]]];
};
Problem: PROC[comment: ROPE, info: SmartsInfo] = {
Log.ProblemFR[Rope.Concat["LarkSmarts(%g): ", comment], $Smarts, info, TU.RefAddr[info]];
};
State Transition Tables
larkStateForState: ARRAY StateInConv OF LarkState ← [
idle, reserved, parsing, initiating, pending, maybe, ringing, canActivate, active,
idle, dialTone, silence, silence, silence, ringBack, ringing, silence, talking,
inActive, any
silence, silence
];
LarkStateForState: INTERNAL PROC[cDesc: ConvDesc, state: StateInConv]
RETURNS [larkState: LarkState] = -- INLINE -- {
SELECT (larkState←larkStateForState[state]) FROM
talking => {
spec: Lark.ConnectionSpec = cDesc.cState.spec;
IF spec=NIL THEN ERROR;
IF spec.localSocket.net = spec.remoteSocket.net
AND spec.localSocket.host = spec.remoteSocket.host THEN larkState ← trunkTalking;
};
dialTone => larkState ← SELECT cDesc.cState.reason FROM
busy, notImportantEnough => busyTone,
absent, noCircuits, noParticular, notFound, error => errorTone,
ENDCASE => dialTone;
ENDCASE;
};
Transition: TYPE = {
Just codes to dispatch on in Supervisor; explained there
noop, elim, idle, rsrv, rers, prsg, alrt, ring, actv, spvs, invl, ntiy
};
transForStates: ARRAY StateInConv OF ARRAY StateInConv OF Transition ← [
idle resrv pars init pend mayb ring canAc activ inact any -- desired
[ elim, invl, prsg, invl, invl, invl, invl, invl, elim, elim, elim ], -- idle  (current)
[ idle, noop, prsg, invl, invl, invl, invl, invl, alrt, ntiy, noop ], -- reserved
[ idle, rers, noop, invl, invl, invl, invl, invl, alrt, ntiy, invl ], -- parsing
[ idle, noop, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- initiating
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, ring ], -- pending
[ idle, noop, invl, invl, invl, invl, invl, invl, noop, ntiy, noop ], -- maybe
[ idle, invl, invl, invl, invl, invl, invl, invl, actv, ntiy, noop ], -- ringing
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, ntiy, ntiy ], -- canActivate
[ idle, spvs, invl, invl, invl, invl, invl, invl, spvs, ntiy, spvs ], -- active
[ idle, invl, invl, invl, invl, invl, invl, invl, ntiy, noop, ntiy ], -- inactive
[ noop, rsrv, prsg, invl, invl, invl, invl, invl, alrt, ntiy, invl ] -- any (nonex)
];
NB: examine relationship to validity table in PartyOpsImpl someday.
Debugging nonsense
ViewCmd: Commander.CommandProc = TRUSTED {
Nice.View[pd, "Lark PD"];
};
Commander.Register["VuLarkSmarts", ViewCmd, "Program Management variables for Lark Smarts"];
}.