use bitnuc::BitSize;
use byteorder::{ByteOrder, LittleEndian};
use std::io::{Read, Write};
use crate::error::{BuilderError, HeaderError, Result};
#[allow(clippy::unreadable_literal)]
const MAGIC: u32 = 0x51455342;
pub const FILE_MAGIC: [u8; 4] = MAGIC.to_le_bytes();
const FORMAT: u8 = 1;
pub const SIZE_HEADER: usize = 32;
pub const RESERVED: [u8; 17] = [42; 17];
#[derive(Debug, Clone, Copy)]
pub struct FileHeaderBuilder {
slen: Option<u32>,
xlen: Option<u32>,
bitsize: Option<BitSize>,
flags: Option<bool>,
}
impl Default for FileHeaderBuilder {
fn default() -> Self {
Self::new()
}
}
impl FileHeaderBuilder {
#[must_use]
pub fn new() -> Self {
FileHeaderBuilder {
slen: None,
xlen: None,
bitsize: None,
flags: None,
}
}
#[must_use]
pub fn slen(mut self, slen: u32) -> Self {
self.slen = Some(slen);
self
}
#[must_use]
pub fn xlen(mut self, xlen: u32) -> Self {
self.xlen = Some(xlen);
self
}
#[must_use]
pub fn bitsize(mut self, bitsize: BitSize) -> Self {
self.bitsize = Some(bitsize);
self
}
#[must_use]
pub fn flags(mut self, flags: bool) -> Self {
self.flags = Some(flags);
self
}
pub fn build(self) -> Result<FileHeader> {
Ok(FileHeader {
magic: MAGIC,
format: FORMAT,
slen: if let Some(slen) = self.slen {
slen
} else {
return Err(BuilderError::MissingSlen.into());
},
xlen: self.xlen.unwrap_or(0),
bits: self.bitsize.unwrap_or_default(),
flags: self.flags.unwrap_or(false),
reserved: RESERVED,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileHeader {
pub magic: u32,
pub format: u8,
pub slen: u32,
pub xlen: u32,
pub bits: BitSize,
pub flags: bool,
pub reserved: [u8; 17],
}
impl FileHeader {
#[must_use]
pub fn new(bits: BitSize, slen: u32, flags: bool) -> Self {
Self {
magic: MAGIC,
format: FORMAT,
slen,
xlen: 0,
bits,
flags,
reserved: RESERVED,
}
}
#[must_use]
pub fn new_extended(bits: BitSize, slen: u32, xlen: u32, flags: bool) -> Self {
Self {
magic: MAGIC,
format: FORMAT,
slen,
xlen,
bits,
flags,
reserved: RESERVED,
}
}
pub fn set_bitsize(&mut self, bits: BitSize) {
self.bits = bits;
}
#[must_use]
pub fn is_paired(&self) -> bool {
self.xlen > 0
}
pub fn from_bytes(buffer: &[u8; SIZE_HEADER]) -> Result<Self> {
let magic = LittleEndian::read_u32(&buffer[0..4]);
if magic != MAGIC {
return Err(HeaderError::InvalidMagicNumber(magic).into());
}
let format = buffer[4];
if format != FORMAT {
return Err(HeaderError::InvalidFormatVersion(format).into());
}
let slen = LittleEndian::read_u32(&buffer[5..9]);
let xlen = LittleEndian::read_u32(&buffer[9..13]);
let bits = match buffer[13] {
0 | 2 | 42 => BitSize::Two,
4 => BitSize::Four,
x => return Err(HeaderError::InvalidBitSize(x).into()),
};
let flags = buffer[14] != 0;
let Ok(reserved) = buffer[15..32].try_into() else {
return Err(HeaderError::InvalidReservedBytes.into());
};
Ok(Self {
magic,
format,
slen,
xlen,
bits,
flags,
reserved,
})
}
pub fn from_buffer(buffer: &[u8]) -> Result<Self> {
let mut bytes = [0u8; SIZE_HEADER];
if buffer.len() < SIZE_HEADER {
return Err(HeaderError::InvalidSize(buffer.len(), SIZE_HEADER).into());
}
bytes.copy_from_slice(&buffer[..SIZE_HEADER]);
Self::from_bytes(&bytes)
}
pub fn write_bytes<W: Write>(&self, writer: &mut W) -> Result<()> {
let mut buffer = [0u8; SIZE_HEADER];
LittleEndian::write_u32(&mut buffer[0..4], self.magic);
buffer[4] = self.format;
LittleEndian::write_u32(&mut buffer[5..9], self.slen);
LittleEndian::write_u32(&mut buffer[9..13], self.xlen);
buffer[13] = self.bits.into();
buffer[14] = self.flags.into();
buffer[15..32].copy_from_slice(&self.reserved);
writer.write_all(&buffer)?;
Ok(())
}
pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
let mut buffer = [0u8; SIZE_HEADER];
reader.read_exact(&mut buffer)?;
Self::from_bytes(&buffer)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_default() {
let builder = FileHeaderBuilder::default();
assert!(builder.build().is_err());
}
#[test]
fn test_builder_bitsize() {
let header = FileHeaderBuilder::new()
.slen(64)
.bitsize(BitSize::Four)
.build()
.unwrap();
assert_eq!(header.bits, BitSize::Four);
}
#[test]
fn test_builder_missing_slen() {
let result = FileHeaderBuilder::new().xlen(10).build();
assert!(result.is_err());
}
#[test]
fn test_header_new() {
let header = FileHeader::new(BitSize::Two, 100, true);
assert_eq!(header.slen, 100);
assert_eq!(header.xlen, 0);
assert!(header.flags);
assert!(!header.is_paired());
}
#[test]
fn test_header_new_extended() {
let header = FileHeader::new_extended(BitSize::Four, 100, 50, false);
assert_eq!(header.slen, 100);
assert_eq!(header.xlen, 50);
assert_eq!(header.bits, BitSize::Four);
assert!(header.is_paired());
}
#[test]
fn test_set_bitsize() {
let mut header = FileHeader::new(BitSize::Two, 100, false);
header.set_bitsize(BitSize::Four);
assert_eq!(header.bits, BitSize::Four);
}
#[test]
fn test_from_bytes_invalid_format_version() {
let header = FileHeader::new(BitSize::Two, 32, false);
let mut buffer = [0u8; SIZE_HEADER];
let mut cursor = std::io::Cursor::new(&mut buffer[..]);
header.write_bytes(&mut cursor).unwrap();
buffer[4] = 99; let result = FileHeader::from_bytes(&buffer);
assert!(result.is_err());
}
#[test]
fn test_from_bytes_four_bit_size() {
let header = FileHeader::new(BitSize::Four, 32, false);
let mut buffer = [0u8; SIZE_HEADER];
let mut cursor = std::io::Cursor::new(&mut buffer[..]);
header.write_bytes(&mut cursor).unwrap();
let parsed = FileHeader::from_bytes(&buffer).unwrap();
assert_eq!(parsed.bits, BitSize::Four);
}
#[test]
fn test_from_bytes_invalid_bitsize() {
let header = FileHeader::new(BitSize::Two, 32, false);
let mut buffer = [0u8; SIZE_HEADER];
let mut cursor = std::io::Cursor::new(&mut buffer[..]);
header.write_bytes(&mut cursor).unwrap();
buffer[13] = 99; let result = FileHeader::from_bytes(&buffer);
assert!(result.is_err());
}
#[test]
fn test_from_bytes_invalid_magic() {
let buffer = [0u8; SIZE_HEADER];
let result = FileHeader::from_bytes(&buffer);
assert!(result.is_err());
}
#[test]
fn test_from_buffer_too_small() {
let buffer = [0u8; 10];
let result = FileHeader::from_buffer(&buffer);
assert!(result.is_err());
}
#[test]
fn test_from_buffer_valid() {
let header = FileHeader::new(BitSize::Two, 32, false);
let mut buffer = Vec::new();
header.write_bytes(&mut buffer).unwrap();
buffer.extend_from_slice(&[0u8; 16]); let parsed = FileHeader::from_buffer(&buffer).unwrap();
assert_eq!(parsed.slen, 32);
}
#[test]
fn test_from_reader_valid() {
let header = FileHeader::new_extended(BitSize::Two, 32, 16, true);
let mut buffer = Vec::new();
header.write_bytes(&mut buffer).unwrap();
let mut cursor = std::io::Cursor::new(buffer);
let parsed = FileHeader::from_reader(&mut cursor).unwrap();
assert_eq!(parsed, header);
}
#[test]
fn test_from_reader_truncated() {
let mut cursor = std::io::Cursor::new(vec![0u8; 5]);
let result = FileHeader::from_reader(&mut cursor);
assert!(result.is_err());
}
}