DIRECTORY
CardTable USING [ Create, EachPairAction, Fetch, Pairs, Ref, Store ],
Commander USING [ CommandProc, Register ],
CommandTool USING [ NextArgument ],
Convert USING [ IntFromRope ],
IO,
Lark USING [ CommandEventSequence, LarkModel, Machine, SHHH ],
LarkControl USING [ GetLark, SetApplicationMode ],
LarkOpsRpcControl USING [ ImportNewInterface, InterfaceRecord ],
LarkSmarts,
LarkSmartsMonitorImpl,
LarkSmartsRpcControl USING [ExportInterface, InterfaceName, UnexportInterface],
LupineRuntime,
MBQueue USING [ Create, Flush, Queue, QueueClientAction ],
NamesGV USING [ GVGetAttribute ],
NamesRPC USING [ StartConversation ],
Nice USING [ LarkConLogStream ],
Process USING [ Detach, Pause, SecondsToTicks ],
Pup USING [ nullSocket ],
RefID USING [ ID, Reseal, Unseal ],
Rope USING [Concat, Equal],
RPC USING [ AuthenticateFailed, EncryptionKey, ExportFailed, GetCaller, ImportFailed ],
ThNet USING [ pd ],
ThPartyPrivate USING [ AssignSmartsID, RegisterLocal, UnsealSmarts ],
ThParty USING [ Deregister, Enable, SmartsInterfaceRecord ],
Thrush USING [
Credentials, epoch, Epoch, NB, NetAddress, noAddress, nullID, PartyID, PartyType, ROPE, SmartsID, unencrypted ],
ThSmartsPrivate
USING [
CheckHookState, EnterLarkState, Fail, LarkInfo, LarkInfoBody, LarkParseEvent, LarkProgress, LarkReportAction, LarkSubstitution, NoteNewState, QueueLarkAction, RegisterTrunk, SetupTimeouts, SmartsInfo, SmartsInfoBody ],
ThSmartsRpcControl,
ThVersions,
Triples USING [ Make ],
TU,
VoiceUtils USING [ CmdOrToken, CurrentPasskey, DNFProc, InstanceFromNetAddress, MakeRName, Problem, ProblemFR, RegisterWhereToReport, ReportFR, RnameToRspec, Rspec, WhereProc ]
;
Registration/Deregistration/Initialization
Register:
PUBLIC
PROC[
shh: SHHH, -- encrypts connection
oldSmartsID: Thrush.SmartsID,
oldEpoch: Thrush.Epoch,
machine: Lark.Machine, -- machine name for registering Lark --
model: Lark.LarkModel,
authenticated: BOOL←FALSE,
clientInstance: ROPE
] RETURNS [ smartsID: Thrush.SmartsID←nullID, epoch: Thrush.Epoch←Thrush.epoch ] = {
This procedure is synchronous with the Lark. We do enough to get started, then serialize the actual registration action with input and fail events.
larkSh: Lark.SHHH;
smartsInfo: SmartsInfo;
larkInfo: LarkInfo;
inputQueue: MBQueue.Queue;
larkInterface: LarkOpsRpcControl.InterfaceRecord←NIL;
fullRname: ROPE = RPC.GetCaller[shh];
partyRname: ROPE ← fullRname;
dbRname: ROPE;
serviceName: ROPE;
partyType: Thrush.PartyType ← $telephone;
s: VoiceUtils.Rspec ← NIL;
netAddress: Thrush.NetAddress = [machine.net, machine.host, Pup.nullSocket];
The LarkSmarts interface definition suggests that a Lark could present enough information during registration to be simply re-validated (kind of like a hand stamp at a dance) without any great effort. That turns out not to be worth it. If a Lark registers when already registered, we get rid of any old information about it first. Don't assume that its type has not changed from $telephone to $service or vice/versa
larkInfo ← LarkInfoForNetAddress[netAddress].info;
IF larkInfo#
NIL
AND ~larkInfo.failed
THEN {
inputQueue ← larkInfo.inputQueue;
ThSmartsPrivate.Fail[larkInfo, "Lark is reregistering", FALSE]; -- already rebooted
}
ELSE inputQueue ← MBQueue.Create[];
Develop partyRname: the actual RName to be used to represent this party; and dbRname: the name under which database items are stored in Grapevine. The rName obtained from the conversation handle may have either form initially, since Finches will supply the former and Larks will supply the latter. Also, obtain the name of the service represented by this Rname, if any, from the database.
IF (s←VoiceUtils.RnameToRspec[partyRname])#
NIL
AND VoiceUtils.RnameToRspec[s.simpleName] # NIL THEN partyRname←s.simpleName;
Above converts from, say, Swinehart.pa.lark to Swinehart.pa.
Also, from Swinehart.pa to Swinehart.pa. Strips one registry if there are two or more.
dbRname ← partyRname.Concat[larkRegistry];
serviceName ← NamesGV.GVGetAttribute[dbRname, $service, NIL];
IF serviceName#NIL THEN partyType ← $service;
Get encryption taken care of
larkSh ← IF NOT ThNet.pd.encryptionRequested THEN Thrush.unencrypted
ELSE NamesRPC.StartConversation [
caller: myName.instance,
callee: fullRname,
key: serverPassword,
level:
--<<ECB>>--CBCCheck !
RPC.AuthenticateFailed=> {
VoiceUtils.ProblemFR["Can't authenticate %g to %g", $System,
NIL,
rope[myName.instance], rope[fullRname]]; GOTO NotSmart; }];
Make sure we can talk to the Lark
larkInterface ← LarkOpsRpcControl.ImportNewInterface[
interfaceName: [type: "Lark.Lark", instance: clientInstance] !
RPC.ImportFailed=> {
VoiceUtils.ProblemFR["Can't import Lark interface from %g", $System, NIL, rope[clientInstance]]; GOTO NotSmart; }];
larkInfo ←
NEW[ThSmartsPrivate.LarkInfoBody ← [
interface: larkInterface,
shh: larkSh,
netAddress: netAddress,
model: model,
inputQueue: inputQueue,
textToSpeech: Rope.Equal[serviceName, "Text-to-Speech", FALSE],
scratchEv: NEW[Lark.CommandEventSequence[15]]
]];
smartsID ← ThPartyPrivate.AssignSmartsID[];
smartsInfo ←
NEW[SmartsInfoBody← [
smartsID: smartsID,
partyType: partyType,
ParseEvent: ThSmartsPrivate.LarkParseEvent,
NoteNewStateP: ThSmartsPrivate.NoteNewState,
requests: MBQueue.Create[],
larkInfo: larkInfo
]];
inputQueue.QueueClientAction[
QdLarkRegistration,
NEW[RegistrationInfoSpec ←
[smartsInfo, partyRname, serviceName, fullRname, clientInstance]]];
EXITS
NotSmart => RETURN;
};
RegistrationInfo: TYPE = REF RegistrationInfoSpec;
RegistrationInfoSpec: TYPE = RECORD [
smartsInfo: SmartsInfo,
partyRname: ROPE,
serviceName: ROPE,
fullRname: ROPE,
clientInstance: ROPE
];
QdLarkRegistration:
ENTRY
PROC[r:
REF] = {
ENABLE UNWIND => NULL;
nb: Thrush.NB;
registrationInfo: RegistrationInfo ← NARROW[r];
smartsInfo: SmartsInfo ← registrationInfo.smartsInfo;
larkInfo: LarkInfo ← smartsInfo.larkInfo;
credentials: Thrush.Credentials;
smartsID: Thrush.SmartsID;
smarts: REF;
localSmarts: ThParty.SmartsInterfaceRecord;
localSmarts ← ThSmartsRpcControl.NewInterfaceRecord[];
localSmarts.clientStubProgress ← ThSmartsPrivate.LarkProgress;
localSmarts.clientStubSubstitution ← ThSmartsPrivate.LarkSubstitution;
localSmarts.clientStubReportAction ← ThSmartsPrivate.LarkReportAction;
[nb, credentials] ← ThPartyPrivate.RegisterLocal[
rName: (
SELECT smartsInfo.partyType
FROM
$telephone => registrationInfo.partyRname,
ENDCASE=> registrationInfo.serviceName),
type: smartsInfo.partyType,
interfaceRecord: localSmarts,
smartsID: smartsInfo.smartsID, -- pre-allocated so Lark could know.
properties: [role: $voiceTerminal, netAddress: larkInfo.netAddress]
];
IF nb # $success
OR (smartsID𡤌redentials.smartsID) = nullID
THEN {
VoiceUtils.ProblemFR["Can't register Lark: %g, %g",$System,NIL,
rope[registrationInfo.fullRname], rope[registrationInfo.clientInstance]]; GOTO NotSmart; };
No further bad things are expected to happen.
larkInfo.larkSmartsInfo ← smartsInfo;
smarts ← RefID.Unseal[smartsID];
IF smarts=NIL THEN ERROR;
Triples.Make[$SmartsData, smarts, smartsInfo];
IF smartsInfo.partyType # $service
THEN
[
--nb--, smartsInfo.otherSmartsID]←
ThSmartsPrivate.RegisterTrunk[ smartsID, smartsInfo, registrationInfo.partyRname ];
VoiceUtils.ReportFR[" (%g = %g)", $Smarts, smartsInfo, rope[registrationInfo.clientInstance], TU.RefAddr[smarts]];
IF potentialServiceProvider#
NIL
THEN potentialServiceProvider[
credentials, smartsInfo];
If anyone has registered, call them. They may want to register a service with our party. See LarkSmartsSynthImpl for an (the) example.
RegisterLarkInfo[larkInfo];
Subsequent input events must relate to new world.
NoteApplicationState[larkInfo, 'r];
[]←ThSmartsPrivate.CheckHookState[larkInfo];
If the phone is on-hook, mark us ready to go, and set up any once-only Lark params.
EXITS
NotSmart => RETURN;
};
EnableSmarts:
PUBLIC
ENTRY
PROC[r:
REF] = {
ENABLE UNWIND => NULL;
larkInfo: LarkInfo ← NARROW[r];
debugging: BOOL;
smartsInfo: SmartsInfo ← larkInfo.larkSmartsInfo;
IF larkInfo.failed THEN RETURN; -- Lost a race
IF ThParty.Enable[ smartsID: smartsInfo.smartsID] # $success
OR
(smartsInfo.otherSmartsID # nullID
AND
ThParty.Enable[smartsID: smartsInfo.otherSmartsID] # $success) THEN {
VoiceUtils.Problem["Could not enable", $Smarts, smartsInfo];
ThSmartsPrivate.Fail[larkInfo, "Lark failure due to failure to enable party",
FALSE];
Notification would loop
};
ThSmartsPrivate.EnterLarkState[ larkInfo, idle ]; -- Reset the Lark
We're debugging if the lark's mode field in the database has the value "D" or "d"
debugging ← Rope.Equal[case:
FALSE, s2: "D",
s1: NamesGV.GVGetAttribute[
VoiceUtils.InstanceFromNetAddress[larkInfo.netAddress, larkRegistry], $mode, "O"]];
larkInfo.debugging ← debugging;
ThSmartsPrivate.SetupTimeouts[ larkInfo, debugging ]; -- Reset the Lark
NoteApplicationState[larkInfo, 'R];
};
Deregister:
PUBLIC
ENTRY
PROC[r:
REF] = {
Serialized with input events. Called (queued) only from LarkOutImpl.Fail.
larkInfo: LarkInfo ← NARROW[r];
smartsID: SmartsID ← nullID;
otherSmartsID: SmartsID ← nullID;
smartsInfo: SmartsInfo ← IF larkInfo#NIL THEN larkInfo.larkSmartsInfo ELSE NIL;
smarts: REF;
IF smartsInfo =
NIL
THEN {
VoiceUtils.Problem["No Smarts to Deregister", $System];
RETURN;
};
NoteApplicationState[larkInfo, 'U];
smartsID ← smartsInfo.smartsID;
smarts ← ThPartyPrivate.UnsealSmarts[smartsID];
VoiceUtils.ReportFR["Smarts %d (%g) is dead", $Smarts, smartsInfo,
card[Reseal[smarts]], TU.RefAddr[smarts]];
smartsInfo.failed ← TRUE;
smartsInfo.larkInfo.larkSmartsInfo ← NIL;
IF smartsInfo.larkInfo.larkTrunkSmartsInfo#
NIL
THEN {
smartsInfo.larkInfo.larkTrunkSmartsInfo.failed ← TRUE;
smartsInfo.larkInfo.larkTrunkSmartsInfo.larkInfo ← NIL;
};
smartsInfo.larkInfo.larkTrunkSmartsInfo ← NIL;
smartsInfo.larkInfo ← NIL;
otherSmartsID ← smartsInfo.otherSmartsID;
IF otherSmartsID # nullID THEN [--nb--]←ThParty.Deregister[smartsID: otherSmartsID];
[--nb--]←ThParty.Deregister[smartsID: smartsID];
smartsInfo.requests.Flush[]; -- Abandon progress-notification queue
};
NoteApplicationState:
INTERNAL
PROC[info: LarkInfo, state:
CHAR] = {
LarkControl.SetApplicationMode[LarkControl.GetLark[info.netAddress], state];
};
Login:
PUBLIC
PROC[shh:
SHHH←, smartsID: SmartsID←, authenticated:
BOOL←
TRUE] = {
NULL;};
Not implemented
Initialization, export "LarkSmarts"
KillLark: Commander.CommandProc = {
netAddress: Thrush.NetAddress =
[[173B],[Convert.IntFromRope[CommandTool.NextArgument[cmd], 8]], Pup.nullSocket];
larkInfo: LarkInfo ← LarkInfoForNetAddress[netAddress].info;
IF larkInfo=NIL THEN RETURN;
ThSmartsPrivate.Fail[larkInfo, "Lark failed through operator request", TRUE];
};
myName: LarkSmartsRpcControl.InterfaceName;
serverPassword: RPC.EncryptionKey;
LarkSmartsInit: Commander.CommandProc = {
ENABLE {
RPC.ExportFailed => { VoiceUtils.Problem["LarkSmarts export failed", $System]; GOTO Failed; };
};
instance:
ROPE = VoiceUtils.MakeRName[style: rName, name:
VoiceUtils.CmdOrToken[cmd: cmd, key: "ThrushServerInstance", default: "Strowger.Lark"]];
serverPassword ← VoiceUtils.CurrentPasskey[VoiceUtils.CmdOrToken[
cmd: cmd, key: "ThrushServerPassword", default: "MFLFLX"]];
myName ← [
type: "LarkSmarts.Lark",
instance: instance,
version: ThVersions.ThrushVR
];
VoiceUtils.RegisterWhereToReport[proc: WhereIsLarkLog, where: $Lark];
VoiceUtils.RegisterWhereToReport[proc: WhereIsLarkLogFerSherr, where: $LarkDetailed, defaultIfNotFound: FerSherrDefault];
Same as $Lark, except doesn't print at all if debugging viewer not found.
VoiceUtils.RegisterWhereToReport[proc: WhereIsSmartsLog, where: $Smarts];
LarkSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE];
LarkSmartsRpcControl.ExportInterface[
interfaceName: myName,
user: instance,
password: serverPassword
];
VoiceUtils.ReportFR["Export[LarkSmarts.Lark, %s]", $System, NIL, rope[myName.instance]];
EXITS
Failed =>
LarkSmartsRpcControl.UnexportInterface[!LupineRuntime.BindingError=>CONTINUE];
};
RegisterServiceProvider:
PUBLIC
PROC[
serviceProvider: PROC[credentials: Thrush.Credentials, smartsInfo: SmartsInfo]] = {
See LarkSmartsSynthImpl for an (the) example. See Register (above) for invocation of potentialServiceProvider.
potentialServiceProvider ← serviceProvider;
};
RegisterLarkInfo:
PROC[info: LarkInfo] = {
index: CARD ← LarkInfoForNetAddress[info.netAddress].index;
[] ← larkInfos.Store[index, info];
};
LarkInfoForNetAddress:
PROC[netAddress: Thrush.NetAddress]
RETURNS[info: LarkInfo, index: CARD] = TRUSTED {
netAddress.socket ← Pup.nullSocket; -- only interested in host values
index ← LOOPHOLE[LONG[@netAddress], LONG POINTER TO CARD]^;
IF larkInfos=NIL THEN {
larkInfos ← CardTable.Create[mod: 59];
TRUSTED { Process.Detach[FORK LarkWatchDogTimerProcess[]]; };
};
info ← NARROW[larkInfos.Fetch[index].val];
};
LarkWatchDogTimerProcess:
PROC = {
Polls active Larks at intervals, so that non-working ones can be discovered and removed from registered service quickly. This happens whether the Larks are involved in conversations or not.
LarkWatchDogTimer: CardTable.EachPairAction = {
[key: CardTable.Key, val: CardTable.Val] RETURNS [quit: BOOLEAN]
info: LarkInfo = NARROW[val];
IF info=NIL OR info.failed THEN RETURN[FALSE];
ThSmartsPrivate.QueueLarkAction[info,
NEW[
BOOL←
TRUE]];
Special case requesting simplest status check. If Lark doesn't return, will trigger
failure of LarkSmarts.
Process.Pause[Process.SecondsToTicks[ThNet.pd.larkWatchDogTimerInterval]];
RETURN[FALSE];
};
DO
IF ThNet.pd.runLarkWatchDogTimer THEN [] ← larkInfos.Pairs[LarkWatchDogTimer];
Process.Pause[Process.SecondsToTicks[1]];
ENDLOOP;
};
}.