diff --git a/can-socket/CHANGELOG b/can-socket/CHANGELOG index 58b2ae2..3a69197 100644 --- a/can-socket/CHANGELOG +++ b/can-socket/CHANGELOG @@ -1,4 +1,6 @@ # Unreleased +- [change][major] Refactor `CanFrame` API. +- [change][major] Rename CAN ID structs and values. - [add][minor] Add `id!()`, `base_id!()` and `extended_id!()` macros to construct compile time checked CAN IDs. # Version 0.1.6 - 2024-10-23 diff --git a/can-socket/Cargo.toml b/can-socket/Cargo.toml index f718724..353d7ad 100644 --- a/can-socket/Cargo.toml +++ b/can-socket/Cargo.toml @@ -1,18 +1,22 @@ [package] name = "can-socket" -description = "no frills CAN sockets (blocking or async with tokio)" +description = "no frills CAN sockets (synchronous or async with tokio)" version = "0.1.6" license = "BSD-2-Clause" keywords = ["CAN", "SocketCAN", "socket", "CANbus", "network"] categories = ["os", "hardware-support", "network-programming", "science::robotics"] -repository = "https://github.com/de-vri-es/can-socket-rs/tree/main/can-socket" +repository = "https://github.com/de-vri-es/can-socket-rs" documentation = "https://docs.rs/can-socket" +readme = "README.md" edition = "2021" publish = ["crates-io"] [features] ignore-vcan-tests = [] +tokio = ["dep:tokio"] +doc = ["tokio", "tokio?/test-util"] +doc-cfg = [] [dependencies] filedesc = "0.6.3" @@ -24,5 +28,8 @@ assert2 = "0.3.14" can-socket = { path = ".", features = ["tokio"] } clap = { version = "4.4.4", features = ["derive"] } rand = "0.8.5" -tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.32.0", features = ["macros", "rt-multi-thread", "test-util"] } trybuild = { version = "1.0.101", features = ["diff"] } + +[package.metadata.docs.rs] +features = ["doc", "doc-cfg"] diff --git a/can-socket/README.md b/can-socket/README.md new file mode 100644 index 0000000..2b6444c --- /dev/null +++ b/can-socket/README.md @@ -0,0 +1,21 @@ +CAN socket + +This library exposes a [`CanSocket`] and related types, +allowing you to communicate over a Controller Area Network (CAN) bus. + +The is a standard blocking or non-blocking [`CanSocket`], +and an asynchronous [`tokio::CanSocket`]. + +This library uses the `SocketCAN` interface and only works on Linux. + +Supported features: +* Bind sockets to specific interfaces by name or index. +* Bind sockets to *all* CAN interfaces at the same time. +* Send and receive data frames and RTR frames. +* Send and receive standard frames and extended frames. +* Setting per-socket filters. +* Control over the `loopback` and `recv_own_msgs` options. +* Constructing compile-time checked CAN IDs. + +[`CanSocket`]: https://docs.rs/can-socket/latest/can_socket/struct.CanSocket.html +[`tokio::CanSocket`]: https://docs.rs/can-socket/latest/can_socket/tokio/struct.CanSocket.html diff --git a/can-socket/README.tpl b/can-socket/README.tpl new file mode 100644 index 0000000..560cb73 --- /dev/null +++ b/can-socket/README.tpl @@ -0,0 +1,4 @@ +{{readme}} + +[`CanSocket`]: https://docs.rs/can-socket/latest/can_socket/struct.CanSocket.html +[`tokio::CanSocket`]: https://docs.rs/can-socket/latest/can_socket/tokio/struct.CanSocket.html diff --git a/can-socket/examples/write-tokio.rs b/can-socket/examples/write-tokio.rs index 65567f5..701a3a4 100644 --- a/can-socket/examples/write-tokio.rs +++ b/can-socket/examples/write-tokio.rs @@ -17,14 +17,6 @@ struct Options { #[clap(long)] rtr: Option, - /// The data length code to send. - /// - /// May only be specified for messages of size 8, - /// and may only be a value from 9 to 15 (inclusive). - #[clap(long)] - #[clap(conflicts_with = "rtr")] - data_length_code: Option, - /// Number of times to send the frame. /// /// Will keep sending forever if you specify 0. @@ -47,10 +39,11 @@ async fn main() { async fn do_main(options: Options) -> Result<(), ()> { let frame = if let Some(len) = options.rtr { - CanFrame::new_rtr(options.id, len) + CanFrame::new_rtr(options.id) + .with_data_length_code(len) .map_err(|e| eprintln!("Invalid input: {e}"))? } else { - CanFrame::new(options.id, &options.data, options.data_length_code) + CanFrame::try_new(options.id, &options.data) .map_err(|e| eprintln!("Invalid input: {e}"))? }; diff --git a/can-socket/examples/write.rs b/can-socket/examples/write.rs index f4e24f9..a340c98 100644 --- a/can-socket/examples/write.rs +++ b/can-socket/examples/write.rs @@ -16,14 +16,6 @@ struct Options { #[clap(long)] rtr: Option, - /// The data length code to send. - /// - /// May only be specified for messages of size 8, - /// and may only be a value from 9 to 15 (inclusive). - #[clap(long)] - #[clap(conflicts_with = "rtr")] - data_length_code: Option, - /// Number of times to send the frame. /// /// Will keep sending forever if you specify 0. @@ -45,10 +37,11 @@ fn main() { fn do_main(options: Options) -> Result<(), ()> { let frame = if let Some(len) = options.rtr { - CanFrame::new_rtr(options.id, len) + CanFrame::new_rtr(options.id) + .with_data_length_code(len) .map_err(|e| eprintln!("Invalid input: {e}"))? } else { - CanFrame::new(options.id, &options.data, options.data_length_code) + CanFrame::try_new(options.id, &options.data) .map_err(|e| eprintln!("Invalid input: {e}"))? }; diff --git a/can-socket/src/can_id.rs b/can-socket/src/can_id.rs deleted file mode 100644 index 026e8e3..0000000 --- a/can-socket/src/can_id.rs +++ /dev/null @@ -1,527 +0,0 @@ -use crate::error::{InvalidId, ParseIdError}; - -pub const MAX_CAN_ID_BASE: u16 = 0x7FF; -pub const MAX_CAN_ID_EXTENDED: u32 = 0x1FFF_FFFF; - -/// Construct a [`CanId`] (base or extended) that is checked at compile time. -/// -/// You can use any expression that can be evaluated at compile time and results in a `u32`. -/// -/// By default, if the value fits in a base CAN ID, a base CAN ID is created. -/// You can also explicitly ask for a base or extended ID. -/// -/// Usage: -/// ``` -/// # use can_socket::{CanId, id}; -/// let id: CanId = id!(0x100 | 0x005); -/// assert2::let_assert!(CanId::Base(id) = id); -/// assert2::assert!(id.as_u16() == 0x105); -/// ``` -/// -/// Force construction of a `CanId::Base` (does not compile because the ID only fits as an extended ID): -/// ```compile_fail -/// # use can_socket::{CanId, id}; -/// let id: CanId = id!(base: 0x10 << 16 | 0x50); -/// ``` -/// -/// Force construction of a `CanId::Extended`: -/// ``` -/// # use can_socket::{CanId, id}; -/// let id: CanId = id!(extended: 0x100 | 0x005); -/// assert2::let_assert!(CanId::Extended(id) = id); -/// assert2::assert!(id.as_u32() == 0x105); -/// ``` -#[macro_export] -macro_rules! id { - ($n:expr) => { - { - #[allow(clippy::all)] - const { ::core::assert!(($n) <= $crate::MAX_CAN_ID_EXTENDED, "invalid CAN ID") }; - unsafe { - if ($n) as u32 <= $crate::MAX_CAN_ID_BASE as u32 { - $crate::CanId::Base($crate::CanBaseId::new_unchecked(($n) as u16)) - } else { - $crate::CanId::Extended($crate::CanExtendedId::new_unchecked($n)) - } - } - } - }; - (base: $n:expr) => { - $crate::CanId::Base($crate::base_id!($n)) - }; - (extended: $n:expr) => { - $crate::CanId::Extended($crate::extended_id!($n)) - }; -} - -/// Construct a [`CanBaseId`] that is checked at compile time. -/// -/// You can use any expression that can be evaluated at compile time and results in a `u16`. -/// -/// Usage: -/// ``` -/// # use assert2::assert; -/// # use can_socket::{CanBaseId, base_id}; -/// let id: CanBaseId = base_id!(0x100 | 0x005); -/// assert!(id.as_u16() == 0x105); -/// ``` -/// -/// Will not accept invalid IDs: -/// ```compile_fail -/// # use can_socket::{CanBaseId, base_id}; -/// let id: CanBaseId = base_id!(0x800); -/// ``` -#[macro_export] -macro_rules! base_id { - ($n:expr) => { - { - #[allow(clippy::all)] - const { ::core::assert!(($n) <= $crate::MAX_CAN_ID_BASE, "invalid base CAN ID") }; - unsafe { - $crate::CanBaseId::new_unchecked($n) - } - } - }; -} - -/// Construct a [`CanExtendedId`] that is checked at compile time. -/// -/// You can use any expression that can be evaluated at compile time and results in a `u32`. -/// -/// Usage: -/// ``` -/// # use assert2::assert; -/// # use can_socket::{CanExtendedId, extended_id}; -/// let id: CanExtendedId = extended_id!(0x10 << 16 | 0x50); -/// assert!(id.as_u32() == 0x10_0050); -/// ``` -/// -/// Will not accept invalid IDs: -/// ```compile_fail -/// # use can_socket::{CanBaseId, extended_id}; -/// let id: CanExtendedId = extended_id!(0x2000_0000); -/// ``` -#[macro_export] -macro_rules! extended_id { - ($n:expr) => { - unsafe { - #[allow(clippy::all)] - const { ::core::assert!(($n) <= $crate::MAX_CAN_ID_EXTENDED, "invalid extended CAN ID"); }; - $crate::CanExtendedId::new_unchecked($n) - } - }; -} - -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -#[repr(C)] -pub enum CanId { - Base(CanBaseId), - Extended(CanExtendedId), -} - -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -#[repr(transparent)] -pub struct CanBaseId { - id: u16, -} - -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] -#[repr(transparent)] -pub struct CanExtendedId { - id: u32, -} - -impl CanId { - pub const fn new(id: u32) -> Result { - if id <= MAX_CAN_ID_BASE as u32 { - let id = id as u16; - Ok(Self::Base(CanBaseId { id })) - } else { - match CanExtendedId::new(id) { - Ok(x) => Ok(Self::Extended(x)), - Err(e) => Err(e) - } - } - } - - pub const fn new_base(id: u16) -> Result { - match CanBaseId::new(id) { - Ok(x) => Ok(Self::Base(x)), - Err(e) => Err(e), - } - } - - pub const fn new_extended(id: u32) -> Result { - match CanExtendedId::new(id) { - Ok(x) => Ok(Self::Extended(x)), - Err(e) => Err(e), - } - } - - pub const fn as_u32(self) -> u32 { - self.to_extended().as_u32() - } - - pub const fn as_base(self) -> Option { - match self { - Self::Base(id) => Some(id), - Self::Extended(_) => None, - } - } - - pub const fn as_extended(self) -> Option { - match self { - Self::Base(_) => None, - Self::Extended(id) => Some(id), - } - } - - pub const fn to_base(self) -> Result { - match self { - Self::Base(id) => Ok(id), - Self::Extended(id) => { - if id.as_u32() <= u16::MAX as u32 { - CanBaseId::new(id.as_u32() as u16) - } else { - Err(InvalidId { - id: Some(id.as_u32()), - extended: false, - }) - } - } - } - } - - pub const fn to_extended(self) -> CanExtendedId { - match self { - Self::Base(id) => CanExtendedId::from_u16(id.as_u16()), - Self::Extended(id) => id, - } - } -} - -impl CanBaseId { - pub const fn new(id: u16) -> Result { - if id <= MAX_CAN_ID_BASE { - Ok(Self { id }) - } else { - Err(InvalidId { - id: Some(id as u32), - extended: false, - }) - } - } - - pub const fn from_u8(id: u8) -> Self { - Self { id: id as u16 } - } - - pub const fn as_u16(self) -> u16 { - self.id - } - - /// Create a new base CAN ID without checking for validity. - /// - /// # Safety - /// The given ID must be a valid base CAN ID (id <= [`MAX_CAN_ID_BASE`]). - pub const unsafe fn new_unchecked(id: u16) -> Self { - debug_assert!(id <= MAX_CAN_ID_BASE); - Self { - id - } - } -} - -impl CanExtendedId { - pub const fn new(id: u32) -> Result { - if id <= MAX_CAN_ID_EXTENDED { - Ok(Self { id }) - } else { - Err(InvalidId { - id: Some(id), - extended: false, - }) - } - } - - pub const fn from_u8(id: u8) -> Self { - Self { id: id as u32 } - } - - pub const fn from_u16(id: u16) -> Self { - Self { id: id as u32 } - } - - pub const fn as_u32(self) -> u32 { - self.id - } - - /// Create a new extended CAN ID without checking for validity. - /// - /// # Safety - /// The given ID must be a valid extended CAN ID (id <= [`MAX_CAN_ID_EXTENDED`]). - pub const unsafe fn new_unchecked(id: u32) -> Self { - debug_assert!(id <= MAX_CAN_ID_EXTENDED); - Self { - id - } - } -} - -impl PartialEq for CanId { - fn eq(&self, other: &CanBaseId) -> bool { - self.as_base() - .map(|x| x == *other) - .unwrap_or(false) - } -} - -impl PartialEq for CanBaseId { - fn eq(&self, other: &CanId) -> bool { - other == self - } -} - -impl PartialEq for CanId { - fn eq(&self, other: &CanExtendedId) -> bool { - self.as_extended() - .map(|x| x == *other) - .unwrap_or(false) - } -} - -impl PartialEq for CanExtendedId { - fn eq(&self, other: &CanId) -> bool { - other == self - } -} - -impl From for CanBaseId { - fn from(value: u8) -> Self { - Self { id: value.into() } - } -} - -impl TryFrom for CanBaseId { - type Error = InvalidId; - - fn try_from(value: u16) -> Result { - Self::new(value) - } -} - -impl TryFrom for CanBaseId { - type Error = InvalidId; - - fn try_from(value: u32) -> Result { - if value > MAX_CAN_ID_BASE.into() { - Err(InvalidId { - id: Some(value), - extended: false, - }) - } else { - Ok(Self { id: value as u16 }) - } - } -} - -impl TryFrom for CanBaseId { - type Error = InvalidId; - - fn try_from(value: CanExtendedId) -> Result { - Self::try_from(value.as_u32()) - } -} - -impl TryFrom for CanBaseId { - type Error = InvalidId; - - fn try_from(value: CanId) -> Result { - Self::try_from(value.as_u32()) - } -} - -impl From for CanExtendedId { - fn from(value: u8) -> Self { - Self { id: value.into() } - } -} - -impl From for CanExtendedId { - fn from(value: u16) -> Self { - Self { id: value.into() } - } -} - -impl TryFrom for CanExtendedId { - type Error = InvalidId; - - fn try_from(value: u32) -> Result { - Self::new(value) - } -} - -impl From for CanExtendedId { - fn from(value: CanBaseId) -> Self { - value.as_u16().into() - } -} - -impl From for CanExtendedId { - fn from(value: CanId) -> Self { - value.to_extended() - } -} - -impl From for CanId { - fn from(value: u8) -> Self { - Self::Base(value.into()) - } -} - -impl From for CanId { - fn from(value: u16) -> Self { - if value <= MAX_CAN_ID_BASE { - CanBaseId { id: value }.into() - } else { - CanExtendedId::from(value).into() - } - } -} - -impl TryFrom for CanId { - type Error = InvalidId; - - fn try_from(value: u32) -> Result { - Self::new(value) - } -} - -impl From for CanId { - fn from(value: CanBaseId) -> Self { - Self::Base(value) - } -} - -impl From for CanId { - fn from(value: CanExtendedId) -> Self { - Self::Extended(value) - } -} - -impl std::str::FromStr for CanBaseId { - type Err = ParseIdError; - - fn from_str(input: &str) -> Result { - let id = parse_number(input) - .map_err(|e| ParseIdError::invalid_format(e, true))?; - let id: u16 = id.try_into() - .map_err(|_| ParseIdError::invalid_value(InvalidId { id: Some(id), extended: false }))?; - let id = id.try_into() - .map_err(|_| ParseIdError::invalid_value(InvalidId { id: Some(id.into()), extended: false }))?; - Ok(id) - } -} - -impl std::str::FromStr for CanExtendedId { - type Err = ParseIdError; - - fn from_str(input: &str) -> Result { - let id = parse_number(input) - .map_err(|e| ParseIdError::invalid_format(e, true))?; - let id = id.try_into() - .map_err(|_| ParseIdError::invalid_value(InvalidId { id: Some(id), extended: true }))?; - Ok(id) - } -} - -impl std::str::FromStr for CanId { - type Err = ParseIdError; - - fn from_str(input: &str) -> Result { - let id = parse_number(input) - .map_err(|e| ParseIdError::invalid_format(e, true))?; - let id = id.try_into() - .map_err(|_| ParseIdError::invalid_value(InvalidId { id: Some(id), extended: true }))?; - Ok(id) - } -} - -fn parse_number(input: &str) -> Result { - if let Some(hexadecimal) = input.strip_prefix("0x") { - u32::from_str_radix(hexadecimal, 16) - } else if let Some(octal) = input.strip_prefix("0o") { - u32::from_str_radix(octal, 8) - } else if let Some(binary) = input.strip_prefix("0b") { - u32::from_str_radix(binary, 2) - } else { - input.parse() - } -} - -impl std::fmt::Debug for CanId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Base(id) => id.fmt(f), - Self::Extended(id) => id.fmt(f), - } - } -} - -impl std::fmt::Debug for CanBaseId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("CanBaseId") - .field(&format_args!("0x{:03X}", self.id)) - .finish() - } -} - -impl std::fmt::Debug for CanExtendedId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("CanExtendedId") - .field(&format_args!("0x{:08X}", self.id)) - .finish() - } -} - -impl std::fmt::LowerHex for CanBaseId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.as_u16().fmt(f) - } -} - -impl std::fmt::LowerHex for CanExtendedId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.as_u32().fmt(f) - } -} - -impl std::fmt::LowerHex for CanId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Base(x) => x.fmt(f), - Self::Extended(x) => x.fmt(f), - } - } -} - -impl std::fmt::UpperHex for CanBaseId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.as_u16().fmt(f) - } -} - -impl std::fmt::UpperHex for CanExtendedId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.as_u32().fmt(f) - } -} - -impl std::fmt::UpperHex for CanId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Base(x) => x.fmt(f), - Self::Extended(x) => x.fmt(f), - } - } -} diff --git a/can-socket/src/error.rs b/can-socket/src/error.rs index 58ab777..d85441b 100644 --- a/can-socket/src/error.rs +++ b/can-socket/src/error.rs @@ -1,10 +1,26 @@ +//! Error types. + +/// The ID was invalid. #[derive(Debug, Clone)] pub struct InvalidId { pub(crate) id: Option, pub(crate) extended: bool, } -#[derive(Debug, Clone)] +impl std::error::Error for InvalidId {} +impl std::fmt::Display for InvalidId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match (self.id, self.extended) { + (Some(id), false) => write!(f, "invalid standard CAN ID: 0x{:03X}, maximum valid value is 0x7FF", id), + (None, false) => write!(f, "invalid standard CAN ID: allowed values are 0 to 0x7FF"), + (Some(id), true) => write!(f, "invalid extended CAN ID: 0x{:08X}, maximum valid value is 0x1FFF_FFFF", id), + (None, true) => write!(f, "invalid extended CAN ID, allowed values are 0 to 0x1FFF_FFFF"), + } + } +} + +/// Failed to parse the ID. +#[derive(Clone)] pub struct ParseIdError { inner: ParseIdErrorInner, } @@ -30,24 +46,104 @@ enum ParseIdErrorInner { InvalidValue(InvalidId), } -impl std::error::Error for InvalidId {} -impl std::fmt::Display for InvalidId { +impl std::error::Error for ParseIdError {} + +impl std::fmt::Display for ParseIdError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match (self.id, self.extended) { - (Some(id), false) => write!(f, "invalid base (non-extened) CAN ID: 0x{:03X}, maximum valid value is 0x7FF", id), - (None, false) => write!(f, "invalid base (non-extened) CAN ID: allowed values are 0 to 0x7FF"), - (Some(id), true) => write!(f, "invalid extened CAN ID: 0x{:08X}, maximum valid value is 0x1FFF_FFFF", id), - (None, true) => write!(f, "invalid extened CAN ID, allowed values are 0 to 0x1FFF_FFFF"), + match &self.inner { + ParseIdErrorInner::InvalidFormat(e) => e.fmt(f), + ParseIdErrorInner::InvalidValue(e) => e.fmt(f), } } } -impl std::error::Error for ParseIdError {} -impl std::fmt::Display for ParseIdError { +impl std::fmt::Debug for ParseIdError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.inner, f) + } +} + +/// The data does not fit in a CAN data frame. +#[derive(Clone, Debug)] +pub struct TryIntoCanDataError { + pub(crate) len: usize, +} + +impl std::error::Error for TryIntoCanDataError {} + +impl std::fmt::Display for TryIntoCanDataError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "data to large for CAN frame, expected at most 8 bytes, got {}", self.len) + } +} + +impl From for std::io::Error { + fn from(value: TryIntoCanDataError) -> Self { + std::io::Error::new(std::io::ErrorKind::InvalidInput, value.to_string()) + } +} + +/// The data or ID used to construct a CAN frame was out of bounds. +#[derive(Clone)] +pub struct TryNewCanFrameError { + inner: TryNewCanFrameErrorInner, +} + +#[derive(Clone, Debug)] +enum TryNewCanFrameErrorInner { + InvalidId(InvalidId), + InvalidData(TryIntoCanDataError), +} + +impl std::error::Error for TryNewCanFrameError { } + +impl std::fmt::Display for TryNewCanFrameError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.inner { - ParseIdErrorInner::InvalidFormat(e) => e.fmt(f), - ParseIdErrorInner::InvalidValue(e) => e.fmt(f), + TryNewCanFrameErrorInner::InvalidId(e) => e.fmt(f), + TryNewCanFrameErrorInner::InvalidData(e) => e.fmt(f), + } + } +} + +impl std::fmt::Debug for TryNewCanFrameError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.inner, f) + } +} + +impl From for TryNewCanFrameError { + fn from(_value: std::convert::Infallible) -> Self { + unreachable!() + } +} + +impl From for TryNewCanFrameError { + fn from(value: InvalidId) -> Self { + Self { + inner: TryNewCanFrameErrorInner::InvalidId(value), + } + } +} + +impl From for TryNewCanFrameError { + fn from(value: TryIntoCanDataError) -> Self { + Self { + inner: TryNewCanFrameErrorInner::InvalidData(value), } } } + +/// The data length code is too large (maximum possible value is 15). +#[derive(Debug, Clone)] +pub struct InvalidDataLengthCode { + pub(crate) value: u8, +} + +impl std::error::Error for InvalidDataLengthCode {} + +impl std::fmt::Display for InvalidDataLengthCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "invalid data length code: {}, maximum allowed value is 15", self.value) + } +} diff --git a/can-socket/src/filter.rs b/can-socket/src/filter.rs index 38fdf44..a59bd8c 100644 --- a/can-socket/src/filter.rs +++ b/can-socket/src/filter.rs @@ -1,5 +1,9 @@ -use crate::{sys, CanBaseId, CanExtendedId, CanFrame, CanId}; +use crate::{sys, StandardId, ExtendedId, CanFrame, CanId}; +/// A CAN filter. +/// +/// Can be used to have the kernel filter incoming frames before they are delivered to userspace, +/// to avoid unnecessarily waking up just to ignore a frame. #[repr(transparent)] #[derive(Copy, Clone)] pub struct CanFilter { @@ -7,26 +11,26 @@ pub struct CanFilter { } impl CanFilter { - /// Create a new pass-all CAN filter with a base or extended ID. + /// Create a new pass-all CAN filter with a standard or extended ID. /// /// The mask is still set to zero when the filter is created. /// You have to call additional methods to restrict what frames match filter. #[inline] pub const fn new(id: CanId) -> Self { match id { - CanId::Base(id) => Self::new_base(id), + CanId::Standard(id) => Self::new_standard(id), CanId::Extended(id) => Self::new_extended(id), } } - /// Create a new pass-all CAN filter with a base ID. + /// Create a new pass-all CAN filter with a standard ID. /// /// The mask is still set to zero when the filter is created. /// You have to call additional methods to restrict what frames match filter. #[inline] - pub const fn new_base(id: CanBaseId) -> Self { + pub const fn new_standard(id: StandardId) -> Self { Self { - filter: sys::CanFilter::new_base(id), + filter: sys::CanFilter::new_standard(id), } } @@ -35,7 +39,7 @@ impl CanFilter { /// The mask is still set to zero when the filter is created. /// You have to call additional methods to restrict what frames match filter. #[inline] - pub const fn new_extended(id: CanExtendedId) -> Self { + pub const fn new_extended(id: ExtendedId) -> Self { Self { filter: sys::CanFilter::new_extended(id), } @@ -43,7 +47,7 @@ impl CanFilter { /// Restrict the filter to match only frames with the same numerical ID. /// - /// The filter will still accept extended and base frames (if the numerical value is possible for base frames). + /// The filter will still accept extended and standard frames (if the numerical value is possible for standard frames). /// /// Adds to any restrictions already applied to the filter. #[inline] @@ -63,25 +67,25 @@ impl CanFilter { self } - /// Restrict the filter to match only extended IDs or base IDs (depending on the ID the filter was constructed with). + /// Restrict the filter to match only extended IDs or standard IDs (depending on the ID the filter was constructed with). /// /// Adds to any restrictions already applied to the filter. #[inline] #[must_use = "returns a new filter, does not modify the existing filter"] - pub const fn match_base_extended(mut self) -> Self { - self.filter = self.filter.match_base_extended(); + pub const fn match_frame_format(mut self) -> Self { + self.filter = self.filter.match_frame_format(); self } /// Restrict the filter to match only on exact ID matches. /// - /// This means the ID must match exactly, including the fact if it was an extended or base ID. + /// This means the ID must match exactly, including the fact if it was an extended or standard ID. /// /// This is equivalent to: /// ``` /// # use can_socket::CanFilter; /// # fn foo(filter: CanFilter) -> CanFilter { - /// filter.match_id_value().match_base_extended() + /// filter.match_id_value().match_frame_format() /// # } /// ``` /// @@ -136,3 +140,17 @@ impl CanFilter { self.filter.test(&frame.inner) } } + +impl std::fmt::Debug for CanFilter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CanFilter") + .field("id", &format_args!("{:02X}", self.filter.id())) + .field("mask", &format_args!("{:02X}", self.filter.id_mask())) + .field("extended_frames", &self.filter.matches_extended_frames()) + .field("standard_frames", &self.filter.matches_standard_frames()) + .field("data_frames", &self.filter.matches_data_frames()) + .field("rtr_frames", &self.filter.matches_rtr_frames()) + .field("inverted", &self.filter.is_inverted()) + .finish() + } +} diff --git a/can-socket/src/frame.rs b/can-socket/src/frame.rs index c86c23e..7ff8005 100644 --- a/can-socket/src/frame.rs +++ b/can-socket/src/frame.rs @@ -1,5 +1,7 @@ use crate::CanId; +use crate::error; +/// A CAN frame as transmitted over a CAN socket. #[derive(Copy, Clone)] #[repr(transparent)] pub struct CanFrame { @@ -7,30 +9,115 @@ pub struct CanFrame { } impl CanFrame { - pub fn new(id: impl Into, data: &[u8], data_length_code: Option) -> std::io::Result { - Ok(Self { - inner: crate::sys::CanFrame::new(id, data, data_length_code)?, - }) + /// Create a new data from with the given CAN ID and data payload. + /// + /// To create a new data frame with a potentially invalid ID or data payload, + /// use [`Self::try_new()`]. + #[inline] + pub fn new(id: impl Into, data: impl Into) -> Self { + Self { + inner: crate::sys::CanFrame::new(id, &data.into()) + } } - pub fn new_rtr(id: impl Into, data_len: u8) -> std::io::Result { - Ok(Self { - inner: crate::sys::CanFrame::new_rtr(id, data_len)?, - }) + + /// Create a new data from with the given CAN ID and data payload. + /// + /// Will report an error if the ID or data is invalid. + /// + /// You should normally prefer [`Self::new()`] if you can guarantee that the ID and data are valid. + #[inline] + pub fn try_new(id: Id, data: Data) -> Result + where + Id: TryInto, + error::TryNewCanFrameError: From, + Data: TryInto, + error::TryNewCanFrameError: From, + { + Ok(Self::new(id.try_into()?, data.try_into()?)) } + /// Create a new remote transmission request (RTR) frame with a data length code of 0. + /// + /// To set a different data length code, you can call [`Self::set_data_length_code()`] + /// or [`Self::with_data_length_code()`] after constructing the RTR frame. + #[inline] + pub fn new_rtr(id: impl Into) -> Self { + Self { + inner: crate::sys::CanFrame::new_rtr(id), + } + } + + /// Get the CAN ID of the frame. + #[inline] pub fn id(&self) -> CanId { self.inner.id() } + /// Check if this frame is a remote transmission request (an RTR frame). + /// + /// RTR frames represent a request to transmit a value over the CAN bus. + /// However, an application could decide to use RTR frames differently. + /// + /// RTR frames have no associated data. + #[inline] pub fn is_rtr(&self) -> bool { self.inner.is_rtr() } - pub fn data(&self) -> &[u8] { + /// Get the data of the frame. + /// + /// Returns `None` for RTR frames and `Some(data)` for data frames. + #[inline] + pub fn data(&self) -> Option { self.inner.data() } - pub fn data_length_code(&self) -> Option { + /// Set the data length code of the frame. + /// + /// If the data length code is higher than the current data length, + /// additional bytes become available in `data()`. + /// + /// These additional bytes are initialized to `0` on construction of the frame, + /// but they retain their value when reducing and increasing the data length. + /// They also carry over to copied frames. + /// + /// If the data length code is in the range 9 to 15 (inclusive), the actual data length of the frame will be set to 8. + /// However, if the CAN controller supports it, it may preserve the given data length code in the frame header. + #[inline] + pub fn set_data_length_code(&mut self, dlc: u8) -> Result<(), error::InvalidDataLengthCode> { + self.inner.set_data_length_code(dlc) + .map_err(|()| error::InvalidDataLengthCode { value: dlc }) + } + + /// Create a copy the frame with a modified data length code. + /// + /// If the data length code is higher than the current data length, + /// additional bytes become available in `data()`. + /// + /// These additional bytes are initialized to `0` on construction of the frame, + /// but they retain their value when reducing and increasing the data length. + /// They also carry over to copied frames. + /// + /// If the data length code is in the range 9 to 15 (inclusive), the actual data length of the frame will be set to 8. + /// However, if the CAN controller supports it, it may preserve the given data length code in the frame header. + #[inline] + #[must_use = "this function returns a new frame, it does not modify self"] + pub fn with_data_length_code(mut self, dlc: u8) -> Result { + self.set_data_length_code(dlc)?; + Ok(self) + } + + /// Get the data length code of the frame (it may be higher than the number of data bytes in the frame). + /// + /// If this is an RTR frame, it is often used to indicate how much bytes are expected in the response data frame. + /// However, the application is free to use the data length code for a different purpose. + /// + /// The CAN controller may preserve data length codes with a value above 8 (but at most 15). + /// The data length should normally be assumed to be 8 bytes, + /// and application is free to interpret the additional values according to it's own logic. + /// Note that your CAN controller or driver may not preserve data length codes above `8`. + #[inline] + pub fn data_length_code(&self) -> u8 { self.inner.data_length_code() } } @@ -49,16 +136,135 @@ impl std::fmt::Debug for CanFrame { } } +/// The data payload of a CAN frame. +/// +/// Can hold up to 8 bytes. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct CanData { + pub(crate) data: [u8; 8], + pub(crate) len: u8, +} + +impl CanData { + /// Construct a CAN data object from a supported fixed size array. + /// + /// Also allows construction from any other type if it implements [`Into`]. + pub fn new(data: impl Into) -> Self { + data.into() + } + + /// Construct a CAN data object from a supported fixed size array. + /// + /// Also allows construction from any other type if it implements [`Into`]. + pub fn try_new(data: impl TryInto) -> Result { + data.try_into() + } + + /// Get the data as a slice of bytes. + #[inline] + pub fn as_slice(&self) -> &[u8] { + &self.data[..self.len.into()] + } + + /// Get the data as a mutable slice of bytes. + #[inline] + pub fn as_slice_mut(&mut self) -> &mut [u8] { + &mut self.data[..self.len.into()] + } +} + +impl std::fmt::Debug for CanData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self.as_slice(), f) + } +} +impl std::ops::Deref for CanData { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.as_slice() + } +} + +impl std::ops::DerefMut for CanData { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_slice_mut() + } +} + +macro_rules! impl_from_array { + ($n:literal) => { + impl From<[u8; $n]> for CanData { + fn from(value: [u8; $n]) -> Self { + let mut data = [0; 8]; + data[..value.len()].copy_from_slice(&value); + Self { + data, + len: $n, + } + } + } + } +} + +impl_from_array!(0); +impl_from_array!(1); +impl_from_array!(2); +impl_from_array!(3); +impl_from_array!(4); +impl_from_array!(5); +impl_from_array!(6); +impl_from_array!(7); +impl_from_array!(8); + +impl TryFrom<&[u8]> for CanData { + type Error = error::TryIntoCanDataError; + + fn try_from(value: &[u8]) -> Result { + if value.len() > 8 { + Err(error::TryIntoCanDataError { + len: value.len(), + }) + } else { + let mut data = [0; 8]; + data[..value.len()].copy_from_slice(value); + Ok(Self { + data, + len: value.len() as u8, + }) + } + } +} + +impl TryFrom<&Vec> for CanData { + type Error = error::TryIntoCanDataError; + + fn try_from(value: &Vec) -> Result { + value.as_slice().try_into() + } +} + +impl TryFrom<&Box<[u8]>> for CanData { + type Error = error::TryIntoCanDataError; + + fn try_from(value: &Box<[u8]>) -> Result { + let value: &[u8] = value; + value.try_into() + } +} + + #[cfg(test)] mod test { use super::*; - use assert2::{assert, let_assert}; + use assert2::assert; + use crate::can_id; #[test] fn can_frame_is_copy() { - let_assert!(Ok(frame) = CanFrame::new(1u8, &[1, 2, 3, 4], None)); + let frame = CanFrame::new(1u8, [1, 2, 3, 4]); let copy = frame; - assert!(copy.id() == CanId::Base(1.into())); - assert!(copy.data() == &[1, 2, 3, 4]); + assert!(copy.id() == can_id!(1)); + assert!(copy.data() == Some(CanData::new([1, 2, 3, 4]))); } } diff --git a/can-socket/src/id.rs b/can-socket/src/id.rs new file mode 100644 index 0000000..c9251bb --- /dev/null +++ b/can-socket/src/id.rs @@ -0,0 +1,598 @@ +use crate::error::{InvalidId, ParseIdError}; + +/// The highest valid value for a standard CAN ID. +pub const MAX_STANDARD_ID: u16 = 0x7FF; + +/// The highest valid value for an extended CAN ID. +pub const MAX_EXTENDED_ID: u32 = 0x1FFF_FFFF; + +/// Construct an [`CanId`] (standard or extended) that is checked at compile time. +/// +/// You can use any expression that can be evaluated at compile time and results in a `u32`. +/// +/// By default, if the value fits in a standard CAN ID, a standard CAN ID is created. +/// You can also explicitly ask for a standard or extended ID. +/// +/// Usage: +/// ``` +/// # use can_socket::{CanId, can_id}; +/// let id: CanId = can_id!(0x100 | 0x005); +/// assert2::let_assert!(CanId::Standard(id) = id); +/// assert2::assert!(id.as_u16() == 0x105); +/// ``` +/// +/// Force construction of a `CanId::Standard` (does not compile because the ID only fits as an extended ID): +/// ```compile_fail +/// # use can_socket::{CanId, can_id}; +/// let id: CanId = id!(standard: 0x10 << 16 | 0x50); +/// ``` +/// +/// Force construction of a `CanId::Extended`: +/// ``` +/// # use can_socket::{CanId, can_id}; +/// let id: CanId = can_id!(extended: 0x100 | 0x005); +/// assert2::let_assert!(CanId::Extended(id) = id); +/// assert2::assert!(id.as_u32() == 0x105); +/// ``` +#[macro_export] +macro_rules! can_id { + ($n:expr) => { + { + const N: u32 = ($n); + const { ::core::assert!(N <= $crate::MAX_EXTENDED_ID, "invalid CAN ID") }; + unsafe { + if N <= $crate::MAX_STANDARD_ID as u32 { + $crate::CanId::Standard($crate::StandardId::new_unchecked(N as u16)) + } else { + $crate::CanId::Extended($crate::ExtendedId::new_unchecked(N)) + } + } + } + }; + (standard: $n:expr) => { + $crate::CanId::Standard($crate::standard_id!($n)) + }; + (extended: $n:expr) => { + $crate::CanId::Extended($crate::extended_id!($n)) + }; +} + +/// Construct a [`StandardId`] that is checked at compile time. +/// +/// You can use any expression that can be evaluated at compile time and results in a `u16`. +/// +/// Usage: +/// ``` +/// # use assert2::assert; +/// # use can_socket::{StandardId, standard_id}; +/// let id: StandardId = standard_id!(0x100 | 0x005); +/// assert!(id.as_u16() == 0x105); +/// ``` +/// +/// Will not accept invalid IDs: +/// ```compile_fail +/// # use can_socket::{StandardId, standard_id}; +/// let id: StandardId = standard_id!(0x800); +/// ``` +#[macro_export] +macro_rules! standard_id { + ($n:expr) => { + { + #[allow(clippy::all)] + const { ::core::assert!(($n) <= $crate::MAX_STANDARD_ID, "invalid standard CAN ID") }; + unsafe { + $crate::StandardId::new_unchecked($n) + } + } + }; +} + +/// Construct an [`ExtendedId`] that is checked at compile time. +/// +/// You can use any expression that can be evaluated at compile time and results in a `u32`. +/// +/// Usage: +/// ``` +/// # use assert2::assert; +/// # use can_socket::{ExtendedId, extended_id}; +/// let id: ExtendedId = extended_id!(0x10 << 16 | 0x50); +/// assert!(id.as_u32() == 0x10_0050); +/// ``` +/// +/// Will not accept invalid IDs: +/// ```compile_fail +/// # use can_socket::{ExtendedId, extended_id}; +/// let id: ExtendedId = extended_id!(0x2000_0000); +/// ``` +#[macro_export] +macro_rules! extended_id { + ($n:expr) => { + unsafe { + #[allow(clippy::all)] + const { ::core::assert!(($n) <= $crate::MAX_EXTENDED_ID, "invalid extended CAN ID"); }; + $crate::ExtendedId::new_unchecked($n) + } + }; +} + +/// A CAN ID, either standard (11 bit) or extended (29 bits). +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[repr(C)] +pub enum CanId { + /// A standard 11 bit CAN ID. + Standard(StandardId), + + /// An extended 29 bit CAN ID. + Extended(ExtendedId), +} + +/// A standard 11 bit CAN ID. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[repr(transparent)] +pub struct StandardId { + /// The raw ID. + id: u16, +} + +/// An extended 29 bit CAN ID. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[repr(transparent)] +pub struct ExtendedId { + /// The raw ID. + id: u32, +} + +impl CanId { + /// Create a new CAN ID from a raw value. + /// + /// If the value fits in a 11 bit standard CAN ID, + /// this function will return [`Self::Standard`]. + /// a standard ID will be created. + /// + /// Otherwise, if the value fits in a 29 bits extended CAN ID, + /// this function returns [`Self::Extended`]. + /// + /// If the value doesn't fit in either, this function returns an error. + pub const fn new(id: u32) -> Result { + if id <= MAX_STANDARD_ID as u32 { + let id = id as u16; + Ok(Self::Standard(StandardId { id })) + } else { + match ExtendedId::new(id) { + Ok(x) => Ok(Self::Extended(x)), + Err(e) => Err(e) + } + } + } + + /// Create a new standard CAN ID from a raw value. + /// + /// If the value doesn't fit in a standard 11 bit CAN ID, + /// this function returns an error. + pub const fn new_standard(id: u16) -> Result { + match StandardId::new(id) { + Ok(x) => Ok(Self::Standard(x)), + Err(e) => Err(e), + } + } + + /// Create a new extended CAN ID from a raw value. + /// + /// If the value doesn't fit in an extended 29 bit CAN ID, + /// this function returns an error. + pub const fn new_extended(id: u32) -> Result { + match ExtendedId::new(id) { + Ok(x) => Ok(Self::Extended(x)), + Err(e) => Err(e), + } + } + + /// Get the raw value as a `u32`. + pub const fn as_u32(self) -> u32 { + self.to_extended().as_u32() + } + + /// Get `self` as a `StandardId`, or `None` if this is an extended ID. + /// + /// Note: This function always returns `None` if `self` is an extended ID. + /// It doesn't matter if the value would have fit in a [`StandardId`]. + /// + /// Use [`Self::to_standard()`] if you want to try to convert extended IDs to standard IDs. + pub const fn as_standard(self) -> Option { + match self { + Self::Standard(id) => Some(id), + Self::Extended(_) => None, + } + } + + /// Get `self` as an `ExtendedId`, or `None` if this is a standard ID. + /// + /// Use [`Self::to_extended()`] if you want to convert standard IDs to extended IDs. + pub const fn as_extended(self) -> Option { + match self { + Self::Standard(_) => None, + Self::Extended(id) => Some(id), + } + } + + /// Try to convert the ID to a standard ID. + /// + /// Returns an error if the value doesn't fit in a standard ID. + pub const fn to_standard(self) -> Result { + match self { + Self::Standard(id) => Ok(id), + Self::Extended(id) => { + if id.as_u32() <= u16::MAX as u32 { + StandardId::new(id.as_u32() as u16) + } else { + Err(InvalidId { + id: Some(id.as_u32()), + extended: false, + }) + } + } + } + } + + /// Convert the ID to an extended ID. + pub const fn to_extended(self) -> ExtendedId { + match self { + Self::Standard(id) => ExtendedId::from_u16(id.as_u16()), + Self::Extended(id) => id, + } + } +} + +impl StandardId { + /// The maximum valid standard ID. + pub const MAX: Self = Self { id: MAX_STANDARD_ID }; + + /// Try to create a new standard ID from a raw value. + /// + /// Returns an error if the value doesn't fit in 11 bits. + pub const fn new(id: u16) -> Result { + if id <= MAX_STANDARD_ID { + Ok(Self { id }) + } else { + Err(InvalidId { + id: Some(id as u32), + extended: false, + }) + } + } + + /// Create a new standard ID from a `u8`. + /// + /// Note that [`StandardId`] also implements `From`. + /// However, this function is usable in `const` context. + pub const fn from_u8(id: u8) -> Self { + Self { id: id as u16 } + } + + /// Get the raw value as a `u16`. + /// + /// Note that [`StandardId`] also implements `Into`. + /// However, this function is usable in `const` context. + pub const fn as_u16(self) -> u16 { + self.id + } + + /// Create a new standard CAN ID without checking for validity. + /// + /// # Safety + /// The given ID must be a valid standard CAN ID (id <= [`MAX_STANDARD_ID`]). + pub const unsafe fn new_unchecked(id: u16) -> Self { + debug_assert!(id <= MAX_STANDARD_ID); + Self { + id + } + } +} + +impl ExtendedId { + /// The maximum valid extended ID. + pub const MAX: Self = Self { id: MAX_EXTENDED_ID }; + + /// Try to create a new extended ID from a raw value. + /// + /// Returns an error if the value doesn't fit in 29 bits. + pub const fn new(id: u32) -> Result { + if id <= MAX_EXTENDED_ID { + Ok(Self { id }) + } else { + Err(InvalidId { + id: Some(id), + extended: false, + }) + } + } + + /// Create a new extended ID from a `u8`. + /// + /// Note that [`ExtendedId`] also implements `From`. + /// However, this function is usable in `const` context. + pub const fn from_u8(id: u8) -> Self { + Self { id: id as u32 } + } + + /// Create a new extended ID from a `u16`. + /// + /// Note that [`ExtendedId`] also implements `From`. + /// However, this function is usable in `const` context. + pub const fn from_u16(id: u16) -> Self { + Self { id: id as u32 } + } + + /// Get the raw value as a `u32`. + /// + /// Note that [`ExtendedId`] also implements `Into`. + /// However, this function is usable in `const` context. + pub const fn as_u32(self) -> u32 { + self.id + } + + /// Create a new extended CAN ID without checking for validity. + /// + /// # Safety + /// The given ID must be a valid extended CAN ID (id <= [`MAX_EXTENDED_ID`]). + pub const unsafe fn new_unchecked(id: u32) -> Self { + debug_assert!(id <= MAX_EXTENDED_ID); + Self { + id + } + } +} + +impl PartialEq for CanId { + fn eq(&self, other: &StandardId) -> bool { + self.as_standard().is_some_and(|x| x == *other) + } +} + +impl PartialEq for StandardId { + fn eq(&self, other: &CanId) -> bool { + other == self + } +} + +impl PartialEq for CanId { + fn eq(&self, other: &ExtendedId) -> bool { + self.as_extended().is_some_and(|x| x == *other) + } +} + +impl PartialEq for ExtendedId { + fn eq(&self, other: &CanId) -> bool { + other == self + } +} + +impl From for StandardId { + fn from(value: u8) -> Self { + Self { id: value.into() } + } +} + +impl TryFrom for StandardId { + type Error = InvalidId; + + fn try_from(value: u16) -> Result { + Self::new(value) + } +} + +impl TryFrom for StandardId { + type Error = InvalidId; + + fn try_from(value: u32) -> Result { + if value > MAX_STANDARD_ID.into() { + Err(InvalidId { + id: Some(value), + extended: false, + }) + } else { + Ok(Self { id: value as u16 }) + } + } +} + +impl TryFrom for StandardId { + type Error = InvalidId; + + fn try_from(value: ExtendedId) -> Result { + Self::try_from(value.as_u32()) + } +} + +impl TryFrom for StandardId { + type Error = InvalidId; + + fn try_from(value: CanId) -> Result { + Self::try_from(value.as_u32()) + } +} + +impl From for ExtendedId { + fn from(value: u8) -> Self { + Self { id: value.into() } + } +} + +impl From for ExtendedId { + fn from(value: u16) -> Self { + Self { id: value.into() } + } +} + +impl TryFrom for ExtendedId { + type Error = InvalidId; + + fn try_from(value: u32) -> Result { + Self::new(value) + } +} + +impl From for ExtendedId { + fn from(value: StandardId) -> Self { + value.as_u16().into() + } +} + +impl From for ExtendedId { + fn from(value: CanId) -> Self { + value.to_extended() + } +} + +impl From for CanId { + fn from(value: u8) -> Self { + Self::Standard(value.into()) + } +} + +impl From for CanId { + fn from(value: u16) -> Self { + if value <= MAX_STANDARD_ID { + StandardId { id: value }.into() + } else { + ExtendedId::from(value).into() + } + } +} + +impl TryFrom for CanId { + type Error = InvalidId; + + fn try_from(value: u32) -> Result { + Self::new(value) + } +} + +impl From for CanId { + fn from(value: StandardId) -> Self { + Self::Standard(value) + } +} + +impl From for CanId { + fn from(value: ExtendedId) -> Self { + Self::Extended(value) + } +} + +impl std::str::FromStr for StandardId { + type Err = ParseIdError; + + fn from_str(input: &str) -> Result { + let id = parse_number(input) + .map_err(|e| ParseIdError::invalid_format(e, true))?; + let id: u16 = id.try_into() + .map_err(|_| ParseIdError::invalid_value(InvalidId { id: Some(id), extended: false }))?; + let id = id.try_into() + .map_err(|_| ParseIdError::invalid_value(InvalidId { id: Some(id.into()), extended: false }))?; + Ok(id) + } +} + +impl std::str::FromStr for ExtendedId { + type Err = ParseIdError; + + fn from_str(input: &str) -> Result { + let id = parse_number(input) + .map_err(|e| ParseIdError::invalid_format(e, true))?; + let id = id.try_into() + .map_err(|_| ParseIdError::invalid_value(InvalidId { id: Some(id), extended: true }))?; + Ok(id) + } +} + +impl std::str::FromStr for CanId { + type Err = ParseIdError; + + fn from_str(input: &str) -> Result { + let id = parse_number(input) + .map_err(|e| ParseIdError::invalid_format(e, true))?; + let id = id.try_into() + .map_err(|_| ParseIdError::invalid_value(InvalidId { id: Some(id), extended: true }))?; + Ok(id) + } +} + +fn parse_number(input: &str) -> Result { + if let Some(hexadecimal) = input.strip_prefix("0x") { + u32::from_str_radix(hexadecimal, 16) + } else if let Some(octal) = input.strip_prefix("0o") { + u32::from_str_radix(octal, 8) + } else if let Some(binary) = input.strip_prefix("0b") { + u32::from_str_radix(binary, 2) + } else { + input.parse() + } +} + +impl std::fmt::Debug for CanId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Standard(id) => id.fmt(f), + Self::Extended(id) => id.fmt(f), + } + } +} + +impl std::fmt::Debug for StandardId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("StandardId") + .field(&format_args!("0x{:03X}", self.id)) + .finish() + } +} + +impl std::fmt::Debug for ExtendedId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("ExtendedId") + .field(&format_args!("0x{:08X}", self.id)) + .finish() + } +} + +impl std::fmt::LowerHex for StandardId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_u16().fmt(f) + } +} + +impl std::fmt::LowerHex for ExtendedId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_u32().fmt(f) + } +} + +impl std::fmt::LowerHex for CanId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Standard(x) => x.fmt(f), + Self::Extended(x) => x.fmt(f), + } + } +} + +impl std::fmt::UpperHex for StandardId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_u16().fmt(f) + } +} + +impl std::fmt::UpperHex for ExtendedId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.as_u32().fmt(f) + } +} + +impl std::fmt::UpperHex for CanId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Standard(x) => x.fmt(f), + Self::Extended(x) => x.fmt(f), + } + } +} diff --git a/can-socket/src/interface.rs b/can-socket/src/interface.rs index ba336ba..a04b566 100644 --- a/can-socket/src/interface.rs +++ b/can-socket/src/interface.rs @@ -1,26 +1,36 @@ +/// A CAN interface identified by its index. +/// +/// This type is used as a socket address to bind CAN sockets to a specific interface. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] #[repr(transparent)] pub struct CanInterface { + /// The inner `CanInterface`, directly usable as a socket address for a CAN socket. pub(crate) inner: crate::sys::CanInterface, } impl CanInterface { + /// Create a new `CanInterface` from a raw index. + /// + /// Index `0` represents the "bind all" interface. pub fn from_index(index: u32) -> Self { Self { inner: crate::sys::CanInterface::from_index(index), } } + /// Resolve a CAN interface name to a [`CanInterface`]. pub fn from_name(name: &str) -> std::io::Result { Ok(Self { inner: crate::sys::CanInterface::from_name(name)?, }) } + /// Get the index of the interface. pub fn index(&self) -> u32 { self.inner.index() } + /// Look up the name of the interface from the index. pub fn get_name(&self) -> std::io::Result { self.inner.get_name() } diff --git a/can-socket/src/lib.rs b/can-socket/src/lib.rs index 700d5d3..47dee23 100644 --- a/can-socket/src/lib.rs +++ b/can-socket/src/lib.rs @@ -1,16 +1,41 @@ +//! CAN socket +//! +//! This library exposes a [`CanSocket`] and related types, +//! allowing you to communicate over a Controller Area Network (CAN) bus. +//! +//! The is a standard blocking or non-blocking [`CanSocket`], +//! and an asynchronous [`tokio::CanSocket`]. +//! +//! This library uses the `SocketCAN` interface and only works on Linux. +//! +//! Supported features: +//! * Bind sockets to specific interfaces by name or index. +//! * Bind sockets to *all* CAN interfaces at the same time. +//! * Send and receive data frames and RTR frames. +//! * Send and receive standard frames and extended frames. +//! * Setting per-socket filters. +//! * Control over the `loopback` and `recv_own_msgs` options. +//! * Constructing compile-time checked CAN IDs. + +#![cfg_attr(feature = "doc-cfg", feature(doc_cfg))] + +#![warn(missing_docs)] +#![warn(missing_debug_implementations)] + pub mod error; #[cfg(feature = "tokio")] +#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "tokio")))] pub mod tokio; -mod can_id; -pub use can_id::{CanBaseId, CanExtendedId, CanId, MAX_CAN_ID_BASE, MAX_CAN_ID_EXTENDED}; +mod id; +pub use id::{ExtendedId, CanId, StandardId, MAX_EXTENDED_ID, MAX_STANDARD_ID}; mod filter; pub use filter::CanFilter; mod frame; -pub use frame::CanFrame; +pub use frame::{CanFrame, CanData}; mod interface; pub use interface::CanInterface; @@ -29,8 +54,8 @@ pub trait Deadline { impl Deadline for std::time::Duration { /// Get the instant at which the timeout/deadline expires. /// - /// If the `"tokio"` feature is enabled, this uses [`tokio::time::Instant`] to compute the deadline. - /// This means that [`tokio::time::pause()`] and [`tokio::time::advance()`] will work as expected. + /// If the `"tokio"` feature is enabled, this uses [`tokio::time::Instant`][::tokio::time::Instant] to compute the deadline. + /// This means that [`tokio::time::pause()`][::tokio::time::pause] and [`tokio::time::advance()`][::tokio::time::advance] will work as expected. fn deadline(&self) -> std::time::Instant { // Use tokio's `Instant::now()` when the `tokio` feature is enabled. // This ensures that tokio::time::pause() and tokio::time::advance() will work. diff --git a/can-socket/src/socket.rs b/can-socket/src/socket.rs index 80ffc51..f0bc6e1 100644 --- a/can-socket/src/socket.rs +++ b/can-socket/src/socket.rs @@ -1,5 +1,11 @@ use crate::{CanFilter, CanFrame, CanInterface}; +/// A synchronous CAN socket. +/// +/// Used to send and receive [`CanFrame`]'s over the network. +/// +/// Although the socket is synchronous, +/// it can be put into non-blocking mode with [`Self::set_nonblocking()`]. #[repr(transparent)] pub struct CanSocket { inner: crate::sys::Socket, @@ -64,7 +70,7 @@ impl CanSocket { /// Send a frame over the socket. /// /// Note that if this function success, it only means that the kernel accepted the frame for transmission. - /// It does not mean the frame has been sucessfully transmitted over the CAN bus. + /// It does not mean the frame has been successfully transmitted over the CAN bus. pub fn send(&self, frame: &CanFrame) -> std::io::Result<()> { self.inner.send(&frame.inner) } @@ -72,7 +78,7 @@ impl CanSocket { /// Send a frame over a particular interface. /// /// Note that if this function success, it only means that the kernel accepted the frame for transmission. - /// It does not mean the frame has been sucessfully transmitted over the CAN bus. + /// It does not mean the frame has been successfully transmitted over the CAN bus. pub fn send_to(&self, frame: &CanFrame, interface: &CanInterface) -> std::io::Result<()> { self.inner.send_to(&frame.inner, &interface.inner) } diff --git a/can-socket/src/sys/linux.rs b/can-socket/src/sys/linux.rs index 234dab7..f425227 100644 --- a/can-socket/src/sys/linux.rs +++ b/can-socket/src/sys/linux.rs @@ -2,10 +2,10 @@ use filedesc::FileDesc; use std::ffi::{c_int, c_void, CString}; use std::mem::MaybeUninit; -use crate::{CanBaseId, CanExtendedId, CanId}; +use crate::{CanData, CanId, ExtendedId, StandardId}; #[repr(C)] -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone)] #[allow(non_camel_case_types)] struct can_frame { pub can_id: u32, @@ -16,7 +16,6 @@ struct can_frame { pub data: [u8; 8], } -#[derive(Debug)] pub struct Socket { fd: FileDesc, } @@ -27,7 +26,7 @@ pub struct CanFrame { } #[repr(transparent)] -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct CanInterface { index: u32, } @@ -39,55 +38,38 @@ pub struct CanFilter { } impl CanFrame { - pub fn new(id: impl Into, data: &[u8], data_length_code: Option) -> std::io::Result { + pub fn new(id: impl Into, data: &crate::CanData) -> Self { let id = id.into(); - if data.len() > 8 { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "maximum CAN data length is 8 bytes")); - } - if let Some(data_length_code) = data_length_code { - if data.len() != 8 { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "data_length_code can only be set if data length is 8")); - } - if !(9..=15).contains(&data_length_code) { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "data_length_code must be in the range 9 to 15 (inclusive)")); - } - } - let mut inner: can_frame = unsafe { std::mem::zeroed() }; inner.can_id = match id { CanId::Extended(x) => x.as_u32() | libc::CAN_EFF_FLAG, - CanId::Base(x) => x.as_u16().into(), + CanId::Standard(x) => x.as_u16().into(), }; inner.can_dlc = data.len() as u8; - inner.len8_dlc = data_length_code.unwrap_or(0); inner.data[..data.len()].copy_from_slice(data); - Ok(Self { inner }) + Self { inner } } - /// Create a new RTR (request-to-read) frame. - pub fn new_rtr(id: impl Into, data_len: u8) -> std::io::Result { + pub fn new_rtr(id: impl Into) -> Self { let id = id.into(); - if data_len > 8 { - return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "maximum CAN data length is 8 bytes")); - } let mut inner: can_frame = unsafe { std::mem::zeroed() }; inner.can_id = match id { CanId::Extended(x) => x.as_u32() | libc::CAN_EFF_FLAG, - CanId::Base(x) => x.as_u16().into(), + CanId::Standard(x) => x.as_u16().into(), }; inner.can_id |= libc::CAN_RTR_FLAG; - inner.can_dlc = data_len; + inner.can_dlc = 0; inner.len8_dlc = 0; - Ok(Self { inner }) + Self { inner } } pub fn id(&self) -> CanId { // Unwrap should be fine: the kernel should never give us an invalid CAN ID, // and the Rust constructor doesn't allow it. if self.inner.can_id & libc::CAN_EFF_FLAG == 0 { - CanId::new_base((self.inner.can_id & libc::CAN_SFF_MASK) as u16).unwrap() + CanId::new_standard((self.inner.can_id & libc::CAN_SFF_MASK) as u16).unwrap() } else { CanId::new_extended(self.inner.can_id & libc::CAN_EFF_MASK).unwrap() } @@ -97,15 +79,35 @@ impl CanFrame { self.inner.can_id & libc::CAN_RTR_FLAG != 0 } - pub fn data(&self) -> &[u8] { - &self.inner.data[..self.inner.can_dlc as usize] + pub fn data(&self) -> Option { + if self.is_rtr() { + None + } else { + Some(CanData { + data: self.inner.data, + len: self.inner.can_dlc, + }) + } } - pub fn data_length_code(&self) -> Option { - if self.inner.len8_dlc > 0 { - Some(self.inner.len8_dlc) + pub fn set_data_length_code(&mut self, dlc: u8) -> Result<(), ()> { + if dlc > 15 { + return Err(()); + } + self.inner.can_dlc = dlc.clamp(0, 8); + if dlc > 8 { + self.inner.len8_dlc = dlc; } else { - None + self.inner.len8_dlc = 0; + } + Ok(()) + } + + pub fn data_length_code(&self) -> u8 { + if self.inner.can_dlc == 8 && self.inner.len8_dlc > 8 { + self.inner.len8_dlc + } else { + self.inner.can_dlc } } @@ -344,7 +346,7 @@ impl Socket { } impl CanFilter { - pub const fn new_base(id: CanBaseId) -> Self { + pub const fn new_standard(id: StandardId) -> Self { Self { filter: libc::can_filter { can_id: id.as_u16() as u32, @@ -353,7 +355,7 @@ impl CanFilter { } } - pub const fn new_extended(id: CanExtendedId) -> Self { + pub const fn new_extended(id: ExtendedId) -> Self { Self { filter: libc::can_filter { can_id: id.as_u32(), @@ -368,6 +370,38 @@ impl CanFilter { self } + pub const fn id(self) -> u32 { + self.filter.can_id & libc::CAN_EFF_MASK + } + + pub const fn id_mask(self) -> u32 { + self.filter.can_mask & libc::CAN_EFF_MASK + } + + pub const fn matches_rtr_frames(self) -> bool { + let rtr_unmasked = self.filter.can_mask & libc::CAN_RTR_FLAG != 0; + let is_rtr = self.filter.can_id & libc::CAN_RTR_FLAG != 0; + !rtr_unmasked || is_rtr + } + + pub const fn matches_data_frames(self) -> bool { + let rtr_unmasked = self.filter.can_mask & libc::CAN_RTR_FLAG != 0; + let is_rtr = self.filter.can_id & libc::CAN_RTR_FLAG != 0; + !rtr_unmasked || !is_rtr + } + + pub const fn matches_standard_frames(self) -> bool { + let frame_type_unmasked = self.filter.can_mask & libc::CAN_EFF_FLAG != 0; + let is_extended = self.filter.can_id & libc::CAN_EFF_FLAG != 0; + !frame_type_unmasked || !is_extended + } + + pub const fn matches_extended_frames(self) -> bool { + let frame_type_unmasked = self.filter.can_mask & libc::CAN_EFF_FLAG != 0; + let is_extended = self.filter.can_id & libc::CAN_EFF_FLAG != 0; + !frame_type_unmasked || is_extended + } + #[must_use = "returns a new filter, does not modify the existing filter"] pub const fn match_id_mask(mut self, mask: u32) -> Self { self.filter.can_mask |= mask & libc::CAN_EFF_MASK; @@ -375,7 +409,7 @@ impl CanFilter { } #[must_use = "returns a new filter, does not modify the existing filter"] - pub const fn match_base_extended(mut self) -> Self { + pub const fn match_frame_format(mut self) -> Self { self.filter.can_mask |= libc::CAN_EFF_FLAG; self } diff --git a/can-socket/src/sys/mod.rs b/can-socket/src/sys/mod.rs index d3bd9c3..a6d8bed 100644 --- a/can-socket/src/sys/mod.rs +++ b/can-socket/src/sys/mod.rs @@ -2,4 +2,4 @@ mod linux; #[cfg(target_os = "linux")] -pub use linux::*; +pub(crate) use linux::*; diff --git a/can-socket/src/tokio/mod.rs b/can-socket/src/tokio/mod.rs new file mode 100644 index 0000000..31356c7 --- /dev/null +++ b/can-socket/src/tokio/mod.rs @@ -0,0 +1,4 @@ +//! Support for [`tokio`]. + +mod socket; +pub use socket::CanSocket; diff --git a/can-socket/src/tokio.rs b/can-socket/src/tokio/socket.rs similarity index 99% rename from can-socket/src/tokio.rs rename to can-socket/src/tokio/socket.rs index f52d7a6..4ef9696 100644 --- a/can-socket/src/tokio.rs +++ b/can-socket/src/tokio/socket.rs @@ -6,6 +6,7 @@ use crate::CanFrame; use crate::CanInterface; use crate::Deadline; +/// An asynchronous CAN socket for `tokio`. pub struct CanSocket { io: AsyncFd, } diff --git a/can-socket/tests/can_id.rs b/can-socket/tests/can_id.rs index ea6eaa0..87f01da 100644 --- a/can-socket/tests/can_id.rs +++ b/can-socket/tests/can_id.rs @@ -1,28 +1,28 @@ use assert2::{assert, let_assert}; -use can_socket::{base_id, extended_id, id, CanId}; +use can_socket::{standard_id, extended_id, can_id, CanId}; #[test] fn id_macro_works() { - let_assert!(CanId::Base(id) = id!(5)); + let_assert!(CanId::Standard(id) = can_id!(5)); assert!(id.as_u16() == 5); - let_assert!(CanId::Base(id) = id!(base: 5)); + let_assert!(CanId::Standard(id) = can_id!(standard: 5)); assert!(id.as_u16() == 5); - let_assert!(CanId::Extended(id) = id!(extended: 5)); + let_assert!(CanId::Extended(id) = can_id!(extended: 5)); assert!(id.as_u32() == 5); - let_assert!(CanId::Extended(id) = id!(0x10 << 16 | 0x50)); + let_assert!(CanId::Extended(id) = can_id!(0x10 << 16 | 0x50)); assert!(id.as_u32() == 0x10_0050); } #[test] -fn base_id_macro_works() { - let id = base_id!(5); +fn standard_id_macro_works() { + let id = standard_id!(5); assert!(id.as_u16() == 5); - let id = base_id!(can_socket::MAX_CAN_ID_BASE); - assert!(id.as_u16() == can_socket::MAX_CAN_ID_BASE); + let id = standard_id!(can_socket::MAX_STANDARD_ID); + assert!(id.as_u16() == can_socket::MAX_STANDARD_ID); } #[test] diff --git a/can-socket/tests/compile-fail/base_can_id_negative.rs b/can-socket/tests/compile-fail/base_can_id_negative.rs deleted file mode 100644 index 2859c79..0000000 --- a/can-socket/tests/compile-fail/base_can_id_negative.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let _id = can_socket::base_id!(-2); -} diff --git a/can-socket/tests/compile-fail/base_can_id_too_high.rs b/can-socket/tests/compile-fail/base_can_id_too_high.rs deleted file mode 100644 index 4ee7468..0000000 --- a/can-socket/tests/compile-fail/base_can_id_too_high.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - let id = can_socket::base_id!(0x800); - println!("{id:?}"); -} diff --git a/can-socket/tests/compile-fail/base_can_id_too_high.stderr b/can-socket/tests/compile-fail/base_can_id_too_high.stderr deleted file mode 100644 index bd00588..0000000 --- a/can-socket/tests/compile-fail/base_can_id_too_high.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0080]: evaluation of `main::{constant#0}` failed - --> tests/compile-fail/base_can_id_too_high.rs:2:11 - | -2 | let id = can_socket::base_id!(0x800); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid base CAN ID', $DIR/tests/compile-fail/base_can_id_too_high.rs:2:14 - | - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::base_id` (in Nightly builds, run with -Z macro-backtrace for more info) - -note: erroneous constant encountered - --> tests/compile-fail/base_can_id_too_high.rs:2:11 - | -2 | let id = can_socket::base_id!(0x800); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this note originates in the macro `can_socket::base_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/can-socket/tests/compile-fail/can_id_negative1.rs b/can-socket/tests/compile-fail/can_id_negative1.rs deleted file mode 100644 index 6359724..0000000 --- a/can-socket/tests/compile-fail/can_id_negative1.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let _id = can_socket::id!(-2); -} diff --git a/can-socket/tests/compile-fail/can_id_negative1.stderr b/can-socket/tests/compile-fail/can_id_negative1.stderr deleted file mode 100644 index bf51c73..0000000 --- a/can-socket/tests/compile-fail/can_id_negative1.stderr +++ /dev/null @@ -1,32 +0,0 @@ -error[E0277]: the trait bound `u32: Neg` is not satisfied - --> tests/compile-fail/can_id_negative1.rs:2:28 - | -2 | let _id = can_socket::id!(-2); - | ^^ the trait `Neg` is not implemented for `u32` - | - = help: the following other types implement trait `Neg`: - &f128 - &f16 - &f32 - &f64 - &i128 - &i16 - &i32 - &i64 - and $N others - -error[E0600]: cannot apply unary operator `-` to type `u32` - --> tests/compile-fail/can_id_negative1.rs:2:28 - | -2 | let _id = can_socket::id!(-2); - | ^^ cannot apply unary operator `-` - | - = note: unsigned values cannot be negated - -error[E0600]: cannot apply unary operator `-` to type `u16` - --> tests/compile-fail/can_id_negative1.rs:2:28 - | -2 | let _id = can_socket::id!(-2); - | ^^ cannot apply unary operator `-` - | - = note: unsigned values cannot be negated diff --git a/can-socket/tests/compile-fail/can_id_negative2.rs b/can-socket/tests/compile-fail/can_id_negative2.rs deleted file mode 100644 index 1611105..0000000 --- a/can-socket/tests/compile-fail/can_id_negative2.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let _id = can_socket::id!(base: -2); -} diff --git a/can-socket/tests/compile-fail/can_id_negative3.rs b/can-socket/tests/compile-fail/can_id_negative3.rs deleted file mode 100644 index a78968d..0000000 --- a/can-socket/tests/compile-fail/can_id_negative3.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let _id = can_socket::id!(extended: -2); -} diff --git a/can-socket/tests/compile-fail/can_id_too_high1.rs b/can-socket/tests/compile-fail/can_id_too_high1.rs deleted file mode 100644 index a5278a9..0000000 --- a/can-socket/tests/compile-fail/can_id_too_high1.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let _id = can_socket::id!(0x2000_0000); -} diff --git a/can-socket/tests/compile-fail/can_id_too_high1.stderr b/can-socket/tests/compile-fail/can_id_too_high1.stderr deleted file mode 100644 index de575ae..0000000 --- a/can-socket/tests/compile-fail/can_id_too_high1.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0080]: evaluation of `main::{constant#0}` failed - --> tests/compile-fail/can_id_too_high1.rs:2:12 - | -2 | let _id = can_socket::id!(0x2000_0000); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid CAN ID', $DIR/tests/compile-fail/can_id_too_high1.rs:2:15 - | - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::id` (in Nightly builds, run with -Z macro-backtrace for more info) - -note: erroneous constant encountered - --> tests/compile-fail/can_id_too_high1.rs:2:12 - | -2 | let _id = can_socket::id!(0x2000_0000); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this note originates in the macro `can_socket::id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/can-socket/tests/compile-fail/can_id_too_high2.rs b/can-socket/tests/compile-fail/can_id_too_high2.rs deleted file mode 100644 index 47f509e..0000000 --- a/can-socket/tests/compile-fail/can_id_too_high2.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let _id = can_socket::id!(base: 0x800); -} diff --git a/can-socket/tests/compile-fail/can_id_too_high2.stderr b/can-socket/tests/compile-fail/can_id_too_high2.stderr deleted file mode 100644 index 7cc600d..0000000 --- a/can-socket/tests/compile-fail/can_id_too_high2.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0080]: evaluation of `main::{constant#0}` failed - --> tests/compile-fail/can_id_too_high2.rs:2:12 - | -2 | let _id = can_socket::id!(base: 0x800); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid base CAN ID', $DIR/tests/compile-fail/can_id_too_high2.rs:2:15 - | - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::id` (in Nightly builds, run with -Z macro-backtrace for more info) - -note: erroneous constant encountered - --> tests/compile-fail/can_id_too_high2.rs:2:12 - | -2 | let _id = can_socket::id!(base: 0x800); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this note originates in the macro `$crate::base_id` which comes from the expansion of the macro `can_socket::id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/can-socket/tests/compile-fail/can_id_too_high3.rs b/can-socket/tests/compile-fail/can_id_too_high3.rs deleted file mode 100644 index b947684..0000000 --- a/can-socket/tests/compile-fail/can_id_too_high3.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - let _id = can_socket::id!(extended: 0x2000_0000); -} diff --git a/can-socket/tests/compile-fail/can_id_too_high3.stderr b/can-socket/tests/compile-fail/can_id_too_high3.stderr deleted file mode 100644 index feb7bc8..0000000 --- a/can-socket/tests/compile-fail/can_id_too_high3.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0080]: evaluation of `main::{constant#0}` failed - --> tests/compile-fail/can_id_too_high3.rs:2:12 - | -2 | let _id = can_socket::id!(extended: 0x2000_0000); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid extended CAN ID', $DIR/tests/compile-fail/can_id_too_high3.rs:2:15 - | - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::id` (in Nightly builds, run with -Z macro-backtrace for more info) - -note: erroneous constant encountered - --> tests/compile-fail/can_id_too_high3.rs:2:12 - | -2 | let _id = can_socket::id!(extended: 0x2000_0000); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: this note originates in the macro `$crate::extended_id` which comes from the expansion of the macro `can_socket::id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/can-socket/tests/compile-fail/extended_can_id_negative.rs b/can-socket/tests/compile-fail/extended_id_negative.rs similarity index 100% rename from can-socket/tests/compile-fail/extended_can_id_negative.rs rename to can-socket/tests/compile-fail/extended_id_negative.rs diff --git a/can-socket/tests/compile-fail/extended_can_id_negative.stderr b/can-socket/tests/compile-fail/extended_id_negative.stderr similarity index 85% rename from can-socket/tests/compile-fail/extended_can_id_negative.stderr rename to can-socket/tests/compile-fail/extended_id_negative.stderr index 194585f..143cb57 100644 --- a/can-socket/tests/compile-fail/extended_can_id_negative.stderr +++ b/can-socket/tests/compile-fail/extended_id_negative.stderr @@ -1,5 +1,5 @@ error[E0277]: the trait bound `u32: Neg` is not satisfied - --> tests/compile-fail/extended_can_id_negative.rs:2:37 + --> tests/compile-fail/extended_id_negative.rs:2:37 | 2 | let _id = can_socket::extended_id!(-2); | ^^ the trait `Neg` is not implemented for `u32` @@ -16,7 +16,7 @@ error[E0277]: the trait bound `u32: Neg` is not satisfied and $N others error[E0600]: cannot apply unary operator `-` to type `u32` - --> tests/compile-fail/extended_can_id_negative.rs:2:37 + --> tests/compile-fail/extended_id_negative.rs:2:37 | 2 | let _id = can_socket::extended_id!(-2); | ^^ cannot apply unary operator `-` diff --git a/can-socket/tests/compile-fail/extended_can_id_too_high.rs b/can-socket/tests/compile-fail/extended_id_too_high.rs similarity index 100% rename from can-socket/tests/compile-fail/extended_can_id_too_high.rs rename to can-socket/tests/compile-fail/extended_id_too_high.rs diff --git a/can-socket/tests/compile-fail/extended_can_id_too_high.stderr b/can-socket/tests/compile-fail/extended_id_too_high.stderr similarity index 82% rename from can-socket/tests/compile-fail/extended_can_id_too_high.stderr rename to can-socket/tests/compile-fail/extended_id_too_high.stderr index 14bd329..1466dbc 100644 --- a/can-socket/tests/compile-fail/extended_can_id_too_high.stderr +++ b/can-socket/tests/compile-fail/extended_id_too_high.stderr @@ -1,13 +1,13 @@ error[E0080]: evaluation of `main::{constant#0}` failed - --> tests/compile-fail/extended_can_id_too_high.rs:2:12 + --> tests/compile-fail/extended_id_too_high.rs:2:12 | 2 | let _id = can_socket::extended_id!(0x2000_0000); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid extended CAN ID', $DIR/tests/compile-fail/extended_can_id_too_high.rs:2:15 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid extended CAN ID', $DIR/tests/compile-fail/extended_id_too_high.rs:2:15 | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::extended_id` (in Nightly builds, run with -Z macro-backtrace for more info) note: erroneous constant encountered - --> tests/compile-fail/extended_can_id_too_high.rs:2:12 + --> tests/compile-fail/extended_id_too_high.rs:2:12 | 2 | let _id = can_socket::extended_id!(0x2000_0000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/can-socket/tests/compile-fail/id_negative1.rs b/can-socket/tests/compile-fail/id_negative1.rs new file mode 100644 index 0000000..5322bf1 --- /dev/null +++ b/can-socket/tests/compile-fail/id_negative1.rs @@ -0,0 +1,3 @@ +fn main() { + let _id = can_socket::can_id!(-2); +} diff --git a/can-socket/tests/compile-fail/id_negative1.stderr b/can-socket/tests/compile-fail/id_negative1.stderr new file mode 100644 index 0000000..95271c9 --- /dev/null +++ b/can-socket/tests/compile-fail/id_negative1.stderr @@ -0,0 +1,7 @@ +error[E0600]: cannot apply unary operator `-` to type `u32` + --> tests/compile-fail/id_negative1.rs:2:32 + | +2 | let _id = can_socket::can_id!(-2); + | ^^ cannot apply unary operator `-` + | + = note: unsigned values cannot be negated diff --git a/can-socket/tests/compile-fail/id_negative2.rs b/can-socket/tests/compile-fail/id_negative2.rs new file mode 100644 index 0000000..c976790 --- /dev/null +++ b/can-socket/tests/compile-fail/id_negative2.rs @@ -0,0 +1,3 @@ +fn main() { + let _id = can_socket::can_id!(standard: -2); +} diff --git a/can-socket/tests/compile-fail/base_can_id_negative.stderr b/can-socket/tests/compile-fail/id_negative2.stderr similarity index 51% rename from can-socket/tests/compile-fail/base_can_id_negative.stderr rename to can-socket/tests/compile-fail/id_negative2.stderr index 7054e68..5a28519 100644 --- a/can-socket/tests/compile-fail/base_can_id_negative.stderr +++ b/can-socket/tests/compile-fail/id_negative2.stderr @@ -1,8 +1,8 @@ error[E0277]: the trait bound `u16: Neg` is not satisfied - --> tests/compile-fail/base_can_id_negative.rs:2:33 + --> tests/compile-fail/id_negative2.rs:2:42 | -2 | let _id = can_socket::base_id!(-2); - | ^^ the trait `Neg` is not implemented for `u16` +2 | let _id = can_socket::can_id!(standard: -2); + | ^^ the trait `Neg` is not implemented for `u16` | = help: the following other types implement trait `Neg`: &f128 @@ -16,9 +16,9 @@ error[E0277]: the trait bound `u16: Neg` is not satisfied and $N others error[E0600]: cannot apply unary operator `-` to type `u16` - --> tests/compile-fail/base_can_id_negative.rs:2:33 + --> tests/compile-fail/id_negative2.rs:2:42 | -2 | let _id = can_socket::base_id!(-2); - | ^^ cannot apply unary operator `-` +2 | let _id = can_socket::can_id!(standard: -2); + | ^^ cannot apply unary operator `-` | = note: unsigned values cannot be negated diff --git a/can-socket/tests/compile-fail/id_negative3.rs b/can-socket/tests/compile-fail/id_negative3.rs new file mode 100644 index 0000000..04ea8e5 --- /dev/null +++ b/can-socket/tests/compile-fail/id_negative3.rs @@ -0,0 +1,3 @@ +fn main() { + let _id = can_socket::can_id!(extended: -2); +} diff --git a/can-socket/tests/compile-fail/can_id_negative3.stderr b/can-socket/tests/compile-fail/id_negative3.stderr similarity index 51% rename from can-socket/tests/compile-fail/can_id_negative3.stderr rename to can-socket/tests/compile-fail/id_negative3.stderr index 4ebe5b9..460824d 100644 --- a/can-socket/tests/compile-fail/can_id_negative3.stderr +++ b/can-socket/tests/compile-fail/id_negative3.stderr @@ -1,8 +1,8 @@ error[E0277]: the trait bound `u32: Neg` is not satisfied - --> tests/compile-fail/can_id_negative3.rs:2:38 + --> tests/compile-fail/id_negative3.rs:2:42 | -2 | let _id = can_socket::id!(extended: -2); - | ^^ the trait `Neg` is not implemented for `u32` +2 | let _id = can_socket::can_id!(extended: -2); + | ^^ the trait `Neg` is not implemented for `u32` | = help: the following other types implement trait `Neg`: &f128 @@ -16,9 +16,9 @@ error[E0277]: the trait bound `u32: Neg` is not satisfied and $N others error[E0600]: cannot apply unary operator `-` to type `u32` - --> tests/compile-fail/can_id_negative3.rs:2:38 + --> tests/compile-fail/id_negative3.rs:2:42 | -2 | let _id = can_socket::id!(extended: -2); - | ^^ cannot apply unary operator `-` +2 | let _id = can_socket::can_id!(extended: -2); + | ^^ cannot apply unary operator `-` | = note: unsigned values cannot be negated diff --git a/can-socket/tests/compile-fail/id_too_high1.rs b/can-socket/tests/compile-fail/id_too_high1.rs new file mode 100644 index 0000000..260f8f1 --- /dev/null +++ b/can-socket/tests/compile-fail/id_too_high1.rs @@ -0,0 +1,3 @@ +fn main() { + let _id = can_socket::can_id!(0x2000_0000); +} diff --git a/can-socket/tests/compile-fail/id_too_high1.stderr b/can-socket/tests/compile-fail/id_too_high1.stderr new file mode 100644 index 0000000..f498a09 --- /dev/null +++ b/can-socket/tests/compile-fail/id_too_high1.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of `main::{constant#0}` failed + --> tests/compile-fail/id_too_high1.rs:2:12 + | +2 | let _id = can_socket::can_id!(0x2000_0000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid CAN ID', $DIR/tests/compile-fail/id_too_high1.rs:2:15 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::can_id` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: erroneous constant encountered + --> tests/compile-fail/id_too_high1.rs:2:12 + | +2 | let _id = can_socket::can_id!(0x2000_0000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this note originates in the macro `can_socket::can_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/can-socket/tests/compile-fail/id_too_high2.rs b/can-socket/tests/compile-fail/id_too_high2.rs new file mode 100644 index 0000000..c30d734 --- /dev/null +++ b/can-socket/tests/compile-fail/id_too_high2.rs @@ -0,0 +1,3 @@ +fn main() { + let _id = can_socket::can_id!(standard: 0x800); +} diff --git a/can-socket/tests/compile-fail/id_too_high2.stderr b/can-socket/tests/compile-fail/id_too_high2.stderr new file mode 100644 index 0000000..6afe300 --- /dev/null +++ b/can-socket/tests/compile-fail/id_too_high2.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of `main::{constant#0}` failed + --> tests/compile-fail/id_too_high2.rs:2:12 + | +2 | let _id = can_socket::can_id!(standard: 0x800); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid standard CAN ID', $DIR/tests/compile-fail/id_too_high2.rs:2:15 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::can_id` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: erroneous constant encountered + --> tests/compile-fail/id_too_high2.rs:2:12 + | +2 | let _id = can_socket::can_id!(standard: 0x800); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this note originates in the macro `$crate::standard_id` which comes from the expansion of the macro `can_socket::can_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/can-socket/tests/compile-fail/id_too_high3.rs b/can-socket/tests/compile-fail/id_too_high3.rs new file mode 100644 index 0000000..3c4be16 --- /dev/null +++ b/can-socket/tests/compile-fail/id_too_high3.rs @@ -0,0 +1,3 @@ +fn main() { + let _id = can_socket::can_id!(extended: 0x2000_0000); +} diff --git a/can-socket/tests/compile-fail/id_too_high3.stderr b/can-socket/tests/compile-fail/id_too_high3.stderr new file mode 100644 index 0000000..8586cb3 --- /dev/null +++ b/can-socket/tests/compile-fail/id_too_high3.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of `main::{constant#0}` failed + --> tests/compile-fail/id_too_high3.rs:2:12 + | +2 | let _id = can_socket::can_id!(extended: 0x2000_0000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid extended CAN ID', $DIR/tests/compile-fail/id_too_high3.rs:2:15 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::can_id` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: erroneous constant encountered + --> tests/compile-fail/id_too_high3.rs:2:12 + | +2 | let _id = can_socket::can_id!(extended: 0x2000_0000); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this note originates in the macro `$crate::extended_id` which comes from the expansion of the macro `can_socket::can_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/can-socket/tests/compile-fail/standard_id_negative.rs b/can-socket/tests/compile-fail/standard_id_negative.rs new file mode 100644 index 0000000..8810561 --- /dev/null +++ b/can-socket/tests/compile-fail/standard_id_negative.rs @@ -0,0 +1,3 @@ +fn main() { + let _id = can_socket::standard_id!(-2); +} diff --git a/can-socket/tests/compile-fail/can_id_negative2.stderr b/can-socket/tests/compile-fail/standard_id_negative.stderr similarity index 51% rename from can-socket/tests/compile-fail/can_id_negative2.stderr rename to can-socket/tests/compile-fail/standard_id_negative.stderr index 85122ed..291a906 100644 --- a/can-socket/tests/compile-fail/can_id_negative2.stderr +++ b/can-socket/tests/compile-fail/standard_id_negative.stderr @@ -1,8 +1,8 @@ error[E0277]: the trait bound `u16: Neg` is not satisfied - --> tests/compile-fail/can_id_negative2.rs:2:34 + --> tests/compile-fail/standard_id_negative.rs:2:37 | -2 | let _id = can_socket::id!(base: -2); - | ^^ the trait `Neg` is not implemented for `u16` +2 | let _id = can_socket::standard_id!(-2); + | ^^ the trait `Neg` is not implemented for `u16` | = help: the following other types implement trait `Neg`: &f128 @@ -16,9 +16,9 @@ error[E0277]: the trait bound `u16: Neg` is not satisfied and $N others error[E0600]: cannot apply unary operator `-` to type `u16` - --> tests/compile-fail/can_id_negative2.rs:2:34 + --> tests/compile-fail/standard_id_negative.rs:2:37 | -2 | let _id = can_socket::id!(base: -2); - | ^^ cannot apply unary operator `-` +2 | let _id = can_socket::standard_id!(-2); + | ^^ cannot apply unary operator `-` | = note: unsigned values cannot be negated diff --git a/can-socket/tests/compile-fail/standard_id_too_high.rs b/can-socket/tests/compile-fail/standard_id_too_high.rs new file mode 100644 index 0000000..47af893 --- /dev/null +++ b/can-socket/tests/compile-fail/standard_id_too_high.rs @@ -0,0 +1,3 @@ +fn main() { + let _id = can_socket::standard_id!(0x800); +} diff --git a/can-socket/tests/compile-fail/standard_id_too_high.stderr b/can-socket/tests/compile-fail/standard_id_too_high.stderr new file mode 100644 index 0000000..7db9d85 --- /dev/null +++ b/can-socket/tests/compile-fail/standard_id_too_high.stderr @@ -0,0 +1,15 @@ +error[E0080]: evaluation of `main::{constant#0}` failed + --> tests/compile-fail/standard_id_too_high.rs:2:12 + | +2 | let _id = can_socket::standard_id!(0x800); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'invalid standard CAN ID', $DIR/tests/compile-fail/standard_id_too_high.rs:2:15 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `can_socket::standard_id` (in Nightly builds, run with -Z macro-backtrace for more info) + +note: erroneous constant encountered + --> tests/compile-fail/standard_id_too_high.rs:2:12 + | +2 | let _id = can_socket::standard_id!(0x800); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this note originates in the macro `can_socket::standard_id` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/can-socket/tests/socket.rs b/can-socket/tests/socket.rs index 0216b1b..2aab6c8 100644 --- a/can-socket/tests/socket.rs +++ b/can-socket/tests/socket.rs @@ -1,7 +1,7 @@ use std::path::Path; use assert2::{assert, let_assert}; -use can_socket::{CanBaseId, CanExtendedId, CanFilter, CanFrame, CanSocket}; +use can_socket::{CanData, CanFilter, CanFrame, CanSocket, ExtendedId, StandardId}; fn random_string(len: usize) -> String { use rand::Rng; @@ -111,11 +111,11 @@ fn can_talk() { assert!(let Ok(()) = socket_a.set_nonblocking(true)); assert!(let Ok(()) = socket_b.set_nonblocking(true)); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, &[1, 2, 3], None).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, [1, 2, 3]))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 1); assert!(frame.is_rtr() == false); - assert!(frame.data() == &[1, 2, 3]); + assert!(frame.data() == Some(CanData::new([1, 2, 3]))); } #[test] @@ -127,7 +127,7 @@ fn can_send_rtr() { assert!(let Ok(()) = socket_a.set_nonblocking(true)); assert!(let Ok(()) = socket_b.set_nonblocking(true)); - assert!(let Ok(()) = socket_a.send(&CanFrame::new_rtr(2u8, 3).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new_rtr(2u8))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 2); assert!(frame.is_rtr() == true); @@ -156,11 +156,11 @@ fn enable_recv_own_message() { assert!(let Ok(()) = socket_a.set_receive_own_messages(true)); assert!(let Ok(true) = socket_a.get_receive_own_messages()); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, &[1, 2, 3], None).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, [1, 2, 3]))); let_assert!(Ok(frame) = socket_a.recv()); assert!(frame.id().as_u32() == 1); assert!(frame.is_rtr() == false); - assert!(frame.data() == &[1, 2, 3]); + assert!(frame.data() == Some(CanData::new([1, 2, 3]))); } #[test] @@ -180,7 +180,7 @@ fn disable_loopback() { // It seems vcan pretends all frames from other sockets are non-local. // So we test by enabling `receive_own_messages` but disabling `loopback` and see if our own message gets dropped as expected. - assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, &[1, 2, 3], None).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, [1, 2, 3]))); let_assert!(Err(e) = socket_a.recv()); assert!(e.kind() == std::io::ErrorKind::WouldBlock); } @@ -198,11 +198,11 @@ fn filter_exact_id() { CanFilter::new(8u8.into()).match_exact_id() ])); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, &[1, 2, 3], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(8u8, &[4, 5, 6], None).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, [1, 2, 3]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(8u8, [4, 5, 6]))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 8); - assert!(frame.data() == [4, 5, 6]); + assert!(frame.data() == Some(CanData::new([4, 5, 6]))); let_assert!(Err(e) = socket_b.recv()); assert!(e.kind() == std::io::ErrorKind::WouldBlock); @@ -221,9 +221,9 @@ fn filter_exact_id_rtr_only() { CanFilter::new(8u8.into()).match_exact_id().match_rtr_only(), ])); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, &[1, 2, 3], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(8u8, &[4, 5, 6], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new_rtr(8u8, 8).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(1u8, [1, 2, 3]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(8u8, [4, 5, 6]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new_rtr(8u8))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 8); assert!(frame.is_rtr()); @@ -242,14 +242,14 @@ fn filter_id_type() { assert!(let Ok(()) = socket_b.set_nonblocking(true)); assert!(let Ok(()) = socket_b.set_filters(&[ - CanFilter::new_base(0.into()).match_base_extended(), + CanFilter::new_standard(0.into()).match_frame_format(), ])); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(CanExtendedId::from(5u8), &[1], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(CanBaseId::from(6), &[2], None).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(ExtendedId::from(5u8), [1]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(StandardId::from(6), [2]))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 6); - assert!(frame.data() == [2]); + assert!(frame.data() == Some(CanData::new([2]))); let_assert!(Err(e) = socket_b.recv()); assert!(e.kind() == std::io::ErrorKind::WouldBlock); @@ -268,17 +268,17 @@ fn filter_mask() { CanFilter::new_extended(0x1200u16.into()).match_id_mask(0xFFFFFF00), ])); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1300u16, &[1], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1200u16, &[2], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x12FFu16, &[3], None).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1300u16, [1]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1200u16, [2]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x12FFu16, [3]))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 0x1200); - assert!(frame.data() == [2]); + assert!(frame.data() == Some(CanData::new([2]))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 0x12FF); - assert!(frame.data() == [3]); + assert!(frame.data() == Some(CanData::new([3]))); let_assert!(Err(e) = socket_b.recv()); assert!(e.kind() == std::io::ErrorKind::WouldBlock); @@ -298,23 +298,23 @@ fn multiple_filters() { CanFilter::new_extended(0x2000u16.into()).match_id_mask(0xFFFFFF00), ])); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1300u16, &[1], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1200u16, &[2], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x12FFu16, &[3], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1900u16, &[4], None).unwrap())); - assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x2002u16, &[5], None).unwrap())); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1300u16, [1]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1200u16, [2]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x12FFu16, [3]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x1900u16, [4]))); + assert!(let Ok(()) = socket_a.send(&CanFrame::new(0x2002u16, [5]))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 0x1200); - assert!(frame.data() == [2]); + assert!(frame.data() == Some(CanData::new([2]))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 0x12FF); - assert!(frame.data() == [3]); + assert!(frame.data() == Some(CanData::new([3]))); let_assert!(Ok(frame) = socket_b.recv()); assert!(frame.id().as_u32() == 0x2002); - assert!(frame.data() == [5]); + assert!(frame.data() == Some(CanData::new([5]))); let_assert!(Err(e) = socket_b.recv()); assert!(e.kind() == std::io::ErrorKind::WouldBlock); diff --git a/canopen-tokio/examples/simple-motor-controller.rs b/canopen-tokio/examples/simple-motor-controller.rs index 6e76e72..130abd7 100644 --- a/canopen-tokio/examples/simple-motor-controller.rs +++ b/canopen-tokio/examples/simple-motor-controller.rs @@ -209,7 +209,7 @@ async fn do_loop(socket: &mut CanOpenSocket, options: &Options) -> Result<(), () /// * Send a SYNC command. /// * Receive the feedback PDO. async fn do_step(socket: &mut CanOpenSocket, velocity_target: u16, deadline: Instant) -> Result<(), ()> { - socket.send_frame(&CanFrame::new(CanId::new(0x201).unwrap(), &velocity_target.to_le_bytes(), None).unwrap()) + socket.send_frame(&CanFrame::new(CanId::new(0x201).unwrap(), velocity_target.to_le_bytes())) .await .map_err(|e| log::error!("Failed to send PDO 0x201: {e}"))?; socket.send_sync(None) @@ -234,7 +234,6 @@ struct Status { /// Parse a CAN frame as the expected PDO. fn parse_pdo(input: &CanFrame) -> Result { let id = input.id(); - let data = input.data(); // Check the CAN ID. if id.as_u32() != 0x181 { @@ -243,6 +242,9 @@ fn parse_pdo(input: &CanFrame) -> Result { } // Check the data length. + let data = input.data() + .ok_or_else(|| log::error!("Received an RTR frame, expected a PDO data frame"))?; + if data.len() != 8 { log::error!("PDO message has wrong length, expected 8 data bytes, got {}", data.len()); return Err(()); diff --git a/canopen-tokio/src/id.rs b/canopen-tokio/src/id.rs index 3179c2b..ab14e8b 100644 --- a/canopen-tokio/src/id.rs +++ b/canopen-tokio/src/id.rs @@ -1,6 +1,6 @@ //! CANopen extensions for CAN IDs. -use can_socket::CanBaseId; +use can_socket::StandardId; /// CANopen extension for [`CanBaseId`]. /// @@ -19,7 +19,7 @@ pub trait CanBaseIdExt { fn node_id(&self) -> u8; } -impl CanBaseIdExt for CanBaseId { +impl CanBaseIdExt for StandardId { fn function_code(&self) -> u16 { self.as_u16() & (0x0F << 7) } diff --git a/canopen-tokio/src/lib.rs b/canopen-tokio/src/lib.rs index b396699..7dfbad0 100644 --- a/canopen-tokio/src/lib.rs +++ b/canopen-tokio/src/lib.rs @@ -4,7 +4,7 @@ #![warn(missing_debug_implementations)] use can_socket::tokio::CanSocket; -use can_socket::{CanFrame, CanBaseId}; +use can_socket::{CanFrame, StandardId}; use std::num::NonZeroU8; use std::time::{Duration, Instant}; @@ -248,8 +248,8 @@ impl CanOpenSocket { /// /// Messages already in the read queue are not returned. /// If a message does not match the filter, it is added to the read queue. - async fn recv_new_by_can_id(&mut self, can_id: CanBaseId, timeout: Duration) -> std::io::Result> { - self.recv_new_filtered(|frame| !frame.is_rtr() && frame.id().to_base().ok() == Some(can_id), timeout).await + async fn recv_new_by_can_id(&mut self, can_id: StandardId, timeout: Duration) -> std::io::Result> { + self.recv_new_filtered(|frame| !frame.is_rtr() && frame.id().to_standard().is_ok_and(|x| x == can_id), timeout).await } } diff --git a/canopen-tokio/src/nmt.rs b/canopen-tokio/src/nmt.rs index 67efa11..43b9169 100644 --- a/canopen-tokio/src/nmt.rs +++ b/canopen-tokio/src/nmt.rs @@ -3,15 +3,15 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use std::time::Duration; -use can_socket::{CanFrame, CanBaseId}; +use can_socket::{CanFrame, StandardId}; use crate::CanOpenSocket; const NMT_COB_ID: u8 = 0x000; const FUNCTION_HEARTBEAT: u16 = 0x700; -fn heartbeat_id(node_id: u8) -> CanBaseId { - CanBaseId::new(FUNCTION_HEARTBEAT | u16::from(node_id)).unwrap() +fn heartbeat_id(node_id: u8) -> StandardId { + StandardId::new(FUNCTION_HEARTBEAT | u16::from(node_id)).unwrap() } @@ -101,9 +101,8 @@ pub async fn send_nmt_command(bus: &mut CanOpenSocket, node_id: u8, command: Nmt log::debug!("Sending NMT command {command:?} to node 0x{node_id:02X} (timeout {timeout:?})"); let command_frame = CanFrame::new( NMT_COB_ID, - &[command as u8, node_id], - None, - ).unwrap(); + [command as u8, node_id], + ); bus.socket.send(&command_frame) .await .map_err(NmtError::SendFailed)?; @@ -124,10 +123,11 @@ pub async fn send_nmt_command(bus: &mut CanOpenSocket, node_id: u8, command: Nmt /// Parse a heartbeat frame. fn parse_heartbeat(frame: &CanFrame) -> Result { - if frame.data().len() != 1 { + let data = frame.data().ok_or_else(|| NmtError::MalformedResponse)?; + if data.len() != 1 { Err(NmtError::MalformedResponse) } else { - let state = frame.data()[0].try_into() + let state = data[0].try_into() .map_err(|_| NmtError::MalformedResponse)?; Ok(state) } diff --git a/canopen-tokio/src/pdo/write_config.rs b/canopen-tokio/src/pdo/write_config.rs index cc620e9..8fba315 100644 --- a/canopen-tokio/src/pdo/write_config.rs +++ b/canopen-tokio/src/pdo/write_config.rs @@ -123,7 +123,7 @@ pub(crate) async fn write_rpdo_communication_parameters( } let cob_id = match params.cob_id { - CanId::Base(x) => u32::from(x.as_u16()) | (1 << 31), // Keep bit 31 enabled to so that the PDO is still disabled. + CanId::Standard(x) => u32::from(x.as_u16()) | (1 << 31), // Keep bit 31 enabled to so that the PDO is still disabled. CanId::Extended(x) => x.as_u32() | (1 << 29) | (1 << 31), // For extended CAN IDs also enable bit 29. }; @@ -165,7 +165,7 @@ pub(crate) async fn write_tpdo_communication_parameters( } let cob_id = match params.cob_id { - CanId::Base(x) => u32::from(x.as_u16()) | (1 << 31), // Keep bit 31 enabled to so that the PDO is still disabled. + CanId::Standard(x) => u32::from(x.as_u16()) | (1 << 31), // Keep bit 31 enabled to so that the PDO is still disabled. CanId::Extended(x) => x.as_u32() | (1 << 29) | (1 << 31), // For extended CAN IDs also enable bit 29. }; diff --git a/canopen-tokio/src/sdo/address.rs b/canopen-tokio/src/sdo/address.rs index a4a4fed..31a10e1 100644 --- a/canopen-tokio/src/sdo/address.rs +++ b/canopen-tokio/src/sdo/address.rs @@ -1,4 +1,4 @@ -use can_socket::CanBaseId; +use can_socket::StandardId; /// The address pair to use for SDO transfers. #[derive(Debug, Copy, Clone)] @@ -16,8 +16,8 @@ impl SdoAddress { /// Note that download means "download to server" and upload means "upload from server". /// Most people outside of [CiA](https://can-cia.org/) would have chosen the opposite meaning. pub fn new(command_address: u16, response_address: u16) -> Result { - CanBaseId::new(command_address)?; - CanBaseId::new(response_address)?; + StandardId::new(command_address)?; + StandardId::new(response_address)?; Ok(Self { command_address, response_address, @@ -33,13 +33,13 @@ impl SdoAddress { } /// Get the full CAN ID for a sending SDO commands to a given node ID. - pub fn command_id(self, node_id: u8) -> CanBaseId { - CanBaseId::new(self.command_address | u16::from(node_id)).unwrap() + pub fn command_id(self, node_id: u8) -> StandardId { + StandardId::new(self.command_address | u16::from(node_id)).unwrap() } /// Get the full CAN ID for receiving SDO responses from a given node ID. - pub fn response_id(self, node_id: u8) -> CanBaseId { - CanBaseId::new(self.response_address | u16::from(node_id)).unwrap() + pub fn response_id(self, node_id: u8) -> StandardId { + StandardId::new(self.response_address | u16::from(node_id)).unwrap() } /// Get the COB ID (excluding the node ID) to use for sending the SDO commands. diff --git a/canopen-tokio/src/sdo/download.rs b/canopen-tokio/src/sdo/download.rs index ee33fc4..6d29bfc 100644 --- a/canopen-tokio/src/sdo/download.rs +++ b/canopen-tokio/src/sdo/download.rs @@ -173,7 +173,7 @@ fn make_sdo_expedited_download_command( data.get(3).copied().unwrap_or(0), ]; - CanFrame::new(address.command_id(node_id), &data, None).unwrap() + CanFrame::new(address.command_id(node_id), data) } /// Make an SDO initiate segmented download command. @@ -196,7 +196,7 @@ fn make_sdo_initiate_segmented_download_command( len[3], ]; - CanFrame::new(address.command_id(node_id), &data, None).unwrap() + CanFrame::new(address.command_id(node_id), data) } /// Make an SDO download segment command. @@ -223,13 +223,12 @@ fn make_sdo_segment_download_command( data.get(5).copied().unwrap_or(0), data.get(6).copied().unwrap_or(0), ]; - CanFrame::new(address.command_id(node_id), &data, None).unwrap() + CanFrame::new(address.command_id(node_id), data) } /// Parse an SDO download segment response. fn parse_segment_download_response(frame: &CanFrame, expected_toggle: bool) -> Result<(), SdoError> { - check_server_command(frame, ServerCommand::SegmentDownload)?; - let data = frame.data(); + let data = check_server_command(frame, ServerCommand::SegmentDownload)?; let toggle = data[0] & 0x10 != 0; if toggle != expected_toggle { diff --git a/canopen-tokio/src/sdo/mod.rs b/canopen-tokio/src/sdo/mod.rs index d981302..648c8df 100644 --- a/canopen-tokio/src/sdo/mod.rs +++ b/canopen-tokio/src/sdo/mod.rs @@ -162,26 +162,27 @@ pub enum AbortReason { /// Extract the response command from a CAN frame. /// /// The CAN frame should be an SDO response from an SDO server. -fn get_server_command(frame: &CanFrame) -> Result { - let data = frame.data(); - if data.len() < 8 { - return Err(MalformedResponse::WrongFrameSize(data.len()).into()); - } +fn get_server_command(frame: &CanFrame) -> Result<(ServerCommand, [u8; 8]), SdoError> { + let data = frame.data() + .ok_or_else(|| SdoError::MalformedResponse(MalformedResponse::WrongFrameSize(0)))?; + let data: [u8; 8] = data.as_slice() + .try_into() + .map_err(|_| MalformedResponse::WrongFrameSize(data.len()))?; let command = ServerCommand::try_from(data[0] >> 5) .map_err(|e| MalformedResponse::InvalidServerCommand(e.number))?; - Ok(command) + Ok((command, data)) } /// Check if the response command is the expected one. /// /// Has special handling for [`ServerCommand::AbortTransfer`] to return a [`TransferAborted`] error. -fn check_server_command(frame: &CanFrame, expected: ServerCommand) -> Result<(), SdoError> { - let command = get_server_command(frame)?; +fn check_server_command(frame: &CanFrame, expected: ServerCommand) -> Result<[u8; 8], SdoError> { + let (command, data) = get_server_command(frame)?; if command == expected { - Ok(()) + Ok(data) } else if command == ServerCommand::AbortTransfer { - let reason = u32::from_le_bytes(frame.data()[4..8].try_into().unwrap()); + let reason = u32::from_le_bytes(data[4..8].try_into().unwrap()); let reason = AbortReason::try_from(reason).map_err(|e| e.number); Err(SdoError::TransferAborted(TransferAborted { reason })) } else { @@ -209,7 +210,7 @@ async fn send_abort_transfer_command( reason[2], reason[3], ]; - let command = CanFrame::new(address.command_id(node_id), &data, None).unwrap(); + let command = CanFrame::new(address.command_id(node_id), data); bus.socket.send(&command).await .map_err(SdoError::SendFailed) } diff --git a/canopen-tokio/src/sdo/upload.rs b/canopen-tokio/src/sdo/upload.rs index 1f22add..3b535fc 100644 --- a/canopen-tokio/src/sdo/upload.rs +++ b/canopen-tokio/src/sdo/upload.rs @@ -1,4 +1,4 @@ -use can_socket::CanFrame; +use can_socket::{CanData, CanFrame}; use std::{time::Duration, convert::Infallible}; use crate::{CanOpenSocket, ObjectIndex}; @@ -67,7 +67,7 @@ pub(crate) async fn sdo_upload( log::debug!("Received SDO expedited upload response"); log::debug!("└─ Data: {data:02X?}"); buffer.reserve(data.len())?; - buffer.append(data); + buffer.append(&data); return Ok(data.len()); } InitiateUploadResponse::Segmented(len) => { @@ -101,7 +101,7 @@ pub(crate) async fn sdo_upload( actual: total_len + segment_data.len(), }.into()) } - buffer.append(segment_data); + buffer.append(&segment_data); total_len += segment_data.len(); if complete { @@ -150,7 +150,7 @@ fn make_sdo_initiate_upload_request( object.subindex, 0, 0, 0, 0, ]; - CanFrame::new(sdo.command_id(node_id), &data, None).unwrap() + CanFrame::new(sdo.command_id(node_id), data) } /// Make an SDO upload segment request. @@ -160,23 +160,22 @@ fn make_sdo_upload_segment_request(address: SdoAddress, node_id: u8, toggle: boo 0, 0, 0, 0, 0, 0, 0, ]; - CanFrame::new(address.command_id(node_id), &data, None).unwrap() + CanFrame::new(address.command_id(node_id), data) } /// An SDO initiate upload response. -enum InitiateUploadResponse<'a> { +enum InitiateUploadResponse { /// An expedited response containing the actual data. - Expedited(&'a [u8]), + Expedited(CanData), /// A segmented response containing the length of the data. Segmented(u32), } -impl<'a> InitiateUploadResponse<'a> { +impl InitiateUploadResponse { /// Parse an InitiateUploadResponse from a CAN frame. - fn parse(frame: &'a CanFrame) -> Result { - check_server_command(frame, ServerCommand::InitiateUpload)?; - let data = frame.data(); + fn parse(frame: &CanFrame) -> Result { + let data = check_server_command(frame, ServerCommand::InitiateUpload)?; let n = data[0] >> 2 & 0x03; let expedited = data[0] & 0x02 != 0; @@ -187,11 +186,12 @@ impl<'a> InitiateUploadResponse<'a> { true => 4 - n as usize, false => 4, }; - Ok(InitiateUploadResponse::Expedited(&data[4..][..len])) + let data = CanData::try_from(&data[4..][..len]).unwrap(); + Ok(InitiateUploadResponse::Expedited(data)) } else if !size_set { Err(SdoError::NoExpeditedOrSizeFlag) } else { - let len = u32::from_le_bytes(frame.data()[4..8].try_into().unwrap()); + let len = u32::from_le_bytes(data[4..8].try_into().unwrap()); Ok(InitiateUploadResponse::Segmented(len)) } } @@ -203,9 +203,8 @@ impl<'a> InitiateUploadResponse<'a> { /// /// The boolean indicates if the transfer is completed by this frame. /// The byte slice holds the data of the frame. -fn parse_segment_upload_response(frame: &CanFrame, expected_toggle: bool) -> Result<(bool, &[u8]), SdoError> { - check_server_command(frame, ServerCommand::SegmentUpload)?; - let data = frame.data(); +fn parse_segment_upload_response(frame: &CanFrame, expected_toggle: bool) -> Result<(bool, CanData), SdoError> { + let data = check_server_command(frame, ServerCommand::SegmentUpload)?; let toggle = data[0] & 0x10 != 0; let n = data[0] >> 1 & 0x07; @@ -216,7 +215,8 @@ fn parse_segment_upload_response(frame: &CanFrame, expected_toggle: bool) -> Res return Err(SdoError::InvalidToggleFlag); } - Ok((complete, &data[1..][..len])) + let data = CanData::try_from(&data[1..][..len]).unwrap(); + Ok((complete, data)) } impl UploadBuffer for Vec { diff --git a/canopen-tokio/src/sync.rs b/canopen-tokio/src/sync.rs index 8f8c2af..b10cde1 100644 --- a/canopen-tokio/src/sync.rs +++ b/canopen-tokio/src/sync.rs @@ -15,11 +15,11 @@ pub(crate) async fn send_sync( let frame = match counter { Some(counter) => { log::debug!("└─ Counter: {counter}"); - CanFrame::new(SYNC_DEFAULT_COB_ID, &[counter.get()], None).unwrap() + CanFrame::new(SYNC_DEFAULT_COB_ID, [counter.get()]) }, None => { log::debug!("└─ Counter: no counter"); - CanFrame::new(SYNC_DEFAULT_COB_ID, &[], None).unwrap() + CanFrame::new(SYNC_DEFAULT_COB_ID, []) } };