diff --git a/src/console/clients/checker/checks/udp.rs b/src/console/clients/checker/checks/udp.rs index 3ba26ceda..36ecb5370 100644 --- a/src/console/clients/checker/checks/udp.rs +++ b/src/console/clients/checker/checks/udp.rs @@ -4,6 +4,7 @@ use std::time::Duration; use aquatic_udp_protocol::TransactionId; use hex_literal::hex; use serde::Serialize; +use url::Url; use crate::console::clients::udp::checker::Client; use crate::console::clients::udp::Error; @@ -23,20 +24,22 @@ pub enum Check { } #[allow(clippy::missing_panics_doc)] -pub async fn run(udp_trackers: Vec, timeout: Duration) -> Vec> { +pub async fn run(udp_trackers: Vec, timeout: Duration) -> Vec> { let mut results = Vec::default(); tracing::debug!("UDP trackers ..."); let info_hash = aquatic_udp_protocol::InfoHash(hex!("9c38422213e30bff212b30c360d26f9a02136422")); // # DevSkim: ignore DS173237 - for remote_addr in udp_trackers { + for remote_url in udp_trackers { + let remote_addr = resolve_socket_addr(&remote_url); + let mut checks = Checks { remote_addr, results: Vec::default(), }; - tracing::debug!("UDP tracker: {:?}", remote_addr); + tracing::debug!("UDP tracker: {:?}", remote_url); // Setup let client = match Client::new(remote_addr, timeout).await { @@ -95,3 +98,33 @@ pub async fn run(udp_trackers: Vec, timeout: Duration) -> Vec SocketAddr { + let socket_addr = url.socket_addrs(|| None).unwrap(); + *socket_addr.first().unwrap() +} + +#[cfg(test)] +mod tests { + use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + + use url::Url; + + use crate::console::clients::checker::checks::udp::resolve_socket_addr; + + #[test] + fn it_should_resolve_the_socket_address_for_udp_scheme_urls_containing_a_domain() { + assert_eq!( + resolve_socket_addr(&Url::parse("udp://localhost:8080").unwrap()), + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080) + ); + } + + #[test] + fn it_should_resolve_the_socket_address_for_udp_scheme_urls_containing_an_ip() { + assert_eq!( + resolve_socket_addr(&Url::parse("udp://127.0.0.1:8080").unwrap()), + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080) + ); + } +} diff --git a/src/console/clients/checker/config.rs b/src/console/clients/checker/config.rs index 6e44d889b..78c5926b8 100644 --- a/src/console/clients/checker/config.rs +++ b/src/console/clients/checker/config.rs @@ -1,6 +1,5 @@ use std::error::Error; use std::fmt; -use std::net::SocketAddr; use reqwest::Url as ServiceUrl; use serde::Deserialize; @@ -31,7 +30,7 @@ struct PlainConfiguration { /// Validated configuration pub struct Configuration { - pub udp_trackers: Vec, + pub udp_trackers: Vec, pub http_trackers: Vec, pub health_checks: Vec, } @@ -62,7 +61,8 @@ impl TryFrom for Configuration { let udp_trackers = plain_config .udp_trackers .into_iter() - .map(|s| s.parse::().map_err(ConfigurationError::InvalidUdpAddress)) + .map(|s| if s.starts_with("udp://") { s } else { format!("udp://{s}") }) + .map(|s| s.parse::().map_err(ConfigurationError::InvalidUrl)) .collect::, _>>()?; let http_trackers = plain_config @@ -87,68 +87,161 @@ impl TryFrom for Configuration { #[cfg(test)] mod tests { - use std::net::{IpAddr, Ipv4Addr}; - use super::*; #[test] fn configuration_should_be_build_from_plain_serializable_configuration() { let dto = PlainConfiguration { - udp_trackers: vec!["127.0.0.1:8080".to_string()], + udp_trackers: vec!["udp://127.0.0.1:8080".to_string()], http_trackers: vec!["http://127.0.0.1:8080".to_string()], health_checks: vec!["http://127.0.0.1:8080/health".to_string()], }; let config = Configuration::try_from(dto).expect("A valid configuration"); - assert_eq!( - config.udp_trackers, - vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080)] - ); + assert_eq!(config.udp_trackers, vec![ServiceUrl::parse("udp://127.0.0.1:8080").unwrap()]); + assert_eq!( config.http_trackers, vec![ServiceUrl::parse("http://127.0.0.1:8080").unwrap()] ); + assert_eq!( config.health_checks, vec![ServiceUrl::parse("http://127.0.0.1:8080/health").unwrap()] ); } - mod building_configuration_from_plan_configuration { - use crate::console::clients::checker::config::{Configuration, PlainConfiguration}; + mod building_configuration_from_plain_configuration_for { + + mod udp_trackers { + use crate::console::clients::checker::config::{Configuration, PlainConfiguration, ServiceUrl}; + + /* The plain configuration should allow UDP URLs with: + + - IP or domain. + - With or without scheme. + - With or without `announce` suffix. + - With or without `/` at the end of the authority section (with empty path). + + For example: + + 127.0.0.1:6969 + 127.0.0.1:6969/ + 127.0.0.1:6969/announce + + localhost:6969 + localhost:6969/ + localhost:6969/announce + + udp://127.0.0.1:6969 + udp://127.0.0.1:6969/ + udp://127.0.0.1:6969/announce + + udp://localhost:6969 + udp://localhost:6969/ + udp://localhost:6969/announce + + */ - #[test] - fn it_should_fail_when_a_tracker_udp_address_is_invalid() { - let plain_config = PlainConfiguration { - udp_trackers: vec!["invalid_address".to_string()], - http_trackers: vec![], - health_checks: vec![], - }; + #[test] + fn it_should_fail_when_a_tracker_udp_url_is_invalid() { + let plain_config = PlainConfiguration { + udp_trackers: vec!["invalid URL".to_string()], + http_trackers: vec![], + health_checks: vec![], + }; - assert!(Configuration::try_from(plain_config).is_err()); + assert!(Configuration::try_from(plain_config).is_err()); + } + + #[test] + fn it_should_add_the_udp_scheme_to_the_udp_url_when_it_is_missing() { + let plain_config = PlainConfiguration { + udp_trackers: vec!["127.0.0.1:6969".to_string()], + http_trackers: vec![], + health_checks: vec![], + }; + + let config = Configuration::try_from(plain_config).expect("Invalid plain configuration"); + + assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969".parse::().unwrap()); + } + + #[test] + fn it_should_allow_using_domains() { + let plain_config = PlainConfiguration { + udp_trackers: vec!["udp://localhost:6969".to_string()], + http_trackers: vec![], + health_checks: vec![], + }; + + let config = Configuration::try_from(plain_config).expect("Invalid plain configuration"); + + assert_eq!(config.udp_trackers[0], "udp://localhost:6969".parse::().unwrap()); + } + + #[test] + fn it_should_allow_the_url_to_have_an_empty_path() { + let plain_config = PlainConfiguration { + udp_trackers: vec!["127.0.0.1:6969/".to_string()], + http_trackers: vec![], + health_checks: vec![], + }; + + let config = Configuration::try_from(plain_config).expect("Invalid plain configuration"); + + assert_eq!(config.udp_trackers[0], "udp://127.0.0.1:6969/".parse::().unwrap()); + } + + #[test] + fn it_should_allow_the_url_to_contain_a_path() { + // This is the common format for UDP tracker URLs: + // udp://domain.com:6969/announce + + let plain_config = PlainConfiguration { + udp_trackers: vec!["127.0.0.1:6969/announce".to_string()], + http_trackers: vec![], + health_checks: vec![], + }; + + let config = Configuration::try_from(plain_config).expect("Invalid plain configuration"); + + assert_eq!( + config.udp_trackers[0], + "udp://127.0.0.1:6969/announce".parse::().unwrap() + ); + } } - #[test] - fn it_should_fail_when_a_tracker_http_address_is_invalid() { - let plain_config = PlainConfiguration { - udp_trackers: vec![], - http_trackers: vec!["not_a_url".to_string()], - health_checks: vec![], - }; + mod http_trackers { + use crate::console::clients::checker::config::{Configuration, PlainConfiguration}; + + #[test] + fn it_should_fail_when_a_tracker_http_url_is_invalid() { + let plain_config = PlainConfiguration { + udp_trackers: vec![], + http_trackers: vec!["invalid URL".to_string()], + health_checks: vec![], + }; - assert!(Configuration::try_from(plain_config).is_err()); + assert!(Configuration::try_from(plain_config).is_err()); + } } - #[test] - fn it_should_fail_when_a_health_check_http_address_is_invalid() { - let plain_config = PlainConfiguration { - udp_trackers: vec![], - http_trackers: vec![], - health_checks: vec!["not_a_url".to_string()], - }; + mod health_checks { + use crate::console::clients::checker::config::{Configuration, PlainConfiguration}; + + #[test] + fn it_should_fail_when_a_health_check_http_url_is_invalid() { + let plain_config = PlainConfiguration { + udp_trackers: vec![], + http_trackers: vec![], + health_checks: vec!["invalid URL".to_string()], + }; - assert!(Configuration::try_from(plain_config).is_err()); + assert!(Configuration::try_from(plain_config).is_err()); + } } } }