use std::collections::HashMap;
use std::net::{Ipv4Addr, Ipv6Addr};
use uuid::Uuid;
use zvariant::Value;
use crate::api::models::ConnectionOptions;
#[derive(Debug, Clone)]
pub struct IpConfig {
pub address: String,
pub prefix: u32,
}
impl IpConfig {
#[must_use]
pub fn new(address: impl Into<String>, prefix: u32) -> Self {
Self {
address: address.into(),
prefix,
}
}
}
#[derive(Debug, Clone)]
pub struct Route {
pub dest: String,
pub prefix: u32,
pub next_hop: Option<String>,
pub metric: Option<u32>,
}
impl Route {
#[must_use]
pub fn new(dest: impl Into<String>, prefix: u32) -> Self {
Self {
dest: dest.into(),
prefix,
next_hop: None,
metric: None,
}
}
#[must_use]
pub fn next_hop(mut self, gateway: impl Into<String>) -> Self {
self.next_hop = Some(gateway.into());
self
}
#[must_use]
pub fn metric(mut self, metric: u32) -> Self {
self.metric = Some(metric);
self
}
}
pub struct ConnectionBuilder {
settings: HashMap<&'static str, HashMap<&'static str, Value<'static>>>,
}
impl ConnectionBuilder {
#[must_use]
pub fn new(connection_type: &str, id: impl Into<String>) -> Self {
let mut settings = HashMap::new();
let mut connection = HashMap::new();
connection.insert("type", Value::from(connection_type.to_string()));
connection.insert("id", Value::from(id.into()));
connection.insert("uuid", Value::from(Uuid::new_v4().to_string()));
settings.insert("connection", connection);
Self { settings }
}
#[must_use]
pub fn uuid(mut self, uuid: Uuid) -> Self {
if let Some(conn) = self.settings.get_mut("connection") {
conn.insert("uuid", Value::from(uuid.to_string()));
}
self
}
#[must_use]
pub fn interface_name(mut self, name: impl Into<String>) -> Self {
if let Some(conn) = self.settings.get_mut("connection") {
conn.insert("interface-name", Value::from(name.into()));
}
self
}
#[must_use]
pub fn autoconnect(mut self, enabled: bool) -> Self {
if let Some(conn) = self.settings.get_mut("connection") {
conn.insert("autoconnect", Value::from(enabled));
}
self
}
#[must_use]
pub fn autoconnect_priority(mut self, priority: i32) -> Self {
if let Some(conn) = self.settings.get_mut("connection") {
conn.insert("autoconnect-priority", Value::from(priority));
}
self
}
#[must_use]
pub fn autoconnect_retries(mut self, retries: i32) -> Self {
if let Some(conn) = self.settings.get_mut("connection") {
conn.insert("autoconnect-retries", Value::from(retries));
}
self
}
#[must_use]
pub fn options(mut self, opts: &ConnectionOptions) -> Self {
if let Some(conn) = self.settings.get_mut("connection") {
conn.insert("autoconnect", Value::from(opts.autoconnect));
if let Some(priority) = opts.autoconnect_priority {
conn.insert("autoconnect-priority", Value::from(priority));
}
if let Some(retries) = opts.autoconnect_retries {
conn.insert("autoconnect-retries", Value::from(retries));
}
}
self
}
#[must_use]
pub fn ipv4_auto(mut self) -> Self {
let mut ipv4 = HashMap::new();
ipv4.insert("method", Value::from("auto"));
self.settings.insert("ipv4", ipv4);
self
}
#[must_use]
pub fn ipv4_manual(mut self, addresses: Vec<IpConfig>) -> Self {
let mut ipv4 = HashMap::new();
ipv4.insert("method", Value::from("manual"));
let address_data: Vec<HashMap<String, Value<'static>>> = addresses
.into_iter()
.map(|config| {
let mut addr_dict = HashMap::new();
addr_dict.insert("address".to_string(), Value::from(config.address));
addr_dict.insert("prefix".to_string(), Value::from(config.prefix));
addr_dict
})
.collect();
ipv4.insert("address-data", Value::from(address_data));
self.settings.insert("ipv4", ipv4);
self
}
#[must_use]
pub fn ipv4_disabled(mut self) -> Self {
let mut ipv4 = HashMap::new();
ipv4.insert("method", Value::from("disabled"));
self.settings.insert("ipv4", ipv4);
self
}
#[must_use]
pub fn ipv4_link_local(mut self) -> Self {
let mut ipv4 = HashMap::new();
ipv4.insert("method", Value::from("link-local"));
self.settings.insert("ipv4", ipv4);
self
}
#[must_use]
pub fn ipv4_shared(mut self) -> Self {
let mut ipv4 = HashMap::new();
ipv4.insert("method", Value::from("shared"));
self.settings.insert("ipv4", ipv4);
self
}
#[must_use]
pub fn ipv4_dns(mut self, servers: Vec<Ipv4Addr>) -> Self {
let dns_u32: Vec<u32> = servers.into_iter().map(u32::from).collect();
if let Some(ipv4) = self.settings.get_mut("ipv4") {
ipv4.insert("dns", Value::from(dns_u32));
}
self
}
#[must_use]
pub fn ipv4_gateway(mut self, gateway: Ipv4Addr) -> Self {
if let Some(ipv4) = self.settings.get_mut("ipv4") {
ipv4.insert("gateway", Value::from(gateway.to_string()));
}
self
}
#[must_use]
pub fn ipv4_routes(mut self, routes: Vec<Route>) -> Self {
let route_data: Vec<HashMap<String, Value<'static>>> = routes
.into_iter()
.map(|route| {
let mut route_dict = HashMap::new();
route_dict.insert("dest".to_string(), Value::from(route.dest));
route_dict.insert("prefix".to_string(), Value::from(route.prefix));
if let Some(next_hop) = route.next_hop {
route_dict.insert("next-hop".to_string(), Value::from(next_hop));
}
if let Some(metric) = route.metric {
route_dict.insert("metric".to_string(), Value::from(metric));
}
route_dict
})
.collect();
if let Some(ipv4) = self.settings.get_mut("ipv4") {
ipv4.insert("route-data", Value::from(route_data));
}
self
}
#[must_use]
pub fn ipv6_auto(mut self) -> Self {
let mut ipv6 = HashMap::new();
ipv6.insert("method", Value::from("auto"));
self.settings.insert("ipv6", ipv6);
self
}
#[must_use]
pub fn ipv6_manual(mut self, addresses: Vec<IpConfig>) -> Self {
let mut ipv6 = HashMap::new();
ipv6.insert("method", Value::from("manual"));
let address_data: Vec<HashMap<String, Value<'static>>> = addresses
.into_iter()
.map(|config| {
let mut addr_dict = HashMap::new();
addr_dict.insert("address".to_string(), Value::from(config.address));
addr_dict.insert("prefix".to_string(), Value::from(config.prefix));
addr_dict
})
.collect();
ipv6.insert("address-data", Value::from(address_data));
self.settings.insert("ipv6", ipv6);
self
}
#[must_use]
pub fn ipv6_ignore(mut self) -> Self {
let mut ipv6 = HashMap::new();
ipv6.insert("method", Value::from("ignore"));
self.settings.insert("ipv6", ipv6);
self
}
#[must_use]
pub fn ipv6_link_local(mut self) -> Self {
let mut ipv6 = HashMap::new();
ipv6.insert("method", Value::from("link-local"));
self.settings.insert("ipv6", ipv6);
self
}
#[must_use]
pub fn ipv6_dns(mut self, servers: Vec<Ipv6Addr>) -> Self {
let dns_strings: Vec<String> = servers.into_iter().map(|s| s.to_string()).collect();
if let Some(ipv6) = self.settings.get_mut("ipv6") {
ipv6.insert("dns", Value::from(dns_strings));
}
self
}
#[must_use]
pub fn ipv6_gateway(mut self, gateway: Ipv6Addr) -> Self {
if let Some(ipv6) = self.settings.get_mut("ipv6") {
ipv6.insert("gateway", Value::from(gateway.to_string()));
}
self
}
#[must_use]
pub fn ipv6_routes(mut self, routes: Vec<Route>) -> Self {
let route_data: Vec<HashMap<String, Value<'static>>> = routes
.into_iter()
.map(|route| {
let mut route_dict = HashMap::new();
route_dict.insert("dest".to_string(), Value::from(route.dest));
route_dict.insert("prefix".to_string(), Value::from(route.prefix));
if let Some(next_hop) = route.next_hop {
route_dict.insert("next-hop".to_string(), Value::from(next_hop));
}
if let Some(metric) = route.metric {
route_dict.insert("metric".to_string(), Value::from(metric));
}
route_dict
})
.collect();
if let Some(ipv6) = self.settings.get_mut("ipv6") {
ipv6.insert("route-data", Value::from(route_data));
}
self
}
#[must_use]
pub fn with_section(
mut self,
name: &'static str,
section: HashMap<&'static str, Value<'static>>,
) -> Self {
self.settings.insert(name, section);
self
}
#[must_use]
pub fn update_section<F>(mut self, name: &'static str, f: F) -> Self
where
F: FnOnce(&mut HashMap<&'static str, Value<'static>>),
{
if let Some(section) = self.settings.get_mut(name) {
f(section);
}
self
}
#[must_use]
pub fn build(self) -> HashMap<&'static str, HashMap<&'static str, Value<'static>>> {
self.settings
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creates_basic_connection() {
let settings = ConnectionBuilder::new("802-11-wireless", "TestNetwork").build();
assert!(settings.contains_key("connection"));
let conn = settings.get("connection").unwrap();
assert_eq!(conn.get("type"), Some(&Value::from("802-11-wireless")));
assert_eq!(conn.get("id"), Some(&Value::from("TestNetwork")));
assert!(conn.contains_key("uuid"));
}
#[test]
fn sets_custom_uuid() {
let test_uuid = Uuid::new_v4();
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.uuid(test_uuid)
.build();
let conn = settings.get("connection").unwrap();
assert_eq!(conn.get("uuid"), Some(&Value::from(test_uuid.to_string())));
}
#[test]
fn sets_interface_name() {
let settings = ConnectionBuilder::new("802-3-ethernet", "MyConnection")
.interface_name("eth0")
.build();
let conn = settings.get("connection").unwrap();
assert_eq!(conn.get("interface-name"), Some(&Value::from("eth0")));
}
#[test]
fn configures_autoconnect() {
let settings = ConnectionBuilder::new("802-11-wireless", "test")
.autoconnect(false)
.autoconnect_priority(10)
.autoconnect_retries(3)
.build();
let conn = settings.get("connection").unwrap();
assert_eq!(conn.get("autoconnect"), Some(&Value::from(false)));
assert_eq!(conn.get("autoconnect-priority"), Some(&Value::from(10i32)));
assert_eq!(conn.get("autoconnect-retries"), Some(&Value::from(3i32)));
}
#[test]
fn applies_connection_options() {
let opts = ConnectionOptions {
autoconnect: true,
autoconnect_priority: Some(5),
autoconnect_retries: Some(2),
};
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.options(&opts)
.build();
let conn = settings.get("connection").unwrap();
assert_eq!(conn.get("autoconnect"), Some(&Value::from(true)));
assert_eq!(conn.get("autoconnect-priority"), Some(&Value::from(5i32)));
assert_eq!(conn.get("autoconnect-retries"), Some(&Value::from(2i32)));
}
#[test]
fn configures_ipv4_auto() {
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.ipv4_auto()
.build();
let ipv4 = settings.get("ipv4").unwrap();
assert_eq!(ipv4.get("method"), Some(&Value::from("auto")));
}
#[test]
fn configures_ipv4_manual() {
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.ipv4_manual(vec![IpConfig::new("192.168.1.100", 24)])
.build();
let ipv4 = settings.get("ipv4").unwrap();
assert_eq!(ipv4.get("method"), Some(&Value::from("manual")));
assert!(ipv4.contains_key("address-data"));
}
#[test]
fn configures_ipv4_disabled() {
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.ipv4_disabled()
.build();
let ipv4 = settings.get("ipv4").unwrap();
assert_eq!(ipv4.get("method"), Some(&Value::from("disabled")));
}
#[test]
fn configures_ipv4_dns() {
let dns = vec!["8.8.8.8".parse().unwrap(), "1.1.1.1".parse().unwrap()];
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.ipv4_auto()
.ipv4_dns(dns)
.build();
let ipv4 = settings.get("ipv4").unwrap();
assert!(ipv4.contains_key("dns"));
}
#[test]
fn configures_ipv6_auto() {
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.ipv6_auto()
.build();
let ipv6 = settings.get("ipv6").unwrap();
assert_eq!(ipv6.get("method"), Some(&Value::from("auto")));
}
#[test]
fn configures_ipv6_ignore() {
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.ipv6_ignore()
.build();
let ipv6 = settings.get("ipv6").unwrap();
assert_eq!(ipv6.get("method"), Some(&Value::from("ignore")));
}
#[test]
fn adds_custom_section() {
let mut bridge = HashMap::new();
bridge.insert("stp", Value::from(true));
let settings = ConnectionBuilder::new("bridge", "br0")
.with_section("bridge", bridge)
.build();
assert!(settings.contains_key("bridge"));
let bridge_section = settings.get("bridge").unwrap();
assert_eq!(bridge_section.get("stp"), Some(&Value::from(true)));
}
#[test]
fn updates_existing_section() {
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.ipv4_auto()
.update_section("ipv4", |ipv4| {
ipv4.insert("may-fail", Value::from(false));
})
.build();
let ipv4 = settings.get("ipv4").unwrap();
assert_eq!(ipv4.get("may-fail"), Some(&Value::from(false)));
}
#[test]
fn configures_complete_static_ipv4() {
let settings = ConnectionBuilder::new("802-3-ethernet", "eth0")
.ipv4_manual(vec![IpConfig::new("192.168.1.100", 24)])
.ipv4_gateway("192.168.1.1".parse().unwrap())
.ipv4_dns(vec!["8.8.8.8".parse().unwrap()])
.build();
let ipv4 = settings.get("ipv4").unwrap();
assert_eq!(ipv4.get("method"), Some(&Value::from("manual")));
assert!(ipv4.contains_key("address-data"));
assert!(ipv4.contains_key("gateway"));
assert!(ipv4.contains_key("dns"));
}
}