1pub mod checksum;
2pub mod error;
3pub mod report;
4
5use crate::layout::header::Header;
6use error::OutputError;
7
8use bin_file::{BinFile, IHexFormat};
9
10#[derive(Copy, Clone, Debug, Eq, PartialEq)]
11pub enum OutputFormat {
12 Hex,
13 Mot,
14}
15
16#[derive(Debug, Clone)]
17pub struct DataRange {
18 pub start_address: u32,
19 pub bytestream: Vec<u8>,
20 pub used_size: u32,
21 pub allocated_size: u32,
22}
23
24pub fn bytestream_to_datarange(
25 bytestream: Vec<u8>,
26 header: &Header,
27 padding_bytes: u32,
28) -> Result<DataRange, OutputError> {
29 if bytestream.len() > header.length as usize {
30 return Err(OutputError::HexOutputError(
31 "Bytestream length exceeds block length.".to_owned(),
32 ));
33 }
34
35 let used_size = (bytestream.len() as u32).saturating_sub(padding_bytes);
36
37 Ok(DataRange {
38 start_address: header.start_address,
39 bytestream,
40 used_size,
41 allocated_size: header.length,
42 })
43}
44
45pub fn emit_hex(
46 ranges: &[DataRange],
47 record_width: usize,
48 format: OutputFormat,
49) -> Result<String, OutputError> {
50 if !(1..=128).contains(&record_width) {
51 return Err(OutputError::HexOutputError(
52 "Record width must be between 1 and 128".to_owned(),
53 ));
54 }
55
56 let mut bf = BinFile::new();
58 let mut max_end: usize = 0;
59
60 for range in ranges {
61 bf.add_bytes(
62 range.bytestream.as_slice(),
63 Some(range.start_address as usize),
64 false,
65 )
66 .map_err(|e| OutputError::HexOutputError(format!("Failed to add bytes: {}", e)))?;
67
68 let end = (range.start_address as usize).saturating_add(range.bytestream.len());
69 if end > max_end {
70 max_end = end;
71 }
72 }
73
74 match format {
75 OutputFormat::Hex => {
76 let ihex_format = if max_end <= 0x1_0000 {
77 IHexFormat::IHex16
78 } else {
79 IHexFormat::IHex32
80 };
81 let lines = bf.to_ihex(Some(record_width), ihex_format).map_err(|e| {
82 OutputError::HexOutputError(format!("Failed to generate Intel HEX: {}", e))
83 })?;
84 Ok(lines.join("\n"))
85 }
86 OutputFormat::Mot => {
87 use bin_file::SRecordAddressLength;
88 let addr_len = if max_end <= 0x1_0000 {
89 SRecordAddressLength::Length16
90 } else if max_end <= 0x100_0000 {
91 SRecordAddressLength::Length24
92 } else {
93 SRecordAddressLength::Length32
94 };
95 let lines = bf.to_srec(Some(record_width), addr_len).map_err(|e| {
96 OutputError::HexOutputError(format!("Failed to generate S-Record: {}", e))
97 })?;
98 Ok(lines.join("\n"))
99 }
100 }
101}
102
103#[derive(Debug, Clone)]
105pub struct OutputFile {
106 pub ranges: Vec<DataRange>,
107 pub format: OutputFormat,
108 pub record_width: usize,
109}
110
111impl OutputFile {
112 pub fn render(&self) -> Result<String, OutputError> {
114 emit_hex(&self.ranges, self.record_width, self.format)
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::layout::header::Header;
122
123 fn sample_header(len: u32) -> Header {
124 Header {
125 start_address: 0,
126 length: len,
127 padding: 0xFF,
128 }
129 }
130
131 #[test]
132 fn basic_datarange_generation() {
133 let header = sample_header(16);
134
135 let bytestream = vec![1u8, 2, 3, 4];
136 let dr = bytestream_to_datarange(bytestream.clone(), &header, 0)
137 .expect("data range generation failed");
138
139 assert_eq!(dr.bytestream.len(), 4);
140 assert_eq!(dr.start_address, 0);
141 assert_eq!(dr.used_size, 4);
142 assert_eq!(dr.allocated_size, 16);
143 }
144
145 #[test]
146 fn bytestream_exceeds_block_length_errors() {
147 let header = sample_header(4);
148
149 let bytestream = vec![1u8; 8]; let result = bytestream_to_datarange(bytestream, &header, 0);
151 assert!(result.is_err());
152 }
153
154 #[test]
155 fn hex_output_format() {
156 let header = sample_header(16);
157
158 let bytestream = vec![1u8, 2, 3, 4];
159 let dr =
160 bytestream_to_datarange(bytestream, &header, 0).expect("data range generation failed");
161 let hex = emit_hex(&[dr], 16, OutputFormat::Hex).expect("hex generation failed");
162
163 assert!(!hex.is_empty());
164 assert!(hex.starts_with(':'));
166 }
167}