1use std::ffi::c_void;
18use std::path::Path;
19use std::sync::Arc;
20
21use fidius_core::descriptor::*;
22use libloading::Library;
23
24use crate::error::LoadError;
25use crate::types::PluginInfo;
26
27pub struct LoadedLibrary {
29 pub library: Arc<Library>,
31 pub plugins: Vec<LoadedPlugin>,
33}
34
35pub struct LoadedPlugin {
37 pub info: PluginInfo,
39 pub vtable: *const c_void,
41 pub free_buffer: Option<unsafe extern "C" fn(*mut u8, usize)>,
43 pub method_count: u32,
45 pub descriptor: *const PluginDescriptor,
49 pub library: Arc<Library>,
51}
52
53unsafe impl Send for LoadedPlugin {}
56unsafe impl Sync for LoadedPlugin {}
57
58impl std::fmt::Debug for LoadedPlugin {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 f.debug_struct("LoadedPlugin")
61 .field("info", &self.info)
62 .field("vtable", &self.vtable)
63 .finish()
64 }
65}
66
67pub fn load_library(path: &Path) -> Result<LoadedLibrary, LoadError> {
72 let path_str = path.display().to_string();
73
74 #[cfg(feature = "tracing")]
75 tracing::debug!(path = %path_str, "loading library");
76
77 crate::arch::check_architecture(path)?;
79
80 let library = unsafe { Library::new(path) }.map_err(|e| {
82 if e.to_string().contains("No such file") || e.to_string().contains("not found") {
83 LoadError::LibraryNotFound {
84 path: path_str.clone(),
85 }
86 } else {
87 LoadError::LibLoading(e)
88 }
89 })?;
90
91 let get_registry: libloading::Symbol<unsafe extern "C" fn() -> *const PluginRegistry> =
93 unsafe { library.get(b"fidius_get_registry") }.map_err(|_| LoadError::SymbolNotFound {
94 path: path_str.clone(),
95 })?;
96
97 let registry = unsafe { &*get_registry() };
99
100 if registry.magic != FIDIUS_MAGIC {
102 return Err(LoadError::InvalidMagic);
103 }
104
105 if registry.registry_version != REGISTRY_VERSION {
107 return Err(LoadError::IncompatibleRegistryVersion {
108 got: registry.registry_version,
109 expected: REGISTRY_VERSION,
110 });
111 }
112
113 let library = Arc::new(library);
114
115 let mut plugins = Vec::with_capacity(registry.plugin_count as usize);
117 for i in 0..registry.plugin_count {
118 let desc = unsafe { &**registry.descriptors.add(i as usize) };
119 let plugin = validate_descriptor(desc, &library)?;
120 plugins.push(plugin);
121 }
122
123 Ok(LoadedLibrary { library, plugins })
124}
125
126fn validate_descriptor(
128 desc: &PluginDescriptor,
129 library: &Arc<Library>,
130) -> Result<LoadedPlugin, LoadError> {
131 check_abi_version(desc.abi_version)?;
134
135 let interface_name = unsafe { desc.interface_name_str() }.to_string();
137 let plugin_name = unsafe { desc.plugin_name_str() }.to_string();
138
139 let info = PluginInfo {
140 name: plugin_name,
141 interface_name,
142 interface_hash: desc.interface_hash,
143 interface_version: desc.interface_version,
144 capabilities: desc.capabilities,
145 buffer_strategy: desc
146 .buffer_strategy_kind()
147 .map_err(|v| LoadError::UnknownBufferStrategy { value: v })?,
148 runtime: crate::types::PluginRuntimeKind::Cdylib,
149 };
150
151 Ok(LoadedPlugin {
152 info,
153 vtable: desc.vtable,
154 free_buffer: desc.free_buffer,
155 method_count: desc.method_count,
156 descriptor: desc as *const PluginDescriptor,
157 library: Arc::clone(library),
158 })
159}
160
161pub fn validate_against_interface(
163 plugin: &LoadedPlugin,
164 expected_hash: Option<u64>,
165 expected_strategy: Option<BufferStrategyKind>,
166) -> Result<(), LoadError> {
167 if let Some(hash) = expected_hash {
168 if plugin.info.interface_hash != hash {
169 return Err(LoadError::InterfaceHashMismatch {
170 got: plugin.info.interface_hash,
171 expected: hash,
172 });
173 }
174 }
175
176 if let Some(strategy) = expected_strategy {
177 if plugin.info.buffer_strategy != strategy {
178 return Err(LoadError::BufferStrategyMismatch {
179 got: plugin.info.buffer_strategy,
180 expected: strategy,
181 });
182 }
183 }
184
185 Ok(())
186}
187
188fn check_abi_version(got: u32) -> Result<(), LoadError> {
193 if got != ABI_VERSION {
194 return Err(LoadError::IncompatibleAbiVersion {
195 got,
196 expected: ABI_VERSION,
197 });
198 }
199 Ok(())
200}
201
202#[cfg(test)]
203mod abi_tests {
204 use super::*;
205
206 #[test]
207 fn rejects_a_stale_abi_plugin() {
208 let err = check_abi_version(400).unwrap_err();
210 assert!(
211 matches!(err, LoadError::IncompatibleAbiVersion { got: 400, expected } if expected == ABI_VERSION),
212 "expected IncompatibleAbiVersion {{ got: 400, expected: {ABI_VERSION} }}, got {err:?}"
213 );
214 assert!(check_abi_version(ABI_VERSION).is_ok());
216 }
217}