use crate::{CanErrorFrame, EmbeddedFrame, Frame};
use smallvec::{SmallVec, smallvec};
use std::{convert::TryFrom, error, fmt, io};
use thiserror::Error;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub use libc::{
CAN_ERR_ACK, CAN_ERR_BUSERROR, CAN_ERR_BUSOFF, CAN_ERR_CNT, CAN_ERR_CRTL, CAN_ERR_LOSTARB,
CAN_ERR_PROT, CAN_ERR_RESTARTED, CAN_ERR_TRX, CAN_ERR_TX_TIMEOUT,
};
pub use libc::CAN_ERROR_WARNING_THRESHOLD;
pub use libc::CAN_ERROR_PASSIVE_THRESHOLD;
pub use libc::CAN_BUS_OFF_THRESHOLD;
const KNOWN_ERR_CLASSES: u32 = CAN_ERR_TX_TIMEOUT
| CAN_ERR_LOSTARB
| CAN_ERR_CRTL
| CAN_ERR_PROT
| CAN_ERR_TRX
| CAN_ERR_ACK
| CAN_ERR_BUSOFF
| CAN_ERR_BUSERROR
| CAN_ERR_RESTARTED
| CAN_ERR_CNT;
#[derive(Error, Debug)]
#[cfg_attr(feature = "serde", derive(Deserialize), serde(from = "ErrorRepr"))]
pub enum Error {
#[error(transparent)]
Can(#[from] CanError),
#[error(transparent)]
Io(#[from] io::Error),
#[cfg(feature = "dump")]
#[error(transparent)]
Parser(#[from] crate::dump::ParseError),
#[cfg(feature = "netlink")]
#[error(transparent)]
Nl(#[from] crate::nl::NlError),
}
impl embedded_can::Error for Error {
fn kind(&self) -> embedded_can::ErrorKind {
match self {
Error::Can(err) => err.kind(),
_ => embedded_can::ErrorKind::Other,
}
}
}
impl From<ErrorCause> for Error {
fn from(cause: ErrorCause) -> Self {
Error::Can(CanError::new(cause))
}
}
impl From<CanErrorFrame> for Error {
fn from(frame: CanErrorFrame) -> Self {
Error::Can(CanError::from(frame))
}
}
impl From<io::ErrorKind> for Error {
fn from(kind: io::ErrorKind) -> Self {
Self::from(io::Error::from(kind))
}
}
#[cfg(feature = "netlink")]
fn clone_io_error(e: &io::Error) -> io::Error {
match e.raw_os_error() {
Some(errno) => io::Error::from_raw_os_error(errno),
None => io::Error::new(e.kind(), e.to_string()),
}
}
#[cfg(feature = "netlink")]
impl<T, P> From<neli::err::RouterError<T, P>> for Error
where
T: neli::consts::nl::NlType,
P: fmt::Debug,
{
fn from(e: neli::err::RouterError<T, P>) -> Error {
use neli::err::{RouterError, SocketError};
match e {
RouterError::Io(kind) => Self::Io(io::Error::from(kind)),
RouterError::Socket(SocketError::Io(err)) => Self::Io(clone_io_error(&err)),
other => Self::Nl(other.into()),
}
}
}
impl From<nix::Error> for Error {
fn from(e: nix::Error) -> Self {
Self::Io(io::Error::from(e))
}
}
#[cfg(feature = "netlink")]
impl From<neli::err::SocketError> for Error {
fn from(e: neli::err::SocketError) -> Self {
use neli::err::SocketError;
match e {
SocketError::Io(err) => Self::Io(clone_io_error(&err)),
other => crate::nl::NlError::Msg(other.to_string()).into(),
}
}
}
#[cfg(feature = "netlink")]
impl From<neli::err::DeError> for Error {
fn from(e: neli::err::DeError) -> Self {
use neli::err::DeError;
match e {
DeError::Io(kind) => Self::Io(io::Error::from(kind)),
other => crate::nl::NlError::Msg(other.to_string()).into(),
}
}
}
#[cfg(feature = "netlink")]
impl From<neli::err::SerError> for Error {
fn from(e: neli::err::SerError) -> Self {
crate::nl::NlError::Msg(e.to_string()).into()
}
}
#[cfg(feature = "netlink")]
impl From<neli::rtnl::RtattrBuilderError> for Error {
fn from(e: neli::rtnl::RtattrBuilderError) -> Self {
crate::nl::NlError::Msg(e.to_string()).into()
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub type IoError = io::Error;
pub type IoErrorKind = io::ErrorKind;
pub type IoResult<T> = io::Result<T>;
const NUM_INLINE_CAUSES: usize = 2;
type Causes = SmallVec<[ErrorCause; NUM_INLINE_CAUSES]>;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(into = "Vec<ErrorCause>", try_from = "Vec<ErrorCause>")
)]
pub struct CanError {
causes: Causes,
}
impl CanError {
pub fn new(cause: ErrorCause) -> Self {
Self {
causes: smallvec![cause],
}
}
pub fn from_multiple(first: ErrorCause, rest: impl IntoIterator<Item = ErrorCause>) -> Self {
let mut causes = Causes::new();
causes.push(first);
causes.extend(rest);
Self { causes }
}
pub fn from_iter_checked(causes: impl IntoIterator<Item = ErrorCause>) -> Option<Self> {
let causes: Causes = causes.into_iter().collect();
(!causes.is_empty()).then_some(Self { causes })
}
pub fn first(&self) -> &ErrorCause {
&self.causes[0]
}
pub fn last(&self) -> &ErrorCause {
self.causes.last().unwrap()
}
pub fn len(&self) -> usize {
self.causes.len()
}
pub fn is_empty(&self) -> bool {
false
}
pub fn is_single(&self) -> bool {
self.causes.len() == 1
}
pub fn causes(&self) -> impl Iterator<Item = &ErrorCause> + '_ {
self.causes.iter()
}
pub fn contains_kind(&self, kind: embedded_can::ErrorKind) -> bool {
use embedded_can::Error as _;
self.causes().any(|c| c.kind() == kind)
}
pub fn lost_arbitration(&self) -> Option<u8> {
self.causes().find_map(|c| match c {
ErrorCause::LostArbitration(bit) => Some(*bit),
_ => None,
})
}
pub fn controller(&self) -> Option<ControllerProblems> {
self.causes().find_map(|c| match c {
ErrorCause::Controller(p) => Some(*p),
_ => None,
})
}
pub fn protocol(&self) -> Option<(ViolationTypes, Location)> {
self.causes().find_map(|c| match c {
ErrorCause::Protocol { types, location } => Some((*types, *location)),
_ => None,
})
}
pub fn transceiver(&self) -> Option<(Option<CanHighFault>, Option<CanLowFault>)> {
self.causes().find_map(|c| match c {
ErrorCause::Transceiver { canh, canl } => Some((*canh, *canl)),
_ => None,
})
}
pub fn counters(&self) -> Option<(u8, u8)> {
self.causes().find_map(|c| match c {
ErrorCause::Counters { tx, rx } => Some((*tx, *rx)),
_ => None,
})
}
}
macro_rules! cause_predicate {
($name:ident, $doc:literal, $pat:pat) => {
#[doc = $doc]
pub fn $name(&self) -> bool {
self.causes().any(|c| matches!(c, $pat))
}
};
}
impl CanError {
cause_predicate!(
is_transmit_timeout,
"Whether a TX timeout was reported.",
ErrorCause::TransmitTimeout
);
cause_predicate!(
is_no_ack,
"Whether the frame went unacknowledged.",
ErrorCause::NoAck
);
cause_predicate!(
is_bus_off,
"Whether the controller reported a bus-off condition.",
ErrorCause::BusOff
);
cause_predicate!(
is_bus_error,
"Whether a bus error was reported.",
ErrorCause::BusError
);
cause_predicate!(
is_restarted,
"Whether the controller restarted.",
ErrorCause::Restarted
);
cause_predicate!(
has_counters,
"Whether error counter values were reported.",
ErrorCause::Counters { .. }
);
}
impl From<ErrorCause> for CanError {
fn from(cause: ErrorCause) -> Self {
Self::new(cause)
}
}
impl IntoIterator for CanError {
type Item = ErrorCause;
type IntoIter = smallvec::IntoIter<[ErrorCause; NUM_INLINE_CAUSES]>;
fn into_iter(self) -> Self::IntoIter {
self.causes.into_iter()
}
}
impl<'a> IntoIterator for &'a CanError {
type Item = &'a ErrorCause;
type IntoIter = std::slice::Iter<'a, ErrorCause>;
fn into_iter(self) -> Self::IntoIter {
self.causes.iter()
}
}
impl error::Error for CanError {}
impl fmt::Display for CanError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.first())?;
for cause in self.causes().skip(1) {
write!(f, "; {}", cause)?;
}
Ok(())
}
}
impl embedded_can::Error for CanError {
fn kind(&self) -> embedded_can::ErrorKind {
use embedded_can::ErrorKind;
self.causes()
.map(|c| c.kind())
.find(|k| *k != ErrorKind::Other)
.unwrap_or(ErrorKind::Other)
}
}
impl From<CanErrorFrame> for CanError {
fn from(frame: CanErrorFrame) -> Self {
let bits = frame.error_bits();
let data = frame.data();
let mut causes = Causes::new();
if bits & CAN_ERR_TX_TIMEOUT != 0 {
causes.push(ErrorCause::TransmitTimeout);
}
if bits & CAN_ERR_LOSTARB != 0 {
causes.push(ErrorCause::LostArbitration(data[0]));
}
if bits & CAN_ERR_CRTL != 0 {
push_controller(&mut causes, data[1]);
}
if bits & CAN_ERR_PROT != 0 {
causes.push(ErrorCause::Protocol {
types: ViolationTypes::from_bits_truncate(data[2]),
location: Location::from_raw(data[3]),
});
}
if bits & CAN_ERR_TRX != 0 {
push_transceiver(&mut causes, data[4]);
}
if bits & CAN_ERR_ACK != 0 {
causes.push(ErrorCause::NoAck);
}
if bits & CAN_ERR_BUSOFF != 0 {
causes.push(ErrorCause::BusOff);
}
if bits & CAN_ERR_BUSERROR != 0 {
causes.push(ErrorCause::BusError);
}
if bits & CAN_ERR_RESTARTED != 0 {
causes.push(ErrorCause::Restarted);
}
if bits & CAN_ERR_CNT != 0 {
causes.push(ErrorCause::Counters {
tx: data[6],
rx: data[7],
});
}
let unknown = bits & !KNOWN_ERR_CLASSES;
if unknown != 0 {
causes.push(ErrorCause::Unknown(unknown));
}
if causes.is_empty() {
causes.push(ErrorCause::Unknown(0));
}
Self { causes }
}
}
fn push_controller(causes: &mut Causes, byte: u8) {
causes.push(ErrorCause::Controller(
ControllerProblems::from_bits_truncate(byte),
));
if byte & !ControllerProblems::all().bits() != 0 {
causes.push(ErrorCause::DecodingFailure(
CanErrorDecodingFailure::InvalidControllerProblem,
));
}
}
fn push_transceiver(causes: &mut Causes, byte: u8) {
let mut invalid = false;
let canh = match byte & 0x0F {
0 => None,
h => CanHighFault::try_from(h).map(Some).unwrap_or_else(|_| {
invalid = true;
None
}),
};
let canl = match byte & 0xF0 {
0 => None,
l => CanLowFault::try_from(l).map(Some).unwrap_or_else(|_| {
invalid = true;
None
}),
};
causes.push(ErrorCause::Transceiver { canh, canl });
if invalid {
causes.push(ErrorCause::DecodingFailure(
CanErrorDecodingFailure::InvalidTransceiverError,
));
}
}
#[cfg(feature = "serde")]
#[derive(Debug, Serialize, Deserialize)]
pub enum ErrorRepr {
Can(CanError),
Io {
kind: String,
message: String,
},
#[cfg(feature = "dump")]
Parser(crate::dump::ParseErrorRepr),
#[cfg(feature = "netlink")]
Nl(crate::nl::NlError),
}
#[cfg(feature = "serde")]
pub(crate) fn io_kind_name(kind: io::ErrorKind) -> &'static str {
use io::ErrorKind::*;
match kind {
NotFound => "NotFound",
PermissionDenied => "PermissionDenied",
ConnectionRefused => "ConnectionRefused",
ConnectionReset => "ConnectionReset",
ConnectionAborted => "ConnectionAborted",
NotConnected => "NotConnected",
NetworkDown => "NetworkDown",
NetworkUnreachable => "NetworkUnreachable",
HostUnreachable => "HostUnreachable",
ResourceBusy => "ResourceBusy",
AddrInUse => "AddrInUse",
AddrNotAvailable => "AddrNotAvailable",
BrokenPipe => "BrokenPipe",
AlreadyExists => "AlreadyExists",
WouldBlock => "WouldBlock",
InvalidInput => "InvalidInput",
InvalidData => "InvalidData",
TimedOut => "TimedOut",
WriteZero => "WriteZero",
Interrupted => "Interrupted",
Unsupported => "Unsupported",
UnexpectedEof => "UnexpectedEof",
OutOfMemory => "OutOfMemory",
_ => "Other",
}
}
#[cfg(feature = "serde")]
pub(crate) fn io_kind_from_name(name: &str) -> io::ErrorKind {
use io::ErrorKind::*;
match name {
"NotFound" => NotFound,
"PermissionDenied" => PermissionDenied,
"ConnectionRefused" => ConnectionRefused,
"ConnectionReset" => ConnectionReset,
"ConnectionAborted" => ConnectionAborted,
"NotConnected" => NotConnected,
"NetworkDown" => NetworkDown,
"NetworkUnreachable" => NetworkUnreachable,
"HostUnreachable" => HostUnreachable,
"ResourceBusy" => ResourceBusy,
"AddrInUse" => AddrInUse,
"AddrNotAvailable" => AddrNotAvailable,
"BrokenPipe" => BrokenPipe,
"AlreadyExists" => AlreadyExists,
"WouldBlock" => WouldBlock,
"InvalidInput" => InvalidInput,
"InvalidData" => InvalidData,
"TimedOut" => TimedOut,
"WriteZero" => WriteZero,
"Interrupted" => Interrupted,
"Unsupported" => Unsupported,
"UnexpectedEof" => UnexpectedEof,
"OutOfMemory" => OutOfMemory,
_ => Other,
}
}
#[cfg(feature = "serde")]
impl Serialize for Error {
fn serialize<S: serde::Serializer>(&self, ser: S) -> std::result::Result<S::Ok, S::Error> {
let repr = match self {
Error::Can(err) => ErrorRepr::Can(err.clone()),
Error::Io(e) => ErrorRepr::Io {
kind: io_kind_name(e.kind()).to_string(),
message: e.to_string(),
},
#[cfg(feature = "dump")]
Error::Parser(e) => ErrorRepr::Parser(e.into()),
#[cfg(feature = "netlink")]
Error::Nl(e) => ErrorRepr::Nl(e.clone()),
};
repr.serialize(ser)
}
}
#[cfg(feature = "serde")]
impl From<ErrorRepr> for Error {
fn from(repr: ErrorRepr) -> Self {
match repr {
ErrorRepr::Can(err) => Self::Can(err),
ErrorRepr::Io { kind, message } => {
Self::Io(io::Error::new(io_kind_from_name(&kind), message))
}
#[cfg(feature = "dump")]
ErrorRepr::Parser(repr) => Self::Parser(repr.into()),
#[cfg(feature = "netlink")]
ErrorRepr::Nl(e) => Self::Nl(e),
}
}
}
#[cfg(feature = "serde")]
impl From<CanError> for Vec<ErrorCause> {
fn from(err: CanError) -> Self {
err.into_iter().collect()
}
}
#[cfg(feature = "serde")]
impl TryFrom<Vec<ErrorCause>> for CanError {
type Error = EmptyCanError;
fn try_from(causes: Vec<ErrorCause>) -> std::result::Result<Self, Self::Error> {
Self::from_iter_checked(causes).ok_or(EmptyCanError)
}
}
#[cfg(feature = "serde")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EmptyCanError;
#[cfg(feature = "serde")]
impl fmt::Display for EmptyCanError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a CanError must hold at least one cause")
}
}
#[cfg(feature = "serde")]
impl error::Error for EmptyCanError {}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ErrorCause {
TransmitTimeout,
LostArbitration(u8),
Controller(ControllerProblems),
Protocol {
types: ViolationTypes,
location: Location,
},
Transceiver {
canh: Option<CanHighFault>,
canl: Option<CanLowFault>,
},
NoAck,
BusOff,
BusError,
Restarted,
Counters {
tx: u8,
rx: u8,
},
DecodingFailure(CanErrorDecodingFailure),
Unknown(u32),
}
impl error::Error for ErrorCause {}
impl fmt::Display for ErrorCause {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ErrorCause::*;
match *self {
TransmitTimeout => write!(f, "transmission timeout"),
LostArbitration(n) => write!(f, "arbitration lost after {} bits", n),
Controller(p) => write!(f, "controller problem: {}", p),
Protocol { types, location } => {
write!(f, "protocol violation at {}: {}", location, types)
}
Transceiver { canh, canl } => {
write!(f, "transceiver error: ")?;
match (canh, canl) {
(Some(h), Some(l)) => write!(f, "CAN High, {}; CAN Low, {}", h, l),
(Some(h), None) => write!(f, "CAN High, {}", h),
(None, Some(l)) => write!(f, "CAN Low, {}", l),
(None, None) => write!(f, "unspecified"),
}
}
NoAck => write!(f, "no ack"),
BusOff => write!(f, "bus off"),
BusError => write!(f, "bus error"),
Restarted => write!(f, "restarted"),
Counters { tx, rx } => write!(f, "error counters: tx={}, rx={}", tx, rx),
DecodingFailure(err) => write!(f, "decoding failure: {}", err),
Unknown(bits) => write!(f, "unknown error ({:#x})", bits),
}
}
}
impl embedded_can::Error for ErrorCause {
fn kind(&self) -> embedded_can::ErrorKind {
use embedded_can::ErrorKind;
match *self {
ErrorCause::Controller(p) => {
if p.intersects(ControllerProblems::RX_OVERFLOW | ControllerProblems::TX_OVERFLOW) {
ErrorKind::Overrun
} else {
ErrorKind::Other
}
}
ErrorCause::Protocol { types, .. } => {
if types
.intersects(ViolationTypes::BIT | ViolationTypes::BIT0 | ViolationTypes::BIT1)
{
ErrorKind::Bit
} else if types.contains(ViolationTypes::FORM) {
ErrorKind::Form
} else if types.contains(ViolationTypes::STUFF) {
ErrorKind::Stuff
} else {
ErrorKind::Other
}
}
ErrorCause::NoAck => ErrorKind::Acknowledge,
_ => ErrorKind::Other,
}
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ControllerProblems: u8 {
const RX_OVERFLOW = libc::CAN_ERR_CRTL_RX_OVERFLOW as u8;
const TX_OVERFLOW = libc::CAN_ERR_CRTL_TX_OVERFLOW as u8;
const RX_WARNING = libc::CAN_ERR_CRTL_RX_WARNING as u8;
const TX_WARNING = libc::CAN_ERR_CRTL_TX_WARNING as u8;
const RX_PASSIVE = libc::CAN_ERR_CRTL_RX_PASSIVE as u8;
const TX_PASSIVE = libc::CAN_ERR_CRTL_TX_PASSIVE as u8;
const ACTIVE = libc::CAN_ERR_CRTL_ACTIVE as u8;
}
}
fn write_flag_names<'a>(
f: &mut fmt::Formatter,
names: impl IntoIterator<Item = &'a str>,
unspecified: &str,
) -> fmt::Result {
let mut names = names.into_iter();
match names.next() {
None => f.write_str(unspecified),
Some(first) => {
f.write_str(first)?;
names.try_for_each(|name| write!(f, ", {}", name))
}
}
}
impl fmt::Display for ControllerProblems {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
const NAMED: [(ControllerProblems, &str); 7] = [
(ControllerProblems::RX_OVERFLOW, "receive buffer overflow"),
(ControllerProblems::TX_OVERFLOW, "transmit buffer overflow"),
(ControllerProblems::RX_WARNING, "rx warning"),
(ControllerProblems::TX_WARNING, "tx warning"),
(ControllerProblems::RX_PASSIVE, "rx passive"),
(ControllerProblems::TX_PASSIVE, "tx passive"),
(ControllerProblems::ACTIVE, "back to error active"),
];
let names = NAMED
.into_iter()
.filter(|(flag, _)| self.contains(*flag))
.map(|(_, name)| name);
write_flag_names(f, names, "unspecified controller problem")
}
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ViolationTypes: u8 {
const BIT = libc::CAN_ERR_PROT_BIT as u8;
const FORM = libc::CAN_ERR_PROT_FORM as u8;
const STUFF = libc::CAN_ERR_PROT_STUFF as u8;
const BIT0 = libc::CAN_ERR_PROT_BIT0 as u8;
const BIT1 = libc::CAN_ERR_PROT_BIT1 as u8;
const OVERLOAD = libc::CAN_ERR_PROT_OVERLOAD as u8;
const ACTIVE = libc::CAN_ERR_PROT_ACTIVE as u8;
const TX = libc::CAN_ERR_PROT_TX as u8;
}
}
impl fmt::Display for ViolationTypes {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
const NAMED: [(ViolationTypes, &str); 8] = [
(ViolationTypes::BIT, "single bit error"),
(ViolationTypes::FORM, "frame format error"),
(ViolationTypes::STUFF, "bit stuffing error"),
(ViolationTypes::BIT0, "unable to send dominant bit"),
(ViolationTypes::BIT1, "unable to send recessive bit"),
(ViolationTypes::OVERLOAD, "bus overload"),
(ViolationTypes::ACTIVE, "active error announcement"),
(ViolationTypes::TX, "error on transmission"),
];
let names = NAMED
.into_iter()
.filter(|(flag, _)| self.contains(*flag))
.map(|(_, name)| name);
write_flag_names(f, names, "unspecified")
}
}
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Location {
Unspecified,
StartOfFrame,
Id2821,
Id2018,
SubstituteRtr,
IdentifierExtension,
Id1713,
Id1205,
Id0400,
Rtr,
Reserved1,
Reserved0,
DataLengthCode,
DataSection,
CrcSequence,
CrcDelimiter,
AckSlot,
AckDelimiter,
EndOfFrame,
Intermission,
ActiveErrorFlag,
TolerateDominantBits,
PassiveErrorFlag,
ErrorDelimiter,
OverloadFlag,
Reserved(u8),
}
impl Location {
pub const fn from_raw(val: u8) -> Self {
use Location::*;
match val {
0x00 => Unspecified,
0x02 => Id2821,
0x03 => StartOfFrame,
0x04 => SubstituteRtr,
0x05 => IdentifierExtension,
0x06 => Id2018,
0x07 => Id1713,
0x08 => CrcSequence,
0x09 => Reserved0,
0x0A => DataSection,
0x0B => DataLengthCode,
0x0C => Rtr,
0x0D => Reserved1,
0x0E => Id0400,
0x0F => Id1205,
0x11 => ActiveErrorFlag,
0x12 => Intermission,
0x13 => TolerateDominantBits,
0x16 => PassiveErrorFlag,
0x17 => ErrorDelimiter,
0x18 => CrcDelimiter,
0x19 => AckSlot,
0x1A => EndOfFrame,
0x1B => AckDelimiter,
0x1C => OverloadFlag,
other => Reserved(other),
}
}
pub const fn as_raw(&self) -> u8 {
use Location::*;
match *self {
Unspecified => 0x00,
Id2821 => 0x02,
StartOfFrame => 0x03,
SubstituteRtr => 0x04,
IdentifierExtension => 0x05,
Id2018 => 0x06,
Id1713 => 0x07,
CrcSequence => 0x08,
Reserved0 => 0x09,
DataSection => 0x0A,
DataLengthCode => 0x0B,
Rtr => 0x0C,
Reserved1 => 0x0D,
Id0400 => 0x0E,
Id1205 => 0x0F,
ActiveErrorFlag => 0x11,
Intermission => 0x12,
TolerateDominantBits => 0x13,
PassiveErrorFlag => 0x16,
ErrorDelimiter => 0x17,
CrcDelimiter => 0x18,
AckSlot => 0x19,
EndOfFrame => 0x1A,
AckDelimiter => 0x1B,
OverloadFlag => 0x1C,
Reserved(v) => v,
}
}
}
impl From<u8> for Location {
fn from(val: u8) -> Self {
Self::from_raw(val)
}
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use Location::*;
let msg = match *self {
Unspecified => "unspecified location",
StartOfFrame => "start of frame",
Id2821 => "ID, bits 28-21",
Id2018 => "ID, bits 20-18",
SubstituteRtr => "substitute RTR bit",
IdentifierExtension => "ID, extension",
Id1713 => "ID, bits 17-13",
Id1205 => "ID, bits 12-05",
Id0400 => "ID, bits 04-00",
Rtr => "RTR bit",
Reserved1 => "reserved bit 1",
Reserved0 => "reserved bit 0",
DataLengthCode => "data length code",
DataSection => "data section",
CrcSequence => "CRC sequence",
CrcDelimiter => "CRC delimiter",
AckSlot => "ACK slot",
AckDelimiter => "ACK delimiter",
EndOfFrame => "end of frame",
Intermission => "intermission",
ActiveErrorFlag => "active error flag",
TolerateDominantBits => "tolerate dominant bits",
PassiveErrorFlag => "passive error flag",
ErrorDelimiter => "error delimiter",
OverloadFlag => "overload flag",
Reserved(v) => return write!(f, "reserved location ({:#04x})", v),
};
write!(f, "{}", msg)
}
}
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
#[repr(u8)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CanHighFault {
NoWire = libc::CAN_ERR_TRX_CANH_NO_WIRE as u8,
ShortToBat = libc::CAN_ERR_TRX_CANH_SHORT_TO_BAT as u8,
ShortToVcc = libc::CAN_ERR_TRX_CANH_SHORT_TO_VCC as u8,
ShortToGnd = libc::CAN_ERR_TRX_CANH_SHORT_TO_GND as u8,
}
impl TryFrom<u8> for CanHighFault {
type Error = CanErrorDecodingFailure;
fn try_from(val: u8) -> std::result::Result<Self, Self::Error> {
use CanHighFault::*;
Ok(match val {
0x04 => NoWire,
0x05 => ShortToBat,
0x06 => ShortToVcc,
0x07 => ShortToGnd,
_ => return Err(CanErrorDecodingFailure::InvalidTransceiverError),
})
}
}
impl fmt::Display for CanHighFault {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Self::NoWire => "no wire",
Self::ShortToBat => "short to BAT",
Self::ShortToVcc => "short to VCC",
Self::ShortToGnd => "short to GND",
})
}
}
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
#[repr(u8)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CanLowFault {
NoWire = libc::CAN_ERR_TRX_CANL_NO_WIRE as u8,
ShortToBat = libc::CAN_ERR_TRX_CANL_SHORT_TO_BAT as u8,
ShortToVcc = libc::CAN_ERR_TRX_CANL_SHORT_TO_VCC as u8,
ShortToGnd = libc::CAN_ERR_TRX_CANL_SHORT_TO_GND as u8,
ShortToCanHigh = libc::CAN_ERR_TRX_CANL_SHORT_TO_CANH as u8,
}
impl TryFrom<u8> for CanLowFault {
type Error = CanErrorDecodingFailure;
fn try_from(val: u8) -> std::result::Result<Self, Self::Error> {
use CanLowFault::*;
Ok(match val {
0x40 => NoWire,
0x50 => ShortToBat,
0x60 => ShortToVcc,
0x70 => ShortToGnd,
0x80 => ShortToCanHigh,
_ => return Err(CanErrorDecodingFailure::InvalidTransceiverError),
})
}
}
impl fmt::Display for CanLowFault {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Self::NoWire => "no wire",
Self::ShortToBat => "short to BAT",
Self::ShortToVcc => "short to VCC",
Self::ShortToGnd => "short to GND",
Self::ShortToCanHigh => "short to CAN High",
})
}
}
pub trait ControllerSpecificErrorInformation {
fn get_ctrl_err(&self) -> Option<&[u8]>;
}
impl<T: Frame> ControllerSpecificErrorInformation for T {
fn get_ctrl_err(&self) -> Option<&[u8]> {
let data = self.data();
if data.len() == 8 {
Some(&data[5..])
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CanErrorDecodingFailure {
InvalidControllerProblem,
InvalidTransceiverError,
}
impl error::Error for CanErrorDecodingFailure {}
impl fmt::Display for CanErrorDecodingFailure {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use CanErrorDecodingFailure::*;
let msg = match *self {
InvalidControllerProblem => "not a valid controller problem",
InvalidTransceiverError => "not a valid transceiver error",
};
write!(f, "{}", msg)
}
}
#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ConstructionError {
WrongFrameType,
IDTooLarge,
TooMuchData,
}
impl error::Error for ConstructionError {}
impl fmt::Display for ConstructionError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ConstructionError::*;
let msg = match *self {
WrongFrameType => "Incompatible frame type",
IDTooLarge => "CAN ID too large",
TooMuchData => "Payload is too large",
};
write!(f, "{}", msg)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{CanErrorFrame, Error};
use embedded_can::{Error as _, ErrorKind};
use std::io;
fn frame(bits: u32, data: [u8; 8]) -> CanErrorFrame {
CanErrorFrame::new_error(bits, &data).unwrap()
}
fn decode(bits: u32, data: [u8; 8]) -> Vec<ErrorCause> {
CanError::from(frame(bits, data)).into_iter().collect()
}
#[test]
fn test_errors() {
const KIND: io::ErrorKind = io::ErrorKind::TimedOut;
let err = Error::from(io::Error::from(KIND));
if let Error::Io(ioerr) = err {
assert_eq!(ioerr.kind(), KIND);
} else {
panic!("Wrong error conversion");
}
let err = Error::from(KIND);
if let Error::Io(ioerr) = err {
assert_eq!(ioerr.kind(), KIND);
} else {
panic!("Wrong error conversion");
}
}
#[cfg(feature = "netlink")]
#[test]
fn netlink_error_conversion() {
use crate::nl::NlError;
use neli::{
consts::rtnl::Rtm,
err::{RouterError, SocketError},
rtnl::Ifinfomsg,
};
use std::sync::Arc;
type RtErr = RouterError<Rtm, Ifinfomsg>;
assert!(matches!(
Error::from(RtErr::NoAck),
Error::Nl(NlError::NoAck)
));
assert!(matches!(
Error::from(RtErr::ClosedChannel),
Error::Nl(NlError::ClosedChannel)
));
let err = Error::from(RtErr::Io(io::ErrorKind::PermissionDenied));
match err {
Error::Io(e) => assert_eq!(e.kind(), io::ErrorKind::PermissionDenied),
other => panic!("expected an I/O error, got {other:?}"),
}
let err = Error::from(RtErr::Socket(SocketError::Io(Arc::new(
io::Error::from_raw_os_error(libc::ENODEV),
))));
match err {
Error::Io(e) => assert_eq!(e.raw_os_error(), Some(libc::ENODEV)),
other => panic!("expected an I/O error, got {other:?}"),
}
assert!(matches!(
Error::from(RtErr::new("malformed attribute")),
Error::Nl(NlError::Msg(_))
));
}
#[cfg(feature = "netlink")]
#[test]
fn nix_errno_becomes_an_io_error() {
use crate::nl::CanInterface;
match CanInterface::open("nosuchcan0") {
Err(Error::Io(e)) => assert_eq!(e.raw_os_error(), Some(libc::ENODEV)),
other => panic!("expected an I/O error, got {other:?}"),
}
}
#[cfg(feature = "netlink")]
#[test]
fn netlink_errno_is_actionable() {
use crate::nl::NlError;
let err = NlError::Netlink { errno: libc::EPERM };
assert_eq!(err.errno(), Some(libc::EPERM));
assert_eq!(err.io_kind(), Some(io::ErrorKind::PermissionDenied));
assert_eq!(NlError::NoAck.errno(), None);
assert_eq!(NlError::NoAck.io_kind(), None);
}
#[cfg(feature = "netlink")]
#[test]
fn error_stays_smaller_than_a_router_error() {
use neli::{consts::rtnl::Rtm, err::RouterError, rtnl::Ifinfomsg};
use std::mem::size_of;
type RtErr = RouterError<Rtm, Ifinfomsg>;
assert!(
size_of::<Error>() < size_of::<RtErr>(),
"Error is {} bytes, RouterError is {}",
size_of::<Error>(),
size_of::<RtErr>()
);
}
#[test]
fn error_types_stay_narrow() {
use std::mem::size_of;
assert_eq!(size_of::<ErrorCause>(), 8, "ErrorCause");
assert_eq!(size_of::<CanError>(), 24, "CanError");
assert_eq!(size_of::<Error>(), 32, "Error");
assert_eq!(size_of::<Result<()>>(), 32, "Result<()>");
assert_eq!(
size_of::<SmallVec<[ErrorCause; 1]>>(),
size_of::<SmallVec<[ErrorCause; 2]>>(),
"capacity 2 should cost the same as capacity 1"
);
}
#[cfg(feature = "dump")]
#[test]
fn parse_error_conversion() {
use crate::dump::ParseError;
fn lift() -> Result<()> {
Err(ParseError::InvalidTimestamp)?
}
assert!(matches!(
Error::from(ParseError::InvalidCanFrame),
Error::Parser(ParseError::InvalidCanFrame)
));
assert!(matches!(
lift(),
Err(Error::Parser(ParseError::InvalidTimestamp))
));
}
#[test]
fn non_empty_invariant() {
let err = CanError::new(ErrorCause::BusOff);
assert_eq!(err.len(), 1);
assert!(err.is_single());
assert!(!err.is_empty());
assert_eq!(*err.first(), ErrorCause::BusOff);
assert_eq!(*err.last(), ErrorCause::BusOff);
let err = CanError::from(frame(0, [0; 8]));
assert_eq!(err.len(), 1);
assert_eq!(*err.first(), ErrorCause::Unknown(0));
}
#[test]
fn single_cause_does_not_allocate() {
let err = CanError::new(ErrorCause::BusOff);
assert!(!err.causes.spilled());
}
#[test]
fn two_causes_do_not_allocate() {
let state_change = frame(
CAN_ERR_CRTL | CAN_ERR_CNT,
[
0,
ControllerProblems::RX_WARNING.bits(),
0,
0,
0,
0,
112,
96,
],
);
let err = CanError::from(state_change);
assert_eq!(err.len(), 2, "decoded: {}", err);
assert!(
!err.causes.spilled(),
"the most common error frame must not allocate"
);
let five = frame(
CAN_ERR_LOSTARB | CAN_ERR_CRTL | CAN_ERR_PROT | CAN_ERR_BUSERROR | CAN_ERR_CNT,
[3, 0x0D, 0x86, 0x1C, 0, 0, 200, 190],
);
let err = CanError::from(five);
assert_eq!(err.len(), 5, "decoded: {}", err);
assert!(err.causes.spilled());
}
#[test]
fn multi_class_crtl_and_cnt() {
let mut data = [0u8; 8];
data[1] = ControllerProblems::RX_PASSIVE.bits();
data[6] = 130;
data[7] = 42;
assert_eq!(
decode(CAN_ERR_CRTL | CAN_ERR_CNT, data),
vec![
ErrorCause::Controller(ControllerProblems::RX_PASSIVE),
ErrorCause::Counters { tx: 130, rx: 42 },
]
);
}
#[test]
fn multi_class_prot_and_buserror() {
let mut data = [0u8; 8];
data[2] = ViolationTypes::STUFF.bits();
data[3] = 0x08; assert_eq!(
decode(CAN_ERR_PROT | CAN_ERR_BUSERROR, data),
vec![
ErrorCause::Protocol {
types: ViolationTypes::STUFF,
location: Location::CrcSequence,
},
ErrorCause::BusError,
]
);
}
#[test]
fn unknown_class_bits_trail() {
let causes = decode(CAN_ERR_BUSOFF | 0x400, [0; 8]);
assert_eq!(causes, vec![ErrorCause::BusOff, ErrorCause::Unknown(0x400)]);
}
#[test]
fn class_bit_ordering_is_ascending() {
let mut data = [0u8; 8];
data[0] = 7;
data[1] = ControllerProblems::RX_OVERFLOW.bits();
data[2] = ViolationTypes::BIT.bits();
data[4] = 0x04; data[6] = 1;
data[7] = 2;
let causes = decode(
CAN_ERR_TX_TIMEOUT
| CAN_ERR_LOSTARB
| CAN_ERR_CRTL
| CAN_ERR_PROT
| CAN_ERR_TRX
| CAN_ERR_ACK
| CAN_ERR_BUSOFF
| CAN_ERR_BUSERROR
| CAN_ERR_RESTARTED
| CAN_ERR_CNT,
data,
);
assert_eq!(
causes,
vec![
ErrorCause::TransmitTimeout,
ErrorCause::LostArbitration(7),
ErrorCause::Controller(ControllerProblems::RX_OVERFLOW),
ErrorCause::Protocol {
types: ViolationTypes::BIT,
location: Location::Unspecified,
},
ErrorCause::Transceiver {
canh: Some(CanHighFault::NoWire),
canl: None,
},
ErrorCause::NoAck,
ErrorCause::BusOff,
ErrorCause::BusError,
ErrorCause::Restarted,
ErrorCause::Counters { tx: 1, rx: 2 },
]
);
}
#[test]
fn ctrl_multi_bit_symmetric_warning() {
let mut data = [0u8; 8];
data[1] = 0x0C;
assert_eq!(
decode(CAN_ERR_CRTL, data),
vec![ErrorCause::Controller(
ControllerProblems::RX_WARNING | ControllerProblems::TX_WARNING
)]
);
}
#[test]
fn ctrl_multi_bit_symmetric_passive() {
let mut data = [0u8; 8];
data[1] = 0x30;
assert_eq!(
decode(CAN_ERR_CRTL, data),
vec![ErrorCause::Controller(
ControllerProblems::RX_PASSIVE | ControllerProblems::TX_PASSIVE
)]
);
}
#[test]
fn ctrl_three_bits_sja1000_overrun_plus_warning() {
let mut data = [0u8; 8];
data[1] = 0x0D;
assert_eq!(
decode(CAN_ERR_CRTL, data),
vec![ErrorCause::Controller(
ControllerProblems::RX_OVERFLOW
| ControllerProblems::RX_WARNING
| ControllerProblems::TX_WARNING
)]
);
}
#[test]
fn ctrl_zero_is_unspecified_not_a_failure() {
assert_eq!(
decode(CAN_ERR_CRTL, [0; 8]),
vec![ErrorCause::Controller(ControllerProblems::empty())]
);
}
#[test]
fn ctrl_unclaimed_bit_reports_failure_after_known_bits() {
let mut data = [0u8; 8];
data[1] = 0x81;
assert_eq!(
decode(CAN_ERR_CRTL, data),
vec![
ErrorCause::Controller(ControllerProblems::RX_OVERFLOW),
ErrorCause::DecodingFailure(CanErrorDecodingFailure::InvalidControllerProblem),
]
);
}
#[test]
fn prot_multi_bit_shares_one_location() {
let mut data = [0u8; 8];
data[2] = 0x9E;
data[3] = 0x08;
assert_eq!(
decode(CAN_ERR_PROT, data),
vec![ErrorCause::Protocol {
types: ViolationTypes::FORM
| ViolationTypes::STUFF
| ViolationTypes::BIT0
| ViolationTypes::BIT1
| ViolationTypes::TX,
location: Location::CrcSequence,
}]
);
}
#[test]
fn prot_zero_is_unspecified_not_a_failure() {
let mut data = [0u8; 8];
data[3] = 0x03;
assert_eq!(
decode(CAN_ERR_PROT, data),
vec![ErrorCause::Protocol {
types: ViolationTypes::empty(),
location: Location::StartOfFrame,
}]
);
}
#[test]
fn trx_both_lines_es58x() {
let mut data = [0u8; 8];
data[4] = 0x44;
assert_eq!(
decode(CAN_ERR_TRX, data),
vec![ErrorCause::Transceiver {
canh: Some(CanHighFault::NoWire),
canl: Some(CanLowFault::NoWire),
}]
);
}
#[test]
fn trx_single_line_each_half() {
let mut data = [0u8; 8];
data[4] = 0x05;
assert_eq!(
decode(CAN_ERR_TRX, data),
vec![ErrorCause::Transceiver {
canh: Some(CanHighFault::ShortToBat),
canl: None,
}]
);
data[4] = 0x80;
assert_eq!(
decode(CAN_ERR_TRX, data),
vec![ErrorCause::Transceiver {
canh: None,
canl: Some(CanLowFault::ShortToCanHigh),
}]
);
}
#[test]
fn trx_zero_is_unspecified() {
assert_eq!(
decode(CAN_ERR_TRX, [0; 8]),
vec![ErrorCause::Transceiver {
canh: None,
canl: None,
}]
);
}
#[test]
fn trx_invalid_half_reports_failure() {
let mut data = [0u8; 8];
data[4] = 0x03; assert_eq!(
decode(CAN_ERR_TRX, data),
vec![
ErrorCause::Transceiver {
canh: None,
canl: None,
},
ErrorCause::DecodingFailure(CanErrorDecodingFailure::InvalidTransceiverError),
]
);
}
#[test]
fn location_decoding_never_fails() {
for v in 0u8..=0xFF {
let loc = Location::from_raw(v);
assert_eq!(loc.as_raw(), v, "round-trip failed for {:#04x}", v);
}
}
#[test]
fn location_named_beyond_error_h() {
assert_eq!(Location::from_raw(0x11), Location::ActiveErrorFlag);
assert_eq!(Location::from_raw(0x13), Location::TolerateDominantBits);
assert_eq!(Location::from_raw(0x16), Location::PassiveErrorFlag);
assert_eq!(Location::from_raw(0x17), Location::ErrorDelimiter);
assert_eq!(Location::from_raw(0x1C), Location::OverloadFlag);
}
#[test]
fn location_unnamed_in_range_is_reserved() {
for v in [0x01u8, 0x10, 0x14, 0x15, 0x1D, 0x1E, 0x1F] {
assert_eq!(Location::from_raw(v), Location::Reserved(v));
}
}
#[test]
fn kind_prefers_specific_over_other() {
let mut data = [0u8; 8];
data[1] = 0x0C;
let err = CanError::from(frame(CAN_ERR_CRTL | CAN_ERR_ACK, data));
assert_eq!(err.kind(), ErrorKind::Acknowledge);
assert!(err.contains_kind(ErrorKind::Acknowledge));
assert!(err.contains_kind(ErrorKind::Other));
}
#[test]
fn kind_maps_violation_types() {
let check = |types: ViolationTypes, expect: ErrorKind| {
let cause = ErrorCause::Protocol {
types,
location: Location::Unspecified,
};
assert_eq!(cause.kind(), expect, "for {:?}", types);
};
check(ViolationTypes::BIT, ErrorKind::Bit);
check(ViolationTypes::BIT0, ErrorKind::Bit);
check(ViolationTypes::BIT1, ErrorKind::Bit);
check(ViolationTypes::FORM, ErrorKind::Form);
check(ViolationTypes::STUFF, ErrorKind::Stuff);
check(ViolationTypes::OVERLOAD, ErrorKind::Other);
}
#[test]
fn kind_overrun_from_buffer_overflow() {
let mut data = [0u8; 8];
data[1] = ControllerProblems::TX_OVERFLOW.bits();
let err = CanError::from(frame(CAN_ERR_CRTL, data));
assert_eq!(err.kind(), ErrorKind::Overrun);
}
#[test]
fn kind_all_other_falls_back() {
let err = CanError::from(frame(CAN_ERR_BUSOFF | CAN_ERR_RESTARTED, [0; 8]));
assert_eq!(err.kind(), ErrorKind::Other);
}
#[test]
fn top_level_error_delegates_kind() {
let mut data = [0u8; 8];
data[1] = 0x0C;
let err = Error::from(frame(CAN_ERR_CRTL | CAN_ERR_ACK, data));
assert_eq!(err.kind(), ErrorKind::Acknowledge);
}
#[test]
fn predicates_and_accessors() {
let mut data = [0u8; 8];
data[1] = 0x0C;
data[6] = 96;
data[7] = 0;
let err = CanError::from(frame(CAN_ERR_CRTL | CAN_ERR_CNT | CAN_ERR_BUSOFF, data));
assert!(err.is_bus_off());
assert!(err.has_counters());
assert!(!err.is_no_ack());
assert_eq!(err.counters(), Some((96, 0)));
assert_eq!(
err.controller(),
Some(ControllerProblems::RX_WARNING | ControllerProblems::TX_WARNING)
);
assert_eq!(err.protocol(), None);
}
#[test]
fn display_single_is_bare() {
let err = CanError::new(ErrorCause::BusOff);
assert_eq!(err.to_string(), "bus off");
}
#[test]
fn display_multi_is_semicolon_joined() {
let mut data = [0u8; 8];
data[1] = 0x0C;
data[6] = 96;
data[7] = 0;
let err = CanError::from(frame(CAN_ERR_CRTL | CAN_ERR_CNT, data));
assert_eq!(
err.to_string(),
"controller problem: rx warning, tx warning; error counters: tx=96, rx=0"
);
}
#[test]
fn display_violations_at_one_location() {
let mut data = [0u8; 8];
data[2] = 0x9E;
data[3] = 0x08;
let err = CanError::from(frame(CAN_ERR_PROT | CAN_ERR_BUSERROR, data));
assert_eq!(
err.to_string(),
"protocol violation at CRC sequence: frame format error, \
bit stuffing error, unable to send dominant bit, \
unable to send recessive bit, error on transmission; bus error"
);
}
#[test]
fn display_unknown_in_hex() {
let err = CanError::new(ErrorCause::Unknown(0x400));
assert_eq!(err.to_string(), "unknown error (0x400)");
}
#[test]
fn iteration_by_value_and_by_ref() {
let err = CanError::from_multiple(
ErrorCause::BusOff,
[ErrorCause::NoAck, ErrorCause::Restarted],
);
assert_eq!(err.len(), 3);
assert!(!err.is_single());
assert_eq!(*err.last(), ErrorCause::Restarted);
let by_ref: Vec<_> = (&err).into_iter().copied().collect();
let by_val: Vec<_> = err.clone().into_iter().collect();
assert_eq!(by_ref, by_val);
assert_eq!(by_ref.len(), 3);
let via_iter: Vec<_> = err.causes().copied().collect();
assert_eq!(via_iter, by_val);
}
#[test]
fn single_cause_promotes_to_error() {
let err: CanError = ErrorCause::BusOff.into();
assert!(err.is_single());
let err: Error = ErrorCause::BusOff.into();
match err {
Error::Can(err) => assert_eq!(*err.first(), ErrorCause::BusOff),
_ => panic!("expected a CAN error"),
}
}
#[test]
fn from_iter_checked_rejects_empty() {
assert!(CanError::from_iter_checked(std::iter::empty()).is_none());
assert!(CanError::from_iter_checked([ErrorCause::BusOff]).is_some());
}
}