runmat_execution/executable/
section.rs1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use super::identity::validate_identity;
6use crate::{ContractError, Digest};
7
8const MAX_OPTIONAL_SECTION_BYTES: usize = 16 * 1024 * 1024;
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum SectionRequirement {
13 Optional,
14 Required,
15}
16
17#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
18#[serde(deny_unknown_fields)]
19pub struct ExecutableOptionalSection {
20 pub name: String,
21 pub schema_version: u16,
22 pub requirement: SectionRequirement,
23 pub payload: Vec<u8>,
24 pub payload_digest: Digest,
25}
26
27impl ExecutableOptionalSection {
28 pub fn new(
29 name: impl Into<String>,
30 schema_version: u16,
31 requirement: SectionRequirement,
32 payload: Vec<u8>,
33 ) -> Self {
34 let payload_digest = Digest::sha256(&payload);
35 Self {
36 name: name.into(),
37 schema_version,
38 requirement,
39 payload,
40 payload_digest,
41 }
42 }
43
44 pub(crate) fn validate(&self) -> Result<(), ContractError> {
45 validate_identity("executable.optional_sections.name", &self.name, 128)?;
46 if self.schema_version == 0 {
47 return Err(ContractError::invalid(
48 "executable.optional_sections.schema_version",
49 "version must be non-zero",
50 ));
51 }
52 if self.payload.len() > MAX_OPTIONAL_SECTION_BYTES {
53 return Err(ContractError::Limit {
54 field: "executable.optional_sections.payload",
55 limit: MAX_OPTIONAL_SECTION_BYTES as u64,
56 });
57 }
58 if self.payload_digest != Digest::sha256(&self.payload) {
59 return Err(ContractError::invalid(
60 "executable.optional_sections.payload_digest",
61 "digest does not match payload",
62 ));
63 }
64 Ok(())
65 }
66}
67
68#[derive(Clone, Debug, Default, Eq, PartialEq)]
69pub struct ExecutableSectionSupport(BTreeMap<String, u16>);
70
71impl ExecutableSectionSupport {
72 pub fn new(sections: impl IntoIterator<Item = (String, u16)>) -> Result<Self, ContractError> {
73 let mut supported = BTreeMap::new();
74 for (name, version) in sections {
75 validate_identity("executable.section_support.name", &name, 128)?;
76 if version == 0 {
77 return Err(ContractError::invalid(
78 "executable.section_support.version",
79 "version must be non-zero",
80 ));
81 }
82 if supported.insert(name, version).is_some() {
83 return Err(ContractError::invalid(
84 "executable.section_support.name",
85 "section names must be unique",
86 ));
87 }
88 }
89 Ok(Self(supported))
90 }
91
92 pub(crate) fn validate_section(
93 &self,
94 section: &ExecutableOptionalSection,
95 ) -> Result<(), ContractError> {
96 let supported = self.0.get(§ion.name).copied();
97 if matches!(section.requirement, SectionRequirement::Required)
98 && supported.is_none_or(|version| version < section.schema_version)
99 {
100 return Err(ContractError::invalid(
101 "executable.optional_sections",
102 format!(
103 "required section '{}' schema {} is unsupported",
104 section.name, section.schema_version
105 ),
106 ));
107 }
108 Ok(())
109 }
110}