1use std::{
26 collections::VecDeque,
27 io,
28 io::IoSliceMut,
29 net::SocketAddr,
30 time::{Duration, Instant},
31};
32
33use ana_gotatun::packet::{Packet, PacketBufPool};
34use quinn_udp::{RecvMeta, Transmit, UdpSockRef, UdpSocketState};
35use tokio::{io::Interest, net::UdpSocket};
36
37const MAX_BATCH_SIZE: usize = 64;
38
39const MAX_UDP_PAYLOAD_SIZE: usize = u16::MAX as usize - 20 - 8;
45
46const DISCARD_LOG_INTERVAL: Duration = Duration::from_secs(60);
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct SocketNotWritable;
59
60impl std::fmt::Display for SocketNotWritable {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.write_str("socket not writable, queued datagrams were left in place")
63 }
64}
65
66impl std::error::Error for SocketNotWritable {}
67
68pub enum RecvBatchError<E> {
70 Io(io::Error),
72 Handler(E),
74}
75
76#[derive(Debug)]
78pub enum QueuePacketError {
79 Full {
81 packet: Packet,
83 target: SocketAddr,
85 },
86 PacketTooLarge {
88 packet: Packet,
90 target: SocketAddr,
92 packet_len: usize,
94 max_packet_size: usize,
96 },
97}
98
99fn new_socket_state(socket: &UdpSocket) -> io::Result<UdpSocketState> {
107 let state = UdpSocketState::new(UdpSockRef::from(socket))?;
108 #[cfg(windows)]
109 if state.max_gso_segments() > 1 {
110 clear_socket_segment_size(socket)?;
111 }
112 Ok(state)
113}
114
115#[cfg(windows)]
116fn clear_socket_segment_size(socket: &UdpSocket) -> io::Result<()> {
117 use std::{mem, os::windows::io::AsRawSocket};
118
119 use windows_sys::Win32::Networking::WinSock;
120
121 let value: u32 = 0;
122 let rc = unsafe {
123 WinSock::setsockopt(
124 socket.as_raw_socket() as usize,
125 WinSock::IPPROTO_UDP,
126 WinSock::UDP_SEND_MSG_SIZE,
127 &value as *const _ as *const _,
128 mem::size_of_val(&value) as _,
129 )
130 };
131
132 if rc == 0 {
133 Ok(())
134 } else {
135 Err(io::Error::last_os_error())
136 }
137}
138
139pub struct UdpBatchReceiver<const BATCH_SIZE: usize, const BUFFER_SIZE: usize = 4096> {
149 state: UdpSocketState,
150 recv_meta: [RecvMeta; BATCH_SIZE],
151 recv_slots: [Packet; BATCH_SIZE],
152}
153
154impl<const BATCH_SIZE: usize, const BUFFER_SIZE: usize> UdpBatchReceiver<BATCH_SIZE, BUFFER_SIZE> {
155 pub fn new(socket: &UdpSocket, pool: &PacketBufPool<BUFFER_SIZE>) -> io::Result<Self> {
161 assert!(
162 BATCH_SIZE > 0,
163 "UdpBatchReceiver BATCH_SIZE must be greater than zero"
164 );
165 assert!(
166 BATCH_SIZE <= MAX_BATCH_SIZE,
167 "UdpBatchReceiver BATCH_SIZE must not exceed MAX_BATCH_SIZE"
168 );
169 let state = new_socket_state(socket)?;
170 let recv_slots = std::array::from_fn(|_| pool.get());
171 Ok(Self {
172 state,
173 recv_meta: std::array::from_fn(|_| RecvMeta::default()),
174 recv_slots,
175 })
176 }
177
178 pub async fn recv_batch<E, F>(
180 &mut self,
181 socket: &UdpSocket,
182 pool: &PacketBufPool<BUFFER_SIZE>,
183 mut handler: F,
184 ) -> Result<(), RecvBatchError<E>>
185 where
186 F: FnMut(Packet, SocketAddr) -> Result<(), E>,
187 {
188 let received = loop {
189 socket.readable().await.map_err(RecvBatchError::Io)?;
190 match socket.try_io(Interest::READABLE, || self.try_recv(socket)) {
191 Ok(count) => break count,
192 Err(err) if err.kind() == io::ErrorKind::WouldBlock => continue,
193 Err(err) => return Err(RecvBatchError::Io(err)),
194 }
195 };
196
197 for index in 0..received {
198 self.handle_received(index, pool, &mut handler)
199 .map_err(RecvBatchError::Handler)?;
200 }
201
202 Ok(())
203 }
204
205 fn handle_received<E, F>(
206 &mut self,
207 index: usize,
208 pool: &PacketBufPool<BUFFER_SIZE>,
209 handler: &mut F,
210 ) -> Result<(), E>
211 where
212 F: FnMut(Packet, SocketAddr) -> Result<(), E>,
213 {
214 let meta = self.recv_meta[index];
218 if meta.len == 0 {
219 return Ok(());
220 }
221 let stride = if meta.stride == 0 {
222 meta.len
223 } else {
224 meta.stride
225 };
226 if stride >= meta.len {
227 let mut packet = std::mem::replace(&mut self.recv_slots[index], pool.get());
230 packet.truncate(meta.len);
231 handler(packet, meta.addr)?;
232 return Ok(());
233 }
234
235 let packet = std::mem::replace(&mut self.recv_slots[index], pool.get());
239 for chunk in packet[..meta.len].chunks(stride) {
240 let mut segment = pool.get();
241 segment[..chunk.len()].copy_from_slice(chunk);
242 segment.truncate(chunk.len());
243 handler(segment, meta.addr)?;
244 }
245 Ok(())
246 }
247
248 fn try_recv(&mut self, socket: &UdpSocket) -> io::Result<usize> {
249 let mut bufs_uninit: [std::mem::MaybeUninit<IoSliceMut<'_>>; BATCH_SIZE] =
253 std::array::from_fn(|_| std::mem::MaybeUninit::uninit());
254 for (index, packet) in self.recv_slots.iter_mut().enumerate() {
255 bufs_uninit[index].write(IoSliceMut::new(packet.as_mut()));
256 }
257 let bufs = unsafe {
264 std::slice::from_raw_parts_mut(
265 bufs_uninit.as_mut_ptr() as *mut IoSliceMut<'_>,
266 BATCH_SIZE,
267 )
268 };
269 self.state
270 .recv(UdpSockRef::from(socket), bufs, &mut self.recv_meta)
271 }
272}
273
274pub struct UdpBatchSender<const BATCH_SIZE: usize, const MAX_PACKET_SIZE: usize = 4096> {
282 state: UdpSocketState,
283 queued_packets: VecDeque<(SocketAddr, Packet)>,
284 scratch: Vec<u8>,
285 discarded_datagrams: u64,
287 last_discard_log: Option<Instant>,
289 discards_since_log: u64,
291}
292
293impl<const BATCH_SIZE: usize, const MAX_PACKET_SIZE: usize>
294 UdpBatchSender<BATCH_SIZE, MAX_PACKET_SIZE>
295{
296 pub fn new(socket: &UdpSocket) -> io::Result<Self> {
301 assert!(
302 BATCH_SIZE > 0,
303 "UdpBatchSender BATCH_SIZE must be greater than zero"
304 );
305 assert!(
306 BATCH_SIZE <= MAX_BATCH_SIZE,
307 "UdpBatchSender BATCH_SIZE must not exceed MAX_BATCH_SIZE"
308 );
309 Ok(Self {
310 state: new_socket_state(socket)?,
311 queued_packets: VecDeque::with_capacity(BATCH_SIZE),
312 scratch: Vec::with_capacity(MAX_PACKET_SIZE * BATCH_SIZE),
313 discarded_datagrams: 0,
314 last_discard_log: None,
315 discards_since_log: 0,
316 })
317 }
318
319 pub fn is_empty(&self) -> bool {
321 self.queued_packets.is_empty()
322 }
323
324 pub fn is_full(&self) -> bool {
326 self.queued_packets.len() == BATCH_SIZE
327 }
328
329 pub fn try_queue_packet(
337 &mut self,
338 packet: Packet,
339 target: SocketAddr,
340 ) -> Result<(), QueuePacketError> {
341 let packet_len = packet.len();
342 if packet.len() > MAX_PACKET_SIZE {
343 return Err(QueuePacketError::PacketTooLarge {
344 packet,
345 target,
346 packet_len,
347 max_packet_size: MAX_PACKET_SIZE,
348 });
349 }
350 if self.is_full() {
351 return Err(QueuePacketError::Full { packet, target });
352 }
353 self.queued_packets.push_back((target, packet));
354 Ok(())
355 }
356
357 pub fn try_flush_best_effort(&mut self, socket: &UdpSocket) -> Result<(), SocketNotWritable> {
371 let mut coalesce = true;
375
376 while !self.is_empty() {
377 let (target, segment_size, segments) = self.fill_scratch_from_front(coalesce);
378 let result = socket.try_io(Interest::WRITABLE, || {
379 let transmit = Transmit {
380 destination: target,
381 ecn: None,
382 contents: &self.scratch,
383 segment_size: (segments > 1).then_some(segment_size),
384 src_ip: None,
385 };
386 self.state.try_send(UdpSockRef::from(socket), &transmit)
387 });
388
389 match result {
390 Ok(()) => self.drop_prefix(segments),
391 Err(err) if err.kind() == io::ErrorKind::WouldBlock => {
392 return Err(SocketNotWritable);
393 }
394 Err(err) if segments > 1 => {
395 tracing::debug!(
396 ?target,
397 segments,
398 segment_size,
399 err = ?err,
400 "coalesced transmit failed, retrying datagrams individually"
401 );
402 coalesce = false;
403 }
404 Err(err) => {
405 self.drop_prefix(segments);
406 self.discarded_datagrams += 1;
407 self.log_discard(target, segment_size, &err);
408 }
409 }
410 }
411 Ok(())
412 }
413
414 pub async fn flush(&mut self, socket: &UdpSocket) -> io::Result<()> {
419 while !self.is_empty() {
420 socket.writable().await?;
421 if self.try_flush_best_effort(socket).is_ok() {
422 break;
423 }
424 }
425 Ok(())
426 }
427
428 pub fn take_discarded_datagrams(&mut self) -> u64 {
434 std::mem::take(&mut self.discarded_datagrams)
435 }
436
437 fn log_discard(&mut self, target: SocketAddr, datagram_len: usize, err: &io::Error) {
441 let now = Instant::now();
442 let due = self
443 .last_discard_log
444 .is_none_or(|last| now.duration_since(last) >= DISCARD_LOG_INTERVAL);
445
446 if !due {
447 self.discards_since_log += 1;
448 tracing::debug!(?target, datagram_len, err = ?err, "discarding refused datagram");
449 return;
450 }
451
452 tracing::warn!(
453 ?target,
454 datagram_len,
455 err = ?err,
456 suppressed_discards = self.discards_since_log,
457 "discarding refused datagram"
458 );
459 self.last_discard_log = Some(now);
460 self.discards_since_log = 0;
461 }
462
463 fn drop_prefix(&mut self, count: usize) {
464 self.queued_packets.drain(..count);
465 }
466
467 fn fill_scratch_from_front(&mut self, coalesce: bool) -> (SocketAddr, usize, usize) {
472 self.scratch.clear();
473 let (target, first_packet) = self
474 .queued_packets
475 .front()
476 .expect("filling the scratch buffer requires a non-empty queue");
477 let target = *target;
478 let segment_size = first_packet.len();
479 let mut segments = 0;
480 let max_segments = if coalesce && segment_size > 0 {
487 self.state
488 .max_gso_segments()
489 .min(BATCH_SIZE)
490 .min(MAX_UDP_PAYLOAD_SIZE / segment_size)
491 .max(1)
492 } else {
493 1
494 };
495
496 for (queued_target, packet) in self.queued_packets.iter().take(max_segments) {
500 if *queued_target != target || packet.len() != segment_size {
501 break;
502 }
503 self.scratch.extend_from_slice(&packet[..]);
504 segments += 1;
505 }
506
507 (target, segment_size, segments)
508 }
509}
510
511#[cfg(test)]
512mod tests {
513 use std::{net::SocketAddr, time::Duration};
514
515 use ana_gotatun::packet::PacketBufPool;
516 use tokio::net::UdpSocket;
517
518 use super::{MAX_BATCH_SIZE, MAX_UDP_PAYLOAD_SIZE, UdpBatchReceiver, UdpBatchSender};
519
520 const TEST_PACKET_SIZE: usize = 128;
521
522 fn packet_pool() -> PacketBufPool<TEST_PACKET_SIZE> {
523 PacketBufPool::new(MAX_BATCH_SIZE)
524 }
525
526 async fn bound_socket() -> UdpSocket {
527 UdpSocket::bind("127.0.0.1:0").await.unwrap()
528 }
529
530 fn packet_from_bytes(
531 pool: &PacketBufPool<TEST_PACKET_SIZE>,
532 bytes: &[u8],
533 ) -> ana_gotatun::packet::Packet {
534 let mut packet = pool.get();
535 packet[..bytes.len()].copy_from_slice(bytes);
536 packet.truncate(bytes.len());
537 packet
538 }
539
540 #[tokio::test]
541 async fn flushes_partially_full_sender_batch() {
542 let sender_socket = bound_socket().await;
543 let receiver_socket = bound_socket().await;
544 let pool = packet_pool();
545 let mut sender =
546 UdpBatchSender::<MAX_BATCH_SIZE, TEST_PACKET_SIZE>::new(&sender_socket).unwrap();
547
548 sender
549 .try_queue_packet(
550 packet_from_bytes(&pool, b"one"),
551 receiver_socket.local_addr().unwrap(),
552 )
553 .unwrap();
554 sender
555 .try_queue_packet(
556 packet_from_bytes(&pool, b"two"),
557 receiver_socket.local_addr().unwrap(),
558 )
559 .unwrap();
560
561 sender.flush(&sender_socket).await.unwrap();
562
563 let mut buf = [0u8; TEST_PACKET_SIZE];
564 let (n1, _) = receiver_socket.recv_from(&mut buf).await.unwrap();
565 let first = buf[..n1].to_vec();
566 let (n2, _) = receiver_socket.recv_from(&mut buf).await.unwrap();
567 let second = buf[..n2].to_vec();
568
569 assert!(sender.is_empty());
570 assert_eq!(vec![first, second], vec![b"one".to_vec(), b"two".to_vec()]);
571 }
572
573 #[tokio::test]
574 async fn flushes_sender_batch_with_mixed_targets() {
575 let sender_socket = bound_socket().await;
576 let first_target = bound_socket().await;
577 let second_target = bound_socket().await;
578 let pool = packet_pool();
579 let mut sender =
580 UdpBatchSender::<MAX_BATCH_SIZE, TEST_PACKET_SIZE>::new(&sender_socket).unwrap();
581
582 sender
583 .try_queue_packet(
584 packet_from_bytes(&pool, b"alpha"),
585 first_target.local_addr().unwrap(),
586 )
587 .unwrap();
588 sender
589 .try_queue_packet(
590 packet_from_bytes(&pool, b"beta"),
591 second_target.local_addr().unwrap(),
592 )
593 .unwrap();
594 sender
595 .try_queue_packet(
596 packet_from_bytes(&pool, b"gamma"),
597 first_target.local_addr().unwrap(),
598 )
599 .unwrap();
600
601 sender.flush(&sender_socket).await.unwrap();
602
603 let mut buf = [0u8; TEST_PACKET_SIZE];
604 let (n_first_a, _) = first_target.recv_from(&mut buf).await.unwrap();
605 let first_a = buf[..n_first_a].to_vec();
606 let (n_second, _) = second_target.recv_from(&mut buf).await.unwrap();
607 let second = buf[..n_second].to_vec();
608 let (n_first_b, _) = first_target.recv_from(&mut buf).await.unwrap();
609 let first_b = buf[..n_first_b].to_vec();
610
611 assert_eq!(first_a, b"alpha".to_vec());
612 assert_eq!(second, b"beta".to_vec());
613 assert_eq!(first_b, b"gamma".to_vec());
614 }
615
616 #[tokio::test]
619 async fn flush_discards_undeliverable_datagram_and_drains_the_rest() {
620 const OVERSIZED_PACKET_SIZE: usize = MAX_UDP_PAYLOAD_SIZE + 1;
621
622 let sender_socket = bound_socket().await;
623 let receiver_socket = bound_socket().await;
624 let target = receiver_socket.local_addr().unwrap();
625 let pool = PacketBufPool::<OVERSIZED_PACKET_SIZE>::new(2);
626 let mut sender =
627 UdpBatchSender::<MAX_BATCH_SIZE, OVERSIZED_PACKET_SIZE>::new(&sender_socket).unwrap();
628
629 let mut oversized = pool.get();
630 oversized.truncate(OVERSIZED_PACKET_SIZE);
631 sender.try_queue_packet(oversized, target).unwrap();
632
633 let mut deliverable = pool.get();
634 deliverable[..5].copy_from_slice(b"after");
635 deliverable.truncate(5);
636 sender.try_queue_packet(deliverable, target).unwrap();
637
638 sender.flush(&sender_socket).await.unwrap();
639
640 let mut buf = [0u8; 64];
641 let (len, _) = receiver_socket.recv_from(&mut buf).await.unwrap();
642
643 assert!(sender.is_empty(), "the queue must not retain the discard");
644 assert_eq!(&buf[..len], b"after");
645 assert_eq!(
646 sender.take_discarded_datagrams(),
647 1,
648 "the discard must be accountable, the log is only sampled"
649 );
650 assert_eq!(
651 sender.take_discarded_datagrams(),
652 0,
653 "taking the count must reset it"
654 );
655 }
656
657 #[tokio::test]
660 async fn flush_delivers_a_single_large_datagram_in_one_piece() {
661 const LARGE_PACKET_SIZE: usize = 2048;
662
663 let sender_socket = bound_socket().await;
664 let receiver_socket = bound_socket().await;
665 let target = receiver_socket.local_addr().unwrap();
666 let pool = PacketBufPool::<LARGE_PACKET_SIZE>::new(MAX_BATCH_SIZE + 1);
667 let mut sender =
668 UdpBatchSender::<MAX_BATCH_SIZE, LARGE_PACKET_SIZE>::new(&sender_socket).unwrap();
669 let _receiver =
670 UdpBatchReceiver::<MAX_BATCH_SIZE, LARGE_PACKET_SIZE>::new(&sender_socket, &pool)
671 .unwrap();
672
673 let mut packet = pool.get();
674 packet.truncate(LARGE_PACKET_SIZE);
675 sender.try_queue_packet(packet, target).unwrap();
676 sender.flush(&sender_socket).await.unwrap();
677
678 let mut buf = [0u8; 2 * LARGE_PACKET_SIZE];
679 let (len, _) = receiver_socket.recv_from(&mut buf).await.unwrap();
680
681 assert_eq!(len, LARGE_PACKET_SIZE);
682 assert!(sender.is_empty());
683 assert_eq!(sender.take_discarded_datagrams(), 0);
684 }
685
686 #[tokio::test]
689 async fn coalescing_respects_the_maximum_udp_payload_size() {
690 const LARGE_PACKET_SIZE: usize = 2048;
691
692 let sender_socket = bound_socket().await;
693 let target = bound_socket().await.local_addr().unwrap();
694 let pool = PacketBufPool::<LARGE_PACKET_SIZE>::new(MAX_BATCH_SIZE);
695 let mut sender =
696 UdpBatchSender::<MAX_BATCH_SIZE, LARGE_PACKET_SIZE>::new(&sender_socket).unwrap();
697
698 for _ in 0..MAX_BATCH_SIZE {
699 let mut packet = pool.get();
700 packet.truncate(LARGE_PACKET_SIZE);
701 sender.try_queue_packet(packet, target).unwrap();
702 }
703
704 let (_, segment_size, segments) = sender.fill_scratch_from_front(true);
705
706 assert_eq!(segment_size, LARGE_PACKET_SIZE);
707 assert!(segments >= 1);
708 assert!(
709 segments * segment_size <= MAX_UDP_PAYLOAD_SIZE,
710 "coalesced {segments} segments of {segment_size} bytes exceed the maximum UDP payload"
711 );
712 }
713
714 #[tokio::test]
721 async fn flush_delivers_every_zero_length_datagram() {
722 const PACKETS: usize = 3;
723
724 let sender_socket = bound_socket().await;
725 let receiver_socket = bound_socket().await;
726 let target = receiver_socket.local_addr().unwrap();
727 let pool = packet_pool();
728 let mut sender =
729 UdpBatchSender::<MAX_BATCH_SIZE, TEST_PACKET_SIZE>::new(&sender_socket).unwrap();
730
731 for _ in 0..PACKETS {
732 sender
733 .try_queue_packet(packet_from_bytes(&pool, b""), target)
734 .unwrap();
735 }
736
737 let (_, segment_size, segments) = sender.fill_scratch_from_front(true);
738 assert_eq!(segment_size, 0);
739 assert_eq!(segments, 1, "zero-length datagrams must be sent one by one");
740
741 sender.flush(&sender_socket).await.unwrap();
742
743 assert!(sender.is_empty());
744 assert_eq!(sender.take_discarded_datagrams(), 0);
745
746 let mut buf = [0u8; TEST_PACKET_SIZE];
747 for index in 0..PACKETS {
748 let received =
749 tokio::time::timeout(Duration::from_secs(5), receiver_socket.recv_from(&mut buf))
750 .await
751 .unwrap_or_else(|_| panic!("zero-length datagram {index} never arrived"))
752 .unwrap();
753
754 assert_eq!(received.0, 0);
755 }
756 }
757
758 #[tokio::test]
761 async fn flush_delivers_more_datagrams_than_one_transmit_can_carry() {
762 const LARGE_PACKET_SIZE: usize = 2048;
763 const PACKETS: usize = MAX_BATCH_SIZE;
764
765 let sender_socket = bound_socket().await;
766 let receiver_socket = bound_socket().await;
767 let target = receiver_socket.local_addr().unwrap();
768 let pool = PacketBufPool::<LARGE_PACKET_SIZE>::new(PACKETS);
769 let mut sender =
770 UdpBatchSender::<MAX_BATCH_SIZE, LARGE_PACKET_SIZE>::new(&sender_socket).unwrap();
771
772 for index in 0..PACKETS {
773 let mut packet = pool.get();
774 packet.truncate(LARGE_PACKET_SIZE);
775 packet[0] = index as u8;
776 sender.try_queue_packet(packet, target).unwrap();
777 }
778
779 let receive = tokio::spawn(async move {
782 let mut buf = [0u8; LARGE_PACKET_SIZE];
783 let mut received = Vec::with_capacity(PACKETS);
784 for _ in 0..PACKETS {
785 let (len, _) = receiver_socket.recv_from(&mut buf).await.unwrap();
786 assert_eq!(len, LARGE_PACKET_SIZE);
787 received.push(buf[0]);
788 }
789 received
790 });
791
792 sender.flush(&sender_socket).await.unwrap();
793
794 let received = tokio::time::timeout(Duration::from_secs(10), receive)
795 .await
796 .expect("all datagrams should arrive")
797 .unwrap();
798
799 assert!(sender.is_empty());
800 assert_eq!(
801 received,
802 (0..PACKETS).map(|index| index as u8).collect::<Vec<_>>(),
803 "datagrams arrived out of order or were lost"
804 );
805 }
806
807 #[tokio::test]
808 async fn receive_with_stride_smaller_than_length_splits_segments() {
809 let socket = bound_socket().await;
810 let pool = packet_pool();
811 let mut receiver =
812 UdpBatchReceiver::<MAX_BATCH_SIZE, TEST_PACKET_SIZE>::new(&socket, &pool).unwrap();
813 let source = "127.0.0.1:30000".parse::<SocketAddr>().unwrap();
814
815 receiver.recv_meta[0].addr = source;
816 receiver.recv_meta[0].len = 10;
817 receiver.recv_meta[0].stride = 4;
818 receiver.recv_slots[0][..10].copy_from_slice(b"abcdefghij");
819
820 let mut seen = Vec::new();
821 receiver
822 .handle_received(0, &pool, &mut |packet, addr| {
823 seen.push((packet[..].to_vec(), addr));
824 Ok::<(), ()>(())
825 })
826 .unwrap();
827
828 assert_eq!(
829 seen,
830 vec![
831 (b"abcd".to_vec(), source),
832 (b"efgh".to_vec(), source),
833 (b"ij".to_vec(), source),
834 ]
835 );
836 }
837
838 #[tokio::test]
839 async fn receive_with_stride_at_least_length_uses_single_packet() {
840 let socket = bound_socket().await;
841 let pool = packet_pool();
842 let mut receiver =
843 UdpBatchReceiver::<MAX_BATCH_SIZE, TEST_PACKET_SIZE>::new(&socket, &pool).unwrap();
844 let source = "127.0.0.1:30001".parse::<SocketAddr>().unwrap();
845
846 receiver.recv_meta[0].addr = source;
847 receiver.recv_meta[0].len = 5;
848 receiver.recv_meta[0].stride = 5;
849 receiver.recv_slots[0][..5].copy_from_slice(b"hello");
850
851 let mut seen = Vec::new();
852 receiver
853 .handle_received(0, &pool, &mut |packet, addr| {
854 seen.push((packet[..].to_vec(), addr));
855 Ok::<(), ()>(())
856 })
857 .unwrap();
858
859 assert_eq!(seen, vec![(b"hello".to_vec(), source)]);
860 }
861
862 #[test]
863 fn refuses_to_grow_beyond_batch_capacity() {
864 let runtime = tokio::runtime::Runtime::new().unwrap();
865 runtime.block_on(async {
866 let socket = bound_socket().await;
867 let pool = packet_pool();
868 let mut sender =
869 UdpBatchSender::<MAX_BATCH_SIZE, TEST_PACKET_SIZE>::new(&socket).unwrap();
870
871 for _ in 0..MAX_BATCH_SIZE {
872 sender
873 .try_queue_packet(packet_from_bytes(&pool, b"x"), socket.local_addr().unwrap())
874 .unwrap();
875 }
876
877 assert!(
878 sender
879 .try_queue_packet(
880 packet_from_bytes(&pool, b"overflow"),
881 socket.local_addr().unwrap()
882 )
883 .is_err()
884 );
885 });
886 }
887}