Skip to main content

nwep/
addr.rs

1use crate::error::{ERR_IDENTITY_INVALID_ADDR, Error, check};
2use crate::ffi;
3use crate::types::{BASE58_ADDR_LEN, NodeId, URL_MAX_LEN};
4use std::ffi::CString;
5use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
6
7/// `Addr` combines a network endpoint (IP + port) with a cryptographic node identity.
8///
9/// The IP is stored internally as 16 bytes in IPv4-mapped IPv6 format
10/// (`::ffff:x.x.x.x`) for IPv4 addresses; pure IPv6 addresses are stored as-is.
11/// Use [`Addr::new_ipv4`] / [`Addr::new_ipv6`] to construct addresses from standard
12/// Rust IP types, and [`Addr::encode`] / [`Addr::decode`] to convert to/from the
13/// compact base58 wire format.
14#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct Addr {
16    /// 16-byte IP address in IPv4-mapped IPv6 form (`::ffff:x.x.x.x` for IPv4).
17    pub ip: [u8; 16],
18    /// The 32-byte node identifier of the peer at this address.
19    pub node_id: NodeId,
20    /// UDP port number.
21    pub port: u16,
22}
23
24impl Addr {
25    /// `new_ipv4` creates an `Addr` from an IPv4 address, storing it as IPv4-mapped IPv6.
26    pub fn new_ipv4(ipv4: Ipv4Addr, node_id: NodeId, port: u16) -> Self {
27        let mut ip = [0u8; 16];
28        // IPv4-mapped IPv6: ::ffff:x.x.x.x
29        ip[10] = 0xff;
30        ip[11] = 0xff;
31        let octets = ipv4.octets();
32        ip[12..16].copy_from_slice(&octets);
33        Addr { ip, node_id, port }
34    }
35
36    /// `new_ipv6` creates an `Addr` from a pure IPv6 address.
37    pub fn new_ipv6(ipv6: Ipv6Addr, node_id: NodeId, port: u16) -> Self {
38        Addr {
39            ip: ipv6.octets(),
40            node_id,
41            port,
42        }
43    }
44
45    /// `ip_addr` returns the address as a standard [`IpAddr`], converting IPv4-mapped IPv6 back to `V4`.
46    pub fn ip_addr(&self) -> IpAddr {
47        // Check if IPv4-mapped
48        if self.ip[..10].iter().all(|&b| b == 0) && self.ip[10] == 0xff && self.ip[11] == 0xff {
49            let octets = [self.ip[12], self.ip[13], self.ip[14], self.ip[15]];
50            IpAddr::V4(Ipv4Addr::from(octets))
51        } else {
52            IpAddr::V6(Ipv6Addr::from(self.ip))
53        }
54    }
55
56    /// `encode` serializes the address to a base58 string for use as a connection target.
57    ///
58    /// # Errors
59    ///
60    /// Returns `Err(ERR_IDENTITY_INVALID_ADDR)` if the IP/port/node_id combination is invalid.
61    pub fn encode(&self) -> Result<String, Error> {
62        let ffi_addr = self.to_ffi();
63        let mut buf = vec![0u8; BASE58_ADDR_LEN + 2];
64        let n = unsafe { ffi::nwep_addr_encode(buf.as_mut_ptr().cast(), buf.len(), &ffi_addr) };
65        if n == 0 {
66            Err(Error::from_code(ERR_IDENTITY_INVALID_ADDR))
67        } else {
68            Ok(String::from_utf8_lossy(&buf[..n]).into_owned())
69        }
70    }
71
72    /// `decode` parses a base58-encoded address string produced by [`encode`](Addr::encode).
73    ///
74    /// # Errors
75    ///
76    /// Returns `Err(ERR_IDENTITY_INVALID_ADDR)` if `encoded` is not a valid NWEP address.
77    pub fn decode(encoded: &str) -> Result<Self, Error> {
78        let c = CString::new(encoded).map_err(|_| Error::from_code(ERR_IDENTITY_INVALID_ADDR))?;
79        let mut ffi_addr = ffi::nwep_addr {
80            ip: [0u8; 16],
81            nodeid: ffi::nwep_nodeid { data: [0u8; 32] },
82            port: 0,
83        };
84        check(unsafe { ffi::nwep_addr_decode(&mut ffi_addr, c.as_ptr()) })?;
85        Ok(Addr::from_ffi(&ffi_addr))
86    }
87
88    pub(crate) fn to_ffi(&self) -> ffi::nwep_addr {
89        ffi::nwep_addr {
90            ip: self.ip,
91            nodeid: ffi::nwep_nodeid {
92                data: self.node_id.0,
93            },
94            port: self.port,
95        }
96    }
97
98    pub(crate) fn from_ffi(a: &ffi::nwep_addr) -> Self {
99        Addr {
100            ip: a.ip,
101            node_id: NodeId(a.nodeid.data),
102            port: a.port,
103        }
104    }
105}
106
107/// `Url` is a NWEP URL combining an [`Addr`] with a path component.
108///
109/// The string form is `web://<base58-addr>/<path>`. Use [`Url::parse`] to parse
110/// a URL string and [`Url::format`] (or the [`Display`](std::fmt::Display) impl)
111/// to serialize it.
112#[derive(Clone, Debug, PartialEq, Eq)]
113pub struct Url {
114    /// The network address and node identity of the target.
115    pub addr: Addr,
116    /// The request path (e.g. `"/hello"`).
117    pub path: String,
118}
119
120impl Url {
121    /// `parse` parses a `web://` URL string into a `Url`.
122    ///
123    /// # Errors
124    ///
125    /// Returns `Err(ERR_IDENTITY_INVALID_ADDR)` if `s` is not a valid NWEP URL.
126    pub fn parse(s: &str) -> Result<Self, Error> {
127        let c = CString::new(s).map_err(|_| Error::from_code(ERR_IDENTITY_INVALID_ADDR))?;
128        let mut ffi_url = unsafe { std::mem::zeroed::<ffi::nwep_url>() };
129        check(unsafe { ffi::nwep_url_parse(&mut ffi_url, c.as_ptr()) })?;
130        let path = unsafe {
131            std::ffi::CStr::from_ptr(ffi_url.path.as_ptr())
132                .to_string_lossy()
133                .into_owned()
134        };
135        Ok(Url {
136            addr: Addr::from_ffi(&ffi_url.addr),
137            path,
138        })
139    }
140
141    /// `format` serializes the URL to a `web://` string.
142    ///
143    /// # Errors
144    ///
145    /// Returns `Err(ERR_IDENTITY_INVALID_ADDR)` if the embedded address is invalid.
146    pub fn format(&self) -> Result<String, Error> {
147        let ffi_url = self.to_ffi_url();
148        let mut buf = vec![0u8; URL_MAX_LEN];
149        let n = unsafe { ffi::nwep_url_format(buf.as_mut_ptr().cast(), buf.len(), &ffi_url) };
150        if n == 0 {
151            Err(Error::from_code(ERR_IDENTITY_INVALID_ADDR))
152        } else {
153            Ok(String::from_utf8_lossy(&buf[..n]).into_owned())
154        }
155    }
156
157    fn to_ffi_url(&self) -> ffi::nwep_url {
158        self.to_ffi()
159    }
160
161    pub(crate) fn to_ffi(&self) -> ffi::nwep_url {
162        let mut ffi_url = unsafe { std::mem::zeroed::<ffi::nwep_url>() };
163        ffi_url.addr = self.addr.to_ffi();
164        let path_bytes = self.path.as_bytes();
165        let len = path_bytes.len().min(255);
166        // path is [i8; 256] in C
167        for (i, &b) in path_bytes[..len].iter().enumerate() {
168            ffi_url.path[i] = b as _;
169        }
170        ffi_url
171    }
172}
173
174impl std::fmt::Display for Url {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        match self.format() {
177            Ok(s) => f.write_str(&s),
178            Err(_) => write!(f, "web://[invalid]:{}{}", self.addr.port, self.path),
179        }
180    }
181}