Skip to main content

fire_scope/
parse.rs

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        // status 列を厳密に判定(allocated / assigned のみ採用)
24        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(&params)?;
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    // RIRファイル単位のパースをrayonで並列化し、結果を順次マージ
68    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    // 集約してVecへ変換(最小CIDR化)— 国ごとに並列実行
87    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
111// 単一RIRテキストをパースし、国コード→(v4セット, v6セット)の部分結果を返す
112fn 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(&params)?;
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}