p2panda_net/
utils.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Formatting, conversion and connectivity status utilities.
4use std::net::{IpAddr, SocketAddr};
5
6use crate::NodeId;
7
8/// Returns a displayable string representing the underlying value in a short format, easy to read
9/// during debugging and logging.
10pub trait ShortFormat {
11    fn fmt_short(&self) -> String;
12}
13
14impl ShortFormat for NodeId {
15    fn fmt_short(&self) -> String {
16        self.to_hex()[0..10].to_string()
17    }
18}
19
20impl ShortFormat for iroh::EndpointId {
21    fn fmt_short(&self) -> String {
22        self.to_string()[0..10].to_string()
23    }
24}
25
26impl ShortFormat for [u8; 32] {
27    fn fmt_short(&self) -> String {
28        hex::encode(&self[0..5]).to_string()
29    }
30}
31
32impl ShortFormat for Vec<u8> {
33    fn fmt_short(&self) -> String {
34        hex::encode(&self[0..5]).to_string()
35    }
36}
37
38impl ShortFormat for Vec<NodeId> {
39    fn fmt_short(&self) -> String {
40        let list: Vec<String> = self.iter().map(|addr| addr.fmt_short()).collect();
41        format!("[{}]", list.join(", "))
42    }
43}
44
45impl ShortFormat for Vec<iroh::EndpointId> {
46    fn fmt_short(&self) -> String {
47        let list: Vec<String> = self
48            .iter()
49            .map(|addr| addr.fmt_short().to_string())
50            .collect();
51        format!("[{}]", list.join(", "))
52    }
53}
54
55/// Connectivity status derived from a given IP address.
56///
57/// Defines whether a node appears to be locally-reachable (on the host machine or via WLAN) or
58/// globally-reachable (via the internet). This is not a guarantee of the overall node connectivity
59/// status but a best-guess based on the provided IP address.
60#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
61pub enum ConnectivityStatus {
62    /// The IP address is neither link-local, loopback nor global.
63    Other,
64
65    /// The IP address is link-local or loopback.
66    Local,
67
68    /// The IP address appears to be globally reachable.
69    Global,
70}
71
72impl ConnectivityStatus {
73    pub fn is_global(&self) -> bool {
74        self == &ConnectivityStatus::Global
75    }
76}
77
78/// Parse a `SocketAddr` and return the best approximation of the connectivity status based on the
79/// IP address.
80pub fn connectivity_status(addr: &SocketAddr) -> ConnectivityStatus {
81    match addr.ip() {
82        IpAddr::V4(ip) => {
83            if ip.is_loopback() || ip.is_link_local() || ip.is_private() {
84                ConnectivityStatus::Local
85            } else if ip.is_multicast()
86                || ip.is_broadcast()
87                || ip.is_documentation()
88                || ip.is_unspecified()
89            {
90                ConnectivityStatus::Other
91            } else {
92                ConnectivityStatus::Global
93            }
94        }
95        IpAddr::V6(ip) => {
96            if ip.is_loopback() || ip.is_unique_local() || ip.is_unicast_link_local() {
97                ConnectivityStatus::Local
98            } else if ip.is_multicast() || ip.is_unspecified() {
99                ConnectivityStatus::Other
100            } else {
101                ConnectivityStatus::Global
102            }
103        }
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::ConnectivityStatus;
110
111    #[test]
112    fn order() {
113        assert!(ConnectivityStatus::Global > ConnectivityStatus::Local);
114        assert!(ConnectivityStatus::Local > ConnectivityStatus::Other);
115    }
116}