netgauze_ipfix_code_generator/xml_parsers/
sub_registries.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// Copyright (C) 2022-present The NetGauze Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::{
    xml_parsers::xml_common::*, InformationElementSubRegistry, ReasonCodeNestedRegistry,
    SubRegistryType, ValueNameDescRegistry, Xref,
};

use regex::Regex;
use roxmltree::Node;

const MAX_WORDS_NAME: usize = 10;

/// Subregistry Trait with getter functions for common values
pub trait SubRegistry {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn comments(&self) -> &Option<String>;
    fn parameters(&self) -> &Option<String>;
    fn xrefs(&self) -> &Vec<Xref>;
}

impl SubRegistry for ValueNameDescRegistry {
    fn name(&self) -> &str {
        &self.name
    }

    fn description(&self) -> &str {
        &self.description
    }

    fn comments(&self) -> &Option<String> {
        &self.comments
    }

    fn parameters(&self) -> &Option<String> {
        &self.parameters
    }

    fn xrefs(&self) -> &Vec<Xref> {
        &self.xrefs
    }
}

impl SubRegistry for ReasonCodeNestedRegistry {
    fn name(&self) -> &str {
        &self.name
    }

    fn description(&self) -> &str {
        &self.description
    }

    fn comments(&self) -> &Option<String> {
        &self.comments
    }

    fn parameters(&self) -> &Option<String> {
        &self.parameters
    }

    fn xrefs(&self) -> &Vec<Xref> {
        &self.xrefs
    }
}

/// Wrapper to call the appropriate sub-registry parsing function based on the
/// registry_type. Returns a `Vec<InformationElementSubRegistry>`
pub fn parse_subregistry(
    node: &Node<'_, '_>,
    registry_type: SubRegistryType,
) -> (u16, Vec<InformationElementSubRegistry>) {
    match registry_type {
        SubRegistryType::ValueNameDescRegistry => {
            let (ie_id, reg) = parse_val_name_desc_u8_registry(node);
            let ie_subreg: Vec<InformationElementSubRegistry> = reg
                .into_iter()
                .map(InformationElementSubRegistry::ValueNameDescRegistry)
                .collect();
            (ie_id, ie_subreg)
        }
        SubRegistryType::ReasonCodeNestedRegistry => {
            let (ie_id, reg) = parse_reason_code_nested_u8_registry_2bit(node);
            let ie_subreg: Vec<InformationElementSubRegistry> = reg
                .into_iter()
                .map(InformationElementSubRegistry::ReasonCodeNestedRegistry)
                .collect();
            (ie_id, ie_subreg)
        }
    }
}

