use rudb_common::{Error, Result};
pub(crate) const ENTRY_BYTES: usize = 56;
pub const MAX_EXTENT: u32 = 64 * 1024 * 1024;
pub const MAX_EXTENTS: u32 = 16 * 1024;
pub const KEY_MAP: &[u8; 8] = b"RUDBKM1\0";
pub const FORWARD_LINK: &[u8; 8] = b"RUDBFL1\0";
pub const ADJACENCY: &[u8; 8] = b"RUDBAJ1\0";
pub const SUMMARY: &[u8; 8] = b"RUDBCS1\0";
pub const SKETCHES: &[u8; 8] = b"RUDBSK1\0";
pub const DEGREES: &[u8; 8] = b"RUDBGD1\0";
pub const SORTED_PROJECTION: &[u8; 8] = b"RUDBSP1\0";
pub const RUN_PROJECTION: &[u8; 8] = b"RUDBRP1\0";
pub const GRAPH_KINDS: &[&[u8; 8]] = &[KEY_MAP, FORWARD_LINK, ADJACENCY];
pub const STATISTICS_KINDS: &[&[u8; 8]] = &[SUMMARY, SKETCHES, DEGREES];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Section {
pub kind: [u8; 8],
pub id: u64,
pub generation: u64,
pub extents: u32,
pub extent_page: u64,
pub extent_bytes: u32,
pub hash: u64,
pub flags: u32,
pub header_bytes: u32,
}
impl Section {
pub(crate) fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
if self.extents > MAX_EXTENTS {
return Err(malformed(format!(
"a section of {} extents exceeds the bound of {MAX_EXTENTS}",
self.extents
)));
}
if self.extent_bytes > MAX_EXTENT {
return Err(malformed("a section's extent table is larger than one extent"));
}
let before = out.len();
out.extend_from_slice(&self.kind);
out.extend_from_slice(&self.id.to_le_bytes());
out.extend_from_slice(&self.generation.to_le_bytes());
out.extend_from_slice(&self.extents.to_le_bytes());
out.extend_from_slice(&self.extent_page.to_le_bytes());
out.extend_from_slice(&self.extent_bytes.to_le_bytes());
out.extend_from_slice(&self.hash.to_le_bytes());
out.extend_from_slice(&self.flags.to_le_bytes());
out.extend_from_slice(&self.header_bytes.to_le_bytes());
debug_assert_eq!(out.len() - before, ENTRY_BYTES, "a section entry is fifty six bytes");
Ok(())
}
pub(crate) fn decode(bytes: &[u8]) -> Result<Self> {
if bytes.len() != ENTRY_BYTES {
return Err(malformed("a section entry is not fifty six bytes"));
}
let section = Self {
kind: bytes[0..8].try_into().expect("eight bytes"),
id: u64::from_le_bytes(bytes[8..16].try_into().expect("eight bytes")),
generation: u64::from_le_bytes(bytes[16..24].try_into().expect("eight bytes")),
extents: u32::from_le_bytes(bytes[24..28].try_into().expect("four bytes")),
extent_page: u64::from_le_bytes(bytes[28..36].try_into().expect("eight bytes")),
extent_bytes: u32::from_le_bytes(bytes[36..40].try_into().expect("four bytes")),
hash: u64::from_le_bytes(bytes[40..48].try_into().expect("eight bytes")),
flags: u32::from_le_bytes(bytes[48..52].try_into().expect("four bytes")),
header_bytes: u32::from_le_bytes(bytes[52..56].try_into().expect("four bytes")),
};
if section.extents > MAX_EXTENTS {
return Err(malformed("a section names more extents than the bound allows"));
}
if section.extent_bytes > MAX_EXTENT {
return Err(malformed("a section's extent table is larger than one extent"));
}
Ok(section)
}
#[must_use]
pub fn known(&self) -> bool {
matches!(
&self.kind,
KEY_MAP
| FORWARD_LINK
| ADJACENCY
| SUMMARY
| SKETCHES
| DEGREES
| SORTED_PROJECTION
| RUN_PROJECTION
)
}
#[must_use]
pub fn among(&self, kinds: &[&[u8; 8]]) -> bool {
kinds.iter().any(|kind| self.kind == **kind)
}
#[must_use]
pub fn current(&self, generation: u64) -> bool {
self.generation == generation
}
#[must_use]
pub fn usable(&self, generation: u64) -> bool {
self.known() && self.current(generation)
}
#[must_use]
pub fn refused(&self) -> Option<u64> {
(self.extents == 0).then(|| u64::from(self.header_bytes))
}
}
#[derive(Debug, Clone, Copy)]
pub struct Attachment<'a> {
pub kind: [u8; 8],
pub id: u64,
pub flags: u32,
pub header_bytes: u32,
pub bytes: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Extent {
pub offset: u64,
pub length: u32,
pub hash: u64,
pub first: u64,
}
pub const EXTENT_BYTES: usize = 28;
impl Extent {
pub(crate) fn encode(&self, out: &mut Vec<u8>) -> Result<()> {
if self.length > MAX_EXTENT {
return Err(malformed(format!(
"an extent of {} bytes exceeds the maximum of {MAX_EXTENT}",
self.length
)));
}
out.extend_from_slice(&self.offset.to_le_bytes());
out.extend_from_slice(&self.length.to_le_bytes());
out.extend_from_slice(&self.hash.to_le_bytes());
out.extend_from_slice(&self.first.to_le_bytes());
Ok(())
}
pub(crate) fn decode(bytes: &[u8]) -> Result<Self> {
if bytes.len() != EXTENT_BYTES {
return Err(malformed("an extent entry is not twenty eight bytes"));
}
let extent = Self {
offset: u64::from_le_bytes(bytes[0..8].try_into().expect("eight bytes")),
length: u32::from_le_bytes(bytes[8..12].try_into().expect("four bytes")),
hash: u64::from_le_bytes(bytes[12..20].try_into().expect("eight bytes")),
first: u64::from_le_bytes(bytes[20..28].try_into().expect("eight bytes")),
};
if extent.length > MAX_EXTENT {
return Err(malformed("an extent is larger than the maximum extent"));
}
Ok(extent)
}
}
pub fn encode_extents(extents: &[Extent], out: &mut Vec<u8>) -> Result<()> {
if extents.len() > MAX_EXTENTS as usize {
return Err(malformed("a section names more extents than the bound allows"));
}
for (at, extent) in extents.iter().enumerate() {
if at == 0 {
if extent.first != 0 {
return Err(malformed("a section's first extent does not start at element zero"));
}
} else if extent.first <= extents[at - 1].first {
return Err(malformed("a section's extents are not in element order"));
}
extent.encode(out)?;
}
Ok(())
}
pub fn decode_extents(bytes: &[u8]) -> Result<Vec<Extent>> {
if bytes.len() % EXTENT_BYTES != 0 {
return Err(malformed("an extent table is not a whole number of entries"));
}
let mut extents: Vec<Extent> = Vec::with_capacity(bytes.len() / EXTENT_BYTES);
for chunk in bytes.chunks(EXTENT_BYTES) {
let extent = Extent::decode(chunk)?;
match extents.last() {
None if extent.first != 0 => {
return Err(malformed("a section's first extent does not start at element zero"));
}
Some(previous) if extent.first <= previous.first => {
return Err(malformed("a section's extents are not in element order"));
}
_ => {}
}
extents.push(extent);
}
Ok(extents)
}
#[must_use]
pub fn locate(extents: &[Extent], element: u64) -> Option<(usize, u64)> {
let at = extents.partition_point(|extent| extent.first <= element);
if at == 0 {
return None;
}
Some((at - 1, element - extents[at - 1].first))
}
fn malformed(message: impl Into<String>) -> Error {
Error::invalid_input(format!("invalid rudb section table: {}", message.into()))
}
#[cfg(test)]
mod tests {
use super::*;
fn entry() -> Section {
Section {
kind: *KEY_MAP,
id: 7,
generation: 42,
extents: 3,
extent_page: 1 << 20,
extent_bytes: 84,
hash: 0xdead_beef_cafe_f00d,
flags: 2,
header_bytes: 24,
}
}
#[test]
fn an_entry_takes_fifty_six_bytes_and_round_trips() {
let mut bytes = Vec::new();
entry().encode(&mut bytes).expect("encode");
assert_eq!(bytes.len(), ENTRY_BYTES, "section 3.2 says fifty six");
assert_eq!(Section::decode(&bytes).expect("decode"), entry());
}
#[test]
fn an_unknown_kind_is_carried_and_not_read() {
let mut unknown = entry();
unknown.kind = *b"RUDBZZ9\0";
let mut bytes = Vec::new();
unknown.encode(&mut bytes).expect("an unknown kind still encodes");
let read = Section::decode(&bytes).expect("an unknown kind still decodes");
assert_eq!(read, unknown, "the entry survives a build that does not know it");
assert!(!read.known());
assert!(!read.usable(42), "a kind this build does not know is never read");
}
#[test]
fn the_kinds_the_two_documents_name_are_known() {
for kind in [KEY_MAP, FORWARD_LINK, ADJACENCY, SUMMARY, SKETCHES] {
let mut section = entry();
section.kind = *kind;
assert!(section.known(), "{}", String::from_utf8_lossy(kind));
}
}
#[test]
fn no_two_kinds_share_a_tag() {
let all = [KEY_MAP, FORWARD_LINK, ADJACENCY, SUMMARY, SKETCHES];
for (at, one) in all.iter().enumerate() {
for other in &all[at + 1..] {
assert_ne!(one, other, "{}", String::from_utf8_lossy(*one));
}
}
}
#[test]
fn a_stale_section_is_ignored_rather_than_repaired() {
let section = entry();
assert!(section.usable(42));
assert!(!section.usable(43), "a rewrite invalidates rather than corrupts");
assert!(section.known(), "staleness is not the same question as familiarity");
}
#[test]
fn an_entry_naming_more_extents_than_the_bound_is_refused_at_both_ends() {
let mut oversized = entry();
oversized.extents = MAX_EXTENTS + 1;
assert!(oversized.encode(&mut Vec::new()).is_err(), "a writer's bug stops at the write");
let mut bytes = Vec::new();
entry().encode(&mut bytes).expect("encode");
bytes[24..28].copy_from_slice(&(MAX_EXTENTS + 1).to_le_bytes());
assert!(Section::decode(&bytes).is_err(), "a torn count is not turned into an allocation");
}
#[test]
fn a_short_entry_is_refused_rather_than_read_past() {
let mut bytes = Vec::new();
entry().encode(&mut bytes).expect("encode");
bytes.pop();
assert!(Section::decode(&bytes).is_err());
assert!(Section::decode(&[]).is_err());
}
#[test]
fn an_extent_at_the_maximum_is_allowed_and_one_past_it_is_not() {
let at_bound = Extent { offset: 4096, length: MAX_EXTENT, hash: 9, first: 0 };
let mut bytes = Vec::new();
at_bound.encode(&mut bytes).expect("an extent at the bound encodes");
assert_eq!(bytes.len(), EXTENT_BYTES);
assert_eq!(Extent::decode(&bytes).expect("decode"), at_bound);
let past = Extent { offset: 4096, length: MAX_EXTENT + 1, hash: 9, first: 0 };
assert!(past.encode(&mut Vec::new()).is_err());
}
fn table() -> Vec<Extent> {
vec![
Extent { offset: 1024, length: MAX_EXTENT, hash: 1, first: 0 },
Extent {
offset: 1024 + u64::from(MAX_EXTENT),
length: MAX_EXTENT,
hash: 2,
first: 100,
},
Extent { offset: 1024 + 2 * u64::from(MAX_EXTENT), length: 512, hash: 3, first: 250 },
]
}
#[test]
fn an_extent_table_round_trips() {
let mut bytes = Vec::new();
encode_extents(&table(), &mut bytes).expect("encode");
assert_eq!(bytes.len(), 3 * EXTENT_BYTES);
assert_eq!(decode_extents(&bytes).expect("decode"), table());
}
#[test]
fn an_extent_table_out_of_element_order_is_refused() {
let mut out_of_order = table();
out_of_order.swap(1, 2);
assert!(encode_extents(&out_of_order, &mut Vec::new()).is_err());
let mut bytes = Vec::new();
encode_extents(&table(), &mut bytes).expect("encode");
bytes[EXTENT_BYTES + 20..EXTENT_BYTES + 28].copy_from_slice(&0_u64.to_le_bytes());
assert!(decode_extents(&bytes).is_err(), "a torn element order is refused");
}
#[test]
fn an_extent_table_not_starting_at_element_zero_is_refused() {
let mut shifted = table();
shifted[0].first = 1;
assert!(encode_extents(&shifted, &mut Vec::new()).is_err());
}
#[test]
fn a_partial_extent_table_is_refused_rather_than_truncated() {
let mut bytes = Vec::new();
encode_extents(&table(), &mut bytes).expect("encode");
bytes.truncate(bytes.len() - 1);
assert!(decode_extents(&bytes).is_err());
}
#[test]
fn an_empty_extent_table_is_a_section_with_no_payload() {
let mut bytes = Vec::new();
encode_extents(&[] as &[Extent], &mut bytes).expect("encode");
assert!(bytes.is_empty());
assert!(decode_extents(&bytes).expect("decode").is_empty());
assert_eq!(locate(&[], 0), None);
}
#[test]
fn an_element_resolves_to_the_extent_holding_it() {
let extents = table();
assert_eq!(locate(&extents, 0), Some((0, 0)));
assert_eq!(locate(&extents, 99), Some((0, 99)));
assert_eq!(locate(&extents, 100), Some((1, 0)), "the first element of the second extent");
assert_eq!(locate(&extents, 249), Some((1, 149)));
assert_eq!(locate(&extents, 250), Some((2, 0)));
assert_eq!(locate(&extents, 1_000_000), Some((2, 999_750)), "past the end of the elements");
}
#[test]
fn a_two_gigabyte_payload_is_tens_of_extents_and_not_one_buffer() {
let payload = 600_037_902_u64 * 28 / 8;
let extents = payload.div_ceil(u64::from(MAX_EXTENT));
assert!(extents > 30, "{extents} extents");
assert!(extents < u64::from(MAX_EXTENTS), "{extents} extents is inside the bound");
}
}