1use core::fmt;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u8)]
15pub enum ExitCode {
16 Success = 0,
18 General = 1,
20 Usage = 2,
22 WrongPassword = 3,
24 Corrupted = 4,
26 UnsupportedVersion = 5,
28 MissingShard = 6,
30 Permission = 7,
32 Cancelled = 8,
34}
35
36#[derive(Debug, thiserror::Error)]
40#[non_exhaustive]
41pub enum Error {
42 #[error("not an omy file: magic mismatch (got {got:02x?})")]
44 BadMagic {
45 got: [u8; 8],
47 },
48
49 #[error("unsupported format version {found_major}.{found_minor}, this build supports up to {supported_major}.x")]
51 UnsupportedVersion {
52 found_major: u16,
54 found_minor: u16,
56 supported_major: u16,
58 },
59
60 #[error("truncated input while reading {context}: need {need} bytes, got {got}")]
62 Truncated {
63 context: &'static str,
65 need: usize,
67 got: usize,
69 },
70
71 #[error("malformed header: {reason}")]
73 MalformedHeader {
74 reason: &'static str,
76 },
77
78 #[error("no key slot could be unwrapped with the provided credentials")]
83 NoMatchingSlot,
84
85 #[error("header MAC verification failed: file tampered or truncated")]
89 HeaderMacMismatch,
90
91 #[error("chunk {index} failed authentication: data corrupted or tampered")]
93 ChunkAuthFailed {
94 index: u64,
96 },
97
98 #[error("unknown critical TLV type 0x{tlv_type:04x}: cannot safely open this file")]
100 UnknownCriticalTlv {
101 tlv_type: u16,
103 },
104
105 #[error("required TLV type 0x{tlv_type:04x} is missing")]
107 MissingTlv {
108 tlv_type: u16,
110 },
111
112 #[error("malformed TLV type 0x{tlv_type:04x}: {reason}")]
114 MalformedTlv {
115 tlv_type: u16,
117 reason: &'static str,
119 },
120
121 #[error("chunk index {index} out of range (file has {total} chunks)")]
123 ChunkOutOfRange {
124 index: u64,
126 total: u64,
128 },
129
130 #[error("too many key slots: {got} exceeds maximum {max}")]
132 TooManySlots {
133 got: usize,
135 max: usize,
137 },
138
139 #[error("chunk size {got} out of allowed range [{min}, {max}]")]
141 InvalidChunkSize {
142 got: u32,
144 min: u32,
146 max: u32,
148 },
149
150 #[error("shard {index} CRC mismatch: expected {expected:08x}, computed {computed:08x}")]
152 ShardCrcMismatch {
153 index: u32,
155 expected: u32,
157 computed: u32,
159 },
160
161 #[error("missing shards: {missing:?} (expected {total} total)")]
163 MissingShards {
164 missing: Vec<u32>,
166 total: u32,
168 },
169
170 #[error("shard {index} belongs to a different file")]
172 ShardUuidMismatch {
173 index: u32,
175 },
176
177 #[error("content hash mismatch after decryption")]
179 ContentHashMismatch,
180
181 #[error("compression error: {reason}")]
183 Compression {
184 reason: String,
186 },
187
188 #[error("key derivation failed: {reason}")]
190 KeyDerivation {
191 reason: &'static str,
193 },
194
195 #[error("io error: {0}")]
197 Io(#[from] std::io::Error),
198}
199
200impl Error {
201 #[must_use]
205 pub const fn exit_code(&self) -> ExitCode {
206 match self {
207 Self::NoMatchingSlot => ExitCode::WrongPassword,
208 Self::BadMagic { .. }
209 | Self::HeaderMacMismatch
210 | Self::ChunkAuthFailed { .. }
211 | Self::ShardCrcMismatch { .. }
212 | Self::ContentHashMismatch
213 | Self::Truncated { .. }
214 | Self::MalformedHeader { .. }
215 | Self::MalformedTlv { .. } => ExitCode::Corrupted,
216 Self::UnsupportedVersion { .. } | Self::UnknownCriticalTlv { .. } => {
217 ExitCode::UnsupportedVersion
218 }
219 Self::MissingShards { .. } | Self::ShardUuidMismatch { .. } => ExitCode::MissingShard,
220 Self::InvalidChunkSize { .. } | Self::TooManySlots { .. } => ExitCode::Usage,
221 Self::Io(_) => ExitCode::Permission,
222 _ => ExitCode::General,
223 }
224 }
225
226 #[must_use]
230 pub const fn code(&self) -> &'static str {
231 match self {
232 Self::BadMagic { .. } => "BAD_MAGIC",
233 Self::UnsupportedVersion { .. } => "UNSUPPORTED_VERSION",
234 Self::Truncated { .. } => "TRUNCATED",
235 Self::MalformedHeader { .. } => "MALFORMED_HEADER",
236 Self::NoMatchingSlot => "WRONG_PASSWORD",
237 Self::HeaderMacMismatch => "HEADER_MAC_MISMATCH",
238 Self::ChunkAuthFailed { .. } => "CHUNK_AUTH_FAILED",
239 Self::UnknownCriticalTlv { .. } => "UNKNOWN_CRITICAL_TLV",
240 Self::MissingTlv { .. } => "MISSING_TLV",
241 Self::MalformedTlv { .. } => "MALFORMED_TLV",
242 Self::ChunkOutOfRange { .. } => "CHUNK_OUT_OF_RANGE",
243 Self::TooManySlots { .. } => "TOO_MANY_SLOTS",
244 Self::InvalidChunkSize { .. } => "INVALID_CHUNK_SIZE",
245 Self::ShardCrcMismatch { .. } => "SHARD_CRC_MISMATCH",
246 Self::MissingShards { .. } => "MISSING_SHARDS",
247 Self::ShardUuidMismatch { .. } => "SHARD_UUID_MISMATCH",
248 Self::ContentHashMismatch => "CONTENT_HASH_MISMATCH",
249 Self::Compression { .. } => "COMPRESSION_ERROR",
250 Self::KeyDerivation { .. } => "KEY_DERIVATION_FAILED",
251 Self::Io(_) => "IO_ERROR",
252 }
253 }
254}
255
256impl fmt::Display for ExitCode {
257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 write!(f, "{}", *self as u8)
259 }
260}
261
262pub type Result<T> = core::result::Result<T, Error>;