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
13/// Decoded ISSY value (EN 302 755 §5.1.7, Annex C).
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum Issy {
17 /// ISCR short form — 15-bit Input Stream Clock Reference (2-byte ISSY).
18 IscrShort(u16),
19 /// ISCR long form — 22-bit Input Stream Clock Reference (3-byte ISSY).
20 IscrLong(u32),
21 /// Long-form BUFS / TTO signalling (3-byte ISSY, `11` prefix). The 22-bit
22 /// payload is exposed raw; see EN 302 755 Annex C for the BUFS/TTO sub-coding.
23 Signalling(u32),
24}
25
26/// Decode a 2-byte (short) ISSY field.
27///
28/// Returns `Some(Issy::IscrShort)` when the short-form bit (bit 7 of byte 0) is
29/// `0`; `None` otherwise (a `1` prefix means a long-form field, which is 3 bytes
30/// and must be decoded with [`decode_issy_long`]).
31#[must_use]
32pub fn decode_issy_short(bytes: [u8; 2]) -> Option<Issy> {
33 if bytes[0] & 0x80 != 0 {
34 return None;
35 }
36 let iscr = ((bytes[0] as u16 & 0x7F) << 8) | bytes[1] as u16;
37 Some(Issy::IscrShort(iscr))
38}
39
40/// Decode a 3-byte (long) ISSY field.
41///
42/// Byte 0 bit 7 must be `1` (long form). Byte 0 bit 6 then selects: `0` → 22-bit
43/// ISCR long; `1` → BUFS/TTO signalling. Returns `None` if bit 7 is `0` (that is
44/// a short-form field — use [`decode_issy_short`]).
45#[must_use]
46pub fn decode_issy_long(bytes: [u8; 3]) -> Option<Issy> {
47 if bytes[0] & 0x80 == 0 {
48 return None;
49 }
50 let payload = ((bytes[0] as u32 & 0x3F) << 16) | (bytes[1] as u32) << 8 | bytes[2] as u32;
51 if bytes[0] & 0x40 == 0 {
52 Some(Issy::IscrLong(payload)) // '10' prefix
53 } else {
54 Some(Issy::Signalling(payload)) // '11' prefix (BUFS / TTO)
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn iscr_short_decodes_15_bits() {
64 // bit7=0 → ISCR short. 0x7ABC → iscr = 0x7ABC & 0x7FFF.
65 assert_eq!(decode_issy_short([0x7A, 0xBC]), Some(Issy::IscrShort(0x7ABC)));
66 assert_eq!(decode_issy_short([0x00, 0x01]), Some(Issy::IscrShort(1)));
67 }
68
69 #[test]
70 fn short_rejects_long_prefix() {
71 // bit7=1 is a long-form field, not short.
72 assert_eq!(decode_issy_short([0x80, 0x00]), None);
73 }
74
75 #[test]
76 fn iscr_long_decodes_22_bits() {
77 // '10' prefix: byte0 = 0b10_xxxxxx. 0x80|0x3F = 0xBF top.
78 assert_eq!(
79 decode_issy_long([0xBF, 0xFF, 0xFF]),
80 Some(Issy::IscrLong(0x3FFFFF))
81 );
82 assert_eq!(decode_issy_long([0x80, 0x12, 0x34]), Some(Issy::IscrLong(0x1234)));
83 }
84
85 #[test]
86 fn signalling_decodes_with_11_prefix() {
87 // '11' prefix: byte0 bit7=1, bit6=1.
88 assert_eq!(
89 decode_issy_long([0xC0, 0x12, 0x34]),
90 Some(Issy::Signalling(0x1234))
91 );
92 }
93
94 #[test]
95 fn long_rejects_short_prefix() {
96 assert_eq!(decode_issy_long([0x00, 0x00, 0x00]), None);
97 }
98}