logicaffeine_compile/spatial/lower/
rdu_sdk.rs1use std::fmt;
16
17use super::rdu::parse_neutral;
18use super::NeutralArtifact;
19use crate::spatial::certificate::{certify_external_spatial, ChoreographyCertificate};
20use crate::spatial::ir::SpatialProgram;
21
22pub const NEUTRAL_FORMAT: &str = "logos.spatial.neutral+json";
24
25pub const SAMBAFLOW_REASON: &str = "needs the vendor SDK";
27
28pub const BRIDGE_NAMES: &[&str] = &["mock", "sambaflow"];
30
31#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct VendorConfig {
35 pub format: String,
36 pub bytes: Vec<u8>,
37 pub certificate_digest: String,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct VendorPlacement {
44 pub program_json: String,
45}
46
47#[derive(Clone, Debug, PartialEq, Eq)]
48pub enum BridgeRefusal {
49 NotImplemented {
51 bridge: &'static str,
52 reason: &'static str,
53 },
54 Malformed(String),
56 Uncertified(String),
58}
59
60impl fmt::Display for BridgeRefusal {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match self {
63 BridgeRefusal::NotImplemented { bridge, reason } => {
64 write!(f, "rdu bridge: {bridge} is not implemented — {reason}")
65 }
66 BridgeRefusal::Malformed(why) => {
67 write!(f, "rdu bridge: malformed neutral artifact — {why}")
68 }
69 BridgeRefusal::Uncertified(why) => {
70 write!(f, "rdu bridge: uncertified artifact — {why}")
71 }
72 }
73 }
74}
75
76impl std::error::Error for BridgeRefusal {}
77
78pub fn certificate_digest(cert: &ChoreographyCertificate) -> String {
80 format!("{:016x}", cert.schedule_digest)
81}
82
83pub fn envelope_digest(json: &str) -> Result<String, BridgeRefusal> {
85 let v: serde_json::Value =
86 serde_json::from_str(json).map_err(|e| BridgeRefusal::Malformed(e.to_string()))?;
87 let digest = v
88 .get("certificate")
89 .and_then(|c| c.get("schedule_digest"))
90 .and_then(serde_json::Value::as_u64)
91 .ok_or_else(|| {
92 BridgeRefusal::Malformed("no certificate.schedule_digest in the envelope".to_string())
93 })?;
94 Ok(format!("{digest:016x}"))
95}
96
97pub trait RdaBridge {
99 fn name(&self) -> &str;
100 fn ingest(&self, neutral: &NeutralArtifact) -> Result<VendorConfig, BridgeRefusal>;
103 fn certify_back(
105 &self,
106 placement: &VendorPlacement,
107 source: &SpatialProgram,
108 ) -> Option<ChoreographyCertificate>;
109}
110
111pub struct MockBridge;
113
114impl RdaBridge for MockBridge {
115 fn name(&self) -> &str {
116 "mock"
117 }
118
119 fn ingest(&self, neutral: &NeutralArtifact) -> Result<VendorConfig, BridgeRefusal> {
120 let program = parse_neutral(&neutral.json).map_err(BridgeRefusal::Malformed)?;
121 let digest = certificate_digest(&neutral.certificate);
122 let in_envelope = envelope_digest(&neutral.json)?;
125 if in_envelope != digest {
126 return Err(BridgeRefusal::Uncertified(format!("the envelope names certificate digest {in_envelope} but the certificate beside it is {digest}")));
127 }
128 if !neutral.certificate.recheck(&program) {
129 return Err(BridgeRefusal::Uncertified(format!("the certificate (digest {digest}) does not recheck against the program in the artifact")));
130 }
131 Ok(VendorConfig {
132 format: NEUTRAL_FORMAT.to_string(),
133 bytes: neutral.json.clone().into_bytes(),
134 certificate_digest: digest,
135 })
136 }
137
138 fn certify_back(
139 &self,
140 placement: &VendorPlacement,
141 source: &SpatialProgram,
142 ) -> Option<ChoreographyCertificate> {
143 let external = parse_neutral(&placement.program_json).ok()?;
144 certify_external_spatial(&external, source)
145 }
146}
147
148pub struct SambaFlowBridge;
150
151impl RdaBridge for SambaFlowBridge {
152 fn name(&self) -> &str {
153 "sambaflow"
154 }
155
156 fn ingest(&self, _neutral: &NeutralArtifact) -> Result<VendorConfig, BridgeRefusal> {
157 Err(BridgeRefusal::NotImplemented {
158 bridge: "sambaflow",
159 reason: SAMBAFLOW_REASON,
160 })
161 }
162
163 fn certify_back(
164 &self,
165 _placement: &VendorPlacement,
166 _source: &SpatialProgram,
167 ) -> Option<ChoreographyCertificate> {
168 None
169 }
170}
171
172pub fn bridge_by_name(name: &str) -> Option<Box<dyn RdaBridge>> {
174 match name {
175 "mock" => Some(Box::new(MockBridge)),
176 "sambaflow" => Some(Box::new(SambaFlowBridge)),
177 _ => None,
178 }
179}