Skip to main content

dvb_si/descriptors/
linkage.rs

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