Skip to main content

dvb_simulcrypt/
message.rs

1//! The generic SimulCrypt message: a 5-byte header + a TLV parameter list.
2//!
3//! ETSI TS 103 197 V1.5.1 §4.4.1, Table 1b (PDF p. 26). All multi-byte fields
4//! are big-endian (Table 1b NOTE 1).
5//!
6//! ```text
7//! generic_message {
8//!   protocol_version    1 byte
9//!   message_type        2 bytes
10//!   message_length      2 bytes   # bytes following this field
11//!   for (i=0; i<n; i++) {         # parameters, any order (NOTE 2)
12//!     parameter_type    2 bytes
13//!     parameter_length  2 bytes   # bytes of parameter_value
14//!     parameter_value   <parameter_length> bytes
15//!   }
16//! }
17//! ```
18//!
19//! `message_length` and each `parameter_length` are **recomputed on serialize**
20//! from the typed fields — there is no raw passthrough. Unknown
21//! `message_type`/`parameter_type` values decode to the `Reserved(_)`
22//! catch-alls (TS 103 197: unknown types "shall be ignored", but this codec
23//! preserves them losslessly rather than discarding).
24
25use alloc::vec::Vec;
26
27use broadcast_common::traits::{Parse, Serialize};
28
29use crate::error::{Error, Result};
30use crate::registry::{Interface, MessageType, ParameterType};
31
32/// Bytes of the fixed `generic_message` header: protocol_version (1) +
33/// message_type (2) + message_length (2).
34pub const HEADER_LEN: usize = 5;
35
36/// Bytes of a parameter TLV's fixed prefix: parameter_type (2) +
37/// parameter_length (2).
38pub const PARAMETER_HEADER_LEN: usize = 4;
39
40/// One TLV parameter of a [`SimulcryptMessage`] (Table 1b): a typed
41/// `parameter_type` plus a borrowed, **opaque** `parameter_value`.
42///
43/// The value is never interpreted — CWs (`CP_CW_combination`/`CW_encryption`),
44/// ECMs (`ECM_datagram`) and EMM/private data (`datagram`) are carried as raw
45/// bytes. `parameter_length` is recomputed from `value.len()` on serialize.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize))]
48pub struct Parameter<'a> {
49    /// The interface-tagged `parameter_type`.
50    pub ptype: ParameterType,
51    /// The opaque `parameter_value` bytes (length = `parameter_length`).
52    #[cfg_attr(feature = "serde", serde(borrow))]
53    pub value: &'a [u8],
54}
55
56impl<'a> Parameter<'a> {
57    /// Construct a parameter from a typed `parameter_type` and a value slice.
58    #[must_use]
59    pub const fn new(ptype: ParameterType, value: &'a [u8]) -> Self {
60        Self { ptype, value }
61    }
62
63    /// The total wire length of this TLV (4-byte header + value).
64    #[must_use]
65    pub const fn wire_len(&self) -> usize {
66        PARAMETER_HEADER_LEN + self.value.len()
67    }
68}
69
70/// A generic SimulCrypt message (TS 103 197 Table 1b): the 5-byte header plus
71/// an ordered list of TLV [`Parameter`]s.
72///
73/// The [`Interface`] is not on the wire — it is fixed by the TCP connection the
74/// message arrived on, and is supplied to [`SimulcryptMessage::parse_on`] so the
75/// raw `message_type`/`parameter_type` values can be decoded into the right
76/// interface-scoped registry.
77#[derive(Debug, Clone, PartialEq, Eq)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize))]
79pub struct SimulcryptMessage<'a> {
80    /// `protocol_version` (Table 2; `0x03` for both implemented interfaces).
81    pub protocol_version: u8,
82    /// The interface-tagged `message_type`.
83    pub message_type: MessageType,
84    /// The TLV parameter list, in wire order (order is not significant per
85    /// NOTE 2, but is preserved for byte-exact round-tripping).
86    #[cfg_attr(feature = "serde", serde(borrow))]
87    pub parameters: Vec<Parameter<'a>>,
88}
89
90impl<'a> SimulcryptMessage<'a> {
91    /// Construct a message from typed fields.
92    #[must_use]
93    pub fn new(
94        protocol_version: u8,
95        message_type: MessageType,
96        parameters: Vec<Parameter<'a>>,
97    ) -> Self {
98        Self {
99            protocol_version,
100            message_type,
101            parameters,
102        }
103    }
104
105    /// The interface this message was decoded against (from its `message_type`).
106    #[must_use]
107    pub const fn interface(&self) -> Interface {
108        self.message_type.interface()
109    }
110
111    /// Sum of the wire lengths of every parameter TLV — i.e. the value the
112    /// `message_length` field will carry on serialize.
113    #[must_use]
114    pub fn body_len(&self) -> usize {
115        self.parameters.iter().map(Parameter::wire_len).sum()
116    }
117
118    /// Find the first parameter whose `parameter_type` matches `ptype`.
119    #[must_use]
120    pub fn find(&self, ptype: ParameterType) -> Option<&Parameter<'a>> {
121        self.parameters.iter().find(|p| p.ptype == ptype)
122    }
123
124    /// Parse a message off the wire, decoding `message_type`/`parameter_type`
125    /// against the given [`Interface`] (the connection's interface).
126    ///
127    /// # Errors
128    /// Returns [`Error::BufferTooShort`] if the header or a TLV header is
129    /// truncated, [`Error::InvalidMessageLength`] if `message_length` overruns
130    /// the buffer, and [`Error::TruncatedParameter`] if a `parameter_length`
131    /// runs past the message body.
132    pub fn parse_on(iface: Interface, bytes: &'a [u8]) -> Result<Self> {
133        if bytes.len() < HEADER_LEN {
134            return Err(Error::BufferTooShort {
135                need: HEADER_LEN,
136                have: bytes.len(),
137                what: "generic_message header",
138            });
139        }
140        let protocol_version = bytes[0];
141        let raw_message_type = u16::from_be_bytes([bytes[1], bytes[2]]);
142        let message_length = u16::from_be_bytes([bytes[3], bytes[4]]) as usize;
143
144        let body = &bytes[HEADER_LEN..];
145        if body.len() < message_length {
146            return Err(Error::InvalidMessageLength {
147                length: message_length as u16,
148                reason: "message_length exceeds available bytes",
149            });
150        }
151        let body = &body[..message_length];
152
153        let message_type = MessageType::from_u16(iface, raw_message_type);
154
155        let mut parameters = Vec::new();
156        let mut off = 0usize;
157        while off < body.len() {
158            if body.len() - off < PARAMETER_HEADER_LEN {
159                return Err(Error::BufferTooShort {
160                    need: PARAMETER_HEADER_LEN,
161                    have: body.len() - off,
162                    what: "parameter TLV header",
163                });
164            }
165            let raw_ptype = u16::from_be_bytes([body[off], body[off + 1]]);
166            let plen = u16::from_be_bytes([body[off + 2], body[off + 3]]) as usize;
167            let vstart = off + PARAMETER_HEADER_LEN;
168            let remaining = body.len() - vstart;
169            if remaining < plen {
170                return Err(Error::TruncatedParameter {
171                    ptype: raw_ptype,
172                    need: plen,
173                    have: remaining,
174                });
175            }
176            let value = &body[vstart..vstart + plen];
177            parameters.push(Parameter::new(
178                ParameterType::from_u16(iface, raw_ptype),
179                value,
180            ));
181            off = vstart + plen;
182        }
183
184        Ok(Self {
185            protocol_version,
186            message_type,
187            parameters,
188        })
189    }
190}
191
192impl<'a> Parse<'a> for SimulcryptMessage<'a> {
193    type Error = Error;
194
195    /// Parse against the ECMG⇔SCS interface by default. Most callers should
196    /// use [`SimulcryptMessage::parse_on`] with the connection's interface;
197    /// this `Parse` impl exists to satisfy the workspace-wide trait contract.
198    fn parse(bytes: &'a [u8]) -> Result<Self> {
199        Self::parse_on(Interface::EcmgScs, bytes)
200    }
201}
202
203impl Serialize for SimulcryptMessage<'_> {
204    type Error = Error;
205
206    fn serialized_len(&self) -> usize {
207        HEADER_LEN + self.body_len()
208    }
209
210    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
211        let total = self.serialized_len();
212        if buf.len() < total {
213            return Err(Error::OutputBufferTooSmall {
214                need: total,
215                have: buf.len(),
216            });
217        }
218
219        let body_len = self.body_len();
220        if body_len > u16::MAX as usize {
221            return Err(Error::FieldTooWide {
222                what: "message_length",
223                value: body_len,
224                bits: 16,
225            });
226        }
227
228        buf[0] = self.protocol_version;
229        buf[1..3].copy_from_slice(&self.message_type.to_u16().to_be_bytes());
230        buf[3..5].copy_from_slice(&(body_len as u16).to_be_bytes());
231
232        let mut off = HEADER_LEN;
233        for p in &self.parameters {
234            let plen = p.value.len();
235            if plen > u16::MAX as usize {
236                return Err(Error::FieldTooWide {
237                    what: "parameter_length",
238                    value: plen,
239                    bits: 16,
240                });
241            }
242            buf[off..off + 2].copy_from_slice(&p.ptype.to_u16().to_be_bytes());
243            buf[off + 2..off + 4].copy_from_slice(&(plen as u16).to_be_bytes());
244            off += PARAMETER_HEADER_LEN;
245            buf[off..off + plen].copy_from_slice(p.value);
246            off += plen;
247        }
248
249        debug_assert_eq!(off, total);
250        Ok(total)
251    }
252}