use std::collections::BTreeMap;
use telltale_types::{GlobalType, LocalTypeR};
use crate::instr::Instr;
#[derive(Debug, Clone)]
pub struct CodeImage {
pub programs: BTreeMap<String, Vec<Instr>>,
pub global_type: GlobalType,
pub local_types: BTreeMap<String, LocalTypeR>,
}
#[cfg(test)]
#[derive(Debug, Clone)]
pub(crate) struct UntrustedImage {
pub programs: BTreeMap<String, Vec<Instr>>,
pub global_type: GlobalType,
pub local_types: BTreeMap<String, LocalTypeR>,
}
#[cfg(test)]
#[derive(Debug)]
pub(crate) enum LoadResult {
ValidationFailed {
reason: String,
},
}
#[cfg(test)]
impl LoadResult {
fn reason(&self) -> &str {
match self {
Self::ValidationFailed { reason } => reason,
}
}
}
impl CodeImage {
#[must_use]
pub fn from_local_types(
local_types: &BTreeMap<String, LocalTypeR>,
global_type: &GlobalType,
) -> Self {
let programs = local_types
.iter()
.map(|(role, lt)| (role.clone(), crate::compiler::compile(lt)))
.collect();
Self {
programs,
global_type: global_type.clone(),
local_types: local_types.clone(),
}
}
#[must_use]
pub fn roles(&self) -> Vec<String> {
self.programs.keys().cloned().collect()
}
pub fn validate_runtime_shape(&self) -> Result<(), String> {
if self.programs.is_empty() {
return Err("code image must contain at least one role program".to_string());
}
if !self.global_type.well_formed() {
return Err("code image global type is not well-formed".to_string());
}
let program_roles: Vec<&String> = self.programs.keys().collect();
let type_roles: Vec<&String> = self.local_types.keys().collect();
if program_roles != type_roles {
return Err(format!(
"code image role mismatch: programs {:?}, local_types {:?}",
program_roles, type_roles
));
}
Ok(())
}
}
#[cfg(test)]
impl UntrustedImage {
#[must_use]
pub(crate) fn from_local_types(
local_types: &BTreeMap<String, LocalTypeR>,
global_type: &GlobalType,
) -> Self {
let programs = local_types
.iter()
.map(|(role, lt)| (role.clone(), crate::compiler::compile(lt)))
.collect();
Self {
programs,
global_type: global_type.clone(),
local_types: local_types.clone(),
}
}
pub(crate) fn validate(self) -> Result<CodeImage, LoadResult> {
if !self.global_type.well_formed() {
return Err(LoadResult::ValidationFailed {
reason: "global type is not well-formed".into(),
});
}
let projected =
telltale_theory::projection::project_all(&self.global_type).map_err(|e| {
LoadResult::ValidationFailed {
reason: format!("projection failed: {e}"),
}
})?;
let projected_map: BTreeMap<String, LocalTypeR> = projected.into_iter().collect();
if self.local_types.keys().collect::<Vec<_>>() != projected_map.keys().collect::<Vec<_>>() {
return Err(LoadResult::ValidationFailed {
reason: format!(
"role mismatch: claimed {:?}, projected {:?}",
self.local_types.keys().collect::<Vec<_>>(),
projected_map.keys().collect::<Vec<_>>(),
),
});
}
for (role, claimed) in &self.local_types {
let expected = &projected_map[role];
if claimed != expected {
return Err(LoadResult::ValidationFailed {
reason: format!(
"local type mismatch for role {role}: claimed {claimed:?}, expected {expected:?}"
),
});
}
}
Ok(CodeImage::from_local_types(
&projected_map,
&self.global_type,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use telltale_types::Label;
fn simple_global() -> GlobalType {
GlobalType::mu(
"step",
GlobalType::send(
"A",
"B",
Label::new("msg"),
GlobalType::send("B", "A", Label::new("msg"), GlobalType::var("step")),
),
)
}
#[test]
fn test_untrusted_validate_correct() {
let global = simple_global();
let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
.expect("projection should succeed for a well-formed test global")
.into_iter()
.collect();
let image = UntrustedImage::from_local_types(&projected, &global);
let verified = image.validate();
assert!(verified.is_ok());
}
#[test]
fn test_untrusted_validate_bad_local_type() {
let global = simple_global();
let mut locals = BTreeMap::new();
locals.insert("A".to_string(), LocalTypeR::End);
locals.insert(
"B".to_string(),
LocalTypeR::mu(
"step",
LocalTypeR::Recv {
partner: "A".into(),
branches: vec![(
Label::new("msg"),
None,
LocalTypeR::Send {
partner: "A".into(),
branches: vec![(Label::new("msg"), None, LocalTypeR::var("step"))],
},
)],
},
),
);
let image = UntrustedImage::from_local_types(&locals, &global);
let result = image.validate();
assert!(result.is_err());
let err = result.expect_err("validation should fail");
assert!(err.reason().contains("local type mismatch"));
}
#[test]
fn test_untrusted_validate_bad_global_type() {
let global = GlobalType::send("A", "A", Label::new("msg"), GlobalType::End);
let mut locals = BTreeMap::new();
locals.insert("A".to_string(), LocalTypeR::End);
let image = UntrustedImage::from_local_types(&locals, &global);
let result = image.validate();
assert!(result.is_err());
let err = result.expect_err("validation should fail");
assert!(err.reason().contains("not well-formed"));
}
#[test]
fn test_trusted_and_untrusted_validated_images_match() {
let global = simple_global();
let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
.expect("projection should succeed for a well-formed test global")
.into_iter()
.collect();
let trusted = CodeImage::from_local_types(&projected, &global);
let validated = UntrustedImage::from_local_types(&projected, &global)
.validate()
.expect("untrusted image should validate");
assert_eq!(trusted.global_type, validated.global_type);
assert_eq!(trusted.local_types, validated.local_types);
assert_eq!(trusted.programs, validated.programs);
}
#[test]
fn test_validate_ignores_untrusted_program_payload_and_recompiles() {
let global = simple_global();
let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
.expect("projection should succeed for a well-formed test global")
.into_iter()
.collect();
let mut untrusted = UntrustedImage::from_local_types(&projected, &global);
untrusted
.programs
.insert("A".to_string(), vec![Instr::Halt, Instr::Halt]);
untrusted
.programs
.insert("B".to_string(), vec![Instr::Yield]);
let validated = untrusted
.validate()
.expect("validation should reproject and recompile");
let trusted = CodeImage::from_local_types(&projected, &global);
assert_eq!(validated.local_types, trusted.local_types);
assert_eq!(validated.programs, trusted.programs);
}
#[test]
fn test_trusted_runtime_shape_rejects_program_local_type_role_mismatch() {
let global = simple_global();
let projected: BTreeMap<_, _> = telltale_theory::projection::project_all(&global)
.expect("projection should succeed for a well-formed test global")
.into_iter()
.collect();
let mut image = CodeImage::from_local_types(&projected, &global);
image.programs.remove("B");
assert!(image.validate_runtime_shape().is_err());
}
}