Skip to main content

fips_core/endpoint/
status.rs

1use crate::NodeAddr;
2use crate::node::{NodeEndpointPeer, NodeEndpointRelayStatus};
3use std::net::SocketAddr;
4
5/// Authenticated FIPS peer state visible to an embedded application.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct FipsEndpointPeer {
8    /// Peer Nostr public key.
9    pub npub: String,
10    /// Peer FIPS node address, derived from the public key and stable across npub encodings.
11    pub node_addr: NodeAddr,
12    /// Whether an authenticated link-layer peer is currently active.
13    pub connected: bool,
14    /// Current underlay transport address, when a link has authenticated.
15    pub transport_addr: Option<String>,
16    /// Current underlay transport kind, when known.
17    pub transport_type: Option<String>,
18    /// Authenticated link id.
19    pub link_id: u64,
20    /// Smoothed RTT in milliseconds, once measured by FIPS MMP.
21    pub srtt_ms: Option<u64>,
22    /// Age of the current SRTT sample in milliseconds.
23    pub srtt_age_ms: Option<u64>,
24    /// Link packets sent.
25    pub packets_sent: u64,
26    /// Link packets received.
27    pub packets_recv: u64,
28    /// Link bytes sent.
29    pub bytes_sent: u64,
30    /// Link bytes received.
31    pub bytes_recv: u64,
32    /// Whether a link-layer rekey is currently in progress.
33    pub rekey_in_progress: bool,
34    /// Whether this peer is draining an old key during rekey.
35    pub rekey_draining: bool,
36    /// Current link-layer key bit for active peers.
37    pub current_k_bit: Option<bool>,
38    /// Last outbound end-to-end session route: `direct` when the first hop was
39    /// the destination peer, `fallback` when traffic went through another peer.
40    pub last_outbound_route: Option<String>,
41    /// Whether direct UDP probing is queued while this peer may still be
42    /// reachable through a fallback transport.
43    pub direct_probe_pending: bool,
44    /// Millisecond timestamp when the queued direct probe becomes eligible.
45    pub direct_probe_after_ms: Option<u64>,
46    /// Number of direct probe/retry attempts accumulated for this peer.
47    pub direct_probe_retry_count: u32,
48    /// Whether the queued direct probe is an unlimited auto-reconnect.
49    pub direct_probe_auto_reconnect: bool,
50    /// Millisecond timestamp when a bounded direct probe/retry entry expires.
51    pub direct_probe_expires_at_ms: Option<u64>,
52    /// Consecutive Nostr traversal failures recorded for this peer.
53    pub nostr_traversal_consecutive_failures: u32,
54    /// Whether Nostr traversal is currently cooling down for this peer.
55    pub nostr_traversal_in_cooldown: bool,
56    /// Millisecond timestamp when Nostr traversal cooldown ends.
57    pub nostr_traversal_cooldown_until_ms: Option<u64>,
58    /// Last observed Nostr timestamp skew in milliseconds for this peer.
59    pub nostr_traversal_last_observed_skew_ms: Option<i64>,
60}
61
62impl FipsEndpointPeer {
63    /// Return a safe UDP restart candidate from this authenticated snapshot.
64    ///
65    /// A candidate is exposed only while the peer is connected over UDP and
66    /// the reported address is a reusable [`SocketAddr`]. In particular, this
67    /// never turns TCP source ports, synthetic WebSocket paths, WebRTC
68    /// signaling identities, or BLE addresses into restart routes.
69    pub fn authenticated_udp_restart_addr(&self) -> Option<SocketAddr> {
70        if !self.connected || self.transport_type.as_deref() != Some("udp") {
71            return None;
72        }
73
74        self.transport_addr
75            .as_deref()?
76            .parse()
77            .ok()
78            .filter(is_reusable_udp_socket_addr)
79    }
80}
81
82pub(super) fn is_reusable_udp_socket_addr(addr: &SocketAddr) -> bool {
83    addr.port() != 0 && !addr.ip().is_unspecified() && !addr.ip().is_multicast()
84}
85
86/// Live Nostr relay state visible to an embedded application.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct FipsEndpointRelayStatus {
89    pub url: String,
90    pub status: String,
91}
92
93impl From<NodeEndpointPeer> for FipsEndpointPeer {
94    fn from(peer: NodeEndpointPeer) -> Self {
95        Self {
96            npub: peer.npub,
97            node_addr: peer.node_addr,
98            connected: peer.connected,
99            transport_addr: peer.transport_addr,
100            transport_type: peer.transport_type,
101            link_id: peer.link_id,
102            srtt_ms: peer.srtt_ms,
103            srtt_age_ms: peer.srtt_age_ms,
104            packets_sent: peer.packets_sent,
105            packets_recv: peer.packets_recv,
106            bytes_sent: peer.bytes_sent,
107            bytes_recv: peer.bytes_recv,
108            rekey_in_progress: peer.rekey_in_progress,
109            rekey_draining: peer.rekey_draining,
110            current_k_bit: peer.current_k_bit,
111            last_outbound_route: peer.last_outbound_route,
112            direct_probe_pending: peer.direct_probe_pending,
113            direct_probe_after_ms: peer.direct_probe_after_ms,
114            direct_probe_retry_count: peer.direct_probe_retry_count,
115            direct_probe_auto_reconnect: peer.direct_probe_auto_reconnect,
116            direct_probe_expires_at_ms: peer.direct_probe_expires_at_ms,
117            nostr_traversal_consecutive_failures: peer.nostr_traversal_consecutive_failures,
118            nostr_traversal_in_cooldown: peer.nostr_traversal_in_cooldown,
119            nostr_traversal_cooldown_until_ms: peer.nostr_traversal_cooldown_until_ms,
120            nostr_traversal_last_observed_skew_ms: peer.nostr_traversal_last_observed_skew_ms,
121        }
122    }
123}
124
125impl From<NodeEndpointRelayStatus> for FipsEndpointRelayStatus {
126    fn from(relay: NodeEndpointRelayStatus) -> Self {
127        Self {
128            url: relay.url,
129            status: relay.status,
130        }
131    }
132}