fire_scope/
output.rs

1use crate::common::IpFamily;
2use chrono::{Datelike, Local, Timelike};
3use ipnet::IpNet;
4use std::{
5    collections::BTreeSet,
6    error::Error,
7    fs::{self, OpenOptions},
8    io::Write,
9    path::Path,
10};
11
12/// ソート済みのIPv4/IPv6リストをファイルに書き出すモジュール。
13/// すべてファイル出力で完結している。
14pub fn write_ip_lists_to_files(
15    country_code: &str,
16    ipv4_list: &BTreeSet<IpNet>,
17    ipv6_list: &BTreeSet<IpNet>,
18    mode: &str,
19) -> Result<(), Box<dyn Error + Send + Sync>> {
20    let ipv4_file = format!("IPv4_{}.txt", country_code);
21    let ipv6_file = format!("IPv6_{}.txt", country_code);
22
23    write_single_ip_list(&ipv4_file, ipv4_list, mode)?;
24    write_single_ip_list(&ipv6_file, ipv6_list, mode)?;
25
26    Ok(())
27}
28
29/// 1つのファイルに書き込むヘルパー関数
30fn write_single_ip_list<P: AsRef<Path>>(
31    path: P,
32    nets: &BTreeSet<IpNet>,
33    mode: &str,
34) -> Result<(), Box<dyn Error + Send + Sync>> {
35    let now = Local::now();
36    let formatted_header = format!(
37        "# {}/{}/{} {}:{}\n",
38        now.year(),
39        now.month(),
40        now.day(),
41        now.hour(),
42        now.minute()
43    );
44
45    let lines: Vec<String> = nets.iter().map(|net| net.to_string()).collect();
46    let content = format!("{}{}\n", formatted_header, lines.join("\n"));
47
48    match mode {
49        "append" => {
50            let mut file = OpenOptions::new().create(true).append(true).open(&path)?;
51            file.write_all(content.as_bytes())?;
52            println!("[output] Appended IP list to: {}", path.as_ref().display());
53        }
54        _ => {
55            fs::write(&path, &content)?;
56            println!(
57                "[output] Wrote (overwrite) IP list to: {}",
58                path.as_ref().display()
59            );
60        }
61    }
62
63    Ok(())
64}
65
66/// AS番号+IPファミリ用のルートリストをファイルに書き出す関数.
67/// 「AS_XXXX_YYYY.txt」という形式でファイルを生成する.
68pub fn write_as_ip_list_to_file(
69    as_number: &str,
70    family: IpFamily,
71    ipnets: &BTreeSet<IpNet>,
72    mode: &str,
73) -> Result<(), Box<dyn Error + Send + Sync>> {
74    let now = Local::now().format("%Y-%m-%d %H:%M").to_string();
75    let file_name = format!("AS_{}_{}.txt", as_number, family.as_str());
76
77    let header = format!("# Execution Date and Time: {}\n", now);
78    let body = ipnets
79        .iter()
80        .map(IpNet::to_string)
81        .collect::<Vec<_>>()
82        .join("\n");
83    let content = format!("{}{}\n", header, body);
84
85    match mode {
86        "append" => {
87            let mut file = OpenOptions::new()
88                .create(true)
89                .append(true)
90                .open(&file_name)?;
91            file.write_all(content.as_bytes())?;
92            println!("[output] Appended IP list to: {}", file_name);
93        }
94        _ => {
95            fs::write(&file_name, content)?;
96            println!("[output] Wrote (overwrite) IP list to: {}", file_name);
97        }
98    }
99
100    Ok(())
101}
102
103/// 国コード & AS番号の重複CIDRリストをファイルに書き出すヘルパー関数.
104/// 例: overlap_JP_AS1234.txt
105pub fn write_overlap_to_file(
106    country_code: &str,
107    as_number: &str,
108    overlaps: &BTreeSet<IpNet>,
109    mode: &str,
110) -> Result<(), Box<dyn Error + Send + Sync>> {
111    if overlaps.is_empty() {
112        println!(
113            "[overlap] No overlap found for country={} and AS={}",
114            country_code, as_number
115        );
116        return Ok(());
117    }
118
119    let filename = format!("overlap_{}_{}.txt", country_code, as_number);
120    let now_str = Local::now().format("%Y-%m-%d %H:%M").to_string();
121    let header = format!(
122        "# Overlap between Country={} and AS={} at {}\n",
123        country_code, as_number, now_str
124    );
125
126    let body = overlaps
127        .iter()
128        .map(|net| net.to_string())
129        .collect::<Vec<_>>()
130        .join("\n");
131
132    let content = format!("{}\n{}\n", header, body);
133
134    match mode {
135        "append" => {
136            let mut file = OpenOptions::new()
137                .create(true)
138                .append(true)
139                .open(&filename)?;
140            file.write_all(content.as_bytes())?;
141            println!("[overlap] Appended overlaps to: {}", filename);
142        }
143        _ => {
144            fs::write(&filename, content)?;
145            println!("[overlap] Wrote overlaps to: {}", filename);
146        }
147    }
148
149    Ok(())
150}