use std::collections::HashSet;
use std::io::{Read, Write};
use bytes::Bytes;
use zeroize::Zeroize;
use crate::domain::errors::OpsecError;
use crate::domain::ports::EmbedTechnique;
use crate::domain::types::{
CoverMedia, CoverMediaKind, GeoShardEntry, GeographicManifest, Payload, WatermarkTripwireTag,
};
fn read_all_zeroizing(reader: &mut dyn Read) -> Result<Vec<u8>, OpsecError> {
let mut buf = Vec::new();
reader.read_to_end(&mut buf).map_err(|e| {
buf.zeroize();
OpsecError::PipelineError {
reason: format!("failed to read input: {e}"),
}
})?;
Ok(buf)
}
pub fn embed_in_memory(
payload_input: &mut dyn Read,
cover_input: &mut dyn Read,
output: &mut dyn Write,
technique: &dyn EmbedTechnique,
) -> Result<(), OpsecError> {
let cover_bytes = read_all_zeroizing(cover_input)?;
let cover = CoverMedia {
kind: CoverMediaKind::PngImage,
data: Bytes::from(cover_bytes),
metadata: std::collections::HashMap::new(),
};
let mut payload_bytes = read_all_zeroizing(payload_input)?;
let payload = Payload::from_bytes(payload_bytes.clone());
payload_bytes.zeroize();
let stego = technique
.embed(cover, &payload)
.map_err(|e| OpsecError::PipelineError {
reason: format!("embed failed: {e}"),
})?;
output
.write_all(&stego.data)
.map_err(|e| OpsecError::PipelineError {
reason: format!("failed to write output: {e}"),
})?;
Ok(())
}
pub fn validate_manifest(manifest: &GeographicManifest) -> Result<(), OpsecError> {
let jurisdictions: HashSet<&str> = manifest
.shards
.iter()
.map(|e| e.jurisdiction.as_str())
.collect();
let distinct = jurisdictions.len();
if distinct < manifest.minimum_jurisdictions as usize {
return Err(OpsecError::ManifestError {
reason: format!(
"manifest requires {} distinct jurisdictions but only {} are assigned",
manifest.minimum_jurisdictions, distinct
),
});
}
Ok(())
}
pub fn build_manifest(
entries: Vec<GeoShardEntry>,
minimum_jurisdictions: u8,
) -> Result<GeographicManifest, OpsecError> {
let manifest = GeographicManifest {
shards: entries,
minimum_jurisdictions,
};
validate_manifest(&manifest)?;
Ok(manifest)
}
#[must_use]
pub fn recovery_complexity_score(manifest: &GeographicManifest) -> String {
let jurisdictions: HashSet<&str> = manifest
.shards
.iter()
.map(|e| e.jurisdiction.as_str())
.collect();
let mut sorted: Vec<&str> = jurisdictions.into_iter().collect();
sorted.sort_unstable();
let country_list = sorted.join(", ");
format!(
"Recovery requires cooperation across {} jurisdictions: [{}]. \
Estimated legal coordination time: > 6 months under MLAT.",
sorted.len(),
country_list
)
}
#[must_use]
pub fn manifest_to_markdown(manifest: &GeographicManifest) -> String {
use std::fmt::Write as _;
let mut md = String::from("# Geographic Distribution Manifest\n\n");
let _ = write!(
md,
"**Minimum jurisdictions for reconstruction:** {}\n\n",
manifest.minimum_jurisdictions
);
md.push_str("| Shard | Jurisdiction | Holder |\n");
md.push_str("|-------|-------------|--------|\n");
for entry in &manifest.shards {
let _ = writeln!(
md,
"| {} | {} | {} |",
entry.shard_index, entry.jurisdiction, entry.holder_description
);
}
md.push('\n');
let _ = writeln!(md, "**{}**", recovery_complexity_score(manifest));
md
}
const MARKER_PATTERN: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
const MARKER_BITS: usize = MARKER_PATTERN.len() * 8;
const LCG_A: u64 = 6_364_136_223_846_793_005;
const LCG_C: u64 = 1_442_695_040_888_963_407;
fn derive_positions(seed_bytes: &[u8], cover_len: usize, count: usize) -> Vec<usize> {
if cover_len == 0 || count == 0 {
return Vec::new();
}
let mut state: u64 = 0;
for (i, &b) in seed_bytes.iter().enumerate() {
#[expect(clippy::cast_possible_truncation, reason = "i % 8 always fits in u32")]
let shift = (i % 8) as u32 * 8;
state ^= u64::from(b).wrapping_shl(shift);
}
let mut positions = Vec::with_capacity(count);
let mut used = HashSet::with_capacity(count);
while positions.len() < count {
state = state.wrapping_mul(LCG_A).wrapping_add(LCG_C);
let pos = (state >> 16) as usize % cover_len;
if used.insert(pos) {
positions.push(pos);
}
}
positions
}
pub fn embed_watermark(
cover: &mut CoverMedia,
tag: &WatermarkTripwireTag,
) -> Result<(), OpsecError> {
if cover.data.len() < MARKER_BITS {
return Err(OpsecError::WatermarkError {
reason: format!(
"cover too small ({} bytes) for watermark ({MARKER_BITS} bits)",
cover.data.len(),
),
});
}
let positions = derive_positions(&tag.embedding_seed, cover.data.len(), MARKER_BITS);
let mut data = cover.data.to_vec();
for (bit_idx, &pos) in positions.iter().enumerate() {
#[expect(
clippy::indexing_slicing,
reason = "bit_idx < MARKER_BITS; pos validated by derive_positions"
)]
let marker_byte = MARKER_PATTERN[bit_idx / 8];
let bit = (marker_byte >> (7 - (bit_idx % 8))) & 1;
if let Some(byte) = data.get_mut(pos) {
*byte = (*byte & 0xFE) | bit;
}
}
cover.data = Bytes::from(data);
Ok(())
}
#[must_use]
pub fn identify_watermark(cover: &CoverMedia, tags: &[WatermarkTripwireTag]) -> Option<usize> {
if cover.data.len() < MARKER_BITS {
return None;
}
for (tag_idx, tag) in tags.iter().enumerate() {
let positions = derive_positions(&tag.embedding_seed, cover.data.len(), MARKER_BITS);
let mut all_match = true;
for (bit_idx, &pos) in positions.iter().enumerate() {
#[expect(
clippy::indexing_slicing,
reason = "bit_idx < MARKER_BITS; pos validated by derive_positions"
)]
let marker_byte = MARKER_PATTERN[bit_idx / 8];
let expected_bit = (marker_byte >> (7 - (bit_idx % 8))) & 1;
let actual_bit = cover.data.get(pos).map_or(0xFF, |b| b & 1);
if actual_bit != expected_bit {
all_match = false;
break;
}
}
if all_match {
return Some(tag_idx);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::domain::errors::StegoError;
use crate::domain::types::{Capacity, StegoTechnique};
use std::io::Cursor;
type TestResult = Result<(), Box<dyn std::error::Error>>;
struct MockEmbedder;
impl EmbedTechnique for MockEmbedder {
fn technique(&self) -> StegoTechnique {
StegoTechnique::LsbImage
}
fn capacity(&self, cover: &CoverMedia) -> Result<Capacity, StegoError> {
Ok(Capacity {
bytes: cover.data.len() as u64,
technique: StegoTechnique::LsbImage,
})
}
fn embed(&self, cover: CoverMedia, payload: &Payload) -> Result<CoverMedia, StegoError> {
let mut combined = cover.data.to_vec();
combined.extend_from_slice(payload.as_bytes());
Ok(CoverMedia {
kind: cover.kind,
data: Bytes::from(combined),
metadata: cover.metadata,
})
}
}
struct FailingEmbedder;
impl EmbedTechnique for FailingEmbedder {
fn technique(&self) -> StegoTechnique {
StegoTechnique::LsbImage
}
fn capacity(&self, _cover: &CoverMedia) -> Result<Capacity, StegoError> {
Ok(Capacity {
bytes: 0,
technique: StegoTechnique::LsbImage,
})
}
fn embed(&self, _cover: CoverMedia, _payload: &Payload) -> Result<CoverMedia, StegoError> {
Err(StegoError::MalformedCoverData {
reason: "forced failure".into(),
})
}
}
#[test]
fn amnesiac_embed_roundtrip() -> TestResult {
let cover_data = b"cover-image-bytes";
let payload_data = b"secret-message";
let mut cover_reader = Cursor::new(cover_data.to_vec());
let mut payload_reader = Cursor::new(payload_data.to_vec());
let mut output = Vec::new();
embed_in_memory(
&mut payload_reader,
&mut cover_reader,
&mut output,
&MockEmbedder,
)?;
assert!(output.len() > cover_data.len());
assert!(output.starts_with(cover_data));
assert!(output.ends_with(payload_data));
Ok(())
}
#[test]
fn amnesiac_embed_empty_payload() -> TestResult {
let cover_data = b"cover";
let payload_data: &[u8] = b"";
let mut cover_reader = Cursor::new(cover_data.to_vec());
let mut payload_reader = Cursor::new(payload_data.to_vec());
let mut output = Vec::new();
embed_in_memory(
&mut payload_reader,
&mut cover_reader,
&mut output,
&MockEmbedder,
)?;
assert_eq!(output.as_slice(), cover_data);
Ok(())
}
#[test]
fn amnesiac_embed_fails_on_bad_technique() {
let cover_data = b"cover";
let payload_data = b"secret";
let mut cover_reader = Cursor::new(cover_data.to_vec());
let mut payload_reader = Cursor::new(payload_data.to_vec());
let mut output = Vec::new();
let result = embed_in_memory(
&mut payload_reader,
&mut cover_reader,
&mut output,
&FailingEmbedder,
);
assert!(result.is_err());
}
#[test]
fn amnesiac_no_heap_leak_on_success() -> TestResult {
for _ in 0..10 {
let mut cover = Cursor::new(b"cover".to_vec());
let mut payload = Cursor::new(b"secret".to_vec());
let mut output = Vec::new();
embed_in_memory(&mut payload, &mut cover, &mut output, &MockEmbedder)?;
}
Ok(())
}
fn sample_manifest() -> GeographicManifest {
GeographicManifest {
shards: vec![
GeoShardEntry {
shard_index: 0,
jurisdiction: "IS".into(),
holder_description: "Trusted contact in Iceland".into(),
},
GeoShardEntry {
shard_index: 1,
jurisdiction: "CH".into(),
holder_description: "Secure facility in Switzerland".into(),
},
GeoShardEntry {
shard_index: 2,
jurisdiction: "SG".into(),
holder_description: "Data centre in Singapore".into(),
},
],
minimum_jurisdictions: 2,
}
}
#[test]
fn validate_manifest_passes_sufficient_jurisdictions() -> TestResult {
let manifest = sample_manifest();
validate_manifest(&manifest)?;
Ok(())
}
#[test]
fn validate_manifest_fails_insufficient_jurisdictions() {
let manifest = GeographicManifest {
shards: vec![GeoShardEntry {
shard_index: 0,
jurisdiction: "IS".into(),
holder_description: "contact".into(),
}],
minimum_jurisdictions: 3,
};
assert!(validate_manifest(&manifest).is_err());
}
#[test]
fn build_manifest_returns_valid() -> TestResult {
let entries = vec![
GeoShardEntry {
shard_index: 0,
jurisdiction: "IS".into(),
holder_description: "Iceland".into(),
},
GeoShardEntry {
shard_index: 1,
jurisdiction: "CH".into(),
holder_description: "Switzerland".into(),
},
];
let manifest = build_manifest(entries, 2)?;
assert_eq!(manifest.shards.len(), 2);
Ok(())
}
#[test]
fn recovery_complexity_score_mentions_jurisdictions() {
let manifest = sample_manifest();
let score = recovery_complexity_score(&manifest);
assert!(score.contains("3 jurisdictions"));
assert!(score.contains("IS"));
assert!(score.contains("CH"));
assert!(score.contains("SG"));
assert!(score.contains("MLAT"));
}
#[test]
fn manifest_to_markdown_contains_heading() {
let manifest = sample_manifest();
let md = manifest_to_markdown(&manifest);
assert!(md.contains("# Geographic Distribution Manifest"));
assert!(md.contains("Iceland"));
assert!(md.contains("IS"));
}
#[test]
fn build_manifest_fails_insufficient() {
let entries = vec![GeoShardEntry {
shard_index: 0,
jurisdiction: "IS".into(),
holder_description: "contact".into(),
}];
assert!(build_manifest(entries, 2).is_err());
}
fn make_cover(size: usize) -> CoverMedia {
CoverMedia {
kind: CoverMediaKind::PngImage,
data: Bytes::from(vec![0u8; size]),
metadata: std::collections::HashMap::new(),
}
}
fn make_tag(seed: &[u8]) -> WatermarkTripwireTag {
WatermarkTripwireTag {
recipient_id: uuid::Uuid::new_v4(),
embedding_seed: seed.to_vec(),
}
}
#[test]
fn embed_then_identify_roundtrip() -> TestResult {
let tag_a = make_tag(b"recipient-a-seed");
let mut cover = make_cover(1024);
embed_watermark(&mut cover, &tag_a)?;
let tags = [tag_a.clone()];
let result = identify_watermark(&cover, &tags);
assert_eq!(result, Some(0));
Ok(())
}
#[test]
fn different_tags_produce_different_covers() -> TestResult {
let tag_a = make_tag(b"seed-alpha");
let tag_b = make_tag(b"seed-beta");
let tag_c = make_tag(b"seed-gamma");
let mut cover_a = make_cover(1024);
let mut cover_b = make_cover(1024);
let mut cover_c = make_cover(1024);
embed_watermark(&mut cover_a, &tag_a)?;
embed_watermark(&mut cover_b, &tag_b)?;
embed_watermark(&mut cover_c, &tag_c)?;
assert_ne!(cover_a.data, cover_b.data);
assert_ne!(cover_a.data, cover_c.data);
assert_ne!(cover_b.data, cover_c.data);
Ok(())
}
#[test]
fn identify_picks_correct_tag() -> TestResult {
let tag_a = make_tag(b"aaaa");
let tag_b = make_tag(b"bbbb");
let mut cover = make_cover(1024);
embed_watermark(&mut cover, &tag_b)?;
let tags = [tag_a, tag_b];
let result = identify_watermark(&cover, &tags);
assert_eq!(result, Some(1)); Ok(())
}
#[test]
fn identify_returns_none_when_no_match() {
let tag_a = make_tag(b"unknown-seed");
let cover = make_cover(1024);
let tags = [tag_a];
let result = identify_watermark(&cover, &tags);
assert_eq!(result, None);
}
#[test]
fn embed_fails_on_small_cover() {
let tag = make_tag(b"seed");
let mut cover = make_cover(2);
let result = embed_watermark(&mut cover, &tag);
assert!(result.is_err());
}
#[test]
fn derive_positions_deterministic() {
let seed = b"test-seed";
let p1 = derive_positions(seed, 1000, 32);
let p2 = derive_positions(seed, 1000, 32);
assert_eq!(p1, p2);
}
#[test]
fn derive_positions_unique() {
let seed = b"unique-seed";
let positions = derive_positions(seed, 10_000, 100);
let unique: HashSet<usize> = positions.iter().copied().collect();
assert_eq!(unique.len(), positions.len());
}
}