Skip to main content

dvb_t2mi/payload/l1/
enums.rs

1//! Enumerations for EN 302 755 §7.2 L1-pre / L1-post signalling fields.
2//!
3//! Every enum has a `Reserved(u8)` catch-all so parsing never fails on
4//! reserved/future values. Use `from_u8` / `to_u8` for lossless round-trips.
5//! PAPR and PLP code-rate decoding are version/profile-dependent and are
6//! handled as methods on `L1Pre` and `PlpConfig` (see `pre.rs` / `post.rs`).
7
8/// Transmitter input stream types — EN 302 755 §7.2.2 Table 21 (8-bit field).
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[cfg_attr(feature = "serde", derive(serde::Serialize))]
11#[non_exhaustive]
12pub enum TxInputStreamType {
13    /// 0x00 — Transport Stream (TS) only.
14    TsOnly,
15    /// 0x01 — Generic Stream (GSE and/or GFPS and/or GCS) but not TS.
16    GenericStream,
17    /// 0x02 — Both TS and Generic Stream.
18    Both,
19    /// All other values — reserved.
20    Reserved(u8),
21}
22
23impl TxInputStreamType {
24    /// Decode from the 8-bit wire value.
25    #[must_use]
26    pub fn from_u8(v: u8) -> Self {
27        match v {
28            0x00 => Self::TsOnly,
29            0x01 => Self::GenericStream,
30            0x02 => Self::Both,
31            other => Self::Reserved(other),
32        }
33    }
34
35    /// Encode to the 8-bit wire value.
36    #[must_use]
37    pub const fn to_u8(self) -> u8 {
38        match self {
39            Self::TsOnly => 0x00,
40            Self::GenericStream => 0x01,
41            Self::Both => 0x02,
42            Self::Reserved(v) => v,
43        }
44    }
45}
46
47/// Guard interval — EN 302 755 §7.2.2 Table 22 (3-bit field).
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize))]
50#[non_exhaustive]
51pub enum GuardInterval {
52    /// 000 — 1/32.
53    G1_32,
54    /// 001 — 1/16.
55    G1_16,
56    /// 010 — 1/8.
57    G1_8,
58    /// 011 — 1/4.
59    G1_4,
60    /// 100 — 1/128.
61    G1_128,
62    /// 101 — 19/128.
63    G19_128,
64    /// 110 — 19/256.
65    G19_256,
66    /// 111 — Reserved.
67    Reserved(u8),
68}
69
70impl GuardInterval {
71    /// Decode from the 3-bit wire value.
72    #[must_use]
73    pub fn from_u8(v: u8) -> Self {
74        match v & 0x07 {
75            0 => Self::G1_32,
76            1 => Self::G1_16,
77            2 => Self::G1_8,
78            3 => Self::G1_4,
79            4 => Self::G1_128,
80            5 => Self::G19_128,
81            6 => Self::G19_256,
82            other => Self::Reserved(other),
83        }
84    }
85
86    /// Encode to the 3-bit wire value.
87    #[must_use]
88    pub const fn to_u8(self) -> u8 {
89        match self {
90            Self::G1_32 => 0,
91            Self::G1_16 => 1,
92            Self::G1_8 => 2,
93            Self::G1_4 => 3,
94            Self::G1_128 => 4,
95            Self::G19_128 => 5,
96            Self::G19_256 => 6,
97            Self::Reserved(v) => v,
98        }
99    }
100}
101
102/// L1-post constellation — EN 302 755 §7.2.2 Table 24 (4-bit field).
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104#[cfg_attr(feature = "serde", derive(serde::Serialize))]
105#[non_exhaustive]
106pub enum L1Modulation {
107    /// 0000 — BPSK.
108    Bpsk,
109    /// 0001 — QPSK.
110    Qpsk,
111    /// 0010 — 16-QAM.
112    Qam16,
113    /// 0011 — 64-QAM.
114    Qam64,
115    /// All other values — reserved.
116    Reserved(u8),
117}
118
119impl L1Modulation {
120    /// Decode from the 4-bit wire value.
121    #[must_use]
122    pub fn from_u8(v: u8) -> Self {
123        match v & 0x0F {
124            0 => Self::Bpsk,
125            1 => Self::Qpsk,
126            2 => Self::Qam16,
127            3 => Self::Qam64,
128            other => Self::Reserved(other),
129        }
130    }
131
132    /// Encode to the 4-bit wire value.
133    #[must_use]
134    pub const fn to_u8(self) -> u8 {
135        match self {
136            Self::Bpsk => 0,
137            Self::Qpsk => 1,
138            Self::Qam16 => 2,
139            Self::Qam64 => 3,
140            Self::Reserved(v) => v,
141        }
142    }
143}
144
145/// L1-post code rate — EN 302 755 §7.2.2 Table 25 (2-bit field).
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147#[cfg_attr(feature = "serde", derive(serde::Serialize))]
148#[non_exhaustive]
149pub enum L1CodeRate {
150    /// 00 — 1/2.
151    R1_2,
152    /// All other values — reserved.
153    Reserved(u8),
154}
155
156impl L1CodeRate {
157    /// Decode from the 2-bit wire value.
158    #[must_use]
159    pub fn from_u8(v: u8) -> Self {
160        match v & 0x03 {
161            0 => Self::R1_2,
162            other => Self::Reserved(other),
163        }
164    }
165
166    /// Encode to the 2-bit wire value.
167    #[must_use]
168    pub const fn to_u8(self) -> u8 {
169        match self {
170            Self::R1_2 => 0,
171            Self::Reserved(v) => v,
172        }
173    }
174}
175
176/// L1-post FEC type — EN 302 755 §7.2.2 Table 26 (2-bit field).
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize))]
179#[non_exhaustive]
180pub enum L1FecType {
181    /// 00 — LDPC 16K.
182    Ldpc16K,
183    /// All other values — reserved.
184    Reserved(u8),
185}
186
187impl L1FecType {
188    /// Decode from the 2-bit wire value.
189    #[must_use]
190    pub fn from_u8(v: u8) -> Self {
191        match v & 0x03 {
192            0 => Self::Ldpc16K,
193            other => Self::Reserved(other),
194        }
195    }
196
197    /// Encode to the 2-bit wire value.
198    #[must_use]
199    pub const fn to_u8(self) -> u8 {
200        match self {
201            Self::Ldpc16K => 0,
202            Self::Reserved(v) => v,
203        }
204    }
205}
206
207/// Scattered pilot pattern — EN 302 755 §7.2.2 Table 27 (4-bit field).
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize))]
210#[non_exhaustive]
211pub enum PilotPattern {
212    /// 0000 — PP1.
213    Pp1,
214    /// 0001 — PP2.
215    Pp2,
216    /// 0010 — PP3.
217    Pp3,
218    /// 0011 — PP4.
219    Pp4,
220    /// 0100 — PP5.
221    Pp5,
222    /// 0101 — PP6.
223    Pp6,
224    /// 0110 — PP7.
225    Pp7,
226    /// 0111 — PP8.
227    Pp8,
228    /// All other values — reserved.
229    Reserved(u8),
230}
231
232impl PilotPattern {
233    /// Decode from the 4-bit wire value.
234    #[must_use]
235    pub fn from_u8(v: u8) -> Self {
236        match v & 0x0F {
237            0 => Self::Pp1,
238            1 => Self::Pp2,
239            2 => Self::Pp3,
240            3 => Self::Pp4,
241            4 => Self::Pp5,
242            5 => Self::Pp6,
243            6 => Self::Pp7,
244            7 => Self::Pp8,
245            other => Self::Reserved(other),
246        }
247    }
248
249    /// Encode to the 4-bit wire value.
250    #[must_use]
251    pub const fn to_u8(self) -> u8 {
252        match self {
253            Self::Pp1 => 0,
254            Self::Pp2 => 1,
255            Self::Pp3 => 2,
256            Self::Pp4 => 3,
257            Self::Pp5 => 4,
258            Self::Pp6 => 5,
259            Self::Pp7 => 6,
260            Self::Pp8 => 7,
261            Self::Reserved(v) => v,
262        }
263    }
264}
265
266/// T2 version field — EN 302 755 §7.2.2 Table 28 (4-bit field).
267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
268#[cfg_attr(feature = "serde", derive(serde::Serialize))]
269#[non_exhaustive]
270pub enum T2Version {
271    /// 0000 — Specification version 1.1.1.
272    V1_1_1,
273    /// 0001 — Specification version 1.2.1.
274    V1_2_1,
275    /// 0010 — Specification version 1.3.1.
276    V1_3_1,
277    /// All other values — reserved.
278    Reserved(u8),
279}
280
281impl T2Version {
282    /// Decode from the 4-bit wire value.
283    #[must_use]
284    pub fn from_u8(v: u8) -> Self {
285        match v & 0x0F {
286            0 => Self::V1_1_1,
287            1 => Self::V1_2_1,
288            2 => Self::V1_3_1,
289            other => Self::Reserved(other),
290        }
291    }
292
293    /// Encode to the 4-bit wire value.
294    #[must_use]
295    pub const fn to_u8(self) -> u8 {
296        match self {
297            Self::V1_1_1 => 0,
298            Self::V1_2_1 => 1,
299            Self::V1_3_1 => 2,
300            Self::Reserved(v) => v,
301        }
302    }
303}
304
305/// PLP type — EN 302 755 §7.2.3.1 Table 30 (3-bit field).
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307#[cfg_attr(feature = "serde", derive(serde::Serialize))]
308#[non_exhaustive]
309pub enum PlpType {
310    /// 000 — Common PLP.
311    Common,
312    /// 001 — Data PLP Type 1.
313    DataType1,
314    /// 010 — Data PLP Type 2.
315    DataType2,
316    /// All other values — reserved.
317    Reserved(u8),
318}
319
320impl PlpType {
321    /// Decode from the 3-bit wire value.
322    #[must_use]
323    pub fn from_u8(v: u8) -> Self {
324        match v & 0x07 {
325            0 => Self::Common,
326            1 => Self::DataType1,
327            2 => Self::DataType2,
328            other => Self::Reserved(other),
329        }
330    }
331
332    /// Encode to the 3-bit wire value.
333    #[must_use]
334    pub const fn to_u8(self) -> u8 {
335        match self {
336            Self::Common => 0,
337            Self::DataType1 => 1,
338            Self::DataType2 => 2,
339            Self::Reserved(v) => v,
340        }
341    }
342}
343
344/// PLP payload type — EN 302 755 §7.2.3.1 Table 31 (5-bit field).
345#[derive(Debug, Clone, Copy, PartialEq, Eq)]
346#[cfg_attr(feature = "serde", derive(serde::Serialize))]
347#[non_exhaustive]
348pub enum PlpPayloadType {
349    /// 00000 — GFPS.
350    Gfps,
351    /// 00001 — GCS.
352    Gcs,
353    /// 00010 — GSE.
354    Gse,
355    /// 00011 — TS.
356    Ts,
357    /// All other values — reserved.
358    Reserved(u8),
359}
360
361impl PlpPayloadType {
362    /// Decode from the 5-bit wire value.
363    #[must_use]
364    pub fn from_u8(v: u8) -> Self {
365        match v & 0x1F {
366            0 => Self::Gfps,
367            1 => Self::Gcs,
368            2 => Self::Gse,
369            3 => Self::Ts,
370            other => Self::Reserved(other),
371        }
372    }
373
374    /// Encode to the 5-bit wire value.
375    #[must_use]
376    pub const fn to_u8(self) -> u8 {
377        match self {
378            Self::Gfps => 0,
379            Self::Gcs => 1,
380            Self::Gse => 2,
381            Self::Ts => 3,
382            Self::Reserved(v) => v,
383        }
384    }
385}
386
387/// PLP modulation — EN 302 755 §7.2.3.1 Table 33 (3-bit field).
388#[derive(Debug, Clone, Copy, PartialEq, Eq)]
389#[cfg_attr(feature = "serde", derive(serde::Serialize))]
390#[non_exhaustive]
391pub enum PlpModulation {
392    /// 000 — QPSK.
393    Qpsk,
394    /// 001 — 16-QAM.
395    Qam16,
396    /// 010 — 64-QAM.
397    Qam64,
398    /// 011 — 256-QAM.
399    Qam256,
400    /// All other values — reserved.
401    Reserved(u8),
402}
403
404impl PlpModulation {
405    /// Decode from the 3-bit wire value.
406    #[must_use]
407    pub fn from_u8(v: u8) -> Self {
408        match v & 0x07 {
409            0 => Self::Qpsk,
410            1 => Self::Qam16,
411            2 => Self::Qam64,
412            3 => Self::Qam256,
413            other => Self::Reserved(other),
414        }
415    }
416
417    /// Encode to the 3-bit wire value.
418    #[must_use]
419    pub const fn to_u8(self) -> u8 {
420        match self {
421            Self::Qpsk => 0,
422            Self::Qam16 => 1,
423            Self::Qam64 => 2,
424            Self::Qam256 => 3,
425            Self::Reserved(v) => v,
426        }
427    }
428}
429
430/// PLP FEC type — EN 302 755 §7.2.3.1 Table 34 (2-bit field).
431#[derive(Debug, Clone, Copy, PartialEq, Eq)]
432#[cfg_attr(feature = "serde", derive(serde::Serialize))]
433#[non_exhaustive]
434pub enum PlpFecType {
435    /// 00 — 16K LDPC.
436    Ldpc16K,
437    /// 01 — 64K LDPC.
438    Ldpc64K,
439    /// All other values — reserved.
440    Reserved(u8),
441}
442
443impl PlpFecType {
444    /// Decode from the 2-bit wire value.
445    #[must_use]
446    pub fn from_u8(v: u8) -> Self {
447        match v & 0x03 {
448            0 => Self::Ldpc16K,
449            1 => Self::Ldpc64K,
450            other => Self::Reserved(other),
451        }
452    }
453
454    /// Encode to the 2-bit wire value.
455    #[must_use]
456    pub const fn to_u8(self) -> u8 {
457        match self {
458            Self::Ldpc16K => 0,
459            Self::Ldpc64K => 1,
460            Self::Reserved(v) => v,
461        }
462    }
463}
464
465/// PLP mode — EN 302 755 §7.2.3.1 Table 35 (2-bit field).
466#[derive(Debug, Clone, Copy, PartialEq, Eq)]
467#[cfg_attr(feature = "serde", derive(serde::Serialize))]
468#[non_exhaustive]
469pub enum PlpMode {
470    /// 00 — Not specified (valid only if T2_VERSION='0000').
471    NotSpecified,
472    /// 01 — Normal Mode.
473    Normal,
474    /// 10 — High Efficiency Mode.
475    HighEfficiency,
476    /// All other values — reserved.
477    Reserved(u8),
478}
479
480impl PlpMode {
481    /// Decode from the 2-bit wire value.
482    #[must_use]
483    pub fn from_u8(v: u8) -> Self {
484        match v & 0x03 {
485            0 => Self::NotSpecified,
486            1 => Self::Normal,
487            2 => Self::HighEfficiency,
488            other => Self::Reserved(other),
489        }
490    }
491
492    /// Encode to the 2-bit wire value.
493    #[must_use]
494    pub const fn to_u8(self) -> u8 {
495        match self {
496            Self::NotSpecified => 0,
497            Self::Normal => 1,
498            Self::HighEfficiency => 2,
499            Self::Reserved(v) => v,
500        }
501    }
502}
503
504/// Auxiliary stream type — EN 302 755 §7.2.3.1 Table 36 (4-bit field).
505#[derive(Debug, Clone, Copy, PartialEq, Eq)]
506#[cfg_attr(feature = "serde", derive(serde::Serialize))]
507#[non_exhaustive]
508pub enum AuxStreamType {
509    /// 0000 — TX-SIG (see ETSI TS 102 992).
510    TxSig,
511    /// All other values — reserved.
512    Reserved(u8),
513}
514
515impl AuxStreamType {
516    /// Decode from the 4-bit wire value.
517    #[must_use]
518    pub fn from_u8(v: u8) -> Self {
519        match v & 0x0F {
520            0 => Self::TxSig,
521            other => Self::Reserved(other),
522        }
523    }
524
525    /// Encode to the 4-bit wire value.
526    #[must_use]
527    pub const fn to_u8(self) -> u8 {
528        match self {
529            Self::TxSig => 0,
530            Self::Reserved(v) => v,
531        }
532    }
533}
534
535/// PAPR reduction for T2_VERSION = '0000' — EN 302 755 §7.2.2 Table 23a (4-bit field).
536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537#[cfg_attr(feature = "serde", derive(serde::Serialize))]
538#[non_exhaustive]
539pub enum PaprReductionV0 {
540    /// 0000 — No PAPR reduction is used.
541    NoReduction,
542    /// 0001 — ACE-PAPR only is used.
543    AceOnly,
544    /// 0010 — TR-PAPR only is used.
545    TrOnly,
546    /// 0011 — Both ACE and TR are used.
547    AceAndTr,
548    /// All other values — reserved.
549    Reserved(u8),
550}
551
552impl PaprReductionV0 {
553    /// Decode from the 4-bit wire value.
554    #[must_use]
555    pub fn from_u8(v: u8) -> Self {
556        match v & 0x0F {
557            0 => Self::NoReduction,
558            1 => Self::AceOnly,
559            2 => Self::TrOnly,
560            3 => Self::AceAndTr,
561            other => Self::Reserved(other),
562        }
563    }
564
565    /// Encode to the 4-bit wire value.
566    #[must_use]
567    pub const fn to_u8(self) -> u8 {
568        match self {
569            Self::NoReduction => 0,
570            Self::AceOnly => 1,
571            Self::TrOnly => 2,
572            Self::AceAndTr => 3,
573            Self::Reserved(v) => v,
574        }
575    }
576}
577
578/// PAPR reduction for T2_VERSION > '0000' — EN 302 755 §7.2.2 Table 23b (4-bit field).
579#[derive(Debug, Clone, Copy, PartialEq, Eq)]
580#[cfg_attr(feature = "serde", derive(serde::Serialize))]
581#[non_exhaustive]
582pub enum PaprReductionVn {
583    /// 0000 — L1-ACE is used and TR is used on P2 symbols only.
584    L1AceTrOnP2,
585    /// 0001 — L1-ACE and ACE only are used.
586    L1AceAndAce,
587    /// 0010 — L1-ACE and TR only are used.
588    L1AceAndTr,
589    /// 0011 — L1-ACE, ACE and TR are used.
590    L1AceAceAndTr,
591    /// All other values — reserved.
592    Reserved(u8),
593}
594
595impl PaprReductionVn {
596    /// Decode from the 4-bit wire value.
597    #[must_use]
598    pub fn from_u8(v: u8) -> Self {
599        match v & 0x0F {
600            0 => Self::L1AceTrOnP2,
601            1 => Self::L1AceAndAce,
602            2 => Self::L1AceAndTr,
603            3 => Self::L1AceAceAndTr,
604            other => Self::Reserved(other),
605        }
606    }
607
608    /// Encode to the 4-bit wire value.
609    #[must_use]
610    pub const fn to_u8(self) -> u8 {
611        match self {
612            Self::L1AceTrOnP2 => 0,
613            Self::L1AceAndAce => 1,
614            Self::L1AceAndTr => 2,
615            Self::L1AceAceAndTr => 3,
616            Self::Reserved(v) => v,
617        }
618    }
619}
620
621/// Version-tagged PAPR reduction result.
622#[derive(Debug, Clone, Copy, PartialEq, Eq)]
623#[cfg_attr(feature = "serde", derive(serde::Serialize))]
624#[non_exhaustive]
625pub enum PaprReduction {
626    /// Decoded using Table 23a (T2_VERSION = '0000').
627    V0(PaprReductionV0),
628    /// Decoded using Table 23b (T2_VERSION > '0000').
629    Vn(PaprReductionVn),
630}
631
632/// PLP code rate (T2-base profile column) — EN 302 755 §7.2.3.1 Table 32 (3-bit field).
633///
634/// The T2-Lite column differs (see Table 32); this enum decodes the T2-base column only.
635#[derive(Debug, Clone, Copy, PartialEq, Eq)]
636#[cfg_attr(feature = "serde", derive(serde::Serialize))]
637#[non_exhaustive]
638pub enum PlpCodeRate {
639    /// 000 — 1/2.
640    R1_2,
641    /// 001 — 3/5.
642    R3_5,
643    /// 010 — 2/3.
644    R2_3,
645    /// 011 — 3/4.
646    R3_4,
647    /// 100 — 4/5.
648    R4_5,
649    /// 101 — 5/6.
650    R5_6,
651    /// All other values — reserved (T2-base; T2-Lite uses 110/111 differently).
652    Reserved(u8),
653}
654
655impl PlpCodeRate {
656    /// Decode from the 3-bit wire value (T2-base profile column, Table 32).
657    #[must_use]
658    pub fn from_u8(v: u8) -> Self {
659        match v & 0x07 {
660            0 => Self::R1_2,
661            1 => Self::R3_5,
662            2 => Self::R2_3,
663            3 => Self::R3_4,
664            4 => Self::R4_5,
665            5 => Self::R5_6,
666            other => Self::Reserved(other),
667        }
668    }
669
670    /// Encode to the 3-bit wire value.
671    #[must_use]
672    pub const fn to_u8(self) -> u8 {
673        match self {
674            Self::R1_2 => 0,
675            Self::R3_5 => 1,
676            Self::R2_3 => 2,
677            Self::R3_4 => 3,
678            Self::R4_5 => 4,
679            Self::R5_6 => 5,
680            Self::Reserved(v) => v,
681        }
682    }
683}
684
685// ── §204 spec-label convention ──────────────────────────────────────────────
686// Each enum exposes `name() -> &'static str` (the spec token; reserved/unknown
687// → "reserved") and a `Display` impl via `impl_spec_display!`, which preserves
688// the byte on the `Reserved(u8)` arm as `reserved(0x..)`. Labels use EN 302 755
689// spec notation (Tables 21–36, 23a/23b).
690
691impl TxInputStreamType {
692    /// Human-readable spec label (EN 302 755 Table 21).
693    #[must_use]
694    pub fn name(&self) -> &'static str {
695        match self {
696            Self::TsOnly => "TS",
697            Self::GenericStream => "Generic Stream",
698            Self::Both => "TS + Generic",
699            Self::Reserved(_) => "reserved",
700        }
701    }
702}
703broadcast_common::impl_spec_display!(TxInputStreamType, Reserved);
704
705impl GuardInterval {
706    /// Human-readable spec label (EN 302 755 Table 22).
707    #[must_use]
708    pub fn name(&self) -> &'static str {
709        match self {
710            Self::G1_32 => "1/32",
711            Self::G1_16 => "1/16",
712            Self::G1_8 => "1/8",
713            Self::G1_4 => "1/4",
714            Self::G1_128 => "1/128",
715            Self::G19_128 => "19/128",
716            Self::G19_256 => "19/256",
717            Self::Reserved(_) => "reserved",
718        }
719    }
720}
721broadcast_common::impl_spec_display!(GuardInterval, Reserved);
722
723impl L1Modulation {
724    /// Human-readable spec label (EN 302 755 Table 24).
725    #[must_use]
726    pub fn name(&self) -> &'static str {
727        match self {
728            Self::Bpsk => "BPSK",
729            Self::Qpsk => "QPSK",
730            Self::Qam16 => "16-QAM",
731            Self::Qam64 => "64-QAM",
732            Self::Reserved(_) => "reserved",
733        }
734    }
735}
736broadcast_common::impl_spec_display!(L1Modulation, Reserved);
737
738impl L1CodeRate {
739    /// Human-readable spec label (EN 302 755 Table 25).
740    #[must_use]
741    pub fn name(&self) -> &'static str {
742        match self {
743            Self::R1_2 => "1/2",
744            Self::Reserved(_) => "reserved",
745        }
746    }
747}
748broadcast_common::impl_spec_display!(L1CodeRate, Reserved);
749
750impl L1FecType {
751    /// Human-readable spec label (EN 302 755 Table 26).
752    #[must_use]
753    pub fn name(&self) -> &'static str {
754        match self {
755            Self::Ldpc16K => "16K LDPC",
756            Self::Reserved(_) => "reserved",
757        }
758    }
759}
760broadcast_common::impl_spec_display!(L1FecType, Reserved);
761
762impl PilotPattern {
763    /// Human-readable spec label (EN 302 755 Table 27).
764    #[must_use]
765    pub fn name(&self) -> &'static str {
766        match self {
767            Self::Pp1 => "PP1",
768            Self::Pp2 => "PP2",
769            Self::Pp3 => "PP3",
770            Self::Pp4 => "PP4",
771            Self::Pp5 => "PP5",
772            Self::Pp6 => "PP6",
773            Self::Pp7 => "PP7",
774            Self::Pp8 => "PP8",
775            Self::Reserved(_) => "reserved",
776        }
777    }
778}
779broadcast_common::impl_spec_display!(PilotPattern, Reserved);
780
781impl T2Version {
782    /// Human-readable spec label (EN 302 755 Table 28).
783    #[must_use]
784    pub fn name(&self) -> &'static str {
785        match self {
786            Self::V1_1_1 => "1.1.1",
787            Self::V1_2_1 => "1.2.1",
788            Self::V1_3_1 => "1.3.1",
789            Self::Reserved(_) => "reserved",
790        }
791    }
792}
793broadcast_common::impl_spec_display!(T2Version, Reserved);
794
795impl PlpType {
796    /// Human-readable spec label (EN 302 755 Table 30).
797    #[must_use]
798    pub fn name(&self) -> &'static str {
799        match self {
800            Self::Common => "Common",
801            Self::DataType1 => "Data Type 1",
802            Self::DataType2 => "Data Type 2",
803            Self::Reserved(_) => "reserved",
804        }
805    }
806}
807broadcast_common::impl_spec_display!(PlpType, Reserved);
808
809impl PlpPayloadType {
810    /// Human-readable spec label (EN 302 755 Table 31).
811    #[must_use]
812    pub fn name(&self) -> &'static str {
813        match self {
814            Self::Gfps => "GFPS",
815            Self::Gcs => "GCS",
816            Self::Gse => "GSE",
817            Self::Ts => "TS",
818            Self::Reserved(_) => "reserved",
819        }
820    }
821}
822broadcast_common::impl_spec_display!(PlpPayloadType, Reserved);
823
824impl PlpModulation {
825    /// Human-readable spec label (EN 302 755 Table 33).
826    #[must_use]
827    pub fn name(&self) -> &'static str {
828        match self {
829            Self::Qpsk => "QPSK",
830            Self::Qam16 => "16-QAM",
831            Self::Qam64 => "64-QAM",
832            Self::Qam256 => "256-QAM",
833            Self::Reserved(_) => "reserved",
834        }
835    }
836}
837broadcast_common::impl_spec_display!(PlpModulation, Reserved);
838
839impl PlpFecType {
840    /// Human-readable spec label (EN 302 755 Table 34).
841    #[must_use]
842    pub fn name(&self) -> &'static str {
843        match self {
844            Self::Ldpc16K => "16K LDPC",
845            Self::Ldpc64K => "64K LDPC",
846            Self::Reserved(_) => "reserved",
847        }
848    }
849}
850broadcast_common::impl_spec_display!(PlpFecType, Reserved);
851
852impl PlpMode {
853    /// Human-readable spec label (EN 302 755 Table 35).
854    #[must_use]
855    pub fn name(&self) -> &'static str {
856        match self {
857            Self::NotSpecified => "Not specified",
858            Self::Normal => "Normal",
859            Self::HighEfficiency => "High Efficiency",
860            Self::Reserved(_) => "reserved",
861        }
862    }
863}
864broadcast_common::impl_spec_display!(PlpMode, Reserved);
865
866impl AuxStreamType {
867    /// Human-readable spec label (EN 302 755 Table 36).
868    #[must_use]
869    pub fn name(&self) -> &'static str {
870        match self {
871            Self::TxSig => "TX-SIG",
872            Self::Reserved(_) => "reserved",
873        }
874    }
875}
876broadcast_common::impl_spec_display!(AuxStreamType, Reserved);
877
878impl PaprReductionV0 {
879    /// Human-readable spec label (EN 302 755 Table 23a).
880    #[must_use]
881    pub fn name(&self) -> &'static str {
882        match self {
883            Self::NoReduction => "No PAPR reduction",
884            Self::AceOnly => "ACE-PAPR only",
885            Self::TrOnly => "TR-PAPR only",
886            Self::AceAndTr => "ACE and TR",
887            Self::Reserved(_) => "reserved",
888        }
889    }
890}
891broadcast_common::impl_spec_display!(PaprReductionV0, Reserved);
892
893impl PaprReductionVn {
894    /// Human-readable spec label (EN 302 755 Table 23b).
895    #[must_use]
896    pub fn name(&self) -> &'static str {
897        match self {
898            Self::L1AceTrOnP2 => "L1-ACE, TR on P2 only",
899            Self::L1AceAndAce => "L1-ACE and ACE",
900            Self::L1AceAndTr => "L1-ACE and TR",
901            Self::L1AceAceAndTr => "L1-ACE, ACE and TR",
902            Self::Reserved(_) => "reserved",
903        }
904    }
905}
906broadcast_common::impl_spec_display!(PaprReductionVn, Reserved);
907
908impl PaprReduction {
909    /// Human-readable spec label, delegating to the version-specific table.
910    #[must_use]
911    pub fn name(&self) -> &'static str {
912        match self {
913            Self::V0(inner) => inner.name(),
914            Self::Vn(inner) => inner.name(),
915        }
916    }
917}
918broadcast_common::impl_spec_display!(PaprReduction);
919
920impl PlpCodeRate {
921    /// Human-readable spec label (EN 302 755 Table 32, T2-base column).
922    #[must_use]
923    pub fn name(&self) -> &'static str {
924        match self {
925            Self::R1_2 => "1/2",
926            Self::R3_5 => "3/5",
927            Self::R2_3 => "2/3",
928            Self::R3_4 => "3/4",
929            Self::R4_5 => "4/5",
930            Self::R5_6 => "5/6",
931            Self::Reserved(_) => "reserved",
932        }
933    }
934}
935broadcast_common::impl_spec_display!(PlpCodeRate, Reserved);
936
937#[cfg(test)]
938mod tests {
939    use super::*;
940
941    #[test]
942    fn spec_label_known_and_reserved() {
943        assert_eq!(GuardInterval::G19_256.name(), "19/256");
944        assert_eq!(GuardInterval::G19_256.to_string(), "19/256");
945        assert_eq!(PlpModulation::Qam256.to_string(), "256-QAM");
946        // reserved: name() is lossy, Display preserves the byte
947        assert_eq!(GuardInterval::Reserved(0x07).name(), "reserved");
948        assert_eq!(GuardInterval::Reserved(0x07).to_string(), "reserved(0x07)");
949        assert_eq!(
950            PaprReduction::V0(PaprReductionV0::AceOnly).to_string(),
951            "ACE-PAPR only"
952        );
953    }
954
955    #[test]
956    fn tx_input_stream_type_round_trip() {
957        for v in 0u8..=2 {
958            let e = TxInputStreamType::from_u8(v);
959            assert_eq!(e.to_u8(), v);
960            assert!(!matches!(e, TxInputStreamType::Reserved(_)));
961        }
962        assert!(matches!(
963            TxInputStreamType::from_u8(3),
964            TxInputStreamType::Reserved(3)
965        ));
966        assert!(matches!(
967            TxInputStreamType::from_u8(0xFF),
968            TxInputStreamType::Reserved(0xFF)
969        ));
970    }
971
972    #[test]
973    fn guard_interval_round_trip() {
974        for v in 0u8..=6 {
975            let e = GuardInterval::from_u8(v);
976            assert_eq!(e.to_u8(), v);
977            assert!(!matches!(e, GuardInterval::Reserved(_)));
978        }
979        assert!(matches!(
980            GuardInterval::from_u8(7),
981            GuardInterval::Reserved(7)
982        ));
983    }
984
985    #[test]
986    fn l1_modulation_round_trip() {
987        for v in 0u8..=3 {
988            let e = L1Modulation::from_u8(v);
989            assert_eq!(e.to_u8(), v);
990            assert!(!matches!(e, L1Modulation::Reserved(_)));
991        }
992        assert!(matches!(
993            L1Modulation::from_u8(0x0F),
994            L1Modulation::Reserved(_)
995        ));
996    }
997
998    #[test]
999    fn l1_code_rate_round_trip() {
1000        assert_eq!(L1CodeRate::from_u8(0).to_u8(), 0);
1001        assert!(!matches!(L1CodeRate::from_u8(0), L1CodeRate::Reserved(_)));
1002        assert!(matches!(L1CodeRate::from_u8(1), L1CodeRate::Reserved(1)));
1003        assert!(matches!(L1CodeRate::from_u8(3), L1CodeRate::Reserved(3)));
1004    }
1005
1006    #[test]
1007    fn l1_fec_type_round_trip() {
1008        assert_eq!(L1FecType::from_u8(0).to_u8(), 0);
1009        assert!(matches!(L1FecType::from_u8(1), L1FecType::Reserved(1)));
1010    }
1011
1012    #[test]
1013    fn pilot_pattern_round_trip() {
1014        for v in 0u8..=7 {
1015            let e = PilotPattern::from_u8(v);
1016            assert_eq!(e.to_u8(), v);
1017            assert!(!matches!(e, PilotPattern::Reserved(_)));
1018        }
1019        assert!(matches!(
1020            PilotPattern::from_u8(8),
1021            PilotPattern::Reserved(8)
1022        ));
1023    }
1024
1025    #[test]
1026    fn t2_version_round_trip() {
1027        for v in 0u8..=2 {
1028            let e = T2Version::from_u8(v);
1029            assert_eq!(e.to_u8(), v);
1030            assert!(!matches!(e, T2Version::Reserved(_)));
1031        }
1032        assert!(matches!(T2Version::from_u8(3), T2Version::Reserved(3)));
1033    }
1034
1035    #[test]
1036    fn plp_type_round_trip() {
1037        for v in 0u8..=2 {
1038            let e = PlpType::from_u8(v);
1039            assert_eq!(e.to_u8(), v);
1040            assert!(!matches!(e, PlpType::Reserved(_)));
1041        }
1042        assert!(matches!(PlpType::from_u8(7), PlpType::Reserved(7)));
1043    }
1044
1045    #[test]
1046    fn plp_payload_type_round_trip() {
1047        for v in 0u8..=3 {
1048            let e = PlpPayloadType::from_u8(v);
1049            assert_eq!(e.to_u8(), v);
1050            assert!(!matches!(e, PlpPayloadType::Reserved(_)));
1051        }
1052        assert!(matches!(
1053            PlpPayloadType::from_u8(0x1F),
1054            PlpPayloadType::Reserved(_)
1055        ));
1056    }
1057
1058    #[test]
1059    fn plp_modulation_round_trip() {
1060        for v in 0u8..=3 {
1061            let e = PlpModulation::from_u8(v);
1062            assert_eq!(e.to_u8(), v);
1063            assert!(!matches!(e, PlpModulation::Reserved(_)));
1064        }
1065        assert!(matches!(
1066            PlpModulation::from_u8(7),
1067            PlpModulation::Reserved(7)
1068        ));
1069    }
1070
1071    #[test]
1072    fn plp_fec_type_round_trip() {
1073        for v in 0u8..=1 {
1074            let e = PlpFecType::from_u8(v);
1075            assert_eq!(e.to_u8(), v);
1076            assert!(!matches!(e, PlpFecType::Reserved(_)));
1077        }
1078        assert!(matches!(PlpFecType::from_u8(2), PlpFecType::Reserved(2)));
1079    }
1080
1081    #[test]
1082    fn plp_mode_round_trip() {
1083        for v in 0u8..=2 {
1084            let e = PlpMode::from_u8(v);
1085            assert_eq!(e.to_u8(), v);
1086            assert!(!matches!(e, PlpMode::Reserved(_)));
1087        }
1088        assert!(matches!(PlpMode::from_u8(3), PlpMode::Reserved(3)));
1089    }
1090
1091    #[test]
1092    fn aux_stream_type_round_trip() {
1093        assert_eq!(AuxStreamType::from_u8(0).to_u8(), 0);
1094        assert!(matches!(
1095            AuxStreamType::from_u8(0x0F),
1096            AuxStreamType::Reserved(_)
1097        ));
1098    }
1099
1100    #[test]
1101    fn plp_code_rate_round_trip() {
1102        for v in 0u8..=5 {
1103            let e = PlpCodeRate::from_u8(v);
1104            assert_eq!(e.to_u8(), v);
1105            assert!(!matches!(e, PlpCodeRate::Reserved(_)));
1106        }
1107        assert!(matches!(PlpCodeRate::from_u8(6), PlpCodeRate::Reserved(6)));
1108        assert!(matches!(PlpCodeRate::from_u8(7), PlpCodeRate::Reserved(7)));
1109    }
1110}