hex_to_uf2/
lib.rs

1use std::{
2    fs::File,
3    io::{BufRead, BufReader, Write},
4    path::Path,
5};
6
7use anyhow::{anyhow, Result};
8use families::{get_family_id, ChipFamily};
9
10use crate::block::Block;
11
12pub mod block;
13pub mod families;
14
15const ADDRESS_MASK: u32 = 0xff;
16const INVERTED_ADDRESS_MASK: u32 = !ADDRESS_MASK;
17
18pub fn hex_to_uf2(
19    hex_lines: impl Iterator<Item = impl AsRef<str>>,
20    family: Option<ChipFamily>,
21) -> Result<Vec<u8>> {
22    let mut upper: u32 = 0;
23    let mut app_start_address: Option<u32> = None;
24
25    let mut current_block: Option<Block> = None;
26
27    let mut blocks: Vec<Block> = vec![];
28
29    for hex_line in hex_lines {
30        let hex_line = hex_line.as_ref();
31        if !hex_line.starts_with(':') {
32            continue;
33        }
34
35        let mut binary_line = vec![];
36        let mut i = 1;
37
38        while i < hex_line.len() - 1 {
39            let byte = u8::from_str_radix(&hex_line[i..i + 2], 16).unwrap_or(0);
40            binary_line.push(byte);
41            i += 2;
42        }
43
44        let byte_count = binary_line[0] as usize;
45        if binary_line.len() - 5 != byte_count {
46            return Err(anyhow!("Invalid HEX line: mismatched byte count"));
47        }
48
49        if binary_line.len() < 4 {
50            return Err(anyhow!(
51                "Line with less than 4 encoded hex chars + 1 byte leads to undefined behaviour!"
52            ));
53        }
54
55        match binary_line[3] {
56            0 => {
57                let mut address =
58                    upper + (((binary_line[1] as u32) << 8) | (binary_line[2] as u32));
59                if app_start_address.is_none() {
60                    app_start_address = Some(address);
61                }
62                // Skip first 4 and last item
63                for byte in binary_line.iter().take(binary_line.len() - 1).skip(4) {
64                    if current_block.is_none()
65                        || (current_block.as_ref().unwrap().address & INVERTED_ADDRESS_MASK)
66                            != (address & INVERTED_ADDRESS_MASK)
67                    {
68                        if let Some(block) = current_block.take() {
69                            blocks.push(block);
70                        }
71                        current_block = Some(Block::new(address & INVERTED_ADDRESS_MASK));
72                    }
73                    let current_mut_block = current_block
74                        .as_mut()
75                        .expect("current_block should always be some here");
76
77                    current_mut_block.bytes[(address & ADDRESS_MASK) as usize] = *byte;
78                    address += 1;
79                }
80            }
81            1 => break, // End of file
82            2 => {
83                upper = (((binary_line[4] as u32) << 8) | (binary_line[5] as u32)) << 4;
84            }
85            4 => {
86                upper = (((binary_line[4] as u32) << 8) | (binary_line[5] as u32)) << 16;
87            }
88            _ => {
89                // do nothing
90            }
91        }
92    }
93
94    if let Some(block) = current_block.take() {
95        // Add last block
96        blocks.push(block);
97    }
98
99    let number_of_blocks = blocks.len() as u32;
100
101    let family_id = family.map(get_family_id);
102
103    Ok(blocks
104        .iter()
105        .enumerate()
106        .flat_map(|(i, block)| {
107            block
108                .encode(i as u32, number_of_blocks, family_id)
109                .into_iter()
110        })
111        .collect())
112}
113
114pub fn hex_to_uf2_file(
115    hex_file: &Path,
116    output_path: &Path,
117    family: Option<ChipFamily>,
118) -> Result<()> {
119    let binary_buffer = BufReader::new(File::open(hex_file).expect("Couldn't open input file!"));
120
121    let uf2_buffer = hex_to_uf2(
122        binary_buffer
123            .lines()
124            .map(|line| line.expect("error reading line")),
125        family,
126    )
127    .expect("Error converting hex to uf2");
128
129    let mut file =
130        File::create(output_path).expect("Error creating or overwriting destination file");
131
132    file.write_all(&uf2_buffer)
133        .expect("Error writing to destination");
134
135    Ok(())
136}
137
138#[cfg(test)]
139mod tests {
140    use std::io::Read;
141
142    use super::*;
143
144    #[test]
145    fn static_string() {
146        let static_string = ":020000041000EA
147:1000000000B5324B212058609868022188439860DF
148:10001000D860186158612E4B002199600221596106
149:100020000121F02299502B49196001219960352056
150:1000300000F044F80222904214D00621196600F024
151:1000400034F8196E01211966002018661A6600F04E
152:100050002CF8196E196E196E052000F02FF8012189
153:100060000842F9D1002199601B49196000215960AB";
154
155        let uf2_bytes = hex_to_uf2(static_string.lines(), None);
156
157        println!("{uf2_bytes:X?}");
158    }
159
160    fn compare_to_python(
161        input: &Path,
162        output: &Path,
163        python_out: &Path,
164        family: Option<ChipFamily>,
165    ) {
166        hex_to_uf2_file(input, output, family).unwrap();
167
168        let rust_reader = BufReader::new(File::open(output).unwrap());
169        let python_reader = BufReader::new(File::open(python_out).unwrap());
170
171        let mut rust_bytes = rust_reader.bytes();
172        let mut python_bytes = python_reader.bytes();
173
174        let mut position: usize = 0;
175
176        loop {
177            match (rust_bytes.next(), python_bytes.next()) {
178                (None, None) => break,
179                (Some(_), None) => {
180                    assert!(false, "rust_bytes is longer than python_bytes");
181                    break;
182                }
183                (None, Some(_)) => {
184                    assert!(false, "python_bytes is longer than rust_bytes");
185                    break;
186                }
187                (Some(rust_byte), Some(python_byte)) => {
188                    assert_eq!(
189                        rust_byte.unwrap(),
190                        python_byte.unwrap(),
191                        "Difference at {position}, hex: {position:X}"
192                    );
193                }
194            }
195
196            position += 1;
197        }
198    }
199
200    #[test]
201    fn no_family_should_be_same() {
202        compare_to_python(
203            Path::new("./test/fenris-rmk-central.hex"),
204            Path::new("./test/fenris-rmk-central.uf2"),
205            Path::new("./test/fenris-rmk-central_py.uf2"),
206            None,
207        );
208    }
209
210    #[test]
211    fn family_should_be_same() {
212        compare_to_python(
213            Path::new("./test/fenris-rmk-central.hex"),
214            Path::new("./test/fenris-rmk-central_id.uf2"),
215            Path::new("./test/fenris-rmk-central_py_id.uf2"),
216            Some(ChipFamily::RP2040),
217        );
218    }
219}