socketcan 4.0.0

Linux SocketCAN library. Send and receive CAN frames via CANbus on Linux.
Documentation
// socketcan/src/timestamp.rs
//
// Timestamp types and helpers for SocketCAN sockets.
//
// This file is part of the Rust 'socketcan-rs' library.
//
// Licensed under the MIT license:
//   <LICENSE or http://opensource.org/licenses/MIT>
// This file may not be copied, modified, or distributed except according
// to those terms.

//! Timestamp support for SocketCAN sockets.
//!
//! Timestamps are delivered atomically with the frame data via `recvmsg()`
//! and ancillary control messages, avoiding the two-syscall race of the old
//! `SIOCGSTAMPNS` approach.
//!
//! # Usage
//!
//! 1. Enable the desired timestamp mode on the socket with
//!    [`SocketOptions::set_recv_timestamp`] or [`SocketOptions::set_timestamping`].
//! 2. Call the corresponding read method on the socket.
//!
//! [`SocketOptions::set_recv_timestamp`]: crate::SocketOptions::set_recv_timestamp
//! [`SocketOptions::set_timestamping`]: crate::SocketOptions::set_timestamping

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime};

// --------------------------------------------------------------------------
// SOF_TIMESTAMPING_* flags
//
// The values come from `libc`, which has carried the full set since 0.2.186;
// only the subset this crate documents is named here. They are re-typed from
// `c_uint` to `u32` — the same type on every target Linux supports — to match
// `SocketOptions::set_timestamping()`, and each keeps the note explaining what
// it selects, which the C header does not.

/// Hardware transmit timestamp, generated by the network adapter at packet departure.
pub const SOF_TIMESTAMPING_TX_HARDWARE: u32 = libc::SOF_TIMESTAMPING_TX_HARDWARE;
/// Software transmit timestamp, generated when the packet leaves the network stack.
pub const SOF_TIMESTAMPING_TX_SOFTWARE: u32 = libc::SOF_TIMESTAMPING_TX_SOFTWARE;
/// Hardware receive timestamp.
pub const SOF_TIMESTAMPING_RX_HARDWARE: u32 = libc::SOF_TIMESTAMPING_RX_HARDWARE;
/// Software receive timestamp, generated when the packet enters the network stack.
pub const SOF_TIMESTAMPING_RX_SOFTWARE: u32 = libc::SOF_TIMESTAMPING_RX_SOFTWARE;
/// Report software timestamps in the ancillary data (distinct from `RX_SOFTWARE`,
/// which selects when the timestamp is taken).
pub const SOF_TIMESTAMPING_SOFTWARE: u32 = libc::SOF_TIMESTAMPING_SOFTWARE;
/// Report the raw hardware clock value (not wall-clock time).
pub const SOF_TIMESTAMPING_RAW_HARDWARE: u32 = libc::SOF_TIMESTAMPING_RAW_HARDWARE;
/// Deliver `SO_TIMESTAMPING` timestamps via a control message on receive.
///
/// Required for RX timestamps to actually appear in the ancillary data
/// returned by `recvmsg()`.
pub const SOF_TIMESTAMPING_OPT_CMSG: u32 = libc::SOF_TIMESTAMPING_OPT_CMSG;

// --------------------------------------------------------------------------
// ethtool constants / structs
// TODO: These should be PR'd into libc

pub(crate) const ETHTOOL_GET_TS_INFO: u32 = 0x0000_0041;

/// Mirror of `ethtool_ts_info` from `<linux/ethtool.h>`.
#[repr(C)]
pub(crate) struct EthtoolTsInfo {
    pub cmd: u32,
    pub so_timestamping: u32,
    pub phc_index: i32,
    pub tx_types: u32,
    pub tx_reserved: [u32; 3],
    pub rx_filters: u32,
    pub rx_reserved: [u32; 3],
}

// ===== Conversion helpers =====

/// Converts a `libc::timespec` to a `SystemTime`.
///
/// This is what the socket read methods apply to the `timespec` in an
/// `SCM_TIMESTAMPNS` control message, and to the software timestamp in an
/// `SCM_TIMESTAMPING` one. It is public so that code implementing another CAN
/// protocol — J1939 or ISO-TP, on a socket this crate does not open — can
/// reuse it when parsing those same control messages off its own
/// `recvmsg()`.
///
/// The `timespec` is taken to be a non-negative offset from the UNIX epoch,
/// which is what the kernel reports for `CLOCK_REALTIME` socket timestamps.
/// Out-of-range values are clamped the same way
/// [`timespec_to_duration()`] clamps them, so this never panics.
#[inline]
pub fn timespec_to_system_time(ts: libc::timespec) -> SystemTime {
    SystemTime::UNIX_EPOCH + timespec_to_duration(ts)
}

