litep2p/
addresses.rs

1// Copyright 2024 litep2p developers
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21use std::{collections::HashSet, sync::Arc};
22
23use multiaddr::{Multiaddr, Protocol};
24use parking_lot::RwLock;
25
26use crate::PeerId;
27
28/// Set of the public addresses of the local node.
29///
30/// The format of the addresses stored in the set contain the local peer ID.
31/// This requirement is enforced by the [`PublicAddresses::add_address`] method,
32/// that will add the local peer ID to the address if it is missing.
33///
34/// # Note
35///
36/// - The addresses are reported to the identify protocol and are used by other nodes
37///  to establish a connection with the local node.
38///
39/// - Users must ensure that the addresses are reachable from the network.
40#[derive(Debug, Clone)]
41pub struct PublicAddresses {
42    pub(crate) inner: Arc<RwLock<HashSet<Multiaddr>>>,
43    local_peer_id: PeerId,
44}
45
46impl PublicAddresses {
47    /// Creates new [`PublicAddresses`] from the given peer ID.
48    pub(crate) fn new(local_peer_id: PeerId) -> Self {
49        Self {
50            inner: Arc::new(RwLock::new(HashSet::new())),
51            local_peer_id,
52        }
53    }
54
55    /// Add a public address to the list of addresses.
56    ///
57    /// The address must contain the local peer ID, otherwise an error is returned.
58    /// In case the address does not contain any peer ID, it will be added.
59    ///
60    /// Returns true if the address was added, false if it was already present.
61    pub fn add_address(&self, address: Multiaddr) -> Result<bool, InsertionError> {
62        let address = ensure_local_peer(address, self.local_peer_id)?;
63        Ok(self.inner.write().insert(address))
64    }
65
66    /// Remove the exact public address.
67    ///
68    /// The provided address must contain the local peer ID.
69    pub fn remove_address(&self, address: &Multiaddr) -> bool {
70        self.inner.write().remove(address)
71    }
72
73    /// Returns a vector of the available listen addresses.
74    pub fn get_addresses(&self) -> Vec<Multiaddr> {
75        self.inner.read().iter().cloned().collect()
76    }
77}
78
79/// Check if the address contains the local peer ID.
80///
81/// If the address does not contain any peer ID, it will be added.
82fn ensure_local_peer(
83    mut address: Multiaddr,
84    local_peer_id: PeerId,
85) -> Result<Multiaddr, InsertionError> {
86    if address.is_empty() {
87        return Err(InsertionError::EmptyAddress);
88    }
89
90    // Verify the peer ID from the address corresponds to the local peer ID.
91    if let Some(peer_id) = PeerId::try_from_multiaddr(&address) {
92        if peer_id != local_peer_id {
93            return Err(InsertionError::DifferentPeerId);
94        }
95    } else {
96        address.push(Protocol::P2p(local_peer_id.into()));
97    }
98
99    Ok(address)
100}
101
102/// The error returned when an address cannot be inserted.
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum InsertionError {
105    /// The address is empty.
106    EmptyAddress,
107    /// The address contains a different peer ID than the local peer ID.
108    DifferentPeerId,
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114    use std::str::FromStr;
115
116    #[test]
117    fn add_remove_contains() {
118        let peer_id = PeerId::random();
119        let addresses = PublicAddresses::new(peer_id);
120        let address = Multiaddr::from_str("/dns/domain1.com/tcp/30333").unwrap();
121        let peer_address = Multiaddr::from_str("/dns/domain1.com/tcp/30333")
122            .unwrap()
123            .with(Protocol::P2p(peer_id.into()));
124
125        assert!(!addresses.get_addresses().contains(&address));
126
127        assert!(addresses.add_address(address.clone()).unwrap());
128        // Adding the address a second time returns Ok(false).
129        assert!(!addresses.add_address(address.clone()).unwrap());
130
131        assert!(!addresses.get_addresses().contains(&address));
132        assert!(addresses.get_addresses().contains(&peer_address));
133
134        addresses.remove_address(&peer_address);
135        assert!(!addresses.get_addresses().contains(&peer_address));
136    }
137
138    #[test]
139    fn get_addresses() {
140        let peer_id = PeerId::random();
141        let addresses = PublicAddresses::new(peer_id);
142        let address1 = Multiaddr::from_str("/dns/domain1.com/tcp/30333").unwrap();
143        let address2 = Multiaddr::from_str("/dns/domain2.com/tcp/30333").unwrap();
144        // Addresses different than the local peer ID are ignored.
145        let address3 = Multiaddr::from_str(
146            "/dns/domain2.com/tcp/30333/p2p/12D3KooWSueCPH3puP2PcvqPJdNaDNF3jMZjtJtDiSy35pWrbt5h",
147        )
148        .unwrap();
149
150        assert!(addresses.add_address(address1.clone()).unwrap());
151        assert!(addresses.add_address(address2.clone()).unwrap());
152        addresses.add_address(address3.clone()).unwrap_err();
153
154        let addresses = addresses.get_addresses();
155        assert_eq!(addresses.len(), 2);
156        assert!(addresses.contains(&address1.with(Protocol::P2p(peer_id.into()))));
157        assert!(addresses.contains(&address2.with(Protocol::P2p(peer_id.into()))));
158    }
159}