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