use super::Semver;
use crate::{Ecc, Endian, Error, Result, NATIVE_ENDIAN};
use byteorder::{BigEndian, ByteOrder, LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{Read, Write};
pub const FORMAT_VERSION: Semver = Semver::new(0, 1, 0);
#[repr(C)]
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Header {
magic: Ecc,
version: Semver,
content: Ecc,
table_count: u32,
chunk_count: u32,
}
impl Header {
pub const SIZE: usize = std::mem::size_of::<Self>();
pub fn new(content: Ecc, table_count: u32, chunk_count: u32) -> Self {
Self {
magic: Ecc::HFF_MAGIC,
version: FORMAT_VERSION,
content,
table_count,
chunk_count,
}
}
pub fn is_valid(&self) -> bool {
match self.magic.endian(Ecc::HFF_MAGIC) {
Some(endian) => {
if endian == NATIVE_ENDIAN {
self.version == FORMAT_VERSION
} else {
self.version.swap_bytes() == FORMAT_VERSION
}
}
None => false,
}
}
pub fn version(&self) -> Semver {
self.version
}
pub fn is_native_endian(&self) -> bool {
self.magic == Ecc::HFF_MAGIC
}
pub fn table_count(&self) -> u32 {
self.table_count
}
pub fn chunk_count(&self) -> u32 {
self.chunk_count
}
pub fn read(reader: &mut dyn Read) -> Result<Self> {
let mut magic = [0_u8; 8];
reader.read_exact(&mut magic)?;
match Ecc::HFF_MAGIC.endian(magic.into()) {
Some(endian) => match endian {
Endian::Little => Ok(Self {
magic: magic.into(),
version: Semver::read::<LittleEndian>(reader)?,
content: Ecc::read::<LittleEndian>(reader)?,
table_count: reader.read_u32::<LittleEndian>()?,
chunk_count: reader.read_u32::<LittleEndian>()?,
}),
Endian::Big => Ok(Self {
magic: magic.into(),
version: Semver::read::<BigEndian>(reader)?,
content: Ecc::read::<BigEndian>(reader)?,
table_count: reader.read_u32::<BigEndian>()?,
chunk_count: reader.read_u32::<BigEndian>()?,
}),
},
None => Err(Error::Invalid("Not an HFF file.".into())),
}
}
pub fn write<E: ByteOrder>(self, writer: &mut dyn Write) -> Result<()> {
self.magic.write::<E>(writer)?;
self.version.write::<E>(writer)?;
self.content.write::<E>(writer)?;
writer.write_u32::<E>(self.table_count)?;
writer.write_u32::<E>(self.chunk_count)?;
Ok(())
}
#[cfg(test)]
pub fn swap_bytes(&self) -> Self {
Self {
magic: self.magic.swap_bytes(),
version: self.version.swap_bytes(),
content: self.content.swap_bytes(),
table_count: self.table_count.swap_bytes(),
chunk_count: self.chunk_count.swap_bytes(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{NE, OP};
#[test]
fn test_struct_layout() {
assert_eq!(std::mem::size_of::<Header>(), 32);
}
#[test]
fn validation() {
assert!(Header::new(Ecc::new("test"), 0, 0).is_valid());
assert!(Header::new(Ecc::new("test"), 0, 0).is_native_endian());
assert!(Header::new(Ecc::new("test"), 0, 0).swap_bytes().is_valid());
assert!(!Header::new(Ecc::new("test"), 0, 0)
.swap_bytes()
.is_native_endian());
}
#[test]
fn serialization() {
{
let header = Header::new("Test".into(), 1, 2);
let mut buffer = vec![];
assert!(header.write::<NE>(&mut buffer).is_ok());
let dup = Header::read(&mut buffer.as_slice()).unwrap();
assert_eq!(dup.magic, Ecc::HFF_MAGIC);
assert_eq!(dup.version, Semver::new(0, 1, 0));
assert_eq!(dup.content, Ecc::new("Test"));
assert_eq!(dup.table_count, 1);
assert_eq!(dup.chunk_count, 2);
}
{
let header = Header::new("Test".into(), 1, 2);
let mut buffer = vec![];
assert!(header.write::<OP>(&mut buffer).is_ok());
let dup = Header::read(&mut buffer.as_slice()).unwrap();
assert_eq!(dup.magic, Ecc::HFF_MAGIC.swap_bytes());
assert_eq!(dup.version, Semver::new(0, 1, 0));
assert_eq!(dup.content, Ecc::new("Test"));
assert_eq!(dup.table_count, 1);
assert_eq!(dup.chunk_count, 2);
}
}
}