shadowforge_lib/domain/deadrop/
mod.rs1use sha2::{Digest, Sha256};
7
8use crate::domain::types::{PlatformProfile, RetrievalManifest, StegoTechnique};
9
10#[must_use]
14pub fn build_retrieval_manifest(
15 platform: &PlatformProfile,
16 retrieval_url: String,
17 technique: StegoTechnique,
18 stego_bytes: &[u8],
19) -> RetrievalManifest {
20 let hash = Sha256::digest(stego_bytes);
21 let stego_hash = hex::encode(hash);
22
23 RetrievalManifest {
24 platform: platform.clone(),
25 retrieval_url,
26 technique,
27 stego_hash,
28 }
29}
30
31#[must_use]
33pub const fn platform_is_lossless(platform: &PlatformProfile) -> bool {
34 matches!(platform, PlatformProfile::Telegram)
35}
36
37#[cfg(test)]
38mod tests {
39 use super::*;
40
41 type TestResult = Result<(), Box<dyn std::error::Error>>;
42
43 #[test]
44 fn retrieval_manifest_serialises_to_json() -> TestResult {
45 let manifest = build_retrieval_manifest(
46 &PlatformProfile::Instagram,
47 "https://instagram.com/p/abc123".to_owned(),
48 StegoTechnique::LsbImage,
49 b"fake stego bytes",
50 );
51
52 let json = serde_json::to_string_pretty(&manifest)?;
53 assert!(json.contains("Instagram"));
54 assert!(json.contains("abc123"));
55 assert!(!manifest.stego_hash.is_empty());
56 Ok(())
57 }
58
59 #[test]
60 fn retrieval_manifest_hash_changes_with_content() {
61 let m1 = build_retrieval_manifest(
62 &PlatformProfile::Twitter,
63 "https://x.com/post/1".to_owned(),
64 StegoTechnique::LsbImage,
65 b"content A",
66 );
67 let m2 = build_retrieval_manifest(
68 &PlatformProfile::Twitter,
69 "https://x.com/post/1".to_owned(),
70 StegoTechnique::LsbImage,
71 b"content B",
72 );
73
74 assert_ne!(m1.stego_hash, m2.stego_hash);
75 }
76
77 #[test]
78 fn telegram_is_lossless() {
79 assert!(platform_is_lossless(&PlatformProfile::Telegram));
80 }
81
82 #[test]
83 fn instagram_is_not_lossless() {
84 assert!(!platform_is_lossless(&PlatformProfile::Instagram));
85 }
86
87 #[test]
88 fn retrieval_manifest_deserialises_roundtrip() -> TestResult {
89 let manifest = build_retrieval_manifest(
90 &PlatformProfile::Custom {
91 quality: 85,
92 subsampling: crate::domain::types::ChromaSubsampling::Yuv420,
93 },
94 "https://example.com/image.png".to_owned(),
95 StegoTechnique::LsbImage,
96 b"test data",
97 );
98
99 let json = serde_json::to_string(&manifest)?;
100 let recovered: RetrievalManifest = serde_json::from_str(&json)?;
101
102 assert_eq!(recovered.stego_hash, manifest.stego_hash);
103 assert_eq!(recovered.retrieval_url, manifest.retrieval_url);
104 Ok(())
105 }
106}