Skip to content

Commit 9c559fd

Browse files
committed
feat: fundation architecture change with - TempInputForInference is the singular buffer — all PN generation, patch load/store, and spread spectrum embed/extract go through it. No secondary allocations.
- WatermarkEngine trait — RasterEngine implements it; VectorEngine and VideoEngine will implement the same trait in their respective phases.
1 parent d819b80 commit 9c559fd

14 files changed

Lines changed: 770 additions & 497 deletions

File tree

File renamed without changes.

src/common/engine.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/// Result of a successful watermark embedding.
2+
#[derive(Debug)]
3+
pub struct EmbedResult {
4+
/// Human-readable status message.
5+
pub message: String,
6+
}
7+
8+
/// Result of a watermark verification/extraction attempt.
9+
#[derive(Debug)]
10+
pub struct ExtractResult {
11+
/// Whether a valid watermark was detected.
12+
pub detected: bool,
13+
/// Detection confidence (0.0 to 1.0).
14+
pub confidence: f64,
15+
/// Extracted message, if decoding succeeded.
16+
pub message: Option<String>,
17+
}
18+
19+
/// Uniform interface for all watermark engines (raster, vector, video).
20+
///
21+
/// Each engine implements format-specific feature detection and embedding
22+
/// while sharing the common layer (ECC, scrambling, password hashing).
23+
pub trait WatermarkEngine {
24+
/// Embed a watermark message into a file.
25+
fn embed(
26+
&self,
27+
input_path: &str,
28+
message: &str,
29+
password: &str,
30+
intensity: u8,
31+
output_path: &str,
32+
) -> Result<EmbedResult, String>;
33+
34+
/// Verify and extract a watermark from a file.
35+
fn verify(&self, input_path: &str, password: &str) -> Result<ExtractResult, String>;
36+
}

src/common/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
pub mod ecc;
2+
pub mod engine;
3+
pub mod password;
4+
pub mod scramble;
5+
pub mod temp_input_for_inference;
6+
7+
pub use engine::{EmbedResult, ExtractResult, WatermarkEngine};
8+
pub use password::password_to_seed;
9+
pub use temp_input_for_inference::TempInputForInference;

