Skip to main content

dvb_ci_runtime/
lib.rs

1//! Pure-Rust EN 50221 DVB Common Interface **runtime** — the driver loop over
2//! the [`dvb_ci`] codecs.
3//!
4//! [`dvb_ci`] is `no_std` and owns the *wire* layer (TPDU / SPDU / APDU
5//! parse+serialize, CA_PMT building, CI Plus extensions). This crate adds the
6//! *runtime*: device I/O, the TPDU poll loop, SPDU session management, and the
7//! per-resource state machines that together drive a physical CAM, per
8//! ETSI EN 50221 and TS 101 699.
9//!
10//! # Design
11//!
12//! The whole runtime is written against the [`CaDevice`] trait (the
13//! hardware-abstraction boundary), so it runs against either:
14//! - a real Linux CA device (`/dev/dvb/adapterN/caM`, the `linux` feature), or
15//! - the in-memory [`MockCaDevice`], which makes the state machines testable
16//!   without hardware and enables differential testing against an external
17//!   reference (drive both with the same scripted mock CAM and assert the
18//!   emitted `write`/ioctl byte sequences match).
19//!
20//! Implemented from the EN 50221 specification.
21//!
22//! # What's implemented
23//!
24//! - **Transport** (TPDU, §A.4): `Create_T_C` handshake, empty-`T_Data_Last`
25//!   poll cadence, `T_SB` data-available → `T_RCV`, `T_Data_More/Last`
26//!   reassembly, reply timeout.
27//! - **Session** (SPDU, §7.2): session table; `open_session_request`/response,
28//!   host-initiated `create_session`, `close_session`; `session_number` + APDU
29//!   routing.
30//! - **Resources** (§8): Resource Manager handshake (profile exchange →
31//!   [`Notification::CamReady`], then opens module resources),
32//!   application_information, conditional_access (`ca_pmt`/`ca_pmt_reply`),
33//!   date_time (MJD + BCD), and mmi (surfaces module menus/enquiries as
34//!   [`Notification::Mmi`]).
35//! - **Descramble helper**: [`Driver::descramble`](crate::driver::Driver::descramble)
36//!   / [`HostRequest::Descramble`] runs the full `ca_pmt` query → reply →
37//!   ok_descrambling sequence, CAID-filtered to the CAM's `ca_info`.
38//! - **Devices**: the in-memory [`MockCaDevice`] + [`MockCiDataDevice`], and the
39//!   Linux `/dev/dvb/adapterN/caM` (control) + `ciM` (TS data-plane) devices
40//!   behind the `linux` feature. The data plane ([`CiDataDevice`]) carries the
41//!   scrambled-in / descrambled-out TS for separate-CI hardware.
42//!
43//! - **Diagnostics**: [`RecordingCaDevice`] captures the link in both directions;
44//!   [`trace::decode_frame`]/[`decode_log`](crate::trace::decode_log) annotate a
45//!   capture (TPDU → SPDU → APDU) for live-CAM debugging.
46//!
47//! # `ci-probe`
48//!
49//! With the `linux` feature this crate also builds a **`ci-probe`** binary that
50//! discovers and engages an installed CAM: `ci-probe list` / `info` /
51//! `descramble <pmt>` / `mmi`, with `--trace` for an annotated link dump.
52//!
53//! Roadmap: the `host_control` resource and a differential test harness against
54//! an external reference.
55//!
56//! # Example
57//!
58//! Drive a CAM with the [`MockCaDevice`] — the same `Driver` API works against
59//! the real Linux device:
60//!
61//! ```
62//! use std::time::Duration;
63//! use dvb_ci_runtime::{Driver, HotPlug, MockCaDevice, Notification};
64//! use dvb_ci_runtime::dvb_ci::tpdu::tags;
65//!
66//! # fn main() -> std::io::Result<()> {
67//! // Script a module that accepts the transport connection.
68//! let dev = MockCaDevice::new([vec![tags::C_T_C_REPLY, 0x01, 0x01]]);
69//! let mut driver = Driver::new(dev);
70//!
71//! driver.init()?; // reset + open the transport connection
72//!
73//! // Pump the device: reads frames when readable, otherwise advances the
74//! // EN 50221 poll cadence by the timeout.
75//! for _ in 0..4 {
76//!     driver.pump(Duration::from_millis(100))?;
77//! }
78//!
79//! // Host-facing events (CamReady, ApplicationInfo, CaInfo, Mmi, HotPlug, …)
80//! // surface here — either poll-drained:
81//! for note in driver.take_notifications() {
82//!     match note {
83//!         Notification::CamReady => { /* now safe to send a ca_pmt */ }
84//!         other => { let _ = other; }
85//!     }
86//! }
87//!
88//! // …or, since this crate is sync/sans-IO (no channels), delivered by a
89//! // closure callback per pump — `pump_with` for every notification, or
90//! // `pump_hotplug` to react only to CAM/card hot-plug transitions:
91//! driver.pump_hotplug(Duration::from_millis(100), |hp| match hp {
92//!     HotPlug::CamPresent => { /* CAM (re)inserted — re-send any pending ca_pmt */ }
93//!     HotPlug::CamRemoved => { /* CAM removed — stop descrambling */ }
94//!     _ => {}
95//! })?;
96//! # Ok(())
97//! # }
98//! ```
99
100// The portable core is unsafe-free; only the optional Linux device leaf
101// (`#[allow(unsafe_code)]` in `linux`) uses ioctls, so this is `deny`, not
102// `forbid`.
103#![deny(unsafe_code)]
104#![warn(missing_docs)]
105
106pub mod dataplane;
107pub mod device;
108pub mod driver;
109pub mod event;
110pub mod resource;
111pub mod session;
112pub mod stack;
113pub mod trace;
114pub mod transport;
115
116#[cfg(all(feature = "linux", target_os = "linux"))]
117pub mod linux;
118
119pub use dataplane::{CiDataDevice, MockCiDataDevice, TS_PACKET_LEN};
120pub use device::{CaDevice, DeviceOp, LinkEvent, MockCaDevice, RecordingCaDevice, SlotInfo};
121pub use driver::Driver;
122pub use event::{Action, Event, HostRequest, HotPlug, Notification};
123#[cfg(all(feature = "linux", target_os = "linux"))]
124pub use linux::{LinuxCaDevice, LinuxCiDataDevice};
125pub use stack::CiStack;
126
127/// Re-export of the wire-codec crate this runtime drives.
128pub use dvb_ci;