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_bytes, _) = bytes
665            .get(pos..)
666            .and_then(|s| s.split_first_chunk::<2>())
667            .ok_or(Error::InvalidDescriptor {
668                tag: TAG,
669                reason: "mobile hand-over info with gated network_id needs at least 3 bytes",
670            })?;
671        let nid = u16::from_be_bytes(*nid_bytes);
672        pos += 2;
673        Some(nid)
674    } else {
675        None
676    };
677    let initial_service_id = if !origin_type {
678        if pos + 2 > end {
679            return Err(Error::InvalidDescriptor {
680                tag: TAG,
681                reason: "mobile hand-over info with origin_type=NIT needs initial_service_id",
682            });
683        }
684        let (sid_bytes, _) = bytes
685            .get(pos..)
686            .and_then(|s| s.split_first_chunk::<2>())
687            .ok_or(Error::InvalidDescriptor {
688                tag: TAG,
689                reason: "mobile hand-over info with origin_type=NIT needs initial_service_id",
690            })?;
691        Some(u16::from_be_bytes(*sid_bytes))
692    } else {
693        None
694    };
695    Ok(MobileHandOverInfo {
696        hand_over_type,
697        origin_type,
698        network_id,
699        initial_service_id,
700    })
701}
702
703fn parse_event_linkage(bytes: &[u8]) -> Result<EventLinkageInfo> {
704    let (hdr, _) = bytes
705        .split_first_chunk::<3>()
706        .ok_or(Error::InvalidDescriptor {
707            tag: TAG,
708            reason: "event linkage info needs 3 bytes",
709        })?;
710    let target_event_id = u16::from_be_bytes([hdr[0], hdr[1]]);
711    let target_listed = (hdr[2] & TARGET_LISTED_MASK) != 0;
712    let event_simulcast = (hdr[2] & EVENT_SIMULCAST_MASK) != 0;
713    Ok(EventLinkageInfo {
714        target_event_id,
715        target_listed,
716        event_simulcast,
717    })
718}
719
720fn parse_extended_event_linkage(bytes: &[u8]) -> Result<ExtendedEventLinkageInfo> {
721    if bytes.is_empty() {
722        return Err(Error::InvalidDescriptor {
723            tag: TAG,
724            reason: "extended event linkage info needs at least loop_length byte",
725        });
726    }
727    let loop_length = bytes[0] as usize;
728    let loop_end = 1 + loop_length;
729    if bytes.len() < loop_end {
730        return Err(Error::BufferTooShort {
731            need: loop_end,
732            have: bytes.len(),
733            what: "extended event linkage info loop",
734        });
735    }
736    let mut entries = Vec::new();
737    let mut pos = 1;
738    let read_u16 = |p: &mut usize| -> Result<u16> {
739        if *p + 2 > loop_end {
740            return Err(Error::InvalidDescriptor {
741                tag: TAG,
742                reason: "extended event linkage entry truncated (need u16)",
743            });
744        }
745        let (b, _) = bytes[*p..]
746            .split_first_chunk::<2>()
747            .ok_or(Error::InvalidDescriptor {
748                tag: TAG,
749                reason: "extended event linkage entry truncated (need u16)",
750            })?;
751        let v = u16::from_be_bytes(*b);
752        *p += 2;
753        Ok(v)
754    };
755    while pos < loop_end {
756        let target_event_id = read_u16(&mut pos)?;
757        if pos >= loop_end {
758            return Err(Error::InvalidDescriptor {
759                tag: TAG,
760                reason: "extended event linkage entry truncated (need flags byte)",
761            });
762        }
763        let fb = bytes[pos];
764        pos += 1;
765        let target_listed = (fb & EXT_TARGET_LISTED_MASK) != 0;
766        let event_simulcast = (fb & EXT_EVENT_SIMULCAST_MASK) != 0;
767        let link_type = LinkType::from_u8((fb & EXT_LINK_TYPE_MASK) >> 4);
768        let target_id_type = TargetIdType::from_u8((fb & EXT_TARGET_ID_TYPE_MASK) >> 2);
769        let original_network_id_flag = (fb & EXT_ONID_FLAG_MASK) != 0;
770        let service_id_flag = (fb & EXT_SID_FLAG_MASK) != 0;
771
772        let target_id = if target_id_type == TargetIdType::UseUserDefinedId {
773            let user_defined_id = read_u16(&mut pos)?;
774            TargetId::UserDefined { user_defined_id }
775        } else {
776            let target_transport_stream_id =
777                if target_id_type == TargetIdType::UseTargetTransportStreamId {
778                    Some(read_u16(&mut pos)?)
779                } else {
780                    None
781                };
782            let target_original_network_id = if original_network_id_flag {
783                Some(read_u16(&mut pos)?)
784            } else {
785                None
786            };
787            let target_service_id = if service_id_flag {
788                Some(read_u16(&mut pos)?)
789            } else {
790                None
791            };
792            TargetId::Dvb {
793                target_id_type,
794                target_transport_stream_id,
795                target_original_network_id,
796                target_service_id,
797            }
798        };
799        entries.push(ExtendedEventLinkageEntry {
800            target_event_id,
801            target_listed,
802            event_simulcast,
803            link_type,
804            target_id_type,
805            original_network_id_flag,
806            service_id_flag,
807            target_id,
808        });
809    }
810    Ok(ExtendedEventLinkageInfo { entries })
811}
812
813/// Linkage Descriptor.
814#[derive(Debug, Clone, PartialEq, Eq)]
815#[cfg_attr(feature = "serde", derive(serde::Serialize))]
816#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
817pub struct LinkageDescriptor<'a> {
818    /// transport_stream_id of the linked-to TS.
819    pub transport_stream_id: u16,
820    /// original_network_id of the linked-to TS.
821    pub original_network_id: u16,
822    /// service_id of the linked-to service (0 if linkage is at the network or
823    /// bouquet level).
824    pub service_id: u16,
825    /// linkage_type (Table 60).
826    pub linkage_type: LinkageType,
827    /// Linkage-type-conditional inner structure.
828    pub linkage_data: LinkageData<'a>,
829    /// Trailing `private_data_byte` run — bytes after the conditional block
830    /// (if any).  Empty when `linkage_data` is `Other`.
831    pub private_data: &'a [u8],
832}
833
834const LINKAGE_TYPES_WITH_OTHER: &[u8] = &[0x09, 0x0A, 0x0B, 0x0C, 0x20, 0x21];
835
836impl<'a> Parse<'a> for LinkageDescriptor<'a> {
837    type Error = crate::error::Error;
838    fn parse(bytes: &'a [u8]) -> Result<Self> {
839        let body = descriptor_body(
840            bytes,
841            TAG,
842            "LinkageDescriptor",
843            "unexpected tag for linkage_descriptor",
844        )?;
845        let (fixed, tail) =
846            body.split_first_chunk::<FIXED_FIELDS_LEN>()
847                .ok_or(Error::InvalidDescriptor {
848                    tag: TAG,
849                    reason: "linkage_descriptor body shorter than minimum 7 bytes",
850                })?;
851        let transport_stream_id = u16::from_be_bytes([fixed[0], fixed[1]]);
852        let original_network_id = u16::from_be_bytes([fixed[2], fixed[3]]);
853        let service_id = u16::from_be_bytes([fixed[4], fixed[5]]);
854        let linkage_type_raw = fixed[6];
855        let linkage_type = LinkageType::from_u8(linkage_type_raw);
856        let tail_len = tail.len();
857
858        let (linkage_data, private_data) = match linkage_type_raw {
859            0x08 => {
860                let info = parse_mobile_handover(tail, tail_len)?;
861                let consumed = info.serialized_len();
862                (LinkageData::MobileHandOver(info), &tail[consumed..])
863            }
864            0x0D => {
865                let info = parse_event_linkage(tail)?;
866                let consumed = EventLinkageInfo::SERIALIZED_LEN;
867                (LinkageData::EventLinkage(info), &tail[consumed..])
868            }
869            0x0E..=0x1F => {
870                let info = parse_extended_event_linkage(tail)?;
871                let consumed = info.serialized_len();
872                (LinkageData::ExtendedEventLinkage(info), &tail[consumed..])
873            }
874            lt if LINKAGE_TYPES_WITH_OTHER.contains(&lt) || (0x80..=0xFE).contains(&lt) => {
875                (LinkageData::Other(tail), &[] as &[u8])
876            }
877            _ => (LinkageData::None, tail),
878        };
879        Ok(Self {
880            transport_stream_id,
881            original_network_id,
882            service_id,
883            linkage_type,
884            linkage_data,
885            private_data,
886        })
887    }
888}
889
890impl Serialize for LinkageDescriptor<'_> {
891    type Error = crate::error::Error;
892    fn serialized_len(&self) -> usize {
893        HEADER_LEN + FIXED_FIELDS_LEN + self.linkage_data.serialized_len() + self.private_data.len()
894    }
895
896    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
897        let len = self.serialized_len();
898        if buf.len() < len {
899            return Err(Error::OutputBufferTooSmall {
900                need: len,
901                have: buf.len(),
902            });
903        }
904        buf[0] = TAG;
905        buf[1] = (len - HEADER_LEN) as u8;
906        let bs = HEADER_LEN;
907        buf[bs..bs + 2].copy_from_slice(&self.transport_stream_id.to_be_bytes());
908        buf[bs + 2..bs + 4].copy_from_slice(&self.original_network_id.to_be_bytes());
909        buf[bs + 4..bs + 6].copy_from_slice(&self.service_id.to_be_bytes());
910        buf[bs + 6] = self.linkage_type.to_u8();
911        let ld_start = bs + FIXED_FIELDS_LEN;
912        let ld_written = self.linkage_data.serialize_into(&mut buf[ld_start..])?;
913        let pd_start = ld_start + ld_written;
914        if !self.private_data.is_empty() {
915            buf[pd_start..pd_start + self.private_data.len()].copy_from_slice(self.private_data);
916        }
917        Ok(len)
918    }
919}
920impl<'a> crate::traits::DescriptorDef<'a> for LinkageDescriptor<'a> {
921    const TAG: u8 = TAG;
922    const NAME: &'static str = "LINKAGE";
923}
924
925#[cfg(test)]
926mod tests {
927    use super::*;
928
929    #[test]
930    fn parse_extracts_tsid_onid_sid() {
931        let bytes = [
932            TAG, 0x09, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05, 0xAA, 0xBB,
933        ];
934        let d = LinkageDescriptor::parse(&bytes).unwrap();
935        assert_eq!(d.transport_stream_id, 0x0001);
936        assert_eq!(d.original_network_id, 0x0002);
937        assert_eq!(d.service_id, 0x0003);
938    }
939
940    #[test]
941    fn parse_extracts_linkage_type() {
942        let bytes = [TAG, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x06];
943        let d = LinkageDescriptor::parse(&bytes).unwrap();
944        assert_eq!(d.linkage_type, LinkageType::DataBroadcastService);
945    }
946
947    #[test]
948    fn parse_none_type_preserves_private_data() {
949        let bytes = [
950            TAG, 0x0A, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05, 0xAA, 0xBB, 0xCC,
951        ];
952        let d = LinkageDescriptor::parse(&bytes).unwrap();
953        assert!(matches!(d.linkage_data, LinkageData::None));
954        assert_eq!(d.private_data, &[0xAA, 0xBB, 0xCC]);
955    }
956
957    #[test]
958    fn parse_accepts_empty_private_data() {
959        let bytes = [TAG, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05];
960        let d = LinkageDescriptor::parse(&bytes).unwrap();
961        assert!(d.private_data.is_empty());
962    }
963
964    #[test]
965    fn parse_mobile_handover_with_initial_sid() {
966        let bytes = [
967            TAG, 0x0E, // length 14 = 7 fixed + 5 handover + 2 priv
968            0x00, 0x01, // ts_id
969            0x00, 0x02, // onid
970            0x00, 0x03, // sid
971            0x08, // linkage_type = mobile hand-over
972            0x12, // hand_over_type=1, rfu=110, origin_type=0 (NIT)
973            0x00, 0x10, // network_id
974            0x00, 0x20, // initial_service_id
975            0xDE, 0xAD, // private_data
976        ];
977        let d = LinkageDescriptor::parse(&bytes).unwrap();
978        assert_eq!(d.linkage_type, LinkageType::MobileHandOver);
979        match &d.linkage_data {
980            LinkageData::MobileHandOver(m) => {
981                assert_eq!(
982                    m.hand_over_type,
983                    HandOverType::DvbIdenticalNeighbouringCountry
984                );
985                assert!(!m.origin_type);
986                assert_eq!(m.network_id, Some(0x0010));
987                assert_eq!(m.initial_service_id, Some(0x0020));
988            }
989            other => panic!("expected MobileHandOver, got {other:?}"),
990        }
991        assert_eq!(d.private_data, &[0xDE, 0xAD]);
992    }
993
994    #[test]
995    fn parse_mobile_handover_sdt_no_initial_sid() {
996        let bytes = [
997            TAG, 0x0C, // length 12 = 7 fixed + 3 handover + 2 priv
998            0x00, 0x01, // ts_id
999            0x00, 0x02, // onid
1000            0x00, 0x03, // sid
1001            0x08, // linkage_type = mobile hand-over
1002            0x2F, // hand_over_type=2, rfu=111, origin_type=1 (SDT)
1003            0x00, 0x10, // network_id
1004            0xCA, 0xFE, // private_data
1005        ];
1006        let d = LinkageDescriptor::parse(&bytes).unwrap();
1007        match &d.linkage_data {
1008            LinkageData::MobileHandOver(m) => {
1009                assert_eq!(m.hand_over_type, HandOverType::DvbLocalVariation);
1010                assert!(m.origin_type);
1011                assert_eq!(m.network_id, Some(0x0010));
1012                assert_eq!(m.initial_service_id, None);
1013            }
1014            other => panic!("expected MobileHandOver, got {other:?}"),
1015        }
1016        assert_eq!(d.private_data, &[0xCA, 0xFE]);
1017    }
1018
1019    #[test]
1020    fn parse_event_linkage() {
1021        let bytes = [
1022            TAG, 0x0C, // length 12 = 7 fixed + 3 event + 2 priv
1023            0x00, 0x01, // ts_id
1024            0x00, 0x02, // onid
1025            0x00, 0x03, // sid
1026            0x0D, // linkage_type = event linkage
1027            0xAB, 0xCD, // target_event_id
1028            0xC0, // target_listed=1, event_simulcast=1, rfu=000000
1029            0xBE, 0xEF, // private_data
1030        ];
1031        let d = LinkageDescriptor::parse(&bytes).unwrap();
1032        match &d.linkage_data {
1033            LinkageData::EventLinkage(e) => {
1034                assert_eq!(e.target_event_id, 0xABCD);
1035                assert!(e.target_listed);
1036                assert!(e.event_simulcast);
1037            }
1038            other => panic!("expected EventLinkage, got {other:?}"),
1039        }
1040        assert_eq!(d.private_data, &[0xBE, 0xEF]);
1041    }
1042
1043    #[test]
1044    fn parse_extended_event_linkage_user_defined() {
1045        let bytes = [
1046            TAG, 0x0E, // length 14 = 7 fixed + 6 ext + 1 priv
1047            0x00, 0x01, // ts_id
1048            0x00, 0x02, // onid
1049            0x00, 0x03, // sid
1050            0x0E, // linkage_type = extended event linkage
1051            0x05, // loop_length = 5
1052            0x12, 0x34, // target_event_id
1053            0xCC, // target_listed=1, event_simulcast=1, link_type=0, target_id_type=3, flags=0
1054            0x56, 0x78, // user_defined_id
1055            0xCC, // private_data
1056        ];
1057        let d = LinkageDescriptor::parse(&bytes).unwrap();
1058        match &d.linkage_data {
1059            LinkageData::ExtendedEventLinkage(x) => {
1060                assert_eq!(x.entries.len(), 1);
1061                let e = &x.entries[0];
1062                assert_eq!(e.target_event_id, 0x1234);
1063                assert!(e.target_listed);
1064                assert!(e.event_simulcast);
1065                assert_eq!(e.link_type, LinkType::SdOrUhd);
1066                assert_eq!(e.target_id_type, TargetIdType::UseUserDefinedId);
1067                assert_eq!(
1068                    e.target_id,
1069                    TargetId::UserDefined {
1070                        user_defined_id: 0x5678
1071                    }
1072                );
1073            }
1074            other => panic!("expected ExtendedEventLinkage, got {other:?}"),
1075        }
1076        assert_eq!(d.private_data, &[0xCC]);
1077    }
1078
1079    #[test]
1080    fn parse_extended_event_linkage_dvb_target() {
1081        let bytes = [
1082            TAG, 0x0F, // length 15 = 7 fixed + 8 ext
1083            0x00, 0x01, // ts_id
1084            0x00, 0x02, // onid
1085            0x00, 0x03, // sid
1086            0x0F, // linkage_type
1087            0x07, // loop_length = 7
1088            0xAA, 0xBB, // target_event_id
1089            0x26, // target_listed=0, event_simulcast=0, link_type=2, target_id_type=1, onid_flag=1, sid_flag=0
1090            0x00, 0x11, // target_transport_stream_id (target_id_type=1)
1091            0x00, 0x22, // target_original_network_id (onid_flag=1)
1092        ];
1093        let d = LinkageDescriptor::parse(&bytes).unwrap();
1094        match &d.linkage_data {
1095            LinkageData::ExtendedEventLinkage(x) => {
1096                assert_eq!(x.entries.len(), 1);
1097                let e = &x.entries[0];
1098                assert_eq!(e.target_id_type, TargetIdType::UseTargetTransportStreamId);
1099                assert!(e.original_network_id_flag);
1100                assert!(!e.service_id_flag);
1101                assert_eq!(
1102                    e.target_id,
1103                    TargetId::Dvb {
1104                        target_id_type: TargetIdType::UseTargetTransportStreamId,
1105                        target_transport_stream_id: Some(0x0011),
1106                        target_original_network_id: Some(0x0022),
1107                        target_service_id: None,
1108                    }
1109                );
1110            }
1111            other => panic!("expected ExtendedEventLinkage, got {other:?}"),
1112        }
1113        assert_eq!(d.private_data, &[] as &[u8]);
1114    }
1115
1116    #[test]
1117    fn parse_other_type_captures_raw_tail() {
1118        let bytes = [
1119            TAG, 0x0A, // length 10
1120            0x00, 0x01, // ts_id
1121            0x00, 0x02, // onid
1122            0x00, 0x03, // sid
1123            0x0B, // linkage_type = IP/MAC notification (EN 301 192)
1124            0xAA, 0xBB, 0xCC, // raw tail
1125        ];
1126        let d = LinkageDescriptor::parse(&bytes).unwrap();
1127        match &d.linkage_data {
1128            LinkageData::Other(b) => assert_eq!(*b, &[0xAA, 0xBB, 0xCC]),
1129            other => panic!("expected Other, got {other:?}"),
1130        }
1131        assert!(d.private_data.is_empty());
1132    }
1133
1134    #[test]
1135    fn parse_user_defined_type_is_other() {
1136        let bytes = [
1137            TAG, 0x09, // length 9
1138            0x00, 0x01, // ts_id
1139            0x00, 0x02, // onid
1140            0x00, 0x03, // sid
1141            0x90, // user-defined linkage_type
1142            0xFF, 0xFE, // raw tail
1143        ];
1144        let d = LinkageDescriptor::parse(&bytes).unwrap();
1145        assert!(matches!(d.linkage_data, LinkageData::Other(_)));
1146    }
1147
1148    #[test]
1149    fn parse_rejects_wrong_tag() {
1150        let err = LinkageDescriptor::parse(&[0x4B, 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x05])
1151            .unwrap_err();
1152        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x4B, .. }));
1153    }
1154
1155    #[test]
1156    fn parse_rejects_body_shorter_than_seven() {
1157        let bytes = [TAG, 0x05, 0x00, 0x01, 0x00, 0x02, 0x00];
1158        let err = LinkageDescriptor::parse(&bytes).unwrap_err();
1159        assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
1160    }
1161
1162    #[test]
1163    fn parse_rejects_truncated_buffer() {
1164        let err = LinkageDescriptor::parse(&[TAG]).unwrap_err();
1165        assert!(matches!(err, Error::BufferTooShort { .. }));
1166    }
1167
1168    #[test]
1169    fn parse_rejects_truncated_mobile_handover() {
1170        let bytes = [
1171            TAG, 0x08, // length 8
1172            0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x08, // linkage_type = mobile hand-over
1173            0x10, // flags byte (origin_type=0, needs initial_service_id)
1174            0x00, 0x10, // network_id
1175        ];
1176        let err = LinkageDescriptor::parse(&bytes).unwrap_err();
1177        assert!(
1178            matches!(err, Error::InvalidDescriptor { .. }),
1179            "expected InvalidDescriptor for truncated mobile hand-over, got {err:?}"
1180        );
1181    }
1182
1183    #[test]
1184    fn serialize_round_trip_no_linkage_data() {
1185        let d = LinkageDescriptor {
1186            transport_stream_id: 0x1234,
1187            original_network_id: 0x5678,
1188            service_id: 0xABCD,
1189            linkage_type: LinkageType::EpgService,
1190            linkage_data: LinkageData::None,
1191            private_data: &[],
1192        };
1193        let mut buf = vec![0u8; d.serialized_len()];
1194        d.serialize_into(&mut buf).unwrap();
1195        let re = LinkageDescriptor::parse(&buf).unwrap();
1196        assert_eq!(d, re);
1197    }
1198
1199    #[test]
1200    fn serialize_round_trip_with_private_data() {
1201        let d = LinkageDescriptor {
1202            transport_stream_id: 0x0001,
1203            original_network_id: 0x0002,
1204            service_id: 0x0003,
1205            linkage_type: LinkageType::ServiceReplacementService,
1206            linkage_data: LinkageData::None,
1207            private_data: &[0xDE, 0xAD, 0xBE, 0xEF],
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_mobile_handover() {
1217        let d = LinkageDescriptor {
1218            transport_stream_id: 0x0001,
1219            original_network_id: 0x0002,
1220            service_id: 0x0003,
1221            linkage_type: LinkageType::MobileHandOver,
1222            linkage_data: LinkageData::MobileHandOver(MobileHandOverInfo {
1223                hand_over_type: HandOverType::DvbAssociatedService,
1224                origin_type: false,
1225                network_id: Some(0x0044),
1226                initial_service_id: Some(0x0055),
1227            }),
1228            private_data: &[0xFF],
1229        };
1230        let mut buf = vec![0u8; d.serialized_len()];
1231        d.serialize_into(&mut buf).unwrap();
1232        let re = LinkageDescriptor::parse(&buf).unwrap();
1233        assert_eq!(d, re);
1234    }
1235
1236    #[test]
1237    fn serialize_round_trip_event_linkage() {
1238        let d = LinkageDescriptor {
1239            transport_stream_id: 0x0001,
1240            original_network_id: 0x0002,
1241            service_id: 0x0003,
1242            linkage_type: LinkageType::EventLinkage,
1243            linkage_data: LinkageData::EventLinkage(EventLinkageInfo {
1244                target_event_id: 0x1234,
1245                target_listed: true,
1246                event_simulcast: false,
1247            }),
1248            private_data: &[],
1249        };
1250        let mut buf = vec![0u8; d.serialized_len()];
1251        d.serialize_into(&mut buf).unwrap();
1252        let re = LinkageDescriptor::parse(&buf).unwrap();
1253        assert_eq!(d, re);
1254    }
1255
1256    #[test]
1257    fn serialize_round_trip_extended_event_linkage() {
1258        let d = LinkageDescriptor {
1259            transport_stream_id: 0x0001,
1260            original_network_id: 0x0002,
1261            service_id: 0x0003,
1262            linkage_type: LinkageType::ExtendedEventLinkage(0x0E),
1263            linkage_data: LinkageData::ExtendedEventLinkage(ExtendedEventLinkageInfo {
1264                entries: vec![ExtendedEventLinkageEntry {
1265                    target_event_id: 0xAAAA,
1266                    target_listed: true,
1267                    event_simulcast: true,
1268                    link_type: LinkType::HdOrServiceFrameCompatible,
1269                    target_id_type: TargetIdType::UseTargetTransportStreamId,
1270                    original_network_id_flag: true,
1271                    service_id_flag: true,
1272                    target_id: TargetId::Dvb {
1273                        target_id_type: TargetIdType::UseTargetTransportStreamId,
1274                        target_transport_stream_id: Some(0x1111),
1275                        target_original_network_id: Some(0x2222),
1276                        target_service_id: Some(0x3333),
1277                    },
1278                }],
1279            }),
1280            private_data: &[0xCC],
1281        };
1282        let mut buf = vec![0u8; d.serialized_len()];
1283        d.serialize_into(&mut buf).unwrap();
1284        let re = LinkageDescriptor::parse(&buf).unwrap();
1285        assert_eq!(d, re);
1286    }
1287
1288    #[test]
1289    fn serialize_round_trip_other() {
1290        let raw = [0xAA, 0xBB, 0xCC];
1291        let d = LinkageDescriptor {
1292            transport_stream_id: 0x0001,
1293            original_network_id: 0x0002,
1294            service_id: 0x0003,
1295            linkage_type: LinkageType::IpMacNotificationService,
1296            linkage_data: LinkageData::Other(&raw),
1297            private_data: &[],
1298        };
1299        let mut buf = vec![0u8; d.serialized_len()];
1300        d.serialize_into(&mut buf).unwrap();
1301        let re = LinkageDescriptor::parse(&buf).unwrap();
1302        assert_eq!(d, re);
1303    }
1304
1305    #[test]
1306    fn serialize_reserved_bits_are_set() {
1307        let d = LinkageDescriptor {
1308            transport_stream_id: 0x0001,
1309            original_network_id: 0x0002,
1310            service_id: 0x0003,
1311            linkage_type: LinkageType::EventLinkage,
1312            linkage_data: LinkageData::EventLinkage(EventLinkageInfo {
1313                target_event_id: 0x0000,
1314                target_listed: false,
1315                event_simulcast: false,
1316            }),
1317            private_data: &[],
1318        };
1319        let mut buf = vec![0u8; d.serialized_len()];
1320        d.serialize_into(&mut buf).unwrap();
1321        assert_eq!(buf[11] & RESERVED_EVENT_MASK, RESERVED_EVENT_MASK);
1322
1323        let d2 = LinkageDescriptor {
1324            transport_stream_id: 0x0001,
1325            original_network_id: 0x0002,
1326            service_id: 0x0003,
1327            linkage_type: LinkageType::MobileHandOver,
1328            linkage_data: LinkageData::MobileHandOver(MobileHandOverInfo {
1329                hand_over_type: HandOverType::Reserved(0),
1330                origin_type: true,
1331                network_id: None,
1332                initial_service_id: None,
1333            }),
1334            private_data: &[],
1335        };
1336        let mut buf2 = vec![0u8; d2.serialized_len()];
1337        d2.serialize_into(&mut buf2).unwrap();
1338        assert_eq!(buf2[9] & RESERVED_HANDOVER_MASK, RESERVED_HANDOVER_MASK);
1339    }
1340
1341    #[test]
1342    fn parse_mobile_handover_type4_no_network_id() {
1343        let bytes = [
1344            TAG, 0x0A, // length 10 = 7 fixed + flags(1) + initial_sid(2)
1345            0x00, 0x01, // ts_id
1346            0x00, 0x02, // onid
1347            0x00, 0x03, // sid
1348            0x08, // linkage_type = mobile hand-over
1349            0x4E, // hand_over_type=4, rfu=111, origin_type=0 (NIT)
1350            0x00, 0x20, // initial_service_id
1351        ];
1352        let d = LinkageDescriptor::parse(&bytes).unwrap();
1353        match &d.linkage_data {
1354            LinkageData::MobileHandOver(m) => {
1355                assert_eq!(m.hand_over_type, HandOverType::Reserved(4));
1356                assert!(!m.origin_type);
1357                assert_eq!(m.network_id, None);
1358                assert_eq!(m.initial_service_id, Some(0x0020));
1359            }
1360            other => panic!("expected MobileHandOver, got {other:?}"),
1361        }
1362    }
1363
1364    #[test]
1365    fn parse_mobile_handover_type1_network_id_present() {
1366        let bytes = [
1367            TAG, 0x0C, // length 12 = 7 fixed + 3 handover + 2 priv
1368            0x00, 0x01, // ts_id
1369            0x00, 0x02, // onid
1370            0x00, 0x03, // sid
1371            0x08, // linkage_type = mobile hand-over
1372            0x1F, // hand_over_type=1, rfu=111, origin_type=1 (SDT)
1373            0x00, 0x10, // network_id
1374            0xCA, 0xFE, // private_data
1375        ];
1376        let d = LinkageDescriptor::parse(&bytes).unwrap();
1377        match &d.linkage_data {
1378            LinkageData::MobileHandOver(m) => {
1379                assert_eq!(
1380                    m.hand_over_type,
1381                    HandOverType::DvbIdenticalNeighbouringCountry
1382                );
1383                assert!(m.origin_type);
1384                assert_eq!(m.network_id, Some(0x0010));
1385                assert_eq!(m.initial_service_id, None);
1386            }
1387            other => panic!("expected MobileHandOver, got {other:?}"),
1388        }
1389        assert_eq!(d.private_data, &[0xCA, 0xFE]);
1390    }
1391
1392    #[test]
1393    fn linkage_type_full_range_round_trip() {
1394        for b in 0..=0xFF_u8 {
1395            let lt = LinkageType::from_u8(b);
1396            assert_eq!(lt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
1397        }
1398    }
1399
1400    #[test]
1401    fn hand_over_type_full_range_round_trip() {
1402        for b in 0..=0xFF_u8 {
1403            let ht = HandOverType::from_u8(b);
1404            assert_eq!(ht.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
1405        }
1406    }
1407
1408    #[test]
1409    fn link_type_full_range_round_trip() {
1410        for b in 0..=0xFF_u8 {
1411            let lt = LinkType::from_u8(b);
1412            assert_eq!(lt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
1413        }
1414    }
1415
1416    #[test]
1417    fn target_id_type_full_range_round_trip() {
1418        for b in 0..=0xFF_u8 {
1419            let tt = TargetIdType::from_u8(b);
1420            assert_eq!(tt.to_u8(), b, "round-trip failed for byte 0x{b:02X}");
1421        }
1422    }
1423}