Skip to main content

dvb_si/tables/
rct.rs

1//! Related Content Table — ETSI TS 102 323 v1.4.1 §10.4.
2//!
3//! Signals links to related material for a service. Carried in the ES whose
4//! PID is named by a `related_content_descriptor` in that service's PMT
5//! (stream_type 0x05, private sections). There is no fixed PID.
6//!
7//! The link_info loop is unfolded into [`LinkInfo`] entries (Table 110, §10.4.3)
8//! with the conditional `dvb_binary_locator` sub-structure typed as
9//! [`DvbBinaryLocator`] (Table 31, §7.3.2.3.3).
10
11use crate::descriptors::DescriptorLoop;
12
13/// Identifier type — ETSI TS 102 323 v1.4.1 §7.3.2.3.3 Table 32
14/// (`docs/ts_102_323_tva.md`, Table 32 — Identifier type).
15///
16/// 2-bit field in the DVB binary locator. Selects what kind of event identifier
17/// field (if any) is present.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[non_exhaustive]
21pub enum IdentifierType {
22    /// `0b00` — no event identifier field is present.
23    None,
24    /// `0b01` — event identifier is an `event_id`.
25    EventId,
26    /// `0b10` — event identifier is a `TVA_id` carried in EIT.
27    TvaIdEit,
28    /// `0b11` — event identifier is a `TVA_id` carried in PES.
29    TvaIdPes,
30}
31
32impl IdentifierType {
33    #[must_use]
34    /// Creates a value from a 2-bit wire nibble (upper bits masked off).
35    pub fn from_u8(v: u8) -> Self {
36        match v & 0x03 {
37            0x00 => Self::None,
38            0x01 => Self::EventId,
39            0x02 => Self::TvaIdEit,
40            0x03 => Self::TvaIdPes,
41            // unreachable after masking to 2 bits, but the compiler requires it
42            _ => unreachable!(),
43        }
44    }
45
46    #[must_use]
47    /// Returns the 2-bit wire nibble for this value.
48    pub fn to_u8(self) -> u8 {
49        match self {
50            Self::None => 0x00,
51            Self::EventId => 0x01,
52            Self::TvaIdEit => 0x02,
53            Self::TvaIdPes => 0x03,
54        }
55    }
56
57    #[must_use]
58    /// Returns the spec token for this value.
59    pub fn name(self) -> &'static str {
60        match self {
61            Self::None => "none",
62            Self::EventId => "event_id",
63            Self::TvaIdEit => "TVA_id (EIT)",
64            Self::TvaIdPes => "TVA_id (PES)",
65        }
66    }
67}
68dvb_common::impl_spec_display!(IdentifierType);
69use crate::error::{Error, Result};
70use crate::text::{DvbText, LangCode};
71use alloc::vec::Vec;
72use dvb_common::{Parse, Serialize};
73
74/// `table_id` for Related Content Table.
75pub const TABLE_ID: u8 = 0x76;
76
77/// Well-known PID on which RCT is carried: none (signalled via PMT).
78pub const PID: u16 = 0x0000;
79
80const MIN_HEADER_LEN: usize = 3;
81const EXTENSION_HEADER_LEN: usize = 5;
82const POST_EXT_FIXED_LEN: usize = 3;
83const LINK_ENTRY_HEADER_LEN: usize = 2;
84const DESC_LOOP_LEN_FIELD: usize = 2;
85const CRC_LEN: usize = 4;
86const MIN_SECTION_LEN: usize =
87    MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXT_FIXED_LEN + DESC_LOOP_LEN_FIELD + CRC_LEN;
88
89const LINK_TYPE_MASK: u8 = 0xF0;
90const LINK_TYPE_SHIFT: u8 = 4;
91const HOW_RELATED_HI_MASK: u8 = 0x03;
92const TERM_ID_HI_MASK: u8 = 0x0F;
93const TERM_ID_HI_SHIFT: u8 = 8;
94const GROUP_ID_MASK: u8 = 0xF0;
95const GROUP_ID_SHIFT: u8 = 4;
96const PRECEDENCE_MASK: u8 = 0x0F;
97
98const LOCATOR_ID_TYPE_MASK: u8 = 0xC0;
99const LOCATOR_ID_TYPE_SHIFT: u8 = 6;
100const LOCATOR_RELIABILITY_MASK: u8 = 0x20;
101const LOCATOR_INLINE_MASK: u8 = 0x10;
102const LOCATOR_START_DATE_HI_MASK: u8 = 0x07;
103const LOCATOR_START_DATE_HI_SHIFT: usize = 6;
104const LOCATOR_RESERVED_BITS: u8 = 0x08;
105
106const ITEM_RFU_MASK: u8 = 0xC0;
107const ITEM_COUNT_MASK: u8 = 0x3F;
108
109const LINK_INFO_HEADER_RFU: u8 = 0x0C;
110
111const ICON_FLAG_MASK: u8 = 0x80;
112const ICON_ID_MASK: u8 = 0x70;
113const ICON_ID_SHIFT: u8 = 4;
114const ICON_DESC_LEN_HI_MASK: u8 = 0x0F;
115
116/// Link type — ETSI TS 102 323 §10.4.3 Table 111.
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize))]
119#[non_exhaustive]
120pub enum LinkType {
121    /// 0x0 — URI string only.
122    UriString,
123    /// 0x1 — Binary locator only.
124    BinaryLocator,
125    /// 0x2 — Both binary locator and URI.
126    Both,
127    /// 0x3 — Through means of a descriptor.
128    Descriptor,
129    /// 0x4..=0xF — DVB reserved.
130    DvbReserved(u8),
131}
132
133impl LinkType {
134    #[must_use]
135    /// Decode from the wire value.  Every value maps (lossless).
136    pub fn from_u8(v: u8) -> Self {
137        match v & 0x0F {
138            0x0 => Self::UriString,
139            0x1 => Self::BinaryLocator,
140            0x2 => Self::Both,
141            0x3 => Self::Descriptor,
142            v => Self::DvbReserved(v),
143        }
144    }
145
146    #[must_use]
147    /// Encode to the wire value.  Inverse of `from_u8` / `from_u16`.
148    pub fn to_u8(self) -> u8 {
149        match self {
150            Self::UriString => 0x0,
151            Self::BinaryLocator => 0x1,
152            Self::Both => 0x2,
153            Self::Descriptor => 0x3,
154            Self::DvbReserved(v) => v,
155        }
156    }
157
158    #[must_use]
159    /// Human-readable spec display name.
160    pub fn name(self) -> &'static str {
161        match self {
162            Self::UriString => "URI String",
163            Self::BinaryLocator => "Binary Locator",
164            Self::Both => "Both",
165            Self::Descriptor => "Descriptor",
166            Self::DvbReserved(_) => "DVB Reserved",
167        }
168    }
169}
170dvb_common::impl_spec_display!(LinkType, DvbReserved);
171
172/// How-related classification scheme ID — ETSI TS 102 323 §10.4.3 Table 112.
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174#[cfg_attr(feature = "serde", derive(serde::Serialize))]
175#[non_exhaustive]
176pub enum HowRelated {
177    /// 0x00 — HowRelatedCS:2004.
178    Cs2004,
179    /// 0x01 — HowRelatedCS:2005.
180    Cs2005,
181    /// 0x02 — HowRelatedCS:2007.
182    Cs2007,
183    /// 0x03..=0x2F — DVB reserved.
184    DvbReserved(u8),
185    /// 0x30..=0x3F — User private.
186    UserPrivate(u8),
187}
188
189impl HowRelated {
190    #[must_use]
191    /// Decode from the wire value.  Every value maps (lossless).
192    pub fn from_u8(v: u8) -> Self {
193        match v {
194            0x00 => Self::Cs2004,
195            0x01 => Self::Cs2005,
196            0x02 => Self::Cs2007,
197            v @ 0x03..0x30 => Self::DvbReserved(v),
198            _ => Self::UserPrivate(v),
199        }
200    }
201
202    #[must_use]
203    /// Encode to the wire value.  Inverse of `from_u8` / `from_u16`.
204    pub fn to_u8(self) -> u8 {
205        match self {
206            Self::Cs2004 => 0x00,
207            Self::Cs2005 => 0x01,
208            Self::Cs2007 => 0x02,
209            Self::DvbReserved(v) | Self::UserPrivate(v) => v,
210        }
211    }
212
213    #[must_use]
214    /// Human-readable spec display name.
215    pub fn name(self) -> &'static str {
216        match self {
217            Self::Cs2004 => "HowRelatedCS:2004",
218            Self::Cs2005 => "HowRelatedCS:2005",
219            Self::Cs2007 => "HowRelatedCS:2007",
220            Self::DvbReserved(_) => "DVB Reserved",
221            Self::UserPrivate(_) => "User Private",
222        }
223    }
224}
225dvb_common::impl_spec_display!(HowRelated, DvbReserved, UserPrivate);
226
227/// A promotional text item within a link_info entry (Table 110, §10.4.3).
228#[derive(Debug, Clone, PartialEq, Eq)]
229#[cfg_attr(feature = "serde", derive(serde::Serialize))]
230pub struct LinkItem<'a> {
231    /// ISO 639-2 language code.
232    pub language_code: LangCode,
233    /// Promotional text (EN 300 468 Annex A).
234    pub promotional_text: DvbText<'a>,
235}
236
237fn link_item_serialized_len(item: &LinkItem) -> usize {
238    3 + 1 + item.promotional_text.len()
239}
240
241/// Service identification within a DVB binary locator (Table 31, §7.3.2.3.3).
242#[derive(Debug, Clone, PartialEq, Eq)]
243#[cfg_attr(feature = "serde", derive(serde::Serialize))]
244#[non_exhaustive]
245pub enum DvbLocatorService {
246    /// `inline_service == 0`: 10-bit `DVB_service_triplet_ID`.
247    Triplet {
248        /// 10-bit DVB service triplet ID.
249        dvb_service_triplet_id: u16,
250    },
251    /// `inline_service == 1`: full triplet.
252    Full {
253        /// `transport_stream_id`.
254        transport_stream_id: u16,
255        /// `original_network_id`.
256        original_network_id: u16,
257        /// `service_id`.
258        service_id: u16,
259    },
260}
261
262/// Event identifier within a DVB binary locator (Table 31).
263#[derive(Debug, Clone, PartialEq, Eq)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize))]
265#[non_exhaustive]
266pub enum DvbLocatorIdentifier {
267    /// `identifier_type == 0x00`: no identifier field.
268    None,
269    /// `identifier_type == 0x01`: `event_id`.
270    EventId {
271        /// 16-bit event_id.
272        event_id: u16,
273    },
274    /// `identifier_type == 0x02`: `TVA_id` carried in EIT.
275    TvaIdEit {
276        /// 16-bit TVA_id.
277        tva_id: u16,
278    },
279    /// `identifier_type == 0x03`: `TVA_id` carried in PES + `component`.
280    TvaIdPes {
281        /// 16-bit TVA_id.
282        tva_id: u16,
283        /// 8-bit component.
284        component: u8,
285    },
286}
287
288/// Time windows within a DVB binary locator (Table 31).
289#[derive(Debug, Clone, PartialEq, Eq)]
290#[cfg_attr(feature = "serde", derive(serde::Serialize))]
291pub struct DvbLocatorWindows {
292    /// `early_start_window` (3 bits).
293    pub early_start_window: u8,
294    /// `late_end_window` (5 bits).
295    pub late_end_window: u8,
296}
297
298/// DVB binary locator (Table 31, §7.3.2.3.3).
299#[derive(Debug, Clone, PartialEq, Eq)]
300#[cfg_attr(feature = "serde", derive(serde::Serialize))]
301pub struct DvbBinaryLocator {
302    /// `identifier_type` (2 bits, Table 32) — [`IdentifierType`].
303    pub identifier_type: IdentifierType,
304    /// `scheduled_time_reliability`.
305    pub scheduled_time_reliability: bool,
306    /// `inline_service`.
307    pub inline_service: bool,
308    /// `start_date` (9 bits).
309    pub start_date: u16,
310    /// Service identification (conditional on `inline_service`).
311    pub service: DvbLocatorService,
312    /// `start_time` (16 bits).
313    pub start_time: u16,
314    /// `duration` (16 bits).
315    pub duration: u16,
316    /// Event identifier (conditional on `identifier_type`).
317    pub identifier: DvbLocatorIdentifier,
318    /// Time windows (present iff `identifier_type == 0 && scheduled_time_reliability == 1`).
319    pub windows: Option<DvbLocatorWindows>,
320}
321
322fn locator_serialized_len(loc: &DvbBinaryLocator) -> usize {
323    let mut len = 2;
324    len += match &loc.service {
325        DvbLocatorService::Triplet { .. } => 1,
326        DvbLocatorService::Full { .. } => 6,
327    };
328    len += 4;
329    len += match &loc.identifier {
330        DvbLocatorIdentifier::None => 0,
331        DvbLocatorIdentifier::EventId { .. } => 2,
332        DvbLocatorIdentifier::TvaIdEit { .. } => 2,
333        DvbLocatorIdentifier::TvaIdPes { .. } => 3,
334    };
335    if loc.windows.is_some() {
336        len += 1;
337    }
338    len
339}
340
341/// A single link_info entry (Table 110, §10.4.3).
342#[derive(Debug, Clone, PartialEq, Eq)]
343#[cfg_attr(feature = "serde", derive(serde::Serialize))]
344pub struct LinkInfo<'a> {
345    /// `link_type` (4 bits, Table 111).
346    pub link_type: LinkType,
347    /// `how_related_classification_scheme_id` (6 bits).
348    pub how_related: HowRelated,
349    /// `term_id` (12 bits).
350    pub term_id: u16,
351    /// `group_id` (4 bits).
352    pub group_id: u8,
353    /// `precedence` (4 bits).
354    pub precedence: u8,
355    /// Media URI bytes (present iff `link_type == 0x00 || link_type == 0x02`).
356    pub media_uri: Option<&'a [u8]>,
357    /// DVB binary locator (present iff `link_type == 0x01 || link_type == 0x02`).
358    pub dvb_binary_locator: Option<DvbBinaryLocator>,
359    /// Promotional text items.
360    pub items: Vec<LinkItem<'a>>,
361    /// `default_icon_flag`.
362    pub default_icon_flag: bool,
363    /// `icon_id` (3 bits).
364    pub icon_id: u8,
365    /// Per-link descriptor loop.
366    pub descriptors: DescriptorLoop<'a>,
367}
368
369fn link_info_serialized_len(li: &LinkInfo) -> usize {
370    let mut len = 4;
371    len += li.media_uri.map_or(0, |u| 1 + u.len());
372    len += li
373        .dvb_binary_locator
374        .as_ref()
375        .map_or(0, locator_serialized_len);
376    len += 1;
377    len += li.items.iter().map(link_item_serialized_len).sum::<usize>();
378    len += 2;
379    len += li.descriptors.len();
380    len
381}
382
383/// Related Content Table (ETSI TS 102 323 v1.4.1 §10.4.2, Table 109).
384///
385/// The link_info loop is unfolded into typed [`LinkInfo`] entries.
386#[derive(Debug, Clone, PartialEq, Eq)]
387#[cfg_attr(feature = "serde", derive(serde::Serialize))]
388#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
389pub struct RctSection<'a> {
390    /// `table_id_extension_flag` (bit 6 of byte 1).
391    pub table_id_extension_flag: bool,
392    /// `service_id` — table_id_extension field.
393    pub service_id: u16,
394    /// 5-bit `version_number`.
395    pub version_number: u8,
396    /// `current_next_indicator`.
397    pub current_next_indicator: bool,
398    /// `section_number`.
399    pub section_number: u8,
400    /// `last_section_number`.
401    pub last_section_number: u8,
402    /// `year_offset` — reference year.
403    pub year_offset: u16,
404    /// Link info entries — unfolded per Table 110.
405    pub links: Vec<LinkInfo<'a>>,
406    /// Trailing descriptor loop.
407    pub descriptors: DescriptorLoop<'a>,
408}
409
410fn parse_locator(data: &[u8]) -> Result<(DvbBinaryLocator, usize)> {
411    if data.len() < 2 {
412        return Err(Error::BufferTooShort {
413            need: 2,
414            have: data.len(),
415            what: "RctSection dvb_binary_locator header",
416        });
417    }
418    let b0 = data[0];
419    let b1 = data[1];
420    let identifier_type =
421        IdentifierType::from_u8((b0 & LOCATOR_ID_TYPE_MASK) >> LOCATOR_ID_TYPE_SHIFT);
422    let scheduled_time_reliability = (b0 & LOCATOR_RELIABILITY_MASK) != 0;
423    let inline_service = (b0 & LOCATOR_INLINE_MASK) != 0;
424    let start_date = ((b0 & LOCATOR_START_DATE_HI_MASK) as u16) << LOCATOR_START_DATE_HI_SHIFT
425        | ((b1 >> 2) as u16 & 0x3F);
426
427    let mut pos = 2;
428    let service = if inline_service {
429        if pos + 6 > data.len() {
430            return Err(Error::BufferTooShort {
431                need: pos + 6,
432                have: data.len(),
433                what: "RctSection dvb_binary_locator full triplet",
434            });
435        }
436        let triplet = data[pos..pos + 6].first_chunk::<6>().unwrap();
437        let tsid = u16::from_be_bytes(*triplet[0..].first_chunk::<2>().unwrap());
438        let onid = u16::from_be_bytes(*triplet[2..].first_chunk::<2>().unwrap());
439        let sid = u16::from_be_bytes(*triplet[4..].first_chunk::<2>().unwrap());
440        pos += 6;
441        DvbLocatorService::Full {
442            transport_stream_id: tsid,
443            original_network_id: onid,
444            service_id: sid,
445        }
446    } else {
447        if data.len() < 3 {
448            return Err(Error::BufferTooShort {
449                need: 3,
450                have: data.len(),
451                what: "RctSection dvb_binary_locator triplet",
452            });
453        }
454        let triplet_id = ((b1 as u16 & 0x03) << 8) | data[2] as u16;
455        pos = 3;
456        DvbLocatorService::Triplet {
457            dvb_service_triplet_id: triplet_id,
458        }
459    };
460
461    if pos + 4 > data.len() {
462        return Err(Error::BufferTooShort {
463            need: pos + 4,
464            have: data.len(),
465            what: "RctSection dvb_binary_locator start_time/duration",
466        });
467    }
468    let (b2, rest) = data[pos..]
469        .split_first_chunk::<2>()
470        .ok_or(Error::BufferTooShort {
471            need: pos + 4,
472            have: data.len(),
473            what: "RctSection dvb_binary_locator start_time/duration",
474        })?;
475    let start_time = u16::from_be_bytes(*b2);
476    let (b2, _) = rest.split_first_chunk::<2>().ok_or(Error::BufferTooShort {
477        need: pos + 4,
478        have: data.len(),
479        what: "RctSection dvb_binary_locator start_time/duration",
480    })?;
481    let duration = u16::from_be_bytes(*b2);
482    pos += 4;
483
484    let identifier = match identifier_type {
485        IdentifierType::None => DvbLocatorIdentifier::None,
486        IdentifierType::EventId => {
487            if pos + 2 > data.len() {
488                return Err(Error::BufferTooShort {
489                    need: pos + 2,
490                    have: data.len(),
491                    what: "RctSection dvb_binary_locator event_id",
492                });
493            }
494            let (b2, _) = data[pos..]
495                .split_first_chunk::<2>()
496                .ok_or(Error::BufferTooShort {
497                    need: pos + 2,
498                    have: data.len(),
499                    what: "RctSection dvb_binary_locator event_id",
500                })?;
501            let event_id = u16::from_be_bytes(*b2);
502            pos += 2;
503            DvbLocatorIdentifier::EventId { event_id }
504        }
505        IdentifierType::TvaIdEit => {
506            if pos + 2 > data.len() {
507                return Err(Error::BufferTooShort {
508                    need: pos + 2,
509                    have: data.len(),
510                    what: "RctSection dvb_binary_locator TVA_id (EIT)",
511                });
512            }
513            let (b2, _) = data[pos..]
514                .split_first_chunk::<2>()
515                .ok_or(Error::BufferTooShort {
516                    need: pos + 2,
517                    have: data.len(),
518                    what: "RctSection dvb_binary_locator TVA_id (EIT)",
519                })?;
520            let tva_id = u16::from_be_bytes(*b2);
521            pos += 2;
522            DvbLocatorIdentifier::TvaIdEit { tva_id }
523        }
524        IdentifierType::TvaIdPes => {
525            if pos + 3 > data.len() {
526                return Err(Error::BufferTooShort {
527                    need: pos + 3,
528                    have: data.len(),
529                    what: "RctSection dvb_binary_locator TVA_id (PES)",
530                });
531            }
532            let (b2, _) = data[pos..]
533                .split_first_chunk::<2>()
534                .ok_or(Error::BufferTooShort {
535                    need: pos + 3,
536                    have: data.len(),
537                    what: "RctSection dvb_binary_locator TVA_id (PES)",
538                })?;
539            let tva_id = u16::from_be_bytes(*b2);
540            let component = data[pos + 2];
541            pos += 3;
542            DvbLocatorIdentifier::TvaIdPes { tva_id, component }
543        }
544    };
545
546    let windows = if identifier_type == IdentifierType::None && scheduled_time_reliability {
547        if pos >= data.len() {
548            return Err(Error::BufferTooShort {
549                need: pos + 1,
550                have: data.len(),
551                what: "RctSection dvb_binary_locator windows",
552            });
553        }
554        let wb = data[pos];
555        pos += 1;
556        Some(DvbLocatorWindows {
557            early_start_window: (wb >> 5) & 0x07,
558            late_end_window: wb & 0x1F,
559        })
560    } else {
561        None
562    };
563
564    Ok((
565        DvbBinaryLocator {
566            identifier_type,
567            scheduled_time_reliability,
568            inline_service,
569            start_date,
570            service,
571            start_time,
572            duration,
573            identifier,
574            windows,
575        },
576        pos,
577    ))
578}
579
580fn serialize_locator(loc: &DvbBinaryLocator, buf: &mut [u8]) -> usize {
581    let b0 = (loc.identifier_type.to_u8() << LOCATOR_ID_TYPE_SHIFT)
582        | (u8::from(loc.scheduled_time_reliability) << 5)
583        | (u8::from(loc.inline_service) << 4)
584        | LOCATOR_RESERVED_BITS
585        | ((loc.start_date >> LOCATOR_START_DATE_HI_SHIFT) as u8 & LOCATOR_START_DATE_HI_MASK);
586    buf[0] = b0;
587
588    let mut pos: usize;
589    match &loc.service {
590        DvbLocatorService::Triplet {
591            dvb_service_triplet_id,
592        } => {
593            let sd_lo = (loc.start_date & 0x3F) as u8;
594            buf[1] = (sd_lo << 2) | ((dvb_service_triplet_id >> 8) as u8 & 0x03);
595            buf[2] = (dvb_service_triplet_id & 0xFF) as u8;
596            pos = 3;
597        }
598        DvbLocatorService::Full {
599            transport_stream_id,
600            original_network_id,
601            service_id,
602        } => {
603            let sd_lo = (loc.start_date & 0x3F) as u8;
604            buf[1] = (sd_lo << 2) | 0x03;
605            buf[2..4].copy_from_slice(&transport_stream_id.to_be_bytes());
606            buf[4..6].copy_from_slice(&original_network_id.to_be_bytes());
607            buf[6..8].copy_from_slice(&service_id.to_be_bytes());
608            pos = 8;
609        }
610    }
611    buf[pos..pos + 2].copy_from_slice(&loc.start_time.to_be_bytes());
612    buf[pos + 2..pos + 4].copy_from_slice(&loc.duration.to_be_bytes());
613    pos += 4;
614
615    match &loc.identifier {
616        DvbLocatorIdentifier::None => {}
617        DvbLocatorIdentifier::EventId { event_id } => {
618            buf[pos..pos + 2].copy_from_slice(&event_id.to_be_bytes());
619            pos += 2;
620        }
621        DvbLocatorIdentifier::TvaIdEit { tva_id } => {
622            buf[pos..pos + 2].copy_from_slice(&tva_id.to_be_bytes());
623            pos += 2;
624        }
625        DvbLocatorIdentifier::TvaIdPes { tva_id, component } => {
626            buf[pos..pos + 2].copy_from_slice(&tva_id.to_be_bytes());
627            buf[pos + 2] = *component;
628            pos += 3;
629        }
630    }
631    if let Some(w) = &loc.windows {
632        buf[pos] = ((w.early_start_window & 0x07) << 5) | (w.late_end_window & 0x1F);
633        pos += 1;
634    }
635    pos
636}
637
638fn parse_link_info(data: &[u8], link_info_length: usize) -> Result<LinkInfo<'_>> {
639    if data.len() < 4 {
640        return Err(Error::BufferTooShort {
641            need: 4,
642            have: data.len(),
643            what: "RctSection link_info header",
644        });
645    }
646    let link_type = LinkType::from_u8((data[0] & LINK_TYPE_MASK) >> LINK_TYPE_SHIFT);
647    let how_related_raw = (data[0] & HOW_RELATED_HI_MASK) << 4 | (data[1] >> 4) & 0x0F;
648    let how_related = HowRelated::from_u8(how_related_raw);
649    let term_id = ((data[1] as u16 & TERM_ID_HI_MASK as u16) << TERM_ID_HI_SHIFT) | data[2] as u16;
650    let group_id = (data[3] & GROUP_ID_MASK) >> GROUP_ID_SHIFT;
651    let precedence = data[3] & PRECEDENCE_MASK;
652
653    let mut pos = 4;
654    let end = link_info_length;
655
656    let media_uri = if link_type == LinkType::UriString || link_type == LinkType::Both {
657        if pos >= end {
658            return Err(Error::BufferTooShort {
659                need: pos + 1,
660                have: end,
661                what: "RctSection media_uri_length",
662            });
663        }
664        let uri_len = data[pos] as usize;
665        pos += 1;
666        if pos + uri_len > end {
667            return Err(Error::BufferTooShort {
668                need: pos + uri_len,
669                have: end,
670                what: "RctSection media_uri",
671            });
672        }
673        let uri = &data[pos..pos + uri_len];
674        pos += uri_len;
675        Some(uri)
676    } else {
677        None
678    };
679
680    let dvb_binary_locator = if link_type == LinkType::BinaryLocator || link_type == LinkType::Both
681    {
682        if pos >= end {
683            return Err(Error::BufferTooShort {
684                need: pos + 1,
685                have: end,
686                what: "RctSection dvb_binary_locator",
687            });
688        }
689        let (loc, consumed) = parse_locator(&data[pos..end])?;
690        pos += consumed;
691        Some(loc)
692    } else {
693        None
694    };
695
696    if pos >= end {
697        return Err(Error::BufferTooShort {
698            need: pos + 1,
699            have: end,
700            what: "RctSection number_items",
701        });
702    }
703    let ni_byte = data[pos];
704    let number_items = (ni_byte & ITEM_COUNT_MASK) as usize;
705    pos += 1;
706
707    let mut items = Vec::with_capacity(number_items);
708    for _ in 0..number_items {
709        if pos + 4 > end {
710            return Err(Error::BufferTooShort {
711                need: pos + 4,
712                have: end,
713                what: "RctSection link_item",
714            });
715        }
716        let language_code = LangCode([data[pos], data[pos + 1], data[pos + 2]]);
717        let text_len = data[pos + 3] as usize;
718        pos += 4;
719        if pos + text_len > end {
720            return Err(Error::BufferTooShort {
721                need: pos + text_len,
722                have: end,
723                what: "RctSection promotional_text",
724            });
725        }
726        let promotional_text = DvbText::new(&data[pos..pos + text_len]);
727        pos += text_len;
728        items.push(LinkItem {
729            language_code,
730            promotional_text,
731        });
732    }
733
734    if pos + 2 > end {
735        return Err(Error::BufferTooShort {
736            need: pos + 2,
737            have: end,
738            what: "RctSection icon/desc",
739        });
740    }
741    let default_icon_flag = (data[pos] & ICON_FLAG_MASK) != 0;
742    let icon_id = (data[pos] & ICON_ID_MASK) >> ICON_ID_SHIFT;
743    let desc_len = (((data[pos] & ICON_DESC_LEN_HI_MASK) as usize) << 8) | data[pos + 1] as usize;
744    pos += 2;
745    let desc_start = pos;
746    let desc_end = desc_start + desc_len;
747    if desc_end > end {
748        return Err(Error::SectionLengthOverflow {
749            declared: desc_len,
750            available: end.saturating_sub(desc_start),
751        });
752    }
753    let descriptors = DescriptorLoop::new(&data[desc_start..desc_end]);
754
755    Ok(LinkInfo {
756        link_type,
757        how_related,
758        term_id,
759        group_id,
760        precedence,
761        media_uri,
762        dvb_binary_locator,
763        items,
764        default_icon_flag,
765        icon_id,
766        descriptors,
767    })
768}
769
770fn serialize_link_info(li: &LinkInfo, buf: &mut [u8]) -> usize {
771    let lt = li.link_type.to_u8();
772    let hr = li.how_related.to_u8();
773    buf[0] =
774        ((lt & 0x0F) << LINK_TYPE_SHIFT) | LINK_INFO_HEADER_RFU | ((hr >> 4) & HOW_RELATED_HI_MASK);
775    buf[1] = ((hr & 0x0F) << 4) | ((li.term_id >> TERM_ID_HI_SHIFT) as u8 & TERM_ID_HI_MASK);
776    buf[2] = li.term_id as u8;
777    buf[3] = ((li.group_id & 0x0F) << GROUP_ID_SHIFT) | (li.precedence & PRECEDENCE_MASK);
778
779    let mut pos = 4;
780    if let Some(uri) = li.media_uri {
781        buf[pos] = uri.len() as u8;
782        pos += 1;
783        buf[pos..pos + uri.len()].copy_from_slice(uri);
784        pos += uri.len();
785    }
786    if let Some(ref loc) = li.dvb_binary_locator {
787        let n = serialize_locator(loc, &mut buf[pos..]);
788        pos += n;
789    }
790    buf[pos] = ITEM_RFU_MASK | (li.items.len() as u8 & ITEM_COUNT_MASK);
791    pos += 1;
792    for item in &li.items {
793        buf[pos..pos + 3].copy_from_slice(&item.language_code.0);
794        buf[pos + 3] = item.promotional_text.len() as u8;
795        pos += 4;
796        buf[pos..pos + item.promotional_text.len()].copy_from_slice(item.promotional_text.raw());
797        pos += item.promotional_text.len();
798    }
799    let dll = li.descriptors.len() as u16;
800    buf[pos] = u8::from(li.default_icon_flag) << 7
801        | ((li.icon_id & 0x07) << ICON_ID_SHIFT)
802        | ((dll >> 8) as u8 & ICON_DESC_LEN_HI_MASK);
803    buf[pos + 1] = (dll & 0xFF) as u8;
804    pos += 2;
805    buf[pos..pos + li.descriptors.len()].copy_from_slice(li.descriptors.raw());
806    pos += li.descriptors.len();
807    pos
808}
809
810impl<'a> Parse<'a> for RctSection<'a> {
811    type Error = crate::error::Error;
812
813    fn parse(bytes: &'a [u8]) -> Result<Self> {
814        if bytes.len() < MIN_SECTION_LEN {
815            return Err(Error::BufferTooShort {
816                need: MIN_SECTION_LEN,
817                have: bytes.len(),
818                what: "RctSection",
819            });
820        }
821        if bytes[0] != TABLE_ID {
822            return Err(Error::UnexpectedTableId {
823                table_id: bytes[0],
824                what: "RctSection",
825                expected: &[TABLE_ID],
826            });
827        }
828
829        let table_id_extension_flag = (bytes[1] & 0x40) != 0;
830        let section_length = (((bytes[1] & 0x0F) as u16) << 8) | bytes[2] as u16;
831        let total = super::check_section_length(
832            bytes.len(),
833            MIN_HEADER_LEN,
834            section_length as usize,
835            MIN_SECTION_LEN,
836        )?;
837
838        let service_id = u16::from_be_bytes(*bytes[3..].first_chunk::<2>().unwrap());
839        let version_number = (bytes[5] >> 1) & 0x1F;
840        let current_next_indicator = (bytes[5] & 0x01) != 0;
841        let section_number = bytes[6];
842        let last_section_number = bytes[7];
843        let year_offset = u16::from_be_bytes(*bytes[8..].first_chunk::<2>().unwrap());
844        let link_count = bytes[10];
845
846        let payload_end = total - CRC_LEN;
847        let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXT_FIXED_LEN;
848
849        let mut links = Vec::with_capacity(link_count as usize);
850        for _ in 0..link_count {
851            if pos + LINK_ENTRY_HEADER_LEN > payload_end {
852                return Err(Error::BufferTooShort {
853                    need: pos + LINK_ENTRY_HEADER_LEN,
854                    have: payload_end,
855                    what: "RctSection link_entry header",
856                });
857            }
858            let link_info_length = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
859            let link_data_start = pos + LINK_ENTRY_HEADER_LEN;
860            let link_data_end = link_data_start + link_info_length;
861            if link_data_end > payload_end {
862                return Err(Error::SectionLengthOverflow {
863                    declared: link_info_length,
864                    available: payload_end.saturating_sub(link_data_start),
865                });
866            }
867            let link_info =
868                parse_link_info(&bytes[link_data_start..link_data_end], link_info_length)?;
869            links.push(link_info);
870            pos = link_data_end;
871        }
872
873        if pos + DESC_LOOP_LEN_FIELD > payload_end {
874            return Err(Error::BufferTooShort {
875                need: pos + DESC_LOOP_LEN_FIELD,
876                have: payload_end,
877                what: "RctSection descriptor_loop_length field",
878            });
879        }
880        let descriptor_loop_length =
881            (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
882        let desc_start = pos + DESC_LOOP_LEN_FIELD;
883        let desc_end = desc_start + descriptor_loop_length;
884        if desc_end > payload_end {
885            return Err(Error::SectionLengthOverflow {
886                declared: descriptor_loop_length,
887                available: payload_end.saturating_sub(desc_start),
888            });
889        }
890        let descriptors = DescriptorLoop::new(&bytes[desc_start..desc_end]);
891
892        Ok(RctSection {
893            table_id_extension_flag,
894            service_id,
895            version_number,
896            current_next_indicator,
897            section_number,
898            last_section_number,
899            year_offset,
900            links,
901            descriptors,
902        })
903    }
904}
905
906impl Serialize for RctSection<'_> {
907    type Error = crate::error::Error;
908
909    fn serialized_len(&self) -> usize {
910        MIN_HEADER_LEN
911            + EXTENSION_HEADER_LEN
912            + POST_EXT_FIXED_LEN
913            + self
914                .links
915                .iter()
916                .map(|li| LINK_ENTRY_HEADER_LEN + link_info_serialized_len(li))
917                .sum::<usize>()
918            + DESC_LOOP_LEN_FIELD
919            + self.descriptors.len()
920            + CRC_LEN
921    }
922
923    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
924        let len = self.serialized_len();
925        if buf.len() < len {
926            return Err(Error::OutputBufferTooSmall {
927                need: len,
928                have: buf.len(),
929            });
930        }
931
932        let section_length_usize = len - MIN_HEADER_LEN;
933        if section_length_usize > 0x0FFF {
934            return Err(Error::SectionLengthOverflow {
935                declared: section_length_usize,
936                available: 0x0FFF,
937            });
938        }
939        let section_length = section_length_usize as u16;
940        buf[0] = TABLE_ID;
941        let tief_bit: u8 = if self.table_id_extension_flag {
942            0x40
943        } else {
944            0x00
945        };
946        buf[1] = super::SECTION_B1_SSI
947            | tief_bit
948            | super::SECTION_B1_RESERVED_HI
949            | ((section_length >> 8) as u8 & 0x0F);
950        buf[2] = (section_length & 0xFF) as u8;
951
952        buf[3..5].copy_from_slice(&self.service_id.to_be_bytes());
953        buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
954        buf[6] = self.section_number;
955        buf[7] = self.last_section_number;
956        buf[8..10].copy_from_slice(&self.year_offset.to_be_bytes());
957        if self.links.len() > u8::MAX as usize {
958            return Err(Error::SectionLengthOverflow {
959                declared: self.links.len(),
960                available: u8::MAX as usize,
961            });
962        }
963        buf[10] = self.links.len() as u8;
964
965        let mut pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + POST_EXT_FIXED_LEN;
966        for li in &self.links {
967            let li_body_len = link_info_serialized_len(li) as u16;
968            buf[pos] = 0xF0 | ((li_body_len >> 8) as u8 & 0x0F);
969            buf[pos + 1] = (li_body_len & 0xFF) as u8;
970            pos += LINK_ENTRY_HEADER_LEN;
971            pos += serialize_link_info(li, &mut buf[pos..]);
972        }
973
974        let dll = self.descriptors.len() as u16;
975        buf[pos] = 0xF0 | ((dll >> 8) as u8 & 0x0F);
976        buf[pos + 1] = (dll & 0xFF) as u8;
977        pos += DESC_LOOP_LEN_FIELD;
978        buf[pos..pos + self.descriptors.len()].copy_from_slice(self.descriptors.raw());
979        pos += self.descriptors.len();
980
981        let crc = dvb_common::crc32_mpeg2::compute(&buf[..pos]);
982        buf[pos..pos + CRC_LEN].copy_from_slice(&crc.to_be_bytes());
983        Ok(len)
984    }
985}
986impl<'a> crate::traits::TableDef<'a> for RctSection<'a> {
987    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
988    const NAME: &'static str = "RELATED_CONTENT";
989}
990
991#[cfg(test)]
992mod tests {
993    use super::*;
994
995    #[test]
996    fn parse_no_links_no_descriptors() {
997        let rct = RctSection {
998            table_id_extension_flag: false,
999            service_id: 0x0064,
1000            version_number: 3,
1001            current_next_indicator: true,
1002            section_number: 0,
1003            last_section_number: 0,
1004            year_offset: 0x07D3,
1005            links: Vec::new(),
1006            descriptors: DescriptorLoop::new(&[]),
1007        };
1008        let mut buf = vec![0u8; rct.serialized_len()];
1009        rct.serialize_into(&mut buf).unwrap();
1010        let p = RctSection::parse(&buf).unwrap();
1011        assert!(!p.table_id_extension_flag);
1012        assert_eq!(p.service_id, 0x0064);
1013        assert_eq!(p.year_offset, 0x07D3);
1014        assert!(p.links.is_empty());
1015    }
1016
1017    #[test]
1018    fn parse_one_link_uri_only() {
1019        let li = LinkInfo {
1020            link_type: LinkType::UriString,
1021            how_related: HowRelated::Cs2005,
1022            term_id: 0x123,
1023            group_id: 0x5,
1024            precedence: 0x9,
1025            media_uri: Some(b"http://example.com"),
1026            dvb_binary_locator: None,
1027            items: Vec::new(),
1028            default_icon_flag: false,
1029            icon_id: 0,
1030            descriptors: DescriptorLoop::new(&[]),
1031        };
1032        let rct = RctSection {
1033            table_id_extension_flag: false,
1034            service_id: 0x1234,
1035            version_number: 7,
1036            current_next_indicator: true,
1037            section_number: 1,
1038            last_section_number: 3,
1039            year_offset: 2003,
1040            links: vec![li],
1041            descriptors: DescriptorLoop::new(&[]),
1042        };
1043        let mut buf = vec![0u8; rct.serialized_len()];
1044        rct.serialize_into(&mut buf).unwrap();
1045        let p = RctSection::parse(&buf).unwrap();
1046        assert_eq!(p.links.len(), 1);
1047        assert_eq!(p.links[0].link_type, LinkType::UriString);
1048        assert_eq!(p.links[0].how_related, HowRelated::Cs2005);
1049        assert_eq!(p.links[0].term_id, 0x123);
1050        assert_eq!(p.links[0].group_id, 0x5);
1051        assert_eq!(p.links[0].precedence, 0x9);
1052        assert_eq!(p.links[0].media_uri.unwrap(), b"http://example.com");
1053    }
1054
1055    #[test]
1056    fn parse_one_link_with_locator_and_items() {
1057        let loc = DvbBinaryLocator {
1058            identifier_type: IdentifierType::EventId,
1059            scheduled_time_reliability: false,
1060            inline_service: true,
1061            start_date: 0x0FF,
1062            service: DvbLocatorService::Full {
1063                transport_stream_id: 0x1000,
1064                original_network_id: 0x2000,
1065                service_id: 0x3000,
1066            },
1067            start_time: 0x8000,
1068            duration: 0x4000,
1069            identifier: DvbLocatorIdentifier::EventId { event_id: 0xBEEF },
1070            windows: None,
1071        };
1072        let li = LinkInfo {
1073            link_type: LinkType::BinaryLocator,
1074            how_related: HowRelated::Cs2007,
1075            term_id: 0x456,
1076            group_id: 0x1,
1077            precedence: 0x2,
1078            media_uri: None,
1079            dvb_binary_locator: Some(loc),
1080            items: vec![LinkItem {
1081                language_code: LangCode(*b"eng"),
1082                promotional_text: DvbText::new(b"Promo"),
1083            }],
1084            default_icon_flag: true,
1085            icon_id: 3,
1086            descriptors: DescriptorLoop::new(&[0x80, 0x00]),
1087        };
1088        let rct = RctSection {
1089            table_id_extension_flag: true,
1090            service_id: 0xABCD,
1091            version_number: 15,
1092            current_next_indicator: false,
1093            section_number: 2,
1094            last_section_number: 5,
1095            year_offset: 2024,
1096            links: vec![li],
1097            descriptors: DescriptorLoop::new(&[]),
1098        };
1099        let mut buf = vec![0u8; rct.serialized_len()];
1100        rct.serialize_into(&mut buf).unwrap();
1101        let mut buf2 = vec![0u8; rct.serialized_len()];
1102        rct.serialize_into(&mut buf2).unwrap();
1103        assert_eq!(buf, buf2, "byte-exact re-serialize");
1104        let p = RctSection::parse(&buf).unwrap();
1105        assert_eq!(p.links.len(), 1);
1106        assert_eq!(p.links[0].link_type, LinkType::BinaryLocator);
1107        let l = p.links[0].dvb_binary_locator.as_ref().unwrap();
1108        assert_eq!(l.identifier_type, IdentifierType::EventId);
1109        assert!(l.inline_service);
1110        assert_eq!(l.start_date, 0x0FF);
1111        assert_eq!(p.links[0].items.len(), 1);
1112        assert_eq!(p.links[0].items[0].language_code, LangCode(*b"eng"));
1113        assert!(p.links[0].default_icon_flag);
1114        assert_eq!(p.links[0].icon_id, 3);
1115    }
1116
1117    #[test]
1118    fn byte_exact_round_trip_simple() {
1119        let li = LinkInfo {
1120            link_type: LinkType::UriString,
1121            how_related: HowRelated::Cs2004,
1122            term_id: 0,
1123            group_id: 0,
1124            precedence: 0,
1125            media_uri: Some(b"uri"),
1126            dvb_binary_locator: None,
1127            items: Vec::new(),
1128            default_icon_flag: false,
1129            icon_id: 0,
1130            descriptors: DescriptorLoop::new(&[]),
1131        };
1132        let rct = RctSection {
1133            table_id_extension_flag: false,
1134            service_id: 0x0001,
1135            version_number: 0,
1136            current_next_indicator: true,
1137            section_number: 0,
1138            last_section_number: 0,
1139            year_offset: 0x07D3,
1140            links: vec![li],
1141            descriptors: DescriptorLoop::new(&[]),
1142        };
1143        let mut buf = vec![0u8; rct.serialized_len()];
1144        rct.serialize_into(&mut buf).unwrap();
1145        let parsed = RctSection::parse(&buf).unwrap();
1146        let mut buf2 = vec![0u8; parsed.serialized_len()];
1147        parsed.serialize_into(&mut buf2).unwrap();
1148        assert_eq!(buf, buf2);
1149    }
1150
1151    #[test]
1152    fn parse_rejects_wrong_table_id() {
1153        let rct = RctSection {
1154            table_id_extension_flag: false,
1155            service_id: 0x0001,
1156            version_number: 0,
1157            current_next_indicator: true,
1158            section_number: 0,
1159            last_section_number: 0,
1160            year_offset: 2024,
1161            links: Vec::new(),
1162            descriptors: DescriptorLoop::new(&[]),
1163        };
1164        let mut buf = vec![0u8; rct.serialized_len()];
1165        rct.serialize_into(&mut buf).unwrap();
1166        buf[0] = 0x4A;
1167        assert!(matches!(
1168            RctSection::parse(&buf).unwrap_err(),
1169            Error::UnexpectedTableId { table_id: 0x4A, .. }
1170        ));
1171    }
1172
1173    #[test]
1174    fn parse_rejects_buffer_too_short() {
1175        assert!(matches!(
1176            RctSection::parse(&[0x76, 0x80, 0x00]).unwrap_err(),
1177            Error::BufferTooShort { .. }
1178        ));
1179    }
1180
1181    #[test]
1182    fn serialize_rejects_output_buffer_too_small() {
1183        let rct = RctSection {
1184            table_id_extension_flag: false,
1185            service_id: 0x0001,
1186            version_number: 0,
1187            current_next_indicator: true,
1188            section_number: 0,
1189            last_section_number: 0,
1190            year_offset: 0,
1191            links: Vec::new(),
1192            descriptors: DescriptorLoop::new(&[]),
1193        };
1194        let mut buf = vec![0u8; 2];
1195        assert!(matches!(
1196            rct.serialize_into(&mut buf).unwrap_err(),
1197            Error::OutputBufferTooSmall { .. }
1198        ));
1199    }
1200
1201    #[test]
1202    fn parse_rejects_zero_section_length() {
1203        let mut buf = vec![0u8; 64];
1204        buf[0] = TABLE_ID;
1205        buf[1] = 0xF0;
1206        buf[2] = 0x00;
1207        for b in &mut buf[3..] {
1208            *b = 0xFF;
1209        }
1210        assert!(matches!(
1211            RctSection::parse(&buf).unwrap_err(),
1212            Error::SectionLengthOverflow { .. }
1213        ));
1214    }
1215
1216    #[test]
1217    fn parse_handwritten_rct_no_links() {
1218        let mut bytes: Vec<u8> = vec![
1219            0x76, 0x80, 0x0E, 0x00, 0x64, 0xC7, 0x00, 0x00, 0x07, 0xD3, 0x00, 0xF0, 0x00,
1220        ];
1221        let crc = dvb_common::crc32_mpeg2::compute(&bytes);
1222        bytes.extend_from_slice(&crc.to_be_bytes());
1223        let rct = RctSection::parse(&bytes).unwrap();
1224        assert_eq!(rct.service_id, 0x0064);
1225        assert_eq!(rct.year_offset, 0x07D3);
1226        assert!(rct.links.is_empty());
1227    }
1228
1229    #[test]
1230    fn link_type_full_range_round_trip() {
1231        for v in 0u8..=0x0F {
1232            let lt = LinkType::from_u8(v);
1233            assert_eq!(lt.to_u8(), v, "LinkType round-trip failed for {v:#04x}");
1234        }
1235    }
1236
1237    #[test]
1238    fn how_related_full_range_round_trip() {
1239        for v in 0u8..=0x3F {
1240            let hr = HowRelated::from_u8(v);
1241            assert_eq!(hr.to_u8(), v, "HowRelated round-trip failed for {v:#04x}");
1242        }
1243    }
1244
1245    #[test]
1246    fn identifier_type_full_range_round_trip() {
1247        for v in 0u8..=0x03 {
1248            let it = IdentifierType::from_u8(v);
1249            assert_eq!(
1250                it.to_u8(),
1251                v,
1252                "IdentifierType round-trip failed for 0x{v:02X}"
1253            );
1254        }
1255    }
1256
1257    #[test]
1258    fn identifier_type_known_values() {
1259        assert_eq!(IdentifierType::from_u8(0), IdentifierType::None);
1260        assert_eq!(IdentifierType::from_u8(1), IdentifierType::EventId);
1261        assert_eq!(IdentifierType::from_u8(2), IdentifierType::TvaIdEit);
1262        assert_eq!(IdentifierType::from_u8(3), IdentifierType::TvaIdPes);
1263        assert_eq!(IdentifierType::None.name(), "none");
1264        assert_eq!(IdentifierType::EventId.name(), "event_id");
1265        assert_eq!(IdentifierType::TvaIdEit.name(), "TVA_id (EIT)");
1266    }
1267}