use thiserror::Error;
#[derive(Debug, Error)]
pub enum CryptoError {
#[error("key generation failed: {reason}")]
KeyGenFailed {
reason: String,
},
#[error("encapsulation failed: {reason}")]
EncapsulationFailed {
reason: String,
},
#[error("decapsulation failed: {reason}")]
DecapsulationFailed {
reason: String,
},
#[error("signing failed: {reason}")]
SigningFailed {
reason: String,
},
#[error("signature verification failed: {reason}")]
VerificationFailed {
reason: String,
},
#[error("encryption failed: {reason}")]
EncryptionFailed {
reason: String,
},
#[error("decryption failed: {reason}")]
DecryptionFailed {
reason: String,
},
#[error("KDF failed: {reason}")]
KdfFailed {
reason: String,
},
#[error("invalid key length: expected {expected} bytes, got {got}")]
InvalidKeyLength {
expected: usize,
got: usize,
},
#[error("invalid nonce length: expected {expected} bytes, got {got}")]
InvalidNonceLength {
expected: usize,
got: usize,
},
}
#[derive(Debug, Error)]
pub enum CorrectionError {
#[error("insufficient shards: need {needed}, have {available}")]
InsufficientShards {
needed: usize,
available: usize,
},
#[error("HMAC mismatch on shard {index}")]
HmacMismatch {
index: u8,
},
#[error("reed-solomon error: {reason}")]
ReedSolomonError {
reason: String,
},
#[error("invalid shard parameters: {reason}")]
InvalidParameters {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum StegoError {
#[error("payload too large: need {needed} bytes, cover holds {available}")]
PayloadTooLarge {
needed: u64,
available: u64,
},
#[error("unsupported cover type for this technique: {reason}")]
UnsupportedCoverType {
reason: String,
},
#[error("malformed cover data: {reason}")]
MalformedCoverData {
reason: String,
},
#[error("no payload found in stego cover")]
NoPayloadFound,
#[error("extracted data failed integrity check: {reason}")]
IntegrityCheckFailed {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum MediaError {
#[error("unsupported media format: {extension}")]
UnsupportedFormat {
extension: String,
},
#[error("decode error: {reason}")]
DecodeFailed {
reason: String,
},
#[error("encode error: {reason}")]
EncodeFailed {
reason: String,
},
#[error("IO error: {reason}")]
IoError {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum PdfError {
#[error("PDF parse error: {reason}")]
ParseFailed {
reason: String,
},
#[error("page render error on page {page}: {reason}")]
RenderFailed {
page: usize,
reason: String,
},
#[error("PDF rebuild error: {reason}")]
RebuildFailed {
reason: String,
},
#[error("PDF embed error: {reason}")]
EmbedFailed {
reason: String,
},
#[error("PDF extract error: {reason}")]
ExtractFailed {
reason: String,
},
#[error("IO error: {reason}")]
IoError {
reason: String,
},
#[error("PDF is encrypted and cannot be processed")]
Encrypted,
}
#[derive(Debug, Error)]
pub enum DistributionError {
#[error("insufficient covers: need {needed}, got {got}")]
InsufficientCovers {
needed: usize,
got: usize,
},
#[error("embed failed on cover {index}: {source}")]
EmbedFailed {
index: usize,
#[source]
source: StegoError,
},
#[error("error correction failed: {source}")]
CorrectionFailed {
#[source]
source: CorrectionError,
},
}
#[derive(Debug, Error)]
pub enum ReconstructionError {
#[error("insufficient covers for reconstruction: need {needed}, got {got}")]
InsufficientCovers {
needed: usize,
got: usize,
},
#[error("extraction failed on cover {index}: {source}")]
ExtractionFailed {
index: usize,
#[source]
source: StegoError,
},
#[error("error correction failed: {source}")]
CorrectionFailed {
#[source]
source: CorrectionError,
},
#[error("manifest verification failed: {reason}")]
ManifestVerificationFailed {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum AnalysisError {
#[error("unsupported cover type for analysis: {reason}")]
UnsupportedCoverType {
reason: String,
},
#[error("statistical computation failed: {reason}")]
ComputationFailed {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum ArchiveError {
#[error("archive pack error: {reason}")]
PackFailed {
reason: String,
},
#[error("archive unpack error: {reason}")]
UnpackFailed {
reason: String,
},
#[error("unsupported archive format: {reason}")]
UnsupportedFormat {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum AdaptiveError {
#[error(
"could not meet detectability budget of {target_db:.2} dB: best was {achieved_db:.2} dB"
)]
BudgetNotMet {
target_db: f64,
achieved_db: f64,
},
#[error("profile matching failed: {reason}")]
ProfileMatchFailed {
reason: String,
},
#[error("compression simulation failed: {reason}")]
CompressionSimFailed {
reason: String,
},
#[error("stego error during adaptive optimisation: {source}")]
StegoFailed {
#[source]
source: StegoError,
},
}
#[derive(Debug, Error)]
pub enum DeniableError {
#[error("cover capacity too small for dual-payload embedding")]
InsufficientCapacity,
#[error("dual embed failed: {reason}")]
EmbedFailed {
reason: String,
},
#[error("extraction failed for provided key: {reason}")]
ExtractionFailed {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum OpsecError {
#[error("wipe step failed for path {path}: {reason}")]
WipeStepFailed {
path: String,
reason: String,
},
#[error("amnesiac pipeline error: {reason}")]
PipelineError {
reason: String,
},
#[error("watermark error: {reason}")]
WatermarkError {
reason: String,
},
#[error("geographic manifest error: {reason}")]
ManifestError {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum CanaryError {
#[error("no covers provided for canary embedding")]
NoCovers,
#[error("canary embed failed: {source}")]
EmbedFailed {
#[source]
source: StegoError,
},
}
#[derive(Debug, Error)]
pub enum DeadDropError {
#[error("unsupported platform for dead drop: {reason}")]
UnsupportedPlatform {
reason: String,
},
#[error("dead drop encode failed: {reason}")]
EncodeFailed {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum TimeLockError {
#[error("time-lock puzzle not yet solvable; unlock at {unlock_at}")]
NotYetSolvable {
unlock_at: String,
},
#[error("puzzle computation failed: {reason}")]
ComputationFailed {
reason: String,
},
#[error("time-lock decrypt failed: {source}")]
DecryptFailed {
#[source]
source: CryptoError,
},
}
#[derive(Debug, Error)]
pub enum ScrubberError {
#[error("input is not valid UTF-8: {reason}")]
InvalidUtf8 {
reason: String,
},
#[error("could not satisfy stylo profile: {reason}")]
ProfileNotSatisfied {
reason: String,
},
}
#[derive(Debug, Error)]
pub enum CorpusError {
#[error("no suitable corpus cover found for payload of {payload_bytes} bytes")]
NoSuitableCover {
payload_bytes: u64,
},
#[error("corpus index error: {reason}")]
IndexError {
reason: String,
},
#[error("corpus add failed for path {path}: {reason}")]
AddFailed {
path: String,
reason: String,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn crypto_error_display_does_not_panic() {
let e = CryptoError::InvalidKeyLength {
expected: 32,
got: 16,
};
assert!(e.to_string().contains("32"));
}
#[test]
fn correction_error_display_does_not_panic() {
let e = CorrectionError::InsufficientShards {
needed: 5,
available: 2,
};
assert!(e.to_string().contains('5'));
}
#[test]
fn stego_error_display_does_not_panic() {
let e = StegoError::PayloadTooLarge {
needed: 1024,
available: 512,
};
assert!(e.to_string().contains("1024"));
}
#[test]
fn all_error_variants_display_without_panic() {
let errors: Vec<Box<dyn std::error::Error>> = vec![
Box::new(CryptoError::KeyGenFailed {
reason: "test".into(),
}),
Box::new(CorrectionError::HmacMismatch { index: 3 }),
Box::new(StegoError::NoPayloadFound),
Box::new(MediaError::UnsupportedFormat {
extension: "xyz".into(),
}),
Box::new(PdfError::ParseFailed {
reason: "test".into(),
}),
Box::new(DistributionError::InsufficientCovers { needed: 3, got: 1 }),
Box::new(ReconstructionError::InsufficientCovers { needed: 3, got: 1 }),
Box::new(AnalysisError::ComputationFailed {
reason: "test".into(),
}),
Box::new(ArchiveError::PackFailed {
reason: "test".into(),
}),
Box::new(AdaptiveError::BudgetNotMet {
target_db: 40.0,
achieved_db: 35.5,
}),
Box::new(DeniableError::InsufficientCapacity),
Box::new(OpsecError::PipelineError {
reason: "test".into(),
}),
Box::new(CanaryError::NoCovers),
Box::new(DeadDropError::UnsupportedPlatform {
reason: "test".into(),
}),
Box::new(TimeLockError::NotYetSolvable {
unlock_at: "2030-01-01T00:00:00Z".into(),
}),
Box::new(ScrubberError::InvalidUtf8 {
reason: "test".into(),
}),
Box::new(CorpusError::NoSuitableCover {
payload_bytes: 1024,
}),
];
for e in &errors {
assert!(!e.to_string().is_empty(), "empty display for {e:?}");
}
}
}