plecto_host/
conformance.rs1use crate::dev_signer::{DevSigner, bound_sbom};
15use crate::{Header, Host, HttpRequest, LoadOptions, LoadedFilter, RequestTrace, SignedArtifact};
16
17pub struct ConformanceCheck {
20 pub name: &'static str,
21 pub passed: bool,
22 pub detail: String,
23}
24
25pub struct ConformanceReport {
28 pub checks: Vec<ConformanceCheck>,
29}
30
31impl ConformanceReport {
32 pub fn is_conformant(&self) -> bool {
33 self.checks.iter().all(|c| c.passed)
34 }
35}
36
37pub fn check(component_bytes: &[u8]) -> ConformanceReport {
39 let mut checks = Vec::new();
40
41 let loaded = load_self_signed(component_bytes);
42 checks.push(ConformanceCheck {
43 name: "loads under the plecto:filter contract",
44 passed: loaded.is_ok(),
45 detail: match &loaded {
46 Ok(_) => "component/SBOM self-signature verified, world satisfied".to_string(),
47 Err(e) => format!("{e:#}"),
48 },
49 });
50
51 let Ok(filter) = loaded else {
52 checks.push(ConformanceCheck {
53 name: "handles a generic request without trapping or exceeding its deadline",
54 passed: false,
55 detail: "skipped: component did not load".to_string(),
56 });
57 return ConformanceReport { checks };
58 };
59
60 let outcome = filter.on_request(&generic_request(), &RequestTrace::root());
61 checks.push(ConformanceCheck {
62 name: "handles a generic request without trapping or exceeding its deadline",
63 passed: outcome.is_ok(),
64 detail: match &outcome {
65 Ok((decision, _logs)) => format!("responded with {}", decision_kind(decision)),
66 Err(e) => e.to_string(),
67 },
68 });
69
70 ConformanceReport { checks }
71}
72
73fn load_self_signed(component_bytes: &[u8]) -> anyhow::Result<LoadedFilter> {
74 let (signer, _private_key_pem) = DevSigner::generate()?;
75 let component_signature = signer.sign(component_bytes)?;
76 let sbom = bound_sbom(component_bytes);
77 let sbom_signature = signer.sign(&sbom)?;
78 let host = Host::new(signer.trust_policy()?)?;
79 let artifact = SignedArtifact {
80 component_bytes,
81 component_signature: &component_signature,
82 sbom: &sbom,
83 sbom_signature: &sbom_signature,
84 };
85 host.load("conformance", &artifact, LoadOptions::untrusted())
88}
89
90fn generic_request() -> HttpRequest {
91 HttpRequest {
92 method: "GET".to_string(),
93 path: "/".to_string(),
94 authority: "conformance.invalid".to_string(),
95 scheme: "https".to_string(),
96 headers: vec![Header {
97 name: "user-agent".to_string(),
98 value: b"plecto-conformance".to_vec(),
99 }],
100 }
101}
102
103fn decision_kind(decision: &crate::RequestDecision) -> &'static str {
104 match decision {
105 crate::RequestDecision::Continue => "continue",
106 crate::RequestDecision::Modified(_) => "modified",
107 crate::RequestDecision::ShortCircuit(_) => "short-circuit",
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn a_real_filter_is_conformant() {
117 let component = crate::test_support::filter_hello_component();
118 let report = check(&component);
119 for c in &report.checks {
120 assert!(c.passed, "{}: {}", c.name, c.detail);
121 }
122 assert!(report.is_conformant());
123 }
124
125 #[test]
126 fn garbage_bytes_fail_the_load_check() {
127 let report = check(b"not a wasm component");
128 assert!(!report.is_conformant());
129 assert!(!report.checks[0].passed);
130 assert_eq!(
131 report.checks.len(),
132 2,
133 "the runtime check is skipped, not silently dropped"
134 );
135 assert!(!report.checks[1].passed);
136 assert!(report.checks[1].detail.contains("skipped"));
137 }
138}