1use std::{
2 fmt::{Display, Formatter},
3 net::ToSocketAddrs,
4 num::ParseIntError,
5 str::FromStr,
6 time::Duration,
7};
8
9pub use hopr_protocol_hopr::{HoprCodecConfig, HoprTicketProcessorConfig, SurbStoreConfig};
10use hopr_transport_identity::Multiaddr;
11pub use hopr_transport_network::config::NetworkConfig;
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;
18
19#[derive(Debug, smart_default::SmartDefault, Validate, Clone, Copy, PartialEq)]
21#[cfg_attr(
22 feature = "serde",
23 derive(serde::Serialize, serde::Deserialize),
24 serde(deny_unknown_fields)
25)]
26pub struct HoprProtocolConfig {
27 #[validate(nested)]
29 pub transport: TransportConfig,
30 #[validate(nested)]
32 pub network: NetworkConfig,
33 #[validate(nested)]
35 pub packet: HoprPacketPipelineConfig,
36 #[validate(nested)]
38 pub probe: ProbeConfig,
39 #[validate(nested)]
41 pub session: SessionGlobalConfig,
42}
43
44#[derive(Clone, Copy, Debug, Default, PartialEq, Validate)]
46#[cfg_attr(
47 feature = "serde",
48 derive(serde::Serialize, serde::Deserialize),
49 serde(deny_unknown_fields)
50)]
51pub struct HoprPacketPipelineConfig {
52 #[validate(nested)]
54 pub codec: HoprCodecConfig,
55 #[validate(nested)]
57 pub ticket_processing: HoprTicketProcessorConfig,
58 #[validate(nested)]
60 pub surb_store: SurbStoreConfig,
61}
62
63regex!(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]$");
64
65#[inline]
67pub fn looks_like_domain(s: &str) -> bool {
68 is_dns_address_regex(s)
69}
70
71pub fn is_reachable_domain(host: &str) -> bool {
73 host.to_socket_addrs().is_ok_and(|i| i.into_iter().next().is_some())
74}
75
76#[derive(Debug, Clone, PartialEq)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub enum HostType {
80 IPv4(String),
82 Domain(String),
84}
85
86impl validator::Validate for HostType {
87 fn validate(&self) -> Result<(), ValidationErrors> {
88 match &self {
89 HostType::IPv4(ip4) => validate_ipv4_address(ip4).map_err(|e| {
90 let mut errs = ValidationErrors::new();
91 errs.add("ipv4", e);
92 errs
93 }),
94 HostType::Domain(domain) => validate_dns_address(domain).map_err(|e| {
95 let mut errs = ValidationErrors::new();
96 errs.add("domain", e);
97 errs
98 }),
99 }
100 }
101}
102
103impl Default for HostType {
104 fn default() -> Self {
105 HostType::IPv4("127.0.0.1".to_owned())
106 }
107}
108
109#[derive(Debug, Validate, Clone, PartialEq)]
115#[cfg_attr(
116 feature = "serde",
117 derive(serde::Serialize, serde::Deserialize),
118 serde(deny_unknown_fields)
119)]
120pub struct HostConfig {
121 #[cfg_attr(feature = "serde", serde(default))]
123 pub address: HostType,
124 #[validate(range(min = 1u16))]
126 #[cfg_attr(feature = "serde", serde(default))]
127 pub port: u16,
128}
129
130impl FromStr for HostConfig {
131 type Err = String;
132
133 fn from_str(s: &str) -> Result<Self, Self::Err> {
134 let (ip_or_dns, str_port) = match s.split_once(':') {
135 None => return Err("Invalid host, is not in the '<host>:<port>' format".into()),
136 Some(split) => split,
137 };
138
139 let port = str_port.parse().map_err(|e: ParseIntError| e.to_string())?;
140
141 if validator::ValidateIp::validate_ipv4(&ip_or_dns) {
142 Ok(Self {
143 address: HostType::IPv4(ip_or_dns.to_owned()),
144 port,
145 })
146 } else if looks_like_domain(ip_or_dns) {
147 Ok(Self {
148 address: HostType::Domain(ip_or_dns.to_owned()),
149 port,
150 })
151 } else {
152 Err("Not a valid IPv4 or domain host".into())
153 }
154 }
155}
156
157impl Display for HostConfig {
158 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
159 write!(f, "{:?}:{}", self.address, self.port)
160 }
161}
162
163fn default_multiaddr_transport(port: u16) -> String {
164 cfg_if::cfg_if! {
165 if #[cfg(all(feature = "p2p-announce-quic", feature = "p2p-transport-quic"))] {
166 let on_dappnode = std::env::var("DAPPNODE")
170 .map(|v| v.to_lowercase() == "true")
171 .unwrap_or(false);
172
173 let uses_nat = std::env::var("HOPRD_NAT")
175 .map(|v| v.to_lowercase() == "true")
176 .unwrap_or(on_dappnode);
177
178 if uses_nat {
179 format!("tcp/{port}")
180 } else {
181 format!("udp/{port}/quic-v1")
182 }
183 } else {
184 format!("tcp/{port}")
185 }
186 }
187}
188
189impl TryFrom<&HostConfig> for Multiaddr {
190 type Error = HoprTransportError;
191
192 fn try_from(value: &HostConfig) -> Result<Self, Self::Error> {
193 match &value.address {
194 HostType::IPv4(ip) => Multiaddr::from_str(
195 format!("/ip4/{}/{}", ip.as_str(), default_multiaddr_transport(value.port)).as_str(),
196 )
197 .map_err(|e| HoprTransportError::Api(e.to_string())),
198 HostType::Domain(domain) => Multiaddr::from_str(
199 format!("/dns4/{}/{}", domain.as_str(), default_multiaddr_transport(value.port)).as_str(),
200 )
201 .map_err(|e| HoprTransportError::Api(e.to_string())),
202 }
203 }
204}
205
206fn validate_ipv4_address(s: &str) -> Result<(), ValidationError> {
207 if validator::ValidateIp::validate_ipv4(&s) {
208 let ipv4 = std::net::Ipv4Addr::from_str(s)
209 .map_err(|_| ValidationError::new("Failed to deserialize the string into an ipv4 address"))?;
210
211 if ipv4.is_private() || ipv4.is_multicast() || ipv4.is_unspecified() {
212 return Err(ValidationError::new(
213 "IPv4 cannot be private, multicast or unspecified (0.0.0.0)",
214 ))?;
215 }
216 Ok(())
217 } else {
218 Err(ValidationError::new("Invalid IPv4 address provided"))
219 }
220}
221
222fn validate_dns_address(s: &str) -> Result<(), ValidationError> {
223 if looks_like_domain(s) || is_reachable_domain(s) {
224 Ok(())
225 } else {
226 Err(ValidationError::new("Invalid DNS address provided"))
227 }
228}
229
230#[derive(Debug, Default, Validate, Clone, Copy, PartialEq)]
232#[cfg_attr(
233 feature = "serde",
234 derive(serde::Serialize, serde::Deserialize),
235 serde(deny_unknown_fields)
236)]
237pub struct TransportConfig {
238 #[cfg_attr(feature = "serde", serde(default))]
241 pub announce_local_addresses: bool,
242 #[cfg_attr(feature = "serde", serde(default))]
245 pub prefer_local_addresses: bool,
246}
247
248const DEFAULT_SESSION_MAX_SESSIONS: u32 = 2048;
249
250const DEFAULT_SESSION_IDLE_TIMEOUT: Duration = Duration::from_mins(3);
251
252const SESSION_IDLE_MIN_TIMEOUT: Duration = Duration::from_secs(2);
253
254const DEFAULT_SESSION_ESTABLISH_RETRY_DELAY: Duration = Duration::from_secs(2);
255
256const DEFAULT_SESSION_ESTABLISH_MAX_RETRIES: u32 = 3;
257
258const DEFAULT_SESSION_BALANCER_SAMPLING: Duration = Duration::from_millis(500);
259
260const DEFAULT_SESSION_BALANCER_BUFFER_DURATION: Duration = Duration::from_secs(5);
261
262fn default_session_max_sessions() -> u32 {
263 DEFAULT_SESSION_MAX_SESSIONS
264}
265
266fn default_session_balancer_buffer_duration() -> Duration {
267 DEFAULT_SESSION_BALANCER_BUFFER_DURATION
268}
269
270fn default_session_establish_max_retries() -> u32 {
271 DEFAULT_SESSION_ESTABLISH_MAX_RETRIES
272}
273
274fn default_session_idle_timeout() -> Duration {
275 DEFAULT_SESSION_IDLE_TIMEOUT
276}
277
278fn default_session_establish_retry_delay() -> Duration {
279 DEFAULT_SESSION_ESTABLISH_RETRY_DELAY
280}
281
282fn default_session_balancer_sampling() -> Duration {
283 DEFAULT_SESSION_BALANCER_SAMPLING
284}
285
286fn validate_session_idle_timeout(value: &Duration) -> Result<(), ValidationError> {
287 if SESSION_IDLE_MIN_TIMEOUT <= *value {
288 Ok(())
289 } else {
290 Err(ValidationError::new("session idle timeout is too low"))
291 }
292}
293
294fn validate_balancer_sampling(value: &Duration) -> Result<(), ValidationError> {
295 if MIN_BALANCER_SAMPLING_INTERVAL <= *value {
296 Ok(())
297 } else {
298 Err(ValidationError::new("balancer sampling interval is too low"))
299 }
300}
301
302fn validate_balancer_buffer_duration(value: &Duration) -> Result<(), ValidationError> {
303 if MIN_SURB_BUFFER_DURATION <= *value {
304 Ok(())
305 } else {
306 Err(ValidationError::new("minmum SURB buffer duration is too low"))
307 }
308}
309
310#[derive(Clone, Copy, Debug, PartialEq, Eq, Validate, smart_default::SmartDefault)]
312#[cfg_attr(
313 feature = "serde",
314 derive(serde::Serialize, serde::Deserialize),
315 serde(deny_unknown_fields)
316)]
317pub struct SessionGlobalConfig {
318 #[validate(custom(function = "validate_session_idle_timeout"))]
322 #[default(default_session_idle_timeout())]
323 #[cfg_attr(
324 feature = "serde",
325 serde(default = "default_session_idle_timeout", with = "humantime_serde")
326 )]
327 pub idle_timeout: Duration,
328
329 #[default(default_session_max_sessions())]
335 #[cfg_attr(feature = "serde", serde(default = "default_session_max_sessions"))]
336 #[validate(range(min = 1))]
337 pub maximum_sessions: u32,
338
339 #[validate(range(min = 0, max = 20))]
344 #[default(default_session_establish_max_retries())]
345 #[cfg_attr(feature = "serde", serde(default = "default_session_establish_max_retries"))]
346 pub establish_max_retries: u32,
347
348 #[default(default_session_establish_retry_delay())]
352 #[cfg_attr(
353 feature = "serde",
354 serde(default = "default_session_establish_retry_delay", with = "humantime_serde")
355 )]
356 pub establish_retry_timeout: Duration,
357
358 #[validate(custom(function = "validate_balancer_sampling"))]
362 #[default(default_session_balancer_sampling())]
363 #[cfg_attr(
364 feature = "serde",
365 serde(default = "default_session_balancer_sampling", with = "humantime_serde")
366 )]
367 pub balancer_sampling_interval: Duration,
368
369 #[validate(custom(function = "validate_balancer_buffer_duration"))]
377 #[default(default_session_balancer_buffer_duration())]
378 #[cfg_attr(
379 feature = "serde",
380 serde(default = "default_session_balancer_buffer_duration", with = "humantime_serde")
381 )]
382 pub balancer_minimum_surb_buffer_duration: Duration,
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388
389 #[test]
390 fn test_valid_domains_for_looks_like_a_domain() {
391 assert!(looks_like_domain("localhost"));
392 assert!(looks_like_domain("hoprnet.org"));
393 assert!(looks_like_domain("hub.hoprnet.org"));
394 }
395
396 #[test]
397 fn test_valid_domains_for_does_not_look_like_a_domain() {
398 assert!(!looks_like_domain(".org"));
399 assert!(!looks_like_domain("-hoprnet-.org"));
400 }
401
402 #[test]
403 fn test_valid_domains_should_be_reachable() {
404 assert!(!is_reachable_domain("google.com"));
405 }
406
407 #[test]
408 fn test_verify_valid_ip4_addresses() {
409 assert!(validate_ipv4_address("1.1.1.1").is_ok());
410 assert!(validate_ipv4_address("1.255.1.1").is_ok());
411 assert!(validate_ipv4_address("187.1.1.255").is_ok());
412 assert!(validate_ipv4_address("127.0.0.1").is_ok());
413 }
414
415 #[test]
416 fn test_verify_invalid_ip4_addresses() {
417 assert!(validate_ipv4_address("1.256.1.1").is_err());
418 assert!(validate_ipv4_address("-1.1.1.255").is_err());
419 assert!(validate_ipv4_address("127.0.0.256").is_err());
420 assert!(validate_ipv4_address("1").is_err());
421 assert!(validate_ipv4_address("1.1").is_err());
422 assert!(validate_ipv4_address("1.1.1").is_err());
423 assert!(validate_ipv4_address("1.1.1.1.1").is_err());
424 }
425
426 #[test]
427 fn test_verify_valid_dns_addresses() {
428 assert!(validate_dns_address("localhost").is_ok());
429 assert!(validate_dns_address("google.com").is_ok());
430 assert!(validate_dns_address("hub.hoprnet.org").is_ok());
431 }
432
433 #[test]
434 fn test_verify_invalid_dns_addresses() {
435 assert!(validate_dns_address("-hoprnet-.org").is_err());
436 }
437
438 #[test]
439 fn test_multiaddress_on_dappnode_default() {
440 temp_env::with_var("DAPPNODE", Some("true"), || {
441 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
442 });
443 }
444
445 #[cfg(feature = "p2p-announce-quic")]
446 #[test]
447 fn test_multiaddress_on_non_dappnode_default() {
448 temp_env::with_vars([("DAPPNODE", Some("false")), ("HOPRD_NAT", Some("false"))], || {
449 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
450 });
451 }
452
453 #[cfg(not(feature = "p2p-announce-quic"))]
454 #[test]
455 fn test_multiaddress_on_non_dappnode_default() {
456 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
457 }
458
459 #[test]
460 fn test_multiaddress_on_non_dappnode_uses_nat() {
461 temp_env::with_var("HOPRD_NAT", Some("true"), || {
462 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
463 });
464 }
465
466 #[cfg(feature = "p2p-announce-quic")]
467 #[test]
468 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
469 temp_env::with_var("HOPRD_NAT", Some("false"), || {
470 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
471 });
472 }
473
474 #[cfg(not(feature = "p2p-announce-quic"))]
475 #[test]
476 fn test_multiaddress_on_non_dappnode_not_uses_nat() {
477 temp_env::with_var("HOPRD_NAT", Some("false"), || {
478 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
479 });
480 }
481
482 #[cfg(feature = "p2p-announce-quic")]
483 #[test]
484 fn test_multiaddress_on_dappnode_not_uses_nat() {
485 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
486 assert_eq!(default_multiaddr_transport(1234), "udp/1234/quic-v1");
487 });
488 }
489
490 #[cfg(not(feature = "p2p-announce-quic"))]
491 #[test]
492 fn test_multiaddress_on_dappnode_not_uses_nat() {
493 temp_env::with_vars([("DAPPNODE", Some("true")), ("HOPRD_NAT", Some("false"))], || {
494 assert_eq!(default_multiaddr_transport(1234), "tcp/1234");
495 });
496 }
497}