Skip to main content

miden_mast_package/package/
section.rs

1use alloc::{
2    borrow::{Cow, ToOwned},
3    format,
4    string::ToString,
5};
6use core::{fmt, str::FromStr};
7
8use miden_assembly_syntax::DisplayHex;
9use miden_core::serde::{
10    ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
11};
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15/// A unique identifier for optional sections of the Miden package format
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18#[cfg_attr(feature = "serde", serde(transparent))]
19#[repr(transparent)]
20pub struct SectionId(Cow<'static, str>);
21
22impl SectionId {
23    /// The section containing debug type definitions (primitives, structs, arrays, pointers,
24    /// function types)
25    pub const DEBUG_TYPES: Self = Self(Cow::Borrowed("debug_types"));
26    /// The section containing debug source file paths and checksums
27    pub const DEBUG_SOURCES: Self = Self(Cow::Borrowed("debug_sources"));
28    /// The section containing debug function metadata, variables, and inlined calls
29    pub const DEBUG_FUNCTIONS: Self = Self(Cow::Borrowed("debug_functions"));
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
64#[derive(Debug, thiserror::Error)]
65pub enum InvalidSectionIdError {
66    #[error("invalid section id: cannot be empty")]
67    Empty,
68    #[error(
69        "invalid section id: contains invalid characters, only the set [a-z0-9._-] are allowed"
70    )]
71    InvalidCharacter,
72    #[error("invalid section id: must start with a character in the set [a-z_]")]
73    InvalidStart,
74}
75
76impl FromStr for SectionId {
77    type Err = InvalidSectionIdError;
78    fn from_str(s: &str) -> Result<Self, Self::Err> {
79        match s {
80            "debug_types" => Ok(Self::DEBUG_TYPES),
81            "debug_sources" => Ok(Self::DEBUG_SOURCES),
82            "debug_functions" => Ok(Self::DEBUG_FUNCTIONS),
83            "account_component_metadata" => Ok(Self::ACCOUNT_COMPONENT_METADATA),
84            "project_source_provenance" => Ok(Self::PROJECT_SOURCE_PROVENANCE),
85            "kernel" => Ok(Self::KERNEL),
86            custom => Self::custom(custom),
87        }
88    }
89}
90
91impl fmt::Display for SectionId {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        f.write_str(self.as_str())
94    }
95}
96
97#[derive(Clone, PartialEq, Eq)]
98#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
99pub struct Section {
100    pub id: SectionId,
101    pub data: Cow<'static, [u8]>,
102}
103
104impl fmt::Debug for Section {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        let verbose = f.alternate();
107        let mut builder = f.debug_struct("Section");
108        builder.field("id", &format_args!("{}", &self.id));
109        if verbose {
110            builder.field("data", &format_args!("{}", DisplayHex(&self.data))).finish()
111        } else {
112            builder.field("data", &format_args!("{} bytes", self.data.len())).finish()
113        }
114    }
115}
116
117impl Section {
118    pub fn new<B>(id: SectionId, data: B) -> Self
119    where
120        B: Into<Cow<'static, [u8]>>,
121    {
122        Self { id, data: data.into() }
123    }
124
125    /// Returns true if this section is empty, i.e. has no data
126    pub fn is_empty(&self) -> bool {
127        self.data.is_empty()
128    }
129
130    /// Returns the size in bytes of this section's data
131    pub fn len(&self) -> usize {
132        self.data.len()
133    }
134}
135
136impl Serializable for Section {
137    fn write_into<W: ByteWriter>(&self, target: &mut W) {
138        let id = self.id.as_str();
139        target.write_usize(id.len());
140        target.write_bytes(id.as_bytes());
141        target.write_usize(self.len());
142        target.write_bytes(&self.data);
143    }
144}
145
146impl Deserializable for Section {
147    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
148        let id_len = source.read_usize()?;
149        let id_bytes = source.read_slice(id_len)?;
150        let id_str = core::str::from_utf8(id_bytes).map_err(|err| {
151            DeserializationError::InvalidValue(format!("invalid utf-8 in section name: {err}"))
152        })?;
153        let id = SectionId(Cow::Owned(id_str.to_owned()));
154
155        let len = source.read_usize()?;
156        let bytes = source.read_slice(len)?;
157        Ok(Section { id, data: Cow::Owned(bytes.to_owned()) })
158    }
159}