zoe_client/
util.rs

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    // Try to parse as a complete SocketAddr first (IP:port)
10    if let Ok(addr) = s.parse::<SocketAddr>() {
11        return Ok(addr);
12    }
13
14    // Try to parse as just an IP address
15    if let Ok(ip) = s.parse::<IpAddr>() {
16        return Ok(SocketAddr::new(ip, DEFAULT_PORT));
17    }
18
19    // Handle DNS resolution - check if it already has a port
20    if s.contains(':') {
21        // Assume it's hostname:port format, let ToSocketAddrs handle it
22        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        // It's just a hostname, add default port
28        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        // The IP could be either 127.0.0.1 or ::1 depending on system configuration
85        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        // Using a well-known domain that should resolve
98        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        // Using a well-known domain that should resolve
108        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}