tramex_tools/interface/parser/
parser_nas.rs

1//! Parser for NAS traces
2use super::ParsingError;
3use super::hex_extractor::extract_binary_from_lines;
4use crate::data::{AdditionalInfos, Trace};
5use crate::interface::association::TraceRelation;
6use std::str::FromStr;
7
8use crate::interface::{layer::Layer, types::Direction};
9
10use super::{FileParser, LayerParser, ParsedHeader};
11
12#[derive(Debug, Clone)]
13/// Data structure to store the NAS message type
14pub struct NASInfos {
15    /// Direction of the message (UL/DL)
16    pub direction: Direction,
17
18    /// Message type (e.g., "Service request", "Authentication response")
19    pub message_type: String,
20}
21
22/// NAS Parser
23pub struct NASParser;
24
25impl NASParser {
26    /// Parse lines
27    fn parse_lines(lines: &[String]) -> Vec<String> {
28        lines.to_vec()
29    }
30}
31
32impl FileParser for NASParser {
33    fn parse_additional_infos(lines: &[String]) -> Result<AdditionalInfos, ParsingError> {
34        let line = &lines[0];
35        let parts: Vec<&str> = line.split_whitespace().collect();
36
37        // Example: "13:20:58.310 [NAS] UL 0048 5GMM: Service request"
38        // Parts: ["13:20:58.310", "[NAS]", "UL", "0048", "5GMM:", "Service", "request"]
39        if parts.len() < 5 {
40            return Err(ParsingError::new(
41                "Could not find enough (5) parameters for NAS".to_string(),
42                1,
43            ));
44        }
45
46        let direction_result = Direction::from_str(parts[2]);
47        let direction = match direction_result {
48            Ok(d) => d,
49            Err(_) => {
50                return Err(ParsingError::new(
51                    format!("The direction could not be parsed in the part {:?} of {}", parts[2], line),
52                    1,
53                ));
54            }
55        };
56
57        // Message type is everything after the protocol identifier (5GMM:, etc.)
58        let message_type = if parts.len() > 5 {
59            parts[5..].join(" ")
60        } else {
61            parts.get(4).unwrap_or(&"Unknown").trim_end_matches(':').to_string()
62        };
63
64        Ok(AdditionalInfos::NASInfos(NASInfos { direction, message_type }))
65    }
66
67    fn parse(lines: &[String]) -> Result<Trace, ParsingError> {
68        let additional_infos = match Self::parse_additional_infos(lines) {
69            Ok(m) => m,
70            Err(e) => {
71                return Err(e);
72            }
73        };
74
75        let text = Self::parse_lines(lines);
76
77        let binary = extract_binary_from_lines(lines);
78
79        // Parse timestamp from first line
80        let timestamp = if let Some(first_line) = lines.first() {
81            super::parse_timestamp(first_line)?
82        } else {
83            0
84        };
85
86        let trace = Trace {
87            timestamp,
88            layer: Layer::NAS,
89            additional_infos,
90            text: Some(text),
91            binary,
92            relation: TraceRelation::default(),
93        };
94        Ok(trace)
95    }
96}
97
98impl LayerParser for NASParser {
99    /// Parse NAS payload lines.
100    /// data_lines[0] should be "PROTO: message" (e.g. "5GMM: Service request")
101    fn parse_layer(header: &ParsedHeader, data_lines: &[String]) -> Result<AdditionalInfos, ParsingError> {
102        let message_type = if data_lines.is_empty() {
103            "Unknown".to_string()
104        } else {
105            let first_line = &data_lines[0];
106            if let Some(colon_pos) = first_line.find(':') {
107                first_line[colon_pos + 1..].trim().to_string()
108            } else {
109                first_line.trim().to_string()
110            }
111        };
112        Ok(AdditionalInfos::NASInfos(NASInfos {
113            direction: header.direction.clone(),
114            message_type,
115        }))
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_parse_nas_line() {
125        let line = "13:20:58.310 [NAS] UL 0048 5GMM: Service request";
126        let lines = vec![line.to_string()];
127
128        let result = NASParser::parse_additional_infos(&lines);
129        assert!(result.is_ok());
130
131        match result.unwrap() {
132            AdditionalInfos::NASInfos(infos) => {
133                assert_eq!(infos.direction, Direction::UL);
134                assert_eq!(infos.message_type, "Service request");
135            }
136            _ => panic!("Expected NASInfos"),
137        }
138    }
139
140    #[test]
141    fn test_parse_nas_dl() {
142        let line = "13:20:58.310 [NAS] DL 0048 5GMM: Registration accept";
143        let lines = vec![line.to_string()];
144
145        let result = NASParser::parse_additional_infos(&lines);
146        assert!(result.is_ok());
147
148        match result.unwrap() {
149            AdditionalInfos::NASInfos(infos) => {
150                assert_eq!(infos.direction, Direction::DL);
151                assert_eq!(infos.message_type, "Registration accept");
152            }
153            _ => panic!("Expected NASInfos"),
154        }
155    }
156
157    #[test]
158    fn test_parse_nas_invalid_direction() {
159        let line = "13:20:58.310 [NAS] INVALID 0048 5GMM: Service request";
160        let lines = vec![line.to_string()];
161
162        let result = NASParser::parse_additional_infos(&lines);
163        assert!(result.is_err());
164    }
165}