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 extension APDUs). 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.
9//!
10//! # What it does / what it does not
11//!
12//! **Does:** drives a CI module end to end — slot reset, transport connection,
13//! Resource Manager handshake, the six host-provided resources, `ca_pmt` for the
14//! services you add, and (with a `ci0` data device) scrambled-in /
15//! descrambled-out TS. Sync and sans-IO at the core: the caller owns the clock.
16//!
17//! **Does not:** run CI Plus (authentication, Content Control, multi-stream), serve
18//! Low-Speed Communications or any TS 101 699 extension resource, re-tune on a
19//! `host_control` request (it surfaces the request), or descramble itself.
20//!
21//! # Standards supported
22//!
23//! | Standard | Edition | Scope | Status |
24//! |---|---|---|---|
25//! | CENELEC EN 50221 | February 1997 | TPDU poll loop, session layer, host-provided Resource Manager / Application Information / Conditional Access / Date-Time / MMI / Host Control | Partial (no Low-Speed Communications; Host Control surfaced, not acted on) |
26//! | ETSI TS 101 699 V1.1.1 | 1999-11 | none at runtime (typed in `dvb-ci`) | Not implemented |
27//! | Linux DVB CA API (`dvb_ca_en50221`) | kernel ABI | `CA_RESET`, `CA_GET_SLOT_INFO`, `[slot, connection_id, ...]` frames, `ciM` data plane | Full for the calls used |
28//!
29//! # Features
30//!
31//! | Feature | Default | Effect |
32//! |---|---|---|
33//! | (none) | yes | Portable sync core and mocks; needs `std` |
34//! | `linux` | no | `LinuxCaDevice`, `LinuxCiDataDevice`, `DeviceOpError`, the `ci-probe` binary |
35//!
36//! # Design
37//!
38//! The whole runtime is written against the [`CaDevice`] trait (the
39//! hardware-abstraction boundary), so it runs against either:
40//! - a real Linux CA device (`/dev/dvb/adapterN/caM`, the `linux` feature), or
41//! - the in-memory [`MockCaDevice`], which makes the state machines testable
42//!   without hardware.
43//!
44//! `#![deny(unsafe_code)]`: only the Linux device leaf uses `unsafe`. Long-form
45//! `length_field`s follow `dvb-ci`'s preserved `LengthForm`; PID accessors are
46//! typed `mpeg_ts::pid::Pid`.
47//!
48//! # What's implemented
49//!
50//! - **Transport** (TPDU, Annex A): `Create_T_C` handshake, empty-`T_Data_Last`
51//!   poll cadence (half-duplex), `T_SB` data-available → `T_RCV`,
52//!   `T_Data_More/Last` reassembly, reply timeout.
53//! - **Session** (SPDU): session table; `open_session_request`/response,
54//!   host-initiated `create_session`, `close_session`; `session_number` + APDU
55//!   routing.
56//! - **Resources** (all host-provided): Resource Manager handshake (profile
57//!   exchange → [`Notification::CamReady`]), application_information,
58//!   conditional_access (`ca_pmt`/`ca_pmt_reply`), date_time (MJD + BCD), mmi
59//!   (menus/enquiries as [`Notification::Mmi`], answered via
60//!   `Driver::mmi_menu_answer` / `mmi_enquiry_answer` / `mmi_cancel`), and
61//!   [`resource::HostControl`] (`tune`/`replace`/`clear_replace`/`ask_release`
62//!   surfaced as [`Notification::HostControl`]).
63//! - **Descramble helper**: [`Driver::descramble`](crate::driver::Driver::descramble)
64//!   / [`HostRequest::Descramble`] runs the `ca_pmt` query → reply →
65//!   ok_descrambling sequence, CAID-filtered to the CAM's `ca_info`.
66//! - **Devices**: the in-memory [`MockCaDevice`] + [`MockCiDataDevice`], and the
67//!   Linux `caM` (control) + `ciM` (TS data-plane) devices behind the `linux`
68//!   feature. The data plane ([`CiDataDevice`]) carries the scrambled-in /
69//!   descrambled-out TS for separate-CI hardware.
70//! - **Diagnostics**: [`RecordingCaDevice`] captures the link in both directions;
71//!   [`trace::decode_frame`]/[`decode_log`](crate::trace::decode_log) annotate a
72//!   capture (TPDU → SPDU → APDU).
73//!
74//! # Managed CAS layer
75//!
76//! A single-slot CA orchestration layer sits on top of the raw `ca_pmt` /
77//! `ca_pmt_reply` surface, fed **parsed `dvb-si` structs** (never raw bytes):
78//!
79//! - [`Driver::add_service`](crate::Driver::add_service) /
80//!   [`remove_service`](crate::Driver::remove_service) — build + send the
81//!   `ca_pmt` from a [`dvb_si::tables::pmt::PmtSection`] and track the slot's
82//!   active service set.
83//! - [`Driver::set_cat`](crate::Driver::set_cat) — feed the CAT; the EMM-PID
84//!   set becomes the CAT's EMM PIDs ∩ the CAM's advertised `ca_info` CAIDs.
85//! - [`Driver::emm_pids`](crate::Driver::emm_pids) /
86//!   [`descramble_pids`](crate::Driver::descramble_pids) /
87//!   [`ca_pids`](crate::Driver::ca_pids) /
88//!   [`required_pids`](crate::Driver::required_pids) — the PIDs to route into
89//!   the `ci0` data plane (EMM ∪ ES ∪ ECM ∪ PCR ∪ CAT = `required_pids`).
90//! - [`Driver::set_requery_interval`](crate::Driver::set_requery_interval) —
91//!   opt-in periodic `ca_pmt` re-query; a per-programme status transition
92//!   surfaces as an edge-triggered [`Notification::Entitlement`].
93//! - [`CaDescrambler`] — turnkey wrapper that also owns the `ci0`
94//!   [`CiDataDevice`]: `feed_ts` filters a scrambled TS chunk to
95//!   `required_pids()` and returns the descrambled TS. One `CaDescrambler` =
96//!   one CI slot = one TS path.
97//!
98//! # Hardware verification
99//!
100//! Only what the CHANGELOG records: an AlphaCrypt / Irdeto module (live) for the
101//! Resource Manager handshake, `application_information` and `ca_info` (18
102//! CA_system_ids); a Digital Devices CXD2099 `ci0` for the split-fd data plane
103//! (150 s, 111k `feed_ts` calls, 0 errors). The periodic re-query is not
104//! verified on any CAM.
105//!
106//! # `ci-probe`
107//!
108//! With the `linux` feature this crate also builds a **`ci-probe`** binary that
109//! discovers and engages an installed CAM: `ci-probe list` / `info` /
110//! `descramble <pmt>` / `mmi`, with `--trace` for an annotated link dump.
111//!
112//! # Related crates
113//!
114//! Depends on [`dvb_ci`] (re-exported), `dvb-si`, `mpeg-ts` and
115//! `broadcast-common`. MSRV 1.95.0; MIT OR Apache-2.0.
116//!
117//! # Example
118//!
119//! Drive a CAM with the [`MockCaDevice`] — the same `Driver` API works against
120//! the real Linux device:
121//!
122//! ```
123//! use std::time::Duration;
124//! use dvb_ci_runtime::{Driver, HotPlug, MockCaDevice, Notification};
125//! use dvb_ci_runtime::dvb_ci::tpdu::tags;
126//!
127//! # fn main() -> std::io::Result<()> {
128//! // Script a module that accepts the transport connection.
129//! let dev = MockCaDevice::new([vec![tags::C_T_C_REPLY, 0x01, 0x01]]);
130//! let mut driver = Driver::new(dev);
131//!
132//! driver.init()?; // reset + open the transport connection
133//!
134//! // Pump the device: reads frames when readable, otherwise advances the
135//! // EN 50221 poll cadence by the timeout.
136//! for _ in 0..4 {
137//!     driver.pump(Duration::from_millis(100))?;
138//! }
139//!
140//! // Host-facing events (CamReady, ApplicationInfo, CaInfo, Mmi, HotPlug, …)
141//! // surface here — either poll-drained:
142//! for note in driver.take_notifications() {
143//!     match note {
144//!         Notification::CamReady => { /* now safe to send a ca_pmt */ }
145//!         other => { let _ = other; }
146//!     }
147//! }
148//!
149//! // …or, since this crate is sync/sans-IO (no channels), delivered by a
150//! // closure callback per pump — `pump_with` for every notification, or
151//! // `pump_hotplug` to react only to CAM/card hot-plug transitions:
152//! driver.pump_hotplug(Duration::from_millis(100), |hp| match hp {
153//!     HotPlug::CamPresent => { /* CAM (re)inserted — re-send any pending ca_pmt */ }
154//!     HotPlug::CamRemoved => { /* CAM removed — stop descrambling */ }
155//!     _ => {}
156//! })?;
157//! # Ok(())
158//! # }
159//! ```
160
161// The portable core is unsafe-free; only the optional Linux device leaf
162// (`#[allow(unsafe_code)]` in `linux`) uses ioctls, so this is `deny`, not
163// `forbid`.
164#![deny(unsafe_code)]
165#![warn(missing_docs)]
166#![cfg_attr(docsrs, feature(doc_cfg))]
167
168pub mod dataplane;
169pub mod descrambler;
170pub mod device;
171pub mod driver;
172pub mod event;
173pub mod managed;
174pub mod resource;
175pub mod session;
176pub mod stack;
177pub mod trace;
178pub mod transport;
179
180#[cfg(all(feature = "linux", target_os = "linux"))]
181pub mod linux;
182
183pub use dataplane::{
184    CiDataDevice, MockCiDataDevice, PartialWrite, TS_PACKET_LEN, partial_write_progress,
185};
186pub use descrambler::CaDescrambler;
187pub use device::{
188    CaDevice, DeviceOp, DeviceOpError, LinkEvent, MockCaDevice, RecordingCaDevice, SlotInfo,
189};
190pub use driver::Driver;
191pub use event::{Action, Event, HostRequest, HotPlug, Notification};
192#[cfg(all(feature = "linux", target_os = "linux"))]
193pub use linux::{LinuxCaDevice, LinuxCiDataDevice};
194pub use managed::{CaError, ManagedCa, ManagedService, REQUERY_DEFAULT};
195pub use stack::CiStack;
196
197/// Re-export of the wire-codec crate this runtime drives.
198pub use dvb_ci;