Skip to main content

telltale_vm/
loader.rs

1//! Dynamic choreography loading.
2//!
3//! Matches `CodeImage`, `UntrustedImage`, `loadTrusted`, `loadUntrusted`
4//! from `lean/Runtime/VM/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#[derive(Debug, Clone)]
31pub struct UntrustedImage {
32    /// Bytecode programs per role.
33    pub programs: BTreeMap<String, Vec<Instr>>,
34    /// The global type this image was derived from.
35    pub global_type: GlobalType,
36    /// Projected local types per role (claimed, not yet verified).
37    pub local_types: BTreeMap<String, LocalTypeR>,
38}
39
40/// Result of loading a code image.
41#[derive(Debug)]
42pub enum LoadResult {
43    /// Image loaded successfully.
44    Ok,
45    /// Validation failed.
46    ValidationFailed {
47        /// Description of the validation failure.
48        reason: String,
49    },
50}
51
52impl CodeImage {
53    /// Create a code image from projected local types by compiling each to bytecode.
54    #[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    /// Role names in this image.
72    #[must_use]
73    pub fn roles(&self) -> Vec<String> {
74        self.programs.keys().cloned().collect()
75    }
76
77    /// Validate trusted-image runtime shape constraints.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error string when the image shape violates VM runtime
82    /// expectations (role coverage, type coverage, or global well-formedness).
83    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    /// Create an untrusted image from projected local types.
104    #[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    /// Validate the image: check well-formedness and projection correctness.
122    ///
123    /// 1. Checks that the global type is well-formed.
124    /// 2. Re-projects the global type onto each role.
125    /// 3. Verifies that the claimed local types match the re-projected types.
126    /// 4. Recompiles from the verified local types (ignoring claimed bytecode).
127    ///
128    /// # Errors
129    ///
130    /// Returns `LoadResult::ValidationFailed` if any check fails.
131    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        // Re-project global type onto all roles.
139        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        // Verify claimed roles match projected roles.
149        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        // Verify each claimed local type matches the re-projected type.
160        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        // Recompile from verified local types (ignore claimed bytecode).
172        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        // Claim A has End instead of correct projection.
213        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        // Self-communication is not well-formed.
239        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}