saorsa_core/
address.rs

1// Copyright 2024 Saorsa Labs Limited
2//
3// This software is dual-licensed under:
4// - GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later)
5// - Commercial License
6//
7// For AGPL-3.0 license, see LICENSE-AGPL-3.0
8// For commercial licensing, contact: saorsalabs@gmail.com
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under these licenses is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
14//! # Address Types
15//!
16//! This module provides address types for the P2P network using IP:port combinations
17//! and four-word human-readable representations.
18
19use std::fmt::{self, Display};
20use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
21use std::str::FromStr;
22
23use anyhow::{Result, anyhow};
24use serde::{Deserialize, Serialize};
25
26use four_word_networking::FourWordAdaptiveEncoder;
27
28/// Network address that can be represented as IP:port or four-word format
29#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
30pub struct NetworkAddress {
31    /// The socket address (IP + port)
32    pub socket_addr: SocketAddr,
33    /// Optional four-word representation
34    pub four_words: Option<String>,
35}
36
37impl NetworkAddress {
38    /// Create a new `NetworkAddress` from a `SocketAddr`
39    #[must_use]
40    pub fn new(socket_addr: SocketAddr) -> Self {
41        let four_words = Self::encode_four_words(&socket_addr);
42        Self {
43            socket_addr,
44            four_words,
45        }
46    }
47
48    /// Create a `NetworkAddress` from an IP address and port
49    #[must_use]
50    pub fn from_ip_port(ip: IpAddr, port: u16) -> Self {
51        let socket_addr = SocketAddr::new(ip, port);
52        Self::new(socket_addr)
53    }
54
55    /// Create a `NetworkAddress` from IPv4 address and port
56    #[must_use]
57    pub fn from_ipv4(ip: Ipv4Addr, port: u16) -> Self {
58        Self::from_ip_port(IpAddr::V4(ip), port)
59    }
60
61    /// Create a `NetworkAddress` from IPv6 address and port
62    #[must_use]
63    pub fn from_ipv6(ip: Ipv6Addr, port: u16) -> Self {
64        Self::from_ip_port(IpAddr::V6(ip), port)
65    }
66
67    /// Get the IP address
68    #[must_use]
69    pub fn ip(&self) -> IpAddr {
70        self.socket_addr.ip()
71    }
72
73    /// Get the port
74    pub fn port(&self) -> u16 {
75        self.socket_addr.port()
76    }
77
78    /// Get the socket address
79    pub fn socket_addr(&self) -> SocketAddr {
80        self.socket_addr
81    }
82
83    /// Get the four-word representation if available
84    pub fn four_words(&self) -> Option<&str> {
85        self.four_words.as_deref()
86    }
87
88    /// Force regeneration of four-word representation
89    pub fn regenerate_four_words(&mut self) {
90        self.four_words = Self::encode_four_words(&self.socket_addr);
91    }
92
93    /// Encode a SocketAddr to four-word format using four-word-networking
94    fn encode_four_words(addr: &SocketAddr) -> Option<String> {
95        match FourWordAdaptiveEncoder::new().and_then(|enc| enc.encode(&addr.to_string())) {
96            Ok(s) => Some(s.replace(' ', "-")),
97            Err(e) => {
98                tracing::warn!("Failed to encode address {addr}: {e}");
99                None
100            }
101        }
102    }
103
104    /// Decode four-word format to NetworkAddress using four-word-networking
105    pub fn from_four_words(words: &str) -> Result<Self> {
106        let enc = FourWordAdaptiveEncoder::new()?;
107        let decoded = enc.decode(words)?; // returns a normalized address string
108        let socket_addr: SocketAddr = decoded.parse()?; // must include port
109        Ok(Self::new(socket_addr))
110    }
111
112    /// Check if this is an IPv4 address
113    pub fn is_ipv4(&self) -> bool {
114        self.socket_addr.is_ipv4()
115    }
116
117    /// Check if this is an IPv6 address
118    pub fn is_ipv6(&self) -> bool {
119        self.socket_addr.is_ipv6()
120    }
121
122    /// Check if this is a loopback address
123    pub fn is_loopback(&self) -> bool {
124        self.socket_addr.ip().is_loopback()
125    }
126
127    /// Check if this is a private/local address
128    pub fn is_private(&self) -> bool {
129        match self.socket_addr.ip() {
130            IpAddr::V4(ip) => ip.is_private(),
131            IpAddr::V6(ip) => {
132                // Check for unique local addresses (fc00::/7)
133                let octets = ip.octets();
134                (octets[0] & 0xfe) == 0xfc
135            }
136        }
137    }
138}
139
140impl Display for NetworkAddress {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        if let Some(ref words) = self.four_words {
143            write!(f, "{} ({})", self.socket_addr, words)
144        } else {
145            write!(f, "{}", self.socket_addr)
146        }
147    }
148}
149
150impl FromStr for NetworkAddress {
151    type Err = anyhow::Error;
152
153    fn from_str(s: &str) -> Result<Self> {
154        // First try to parse as a socket address
155        if let Ok(socket_addr) = SocketAddr::from_str(s) {
156            return Ok(Self::new(socket_addr));
157        }
158
159        // Basic Multiaddr support: /ip4/<ip>/tcp/<port> or /ip6/<ip>/tcp/<port>
160        if s.starts_with("/ip4/") || s.starts_with("/ip6/") {
161            let parts: Vec<&str> = s.split('/').filter(|p| !p.is_empty()).collect();
162            // Expect: ["ip4"|"ip6", ip, "tcp", port]
163            #[allow(clippy::collapsible_if)]
164            if parts.len() >= 4 && (parts[0] == "ip4" || parts[0] == "ip6") && parts[2] == "tcp" {
165                if let Ok(port) = parts[3].parse::<u16>() {
166                    // Parse IP
167                    let ip_str = parts[1];
168                    if let Ok(ip) = ip_str.parse::<IpAddr>() {
169                        let socket_addr = SocketAddr::new(ip, port);
170                        return Ok(Self::new(socket_addr));
171                    }
172                }
173            }
174        }
175
176        // Then try to parse as four-word format
177        if let Ok(addr) = Self::from_four_words(s) {
178            return Ok(addr);
179        }
180
181        Err(anyhow!("Invalid address format: {}", s))
182    }
183}
184
185impl From<SocketAddr> for NetworkAddress {
186    fn from(socket_addr: SocketAddr) -> Self {
187        Self::new(socket_addr)
188    }
189}
190
191impl From<&SocketAddr> for NetworkAddress {
192    fn from(socket_addr: &SocketAddr) -> Self {
193        Self::new(*socket_addr)
194    }
195}
196
197impl From<NetworkAddress> for SocketAddr {
198    fn from(addr: NetworkAddress) -> Self {
199        addr.socket_addr
200    }
201}
202
203impl From<&NetworkAddress> for SocketAddr {
204    fn from(addr: &NetworkAddress) -> Self {
205        addr.socket_addr
206    }
207}
208
209/// Collection of network addresses for a peer
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
211pub struct AddressBook {
212    /// Primary addresses for this peer
213    pub addresses: Vec<NetworkAddress>,
214    /// Last known good address
215    pub last_known_good: Option<NetworkAddress>,
216}
217
218impl AddressBook {
219    /// Create a new empty address book
220    pub fn new() -> Self {
221        Self {
222            addresses: Vec::new(),
223            last_known_good: None,
224        }
225    }
226
227    /// Create an address book with a single address
228    pub fn with_address(address: NetworkAddress) -> Self {
229        Self {
230            addresses: vec![address.clone()],
231            last_known_good: Some(address),
232        }
233    }
234
235    /// Add an address to the book
236    pub fn add_address(&mut self, address: NetworkAddress) {
237        if !self.addresses.contains(&address) {
238            self.addresses.push(address);
239        }
240    }
241
242    /// Remove an address from the book
243    pub fn remove_address(&mut self, address: &NetworkAddress) {
244        self.addresses.retain(|a| a != address);
245        if self.last_known_good.as_ref() == Some(address) {
246            self.last_known_good = self.addresses.first().cloned();
247        }
248    }
249
250    /// Update the last known good address
251    pub fn update_last_known_good(&mut self, address: NetworkAddress) {
252        if self.addresses.contains(&address) {
253            self.last_known_good = Some(address);
254        }
255    }
256
257    /// Get the best address to try first
258    pub fn best_address(&self) -> Option<&NetworkAddress> {
259        self.last_known_good
260            .as_ref()
261            .or_else(|| self.addresses.first())
262    }
263
264    /// Get all addresses
265    pub fn addresses(&self) -> &[NetworkAddress] {
266        &self.addresses
267    }
268
269    /// Check if the address book is empty
270    pub fn is_empty(&self) -> bool {
271        self.addresses.is_empty()
272    }
273
274    /// Get the number of addresses
275    pub fn len(&self) -> usize {
276        self.addresses.len()
277    }
278}
279
280impl Default for AddressBook {
281    fn default() -> Self {
282        Self::new()
283    }
284}
285
286impl Display for AddressBook {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        if self.addresses.is_empty() {
289            write!(f, "Empty address book")
290        } else {
291            write!(
292                f,
293                "Addresses: [{}]",
294                self.addresses
295                    .iter()
296                    .map(|a| a.to_string())
297                    .collect::<Vec<_>>()
298                    .join(", ")
299            )
300        }
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307    use std::net::{Ipv4Addr, Ipv6Addr};
308
309    #[test]
310    fn test_network_address_creation() {
311        let addr = NetworkAddress::from_ipv4(Ipv4Addr::new(127, 0, 0, 1), 8080);
312        assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
313        assert_eq!(addr.port(), 8080);
314        assert!(addr.is_ipv4());
315        assert!(addr.is_loopback());
316    }
317
318    #[test]
319    fn test_network_address_from_string() {
320        let addr = "127.0.0.1:8080".parse::<NetworkAddress>().unwrap();
321        assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
322        assert_eq!(addr.port(), 8080);
323    }
324
325    #[test]
326    fn test_network_address_display() {
327        let addr = NetworkAddress::from_ipv4(Ipv4Addr::new(192, 168, 1, 1), 9000);
328        let display = addr.to_string();
329        assert!(display.contains("192.168.1.1:9000"));
330    }
331
332    #[test]
333    fn test_address_book() {
334        let mut book = AddressBook::new();
335        let addr1 = NetworkAddress::from_ipv4(Ipv4Addr::new(192, 168, 1, 1), 9000);
336        let addr2 = NetworkAddress::from_ipv4(Ipv4Addr::new(192, 168, 1, 2), 9001);
337
338        book.add_address(addr1.clone());
339        book.add_address(addr2.clone());
340
341        assert_eq!(book.len(), 2);
342        assert_eq!(book.best_address(), Some(&addr1));
343
344        book.update_last_known_good(addr2.clone());
345        assert_eq!(book.best_address(), Some(&addr2));
346    }
347
348    #[test]
349    fn test_private_address_detection() {
350        let private_addr = NetworkAddress::from_ipv4(Ipv4Addr::new(192, 168, 1, 1), 9000);
351        assert!(private_addr.is_private());
352
353        let public_addr = NetworkAddress::from_ipv4(Ipv4Addr::new(8, 8, 8, 8), 53);
354        assert!(!public_addr.is_private());
355    }
356
357    #[test]
358    fn test_ipv6_address() {
359        let addr = NetworkAddress::from_ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080);
360        assert!(addr.is_ipv6());
361        assert!(addr.is_loopback());
362    }
363}