use std::collections::HashMap;
use zvariant::Value;
use crate::ConnectionOptions;
use crate::api::models::{ConnectionError, VlanConfig};
pub fn build_vlan_connection(
config: &VlanConfig,
opts: &ConnectionOptions,
) -> Result<HashMap<&'static str, HashMap<&'static str, Value<'static>>>, ConnectionError> {
config.validate()?;
let mut conn: HashMap<&'static str, HashMap<&'static str, Value<'static>>> = HashMap::new();
conn.insert("connection", connection_section(config, opts));
conn.insert("vlan", vlan_section(config));
let mut ipv4 = HashMap::new();
ipv4.insert("method", Value::from("auto"));
conn.insert("ipv4", ipv4);
let mut ipv6 = HashMap::new();
ipv6.insert("method", Value::from("auto"));
conn.insert("ipv6", ipv6);
Ok(conn)
}
fn connection_section(
config: &VlanConfig,
opts: &ConnectionOptions,
) -> HashMap<&'static str, Value<'static>> {
let mut s = HashMap::new();
s.insert("type", Value::from("vlan"));
s.insert("id", Value::from(config.effective_connection_name()));
s.insert("uuid", Value::from(uuid::Uuid::new_v4().to_string()));
s.insert("autoconnect", Value::from(opts.autoconnect));
s.insert(
"interface-name",
Value::from(config.effective_interface_name()),
);
if let Some(p) = opts.autoconnect_priority {
s.insert("autoconnect-priority", Value::from(p));
}
if let Some(r) = opts.autoconnect_retries {
s.insert("autoconnect-retries", Value::from(r));
}
s
}
fn vlan_section(config: &VlanConfig) -> HashMap<&'static str, Value<'static>> {
let mut s = HashMap::new();
s.insert("parent", Value::from(config.parent.clone()));
s.insert("id", Value::from(u32::from(config.id)));
if let Some(flags) = config.flags {
s.insert("flags", Value::from(flags));
}
if let Some(ref map) = config.ingress_priority_map {
let entries: Vec<Value<'static>> = map.iter().map(|e| Value::from(e.clone())).collect();
s.insert("ingress-priority-map", Value::Array(entries.into()));
}
if let Some(ref map) = config.egress_priority_map {
let entries: Vec<Value<'static>> = map.iter().map(|e| Value::from(e.clone())).collect();
s.insert("egress-priority-map", Value::Array(entries.into()));
}
s
}
#[cfg(test)]
mod tests {
use super::*;
fn test_opts() -> ConnectionOptions {
ConnectionOptions {
autoconnect: true,
autoconnect_priority: Some(10),
autoconnect_retries: Some(3),
}
}
#[test]
fn builds_basic_vlan_connection() {
let config = VlanConfig::new("eth0", 100);
let opts = test_opts();
let conn = build_vlan_connection(&config, &opts).unwrap();
assert!(conn.contains_key("connection"));
assert!(conn.contains_key("vlan"));
assert!(conn.contains_key("ipv4"));
assert!(conn.contains_key("ipv6"));
}
#[test]
fn connection_section_has_correct_type() {
let config = VlanConfig::new("eth0", 100);
let opts = test_opts();
let conn = build_vlan_connection(&config, &opts).unwrap();
let connection = conn.get("connection").unwrap();
if let Some(Value::Str(t)) = connection.get("type") {
assert_eq!(t.as_str(), "vlan");
} else {
panic!("type field missing or wrong type");
}
}
#[test]
fn vlan_section_has_parent_and_id() {
let config = VlanConfig::new("enp3s0", 200);
let opts = test_opts();
let conn = build_vlan_connection(&config, &opts).unwrap();
let vlan = conn.get("vlan").unwrap();
if let Some(Value::Str(parent)) = vlan.get("parent") {
assert_eq!(parent.as_str(), "enp3s0");
} else {
panic!("parent field missing or wrong type");
}
if let Some(Value::U32(id)) = vlan.get("id") {
assert_eq!(*id, 200);
} else {
panic!("id field missing or wrong type");
}
}
#[test]
fn uses_default_interface_name() {
let config = VlanConfig::new("eth0", 100);
let opts = test_opts();
let conn = build_vlan_connection(&config, &opts).unwrap();
let connection = conn.get("connection").unwrap();
if let Some(Value::Str(name)) = connection.get("interface-name") {
assert_eq!(name.as_str(), "eth0.100");
} else {
panic!("interface-name field missing or wrong type");
}
}
#[test]
fn uses_custom_interface_name() {
let config = VlanConfig::new("eth0", 100).with_interface_name("office-vlan");
let opts = test_opts();
let conn = build_vlan_connection(&config, &opts).unwrap();
let connection = conn.get("connection").unwrap();
if let Some(Value::Str(name)) = connection.get("interface-name") {
assert_eq!(name.as_str(), "office-vlan");
} else {
panic!("interface-name field missing or wrong type");
}
}
#[test]
fn uses_default_connection_name() {
let config = VlanConfig::new("eth0", 100);
let opts = test_opts();
let conn = build_vlan_connection(&config, &opts).unwrap();
let connection = conn.get("connection").unwrap();
if let Some(Value::Str(name)) = connection.get("id") {
assert_eq!(name.as_str(), "VLAN 100 on eth0");
} else {
panic!("id field missing or wrong type");
}
}
#[test]
fn uses_custom_connection_name() {
let config = VlanConfig::new("eth0", 100).with_connection_name("Office Network");
let opts = test_opts();
let conn = build_vlan_connection(&config, &opts).unwrap();
let connection = conn.get("connection").unwrap();
if let Some(Value::Str(name)) = connection.get("id") {
assert_eq!(name.as_str(), "Office Network");
} else {
panic!("id field missing or wrong type");
}
}
#[test]
fn includes_vlan_flags() {
let config = VlanConfig::new("eth0", 100).with_flags(0x5);
let opts = test_opts();
let conn = build_vlan_connection(&config, &opts).unwrap();
let vlan = conn.get("vlan").unwrap();
if let Some(Value::U32(flags)) = vlan.get("flags") {
assert_eq!(*flags, 0x5);
} else {
panic!("flags field missing or wrong type");
}
}
#[test]
fn rejects_invalid_vlan_id_zero() {
let config = VlanConfig::new("eth0", 0);
let opts = test_opts();
let result = build_vlan_connection(&config, &opts);
assert!(result.is_err());
}
#[test]
fn rejects_invalid_vlan_id_too_high() {
let config = VlanConfig::new("eth0", 4095);
let opts = test_opts();
let result = build_vlan_connection(&config, &opts);
assert!(result.is_err());
}
#[test]
fn rejects_empty_parent() {
let config = VlanConfig::new("", 100);
let opts = test_opts();
let result = build_vlan_connection(&config, &opts);
assert!(result.is_err());
}
#[test]
fn uuid_is_unique() {
let config = VlanConfig::new("eth0", 100);
let opts = test_opts();
let conn1 = build_vlan_connection(&config, &opts).unwrap();
let conn2 = build_vlan_connection(&config, &opts).unwrap();
let uuid1 = conn1
.get("connection")
.and_then(|c| c.get("uuid"))
.map(|v| match v {
Value::Str(s) => s.as_str(),
_ => "",
})
.unwrap_or("");
let uuid2 = conn2
.get("connection")
.and_then(|c| c.get("uuid"))
.map(|v| match v {
Value::Str(s) => s.as_str(),
_ => "",
})
.unwrap_or("");
assert_ne!(uuid1, uuid2, "UUIDs should be unique");
}
}