icsneoc2 0.1002002.0-rc.4

High-level Rust interface for Intrepid Control Systems vehicle network adapters
Documentation
//! CAN and network message types.
//!
//! A [`Message`] wraps an opaque C library handle to a network frame.
//! Messages are created with [`Message::new`], or received from a device
//! via [`Device::message`](crate::Device::message).
//!
//! # Example
//!
//! ```no_run
//! use icsneoc2::message::{Message, MessageType};
//!
//! let msg = Message::new(MessageType::Can)?;
//! msg.set_data(&[0x01, 0x02, 0x03])?;
//! msg.set_can_props(Some(0x7DF), None)?;
//! # Ok::<(), icsneoc2::Error>(())
//! ```

use std::ptr::NonNull;
use std::sync::Arc;

use crate::enums::MessageCanFlags;
use crate::error::{Error, Result};
use crate::functions::{api_error, check};
use crate::sys;

/// The protocol type of a message.
pub enum MessageType {
    /// Classical CAN or CAN FD.
    Can,
}

/// CAN arbitration ID and flags returned by [`Message::can_props`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CanProps {
    pub arb_id: u64,
    pub flags: MessageCanFlags,
}

#[derive(Debug)]
struct MessageInner {
    ptr: NonNull<sys::icsneoc2_message_t>,
    no_cleanup: bool,
}

unsafe impl Send for MessageInner {}
unsafe impl Sync for MessageInner {}

impl Drop for MessageInner {
    fn drop(&mut self) {
        if self.no_cleanup {
            return;
        }
        let raw = unsafe { sys::icsneoc2_message_free(self.ptr.as_ptr()) };
        match sys::Error::try_from(raw) {
            Ok(sys::Error::Success) => {}
            Ok(code) => eprintln!("Failed to free message: {}", api_error(code)),
            Err(e) => eprintln!("Failed to free message: {e}"),
        }
    }
}

/// A handle to a CAN/Ethernet/etc. message managed by the C library.
///
/// # Interior mutability
///
/// `Message` is `Clone` (via `Arc`), and setter methods take `&self`.
/// The underlying C library serialises access to the message data, so
/// concurrent mutation from multiple clones is safe.  If you do not need
/// shared ownership, prefer not cloning and using a single owner.
#[derive(Debug, Clone)]
pub struct Message {
    msg: Arc<MessageInner>,
}

impl Message {
    /// Creates a new empty message of the given protocol type.
    ///
    /// The returned message is owned by the caller and will be freed on
    /// drop via `icsneoc2_message_free`.
    ///
    /// # Errors
    ///
    /// Returns [`Error::MemoryError`] if the
    /// C library returns a null pointer.
    pub fn new(message_type: MessageType) -> Result<Self> {
        let mut ptr: *mut sys::icsneoc2_message_t = std::ptr::null_mut();
        check(match message_type {
            MessageType::Can => unsafe { sys::icsneoc2_message_can_create(&raw mut ptr) },
        })?;
        Ok(Message {
            msg: Arc::new(MessageInner {
                ptr: NonNull::new(ptr).ok_or_else(|| {
                    Error::MemoryError(
                        "icsneoc2_message_can_create returned a null pointer".to_string(),
                    )
                })?,
                no_cleanup: false,
            }),
        })
    }

    /// Wraps a library-managed pointer.
    ///
    /// The library retains ownership of the underlying memory and will free it itself;
    /// the `Message` wrapper will **not** call `icsneoc2_message_free` on drop.
    ///
    /// # Safety
    /// `ptr` must be a valid, non-null pointer that remains valid for the lifetime of
    /// the returned `Message`.
    pub unsafe fn from_library_ptr(ptr: *mut sys::icsneoc2_message_t) -> Result<Self> {
        Ok(Message {
            msg: Arc::new(MessageInner {
                ptr: NonNull::new(ptr).ok_or_else(|| {
                    Error::MemoryError("from_library_ptr: null pointer".to_string())
                })?,
                no_cleanup: true,
            }),
        })
    }

    /// Wraps a pointer that the caller is responsible for freeing.
    ///
    /// The `Message` wrapper **will** call `icsneoc2_message_free` on drop.
    ///
    /// # Safety
    /// `ptr` must be a valid, non-null, exclusively-owned pointer to a message
    /// that was allocated by `icsneoc2_message_can_create` (or equivalent).
    pub unsafe fn from_owned_ptr(ptr: *mut sys::icsneoc2_message_t) -> Result<Self> {
        Ok(Message {
            msg: Arc::new(MessageInner {
                ptr: NonNull::new(ptr).ok_or_else(|| {
                    Error::MemoryError("from_owned_ptr: null pointer".to_string())
                })?,
                no_cleanup: false,
            }),
        })
    }

    /// Returns a mutable raw pointer to the underlying `icsneoc2_message_t`.
    ///
    /// This method provides access to the raw pointer managed by the `Message` wrapper,
    /// allowing it to be passed to FFI functions that require a mutable pointer.
    ///
    /// # Safety
    /// The caller must ensure that the pointer is not used after the `Message` instance
    /// has been dropped, as the underlying memory may be freed at that point.
    /// Additionally, the caller must not free the pointer manually, as the `Message`
    /// wrapper manages the lifetime of the underlying memory.
    ///
    /// # Returns
    /// A mutable raw pointer to the underlying `icsneoc2_message_t`.
    pub fn as_mut_ptr(&self) -> *mut sys::icsneoc2_message_t {
        self.msg.ptr.as_ptr()
    }

