pub mod engines;
pub mod error;
pub mod operations;
use crate::engines::{PdfEngine, PngEngine};
use crate::error::Result;
use std::io;
pub trait SteganographyEngine {
fn magic_bytes(&self) -> &[u8];
fn format_name(&self) -> &str;
fn format_ext(&self) -> &str;
fn embed(&self, source_data: &[u8], payload: &[u8]) -> Result<Vec<u8>>;
fn extract(&self, source_data: &[u8]) -> Result<Vec<u8>>;
}
#[derive(Default)]
pub struct EngineRouter {
engines: Vec<Box<dyn SteganographyEngine>>,
}
impl EngineRouter {
pub fn new() -> Self {
Self {
engines: vec![Box::new(PdfEngine::new()), Box::new(PngEngine::new())],
}
}
pub fn detect_engine(&self, data: &[u8]) -> Result<&dyn SteganographyEngine> {
for engine in &self.engines {
if data.starts_with(engine.magic_bytes()) {
return Ok(engine.as_ref());
}
}
Err(crate::error::LupinError::EngineDetection {
source: io::Error::new(
io::ErrorKind::Unsupported,
"Unsupported file format - no matching engine found",
),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_minimal_pdf() -> Vec<u8> {
b"%PDF-1.4\n1 0 obj\n<<\n/Type /Catalog\n>>\nendobj\nxref\n0 1\n0000000000 65535 f\ntrailer\n<<\n/Size 1\n/Root 1 0 R\n>>\nstartxref\n73\n%%EOF".to_vec()
}
fn create_minimal_png() -> Vec<u8> {
let mut png = Vec::new();
png.extend_from_slice(b"\x89PNG\r\n\x1a\n"); png.extend_from_slice(&[0, 0, 0, 13]); png.extend_from_slice(b"IHDR");
png.extend_from_slice(&[0; 13]); png.extend_from_slice(&[0; 4]); png
}
fn create_unsupported_format() -> Vec<u8> {
b"RIFF....WEBP".to_vec() }
#[test]
fn test_detect_engine_pdf() {
let router = EngineRouter::new();
let pdf_data = create_minimal_pdf();
let result = router.detect_engine(&pdf_data);
assert!(result.is_ok());
let engine = result.unwrap();
assert_eq!(engine.format_name(), "PDF");
}
#[test]
fn test_detect_engine_png() {
let router = EngineRouter::new();
let png_data = create_minimal_png();
let result = router.detect_engine(&png_data);
assert!(result.is_ok());
let engine = result.unwrap();
assert_eq!(engine.format_name(), "PNG");
}
#[test]
fn test_detect_engine_unsupported() {
let router = EngineRouter::new();
let unsupported_data = create_unsupported_format();
let result = router.detect_engine(&unsupported_data);
assert!(result.is_err());
if let Err(error) = result {
match error {
crate::error::LupinError::EngineDetection { .. } => (), other => panic!("Expected EngineDetection error, got {:?}", other),
}
}
}
#[test]
fn test_detect_engine_empty_data() {
let router = EngineRouter::new();
let empty_data = Vec::new();
let result = router.detect_engine(&empty_data);
assert!(result.is_err());
if let Err(error) = result {
match error {
crate::error::LupinError::EngineDetection { .. } => (), other => panic!("Expected EngineDetection error, got {:?}", other),
}
}
}
#[test]
fn test_detect_engine_partial_magic_bytes() {
let router = EngineRouter::new();
let partial_pdf = b"%PD".to_vec();
let result = router.detect_engine(&partial_pdf);
assert!(result.is_err());
if let Err(error) = result {
match error {
crate::error::LupinError::EngineDetection { .. } => (), other => panic!("Expected EngineDetection error, got {:?}", other),
}
}
}
}