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 proc_macro2::{Ident, Span, TokenStream};
24use quote::quote;
25use std::{collections::HashMap, ffi::OsString, fs, path::Path};
26use thiserror::Error;
27use xml_parsers::sub_registries::parse_subregistry;
28
29mod generator;
30mod generator_sub_registries;
31
32pub mod xml_parsers {
33 pub mod ipfix;
34 pub mod sub_registries;
35 pub mod xml_common;
36}
37
38const APP_USER_AGENT: &str = "curl/7.79.1";
39const GENERATED_VENDOR_MAIN_SUFFIX: &str = "generated.rs";
40const GENERATED_VENDOR_DESER_SUFFIX: &str = "deser_generated.rs";
41const GENERATED_VENDOR_SER_SUFFIX: &str = "ser_generated.rs";
42
43#[derive(Debug, Clone)]
45pub struct InformationElement {
46 pub pen: u32,
47 pub name: String,
48 pub data_type: String,
49 pub group: Option<String>,
50 pub data_type_semantics: Option<String>,
51 pub element_id: u16,
52 pub applicability: Option<String>,
53 pub status: String,
54 pub description: String,
55 pub revision: u32,
56 pub date: String,
57 pub references: Option<String>,
58 pub xrefs: Vec<Xref>,
59 pub units: Option<String>,
60 pub range: Option<String>,
61 pub subregistry: Option<Vec<InformationElementSubRegistry>>,
62}
63
64#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
67pub enum SubRegistryType {
68 ValueNameDescRegistry,
72 ReasonCodeNestedRegistry,
75}
76
77#[derive(Debug, Clone)]
79pub enum InformationElementSubRegistry {
80 ValueNameDescRegistry(ValueNameDescRegistry),
81 ReasonCodeNestedRegistry(ReasonCodeNestedRegistry),
82}
83
84#[derive(Debug, Clone, Default)]
88pub struct ValueNameDescRegistry {
89 pub value: u8,
90 pub name: String,
91 pub display_name: String,
92 pub description: String,
93 pub comments: Option<String>,
94 pub parameters: Option<String>,
95 pub xrefs: Vec<Xref>,
96}
97
98#[derive(Debug, Clone, Default)]
101pub struct ReasonCodeNestedRegistry {
102 pub value: u8,
103 pub name: String,
104 pub display_name: String,
105 pub description: String,
106 pub comments: Option<String>,
107 pub parameters: Option<String>,
108 pub xrefs: Vec<Xref>,
109 pub reason_code_reg: Vec<InformationElementSubRegistry>,
110}
111
112#[derive(Debug)]
116pub struct SimpleRegistry {
117 pub value: u8,
118 pub description: String,
119 pub comments: Option<String>,
120 pub xref: Vec<Xref>,
121}
122
123#[derive(Debug, Clone)]
125pub struct Xref {
126 pub ty: String,
127 pub data: String,
128}
129
130#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
132pub enum RegistrySource {
133 String(String),
135
136 Http(String),
138
139 File(String),
141}
142
143#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Eq, PartialEq)]
145pub enum RegistryType {
146 IanaXML,
149}
150
151#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
154pub struct SourceConfig {
155 source: RegistrySource,
156 registry_type: RegistryType,
157 pen: u32,
159 mod_name: String,
161 name: String,
163 ext_subregs_source: Option<Vec<ExternalSubRegistrySource>>,
165}
166
167impl SourceConfig {
168 pub const fn new(
169 source: RegistrySource,
170 registry_type: RegistryType,
171 pen: u32,
172 mod_name: String,
173 name: String,
174 ext_subregs_source: Option<Vec<ExternalSubRegistrySource>>,
175 ) -> Self {
176 Self {
177 source,
178 registry_type,
179 pen,
180 mod_name,
181 name,
182 ext_subregs_source,
183 }
184 }
185
186 pub const fn source(&self) -> &RegistrySource {
187 &self.source
188 }
189
190 pub const fn registry_type(&self) -> &RegistryType {
191 &self.registry_type
192 }
193
194 pub const fn pen(&self) -> u32 {
195 self.pen
196 }
197
198 pub const fn mod_name(&self) -> &String {
199 &self.mod_name
200 }
201
202 pub const fn name(&self) -> &String {
203 &self.name
204 }
205
206 pub const fn ext_subregs_source(&self) -> &Option<Vec<ExternalSubRegistrySource>> {
207 &self.ext_subregs_source
208 }
209}
210
211#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
213pub struct ExternalSubRegistrySource {
214 source: RegistrySource,
215 registry_type: SubRegistryType,
217 registry_id: String,
219 ie_id: u16,
221}
222
223impl ExternalSubRegistrySource {
224 pub const fn new(
225 source: RegistrySource,
226 registry_type: SubRegistryType,
227 registry_id: String,
228 ie_id: u16,
229 ) -> Self {
230 Self {
231 source,
232 registry_type,
233 registry_id,
234 ie_id,
235 }
236 }
237
238 pub const fn registry_type(&self) -> &SubRegistryType {
239 &self.registry_type
240 }
241}
242
243#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
245pub struct Config {
246 iana: SourceConfig,
247 vendors: Vec<SourceConfig>,
248}
249
250impl Config {
251 pub const fn new(iana: SourceConfig, vendors: Vec<SourceConfig>) -> Self {
252 Self { iana, vendors }
253 }
254
255 pub const fn iana(&self) -> &SourceConfig {
256 &self.iana
257 }
258
259 pub const fn vendors(&self) -> &Vec<SourceConfig> {
260 &self.vendors
261 }
262}
263
264#[derive(Error, Debug)]
265pub enum GetStringSourceError {
266 #[error("http request error")]
267 HttpError(#[from] reqwest::Error),
268 #[error("reading data from filesystem error")]
269 StdIoError(#[from] std::io::Error),
270}
271
272pub fn get_string_source(source: &RegistrySource) -> Result<String, GetStringSourceError> {
274 let str = match source {
275 RegistrySource::String(xml_string) => xml_string.clone(),
276 RegistrySource::Http(url) => {
277 let client = reqwest::blocking::ClientBuilder::new()
278 .user_agent(APP_USER_AGENT)
279 .build()?;
280 let resp = client.get(url).send()?;
281 resp.text()?
282 }
283 RegistrySource::File(path) => fs::read_to_string(path)?,
284 };
285 Ok(str)
286}
287
288#[derive(Error, Debug)]
289pub enum GenerateIanaConfigError {
290 #[error("writing generated code to filesystem error")]
291 StdIoError(#[from] std::io::Error),
292
293 #[error("error getting registry data from the source")]
294 SourceError(#[from] GetStringSourceError),
295
296 #[error("error parsing xml data from the given source")]
297 XmlParsingError(#[from] roxmltree::Error),
298
299 #[error("registry type is not supported")]
300 UnsupportedRegistryType(RegistryType),
301}
302
303fn generate_vendor_ie(
306 out_dir: &OsString,
307 config: &SourceConfig,
308) -> Result<(), GenerateIanaConfigError> {
309 if config.registry_type != RegistryType::IanaXML {
310 return Err(GenerateIanaConfigError::UnsupportedRegistryType(
311 config.registry_type.clone(),
312 ));
313 }
314 let ipfix_xml_string = get_string_source(&config.source)?;
315 let ipfix_xml_doc = roxmltree::Document::parse(ipfix_xml_string.as_str())?;
316 let ipfix_root = ipfix_xml_doc.root();
317
318 let mut ext_subregs: HashMap<u16, Vec<InformationElementSubRegistry>> = HashMap::new();
320 if let Some(ext_subregs_source) = &config.ext_subregs_source {
321 for subreg in ext_subregs_source {
322 let subreg_xml_string = get_string_source(&subreg.source)?;
323 let subreg_xml_doc = roxmltree::Document::parse(subreg_xml_string.as_str())?;
324 let subreg_root = subreg_xml_doc.root();
325
326 let subreg_node = find_node_by_id(&subreg_root, &subreg.registry_id).unwrap();
327 ext_subregs.insert(
328 subreg.ie_id,
329 parse_subregistry(&subreg_node, subreg.registry_type).1,
330 );
331 }
332 }
333
334 let ipfix_ie_node = find_node_by_id(&ipfix_root, ID_IE).unwrap();
335 let ie_parsed = parse_information_elements(&ipfix_ie_node, config.pen, ext_subregs);
336
337 let ie_generated = generate_information_element_ids(&config.name, &ie_parsed);
338
339 let deser_generated = generate_pkg_ie_deserializers(config.mod_name.as_str(), &ie_parsed);
340 let ser_generated = generate_pkg_ie_serializers(config.mod_name.as_str(), &ie_parsed);
341
342 let mut tokens = TokenStream::new();
343 tokens.extend(ie_generated);
344 tokens.extend(generate_ie_values(&ie_parsed, Some(config.name().clone())));
345 tokens.extend(generate_fields_enum(&ie_parsed));
346
347 let mut output = format_tokens(tokens);
348 output.push_str("\n\n");
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, format_tokens(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, format_tokens(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 tokens = TokenStream::new();
391 tokens.extend(generate_common_types());
392 tokens.extend(generate_ie_status());
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 tokens.extend(generate_ie_semantics(&semantics_parsed));
402 tokens.extend(generate_ie_units(&units_parsed));
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 tokens.extend(generate_ie_ids(&iana_ie_parsed, &vendors));
431 tokens.extend(generate_ie_values(&iana_ie_parsed, None));
432
433 let mut ie_deser = quote! { use crate::ie::*; };
434 let mut ie_ser = quote! { use crate::ie::*; };
435
436 for vendor in &config.vendors {
437 let vendor_mod = vendor.mod_name();
438 let vendor_ident = Ident::new(vendor.mod_name(), Span::call_site());
439 let main_name = format!("/{vendor_mod}_{GENERATED_VENDOR_MAIN_SUFFIX}");
440 tokens.extend(quote! {
441 pub mod #vendor_ident { include!(concat!(env!("OUT_DIR"), #main_name)); }
442 });
443 let deser_name = format!("/{vendor_mod}_{GENERATED_VENDOR_DESER_SUFFIX}");
444 ie_deser.extend(quote! {
445 pub mod #vendor_ident {include!(concat!(env!("OUT_DIR"), #deser_name)); }
446 });
447 let ser_name = format!("/{vendor_mod}_{GENERATED_VENDOR_SER_SUFFIX}");
448 ie_ser.extend(quote! {
449 pub mod #vendor_ident {include!(concat!(env!("OUT_DIR"), #ser_name)); }
450 });
451 }
452
453 let mut ie_output = String::new();
454 ie_output.push_str(format_tokens(tokens).as_str());
455 ie_deser.extend(generate_ie_deser_main(&iana_ie_parsed, &vendors));
456 ie_ser.extend(generate_ie_ser_main(&iana_ie_parsed, &vendors));
457
458 let ie_dest_path = Path::new(&out_dir).join("ie_generated.rs");
459 fs::write(ie_dest_path, ie_output)?;
460
461 let ie_deser_dest_path = Path::new(&out_dir).join("ie_deser_generated.rs");
462 fs::write(ie_deser_dest_path, format_tokens(ie_deser))?;
463
464 let ie_ser_dest_path = Path::new(&out_dir).join("ie_ser_generated.rs");
465 fs::write(ie_ser_dest_path, format_tokens(ie_ser))?;
466 Ok(())
467}