use std::collections::HashMap;
use std::io::{Read, Write};
use std::path::Path;
use bytes::Bytes;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::domain::errors::{
AdaptiveError, AnalysisError, ArchiveError, CanaryError, CorrectionError, CryptoError,
DeadDropError, DeniableError, DistributionError, MediaError, OpsecError, PdfError,
ReconstructionError, ScrubberError, StegoError, TimeLockError,
};
use crate::domain::types::{
AnalysisReport, ArchiveFormat, CanaryShard, Capacity, CoverMedia, DeniableKeySet,
DeniablePayloadPair, EmbeddingProfile, GeographicManifest, KeyPair, Payload, PlatformProfile,
Shard, Signature, StegoTechnique, StyloProfile, TimeLockPuzzle, WatermarkReceipt,
WatermarkTripwireTag,
};
pub trait Encryptor {
fn generate_keypair(&self) -> Result<KeyPair, CryptoError>;
fn encapsulate(&self, public_key: &[u8]) -> Result<(Bytes, Bytes), CryptoError>;
fn decapsulate(&self, secret_key: &[u8], ciphertext: &[u8]) -> Result<Bytes, CryptoError>;
}
pub trait Signer {
fn generate_keypair(&self) -> Result<KeyPair, CryptoError>;
fn sign(&self, secret_key: &[u8], message: &[u8]) -> Result<Signature, CryptoError>;
fn verify(
&self,
public_key: &[u8],
message: &[u8],
signature: &Signature,
) -> Result<bool, CryptoError>;
}
pub trait SymmetricCipher {
fn encrypt(&self, key: &[u8], nonce: &[u8], plaintext: &[u8]) -> Result<Bytes, CryptoError>;
fn decrypt(&self, key: &[u8], nonce: &[u8], ciphertext: &[u8]) -> Result<Bytes, CryptoError>;
}
pub trait ErrorCorrector {
fn encode(
&self,
data: &[u8],
data_shards: u8,
parity_shards: u8,
) -> Result<Vec<Shard>, CorrectionError>;
fn decode(
&self,
shards: &[Option<Shard>],
data_shards: u8,
parity_shards: u8,
) -> Result<Bytes, CorrectionError>;
}
pub trait EmbedTechnique {
fn technique(&self) -> StegoTechnique;
fn capacity(&self, cover: &CoverMedia) -> Result<Capacity, StegoError>;
fn embed(&self, cover: CoverMedia, payload: &Payload) -> Result<CoverMedia, StegoError>;
}
pub trait ExtractTechnique {
fn technique(&self) -> StegoTechnique;
fn extract(&self, stego: &CoverMedia) -> Result<Payload, StegoError>;
}
pub trait MediaLoader {
fn load(&self, path: &Path) -> Result<CoverMedia, MediaError>;
fn save(&self, media: &CoverMedia, path: &Path) -> Result<(), MediaError>;
}
pub trait PdfProcessor {
fn load_pdf(&self, path: &Path) -> Result<CoverMedia, PdfError>;
fn save_pdf(&self, media: &CoverMedia, path: &Path) -> Result<(), PdfError>;
fn render_pages_to_images(&self, pdf: &CoverMedia) -> Result<Vec<CoverMedia>, PdfError>;
fn rebuild_pdf_from_images(
&self,
images: Vec<CoverMedia>,
original: &CoverMedia,
) -> Result<CoverMedia, PdfError>;
fn embed_in_content_stream(
&self,
pdf: CoverMedia,
payload: &Payload,
) -> Result<CoverMedia, PdfError>;
fn extract_from_content_stream(&self, pdf: &CoverMedia) -> Result<Payload, PdfError>;
fn embed_in_metadata(&self, pdf: CoverMedia, payload: &Payload)
-> Result<CoverMedia, PdfError>;
fn extract_from_metadata(&self, pdf: &CoverMedia) -> Result<Payload, PdfError>;
}
pub trait Distributor {
fn distribute(
&self,
payload: &Payload,
profile: &EmbeddingProfile,
covers: Vec<CoverMedia>,
embedder: &dyn EmbedTechnique,
) -> Result<Vec<CoverMedia>, DistributionError>;
}
pub trait Reconstructor {
fn reconstruct(
&self,
covers: Vec<CoverMedia>,
extractor: &dyn ExtractTechnique,
progress_cb: &dyn Fn(usize, usize),
) -> Result<Payload, ReconstructionError>;
}
pub trait CapacityAnalyser {
fn analyse(
&self,
cover: &CoverMedia,
technique: StegoTechnique,
) -> Result<AnalysisReport, AnalysisError>;
}
pub trait ArchiveHandler {
fn pack(&self, files: &[(&str, &[u8])], format: ArchiveFormat) -> Result<Bytes, ArchiveError>;
fn unpack(
&self,
archive: &[u8],
format: ArchiveFormat,
) -> Result<Vec<(String, Bytes)>, ArchiveError>;
}
mod serde_quant_table {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(arr: &[u16; 64], s: S) -> Result<S::Ok, S::Error> {
arr.as_slice().serialize(s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u16; 64], D::Error> {
let v = Vec::<u16>::deserialize(d)?;
v.as_slice().try_into().map_err(|_| {
serde::de::Error::invalid_length(v.len(), &"exactly 64 JPEG quantisation coefficients")
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CameraProfile {
#[serde(with = "serde_quant_table")]
pub quantisation_table: [u16; 64],
pub noise_floor_db: f64,
pub model_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiGenProfile {
pub model_id: String,
pub channel_weights: [f64; 3],
pub carrier_map: HashMap<String, Vec<CarrierBin>>,
}
impl AiGenProfile {
#[must_use]
pub fn carrier_bins_for(&self, width: u32, height: u32) -> Option<&[CarrierBin]> {
let key = format!("{width}x{height}");
self.carrier_map.get(&key).map(Vec::as_slice)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CarrierBin {
pub freq: (u32, u32),
pub phase: f64,
#[serde(deserialize_with = "de_clamp_coherence")]
coherence: f64,
}
fn de_clamp_coherence<'de, D: serde::Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
let v = f64::deserialize(d)?;
Ok(v.clamp(0.0, 1.0))
}
impl CarrierBin {
#[must_use]
pub const fn new(freq: (u32, u32), phase: f64, coherence: f64) -> Self {
Self {
freq,
phase,
coherence: coherence.clamp(0.0, 1.0),
}
}
#[must_use]
pub const fn coherence(&self) -> f64 {
self.coherence
}
#[must_use]
pub fn is_strong(&self) -> bool {
self.coherence >= 0.90
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", content = "data")]
pub enum CoverProfile {
Camera(CameraProfile),
AiGenerator(AiGenProfile),
}
impl CoverProfile {
#[must_use]
pub fn model_id(&self) -> &str {
match self {
Self::Camera(p) => &p.model_id,
Self::AiGenerator(p) => &p.model_id,
}
}
}
pub trait AdaptiveOptimiser {
fn optimise(
&self,
stego: CoverMedia,
original: &CoverMedia,
target_db: f64,
) -> Result<CoverMedia, AdaptiveError>;
}
pub trait CoverProfileMatcher {
fn profile_for(&self, cover: &CoverMedia) -> Option<CoverProfile>;
fn apply_profile(
&self,
cover: CoverMedia,
profile: &CoverProfile,
) -> Result<CoverMedia, AdaptiveError>;
}
pub trait CompressionSimulator {
fn simulate(
&self,
cover: CoverMedia,
platform: &PlatformProfile,
) -> Result<CoverMedia, AdaptiveError>;
fn survivable_capacity(
&self,
cover: &CoverMedia,
platform: &PlatformProfile,
) -> Result<Capacity, AdaptiveError>;
}
pub trait DeniableEmbedder {
fn embed_dual(
&self,
cover: CoverMedia,
pair: &DeniablePayloadPair,
keys: &DeniableKeySet,
embedder: &dyn EmbedTechnique,
) -> Result<CoverMedia, DeniableError>;
fn extract_with_key(
&self,
stego: &CoverMedia,
key: &[u8],
extractor: &dyn ExtractTechnique,
) -> Result<Payload, DeniableError>;
}
pub trait PanicWiper {
fn wipe(&self, config: &crate::domain::types::PanicWipeConfig) -> Result<(), OpsecError>;
}
pub trait ForensicWatermarker {
fn embed_tripwire(
&self,
cover: CoverMedia,
tag: &WatermarkTripwireTag,
) -> Result<CoverMedia, OpsecError>;
fn identify_recipient(
&self,
stego: &CoverMedia,
tags: &[WatermarkTripwireTag],
) -> Result<Option<WatermarkReceipt>, OpsecError>;
}
pub trait AmnesiaPipeline {
fn embed_in_memory(
&self,
payload_input: &mut dyn Read,
cover_input: &mut dyn Read,
output: &mut dyn Write,
technique: &dyn EmbedTechnique,
) -> Result<(), OpsecError>;
}
pub trait GeographicDistributor {
fn distribute_with_manifest(
&self,
payload: &Payload,
covers: Vec<CoverMedia>,
manifest: &GeographicManifest,
embedder: &dyn EmbedTechnique,
) -> Result<Vec<CoverMedia>, OpsecError>;
}
pub trait CanaryService {
fn embed_canary(
&self,
covers: Vec<CoverMedia>,
embedder: &dyn EmbedTechnique,
) -> Result<(Vec<CoverMedia>, CanaryShard), CanaryError>;
fn check_canary(&self, shard: &CanaryShard) -> bool;
}
pub trait DeadDropEncoder {
fn encode_for_platform(
&self,
cover: CoverMedia,
payload: &Payload,
platform: &PlatformProfile,
embedder: &dyn EmbedTechnique,
) -> Result<CoverMedia, DeadDropError>;
}
pub trait TimeLockService {
fn lock(
&self,
payload: &Payload,
unlock_at: DateTime<Utc>,
) -> Result<TimeLockPuzzle, TimeLockError>;
fn unlock(&self, puzzle: &TimeLockPuzzle) -> Result<Payload, TimeLockError>;
fn try_unlock(&self, puzzle: &TimeLockPuzzle) -> Result<Option<Payload>, TimeLockError>;
}
pub trait StyloScrubber {
fn scrub(&self, text: &str, profile: &StyloProfile) -> Result<String, ScrubberError>;
}
pub trait CorpusIndex {
fn search(
&self,
payload: &Payload,
technique: StegoTechnique,
max_results: usize,
) -> Result<Vec<crate::domain::types::CorpusEntry>, crate::domain::errors::CorpusError>;
fn add_to_index(
&self,
path: &Path,
) -> Result<crate::domain::types::CorpusEntry, crate::domain::errors::CorpusError>;
fn build_index(&self, corpus_dir: &Path) -> Result<usize, crate::domain::errors::CorpusError>;
fn search_for_model(
&self,
payload: &Payload,
model_id: &str,
resolution: (u32, u32),
max_results: usize,
) -> Result<Vec<crate::domain::types::CorpusEntry>, crate::domain::errors::CorpusError>;
fn model_stats(&self) -> Vec<(crate::domain::types::SpectralKey, usize)>;
}
#[cfg(test)]
mod object_safety_tests {
use super::*;
#[test]
fn all_port_traits_are_object_safe() {
fn assert_object_safe<T: ?Sized>() {}
assert_object_safe::<dyn Encryptor>();
assert_object_safe::<dyn Signer>();
assert_object_safe::<dyn SymmetricCipher>();
assert_object_safe::<dyn ErrorCorrector>();
assert_object_safe::<dyn EmbedTechnique>();
assert_object_safe::<dyn ExtractTechnique>();
assert_object_safe::<dyn MediaLoader>();
assert_object_safe::<dyn PdfProcessor>();
assert_object_safe::<dyn Distributor>();
assert_object_safe::<dyn Reconstructor>();
assert_object_safe::<dyn CapacityAnalyser>();
assert_object_safe::<dyn ArchiveHandler>();
assert_object_safe::<dyn AdaptiveOptimiser>();
assert_object_safe::<dyn CoverProfileMatcher>();
assert_object_safe::<dyn CompressionSimulator>();
assert_object_safe::<dyn DeniableEmbedder>();
assert_object_safe::<dyn PanicWiper>();
assert_object_safe::<dyn ForensicWatermarker>();
assert_object_safe::<dyn AmnesiaPipeline>();
assert_object_safe::<dyn GeographicDistributor>();
assert_object_safe::<dyn CanaryService>();
assert_object_safe::<dyn DeadDropEncoder>();
assert_object_safe::<dyn TimeLockService>();
assert_object_safe::<dyn StyloScrubber>();
assert_object_safe::<dyn CorpusIndex>();
}
}
#[cfg(test)]
mod cover_profile_tests {
use super::*;
type TestResult = Result<(), Box<dyn std::error::Error>>;
#[test]
fn camera_profile_model_id_via_cover_profile() {
let profile = CoverProfile::Camera(CameraProfile {
quantisation_table: [0u16; 64],
noise_floor_db: -80.0,
model_id: "canon-eos-r6".to_string(),
});
assert_eq!(profile.model_id(), "canon-eos-r6");
}
#[test]
fn ai_gen_profile_model_id_via_cover_profile() {
let profile = CoverProfile::AiGenerator(AiGenProfile {
model_id: "gemini".to_string(),
channel_weights: [0.85, 1.0, 0.70],
carrier_map: HashMap::new(),
});
assert_eq!(profile.model_id(), "gemini");
}
#[test]
fn carrier_bin_coherence_clamped_above_one() {
let bin = CarrierBin::new((9, 9), 0.0, 1.5);
assert!((bin.coherence() - 1.0).abs() < f64::EPSILON);
}
#[test]
fn carrier_bin_coherence_clamped_below_zero() {
let bin = CarrierBin::new((9, 9), 0.0, -0.5);
assert!((bin.coherence() - 0.0).abs() < f64::EPSILON);
}
#[test]
fn carrier_bin_is_strong_at_exactly_0_90() {
let strong = CarrierBin::new((9, 9), 0.0, 0.90);
assert!(strong.is_strong());
}
#[test]
fn carrier_bin_not_strong_below_0_90() {
let weak = CarrierBin::new((9, 9), 0.0, 0.899_999);
assert!(!weak.is_strong());
}
#[test]
fn ai_gen_profile_carrier_bins_for_known_resolution() {
let bins = vec![CarrierBin::new((9, 9), 0.0, 1.0)];
let mut carrier_map = HashMap::new();
carrier_map.insert("1024x1024".to_string(), bins);
let profile = AiGenProfile {
model_id: "gemini".to_string(),
channel_weights: [0.85, 1.0, 0.70],
carrier_map,
};
assert!(profile.carrier_bins_for(1024, 1024).is_some());
assert!(profile.carrier_bins_for(512, 512).is_none());
}
#[test]
fn ai_gen_profile_carrier_bins_count() {
let bins = vec![
CarrierBin::new((9, 9), 0.0, 1.0),
CarrierBin::new((5, 5), 0.0, 1.0),
];
let mut carrier_map = HashMap::new();
carrier_map.insert("1024x1024".to_string(), bins);
let profile = AiGenProfile {
model_id: "gemini".to_string(),
channel_weights: [0.85, 1.0, 0.70],
carrier_map,
};
assert_eq!(
profile.carrier_bins_for(1024, 1024).map(<[_]>::len),
Some(2)
);
}
#[test]
fn cover_profile_round_trips_through_serde_json() -> TestResult {
let profile = CoverProfile::AiGenerator(AiGenProfile {
model_id: "gemini".to_string(),
channel_weights: [0.85, 1.0, 0.70],
carrier_map: HashMap::new(),
});
let json = serde_json::to_string(&profile)?;
let decoded: CoverProfile = serde_json::from_str(&json)?;
assert_eq!(decoded.model_id(), "gemini");
Ok(())
}
#[test]
fn camera_cover_profile_round_trips_through_serde_json() -> TestResult {
let profile = CoverProfile::Camera(CameraProfile {
quantisation_table: [2u16; 64],
noise_floor_db: -75.0,
model_id: "nikon-z9".to_string(),
});
let json = serde_json::to_string(&profile)?;
let decoded: CoverProfile = serde_json::from_str(&json)?;
assert_eq!(decoded.model_id(), "nikon-z9");
Ok(())
}
}