tramex_tools/interface/parser/
parser_ngap.rs

1//! Parser for NGAP 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;
11
12#[derive(Debug, Clone)]
13/// Data structure to store the NGAP message type
14pub struct NGAPInfos {
15    /// Direction of the message (TO/FROM)
16    /// Check direction NGAP: Currently TO = BST -> CN, FROM = CN -> BST
17    pub direction: Direction,
18
19    /// Message type (e.g., "UE context release complete", "Initial context setup request")
20    pub message_type: String,
21
22    /// Connection info (e.g., "127.0.1.100:38412")
23    pub connection_info: Option<String>,
24}
25
26/// NGAP Parser
27pub struct NGAPParser;
28
29impl FileParser for NGAPParser {
30    fn parse_additional_infos(lines: &[String]) -> Result<AdditionalInfos, ParsingError> {
31        let line = &lines[0];
32        let parts: Vec<&str> = line.split_whitespace().collect();
33
34        // Example: "13:20:58.310 [NGAP] TO 0080 003d 127.0.1.100:38412 UE context release complete"
35        // Parts: ["13:20:58.310", "[NGAP]", "TO", "0080", "003d", "127.0.1.100:38412", "UE", "context", "release", "complete"]
36        if parts.len() < 6 {
37            return Err(ParsingError::new(
38                "Could not find enough (6) parameters for NGAP".to_string(),
39                1,
40            ));
41        }
42
43        let direction_result = Direction::from_str(parts[2]);
44        let direction = match direction_result {
45            Ok(d) => d,
46            Err(_) => {
47                return Err(ParsingError::new(
48                    format!("The direction could not be parsed in the part {:?} of {}", parts[2], line),
49                    1,
50                ));
51            }
52        };
53
54        // Connection info is typically at parts[5] (IP:port)
55        let connection_info = if parts.len() > 5 && parts[5].contains(':') {
56            Some(parts[5].to_string())
57        } else {
58            None
59        };
60
61        // Message type is everything after connection info
62        let message_start_idx = if connection_info.is_some() { 6 } else { 5 };
63        let message_type = if parts.len() > message_start_idx {
64            parts[message_start_idx..].join(" ")
65        } else {
66            "Unknown".to_string()
67        };
68
69        Ok(AdditionalInfos::NGAPInfos(NGAPInfos {
70            direction,
71            message_type,
72            connection_info,
73        }))
74    }
75
76    fn parse(lines: &[String]) -> Result<Trace, ParsingError> {
77        let additional_infos = match Self::parse_additional_infos(lines) {
78            Ok(m) => m,
79            Err(e) => {
80                return Err(e);
81            }
82        };
83
84        let text = lines.to_vec();
85        let binary = extract_binary_from_lines(lines);
86
87        // Parse timestamp from first line
88        let timestamp = if let Some(first_line) = lines.first() {
89            super::parse_timestamp(first_line)?
90        } else {
91            0
92        };
93
94        let trace = Trace {
95            timestamp,
96            layer: Layer::NGAP,
97            additional_infos,
98            text: Some(text),
99            binary,
100            relation: TraceRelation::default(),
101        };
102        Ok(trace)
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109
110    #[test]
111    fn test_parse_ngap_line() {
112        let line = "13:20:58.310 [NGAP] TO 0080 003d 127.0.1.100:38412 UE context release complete";
113        let lines = vec![line.to_string()];
114
115        let result = NGAPParser::parse_additional_infos(&lines);
116        assert!(result.is_ok());
117
118        match result.unwrap() {
119            AdditionalInfos::NGAPInfos(infos) => {
120                assert_eq!(infos.direction, Direction::TO);
121                assert_eq!(infos.message_type, "UE context release complete");
122                assert_eq!(infos.connection_info, Some("127.0.1.100:38412".to_string()));
123            }
124            _ => panic!("Expected NGAPInfos"),
125        }
126    }
127
128    #[test]
129    fn test_parse_ngap_from() {
130        let line = "13:20:58.310 [NGAP] FROM 0080 003d 127.0.1.100:38412 Downlink NAS transport";
131        let lines = vec![line.to_string()];
132
133        let result = NGAPParser::parse_additional_infos(&lines);
134        assert!(result.is_ok());
135
136        match result.unwrap() {
137            AdditionalInfos::NGAPInfos(infos) => {
138                assert_eq!(infos.direction, Direction::FROM);
139                assert_eq!(infos.message_type, "Downlink NAS transport");
140            }
141            _ => panic!("Expected NGAPInfos"),
142        }
143    }
144
145    #[test]
146    fn test_parse_ngap_no_connection_info() {
147        let line = "13:20:58.310 [NGAP] TO 0080 003d UE context release complete";
148        let lines = vec![line.to_string()];
149
150        let result = NGAPParser::parse_additional_infos(&lines);
151        assert!(result.is_ok());
152
153        match result.unwrap() {
154            AdditionalInfos::NGAPInfos(infos) => {
155                assert_eq!(infos.connection_info, None);
156                assert_eq!(infos.message_type, "UE context release complete");
157            }
158            _ => panic!("Expected NGAPInfos"),
159        }
160    }
161
162    #[test]
163    fn test_parse_ngap_invalid_direction() {
164        let line = "13:20:58.310 [NGAP] INVALID 0080 003d 127.0.1.100:38412 UE context release complete";
165        let lines = vec![line.to_string()];
166
167        let result = NGAPParser::parse_additional_infos(&lines);
168        assert!(result.is_err());
169    }
170}