miden_mast_package/package/
section.rs1#[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#[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 pub const DEBUG_INFO: Self = Self(Cow::Borrowed("debug_info"));
30 pub const ACCOUNT_COMPONENT_METADATA: Self = Self(Cow::Borrowed("account_component_metadata"));
36 pub const PROJECT_SOURCE_PROVENANCE: Self = Self(Cow::Borrowed("project_source_provenance"));
38 pub const KERNEL: Self = Self(Cow::Borrowed("kernel"));
40
41 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 #[inline]
59 pub fn as_str(&self) -> &str {
60 self.0.as_ref()
61 }
62
63 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 pub fn is_empty(&self) -> bool {
130 self.data.is_empty()
131 }
132
133 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}