dvb-ci 0.12.0

DVB Common Interface (EN 50221) — APDU/resource objects, session/transport PDUs, and a CA_PMT builder from a dvb-si PMT.
Documentation
//! Event Manager objects — ETSI TS 101 699 V1.1.1 §6.4, Tables 56-61
//! (PDF pp. 55-56). See `private/specs-verified/dvb/dvb-ci/ci_plus/event-manager.md`.
//!
//! Resource ID `0x00231ii1` (`ii` = Module ID). Lets a module book timer events
//! that wake the host.
//!
//! - `event_request` (`9F 80 00`, Table 56) — module → host: book/cancel an event.
//! - `event_request_ack` (`9F 80 01`, Table 59) — host → module: reply.
//! - `event_notification` (`9F 80 02`, Table 61) — host → module: event occurred.

use crate::error::{Error, Result};
use crate::objects;
use broadcast_common::len::need_in;
use broadcast_common::{Parse, Serialize};

/// Resource-scoped `apdu_tag`s for the Event Manager (Tables 56, 59, 61).
pub mod tag {
    use crate::tag::ApduTag;
    /// `event_request_tag` = `9F 80 00`.
    pub const EVENT_REQUEST: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
    /// `event_request_ack_tag` = `9F 80 01`.
    pub const EVENT_REQUEST_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
    /// `event_notification_tag` = `9F 80 02`.
    pub const EVENT_NOTIFICATION: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
}

/// `event_type` — the kind of event (Table 57).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum EventType {
    /// `0` — Timer.
    Timer,
    /// `1`-`255` — reserved for future use.
    Reserved(u8),
}

impl EventType {
    /// Decode the `event_type` byte.
    #[must_use]
    pub fn from_u8(v: u8) -> Self {
        match v {
            0 => Self::Timer,
            other => Self::Reserved(other),
        }
    }
    /// Wire byte.
    #[must_use]
    pub const fn to_u8(self) -> u8 {
        match self {
            Self::Timer => 0,
            Self::Reserved(v) => v,
        }
    }
    /// Spec token, or `"reserved"`.
    #[must_use]
    pub fn name(&self) -> &'static str {
        match self {
            Self::Timer => "Timer",
            Self::Reserved(_) => "reserved",
        }
    }
}
broadcast_common::impl_spec_display!(EventType, Reserved);

/// `reply` — the event request reply code (Table 60).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum EventReply {
    /// `0` — Event booked OK.
    BookedOk,
    /// `1` — Event type not supported.
    TypeNotSupported,
    /// `2` — Event resources consumed.
    ResourcesConsumed,
    /// `3`-`255` — reserved for future use.
    Reserved(u8),
}

impl EventReply {
    /// Decode the `reply` byte.
    #[must_use]
    pub fn from_u8(v: u8) -> Self {
        match v {
            0 => Self::BookedOk,
            1 => Self::TypeNotSupported,
            2 => Self::ResourcesConsumed,
            other => Self::Reserved(other),
        }
    }
    /// Wire byte.
    #[must_use]
    pub const fn to_u8(self) -> u8 {
        match self {
            Self::BookedOk => 0,
            Self::TypeNotSupported => 1,
            Self::ResourcesConsumed => 2,
            Self::Reserved(v) => v,
        }
    }
    /// Spec token, or `"reserved"`.
    #[must_use]
    pub fn name(&self) -> &'static str {
        match self {
            Self::BookedOk => "Event booked OK",
            Self::TypeNotSupported => "Event type not supported",
            Self::ResourcesConsumed => "Event resources consumed",
            Self::Reserved(_) => "reserved",
        }
    }
}
broadcast_common::impl_spec_display!(EventReply, Reserved);

/// `event_request()` (Table 56): module → host. The `event_desc` bytes define
/// the event; their format depends on `event_type` (Table 58 — for a Timer,
/// 40-bit start_time + 24-bit duration). An empty `event_desc` cancels any
/// previously-booked event of this type. Carried verbatim for fidelity.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EventRequest<'a> {
    /// How the `length_field` was coded on the wire: a non-minimal long form
    /// (e.g. `82 00 09`) is preserved so the object re-serializes
    /// byte-for-byte; [`LengthForm::Minimal`](crate::length::LengthForm::Minimal) for a hand-built value.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub length_form: crate::length::LengthForm,

    /// `event_type`.
    pub event_type: EventType,
    /// The `event_desc` block (format depends on `event_type`).
    #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
    pub event_desc: &'a [u8],
}

