Skip to main content

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}