dvb_bbframe/issy.rs
1//! ISSY (Input Stream SYnchronizer) field decoding per EN 302 755 §5.1.7 /
2//! Annex C Table C.1 (DVB-T2) and EN 302 307-1 Annex D Table D.1 (DVB-S2 BUFSTAT).
3//!
4//! ISSY carries the Input Stream Clock Reference (ISCR) and, in its long form,
5//! buffer-status / time-to-output signalling, used for jitter-free transport
6//! reconstruction at the receiver. The first bit selects the form:
7//!
8//! ```text
9//! bit7 = 0 -> ISCR short: 15-bit ISCR (2-byte ISSY)
10//! bit7 = 1, bit6 = 0 -> ISCR long: 22-bit ISCR (3-byte ISSY)
11//! bit7 = 1, bit6 = 1 -> BUFS / TTO signalling (3-byte ISSY)
12//! ```
13
14use crate::Error;
15
16const ISSY_LONG_FORM_BIT: u8 = 0x80;
17const ISCR_SHORT_PAYLOAD_MASK: u8 = 0x7F;
18const ISCR_LONG_PAYLOAD_MASK: u8 = 0x3F;
19const ISSY_SIGNALLING_BIT: u8 = 0x40;
20
21const SIGNALLING_KIND_SHIFT: u32 = 20;
22const SIGNALLING_KIND_MASK: u32 = 0x03;
23const BUFS_UNIT_SHIFT: u32 = 18;
24const BUFS_UNIT_MASK: u32 = 0x03;
25const BUFS_VALUE_SHIFT: u32 = 8;
26const BUFS_VALUE_MASK: u32 = 0x03FF;
27const TTO_E_MSB_SHIFT: u32 = 16;
28const TTO_E_MSB_MASK: u32 = 0x0F;
29const TTO_E_LSB_SHIFT: u32 = 15;
30const TTO_E_LSB_MASK: u32 = 0x01;
31const TTO_M_SHIFT: u32 = 8;
32const TTO_M_MASK: u32 = 0x7F;
33const RESERVED_PAYLOAD_MASK: u32 = 0x0F_FFFF;
34
35/// BUFS unit selector — EN 302 755 Annex C, Table C.1 (2-bit field).
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize))]
38#[non_exhaustive]
39pub enum BufsUnit {
40 /// 0b00 — bits.
41 Bits,
42 /// 0b01 — Kbits.
43 Kbits,
44 /// 0b10 — Mbits.
45 Mbits,
46 /// 0b11 — 8 Kbits.
47 Kbits8,
48}
49
50impl BufsUnit {
51 #[must_use]
52 /// Construct from a raw `u8` (only the low 2 bits are used).
53 pub fn from_u8(v: u8) -> Self {
54 match v & BUFS_UNIT_MASK as u8 {
55 0 => Self::Bits,
56 1 => Self::Kbits,
57 2 => Self::Mbits,
58 3 => Self::Kbits8,
59 _ => unreachable!(),
60 }
61 }
62
63 #[must_use]
64 /// Return the wire byte for this unit.
65 pub fn to_u8(self) -> u8 {
66 match self {
67 Self::Bits => 0,
68 Self::Kbits => 1,
69 Self::Mbits => 2,
70 Self::Kbits8 => 3,
71 }
72 }
73
74 #[must_use]
75 /// Human-readable unit name.
76 pub fn name(self) -> &'static str {
77 match self {
78 Self::Bits => "bits",
79 Self::Kbits => "Kbits",
80 Self::Mbits => "Mbits",
81 Self::Kbits8 => "8 Kbits",
82 }
83 }
84
85 #[must_use]
86 /// Number of bits per BUFS unit.
87 ///
88 /// Per EN 302 755 Annex C Table C.1, the 2-bit unit selector names the
89 /// scale (bits / Kbits / Mbits / 8Kbits). The standard does not numerically
90 /// define K/M; the decimal convention (K = 1 000, M = 1 000 000, 8K = 8 000)
91 /// is used, consistent with the standard's use of decimal `Mbit/s`
92 /// elsewhere (see `docs/en_302_755_t2.md` §BUFS/TTO semantics).
93 pub fn multiplier_bits(self) -> u64 {
94 match self {
95 Self::Bits => 1,
96 Self::Kbits => 1_000,
97 Self::Mbits => 1_000_000,
98 Self::Kbits8 => 8_000,
99 }
100 }
101}
102
103/// Decoded BUFS/TTO signalling — EN 302 755 Annex C, Table C.1.
104///
105/// The `11` prefix in the first ISSY byte selects one of two alternatives:
106/// BUFS (buffer status) or TTO (time-to-output), indicated by bits `[5:4]`.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
109#[non_exhaustive]
110pub enum SignallingKind {
111 /// BUFS — maximum size of the requested receiver buffer.
112 ///
113 /// Fields: `(bufs, units)` where `bufs` is the 10-bit buffer status
114 /// and `units` is the 2-bit unit selector.
115 Bufs {
116 /// 10-bit buffer status value.
117 bufs: u16,
118 /// 2-bit unit selector (Table C.1).
119 units: BufsUnit,
120 },
121 /// TTO — time-to-output (mantissa + exponent form).
122 ///
123 /// The output time is `TTO = (tto_m + tto_l / 256) * 2^tto_e`
124 /// where `tto_l` is zero when ISCRshort is in use.
125 Tto {
126 /// 5-bit exponent `TTO_E`.
127 tto_e: u8,
128 /// 7-bit mantissa `TTO_M`.
129 tto_m: u8,
130 /// 8-bit low-fraction `TTO_L` (zero when ISCRshort is in use).
131 tto_l: u8,
132 },
133 /// BUFSTAT — actual receiver-buffer fill status (DVB-S2 only).
134 ///
135 /// EN 302 307-1 Annex D Table D.1: selected by bits `[5:4] = 0b10` (the
136 /// ISSY code range `0xE_XXXX`). Same field layout as [`Self::Bufs`] — a
137 /// 2-bit `units` selector and a 10-bit value (the number of filled bits,
138 /// scaled by `units`). **Not used in DVB-T2**, where EN 302 755 Annex C
139 /// reserves this code range ("shall not be transmitted in DVB-T2") and
140 /// replaces it with [`Self::Tto`]; decoding it as BUFSTAT here is correct
141 /// for DVB-S2 streams and harmless for T2 (the range is not transmitted).
142 BufStat {
143 /// 10-bit buffer-status value (filled bits, scaled by `units`).
144 bufstat: u16,
145 /// 2-bit unit selector (EN 302 307-1 Annex D Table D.1; note S2 marks
146 /// `0b11` reserved whereas T2's [`BufsUnit::Kbits8`] uses it).
147 units: BufsUnit,
148 },
149 /// Reserved signalling type (bits `[5:4]` = `0b11`).
150 ///
151 /// Holds the low 20 bits of the signalling payload; the 2-bit kind
152 /// selector is not retained. This is a decode-only view — the wire bytes
153 /// live in `Bbheader::issy_in_header` and are serialized verbatim, so the
154 /// dropped selector does not affect round-trip fidelity.
155 Reserved(u32),
156}
157
158impl SignallingKind {
159 #[must_use]
160 /// Decoded BUFS buffer size in bits, or `None` if this is not a BUFS variant.
161 ///
162 /// `bufs_bits = bufs × units.multiplier_bits()`
163 ///
164 /// See EN 302 755 Annex C Table C.1 + §BUFS/TTO semantics
165 /// (`docs/en_302_755_t2.md`).
166 ///
167 /// # Note on encoders
168 ///
169 /// Only decode accessors are provided. No `set_*` or `from_*` encoders are
170 /// added because the physical-value → mantissa/exponent TTO encoding is
171 /// lossy and the wire round-trip is already guaranteed by the existing
172 /// raw-field serialization in `Bbheader`. Use the raw-field constructors
173 /// (`SignallingKind::Bufs { … }` / `SignallingKind::Tto { … }`) for
174 /// encoding.
175 pub fn bufs_bits(&self) -> Option<u64> {
176 match self {
177 Self::Bufs { bufs, units } => Some(*bufs as u64 * units.multiplier_bits()),
178 _ => None,
179 }
180 }
181
182 #[must_use]
183 /// Decoded BUFS buffer size in bytes (integer floor), or `None`.
184 ///
185 /// `bufs_bytes = bufs_bits() / 8`. Integer division is used: BUFS is a
186 /// maximum-size bound per the standard, so a floor is appropriate.
187 ///
188 /// See EN 302 755 Annex C Table C.1 + §BUFS/TTO semantics
189 /// (`docs/en_302_755_t2.md`).
190 pub fn bufs_bytes(&self) -> Option<u64> {
191 self.bufs_bits().map(|b| b / 8)
192 }
193
194 #[must_use]
195 /// Decoded BUFSTAT fill status in bits, or `None` if this is not a BUFSTAT
196 /// variant (DVB-S2 only — EN 302 307-1 Annex D Table D.1).
197 ///
198 /// `bufstat_bits = bufstat × units.multiplier_bits()`
199 pub fn bufstat_bits(&self) -> Option<u64> {
200 match self {
201 Self::BufStat { bufstat, units } => Some(*bufstat as u64 * units.multiplier_bits()),
202 _ => None,
203 }
204 }
205
206 #[must_use]
207 /// Decoded BUFSTAT fill status in bytes (integer floor), or `None`.
208 pub fn bufstat_bytes(&self) -> Option<u64> {
209 self.bufstat_bits().map(|b| b / 8)
210 }
211
212 #[must_use]
213 /// Decoded time-to-output in units of T/256, or `None` if this is not a
214 /// TTO variant.
215 ///
216 /// `tto_t_over_256 = ((TTO_M × 256) + TTO_L) × 2^TTO_E`
217 ///
218 /// This is `TTO × 256` in units of the elementary period **T** (see EN 302
219 /// 755 §9.5 / Table 65). The `TTO_L / 256` fractional term is preserved
220 /// exactly without floating point; consumers divide by 256.0 to obtain
221 /// `TTO` in units of T.
222 ///
223 /// Per EN 302 755 Annex C Table C.1 + §8.3.3
224 /// (`docs/en_302_755_t2.md`).
225 pub fn tto_t_over_256(&self) -> Option<u64> {
226 match self {
227 Self::Tto {
228 tto_e,
229 tto_m,
230 tto_l,
231 } => Some((u64::from(*tto_m) * 256 + u64::from(*tto_l)) << tto_e),
232 _ => None,
233 }
234 }
235}
236
237/// Decoded ISSY value (EN 302 755 §5.1.7, Annex C).
238#[derive(Debug, Clone, Copy, PartialEq, Eq)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize))]
240#[non_exhaustive]
241pub enum Issy {
242 /// ISCR short form — 15-bit Input Stream Clock Reference (2-byte ISSY).
243 IscrShort(u16),
244 /// ISCR long form — 22-bit Input Stream Clock Reference (3-byte ISSY).
245 IscrLong(u32),
246 /// Long-form BUFS/TTO signalling (3-byte ISSY, `11` prefix).
247 ///
248 /// The 22-bit payload is decoded into [`SignallingKind`]; see
249 /// Annex C for the sub-coding.
250 Signalling(SignallingKind),
251}
252
253/// Decode a 2-byte (short) ISSY field.
254///
255/// Returns `Ok(Issy::IscrShort)` when the short-form bit (`[7]` of byte 0) is
256/// `0`; `Err` otherwise (a `1` prefix means a long-form field, which is 3 bytes
257/// and must be decoded with [`decode_issy_long`]).
258pub fn decode_issy_short(bytes: [u8; 2]) -> crate::Result<Issy> {
259 if bytes[0] & ISSY_LONG_FORM_BIT != 0 {
260 return Err(Error::InvalidIssyForm {
261 reason: "bit [7] is 1 (long form); use decode_issy_long for 3-byte ISSY",
262 });
263 }
264 let iscr = ((bytes[0] as u16 & ISCR_SHORT_PAYLOAD_MASK as u16) << 8) | bytes[1] as u16;
265 Ok(Issy::IscrShort(iscr))
266}
267
268/// Decode a 3-byte (long) ISSY field.
269///
270/// Byte 0 bit `[7]` must be `1` (long form). Byte 0 bit `[6]` then selects: `0` → 22-bit
271/// ISCR long; `1` → BUFS/TTO signalling. Returns `Err` if bit `[7]` is `0` (that is
272/// a short-form field — use [`decode_issy_short`]).
273pub fn decode_issy_long(bytes: [u8; 3]) -> crate::Result<Issy> {
274 if bytes[0] & ISSY_LONG_FORM_BIT == 0 {
275 return Err(Error::InvalidIssyForm {
276 reason: "bit [7] is 0 (short form); use decode_issy_short for 2-byte ISSY",
277 });
278 }
279 let payload = ((bytes[0] as u32 & ISCR_LONG_PAYLOAD_MASK as u32) << 16)
280 | (bytes[1] as u32) << 8
281 | bytes[2] as u32;
282 if bytes[0] & ISSY_SIGNALLING_BIT == 0 {
283 Ok(Issy::IscrLong(payload))
284 } else {
285 Ok(Issy::Signalling(decode_signalling(payload)))
286 }
287}
288
289/// Decode the 22-bit `11`-prefix payload per EN 302 755 Annex C Table C.1
290/// (DVB-T2) and EN 302 307-1 Annex D Table D.1 (DVB-S2).
291///
292/// Bits `[21:20]` select the signalling type:
293/// - `0b00` → BUFS: bits `[19:18]` = unit, bits `[17:8]` = 10-bit BUFS, `[7:0]` reserved
294/// - `0b01` → TTO (DVB-T2): bits `[19:16]` = 4 MSBs of TTO_E, byte 1 bit `[7]` = LSB
295/// of TTO_E, byte 1 bits `[6:0]` = TTO_M, byte 2 = TTO_L (or reserved for ISCRshort)
296/// - `0b10` → BUFSTAT (DVB-S2): bits `[19:18]` = unit, bits `[17:8]` = 10-bit BUFSTAT
297/// (reserved / not transmitted in DVB-T2 — replaced by TTO)
298/// - `0b11` → reserved
299fn decode_signalling(payload: u32) -> SignallingKind {
300 let kind = (payload >> SIGNALLING_KIND_SHIFT) & SIGNALLING_KIND_MASK;
301 match kind {
302 0 => {
303 let units = BufsUnit::from_u8(((payload >> BUFS_UNIT_SHIFT) & BUFS_UNIT_MASK) as u8);
304 let bufs = ((payload >> BUFS_VALUE_SHIFT) & BUFS_VALUE_MASK) as u16;
305 SignallingKind::Bufs { bufs, units }
306 }
307 1 => {
308 let tto_e = (((payload >> TTO_E_MSB_SHIFT) & TTO_E_MSB_MASK) << 1
309 | ((payload >> TTO_E_LSB_SHIFT) & TTO_E_LSB_MASK)) as u8;
310 let tto_m = ((payload >> TTO_M_SHIFT) & TTO_M_MASK) as u8;
311 let tto_l = (payload & 0xFF) as u8;
312 SignallingKind::Tto {
313 tto_e,
314 tto_m,
315 tto_l,
316 }
317 }
318 2 => {
319 // BUFSTAT (DVB-S2, EN 302 307-1 Annex D Table D.1) — same layout as BUFS.
320 let units = BufsUnit::from_u8(((payload >> BUFS_UNIT_SHIFT) & BUFS_UNIT_MASK) as u8);
321 let bufstat = ((payload >> BUFS_VALUE_SHIFT) & BUFS_VALUE_MASK) as u16;
322 SignallingKind::BufStat { bufstat, units }
323 }
324 _ => {
325 let remainder = payload & RESERVED_PAYLOAD_MASK;
326 SignallingKind::Reserved(remainder)
327 }
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn iscr_short_decodes_15_bits() {
337 assert_eq!(decode_issy_short([0x7A, 0xBC]), Ok(Issy::IscrShort(0x7ABC)));
338 assert_eq!(decode_issy_short([0x00, 0x01]), Ok(Issy::IscrShort(1)));
339 }
340
341 #[test]
342 fn short_rejects_long_prefix() {
343 assert!(decode_issy_short([0x80, 0x00]).is_err());
344 }
345
346 #[test]
347 fn iscr_long_decodes_22_bits() {
348 assert_eq!(
349 decode_issy_long([0xBF, 0xFF, 0xFF]),
350 Ok(Issy::IscrLong(0x3FFFFF))
351 );
352 assert_eq!(
353 decode_issy_long([0x80, 0x12, 0x34]),
354 Ok(Issy::IscrLong(0x1234))
355 );
356 }
357
358 #[test]
359 fn signalling_decodes_with_11_prefix() {
360 assert_eq!(
361 decode_issy_long([0xC0, 0x12, 0x34]),
362 Ok(Issy::Signalling(decode_signalling(0x1234)))
363 );
364 }
365
366 #[test]
367 fn long_rejects_short_prefix() {
368 assert!(decode_issy_long([0x00, 0x00, 0x00]).is_err());
369 }
370
371 #[test]
372 fn signalling_bufs_decode() {
373 // bytes [0xCB, 0xFF, 0x00]: byte0 has the '11' ISSY prefix in bits[7:6];
374 // the 22-bit payload = ((0xCB & 0x3F) << 16) | (0xFF << 8) | 0x00 = 0x0B_FF_00
375 // = 0000_1011_1111_1111_0000_0000, so:
376 // bits[21:20] = 00 => BUFS form
377 // bits[19:18] = 10 => unit (Mbit)
378 // bits[17:8] = 11_1111_1111 = 0x3FF => BUFS = 1023
379 // bits[7:0] = reserved
380 let result = decode_issy_long([0xCB, 0xFF, 0x00]).unwrap();
381 match result {
382 Issy::Signalling(SignallingKind::Bufs { bufs, units }) => {
383 assert_eq!(bufs, 0x3FF);
384 assert_eq!(units, BufsUnit::Mbits);
385 }
386 other => panic!("expected BUFS, got {other:?}"),
387 }
388 }
389
390 #[test]
391 fn signalling_tto_decode() {
392 // '11' prefix, bits[21:20]=0b01 (TTO)
393 // bits[19:16]=0b0101 (4 MSBs of TTO_E = 5)
394 // byte1 bit7 = LSB of TTO_E (1 => TTO_E = 0b1011 = 11)
395 // byte1 bits[6:0] = TTO_M = 0x7F = 127
396 // byte2 = TTO_L = 0x80
397 // byte0: 0b11_01_0101 = 0xD5
398 // byte1: 0b1_1111111 = 0xFF
399 // byte2: 0x80
400 // payload = ((0xD5 & 0x3F) << 16) | (0xFF << 8) | 0x80
401 // = (0x15 << 16) | 0xFF00 | 0x80
402 // = 0x15_FF_80
403 // bits[21:20] = 01 => TTO
404 // bits[19:16] = 0101 => TTO_E MSBs = 5
405 // bit 15 = 1 => TTO_E LSB = 1 => TTO_E = 0b1011 = 11
406 // bits[14:8] = 1111111 => TTO_M = 127
407 // bits[7:0] = 10000000 => TTO_L = 128
408 let result = decode_issy_long([0xD5, 0xFF, 0x80]).unwrap();
409 match result {
410 Issy::Signalling(SignallingKind::Tto {
411 tto_e,
412 tto_m,
413 tto_l,
414 }) => {
415 assert_eq!(tto_e, 11);
416 assert_eq!(tto_m, 127);
417 assert_eq!(tto_l, 128);
418 }
419 other => panic!("expected TTO, got {other:?}"),
420 }
421 }
422
423 #[test]
424 fn signalling_bufstat_decode() {
425 // BUFSTAT (DVB-S2, EN 302 307-1 Annex D Table D.1): '11' prefix,
426 // bits[21:20]=0b10 (BUFSTAT), bits[19:18]=0b10 (Mbits unit),
427 // bits[17:8]=11_1111_1111=0x3FF (BUFSTAT=1023).
428 // byte0: 0b11_10_10_11 = 0xEB; payload = (0xEB & 0x3F)<<16 | 0xFF<<8 = 0x2BFF00.
429 let result = decode_issy_long([0xEB, 0xFF, 0x00]).unwrap();
430 match result {
431 Issy::Signalling(SignallingKind::BufStat { bufstat, units }) => {
432 assert_eq!(bufstat, 0x3FF);
433 assert_eq!(units, BufsUnit::Mbits);
434 }
435 other => panic!("expected BufStat, got {other:?}"),
436 }
437 // BUFSTAT accessors
438 let bs = SignallingKind::BufStat {
439 bufstat: 2,
440 units: BufsUnit::Mbits,
441 };
442 assert_eq!(bs.bufstat_bits(), Some(2_000_000));
443 assert_eq!(bs.bufstat_bytes(), Some(250_000));
444 // non-BUFSTAT variants return None
445 assert_eq!(SignallingKind::Reserved(0).bufstat_bits(), None);
446 }
447
448 #[test]
449 fn signalling_reserved_decode() {
450 // '11' prefix, bits[21:20]=0b11 (reserved — the only remaining reserved kind
451 // now that 0b10 is decoded as BUFSTAT).
452 // byte0: 0b11_11_0000 = 0xF0; payload = ((0xF0 & 0x3F) << 16) = 0x300000
453 // kind = (0x300000 >> 20) & 0x03 = 3 => reserved; remainder = 0x300000 & 0x0FFFFF = 0
454 let result = decode_issy_long([0xF0, 0x00, 0x00]).unwrap();
455 match result {
456 Issy::Signalling(SignallingKind::Reserved(remainder)) => {
457 assert_eq!(remainder, 0x00000);
458 }
459 other => panic!("expected Reserved, got {other:?}"),
460 }
461 }
462
463 #[test]
464 fn bufs_unit_round_trip() {
465 for b in 0..=3u8 {
466 assert_eq!(BufsUnit::from_u8(b).to_u8(), b);
467 }
468 }
469
470 #[test]
471 fn bufs_unit_name() {
472 assert_eq!(BufsUnit::Bits.name(), "bits");
473 assert_eq!(BufsUnit::Kbits.name(), "Kbits");
474 assert_eq!(BufsUnit::Mbits.name(), "Mbits");
475 assert_eq!(BufsUnit::Kbits8.name(), "8 Kbits");
476 }
477
478 #[test]
479 fn multiplier_bits() {
480 assert_eq!(BufsUnit::Bits.multiplier_bits(), 1);
481 assert_eq!(BufsUnit::Kbits.multiplier_bits(), 1_000);
482 assert_eq!(BufsUnit::Mbits.multiplier_bits(), 1_000_000);
483 assert_eq!(BufsUnit::Kbits8.multiplier_bits(), 8_000);
484 }
485
486 #[test]
487 fn bufs_bits_and_bytes() {
488 // BUFS = 2 Mbits → 2 * 1_000_000 = 2_000_000 bits → 250_000 bytes
489 let b = SignallingKind::Bufs {
490 bufs: 2,
491 units: BufsUnit::Mbits,
492 };
493 assert_eq!(b.bufs_bits(), Some(2_000_000));
494 assert_eq!(b.bufs_bytes(), Some(250_000));
495
496 // BUFS = 1 bit
497 let b = SignallingKind::Bufs {
498 bufs: 1,
499 units: BufsUnit::Bits,
500 };
501 assert_eq!(b.bufs_bits(), Some(1));
502 assert_eq!(b.bufs_bytes(), Some(0)); // 1/8 = 0 (integer floor)
503
504 // BUFS = 3 × 8Kbits → 3 * 8000 = 24_000 bits → 3_000 bytes
505 let b = SignallingKind::Bufs {
506 bufs: 3,
507 units: BufsUnit::Kbits8,
508 };
509 assert_eq!(b.bufs_bits(), Some(24_000));
510 assert_eq!(b.bufs_bytes(), Some(3_000));
511
512 // TTO variant returns None
513 let t = SignallingKind::Tto {
514 tto_e: 0,
515 tto_m: 0,
516 tto_l: 0,
517 };
518 assert_eq!(t.bufs_bits(), None);
519 assert_eq!(t.bufs_bytes(), None);
520
521 // Reserved variant returns None
522 assert_eq!(SignallingKind::Reserved(0).bufs_bits(), None);
523 }
524
525 #[test]
526 fn tto_t_over_256() {
527 // TTO_E=0, TTO_M=1, TTO_L=0 → ((1*256 + 0) << 0) = 256 (= 1·T in T/256 units)
528 let t = SignallingKind::Tto {
529 tto_e: 0,
530 tto_m: 1,
531 tto_l: 0,
532 };
533 assert_eq!(t.tto_t_over_256(), Some(256));
534
535 // TTO_E=1, TTO_M=0, TTO_L=128 → ((0*256 + 128) << 1) = 256 (= (128/256)*T*2 = 1·T → 256 in T/256 units)
536 let t = SignallingKind::Tto {
537 tto_e: 1,
538 tto_m: 0,
539 tto_l: 128,
540 };
541 assert_eq!(t.tto_t_over_256(), Some(256));
542
543 // TTO_E=5, TTO_M=3, TTO_L=64 → ((3*256 + 64) << 5) = (768 + 64) * 32 = 832 * 32 = 26_624
544 let t = SignallingKind::Tto {
545 tto_e: 5,
546 tto_m: 3,
547 tto_l: 64,
548 };
549 assert_eq!(t.tto_t_over_256(), Some(26_624));
550
551 // Bufs variant returns None
552 let b = SignallingKind::Bufs {
553 bufs: 1,
554 units: BufsUnit::Bits,
555 };
556 assert_eq!(b.tto_t_over_256(), None);
557
558 // Reserved variant returns None
559 assert_eq!(SignallingKind::Reserved(0).tto_t_over_256(), None);
560 }
561}