1use alloc::{borrow::Cow, string::String, vec::Vec};
2use codec::{Compact, CompactLen, Decode, Encode};
3
4#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
6pub struct CrateInfo {
7 pub name: String,
8 pub version: String,
9 pub license: String,
10 pub authors: Vec<String>,
11}
12
13impl core::fmt::Display for CrateInfo {
14 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
15 let name = match self.name.trim() {
16 "" => "<no-name>",
17 other => other,
18 };
19 let version = match self.version.trim() {
20 "" => "<no-version>",
21 other => other,
22 };
23 write!(f, "{name} v{version} by ")?;
24 if self.authors.is_empty() || self.authors.iter().all(|s| s.trim().is_empty()) {
25 write!(f, "<no-authors>")?;
26 } else {
27 let mut iter = self.authors.iter().map(|s| s.trim()).filter(|s| !s.is_empty());
28 if let Some(first) = iter.next() {
29 write!(f, "{first}")?;
30 }
31 for author in iter {
32 write!(f, ", {author}")?;
33 }
34 }
35 Ok(())
36 }
37}
38
39#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
41pub enum ConventionalMetadata {
42 Info(CrateInfo),
43}
44
45pub fn write_metadata(
47 metadata: &ConventionalMetadata,
48 output: &mut Vec<u8>,
49) -> Result<(), &'static str> {
50 let len = metadata.encoded_size();
51 write_var(u32::try_from(len).map_err(|_| "metadata too large")?, output);
52 metadata.encode_to(output);
53 Ok(())
54}
55
56pub fn read_metadata(bytes: &mut &[u8]) -> Result<ConventionalMetadata, codec::Error> {
58 let mut metadata = read_metadata_slice(bytes).ok_or("Failed to parse metadata")?;
59 ConventionalMetadata::decode(&mut metadata)
60}
61
62pub fn read_metadata_slice<'a>(bytes: &mut &'a [u8]) -> Option<&'a [u8]> {
64 let offset = read_var(bytes)?;
65 let metadata = read_slice(bytes, offset)?;
66 Some(metadata)
67}
68
69pub struct ProgramBlob<'a> {
71 pub metadata: Cow<'a, [u8]>,
72 pub ro_data: Cow<'a, [u8]>,
73 pub rw_data: Cow<'a, [u8]>,
74 pub code_blob: Cow<'a, [u8]>,
75 pub rw_data_padding_pages: u16,
76 pub stack_size: u32,
77}
78
79fn read_u24(bytes: &mut &[u8]) -> Option<u32> {
80 let xs = bytes.get(..3)?;
81 *bytes = &bytes[3..];
82 Some(u32::from_le_bytes([xs[0], xs[1], xs[2], 0]))
83}
84
85fn write_u24(value: u32, output: &mut Vec<u8>) -> Result<(), ()> {
86 if value >= (1 << 24) {
87 return Err(());
88 }
89
90 output.extend_from_slice(&value.to_le_bytes()[0..3]);
91 Ok(())
92}
93
94fn read_u16(bytes: &mut &[u8]) -> Option<u16> {
95 let xs = bytes.get(..2)?;
96 *bytes = &bytes[2..];
97 Some(u16::from_le_bytes([xs[0], xs[1]]))
98}
99
100fn read_u32(bytes: &mut &[u8]) -> Option<u32> {
101 let xs = bytes.get(..4)?;
102 *bytes = &bytes[4..];
103 Some(u32::from_le_bytes([xs[0], xs[1], xs[2], xs[3]]))
104}
105
106fn read_var(bytes: &mut &[u8]) -> Option<u32> {
107 Some(Compact::<u32>::decode(bytes).ok()?.0)
108}
109
110fn write_var(value: u32, output: &mut Vec<u8>) {
111 Compact::<u32>(value).encode_to(output)
112}
113
114fn read_cow<'a>(bytes: &mut &'a [u8], length: u32) -> Option<Cow<'a, [u8]>> {
115 read_slice(bytes, length).map(Cow::Borrowed)
116}
117
118fn read_slice<'a>(bytes: &mut &'a [u8], length: u32) -> Option<&'a [u8]> {
119 let length = length as usize;
120 let slice = bytes.get(..length)?;
121 *bytes = &bytes[length..];
122 Some(slice)
123}
124
125impl<'a> ProgramBlob<'a> {
126 pub fn from_bytes(mut bytes: &'a [u8]) -> Option<Self> {
127 let metadata = Cow::Borrowed(read_metadata_slice(&mut bytes)?);
128 let ro_data_len = read_u24(&mut bytes)?;
129 let rw_data_len = read_u24(&mut bytes)?;
130 let rw_data_padding_pages = read_u16(&mut bytes)?;
131 let stack_size = read_u24(&mut bytes)?;
132 let ro_data = read_cow(&mut bytes, ro_data_len)?;
133 let rw_data = read_cow(&mut bytes, rw_data_len)?;
134 let code_blob_len = read_u32(&mut bytes)?;
135 let code_blob = read_cow(&mut bytes, code_blob_len)?;
136
137 if !bytes.is_empty() {
138 return None;
139 }
140
141 Some(ProgramBlob {
142 metadata,
143 rw_data_padding_pages,
144 stack_size,
145 ro_data,
146 rw_data,
147 code_blob,
148 })
149 }
150
151 pub fn to_vec(&self) -> Result<Vec<u8>, &'static str> {
152 let mut output = Vec::new();
153 write_var(
154 u32::try_from(self.metadata.len()).map_err(|_| "metadata too large")?,
155 &mut output,
156 );
157 output.extend_from_slice(&self.metadata);
158 write_u24(u32::try_from(self.ro_data.len()).map_err(|_| "too large RO data")?, &mut output)
159 .map_err(|_| "too large RO data")?;
160 write_u24(u32::try_from(self.rw_data.len()).map_err(|_| "too large RW data")?, &mut output)
161 .map_err(|_| "too large RW data")?;
162 output.extend_from_slice(&self.rw_data_padding_pages.to_le_bytes());
163 write_u24(self.stack_size, &mut output).map_err(|_| "too large stack size")?;
164 output.extend_from_slice(&self.ro_data);
165 output.extend_from_slice(&self.rw_data);
166 output.extend_from_slice(
167 &u32::try_from(self.code_blob.len()).map_err(|_| "too large code")?.to_le_bytes(),
168 );
169 output.extend_from_slice(&self.code_blob);
170 Ok(output)
171 }
172}
173
174#[cfg(feature = "polkavm")]
175impl From<ProgramBlob<'_>> for polkavm::ProgramParts {
176 fn from(other: ProgramBlob<'_>) -> Self {
177 let mut parts = polkavm::ProgramParts::empty(polkavm::program::InstructionSetKind::JamV1);
178 parts.ro_data_size = other.ro_data.len() as u32;
179 parts.rw_data_size = other.rw_data.len().next_multiple_of(4096) as u32 +
180 other.rw_data_padding_pages as u32 * 4096;
181 parts.stack_size = other.stack_size;
182 parts.ro_data = other.ro_data.into();
183 parts.rw_data = other.rw_data.into();
184 parts.code_and_jump_table = other.code_blob.into();
185 parts
186 }
187}
188
189#[cfg(feature = "polkavm")]
190impl<'a> ProgramBlob<'a> {
191 pub fn from_pvm(parts: &'a polkavm::ProgramParts, metadata: Cow<'a, [u8]>) -> Self {
192 let mut ro_data = parts.ro_data.to_vec();
194 ro_data.resize(parts.ro_data_size as usize, 0);
195 let padding = (parts.rw_data_size as usize).next_multiple_of(4096) -
197 parts.rw_data.len().next_multiple_of(4096);
198 let rw_data_padding_pages = padding / 4096;
199 let rw_data_padding_pages =
200 rw_data_padding_pages.try_into().expect("The RW data section is too big");
201 Self {
202 metadata,
203 ro_data: ro_data.into(),
204 rw_data: (&parts.rw_data[..]).into(),
205 code_blob: (&parts.code_and_jump_table[..]).into(),
206 rw_data_padding_pages,
207 stack_size: parts.stack_size,
208 }
209 }
210}
211
212pub struct CoreVmProgramBlob<'a, 'b> {
214 pub metadata: Cow<'a, [u8]>,
216 pub pvm_blob: Cow<'b, [u8]>,
218}
219
220impl<'a> CoreVmProgramBlob<'a, 'a> {
221 pub fn from_bytes(mut bytes: &'a [u8]) -> Option<Self> {
223 let metadata = Cow::Borrowed(read_metadata_slice(&mut bytes)?);
224 let pvm_blob = Cow::Borrowed(bytes);
225 Some(Self { metadata, pvm_blob })
226 }
227
228 pub fn to_vec(&self) -> Result<Vec<u8>, &'static str> {
230 let metadata_len = u32::try_from(self.metadata.len()).map_err(|_| "metadata too large")?;
231 let mut output = Vec::with_capacity(
232 Compact::<u32>::compact_len(&metadata_len) +
233 metadata_len as usize +
234 self.pvm_blob.len(),
235 );
236 write_var(metadata_len, &mut output);
237 output.extend_from_slice(&self.metadata);
238 output.extend_from_slice(&self.pvm_blob);
239 Ok(output)
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn read_write_u24() {
249 let mut output = Vec::new();
250 write_u24(0x00345678, &mut output).unwrap();
251 assert_eq!(read_u24(&mut &output[..]), Some(0x00345678));
252
253 assert!(write_u24(0x00ffffff, &mut output).is_ok());
254 assert!(write_u24(0x01000000, &mut output).is_err());
255 }
256
257 #[test]
258 fn read_write_var() {
259 let mut output = Vec::new();
260 let vals = [0x00345678, 0x00, 0x01, 0x7f, 0x80, 0xffffffff];
261 for i in vals.into_iter() {
262 write_var(i, &mut output);
263 }
264 let mut cursor = output.as_ref();
265 for i in vals.into_iter() {
266 assert_eq!(read_var(&mut cursor), Some(i));
267 }
268 }
269
270 #[test]
271 fn metadata_read_write() {
272 let info = CrateInfo {
273 name: "x".into(),
274 version: "y".into(),
275 license: "z".into(),
276 authors: vec!["w".into(), "v".into()],
277 };
278 let metadata = ConventionalMetadata::Info(info);
279 let mut output = Vec::new();
280 write_metadata(&metadata, &mut output).unwrap();
281 let actual = read_metadata(&mut &output[..]).unwrap();
282 assert_eq!(metadata, actual);
283 }
284
285 #[test]
286 fn corevm_program_blob_read_write() {
287 let info = CrateInfo {
288 name: "x".into(),
289 version: "y".into(),
290 license: "z".into(),
291 authors: vec!["w".into(), "v".into()],
292 };
293 let metadata = ConventionalMetadata::Info(info).encode().into();
294 let pvm_blob = vec![0, 0, 2, 50, 0, 1];
296 let corevm_blob = CoreVmProgramBlob { metadata, pvm_blob: pvm_blob.as_slice().into() };
297 let corevm_blob_bytes = corevm_blob.to_vec().unwrap();
298 let actual_blob = CoreVmProgramBlob::from_bytes(&corevm_blob_bytes[..]).unwrap();
299 assert_eq!(corevm_blob.metadata, actual_blob.metadata);
300 assert_eq!(corevm_blob.pvm_blob, actual_blob.pvm_blob);
301 assert_eq!(pvm_blob.as_slice(), actual_blob.pvm_blob.as_ref());
302 }
303}