use crate::page::Page;
const TRAILER_START: usize = 0xFF0;
const SEARCH_LIMIT: usize = 0x300;
const SLOT_OFFSET_MIN: u16 = 0x20;
const SLOT_OFFSET_MAX: u16 = TRAILER_START as u16;
const MIN_LIVE_SLOTS: usize = 8;
#[derive(Debug, Clone)]
pub struct SlotDirectory {
pub scan_start: usize,
pub array_start: usize,
pub end: usize,
pub slots: Vec<u16>,
pub leading_zero: bool,
}
impl SlotDirectory {
pub fn live_slots(&self) -> impl Iterator<Item = u16> + '_ {
self.slots.iter().copied().filter(|&s| s != 0)
}
pub fn live_count(&self) -> usize {
self.slots.iter().filter(|&&s| s != 0).count()
}
pub fn deleted_count(&self) -> usize {
self.slots.len() - self.live_count()
}
pub fn min_offset(&self) -> Option<u16> {
self.live_slots().min()
}
}
#[derive(Debug, Clone)]
pub struct SlottedPage<'a> {
pub page: Page<'a>,
pub directory: Option<SlotDirectory>,
}
impl<'a> SlottedPage<'a> {
pub fn parse(page: Page<'a>) -> Self {
let directory = find_slot_directory(page.bytes());
SlottedPage { page, directory }
}
pub fn row_bytes(&self) -> Vec<(u16, &'a [u8])> {
let Some(dir) = &self.directory else {
return Vec::new();
};
let bytes = self.page.bytes();
let mut live: Vec<u16> = dir.live_slots().collect();
live.sort_unstable();
let mut out = Vec::with_capacity(live.len());
for (i, &off) in live.iter().enumerate() {
let start = off as usize;
let end = live
.get(i + 1)
.map(|n| *n as usize)
.unwrap_or(TRAILER_START);
if start < end && end <= TRAILER_START {
out.push((off, &bytes[start..end]));
}
}
out
}
pub fn overflow_prefix(&self) -> Option<&'a [u8]> {
let dir = self.directory.as_ref()?;
let end = dir.array_start;
if end == 0 {
return None;
}
let bytes = self.page.bytes();
let prefix = &bytes[..end];
if prefix.iter().all(|&b| b == 0) {
None
} else {
Some(prefix)
}
}
}
fn u16le(buf: &[u8], off: usize) -> u16 {
u16::from_le_bytes([buf[off], buf[off + 1]])
}
fn is_slot_offset(value: u16) -> bool {
(SLOT_OFFSET_MIN..SLOT_OFFSET_MAX).contains(&value)
}
fn scan_from(plain: &[u8], start: usize) -> Option<SlotDirectory> {
let mut pos = start;
let mut leading_zero = false;
let mut slots: Vec<u16> = Vec::new();
let mut prev: u32 = 0x10000;
if pos + 3 < SEARCH_LIMIT && u16le(plain, pos) == 0 && is_slot_offset(u16le(plain, pos + 2)) {
leading_zero = true;
pos += 2;
}
let array_start = pos;
let mut seen_live = false;
while pos + 1 < SEARCH_LIMIT {
let value = u16le(plain, pos);
if value == 0 && seen_live {
slots.push(0);
pos += 2;
continue;
}
if is_slot_offset(value) && (value as u32) < prev {
slots.push(value);
prev = value as u32;
seen_live = true;
pos += 2;
continue;
}
break;
}
let live_count = slots.iter().filter(|&&s| s != 0).count();
if live_count < MIN_LIVE_SLOTS {
return None;
}
Some(SlotDirectory {
scan_start: start,
array_start,
end: pos,
slots,
leading_zero,
})
}
fn find_slot_directory(plain: &[u8]) -> Option<SlotDirectory> {
let mut best: Option<SlotDirectory> = None;
for start in 0..SEARCH_LIMIT {
let Some(cand) = scan_from(plain, start) else {
continue;
};
let better = match &best {
None => true,
Some(b) => {
let cand_live = cand.slots.iter().filter(|&&s| s != 0).count();
let best_live = b.slots.iter().filter(|&&s| s != 0).count();
cand_live > best_live
|| (cand_live == best_live && cand.slots.len() > b.slots.len())
}
};
if better {
best = Some(cand);
}
}
best
}