radixdb-catalog 1.1.0

Generational logical catalog owner for RadixDB
Documentation
use crate::{CatalogError, CatalogResult};

pub const FORMAT_MAJOR: u16 = 6;
pub const BASELINE_FORMAT_MINOR: u16 = crate::BASELINE_CATALOG_MINOR;
pub const LATEST_FORMAT_MINOR: u16 = crate::LATEST_CATALOG_MINOR;
pub const CATALOG_MAGIC: &[u8; 8] = b"RDX6CAT\0";
pub const PAYLOAD_MAGIC: &[u8; 4] = b"COBJ";
pub const FOOTER_MAGIC: &[u8; 8] = b"RDX6END\0";
pub const HEADER_BYTES: usize = 256;
pub const OBJECT_ENTRY_BYTES: usize = 160;
pub const EDGE_ENTRY_BYTES: usize = 48;
pub const PAYLOAD_HEADER_BYTES: usize = 32;
pub const FIELD_ENTRY_BYTES: usize = 24;
pub const FOOTER_BYTES: usize = 48;

pub const MAX_CATALOG_FILE_BYTES: u64 = 512 * 1024 * 1024;
pub const MAX_CATALOG_OBJECTS: u64 = 262_144;
pub const MAX_CATALOG_EDGES: u64 = 1_048_576;
pub const MAX_PAYLOAD_AREA_BYTES: u64 = 256 * 1024 * 1024;
pub const MAX_STRING_AREA_BYTES: u64 = 128 * 1024 * 1024;
pub const MAX_FIELDS_PER_OBJECT: u64 = 64;
pub const MAX_PAYLOAD_BYTES_PER_OBJECT: u64 = 16 * 1024 * 1024;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BlobRef {
    pub offset: u64,
    pub length: u32,
    pub crc32: u32,
}

impl BlobRef {
    pub const fn absent() -> Self {
        Self {
            offset: 0,
            length: 0,
            crc32: 0,
        }
    }

    pub fn write_at(self, output: &mut [u8], offset: usize) -> CatalogResult<()> {
        put_u64(output, offset, self.offset)?;
        put_u32(output, offset + 8, self.length)?;
        put_u32(output, offset + 12, self.crc32)
    }

    pub fn read_at(input: &[u8], offset: usize) -> CatalogResult<Self> {
        Ok(Self {
            offset: read_u64(input, offset)?,
            length: read_u32(input, offset + 8)?,
            crc32: read_u32(input, offset + 12)?,
        })
    }
}

pub fn checked_usize(value: u64, field: &'static str) -> CatalogResult<usize> {
    usize::try_from(value).map_err(|_| CatalogError::CatalogLimitExceeded {
        field,
        actual: value,
        limit: usize::MAX as u64,
    })
}

pub fn checked_product(count: u64, width: usize, field: &'static str) -> CatalogResult<usize> {
    let bytes = count
        .checked_mul(width as u64)
        .ok_or(CatalogError::InvalidCatalogFormat {
            detail: "directory length multiplication overflow",
        })?;
    checked_usize(bytes, field)
}

pub fn enforce_limit(field: &'static str, actual: u64, limit: u64) -> CatalogResult<()> {
    if actual > limit {
        return Err(CatalogError::CatalogLimitExceeded {
            field,
            actual,
            limit,
        });
    }
    Ok(())
}

pub fn align_8(value: usize) -> CatalogResult<usize> {
    value
        .checked_add(7)
        .map(|value| value & !7)
        .ok_or(CatalogError::InvalidCatalogFormat {
            detail: "alignment overflow",
        })
}

pub fn range<'a>(
    input: &'a [u8],
    offset: u64,
    length: u64,
    detail: &'static str,
) -> CatalogResult<&'a [u8]> {
    let start = checked_usize(offset, detail)?;
    let len = checked_usize(length, detail)?;
    let end = start
        .checked_add(len)
        .ok_or(CatalogError::InvalidCatalogFormat { detail })?;
    input
        .get(start..end)
        .ok_or(CatalogError::InvalidCatalogFormat { detail })
}

pub fn read_array<const N: usize>(input: &[u8], offset: usize) -> CatalogResult<[u8; N]> {
    let end = offset
        .checked_add(N)
        .ok_or(CatalogError::InvalidCatalogFormat {
            detail: "fixed-width field offset overflow",
        })?;
    input
        .get(offset..end)
        .and_then(|bytes| bytes.try_into().ok())
        .ok_or(CatalogError::InvalidCatalogFormat {
            detail: "fixed-width field is out of bounds",
        })
}

pub fn read_u16(input: &[u8], offset: usize) -> CatalogResult<u16> {
    Ok(u16::from_le_bytes(read_array(input, offset)?))
}

pub fn read_u32(input: &[u8], offset: usize) -> CatalogResult<u32> {
    Ok(u32::from_le_bytes(read_array(input, offset)?))
}

pub fn read_u64(input: &[u8], offset: usize) -> CatalogResult<u64> {
    Ok(u64::from_le_bytes(read_array(input, offset)?))
}

pub fn put_bytes(output: &mut [u8], offset: usize, value: &[u8]) -> CatalogResult<()> {
    let end = offset
        .checked_add(value.len())
        .ok_or(CatalogError::InvalidCatalogFormat {
            detail: "fixed-width output field offset overflow",
        })?;
    let target = output
        .get_mut(offset..end)
        .ok_or(CatalogError::InvalidCatalogFormat {
            detail: "fixed-width output field is out of bounds",
        })?;
    target.copy_from_slice(value);
    Ok(())
}

pub fn put_u16(output: &mut [u8], offset: usize, value: u16) -> CatalogResult<()> {
    put_bytes(output, offset, &value.to_le_bytes())
}

pub fn put_u32(output: &mut [u8], offset: usize, value: u32) -> CatalogResult<()> {
    put_bytes(output, offset, &value.to_le_bytes())
}

pub fn put_u64(output: &mut [u8], offset: usize, value: u64) -> CatalogResult<()> {
    put_bytes(output, offset, &value.to_le_bytes())
}

pub fn append_aligned(output: &mut Vec<u8>) -> CatalogResult<usize> {
    let aligned = align_8(output.len())?;
    output.resize(aligned, 0);
    Ok(aligned)
}

pub fn optional_id_bytes(id: Option<crate::ObjectId>) -> [u8; 16] {
    id.map(crate::ObjectId::into_bytes).unwrap_or([0; 16])
}