1pub mod crc32;
10pub mod dfuse;
11
12use std::io::{Read, Seek};
13
14use anyhow::{anyhow, Result};
15
16#[derive(Debug)]
20pub struct DfuFile {
21 pub file: std::fs::File,
23
24 pub path: std::path::PathBuf,
26
27 pub content: Content,
29
30 pub suffix: Suffix,
32}
33
34impl DfuFile {
35 pub fn new(
37 file: std::fs::File,
38 path: std::path::PathBuf,
39 content: Content,
40 suffix: Suffix,
41 ) -> Self {
42 Self {
43 file,
44 path,
45 content,
46 suffix,
47 }
48 }
49
50 pub fn open<P: AsRef<std::path::Path> + Clone>(path: P) -> Result<Self> {
52 let mut file = std::fs::File::open(path.clone())?;
53
54 let file_size = file.seek(std::io::SeekFrom::End(0))?;
55
56 if file_size < SUFFIX_LENGTH as u64 {
58 return Err(anyhow!(Error::InsufficientFileSize));
59 }
60
61 let content = if dfuse::detect(&mut file)? {
62 Content::DfuSe(dfuse::Content::from_file(&mut file)?)
63 } else {
64 Content::Plain
65 };
66
67 let suffix = Suffix::from_file(&mut file)?;
68
69 Ok(Self::new(
70 file,
71 std::path::PathBuf::from(path.as_ref()),
72 content,
73 suffix,
74 ))
75 }
76
77 pub fn calc_crc(&mut self) -> Result<u32> {
80 let file_size = self.file.seek(std::io::SeekFrom::End(0))?;
81 self.file.rewind()?;
82
83 const CHUNK_SIZE: u64 = 1024;
84 let mut file_pos = 0;
85 let mut crc = 0;
86
87 loop {
88 let read_size = std::cmp::min(CHUNK_SIZE, file_size - 4 - file_pos);
89
90 if read_size == 0 {
91 break;
92 }
93
94 let mut buffer = vec![0; read_size as usize];
95 self.file.read_exact(&mut buffer)?;
96
97 crc = crc32::crc32(&buffer, crc);
98
99 file_pos += read_size;
100 }
101
102 Ok(crc ^ 0xFFFFFFFF_u32)
103 }
104}
105
106#[derive(Debug)]
110pub enum Content {
111 Plain,
113
114 DfuSe(dfuse::Content),
116}
117
118impl std::fmt::Display for Content {
119 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
120 write!(
121 f,
122 "{}",
123 match self {
124 Self::Plain => "Plain".to_string(),
125 Self::DfuSe(content) => format!("DfuSe v{}", content.prefix.bVersion),
126 }
127 )
128 }
129}
130
131pub const SUFFIX_LENGTH: usize = 16;
135
136#[allow(non_snake_case)]
138#[derive(Debug, Clone)]
139pub struct Suffix {
140 pub bcdDevice: u16,
142
143 pub idProduct: u16,
145
146 pub idVendor: u16,
148
149 pub bcdDFU: u16,
153
154 pub ucDFUSignature: String,
156
157 pub bLength: u8,
159
160 pub dwCRC: u32,
162}
163
164impl Default for Suffix {
165 fn default() -> Self {
167 Self {
168 bcdDevice: 0xFFFF,
169 idProduct: 0xFFFF,
170 idVendor: 0xFFFF,
171 bcdDFU: 0x0100,
172 ucDFUSignature: String::from("UFD"),
173 bLength: SUFFIX_LENGTH as u8,
174 dwCRC: 0,
175 }
176 }
177}
178
179impl Suffix {
180 pub fn new(
182 device_version: u16,
183 product_id: u16,
184 vendor_id: u16,
185 dfu_spec_no: u16,
186 signature: String,
187 length: u8,
188 crc: u32,
189 ) -> Self {
190 Self {
191 bcdDevice: device_version,
192 idProduct: product_id,
193 idVendor: vendor_id,
194 bcdDFU: dfu_spec_no,
195 ucDFUSignature: signature,
196 bLength: length,
197 dwCRC: crc,
198 }
199 }
200
201 pub fn from_bytes(buffer: &[u8; SUFFIX_LENGTH]) -> Self {
203 Self::new(
204 u16::from_le_bytes([buffer[0], buffer[1]]),
205 u16::from_le_bytes([buffer[2], buffer[3]]),
206 u16::from_le_bytes([buffer[4], buffer[5]]),
207 u16::from_le_bytes([buffer[6], buffer[7]]),
208 String::from_utf8_lossy(&buffer[8..11]).to_string(),
209 u8::from_le(buffer[11]),
210 u32::from_le_bytes([buffer[12], buffer[13], buffer[14], buffer[15]]),
211 )
212 }
213
214 pub fn from_file(file: &mut std::fs::File) -> Result<Self> {
216 file.seek(std::io::SeekFrom::End(-(SUFFIX_LENGTH as i64)))?;
217 let mut buffer = [0; SUFFIX_LENGTH];
218 file.read_exact(&mut buffer)?;
219
220 let data = Self::from_bytes(&buffer);
221
222 if &data.ucDFUSignature != "UFD" {
223 return Err(anyhow!(Error::InvalidSuffixSignature));
224 }
225
226 Ok(data)
227 }
228}
229
230#[derive(Debug)]
234pub enum Error {
235 InvalidSuffixSignature,
237
238 InsufficientFileSize,
240}
241
242impl std::error::Error for Error {}
243
244impl std::fmt::Display for Error {
245 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
246 write!(
247 f,
248 "{}",
249 match self {
250 Self::InvalidSuffixSignature => "Invalid file suffix signature",
251 Self::InsufficientFileSize => "File size is to small to contain suffix",
252 }
253 )
254 }
255}