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-objects::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
37    /// Construct a user-defined (i.e. "custom") section identifier
38    ///
39    /// Section identifiers must be either an ASCII alphanumeric, or one of the following
40    /// characters: `.`, `_`, `-`. Additionally, the identifier must start with an ASCII alphabetic
41    /// character or `_`.
42    pub fn custom(name: impl AsRef<str>) -> Result<Self, InvalidSectionIdError> {
43        let name = name.as_ref();
44        if !name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
45            return Err(InvalidSectionIdError::InvalidStart);
46        }
47        if name.contains(|c: char| !c.is_ascii_alphanumeric() && !matches!(c, '.' | '_' | '-')) {
48            return Err(InvalidSectionIdError::InvalidCharacter);
49        }
50        Ok(Self(name.to_string().into()))
51    }
52
53    /// Get this section identifier as a string
54    #[inline]
55    pub fn as_str(&self) -> &str {
56        self.0.as_ref()
57    }
58}
59
60#[derive(Debug, thiserror::Error)]
61pub enum InvalidSectionIdError {
62    #[error("invalid section id: cannot be empty")]
63    Empty,
64    #[error(
65        "invalid section id: contains invalid characters, only the set [a-z0-9._-] are allowed"
66    )]
67    InvalidCharacter,
68    #[error("invalid section id: must start with a character in the set [a-z_]")]
69    InvalidStart,
70}
71
72impl FromStr for SectionId {
73    type Err = InvalidSectionIdError;
74    fn from_str(s: &str) -> Result<Self, Self::Err> {
75        match s {
76            "debug_types" => Ok(Self::DEBUG_TYPES),
77            "debug_sources" => Ok(Self::DEBUG_SOURCES),
78            "debug_functions" => Ok(Self::DEBUG_FUNCTIONS),
79            "account_component_metadata" => Ok(Self::ACCOUNT_COMPONENT_METADATA),
80            custom => Self::custom(custom),
81        }
82    }
83}
84
85impl fmt::Display for SectionId {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        f.write_str(self.as_str())
88    }
89}
90
91#[derive(Clone, PartialEq, Eq)]
92#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
93pub struct Section {
94    pub id: SectionId,
95    pub data: Cow<'static, [u8]>,
96}
97
98impl fmt::Debug for Section {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        let verbose = f.alternate();
101        let mut builder = f.debug_struct("Section");
102        builder.field("id", &format_args!("{}", &self.id));
103        if verbose {
104            builder.field("data", &format_args!("{}", DisplayHex(&self.data))).finish()
105        } else {
106            builder.field("data", &format_args!("{} bytes", self.data.len())).finish()
107        }
108    }
109}
110
111impl Section {
112    pub fn new<B>(id: SectionId, data: B) -> Self
113    where
114        B: Into<Cow<'static, [u8]>>,
115    {
116        Self { id, data: data.into() }
117    }
118
119    /// Returns true if this section is empty, i.e. has no data
120    pub fn is_empty(&self) -> bool {
121        self.data.is_empty()
122    }
123
124    /// Returns the size in bytes of this section's data
125    pub fn len(&self) -> usize {
126        self.data.len()
127    }
128}
129
130impl Serializable for Section {
131    fn write_into<W: ByteWriter>(&self, target: &mut W) {
132        let id = self.id.as_str();
133        target.write_usize(id.len());
134        target.write_bytes(id.as_bytes());
135        target.write_usize(self.len());
136        target.write_bytes(&self.data);
137    }
138}
139
140impl Deserializable for Section {
141    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
142        let id_len = source.read_usize()?;
143        let id_bytes = source.read_slice(id_len)?;
144        let id_str = core::str::from_utf8(id_bytes).map_err(|err| {
145            DeserializationError::InvalidValue(format!("invalid utf-8 in section name: {err}"))
146        })?;
147        let id = SectionId(Cow::Owned(id_str.to_owned()));
148
149        let len = source.read_usize()?;
150        let bytes = source.read_slice(len)?;
151        Ok(Section { id, data: Cow::Owned(bytes.to_owned()) })
152    }
153}