Skip to main content

dvb_ci/objects/
mod.rs

1//! Application-layer APDU objects (resource APDUs) — ETSI EN 50221 §8.4-§8.6.
2//!
3//! Each object implements [`broadcast_common::Parse`] / [`broadcast_common::Serialize`] over
4//! the **whole** APDU including its `apdu_tag` (3 bytes) + `length_field`
5//! header, so dispatch routes on the header and round-trips are byte-symmetric.
6//! The shared header helpers here keep every object's length field computed from
7//! its content.
8
9use crate::error::{Error, Result};
10use crate::length;
11use crate::tag::ApduTag;
12
13pub mod application_info;
14pub mod ca_info;
15pub mod ca_pmt;
16pub mod ca_pmt_reply;
17pub mod date_time;
18pub mod host_control;
19pub mod low_speed_comms;
20pub mod mmi_close;
21pub mod mmi_display;
22pub mod mmi_high;
23pub mod resource_manager;
24
25/// Parse an APDU header: verify the 3-byte `apdu_tag` matches `expected`, decode
26/// the `length_field`, and return the body slice (exactly `length_value` bytes).
27pub(crate) fn parse_apdu_header<'a>(
28    bytes: &'a [u8],
29    expected: ApduTag,
30    what: &'static str,
31) -> Result<&'a [u8]> {
32    if bytes.len() < 3 {
33        return Err(Error::BufferTooShort {
34            need: 3,
35            have: bytes.len(),
36            what,
37        });
38    }
39    let got = ApduTag::from_bytes(bytes[0], bytes[1], bytes[2]);
40    if got != expected {
41        return Err(Error::UnexpectedApduTag {
42            got: got.as_u24(),
43            expected: expected.as_u24(),
44            what,
45        });
46    }
47    let (len_value, len_hdr) = length::decode(&bytes[3..])?;
48    let body_start = 3 + len_hdr;
49    let body_end = body_start + len_value;
50    if bytes.len() < body_end {
51        return Err(Error::LengthMismatch {
52            what,
53            declared: len_value,
54            actual: bytes.len().saturating_sub(body_start),
55        });
56    }
57    Ok(&bytes[body_start..body_end])
58}
59
60/// Parse a header-only (empty-body) APDU, erroring if the body is non-empty.
61pub(crate) fn parse_empty_apdu(bytes: &[u8], expected: ApduTag, what: &'static str) -> Result<()> {
62    let body = parse_apdu_header(bytes, expected, what)?;
63    if !body.is_empty() {
64        return Err(Error::InvalidObject {
65            what,
66            reason: "expected empty body",
67        });
68    }
69    Ok(())
70}
71
72/// Serialized length of an APDU with a `body_len`-byte body.
73pub(crate) fn apdu_len(body_len: usize) -> usize {
74    3 + length::encoded_len(body_len) + body_len
75}
76
77/// Serialized length of a header-only (empty-body) APDU.
78pub(crate) fn empty_apdu_len() -> usize {
79    apdu_len(0)
80}
81
82/// Write an APDU header (tag + `length_field`) into `buf`, returning the number
83/// of header bytes written (the body starts at that offset). Checks the buffer
84/// can hold the whole APDU (`apdu_len(body_len)`) up front.
85pub(crate) fn write_apdu_header(tag: ApduTag, body_len: usize, buf: &mut [u8]) -> Result<usize> {
86    let total = apdu_len(body_len);
87    if buf.len() < total {
88        return Err(Error::OutputBufferTooSmall {
89            need: total,
90            have: buf.len(),
91        });
92    }
93    buf[..3].copy_from_slice(&tag.to_bytes());
94    let n = length::encode_into(body_len, &mut buf[3..])?;
95    Ok(3 + n)
96}
97
98/// Serialize a header-only (empty-body) APDU into `buf`.
99pub(crate) fn serialize_empty_apdu(tag: ApduTag, buf: &mut [u8]) -> Result<usize> {
100    write_apdu_header(tag, 0, buf)
101}
102
103/// serde helper: serialize a borrowed `&[u8]` field as a byte sequence.
104#[cfg(feature = "serde")]
105pub(crate) mod bytes_serde {
106    pub fn serialize<S: serde::Serializer>(bytes: &[u8], s: S) -> Result<S::Ok, S::Error> {
107        s.serialize_bytes(bytes)
108    }
109}