Skip to main content

stenoxide_core/pipeline/
error.rs

1//! The single error type of the pipeline layer.
2//!
3//! Every layer below reports its own failures in its own vocabulary, and none
4//! of them knows the others exist. [`PipelineError`] is where those vocabularies
5//! meet: one variant per lower layer, each holding the original error rather
6//! than a message rendered from it, so a caller that wants to react to a
7//! specific condition — a payload that does not fit, a container without
8//! texture — can still match on it after it has crossed the pipeline boundary.
9//!
10//! The [`std::error::Error::source`] chain is wired for every variant, which is
11//! what lets a front-end print the whole causal chain without this module
12//! having to flatten it into a string.
13
14use std::fmt;
15
16use crate::cost::hill::CostError;
17use crate::crypto::aead::CryptoError;
18use crate::crypto::expand::ExpandError;
19use crate::crypto::kdf::KdfError;
20use crate::image_io::phash::PHashError;
21use crate::image_io::validate::ValidationError;
22use crate::stego::sizer::SizerError;
23use crate::stego::stc::StcError;
24
25/// Failure while writing the stego image to disk.
26///
27/// The only failure of the pipeline that no lower layer can report: layers 1 to
28/// 4 read images and never write them, so there is no error type to reuse here.
29#[derive(Debug)]
30pub enum OutputError {
31    /// The sample buffer did not match the geometry it claims to have.
32    ///
33    /// Unreachable for a buffer that came out of
34    /// [`crate::image_io::validate::load_and_validate`], whose length is fixed
35    /// by the decoder. Kept as a variant rather than as an assertion because the
36    /// encoder's constructor is fallible and swallowing that would mean writing
37    /// a file the caller believes is a valid container.
38    MalformedBuffer,
39    /// The PNG encoder or the filesystem refused the write.
40    EncodingFailed(String),
41}
42
43impl fmt::Display for OutputError {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            OutputError::MalformedBuffer => write!(
47                f,
48                "the stego image buffer does not match its own dimensions and cannot be encoded"
49            ),
50            OutputError::EncodingFailed(message) => {
51                write!(f, "failed to write the stego image: {message}")
52            }
53        }
54    }
55}
56
57impl std::error::Error for OutputError {}
58
59/// Everything that can go wrong between a plaintext and a stego image.
60///
61/// The variants follow the order the layers run in, which is also the order in
62/// which a caller will meet them.
63#[derive(Debug)]
64pub enum PipelineError {
65    /// The container image failed a validation gate of layer 1.
66    Validation(ValidationError),
67    /// The container's perceptual hash is not reproducible, or the salt of a
68    /// stego image could not be recovered.
69    PHash(PHashError),
70    /// Argon2id password stretching failed.
71    Kdf(KdfError),
72    /// HKDF-SHA3-512 expansion of the master key failed.
73    Expand(ExpandError),
74    /// The cost layer refused the container.
75    Cost(CostError),
76    /// The payload does not fit in the container.
77    Sizer(SizerError),
78    /// The Syndrome-Trellis coder refused the operation.
79    Stc(StcError),
80    /// Compression, encryption or authentication failed.
81    Crypto(CryptoError),
82    /// The stego image could not be written to disk.
83    Output(OutputError),
84}
85
86impl fmt::Display for PipelineError {
87    /// Delegates to the wrapped error.
88    ///
89    /// No prefix is added. The lower layers already phrase their messages for a
90    /// user — "the message does not fit in this image", "choose a container with
91    /// more texture" — and wrapping them in "pipeline error: ..." would only
92    /// push the actionable part of the sentence further from the start of the
93    /// line.
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            PipelineError::Validation(err) => write!(f, "{err}"),
97            PipelineError::PHash(err) => write!(f, "{err}"),
98            PipelineError::Kdf(err) => write!(f, "{err}"),
99            PipelineError::Expand(err) => write!(f, "{err}"),
100            PipelineError::Cost(err) => write!(f, "{err}"),
101            PipelineError::Sizer(err) => write!(f, "{err}"),
102            PipelineError::Stc(err) => write!(f, "{err}"),
103            PipelineError::Crypto(err) => write!(f, "{err}"),
104            PipelineError::Output(err) => write!(f, "{err}"),
105        }
106    }
107}
108
109impl std::error::Error for PipelineError {
110    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
111        match self {
112            PipelineError::Validation(err) => Some(err),
113            PipelineError::PHash(err) => Some(err),
114            PipelineError::Kdf(err) => Some(err),
115            PipelineError::Expand(err) => Some(err),
116            PipelineError::Cost(err) => Some(err),
117            PipelineError::Sizer(err) => Some(err),
118            PipelineError::Stc(err) => Some(err),
119            PipelineError::Crypto(err) => Some(err),
120            PipelineError::Output(err) => Some(err),
121        }
122    }
123}
124
125impl From<ValidationError> for PipelineError {
126    fn from(err: ValidationError) -> Self {
127        PipelineError::Validation(err)
128    }
129}
130
131impl From<PHashError> for PipelineError {
132    fn from(err: PHashError) -> Self {
133        PipelineError::PHash(err)
134    }
135}
136
137impl From<KdfError> for PipelineError {
138    fn from(err: KdfError) -> Self {
139        PipelineError::Kdf(err)
140    }
141}
142
143impl From<ExpandError> for PipelineError {
144    fn from(err: ExpandError) -> Self {
145        PipelineError::Expand(err)
146    }
147}
148
149impl From<CostError> for PipelineError {
150    fn from(err: CostError) -> Self {
151        PipelineError::Cost(err)
152    }
153}
154
155impl From<SizerError> for PipelineError {
156    fn from(err: SizerError) -> Self {
157        PipelineError::Sizer(err)
158    }
159}
160
161impl From<StcError> for PipelineError {
162    fn from(err: StcError) -> Self {
163        PipelineError::Stc(err)
164    }
165}
166
167impl From<CryptoError> for PipelineError {
168    fn from(err: CryptoError) -> Self {
169        PipelineError::Crypto(err)
170    }
171}
172
173impl From<OutputError> for PipelineError {
174    fn from(err: OutputError) -> Self {
175        PipelineError::Output(err)
176    }
177}