src/common/password.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use sha2::{Digest, Sha256};
2+
3+
/// Hash a password string into a 32-byte seed for the ChaCha20 PRNG.
4+
pub fn password_to_seed(password: &str) -> [u8; 32] {
5+
let mut hasher = Sha256::new();
6+
hasher.update(password.as_bytes());
7+
let result = hasher.finalize();
8+
let mut seed = [0u8; 32];
9+
seed.copy_from_slice(&result);
10+
seed
11+
}
12+
13+
#[cfg(test)]
14+
mod tests {
15+
use super::*;
16+
17+
#[test]
18+
fn test_deterministic() {
19+
let s1 = password_to_seed("d1ng0");
20+
let s2 = password_to_seed("d1ng0");
21+
assert_eq!(s1, s2);
22+
}
23+
24+
#[test]
25+
fn test_different_passwords() {
26+
let s1 = password_to_seed("d1ng0");
27+
let s2 = password_to_seed("wrong");
28+
assert_ne!(s1, s2);
29+
}
30+
}
File renamed without changes.
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
//! Singular managed buffer for all algorithmic inference workloads.
2+
//!
3+
//! All intermediate matrices, tensor data, and PN sequences during feature detection,
4+
//! patch normalization, and transform operations (DWT, DFT, SVD) must be routed
5+
//! exclusively through this buffer. No secondary temporary allocations or unmanaged
6+
//! in-memory buffers may be instantiated for inference workloads.
7+
//!
8+
//! Thread safety: each thread gets its own `TempInputForInference` instance.
9+
//! The struct is `Send` but not `Sync` — pass by `&mut` reference only.
10+
11+
use rand::Rng;
12+
use rand::SeedableRng;
13+
use rand_chacha::ChaCha20Rng;
14+
use sha2::{Digest, Sha256};
15+
16+
/// Block size used across all engines for spread spectrum embedding.
17+
pub const BLOCK_SIZE: usize = 16;
18+
19+
/// Number of coefficients per block (BLOCK_SIZE × BLOCK_SIZE).
20+
pub const COEFFS_PER_BLOCK: usize = BLOCK_SIZE * BLOCK_SIZE;
21+
22+
/// The singular temporary buffer for all inference workloads.
23+
///
24+
/// Pre-allocates scratch space for patch pixels, DWT coefficients, and PN chip
25+
/// sequences. Buffers are reused across patches to avoid per-patch allocation.
26+
///
27+
/// # Usage
28+
///
29+
/// ```rust,no_run
30+
/// use infinishield::common::TempInputForInference;
31+
///
32+
/// let mut ctx = TempInputForInference::new(64); // 64×64 patches
33+
/// ctx.set_seed([0u8; 32]);
34+
/// let pn = ctx.generate_pn_chip(0);
35+
/// let data = ctx.patch_buffer();
36+
/// ```
37+
pub struct TempInputForInference {
38+
/// Scratch buffer for patch pixel data, sized for max patch dimensions.
39+
patch_buf: Vec<f64>,
40+
/// Maximum patch side length this context was allocated for.
41+
max_patch_size: usize,
42+
/// Pre-allocated PN chip sequence buffer (COEFFS_PER_BLOCK elements).
43+
pn_buf: Vec<f64>,
44+
/// Seed for PN sequence generation (derived from password).
45+
seed: [u8; 32],
46+
}
47+
48+
impl TempInputForInference {
49+
/// Create a new inference buffer pre-allocated for patches up to
50+
/// `max_patch_size × max_patch_size` pixels.
51+
pub fn new(max_patch_size: usize) -> Self {
52+
Self {
53+
patch_buf: vec![0.0; max_patch_size * max_patch_size],
54+
max_patch_size,
55+
pn_buf: vec![0.0; COEFFS_PER_BLOCK],
56+
seed: [0u8; 32],
57+
}
58+
}
59+
60+
/// Set the password-derived seed for PN sequence generation.
61+
pub fn set_seed(&mut self, seed: [u8; 32]) {
62+
self.seed = seed;
63+
}
64+
65+
/// Get the current seed.
66+
pub fn seed(&self) -> &[u8; 32] {
67+
&self.seed
68+
}
69+
70+
/// Generate a pseudo-random PN (pseudo-noise) chip sequence of ±1 values
71+
/// for a specific block index. Uses ChaCha20 seeded by a per-block hash
72+
/// of the master seed + block index.
73+
///
74+
/// The result is written into the internal `pn_buf` and a reference is returned.
75+
pub fn generate_pn_chip(&mut self, block_idx: usize) -> &[f64] {
76+
let mut hasher = Sha256::new();
77+
hasher.update(self.seed);
78+
hasher.update(block_idx.to_le_bytes());
79+
let block_seed_hash = hasher.finalize();
80+
let mut block_seed = [0u8; 32];
81+
block_seed.copy_from_slice(&block_seed_hash);
82+
83+
let mut rng = ChaCha20Rng::from_seed(block_seed);
84+
for v in self.pn_buf.iter_mut() {
85+
*v = if rng.gen_bool(0.5) { 1.0 } else { -1.0 };
86+
}
87+
&self.pn_buf
88+
}
89+
90+
/// Load a rectangular region from a 2D coefficient array into the patch buffer.
91+
///
92+
/// Copies `rows × cols` values starting at `(start_row, start_col)` from `source`
93+
/// into the internal patch buffer in row-major order.
94+
pub fn load_patch(
95+
&mut self,
96+
source: &[Vec<f64>],
97+
start_row: usize,
98+
start_col: usize,
99+
rows: usize,
100+
cols: usize,
101+
) {
102+
assert!(
103+
rows * cols <= self.patch_buf.len(),
104+
"Patch {}×{} exceeds buffer capacity (max {}×{})",
105+
rows,
106+
cols,
107+
self.max_patch_size,
108+
self.max_patch_size
109+
);
110+
for r in 0..rows {
111+
for c in 0..cols {
112+
self.patch_buf[r * cols + c] = source[start_row + r][start_col + c];
113+
}
114+
}
115+
}
116+
117+
/// Write the patch buffer contents back to a 2D coefficient array.
118+
pub fn store_patch(
119+
&self,
120+
dest: &mut [Vec<f64>],
121+
start_row: usize,
122+
start_col: usize,
123+
rows: usize,
124+
cols: usize,
125+
) {
126+
for r in 0..rows {
127+
for c in 0..cols {
128+
dest[start_row + r][start_col + c] = self.patch_buf[r * cols + c];
129+
}
130+
}
131+
}
132+
133+
/// Read-only access to the patch buffer.
134+
pub fn patch_buffer(&self) -> &[f64] {
135+
&self.patch_buf
136+
}
137+
138+
/// Mutable access to the patch buffer for in-place modification.
139+
pub fn patch_buffer_mut(&mut self) -> &mut [f64] {
140+
&mut self.patch_buf
141+
}
142+
143+
/// Get the PN buffer (read-only, from last `generate_pn_chip` call).
144+
pub fn pn_buffer(&self) -> &[f64] {
145+
&self.pn_buf
146+
}
147+
148+
/// Embed a single bit into BLOCK_SIZE×BLOCK_SIZE coefficients in the patch buffer
149+
/// using additive spread spectrum.
150+
///
151+
/// `offset` is the starting index in `patch_buf` for this block.
152+
/// The PN chip must have been generated prior to calling this.
153+
pub fn embed_spread_spectrum(&mut self, offset: usize, bit: bool, alpha: f64) {
154+
let signal = if bit { 1.0 } else { -1.0 };
155+
for i in 0..COEFFS_PER_BLOCK {
156+
self.patch_buf[offset + i] += alpha * self.pn_buf[i] * signal;
157+
}
158+
}
159+
160+
/// Extract a single bit from BLOCK_SIZE×BLOCK_SIZE coefficients in the patch buffer
161+
/// using spread spectrum correlation.
162+
///
163+
/// Returns `(bit, confidence)` where confidence is normalized correlation strength.
164+
pub fn extract_spread_spectrum(&self, offset: usize) -> (bool, f64) {
165+
let mut correlation = 0.0;
166+
for i in 0..COEFFS_PER_BLOCK {
167+
correlation += self.patch_buf[offset + i] * self.pn_buf[i];
168+
}
169+
let bit = correlation >= 0.0;
170+
let confidence = (correlation.abs() / COEFFS_PER_BLOCK as f64).min(1.0);
171+
(bit, confidence)
172+
}
173+
}
174+
175+
// TempInputForInference is Send (can move between threads) but NOT Sync
176+
// (cannot be shared between threads). Each thread must own its own instance.
177+
// This is the default for structs with no interior mutability issues,
178+
// but we document it explicitly as part of the memory protocol.
179+
unsafe impl Send for TempInputForInference {}
180+
181+
#[cfg(test)]
182+
mod tests {
183+
use super::*;
184+
185+
#[test]
186+
fn test_pn_chip_deterministic() {
187+
let seed = [42u8; 32];
188+
let mut ctx1 = TempInputForInference::new(16);
189+
let mut ctx2 = TempInputForInference::new(16);
190+
ctx1.set_seed(seed);
191+
ctx2.set_seed(seed);
192+
193+
let pn1 = ctx1.generate_pn_chip(0).to_vec();
194+
let pn2 = ctx2.generate_pn_chip(0).to_vec();
195+
assert_eq!(pn1, pn2);
196+
}
197+
198+
#[test]
199+
fn test_pn_chip_different_blocks() {
200+
let seed = [42u8; 32];
201+
let mut ctx = TempInputForInference::new(16);
202+
ctx.set_seed(seed);
203+
204+
let pn0 = ctx.generate_pn_chip(0).to_vec();
205+
let pn1 = ctx.generate_pn_chip(1).to_vec();
206+
assert_ne!(pn0, pn1);
207+
}
208+
209+
#[test]
210+
fn test_pn_chip_values_are_pm1() {
211+
let mut ctx = TempInputForInference::new(16);
212+
ctx.set_seed([7u8; 32]);
213+
let pn = ctx.generate_pn_chip(0);
214+
for &v in pn {
215+
assert!(v == 1.0 || v == -1.0, "PN value must be ±1, got {}", v);
216+
}
217+
}
218+
219+
#[test]
220+
fn test_spread_spectrum_round_trip() {
221+
let mut ctx = TempInputForInference::new(16);
222+
ctx.set_seed([99u8; 32]);
223+
224+
// Fill patch buffer with some baseline values
225+
for (i, v) in ctx.patch_buffer_mut().iter_mut().enumerate() {
226+
*v = (i as f64) * 0.5 - 64.0;
227+
}
228+
229+
for bit in [true, false] {
230+
// Reset buffer
231+
for (i, v) in ctx.patch_buffer_mut().iter_mut().enumerate() {
232+
*v = (i as f64) * 0.5 - 64.0;
233+
}
234+
ctx.generate_pn_chip(0);
235+
ctx.embed_spread_spectrum(0, bit, 2.0);
236+
237+
// Re-generate PN for extraction (same chip)
238+
ctx.generate_pn_chip(0);
239+
let (extracted, confidence) = ctx.extract_spread_spectrum(0);
240+
assert_eq!(extracted, bit, "Failed for bit={}", bit);
241+
assert!(
242+
confidence > 0.1,
243+
"Low confidence {} for bit={}",
244+
confidence,
245+
bit
246+
);
247+
}
248+
}
249+
250+
#[test]
251+
fn test_load_store_patch() {
252+
let mut ctx = TempInputForInference::new(16);
253+
254+
let source = vec![
255+
vec![1.0, 2.0, 3.0, 4.0],
256+
vec![5.0, 6.0, 7.0, 8.0],
257+
vec![9.0, 10.0, 11.0, 12.0],
258+
];
259+
260+
ctx.load_patch(&source, 0, 1, 2, 2);
261+
assert_eq!(&ctx.patch_buffer()[..4], &[2.0, 3.0, 6.0, 7.0]);
262+
263+
let mut dest = vec![vec![0.0; 4]; 3];
264+
ctx.store_patch(&mut dest, 1, 2, 2, 2);
265+
assert_eq!(dest[1][2], 2.0);
266+
assert_eq!(dest[1][3], 3.0);
267+
assert_eq!(dest[2][2], 6.0);
268+
assert_eq!(dest[2][3], 7.0);
269+
}
270+
}

src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pub mod dwt;
2-
pub mod ecc;
3-
pub mod scramble;
4-
pub mod watermark;
1+
pub mod common;
2+
pub mod raster;
3+
pub mod vector;
4+
pub mod video;

0 commit comments

Comments
 (0)