use crate::server::can::frame::CanFrame;
use anyhow::{anyhow, Result};
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use tokio::net::UdpSocket;
pub const UNSUPPORTED_PLATFORM_MESSAGE: &str = concat!(
"SocketCAN requires the Linux kernel's AF_CAN address family, which does not exist on macOS ",
"or Windows: there is no can0 interface, no can_frame struct in the network stack and no ",
"socket family to bind. A CAN adapter on those platforms is reached through a vendor's ",
"userspace USB driver, which shares nothing with this code. Run the can protocol on Linux ",
"-- `sudo modprobe vcan && sudo ip link add dev vcan0 type vcan && sudo ip link set up ",
"vcan0` gives a fully functional virtual CAN bus with no hardware at all -- or start it with ",
"startup_params {\"transport\": \"udp\"} for the unprivileged test transport, which carries ",
"SocketCAN frame structs in datagrams and which no real CAN node speaks."
);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransportKind {
SocketCan,
Udp,
}
impl TransportKind {
pub fn parse(value: Option<&str>) -> Result<Self> {
match value {
None | Some("socketcan") => Ok(TransportKind::SocketCan),
Some("udp") => Ok(TransportKind::Udp),
Some(other) => Err(anyhow!(
"transport must be \"socketcan\" (a real AF_CAN socket, Linux only) or \"udp\" \
(the test transport carrying SocketCAN frame structs in datagrams), got \
'{other}'"
)),
}
}
}
pub fn socketcan_unavailable<T>() -> Result<T> {
Err(anyhow!(UNSUPPORTED_PLATFORM_MESSAGE))
}
pub const fn socketcan_supported() -> bool {
cfg!(target_os = "linux")
}
#[derive(Clone)]
pub enum FrameSink {
SocketCan(std::sync::mpsc::Sender<Vec<u8>>),
Udp {
socket: Arc<UdpSocket>,
configured_peer: Option<SocketAddr>,
last_peer: Arc<Mutex<Option<SocketAddr>>>,
},
}
impl FrameSink {
pub async fn send(&self, frame: &CanFrame) -> Result<usize> {
let bytes = frame.to_wire_bytes()?;
match self {
FrameSink::SocketCan(tx) => {
let len = bytes.len();
tx.send(bytes)
.map_err(|_| anyhow!("the CAN transmit thread has gone away"))?;
Ok(len)
}
FrameSink::Udp {
socket,
configured_peer,
last_peer,
} => {
let peer = match configured_peer {
Some(peer) => Some(*peer),
None => *last_peer.lock().expect("can peer mutex poisoned"),
};
let peer = peer.ok_or_else(|| {
anyhow!(
"no UDP peer to transmit to: nothing has been received yet and no \
udp_peer startup parameter was given"
)
})?;
Ok(socket.send_to(&bytes, peer).await?)
}
}
}
}
#[cfg(target_os = "linux")]
pub mod linux {
use super::CanFrame;
use anyhow::{anyhow, Context, Result};
use socketcan::{
CanAnyFrame, CanDataFrame, CanFdFrame, CanFdSocket, CanRemoteFrame, EmbeddedFrame,
ExtendedId, Frame, Id, Socket, SocketOptions, StandardId,
};
pub fn open(interface: &str) -> Result<CanFdSocket> {
let socket = CanFdSocket::open(interface).with_context(|| {
format!(
"failed to open an AF_CAN socket on '{interface}'. The interface must exist and \
be up: `ip link show {interface}`, then `sudo ip link set up {interface}`. For a \
bus with no hardware, `sudo modprobe vcan && sudo ip link add dev vcan0 type \
vcan && sudo ip link set up vcan0`."
)
})?;
socket.set_error_filter_accept_all().with_context(|| {
format!(
"failed to enable error-frame delivery on '{interface}'; without it the bus \
state can never be reported"
)
})?;
Ok(socket)
}
fn hal_id(frame: &CanFrame) -> Result<Id> {
if frame.extended {
ExtendedId::new(frame.id)
.map(Id::Extended)
.ok_or_else(|| anyhow!("0x{:X} is not a valid 29-bit identifier", frame.id))
} else {
u16::try_from(frame.id)
.ok()
.and_then(StandardId::new)
.map(Id::Standard)
.ok_or_else(|| anyhow!("0x{:X} is not a valid 11-bit identifier", frame.id))
}
}
pub fn to_socketcan(frame: &CanFrame) -> Result<CanAnyFrame> {
frame.validate()?;
let id = hal_id(frame)?;
if frame.rtr {
CanRemoteFrame::new_remote(id, frame.rtr_dlc as usize)
.map(CanAnyFrame::Remote)
.ok_or_else(|| {
anyhow!(
"socketcan rejected a remote frame with dlc {}",
frame.rtr_dlc
)
})
} else if frame.fd {
let mut fd = CanFdFrame::new(id, &frame.data).ok_or_else(|| {
anyhow!(
"socketcan rejected a {}-byte CAN FD payload",
frame.data.len()
)
})?;
fd.set_brs(frame.brs);
Ok(CanAnyFrame::Fd(fd))
} else {
CanDataFrame::new(id, &frame.data)
.map(CanAnyFrame::Normal)
.ok_or_else(|| {
anyhow!(
"socketcan rejected a {}-byte classic payload",
frame.data.len()
)
})
}
}
pub fn from_socketcan(any: &CanAnyFrame) -> CanFrame {
match any {
CanAnyFrame::Normal(f) => {
CanFrame::from_id_word(f.id_word(), f.data().to_vec(), false, false, false)
}
CanAnyFrame::Remote(f) => {
let mut frame =
CanFrame::from_id_word(f.id_word(), Vec::new(), false, false, false);
frame.rtr_dlc = f.dlc() as u8;
frame
}
CanAnyFrame::Error(f) => {
CanFrame::from_id_word(f.id_word(), f.data().to_vec(), false, false, false)
}
CanAnyFrame::Fd(f) => {
CanFrame::from_id_word(f.id_word(), f.data().to_vec(), true, f.is_brs(), f.is_esi())
}
}
}
}