1pub mod error;
23pub mod stc;
24pub mod crypto;
25pub mod frame;
26pub mod permute;
27pub mod payload;
28pub mod progress;
29pub mod shadow_layer;
30
31pub mod cost;
33pub(crate) mod ghost;
34pub mod armor;
35#[cfg(feature = "video")]
36pub mod video;
37
38pub use error::StegoError;
39pub use ghost::quality;
40pub use ghost::quality::EncodeQuality;
41pub use ghost::optimizer::{optimize_cover, OptimizerConfig, OptimizerMode};
42
43pub use ghost::capacity;
45pub use ghost::side_info;
46pub use ghost::shadow;
47pub use ghost::optimizer;
48
49pub const MAX_DIMENSION: u32 = 16384;
52
53pub const MAX_PIXELS: u32 = 200_000_000;
58
59pub const MIN_ENCODE_DIMENSION: u32 = 200;
62
63pub const ARMOR_TARGET_DIMENSION: u32 = 1600;
68
69pub fn validate_encode_dimensions(width: u32, height: u32) -> Result<(), StegoError> {
78 if width < MIN_ENCODE_DIMENSION || height < MIN_ENCODE_DIMENSION {
79 return Err(StegoError::ImageTooSmall);
80 }
81 if width > MAX_DIMENSION || height > MAX_DIMENSION || width.checked_mul(height).is_none_or(|p| p > MAX_PIXELS) {
82 return Err(StegoError::ImageTooLarge);
83 }
84 Ok(())
85}
86pub 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};
87pub use ghost::pipeline::{ghost_encode_with_quality, ghost_encode_with_files_quality, ghost_encode_si_with_quality, ghost_encode_si_with_files_quality};
88pub use ghost::pipeline::{ghost_encode_with_shadows, ghost_encode_si_with_shadows, ghost_shadow_decode, ShadowLayer, GHOST_ENCODE_WITH_SHADOWS_STEPS};
89pub use ghost::pipeline::{ghost_encode_with_shadows_quality, ghost_encode_si_with_shadows_quality};
90pub use shadow::shadow_capacity;
91pub use capacity::estimate_shadow_capacity;
92pub use capacity::estimate_capacity as ghost_capacity;
93pub use capacity::estimate_capacity_si as ghost_capacity_si;
94pub use capacity::estimate_capacity_with_shadows as ghost_capacity_with_shadows;
95pub use armor::pipeline::{armor_encode, armor_encode_with_quality, armor_decode, DecodeQuality, ArmorCapacityInfo, armor_capacity_info};
96pub use armor::capacity::estimate_armor_capacity as armor_capacity;
97pub use payload::{PayloadData, FileEntry, compressed_payload_size};
98
99#[cfg(test)]
100mod dimension_tests {
101 use super::*;
102
103 #[test]
104 fn valid_dimensions() {
105 assert!(validate_encode_dimensions(800, 600).is_ok());
106 assert!(validate_encode_dimensions(3000, 4000).is_ok());
107 }
108
109 #[test]
110 fn boundary_min() {
111 assert!(validate_encode_dimensions(200, 200).is_ok());
112 assert!(validate_encode_dimensions(199, 200).is_err());
113 assert!(validate_encode_dimensions(200, 199).is_err());
114 }
115
116 #[test]
117 fn boundary_max_dimension() {
118 assert!(validate_encode_dimensions(16384, 1000).is_ok());
119 assert!(validate_encode_dimensions(1000, 16384).is_ok());
120 assert!(validate_encode_dimensions(16385, 1000).is_err());
121 assert!(validate_encode_dimensions(1000, 16385).is_err());
122 }
123
124 #[test]
125 fn too_many_pixels() {
126 assert!(validate_encode_dimensions(14143, 14143).is_err());
128 assert!(validate_encode_dimensions(14142, 14142).is_ok());
130 }
131
132 #[test]
133 fn error_variants() {
134 match validate_encode_dimensions(100, 300) {
135 Err(StegoError::ImageTooSmall) => {}
136 other => panic!("expected ImageTooSmall, got {other:?}"),
137 }
138 match validate_encode_dimensions(16385, 1000) {
139 Err(StegoError::ImageTooLarge) => {}
140 other => panic!("expected ImageTooLarge, got {other:?}"),
141 }
142 }
143}
144
145pub fn smart_decode(stego_bytes: &[u8], passphrase: &str) -> Result<(PayloadData, DecodeQuality), StegoError> {
153 let result = smart_decode_inner(stego_bytes, passphrase);
154 progress::finish();
155 result
156}
157
158#[cfg(not(feature = "parallel"))]
165fn smart_decode_inner(stego_bytes: &[u8], passphrase: &str) -> Result<(PayloadData, DecodeQuality), StegoError> {
166 progress::init(0); progress::check_cancelled()?;
169
170 let mut saw_decryption_failed = false;
171
172 match armor_decode(stego_bytes, passphrase) {
174 Ok((payload, quality)) => return Ok((payload, quality)),
175 Err(StegoError::DecryptionFailed) => {
176 saw_decryption_failed = true;
177 }
179 Err(StegoError::FrameCorrupted) => {
180 }
182 Err(e) => {
183 match ghost_decode(stego_bytes, passphrase) {
186 Ok(payload) => return Ok((payload, DecodeQuality::ghost())),
187 Err(_) => return Err(e), }
189 }
190 }
191
192 let (armor_done, _) = progress::get();
196 progress::set_total(armor_done + GHOST_DECODE_STEPS as u32);
197 let ghost_result = ghost_decode(stego_bytes, passphrase);
198 match ghost_result {
199 Ok(payload) => return Ok((payload, DecodeQuality::ghost())),
200 Err(StegoError::DecryptionFailed) => {
201 saw_decryption_failed = true;
202 }
203 Err(_) => {}
204 }
205
206 match ghost::pipeline::ghost_shadow_decode(stego_bytes, passphrase) {
208 Ok(payload) => return Ok((payload, DecodeQuality::ghost())),
209 Err(StegoError::DecryptionFailed) => {
210 saw_decryption_failed = true;
211 }
212 Err(_) => {}
213 }
214
215 if saw_decryption_failed {
216 Err(StegoError::DecryptionFailed)
217 } else {
218 Err(StegoError::FrameCorrupted)
219 }
220}
221
222#[cfg(feature = "parallel")]
228fn smart_decode_inner(stego_bytes: &[u8], passphrase: &str) -> Result<(PayloadData, DecodeQuality), StegoError> {
229 use crate::codec::jpeg::JpegImage;
230 use crate::stego::armor::fortress;
231 use crate::stego::armor::pipeline::armor_decode_no_fortress;
232
233 progress::init(0);
238 progress::check_cancelled()?;
239
240 let img = JpegImage::from_bytes(stego_bytes)?;
241
242 let (fortress_result, (stdm_result, (ghost_result, shadow_result))) = rayon::join(
243 || {
244 if img.num_components() > 0 {
245 fortress::fortress_decode(&img, passphrase)
246 } else {
247 Err(StegoError::FrameCorrupted)
248 }
249 },
250 || rayon::join(
251 || armor_decode_no_fortress(&img, stego_bytes, passphrase),
252 || rayon::join(
253 || ghost_decode(stego_bytes, passphrase),
254 || ghost::pipeline::ghost_shadow_decode_from_image(&img, passphrase),
255 ),
256 ),
257 );
258
259 if let Ok((payload, quality)) = fortress_result {
261 return Ok((payload, quality));
262 }
263
264 if let Ok((payload, quality)) = stdm_result {
266 return Ok((payload, quality));
267 }
268
269 if let Ok(payload) = ghost_result {
271 return Ok((payload, DecodeQuality::ghost()));
272 }
273
274 if let Ok(payload) = shadow_result {
276 return Ok((payload, DecodeQuality::ghost()));
277 }
278
279 let saw_decryption_failed = matches!(&fortress_result, Err(StegoError::DecryptionFailed))
281 || matches!(&stdm_result, Err(StegoError::DecryptionFailed))
282 || matches!(&ghost_result, Err(StegoError::DecryptionFailed))
283 || matches!(&shadow_result, Err(StegoError::DecryptionFailed));
284
285 if saw_decryption_failed {
286 return Err(StegoError::DecryptionFailed);
287 }
288
289 Err(stdm_result.unwrap_err())
290}