Skip to main content

miden_mast_package/package/
section.rs

1#[cfg(feature = "arbitrary")]
2use alloc::vec;
3use alloc::{
4    borrow::{Cow, ToOwned},
5    format,
6    string::ToString,
7};
8use core::{fmt, str::FromStr};
9
10use miden_assembly_syntax::DisplayHex;
11use miden_core::serde::{
12    ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
13};
14#[cfg(feature = "arbitrary")]
15use proptest::prelude::*;
16#[cfg(feature = "serde")]
17use serde::{Deserialize, Serialize};
18
19/// A unique identifier for optional sections of the Miden package format
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22#[cfg_attr(feature = "serde", serde(transparent))]
23#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
24#[repr(transparent)]
25pub struct SectionId(Cow<'static, str>);
26
27impl SectionId {
28    /// The section containing debug type definitions (primitives, structs, arrays, pointers,
29    /// function types)
30    pub const DEBUG_TYPES: Self = Self(Cow::Borrowed("debug_types"));
31    /// The section containing debug source file paths and checksums
32    pub const DEBUG_SOURCES: Self = Self(Cow::Borrowed("debug_sources"));
33    /// The section containing debug function metadata, variables, and inlined calls
34    pub const DEBUG_FUNCTIONS: Self = Self(Cow::Borrowed("debug_functions"));
35    /// This section provides the encoded metadata for a compiled account component
36    ///
37    /// Currently, this corresponds to the serialized representation of
38    /// `miden-protocol::account::AccountComponentMetadata`, i.e. name, descrioption, storage, that
39    /// is associated with this package.
40    pub const ACCOUNT_COMPONENT_METADATA: Self = Self(Cow::Borrowed("account_component_metadata"));
41    /// This section contains provenance metadata for packages assembled from project sources.
42    pub const PROJECT_SOURCE_PROVENANCE: Self = Self(Cow::Borrowed("project_source_provenance"));
43    /// This section contains the serialized kernel package linked against by an executable package.
44    pub const KERNEL: Self = Self(Cow::Borrowed("kernel"));
45
46    /// Construct a user-defined (i.e. "custom") section identifier
47    ///
48    /// Section identifiers must be either an ASCII alphanumeric, or one of the following
49    /// characters: `.`, `_`, `-`. Additionally, the identifier must start with an ASCII alphabetic
50    /// character or `_`.
51    pub fn custom(name: impl AsRef<str>) -> Result<Self, InvalidSectionIdError> {
52        let name = name.as_ref();
53        if !name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
54            return Err(InvalidSectionIdError::InvalidStart);
55        }
56        if name.contains(|c: char| !c.is_ascii_alphanumeric() && !matches!(c, '.' | '_' | '-')) {
57            return Err(InvalidSectionIdError::InvalidCharacter);
58        }
59        Ok(Self(name.to_string().into()))
60    }
61
62    /// Get this section identifier as a string
63    #[inline]
64    pub fn as_str(&self) -> &str {
65        self.0.as_ref()
66    }
67}
68
69#[derive(Debug, thiserror::Error)]
70pub enum InvalidSectionIdError {
71    #[error("invalid section id: cannot be empty")]
72    Empty,
73    #[error(
74        "invalid section id: contains invalid characters, only the set [a-z0-9._-] are allowed"
75    )]
76    InvalidCharacter,
77    #[error("invalid section id: must start with a character in the set [a-z_]")]
78    InvalidStart,
79}
80
81impl FromStr for SectionId {
82    type Err = InvalidSectionIdError;
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        match s {
85            "debug_types" => Ok(Self::DEBUG_TYPES),
86            "debug_sources" => Ok(Self::DEBUG_SOURCES),
87            "debug_functions" => Ok(Self::DEBUG_FUNCTIONS),
88            "account_component_metadata" => Ok(Self::ACCOUNT_COMPONENT_METADATA),
89            "project_source_provenance" => Ok(Self::PROJECT_SOURCE_PROVENANCE),
90            "kernel" => Ok(Self::KERNEL),
91            custom => Self::custom(custom),
92        }
93    }
94}
95
96impl fmt::Display for SectionId {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        f.write_str(self.as_str())
99    }
100}
101
102#[derive(Clone, PartialEq, Eq)]
103#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
104pub struct Section {
105    pub id: SectionId,
106    pub data: Cow<'static, [u8]>,
107}
108
109impl fmt::Debug for Section {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        let verbose = f.alternate();
112        let mut builder = f.debug_struct("Section");
113        builder.field("id", &format_args!("{}", &self.id));
114        if verbose {
115            builder.field("data", &format_args!("{}", DisplayHex(&self.data))).finish()
116        } else {
117            builder.field("data", &format_args!("{} bytes", self.data.len())).finish()
118        }
119    }
120}
121
122impl Section {
123    pub fn new<B>(id: SectionId, data: B) -> Self
124    where
125        B: Into<Cow<'static, [u8]>>,
126    {
127        Self { id, data: data.into() }
128    }
129
130    /// Returns true if this section is empty, i.e. has no data
131    pub fn is_empty(&self) -> bool {
132        self.data.is_empty()
133    }
134
135    /// Returns the size in bytes of this section's data
136    pub fn len(&self) -> usize {
137        self.data.len()
138    }
139}
140
141impl Serializable for Section {
142    fn write_into<W: ByteWriter>(&self, target: &mut W) {
143        let id = self.id.as_str();
144        target.write_usize(id.len());
145        target.write_bytes(id.as_bytes());
146        target.write_usize(self.len());
147        target.write_bytes(&self.data);
148    }
149}
150
151impl Deserializable for Section {
152    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
153        let id_len = source.read_usize()?;
154        let id_bytes = source.read_slice(id_len)?;
155        let id_str = core::str::from_utf8(id_bytes).map_err(|err| {
156            DeserializationError::InvalidValue(format!("invalid utf-8 in section name: {err}"))
157        })?;
158        let id = SectionId(Cow::Owned(id_str.to_owned()));
159
160        let len = source.read_usize()?;
161        let bytes = source.read_slice(len)?;
162        Ok(Section { id, data: Cow::Owned(bytes.to_owned()) })
163    }
164}
165
166#[cfg(feature = "arbitrary")]
167impl Arbitrary for SectionId {
168    type Parameters = ();
169    type Strategy = BoxedStrategy<Self>;
170
171    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
172        use alloc::string::String;
173
174        let builtins = proptest::sample::select(vec![
175            Self::DEBUG_TYPES,
176            Self::DEBUG_SOURCES,
177            Self::DEBUG_FUNCTIONS,
178            Self::ACCOUNT_COMPONENT_METADATA,
179            Self::PROJECT_SOURCE_PROVENANCE,
180            Self::KERNEL,
181        ]);
182
183        let custom = (
184            proptest::prop_oneof![
185                proptest::char::range('a', 'z'),
186                proptest::char::range('A', 'Z'),
187                Just('_'),
188            ],
189            proptest::collection::vec(
190                proptest::prop_oneof![
191                    proptest::char::range('a', 'z'),
192                    proptest::char::range('A', 'Z'),
193                    proptest::char::range('0', '9'),
194                    Just('.'),
195                    Just('_'),
196                    Just('-'),
197                ],
198                0..31,
199            ),
200        )
201            .prop_map(|(first, rest)| {
202                let mut name = String::with_capacity(rest.len() + 1);
203                name.push(first);
204                name.extend(rest);
205                Self::custom(name).expect("generated custom section ids are valid")
206            });
207
208        proptest::prop_oneof![builtins, custom].boxed()
209    }
210}