Skip to main content

dvb_ci/ci_plus/
uri.rs

1//! Usage Rules Information (URI) version 3 — ETSI TS 103 205 V1.4.1 §11,
2//! Tables 90-92 (PDF pp. 110-112). See `docs/ts_103_205/usage-rules-v3.md`.
3//!
4//! CI Plus URI v3 extends the v1/v2 `uri_message` syntax (CI Plus V1.3 \[3\]
5//! §5.7.5.2) with the `trick_mode_control_info` signal, applicable to content with
6//! `emi_copy_control_info == 0b10` ("one generation copy is permitted").
7//!
8//! The URI message is the `uri_message` datatype carried in the SAC URI
9//! transmission protocol (datatype_id 25; see `content-control.md` §6.4.3.3.1). It
10//! is **not** an APDU and has **no resource of its own**, so [`UriMessage`] is a
11//! standalone typed struct (full `Parse` / `Serialize`) — it is not wired into
12//! [`crate::ci_plus::CiPlusApdu`]. Callers extract it from the Content Control SAC
13//! layer's URI datatype payload.
14//!
15//! The on-wire structure is a fixed **64 bits (8 bytes)** (matching the
16//! `uri_message` SAC datatype length). Several fields are conditional on
17//! `emi_copy_control_info` (Table 91): the `rct`/`dot`/`rl`/`trick_mode` bits only
18//! carry meaning in the matching EMI case; in the other cases those bit positions
19//! are reserved. To keep serialize byte-exact and lossless this struct stores the
20//! always-present fields plus an [`EmiData`] enum selected by the EMI value.
21
22use crate::error::{Error, Result};
23use broadcast_common::{Parse, Serialize};
24
25/// `uri_message` fixed length — 64 bits (8 bytes).
26pub const URI_MESSAGE_LEN: usize = 8;
27
28/// `protocol_version` value for URI v3 (Table 90).
29pub const PROTOCOL_VERSION_V3: u8 = 0x03;
30
31/// The EMI-case-selected fields of a [`UriMessage`] (Table 91). The variant is
32/// chosen by `emi_copy_control_info`; in the non-matching cases the corresponding
33/// bit positions are reserved (encoded as zero) and not carried.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36#[non_exhaustive]
37pub enum EmiData {
38    /// `emi_copy_control_info == 0b00` ("copying not restricted") — carries the
39    /// `rct_copy_control_info` bit.
40    CopyingNotRestricted {
41        /// `rct_copy_control_info` (1).
42        rct: bool,
43    },
44    /// `emi_copy_control_info == 0b01` — no case-specific bits (those positions
45    /// are reserved).
46    CopyOnce,
47    /// `emi_copy_control_info == 0b10` ("one generation copy is permitted") —
48    /// carries the `trick_mode_control_info` bit (Table 92).
49    OneGenerationCopy {
50        /// `trick_mode_control_info` (1) — `true` = trick mode control enabled.
51        trick_mode: bool,
52    },
53    /// `emi_copy_control_info == 0b11` ("no more copies") — carries
54    /// `dot_copy_control_info` and `rl_copy_control_info`.
55    NoMoreCopies {
56        /// `dot_copy_control_info` (1).
57        dot: bool,
58        /// `rl_copy_control_info` (8).
59        rl: u8,
60    },
61}
62impl EmiData {
63    /// The 2-bit `emi_copy_control_info` value this variant represents.
64    #[must_use]
65    pub fn emi(&self) -> u8 {
66        match self {
67            Self::CopyingNotRestricted { .. } => 0b00,
68            Self::CopyOnce => 0b01,
69            Self::OneGenerationCopy { .. } => 0b10,
70            Self::NoMoreCopies { .. } => 0b11,
71        }
72    }
73}
74
75/// A CI Plus `uri_message()` (Table 91), URI version 3.
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77#[cfg_attr(feature = "serde", derive(serde::Serialize))]
78pub struct UriMessage {
79    /// `protocol_version` (8) — `0x03` for URI v3.
80    pub protocol_version: u8,
81    /// `aps_copy_control_info` (2).
82    pub aps: u8,
83    /// `ict_copy_control_info` (1).
84    pub ict: bool,
85    /// The `emi_copy_control_info`-selected fields (also encodes the 2-bit EMI).
86    pub emi_data: EmiData,
87}
88
89// Bit layout (Table 91), MSB-first within the 8-byte field, after protocol_version:
90//   aps(2) emi(2) ict(1) | [rct|reserved](1) reserved(1) |
91//   [dot,rl(8) | reserved(9)](9) | [trick|reserved](1) | reserved(39)
92//
93// We assemble the 64-bit value MSB-first then split to bytes.
94
95// EMI case selectors.
96const EMI_COPY_NOT_RESTRICTED: u8 = 0b00;
97const EMI_COPY_ONCE: u8 = 0b01;
98const EMI_ONE_GEN_COPY: u8 = 0b10;
99const EMI_NO_MORE_COPIES: u8 = 0b11;
100
101impl<'a> Parse<'a> for UriMessage {
102    type Error = Error;
103    fn parse(bytes: &'a [u8]) -> Result<Self> {
104        if bytes.len() < URI_MESSAGE_LEN {
105            return Err(Error::BufferTooShort {
106                need: URI_MESSAGE_LEN,
107                have: bytes.len(),
108                what: "uri_message",
109            });
110        }
111        let protocol_version = bytes[0];
112        // Assemble the 56 bits following protocol_version into a u64 (in the low
113        // 56 bits), MSB-first, so we can index bit positions from the top.
114        let mut acc: u64 = 0;
115        for &b in &bytes[1..URI_MESSAGE_LEN] {
116            acc = (acc << 8) | b as u64;
117        }
118        // acc holds 56 bits. Bit 55 is the first field bit (aps MSB).
119        let take = |hi_from_top: u32, width: u32| -> u64 {
120            let shift = 56 - hi_from_top - width;
121            (acc >> shift) & ((1u64 << width) - 1)
122        };
123        let aps = take(0, 2) as u8;
124        let emi = take(2, 2) as u8;
125        let ict = take(4, 1) != 0;
126        // bit position 5: rct (emi==00) else reserved.
127        let rct = take(5, 1) != 0;
128        // bit position 6: reserved.
129        // bits 7..16 (9 bits): dot(1)+rl(8) when emi==11 else reserved.
130        let dot = take(7, 1) != 0;
131        let rl = take(8, 8) as u8;
132        // bit position 16: trick_mode when emi==10 else reserved.
133        let trick_mode = take(16, 1) != 0;
134        // bits 17..56 reserved (39).
135        let emi_data = match emi {
136            EMI_COPY_NOT_RESTRICTED => EmiData::CopyingNotRestricted { rct },
137            EMI_COPY_ONCE => EmiData::CopyOnce,
138            EMI_ONE_GEN_COPY => EmiData::OneGenerationCopy { trick_mode },
139            EMI_NO_MORE_COPIES => EmiData::NoMoreCopies { dot, rl },
140            _ => unreachable!("emi is 2 bits"),
141        };
142        Ok(Self {
143            protocol_version,
144            aps,
145            ict,
146            emi_data,
147        })
148    }
149}
150
151impl Serialize for UriMessage {
152    type Error = Error;
153    fn serialized_len(&self) -> usize {
154        URI_MESSAGE_LEN
155    }
156    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
157        if buf.len() < URI_MESSAGE_LEN {
158            return Err(Error::OutputBufferTooSmall {
159                need: URI_MESSAGE_LEN,
160                have: buf.len(),
161            });
162        }
163        let mut acc: u64 = 0;
164        let mut put = |value: u64, hi_from_top: u32, width: u32| {
165            let shift = 56 - hi_from_top - width;
166            acc |= (value & ((1u64 << width) - 1)) << shift;
167        };
168        put(self.aps as u64, 0, 2);
169        put(self.emi_data.emi() as u64, 2, 2);
170        put(u64::from(self.ict), 4, 1);
171        match self.emi_data {
172            EmiData::CopyingNotRestricted { rct } => put(u64::from(rct), 5, 1),
173            EmiData::CopyOnce => {}
174            EmiData::OneGenerationCopy { trick_mode } => put(u64::from(trick_mode), 16, 1),
175            EmiData::NoMoreCopies { dot, rl } => {
176                put(u64::from(dot), 7, 1);
177                put(rl as u64, 8, 8);
178            }
179        }
180        buf[0] = self.protocol_version;
181        // acc holds 56 bits; emit big-endian into bytes 1..8.
182        for (i, slot) in buf[1..URI_MESSAGE_LEN].iter_mut().enumerate() {
183            let shift = 56 - 8 * (i as u32 + 1);
184            *slot = (acc >> shift) as u8;
185        }
186        Ok(URI_MESSAGE_LEN)
187    }
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193
194    #[test]
195    fn default_values_round_trip() {
196        // Table 90 default: protocol 0x03, emi 0b11 (no-more-copies), aps 0b00,
197        // ict 0, dot 0, rl 0x00.
198        let u = UriMessage {
199            protocol_version: PROTOCOL_VERSION_V3,
200            aps: 0b00,
201            ict: false,
202            emi_data: EmiData::NoMoreCopies { dot: false, rl: 0 },
203        };
204        let bytes = u.to_bytes();
205        // protocol_version 0x03; aps(00) emi(11) -> first field byte top:
206        //   bits: aps=00, emi=11, ict=0, ... => 0b00_11_0_..  high byte = 0x30.
207        assert_eq!(bytes.len(), 8);
208        assert_eq!(bytes[0], 0x03);
209        assert_eq!(bytes[1], 0b0011_0000); // aps00 emi11 ict0 + 3 reserved bits
210        assert_eq!(UriMessage::parse(&bytes).unwrap(), u);
211    }
212
213    #[test]
214    fn one_generation_trick_mode_enabled_bites() {
215        let u = UriMessage {
216            protocol_version: PROTOCOL_VERSION_V3,
217            aps: 0b10,
218            ict: true,
219            emi_data: EmiData::OneGenerationCopy { trick_mode: true },
220        };
221        let bytes = u.to_bytes();
222        assert_eq!(bytes[0], 0x03);
223        // aps=10, emi=10, ict=1 => byte1 = 0b10_10_1_000 = 0xA8.
224        assert_eq!(bytes[1], 0b1010_1000);
225        // trick_mode is bit position 16 (from top of the 56-bit field) => bit 0
226        // of byte index 2 (counting field bits: byte1 covers bits 0..7, byte2 8..15,
227        // byte3 16..23). trick at bit 16 => MSB of byte3 (bytes[3]).
228        assert_eq!(bytes[3] & 0x80, 0x80);
229        let parsed = UriMessage::parse(&bytes).unwrap();
230        assert_eq!(parsed, u);
231        // Mutation: disable trick mode.
232        let off = UriMessage {
233            protocol_version: PROTOCOL_VERSION_V3,
234            aps: 0b10,
235            ict: true,
236            emi_data: EmiData::OneGenerationCopy { trick_mode: false },
237        };
238        assert_ne!(bytes, off.to_bytes());
239        assert_eq!(off.to_bytes()[3] & 0x80, 0x00);
240    }
241
242    #[test]
243    fn copying_not_restricted_rct_bit() {
244        let u = UriMessage {
245            protocol_version: PROTOCOL_VERSION_V3,
246            aps: 0b00,
247            ict: false,
248            emi_data: EmiData::CopyingNotRestricted { rct: true },
249        };
250        let bytes = u.to_bytes();
251        // emi=00, ict=0, rct at bit position 5 => byte1 = 0b00_00_0_1_0_0 = 0x04.
252        assert_eq!(bytes[1], 0b0000_0100);
253        assert_eq!(UriMessage::parse(&bytes).unwrap(), u);
254    }
255
256    #[test]
257    fn no_more_copies_dot_rl_round_trips() {
258        let u = UriMessage {
259            protocol_version: PROTOCOL_VERSION_V3,
260            aps: 0b01,
261            ict: true,
262            emi_data: EmiData::NoMoreCopies {
263                dot: true,
264                rl: 0xA5,
265            },
266        };
267        let bytes = u.to_bytes();
268        let parsed = UriMessage::parse(&bytes).unwrap();
269        assert_eq!(parsed, u);
270        // Mutation: change rl.
271        let mut other = u;
272        other.emi_data = EmiData::NoMoreCopies {
273            dot: true,
274            rl: 0xA4,
275        };
276        assert_ne!(bytes, other.to_bytes());
277    }
278
279    #[test]
280    fn copy_once_has_no_case_bits() {
281        let u = UriMessage {
282            protocol_version: PROTOCOL_VERSION_V3,
283            aps: 0b11,
284            ict: false,
285            emi_data: EmiData::CopyOnce,
286        };
287        let bytes = u.to_bytes();
288        // aps=11, emi=01, ict=0 => byte1 = 0b11_01_0_000 = 0xD0; rest reserved 0.
289        assert_eq!(bytes[1], 0b1101_0000);
290        assert_eq!(&bytes[2..8], &[0u8; 6]);
291        assert_eq!(UriMessage::parse(&bytes).unwrap(), u);
292    }
293
294    #[test]
295    fn reserved_bits_in_other_emi_cases_dont_leak() {
296        // A wire message with emi==01 but stray bits set in reserved positions
297        // parses to CopyOnce (case bits ignored), and re-serializes those reserved
298        // bits as zero (lossless within the typed model).
299        let u = UriMessage {
300            protocol_version: PROTOCOL_VERSION_V3,
301            aps: 0,
302            ict: false,
303            emi_data: EmiData::CopyOnce,
304        };
305        let bytes = u.to_bytes();
306        assert_eq!(UriMessage::parse(&bytes).unwrap(), u);
307    }
308
309    #[test]
310    fn too_short_errors() {
311        assert!(matches!(
312            UriMessage::parse(&[0x03, 0x00]),
313            Err(Error::BufferTooShort { .. })
314        ));
315    }
316}