use std::io::{Read, Write};
use super::error::{AcbError, AcbResult};
use super::{ACB_MAGIC, FORMAT_VERSION};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FileHeader {
pub magic: [u8; 4],
pub version: u32,
pub dimension: u32,
pub language_count: u32,
pub unit_count: u64,
pub edge_count: u64,
pub unit_table_offset: u64,
pub edge_table_offset: u64,
pub string_pool_offset: u64,
pub feature_vec_offset: u64,
pub temporal_offset: u64,
pub index_offset: u64,
pub repo_hash: [u8; 32],
pub compiled_at: u64,
pub _reserved: [u8; 8],
}
pub const HEADER_SIZE: usize = 128;
impl FileHeader {
pub fn new(dimension: u32) -> Self {
Self {
magic: ACB_MAGIC,
version: FORMAT_VERSION,
dimension,
language_count: 0,
unit_count: 0,
edge_count: 0,
unit_table_offset: HEADER_SIZE as u64,
edge_table_offset: HEADER_SIZE as u64,
string_pool_offset: 0,
feature_vec_offset: 0,
temporal_offset: 0,
index_offset: 0,
repo_hash: [0u8; 32],
compiled_at: crate::types::now_micros(),
_reserved: [0u8; 8],
}
}
pub fn write_to(&self, w: &mut impl Write) -> AcbResult<()> {
w.write_all(&self.magic)?;
w.write_all(&self.version.to_le_bytes())?;
w.write_all(&self.dimension.to_le_bytes())?;
w.write_all(&self.language_count.to_le_bytes())?;
w.write_all(&self.unit_count.to_le_bytes())?;
w.write_all(&self.edge_count.to_le_bytes())?;
w.write_all(&self.unit_table_offset.to_le_bytes())?;
w.write_all(&self.edge_table_offset.to_le_bytes())?;
w.write_all(&self.string_pool_offset.to_le_bytes())?;
w.write_all(&self.feature_vec_offset.to_le_bytes())?;
w.write_all(&self.temporal_offset.to_le_bytes())?;
w.write_all(&self.index_offset.to_le_bytes())?;
w.write_all(&self.repo_hash)?;
w.write_all(&self.compiled_at.to_le_bytes())?;
w.write_all(&self._reserved)?;
Ok(())
}
pub fn read_from(r: &mut impl Read) -> AcbResult<Self> {
let mut magic = [0u8; 4];
r.read_exact(&mut magic)?;
if magic != ACB_MAGIC {
return Err(AcbError::InvalidMagic);
}
let version = read_u32(r)?;
if version > FORMAT_VERSION {
return Err(AcbError::UnsupportedVersion(version));
}
let dimension = read_u32(r)?;
let language_count = read_u32(r)?;
let unit_count = read_u64(r)?;
let edge_count = read_u64(r)?;
let unit_table_offset = read_u64(r)?;
let edge_table_offset = read_u64(r)?;
let string_pool_offset = read_u64(r)?;
let feature_vec_offset = read_u64(r)?;
let temporal_offset = read_u64(r)?;
let index_offset = read_u64(r)?;
let mut repo_hash = [0u8; 32];
r.read_exact(&mut repo_hash)?;
let compiled_at = read_u64(r)?;
let mut reserved = [0u8; 8];
r.read_exact(&mut reserved)?;
Ok(Self {
magic,
version,
dimension,
language_count,
unit_count,
edge_count,
unit_table_offset,
edge_table_offset,
string_pool_offset,
feature_vec_offset,
temporal_offset,
index_offset,
repo_hash,
compiled_at,
_reserved: reserved,
})
}
pub fn to_bytes(&self) -> [u8; HEADER_SIZE] {
let mut buf = [0u8; HEADER_SIZE];
let mut cursor = std::io::Cursor::new(&mut buf[..]);
self.write_to(&mut cursor)
.expect("header write to fixed buffer");
buf
}
pub fn from_bytes(data: &[u8; HEADER_SIZE]) -> AcbResult<Self> {
let mut cursor = std::io::Cursor::new(&data[..]);
Self::read_from(&mut cursor)
}
}
fn read_u32(r: &mut impl Read) -> AcbResult<u32> {
let mut buf = [0u8; 4];
r.read_exact(&mut buf)?;
Ok(u32::from_le_bytes(buf))
}
fn read_u64(r: &mut impl Read) -> AcbResult<u64> {
let mut buf = [0u8; 8];
r.read_exact(&mut buf)?;
Ok(u64::from_le_bytes(buf))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_roundtrip() {
let h = FileHeader::new(256);
let bytes = h.to_bytes();
assert_eq!(bytes.len(), HEADER_SIZE);
let h2 = FileHeader::from_bytes(&bytes).unwrap();
assert_eq!(h, h2);
}
}