    /// Sets the raw byte payload of the message.
    ///
    /// For CAN frames the maximum payload is 8 bytes (classical) or
    /// 64 bytes (CAN FD). For Ethernet frames, up to the MTU.
    pub fn set_data(&self, data: &[u8]) -> Result<()> {
        check(unsafe {
            sys::icsneoc2_message_data_set(self.as_mut_ptr(), data.as_ptr() as *mut u8, data.len())
        })
    }

    /// Returns the CAN arbitration ID and flags for this message.
    ///
    /// Only meaningful for CAN/CAN FD messages.
    pub fn can_props(&self) -> Result<CanProps> {
        let mut arb_id: u64 = 0;
        let mut flags: u64 = 0;
        check(unsafe {
            sys::icsneoc2_message_can_props_get(self.as_mut_ptr(), &raw mut arb_id, &raw mut flags)
        })?;
        Ok(CanProps {
            arb_id,
            flags: MessageCanFlags::from(flags),
        })
    }

    /// Sets the CAN arbitration ID and/or flags.
    ///
    /// Pass `None` to leave a field unchanged. Only meaningful for
    /// CAN/CAN FD messages.
    pub fn set_can_props(&self, arb_id: Option<u64>, flags: Option<MessageCanFlags>) -> Result<()> {
        let flags_raw = flags.map(u64::from);
        check(unsafe {
            sys::icsneoc2_message_can_props_set(
                self.as_mut_ptr(),
                arb_id
                    .as_ref()
                    .map_or(std::ptr::null(), |v| v as *const u64),
                flags_raw
                    .as_ref()
                    .map_or(std::ptr::null(), |v| v as *const u64),
            )
        })
    }

    /// Returns the raw byte payload.
    ///
    /// `max_size` caps the read buffer (default 4096). The returned `Vec`
    /// is truncated to the actual data length.
    pub fn data(&self, max_size: Option<usize>) -> Result<Vec<u8>> {
        let mut data: Vec<u8> = vec![0; max_size.unwrap_or(4096)];
        let mut len = data.len();
        check(unsafe {
            sys::icsneoc2_message_data_get(self.as_mut_ptr(), data.as_mut_ptr(), &raw mut len)
        })?;
        data.truncate(len);
        Ok(data)
    }

    /// Returns `true` if this is an outgoing (transmit) message.
    ///
    /// Messages created locally for transmission will return `true`,
    /// while received messages return `false`.
    pub fn is_transmit(&self) -> Result<bool> {
        let mut value = false;
        check(unsafe { sys::icsneoc2_message_is_transmit(self.as_mut_ptr(), &raw mut value) })?;
        Ok(value)
    }

    /// Returns `true` if the message passed CRC / format validation.
    pub fn is_valid(&self) -> Result<bool> {
        let mut value = false;
        check(unsafe { sys::icsneoc2_message_is_valid(self.as_mut_ptr(), &raw mut value) })?;
        Ok(value)
    }

    /// Returns `true` if the message is a bus frame.
    ///
    /// Returns `false` for status or event pseudo-messages.
    pub fn is_frame(&self) -> Result<bool> {
        let mut value = false;
        check(unsafe { sys::icsneoc2_message_is_frame(self.as_mut_ptr(), &raw mut value) })?;
        Ok(value)
    }

    /// Returns `true` if this is a raw (unprocessed) message.
    ///
    /// Raw messages have not been decoded by the library's protocol stack.
    pub fn is_raw(&self) -> Result<bool> {
        let mut value = false;
        check(unsafe { sys::icsneoc2_message_is_raw(self.as_mut_ptr(), &raw mut value) })?;
        Ok(value)
    }

    /// Returns `true` if this is a CAN or CAN FD message.
    pub fn is_can(&self) -> Result<bool> {
        let mut value = false;
        check(unsafe { sys::icsneoc2_message_is_can(self.as_mut_ptr(), &raw mut value) })?;
        Ok(value)
    }

    /// Returns the network ID this message was received on (or will be sent to).
    ///
    /// See [`Netid`](crate::Netid) for the full list of network identifiers.
    pub fn netid(&self) -> Result<sys::Netid> {
        let mut raw: sys::icsneoc2_netid_t = 0;
        check(unsafe { sys::icsneoc2_message_netid_get(self.as_mut_ptr(), &raw mut raw) })?;
        Ok(sys::Netid::try_from(raw)?)
    }

    /// Sets the target network ID for transmission.
    pub fn set_netid(&self, value: sys::Netid) -> Result<()> {
        check(unsafe {
            sys::icsneoc2_message_netid_set(self.as_mut_ptr(), value as sys::icsneoc2_netid_t)
        })
    }

    /// Returns the network type (CAN, LIN, Ethernet, etc.).
    pub fn network_type(&self) -> Result<sys::NetworkType> {
        let mut raw: sys::icsneoc2_network_type_t = 0;
        check(unsafe { sys::icsneoc2_message_network_type_get(self.as_mut_ptr(), &raw mut raw) })?;
        Ok(sys::NetworkType::try_from(raw)?)
    }
}