gc_abi 0.5.0

Gridcore Plugin Safe ABI Bindings
Documentation
use crate::GCLogLevel;
use crate::GCPluginConfig;
use crate::device_config::{GCEthernetInterface, GCSerialInterface};
use crate::error::GCPluginABIError;
use crate::raw;
use crate::{GCAbiConversion, GCDatapointID, GCDatapointValue};
use std::ffi::CStr;

pub struct GCCoreExportedFunctions {
    pub log_system: raw::GCSystemLogCoreExport,
    pub log_audit: raw::GCSystemLogAuditCoreExport,
    pub release_datapoint_value: raw::GCReleaseDatapointValueCoreExport,
    pub get_last_datapoint_value: raw::GCGetLastDatapointValueCoreExport,
    pub publish_datapoint_value: raw::GCPublishDatapointValueCoreExport,
    pub get_ethernet_interface: raw::GCGetEthernetInterfaceCoreExport,
    pub get_serial_interface: raw::GCGetSerialInterfaceCoreExport,
    pub store_datapoint_value: raw::GCStoreDatapointValueCoreExport,
}

/// Zero cost abstraction over the GCPluginInterface
/// Safety:
/// All the function calls are valid as long as the underlying GCPluginInterface pointer is valid
#[repr(transparent)]
pub struct GCPluginInterface(gc_abi_sys::sGCPluginInterface);

impl GCPluginInterface {
    /// Create a new GCPluginInterface
    ///
    /// # Safety
    /// The caller is responsible for ensuring the ctx and config are valid for the lifetime of the GCPluginInterface
    pub unsafe fn new<T>(config: GCPluginConfig, ctx: *const T, exports: GCCoreExportedFunctions, log_level: GCLogLevel) -> Self {
        GCPluginInterface(gc_abi_sys::sGCPluginInterface {
            config: config.into_c(),
            ctx: ctx as *mut std::ffi::c_void,
            logSystemCallback: exports.log_system,
            logLevel: log_level.into_c(),
            logAuditCallback: exports.log_audit,
            releaseDatapointValueCallback: exports.release_datapoint_value,
            getLastDatapointValueCallback: exports.get_last_datapoint_value,
            publishDatapointValueCallback: exports.publish_datapoint_value,
            getEthernetInterfaceCallback: exports.get_ethernet_interface,
            getSerialInterfaceCallback: exports.get_serial_interface,
            storeDatapointValueCallback: exports.store_datapoint_value,
        })
    }

    /// Publishes a datapoint value
    #[inline]
    pub fn publish_datapoint(&self, datapoint_value: &GCDatapointValue) -> bool {
        // Safety:
        // Calling FFI functions is inherently unsafe, the gateway core guarantees that callbacks are always valid
        unsafe {
            let ptr = datapoint_value as *const GCDatapointValue;
            self.0.publishDatapointValueCallback.unwrap_unchecked()(self.0.ctx, ptr as *mut gc_abi_sys::sGCDatapointValue)
        }
    }

    /// Stores a datapoint value in the timeseries database
    #[inline]
    pub fn store_datapoint(&self, datapoint_value: &GCDatapointValue) {
        // Safety:
        // Calling FFI functions is inherently unsafe, the gateway core guarantees that callbacks are always valid
        unsafe {
            let ptr = datapoint_value as *const GCDatapointValue;
            self.0.storeDatapointValueCallback.unwrap_unchecked()(self.0.ctx, ptr as *mut gc_abi_sys::sGCDatapointValue)
        }
    }

    /// Returns the configuration of the plugin
    #[inline]
    pub fn get_config(&self) -> &GCPluginConfig {
        GCPluginConfig::from_c_ref(&self.0.config)
    }

    /// Retrieve the last value of a datapoint from the realtime database
    ///
    /// # Safety
    /// The caller is responsible for freeing the memory of the returned value and also it has to ensure the pointer is not null
    #[inline]
    pub unsafe fn get_last_datapoint_value(&self, datapoint_id: GCDatapointID) -> *const GCDatapointValue {
        unsafe {
            let ptr: gc_abi_sys::GCDatapointValue = self.0.getLastDatapointValueCallback.unwrap()(self.0.ctx, datapoint_id);
            ptr as *const GCDatapointValue
        }
    }

    /// Log a message
    #[inline]
    pub fn log(&self, level: gc_abi_sys::eGCLogLevel, message: &CStr) {
        // Safety:
        // The pointer is guaranteed to be valid by the gateway core
        unsafe { self.0.logSystemCallback.unwrap_unchecked()(self.0.ctx, level, message.as_ptr()) }
    }

    /// Log a message audit
    #[inline]
    pub fn log_audit(&self, message: &CStr) {
        // Safety:
        // The pointer is guaranteed to be valid by the gateway core
        unsafe { self.0.logAuditCallback.unwrap_unchecked()(self.0.ctx, message.as_ptr()) }
    }

    /// Get the C log level of the plugin
    #[inline]
    pub fn get_raw_log_level(&self) -> gc_abi_sys::eGCLogLevel {
        self.0.logLevel
    }

    /// Get the log level of the plugin
    #[inline]
    pub fn get_log_level(&self) -> Result<GCLogLevel, GCPluginABIError> {
        GCLogLevel::from_c(self.get_raw_log_level())
    }

    /// Retrieves an Ethernet interface configuration by logical name.
    #[inline]
    pub fn get_ethernet_interface(&self, name: &CStr) -> Option<GCEthernetInterface> {
        let callback = self.0.getEthernetInterfaceCallback?;

        // Allocate struct on stack
        // Safety:
        // FFI calls are inherently unsafe
        unsafe {
            let mut out: raw::sGCEthernetInterface = std::mem::zeroed();

            // Call the callback - it fills the struct and returns true if found
            let success = callback(self.0.ctx, name.as_ptr(), &mut out as *mut _);

            if success {
                // Convert C struct to Rust type
                Some(GCEthernetInterface::from_c(out))
            } else {
                None
            }
        }
    }

    /// Retrieves a serial interface configuration by logical name.
    #[inline]
    pub fn get_serial_interface(&self, name: &CStr) -> Option<GCSerialInterface> {
        let callback = self.0.getSerialInterfaceCallback?;

        // Allocate struct on stack
        // Safety:
        // FFI calls are inherently unsafe
        unsafe {
            let mut out: raw::sGCSerialInterface = std::mem::zeroed();

            // Call the callback - it fills the struct and returns true if found
            let success = callback(self.0.ctx, name.as_ptr(), &mut out as *mut _);

            if success {
                // Convert C struct to Rust type
                Some(GCSerialInterface::from_c(out))
            } else {
                None
            }
        }
    }
}

unsafe impl GCAbiConversion for GCPluginInterface {
    type CType = gc_abi_sys::sGCPluginInterface;

    fn from_c(c_type: Self::CType) -> Self {
        GCPluginInterface(c_type)
    }

    fn into_c(self) -> Self::CType {
        self.0
    }
}

unsafe impl Send for GCPluginInterface {}
unsafe impl Sync for GCPluginInterface {}