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 library: Arc<Library>,
47}
48
49unsafe impl Send for LoadedPlugin {}
52unsafe impl Sync for LoadedPlugin {}
53
54impl std::fmt::Debug for LoadedPlugin {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 f.debug_struct("LoadedPlugin")
57 .field("info", &self.info)
58 .field("vtable", &self.vtable)
59 .finish()
60 }
61}
62
63pub fn load_library(path: &Path) -> Result<LoadedLibrary, LoadError> {
68 let path_str = path.display().to_string();
69
70 #[cfg(feature = "tracing")]
71 tracing::debug!(path = %path_str, "loading library");
72
73 crate::arch::check_architecture(path)?;
75
76 let library = unsafe { Library::new(path) }.map_err(|e| {
78 if e.to_string().contains("No such file") || e.to_string().contains("not found") {
79 LoadError::LibraryNotFound {
80 path: path_str.clone(),
81 }
82 } else {
83 LoadError::LibLoading(e)
84 }
85 })?;
86
87 let get_registry: libloading::Symbol<unsafe extern "C" fn() -> *const PluginRegistry> =
89 unsafe { library.get(b"fidius_get_registry") }.map_err(|_| LoadError::SymbolNotFound {
90 path: path_str.clone(),
91 })?;
92
93 let registry = unsafe { &*get_registry() };
95
96 if registry.magic != FIDIUS_MAGIC {
98 return Err(LoadError::InvalidMagic);
99 }
100
101 if registry.registry_version != REGISTRY_VERSION {
103 return Err(LoadError::IncompatibleRegistryVersion {
104 got: registry.registry_version,
105 expected: REGISTRY_VERSION,
106 });
107 }
108
109 let library = Arc::new(library);
110
111 let mut plugins = Vec::with_capacity(registry.plugin_count as usize);
113 for i in 0..registry.plugin_count {
114 let desc = unsafe { &**registry.descriptors.add(i as usize) };
115 let plugin = validate_descriptor(desc, &library)?;
116 plugins.push(plugin);
117 }
118
119 Ok(LoadedLibrary { library, plugins })
120}
121
122fn validate_descriptor(
124 desc: &PluginDescriptor,
125 library: &Arc<Library>,
126) -> Result<LoadedPlugin, LoadError> {
127 if desc.abi_version != ABI_VERSION {
129 return Err(LoadError::IncompatibleAbiVersion {
130 got: desc.abi_version,
131 expected: ABI_VERSION,
132 });
133 }
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 wire_format: desc
146 .wire_format_kind()
147 .map_err(|v| LoadError::UnknownWireFormat { value: v })?,
148 buffer_strategy: desc
149 .buffer_strategy_kind()
150 .map_err(|v| LoadError::UnknownBufferStrategy { value: v })?,
151 };
152
153 Ok(LoadedPlugin {
154 info,
155 vtable: desc.vtable,
156 free_buffer: desc.free_buffer,
157 method_count: desc.method_count,
158 library: Arc::clone(library),
159 })
160}
161
162pub fn validate_against_interface(
164 plugin: &LoadedPlugin,
165 expected_hash: Option<u64>,
166 expected_wire: Option<WireFormat>,
167 expected_strategy: Option<BufferStrategyKind>,
168) -> Result<(), LoadError> {
169 if let Some(hash) = expected_hash {
170 if plugin.info.interface_hash != hash {
171 return Err(LoadError::InterfaceHashMismatch {
172 got: plugin.info.interface_hash,
173 expected: hash,
174 });
175 }
176 }
177
178 if let Some(wire) = expected_wire {
179 if plugin.info.wire_format != wire {
180 return Err(LoadError::WireFormatMismatch {
181 got: plugin.info.wire_format,
182 expected: wire,
183 });
184 }
185 }
186
187 if let Some(strategy) = expected_strategy {
188 if plugin.info.buffer_strategy != strategy {
189 return Err(LoadError::BufferStrategyMismatch {
190 got: plugin.info.buffer_strategy,
191 expected: strategy,
192 });
193 }
194 }
195
196 Ok(())
197}