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
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
85/// Decoded BUFS/TTO signalling — EN 302 755 Annex C, Table C.1.
86///
87/// The `11` prefix in the first ISSY byte selects one of two alternatives:
88/// BUFS (buffer status) or TTO (time-to-output), indicated by bits `[5:4]`.
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90#[cfg_attr(feature = "serde", derive(serde::Serialize))]
91#[non_exhaustive]
92pub enum SignallingKind {
93    /// BUFS — maximum size of the requested receiver buffer.
94    ///
95    /// Fields: `(bufs, units)` where `bufs` is the 10-bit buffer status
96    /// and `units` is the 2-bit unit selector.
97    Bufs {
98        /// 10-bit buffer status value.
99        bufs: u16,
100        /// 2-bit unit selector (Table C.1).
101        units: BufsUnit,
102    },
103    /// TTO — time-to-output (mantissa + exponent form).
104    ///
105    /// The output time is `TTO = (tto_m + tto_l / 256) * 2^tto_e`
106    /// where `tto_l` is zero when ISCRshort is in use.
107    Tto {
108        /// 5-bit exponent `TTO_E`.
109        tto_e: u8,
110        /// 7-bit mantissa `TTO_M`.
111        tto_m: u8,
112        /// 8-bit low-fraction `TTO_L` (zero when ISCRshort is in use).
113        tto_l: u8,
114    },
115    /// Reserved signalling type (bits `[5:4]` = `0b10` or `0b11`).
116    ///
117    /// Holds the low 20 bits of the signalling payload; the 2-bit kind
118    /// selector is not retained. This is a decode-only view — the wire bytes
119    /// live in `Bbheader::issy_in_header` and are serialized verbatim, so the
120    /// dropped selector does not affect round-trip fidelity.
121    Reserved(u32),
122}
123
124/// Decoded ISSY value (EN 302 755 §5.1.7, Annex C).
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize))]
127#[non_exhaustive]
128pub enum Issy {
129    /// ISCR short form — 15-bit Input Stream Clock Reference (2-byte ISSY).
130    IscrShort(u16),
131    /// ISCR long form — 22-bit Input Stream Clock Reference (3-byte ISSY).
132    IscrLong(u32),
133    /// Long-form BUFS/TTO signalling (3-byte ISSY, `11` prefix).
134    ///
135    /// The 22-bit payload is decoded into [`SignallingKind`]; see
136    /// Annex C for the sub-coding.
137    Signalling(SignallingKind),
138}
139
140/// Decode a 2-byte (short) ISSY field.
141///
142/// Returns `Ok(Issy::IscrShort)` when the short-form bit (`[7]` of byte 0) is
143/// `0`; `Err` otherwise (a `1` prefix means a long-form field, which is 3 bytes
144/// and must be decoded with [`decode_issy_long`]).
145pub fn decode_issy_short(bytes: [u8; 2]) -> crate::Result<Issy> {
146    if bytes[0] & ISSY_LONG_FORM_BIT != 0 {
147        return Err(Error::InvalidIssyForm {
148            reason: "bit [7] is 1 (long form); use decode_issy_long for 3-byte ISSY",
149        });
150    }
151    let iscr = ((bytes[0] as u16 & ISCR_SHORT_PAYLOAD_MASK as u16) << 8) | bytes[1] as u16;
152    Ok(Issy::IscrShort(iscr))
153}
154
155/// Decode a 3-byte (long) ISSY field.
156///
157/// Byte 0 bit `[7]` must be `1` (long form). Byte 0 bit `[6]` then selects: `0` → 22-bit
158/// ISCR long; `1` → BUFS/TTO signalling. Returns `Err` if bit `[7]` is `0` (that is
159/// a short-form field — use [`decode_issy_short`]).
160pub fn decode_issy_long(bytes: [u8; 3]) -> crate::Result<Issy> {
161    if bytes[0] & ISSY_LONG_FORM_BIT == 0 {
162        return Err(Error::InvalidIssyForm {
163            reason: "bit [7] is 0 (short form); use decode_issy_short for 2-byte ISSY",
164        });
165    }
166    let payload = ((bytes[0] as u32 & ISCR_LONG_PAYLOAD_MASK as u32) << 16)
167        | (bytes[1] as u32) << 8
168        | bytes[2] as u32;
169    if bytes[0] & ISSY_SIGNALLING_BIT == 0 {
170        Ok(Issy::IscrLong(payload))
171    } else {
172        Ok(Issy::Signalling(decode_signalling(payload)))
173    }
174}
175
176/// Decode the 22-bit `11`-prefix payload per Annex C Table C.1.
177///
178/// Bits `[21:20]` select the signalling type:
179/// - `0b00` → BUFS: bits `[19:18]` = unit, bits `[17:8]` = 10-bit BUFS, `[7:0]` reserved
180/// - `0b01` → TTO: bits `[19:16]` = 4 MSBs of TTO_E, byte 1 bit `[7]` = LSB of TTO_E,
181///   byte 1 bits `[6:0]` = TTO_M, byte 2 = TTO_L (or reserved for ISCRshort)
182/// - `0b10`, `0b11` → reserved
183fn decode_signalling(payload: u32) -> SignallingKind {
184    let kind = (payload >> SIGNALLING_KIND_SHIFT) & SIGNALLING_KIND_MASK;
185    match kind {
186        0 => {
187            let units = BufsUnit::from_u8(((payload >> BUFS_UNIT_SHIFT) & BUFS_UNIT_MASK) as u8);
188            let bufs = ((payload >> BUFS_VALUE_SHIFT) & BUFS_VALUE_MASK) as u16;
189            SignallingKind::Bufs { bufs, units }
190        }
191        1 => {
192            let tto_e = (((payload >> TTO_E_MSB_SHIFT) & TTO_E_MSB_MASK) << 1
193                | ((payload >> TTO_E_LSB_SHIFT) & TTO_E_LSB_MASK)) as u8;
194            let tto_m = ((payload >> TTO_M_SHIFT) & TTO_M_MASK) as u8;
195            let tto_l = (payload & 0xFF) as u8;
196            SignallingKind::Tto {
197                tto_e,
198                tto_m,
199                tto_l,
200            }
201        }
202        _ => {
203            let remainder = payload & RESERVED_PAYLOAD_MASK;
204            SignallingKind::Reserved(remainder)
205        }
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212
213    #[test]
214    fn iscr_short_decodes_15_bits() {
215        assert_eq!(decode_issy_short([0x7A, 0xBC]), Ok(Issy::IscrShort(0x7ABC)));
216        assert_eq!(decode_issy_short([0x00, 0x01]), Ok(Issy::IscrShort(1)));
217    }
218
219    #[test]
220    fn short_rejects_long_prefix() {
221        assert!(decode_issy_short([0x80, 0x00]).is_err());
222    }
223
224    #[test]
225    fn iscr_long_decodes_22_bits() {
226        assert_eq!(
227            decode_issy_long([0xBF, 0xFF, 0xFF]),
228            Ok(Issy::IscrLong(0x3FFFFF))
229        );
230        assert_eq!(
231            decode_issy_long([0x80, 0x12, 0x34]),
232            Ok(Issy::IscrLong(0x1234))
233        );
234    }
235
236    #[test]
237    fn signalling_decodes_with_11_prefix() {
238        assert_eq!(
239            decode_issy_long([0xC0, 0x12, 0x34]),
240            Ok(Issy::Signalling(decode_signalling(0x1234)))
241        );
242    }
243
244    #[test]
245    fn long_rejects_short_prefix() {
246        assert!(decode_issy_long([0x00, 0x00, 0x00]).is_err());
247    }
248
249    #[test]
250    fn signalling_bufs_decode() {
251        // bytes [0xCB, 0xFF, 0x00]: byte0 has the '11' ISSY prefix in bits[7:6];
252        // the 22-bit payload = ((0xCB & 0x3F) << 16) | (0xFF << 8) | 0x00 = 0x0B_FF_00
253        // = 0000_1011_1111_1111_0000_0000, so:
254        //   bits[21:20] = 00 => BUFS form
255        //   bits[19:18] = 10 => unit (Mbit)
256        //   bits[17:8]  = 11_1111_1111 = 0x3FF => BUFS = 1023
257        //   bits[7:0]   = reserved
258        let result = decode_issy_long([0xCB, 0xFF, 0x00]).unwrap();
259        match result {
260            Issy::Signalling(SignallingKind::Bufs { bufs, units }) => {
261                assert_eq!(bufs, 0x3FF);
262                assert_eq!(units, BufsUnit::Mbits);
263            }
264            other => panic!("expected BUFS, got {other:?}"),
265        }
266    }
267
268    #[test]
269    fn signalling_tto_decode() {
270        // '11' prefix, bits[21:20]=0b01 (TTO)
271        // bits[19:16]=0b0101 (4 MSBs of TTO_E = 5)
272        // byte1 bit7 = LSB of TTO_E (1 => TTO_E = 0b1011 = 11)
273        // byte1 bits[6:0] = TTO_M = 0x7F = 127
274        // byte2 = TTO_L = 0x80
275        // byte0: 0b11_01_0101 = 0xD5
276        // byte1: 0b1_1111111 = 0xFF
277        // byte2: 0x80
278        // payload = ((0xD5 & 0x3F) << 16) | (0xFF << 8) | 0x80
279        // = (0x15 << 16) | 0xFF00 | 0x80
280        // = 0x15_FF_80
281        // bits[21:20] = 01 => TTO
282        // bits[19:16] = 0101 => TTO_E MSBs = 5
283        // bit 15 = 1 => TTO_E LSB = 1 => TTO_E = 0b1011 = 11
284        // bits[14:8] = 1111111 => TTO_M = 127
285        // bits[7:0] = 10000000 => TTO_L = 128
286        let result = decode_issy_long([0xD5, 0xFF, 0x80]).unwrap();
287        match result {
288            Issy::Signalling(SignallingKind::Tto {
289                tto_e,
290                tto_m,
291                tto_l,
292            }) => {
293                assert_eq!(tto_e, 11);
294                assert_eq!(tto_m, 127);
295                assert_eq!(tto_l, 128);
296            }
297            other => panic!("expected TTO, got {other:?}"),
298        }
299    }
300
301    #[test]
302    fn signalling_reserved_decode() {
303        // '11' prefix, bits[21:20]=0b10 (reserved)
304        // byte0: 0b11_10_XXXX = 0b11_10_0000 = 0xE0
305        // payload = ((0xE0 & 0x3F) << 16) | 0x0000 = 0x200000
306        // kind = (0x200000 >> 20) & 0x03 = 2 => reserved
307        let result = decode_issy_long([0xE0, 0x00, 0x00]).unwrap();
308        match result {
309            Issy::Signalling(SignallingKind::Reserved(remainder)) => {
310                assert_eq!(remainder, 0x00000);
311            }
312            other => panic!("expected Reserved, got {other:?}"),
313        }
314    }
315
316    #[test]
317    fn bufs_unit_round_trip() {
318        for b in 0..=3u8 {
319            assert_eq!(BufsUnit::from_u8(b).to_u8(), b);
320        }
321    }
322
323    #[test]
324    fn bufs_unit_name() {
325        assert_eq!(BufsUnit::Bits.name(), "bits");
326        assert_eq!(BufsUnit::Kbits.name(), "Kbits");
327        assert_eq!(BufsUnit::Mbits.name(), "Mbits");
328        assert_eq!(BufsUnit::Kbits8.name(), "8 Kbits");
329    }
330}