1use std::collections::BTreeMap;
7
8use telltale_types::{GlobalType, LocalTypeR};
9
10use crate::instr::Instr;
11
12#[derive(Debug, Clone)]
18pub struct CodeImage {
19 pub programs: BTreeMap<String, Vec<Instr>>,
21 pub global_type: GlobalType,
23 pub local_types: BTreeMap<String, LocalTypeR>,
25}
26
27#[derive(Debug, Clone)]
31pub struct UntrustedImage {
32 pub programs: BTreeMap<String, Vec<Instr>>,
34 pub global_type: GlobalType,
36 pub local_types: BTreeMap<String, LocalTypeR>,
38}
39
40#[derive(Debug)]
42pub enum LoadResult {
43 Ok,
45 ValidationFailed {
47 reason: String,
49 },
50}
51
52impl CodeImage {
53 #[must_use]
55 pub fn from_local_types(
56 local_types: &BTreeMap<String, LocalTypeR>,
57 global_type: &GlobalType,
58 ) -> Self {
59 let programs = local_types
60 .iter()
61 .map(|(role, lt)| (role.clone(), crate::compiler::compile(lt)))
62 .collect();
63
64 Self {
65 programs,
66 global_type: global_type.clone(),
67 local_types: local_types.clone(),
68 }
69 }
70
71 #[must_use]
73 pub fn roles(&self) -> Vec<String> {
74 self.programs.keys().cloned().collect()
75 }
76
77 pub fn validate_runtime_shape(&self) -> Result<(), String> {
84 if self.programs.is_empty() {
85 return Err("code image must contain at least one role program".to_string());
86 }
87 if !self.global_type.well_formed() {
88 return Err("code image global type is not well-formed".to_string());
89 }
90 let program_roles: Vec<&String> = self.programs.keys().collect();
91 let type_roles: Vec<&String> = self.local_types.keys().collect();
92 if program_roles != type_roles {
93 return Err(format!(
94 "code image role mismatch: programs {:?}, local_types {:?}",
95 program_roles, type_roles
96 ));
97 }
98 Ok(())
99 }
100}
101
102impl UntrustedImage {
103 #[must_use]
105 pub fn from_local_types(
106 local_types: &BTreeMap<String, LocalTypeR>,
107 global_type: &GlobalType,
108 ) -> Self {
109 let programs = local_types
110 .iter()
111 .map(|(role, lt)| (role.clone(), crate::compiler::compile(lt)))
112 .collect();
113
114 Self {
115 programs,
116 global_type: global_type.clone(),
117 local_types: local_types.clone(),
118 }
119 }
120
121 pub fn validate(self) -> Result<CodeImage, LoadResult> {
132 if !self.global_type.well_formed() {
133 return Err(LoadResult::ValidationFailed {
134 reason: "global type is not well-formed".into(),
135 });
136 }
137
138 let projected =
140 telltale_theory::projection::project_all(&self.global_type).map_err(|e| {
141 LoadResult::ValidationFailed {
142 reason: format!("projection failed: {e}"),
143 }
144 })?;
145
146 let projected_map: BTreeMap<String, LocalTypeR> = projected.into_iter().collect();
147
148 if self.local_types.keys().collect::<Vec<_>>() != projected_map.keys().collect::<Vec<_>>() {
150 return Err(LoadResult::ValidationFailed {
151 reason: format!(
152 "role mismatch: claimed {:?}, projected {:?}",
153 self.local_types.keys().collect::<Vec<_>>(),
154 projected_map.keys().collect::<Vec<_>>(),
155 ),
156 });
157 }
158
159 for (role, claimed) in &self.local_types {
161 let expected = &projected_map[role];
162 if claimed != expected {
163 return Err(LoadResult::ValidationFailed {
164 reason: format!(
165 "local type mismatch for role {role}: claimed {claimed:?}, expected {expected:?}"
166 ),
167 });
168 }
169 }
170
171 Ok(CodeImage::from_local_types(
173 &projected_map,
174 &self.global_type,
175 ))
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182 use telltale_types::Label;
183
184 fn simple_global() -> GlobalType {
185 GlobalType::mu(
186 "step",
187 GlobalType::send(
188 "A",
189 "B",
190 Label::new("msg"),
191 GlobalType::send("B", "A", Label::new("msg"), GlobalType::var("step")),
192 ),
193 )
194 }
195
196 #[test]
197 fn test_untrusted_validate_correct() {
198 let global = simple_global();
199 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
200 .unwrap()
201 .into_iter()
202 .collect();
203 let image = UntrustedImage::from_local_types(&projected, &global);
204 let verified = image.validate();
205 assert!(verified.is_ok());
206 }
207
208 #[test]
209 fn test_untrusted_validate_bad_local_type() {
210 let global = simple_global();
211 let mut locals = BTreeMap::new();
212 locals.insert("A".to_string(), LocalTypeR::End);
214 locals.insert(
215 "B".to_string(),
216 LocalTypeR::mu(
217 "step",
218 LocalTypeR::Recv {
219 partner: "A".into(),
220 branches: vec![(
221 Label::new("msg"),
222 None,
223 LocalTypeR::Send {
224 partner: "A".into(),
225 branches: vec![(Label::new("msg"), None, LocalTypeR::var("step"))],
226 },
227 )],
228 },
229 ),
230 );
231 let image = UntrustedImage::from_local_types(&locals, &global);
232 let result = image.validate();
233 assert!(result.is_err());
234 }
235
236 #[test]
237 fn test_untrusted_validate_bad_global_type() {
238 let global = GlobalType::send("A", "A", Label::new("msg"), GlobalType::End);
240 let mut locals = BTreeMap::new();
241 locals.insert("A".to_string(), LocalTypeR::End);
242 let image = UntrustedImage::from_local_types(&locals, &global);
243 let result = image.validate();
244 assert!(result.is_err());
245 }
246
247 #[test]
248 fn test_trusted_and_untrusted_validated_images_match() {
249 let global = simple_global();
250 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
251 .unwrap()
252 .into_iter()
253 .collect();
254
255 let trusted = CodeImage::from_local_types(&projected, &global);
256 let validated = UntrustedImage::from_local_types(&projected, &global)
257 .validate()
258 .expect("untrusted image should validate");
259
260 assert_eq!(trusted.global_type, validated.global_type);
261 assert_eq!(trusted.local_types, validated.local_types);
262 assert_eq!(trusted.programs, validated.programs);
263 }
264
265 #[test]
266 fn test_validate_ignores_untrusted_program_payload_and_recompiles() {
267 let global = simple_global();
268 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
269 .unwrap()
270 .into_iter()
271 .collect();
272
273 let mut untrusted = UntrustedImage::from_local_types(&projected, &global);
274 untrusted
275 .programs
276 .insert("A".to_string(), vec![Instr::Halt, Instr::Halt]);
277 untrusted
278 .programs
279 .insert("B".to_string(), vec![Instr::Yield]);
280
281 let validated = untrusted
282 .validate()
283 .expect("validation should reproject and recompile");
284 let trusted = CodeImage::from_local_types(&projected, &global);
285
286 assert_eq!(validated.local_types, trusted.local_types);
287 assert_eq!(validated.programs, trusted.programs);
288 }
289
290 #[test]
291 fn test_trusted_runtime_shape_rejects_program_local_type_role_mismatch() {
292 let global = simple_global();
293 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
294 .unwrap()
295 .into_iter()
296 .collect();
297 let mut image = CodeImage::from_local_types(&projected, &global);
298 image.programs.remove("B");
299 assert!(image.validate_runtime_shape().is_err());
300 }
301}