Skip to main content

dvb_si/descriptors/
linkage.rs

1//! Linkage Descriptor — ETSI EN 300 468 §6.2.19 (tag 0x4A).
2//!
3//! Carried inside NIT (network linkage), BAT (bouquet linkage) and
4//! SDT (service replacement / premiere hand-over).
5//!
6//! Conditional inner structures (§6.2.19.2–4):
7//! - `linkage_type == 0x08` → [`MobileHandOverInfo`] (Table 61)
8//! - `linkage_type == 0x0D` → [`EventLinkageInfo`]    (Table 64)
9//! - `linkage_type 0x0E..=0x1F` → [`ExtendedEventLinkageInfo`] (Table 65)
10//!
11//! Other linkage types (including user-defined `0x80..=0xFE` and those
12//! defined in companion specs such as TS 102 006 / EN 301 192 / EN 303 560)
13//! have no EN 300 468 conditional block; their bytes after the fixed fields
14//! are the `private_data_byte` tail.
15
16use super::descriptor_body;
17use crate::error::{Error, Result};
18use dvb_common::{Parse, Serialize};
19
20/// Descriptor tag for linkage_descriptor.
21pub const TAG: u8 = 0x4A;
22const HEADER_LEN: usize = 2;
23const FIXED_FIELDS_LEN: usize = 7;
24
25const HANDOVER_TYPE_MASK: u8 = 0xF0;
26const ORIGIN_TYPE_MASK: u8 = 0x01;
27const RESERVED_HANDOVER_MASK: u8 = 0x0E;
28
29const TARGET_LISTED_MASK: u8 = 0x80;
30const EVENT_SIMULCAST_MASK: u8 = 0x40;
31const RESERVED_EVENT_MASK: u8 = 0x3F;
32
33const EXT_TARGET_LISTED_MASK: u8 = 0x80;
34const EXT_EVENT_SIMULCAST_MASK: u8 = 0x40;
35const EXT_LINK_TYPE_MASK: u8 = 0x30;
36const EXT_TARGET_ID_TYPE_MASK: u8 = 0x0C;
37const EXT_ONID_FLAG_MASK: u8 = 0x02;
38const EXT_SID_FLAG_MASK: u8 = 0x01;
39
40/// Linkage type — ETSI EN 300 468 Table 60.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize))]
43#[non_exhaustive]
44pub enum LinkageType {
45    /// 0x01 — information service.
46    InformationService,
47    /// 0x02 — EPG service.
48    EpgService,
49    /// 0x03 — CA replacement service.
50    CaReplacementService,
51    /// 0x04 — TS containing complete network/bouquet SI.
52    TsContainingCompleteSi,
53    /// 0x05 — service replacement service.
54    ServiceReplacementService,
55    /// 0x06 — data broadcast service.
56    DataBroadcastService,
57    /// 0x07 — RCS map.
58    RcsMap,
59    /// 0x08 — mobile hand-over.
60    MobileHandOver,
61    /// 0x09 — SSU service.
62    SsuService,
63    /// 0x0A — TS containing SSU BAT or NIT.
64    TsContainingSsuBatOrNit,
65    /// 0x0B — IP/MAC notification service.
66    IpMacNotificationService,
67    /// 0x0C — TS containing INT BAT or NIT.
68    TsContainingIntBatOrNit,
69    /// 0x0D — event linkage.
70    EventLinkage,
71    /// 0x0E..=0x1F — extended event linkage.
72    ExtendedEventLinkage(u8),
73    /// 0x20 — downloadable font info linkage.
74    DownloadableFontInfoLinkage,
75    /// 0x21 — Native IP bootstrap MPE stream.
76    NativeIpBootstrapMpeStream,
77    /// Reserved/unallocated wire value, preserved verbatim for round-trip.
78    Reserved(u8),
79}
80
81impl LinkageType {
82    #[must_use]
83    /// Creates a value from a wire byte, preserving every possible
84    /// byte value for lossless round-trip.
85    pub fn from_u8(v: u8) -> Self {
86        match v {
87            0x01 => Self::InformationService,
88            0x02 => Self::EpgService,
89            0x03 => Self::CaReplacementService,
90            0x04 => Self::TsContainingCompleteSi,
91            0x05 => Self::ServiceReplacementService,
92            0x06 => Self::DataBroadcastService,
93            0x07 => Self::RcsMap,
94            0x08 => Self::MobileHandOver,
95            0x09 => Self::SsuService,
96            0x0A => Self::TsContainingSsuBatOrNit,
97            0x0B => Self::IpMacNotificationService,
98            0x0C => Self::TsContainingIntBatOrNit,
99            0x0D => Self::EventLinkage,
100            0x0E..=0x1F => Self::ExtendedEventLinkage(v),
101            0x20 => Self::DownloadableFontInfoLinkage,
102            0x21 => Self::NativeIpBootstrapMpeStream,
103            v => Self::Reserved(v),
104        }
105    }
106
107    #[must_use]
108    /// Returns the wire byte for this value.
109    pub fn to_u8(self) -> u8 {
110        match self {
111            Self::InformationService => 0x01,
112            Self::EpgService => 0x02,
113            Self::CaReplacementService => 0x03,
114            Self::TsContainingCompleteSi => 0x04,
115            Self::ServiceReplacementService => 0x05,
116            Self::DataBroadcastService => 0x06,
117            Self::RcsMap => 0x07,
118            Self::MobileHandOver => 0x08,
119            Self::SsuService => 0x09,
120            Self::TsContainingSsuBatOrNit => 0x0A,
121            Self::IpMacNotificationService => 0x0B,
122            Self::TsContainingIntBatOrNit => 0x0C,
123            Self::EventLinkage => 0x0D,
124            Self::ExtendedEventLinkage(v) => v,
125            Self::DownloadableFontInfoLinkage => 0x20,
126            Self::NativeIpBootstrapMpeStream => 0x21,
127            Self::Reserved(v) => v,
128        }
129    }
130
131    #[must_use]
132    /// Returns a human-readable spec name for this value.
133    pub fn name(self) -> &'static str {
134        match self {
135            Self::InformationService => "information service",
136            Self::EpgService => "EPG service",
137            Self::CaReplacementService => "CA replacement service",
138            Self::TsContainingCompleteSi => "TS containing complete network/bouquet SI",
139            Self::ServiceReplacementService => "service replacement service",
140            Self::DataBroadcastService => "data broadcast service",
141            Self::RcsMap => "RCS map",
142            Self::MobileHandOver => "mobile hand-over",
143            Self::SsuService => "SSU service",
144            Self::TsContainingSsuBatOrNit => "TS containing SSU BAT or NIT",
145            Self::IpMacNotificationService => "IP/MAC notification service",
146            Self::TsContainingIntBatOrNit => "TS containing INT BAT or NIT",
147            Self::EventLinkage => "event linkage",
148            Self::ExtendedEventLinkage(_) => "extended event linkage",
149            Self::DownloadableFontInfoLinkage => "downloadable font info linkage",
150            Self::NativeIpBootstrapMpeStream => "Native IP bootstrap MPE stream",
151            Self::Reserved(_) => "reserved",
152        }
153    }
154}
155dvb_common::impl_spec_display!(LinkageType, ExtendedEventLinkage, Reserved);
156
157/// Hand-over type — ETSI EN 300 468 Table 62.
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize))]
160#[non_exhaustive]
161pub enum HandOverType {
162    /// 0x1 — DVB hand-over to an identical service in a neighbouring country.
163    DvbIdenticalNeighbouringCountry,
164    /// 0x2 — DVB hand-over to a local variation of the same service.
165    DvbLocalVariation,
166    /// 0x3 — DVB hand-over to an associated service.
167    DvbAssociatedService,
168    /// Reserved/unallocated wire value, preserved verbatim for round-trip.
169    Reserved(u8),
170}
171
172impl HandOverType {
173    #[must_use]
174    /// Creates a value from a wire byte, preserving every possible
175    /// byte value for lossless round-trip.
176    pub fn from_u8(v: u8) -> Self {
177        match v {
178            0x1 => Self::DvbIdenticalNeighbouringCountry,
179            0x2 => Self::DvbLocalVariation,
180            0x3 => Self::DvbAssociatedService,
181            v => Self::Reserved(v),
182        }
183    }
184
185    #[must_use]
186    /// Returns the wire byte for this value.
187    pub fn to_u8(self) -> u8 {
188        match self {
189            Self::DvbIdenticalNeighbouringCountry => 0x1,
190            Self::DvbLocalVariation => 0x2,
191            Self::DvbAssociatedService => 0x3,
192            Self::Reserved(v) => v,
193        }
194    }
195
196    #[must_use]
197    /// Returns a human-readable spec name for this value.
198    pub fn name(self) -> &'static str {
199        match self {
200            Self::DvbIdenticalNeighbouringCountry => {
201                "DVB hand-over to an identical service in a neighbouring country"
202            }
203            Self::DvbLocalVariation => "DVB hand-over to a local variation of the same service",
204            Self::DvbAssociatedService => "DVB hand-over to an associated service",
205            Self::Reserved(_) => "reserved",
206        }
207    }
208}
209dvb_common::impl_spec_display!(HandOverType, Reserved);
210
211/// Link type — ETSI EN 300 468 Table 66.
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
213#[cfg_attr(feature = "serde", derive(serde::Serialize))]
214#[non_exhaustive]
215pub enum LinkType {
216    /// 0 — SD (linkage_type 0x0E) or UHD (linkage_type 0x0F).
217    SdOrUhd,
218    /// 1 — HD (linkage_type 0x0E) or service frame compatible
219    /// plano-stereoscopic (linkage_type 0x0F).
220    HdOrServiceFrameCompatible,
221    /// 2 — frame compatible plano-stereoscopic H.264/AVC (linkage_type 0x0E).
222    FrameCompatiblePlanoStereoscopic,
223    /// 3 — service compatible plano-stereoscopic MVC (linkage_type 0x0E).
224    ServiceCompatiblePlanoStereoscopicMvc,
225    /// Reserved/unallocated wire value, preserved verbatim for round-trip.
226    Reserved(u8),
227}
228
229impl LinkType {
230    #[must_use]
231    /// Creates a value from a wire byte, preserving every possible
232    /// byte value for lossless round-trip.
233    pub fn from_u8(v: u8) -> Self {
234        match v {
235            0 => Self::SdOrUhd,
236            1 => Self::HdOrServiceFrameCompatible,
237            2 => Self::FrameCompatiblePlanoStereoscopic,
238            3 => Self::ServiceCompatiblePlanoStereoscopicMvc,
239            v => Self::Reserved(v),
240        }
241    }
242
243    #[must_use]
244    /// Returns the wire byte for this value.
245    pub fn to_u8(self) -> u8 {
246        match self {
247            Self::SdOrUhd => 0,
248            Self::HdOrServiceFrameCompatible => 1,
249            Self::FrameCompatiblePlanoStereoscopic => 2,
250            Self::ServiceCompatiblePlanoStereoscopicMvc => 3,
251            Self::Reserved(v) => v,
252        }
253    }
254
255    #[must_use]
256    /// Returns a human-readable spec name for this value.
257    pub fn name(self) -> &'static str {
258        match self {
259            Self::SdOrUhd => "SD (0x0E) / UHD (0x0F)",
260            Self::HdOrServiceFrameCompatible => {
261                "HD (0x0E) / service frame compatible plano-stereoscopic (0x0F)"
262            }
263            Self::FrameCompatiblePlanoStereoscopic => {
264                "frame compatible plano-stereoscopic H.264/AVC"
265            }
266            Self::ServiceCompatiblePlanoStereoscopicMvc => {
267                "service compatible plano-stereoscopic MVC"
268            }
269            Self::Reserved(_) => "reserved",
270        }
271    }
272}
273dvb_common::impl_spec_display!(LinkType, Reserved);
274
275/// Target id type — ETSI EN 300 468 Table 67.
276#[derive(Debug, Clone, Copy, PartialEq, Eq)]
277#[cfg_attr(feature = "serde", derive(serde::Serialize))]
278#[non_exhaustive]
279pub enum TargetIdType {
280    /// 0 — use transport_stream_id.
281    UseTransportStreamId,
282    /// 1 — use target_transport_stream_id.
283    UseTargetTransportStreamId,
284    /// 2 — match any transport_stream_id (wildcard).
285    MatchAnyTransportStreamId,
286    /// 3 — use user_defined_id.
287    UseUserDefinedId,
288    /// Reserved/unallocated wire value, preserved verbatim for round-trip.
289    Reserved(u8),
290}
291
292impl TargetIdType {
293    #[must_use]
294    /// Creates a value from a wire byte, preserving every possible
295    /// byte value for lossless round-trip.
296    pub fn from_u8(v: u8) -> Self {
297        match v {
298            0 => Self::UseTransportStreamId,
299            1 => Self::UseTargetTransportStreamId,
300            2 => Self::MatchAnyTransportStreamId,
301            3 => Self::UseUserDefinedId,
302            v => Self::Reserved(v),
303        }
304    }
305
306    #[must_use]
307    /// Returns the wire byte for this value.
308    pub fn to_u8(self) -> u8 {
309        match self {
310            Self::UseTransportStreamId => 0,
311            Self::UseTargetTransportStreamId => 1,
312            Self::MatchAnyTransportStreamId => 2,
313            Self::UseUserDefinedId => 3,
314            Self::Reserved(v) => v,
315        }
316    }
317
318    #[must_use]
319    /// Returns a human-readable spec name for this value.
320    pub fn name(self) -> &'static str {
321        match self {
322            Self::UseTransportStreamId => "use transport_stream_id",
323            Self::UseTargetTransportStreamId => "use target_transport_stream_id",
324            Self::MatchAnyTransportStreamId => "match any transport_stream_id (wildcard)",
325            Self::UseUserDefinedId => "use user_defined_id",
326            Self::Reserved(_) => "reserved",
327        }
328    }
329}
330dvb_common::impl_spec_display!(TargetIdType, Reserved);
331
332/// Mobile hand-over info — EN 300 468 Table 61 (`linkage_type == 0x08`).
333#[derive(Debug, Clone, PartialEq, Eq)]
334#[cfg_attr(feature = "serde", derive(serde::Serialize))]
335pub struct MobileHandOverInfo {
336    /// hand_over_type — 4 bits `[7:4]`.  See Table 62.
337    pub hand_over_type: HandOverType,
338    /// origin_type — 1 bit `[0]`.  `false` = NIT, `true` = SDT (Table 63).
339    pub origin_type: bool,
340    /// network_id — 16 bits, present only when hand_over_type in {1, 2, 3}.
341    pub network_id: Option<u16>,
342    /// initial_service_id — present only when `origin_type == false` (NIT).
343    pub initial_service_id: Option<u16>,
344}
345
346impl MobileHandOverInfo {
347    fn serialized_len(&self) -> usize {
348        1 + self.network_id.map_or(0, |_| 2) + self.initial_service_id.map_or(0, |_| 2)
349    }
350
351    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
352        let len = self.serialized_len();
353        if buf.len() < len {
354            return Err(Error::OutputBufferTooSmall {
355                need: len,
356                have: buf.len(),
357            });
358        }
359        let flags_byte = (self.hand_over_type.to_u8() << 4)
360            | RESERVED_HANDOVER_MASK
361            | u8::from(self.origin_type);
362        buf[0] = flags_byte;
363        let mut pos = 1;
364        if let Some(nid) = self.network_id {
365            buf[pos..pos + 2].copy_from_slice(&nid.to_be_bytes());
366            pos += 2;
367        }
368        if let Some(sid) = self.initial_service_id {
369            buf[pos..pos + 2].copy_from_slice(&sid.to_be_bytes());
370        }
371        Ok(len)
372    }
373}
374
375/// Event linkage info — EN 300 468 Table 64 (`linkage_type == 0x0D`).
376#[derive(Debug, Clone, PartialEq, Eq)]
377#[cfg_attr(feature = "serde", derive(serde::Serialize))]
378pub struct EventLinkageInfo {
379    /// target_event_id — 16 bits.
380    pub target_event_id: u16,
381    /// target_listed — 1 bit `[7]`.
382    pub target_listed: bool,
383    /// event_simulcast — 1 bit `[6]`.
384    pub event_simulcast: bool,
385}
386
387impl EventLinkageInfo {
388    const SERIALIZED_LEN: usize = 3;
389
390    fn serialized_len(&self) -> usize {
391        Self::SERIALIZED_LEN
392    }
393
394    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
395        if buf.len() < Self::SERIALIZED_LEN {
396            return Err(Error::OutputBufferTooSmall {
397                need: Self::SERIALIZED_LEN,
398                have: buf.len(),
399            });
400        }
401        buf[0..2].copy_from_slice(&self.target_event_id.to_be_bytes());
402        let mut byte2: u8 = RESERVED_EVENT_MASK;
403        if self.target_listed {
404            byte2 |= TARGET_LISTED_MASK;
405        }
406        if self.event_simulcast {
407            byte2 |= EVENT_SIMULCAST_MASK;
408        }
409        buf[2] = byte2;
410        Ok(Self::SERIALIZED_LEN)
411    }
412}
413
414/// Target identification — EN 300 468 Table 65 inner conditional fields.
415#[derive(Debug, Clone, PartialEq, Eq)]
416#[cfg_attr(feature = "serde", derive(serde::Serialize))]
417pub enum TargetId {
418    /// `target_id_type == 3` — user_defined_id (16 bits).
419    UserDefined {
420        /// User-defined target service identifier.
421        user_defined_id: u16,
422    },
423    /// `target_id_type != 3` — DVB addressing with optional sub-fields.
424    Dvb {
425        /// `target_id_type` value (0, 1, or 2).  See Table 67.
426        target_id_type: TargetIdType,
427        /// target_transport_stream_id — present when `target_id_type == 1`.
428        target_transport_stream_id: Option<u16>,
429        /// target_original_network_id — present when `original_network_id_flag == 1`.
430        target_original_network_id: Option<u16>,
431        /// target_service_id — present when `service_id_flag == 1`.
432        target_service_id: Option<u16>,
433    },
434}
435
436impl TargetId {
437    fn serialized_len(&self) -> usize {
438        match self {
439            TargetId::UserDefined { .. } => 2,
440            TargetId::Dvb {
441                target_transport_stream_id,
442                target_original_network_id,
443                target_service_id,
444                ..
445            } => {
446                usize::from(target_transport_stream_id.is_some()) * 2
447                    + usize::from(target_original_network_id.is_some()) * 2
448                    + usize::from(target_service_id.is_some()) * 2
449            }
450        }
451    }
452
453    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
454        let len = self.serialized_len();
455        if buf.len() < len {
456            return Err(Error::OutputBufferTooSmall {
457                need: len,
458                have: buf.len(),
459            });
460        }
461        match self {
462            TargetId::UserDefined { user_defined_id } => {
463                buf[..2].copy_from_slice(&user_defined_id.to_be_bytes());
464            }
465            TargetId::Dvb {
466                target_transport_stream_id,
467                target_original_network_id,
468                target_service_id,
469                ..
470            } => {
471                let ts_len = target_transport_stream_id.map_or(0, |_| 2);
472                let onid_len = target_original_network_id.map_or(0, |_| 2);
473                if let Some(ts_id) = target_transport_stream_id {
474                    buf[..2].copy_from_slice(&ts_id.to_be_bytes());
475                }
476                if let Some(onid) = target_original_network_id {
477                    buf[ts_len..ts_len + 2].copy_from_slice(&onid.to_be_bytes());
478                }
479                if let Some(sid) = target_service_id {
480                    let off = ts_len + onid_len;
481                    buf[off..off + 2].copy_from_slice(&sid.to_be_bytes());
482                }
483            }
484        }
485        Ok(len)
486    }
487}
488
489/// One entry in the extended event linkage loop — EN 300 468 Table 65.
490#[derive(Debug, Clone, PartialEq, Eq)]
491#[cfg_attr(feature = "serde", derive(serde::Serialize))]
492pub struct ExtendedEventLinkageEntry {
493    /// target_event_id — 16 bits.
494    pub target_event_id: u16,
495    /// target_listed — 1 bit `[7]`.
496    pub target_listed: bool,
497    /// event_simulcast — 1 bit `[6]`.
498    pub event_simulcast: bool,
499    /// link_type — 2 bits `[5:4]`.  See Table 66.
500    pub link_type: LinkType,
501    /// target_id_type — 2 bits `[3:2]`.  See Table 67.
502    pub target_id_type: TargetIdType,
503    /// original_network_id_flag — 1 bit `[1]`.
504    pub original_network_id_flag: bool,
505    /// service_id_flag — 1 bit `[0]`.
506    pub service_id_flag: bool,
507    /// Conditional target identification.
508    pub target_id: TargetId,
509}
510
511impl ExtendedEventLinkageEntry {
512    fn serialized_len(&self) -> usize {
513        2 + 1 + self.target_id.serialized_len()
514    }
515
516    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
517        let flags_byte_len = 3;
518        if buf.len() < flags_byte_len {
519            return Err(Error::OutputBufferTooSmall {
520                need: flags_byte_len,
521                have: buf.len(),
522            });
523        }
524        buf[0..2].copy_from_slice(&self.target_event_id.to_be_bytes());
525        let mut byte2: u8 = 0;
526        if self.target_listed {
527            byte2 |= EXT_TARGET_LISTED_MASK;
528        }
529        if self.event_simulcast {
530            byte2 |= EXT_EVENT_SIMULCAST_MASK;
531        }
532        byte2 |= (self.link_type.to_u8() & 0x03) << 4;
533        byte2 |= (self.target_id_type.to_u8() & 0x03) << 2;
534        if self.original_network_id_flag {
535            byte2 |= EXT_ONID_FLAG_MASK;
536        }
537        if self.service_id_flag {
538            byte2 |= EXT_SID_FLAG_MASK;
539        }
540        buf[2] = byte2;
541        let tid_written = self.target_id.serialize_into(&mut buf[3..])?;
542        Ok(3 + tid_written)
543    }
544}
545
546/// Extended event linkage info — EN 300 468 Table 65
547/// (`linkage_type 0x0E..=0x1F`).
548#[derive(Debug, Clone, PartialEq, Eq)]
549#[cfg_attr(feature = "serde", derive(serde::Serialize))]
550pub struct ExtendedEventLinkageInfo {
551    /// Entries in the extended event linkage loop.
552    pub entries: Vec<ExtendedEventLinkageEntry>,
553}
554
555impl ExtendedEventLinkageInfo {
556    fn serialized_len(&self) -> usize {
557        1 + self
558            .entries
559            .iter()
560            .map(|e| e.serialized_len())
561            .sum::<usize>()
562    }
563
564    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
565        let loop_len: usize = self.entries.iter().map(|e| e.serialized_len()).sum();
566        let total = 1 + loop_len;
567        if buf.len() < total {
568            return Err(Error::OutputBufferTooSmall {
569                need: total,
570                have: buf.len(),
571            });
572        }
573        buf[0] = loop_len as u8;
574        let mut pos = 1;
575        for entry in &self.entries {
576            let written = entry.serialize_into(&mut buf[pos..])?;
577            pos += written;
578        }
579        Ok(total)
580    }
581}
582
583/// Linkage-type-conditional inner data — EN 300 468 §6.2.19.2–4.
584///
585/// Typed variants correspond to the conditional blocks defined in EN 300 468;
586/// `None` covers all other linkage types (no conditional block per the main
587/// spec, remaining bytes go to `private_data`); `Other` captures the raw tail
588/// for linkage types whose conditional structure is defined in companion specs
589/// (e.g. TS 102 006 linkage_type 0x09/0x0A, EN 301 192 0x0B/0x0C,
590/// EN 303 560 0x20) or user-defined types (`0x80..=0xFE`) where we cannot
591/// distinguish the conditional block from the `private_data_byte` loop.
592#[derive(Debug, Clone, PartialEq, Eq)]
593#[non_exhaustive]
594#[cfg_attr(feature = "serde", derive(serde::Serialize))]
595pub enum LinkageData<'a> {
596    /// `linkage_type == 0x08` — mobile hand-over info (Table 61).
597    MobileHandOver(MobileHandOverInfo),
598    /// `linkage_type == 0x0D` — event linkage info (Table 64).
599    EventLinkage(EventLinkageInfo),
600    /// `linkage_type 0x0E..=0x1F` — extended event linkage info (Table 65).
601    ExtendedEventLinkage(ExtendedEventLinkageInfo),
602    /// No EN 300 468 conditional block — `private_data` starts immediately
603    /// after the fixed fields.
604    None,
605    /// Raw tail for linkage types with externally-defined or user-defined
606    /// conditional structure.  Because we cannot determine the boundary
607    /// between the conditional block and the `private_data_byte` loop, all
608    /// remaining bytes are captured here and `private_data` is empty.
609    #[cfg_attr(feature = "serde", serde(borrow))]
610    Other(&'a [u8]),
611}
612
613impl LinkageData<'_> {
614    fn serialized_len(&self) -> usize {
615        match self {
616            LinkageData::MobileHandOver(m) => m.serialized_len(),
617            LinkageData::EventLinkage(e) => e.serialized_len(),
618            LinkageData::ExtendedEventLinkage(x) => x.serialized_len(),
619            LinkageData::None => 0,
620            LinkageData::Other(b) => b.len(),
621        }
622    }
623
624    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
625        match self {
626            LinkageData::MobileHandOver(m) => m.serialize_into(buf),
627            LinkageData::EventLinkage(e) => e.serialize_into(buf),
628            LinkageData::ExtendedEventLinkage(x) => x.serialize_into(buf),
629            LinkageData::None => Ok(0),
630            LinkageData::Other(b) => {
631                if buf.len() < b.len() {
632                    return Err(Error::OutputBufferTooSmall {
633                        need: b.len(),
634                        have: buf.len(),
635                    });
636                }
637                buf[..b.len()].copy_from_slice(b);
638                Ok(b.len())
639            }
640        }
641    }
642}
643
644fn parse_mobile_handover(bytes: &[u8], end: usize) -> Result<MobileHandOverInfo> {
645    if end < 1 {
646        return Err(Error::InvalidDescriptor {
647            tag: TAG,
648            reason: "mobile hand-over info needs at least flags byte",
649        });
650    }
651    let flags_byte = bytes[0];
652    let hand_over_type = HandOverType::from_u8((flags_byte & HANDOVER_TYPE_MASK) >> 4);
653    let origin_type = (flags_byte & ORIGIN_TYPE_MASK) != 0;
654    let mut pos = 1;
655    let network_id = if matches!(hand_over_type.to_u8(), 0x01..=0x03) {
656        if pos + 2 > end {
657            return Err(Error::InvalidDescriptor {
658                tag: TAG,
659                reason: "mobile hand-over info with gated network_id needs at least 3 bytes",
660            });
661        }
662        let nid = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
663        pos += 2;
664        Some(nid)
665    } else {
666        None
667    };
668    let initial_service_id = if !origin_type {
669        if pos + 2 > end {
670            return Err(Error::InvalidDescriptor {
671                tag: TAG,
672                reason: "mobile hand-over info with origin_type=NIT needs initial_service_id",
673            });
674        }
675        Some(u16::from_be_bytes([bytes[pos], bytes[pos + 1]]))
676    } else {
677        None
678    };
679    Ok(MobileHandOverInfo {
680        hand_over_type,
681        origin_type,
682        network_id,
683        initial_service_id,
684    })
685}
686
687fn parse_event_linkage(bytes: &[u8]) -> Result<EventLinkageInfo> {
688    if bytes.len() < 3 {
689        return Err(Error::InvalidDescriptor {
690            tag: TAG,
691            reason: "event linkage info needs 3 bytes",
692        });
693    }
694    let target_event_id = u16::from_be_bytes([bytes[0], bytes[1]]);
695    let target_listed = (bytes[2] & TARGET_LISTED_MASK) != 0;
696    let event_simulcast = (bytes[2] & EVENT_SIMULCAST_MASK) != 0;
697    Ok(EventLinkageInfo {
698        target_event_id,
699        target_listed,
700        event_simulcast,
701    })
702}
703
704fn parse_extended_event_linkage(bytes: &[u8]) -> Result<ExtendedEventLinkageInfo> {
705    if bytes.is_empty() {
706        return Err(Error::InvalidDescriptor {
707            tag: TAG,
708            reason: "extended event linkage info needs at least loop_length byte",
709        });
710    }
711    let loop_length = bytes[0] as usize;
712    let loop_end = 1 + loop_length;
713    if bytes.len() < loop_end {
714        return Err(Error::BufferTooShort {
715            need: loop_end,
716            have: bytes.len(),
717            what: "extended event linkage info loop",
718        });
719    }
720    let mut entries = Vec::new();
721    let mut pos = 1;
722    let read_u16 = |p: &mut usize| -> Result<u16> {
723        if *p + 2 > loop_end {
724            return Err(Error::InvalidDescriptor {
725                tag: TAG,
726                reason: "extended event linkage entry truncated (need u16)",
727            });
728        }
729        let v = u16::from_be_bytes([bytes[*p], bytes[*p + 1]]);
730        *p += 2;
731        Ok(v)
732    };
733    while pos < loop_end {
734        let target_event_id = read_u16(&mut pos)?;
735        if pos >= loop_end {
736            return Err(Error::InvalidDescriptor {
737                tag: TAG,
738                reason: "extended event linkage entry truncated (need flags byte)",
739            });
740        }
741        let fb = bytes[pos];
742        pos += 1;
743        let target_listed = (fb & EXT_TARGET_LISTED_MASK) != 0;
744        let event_simulcast = (fb & EXT_EVENT_SIMULCAST_MASK) != 0;
745        let link_type = LinkType::from_u8((fb & EXT_LINK_TYPE_MASK) >> 4);
746        let target_id_type = TargetIdType::from_u8((fb & EXT_TARGET_ID_TYPE_MASK) >> 2);
747        let original_network_id_flag = (fb & EXT_ONID_FLAG_MASK) != 0;
748        let service_id_flag = (fb & EXT_SID_FLAG_MASK) != 0;
749
750        let target_id = if target_id_type == TargetIdType::UseUserDefinedId {
751            let user_defined_id = read_u16(&mut pos)?;
752            TargetId::UserDefined { user_defined_id }
753        } else {
754            let target_transport_stream_id =
755                if target_id_type == TargetIdType::UseTargetTransportStreamId {
756                    Some(read_u16(&mut pos)?)
757                } else {
758                    None
759                };
760            let target_original_network_id = if original_network_id_flag {
761                Some(read_u16(&mut pos)?)
762            } else {
763                None
764            };
765            let target_service_id = if service_id_flag {
766                Some(read_u16(&mut pos)?)
767            } else {
768                None
769            };
770            TargetId::Dvb {
771                target_id_type,
772                target_transport_stream_id,
773                target_original_network_id,
774                target_service_id,
775            }
776        };
777        entries.push(ExtendedEventLinkageEntry {
778            target_event_id,
779            target_listed,
780            event_simulcast,
781            link_type,
782            target_id_type,
783            original_network_id_flag,
784            service_id_flag,
785            target_id,
786        });
787    }
788    Ok(ExtendedEventLinkageInfo { entries })
789}
790
791/// Linkage Descriptor.
792#[derive(Debug, Clone, PartialEq, Eq)]
793#[cfg_attr(feature = "serde", derive(serde::Serialize))]
794#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
795pub struct LinkageDescriptor<'a> {
796    /// transport_stream_id of the linked-to TS.
797    pub transport_stream_id: u16,
798    /// original_network_id of the linked-to TS.
799    pub original_network_id: u16,
800    /// service_id of the linked-to service (0 if linkage is at the network or
801    /// bouquet level).
802    pub service_id: u16,
803    /// linkage_type (Table 60).
804    pub linkage_type: LinkageType,
805    /// Linkage-type-conditional inner structure.
806    pub linkage_data: LinkageData<'a>,
807    /// Trailing `private_data_byte` run — bytes after the conditional block
808    /// (if any).  Empty when `linkage_data` is `Other`.
809    pub private_data: &'a [u8],
810}
811
812const LINKAGE_TYPES_WITH_OTHER: &[u8] = &[0x09, 0x0A, 0x0B, 0x0C, 0x20, 0x21];
813
814impl<'a> Parse<'a> for LinkageDescriptor<'a> {
815    type Error = crate::error::Error;
816    fn parse(bytes: &'a [u8]) -> Result<Self> {
817        let body = descriptor_body(
818            bytes,
819            TAG,
820            "LinkageDescriptor",
821            "unexpected tag for linkage_descriptor",
822        )?;
823        if body.len() < FIXED_FIELDS_LEN {
824            return Err(Error::InvalidDescriptor {
825                tag: TAG,
826                reason: "linkage_descriptor body shorter than minimum 7 bytes",
827            });
828        }
829        let transport_stream_id = u16::from_be_bytes([body[0], body[1]]);
830        let original_network_id = u16::from_be_bytes([body[2], body[3]]);
831        let service_id = u16::from_be_bytes([body[4], body[5]]);
832        let linkage_type_raw = body[6];
833        let linkage_type = LinkageType::from_u8(linkage_type_raw);
834        let tail = &body[FIXED_FIELDS_LEN..];
835        let tail_len = tail.len();
836
837        let (linkage_data, private_data) = match linkage_type_raw {
838            0x08 => {
839                let info = parse_mobile_handover(tail, tail_len)?;
840                let consumed = info.serialized_len();
841                (LinkageData::MobileHandOver(info), &tail[consumed..])
842            }
843            0x0D => {
844                let info = parse_event_linkage(tail)?;
845                let consumed = EventLinkageInfo::SERIALIZED_LEN;
846                (LinkageData::EventLinkage(info), &tail[consumed..])
847            }
848            0x0E..=0x1F => {
849                let info = parse_extended_event_linkage(tail)?;
850                let consumed = info.serialized_len();
851                (LinkageData::ExtendedEventLinkage(info), &tail[consumed..])
852            }
853            lt if LINKAGE_TYPES_WITH_OTHER.contains(&lt) || (0x80..=0xFE).contains(&lt) => {
854                (LinkageData::Other(tail), &[] as &[u8])
855            }
856            _ => (LinkageData::None, tail),
857        };
858        Ok(Self {
859            transport_stream_id,
860            original_network_id,
861            service_id,
862            linkage_type,
863            linkage_data,
864            private_data,
865        })
866    }
867}
868
869impl Serialize for LinkageDescriptor<'_> {
870    type Error = crate::error::Error;
871    fn serialized_len(&self) -> usize {
872        HEADER_LEN + FIXED_FIELDS_LEN + self.linkage_data.serialized_len() + self.private_data.len()
873    }
874
875    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
876        let len = self.serialized_len();
877        if buf.len() < len {
878            return Err(Error::OutputBufferTooSmall {
879                need: len,
880                have: buf.len(),
881            });
882        }
883        buf[0] = TAG;
884        buf[1] = (len - HEADER_LEN) as u8;
885        let bs = HEADER_LEN;
886        buf[bs..bs + 2].copy_from_slice(&self.transport_stream_id.to_be_bytes());
887        buf[bs + 2..bs + 4].copy_from_slice(&self.original_network_id.to_be_bytes());
888        buf[bs + 4..bs + 6].copy_from_slice(&self.service_id.to_be_bytes());
889        buf[bs + 6] = self.linkage_type.to_u8();
890        let ld_start = bs + FIXED_FIELDS_LEN;
891        let ld_written = self.linkage_data.serialize_into(&mut buf[ld_start..])?;
892        let pd_start = ld_start + ld_written;
893        if !self.private_data.is_empty() {
894            buf[pd_start..pd_start + self.private_data.len()].copy_from_slice(self.private_data);
895        }
896        Ok(len)
897    }
898}
899impl<'a> crate::traits::DescriptorDef<'a> for LinkageDescriptor<'a> {
900    const TAG: u8 = TAG;
901    const NAME: &'static str = "LINKAGE";
902}
903
904#[cfg(test)]
905mod tests {
906    use super::*;
907
908    #[test]
909    fn parse_extracts_tsid_onid_sid() {
910        let bytes = [
911            TAG, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05, 0xAA, 0xBB,
912        ];
913        let d = LinkageDescriptor::parse(&bytes).unwrap();
914        assert_eq!(d.transport_stream_id, 0x0001);
915        assert_eq!(d.original_network_id, 0x0002);
916        assert_eq!(d.service_id, 0x0003);
917    }
918
919    #[test]
920    fn parse_extracts_linkage_type() {
921        let bytes = [TAG, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x06];
922        let d = LinkageDescriptor::parse(&bytes).unwrap();
923        assert_eq!(d.linkage_type, LinkageType::DataBroadcastService);
924    }
925
926    #[test]
927    fn parse_none_type_preserves_private_data() {
928        let bytes = [
929            TAG, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05, 0xAA, 0xBB, 0xCC,
930        ];
931        let d = LinkageDescriptor::parse(&bytes).unwrap();
932        assert!(matches!(d.linkage_data, LinkageData::None));
933        assert_eq!(d.private_data, &[0xAA, 0xBB, 0xCC]);
934    }
935
936    #[test]
937    fn parse_accepts_empty_private_data() {
938        let bytes = [TAG, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05];
939        let d = LinkageDescriptor::parse(&bytes).unwrap();
940        assert!(d.private_data.is_empty());
941    }
942
943    #[test]
944    fn parse_mobile_handover_with_initial_sid() {
945        let bytes = [
946            TAG, 0x0E, // length 14 = 7 fixed + 5 handover + 2 priv
947            0x00, 0x01, // ts_id
948            0x00, 0x02, // onid
949            0x00, 0x03, // sid
950            0x08, // linkage_type = mobile hand-over
951            0x12, // hand_over_type=1, rfu=110, origin_type=0 (NIT)
952            0x00, 0x10, // network_id
953            0x00, 0x20, // initial_service_id
954            0xDE, 0xAD, // private_data
955        ];
956        let d = LinkageDescriptor::parse(&bytes).unwrap();
957        assert_eq!(d.linkage_type, LinkageType::MobileHandOver);
958        match &d.linkage_data {
959            LinkageData::MobileHandOver(m) => {
960                assert_eq!(
961                    m.hand_over_type,
962                    HandOverType::DvbIdenticalNeighbouringCountry
963                );
964                assert!(!m.origin_type);
965                assert_eq!(m.network_id, Some(0x0010));
966                assert_eq!(m.initial_service_id, Some(0x0020));
967            }
968            other => panic!("expected MobileHandOver, got {other:?}"),
969        }
970        assert_eq!(d.private_data, &[0xDE, 0xAD]);
971    }
972
973    #[test]
974    fn parse_mobile_handover_sdt_no_initial_sid() {
975        let bytes = [
976            TAG, 0x0C, // length 12 = 7 fixed + 3 handover + 2 priv
977            0x00, 0x01, // ts_id
978            0x00, 0x02, // onid
979            0x00, 0x03, // sid
980            0x08, // linkage_type = mobile hand-over
981            0x2F, // hand_over_type=2, rfu=111, origin_type=1 (SDT)
982            0x00, 0x10, // network_id
983            0xCA, 0xFE, // private_data
984        ];
985        let d = LinkageDescriptor::parse(&bytes).unwrap();
986        match &d.linkage_data {
987            LinkageData::MobileHandOver(m) => {
988                assert_eq!(m.hand_over_type, HandOverType::DvbLocalVariation);
989                assert!(m.origin_type);
990                assert_eq!(m.network_id, Some(0x0010));
991                assert_eq!(m.initial_service_id, None);
992            }
993            other => panic!("expected MobileHandOver, got {other:?}"),
994        }
995        assert_eq!(d.private_data, &[0xCA, 0xFE]);
996    }
997
998    #[test]
999    fn parse_event_linkage() {
1000        let bytes = [
1001            TAG, 0x0C, // length 12 = 7 fixed + 3 event + 2 priv
1002            0x00, 0x01, // ts_id
1003            0x00, 0x02, // onid
1004            0x00, 0x03, // sid
1005            0x0D, // linkage_type = event linkage
1006            0xAB, 0xCD, // target_event_id
1007            0xC0, // target_listed=1, event_simulcast=1, rfu=000000
1008            0xBE, 0xEF, // private_data
1009        ];
1010        let d = LinkageDescriptor::parse(&bytes).unwrap();
1011        match &d.linkage_data {
1012            LinkageData::EventLinkage(e) => {
1013                assert_eq!(e.target_event_id, 0xABCD);
1014                assert!(e.target_listed);
1015                assert!(e.event_simulcast);
1016            }
1017            other => panic!("expected EventLinkage, got {other:?}"),
1018        }
1019        assert_eq!(d.private_data, &[0xBE, 0xEF]);
1020    }
1021
1022    #[test]
1023    fn parse_extended_event_linkage_user_defined() {
1024        let bytes = [
1025            TAG, 0x0E, // length 14 = 7 fixed + 6 ext + 1 priv
1026            0x00, 0x01, // ts_id
1027            0x00, 0x02, // onid
1028            0x00, 0x03, // sid
1029            0x0E, // linkage_type = extended event linkage
1030            0x05, // loop_length = 5
1031            0x12, 0x34, // target_event_id
1032            0xCC, // target_listed=1, event_simulcast=1, link_type=0, target_id_type=3, flags=0
1033            0x56, 0x78, // user_defined_id
1034            0xCC, // private_data
1035        ];
1036        let d = LinkageDescriptor::parse(&bytes).unwrap();
1037        match &d.linkage_data {
1038            LinkageData::ExtendedEventLinkage(x) => {
1039                assert_eq!(x.entries.len(), 1);
1040                let e = &x.entries[0];
1041                assert_eq!(e.target_event_id, 0x1234);
1042                assert!(e.target_listed);
1043                assert!(e.event_simulcast);
1044                assert_eq!(e.link_type, LinkType::SdOrUhd);
1045                assert_eq!(e.target_id_type, TargetIdType::UseUserDefinedId);
1046                assert_eq!(
1047                    e.target_id,
1048                    TargetId::UserDefined {
1049                        user_defined_id: 0x5678
1050                    }
1051                );
1052            }
1053            other => panic!("expected ExtendedEventLinkage, got {other:?}"),
1054        }
1055        assert_eq!(d.private_data, &[0xCC]);
1056    }
1057
1058    #[test]
1059    fn parse_extended_event_linkage_dvb_target() {
1060        let bytes = [
1061            TAG, 0x0F, // length 15 = 7 fixed + 8 ext
1062            0x00, 0x01, // ts_id
1063            0x00, 0x02, // onid
1064            0x00, 0x03, // sid
1065            0x0F, // linkage_type
1066            0x07, // loop_length = 7
1067            0xAA, 0xBB, // target_event_id
1068            0x26, // target_listed=0, event_simulcast=0, link_type=2, target_id_type=1, onid_flag=1, sid_flag=0
1069            0x00, 0x11, // target_transport_stream_id (target_id_type=1)
1070            0x00, 0x22, // target_original_network_id (onid_flag=1)
1071        ];
1072        let d = LinkageDescriptor::parse(&bytes).unwrap();
1073        match &d.linkage_data {
1074            LinkageData::ExtendedEventLinkage(x) => {
1075                assert_eq!(x.entries.len(), 1);
1076                let e = &x.entries[0];
1077                assert_eq!(e.target_id_type, TargetIdType::UseTargetTransportStreamId);
1078                assert!(e.original_network_id_flag);
1079                assert!(!e.service_id_flag);
1080                assert_eq!(
1081                    e.target_id,
1082                    TargetId::Dvb {
1083                        target_id_type: TargetIdType::UseTargetTransportStreamId,
1084                        target_transport_stream_id: Some(0x0011),
1085                        target_original_network_id: Some(0x0022),
1086                        target_service_id: None,
1087                    }
1088                );
1089            }
1090            other => panic!("expected ExtendedEventLinkage, got {other:?}"),
1091        }
1092        assert_eq!(d.private_data, &[] as &[u8]);
1093    }
1094
1095    #[test]
1096    fn parse_other_type_captures_raw_tail() {
1097        let bytes = [
1098            TAG, 0x0A, // length 10
1099            0x00, 0x01, // ts_id
1100            0x00, 0x02, // onid
1101            0x00, 0x03, // sid
1102            0x0B, // linkage_type = IP/MAC notification (EN 301 192)
1103            0xAA, 0xBB, 0xCC, // raw tail
1104        ];
1105        let d = LinkageDescriptor::parse(&bytes).unwrap();
1106        match &d.linkage_data {
1107            LinkageData::Other(b) => assert_eq!(*b, &[0xAA, 0xBB, 0xCC]),
1108            other => panic!("expected Other, got {other:?}"),
1109        }
1110        assert!(d.private_data.is_empty());
1111    }
1112
1113    #[test]
1114    fn parse_user_defined_type_is_other() {
1115        let bytes = [
1116            TAG, 0x09, // length 9
1117            0x00, 0x01, // ts_id
1118            0x00, 0x02, // onid
1119            0x00, 0x03, // sid
1120            0x90, // user-defined linkage_type
1121            0xFF, 0xFE, // raw tail
1122        ];
1123        let d = LinkageDescriptor::parse(&bytes).unwrap();
1124        assert!(matches!(d.linkage_data, LinkageData::Other(_)));
1125    }
1126
1127    #[test]
1128    fn parse_rejects_wrong_tag() {
1129        let err = LinkageDescriptor::parse(&[0x4B, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05])
1130            .unwrap_err();
1131        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x4B, .. }));
1132    }
1133
1134    #[test]
1135    fn parse_rejects_body_shorter_than_seven() {
1136        let bytes = [TAG, 0x05, 0x00, 0x01, 0x00, 0x02, 0x00];
1137        let err = LinkageDescriptor::parse(&bytes).unwrap_err();
1138        assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
1139    }
1140
1141    #[test]
1142    fn parse_rejects_truncated_buffer() {
1143        let err = LinkageDescriptor::parse(&[TAG]).unwrap_err();
1144        assert!(matches!(err, Error::BufferTooShort { .. }));
1145    }
1146
1147    #[test]
1148    fn parse_rejects_truncated_mobile_handover() {
1149        let bytes = [
1150            TAG, 0x08, // length 8
1151            0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x08, // linkage_type = mobile hand-over
1152            0x10, // flags byte (origin_type=0, needs initial_service_id)
1153            0x00, 0x10, // network_id
1154        ];
1155        let err = LinkageDescriptor::parse(&bytes).unwrap_err();
1156        assert!(
1157            matches!(err, Error::InvalidDescriptor { .. }),
1158            "expected InvalidDescriptor for truncated mobile hand-over, got {err:?}"
1159        );
1160    }
1161
1162    #[test]
1163    fn serialize_round_trip_no_linkage_data() {
1164        let d = LinkageDescriptor {
1165            transport_stream_id: 0x1234,
1166            original_network_id: 0x5678,
1167            service_id: 0xABCD,
1168            linkage_type: LinkageType::EpgService,
1169            linkage_data: LinkageData::None,
1170            private_data: &[],
1171        };
1172        let mut buf = vec![0u8; d.serialized_len()];
1173        d.serialize_into(&mut buf).unwrap();
1174        let re = LinkageDescriptor::parse(&buf).unwrap();
1175        assert_eq!(d, re);
1176    }
1177
1178    #[test]
1179    fn serialize_round_trip_with_private_data() {
1180        let d = LinkageDescriptor {
1181            transport_stream_id: 0x0001,
1182            original_network_id: 0x0002,
1183            service_id: 0x0003,
1184            linkage_type: LinkageType::ServiceReplacementService,
1185            linkage_data: LinkageData::None,
1186            private_data: &[0xDE, 0xAD, 0xBE, 0xEF],
1187        };
1188        let mut buf = vec![0u8; d.serialized_len()];
1189        d.serialize_into(&mut buf).unwrap();
1190        let re = LinkageDescriptor::parse(&buf).unwrap();
1191        assert_eq!(d, re);
1192    }
1193
1194    #[test]
1195    fn serialize_round_trip_mobile_handover() {
1196        let d = LinkageDescriptor {
1197            transport_stream_id: 0x0001,
1198            original_network_id: 0x0002,
1199            service_id: 0x0003,
1200            linkage_type: LinkageType::MobileHandOver,
1201            linkage_data: LinkageData::MobileHandOver(MobileHandOverInfo {
1202                hand_over_type: HandOverType::DvbAssociatedService,
1203                origin_type: false,
1204                network_id: Some(0x0044),
1205                initial_service_id: Some(0x0055),
1206            }),
1207            private_data: &[0xFF],
1208        };
1209        let mut buf = vec![0u8; d.serialized_len()];
1210        d.serialize_into(&mut buf).unwrap();
1211        let re = LinkageDescriptor::parse(&buf).unwrap();
1212        assert_eq!(d, re);
1213    }
1214
1215    #[test]
1216    fn serialize_round_trip_event_linkage() {
1217        let d = LinkageDescriptor {
1218            transport_stream_id: 0x0001,
1219            original_network_id: 0x0002,
1220            service_id: 0x0003,
1221            linkage_type: LinkageType::EventLinkage,
1222            linkage_data: LinkageData::EventLinkage(EventLinkageInfo {
1223                target_event_id: 0x1234,
1224                target_listed: true,
1225                event_simulcast: false,
1226            }),
1227            private_data: &[],
1228        };
1229        let mut buf = vec![0u8; d.serialized_len()];
1230        d.serialize_into(&mut buf).unwrap();
1231        let re = LinkageDescriptor::parse(&buf).unwrap();
1232        assert_eq!(d, re);
1233    }
1234
1235    #[test]
1236    fn serialize_round_trip_extended_event_linkage() {
1237        let d = LinkageDescriptor {
1238            transport_stream_id: 0x0001,
1239            original_network_id: 0x0002,
1240            service_id: 0x0003,
1241            linkage_type: LinkageType::ExtendedEventLinkage(0x0E),
1242            linkage_data: LinkageData::ExtendedEventLinkage(ExtendedEventLinkageInfo {
1243                entries: vec![ExtendedEventLinkageEntry {
1244                    target_event_id: 0xAAAA,
1245                    target_listed: true,
1246                    event_simulcast: true,
1247                    link_type: LinkType::HdOrServiceFrameCompatible,
1248                    target_id_type: TargetIdType::UseTargetTransportStreamId,
1249                    original_network_id_flag: true,
1250                    service_id_flag: true,
1251                    target_id: TargetId::Dvb {
1252                        target_id_type: TargetIdType::UseTargetTransportStreamId,
1253                        target_transport_stream_id: Some(0x1111),
1254                        target_original_network_id: Some(0x2222),
1255                        target_service_id: Some(0x3333),
1256                    },
1257                }],
1258            }),
1259            private_data: &[0xCC],
1260        };
1261        let mut buf = vec![0u8; d.serialized_len()];
1262        d.serialize_into(&mut buf).unwrap();
1263        let re = LinkageDescriptor::parse(&buf).unwrap();
1264        assert_eq!(d, re);
1265    }
1266
1267    #[test]
1268    fn serialize_round_trip_other() {
1269        let raw = [0xAA, 0xBB, 0xCC];
1270        let d = LinkageDescriptor {
1271            transport_stream_id: 0x0001,
1272            original_network_id: 0x0002,
1273            service_id: 0x0003,
1274            linkage_type: LinkageType::IpMacNotificationService,
1275            linkage_data: LinkageData::Other(&raw),
1276            private_data: &[],
1277        };
1278        let mut buf = vec![0u8; d.serialized_len()];
1279        d.serialize_into(&mut buf).unwrap();
1280        let re = LinkageDescriptor::parse(&buf).unwrap();
1281        assert_eq!(d, re);
1282    }
1283
1284    #[test]
1285    fn serialize_reserved_bits_are_set() {
1286        let d = LinkageDescriptor {
1287            transport_stream_id: 0x0001,
1288            original_network_id: 0x0002,
1289            service_id: 0x0003,
1290            linkage_type: LinkageType::EventLinkage,
1291            linkage_data: LinkageData::EventLinkage(EventLinkageInfo {
1292                target_event_id: 0x0000,
1293                target_listed: false,
1294                event_simulcast: false,
1295            }),
1296            private_data: &[],
1297        };
1298        let mut buf = vec![0u8; d.serialized_len()];
1299        d.serialize_into(&mut buf).unwrap();
1300        assert_eq!(buf[11] & RESERVED_EVENT_MASK, RESERVED_EVENT_MASK);
1301
1302        let d2 = LinkageDescriptor {
1303            transport_stream_id: 0x0001,
1304            original_network_id: 0x0002,
1305            service_id: 0x0003,
1306            linkage_type: LinkageType::MobileHandOver,
1307            linkage_data: LinkageData::MobileHandOver(MobileHandOverInfo {
1308                hand_over_type: HandOverType::Reserved(0),
1309                origin_type: true,
1310                network_id: None,
1311                initial_service_id: None,
1312            }),
1313            private_data: &[],
1314        };
1315        let mut buf2 = vec![0u8; d2.serialized_len()];
1316        d2.serialize_into(&mut buf2).unwrap();
1317        assert_eq!(buf2[9] & RESERVED_HANDOVER_MASK, RESERVED_HANDOVER_MASK);
1318    }
1319
1320    #[test]
1321    fn parse_mobile_handover_type4_no_network_id() {
1322        let bytes = [
1323            TAG, 0x0A, // length 10 = 7 fixed + flags(1) + initial_sid(2)
1324            0x00, 0x01, // ts_id
1325            0x00, 0x02, // onid
1326            0x00, 0x03, // sid
1327            0x08, // linkage_type = mobile hand-over
1328            0x4E, // hand_over_type=4, rfu=111, origin_type=0 (NIT)
1329            0x00, 0x20, // initial_service_id
1330        ];
1331        let d = LinkageDescriptor::parse(&bytes).unwrap();
1332        match &d.linkage_data {
1333            LinkageData::MobileHandOver(m) => {
1334                assert_eq!(m.hand_over_type, HandOverType::Reserved(4));
1335                assert!(!m.origin_type);
1336                assert_eq!(m.network_id, None);
1337                assert_eq!(m.initial_service_id, Some(0x0020));
1338            }
1339            other => panic!("expected MobileHandOver, got {other:?}"),
1340        }
1341    }
1342
1343    #[test]
1344    fn parse_mobile_handover_type1_network_id_present() {
1345        let bytes = [
1346            TAG, 0x0C, // length 12 = 7 fixed + 3 handover + 2 priv
1347            0x00, 0x01, // ts_id
1348            0x00, 0x02, // onid
1349            0x00, 0x03, // sid
1350            0x08, // linkage_type = mobile hand-over
1351            0x1F, // hand_over_type=1, rfu=111, origin_type=1 (SDT)
1352            0x00, 0x10, // network_id
1353            0xCA, 0xFE, // private_data
1354        ];
1355        let d = LinkageDescriptor::parse(&bytes).unwrap();
1356        match &d.linkage_data {
1357            LinkageData::MobileHandOver(m) => {
1358                assert_eq!(
1359                    m.hand_over_type,
1360                    HandOverType::DvbIdenticalNeighbouringCountry
1361                );
1362                assert!(m.origin_type);
1363                assert_eq!(m.network_id, Some(0x0010));
1364                assert_eq!(m.initial_service_id, None);
1365            }
1366            other => panic!("expected MobileHandOver, got {other:?}"),
1367        }
1368        assert_eq!(d.private_data, &[0xCA, 0xFE]);
1369    }
1370
1371    #[test]
1372    fn linkage_type_full_range_round_trip() {
1373        for b in 0..=0xFF_u8 {
1374            let lt = LinkageType::from_u8(b);
1375            assert_eq!(lt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
1376        }
1377    }
1378
1379    #[test]
1380    fn hand_over_type_full_range_round_trip() {
1381        for b in 0..=0xFF_u8 {
1382            let ht = HandOverType::from_u8(b);
1383            assert_eq!(ht.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
1384        }
1385    }
1386
1387    #[test]
1388    fn link_type_full_range_round_trip() {
1389        for b in 0..=0xFF_u8 {
1390            let lt = LinkType::from_u8(b);
1391            assert_eq!(lt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
1392        }
1393    }
1394
1395    #[test]
1396    fn target_id_type_full_range_round_trip() {
1397        for b in 0..=0xFF_u8 {
1398            let tt = TargetIdType::from_u8(b);
1399            assert_eq!(tt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
1400        }
1401    }
1402}