Skip to main content

wasmbin/
sections.rs

1//! [Module sections](https://webassembly.github.io/spec/core/binary/modules.html#sections).
2
3// Copyright 2020 Google Inc. All Rights Reserved.
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17use crate::builtins::{Blob, Lazy, UnparsedBytes, WasmbinCountable};
18#[cfg(feature = "extended-name-section")]
19use crate::indices::{DataId, ElemId, LabelId};
20use crate::indices::{ExceptionId, FuncId, GlobalId, LocalId, MemId, TableId, TypeId};
21use crate::instructions::Expression;
22use crate::io::{Decode, DecodeError, DecodeWithDiscriminant, Encode, PathItem, Wasmbin};
23use crate::types::{
24    ExceptionType, GlobalType, MemType, RecursiveType, RefType, TableType, ValueType,
25};
26use crate::visit::{Visit, VisitError};
27use custom_debug::Debug as CustomDebug;
28use std::convert::TryFrom;
29use thiserror::Error;
30
31/// A [name association](https://webassembly.github.io/spec/core/appendix/custom.html#binary-namemap) key-value pair.
32///
33/// Might also be used to represent an [indirect name association](https://webassembly.github.io/spec/core/appendix/custom.html#binary-indirectnamemap).
34#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
35pub struct NameAssoc<I, V = String> {
36    pub index: I,
37    pub value: V,
38}
39
40impl<I, V> WasmbinCountable for NameAssoc<I, V> {}
41
42/// [Name map](https://webassembly.github.io/spec/core/appendix/custom.html#binary-namemap).
43#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
44pub struct NameMap<I, V = String> {
45    pub items: Vec<NameAssoc<I, V>>,
46}
47
48/// [Indirect name map](https://webassembly.github.io/spec/core/appendix/custom.html#binary-indirectnamemap).
49pub type IndirectNameMap<I1, I2> = NameMap<I1, NameMap<I2>>;
50
51/// [Name subsection](https://webassembly.github.io/spec/core/appendix/custom.html#subsections).
52#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
53#[repr(u8)]
54pub enum NameSubSection {
55    /// [Module name](https://webassembly.github.io/spec/core/appendix/custom.html#module-names).
56    Module(Blob<String>) = 0,
57    /// [Function names](https://webassembly.github.io/spec/core/appendix/custom.html#function-names).
58    Func(Blob<NameMap<FuncId>>) = 1,
59    /// [Local names](https://webassembly.github.io/spec/core/appendix/custom.html#local-names) grouped by function index.
60    Local(Blob<IndirectNameMap<FuncId, LocalId>>) = 2,
61    #[cfg(feature = "extended-name-section")]
62    /// [Label names](https://www.scheidecker.net/2019-07-08-extended-name-section-spec/appendix/custom.html#label-names) grouped by function index.
63    Label(Blob<IndirectNameMap<FuncId, LabelId>>) = 3,
64    #[cfg(feature = "extended-name-section")]
65    /// [Type names](https://www.scheidecker.net/2019-07-08-extended-name-section-spec/appendix/custom.html#type-names).
66    Type(Blob<NameMap<TypeId>>) = 4,
67    #[cfg(feature = "extended-name-section")]
68    /// [Table names](https://www.scheidecker.net/2019-07-08-extended-name-section-spec/appendix/custom.html#table-names).
69    Table(Blob<NameMap<TableId>>) = 5,
70    #[cfg(feature = "extended-name-section")]
71    /// [Memory names](https://www.scheidecker.net/2019-07-08-extended-name-section-spec/appendix/custom.html#memory-names).
72    Memory(Blob<NameMap<MemId>>) = 6,
73    #[cfg(feature = "extended-name-section")]
74    /// [Global names](https://www.scheidecker.net/2019-07-08-extended-name-section-spec/appendix/custom.html#global-names).
75    Global(Blob<NameMap<GlobalId>>) = 7,
76    #[cfg(feature = "extended-name-section")]
77    /// Element segment names.
78    Elem(Blob<NameMap<ElemId>>) = 8,
79    #[cfg(feature = "extended-name-section")]
80    /// Data segment names.
81    Data(Blob<NameMap<DataId>>) = 9,
82}
83
84impl Encode for [NameSubSection] {
85    fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
86        for sub in self {
87            sub.encode(w)?;
88        }
89        Ok(())
90    }
91}
92
93impl Decode for Vec<NameSubSection> {
94    fn decode(r: &mut impl std::io::Read) -> Result<Self, DecodeError> {
95        let mut sub = Vec::new();
96        while let Some(disc) = Option::decode(r)? {
97            let i = sub.len();
98            sub.push(
99                NameSubSection::decode_with_discriminant(disc, r)
100                    .map_err(move |err| err.in_path(PathItem::Index(i)))?,
101            );
102        }
103        Ok(sub)
104    }
105}
106
107/// [`producer`](https://github.com/WebAssembly/tool-conventions/blob/08bacbed7d0daff49808370cd93b6a6f0c962d76/ProducersSection.md#custom-section) field.
108#[derive(Wasmbin, WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
109pub struct ProducerField {
110    pub name: String,
111    pub values: Vec<ProducerVersionedName>,
112}
113
114/// [`producer`](https://github.com/WebAssembly/tool-conventions/blob/08bacbed7d0daff49808370cd93b6a6f0c962d76/ProducersSection.md#custom-section) `versioned-name` structure.
115#[derive(Wasmbin, WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
116pub struct ProducerVersionedName {
117    pub name: String,
118    pub version: String,
119}
120
121/// A raw [custom section](https://webassembly.github.io/spec/core/binary/modules.html#custom-section).
122///
123/// Used to represent custom sections with unknown semantics.
124#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
125pub struct RawCustomSection {
126    pub name: String,
127    pub data: UnparsedBytes,
128}
129
130macro_rules! define_custom_sections {
131    ($(#[doc = $url:literal] $name:ident($ty:ty) = $disc:literal,)*) => {
132        /// A [custom section](https://webassembly.github.io/spec/core/binary/modules.html#custom-section).
133        ///
134        /// This enum supports some non-standard custom sections commonly used in tooling, but is marked
135        /// as non-exhaustive to allow for future additions that would transform some sections
136        /// currently represented by the [`Other`](CustomSection::Other) variant into new variants.
137        #[derive(Debug, PartialEq, Eq, Hash, Clone)]
138        #[non_exhaustive]
139        pub enum CustomSection {
140            $(
141                #[doc = "[`"]
142                #[doc = $disc]
143                #[doc = "`]("]
144                #[doc = $url]
145                #[doc = ") custom section."]
146                $name($ty),
147            )*
148            /// A custom section that is not recognized by this library.
149            Other(RawCustomSection),
150        }
151
152        impl CustomSection {
153            /// Name of this custom section.
154            pub fn name(&self) -> &str {
155                match self {
156                    $(Self::$name(_) => $disc,)*
157                    Self::Other(raw) => raw.name.as_str(),
158                }
159            }
160        }
161
162        impl Encode for CustomSection {
163            fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
164                match self {
165                    $(CustomSection::$name(data) => {
166                        $disc.encode(w)?;
167                        data.encode(w)
168                    })*
169                    CustomSection::Other(raw) => raw.encode(w)
170                }
171            }
172        }
173
174        impl Decode for CustomSection {
175            fn decode(r: &mut impl std::io::Read) -> Result<Self, DecodeError> {
176                let name = String::decode(r)?;
177                Ok(match name.as_str() {
178                    $($disc => CustomSection::$name(<$ty>::decode(r)?),)*
179                    _ => CustomSection::Other(RawCustomSection {
180                        name,
181                        data: UnparsedBytes::decode(r)?
182                    })
183                })
184            }
185        }
186
187        impl Visit for CustomSection {
188            fn visit_children<'a, VisitT: 'static, E, F: FnMut(&'a VisitT) -> Result<(), E>>(
189                &'a self,
190                f: &mut F,
191            ) -> Result<(), VisitError<E>> {
192                // Custom section decoding errors must be ignored.
193                drop(match self {
194                    $(CustomSection::$name(data) => Visit::visit_child(data, f),)*
195                    CustomSection::Other(raw) => Visit::visit_child(raw, f),
196                });
197                Ok(())
198            }
199
200            fn visit_children_mut<VisitT: 'static, E, F: FnMut(&mut VisitT) -> Result<(), E>>(
201                &mut self,
202                f: &mut F,
203            ) -> Result<(), VisitError<E>> {
204                // Custom section decoding errors must be ignored.
205                drop(match self {
206                    $(CustomSection::$name(data) => Visit::visit_child_mut(data, f),)*
207                    CustomSection::Other(raw) => Visit::visit_child_mut(raw, f),
208                });
209                Ok(())
210            }
211        }
212    };
213}
214
215define_custom_sections! {
216    /// https://webassembly.github.io/spec/core/appendix/custom.html#name-section
217    Name(Lazy<Vec<NameSubSection>>) = "name",
218    /// https://github.com/WebAssembly/tool-conventions/blob/08bacbed7d0daff49808370cd93b6a6f0c962d76/ProducersSection.md
219    Producers(Lazy<Vec<ProducerField>>) = "producers",
220    /// https://github.com/WebAssembly/tool-conventions/blob/08bacbed/Debugging.md#external-dwarf
221    ExternalDebugInfo(Lazy<String>) = "external_debug_info",
222    /// https://github.com/WebAssembly/tool-conventions/blob/08bacbed/Debugging.md#source-maps
223    SourceMappingUrl(Lazy<String>) = "sourceMappingURL",
224    /// https://github.com/WebAssembly/tool-conventions/blob/9b80cd2339c648822bb845a083d9ffa6e20fb1ee/BuildId.md
225    BuildId(Vec<u8>) = "build_id",
226}
227
228/// [Import descriptor](https://webassembly.github.io/spec/core/binary/modules.html#binary-importdesc).
229#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
230#[repr(u8)]
231pub enum ImportDesc {
232    Func(TypeId) = 0x00,
233    Table(TableType) = 0x01,
234    Mem(MemType) = 0x02,
235    Global(GlobalType) = 0x03,
236    Exception(ExceptionType) = 0x04,
237}
238
239/// [Import](https://webassembly.github.io/spec/core/binary/modules.html#import-section) path.
240#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
241pub struct ImportPath {
242    pub module: String,
243    pub name: String,
244}
245
246/// A single [import](https://webassembly.github.io/spec/core/binary/modules.html#binary-import).
247#[derive(Wasmbin, WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
248pub struct Import {
249    pub path: ImportPath,
250    pub desc: ImportDesc,
251}
252
253/// A single [global](https://webassembly.github.io/spec/core/binary/modules.html#binary-global).
254#[derive(Wasmbin, WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
255pub struct Global {
256    pub ty: GlobalType,
257    pub init: Expression,
258}
259
260/// [Export descriptor](https://webassembly.github.io/spec/core/binary/modules.html#binary-exportdesc).
261#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
262#[repr(u8)]
263pub enum ExportDesc {
264    Func(FuncId) = 0x00,
265    Table(TableId) = 0x01,
266    Mem(MemId) = 0x02,
267    Global(GlobalId) = 0x03,
268    Exception(ExceptionId) = 0x04,
269}
270
271/// A single [export](https://webassembly.github.io/spec/core/binary/modules.html#binary-export).
272#[derive(Wasmbin, WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
273pub struct Export {
274    pub name: String,
275    pub desc: ExportDesc,
276}
277
278/// [Element kind](https://webassembly.github.io/spec/core/binary/modules.html#binary-elemkind).
279#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
280#[repr(u8)]
281pub enum ElemKind {
282    FuncRef = 0x00,
283}
284
285/// A single [element](https://webassembly.github.io/spec/core/binary/modules.html#binary-elem).
286#[derive(Wasmbin, WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
287#[repr(u32)]
288pub enum Element {
289    ActiveWithFuncs {
290        offset: Expression,
291        funcs: Vec<FuncId>,
292    } = 0,
293    PassiveWithFuncs {
294        kind: ElemKind,
295        funcs: Vec<FuncId>,
296    } = 1,
297    ActiveWithTableAndFuncs {
298        table: TableId,
299        offset: Expression,
300        kind: ElemKind,
301        funcs: Vec<FuncId>,
302    } = 2,
303    DeclarativeWithFuncs {
304        kind: ElemKind,
305        funcs: Vec<FuncId>,
306    } = 3,
307    ActiveWithExprs {
308        offset: Expression,
309        exprs: Vec<Expression>,
310    } = 4,
311    PassiveWithExprs {
312        ty: RefType,
313        exprs: Vec<Expression>,
314    } = 5,
315    ActiveWithTableAndExprs {
316        table: TableId,
317        offset: Expression,
318        ty: RefType,
319        exprs: Vec<Expression>,
320    } = 6,
321    DeclarativeWithExprs {
322        ty: RefType,
323        exprs: Vec<Expression>,
324    } = 7,
325}
326
327/// Number of repeated consecutive [locals](https://webassembly.github.io/spec/core/binary/modules.html#binary-local) of a single type.
328#[derive(Wasmbin, WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
329pub struct Locals {
330    pub repeat: u32,
331    pub ty: ValueType,
332}
333
334/// [Exception tag](https://webassembly.github.io/exception-handling/core/binary/modules.html#exception-section).
335#[derive(Wasmbin, WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
336#[wasmbin(discriminant = 0x00)]
337pub struct Exception {
338    pub ty: TypeId,
339}
340
341/// A single [table](https://webassembly.github.io/spec/core/binary/modules.html#binary-table).
342#[derive(WasmbinCountable, Debug, PartialEq, Eq, Hash, Clone, Visit)]
343pub struct Table {
344    pub table_type: TableType,
345    pub expr: Option<Expression>,
346}
347
348impl Encode for Table {
349    fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
350        if let Some(expr) = &self.expr {
351            0x40u8.encode(w)?; // 0x40 indicates a table with init expression
352            0x00u8.encode(w)?;
353            self.table_type.encode(w)?;
354            expr.encode(w)?;
355        } else {
356            self.table_type.encode(w)?;
357        }
358        Ok(())
359    }
360}
361
362impl Decode for Table {
363    fn decode(r: &mut impl std::io::Read) -> Result<Self, DecodeError> {
364        // 0x40 indicates a table with init expression
365        let discriminant1 = u8::decode(r)?;
366        if discriminant1 == 0x40 {
367            // Second discriminant reserved for future extensions
368            let discriminant2 = u8::decode(r)?;
369            if discriminant2 == 0x00 {
370                return Ok(Self {
371                    table_type: TableType::decode(r)?,
372                    expr: Some(Expression::decode(r)?),
373                });
374            }
375            return Err(DecodeError::unsupported_discriminant::<Self>(discriminant2));
376        }
377        // Table without init expression
378        let buf = [discriminant1];
379        let mut r = std::io::Read::chain(&buf[..], r);
380        Ok(Self {
381            table_type: TableType::decode(&mut r)?,
382            expr: None,
383        })
384    }
385}
386
387/// [Function body](https://webassembly.github.io/spec/core/binary/modules.html#binary-func).
388#[derive(Wasmbin, WasmbinCountable, Debug, Default, PartialEq, Eq, Hash, Clone, Visit)]
389pub struct FuncBody {
390    pub locals: Vec<Locals>,
391    pub expr: Expression,
392}
393
394/// [`Data`] segment initialization.
395#[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
396#[repr(u32)]
397pub enum DataInit {
398    Active { offset: Expression } = 0,
399    Passive = 1,
400    ActiveWithMemory { memory: MemId, offset: Expression } = 2,
401}
402
403/// [Data segment](https://webassembly.github.io/spec/core/binary/modules.html#binary-data).
404#[derive(Wasmbin, WasmbinCountable, CustomDebug, PartialEq, Eq, Hash, Clone, Visit)]
405pub struct Data {
406    pub init: DataInit,
407    #[debug(with = "custom_debug::hexbuf_str")]
408    pub blob: Vec<u8>,
409}
410
411pub(crate) trait Payload: Encode + Decode + Into<Section> {
412    const KIND: Kind;
413
414    fn try_from_ref(section: &Section) -> Option<&Blob<Self>>;
415    fn try_from_mut(section: &mut Section) -> Option<&mut Blob<Self>>;
416}
417
418/// A common marker trait for the [standard payloads](payload).
419#[expect(private_bounds)]
420pub trait StdPayload: Payload {}
421
422macro_rules! define_sections {
423    ($($(# $attr:tt)* $name:ident($(# $ty_attr:tt)* $ty:ty) = $disc:literal,)*) => {
424        /// Payload types of the [`Section`] variants.
425        pub mod payload {
426            $($(# $attr)* pub type $name = $ty;)*
427        }
428
429        /// [Module section](https://webassembly.github.io/spec/core/binary/modules.html#sections).
430        #[derive(Wasmbin, Debug, PartialEq, Eq, Hash, Clone, Visit)]
431        #[repr(u8)]
432        pub enum Section {
433            $($(# $attr)* $name($(# $ty_attr)* Blob<payload::$name>) = $disc,)*
434        }
435
436        /// A kind of the [`Section`] without the payload.
437        #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
438        #[repr(u8)]
439        pub enum Kind {
440            $($(# $attr)* $name = $disc,)*
441        }
442
443        impl TryFrom<u8> for Kind {
444            type Error = u8;
445
446            fn try_from(discriminant: u8) -> Result<Kind, u8> {
447                #[expect(unused_doc_comments)]
448                Ok(match discriminant {
449                    $($(# $attr)* $disc => Kind::$name,)*
450                    _ => return Err(discriminant),
451                })
452            }
453        }
454
455        impl Ord for Kind {
456            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
457                // Some new sections might have larger discriminants,
458                // but be ordered logically between those will smaller
459                // discriminants.
460                //
461                // To compare their Kinds in a defined order, we need an
462                // intermediate enum without discriminants.
463                #[derive(PartialEq, Eq, PartialOrd, Ord)]
464                #[repr(u8)]
465                enum OrderedRepr {
466                    $($(# $attr)* $name,)*
467                }
468
469                impl From<Kind> for OrderedRepr {
470                    fn from(kind: Kind) -> Self {
471                        #[expect(unused_doc_comments)]
472                        match kind {
473                            $($(# $attr)* Kind::$name => Self::$name,)*
474                        }
475                    }
476                }
477
478                OrderedRepr::from(*self).cmp(&OrderedRepr::from(*other))
479            }
480        }
481
482        impl PartialOrd for Kind {
483            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
484                Some(self.cmp(other))
485            }
486        }
487
488        $($(# $attr)* const _: () = {
489            impl From<Blob<payload::$name>> for Section {
490                fn from(value: Blob<payload::$name>) -> Self {
491                    Section::$name(value)
492                }
493            }
494
495            impl From<payload::$name> for Section {
496                fn from(value: payload::$name) -> Self {
497                    Section::$name(Blob::from(value))
498                }
499            }
500
501            impl Payload for payload::$name {
502                const KIND: Kind = Kind::$name;
503
504                fn try_from_ref(section: &Section) -> Option<&Blob<Self>> {
505                    match section {
506                        Section::$name(res) => Some(res),
507                        _ => None,
508                    }
509                }
510
511                fn try_from_mut(section: &mut Section) -> Option<&mut Blob<Self>> {
512                    match section {
513                        Section::$name(res) => Some(res),
514                        _ => None,
515                    }
516                }
517            }
518        };)*
519
520        impl Section {
521            /// Get the kind of the section without its payload.
522            pub fn kind(&self) -> Kind {
523                #[expect(unused_doc_comments)]
524                match self {
525                    $($(# $attr)* Section::$name(_) => Kind::$name,)*
526                }
527            }
528
529            /// Try to interpret the section as a specific payload.
530            pub fn try_as<T: StdPayload>(&self) -> Option<&Blob<T>> {
531                T::try_from_ref(self)
532            }
533
534            /// Try to interpret the section as a specific payload mutably.
535            pub fn try_as_mut<T: StdPayload>(&mut self) -> Option<&mut Blob<T>> {
536                T::try_from_mut(self)
537            }
538        }
539
540        define_sections!(@std $($(# $attr)* $name)*);
541    };
542
543    (@std $(# $ignore_custom_attr:tt)* $ignore_custom:ident $($(# $attr:tt)* $name:ident)*) => {
544        $($(# $attr)* impl StdPayload for payload::$name {})*
545    };
546}
547
548define_sections! {
549    /// [Custom section](https://webassembly.github.io/spec/core/binary/modules.html#custom-section).
550    Custom(super::CustomSection) = 0,
551    /// [Type section](https://webassembly.github.io/spec/core/binary/modules.html#type-section).
552    Type(Vec<super::RecursiveType>) = 1,
553    /// [Import section](https://webassembly.github.io/spec/core/binary/modules.html#import-section).
554    Import(Vec<super::Import>) = 2,
555    /// [Function section](https://webassembly.github.io/spec/core/binary/modules.html#function-section).
556    Function(Vec<super::TypeId>) = 3,
557    /// [Table section](https://webassembly.github.io/spec/core/binary/modules.html#table-section).
558    Table(Vec<super::Table>) = 4,
559    /// [Memory section](https://webassembly.github.io/spec/core/binary/modules.html#memory-section).
560    Memory(Vec<super::MemType>) = 5,
561    /// [Exception tag section](https://webassembly.github.io/exception-handling/core/binary/modules.html#tag-section).
562    Exception(Vec<super::Exception>) = 13,
563    /// [Global section](https://webassembly.github.io/spec/core/binary/modules.html#global-section).
564    Global(Vec<super::Global>) = 6,
565    /// [Export section](https://webassembly.github.io/spec/core/binary/modules.html#export-section).
566    Export(Vec<super::Export>) = 7,
567    /// [Start section](https://webassembly.github.io/spec/core/binary/modules.html#start-section).
568    Start(
569        /// [Start function](https://webassembly.github.io/spec/core/syntax/modules.html#syntax-start).
570        super::FuncId
571    ) = 8,
572    /// [Element section](https://webassembly.github.io/spec/core/binary/modules.html#element-section).
573    Element(Vec<super::Element>) = 9,
574    /// [Data count section](https://webassembly.github.io/spec/core/binary/modules.html#binary-datacountsec).
575    DataCount(
576        /// Number of data segments in the [`Data`](Section::Data) section.
577        u32
578    ) = 12,
579    /// [Code section](https://webassembly.github.io/spec/core/binary/modules.html#code-section).
580    Code(Vec<super::Blob<super::FuncBody>>) = 10,
581    /// [Data section](https://webassembly.github.io/spec/core/binary/modules.html#data-section).
582    Data(Vec<super::Data>) = 11,
583}
584
585/// Error returned when a section is out of order.
586#[derive(Debug, Error)]
587#[error("Section out of order: {current:?} after {prev:?}")]
588pub struct SectionOrderError {
589    pub current: Kind,
590    pub prev: Kind,
591}
592
593impl From<SectionOrderError> for std::io::Error {
594    fn from(err: SectionOrderError) -> Self {
595        Self::new(std::io::ErrorKind::InvalidData, err)
596    }
597}
598
599struct SectionOrderTracker {
600    last_kind: Kind,
601}
602
603impl Default for SectionOrderTracker {
604    fn default() -> Self {
605        Self {
606            last_kind: Kind::Custom,
607        }
608    }
609}
610
611impl SectionOrderTracker {
612    fn try_add(&mut self, section: &Section) -> Result<(), SectionOrderError> {
613        match section.kind() {
614            Kind::Custom => {}
615            kind if kind > self.last_kind => {
616                self.last_kind = kind;
617            }
618            kind => {
619                return Err(SectionOrderError {
620                    prev: self.last_kind,
621                    current: kind,
622                });
623            }
624        }
625        Ok(())
626    }
627}
628
629impl Encode for [Section] {
630    fn encode(&self, w: &mut impl std::io::Write) -> std::io::Result<()> {
631        let mut section_order_tracker = SectionOrderTracker::default();
632        for section in self {
633            section_order_tracker.try_add(section)?;
634            section.encode(w)?;
635        }
636        Ok(())
637    }
638}
639
640impl Decode for Vec<Section> {
641    fn decode(r: &mut impl std::io::Read) -> Result<Self, DecodeError> {
642        let mut sections = Vec::new();
643        let mut section_order_tracker = SectionOrderTracker::default();
644        while let Some(disc) = Option::decode(r)? {
645            let i = sections.len();
646            (|| -> Result<(), DecodeError> {
647                let section = Section::decode_with_discriminant(disc, r)?;
648                section_order_tracker.try_add(&section)?;
649                sections.push(section);
650                Ok(())
651            })()
652            .map_err(move |err| err.in_path(PathItem::Index(i)))?;
653        }
654        Ok(sections)
655    }
656}