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