1use std::{
2 future::{poll_fn, Future},
3 io,
4 net::SocketAddr,
5 pin::Pin,
6 task::{Context, Poll},
7 time::{Duration, Instant},
8};
9
10#[cfg(test)]
11use std::{
12 cell::{Cell, RefCell},
13 collections::VecDeque,
14};
15
16use tokio::time;
17
18use crate::{
19 client::{
20 echo_sent_event,
21 schedule::{instant_abs_diff, ProbeSchedule},
22 validate_datagram_length,
23 },
24 config::ClientConfig,
25 error::ClientError,
26 event::{ClientEvent, OpenOutcome},
27 receive::{drain_tx_timestamps, try_enable_tx_timestamping, try_recv_tokio_datagram},
28 session::machine::{
29 recv_buffer_size, OpenDatagramDisposition, PreparedOpenAcceptance, PreparedOpenRequest,
30 PreparedProbe, SessionMachine, TimeoutBatch, MAX_OPEN_PACKET_SIZE,
31 },
32 socket::{connect_tokio_udp_socket, resolve_remote_tokio, validate_open_timeouts},
33 socket_options::{apply_traffic_class_to_tokio_socket, clear_dscp_on_tokio_socket},
34 timing::ClientTimestamp,
35};
36
37const OPEN_POLL_WORK_BUDGET: usize = 64;
43
44const RECV_POLL_WORK_BUDGET: usize = 64;
56
57#[cfg(test)]
58use crate::client::ProbeSendTimestamps;
59
60#[derive(Debug)]
61struct PreparedAsyncOpen {
62 machine: PreparedOpenAcceptance,
63 schedule: Option<ProbeSchedule>,
64 recv_buffer_len: Option<usize>,
65 negotiated_traffic_class: Option<u8>,
66}
67
68#[derive(Debug)]
69struct PreparedAsyncOpenFailure {
70 primary: ClientError,
71 machine: PreparedOpenAcceptance,
72}
73
74#[derive(Debug)]
75enum AsyncOpenCleanup {
76 Acceptance {
77 primary: ClientError,
78 packet: Option<Box<[u8]>>,
79 },
80 Adapter(Box<PreparedAsyncOpenFailure>),
81}
82
83impl AsyncOpenCleanup {
84 fn packet(&self) -> Option<&[u8]> {
85 match self {
86 Self::Acceptance { packet, .. } => packet.as_deref(),
87 Self::Adapter(failure) => failure.machine.cleanup_close_packet(),
88 }
89 }
90
91 fn into_primary(self) -> ClientError {
92 match self {
93 Self::Acceptance { primary, .. } => primary,
94 Self::Adapter(failure) => failure.primary,
95 }
96 }
97}
98
99#[derive(Debug)]
104pub(crate) struct AsyncOpenState {
105 attempt: usize,
106 deadline: Option<Instant>,
107 deadline_timer: Option<Pin<Box<time::Sleep>>>,
108 request_submitted: bool,
109 stop_after_current_attempt: bool,
110 stopped_after_current_attempt: bool,
111 buffer: [u8; MAX_OPEN_PACKET_SIZE],
112 cleanup: Option<AsyncOpenCleanup>,
113}
114
115impl AsyncOpenState {
116 pub(crate) fn new() -> Self {
117 Self {
118 attempt: 0,
119 deadline: None,
120 deadline_timer: None,
121 request_submitted: false,
122 stop_after_current_attempt: false,
123 stopped_after_current_attempt: false,
124 buffer: [0_u8; MAX_OPEN_PACKET_SIZE],
125 cleanup: None,
126 }
127 }
128
129 fn start_attempt(&mut self, timeout: Duration) -> Result<(), ClientError> {
130 let deadline = Instant::now()
131 .checked_add(timeout)
132 .ok_or(ClientError::DurationOverflow)?;
133 self.deadline = Some(deadline);
134 self.deadline_timer = Some(Box::pin(time::sleep_until(deadline.into())));
135 self.request_submitted = false;
136 Ok(())
137 }
138
139 fn finish_attempt(&mut self) {
140 self.attempt += 1;
141 self.deadline = None;
142 self.deadline_timer = None;
143 self.request_submitted = false;
144 self.stopped_after_current_attempt = self.stop_after_current_attempt;
145 }
146
147 pub(crate) fn has_in_flight_work(&self) -> bool {
148 self.request_submitted || self.cleanup.is_some()
149 }
150
151 pub(crate) fn request_stop_after_current_attempt(&mut self) {
152 self.stop_after_current_attempt = true;
153 }
154
155 pub(crate) fn stopped_after_current_attempt(&self) -> bool {
156 self.stopped_after_current_attempt
157 }
158
159 #[cfg(test)]
160 pub(crate) fn has_retained_cleanup(&self) -> bool {
161 self.cleanup.is_some()
162 }
163
164 fn deadline(&self) -> Instant {
165 self.deadline
166 .expect("an open attempt has an active deadline")
167 }
168
169 fn deadline_timer(&mut self) -> &mut Pin<Box<time::Sleep>> {
170 self.deadline_timer
171 .as_mut()
172 .expect("an open attempt has an active deadline timer")
173 }
174}
175
176#[cfg(test)]
177#[derive(Debug)]
178enum InjectedSend {
179 WouldBlock,
180 Error,
181 ReportedLength(usize),
182}
183
184#[cfg(test)]
185#[derive(Debug, Default)]
186struct AsyncClientTestHooks {
187 sends: RefCell<VecDeque<InjectedSend>>,
188 send_attempts: Cell<usize>,
189 receive_would_block: Cell<usize>,
190 inject_recv_interrupted: Cell<usize>,
195 fail_open_dscp: Cell<bool>,
196 fail_cleanup_send: Cell<bool>,
197 fail_peer_close_dscp: Cell<bool>,
198 fail_dscp_restore: Cell<bool>,
199 pause_open_cleanup_before_send: Cell<bool>,
200 pause_open_before_writable: Cell<bool>,
201 pause_open_before_readable: Cell<bool>,
202 pause_probe_before_writable: Cell<bool>,
203 pause_close_before_writable: Cell<bool>,
204 pause_close_after_would_block: Cell<bool>,
205 probe_timestamps: RefCell<VecDeque<ProbeSendTimestamps>>,
206 close_sent_at: Cell<Option<ClientTimestamp>>,
207}
208
209#[cfg(test)]
210impl AsyncClientTestHooks {
211 fn try_send(&self, socket: &tokio::net::UdpSocket, packet: &[u8]) -> io::Result<usize> {
212 self.send_attempts.set(self.send_attempts.get() + 1);
213 if let Some(injected) = self.sends.borrow_mut().pop_front() {
214 return match injected {
215 InjectedSend::WouldBlock => Err(io::Error::from(io::ErrorKind::WouldBlock)),
216 InjectedSend::Error => Err(io::Error::other("injected send failure")),
217 InjectedSend::ReportedLength(bytes) => Ok(bytes),
218 };
219 }
220 socket.try_send(packet)
221 }
222
223 fn take_probe_timestamps(&self) -> Option<ProbeSendTimestamps> {
224 self.probe_timestamps.borrow_mut().pop_front()
225 }
226
227 fn take_injected_interrupted(&self) -> bool {
231 let remaining = self.inject_recv_interrupted.get();
232 if remaining == 0 {
233 return false;
234 }
235 self.inject_recv_interrupted.set(remaining - 1);
236 true
237 }
238}
239
240#[derive(Debug)]
271pub struct AsyncClient {
272 socket: tokio::net::UdpSocket,
273 machine: SessionMachine,
274 schedule: Option<ProbeSchedule>,
275 remote: SocketAddr,
276 recv_buffer: Vec<u8>,
277 applied_traffic_class: Option<u8>,
278 prepared_open: Option<PreparedOpenRequest>,
279 prepared_probe: Option<PreparedProbe>,
280 tx_timestamping_enabled: bool,
283 #[cfg(test)]
284 test_hooks: AsyncClientTestHooks,
285}
286
287impl AsyncClient {
288 pub async fn connect(config: ClientConfig) -> Result<Self, ClientError> {
295 tokio::runtime::Handle::try_current().map_err(|_| ClientError::NoTokioRuntime)?;
296 validate_open_timeouts(&config.open_timeouts)?;
297 let remote = resolve_remote_tokio(&config).await?;
298 let machine = SessionMachine::new(config.clone(), remote)?;
299 let prepared_open = machine.prepare_open_request()?;
300 let socket = connect_tokio_udp_socket(&config.socket_config, remote)?;
301
302 Ok(Self {
303 socket,
304 machine,
305 schedule: None,
306 remote,
307 recv_buffer: vec![0_u8; recv_buffer_size(false, None)?],
308 applied_traffic_class: None,
309 prepared_open: Some(prepared_open),
310 prepared_probe: None,
311 tx_timestamping_enabled: false,
312 #[cfg(test)]
313 test_hooks: AsyncClientTestHooks::default(),
314 })
315 }
316
317 pub async fn open(&mut self) -> Result<OpenOutcome, ClientError> {
329 let mut state = AsyncOpenState::new();
330 poll_fn(|cx| self.poll_open(&mut state, cx)).await
331 }
332
333 pub async fn send_probe(&mut self) -> Result<Vec<ClientEvent>, ClientError> {
340 poll_fn(|cx| self.poll_send_probe(cx)).await
341 }
342
343 pub async fn recv(&mut self) -> Result<Vec<ClientEvent>, ClientError> {
349 poll_fn(|cx| self.poll_recv(cx)).await
350 }
351
352 pub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError> {
354 self.poll_timeouts_at(Instant::now())
355 }
356
357 pub fn poll_timeouts_at(&mut self, now: Instant) -> Result<Vec<ClientEvent>, ClientError> {
359 self.drain_tx_timestamps()?;
360 self.machine.poll_timeouts_at(now)
361 }
362
363 pub(crate) fn poll_timeouts_bounded_at(
364 &mut self,
365 now: Instant,
366 limit: usize,
367 ) -> Result<TimeoutBatch, ClientError> {
368 self.drain_tx_timestamps()?;
369 self.machine.poll_timeouts_bounded_at(now, limit)
370 }
371
372 pub async fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError> {
379 poll_fn(|cx| self.poll_close(cx)).await
380 }
381
382 pub(crate) fn poll_open(
383 &mut self,
384 state: &mut AsyncOpenState,
385 cx: &mut Context<'_>,
386 ) -> Poll<Result<OpenOutcome, ClientError>> {
387 if self.prepared_open.is_none() {
388 if let Err(error) = self.machine.prepare_open_request() {
389 return Poll::Ready(Err(error));
390 }
391 unreachable!("connected async clients retain an open request");
392 }
393
394 for _ in 0..OPEN_POLL_WORK_BUDGET {
395 if state.cleanup.is_some() {
396 return self.poll_open_cleanup(state, cx);
397 }
398
399 let attempt_count = self.machine.config().open_timeouts.len();
400 if state.attempt >= attempt_count {
401 return Poll::Ready(Err(ClientError::OpenTimeout));
402 }
403 if state.stop_after_current_attempt && state.deadline.is_none() {
404 state.stopped_after_current_attempt = true;
405 return Poll::Ready(Err(ClientError::OpenTimeout));
406 }
407 if state.deadline.is_none() {
408 let timeout = self.machine.config().open_timeouts[state.attempt];
409 if let Err(error) = state.start_attempt(timeout) {
410 return Poll::Ready(Err(error));
411 }
412 }
413
414 if !state.request_submitted {
415 #[cfg(test)]
416 if self.test_hooks.pause_open_before_writable.replace(false) {
417 return Poll::Pending;
418 }
419 match poll_writable_until(&self.socket, state.deadline_timer(), cx) {
420 Poll::Pending => return Poll::Pending,
421 Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
422 Poll::Ready(Ok(false)) => {
423 state.finish_attempt();
424 continue;
425 }
426 Poll::Ready(Ok(true)) => {}
427 }
428
429 let request = self
430 .prepared_open
431 .as_ref()
432 .expect("connected clients retain their prepared open request");
433 #[cfg(test)]
434 let send_result = self.test_hooks.try_send(&self.socket, &request.bytes);
435 #[cfg(not(test))]
436 let send_result = self.socket.try_send(&request.bytes);
437 match send_result {
438 Ok(bytes) => {
439 state.request_submitted = true;
440 if let Err(error) = validate_datagram_length(request.bytes.len(), bytes) {
441 return Poll::Ready(Err(error));
442 }
443 }
444 Err(error) if error.kind() == io::ErrorKind::WouldBlock => continue,
445 Err(error) => return Poll::Ready(Err(ClientError::Socket(error))),
446 }
447 }
448
449 #[cfg(test)]
450 if self.test_hooks.pause_open_before_readable.replace(false) {
451 return Poll::Pending;
452 }
453 match poll_readable_until(&self.socket, state.deadline_timer(), cx) {
454 Poll::Pending => return Poll::Pending,
455 Poll::Ready(Err(error)) => return Poll::Ready(Err(error)),
456 Poll::Ready(Ok(false)) => {
457 state.finish_attempt();
458 if state.stopped_after_current_attempt() {
459 return Poll::Ready(Err(ClientError::OpenTimeout));
460 }
461 continue;
462 }
463 Poll::Ready(Ok(true)) => {}
464 }
465
466 #[cfg(test)]
467 if self.test_hooks.take_injected_interrupted() {
468 continue;
469 }
470 let datagram = match try_recv_tokio_datagram(&self.socket, &mut state.buffer) {
471 Ok(datagram) => datagram,
472 Err(error)
479 if matches!(
480 error.kind(),
481 io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted
482 ) =>
483 {
484 continue
485 }
486 Err(error) => return Poll::Ready(Err(ClientError::Socket(error))),
487 };
488 if datagram.received_at.mono > state.deadline() {
489 state.finish_attempt();
490 if state.stopped_after_current_attempt() {
491 return Poll::Ready(Err(ClientError::OpenTimeout));
492 }
493 continue;
494 }
495
496 let reply = match self
497 .machine
498 .inspect_open_datagram(&state.buffer[..datagram.len])
499 {
500 Ok(OpenDatagramDisposition::Ignore) => continue,
501 Ok(OpenDatagramDisposition::Trusted(reply)) => reply,
502 Err(error) => return Poll::Ready(Err(error)),
503 };
504 let machine = match self
505 .machine
506 .prepare_open_acceptance(reply, datagram.received_at)
507 {
508 Ok(machine) => machine,
509 Err(failure) => {
510 state.cleanup = Some(AsyncOpenCleanup::Acceptance {
511 primary: failure.primary,
512 packet: failure.cleanup_close,
513 });
514 continue;
515 }
516 };
517 let prepared = match self.prepare_async_open(machine, datagram.received_at) {
518 Ok(prepared) => prepared,
519 Err(failure) => {
520 state.cleanup = Some(AsyncOpenCleanup::Adapter(failure));
521 continue;
522 }
523 };
524 return Poll::Ready(Ok(self.commit_async_open(prepared)));
525 }
526
527 cx.waker().wake_by_ref();
530 Poll::Pending
531 }
532
533 pub(crate) fn poll_send_probe(
534 &mut self,
535 cx: &mut Context<'_>,
536 ) -> Poll<Result<Vec<ClientEvent>, ClientError>> {
537 if let Err(error) = self.machine.ensure_open() {
538 return Poll::Ready(Err(error));
539 }
540 if self.prepared_probe.is_none() {
541 match self.machine.prepare_probe() {
542 Ok(prepared) => self.prepared_probe = prepared,
543 Err(error) => return Poll::Ready(Err(error)),
544 }
545 }
546 if self.prepared_probe.is_none() {
547 return Poll::Ready(Ok(Vec::new()));
548 }
549
550 loop {
551 #[cfg(test)]
552 if self.test_hooks.pause_probe_before_writable.replace(false) {
553 return Poll::Pending;
554 }
555 match self.socket.poll_send_ready(cx) {
556 Poll::Pending => return Poll::Pending,
557 Poll::Ready(Ok(())) => {}
558 Poll::Ready(Err(error)) => {
559 return Poll::Ready(Err(ClientError::Socket(error)));
560 }
561 }
562
563 #[cfg(test)]
564 let test_timestamps = self.test_hooks.take_probe_timestamps();
565 #[cfg(not(test))]
566 let permission_at = Instant::now();
567 #[cfg(test)]
568 let permission_at = test_timestamps
569 .map(|timestamps| timestamps.permission_at)
570 .unwrap_or_else(Instant::now);
571
572 let schedule = self
573 .schedule
574 .as_mut()
575 .expect("open sessions always have a probe schedule");
576 if !schedule.permit_probe_at(permission_at) {
577 self.prepared_probe = None;
578 return Poll::Ready(Ok(Vec::new()));
579 }
580 let prepared = self
581 .prepared_probe
582 .as_ref()
583 .expect("prepared probe was retained across readiness");
584 let machine_preflight = match self.machine.preflight_probe_commit(prepared) {
585 Ok(preflight) => preflight,
586 Err(error) => return Poll::Ready(Err(error)),
587 };
588 let schedule_commit =
589 match schedule.preflight_caller_commit(machine_preflight.next_packets_sent) {
590 Ok(commit) => commit,
591 Err(error) => return Poll::Ready(Err(error)),
592 };
593 let mut events = Vec::new();
594 if let Err(source) = events.try_reserve(1) {
595 return Poll::Ready(Err(ClientError::AllocationFailed {
596 operation: "probe event result",
597 source,
598 }));
599 }
600
601 let expected_bytes = prepared.bytes.len();
602 let scheduled_at = schedule_commit.scheduled_at;
603 #[cfg(not(test))]
609 let send_anchor = ClientTimestamp::now();
610 #[cfg(test)]
611 let send_anchor = test_timestamps
612 .map(|timestamps| timestamps.send_anchor)
613 .unwrap_or_else(ClientTimestamp::now);
614 let machine_commit = match self
615 .machine
616 .finalize_probe_commit(machine_preflight, send_anchor)
617 {
618 Ok(commit) => commit,
619 Err(error) => return Poll::Ready(Err(error)),
620 };
621 #[cfg(not(test))]
622 let send_call_start = Instant::now();
623 #[cfg(test)]
624 let send_call_start = test_timestamps
625 .map(|timestamps| timestamps.send_call_start)
626 .unwrap_or_else(Instant::now);
627 #[cfg(test)]
628 let send_result = self.test_hooks.try_send(&self.socket, &prepared.bytes);
629 #[cfg(not(test))]
630 let send_result = self.socket.try_send(&prepared.bytes);
631 let bytes = match send_result {
632 Ok(bytes) => bytes,
633 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
637 self.machine.invalidate_kernel_tx_correlation();
638 continue;
639 }
640 Err(error) => {
641 self.machine.invalidate_kernel_tx_correlation();
642 return Poll::Ready(Err(ClientError::Socket(error)));
643 }
644 };
645
646 #[cfg(not(test))]
647 let send_finished_at = Instant::now();
648 #[cfg(test)]
649 let send_finished_at = test_timestamps
650 .map(|timestamps| timestamps.send_finished_at)
651 .unwrap_or_else(Instant::now);
652 #[cfg(not(test))]
656 let sent_at = ClientTimestamp::now();
657 #[cfg(test)]
658 let sent_at = test_timestamps
659 .map(|timestamps| timestamps.sent_at)
660 .unwrap_or_else(ClientTimestamp::now);
661 let sent = self
662 .machine
663 .commit_probe_sent(machine_commit, sent_at, bytes);
664 schedule.commit(schedule_commit);
665 self.prepared_probe = None;
666
667 let send_call = send_finished_at.saturating_duration_since(send_call_start);
668 let timer_error = instant_abs_diff(sent_at.mono, scheduled_at);
669 if let Err(error) = validate_datagram_length(expected_bytes, bytes) {
670 return Poll::Ready(Err(error));
671 }
672 if let Err(error) = self.drain_tx_timestamps() {
673 return Poll::Ready(Err(error));
674 }
675 events.push(echo_sent_event(
676 self.remote,
677 sent,
678 send_call,
679 scheduled_at,
680 timer_error,
681 ));
682 return Poll::Ready(Ok(events));
683 }
684 }
685
686 pub(crate) fn poll_recv(
687 &mut self,
688 cx: &mut Context<'_>,
689 ) -> Poll<Result<Vec<ClientEvent>, ClientError>> {
690 if let Err(error) = self.machine.ensure_open() {
691 return Poll::Ready(Err(error));
692 }
693
694 for _ in 0..RECV_POLL_WORK_BUDGET {
695 match self.socket.poll_recv_ready(cx) {
696 Poll::Pending => return Poll::Pending,
697 Poll::Ready(Ok(())) => {}
698 Poll::Ready(Err(error)) => {
699 return Poll::Ready(Err(ClientError::Socket(error)));
700 }
701 }
702 #[cfg(test)]
703 if self.test_hooks.receive_would_block.get() > 0 {
704 self.test_hooks
705 .receive_would_block
706 .set(self.test_hooks.receive_would_block.get() - 1);
707 continue;
708 }
709 #[cfg(test)]
710 if self.test_hooks.take_injected_interrupted() {
711 continue;
712 }
713 let datagram = match try_recv_tokio_datagram(&self.socket, &mut self.recv_buffer) {
714 Ok(datagram) => datagram,
715 Err(error)
719 if matches!(
720 error.kind(),
721 io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted
722 ) =>
723 {
724 continue
725 }
726 Err(error) => return Poll::Ready(Err(ClientError::Socket(error))),
727 };
728
729 if let Err(error) = self.drain_tx_timestamps() {
734 return Poll::Ready(Err(error));
735 }
736
737 let events = match self.machine.process_received_echo_packet(
738 &self.recv_buffer[..datagram.len],
739 datagram.received_at,
740 datagram.meta,
741 ) {
742 Ok(events) => events,
743 Err(error) => return Poll::Ready(Err(error)),
744 };
745 if self.machine.is_peer_closed() {
746 self.schedule = None;
747 self.prepared_probe = None;
748 if self.clear_peer_close_dscp().is_ok() {
749 self.applied_traffic_class = None;
750 }
751 }
752 return Poll::Ready(Ok(events));
753 }
754
755 cx.waker().wake_by_ref();
759 Poll::Pending
760 }
761
762 pub(crate) fn poll_close(
763 &mut self,
764 cx: &mut Context<'_>,
765 ) -> Poll<Result<Vec<ClientEvent>, ClientError>> {
766 loop {
767 let prepared = match self.machine.prepare_close() {
768 Ok(prepared) => prepared,
769 Err(error) => return Poll::Ready(Err(error)),
770 };
771 let mut events = Vec::new();
772 if let Err(source) = events.try_reserve(1) {
773 return Poll::Ready(Err(ClientError::AllocationFailed {
774 operation: "close event result",
775 source,
776 }));
777 }
778 let previous_traffic_class = self.applied_traffic_class;
779 let expected_bytes = prepared.bytes.len();
780
781 #[cfg(test)]
782 if self.test_hooks.pause_close_before_writable.replace(false) {
783 return Poll::Pending;
784 }
785 match self.socket.poll_send_ready(cx) {
786 Poll::Pending => return Poll::Pending,
787 Poll::Ready(Ok(())) => {}
788 Poll::Ready(Err(error)) => {
789 return Poll::Ready(Err(ClientError::Socket(error)));
790 }
791 }
792 if let Err(error) = self.clear_close_dscp() {
793 return Poll::Ready(Err(error));
794 }
795 let mut rollback = DscpRollback::armed(
796 &self.socket,
797 self.remote,
798 previous_traffic_class,
799 #[cfg(test)]
800 &self.test_hooks.fail_dscp_restore,
801 );
802 #[cfg(test)]
803 let send_result = self.test_hooks.try_send(&self.socket, prepared.bytes);
804 #[cfg(not(test))]
805 let send_result = self.socket.try_send(prepared.bytes);
806 let bytes = match send_result {
807 Ok(bytes) => bytes,
808 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {
809 if let Err(error) = rollback.restore() {
810 return Poll::Ready(Err(error));
811 }
812 #[cfg(test)]
813 if self.test_hooks.pause_close_after_would_block.replace(false) {
814 return Poll::Pending;
815 }
816 continue;
817 }
818 Err(error) => return Poll::Ready(Err(ClientError::Socket(error))),
819 };
820
821 #[cfg(not(test))]
822 let close_sent_at = ClientTimestamp::now();
823 #[cfg(test)]
824 let close_sent_at = self
825 .test_hooks
826 .close_sent_at
827 .take()
828 .unwrap_or_else(ClientTimestamp::now);
829 let event = self
830 .machine
831 .commit_local_close(prepared.commit, close_sent_at);
832 rollback.disarm();
833 self.schedule = None;
834 self.prepared_probe = None;
835 self.applied_traffic_class = None;
836
837 if let Err(error) = validate_datagram_length(expected_bytes, bytes) {
838 return Poll::Ready(Err(error));
839 }
840 events.push(event);
841 return Poll::Ready(Ok(events));
842 }
843 }
844
845 pub fn next_send_deadline(&self) -> Option<Instant> {
847 if !self.machine.is_open() {
848 return None;
849 }
850 self.schedule.as_ref()?.next_send_deadline()
851 }
852
853 pub(crate) fn probe_interval(&self) -> Option<Duration> {
854 if !self.machine.is_open() {
855 return None;
856 }
857 self.schedule.as_ref().map(ProbeSchedule::interval)
858 }
859
860 pub fn probe_timeout(&self) -> Duration {
862 self.machine.probe_timeout()
863 }
864
865 pub fn is_run_complete(&self) -> bool {
867 self.machine.is_terminal()
868 || self
869 .schedule
870 .as_ref()
871 .is_some_and(|schedule| schedule.is_finished() && self.machine.pending_is_empty())
872 }
873
874 pub fn is_peer_closed(&self) -> bool {
876 self.machine.is_peer_closed()
877 }
878
879 pub(crate) fn remote_addr(&self) -> SocketAddr {
880 self.remote
881 }
882
883 pub(crate) fn packets_sent(&self) -> u64 {
884 self.machine.packets_sent()
885 }
886
887 pub(crate) fn next_probe_timeout_deadline(&self) -> Option<Instant> {
888 self.machine.next_probe_timeout_deadline()
889 }
890
891 pub(crate) fn latest_probe_timeout_deadline(&self) -> Option<Instant> {
892 self.machine.latest_probe_timeout_deadline()
893 }
894
895 pub(crate) fn discard_prepared_probe(&mut self) {
896 self.prepared_probe = None;
897 }
898
899 #[cfg(test)]
900 pub(crate) fn retain_open_cleanup_once(&self) {
901 self.test_hooks.fail_open_dscp.set(true);
902 self.test_hooks.pause_open_cleanup_before_send.set(true);
903 }
904
905 #[cfg(test)]
906 pub(crate) fn poll_recv_ready_for_test(&self, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
907 self.socket.poll_recv_ready(cx)
908 }
909
910 #[cfg(test)]
911 pub(crate) fn remove_pending_for_test(
912 &mut self,
913 wire_seq: u32,
914 ) -> Option<crate::probe::PendingProbe> {
915 self.machine.remove_pending_for_test(wire_seq)
916 }
917
918 #[cfg(test)]
919 pub(crate) fn replace_pending_for_test(&mut self, probe: crate::probe::PendingProbe) {
920 self.machine.replace_pending_for_test(probe);
921 }
922
923 pub(crate) fn skip_missed_probe_slots_at(&mut self, now: Instant) -> Result<(), ClientError> {
924 let Some(schedule) = self.schedule.as_mut() else {
925 return Ok(());
926 };
927 let Some(deadline) = schedule.next_send_deadline() else {
928 return Ok(());
929 };
930 if deadline > now {
931 return Ok(());
932 }
933 let commit = schedule.preflight_managed_commit(deadline, now)?;
934 schedule.commit(commit);
935 Ok(())
936 }
937
938 fn prepare_async_open(
939 &mut self,
940 machine: PreparedOpenAcceptance,
941 opened_at: ClientTimestamp,
942 ) -> Result<PreparedAsyncOpen, Box<PreparedAsyncOpenFailure>> {
943 let Some(negotiated) = machine.normal_negotiated() else {
944 return Ok(PreparedAsyncOpen {
945 machine,
946 schedule: None,
947 recv_buffer_len: None,
948 negotiated_traffic_class: None,
949 });
950 };
951 let schedule = match ProbeSchedule::new(opened_at.mono, negotiated) {
952 Ok(schedule) => schedule,
953 Err(primary) => {
954 return Err(Box::new(PreparedAsyncOpenFailure { primary, machine }));
955 }
956 };
957 let recv_buffer_len = match recv_buffer_size(self.machine.has_hmac(), Some(negotiated)) {
958 Ok(size) => size,
959 Err(primary) => {
960 return Err(Box::new(PreparedAsyncOpenFailure { primary, machine }));
961 }
962 };
963 let negotiated_traffic_class = match u8::try_from(negotiated.params.dscp) {
964 Ok(traffic_class) => traffic_class,
965 Err(_) => {
966 return Err(Box::new(PreparedAsyncOpenFailure {
967 primary: ClientError::InvalidConfig {
968 reason: "negotiated dscp must be in range 0..=255".to_owned(),
969 },
970 machine,
971 }));
972 }
973 };
974 let additional = recv_buffer_len.saturating_sub(self.recv_buffer.len());
975 if let Err(source) = self.recv_buffer.try_reserve(additional) {
976 return Err(Box::new(PreparedAsyncOpenFailure {
977 primary: ClientError::AllocationFailed {
978 operation: "negotiated receive buffer",
979 source,
980 },
981 machine,
982 }));
983 }
984 #[cfg(test)]
985 let dscp_result = if self.test_hooks.fail_open_dscp.replace(false) {
986 Err(ClientError::SocketOption {
987 operation: "set negotiated DSCP",
988 remote: self.remote,
989 source: io::Error::other("injected negotiated DSCP failure"),
990 })
991 } else {
992 apply_traffic_class_to_tokio_socket(&self.socket, self.remote, negotiated_traffic_class)
993 };
994 #[cfg(not(test))]
995 let dscp_result = apply_traffic_class_to_tokio_socket(
996 &self.socket,
997 self.remote,
998 negotiated_traffic_class,
999 );
1000 if let Err(primary) = dscp_result {
1001 self.restore_dscp_best_effort(self.applied_traffic_class);
1002 return Err(Box::new(PreparedAsyncOpenFailure { primary, machine }));
1003 }
1004
1005 Ok(PreparedAsyncOpen {
1006 machine,
1007 schedule: Some(schedule),
1008 recv_buffer_len: Some(recv_buffer_len),
1009 negotiated_traffic_class: Some(negotiated_traffic_class),
1010 })
1011 }
1012
1013 fn commit_async_open(&mut self, prepared: PreparedAsyncOpen) -> OpenOutcome {
1014 if let Some(recv_buffer_len) = prepared.recv_buffer_len {
1015 self.recv_buffer.resize(recv_buffer_len, 0);
1016 }
1017 let outcome = self.machine.commit_open(prepared.machine);
1018 self.schedule = prepared.schedule;
1019 self.applied_traffic_class = prepared.negotiated_traffic_class;
1020 self.prepared_open = None;
1021 self.tx_timestamping_enabled = try_enable_tx_timestamping(&self.socket);
1022 outcome
1023 }
1024
1025 fn drain_tx_timestamps(&mut self) -> Result<(), ClientError> {
1028 if !self.tx_timestamping_enabled {
1029 return Ok(());
1030 }
1031 let machine = &mut self.machine;
1032 drain_tx_timestamps(&self.socket, |id, timestamp| {
1033 machine.record_kernel_tx_timestamp(id, timestamp);
1034 })
1035 .map_err(ClientError::Socket)
1036 }
1037
1038 fn poll_open_cleanup(
1039 &self,
1040 state: &mut AsyncOpenState,
1041 cx: &mut Context<'_>,
1042 ) -> Poll<Result<OpenOutcome, ClientError>> {
1043 let Some(packet) = state
1044 .cleanup
1045 .as_ref()
1046 .expect("cleanup polling requires a retained primary error")
1047 .packet()
1048 else {
1049 return Poll::Ready(Err(state.cleanup.take().unwrap().into_primary()));
1050 };
1051
1052 #[cfg(test)]
1053 if self
1054 .test_hooks
1055 .pause_open_cleanup_before_send
1056 .replace(false)
1057 {
1058 return Poll::Pending;
1059 }
1060
1061 #[cfg(test)]
1062 if self.test_hooks.fail_cleanup_send.replace(false) {
1063 return Poll::Ready(Err(state.cleanup.take().unwrap().into_primary()));
1064 }
1065 #[cfg(test)]
1066 let send_result = self.test_hooks.try_send(&self.socket, packet);
1067 #[cfg(not(test))]
1068 let send_result = self.socket.try_send(packet);
1069 match send_result {
1070 Ok(_) => return Poll::Ready(Err(state.cleanup.take().unwrap().into_primary())),
1071 Err(error) if error.kind() == io::ErrorKind::WouldBlock => {}
1072 Err(_) => return Poll::Ready(Err(state.cleanup.take().unwrap().into_primary())),
1073 }
1074
1075 match poll_writable_until(&self.socket, state.deadline_timer(), cx) {
1076 Poll::Pending => Poll::Pending,
1077 Poll::Ready(Ok(true)) => {
1078 cx.waker().wake_by_ref();
1081 Poll::Pending
1082 }
1083 Poll::Ready(Ok(false) | Err(_)) => {
1084 Poll::Ready(Err(state.cleanup.take().unwrap().into_primary()))
1085 }
1086 }
1087 }
1088
1089 fn clear_peer_close_dscp(&self) -> Result<(), ClientError> {
1090 #[cfg(test)]
1091 if self.test_hooks.fail_peer_close_dscp.replace(false) {
1092 return Err(ClientError::SocketOption {
1093 operation: "clear DSCP before close",
1094 remote: self.remote,
1095 source: io::Error::other("injected peer-close DSCP cleanup failure"),
1096 });
1097 }
1098 clear_dscp_on_tokio_socket(&self.socket, self.remote)
1099 }
1100
1101 fn clear_close_dscp(&self) -> Result<(), ClientError> {
1102 clear_dscp_on_tokio_socket(&self.socket, self.remote)
1103 }
1104
1105 fn restore_dscp_best_effort(&self, previous_traffic_class: Option<u8>) {
1106 let _ = match previous_traffic_class {
1107 Some(traffic_class) => {
1108 apply_traffic_class_to_tokio_socket(&self.socket, self.remote, traffic_class)
1109 }
1110 None => clear_dscp_on_tokio_socket(&self.socket, self.remote),
1111 };
1112 }
1113}
1114
1115struct DscpRollback<'a> {
1116 socket: &'a tokio::net::UdpSocket,
1117 remote: SocketAddr,
1118 previous_traffic_class: Option<u8>,
1119 armed: bool,
1120 #[cfg(test)]
1121 fail_restore: &'a Cell<bool>,
1122}
1123
1124impl<'a> DscpRollback<'a> {
1125 fn armed(
1126 socket: &'a tokio::net::UdpSocket,
1127 remote: SocketAddr,
1128 previous_traffic_class: Option<u8>,
1129 #[cfg(test)] fail_restore: &'a Cell<bool>,
1130 ) -> Self {
1131 Self {
1132 socket,
1133 remote,
1134 previous_traffic_class,
1135 armed: true,
1136 #[cfg(test)]
1137 fail_restore,
1138 }
1139 }
1140
1141 fn restore(&mut self) -> Result<(), ClientError> {
1142 if !self.armed {
1143 return Ok(());
1144 }
1145 #[cfg(test)]
1146 if self.fail_restore.replace(false) {
1147 return Err(ClientError::SocketOption {
1148 operation: "restore negotiated DSCP",
1149 remote: self.remote,
1150 source: io::Error::other("injected DSCP restore failure"),
1151 });
1152 }
1153 match self.previous_traffic_class {
1154 Some(traffic_class) => {
1155 apply_traffic_class_to_tokio_socket(self.socket, self.remote, traffic_class)?
1156 }
1157 None => clear_dscp_on_tokio_socket(self.socket, self.remote)?,
1158 }
1159 self.armed = false;
1160 Ok(())
1161 }
1162
1163 fn disarm(&mut self) {
1164 self.armed = false;
1165 }
1166}
1167
1168impl Drop for DscpRollback<'_> {
1169 fn drop(&mut self) {
1170 if self.armed {
1171 let _ = self.restore();
1172 }
1173 }
1174}
1175
1176fn poll_writable_until(
1177 socket: &tokio::net::UdpSocket,
1178 deadline: &mut Pin<Box<time::Sleep>>,
1179 cx: &mut Context<'_>,
1180) -> Poll<Result<bool, ClientError>> {
1181 match socket.poll_send_ready(cx) {
1182 Poll::Ready(Ok(())) => Poll::Ready(Ok(true)),
1183 Poll::Ready(Err(error)) => Poll::Ready(Err(ClientError::Socket(error))),
1184 Poll::Pending => match deadline.as_mut().poll(cx) {
1185 Poll::Ready(()) => Poll::Ready(Ok(false)),
1186 Poll::Pending => Poll::Pending,
1187 },
1188 }
1189}
1190
1191fn poll_readable_until(
1192 socket: &tokio::net::UdpSocket,
1193 deadline: &mut Pin<Box<time::Sleep>>,
1194 cx: &mut Context<'_>,
1195) -> Poll<Result<bool, ClientError>> {
1196 match socket.poll_recv_ready(cx) {
1197 Poll::Ready(Ok(())) => Poll::Ready(Ok(true)),
1198 Poll::Ready(Err(error)) => Poll::Ready(Err(ClientError::Socket(error))),
1199 Poll::Pending => match deadline.as_mut().poll(cx) {
1200 Poll::Ready(()) => Poll::Ready(Ok(false)),
1201 Poll::Pending => Poll::Pending,
1202 },
1203 }
1204}
1205
1206#[cfg(test)]
1207mod tests;