tramex_tools/interface/parser/
parser_ngap.rs1use 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)]
13pub struct NGAPInfos {
15 pub direction: Direction,
18
19 pub message_type: String,
21
22 pub connection_info: Option<String>,
24}
25
26pub 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 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 let connection_info = if parts.len() > 5 && parts[5].contains(':') {
56 Some(parts[5].to_string())
57 } else {
58 None
59 };
60
61 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 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
106impl LayerParser for NGAPParser {
107 fn parse_layer(header: &ParsedHeader, data_lines: &[String]) -> Result<AdditionalInfos, ParsingError> {
110 if data_lines.is_empty() {
111 return Err(ParsingError::new("NGAP: empty data lines".to_string(), 0));
112 }
113 let first_line = &data_lines[0];
114 let parts: Vec<&str> = first_line.split_whitespace().collect();
115
116 let mut connection_info = header.connection_info.clone();
118 let mut message_start_idx = 0;
119
120 for (i, part) in parts.iter().enumerate() {
122 if part.contains(':') && part.contains('.') {
123 connection_info = Some(part.to_string());
124 message_start_idx = i + 1;
125 break;
126 }
127 }
128
129 let message_type = if parts.len() > message_start_idx && message_start_idx > 0 {
130 parts[message_start_idx..].join(" ")
131 } else if !parts.is_empty() {
132 parts.join(" ")
133 } else {
134 "Unknown".to_string()
135 };
136
137 Ok(AdditionalInfos::NGAPInfos(NGAPInfos {
138 direction: header.direction.clone(),
139 message_type,
140 connection_info,
141 }))
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn test_parse_ngap_line() {
151 let line = "13:20:58.310 [NGAP] TO 0080 003d 127.0.1.100:38412 UE context release complete";
152 let lines = vec![line.to_string()];
153
154 let result = NGAPParser::parse_additional_infos(&lines);
155 assert!(result.is_ok());
156
157 match result.unwrap() {
158 AdditionalInfos::NGAPInfos(infos) => {
159 assert_eq!(infos.direction, Direction::TO);
160 assert_eq!(infos.message_type, "UE context release complete");
161 assert_eq!(infos.connection_info, Some("127.0.1.100:38412".to_string()));
162 }
163 _ => panic!("Expected NGAPInfos"),
164 }
165 }
166
167 #[test]
168 fn test_parse_ngap_from() {
169 let line = "13:20:58.310 [NGAP] FROM 0080 003d 127.0.1.100:38412 Downlink NAS transport";
170 let lines = vec![line.to_string()];
171
172 let result = NGAPParser::parse_additional_infos(&lines);
173 assert!(result.is_ok());
174
175 match result.unwrap() {
176 AdditionalInfos::NGAPInfos(infos) => {
177 assert_eq!(infos.direction, Direction::FROM);
178 assert_eq!(infos.message_type, "Downlink NAS transport");
179 }
180 _ => panic!("Expected NGAPInfos"),
181 }
182 }
183
184 #[test]
185 fn test_parse_ngap_no_connection_info() {
186 let line = "13:20:58.310 [NGAP] TO 0080 003d UE context release complete";
187 let lines = vec![line.to_string()];
188
189 let result = NGAPParser::parse_additional_infos(&lines);
190 assert!(result.is_ok());
191
192 match result.unwrap() {
193 AdditionalInfos::NGAPInfos(infos) => {
194 assert_eq!(infos.connection_info, None);
195 assert_eq!(infos.message_type, "UE context release complete");
196 }
197 _ => panic!("Expected NGAPInfos"),
198 }
199 }
200
201 #[test]
202 fn test_parse_ngap_invalid_direction() {
203 let line = "13:20:58.310 [NGAP] INVALID 0080 003d 127.0.1.100:38412 UE context release complete";
204 let lines = vec![line.to_string()];
205
206 let result = NGAPParser::parse_additional_infos(&lines);
207 assert!(result.is_err());
208 }
209}