1use crate::{
17 generator::*,
18 xml_parsers::{
19 ipfix::{parse_iana_common_values, parse_information_elements, ID_IE},
20 xml_common::find_node_by_id,
21 },
22};
23use std::{collections::HashMap, ffi::OsString, fs, path::Path};
24use thiserror::Error;
25use xml_parsers::sub_registries::parse_subregistry;
26
27mod generator;
28mod generator_aggregation;
29mod generator_sub_registries;
30
31pub mod xml_parsers {
32 pub mod ipfix;
33 pub mod sub_registries;
34 pub mod xml_common;
35}
36
37const APP_USER_AGENT: &str = "curl/7.79.1";
38const GENERATED_VENDOR_MAIN_SUFFIX: &str = "generated.rs";
39const GENERATED_VENDOR_DESER_SUFFIX: &str = "deser_generated.rs";
40const GENERATED_VENDOR_SER_SUFFIX: &str = "ser_generated.rs";
41
42#[derive(Debug, Clone)]
44pub struct InformationElement {
45 pub pen: u32,
46 pub name: String,
47 pub data_type: String,
48 pub group: Option<String>,
49 pub data_type_semantics: Option<String>,
50 pub element_id: u16,
51 pub applicability: Option<String>,
52 pub status: String,
53 pub description: String,
54 pub revision: u32,
55 pub date: String,
56 pub references: Option<String>,
57 pub xrefs: Vec<Xref>,
58 pub units: Option<String>,
59 pub range: Option<String>,
60 pub subregistry: Option<Vec<InformationElementSubRegistry>>,
61}
62
63#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
66pub enum SubRegistryType {
67 ValueNameDescRegistry,
71 ReasonCodeNestedRegistry,
74}
75
76#[derive(Debug, Clone)]
78pub enum InformationElementSubRegistry {
79 ValueNameDescRegistry(ValueNameDescRegistry),
80 ReasonCodeNestedRegistry(ReasonCodeNestedRegistry),
81}
82
83#[derive(Debug, Clone, Default)]
87pub struct ValueNameDescRegistry {
88 pub value: u8,
89 pub name: String,
90 pub display_name: String,
91 pub description: String,
92 pub comments: Option<String>,
93 pub parameters: Option<String>,
94 pub xrefs: Vec<Xref>,
95}
96
97#[derive(Debug, Clone, Default)]
100pub struct ReasonCodeNestedRegistry {
101 pub value: u8,
102 pub name: String,
103 pub display_name: String,
104 pub description: String,
105 pub comments: Option<String>,
106 pub parameters: Option<String>,
107 pub xrefs: Vec<Xref>,
108 pub reason_code_reg: Vec<InformationElementSubRegistry>,
109}
110
111#[derive(Debug)]
115pub struct SimpleRegistry {
116 pub value: u8,
117 pub description: String,
118 pub comments: Option<String>,
119 pub xref: Vec<Xref>,
120}
121
122#[derive(Debug, Clone)]
124pub struct Xref {
125 pub ty: String,
126 pub data: String,
127}
128
129#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
131pub enum RegistrySource {
132 String(String),
134
135 Http(String),
137
138 File(String),
140}
141
142#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)]
144pub enum RegistryType {
145 IanaXML,
148}
149
150#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
153pub struct SourceConfig {
154 source: RegistrySource,
155 registry_type: RegistryType,
156 pen: u32,
158 mod_name: String,
160 name: String,
162 ext_subregs_source: Option<Vec<ExternalSubRegistrySource>>,
164}
165
166impl SourceConfig {
167 pub const fn new(
168 source: RegistrySource,
169 registry_type: RegistryType,
170 pen: u32,
171 mod_name: String,
172 name: String,
173 ext_subregs_source: Option<Vec<ExternalSubRegistrySource>>,
174 ) -> Self {
175 Self {
176 source,
177 registry_type,
178 pen,
179 mod_name,
180 name,
181 ext_subregs_source,
182 }
183 }
184
185 pub const fn source(&self) -> &RegistrySource {
186 &self.source
187 }
188
189 pub const fn registry_type(&self) -> &RegistryType {
190 &self.registry_type
191 }
192
193 pub const fn pen(&self) -> u32 {
194 self.pen
195 }
196
197 pub const fn mod_name(&self) -> &String {
198 &self.mod_name
199 }
200
201 pub const fn name(&self) -> &String {
202 &self.name
203 }
204
205 pub const fn ext_subregs_source(&self) -> &Option<Vec<ExternalSubRegistrySource>> {
206 &self.ext_subregs_source
207 }
208}
209
210#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
212pub struct ExternalSubRegistrySource {
213 source: RegistrySource,
214 registry_type: SubRegistryType,
216 registry_id: String,
218 ie_id: u16,
220}
221
222impl ExternalSubRegistrySource {
223 pub const fn new(
224 source: RegistrySource,
225 registry_type: SubRegistryType,
226 registry_id: String,
227 ie_id: u16,
228 ) -> Self {
229 Self {
230 source,
231 registry_type,
232 registry_id,
233 ie_id,
234 }
235 }
236
237 pub const fn registry_type(&self) -> &SubRegistryType {
238 &self.registry_type
239 }
240}
241
242#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
244pub struct Config {
245 iana: SourceConfig,
246 vendors: Vec<SourceConfig>,
247}
248
249impl Config {
250 pub const fn new(iana: SourceConfig, vendors: Vec<SourceConfig>) -> Self {
251 Self { iana, vendors }
252 }
253
254 pub const fn iana(&self) -> &SourceConfig {
255 &self.iana
256 }
257
258 pub const fn vendors(&self) -> &Vec<SourceConfig> {
259 &self.vendors
260 }
261}
262
263#[derive(Error, Debug)]
264pub enum GetStringSourceError {
265 #[error("http request error")]
266 HttpError(#[from] reqwest::Error),
267 #[error("reading data from filesystem error")]
268 StdIoError(#[from] std::io::Error),
269}
270
271pub fn get_string_source(source: &RegistrySource) -> Result<String, GetStringSourceError> {
273 let str = match source {
274 RegistrySource::String(xml_string) => xml_string.clone(),
275 RegistrySource::Http(url) => {
276 let client = reqwest::blocking::ClientBuilder::new()
277 .user_agent(APP_USER_AGENT)
278 .build()?;
279 let resp = client.get(url).send()?;
280 resp.text()?
281 }
282 RegistrySource::File(path) => fs::read_to_string(path)?,
283 };
284 Ok(str)
285}
286
287#[derive(Error, Debug)]
288pub enum GenerateIanaConfigError {
289 #[error("writing generated code to filesystem error")]
290 StdIoError(#[from] std::io::Error),
291
292 #[error("error getting registry data from the source")]
293 SourceError(#[from] GetStringSourceError),
294
295 #[error("error parsing xml data from the given source")]
296 XmlParsingError(#[from] roxmltree::Error),
297
298 #[error("registry type is not supported")]
299 UnsupportedRegistryType(RegistryType),
300}
301
302fn generate_vendor_ie(
305 out_dir: &OsString,
306 config: &SourceConfig,
307) -> Result<(), GenerateIanaConfigError> {
308 if config.registry_type != RegistryType::IanaXML {
309 return Err(GenerateIanaConfigError::UnsupportedRegistryType(
310 config.registry_type.clone(),
311 ));
312 }
313 let ipfix_xml_string = get_string_source(&config.source)?;
314 let ipfix_xml_doc = roxmltree::Document::parse(ipfix_xml_string.as_str())?;
315 let ipfix_root = ipfix_xml_doc.root();
316
317 let mut ext_subregs: HashMap<u16, Vec<InformationElementSubRegistry>> = HashMap::new();
319 if let Some(ext_subregs_source) = &config.ext_subregs_source {
320 for subreg in ext_subregs_source {
321 let subreg_xml_string = get_string_source(&subreg.source)?;
322 let subreg_xml_doc = roxmltree::Document::parse(subreg_xml_string.as_str())?;
323 let subreg_root = subreg_xml_doc.root();
324
325 let subreg_node = find_node_by_id(&subreg_root, &subreg.registry_id).unwrap();
326 ext_subregs.insert(
327 subreg.ie_id,
328 parse_subregistry(&subreg_node, subreg.registry_type).1,
329 );
330 }
331 }
332
333 let ipfix_ie_node = find_node_by_id(&ipfix_root, ID_IE).unwrap();
334 let ie_parsed = parse_information_elements(&ipfix_ie_node, config.pen, ext_subregs);
335
336 let ie_generated = generate_information_element_ids(&ie_parsed);
337
338 let deser_generated = generate_pkg_ie_deserializers(config.mod_name.as_str(), &ie_parsed);
339 let ser_generated = generate_pkg_ie_serializers(config.mod_name.as_str(), &ie_parsed);
340
341 let mut output = String::new();
342 output.push_str(ie_generated.as_str());
343 output.push_str("\n\n");
344
345 output.push_str(generate_ie_values(&ie_parsed, Some(config.name().clone())).as_str());
346 output.push_str(generate_fields_enum(&ie_parsed).as_str());
347
348 output.push_str(generate_flat_ie_struct(&ie_parsed, &vec![]).as_str());
349
350 let dest_path = Path::new(&out_dir).join(format!(
351 "{}_{}",
352 config.mod_name, GENERATED_VENDOR_MAIN_SUFFIX
353 ));
354 fs::write(dest_path, output)?;
355
356 let deser_dest_path = Path::new(&out_dir).join(format!(
357 "{}_{}",
358 config.mod_name, GENERATED_VENDOR_DESER_SUFFIX
359 ));
360 fs::write(deser_dest_path, deser_generated)?;
361
362 let ser_dest_path = Path::new(&out_dir).join(format!(
363 "{}_{}",
364 config.mod_name, GENERATED_VENDOR_SER_SUFFIX
365 ));
366 fs::write(ser_dest_path, ser_generated)?;
367
368 Ok(())
369}
370
371#[derive(Error, Debug)]
372pub enum GenerateError {
373 #[error("writing generated code to filesystem error")]
374 StdIoError(#[from] std::io::Error),
375
376 #[error("error in generating IANA configs")]
377 GenerateIanaConfigError(#[from] GenerateIanaConfigError),
378
379 #[error("error getting registry data from the source")]
380 SourceError(#[from] GetStringSourceError),
381
382 #[error("error parsing xml data from the given source")]
383 XmlParsingError(#[from] roxmltree::Error),
384
385 #[error("registry type is not supported")]
386 UnsupportedRegistryType(RegistryType),
387}
388
389pub fn generate(out_dir: &OsString, config: &Config) -> Result<(), GenerateError> {
390 let mut ie_output = String::new();
391 ie_output.push_str(generate_common_types().as_str());
392 ie_output.push_str(generate_ie_status().as_str());
393
394 let ipfix_xml_string = get_string_source(&config.iana.source)?;
396 let ipfix_xml_doc = roxmltree::Document::parse(ipfix_xml_string.as_str())?;
397 let iana_ipfix_root = ipfix_xml_doc.root();
398
399 let (_data_types_parsed, semantics_parsed, units_parsed) =
400 parse_iana_common_values(&iana_ipfix_root);
401 let semantics_generated = generate_ie_semantics(&semantics_parsed);
402 let units_generated = generate_ie_units(&units_parsed);
403 ie_output.push_str(semantics_generated.as_str());
404 ie_output.push_str(units_generated.as_str());
405
406 let mut ext_subregs: HashMap<u16, Vec<InformationElementSubRegistry>> = HashMap::new();
408 if let Some(ext_subregs_source) = &config.iana.ext_subregs_source {
409 for subreg in ext_subregs_source {
410 let subreg_xml_string = get_string_source(&subreg.source)?;
411 let subreg_xml_doc = roxmltree::Document::parse(subreg_xml_string.as_str())?;
412 let subreg_root = subreg_xml_doc.root();
413
414 let subreg_node = find_node_by_id(&subreg_root, &subreg.registry_id).unwrap();
415 ext_subregs.insert(
416 subreg.ie_id,
417 parse_subregistry(&subreg_node, subreg.registry_type).1,
418 );
419 }
420 }
421
422 let iana_ipfix_ie_node = find_node_by_id(&iana_ipfix_root, ID_IE).unwrap();
423 let iana_ie_parsed = parse_information_elements(&iana_ipfix_ie_node, 0, ext_subregs);
424
425 let mut vendors = vec![];
426 for vendor in &config.vendors {
427 vendors.push((vendor.name.clone(), vendor.mod_name.clone(), vendor.pen));
428 generate_vendor_ie(out_dir, vendor)?;
429 }
430
431 ie_output.push_str(generate_ie_ids(&iana_ie_parsed, &vendors).as_str());
433 ie_output.push_str(generate_ie_values(&iana_ie_parsed, None).as_str());
434
435 let mut ie_deser = String::new();
436 let mut ie_ser = String::new();
437 ie_deser.push_str("use crate::ie::*;\n\n");
438 ie_ser.push_str("use crate::ie::*;\n\n");
439
440 for vendor in &config.vendors {
441 ie_output.push_str(
442 format!(
443 "pub mod {} {{include!(concat!(env!(\"OUT_DIR\"), \"/{}_{}\"));}}\n\n",
444 vendor.mod_name(),
445 vendor.mod_name(),
446 GENERATED_VENDOR_MAIN_SUFFIX
447 )
448 .as_str(),
449 );
450 ie_deser.push_str(
451 format!(
452 "pub mod {} {{include!(concat!(env!(\"OUT_DIR\"), \"/{}_{}\"));}}\n\n",
453 vendor.mod_name(),
454 vendor.mod_name(),
455 GENERATED_VENDOR_DESER_SUFFIX
456 )
457 .as_str(),
458 );
459 ie_ser.push_str(
460 format!(
461 "pub mod {} {{include!(concat!(env!(\"OUT_DIR\"), \"/{}_{}\"));}}\n\n",
462 vendor.mod_name(),
463 vendor.mod_name(),
464 GENERATED_VENDOR_SER_SUFFIX
465 )
466 .as_str(),
467 );
468 }
469
470 ie_output.push_str(generate_flat_ie_struct(&iana_ie_parsed, &vendors).as_str());
471 ie_deser.push_str(generate_ie_deser_main(&iana_ie_parsed, &vendors).as_str());
472 ie_ser.push_str(generate_ie_ser_main(&iana_ie_parsed, &vendors).as_str());
473
474 let ie_dest_path = Path::new(&out_dir).join("ie_generated.rs");
475 fs::write(ie_dest_path, ie_output)?;
476
477 let ie_deser_dest_path = Path::new(&out_dir).join("ie_deser_generated.rs");
478 fs::write(ie_deser_dest_path, ie_deser)?;
479
480 let ie_ser_dest_path = Path::new(&out_dir).join("ie_ser_generated.rs");
481 fs::write(ie_ser_dest_path, ie_ser)?;
482 Ok(())
483}