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 if desc.abi_version != ABI_VERSION {
133 return Err(LoadError::IncompatibleAbiVersion {
134 got: desc.abi_version,
135 expected: ABI_VERSION,
136 });
137 }
138
139 let interface_name = unsafe { desc.interface_name_str() }.to_string();
141 let plugin_name = unsafe { desc.plugin_name_str() }.to_string();
142
143 let info = PluginInfo {
144 name: plugin_name,
145 interface_name,
146 interface_hash: desc.interface_hash,
147 interface_version: desc.interface_version,
148 capabilities: desc.capabilities,
149 buffer_strategy: desc
150 .buffer_strategy_kind()
151 .map_err(|v| LoadError::UnknownBufferStrategy { value: v })?,
152 runtime: crate::types::PluginRuntimeKind::Cdylib,
153 };
154
155 Ok(LoadedPlugin {
156 info,
157 vtable: desc.vtable,
158 free_buffer: desc.free_buffer,
159 method_count: desc.method_count,
160 descriptor: desc as *const PluginDescriptor,
161 library: Arc::clone(library),
162 })
163}
164
165pub fn validate_against_interface(
167 plugin: &LoadedPlugin,
168 expected_hash: Option<u64>,
169 expected_strategy: Option<BufferStrategyKind>,
170) -> Result<(), LoadError> {
171 if let Some(hash) = expected_hash {
172 if plugin.info.interface_hash != hash {
173 return Err(LoadError::InterfaceHashMismatch {
174 got: plugin.info.interface_hash,
175 expected: hash,
176 });
177 }
178 }
179
180 if let Some(strategy) = expected_strategy {
181 if plugin.info.buffer_strategy != strategy {
182 return Err(LoadError::BufferStrategyMismatch {
183 got: plugin.info.buffer_strategy,
184 expected: strategy,
185 });
186 }
187 }
188
189 Ok(())
190}