miden_mast_package/package/
section.rs1#[cfg(feature = "arbitrary")]
2use alloc::vec;
3use alloc::{
4 borrow::{Cow, ToOwned},
5 format,
6 string::{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
17#[derive(Debug, Clone, PartialEq, Eq)]
19#[repr(transparent)]
20#[cfg_attr(
21 all(feature = "arbitrary", test),
22 miden_test_serialization_macros::serialization_test
23)]
24pub struct SectionId(Cow<'static, str>);
25
26impl SectionId {
27 pub const DEBUG_INFO: Self = Self(Cow::Borrowed("debug_info"));
29 pub const ACCOUNT_COMPONENT_METADATA: Self = Self(Cow::Borrowed("account_component_metadata"));
35 pub const PROJECT_SOURCE_PROVENANCE: Self = Self(Cow::Borrowed("project_source_provenance"));
37 pub const KERNEL: Self = Self(Cow::Borrowed("kernel"));
39
40 pub fn custom(name: impl AsRef<str>) -> Result<Self, InvalidSectionIdError> {
46 let name = name.as_ref();
47 if !name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
48 return Err(InvalidSectionIdError::InvalidStart);
49 }
50 if name.contains(|c: char| !c.is_ascii_alphanumeric() && !matches!(c, '.' | '_' | '-')) {
51 return Err(InvalidSectionIdError::InvalidCharacter);
52 }
53 Ok(Self(name.to_string().into()))
54 }
55
56 #[inline]
58 pub fn as_str(&self) -> &str {
59 self.0.as_ref()
60 }
61
62 pub fn is_debug(&self) -> bool {
64 self == &Self::DEBUG_INFO
65 }
66}
67
68#[derive(Debug, thiserror::Error)]
69pub enum InvalidSectionIdError {
70 #[error("invalid section id: cannot be empty")]
71 Empty,
72 #[error(
73 "invalid section id: contains invalid characters, only the set [a-z0-9._-] are allowed"
74 )]
75 InvalidCharacter,
76 #[error("invalid section id: must start with a character in the set [a-z_]")]
77 InvalidStart,
78}
79
80impl FromStr for SectionId {
81 type Err = InvalidSectionIdError;
82 fn from_str(s: &str) -> Result<Self, Self::Err> {
83 match s {
84 "debug_info" => Ok(Self::DEBUG_INFO),
85 "account_component_metadata" => Ok(Self::ACCOUNT_COMPONENT_METADATA),
86 "project_source_provenance" => Ok(Self::PROJECT_SOURCE_PROVENANCE),
87 "kernel" => Ok(Self::KERNEL),
88 custom => Self::custom(custom),
89 }
90 }
91}
92
93impl fmt::Display for SectionId {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 f.write_str(self.as_str())
96 }
97}
98
99impl Serializable for SectionId {
100 fn write_into<W: ByteWriter>(&self, target: &mut W) {
101 self.as_str().write_into(target);
102 }
103}
104
105impl Deserializable for SectionId {
106 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
107 String::read_from(source)?
108 .parse()
109 .map_err(|err| DeserializationError::InvalidValue(format!("invalid section id: {err}")))
110 }
111}
112
113#[derive(Clone, PartialEq, Eq)]
114pub struct Section {
115 pub id: SectionId,
116 pub data: Cow<'static, [u8]>,
117}
118
119impl fmt::Debug for Section {
120 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121 let verbose = f.alternate();
122 let mut builder = f.debug_struct("Section");
123 builder.field("id", &format_args!("{}", self.id));
124 if verbose {
125 builder.field("data", &format_args!("{}", DisplayHex(&self.data))).finish()
126 } else {
127 builder.field("data", &format_args!("{} bytes", self.data.len())).finish()
128 }
129 }
130}
131
132impl Section {
133 pub fn new<B>(id: SectionId, data: B) -> Self
134 where
135 B: Into<Cow<'static, [u8]>>,
136 {
137 Self { id, data: data.into() }
138 }
139
140 pub fn is_empty(&self) -> bool {
142 self.data.is_empty()
143 }
144
145 pub fn len(&self) -> usize {
147 self.data.len()
148 }
149}
150
151impl Serializable for Section {
152 fn write_into<W: ByteWriter>(&self, target: &mut W) {
153 let id = self.id.as_str();
154 target.write_usize(id.len());
155 target.write_bytes(id.as_bytes());
156 target.write_usize(self.len());
157 target.write_bytes(&self.data);
158 }
159}
160
161impl Deserializable for Section {
162 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
163 let id_len = source.read_usize()?;
164 let id_bytes = source.read_slice(id_len)?;
165 let id_str = core::str::from_utf8(id_bytes).map_err(|err| {
166 DeserializationError::InvalidValue(format!("invalid utf-8 in section name: {err}"))
167 })?;
168 let id = id_str.parse::<SectionId>().map_err(|err| {
169 DeserializationError::InvalidValue(format!("invalid section id {id_str:?}: {err}"))
170 })?;
171
172 let len = source.read_usize()?;
173 let bytes = source.read_slice(len)?;
174 Ok(Section { id, data: Cow::Owned(bytes.to_owned()) })
175 }
176}
177
178#[cfg(feature = "arbitrary")]
179impl Arbitrary for SectionId {
180 type Parameters = ();
181 type Strategy = BoxedStrategy<Self>;
182
183 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
184 use alloc::string::String;
185
186 let builtins = proptest::sample::select(vec![
187 Self::DEBUG_INFO,
188 Self::ACCOUNT_COMPONENT_METADATA,
189 Self::PROJECT_SOURCE_PROVENANCE,
190 Self::KERNEL,
191 ]);
192
193 let custom = (
194 proptest::prop_oneof![
195 proptest::char::range('a', 'z'),
196 proptest::char::range('A', 'Z'),
197 Just('_'),
198 ],
199 proptest::collection::vec(
200 proptest::prop_oneof![
201 proptest::char::range('a', 'z'),
202 proptest::char::range('A', 'Z'),
203 proptest::char::range('0', '9'),
204 Just('.'),
205 Just('_'),
206 Just('-'),
207 ],
208 0..31,
209 ),
210 )
211 .prop_map(|(first, rest)| {
212 let mut name = String::with_capacity(rest.len() + 1);
213 name.push(first);
214 name.extend(rest);
215 Self::custom(name).expect("generated custom section ids are valid")
216 });
217
218 proptest::prop_oneof![builtins, custom].boxed()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use alloc::vec::Vec;
225
226 use miden_core::serde::{ByteWriter, Deserializable, Serializable, SliceReader};
227
228 use super::*;
229
230 fn section_bytes_with_id(id: &str) -> Vec<u8> {
231 let mut buf = Vec::new();
232 buf.write_usize(id.len());
233 buf.write_bytes(id.as_bytes());
234 buf.write_usize(0);
235 buf
236 }
237
238 #[test]
239 fn deserialize_rejects_invalid_section_id() {
240 for bad_id in ["", "1bad", "-bad", "bad id", "../etc"] {
241 let bytes = section_bytes_with_id(bad_id);
242 let mut reader = SliceReader::new(&bytes);
243 assert!(Section::read_from(&mut reader).is_err(), "expected error for {bad_id:?}",);
244 }
245 }
246
247 #[test]
248 fn deserialize_accepts_valid_section_id() {
249 let section = Section {
250 id: SectionId::custom("my_section").unwrap(),
251 data: Cow::Borrowed(&[]),
252 };
253 let bytes = section.to_bytes();
254 let mut reader = SliceReader::new(&bytes);
255 let result = Section::read_from(&mut reader);
256 assert!(result.is_ok());
257 assert_eq!(result.unwrap().id, section.id);
258 }
259}