#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::config::LocatorPrecision;
use crate::table::filter::ribbon::burr::{BurrBuilder, BurrParams};
pub const SECTION_VERSION: u8 = 1;
pub const SECTION_HEADER_LEN: usize = 4;
#[derive(Copy, Clone, Debug)]
pub struct LocatorSpec {
pub precision: LocatorPrecision,
pub block_id_bits: Option<u8>,
pub slot_bits: Option<u8>,
}
fn bits_for(max: u64) -> u8 {
#[expect(
clippy::cast_possible_truncation,
reason = "64 - leading_zeros() is in 0..=64, fits u8"
)]
let bits = (64 - max.leading_zeros()) as u8;
bits.max(1)
}
const PRECISION_RESTART: u8 = 0;
const PRECISION_ENTRY: u8 = 1;
const PRECISION_BLOCK: u8 = 2;
fn precision_byte(p: LocatorPrecision) -> u8 {
match p {
LocatorPrecision::Restart => PRECISION_RESTART,
LocatorPrecision::Entry => PRECISION_ENTRY,
LocatorPrecision::Block => PRECISION_BLOCK,
}
}
#[must_use]
pub fn build_locator_section(entries: &[(u64, u64, u64)], spec: LocatorSpec) -> Option<Vec<u8>> {
if entries.is_empty() {
return None;
}
let max_block = entries.iter().map(|e| e.1).max().unwrap_or(0);
let max_slot = entries.iter().map(|e| e.2).max().unwrap_or(0);
let block_only = spec.precision == LocatorPrecision::Block;
let block_id_bits = spec.block_id_bits.unwrap_or_else(|| bits_for(max_block));
let slot_bits = if block_only {
0
} else {
spec.slot_bits.unwrap_or_else(|| bits_for(max_slot))
};
let block_fits = block_id_bits >= bits_for(max_block);
let slot_fits = block_only || slot_bits >= bits_for(max_slot);
let r = u16::from(block_id_bits) + u16::from(slot_bits);
if !block_fits || !slot_fits || r == 0 || r > 64 {
log::debug!(
"locator section skipped: widths block_id_bits={block_id_bits} slot_bits={slot_bits} \
cannot represent max_block={max_block} max_slot={max_slot} (r={r})"
);
return None;
}
let slot_mask: u64 = if slot_bits == 0 {
0
} else if slot_bits >= 64 {
u64::MAX
} else {
(1u64 << slot_bits) - 1
};
#[expect(
clippy::cast_possible_truncation,
reason = "r is validated to 1..=64 above, fits u8"
)]
let r_u8 = r as u8;
let mut hashes = Vec::with_capacity(entries.len());
let mut locators = Vec::with_capacity(entries.len());
for &(hash, block_id, slot) in entries {
hashes.push(hash);
locators.push((block_id << slot_bits) | (slot & slot_mask));
}
let params = match BurrParams::with_bpk(entries.len(), f32::from(r_u8)) {
Ok(p) => p,
Err(e) => {
log::debug!("locator section skipped: BurrParams::with_bpk failed: {e}");
return None;
}
};
let builder = match BurrBuilder::new(params) {
Ok(b) => b,
Err(e) => {
log::debug!("locator section skipped: BurrBuilder::new failed: {e}");
return None;
}
};
let filter = match builder.build_from_hashes_with_values(&hashes, &locators) {
Ok(f) => f,
Err(e) => {
log::debug!("locator section skipped: retrieval ribbon build failed: {e}");
return None;
}
};
let wire = filter.to_wire_bytes();
let mut section = Vec::with_capacity(SECTION_HEADER_LEN + wire.len());
section.push(SECTION_VERSION);
section.push(precision_byte(spec.precision));
section.push(block_id_bits);
section.push(slot_bits);
section.extend_from_slice(&wire);
Some(section)
}
#[expect(
clippy::indexing_slicing,
reason = "header bytes [0..4) are gated by the `section.len() < SECTION_HEADER_LEN` \
check on the line above; the wire slice starts at the validated header end"
)]
pub fn locate(section: &[u8], hash: u64) -> crate::Result<Option<(u64, u64)>> {
use crate::table::filter::ribbon::burr::recover_value_from_bytes;
if section.len() < SECTION_HEADER_LEN {
return Err(crate::Error::InvalidHeader("LocatorSection"));
}
if section[0] != SECTION_VERSION {
return Err(crate::Error::InvalidHeader("LocatorSection version"));
}
let slot_bits = section[3];
let Some(packed) = recover_value_from_bytes(§ion[SECTION_HEADER_LEN..], hash)? else {
return Ok(None);
};
let slot_mask = if slot_bits == 0 {
0
} else if slot_bits >= 64 {
u64::MAX
} else {
(1u64 << slot_bits) - 1
};
let block_id = if slot_bits >= 64 {
0
} else {
packed >> slot_bits
};
Ok(Some((block_id, packed & slot_mask)))
}
#[derive(Debug)]
pub struct LoadedLocator {
section: crate::Slice,
blocks: Vec<crate::table::BlockHandle>,
precision: u8,
}
pub type Located = (crate::table::BlockHandle, Option<(u64, bool)>);
impl LoadedLocator {
#[must_use]
pub fn new(section: crate::Slice, blocks: Vec<crate::table::BlockHandle>) -> Self {
let precision = section.get(1).copied().unwrap_or(PRECISION_BLOCK);
Self {
section,
blocks,
precision,
}
}
pub fn locate_block(&self, key_hash: u64) -> crate::Result<Option<Located>> {
let Some((block_id, slot)) = locate(&self.section, key_hash)? else {
return Ok(None);
};
let Some(handle) = usize::try_from(block_id)
.ok()
.and_then(|i| self.blocks.get(i))
.copied()
else {
return Ok(None);
};
let hint = match self.precision {
PRECISION_RESTART => Some((slot, false)),
PRECISION_ENTRY => Some((slot, true)),
_ => None,
};
Ok(Some((handle, hint)))
}
}
#[cfg(test)]
mod tests;