1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
use std::os::unix::io::AsRawFd;

use crate::{interface_name, raw_socket::cerr};

const fn standard_hwtstamp_config() -> libc::hwtstamp_config {
    libc::hwtstamp_config {
        flags: 0,
        tx_type: libc::HWTSTAMP_TX_ON as _,
        rx_filter: libc::HWTSTAMP_FILTER_ALL as _,
    }
}

pub fn driver_enable_hardware_timestamping(
    udp_socket: &std::net::UdpSocket,
) -> std::io::Result<()> {
    set_hardware_timestamp(udp_socket, standard_hwtstamp_config())
}

fn set_hardware_timestamp(
    udp_socket: &std::net::UdpSocket,
    mut config: libc::hwtstamp_config,
) -> std::io::Result<()> {
    let mut ifreq: libc::ifreq = libc::ifreq {
        ifr_name: socket_interface_name(udp_socket)?,
        ifr_ifru: libc::__c_anonymous_ifr_ifru {
            ifru_data: (&mut config as *mut _) as *mut libc::c_char,
        },
    };

    let fd = udp_socket.as_raw_fd();
    cerr(unsafe { libc::ioctl(fd, libc::SIOCSHWTSTAMP as _, &mut ifreq) })?;

    Ok(())
}

#[allow(unused)]
fn get_hardware_timestamp(
    udp_socket: &std::net::UdpSocket,
) -> std::io::Result<libc::hwtstamp_config> {
    let mut tstamp_config = libc::hwtstamp_config {
        flags: 0,
        tx_type: 0,
        rx_filter: 0,
    };

    let mut ifreq: libc::ifreq = libc::ifreq {
        ifr_name: socket_interface_name(udp_socket)?,
        ifr_ifru: libc::__c_anonymous_ifr_ifru {
            ifru_data: (&mut tstamp_config as *mut _) as *mut libc::c_char,
        },
    };

    let fd = udp_socket.as_raw_fd();
    cerr(unsafe { libc::ioctl(fd, libc::SIOCGHWTSTAMP as _, &mut ifreq) })?;

    Ok(tstamp_config)
}

fn socket_interface_name(
    udp_socket: &std::net::UdpSocket,
) -> std::io::Result<[libc::c_char; libc::IFNAMSIZ]> {
    use std::io::{Error, ErrorKind};

    match interface_name::interface_name(udp_socket.local_addr()?)? {
        Some(ifr_name) => Ok(ifr_name),
        None => Err(Error::new(ErrorKind::Other, "socket has no interface name")),
    }
}

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

    #[test]
    fn get_hwtimestamp() -> std::io::Result<()> {
        let udp_socket = std::net::UdpSocket::bind(("0.0.0.0", 9000))?;
        udp_socket.connect(("10.0.0.18", 9001))?;

        if let Err(e) = get_hardware_timestamp(&udp_socket) {
            assert!(e.to_string().contains("Operation not supported"))
        }

        Ok(())
    }

    #[test]
    #[ignore = "requires elevated permissions to run"]
    fn get_set_hwtimestamp() -> std::io::Result<()> {
        let udp_socket = std::net::UdpSocket::bind(("0.0.0.0", 9002))?;
        udp_socket.connect(("10.0.0.18", 9003))?;

        let old = get_hardware_timestamp(&udp_socket)?;

        let custom = standard_hwtstamp_config();

        set_hardware_timestamp(&udp_socket, custom)?;
        let new = get_hardware_timestamp(&udp_socket)?;

        let custom = standard_hwtstamp_config();
        assert_eq!(new.flags, custom.flags);
        assert_eq!(new.tx_type, custom.tx_type);
        assert_eq!(new.rx_filter, custom.rx_filter);

        set_hardware_timestamp(&udp_socket, old)?;

        Ok(())
    }
}