radixdb-storage 1.1.0

Storage contracts and physical persistence engine for RadixDB
Documentation
use super::super::manifest::codec::{
    finish_file, put_u16, put_u32, put_u64, read_array, read_u16, read_u32, read_u64, require_zero,
    validate_canonical_directory, validate_file_shell_with_limit, FOOTER_BYTES, FORMAT_MAJOR,
    FORMAT_MINOR, HEADER_BYTES,
};
use super::super::{
    CatalogGeneration, CatalogId, CatalogRootRef, DatabaseGeneration, DatabaseId,
    DatabaseManifestRootRef, FormatError, FormatResult, ManifestGeneration, ManifestId, SnapshotId,
};
use super::{
    SnapshotManifest, SnapshotMember, SnapshotMemberKind, MAX_SNAPSHOT_MANIFEST_BYTES,
    MAX_SNAPSHOT_MEMBERS, SNAPSHOT_MEMBER_BYTES,
};

const KIND: &str = "snapshot manifest";
const MAGIC: [u8; 8] = *b"RDX6SNP\0";
const COPY_COMPLETE: u64 = 1;

pub fn encode_snapshot_manifest(manifest: &SnapshotManifest) -> FormatResult<Vec<u8>> {
    let directory_length = manifest
        .members()
        .len()
        .checked_mul(SNAPSHOT_MEMBER_BYTES)
        .ok_or(FormatError::InvalidSnapshot {
            detail: "member directory length overflows",
        })?;
    let body_length =
        HEADER_BYTES
            .checked_add(directory_length)
            .ok_or(FormatError::InvalidSnapshot {
                detail: "snapshot manifest length overflows",
            })?;
    let file_length =
        body_length
            .checked_add(FOOTER_BYTES)
            .ok_or(FormatError::InvalidSnapshot {
                detail: "snapshot manifest length overflows",
            })?;
    if file_length > MAX_SNAPSHOT_MANIFEST_BYTES {
        return Err(FormatError::SnapshotLimitExceeded {
            field: "manifest bytes",
            actual: file_length as u64,
            limit: MAX_SNAPSHOT_MANIFEST_BYTES as u64,
        });
    }

    let mut output = vec![0_u8; body_length];
    output[..8].copy_from_slice(&MAGIC);
    put_u16(&mut output, 8, FORMAT_MAJOR);
    put_u16(&mut output, 10, FORMAT_MINOR);
    put_u32(&mut output, 12, HEADER_BYTES as u32);
    output[24..40].copy_from_slice(manifest.snapshot_id().as_bytes());
    output[40..56].copy_from_slice(manifest.database_id().as_bytes());
    put_u64(&mut output, 56, manifest.database_generation().get());
    output[64..80].copy_from_slice(manifest.database_manifest().id().as_bytes());
    output[80..96].copy_from_slice(manifest.catalog().id().as_bytes());
    put_u64(&mut output, 96, manifest.members().len() as u64);
    put_u64(&mut output, 104, HEADER_BYTES as u64);
    put_u64(&mut output, 112, directory_length as u64);
    put_u64(&mut output, 120, manifest.created_unix_ns());
    put_u64(&mut output, 128, COPY_COMPLETE);
    output[136..168].copy_from_slice(manifest.database_manifest().body_sha256());
    output[168..200].copy_from_slice(manifest.catalog().body_sha256());

    for (index, member) in manifest.members().iter().copied().enumerate() {
        let start = HEADER_BYTES + index * SNAPSHOT_MEMBER_BYTES;
        let entry = &mut output[start..start + SNAPSHOT_MEMBER_BYTES];
        put_u16(entry, 0, member.kind().tag());
        put_u16(entry, 2, member.format_version());
        put_u32(entry, 4, member.flags());
        entry[8..24].copy_from_slice(&member.id());
        put_u64(entry, 24, member.generation());
        put_u64(entry, 32, member.byte_length());
        entry[40..72].copy_from_slice(&member.body_sha256());
        put_u16(entry, 72, member.locator_shard());
        put_u16(entry, 74, member.locator_suffix().tag());
    }

    finish_file(&mut output);
    Ok(output)
}

