mod constants;
mod errors;
mod ffi;
mod strings;
use std::{
cell::{Ref, RefCell},
cmp::PartialEq,
error::Error,
ffi::{CStr, CString, FromBytesWithNulError},
fmt, slice,
};
use libc::{c_char, c_double, c_int, c_uchar, c_uint, size_t};
pub use multi_map::{multimap, MultiMap};
pub use {
constants::*,
errors::error_codes,
errors::{PluginUninitializedError, ERRORS},
ffi::*,
strings::copy_string,
};
pub trait PluginAPI<E: Error + PluginError + 'static>
where
Self: Sized,
{
fn new() -> Result<Self, E>;
fn init(&mut self) -> Result<(), E>;
fn attributes(&self) -> &Attributes<Self, E>;
fn attribute_count(&self) -> usize {
self.attributes().borrow().iter().count()
}
fn attribute_ids(&self) -> Vec<usize> {
self.attributes()
.borrow()
.iter()
.map(|(id, _)| *id)
.collect()
}
fn attribute_name(&self, id: usize) -> Result<Ref<CString>, E> {
log::debug!("Received request for the name of attribute: {}", id);
let attributes = self.attributes().borrow();
match attributes.get(&id) {
Some(_) => Ok(Ref::map(attributes, |a| {
&a.get(&id)
.expect("Attribute does not exist. This should never happen.")
.name
})),
None => Err(E::new(error_codes::ATTRIBUTE_DOES_NOT_EXIST)),
}
}
fn attribute_pre_init(&self, id: usize) -> Result<bool, E> {
log::debug!(
"Received request for attribute pre-initialzation status: {}",
id
);
let attributes = self.attributes();
let attributes = attributes.borrow();
let attribute = attributes
.get(&id)
.ok_or_else(|| E::new(error_codes::ATTRIBUTE_DOES_NOT_EXIST))?;
match attribute.callbacks_init {
Callbacks::Update => Ok(true),
_ => Ok(false),
}
}
fn attribute_value(&self, id: usize, phase: Phase) -> Result<Val, E> {
log::debug!("Received request for the value of attribute: {}", id);
let attributes = self.attributes();
let mut attributes = attributes.borrow_mut();
let attribute = attributes
.get_mut(&id)
.ok_or_else(|| E::new(error_codes::ATTRIBUTE_DOES_NOT_EXIST))?;
let get = if phase == constants::INIT_PHASE {
match attribute.callbacks_init {
Callbacks::Constant => return Ok(attribute.value.as_val()),
Callbacks::Update => return Ok(attribute.value.as_val()),
Callbacks::Get(get) => get,
Callbacks::GetAndSet(get, _) => get,
}
} else if phase == constants::RUN_PHASE {
match attribute.callbacks_run {
Callbacks::Constant => return Ok(attribute.value.as_val()),
Callbacks::Update => return Ok(attribute.value.as_val()),
Callbacks::Get(get) => get,
Callbacks::GetAndSet(get, _) => get,
}
} else {
return Err(E::new(error_codes::LIFECYCLE_PHASE_ERR));
};
let value = get(&self, &attribute.value).map_err(|err| {
log::error!("Callback error {{ id: {:?}, error: {:?} }}", id, err);
E::new(error_codes::CALLBACK_ERR)
})?;
attribute.value = value;
Ok(attribute.value.as_val())
}
fn attribute_set_value(&self, id: usize, val: &Val, phase: Phase) -> Result<(), E> {
log::debug!("Received request to set the value of attribute: {}", id);
let attributes = self.attributes();
let mut attributes = attributes.borrow_mut();
let attribute = attributes
.get_mut(&id)
.ok_or_else(|| E::new(error_codes::ATTRIBUTE_DOES_NOT_EXIST))?;
let option_set = if phase == constants::INIT_PHASE {
match attribute.callbacks_init {
Callbacks::Update => None,
Callbacks::GetAndSet(_, set) => Some(set),
_ => return Err(E::new(error_codes::ATTRIBUTE_IS_NOT_SETTABLE)),
}
} else if phase == constants::RUN_PHASE {
match attribute.callbacks_run {
Callbacks::Update => None,
Callbacks::GetAndSet(_, set) => Some(set),
_ => return Err(E::new(error_codes::ATTRIBUTE_IS_NOT_SETTABLE)),
}
} else {
return Err(E::new(error_codes::LIFECYCLE_PHASE_ERR));
};
if let Some(set) = option_set {
let result = match (&attribute.value, &val) {
(Value::Int(_), Val::Int(_))
| (Value::Double(_), Val::Double(_))
| (Value::String(_), Val::String(_, _))
| (Value::Uint(_), Val::Uint(_)) => set(&self, &attribute.value, val),
_ => Err(E::new(error_codes::ATTRIBUTE_TYPE_MISMATCH)),
};
result.map_err(|err| {
log::error!("Callback error {{ id: {:?}, error: {:?} }}", id, err);
E::new(error_codes::CALLBACK_ERR)
})?;
};
attribute.value = val.to_value().map_err(|err| {
log::error!(
"Could not update plugin attribute's cached value: {{ id: {:?}, error: {:?} }}",
id,
err
);
E::new(error_codes::UPDATE_CACHED_VALUE_ERR)
})?;
Ok(())
}
}
pub trait PluginError: std::error::Error {
fn new(error_code: c_int) -> Self;
fn error_code(&self) -> c_int;
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct Plugin {
pub plugin_data: *mut PluginData,
pub vtable: VTable,
}
impl Drop for Plugin {
fn drop(&mut self) {
(self.vtable.plugin_free)(self.plugin_data);
}
}
unsafe impl Send for Plugin {}
#[derive(Debug)]
#[repr(C)]
pub struct PluginData {
_private: [u8; 0],
}
#[derive(Clone, Debug)]
#[repr(C)]
pub struct VTable {
pub plugin_free: extern "C" fn(*mut PluginData),
pub plugin_init: unsafe extern "C" fn(*mut PluginData) -> c_int,
pub error_message_ns: extern "C" fn(c_int) -> *const c_uchar,
pub attribute_count:
unsafe extern "C" fn(plugin_data: *const PluginData, count: *mut size_t) -> c_int,
pub attribute_ids:
unsafe extern "C" fn(plugin_data: *const PluginData, ids: *mut size_t, size_t) -> c_int,
pub attribute_name: unsafe extern "C" fn(
plugin_data: *const PluginData,
id: size_t,
buffer: *mut c_uchar,
length: size_t,
) -> c_int,
pub attribute_pre_init: unsafe extern "C" fn(
plugin_data: *const PluginData,
id: size_t,
pre_init: *mut c_char,
) -> c_int,
pub attribute_value: unsafe extern "C" fn(
plugin_data: *const PluginData,
id: size_t,
value: *mut Val,
phase: Phase,
) -> c_int,
pub set_attribute_value: unsafe extern "C" fn(
plugin_data: *mut PluginData,
id: size_t,
value: *const Val,
phase: Phase,
) -> c_int,
}
pub type KpalPluginInit = unsafe extern "C" fn(*mut Plugin) -> c_int;
pub type KpalLibraryInit = unsafe extern "C" fn() -> c_int;
pub type Attributes<T, E> = RefCell<MultiMap<usize, &'static str, Attribute<T, E>>>;
#[derive(Debug)]
#[repr(C)]
pub struct Attribute<T, E: Error + PluginError> {
pub name: CString,
pub value: Value,
pub callbacks_init: Callbacks<T, E>,
pub callbacks_run: Callbacks<T, E>,
}
#[derive(Clone, Debug, PartialEq)]
#[repr(C)]
pub enum Value {
Int(c_int),
Double(c_double),
String(CString),
Uint(c_uint),
}
impl Value {
pub fn as_val(&self) -> Val {
match self {
Value::Int(value) => Val::Int(*value),
Value::Double(value) => Val::Double(*value),
Value::String(value) => {
let slice = value.as_bytes_with_nul();
Val::String(slice.as_ptr(), slice.len())
}
Value::Uint(value) => Val::Uint(*value),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[repr(C)]
pub enum Val {
Int(c_int),
Double(c_double),
String(*const c_uchar, size_t),
Uint(c_uint),
}
impl Val {
pub fn to_value(&self) -> Result<Value, ValueConversionError> {
match self {
Val::Int(value) => Ok(Value::Int(*value)),
Val::Double(value) => Ok(Value::Double(*value)),
Val::String(p_value, length) => {
let slice = unsafe { slice::from_raw_parts(*p_value, *length) };
let c_string = CStr::from_bytes_with_nul(slice)?.to_owned();
Ok(Value::String(c_string))
}
Val::Uint(value) => Ok(Value::Uint(*value)),
}
}
}
#[repr(C)]
pub enum Callbacks<T, E: Error + PluginError> {
Constant,
Get(fn(plugin: &T, cached: &Value) -> Result<Value, E>),
GetAndSet(
fn(plugin: &T, cached: &Value) -> Result<Value, E>,
fn(plugin: &T, cached: &Value, value: &Val) -> Result<(), E>,
),
Update,
}
impl<T, E: Error + PluginError> fmt::Debug for Callbacks<T, E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Callbacks::*;
match *self {
Constant => write!(f, "Constant"),
Get(get) => write!(f, "Get Callback: {:x}", get as usize),
GetAndSet(get, set) => write!(
f,
"Get Callback: {:x}, Set Callback: {:x}",
get as usize, set as usize
),
Update => write!(f, "Update"),
}
}
}
#[macro_export]
macro_rules! declare_plugin {
($plugin_type:ty, $plugin_err_type:ty) => {
#[no_mangle]
pub extern "C" fn kpal_library_init() -> c_int {
env_logger::init();
PLUGIN_OK
}
#[no_mangle]
pub unsafe extern "C" fn kpal_plugin_new(plugin: *mut Plugin) -> c_int {
let plugin_data = match <$plugin_type>::new() {
Ok(plugin_data) => plugin_data,
Err(e) => {
log::error!("Failed to initialize the plugin: {:?}", e);
return PLUGIN_INIT_ERR;
}
};
let plugin_data: Box<$plugin_type> = Box::new(plugin_data);
let plugin_data = Box::into_raw(plugin_data) as *mut PluginData;
let vtable = VTable {
plugin_free,
plugin_init: plugin_init::<$plugin_type, $plugin_err_type>,
error_message_ns,
attribute_count: attribute_count::<$plugin_type, $plugin_err_type>,
attribute_ids: attribute_ids::<$plugin_type, $plugin_err_type>,
attribute_name: attribute_name::<$plugin_type, $plugin_err_type>,
attribute_pre_init: attribute_pre_init::<$plugin_type, $plugin_err_type>,
attribute_value: attribute_value::<$plugin_type, $plugin_err_type>,
set_attribute_value: set_attribute_value::<$plugin_type, $plugin_err_type>,
};
plugin.write(Plugin {
plugin_data,
vtable,
});
log::debug!("Created new plugin: {:?}", plugin);
PLUGIN_OK
}
};
}
#[derive(Debug)]
pub struct ValueConversionError {
side: FromBytesWithNulError,
}
impl Error for ValueConversionError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.side)
}
}
impl fmt::Display for ValueConversionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PluginError: {:?}", self)
}
}
impl From<FromBytesWithNulError> for ValueConversionError {
fn from(error: FromBytesWithNulError) -> Self {
ValueConversionError { side: error }
}
}