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    /// Descramble the services in a PMT section (raw `dvb-si` PMT bytes). The
41    /// stack filters the PMT's `CA_descriptor`s to the CAM's advertised CAIDs
42    /// (from its `ca_info`), sends a `ca_pmt` with `cmd_id = query`, and — when
43    /// the `ca_pmt_reply` reports descrambling is possible — automatically sends
44    /// `cmd_id = ok_descrambling`. The reply outcome surfaces as
45    /// [`Notification::CaPmtReply`].
46    Descramble(&'a [u8]),
47    /// Tear the interface down (close sessions + transport connection).
48    Shutdown,
49}
50
51/// An output the driver loop must perform.
52#[derive(Debug, Clone, PartialEq, Eq)]
53#[non_exhaustive]
54pub enum Action {
55    /// Write one link-layer frame to the device.
56    Write(Vec<u8>),
57    /// Issue the `CA_RESET` ioctl.
58    Reset,
59    /// Issue the `CA_GET_SLOT_INFO` ioctl.
60    QuerySlot,
61    /// Arm the poll/timer to fire after `after` (coalesced: the latest wins).
62    SetTimer {
63        /// Delay before the next [`Event::Tick`] should be delivered.
64        after: Duration,
65    },
66    /// Surface a host-facing [`Notification`].
67    Notify(Notification),
68}
69
70/// A host-facing event surfaced by the stack (the useful outputs of a CI
71/// session — what an application reacts to).
72#[derive(Debug, Clone, PartialEq, Eq)]
73#[non_exhaustive]
74pub enum Notification {
75    /// The module is present and the resource-manager handshake completed.
76    CamReady,
77    /// `application_information` was received.
78    ApplicationInfo {
79        /// `application_type` (0x01 = CA).
80        application_type: u8,
81        /// `application_manufacturer`.
82        manufacturer: u16,
83        /// `manufacturer_code`.
84        code: u16,
85        /// The decoded menu string.
86        menu: String,
87    },
88    /// `ca_info` was received — the CA system ids the module supports.
89    CaInfo {
90        /// `CA_system_id` values the CAM can descramble.
91        ca_system_ids: Vec<u16>,
92    },
93    /// A `ca_pmt_reply` was received for a prior CA_PMT.
94    CaPmtReply {
95        /// `program_number` the reply pertains to.
96        program_number: u16,
97        /// Raw `CA_enable`/descrambling-possibility bytes (per-program/ES).
98        descrambling_ok: bool,
99    },
100    /// An MMI menu/enquiry the host should display.
101    Mmi(MmiEvent),
102    /// A session for `resource` was opened.
103    SessionOpened {
104        /// The resource the session serves.
105        resource: ResourceId,
106    },
107    /// A session closed.
108    SessionClosed {
109        /// The `session_nb` that closed.
110        session_nb: u16,
111    },
112    /// A protocol error surfaced by the stack (non-fatal; informational).
113    Error {
114        /// Human-readable detail.
115        detail: String,
116    },
117}
118
119/// MMI (man-machine interface) host events.
120#[derive(Debug, Clone, PartialEq, Eq)]
121#[non_exhaustive]
122pub enum MmiEvent {
123    /// A `menu`/`list` to display: title + items.
124    Menu {
125        /// Menu title text.
126        title: String,
127        /// Selectable item texts.
128        items: Vec<String>,
129    },
130    /// An `enquiry` (text prompt) expecting an answer.
131    Enquiry {
132        /// Prompt text.
133        prompt: String,
134        /// Whether the answer should be hidden (e.g. PIN).
135        blind: bool,
136        /// Expected answer length.
137        answer_len: u8,
138    },
139    /// The module closed the MMI dialog.
140    Close,
141}