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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
//! SREC file parsing and memory layout utilities.
use std::{fs::File, io::BufRead, io::BufReader};
mod record;
pub use record::{Address, Data, Record};
/// Errors which may occur during reading or parsing SREC files.
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Checksum did not match calculated checksum")]
CheckSumError,
#[error("Data length was not as expected")]
DataLengthError,
#[error("Character was unexpected")]
UnexpectedCharacter,
#[error("Can't open srec file")]
SrecFileError,
}
/// Represents a parsed SREC file and its memory layout.
pub struct SRecord<const MAX: u32> {
/// All parsed SREC records.
record: Vec<record::Record>,
/// Memory layout: (start address, region size) for each region.
memory_layout: Vec<(Address, usize)>,
/// Concatenated data bytes from all S1/S2/S3 records.
data: Vec<u8>,
/// Total data length in bytes.
data_length: usize,
}
impl<const MAX: u32> SRecord<MAX> {
/// Returns the header string from the S0 record, if present.
pub fn get_header(&self) -> Option<&str> {
for rec in &self.record {
if let record::Record::S0(header) = rec {
return Some(header);
}
}
None
}
/// Returns the memory layout as a slice of (Address, size) tuples.
pub fn get_memory_layout(&self) -> &[(Address, usize)] {
&self.memory_layout
}
/// Returns the data layout as a vector of (Address, &[u8]) tuples.
pub fn get_data_layout(&self) -> Vec<(Address, &[u8])> {
let mut offset = 0;
self.memory_layout
.iter()
.map(|(addr, size)| {
let data_slice = &self.data[offset..offset + size];
offset += size;
(addr.clone(), data_slice)
})
.collect()
}
/// Returns a slice of all concatenated data bytes.
pub fn get_data(&self) -> &[u8] {
&self.data
}
/// Returns the total data length in bytes.
pub fn get_data_length(&self) -> usize {
self.data_length
}
/// Returns a full binary image from the lowest to highest address, padding 0x00 for unknown data.
pub fn to_full_binary(&self) -> Vec<u8> {
// Find the lowest and highest address
let min_addr = self
.memory_layout
.iter()
.map(|(addr, _)| match addr {
Address::Address16(a) => *a as u32,
Address::Address24(a) => *a,
Address::Address32(a) => *a,
})
.min()
.unwrap();
let max_addr = self
.memory_layout
.iter()
.map(|(addr, size)| match addr {
Address::Address16(a) => *a as u32 + *size as u32,
Address::Address24(a) => *a + *size as u32,
Address::Address32(a) => *a + *size as u32,
})
.max()
.unwrap();
let mut image = vec![0x00; (max_addr - min_addr) as usize];
// Fill in known data
let mut offset = 0;
for (addr, size) in &self.memory_layout {
let start = match addr {
Address::Address16(a) => *a as u32,
Address::Address24(a) => *a,
Address::Address32(a) => *a,
} - min_addr;
let end = start + *size as u32;
image[start as usize..end as usize].copy_from_slice(&self.data[offset..offset + size]);
offset += size;
}
image
}
/// Parse an SREC file and build the memory layout and data.
///
/// - Merges adjacent/overlapping regions up to MAX bytes per region.
/// - Supports S1, S2, S3 records for data.
pub fn from_srec(f: File) -> Result<Self, Error> {
let reader = BufReader::new(f);
let mut records = Vec::new();
let mut regions = Vec::new();
let mut data = Vec::new();
// Parse each line and collect regions and data
for line in reader.lines() {
if let Ok(line) = line {
let rec = record::Record::parse_from_str(&line)?;
match &rec {
record::Record::S1(d) => {
regions.push((d.address as u32, d.data.len()));
data.extend(&d.data);
}
record::Record::S2(d) => {
regions.push((d.address, d.data.len()));
data.extend(&d.data);
}
record::Record::S3(d) => {
regions.push((d.address, d.data.len()));
data.extend(&d.data);
}
_ => {}
}
records.push(rec);
} else {
return Err(Error::SrecFileError);
}
}
// Sort regions by address and merge adjacent/overlapping ones, splitting if MAX is exceeded
regions.sort_by_key(|&(addr, _)| addr);
let mut merged = Vec::new();
for (addr, size) in regions {
if let Some((last_addr, last_size)) = merged.last_mut() {
let last_end = *last_addr + *last_size as u32;
if addr <= last_end && (last_end - *last_addr) < MAX {
// Merge overlapping/adjacent, but do not exceed MAX
let new_end = (addr + size as u32).max(last_end);
let region_size = (new_end - *last_addr) as usize;
if region_size as u32 <= MAX {
*last_size = region_size;
} else {
// Split region if it would exceed MAX
let allowed = (MAX - (*last_size as u32)) as usize;
*last_size = MAX as usize;
merged.push((addr, size - allowed));
}
} else {
merged.push((addr, size));
}
} else {
merged.push((addr, size));
}
}
// Split regions that are larger than MAX
let mut final_regions = Vec::new();
for (mut addr, mut size) in merged {
while size as u32 > MAX {
final_regions.push((Address::Address32(addr), MAX as usize));
addr += MAX;
size -= MAX as usize;
}
if size > 0 {
final_regions.push((Address::Address32(addr), size));
}
}
let data_length = data.len();
Ok(Self {
record: records,
memory_layout: final_regions,
data,
data_length,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
/// Test loading an SREC file and checking the memory layout and data.
#[test]
fn test_from_srec_file() {
let file = File::open("test_data/test.srec").expect("Failed to open test.srec");
let srec = SRecord::<0x8000>::from_srec(file).unwrap();
// Check that records were parsed
assert!(!srec.record.is_empty(), "No records parsed");
// Check that data_memory_layout is not empty and contains reasonable regions
assert!(
!srec.memory_layout.is_empty(),
"No memory layout regions found"
);
// Check that data and data_length are consistent
assert_eq!(srec.data.len(), srec.data_length);
// Optionally, print for debug
for (addr, size) in &srec.memory_layout {
println!("Region: {:?}, size: {:#X}", addr, size);
}
}
}