/
vol4oks
/
LANIPTracker
Обзор
Документация
Войти
/
vol4oks
/
LANIPTracker
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/subnet.rs
453 строки
13 KB
Vol4ok
version = "0.1.1"
03 июл 2025, 21:16
03 июл 2025, 21:16
949338a
Код
Авторство
О чём код?
/* TODO: удаление типов адресов TODO: поиск по типу адреса TODO: разбить на подфайлы TODO: добавить проверку на длинну комментария */ use std::{ collections::HashMap, fmt::Display, io::{self, Write}, net::Ipv4Addr, str::FromStr, }; use ipnetwork::{IpNetworkError, Ipv4Network}; use serde::{Deserialize, Serialize}; // типы адресов(ПК, свитч, роутер) pub type TypeIPAddr = String; pub type IdTypeIPAddr = usize; pub type SaveTypeIPAddr = HashMap<IdTypeIPAddr, TypeIPAddr>; pub type IdIPAddr = usize; pub type StaticIpId = HashMap<IdIPAddr, Ipv4Addr>; pub type LocalNetwork = HashMap<IdIPAddr, IP>; pub type ResultSerch = Vec<(IdIPAddr, Ipv4Addr, IP, TypeIPAddr)>; pub type ResultSerchType = Vec<(IdTypeIPAddr, TypeIPAddr)>; const FILE_IP_ID: &str = "_ip_id.json"; const FILE_NETWORK: &str = "_network.json"; const FILE_TYPE_IP: &str = "_type_ip.json"; const BASE_DIR: &str = "DB"; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] pub enum Status { #[default] Free, Used, Reserved, } impl Status { pub fn get_all() -> Vec<Self> { vec![Status::Free, Status::Used, Status::Reserved] } } impl Display for Status { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Status::Free => write!(f, "Free"), Status::Used => write!(f, "Used"), Status::Reserved => write!(f, "Reserved"), } } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)] pub struct IP { pub id_type_ip: IdTypeIPAddr, pub status: Status, pub comment: String, } impl Display for IP { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}, {}, {}", self.id_type_ip, self.status, self.comment) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] struct StaticData { pub start_ip: Ipv4Addr, pub mask: u8, pub static_ips: StaticIpId, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct Subnet { data: StaticData, network: LocalNetwork, base_dir: String, hashmap_type_ip: SaveTypeIPAddr, } impl Subnet { pub fn new(start_ip: Ipv4Addr, mask: u8) -> Result<Self, IpNetworkError> { // TODO: добавить проверку на валидность адреса и маски let vec_addr = Ipv4Network::new(start_ip, mask)?; let (static_ips, network) = generate_addr(vec_addr); Ok(Self { data: StaticData { start_ip: start_ip, mask: mask, static_ips: static_ips, }, network: network, base_dir: BASE_DIR.to_string(), hashmap_type_ip: generate_type_ip(), }) } pub fn from_str(ip: &str) -> Result<Self, IpNetworkError> { let vec_addr = Ipv4Network::from_str(ip)?; let (static_ips, network) = generate_addr(vec_addr); Ok(Self { data: StaticData { start_ip: vec_addr.ip(), mask: vec_addr.prefix(), static_ips: static_ips, }, network: network, base_dir: BASE_DIR.to_string(), hashmap_type_ip: generate_type_ip(), }) } pub fn set_base_dir(mut self, path: String) -> Self { self.base_dir = path; self } pub fn save(&self) -> Result<(), io::Error> { let file_path = std::path::Path::new(&self.base_dir); if !file_path.exists() { std::fs::create_dir_all(file_path)?; } self.save_static()?; self.save_network()?; self.save_type_ip()?; Ok(()) } pub fn save_update(&self) -> Result<(), io::Error> { //FIXME: добавить проверку на изменение статуса и разделить на 2 функции self.save_network()?; self.save_type_ip()?; Ok(()) } fn save_static(&self) -> Result<(), io::Error> { let json = serde_json::to_string(&self.data)?; let file_path = std::path::Path::new(&self.base_dir).join(FILE_IP_ID); let mut file = std::fs::File::create(file_path)?; file.write_all(json.as_bytes())?; Ok(()) } fn save_network(&self) -> Result<(), io::Error> { // TODO: записать только измененные поля в файл //let json = serde_json::to_string(&self.get_not_free())?; let json = serde_json::to_string(&self.network)?; let file_path = std::path::Path::new(&self.base_dir).join(FILE_NETWORK); let mut file = std::fs::File::create(file_path)?; file.write_all(json.as_bytes())?; tracing::info!("save network"); Ok(()) } fn save_type_ip(&self) -> Result<(), io::Error> { let json = serde_json::to_string(&self.hashmap_type_ip)?; let file_path = std::path::Path::new(&self.base_dir).join(FILE_TYPE_IP); let mut file = std::fs::File::create(file_path)?; file.write_all(json.as_bytes())?; tracing::info!("save type ip"); Ok(()) } #[allow(dead_code)] fn get_not_free(&mut self) -> LocalNetwork { todo!() } pub fn read_from_file(path: &str) -> Result<Self, io::Error> { let file_path = std::path::Path::new(path); if !file_path.exists() { return Err(io::Error::new( io::ErrorKind::NotFound, format!("Dir {} not found", path), )); } let file_id = file_path.join(FILE_IP_ID); let file_network = file_path.join(FILE_NETWORK); let file_type_ip = file_path.join(FILE_TYPE_IP); let file_static = std::fs::File::open(file_id)?; let file_network = std::fs::File::open(file_network)?; let file_type_ip = std::fs::File::open(file_type_ip)?; let id_ip = serde_json::from_reader(file_static)?; let net = serde_json::from_reader(file_network)?; let type_ip = serde_json::from_reader(file_type_ip)?; Ok(Subnet { data: id_ip, network: net, base_dir: path.to_string(), hashmap_type_ip: type_ip, }) } pub fn get_ip(&self, id: IdIPAddr) -> Option<(Ipv4Addr, IP)> { self.data .static_ips .get(&id) .map(|ip| (*ip, self.network[&id].clone())) } pub fn get_all_ip(&self) -> ResultSerch { let mut sort_list = self .data .static_ips .keys() .clone() .into_iter() .collect::<Vec<_>>(); sort_list.sort(); sort_list .iter() .map(|id| { ( **id, self.data.static_ips[id], self.network[id].clone(), self.get_type_ip(&self.network[id].id_type_ip), ) }) .collect() } pub fn update_ip( &mut self, id: IdIPAddr, id_type_ip: IdTypeIPAddr, status: Status, comment: String, ) -> Result<(), std::io::Error> { self.network.insert( id, IP { id_type_ip, status, comment, }, ); self.save_update()?; Ok(()) } pub fn get_all_type_ip(&self) -> ResultSerchType { if self.hashmap_type_ip.is_empty() { return Vec::new(); } let mut sort_id = self .hashmap_type_ip .keys() .clone() .into_iter() .collect::<Vec<_>>(); sort_id.sort(); sort_id .iter() .map(|id| (**id, self.hashmap_type_ip[id].clone())) .collect() } pub fn add_type_ip(&mut self, type_ip: TypeIPAddr) -> Result<(), std::io::Error> { tracing::debug!("add type ip: {:?}", type_ip); let type_ip_vec = self.hashmap_type_ip.values().collect::<Vec<_>>(); if type_ip_vec.contains(&&type_ip){ return Err(std::io::Error::new( std::io::ErrorKind::AlreadyExists, format!("Type ip {} already exists", type_ip), )); } let mut keys = self.hashmap_type_ip.keys().collect::<Vec<_>>(); keys.sort(); self.hashmap_type_ip .insert(keys.pop().unwrap_or(&0) + 1, type_ip.clone()); self.save_type_ip()?; Ok(()) } pub fn delete_type_ip(&mut self, type_ip: IdTypeIPAddr) -> Result<(), std::io::Error> { if type_ip == 0 { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, "Can't delete default type ip", )); } if self.hashmap_type_ip.remove(&type_ip).is_some() { self.save_type_ip()?; Ok(()) } else { Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("Type ip {} not found", type_ip), )) } } pub fn get_type_ip(&self, &id: &IdTypeIPAddr) -> TypeIPAddr { if let Some(t) = self.hashmap_type_ip.get(&id) { t.clone() }else { self.hashmap_type_ip.get(&0).cloned().unwrap() } } #[allow(dead_code)] pub fn filter_by_ip(&self, start: Ipv4Addr, fin: Ipv4Addr) -> ResultSerch { todo!() } #[allow(dead_code)] pub fn filter_by_type(&self, t: TypeIPAddr) -> ResultSerch { todo!() } pub fn filter_by_status(&self, t: Status) -> ResultSerch { let mut sort_list = self .data .static_ips .keys() .clone() .into_iter() .collect::<Vec<_>>(); sort_list.sort(); sort_list .iter() .filter(|id| self.network[id].status == t) .map(|id| { ( **id, self.data.static_ips[id], self.network[id].clone(), self.hashmap_type_ip[&self.network[id].id_type_ip].clone(), ) }) .collect() } #[allow(dead_code)] pub async fn ping_ip(&self, ip: Ipv4Addr) -> bool { // TODO: https://github.com/kolapapa/surge-ping/blob/main/examples/simple.rs todo!() } #[allow(dead_code)] pub async fn test_all_ip(&mut self) { todo!() } } fn generate_addr(net: Ipv4Network) -> (StaticIpId, LocalNetwork) { let mut network = HashMap::new(); let mut static_ips = HashMap::new(); for (id, ip) in net.iter().enumerate() { if ip == net.network() || ip == net.broadcast() { continue; } static_ips.insert(id, ip); network.insert( id, IP { id_type_ip: 0, status: Status::Free, comment: "".to_string(), }, ); } (static_ips, network) } fn generate_type_ip() -> HashMap<IdTypeIPAddr, TypeIPAddr> { let mut hashmap_type_ip = HashMap::new(); hashmap_type_ip.insert(0, "Неизвестный".to_string()); hashmap_type_ip } #[cfg(test)] mod tests { // TODO: добавить тест на максимальный размер сети и коментарий, замерить скорость use super::*; const FILE: &str = "test_dir"; #[test] fn test_new() { let subnet = Subnet::new(Ipv4Addr::new(192, 168, 0, 0), 24).unwrap(); //println!("{:?}", subnet); assert_eq!(subnet.network.len(), 254); } #[test] fn test_from_str() { let subnet = Subnet::from_str("192.168.0.0/24").unwrap(); //println!("{:?}", subnet); assert_eq!(subnet.network.len(), 254); } #[ignore] #[test] fn test_save_to_file() { let subnet = Subnet::from_str("192.168.0.0/16") .unwrap() .set_base_dir(FILE.to_string()); //println!("{:?}", subnet); subnet.save().unwrap(); let file_path = std::path::Path::new(FILE); assert!(file_path.exists()); //std::fs::remove_file(file_path).unwrap(); } #[test] fn test_read_from_file() { let subnet = Subnet::from_str("192.168.0.0/24") .unwrap() .set_base_dir(FILE.to_string()); //println!("{:?}", subnet); subnet.save().unwrap(); let file_path = std::path::Path::new(FILE); assert!(file_path.exists()); let subnet_new = Subnet::read_from_file(FILE).unwrap(); std::fs::remove_dir_all(file_path).unwrap(); assert_eq!(subnet_new, subnet); } #[tokio::test] async fn test_ping_ip() { let subnet = Subnet::from_str("192.168.1.0/24").unwrap(); subnet .ping_ip(Ipv4Addr::from_str("192.168.1.1").unwrap()) .await; } }