1use std::{
2 fmt::{Display, Formatter},
3 net::ToSocketAddrs,
4 num::ParseIntError,
5 str::FromStr,
6 time::Duration,
7};
8
9use hopr_api::Multiaddr;
10pub use hopr_protocol_hopr::{HoprCodecConfig, HoprUnacknowledgedTicketProcessorConfig, SurbStoreConfig};
11pub use hopr_transport_mixer::config::MixerConfig;
12pub use hopr_transport_probe::config::ProbeConfig;
13use hopr_transport_session::{MIN_BALANCER_SAMPLING_INTERVAL, MIN_SURB_BUFFER_DURATION};
14use proc_macro_regex::regex;
15use validator::{Validate, ValidationError, ValidationErrors};
16
17use crate::{errors::HoprTransportError, protocol::PacketPipelineConfig};
18
19const DEFAULT_COUNTER_FLUSH_INTERVAL: Duration = Duration::from_secs(15);
20
21const DEFAULT_PER_PEER_CHANNEL_CAPACITY: usize = 5_000;
22
23fn default_per_peer_channel_capacity() -> usize {
24 DEFAULT_PER_PEER_CHANNEL_CAPACITY
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Validate, smart_default::SmartDefault)]
29#[cfg_attr(
30 feature = "serde",
31 derive(serde::Serialize, serde::Deserialize),
32 serde(deny_unknown_fields)
33)]
34pub struct StreamProtocolConfig {
35 #[validate(range(min = 1))]
45 #[default(default_per_peer_channel_capacity())]
46 #[cfg_attr(feature = "serde", serde(default = "default_per_peer_channel_capacity"))]
47 pub per_peer_channel_capacity: usize,
48}
49
50fn default_counter_flush_interval() -> Duration {
51 DEFAULT_COUNTER_FLUSH_INTERVAL
52}
53
54#[derive(Debug, smart_default::SmartDefault, Validate, Clone, PartialEq)]
56#[cfg_attr(
57 feature = "serde",
58 derive(serde::Serialize, serde::Deserialize),
59 serde(deny_unknown_fields)
60)]
61pub struct HoprProtocolConfig {
62 #[validate(nested)]
64 #[cfg_attr(feature = "serde", serde(default))]
65 pub transport: TransportConfig,
66 #[validate(nested)]
68 #[cfg_attr(feature = "serde", serde(default))]
69 pub packet: HoprPacketPipelineConfig,
70 #[validate(nested)]
72 #[cfg_attr(feature = "serde", serde(default))]
73 pub probe: ProbeConfig,
74 #[validate(nested)]
76 #[cfg_attr(feature = "serde", serde(default))]
77 pub session: SessionGlobalConfig,
78 #[cfg_attr(feature = "serde", serde(default))]
80 pub mixer: MixerConfig,
81 #[validate(nested)]
83 #[cfg_attr(feature = "serde", serde(default))]
84 pub stream: StreamProtocolConfig,
85 #[validate(nested)]
87 #[cfg_attr(feature = "serde", serde(skip))]
88 pub path_planner: crate::path::PathPlannerConfig,
89 #[default(default_counter_flush_interval())]
94 #[cfg_attr(
95 feature = "serde",
96 serde(default = "default_counter_flush_interval", with = "humantime_serde")
97 )]
98 pub counter_flush_interval: Duration,
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Validate, smart_default::SmartDefault)]
103#[cfg_attr(
104 feature = "serde",
105 derive(serde::Serialize, serde::Deserialize),
106 serde(deny_unknown_fields)
107)]
108pub struct HoprPacketPipelineConfig {
109 #[validate(nested)]
111 #[cfg_attr(feature = "serde", serde(default))]
112 pub codec: HoprCodecConfig,
113 #[validate(nested)]
115 #[cfg_attr(feature = "serde", serde(default))]
116 pub ack_processor: HoprUnacknowledgedTicketProcessorConfig,
117 #[validate(nested)]
119 #[cfg_attr(feature = "serde", serde(default))]
120 pub surb_store: SurbStoreConfig,
121 #[validate(nested)]
123 #[cfg_attr(feature = "serde", serde(default))]
124 pub pipeline: PacketPipelineConfig,
125}
126
127regex!(is_dns_address_regex "^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)*[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$");
128
129#[inline]
131pub fn looks_like_domain(s: &str) -> bool {
132 is_dns_address_regex(s)
133}
134
135pub fn is_reachable_domain(host: &str) -> bool {
137 host.to_socket_addrs().is_ok_and(|i| i.into_iter().next().is_some())
138}
139
140#[derive(Debug, Clone, PartialEq)]
142#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
143pub enum HostType {
144 IPv4(String),
146 Domain(String),
148}
149
150impl validator::Validate for HostType {
151 fn validate(&self) -> Result<(), ValidationErrors> {
152 match &self {
153 HostType::IPv4(ip4) => validate_ipv4_address(ip4).map_err(|e| {
154 let mut errs = ValidationErrors::new();
155 errs.add("ipv4", e);
156 errs
157 }),
158 HostType::Domain(domain) => validate_dns_address(domain).map_err(|e| {
159 let mut errs = ValidationErrors::new();
160 errs.add("domain", e);
161 errs
162 }),
163 }
164 }
165}
166
167impl Default for HostType {
168 fn default() -> Self {
169 HostType::IPv4("127.0.0.1".to_owned())
170 }
171}
172
173#[derive(Debug, Validate, Clone, PartialEq)]
179#[cfg_attr(
180 feature = "serde",
181 derive(serde::Serialize, serde::Deserialize),
182 serde(deny_unknown_fields)
183)]
184pub struct HostConfig {
185 #[cfg_attr(feature = "serde", serde(default))]
187 pub address: HostType,
188 #[validate(range(min = 1u16))]
190 #[cfg_attr(feature = "serde", serde(default))]
191 pub port: u16,
192}
193
194impl FromStr for HostConfig {
195 type Err = String;
196
197 fn from_str(s: &str) -> Result<Self, Self::Err> {
198 let (ip_or_dns, str_port) = match s.split_once(':') {
199 None => return Err("Invalid host, is not in the '<host>:<port>' format".into()),
200 Some(split) => split,
201 };
202
203 let port = str_port.parse().map_err(|e: ParseIntError| e.to_string())?;
204
205 if validator::ValidateIp::validate_ipv4(&ip_or_dns) {
206 Ok(Self {
207 address: HostType::IPv4(ip_or_dns.to_owned()),
208 port,
209 })
210 } else if looks_like_domain(ip_or_dns) {
211 Ok(Self {
212 address: HostType::Domain(ip_or_dns.to_owned()),
213 port,
214 })
215 } else {
216 Err("Not a valid IPv4 or domain host".into())
217 }
218 }
219}
220
221impl Display for HostConfig {
222 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
223 write!(f, "{:?}:{}", self.address, self.port)
224 }
225}
226
227fn default_multiaddr_transport(port: u16) -> String {
228 cfg_if::cfg_if! {
229 if #[cfg(feature = "p2p-announce-quic")] {
230 let on_dappnode = std::env::var("DAPPNODE")
234 .map(|v| v.to_lowercase() == "true")
235 .unwrap_or(false);
236
237 let uses_nat = std::env::var("HOPRD_NAT")
239 .map(|v| v.to_lowercase() == "true")
240 .unwrap_or(on_dappnode);
241
242 if uses_nat {
243 format!("tcp/{port}")
244 } else {
245 format!("udp/{port}/quic-v1")
246 }
247 } else {
248 format!("tcp/{port}")
249 }
250 }
251}
252
253impl TryFrom<&HostConfig> for Multiaddr {
254 type Error = HoprTransportError;
255
256 fn try_from(value: &HostConfig) -> Result<Self, Self::Error> {
257 match &value.address {
258 HostType::IPv4(ip) => Multiaddr::from_str(
259 format!("/ip4/{}/{}", ip.as_str(), default_multiaddr_transport(value.port)).as_str(),
260 )
261 .map_err(|e| HoprTransportError::Api(e.to_string())),
262 HostType::Domain(domain) => Multiaddr::from_str(
263 format!("/dns4/{}/{}", domain.as_str(), default_multiaddr_transport(value.port)).as_str(),
264 )
265 .map_err(|e| HoprTransportError::Api(e.to_string())),
266 }
267 }
268}
269
270fn validate_ipv4_address(s: &str) -> Result<(), ValidationError> {
271 if validator::ValidateIp::validate_ipv4(&s) {
272 let ipv4 = std::net::Ipv4Addr::from_str(s)
273 .map_err(|_| ValidationError::new("Failed to deserialize the string into an ipv4 address"))?;
274
275 if ipv4.is_private() || ipv4.is_multicast() || ipv4.is_unspecified() {
276 return Err(ValidationError::new(
277 "IPv4 cannot be private, multicast or unspecified (0.0.0.0)",
278 ))?;
279 }
280 Ok(())
281 } else {
282 Err(ValidationError::new("Invalid IPv4 address provided"))
283 }
284}
285
286fn validate_dns_address(s: &str) -> Result<(), ValidationError> {
287 if looks_like_domain(s) || is_reachable_domain(s) {
288 Ok(())
289 } else {
290 Err(ValidationError::new("Invalid DNS address provided"))
291 }
292}
293
294#[derive(Debug, Default, Validate, Clone, Copy, PartialEq)]
296#[cfg_attr(
297 feature = "serde",
298 derive(serde::Serialize, serde::Deserialize),
299 serde(deny_unknown_fields)
300)]
301pub struct TransportConfig {
302 #[cfg_attr(feature = "serde", serde(default))]
305 pub announce_local_addresses: bool,
306 #[cfg_attr(feature = "serde", serde(default))]
309 pub prefer_local_addresses: bool,
310}
311
312const DEFAULT_SESSION_IDLE_TIMEOUT: Duration = Duration::from_mins(3);
313
314const SESSION_IDLE_MIN_TIMEOUT: Duration = Duration::from_secs(2);
315
316const DEFAULT_SESSION_ESTABLISH_RETRY_DELAY: Duration = Duration::from_secs(2);
317
318const DEFAULT_SESSION_ESTABLISH_MAX_RETRIES: usize = 3;
319
320const DEFAULT_SESSION_BALANCER_SAMPLING: Duration = Duration::from_millis(100);
321
322const DEFAULT_SESSION_BALANCER_BUFFER_DURATION: Duration = Duration::from_secs(5);
323
324const DEFAULT_MAXIMUM_MANAGED_SESSIONS: usize = 100;
325
326fn default_session_balancer_buffer_duration() -> Duration {
327 DEFAULT_SESSION_BALANCER_BUFFER_DURATION
328}
329
330fn default_session_establish_max_retries() -> usize {
331 DEFAULT_SESSION_ESTABLISH_MAX_RETRIES
332}
333
334fn default_session_idle_timeout() -> Duration {
335 DEFAULT_SESSION_IDLE_TIMEOUT
336}
337
338fn default_session_establish_retry_delay() -> Duration {
339 DEFAULT_SESSION_ESTABLISH_RETRY_DELAY
340}
341
342fn default_session_balancer_sampling() -> Duration {
343 DEFAULT_SESSION_BALANCER_SAMPLING
344}
345
346fn default_max_managed_sessions() -> usize {
347 DEFAULT_MAXIMUM_MANAGED_SESSIONS
348}
349
350fn validate_session_idle_timeout(value: &Duration) -> Result<(), ValidationError> {
351 if SESSION_IDLE_MIN_TIMEOUT <= *value {
352 Ok(())
353 } else {
354 Err(ValidationError::new("session idle timeout is too low"))
355 }
356}
357
358fn validate_balancer_sampling(value: &Duration) -> Result<(), ValidationError> {
359 if MIN_BALANCER_SAMPLING_INTERVAL <= *value {
360 Ok(())
361 } else {
362 Err(ValidationError::new("balancer sampling interval is too low"))
363 }
364}
365
366fn validate_balancer_buffer_duration(value: &Duration) -> Result<(), ValidationError> {
367 if MIN_SURB_BUFFER_DURATION <= *value {
368 Ok(())
369 } else {
370 Err(ValidationError::new("minmum SURB buffer duration is too low"))
371 }
372}
373
374#[derive(Clone, Copy, Debug, PartialEq, Eq, Validate, smart_default::SmartDefault)]
376#[cfg_attr(
377 feature = "serde",
378 derive(serde::Serialize, serde::Deserialize),
379 serde(deny_unknown_fields)
380)]
381pub struct SessionGlobalConfig {
382 #[validate(custom(function = "validate_session_idle_timeout"))]
386 #[default(default_session_idle_timeout())]
387 #[cfg_attr(
388 feature = "serde",
389 serde(default = "default_session_idle_timeout", with = "humantime_serde")
390 )]
391 pub idle_timeout: Duration,
392
393 #[validate(range(min = 2, max = 100_000))]
397 #[default(default_max_managed_sessions())]
398 #[cfg_attr(feature = "serde", serde(default = "default_max_managed_sessions"))]
399 pub maximum_managed_sessions: usize,
400
401 #[validate(range(min = 0, max = 20))]
406 #[default(default_session_establish_max_retries())]
407 #[cfg_attr(feature = "serde", serde(default = "default_session_establish_max_retries"))]
408 pub establish_max_retries: usize,
409
410 #[default(default_session_establish_retry_delay())]
414 #[cfg_attr(
415 feature = "serde",
416 serde(default = "default_session_establish_retry_delay", with = "humantime_serde")
417 )]
418 pub establish_retry_timeout: Duration,
419
420 #[validate(custom(function = "validate_balancer_sampling"))]
424 #[default(default_session_balancer_sampling())]
425 #[cfg_attr(
426 feature = "serde",
427 serde(default = "default_session_balancer_sampling", with = "humantime_serde")
428 )]
429 pub balancer_sampling_interval: Duration,
430
431 #[validate(custom(function = "validate_balancer_buffer_duration"))]
439 #[default(default_session_balancer_buffer_duration())]
440 #[cfg_attr(
441 feature = "serde",
442 serde(default = "default_session_balancer_buffer_duration", with = "humantime_serde")
443 )]
444 pub balancer_minimum_surb_buffer_duration: Duration,
445
446 #[validate(nested)]
448 #[cfg_attr(feature = "serde", serde(default))]
449 pub tag_allocator: hopr_transport_tag_allocator::TagAllocatorConfig,
450}
451
452#[cfg(test)]
453mod tests {
454 use super::*;
455
456 #[test]
457 fn test_valid_domains_for_looks_like_a_domain() {
458 assert!(looks_like_domain("localhost"));
459 assert!(looks_like_domain("hoprnet.org"));
460 assert!(looks_like_domain("hub.hoprnet.org"));
461 }
462
463 #[test]
464 fn test_valid_domains_for_does_not_look_like_a_domain() {
465 assert!(!looks_like_domain(".org"));
466 assert!(!looks_like_domain("-hoprnet-.org"));
467 }
468
469 #[test]
470 fn test_valid_domains_should_be_reachable() {
471 assert!(!is_reachable_domain("google.com"));
472 }
473
474 #[test]
475 fn test_verify_valid_ip4_addresses() {
476 assert!(validate_ipv4_address("1.1.1.1").is_ok());
477 assert!(validate_ipv4_address("1.255.1.1").is_ok());
478 assert!(validate_ipv4_address("187.1.1.255").is_ok());
479 assert!(validate_ipv4_address("127.0.0.1").is_ok());
480 }
481
482 #[test]
483 fn test_verify_invalid_ip4_addresses() {
484 assert!(validate_ipv4_address("1.256.1.1").is_err());
485 assert!(validate_ipv4_address("-1.1.1.255").is_err());
486 assert!(validate_ipv4_address("127.0.0.256").is_err());
487 assert!(validate_ipv4_address("1").is_err());
488 assert!(validate_ipv4_address("1.1").is_err());
489 assert!(validate_ipv4_address("1.1.1").is_err());
490 assert!(validate_ipv4_address("1.1.1.1.1").is_err());
491 }
492
493 #[test]
494 fn test_verify_valid_dns_addresses() {
495 assert!(validate_dns_address("localhost").is_ok());
496 assert!(validate_dns_address("google.com").is_ok());
497 assert!(validate_dns_address("hub.hoprnet.org").is_ok());
498 }
499
500 #[test]
501 fn test_verify_invalid_dns_addresses() {
502 assert!(validate_dns_address("-hoprnet-.org").is_err());
503 }
504
505 #[test]
506 fn test_multiaddress_on_dappnode_default() {
507 temp_env::with_var("DAPPNODE", Some("true"), || {
508 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
509 });
510 }
511
512 #[cfg(feature = "p2p-announce-quic")]
513 #[test]
514 fn test_multiaddress_on_non_dappnode_default() {
515 temp_env::with_vars([("DAPPNODE", Some("false")), ("HOPRD_NAT", Some("false"))], || {
516 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
517 });
518 }
519
520 #[cfg(not(feature = "p2p-announce-quic"))]
521 #[test]
522 fn test_multiaddress_on_non_dappnode_default() {
523 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
524 }
525
526 #[test]
527 fn test_multiaddress_on_non_dappnode_uses_nat() {
528 temp_env::with_var("HOPRD_NAT", Some("true"), || {
529 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
530 });
531 }
532
533 #[cfg(feature = "p2p-announce-quic")]
534 #[test]
535 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
536 temp_env::with_var("HOPRD_NAT", Some("false"), || {
537 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
538 });
539 }
540
541 #[cfg(not(feature = "p2p-announce-quic"))]
542 #[test]
543 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
544 temp_env::with_var("HOPRD_NAT", Some("false"), || {
545 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
546 });
547 }
548
549 #[cfg(feature = "p2p-announce-quic")]
550 #[test]
551 fn test_multiaddress_on_dappnode_not_uses_nat() {
552 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
553 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
554 });
555 }
556
557 #[cfg(not(feature = "p2p-announce-quic"))]
558 #[test]
559 fn test_multiaddress_on_dappnode_not_uses_nat() {
560 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
561 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
562 });
563 }
564
565 #[test]
568 fn host_config_parses_ipv4_address() {
569 let cfg = HostConfig::from_str("1.2.3.4:9091").unwrap();
570 insta::assert_debug_snapshot!(cfg);
571 }
572
573 #[test]
574 fn host_config_parses_domain() {
575 let cfg = HostConfig::from_str("example.com:443").unwrap();
576 insta::assert_debug_snapshot!(cfg);
577 }
578
579 #[test]
580 fn host_config_rejects_missing_port() {
581 assert!(HostConfig::from_str("1.2.3.4").is_err());
582 }
583
584 #[test]
585 fn host_config_rejects_invalid_port() {
586 assert!(HostConfig::from_str("1.2.3.4:abc").is_err());
587 }
588
589 #[test]
590 fn host_config_rejects_invalid_host() {
591 assert!(HostConfig::from_str("-invalid-.com:80").is_err());
592 }
593
594 #[test]
595 fn host_config_display_roundtrip() {
596 let cfg = HostConfig {
597 address: HostType::IPv4("10.0.0.1".into()),
598 port: 8080,
599 };
600 insta::assert_yaml_snapshot!(cfg.to_string());
601 }
602
603 #[test]
606 fn multiaddr_from_ipv4_host_config() {
607 let cfg = HostConfig {
608 address: HostType::IPv4("1.2.3.4".into()),
609 port: 9091,
610 };
611 let addr = Multiaddr::try_from(&cfg).unwrap();
612 insta::assert_yaml_snapshot!(addr.to_string());
613 }
614
615 #[test]
616 fn multiaddr_from_domain_host_config() {
617 let cfg = HostConfig {
618 address: HostType::Domain("example.com".into()),
619 port: 443,
620 };
621 let addr = Multiaddr::try_from(&cfg).unwrap();
622 insta::assert_yaml_snapshot!(addr.to_string());
623 }
624
625 #[test]
628 fn session_global_config_default_is_valid() {
629 let cfg = SessionGlobalConfig::default();
630 assert!(cfg.validate().is_ok());
631 }
632
633 #[test]
634 fn session_global_config_too_low_idle_timeout_is_rejected() {
635 let cfg = SessionGlobalConfig {
636 idle_timeout: Duration::from_millis(100),
637 ..Default::default()
638 };
639 assert!(cfg.validate().is_err());
640 }
641
642 #[test]
643 fn session_global_config_too_many_retries_is_rejected() {
644 let cfg = SessionGlobalConfig {
645 establish_max_retries: 21,
646 ..Default::default()
647 };
648 assert!(cfg.validate().is_err());
649 }
650
651 #[test]
652 fn stream_protocol_config_default_has_expected_capacity() {
653 let cfg = StreamProtocolConfig::default();
654 assert_eq!(cfg.per_peer_channel_capacity, DEFAULT_PER_PEER_CHANNEL_CAPACITY);
655 cfg.validate().expect("default StreamProtocolConfig must be valid");
656 }
657
658 #[test]
659 fn stream_protocol_config_zero_capacity_is_rejected() {
660 let cfg = StreamProtocolConfig {
661 per_peer_channel_capacity: 0,
662 };
663 assert!(cfg.validate().is_err());
664 }
665}