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 description: String,
91 pub comments: Option<String>,
92 pub parameters: Option<String>,
93 pub xrefs: Vec<Xref>,
94}
95
96#[derive(Debug, Clone, Default)]
99pub struct ReasonCodeNestedRegistry {
100 pub value: u8,
101 pub name: String,
102 pub description: String,
103 pub comments: Option<String>,
104 pub parameters: Option<String>,
105 pub xrefs: Vec<Xref>,
106 pub reason_code_reg: Vec<InformationElementSubRegistry>,
107}
108
109#[derive(Debug)]
113pub struct SimpleRegistry {
114 pub value: u8,
115 pub description: String,
116 pub comments: Option<String>,
117 pub xref: Vec<Xref>,
118}
119
120#[derive(Debug, Clone)]
122pub struct Xref {
123 pub ty: String,
124 pub data: String,
125}
126
127#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
129pub enum RegistrySource {
130 String(String),
132
133 Http(String),
135
136 File(String),
138}
139
140#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)]
142pub enum RegistryType {
143 IanaXML,
146}
147
148#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
151pub struct SourceConfig {
152 source: RegistrySource,
153 registry_type: RegistryType,
154 pen: u32,
156 mod_name: String,
158 name: String,
160 ext_subregs_source: Option<Vec<ExternalSubRegistrySource>>,
162}
163
164impl SourceConfig {
165 pub const fn new(
166 source: RegistrySource,
167 registry_type: RegistryType,
168 pen: u32,
169 mod_name: String,
170 name: String,
171 ext_subregs_source: Option<Vec<ExternalSubRegistrySource>>,
172 ) -> Self {
173 Self {
174 source,
175 registry_type,
176 pen,
177 mod_name,
178 name,
179 ext_subregs_source,
180 }
181 }
182
183 pub const fn source(&self) -> &RegistrySource {
184 &self.source
185 }
186
187 pub const fn registry_type(&self) -> &RegistryType {
188 &self.registry_type
189 }
190
191 pub const fn pen(&self) -> u32 {
192 self.pen
193 }
194
195 pub const fn mod_name(&self) -> &String {
196 &self.mod_name
197 }
198
199 pub const fn name(&self) -> &String {
200 &self.name
201 }
202
203 pub const fn ext_subregs_source(&self) -> &Option<Vec<ExternalSubRegistrySource>> {
204 &self.ext_subregs_source
205 }
206}
207
208#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
210pub struct ExternalSubRegistrySource {
211 source: RegistrySource,
212 registry_type: SubRegistryType,
214 registry_id: String,
216 ie_id: u16,
218}
219
220impl ExternalSubRegistrySource {
221 pub const fn new(
222 source: RegistrySource,
223 registry_type: SubRegistryType,
224 registry_id: String,
225 ie_id: u16,
226 ) -> Self {
227 Self {
228 source,
229 registry_type,
230 registry_id,
231 ie_id,
232 }
233 }
234
235 pub const fn registry_type(&self) -> &SubRegistryType {
236 &self.registry_type
237 }
238}
239
240#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
242pub struct Config {
243 iana: SourceConfig,
244 vendors: Vec<SourceConfig>,
245}
246
247impl Config {
248 pub const fn new(iana: SourceConfig, vendors: Vec<SourceConfig>) -> Self {
249 Self { iana, vendors }
250 }
251
252 pub const fn iana(&self) -> &SourceConfig {
253 &self.iana
254 }
255
256 pub const fn vendors(&self) -> &Vec<SourceConfig> {
257 &self.vendors
258 }
259}
260
261#[derive(Error, Debug)]
262pub enum GetStringSourceError {
263 #[error("http request error")]
264 HttpError(#[from] reqwest::Error),
265 #[error("reading data from filesystem error")]
266 StdIoError(#[from] std::io::Error),
267}
268
269pub fn get_string_source(source: &RegistrySource) -> Result<String, GetStringSourceError> {
271 let str = match source {
272 RegistrySource::String(xml_string) => xml_string.clone(),
273 RegistrySource::Http(url) => {
274 let client = reqwest::blocking::ClientBuilder::new()
275 .user_agent(APP_USER_AGENT)
276 .build()?;
277 let resp = client.get(url).send()?;
278 resp.text()?
279 }
280 RegistrySource::File(path) => fs::read_to_string(path)?,
281 };
282 Ok(str)
283}
284
285#[derive(Error, Debug)]
286pub enum GenerateIanaConfigError {
287 #[error("writing generated code to filesystem error")]
288 StdIoError(#[from] std::io::Error),
289
290 #[error("error getting registry data from the source")]
291 SourceError(#[from] GetStringSourceError),
292
293 #[error("error parsing xml data from the given source")]
294 XmlParsingError(#[from] roxmltree::Error),
295
296 #[error("registry type is not supported")]
297 UnsupportedRegistryType(RegistryType),
298}
299
300fn generate_vendor_ie(
303 out_dir: &OsString,
304 config: &SourceConfig,
305) -> Result<(), GenerateIanaConfigError> {
306 if config.registry_type != RegistryType::IanaXML {
307 return Err(GenerateIanaConfigError::UnsupportedRegistryType(
308 config.registry_type.clone(),
309 ));
310 }
311 let ipfix_xml_string = get_string_source(&config.source)?;
312 let ipfix_xml_doc = roxmltree::Document::parse(ipfix_xml_string.as_str())?;
313 let ipfix_root = ipfix_xml_doc.root();
314
315 let mut ext_subregs: HashMap<u16, Vec<InformationElementSubRegistry>> = HashMap::new();
317 if let Some(ext_subregs_source) = &config.ext_subregs_source {
318 for subreg in ext_subregs_source {
319 let subreg_xml_string = get_string_source(&subreg.source)?;
320 let subreg_xml_doc = roxmltree::Document::parse(subreg_xml_string.as_str())?;
321 let subreg_root = subreg_xml_doc.root();
322
323 let subreg_node = find_node_by_id(&subreg_root, &subreg.registry_id).unwrap();
324 ext_subregs.insert(
325 subreg.ie_id,
326 parse_subregistry(&subreg_node, subreg.registry_type).1,
327 );
328 }
329 }
330
331 let ipfix_ie_node = find_node_by_id(&ipfix_root, ID_IE).unwrap();
332 let ie_parsed = parse_information_elements(&ipfix_ie_node, config.pen, ext_subregs);
333
334 let ie_generated = generate_information_element_ids(&ie_parsed);
335
336 let deser_generated = generate_pkg_ie_deserializers(config.mod_name.as_str(), &ie_parsed);
337 let ser_generated = generate_pkg_ie_serializers(config.mod_name.as_str(), &ie_parsed);
338
339 let mut output = String::new();
340 output.push_str(ie_generated.as_str());
341 output.push_str("\n\n");
342
343 output.push_str(generate_ie_values(&ie_parsed, Some(config.name().clone())).as_str());
344 output.push_str(generate_fields_enum(&ie_parsed).as_str());
345
346 output.push_str(generate_flat_ie_struct(&ie_parsed, &vec![]).as_str());
347
348 let dest_path = Path::new(&out_dir).join(format!(
349 "{}_{}",
350 config.mod_name, GENERATED_VENDOR_MAIN_SUFFIX
351 ));
352 fs::write(dest_path, output)?;
353
354 let deser_dest_path = Path::new(&out_dir).join(format!(
355 "{}_{}",
356 config.mod_name, GENERATED_VENDOR_DESER_SUFFIX
357 ));
358 fs::write(deser_dest_path, deser_generated)?;
359
360 let ser_dest_path = Path::new(&out_dir).join(format!(
361 "{}_{}",
362 config.mod_name, GENERATED_VENDOR_SER_SUFFIX
363 ));
364 fs::write(ser_dest_path, ser_generated)?;
365
366 Ok(())
367}
368
369#[derive(Error, Debug)]
370pub enum GenerateError {
371 #[error("writing generated code to filesystem error")]
372 StdIoError(#[from] std::io::Error),
373
374 #[error("error in generating IANA configs")]
375 GenerateIanaConfigError(#[from] GenerateIanaConfigError),
376
377 #[error("error getting registry data from the source")]
378 SourceError(#[from] GetStringSourceError),
379
380 #[error("error parsing xml data from the given source")]
381 XmlParsingError(#[from] roxmltree::Error),
382
383 #[error("registry type is not supported")]
384 UnsupportedRegistryType(RegistryType),
385}
386
387pub fn generate(out_dir: &OsString, config: &Config) -> Result<(), GenerateError> {
388 let mut ie_output = String::new();
389 ie_output.push_str(generate_common_types().as_str());
390 ie_output.push_str(generate_ie_status().as_str());
391
392 let ipfix_xml_string = get_string_source(&config.iana.source)?;
394 let ipfix_xml_doc = roxmltree::Document::parse(ipfix_xml_string.as_str())?;
395 let iana_ipfix_root = ipfix_xml_doc.root();
396
397 let (_data_types_parsed, semantics_parsed, units_parsed) =
398 parse_iana_common_values(&iana_ipfix_root);
399 let semantics_generated = generate_ie_semantics(&semantics_parsed);
400 let units_generated = generate_ie_units(&units_parsed);
401 ie_output.push_str(semantics_generated.as_str());
402 ie_output.push_str(units_generated.as_str());
403
404 let mut ext_subregs: HashMap<u16, Vec<InformationElementSubRegistry>> = HashMap::new();
406 if let Some(ext_subregs_source) = &config.iana.ext_subregs_source {
407 for subreg in ext_subregs_source {
408 let subreg_xml_string = get_string_source(&subreg.source)?;
409 let subreg_xml_doc = roxmltree::Document::parse(subreg_xml_string.as_str())?;
410 let subreg_root = subreg_xml_doc.root();
411
412 let subreg_node = find_node_by_id(&subreg_root, &subreg.registry_id).unwrap();
413 ext_subregs.insert(
414 subreg.ie_id,
415 parse_subregistry(&subreg_node, subreg.registry_type).1,
416 );
417 }
418 }
419
420 let iana_ipfix_ie_node = find_node_by_id(&iana_ipfix_root, ID_IE).unwrap();
421 let iana_ie_parsed = parse_information_elements(&iana_ipfix_ie_node, 0, ext_subregs);
422
423 let mut vendors = vec![];
424 for vendor in &config.vendors {
425 vendors.push((vendor.name.clone(), vendor.mod_name.clone(), vendor.pen));
426 generate_vendor_ie(out_dir, vendor)?;
427 }
428
429 ie_output.push_str(generate_ie_ids(&iana_ie_parsed, &vendors).as_str());
431 ie_output.push_str(generate_ie_values(&iana_ie_parsed, None).as_str());
432
433 let mut ie_deser = String::new();
434 let mut ie_ser = String::new();
435 ie_deser.push_str("use crate::ie::*;\n\n");
436 ie_ser.push_str("use crate::ie::*;\n\n");
437
438 for vendor in &config.vendors {
439 ie_output.push_str(
440 format!(
441 "pub mod {} {{include!(concat!(env!(\"OUT_DIR\"), \"/{}_{}\"));}}\n\n",
442 vendor.mod_name(),
443 vendor.mod_name(),
444 GENERATED_VENDOR_MAIN_SUFFIX
445 )
446 .as_str(),
447 );
448 ie_deser.push_str(
449 format!(
450 "pub mod {} {{include!(concat!(env!(\"OUT_DIR\"), \"/{}_{}\"));}}\n\n",
451 vendor.mod_name(),
452 vendor.mod_name(),
453 GENERATED_VENDOR_DESER_SUFFIX
454 )
455 .as_str(),
456 );
457 ie_ser.push_str(
458 format!(
459 "pub mod {} {{include!(concat!(env!(\"OUT_DIR\"), \"/{}_{}\"));}}\n\n",
460 vendor.mod_name(),
461 vendor.mod_name(),
462 GENERATED_VENDOR_SER_SUFFIX
463 )
464 .as_str(),
465 );
466 }
467
468 ie_output.push_str(generate_flat_ie_struct(&iana_ie_parsed, &vendors).as_str());
469 ie_deser.push_str(generate_ie_deser_main(&iana_ie_parsed, &vendors).as_str());
470 ie_ser.push_str(generate_ie_ser_main(&iana_ie_parsed, &vendors).as_str());
471
472 let ie_dest_path = Path::new(&out_dir).join("ie_generated.rs");
473 fs::write(ie_dest_path, ie_output)?;
474
475 let ie_deser_dest_path = Path::new(&out_dir).join("ie_deser_generated.rs");
476 fs::write(ie_deser_dest_path, ie_deser)?;
477
478 let ie_ser_dest_path = Path::new(&out_dir).join("ie_ser_generated.rs");
479 fs::write(ie_ser_dest_path, ie_ser)?;
480 Ok(())
481}