Skip to main content

telltale_machine/
loader.rs

1//! Dynamic choreography loading.
2//!
3//! Matches `CodeImage`, `UntrustedImage`, `loadTrusted`, `loadUntrusted`
4//! from `lean/Runtime/ProtocolMachine/Model/Program.lean`.
5
6use std::collections::BTreeMap;
7
8use telltale_types::{GlobalType, LocalTypeR};
9
10use crate::instr::Instr;
11
12/// A verified code image: program + global type + local types.
13///
14/// In the Lean spec, this carries well-formedness and projection correctness
15/// proofs. In Rust, the proofs are replaced by runtime validation that was
16/// performed before constructing the image.
17#[derive(Debug, Clone)]
18pub struct CodeImage {
19    /// Bytecode programs per role.
20    pub programs: BTreeMap<String, Vec<Instr>>,
21    /// The global type this image was derived from.
22    pub global_type: GlobalType,
23    /// Projected local types per role.
24    pub local_types: BTreeMap<String, LocalTypeR>,
25}
26
27/// An unverified code image: program + global type, no validation proof.
28///
29/// Must be validated before execution via `validate`.
30#[cfg(test)]
31#[derive(Debug, Clone)]
32pub(crate) struct UntrustedImage {
33    /// Bytecode programs per role.
34    pub programs: BTreeMap<String, Vec<Instr>>,
35    /// The global type this image was derived from.
36    pub global_type: GlobalType,
37    /// Projected local types per role (claimed, not yet verified).
38    pub local_types: BTreeMap<String, LocalTypeR>,
39}
40
41/// Result of validating an untrusted code image in tests.
42#[cfg(test)]
43#[derive(Debug)]
44pub(crate) enum LoadResult {
45    /// Validation failed.
46    ValidationFailed {
47        /// Description of the validation failure.
48        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    /// Create a code image from projected local types by compiling each to bytecode.
63    #[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    /// Role names in this image.
81    #[must_use]
82    pub fn roles(&self) -> Vec<String> {
83        self.programs.keys().cloned().collect()
84    }
85
86    /// Validate trusted-image runtime shape constraints.
87    ///
88    /// # Errors
89    ///
90    /// Returns an error string when the image shape violates ProtocolMachine runtime
91    /// expectations (role coverage, type coverage, or global well-formedness).
92    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    /// Create an untrusted image from projected local types.
114    #[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    /// Validate the image: check well-formedness and projection correctness.
132    ///
133    /// 1. Checks that the global type is well-formed.
134    /// 2. Re-projects the global type onto each role.
135    /// 3. Verifies that the claimed local types match the re-projected types.
136    /// 4. Recompiles from the verified local types (ignoring claimed bytecode).
137    ///
138    /// # Errors
139    ///
140    /// Returns `LoadResult::ValidationFailed` if any check fails.
141    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        // Re-project global type onto all roles.
149        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        // Verify claimed roles match projected roles.
159        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        // Verify each claimed local type matches the re-projected type.
170        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        // Recompile from verified local types (ignore claimed bytecode).
182        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        // Claim A has End instead of correct projection.
223        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        // Self-communication is not well-formed.
251        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}