/// Converts a `libc::timespec` to a `Duration`.
///
/// Used for hardware timestamps, which are reported in the adapter's own
/// clock domain rather than as wall-clock time, so a bare `Duration` is the
/// honest type: it is a counter reading, not a point in time. Public for the
/// same reason as [`timespec_to_system_time()`].
///
/// Negative `tv_sec` or `tv_nsec` values — which the kernel should never
/// produce here — are clamped to zero, and `tv_nsec` above one second is
/// clamped down, so the conversion never panics on `Duration::new()`.
#[inline]
pub fn timespec_to_duration(ts: libc::timespec) -> Duration {
    let secs = ts.tv_sec.max(0) as u64;
    let nsec = ts.tv_nsec.clamp(0, 999_999_999) as u32;
    Duration::new(secs, nsec)
}

/////////////////////////////////////////////////////////////////////////////

/// Timestamps associated with a received CAN frame.
///
/// Each field is `None` when the corresponding timestamp mode was not enabled
/// on the socket before the frame was read.
///
/// Enable socket-layer timestamps with [`SocketOptions::set_recv_timestamp`]
/// and network-stack / hardware timestamps with [`SocketOptions::set_timestamping`].
///
/// # Limitation
///
/// The kernel reports each unrequested timestamp source as all-zero rather
/// than omitting it from the cmsg. This implementation treats an exactly-zero
/// `sw` or `hw` value as "not delivered" and reports it as `None`, which
/// collapses three otherwise-distinct cases: the source was disabled, the
/// kernel returned zero, or (for `hw` only) the adapter's clock genuinely
/// read zero. In practice this is only ambiguous in the first nanosecond
/// after a hardware clock starts up.
///
/// # Filling this in yourself
///
/// The fields are public and the type is `Default`, so code that runs its own
/// `recvmsg()` — for a protocol this crate does not open a socket for — can
/// build one from the control messages it parsed, using
/// [`timespec_to_system_time()`] and [`timespec_to_duration()`] for the
/// conversions.
///
/// [`SocketOptions::set_recv_timestamp`]: crate::SocketOptions::set_recv_timestamp
/// [`SocketOptions::set_timestamping`]: crate::SocketOptions::set_timestamping
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CanTimestamps {
    /// `SO_TIMESTAMPNS` — socket-layer arrival time (wall clock).
    pub socket: Option<SystemTime>,
    /// `SOF_TIMESTAMPING_RX_SOFTWARE` — network-stack entry time (wall clock).
    pub sw: Option<SystemTime>,
    /// `SOF_TIMESTAMPING_RX_HARDWARE` — raw hardware clock value.
    ///
    /// Reported as nanoseconds in the adapter's own clock domain.
    /// This is not a wall-clock time!
    pub hw: Option<Duration>,
}

/////////////////////////////////////////////////////////////////////////////

#[cfg(test)]
mod tests {
    use super::*;

    fn ts(tv_sec: libc::time_t, tv_nsec: libc::c_long) -> libc::timespec {
        libc::timespec { tv_sec, tv_nsec }
    }

    #[test]
    fn timespec_conversions() {
        assert_eq!(timespec_to_duration(ts(0, 0)), Duration::ZERO);
        assert_eq!(
            timespec_to_duration(ts(3, 500_000_000)),
            Duration::new(3, 500_000_000)
        );
        assert_eq!(
            timespec_to_system_time(ts(1_785_099_856, 242_430_000)),
            SystemTime::UNIX_EPOCH + Duration::new(1_785_099_856, 242_430_000)
        );
    }

    /// A value the kernel should never report must not panic `Duration::new()`
    /// now that these conversions are public.
    #[test]
    fn out_of_range_values_are_clamped() {
        assert_eq!(timespec_to_duration(ts(-5, 0)), Duration::ZERO);
        assert_eq!(timespec_to_duration(ts(1, -1)), Duration::new(1, 0));
        assert_eq!(
            timespec_to_duration(ts(1, 2_000_000_000)),
            Duration::new(1, 999_999_999)
        );
        assert_eq!(timespec_to_system_time(ts(-1, -1)), SystemTime::UNIX_EPOCH);
    }
}