1use std::net::Ipv6Addr;
4use std::path::PathBuf;
5use std::str::FromStr;
6use tokio::fs::File as AsyncFile;
7use tokio::io::{
8 AsyncBufReadExt, AsyncWriteExt, BufReader as AsyncBufReader, BufWriter as AsyncBufWriter,
9};
10
11pub async fn load_ipv6_addresses_from_file(file: &PathBuf) -> Result<Vec<[u8; 16]>, String> {
24 let file = AsyncFile::open(file)
25 .await
26 .map_err(|e| format!("Failed to open input file: {}", e))?;
27
28 let reader = AsyncBufReader::new(file);
29 let mut lines = reader.lines();
30 let mut addresses = Vec::new();
31 let mut line_num = 0;
32
33 while let Some(line) = lines
34 .next_line()
35 .await
36 .map_err(|e| format!("Failed to read line {}: {}", line_num + 1, e))?
37 {
38 line_num += 1;
39 let line = line.trim();
40
41 if line.is_empty() || line.starts_with('#') {
42 continue;
43 }
44
45 let ip = Ipv6Addr::from_str(line)
46 .map_err(|e| format!("Failed to parse IPv6 address on line {}: {}", line_num, e))?;
47
48 addresses.push(ip.octets());
49 }
50
51 if addresses.is_empty() {
52 return Err("No valid IPv6 addresses found in input file".to_string());
53 }
54
55 Ok(addresses)
56}
57
58pub async fn write_ipv6_addresses_to_file(
68 addresses: &[[u8; 16]],
69 file_path: &PathBuf,
70) -> Result<(), String> {
71 let file = AsyncFile::create(file_path)
72 .await
73 .map_err(|e| format!("Failed to create output file: {}", e))?;
74
75 let mut writer = AsyncBufWriter::new(file);
76
77 for address in addresses {
78 let ipv6 = Ipv6Addr::from(*address);
79 writer
80 .write_all(format!("{}\n", ipv6).as_bytes())
81 .await
82 .map_err(|e| format!("Failed to write address: {}", e))?;
83 }
84
85 writer
86 .flush()
87 .await
88 .map_err(|e| format!("Failed to flush output: {}", e))?;
89
90 Ok(())
91}
92
93#[cfg(test)]
94mod tests {
95 use std::io::Write;
96
97 use super::*;
98 use tempfile::NamedTempFile;
99 use tokio::io::AsyncWriteExt;
100
101 #[tokio::test]
102 async fn test_load_ipv6_addresses() {
103 let mut temp_file = NamedTempFile::new().unwrap();
104 let content = "2001:db8::1\n::1\n# This is a comment\n\nfe80::1\n";
105 temp_file.write_all(content.as_bytes()).unwrap();
106 temp_file.flush().unwrap();
107
108 let path = temp_file.path().to_path_buf();
109 let addresses = load_ipv6_addresses_from_file(&path).await.unwrap();
110
111 assert_eq!(addresses.len(), 3);
112
113 let expected = vec![
115 Ipv6Addr::from_str("2001:db8::1").unwrap().octets(),
116 Ipv6Addr::from_str("::1").unwrap().octets(),
117 Ipv6Addr::from_str("fe80::1").unwrap().octets(),
118 ];
119
120 assert_eq!(addresses, expected);
121 }
122
123 #[tokio::test]
124 async fn test_write_ipv6_addresses() {
125 let addresses = vec![
126 Ipv6Addr::from_str("2001:db8::1").unwrap().octets(),
127 Ipv6Addr::from_str("::1").unwrap().octets(),
128 ];
129
130 let temp_file = NamedTempFile::new().unwrap();
131 let path = temp_file.path().to_path_buf();
132
133 write_ipv6_addresses_to_file(&addresses, &path)
134 .await
135 .unwrap();
136
137 let content = std::fs::read_to_string(&path).unwrap();
139 let lines: Vec<&str> = content.trim().split('\n').collect();
140
141 assert_eq!(lines.len(), 2);
142 assert_eq!(lines[0], "2001:db8::1");
143 assert_eq!(lines[1], "::1");
144 }
145}