1use std::fmt::{Display, Formatter};
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5#[non_exhaustive]
6pub enum ErrorContext {
7 AssociatedData,
9 AeadCiphertext,
11 AeadEnvelope,
13 AuthenticationTag,
15 Blake2bKey,
17 Blake2bOutput,
19 Blake2b,
21 Box,
23 Ciphertext,
25 Curve25519PublicKey,
27 Data,
29 Ed25519PublicKey,
31 EphemeralPublicKey,
33 MemoryCost,
35 MemoryLimit,
37 Message,
39 Nonce,
41 OperationsLimit,
43 Output,
45 Parallelism,
47 Password,
49 PasswordHash,
51 PasswordHashAlgorithm,
53 PasswordHashMemoryCost,
55 PasswordHashParallelism,
57 PasswordHashSalt,
59 PasswordHashTimeCost,
61 PasswordHashVersion,
63 ProtectedMemory,
65 PublicKey,
67 SealedBox,
69 Secret,
71 SecretBox,
73 SecretKey,
75 Signature,
77 SignedMessage,
79 Slice,
81 Subkey,
83 Tag,
85 TimeCost,
87}
88
89impl Display for ErrorContext {
90 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
91 f.write_str(match self {
92 Self::AssociatedData => "associated data",
93 Self::AeadCiphertext => "AEAD ciphertext",
94 Self::AeadEnvelope => "AEAD envelope",
95 Self::AuthenticationTag => "authentication tag",
96 Self::Blake2bKey => "BLAKE2b key",
97 Self::Blake2bOutput => "BLAKE2b output",
98 Self::Blake2b => "BLAKE2b",
99 Self::Box => "box",
100 Self::Ciphertext => "ciphertext",
101 Self::Curve25519PublicKey => "Curve25519 public key",
102 Self::Data => "data",
103 Self::Ed25519PublicKey => "Ed25519 public key",
104 Self::EphemeralPublicKey => "ephemeral public key",
105 Self::MemoryCost => "memory cost",
106 Self::MemoryLimit => "memory limit",
107 Self::Message => "message",
108 Self::Nonce => "nonce",
109 Self::OperationsLimit => "operations limit",
110 Self::Output => "output",
111 Self::Parallelism => "parallelism",
112 Self::Password => "password",
113 Self::PasswordHash => "password hash",
114 Self::PasswordHashAlgorithm => "password hash algorithm",
115 Self::PasswordHashMemoryCost => "password hash memory cost",
116 Self::PasswordHashParallelism => "password hash parallelism",
117 Self::PasswordHashSalt => "password hash salt",
118 Self::PasswordHashTimeCost => "password hash time cost",
119 Self::PasswordHashVersion => "password hash version",
120 Self::ProtectedMemory => "protected memory",
121 Self::PublicKey => "public key",
122 Self::SealedBox => "sealed box",
123 Self::Secret => "secret",
124 Self::SecretBox => "secretbox",
125 Self::SecretKey => "secret key",
126 Self::Signature => "signature",
127 Self::SignedMessage => "signed message",
128 Self::Slice => "slice",
129 Self::Subkey => "subkey",
130 Self::Tag => "tag",
131 Self::TimeCost => "time cost",
132 })
133 }
134}
135
136#[derive(Clone, Copy, Debug, Eq, PartialEq)]
138#[non_exhaustive]
139pub enum LengthConstraint {
140 Exact(usize),
142 AtLeast(usize),
144 AtMost(usize),
146 Between { min: usize, max: usize },
148}
149
150impl Display for LengthConstraint {
151 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
152 match self {
153 Self::Exact(expected) => write!(f, "exactly {expected}"),
154 Self::AtLeast(min) => write!(f, "at least {min}"),
155 Self::AtMost(max) => write!(f, "at most {max}"),
156 Self::Between { min, max } => write!(f, "between {min} and {max} (inclusive)"),
157 }
158 }
159}
160
161#[derive(Clone, Copy, Debug, Eq, PartialEq)]
163#[non_exhaustive]
164pub enum ValueConstraint {
165 Between { min: u64, max: u64 },
167 AllowedBits { mask: u64 },
169}
170
171impl Display for ValueConstraint {
172 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
173 match self {
174 Self::Between { min, max } => write!(f, "between {min} and {max} (inclusive)"),
175 Self::AllowedBits { mask } => {
176 write!(f, "a value containing only bits from mask 0x{mask:x}")
177 }
178 }
179 }
180}
181
182#[derive(Debug)]
187#[non_exhaustive]
188pub enum Error {
189 AuthenticationFailed,
191
192 InvalidLength {
194 context: ErrorContext,
196 actual: usize,
198 constraint: LengthConstraint,
200 },
201
202 InvalidValue {
204 context: ErrorContext,
206 actual: u64,
208 constraint: ValueConstraint,
210 },
211
212 InvalidEncoding {
214 context: ErrorContext,
216 },
217
218 InvalidKey {
220 context: ErrorContext,
222 },
223
224 MissingData {
226 context: ErrorContext,
228 },
229
230 InvalidState {
232 context: ErrorContext,
234 },
235
236 ArithmeticOverflow {
238 context: ErrorContext,
240 },
241
242 AllocationFailed {
244 context: ErrorContext,
246 },
247
248 Io(std::io::Error),
250}
251
252impl Error {
253 pub(crate) const fn invalid_encoding(context: ErrorContext) -> Self {
254 Self::InvalidEncoding { context }
255 }
256
257 pub(crate) const fn invalid_key(context: ErrorContext) -> Self {
258 Self::InvalidKey { context }
259 }
260
261 pub(crate) const fn missing_data(context: ErrorContext) -> Self {
262 Self::MissingData { context }
263 }
264
265 pub(crate) const fn invalid_state(context: ErrorContext) -> Self {
266 Self::InvalidState { context }
267 }
268
269 pub(crate) const fn arithmetic_overflow(context: ErrorContext) -> Self {
270 Self::ArithmeticOverflow { context }
271 }
272
273 pub(crate) const fn allocation_failed(context: ErrorContext) -> Self {
274 Self::AllocationFailed { context }
275 }
276}
277
278impl From<std::io::Error> for Error {
279 fn from(error: std::io::Error) -> Self {
280 Self::Io(error)
281 }
282}
283
284impl Display for Error {
285 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
286 match self {
287 Self::AuthenticationFailed => f.write_str("authentication failed"),
288 Self::InvalidLength {
289 context,
290 actual,
291 constraint,
292 } => write!(
293 f,
294 "invalid {context} length: expected {constraint}, got {actual}"
295 ),
296 Self::InvalidValue {
297 context,
298 actual,
299 constraint,
300 } => write!(
301 f,
302 "invalid {context} value: expected {constraint}, got {actual}"
303 ),
304 Self::InvalidEncoding { context } => write!(f, "invalid {context} encoding"),
305 Self::InvalidKey { context } => write!(f, "invalid {context}"),
306 Self::MissingData { context } => write!(f, "missing {context}"),
307 Self::InvalidState { context } => write!(f, "invalid {context} state"),
308 Self::ArithmeticOverflow { context } => {
309 write!(f, "arithmetic overflow while calculating {context} length")
310 }
311 Self::AllocationFailed { context } => {
312 write!(f, "unable to allocate memory for {context}")
313 }
314 Self::Io(error) => write!(f, "I/O error: {error}"),
315 }
316 }
317}
318
319impl std::error::Error for Error {
320 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
321 match self {
322 Self::Io(error) => Some(error),
323 _ => None,
324 }
325 }
326}
327
328macro_rules! length_error {
329 ($context:expr_2021, $actual:expr_2021,exact $expected:expr_2021) => {
330 crate::error::Error::InvalidLength {
331 context: $context,
332 actual: $actual,
333 constraint: crate::error::LengthConstraint::Exact($expected),
334 }
335 };
336 ($context:expr_2021, $actual:expr_2021,min $min:expr_2021) => {
337 crate::error::Error::InvalidLength {
338 context: $context,
339 actual: $actual,
340 constraint: crate::error::LengthConstraint::AtLeast($min),
341 }
342 };
343 ($context:expr_2021, $actual:expr_2021,max $max:expr_2021) => {
344 crate::error::Error::InvalidLength {
345 context: $context,
346 actual: $actual,
347 constraint: crate::error::LengthConstraint::AtMost($max),
348 }
349 };
350 ($context:expr_2021, $actual:expr_2021,range $min:expr_2021, $max:expr_2021) => {
351 crate::error::Error::InvalidLength {
352 context: $context,
353 actual: $actual,
354 constraint: crate::error::LengthConstraint::Between {
355 min: $min,
356 max: $max,
357 },
358 }
359 };
360}
361
362macro_rules! validate_value {
363 ($min:expr_2021, $max:expr_2021, $value:expr_2021, $context:expr_2021) => {
364 if !($min..=$max).contains(&$value) {
365 return Err(crate::error::Error::InvalidValue {
366 context: $context,
367 actual: $value as u64,
368 constraint: crate::error::ValueConstraint::Between {
369 min: $min as u64,
370 max: $max as u64,
371 },
372 });
373 }
374 };
375}
376
377macro_rules! validate_length {
378 (exact $expected:expr_2021, $value:expr_2021, $context:expr_2021) => {
379 if $value != $expected {
380 return Err(length_error!($context, $value, exact $expected));
381 }
382 };
383 ($min:expr_2021, $max:expr_2021, $value:expr_2021, $context:expr_2021) => {
384 if !($min..=$max).contains(&$value) {
385 return Err(length_error!($context, $value, range $min, $max));
386 }
387 };
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn contexts_have_clear_human_readable_names() {
396 let cases = [
397 (ErrorContext::AssociatedData, "associated data"),
398 (ErrorContext::AeadCiphertext, "AEAD ciphertext"),
399 (ErrorContext::AeadEnvelope, "AEAD envelope"),
400 (ErrorContext::AuthenticationTag, "authentication tag"),
401 (ErrorContext::Blake2bKey, "BLAKE2b key"),
402 (ErrorContext::Blake2bOutput, "BLAKE2b output"),
403 (ErrorContext::Blake2b, "BLAKE2b"),
404 (ErrorContext::Box, "box"),
405 (ErrorContext::Ciphertext, "ciphertext"),
406 (ErrorContext::Curve25519PublicKey, "Curve25519 public key"),
407 (ErrorContext::Data, "data"),
408 (ErrorContext::Ed25519PublicKey, "Ed25519 public key"),
409 (ErrorContext::EphemeralPublicKey, "ephemeral public key"),
410 (ErrorContext::MemoryCost, "memory cost"),
411 (ErrorContext::MemoryLimit, "memory limit"),
412 (ErrorContext::Message, "message"),
413 (ErrorContext::Nonce, "nonce"),
414 (ErrorContext::OperationsLimit, "operations limit"),
415 (ErrorContext::Output, "output"),
416 (ErrorContext::Parallelism, "parallelism"),
417 (ErrorContext::Password, "password"),
418 (ErrorContext::PasswordHash, "password hash"),
419 (
420 ErrorContext::PasswordHashAlgorithm,
421 "password hash algorithm",
422 ),
423 (
424 ErrorContext::PasswordHashMemoryCost,
425 "password hash memory cost",
426 ),
427 (
428 ErrorContext::PasswordHashParallelism,
429 "password hash parallelism",
430 ),
431 (ErrorContext::PasswordHashSalt, "password hash salt"),
432 (
433 ErrorContext::PasswordHashTimeCost,
434 "password hash time cost",
435 ),
436 (ErrorContext::PasswordHashVersion, "password hash version"),
437 (ErrorContext::ProtectedMemory, "protected memory"),
438 (ErrorContext::PublicKey, "public key"),
439 (ErrorContext::SealedBox, "sealed box"),
440 (ErrorContext::Secret, "secret"),
441 (ErrorContext::SecretBox, "secretbox"),
442 (ErrorContext::SecretKey, "secret key"),
443 (ErrorContext::Signature, "signature"),
444 (ErrorContext::SignedMessage, "signed message"),
445 (ErrorContext::Slice, "slice"),
446 (ErrorContext::Subkey, "subkey"),
447 (ErrorContext::Tag, "tag"),
448 (ErrorContext::TimeCost, "time cost"),
449 ];
450
451 for (context, expected) in cases {
452 assert_eq!(context.to_string(), expected);
453 }
454 }
455
456 #[test]
457 fn constraints_describe_their_requirements() {
458 let length_cases = [
459 (LengthConstraint::Exact(4), "exactly 4"),
460 (LengthConstraint::AtLeast(4), "at least 4"),
461 (LengthConstraint::AtMost(4), "at most 4"),
462 (
463 LengthConstraint::Between { min: 2, max: 4 },
464 "between 2 and 4 (inclusive)",
465 ),
466 ];
467 for (constraint, expected) in length_cases {
468 assert_eq!(constraint.to_string(), expected);
469 }
470
471 let value_cases = [
472 (
473 ValueConstraint::Between { min: 2, max: 4 },
474 "between 2 and 4 (inclusive)",
475 ),
476 (
477 ValueConstraint::AllowedBits { mask: 0x3 },
478 "a value containing only bits from mask 0x3",
479 ),
480 ];
481 for (constraint, expected) in value_cases {
482 assert_eq!(constraint.to_string(), expected);
483 }
484 }
485
486 #[test]
487 fn display_is_human_readable_without_source_locations() {
488 let cases = [
489 (Error::AuthenticationFailed, "authentication failed"),
490 (
491 Error::InvalidLength {
492 context: ErrorContext::Nonce,
493 actual: 12,
494 constraint: LengthConstraint::Exact(24),
495 },
496 "invalid nonce length: expected exactly 24, got 12",
497 ),
498 (
499 Error::InvalidLength {
500 context: ErrorContext::Blake2bOutput,
501 actual: 0,
502 constraint: LengthConstraint::Between { min: 1, max: 64 },
503 },
504 "invalid BLAKE2b output length: expected between 1 and 64 (inclusive), got 0",
505 ),
506 (
507 Error::InvalidValue {
508 context: ErrorContext::Parallelism,
509 actual: 8,
510 constraint: ValueConstraint::Between { min: 1, max: 4 },
511 },
512 "invalid parallelism value: expected between 1 and 4 (inclusive), got 8",
513 ),
514 (
515 Error::InvalidValue {
516 context: ErrorContext::Tag,
517 actual: 128,
518 constraint: ValueConstraint::AllowedBits { mask: 3 },
519 },
520 "invalid tag value: expected a value containing only bits from mask 0x3, got 128",
521 ),
522 (
523 Error::InvalidEncoding {
524 context: ErrorContext::PasswordHashSalt,
525 },
526 "invalid password hash salt encoding",
527 ),
528 (
529 Error::InvalidKey {
530 context: ErrorContext::Ed25519PublicKey,
531 },
532 "invalid Ed25519 public key",
533 ),
534 (
535 Error::MissingData {
536 context: ErrorContext::EphemeralPublicKey,
537 },
538 "missing ephemeral public key",
539 ),
540 (
541 Error::InvalidState {
542 context: ErrorContext::Blake2b,
543 },
544 "invalid BLAKE2b state",
545 ),
546 (
547 Error::ArithmeticOverflow {
548 context: ErrorContext::Ciphertext,
549 },
550 "arithmetic overflow while calculating ciphertext length",
551 ),
552 (
553 Error::AllocationFailed {
554 context: ErrorContext::MemoryCost,
555 },
556 "unable to allocate memory for memory cost",
557 ),
558 ];
559
560 for (error, expected) in cases {
561 assert_eq!(error.to_string(), expected);
562 }
563 }
564
565 #[test]
566 fn debug_is_structured_and_does_not_include_internal_source_locations() {
567 let error = Error::InvalidLength {
568 context: ErrorContext::Ciphertext,
569 actual: 7,
570 constraint: LengthConstraint::AtLeast(16),
571 };
572
573 assert_eq!(
574 format!("{error:?}"),
575 "InvalidLength { context: Ciphertext, actual: 7, constraint: AtLeast(16) }"
576 );
577 }
578
579 #[test]
580 fn wrapped_errors_preserve_their_source() {
581 use std::error::Error as _;
582
583 let error = Error::from(std::io::Error::new(
584 std::io::ErrorKind::PermissionDenied,
585 "access denied",
586 ));
587 assert_eq!(error.to_string(), "I/O error: access denied");
588 let debug = format!("{error:?}");
589 assert!(debug.contains("Io"));
590 assert!(debug.contains("PermissionDenied"));
591 assert!(debug.contains("access denied"));
592 assert!(error.source().is_some());
593 assert!(Error::AuthenticationFailed.source().is_none());
594 }
595}