pub mod error;
pub mod stc;
pub mod crypto;
pub mod frame;
pub mod permute;
pub mod payload;
pub mod progress;
pub mod shadow_layer;
pub mod cost;
pub(crate) mod ghost;
pub mod armor;
#[cfg(feature = "video")]
pub mod video;
pub use error::StegoError;
pub use ghost::quality;
pub use ghost::quality::EncodeQuality;
pub use ghost::optimizer::{optimize_cover, OptimizerConfig, OptimizerMode};
pub use ghost::capacity;
pub use ghost::side_info;
pub use ghost::shadow;
pub use ghost::optimizer;
pub const MAX_DIMENSION: u32 = 16384;
pub const MAX_PIXELS: u32 = 200_000_000;
pub const MIN_ENCODE_DIMENSION: u32 = 200;
pub const ARMOR_TARGET_DIMENSION: u32 = 1600;
pub fn validate_encode_dimensions(width: u32, height: u32) -> Result<(), StegoError> {
if width < MIN_ENCODE_DIMENSION || height < MIN_ENCODE_DIMENSION {
return Err(StegoError::ImageTooSmall);
}
if width > MAX_DIMENSION || height > MAX_DIMENSION || width.checked_mul(height).is_none_or(|p| p > MAX_PIXELS) {
return Err(StegoError::ImageTooLarge);
}
Ok(())
}
pub use ghost::pipeline::{ghost_encode, ghost_decode, ghost_encode_with_files, ghost_encode_si, ghost_encode_si_with_files, GHOST_DECODE_STEPS, GHOST_ENCODE_STEPS};
pub use ghost::pipeline::{ghost_encode_with_quality, ghost_encode_with_files_quality, ghost_encode_si_with_quality, ghost_encode_si_with_files_quality};
pub use ghost::pipeline::{ghost_encode_with_shadows, ghost_encode_si_with_shadows, ghost_shadow_decode, ShadowLayer, GHOST_ENCODE_WITH_SHADOWS_STEPS};
pub use ghost::pipeline::{ghost_encode_with_shadows_quality, ghost_encode_si_with_shadows_quality};
pub use shadow::shadow_capacity;
pub use capacity::estimate_shadow_capacity;
pub use capacity::estimate_capacity as ghost_capacity;
pub use capacity::estimate_capacity_si as ghost_capacity_si;
pub use capacity::estimate_capacity_with_shadows as ghost_capacity_with_shadows;
pub use armor::pipeline::{armor_encode, armor_encode_with_quality, armor_decode, DecodeQuality, ArmorCapacityInfo, armor_capacity_info};
pub use armor::capacity::estimate_armor_capacity as armor_capacity;
pub use payload::{PayloadData, FileEntry, compressed_payload_size};
#[cfg(test)]
mod dimension_tests {
use super::*;
#[test]
fn valid_dimensions() {
assert!(validate_encode_dimensions(800, 600).is_ok());
assert!(validate_encode_dimensions(3000, 4000).is_ok());
}
#[test]
fn boundary_min() {
assert!(validate_encode_dimensions(200, 200).is_ok());
assert!(validate_encode_dimensions(199, 200).is_err());
assert!(validate_encode_dimensions(200, 199).is_err());
}
#[test]
fn boundary_max_dimension() {
assert!(validate_encode_dimensions(16384, 1000).is_ok());
assert!(validate_encode_dimensions(1000, 16384).is_ok());
assert!(validate_encode_dimensions(16385, 1000).is_err());
assert!(validate_encode_dimensions(1000, 16385).is_err());
}
#[test]
fn too_many_pixels() {
assert!(validate_encode_dimensions(14143, 14143).is_err());
assert!(validate_encode_dimensions(14142, 14142).is_ok());
}
#[test]
fn error_variants() {
match validate_encode_dimensions(100, 300) {
Err(StegoError::ImageTooSmall) => {}
other => panic!("expected ImageTooSmall, got {other:?}"),
}
match validate_encode_dimensions(16385, 1000) {
Err(StegoError::ImageTooLarge) => {}
other => panic!("expected ImageTooLarge, got {other:?}"),
}
}
}
pub fn smart_decode(stego_bytes: &[u8], passphrase: &str) -> Result<(PayloadData, DecodeQuality), StegoError> {
let result = smart_decode_inner(stego_bytes, passphrase);
progress::finish();
result
}
#[cfg(not(feature = "parallel"))]
fn smart_decode_inner(stego_bytes: &[u8], passphrase: &str) -> Result<(PayloadData, DecodeQuality), StegoError> {
progress::init(0);
progress::check_cancelled()?;
let mut saw_decryption_failed = false;
match armor_decode(stego_bytes, passphrase) {
Ok((payload, quality)) => return Ok((payload, quality)),
Err(StegoError::DecryptionFailed) => {
saw_decryption_failed = true;
}
Err(StegoError::FrameCorrupted) => {
}
Err(e) => {
match ghost_decode(stego_bytes, passphrase) {
Ok(payload) => return Ok((payload, DecodeQuality::ghost())),
Err(_) => return Err(e), }
}
}
let (armor_done, _) = progress::get();
progress::set_total(armor_done + GHOST_DECODE_STEPS as u32);
let ghost_result = ghost_decode(stego_bytes, passphrase);
match ghost_result {
Ok(payload) => return Ok((payload, DecodeQuality::ghost())),
Err(StegoError::DecryptionFailed) => {
saw_decryption_failed = true;
}
Err(_) => {}
}
match ghost::pipeline::ghost_shadow_decode(stego_bytes, passphrase) {
Ok(payload) => return Ok((payload, DecodeQuality::ghost())),
Err(StegoError::DecryptionFailed) => {
saw_decryption_failed = true;
}
Err(_) => {}
}
if saw_decryption_failed {
Err(StegoError::DecryptionFailed)
} else {
Err(StegoError::FrameCorrupted)
}
}
#[cfg(feature = "parallel")]
fn smart_decode_inner(stego_bytes: &[u8], passphrase: &str) -> Result<(PayloadData, DecodeQuality), StegoError> {
use crate::codec::jpeg::JpegImage;
use crate::stego::armor::fortress;
use crate::stego::armor::pipeline::armor_decode_no_fortress;
progress::init(0);
progress::check_cancelled()?;
let img = JpegImage::from_bytes(stego_bytes)?;
let (fortress_result, (stdm_result, (ghost_result, shadow_result))) = rayon::join(
|| {
if img.num_components() > 0 {
fortress::fortress_decode(&img, passphrase)
} else {
Err(StegoError::FrameCorrupted)
}
},
|| rayon::join(
|| armor_decode_no_fortress(&img, stego_bytes, passphrase),
|| rayon::join(
|| ghost_decode(stego_bytes, passphrase),
|| ghost::pipeline::ghost_shadow_decode_from_image(&img, passphrase),
),
),
);
if let Ok((payload, quality)) = fortress_result {
return Ok((payload, quality));
}
if let Ok((payload, quality)) = stdm_result {
return Ok((payload, quality));
}
if let Ok(payload) = ghost_result {
return Ok((payload, DecodeQuality::ghost()));
}
if let Ok(payload) = shadow_result {
return Ok((payload, DecodeQuality::ghost()));
}
let saw_decryption_failed = matches!(&fortress_result, Err(StegoError::DecryptionFailed))
|| matches!(&stdm_result, Err(StegoError::DecryptionFailed))
|| matches!(&ghost_result, Err(StegoError::DecryptionFailed))
|| matches!(&shadow_result, Err(StegoError::DecryptionFailed));
if saw_decryption_failed {
return Err(StegoError::DecryptionFailed);
}
Err(stdm_result.unwrap_err())
}