radixdb-storage 1.1.0

Storage contracts and physical persistence engine for RadixDB
Documentation
use super::super::{
    ArtifactId, ArtifactKind, ArtifactRef, DatabaseGeneration, FormatError, FormatResult,
    MAX_MANIFEST_FILE_BYTES,
};
use radixdb_catalog::ObjectId;

pub(crate) const HEADER_BYTES: usize = 256;
pub(crate) const FOOTER_BYTES: usize = 48;
pub(super) const MAX_MANIFEST_BYTES: usize = MAX_MANIFEST_FILE_BYTES as usize;
pub(crate) const FORMAT_MAJOR: u16 = 6;
pub(crate) const FORMAT_MINOR: u16 = 0;
pub(super) const ARTIFACT_REF_BYTES: usize = 88;
const FOOTER_MAGIC: [u8; 8] = *b"RDX6END\0";

pub(super) fn validate_file_shell(
    bytes: &[u8],
    kind: &'static str,
    magic: &[u8; 8],
) -> FormatResult<()> {
    validate_file_shell_with_limit(bytes, kind, magic, MAX_MANIFEST_BYTES)
}

pub(crate) fn validate_file_shell_with_limit(
    bytes: &[u8],
    kind: &'static str,
    magic: &[u8; 8],
    maximum_bytes: usize,
) -> FormatResult<()> {
    if bytes.len() < HEADER_BYTES + FOOTER_BYTES {
        return Err(invalid(kind, "file is shorter than header plus footer"));
    }
    if bytes.len() > maximum_bytes {
        return Err(limit(
            kind,
            "file bytes",
            bytes.len() as u64,
            maximum_bytes as u64,
        ));
    }
    if &bytes[..8] != magic {
        return Err(invalid(kind, "magic mismatch"));
    }
    let major = read_u16(bytes, 8);
    let minor = read_u16(bytes, 10);
    if (major, minor) != (FORMAT_MAJOR, FORMAT_MINOR) {
        return Err(FormatError::UnsupportedFormatVersion {
            owner: kind,
            major,
            minor,
        });
    }
    if read_u32(bytes, 12) != HEADER_BYTES as u32 {
        return Err(invalid(kind, "header length is not 256"));
    }
    if read_u64(bytes, 16) != bytes.len() as u64 {
        return Err(invalid(kind, "header file length mismatch"));
    }
    if bytes[252..256].iter().any(|byte| *byte != 0) {
        return Err(invalid(kind, "reserved header bytes are non-zero"));
    }
    if read_u32(bytes, 248) != radixdb_core::crc32_ieee(&bytes[..248]) {
        return Err(FormatError::ManifestChecksumMismatch {
            kind,
            scope: "header",
        });
    }

    let footer = bytes.len() - FOOTER_BYTES;
    if bytes[footer..footer + 8] != FOOTER_MAGIC {
        return Err(invalid(kind, "footer magic mismatch"));
    }
    if read_u64(bytes, footer + 8) != bytes.len() as u64 {
        return Err(invalid(kind, "footer file length mismatch"));
    }
    if bytes[footer + 16..] != radixdb_core::sha256_digest(&bytes[..footer]) {
        return Err(FormatError::ManifestChecksumMismatch {
            kind,
            scope: "body SHA-256",
        });
    }
    Ok(())
}

pub(crate) fn finish_file(output: &mut Vec<u8>) {
    let file_length = output.len() + FOOTER_BYTES;
    put_u64(output, 16, file_length as u64);
    let header_crc = radixdb_core::crc32_ieee(&output[..248]);
    put_u32(output, 248, header_crc);
    let body_sha = radixdb_core::sha256_digest(output);
    output.extend_from_slice(&FOOTER_MAGIC);
    output.extend_from_slice(&(file_length as u64).to_le_bytes());
    output.extend_from_slice(&body_sha);
}

pub(crate) fn validate_canonical_directory(
    bytes: &[u8],
    kind: &'static str,
    count: u64,
    count_limit: u64,
    offset: u64,
    length: u64,
    entry_bytes: u64,
) -> FormatResult<std::ops::Range<usize>> {
    if count > count_limit {
        return Err(limit(kind, "directory count", count, count_limit));
    }
    let expected_length = count
        .checked_mul(entry_bytes)
        .ok_or_else(|| invalid(kind, "directory length multiplication overflow"))?;
    if length != expected_length {
        return Err(invalid(kind, "directory length does not match count"));
    }
    if count == 0 {
        if offset != 0 {
            return Err(invalid(kind, "empty directory has a non-zero offset"));
        }
        if bytes.len() != HEADER_BYTES + FOOTER_BYTES {
            return Err(invalid(kind, "empty manifest has trailing body bytes"));
        }
        return Ok(0..0);
    }
    if offset != HEADER_BYTES as u64 || !offset.is_multiple_of(8) {
        return Err(invalid(kind, "directory has a non-canonical offset"));
    }
    let end = offset
        .checked_add(length)
        .ok_or_else(|| invalid(kind, "directory range overflows"))?;
    if end != (bytes.len() - FOOTER_BYTES) as u64 {
        return Err(invalid(
            kind,
            "directory does not exactly own the file body",
        ));
    }
    let start = usize::try_from(offset)
        .map_err(|_| invalid(kind, "directory offset does not fit this platform"))?;
    let end = usize::try_from(end)
        .map_err(|_| invalid(kind, "directory end does not fit this platform"))?;
    Ok(start..end)
}

