1use serde::{Deserialize, Serialize};
16
17pub const ATTEST_VSOCK_PORT: u32 = a3s_transport::ports::TEE_CHANNEL;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[serde(rename_all = "snake_case")]
27pub enum TeeType {
28 SevSnp,
30 Tdx,
32 Simulated,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38pub struct TeeCapability {
39 pub available: bool,
41 pub tee_type: Option<TeeType>,
43 pub sev_guest_device: bool,
45 pub sev_device: bool,
47 pub simulated: bool,
49}
50
51pub fn detect_tee() -> TeeCapability {
69 let simulated = std::env::var("A3S_TEE_SIMULATE").is_ok();
70 let sev_guest_device = std::path::Path::new("/dev/sev-guest").exists();
71 let sev_device = std::path::Path::new("/dev/sev").exists();
72 let tdx_guest_device = std::path::Path::new("/dev/tdx_guest").exists()
73 || std::path::Path::new("/dev/tdx-guest").exists();
74
75 let (available, tee_type) = if simulated {
76 (true, Some(TeeType::Simulated))
77 } else if sev_guest_device || sev_device {
78 (true, Some(TeeType::SevSnp))
79 } else if tdx_guest_device {
80 (true, Some(TeeType::Tdx))
81 } else {
82 (false, None)
83 };
84
85 TeeCapability {
86 available,
87 tee_type,
88 sev_guest_device,
89 sev_device,
90 simulated,
91 }
92}
93
94pub fn is_tee_available() -> bool {
98 detect_tee().available
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct AttestRequest {
104 pub route: AttestRoute,
106 #[serde(default)]
108 pub payload: serde_json::Value,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
113#[serde(rename_all = "snake_case")]
114pub enum AttestRoute {
115 Status,
117 Secrets,
119 Seal,
121 Unseal,
123 Process,
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
134 fn test_detect_tee_returns_capability() {
135 let cap = detect_tee();
136 assert_eq!(cap.available, cap.tee_type.is_some());
139 }
140
141 #[test]
142 fn test_is_tee_available_matches_detect() {
143 let cap = detect_tee();
144 assert_eq!(is_tee_available(), cap.available);
145 }
146
147 #[test]
148 fn test_tee_capability_serde_roundtrip() {
149 let cap = TeeCapability {
150 available: true,
151 tee_type: Some(TeeType::SevSnp),
152 sev_guest_device: true,
153 sev_device: true,
154 simulated: false,
155 };
156 let json = serde_json::to_string(&cap).unwrap();
157 let parsed: TeeCapability = serde_json::from_str(&json).unwrap();
158 assert_eq!(parsed, cap);
159 }
160
161 #[test]
162 fn test_tee_capability_simulated() {
163 let cap = TeeCapability {
164 available: true,
165 tee_type: Some(TeeType::Simulated),
166 sev_guest_device: false,
167 sev_device: false,
168 simulated: true,
169 };
170 let json = serde_json::to_string(&cap).unwrap();
171 assert!(json.contains("\"simulated\""));
172 let parsed: TeeCapability = serde_json::from_str(&json).unwrap();
173 assert_eq!(parsed.tee_type, Some(TeeType::Simulated));
174 }
175
176 #[test]
177 fn test_tee_capability_none() {
178 let cap = TeeCapability {
179 available: false,
180 tee_type: None,
181 sev_guest_device: false,
182 sev_device: false,
183 simulated: false,
184 };
185 assert!(!cap.available);
186 assert!(cap.tee_type.is_none());
187 }
188
189 #[test]
190 fn test_tee_type_serde() {
191 assert_eq!(
192 serde_json::to_string(&TeeType::SevSnp).unwrap(),
193 "\"sev_snp\""
194 );
195 assert_eq!(serde_json::to_string(&TeeType::Tdx).unwrap(), "\"tdx\"");
196 assert_eq!(
197 serde_json::to_string(&TeeType::Simulated).unwrap(),
198 "\"simulated\""
199 );
200 }
201
202 #[test]
203 fn test_tee_type_tdx_roundtrip() {
204 let cap = TeeCapability {
205 available: true,
206 tee_type: Some(TeeType::Tdx),
207 sev_guest_device: false,
208 sev_device: false,
209 simulated: false,
210 };
211 let json = serde_json::to_string(&cap).unwrap();
212 let parsed: TeeCapability = serde_json::from_str(&json).unwrap();
213 assert_eq!(parsed.tee_type, Some(TeeType::Tdx));
214 assert!(parsed.available);
215 }
216
217 #[test]
220 fn test_attest_vsock_port() {
221 assert_eq!(ATTEST_VSOCK_PORT, 4091);
222 }
223
224 #[test]
225 fn test_attest_request_serde_roundtrip() {
226 let req = AttestRequest {
227 route: AttestRoute::Status,
228 payload: serde_json::Value::Null,
229 };
230 let json = serde_json::to_string(&req).unwrap();
231 let parsed: AttestRequest = serde_json::from_str(&json).unwrap();
232 assert_eq!(parsed.route, AttestRoute::Status);
233 }
234
235 #[test]
236 fn test_attest_route_variants() {
237 let routes = [
238 (AttestRoute::Status, "\"status\""),
239 (AttestRoute::Secrets, "\"secrets\""),
240 (AttestRoute::Seal, "\"seal\""),
241 (AttestRoute::Unseal, "\"unseal\""),
242 (AttestRoute::Process, "\"process\""),
243 ];
244 for (route, expected) in routes {
245 let json = serde_json::to_string(&route).unwrap();
246 assert_eq!(json, expected);
247 }
248 }
249
250 #[test]
251 fn test_attest_request_with_payload() {
252 let req = AttestRequest {
253 route: AttestRoute::Seal,
254 payload: serde_json::json!({"data": "base64data", "context": "test"}),
255 };
256 let json = serde_json::to_string(&req).unwrap();
257 let parsed: AttestRequest = serde_json::from_str(&json).unwrap();
258 assert_eq!(parsed.route, AttestRoute::Seal);
259 assert_eq!(parsed.payload["context"], "test");
260 }
261
262 #[test]
263 fn test_attest_request_default_payload() {
264 let json = r#"{"route":"status"}"#;
265 let req: AttestRequest = serde_json::from_str(json).unwrap();
266 assert_eq!(req.route, AttestRoute::Status);
267 assert!(req.payload.is_null());
268 }
269}