use std::path::{Path, PathBuf};
use ed25519_dalek::VerifyingKey;
use fidius_core::descriptor::BufferStrategyKind;
use crate::error::LoadError;
use crate::loader::{self, LoadedPlugin};
use crate::signing;
use crate::types::{LoadPolicy, PluginInfo, PluginRuntimeKind};
#[allow(dead_code)] pub struct PluginHost {
search_paths: Vec<PathBuf>,
load_policy: LoadPolicy,
require_signature: bool,
trusted_keys: Vec<VerifyingKey>,
expected_hash: Option<u64>,
expected_strategy: Option<BufferStrategyKind>,
}
pub struct PluginHostBuilder {
search_paths: Vec<PathBuf>,
load_policy: LoadPolicy,
require_signature: bool,
trusted_keys: Vec<VerifyingKey>,
expected_hash: Option<u64>,
expected_strategy: Option<BufferStrategyKind>,
}
impl PluginHostBuilder {
fn new() -> Self {
Self {
search_paths: Vec::new(),
load_policy: LoadPolicy::Strict,
require_signature: false,
trusted_keys: Vec::new(),
expected_hash: None,
expected_strategy: None,
}
}
pub fn search_path(mut self, path: impl Into<PathBuf>) -> Self {
self.search_paths.push(path.into());
self
}
pub fn load_policy(mut self, policy: LoadPolicy) -> Self {
self.load_policy = policy;
self
}
pub fn require_signature(mut self, require: bool) -> Self {
self.require_signature = require;
self
}
pub fn trusted_keys(mut self, keys: &[VerifyingKey]) -> Self {
self.trusted_keys = keys.to_vec();
self
}
pub fn interface_hash(mut self, hash: u64) -> Self {
self.expected_hash = Some(hash);
self
}
pub fn buffer_strategy(mut self, strategy: BufferStrategyKind) -> Self {
self.expected_strategy = Some(strategy);
self
}
pub fn build(self) -> Result<PluginHost, LoadError> {
Ok(PluginHost {
search_paths: self.search_paths,
load_policy: self.load_policy,
require_signature: self.require_signature,
trusted_keys: self.trusted_keys,
expected_hash: self.expected_hash,
expected_strategy: self.expected_strategy,
})
}
}
impl PluginHost {
pub fn builder() -> PluginHostBuilder {
PluginHostBuilder::new()
}
pub fn discover(&self) -> Result<Vec<PluginInfo>, LoadError> {
#[cfg(feature = "tracing")]
tracing::info!(search_paths = ?self.search_paths, "discovering plugins");
let mut plugins = Vec::new();
for search_path in &self.search_paths {
if !search_path.is_dir() {
continue;
}
let entries = std::fs::read_dir(search_path)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if is_dylib(&path) {
self.discover_cdylib(&path, &mut plugins);
} else if path.is_dir() && path.join("package.toml").exists() {
self.discover_python_package(&path, &mut plugins);
}
}
}
Ok(plugins)
}
fn discover_cdylib(&self, path: &Path, plugins: &mut Vec<PluginInfo>) {
if self.require_signature && signing::verify_signature(path, &self.trusted_keys).is_err() {
return;
}
let Ok(loaded) = loader::load_library(path) else {
return; };
for plugin in &loaded.plugins {
if loader::validate_against_interface(
plugin,
self.expected_hash,
self.expected_strategy,
)
.is_ok()
{
plugins.push(plugin.info.clone());
}
}
}
fn discover_python_package(&self, dir: &Path, plugins: &mut Vec<PluginInfo>) {
let Ok(manifest) = fidius_core::package::load_manifest_untyped(dir) else {
return;
};
if !matches!(
manifest.package.runtime(),
fidius_core::package::PackageRuntime::Python
) {
return;
}
plugins.push(PluginInfo {
name: manifest.package.name.clone(),
interface_name: manifest.package.interface.clone(),
interface_hash: 0,
interface_version: manifest.package.interface_version,
capabilities: 0,
buffer_strategy: BufferStrategyKind::PluginAllocated,
runtime: PluginRuntimeKind::Python,
});
}
pub fn load(&self, name: &str) -> Result<LoadedPlugin, LoadError> {
#[cfg(feature = "tracing")]
tracing::info!(plugin_name = name, "loading plugin");
for search_path in &self.search_paths {
if !search_path.is_dir() {
continue;
}
let entries = std::fs::read_dir(search_path)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if !is_dylib(&path) {
continue;
}
if self.require_signature {
signing::verify_signature(&path, &self.trusted_keys)?;
}
match loader::load_library(&path) {
Ok(loaded) => {
for plugin in loaded.plugins {
if plugin.info.name == name {
loader::validate_against_interface(
&plugin,
self.expected_hash,
self.expected_strategy,
)?;
return Ok(plugin);
}
}
}
Err(_) => continue,
}
}
}
Err(LoadError::PluginNotFound {
name: name.to_string(),
})
}
pub fn find_python_package(&self, name: &str) -> Result<PathBuf, LoadError> {
for search_path in &self.search_paths {
if !search_path.is_dir() {
continue;
}
let entries = std::fs::read_dir(search_path)?;
for entry in entries {
let entry = entry?;
let path = entry.path();
if !path.is_dir() {
continue;
}
if !path.join("package.toml").exists() {
continue;
}
let Ok(manifest) = fidius_core::package::load_manifest_untyped(&path) else {
continue;
};
if matches!(
manifest.package.runtime(),
fidius_core::package::PackageRuntime::Python
) && manifest.package.name == name
{
return Ok(path);
}
}
}
Err(LoadError::PluginNotFound {
name: name.to_string(),
})
}
#[cfg(feature = "python")]
pub fn load_python(
&self,
name: &str,
descriptor: &'static fidius_core::python_descriptor::PythonInterfaceDescriptor,
) -> Result<fidius_python::PythonPluginHandle, LoadError> {
let dir = self.find_python_package(name)?;
fidius_python::load_python_plugin(&dir, descriptor)
.map_err(|e| LoadError::PythonLoad(e.to_string()))
}
}
fn is_dylib(path: &Path) -> bool {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if cfg!(target_os = "macos") {
ext == "dylib"
} else if cfg!(target_os = "windows") {
ext == "dll"
} else {
ext == "so"
}
}