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 a serialized [`crate::debug_info::PackageDebugInfo`] struct
29    pub const DEBUG_INFO: Self = Self(Cow::Borrowed("debug_info"));
30    /// This section provides the encoded metadata for a compiled account component
31    ///
32    /// Currently, this corresponds to the serialized representation of
33    /// `miden-protocol::account::AccountComponentMetadata`, i.e. name, descrioption, storage, that
34    /// is associated with this package.
35    pub const ACCOUNT_COMPONENT_METADATA: Self = Self(Cow::Borrowed("account_component_metadata"));
36    /// This section contains provenance metadata for packages assembled from project sources.
37    pub const PROJECT_SOURCE_PROVENANCE: Self = Self(Cow::Borrowed("project_source_provenance"));
38    /// This section contains the serialized kernel package linked against by an executable package.
39    pub const KERNEL: Self = Self(Cow::Borrowed("kernel"));
40
41    /// Construct a user-defined (i.e. "custom") section identifier
42    ///
43    /// Section identifiers must be either an ASCII alphanumeric, or one of the following
44    /// characters: `.`, `_`, `-`. Additionally, the identifier must start with an ASCII alphabetic
45    /// character or `_`.
46    pub fn custom(name: impl AsRef<str>) -> Result<Self, InvalidSectionIdError> {
47        let name = name.as_ref();
48        if !name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
49            return Err(InvalidSectionIdError::InvalidStart);
50        }
51        if name.contains(|c: char| !c.is_ascii_alphanumeric() && !matches!(c, '.' | '_' | '-')) {
52            return Err(InvalidSectionIdError::InvalidCharacter);
53        }
54        Ok(Self(name.to_string().into()))
55    }
56
57    /// Get this section identifier as a string
58    #[inline]
59    pub fn as_str(&self) -> &str {
60        self.0.as_ref()
61    }
62
63    /// Returns true if this section contains package debug metadata.
64    pub fn is_debug(&self) -> bool {
65        self == &Self::DEBUG_INFO
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_info" => Ok(Self::DEBUG_INFO),
86            "account_component_metadata" => Ok(Self::ACCOUNT_COMPONENT_METADATA),
87            "project_source_provenance" => Ok(Self::PROJECT_SOURCE_PROVENANCE),
88            "kernel" => Ok(Self::KERNEL),
89            custom => Self::custom(custom),
90        }
91    }
92}
93
94impl fmt::Display for SectionId {
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        f.write_str(self.as_str())
97    }
98}
99
100#[derive(Clone, PartialEq, Eq)]
101#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
102pub struct Section {
103    pub id: SectionId,
104    pub data: Cow<'static, [u8]>,
105}
106
107impl fmt::Debug for Section {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        let verbose = f.alternate();
110        let mut builder = f.debug_struct("Section");
111        builder.field("id", &format_args!("{}", self.id));
112        if verbose {
113            builder.field("data", &format_args!("{}", DisplayHex(&self.data))).finish()
114        } else {
115            builder.field("data", &format_args!("{} bytes", self.data.len())).finish()
116        }
117    }
118}
119
120impl Section {
121    pub fn new<B>(id: SectionId, data: B) -> Self
122    where
123        B: Into<Cow<'static, [u8]>>,
124    {
125        Self { id, data: data.into() }
126    }
127
128    /// Returns true if this section is empty, i.e. has no data
129    pub fn is_empty(&self) -> bool {
130        self.data.is_empty()
131    }
132
133    /// Returns the size in bytes of this section's data
134    pub fn len(&self) -> usize {
135        self.data.len()
136    }
137}
138
139impl Serializable for Section {
140    fn write_into<W: ByteWriter>(&self, target: &mut W) {
141        let id = self.id.as_str();
142        target.write_usize(id.len());
143        target.write_bytes(id.as_bytes());
144        target.write_usize(self.len());
145        target.write_bytes(&self.data);
146    }
147}
148
149impl Deserializable for Section {
150    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
151        let id_len = source.read_usize()?;
152        let id_bytes = source.read_slice(id_len)?;
153        let id_str = core::str::from_utf8(id_bytes).map_err(|err| {
154            DeserializationError::InvalidValue(format!("invalid utf-8 in section name: {err}"))
155        })?;
156        let id = SectionId(Cow::Owned(id_str.to_owned()));
157
158        let len = source.read_usize()?;
159        let bytes = source.read_slice(len)?;
160        Ok(Section { id, data: Cow::Owned(bytes.to_owned()) })
161    }
162}
163
164#[cfg(feature = "arbitrary")]
165impl Arbitrary for SectionId {
166    type Parameters = ();
167    type Strategy = BoxedStrategy<Self>;
168
169    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
170        use alloc::string::String;
171
172        let builtins = proptest::sample::select(vec![
173            Self::DEBUG_INFO,
174            Self::ACCOUNT_COMPONENT_METADATA,
175            Self::PROJECT_SOURCE_PROVENANCE,
176            Self::KERNEL,
177        ]);
178
179        let custom = (
180            proptest::prop_oneof![
181                proptest::char::range('a', 'z'),
182                proptest::char::range('A', 'Z'),
183                Just('_'),
184            ],
185            proptest::collection::vec(
186                proptest::prop_oneof![
187                    proptest::char::range('a', 'z'),
188                    proptest::char::range('A', 'Z'),
189                    proptest::char::range('0', '9'),
190                    Just('.'),
191                    Just('_'),
192                    Just('-'),
193                ],
194                0..31,
195            ),
196        )
197            .prop_map(|(first, rest)| {
198                let mut name = String::with_capacity(rest.len() + 1);
199                name.push(first);
200                name.extend(rest);
201                Self::custom(name).expect("generated custom section ids are valid")
202            });
203
204        proptest::prop_oneof![builtins, custom].boxed()
205    }
206}