1pub mod args;
2pub mod checksum;
3pub mod errors;
4
5use crate::layout::header::{CrcLocation, Header};
6use crate::layout::settings::{CrcArea, Endianness, Settings};
7use crate::output::args::OutputFormat;
8use errors::OutputError;
9
10use bin_file::{BinFile, IHexFormat};
11
12#[derive(Debug, Clone)]
13pub struct DataRange {
14 pub start_address: u32,
15 pub bytestream: Vec<u8>,
16 pub crc_address: u32,
17 pub crc_bytestream: Vec<u8>,
18 pub used_size: u32,
19 pub allocated_size: u32,
20}
21
22fn byte_swap_inplace(bytes: &mut [u8]) {
23 for chunk in bytes.chunks_exact_mut(2) {
24 chunk.swap(0, 1);
25 }
26}
27
28fn validate_crc_location(length: usize, header: &Header) -> Result<u32, OutputError> {
29 let crc_offset = match &header.crc_location {
30 CrcLocation::Address(address) => {
31 let crc_offset = address.checked_sub(header.start_address).ok_or_else(|| {
32 OutputError::HexOutputError("CRC address before block start.".to_string())
33 })?;
34
35 if crc_offset < length as u32 {
36 return Err(OutputError::HexOutputError(
37 "CRC overlaps with payload.".to_string(),
38 ));
39 }
40
41 crc_offset
42 }
43 CrcLocation::Keyword(option) => match option.as_str() {
44 "end" => (length as u32 + 3) & !3,
45 _ => {
46 return Err(OutputError::HexOutputError(format!(
47 "Invalid CRC location: {}",
48 option
49 )));
50 }
51 },
52 };
53
54 if header.length < crc_offset + 4 {
55 return Err(OutputError::HexOutputError(
56 "CRC location would overrun block.".to_string(),
57 ));
58 }
59
60 Ok(crc_offset)
61}
62
63pub fn bytestream_to_datarange(
64 mut bytestream: Vec<u8>,
65 header: &Header,
66 settings: &Settings,
67 byte_swap: bool,
68 pad_to_end: bool,
69 padding_bytes: u32,
70) -> Result<DataRange, OutputError> {
71 if bytestream.len() > header.length as usize {
72 return Err(OutputError::HexOutputError(
73 "Bytestream length exceeds block length.".to_string(),
74 ));
75 }
76
77 if byte_swap {
79 if bytestream.len() % 2 != 0 {
80 bytestream.push(header.padding);
81 }
82 byte_swap_inplace(bytestream.as_mut_slice());
83 }
84
85 let crc_location = validate_crc_location(bytestream.len(), header)?;
87
88 let used_size = ((bytestream.len() as u32).saturating_add(4)).saturating_sub(padding_bytes);
89 let allocated_size = header.length;
90
91 if let CrcLocation::Keyword(_) = &header.crc_location {
93 bytestream.resize(crc_location as usize, header.padding);
94 }
95
96 if settings.crc.area == CrcArea::Block {
98 bytestream.resize(header.length as usize, header.padding);
99 bytestream[crc_location as usize..(crc_location + 4) as usize].fill(0);
100 }
101
102 let crc_val = checksum::calculate_crc(&bytestream, &settings.crc);
104
105 let mut crc_bytes: [u8; 4] = match settings.endianness {
106 Endianness::Big => crc_val.to_be_bytes(),
107 Endianness::Little => crc_val.to_le_bytes(),
108 };
109 if byte_swap {
110 byte_swap_inplace(&mut crc_bytes);
111 }
112
113 if pad_to_end {
115 bytestream.resize(header.length as usize, header.padding);
116 }
117
118 Ok(DataRange {
119 start_address: header.start_address + settings.virtual_offset,
120 bytestream,
121 crc_address: header.start_address + settings.virtual_offset + crc_location,
122 crc_bytestream: crc_bytes.to_vec(),
123 used_size,
124 allocated_size,
125 })
126}
127
128pub fn emit_hex(
129 ranges: &[DataRange],
130 record_width: usize,
131 format: OutputFormat,
132) -> Result<String, OutputError> {
133 if !(1..=128).contains(&record_width) {
134 return Err(OutputError::HexOutputError(
135 "Record width must be between 1 and 128".to_string(),
136 ));
137 }
138
139 let mut bf = BinFile::new();
141 let mut max_end: usize = 0;
142
143 for range in ranges {
144 bf.add_bytes(
145 range.bytestream.as_slice(),
146 Some(range.start_address as usize),
147 false,
148 )
149 .map_err(|e| OutputError::HexOutputError(format!("Failed to add bytes: {}", e)))?;
150 bf.add_bytes(
151 range.crc_bytestream.as_slice(),
152 Some(range.crc_address as usize),
153 true,
154 )
155 .map_err(|e| OutputError::HexOutputError(format!("Failed to add bytes: {}", e)))?;
156
157 let end = (range.start_address as usize).saturating_add(range.bytestream.len());
158 if end > max_end {
159 max_end = end;
160 }
161 let end = (range.crc_address as usize).saturating_add(range.crc_bytestream.len());
162 if end > max_end {
163 max_end = end;
164 }
165 }
166
167 match format {
168 OutputFormat::Hex => {
169 let ihex_format = if max_end <= 0x1_0000 {
170 IHexFormat::IHex16
171 } else {
172 IHexFormat::IHex32
173 };
174 let lines = bf.to_ihex(Some(record_width), ihex_format).map_err(|e| {
175 OutputError::HexOutputError(format!("Failed to generate Intel HEX: {}", e))
176 })?;
177 Ok(lines.join("\n"))
178 }
179 OutputFormat::Mot => {
180 use bin_file::SRecordAddressLength;
181 let addr_len = if max_end <= 0x1_0000 {
182 SRecordAddressLength::Length16
183 } else if max_end <= 0x100_0000 {
184 SRecordAddressLength::Length24
185 } else {
186 SRecordAddressLength::Length32
187 };
188 let lines = bf.to_srec(Some(record_width), addr_len).map_err(|e| {
189 OutputError::HexOutputError(format!("Failed to generate S-Record: {}", e))
190 })?;
191 Ok(lines.join("\n"))
192 }
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::layout::header::CrcLocation;
200 use crate::layout::header::Header;
201 use crate::layout::settings::Endianness;
202 use crate::layout::settings::Settings;
203 use crate::layout::settings::{CrcArea, CrcData};
204
205 fn sample_settings() -> Settings {
206 Settings {
207 endianness: Endianness::Little,
208 virtual_offset: 0,
209 crc: CrcData {
210 polynomial: 0x04C11DB7,
211 start: 0xFFFF_FFFF,
212 xor_out: 0xFFFF_FFFF,
213 ref_in: true,
214 ref_out: true,
215 area: CrcArea::Data,
216 },
217 byte_swap: false,
218 pad_to_end: false,
219 }
220 }
221
222 fn sample_header(len: u32) -> Header {
223 Header {
224 start_address: 0,
225 length: len,
226 crc_location: CrcLocation::Keyword("end".to_string()),
227 padding: 0xFF,
228 }
229 }
230
231 #[test]
232 fn pad_to_end_false_resizes_to_crc_end_only() {
233 let settings = sample_settings();
234 let header = sample_header(16);
235
236 let bytestream = vec![1u8, 2, 3, 4];
237 let dr = bytestream_to_datarange(bytestream.clone(), &header, &settings, false, false, 0)
238 .expect("data range generation failed");
239 let hex = emit_hex(&[dr], 16, crate::output::args::OutputFormat::Hex)
240 .expect("hex generation failed");
241
242 assert_eq!(bytestream.len(), 4);
244
245 let crc_location = super::validate_crc_location(4usize, &header).expect("crc loc");
247 assert_eq!(crc_location as usize, 4, "crc should follow payload end");
248 let crc_val = checksum::calculate_crc(&bytestream[..crc_location as usize], &settings.crc);
249 let crc_bytes = match settings.endianness {
250 Endianness::Big => crc_val.to_be_bytes(),
251 Endianness::Little => crc_val.to_le_bytes(),
252 };
253 let expected_crc_ascii = crc_bytes
255 .iter()
256 .map(|b| format!("{:02X}", b))
257 .collect::<String>();
258 assert!(
259 hex.to_uppercase().contains(&expected_crc_ascii),
260 "hex should contain CRC bytes"
261 );
262 }
263 #[test]
264 fn pad_to_end_true_resizes_to_full_block() {
265 let settings = sample_settings();
266 let header = sample_header(32);
267
268 let bytestream = vec![1u8, 2, 3, 4];
269 let dr = bytestream_to_datarange(bytestream, &header, &settings, false, true, 0)
270 .expect("data range generation failed");
271
272 assert_eq!(dr.bytestream.len(), header.length as usize);
273 }
274}