refactor: enforce layered crates and Rust audit gates
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "socketcan"
|
||||
description = "SocketCAN transport bridge"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
can = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
socketcan-rs = { workspace = true }
|
||||
|
||||
[package.metadata.sentry-chassis]
|
||||
layer = "transport/can"
|
||||
@@ -0,0 +1,122 @@
|
||||
#![cfg_attr(test, allow(clippy::unwrap_used, clippy::expect_used))]
|
||||
|
||||
//! 真实总线桥接实现(Linux)。
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod imp {
|
||||
use std::io;
|
||||
use std::sync::Arc;
|
||||
|
||||
use can::{CanFrame as LinkFrame, CanLink};
|
||||
use socketcan_rs::{
|
||||
CanDataFrame, CanFilter, CanFrame as ScFrame, EmbeddedFrame as _, Frame as _,
|
||||
SocketOptions as _, StandardId, tokio::CanSocket,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, error};
|
||||
|
||||
/// 单个链路的通道缓冲(1kHz × 8 电调反馈余量)。
|
||||
const CHANNEL_BOUND: usize = 512;
|
||||
|
||||
/// 打开 CAN 接口并桥接为 [`CanLink`],返回面向总线驱动的端点。
|
||||
///
|
||||
/// 内部:
|
||||
/// - 硬件过滤只放行电调反馈 ID 段(`0x200..=0x20F`,覆盖 0x201–0x208,
|
||||
/// 非反馈帧再由协议层解码拒绝,双保险);
|
||||
/// - 两个桥接任务:socket → 通道(反馈)、通道 → socket(命令)。
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// 接口不存在 / 权限不足等 `io::Error`。
|
||||
pub fn open_link(interface: &str) -> io::Result<CanLink> {
|
||||
let socket = CanSocket::open(interface)?;
|
||||
socket.set_filters(&[CanFilter::new(0x200, 0x7F0)])?;
|
||||
let socket = Arc::new(socket);
|
||||
|
||||
let (bus_side, bridge_side) = CanLink::pair(CHANNEL_BOUND);
|
||||
let CanLink { outgoing_tx, incoming_rx } = bridge_side;
|
||||
|
||||
tokio::spawn(rx_bridge(Arc::clone(&socket), outgoing_tx));
|
||||
tokio::spawn(tx_bridge(socket, incoming_rx));
|
||||
Ok(bus_side)
|
||||
}
|
||||
|
||||
/// socket → 通道:真实总线反馈进入协议 bus。
|
||||
async fn rx_bridge(socket: Arc<CanSocket>, tx: mpsc::Sender<LinkFrame>) {
|
||||
loop {
|
||||
match socket.read_frame().await {
|
||||
Ok(frame) => {
|
||||
if let Some(frame) = to_link_frame(&frame) {
|
||||
if tx.send(frame).await.is_err() {
|
||||
debug!("bus side closed, rx bridge exiting");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// 接口脱落(如 USB 转 CAN 拔出)等致命错误:退出守护进程,
|
||||
// 由 systemd 拉起重连。
|
||||
error!(error = %e, "CAN read failed, rx bridge exiting");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 通道 → socket:协议 bus 命令写入真实总线。
|
||||
async fn tx_bridge(socket: Arc<CanSocket>, mut rx: mpsc::Receiver<LinkFrame>) {
|
||||
while let Some(frame) = rx.recv().await {
|
||||
match to_socketcan_frame(&frame) {
|
||||
Some(sc_frame) => {
|
||||
if let Err(e) = socket.write_frame(sc_frame).await {
|
||||
error!(error = %e, "CAN write failed, tx bridge exiting");
|
||||
return;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!(id = frame.id, "drop frame with non-standard CAN id");
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("bus side closed, tx bridge exiting");
|
||||
}
|
||||
|
||||
/// socketcan 帧 → 链路帧(仅 8 字节数据帧)。
|
||||
fn to_link_frame(frame: &ScFrame) -> Option<LinkFrame> {
|
||||
match frame {
|
||||
ScFrame::Data(df) if df.data().len() == 8 => {
|
||||
Some(LinkFrame::new(df.raw_id(), df.data().try_into().ok()?))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 链路帧 → socketcan 帧(ID 必须落在 11 位标准 ID 范围内)。
|
||||
fn to_socketcan_frame(frame: &LinkFrame) -> Option<ScFrame> {
|
||||
let id = StandardId::new(frame.id as u16)?;
|
||||
let df = CanDataFrame::new(id, &frame.data)?;
|
||||
Some(ScFrame::Data(df))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use imp::*;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Unsupported;
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
impl std::fmt::Display for Unsupported {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("SocketCAN is only available on Linux")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
impl std::error::Error for Unsupported {}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn open_link(_interface: &str) -> Result<can::CanLink, Unsupported> {
|
||||
Err(Unsupported)
|
||||
}
|
||||
Reference in New Issue
Block a user