pub fn decode_snapshot_manifest(bytes: &[u8]) -> FormatResult<SnapshotManifest> {
    validate_file_shell_with_limit(bytes, KIND, &MAGIC, MAX_SNAPSHOT_MANIFEST_BYTES).map_err(
        |error| match error {
            FormatError::ManifestLimitExceeded {
                field,
                actual,
                limit,
                ..
            } => FormatError::SnapshotLimitExceeded {
                field,
                actual,
                limit,
            },
            FormatError::ManifestChecksumMismatch { scope, .. } => {
                FormatError::SnapshotChecksumMismatch { scope }
            }
            FormatError::InvalidManifest { detail, .. } => FormatError::InvalidSnapshot { detail },
            other => other,
        },
    )?;
    if read_u64(bytes, 128) != COPY_COMPLETE {
        return invalid("COPY_COMPLETE is absent or unknown header flags are set");
    }
    require_zero(bytes, 200..248, KIND, "reserved header bytes are non-zero")
        .map_err(map_manifest_error)?;

    let member_count = read_u64(bytes, 96);
    let directory = validate_canonical_directory(
        bytes,
        KIND,
        member_count,
        MAX_SNAPSHOT_MEMBERS as u64,
        read_u64(bytes, 104),
        read_u64(bytes, 112),
        SNAPSHOT_MEMBER_BYTES as u64,
    )
    .map_err(map_manifest_error)?;
    let member_count = usize::try_from(member_count).map_err(|_| FormatError::InvalidSnapshot {
        detail: "member count does not fit this platform",
    })?;
    let mut members = Vec::new();
    members
        .try_reserve_exact(member_count)
        .map_err(|_| FormatError::SnapshotLimitExceeded {
            field: "member allocation",
            actual: member_count as u64,
            limit: MAX_SNAPSHOT_MEMBERS as u64,
        })?;
    let mut previous_key = None;
    for entry in bytes[directory].chunks_exact(SNAPSHOT_MEMBER_BYTES) {
        require_zero(entry, 76..96, KIND, "member reserved bytes are non-zero")
            .map_err(map_manifest_error)?;
        let member = SnapshotMember::from_persisted(
            SnapshotMemberKind::from_tag(read_u16(entry, 0))?,
            read_u16(entry, 2),
            read_u32(entry, 4),
            read_array(entry, 8),
            read_u64(entry, 24),
            read_u64(entry, 32),
            read_array(entry, 40),
            read_u16(entry, 72),
            read_u16(entry, 74),
        )?;
        let key = (member.kind(), member.id());
        if previous_key.is_some_and(|previous| previous >= key) {
            return invalid("members are not strictly sorted by kind and identity");
        }
        previous_key = Some(key);
        members.push(member);
    }

    let database_generation = DatabaseGeneration::new(read_u64(bytes, 56))?;
    let database_manifest = DatabaseManifestRootRef::new(
        ManifestId::from_bytes(read_array(bytes, 64))?,
        ManifestGeneration::new(database_generation.get())?,
        read_array(bytes, 136),
    );
    let catalog_member = members
        .iter()
        .find(|member| member.kind() == SnapshotMemberKind::Catalog)
        .ok_or(FormatError::InvalidSnapshot {
            detail: "snapshot has no catalog member",
        })?;
    let catalog = CatalogRootRef::new(
        CatalogId::from_bytes(read_array(bytes, 80))?,
        CatalogGeneration::new(catalog_member.generation())?,
        read_array(bytes, 168),
    );
    SnapshotManifest::new(
        SnapshotId::from_bytes(read_array(bytes, 24))?,
        DatabaseId::from_bytes(read_array(bytes, 40))?,
        database_generation,
        database_manifest,
        catalog,
        members,
        read_u64(bytes, 120),
    )
}

fn map_manifest_error(error: FormatError) -> FormatError {
    match error {
        FormatError::ManifestLimitExceeded {
            field,
            actual,
            limit,
            ..
        } => FormatError::SnapshotLimitExceeded {
            field,
            actual,
            limit,
        },
        FormatError::ManifestChecksumMismatch { scope, .. } => {
            FormatError::SnapshotChecksumMismatch { scope }
        }
        FormatError::InvalidManifest { detail, .. } => FormatError::InvalidSnapshot { detail },
        other => other,
    }
}

fn invalid<T>(detail: &'static str) -> FormatResult<T> {
    Err(FormatError::InvalidSnapshot { detail })
}