feat: DhcpConfig can now effectively manage a config file to add a static map entry

This commit is contained in:
Jean-Gabriel Gill-Couture
2024-11-18 17:05:48 -05:00
parent cb1fea1eda
commit cc9bcb902c
13 changed files with 1595 additions and 1114 deletions

View File

@@ -4,12 +4,12 @@ use yaserde::MaybeString;
use super::opnsense::{NumberOption, Range, StaticMap};
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "dhcpd")]
pub struct Dhcpd {
#[yaserde(rename = "lan")]
pub lan: DhcpInterface,
}
// #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
// #[yaserde(rename = "dhcpd")]
// pub struct Dhcpd {
// #[yaserde(rename = "lan")]
// pub lan: DhcpInterface,
// }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct DhcpInterface {

View File

@@ -1,65 +1,106 @@
use yaserde::{NamedList, YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait};
use yaserde_derive::{YaDeserialize, YaSerialize};
use yaserde::MaybeString;
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Interfaces {
pub interfaces: NamedList<Interface>,
}
#[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)]
pub struct Interface {
pub internal_dynamic: Option<MaybeString>,
#[yaserde(rename = "if")]
pub physical_interface_name: String,
pub descr: String,
pub enable: MaybeString,
pub lock: Option<MaybeString>,
#[yaserde(rename = "spoofmac")]
pub spoof_mac: Option<MaybeString>,
pub internal_dynamic: Option<MaybeString>,
pub ipaddr: Option<MaybeString>,
#[yaserde(rename = "blockpriv")]
pub block_priv: Option<MaybeString>,
#[yaserde(rename = "blockbogons")]
pub block_bogons: Option<MaybeString>,
pub lock: Option<MaybeString>,
#[yaserde(rename = "type")]
pub r#type: Option<MaybeString>,
#[yaserde(rename = "virtual")]
pub r#virtual: Option<MaybeString>,
pub subnet: Option<MaybeString>,
pub ipaddrv6: Option<MaybeString>,
pub networks: Option<MaybeString>,
pub subnetv6: Option<MaybeString>,
pub ipaddrv6: Option<MaybeString>,
#[yaserde(rename = "track6-interface")]
pub track6_interface: Option<MaybeString>,
#[yaserde(rename = "track6-prefix-id")]
pub track6_prefix_id: Option<MaybeString>,
}
pub trait MyDeserialize: YaDeserializeTrait {}
pub trait MySerialize: YaSerializeTrait {}
#[cfg(test)]
mod test {
use crate::xml_utils::to_xml_str;
use super::*;
use pretty_assertions::assert_eq;
use yaserde::NamedList;
#[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)]
pub struct TestStruct {
pub struct InterfacesParent {
foo: String,
interfaces: NamedList<Interface>,
bar: String,
}
#[test]
fn should_deserialize_interfaces() {
let test_struct =
yaserde::de::from_str::<TestStruct>("<xml><foo>aodisj</foo><bar>barbaba</bar></xml>")
.unwrap();
println!("test_struct : {:?}", test_struct);
let interfaces =
yaserde::de::from_str::<NamedList<Interface>>(FULL_INTERFACES_XML).unwrap();
assert_eq!(interfaces.elements.len(), 6)
}
let interfaces = yaserde::de::from_str::<Interfaces>(FULL_INTERFACES_XML).unwrap();
assert_eq!(interfaces.interfaces.elements.len(), 6)
#[test]
fn should_serialize_interfaces() {
let named_list = NamedList {
elements: vec![
(String::from("paul"), Interface::default()),
(String::from("anotherpaul"), Interface::default()),
(String::from("thirdone"), Interface::default()),
(String::from("andgofor4"), Interface::default()),
],
};
let parent = InterfacesParent {
foo: String::from("foo"),
interfaces: named_list,
bar: String::from("foo"),
};
assert_eq!(
&to_xml_str(&parent).unwrap(),
r#"<?xml version="1.0"?>
<InterfacesParent>
<foo>foo</foo>
<interfaces>
<paul>
<if></if>
<descr></descr>
<enable/>
</paul>
<anotherpaul>
<if></if>
<descr></descr>
<enable/>
</anotherpaul>
<thirdone>
<if></if>
<descr></descr>
<enable/>
</thirdone>
<andgofor4>
<if></if>
<descr></descr>
<enable/>
</andgofor4>
</interfaces>
<bar>foo</bar>
</InterfacesParent>
"#
)
}
const FULL_INTERFACES_XML: &str = "<interfaces>

View File

@@ -1,23 +1,9 @@
use crate::data::dhcpd::Dhcpd;
use yaserde::{MaybeString, RawXml};
use crate::{data::dhcpd::DhcpInterface, xml_utils::to_xml_str};
use log::error;
use yaserde::{MaybeString, NamedList, RawXml};
use yaserde_derive::{YaDeserialize, YaSerialize};
impl From<String> for OPNsense {
fn from(content: String) -> Self {
yaserde::de::from_str(&content)
.map_err(|e| error!("{}", e.to_string()))
.expect("OPNSense received invalid string, should be full XML")
}
}
impl OPNsense {
pub fn to_xml(&self) -> String {
yaserde::ser::to_string(self)
.map_err(|e| error!("{}", e.to_string()))
.expect("OPNSense could not serialize to XML")
}
}
use super::Interface;
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "opnsense")]
@@ -25,8 +11,9 @@ pub struct OPNsense {
pub theme: String,
pub sysctl: Sysctl,
pub system: RawXml,
pub interfaces: RawXml,
pub dhcpd: Dhcpd,
// pub interfaces: RawXml,
pub interfaces: NamedList<Interface>,
pub dhcpd: NamedList<DhcpInterface>,
pub snmpd: Snmpd,
pub syslog: Syslog,
pub nat: Nat,
@@ -56,6 +43,23 @@ pub struct OPNsense {
pub ifgroups: Ifgroups,
}
impl From<String> for OPNsense {
fn from(content: String) -> Self {
yaserde::de::from_str(&content)
.map_err(|e| println!("{}", e.to_string()))
.expect("OPNSense received invalid string, should be full XML")
}
}
impl OPNsense {
pub fn to_xml(&self) -> String {
to_xml_str(self)
.map_err(|e| error!("{}", e.to_string()))
.expect("OPNSense could not serialize to XML")
}
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct LoadBalancer {
pub monitor_type: Vec<MonitorType>,
@@ -945,6 +949,7 @@ pub struct Acl {
pub browser: MaybeString,
#[yaserde(rename = "mimeType")]
pub mime_type: MaybeString,
#[yaserde(rename = "googleapps")]
pub google_apps: MaybeString,
pub youtube: MaybeString,
#[yaserde(rename = "safePorts")]

View File

@@ -1,256 +0,0 @@
use xml::reader::XmlEvent as ReadEvent;
use yaserde::{ser, YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait};
#[derive(Debug, PartialEq, Default)]
pub struct RawXml(String);
impl YaDeserializeTrait for RawXml {
fn deserialize<R: std::io::Read>(
reader: &mut yaserde::de::Deserializer<R>,
) -> Result<Self, String> {
let mut buffer = String::new();
let mut depth = 0;
let own_name = match reader.peek()? {
ReadEvent::StartElement { name, .. } => name.local_name.clone(),
_ => return Err("RawXml Should start deserializing with StartElement".to_string()),
};
println!("RawXml deserialize from root element name : {own_name}");
loop {
let current_event = reader.peek()?.to_owned();
match current_event.clone() {
ReadEvent::StartElement {
name, attributes, ..
} => {
println!("StartElement {name} depth {depth}");
depth += 1;
let mut attr_string = String::new();
attributes.iter().for_each(|a| {
attr_string.push_str(&format!(r#" {}="{}""#, &a.name, &a.value));
});
buffer.push_str(&format!("<{}{}>", name, attr_string));
let _event = reader.next_event()?;
}
ReadEvent::EndElement { name } => {
println!("EndElement {name} depth {depth}");
depth -= 1;
buffer.push_str(&format!("</{}>", name));
println!(
"Checking if name.local_name {} matches own_name {} at depth {depth}",
&name.local_name, &own_name
);
if name.local_name == own_name && depth == 0 {
println!(
"Found next EndElement is closing my struct, breaking out of loop"
);
break;
} else {
let _event = reader.next_event()?;
}
}
ReadEvent::Characters(content) => {
println!("Characters {content} depth {depth}");
buffer.push_str(&content);
let _event = reader.next_event()?;
}
ReadEvent::StartDocument {
version,
encoding,
standalone,
} => todo!(
"StartDocument {:?} {:?} {:?}",
version,
encoding,
standalone
),
ReadEvent::EndDocument => todo!(),
ReadEvent::ProcessingInstruction { name, data } => {
todo!("ProcessingInstruction {:?}, {:?}", name, data)
}
ReadEvent::CData(cdata) => todo!("CData, {:?}", cdata),
ReadEvent::Comment(comment) => todo!("Comment, {:?}", comment),
ReadEvent::Whitespace(whitespace) => todo!("Whitespace, {:?}", whitespace),
}
let next = reader.peek()?;
println!(
"Processing done on \ncurrent_event : {:?} \nnext : {:?}",
&current_event, &next
);
}
println!("buffered events {buffer}");
let next = reader.peek()?;
println!("next : {:?}", &next);
Ok(RawXml(buffer))
}
}
impl YaSerializeTrait for RawXml {
fn serialize<W: std::io::Write>(&self, writer: &mut ser::Serializer<W>) -> Result<(), String> {
let content = self.0.clone();
let content = xml::EventReader::from_str(content.as_str());
let mut reader = yaserde::de::Deserializer::new(content);
loop {
let e = reader.next_event()?;
if let ReadEvent::EndDocument = e {
break;
}
writer
.write(e.as_writer_event().unwrap())
.expect("Writer should write write event");
}
Ok(())
}
fn serialize_attributes(
&self,
attributes: Vec<xml::attribute::OwnedAttribute>,
namespace: xml::namespace::Namespace,
) -> Result<
(
Vec<xml::attribute::OwnedAttribute>,
xml::namespace::Namespace,
),
String,
> {
todo!()
}
}
#[cfg(test)]
mod test {
use crate::xml_utils::to_xml_str;
use super::*;
use pretty_assertions::assert_eq;
use yaserde_derive::YaDeserialize;
use yaserde_derive::YaSerialize;
#[derive(Debug, PartialEq, Default, YaDeserialize)]
pub struct Parent {
// pub rawxml_child: RawXml,
pub string_child: String,
pub child_child: Child,
}
#[derive(Debug, PartialEq, Default, YaDeserialize)]
pub struct Child {
pub child_val: String,
pub child_val2: String,
pub child_option: Option<String>,
}
#[test]
fn rawxml_should_buffer_empty_element() {
let rawxml: RawXml = yaserde::de::from_str("<something/>").unwrap();
assert_eq!(rawxml.0, String::from("<something></something>"));
}
#[test]
fn rawxml_should_buffer_elements_with_different_case_as_they_are() {
let xml = "<xml><Some_thing></Some_thing><something></something></xml>";
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
assert_eq!(rawxml.0, String::from(xml));
}
#[test]
fn rawxml_should_buffer_elements_with_attributes() {
let xml = r#"<xml version="ababa"><Some_thing></Some_thing><something></something></xml>"#;
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
assert_eq!(rawxml.0, String::from(xml));
}
#[test]
fn rawxml_should_handle_complex_documents() {
let xml = r#"<xml><OpenVPN version="1.0.0"><Overwrites></Overwrites><Instances></Instances><StaticKeys></StaticKeys></OpenVPN><Gateways version="0.0.1"></Gateways><HAProxy version="4.0.0"><general><enabled>1</enabled><gracefulStop>0</gracefulStop><hardStopAfter>60s</hardStopAfter><closeSpreadTime></closeSpreadTime><seamlessReload>0</seamlessReload><storeOcsp>0</storeOcsp><showIntro>1</showIntro><peers><enabled>0</enabled><name1></name1><listen1></listen1><port1>1024</port1><name2></name2><listen2></listen2><port2>1024</port2></peers><tuning><root>0</root><maxConnections></maxConnections><nbthread>1</nbthread><sslServerVerify>ignore</sslServerVerify><maxDHSize>2048</maxDHSize><bufferSize>16384</bufferSize></tuning></general></HAProxy></xml>"#;
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
assert_eq!(rawxml.0, String::from(xml));
}
#[test]
fn rawxml_should_serialize_simple_documents() {
let xml = r#"<?xml version="1.0" encoding="utf-8"?><xml />"#;
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
assert_eq!(yaserde::ser::to_string(&rawxml).unwrap(), xml);
}
#[test]
fn rawxml_should_serialize_complex_documents() {
let xml = r#"<?xml version="1.0"?>
<xml>
<OpenVPN version="1.0.0">
<Overwrites/>
<Instances/>
<StaticKeys/>
</OpenVPN>
<Gateways version="0.0.1"/>
<HAProxy version="4.0.0">
<general>
<enabled>1</enabled>
<gracefulStop>0</gracefulStop>
<hardStopAfter>60s</hardStopAfter>
<closeSpreadTime/>
<seamlessReload>0</seamlessReload>
<storeOcsp>0</storeOcsp>
<showIntro>1</showIntro>
<peers>
<enabled>0</enabled>
<name1/>
<listen1/>
<port1>1024</port1>
<name2/>
<listen2/>
<port2>1024</port2>
</peers>
<tuning>
<root>0</root>
<maxConnections/>
<nbthread>1</nbthread>
<sslServerVerify>ignore</sslServerVerify>
<maxDHSize>2048</maxDHSize>
<bufferSize>16384</bufferSize>
</tuning>
</general>
</HAProxy>
</xml>
"#;
let rawxml: RawXml = yaserde::de::from_str(xml).unwrap();
assert_eq!(to_xml_str(&rawxml).unwrap(), xml);
}
#[test]
fn rawxml_should_allow_siblings_before() {
#[derive(YaDeserialize, YaSerialize)]
struct Config {
paul: Vec<String>,
raw: RawXml,
}
let xml = r#"<?xml version="1.0" encoding="utf-8"?><Config><paul>bobob</paul><paul>patate</paul><raw>allo something</raw></Config>"#;
let config: Config = yaserde::de::from_str(xml).unwrap();
assert_eq!(yaserde::ser::to_string(&config).unwrap(), xml);
}
#[test]
fn rawxml_should_allow_siblings_after() {
#[derive(YaDeserialize, YaSerialize)]
struct Config {
raw: RawXml,
paul: Vec<String>,
}
let xml = r#"<?xml version="1.0" encoding="utf-8"?><Config><raw>allo something</raw><paul>bobob</paul><paul>patate</paul></Config>"#;
let config: Config = yaserde::de::from_str(xml).unwrap();
assert_eq!(config.paul.get(0).unwrap(), "bobob");
assert_eq!(config.paul.get(1).unwrap(), "patate");
assert_eq!(config.paul.len(), 2);
assert_eq!(config.raw.0, "<raw>allo something</raw>");
assert_eq!(yaserde::ser::to_string(&config).unwrap(), xml);
}
#[test]
fn rawxml_should_allow_being_end_of_document() {
let xml = r#"<?xml version="1.0" encoding="utf-8"?><Config><raw>allo something</raw><paul>bobob</paul><paul>patate</paul></Config>"#;
let config: RawXml = yaserde::de::from_str(xml).unwrap();
assert_eq!(yaserde::ser::to_string(&config).unwrap(), xml);
}
}

View File

@@ -1,173 +0,0 @@
use xml::reader::XmlEvent as ReadEvent;
use xml::writer::XmlEvent as WriteEvent;
use yaserde::{ser, YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait};
#[derive(Debug, PartialEq, Default)]
pub struct MaybeString {
field_name: String,
content: Option<String>,
}
impl YaDeserializeTrait for MaybeString {
fn deserialize<R: std::io::Read>(
reader: &mut yaserde::de::Deserializer<R>,
) -> Result<Self, String> {
let field_name = match reader.peek()? {
ReadEvent::StartElement {
name, attributes, ..
} => {
if attributes.len() > 0 {
return Err(String::from(
"Attributes not currently supported by MaybeString",
));
}
name.local_name.clone()
}
_ => return Err(String::from("Unsupporte ReadEvent type")),
};
reader.next_event()?;
let content = match reader.peek()? {
ReadEvent::Characters(content) => Some(content.clone()),
ReadEvent::EndElement { name } => {
if name.local_name != field_name {
return Err(format!(
"Invalid EndElement, expected {field_name} but got {}",
name.local_name
));
}
None
}
_ => return Err(String::from("Unsupporte ReadEvent type")),
};
Ok(Self {
field_name,
content,
})
}
}
impl YaSerializeTrait for MaybeString {
fn serialize<W: std::io::Write>(&self, writer: &mut ser::Serializer<W>) -> Result<(), String> {
let start_element_event = WriteEvent::start_element(self.field_name.as_str());
writer.write(start_element_event).expect("Writer failed");
match &self.content {
Some(content) => {
writer
.write(WriteEvent::characters(content))
.expect("Writer failed");
}
None => {}
};
writer
.write(WriteEvent::end_element())
.expect("Writer failed");
Ok(())
}
fn serialize_attributes(
&self,
_attributes: Vec<xml::attribute::OwnedAttribute>,
_namespace: xml::namespace::Namespace,
) -> Result<
(
Vec<xml::attribute::OwnedAttribute>,
xml::namespace::Namespace,
),
String,
> {
unimplemented!("MaybeString does not currently support attributes")
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
use yaserde_derive::YaDeserialize;
use yaserde_derive::YaSerialize;
#[derive(Debug, PartialEq, Default, YaDeserialize, YaSerialize)]
struct TestStruct {
maybe: MaybeString,
}
#[test]
fn maybe_string_should_deserialize_empty_element() {
let initial_xml = "<struct><maybe/></struct>";
let test_struct: TestStruct =
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
println!("Got test_struct {:?}", test_struct);
assert_eq!(
test_struct,
TestStruct {
maybe: MaybeString {
field_name: String::from("maybe"),
content: None
}
}
);
}
#[test]
fn maybe_string_should_deserialize_content() {
let initial_xml = "<struct><maybe>some content</maybe></struct>";
let test_struct: TestStruct =
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
println!("Got test_struct {:?}", test_struct);
assert_eq!(
test_struct,
TestStruct {
maybe: MaybeString {
field_name: String::from("maybe"),
content: Some(String::from("some content"))
}
}
);
}
#[test]
fn maybe_string_should_deserialize_empty_long_format() {
let initial_xml = "<struct><maybe></maybe></struct>";
let test_struct: TestStruct =
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
println!("Got test_struct {:?}", test_struct);
assert_eq!(
test_struct,
TestStruct {
maybe: MaybeString {
field_name: String::from("maybe"),
content: None
}
}
);
}
#[test]
fn maybe_string_should_serialize_to_empty_element() {
let initial_xml =
r#"<?xml version="1.0" encoding="utf-8"?><TestStruct><maybe /></TestStruct>"#;
let test_struct: TestStruct =
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
println!("Got test_struct {:?}", test_struct);
assert_eq!(
yaserde::ser::to_string(&test_struct).expect("should serialize teststruct"),
initial_xml
);
}
#[test]
fn maybe_string_should_serialize_content() {
let initial_xml = r#"<?xml version="1.0" encoding="utf-8"?><TestStruct><maybe>some content</maybe></TestStruct>"#;
let test_struct: TestStruct =
yaserde::de::from_str(initial_xml).expect("Shoudl deserialize teststruct");
println!("Got test_struct {:?}", test_struct);
assert_eq!(
yaserde::ser::to_string(&test_struct).expect("should serialize teststruct"),
initial_xml
);
}
}

View File

@@ -1,6 +1,20 @@
//mod generic_xml;
//mod maybe_string;
mod yaserde;
//pub use generic_xml::*;
//pub use maybe_string::*;
pub use yaserde::*;
use yaserde::YaSerialize;
pub fn to_xml_str<T: YaSerialize>(model: &T) -> Result<String, String> {
let yaserde_cfg = yaserde::ser::Config {
perform_indent: true,
write_document_declaration: false,
pad_self_closing: false,
..Default::default()
};
let serialized = yaserde::ser::to_string_with_config::<T>(model, &yaserde_cfg)?;
// Opnsense does not specify encoding in the document declaration
//
// yaserde / xml-rs does not allow disabling the encoding attribute in the
// document declaration
//
// So here we just manually prefix the xml document with the exact document declaration
// that opnsense uses
Ok(format!("<?xml version=\"1.0\"?>\n{serialized}\n"))
}

View File

@@ -1,20 +0,0 @@
use yaserde::YaSerialize;
pub fn to_xml_str<T: YaSerialize>(model: &T) -> Result<String, String> {
let yaserde_cfg = yaserde::ser::Config {
perform_indent: true,
write_document_declaration: false,
pad_self_closing: false,
..Default::default()
};
let serialized = yaserde::ser::to_string_with_config::<T>(model, &yaserde_cfg)?;
// Opnsense does not specify encoding in the document declaration
//
// yaserde / xml-rs does not allow disabling the encoding attribute in the
// document declaration
//
// So here we just manually prefix the xml document with the exact document declaration
// that opnsense uses
Ok(format!("<?xml version=\"1.0\"?>\n{serialized}\n"))
}