Add to your Cargo.toml:
[dependencies]
lupin = "1.0"use lupin::operations::{embed, extract};
use lupin::EmbedMode;
// Read source and payload
let source_data = std::fs::read("document.pdf")?;
let payload_data = std::fs::read("secret.txt")?;
// Embed with rich metadata (EmbedMode::Capacity: unlimited size, easier to detect;
// EmbedMode::Stealth: harder to detect, not yet supported by every engine)
let (embedded_data, embed_result) = embed(&source_data, &payload_data, EmbedMode::Capacity)?;
println!("Embedded {} bytes into {} using {} engine",
payload_data.len(), embed_result.source_size, embed_result.engine);
// Save result
std::fs::write("output.pdf", embedded_data)?;
// Extract later - detection is automatic, no mode needed
let output_data = std::fs::read("output.pdf")?;
let (extracted_data, extract_result) = extract(&output_data)?;
std::fs::write("recovered.txt", extracted_data)?;use lupin::operations::{embed, extract, EmbedResult, ExtractResult};
use lupin::EmbedMode;
// Vector-based operations
pub fn embed(source_data: &[u8], payload_data: &[u8], mode: EmbedMode) -> Result<(Vec<u8>, EmbedResult)>
pub fn extract(source_data: &[u8]) -> Result<(Vec<u8>, ExtractResult)>pub enum EmbedMode {
Capacity, // default: unlimited payload size, easy to detect
Stealth, // reserved for a future low-detectability strategy; no engine implements
// it yet, so passing it returns LupinError::StealthNotSupported
}#[derive(Debug, Clone)]
pub struct EmbedResult {
pub source_size: usize, // Original file size
pub output_size: usize, // Final file size (source + hidden data)
pub engine: String, // Engine used (e.g., "PDF")
}
#[derive(Debug, Clone)]
pub struct ExtractResult {
pub source_size: usize, // Source file size
pub payload_size: usize, // Extracted data size
pub engine: String, // Engine used
}Operations take and return &[u8]/Vec<u8> rather than file paths, so the library itself never touches the filesystem. That means you can embed and extract data from files, network responses, or in-memory buffers, and tests can use byte literals directly instead of fixture files. Reading and writing files is left to the caller.
use lupin::operations::{embed, extract};
use lupin::EmbedMode;
// From network or any byte source
let source_data = download_pdf_from_url("https://example.com/doc.pdf").await?;
let payload_data = b"secret message".to_vec();
// Embed without touching filesystem
let (result, metadata) = embed(&source_data, &payload_data, EmbedMode::Capacity)?;
println!("Output size: {} bytes (+{:.1}% increase)",
metadata.output_size,
(metadata.output_size as f64 / metadata.source_size as f64 - 1.0) * 100.0);
// Stream result anywhere
send_to_storage(&result).await?;use lupin::error::LupinError;
use lupin::operations::embed;
use lupin::EmbedMode;
let source_data = std::fs::read("document.pdf")?;
let payload_data = std::fs::read("secret.txt")?;
match embed(&source_data, &payload_data, EmbedMode::Capacity) {
Ok((embedded_data, metadata)) => {
println!("Success! Used {} engine", metadata.engine);
std::fs::write("output.pdf", embedded_data)?;
}
Err(LupinError::EngineDetection { .. }) => {
eprintln!("File format not supported");
}
Err(LupinError::EmbedCollision { .. }) => {
eprintln!("File already contains hidden data");
}
Err(LupinError::PdfNoEofMarker) => {
eprintln!("Invalid PDF file");
}
// Returned if you pass EmbedMode::Stealth: no engine implements stealth yet
Err(LupinError::StealthNotSupported { format }) => {
eprintln!("Stealth mode isn't implemented for {format} yet");
}
Err(e) => {
eprintln!("Other error: {}", e);
}
}For more control, you can use the engine system directly:
use lupin::{EmbedMode, EngineRouter, SteganographyEngine};
let router = EngineRouter::new();
let data = std::fs::read("document.pdf")?;
// Auto-detect and get the appropriate engine
let engine = router.detect_engine(&data)?;
println!("Detected format: {}", engine.format_name());
// Use the engine directly
let payload = b"secret data";
let result = engine.embed(&data, payload, EmbedMode::Capacity)?;
// Save the embedded data
std::fs::write("embedded.pdf", result)?;#[cfg(test)]
mod tests {
use lupin::operations::{embed, extract};
use lupin::EmbedMode;
#[test]
fn test_round_trip() {
// Create minimal PDF
let pdf_data = b"%PDF-1.4\n%%EOF".to_vec();
let payload = b"test payload";
// Embed
let (embedded, embed_result) = embed(&pdf_data, payload, EmbedMode::Capacity).unwrap();
assert_eq!(embed_result.engine, "PDF");
assert_eq!(embed_result.source_size, pdf_data.len());
assert!(embed_result.output_size > embed_result.source_size);
// Extract
let (extracted, extract_result) = extract(&embedded).unwrap();
assert_eq!(extracted, payload);
assert_eq!(extract_result.payload_size, payload.len());
}
}The full list of error types can always be found in the lupin::error module.
use lupin::error::LupinError;
// Common error types you'll encounter:
LupinError::EngineDetection { source } // File format not supported
LupinError::EmbedCollision { source } // Source already has hidden data
LupinError::EmptyPayload // Payload must not be empty
LupinError::StealthNotSupported { format } // That engine doesn't implement stealth mode yet
LupinError::PdfNoEofMarker // Invalid PDF (no %%EOF)
LupinError::PdfNoHiddenData // No steganographic data found
LupinError::PdfCorruptedData // Hidden data is corrupted
LupinError::PngNoIendChunk // Invalid PNG (no IEND chunk)
LupinError::PngNoHiddenData // No steganographic data found
LupinError::PngCorruptedData // Hidden data is corrupted
LupinError::JpegInvalidFormat { reason } // Invalid JPEG (e.g. no SOI marker)
LupinError::JpegNoHiddenData // No steganographic data found
LupinError::SourceFileRead { path, source } // CLI: Can't read source file
LupinError::PayloadFileRead { path, source } // CLI: Can't read payload file
LupinError::OutputFileWrite { path, source } // CLI: Can't write output file