Skip to main content

dvb_ci/ci_ext/
event_manager.rs

1//! Event Manager objects — ETSI TS 101 699 V1.1.1 §6.4, Tables 56-61
2//! (PDF pp. 55-56). See `docs/ci_plus/event-manager.md`.
3//!
4//! Resource ID `0x00231ii1` (`ii` = Module ID). Lets a module book timer events
5//! that wake the host.
6//!
7//! - `event_request` (`9F 80 00`, Table 56) — module → host: book/cancel an event.
8//! - `event_request_ack` (`9F 80 01`, Table 59) — host → module: reply.
9//! - `event_notification` (`9F 80 02`, Table 61) — host → module: event occurred.
10
11use crate::error::{Error, Result};
12use crate::objects;
13use crate::tag::ApduTag;
14use broadcast_common::{Parse, Serialize};
15
16/// Resource-scoped `apdu_tag`s for the Event Manager (Tables 56, 59, 61).
17pub mod tag {
18    use crate::tag::ApduTag;
19    /// `event_request_tag` = `9F 80 00`.
20    pub const EVENT_REQUEST: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
21    /// `event_request_ack_tag` = `9F 80 01`.
22    pub const EVENT_REQUEST_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
23    /// `event_notification_tag` = `9F 80 02`.
24    pub const EVENT_NOTIFICATION: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
25}
26
27/// `event_type` — the kind of event (Table 57).
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize))]
30#[non_exhaustive]
31pub enum EventType {
32    /// `0` — Timer.
33    Timer,
34    /// `1`-`255` — reserved for future use.
35    Reserved(u8),
36}
37
38impl EventType {
39    /// Decode the `event_type` byte.
40    #[must_use]
41    pub fn from_u8(v: u8) -> Self {
42        match v {
43            0 => Self::Timer,
44            other => Self::Reserved(other),
45        }
46    }
47    /// Wire byte.
48    #[must_use]
49    pub const fn to_u8(self) -> u8 {
50        match self {
51            Self::Timer => 0,
52            Self::Reserved(v) => v,
53        }
54    }
55    /// Spec token, or `"reserved"`.
56    #[must_use]
57    pub fn name(&self) -> &'static str {
58        match self {
59            Self::Timer => "Timer",
60            Self::Reserved(_) => "reserved",
61        }
62    }
63}
64broadcast_common::impl_spec_display!(EventType, Reserved);
65
66/// `reply` — the event request reply code (Table 60).
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize))]
69#[non_exhaustive]
70pub enum EventReply {
71    /// `0` — Event booked OK.
72    BookedOk,
73    /// `1` — Event type not supported.
74    TypeNotSupported,
75    /// `2` — Event resources consumed.
76    ResourcesConsumed,
77    /// `3`-`255` — reserved for future use.
78    Reserved(u8),
79}
80
81impl EventReply {
82    /// Decode the `reply` byte.
83    #[must_use]
84    pub fn from_u8(v: u8) -> Self {
85        match v {
86            0 => Self::BookedOk,
87            1 => Self::TypeNotSupported,
88            2 => Self::ResourcesConsumed,
89            other => Self::Reserved(other),
90        }
91    }
92    /// Wire byte.
93    #[must_use]
94    pub const fn to_u8(self) -> u8 {
95        match self {
96            Self::BookedOk => 0,
97            Self::TypeNotSupported => 1,
98            Self::ResourcesConsumed => 2,
99            Self::Reserved(v) => v,
100        }
101    }
102    /// Spec token, or `"reserved"`.
103    #[must_use]
104    pub fn name(&self) -> &'static str {
105        match self {
106            Self::BookedOk => "Event booked OK",
107            Self::TypeNotSupported => "Event type not supported",
108            Self::ResourcesConsumed => "Event resources consumed",
109            Self::Reserved(_) => "reserved",
110        }
111    }
112}
113broadcast_common::impl_spec_display!(EventReply, Reserved);
114
115/// `event_request()` (Table 56): module → host. The `event_desc` bytes define
116/// the event; their format depends on `event_type` (Table 58 — for a Timer,
117/// 40-bit start_time + 24-bit duration). An empty `event_desc` cancels any
118/// previously-booked event of this type. Carried verbatim for fidelity.
119#[derive(Debug, Clone, PartialEq, Eq)]
120#[cfg_attr(feature = "serde", derive(serde::Serialize))]
121pub struct EventRequest<'a> {
122    /// `event_type`.
123    pub event_type: EventType,
124    /// The `event_desc` block (format depends on `event_type`).
125    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
126    pub event_desc: &'a [u8],
127}
128
129/// `event_request_ack()` (Table 59): host → module.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize))]
132pub struct EventRequestAck {
133    /// `event_type`.
134    pub event_type: EventType,
135    /// `reply`.
136    pub reply: EventReply,
137}
138
139/// `event_notification()` (Table 61): host → module.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141#[cfg_attr(feature = "serde", derive(serde::Serialize))]
142pub struct EventNotification {
143    /// `event_type`.
144    pub event_type: EventType,
145}
146
147// --- event_request ---
148
149// event_type(1) + event_desc(N).
150const EVENT_REQUEST_PREFIX: usize = 1;
151
152impl<'a> Parse<'a> for EventRequest<'a> {
153    type Error = Error;
154    fn parse(bytes: &'a [u8]) -> Result<Self> {
155        let body = objects::parse_apdu_header(bytes, tag::EVENT_REQUEST, "event_request")?;
156        if body.len() < EVENT_REQUEST_PREFIX {
157            return Err(Error::BufferTooShort {
158                need: EVENT_REQUEST_PREFIX,
159                have: body.len(),
160                what: "event_request",
161            });
162        }
163        Ok(Self {
164            event_type: EventType::from_u8(body[0]),
165            event_desc: &body[EVENT_REQUEST_PREFIX..],
166        })
167    }
168}
169impl Serialize for EventRequest<'_> {
170    type Error = Error;
171    fn serialized_len(&self) -> usize {
172        objects::apdu_len(EVENT_REQUEST_PREFIX + self.event_desc.len())
173    }
174    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
175        let body_len = EVENT_REQUEST_PREFIX + self.event_desc.len();
176        let mut pos = objects::write_apdu_header(tag::EVENT_REQUEST, body_len, buf)?;
177        buf[pos] = self.event_type.to_u8();
178        pos += EVENT_REQUEST_PREFIX;
179        buf[pos..pos + self.event_desc.len()].copy_from_slice(self.event_desc);
180        Ok(pos + self.event_desc.len())
181    }
182}
183
184// --- event_request_ack ---
185
186// event_type(1) + reply(1).
187const EVENT_REQUEST_ACK_BODY: usize = 2;
188
189impl<'a> Parse<'a> for EventRequestAck {
190    type Error = Error;
191    fn parse(bytes: &'a [u8]) -> Result<Self> {
192        let body = objects::parse_apdu_header(bytes, tag::EVENT_REQUEST_ACK, "event_request_ack")?;
193        if body.len() < EVENT_REQUEST_ACK_BODY {
194            return Err(Error::BufferTooShort {
195                need: EVENT_REQUEST_ACK_BODY,
196                have: body.len(),
197                what: "event_request_ack",
198            });
199        }
200        Ok(Self {
201            event_type: EventType::from_u8(body[0]),
202            reply: EventReply::from_u8(body[1]),
203        })
204    }
205}
206impl Serialize for EventRequestAck {
207    type Error = Error;
208    fn serialized_len(&self) -> usize {
209        objects::apdu_len(EVENT_REQUEST_ACK_BODY)
210    }
211    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
212        let pos = objects::write_apdu_header(tag::EVENT_REQUEST_ACK, EVENT_REQUEST_ACK_BODY, buf)?;
213        buf[pos] = self.event_type.to_u8();
214        buf[pos + 1] = self.reply.to_u8();
215        Ok(pos + EVENT_REQUEST_ACK_BODY)
216    }
217}
218
219// --- event_notification ---
220
221// event_type(1).
222const EVENT_NOTIFICATION_BODY: usize = 1;
223
224impl<'a> Parse<'a> for EventNotification {
225    type Error = Error;
226    fn parse(bytes: &'a [u8]) -> Result<Self> {
227        let body =
228            objects::parse_apdu_header(bytes, tag::EVENT_NOTIFICATION, "event_notification")?;
229        if body.len() < EVENT_NOTIFICATION_BODY {
230            return Err(Error::BufferTooShort {
231                need: EVENT_NOTIFICATION_BODY,
232                have: body.len(),
233                what: "event_notification",
234            });
235        }
236        Ok(Self {
237            event_type: EventType::from_u8(body[0]),
238        })
239    }
240}
241impl Serialize for EventNotification {
242    type Error = Error;
243    fn serialized_len(&self) -> usize {
244        objects::apdu_len(EVENT_NOTIFICATION_BODY)
245    }
246    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
247        let pos =
248            objects::write_apdu_header(tag::EVENT_NOTIFICATION, EVENT_NOTIFICATION_BODY, buf)?;
249        buf[pos] = self.event_type.to_u8();
250        Ok(pos + EVENT_NOTIFICATION_BODY)
251    }
252}
253
254/// Resource-scoped dispatch over the Event Manager objects.
255#[derive(Debug, Clone, PartialEq, Eq)]
256#[cfg_attr(feature = "serde", derive(serde::Serialize))]
257#[non_exhaustive]
258pub enum EventManagerApdu<'a> {
259    /// `event_request` (`9F 80 00`).
260    EventRequest(EventRequest<'a>),
261    /// `event_request_ack` (`9F 80 01`).
262    EventRequestAck(EventRequestAck),
263    /// `event_notification` (`9F 80 02`).
264    EventNotification(EventNotification),
265}
266
267impl<'a> EventManagerApdu<'a> {
268    /// Parse an Event Manager APDU, dispatching on the `apdu_tag`.
269    pub fn parse(body: &'a [u8]) -> Result<Self> {
270        if body.len() < 3 {
271            return Err(Error::BufferTooShort {
272                need: 3,
273                have: body.len(),
274                what: "event_manager apdu_tag",
275            });
276        }
277        let t = ApduTag::from_bytes(body[0], body[1], body[2]);
278        match t {
279            tag::EVENT_REQUEST => Ok(Self::EventRequest(EventRequest::parse(body)?)),
280            tag::EVENT_REQUEST_ACK => Ok(Self::EventRequestAck(EventRequestAck::parse(body)?)),
281            tag::EVENT_NOTIFICATION => Ok(Self::EventNotification(EventNotification::parse(body)?)),
282            _ => Err(Error::UnexpectedApduTag {
283                got: t.as_u24(),
284                expected: tag::EVENT_REQUEST.as_u24(),
285                what: "event_manager",
286            }),
287        }
288    }
289}
290
291impl Serialize for EventManagerApdu<'_> {
292    type Error = Error;
293    fn serialized_len(&self) -> usize {
294        match self {
295            Self::EventRequest(o) => o.serialized_len(),
296            Self::EventRequestAck(o) => o.serialized_len(),
297            Self::EventNotification(o) => o.serialized_len(),
298        }
299    }
300    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
301        match self {
302            Self::EventRequest(o) => o.serialize_into(buf),
303            Self::EventRequestAck(o) => o.serialize_into(buf),
304            Self::EventNotification(o) => o.serialize_into(buf),
305        }
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    #[test]
314    fn event_request_timer_round_trips_and_bites() {
315        // Timer: 40-bit start_time + 24-bit duration = 8 desc bytes.
316        let desc = [0x11, 0x22, 0x33, 0x44, 0x55, 0x06, 0x07, 0x08];
317        let r = EventRequest {
318            event_type: EventType::Timer,
319            event_desc: &desc,
320        };
321        let bytes = r.to_bytes();
322        // tag(3) + len(1) + type(1) + 8 = 13; body len = 9 = 0x09.
323        assert_eq!(
324            bytes,
325            [
326                0x9F, 0x80, 0x00, 0x09, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x06, 0x07, 0x08
327            ]
328        );
329        assert_eq!(EventRequest::parse(&bytes).unwrap(), r);
330        // mutate a desc byte.
331        let mut desc2 = desc;
332        desc2[0] = 0xFF;
333        let other = EventRequest {
334            event_type: EventType::Timer,
335            event_desc: &desc2,
336        };
337        assert_ne!(bytes, other.to_bytes());
338    }
339
340    #[test]
341    fn event_request_cancel_empty_desc() {
342        let r = EventRequest {
343            event_type: EventType::Timer,
344            event_desc: &[],
345        };
346        let bytes = r.to_bytes();
347        assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x01, 0x00]);
348        assert_eq!(EventRequest::parse(&bytes).unwrap(), r);
349    }
350
351    #[test]
352    fn event_request_ack_round_trips_and_bites() {
353        let a = EventRequestAck {
354            event_type: EventType::Timer,
355            reply: EventReply::ResourcesConsumed,
356        };
357        let bytes = a.to_bytes();
358        assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x02, 0x00, 0x02]);
359        assert_eq!(EventRequestAck::parse(&bytes).unwrap(), a);
360        assert_eq!(a.reply.name(), "Event resources consumed");
361        let mut other = a;
362        other.reply = EventReply::BookedOk;
363        assert_ne!(bytes, other.to_bytes());
364    }
365
366    #[test]
367    fn event_notification_round_trips() {
368        let n = EventNotification {
369            event_type: EventType::Timer,
370        };
371        let bytes = n.to_bytes();
372        assert_eq!(bytes, [0x9F, 0x80, 0x02, 0x01, 0x00]);
373        assert_eq!(EventNotification::parse(&bytes).unwrap(), n);
374    }
375
376    #[test]
377    fn dispatch_routes_each_tag() {
378        let req = EventRequest {
379            event_type: EventType::Timer,
380            event_desc: &[],
381        }
382        .to_bytes();
383        assert!(matches!(
384            EventManagerApdu::parse(&req).unwrap(),
385            EventManagerApdu::EventRequest(_)
386        ));
387        let notif = EventNotification {
388            event_type: EventType::Timer,
389        }
390        .to_bytes();
391        let parsed = EventManagerApdu::parse(&notif).unwrap();
392        assert!(matches!(parsed, EventManagerApdu::EventNotification(_)));
393        assert_eq!(parsed.to_bytes(), notif);
394    }
395}