/// `event_request_ack()` (Table 59): host → module.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EventRequestAck {
    /// How the `length_field` was coded on the wire: a non-minimal long form
    /// (e.g. `82 00 09`) is preserved so the object re-serializes
    /// byte-for-byte; [`LengthForm::Minimal`](crate::length::LengthForm::Minimal) for a hand-built value.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub length_form: crate::length::LengthForm,

    /// `event_type`.
    pub event_type: EventType,
    /// `reply`.
    pub reply: EventReply,
}

/// `event_notification()` (Table 61): host → module.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct EventNotification {
    /// How the `length_field` was coded on the wire: a non-minimal long form
    /// (e.g. `82 00 09`) is preserved so the object re-serializes
    /// byte-for-byte; [`LengthForm::Minimal`](crate::length::LengthForm::Minimal) for a hand-built value.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub length_form: crate::length::LengthForm,

    /// `event_type`.
    pub event_type: EventType,
}

// --- event_request ---

// event_type(1) + event_desc(N).
const EVENT_REQUEST_PREFIX: usize = 1;

impl<'a> Parse<'a> for EventRequest<'a> {
    type Error = Error;
    fn parse(bytes: &'a [u8]) -> Result<Self> {
        let (length_form, body) =
            objects::parse_apdu_header(bytes, tag::EVENT_REQUEST, "event_request")?;
        need_in(body, EVENT_REQUEST_PREFIX, "event_request")?;
        Ok(Self {
            length_form,
            event_type: EventType::from_u8(body[0]),
            event_desc: &body[EVENT_REQUEST_PREFIX..],
        })
    }
}
impl Serialize for EventRequest<'_> {
    type Error = Error;
    fn serialized_len(&self) -> usize {
        objects::apdu_len(
            EVENT_REQUEST_PREFIX + self.event_desc.len(),
            self.length_form,
        )
    }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
        let body_len = EVENT_REQUEST_PREFIX + self.event_desc.len();
        let mut pos =
            objects::write_apdu_header(tag::EVENT_REQUEST, body_len, self.length_form, buf)?;
        buf[pos] = self.event_type.to_u8();
        pos += EVENT_REQUEST_PREFIX;
        buf[pos..pos + self.event_desc.len()].copy_from_slice(self.event_desc);
        Ok(pos + self.event_desc.len())
    }
}

// --- event_request_ack ---

// event_type(1) + reply(1).
const EVENT_REQUEST_ACK_BODY: usize = 2;

impl<'a> Parse<'a> for EventRequestAck {
    type Error = Error;
    fn parse(bytes: &'a [u8]) -> Result<Self> {
        let (length_form, body) =
            objects::parse_apdu_header(bytes, tag::EVENT_REQUEST_ACK, "event_request_ack")?;
        need_in(body, EVENT_REQUEST_ACK_BODY, "event_request_ack")?;
        crate::objects::reject_trailing_body(body, EVENT_REQUEST_ACK_BODY, "event_request_ack")?;
        Ok(Self {
            length_form,
            event_type: EventType::from_u8(body[0]),
            reply: EventReply::from_u8(body[1]),
        })
    }
}
impl Serialize for EventRequestAck {
    type Error = Error;
    fn serialized_len(&self) -> usize {
        objects::apdu_len(EVENT_REQUEST_ACK_BODY, self.length_form)
    }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
        let pos = objects::write_apdu_header(
            tag::EVENT_REQUEST_ACK,
            EVENT_REQUEST_ACK_BODY,
            self.length_form,
            buf,
        )?;
        buf[pos] = self.event_type.to_u8();
        buf[pos + 1] = self.reply.to_u8();
        Ok(pos + EVENT_REQUEST_ACK_BODY)
    }
}

// --- event_notification ---

// event_type(1).
const EVENT_NOTIFICATION_BODY: usize = 1;

impl<'a> Parse<'a> for EventNotification {
    type Error = Error;
    fn parse(bytes: &'a [u8]) -> Result<Self> {
        let (length_form, body) =
            objects::parse_apdu_header(bytes, tag::EVENT_NOTIFICATION, "event_notification")?;
        need_in(body, EVENT_NOTIFICATION_BODY, "event_notification")?;
        crate::objects::reject_trailing_body(body, EVENT_NOTIFICATION_BODY, "event_notification")?;
        Ok(Self {
            length_form,
            event_type: EventType::from_u8(body[0]),
        })
    }
}
impl Serialize for EventNotification {
    type Error = Error;
    fn serialized_len(&self) -> usize {
        objects::apdu_len(EVENT_NOTIFICATION_BODY, self.length_form)
    }
    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
        let pos = objects::write_apdu_header(
            tag::EVENT_NOTIFICATION,
            EVENT_NOTIFICATION_BODY,
            self.length_form,
            buf,
        )?;
        buf[pos] = self.event_type.to_u8();
        Ok(pos + EVENT_NOTIFICATION_BODY)
    }
}

