Skip to main content

runmat_execution_artifact/program/
target.rs

1use serde::{Deserialize, Serialize};
2
3use super::ExecutableForm;
4use crate::{ArtifactError, ArtifactResult};
5
6pub const PROGRAM_TARGET_SCHEMA_VERSION: u16 = 1;
7
8#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ProgramTargetCohort {
11    Portable,
12    Native,
13}
14
15#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
16#[serde(deny_unknown_fields)]
17pub struct NativeTargetIdentity {
18    pub architecture: String,
19    pub operating_system: String,
20    pub pointer_width: u16,
21    pub abi: String,
22    pub object_format: String,
23}
24
25impl NativeTargetIdentity {
26    pub fn validate(&self) -> ArtifactResult<()> {
27        if !valid_token(&self.architecture, 64)
28            || !valid_token(&self.operating_system, 64)
29            || !valid_token(&self.abi, 256)
30            || !valid_token(&self.object_format, 32)
31            || !matches!(self.pointer_width, 32 | 64)
32        {
33            return Err(ArtifactError::Invalid(
34                "native artifact target is not canonical".into(),
35            ));
36        }
37        Ok(())
38    }
39}
40
41#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct ProgramTarget {
44    pub schema_version: u16,
45    pub profile: String,
46    pub cohort: ProgramTargetCohort,
47    pub native: Option<NativeTargetIdentity>,
48}
49
50impl ProgramTarget {
51    pub fn portable(profile: impl Into<String>) -> Self {
52        Self {
53            schema_version: PROGRAM_TARGET_SCHEMA_VERSION,
54            profile: profile.into(),
55            cohort: ProgramTargetCohort::Portable,
56            native: None,
57        }
58    }
59
60    pub fn native(profile: impl Into<String>, target: NativeTargetIdentity) -> Self {
61        Self {
62            schema_version: PROGRAM_TARGET_SCHEMA_VERSION,
63            profile: profile.into(),
64            cohort: ProgramTargetCohort::Native,
65            native: Some(target),
66        }
67    }
68
69    pub fn validate(&self) -> ArtifactResult<()> {
70        if self.schema_version != PROGRAM_TARGET_SCHEMA_VERSION || !valid_token(&self.profile, 256)
71        {
72            return Err(ArtifactError::Invalid(
73                "program artifact target is not canonical".into(),
74            ));
75        }
76        match (self.cohort, self.native.as_ref()) {
77            (ProgramTargetCohort::Portable, None) => Ok(()),
78            (ProgramTargetCohort::Native, Some(native)) => native.validate(),
79            _ => Err(ArtifactError::Invalid(
80                "program target cohort has inconsistent native identity".into(),
81            )),
82        }
83    }
84
85    pub fn validate_form(&self, form: ExecutableForm) -> ArtifactResult<()> {
86        self.validate()?;
87        let compatible = match form {
88            ExecutableForm::NativeObjectV1 => self.cohort == ProgramTargetCohort::Native,
89            ExecutableForm::InterpreterBytecodeV1
90            | ExecutableForm::InterpreterScriptV1
91            | ExecutableForm::TestAttemptV1
92            | ExecutableForm::MeshingWorkload
93            | ExecutableForm::ExecutableUnitV3 => self.cohort == ProgramTargetCohort::Portable,
94        };
95        if compatible {
96            Ok(())
97        } else {
98            Err(ArtifactError::Invalid(
99                "program executable form is incompatible with its target cohort".into(),
100            ))
101        }
102    }
103
104    pub fn validate_for_portable_host(&self) -> ArtifactResult<()> {
105        self.validate()?;
106        if self.cohort == ProgramTargetCohort::Portable {
107            Ok(())
108        } else {
109            Err(ArtifactError::Invalid(
110                "native program artifact is incompatible with a portable execution host".into(),
111            ))
112        }
113    }
114
115    pub fn validate_for_native_host(&self, host: &NativeTargetIdentity) -> ArtifactResult<()> {
116        self.validate()?;
117        host.validate()?;
118        match self.cohort {
119            ProgramTargetCohort::Portable => Ok(()),
120            ProgramTargetCohort::Native if self.native.as_ref() == Some(host) => Ok(()),
121            ProgramTargetCohort::Native => Err(ArtifactError::Invalid(
122                "native program artifact does not match this execution host".into(),
123            )),
124        }
125    }
126
127    pub fn canonical_bytes(&self) -> ArtifactResult<Vec<u8>> {
128        self.validate()?;
129        serde_json::to_vec(self).map_err(|error| ArtifactError::Encoding(error.to_string()))
130    }
131
132    pub fn from_canonical_bytes(bytes: &[u8]) -> ArtifactResult<Self> {
133        let target: Self = serde_json::from_slice(bytes)
134            .map_err(|error| ArtifactError::Encoding(error.to_string()))?;
135        target.validate()?;
136        if target.canonical_bytes()? != bytes {
137            return Err(ArtifactError::Invalid(
138                "program target encoding is not canonical".into(),
139            ));
140        }
141        Ok(target)
142    }
143}
144
145fn valid_token(value: &str, maximum: usize) -> bool {
146    !value.is_empty()
147        && value.len() <= maximum
148        && value.is_ascii()
149        && !value.chars().any(char::is_control)
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    fn native() -> NativeTargetIdentity {
157        NativeTargetIdentity {
158            architecture: "aarch64".into(),
159            operating_system: "macos".into(),
160            pointer_width: 64,
161            abi: "runmat-native-abi-v1".into(),
162            object_format: "mach-o".into(),
163        }
164    }
165
166    #[test]
167    fn target_cohorts_reject_incompatible_forms_and_hosts() {
168        let portable = ProgramTarget::portable("portable-executable-unit-v3");
169        portable
170            .validate_form(ExecutableForm::ExecutableUnitV3)
171            .unwrap();
172        assert!(portable
173            .validate_form(ExecutableForm::NativeObjectV1)
174            .is_err());
175
176        let target = native();
177        let native_program = ProgramTarget::native("native-object-v1", target.clone());
178        native_program
179            .validate_form(ExecutableForm::NativeObjectV1)
180            .unwrap();
181        assert!(native_program.validate_for_portable_host().is_err());
182        native_program.validate_for_native_host(&target).unwrap();
183
184        let mut different_host = target.clone();
185        different_host.architecture = "x86_64".into();
186        assert!(native_program
187            .validate_for_native_host(&different_host)
188            .is_err());
189    }
190
191    #[test]
192    fn target_cohorts_require_exactly_one_consistent_native_identity() {
193        let mut portable_with_native = ProgramTarget::portable("portable-test");
194        portable_with_native.native = Some(native());
195        assert!(portable_with_native.validate().is_err());
196
197        let mut native_without_identity = ProgramTarget::native("native-test", native());
198        native_without_identity.native = None;
199        assert!(native_without_identity.validate().is_err());
200    }
201}