1use thiserror::Error;
2
3pub const FORMAT_VERSION: u16 = 1;
4pub const VOLUME_FORMAT_REV: u16 = 36;
5
6pub const VOLUME_HEADER_LEN: usize = 128;
7pub const CRYPTO_HEADER_FIXED_LEN: usize = 76;
8pub const MANIFEST_FOOTER_LEN: usize = 136;
9pub const VOLUME_TRAILER_LEN: usize = 128;
10pub const BOOTSTRAP_SIDECAR_HEADER_LEN: usize = 128;
11pub const BLOCK_RECORD_FRAMING_LEN: usize = 20;
12pub const CRYPTO_HEADER_HMAC_LEN: usize = 32;
13pub const CRYPTO_EXTENSION_HEADER_LEN: usize = 6;
14pub const CRYPTO_EXTENSION_MAX_VALUE_LEN: u32 = 256;
15pub const MASTER_KEY_LEN: usize = 32;
16pub const SUBKEY_LEN: usize = 32;
17pub const READER_MAX_ARGON2ID_M_COST_KIB: u32 = 4 * 1024 * 1024;
18pub const READER_MAX_ARGON2ID_T_COST: u32 = 100;
19pub const READER_MAX_ARGON2ID_PARALLELISM: u32 = 64;
20pub const READER_MAX_CRYPTO_HEADER_LEN: u32 = 64 * 1024;
21pub const READER_MAX_CHUNK_SIZE: u32 = 64 * 1024 * 1024;
22pub const READER_MAX_ENVELOPE_TARGET_SIZE: u32 = 64 * 1024 * 1024;
23pub const READER_MAX_BLOCK_SIZE: u32 = 1024 * 1024;
24pub const READER_MAX_STRIPE_WIDTH: u32 = 4096;
25pub const READER_MAX_FEC_CLASS_SHARDS: u32 = 4096;
26pub const READER_MAX_INDEX_FEC_CLASS_SHARDS: u32 = 4096;
27pub const READER_MAX_INDEX_ROOT_FEC_CLASS_SHARDS: u32 = 131_070;
28pub const READER_MAX_PATH_LENGTH: u32 = 4096;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31#[repr(u16)]
32pub enum CompressionAlgo {
33 None = 0,
34 ZstdFramed = 1,
35}
36
37impl TryFrom<u16> for CompressionAlgo {
38 type Error = FormatError;
39
40 fn try_from(value: u16) -> Result<Self, Self::Error> {
41 match value {
42 0 => Ok(Self::None),
43 1 => Ok(Self::ZstdFramed),
44 other => Err(FormatError::UnknownCompressionAlgo(other)),
45 }
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50#[repr(u16)]
51pub enum AeadAlgo {
52 AesGcmSiv256 = 1,
53 XChaCha20Poly1305 = 2,
54 AesGcm256 = 3,
55}
56
57impl TryFrom<u16> for AeadAlgo {
58 type Error = FormatError;
59
60 fn try_from(value: u16) -> Result<Self, Self::Error> {
61 match value {
62 1 => Ok(Self::AesGcmSiv256),
63 2 => Ok(Self::XChaCha20Poly1305),
64 3 => Ok(Self::AesGcm256),
65 other => Err(FormatError::UnknownAeadAlgo(other)),
66 }
67 }
68}
69
70impl AeadAlgo {
71 pub const fn nonce_len(self) -> usize {
72 match self {
73 Self::AesGcmSiv256 | Self::AesGcm256 => 12,
74 Self::XChaCha20Poly1305 => 24,
75 }
76 }
77
78 pub const fn tag_len(self) -> usize {
79 16
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[repr(u16)]
85pub enum FecAlgo {
86 None = 0,
87 ReedSolomonGF16 = 1,
88 Wirehair = 2,
89}
90
91impl TryFrom<u16> for FecAlgo {
92 type Error = FormatError;
93
94 fn try_from(value: u16) -> Result<Self, Self::Error> {
95 match value {
96 0 => Ok(Self::None),
97 1 => Ok(Self::ReedSolomonGF16),
98 2 => Ok(Self::Wirehair),
99 other => Err(FormatError::UnknownFecAlgo(other)),
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105#[repr(u16)]
106pub enum KdfAlgo {
107 Raw = 0,
108 Argon2id = 1,
109}
110
111impl TryFrom<u16> for KdfAlgo {
112 type Error = FormatError;
113
114 fn try_from(value: u16) -> Result<Self, Self::Error> {
115 match value {
116 0 => Ok(Self::Raw),
117 1 => Ok(Self::Argon2id),
118 other => Err(FormatError::UnknownKdfAlgo(other)),
119 }
120 }
121}
122
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124#[repr(u8)]
125pub enum BlockKind {
126 PayloadData = 0,
127 PayloadParity = 1,
128 IndexRootData = 2,
129 IndexRootParity = 3,
130 IndexShardData = 4,
131 IndexShardParity = 5,
132 DictionaryData = 6,
133 DictionaryParity = 7,
134 DirectoryHintData = 8,
135 DirectoryHintParity = 9,
136}
137
138impl TryFrom<u8> for BlockKind {
139 type Error = FormatError;
140
141 fn try_from(value: u8) -> Result<Self, Self::Error> {
142 match value {
143 0 => Ok(Self::PayloadData),
144 1 => Ok(Self::PayloadParity),
145 2 => Ok(Self::IndexRootData),
146 3 => Ok(Self::IndexRootParity),
147 4 => Ok(Self::IndexShardData),
148 5 => Ok(Self::IndexShardParity),
149 6 => Ok(Self::DictionaryData),
150 7 => Ok(Self::DictionaryParity),
151 8 => Ok(Self::DirectoryHintData),
152 9 => Ok(Self::DirectoryHintParity),
153 other => Err(FormatError::UnknownBlockKind(other)),
154 }
155 }
156}
157
158impl BlockKind {
159 pub const fn is_data(self) -> bool {
160 matches!(
161 self,
162 Self::PayloadData
163 | Self::IndexRootData
164 | Self::IndexShardData
165 | Self::DictionaryData
166 | Self::DirectoryHintData
167 )
168 }
169
170 pub const fn is_parity(self) -> bool {
171 !self.is_data()
172 }
173}
174
175#[derive(Debug, Error, PartialEq, Eq)]
176pub enum FormatError {
177 #[error("unknown compression algorithm id {0}")]
178 UnknownCompressionAlgo(u16),
179
180 #[error("unknown AEAD algorithm id {0}")]
181 UnknownAeadAlgo(u16),
182
183 #[error("unknown FEC algorithm id {0}")]
184 UnknownFecAlgo(u16),
185
186 #[error("unknown KDF algorithm id {0}")]
187 UnknownKdfAlgo(u16),
188
189 #[error("unknown block kind {0}")]
190 UnknownBlockKind(u8),
191
192 #[error("invalid length for {structure}: expected {expected}, actual {actual}")]
193 InvalidLength {
194 structure: &'static str,
195 expected: usize,
196 actual: usize,
197 },
198
199 #[error("bad magic for {structure}")]
200 BadMagic { structure: &'static str },
201
202 #[error("bad CRC32C for {structure}")]
203 BadCrc { structure: &'static str },
204
205 #[error("unsupported format version {0}")]
206 UnsupportedFormatVersion(u16),
207
208 #[error("unsupported volume format revision {0}")]
209 UnsupportedVolumeFormatRevision(u16),
210
211 #[error("non-zero reserved bytes in {structure}")]
212 NonZeroReserved { structure: &'static str },
213
214 #[error("non-canonical CryptoHeader offset {0}")]
215 NonCanonicalCryptoHeaderOffset(u32),
216
217 #[error("stripe width must be non-zero")]
218 ZeroStripeWidth,
219
220 #[error("volume index {volume_index} is outside stripe width {stripe_width}")]
221 VolumeIndexOutOfRange {
222 volume_index: u32,
223 stripe_width: u32,
224 },
225
226 #[error(
227 "CryptoHeader length mismatch: fixed header says {fixed}, volume header says {volume}"
228 )]
229 CryptoHeaderLengthMismatch { fixed: u32, volume: u32 },
230
231 #[error("compression algorithm {0:?} is not valid for v0.36")]
232 UnsupportedCompressionForV36(CompressionAlgo),
233
234 #[error("FEC algorithm {0:?} is not valid for v0.36")]
235 UnsupportedFecForV36(FecAlgo),
236
237 #[error("invalid boolean field {field}={value}")]
238 InvalidBoolean { field: &'static str, value: u8 },
239
240 #[error("volume loss tolerance {volume_loss_tolerance} must be less than stripe width {stripe_width}")]
241 VolumeLossToleranceOutOfRange {
242 volume_loss_tolerance: u8,
243 stripe_width: u32,
244 },
245
246 #[error("bit rot buffer pct {0} exceeds 100")]
247 BitRotBufferPctTooLarge(u8),
248
249 #[error("data shard maximum {field} must be non-zero")]
250 ZeroDataShardMaximum { field: &'static str },
251
252 #[error("chunk_size must be non-zero")]
253 ZeroChunkSize,
254
255 #[error("envelope_target_size must be non-zero")]
256 ZeroEnvelopeTargetSize,
257
258 #[error("chunk_size {chunk_size} exceeds envelope_target_size {envelope_target_size}")]
259 ChunkSizeExceedsEnvelopeTarget {
260 chunk_size: u32,
261 envelope_target_size: u32,
262 },
263
264 #[error("block_size {0} is below the v0.36 minimum")]
265 BlockSizeTooSmall(u32),
266
267 #[error("block_size {0} must be even")]
268 OddBlockSize(u32),
269
270 #[error("reader resource cap exceeded for {field}: cap {cap}, actual {actual}")]
271 ReaderResourceLimitExceeded {
272 field: &'static str,
273 cap: u64,
274 actual: u64,
275 },
276
277 #[error("invalid block flags 0x{0:02x}")]
278 InvalidBlockFlags(u8),
279
280 #[error("parity block must not set the last-data flag")]
281 ParityBlockHasLastDataFlag,
282
283 #[error("invalid authoritative flag {0}")]
284 InvalidAuthoritativeFlag(u8),
285
286 #[error("invalid ManifestFooter length {0}")]
287 InvalidManifestFooterLength(u32),
288
289 #[error("IndexRoot encrypted size is not data_block_count * block_size")]
290 IndexRootSizeMismatch,
291
292 #[error("IndexRoot data block count and encrypted size must be non-zero")]
293 EmptyIndexRootExtent,
294
295 #[error("bootstrap sidecar version {0} is unsupported")]
296 UnsupportedBootstrapSidecarVersion(u32),
297
298 #[error("bootstrap sidecar has unknown flags 0x{0:08x}")]
299 UnknownBootstrapSidecarFlags(u32),
300
301 #[error("bootstrap sidecar present section has zero offset or length")]
302 EmptyBootstrapSidecarSection,
303
304 #[error("bootstrap sidecar absent section has non-zero offset or length")]
305 NonZeroAbsentBootstrapSidecarSection,
306
307 #[error("bootstrap sidecar sections are not packed canonically")]
308 NonCanonicalBootstrapSidecarLayout,
309
310 #[error("extension TLV header is truncated")]
311 TruncatedExtensionHeader,
312
313 #[error("extension TLV payload is truncated")]
314 TruncatedExtensionPayload,
315
316 #[error("extension TLV payload length {0} exceeds 256")]
317 ExtensionPayloadTooLarge(u32),
318
319 #[error("extension terminator is malformed")]
320 MalformedExtensionTerminator,
321
322 #[error("extension terminator is missing")]
323 MissingExtensionTerminator,
324
325 #[error("bytes follow extension terminator")]
326 BytesAfterExtensionTerminator,
327
328 #[error("CryptoHeader is too short: minimum {min}, actual {actual}")]
329 CryptoHeaderTooShort { min: usize, actual: usize },
330
331 #[error("KdfParams algo_tag {actual} does not match expected {expected}")]
332 KdfAlgoTagMismatch { expected: u16, actual: u16 },
333
334 #[error("KdfParams are truncated")]
335 TruncatedKdfParams,
336
337 #[error("invalid KdfParams: {0}")]
338 InvalidKdfParams(&'static str),
339
340 #[error("key material does not match KDF mode")]
341 KeyMaterialMismatch,
342
343 #[error("raw master key must be exactly 32 bytes")]
344 InvalidRawMasterKeyLength,
345
346 #[error("Argon2id derivation failed")]
347 Argon2idFailure,
348
349 #[error("HKDF expansion failed")]
350 HkdfExpandFailure,
351
352 #[error("HMAC verification failed for {structure}")]
353 HmacMismatch { structure: &'static str },
354
355 #[error("forbidden CryptoHeader extension tag 0x{0:04x}")]
356 ForbiddenExtensionTag(u16),
357
358 #[error("unknown critical CryptoHeader extension tag 0x{0:04x}")]
359 UnknownCriticalExtension(u16),
360
361 #[error("duplicate known CryptoHeader extension tag 0x{0:04x}")]
362 DuplicateKnownExtension(u16),
363
364 #[error("malformed known CryptoHeader extension tag 0x{0:04x}")]
365 MalformedKnownExtension(u16),
366
367 #[error("padding input is empty")]
368 EmptyPaddedPlaintext,
369
370 #[error("invalid suffix padding")]
371 InvalidSuffixPadding,
372
373 #[error("non-zero suffix padding bytes")]
374 NonZeroPaddingBytes,
375
376 #[error("padding arithmetic overflow")]
377 PaddingOverflow,
378
379 #[error("AEAD operation failed")]
380 AeadFailure,
381
382 #[error("nonce/AAD domain is too long")]
383 DomainTooLong,
384
385 #[error("invalid nonce length for {algo:?}: expected {expected}, actual {actual}")]
386 InvalidNonceLength {
387 algo: AeadAlgo,
388 expected: usize,
389 actual: usize,
390 },
391
392 #[error("invalid AEAD key length")]
393 InvalidAeadKeyLength,
394
395 #[error("zstd compression failed")]
396 ZstdCompressionFailure,
397
398 #[error("zstd frame is empty")]
399 EmptyZstdFrame,
400
401 #[error("zstd frame is not a standard non-skippable frame")]
402 NotStandardZstdFrame,
403
404 #[error("zstd frame is truncated or corrupt")]
405 InvalidZstdFrame,
406
407 #[error("zstd frame has trailing bytes after the first complete frame")]
408 TrailingBytesAfterZstdFrame,
409
410 #[error("zstd decompression failed")]
411 ZstdDecompressionFailure,
412
413 #[error("zstd decompressed size mismatch: expected {expected}, actual {actual}")]
414 ZstdDecompressedSizeMismatch { expected: usize, actual: usize },
415
416 #[error("FEC object must contain at least one data shard")]
417 FecZeroDataShards,
418
419 #[error("FEC object total shard count {0} exceeds ReedSolomonGF16 limit")]
420 FecTooManyShards(usize),
421
422 #[error("FEC shard size must be even")]
423 FecOddShardSize,
424
425 #[error("FEC shards have inconsistent sizes")]
426 FecInconsistentShardSize,
427
428 #[error("FEC repair has too few available shards")]
429 FecTooFewAvailableShards,
430
431 #[error("FEC repair matrix is singular")]
432 FecSingularMatrix,
433
434 #[error("invalid metadata in {structure}: {reason}")]
435 InvalidMetadata {
436 structure: &'static str,
437 reason: &'static str,
438 },
439
440 #[error("metadata arithmetic overflow in {structure}")]
441 MetadataArithmeticOverflow { structure: &'static str },
442
443 #[error("hash-prefix collision run exceeds resource caps")]
444 HashPrefixCollisionRunExceeded,
445
446 #[error("unsafe archive path")]
447 UnsafeArchivePath,
448
449 #[error("unsafe extraction overwrite")]
450 UnsafeOverwrite,
451
452 #[error("filesystem extraction failed: {0}")]
453 FilesystemExtractionFailed(&'static str),
454
455 #[error("writer unsupported case: {0}")]
456 WriterUnsupported(&'static str),
457
458 #[error("writer invariant failed: {0}")]
459 WriterInvariant(&'static str),
460
461 #[error("reader unsupported case: {0}")]
462 ReaderUnsupported(&'static str),
463
464 #[error("invalid archive: {0}")]
465 InvalidArchive(&'static str),
466}