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#[cfg(test)]
31#[derive(Debug, Clone)]
32pub(crate) struct UntrustedImage {
33 pub programs: BTreeMap<String, Vec<Instr>>,
35 pub global_type: GlobalType,
37 pub local_types: BTreeMap<String, LocalTypeR>,
39}
40
41#[cfg(test)]
43#[derive(Debug)]
44pub(crate) enum LoadResult {
45 ValidationFailed {
47 reason: String,
49 },
50}
51
52#[cfg(test)]
53impl LoadResult {
54 fn reason(&self) -> &str {
55 match self {
56 Self::ValidationFailed { reason } => reason,
57 }
58 }
59}
60
61impl CodeImage {
62 #[must_use]
64 pub fn from_local_types(
65 local_types: &BTreeMap<String, LocalTypeR>,
66 global_type: &GlobalType,
67 ) -> Self {
68 let programs = local_types
69 .iter()
70 .map(|(role, lt)| (role.clone(), crate::compiler::compile(lt)))
71 .collect();
72
73 Self {
74 programs,
75 global_type: global_type.clone(),
76 local_types: local_types.clone(),
77 }
78 }
79
80 #[must_use]
82 pub fn roles(&self) -> Vec<String> {
83 self.programs.keys().cloned().collect()
84 }
85
86 pub fn validate_runtime_shape(&self) -> Result<(), String> {
93 if self.programs.is_empty() {
94 return Err("code image must contain at least one role program".to_string());
95 }
96 if !self.global_type.well_formed() {
97 return Err("code image global type is not well-formed".to_string());
98 }
99 let program_roles: Vec<&String> = self.programs.keys().collect();
100 let type_roles: Vec<&String> = self.local_types.keys().collect();
101 if program_roles != type_roles {
102 return Err(format!(
103 "code image role mismatch: programs {:?}, local_types {:?}",
104 program_roles, type_roles
105 ));
106 }
107 Ok(())
108 }
109}
110
111#[cfg(test)]
112impl UntrustedImage {
113 #[must_use]
115 pub(crate) fn from_local_types(
116 local_types: &BTreeMap<String, LocalTypeR>,
117 global_type: &GlobalType,
118 ) -> Self {
119 let programs = local_types
120 .iter()
121 .map(|(role, lt)| (role.clone(), crate::compiler::compile(lt)))
122 .collect();
123
124 Self {
125 programs,
126 global_type: global_type.clone(),
127 local_types: local_types.clone(),
128 }
129 }
130
131 pub(crate) fn validate(self) -> Result<CodeImage, LoadResult> {
142 if !self.global_type.well_formed() {
143 return Err(LoadResult::ValidationFailed {
144 reason: "global type is not well-formed".into(),
145 });
146 }
147
148 let projected =
150 telltale_theory::projection::project_all(&self.global_type).map_err(|e| {
151 LoadResult::ValidationFailed {
152 reason: format!("projection failed: {e}"),
153 }
154 })?;
155
156 let projected_map: BTreeMap<String, LocalTypeR> = projected.into_iter().collect();
157
158 if self.local_types.keys().collect::<Vec<_>>() != projected_map.keys().collect::<Vec<_>>() {
160 return Err(LoadResult::ValidationFailed {
161 reason: format!(
162 "role mismatch: claimed {:?}, projected {:?}",
163 self.local_types.keys().collect::<Vec<_>>(),
164 projected_map.keys().collect::<Vec<_>>(),
165 ),
166 });
167 }
168
169 for (role, claimed) in &self.local_types {
171 let expected = &projected_map[role];
172 if claimed != expected {
173 return Err(LoadResult::ValidationFailed {
174 reason: format!(
175 "local type mismatch for role {role}: claimed {claimed:?}, expected {expected:?}"
176 ),
177 });
178 }
179 }
180
181 Ok(CodeImage::from_local_types(
183 &projected_map,
184 &self.global_type,
185 ))
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use telltale_types::Label;
193
194 fn simple_global() -> GlobalType {
195 GlobalType::mu(
196 "step",
197 GlobalType::send(
198 "A",
199 "B",
200 Label::new("msg"),
201 GlobalType::send("B", "A", Label::new("msg"), GlobalType::var("step")),
202 ),
203 )
204 }
205
206 #[test]
207 fn test_untrusted_validate_correct() {
208 let global = simple_global();
209 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
210 .expect("projection should succeed for a well-formed test global")
211 .into_iter()
212 .collect();
213 let image = UntrustedImage::from_local_types(&projected, &global);
214 let verified = image.validate();
215 assert!(verified.is_ok());
216 }
217
218 #[test]
219 fn test_untrusted_validate_bad_local_type() {
220 let global = simple_global();
221 let mut locals = BTreeMap::new();
222 locals.insert("A".to_string(), LocalTypeR::End);
224 locals.insert(
225 "B".to_string(),
226 LocalTypeR::mu(
227 "step",
228 LocalTypeR::Recv {
229 partner: "A".into(),
230 branches: vec![(
231 Label::new("msg"),
232 None,
233 LocalTypeR::Send {
234 partner: "A".into(),
235 branches: vec![(Label::new("msg"), None, LocalTypeR::var("step"))],
236 },
237 )],
238 },
239 ),
240 );
241 let image = UntrustedImage::from_local_types(&locals, &global);
242 let result = image.validate();
243 assert!(result.is_err());
244 let err = result.expect_err("validation should fail");
245 assert!(err.reason().contains("local type mismatch"));
246 }
247
248 #[test]
249 fn test_untrusted_validate_bad_global_type() {
250 let global = GlobalType::send("A", "A", Label::new("msg"), GlobalType::End);
252 let mut locals = BTreeMap::new();
253 locals.insert("A".to_string(), LocalTypeR::End);
254 let image = UntrustedImage::from_local_types(&locals, &global);
255 let result = image.validate();
256 assert!(result.is_err());
257 let err = result.expect_err("validation should fail");
258 assert!(err.reason().contains("not well-formed"));
259 }
260
261 #[test]
262 fn test_trusted_and_untrusted_validated_images_match() {
263 let global = simple_global();
264 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
265 .expect("projection should succeed for a well-formed test global")
266 .into_iter()
267 .collect();
268
269 let trusted = CodeImage::from_local_types(&projected, &global);
270 let validated = UntrustedImage::from_local_types(&projected, &global)
271 .validate()
272 .expect("untrusted image should validate");
273
274 assert_eq!(trusted.global_type, validated.global_type);
275 assert_eq!(trusted.local_types, validated.local_types);
276 assert_eq!(trusted.programs, validated.programs);
277 }
278
279 #[test]
280 fn test_validate_ignores_untrusted_program_payload_and_recompiles() {
281 let global = simple_global();
282 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
283 .expect("projection should succeed for a well-formed test global")
284 .into_iter()
285 .collect();
286
287 let mut untrusted = UntrustedImage::from_local_types(&projected, &global);
288 untrusted
289 .programs
290 .insert("A".to_string(), vec![Instr::Halt, Instr::Halt]);
291 untrusted
292 .programs
293 .insert("B".to_string(), vec![Instr::Yield]);
294
295 let validated = untrusted
296 .validate()
297 .expect("validation should reproject and recompile");
298 let trusted = CodeImage::from_local_types(&projected, &global);
299
300 assert_eq!(validated.local_types, trusted.local_types);
301 assert_eq!(validated.programs, trusted.programs);
302 }
303
304 #[test]
305 fn test_trusted_runtime_shape_rejects_program_local_type_role_mismatch() {
306 let global = simple_global();
307 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
308 .expect("projection should succeed for a well-formed test global")
309 .into_iter()
310 .collect();
311 let mut image = CodeImage::from_local_types(&projected, &global);
312 image.programs.remove("B");
313 assert!(image.validate_runtime_shape().is_err());
314 }
315}