Skip to main content

pb_mapper_client/client/
mod.rs

1pub mod error;
2pub mod status;
3pub(crate) mod stream;
4
5use crate::diagnostics::{Diagnostics, RecoveryFailure, RecoveryPhase};
6use crate::endpoint::RelayEndpoint;
7use crate::relay::CarrierId;
8use std::fmt::Debug;
9use std::sync::Arc;
10use std::time::Duration;
11
12use snafu::ResultExt;
13use tokio::sync::{Mutex, Semaphore};
14use tokio::task::JoinSet;
15use tokio::time::Instant;
16use tokio_util::sync::CancellationToken;
17use uni_stream::udp::set_custom_timeout;
18
19use self::error::{AcceptLocalStreamSnafu, BindLocalListenerSnafu};
20use self::status::{get_status_scoped, get_status_with_credential};
21use self::stream::{StreamSetup, handle_local_stream};
22use crate::addr::{resolve_all, resolve_tunnel_ends};
23use crate::recovery::{RecoveryTiming, jitter};
24use pb_mapper_core::checksum::{Credential, get_process_credential};
25use pb_mapper_core::config::ResolvedAddrs;
26use pb_mapper_core::config::{
27    StatusOp, client_health_check_interval, client_health_check_timeout,
28    client_health_failure_threshold,
29};
30use pb_mapper_core::timeout::RetryBackoff;
31use pb_mapper_protocol::command::{PbConnStatusReq, PbConnStatusResp};
32use pb_mapper_protocol::forward::StreamForward;
33use uni_stream::addr::ToSocketAddrs;
34use uni_stream::stream::{ListenerProvider, StreamAccept};
35
36// Callback for notifying status changes to external systems
37pub type ClientStatusCallback = Box<dyn Fn(&str) + Send + Sync>;
38
39/// Resolve both ends, reporting a bad address through `status_callback`.
40///
41/// The callback is the only channel a caller has here — these entry points return
42/// `()` — so an address that never resolves has to settle the tunnel as failed
43/// rather than just leaving a log line behind.
44async fn resolve_ends_or_fail<A: ToSocketAddrs>(
45    local_addr: A,
46    remote_addr: A,
47    status_callback: Option<&ClientStatusCallback>,
48) -> Option<(ResolvedAddrs, ResolvedAddrs)> {
49    let resolved = resolve_tunnel_ends(local_addr, remote_addr).await;
50    if let (None, Some(callback)) = (&resolved, status_callback) {
51        callback("failed");
52    }
53    resolved
54}
55
56pub async fn run_client_side_cli<LocalListener: ListenerProvider, A: ToSocketAddrs>(
57    local_addr: A,
58    remote_addr: A,
59    key: Arc<str>,
60    keep_alive: bool,
61) where
62    <LocalListener::Listener as StreamAccept>::Item: StreamForward,
63{
64    run_client_side_cli_with_callback::<LocalListener, A>(
65        local_addr,
66        remote_addr,
67        key,
68        keep_alive,
69        None,
70    )
71    .await
72}
73
74pub async fn run_client_side_cli_with_callback<LocalListener: ListenerProvider, A: ToSocketAddrs>(
75    local_addr: A,
76    remote_addr: A,
77    key: Arc<str>,
78    keep_alive: bool,
79    status_callback: Option<ClientStatusCallback>,
80) where
81    <LocalListener::Listener as StreamAccept>::Item: StreamForward,
82{
83    run_client_side_cli_with_callback_scoped::<LocalListener, A>(
84        local_addr,
85        remote_addr,
86        key,
87        keep_alive,
88        None,
89        status_callback,
90        None,
91    )
92    .await
93}
94
95pub async fn run_client_side_cli_with_pinned_credential<
96    LocalListener: ListenerProvider,
97    A: ToSocketAddrs,
98>(
99    local_addr: A,
100    remote_addr: A,
101    key: Arc<str>,
102    keep_alive: bool,
103    status_callback: Option<ClientStatusCallback>,
104    credential: pb_mapper_core::checksum::Credential,
105) where
106    <LocalListener::Listener as StreamAccept>::Item: StreamForward,
107{
108    run_client_side_cli_with_callback_scoped::<LocalListener, A>(
109        local_addr,
110        remote_addr,
111        key,
112        keep_alive,
113        None,
114        status_callback,
115        Some(credential),
116    )
117    .await
118}
119
120pub async fn run_client_side_cli_with_callback_scoped<
121    LocalListener: ListenerProvider,
122    A: ToSocketAddrs,
123>(
124    local_addr: A,
125    remote_addr: A,
126    key: Arc<str>,
127    keep_alive: bool,
128    namespace: Option<u64>,
129    status_callback: Option<ClientStatusCallback>,
130    pinned_credential: Option<pb_mapper_core::checksum::Credential>,
131) where
132    <LocalListener::Listener as StreamAccept>::Item: StreamForward,
133{
134    let Some((local_addr, remote_addr)) =
135        resolve_ends_or_fail(local_addr, remote_addr, status_callback.as_ref()).await
136    else {
137        return;
138    };
139    run_client_side_cli_loop::<LocalListener>(
140        local_addr,
141        remote_addr,
142        key,
143        keep_alive,
144        namespace,
145        status_callback,
146        pinned_credential,
147        CancellationToken::new(),
148    )
149    .await
150}
151
152/// Same as [`run_client_side_cli_with_callback_scoped`], but the retry loop
153/// returns when `shutdown` is cancelled.
154///
155/// Takes both ends already resolved, because its caller — the SDK — resolves them
156/// itself in order to report a bad address as an error rather than a log line.
157#[allow(clippy::too_many_arguments)]
158pub async fn run_client_side_cli_with_shutdown<LocalListener: ListenerProvider>(
159    local_addr: ResolvedAddrs,
160    remote_addr: ResolvedAddrs,
161    key: Arc<str>,
162    keep_alive: bool,
163    namespace: Option<u64>,
164    status_callback: Option<ClientStatusCallback>,
165    pinned_credential: Option<pb_mapper_core::checksum::Credential>,
166    shutdown: CancellationToken,
167) where
168    <LocalListener::Listener as StreamAccept>::Item: StreamForward,
169{
170    run_client_side_cli_loop::<LocalListener>(
171        local_addr,
172        remote_addr,
173        key,
174        keep_alive,
175        namespace,
176        status_callback,
177        pinned_credential,
178        shutdown,
179    )
180    .await
181}
182
183#[allow(clippy::too_many_arguments)]
184async fn run_client_side_cli_loop<LocalListener: ListenerProvider>(
185    local_addr: ResolvedAddrs,
186    remote_addr: ResolvedAddrs,
187    key: Arc<str>,
188    keep_alive: bool,
189    namespace: Option<u64>,
190    status_callback: Option<ClientStatusCallback>,
191    pinned_credential: Option<pb_mapper_core::checksum::Credential>,
192    shutdown: CancellationToken,
193) where
194    <LocalListener::Listener as StreamAccept>::Item: StreamForward,
195{
196    run_client_side_cli_recovering::<LocalListener>(
197        local_addr,
198        RelayEndpoint::fixed(remote_addr),
199        key,
200        keep_alive,
201        namespace,
202        status_callback,
203        pinned_credential,
204        shutdown,
205        Diagnostics::default(),
206    )
207    .await;
208}
209
210#[allow(clippy::too_many_arguments)]
211pub(crate) async fn run_client_side_cli_recovering<LocalListener: ListenerProvider>(
212    local_addr: ResolvedAddrs,
213    remote_addr: RelayEndpoint,
214    key: Arc<str>,
215    keep_alive: bool,
216    namespace: Option<u64>,
217    status_callback: Option<ClientStatusCallback>,
218    pinned_credential: Option<pb_mapper_core::checksum::Credential>,
219    shutdown: CancellationToken,
220    diagnostics: Diagnostics,
221) where
222    <LocalListener::Listener as StreamAccept>::Item: StreamForward,
223{
224    run_client_side_cli_recovering_with_path::<LocalListener>(
225        local_addr,
226        remote_addr,
227        key,
228        keep_alive,
229        namespace,
230        status_callback,
231        pinned_credential,
232        shutdown,
233        diagnostics,
234        crate::sdk::PathPolicy::RelayOnly,
235        false,
236        crate::sdk::PortMapping::Off,
237    )
238    .await;
239}
240
241#[allow(clippy::too_many_arguments)]
242pub(crate) async fn run_client_side_cli_recovering_with_path<LocalListener: ListenerProvider>(
243    local_addr: ResolvedAddrs,
244    remote_addr: RelayEndpoint,
245    key: Arc<str>,
246    keep_alive: bool,
247    namespace: Option<u64>,
248    status_callback: Option<ClientStatusCallback>,
249    pinned_credential: Option<Credential>,
250    shutdown: CancellationToken,
251    diagnostics: Diagnostics,
252    path: crate::sdk::PathPolicy,
253    is_datagram: bool,
254    port_mapping: crate::sdk::PortMapping,
255) where
256    <LocalListener::Listener as StreamAccept>::Item: StreamForward,
257{
258    remote_addr.start();
259    let mut wake = remote_addr.wake();
260    diagnostics.network_seen(remote_addr.network_generation());
261    set_custom_timeout(Duration::from_secs(120));
262
263    let credential = match pinned_credential {
264        Some(credential) => credential,
265        None => match get_process_credential() {
266            Ok(credential) => credential,
267            Err(e) => {
268                tracing::error!("load client credential failed: {e}");
269                if let Some(ref callback) = status_callback {
270                    callback("failed");
271                }
272                return;
273            }
274        },
275    };
276
277    #[cfg(feature = "p2p")]
278    let direct = (path == crate::sdk::PathPolicy::Auto).then(|| {
279        crate::direct::Consumer::new(
280            remote_addr.clone(),
281            credential,
282            namespace,
283            is_datagram,
284            keep_alive,
285            diagnostics.clone(),
286            port_mapping,
287        )
288    });
289    #[cfg(not(feature = "p2p"))]
290    let _ = (path, is_datagram, port_mapping);
291    let mut retry_backoff = RetryBackoff::new(Duration::from_millis(100), Duration::from_secs(2));
292    let mut stream_tasks = JoinSet::new();
293    let setup_slots = Arc::new(Semaphore::new(64));
294    let timing = Arc::new(Mutex::new(RecoveryTiming::default()));
295
296    'outer: loop {
297        // Listener lifetime follows the local endpoint, not a remote health
298        // snapshot. Only a local bind/accept error requires replacing it.
299        let listener = tokio::select! {
300            () = shutdown.cancelled() => break,
301            result = LocalListener::bind(local_addr.as_slice()) => match result.context(BindLocalListenerSnafu) {
302                Ok(listener) => listener,
303                Err(error) => {
304                    tracing::warn!(event = "client_local_bind_failed", %key, %local_addr, %error);
305                    if let Some(callback) = &status_callback { callback("retrying"); }
306                    tokio::select! {
307                        () = shutdown.cancelled() => break,
308                        () = tokio::time::sleep(jitter(retry_backoff.next_delay())) => {}
309                    }
310                    continue;
311                }
312            }
313        };
314        tracing::info!(event = "client_local_listener_bound", %key, %local_addr, "local listener bound; checking remote service");
315        let mut probes = JoinSet::new();
316        let mut next_probe = Instant::now();
317        let mut last_success = None;
318        let mut connected = false;
319        let mut consecutive_health_failures = 0usize;
320        let (stream_event_tx, mut stream_event_rx) = tokio::sync::mpsc::channel(1);
321
322        loop {
323            tokio::select! {
324                () = shutdown.cancelled() => break 'outer,
325                woke = wake.changed() => {
326                    if let crate::endpoint::Wake::Network(generation) = woke {
327                        diagnostics.network_changed(generation);
328                        #[cfg(feature = "p2p")]
329                        if let Some(direct) = &direct { direct.invalidate(); }
330                    }
331                    probes.abort_all();
332                    next_probe = Instant::now();
333                    retry_backoff.reset();
334                },
335                accepted = async {
336                    // Backpressure stays in the listener backlog while setup is
337                    // saturated. Established forwarding releases its permit.
338                    let permit = setup_slots.clone().acquire_owned().await;
339                    (permit, listener.accept().await)
340                } => {
341                    let (Ok(permit), accepted) = accepted else { break 'outer; };
342                    let (stream, _) = match accepted.context(AcceptLocalStreamSnafu) {
343                        Ok(accepted) => accepted,
344                        Err(error) => {
345                            tracing::warn!(event = "client_local_accept_failed", %key, %local_addr, %error);
346                            break;
347                        }
348                    };
349                    let stream_key = key.clone();
350                    let stream_remote = remote_addr.clone();
351                    let stream_shutdown = shutdown.clone();
352                    let event_tx = stream_event_tx.clone();
353                    let setup = StreamSetup { permit, timing: timing.clone(), events: event_tx.clone(), diagnostics: diagnostics.clone(), #[cfg(feature = "p2p")] direct: direct.clone() };
354                    stream_tasks.spawn(async move {
355                        let result = tokio::select! {
356                            () = stream_shutdown.cancelled() => return,
357                            result = handle_local_stream(stream, stream_key, stream_remote, keep_alive, namespace, credential, setup) => result,
358                        };
359                        if let Err(error) = result {
360                            tracing::warn!(event = "client_local_stream_failed_before_forward", reason = %snafu::Report::from_error(error));
361                            let _ = event_tx.try_send(false);
362                        }
363                    });
364                }
365                () = tokio::time::sleep_until(next_probe), if probes.is_empty() => {
366                    diagnostics.attempt();
367                    diagnostics.phase(RecoveryPhase::Handshake);
368                    let probe_remote = remote_addr.clone();
369                    let probe_key = key.clone();
370                    let budget = timing.lock().await.timeout().min(client_health_check_timeout());
371                    probes.spawn(async move {
372                        let _permit = probe_remote.control_permit().await;
373                        let started = Instant::now();
374                        let mut carrier = None;
375                        let result = probe_remote_key(&probe_remote, &probe_key, namespace, credential, budget, &mut carrier).await;
376                        (started, started.elapsed(), result, carrier)
377                    });
378                }
379                Some(result) = probes.join_next() => {
380                    let (started, elapsed, result, carrier) = match result {
381                        Ok(result) => result,
382                        Err(error) if error.is_cancelled() => continue,
383                        Err(error) => (Instant::now(), Duration::ZERO, Err(ProbeFailure::transient(error.to_string())), None),
384                    };
385                    match result {
386                        Ok(()) => {
387                            timing.lock().await.record(elapsed);
388                            remote_addr.protocol_succeeded();
389                            diagnostics.succeeded(elapsed);
390                            last_success = Some(Instant::now());
391                            consecutive_health_failures = 0;
392                            retry_backoff.reset();
393                            next_probe = Instant::now() + client_health_check_interval();
394                            if !connected {
395                                connected = true;
396                                if let Some(callback) = &status_callback { callback("connected"); }
397                            }
398                        }
399                        Err(failure) if failure.permanent && diagnostics.has_active_direct() => {
400                            diagnostics.failed(failure.kind, Duration::ZERO);
401                            if let Some(callback) = &status_callback { callback("retrying"); }
402                            next_probe = Instant::now() + Duration::from_secs(30);
403                        }
404                        Err(failure) if failure.permanent => {
405                            if failure.protocol_replied {
406                                remote_addr.protocol_succeeded();
407                                diagnostics.responded(elapsed);
408                            }
409                            diagnostics.failed(failure.kind, Duration::ZERO);
410                            if let Some(callback) = &status_callback { callback(&format!("failed: {failure}")); }
411                            break 'outer;
412                        }
413                        Err(_) if last_success.is_some_and(|success| success > started) => {
414                            // Actual traffic completed after this probe began.
415                            next_probe = Instant::now() + client_health_check_interval();
416                        }
417                        Err(failure) => {
418                            failure.update_timing(&mut *timing.lock().await, elapsed);
419                            if failure.protocol_replied {
420                                remote_addr.protocol_succeeded();
421                                diagnostics.responded(elapsed);
422                            } else { remote_addr.transport_failed(carrier); }
423                            consecutive_health_failures = consecutive_health_failures.saturating_add(1);
424                            if !connected || consecutive_health_failures >= client_health_failure_threshold() {
425                                connected = false;
426                                if let Some(callback) = &status_callback { callback("retrying"); }
427                            }
428                            let delay = jitter(retry_backoff.next_delay());
429                            next_probe = Instant::now() + delay;
430                            if diagnostics.failed(failure.kind, delay) {
431                            tracing::warn!(event = "client_remote_probe_failed", %key, reason = %failure, consecutive_health_failures, retry_delay = ?delay, "remote probe failed; listener remains active");
432                            }
433                        }
434                    }
435                }
436                Some(success) = stream_event_rx.recv() => {
437                    if success {
438                        last_success = Some(Instant::now());
439                        consecutive_health_failures = 0;
440                        retry_backoff.reset();
441                        if !connected {
442                            connected = true;
443                            if let Some(callback) = &status_callback { callback("connected"); }
444                        }
445                    } else if probes.is_empty() {
446                        // Coalesce failures and rate limit probes independently
447                        // of the number of callers arriving during an outage.
448                        next_probe = next_probe.min(Instant::now() + Duration::from_millis(100));
449                    }
450                }
451                Some(_) = stream_tasks.join_next() => {}
452            }
453        }
454        drop(probes);
455        if let Some(callback) = &status_callback {
456            callback("retrying");
457        }
458        tokio::select! {
459            () = shutdown.cancelled() => break,
460            () = tokio::time::sleep(jitter(retry_backoff.next_delay())) => {}
461        }
462    }
463
464    // Wait for the forwarding sessions to observe the cancellation, so returning
465    // from here means no stream of this tunnel is still moving bytes.
466    stream_tasks.shutdown().await;
467    #[cfg(feature = "p2p")]
468    if let Some(direct) = direct {
469        direct.stop().await;
470    }
471}
472
473/// Why a remote probe did not confirm the service, and whether waiting could
474/// change that.
475///
476/// The distinction is what keeps the retry loop from spinning: a service that has
477/// not registered yet may still appear, but a namespace the credential does not
478/// own never will.
479#[derive(Debug)]
480struct ProbeFailure {
481    reason: String,
482    permanent: bool,
483    kind: RecoveryFailure,
484    protocol_replied: bool,
485}
486
487impl ProbeFailure {
488    fn update_timing(&self, timing: &mut RecoveryTiming, elapsed: Duration) {
489        if self.kind == RecoveryFailure::Timeout {
490            timing.timed_out();
491        } else if self.protocol_replied {
492            timing.record(elapsed);
493        }
494    }
495
496    /// A failure worth retrying: a transport error, or a service that is simply
497    /// not registered yet.
498    fn transient(reason: impl Into<String>) -> Self {
499        Self {
500            reason: reason.into(),
501            permanent: false,
502            kind: RecoveryFailure::Transport,
503            protocol_replied: false,
504        }
505    }
506
507    /// A status failure classified by the relay's own `retryable` verdict, which
508    /// is the only party that knows whether the refusal is final.
509    fn from_status_error(context: &str, error: crate::client::error::Error) -> Self {
510        let permanent = error.remote_retryable() == Some(false);
511        let protocol_replied = error.remote_retryable().is_some();
512        Self {
513            reason: format!("{context}: {}", snafu::Report::from_error(error)),
514            permanent,
515            kind: if protocol_replied {
516                RecoveryFailure::Rejected
517            } else {
518                RecoveryFailure::Transport
519            },
520            protocol_replied,
521        }
522    }
523}
524
525impl std::fmt::Display for ProbeFailure {
526    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
527        formatter.write_str(&self.reason)
528    }
529}
530
531async fn probe_remote_key(
532    remote_addr: &RelayEndpoint,
533    key: &str,
534    namespace: Option<u64>,
535    credential: Credential,
536    timeout: Duration,
537    carrier: &mut Option<CarrierId>,
538) -> std::result::Result<(), ProbeFailure> {
539    match tokio::time::timeout(timeout, async {
540        // Resolved once, here, so a name that does not resolve is reported
541        // as such rather than as the relay being unreachable.
542        let addresses = remote_addr
543            .addresses()
544            .await
545            .map_err(|error| ProbeFailure {
546                reason: error.to_string(),
547                permanent: false,
548                kind: RecoveryFailure::Dns,
549                protocol_replied: false,
550            })?;
551        probe_remote_key_once(remote_addr, &addresses, key, namespace, credential, carrier).await
552    })
553    .await
554    {
555        Ok(result) => result,
556        Err(_) => Err(ProbeFailure {
557            reason: format!("remote key probe timed out after {timeout:?}"),
558            permanent: false,
559            kind: RecoveryFailure::Timeout,
560            protocol_replied: false,
561        }),
562    }
563}
564
565async fn probe_remote_key_once(
566    remote_addr: &RelayEndpoint,
567    addresses: &ResolvedAddrs,
568    key: &str,
569    namespace: Option<u64>,
570    credential: Credential,
571    carrier: &mut Option<CarrierId>,
572) -> std::result::Result<(), ProbeFailure> {
573    match fetch_remote_status(
574        remote_addr,
575        addresses,
576        PbConnStatusReq::Service {
577            direct_query: None,
578            key: key.to_string(),
579        },
580        namespace,
581        credential,
582        carrier,
583    )
584    .await
585    {
586        Ok(PbConnStatusResp::Service { connections, .. }) => {
587            if connections.iter().any(|conn| conn.healthy) {
588                return Ok(());
589            }
590            return Err(ProbeFailure {
591                reason: format!("client key `{key}` has no healthy remote server connections"),
592                permanent: false,
593                kind: RecoveryFailure::ServiceUnavailable,
594                protocol_replied: true,
595            });
596        }
597        Ok(status_resp) => {
598            return Err(ProbeFailure::transient(format!(
599                "expected service status response, got {status_resp:?}"
600            )));
601        }
602        // A refusal the relay calls final applies to the keys probe just as much,
603        // so there is nothing to fall back to.
604        Err(failure) if failure.permanent => return Err(failure),
605        Err(service_failure) => {
606            tracing::debug!(
607                event = "client_remote_service_probe_failed",
608                key = %key,
609                remote_addr = %remote_addr,
610                reason = %service_failure,
611                "service status probe failed; falling back to key status"
612            );
613        }
614    }
615
616    let status_resp = fetch_remote_status(
617        remote_addr,
618        addresses,
619        PbConnStatusReq::Keys,
620        namespace,
621        credential,
622        carrier,
623    )
624    .await?;
625    let PbConnStatusResp::Keys(keys) = status_resp else {
626        return Err(ProbeFailure::transient(format!(
627            "expected keys status response, got {status_resp:?}"
628        )));
629    };
630    if keys.iter().any(|candidate| candidate == key) {
631        Ok(())
632    } else {
633        Err(ProbeFailure {
634            reason: format!("client key `{key}` is not registered on remote server"),
635            permanent: false,
636            kind: RecoveryFailure::ServiceUnavailable,
637            protocol_replied: true,
638        })
639    }
640}
641
642async fn fetch_remote_status(
643    remote_addr: &RelayEndpoint,
644    addresses: &ResolvedAddrs,
645    req: PbConnStatusReq,
646    namespace: Option<u64>,
647    credential: Credential,
648    carrier: &mut Option<CarrierId>,
649) -> std::result::Result<PbConnStatusResp, ProbeFailure> {
650    let mut stream = remote_addr
651        .connect_addresses(addresses, credential, carrier)
652        .await
653        .map_err(|error| match error {
654            crate::relay::ConnectError::Rejected { response } => ProbeFailure {
655                reason: format!("{}: {}", response.code, response.message),
656                permanent: !response.retryable,
657                kind: RecoveryFailure::Rejected,
658                protocol_replied: true,
659            },
660            error => ProbeFailure::transient(format!("connect remote stream failed: {error}")),
661        })?;
662    get_status_with_credential(&mut stream, req, namespace, &credential)
663        .await
664        .map_err(|error| ProbeFailure::from_status_error("get status failed", error))
665}
666
667pub async fn handle_status_cli_scoped<A: ToSocketAddrs>(
668    op: StatusOp,
669    addr: A,
670    namespace: Option<u64>,
671) -> Result<(), Box<dyn std::error::Error>> {
672    match op {
673        StatusOp::Sessions => show_status_scoped(addr, PbConnStatusReq::Sessions, namespace).await,
674        StatusOp::RemoteId => show_status_scoped(addr, PbConnStatusReq::RemoteId, namespace).await,
675        StatusOp::Keys => show_status_scoped(addr, PbConnStatusReq::Keys, namespace).await,
676    }
677}
678
679pub async fn show_status_scoped<A: ToSocketAddrs>(
680    remote_addr: A,
681    req: PbConnStatusReq,
682    namespace: Option<u64>,
683) -> Result<(), Box<dyn std::error::Error>> {
684    let remote_addr = resolve_all(remote_addr).await?;
685    let mut stream = crate::addr::connect_tcp(&remote_addr)
686        .await
687        .map_err(|error| format!("get status stream: {error}"))?;
688    let status = get_status_scoped(&mut stream, req, namespace).await?;
689    let status = serde_json::to_string_pretty(&status)?;
690    println!("Status:{status}");
691    Ok(())
692}
693
694#[cfg(test)]
695#[allow(clippy::unwrap_used, clippy::expect_used)]
696mod cancellation_audit {
697    use std::{
698        future::Future,
699        sync::{
700            Arc,
701            atomic::{AtomicBool, Ordering},
702        },
703        task::{Context, Wake, Waker},
704        time::Duration,
705    };
706    struct Notified(AtomicBool);
707    impl Wake for Notified {
708        fn wake(self: Arc<Self>) {
709            self.0.store(true, Ordering::SeqCst);
710        }
711        fn wake_by_ref(self: &Arc<Self>) {
712            self.0.store(true, Ordering::SeqCst);
713        }
714    }
715    #[tokio::test]
716    async fn cancelled_udp_accept_must_preserve_the_delivered_peer() {
717        let listener = uni_stream::udp::UdpListener::bind("127.0.0.1:0")
718            .await
719            .unwrap();
720        let addr = listener.local_addr().unwrap();
721        let client = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap();
722        let notified = Arc::new(Notified(AtomicBool::new(false)));
723        let waker = Waker::from(notified.clone());
724        let mut accept = Box::pin(listener.accept());
725        assert!(
726            accept
727                .as_mut()
728                .poll(&mut Context::from_waker(&waker))
729                .is_pending()
730        );
731        client.send_to(b"first datagram", addr).await.unwrap();
732        tokio::time::timeout(Duration::from_secs(1), async {
733            while !notified.0.load(Ordering::SeqCst) {
734                tokio::task::yield_now().await;
735            }
736        })
737        .await
738        .unwrap();
739        drop(accept);
740        let delivered = tokio::time::timeout(Duration::from_millis(100), listener.accept()).await;
741        assert!(
742            delivered.is_ok(),
743            "UDP peer/first datagram disappeared when a competing select branch cancelled accept"
744        );
745    }
746}
747
748#[cfg(test)]
749mod recovery_tests {
750    use super::*;
751    #[test]
752    fn absent_service_learns_response_latency_without_inflating_timeout() {
753        let mut timing = RecoveryTiming::default();
754        let missing = ProbeFailure {
755            reason: String::new(),
756            permanent: false,
757            kind: RecoveryFailure::ServiceUnavailable,
758            protocol_replied: true,
759        };
760        for _ in 0..20 {
761            missing.update_timing(&mut timing, Duration::from_millis(40));
762        }
763        assert_eq!(timing.timeout(), Duration::from_secs(1));
764        let refusal = ProbeFailure::transient("connection refused");
765        refusal.update_timing(&mut timing, Duration::from_millis(1));
766        assert_eq!(timing.timeout(), Duration::from_secs(1));
767        let timeout = ProbeFailure {
768            kind: RecoveryFailure::Timeout,
769            ..refusal
770        };
771        timeout.update_timing(&mut timing, Duration::from_secs(1));
772        assert_eq!(timing.timeout(), Duration::from_secs(2));
773    }
774}