gc_plugin_abi 0.5.0

Gridcore Plugin API
Documentation
use std::ffi::c_void;
use std::marker::PhantomData;
use std::ptr::NonNull;

use super::errors::GCMockingError;
use super::gateway_instance_ctx::GCGatewayMockInstanceCtx;
use super::gateway_mock_builder::GCGatewayMockBuilder;
use super::gateway_mock_extern::{
    audit_log, get_ethernet_interface, get_last_datapoint_value, get_serial_interface, publish_datapoint_value, release_datapoint_value, system_log,
};
use crate::{GCDatapoint, GCDatapointValue, GCPluginInfo};
use crate::{GCPluginInstance, GCPluginInterface};
use gc_abi::raw;
use gc_json_bridge::GCJsonBridge;
use serde_json::Value;

/// A mock for the gateway that allows to send datapoints to the plugin
pub struct GCGatewayMock<'a, T: GCPluginInstance<'a>> {
    plugin_interface: Box<raw::sGCPluginInterface>,
    _own_datapoints: Vec<GCDatapoint>,
    _subscribed_datapoints: Vec<GCDatapoint>,
    _json_bridge: Box<GCJsonBridge>,
    json_data: &'static Value,

    plugin_ctx: Option<NonNull<T>>, // Context returned by the plugin
    mark: PhantomData<&'a T>,
}

impl<'a, T: GCPluginInstance<'a>> GCGatewayMock<'a, T> {
    pub fn new(mock_builder: GCGatewayMockBuilder) -> Result<Self, GCMockingError> {
        let leaked_json = Box::leak(mock_builder.config);
        let own_datapoints = mock_builder.own_datapoints;
        let subscribed_datapoints = mock_builder.subscribed_datapoints;
        let gateway_ctx = mock_builder.core_ctx;

        // Set interface
        let bridge = Box::new(GCJsonBridge::new(leaked_json));
        let callbacks = std::ptr::from_ref(bridge.gc_json_bridge_callbacks());

        let config = raw::GCPluginConfig {
            pluginJsonConfig: callbacks,
            ownDatapoints: own_datapoints.as_ptr() as *const raw::GCDatapoint,
            ownDatapointsCount: own_datapoints.len() as u64,
            subscribedDatapoints: subscribed_datapoints.as_ptr() as *const raw::GCDatapoint,
            subscribedDatapointsCount: subscribed_datapoints.len() as u64,
        };

        let interface: Box<raw::sGCPluginInterface> = Box::new(raw::sGCPluginInterface {
            ctx: Box::into_raw(gateway_ctx) as raw::GCCoreCtx,
            logLevel: mock_builder.log_level.into_c(),
            config,
            publishDatapointValueCallback: Some(publish_datapoint_value),
            logSystemCallback: Some(system_log),
            logAuditCallback: Some(audit_log),
            releaseDatapointValueCallback: Some(release_datapoint_value),
            getLastDatapointValueCallback: Some(get_last_datapoint_value),
            getEthernetInterfaceCallback: Some(get_ethernet_interface),
            getSerialInterfaceCallback: Some(get_serial_interface),
            storeDatapointValueCallback: None,
        });

        // Create object
        let ctx = Self {
            plugin_interface: interface,
            _own_datapoints: own_datapoints,
            _subscribed_datapoints: subscribed_datapoints,
            _json_bridge: bridge,
            json_data: leaked_json,

            plugin_ctx: None,
            mark: PhantomData,
        };
        Ok(ctx)
    }

    pub(crate) fn start(&mut self) -> Result<*const T, GCMockingError> {
        if self.plugin_ctx.is_some() {
            return Err(GCMockingError::ErrorPluginAlreadyLoaded);
        }
        let interface: raw::GCPluginInterface = &*self.plugin_interface;
        let result = unsafe { T::gc_plugin_init(interface) };
        if result.is_null() {
            return Err(GCMockingError::ErrorPluginInitFailed);
        }
        self.plugin_ctx = Some(NonNull::new(result as *mut T)).ok_or(GCMockingError::ErrorPluginInitFailed)?;
        Ok(result as *const T)
    }

    pub fn start_from<F>(&mut self, f: F) -> Result<(), GCMockingError>
    where
        F: FnOnce(&'a GCPluginInterface) -> Box<T> + 'a,
    {
        if self.plugin_ctx.is_some() {
            return Err(GCMockingError::ErrorPluginAlreadyLoaded);
        }
        let release_callback = self.plugin_interface.releaseDatapointValueCallback;
        crate::set_datapoint_release_fn(release_callback);
        let result = f(self.get_gateway_interface());
        self.plugin_ctx = Some(NonNull::new(Box::into_raw(result))).ok_or(GCMockingError::ErrorPluginInitFailed)?;
        Ok(())
    }

    /// Send a datapoint to the plugin
    pub fn send_subscribed_datapoint(&self, datapoint_value: &GCDatapointValue) -> Result<(), GCMockingError> {
        if let Some(plugin_ctx) = self.plugin_ctx {
            let dp_copy = Box::new(datapoint_value.clone());
            let dp_copy_raw = Box::into_raw(dp_copy);
            unsafe { T::gc_plugin_receive_datapoint(plugin_ctx.as_ptr() as *mut c_void, dp_copy_raw as raw::GCDatapointValue) };
            Ok(())
        } else {
            Err(GCMockingError::ErrorPluginNotLoaded)
        }
    }

    /// Get the plugin interface
    pub fn get_gateway_interface(&self) -> &'a GCPluginInterface {
        let ptr = self.plugin_interface.as_ref() as raw::GCPluginInterface;
        ptr.into()
    }

    /// Get the plugin info
    /// This function can be called even without the plugin being started
    pub fn get_plugin_info() -> &'static GCPluginInfo {
        // Safety:
        // The info is owned by the plugin and is guaranteed to be valid until even after the plugin is shutdown
        // Additionally the pointer cast to gc_plugin_abi::GCPluginInfo is safe as long as this struct
        // contains the same memory layout as raw::GCPluginInfo
        unsafe {
            let result = T::gc_plugin_get_info() as *const GCPluginInfo;

            (&*result) as _
        }
    }

    /// Get and cast the loaded plugin instance
    pub fn get_plugin_instance(&self) -> Result<&T, GCMockingError> {
        if let Some(plugin_ctx) = self.plugin_ctx {
            Ok(unsafe { &*(plugin_ctx.as_ptr()) })
        } else {
            Err(GCMockingError::ErrorPluginNotLoaded)
        }
        // Safety:
        // The dereference is safe is safe as long as the plugin is not shutdown (which happens when this instance is dropped)
    }
}

impl<'a, T: GCPluginInstance<'a>> Drop for GCGatewayMock<'a, T> {
    fn drop(&mut self) {
        if let Some(plugin_ctx) = self.plugin_ctx {
            unsafe { T::gc_plugin_shutdown(plugin_ctx.as_ptr() as *mut c_void) };

            // Safety:
            // The json data is owned by the plugin and is guaranteed to be valid until it is shutdown
            // which only happens when the plugin is dropped
            // Additionally, the plugin is already shutdown, so it's safe to cast the const pointer to a mutable pointer
            unsafe {
                drop(Box::from_raw(self.json_data as *const Value as *mut Value));
                drop(Box::from_raw(self.plugin_interface.ctx as *mut GCGatewayMockInstanceCtx));
            }
        }
    }
}

unsafe impl<'a, T: GCPluginInstance<'a>> Send for GCGatewayMock<'a, T> {}
unsafe impl<'a, T: GCPluginInstance<'a>> Sync for GCGatewayMock<'a, T> {}