acyclic_fs/kernel/
export.rs1use super::codec::{CanonicalDecodeError, Decoder, Encoder};
4use super::volume::{decode_config, encode_config};
5use crate::foundation::{Digest, GenerationId, VolumeId};
6use crate::model::{VolumeConfig, VolumeConfigError};
7use crate::storage::{ObjectId, ObjectKind};
8use std::cmp::Ordering;
9use thiserror::Error;
10
11const DOMAIN: &[u8] = b"acyclic-fs-generation-export-v1\0";
12const VERSION: u16 = 1;
13
14#[derive(Clone, Debug, Eq, PartialEq)]
16pub struct GenerationExportManifest {
17 pub volume_id: VolumeId,
19 pub config: VolumeConfig,
21 pub generation_root: ObjectId,
23 pub generation_id: GenerationId,
25 pub objects: Vec<ObjectId>,
27 pub file_count: u64,
29}
30
31#[derive(Clone, Debug, Eq, Error, PartialEq)]
33pub enum GenerationExportManifestError {
34 #[error(transparent)]
36 Config(#[from] VolumeConfigError),
37 #[error(transparent)]
39 Decode(#[from] CanonicalDecodeError),
40 #[error("export manifest root is not a generation root")]
42 WrongRootKind,
43 #[error("export manifest generation identity does not match its root")]
45 GenerationMismatch,
46 #[error("export manifest closure omits its generation root")]
48 MissingRoot,
49 #[error("export manifest objects are not strictly canonically ordered")]
51 NonCanonicalObjectOrder,
52 #[error("export manifest object count exceeds its bound")]
54 TooManyObjects,
55 #[error("export manifest file count exceeds its bound")]
57 TooManyFiles,
58}
59
60pub fn encode_generation_export_manifest(
67 manifest: &GenerationExportManifest,
68) -> Result<Vec<u8>, GenerationExportManifestError> {
69 validate_generation_export_manifest(
70 manifest,
71 manifest.config.limits.maximum_objects_per_generation,
72 )?;
73 let count = u32::try_from(manifest.objects.len())
74 .map_err(|_| GenerationExportManifestError::TooManyObjects)?;
75 let mut encoder = Encoder::new(DOMAIN, VERSION);
76 encoder.fixed(&manifest.volume_id.into_bytes());
77 encode_config(&mut encoder, manifest.config);
78 encode_object_id(&mut encoder, manifest.generation_root);
79 encoder.fixed(manifest.generation_id.digest().as_bytes());
80 encoder.u64(manifest.file_count);
81 encoder.u32(count);
82 for object in &manifest.objects {
83 encode_object_id(&mut encoder, *object);
84 }
85 Ok(encoder.finish())
86}
87
88pub fn decode_generation_export_manifest(
95 bytes: &[u8],
96 maximum_bytes: u64,
97 maximum_objects: u64,
98) -> Result<GenerationExportManifest, GenerationExportManifestError> {
99 let mut decoder = Decoder::new(bytes, DOMAIN, VERSION, maximum_bytes)?;
100 let volume_id = VolumeId::from_bytes(decoder.fixed()?);
101 let config = decode_config(&mut decoder)?.validate()?;
102 let generation_root = decode_object_id(&mut decoder)?;
103 let generation_id = GenerationId::new(Digest::from_bytes(decoder.fixed()?));
104 let file_count = decoder.u64()?;
105 let count = decoder.u32()?;
106 let admitted = maximum_objects.min(config.limits.maximum_objects_per_generation);
107 if u64::from(count) > admitted {
108 return Err(GenerationExportManifestError::TooManyObjects);
109 }
110 let capacity =
111 usize::try_from(count).map_err(|_| GenerationExportManifestError::TooManyObjects)?;
112 let mut objects = Vec::new();
113 objects
114 .try_reserve_exact(capacity)
115 .map_err(|_| CanonicalDecodeError::AllocationFailed)?;
116 for _ in 0..count {
117 objects.push(decode_object_id(&mut decoder)?);
118 }
119 decoder.finish()?;
120 let manifest = GenerationExportManifest {
121 volume_id,
122 config,
123 generation_root,
124 generation_id,
125 objects,
126 file_count,
127 };
128 validate_generation_export_manifest(&manifest, maximum_objects)?;
129 Ok(manifest)
130}
131
132pub fn validate_generation_export_manifest(
139 manifest: &GenerationExportManifest,
140 maximum_objects: u64,
141) -> Result<(), GenerationExportManifestError> {
142 manifest.config.validate()?;
143 if manifest.generation_root.kind != ObjectKind::GenerationRoot {
144 return Err(GenerationExportManifestError::WrongRootKind);
145 }
146 if manifest.generation_id.digest() != manifest.generation_root.digest {
147 return Err(GenerationExportManifestError::GenerationMismatch);
148 }
149 if manifest.file_count > manifest.config.limits.maximum_files_per_generation {
150 return Err(GenerationExportManifestError::TooManyFiles);
151 }
152 let count = u64::try_from(manifest.objects.len()).unwrap_or(u64::MAX);
153 if count > maximum_objects || count > manifest.config.limits.maximum_objects_per_generation {
154 return Err(GenerationExportManifestError::TooManyObjects);
155 }
156 if !manifest.objects.contains(&manifest.generation_root) {
157 return Err(GenerationExportManifestError::MissingRoot);
158 }
159 if manifest.objects.windows(2).any(|pair| {
160 let [left, right] = pair else {
161 unreachable!("windows(2) always yields exactly two elements")
162 };
163 compare_object_id(*left, *right) != Ordering::Less
164 }) {
165 return Err(GenerationExportManifestError::NonCanonicalObjectOrder);
166 }
167 Ok(())
168}
169
170fn compare_object_id(left: ObjectId, right: ObjectId) -> Ordering {
171 left.kind
172 .canonical_tag()
173 .cmp(&right.kind.canonical_tag())
174 .then_with(|| left.digest.cmp(&right.digest))
175}
176
177fn encode_object_id(encoder: &mut Encoder, object: ObjectId) {
178 encoder.u8(object.kind.canonical_tag());
179 encoder.fixed(object.digest.as_bytes());
180}
181
182fn decode_object_id(decoder: &mut Decoder<'_>) -> Result<ObjectId, CanonicalDecodeError> {
183 let kind = ObjectKind::from_canonical_tag(decoder.u8()?)
184 .map_err(|error| CanonicalDecodeError::Invariant(error.to_string()))?;
185 Ok(ObjectId {
186 kind,
187 digest: Digest::from_bytes(decoder.fixed()?),
188 })
189}
190
191#[cfg(test)]
192#[path = "tests/export.rs"]
193mod tests;