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