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