1use std::collections::HashMap;
14use std::fs;
15use std::path::Path;
16use std::sync::Arc;
17
18use serde_json::Value;
19
20use crate::canonical::canonicalize;
21use crate::crypto::{b64decode, ed25519_verify, CryptoError};
22
23#[derive(Debug, thiserror::Error)]
24pub enum PluginError {
25 #[error("plugin I/O error: {0}")]
26 Io(String),
27 #[error("plugin parse error: {0}")]
28 Parse(String),
29 #[error("plugin signature invalid: {0}")]
30 BadSignature(String),
31 #[error("unknown plugin kind: {0}")]
32 UnknownKind(String),
33 #[error("crypto error: {0}")]
34 Crypto(String),
35}
36
37impl From<CryptoError> for PluginError {
38 fn from(e: CryptoError) -> Self {
39 PluginError::Crypto(e.to_string())
40 }
41}
42
43pub type NativeHandler = Arc<dyn Fn(&Value) -> Result<Value, String> + Send + Sync + 'static>;
45
46pub struct LoadedPlugin {
47 pub plugin_id: String,
48 pub actor_id: String,
49 pub kind: String,
50 pub capabilities: Vec<String>,
51 pub handlers: HashMap<String, NativeHandler>,
54}
55
56impl std::fmt::Debug for LoadedPlugin {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.debug_struct("LoadedPlugin")
59 .field("plugin_id", &self.plugin_id)
60 .field("actor_id", &self.actor_id)
61 .field("kind", &self.kind)
62 .field("capabilities", &self.capabilities)
63 .field("handler_count", &self.handlers.len())
64 .finish()
65 }
66}
67
68pub struct PluginRegistry {
69 plugins: Vec<LoadedPlugin>,
70}
71
72impl PluginRegistry {
73 pub fn new() -> Self {
74 PluginRegistry {
75 plugins: Vec::new(),
76 }
77 }
78
79 pub fn load_native<P: AsRef<Path>>(
82 &mut self,
83 manifest_path: P,
84 handlers: HashMap<String, NativeHandler>,
85 ) -> Result<&LoadedPlugin, PluginError> {
86 let raw = fs::read_to_string(manifest_path.as_ref())
87 .map_err(|e| PluginError::Io(e.to_string()))?;
88 let manifest: Value =
89 crate::yaml::parse(&raw).map_err(|e| PluginError::Parse(e.to_string()))?;
90 let kind = manifest
91 .get("kind")
92 .and_then(|v| v.as_str())
93 .unwrap_or("")
94 .to_string();
95 if kind != "native" {
96 return Err(PluginError::UnknownKind(kind));
97 }
98 let plugin_id = manifest
99 .get("plugin_id")
100 .and_then(|v| v.as_str())
101 .unwrap_or("")
102 .to_string();
103 verify_signature_value(&manifest, &plugin_id)?;
104 let actor_id = manifest
105 .get("actor_id")
106 .and_then(|v| v.as_str())
107 .unwrap_or("")
108 .to_string();
109 let capability_names: Vec<String> = manifest
110 .get("capabilities")
111 .and_then(|v| v.as_array())
112 .map(|arr| {
113 arr.iter()
114 .filter_map(|v| v.get("name").and_then(|n| n.as_str()).map(str::to_string))
115 .collect()
116 })
117 .unwrap_or_default();
118 let plugin = LoadedPlugin {
119 plugin_id,
120 actor_id,
121 kind,
122 capabilities: capability_names,
123 handlers,
124 };
125 self.plugins.push(plugin);
126 Ok(self.plugins.last().unwrap())
127 }
128
129 pub fn list(&self) -> &[LoadedPlugin] {
130 &self.plugins
131 }
132
133 pub fn invoke(
134 &self,
135 plugin_id: &str,
136 capability: &str,
137 request: &Value,
138 ) -> Result<Value, PluginError> {
139 let plugin = self
140 .plugins
141 .iter()
142 .find(|p| p.plugin_id == plugin_id)
143 .ok_or_else(|| PluginError::Parse(format!("plugin not loaded: {}", plugin_id)))?;
144 let handler = plugin.handlers.get(capability).ok_or_else(|| {
145 PluginError::Parse(format!("no handler for capability: {}", capability))
146 })?;
147 handler(request).map_err(PluginError::Parse)
148 }
149}
150
151pub fn verify_plugin_signature<P: AsRef<Path>>(manifest_path: P) -> Result<String, PluginError> {
153 let raw =
154 fs::read_to_string(manifest_path.as_ref()).map_err(|e| PluginError::Io(e.to_string()))?;
155 let manifest: Value =
156 crate::yaml::parse(&raw).map_err(|e| PluginError::Parse(e.to_string()))?;
157 let plugin_id = manifest
158 .get("plugin_id")
159 .and_then(|v| v.as_str())
160 .unwrap_or("")
161 .to_string();
162 verify_signature_value(&manifest, &plugin_id)?;
163 Ok(plugin_id)
164}
165
166fn verify_signature_value(manifest: &Value, plugin_id: &str) -> Result<(), PluginError> {
167 let sig_b64 = manifest
168 .get("signature")
169 .and_then(|s| s.get("signature"))
170 .and_then(|v| v.as_str())
171 .ok_or_else(|| PluginError::BadSignature(plugin_id.to_string()))?
172 .to_string();
173 let ident_pub = manifest
174 .get("identity_pub")
175 .and_then(|v| v.as_str())
176 .ok_or_else(|| PluginError::BadSignature(plugin_id.to_string()))?
177 .to_string();
178
179 let mut cleared = manifest.clone();
181 if let Some(sig) = cleared.get_mut("signature").and_then(|s| s.as_object_mut()) {
182 sig.insert("signature".to_string(), Value::String(String::new()));
183 }
184 let canonical = canonicalize(&cleared).map_err(|e| PluginError::Parse(e.to_string()))?;
185 let sig_bytes = b64decode(&sig_b64)?;
186 let pubkey_bytes = b64decode(&ident_pub)?;
187 ed25519_verify(&pubkey_bytes, canonical.as_bytes(), &sig_bytes)
188 .map_err(|_| PluginError::BadSignature(plugin_id.to_string()))?;
189 Ok(())
190}
191
192impl Default for PluginRegistry {
193 fn default() -> Self {
194 PluginRegistry::new()
195 }
196}