pub struct Driver<D: CaDevice> { /* private fields */ }Implementations§
Source§impl<D: CaDevice> Driver<D>
impl<D: CaDevice> Driver<D>
Sourcepub fn new(device: D) -> Self
pub fn new(device: D) -> Self
New driver over device, single transport connection.
Examples found in repository?
15fn main() -> std::io::Result<()> {
16 // Script a module that accepts the transport connection (C_T_C_Reply).
17 let dev = MockCaDevice::new([vec![tags::C_T_C_REPLY, 0x01, 0x01]]);
18 let mut driver = Driver::new(dev);
19
20 // Bring the interface up: reset + open the transport connection.
21 driver.init()?;
22 println!("init: sent {} device op(s)", driver.device().ops.len());
23
24 // Pump the device. When readable it reads a frame and feeds the stack;
25 // otherwise it advances the EN 50221 poll cadence by the timeout.
26 for step in 0..5 {
27 let read = driver.pump(Duration::from_millis(100))?;
28 println!("pump {step}: processed_frame={read}");
29 }
30
31 // Anything the host application needs to act on surfaces as a Notification.
32 for note in driver.take_notifications() {
33 match note {
34 Notification::CamReady => println!("note: CAM ready — safe to send ca_pmt"),
35 Notification::ApplicationInfo { menu, .. } => {
36 println!("note: application_information menu={menu:?}")
37 }
38 Notification::CaInfo { ca_system_ids } => {
39 println!("note: ca_info system_ids={ca_system_ids:?}")
40 }
41 other => println!("note: {other:?}"),
42 }
43 }
44
45 // The mock records every device op (writes/ioctls) — handy for assertions.
46 println!("total recorded device ops: {}", driver.device().ops.len());
47 Ok(())
48}Sourcepub fn with_clock(self, clock: impl Fn() -> Instant + 'static) -> Self
pub fn with_clock(self, clock: impl Fn() -> Instant + 'static) -> Self
Override Self::pump’s clock source (r10-W-21) — Instant::now
by default. A test uses this to simulate an arbitrary elapsed jump
deterministically, in place of a real sleep.
Sourcepub fn managed_ca(&self) -> &ManagedCa
pub fn managed_ca(&self) -> &ManagedCa
The slot’s managed CAS-layer state (#763 Layer 1) — the active
service set built via add_service.
Sourcepub fn device(&self) -> &D
pub fn device(&self) -> &D
Borrow the underlying device (e.g. to inspect a mock’s recorded ops).
Examples found in repository?
15fn main() -> std::io::Result<()> {
16 // Script a module that accepts the transport connection (C_T_C_Reply).
17 let dev = MockCaDevice::new([vec![tags::C_T_C_REPLY, 0x01, 0x01]]);
18 let mut driver = Driver::new(dev);
19
20 // Bring the interface up: reset + open the transport connection.
21 driver.init()?;
22 println!("init: sent {} device op(s)", driver.device().ops.len());
23
24 // Pump the device. When readable it reads a frame and feeds the stack;
25 // otherwise it advances the EN 50221 poll cadence by the timeout.
26 for step in 0..5 {
27 let read = driver.pump(Duration::from_millis(100))?;
28 println!("pump {step}: processed_frame={read}");
29 }
30
31 // Anything the host application needs to act on surfaces as a Notification.
32 for note in driver.take_notifications() {
33 match note {
34 Notification::CamReady => println!("note: CAM ready — safe to send ca_pmt"),
35 Notification::ApplicationInfo { menu, .. } => {
36 println!("note: application_information menu={menu:?}")
37 }
38 Notification::CaInfo { ca_system_ids } => {
39 println!("note: ca_info system_ids={ca_system_ids:?}")
40 }
41 other => println!("note: {other:?}"),
42 }
43 }
44
45 // The mock records every device op (writes/ioctls) — handy for assertions.
46 println!("total recorded device ops: {}", driver.device().ops.len());
47 Ok(())
48}Sourcepub fn device_mut(&mut self) -> &mut D
pub fn device_mut(&mut self) -> &mut D
Mutably borrow the underlying device (e.g. to script a mock’s inbound frames between pumps).
Sourcepub fn next_timer(&self) -> Option<Duration>
pub fn next_timer(&self) -> Option<Duration>
The poll delay the stack most recently requested, if any.
Sourcepub fn take_notifications(&mut self) -> Vec<Notification>
pub fn take_notifications(&mut self) -> Vec<Notification>
Drain the notifications collected so far.
Examples found in repository?
15fn main() -> std::io::Result<()> {
16 // Script a module that accepts the transport connection (C_T_C_Reply).
17 let dev = MockCaDevice::new([vec![tags::C_T_C_REPLY, 0x01, 0x01]]);
18 let mut driver = Driver::new(dev);
19
20 // Bring the interface up: reset + open the transport connection.
21 driver.init()?;
22 println!("init: sent {} device op(s)", driver.device().ops.len());
23
24 // Pump the device. When readable it reads a frame and feeds the stack;
25 // otherwise it advances the EN 50221 poll cadence by the timeout.
26 for step in 0..5 {
27 let read = driver.pump(Duration::from_millis(100))?;
28 println!("pump {step}: processed_frame={read}");
29 }
30
31 // Anything the host application needs to act on surfaces as a Notification.
32 for note in driver.take_notifications() {
33 match note {
34 Notification::CamReady => println!("note: CAM ready — safe to send ca_pmt"),
35 Notification::ApplicationInfo { menu, .. } => {
36 println!("note: application_information menu={menu:?}")
37 }
38 Notification::CaInfo { ca_system_ids } => {
39 println!("note: ca_info system_ids={ca_system_ids:?}")
40 }
41 other => println!("note: {other:?}"),
42 }
43 }
44
45 // The mock records every device op (writes/ioctls) — handy for assertions.
46 println!("total recorded device ops: {}", driver.device().ops.len());
47 Ok(())
48}Sourcepub fn init(&mut self) -> Result<()>
pub fn init(&mut self) -> Result<()>
Bring the interface up (reset + open the transport connection).
Examples found in repository?
15fn main() -> std::io::Result<()> {
16 // Script a module that accepts the transport connection (C_T_C_Reply).
17 let dev = MockCaDevice::new([vec![tags::C_T_C_REPLY, 0x01, 0x01]]);
18 let mut driver = Driver::new(dev);
19
20 // Bring the interface up: reset + open the transport connection.
21 driver.init()?;
22 println!("init: sent {} device op(s)", driver.device().ops.len());
23
24 // Pump the device. When readable it reads a frame and feeds the stack;
25 // otherwise it advances the EN 50221 poll cadence by the timeout.
26 for step in 0..5 {
27 let read = driver.pump(Duration::from_millis(100))?;
28 println!("pump {step}: processed_frame={read}");
29 }
30
31 // Anything the host application needs to act on surfaces as a Notification.
32 for note in driver.take_notifications() {
33 match note {
34 Notification::CamReady => println!("note: CAM ready — safe to send ca_pmt"),
35 Notification::ApplicationInfo { menu, .. } => {
36 println!("note: application_information menu={menu:?}")
37 }
38 Notification::CaInfo { ca_system_ids } => {
39 println!("note: ca_info system_ids={ca_system_ids:?}")
40 }
41 other => println!("note: {other:?}"),
42 }
43 }
44
45 // The mock records every device op (writes/ioctls) — handy for assertions.
46 println!("total recorded device ops: {}", driver.device().ops.len());
47 Ok(())
48}Sourcepub fn send_ca_pmt(&mut self, ca_pmt: &[u8]) -> Result<()>
pub fn send_ca_pmt(&mut self, ca_pmt: &[u8]) -> Result<()>
Request the module descramble the services in ca_pmt (a serialized
ca_pmt APDU body, e.g. from dvb_ci::build_ca_pmt).
Sourcepub fn descramble(&mut self, pmt_section: &[u8]) -> Result<()>
pub fn descramble(&mut self, pmt_section: &[u8]) -> Result<()>
Descramble the services in a PMT section: the stack filters the PMT’s
CA_descriptors to the CAM’s advertised CAIDs and sends a ca_pmt
(list_management = only, cmd_id = ok_descrambling). The outcome
surfaces as Notification::CaPmtReply. Call after the CAM is ready and
its ca_info has been received (otherwise no CAID filter is applied).
Sourcepub fn descramble_programs(&mut self, pmt_sections: &[&[u8]]) -> Result<()>
pub fn descramble_programs(&mut self, pmt_sections: &[&[u8]]) -> Result<()>
Descramble a set of programmes in one CA-PMT list (first/more/last),
replacing any previously selected set. Each element is a raw PMT section.
Sourcepub fn add_program(&mut self, pmt_section: &[u8]) -> Result<()>
pub fn add_program(&mut self, pmt_section: &[u8]) -> Result<()>
Add one programme to the descrambled set (list_management = add) without
re-listing the others — for a capacity manager adding a viewer’s service.
Sourcepub fn remove_program(&mut self, pmt_section: &[u8]) -> Result<()>
pub fn remove_program(&mut self, pmt_section: &[u8]) -> Result<()>
Remove one programme from the descrambled set (list_management = update,
cmd_id = not_selected) — tells the CAM to stop descrambling it.
Sourcepub fn add_service(&mut self, pmt: &PmtSection<'_>) -> Result<(), CaError>
pub fn add_service(&mut self, pmt: &PmtSection<'_>) -> Result<(), CaError>
Build + send the ca_pmt for pmt (via
dvb_ci::builder::build_ca_pmt, ETSI EN 50221 §8.4.3.4 Table 25) and
track it in the slot’s managed active-service set (#763 Layer 1).
Additive alongside the raw send_ca_pmt and the
existing multi-programme API
(descramble_programs/
add_program).
list_management (EN 50221 Table 25) is auto-selected from the tracked
set: Only when this is the first service added to an empty managed
set, Add when joining an already-active set. (Contrast the raw
add_program, which always sends Add and leaves
list-management sequencing to the caller.)
§Errors
CaError::NoCaDescriptor if pmt carries no CA_descriptor
(ISO/IEC 13818-1 §2.6.16, tag 0x09) at programme or
elementary-stream level — there would be nothing for the CAM to
descramble. CaError::Io if sending the built ca_pmt fails.
Sourcepub fn update_service(&mut self, pmt: &PmtSection<'_>) -> Result<(), CaError>
pub fn update_service(&mut self, pmt: &PmtSection<'_>) -> Result<(), CaError>
Update (re-send) the ca_pmt for a programme already in the managed
set, using ca_pmt_list_management = update (ETSI EN 50221 §8.4.3.4
Table 25, ca_pmt_list_management value 0x05 “update”). EN 50221
p. 31: “the CA PMT of a programme already in the list is sent again
because the version_number or the current_next_indicator has changed.
… Since the list management commands act only at the programme level,
any changes at the elementary stream level in an existing programme
must be signalled by an ‘update’ command with the complete elementary
stream list re-sent.” The same page notes that re-sending add for an
already-existing programme is already equivalent to update, so
add_service remains spec-conforming; this method
exists as the explicit, clearer entry point for that case.
The stored service record is replaced with the new PMT bytes, so
later remove_service and the periodic
re-query use the new PMT.
§Errors
CaError::NotManaged if pmt.program_number is not currently
managed. CaError::NoCaDescriptor if pmt carries no
CA_descriptor (ISO/IEC 13818-1 §2.6.16, tag 0x09) — the stored
record is left unchanged. CaError::Serialize if the projected
ca_pmt has no valid wire encoding — the record is left unchanged and
nothing is sent. CaError::Io if sending the built ca_pmt fails.
Sourcepub fn remove_service(&mut self, program_number: u16) -> Result<(), CaError>
pub fn remove_service(&mut self, program_number: u16) -> Result<(), CaError>
Stop descrambling a previously-added service (#763 Task 6): sends the
removal ca_pmt (list_management = update, cmd_id = not_selected,
EN 50221 §8.4.3.4 Table 25) via the existing
remove_program path — re-driving it with the
raw PMT bytes stashed at add_service time — then
drops the service from the managed set.
Removing a program_number that isn’t currently tracked (never
add_service’d, or already removed) is a no-op, not an error:
CaError has no not-found arm, and remove_service is idempotent.
§Errors
CaError::Io if sending the removal ca_pmt fails.
Sourcepub fn set_requery_interval(&mut self, interval: Duration)
pub fn set_requery_interval(&mut self, interval: Duration)
Set the entitlement re-query cadence (#763 Task 5): every
interval, the driver re-sends each actively-managed service’s
ca_pmt as a list_management = update pair — cmd_id = query
immediately followed by cmd_id = ok_descrambling — see
requery_tick for why both, #1032. Duration::ZERO disables
re-query. Opt-in: defaults to managed::REQUERY_DEFAULT
(Duration::ZERO) at construction (the periodic re-query is hardware-verified
on an entitled AlphaCrypt module since #1032, but stays opt-in).
Sourcepub fn set_cat(&mut self, cat: &CatSection<'_>) -> Result<(), CaError>
pub fn set_cat(&mut self, cat: &CatSection<'_>) -> Result<(), CaError>
Feed a freshly-parsed CAT (ISO/IEC 13818-1 §2.4.4.7) to the managed
CAS-layer state: extracts its CA_descriptors (ISO/IEC 13818-1 §2.6.16,
CAID → EMM PID) and recomputes emm_pids against
the CAM’s advertised CAIDs (last Notification::CaInfo, captured
automatically as it arrives — see pump).
Calling this before any ca_info has been observed is not an
error: emm_pids stays empty until the CAM
advertises its CAIDs, then recomputes against the CAT stored here —
set_cat need not be re-called once ca_info arrives.
§Errors
CaError::Cat if the CAT’s descriptor loop carries a truncated
CA_descriptor.
Sourcepub fn emm_pids(&self) -> &[Pid]
pub fn emm_pids(&self) -> &[Pid]
The EMM PIDs to route into ci0 — the last set_cat’s
list of (CAID, EMM PID) entries (a CAID may have several) intersected with the CAM’s advertised CAIDs (#763
Task 4).
Sourcepub fn descramble_pids(&self) -> &[Pid]
pub fn descramble_pids(&self) -> &[Pid]
The PIDs to route into ci0 for descrambling — the union of every
actively-managed service’s elementary-stream PIDs (#763 Task 4).
Sourcepub fn ca_pids(&self) -> &[Pid]
pub fn ca_pids(&self) -> &[Pid]
The union of every actively-managed service’s CA_PIDs (ECM PIDs —
ISO/IEC 13818-1 §2.6.16 CA_descriptor CA_PID, programme + ES level
combined) — the control-word channel, without which the module has ES
to descramble but no control words to do it with (#763 Task 7).
Sourcepub fn required_pids(&self) -> Vec<Pid>
pub fn required_pids(&self) -> Vec<Pid>
descramble_pids() ∪ ca_pids() ∪ emm_pids() ∪ PCR ∪ CAT — every PID class
this slot needs on ci0 (ES to descramble ∪ ECM for control words ∪
EMM for entitlements ∪ each active service’s PCR PID — ISO/IEC
13818-1 §2.4.4.9 — so the descrambled TS keeps its clock reference
even when the PCR rides a dedicated PID). #763 Task 7’s turnkey
CaDescrambler filters its
feed_ts input to exactly this set.
Answer an MMI menu/list by 1-based choice_ref (0 = back/cancel).
Sourcepub fn mmi_enquiry_answer(&mut self, text: &[u8]) -> Result<()>
pub fn mmi_enquiry_answer(&mut self, text: &[u8]) -> Result<()>
Answer an MMI enquiry with the user’s input (EN 300 468 Annex A bytes).
Sourcepub fn mmi_cancel(&mut self) -> Result<()>
pub fn mmi_cancel(&mut self) -> Result<()>
Abort the current MMI dialogue (answ with answ_id = cancel).
Ask the module to open its MMI menu (enter_menu) — e.g. to read card /
entitlement info from the module’s own menus.
Sourcepub fn pump(&mut self, timeout: Duration) -> Result<bool>
pub fn pump(&mut self, timeout: Duration) -> Result<bool>
One pump step: if the device is readable within timeout, read a frame
and feed it; otherwise advance the stack’s timers by timeout (driving
the poll cadence). Returns whether a frame was processed.
Also samples SlotInfo once per call (the DVB-CA slot has no
interrupt/event of its own; CA_GET_SLOT_INFO is a poll) so a hot-plug
edge is caught between reads — see Notification::HotPlug carrying
HotPlug::CamPresent/CamRemoved (#726).
Examples found in repository?
15fn main() -> std::io::Result<()> {
16 // Script a module that accepts the transport connection (C_T_C_Reply).
17 let dev = MockCaDevice::new([vec![tags::C_T_C_REPLY, 0x01, 0x01]]);
18 let mut driver = Driver::new(dev);
19
20 // Bring the interface up: reset + open the transport connection.
21 driver.init()?;
22 println!("init: sent {} device op(s)", driver.device().ops.len());
23
24 // Pump the device. When readable it reads a frame and feeds the stack;
25 // otherwise it advances the EN 50221 poll cadence by the timeout.
26 for step in 0..5 {
27 let read = driver.pump(Duration::from_millis(100))?;
28 println!("pump {step}: processed_frame={read}");
29 }
30
31 // Anything the host application needs to act on surfaces as a Notification.
32 for note in driver.take_notifications() {
33 match note {
34 Notification::CamReady => println!("note: CAM ready — safe to send ca_pmt"),
35 Notification::ApplicationInfo { menu, .. } => {
36 println!("note: application_information menu={menu:?}")
37 }
38 Notification::CaInfo { ca_system_ids } => {
39 println!("note: ca_info system_ids={ca_system_ids:?}")
40 }
41 other => println!("note: {other:?}"),
42 }
43 }
44
45 // The mock records every device op (writes/ioctls) — handy for assertions.
46 println!("total recorded device ops: {}", driver.device().ops.len());
47 Ok(())
48}Sourcepub fn pump_with<F: FnMut(&Notification)>(
&mut self,
timeout: Duration,
handler: F,
) -> Result<bool>
pub fn pump_with<F: FnMut(&Notification)>( &mut self, timeout: Duration, handler: F, ) -> Result<bool>
Pump once (pump), then invoke handler for each
Notification produced this cycle (drain-and-dispatch via
take_notifications). Returns the same
bool as pump. The closure is per-call — nothing is stored, so there
are no lifetime constraints beyond the call itself. This crate is
sync/sans-IO (no channels/async runtime), so a closure callback is the
idiomatic push-style alternative to poll-draining take_notifications
yourself.
Sourcepub fn pump_hotplug<F: FnMut(HotPlug)>(
&mut self,
timeout: Duration,
handler: F,
) -> Result<bool>
pub fn pump_hotplug<F: FnMut(HotPlug)>( &mut self, timeout: Duration, handler: F, ) -> Result<bool>
Convenience over pump_with: invoke handler only
for HotPlug transitions, ignoring every other Notification
produced this cycle.