1use crate::error::AppError;
2use ipnet::{IpNet, Ipv6Net};
3use std::collections::{BTreeSet, HashMap};
4use rayon::prelude::*;
5use rayon::join;
6
7pub fn parse_ip_lines(
8 text: &str,
9 country_code: &str,
10) -> Result<(Vec<IpNet>, Vec<IpNet>), AppError> {
11 let mut ipv4_list = Vec::new();
12 let mut ipv6_list = Vec::new();
13
14 for line in text.lines() {
15 if line.starts_with('#') || line.contains('*') || line.contains("reserved") {
16 continue;
17 }
18 let params: Vec<&str> = line.split('|').collect();
19 if params.len() < 7 {
20 continue;
21 }
22
23 let status = params[6].to_ascii_lowercase();
25 if status != "allocated" && status != "assigned" {
26 continue;
27 }
28
29 if params[1].eq_ignore_ascii_case(country_code) {
30 let ip_type = params[2];
31 match ip_type {
32 "ipv4" | "ipv6" => {
33 let nets = parse_ip_params(¶ms)?;
34 if ip_type == "ipv4" {
35 ipv4_list.extend(nets);
36 } else {
37 ipv6_list.extend(nets);
38 }
39 }
40 _ => {}
41 }
42 }
43 }
44
45 Ok((ipv4_list, ipv6_list))
46}
47
48fn parse_ip_params(params: &[&str]) -> Result<Vec<IpNet>, AppError> {
49 match params[2] {
50 "ipv4" => crate::ipv4_utils::parse_ipv4_range_to_cidrs(params[3], params[4]),
51 "ipv6" => parse_ipv6_range(params[3], params[4]),
52 _ => Ok(vec![]),
53 }
54}
55
56fn parse_ipv6_range(start_str: &str, value_str: &str) -> Result<Vec<IpNet>, AppError> {
57 let cidr = format!("{}/{}", start_str, value_str);
58 let net = cidr
59 .parse::<Ipv6Net>()
60 .map_err(|e| AppError::ParseError(format!("Ipv6Net parse error: {e}")))?;
61 Ok(vec![IpNet::V6(net)])
62}
63
64pub fn parse_all_country_codes(
65 rir_texts: &[String],
66) -> Result<HashMap<String, (Vec<IpNet>, Vec<IpNet>)>, AppError> {
67 let partials: Vec<Result<HashMap<String, (BTreeSet<IpNet>, BTreeSet<IpNet>)>, AppError>> =
69 rir_texts
70 .par_iter()
71 .map(|text| parse_one_rir_text_to_sets(text))
72 .collect();
73
74 let mut country_sets: HashMap<String, (BTreeSet<IpNet>, BTreeSet<IpNet>)> = HashMap::new();
75 for res in partials {
76 let map = res?;
77 for (cc, (v4s, v6s)) in map.into_iter() {
78 let entry = country_sets
79 .entry(cc)
80 .or_insert((BTreeSet::new(), BTreeSet::new()));
81 entry.0.extend(v4s);
82 entry.1.extend(v6s);
83 }
84 }
85
86 let aggregated: Vec<(String, (Vec<IpNet>, Vec<IpNet>))> = country_sets
88 .into_iter()
89 .collect::<Vec<_>>()
90 .into_par_iter()
91 .map(|(cc, (v4set, v6set))| {
92 let v4_vec = v4set.iter().copied().collect::<Vec<_>>();
93 let v6_vec = v6set.iter().copied().collect::<Vec<_>>();
94
95 let (agg_v4, agg_v6) = join(
96 || IpNet::aggregate(&v4_vec),
97 || IpNet::aggregate(&v6_vec),
98 );
99
100 (cc, (agg_v4, agg_v6))
101 })
102 .collect();
103
104 let mut country_map: HashMap<String, (Vec<IpNet>, Vec<IpNet>)> = HashMap::new();
105 for (cc, pair) in aggregated {
106 country_map.insert(cc, pair);
107 }
108 Ok(country_map)
109}
110
111fn parse_one_rir_text_to_sets(
113 text: &str,
114) -> Result<HashMap<String, (BTreeSet<IpNet>, BTreeSet<IpNet>)>, AppError> {
115 let mut country_sets: HashMap<String, (BTreeSet<IpNet>, BTreeSet<IpNet>)> = HashMap::new();
116
117 for line in text.lines() {
118 if line.starts_with('#') || line.contains('*') || line.contains("reserved") {
119 continue;
120 }
121 let params: Vec<&str> = line.split('|').collect();
122 if params.len() < 7 {
123 continue;
124 }
125
126 let status = params[6].to_ascii_lowercase();
127 if status != "allocated" && status != "assigned" {
128 continue;
129 }
130
131 let country_code = params[1].to_uppercase();
132 match params[2] {
133 "ipv4" | "ipv6" => {
134 let nets = parse_ip_params(¶ms)?;
135 let entry = country_sets
136 .entry(country_code)
137 .or_insert((BTreeSet::new(), BTreeSet::new()));
138 if params[2] == "ipv4" {
139 for n in nets { entry.0.insert(n); }
140 } else {
141 for n in nets { entry.1.insert(n); }
142 }
143 }
144 _ => {}
145 }
146 }
147
148 Ok(country_sets)
149}