pagedb 0.1.0-beta.6

Encrypted, portable, embedded page store with B+ tree and segment-file surfaces.
Documentation
use crate::Result;
use crate::crypto::{Cipher, Nonce};
use crate::errors::PagedbError;

use super::auth::{footer_aad, mac_hk};
use super::fields::{
    FOOTER_CLEARTEXT_END, FOOTER_FIELDS_END, FOOTER_HEADER_MAC_LEN, FORMAT_VERSION,
    MANIFEST_TAG_LEN, SegmentFooterFields, max_manifest_len,
};

pub fn encode_segment_footer(
    fields: &SegmentFooterFields,
    manifest: &[u8],
    hk: &crate::crypto::keys::DerivedKey,
    cipher: &Cipher,
    page_size: usize,
) -> Result<Vec<u8>> {
    if fields.format_version != FORMAT_VERSION {
        return Err(PagedbError::Unsupported);
    }
    if page_size < FOOTER_CLEARTEXT_END + MANIFEST_TAG_LEN {
        return Err(PagedbError::Unsupported);
    }
    if manifest.len() > max_manifest_len(page_size) {
        return Err(PagedbError::ManifestTooLarge);
    }
    if cipher.id().as_byte() != fields.cipher_id {
        return Err(PagedbError::Unsupported);
    }

    let manifest_offset =
        u32::try_from(FOOTER_CLEARTEXT_END).map_err(|_| PagedbError::Unsupported)?;
    let manifest_len = u32::try_from(manifest.len()).map_err(|_| PagedbError::ManifestTooLarge)?;
    let mut out = vec![0u8; page_size];
    let mut offset = 0usize;
    out[offset..offset + 8].copy_from_slice(&super::fields::MAGIC);
    offset += 8;
    out[offset..offset + 2].copy_from_slice(&fields.format_version.to_le_bytes());
    offset += 2;
    out[offset] = fields.cipher_id;
    offset += 1;
    out[offset..offset + 16].copy_from_slice(&fields.segment_id);
    offset += 16;
    out[offset..offset + 16].copy_from_slice(&fields.parent_file_id);
    offset += 16;
    out[offset..offset + 16].copy_from_slice(&fields.realm_id.0);
    offset += 16;
    out[offset..offset + 8].copy_from_slice(&fields.mk_epoch.to_le_bytes());
    offset += 8;
    out[offset..offset + 8].copy_from_slice(&fields.page_count.to_le_bytes());
    offset += 8;
    out[offset..offset + 8].copy_from_slice(&fields.total_bytes.to_le_bytes());
    offset += 8;
    out[offset..offset + 8].copy_from_slice(&fields.final_counter.to_le_bytes());
    offset += 8;
    out[offset..offset + 4].copy_from_slice(&manifest_offset.to_le_bytes());
    offset += 4;
    out[offset..offset + 4].copy_from_slice(&manifest_len.to_le_bytes());
    offset += 4;
    out[offset..offset + 8].copy_from_slice(&fields.index_start_page.to_le_bytes());
    offset += 8;
    out[offset..offset + 4].copy_from_slice(&fields.index_page_count.to_le_bytes());
    offset += 4;
    debug_assert_eq!(offset, FOOTER_FIELDS_END);
    let mac = mac_hk(hk, &out[..offset])?;
    out[offset..offset + FOOTER_HEADER_MAC_LEN].copy_from_slice(&mac);
    offset += FOOTER_HEADER_MAC_LEN;
    debug_assert_eq!(offset, FOOTER_CLEARTEXT_END);

    let nonce_counter = fields
        .final_counter
        .checked_add(1)
        .filter(|counter| *counter <= Nonce::COUNTER_MAX)
        .ok_or(PagedbError::NonceCounterExhausted)?;
    let mut file_id = [0u8; 6];
    file_id.copy_from_slice(&fields.segment_id[..6]);
    let nonce = Nonce::from_parts(file_id, nonce_counter);
    let manifest_end = FOOTER_CLEARTEXT_END
        .checked_add(manifest.len())
        .ok_or_else(|| PagedbError::arithmetic_overflow("footer manifest end"))?;
    out[FOOTER_CLEARTEXT_END..manifest_end].copy_from_slice(manifest);
    let tag = cipher.encrypt(
        &nonce,
        &footer_aad(fields),
        &mut out[FOOTER_CLEARTEXT_END..manifest_end],
    )?;
    let tag_end = manifest_end
        .checked_add(MANIFEST_TAG_LEN)
        .ok_or_else(|| PagedbError::arithmetic_overflow("footer manifest tag end"))?;
    out[manifest_end..tag_end].copy_from_slice(&tag);
    Ok(out)
}