tramex_tools/interface/parser/
hex_extractor.rs

1//! Hex dump extraction utilities
2
3/// Extract binary data from hex dump lines
4///
5/// Hex dump format:
6/// ```text
7/// 0000:  74 81 01 70 10 40 04 06  00 00 ca 00 24 68 a0 38  t..p.@......$h.8
8/// 0010:  05 21 09 a0 30 00 00 46  4c 6b 6c 61 f3 70 40 20  .!..0..FLkla.p@
9/// ```
10///
11/// # Arguments
12/// * `lines` - All lines of the trace
13///
14/// # Returns
15/// * `Some(Vec<u8>)` - Binary data if complete hex dump found
16/// * `None` - If no hex dump, incomplete dump (contains "..."), or parsing error
17pub fn extract_binary_from_lines(lines: &[String]) -> Option<Vec<u8>> {
18    let mut binary = Vec::new();
19    let mut found_hex_lines = false;
20    let mut last_offset: Option<usize> = None;
21
22    for line in lines {
23        let trimmed = line.trim();
24
25        // Check for incomplete marker (three dots, not just spaces)
26        // The incomplete marker is typically "..." on its own line or "        ..."
27        if trimmed == "..." || trimmed.starts_with("...") {
28            return None;
29        }
30
31        // Check if line contains hex offset pattern (e.g., "0000:", "0010:")
32        // Look for 4 hex digits followed by a colon
33        if let Some(colon_pos) = trimmed.find(':') {
34            // Extract the offset part before colon
35            let before_colon = &trimmed[..colon_pos];
36            let offset_str = before_colon.trim();
37
38            // Check if it's a valid hex offset (exactly 4 hex digits)
39            if offset_str.len() == 4
40                && offset_str.chars().all(|c| c.is_ascii_hexdigit())
41                && let Ok(offset) = usize::from_str_radix(offset_str, 16)
42            {
43                // Verify sequential offsets (should increment by 16)
44                // But allow the first offset to be anything
45                if let Some(last) = last_offset {
46                    // Check if offset increments properly (typically by 16, but could vary)
47                    // Allow some flexibility - just ensure it's increasing
48                    if offset <= last {
49                        // Non-sequential or going backwards, might be incomplete
50                        return None;
51                    }
52                }
53                last_offset = Some(offset);
54
55                found_hex_lines = true;
56
57                // Extract hex bytes after the colon
58                let hex_part = &trimmed[colon_pos + 1..];
59
60                // Split by whitespace and parse hex bytes
61                // Stop when we hit non-hex content (ASCII representation)
62                for part in hex_part.split_whitespace() {
63                    // Each hex byte should be exactly 2 hex characters
64                    if part.len() == 2 && part.chars().all(|c| c.is_ascii_hexdigit()) {
65                        if let Ok(byte) = u8::from_str_radix(part, 16) {
66                            binary.push(byte);
67                        }
68                    } else {
69                        // Hit non-hex content, stop parsing this line
70                        break;
71                    }
72                }
73            }
74        }
75    }
76
77    if found_hex_lines && !binary.is_empty() {
78        Some(binary)
79    } else {
80        None
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_extract_complete_hex() {
90        let lines = vec![
91            "13:21:07.077 [RRC] DL    - 01 BCCH-NR: SIB2".to_string(),
92            "        0000:  74 81 01 70 10 40 04 06  00 00 ca 00 24 68 a0 38  t..p.@......$h.8".to_string(),
93            "        0010:  05 21 09 a0 30 00 00 46  4c 6b 6c 61 f3 70 40 20  .!..0..FLkla.p@ ".to_string(),
94        ];
95
96        let binary = extract_binary_from_lines(&lines);
97        assert!(binary.is_some(), "Binary extraction failed");
98        let bytes = binary.unwrap();
99        assert_eq!(bytes.len(), 32, "Expected 32 bytes, got {}", bytes.len());
100        assert_eq!(bytes[0], 0x74);
101        assert_eq!(bytes[1], 0x81);
102        assert_eq!(bytes[16], 0x05);
103    }
104
105    #[test]
106    fn test_extract_incomplete_hex() {
107        let lines = vec![
108            "13:20:45.574 [GTPU] TO 127.0.1.100:2152 G-PDU".to_string(),
109            "        0000:  34 ff 05 b4 53 15 ca a3  00 00 00 85 01 10 01 00  4...S...........".to_string(),
110            "        ...".to_string(),
111        ];
112
113        let binary = extract_binary_from_lines(&lines);
114        assert!(binary.is_none());
115    }
116
117    #[test]
118    fn test_no_hex_dump() {
119        let lines = vec![
120            "13:20:45.575 [PHY] UL 003d 01 4644   729.9 PUSCH".to_string(),
121            "        Link: re@0".to_string(),
122        ];
123
124        let binary = extract_binary_from_lines(&lines);
125        assert!(binary.is_none());
126    }
127}