<> <> <> <> <<>> DIRECTORY BluejaySmarts, GList USING [ DRemove, Nconc ], IO, Jukebox USING [ Handle, IntervalSpec, VoiceDirection ], MBQueue USING [ QueueClientAction ], Process USING [ SecondsToTicks, SetTimeout ], PupSocket USING [ SetGetTimeout, SetRemoteAddress, Socket ], RecordingServiceRegister USING [ jayShh, jukebox, numParties ], RefID USING [ ID ], ThParty USING [ Advance, ConversationInfo, GetConversationInfo, GetKeyTable, GetPartyInfo, PartyInfo, RegisterKey, ReportAction ], ThPartyPrivate USING [ GetPupSocket ], <<Needing this is a travesty. See comments about sockets in ThPartyPrivate.>> Thrush USING [ ActionID, ActionReport, ConversationID, ConvEvent, Credentials, EncryptionKey, NB, NetAddress, notReallyInConv, nullConvID, PartyID, ROPE, SHHH, SmartsID, StateInConv ], ThSmarts, TU USING [ RefAddr ], VoiceStream USING [ Close, VoiceStreamEvent, Handle, NotifyProc, Open, SetSocket, wholeTune ], VoiceUtils USING [ Problem, ProblemFR, ReportFR ] ; BluejaySmartsImpl: CEDAR MONITOR IMPORTS GList, IO, MBQueue, Process, PupSocket, RecordingServiceRegister, ThParty, ThPartyPrivate, TU, VoiceStream, VoiceUtils EXPORTS BluejaySmarts, ThSmarts = { OPEN IO; <> ConversationID: TYPE = Thrush.ConversationID; nullConvID: ConversationID = Thrush.nullConvID; PartyID: TYPE = Thrush.PartyID; SHHH: TYPE = Thrush.SHHH; SmartsID: TYPE = Thrush.SmartsID; StateInConv: TYPE = Thrush.StateInConv; JayInfo: TYPE = BluejaySmarts.JayInfo; NB: TYPE = Thrush.NB; IntID: TYPE = BluejaySmarts.IntID; nullIntID: IntID = BluejaySmarts.nullIntID; IntervalSpec: TYPE = Jukebox.IntervalSpec; IntervalReq: TYPE = BluejaySmarts.IntervalReq; infos: PUBLIC ARRAY[0..100) OF BluejaySmarts.JayInfo_ALL[NIL]; keyDistributionTimeoutInSeconds: INT _ 300; ConvDesc: TYPE = BluejaySmarts.ConvDesc; ConvDescBody: TYPE = BluejaySmarts.ConvDescBody; <<>> NconcIntervals: PROC[l1, l2: LIST OF IntervalReq] RETURNS[LIST OF IntervalReq] = INLINE { RETURN[NARROW[GList.Nconc[l1, l2]]]; }; <> Progress: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, convEvent: Thrush.ConvEvent ] = { ENABLE UNWIND => NULL; <> info: JayInfo = InfoForSmarts[smartsID: convEvent.self.smartsID]; convID: Thrush.ConversationID = convEvent.self.convID; cDesc: ConvDesc _ GetConv[info, convID]; whatNeedsDoing: WhatNeedsDoing; IF cDesc=NIL THEN { VoiceUtils.Problem["No info at Progress", $Bluejay]; RETURN; }; IF convEvent.self.partyID = convEvent.other.partyID THEN { -- own state changed NoteNewState[cDesc, convEvent]; SELECT info.currentConvID FROM convID, nullConvID => NULL; <<(new conversation and we're idle) or (this is the one we like)>> ENDCASE => []_ChangeState[cDesc, $idle, $busy, "One conversation at a time, please"]; <> <> RETURN; }; IF convID#info.currentConvID THEN { ForgetConv[cDesc]; RETURN; }; <> <> whatNeedsDoing _ whatNeedsDoingIf[cDesc.situation.self.state][convEvent.other.state]; SELECT whatNeedsDoing FROM $noop, $ntiy, $reac, $deac => NULL; -- No action is needed, or we don't yet know what one to take. $imp => ERROR; -- This is supposed to be impossible! $xrep => VoiceUtils.Problem["Didn't expect state change report", $Bluejay]; $frgt => ForgetConv[cDesc]; $invl => { VoiceUtils.Problem["Invalid state transition", $Bluejay]; []_ChangeState[cDesc: cDesc, state: $idle, reason: $error, comment: "System Error: Invalid state transition"]; }; $idle => { nb: Thrush.NB; cInfo: ThParty.ConversationInfo; [nb, cInfo] _ ThParty.GetConversationInfo[shh: RecordingServiceRegister.jayShh, convID: convID]; SELECT nb FROM $success => NULL; $noSuchConv => { VoiceUtils.ProblemFR["BluejaySmarts (%g): Conversation disappeared, can't get ConversationInfo", $Bluejay, NIL, TU.RefAddr[cDesc.info]]; ForgetConv[cDesc]; RETURN; }; ENDCASE => ERROR; IF (cInfo.numParties-cInfo.numIdle) <= 1 THEN []_ChangeState[cDesc, $idle, $terminating]; }; ENDCASE => ERROR; }; Substitution: PUBLIC ENTRY PROC[ shh: Thrush.SHHH, convEvent: Thrush.ConvEvent, oldPartyID: Thrush.PartyID, newPartyID: Thrush.PartyID ] = { ENABLE UNWIND => NULL; <> info: JayInfo = InfoForSmarts[smartsID: convEvent.self.smartsID]; cDesc: ConvDesc _ GetConv[info, convEvent.self.convID]; IF cDesc=NIL THEN { VoiceUtils.Problem["No info at Substitution", $Bluejay]; RETURN; }; NoteNewState[cDesc, convEvent]; }; ReportAction: PUBLIC ENTRY PROC[ shh: SHHH, report: Thrush.ActionReport ] = { ENABLE UNWIND => NULL; info: JayInfo = InfoForSmarts[smartsID: report.self.smartsID]; convID: Thrush.ConversationID = report.self.convID; cDesc: ConvDesc _ GetConv[info, convID]; IF cDesc=NIL THEN { VoiceUtils.Problem["can't find conversation for report", $Bluejay]; RETURN; }; SELECT report.actionClass FROM $keyDistribution => { BROADCAST cDesc.keysMightBeDistributed; cDesc.keysDistributed _ TRUE; RETURN; }; ENDCASE=> shh _ shh; -- a place to stand during debugging }; NoteNewState: INTERNAL PROC[cDesc: ConvDesc, convEvent: Thrush.ConvEvent] = { OPEN now: cDesc.situation.self; nb: Thrush.NB; state: StateInConv _ convEvent.self.state; previousState: StateInConv = cDesc.situation.self.state; info: JayInfo = cDesc.info; cDesc.situation _ convEvent^; IF state = previousState THEN RETURN; -- No conceivable value in acting. IF info.currentConvID=nullConvID THEN info.currentConvID _ cDesc.situation.self.convID; SELECT state FROM $notified => { nb _ OpenConnection[cDesc]; -- Set up Bluejay streams, socket stuff IF nb=$success THEN [] _ ChangeState[cDesc, $active]; -- Errors have been dealt with }; $idle, $neverWas => ForgetConv[cDesc]; $active => NULL; -- Need take no action until recording or playback requested. ENDCASE; }; <> <<>> GetConversation: PUBLIC ENTRY PROC [ -- exported to BluejaySmarts smartsID: Thrush.SmartsID, conv: Thrush.ConversationID ] RETURNS [nb: Thrush.NB_$success, cDesc: ConvDesc] = { ENABLE UNWIND => NULL; info: JayInfo _ InfoForSmarts[smartsID]; cDesc _ GetConv[info, conv]; nb _ SELECT TRUE FROM info=NIL => $noSuchSmarts2, conv=nullConvID OR cDesc=NIL => $noSuchConv, conv#info.currentConvID => $notInConv, cDesc.situation.self.state#$active => $convNotActive, ENDCASE => $success; IF nb=$success AND cDesc.keyTable=NIL THEN [, cDesc.keyTable] _ ThParty.GetKeyTable[info.shh, cDesc.situation.self]; }; <> <> <> <> <> DistributeKey: PUBLIC ENTRY PROC [ -- exported to BluejaySmarts cDesc: ConvDesc, key: Thrush.EncryptionKey, wait: BOOL_FALSE ] RETURNS [nb: Thrush.NB, keyIndex: [0..17B]] = TRUSTED { ENABLE UNWIND => NULL; keyID: LONG POINTER TO Thrush.ActionID = LOOPHOLE[LONG[@key]]; num: NAT; [nb, keyIndex] _ ThParty.RegisterKey[credentials: cDesc.situation.self, key: key]; SELECT nb FROM $success, $newKeys => NULL; $noSuchSmarts, $noSuchParty, $noSuchConv, $notInConv, $interfaceError => { VoiceUtils.Problem["Serious problem in DistributeKey"]; RETURN; }; ENDCASE => ERROR; IF wait THEN { <> [nb, num] _ ThParty.ReportAction[report: [other: cDesc.situation.self, requestingParty: cDesc.situation.self.partyID, actionID: keyID^, actionClass: $keyDistribution, actionType: $newKeys], reportToAll: TRUE, selfOnCompletion: TRUE]; SELECT nb FROM $success => NULL; $noSuchSmarts, $noSuchParty, $noSuchConv, $notInConv, $interfaceError => { VoiceUtils.Problem["Serious problem in DistributeKey"]; RETURN; }; ENDCASE => ERROR; IF num=0 THEN RETURN; nb _ $newKeys; cDesc.keysDistributed _ FALSE; Process.SetTimeout[@cDesc.keysMightBeDistributed, Process.SecondsToTicks[keyDistributionTimeoutInSeconds]]; WAIT cDesc.keysMightBeDistributed; IF ~cDesc.keysDistributed THEN { [] _ ChangeState[cDesc, $idle, $error, "Could not distribute encryption key"]; nb _ $keyReportTimedOut; }; }; }; <> <> <> <> SetInterval: PUBLIC ENTRY SAFE PROC [ir: IntervalReq] -- exported to BluejaySmarts RETURNS [nb: Thrush.NB_$success] = CHECKED { ENABLE UNWIND => NULL; IF ir.iSpec.tuneID <0 THEN RETURN[nb: $noTuneSpecified]; IF ir.iSpec.length = -1 THEN ir.iSpec.length _ VoiceStream.wholeTune; IF ir.intID # nullIntID THEN { ir.reportRequested _ TRUE; IssueReport[ir, $scheduled]; }; ir.cDesc.info.intervalReqs _ NconcIntervals[ir.cDesc.info.intervalReqs, LIST[ir]]; }; <> <> IssueReport: INTERNAL PROC[req: IntervalReq, event: VoiceStream.VoiceStreamEvent] = { cDesc: ConvDesc _ req.cDesc; info: JayInfo _ cDesc.info; nb: NB; SELECT event FROM $started, $scheduled => IF ~req.firstInterval THEN RETURN; $finished, $flushed => IF ~req.lastInterval THEN RETURN; ENDCASE; IF cDesc.situation.self.state <= Thrush.notReallyInConv OR NOT req.reportRequested THEN RETURN; nb _ ThParty.ReportAction[ shhh: RecordingServiceRegister.jayShh, report: [ self: cDesc.situation.self, -- placeholder, will be filled in by ThParty other: cDesc.situation.self, requestingParty: req.requestingParty, actionID: req.intID, actionClass: IF req.direction = $record THEN $recording ELSE $playback, actionType: event ], reportToAll: TRUE ].nb; IF nb#$success THEN VoiceUtils.Problem["Bluejay report failed", $Bluejay]; }; <> <<>> IREvent: TYPE = REF IREventRec; IREventRec: TYPE = RECORD [ ir: IntervalReq, event: VoiceStream.VoiceStreamEvent ]; ReportFromBluejay: VoiceStream.NotifyProc = TRUSTED { ir: IntervalReq _ NARROW[clientData]; irE: IREvent; IF ir=NIL OR ir.cDesc=NIL THEN RETURN; -- We don't report actions that do not affect our requests irE _ NEW[IREventRec _ [ir, event]]; ir.cDesc.info.notifications.QueueClientAction[QdReportFromBluejay, irE]; }; QdReportFromBluejay: ENTRY PROC[r: REF] = { <> <> <<>> irE: IREvent _ NARROW[r]; ir: IntervalReq _ irE.ir; event: VoiceStream.VoiceStreamEvent _ irE.event; cDesc: ConvDesc _ ir.cDesc; info: JayInfo _ cDesc.info; IF info.intervalReqs=NIL THEN VoiceUtils.Problem["Unexpected report from Bluejay", $Bluejay]; SELECT event FROM $flushed => { <> FOR irS: LIST OF IntervalReq _ info.intervalReqs, irS.rest WHILE irS#NIL DO IF NOT (irS.first.state=$started AND irS.first.direction=record) THEN irS.first.state _ $flushed; IF ir=irS.first THEN EXIT; ENDLOOP; }; $started, $finished => { ir1: IntervalReq _ NIL; <> FOR irS: LIST OF IntervalReq _ info.intervalReqs, irS.rest WHILE irS#NIL DO ir1 _ irS.first; IF ir=ir1 THEN EXIT; IssueReport[ir1, $flushed]; info.intervalReqs _ irS.rest; ENDLOOP; <> IF ir=ir1 THEN { IF event=$finished THEN { info.intervalReqs _ info.intervalReqs.rest; -- Shorten list IF ir1.state=$flushed THEN event _ $flushed; }; IssueReport[ir, event]; IF event=$started AND ir1.state # $flushed THEN ir1.state _ $started; }; }; ENDCASE => VoiceUtils.Problem["Unexpected report from Bluejay", $Bluejay]; }; <<>> <> <<>> GetConv: --PUBLIC-- INTERNAL PROC[info: JayInfo, convID: ConversationID _ nullConvID] RETURNS [ cDesc: ConvDesc ] = { IF info = NIL THEN RETURN[NIL]; IF convID = nullConvID THEN convID _ info.currentConvID; IF convID = nullConvID THEN RETURN[NIL]; FOR convs: LIST OF BluejaySmarts.ConvDesc _ info.conversations, convs.rest WHILE convs#NIL DO IF convs.first.situation.self.convID = convID THEN RETURN[convs.first]; ENDLOOP; cDesc _ NEW[ConvDescBody_[]]; cDesc.situation.self.convID _ convID; cDesc.info _ info; info.conversations _ CONS[cDesc, info.conversations]; }; InfoForSmarts: INTERNAL PROC [ smartsID: SmartsID ] RETURNS [ info: JayInfo_NIL ] = { FOR i: NAT IN [0..RecordingServiceRegister.numParties) DO IF smartsID=infos[i].credentials.smartsID THEN RETURN [ infos[i] ]; ENDLOOP; }; ChangeState: INTERNAL PROC[ cDesc: ConvDesc, state: StateInConv, reason: ATOM_NIL, comment: ROPE_NIL, secondTry: BOOL_FALSE] RETURNS [nb: NB_$success] = { convEvent: Thrush.ConvEvent; IF cDesc.situation.self.state = state THEN RETURN; [nb, convEvent] _ ThParty.Advance[ shhh: RecordingServiceRegister.jayShh, credentials: cDesc.situation.self, state: state, reason: reason, comment: comment, reportToAll: TRUE -- parameterize if other states than $idle and $active become possible ]; SELECT nb FROM $success => NoteNewState[cDesc, convEvent]; $convIdle, $bilateralConv, $conferenceConv, $voiceTerminalUnavailable, $stateMismatch, $interfaceError => { <> IF secondTry THEN ERROR; -- Don't loop forever NoteNewState[cDesc, convEvent]; IF cDesc.situation.self.state#$idle THEN []_ChangeState[cDesc, $idle, $error, "Connection failed due to voice terminal restrictions or system error", TRUE]; }; $noSuchSmarts, $noSuchParty, $noSuchConv, $notInConv => VoiceUtils.Problem["Serious problem at Advance", $Bluejay]; <<Should fail, as Lark does, so as to do a ForgetConv, create a new instance, etc. Haven't worked on this yet. >> ENDCASE =>ERROR; }; <<>> ForgetConv: INTERNAL PROC[cDesc: ConvDesc ] = { info: JayInfo _ cDesc.info; CloseConnection[cDesc]; IF cDesc.situation.self.convID = info.currentConvID THEN info.currentConvID _ nullConvID; info.conversations _ NARROW[GList.DRemove[cDesc, info.conversations]]; }; OpenConnection: INTERNAL PROC[cDesc: ConvDesc] RETURNS[nb: NB] = TRUSTED { info: JayInfo = cDesc.info; pInfo: ThParty.PartyInfo; socket: PupSocket.Socket; remoteAddress: Thrush.NetAddress; IF info.stream=NIL THEN info.stream _ VoiceStream.Open[jukebox: RecordingServiceRegister.jukebox, proc: ReportFromBluejay]; [nb, pInfo] _ ThParty.GetPartyInfo[credentials: cDesc.situation.self, nameReq: $none, allParties: TRUE]; IF nb # $success OR pInfo[0].partyID=0 THEN { VoiceUtils.Problem["No conversation info, or incomplete", $Bluejay]; RETURN; }; -- This is really bad! <<Neither this nor Bluejay deals at all properly with multicasting. Worse yet on recording! See also ThPartyPrivate discussion about sockets, and discussion of socket assignments in LarkSmartsSupImpl.ComputeConnection.>> socket _ ThPartyPrivate.GetPupSocket[pInfo[1].partyID]; <> IF socket=NIL THEN ERROR; remoteAddress _ pInfo[1].socket; -- Transmit to remote net and host remoteAddress.socket _ pInfo[0].socket.socket; -- Transmit to own assigned socket ID PupSocket.SetRemoteAddress[socket, remoteAddress]; PupSocket.SetGetTimeout[socket, 100]; VoiceStream.SetSocket[socket: socket, handle: info.stream]; VoiceUtils.ReportFR["C %d ", $Bluejay, NIL, card[info.credentials.smartsID]]; }; <> <>> CloseConnection: INTERNAL PROC[cDesc: ConvDesc] = TRUSTED { info: JayInfo = cDesc.info; IF info.stream#NIL THEN VoiceStream.Close[info.stream]; info.stream _ NIL; VoiceUtils.ReportFR["D %d ", $Bluejay, NIL, card[info.credentials.smartsID]]; }; <<>> <> WhatNeedsDoing: TYPE = ATOM; -- { <> <<$noop, $idle, $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 _ [ <> <<>> << 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.)>> <> [ $imp, $noop, $frgt, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $noop, $ntiy ], -- idle [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- failed [ $imp, $imp, $imp , $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- reserved [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- parsing [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp , $imp, $imp, $imp ], -- initiating [ $imp, $idle,$idle, $invl, $invl, $invl, $xrep, $noop, $noop, $ntiy, $noop, $ntiy ], -- notified <> [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- ringback [ $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp, $imp ], -- ringing [ $imp, $idle,$idle,$invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $noop, $ntiy ], -- canActivate [ $imp, $idle,$idle,$invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $reac, $deac ], -- active [ $imp, $idle,$idle,$invl, $invl, $invl, $xrep, $xrep, $noop, $ntiy, $ntiy, $ntiy ] -- inactive (current ^) <<failed column and possibly idle column is bogus!>> ]; }. <> <> <> <> < service, Jay => Recording>> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <<>>