tramex_tools/interface/association/
mod.rs

1//! Association module for parent/child trace relationships
2//!
3//! This module provides the logic for associating traces that are logically related
4//! (e.g., NAS messages encapsulated in RRC messages).
5
6mod matcher;
7mod relation;
8mod rules;
9
10pub use matcher::TraceMatcher;
11pub use relation::{AssociationStatus, TraceRelation};
12pub use rules::{AssociationRule, AssociationRules, NasToRrcRule, SearchDirection};
13
14use crate::data::Trace;
15
16/// Default lookback window for cross-batch association computation
17pub const ASSOCIATION_LOOKBACK_WINDOW: usize = 10;
18
19/// Compute associations for traces starting from a given index.
20/// This is a standalone function that can be used by any code that has a `&mut [Trace]`.
21///
22/// # Arguments
23/// * `events` - Mutable slice of traces to compute associations for
24/// * `rules` - The association rules to use for matching
25/// * `start_index` - Index to start computing from (useful for incremental batch processing)
26///
27/// # Note
28/// When processing new batches, pass `start_index = max(0, new_batch_start - LOOKBACK_WINDOW)`
29/// to handle cases where parent/child relationships span across batches.
30pub fn compute_associations(events: &mut [Trace], rules: &AssociationRules, start_index: usize) {
31    let trace_count = events.len();
32
33    for index in start_index..trace_count {
34        // Check if we're in the lookback window for recomputation
35        let in_lookback = index < start_index + ASSOCIATION_LOOKBACK_WINDOW;
36
37        // Reset relation if we're recomputing in lookback window
38        if in_lookback &&
39             let Some(trace) = events.get_mut(index) &&
40                // Only reset if it was NotFound (might find match in new batch)
41                matches!(trace.relation.parent, AssociationStatus::NotFound)
42        {
43            trace.relation.parent = AssociationStatus::NotComputed;
44        }
45
46        // Get the trace's layer
47        let layer = match events.get(index) {
48            Some(t) => t.layer.clone(),
49            None => continue,
50        };
51
52        // Find applicable rules for this layer
53        let applicable_rules = rules.rules_for_layer(&layer);
54
55        if applicable_rules.is_empty() {
56            // No rules for this layer - mark as not applicable
57            if let Some(trace) = events.get_mut(index) {
58                trace.relation.set_parent_not_applicable();
59            }
60            continue;
61        }
62
63        // Try each applicable rule
64        let mut found = false;
65        for rule in applicable_rules {
66            let status = TraceMatcher::find_relative(index, events, rule, &layer, &rule.target_layer());
67
68            // If found, set parent/child based on rule's direction
69            if let AssociationStatus::Found(target_indices) = status {
70                for &target_idx in &target_indices {
71                    if rule.source_is_child() {
72                        // Source is child, target is parent (e.g., NAS→RRC)
73                        if let Some(trace) = events.get_mut(index) {
74                            trace.relation.add_parent(target_idx);
75                        }
76                        if let Some(target_trace) = events.get_mut(target_idx) {
77                            target_trace.relation.add_child(index);
78                        }
79                    } else {
80                        // Source is parent, target is child (e.g., NGAP→NAS)
81                        if let Some(trace) = events.get_mut(index) {
82                            trace.relation.add_child(target_idx);
83                        }
84                        if let Some(target_trace) = events.get_mut(target_idx) {
85                            target_trace.relation.add_parent(index);
86                        }
87                    }
88                }
89                found = true;
90                // Don't break - continue to run other rules for additional associations
91            }
92        }
93
94        // Mark as not found if no rules matched
95        if !found
96            && let Some(trace) = events.get_mut(index)
97            && !trace.relation.parent.is_computed()
98        {
99            trace.relation.parent = AssociationStatus::NotFound;
100        }
101    }
102}