#![forbid(unsafe_code)]
use crate::btree::{max_inline_value, max_key_len};
use crate::error::{Error, Result};
pub const DOC_HEADER_SIZE: usize = 16;
const OFF_COLLECTION_ID: usize = 0;
const OFF_TYPE_VERSION: usize = 4;
const OFF_PAYLOAD_LEN: usize = 8;
const OFF_PAYLOAD_CRC32C: usize = 12;
pub const MAX_INLINE_DOC: usize = max_inline_value(max_key_len());
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct DocumentHeader {
pub collection_id: u32,
pub type_version: u32,
pub payload_len: u32,
pub payload_crc32c: u32,
}
impl DocumentHeader {
pub fn write_to(&self, dst: &mut Vec<u8>) {
debug_assert_eq!(
OFF_PAYLOAD_CRC32C + 4,
DOC_HEADER_SIZE,
"header offsets must cover exactly DOC_HEADER_SIZE bytes"
);
dst.reserve(DOC_HEADER_SIZE);
dst.extend_from_slice(&self.collection_id.to_le_bytes());
dst.extend_from_slice(&self.type_version.to_le_bytes());
dst.extend_from_slice(&self.payload_len.to_le_bytes());
dst.extend_from_slice(&self.payload_crc32c.to_le_bytes());
}
pub fn read_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() < DOC_HEADER_SIZE {
return Err(Error::Corruption { page_id: 0 });
}
let collection_id = u32::from_le_bytes(read_array(bytes, OFF_COLLECTION_ID));
let type_version = u32::from_le_bytes(read_array(bytes, OFF_TYPE_VERSION));
let payload_len = u32::from_le_bytes(read_array(bytes, OFF_PAYLOAD_LEN));
let payload_crc32c = u32::from_le_bytes(read_array(bytes, OFF_PAYLOAD_CRC32C));
Ok(Self {
collection_id,
type_version,
payload_len,
payload_crc32c,
})
}
}
fn read_array<const N: usize>(bytes: &[u8], off: usize) -> [u8; N] {
debug_assert!(off + N <= bytes.len(), "header field out of bounds");
let mut out = [0u8; N];
out.copy_from_slice(&bytes[off..off + N]);
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn header_round_trip() {
let h = DocumentHeader {
collection_id: 0x1122_3344,
type_version: 5,
payload_len: 99,
payload_crc32c: 0xDEAD_BEEF,
};
let mut buf = Vec::new();
h.write_to(&mut buf);
assert_eq!(buf.len(), DOC_HEADER_SIZE);
let decoded = DocumentHeader::read_from(&buf).expect("decode");
assert_eq!(decoded, h);
}
#[test]
fn header_layout_little_endian() {
let h = DocumentHeader {
collection_id: 0x0403_0201,
type_version: 0x0807_0605,
payload_len: 0x0C0B_0A09,
payload_crc32c: 0x100F_0E0D,
};
let mut buf = Vec::new();
h.write_to(&mut buf);
assert_eq!(
&buf[..],
&[
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10,
]
);
}
#[test]
fn header_short_input_errors() {
let err = DocumentHeader::read_from(&[0u8; DOC_HEADER_SIZE - 1])
.expect_err("short input rejected");
assert!(matches!(err, Error::Corruption { page_id: 0 }));
}
#[test]
fn max_inline_doc_is_positive() {
const {
assert!(
MAX_INLINE_DOC > DOC_HEADER_SIZE,
"MAX_INLINE_DOC must leave room for at least one payload byte",
);
}
}
}