tramex_tools/
asn1_parser.rs

1//! ASN.1 Text Notation to JSON Parser
2//!
3//! This module provides functionality to parse ASN.1 value notation (text format)
4//! and convert it to JSON for easier manipulation and display.
5
6#![allow(missing_docs)]
7
8use pest::Parser;
9use pest_derive::Parser;
10use serde_json::{Map, Value, json};
11
12/// ASN.1 Parser generated from pest grammar
13#[derive(Parser)]
14#[grammar = "asn1.pest"]
15struct ASN1Parser;
16
17/// Parse ASN.1 text notation and convert to JSON
18///
19/// # Arguments
20/// * `input` - ASN.1 text notation string
21///
22/// # Returns
23/// * `Ok(Value)` - Parsed JSON value
24/// * `Err(String)` - Error message if parsing fails
25///
26/// # Example
27/// ```
28/// use tramex_tools::asn1_parser::parse_asn1_to_json;
29///
30/// let asn1 = r#"{
31///     cellIdentity '001234501'H,
32///     trackingAreaCode '000065'H
33/// }"#;
34///
35/// let json = parse_asn1_to_json(asn1)?;
36/// println!("{}", serde_json::to_string_pretty(&json)?);
37/// # Ok::<(), Box<dyn std::error::Error>>(())
38/// ```
39///
40/// # Errors
41///
42/// Fails on parsing failure
43pub fn parse_asn1_to_json(input: &str) -> Result<Value, String> {
44    let pairs = ASN1Parser::parse(Rule::asn1_value, input).map_err(|e| format!("ASN.1 parse error: {}", e))?;
45
46    for pair in pairs {
47        if let Some(inner_pair) = pair.into_inner().next() {
48            return parse_value(inner_pair);
49        }
50    }
51
52    Err("No value found in ASN.1 input".to_string())
53}
54
55/// Parse a single ASN.1 value into JSON
56///
57/// # Errors
58///
59/// Fails on parsing failure
60fn parse_value(pair: pest::iterators::Pair<Rule>) -> Result<Value, String> {
61    match pair.as_rule() {
62        Rule::sequence => parse_sequence(pair),
63        Rule::choice => parse_choice(pair),
64        Rule::hex_string => Ok(parse_hex_string(pair.as_str())),
65        Rule::bit_string => Ok(parse_bit_string(pair.as_str())),
66        Rule::null_value => Ok(Value::Null),
67        Rule::number => {
68            let num_str = pair.as_str();
69            if let Ok(num) = num_str.parse::<i64>() {
70                Ok(json!(num))
71            } else {
72                Ok(Value::String(num_str.to_string()))
73            }
74        }
75        Rule::identifier => Ok(Value::String(pair.as_str().to_string())),
76        Rule::value => {
77            // Unwrap nested value
78            if let Some(inner) = pair.into_inner().next() {
79                parse_value(inner)
80            } else {
81                Err("Empty value".to_string())
82            }
83        }
84        _ => Err(format!("Unexpected rule: {:?}", pair.as_rule())),
85    }
86}
87
88/// Parse ASN.1 sequence (object with fields or array)
89///
90/// # Errors
91///
92/// Fails if parsing failure
93fn parse_sequence(pair: pest::iterators::Pair<Rule>) -> Result<Value, String> {
94    let mut map = Map::new();
95    let mut array_items = Vec::new();
96    let mut has_named_fields = false;
97    let mut has_bare_values = false;
98
99    for inner in pair.into_inner() {
100        if inner.as_rule() == Rule::element_list {
101            for element_pair in inner.into_inner() {
102                if element_pair.as_rule() == Rule::element {
103                    // Check what's inside the element
104                    let mut element_parts = element_pair.into_inner();
105                    if let Some(first) = element_parts.next() {
106                        match first.as_rule() {
107                            Rule::named_field => {
108                                // This is a named field
109                                has_named_fields = true;
110                                let mut field_parts = first.into_inner();
111                                if let (Some(key_pair), Some(value_pair)) = (field_parts.next(), field_parts.next()) {
112                                    let key = key_pair.as_str().to_string();
113                                    let value = parse_value(value_pair)?;
114                                    map.insert(key, value);
115                                }
116                            }
117                            _ => {
118                                // This is a bare value (array element)
119                                has_bare_values = true;
120                                let value = parse_value(first)?;
121                                array_items.push(value);
122                            }
123                        }
124                    }
125                }
126            }
127        }
128    }
129
130    // Decide if this is an object or array
131    if has_bare_values && !has_named_fields {
132        // Pure array
133        Ok(Value::Array(array_items))
134    } else if has_named_fields {
135        // Object (possibly with some array items mixed in, but we prioritize object)
136        Ok(Value::Object(map))
137    } else {
138        // Empty sequence
139        Ok(Value::Object(Map::new()))
140    }
141}
142
143/// Parse ASN.1 choice (tagged value)
144///
145/// # Errors
146///
147/// Fails on parsing failure
148fn parse_choice(pair: pest::iterators::Pair<Rule>) -> Result<Value, String> {
149    let mut parts = pair.into_inner();
150
151    if let (Some(tag), Some(value)) = (parts.next(), parts.next()) {
152        let mut map = Map::new();
153        map.insert(tag.as_str().to_string(), parse_value(value)?);
154        Ok(Value::Object(map))
155    } else {
156        Err("Invalid choice format".to_string())
157    }
158}
159
160/// Parse hex string and convert to readable format
161fn parse_hex_string(s: &str) -> Value {
162    // Remove quotes and 'H' suffix
163    let hex = s.trim_start_matches('\'').trim_end_matches("'H");
164
165    // Try to convert to decimal if it's a reasonable size
166    if hex.len() <= 16
167        && let Ok(num) = u64::from_str_radix(hex, 16)
168    {
169        return json!({
170            "hex": hex,
171            "decimal": num
172        });
173    }
174
175    // Otherwise just return the hex string
176    Value::String(format!("0x{}", hex))
177}
178
179/// Parse bit string
180fn parse_bit_string(s: &str) -> Value {
181    // Remove quotes and 'B' suffix
182    let bits = s.trim_start_matches('\'').trim_end_matches("'B");
183
184    // Try to convert to decimal if it's a reasonable size
185    if bits.len() <= 64
186        && let Ok(num) = u64::from_str_radix(bits, 2)
187    {
188        return json!({
189            "bits": bits,
190            "decimal": num
191        });
192    }
193
194    // Otherwise just return the bit string
195    Value::String(format!("0b{}", bits))
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_simple_sequence() {
204        let asn1 = r#"{
205            q-RxLevMin -70,
206            q-QualMin -20
207        }"#;
208
209        let result = parse_asn1_to_json(asn1);
210        assert!(result.is_ok());
211
212        let json = result.unwrap();
213        assert_eq!(json["q-RxLevMin"], -70);
214        assert_eq!(json["q-QualMin"], -20);
215    }
216
217    #[test]
218    fn test_hex_string() {
219        let asn1 = r#"{
220            trackingAreaCode '000065'H,
221            cellIdentity '001234501'H
222        }"#;
223
224        let result = parse_asn1_to_json(asn1);
225        assert!(result.is_ok());
226    }
227
228    #[test]
229    fn test_nested_sequence() {
230        let asn1 = r#"{
231            cellSelectionInfo {
232                q-RxLevMin -70,
233                q-QualMin -20
234            }
235        }"#;
236
237        let result = parse_asn1_to_json(asn1);
238        assert!(result.is_ok());
239
240        let json = result.unwrap();
241        assert!(json["cellSelectionInfo"].is_object());
242    }
243
244    #[test]
245    fn test_array() {
246        let asn1 = r#"{
247            {
248                0,
249                0,
250                1
251            }
252        }"#;
253
254        let result = parse_asn1_to_json(asn1);
255        assert!(result.is_ok());
256
257        let json = result.unwrap();
258        assert!(json.is_array());
259    }
260
261    #[test]
262    fn test_choice() {
263        let asn1 = r#"{
264            c1: systemInformationBlockType1: {
265                cellSelectionInfo {
266                    q-RxLevMin -70
267                }
268            }
269        }"#;
270
271        let result = parse_asn1_to_json(asn1);
272        assert!(result.is_ok());
273    }
274
275    #[test]
276    fn test_null_value() {
277        let asn1 = r#"{
278            someField NULL
279        }"#;
280
281        let result = parse_asn1_to_json(asn1);
282        assert!(result.is_ok());
283
284        let json = result.unwrap();
285        assert!(json["someField"].is_null());
286    }
287
288    #[test]
289    fn test_bit_string() {
290        let asn1 = r#"{
291            monitoringSymbolsWithinSlot '10000000000000'B
292        }"#;
293
294        let result = parse_asn1_to_json(asn1);
295        assert!(result.is_ok());
296    }
297}