#![cfg(feature = "serde")]
use socketcan::{
CanAnyFrame, CanDataFrame, CanError, CanErrorFrame, CanFdFrame, CanFilter, CanFrame, CanId,
CanTimestamps, EmbeddedFrame, Error, ErrorCause, StandardId,
errors::{
CAN_ERR_CNT, CAN_ERR_CRTL, CanLowFault, ControllerProblems, Location, ViolationTypes,
},
id::FdFlags,
};
fn check_json<T>(value: &T, expect: &str) -> T
where
T: serde::Serialize + serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
{
let json = serde_json::to_string(value).unwrap();
assert_eq!(json, expect, "serialized format changed");
let back: T = serde_json::from_str(&json).unwrap();
assert_eq!(&back, value, "JSON round-trip lost information");
back
}
fn round_trip_msgpack<T>(value: &T) -> Vec<u8>
where
T: serde::Serialize + serde::de::DeserializeOwned + PartialEq + std::fmt::Debug,
{
let buf = rmp_serde::to_vec(value).unwrap();
let back: T = rmp_serde::from_slice(&buf).unwrap();
assert_eq!(&back, value, "MessagePack round-trip lost information");
buf
}
#[test]
fn can_id_is_self_describing() {
let std_id = CanId::standard(0x123).unwrap();
let ext_id = CanId::extended(0x123).unwrap();
check_json(&std_id, r#"{"Standard":291}"#);
check_json(&ext_id, r#"{"Extended":291}"#);
assert_ne!(
serde_json::to_string(&std_id).unwrap(),
serde_json::to_string(&ext_id).unwrap()
);
}
#[test]
fn can_id_range_is_validated_on_deserialize() {
assert!(serde_json::from_str::<CanId>(r#"{"Standard":2048}"#).is_err());
assert!(serde_json::from_str::<CanId>(r#"{"Extended":536870912}"#).is_err());
}
#[test]
fn data_frame_format() {
let frame =
CanDataFrame::new(StandardId::new(0x123).unwrap(), &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
check_json(
&frame,
r#"{"id":{"Standard":291},"data":[222,173,190,239]}"#,
);
}
#[test]
fn empty_payload_is_omitted() {
let frame = CanDataFrame::new(StandardId::new(0x080).unwrap(), &[]).unwrap();
let back = check_json(&frame, r#"{"id":{"Standard":128}}"#);
assert_eq!(back.data(), &[] as &[u8]);
}
#[test]
fn remote_frame_carries_dlc_not_data() {
let frame = CanFrame::new_remote(StandardId::new(0x104).unwrap(), 4).unwrap();
check_json(&frame, r#"{"Remote":{"id":{"Standard":260},"dlc":4}}"#);
}
#[test]
fn fd_frame_flags_are_named() {
let frame = CanFdFrame::with_flags(
StandardId::new(0x701).unwrap(),
&[0x7F],
FdFlags::BRS | FdFlags::FDF,
)
.unwrap();
check_json(
&frame,
r#"{"id":{"Standard":1793},"flags":"BRS | FDF","data":[127]}"#,
);
}
#[test]
fn id_flags_serialize_by_name() {
use socketcan::id::IdFlags;
check_json(&(IdFlags::EFF | IdFlags::RTR), r#""EFF | RTR""#);
check_json(&IdFlags::empty(), r#""""#);
assert!(serde_json::from_str::<IdFlags>(r#""NOPE""#).is_err());
}
#[test]
fn unknown_flag_name_is_rejected() {
let bad = r#"{"id":{"Standard":1},"flags":"NOPE","data":[]}"#;
assert!(serde_json::from_str::<CanFdFrame>(bad).is_err());
}
#[test]
fn error_frame_format() {
let frame = CanErrorFrame::new_error(CAN_ERR_CRTL, &[0, 0x0C, 0, 0, 0, 0, 0, 0]).unwrap();
check_json(&frame, r#"{"error_bits":4,"data":[0,12,0,0,0,0,0,0]}"#);
}
#[test]
fn payload_length_is_validated_on_deserialize() {
let bad = r#"{"id":{"Standard":1},"data":[1,2,3,4,5,6,7,8,9]}"#;
assert!(serde_json::from_str::<CanDataFrame>(bad).is_err());
let big: Vec<u8> = (0..65).collect();
let bad = format!(
r#"{{"id":{{"Standard":1}},"flags":"FDF","data":{}}}"#,
serde_json::to_string(&big).unwrap()
);
assert!(serde_json::from_str::<CanFdFrame>(&bad).is_err());
}
#[test]
fn frame_enums_round_trip() {
let data = CanFrame::new(StandardId::new(0x123).unwrap(), &[1, 2]).unwrap();
check_json(&data, r#"{"Data":{"id":{"Standard":291},"data":[1,2]}}"#);
let any: CanAnyFrame = data.into();
check_json(&any, r#"{"Normal":{"id":{"Standard":291},"data":[1,2]}}"#);
}
#[test]
fn payload_uses_native_bytes_in_binary_formats() {
let frame =
CanDataFrame::new(StandardId::new(0x123).unwrap(), &[0xDE, 0xAD, 0xBE, 0xEF]).unwrap();
let buf = round_trip_msgpack(&frame);
let needle = [0xC4u8, 0x04, 0xDE, 0xAD, 0xBE, 0xEF];
assert!(
buf.windows(needle.len()).any(|w| w == needle),
"payload was not encoded as a MessagePack byte string: {buf:02X?}"
);
}
#[test]
fn can_error_serializes_as_a_flat_array() {
let frame =
CanErrorFrame::new_error(CAN_ERR_CRTL | CAN_ERR_CNT, &[0, 0x0C, 0, 0, 0, 0, 112, 96])
.unwrap();
let err = frame.into_error();
assert_eq!(err.len(), 2);
check_json(
&err,
r#"[{"Controller":"RX_WARNING | TX_WARNING"},{"Counters":{"tx":112,"rx":96}}]"#,
);
}
#[test]
fn can_error_rejects_an_empty_sequence() {
assert!(serde_json::from_str::<CanError>("[]").is_err());
let one: CanError = serde_json::from_str(r#"["BusOff"]"#).unwrap();
assert!(one.is_single());
assert_eq!(*one.first(), ErrorCause::BusOff);
}
#[test]
fn error_cause_variants_round_trip() {
let cases: &[(ErrorCause, &str)] = &[
(ErrorCause::TransmitTimeout, r#""TransmitTimeout""#),
(ErrorCause::LostArbitration(7), r#"{"LostArbitration":7}"#),
(
ErrorCause::Controller(ControllerProblems::ACTIVE),
r#"{"Controller":"ACTIVE"}"#,
),
(
ErrorCause::Protocol {
types: ViolationTypes::STUFF,
location: Location::CrcSequence,
},
r#"{"Protocol":{"types":"STUFF","location":"CrcSequence"}}"#,
),
(
ErrorCause::Transceiver {
canh: None,
canl: Some(CanLowFault::NoWire),
},
r#"{"Transceiver":{"canh":null,"canl":"NoWire"}}"#,
),
(
ErrorCause::Counters { tx: 1, rx: 2 },
r#"{"Counters":{"tx":1,"rx":2}}"#,
),
(ErrorCause::Unknown(0x400), r#"{"Unknown":1024}"#),
];
for (cause, expect) in cases {
check_json(cause, expect);
}
}
#[test]
fn reserved_location_keeps_its_value() {
check_json(&Location::Reserved(0x1F), r#"{"Reserved":31}"#);
}
#[test]
fn error_can_variant_round_trips_exactly() {
let err = Error::from(ErrorCause::BusOff);
let json = serde_json::to_string(&err).unwrap();
assert_eq!(json, r#"{"Can":["BusOff"]}"#);
let back: Error = serde_json::from_str(&json).unwrap();
match back {
Error::Can(err) => {
assert!(err.is_single());
assert_eq!(*err.first(), ErrorCause::BusOff);
}
other => panic!("expected a CAN error, got {other:?}"),
}
}
#[test]
fn error_io_variant_is_lossy_as_documented() {
let original = std::io::Error::from_raw_os_error(libc::ENOENT);
assert_eq!(original.kind(), std::io::ErrorKind::NotFound);
assert!(original.raw_os_error().is_some());
let err = Error::Io(original);
let json = serde_json::to_string(&err).unwrap();
let back: Error = serde_json::from_str(&json).unwrap();
match back {
Error::Io(e) => {
assert_eq!(e.kind(), std::io::ErrorKind::NotFound);
assert!(!e.to_string().is_empty());
assert!(
e.raw_os_error().is_none(),
"raw_os_error unexpectedly survived; update the docs if this changed"
);
}
other => panic!("expected an I/O error, got {other:?}"),
}
}
#[test]
fn network_down_kind_survives() {
let err = Error::Io(std::io::Error::from_raw_os_error(libc::ENETDOWN));
let json = serde_json::to_string(&err).unwrap();
assert!(json.contains("NetworkDown"), "{json}");
let back: Error = serde_json::from_str(&json).unwrap();
match back {
Error::Io(e) => assert_eq!(e.kind(), std::io::ErrorKind::NetworkDown),
other => panic!("expected an I/O error, got {other:?}"),
}
}
#[test]
fn unnameable_io_kind_becomes_other() {
let original = std::io::Error::from_raw_os_error(libc::ENODEV);
assert_ne!(original.kind(), std::io::ErrorKind::Other);
let err = Error::Io(original);
let back: Error = serde_json::from_str(&serde_json::to_string(&err).unwrap()).unwrap();
match back {
Error::Io(e) => assert_eq!(e.kind(), std::io::ErrorKind::Other),
other => panic!("expected an I/O error, got {other:?}"),
}
}
#[cfg(feature = "dump")]
#[test]
fn parser_variant_round_trips() {
use socketcan::dump::ParseError;
let err = Error::Parser(ParseError::InvalidTimestamp);
let json = serde_json::to_string(&err).unwrap();
assert_eq!(json, r#"{"Parser":"InvalidTimestamp"}"#);
let back: Error = serde_json::from_str(&json).unwrap();
match back {
Error::Parser(ParseError::InvalidTimestamp) => (),
other => panic!("expected a parse error, got {other:?}"),
}
let err = Error::Parser(ParseError::Io(std::io::Error::from_raw_os_error(
libc::ENOENT,
)));
let json = serde_json::to_string(&err).unwrap();
assert!(json.contains("NotFound"), "{json}");
match serde_json::from_str::<Error>(&json).unwrap() {
Error::Parser(ParseError::Io(e)) => {
assert_eq!(e.kind(), std::io::ErrorKind::NotFound);
assert!(e.raw_os_error().is_none(), "documented loss");
}
other => panic!("expected a nested I/O error, got {other:?}"),
}
}
#[cfg(feature = "netlink")]
#[test]
fn nl_variant_round_trips() {
use socketcan::NlError;
let err = Error::Nl(NlError::Netlink { errno: libc::EPERM });
let json = serde_json::to_string(&err).unwrap();
assert_eq!(json, r#"{"Nl":{"Netlink":{"errno":1}}}"#);
match serde_json::from_str::<Error>(&json).unwrap() {
Error::Nl(e) => {
assert_eq!(e.errno(), Some(libc::EPERM));
assert_eq!(e.io_kind(), Some(std::io::ErrorKind::PermissionDenied));
}
other => panic!("expected a netlink error, got {other:?}"),
}
let err = Error::Nl(NlError::BadSeqOrPid { seq: 7, pid: 42 });
let json = serde_json::to_string(&err).unwrap();
let back: Error = serde_json::from_str(&json).unwrap();
assert_eq!(back.to_string(), err.to_string());
}
#[test]
fn unknown_io_kind_degrades_to_other() {
let json = r#"{"Io":{"kind":"SomeFutureKind","message":"whatever"}}"#;
let back: Error = serde_json::from_str(json).unwrap();
match back {
Error::Io(e) => assert_eq!(e.kind(), std::io::ErrorKind::Other),
other => panic!("expected an I/O error, got {other:?}"),
}
}
#[test]
fn filter_round_trips() {
let filter = CanFilter::new(0x123, 0x7FF);
let json = serde_json::to_string(&filter).unwrap();
assert_eq!(json, r#"{"id":291,"mask":2047}"#);
let back: CanFilter = serde_json::from_str(&json).unwrap();
assert_eq!(back, filter);
}
#[test]
fn timestamps_round_trip() {
let ts = CanTimestamps {
socket: Some(
std::time::UNIX_EPOCH + std::time::Duration::from_micros(1_785_099_856_242_430),
),
sw: None,
hw: Some(std::time::Duration::from_nanos(12345)),
};
let json = serde_json::to_string(&ts).unwrap();
let back: CanTimestamps = serde_json::from_str(&json).unwrap();
assert_eq!(back.socket, ts.socket);
assert_eq!(back.sw, ts.sw);
assert_eq!(back.hw, ts.hw);
}
#[cfg(feature = "dump")]
#[test]
fn dump_record_round_trips() {
use socketcan::dump::{CanDumpRecord, Direction};
let frame = CanDataFrame::new(StandardId::new(0x123).unwrap(), &[0xDE, 0xAD]).unwrap();
let rec = CanDumpRecord {
t_us: 1_785_099_856_242_430,
device: "vcan0".into(),
frame: CanFrame::Data(frame).into(),
direction: None,
};
let json = serde_json::to_string(&rec).unwrap();
assert!(!json.contains("direction"), "{json}");
let back: CanDumpRecord = serde_json::from_str(&json).unwrap();
assert_eq!(back.to_string(), rec.to_string());
assert_eq!(back.t_us, rec.t_us);
assert_eq!(back.device, rec.device);
assert_eq!(back.direction, None);
let rec = CanDumpRecord {
direction: Some(Direction::Transmitted),
..rec
};
let json = serde_json::to_string(&rec).unwrap();
assert!(json.contains(r#""direction":"Transmitted""#), "{json}");
let back: CanDumpRecord = serde_json::from_str(&json).unwrap();
assert_eq!(back.direction, Some(Direction::Transmitted));
assert_eq!(back.to_string(), rec.to_string());
}
#[cfg(feature = "dump")]
#[test]
fn parse_error_round_trips() {
use socketcan::dump::ParseError;
let e = ParseError::InvalidCanFrame;
let json = serde_json::to_string(&e).unwrap();
assert_eq!(json, r#""InvalidCanFrame""#);
assert!(matches!(
serde_json::from_str::<ParseError>(&json).unwrap(),
ParseError::InvalidCanFrame
));
let e = ParseError::InvalidFrameDirection;
let json = serde_json::to_string(&e).unwrap();
assert_eq!(json, r#""InvalidFrameDirection""#);
assert!(matches!(
serde_json::from_str::<ParseError>(&json).unwrap(),
ParseError::InvalidFrameDirection
));
let e = ParseError::Io(std::io::Error::from_raw_os_error(libc::ENOENT));
let json = serde_json::to_string(&e).unwrap();
assert!(json.contains("NotFound"), "{json}");
match serde_json::from_str::<ParseError>(&json).unwrap() {
ParseError::Io(e) => {
assert_eq!(e.kind(), std::io::ErrorKind::NotFound);
assert!(e.raw_os_error().is_none(), "documented loss");
}
other => panic!("expected an I/O error, got {other:?}"),
}
}
#[cfg(feature = "netlink")]
#[test]
fn interface_params_round_trip() {
use socketcan::nl::{CanBitTiming, CanCtrlModes, InterfaceCanParams};
let params = InterfaceCanParams {
bit_timing: Some(CanBitTiming {
bitrate: 500_000,
sample_point: 875,
..Default::default()
}),
restart_ms: Some(100),
ctrl_mode: Some(CanCtrlModes::new(0x03, 0x01)),
..Default::default()
};
let json = serde_json::to_string(¶ms).unwrap();
let back: InterfaceCanParams = serde_json::from_str(&json).unwrap();
assert_eq!(back.bit_timing.unwrap().bitrate, 500_000);
assert_eq!(back.bit_timing.unwrap().sample_point, 875);
assert_eq!(back.restart_ms, Some(100));
assert!(back.clock.is_none());
let authored = r#"{"bit_timing":{"bitrate":250000,"sample_point":0,"tq":0,
"prop_seg":0,"phase_seg1":0,"phase_seg2":0,"sjw":0,"brp":0},
"bit_timing_const":null,"clock":null,"state":null,"restart_ms":50,
"berr_counter":null,"ctrl_mode":null,"data_bit_timing":null,
"data_bit_timing_const":null,"termination":null}"#;
let parsed: InterfaceCanParams = serde_json::from_str(authored).unwrap();
assert_eq!(parsed.bit_timing.unwrap().bitrate, 250_000);
assert_eq!(parsed.restart_ms, Some(50));
}