Skip to main content

tzap_core/
format.rs

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}