use super::codec::{CanonicalDecodeError, Decoder, Encoder};
use super::volume::{decode_config, encode_config};
use crate::foundation::{Digest, GenerationId, VolumeId};
use crate::model::{VolumeConfig, VolumeConfigError};
use crate::storage::{ObjectId, ObjectKind};
use std::cmp::Ordering;
use thiserror::Error;
const DOMAIN: &[u8] = b"acyclic-fs-generation-export-v1\0";
const VERSION: u16 = 1;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GenerationExportManifest {
pub volume_id: VolumeId,
pub config: VolumeConfig,
pub generation_root: ObjectId,
pub generation_id: GenerationId,
pub objects: Vec<ObjectId>,
pub file_count: u64,
}
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum GenerationExportManifestError {
#[error(transparent)]
Config(#[from] VolumeConfigError),
#[error(transparent)]
Decode(#[from] CanonicalDecodeError),
#[error("export manifest root is not a generation root")]
WrongRootKind,
#[error("export manifest generation identity does not match its root")]
GenerationMismatch,
#[error("export manifest closure omits its generation root")]
MissingRoot,
#[error("export manifest objects are not strictly canonically ordered")]
NonCanonicalObjectOrder,
#[error("export manifest object count exceeds its bound")]
TooManyObjects,
#[error("export manifest file count exceeds its bound")]
TooManyFiles,
}
pub fn encode_generation_export_manifest(
manifest: &GenerationExportManifest,
) -> Result<Vec<u8>, GenerationExportManifestError> {
validate_generation_export_manifest(
manifest,
manifest.config.limits.maximum_objects_per_generation,
)?;
let count = u32::try_from(manifest.objects.len())
.map_err(|_| GenerationExportManifestError::TooManyObjects)?;
let mut encoder = Encoder::new(DOMAIN, VERSION);
encoder.fixed(&manifest.volume_id.into_bytes());
encode_config(&mut encoder, manifest.config);
encode_object_id(&mut encoder, manifest.generation_root);
encoder.fixed(manifest.generation_id.digest().as_bytes());
encoder.u64(manifest.file_count);
encoder.u32(count);
for object in &manifest.objects {
encode_object_id(&mut encoder, *object);
}
Ok(encoder.finish())
}
pub fn decode_generation_export_manifest(
bytes: &[u8],
maximum_bytes: u64,
maximum_objects: u64,
) -> Result<GenerationExportManifest, GenerationExportManifestError> {
let mut decoder = Decoder::new(bytes, DOMAIN, VERSION, maximum_bytes)?;
let volume_id = VolumeId::from_bytes(decoder.fixed()?);
let config = decode_config(&mut decoder)?.validate()?;
let generation_root = decode_object_id(&mut decoder)?;
let generation_id = GenerationId::new(Digest::from_bytes(decoder.fixed()?));
let file_count = decoder.u64()?;
let count = decoder.u32()?;
let admitted = maximum_objects.min(config.limits.maximum_objects_per_generation);
if u64::from(count) > admitted {
return Err(GenerationExportManifestError::TooManyObjects);
}
let capacity =
usize::try_from(count).map_err(|_| GenerationExportManifestError::TooManyObjects)?;
let mut objects = Vec::new();
objects
.try_reserve_exact(capacity)
.map_err(|_| CanonicalDecodeError::AllocationFailed)?;
for _ in 0..count {
objects.push(decode_object_id(&mut decoder)?);
}
decoder.finish()?;
let manifest = GenerationExportManifest {
volume_id,
config,
generation_root,
generation_id,
objects,
file_count,
};
validate_generation_export_manifest(&manifest, maximum_objects)?;
Ok(manifest)
}
pub fn validate_generation_export_manifest(
manifest: &GenerationExportManifest,
maximum_objects: u64,
) -> Result<(), GenerationExportManifestError> {
manifest.config.validate()?;
if manifest.generation_root.kind != ObjectKind::GenerationRoot {
return Err(GenerationExportManifestError::WrongRootKind);
}
if manifest.generation_id.digest() != manifest.generation_root.digest {
return Err(GenerationExportManifestError::GenerationMismatch);
}
if manifest.file_count > manifest.config.limits.maximum_files_per_generation {
return Err(GenerationExportManifestError::TooManyFiles);
}
let count = u64::try_from(manifest.objects.len()).unwrap_or(u64::MAX);
if count > maximum_objects || count > manifest.config.limits.maximum_objects_per_generation {
return Err(GenerationExportManifestError::TooManyObjects);
}
if !manifest.objects.contains(&manifest.generation_root) {
return Err(GenerationExportManifestError::MissingRoot);
}
if manifest.objects.windows(2).any(|pair| {
let [left, right] = pair else {
unreachable!("windows(2) always yields exactly two elements")
};
compare_object_id(*left, *right) != Ordering::Less
}) {
return Err(GenerationExportManifestError::NonCanonicalObjectOrder);
}
Ok(())
}
fn compare_object_id(left: ObjectId, right: ObjectId) -> Ordering {
left.kind
.canonical_tag()
.cmp(&right.kind.canonical_tag())
.then_with(|| left.digest.cmp(&right.digest))
}
fn encode_object_id(encoder: &mut Encoder, object: ObjectId) {
encoder.u8(object.kind.canonical_tag());
encoder.fixed(object.digest.as_bytes());
}
fn decode_object_id(decoder: &mut Decoder<'_>) -> Result<ObjectId, CanonicalDecodeError> {
let kind = ObjectKind::from_canonical_tag(decoder.u8()?)
.map_err(|error| CanonicalDecodeError::Invariant(error.to_string()))?;
Ok(ObjectId {
kind,
digest: Digest::from_bytes(decoder.fixed()?),
})
}
#[cfg(test)]
#[path = "tests/export.rs"]
mod tests;