use crate::codec::h264::cabac::bin_decoder::{
walk_annex_b_for_cover_with_options, WalkOptions,
};
use crate::stego::error::StegoError;
use super::encode_pixels::h264_stego_encode_yuv_string_4domain_multigop;
use super::inject::DomainCover;
pub fn pass3_emit_provisional(
yuv: &[u8],
width: u32,
height: u32,
n_frames: usize,
gop_size: usize,
primary_message: &str,
primary_passphrase: &str,
) -> Result<(Vec<u8>, DomainCover), StegoError> {
let bytes = h264_stego_encode_yuv_string_4domain_multigop(
yuv, width, height, n_frames, gop_size,
primary_message, primary_passphrase,
)?;
let walk_opts = WalkOptions { record_mvd: true };
let walk = walk_annex_b_for_cover_with_options(&bytes, walk_opts)
.map_err(|e| StegoError::InvalidVideo(format!("provisional walk: {e}")))?;
Ok((bytes, walk.cover))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codec::h264::stego::decode_pixels::h264_stego_decode_yuv_string_4domain;
use crate::codec::h264::stego::encode_pixels::h264_stego_encode_yuv_string_with_n_shadows;
fn make_yuv(width: u32, height: u32, n_frames: usize) -> Vec<u8> {
let frame_size = (width * height * 3 / 2) as usize;
let mut out = Vec::with_capacity(frame_size * n_frames);
let mut base = Vec::with_capacity(frame_size);
let mut s: u32 = 0x1234_5678;
for _ in 0..frame_size {
s = s.wrapping_mul(1103515245).wrapping_add(12345);
let v = 64i32 + ((s >> 24) & 0x7F) as i32;
base.push(v.clamp(0, 255) as u8);
}
for fi in 0..n_frames {
let mut p: u32 = 0x4242_DEAD ^ (fi as u32 * 17);
for &b in &base {
p = p.wrapping_mul(1103515245).wrapping_add(12345);
let delta = ((p >> 28) & 0x07) as i32 - 4;
let v = b as i32 + delta;
out.push(v.clamp(0, 255) as u8);
}
}
out
}
#[test]
fn provisional_emit_round_trips_primary() {
let yuv = make_yuv(32, 32, 1);
let (bytes, cover) = pass3_emit_provisional(
&yuv, 32, 32, 1, 1,
"primary message", "primary-pass",
).expect("provisional emit");
assert!(!bytes.is_empty(), "provisional bytes must be non-empty");
let total_cover_bits = cover.coeff_sign_bypass.len()
+ cover.coeff_suffix_lsb.len()
+ cover.mvd_sign_bypass.len()
+ cover.mvd_suffix_lsb.len();
assert!(
total_cover_bits > 0,
"provisional walk must yield non-empty cover"
);
let decoded = h264_stego_decode_yuv_string_4domain(&bytes, "primary-pass")
.expect("decode provisional bytes");
assert_eq!(decoded, "primary message");
}
#[test]
fn provisional_emit_decodes_same_as_no_shadow_full_emit() {
let yuv = make_yuv(32, 32, 1);
let (provisional_bytes, _cover) = pass3_emit_provisional(
&yuv, 32, 32, 1, 1,
"match test", "primary-pass",
).expect("provisional emit");
let full_bytes = h264_stego_encode_yuv_string_with_n_shadows(
&yuv, 32, 32, 1, 1,
"match test", "primary-pass",
&[],
).expect("no-shadow full encode");
let prov_decoded =
h264_stego_decode_yuv_string_4domain(&provisional_bytes, "primary-pass")
.expect("decode provisional");
let full_decoded =
h264_stego_decode_yuv_string_4domain(&full_bytes, "primary-pass")
.expect("decode no-shadow full");
assert_eq!(prov_decoded, "match test");
assert_eq!(full_decoded, "match test");
assert_eq!(prov_decoded, full_decoded);
}
#[test]
fn provisional_emit_multi_gop_round_trip() {
let yuv = make_yuv(32, 32, 4);
let (bytes, cover) = pass3_emit_provisional(
&yuv, 32, 32, 4, 2,
"multi-gop msg", "pass-mg",
).expect("provisional multi-gop emit");
assert!(!bytes.is_empty());
assert!(
cover.coeff_sign_bypass.len() + cover.mvd_sign_bypass.len() > 0,
"multi-GOP provisional walk must yield bypass-bin positions"
);
let decoded = h264_stego_decode_yuv_string_4domain(&bytes, "pass-mg")
.expect("decode multi-gop provisional bytes");
assert_eq!(decoded, "multi-gop msg");
}
}