use std::path::{Path, PathBuf};
use ed25519_dalek::VerifyingKey;
use fidius_core::descriptor::{BufferStrategyKind, WireFormat};
use crate::error::LoadError;
use crate::loader::{self, LoadedPlugin};
use crate::signing;
use crate::types::{LoadPolicy, PluginInfo};
#[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_wire: Option<WireFormat>,
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_wire: Option<WireFormat>,
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_wire: 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 wire_format(mut self, format: WireFormat) -> Self {
self.expected_wire = Some(format);
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_wire: self.expected_wire,
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) {
continue;
}
if self.require_signature
&& signing::verify_signature(&path, &self.trusted_keys).is_err()
{
continue;
}
match loader::load_library(&path) {
Ok(loaded) => {
for plugin in &loaded.plugins {
if let Ok(()) = loader::validate_against_interface(
plugin,
self.expected_hash,
self.expected_wire,
self.expected_strategy,
) {
plugins.push(plugin.info.clone());
}
}
}
Err(_) => {
continue;
}
}
}
}
Ok(plugins)
}
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_wire,
self.expected_strategy,
)?;
return Ok(plugin);
}
}
}
Err(_) => continue,
}
}
}
Err(LoadError::PluginNotFound {
name: name.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"
}
}