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, 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}