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