netgauze_ipfix_code_generator/xml_parsers/
sub_registries.rs

1// Copyright (C) 2022-present The NetGauze Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//    http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12// implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::{
17    xml_parsers::xml_common::*, InformationElementSubRegistry, ReasonCodeNestedRegistry,
18    SubRegistryType, ValueNameDescRegistry, Xref,
19};
20
21use regex::Regex;
22use roxmltree::Node;
23
24const MAX_WORDS_NAME: usize = 10;
25const MAX_CHARS_DISPLAY_NAME: usize = 50;
26
27/// Subregistry Trait with getter functions for common values
28pub trait SubRegistry {
29    fn name(&self) -> &str;
30    fn display_name(&self) -> &str;
31    fn description(&self) -> &str;
32    fn comments(&self) -> &Option<String>;
33    fn parameters(&self) -> &Option<String>;
34    fn xrefs(&self) -> &Vec<Xref>;
35}
36
37impl SubRegistry for ValueNameDescRegistry {
38    fn name(&self) -> &str {
39        &self.name
40    }
41
42    fn display_name(&self) -> &str {
43        &self.display_name
44    }
45
46    fn description(&self) -> &str {
47        &self.description
48    }
49
50    fn comments(&self) -> &Option<String> {
51        &self.comments
52    }
53
54    fn parameters(&self) -> &Option<String> {
55        &self.parameters
56    }
57
58    fn xrefs(&self) -> &Vec<Xref> {
59        &self.xrefs
60    }
61}
62
63impl SubRegistry for ReasonCodeNestedRegistry {
64    fn name(&self) -> &str {
65        &self.name
66    }
67
68    fn display_name(&self) -> &str {
69        &self.display_name
70    }
71
72    fn description(&self) -> &str {
73        &self.description
74    }
75
76    fn comments(&self) -> &Option<String> {
77        &self.comments
78    }
79
80    fn parameters(&self) -> &Option<String> {
81        &self.parameters
82    }
83
84    fn xrefs(&self) -> &Vec<Xref> {
85        &self.xrefs
86    }
87}
88
89/// Wrapper to call the appropriate sub-registry parsing function based on the
90/// registry_type. Returns a `Vec<InformationElementSubRegistry>`
91pub fn parse_subregistry(
92    node: &Node<'_, '_>,
93    registry_type: SubRegistryType,
94) -> (u16, Vec<InformationElementSubRegistry>) {
95    match registry_type {
96        SubRegistryType::ValueNameDescRegistry => {
97            let (ie_id, reg) = parse_val_name_desc_u8_registry(node);
98            let ie_subreg: Vec<InformationElementSubRegistry> = reg
99                .into_iter()
100                .map(InformationElementSubRegistry::ValueNameDescRegistry)
101                .collect();
102            (ie_id, ie_subreg)
103        }
104        SubRegistryType::ReasonCodeNestedRegistry => {
105            let (ie_id, reg) = parse_reason_code_nested_u8_registry_2bit(node);
106            let ie_subreg: Vec<InformationElementSubRegistry> = reg
107                .into_iter()
108                .map(InformationElementSubRegistry::ReasonCodeNestedRegistry)
109                .collect();
110            (ie_id, ie_subreg)
111        }
112    }
113}
114
115/// Parse generic sub-registries with value (or id), name and/or description,
116/// and optionally a comment, and parameter set. Examples:
117/// - [flowEndReason (Value 136)](https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flow-end-reason)
118/// - [flowSelectorAlgorithm (Value 390)](https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flowselectoralgorithm)
119pub fn parse_val_name_desc_u8_registry(node: &Node<'_, '_>) -> (u16, Vec<ValueNameDescRegistry>) {
120    let mut ret = Vec::new();
121
122    let children = node
123        .children()
124        .filter(|x| x.tag_name() == (IANA_NAMESPACE, "record").into())
125        .collect::<Vec<_>>();
126
127    let title = get_string_child(node, (IANA_NAMESPACE, "title").into()).unwrap_or_default();
128
129    let ie_id_regex = Regex::new(r"Value (\d+)").unwrap();
130    let ie_id = ie_id_regex
131        .captures(&title)
132        .and_then(|captures| captures.get(1))
133        .and_then(|capture| capture.as_str().parse().ok())
134        .unwrap_or(0);
135
136    for child in &children {
137        let value = get_string_child(child, (IANA_NAMESPACE, "value").into()).map(|x| {
138            if let Some(hex_value) = x.strip_prefix("0x") {
139                u8::from_str_radix(hex_value, 16)
140            } else if let Some(bin_value) = x.strip_prefix("0b") {
141                u8::from_str_radix(bin_value, 2)
142            } else if let Some(bin_value) = x.strip_suffix('b') {
143                u8::from_str_radix(bin_value, 2)
144            } else {
145                x.parse::<u8>()
146            }
147        });
148
149        // If value column is not present, fallback to id (e.g. psamp-parameters
150        // registry)
151        let value = match value {
152            Some(_) => value,
153            None => get_string_child(child, (IANA_NAMESPACE, "id").into()).map(|x| {
154                if let Some(hex_value) = x.strip_prefix("0x") {
155                    u8::from_str_radix(hex_value, 16)
156                } else if let Some(bin_value) = x.strip_prefix("0b") {
157                    u8::from_str_radix(bin_value, 2)
158                } else if let Some(bin_value) = x.strip_suffix('b') {
159                    u8::from_str_radix(bin_value, 2)
160                } else {
161                    x.parse::<u8>()
162                }
163            }),
164        };
165
166        let name_parsed = get_string_child(child, (IANA_NAMESPACE, "name").into());
167
168        // TODO: also consider unassigned and experimentation values
169        if Some(true)
170            == name_parsed
171                .as_ref()
172                .map(|x| x.as_str() == UNASSIGNED || x.contains(EXPERIMENTATION))
173        {
174            continue;
175        }
176
177        let description_parsed = parse_simple_description_string(child);
178        if Some(true)
179            == description_parsed
180                .as_ref()
181                .map(|x| x.as_str() == UNASSIGNED || x.contains(EXPERIMENTATION))
182        {
183            continue;
184        }
185
186        // Populate name, display_name, and description
187        // - name is always a usable enum variant type name (if not there in the
188        //   registry, take it from the description)
189        // - display_name matches the IANA registry name (apart from when name is
190        //   populated from description field)
191        let mut name: String;
192        let mut display_name: String;
193        let description: String;
194        if let Some(Ok(value)) = value {
195            if value == u8::MAX {
196                // TODO: also consider unassigned and experimentation values
197                continue;
198            }
199
200            if let Some(name_parsed) = name_parsed {
201                display_name = name_parsed.clone();
202
203                (_, name) = xml_string_to_enum_type(&name_parsed);
204                if let Some(desc_parsed) = description_parsed {
205                    description = desc_parsed;
206                } else {
207                    description = name_parsed;
208                }
209            } else if let Some(mut desc_parsed) = description_parsed {
210                description = desc_parsed.clone();
211
212                let desc_words_amount: usize;
213                (desc_words_amount, desc_parsed) = xml_string_to_enum_type(&desc_parsed);
214
215                if desc_words_amount < MAX_WORDS_NAME {
216                    display_name = desc_parsed.clone();
217                    name = desc_parsed;
218                } else {
219                    display_name = format!("Value{value}");
220                    name = format!("Value{value}");
221                }
222
223                if description.len() < MAX_CHARS_DISPLAY_NAME {
224                    display_name = description.clone();
225                }
226            } else {
227                log::info!("Skipping sub-registry: missing both name and description!");
228                continue;
229            }
230
231            // Handle duplicates
232            if name == *RESERVED || name == *PRIVATE {
233                name = format!("{name}{value}");
234            }
235
236            let comments = get_string_child(child, (IANA_NAMESPACE, "comments").into());
237            let parameters = get_string_child(child, (IANA_NAMESPACE, "parameters").into());
238            let xrefs = parse_xref(child);
239
240            ret.push(ValueNameDescRegistry {
241                value,
242                name,
243                display_name,
244                description,
245                comments,
246                parameters,
247                xrefs,
248            });
249        }
250    }
251
252    (ie_id, ret)
253}
254
255/// Parse sub-registries with nested (first 2bit = status) registries for reason
256/// code, such as: [Forwarding Status (Value 89)](https://www.iana.org/assignments/ipfix/ipfix.xml#forwarding-status)
257pub fn parse_reason_code_nested_u8_registry_2bit(
258    node: &Node<'_, '_>,
259) -> (u16, Vec<ReasonCodeNestedRegistry>) {
260    let (ie_id, subreg) = parse_val_name_desc_u8_registry(node);
261
262    let ret: Vec<ReasonCodeNestedRegistry> = subreg
263        .iter()
264        .map(|subreg| {
265            let val_bin_str = format!("{:02b}", subreg.value);
266            let reason_code_pattern = format!(r".*-{val_bin_str}b");
267            let reason_code_reg_pattern = Regex::new(&reason_code_pattern).unwrap();
268            let reason_code_node = find_node_by_regex(node, &reason_code_reg_pattern).unwrap();
269            ReasonCodeNestedRegistry {
270                value: subreg.value << 6,
271                name: SubRegistry::name(subreg).to_string(),
272                display_name: SubRegistry::display_name(subreg).to_string(),
273                description: SubRegistry::description(subreg).to_string(),
274                comments: SubRegistry::comments(subreg).to_owned(),
275                parameters: SubRegistry::parameters(subreg).to_owned(),
276                xrefs: SubRegistry::xrefs(subreg).to_owned(),
277                reason_code_reg: {
278                    let (_, reg) = parse_val_name_desc_u8_registry(&reason_code_node);
279                    let reg: Vec<InformationElementSubRegistry> = reg
280                        .into_iter()
281                        .map(InformationElementSubRegistry::ValueNameDescRegistry)
282                        .collect();
283                    reg
284                },
285            }
286        })
287        .collect();
288
289    (ie_id, ret)
290}