dvb_ci_runtime/event.rs
1//! The sans-IO event/action model.
2//!
3//! The protocol core is pure: it consumes [`Event`]s and produces [`Action`]s,
4//! with no device, threads, or clock of its own. The driver loop executes the
5//! actions against a [`CaDevice`](crate::CaDevice) and feeds events back. This
6//! keeps every state machine deterministic and testable without
7//! hardware — a test (or a differential comparison against an external
8//! reference) drives a sequence of events and asserts the emitted action
9//! sequence.
10
11use std::time::Duration;
12
13use dvb_ci::resource::ResourceId;
14
15/// An input to the protocol core.
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum Event<'a> {
19 /// One link-layer frame was read from the device.
20 Readable(&'a [u8]),
21 /// Logical time advanced by `elapsed` since the last tick (drives poll
22 /// cadence and resource timers without a real clock in the core).
23 Tick {
24 /// Time since the previous tick.
25 elapsed: Duration,
26 },
27 /// A request from the host application.
28 Host(HostRequest<'a>),
29}
30
31/// A request the host application makes of the stack.
32#[derive(Debug, Clone, PartialEq, Eq)]
33#[non_exhaustive]
34pub enum HostRequest<'a> {
35 /// Bring the interface up: reset the slot and open the transport connection.
36 Init,
37 /// Send a serialized `ca_pmt` APDU body to the CAM's conditional-access
38 /// resource (descrambling request).
39 SendCaPmt(&'a [u8]),
40 /// Answer an MMI `menu`/`list` by 1-based `choice_ref` (0 = "back"/cancel),
41 /// sent as `menu_answ` to the module.
42 MmiMenuAnswer(u8),
43 /// Answer an MMI `enquiry` with the user's input text (EN 300 468 Annex A
44 /// bytes), sent as `answ` (`answ_id = answer`).
45 MmiEnquiryAnswer(&'a [u8]),
46 /// Abort the current MMI enquiry (`answ` with `answ_id = cancel`).
47 MmiCancel,
48 /// Ask the module to open its MMI menu (`enter_menu` on the
49 /// application_information session) — e.g. to read card / entitlement info.
50 EnterMenu,
51 /// Descramble the services in a PMT section (raw `dvb-si` PMT bytes). The
52 /// stack filters the PMT's `CA_descriptor`s to the CAM's advertised CAIDs
53 /// (from its `ca_info`) and sends a `ca_pmt` with `list_management = only`,
54 /// `cmd_id = ok_descrambling`. The reply outcome surfaces as
55 /// [`Notification::CaPmtReply`].
56 Descramble(&'a [u8]),
57 /// Descramble a **set** of programmes in one CA-PMT list (`first`/`more`/
58 /// `last`, all `ok_descrambling`), replacing any prior set. Each element is a
59 /// raw PMT section.
60 DescramblePrograms(&'a [&'a [u8]]),
61 /// Add one programme to the descrambled set (`list_management = add`).
62 AddProgram(&'a [u8]),
63 /// Remove one programme from the descrambled set (`list_management = update`,
64 /// `cmd_id = not_selected`).
65 RemoveProgram(&'a [u8]),
66 /// Tear the interface down (close sessions + transport connection).
67 Shutdown,
68}
69
70/// An output the driver loop must perform.
71#[derive(Debug, Clone, PartialEq, Eq)]
72#[non_exhaustive]
73pub enum Action {
74 /// Write one link-layer frame to the device.
75 Write(Vec<u8>),
76 /// Issue the `CA_RESET` ioctl.
77 Reset,
78 /// Issue the `CA_GET_SLOT_INFO` ioctl.
79 QuerySlot,
80 /// Arm the poll/timer to fire after `after` (coalesced: the latest wins).
81 SetTimer {
82 /// Delay before the next [`Event::Tick`] should be delivered.
83 after: Duration,
84 },
85 /// Surface a host-facing [`Notification`].
86 Notify(Notification),
87}
88
89/// A host-facing event surfaced by the stack (the useful outputs of a CI
90/// session — what an application reacts to).
91#[derive(Debug, Clone, PartialEq, Eq)]
92#[non_exhaustive]
93pub enum Notification {
94 /// The module is present and the resource-manager handshake completed.
95 CamReady,
96 /// `application_information` was received.
97 ApplicationInfo {
98 /// `application_type` (0x01 = CA).
99 application_type: u8,
100 /// `application_manufacturer`.
101 manufacturer: u16,
102 /// `manufacturer_code`.
103 code: u16,
104 /// The decoded menu string.
105 menu: String,
106 },
107 /// `ca_info` was received — the CA system ids the module supports.
108 CaInfo {
109 /// `CA_system_id` values the CAM can descramble.
110 ca_system_ids: Vec<u16>,
111 },
112 /// A `ca_pmt_reply` was received for a prior CA_PMT.
113 CaPmtReply {
114 /// `program_number` the reply pertains to.
115 program_number: u16,
116 /// Raw `CA_enable`/descrambling-possibility bytes (per-program/ES).
117 descrambling_ok: bool,
118 },
119 /// An MMI menu/enquiry the host should display.
120 Mmi(MmiEvent),
121 /// A `host_control` request the CAM made of the host (EN 50221 §8.5.1). The
122 /// host acts on it out-of-band (retune / PID replace); the runtime only
123 /// surfaces the decoded request.
124 HostControl(HostControlEvent),
125 /// A session for `resource` was opened.
126 SessionOpened {
127 /// The resource the session serves.
128 resource: ResourceId,
129 },
130 /// A session closed.
131 SessionClosed {
132 /// The `session_nb` that closed.
133 session_nb: u16,
134 },
135 /// A protocol error surfaced by the stack (non-fatal; informational).
136 Error {
137 /// Human-readable detail.
138 detail: String,
139 },
140}
141
142/// A decoded high-level MMI `menu()` / `list()` ready for display (§8.6.5,
143/// Tables 49/51). The three header lines and the choice list are kept separate
144/// so a UI can render them directly — a title bar, two sub-lines, and a list of
145/// selectable rows — without re-parsing.
146#[derive(Debug, Clone, PartialEq, Eq, Default)]
147pub struct MmiMenu {
148 /// Title line.
149 pub title: String,
150 /// Sub-title line (often a section heading).
151 pub subtitle: String,
152 /// Bottom line (often a hint such as "Select item and press OK").
153 pub bottom: String,
154 /// The selectable choices, in wire order. Answer the Nth (1-based) with
155 /// [`Driver::mmi_menu_answer`](crate::Driver::mmi_menu_answer)`(N)`; `0`
156 /// cancels / goes back.
157 pub choices: Vec<String>,
158}
159
160/// MMI (man-machine interface) host events.
161#[derive(Debug, Clone, PartialEq, Eq)]
162#[non_exhaustive]
163pub enum MmiEvent {
164 /// A `menu()` to display — the user picks one choice. Answer via
165 /// [`Driver::mmi_menu_answer`](crate::Driver::mmi_menu_answer).
166 Menu(MmiMenu),
167 /// A `list()` to display — informational (e.g. an entitlement listing); the
168 /// host typically dismisses it with
169 /// [`Driver::mmi_menu_answer`](crate::Driver::mmi_menu_answer)`(0)`.
170 List(MmiMenu),
171 /// An `enquiry` (text prompt) expecting an answer. Reply via
172 /// [`Driver::mmi_enquiry_answer`](crate::Driver::mmi_enquiry_answer) or
173 /// [`Driver::mmi_cancel`](crate::Driver::mmi_cancel).
174 Enquiry {
175 /// Prompt text.
176 prompt: String,
177 /// Whether the answer should be hidden (e.g. PIN).
178 blind: bool,
179 /// Expected answer length.
180 answer_len: u8,
181 },
182 /// The module closed the MMI dialog.
183 Close,
184}
185
186/// A `host_control` request the CAM makes of the host (ETSI EN 50221 §8.5.1,
187/// Tables 27-30). The runtime surfaces the decoded request; the host performs
188/// the retune / PID replacement itself (out of band) — there is no re-tune
189/// logic in the stack.
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191#[non_exhaustive]
192pub enum HostControlEvent {
193 /// `tune()` (Table 27): retune to the identified service.
194 Tune {
195 /// `network_id`.
196 network_id: u16,
197 /// `original_network_id`.
198 original_network_id: u16,
199 /// `transport_stream_id`.
200 transport_stream_id: u16,
201 /// `service_id`.
202 service_id: u16,
203 },
204 /// `replace()` (Table 28): temporarily replace one component PID with
205 /// another from the same multiplex.
206 Replace {
207 /// `replacement_ref` — matched later by a Clear Replace.
208 replacement_ref: u8,
209 /// 13-bit `replaced_PID`.
210 replaced_pid: u16,
211 /// 13-bit `replacement_PID`.
212 replacement_pid: u16,
213 },
214 /// `clear_replace()` (Table 29): undo all Replace operations sharing this
215 /// `replacement_ref`.
216 ClearReplace {
217 /// `replacement_ref` shared with one or more prior Replace requests.
218 replacement_ref: u8,
219 },
220 /// `ask_release()` (Table 30): the CAM asks the host to release any
221 /// replacements it holds (header-only request).
222 AskRelease,
223}