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