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