1use std::{
2 io,
3 net::{SocketAddr, UdpSocket},
4 time::{Duration, Instant},
5};
6
7use crate::{
8 config::{ClientConfig, RecvBudget},
9 error::ClientError,
10 event::{ClientEvent, OpenOutcome},
11 receive::{drain_tx_timestamps, recv_datagram, try_enable_tx_timestamping, ReceivedDatagram},
12 session::machine::{
13 recv_buffer_size, OpenDatagramDisposition, PreparedOpenAcceptance, SessionMachine,
14 MAX_OPEN_PACKET_SIZE,
15 },
16 socket::{connect_udp_socket, resolve_remote},
17 socket_options::{apply_traffic_class_to_socket, clear_dscp_on_socket},
18 timing::ClientTimestamp,
19 SendProbeError, SendReceipt,
20};
21
22#[derive(Debug)]
23struct PreparedClientOpen {
24 machine: PreparedOpenAcceptance,
25 recv_buffer_len: Option<usize>,
26 negotiated_traffic_class: Option<u8>,
27 previous_traffic_class: Option<u8>,
28 post_open_recv_timeout: Option<Duration>,
29}
30
31#[derive(Debug)]
32struct PreparedClientOpenFailure {
33 primary: ClientError,
34 machine: PreparedOpenAcceptance,
35}
36
37#[derive(Debug)]
69pub struct Client {
70 runtime: SessionMachine,
71 socket: UdpSocket,
72 remote: SocketAddr,
73 recv_buffer: Vec<u8>,
74 recv_timeout: Option<Duration>,
75 applied_traffic_class: Option<u8>,
76 tx_timestamping_enabled: bool,
83}
84
85impl Client {
86 pub fn connect(endpoint: impl AsRef<str>, config: ClientConfig) -> Result<Self, ClientError> {
96 config.open.validate()?;
97 let remote = resolve_remote(endpoint.as_ref(), config.address_family)?;
98 let runtime = SessionMachine::new(config.clone(), remote)?;
99 let socket = connect_udp_socket(&config.socket, remote, config.address_family)?;
100
101 Ok(Self {
102 runtime,
103 socket,
104 remote,
105 recv_buffer: vec![0_u8; recv_buffer_size(false, None)?],
106 recv_timeout: None,
107 applied_traffic_class: None,
108 tx_timestamping_enabled: false,
109 })
110 }
111
112 pub fn set_recv_timeout(&mut self, timeout: Option<Duration>) -> Result<(), ClientError> {
123 self.socket.set_read_timeout(timeout)?;
124 self.recv_timeout = timeout;
125 Ok(())
126 }
127
128 pub fn open(&mut self) -> Result<OpenOutcome, ClientError> {
145 let result = self.open_transaction();
146 if result.is_err() {
147 let _ = self.socket.set_read_timeout(self.recv_timeout);
148 }
149 result
150 }
151
152 pub fn close(&mut self) -> Result<Vec<ClientEvent>, ClientError> {
159 let prepared = self.runtime.prepare_close()?;
160 let mut events = Vec::new();
161
162 events
163 .try_reserve(1)
164 .map_err(|source| ClientError::AllocationFailed {
165 operation: "close event result",
166 source,
167 })?;
168 let previous_traffic_class = self.applied_traffic_class;
169 let expected_bytes = prepared.bytes.len();
170
171 self.clear_close_dscp()?;
172 let bytes = match self.socket.send(prepared.bytes) {
173 Ok(bytes) => bytes,
174 Err(err) => {
175 self.restore_dscp_best_effort(previous_traffic_class);
176 return Err(ClientError::Socket(err));
177 }
178 };
179 let sent_at = ClientTimestamp::now();
180
181 let event = self.runtime.commit_local_close(prepared.commit, sent_at);
182
183 self.applied_traffic_class = None;
184 validate_datagram_length(expected_bytes, bytes)?;
185 events.push(event);
186 Ok(events)
187 }
188
189 pub fn probe_timeout(&self) -> Duration {
191 self.runtime.probe_timeout()
192 }
193
194 pub fn send_probe(&mut self) -> Result<SendReceipt, SendProbeError> {
202 let remote = self.remote;
203 let (runtime, socket) = (&mut self.runtime, &self.socket);
204 let prepared = runtime
205 .prepare_probe()
206 .map_err(SendProbeError::NotCommitted)?;
207 let machine_preflight = runtime
208 .preflight_probe_commit(&prepared)
209 .map_err(SendProbeError::NotCommitted)?;
210 let expected_bytes = prepared.bytes.len();
211
212 let send_anchor = ClientTimestamp::now();
217 let machine_commit = runtime
218 .finalize_probe_commit(machine_preflight, send_anchor)
219 .map_err(SendProbeError::NotCommitted)?;
220 let send_call_start = Instant::now();
221
222 let bytes = match socket.send(&prepared.bytes) {
223 Ok(bytes) => bytes,
224 Err(error) => {
225 runtime.invalidate_kernel_tx_correlation();
226 return Err(SendProbeError::NotCommitted(ClientError::Socket(error)));
227 }
228 };
229 let send_finished_at = Instant::now();
230
231 let sent_at = ClientTimestamp::now();
236 let sent = runtime.commit_probe_sent(machine_commit, sent_at, bytes);
237 let send_call = send_finished_at.saturating_duration_since(send_call_start);
238 let receipt = SendReceipt {
239 seq: sent.seq,
240 remote,
241 sent_at: sent.sent_at,
242 bytes: sent.bytes,
243 send_call,
244 };
245 validate_datagram_length(expected_bytes, bytes).map_err(|source| {
246 SendProbeError::AfterCommit {
247 receipt,
248 source: Box::new(source),
249 }
250 })?;
251 self.drain_tx_timestamps()
252 .map_err(|source| SendProbeError::AfterCommit {
253 receipt,
254 source: Box::new(source),
255 })?;
256
257 Ok(receipt)
258 }
259
260 pub fn recv_once(&mut self) -> Result<Vec<ClientEvent>, ClientError> {
266 self.runtime.ensure_open()?;
267 self.socket.set_read_timeout(self.recv_timeout)?;
268 self.recv_once_inner()
269 }
270
271 fn recv_once_inner(&mut self) -> Result<Vec<ClientEvent>, ClientError> {
272 let Some(datagram) = self.recv_datagram_retrying_interrupted()? else {
273 return Ok(vec![]);
274 };
275
276 self.drain_tx_timestamps()?;
280
281 let events = self.runtime.process_received_echo_packet(
282 &self.recv_buffer[..datagram.len],
283 datagram.received_at,
284 datagram.meta,
285 )?;
286 if self.runtime.is_peer_closed() && self.clear_close_dscp().is_ok() {
287 self.applied_traffic_class = None;
288 }
289 Ok(events)
290 }
291
292 fn recv_datagram_retrying_interrupted(
306 &mut self,
307 ) -> Result<Option<ReceivedDatagram>, ClientError> {
308 let configured_timeout = self.recv_timeout;
309 let deadline = match configured_timeout {
310 Some(timeout) => Some(
311 Instant::now()
312 .checked_add(timeout)
313 .ok_or(ClientError::DurationOverflow)?,
314 ),
315 None => None,
316 };
317 let mut timeout_narrowed = false;
318
319 let result = loop {
320 match recv_datagram(&self.socket, &mut self.recv_buffer) {
321 Ok(datagram) => break Ok(Some(datagram)),
322 Err(err)
323 if matches!(
324 err.kind(),
325 io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
326 ) =>
327 {
328 break Ok(None);
329 }
330 Err(err) if err.kind() == io::ErrorKind::Interrupted => {
331 if let Some(deadline) = deadline {
332 let Some(remaining) = deadline.checked_duration_since(Instant::now())
333 else {
334 break Ok(None);
335 };
336 if remaining.is_zero() {
337 break Ok(None);
338 }
339 if let Err(err) = self.socket.set_read_timeout(Some(remaining)) {
340 break Err(ClientError::Socket(err));
341 }
342 timeout_narrowed = true;
343 }
344 }
345 Err(err) => break Err(ClientError::Socket(err)),
346 }
347 };
348
349 if timeout_narrowed {
350 if let Err(err) = self.socket.set_read_timeout(configured_timeout) {
351 return Err(ClientError::Socket(err));
352 }
353 }
354 result
355 }
356
357 pub fn recv_available(&mut self, budget: RecvBudget) -> Result<Vec<ClientEvent>, ClientError> {
362 self.runtime.ensure_open()?;
363 self.socket.set_read_timeout(self.recv_timeout)?;
364 let mut all_events = Vec::new();
365 for _ in 0..budget.max_packets {
366 let events = self.recv_once_inner()?;
367 if events.is_empty() {
368 break;
369 }
370 all_events.extend(events);
371 if self.is_peer_closed() {
372 break;
373 }
374 }
375 Ok(all_events)
376 }
377
378 pub fn poll_timeouts(&mut self) -> Result<Vec<ClientEvent>, ClientError> {
380 self.poll_timeouts_at(Instant::now())
381 }
382
383 pub fn poll_timeouts_at(&mut self, now: Instant) -> Result<Vec<ClientEvent>, ClientError> {
391 self.drain_tx_timestamps()?;
392 self.runtime.poll_timeouts_at(now)
393 }
394
395 pub fn negotiation(&self) -> Option<&crate::NegotiationResult> {
399 self.runtime.negotiation()
400 }
401
402 pub fn next_probe_timeout_deadline(&self) -> Option<Instant> {
404 self.runtime.next_probe_timeout_deadline()
405 }
406
407 pub fn has_pending_probes(&self) -> bool {
409 !self.runtime.pending_is_empty()
410 }
411
412 pub fn is_peer_closed(&self) -> bool {
419 self.runtime.is_peer_closed()
420 }
421
422 fn open_transaction(&mut self) -> Result<OpenOutcome, ClientError> {
423 let request = self.runtime.prepare_open_request()?;
424 let mut buf = [0_u8; MAX_OPEN_PACKET_SIZE];
425 let attempt_count = self.runtime.config().open.timeouts.len();
426
427 for attempt in 0..attempt_count {
428 let timeout = self.runtime.config().open.timeouts[attempt];
429 let deadline = Instant::now()
430 .checked_add(timeout)
431 .ok_or(ClientError::DurationOverflow)?;
432 self.socket.set_read_timeout(Some(timeout))?;
433 self.socket.send(&request.bytes)?;
434
435 loop {
436 let now = Instant::now();
437 let Some(remaining) = deadline.checked_duration_since(now) else {
438 break;
439 };
440 if remaining.is_zero() {
441 break;
442 }
443 self.socket.set_read_timeout(Some(remaining))?;
444
445 let datagram = match recv_datagram(&self.socket, &mut buf) {
446 Ok(datagram) => datagram,
447 Err(err)
456 if matches!(
457 err.kind(),
458 io::ErrorKind::WouldBlock
459 | io::ErrorKind::TimedOut
460 | io::ErrorKind::Interrupted
461 ) =>
462 {
463 if Instant::now() >= deadline {
464 break;
465 }
466 continue;
467 }
468 Err(err) => return Err(ClientError::Socket(err)),
469 };
470 if datagram.received_at.mono > deadline {
471 break;
472 }
473
474 let reply = match self.runtime.inspect_open_datagram(&buf[..datagram.len])? {
475 OpenDatagramDisposition::Ignore => continue,
476 OpenDatagramDisposition::Trusted(reply) => reply,
477 };
478 let machine = match self
479 .runtime
480 .prepare_open_acceptance(reply, datagram.received_at)
481 {
482 Ok(machine) => machine,
483 Err(failure) => {
484 self.send_cleanup_close_best_effort(failure.cleanup_close.as_deref());
485 return Err(failure.primary);
486 }
487 };
488 let prepared = match self.prepare_client_open(machine) {
489 Ok(prepared) => prepared,
490 Err(failure) => {
491 self.send_cleanup_close_best_effort(failure.machine.cleanup_close_packet());
492 return Err(failure.primary);
493 }
494 };
495 return self.apply_prepared_open(prepared);
496 }
497 }
498
499 Err(ClientError::OpenTimeout)
500 }
501
502 fn prepare_client_open(
503 &self,
504 machine: PreparedOpenAcceptance,
505 ) -> Result<PreparedClientOpen, Box<PreparedClientOpenFailure>> {
506 let Some(negotiated) = machine.normal_negotiated() else {
507 return Ok(PreparedClientOpen {
508 machine,
509 recv_buffer_len: None,
510 negotiated_traffic_class: None,
511 previous_traffic_class: self.applied_traffic_class,
512 post_open_recv_timeout: self.recv_timeout,
513 });
514 };
515 let recv_buffer_len = match recv_buffer_size(self.runtime.has_hmac(), Some(negotiated)) {
516 Ok(size) => size,
517 Err(primary) => return Err(Box::new(PreparedClientOpenFailure { primary, machine })),
518 };
519
520 let negotiated_traffic_class = negotiated.accepted.dscp << 2;
521 Ok(PreparedClientOpen {
522 machine,
523 recv_buffer_len: Some(recv_buffer_len),
524 negotiated_traffic_class: Some(negotiated_traffic_class),
525 previous_traffic_class: self.applied_traffic_class,
526 post_open_recv_timeout: self.recv_timeout,
527 })
528 }
529
530 fn apply_prepared_open(
531 &mut self,
532 prepared: PreparedClientOpen,
533 ) -> Result<OpenOutcome, ClientError> {
534 let PreparedClientOpen {
535 machine,
536 recv_buffer_len,
537 negotiated_traffic_class,
538 previous_traffic_class,
539 post_open_recv_timeout,
540 } = prepared;
541
542 if let (Some(recv_buffer_len), Some(negotiated_traffic_class)) =
543 (recv_buffer_len, negotiated_traffic_class)
544 {
545 let previous_len = self.recv_buffer.len();
546 let additional = recv_buffer_len.saturating_sub(previous_len);
547 if let Err(source) = self.recv_buffer.try_reserve(additional) {
548 self.send_cleanup_close_best_effort(machine.cleanup_close_packet());
549 return Err(ClientError::AllocationFailed {
550 operation: "negotiated receive buffer",
551 source,
552 });
553 }
554 self.recv_buffer.resize(recv_buffer_len, 0);
555
556 if let Err(primary) =
557 apply_traffic_class_to_socket(&self.socket, self.remote, negotiated_traffic_class)
558 {
559 self.recv_buffer.truncate(previous_len);
560 self.restore_dscp_best_effort(previous_traffic_class);
561 self.send_cleanup_close_best_effort(machine.cleanup_close_packet());
562 return Err(primary);
563 }
564 if let Err(source) = self.socket.set_read_timeout(post_open_recv_timeout) {
565 let primary = ClientError::ReadTimeoutRestore { source };
566 self.recv_buffer.truncate(previous_len);
567 self.restore_dscp_best_effort(previous_traffic_class);
568 self.send_cleanup_close_best_effort(machine.cleanup_close_packet());
569 return Err(primary);
570 }
571
572 let outcome = self.runtime.commit_open(machine);
573
574 self.applied_traffic_class = Some(negotiated_traffic_class);
575 self.tx_timestamping_enabled = try_enable_tx_timestamping(&self.socket);
576 Ok(outcome)
577 } else {
578 if let Err(source) = self.socket.set_read_timeout(post_open_recv_timeout) {
579 return Err(ClientError::ReadTimeoutRestore { source });
580 }
581 let outcome = self.runtime.commit_open(machine);
582
583 self.applied_traffic_class = None;
584 self.tx_timestamping_enabled = try_enable_tx_timestamping(&self.socket);
585 Ok(outcome)
586 }
587 }
588
589 fn drain_tx_timestamps(&mut self) -> Result<(), ClientError> {
596 if !self.tx_timestamping_enabled {
597 return Ok(());
598 }
599 let runtime = &mut self.runtime;
600 drain_tx_timestamps(&self.socket, |id, timestamp| {
601 runtime.record_kernel_tx_timestamp(id, timestamp);
602 })
603 .map_err(ClientError::Socket)
604 }
605
606 fn restore_dscp_best_effort(&self, previous_traffic_class: Option<u8>) {
607 let _ = match previous_traffic_class {
608 Some(traffic_class) => {
609 apply_traffic_class_to_socket(&self.socket, self.remote, traffic_class)
610 }
611 None => clear_dscp_on_socket(&self.socket, self.remote),
612 };
613 }
614
615 fn clear_close_dscp(&self) -> Result<(), ClientError> {
616 clear_dscp_on_socket(&self.socket, self.remote)
617 }
618
619 fn send_cleanup_close_best_effort(&self, packet: Option<&[u8]>) {
620 if let Some(packet) = packet {
621 let _ = self.socket.send(packet);
622 }
623 }
624}
625
626pub(crate) fn validate_datagram_length(expected: usize, actual: usize) -> Result<(), ClientError> {
627 if actual == expected {
628 Ok(())
629 } else {
630 Err(ClientError::DatagramLengthMismatch { expected, actual })
631 }
632}