kpal_plugin/lib.rs
1//! The KPAL plugin crate provides tools to write your own KPAL plugins.
2//!
3//! See the examples folder for ideas on how to implement the datatypes and methods defined in this
4//! library.
5mod constants;
6mod errors;
7mod ffi;
8mod strings;
9
10use std::{
11 cell::{Ref, RefCell},
12 cmp::PartialEq,
13 error::Error,
14 ffi::{CStr, CString, FromBytesWithNulError},
15 fmt, slice,
16};
17
18use libc::{c_char, c_double, c_int, c_uchar, c_uint, size_t};
19pub use multi_map::{multimap, MultiMap};
20
21pub use {
22 constants::*,
23 errors::error_codes,
24 errors::{PluginUninitializedError, ERRORS},
25 ffi::*,
26 strings::copy_string,
27};
28
29/// The set of functions that must be implemented by a plugin.
30pub trait PluginAPI<E: Error + PluginError + 'static>
31where
32 Self: Sized,
33{
34 /// Returns a new instance of the plugin. No initialization of the hardware is performed.
35 fn new() -> Result<Self, E>;
36
37 /// Initialzes the plugin by performing any hardware initialization.
38 fn init(&mut self) -> Result<(), E>;
39
40 /// Returns the attributes of the plugin.
41 fn attributes(&self) -> &Attributes<Self, E>;
42
43 /// Returns the number of attributes of the plugin.
44 fn attribute_count(&self) -> usize {
45 self.attributes().borrow().iter().count()
46 }
47
48 /// Returns the attribute IDs.
49 fn attribute_ids(&self) -> Vec<usize> {
50 self.attributes()
51 .borrow()
52 .iter()
53 .map(|(id, _)| *id)
54 .collect()
55 }
56
57 /// Returns the name of an attribute.
58 ///
59 /// If the attribute that corresponds to the `id` does not exist, then an error is
60 /// returned. Otherwise, the name is returned as a C-compatible `&CStr`.
61 ///
62 /// # Arguments
63 ///
64 /// * `id` - the numeric ID of the attribute
65 fn attribute_name(&self, id: usize) -> Result<Ref<CString>, E> {
66 log::debug!("Received request for the name of attribute: {}", id);
67 let attributes = self.attributes().borrow();
68 match attributes.get(&id) {
69 Some(_) => Ok(Ref::map(attributes, |a| {
70 &a.get(&id)
71 .expect("Attribute does not exist. This should never happen.")
72 .name
73 })),
74 None => Err(E::new(error_codes::ATTRIBUTE_DOES_NOT_EXIST)),
75 }
76 }
77
78 /// Indicates whether an attribute may be set before initialization.
79 ///
80 /// # Arguments
81 ///
82 /// # `id` - the numeric ID of the attribute
83 fn attribute_pre_init(&self, id: usize) -> Result<bool, E> {
84 log::debug!(
85 "Received request for attribute pre-initialzation status: {}",
86 id
87 );
88 let attributes = self.attributes();
89 let attributes = attributes.borrow();
90 let attribute = attributes
91 .get(&id)
92 .ok_or_else(|| E::new(error_codes::ATTRIBUTE_DOES_NOT_EXIST))?;
93
94 match attribute.callbacks_init {
95 Callbacks::Update => Ok(true),
96 _ => Ok(false),
97 }
98 }
99
100 /// Returns the value of an attribute.
101 ///
102 /// If the attribute that corresponds to the `id` does not exist, then an error is
103 /// returned. Otherwise, the value is returnd as a C-compatible tagged enum.
104 ///
105 /// # Arguments
106 ///
107 /// * `id` - the numeric ID of the attribute
108 /// * `phase` - the lifecycle phase of the plugin that determines which callbacks to use
109 fn attribute_value(&self, id: usize, phase: Phase) -> Result<Val, E> {
110 log::debug!("Received request for the value of attribute: {}", id);
111 let attributes = self.attributes();
112 let mut attributes = attributes.borrow_mut();
113 let attribute = attributes
114 .get_mut(&id)
115 .ok_or_else(|| E::new(error_codes::ATTRIBUTE_DOES_NOT_EXIST))?;
116
117 let get = if phase == constants::INIT_PHASE {
118 match attribute.callbacks_init {
119 Callbacks::Constant => return Ok(attribute.value.as_val()),
120 Callbacks::Update => return Ok(attribute.value.as_val()),
121 Callbacks::Get(get) => get,
122 Callbacks::GetAndSet(get, _) => get,
123 }
124 } else if phase == constants::RUN_PHASE {
125 match attribute.callbacks_run {
126 Callbacks::Constant => return Ok(attribute.value.as_val()),
127 Callbacks::Update => return Ok(attribute.value.as_val()),
128 Callbacks::Get(get) => get,
129 Callbacks::GetAndSet(get, _) => get,
130 }
131 } else {
132 return Err(E::new(error_codes::LIFECYCLE_PHASE_ERR));
133 };
134
135 let value = get(&self, &attribute.value).map_err(|err| {
136 log::error!("Callback error {{ id: {:?}, error: {:?} }}", id, err);
137 E::new(error_codes::CALLBACK_ERR)
138 })?;
139
140 // Update the attribute's cached value.
141 attribute.value = value;
142
143 Ok(attribute.value.as_val())
144 }
145
146 /// Sets the value of the attribute given by the id.
147 ///
148 /// If the attribute that corresponds to the `id` does not exist, or if the attribute cannot be
149 /// set, then an error is returned.
150 ///
151 /// # Arguments
152 ///
153 /// * `id` - the numeric ID of the attribute
154 /// * `val` - a reference to a Val instance
155 /// * `phase` - the lifecycle phase of the plugin that determines which callbacks to use
156 fn attribute_set_value(&self, id: usize, val: &Val, phase: Phase) -> Result<(), E> {
157 log::debug!("Received request to set the value of attribute: {}", id);
158 let attributes = self.attributes();
159 let mut attributes = attributes.borrow_mut();
160 let attribute = attributes
161 .get_mut(&id)
162 .ok_or_else(|| E::new(error_codes::ATTRIBUTE_DOES_NOT_EXIST))?;
163
164 let option_set = if phase == constants::INIT_PHASE {
165 match attribute.callbacks_init {
166 Callbacks::Update => None,
167 Callbacks::GetAndSet(_, set) => Some(set),
168 _ => return Err(E::new(error_codes::ATTRIBUTE_IS_NOT_SETTABLE)),
169 }
170 } else if phase == constants::RUN_PHASE {
171 match attribute.callbacks_run {
172 Callbacks::Update => None,
173 Callbacks::GetAndSet(_, set) => Some(set),
174 _ => return Err(E::new(error_codes::ATTRIBUTE_IS_NOT_SETTABLE)),
175 }
176 } else {
177 return Err(E::new(error_codes::LIFECYCLE_PHASE_ERR));
178 };
179
180 if let Some(set) = option_set {
181 // This MUST be updated each time a new variant is added to the Value and Val enums.
182 let result = match (&attribute.value, &val) {
183 (Value::Int(_), Val::Int(_))
184 | (Value::Double(_), Val::Double(_))
185 | (Value::String(_), Val::String(_, _))
186 | (Value::Uint(_), Val::Uint(_)) => set(&self, &attribute.value, val),
187 _ => Err(E::new(error_codes::ATTRIBUTE_TYPE_MISMATCH)),
188 };
189
190 result.map_err(|err| {
191 log::error!("Callback error {{ id: {:?}, error: {:?} }}", id, err);
192 E::new(error_codes::CALLBACK_ERR)
193 })?;
194 };
195
196 // Update the attribute's cached value.
197 attribute.value = val.to_value().map_err(|err| {
198 log::error!(
199 "Could not update plugin attribute's cached value: {{ id: {:?}, error: {:?} }}",
200 id,
201 err
202 );
203 E::new(error_codes::UPDATE_CACHED_VALUE_ERR)
204 })?;
205
206 Ok(())
207 }
208}
209
210/// The set of functions that must be implemented by a plugin library's main error type.
211pub trait PluginError: std::error::Error {
212 /// Initializes and returns a new instace of the error type.
213 ///
214 /// # Arguments
215 ///
216 /// * `error_code` - One of the integer error codes recognized by KPAL.
217 fn new(error_code: c_int) -> Self;
218
219 /// Returns the error code of the instance.
220 fn error_code(&self) -> c_int;
221}
222
223/// A Plugin combines the data that determines its state and with its functionality.
224///
225/// This struct holds a raw pointer to a data struct that is created by the plugin library. In
226/// addition, it contains the vtable of function pointers defined by the C API and implemented
227/// within the plugin library.
228///
229/// # Safety
230///
231/// The plugin implements the `Send` trait because after creation the plugin is moved into the
232/// thread that is dedicated to the plugin that it manages. Once it is moved, it will only ever be
233/// owned and used by this single thread by design.
234#[derive(Clone, Debug)]
235#[repr(C)]
236pub struct Plugin {
237 /// A pointer to a struct containing the state of the plugin.
238 pub plugin_data: *mut PluginData,
239
240 /// The table of function pointers that define part of the plugin API.
241 pub vtable: VTable,
242}
243
244impl Drop for Plugin {
245 /// Frees the memory allocated to the plugin data.
246 fn drop(&mut self) {
247 (self.vtable.plugin_free)(self.plugin_data);
248 }
249}
250
251unsafe impl Send for Plugin {}
252
253/// An opaque struct that contains the state of an individual plugin.
254///
255/// The daemon does not actually work directly with structs provided by a plugin library. Instead,
256/// they are hidden behind pointers to opaque structs of this type. The kpal-plugin FFI code takes
257/// care of casting the pointers back into the appropriate type inside the library code.
258///
259/// # Notes
260///
261/// In Rust, an opaque struct is defined as a struct with a field that is a zero-length array of
262/// unsigned 8-bit integers. It is used to hide the plugin's state, forcing all interactions
263/// with the data through the functions in the vtable instead.
264#[derive(Debug)]
265#[repr(C)]
266pub struct PluginData {
267 _private: [u8; 0],
268}
269
270/// A table of function pointers that comprise the plugin API for the foreign function interface.
271///
272/// By default, functions in the VTable return a number that represents a status code that maps
273/// onto a particular reason for an error. All functions should use the same mapping between status
274/// code and reason.
275///
276/// Functions that return values that do not represent status codes have names that end in the
277/// characters '_ns' that stand for "no status."
278#[derive(Clone, Debug)]
279#[repr(C)]
280pub struct VTable {
281 /// Frees the memory associated with a plugin's data.
282 pub plugin_free: extern "C" fn(*mut PluginData),
283
284 /// Initializes a plugin.
285 ///
286 /// This method is distinct from the `kpal_plugin_new` FFI call in that it actually
287 /// communicates with the hardware, whereas `kpal_plugin_new` is used merely to create the
288 /// plugin data structures.
289 pub plugin_init: unsafe extern "C" fn(*mut PluginData) -> c_int,
290
291 /// Returns an error message associated with a Plugin error code.
292 pub error_message_ns: extern "C" fn(c_int) -> *const c_uchar,
293
294 /// Returns the number of attributes of the plugin.
295 pub attribute_count:
296 unsafe extern "C" fn(plugin_data: *const PluginData, count: *mut size_t) -> c_int,
297
298 /// Returns the attribute IDs in a buffer provided by the caller.
299 pub attribute_ids:
300 unsafe extern "C" fn(plugin_data: *const PluginData, ids: *mut size_t, size_t) -> c_int,
301
302 /// Writes the name of an attribute to a buffer that is provided by the caller.
303 pub attribute_name: unsafe extern "C" fn(
304 plugin_data: *const PluginData,
305 id: size_t,
306 buffer: *mut c_uchar,
307 length: size_t,
308 ) -> c_int,
309
310 /// Indicates whether an attribute may be set before initialization.
311 pub attribute_pre_init: unsafe extern "C" fn(
312 plugin_data: *const PluginData,
313 id: size_t,
314 pre_init: *mut c_char,
315 ) -> c_int,
316
317 /// Writes the value of an attribute to a Value instance that is provided by the caller.
318 pub attribute_value: unsafe extern "C" fn(
319 plugin_data: *const PluginData,
320 id: size_t,
321 value: *mut Val,
322 phase: Phase,
323 ) -> c_int,
324
325 /// Sets the value of an attribute.
326 pub set_attribute_value: unsafe extern "C" fn(
327 plugin_data: *mut PluginData,
328 id: size_t,
329 value: *const Val,
330 phase: Phase,
331 ) -> c_int,
332}
333
334/// The type signature of the function that returns a new plugin instance.
335pub type KpalPluginInit = unsafe extern "C" fn(*mut Plugin) -> c_int;
336
337/// The type signature of the function that initializes a library.
338pub type KpalLibraryInit = unsafe extern "C" fn() -> c_int;
339
340/// The type signature of the collection of attributes that is owned by the plugin.
341pub type Attributes<T, E> = RefCell<MultiMap<usize, &'static str, Attribute<T, E>>>;
342
343/// A single piece of information that partly determines the state of a plugin.
344#[derive(Debug)]
345#[repr(C)]
346pub struct Attribute<T, E: Error + PluginError> {
347 /// The name of the attribute.
348 pub name: CString,
349
350 /// The value of the attribute.
351 ///
352 /// This field may be used to cache values retrieved from the hardware. This is the initial
353 /// value of non-constant attributes.
354 pub value: Value,
355
356 /// The callback functions that are fired when the attribute is either read or set during the
357 /// init phase of the plugin.
358 pub callbacks_init: Callbacks<T, E>,
359
360 /// The callback functions that are fired when the attribute is either read or set during the
361 /// run phase of the plugin.
362 pub callbacks_run: Callbacks<T, E>,
363}
364
365/// An owned value of an attribute.
366///
367/// Unlike the `Val` enum, these are intended to be owned by an instance of a PluginData struct and
368/// do not pass through the FFI.
369#[derive(Clone, Debug, PartialEq)]
370#[repr(C)]
371pub enum Value {
372 Int(c_int),
373 Double(c_double),
374 String(CString),
375 Uint(c_uint),
376}
377
378impl Value {
379 /// Returns a reference type to a Value.
380 ///
381 /// as_val creates a new Val instance from a Value. Value variants that contain datatypes that
382 /// implement Copy are copied into the new Val instance. For complex datatypes that are not
383 /// Copy, pointers to the data are embedded inside the Val instance instead.
384 ///
385 /// This method is used to generate datatypes that represent attribute values and that may pass
386 /// through the FFI.
387 pub fn as_val(&self) -> Val {
388 match self {
389 Value::Int(value) => Val::Int(*value),
390 Value::Double(value) => Val::Double(*value),
391 Value::String(value) => {
392 let slice = value.as_bytes_with_nul();
393 Val::String(slice.as_ptr(), slice.len())
394 }
395 Value::Uint(value) => Val::Uint(*value),
396 }
397 }
398}
399
400/// A wrapper type for transporting Values through the plugin API.
401///
402/// Unlike the `Value` enum, this type is intended to be sent through the FFI. Because of this, the
403/// enum variants can only contain C-compatible datatypes.
404#[derive(Clone, Debug, PartialEq)]
405#[repr(C)]
406pub enum Val {
407 Int(c_int),
408 Double(c_double),
409 String(*const c_uchar, size_t),
410 Uint(c_uint),
411}
412
413impl Val {
414 /// Clones the data inside a Val into a new Value type.
415 ///
416 /// This method is used to convert Vals, which pass through the FFI, into owned Value
417 /// datatypes. Wrapped data that is not Copy is necessarily cloned when the new Value instance
418 /// is created.
419 pub fn to_value(&self) -> Result<Value, ValueConversionError> {
420 match self {
421 Val::Int(value) => Ok(Value::Int(*value)),
422 Val::Double(value) => Ok(Value::Double(*value)),
423 Val::String(p_value, length) => {
424 let slice = unsafe { slice::from_raw_parts(*p_value, *length) };
425 let c_string = CStr::from_bytes_with_nul(slice)?.to_owned();
426 Ok(Value::String(c_string))
427 }
428 Val::Uint(value) => Ok(Value::Uint(*value)),
429 }
430 }
431}
432
433/// Callback functions that communicate with the hardware when an attribute is read or set.
434///
435/// The purpose of a callback is two-fold: it performs the actual communication with the hardware
436/// and/or it modifies the plugin's cached attribute data.
437///
438/// If the attribute is constant and never changes its original value, then the `Constant` variant
439/// should be used. If the attribute's value changes without user input (e.g. a sensor reading) but
440/// cannot be set, then use the `Get` variant. Otherwise, for attributes that can be both read and
441/// set, use the `GetAndSet` variant.
442///
443/// The Update variant is used to set only the cached value of attributes. Attributes that are
444/// Update always return their cached value when the attribute's value is read.
445#[repr(C)]
446pub enum Callbacks<T, E: Error + PluginError> {
447 Constant,
448 Get(fn(plugin: &T, cached: &Value) -> Result<Value, E>),
449 GetAndSet(
450 fn(plugin: &T, cached: &Value) -> Result<Value, E>,
451 fn(plugin: &T, cached: &Value, value: &Val) -> Result<(), E>,
452 ),
453 Update,
454}
455
456impl<T, E: Error + PluginError> fmt::Debug for Callbacks<T, E> {
457 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458 use Callbacks::*;
459 match *self {
460 Constant => write!(f, "Constant"),
461 Get(get) => write!(f, "Get Callback: {:x}", get as usize),
462 GetAndSet(get, set) => write!(
463 f,
464 "Get Callback: {:x}, Set Callback: {:x}",
465 get as usize, set as usize
466 ),
467 Update => write!(f, "Update"),
468 }
469 }
470}
471
472/// Creates the required symbols for a plugin library.
473///
474/// Any plugin library must call this macro exactly once to generate the symbols that are required
475/// by the daemon.
476#[macro_export]
477macro_rules! declare_plugin {
478 ($plugin_type:ty, $plugin_err_type:ty) => {
479 /// Initializes the library.
480 ///
481 /// This function is called only once by the daemon. It is called when a library is first
482 /// loaded into memory.
483 #[no_mangle]
484 pub extern "C" fn kpal_library_init() -> c_int {
485 env_logger::init();
486 PLUGIN_OK
487 }
488
489 /// Returns a new Plugin instance containing the plugin data and the function vtable.
490 ///
491 /// The plugin is used by the daemon to communicate with it. It contains an opaque pointer
492 /// to the plugin data and a vtable. The vtable is a struct of function pointers to the
493 /// methods in the plugin API.
494 ///
495 /// # Safety
496 ///
497 /// This function is unsafe because it dereferences a null pointer and assigns data to a
498 /// variable of the type `MaybeUnit`.
499 #[no_mangle]
500 pub unsafe extern "C" fn kpal_plugin_new(plugin: *mut Plugin) -> c_int {
501 let plugin_data = match <$plugin_type>::new() {
502 Ok(plugin_data) => plugin_data,
503 Err(e) => {
504 log::error!("Failed to initialize the plugin: {:?}", e);
505 return PLUGIN_INIT_ERR;
506 }
507 };
508
509 let plugin_data: Box<$plugin_type> = Box::new(plugin_data);
510 let plugin_data = Box::into_raw(plugin_data) as *mut PluginData;
511
512 let vtable = VTable {
513 plugin_free,
514 plugin_init: plugin_init::<$plugin_type, $plugin_err_type>,
515 error_message_ns,
516 attribute_count: attribute_count::<$plugin_type, $plugin_err_type>,
517 attribute_ids: attribute_ids::<$plugin_type, $plugin_err_type>,
518 attribute_name: attribute_name::<$plugin_type, $plugin_err_type>,
519 attribute_pre_init: attribute_pre_init::<$plugin_type, $plugin_err_type>,
520 attribute_value: attribute_value::<$plugin_type, $plugin_err_type>,
521 set_attribute_value: set_attribute_value::<$plugin_type, $plugin_err_type>,
522 };
523
524 plugin.write(Plugin {
525 plugin_data,
526 vtable,
527 });
528
529 log::debug!("Created new plugin: {:?}", plugin);
530 PLUGIN_OK
531 }
532 };
533}
534
535/// An error type that represents a failure to convert a Val to a Value.
536#[derive(Debug)]
537pub struct ValueConversionError {
538 side: FromBytesWithNulError,
539}
540
541impl Error for ValueConversionError {
542 fn source(&self) -> Option<&(dyn Error + 'static)> {
543 Some(&self.side)
544 }
545}
546
547impl fmt::Display for ValueConversionError {
548 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
549 write!(f, "PluginError: {:?}", self)
550 }
551}
552
553impl From<FromBytesWithNulError> for ValueConversionError {
554 fn from(error: FromBytesWithNulError) -> Self {
555 ValueConversionError { side: error }
556 }
557}