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_TYPES: Self = Self(Cow::Borrowed("debug_types"));
31 pub const DEBUG_SOURCES: Self = Self(Cow::Borrowed("debug_sources"));
33 pub const DEBUG_FUNCTIONS: Self = Self(Cow::Borrowed("debug_functions"));
35 pub const ACCOUNT_COMPONENT_METADATA: Self = Self(Cow::Borrowed("account_component_metadata"));
41 pub const PROJECT_SOURCE_PROVENANCE: Self = Self(Cow::Borrowed("project_source_provenance"));
43 pub const KERNEL: Self = Self(Cow::Borrowed("kernel"));
45
46 pub fn custom(name: impl AsRef<str>) -> Result<Self, InvalidSectionIdError> {
52 let name = name.as_ref();
53 if !name.starts_with(|c: char| c.is_ascii_alphabetic() || c == '_') {
54 return Err(InvalidSectionIdError::InvalidStart);
55 }
56 if name.contains(|c: char| !c.is_ascii_alphanumeric() && !matches!(c, '.' | '_' | '-')) {
57 return Err(InvalidSectionIdError::InvalidCharacter);
58 }
59 Ok(Self(name.to_string().into()))
60 }
61
62 #[inline]
64 pub fn as_str(&self) -> &str {
65 self.0.as_ref()
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_types" => Ok(Self::DEBUG_TYPES),
86 "debug_sources" => Ok(Self::DEBUG_SOURCES),
87 "debug_functions" => Ok(Self::DEBUG_FUNCTIONS),
88 "account_component_metadata" => Ok(Self::ACCOUNT_COMPONENT_METADATA),
89 "project_source_provenance" => Ok(Self::PROJECT_SOURCE_PROVENANCE),
90 "kernel" => Ok(Self::KERNEL),
91 custom => Self::custom(custom),
92 }
93 }
94}
95
96impl fmt::Display for SectionId {
97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 f.write_str(self.as_str())
99 }
100}
101
102#[derive(Clone, PartialEq, Eq)]
103#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
104pub struct Section {
105 pub id: SectionId,
106 pub data: Cow<'static, [u8]>,
107}
108
109impl fmt::Debug for Section {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 let verbose = f.alternate();
112 let mut builder = f.debug_struct("Section");
113 builder.field("id", &format_args!("{}", self.id));
114 if verbose {
115 builder.field("data", &format_args!("{}", DisplayHex(&self.data))).finish()
116 } else {
117 builder.field("data", &format_args!("{} bytes", self.data.len())).finish()
118 }
119 }
120}
121
122impl Section {
123 pub fn new<B>(id: SectionId, data: B) -> Self
124 where
125 B: Into<Cow<'static, [u8]>>,
126 {
127 Self { id, data: data.into() }
128 }
129
130 pub fn is_empty(&self) -> bool {
132 self.data.is_empty()
133 }
134
135 pub fn len(&self) -> usize {
137 self.data.len()
138 }
139}
140
141impl Serializable for Section {
142 fn write_into<W: ByteWriter>(&self, target: &mut W) {
143 let id = self.id.as_str();
144 target.write_usize(id.len());
145 target.write_bytes(id.as_bytes());
146 target.write_usize(self.len());
147 target.write_bytes(&self.data);
148 }
149}
150
151impl Deserializable for Section {
152 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
153 let id_len = source.read_usize()?;
154 let id_bytes = source.read_slice(id_len)?;
155 let id_str = core::str::from_utf8(id_bytes).map_err(|err| {
156 DeserializationError::InvalidValue(format!("invalid utf-8 in section name: {err}"))
157 })?;
158 let id = SectionId(Cow::Owned(id_str.to_owned()));
159
160 let len = source.read_usize()?;
161 let bytes = source.read_slice(len)?;
162 Ok(Section { id, data: Cow::Owned(bytes.to_owned()) })
163 }
164}
165
166#[cfg(feature = "arbitrary")]
167impl Arbitrary for SectionId {
168 type Parameters = ();
169 type Strategy = BoxedStrategy<Self>;
170
171 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
172 use alloc::string::String;
173
174 let builtins = proptest::sample::select(vec![
175 Self::DEBUG_TYPES,
176 Self::DEBUG_SOURCES,
177 Self::DEBUG_FUNCTIONS,
178 Self::ACCOUNT_COMPONENT_METADATA,
179 Self::PROJECT_SOURCE_PROVENANCE,
180 Self::KERNEL,
181 ]);
182
183 let custom = (
184 proptest::prop_oneof![
185 proptest::char::range('a', 'z'),
186 proptest::char::range('A', 'Z'),
187 Just('_'),
188 ],
189 proptest::collection::vec(
190 proptest::prop_oneof![
191 proptest::char::range('a', 'z'),
192 proptest::char::range('A', 'Z'),
193 proptest::char::range('0', '9'),
194 Just('.'),
195 Just('_'),
196 Just('-'),
197 ],
198 0..31,
199 ),
200 )
201 .prop_map(|(first, rest)| {
202 let mut name = String::with_capacity(rest.len() + 1);
203 name.push(first);
204 name.extend(rest);
205 Self::custom(name).expect("generated custom section ids are valid")
206 });
207
208 proptest::prop_oneof![builtins, custom].boxed()
209 }
210}