Skip to main content

fips_core/config/
peer.rs

1//! Peer configuration types.
2//!
3//! Known peer definitions with transport addresses and connection policies.
4
5use serde::{Deserialize, Serialize};
6
7/// Connection policy for a peer.
8///
9/// Determines when and how to establish a connection to a peer.
10#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum ConnectPolicy {
13    /// Connect to this peer automatically on node startup.
14    /// This is the only policy supported in the initial implementation.
15    #[default]
16    AutoConnect,
17
18    /// Connect only when traffic needs to be routed through this peer (future).
19    OnDemand,
20
21    /// Wait for explicit API call to connect (future).
22    Manual,
23}
24
25/// A transport-specific address for reaching a peer.
26///
27/// Each peer can have multiple addresses across different transports,
28/// allowing fallback if one transport is unavailable.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(deny_unknown_fields)]
31pub struct PeerAddress {
32    /// Transport type (e.g., "udp", "tor", "ethernet").
33    pub transport: String,
34
35    /// Transport-specific address string.
36    ///
37    /// Format depends on transport type:
38    /// - UDP/TCP: "host:port" — IP address or DNS hostname
39    ///   (e.g., "192.168.1.1:2121" or "peer1.example.com:2121")
40    /// - Ethernet: "interface/mac" (e.g., "eth0/aa:bb:cc:dd:ee:ff")
41    pub addr: String,
42
43    /// Priority for address selection (lower = preferred).
44    /// When multiple addresses are available, lower priority addresses
45    /// are tried first.
46    #[serde(default = "default_priority")]
47    pub priority: u8,
48}
49
50fn default_priority() -> u8 {
51    100
52}
53
54fn default_auto_reconnect() -> bool {
55    true
56}
57
58impl PeerAddress {
59    /// Create a new peer address.
60    pub fn new(transport: impl Into<String>, addr: impl Into<String>) -> Self {
61        Self {
62            transport: transport.into(),
63            addr: addr.into(),
64            priority: default_priority(),
65        }
66    }
67
68    /// Create a new peer address with priority.
69    pub fn with_priority(
70        transport: impl Into<String>,
71        addr: impl Into<String>,
72        priority: u8,
73    ) -> Self {
74        Self {
75            transport: transport.into(),
76            addr: addr.into(),
77            priority,
78        }
79    }
80}
81
82/// Configuration for a known peer.
83///
84/// Peers are identified by their Nostr public key (npub) and can have
85/// multiple transport addresses for reaching them.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(deny_unknown_fields)]
88pub struct PeerConfig {
89    /// The peer's Nostr public key in npub (bech32) or hex format.
90    pub npub: String,
91
92    /// Human-readable alias for the peer (optional).
93    #[serde(default, skip_serializing_if = "Option::is_none")]
94    pub alias: Option<String>,
95
96    /// Transport addresses for reaching this peer.
97    ///
98    /// At least one address is required unless Nostr discovery is enabled,
99    /// in which case the address list may be empty and endpoints are
100    /// resolved from the peer's Nostr advert at dial time.
101    #[serde(default)]
102    pub addresses: Vec<PeerAddress>,
103
104    /// Connection policy for this peer.
105    #[serde(default)]
106    pub connect_policy: ConnectPolicy,
107
108    /// Whether to automatically reconnect after link-dead removal.
109    /// When true (default), the node will retry connecting with exponential
110    /// backoff after MMP removes this peer due to liveness timeout.
111    #[serde(default = "default_auto_reconnect")]
112    pub auto_reconnect: bool,
113}
114
115impl Default for PeerConfig {
116    fn default() -> Self {
117        Self {
118            npub: String::new(),
119            alias: None,
120            addresses: Vec::new(),
121            connect_policy: ConnectPolicy::default(),
122            auto_reconnect: default_auto_reconnect(),
123        }
124    }
125}
126
127impl PeerConfig {
128    /// Create a new peer config with a single address.
129    pub fn new(
130        npub: impl Into<String>,
131        transport: impl Into<String>,
132        addr: impl Into<String>,
133    ) -> Self {
134        Self {
135            npub: npub.into(),
136            alias: None,
137            addresses: vec![PeerAddress::new(transport, addr)],
138            connect_policy: ConnectPolicy::default(),
139            auto_reconnect: default_auto_reconnect(),
140        }
141    }
142
143    /// Set an alias for the peer.
144    pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
145        self.alias = Some(alias.into());
146        self
147    }
148
149    /// Add an additional address for the peer.
150    pub fn with_address(mut self, addr: PeerAddress) -> Self {
151        self.addresses.push(addr);
152        self
153    }
154
155    /// Get addresses sorted by priority (lowest first).
156    pub fn addresses_by_priority(&self) -> Vec<&PeerAddress> {
157        let mut addrs: Vec<_> = self.addresses.iter().collect();
158        addrs.sort_by_key(|a| a.priority);
159        addrs
160    }
161
162    /// Check if this peer should auto-connect on startup.
163    pub fn is_auto_connect(&self) -> bool {
164        matches!(self.connect_policy, ConnectPolicy::AutoConnect)
165    }
166}