Skip to main content

runmat_execution/executable/
section.rs

1use 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(&section.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}