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}