tramex_tools/
data.rs

1//! This module contains the data structures used to store the data of the application.
2use crate::asn1_parser::parse_asn1_to_json;
3use crate::interface::association::{AssociationRules, AssociationStatus, TraceMatcher, TraceRelation};
4use crate::interface::{
5    layer::Layer,
6    parse_config::FileMetadata,
7    parser::{
8        parser_gtpu::GTPUInfos, parser_nas::NASInfos, parser_ngap::NGAPInfos, parser_phy::PHYInfos, parser_rrc::RRCInfos,
9    },
10    types::Direction,
11};
12use core::fmt::Debug;
13
14#[derive(Debug)]
15/// Data structure to store Trace of the application.
16pub struct Data {
17    /// Vector of Trace.
18    pub events: Vec<Trace>,
19    /// Current index of the vector.
20    pub current_index: usize,
21    /// File metadata (connection type, version, etc.)
22    pub metadata: FileMetadata,
23}
24
25impl Data {
26    /// return the current trace
27    pub fn get_current_trace(&self) -> Option<&Trace> {
28        self.events.get(self.current_index)
29    }
30
31    /// return if the index is different from the current index
32    pub fn is_different_index(&self, index: usize) -> bool {
33        if index == 0 {
34            return true;
35        }
36        self.current_index != index
37    }
38
39    /// clear the data
40    pub fn clear(&mut self) {
41        self.events.clear();
42        self.current_index = 0;
43        self.metadata = FileMetadata::default();
44    }
45
46    /// Compute parent association for a trace at the given index using lazy evaluation.
47    /// If already computed, returns the cached result. Otherwise, computes and caches it.
48    ///
49    /// # Arguments
50    /// * `index` - Index of the trace to compute parent for
51    /// * `rules` - The association rules to use for matching
52    ///
53    /// # Returns
54    /// * The parent index if found, None otherwise
55    pub fn compute_parent(&mut self, index: usize, rules: &AssociationRules) -> Option<usize> {
56        // Check if already computed
57        if let Some(trace) = self.events.get(index)
58            && trace.relation.parent.is_computed()
59        {
60            return trace.relation.get_parent_index();
61        }
62
63        // Get the trace's layer to find applicable rules
64        let layer = match self.events.get(index) {
65            Some(t) => t.layer.clone(),
66            None => return None,
67        };
68
69        // Try each applicable rule
70        let applicable_rules = rules.rules_for_layer(&layer);
71        for rule in applicable_rules {
72            let status = TraceMatcher::find_relative(index, &self.events, rule, &layer, &rule.target_layer());
73
74            // Update the trace's relation
75            // If we found a parent, set the relations
76            if let AssociationStatus::Found(parent_indices) = status {
77                // Update source trace with all found parents
78                if let Some(trace) = self.events.get_mut(index) {
79                    for &parent_idx in &parent_indices {
80                        trace.relation.add_parent(parent_idx);
81                    }
82                }
83                // Set child relation on each parent
84                for &parent_idx in &parent_indices {
85                    if let Some(parent_trace) = self.events.get_mut(parent_idx) {
86                        parent_trace.relation.add_child(index);
87                    }
88                }
89                // Return first parent for backwards compatibility
90                return parent_indices.first().copied();
91            }
92        }
93
94        // Mark as not found if no rules matched
95        if let Some(trace) = self.events.get_mut(index)
96            && !trace.relation.parent.is_computed()
97        {
98            trace.relation.set_parent_not_applicable();
99        }
100
101        None
102    }
103
104    /// Compute all associations for all traces in the events vector.
105    /// This applies all rules to each trace automatically:
106    /// - For each trace, finds rules where the trace's layer is the source layer
107    /// - Computes the relationship and updates both parent (on source) and child (on target)
108    ///
109    /// After calling this method, you can use `trace.relation.get_parent_index()` or
110    /// `trace.relation.get_child_index()` to get related traces.
111    pub fn compute_all_associations(&mut self, rules: &AssociationRules) {
112        crate::interface::association::compute_associations(&mut self.events, rules, 0);
113    }
114
115    /// Get the parent trace for a trace at the given index.
116    /// This will compute the association if not already done.
117    ///
118    /// # Arguments
119    /// * `index` - Index of the trace
120    /// * `rules` - The association rules to use
121    ///
122    /// # Returns
123    /// * Reference to the parent trace if found
124    pub fn get_parent_trace(&mut self, index: usize, rules: &AssociationRules) -> Option<&Trace> {
125        let parent_idx = self.compute_parent(index, rules)?;
126        self.events.get(parent_idx)
127    }
128
129    /// Get the child trace for a trace at the given index.
130    /// Note: child relation is set when the child's parent is computed.
131    ///
132    /// # Arguments
133    /// * `index` - Index of the trace
134    ///
135    /// # Returns
136    /// * Reference to the child trace if found
137    pub fn get_child_trace(&self, index: usize) -> Option<&Trace> {
138        let trace = self.events.get(index)?;
139        let child_idx = trace.relation.get_child_index()?;
140        self.events.get(child_idx)
141    }
142
143    /// Invalidate all associations (useful when new traces are added)
144    pub fn invalidate_associations(&mut self) {
145        for trace in &mut self.events {
146            trace.relation.invalidate();
147        }
148    }
149
150    /// Invalidate associations in a range around newly added traces
151    /// This is more efficient than invalidating all associations
152    ///
153    /// # Arguments
154    /// * `start_index` - Start of the range where new traces were added
155    /// * `window` - Window size to invalidate around the new traces
156    pub fn invalidate_associations_in_range(&mut self, start_index: usize, window: usize) {
157        let start = start_index.saturating_sub(window);
158        let end = (start_index + window).min(self.events.len());
159
160        for trace in &mut self.events[start..end] {
161            trace.relation.invalidate();
162        }
163    }
164}
165
166impl Default for Data {
167    fn default() -> Self {
168        let default_data_size = 2048;
169        Self {
170            events: Vec::with_capacity(default_data_size),
171            current_index: 0,
172            metadata: FileMetadata::default(),
173        }
174    }
175}
176
177#[derive(Debug, Clone)]
178/// Data structure to store Trace of the application.
179pub struct Trace {
180    /// Timestamp of the message.
181    pub timestamp: i64,
182
183    /// Layer of the message.
184    pub layer: Layer,
185
186    /// Additional layer-specific information.
187    pub additional_infos: AdditionalInfos,
188
189    /// Text representation of the message from the API
190    pub text: Option<Vec<String>>,
191
192    /// Binary representation extracted from hex dump (if present and complete)
193    pub binary: Option<Vec<u8>>,
194
195    /// Parent/child relationship with other traces
196    pub relation: TraceRelation,
197}
198
199impl Trace {
200    /// Parse ASN.1 text from RRC messages and return as JSON
201    ///
202    /// # Returns
203    /// * `Some(Value)` - Parsed JSON if the trace has ASN.1 text and is an RRC layer
204    /// * `None` - If no text available or not an RRC layer
205    ///
206    /// # Errors
207    /// Logs error if parsing fails but returns None
208    pub fn parse_asn1_to_json(&self) -> Option<serde_json::Value> {
209        // Only parse RRC layers
210        if !matches!(self.layer, Layer::RRC) {
211            return None;
212        }
213
214        // Check if we have text to parse
215        let text = self.text.as_ref()?;
216
217        // Find the start of ASN.1 structure (first line starting with '{')
218        // Skip header lines and hex dump
219        let asn1_lines: Vec<&String> = text
220            .iter()
221            .skip_while(|line| {
222                let trimmed = line.trim();
223                // Skip until we find a line that starts with '{'
224                !trimmed.starts_with('{')
225            })
226            .collect();
227
228        if asn1_lines.is_empty() {
229            log::debug!("No ASN.1 structure found in text");
230            return None;
231        }
232
233        // Join the ASN.1 lines
234        let asn1_text: String = asn1_lines.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\n");
235
236        // Parse ASN.1 to JSON
237        match parse_asn1_to_json(&asn1_text) {
238            Ok(json) => {
239                // log::debug!("Parsed ASN.1 to JSON: {}", serde_json::to_string_pretty(&json).unwrap_or_default());
240                Some(json)
241            }
242            Err(e) => {
243                log::warn!("Failed to parse ASN.1: {}", e);
244                None
245            }
246        }
247    }
248}
249
250/// Data structure to store custom messages (from the amarisoft API)
251#[derive(Debug, Clone)]
252pub enum AdditionalInfos {
253    /// RRC message
254    RRCInfos(RRCInfos),
255    /// NAS message
256    NASInfos(NASInfos),
257    /// NGAP message
258    NGAPInfos(NGAPInfos),
259    /// GTPU message
260    GTPUInfos(GTPUInfos),
261    /// PHY layer message (PDSCH/PUSCH)
262    PHYInfos(PHYInfos),
263    /// No additional info (for simple log entries like MAC, RLC, etc.)
264    None,
265}
266
267impl AdditionalInfos {
268    /// Get direction from additional infos
269    pub fn get_direction(&self) -> Option<Direction> {
270        match self {
271            AdditionalInfos::RRCInfos(info) => Some(info.direction.clone()),
272            AdditionalInfos::NASInfos(info) => Some(info.direction.clone()),
273            AdditionalInfos::NGAPInfos(info) => Some(info.direction.clone()),
274            AdditionalInfos::GTPUInfos(info) => Some(info.direction.clone()),
275            AdditionalInfos::PHYInfos(info) => Some(info.direction.clone()),
276            AdditionalInfos::None => None,
277        }
278    }
279
280    /// Get message name from additional infos
281    pub fn get_message_name(&self) -> Option<String> {
282        match self {
283            AdditionalInfos::RRCInfos(info) => Some(info.canal_msg.clone()),
284            AdditionalInfos::NASInfos(info) => Some(info.message_type.clone()),
285            AdditionalInfos::NGAPInfos(info) => Some(info.message_type.clone()),
286            AdditionalInfos::GTPUInfos(info) => Some(info.message_type.clone()),
287            AdditionalInfos::PHYInfos(info) => Some(format!("{:?}", info.channel_type)),
288            AdditionalInfos::None => None,
289        }
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296    use crate::interface::parser::parser_nas::NASInfos;
297    use crate::interface::parser::parser_ngap::NGAPInfos;
298    use crate::interface::parser::parser_rrc::RRCInfos;
299
300    #[test]
301    fn test_additional_infos_get_direction() {
302        // Test RRCInfos
303        let rrc_infos = RRCInfos {
304            direction: Direction::DL,
305            canal: "BCCH".to_string(),
306            canal_msg: "SIB".to_string(),
307        };
308        let rrc_additional = AdditionalInfos::RRCInfos(rrc_infos);
309        assert_eq!(rrc_additional.get_direction(), Some(Direction::DL));
310
311        // Test NASInfos
312        let nas_infos = NASInfos {
313            direction: Direction::UL,
314            message_type: "Registration request".to_string(),
315        };
316        let nas_additional = AdditionalInfos::NASInfos(nas_infos);
317        assert_eq!(nas_additional.get_direction(), Some(Direction::UL));
318
319        // Test NGAPInfos
320        let ngap_infos = NGAPInfos {
321            direction: Direction::DL,
322            message_type: "Downlink NAS transport".to_string(),
323            connection_info: Some("127.0.1.100:38412".to_string()),
324        };
325        let ngap_additional = AdditionalInfos::NGAPInfos(ngap_infos);
326        assert_eq!(ngap_additional.get_direction(), Some(Direction::DL));
327
328        // Test None
329        let none_additional = AdditionalInfos::None;
330        assert_eq!(none_additional.get_direction(), None);
331    }
332
333    #[test]
334    fn test_additional_infos_get_message_name() {
335        // Test RRCInfos
336        let rrc_infos = RRCInfos {
337            direction: Direction::DL,
338            canal: "BCCH".to_string(),
339            canal_msg: "SIB".to_string(),
340        };
341        let rrc_additional = AdditionalInfos::RRCInfos(rrc_infos);
342        assert_eq!(rrc_additional.get_message_name(), Some("SIB".to_string()));
343
344        // Test NASInfos
345        let nas_infos = NASInfos {
346            direction: Direction::UL,
347            message_type: "Registration request".to_string(),
348        };
349        let nas_additional = AdditionalInfos::NASInfos(nas_infos);
350        assert_eq!(nas_additional.get_message_name(), Some("Registration request".to_string()));
351
352        // Test NGAPInfos
353        let ngap_infos = NGAPInfos {
354            direction: Direction::DL,
355            message_type: "Downlink NAS transport".to_string(),
356            connection_info: Some("127.0.1.100:38412".to_string()),
357        };
358        let ngap_additional = AdditionalInfos::NGAPInfos(ngap_infos);
359        assert_eq!(ngap_additional.get_message_name(), Some("Downlink NAS transport".to_string()));
360
361        // Test None
362        let none_additional = AdditionalInfos::None;
363        assert_eq!(none_additional.get_message_name(), None);
364    }
365}