Skip to main content

mint_core/output/
mod.rs

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    // Use bin_file to format output.
57    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/// Represents an output file to be written.
104#[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    /// Render this file's contents as a hex/mot string.
113    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]; // 8 bytes > 4 byte block
150        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        // Intel HEX starts with ':'
165        assert!(hex.starts_with(':'));
166    }
167}