socketcan_isotp/
lib.rs

1#![deny(clippy::all)]
2
3//! Socketcan ISO-TP support.
4//!
5//! The Linux kernel supports using CAN-devices through a
6//! [network-like API](https://www.kernel.org/doc/Documentation/networking/can.txt).
7//! This crate allows easy access to this functionality without having to wrestle
8//! libc calls.
9//!
10//! ISO-TP allows sending data packets that exceed the eight byte of a default CAN frame.
11//! [can-isotp](https://github.com/hartkopp/can-isotp) is an ISO-TP kernel module that takes
12//! care of handling the ISO-TP protocol.
13//!
14//! Instructions on how the can-isotp kernel module can be build and loaded can be found
15//! at [https://github.com/hartkopp/can-isotp](https://github.com/hartkopp/can-isotp) .
16//!
17//! ```rust,no_run
18//! use socketcan_isotp::IsoTpSocket;
19//!
20//! fn main() -> Result<(), socketcan_isotp::Error> {
21//!     let mut tp_socket = IsoTpSocket::open(
22//!         "vcan0",
23//!         0x123,
24//!         0x321
25//!     )?;
26//!
27//!     loop {
28//!         let buffer = tp_socket.read()?;
29//!         println!("read {} bytes", buffer.len());
30//!
31//!         // print TP frame data
32//!         for x in buffer {
33//!             print!("{:X?} ", x);
34//!         }
35//!
36//!         println!("");
37//!     }
38//!
39//!     Ok(())
40//! }
41//! ```
42//!
43
44use bitflags::bitflags;
45pub use embedded_can::{ExtendedId, Id, StandardId};
46use libc::{
47    bind, c_int, c_short, c_void, close, fcntl, read, setsockopt, sockaddr, socket, write, F_GETFL,
48    F_SETFL, O_NONBLOCK, SOCK_DGRAM,
49};
50use nix::net::if_::if_nametoindex;
51use std::convert::TryFrom;
52use std::convert::TryInto;
53use std::io;
54use std::mem::size_of;
55use std::num::TryFromIntError;
56use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
57use std::time::Duration;
58use thiserror::Error;
59
60/// CAN address family
61pub const AF_CAN: c_short = 29;
62
63/// CAN protocol family
64pub const PF_CAN: c_int = 29;
65
66/// ISO 15765-2 Transport Protocol
67pub const CAN_ISOTP: c_int = 6;
68
69/// undocumented can.h constant
70pub const SOL_CAN_BASE: c_int = 100;
71
72/// undocumented isotp.h constant
73pub const SOL_CAN_ISOTP: c_int = SOL_CAN_BASE + CAN_ISOTP;
74
75/// pass struct `IsoTpOptions`
76pub const CAN_ISOTP_OPTS: c_int = 1;
77
78/// pass struct `FlowControlOptions`
79pub const CAN_ISOTP_RECV_FC: c_int = 2;
80
81/// pass __u32 value in nano secs
82/// use this time instead of value
83/// provided in FC from the receiver
84pub const CAN_ISOTP_TX_STMIN: c_int = 3;
85
86/// pass __u32 value in nano secs
87/// ignore received CF frames which
88/// timestamps differ less than val
89pub const CAN_ISOTP_RX_STMIN: c_int = 4;
90
91/// pass struct `LinkLayerOptions`
92pub const CAN_ISOTP_LL_OPTS: c_int = 5;
93
94/// `CAN_MAX_DLEN` According to ISO 11898-1
95pub const CAN_MAX_DLEN: u8 = 8;
96
97/// Size of buffer allocated for reading TP data
98const RECV_BUFFER_SIZE: usize = 4096;
99
100/// Size of a canframe, constant to reduce crate dependencies
101/// `std::mem::size_of::<socketcan::CANFrame>())`
102const SIZE_OF_CAN_FRAME: u8 = 16;
103
104const FLOW_CONTROL_OPTIONS_SIZE: usize = size_of::<FlowControlOptions>();
105
106const ISOTP_OPTIONS_SIZE: usize = size_of::<IsoTpOptions>();
107
108const LINK_LAYER_OPTIONS_SIZE: usize = size_of::<LinkLayerOptions>();
109
110const CAN_ISOTP_DEFAULT_RECV_BS: u8 = 0;
111
112const CAN_ISOTP_DEFAULT_RECV_STMIN: u8 = 0x00;
113
114const CAN_ISOTP_DEFAULT_RECV_WFTMAX: u8 = 0;
115
116bitflags! {
117    pub struct IsoTpBehaviour: u32 {
118        /// listen only (do not send FC)
119        const CAN_ISOTP_LISTEN_MODE = 0x001;
120        /// enable extended addressing
121        const CAN_ISOTP_EXTEND_ADDR	= 0x002;
122        /// enable CAN frame padding tx path
123        const CAN_ISOTP_TX_PADDING	= 0x004;
124        /// enable CAN frame padding rx path
125        const CAN_ISOTP_RX_PADDING	= 0x008;
126        /// check received CAN frame padding
127        const CAN_ISOTP_CHK_PAD_LEN	= 0x010;
128        /// check received CAN frame padding
129        const CAN_ISOTP_CHK_PAD_DATA = 0x020;
130        /// half duplex error state handling
131        const CAN_ISOTP_HALF_DUPLEX = 0x040;
132        /// ignore stmin from received FC
133        const CAN_ISOTP_FORCE_TXSTMIN = 0x080;
134        /// ignore CFs depending on rx stmin
135        const CAN_ISOTP_FORCE_RXSTMIN = 0x100;
136        /// different rx extended addressing
137        const CAN_ISOTP_RX_EXT_ADDR = 0x200;
138    }
139}
140
141/// if set, indicate 29 bit extended format
142pub const EFF_FLAG: u32 = 0x8000_0000;
143
144/// remote transmission request flag
145pub const RTR_FLAG: u32 = 0x4000_0000;
146
147/// error flag
148pub const ERR_FLAG: u32 = 0x2000_0000;
149
150/// valid bits in standard frame id
151pub const SFF_MASK: u32 = 0x0000_07ff;
152
153/// valid bits in extended frame id
154pub const EFF_MASK: u32 = 0x1fff_ffff;
155
156/// valid bits in error frame
157pub const ERR_MASK: u32 = 0x1fff_ffff;
158
159/// an error mask that will cause Socketcan to report all errors
160pub const ERR_MASK_ALL: u32 = ERR_MASK;
161
162/// an error mask that will cause Socketcan to silently drop all errors
163pub const ERR_MASK_NONE: u32 = 0;
164
165#[derive(Debug)]
166#[repr(C)]
167struct CanAddr {
168    _af_can: c_short,
169    if_index: c_int, // address familiy,
170    /// transport protocol class address information
171    rx_id: u32,
172    /// transport protocol class address information
173    tx_id: u32,
174    _pgn: u32,
175    _addr: u8,
176}
177
178/// ISO-TP otions aka `can_isotp_options`
179#[repr(C)]
180pub struct IsoTpOptions {
181    /// set flags for isotp behaviour.
182    flags: u32,
183    /// frame transmission time (N_As/N_Ar)
184    /// time in nano secs
185    frame_txtime: u32,
186    /// set address for extended addressing
187    ext_address: u8,
188    /// set content of padding byte (tx)
189    txpad_content: u8,
190    /// set content of padding byte (rx)
191    rxpad_content: u8,
192    /// set address for extended addressing
193    rx_ext_address: u8,
194}
195
196impl IsoTpOptions {
197    pub fn new(
198        flags: IsoTpBehaviour,
199        frame_txtime: Duration,
200        ext_address: u8,
201        txpad_content: u8,
202        rxpad_content: u8,
203        rx_ext_address: u8,
204    ) -> Result<Self, TryFromIntError> {
205        let flags = flags.bits();
206        let frame_txtime = u32::try_from(frame_txtime.as_nanos())?;
207
208        Ok(Self {
209            flags,
210            frame_txtime,
211            ext_address,
212            txpad_content,
213            rxpad_content,
214            rx_ext_address,
215        })
216    }
217
218    /// get flags for isotp behaviour.
219    pub fn get_flags(&self) -> Option<IsoTpBehaviour> {
220        IsoTpBehaviour::from_bits(self.flags)
221    }
222
223    /// set flags for isotp behaviour.
224    pub fn set_flags(&mut self, flags: IsoTpBehaviour) {
225        self.flags = flags.bits();
226    }
227
228    /// get frame transmission time (N_As/N_Ar)
229    pub fn get_frame_txtime(&self) -> Duration {
230        Duration::from_nanos(self.frame_txtime.into())
231    }
232
233    /// set frame transmission time (N_As/N_Ar)
234    pub fn set_frame_txtime(&mut self, frame_txtime: Duration) -> Result<(), TryFromIntError> {
235        self.frame_txtime = u32::try_from(frame_txtime.as_nanos())?;
236        Ok(())
237    }
238
239    /// get frame transmission time (N_As/N_Ar)
240    pub fn get_ext_address(&self) -> u8 {
241        self.ext_address
242    }
243
244    /// set address for extended addressing
245    pub fn set_ext_address(&mut self, ext_address: u8) {
246        self.ext_address = ext_address;
247    }
248
249    /// get address for extended addressing
250    pub fn get_txpad_content(&self) -> u8 {
251        self.txpad_content
252    }
253
254    /// set content of padding byte (tx)
255    pub fn set_txpad_content(&mut self, txpad_content: u8) {
256        self.txpad_content = txpad_content;
257    }
258
259    /// get content of padding byte (rx)
260    pub fn get_rxpad_content(&self) -> u8 {
261        self.rxpad_content
262    }
263
264    /// set content of padding byte (rx)
265    pub fn set_rxpad_content(&mut self, rxpad_content: u8) {
266        self.rxpad_content = rxpad_content;
267    }
268
269    /// get address for extended addressing
270    pub fn get_rx_ext_address(&self) -> u8 {
271        self.rx_ext_address
272    }
273
274    /// set address for extended addressing
275    pub fn set_rx_ext_address(&mut self, rx_ext_address: u8) {
276        self.rx_ext_address = rx_ext_address;
277    }
278}
279
280impl Default for IsoTpOptions {
281    fn default() -> Self {
282        // Defaults defined in linux/can/isotp.h
283        Self {
284            flags: 0x00,
285            frame_txtime: 0x00,
286            ext_address: 0x00,
287            txpad_content: 0xCC,
288            rxpad_content: 0xCC,
289            rx_ext_address: 0x00,
290        }
291    }
292}
293
294/// Flow control options aka `can_isotp_fc_options`
295#[repr(C)]
296pub struct FlowControlOptions {
297    /// blocksize provided in FC frame
298    /// 0 = off
299    bs: u8,
300    /// separation time provided in FC frame
301    ///
302    /// 0x00 - 0x7F : 0 - 127 ms
303    /// 0x80 - 0xF0 : reserved
304    /// 0xF1 - 0xF9 : 100 us - 900 us
305    /// 0xFA - 0xFF : reserved
306    stmin: u8,
307    /// max. number of wait frame transmiss.
308    /// 0 = omit FC N_PDU WT
309    wftmax: u8,
310}
311
312impl Default for FlowControlOptions {
313    fn default() -> Self {
314        Self {
315            bs: CAN_ISOTP_DEFAULT_RECV_BS,
316            stmin: CAN_ISOTP_DEFAULT_RECV_STMIN,
317            wftmax: CAN_ISOTP_DEFAULT_RECV_WFTMAX,
318        }
319    }
320}
321
322impl FlowControlOptions {
323    /// Creates new flow control options
324    /// # Parameters
325    /// * bs - Blocksize for the ISO-TP frame, set 0 for off (Sending ECU shall send all frames in 1 block)
326    /// * stmin - Separation time between consecutive frames. The following values are allowed:
327    ///     * 0x00 - 0x7F : 0 - 127 ms
328    ///     * 0xF1 - 0xF9 : 100 - 900 us
329    /// * wftmax - Maximum number of wait frame transmiss
330    ///     * Default value is 0 (omit)
331    pub fn new(bs: u8, stmin: u8, wftmax: u8) -> Self {
332        Self { bs, stmin, wftmax }
333    }
334}
335
336bitflags! {
337    pub struct TxFlags: u8 {
338        /// bit rate switch (second bitrate for payload data)
339        const CANFD_BRS = 0x01;
340        /// error state indicator of the transmitting node
341        const CANFD_ESI	= 0x02;
342    }
343}
344
345/// Link layer options aka `can_isotp_ll_options`
346#[repr(C)]
347pub struct LinkLayerOptions {
348    /// generated & accepted CAN frame type
349    /// CAN_MTU   (16) -> standard CAN 2.0
350    /// CANFD_MTU (72) -> CAN FD frame
351    mtu: u8,
352    /// tx link layer data length in bytes
353    /// (configured maximum payload length)
354    /// __u8 value : 8,12,16,20,24,32,48,64
355    /// => rx path supports all LL_DL values
356    tx_dl: u8,
357    /// set into struct canfd_frame.flags
358    /// at frame creation: e.g. CANFD_BRS
359    /// Obsolete when the BRS flag is fixed
360    /// by the CAN netdriver configuration
361    tx_flags: u8,
362}
363
364impl LinkLayerOptions {
365    pub fn new(mtu: u8, tx_dl: u8, tx_flags: TxFlags) -> Self {
366        let tx_flags = tx_flags.bits();
367        Self {
368            mtu,
369            tx_dl,
370            tx_flags,
371        }
372    }
373}
374
375impl Default for LinkLayerOptions {
376    fn default() -> Self {
377        Self {
378            // CAN_ISOTP_DEFAULT_LL_MTU
379            mtu: SIZE_OF_CAN_FRAME,
380            // CAN_ISOTP_DEFAULT_LL_TX_DL
381            tx_dl: CAN_MAX_DLEN,
382            // CAN_ISOTP_DEFAULT_LL_TX_FLAGS
383            tx_flags: 0x00,
384        }
385    }
386}
387
388#[derive(Error, Debug)]
389/// Possible errors
390pub enum Error {
391    /// CAN device could not be found
392    #[error("Failed to find can device: {source:?}")]
393    Lookup {
394        #[from]
395        source: nix::Error,
396    },
397
398    /// IO Error
399    #[error("IO error: {source:?}")]
400    Io {
401        #[from]
402        source: io::Error,
403    },
404}
405/// An ISO-TP socketcan socket.
406///
407/// Will be closed upon deallocation. To close manually, use `std::drop::Drop`.
408/// Internally this is just a wrapped file-descriptor.
409pub struct IsoTpSocket {
410    fd: c_int,
411    recv_buffer: [u8; RECV_BUFFER_SIZE],
412}
413
414impl IsoTpSocket {
415    /// Open a named CAN ISO-TP device.
416    ///
417    /// Usually the more common case, opens a socket can device by name, such
418    /// as "vcan0" or "socan0".
419    pub fn open(ifname: &str, rx_id: impl Into<Id>, tx_id: impl Into<Id>) -> Result<Self, Error> {
420        Self::open_with_opts(
421            ifname,
422            rx_id,
423            tx_id,
424            Some(IsoTpOptions::default()),
425            Some(FlowControlOptions::default()),
426            Some(LinkLayerOptions::default()),
427        )
428    }
429
430    /// Open a named CAN ISO-TP device, passing additional options.
431    ///
432    /// Usually the more common case, opens a socket can device by name, such
433    /// as "vcan0" or "socan0".
434    pub fn open_with_opts(
435        ifname: &str,
436        rx_id: impl Into<Id>,
437        tx_id: impl Into<Id>,
438        isotp_options: Option<IsoTpOptions>,
439        rx_flow_control_options: Option<FlowControlOptions>,
440        link_layer_options: Option<LinkLayerOptions>,
441    ) -> Result<Self, Error> {
442        let if_index = if_nametoindex(ifname)?;
443        Self::open_if_with_opts(
444            if_index.try_into().unwrap(),
445            rx_id,
446            tx_id,
447            isotp_options,
448            rx_flow_control_options,
449            link_layer_options,
450        )
451    }
452
453    /// Open CAN ISO-TP device device by interface number.
454    ///
455    /// Opens a CAN device by kernel interface number.
456    pub fn open_if(
457        if_index: c_int,
458        rx_id: impl Into<Id>,
459        tx_id: impl Into<Id>,
460    ) -> Result<Self, Error> {
461        Self::open_if_with_opts(
462            if_index,
463            rx_id,
464            tx_id,
465            Some(IsoTpOptions::default()),
466            Some(FlowControlOptions::default()),
467            Some(LinkLayerOptions::default()),
468        )
469    }
470
471    /// Open CAN ISO-TP device device by interface number, passing additional options.
472    ///
473    /// Opens a CAN device by kernel interface number.
474    pub fn open_if_with_opts(
475        if_index: c_int,
476        rx_id: impl Into<Id>,
477        tx_id: impl Into<Id>,
478        isotp_options: Option<IsoTpOptions>,
479        rx_flow_control_options: Option<FlowControlOptions>,
480        link_layer_options: Option<LinkLayerOptions>,
481    ) -> Result<Self, Error> {
482        let rx_id = match rx_id.into() {
483            Id::Standard(standard_id) => standard_id.as_raw() as u32,
484            Id::Extended(extended_id) => extended_id.as_raw() | EFF_FLAG,
485        };
486        let tx_id = match tx_id.into() {
487            Id::Standard(standard_id) => standard_id.as_raw() as u32,
488            Id::Extended(extended_id) => extended_id.as_raw() | EFF_FLAG,
489        };
490        let addr = CanAddr {
491            _af_can: AF_CAN,
492            if_index,
493            rx_id,
494            tx_id,
495            _pgn: 0,
496            _addr: 0,
497        };
498
499        // open socket
500        let sock_fd;
501        unsafe {
502            sock_fd = socket(PF_CAN, SOCK_DGRAM, CAN_ISOTP);
503        }
504
505        if sock_fd == -1 {
506            return Err(Error::from(io::Error::last_os_error()));
507        }
508
509        // Set IsoTpOptions
510        if let Some(isotp_options) = isotp_options {
511            let isotp_options_ptr: *const c_void = &isotp_options as *const _ as *const c_void;
512            let err = unsafe {
513                setsockopt(
514                    sock_fd,
515                    SOL_CAN_ISOTP,
516                    CAN_ISOTP_OPTS,
517                    isotp_options_ptr,
518                    ISOTP_OPTIONS_SIZE.try_into().unwrap(),
519                )
520            };
521            if err == -1 {
522                return Err(Error::from(io::Error::last_os_error()));
523            }
524        }
525
526        // Set FlowControlOptions
527        if let Some(rx_flow_control_options) = rx_flow_control_options {
528            let rx_flow_control_options_ptr: *const c_void =
529                &rx_flow_control_options as *const _ as *const c_void;
530            let err = unsafe {
531                setsockopt(
532                    sock_fd,
533                    SOL_CAN_ISOTP,
534                    CAN_ISOTP_RECV_FC,
535                    rx_flow_control_options_ptr,
536                    FLOW_CONTROL_OPTIONS_SIZE.try_into().unwrap(),
537                )
538            };
539            if err == -1 {
540                return Err(Error::from(io::Error::last_os_error()));
541            }
542        }
543
544        // Set LinkLayerOptions
545        if let Some(link_layer_options) = link_layer_options {
546            let link_layer_options_ptr: *const c_void =
547                &link_layer_options as *const _ as *const c_void;
548            let err = unsafe {
549                setsockopt(
550                    sock_fd,
551                    SOL_CAN_ISOTP,
552                    CAN_ISOTP_LL_OPTS,
553                    link_layer_options_ptr,
554                    LINK_LAYER_OPTIONS_SIZE.try_into().unwrap(),
555                )
556            };
557            if err == -1 {
558                return Err(Error::from(io::Error::last_os_error()));
559            }
560        }
561
562        // bind it
563        let bind_rv;
564        unsafe {
565            let sockaddr_ptr = &addr as *const CanAddr;
566            bind_rv = bind(
567                sock_fd,
568                sockaddr_ptr as *const sockaddr,
569                size_of::<CanAddr>().try_into().unwrap(),
570            );
571        }
572
573        // FIXME: on fail, close socket (do not leak socketfds)
574        if bind_rv == -1 {
575            let e = io::Error::last_os_error();
576            unsafe {
577                close(sock_fd);
578            }
579            return Err(Error::from(e));
580        }
581
582        Ok(Self {
583            fd: sock_fd,
584            recv_buffer: [0x00; RECV_BUFFER_SIZE],
585        })
586    }
587
588    fn close(&mut self) -> io::Result<()> {
589        unsafe {
590            let rv = close(self.fd);
591            if rv != -1 {
592                return Err(io::Error::last_os_error());
593            }
594        }
595        Ok(())
596    }
597
598    /// Change socket to non-blocking mode
599    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
600        // retrieve current flags
601        let oldfl = unsafe { fcntl(self.fd, F_GETFL) };
602
603        if oldfl == -1 {
604            return Err(io::Error::last_os_error());
605        }
606
607        let newfl = if nonblocking {
608            oldfl | O_NONBLOCK
609        } else {
610            oldfl & !O_NONBLOCK
611        };
612
613        let rv = unsafe { fcntl(self.fd, F_SETFL, newfl) };
614
615        if rv != 0 {
616            return Err(io::Error::last_os_error());
617        }
618        Ok(())
619    }
620
621    /// Blocking read data
622    pub fn read(&mut self) -> io::Result<&[u8]> {
623        let buffer_ptr = &mut self.recv_buffer as *mut _ as *mut c_void;
624
625        let read_rv = unsafe { read(self.fd, buffer_ptr, RECV_BUFFER_SIZE) };
626
627        if read_rv < 0 {
628            return Err(io::Error::last_os_error());
629        }
630
631        Ok(&self.recv_buffer[0..read_rv.try_into().unwrap()])
632    }
633
634    /// Blocking write a slice of data
635    pub fn write(&self, buffer: &[u8]) -> io::Result<()> {
636        let write_rv = unsafe {
637            let buffer_ptr = buffer as *const _ as *const c_void;
638            write(self.fd, buffer_ptr, buffer.len())
639        };
640
641        if write_rv != buffer.len().try_into().unwrap() {
642            return Err(io::Error::last_os_error());
643        }
644
645        Ok(())
646    }
647}
648
649impl AsRawFd for IsoTpSocket {
650    fn as_raw_fd(&self) -> RawFd {
651        self.fd
652    }
653}
654
655impl FromRawFd for IsoTpSocket {
656    unsafe fn from_raw_fd(fd: RawFd) -> Self {
657        Self {
658            fd,
659            recv_buffer: [0x00; RECV_BUFFER_SIZE],
660        }
661    }
662}
663
664impl IntoRawFd for IsoTpSocket {
665    fn into_raw_fd(self) -> RawFd {
666        self.fd
667    }
668}
669
670impl Drop for IsoTpSocket {
671    fn drop(&mut self) {
672        self.close().ok(); // ignore result
673    }
674}