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, SurbPopOrder, 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;
22const DEFAULT_STREAM_OPEN_TIMEOUT: Duration = Duration::from_secs(2);
23const DEFAULT_FRAME_WRITER_BACKPRESSURE_BYTES: usize = 131_072;
24const DEFAULT_EGRESS_BACKPRESSURE_TIMEOUT: Duration = Duration::from_secs(2);
25
26pub const MIN_STREAM_OPEN_TIMEOUT: Duration = Duration::from_millis(1);
28
29pub const MIN_EGRESS_BACKPRESSURE_TIMEOUT: Duration = Duration::from_millis(1);
31
32fn default_per_peer_channel_capacity() -> usize {
33 DEFAULT_PER_PEER_CHANNEL_CAPACITY
34}
35
36fn default_stream_open_timeout() -> Duration {
37 DEFAULT_STREAM_OPEN_TIMEOUT
38}
39
40fn default_frame_writer_backpressure_bytes() -> usize {
41 DEFAULT_FRAME_WRITER_BACKPRESSURE_BYTES
42}
43
44#[inline]
45fn default_egress_backpressure_timeout() -> Duration {
46 DEFAULT_EGRESS_BACKPRESSURE_TIMEOUT
47}
48
49fn validate_stream_open_timeout(value: &Duration) -> Result<(), ValidationError> {
50 if MIN_STREAM_OPEN_TIMEOUT <= *value {
51 Ok(())
52 } else {
53 Err(ValidationError::new("stream open timeout must be at least 1 ms"))
54 }
55}
56
57fn validate_egress_backpressure_timeout(value: &Duration) -> Result<(), ValidationError> {
58 if MIN_EGRESS_BACKPRESSURE_TIMEOUT <= *value {
59 Ok(())
60 } else {
61 Err(ValidationError::new(
64 "egress backpressure timeout must be at least 1 ms",
65 ))
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Validate, smart_default::SmartDefault)]
71#[cfg_attr(
72 feature = "serde",
73 derive(serde::Serialize, serde::Deserialize),
74 serde(deny_unknown_fields)
75)]
76pub struct StreamProtocolConfig {
77 #[validate(range(min = 1))]
94 #[default(default_per_peer_channel_capacity())]
95 #[cfg_attr(feature = "serde", serde(default = "default_per_peer_channel_capacity"))]
96 pub per_peer_channel_capacity: usize,
97
98 #[validate(custom(function = "validate_stream_open_timeout"))]
109 #[default(default_stream_open_timeout())]
110 #[cfg_attr(
111 feature = "serde",
112 serde(default = "default_stream_open_timeout", with = "humantime_serde")
113 )]
114 pub stream_open_timeout: Duration,
115
116 #[validate(range(min = 1))]
126 #[default(default_frame_writer_backpressure_bytes())]
127 #[cfg_attr(feature = "serde", serde(default = "default_frame_writer_backpressure_bytes"))]
128 pub frame_writer_backpressure_bytes: usize,
129
130 #[validate(custom(function = "validate_egress_backpressure_timeout"))]
147 #[default(default_egress_backpressure_timeout())]
148 #[cfg_attr(feature = "serde", serde(default = "default_egress_backpressure_timeout"))]
149 pub egress_backpressure_timeout: Duration,
150}
151
152fn default_counter_flush_interval() -> Duration {
153 DEFAULT_COUNTER_FLUSH_INTERVAL
154}
155
156const DEFAULT_SURB_FLUSH_INTERVAL: Duration = Duration::from_secs(1);
163
164fn default_surb_flush_interval() -> Duration {
165 DEFAULT_SURB_FLUSH_INTERVAL
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, smart_default::SmartDefault)]
179#[cfg_attr(
180 feature = "serde",
181 derive(serde::Serialize, serde::Deserialize),
182 serde(deny_unknown_fields)
183)]
184pub struct TransitLatencyConfig {
185 #[default(Duration::from_millis(50))]
187 #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
188 pub mean: Duration,
189 #[default(Duration::from_millis(5))]
193 #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
194 pub std_dev: Duration,
195}
196
197#[derive(Debug, smart_default::SmartDefault, Validate, Clone, PartialEq)]
199#[cfg_attr(
200 feature = "serde",
201 derive(serde::Serialize, serde::Deserialize),
202 serde(deny_unknown_fields)
203)]
204pub struct HoprProtocolConfig {
205 #[validate(nested)]
207 #[cfg_attr(feature = "serde", serde(default))]
208 pub transport: TransportConfig,
209 #[validate(nested)]
211 #[cfg_attr(feature = "serde", serde(default))]
212 pub packet: HoprPacketPipelineConfig,
213 #[validate(nested)]
215 #[cfg_attr(feature = "serde", serde(default))]
216 pub probe: ProbeConfig,
217 #[validate(nested)]
219 #[cfg_attr(feature = "serde", serde(default))]
220 pub session: SessionGlobalConfig,
221 #[cfg_attr(feature = "serde", serde(default))]
223 pub mixer: MixerConfig,
224 #[cfg_attr(feature = "serde", serde(default))]
230 pub transit_latency: Option<TransitLatencyConfig>,
231 #[validate(nested)]
233 #[cfg_attr(feature = "serde", serde(default))]
234 pub stream: StreamProtocolConfig,
235 #[validate(nested)]
237 #[cfg_attr(feature = "serde", serde(skip))]
238 pub path_planner: crate::path::PathPlannerConfig,
239 #[default(default_counter_flush_interval())]
244 #[cfg_attr(
245 feature = "serde",
246 serde(default = "default_counter_flush_interval", with = "humantime_serde")
247 )]
248 pub counter_flush_interval: Duration,
249 #[default(default_surb_flush_interval())]
253 #[cfg_attr(
254 feature = "serde",
255 serde(default = "default_surb_flush_interval", with = "humantime_serde")
256 )]
257 pub surb_flush_interval: Duration,
258}
259
260#[derive(Clone, Copy, Debug, PartialEq, Validate, smart_default::SmartDefault)]
262#[cfg_attr(
263 feature = "serde",
264 derive(serde::Serialize, serde::Deserialize),
265 serde(deny_unknown_fields)
266)]
267pub struct HoprPacketPipelineConfig {
268 #[validate(nested)]
270 #[cfg_attr(feature = "serde", serde(default))]
271 pub codec: HoprCodecConfig,
272 #[validate(nested)]
274 #[cfg_attr(feature = "serde", serde(default))]
275 pub ack_processor: HoprUnacknowledgedTicketProcessorConfig,
276 #[validate(nested)]
278 #[cfg_attr(feature = "serde", serde(default))]
279 pub surb_store: SurbStoreConfig,
280 #[validate(nested)]
282 #[cfg_attr(feature = "serde", serde(default))]
283 pub pipeline: PacketPipelineConfig,
284}
285
286regex!(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]$");
287
288#[inline]
290pub fn looks_like_domain(s: &str) -> bool {
291 is_dns_address_regex(s)
292}
293
294pub fn is_reachable_domain(host: &str) -> bool {
296 host.to_socket_addrs().is_ok_and(|i| i.into_iter().next().is_some())
297}
298
299#[derive(Debug, Clone, PartialEq)]
301#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
302pub enum HostType {
303 IPv4(String),
305 Domain(String),
307}
308
309impl validator::Validate for HostType {
310 fn validate(&self) -> Result<(), ValidationErrors> {
311 match &self {
312 HostType::IPv4(ip4) => validate_ipv4_address(ip4).map_err(|e| {
313 let mut errs = ValidationErrors::new();
314 errs.add("ipv4", e);
315 errs
316 }),
317 HostType::Domain(domain) => validate_dns_address(domain).map_err(|e| {
318 let mut errs = ValidationErrors::new();
319 errs.add("domain", e);
320 errs
321 }),
322 }
323 }
324}
325
326impl Default for HostType {
327 fn default() -> Self {
328 HostType::IPv4("127.0.0.1".to_owned())
329 }
330}
331
332#[derive(Debug, Validate, Clone, PartialEq)]
338#[cfg_attr(
339 feature = "serde",
340 derive(serde::Serialize, serde::Deserialize),
341 serde(deny_unknown_fields)
342)]
343pub struct HostConfig {
344 #[cfg_attr(feature = "serde", serde(default))]
346 pub address: HostType,
347 #[validate(range(min = 1u16))]
349 #[cfg_attr(feature = "serde", serde(default))]
350 pub port: u16,
351}
352
353impl FromStr for HostConfig {
354 type Err = String;
355
356 fn from_str(s: &str) -> Result<Self, Self::Err> {
357 let (ip_or_dns, str_port) = match s.split_once(':') {
358 None => return Err("Invalid host, is not in the '<host>:<port>' format".into()),
359 Some(split) => split,
360 };
361
362 let port = str_port.parse().map_err(|e: ParseIntError| e.to_string())?;
363
364 if validator::ValidateIp::validate_ipv4(&ip_or_dns) {
365 Ok(Self {
366 address: HostType::IPv4(ip_or_dns.to_owned()),
367 port,
368 })
369 } else if looks_like_domain(ip_or_dns) {
370 Ok(Self {
371 address: HostType::Domain(ip_or_dns.to_owned()),
372 port,
373 })
374 } else {
375 Err("Not a valid IPv4 or domain host".into())
376 }
377 }
378}
379
380impl Display for HostConfig {
381 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
382 write!(f, "{:?}:{}", self.address, self.port)
383 }
384}
385
386fn default_multiaddr_transport(port: u16) -> String {
387 cfg_if::cfg_if! {
388 if #[cfg(feature = "p2p-announce-quic")] {
389 let on_dappnode = std::env::var("DAPPNODE")
393 .map(|v| v.to_lowercase() == "true")
394 .unwrap_or(false);
395
396 let uses_nat = std::env::var("HOPRD_NAT")
398 .map(|v| v.to_lowercase() == "true")
399 .unwrap_or(on_dappnode);
400
401 if uses_nat {
402 format!("tcp/{port}")
403 } else {
404 format!("udp/{port}/quic-v1")
405 }
406 } else {
407 format!("tcp/{port}")
408 }
409 }
410}
411
412impl TryFrom<&HostConfig> for Multiaddr {
413 type Error = HoprTransportError;
414
415 fn try_from(value: &HostConfig) -> Result<Self, Self::Error> {
416 match &value.address {
417 HostType::IPv4(ip) => Multiaddr::from_str(
418 format!("/ip4/{}/{}", ip.as_str(), default_multiaddr_transport(value.port)).as_str(),
419 )
420 .map_err(|e| HoprTransportError::Api(e.to_string())),
421 HostType::Domain(domain) => Multiaddr::from_str(
422 format!("/dns4/{}/{}", domain.as_str(), default_multiaddr_transport(value.port)).as_str(),
423 )
424 .map_err(|e| HoprTransportError::Api(e.to_string())),
425 }
426 }
427}
428
429fn validate_ipv4_address(s: &str) -> Result<(), ValidationError> {
430 if validator::ValidateIp::validate_ipv4(&s) {
431 let ipv4 = std::net::Ipv4Addr::from_str(s)
432 .map_err(|_| ValidationError::new("Failed to deserialize the string into an ipv4 address"))?;
433
434 if ipv4.is_private() || ipv4.is_multicast() || ipv4.is_unspecified() {
435 return Err(ValidationError::new(
436 "IPv4 cannot be private, multicast or unspecified (0.0.0.0)",
437 ))?;
438 }
439 Ok(())
440 } else {
441 Err(ValidationError::new("Invalid IPv4 address provided"))
442 }
443}
444
445fn validate_dns_address(s: &str) -> Result<(), ValidationError> {
446 if looks_like_domain(s) || is_reachable_domain(s) {
447 Ok(())
448 } else {
449 Err(ValidationError::new("Invalid DNS address provided"))
450 }
451}
452
453#[derive(Debug, Default, Validate, Clone, Copy, PartialEq)]
455#[cfg_attr(
456 feature = "serde",
457 derive(serde::Serialize, serde::Deserialize),
458 serde(deny_unknown_fields)
459)]
460pub struct TransportConfig {
461 #[cfg_attr(feature = "serde", serde(default))]
464 pub announce_local_addresses: bool,
465 #[cfg_attr(feature = "serde", serde(default))]
468 pub prefer_local_addresses: bool,
469}
470
471const DEFAULT_SESSION_IDLE_TIMEOUT: Duration = Duration::from_mins(3);
472
473const SESSION_IDLE_MIN_TIMEOUT: Duration = Duration::from_secs(2);
474
475const DEFAULT_SESSION_ESTABLISH_RETRY_DELAY: Duration = Duration::from_secs(2);
476
477const DEFAULT_SESSION_ESTABLISH_MAX_RETRIES: usize = 3;
478
479const DEFAULT_SESSION_BALANCER_SAMPLING: Duration = Duration::from_millis(100);
480
481const DEFAULT_SESSION_BALANCER_BUFFER_DURATION: Duration = Duration::from_secs(5);
482
483const DEFAULT_MAXIMUM_MANAGED_SESSIONS: usize = 100;
484
485fn default_session_balancer_buffer_duration() -> Duration {
486 DEFAULT_SESSION_BALANCER_BUFFER_DURATION
487}
488
489fn default_session_establish_max_retries() -> usize {
490 DEFAULT_SESSION_ESTABLISH_MAX_RETRIES
491}
492
493fn default_session_idle_timeout() -> Duration {
494 DEFAULT_SESSION_IDLE_TIMEOUT
495}
496
497fn default_session_establish_retry_delay() -> Duration {
498 DEFAULT_SESSION_ESTABLISH_RETRY_DELAY
499}
500
501fn default_session_balancer_sampling() -> Duration {
502 DEFAULT_SESSION_BALANCER_SAMPLING
503}
504
505fn default_max_managed_sessions() -> usize {
506 DEFAULT_MAXIMUM_MANAGED_SESSIONS
507}
508
509fn default_session_surb_balance_notify_period() -> Option<Duration> {
517 Some(Duration::from_secs(15))
518}
519
520fn default_session_max_frames_behind_gap() -> Option<usize> {
521 Some(256)
522}
523
524fn validate_session_idle_timeout(value: &Duration) -> Result<(), ValidationError> {
525 if SESSION_IDLE_MIN_TIMEOUT <= *value {
526 Ok(())
527 } else {
528 Err(ValidationError::new("session idle timeout is too low"))
529 }
530}
531
532fn validate_balancer_sampling(value: &Duration) -> Result<(), ValidationError> {
533 if MIN_BALANCER_SAMPLING_INTERVAL <= *value {
534 Ok(())
535 } else {
536 Err(ValidationError::new("balancer sampling interval is too low"))
537 }
538}
539
540fn validate_balancer_buffer_duration(value: &Duration) -> Result<(), ValidationError> {
541 if MIN_SURB_BUFFER_DURATION <= *value {
542 Ok(())
543 } else {
544 Err(ValidationError::new("minmum SURB buffer duration is too low"))
545 }
546}
547
548fn validate_surb_balance_notify_period(value: &Duration) -> Result<(), ValidationError> {
549 if *value >= Duration::from_secs(1) {
551 Ok(())
552 } else {
553 Err(ValidationError::new(
554 "SURB balance notify period must be at least 1 second",
555 ))
556 }
557}
558
559#[derive(Clone, Copy, Debug, PartialEq, Eq, Validate, smart_default::SmartDefault)]
561#[cfg_attr(
562 feature = "serde",
563 derive(serde::Serialize, serde::Deserialize),
564 serde(deny_unknown_fields)
565)]
566pub struct SessionGlobalConfig {
567 #[validate(custom(function = "validate_session_idle_timeout"))]
571 #[default(default_session_idle_timeout())]
572 #[cfg_attr(
573 feature = "serde",
574 serde(default = "default_session_idle_timeout", with = "humantime_serde")
575 )]
576 pub idle_timeout: Duration,
577
578 #[validate(range(min = 2, max = 100_000))]
582 #[default(default_max_managed_sessions())]
583 #[cfg_attr(feature = "serde", serde(default = "default_max_managed_sessions"))]
584 pub maximum_managed_sessions: usize,
585
586 #[validate(range(min = 0, max = 20))]
591 #[default(default_session_establish_max_retries())]
592 #[cfg_attr(feature = "serde", serde(default = "default_session_establish_max_retries"))]
593 pub establish_max_retries: usize,
594
595 #[default(default_session_establish_retry_delay())]
599 #[cfg_attr(
600 feature = "serde",
601 serde(default = "default_session_establish_retry_delay", with = "humantime_serde")
602 )]
603 pub establish_retry_timeout: Duration,
604
605 #[validate(custom(function = "validate_balancer_sampling"))]
609 #[default(default_session_balancer_sampling())]
610 #[cfg_attr(
611 feature = "serde",
612 serde(default = "default_session_balancer_sampling", with = "humantime_serde")
613 )]
614 pub balancer_sampling_interval: Duration,
615
616 #[validate(custom(function = "validate_balancer_buffer_duration"))]
624 #[default(default_session_balancer_buffer_duration())]
625 #[cfg_attr(
626 feature = "serde",
627 serde(default = "default_session_balancer_buffer_duration", with = "humantime_serde")
628 )]
629 pub balancer_minimum_surb_buffer_duration: Duration,
630
631 #[validate(custom(function = "validate_surb_balance_notify_period"))]
638 #[default(default_session_surb_balance_notify_period())]
639 #[cfg_attr(
640 feature = "serde",
641 serde(
642 default = "default_session_surb_balance_notify_period",
643 with = "humantime_serde::option"
644 )
645 )]
646 pub surb_balance_notify_period: Option<Duration>,
647
648 #[default(default_session_max_frames_behind_gap())]
659 #[cfg_attr(feature = "serde", serde(default = "default_session_max_frames_behind_gap"))]
660 pub max_frames_behind_gap: Option<usize>,
661
662 #[validate(nested)]
664 #[cfg_attr(feature = "serde", serde(default))]
665 pub tag_allocator: hopr_transport_tag_allocator::TagAllocatorConfig,
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[test]
673 fn egress_backpressure_timeout_rejects_sub_minimum_values() {
674 assert!(validate_egress_backpressure_timeout(&Duration::ZERO).is_err());
675 assert!(validate_egress_backpressure_timeout(&Duration::from_micros(500)).is_err());
676 assert!(validate_egress_backpressure_timeout(&MIN_EGRESS_BACKPRESSURE_TIMEOUT).is_ok());
677 assert!(validate_egress_backpressure_timeout(&DEFAULT_EGRESS_BACKPRESSURE_TIMEOUT).is_ok());
678 }
679
680 #[test]
681 fn stream_protocol_config_default_is_valid() {
682 assert!(StreamProtocolConfig::default().validate().is_ok());
683 }
684
685 #[test]
686 fn test_valid_domains_for_looks_like_a_domain() {
687 assert!(looks_like_domain("localhost"));
688 assert!(looks_like_domain("hoprnet.org"));
689 assert!(looks_like_domain("hub.hoprnet.org"));
690 }
691
692 #[test]
693 fn test_valid_domains_for_does_not_look_like_a_domain() {
694 assert!(!looks_like_domain(".org"));
695 assert!(!looks_like_domain("-hoprnet-.org"));
696 }
697
698 #[test]
699 fn test_valid_domains_should_be_reachable() {
700 assert!(!is_reachable_domain("google.com"));
701 }
702
703 #[test]
704 fn test_verify_valid_ip4_addresses() {
705 assert!(validate_ipv4_address("1.1.1.1").is_ok());
706 assert!(validate_ipv4_address("1.255.1.1").is_ok());
707 assert!(validate_ipv4_address("187.1.1.255").is_ok());
708 assert!(validate_ipv4_address("127.0.0.1").is_ok());
709 }
710
711 #[test]
712 fn test_verify_invalid_ip4_addresses() {
713 assert!(validate_ipv4_address("1.256.1.1").is_err());
714 assert!(validate_ipv4_address("-1.1.1.255").is_err());
715 assert!(validate_ipv4_address("127.0.0.256").is_err());
716 assert!(validate_ipv4_address("1").is_err());
717 assert!(validate_ipv4_address("1.1").is_err());
718 assert!(validate_ipv4_address("1.1.1").is_err());
719 assert!(validate_ipv4_address("1.1.1.1.1").is_err());
720 }
721
722 #[test]
723 fn test_verify_valid_dns_addresses() {
724 assert!(validate_dns_address("localhost").is_ok());
725 assert!(validate_dns_address("google.com").is_ok());
726 assert!(validate_dns_address("hub.hoprnet.org").is_ok());
727 }
728
729 #[test]
730 fn test_verify_invalid_dns_addresses() {
731 assert!(validate_dns_address("-hoprnet-.org").is_err());
732 }
733
734 #[test]
735 fn test_multiaddress_on_dappnode_default() {
736 temp_env::with_var("DAPPNODE", Some("true"), || {
737 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
738 });
739 }
740
741 #[cfg(feature = "p2p-announce-quic")]
742 #[test]
743 fn test_multiaddress_on_non_dappnode_default() {
744 temp_env::with_vars([("DAPPNODE", Some("false")), ("HOPRD_NAT", Some("false"))], || {
745 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
746 });
747 }
748
749 #[cfg(not(feature = "p2p-announce-quic"))]
750 #[test]
751 fn test_multiaddress_on_non_dappnode_default() {
752 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
753 }
754
755 #[test]
756 fn test_multiaddress_on_non_dappnode_uses_nat() {
757 temp_env::with_var("HOPRD_NAT", Some("true"), || {
758 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
759 });
760 }
761
762 #[cfg(feature = "p2p-announce-quic")]
763 #[test]
764 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
765 temp_env::with_var("HOPRD_NAT", Some("false"), || {
766 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
767 });
768 }
769
770 #[cfg(not(feature = "p2p-announce-quic"))]
771 #[test]
772 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
773 temp_env::with_var("HOPRD_NAT", Some("false"), || {
774 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
775 });
776 }
777
778 #[cfg(feature = "p2p-announce-quic")]
779 #[test]
780 fn test_multiaddress_on_dappnode_not_uses_nat() {
781 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
782 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
783 });
784 }
785
786 #[cfg(not(feature = "p2p-announce-quic"))]
787 #[test]
788 fn test_multiaddress_on_dappnode_not_uses_nat() {
789 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
790 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
791 });
792 }
793
794 #[test]
797 fn host_config_parses_ipv4_address() {
798 let cfg = HostConfig::from_str("1.2.3.4:9091").unwrap();
799 insta::assert_debug_snapshot!(cfg);
800 }
801
802 #[test]
803 fn host_config_parses_domain() {
804 let cfg = HostConfig::from_str("example.com:443").unwrap();
805 insta::assert_debug_snapshot!(cfg);
806 }
807
808 #[test]
809 fn host_config_rejects_missing_port() {
810 assert!(HostConfig::from_str("1.2.3.4").is_err());
811 }
812
813 #[test]
814 fn host_config_rejects_invalid_port() {
815 assert!(HostConfig::from_str("1.2.3.4:abc").is_err());
816 }
817
818 #[test]
819 fn host_config_rejects_invalid_host() {
820 assert!(HostConfig::from_str("-invalid-.com:80").is_err());
821 }
822
823 #[test]
824 fn host_config_display_roundtrip() {
825 let cfg = HostConfig {
826 address: HostType::IPv4("10.0.0.1".into()),
827 port: 8080,
828 };
829 insta::assert_yaml_snapshot!(cfg.to_string());
830 }
831
832 #[test]
835 fn multiaddr_from_ipv4_host_config() {
836 let cfg = HostConfig {
837 address: HostType::IPv4("1.2.3.4".into()),
838 port: 9091,
839 };
840 let addr = Multiaddr::try_from(&cfg).unwrap();
841 insta::assert_yaml_snapshot!(addr.to_string());
842 }
843
844 #[test]
845 fn multiaddr_from_domain_host_config() {
846 let cfg = HostConfig {
847 address: HostType::Domain("example.com".into()),
848 port: 443,
849 };
850 let addr = Multiaddr::try_from(&cfg).unwrap();
851 insta::assert_yaml_snapshot!(addr.to_string());
852 }
853
854 #[test]
857 fn session_global_config_default_is_valid() {
858 let cfg = SessionGlobalConfig::default();
859 assert!(cfg.validate().is_ok());
860 }
861
862 #[test]
863 fn session_global_config_too_low_idle_timeout_is_rejected() {
864 let cfg = SessionGlobalConfig {
865 idle_timeout: Duration::from_millis(100),
866 ..Default::default()
867 };
868 assert!(cfg.validate().is_err());
869 }
870
871 #[test]
872 fn session_global_config_too_many_retries_is_rejected() {
873 let cfg = SessionGlobalConfig {
874 establish_max_retries: 21,
875 ..Default::default()
876 };
877 assert!(cfg.validate().is_err());
878 }
879
880 #[test]
881 fn stream_protocol_config_default_has_expected_values() {
882 let cfg = StreamProtocolConfig::default();
883 assert_eq!(cfg.per_peer_channel_capacity, DEFAULT_PER_PEER_CHANNEL_CAPACITY);
884 assert_eq!(cfg.stream_open_timeout, DEFAULT_STREAM_OPEN_TIMEOUT);
885 assert_eq!(
886 cfg.frame_writer_backpressure_bytes,
887 DEFAULT_FRAME_WRITER_BACKPRESSURE_BYTES
888 );
889 cfg.validate().expect("default StreamProtocolConfig must be valid");
890 }
891
892 #[test]
893 fn stream_protocol_config_zero_capacity_is_rejected() {
894 let cfg = StreamProtocolConfig {
895 per_peer_channel_capacity: 0,
896 ..Default::default()
897 };
898 assert!(cfg.validate().is_err());
899 }
900
901 #[test]
902 fn stream_protocol_config_zero_backpressure_bytes_is_rejected() {
903 let cfg = StreamProtocolConfig {
904 frame_writer_backpressure_bytes: 0,
905 ..Default::default()
906 };
907 assert!(cfg.validate().is_err());
908 }
909
910 #[test]
911 fn stream_protocol_config_zero_stream_open_timeout_is_rejected() {
912 let cfg = StreamProtocolConfig {
913 stream_open_timeout: Duration::ZERO,
914 ..Default::default()
915 };
916 assert!(cfg.validate().is_err());
917 }
918
919 #[test]
920 fn stream_protocol_config_zero_backpressure_timeout_is_rejected() {
921 let cfg = StreamProtocolConfig {
925 egress_backpressure_timeout: Duration::ZERO,
926 ..Default::default()
927 };
928 assert!(cfg.validate().is_err());
929 }
930}