dfufile/
lib.rs

1//! This crate offers tools for processing DFU files as described
2//! in the document "Universal Serial Bus Device Class Specification for
3//! Device Firmware Upgrade", Revision 1.1 published at <https://usb.org>
4//!
5//! It also supports the extensions added by STMicroelectronics (DfuSe)
6//! that are widely used with STM32 microcontrollers, as well as
7//! several other products.
8
9pub mod crc32;
10pub mod dfuse;
11
12use std::io::{Read, Seek};
13
14use anyhow::{anyhow, Result};
15
16////////////////////////////////////////////////////////////////////////////////
17
18/// File handle
19#[derive(Debug)]
20pub struct DfuFile {
21    /// Reference to the file on the filesystem.
22    pub file: std::fs::File,
23
24    /// Path to the file.
25    pub path: std::path::PathBuf,
26
27    /// The content representation.
28    pub content: Content,
29
30    /// The file suffix with meta information.
31    pub suffix: Suffix,
32}
33
34impl DfuFile {
35    /// Creates a new instance.
36    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    /// Open existing file.
51    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        // File must be at least as large as the suffix
57        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    /// Calculate the CRC32 checksum of whole file excluding the last 4 bytes,
78    /// which contain the checksum itself.
79    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////////////////////////////////////////////////////////////////////////////////
107
108/// File content variants.
109#[derive(Debug)]
110pub enum Content {
111    /// Standard file with raw content.
112    Plain,
113
114    /// DfuSe file with extensions from STMicroelectronics.
115    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
131////////////////////////////////////////////////////////////////////////////////
132
133/// Length of the file suffix in bytes.
134pub const SUFFIX_LENGTH: usize = 16;
135
136/// File suffix containing the metadata.
137#[allow(non_snake_case)]
138#[derive(Debug, Clone)]
139pub struct Suffix {
140    /// Firmware version contained in the file, or 0xFFFF if ignored.
141    pub bcdDevice: u16,
142
143    /// Intended product id of the device or 0xFFFF if the field is ignored.
144    pub idProduct: u16,
145
146    /// Intended vendor id of the device or 0xFFFF if the field is ignored.
147    pub idVendor: u16,
148
149    /// DFU specification number.
150    /// - 0x0100 for standard files.
151    /// - 0x011A for DfuSe files.
152    pub bcdDFU: u16,
153
154    /// File identifier, must contain "DFU" in reversed order.
155    pub ucDFUSignature: String,
156
157    /// Length of the suffix itself, fixed to 16.
158    pub bLength: u8,
159
160    /// Calculated CRC32 over the whole file except for the dwCRC data itself.
161    pub dwCRC: u32,
162}
163
164impl Default for Suffix {
165    /// Creates a new suffix with default values.
166    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    /// Creates a new suffix.
181    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    /// Creates a new suffix from a buffer of u8 values.
202    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    /// Creates a new suffix from reading a file.
215    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////////////////////////////////////////////////////////////////////////////////
231
232/// Parsing errors.
233#[derive(Debug)]
234pub enum Error {
235    /// File suffix signature is not "UFD" (DFU reversed).
236    InvalidSuffixSignature,
237
238    /// File is too small (smaller than suffix size).
239    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}