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
78impl UntrustedImage {
79 #[must_use]
81 pub fn from_local_types(
82 local_types: &BTreeMap<String, LocalTypeR>,
83 global_type: &GlobalType,
84 ) -> Self {
85 let programs = local_types
86 .iter()
87 .map(|(role, lt)| (role.clone(), crate::compiler::compile(lt)))
88 .collect();
89
90 Self {
91 programs,
92 global_type: global_type.clone(),
93 local_types: local_types.clone(),
94 }
95 }
96
97 pub fn validate(self) -> Result<CodeImage, LoadResult> {
108 if !self.global_type.well_formed() {
109 return Err(LoadResult::ValidationFailed {
110 reason: "global type is not well-formed".into(),
111 });
112 }
113
114 let projected =
116 telltale_theory::projection::project_all(&self.global_type).map_err(|e| {
117 LoadResult::ValidationFailed {
118 reason: format!("projection failed: {e}"),
119 }
120 })?;
121
122 let projected_map: BTreeMap<String, LocalTypeR> = projected.into_iter().collect();
123
124 if self.local_types.keys().collect::<Vec<_>>() != projected_map.keys().collect::<Vec<_>>() {
126 return Err(LoadResult::ValidationFailed {
127 reason: format!(
128 "role mismatch: claimed {:?}, projected {:?}",
129 self.local_types.keys().collect::<Vec<_>>(),
130 projected_map.keys().collect::<Vec<_>>(),
131 ),
132 });
133 }
134
135 for (role, claimed) in &self.local_types {
137 let expected = &projected_map[role];
138 if claimed != expected {
139 return Err(LoadResult::ValidationFailed {
140 reason: format!(
141 "local type mismatch for role {role}: claimed {claimed:?}, expected {expected:?}"
142 ),
143 });
144 }
145 }
146
147 Ok(CodeImage::from_local_types(
149 &projected_map,
150 &self.global_type,
151 ))
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use telltale_types::Label;
159
160 fn simple_global() -> GlobalType {
161 GlobalType::mu(
162 "step",
163 GlobalType::send(
164 "A",
165 "B",
166 Label::new("msg"),
167 GlobalType::send("B", "A", Label::new("msg"), GlobalType::var("step")),
168 ),
169 )
170 }
171
172 #[test]
173 fn test_untrusted_validate_correct() {
174 let global = simple_global();
175 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
176 .unwrap()
177 .into_iter()
178 .collect();
179 let image = UntrustedImage::from_local_types(&projected, &global);
180 let verified = image.validate();
181 assert!(verified.is_ok());
182 }
183
184 #[test]
185 fn test_untrusted_validate_bad_local_type() {
186 let global = simple_global();
187 let mut locals = BTreeMap::new();
188 locals.insert("A".to_string(), LocalTypeR::End);
190 locals.insert(
191 "B".to_string(),
192 LocalTypeR::mu(
193 "step",
194 LocalTypeR::Recv {
195 partner: "A".into(),
196 branches: vec![(
197 Label::new("msg"),
198 None,
199 LocalTypeR::Send {
200 partner: "A".into(),
201 branches: vec![(Label::new("msg"), None, LocalTypeR::var("step"))],
202 },
203 )],
204 },
205 ),
206 );
207 let image = UntrustedImage::from_local_types(&locals, &global);
208 let result = image.validate();
209 assert!(result.is_err());
210 }
211
212 #[test]
213 fn test_untrusted_validate_bad_global_type() {
214 let global = GlobalType::send("A", "A", Label::new("msg"), GlobalType::End);
216 let mut locals = BTreeMap::new();
217 locals.insert("A".to_string(), LocalTypeR::End);
218 let image = UntrustedImage::from_local_types(&locals, &global);
219 let result = image.validate();
220 assert!(result.is_err());
221 }
222
223 #[test]
224 fn test_trusted_and_untrusted_validated_images_match() {
225 let global = simple_global();
226 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
227 .unwrap()
228 .into_iter()
229 .collect();
230
231 let trusted = CodeImage::from_local_types(&projected, &global);
232 let validated = UntrustedImage::from_local_types(&projected, &global)
233 .validate()
234 .expect("untrusted image should validate");
235
236 assert_eq!(trusted.global_type, validated.global_type);
237 assert_eq!(trusted.local_types, validated.local_types);
238 assert_eq!(trusted.programs, validated.programs);
239 }
240
241 #[test]
242 fn test_validate_ignores_untrusted_program_payload_and_recompiles() {
243 let global = simple_global();
244 let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
245 .unwrap()
246 .into_iter()
247 .collect();
248
249 let mut untrusted = UntrustedImage::from_local_types(&projected, &global);
250 untrusted
251 .programs
252 .insert("A".to_string(), vec![Instr::Halt, Instr::Halt]);
253 untrusted
254 .programs
255 .insert("B".to_string(), vec![Instr::Yield]);
256
257 let validated = untrusted
258 .validate()
259 .expect("validation should reproject and recompile");
260 let trusted = CodeImage::from_local_types(&projected, &global);
261
262 assert_eq!(validated.local_types, trusted.local_types);
263 assert_eq!(validated.programs, trusted.programs);
264 }
265}