dvb_bbframe/issy.rs
1//! ISSY (Input Stream SYnchronizer) field decoding per EN 302 755 §5.1.7 / Annex C.
2//!
3//! ISSY carries the Input Stream Clock Reference (ISCR) and, in its long form,
4//! buffer-status / time-to-output signalling, used for jitter-free transport
5//! reconstruction at the receiver. The first bit selects the form:
6//!
7//! ```text
8//! bit7 = 0 -> ISCR short: 15-bit ISCR (2-byte ISSY)
9//! bit7 = 1, bit6 = 0 -> ISCR long: 22-bit ISCR (3-byte ISSY)
10//! bit7 = 1, bit6 = 1 -> BUFS / TTO signalling (3-byte ISSY)
11//! ```
12
13use crate::Error;
14
15const ISSY_LONG_FORM_BIT: u8 = 0x80;
16const ISCR_SHORT_PAYLOAD_MASK: u8 = 0x7F;
17const ISCR_LONG_PAYLOAD_MASK: u8 = 0x3F;
18const ISSY_SIGNALLING_BIT: u8 = 0x40;
19
20const SIGNALLING_KIND_SHIFT: u32 = 20;
21const SIGNALLING_KIND_MASK: u32 = 0x03;
22const BUFS_UNIT_SHIFT: u32 = 18;
23const BUFS_UNIT_MASK: u32 = 0x03;
24const BUFS_VALUE_SHIFT: u32 = 8;
25const BUFS_VALUE_MASK: u32 = 0x03FF;
26const TTO_E_MSB_SHIFT: u32 = 16;
27const TTO_E_MSB_MASK: u32 = 0x0F;
28const TTO_E_LSB_SHIFT: u32 = 15;
29const TTO_E_LSB_MASK: u32 = 0x01;
30const TTO_M_SHIFT: u32 = 8;
31const TTO_M_MASK: u32 = 0x7F;
32const RESERVED_PAYLOAD_MASK: u32 = 0x0F_FFFF;
33
34/// BUFS unit selector — EN 302 755 Annex C, Table C.1 (2-bit field).
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize))]
37#[non_exhaustive]
38pub enum BufsUnit {
39 /// 0b00 — bits.
40 Bits,
41 /// 0b01 — Kbits.
42 Kbits,
43 /// 0b10 — Mbits.
44 Mbits,
45 /// 0b11 — 8 Kbits.
46 Kbits8,
47}
48
49impl BufsUnit {
50 #[must_use]
51 /// Construct from a raw `u8` (only the low 2 bits are used).
52 pub fn from_u8(v: u8) -> Self {
53 match v & BUFS_UNIT_MASK as u8 {
54 0 => Self::Bits,
55 1 => Self::Kbits,
56 2 => Self::Mbits,
57 3 => Self::Kbits8,
58 _ => unreachable!(),
59 }
60 }
61
62 #[must_use]
63 /// Return the wire byte for this unit.
64 pub fn to_u8(self) -> u8 {
65 match self {
66 Self::Bits => 0,
67 Self::Kbits => 1,
68 Self::Mbits => 2,
69 Self::Kbits8 => 3,
70 }
71 }
72
73 #[must_use]
74 /// Human-readable unit name.
75 pub fn name(self) -> &'static str {
76 match self {
77 Self::Bits => "bits",
78 Self::Kbits => "Kbits",
79 Self::Mbits => "Mbits",
80 Self::Kbits8 => "8 Kbits",
81 }
82 }
83
84 #[must_use]
85 /// Number of bits per BUFS unit.
86 ///
87 /// Per EN 302 755 Annex C Table C.1, the 2-bit unit selector names the
88 /// scale (bits / Kbits / Mbits / 8Kbits). The standard does not numerically
89 /// define K/M; the decimal convention (K = 1 000, M = 1 000 000, 8K = 8 000)
90 /// is used, consistent with the standard's use of decimal `Mbit/s`
91 /// elsewhere (see `docs/en_302_755_t2.md` §BUFS/TTO semantics).
92 pub fn multiplier_bits(self) -> u64 {
93 match self {
94 Self::Bits => 1,
95 Self::Kbits => 1_000,
96 Self::Mbits => 1_000_000,
97 Self::Kbits8 => 8_000,
98 }
99 }
100}
101
102/// Decoded BUFS/TTO signalling — EN 302 755 Annex C, Table C.1.
103///
104/// The `11` prefix in the first ISSY byte selects one of two alternatives:
105/// BUFS (buffer status) or TTO (time-to-output), indicated by bits `[5:4]`.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize))]
108#[non_exhaustive]
109pub enum SignallingKind {
110 /// BUFS — maximum size of the requested receiver buffer.
111 ///
112 /// Fields: `(bufs, units)` where `bufs` is the 10-bit buffer status
113 /// and `units` is the 2-bit unit selector.
114 Bufs {
115 /// 10-bit buffer status value.
116 bufs: u16,
117 /// 2-bit unit selector (Table C.1).
118 units: BufsUnit,
119 },
120 /// TTO — time-to-output (mantissa + exponent form).
121 ///
122 /// The output time is `TTO = (tto_m + tto_l / 256) * 2^tto_e`
123 /// where `tto_l` is zero when ISCRshort is in use.
124 Tto {
125 /// 5-bit exponent `TTO_E`.
126 tto_e: u8,
127 /// 7-bit mantissa `TTO_M`.
128 tto_m: u8,
129 /// 8-bit low-fraction `TTO_L` (zero when ISCRshort is in use).
130 tto_l: u8,
131 },
132 /// Reserved signalling type (bits `[5:4]` = `0b10` or `0b11`).
133 ///
134 /// Holds the low 20 bits of the signalling payload; the 2-bit kind
135 /// selector is not retained. This is a decode-only view — the wire bytes
136 /// live in `Bbheader::issy_in_header` and are serialized verbatim, so the
137 /// dropped selector does not affect round-trip fidelity.
138 Reserved(u32),
139}
140
141impl SignallingKind {
142 #[must_use]
143 /// Decoded BUFS buffer size in bits, or `None` if this is not a BUFS variant.
144 ///
145 /// `bufs_bits = bufs × units.multiplier_bits()`
146 ///
147 /// See EN 302 755 Annex C Table C.1 + §BUFS/TTO semantics
148 /// (`docs/en_302_755_t2.md`).
149 ///
150 /// # Note on encoders
151 ///
152 /// Only decode accessors are provided. No `set_*` or `from_*` encoders are
153 /// added because the physical-value → mantissa/exponent TTO encoding is
154 /// lossy and the wire round-trip is already guaranteed by the existing
155 /// raw-field serialization in `Bbheader`. Use the raw-field constructors
156 /// (`SignallingKind::Bufs { … }` / `SignallingKind::Tto { … }`) for
157 /// encoding.
158 pub fn bufs_bits(&self) -> Option<u64> {
159 match self {
160 Self::Bufs { bufs, units } => Some(*bufs as u64 * units.multiplier_bits()),
161 _ => None,
162 }
163 }
164
165 #[must_use]
166 /// Decoded BUFS buffer size in bytes (integer floor), or `None`.
167 ///
168 /// `bufs_bytes = bufs_bits() / 8`. Integer division is used: BUFS is a
169 /// maximum-size bound per the standard, so a floor is appropriate.
170 ///
171 /// See EN 302 755 Annex C Table C.1 + §BUFS/TTO semantics
172 /// (`docs/en_302_755_t2.md`).
173 pub fn bufs_bytes(&self) -> Option<u64> {
174 self.bufs_bits().map(|b| b / 8)
175 }
176
177 #[must_use]
178 /// Decoded time-to-output in units of T/256, or `None` if this is not a
179 /// TTO variant.
180 ///
181 /// `tto_t_over_256 = ((TTO_M × 256) + TTO_L) × 2^TTO_E`
182 ///
183 /// This is `TTO × 256` in units of the elementary period **T** (see EN 302
184 /// 755 §9.5 / Table 65). The `TTO_L / 256` fractional term is preserved
185 /// exactly without floating point; consumers divide by 256.0 to obtain
186 /// `TTO` in units of T.
187 ///
188 /// Per EN 302 755 Annex C Table C.1 + §8.3.3
189 /// (`docs/en_302_755_t2.md`).
190 pub fn tto_t_over_256(&self) -> Option<u64> {
191 match self {
192 Self::Tto {
193 tto_e,
194 tto_m,
195 tto_l,
196 } => Some((u64::from(*tto_m) * 256 + u64::from(*tto_l)) << tto_e),
197 _ => None,
198 }
199 }
200}
201
202/// Decoded ISSY value (EN 302 755 §5.1.7, Annex C).
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204#[cfg_attr(feature = "serde", derive(serde::Serialize))]
205#[non_exhaustive]
206pub enum Issy {
207 /// ISCR short form — 15-bit Input Stream Clock Reference (2-byte ISSY).
208 IscrShort(u16),
209 /// ISCR long form — 22-bit Input Stream Clock Reference (3-byte ISSY).
210 IscrLong(u32),
211 /// Long-form BUFS/TTO signalling (3-byte ISSY, `11` prefix).
212 ///
213 /// The 22-bit payload is decoded into [`SignallingKind`]; see
214 /// Annex C for the sub-coding.
215 Signalling(SignallingKind),
216}
217
218/// Decode a 2-byte (short) ISSY field.
219///
220/// Returns `Ok(Issy::IscrShort)` when the short-form bit (`[7]` of byte 0) is
221/// `0`; `Err` otherwise (a `1` prefix means a long-form field, which is 3 bytes
222/// and must be decoded with [`decode_issy_long`]).
223pub fn decode_issy_short(bytes: [u8; 2]) -> crate::Result<Issy> {
224 if bytes[0] & ISSY_LONG_FORM_BIT != 0 {
225 return Err(Error::InvalidIssyForm {
226 reason: "bit [7] is 1 (long form); use decode_issy_long for 3-byte ISSY",
227 });
228 }
229 let iscr = ((bytes[0] as u16 & ISCR_SHORT_PAYLOAD_MASK as u16) << 8) | bytes[1] as u16;
230 Ok(Issy::IscrShort(iscr))
231}
232
233/// Decode a 3-byte (long) ISSY field.
234///
235/// Byte 0 bit `[7]` must be `1` (long form). Byte 0 bit `[6]` then selects: `0` → 22-bit
236/// ISCR long; `1` → BUFS/TTO signalling. Returns `Err` if bit `[7]` is `0` (that is
237/// a short-form field — use [`decode_issy_short`]).
238pub fn decode_issy_long(bytes: [u8; 3]) -> crate::Result<Issy> {
239 if bytes[0] & ISSY_LONG_FORM_BIT == 0 {
240 return Err(Error::InvalidIssyForm {
241 reason: "bit [7] is 0 (short form); use decode_issy_short for 2-byte ISSY",
242 });
243 }
244 let payload = ((bytes[0] as u32 & ISCR_LONG_PAYLOAD_MASK as u32) << 16)
245 | (bytes[1] as u32) << 8
246 | bytes[2] as u32;
247 if bytes[0] & ISSY_SIGNALLING_BIT == 0 {
248 Ok(Issy::IscrLong(payload))
249 } else {
250 Ok(Issy::Signalling(decode_signalling(payload)))
251 }
252}
253
254/// Decode the 22-bit `11`-prefix payload per Annex C Table C.1.
255///
256/// Bits `[21:20]` select the signalling type:
257/// - `0b00` → BUFS: bits `[19:18]` = unit, bits `[17:8]` = 10-bit BUFS, `[7:0]` reserved
258/// - `0b01` → TTO: bits `[19:16]` = 4 MSBs of TTO_E, byte 1 bit `[7]` = LSB of TTO_E,
259/// byte 1 bits `[6:0]` = TTO_M, byte 2 = TTO_L (or reserved for ISCRshort)
260/// - `0b10`, `0b11` → reserved
261fn decode_signalling(payload: u32) -> SignallingKind {
262 let kind = (payload >> SIGNALLING_KIND_SHIFT) & SIGNALLING_KIND_MASK;
263 match kind {
264 0 => {
265 let units = BufsUnit::from_u8(((payload >> BUFS_UNIT_SHIFT) & BUFS_UNIT_MASK) as u8);
266 let bufs = ((payload >> BUFS_VALUE_SHIFT) & BUFS_VALUE_MASK) as u16;
267 SignallingKind::Bufs { bufs, units }
268 }
269 1 => {
270 let tto_e = (((payload >> TTO_E_MSB_SHIFT) & TTO_E_MSB_MASK) << 1
271 | ((payload >> TTO_E_LSB_SHIFT) & TTO_E_LSB_MASK)) as u8;
272 let tto_m = ((payload >> TTO_M_SHIFT) & TTO_M_MASK) as u8;
273 let tto_l = (payload & 0xFF) as u8;
274 SignallingKind::Tto {
275 tto_e,
276 tto_m,
277 tto_l,
278 }
279 }
280 _ => {
281 let remainder = payload & RESERVED_PAYLOAD_MASK;
282 SignallingKind::Reserved(remainder)
283 }
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn iscr_short_decodes_15_bits() {
293 assert_eq!(decode_issy_short([0x7A, 0xBC]), Ok(Issy::IscrShort(0x7ABC)));
294 assert_eq!(decode_issy_short([0x00, 0x01]), Ok(Issy::IscrShort(1)));
295 }
296
297 #[test]
298 fn short_rejects_long_prefix() {
299 assert!(decode_issy_short([0x80, 0x00]).is_err());
300 }
301
302 #[test]
303 fn iscr_long_decodes_22_bits() {
304 assert_eq!(
305 decode_issy_long([0xBF, 0xFF, 0xFF]),
306 Ok(Issy::IscrLong(0x3FFFFF))
307 );
308 assert_eq!(
309 decode_issy_long([0x80, 0x12, 0x34]),
310 Ok(Issy::IscrLong(0x1234))
311 );
312 }
313
314 #[test]
315 fn signalling_decodes_with_11_prefix() {
316 assert_eq!(
317 decode_issy_long([0xC0, 0x12, 0x34]),
318 Ok(Issy::Signalling(decode_signalling(0x1234)))
319 );
320 }
321
322 #[test]
323 fn long_rejects_short_prefix() {
324 assert!(decode_issy_long([0x00, 0x00, 0x00]).is_err());
325 }
326
327 #[test]
328 fn signalling_bufs_decode() {
329 // bytes [0xCB, 0xFF, 0x00]: byte0 has the '11' ISSY prefix in bits[7:6];
330 // the 22-bit payload = ((0xCB & 0x3F) << 16) | (0xFF << 8) | 0x00 = 0x0B_FF_00
331 // = 0000_1011_1111_1111_0000_0000, so:
332 // bits[21:20] = 00 => BUFS form
333 // bits[19:18] = 10 => unit (Mbit)
334 // bits[17:8] = 11_1111_1111 = 0x3FF => BUFS = 1023
335 // bits[7:0] = reserved
336 let result = decode_issy_long([0xCB, 0xFF, 0x00]).unwrap();
337 match result {
338 Issy::Signalling(SignallingKind::Bufs { bufs, units }) => {
339 assert_eq!(bufs, 0x3FF);
340 assert_eq!(units, BufsUnit::Mbits);
341 }
342 other => panic!("expected BUFS, got {other:?}"),
343 }
344 }
345
346 #[test]
347 fn signalling_tto_decode() {
348 // '11' prefix, bits[21:20]=0b01 (TTO)
349 // bits[19:16]=0b0101 (4 MSBs of TTO_E = 5)
350 // byte1 bit7 = LSB of TTO_E (1 => TTO_E = 0b1011 = 11)
351 // byte1 bits[6:0] = TTO_M = 0x7F = 127
352 // byte2 = TTO_L = 0x80
353 // byte0: 0b11_01_0101 = 0xD5
354 // byte1: 0b1_1111111 = 0xFF
355 // byte2: 0x80
356 // payload = ((0xD5 & 0x3F) << 16) | (0xFF << 8) | 0x80
357 // = (0x15 << 16) | 0xFF00 | 0x80
358 // = 0x15_FF_80
359 // bits[21:20] = 01 => TTO
360 // bits[19:16] = 0101 => TTO_E MSBs = 5
361 // bit 15 = 1 => TTO_E LSB = 1 => TTO_E = 0b1011 = 11
362 // bits[14:8] = 1111111 => TTO_M = 127
363 // bits[7:0] = 10000000 => TTO_L = 128
364 let result = decode_issy_long([0xD5, 0xFF, 0x80]).unwrap();
365 match result {
366 Issy::Signalling(SignallingKind::Tto {
367 tto_e,
368 tto_m,
369 tto_l,
370 }) => {
371 assert_eq!(tto_e, 11);
372 assert_eq!(tto_m, 127);
373 assert_eq!(tto_l, 128);
374 }
375 other => panic!("expected TTO, got {other:?}"),
376 }
377 }
378
379 #[test]
380 fn signalling_reserved_decode() {
381 // '11' prefix, bits[21:20]=0b10 (reserved)
382 // byte0: 0b11_10_XXXX = 0b11_10_0000 = 0xE0
383 // payload = ((0xE0 & 0x3F) << 16) | 0x0000 = 0x200000
384 // kind = (0x200000 >> 20) & 0x03 = 2 => reserved
385 let result = decode_issy_long([0xE0, 0x00, 0x00]).unwrap();
386 match result {
387 Issy::Signalling(SignallingKind::Reserved(remainder)) => {
388 assert_eq!(remainder, 0x00000);
389 }
390 other => panic!("expected Reserved, got {other:?}"),
391 }
392 }
393
394 #[test]
395 fn bufs_unit_round_trip() {
396 for b in 0..=3u8 {
397 assert_eq!(BufsUnit::from_u8(b).to_u8(), b);
398 }
399 }
400
401 #[test]
402 fn bufs_unit_name() {
403 assert_eq!(BufsUnit::Bits.name(), "bits");
404 assert_eq!(BufsUnit::Kbits.name(), "Kbits");
405 assert_eq!(BufsUnit::Mbits.name(), "Mbits");
406 assert_eq!(BufsUnit::Kbits8.name(), "8 Kbits");
407 }
408
409 #[test]
410 fn multiplier_bits() {
411 assert_eq!(BufsUnit::Bits.multiplier_bits(), 1);
412 assert_eq!(BufsUnit::Kbits.multiplier_bits(), 1_000);
413 assert_eq!(BufsUnit::Mbits.multiplier_bits(), 1_000_000);
414 assert_eq!(BufsUnit::Kbits8.multiplier_bits(), 8_000);
415 }
416
417 #[test]
418 fn bufs_bits_and_bytes() {
419 // BUFS = 2 Mbits → 2 * 1_000_000 = 2_000_000 bits → 250_000 bytes
420 let b = SignallingKind::Bufs {
421 bufs: 2,
422 units: BufsUnit::Mbits,
423 };
424 assert_eq!(b.bufs_bits(), Some(2_000_000));
425 assert_eq!(b.bufs_bytes(), Some(250_000));
426
427 // BUFS = 1 bit
428 let b = SignallingKind::Bufs {
429 bufs: 1,
430 units: BufsUnit::Bits,
431 };
432 assert_eq!(b.bufs_bits(), Some(1));
433 assert_eq!(b.bufs_bytes(), Some(0)); // 1/8 = 0 (integer floor)
434
435 // BUFS = 3 × 8Kbits → 3 * 8000 = 24_000 bits → 3_000 bytes
436 let b = SignallingKind::Bufs {
437 bufs: 3,
438 units: BufsUnit::Kbits8,
439 };
440 assert_eq!(b.bufs_bits(), Some(24_000));
441 assert_eq!(b.bufs_bytes(), Some(3_000));
442
443 // TTO variant returns None
444 let t = SignallingKind::Tto {
445 tto_e: 0,
446 tto_m: 0,
447 tto_l: 0,
448 };
449 assert_eq!(t.bufs_bits(), None);
450 assert_eq!(t.bufs_bytes(), None);
451
452 // Reserved variant returns None
453 assert_eq!(SignallingKind::Reserved(0).bufs_bits(), None);
454 }
455
456 #[test]
457 fn tto_t_over_256() {
458 // TTO_E=0, TTO_M=1, TTO_L=0 → ((1*256 + 0) << 0) = 256 (= 1·T in T/256 units)
459 let t = SignallingKind::Tto {
460 tto_e: 0,
461 tto_m: 1,
462 tto_l: 0,
463 };
464 assert_eq!(t.tto_t_over_256(), Some(256));
465
466 // TTO_E=1, TTO_M=0, TTO_L=128 → ((0*256 + 128) << 1) = 256 (= (128/256)*T*2 = 1·T → 256 in T/256 units)
467 let t = SignallingKind::Tto {
468 tto_e: 1,
469 tto_m: 0,
470 tto_l: 128,
471 };
472 assert_eq!(t.tto_t_over_256(), Some(256));
473
474 // TTO_E=5, TTO_M=3, TTO_L=64 → ((3*256 + 64) << 5) = (768 + 64) * 32 = 832 * 32 = 26_624
475 let t = SignallingKind::Tto {
476 tto_e: 5,
477 tto_m: 3,
478 tto_l: 64,
479 };
480 assert_eq!(t.tto_t_over_256(), Some(26_624));
481
482 // Bufs variant returns None
483 let b = SignallingKind::Bufs {
484 bufs: 1,
485 units: BufsUnit::Bits,
486 };
487 assert_eq!(b.tto_t_over_256(), None);
488
489 // Reserved variant returns None
490 assert_eq!(SignallingKind::Reserved(0).tto_t_over_256(), None);
491 }
492}