1use crate::generator::*;
17use crate::xml_parsers::ipfix::{ID_IE, parse_iana_common_values, parse_information_elements};
18use crate::xml_parsers::xml_common::find_node_by_id;
19use proc_macro2::{Ident, Span, TokenStream};
20use quote::quote;
21use std::collections::HashMap;
22use std::ffi::OsString;
23use std::fs;
24use std::path::Path;
25use thiserror::Error;
26use xml_parsers::sub_registries::parse_subregistry;
27
28mod generator;
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(&config.name, &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 tokens = TokenStream::new();
342 tokens.extend(ie_generated);
343 tokens.extend(generate_ie_values(&ie_parsed, Some(config.name().clone())));
344 tokens.extend(generate_fields_enum(&ie_parsed));
345
346 let mut output = format_tokens(tokens);
347 output.push_str("\n\n");
348
349 let dest_path = Path::new(&out_dir).join(format!(
350 "{}_{}",
351 config.mod_name, GENERATED_VENDOR_MAIN_SUFFIX
352 ));
353 fs::write(dest_path, output)?;
354
355 let deser_dest_path = Path::new(&out_dir).join(format!(
356 "{}_{}",
357 config.mod_name, GENERATED_VENDOR_DESER_SUFFIX
358 ));
359 fs::write(deser_dest_path, format_tokens(deser_generated))?;
360
361 let ser_dest_path = Path::new(&out_dir).join(format!(
362 "{}_{}",
363 config.mod_name, GENERATED_VENDOR_SER_SUFFIX
364 ));
365 fs::write(ser_dest_path, format_tokens(ser_generated))?;
366
367 Ok(())
368}
369
370#[derive(Error, Debug)]
371pub enum GenerateError {
372 #[error("writing generated code to filesystem error")]
373 StdIoError(#[from] std::io::Error),
374
375 #[error("error in generating IANA configs")]
376 GenerateIanaConfigError(#[from] GenerateIanaConfigError),
377
378 #[error("error getting registry data from the source")]
379 SourceError(#[from] GetStringSourceError),
380
381 #[error("error parsing xml data from the given source")]
382 XmlParsingError(#[from] roxmltree::Error),
383
384 #[error("registry type is not supported")]
385 UnsupportedRegistryType(RegistryType),
386}
387
388pub fn generate(out_dir: &OsString, config: &Config) -> Result<(), GenerateError> {
389 let mut tokens = TokenStream::new();
390 tokens.extend(generate_common_types());
391 tokens.extend(generate_ie_status());
392
393 let ipfix_xml_string = get_string_source(&config.iana.source)?;
395 let ipfix_xml_doc = roxmltree::Document::parse(ipfix_xml_string.as_str())?;
396 let iana_ipfix_root = ipfix_xml_doc.root();
397
398 let (_data_types_parsed, semantics_parsed, units_parsed) =
399 parse_iana_common_values(&iana_ipfix_root);
400 tokens.extend(generate_ie_semantics(&semantics_parsed));
401 tokens.extend(generate_ie_units(&units_parsed));
402
403 let mut ext_subregs: HashMap<u16, Vec<InformationElementSubRegistry>> = HashMap::new();
405 if let Some(ext_subregs_source) = &config.iana.ext_subregs_source {
406 for subreg in ext_subregs_source {
407 let subreg_xml_string = get_string_source(&subreg.source)?;
408 let subreg_xml_doc = roxmltree::Document::parse(subreg_xml_string.as_str())?;
409 let subreg_root = subreg_xml_doc.root();
410
411 let subreg_node = find_node_by_id(&subreg_root, &subreg.registry_id).unwrap();
412 ext_subregs.insert(
413 subreg.ie_id,
414 parse_subregistry(&subreg_node, subreg.registry_type).1,
415 );
416 }
417 }
418
419 let iana_ipfix_ie_node = find_node_by_id(&iana_ipfix_root, ID_IE).unwrap();
420 let iana_ie_parsed = parse_information_elements(&iana_ipfix_ie_node, 0, ext_subregs);
421
422 let mut vendors = vec![];
423 for vendor in &config.vendors {
424 vendors.push((vendor.name.clone(), vendor.mod_name.clone(), vendor.pen));
425 generate_vendor_ie(out_dir, vendor)?;
426 }
427
428 tokens.extend(generate_ie_ids(&iana_ie_parsed, &vendors));
430 tokens.extend(generate_ie_values(&iana_ie_parsed, None));
431
432 let mut ie_deser = quote! { use crate::ie::*; };
433 let mut ie_ser = quote! { use crate::ie::*; };
434
435 for vendor in &config.vendors {
436 let vendor_mod = vendor.mod_name();
437 let vendor_ident = Ident::new(vendor.mod_name(), Span::call_site());
438 let main_name = format!("/{vendor_mod}_{GENERATED_VENDOR_MAIN_SUFFIX}");
439 tokens.extend(quote! {
440 pub mod #vendor_ident { include!(concat!(env!("OUT_DIR"), #main_name)); }
441 });
442 let deser_name = format!("/{vendor_mod}_{GENERATED_VENDOR_DESER_SUFFIX}");
443 ie_deser.extend(quote! {
444 pub mod #vendor_ident {include!(concat!(env!("OUT_DIR"), #deser_name)); }
445 });
446 let ser_name = format!("/{vendor_mod}_{GENERATED_VENDOR_SER_SUFFIX}");
447 ie_ser.extend(quote! {
448 pub mod #vendor_ident {include!(concat!(env!("OUT_DIR"), #ser_name)); }
449 });
450 }
451
452 let mut ie_output = String::new();
453 ie_output.push_str(format_tokens(tokens).as_str());
454 ie_deser.extend(generate_ie_deser_main(&iana_ie_parsed, &vendors));
455 ie_ser.extend(generate_ie_ser_main(&iana_ie_parsed, &vendors));
456
457 let ie_dest_path = Path::new(&out_dir).join("ie_generated.rs");
458 fs::write(ie_dest_path, ie_output)?;
459
460 let ie_deser_dest_path = Path::new(&out_dir).join("ie_deser_generated.rs");
461 fs::write(ie_deser_dest_path, format_tokens(ie_deser))?;
462
463 let ie_ser_dest_path = Path::new(&out_dir).join("ie_ser_generated.rs");
464 fs::write(ie_ser_dest_path, format_tokens(ie_ser))?;
465 Ok(())
466}