gc_abi 0.5.0

Gridcore Plugin Safe ABI Bindings
Documentation
#![doc(html_no_source)]
//! This crate contains safe zero-cost abstractions over the raw C types in [gc_abi_sys].
//! Every interaction with the core gateway should be done through these abstractions.
//!
//! # Accessing the raw C types
//! Every abstraction implements the [GCAbiConversion] trait, which allows for easy conversion between the Rust and C types.
//! Some raw C types can also be accessed through the [raw] module.
mod datapoint;
mod datapoint_value;
mod datapoint_value_quality;
pub mod device_config;
pub mod error;
mod log;
mod plugin_config;
mod plugin_info;
mod plugin_interface;
pub mod raw;

use std::ffi::c_char;

pub use log::GCLogLevel;
pub type GCDatapointID = gc_abi_sys::GCDatapointIdentifier;
pub use datapoint::GCDatapoint;
pub use datapoint_value::{GCDatapointValue, GCDatapointValueType};
pub use datapoint_value_quality::GCDatapointValueQuality;
pub use device_config::{DataBits, GCEthernetInterface, GCSerialInterface, Parity, StopBits};
pub use plugin_config::GCPluginConfig;
pub use plugin_info::GCPluginInfo;
pub use plugin_interface::GCCoreExportedFunctions;
pub use plugin_interface::GCPluginInterface;

use crate::error::GCPluginABIError;

/// Trait for converting between Rust and C types
/// Provides methods for converting between raw and abstraction types.
///
/// # Safety
/// Implementors must ensure that [GCAbiConversion::CType] has the same memory layout as whatever implements [GCAbiConversion]
pub unsafe trait GCAbiConversion: Sized {
    type CType: Sized;

    /// Convert from the C type to the Rust type
    fn from_c(c_type: Self::CType) -> Self;

    /// Convert from the Rust type to the C type
    fn into_c(self) -> Self::CType;

    /// Get a reference to the C type
    fn ref_c(&self) -> &Self::CType {
        // Safety:
        // As long as the layout of the two types is the same, this is safe
        unsafe { &*(self as *const Self as *const Self::CType) }
    }

    /// Get a reference to the converted type
    fn from_c_ref(c_type: &Self::CType) -> &Self {
        // Safety:
        // As long as the layout of the two types is the same, this is safe
        unsafe { &*(c_type as *const Self::CType as *const Self) }
    }

    /// Get a mutable reference to the C type
    ///
    /// # Safety
    /// Assumes the pointer is valid and not null
    /// The caller is responsible for dealing with the lifetime of the pointer
    unsafe fn from_c_ptr(ptr: *const Self::CType) -> &'static Self {
        unsafe {
            let result = ptr as *const Self;
            &*result
        }
    }

    /// Get a immutable reference to the C type
    ///
    /// # Safety
    /// The caller is responsible for manually handle the lifetime of the pointer
    unsafe fn as_ptr(&self) -> *const Self::CType {
        self as *const Self as *mut Self::CType
    }

    /// Get a mutable reference to the C type
    ///
    /// # Safety
    /// The caller is responsible for dealing with the lifetime of the pointer
    /// Also, the caller must not mutate both the reference at pointer at the same time
    /// as this goes against the borrowing rules
    unsafe fn as_mut_ptr(&mut self) -> *mut Self::CType {
        self as *mut Self as *mut Self::CType
    }
}

fn copy_str_to_c<const N: usize>(str: &str) -> Result<[c_char; N], GCPluginABIError> {
    let mut bytes = [0; N];

    if str.len() >= N {
        return Err(GCPluginABIError::StringTooLong(N, str.to_string()));
    }

    for (dst, src) in bytes.iter_mut().zip(str.bytes()) {
        *dst = src as c_char;
    }
    Ok(bytes)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_copy_str_to_c_valid_string() {
        // String shorter than buffer should succeed
        let result = copy_str_to_c::<6>("hello");
        assert!(result.is_ok(), "Short string should succeed");

        let bytes = result.unwrap();
        assert_eq!(bytes[0], b'h' as i8);
        assert_eq!(bytes[1], b'e' as i8);
        assert_eq!(bytes[2], b'l' as i8);
        assert_eq!(bytes[3], b'l' as i8);
        assert_eq!(bytes[4], b'o' as i8);
        assert_eq!(bytes[5], 0); // Rest should be zero
    }

    #[test]
    fn test_copy_str_to_c_string_too_long() {
        // String equal to buffer size should fail (no room for null terminator)
        let result = copy_str_to_c::<5>("hello");
        assert!(result.is_err(), "String of length N should fail");

        match result {
            Err(GCPluginABIError::StringTooLong(size, string)) => {
                assert_eq!(size, 5);
                assert_eq!(string, "hello");
            }
            _ => panic!("Expected StringTooLong error"),
        }
    }

    #[test]
    fn test_copy_str_to_c_string_much_longer() {
        // String longer than buffer should fail
        let result = copy_str_to_c::<5>("hello world");
        assert!(result.is_err(), "String longer than N should fail");

        match result {
            Err(GCPluginABIError::StringTooLong(size, string)) => {
                assert_eq!(size, 5);
                assert_eq!(string, "hello world");
            }
            _ => panic!("Expected StringTooLong error"),
        }
    }
}