Skip to main content

n0_mainline/actor/
socket.rs

1//! UDP socket layer managing incoming/outgoing requests and responses.
2//!
3//! Uses [`noq_udp`] for all socket I/O (`sendmsg`/`recvmsg`) with optimal
4//! platform-specific configuration (ECN, buffer sizes, MTU discovery).
5//! Async readiness is driven by [`tokio::net::UdpSocket`] via `try_io`.
6
7use std::collections::VecDeque;
8use std::fmt::Debug;
9use std::io::{self, ErrorKind, IoSliceMut};
10use std::net::{SocketAddr, SocketAddrV4};
11use std::time::{Duration, Instant};
12use tokio::io::Interest;
13use tokio::net::UdpSocket;
14use tracing::{debug, trace, warn};
15
16use crate::common::{ErrorSpecific, Message, MessageType, RequestSpecific, ResponseSpecific};
17#[cfg(not(test))]
18use crate::core::VERSION;
19
20use super::config::Config;
21
22const MTU: usize = 2048;
23
24const DEFAULT_PORT: u16 = 6881;
25
26const MIN_REQUEST_TIMEOUT: Duration = Duration::from_millis(500);
27
28/// Version before supporting `announce_signed_peers`
29#[cfg(test)]
30const LEGACY_VERSION: [u8; 4] = [82, 83, 0, 5]; // "RS" version 05
31
32// ---------------------------------------------------------------------------
33// UdpIo – thin async wrapper around noq-udp + tokio readiness
34// ---------------------------------------------------------------------------
35
36/// Async UDP I/O backed by [`noq_udp`].
37///
38/// [`tokio::net::UdpSocket`] provides readiness notifications (epoll/kqueue);
39/// [`noq_udp::UdpSocketState`] performs the actual `sendmsg`/`recvmsg` syscalls
40/// with platform-optimal settings.
41#[derive(Debug)]
42struct UdpIo {
43    socket: UdpSocket,
44    state: noq_udp::UdpSocketState,
45    /// Receive buffer, large enough for a whole GRO batch.
46    batch: Box<[u8]>,
47}
48
49impl UdpIo {
50    /// Bind to `addr`, configure via noq-udp, register with tokio reactor.
51    fn bind(addr: SocketAddr) -> io::Result<Self> {
52        let std_socket = std::net::UdpSocket::bind(addr)?;
53        let state = noq_udp::UdpSocketState::new((&std_socket).into())?;
54        let socket = UdpSocket::from_std(std_socket)?;
55        // With GRO, one receive can return up to `gro_segments` datagrams.
56        let batch = vec![0; MTU * state.gro_segments().get()].into_boxed_slice();
57        Ok(Self {
58            socket,
59            state,
60            batch,
61        })
62    }
63
64    fn local_addr(&self) -> io::Result<SocketAddr> {
65        self.socket.local_addr()
66    }
67
68    /// Send `buf` to `dest`. Waits for writability, then calls
69    /// noq-udp `sendmsg`.
70    async fn send_to(&self, buf: &[u8], dest: SocketAddr) -> io::Result<()> {
71        let transmit = noq_udp::Transmit {
72            destination: dest,
73            ecn: None,
74            contents: buf,
75            segment_size: None,
76            src_ip: None,
77        };
78        loop {
79            self.socket.writable().await?;
80            match self.socket.try_io(Interest::WRITABLE, || {
81                self.state.send((&self.socket).into(), &transmit)
82            }) {
83                Ok(()) => return Ok(()),
84                Err(ref e) if e.kind() == ErrorKind::WouldBlock => continue,
85                Err(e) => return Err(e),
86            }
87        }
88    }
89
90    /// Receive one batch into `batch`: every datagram of a single `recvmsg`.
91    ///
92    /// Usually one datagram. With GRO the kernel can coalesce several from the
93    /// same sender. Returns the sender, the total length and the stride: the
94    /// datagrams start every `stride` bytes, and the last one may be shorter.
95    async fn recv_batch(&mut self) -> io::Result<(SocketAddr, usize, usize)> {
96        loop {
97            self.socket.readable().await?;
98            match self.try_recv() {
99                Ok(result) => return Ok(result),
100                Err(ref e) if e.kind() == ErrorKind::WouldBlock => continue,
101                Err(e) => return Err(e),
102            }
103        }
104    }
105
106    /// Non-blocking receive attempt via `try_io` + noq-udp `recvmsg`.
107    fn try_recv(&mut self) -> io::Result<(SocketAddr, usize, usize)> {
108        let Self {
109            socket,
110            state,
111            batch,
112        } = self;
113        socket.try_io(Interest::READABLE, || {
114            let mut iov = [IoSliceMut::new(batch)];
115            let mut meta = [noq_udp::RecvMeta::default()];
116            let n = state.recv((&*socket).into(), &mut iov, &mut meta)?;
117            if n > 0 {
118                Ok((meta[0].addr, meta[0].len, meta[0].stride))
119            } else {
120                Err(io::Error::new(ErrorKind::WouldBlock, "no data"))
121            }
122        })
123    }
124}
125
126/// Splits the first `len` bytes of a receive buffer into its datagrams.
127///
128/// Datagrams start every `stride` bytes, and the last one may be shorter, as
129/// the kernel builds GRO batches. noq-udp reports `stride == len` without GRO,
130/// so a stride of zero only occurs for an empty receive; it is treated as one
131/// datagram rather than looping forever.
132fn datagrams(batch: &[u8], len: usize, stride: usize) -> impl Iterator<Item = &[u8]> {
133    let stride = if stride == 0 { len.max(1) } else { stride };
134    batch[..len].chunks(stride)
135}
136
137// ---------------------------------------------------------------------------
138// KrpcSocket – KRPC message framing on top of UdpIo
139// ---------------------------------------------------------------------------
140
141/// A UdpSocket wrapper that formats and correlates DHT requests and responses.
142///
143/// Sends are queued synchronously and flushed asynchronously via [`Self::flush`].
144/// Receives are fully async via [`Self::recv_batch`].
145#[derive(Debug)]
146pub struct KrpcSocket {
147    io: UdpIo,
148    pub(crate) server_mode: bool,
149    local_addr: SocketAddrV4,
150
151    inflight_requests: InflightRequests,
152    /// Outgoing packets queued by sync send methods.
153    outbox: VecDeque<(Vec<u8>, SocketAddr)>,
154    datagram_hook: Option<DatagramHook>,
155
156    #[cfg(test)]
157    version: [u8; 4],
158}
159
160type DatagramHookFn = dyn FnMut(&[u8], SocketAddrV4) -> bool + Send + 'static;
161
162/// A synchronous filter for incoming IPv4 UDP datagrams.
163pub struct DatagramHook(Box<DatagramHookFn>);
164
165impl DatagramHook {
166    /// Wrap a non-blocking filter; returning true consumes the datagram.
167    pub fn new(filter: impl FnMut(&[u8], SocketAddrV4) -> bool + Send + 'static) -> Self {
168        Self(Box::new(filter))
169    }
170
171    fn accept(&mut self, bytes: &[u8], from: SocketAddrV4) -> bool {
172        (self.0)(bytes, from)
173    }
174}
175
176impl Debug for DatagramHook {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        f.write_str("DatagramHook(..)")
179    }
180}
181
182impl KrpcSocket {
183    pub(crate) fn new(config: &Config) -> io::Result<Self> {
184        let port = config.port;
185
186        let io = if let Some(port) = port {
187            UdpIo::bind(SocketAddr::from(([0, 0, 0, 0], port)))?
188        } else {
189            match UdpIo::bind(SocketAddr::from(([0, 0, 0, 0], DEFAULT_PORT))) {
190                Ok(io) => Ok(io),
191                Err(_) => UdpIo::bind(SocketAddr::from(([0, 0, 0, 0], 0))),
192            }?
193        };
194
195        let local_addr = match io.local_addr()? {
196            SocketAddr::V4(addr) => addr,
197            SocketAddr::V6(_) => unimplemented!("KrpcSocket does not support Ipv6"),
198        };
199
200        Ok(Self {
201            io,
202            server_mode: config.server_mode,
203            inflight_requests: InflightRequests::new(),
204            local_addr,
205            outbox: VecDeque::new(),
206            datagram_hook: None,
207
208            #[cfg(test)]
209            version: if config.disable_announce_signed_peers {
210                LEGACY_VERSION
211            } else {
212                crate::core::VERSION
213            },
214        })
215    }
216
217    #[cfg(test)]
218    pub(crate) fn server() -> io::Result<Self> {
219        Self::new(&Config {
220            server_mode: true,
221            ..Default::default()
222        })
223    }
224
225    #[cfg(test)]
226    pub(crate) fn client() -> io::Result<Self> {
227        Self::new(&Config::default())
228    }
229
230    // === Getters ===
231
232    /// Returns the address the server is listening to.
233    #[inline]
234    pub fn local_addr(&self) -> SocketAddrV4 {
235        self.local_addr
236    }
237
238    // === Public Methods ===
239
240    /// Returns true if this message's transaction_id is still inflight
241    pub fn inflight(&self, transaction_id: &u32) -> bool {
242        self.inflight_requests.get(*transaction_id).is_some()
243    }
244
245    /// Send a request to the given address and return the transaction_id.
246    /// The packet is queued; call [`Self::flush`] to actually send.
247    pub fn request(&mut self, address: SocketAddrV4, request: RequestSpecific) -> u32 {
248        let transaction_id = self.inflight_requests.add(address);
249
250        let message = self.request_message(transaction_id, request);
251        trace!(context = "socket_message_sending", message = ?message);
252
253        let tid = message.transaction_id;
254        let _ = self.enqueue(address, message).map_err(|e| {
255            debug!(?e, "Error encoding request message");
256        });
257
258        tid
259    }
260
261    /// Send a response to the given address.
262    /// The packet is queued; call [`Self::flush`] to actually send.
263    pub fn response(
264        &mut self,
265        address: SocketAddrV4,
266        transaction_id: u32,
267        response: ResponseSpecific,
268    ) {
269        let message =
270            self.response_message(MessageType::Response(response), address, transaction_id);
271        trace!(context = "socket_message_sending", message = ?message);
272        let _ = self.enqueue(address, message).map_err(|e| {
273            debug!(?e, "Error encoding response message");
274        });
275    }
276
277    /// Send an error to the given address.
278    /// The packet is queued; call [`Self::flush`] to actually send.
279    pub fn error(&mut self, address: SocketAddrV4, transaction_id: u32, error: ErrorSpecific) {
280        let message = self.response_message(MessageType::Error(error), address, transaction_id);
281        let _ = self.enqueue(address, message).map_err(|e| {
282            debug!(?e, "Error encoding error message");
283        });
284    }
285
286    /// Flush all queued outgoing packets.
287    pub async fn flush(&mut self) {
288        while let Some((bytes, addr)) = self.outbox.pop_front() {
289            if let Err(e) = self.io.send_to(&bytes, addr).await {
290                debug!(?e, "Error sending UDP packet");
291            }
292        }
293    }
294
295    pub(crate) fn send_datagram(&mut self, bytes: Vec<u8>, address: SocketAddrV4) {
296        self.outbox.push_back((bytes, address.into()));
297    }
298
299    pub(crate) fn set_datagram_hook(&mut self, hook: Option<DatagramHook>) {
300        self.datagram_hook = hook;
301    }
302
303    /// Async receive: waits for one batch of datagrams and returns the KRPC
304    /// messages among them.
305    ///
306    /// A batch is usually one datagram. With GRO it can be several from the
307    /// same sender, and all of them are handled. Cancel safe: everything after
308    /// the receive itself is synchronous.
309    pub async fn recv_batch(&mut self) -> Vec<(Message, SocketAddrV4)> {
310        self.inflight_requests.cleanup();
311
312        let (from, len, stride) = match self.io.recv_batch().await {
313            Ok(received) => received,
314            Err(error) => {
315                warn!("IO error {error}");
316                return Vec::new();
317            }
318        };
319        let SocketAddr::V4(from) = from else {
320            // Ignore unsupported Ipv6 messages
321            return Vec::new();
322        };
323        // Taken out so each datagram can be handled with `&mut self`.
324        let batch = std::mem::take(&mut self.io.batch);
325        let messages = datagrams(&batch, len, stride)
326            .filter_map(|bytes| self.handle_datagram(bytes, from))
327            .collect();
328        self.io.batch = batch;
329        messages
330    }
331
332    /// Passes one datagram to the hook, or parses it as a KRPC message.
333    fn handle_datagram(
334        &mut self,
335        bytes: &[u8],
336        from: SocketAddrV4,
337    ) -> Option<(Message, SocketAddrV4)> {
338        if from.port() == 0 {
339            trace!(
340                context = "socket_validation",
341                message = "Response from port 0"
342            );
343            return None;
344        }
345
346        if self
347            .datagram_hook
348            .as_mut()
349            .is_some_and(|hook| hook.accept(bytes, from))
350        {
351            return None;
352        }
353
354        match Message::from_bytes(bytes) {
355            Ok(message) => {
356                // Parsed correctly.
357                let should_return = match message.message_type {
358                    MessageType::Request(ref _request_specific) => {
359                        // simulate legacy nodes not supporting `announce_signed_peers` and `get_signed_peers`
360                        #[cfg(test)]
361                        let should_return = supports_request(&self.version, _request_specific);
362                        #[cfg(not(test))]
363                        let should_return = true;
364
365                        trace!(
366                            context = "socket_message_receiving",
367                            ?message,
368                            ?from,
369                            "Received request message"
370                        );
371
372                        should_return
373                    }
374                    MessageType::Response(_) => {
375                        trace!(
376                            context = "socket_message_receiving",
377                            ?message,
378                            ?from,
379                            "Received response message"
380                        );
381
382                        self.is_expected_response(&message, &from)
383                    }
384                    MessageType::Error(_) => {
385                        trace!(
386                            context = "socket_message_receiving",
387                            ?message,
388                            ?from,
389                            "Received error message"
390                        );
391
392                        self.is_expected_response(&message, &from)
393                    }
394                };
395
396                if should_return {
397                    return Some((message, from));
398                }
399            }
400            Err(error) => {
401                trace!(context = "socket_error", ?error, ?from, message = ?String::from_utf8_lossy(bytes), "Received invalid Bencode message.");
402            }
403        };
404        None
405    }
406
407    // === Private Methods ===
408
409    fn is_expected_response(&mut self, message: &Message, from: &SocketAddrV4) -> bool {
410        // Positive or an error response or to an inflight request.
411        match self.inflight_requests.remove(message.transaction_id) {
412            Some(request) => {
413                if compare_socket_addr(&request.to, from) {
414                    return true;
415                } else {
416                    trace!(
417                        context = "socket_validation",
418                        message = "Response from wrong address"
419                    );
420                }
421            }
422            None => {
423                trace!(
424                    context = "socket_validation",
425                    message = "Unexpected response id, or timedout request"
426                );
427            }
428        }
429
430        false
431    }
432
433    /// Set transactin_id, version and read_only
434    fn request_message(&mut self, transaction_id: u32, message: RequestSpecific) -> Message {
435        Message {
436            transaction_id,
437            message_type: MessageType::Request(message),
438            version: Some(self.version()),
439            read_only: !self.server_mode,
440            requester_ip: None,
441        }
442    }
443
444    /// Same as request_message but with request transaction_id and the requester_ip.
445    fn response_message(
446        &mut self,
447        message: MessageType,
448        requester_ip: SocketAddrV4,
449        request_tid: u32,
450    ) -> Message {
451        Message {
452            transaction_id: request_tid,
453            message_type: message,
454            version: Some(self.version()),
455            read_only: !self.server_mode,
456            // BEP_0042 Only relevant in responses.
457            requester_ip: Some(requester_ip),
458        }
459    }
460
461    fn version(&self) -> [u8; 4] {
462        #[cfg(test)]
463        return self.version;
464        #[cfg(not(test))]
465        return VERSION;
466    }
467
468    /// Encode a message and queue it for sending.
469    fn enqueue(&mut self, address: SocketAddrV4, message: Message) -> Result<(), SendMessageError> {
470        let bytes = message.to_bytes()?;
471        trace!(context = "socket_message_sending", message = ?message);
472        self.outbox.push_back((bytes, SocketAddr::from(address)));
473        Ok(())
474    }
475}
476
477#[n0_error::stack_error(derive, from_sources, std_sources)]
478/// Mainline crate error enum.
479enum SendMessageError {
480    /// Errors related to parsing DHT messages.
481    #[error(transparent)]
482    BencodeError(serde_bencode::Error),
483
484    #[error(transparent)]
485    /// IO error
486    IO(std::io::Error),
487}
488
489// Same as SocketAddr::eq but ignores the ip if it is unspecified for testing reasons.
490fn compare_socket_addr(a: &SocketAddrV4, b: &SocketAddrV4) -> bool {
491    if a.port() != b.port() {
492        return false;
493    }
494
495    if a.ip().is_unspecified() {
496        return true;
497    }
498
499    a.ip() == b.ip()
500}
501
502struct InflightRequest {
503    tid: u32,
504    to: SocketAddrV4,
505    sent_at: Instant,
506}
507
508impl Debug for InflightRequest {
509    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
510        f.debug_struct("InflightRequest")
511            .field("tid", &self.tid)
512            .field("to", &self.to)
513            .field("elapsed", &self.sent_at.elapsed())
514            .finish()
515    }
516}
517
518/// We don't need a map, since we know the maximum size is `65536` requests.
519/// Requests are also ordered by their transaction_id and thus sent_at, so lookup is fast.
520struct InflightRequests {
521    next_tid: u32,
522    requests: Vec<InflightRequest>,
523    estimated_rtt: Duration,
524    deviation_rtt: Duration,
525}
526
527impl InflightRequests {
528    fn new() -> Self {
529        Self {
530            next_tid: 0,
531            requests: Vec::new(),
532            estimated_rtt: MIN_REQUEST_TIMEOUT,
533            deviation_rtt: Duration::from_secs(0),
534        }
535    }
536
537    /// Increments self.next_tid and returns the previous value.
538    fn tid(&mut self) -> u32 {
539        // We don't reuse freed transaction ids, to preserve the sortablitiy
540        // of both `tid`s and `sent_at`.
541        let tid = self.next_tid;
542        self.next_tid = self.next_tid.wrapping_add(1);
543        tid
544    }
545
546    fn request_timeout(&self) -> Duration {
547        self.estimated_rtt + self.deviation_rtt.mul_f64(4.0)
548    }
549
550    fn get(&self, key: u32) -> Option<&InflightRequest> {
551        if let Ok(index) = self.find_by_tid(key)
552            && let Some(request) = self.requests.get(index)
553            && request.sent_at.elapsed() < self.request_timeout()
554        {
555            return Some(request);
556        };
557
558        None
559    }
560
561    /// Adds a [InflightRequest] with new transaction_id, and returns that id.
562    fn add(&mut self, to: SocketAddrV4) -> u32 {
563        let tid = self.tid();
564        self.requests.push(InflightRequest {
565            tid,
566            to,
567            sent_at: Instant::now(),
568        });
569
570        tid
571    }
572
573    fn remove(&mut self, key: u32) -> Option<InflightRequest> {
574        match self.find_by_tid(key) {
575            Ok(index) => {
576                let request = self.requests.remove(index);
577
578                self.update_rtt_estimates(request.sent_at.elapsed());
579                trace!(
580                    "Updated estimated round trip time {:?} and request timeout {:?}",
581                    self.estimated_rtt,
582                    self.request_timeout()
583                );
584
585                Some(request)
586            }
587            Err(_) => None,
588        }
589    }
590
591    fn update_rtt_estimates(&mut self, sample_rtt: Duration) {
592        if sample_rtt < MIN_REQUEST_TIMEOUT {
593            return;
594        }
595
596        // Use TCP-like alpha = 1/8, beta = 1/4
597        let alpha = 0.125;
598        let beta = 0.25;
599
600        let sample_rtt_secs = sample_rtt.as_secs_f64();
601        let est_rtt_secs = self.estimated_rtt.as_secs_f64();
602        let dev_rtt_secs = self.deviation_rtt.as_secs_f64();
603
604        let new_est_rtt = (1.0 - alpha) * est_rtt_secs + alpha * sample_rtt_secs;
605        let new_dev_rtt =
606            (1.0 - beta) * dev_rtt_secs + beta * (sample_rtt_secs - new_est_rtt).abs();
607
608        self.estimated_rtt = Duration::from_secs_f64(new_est_rtt);
609        self.deviation_rtt = Duration::from_secs_f64(new_dev_rtt);
610    }
611
612    fn find_by_tid(&self, tid: u32) -> Result<usize, usize> {
613        self.requests
614            .binary_search_by(|request| request.tid.cmp(&tid))
615    }
616
617    /// Removes timeedout requests if necessary to save memory
618    fn cleanup(&mut self) {
619        if self.requests.len() < self.requests.capacity() {
620            return;
621        }
622
623        let index = match self
624            .requests
625            .binary_search_by(|request| self.request_timeout().cmp(&request.sent_at.elapsed()))
626        {
627            Ok(index) => index,
628            Err(index) => index,
629        };
630
631        self.requests.drain(0..index);
632    }
633}
634
635impl Debug for InflightRequests {
636    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637        let timeout = self.request_timeout();
638        let non_expired: Vec<_> = self
639            .requests
640            .iter()
641            .filter(|req| req.sent_at.elapsed() < timeout)
642            .collect();
643
644        f.debug_struct("InflightRequests")
645            .field("next_tid", &self.next_tid)
646            .field("requests", &non_expired)
647            .field("estimated_rtt", &self.estimated_rtt)
648            .field("deviation_rtt", &self.deviation_rtt)
649            .field("request_timeout", &timeout)
650            .finish()
651    }
652}
653
654#[cfg(test)]
655fn supports_request(version: &[u8; 4], request_specific: &crate::common::RequestSpecific) -> bool {
656    use crate::{
657        PutRequestSpecific,
658        common::{PutRequest, RequestTypeSpecific},
659    };
660    match request_specific.request_type {
661        RequestTypeSpecific::GetSignedPeers(_)
662        | RequestTypeSpecific::Put(PutRequest {
663            put_request_type: PutRequestSpecific::AnnounceSignedPeer(_),
664            ..
665        }) => version != &LEGACY_VERSION,
666        _ => true,
667    }
668}
669
670#[cfg(test)]
671mod test {
672    use crate::{
673        common::{Id, PingResponseArguments, RequestTypeSpecific},
674        core::VERSION,
675    };
676
677    use super::*;
678
679    #[tokio::test]
680    async fn tid() {
681        let socket = KrpcSocket::server().unwrap();
682
683        let mut requests = socket.inflight_requests;
684        assert_eq!(requests.tid(), 0);
685        assert_eq!(requests.tid(), 1);
686        assert_eq!(requests.tid(), 2);
687
688        requests.next_tid = u32::MAX;
689
690        assert_eq!(requests.tid(), u32::MAX);
691        assert_eq!(requests.tid(), 0);
692    }
693
694    #[tokio::test]
695    async fn recv_request() {
696        let mut server = KrpcSocket::server().unwrap();
697        let server_address = SocketAddrV4::new([127, 0, 0, 1].into(), server.local_addr().port());
698
699        let mut client = KrpcSocket::client().unwrap();
700        client.inflight_requests.next_tid = 120;
701
702        let client_address = client.local_addr();
703        let request = RequestSpecific {
704            requester_id: Id::random(),
705            request_type: RequestTypeSpecific::Ping,
706        };
707
708        let expected_request = request.clone();
709
710        client.request(server_address, request);
711        client.flush().await;
712
713        let (message, from) = loop {
714            if let Some(result) = server.recv_batch().await.into_iter().next() {
715                break result;
716            }
717        };
718
719        assert_eq!(from.port(), client_address.port());
720        assert_eq!(message.transaction_id, 120);
721        assert!(message.read_only, "Read-only should be true");
722        assert_eq!(message.version, Some(VERSION), "Version should be 'RS'");
723        assert_eq!(message.message_type, MessageType::Request(expected_request));
724    }
725
726    #[tokio::test]
727    async fn datagram_filter_runs_before_krpc_decode() {
728        let mut server = KrpcSocket::server().unwrap();
729        let server_address = SocketAddrV4::new([127, 0, 0, 1].into(), server.local_addr().port());
730        let (sender, mut receiver) = tokio::sync::mpsc::channel::<(Box<[u8]>, SocketAddrV4)>(1);
731        server.set_datagram_hook(Some(DatagramHook::new(move |bytes, from| {
732            let _ = sender.try_send((Box::from(bytes), from));
733            true
734        })));
735
736        let mut client = KrpcSocket::client().unwrap();
737        client.request(
738            server_address,
739            RequestSpecific {
740                requester_id: Id::random(),
741                request_type: RequestTypeSpecific::Ping,
742            },
743        );
744        client.flush().await;
745
746        assert!(server.recv_batch().await.is_empty());
747        let (bytes, _) = receiver.recv().await.unwrap();
748        assert!(Message::from_bytes(&bytes).is_ok());
749    }
750
751    #[tokio::test]
752    async fn recv_response() {
753        let mut client = KrpcSocket::client().unwrap();
754        let client_address = client.local_addr();
755
756        let mut server = KrpcSocket::client().unwrap();
757        let server_address = SocketAddrV4::new([127, 0, 0, 1].into(), server.local_addr().port());
758
759        let responder_id = Id::random();
760        let response = ResponseSpecific::Ping(PingResponseArguments { responder_id });
761
762        server.inflight_requests.requests.push(InflightRequest {
763            tid: 8,
764            to: client_address,
765            sent_at: Instant::now(),
766        });
767
768        client.response(server_address, 8, response);
769        client.flush().await;
770
771        let (message, from) = loop {
772            if let Some(result) = server.recv_batch().await.into_iter().next() {
773                break result;
774            }
775        };
776
777        assert_eq!(from.port(), client_address.port());
778        assert_eq!(message.transaction_id, 8);
779        assert!(message.read_only, "Read-only should be true");
780        assert_eq!(message.version, Some(VERSION), "Version should be 'RS'");
781        assert_eq!(
782            message.message_type,
783            MessageType::Response(ResponseSpecific::Ping(PingResponseArguments {
784                responder_id,
785            }))
786        );
787    }
788
789    #[tokio::test]
790    async fn ignore_response_from_wrong_address() {
791        let mut server = KrpcSocket::client().unwrap();
792        let server_address = server.local_addr();
793
794        let mut client = KrpcSocket::client().unwrap();
795
796        let client_address = client.local_addr();
797
798        server.inflight_requests.requests.push(InflightRequest {
799            tid: 8,
800            to: SocketAddrV4::new([127, 0, 0, 1].into(), client_address.port() + 1),
801            sent_at: Instant::now(),
802        });
803
804        let response = ResponseSpecific::Ping(PingResponseArguments {
805            responder_id: Id::random(),
806        });
807
808        client.response(server_address, 8, response);
809        client.flush().await;
810
811        // Use a timeout to avoid hanging - recv should not return a valid response
812        let result = tokio::time::timeout(Duration::from_millis(50), server.recv_batch()).await;
813
814        assert!(
815            result.is_err() || result.unwrap().is_empty(),
816            "Should not receive a response from wrong address"
817        );
818    }
819
820    #[tokio::test]
821    async fn inflight_request_timeout() {
822        let mut server = KrpcSocket::client().unwrap();
823
824        let tid = 8;
825        let sent_at = Instant::now();
826
827        server.inflight_requests.requests.push(InflightRequest {
828            tid,
829            to: SocketAddrV4::new([0, 0, 0, 0].into(), 0),
830            sent_at,
831        });
832
833        tokio::time::sleep(server.inflight_requests.request_timeout()).await;
834
835        assert!(!server.inflight(&tid));
836    }
837
838    /// Datagrams that arrive in one GRO batch are handed out one by one.
839    ///
840    /// Where the sender supports GSO (Linux), one segmented send over loopback
841    /// reaches the receiver as a single GRO batch. Elsewhere the datagrams are
842    /// sent separately, which checks the path without GRO.
843    #[tokio::test]
844    async fn gro_batches_are_split_into_datagrams() {
845        let mut receiver = UdpIo::bind(SocketAddr::from(([127, 0, 0, 1], 0))).unwrap();
846        let destination = receiver.local_addr().unwrap();
847        let sender = std::net::UdpSocket::bind("127.0.0.1:0").unwrap();
848        let state = noq_udp::UdpSocketState::new((&sender).into()).unwrap();
849        // Equal-sized datagrams, and a shorter last one, as GRO allows.
850        let sent = [vec![1u8; 100], vec![2; 100], vec![3; 40]];
851        if state.max_gso_segments().get() > 1 {
852            let contents = sent.concat();
853            let transmit = noq_udp::Transmit {
854                destination,
855                ecn: None,
856                contents: &contents,
857                segment_size: Some(100),
858                src_ip: None,
859            };
860            state.send((&sender).into(), &transmit).unwrap();
861        } else {
862            for datagram in &sent {
863                sender.send_to(datagram, destination).unwrap();
864            }
865        }
866        let mut received = Vec::new();
867        while received.len() < sent.len() {
868            let (from, len, stride) =
869                tokio::time::timeout(Duration::from_secs(5), receiver.recv_batch())
870                    .await
871                    .expect("datagrams in time")
872                    .unwrap();
873            assert_eq!(from, sender.local_addr().unwrap());
874            received.extend(datagrams(&receiver.batch, len, stride).map(<[u8]>::to_vec));
875        }
876        assert_eq!(received, sent);
877    }
878
879    #[test]
880    fn datagrams_split_at_the_stride() {
881        let batch: Vec<u8> = (0..=255u8).cycle().take(300).collect();
882        let lengths = |len, stride| {
883            datagrams(&batch, len, stride)
884                .map(<[u8]>::len)
885                .collect::<Vec<_>>()
886        };
887        // Equal segments, and a shorter last one.
888        assert_eq!(lengths(300, 100), vec![100, 100, 100]);
889        assert_eq!(lengths(240, 100), vec![100, 100, 40]);
890        // Without GRO, noq-udp reports the length as the stride.
891        assert_eq!(lengths(57, 57), vec![57]);
892        // A stride longer than the data is one datagram.
893        assert_eq!(lengths(57, 100), vec![57]);
894        // An empty receive yields nothing, and a zero stride does not loop.
895        assert_eq!(lengths(0, 0), Vec::<usize>::new());
896        assert_eq!(lengths(57, 0), vec![57]);
897        // Datagrams are the right bytes, in order.
898        let parts: Vec<&[u8]> = datagrams(&batch, 5, 2).collect();
899        assert_eq!(parts, vec![&[0, 1][..], &[2, 3][..], &[4][..]]);
900    }
901}