Skip to main content

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: david@saorsalabs.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 normalized = words.replace('-', " ");
108        let decoded = enc.decode(&normalized)?; // returns a normalized address string
109        let socket_addr: SocketAddr = decoded.parse()?; // must include port
110        Ok(Self::new(socket_addr))
111    }
112
113    /// Check if this is an IPv4 address
114    pub fn is_ipv4(&self) -> bool {
115        self.socket_addr.is_ipv4()
116    }
117
118    /// Check if this is an IPv6 address
119    pub fn is_ipv6(&self) -> bool {
120        self.socket_addr.is_ipv6()
121    }
122
123    /// Check if this is a loopback address
124    pub fn is_loopback(&self) -> bool {
125        self.socket_addr.ip().is_loopback()
126    }
127
128    /// Check if this is a private/local address
129    pub fn is_private(&self) -> bool {
130        match self.socket_addr.ip() {
131            IpAddr::V4(ip) => ip.is_private(),
132            IpAddr::V6(ip) => {
133                // Check for unique local addresses (fc00::/7)
134                let octets = ip.octets();
135                (octets[0] & 0xfe) == 0xfc
136            }
137        }
138    }
139}
140
141impl Display for NetworkAddress {
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        if let Some(ref words) = self.four_words {
144            write!(f, "{} ({})", self.socket_addr, words)
145        } else {
146            write!(f, "{}", self.socket_addr)
147        }
148    }
149}
150
151impl FromStr for NetworkAddress {
152    type Err = anyhow::Error;
153
154    fn from_str(s: &str) -> Result<Self> {
155        // First try to parse as a socket address
156        if let Ok(socket_addr) = SocketAddr::from_str(s) {
157            return Ok(Self::new(socket_addr));
158        }
159
160        // Basic Multiaddr support: /ip4/<ip>/tcp/<port> or /ip6/<ip>/tcp/<port>
161        if s.starts_with("/ip4/") || s.starts_with("/ip6/") {
162            let parts: Vec<&str> = s.split('/').filter(|p| !p.is_empty()).collect();
163            // Expect: ["ip4"|"ip6", ip, "tcp", port]
164            #[allow(clippy::collapsible_if)]
165            if parts.len() >= 4 && (parts[0] == "ip4" || parts[0] == "ip6") && parts[2] == "tcp" {
166                if let Ok(port) = parts[3].parse::<u16>() {
167                    // Parse IP
168                    let ip_str = parts[1];
169                    if let Ok(ip) = ip_str.parse::<IpAddr>() {
170                        let socket_addr = SocketAddr::new(ip, port);
171                        return Ok(Self::new(socket_addr));
172                    }
173                }
174            }
175        }
176
177        // Then try to parse as four-word format
178        if let Ok(addr) = Self::from_four_words(s) {
179            return Ok(addr);
180        }
181
182        Err(anyhow!("Invalid address format: {}", s))
183    }
184}
185
186impl From<SocketAddr> for NetworkAddress {
187    fn from(socket_addr: SocketAddr) -> Self {
188        Self::new(socket_addr)
189    }
190}
191
192impl From<&SocketAddr> for NetworkAddress {
193    fn from(socket_addr: &SocketAddr) -> Self {
194        Self::new(*socket_addr)
195    }
196}
197
198impl From<NetworkAddress> for SocketAddr {
199    fn from(addr: NetworkAddress) -> Self {
200        addr.socket_addr
201    }
202}
203
204impl From<&NetworkAddress> for SocketAddr {
205    fn from(addr: &NetworkAddress) -> Self {
206        addr.socket_addr
207    }
208}
209
210/// Collection of network addresses for a peer
211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
212pub struct AddressBook {
213    /// Primary addresses for this peer
214    pub addresses: Vec<NetworkAddress>,
215    /// Last known good address
216    pub last_known_good: Option<NetworkAddress>,
217}
218
219impl AddressBook {
220    /// Create a new empty address book
221    pub fn new() -> Self {
222        Self {
223            addresses: Vec::new(),
224            last_known_good: None,
225        }
226    }
227
228    /// Create an address book with a single address
229    pub fn with_address(address: NetworkAddress) -> Self {
230        Self {
231            addresses: vec![address.clone()],
232            last_known_good: Some(address),
233        }
234    }
235
236    /// Add an address to the book
237    pub fn add_address(&mut self, address: NetworkAddress) {
238        if !self.addresses.contains(&address) {
239            self.addresses.push(address);
240        }
241    }
242
243    /// Remove an address from the book
244    pub fn remove_address(&mut self, address: &NetworkAddress) {
245        self.addresses.retain(|a| a != address);
246        if self.last_known_good.as_ref() == Some(address) {
247            self.last_known_good = self.addresses.first().cloned();
248        }
249    }
250
251    /// Update the last known good address
252    pub fn update_last_known_good(&mut self, address: NetworkAddress) {
253        if self.addresses.contains(&address) {
254            self.last_known_good = Some(address);
255        }
256    }
257
258    /// Get the best address to try first
259    pub fn best_address(&self) -> Option<&NetworkAddress> {
260        self.last_known_good
261            .as_ref()
262            .or_else(|| self.addresses.first())
263    }
264
265    /// Get all addresses
266    pub fn addresses(&self) -> &[NetworkAddress] {
267        &self.addresses
268    }
269
270    /// Check if the address book is empty
271    pub fn is_empty(&self) -> bool {
272        self.addresses.is_empty()
273    }
274
275    /// Get the number of addresses
276    pub fn len(&self) -> usize {
277        self.addresses.len()
278    }
279}
280
281impl Default for AddressBook {
282    fn default() -> Self {
283        Self::new()
284    }
285}
286
287impl Display for AddressBook {
288    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
289        if self.addresses.is_empty() {
290            write!(f, "Empty address book")
291        } else {
292            write!(
293                f,
294                "Addresses: [{}]",
295                self.addresses
296                    .iter()
297                    .map(|a| a.to_string())
298                    .collect::<Vec<_>>()
299                    .join(", ")
300            )
301        }
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use super::*;
308    use std::net::{Ipv4Addr, Ipv6Addr};
309
310    #[test]
311    fn test_network_address_creation() {
312        let addr = NetworkAddress::from_ipv4(Ipv4Addr::new(127, 0, 0, 1), 8080);
313        assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
314        assert_eq!(addr.port(), 8080);
315        assert!(addr.is_ipv4());
316        assert!(addr.is_loopback());
317    }
318
319    #[test]
320    fn test_network_address_from_string() {
321        let addr = "127.0.0.1:8080".parse::<NetworkAddress>().unwrap();
322        assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)));
323        assert_eq!(addr.port(), 8080);
324    }
325
326    #[test]
327    fn test_network_address_display() {
328        let addr = NetworkAddress::from_ipv4(Ipv4Addr::new(192, 168, 1, 1), 9000);
329        let display = addr.to_string();
330        assert!(display.contains("192.168.1.1:9000"));
331    }
332
333    #[test]
334    fn test_address_book() {
335        let mut book = AddressBook::new();
336        let addr1 = NetworkAddress::from_ipv4(Ipv4Addr::new(192, 168, 1, 1), 9000);
337        let addr2 = NetworkAddress::from_ipv4(Ipv4Addr::new(192, 168, 1, 2), 9001);
338
339        book.add_address(addr1.clone());
340        book.add_address(addr2.clone());
341
342        assert_eq!(book.len(), 2);
343        assert_eq!(book.best_address(), Some(&addr1));
344
345        book.update_last_known_good(addr2.clone());
346        assert_eq!(book.best_address(), Some(&addr2));
347    }
348
349    #[test]
350    fn test_private_address_detection() {
351        let private_addr = NetworkAddress::from_ipv4(Ipv4Addr::new(192, 168, 1, 1), 9000);
352        assert!(private_addr.is_private());
353
354        let public_addr = NetworkAddress::from_ipv4(Ipv4Addr::new(8, 8, 8, 8), 53);
355        assert!(!public_addr.is_private());
356    }
357
358    #[test]
359    fn test_ipv6_address() {
360        let addr = NetworkAddress::from_ipv6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), 8080);
361        assert!(addr.is_ipv6());
362        assert!(addr.is_loopback());
363    }
364}