1use std::collections::HashMap;
14use std::rc::Rc;
15
16use serde_json::Value;
17
18use crate::error::RpcError;
19use crate::methods;
20use crate::server::SharedConfium;
21
22pub type Handler = Rc<
30 dyn Fn(
31 SharedConfium,
32 Value,
33 ) -> std::pin::Pin<
34 Box<dyn std::future::Future<Output = std::result::Result<Value, RpcError>>>,
35 >,
36>;
37
38pub struct Dispatch {
40 table: HashMap<String, Handler>,
41}
42
43impl Dispatch {
44 pub fn new() -> Self {
46 let mut table = HashMap::new();
47
48 table.insert("version".to_string(), rc(methods::meta::version));
50 table.insert("shutdown".to_string(), rc(methods::meta::shutdown));
51
52 table.insert("plugin_load".to_string(), rc(methods::plugin::plugin_load));
54 table.insert(
55 "plugin_unload".to_string(),
56 rc(methods::plugin::plugin_unload),
57 );
58 table.insert("plugin_list".to_string(), rc(methods::plugin::plugin_list));
59
60 table.insert("hash_create".to_string(), rc(methods::hash::hash_create));
62 table.insert("hash_update".to_string(), rc(methods::hash::hash_update));
63 table.insert(
64 "hash_finalize".to_string(),
65 rc(methods::hash::hash_finalize),
66 );
67
68 table.insert(
70 "cipher_create".to_string(),
71 rc(methods::cipher::cipher_create),
72 );
73 table.insert(
74 "cipher_update".to_string(),
75 rc(methods::cipher::cipher_update),
76 );
77 table.insert(
78 "cipher_finalize".to_string(),
79 rc(methods::cipher::cipher_finalize),
80 );
81
82 table.insert("aead_create".to_string(), rc(methods::aead::aead_create));
84 table.insert(
85 "aead_encrypt_update".to_string(),
86 rc(methods::aead::aead_encrypt_update),
87 );
88 table.insert(
89 "aead_decrypt_update".to_string(),
90 rc(methods::aead::aead_decrypt_update),
91 );
92 table.insert(
93 "aead_finalize".to_string(),
94 rc(methods::aead::aead_finalize),
95 );
96
97 table.insert("kdf_create".to_string(), rc(methods::kdf::kdf_create));
99 table.insert("kdf_derive".to_string(), rc(methods::kdf::kdf_derive));
100
101 table.insert("rng_create".to_string(), rc(methods::rng::rng_create));
103 table.insert("rng_generate".to_string(), rc(methods::rng::rng_generate));
104
105 table.insert(
107 "signature_keypair_generate".to_string(),
108 rc(methods::signature::signature_keypair_generate),
109 );
110 table.insert(
111 "signature_signer_update".to_string(),
112 rc(methods::signature::signature_signer_update),
113 );
114 table.insert(
115 "signature_signer_finalize".to_string(),
116 rc(methods::signature::signature_signer_finalize),
117 );
118 table.insert(
119 "signature_verifier_update".to_string(),
120 rc(methods::signature::signature_verifier_update),
121 );
122 table.insert(
123 "signature_verifier_finalize".to_string(),
124 rc(methods::signature::signature_verifier_finalize),
125 );
126
127 table.insert(
129 "kem_keypair_generate".to_string(),
130 rc(methods::kem::kem_keypair_generate),
131 );
132 table.insert(
133 "kem_encapsulate".to_string(),
134 rc(methods::kem::kem_encapsulate),
135 );
136 table.insert(
137 "kem_decapsulate".to_string(),
138 rc(methods::kem::kem_decapsulate),
139 );
140
141 table.insert(
143 "keyfmt_parse".to_string(),
144 rc(methods::keyfmt::keyfmt_parse),
145 );
146 table.insert(
147 "keyfmt_serialize".to_string(),
148 rc(methods::keyfmt::keyfmt_serialize),
149 );
150
151 table.insert(
153 "keystore_create".to_string(),
154 rc(methods::keystore::keystore_create),
155 );
156 table.insert(
157 "keystore_put_secret".to_string(),
158 rc(methods::keystore::keystore_put_secret),
159 );
160 table.insert(
161 "keystore_get_secret".to_string(),
162 rc(methods::keystore::keystore_get_secret),
163 );
164
165 table.insert(
167 "tc_session_create".to_string(),
168 rc(methods::tc::tc_session_create),
169 );
170 table.insert(
171 "tc_session_round".to_string(),
172 rc(methods::tc::tc_session_round),
173 );
174 table.insert(
175 "tc_session_result".to_string(),
176 rc(methods::tc::tc_session_result),
177 );
178
179 table.insert(
181 "registry_install".to_string(),
182 rc(methods::registry::registry_install),
183 );
184 table.insert(
185 "registry_search".to_string(),
186 rc(methods::registry::registry_search),
187 );
188
189 table.insert(
191 "audit_subscribe".to_string(),
192 rc(methods::audit::audit_subscribe),
193 );
194
195 table.insert(
197 "composite_verify".to_string(),
198 rc(methods::composite::composite_verify),
199 );
200
201 table.insert(
203 "attributes_evaluate".to_string(),
204 rc(methods::attributes::attributes_evaluate),
205 );
206
207 Dispatch { table }
208 }
209
210 pub fn get(&self, method: &str) -> Option<&Handler> {
213 self.table.get(method)
214 }
215
216 pub fn methods(&self) -> impl Iterator<Item = &str> {
219 self.table.keys().map(String::as_str)
220 }
221}
222
223impl Default for Dispatch {
224 fn default() -> Self {
225 Self::new()
226 }
227}
228
229fn rc<F, Fut>(f: F) -> Handler
232where
233 F: Fn(SharedConfium, Value) -> Fut + 'static,
234 Fut: std::future::Future<Output = std::result::Result<Value, RpcError>> + 'static,
235{
236 Rc::new(move |cfm, params| Box::pin(f(cfm, params)))
237}
238
239pub fn parse_params<T: serde::de::DeserializeOwned>(
242 params: &Value,
243) -> std::result::Result<T, RpcError> {
244 serde_json::from_value(params.clone()).map_err(|e| RpcError::InvalidParams {
245 detail: e.to_string(),
246 })
247}
248
249pub fn params_object(
252 params: &Value,
253) -> std::result::Result<serde_json::Map<String, Value>, RpcError> {
254 match params {
255 Value::Object(map) => Ok(map.clone()),
256 Value::Null => Ok(serde_json::Map::new()),
257 _ => Err(RpcError::InvalidParams {
258 detail: "expected an object".to_string(),
259 }),
260 }
261}
262
263pub fn require_str(
265 map: &serde_json::Map<String, Value>,
266 key: &str,
267) -> std::result::Result<String, RpcError> {
268 map.get(key)
269 .and_then(|v| v.as_str())
270 .map(|s| s.to_string())
271 .ok_or_else(|| RpcError::InvalidParams {
272 detail: format!("missing or non-string field '{key}'"),
273 })
274}
275
276pub fn require_bytes(
279 map: &serde_json::Map<String, Value>,
280 key: &str,
281) -> std::result::Result<Vec<u8>, RpcError> {
282 let v = map.get(key).ok_or_else(|| RpcError::InvalidParams {
283 detail: format!("missing field '{key}'"),
284 })?;
285 match v {
286 Value::String(s) => decode_base64(s).map_err(|e| RpcError::InvalidParams {
287 detail: format!("field '{key}' is not valid base64: {e}"),
288 }),
289 Value::Array(arr) => {
290 let mut out = Vec::with_capacity(arr.len());
291 for (i, n) in arr.iter().enumerate() {
292 let b = n.as_u64().ok_or_else(|| RpcError::InvalidParams {
293 detail: format!("field '{key}[{i}]' is not a number"),
294 })?;
295 if b > 255 {
296 return Err(RpcError::InvalidParams {
297 detail: format!("field '{key}[{i}]' = {b} exceeds 255"),
298 });
299 }
300 out.push(b as u8);
301 }
302 Ok(out)
303 }
304 _ => Err(RpcError::InvalidParams {
305 detail: format!("field '{key}' must be a base64 string or byte array"),
306 }),
307 }
308}
309
310fn decode_base64(s: &str) -> std::result::Result<Vec<u8>, String> {
313 let s = s.trim_end_matches('=');
314 let mut out = Vec::with_capacity(s.len() * 3 / 4);
315 let mut buf: u32 = 0;
316 let mut bits: u32 = 0;
317 for (i, c) in s.chars().enumerate() {
318 let val = match c {
319 'A'..='Z' => c as u32 - 'A' as u32,
320 'a'..='z' => c as u32 - 'a' as u32 + 26,
321 '0'..='9' => c as u32 - '0' as u32 + 52,
322 '+' | '-' => 62,
323 '/' | '_' => 63,
324 _ => return Err(format!("invalid base64 char at index {i}")),
325 };
326 buf = (buf << 6) | val;
327 bits += 6;
328 if bits >= 8 {
329 bits -= 8;
330 out.push((buf >> bits) as u8);
331 buf &= (1 << bits) - 1;
332 }
333 }
334 Ok(out)
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn base64_decode_roundtrip() {
343 assert_eq!(decode_base64("aGVsbG8=").unwrap(), b"hello");
344 assert_eq!(decode_base64("Zm9vYmFy").unwrap(), b"foobar");
345 assert_eq!(decode_base64("-_8=").unwrap(), vec![0xfb, 0xff]);
347 }
348
349 #[test]
350 fn dispatch_registers_all_methods() {
351 let d = Dispatch::new();
352 let names: Vec<&str> = d.methods().collect();
353 assert!(names.contains(&"version"));
354 assert!(names.contains(&"shutdown"));
355 assert!(names.contains(&"plugin_load"));
356 assert!(names.contains(&"hash_create"));
357 assert!(names.contains(&"audit_subscribe"));
358 }
359}