1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! Section scanner for TPX3 files.
//!
//! Identifies logical sections in the file based on TPX3 headers.
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
/// A section of the TPX3 file belonging to a specific chip.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Section {
/// Start offset in bytes (inclusive).
pub start_offset: usize,
/// End offset in bytes (exclusive).
pub end_offset: usize,
/// Chip ID for this section.
pub chip_id: u8,
}
/// Scanner for discovering sections in TPX3 data.
pub struct PacketScanner;
impl PacketScanner {
/// Scans the provided data for TPX3 sections.
///
/// The data should be 8-byte aligned.
///
/// # Arguments
/// * `data` - The byte slice to scan.
/// * `is_eof` - Whether this is the final chunk of data.
///
/// # Returns
/// A tuple `(sections, consumed_bytes)`.
/// `consumed_bytes` indicates how many bytes can be safely advanced.
/// Bytes after `consumed_bytes` belong to an incomplete section (unless `is_eof`).
///
/// # Panics
/// Panics if a chunk is not exactly 8 bytes. This should be unreachable because
/// `chunks_exact(8)` guarantees each chunk length.
#[must_use]
pub fn scan_sections(data: &[u8], is_eof: bool) -> (Vec<Section>, usize) {
let mut sections = Vec::new();
let mut current_section_start = 0;
let mut current_chip_id = 0;
let mut in_section = false;
// Track the end of the last fully completed section
let mut consumed_bytes = 0;
// Iterate over data in 8-byte chunks
for (offset, chunk) in data.chunks_exact(8).enumerate() {
let offset_bytes = offset * 8;
let packet = u64::from_le_bytes(chunk.try_into().unwrap());
// Check if this is a TPX3 header: magic "TPX3" (0x33585054) in lower 32 bits
if (packet & 0xFFFF_FFFF) == 0x3358_5054 {
let chip_id = ((packet >> 32) & 0xFF) as u8;
if in_section {
// Close previous section
sections.push(Section {
start_offset: current_section_start,
end_offset: offset_bytes,
chip_id: current_chip_id,
});
// Previous section ended here, so we have consumed up to here
consumed_bytes = offset_bytes;
}
// Start new section
current_section_start = offset_bytes;
current_chip_id = chip_id;
in_section = true;
}
}
if is_eof {
// fast forward consumed to end
consumed_bytes = data.len();
if in_section && data.len() > current_section_start {
sections.push(Section {
start_offset: current_section_start,
end_offset: data.len(),
chip_id: current_chip_id,
});
}
} else {
// If we are not at EOF, and we are inside a section,
// that section is incomplete.
// Special case: If we haven't found ANY closed sections (consumed_bytes == 0)
// but we found a start header?
// Then we return 0 consumed, meaning "need more data".
// If the buffer is huge and we still return 0, the section is huge.
}
(sections, consumed_bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reader::MappedFileReader;
use std::path::PathBuf;
#[test]
fn test_scan_tiny_tpx3() {
let path = PathBuf::from("../tests/data/tiny.tpx3");
if !path.exists() {
eprintln!("Skipping test: tiny.tpx3 not found");
return;
}
let reader = MappedFileReader::open(path.to_str().unwrap()).unwrap();
let (sections, consumed) = PacketScanner::scan_sections(reader.as_bytes(), true);
let sections_len = sections.len();
println!("Found {sections_len} sections");
println!("Consumed {consumed} bytes");
assert_eq!(consumed, reader.len()); // Should consume everything at EOF
for (i, section) in sections.iter().enumerate() {
let chip_id = section.chip_id;
let byte_len = section.end_offset - section.start_offset;
println!("Section {i}: Chip {chip_id}, {byte_len} bytes");
}
assert!(!sections.is_empty(), "Should find at least one section");
// Verify contiguous sections (optional, but good sanity check)
for i in 0..sections.len() - 1 {
assert_eq!(
sections[i].end_offset,
sections[i + 1].start_offset,
"Sections should be contiguous"
);
}
}
}