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