use crate::atp::manifest::{ChunkBoundary, ChunkPlan};
pub(crate) use crate::atp::manifest::{ChunkMetadata, SparseHoleMetadata};
use profiles::ChunkingProfile as ChunkingProfileTrait;
pub mod artifact;
pub mod bulk_file;
pub mod dedupe;
pub mod media;
pub mod profiles;
pub mod sparse_image;
pub mod stream;
pub mod sync_tree;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ChunkingProfile {
BulkFile,
SyncTree,
Media,
SparseImage,
Artifact,
Stream,
}
impl ChunkingProfile {
pub const ALL: [Self; 6] = [
Self::BulkFile,
Self::SyncTree,
Self::Media,
Self::SparseImage,
Self::Artifact,
Self::Stream,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::BulkFile => "bulk-file",
Self::SyncTree => "sync-tree",
Self::Media => "media",
Self::SparseImage => "sparse-image",
Self::Artifact => "artifact",
Self::Stream => "stream",
}
}
#[must_use]
pub fn recommended_chunk_plan(self, object_size_bytes: u64) -> ChunkPlan {
match self {
Self::BulkFile => bulk_file::BulkFileProfile::chunk_plan(object_size_bytes),
Self::SyncTree => sync_tree::SyncTreeProfile::chunk_plan(object_size_bytes),
Self::Media => media::MediaProfile::chunk_plan(object_size_bytes),
Self::SparseImage => sparse_image::SparseImageProfile::chunk_plan(object_size_bytes),
Self::Artifact => artifact::ArtifactProfile::chunk_plan(object_size_bytes),
Self::Stream => stream::StreamProfile::chunk_plan(object_size_bytes),
}
}
#[must_use = "this returns computed boundaries; consume or inspect the result"]
pub fn compute_boundaries(
self,
data: &[u8],
) -> Result<Vec<ChunkBoundary>, ChunkingProfileError> {
use profiles::ChunkingProfile as ChunkingProfileTrait;
match self {
Self::BulkFile => bulk_file::BulkFileProfile::compute_boundaries(data),
Self::SyncTree => sync_tree::SyncTreeProfile::compute_boundaries(data),
Self::Media => media::MediaProfile::compute_boundaries(data),
Self::SparseImage => sparse_image::SparseImageProfile::compute_boundaries(data),
Self::Artifact => artifact::ArtifactProfile::compute_boundaries(data),
Self::Stream => stream::StreamProfile::compute_boundaries(data),
}
}
#[must_use]
pub const fn supports_deduplication(self) -> bool {
matches!(self, Self::SyncTree | Self::Artifact | Self::Stream)
}
#[must_use]
pub const fn supports_incremental_chunking(self) -> bool {
matches!(
self,
Self::SyncTree | Self::Media | Self::Artifact | Self::Stream
)
}
#[must_use]
pub const fn supports_streaming(self) -> bool {
matches!(self, Self::Media | Self::Stream)
}
#[must_use]
pub const fn optimizes_for_deduplication(self) -> bool {
matches!(self, Self::SyncTree | Self::Artifact)
}
#[must_use]
pub const fn supports_sparse_data(self) -> bool {
matches!(self, Self::SparseImage)
}
#[must_use]
pub const fn provides_reproducible_chunking(self) -> bool {
matches!(self, Self::BulkFile | Self::Artifact)
}
}
impl std::fmt::Display for ChunkingProfile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl std::str::FromStr for ChunkingProfile {
type Err = ChunkingProfileError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"bulk-file" => Ok(Self::BulkFile),
"sync-tree" => Ok(Self::SyncTree),
"media" => Ok(Self::Media),
"sparse-image" => Ok(Self::SparseImage),
"artifact" => Ok(Self::Artifact),
"stream" => Ok(Self::Stream),
_ => Err(ChunkingProfileError::InvalidProfile(s.to_string())),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChunkingProfileError {
InvalidProfile(String),
UnsupportedOperation(ChunkingProfile, String),
InvalidChunkParameters(String),
SparseHoleDetectionFailed(String),
BuildContextValidationFailed(String),
StreamSequencingError(String),
}
impl std::fmt::Display for ChunkingProfileError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidProfile(name) => write!(f, "invalid chunking profile: {name}"),
Self::UnsupportedOperation(profile, op) => {
write!(f, "unsupported operation '{op}' for profile {profile}")
}
Self::InvalidChunkParameters(msg) => write!(f, "invalid chunk parameters: {msg}"),
Self::SparseHoleDetectionFailed(msg) => {
write!(f, "sparse hole detection failed: {msg}")
}
Self::BuildContextValidationFailed(msg) => {
write!(f, "build context validation failed: {msg}")
}
Self::StreamSequencingError(msg) => {
write!(f, "stream sequencing error: {msg}")
}
}
}
}
impl std::error::Error for ChunkingProfileError {}
#[cfg(test)]
mod tests {
use super::*;
use crate::atp::manifest::{ChunkStrategy, ProofStrength, ThroughputTier};
#[test]
fn chunking_profile_all_variants_listed() {
assert_eq!(ChunkingProfile::ALL.len(), 6);
for profile in ChunkingProfile::ALL {
assert!(!profile.name().is_empty());
}
}
#[test]
fn chunking_profile_string_conversion() {
for profile in ChunkingProfile::ALL {
let name = profile.name();
let parsed: ChunkingProfile = name.parse().unwrap();
assert_eq!(parsed, profile);
}
let result: Result<ChunkingProfile, _> = "invalid-profile".parse();
assert!(result.is_err());
}
#[test]
fn chunking_profile_properties() {
assert!(ChunkingProfile::Media.supports_streaming());
assert!(ChunkingProfile::Stream.supports_streaming());
assert!(!ChunkingProfile::BulkFile.supports_streaming());
assert!(ChunkingProfile::SyncTree.optimizes_for_deduplication());
assert!(ChunkingProfile::Artifact.optimizes_for_deduplication());
assert!(!ChunkingProfile::BulkFile.optimizes_for_deduplication());
assert!(ChunkingProfile::SparseImage.supports_sparse_data());
assert!(!ChunkingProfile::BulkFile.supports_sparse_data());
assert!(ChunkingProfile::Artifact.provides_reproducible_chunking());
assert!(ChunkingProfile::BulkFile.provides_reproducible_chunking());
assert!(!ChunkingProfile::Media.provides_reproducible_chunking());
}
#[test]
fn chunk_boundary_ordering() {
let chunk1 = ChunkBoundary {
index: 0,
byte_offset: 0,
size_bytes: 1024,
content_hash: [1; 32],
strategy: ChunkStrategy::FixedSize,
metadata: Some(ChunkMetadata::BulkFile {
throughput_tier: ThroughputTier::Standard,
}),
};
let chunk2 = ChunkBoundary {
index: 1,
byte_offset: 1024,
size_bytes: 1024,
content_hash: [2; 32],
strategy: ChunkStrategy::FixedSize,
metadata: Some(ChunkMetadata::BulkFile {
throughput_tier: ThroughputTier::Standard,
}),
};
assert!(chunk1 < chunk2);
let mut chunks = vec![chunk2.clone(), chunk1.clone()];
chunks.sort();
assert_eq!(chunks[0], chunk1);
assert_eq!(chunks[1], chunk2);
}
#[test]
fn throughput_tier_ordering() {
assert!(ThroughputTier::Tail < ThroughputTier::Standard);
assert!(ThroughputTier::Standard < ThroughputTier::Bulk);
}
#[test]
fn proof_strength_ordering() {
assert!(ProofStrength::Basic < ProofStrength::Enhanced);
assert!(ProofStrength::Enhanced < ProofStrength::Cryptographic);
}
}