#[cfg(target_os = "linux")]
mod linux {
use crate::error::{Error, Result};
use crate::transport::device::{TransportDevice, TransportDeviceInfo};
use crate::transport::socketcan_common::linux::*;
use crate::transport::transaction::{dispatch_frame, Request};
use moteus_protocol::CanFdFrame;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::time::{Duration, Instant};
mod select_ffi {
use std::os::raw::c_int;
#[repr(C)]
pub struct timeval {
pub tv_sec: i64,
pub tv_usec: i64,
}
#[repr(C)]
pub struct fd_set {
pub fds_bits: [u64; 16], }
impl fd_set {
pub fn zero(&mut self) {
for bit in &mut self.fds_bits {
*bit = 0;
}
}
pub fn set(&mut self, fd: c_int) {
let idx = fd as usize / 64;
let bit = fd as usize % 64;
if idx < 16 {
self.fds_bits[idx] |= 1 << bit;
}
}
}
extern "C" {
pub fn select(
nfds: c_int,
readfds: *mut fd_set,
writefds: *mut fd_set,
exceptfds: *mut fd_set,
timeout: *mut timeval,
) -> c_int;
}
}
use select_ffi::*;
pub struct SocketCanDevice {
fd: RawFd,
timeout: Duration,
disable_brs: bool,
pub(crate) info: TransportDeviceInfo,
}
impl std::fmt::Debug for SocketCanDevice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SocketCanDevice")
.field("info", &self.info)
.field("timeout", &self.timeout)
.field("disable_brs", &self.disable_brs)
.finish()
}
}
impl SocketCanDevice {
pub fn new(interface: &str) -> Result<Self> {
Self::with_options(interface, crate::transport::factory::DEFAULT_TIMEOUT, false)
}
pub fn with_timeout(interface: &str, timeout: Duration) -> Result<Self> {
Self::with_options(interface, timeout, false)
}
pub fn with_options(interface: &str, timeout: Duration, disable_brs: bool) -> Result<Self> {
let fd = unsafe { socket(PF_CAN, SOCK_RAW, CAN_RAW) };
if fd < 0 {
return Err(Error::Io(io::Error::last_os_error()));
}
let ifindex = match get_ifindex(interface) {
Ok(idx) => idx,
Err(e) => {
unsafe { close(fd) };
return Err(e);
}
};
let addr = SockAddrCan {
can_family: AF_CAN as u16,
can_ifindex: ifindex,
rx_id: 0,
tx_id: 0,
};
let ret = unsafe {
bind(
fd,
&addr as *const SockAddrCan as *const std::ffi::c_void,
std::mem::size_of::<SockAddrCan>() as u32,
)
};
if ret < 0 {
unsafe { close(fd) };
return Err(Error::Io(io::Error::last_os_error()));
}
let enable: i32 = 1;
let ret = unsafe {
setsockopt(
fd,
SOL_CAN_RAW,
CAN_RAW_FD_FRAMES,
&enable as *const i32 as *const std::ffi::c_void,
std::mem::size_of::<i32>() as u32,
)
};
if ret < 0 {
unsafe { close(fd) };
return Err(Error::Io(io::Error::last_os_error()));
}
let flags = unsafe { fcntl(fd, F_GETFL) };
unsafe { fcntl(fd, F_SETFL, flags | O_NONBLOCK) };
Ok(SocketCanDevice {
fd,
timeout,
disable_brs,
info: TransportDeviceInfo::new(0, "SocketCan")
.with_serial(interface.to_string())
.with_detail(format!("'{}'", interface)),
})
}
fn send_frame(&self, frame: &CanFdFrame) -> Result<()> {
let raw = frame_to_raw(frame, self.disable_brs);
let ret = unsafe {
write(
self.fd,
&raw as *const CanFdFrameRaw as *const std::ffi::c_void,
CANFD_MTU,
)
};
if ret < 0 {
return Err(Error::Io(io::Error::last_os_error()));
}
Ok(())
}
fn receive_frames(&self, expected_count: usize) -> Result<Vec<CanFdFrame>> {
let mut frames = Vec::new();
let deadline = Instant::now() + self.timeout;
while frames.len() < expected_count {
if Instant::now() > deadline {
break;
}
let remaining = deadline.saturating_duration_since(Instant::now());
let mut tv = timeval {
tv_sec: remaining.as_secs() as i64,
tv_usec: remaining.subsec_micros() as i64,
};
let mut readfds = fd_set { fds_bits: [0; 16] };
readfds.zero();
readfds.set(self.fd);
let ret = unsafe {
select(
self.fd + 1,
&mut readfds,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut tv,
)
};
if ret <= 0 {
break; }
let mut raw = CanFdFrameRaw::default();
let ret = unsafe {
read(
self.fd,
&mut raw as *mut CanFdFrameRaw as *mut std::ffi::c_void,
CANFD_MTU,
)
};
if ret > 0 {
frames.push(frame_from_raw(&raw));
}
}
Ok(frames)
}
}
impl Drop for SocketCanDevice {
fn drop(&mut self) {
unsafe { close(self.fd) };
}
}
impl AsRawFd for SocketCanDevice {
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl TransportDevice for SocketCanDevice {
fn transaction(&mut self, requests: &mut [Request]) -> Result<()> {
debug_assert!(
requests.iter().all(|r| r.child_device.is_none()),
"SocketCanDevice does not support child devices"
);
for req in requests.iter() {
if let Some(frame) = &req.frame {
self.send_frame(frame)?;
}
}
let expected: usize = Request::total_expected_replies(requests);
if expected > 0 {
let responses = self.receive_frames(expected)?;
for frame in responses {
dispatch_frame(&frame, requests);
}
}
Ok(())
}
fn write(&mut self, frame: &CanFdFrame) -> Result<()> {
self.send_frame(frame)
}
fn read(&mut self) -> Result<Option<CanFdFrame>> {
let deadline = Instant::now() + self.timeout;
let remaining = deadline.saturating_duration_since(Instant::now());
let mut tv = timeval {
tv_sec: remaining.as_secs() as i64,
tv_usec: remaining.subsec_micros() as i64,
};
let mut readfds = fd_set { fds_bits: [0; 16] };
readfds.zero();
readfds.set(self.fd);
let ret = unsafe {
select(
self.fd + 1,
&mut readfds,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut tv,
)
};
if ret <= 0 {
return Ok(None); }
let mut raw = CanFdFrameRaw::default();
let ret = unsafe {
read(
self.fd,
&mut raw as *mut CanFdFrameRaw as *mut std::ffi::c_void,
CANFD_MTU,
)
};
if ret > 0 {
return Ok(Some(frame_from_raw(&raw)));
}
Ok(None)
}
fn flush(&mut self) -> Result<()> {
let deadline = Instant::now() + Duration::from_millis(50);
while Instant::now() < deadline {
let mut tv = timeval {
tv_sec: 0,
tv_usec: 1000, };
let mut readfds = fd_set { fds_bits: [0; 16] };
readfds.zero();
readfds.set(self.fd);
let ret = unsafe {
select(
self.fd + 1,
&mut readfds,
std::ptr::null_mut(),
std::ptr::null_mut(),
&mut tv,
)
};
if ret <= 0 {
break; }
let mut raw = CanFdFrameRaw::default();
unsafe {
read(
self.fd,
&mut raw as *mut CanFdFrameRaw as *mut std::ffi::c_void,
CANFD_MTU,
)
};
}
Ok(())
}
fn info(&self) -> &TransportDeviceInfo {
&self.info
}
fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
fn timeout(&self) -> Duration {
self.timeout
}
}
}
#[cfg(target_os = "linux")]
pub use linux::SocketCanDevice;
#[cfg(test)]
mod tests {
#[cfg(target_os = "linux")]
#[test]
fn test_socketcan_interface_not_found() {
let result = super::SocketCanDevice::new("nonexistent_can_interface_12345");
assert!(result.is_err());
}
}