use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, Cursor, Read, Write};
use crate::error::{Error, Result};
use crate::structures::{HBIN_SIGNATURE, MIN_HIVE_BIN_SIZE};
pub const HIVE_BIN_HEADER_SIZE: usize = 32;
#[derive(Debug, Clone)]
pub struct HiveBinHeader {
pub signature: [u8; 4],
pub offset: u32,
pub size: u32,
pub reserved: [u8; 8],
pub timestamp: u64,
pub spare: u32,
}
impl HiveBinHeader {
pub fn parse(data: &[u8]) -> Result<Self> {
if data.len() < HIVE_BIN_HEADER_SIZE {
return Err(Error::BufferTooSmall {
needed: HIVE_BIN_HEADER_SIZE,
available: data.len(),
});
}
let mut cursor = Cursor::new(data);
let mut signature = [0u8; 4];
cursor.read_exact(&mut signature)?;
if &signature != HBIN_SIGNATURE {
return Err(Error::InvalidSignature {
expected: String::from_utf8_lossy(HBIN_SIGNATURE).to_string(),
found: String::from_utf8_lossy(&signature).to_string(),
});
}
let offset = cursor.read_u32::<LittleEndian>()?;
let size = cursor.read_u32::<LittleEndian>()?;
if (size as usize) < MIN_HIVE_BIN_SIZE || size % 4096 != 0 {
return Err(Error::InvalidHiveBin {
offset,
message: format!("Invalid size: {} (must be multiple of 4096)", size),
});
}
let mut reserved = [0u8; 8];
cursor.read_exact(&mut reserved)?;
let timestamp = cursor.read_u64::<LittleEndian>()?;
let spare = cursor.read_u32::<LittleEndian>()?;
Ok(Self {
signature,
offset,
size,
reserved,
timestamp,
spare,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.signature)?;
writer.write_u32::<LittleEndian>(self.offset)?;
writer.write_u32::<LittleEndian>(self.size)?;
writer.write_all(&self.reserved)?;
writer.write_u64::<LittleEndian>(self.timestamp)?;
writer.write_u32::<LittleEndian>(self.spare)?;
Ok(())
}
pub fn new(offset: u32, size: u32) -> Self {
Self {
signature: *HBIN_SIGNATURE,
offset,
size,
reserved: [0; 8],
timestamp: 0,
spare: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct HiveBin {
pub header: HiveBinHeader,
pub data: Vec<u8>,
}
impl HiveBin {
pub fn parse(data: &[u8]) -> Result<Self> {
let header = HiveBinHeader::parse(data)?;
let bin_size = header.size as usize;
if data.len() < bin_size {
return Err(Error::BufferTooSmall {
needed: bin_size,
available: data.len(),
});
}
let cell_data = data[HIVE_BIN_HEADER_SIZE..bin_size].to_vec();
Ok(Self {
header,
data: cell_data,
})
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
self.header.write(writer)?;
writer.write_all(&self.data)?;
Ok(())
}
pub fn new(offset: u32, size: u32) -> Self {
let header = HiveBinHeader::new(offset, size);
let cell_data_size = size as usize - HIVE_BIN_HEADER_SIZE;
let mut data = vec![0u8; cell_data_size];
let cell_size = cell_data_size as i32;
data[0..4].copy_from_slice(&cell_size.to_le_bytes());
Self { header, data }
}
pub fn size(&self) -> u32 {
self.header.size
}
pub fn offset(&self) -> u32 {
self.header.offset
}
pub fn is_first_bin(&self) -> bool {
self.header.offset == 0
}
pub fn get_backup_timestamp(&self) -> Option<u64> {
if self.is_first_bin() {
Some(self.header.timestamp)
} else {
None
}
}
pub fn cell_file_offset(&self, cell_offset: u32) -> u64 {
4096 + cell_offset as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_hive_bin() {
let bin = HiveBin::new(0, 4096);
assert_eq!(bin.size(), 4096);
assert_eq!(bin.offset(), 0);
assert_eq!(bin.data.len(), 4096 - HIVE_BIN_HEADER_SIZE);
}
#[test]
fn test_hive_bin_roundtrip() {
let bin = HiveBin::new(0, 4096);
let mut buffer = Vec::new();
bin.write(&mut buffer).unwrap();
let parsed = HiveBin::parse(&buffer).unwrap();
assert_eq!(parsed.size(), bin.size());
assert_eq!(parsed.offset(), bin.offset());
}
}