Skip to main content

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`), sends a `ca_pmt` with `cmd_id = query`, and — when
54    /// the `ca_pmt_reply` reports descrambling is possible — automatically sends
55    /// `cmd_id = ok_descrambling`. The reply outcome surfaces as
56    /// [`Notification::CaPmtReply`].
57    Descramble(&'a [u8]),
58    /// Tear the interface down (close sessions + transport connection).
59    Shutdown,
60}
61
62/// An output the driver loop must perform.
63#[derive(Debug, Clone, PartialEq, Eq)]
64#[non_exhaustive]
65pub enum Action {
66    /// Write one link-layer frame to the device.
67    Write(Vec<u8>),
68    /// Issue the `CA_RESET` ioctl.
69    Reset,
70    /// Issue the `CA_GET_SLOT_INFO` ioctl.
71    QuerySlot,
72    /// Arm the poll/timer to fire after `after` (coalesced: the latest wins).
73    SetTimer {
74        /// Delay before the next [`Event::Tick`] should be delivered.
75        after: Duration,
76    },
77    /// Surface a host-facing [`Notification`].
78    Notify(Notification),
79}
80
81/// A host-facing event surfaced by the stack (the useful outputs of a CI
82/// session — what an application reacts to).
83#[derive(Debug, Clone, PartialEq, Eq)]
84#[non_exhaustive]
85pub enum Notification {
86    /// The module is present and the resource-manager handshake completed.
87    CamReady,
88    /// `application_information` was received.
89    ApplicationInfo {
90        /// `application_type` (0x01 = CA).
91        application_type: u8,
92        /// `application_manufacturer`.
93        manufacturer: u16,
94        /// `manufacturer_code`.
95        code: u16,
96        /// The decoded menu string.
97        menu: String,
98    },
99    /// `ca_info` was received — the CA system ids the module supports.
100    CaInfo {
101        /// `CA_system_id` values the CAM can descramble.
102        ca_system_ids: Vec<u16>,
103    },
104    /// A `ca_pmt_reply` was received for a prior CA_PMT.
105    CaPmtReply {
106        /// `program_number` the reply pertains to.
107        program_number: u16,
108        /// Raw `CA_enable`/descrambling-possibility bytes (per-program/ES).
109        descrambling_ok: bool,
110    },
111    /// An MMI menu/enquiry the host should display.
112    Mmi(MmiEvent),
113    /// A session for `resource` was opened.
114    SessionOpened {
115        /// The resource the session serves.
116        resource: ResourceId,
117    },
118    /// A session closed.
119    SessionClosed {
120        /// The `session_nb` that closed.
121        session_nb: u16,
122    },
123    /// A protocol error surfaced by the stack (non-fatal; informational).
124    Error {
125        /// Human-readable detail.
126        detail: String,
127    },
128}
129
130/// A decoded high-level MMI `menu()` / `list()` ready for display (§8.6.5,
131/// Tables 49/51). The three header lines and the choice list are kept separate
132/// so a UI can render them directly — a title bar, two sub-lines, and a list of
133/// selectable rows — without re-parsing.
134#[derive(Debug, Clone, PartialEq, Eq, Default)]
135pub struct MmiMenu {
136    /// Title line.
137    pub title: String,
138    /// Sub-title line (often a section heading).
139    pub subtitle: String,
140    /// Bottom line (often a hint such as "Select item and press OK").
141    pub bottom: String,
142    /// The selectable choices, in wire order. Answer the Nth (1-based) with
143    /// [`Driver::mmi_menu_answer`](crate::Driver::mmi_menu_answer)`(N)`; `0`
144    /// cancels / goes back.
145    pub choices: Vec<String>,
146}
147
148/// MMI (man-machine interface) host events.
149#[derive(Debug, Clone, PartialEq, Eq)]
150#[non_exhaustive]
151pub enum MmiEvent {
152    /// A `menu()` to display — the user picks one choice. Answer via
153    /// [`Driver::mmi_menu_answer`](crate::Driver::mmi_menu_answer).
154    Menu(MmiMenu),
155    /// A `list()` to display — informational (e.g. an entitlement listing); the
156    /// host typically dismisses it with
157    /// [`Driver::mmi_menu_answer`](crate::Driver::mmi_menu_answer)`(0)`.
158    List(MmiMenu),
159    /// An `enquiry` (text prompt) expecting an answer. Reply via
160    /// [`Driver::mmi_enquiry_answer`](crate::Driver::mmi_enquiry_answer) or
161    /// [`Driver::mmi_cancel`](crate::Driver::mmi_cancel).
162    Enquiry {
163        /// Prompt text.
164        prompt: String,
165        /// Whether the answer should be hidden (e.g. PIN).
166        blind: bool,
167        /// Expected answer length.
168        answer_len: u8,
169    },
170    /// The module closed the MMI dialog.
171    Close,
172}