Skip to main content

pamoja_can/
bus.rs

1//! One CAN bus, joined by each node on it.
2//!
3//! CAN has no master: every node sees every frame on the wire and keeps the ones it wants.
4//! [`CanBus`] is one node's place on a bus. On a Linux board it is a SocketCAN socket on an
5//! interface such as `can0`, opened with [`CanBus::open`]; anywhere, [`CanBus::simulated`]
6//! makes a bus inside the program. [`CanBus::join`] puts another node on the same bus. A node
7//! hears every frame the others send and none of its own, as a SocketCAN socket does, and
8//! keeps only the frames its [`Filter`]s pass.
9//!
10//! A receive on a simulated bus never waits: when nothing is waiting it returns at once and
11//! adds its timeout to [`CanBus::waited_micros`], so a test of a controller that has gone
12//! quiet runs at once and still says how long a real one would have taken.
13//!
14//! # Examples
15//!
16//! ```
17//! use std::time::Duration;
18//!
19//! use pamoja_can::bus::CanBus;
20//! use pamoja_can::{CanId, Frame};
21//!
22//! // Two nodes on one bus with nothing plugged in.
23//! let controller = CanBus::simulated();
24//! let gateway = controller.join()?;
25//!
26//! // The controller reports a motor speed of 500 rpm, big-endian.
27//! let speed = 500u16.to_be_bytes();
28//! controller.send(&Frame::new(CanId::standard(0x20A), &speed)?)?;
29//! let heard = gateway.receive(Duration::from_millis(10))?.expect("a frame is waiting");
30//! assert_eq!(heard.data(), speed);
31//!
32//! // A node does not hear its own frames, and a receive with nothing waiting is counted.
33//! assert_eq!(controller.receive(Duration::from_millis(250))?, None);
34//! assert_eq!(controller.waited_micros(), 250_000);
35//! # Ok::<(), Box<dyn std::error::Error>>(())
36//! ```
37
38use std::collections::VecDeque;
39use std::fmt;
40use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
41use std::time::Duration;
42
43use crate::frame::Frame;
44use crate::id::CanId;
45use crate::j1939::J1939Id;
46
47#[cfg(all(feature = "linux", target_os = "linux"))]
48mod socketcan;
49
50/// What a node's bus is.
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum CanBusKind {
53    /// A kernel CAN interface reached through SocketCAN: `can0` for a controller, `vcan0` for a
54    /// virtual one.
55    Device,
56    /// A bus inside the program, with every node on it simulated.
57    Simulated,
58}
59
60/// A frame a node keeps: one whose identifier, masked, equals the filter's, and whose width,
61/// standard or extended, is the filter's.
62///
63/// This is the SocketCAN filter, `received_id & mask == id & mask`, with the frame format
64/// always part of the match, so a filter for an extended identifier never passes a standard
65/// frame that happens to share its low bits.
66///
67/// # Examples
68///
69/// ```
70/// use pamoja_can::bus::Filter;
71/// use pamoja_can::{CanId, J1939Id};
72///
73/// // Engine speed, parameter group 61444, from any engine controller at any priority.
74/// let engine_speed = Filter::pgn(61_444);
75/// assert!(engine_speed.matches(J1939Id::broadcast(3, 61_444, 0x00).to_id()));
76/// assert!(engine_speed.matches(J1939Id::broadcast(6, 61_444, 0x01).to_id()));
77/// assert!(!engine_speed.matches(J1939Id::broadcast(3, 61_443, 0x00).to_id()));
78///
79/// // One standard identifier and nothing else.
80/// let motor = Filter::exact(CanId::standard(0x20A));
81/// assert!(motor.matches(CanId::standard(0x20A)));
82/// assert!(!motor.matches(CanId::extended(0x20A)));
83/// ```
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct Filter {
86    id: CanId,
87    mask: u32,
88}
89
90fn width(id: CanId) -> u32 {
91    if id.is_extended() {
92        CanId::EXTENDED_MASK
93    } else {
94        CanId::STANDARD_MASK
95    }
96}
97
98impl Filter {
99    /// A filter on the identifier bits a mask selects.
100    ///
101    /// # Arguments
102    ///
103    /// * `id` - the identifier to match, which also fixes the frame format.
104    /// * `mask` - the identifier bits that have to match; bits outside the identifier's width
105    ///   are dropped.
106    ///
107    /// # Returns
108    ///
109    /// The filter.
110    pub fn new(id: CanId, mask: u32) -> Filter {
111        Filter {
112            id,
113            mask: mask & width(id),
114        }
115    }
116
117    /// A filter that passes one identifier and nothing else.
118    ///
119    /// # Arguments
120    ///
121    /// * `id` - the identifier.
122    ///
123    /// # Returns
124    ///
125    /// The filter.
126    pub fn exact(id: CanId) -> Filter {
127        Filter::new(id, width(id))
128    }
129
130    /// A filter that passes one J1939 parameter group at any priority, from any source, and
131    /// for an addressed group, to any destination.
132    ///
133    /// # Arguments
134    ///
135    /// * `pgn` - the parameter group number.
136    ///
137    /// # Returns
138    ///
139    /// The filter.
140    pub fn pgn(pgn: u32) -> Filter {
141        let id = J1939Id::from_parts(0, pgn, 0, 0);
142        let mask = if id.is_broadcast() {
143            0x03FF_FF00
144        } else {
145            0x03FF_0000
146        };
147        Filter::new(id.to_id(), mask)
148    }
149
150    /// Returns the identifier the filter matches.
151    pub fn id(&self) -> CanId {
152        self.id
153    }
154
155    /// Returns the identifier bits that have to match.
156    pub fn mask(&self) -> u32 {
157        self.mask
158    }
159
160    /// Reports whether a frame with an identifier passes the filter.
161    ///
162    /// # Arguments
163    ///
164    /// * `id` - the frame's identifier.
165    ///
166    /// # Returns
167    ///
168    /// `true` when the frame formats agree and the masked bits are equal.
169    pub fn matches(&self, id: CanId) -> bool {
170        id.is_extended() == self.id.is_extended()
171            && id.raw() & self.mask == self.id.raw() & self.mask
172    }
173}
174
175/// Why a CAN interface could not be opened.
176#[derive(Clone, Debug, PartialEq, Eq)]
177pub enum OpenError {
178    /// This platform has no SocketCAN, or this build left out the `linux` feature.
179    Unsupported,
180    /// The interface could not be found or bound.
181    Device {
182        /// The interface, such as `can0`.
183        interface: String,
184        /// Why, in the kernel's words.
185        reason: String,
186    },
187}
188
189impl fmt::Display for OpenError {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        match self {
192            OpenError::Unsupported => f.write_str(
193                "a CAN interface is opened through SocketCAN, which only Linux has here",
194            ),
195            OpenError::Device { interface, reason } => write!(f, "{interface}: {reason}"),
196        }
197    }
198}
199
200impl std::error::Error for OpenError {}
201
202/// Why a send or a receive on a [`CanBus`] failed.
203#[derive(Clone, Debug, PartialEq, Eq)]
204pub enum BusError {
205    /// The kernel's interface failed, or refused the frame: a CAN FD frame on an interface
206    /// that runs classic CAN, or a bus that has gone bus-off.
207    Device {
208        /// The interface and the kernel's reason.
209        reason: String,
210    },
211}
212
213impl fmt::Display for BusError {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        match self {
216            BusError::Device { reason } => f.write_str(reason),
217        }
218    }
219}
220
221impl std::error::Error for BusError {}
222
223/// One node's place on a CAN bus, shared by every clone of this handle.
224///
225/// # Examples
226///
227/// ```
228/// use std::time::Duration;
229///
230/// use pamoja_can::bus::{CanBus, Filter};
231/// use pamoja_can::{Frame, J1939Id, Signals};
232///
233/// // EEC1 carries engine speed, and ET1 the engine's temperatures.
234/// const EEC1: u32 = 61_444;
235/// const ET1: u32 = 65_262;
236///
237/// let engine = CanBus::simulated();
238/// let gateway = engine.join()?;
239/// gateway.set_filters(&[Filter::pgn(EEC1)])?;
240///
241/// // The gateway keeps engine speed and lets the engine's other groups go by. Each frame
242/// // here reports nothing yet, every signal marked not available.
243/// let nothing = Signals::new();
244/// let speed = J1939Id::broadcast(3, EEC1, 0).to_id();
245/// let temperatures = J1939Id::broadcast(6, ET1, 0).to_id();
246/// engine.send(&Frame::new(temperatures, nothing.as_bytes())?)?;
247/// engine.send(&Frame::new(speed, nothing.as_bytes())?)?;
248///
249/// let heard = gateway.receive(Duration::from_millis(10))?.expect("engine speed");
250/// assert_eq!(heard.id(), speed);
251/// assert_eq!(gateway.receive(Duration::from_millis(10))?, None);
252/// # Ok::<(), Box<dyn std::error::Error>>(())
253/// ```
254#[derive(Clone)]
255pub struct CanBus {
256    node: Arc<Mutex<Node>>,
257}
258
259struct Node {
260    backend: Backend,
261    sent: usize,
262    received: usize,
263    waited_ns: u64,
264}
265
266enum Backend {
267    #[cfg(all(feature = "linux", target_os = "linux"))]
268    Device {
269        interface: String,
270        socket: Arc<socketcan::Socket>,
271    },
272    Simulated {
273        network: Arc<Mutex<Network>>,
274        seat: usize,
275    },
276}
277
278#[derive(Default)]
279struct Network {
280    seats: Vec<Seat>,
281}
282
283struct Seat {
284    inbox: VecDeque<Frame>,
285    filters: Option<Vec<Filter>>,
286    joined: bool,
287}
288
289impl Seat {
290    fn new() -> Seat {
291        Seat {
292            inbox: VecDeque::new(),
293            filters: None,
294            joined: true,
295        }
296    }
297
298    fn keeps(&self, frame: &Frame) -> bool {
299        self.joined
300            && self
301                .filters
302                .as_ref()
303                .is_none_or(|filters| filters.iter().any(|filter| filter.matches(frame.id())))
304    }
305}
306
307impl Drop for Node {
308    // Without the `linux` feature a node is always simulated, and the pattern cannot fail.
309    #[allow(irrefutable_let_patterns)]
310    fn drop(&mut self) {
311        if let Backend::Simulated { network, seat } = &self.backend {
312            let mut network = lock(network);
313            let seat = &mut network.seats[*seat];
314            seat.joined = false;
315            seat.inbox.clear();
316        }
317    }
318}
319
320fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
321    mutex.lock().unwrap_or_else(PoisonError::into_inner)
322}
323
324fn nanos(duration: Duration) -> u64 {
325    u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX)
326}
327
328impl CanBus {
329    fn with(backend: Backend) -> CanBus {
330        CanBus {
331            node: Arc::new(Mutex::new(Node {
332                backend,
333                sent: 0,
334                received: 0,
335                waited_ns: 0,
336            })),
337        }
338    }
339
340    /// Opens a kernel CAN interface through SocketCAN, as one node on its bus.
341    ///
342    /// The interface is brought up and given its bit rate outside the program, with
343    /// `ip link set can0 up type can bitrate 250000`. The node takes CAN FD frames too where the
344    /// interface runs CAN FD.
345    ///
346    /// # Arguments
347    ///
348    /// * `interface` - the interface: `can0` for the first controller, `vcan0` for a virtual
349    ///   one.
350    ///
351    /// # Returns
352    ///
353    /// The node.
354    ///
355    /// # Errors
356    ///
357    /// [`OpenError::Unsupported`] anywhere but Linux, or in a build without the `linux`
358    /// feature, and [`OpenError::Device`] when the interface does not exist or cannot be bound.
359    ///
360    /// # Examples
361    ///
362    /// ```no_run
363    /// use pamoja_can::bus::CanBus;
364    ///
365    /// let bus = CanBus::open("can0")?;
366    /// # Ok::<(), pamoja_can::bus::OpenError>(())
367    /// ```
368    pub fn open(interface: &str) -> Result<CanBus, OpenError> {
369        #[cfg(all(feature = "linux", target_os = "linux"))]
370        {
371            let socket = socketcan::Socket::open(interface).map_err(|error| OpenError::Device {
372                interface: interface.to_string(),
373                reason: error.to_string(),
374            })?;
375            Ok(CanBus::with(Backend::Device {
376                interface: interface.to_string(),
377                socket: Arc::new(socket),
378            }))
379        }
380        #[cfg(not(all(feature = "linux", target_os = "linux")))]
381        {
382            let _ = interface;
383            Err(OpenError::Unsupported)
384        }
385    }
386
387    /// A new bus inside the program, with this node the first on it.
388    ///
389    /// # Returns
390    ///
391    /// The node.
392    pub fn simulated() -> CanBus {
393        let mut network = Network::default();
394        network.seats.push(Seat::new());
395        CanBus::with(Backend::Simulated {
396            network: Arc::new(Mutex::new(network)),
397            seat: 0,
398        })
399    }
400
401    /// Puts another node on the same bus: another socket on the same interface, or another
402    /// node on the same simulated bus. It hears what this node sends, and this node hears it.
403    ///
404    /// # Returns
405    ///
406    /// The new node.
407    ///
408    /// # Errors
409    ///
410    /// [`OpenError::Device`] when the kernel refuses another socket on the interface.
411    pub fn join(&self) -> Result<CanBus, OpenError> {
412        let node = lock(&self.node);
413        match &node.backend {
414            #[cfg(all(feature = "linux", target_os = "linux"))]
415            Backend::Device { interface, .. } => {
416                let interface = interface.clone();
417                drop(node);
418                CanBus::open(&interface)
419            }
420            Backend::Simulated { network, .. } => {
421                let mut shared = lock(network);
422                shared.seats.push(Seat::new());
423                let seat = shared.seats.len() - 1;
424                Ok(CanBus::with(Backend::Simulated {
425                    network: Arc::clone(network),
426                    seat,
427                }))
428            }
429        }
430    }
431
432    /// Returns what the bus is.
433    pub fn kind(&self) -> CanBusKind {
434        match lock(&self.node).backend {
435            #[cfg(all(feature = "linux", target_os = "linux"))]
436            Backend::Device { .. } => CanBusKind::Device,
437            Backend::Simulated { .. } => CanBusKind::Simulated,
438        }
439    }
440
441    /// Returns the kernel interface the node is on, or `None` on a simulated bus.
442    pub fn interface(&self) -> Option<String> {
443        match &lock(&self.node).backend {
444            #[cfg(all(feature = "linux", target_os = "linux"))]
445            Backend::Device { interface, .. } => Some(interface.clone()),
446            Backend::Simulated { .. } => None,
447        }
448    }
449
450    /// Sends a frame to every other node on the bus.
451    ///
452    /// # Arguments
453    ///
454    /// * `frame` - the frame.
455    ///
456    /// # Errors
457    ///
458    /// [`BusError::Device`] when the kernel refuses the frame or the interface fails.
459    pub fn send(&self, frame: &Frame) -> Result<(), BusError> {
460        let mut node = lock(&self.node);
461        match &node.backend {
462            #[cfg(all(feature = "linux", target_os = "linux"))]
463            Backend::Device { interface, socket } => {
464                let (interface, socket) = (interface.clone(), Arc::clone(socket));
465                drop(node);
466                socket.send(frame).map_err(|error| BusError::Device {
467                    reason: format!("{interface}: {error}"),
468                })?;
469                node = lock(&self.node);
470            }
471            Backend::Simulated { network, seat } => {
472                let mut network = lock(network);
473                for (index, other) in network.seats.iter_mut().enumerate() {
474                    if index != *seat && other.keeps(frame) {
475                        other.inbox.push_back(*frame);
476                    }
477                }
478            }
479        }
480        node.sent += 1;
481        Ok(())
482    }
483
484    /// Takes the next frame this node keeps, waiting up to `timeout` for one when none has
485    /// arrived.
486    ///
487    /// On a simulated bus nothing waits: with no frame there it returns at once, and the
488    /// timeout is added to [`CanBus::waited_micros`].
489    ///
490    /// # Arguments
491    ///
492    /// * `timeout` - how long to wait.
493    ///
494    /// # Returns
495    ///
496    /// The frame, or `None` when the timeout passed with nothing.
497    ///
498    /// # Errors
499    ///
500    /// [`BusError::Device`] when the interface fails.
501    pub fn receive(&self, timeout: Duration) -> Result<Option<Frame>, BusError> {
502        let mut node = lock(&self.node);
503        let frame = match &node.backend {
504            #[cfg(all(feature = "linux", target_os = "linux"))]
505            Backend::Device { interface, socket } => {
506                let (interface, socket) = (interface.clone(), Arc::clone(socket));
507                drop(node);
508                let frame = socket.receive(timeout).map_err(|error| BusError::Device {
509                    reason: format!("{interface}: {error}"),
510                })?;
511                node = lock(&self.node);
512                frame
513            }
514            Backend::Simulated { network, seat } => lock(network).seats[*seat].inbox.pop_front(),
515        };
516        match frame {
517            Some(_) => node.received += 1,
518            None => node.waited_ns = node.waited_ns.saturating_add(nanos(timeout)),
519        }
520        Ok(frame)
521    }
522
523    /// Keeps only the frames that pass at least one of the filters, from now on.
524    ///
525    /// An empty list keeps nothing, as SocketCAN's does; [`clear_filters`](CanBus::clear_filters)
526    /// keeps everything again.
527    ///
528    /// # Arguments
529    ///
530    /// * `filters` - the filters, at most 512, the most SocketCAN takes.
531    ///
532    /// # Errors
533    ///
534    /// [`BusError::Device`] when the kernel refuses the filters.
535    pub fn set_filters(&self, filters: &[Filter]) -> Result<(), BusError> {
536        self.filter(Some(filters.to_vec()))
537    }
538
539    /// Keeps every frame again, as a node does when it joins.
540    ///
541    /// # Errors
542    ///
543    /// [`BusError::Device`] when the kernel refuses the change.
544    pub fn clear_filters(&self) -> Result<(), BusError> {
545        self.filter(None)
546    }
547
548    fn filter(&self, filters: Option<Vec<Filter>>) -> Result<(), BusError> {
549        let node = lock(&self.node);
550        match &node.backend {
551            #[cfg(all(feature = "linux", target_os = "linux"))]
552            Backend::Device { interface, socket } => socket
553                .set_filters(filters.as_deref())
554                .map_err(|error| BusError::Device {
555                    reason: format!("{interface}: {error}"),
556                }),
557            Backend::Simulated { network, seat } => {
558                lock(network).seats[*seat].filters = filters;
559                Ok(())
560            }
561        }
562    }
563
564    /// Returns how many frames this node and its clones have sent.
565    pub fn sent(&self) -> usize {
566        lock(&self.node).sent
567    }
568
569    /// Returns how many frames this node and its clones have received.
570    pub fn received(&self) -> usize {
571        lock(&self.node).received
572    }
573
574    /// Returns how long receives on this node have waited without a frame, in microseconds,
575    /// whether or not the process slept through it.
576    pub fn waited_micros(&self) -> u64 {
577        lock(&self.node).waited_ns / 1_000
578    }
579}
580
581impl fmt::Debug for CanBus {
582    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
583        let node = lock(&self.node);
584        f.debug_struct("CanBus")
585            .field(
586                "kind",
587                &match node.backend {
588                    #[cfg(all(feature = "linux", target_os = "linux"))]
589                    Backend::Device { .. } => CanBusKind::Device,
590                    Backend::Simulated { .. } => CanBusKind::Simulated,
591                },
592            )
593            .field("sent", &node.sent)
594            .field("received", &node.received)
595            .finish_non_exhaustive()
596    }
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602
603    fn speed() -> CanId {
604        J1939Id::broadcast(3, 61_444, 0x00).to_id()
605    }
606
607    #[test]
608    fn every_other_node_hears_a_frame_and_the_sender_does_not() {
609        let engine = CanBus::simulated();
610        let gateway = engine.join().unwrap();
611        let logger = gateway.join().unwrap();
612        let frame = Frame::new(speed(), &[0xFF; 8]).unwrap();
613        engine.send(&frame).unwrap();
614
615        assert_eq!(gateway.receive(Duration::ZERO).unwrap(), Some(frame));
616        assert_eq!(logger.receive(Duration::ZERO).unwrap(), Some(frame));
617        assert_eq!(engine.receive(Duration::from_millis(5)).unwrap(), None);
618        assert_eq!(engine.waited_micros(), 5_000);
619        assert_eq!((engine.sent(), gateway.received()), (1, 1));
620        assert_eq!(engine.kind(), CanBusKind::Simulated);
621        assert_eq!(engine.interface(), None);
622    }
623
624    #[test]
625    fn frames_arrive_in_the_order_they_were_sent() {
626        let one = CanBus::simulated();
627        let other = one.join().unwrap();
628        for value in 0u8..5 {
629            one.send(&Frame::new(CanId::standard(0x100), &[value]).unwrap())
630                .unwrap();
631        }
632        let heard: Vec<u8> = (0..5)
633            .map(|_| other.receive(Duration::ZERO).unwrap().unwrap().data()[0])
634            .collect();
635        assert_eq!(heard, [0, 1, 2, 3, 4]);
636    }
637
638    #[test]
639    fn a_filter_keeps_what_it_passes_and_an_empty_list_keeps_nothing() {
640        let engine = CanBus::simulated();
641        let gateway = engine.join().unwrap();
642        gateway.set_filters(&[Filter::pgn(61_444)]).unwrap();
643        let other = J1939Id::broadcast(6, 65_262, 0x00).to_id();
644        engine.send(&Frame::new(other, &[0; 8]).unwrap()).unwrap();
645        engine.send(&Frame::new(speed(), &[0; 8]).unwrap()).unwrap();
646        assert_eq!(
647            gateway.receive(Duration::ZERO).unwrap().unwrap().id(),
648            speed()
649        );
650        assert_eq!(gateway.receive(Duration::ZERO).unwrap(), None);
651
652        gateway.set_filters(&[]).unwrap();
653        engine.send(&Frame::new(speed(), &[0; 8]).unwrap()).unwrap();
654        assert_eq!(gateway.receive(Duration::ZERO).unwrap(), None);
655
656        gateway.clear_filters().unwrap();
657        engine.send(&Frame::new(other, &[0; 8]).unwrap()).unwrap();
658        assert!(gateway.receive(Duration::ZERO).unwrap().is_some());
659    }
660
661    #[test]
662    fn a_pgn_filter_ignores_priority_source_and_an_addressed_groups_destination() {
663        // Engine speed is a broadcast group, whose PS byte is part of the group.
664        let broadcast = Filter::pgn(61_444);
665        assert!(broadcast.matches(J1939Id::broadcast(7, 61_444, 0x21).to_id()));
666        assert!(!broadcast.matches(J1939Id::broadcast(3, 61_445, 0x00).to_id()));
667        // The request group, 59904, is addressed: its PS byte is the destination.
668        let request = Filter::pgn(59_904);
669        assert!(request.matches(J1939Id::from_parts(6, 59_904, 0x01, 0x00).to_id()));
670        assert!(request.matches(J1939Id::from_parts(6, 59_904, 0xF9, 0x21).to_id()));
671        assert!(!request.matches(J1939Id::from_parts(6, 60_160, 0x01, 0x00).to_id()));
672        assert!(!request.matches(CanId::standard(0x000)));
673    }
674
675    #[test]
676    fn a_node_that_leaves_stops_collecting_frames() {
677        let engine = CanBus::simulated();
678        let gateway = engine.join().unwrap();
679        drop(gateway);
680        engine.send(&Frame::new(speed(), &[0; 8]).unwrap()).unwrap();
681        let network = match &lock(&engine.node).backend {
682            Backend::Simulated { network, .. } => Arc::clone(network),
683            #[cfg(all(feature = "linux", target_os = "linux"))]
684            Backend::Device { .. } => unreachable!(),
685        };
686        assert!(lock(&network).seats[1].inbox.is_empty());
687    }
688
689    #[test]
690    fn opening_an_interface_off_linux_is_unsupported() {
691        if !cfg!(all(feature = "linux", target_os = "linux")) {
692            assert_eq!(CanBus::open("can0").err(), Some(OpenError::Unsupported));
693        }
694    }
695
696    // Runs against a virtual interface named by PAMOJA_VCAN, which CI creates with
697    // `ip link add dev vcan0 type vcan`, and passes quietly where there is none.
698    #[cfg(all(feature = "linux", target_os = "linux"))]
699    #[test]
700    fn a_virtual_interface_carries_every_kind_of_frame_between_two_sockets() {
701        let Ok(interface) = std::env::var("PAMOJA_VCAN") else {
702            return;
703        };
704        let engine = CanBus::open(&interface).unwrap();
705        let gateway = engine.join().unwrap();
706        assert_eq!(engine.kind(), CanBusKind::Device);
707        assert_eq!(gateway.interface().as_deref(), Some(interface.as_str()));
708
709        let frames = [
710            Frame::new(CanId::standard(0x20A), &[0x01, 0xF4]).unwrap(),
711            Frame::new(speed(), &[0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]).unwrap(),
712            Frame::remote(CanId::standard(0x301), 4),
713            Frame::fd(CanId::extended(0x1ABC_DEF0), &[0xA5; 32]).unwrap(),
714        ];
715        for frame in &frames {
716            engine.send(frame).unwrap();
717        }
718        for frame in &frames {
719            let heard = gateway.receive(Duration::from_millis(500)).unwrap();
720            assert_eq!(heard, Some(*frame));
721        }
722        assert_eq!(engine.receive(Duration::from_millis(20)).unwrap(), None);
723
724        gateway.set_filters(&[Filter::pgn(61_444)]).unwrap();
725        engine
726            .send(&Frame::new(CanId::standard(0x20A), &[0]).unwrap())
727            .unwrap();
728        engine.send(&frames[1]).unwrap();
729        let kept = gateway.receive(Duration::from_millis(500)).unwrap();
730        assert_eq!(kept.map(|frame| frame.id()), Some(speed()));
731        assert_eq!(gateway.receive(Duration::from_millis(20)).unwrap(), None);
732        gateway.clear_filters().unwrap();
733        assert_eq!((engine.sent(), gateway.received()), (6, 5));
734    }
735}