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;
25
26/// Subregistry Trait with getter functions for common values
27pub trait SubRegistry {
28    fn name(&self) -> &str;
29    fn description(&self) -> &str;
30    fn comments(&self) -> &Option<String>;
31    fn parameters(&self) -> &Option<String>;
32    fn xrefs(&self) -> &Vec<Xref>;
33}
34
35impl SubRegistry for ValueNameDescRegistry {
36    fn name(&self) -> &str {
37        &self.name
38    }
39
40    fn description(&self) -> &str {
41        &self.description
42    }
43
44    fn comments(&self) -> &Option<String> {
45        &self.comments
46    }
47
48    fn parameters(&self) -> &Option<String> {
49        &self.parameters
50    }
51
52    fn xrefs(&self) -> &Vec<Xref> {
53        &self.xrefs
54    }
55}
56
57impl SubRegistry for ReasonCodeNestedRegistry {
58    fn name(&self) -> &str {
59        &self.name
60    }
61
62    fn description(&self) -> &str {
63        &self.description
64    }
65
66    fn comments(&self) -> &Option<String> {
67        &self.comments
68    }
69
70    fn parameters(&self) -> &Option<String> {
71        &self.parameters
72    }
73
74    fn xrefs(&self) -> &Vec<Xref> {
75        &self.xrefs
76    }
77}
78
79/// Wrapper to call the appropriate sub-registry parsing function based on the
80/// registry_type. Returns a `Vec<InformationElementSubRegistry>`
81pub fn parse_subregistry(
82    node: &Node<'_, '_>,
83    registry_type: SubRegistryType,
84) -> (u16, Vec<InformationElementSubRegistry>) {
85    match registry_type {
86        SubRegistryType::ValueNameDescRegistry => {
87            let (ie_id, reg) = parse_val_name_desc_u8_registry(node);
88            let ie_subreg: Vec<InformationElementSubRegistry> = reg
89                .into_iter()
90                .map(InformationElementSubRegistry::ValueNameDescRegistry)
91                .collect();
92            (ie_id, ie_subreg)
93        }
94        SubRegistryType::ReasonCodeNestedRegistry => {
95            let (ie_id, reg) = parse_reason_code_nested_u8_registry_2bit(node);
96            let ie_subreg: Vec<InformationElementSubRegistry> = reg
97                .into_iter()
98                .map(InformationElementSubRegistry::ReasonCodeNestedRegistry)
99                .collect();
100            (ie_id, ie_subreg)
101        }
102    }
103}
104
105/// Parse generic sub-registries with value, name and/or description, and
106/// optionally a comment, and parameter set. Examples:
107/// - [flowEndReason (Value 136)](https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flow-end-reason)
108/// - [flowSelectorAlgorithm (Value 390)](https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flowselectoralgorithm)
109pub fn parse_val_name_desc_u8_registry(node: &Node<'_, '_>) -> (u16, Vec<ValueNameDescRegistry>) {
110    let mut ret = Vec::new();
111
112    let children = node
113        .children()
114        .filter(|x| x.tag_name() == (IANA_NAMESPACE, "record").into())
115        .collect::<Vec<_>>();
116
117    let title = get_string_child(node, (IANA_NAMESPACE, "title").into()).unwrap_or_default();
118
119    let ie_id_regex = Regex::new(r"Value (\d+)").unwrap();
120    let ie_id = ie_id_regex
121        .captures(&title)
122        .and_then(|captures| captures.get(1))
123        .and_then(|capture| capture.as_str().parse().ok())
124        .unwrap_or(0);
125
126    for child in &children {
127        let value = get_string_child(child, (IANA_NAMESPACE, "value").into()).map(|x| {
128            if let Some(hex_value) = x.strip_prefix("0x") {
129                u8::from_str_radix(hex_value, 16)
130            } else if let Some(bin_value) = x.strip_prefix("0b") {
131                u8::from_str_radix(bin_value, 2)
132            } else if let Some(bin_value) = x.strip_suffix('b') {
133                u8::from_str_radix(bin_value, 2)
134            } else {
135                x.parse::<u8>()
136            }
137        });
138
139        let name_parsed = get_string_child(child, (IANA_NAMESPACE, "name").into());
140
141        // TODO: also consider unassigned and experimentation values
142        if Some(true)
143            == name_parsed
144                .as_ref()
145                .map(|x| x.as_str() == UNASSIGNED || x.contains(EXPERIMENTATION))
146        {
147            continue;
148        }
149
150        let description_parsed = parse_simple_description_string(child);
151        if Some(true)
152            == description_parsed
153                .as_ref()
154                .map(|x| x.as_str() == UNASSIGNED || x.contains(EXPERIMENTATION))
155        {
156            continue;
157        }
158
159        // Populate name, description
160        // If name not there, take it from the description
161        let mut name: String;
162        let description: String;
163        if let Some(Ok(value)) = value {
164            if value == u8::MAX {
165                // TODO: also consider unassigned and experimentation values
166                continue;
167            }
168
169            if let Some(name_parsed) = name_parsed {
170                (_, name) = xml_string_to_enum_type(&name_parsed);
171                if let Some(desc_parsed) = description_parsed {
172                    description = desc_parsed;
173                } else {
174                    description = name_parsed;
175                }
176            } else if let Some(mut desc_parsed) = description_parsed {
177                description = desc_parsed.clone();
178
179                let desc_words_amount: usize;
180                (desc_words_amount, desc_parsed) = xml_string_to_enum_type(&desc_parsed);
181
182                if desc_words_amount < MAX_WORDS_NAME {
183                    name = desc_parsed;
184                } else {
185                    name = format!("Value{value}");
186                }
187            } else {
188                log::info!("Skipping sub-registry: missing both name and description!");
189                continue;
190            }
191
192            // Handle duplicates
193            if name == *RESERVED || name == *PRIVATE {
194                name = format!("{name}{value}");
195            }
196
197            let comments = get_string_child(child, (IANA_NAMESPACE, "comments").into());
198            let parameters = get_string_child(child, (IANA_NAMESPACE, "parameters").into());
199            let xrefs = parse_xref(child);
200
201            ret.push(ValueNameDescRegistry {
202                value,
203                name,
204                description,
205                comments,
206                parameters,
207                xrefs,
208            });
209        }
210    }
211
212    (ie_id, ret)
213}
214
215/// Parse sub-registries with nested (first 2bit = status) registries for reason
216/// code, such as: [Forwarding Status (Value 89)](https://www.iana.org/assignments/ipfix/ipfix.xml#forwarding-status)
217pub fn parse_reason_code_nested_u8_registry_2bit(
218    node: &Node<'_, '_>,
219) -> (u16, Vec<ReasonCodeNestedRegistry>) {
220    let (ie_id, subreg) = parse_val_name_desc_u8_registry(node);
221
222    let ret: Vec<ReasonCodeNestedRegistry> = subreg
223        .iter()
224        .map(|subreg| {
225            let val_bin_str = format!("{:02b}", subreg.value);
226            let reason_code_pattern = format!(r".*-{val_bin_str}b");
227            let reason_code_reg_pattern = Regex::new(&reason_code_pattern).unwrap();
228            let reason_code_node = find_node_by_regex(node, &reason_code_reg_pattern).unwrap();
229            ReasonCodeNestedRegistry {
230                value: subreg.value << 6,
231                name: SubRegistry::name(subreg).to_string(),
232                description: SubRegistry::description(subreg).to_string(),
233                comments: SubRegistry::comments(subreg).to_owned(),
234                parameters: SubRegistry::parameters(subreg).to_owned(),
235                xrefs: SubRegistry::xrefs(subreg).to_owned(),
236                reason_code_reg: {
237                    let (_, reg) = parse_val_name_desc_u8_registry(&reason_code_node);
238                    let reg: Vec<InformationElementSubRegistry> = reg
239                        .into_iter()
240                        .map(InformationElementSubRegistry::ValueNameDescRegistry)
241                        .collect();
242                    reg
243                },
244            }
245        })
246        .collect();
247
248    (ie_id, ret)
249}