Skip to main content

dcrypt_algorithms/error/
mod.rs

1//! Error handling for cryptographic primitives
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[cfg(feature = "alloc")]
7use alloc::borrow::Cow;
8
9#[cfg(feature = "std")]
10use std::fmt;
11
12#[cfg(not(feature = "std"))]
13use core::fmt;
14
15use dcrypt_api::{Error as CoreError, Result as CoreResult};
16
17/// The error type for cryptographic primitives
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum Error {
20    /// Parameter validation error
21    Parameter {
22        /// Name of the invalid parameter
23        name: Cow<'static, str>, // Changed from &'static str
24        /// Reason why the parameter is invalid
25        reason: Cow<'static, str>, // Changed from &'static str
26    },
27
28    /// Length validation error
29    Length {
30        /// Context where the length error occurred
31        context: &'static str,
32        /// Expected length in bytes
33        expected: usize,
34        /// Actual length in bytes
35        actual: usize,
36    },
37
38    /// Authentication failure (e.g., AEAD tag verification)
39    Authentication {
40        /// Algorithm that failed authentication
41        algorithm: &'static str,
42    },
43
44    /// A caller-provided cryptographic randomness source failed.
45    Randomness,
46
47    /// Feature not implemented
48    NotImplemented {
49        /// Name of the unimplemented feature
50        feature: &'static str,
51    },
52
53    /// Processing error during cryptographic operation
54    Processing {
55        /// Operation that failed
56        operation: &'static str,
57        /// Additional details about the failure
58        details: &'static str,
59    },
60
61    /// MAC error
62    MacError {
63        /// MAC algorithm that encountered the error
64        algorithm: &'static str,
65        /// Additional details about the MAC error
66        details: &'static str,
67    },
68
69    /// External errors with String details (only available with alloc/std)
70    #[cfg(feature = "std")]
71    External {
72        /// Source of the external error
73        source: &'static str,
74        /// Detailed error message
75        details: String,
76    },
77
78    #[cfg(not(feature = "std"))]
79    /// External error context without heap-allocated details.
80    External {
81        /// Source of the external error
82        source: &'static str,
83    },
84
85    /// Fallback for other errors
86    Other(&'static str),
87}
88
89// Add convenience helper
90impl Error {
91    /// Shorthand to create a Parameter error
92    pub fn param<N: Into<Cow<'static, str>>, R: Into<Cow<'static, str>>>(
93        name: N,
94        reason: R,
95    ) -> Self {
96        Error::Parameter {
97            name: name.into(),
98            reason: reason.into(),
99        }
100    }
101}
102
103/// Result type for cryptographic primitives operations
104pub type Result<T> = core::result::Result<T, Error>;
105
106// Specialized result types for different cryptographic operations
107/// Result type for cipher operations
108pub type CipherResult<T> = Result<T>;
109/// Result type for hash operations
110pub type HashResult<T> = Result<T>;
111/// Result type for MAC operations
112pub type MacResult<T> = Result<T>;
113
114// Display implementation for error formatting
115impl fmt::Display for Error {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        match self {
118            Error::Parameter { name, reason } => {
119                write!(f, "Invalid parameter '{}': {}", name, reason)
120            }
121            Error::Length {
122                context,
123                expected,
124                actual,
125            } => {
126                write!(
127                    f,
128                    "Invalid length for {}: expected {}, got {}",
129                    context, expected, actual
130                )
131            }
132            Error::Authentication { algorithm } => {
133                write!(f, "Authentication failed for {}", algorithm)
134            }
135            Error::Randomness => f.write_str("Caller-provided randomness source failed"),
136            Error::NotImplemented { feature } => {
137                write!(f, "Feature not implemented: {}", feature)
138            }
139            Error::Processing { operation, details } => {
140                write!(f, "Processing error in {}: {}", operation, details)
141            }
142            Error::MacError { algorithm, details } => {
143                write!(f, "MAC error in {}: {}", algorithm, details)
144            }
145            #[cfg(feature = "std")]
146            Error::External { source, details } => {
147                write!(f, "External error from {}: {}", source, details)
148            }
149            #[cfg(not(feature = "std"))]
150            Error::External { source } => {
151                write!(f, "External error from {}", source)
152            }
153            Error::Other(msg) => write!(f, "{}", msg),
154        }
155    }
156}
157
158// Implement std::error::Error when std is available
159#[cfg(feature = "std")]
160impl std::error::Error for Error {}
161
162// Implement conversion to CoreError
163impl From<Error> for CoreError {
164    fn from(err: Error) -> Self {
165        match err {
166            Error::Parameter { name, reason } => {
167                // Preserve static parameter names, but never leak an owned name
168                // merely to satisfy the public error type's static context.
169                let context = match &name {
170                    Cow::Borrowed(name) => *name,
171                    Cow::Owned(_) => "algorithm parameter",
172                };
173                #[cfg(not(feature = "std"))]
174                let _ = reason;
175                CoreError::InvalidParameter {
176                    context,
177                    #[cfg(feature = "std")]
178                    message: match name {
179                        Cow::Borrowed(_) => reason.into_owned(),
180                        Cow::Owned(name) => format!("{name}: {reason}"),
181                    },
182                }
183            }
184            Error::Length {
185                context,
186                expected,
187                actual,
188            } => CoreError::InvalidLength {
189                context,
190                expected,
191                actual,
192            },
193            Error::Authentication { algorithm } => CoreError::AuthenticationFailed {
194                context: algorithm,
195                #[cfg(feature = "std")]
196                message: "authentication failed".to_string(),
197            },
198            Error::Randomness => CoreError::RandomGenerationError {
199                context: "caller-provided RNG",
200                #[cfg(feature = "std")]
201                message: "caller-provided randomness source failed".to_string(),
202            },
203            Error::NotImplemented { feature } => CoreError::NotImplemented { feature },
204            Error::Processing { operation, details } => {
205                #[cfg(not(feature = "std"))]
206                let _ = details;
207                CoreError::Other {
208                    context: operation,
209                    #[cfg(feature = "std")]
210                    message: details.to_string(),
211                }
212            }
213            Error::MacError { algorithm, details } => {
214                #[cfg(not(feature = "std"))]
215                let _ = details;
216                CoreError::Other {
217                    context: algorithm,
218                    #[cfg(feature = "std")]
219                    message: details.to_string(),
220                }
221            }
222            #[cfg(feature = "std")]
223            Error::External { source, details } => CoreError::Other {
224                context: source,
225                message: details,
226            },
227            #[cfg(not(feature = "std"))]
228            Error::External { source } => CoreError::Other {
229                context: source,
230                #[cfg(feature = "std")]
231                message: "external error".to_string(),
232            },
233            Error::Other(msg) => {
234                #[cfg(not(feature = "std"))]
235                let _ = msg;
236                CoreError::Other {
237                    context: "primitives",
238                    #[cfg(feature = "std")]
239                    message: msg.to_string(),
240                }
241            }
242        }
243    }
244}
245
246impl From<dcrypt_internal::random::Error> for Error {
247    fn from(_: dcrypt_internal::random::Error) -> Self {
248        Self::Randomness
249    }
250}
251
252/// Convert a primitives result to a core result with additional context
253#[inline]
254pub fn to_core_result<T>(r: Result<T>, ctx: &'static str) -> CoreResult<T> {
255    r.map_err(|e| {
256        let mut core = CoreError::from(e);
257        core = core.with_context(ctx);
258        core
259    })
260}
261
262// Include the validation submodule
263pub mod validate;
264
265#[cfg(test)]
266mod conversion_tests {
267    use super::*;
268
269    #[test]
270    fn randomness_preserves_its_public_error_category() {
271        let error = CoreError::from(Error::Randomness);
272        assert!(matches!(
273            error,
274            CoreError::RandomGenerationError {
275                context: "caller-provided RNG",
276                ..
277            }
278        ));
279    }
280
281    #[test]
282    fn owned_parameter_names_do_not_become_static_allocations() {
283        let error = CoreError::from(Error::param(
284            String::from("attacker-controlled name"),
285            "invalid value",
286        ));
287        assert!(matches!(
288            error,
289            CoreError::InvalidParameter {
290                context: "algorithm parameter",
291                ..
292            }
293        ));
294    }
295}