1use std::net::SocketAddr;
2
3use rtime_core::timestamp::NtpTimestamp;
4use tokio::net::UdpSocket;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum TimestampMode {
9 Userspace,
11 Software,
13 Hardware,
15}
16
17pub struct TimestampedSocket {
24 inner: UdpSocket,
25 mode: TimestampMode,
26}
27
28pub struct ReceivedPacket {
30 pub data: Vec<u8>,
31 pub addr: SocketAddr,
32 pub recv_time: NtpTimestamp,
33}
34
35impl TimestampedSocket {
36 pub async fn bind(addr: &str) -> Result<Self, std::io::Error> {
38 let socket = UdpSocket::bind(addr).await?;
39 Ok(Self {
40 inner: socket,
41 mode: TimestampMode::Userspace,
42 })
43 }
44
45 pub fn from_socket(socket: UdpSocket) -> Self {
47 Self {
48 inner: socket,
49 mode: TimestampMode::Userspace,
50 }
51 }
52
53 pub async fn send_to(
58 &self,
59 buf: &[u8],
60 addr: SocketAddr,
61 ) -> Result<NtpTimestamp, std::io::Error> {
62 self.inner.send_to(buf, addr).await?;
63 let ts = NtpTimestamp::now();
64 Ok(ts)
65 }
66
67 pub async fn recv_from(&self, buf: &mut [u8]) -> Result<ReceivedPacket, std::io::Error> {
73 let (len, addr) = self.inner.recv_from(buf).await?;
74 let recv_time = NtpTimestamp::now();
75 Ok(ReceivedPacket {
76 data: buf[..len].to_vec(),
77 addr,
78 recv_time,
79 })
80 }
81
82 pub fn local_addr(&self) -> Result<SocketAddr, std::io::Error> {
84 self.inner.local_addr()
85 }
86
87 pub fn inner(&self) -> &UdpSocket {
89 &self.inner
90 }
91
92 pub fn timestamp_mode(&self) -> TimestampMode {
94 self.mode
95 }
96
97 #[cfg(target_os = "linux")]
104 pub fn enable_software_timestamps(&mut self) -> std::io::Result<()> {
105 nix::sys::socket::setsockopt(
106 &self.inner,
107 nix::sys::socket::sockopt::ReceiveTimestampns,
108 &true,
109 )
110 .map_err(std::io::Error::from)?;
111
112 self.mode = TimestampMode::Software;
113 Ok(())
114 }
115
116 #[cfg(target_os = "freebsd")]
121 pub fn enable_software_timestamps(&mut self) -> std::io::Result<()> {
122 nix::sys::socket::setsockopt(
123 &self.inner,
124 nix::sys::socket::sockopt::ReceiveTimestamp,
125 &true,
126 )
127 .map_err(std::io::Error::from)?;
128
129 self.mode = TimestampMode::Software;
130 Ok(())
131 }
132
133 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
135 pub fn enable_software_timestamps(&mut self) -> std::io::Result<()> {
136 Ok(())
138 }
139
140 #[cfg(target_os = "linux")]
150 pub fn enable_hardware_timestamps(&mut self) -> std::io::Result<bool> {
151 use nix::sys::socket::{TimestampingFlag, setsockopt, sockopt};
152
153 let hw_flags = TimestampingFlag::SOF_TIMESTAMPING_TX_HARDWARE
155 | TimestampingFlag::SOF_TIMESTAMPING_RX_HARDWARE
156 | TimestampingFlag::SOF_TIMESTAMPING_RAW_HARDWARE;
157
158 if setsockopt(&self.inner, sockopt::Timestamping, &hw_flags).is_ok() {
159 self.mode = TimestampMode::Hardware;
160 return Ok(true);
161 }
162
163 let sw_flags = TimestampingFlag::SOF_TIMESTAMPING_TX_SOFTWARE
165 | TimestampingFlag::SOF_TIMESTAMPING_RX_SOFTWARE
166 | TimestampingFlag::SOF_TIMESTAMPING_SOFTWARE;
167
168 setsockopt(&self.inner, sockopt::Timestamping, &sw_flags).map_err(std::io::Error::from)?;
169
170 self.mode = TimestampMode::Software;
171 Ok(false)
172 }
173
174 #[cfg(target_os = "freebsd")]
179 pub fn enable_hardware_timestamps(&mut self) -> std::io::Result<bool> {
180 self.enable_software_timestamps()?;
183 Ok(false)
184 }
185
186 #[cfg(not(any(target_os = "linux", target_os = "freebsd")))]
190 pub fn enable_hardware_timestamps(&mut self) -> std::io::Result<bool> {
191 Ok(false)
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[tokio::test]
200 async fn bind_ephemeral_port() {
201 let sock = TimestampedSocket::bind("127.0.0.1:0")
202 .await
203 .expect("bind should succeed");
204 let addr = sock.local_addr().expect("local_addr should succeed");
205 assert!(addr.port() > 0, "should have been assigned a port");
206 assert_eq!(addr.ip(), std::net::Ipv4Addr::LOCALHOST);
207 }
208
209 #[tokio::test]
210 async fn send_recv_loopback() {
211 let sender = TimestampedSocket::bind("127.0.0.1:0")
213 .await
214 .expect("bind sender");
215 let receiver = TimestampedSocket::bind("127.0.0.1:0")
216 .await
217 .expect("bind receiver");
218
219 let receiver_addr = receiver.local_addr().expect("receiver local_addr");
220 let payload = b"NTP test packet data";
221
222 let send_ts = sender
224 .send_to(payload, receiver_addr)
225 .await
226 .expect("send_to");
227
228 let mut buf = [0u8; 1024];
230 let received = receiver.recv_from(&mut buf).await.expect("recv_from");
231
232 assert_eq!(received.data, payload);
233 assert_eq!(received.addr, sender.local_addr().expect("sender addr"));
234
235 assert_ne!(send_ts, NtpTimestamp::ZERO);
237 assert_ne!(received.recv_time, NtpTimestamp::ZERO);
238 assert!(
240 received.recv_time.raw() >= send_ts.raw(),
241 "recv_time ({:?}) should be >= send_ts ({:?})",
242 received.recv_time,
243 send_ts
244 );
245 }
246
247 #[tokio::test]
248 async fn from_socket_works() {
249 let raw = UdpSocket::bind("127.0.0.1:0")
250 .await
251 .expect("bind raw socket");
252 let addr = raw.local_addr().expect("raw local_addr");
253
254 let wrapped = TimestampedSocket::from_socket(raw);
255 assert_eq!(wrapped.local_addr().expect("wrapped local_addr"), addr);
256 }
257
258 #[tokio::test]
259 async fn multiple_packets_loopback() {
260 let sender = TimestampedSocket::bind("127.0.0.1:0")
261 .await
262 .expect("bind sender");
263 let receiver = TimestampedSocket::bind("127.0.0.1:0")
264 .await
265 .expect("bind receiver");
266
267 let receiver_addr = receiver.local_addr().expect("receiver addr");
268
269 for i in 0u8..5 {
271 let payload = [i; 48]; sender.send_to(&payload, receiver_addr).await.expect("send");
273
274 let mut buf = [0u8; 1024];
275 let pkt = receiver.recv_from(&mut buf).await.expect("recv");
276 assert_eq!(pkt.data.len(), 48);
277 assert_eq!(pkt.data[0], i);
278 }
279 }
280}