1use flutter_rust_bridge::frb;
2use std::net::{IpAddr, SocketAddr};
3use tokio::net::lookup_host;
4
5pub const DEFAULT_PORT: u16 = 13908;
6
7#[cfg_attr(feature = "frb-api", frb)]
8pub async fn resolve_to_socket_addr(s: &str) -> Result<SocketAddr, String> {
9 if let Ok(addr) = s.parse::<SocketAddr>() {
11 return Ok(addr);
12 }
13
14 if let Ok(ip) = s.parse::<IpAddr>() {
16 return Ok(SocketAddr::new(ip, DEFAULT_PORT));
17 }
18
19 if s.contains(':') {
21 let addrs = lookup_host(s).await.map_err(|e| e.to_string())?;
23 if let Some(addr) = addrs.into_iter().next() {
24 return Ok(addr);
25 }
26 } else {
27 let addrs = lookup_host(format!("{s}:{DEFAULT_PORT}"))
29 .await
30 .map_err(|e| e.to_string())?;
31 if let Some(addr) = addrs.into_iter().next() {
32 return Ok(addr);
33 }
34 }
35
36 Err(format!("Invalid socket address: {s}"))
37}
38
39#[cfg(test)]
40mod tests {
41 use super::*;
42 use std::net::{Ipv4Addr, Ipv6Addr};
43
44 #[tokio::test]
45 async fn test_resolve_ipv4_with_port() {
46 let result = resolve_to_socket_addr("192.168.1.1:8080").await.unwrap();
47 assert_eq!(
48 result,
49 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8080)
50 );
51 }
52
53 #[tokio::test]
54 async fn test_resolve_ipv4_without_port() {
55 let result = resolve_to_socket_addr("192.168.1.1").await.unwrap();
56 assert_eq!(
57 result,
58 SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), DEFAULT_PORT)
59 );
60 }
61
62 #[tokio::test]
63 async fn test_resolve_ipv6_with_port() {
64 let result = resolve_to_socket_addr("[::1]:8080").await.unwrap();
65 assert_eq!(
66 result,
67 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), 8080)
68 );
69 }
70
71 #[tokio::test]
72 async fn test_resolve_ipv6_without_port() {
73 let result = resolve_to_socket_addr("::1").await.unwrap();
74 assert_eq!(
75 result,
76 SocketAddr::new(IpAddr::V6(Ipv6Addr::LOCALHOST), DEFAULT_PORT)
77 );
78 }
79
80 #[tokio::test]
81 async fn test_resolve_localhost_with_port() {
82 let result = resolve_to_socket_addr("localhost:9000").await.unwrap();
83 assert_eq!(result.port(), 9000);
84 assert!(result.ip().is_loopback());
86 }
87
88 #[tokio::test]
89 async fn test_resolve_localhost_without_port() {
90 let result = resolve_to_socket_addr("localhost").await.unwrap();
91 assert_eq!(result.port(), DEFAULT_PORT);
92 assert!(result.ip().is_loopback());
93 }
94
95 #[tokio::test]
96 async fn test_resolve_domain_with_port() {
97 let result = resolve_to_socket_addr("google.com:443").await;
99 assert!(result.is_ok());
100 if let Ok(addr) = result {
101 assert_eq!(addr.port(), 443);
102 }
103 }
104
105 #[tokio::test]
106 async fn test_resolve_domain_without_port() {
107 let result = resolve_to_socket_addr("google.com").await;
109 assert!(result.is_ok());
110 if let Ok(addr) = result {
111 assert_eq!(addr.port(), DEFAULT_PORT);
112 }
113 }
114
115 #[tokio::test]
116 async fn test_resolve_invalid_address() {
117 let result = resolve_to_socket_addr("invalid..domain..name").await;
118 assert!(result.is_err());
119 }
120
121 #[tokio::test]
122 async fn test_resolve_empty_string() {
123 let result = resolve_to_socket_addr("").await;
124 assert!(result.is_err());
125 }
126
127 #[tokio::test]
128 async fn test_resolve_port_only() {
129 let result = resolve_to_socket_addr(":8080").await;
130 assert!(result.is_err());
131 }
132
133 #[tokio::test]
134 async fn test_resolve_invalid_port() {
135 let result = resolve_to_socket_addr("192.168.1.1:99999").await;
136 assert!(result.is_err());
137 }
138}