pub mod crc32;
pub mod dfuse;
use std::io::{Read, Seek};
#[derive(Debug)]
pub struct DfuFile {
pub file: std::fs::File,
pub path: std::path::PathBuf,
pub content: Content,
pub suffix: Suffix,
}
impl DfuFile {
pub fn new(
file: std::fs::File,
path: std::path::PathBuf,
content: Content,
suffix: Suffix,
) -> Self {
Self {
file,
path,
content,
suffix,
}
}
pub fn open<P: AsRef<std::path::Path> + Clone>(path: P) -> Result<Self, Error> {
let mut file = std::fs::File::open(path.clone())?;
let file_size = file.seek(std::io::SeekFrom::End(0))?;
if file_size < SUFFIX_LENGTH as u64 {
return Err(Error::InsufficientFileSize);
}
let content = if dfuse::detect(&mut file)? {
Content::Dfuse(dfuse::Content::from_file(&mut file)?)
} else {
Content::Plain
};
let suffix = Suffix::from_file(&mut file)?;
Ok(Self::new(
file,
std::path::PathBuf::from(path.as_ref()),
content,
suffix,
))
}
pub fn size(&mut self) -> Result<u64, Error> {
let size = self.file.seek(std::io::SeekFrom::End(0))?;
Ok(size)
}
pub fn read_raw_at(&mut self, position: u64, buffer: &mut [u8]) -> Result<usize, Error> {
self.file.seek(std::io::SeekFrom::Start(position))?;
let read_size = self.file.read(buffer)?;
Ok(read_size)
}
pub fn calc_crc(&mut self) -> Result<u32, Error> {
let file_size = self.size()?;
self.file.rewind()?;
const CHUNK_SIZE: u64 = 1024;
let mut file_pos = 0;
let mut crc = 0;
loop {
let read_size = std::cmp::min(CHUNK_SIZE, file_size - 4 - file_pos);
if read_size == 0 {
break;
}
let mut buffer = vec![0; read_size as usize];
self.file.read_exact(&mut buffer)?;
crc = crc32::crc32(&buffer, crc);
file_pos += read_size;
}
Ok(crc ^ 0xFFFFFFFF_u32)
}
}
#[derive(Debug)]
pub enum Content {
Plain,
Dfuse(dfuse::Content),
}
impl std::fmt::Display for Content {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::Plain => "Plain".to_string(),
Self::Dfuse(content) => format!("DfuSe v{}", content.prefix.bVersion),
}
)
}
}
pub const SUFFIX_LENGTH: usize = 16;
#[allow(non_snake_case)]
#[derive(Debug, Clone)]
pub struct Suffix {
pub bcdDevice: u16,
pub idProduct: u16,
pub idVendor: u16,
pub bcdDFU: u16,
pub ucDFUSignature: String,
pub bLength: u8,
pub dwCRC: u32,
}
impl Default for Suffix {
fn default() -> Self {
Self {
bcdDevice: 0xFFFF,
idProduct: 0xFFFF,
idVendor: 0xFFFF,
bcdDFU: 0x0100,
ucDFUSignature: String::from("UFD"),
bLength: SUFFIX_LENGTH as u8,
dwCRC: 0,
}
}
}
impl Suffix {
pub fn new(
device_version: u16,
product_id: u16,
vendor_id: u16,
dfu_spec_no: u16,
signature: String,
length: u8,
crc: u32,
) -> Self {
Self {
bcdDevice: device_version,
idProduct: product_id,
idVendor: vendor_id,
bcdDFU: dfu_spec_no,
ucDFUSignature: signature,
bLength: length,
dwCRC: crc,
}
}
pub fn from_bytes(buffer: &[u8; SUFFIX_LENGTH]) -> Self {
Self::new(
u16::from_le_bytes([buffer[0], buffer[1]]),
u16::from_le_bytes([buffer[2], buffer[3]]),
u16::from_le_bytes([buffer[4], buffer[5]]),
u16::from_le_bytes([buffer[6], buffer[7]]),
String::from_utf8_lossy(&buffer[8..11]).to_string(),
u8::from_le(buffer[11]),
u32::from_le_bytes([buffer[12], buffer[13], buffer[14], buffer[15]]),
)
}
pub fn from_file(file: &mut std::fs::File) -> Result<Self, Error> {
file.seek(std::io::SeekFrom::End(-(SUFFIX_LENGTH as i64)))?;
let mut buffer = [0; SUFFIX_LENGTH];
file.read_exact(&mut buffer)?;
let data = Self::from_bytes(&buffer);
if &data.ucDFUSignature != "UFD" {
return Err(Error::InvalidSuffixSignature);
}
Ok(data)
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Invalid file suffix signature.")]
InvalidSuffixSignature,
#[error("File size is to small to contain suffix.")]
InsufficientFileSize,
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Dfuse(#[from] dfuse::Error),
}