Skip to main content

st377_1/
local_set.rs

1//! The "local set" KLV-lite framing used by every Header Metadata Set —
2//! SMPTE ST 377-1:2019 §9.3/§9.6.1 (`docs/st377-1.md`).
3//!
4//! A local set's outer Key+Length is an ordinary [`crate::KlvItem`]; its
5//! Value is a sequence of `{local_tag: u16, length: u16 | BER, value}`
6//! items (Figure 8), with the Key's byte 6 selecting which length encoding
7//! applies (§9.3 Note 1). [`LocalSet`] is the generic, identified-but-not-
8//! deeply-typed fallback this crate uses for every Header Metadata Set
9//! other than the four Root Metadata Sets (`Preface`/`Identification`/
10//! `ContentStorage`/`EssenceContainerData`) — see `docs/st377-1.md`'s Scope
11//! section.
12
13extern crate alloc;
14
15use alloc::vec::Vec;
16
17use broadcast_common::{Parse, Serialize};
18
19use crate::ber::{ber_length_size, decode_ber_length, encode_ber_length};
20use crate::error::{Error, Result};
21use crate::types::{UlBytes, ul_bytes_from_prefix};
22
23/// Byte 6 of a Local Set Key (§9.3 Note 1): which length encoding its
24/// items use.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[non_exhaustive]
28pub enum ItemLengthMode {
29    /// `0x53` — every item's length is a fixed 2-byte `UInt16` (default;
30    /// required whenever every property's value is <= 65535 bytes).
31    TwoByte,
32    /// `0x13` — every item's length is BER-encoded, short or long form
33    /// (required whenever any property's value exceeds 65535 bytes).
34    Ber,
35}
36
37impl ItemLengthMode {
38    /// The spec's own label.
39    #[must_use]
40    pub fn name(&self) -> &'static str {
41        match self {
42            Self::TwoByte => "2-byte length",
43            Self::Ber => "BER length",
44        }
45    }
46
47    /// Byte 6 value for this mode.
48    #[must_use]
49    pub fn registry_designator_byte(self) -> u8 {
50        match self {
51            Self::TwoByte => 0x53,
52            Self::Ber => 0x13,
53        }
54    }
55
56    /// Decode from a Local Set Key's byte 6. Any value other than `0x53`/
57    /// `0x13` is not a Local Set key at all (see [`is_local_set_key`]).
58    #[must_use]
59    pub fn from_registry_designator_byte(b: u8) -> Option<Self> {
60        match b {
61            0x53 => Some(Self::TwoByte),
62            0x13 => Some(Self::Ber),
63            _ => None,
64        }
65    }
66}
67
68broadcast_common::impl_spec_display!(ItemLengthMode);
69
70/// The fixed bytes of every Structural Metadata Set Key (Table 16) other
71/// than byte 6 (item length mode, [`ItemLengthMode`]), byte 8 (registry
72/// version, a wildcard on parse), and bytes 14/15 (Set Kind, see
73/// [`StructuralSetKind`]).
74const SET_KEY_FIXED: [(usize, u8); 8] = [
75    (0, 0x06),
76    (1, 0x0E),
77    (2, 0x2B),
78    (3, 0x34),
79    (4, 0x02),
80    (6, 0x01),
81    (9, 0x01),
82    (11, 0x01),
83];
84// Byte 10 (Organization) and byte 13 (Structure Kind) both `0x01` too, but
85// byte 10 doubles as part of the Application field family shared with
86// Abstract Group ULs (Table 18) which also uses `0x01`; kept explicit
87// below for clarity rather than folded into the table above.
88const SET_KEY_BYTE10: u8 = 0x01;
89const SET_KEY_BYTE12: u8 = 0x01;
90
91/// True if `key` matches the common Local Set Key structure (Table 16):
92/// fixed prefix bytes, byte 6 a valid [`ItemLengthMode`], and the fixed
93/// `0x0D`/organization/application/structure-kind bytes. Byte 15
94/// (reserved) is not checked (some dark extensions may not zero it).
95#[must_use]
96pub fn is_local_set_key(key: &UlBytes) -> bool {
97    SET_KEY_FIXED.iter().all(|&(i, v)| key[i] == v)
98        && ItemLengthMode::from_registry_designator_byte(key[5]).is_some()
99        && key[8] == 0x0D
100        && key[9] == SET_KEY_BYTE10
101        && key[10] == 0x01
102        && key[12] == SET_KEY_BYTE12
103}
104
105/// Set Kind (Table 17 — this crate's byte 14/15 identification list for
106/// every Header Metadata Set the spec defines, whether or not this crate
107/// types its properties). See `docs/st377-1.md`'s Table 17 reproduction.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[non_exhaustive]
111pub enum StructuralSetKind {
112    /// Preface (A.2) — typed, [`crate::Preface`].
113    Preface,
114    /// Identification (A.3) — typed, [`crate::Identification`].
115    Identification,
116    /// Content Storage (A.4) — typed, [`crate::ContentStorage`].
117    ContentStorage,
118    /// Essence Container Data (A.5) — typed, [`crate::EssenceContainerData`].
119    EssenceContainerData,
120    /// Material Package (E.1) — identified only.
121    MaterialPackage,
122    /// Source Package, File/Physical variants (E.2-E.4) — identified only.
123    SourcePackage,
124    /// Timeline Track, all cases (B.12/B.15/B.18/B.21/B.24/B.27.1) —
125    /// identified only.
126    TimelineTrack,
127    /// Event Track (DM) (B.13/B.27.2) — identified only.
128    EventTrackDm,
129    /// Static Track (DM) (B.14/B.27.3) — identified only.
130    StaticTrackDm,
131    /// Sequence, all cases (B.9/B.16/B.19/B.22/B.25/B.28) — identified only.
132    Sequence,
133    /// Source Clip, Picture/Sound/Data (B.10/B.20/B.23/B.26) — identified
134    /// only.
135    SourceClip,
136    /// Timecode Component (B.17) — identified only.
137    TimecodeComponent,
138    /// DM Segment (B.32) — identified only.
139    DmSegment,
140    /// DM Source Clip (B.33) — identified only.
141    DmSourceClip,
142    /// Filler (B.11) — identified only.
143    Filler,
144    /// Package Marker Object (B.34) — identified only.
145    PackageMarkerObject,
146    /// File Descriptor (F.2) — identified only.
147    FileDescriptor,
148    /// Generic Picture Essence Descriptor (F.4.1) — identified only.
149    GenericPictureEssenceDescriptor,
150    /// CDCI Essence Descriptor (F.4.2) — identified only.
151    CdciEssenceDescriptor,
152    /// RGBA Essence Descriptor (F.4.3) — identified only.
153    RgbaEssenceDescriptor,
154    /// Generic Sound Essence Descriptor (F.5) — identified only.
155    GenericSoundEssenceDescriptor,
156    /// Generic Data Essence Descriptor (F.6) — identified only.
157    GenericDataEssenceDescriptor,
158    /// Multiple Descriptor (F.3) — identified only.
159    MultipleDescriptor,
160    /// Network Locator (B.4) — identified only.
161    NetworkLocator,
162    /// Text Locator (B.5) — identified only.
163    TextLocator,
164    /// Application Plug-In Object (C.2) — identified only.
165    ApplicationPlugInObject,
166    /// Application Referenced Object (C.3) — identified only.
167    ApplicationReferencedObject,
168    /// Any byte 14/15 pair not in Table 17 (private/dark extension, or a
169    /// Set defined by another SMPTE document, e.g. an Essence Container or
170    /// Operational Pattern spec — §9.6.1 Note 3).
171    Unknown([u8; 2]),
172}
173
174impl StructuralSetKind {
175    /// The spec's own Set name (Table 17), `"unknown"` for
176    /// [`Self::Unknown`].
177    #[must_use]
178    pub fn name(&self) -> &'static str {
179        match self {
180            Self::Preface => "Preface",
181            Self::Identification => "Identification",
182            Self::ContentStorage => "Content Storage",
183            Self::EssenceContainerData => "Essence Container Data",
184            Self::MaterialPackage => "Material Package",
185            Self::SourcePackage => "Source Package",
186            Self::TimelineTrack => "Timeline Track",
187            Self::EventTrackDm => "Event Track (DM)",
188            Self::StaticTrackDm => "Static Track (DM)",
189            Self::Sequence => "Sequence",
190            Self::SourceClip => "Source Clip",
191            Self::TimecodeComponent => "Timecode Component",
192            Self::DmSegment => "DM Segment",
193            Self::DmSourceClip => "DM Source Clip",
194            Self::Filler => "Filler",
195            Self::PackageMarkerObject => "Package Marker Object",
196            Self::FileDescriptor => "File Descriptor",
197            Self::GenericPictureEssenceDescriptor => "Generic Picture Essence Descriptor",
198            Self::CdciEssenceDescriptor => "CDCI Essence Descriptor",
199            Self::RgbaEssenceDescriptor => "RGBA Essence Descriptor",
200            Self::GenericSoundEssenceDescriptor => "Generic Sound Essence Descriptor",
201            Self::GenericDataEssenceDescriptor => "Generic Data Essence Descriptor",
202            Self::MultipleDescriptor => "Multiple Descriptor",
203            Self::NetworkLocator => "Network Locator",
204            Self::TextLocator => "Text Locator",
205            Self::ApplicationPlugInObject => "Application Plug-In Object",
206            Self::ApplicationReferencedObject => "Application Referenced Object",
207            Self::Unknown(_) => "unknown",
208        }
209    }
210
211    /// Decode from a Set Key's bytes 14/15 (Table 17).
212    #[must_use]
213    pub fn from_bytes(b14: u8, b15: u8) -> Self {
214        match (b14, b15) {
215            (0x01, 0x2F) => Self::Preface,
216            (0x01, 0x30) => Self::Identification,
217            (0x01, 0x18) => Self::ContentStorage,
218            (0x01, 0x23) => Self::EssenceContainerData,
219            (0x01, 0x36) => Self::MaterialPackage,
220            (0x01, 0x37) => Self::SourcePackage,
221            (0x01, 0x3B) => Self::TimelineTrack,
222            (0x01, 0x39) => Self::EventTrackDm,
223            (0x01, 0x3A) => Self::StaticTrackDm,
224            (0x01, 0x0F) => Self::Sequence,
225            (0x01, 0x11) => Self::SourceClip,
226            (0x01, 0x14) => Self::TimecodeComponent,
227            (0x01, 0x41) => Self::DmSegment,
228            (0x01, 0x45) => Self::DmSourceClip,
229            (0x01, 0x09) => Self::Filler,
230            (0x01, 0x60) => Self::PackageMarkerObject,
231            (0x01, 0x25) => Self::FileDescriptor,
232            (0x01, 0x27) => Self::GenericPictureEssenceDescriptor,
233            (0x01, 0x28) => Self::CdciEssenceDescriptor,
234            (0x01, 0x29) => Self::RgbaEssenceDescriptor,
235            (0x01, 0x42) => Self::GenericSoundEssenceDescriptor,
236            (0x01, 0x43) => Self::GenericDataEssenceDescriptor,
237            (0x01, 0x44) => Self::MultipleDescriptor,
238            (0x01, 0x32) => Self::NetworkLocator,
239            (0x01, 0x33) => Self::TextLocator,
240            (0x01, 0x61) => Self::ApplicationPlugInObject,
241            (0x01, 0x62) => Self::ApplicationReferencedObject,
242            other => Self::Unknown([other.0, other.1]),
243        }
244    }
245
246    /// Encode to a Set Key's bytes 14/15.
247    #[must_use]
248    pub fn to_bytes(self) -> [u8; 2] {
249        match self {
250            Self::Preface => [0x01, 0x2F],
251            Self::Identification => [0x01, 0x30],
252            Self::ContentStorage => [0x01, 0x18],
253            Self::EssenceContainerData => [0x01, 0x23],
254            Self::MaterialPackage => [0x01, 0x36],
255            Self::SourcePackage => [0x01, 0x37],
256            Self::TimelineTrack => [0x01, 0x3B],
257            Self::EventTrackDm => [0x01, 0x39],
258            Self::StaticTrackDm => [0x01, 0x3A],
259            Self::Sequence => [0x01, 0x0F],
260            Self::SourceClip => [0x01, 0x11],
261            Self::TimecodeComponent => [0x01, 0x14],
262            Self::DmSegment => [0x01, 0x41],
263            Self::DmSourceClip => [0x01, 0x45],
264            Self::Filler => [0x01, 0x09],
265            Self::PackageMarkerObject => [0x01, 0x60],
266            Self::FileDescriptor => [0x01, 0x25],
267            Self::GenericPictureEssenceDescriptor => [0x01, 0x27],
268            Self::CdciEssenceDescriptor => [0x01, 0x28],
269            Self::RgbaEssenceDescriptor => [0x01, 0x29],
270            Self::GenericSoundEssenceDescriptor => [0x01, 0x42],
271            Self::GenericDataEssenceDescriptor => [0x01, 0x43],
272            Self::MultipleDescriptor => [0x01, 0x44],
273            Self::NetworkLocator => [0x01, 0x32],
274            Self::TextLocator => [0x01, 0x33],
275            Self::ApplicationPlugInObject => [0x01, 0x61],
276            Self::ApplicationReferencedObject => [0x01, 0x62],
277            Self::Unknown([a, b]) => [a, b],
278        }
279    }
280}
281
282impl core::fmt::Display for StructuralSetKind {
283    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
284        match self {
285            Self::Unknown([a, b]) => write!(f, "unknown(0x{a:02X}{b:02X})"),
286            other => f.write_str(other.name()),
287        }
288    }
289}
290
291/// One `{local_tag, value}` item inside a [`LocalSet`] (Figure 8).
292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
293pub struct LocalSetItem<'a> {
294    /// The item's 2-byte local tag.
295    pub tag: u16,
296    /// The item's value bytes (borrowed).
297    pub value: &'a [u8],
298}
299
300/// A Header Metadata Set encoded with MXF's "local set" framing (§9.3): a
301/// 16-byte Set Key identifying which Set this is (see
302/// [`StructuralSetKind`]), a BER Length, and a sequence of
303/// [`LocalSetItem`]s.
304#[derive(Debug, Clone, PartialEq, Eq)]
305pub struct LocalSet<'a> {
306    /// The Set Key (all 16 bytes, as found on the wire).
307    pub key: UlBytes,
308    /// The Set's items, in on-wire order.
309    pub items: Vec<LocalSetItem<'a>>,
310}
311
312impl<'a> LocalSet<'a> {
313    /// This Set's Kind (Table 17), from its Key's bytes 14/15.
314    #[must_use]
315    pub fn kind(&self) -> StructuralSetKind {
316        StructuralSetKind::from_bytes(self.key[13], self.key[14])
317    }
318
319    /// This Set's item length mode (Table 16 byte 6).
320    ///
321    /// `key` is a public field (callers can build a `LocalSet` directly, not
322    /// only via [`LocalSet::parse_prefix`]), so this cannot assume byte 6 is
323    /// one of the two valid values — falls back to [`ItemLengthMode::TwoByte`]
324    /// (the spec's own documented default) rather than panicking on a
325    /// self-constructed `LocalSet` with an invalid key.
326    #[must_use]
327    pub fn item_length_mode(&self) -> ItemLengthMode {
328        ItemLengthMode::from_registry_designator_byte(self.key[5])
329            .unwrap_or(ItemLengthMode::TwoByte)
330    }
331
332    /// The first item with local tag `tag`, if any.
333    #[must_use]
334    pub fn get(&self, tag: u16) -> Option<&'a [u8]> {
335        self.items.iter().find(|i| i.tag == tag).map(|i| i.value)
336    }
337
338    /// Build a fresh Local Set Key for `kind`, using `mode` for the item
339    /// length encoding and registry version `0x01`.
340    #[must_use]
341    pub fn build_key(kind: StructuralSetKind, mode: ItemLengthMode) -> UlBytes {
342        let [b14, b15] = kind.to_bytes();
343        [
344            0x06,
345            0x0E,
346            0x2B,
347            0x34,
348            0x02,
349            mode.registry_designator_byte(),
350            0x01,
351            0x01, // registry version
352            0x0D,
353            0x01,
354            0x01,
355            0x01,
356            0x01,
357            b14,
358            b15,
359            0x00,
360        ]
361    }
362
363    /// Parse one Local Set (Key + BER Length + items) from the start of
364    /// `bytes`, returning it with the total bytes consumed — use this to
365    /// walk a sequence of Header Metadata Sets in a stream.
366    pub fn parse_prefix(bytes: &'a [u8]) -> Result<(Self, usize)> {
367        if bytes.len() < 16 {
368            return Err(Error::BufferTooShort {
369                need: 16,
370                have: bytes.len(),
371                what: "Local Set key",
372            });
373        }
374        let key: UlBytes = ul_bytes_from_prefix(bytes);
375        if !is_local_set_key(&key) {
376            return Err(Error::KeyPrefixMismatch {
377                what: "Local Set (Table 16)",
378            });
379        }
380        // is_local_set_key already confirmed key[5] is a valid mode byte;
381        // TwoByte is unreachable here but kept as the same documented
382        // fallback item_length_mode() uses, rather than a second code path.
383        let mode = ItemLengthMode::from_registry_designator_byte(key[5])
384            .unwrap_or(ItemLengthMode::TwoByte);
385
386        let (len, len_size) = decode_ber_length(&bytes[16..])?;
387        let value_start = 16 + len_size;
388        let len = usize::try_from(len).map_err(|_| Error::BufferTooShort {
389            need: usize::MAX,
390            have: bytes.len(),
391            what: "Local Set value (length exceeds platform usize)",
392        })?;
393        let value_end = value_start.checked_add(len).ok_or(Error::BufferTooShort {
394            need: usize::MAX,
395            have: bytes.len(),
396            what: "Local Set value (length overflow)",
397        })?;
398        if bytes.len() < value_end {
399            return Err(Error::BufferTooShort {
400                need: value_end,
401                have: bytes.len(),
402                what: "Local Set value",
403            });
404        }
405
406        let mut cursor = &bytes[value_start..value_end];
407        let mut items = Vec::new();
408        while !cursor.is_empty() {
409            if cursor.len() < 2 {
410                return Err(Error::BufferTooShort {
411                    need: 2,
412                    have: cursor.len(),
413                    what: "Local Set item tag",
414                });
415            }
416            let tag = u16::from_be_bytes([cursor[0], cursor[1]]);
417            let rest = &cursor[2..];
418            let (item_len, item_len_size) = match mode {
419                ItemLengthMode::TwoByte => {
420                    if rest.len() < 2 {
421                        return Err(Error::BufferTooShort {
422                            need: 2,
423                            have: rest.len(),
424                            what: "Local Set item 2-byte length",
425                        });
426                    }
427                    (u64::from(u16::from_be_bytes([rest[0], rest[1]])), 2)
428                }
429                ItemLengthMode::Ber => decode_ber_length(rest)?,
430            };
431            let item_len = item_len as usize;
432            let value_start = item_len_size;
433            let value_end = value_start
434                .checked_add(item_len)
435                .ok_or(Error::BufferTooShort {
436                    need: usize::MAX,
437                    have: rest.len(),
438                    what: "Local Set item value (length overflow)",
439                })?;
440            if rest.len() < value_end {
441                return Err(Error::BufferTooShort {
442                    need: value_end,
443                    have: rest.len(),
444                    what: "Local Set item value",
445                });
446            }
447            items.push(LocalSetItem {
448                tag,
449                value: &rest[value_start..value_end],
450            });
451            cursor = &rest[value_end..];
452        }
453
454        Ok((LocalSet { key, items }, value_end))
455    }
456}
457
458impl<'a> Parse<'a> for LocalSet<'a> {
459    type Error = Error;
460
461    fn parse(bytes: &'a [u8]) -> Result<Self> {
462        let (set, consumed) = Self::parse_prefix(bytes)?;
463        if consumed != bytes.len() {
464            return Err(Error::BufferTooShort {
465                need: consumed,
466                have: bytes.len(),
467                what: "Local Set (trailing bytes after exact-fit parse)",
468            });
469        }
470        Ok(set)
471    }
472}
473
474impl Serialize for LocalSet<'_> {
475    type Error = Error;
476
477    fn serialized_len(&self) -> usize {
478        let mode = self.item_length_mode();
479        let items_len: usize = self
480            .items
481            .iter()
482            .map(|i| {
483                2 + match mode {
484                    ItemLengthMode::TwoByte => 2,
485                    ItemLengthMode::Ber => ber_length_size(i.value.len() as u64),
486                } + i.value.len()
487            })
488            .sum();
489        16 + ber_length_size(items_len as u64) + items_len
490    }
491
492    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
493        let total = self.serialized_len();
494        if buf.len() < total {
495            return Err(Error::BufferTooShort {
496                need: total,
497                have: buf.len(),
498                what: "Local Set",
499            });
500        }
501        buf[..16].copy_from_slice(&self.key);
502        let mode = self.item_length_mode();
503        let items_len: usize = self
504            .items
505            .iter()
506            .map(|i| {
507                2 + match mode {
508                    ItemLengthMode::TwoByte => 2,
509                    ItemLengthMode::Ber => ber_length_size(i.value.len() as u64),
510                } + i.value.len()
511            })
512            .sum();
513        let len_size = encode_ber_length(items_len as u64, &mut buf[16..])?;
514        let mut pos = 16 + len_size;
515        for item in &self.items {
516            buf[pos..pos + 2].copy_from_slice(&item.tag.to_be_bytes());
517            pos += 2;
518            match mode {
519                ItemLengthMode::TwoByte => {
520                    let len = u16::try_from(item.value.len()).map_err(|_| {
521                        Error::InvalidPropertyLength {
522                            tag: item.tag,
523                            name: "Local Set item",
524                            found: item.value.len(),
525                            expected: usize::from(u16::MAX),
526                        }
527                    })?;
528                    buf[pos..pos + 2].copy_from_slice(&len.to_be_bytes());
529                    pos += 2;
530                }
531                ItemLengthMode::Ber => {
532                    pos += encode_ber_length(item.value.len() as u64, &mut buf[pos..])?;
533                }
534            }
535            buf[pos..pos + item.value.len()].copy_from_slice(item.value);
536            pos += item.value.len();
537        }
538        Ok(pos)
539    }
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545
546    #[test]
547    fn structural_set_kind_round_trips_every_named_variant() {
548        let variants = [
549            StructuralSetKind::Preface,
550            StructuralSetKind::Identification,
551            StructuralSetKind::ContentStorage,
552            StructuralSetKind::EssenceContainerData,
553            StructuralSetKind::MaterialPackage,
554            StructuralSetKind::SourcePackage,
555            StructuralSetKind::TimelineTrack,
556            StructuralSetKind::EventTrackDm,
557            StructuralSetKind::StaticTrackDm,
558            StructuralSetKind::Sequence,
559            StructuralSetKind::SourceClip,
560            StructuralSetKind::TimecodeComponent,
561            StructuralSetKind::DmSegment,
562            StructuralSetKind::DmSourceClip,
563            StructuralSetKind::Filler,
564            StructuralSetKind::PackageMarkerObject,
565            StructuralSetKind::FileDescriptor,
566            StructuralSetKind::GenericPictureEssenceDescriptor,
567            StructuralSetKind::CdciEssenceDescriptor,
568            StructuralSetKind::RgbaEssenceDescriptor,
569            StructuralSetKind::GenericSoundEssenceDescriptor,
570            StructuralSetKind::GenericDataEssenceDescriptor,
571            StructuralSetKind::MultipleDescriptor,
572            StructuralSetKind::NetworkLocator,
573            StructuralSetKind::TextLocator,
574            StructuralSetKind::ApplicationPlugInObject,
575            StructuralSetKind::ApplicationReferencedObject,
576        ];
577        for v in variants {
578            let bytes = v.to_bytes();
579            assert_eq!(StructuralSetKind::from_bytes(bytes[0], bytes[1]), v);
580        }
581        assert_eq!(
582            StructuralSetKind::from_bytes(0xFE, 0xFD),
583            StructuralSetKind::Unknown([0xFE, 0xFD])
584        );
585    }
586
587    #[test]
588    fn local_set_round_trip_two_byte_mode() {
589        let key = LocalSet::build_key(StructuralSetKind::Preface, ItemLengthMode::TwoByte);
590        let set = LocalSet {
591            key,
592            items: alloc::vec![
593                LocalSetItem {
594                    tag: 0x3B02,
595                    value: &[1, 2, 3, 4, 5, 6, 7, 8],
596                },
597                LocalSetItem {
598                    tag: 0x3B05,
599                    value: &[0x01, 0x03],
600                },
601            ],
602        };
603        let mut buf = alloc::vec![0u8; set.serialized_len()];
604        set.serialize_into(&mut buf).unwrap();
605        let parsed = LocalSet::parse(&buf).unwrap();
606        assert_eq!(parsed, set);
607        assert_eq!(parsed.kind(), StructuralSetKind::Preface);
608        assert_eq!(parsed.get(0x3B02), Some(&[1, 2, 3, 4, 5, 6, 7, 8][..]));
609    }
610
611    #[test]
612    fn local_set_round_trip_ber_mode() {
613        let key = LocalSet::build_key(StructuralSetKind::Identification, ItemLengthMode::Ber);
614        let big_value = alloc::vec![0x42u8; 70000];
615        let set = LocalSet {
616            key,
617            items: alloc::vec![LocalSetItem {
618                tag: 0x3C01,
619                value: &big_value,
620            }],
621        };
622        let mut buf = alloc::vec![0u8; set.serialized_len()];
623        set.serialize_into(&mut buf).unwrap();
624        let parsed = LocalSet::parse(&buf).unwrap();
625        assert_eq!(parsed, set);
626        assert_eq!(parsed.item_length_mode(), ItemLengthMode::Ber);
627    }
628
629    #[test]
630    fn non_local_set_key_rejected() {
631        let bytes = [0u8; 20];
632        assert!(matches!(
633            LocalSet::parse_prefix(&bytes),
634            Err(Error::KeyPrefixMismatch { .. })
635        ));
636    }
637}