pub(super) fn encode_artifact_ref(output: &mut [u8], reference: ArtifactRef) {
    output[..16].copy_from_slice(reference.id().as_bytes());
    put_u16(output, 16, reference.kind().tag());
    put_u16(output, 18, reference.codec_version());
    put_u32(output, 20, 0);
    put_u64(output, 24, reference.creation_generation().get());
    put_u64(output, 32, reference.byte_length());
    output[40..72].copy_from_slice(reference.body_sha256());
    put_u16(output, 72, u16::from(reference.locator().shard()));
    put_u16(output, 74, reference.locator().suffix().tag());
}

pub(super) fn decode_artifact_ref(bytes: &[u8], owner: &'static str) -> FormatResult<ArtifactRef> {
    if bytes.len() != ARTIFACT_REF_BYTES {
        return Err(invalid(owner, "ArtifactRef width mismatch"));
    }
    require_zero(
        bytes,
        76..88,
        owner,
        "ArtifactRef reserved bytes are non-zero",
    )?;
    ArtifactRef::from_persisted(
        ArtifactId::from_bytes(read_array(bytes, 0))?,
        ArtifactKind::from_tag(read_u16(bytes, 16))?,
        read_u16(bytes, 18),
        read_u32(bytes, 20),
        DatabaseGeneration::new(read_u64(bytes, 24))?,
        read_u64(bytes, 32),
        read_array(bytes, 40),
        read_u16(bytes, 72),
        read_u16(bytes, 74),
    )
}

pub(super) fn decode_optional_artifact_ref(
    bytes: &[u8],
    owner: &'static str,
) -> FormatResult<Option<ArtifactRef>> {
    if bytes.iter().all(|byte| *byte == 0) {
        return Ok(None);
    }
    decode_artifact_ref(bytes, owner).map(Some)
}

pub(super) fn decode_user_object_id(
    bytes: [u8; 16],
    kind: &'static str,
    detail: &'static str,
) -> FormatResult<ObjectId> {
    ObjectId::from_user_bytes(bytes).map_err(|_| invalid(kind, detail))
}

pub(crate) fn require_zero(
    bytes: &[u8],
    range: std::ops::Range<usize>,
    kind: &'static str,
    detail: &'static str,
) -> FormatResult<()> {
    if bytes[range].iter().any(|byte| *byte != 0) {
        return Err(invalid(kind, detail));
    }
    Ok(())
}

pub(crate) const fn invalid(kind: &'static str, detail: &'static str) -> FormatError {
    FormatError::InvalidManifest { kind, detail }
}

pub(crate) const fn limit(
    kind: &'static str,
    field: &'static str,
    actual: u64,
    limit: u64,
) -> FormatError {
    FormatError::ManifestLimitExceeded {
        kind,
        field,
        actual,
        limit,
    }
}

pub(crate) fn read_array<const N: usize>(bytes: &[u8], offset: usize) -> [u8; N] {
    bytes[offset..offset + N]
        .try_into()
        .expect("manifest range was validated before fixed-width read")
}

pub(crate) fn read_u16(bytes: &[u8], offset: usize) -> u16 {
    u16::from_le_bytes(read_array(bytes, offset))
}

pub(crate) fn read_u32(bytes: &[u8], offset: usize) -> u32 {
    u32::from_le_bytes(read_array(bytes, offset))
}

pub(crate) fn read_u64(bytes: &[u8], offset: usize) -> u64 {
    u64::from_le_bytes(read_array(bytes, offset))
}

pub(crate) fn put_u16(bytes: &mut [u8], offset: usize, value: u16) {
    bytes[offset..offset + 2].copy_from_slice(&value.to_le_bytes());
}

pub(crate) fn put_u32(bytes: &mut [u8], offset: usize, value: u32) {
    bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
}

pub(crate) fn put_u64(bytes: &mut [u8], offset: usize, value: u64) {
    bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
}