LarkTrunkSmartsImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Last modified by D. Swinehart, June 1, 1986 4:56:44 pm PDT
Note: Incoming calls are those from the Etherphone to the outside party, whom this system represents. Outgoing calls are those from the outside world to an Etherphone. Remember; trunk party is a representative of people in the public telephone system.
See LarkSmartsImpl and LarkSmartsSupImpl for various comments guiding the structure of this module; it's simpler, but similar.
DIRECTORY
Atom USING [ DottedPairNode ],
Commander USING [ CommandProc, Register ],
IO,
Lark
-- USING [
-- CommandEvents, ConnectionSpec, Event, LarkModel, Passel, StatusEvents ]--,
LarkFeep,
LarkFeepRpcControl USING [ ExportInterface ],
LarkSmartsMonitorImpl,
MBQueue USING [ Create, QueueClientAction ],
Nice,
RefID USING [ ID, nullID, Reseal, Unseal ],
Rope USING [ ROPE ],
RPC USING [ EncryptionKey, ExportFailed ],
ThParty USING [ Alert, ConversationInfo, CreateConversation, DescribeParty, GetConversationInfo, GetKeyTable, GetParty, RegisterServiceInterface, SmartsInterfaceRecord ],
ThPartyPrivate USING [ RegisterLocal ],
Thrush
USING [
ConversationID, ConvEvent, Credentials, InterfaceSpec, PartyID, NB, noAddress, nullConvID, nullID, ROPE, SHHH, SmartsID, StateInConv ],
ThSmartsPrivate
USING [
AssessDamage, ChangeState, ComputeConnection, ConvDesc, Deregister, EnterLarkState, ForgetConv, GetConv, GetConvDesc, GetSIC, GetSmartsInfo, LarkFailed, LarkInfo, LarkState, OpenConversations, QueueFeeps, SmartsInfo, SmartsInfoBody ],
ThSmartsRpcControl,
Triples USING [Make ],
TU,
VoiceUtils USING [ CmdOrToken, CurrentPasskey, MakeRName, Problem, ProblemFR, Report, ReportFR ]
;
LarkTrunkSmartsImpl:
CEDAR MONITOR
LOCKS root
IMPORTS
Commander,
root: LarkSmartsMonitorImpl,
IO,
LarkFeepRpcControl,
MBQueue,
Nice,
RefID,
RPC,
ThParty,
ThPartyPrivate,
ThSmartsPrivate,
ThSmartsRpcControl,
Triples,
TU,
VoiceUtils
EXPORTS ThSmartsPrivate, LarkFeep --, ThSmarts via Interface record --
SHARES LarkSmartsMonitorImpl = { OPEN IO;
Types and Constants
ConvDesc: TYPE = ThSmartsPrivate.ConvDesc;
Reseal: PROC[r: REF] RETURNS[RefID.ID] = INLINE {RETURN[RefID.Reseal[r]]; };
ROPE: TYPE = Thrush.ROPE;
SHHH: TYPE = Lark.SHHH; -- Encrypts conv. if first arg to RPC PROC
PartyID: TYPE = Thrush.PartyID;
nullID: RefID.ID = Thrush.nullID;
nullConvID: Thrush.ConversationID = Thrush.nullConvID;
SmartsInfo: TYPE = ThSmartsPrivate.SmartsInfo;
StateInConv: TYPE = Thrush.StateInConv;
PD:
TYPE =
RECORD [
timeoutNoAction: INT,
doReports: BOOL←FALSE,
doNice: BOOL←FALSE
];
pd: REF PD ← NEW[PD←[]];
larkFeepSpec: Thrush.InterfaceSpec ← [interfaceName: [type: "LarkFeep"], serviceID: RefID.nullID];
larkFeepUser: ROPE;
larkFeepPassword: RPC.EncryptionKey;
Report:
PROC[what:
ROPE, info: SmartsInfo] = {
IF NOT pd.doReports THEN RETURN;
VoiceUtils.Report[what, $Lark, info.larkInfo];
};
Initialization
RegisterTrunk:
PUBLIC
ENTRY PROC[
hostSmartsID: Thrush.SmartsID, hostInfo: SmartsInfo, partyRname: ROPE ]
RETURNS [ nb: Thrush.NB←$success, smartsID: Thrush.SmartsID←nullID ] = {
ENABLE UNWIND => NULL;
There's no provision here at all for reregistration. Yet.
localSmarts: ThParty.SmartsInterfaceRecord;
info: SmartsInfo;
smarts: REF;
credentials: Thrush.Credentials;
nbLocal: Thrush.NB;
exportRequired: BOOL ← larkFeepSpec.interfaceName.instance = NIL;
localSmarts ← ThSmartsRpcControl.NewInterfaceRecord[];
localSmarts.clientStubProgress ← LarkTrunkProgress;
localSmarts.clientStubSubstitution ← LarkTrunkSubstitution;
[nb, credentials] ← ThPartyPrivate.RegisterLocal [
rName: partyRname,
type: $trunk,
interfaceRecord: localSmarts,
properties: [role: $voiceTerminal, netAddress: hostInfo.larkInfo.netAddress]
];
IF nb # $success
OR (smartsID𡤌redentials.smartsID) = nullID
THEN {
VoiceUtils.ProblemFR["Can't register Lark trunk: %g",$System,NIL, rope[partyRname]];
RETURN;
};
smarts ← RefID.Unseal[smartsID];
IF smarts =
NIL
THEN { VoiceUtils.Problem["No smarts already?"];
RETURN[$noSuchSmarts];
};
info ←
NEW[ThSmartsPrivate.SmartsInfoBody ← [
smartsID: smartsID,
otherSmartsID: hostSmartsID,
partyType: $trunk,
ParseEvent: LarkTrunkParseEvent,
NoteNewStateP: NoteNewTkState,
requests: MBQueue.Create[],
larkInfo: hostInfo.larkInfo ]];
info.larkInfo.larkTrunkSmartsInfo ← info;
Triples.Make[$SmartsData, smarts, info];
larkFeepSpec.serviceID ← smartsID; -- identify self.
[nbLocal, larkFeepSpec] ← ThParty.RegisterServiceInterface[credentials: credentials,
interfaceSpecPattern: larkFeepSpec];
SELECT nbLocal FROM $success, $stateMismatch => NULL; ENDCASE => GOTO CantExportFeep;
IF exportRequired
THEN LarkFeepRpcControl.ExportInterface[
larkFeepSpec.interfaceName, larkFeepUser, larkFeepPassword!
RPC.ExportFailed => GOTO CantExportFeep]
EXITS
CantExportFeep => {
VoiceUtils.Problem["Couldn't export LarkFeep from TrunkSmarts"]; RETURN; };
Events
RecordTrunkEvent handles events from the EtherPhone Trunk connection.
The only interesting events at present are "ring" (place trunk->station call) and "tones F" (station->trunk call being "answered")
The upstream code produces a single "enabled" event when the line starts ringing, and a single "disabled" event when it appears that ringing has stopped. This "disabled" event must be anticipated and ignored if the station answers (calling trunk can't hang up on completed call!)
EventSpec: TYPE = REF EventSpecBody;
EventSpecBody:
TYPE =
RECORD [
smartsInfo: SmartsInfo,
sEvent: Lark.StatusEvent
];
LarkTrunkParseEvent:
PROC[
smartsInfo: SmartsInfo, sEvent: Lark.StatusEvent] = {
smartsInfo.requests.QueueClientAction[
QdLarkTrunkParseEvent, NEW[EventSpecBody ← [smartsInfo, sEvent]]];
};
QdLarkTrunkParseEvent:
ENTRY
PROC[r:
REF] = {
-- r is the EventSpec
eventSpec: EventSpec = NARROW[r];
smartsInfo: SmartsInfo = eventSpec.smartsInfo; {
ENABLE {
UNWIND=>NULL;
ThSmartsPrivate.LarkFailed => { VoiceUtils.ProblemFR["%g: Lark Failed", $Smarts, smartsInfo, TU.RefAddr[smartsInfo]]; GOTO Failed; };
};
sEvent: Lark.StatusEvent ← eventSpec.sEvent;
SELECT sEvent.device
FROM
ringDetect =>
SELECT sEvent.event
FROM
Lark.enabled => CmdRinging[smartsInfo];
Lark.disabled => CmdStopRinging[smartsInfo];
ENDCASE => ERROR;
ENDCASE;
EXITS
Failed =>
ThSmartsPrivate.Deregister[smartsInfo.otherSmartsID];
}; };
CmdRinging:
INTERNAL
PROC[info: SmartsInfo] = {
partyID: PartyID; -- self
calledPartyRname: ROPE;
calledPartyID: PartyID;
cDesc: ConvDesc;
convEvent: Thrush.ConvEvent;
nb: Thrush.NB; {
SELECT ThSmartsPrivate.GetSIC[info]
FROM
$idle => NULL;
ENDCASE => RETURN; -- not a valid time for this call. Ignore. Host system will handle.
cDesc ← ThSmartsPrivate.GetConv[info, nullConvID, TRUE];
partyID ← cDesc.situation.self.partyID; -- self
[nb, calledPartyRname] ←
ThParty.DescribeParty[partyID: partyID, nameReq: $owner];
IF nb # $success
OR calledPartyRname=
NIL
THEN
GOTO Failed;
-- Enter error state? Forget Conv?
[nb, calledPartyID] ← ThParty.GetParty[partyID: partyID, rName: calledPartyRname];
SELECT nb
FROM
$success => NULL;
$noSuchParty2 => GOTO Failed; -- Enter error state? Forget Conv?
$voiceTerminalBusy => GOTO Failed; -- can't ring in
ENDCASE => GOTO Failed; -- Enter error state? Forget Conv?
[nb, convEvent] ← ThParty.CreateConversation[
credentials: cDesc.situation.self,
state: $initiating
];
IF nb # $success
THEN {
ThSmartsPrivate.AssessDamage[nb, cDesc, convEvent];
GOTO Failed;
No idea what to do! This is pretty catastrophic, since no one else know about conv
}
ELSE NoteNewTkState[cDesc, convEvent];
nb←ThParty.Alert[credentials: cDesc.situation.self, calledPartyID: calledPartyID];
IF nb # $success THEN GOTO Failed; -- enter error state?
EXITS
Failed => ThSmartsPrivate.ForgetConv[cDesc];
}};
CmdStopRinging:
INTERNAL
PROC[info: SmartsInfo] = {
nb: Thrush.NB;
cDesc: ConvDesc=ThSmartsPrivate.GetConvDesc[info];
IF cDesc=NIL THEN RETURN;
SELECT cDesc.situation.self.state
FROM
$initiating, $ringback => NULL; -- ringing stopped before called party answered.
ENDCASE => RETURN; -- already active or something; ignore end of ringing
nb ← ThSmartsPrivate.ChangeState[cDesc, $idle, $terminating];
Complain about nb # $success here?
};
LarkTrunkProgress:
PUBLIC
ENTRY
PROC[
interface: ThSmartsRpcControl.InterfaceRecord,
shh: Thrush.SHHH,
convEvent: Thrush.ConvEvent
] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
Some party has changed state in a conversation we know about.
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: convEvent.self.smartsID];
IF info=NIL THEN { Report["No info at Progress", NIL]; RETURN; };
info.requests.QueueClientAction[QdLarkTrunkProgress, convEvent];
};
QdLarkTrunkProgress:
ENTRY
PROC[r:
REF] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
convEvent: Thrush.ConvEvent = NARROW[r];
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: convEvent.self.smartsID];
cDesc: ConvDesc;
convID: Thrush.ConversationID = convEvent.self.convID;
whatNeedsDoing: WhatNeedsDoing;
IF info=NIL THEN { Report["Smarts Info Missing at Progress", NIL]; RETURN; };
cDesc ← ThSmartsPrivate.GetConv[ info, convID, TRUE ];
IF convEvent.self.partyID = convEvent.other.partyID
THEN {
-- own state changed
NoteNewTkState[cDesc, convEvent];
SELECT info.currentConvID
FROM
convID, nullConvID =>
NULL;
(new conversation and we're idle) or (this is the one we like)
ENDCASE => DoRejectCall[cDesc, convEvent];
We have to reject this, since we're already dealing with another conv.
We are (still) willing to seriously consider only one conversation at a time.
RETURN;
};
IF convID#info.currentConvID
THEN {
Situation is that someone else reported a change of theirs concerning a conversation we've expressed no interest in. I don't believe this should happen.
Report["Strange progress report", cDesc.info];
ThSmartsPrivate.ForgetConv[cDesc];
RETURN;
};
Someone else's state changed in a conv. we're interested in; see if it means anything to us!
whatNeedsDoing ← whatNeedsDoingIf[cDesc.situation.self.state][convEvent.other.state];
SELECT whatNeedsDoing
FROM
$noop, $ntiy => NULL; -- No action is needed, or we don't know what one to take.
$imp => ERROR; -- This is supposed to be impossible!
$xrep => Report["Didn't expect state change report", info];
$frgt => ThSmartsPrivate.ForgetConv[cDesc];
$invl => {
Report["Invalid state transition", info];
[]←ThSmartsPrivate.ChangeState[cDesc: cDesc, state: $idle,
reason: $error, comment: "System Error: Invalid state transition"];
};
$idle => {
nb2: Thrush.NB; cInfo: ThParty.ConversationInfo;
[nb2, cInfo] ← ThParty.GetConversationInfo[convID: convEvent.self.convID];
IF nb2 # $success
OR (cInfo.numParties-cInfo.numIdle) <= 1
THEN
[]←ThSmartsPrivate.ChangeState[cDesc, $idle,
IF nb2#$success THEN $error ELSE $terminating];
};
$actv => [] ← ThSmartsPrivate.ChangeState[cDesc, $active];
$reac, $deac => NULL;
ENDCASE => ERROR;
};
LarkTrunkSubstitution:
PUBLIC
ENTRY
PROC[
interface: ThSmartsRpcControl.InterfaceRecord,
shh: Thrush.SHHH,
convEvent: Thrush.ConvEvent,
oldPartyID: Thrush.PartyID,
newPartyID: Thrush.PartyID
] = {
ENABLE UNWIND => NULL; -- RestoreInvariant;
A poacher has substituted for a poachee, or the other way around. Update state so that if it's us, we know who we are.
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: convEvent.self.smartsID];
IF info=NIL THEN { Report["No info at Substitution", NIL]; RETURN; };
info.requests.QueueClientAction[QdTkSubstitution, convEvent];
};
QdTkSubstitution:
ENTRY
PROC [r:
REF] ~ {
Poacher has substituted for Poachee or vice/versa. No conversation state has changed, so just update our notion of our state and quit.
ENABLE UNWIND => NULL; -- RestoreInvariant;
convEvent: Thrush.ConvEvent = NARROW[r];
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: convEvent.self.smartsID];
cDesc: ConvDesc;
IF info=NIL THEN { Report["Smarts Info Missing at Substitution", NIL]; RETURN; };
cDesc ← ThSmartsPrivate.GetConv[ info, convEvent.self.convID, FALSE ];
IF cDesc=NIL THEN RETURN;
NoteNewTkState[cDesc, convEvent];
};
QdNotification:
ENTRY
PROC [r:
REF] ~ {
ENABLE UNWIND => NULL; -- RestoreInvariant;
cDesc: ConvDesc = NARROW[r];
IF cDesc.situation.self.state = $notified
THEN
[] ← ThSmartsPrivate.ChangeState[cDesc, $active];
};
DoRejectCall:
INTERNAL
PROC[cDesc: ConvDesc, convEvent: Thrush.ConvEvent] = {
[]←ThSmartsPrivate.ChangeState[cDesc, $idle, $busy];
};
NoteNewTkState:
INTERNAL
PROC[cDesc: ConvDesc, convEvent: Thrush.ConvEvent] ~ {
OPEN now: cDesc.situation.self;
nb: Thrush.NB;
larkStateData: LIST OF REF ANY←NIL;
state: StateInConv ← convEvent.self.state;
previousState: StateInConv = cDesc.situation.self.state;
info: ThSmartsPrivate.SmartsInfo = cDesc.info;
larkInfo: ThSmartsPrivate.LarkInfo = info.larkInfo;
cInfo: ThParty.ConversationInfo;
larkState: ThSmartsPrivate.LarkState ← $none;
savedCurrentConvID: Thrush.ConversationID;
cDesc.situation ← convEvent^;
IF state = previousState THEN RETURN; -- No conceivable value in acting.
IF info.currentConvID=nullConvID THEN info.currentConvID ← cDesc.situation.self.convID;
savedCurrentConvID ← info.currentConvID;
SELECT state
FROM
$notified => info.requests.QueueClientAction[QdNotification, cDesc];
$idle => {
IF larkInfo.forwardedCall THEN larkState ← $idle;
ThSmartsPrivate.ForgetConv[cDesc];
};
$neverWas => ThSmartsPrivate.ForgetConv[cDesc];
$active => {
phoneNumber: ROPE;
[nb, phoneNumber] ← ThParty.DescribeParty[partyID: now.partyID, nameReq: $address];
IF nb = $success THEN [nb, cInfo] ← ThParty.GetConversationInfo[convID: now.convID];
larkState ←
IF nb # $success OR phoneNumber=NIL OR cInfo.originator=now.partyID THEN trunkTalking ELSE trunkSignalling;
larkStateData ←
LIST[
ThSmartsPrivate.ComputeConnection[cDesc],
NEW[Atom.DottedPairNode ← [$phoneNumber, phoneNumber]]];
};
ENDCASE;
IF state > $idle
AND cDesc.keyTable=
NIL
THEN {
[nb, cDesc.keyTable] ← ThParty.GetKeyTable[credentials: now];
IF nb # $success THEN cDesc.keyTable←NIL;
};
IF state>$idle
AND cDesc.keyTable#info.larkInfo.keyTable
THEN {
larkStateData ← CONS[cDesc.keyTable, larkStateData];
larkState ← larkInfo.larkState;
};
IF now.convID = savedCurrentConvID
AND larkState # $none
THEN
ThSmartsPrivate.EnterLarkState[larkInfo, larkState, larkStateData];
};
Feep:
PUBLIC
ENTRY
PROCEDURE [
-- Exports to voice service interface LarkFeep
shhh: Thrush.SHHH, serviceID: Thrush.SmartsID, convID: Thrush.ConversationID, requestingParty: Thrush.PartyID,
number: Rope.ROPE, noisy: BOOLEAN ← FALSE, on, off: CARDINAL← 177777B]
RETURNS [nb: Thrush.NB←$success] = {
info: SmartsInfo = ThSmartsPrivate.GetSmartsInfo[smartsID: serviceID];
cDesc: ConvDesc;
IF info = NIL THEN RETURN[$noSuchSmarts];
cDesc ← ThSmartsPrivate.GetConv[info, convID, FALSE];
IF cDesc = NIL THEN RETURN[$notInConv];
IF cDesc.situation.self.state # $active THEN RETURN[$convIdle];
IF noisy THEN number ← IO.PutFR["A%g", rope[number]];
IF on#177777B THEN number ← IO.PutFR["%gO%g", char[LOOPHOLE[on/10]], rope[number]];
IF off#177777B THEN number ← IO.PutFR["%gF%g", char[LOOPHOLE[off/10]], rope[number]];
ThSmartsPrivate.QueueFeeps[info, number];
};
LarkTrunkSmartsInit: Commander.CommandProc = {
larkFeepSpec.interfaceName.instance ← NIL;
larkFeepSpec.hostHint ← Thrush.noAddress;
larkFeepUser ← VoiceUtils.MakeRName[style: rName, name:
VoiceUtils.CmdOrToken[cmd: cmd, key: "ThrushServerInstance", default: "Strowger.Lark"]];
larkFeepPassword ← VoiceUtils.CurrentPasskey[VoiceUtils.CmdOrToken[
cmd: cmd, key: "ThrushServerPassword", default: "MFLFLX"]];
VoiceUtils.ReportFR["Initialize[LarkTrunkSmarts.Lark, %s]", $System, NIL, rope[larkFeepUser]];
};
WhatNeedsDoing:
TYPE =
ATOM;
-- {
Just codes to dispatch on in Supervisor; explained there
$noop, $idle, $actv, $frgt, $reac, $deac, -- cases explained in code
$invl, -- considered an invalid request
$xrep, -- we got a report we feel we shouldn't have got
$ntiy, -- not implemented yet
$imp -- this situation should not arise even in the face of invalid requests
};
whatNeedsDoingIf:
ARRAY StateInConv
OF
ARRAY StateInConv
OF WhatNeedsDoing ← [
If we're in the state identified by the row, and someone else in the conversation reports a transition onto the state identified by the column, what should we do?
never idle failed resrv pars init notif rback ring canAc activ inact -- ← (other)
[ $imp, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt, $frgt],
--neverWas
(Clip this table to view without these comments.)
This situation arises when we've forgotten about the conversation that somebody else is still reporting on.
[ $imp, $noop, $frgt, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $ntiy ], -- idle
[ $imp, $imp, $frgt, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- failed
[ $imp, $imp, $frgt, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ],
-- reserved
The actions of other parties are not of interest to us yet, since they're not in this conv.
[ $imp, $imp, $frgt, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ],
-- parsing
Ditto.
[ $imp,
$idle,$frgt, $invl, $invl, $invl, $xrep, $xrep,
$xrep
,
$actv,
$actv,
$actv ],
-- initiating
They can either enter ringing to indicate interest, or go active without ringing
[ $imp,
$idle,$frgt, $invl, $invl, $invl, $xrep, $noop, $noop, $ntiy, $noop, $ntiy ],
-- notified
We don't expect to hear from others while we're deciding whether to play
[ $imp,
$idle,$frgt, $invl, $invl, $invl, $invl, $invl, $invl, $invl, $invl, $invl ],
-- ringback
They have earlier expressed interest noopringing), and are now joining the fray
[ $imp,
$idle,$frgt, $invl, $invl, $invl, $invl, $invl, $invl, $invl, $invl, $invl ],
-- ringing
The only thing that interests us here is everybody else quitting.
[ $imp, $idle,$frgt, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $noop, $ntiy ], -- canActivate
[ $imp, $idle,$frgt, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $reac, $deac ], -- active
[ $imp, $idle,$frgt, $invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ntiy, $ntiy ] -- inactive (current ^)
];
Debugging nonsense
ViewCmd: Commander.CommandProc =
TRUSTED {
Nice.View[pd, "Lark Trunk PD"];
};
Commander.Register["LarkTrunkSmarts", LarkTrunkSmartsInit, "LarkTrunkSmarts <ExportInstance[Strowger]> <ServerPassword[...]>\nInitialize and Export LarkTrunkSmarts"];
Commander.Register["VuLarkTrunkSmarts", ViewCmd, "Program Management variables for Lark Trunk Smarts"];
}.
<< Comments left over from previous system. Think about it all sometime. was in section involving analysis of new pending state. >>
<<This is here to deal with the case where we saw ringing, launched a call, were rebuffed by the selected station, and thus went idle. Now we look available even though the phone is still ringing. Alternative to having a hook state for the back door is to enter the reserved state on rebuff until the disabled event comes in (caller gives up or is transferred.)>>
nb ← ChangeState[info, busy, "Sorry, trunk in use"]; RETURN; };
Swinehart, October 28, 1985 10:01:46 am PST
Log => VoiceUtils, Handle => ID
changes to: DIRECTORY, LarkTrunkSmartsImpl, Reseal, PartyID, nullID, nullConvID, RegisterTrunk, LarkTrunkParseEvent, CmdCall, TrunkSupervise
Swinehart, May 17, 1986 5:37:22 pm PDT
Cedar 6.1
changes to: RegisterTrunk