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