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