use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::io::{self, Cursor, Write};
use crate::error::{Error, Result};
pub const MIN_CELL_SIZE: usize = 8;
pub const CELL_ALIGNMENT: usize = 8;
pub const CELL_ALIGNMENT_V1_1: usize = 16;
pub mod signatures {
pub const KEY_NODE: &[u8; 2] = b"nk";
pub const KEY_VALUE: &[u8; 2] = b"vk";
pub const KEY_SECURITY: &[u8; 2] = b"sk";
pub const INDEX_LEAF: &[u8; 2] = b"li";
pub const FAST_LEAF: &[u8; 2] = b"lf";
pub const HASH_LEAF: &[u8; 2] = b"lh";
pub const INDEX_ROOT: &[u8; 2] = b"ri";
pub const BIG_DATA: &[u8; 2] = b"db";
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CellType {
KeyNode,
KeyValue,
KeySecurity,
IndexLeaf,
FastLeaf,
HashLeaf,
IndexRoot,
BigData,
Unknown,
}
impl CellType {
pub fn from_signature(sig: &[u8; 2]) -> Self {
match sig {
b"nk" => CellType::KeyNode,
b"vk" => CellType::KeyValue,
b"sk" => CellType::KeySecurity,
b"li" => CellType::IndexLeaf,
b"lf" => CellType::FastLeaf,
b"lh" => CellType::HashLeaf,
b"ri" => CellType::IndexRoot,
b"db" => CellType::BigData,
_ => CellType::Unknown,
}
}
pub fn signature(&self) -> Option<&'static [u8; 2]> {
match self {
CellType::KeyNode => Some(signatures::KEY_NODE),
CellType::KeyValue => Some(signatures::KEY_VALUE),
CellType::KeySecurity => Some(signatures::KEY_SECURITY),
CellType::IndexLeaf => Some(signatures::INDEX_LEAF),
CellType::FastLeaf => Some(signatures::FAST_LEAF),
CellType::HashLeaf => Some(signatures::HASH_LEAF),
CellType::IndexRoot => Some(signatures::INDEX_ROOT),
CellType::BigData => Some(signatures::BIG_DATA),
CellType::Unknown => None,
}
}
}
#[derive(Debug, Clone)]
pub struct RawCell {
pub size: i32,
pub data: Vec<u8>,
pub offset: u32,
}
impl RawCell {
pub fn parse(data: &[u8], offset: u32) -> Result<Self> {
if data.len() < 4 {
return Err(Error::BufferTooSmall {
needed: 4,
available: data.len(),
});
}
let mut cursor = Cursor::new(data);
let size = cursor.read_i32::<LittleEndian>()?;
let abs_size = size.abs() as usize;
if abs_size < MIN_CELL_SIZE {
return Err(Error::InvalidCellSize(size));
}
if data.len() < abs_size {
return Err(Error::BufferTooSmall {
needed: abs_size,
available: data.len(),
});
}
let cell_data = data[4..abs_size].to_vec();
Ok(Self {
size,
data: cell_data,
offset,
})
}
pub fn is_allocated(&self) -> bool {
self.size < 0
}
pub fn abs_size(&self) -> usize {
self.size.abs() as usize
}
pub fn data_size(&self) -> usize {
self.abs_size().saturating_sub(4)
}
pub fn cell_type(&self) -> CellType {
if self.data.len() < 2 {
return CellType::Unknown;
}
let sig: [u8; 2] = [self.data[0], self.data[1]];
CellType::from_signature(&sig)
}
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_i32::<LittleEndian>(self.size)?;
writer.write_all(&self.data)?;
Ok(())
}
pub fn new_allocated(data: Vec<u8>, offset: u32) -> Self {
let total_size = 4 + data.len(); let aligned_size = (total_size + CELL_ALIGNMENT - 1) & !(CELL_ALIGNMENT - 1);
let padded_data_len = aligned_size - 4;
let mut padded_data = data;
padded_data.resize(padded_data_len, 0);
Self {
size: -(aligned_size as i32),
data: padded_data,
offset,
}
}
pub fn new_unallocated(size: usize, offset: u32) -> Self {
let aligned_size = (size + CELL_ALIGNMENT - 1) & !(CELL_ALIGNMENT - 1);
let data_size = aligned_size - 4;
Self {
size: aligned_size as i32,
data: vec![0; data_size],
offset,
}
}
pub fn allocate(&mut self) {
if self.size > 0 {
self.size = -self.size;
}
}
pub fn deallocate(&mut self) {
if self.size < 0 {
self.size = -self.size;
}
}
}
pub fn align_cell_size(size: usize) -> usize {
(size + CELL_ALIGNMENT - 1) & !(CELL_ALIGNMENT - 1)
}
pub fn required_cell_size(data_len: usize) -> usize {
align_cell_size(4 + data_len) }
#[derive(Debug, Clone, Copy)]
pub struct FreeListEntry {
pub next: u32,
}
impl FreeListEntry {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 4 {
return None;
}
let next = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
Some(Self { next })
}
pub fn has_next(&self) -> bool {
self.next != 0xFFFFFFFF
}
}
#[derive(Debug, Clone, Copy)]
pub struct FreeListEntryV1 {
pub next: u32,
pub previous: u32,
}
impl FreeListEntryV1 {
pub fn parse(data: &[u8]) -> Option<Self> {
if data.len() < 8 {
return None;
}
let next = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let previous = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
Some(Self { next, previous })
}
pub fn has_next(&self) -> bool {
self.next != 0xFFFFFFFF
}
pub fn has_previous(&self) -> bool {
self.previous != 0xFFFFFFFF
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cell_alignment() {
assert_eq!(align_cell_size(1), 8);
assert_eq!(align_cell_size(8), 8);
assert_eq!(align_cell_size(9), 16);
assert_eq!(align_cell_size(16), 16);
}
#[test]
fn test_cell_type_detection() {
assert_eq!(CellType::from_signature(b"nk"), CellType::KeyNode);
assert_eq!(CellType::from_signature(b"vk"), CellType::KeyValue);
assert_eq!(CellType::from_signature(b"??"), CellType::Unknown);
}
#[test]
fn test_raw_cell() {
let cell = RawCell::new_allocated(vec![b'n', b'k', 0, 0], 0);
assert!(cell.is_allocated());
assert_eq!(cell.cell_type(), CellType::KeyNode);
}
}