use crate::error::NetworkInterfacesError;
use crate::interface::Interface;
use crate::parser::Parser;
use crate::helper::sort::natural;
use std::collections::HashMap;
use std::fmt;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[derive(Debug)]
pub struct NetworkInterfaces {
interfaces: HashMap<String, Interface>,
path: Option<PathBuf>,
last_modified: Option<SystemTime>,
comments: Vec<String>,
sources: Vec<String>,
}
impl NetworkInterfaces {
fn new(
interfaces: HashMap<String, Interface>,
comments: Vec<String>,
sources: Vec<String>,
path: Option<PathBuf>,
last_modified: Option<SystemTime>,
) -> Self {
NetworkInterfaces {
interfaces,
comments,
sources,
path,
last_modified,
}
}
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self, NetworkInterfacesError> {
let path_buf = path.as_ref().to_path_buf();
let metadata = fs::metadata(&path_buf)?;
let last_modified = metadata.modified()?;
let content = fs::read_to_string(&path_buf)?;
let parser = Parser::new();
let (interfaces, comments, sources) = parser.parse(&content)?;
Ok(NetworkInterfaces::new(
interfaces,
comments,
sources,
Some(path_buf),
Some(last_modified),
))
}
pub fn get_interface(&self, name: &str) -> Option<&Interface> {
self.interfaces.get(name)
}
pub fn get_interface_mut(&mut self, name: &str) -> Option<&mut Interface> {
self.interfaces.get_mut(name)
}
pub fn add_interface(&mut self, iface: Interface) {
self.interfaces.insert(iface.name.clone(), iface);
}
pub fn delete_interface(&mut self, name: &str) {
self.interfaces.remove(name);
}
pub fn len(&self) -> usize {
self.interfaces.len()
}
pub fn is_empty(&self) -> bool {
self.interfaces.is_empty()
}
pub fn next_unused_vlan_in_range(&self, start: u16, end: u16) -> Option<u16> {
for vlan_id in start..=end {
let vlan_name = format!("vlan{}", vlan_id);
if !self.interfaces.contains_key(&vlan_name) {
return Some(vlan_id);
}
}
None }
pub fn get_existing_vni_vlan(&self, vni_id: u32) -> Option<u16> {
let vni_name = format!("vni{}", vni_id);
let interface = self.interfaces.get(&vni_name)?;
interface.get_option("bridge-access").and_then(|v| v.parse().ok())
}
pub fn get_bridge_interfaces(&self) -> Vec<String> {
self.interfaces
.iter()
.filter_map(|(name, iface)| {
iface.get_option("bridge-access").map(|_| name.clone())
})
.collect()
}
pub fn save(&mut self) -> Result<(), NetworkInterfacesError> {
let path = match &self.path {
Some(p) => p.clone(),
None => {
return Err(NetworkInterfacesError::Other(
"No file path specified".to_string(),
))
}
};
let metadata = fs::metadata(&path)?;
let current_modified = metadata.modified()?;
if let Some(last_modified) = self.last_modified {
if current_modified > last_modified {
return Err(NetworkInterfacesError::FileModified);
}
}
let mut file = fs::File::create(&path)?;
write!(file, "{}", self)?;
self.last_modified = Some(SystemTime::now());
Ok(())
}
pub fn reload(&mut self) -> Result<(), NetworkInterfacesError> {
let path = match &self.path {
Some(p) => p.clone(),
None => {
return Err(NetworkInterfacesError::Other(
"No file path specified".to_string(),
))
}
};
let reloaded = NetworkInterfaces::load(path)?;
self.interfaces = reloaded.interfaces;
self.comments = reloaded.comments;
self.sources = reloaded.sources;
self.last_modified = reloaded.last_modified;
Ok(())
}
}
impl fmt::Display for NetworkInterfaces {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for comment in &self.comments {
writeln!(f, "{}", comment)?;
}
for source in &self.sources {
writeln!(f, "{}", source)?;
}
let mut interfaces: Vec<&Interface> = self.interfaces.values().collect();
interfaces.sort_by(|a, b| natural(&a.name, &b.name));
for iface in interfaces {
writeln!(f)?;
write!(f, "{}", iface)?;
}
Ok(())
}
}
impl NetworkInterfaces {
pub fn iter(&self) -> impl Iterator<Item = (&String, &Interface)> {
self.interfaces.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_next_unused_vlan_in_range() {
let mut network_interfaces = NetworkInterfaces {
interfaces: HashMap::new(),
path: None,
last_modified: None,
comments: Vec::new(),
sources: Vec::new(),
};
network_interfaces.add_interface(Interface::builder("vlan1000").build());
network_interfaces.add_interface(Interface::builder("vlan1001").build());
network_interfaces.add_interface(Interface::builder("vlan1003").build());
let next_vlan_id = network_interfaces.next_unused_vlan_in_range(1000, 1999);
assert_eq!(next_vlan_id, Some(1002));
network_interfaces.add_interface(Interface::builder("vlan1002").build());
let next_vlan_id = network_interfaces.next_unused_vlan_in_range(1000, 1003);
assert_eq!(next_vlan_id, None);
let next_vlan_id = network_interfaces.next_unused_vlan_in_range(2000, 2005);
assert_eq!(next_vlan_id, Some(2000));
}
#[test]
fn test_get_existing_vni_vlan() {
let mut network_interfaces = NetworkInterfaces {
interfaces: HashMap::new(),
path: None,
last_modified: None,
comments: Vec::new(),
sources: Vec::new(),
};
network_interfaces.add_interface(
Interface::builder("vni123456")
.with_auto(true)
.with_option("bridge-access", "1002")
.build(),
);
assert_eq!(network_interfaces.get_existing_vni_vlan(123456), Some(1002));
network_interfaces.add_interface(Interface::builder("vni987654").with_auto(true).build());
assert_eq!(network_interfaces.get_existing_vni_vlan(987654), None);
assert_eq!(network_interfaces.get_existing_vni_vlan(666), None);
}
#[test]
fn test_get_bridge_interfaces() {
let mut network_interfaces = NetworkInterfaces {
interfaces: HashMap::new(),
path: None,
last_modified: None,
comments: Vec::new(),
sources: Vec::new(),
};
network_interfaces.add_interface(
Interface::builder("vni1234")
.with_option("bridge-access", "1000")
.build(),
);
network_interfaces.add_interface(
Interface::builder("swp2")
.with_option("bridge-access", "1001")
.build(),
);
network_interfaces.add_interface(Interface::builder("swp1").build());
let mut bridge_interfaces = network_interfaces.get_bridge_interfaces();
bridge_interfaces.sort();
assert_eq!(bridge_interfaces, vec!["swp2", "vni1234"]);
}
}