/// Parse generic sub-registries with value, name and/or description, and
/// optionally a comment, and parameter set. Examples:
/// - [flowEndReason (Value 136)](https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flow-end-reason)
/// - [flowSelectorAlgorithm (Value 390)](https://www.iana.org/assignments/ipfix/ipfix.xhtml#ipfix-flowselectoralgorithm)
pub fn parse_val_name_desc_u8_registry(node: &Node<'_, '_>) -> (u16, Vec<ValueNameDescRegistry>) {
    let mut ret = Vec::new();

    let children = node
        .children()
        .filter(|x| x.tag_name() == (IANA_NAMESPACE, "record").into())
        .collect::<Vec<_>>();

    let title = get_string_child(node, (IANA_NAMESPACE, "title").into()).unwrap_or_default();

    let ie_id_regex = Regex::new(r"Value (\d+)").unwrap();
    let ie_id = ie_id_regex
        .captures(&title)
        .and_then(|captures| captures.get(1))
        .and_then(|capture| capture.as_str().parse().ok())
        .unwrap_or(0);

    for child in &children {
        let value = get_string_child(child, (IANA_NAMESPACE, "value").into()).map(|x| {
            if let Some(hex_value) = x.strip_prefix("0x") {
                u8::from_str_radix(hex_value, 16)
            } else if let Some(bin_value) = x.strip_prefix("0b") {
                u8::from_str_radix(bin_value, 2)
            } else if let Some(bin_value) = x.strip_suffix('b') {
                u8::from_str_radix(bin_value, 2)
            } else {
                x.parse::<u8>()
            }
        });

        let name_parsed = get_string_child(child, (IANA_NAMESPACE, "name").into());

        // TODO: also consider unassigned and experimentation values
        if Some(true)
            == name_parsed
                .as_ref()
                .map(|x| x.as_str() == UNASSIGNED || x.contains(EXPERIMENTATION))
        {
            continue;
        }

        let description_parsed = parse_simple_description_string(child);
        if Some(true)
            == description_parsed
                .as_ref()
                .map(|x| x.as_str() == UNASSIGNED || x.contains(EXPERIMENTATION))
        {
            continue;
        }

        // Populate name, description
        // If name not there, take it from the description
        let mut name: String;
        let description: String;
        if let Some(Ok(value)) = value {
            if value == u8::MAX {
                // TODO: also consider unassigned and experimentation values
                continue;
            }

            if let Some(name_parsed) = name_parsed {
                (_, name) = xml_string_to_enum_type(&name_parsed);
                if let Some(desc_parsed) = description_parsed {
                    description = desc_parsed;
                } else {
                    description = name_parsed;
                }
            } else if let Some(mut desc_parsed) = description_parsed {
                description = desc_parsed.clone();

                let desc_words_amount: usize;
                (desc_words_amount, desc_parsed) = xml_string_to_enum_type(&desc_parsed);

                if desc_words_amount < MAX_WORDS_NAME {
                    name = desc_parsed;
                } else {
                    name = format!("Value{value}");
                }
            } else {
                log::info!("Skipping sub-registry: missing both name and description!");
                continue;
            }

            // Handle duplicates
            if name == *RESERVED || name == *PRIVATE {
                name = format!("{name}{value}");
            }

            let comments = get_string_child(child, (IANA_NAMESPACE, "comments").into());
            let parameters = get_string_child(child, (IANA_NAMESPACE, "parameters").into());
            let xrefs = parse_xref(child);

            ret.push(ValueNameDescRegistry {
                value,
                name,
                description,
                comments,
                parameters,
                xrefs,
            });
        }
    }

    (ie_id, ret)
}

/// Parse sub-registries with nested (first 2bit = status) registries for reason
/// code, such as: [Forwarding Status (Value 89)](https://www.iana.org/assignments/ipfix/ipfix.xml#forwarding-status)
pub fn parse_reason_code_nested_u8_registry_2bit(
    node: &Node<'_, '_>,
) -> (u16, Vec<ReasonCodeNestedRegistry>) {
    let (ie_id, subreg) = parse_val_name_desc_u8_registry(node);

    let ret: Vec<ReasonCodeNestedRegistry> = subreg
        .iter()
        .map(|subreg| {
            let val_bin_str = format!("{:02b}", subreg.value);
            let reason_code_pattern = format!(r".*-{val_bin_str}b");
            let reason_code_reg_pattern = Regex::new(&reason_code_pattern).unwrap();
            let reason_code_node = find_node_by_regex(node, &reason_code_reg_pattern).unwrap();
            ReasonCodeNestedRegistry {
                value: subreg.value << 6,
                name: SubRegistry::name(subreg).to_string(),
                description: SubRegistry::description(subreg).to_string(),
                comments: SubRegistry::comments(subreg).to_owned(),
                parameters: SubRegistry::parameters(subreg).to_owned(),
                xrefs: SubRegistry::xrefs(subreg).to_owned(),
                reason_code_reg: {
                    let (_, reg) = parse_val_name_desc_u8_registry(&reason_code_node);
                    let reg: Vec<InformationElementSubRegistry> = reg
                        .into_iter()
                        .map(InformationElementSubRegistry::ValueNameDescRegistry)
                        .collect();
                    reg
                },
            }
        })
        .collect();

    (ie_id, ret)
}