diff --git a/harmony-rs/opnsense-config/src/config.rs b/harmony-rs/opnsense-config/src/config.rs index 6d26b65..79829c6 100644 --- a/harmony-rs/opnsense-config/src/config.rs +++ b/harmony-rs/opnsense-config/src/config.rs @@ -181,11 +181,20 @@ mod tests { let config_file_path = test_file_path.to_str().unwrap().to_string(); println!("File path {config_file_path}"); let repository = Box::new(LocalFileConfigRepository::new(config_file_path)); + let config_file_str = repository.load().await.unwrap(); let config = Config::new(repository) .await .expect("Failed to load config"); println!("Config {:?}", config); - assert!(false); + + let yaserde_cfg = yaserde::ser::Config { perform_indent: true, + .. Default::default() + }; + let serialized = yaserde::ser::to_string_with_config(&config.opnsense, &yaserde_cfg).unwrap(); + + fs::write("/tmp/serialized.xml", &serialized).unwrap(); + assert_eq!(config_file_str, serialized); } + } diff --git a/harmony-rs/opnsense-config/src/infra/generic_xml.rs b/harmony-rs/opnsense-config/src/infra/generic_xml.rs new file mode 100644 index 0000000..6e1d1a5 --- /dev/null +++ b/harmony-rs/opnsense-config/src/infra/generic_xml.rs @@ -0,0 +1,249 @@ +use xml::reader::{EventReader, XmlEvent as ReadEvent}; +use xml::writer::{EventWriter, XmlEvent as WriteEvent}; +use yaserde::{ser, YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait}; +use yaserde_derive::{YaDeserialize, 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, +} + +#[derive(Debug, PartialEq, Default)] +pub struct RawXml(String); + +impl YaDeserializeTrait for RawXml { + fn deserialize( + reader: &mut yaserde::de::Deserializer, + ) -> Result { + 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 : {:?}", + ¤t_event, &next + ); + } + + println!("buffered events {buffer}"); + + Ok(RawXml(buffer)) + } +} + +impl YaSerializeTrait for RawXml { + fn serialize(&self, writer: &mut ser::Serializer) -> 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, + namespace: xml::namespace::Namespace, + ) -> Result< + ( + Vec, + xml::namespace::Namespace, + ), + String, + > { + todo!() + } +} +// impl YaSerializeTrait for RawXml { +// fn serialize( +// &self, +// writer: &mut yaserde::ser::Serializer, +// ) -> Result<(), String> { +// let mut reader = EventReader::from_str(&self.0); +// loop { +// match reader.next() { +// Ok(ReadEvent::StartElement { +// name, +// attributes, +// namespace, +// }) => { +// let write = WriteEvent::from(reader.next().unwrap()); +// writer +// .write(WriteEvent::StartElement { +// name: name.clone(), +// attributes: attributes.clone(), +// namespace: namespace.clone(), +// }) +// .map_err(|e| e.to_string())?; +// } +// Ok(ReadEvent::EndElement { name }) => { +// writer +// .write(WriteEvent::EndElement { name: Some(name) }) +// .map_err(|e| e.to_string())?; +// } +// Ok(ReadEvent::Characters(content)) => { +// writer +// .write(WriteEvent::Characters(&content)) +// .map_err(|e| e.to_string())?; +// } +// Ok(ReadEvent::Eof) => break, +// Err(e) => return Err(e.to_string()), +// _ => {} +// } +// } +// Ok(()) +// } +// } +// +#[test] +fn rawxml_should_buffer_empty_element() { + let rawxml: RawXml = yaserde::de::from_str("").unwrap(); + assert_eq!(rawxml.0, String::from("")); +} + +#[test] +fn rawxml_should_buffer_elements_with_different_case_as_they_are() { + let 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#""#; + 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#"1060s00101024102401ignore204816384"#; + 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#""#; + 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#"1060s00101024102401ignore204816384"#; + let rawxml: RawXml = yaserde::de::from_str(xml).unwrap(); + assert_eq!(yaserde::ser::to_string(&rawxml).unwrap(), xml); +} + +#[test] +fn rawxml_should_allow_siblings_before() { + #[derive(YaDeserialize, YaSerialize)] + struct Config { + paul: Vec, + raw: RawXml, + } + let xml = r#"bobobpatateallo something"#; + 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, + } + let xml = r#"allo somethingbobobpatate"#; + 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, "allo something"); + assert_eq!(yaserde::ser::to_string(&config).unwrap(), xml); +} + +#[test] +fn rawxml_should_allow_being_end_of_document() { + let xml = r#"allo somethingbobobpatate"#; + let config: RawXml = yaserde::de::from_str(xml).unwrap(); + assert_eq!(yaserde::ser::to_string(&config).unwrap(), xml); +} diff --git a/harmony-rs/opnsense-config/src/infra/mod.rs b/harmony-rs/opnsense-config/src/infra/mod.rs new file mode 100644 index 0000000..66b22fc --- /dev/null +++ b/harmony-rs/opnsense-config/src/infra/mod.rs @@ -0,0 +1,3 @@ + +pub mod generic_xml; + diff --git a/harmony-rs/opnsense-config/src/lib.rs b/harmony-rs/opnsense-config/src/lib.rs index 7673ffc..e0e5967 100644 --- a/harmony-rs/opnsense-config/src/lib.rs +++ b/harmony-rs/opnsense-config/src/lib.rs @@ -1,6 +1,7 @@ pub mod config; pub mod modules; pub mod error; +pub mod infra; pub use config::Config; pub use error::Error; diff --git a/harmony-rs/opnsense-config/src/modules/deserializer/interfaces.rs b/harmony-rs/opnsense-config/src/modules/deserializer/interfaces.rs index ee20f80..277048f 100644 --- a/harmony-rs/opnsense-config/src/modules/deserializer/interfaces.rs +++ b/harmony-rs/opnsense-config/src/modules/deserializer/interfaces.rs @@ -8,7 +8,8 @@ impl YaSerialize for Interfaces { &self, writer: &mut yaserde::ser::Serializer, ) -> Result<(), String> { - todo!() + writer.write("Interfaces serializer TODO"); + Ok(()) } fn serialize_attributes( @@ -88,7 +89,7 @@ impl YaDeserialize for Interfaces { } let peek = reader.peek()?; - let mut read_next = true; + let read_next = true; if let XmlEvent::EndElement { name } = peek { if name.local_name == "interfaces" { println!("Got endElement interfaces, not reading next event"); diff --git a/harmony-rs/opnsense-config/src/modules/opnsense.rs b/harmony-rs/opnsense-config/src/modules/opnsense.rs index d7c2730..6b2c602 100644 --- a/harmony-rs/opnsense-config/src/modules/opnsense.rs +++ b/harmony-rs/opnsense-config/src/modules/opnsense.rs @@ -6,7 +6,7 @@ pub struct OPNsense { pub dhcpd: Dhcpd, pub theme: String, pub sysctl: Sysctl, - pub system: System, + pub system: RawXml, pub interfaces: Interfaces, pub snmpd: Snmpd, pub syslog: Syslog, @@ -107,7 +107,7 @@ pub struct Rule { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Source { - pub any: Option, + pub any: Option, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] @@ -168,24 +168,24 @@ pub struct System { pub timezone: String, pub timeservers: String, pub webgui: WebGui, - pub usevirtualterminal: bool, - pub disableconsolemenu: bool, - pub disablevlanhwfilter: bool, - pub disablechecksumoffloading: bool, - pub disablesegmentationoffloading: bool, - pub disablelargereceiveoffloading: bool, - pub ipv6allow: bool, + pub usevirtualterminal: u8, + pub disableconsolemenu: u8, + pub disablevlanhwfilter: u8, + pub disablechecksumoffloading: u8, + pub disablesegmentationoffloading: u8, + pub disablelargereceiveoffloading: u8, + pub ipv6allow: u8, pub powerd_ac_mode: String, pub powerd_battery_mode: String, pub powerd_normal_mode: String, pub bogons: Bogons, pub crypto_hardware: String, - pub pf_share_forward: bool, - pub lb_use_sticky: bool, - pub kill_states: bool, + pub pf_share_forward: u8, + pub lb_use_sticky: u8, + pub kill_states: u8, pub ssh: Ssh, pub firmware: Firmware, - pub sudo_allow_wheel: bool, + pub sudo_allow_wheel: u8, pub sudo_allow_group: String, pub enablenatreflectionhelper: String, pub rulesetoptimization: String, @@ -211,23 +211,23 @@ pub struct System { pub dns7gw: String, #[yaserde(rename = "dns8gw")] pub dns8gw: String, - pub dnsallowoverride: bool, + pub dnsallowoverride: u8, pub dnsallowoverride_exclude: Option, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Ssh { pub group: String, - pub noauto: bool, + pub noauto: u8, pub interfaces: Option, pub kex: Option, pub ciphers: Option, pub macs: Option, pub keys: Option, pub enabled: String, - pub passwordauth: bool, + pub passwordauth: u8, pub keysig: Option, - pub permitrootlogin: bool, + pub permitrootlogin: u8, } #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] @@ -288,6 +288,8 @@ pub struct WebGui { use std::collections::HashMap; +use crate::infra::generic_xml::RawXml; + #[derive(Default, PartialEq, Debug)] pub struct Interfaces { pub interfaces: HashMap, @@ -313,7 +315,7 @@ pub struct Interface { #[yaserde(rename = "virtual")] pub r#virtual: Option, pub subnet: Option, - pub networks: Option, + pub networks: Option, pub subnetv6: Option, pub ipaddrv6: Option, #[yaserde(rename = "track6-interface")] @@ -374,7 +376,7 @@ pub struct Snmpd { #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] pub struct Syslog { - pub reverse: Option, + pub reverse: Option, pub preservelogs: i32, } @@ -399,7 +401,7 @@ pub struct NatRule { pub ipprotocol: String, pub descr: Option, pub tag: Option, - pub tagged: Option, + pub tagged: Option, pub poolopts: PoolOpts, #[yaserde(rename = "associated-rule-id")] pub associated_rule_id: String, @@ -666,7 +668,7 @@ pub struct Capture { #[yaserde(rename = "interfaces")] pub interfaces: Option, #[yaserde(rename = "egress_only")] - pub egress_only: Option, + pub egress_only: Option, #[yaserde(rename = "version")] pub version: Option, #[yaserde(rename = "targets")] @@ -1536,7 +1538,7 @@ pub struct Backend { #[yaserde(rename = "id")] pub id: String, #[yaserde(rename = "enabled")] - pub enabled: bool, + pub enabled: u8, #[yaserde(rename = "name")] pub name: String, #[yaserde(rename = "description")] @@ -1562,11 +1564,11 @@ pub struct Backend { #[yaserde(rename = "source")] pub source: Option, #[yaserde(rename = "healthCheckEnabled")] - pub health_check_enabled: bool, + pub health_check_enabled: u8, #[yaserde(rename = "healthCheck")] pub health_check: Option, #[yaserde(rename = "healthCheckLogStatus")] - pub health_check_log_status: bool, + pub health_check_log_status: u8, #[yaserde(rename = "checkInterval")] pub check_interval: Option, #[yaserde(rename = "checkDownInterval")] @@ -1578,9 +1580,9 @@ pub struct Backend { #[yaserde(rename = "linkedMailer")] pub linked_mailer: Option, #[yaserde(rename = "http2Enabled")] - pub http2_enabled: bool, + pub http2_enabled: u8, #[yaserde(rename = "http2Enabled_nontls")] - pub http2_enabled_nontls: bool, + pub http2_enabled_nontls: u8, #[yaserde(rename = "ba_advertised_protocols")] pub ba_advertised_protocols: String, #[yaserde(rename = "persistence")] @@ -1590,7 +1592,7 @@ pub struct Backend { #[yaserde(rename = "persistence_cookiename")] pub persistence_cookiename: Option, #[yaserde(rename = "persistence_stripquotes")] - pub persistence_stripquotes: bool, + pub persistence_stripquotes: u8, #[yaserde(rename = "stickiness_pattern")] pub stickiness_pattern: String, #[yaserde(rename = "stickiness_dataTypes")] @@ -1616,7 +1618,7 @@ pub struct Backend { #[yaserde(rename = "stickiness_bytesOutRatePeriod")] pub stickiness_bytes_out_rate_period: String, #[yaserde(rename = "basicAuthEnabled")] - pub basic_auth_enabled: bool, + pub basic_auth_enabled: u8, #[yaserde(rename = "basicAuthUsers")] pub basic_auth_users: Option, #[yaserde(rename = "basicAuthGroups")] @@ -1634,11 +1636,11 @@ pub struct Backend { #[yaserde(rename = "tuning_defaultserver")] pub tuning_defaultserver: Option, #[yaserde(rename = "tuning_noport")] - pub tuning_noport: bool, + pub tuning_noport: u8, #[yaserde(rename = "tuning_httpreuse")] pub tuning_httpreuse: Option, #[yaserde(rename = "tuning_caching")] - pub tuning_caching: bool, + pub tuning_caching: u8, #[yaserde(rename = "linkedActions")] pub linked_actions: Option, #[yaserde(rename = "linkedErrorfiles")] @@ -1658,7 +1660,7 @@ pub struct HAProxyServer { #[yaserde(rename = "id")] pub id: String, #[yaserde(rename = "enabled")] - pub enabled: bool, + pub enabled: u8, #[yaserde(rename = "name")] pub name: String, #[yaserde(rename = "description")] @@ -1686,11 +1688,11 @@ pub struct HAProxyServer { #[yaserde(rename = "resolvePrefer")] pub resolve_prefer: Option, #[yaserde(rename = "ssl")] - pub ssl: bool, + pub ssl: u8, #[yaserde(rename = "sslSNI")] pub ssl_sni: Option, #[yaserde(rename = "sslVerify")] - pub ssl_verify: bool, + pub ssl_verify: u8, #[yaserde(rename = "sslCA")] pub ssl_ca: Option, #[yaserde(rename = "sslCRL")] @@ -1731,7 +1733,7 @@ pub struct HAProxyHealthCheck { pub ssl: String, #[yaserde(rename = "sslSNI")] pub ssl_sni: Option, - pub force_ssl: bool, + pub force_ssl: u8, pub checkport: Option, pub http_method: String, pub http_uri: String,