1use std::{
2 fmt::{Display, Formatter},
3 net::SocketAddr,
4 str::FromStr,
5};
6
7use hickory_resolver::name_server::ConnectionProvider;
8use hopr_crypto_packet::{HoprSurb, prelude::HoprSenderId};
9use hopr_crypto_random::Randomizable;
10use hopr_internal_types::{NodeId, prelude::HoprPseudonym};
11pub use hopr_path::ValidatedPath;
12use hopr_primitive_types::bounded::{BoundedSize, BoundedVec};
13use libp2p_identity::PeerId;
14
15use crate::errors::NetworkTypeError;
16
17#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, strum::Display, strum::EnumString)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20#[strum(serialize_all = "lowercase", ascii_case_insensitive)]
21#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
22pub enum IpProtocol {
23 TCP,
24 UDP,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35pub enum IpOrHost {
36 Dns(String, u16),
38 Ip(std::net::SocketAddr),
40}
41
42impl Display for IpOrHost {
43 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
44 match &self {
45 IpOrHost::Dns(host, port) => write!(f, "{host}:{port}"),
46 IpOrHost::Ip(ip) => write!(f, "{ip}"),
47 }
48 }
49}
50
51impl FromStr for IpOrHost {
52 type Err = NetworkTypeError;
53
54 fn from_str(s: &str) -> Result<Self, Self::Err> {
55 if let Ok(addr) = std::net::SocketAddr::from_str(s) {
56 Ok(IpOrHost::Ip(addr))
57 } else {
58 s.split_once(":")
59 .ok_or(NetworkTypeError::Other("missing port delimiter".into()))
60 .and_then(|(host, port_str)| {
61 u16::from_str(port_str)
62 .map(|port| IpOrHost::Dns(host.to_string(), port))
63 .map_err(|_| NetworkTypeError::Other("invalid port number".into()))
64 })
65 }
66 }
67}
68
69impl From<std::net::SocketAddr> for IpOrHost {
70 fn from(value: std::net::SocketAddr) -> Self {
71 IpOrHost::Ip(value)
72 }
73}
74
75impl IpOrHost {
76 #[cfg(feature = "runtime-tokio")]
81 pub async fn resolve_tokio(self) -> std::io::Result<Vec<SocketAddr>> {
82 cfg_if::cfg_if! {
83 if #[cfg(test)] {
84 let config = hickory_resolver::config::ResolverConfig::new();
87 let options = hickory_resolver::config::ResolverOpts::default();
88 let resolver = hickory_resolver::Resolver::builder_with_config(config, hickory_resolver::name_server::TokioConnectionProvider::default()).with_options(options).build();
89 } else {
90 let resolver = hickory_resolver::Resolver::builder_tokio()?.build();
91 }
92 };
93
94 self.resolve(resolver).await
95 }
96
97 pub async fn resolve<P: ConnectionProvider>(
100 self,
101 resolver: hickory_resolver::Resolver<P>,
102 ) -> std::io::Result<Vec<SocketAddr>> {
103 match self {
104 IpOrHost::Dns(name, port) => Ok(resolver
105 .lookup_ip(name)
106 .await?
107 .into_iter()
108 .map(|ip| SocketAddr::new(ip, port))
109 .collect()),
110 IpOrHost::Ip(addr) => Ok(vec![addr]),
111 }
112 }
113
114 pub fn port(&self) -> u16 {
116 match &self {
117 IpOrHost::Dns(_, port) => *port,
118 IpOrHost::Ip(addr) => addr.port(),
119 }
120 }
121
122 pub fn host(&self) -> String {
124 match &self {
125 IpOrHost::Dns(host, _) => host.clone(),
126 IpOrHost::Ip(addr) => addr.ip().to_string(),
127 }
128 }
129
130 pub fn is_dns(&self) -> bool {
132 matches!(self, IpOrHost::Dns(..))
133 }
134
135 pub fn is_ipv4(&self) -> bool {
141 matches!(self, IpOrHost::Ip(addr) if addr.is_ipv4())
142 }
143
144 pub fn is_ipv6(&self) -> bool {
150 matches!(self, IpOrHost::Ip(addr) if addr.is_ipv6())
151 }
152
153 pub fn is_loopback_ip(&self) -> bool {
159 matches!(self, IpOrHost::Ip(addr) if addr.ip().is_loopback())
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Hash, strum::EnumTryAs)]
209#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
210pub enum SealedHost {
211 Plain(IpOrHost),
213 Sealed(Box<[u8]>),
215}
216
217impl SealedHost {
218 const MAX_LEN_WITH_PADDING: usize = 50;
219 pub const PADDING_CHAR: char = '@';
224
225 pub fn seal(host: IpOrHost, peer_id: PeerId) -> crate::errors::Result<Self> {
227 let mut host_str = host.to_string();
228
229 if host_str.len() < Self::MAX_LEN_WITH_PADDING {
231 let pad_len = hopr_crypto_random::random_integer(0, (Self::MAX_LEN_WITH_PADDING as u64).into());
232 for _ in 0..pad_len {
233 host_str.push(Self::PADDING_CHAR);
234 }
235 }
236
237 hopr_crypto_types::seal::seal_data(host_str.as_bytes(), peer_id)
238 .map(Self::Sealed)
239 .map_err(|e| NetworkTypeError::Other(e.to_string()))
240 }
241
242 pub fn unseal(self, key: &hopr_crypto_types::keypairs::OffchainKeypair) -> crate::errors::Result<IpOrHost> {
245 match self {
246 SealedHost::Plain(host) => Ok(host),
247 SealedHost::Sealed(enc) => hopr_crypto_types::seal::unseal_data(&enc, key)
248 .map_err(|e| NetworkTypeError::Other(e.to_string()))
249 .and_then(|data| {
250 String::from_utf8(data.into_vec())
251 .map_err(|e| NetworkTypeError::Other(e.to_string()))
252 .and_then(|s| IpOrHost::from_str(s.trim_end_matches(Self::PADDING_CHAR)))
253 }),
254 }
255 }
256}
257
258impl From<IpOrHost> for SealedHost {
259 fn from(value: IpOrHost) -> Self {
260 Self::Plain(value)
261 }
262}
263
264impl TryFrom<SealedHost> for IpOrHost {
265 type Error = NetworkTypeError;
266
267 fn try_from(value: SealedHost) -> Result<Self, Self::Error> {
268 match value {
269 SealedHost::Plain(host) => Ok(host),
270 SealedHost::Sealed(_) => Err(NetworkTypeError::SealedTarget),
271 }
272 }
273}
274
275impl std::fmt::Display for SealedHost {
276 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
277 match self {
278 SealedHost::Plain(h) => write!(f, "{h}"),
279 SealedHost::Sealed(_) => write!(f, "<redacted host>"),
280 }
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, Eq)]
286#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
287pub enum RoutingOptions {
288 IntermediatePath(BoundedVec<NodeId, { RoutingOptions::MAX_INTERMEDIATE_HOPS }>),
290 Hops(BoundedSize<{ RoutingOptions::MAX_INTERMEDIATE_HOPS }>),
293}
294
295impl RoutingOptions {
296 pub const MAX_INTERMEDIATE_HOPS: usize = 3;
298
299 pub fn invert(self) -> RoutingOptions {
302 match self {
303 RoutingOptions::IntermediatePath(v) => RoutingOptions::IntermediatePath(v.into_iter().rev().collect()),
304 _ => self,
305 }
306 }
307
308 pub fn count_hops(&self) -> usize {
311 match &self {
312 RoutingOptions::IntermediatePath(v) => v.as_ref().len(),
313 RoutingOptions::Hops(h) => (*h).into(),
314 }
315 }
316}
317
318#[derive(Clone, Debug, Copy, PartialEq, Eq)]
320#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
321pub enum SurbMatcher {
322 Exact(HoprSenderId),
324 Pseudonym(HoprPseudonym),
326}
327
328impl SurbMatcher {
329 pub fn pseudonym(&self) -> HoprPseudonym {
331 match self {
332 SurbMatcher::Exact(id) => id.pseudonym(),
333 SurbMatcher::Pseudonym(p) => *p,
334 }
335 }
336}
337
338impl From<HoprPseudonym> for SurbMatcher {
339 fn from(value: HoprPseudonym) -> Self {
340 Self::Pseudonym(value)
341 }
342}
343
344impl From<&HoprPseudonym> for SurbMatcher {
345 fn from(pseudonym: &HoprPseudonym) -> Self {
346 (*pseudonym).into()
347 }
348}
349
350#[derive(Debug, Clone, PartialEq, Eq, strum::EnumIs)]
357#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
358pub enum DestinationRouting {
359 Forward {
362 destination: Box<NodeId>,
364 pseudonym: Option<HoprPseudonym>,
368 forward_options: RoutingOptions,
370 return_options: Option<RoutingOptions>,
372 },
373 Return(SurbMatcher),
377}
378
379impl DestinationRouting {
380 pub fn forward_only<T: Into<NodeId>>(destination: T, forward_options: RoutingOptions) -> Self {
382 Self::Forward {
383 destination: Box::new(destination.into()),
384 pseudonym: None,
385 forward_options,
386 return_options: None,
387 }
388 }
389}
390
391#[derive(Clone, Debug, strum::EnumIs)]
399pub enum ResolvedTransportRouting {
400 Forward {
402 pseudonym: HoprPseudonym,
404 forward_path: ValidatedPath,
406 return_paths: Vec<ValidatedPath>,
408 },
409 Return(HoprSenderId, HoprSurb),
411}
412
413impl ResolvedTransportRouting {
414 pub fn forward_only(forward_path: ValidatedPath) -> Self {
416 Self::Forward {
417 pseudonym: HoprPseudonym::random(),
418 forward_path,
419 return_paths: vec![],
420 }
421 }
422
423 pub fn count_return_paths(&self) -> usize {
426 match self {
427 ResolvedTransportRouting::Forward { return_paths, .. } => return_paths.len(),
428 ResolvedTransportRouting::Return(..) => 0,
429 }
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use hopr_crypto_types::prelude::{Keypair, OffchainKeypair};
436 #[cfg(feature = "runtime-tokio")]
437 use {anyhow::anyhow, std::net::SocketAddr};
438
439 use super::*;
440
441 #[cfg(feature = "runtime-tokio")]
442 #[tokio::test]
443 async fn ip_or_host_must_resolve_dns_name() -> anyhow::Result<()> {
444 match IpOrHost::Dns("localhost".to_string(), 1000)
445 .resolve_tokio()
446 .await?
447 .first()
448 .ok_or(anyhow!("must resolve"))?
449 {
450 SocketAddr::V4(addr) => assert_eq!(*addr, "127.0.0.1:1000".parse()?),
451 SocketAddr::V6(addr) => assert_eq!(*addr, "::1:1000".parse()?),
452 }
453 Ok(())
454 }
455
456 #[cfg(feature = "runtime-tokio")]
457 #[tokio::test]
458 async fn ip_or_host_must_resolve_ip_address() -> anyhow::Result<()> {
459 let actual = IpOrHost::Ip("127.0.0.1:1000".parse()?).resolve_tokio().await?;
460
461 let actual = actual.first().ok_or(anyhow!("must resolve"))?;
462
463 let expected: SocketAddr = "127.0.0.1:1000".parse()?;
464
465 assert_eq!(*actual, expected);
466 Ok(())
467 }
468
469 #[test]
470 fn ip_or_host_should_parse_from_string() -> anyhow::Result<()> {
471 assert_eq!(
472 IpOrHost::Dns("some.dns.name.info".into(), 1234),
473 IpOrHost::from_str("some.dns.name.info:1234")?
474 );
475 assert_eq!(
476 IpOrHost::Ip("1.2.3.4:1234".parse()?),
477 IpOrHost::from_str("1.2.3.4:1234")?
478 );
479 Ok(())
480 }
481
482 #[test]
483 fn sealing_adds_padding_to_hide_length() -> anyhow::Result<()> {
484 let peer_id: PeerId = OffchainKeypair::random().public().into();
485 let last_len = SealedHost::seal("127.0.0.1:1234".parse()?, peer_id)?
486 .try_as_sealed()
487 .unwrap()
488 .len();
489 for _ in 0..20 {
490 let current_len = SealedHost::seal("127.0.0.1:1234".parse()?, peer_id)?
491 .try_as_sealed()
492 .unwrap()
493 .len();
494 if current_len != last_len {
495 return Ok(());
496 }
497 }
498
499 anyhow::bail!("sealed length not randomized");
500 }
501}