use crate::stego::crypto::{NONCE_LEN, SALT_LEN};
use crate::stego::error::StegoError;
use crate::stego::payload::FileEntry;
pub struct ShadowLayer<'a> {
pub message: &'a str,
pub passphrase: &'a str,
pub files: &'a [FileEntry],
}
pub const SHADOW_FRAME_OVERHEAD: usize = 2 + SALT_LEN + NONCE_LEN + 16;
pub const SHADOW_FRAME_OVERHEAD_WIDE: usize = 4 + SALT_LEN + NONCE_LEN + 16;
pub const SHADOW_PARITY_TIERS: [usize; 6] = [4, 8, 16, 32, 64, 128];
pub const MAX_SHADOW_FRAME_BYTES: usize = 256 * 1024;
pub const MAX_SHADOW_FRAME_BYTES_WIDE: usize = 16 * 1024 * 1024;
pub fn build_shadow_frame(
plaintext_len: usize,
salt: &[u8; SALT_LEN],
nonce: &[u8; NONCE_LEN],
ciphertext: &[u8],
) -> Vec<u8> {
assert!(
plaintext_len <= u16::MAX as usize,
"shadow frame plaintext exceeds u16::MAX",
);
let mut fr = Vec::with_capacity(SHADOW_FRAME_OVERHEAD + plaintext_len);
fr.extend_from_slice(&(plaintext_len as u16).to_be_bytes());
fr.extend_from_slice(salt);
fr.extend_from_slice(nonce);
fr.extend_from_slice(ciphertext);
fr
}
pub struct ParsedShadowFrame {
pub plaintext_len: u16,
pub salt: [u8; SALT_LEN],
pub nonce: [u8; NONCE_LEN],
pub ciphertext: Vec<u8>,
}
pub fn parse_shadow_frame(data: &[u8]) -> Result<ParsedShadowFrame, StegoError> {
if data.len() < SHADOW_FRAME_OVERHEAD {
return Err(StegoError::FrameCorrupted);
}
let plaintext_len = u16::from_be_bytes([data[0], data[1]]);
let expected_len = SHADOW_FRAME_OVERHEAD + plaintext_len as usize;
if data.len() < expected_len {
return Err(StegoError::FrameCorrupted);
}
let mut salt = [0u8; SALT_LEN];
salt.copy_from_slice(&data[2..2 + SALT_LEN]);
let mut nonce = [0u8; NONCE_LEN];
nonce.copy_from_slice(&data[2 + SALT_LEN..2 + SALT_LEN + NONCE_LEN]);
let ciphertext = data[2 + SALT_LEN + NONCE_LEN..expected_len].to_vec();
Ok(ParsedShadowFrame { plaintext_len, salt, nonce, ciphertext })
}
pub fn build_shadow_frame_wide(
plaintext_len: usize,
salt: &[u8; SALT_LEN],
nonce: &[u8; NONCE_LEN],
ciphertext: &[u8],
) -> Vec<u8> {
assert!(
plaintext_len <= u32::MAX as usize,
"shadow frame plaintext exceeds u32::MAX",
);
let mut fr = Vec::with_capacity(SHADOW_FRAME_OVERHEAD_WIDE + plaintext_len);
fr.extend_from_slice(&(plaintext_len as u32).to_be_bytes());
fr.extend_from_slice(salt);
fr.extend_from_slice(nonce);
fr.extend_from_slice(ciphertext);
fr
}
pub struct ParsedShadowFrameWide {
pub plaintext_len: u32,
pub salt: [u8; SALT_LEN],
pub nonce: [u8; NONCE_LEN],
pub ciphertext: Vec<u8>,
}
pub fn parse_shadow_frame_wide(data: &[u8]) -> Result<ParsedShadowFrameWide, StegoError> {
if data.len() < SHADOW_FRAME_OVERHEAD_WIDE {
return Err(StegoError::FrameCorrupted);
}
let plaintext_len =
u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let expected_len =
SHADOW_FRAME_OVERHEAD_WIDE + plaintext_len as usize;
if data.len() < expected_len {
return Err(StegoError::FrameCorrupted);
}
let mut salt = [0u8; SALT_LEN];
salt.copy_from_slice(&data[4..4 + SALT_LEN]);
let mut nonce = [0u8; NONCE_LEN];
nonce.copy_from_slice(&data[4 + SALT_LEN..4 + SALT_LEN + NONCE_LEN]);
let ciphertext =
data[4 + SALT_LEN + NONCE_LEN..expected_len].to_vec();
Ok(ParsedShadowFrameWide { plaintext_len, salt, nonce, ciphertext })
}
pub fn compute_max_shadow_fdl(max_rs_bytes: usize, parity_len: usize) -> usize {
let k = 255usize.saturating_sub(parity_len);
if k == 0 || max_rs_bytes == 0 {
return 0;
}
let full_blocks = max_rs_bytes / 255;
let remainder = max_rs_bytes % 255;
let mut max_data = full_blocks * k;
if remainder > parity_len {
max_data += remainder - parity_len;
}
max_data
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_roundtrip() {
let salt = [1u8; SALT_LEN];
let nonce = [2u8; NONCE_LEN];
let ciphertext = vec![0xAAu8; 20];
let fr = build_shadow_frame(4, &salt, &nonce, &ciphertext);
let parsed = parse_shadow_frame(&fr).unwrap();
assert_eq!(parsed.plaintext_len, 4);
assert_eq!(parsed.salt, salt);
assert_eq!(parsed.nonce, nonce);
assert_eq!(parsed.ciphertext, ciphertext);
}
#[test]
fn frame_wide_roundtrip() {
let salt = [3u8; SALT_LEN];
let nonce = [4u8; NONCE_LEN];
let ciphertext = vec![0xBBu8; 20];
let fr = build_shadow_frame_wide(4, &salt, &nonce, &ciphertext);
assert_eq!(fr.len(), SHADOW_FRAME_OVERHEAD_WIDE + ciphertext.len() - 16);
let parsed = parse_shadow_frame_wide(&fr).unwrap();
assert_eq!(parsed.plaintext_len, 4);
assert_eq!(parsed.salt, salt);
assert_eq!(parsed.nonce, nonce);
assert_eq!(parsed.ciphertext, ciphertext);
}
#[test]
fn wide_overhead_two_bytes_more_than_standard() {
assert_eq!(SHADOW_FRAME_OVERHEAD, 46);
assert_eq!(SHADOW_FRAME_OVERHEAD_WIDE, 48);
assert_eq!(SHADOW_FRAME_OVERHEAD_WIDE - SHADOW_FRAME_OVERHEAD, 2);
}
#[test]
fn wide_parser_rejects_standard_frame_layout() {
let salt = [5u8; SALT_LEN];
let nonce = [6u8; NONCE_LEN];
let ciphertext = vec![0xCCu8; 100];
let fr_standard = build_shadow_frame(84, &salt, &nonce, &ciphertext);
let parsed = parse_shadow_frame_wide(&fr_standard);
if let Ok(p) = parsed {
assert_ne!(p.salt, salt);
}
}
#[test]
fn fdl_capacity_arithmetic() {
assert_eq!(compute_max_shadow_fdl(0, 4), 0);
assert_eq!(compute_max_shadow_fdl(255, 4), 251);
assert_eq!(compute_max_shadow_fdl(510, 4), 502);
assert_eq!(compute_max_shadow_fdl(258, 4), 251);
}
}