crate::dispatch::declare_resource_apdus! {
    /// Resource-scoped dispatch over the Event Manager objects.
    #[derive(Debug, Clone, PartialEq, Eq)]
    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
    #[non_exhaustive]
    pub enum EventManagerApdu<'a> ("event_manager") {
        /// `event_request` (`9F 80 00`).
        EventRequest(EventRequest<'a>) = tag::EVENT_REQUEST,
        /// `event_request_ack` (`9F 80 01`).
        EventRequestAck(EventRequestAck) = tag::EVENT_REQUEST_ACK,
        /// `event_notification` (`9F 80 02`).
        EventNotification(EventNotification) = tag::EVENT_NOTIFICATION,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn event_request_timer_round_trips_and_bites() {
        // Timer: 40-bit start_time + 24-bit duration = 8 desc bytes.
        let desc = [0x11, 0x22, 0x33, 0x44, 0x55, 0x06, 0x07, 0x08];
        let r = EventRequest {
            length_form: crate::length::LengthForm::Minimal,
            event_type: EventType::Timer,
            event_desc: &desc,
        };
        let bytes = r.try_to_bytes().unwrap();
        // tag(3) + len(1) + type(1) + 8 = 13; body len = 9 = 0x09.
        assert_eq!(
            bytes,
            [
                0x9F, 0x80, 0x00, 0x09, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x06, 0x07, 0x08
            ]
        );
        assert_eq!(EventRequest::parse(&bytes).unwrap(), r);
        // mutate a desc byte.
        let mut desc2 = desc;
        desc2[0] = 0xFF;
        let other = EventRequest {
            length_form: crate::length::LengthForm::Minimal,
            event_type: EventType::Timer,
            event_desc: &desc2,
        };
        assert_ne!(bytes, other.try_to_bytes().unwrap());
    }

    #[test]
    fn event_request_cancel_empty_desc() {
        let r = EventRequest {
            length_form: crate::length::LengthForm::Minimal,
            event_type: EventType::Timer,
            event_desc: &[],
        };
        let bytes = r.try_to_bytes().unwrap();
        assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x01, 0x00]);
        assert_eq!(EventRequest::parse(&bytes).unwrap(), r);
    }

    #[test]
    fn event_request_ack_round_trips_and_bites() {
        let a = EventRequestAck {
            length_form: crate::length::LengthForm::Minimal,
            event_type: EventType::Timer,
            reply: EventReply::ResourcesConsumed,
        };
        let bytes = a.try_to_bytes().unwrap();
        assert_eq!(bytes, [0x9F, 0x80, 0x01, 0x02, 0x00, 0x02]);
        assert_eq!(EventRequestAck::parse(&bytes).unwrap(), a);
        assert_eq!(a.reply.name(), "Event resources consumed");
        let mut other = a;
        other.reply = EventReply::BookedOk;
        assert_ne!(bytes, other.try_to_bytes().unwrap());
    }

    #[test]
    fn event_notification_round_trips() {
        let n = EventNotification {
            length_form: crate::length::LengthForm::Minimal,
            event_type: EventType::Timer,
        };
        let bytes = n.try_to_bytes().unwrap();
        assert_eq!(bytes, [0x9F, 0x80, 0x02, 0x01, 0x00]);
        assert_eq!(EventNotification::parse(&bytes).unwrap(), n);
    }

    #[test]
    fn dispatch_routes_each_tag() {
        let req = EventRequest {
            length_form: crate::length::LengthForm::Minimal,
            event_type: EventType::Timer,
            event_desc: &[],
        }
        .try_to_bytes()
        .unwrap();
        assert!(matches!(
            EventManagerApdu::parse(&req).unwrap(),
            EventManagerApdu::EventRequest(_)
        ));
        let notif = EventNotification {
            length_form: crate::length::LengthForm::Minimal,
            event_type: EventType::Timer,
        }
        .try_to_bytes()
        .unwrap();
        let parsed = EventManagerApdu::parse(&notif).unwrap();
        assert!(matches!(parsed, EventManagerApdu::EventNotification(_)));
        assert_eq!(parsed.try_to_bytes().unwrap(), notif);
    }
}