use {
crate::{
error::{Error, Result},
protocol::crc::crc16_xmodem,
},
byteorder::{LittleEndian, ReadBytesExt},
log::debug,
std::{
fs::File,
io::{BufReader, Read},
path::Path,
},
};
pub const FWPKG_MAGIC_V1: u32 = 0xEFBEADDF;
pub const FWPKG_MAGIC_V2_MIN: u32 = 0xEFBEADD0;
pub const FWPKG_MAGIC_V2_MAX: u32 = 0xEFBEADDE;
pub const FWPKG_MAGIC: u32 = FWPKG_MAGIC_V1;
pub const MAX_PARTITIONS: usize = 255;
pub const HEADER_SIZE_V1: usize = 12;
pub const HEADER_SIZE_V2: usize = 272;
pub const HEADER_SIZE: usize = HEADER_SIZE_V1;
pub const BIN_INFO_SIZE_V1: usize = 52;
pub const BIN_INFO_SIZE_V2: usize = 284;
pub const BIN_INFO_SIZE: usize = BIN_INFO_SIZE_V1;
pub const NAME_SIZE_V1: usize = 32;
pub const NAME_SIZE_V2: usize = 260;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FwpkgVersion {
V1,
V2,
}
#[derive(Debug, Clone)]
pub struct FwpkgHeader {
pub magic: u32,
pub crc: u16,
pub cnt: u16,
pub len: u32,
pub name: String,
pub version: FwpkgVersion,
}
impl FwpkgHeader {
pub fn read_v1<R: Read>(reader: &mut R) -> Result<Self> {
let magic = reader.read_u32::<LittleEndian>()?;
let crc = reader.read_u16::<LittleEndian>()?;
let cnt = reader.read_u16::<LittleEndian>()?;
let len = reader.read_u32::<LittleEndian>()?;
Ok(Self {
magic,
crc,
cnt,
len,
name: String::new(),
version: FwpkgVersion::V1,
})
}
pub fn read_v2<R: Read>(reader: &mut R) -> Result<Self> {
let magic = reader.read_u32::<LittleEndian>()?;
let crc = reader.read_u16::<LittleEndian>()?;
let cnt = reader.read_u16::<LittleEndian>()?;
let len = reader.read_u32::<LittleEndian>()?;
let mut name_bytes = [0u8; NAME_SIZE_V2];
reader.read_exact(&mut name_bytes)?;
let name_end = name_bytes
.iter()
.position(|&c| c == 0)
.unwrap_or(NAME_SIZE_V2);
let name = String::from_utf8_lossy(&name_bytes[..name_end]).to_string();
Ok(Self {
magic,
crc,
cnt,
len,
name,
version: FwpkgVersion::V2,
})
}
pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
let magic = reader.read_u32::<LittleEndian>()?;
let crc = reader.read_u16::<LittleEndian>()?;
let cnt = reader.read_u16::<LittleEndian>()?;
let len = reader.read_u32::<LittleEndian>()?;
let (name, version) = if magic == FWPKG_MAGIC_V1 {
(String::new(), FwpkgVersion::V1)
} else if (FWPKG_MAGIC_V2_MIN..=FWPKG_MAGIC_V2_MAX).contains(&magic) {
let mut name_bytes = [0u8; NAME_SIZE_V2];
reader.read_exact(&mut name_bytes)?;
let name_end = name_bytes
.iter()
.position(|&c| c == 0)
.unwrap_or(NAME_SIZE_V2);
let name = String::from_utf8_lossy(&name_bytes[..name_end]).to_string();
(name, FwpkgVersion::V2)
} else {
(String::new(), FwpkgVersion::V1)
};
Ok(Self {
magic,
crc,
cnt,
len,
name,
version,
})
}
pub fn is_valid(&self) -> bool {
let valid_magic = self.magic == FWPKG_MAGIC_V1
|| (FWPKG_MAGIC_V2_MIN..=FWPKG_MAGIC_V2_MAX).contains(&self.magic);
valid_magic && (self.cnt as usize) <= MAX_PARTITIONS
}
pub fn header_size(&self) -> usize {
match self.version {
FwpkgVersion::V1 => HEADER_SIZE_V1,
FwpkgVersion::V2 => HEADER_SIZE_V2,
}
}
pub fn bin_info_size(&self) -> usize {
match self.version {
FwpkgVersion::V1 => BIN_INFO_SIZE_V1,
FwpkgVersion::V2 => BIN_INFO_SIZE_V2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum PartitionType {
Loader = 0,
Normal = 1,
KvNv = 2,
Efuse = 3,
Otp = 4,
Flashboot = 5,
Factory = 6,
Version = 7,
SecurityA = 8,
SecurityB = 9,
SecurityC = 10,
ProtocolA = 11,
AppsA = 12,
RadioConfig = 13,
Rom = 14,
Emmc = 15,
Database = 16,
Unknown(u32),
}
impl From<u32> for PartitionType {
fn from(value: u32) -> Self {
match value {
0 => Self::Loader,
1 => Self::Normal,
2 => Self::KvNv,
3 => Self::Efuse,
4 => Self::Otp,
5 => Self::Flashboot,
6 => Self::Factory,
7 => Self::Version,
8 => Self::SecurityA,
9 => Self::SecurityB,
10 => Self::SecurityC,
11 => Self::ProtocolA,
12 => Self::AppsA,
13 => Self::RadioConfig,
14 => Self::Rom,
15 => Self::Emmc,
16 => Self::Database,
v => Self::Unknown(v),
}
}
}
impl PartitionType {
pub fn as_u32(&self) -> u32 {
match self {
Self::Loader => 0,
Self::Normal => 1,
Self::KvNv => 2,
Self::Efuse => 3,
Self::Otp => 4,
Self::Flashboot => 5,
Self::Factory => 6,
Self::Version => 7,
Self::SecurityA => 8,
Self::SecurityB => 9,
Self::SecurityC => 10,
Self::ProtocolA => 11,
Self::AppsA => 12,
Self::RadioConfig => 13,
Self::Rom => 14,
Self::Emmc => 15,
Self::Database => 16,
Self::Unknown(v) => *v,
}
}
#[allow(non_upper_case_globals)]
pub const LoaderBoot: Self = Self::Loader;
}
#[derive(Debug, Clone)]
pub struct FwpkgBinInfo {
pub name: String,
pub offset: u32,
pub length: u32,
pub burn_addr: u32,
pub burn_size: u32,
pub partition_type: PartitionType,
}
impl FwpkgBinInfo {
pub fn read_v1<R: Read>(reader: &mut R) -> Result<Self> {
let mut name_bytes = [0u8; NAME_SIZE_V1];
reader.read_exact(&mut name_bytes)?;
let name_end = name_bytes
.iter()
.position(|&c| c == 0)
.unwrap_or(NAME_SIZE_V1);
let name = String::from_utf8_lossy(&name_bytes[..name_end]).to_string();
let offset = reader.read_u32::<LittleEndian>()?;
let length = reader.read_u32::<LittleEndian>()?;
let burn_addr = reader.read_u32::<LittleEndian>()?;
let burn_size = reader.read_u32::<LittleEndian>()?;
let type_value = reader.read_u32::<LittleEndian>()?;
Ok(Self {
name,
offset,
length,
burn_addr,
burn_size,
partition_type: type_value.into(),
})
}
pub fn read_v2<R: Read>(reader: &mut R) -> Result<Self> {
let mut name_bytes = [0u8; NAME_SIZE_V2];
reader.read_exact(&mut name_bytes)?;
let name_end = name_bytes
.iter()
.position(|&c| c == 0)
.unwrap_or(NAME_SIZE_V2);
let name = String::from_utf8_lossy(&name_bytes[..name_end]).to_string();
let offset = reader.read_u32::<LittleEndian>()?;
let length = reader.read_u32::<LittleEndian>()?;
let burn_addr = reader.read_u32::<LittleEndian>()?;
let burn_size = reader.read_u32::<LittleEndian>()?;
let type_value = reader.read_u32::<LittleEndian>()?;
let mut padding = [0u8; 4];
reader.read_exact(&mut padding)?;
Ok(Self {
name,
offset,
length,
burn_addr,
burn_size,
partition_type: type_value.into(),
})
}
pub fn read_from<R: Read>(reader: &mut R, version: FwpkgVersion) -> Result<Self> {
match version {
FwpkgVersion::V1 => Self::read_v1(reader),
FwpkgVersion::V2 => Self::read_v2(reader),
}
}
pub fn is_loaderboot(&self) -> bool {
self.partition_type == PartitionType::Loader
}
}
pub struct Fwpkg {
pub header: FwpkgHeader,
pub bins: Vec<FwpkgBinInfo>,
data: Vec<u8>,
}
impl Fwpkg {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
debug!("Loading FWPKG from: {}", path.display());
let file = File::open(path)?;
let mut reader = BufReader::new(file);
let mut data = Vec::new();
reader.read_to_end(&mut data)?;
Self::from_bytes(data)
}
pub fn from_bytes(data: Vec<u8>) -> Result<Self> {
if data.len() < HEADER_SIZE_V1 {
return Err(Error::InvalidFwpkg("File too small for header".into()));
}
let mut cursor = std::io::Cursor::new(&data);
let header = FwpkgHeader::read_from(&mut cursor)?;
if !header.is_valid() {
return Err(Error::InvalidFwpkg(format!(
"Invalid magic: expected {:#010X} (V1) or {:#010X}~{:#010X} (V2), got {:#010X}",
FWPKG_MAGIC_V1, FWPKG_MAGIC_V2_MIN, FWPKG_MAGIC_V2_MAX, header.magic
)));
}
debug!(
"FWPKG {:?} header: {} partitions, {} bytes total{}",
header.version,
header.cnt,
header.len,
if header
.name
.is_empty()
{
String::new()
} else {
format!(", name: {}", header.name)
}
);
let bin_count = header.cnt as usize;
let expected_size = header.header_size() + bin_count * header.bin_info_size();
if data.len() < expected_size {
return Err(Error::InvalidFwpkg(format!(
"File too small for {} partitions (need {} bytes, got {})",
bin_count,
expected_size,
data.len()
)));
}
let mut bins = Vec::with_capacity(bin_count);
for i in 0..bin_count {
let bin_info = FwpkgBinInfo::read_from(&mut cursor, header.version)?;
debug!(
" [{}] {} @ 0x{:08X}, {} bytes -> 0x{:08X} (type: {:?})",
i,
bin_info.name,
bin_info.offset,
bin_info.length,
bin_info.burn_addr,
bin_info.partition_type
);
bins.push(bin_info);
}
Ok(Self { header, bins, data })
}
pub fn version(&self) -> FwpkgVersion {
self.header
.version
}
pub fn package_name(&self) -> &str {
&self
.header
.name
}
pub fn loaderboot(&self) -> Option<&FwpkgBinInfo> {
self.bins
.iter()
.find(|b| b.is_loaderboot())
}
pub fn normal_bins(&self) -> impl Iterator<Item = &FwpkgBinInfo> {
self.bins
.iter()
.filter(|b| !b.is_loaderboot())
}
pub fn bin_data(&self, bin: &FwpkgBinInfo) -> Result<&[u8]> {
let start = bin.offset as usize;
let end = start + bin.length as usize;
if end
> self
.data
.len()
{
return Err(Error::InvalidFwpkg(format!(
"Partition {} data out of bounds (offset {}, length {}, file size {})",
bin.name,
bin.offset,
bin.length,
self.data
.len()
)));
}
Ok(&self.data[start..end])
}
pub fn verify_crc(&self) -> Result<()> {
let header_size = self
.header
.header_size();
if self
.data
.len()
< header_size
{
return Err(Error::InvalidFwpkg("File too small".into()));
}
let crc_start = 6; let crc_end = header_size
+ self
.bins
.len()
* self
.header
.bin_info_size();
if self
.data
.len()
< crc_end
{
return Err(Error::InvalidFwpkg(
"File too small for CRC verification".into(),
));
}
let crc_data = &self.data[crc_start..crc_end];
let calculated_crc = crc16_xmodem(crc_data);
if calculated_crc
!= self
.header
.crc
{
return Err(Error::CrcMismatch {
expected: self
.header
.crc,
actual: calculated_crc,
});
}
debug!(
"FWPKG CRC verified: {:#06X}",
self.header
.crc
);
Ok(())
}
pub fn partition_count(&self) -> usize {
self.bins
.len()
}
pub fn find_by_name(&self, name: &str) -> Option<&FwpkgBinInfo> {
self.bins
.iter()
.find(|b| b.name == name)
}
}
impl std::fmt::Debug for Fwpkg {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Fwpkg")
.field("header", &self.header)
.field("bins", &self.bins)
.field(
"data_len",
&self
.data
.len(),
)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partition_type_from_u32() {
assert_eq!(PartitionType::from(0), PartitionType::Loader);
assert_eq!(PartitionType::from(1), PartitionType::Normal);
assert_eq!(PartitionType::from(2), PartitionType::KvNv);
assert_eq!(PartitionType::from(5), PartitionType::Flashboot);
assert_eq!(PartitionType::from(16), PartitionType::Database);
assert_eq!(PartitionType::from(99), PartitionType::Unknown(99));
}
#[test]
fn test_partition_type_as_u32() {
assert_eq!(PartitionType::Loader.as_u32(), 0);
assert_eq!(PartitionType::Normal.as_u32(), 1);
assert_eq!(PartitionType::Unknown(42).as_u32(), 42);
}
#[test]
fn test_partition_type_roundtrip() {
for i in 0..=16 {
let pt = PartitionType::from(i);
assert_eq!(pt.as_u32(), i);
}
let unknown = PartitionType::from(999);
assert_eq!(unknown.as_u32(), 999);
}
#[test]
fn test_partition_type_all_variants() {
let cases = [
(0, PartitionType::Loader),
(1, PartitionType::Normal),
(2, PartitionType::KvNv),
(3, PartitionType::Efuse),
(4, PartitionType::Otp),
(5, PartitionType::Flashboot),
(6, PartitionType::Factory),
(7, PartitionType::Version),
(8, PartitionType::SecurityA),
(9, PartitionType::SecurityB),
(10, PartitionType::SecurityC),
(11, PartitionType::ProtocolA),
(12, PartitionType::AppsA),
(13, PartitionType::RadioConfig),
(14, PartitionType::Rom),
(15, PartitionType::Emmc),
(16, PartitionType::Database),
];
for (val, expected) in &cases {
assert_eq!(
PartitionType::from(*val),
*expected,
"Failed for value {val}"
);
}
}
#[test]
fn test_partition_type_loaderboot_alias() {
assert_eq!(PartitionType::LoaderBoot, PartitionType::Loader);
assert_eq!(PartitionType::LoaderBoot.as_u32(), 0);
}
#[test]
fn test_magic_constants() {
assert_eq!(FWPKG_MAGIC_V1, 0xEFBEADDF);
assert_eq!(FWPKG_MAGIC_V2_MIN, 0xEFBEADD0);
assert_eq!(FWPKG_MAGIC_V2_MAX, 0xEFBEADDE);
const { assert!(FWPKG_MAGIC_V1 > FWPKG_MAGIC_V2_MAX) };
}
#[test]
fn test_header_sizes() {
assert_eq!(HEADER_SIZE_V1, 12);
assert_eq!(HEADER_SIZE_V2, 272); assert_eq!(BIN_INFO_SIZE_V1, 52); assert_eq!(BIN_INFO_SIZE_V2, 284); }
#[test]
fn test_fwpkg_version_header_size() {
let v1_header = FwpkgHeader {
magic: FWPKG_MAGIC_V1,
crc: 0,
cnt: 0,
len: 0,
name: String::new(),
version: FwpkgVersion::V1,
};
assert_eq!(v1_header.header_size(), HEADER_SIZE_V1);
assert_eq!(v1_header.bin_info_size(), BIN_INFO_SIZE_V1);
let v2_header = FwpkgHeader {
magic: FWPKG_MAGIC_V2_MIN,
crc: 0,
cnt: 0,
len: 0,
name: "test".to_string(),
version: FwpkgVersion::V2,
};
assert_eq!(v2_header.header_size(), HEADER_SIZE_V2);
assert_eq!(v2_header.bin_info_size(), BIN_INFO_SIZE_V2);
}
#[test]
fn test_header_is_valid() {
let h = FwpkgHeader {
magic: FWPKG_MAGIC_V1,
crc: 0,
cnt: 3,
len: 1000,
name: String::new(),
version: FwpkgVersion::V1,
};
assert!(h.is_valid());
for magic in FWPKG_MAGIC_V2_MIN..=FWPKG_MAGIC_V2_MAX {
let h = FwpkgHeader {
magic,
crc: 0,
cnt: 1,
len: 100,
name: String::new(),
version: FwpkgVersion::V2,
};
assert!(h.is_valid(), "Magic {magic:#010X} should be valid V2");
}
let h = FwpkgHeader {
magic: 0x12345678,
crc: 0,
cnt: 1,
len: 100,
name: String::new(),
version: FwpkgVersion::V1,
};
assert!(!h.is_valid());
let h = FwpkgHeader {
magic: FWPKG_MAGIC_V1,
crc: 0,
cnt: 256,
len: 100,
name: String::new(),
version: FwpkgVersion::V1,
};
assert!(!h.is_valid());
}
fn build_test_fwpkg_v1(partitions: &[(&str, u32, u32, u32, u32, u32)]) -> Vec<u8> {
use byteorder::{LittleEndian, WriteBytesExt};
#[allow(clippy::cast_possible_truncation)]
let cnt = partitions.len() as u16;
let header_size = HEADER_SIZE_V1;
let bin_infos_size = partitions.len() * BIN_INFO_SIZE_V1;
let total_data: u32 = partitions
.iter()
.map(|p| p.2)
.sum();
#[allow(clippy::cast_possible_truncation)]
let total_len = (header_size + bin_infos_size) as u32 + total_data;
let mut data = Vec::new();
data.write_u32::<LittleEndian>(FWPKG_MAGIC_V1)
.unwrap();
data.write_u16::<LittleEndian>(0)
.unwrap();
data.write_u16::<LittleEndian>(cnt)
.unwrap();
data.write_u32::<LittleEndian>(total_len)
.unwrap();
#[allow(clippy::cast_possible_truncation)]
let mut data_offset = (header_size + bin_infos_size) as u32;
for (name, _offset, length, burn_addr, burn_size, ptype) in partitions {
let mut name_bytes = [0u8; NAME_SIZE_V1];
let name_b = name.as_bytes();
let copy_len = name_b
.len()
.min(NAME_SIZE_V1);
name_bytes[..copy_len].copy_from_slice(&name_b[..copy_len]);
data.extend_from_slice(&name_bytes);
data.write_u32::<LittleEndian>(data_offset)
.unwrap();
data.write_u32::<LittleEndian>(*length)
.unwrap();
data.write_u32::<LittleEndian>(*burn_addr)
.unwrap();
data.write_u32::<LittleEndian>(*burn_size)
.unwrap();
data.write_u32::<LittleEndian>(*ptype)
.unwrap();
data_offset += *length;
}
let crc = crate::protocol::crc::crc16_xmodem(&data[6..]);
data[4] = (crc & 0xFF) as u8;
data[5] = (crc >> 8) as u8;
for (_, _, length, _, _, _) in partitions {
data.extend(vec![0xAA; *length as usize]);
}
data
}
#[test]
fn test_header_read_v1() {
use byteorder::{LittleEndian, WriteBytesExt};
let mut buf = Vec::new();
buf.write_u32::<LittleEndian>(FWPKG_MAGIC_V1)
.unwrap();
buf.write_u16::<LittleEndian>(0x1234)
.unwrap(); buf.write_u16::<LittleEndian>(3)
.unwrap(); buf.write_u32::<LittleEndian>(0x5678)
.unwrap();
let header = FwpkgHeader::read_v1(&mut std::io::Cursor::new(&buf)).unwrap();
assert_eq!(header.magic, FWPKG_MAGIC_V1);
assert_eq!(header.crc, 0x1234);
assert_eq!(header.cnt, 3);
assert_eq!(header.len, 0x5678);
assert_eq!(header.version, FwpkgVersion::V1);
assert!(
header
.name
.is_empty()
);
}
#[test]
fn test_header_read_from_auto_detect_v1() {
use byteorder::{LittleEndian, WriteBytesExt};
let mut buf = Vec::new();
buf.write_u32::<LittleEndian>(FWPKG_MAGIC_V1)
.unwrap();
buf.write_u16::<LittleEndian>(0)
.unwrap();
buf.write_u16::<LittleEndian>(2)
.unwrap();
buf.write_u32::<LittleEndian>(100)
.unwrap();
let header = FwpkgHeader::read_from(&mut std::io::Cursor::new(&buf)).unwrap();
assert_eq!(header.version, FwpkgVersion::V1);
assert_eq!(header.cnt, 2);
}
#[test]
fn test_header_read_from_invalid_magic() {
use byteorder::{LittleEndian, WriteBytesExt};
let mut buf = Vec::new();
buf.write_u32::<LittleEndian>(0xDEADC0DE)
.unwrap();
buf.write_u16::<LittleEndian>(0)
.unwrap();
buf.write_u16::<LittleEndian>(1)
.unwrap();
buf.write_u32::<LittleEndian>(100)
.unwrap();
let header = FwpkgHeader::read_from(&mut std::io::Cursor::new(&buf)).unwrap();
assert!(!header.is_valid());
}
#[test]
fn test_bin_info_read_v1() {
use byteorder::{LittleEndian, WriteBytesExt};
let mut buf = Vec::new();
let mut name = [0u8; NAME_SIZE_V1];
name[..9].copy_from_slice(b"flashboot");
buf.extend_from_slice(&name);
buf.write_u32::<LittleEndian>(64)
.unwrap(); buf.write_u32::<LittleEndian>(1024)
.unwrap(); buf.write_u32::<LittleEndian>(0x00200000)
.unwrap(); buf.write_u32::<LittleEndian>(0x10000)
.unwrap(); buf.write_u32::<LittleEndian>(5)
.unwrap();
let info = FwpkgBinInfo::read_v1(&mut std::io::Cursor::new(&buf)).unwrap();
assert_eq!(info.name, "flashboot");
assert_eq!(info.offset, 64);
assert_eq!(info.length, 1024);
assert_eq!(info.burn_addr, 0x00200000);
assert_eq!(info.burn_size, 0x10000);
assert_eq!(info.partition_type, PartitionType::Flashboot);
assert!(!info.is_loaderboot());
}
#[test]
fn test_bin_info_is_loaderboot() {
let info = FwpkgBinInfo {
name: "loaderboot".to_string(),
offset: 0,
length: 100,
burn_addr: 0,
burn_size: 100,
partition_type: PartitionType::Loader,
};
assert!(info.is_loaderboot());
let info = FwpkgBinInfo {
name: "app".to_string(),
offset: 0,
length: 100,
burn_addr: 0,
burn_size: 100,
partition_type: PartitionType::Normal,
};
assert!(!info.is_loaderboot());
}
#[test]
fn test_fwpkg_from_bytes_valid_v1() {
let data = build_test_fwpkg_v1(&[
("loaderboot", 0, 16, 0x0, 16, 0), ("flashboot", 0, 32, 0x200000, 32, 5), ("app", 0, 64, 0x800000, 64, 1), ]);
let fwpkg = Fwpkg::from_bytes(data).unwrap();
assert_eq!(fwpkg.version(), FwpkgVersion::V1);
assert_eq!(fwpkg.partition_count(), 3);
assert!(
fwpkg
.package_name()
.is_empty()
);
assert!(
fwpkg
.loaderboot()
.is_some()
);
assert_eq!(
fwpkg
.loaderboot()
.unwrap()
.name,
"loaderboot"
);
let normals: Vec<_> = fwpkg
.normal_bins()
.collect();
assert_eq!(normals.len(), 2);
assert!(
fwpkg
.find_by_name("flashboot")
.is_some()
);
assert!(
fwpkg
.find_by_name("nonexistent")
.is_none()
);
}
#[test]
fn test_fwpkg_from_bytes_too_small() {
let data = vec![0u8; 4]; let result = Fwpkg::from_bytes(data);
assert!(result.is_err());
}
#[test]
fn test_fwpkg_from_bytes_invalid_magic() {
use byteorder::{LittleEndian, WriteBytesExt};
let mut data = Vec::new();
data.write_u32::<LittleEndian>(0xBAADF00D)
.unwrap();
data.write_u16::<LittleEndian>(0)
.unwrap();
data.write_u16::<LittleEndian>(0)
.unwrap();
data.write_u32::<LittleEndian>(12)
.unwrap();
let result = Fwpkg::from_bytes(data);
assert!(result.is_err());
}
#[test]
fn test_fwpkg_verify_crc() {
let data = build_test_fwpkg_v1(&[("app", 0, 8, 0x800000, 8, 1)]);
let fwpkg = Fwpkg::from_bytes(data).unwrap();
assert!(
fwpkg
.verify_crc()
.is_ok()
);
}
#[test]
fn test_fwpkg_verify_crc_mismatch() {
let mut data = build_test_fwpkg_v1(&[("app", 0, 8, 0x800000, 8, 1)]);
data[4] ^= 0xFF;
let fwpkg = Fwpkg::from_bytes(data).unwrap();
assert!(
fwpkg
.verify_crc()
.is_err()
);
}
#[test]
fn test_fwpkg_bin_data() {
let data = build_test_fwpkg_v1(&[("app", 0, 8, 0x800000, 8, 1)]);
let fwpkg = Fwpkg::from_bytes(data).unwrap();
let bin = &fwpkg.bins[0];
let bin_data = fwpkg
.bin_data(bin)
.unwrap();
assert_eq!(bin_data.len(), 8);
assert!(
bin_data
.iter()
.all(|&b| b == 0xAA)
);
}
#[test]
fn test_fwpkg_bin_data_out_of_bounds() {
let data = build_test_fwpkg_v1(&[("app", 0, 8, 0x800000, 8, 1)]);
let fwpkg = Fwpkg::from_bytes(data).unwrap();
let fake_bin = FwpkgBinInfo {
name: "fake".into(),
offset: 99999,
length: 100,
burn_addr: 0,
burn_size: 100,
partition_type: PartitionType::Normal,
};
assert!(
fwpkg
.bin_data(&fake_bin)
.is_err()
);
}
#[test]
fn test_fwpkg_debug_format() {
let data = build_test_fwpkg_v1(&[("app", 0, 4, 0, 4, 1)]);
let fwpkg = Fwpkg::from_bytes(data).unwrap();
let debug_str = format!("{fwpkg:?}");
assert!(debug_str.contains("Fwpkg"));
assert!(debug_str.contains("data_len"));
}
}