From 982603e64f3a312467ed4b68aba89c3deff0190c Mon Sep 17 00:00:00 2001 From: randogoth Date: Wed, 1 Jan 2025 20:43:00 +0200 Subject: [PATCH 1/3] recovered main.rs --- Cargo.toml | 4 +- src/bin/main.rs | 146 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/bin/main.rs diff --git a/Cargo.toml b/Cargo.toml index 8f24fec..bf62e94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,8 @@ edition = "2021" [lib] name = "ocelli" path = "src/lib.rs" -crate-type = ["cdylib"] [dependencies] +v4l = "0.14" +chrono = "0.4" +image = "0.25.5" diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..a71386c --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,146 @@ +use ocelli::Ocelli; +use v4l::prelude::*; +use v4l::video::Capture; +use v4l::buffer::Type; +use v4l::format::FourCC; +use v4l::io::traits::CaptureStream; +use image::ImageReader; +use std::fs::File; +use std::io::{stdin, Write}; +use chrono::Local; +use std::time::Instant; + +fn frame_to_grayscale(data: &[u8]) -> Vec { + let img = ImageReader::new(std::io::Cursor::new(data)) + .with_guessed_format() + .expect("Failed to guess format") + .decode() + .expect("Failed to decode image"); + let gray = img.into_luma8(); // Convert to grayscale + gray.into_raw() // Return raw pixel data as Vec +} + +fn main() -> Result<(), Box> { + let args: Vec = std::env::args().collect(); + if args.len() < 3 { + eprintln!("Usage: {} ", args[0]); + std::process::exit(1); + } + + // Check if the quick flag is set + let quick = args.contains(&String::from("-q")); + + let camera_index: usize = args[1].parse().expect("Failed to parse camera index as a number"); + let length: usize = args[2].parse().expect("Failed to parse entropy length as a number"); + + let dev = Device::new(camera_index).expect("Failed to open camera"); + + // Set the desired format and resolution + let mut format = dev.format().expect("Failed to get camera format"); + format.fourcc = FourCC::new(b"MJPG"); // Use MJPG for higher resolutions + format.width = 1920; + format.height = 1080; + + if let Err(_) = dev.set_format(&format) { + println!("Failed to set resolution to 1920x1080. Falling back to 1280x720."); + format.width = 1280; + format.height = 720; + dev.set_format(&format) + .expect("Failed to set resolution to 1280x720"); + } + + println!( + "Using resolution: {}x{} (FourCC: {})", + format.width, format.height, format.fourcc + ); + + let mut stream = MmapStream::with_buffers(&dev, Type::VideoCapture, 4) + .expect("Failed to create stream"); + + let ocelli = Ocelli; + let mut total_entropy = Vec::new(); + let start_time = Instant::now(); + let shannon_threshold = 7.9; + let mut frame_count = 0; + + while total_entropy.len() < length { + // Capture first frame + let (data1, _) = stream.next().expect("Failed to capture frame"); + + // Skip the first 30 frames + if frame_count <= 30 { + frame_count += 1; + } else { + + let grayscale_data1 = frame_to_grayscale(&data1); + + let mut entropy: Vec = [0].to_vec(); + + if quick { + // Quicker capture using Pick and Flip + entropy = ocelli.whiten(&ocelli.pick_and_flip(&grayscale_data1, frame_count as usize)); + } else { + if ocelli.is_covered(&grayscale_data1, 50) { + // Capture second frame + let (data2, _) = stream.next().expect("Failed to capture second frame"); + let grayscale_data2 = frame_to_grayscale(&data2); + + // Generate entropy using chop_and_tack + entropy = ocelli.chop_and_tack(&grayscale_data1, &grayscale_data2, format.width as usize, 30); + } else { + println!("Camera is not covered. Please cover the camera."); + frame_count = 0; + } + } + + let shannon_entropy = ocelli.shannon(&entropy); + + if shannon_entropy >= shannon_threshold { + total_entropy.extend(entropy); + println!( + "Collected {} of {} bytes of entropy (Shannon entropy: {:.3})", + total_entropy.len(), + length, + shannon_entropy + ); + } else { + println!("Rejected entropy for frame {} (Shannon entropy: {:.3})", frame_count, shannon_entropy); + } + + } + } + + // Trim total_entropy to the exact length + total_entropy.truncate(length); + + // Print elapsed time + let elapsed_time = start_time.elapsed(); + println!( + "Process completed in {:.3} seconds.", + elapsed_time.as_secs_f64() + ); + + // Final Shannon entropy test + let final_shannon_entropy = ocelli.shannon(&total_entropy); + println!("Final Shannon entropy: {:.3}", final_shannon_entropy); + + // Ask if the result should be saved + println!("Save result to a file or print as hex string? (file/print):"); + let mut input = String::new(); + stdin().read_line(&mut input).expect("Failed to read input"); + + if input.trim().eq_ignore_ascii_case("file") { + let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string(); + let filename = format!("entropy_{}.bin", timestamp); + + let mut file = File::create(&filename).expect("Failed to create file"); + file.write_all(&total_entropy).expect("Failed to write data to file"); + + println!("Entropy saved to file: {}", filename); + } else if input.trim().eq_ignore_ascii_case("print") { + let entropy_hex: String = total_entropy.iter().map(|b| format!("{:02x}", b)).collect(); + println!("Generated entropy (hex): {}", entropy_hex); + } + + Ok(()) +} From 92fd5531e94bb9068542a73a32052b9bbbde7e0b Mon Sep 17 00:00:00 2001 From: randogoth Date: Sun, 5 Jan 2025 15:15:37 +0200 Subject: [PATCH 2/3] updated method and sequential read --- src/bin/main.rs | 105 ++++++++++++++++++++++-------------------- src/lib.rs | 120 ++++++++++++++++++++++++++++-------------------- 2 files changed, 124 insertions(+), 101 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index a71386c..3668ad4 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -6,7 +6,7 @@ use v4l::format::FourCC; use v4l::io::traits::CaptureStream; use image::ImageReader; use std::fs::File; -use std::io::{stdin, Write}; +use std::io::{stdin, Read, Write}; use chrono::Local; use std::time::Instant; @@ -27,15 +27,14 @@ fn main() -> Result<(), Box> { std::process::exit(1); } - // Check if the quick flag is set let quick = args.contains(&String::from("-q")); + let pp = args.contains(&String::from("-p")); let camera_index: usize = args[1].parse().expect("Failed to parse camera index as a number"); let length: usize = args[2].parse().expect("Failed to parse entropy length as a number"); let dev = Device::new(camera_index).expect("Failed to open camera"); - // Set the desired format and resolution let mut format = dev.format().expect("Failed to get camera format"); format.fourcc = FourCC::new(b"MJPG"); // Use MJPG for higher resolutions format.width = 1920; @@ -45,8 +44,7 @@ fn main() -> Result<(), Box> { println!("Failed to set resolution to 1920x1080. Falling back to 1280x720."); format.width = 1280; format.height = 720; - dev.set_format(&format) - .expect("Failed to set resolution to 1280x720"); + dev.set_format(&format).expect("Failed to set resolution to 1280x720"); } println!( @@ -62,69 +60,76 @@ fn main() -> Result<(), Box> { let start_time = Instant::now(); let shannon_threshold = 7.9; let mut frame_count = 0; + let mut last_frame = None; while total_entropy.len() < length { - // Capture first frame - let (data1, _) = stream.next().expect("Failed to capture frame"); + let (data, _) = stream.next().expect("Failed to capture frame"); + frame_count += 1; - // Skip the first 30 frames if frame_count <= 30 { - frame_count += 1; - } else { - - let grayscale_data1 = frame_to_grayscale(&data1); - - let mut entropy: Vec = [0].to_vec(); - - if quick { - // Quicker capture using Pick and Flip - entropy = ocelli.whiten(&ocelli.pick_and_flip(&grayscale_data1, frame_count as usize)); - } else { - if ocelli.is_covered(&grayscale_data1, 50) { - // Capture second frame - let (data2, _) = stream.next().expect("Failed to capture second frame"); - let grayscale_data2 = frame_to_grayscale(&data2); - - // Generate entropy using chop_and_tack - entropy = ocelli.chop_and_tack(&grayscale_data1, &grayscale_data2, format.width as usize, 30); - } else { - println!("Camera is not covered. Please cover the camera."); - frame_count = 0; - } - } - - let shannon_entropy = ocelli.shannon(&entropy); - - if shannon_entropy >= shannon_threshold { - total_entropy.extend(entropy); - println!( - "Collected {} of {} bytes of entropy (Shannon entropy: {:.3})", - total_entropy.len(), - length, - shannon_entropy - ); - } else { - println!("Rejected entropy for frame {} (Shannon entropy: {:.3})", frame_count, shannon_entropy); - } - + continue; // Skip the first 30 frames } + + let grayscale_data = frame_to_grayscale(&data); + + let entropy: Vec = if pp { + let mut low = 10; + let mut high = 245; + + if ocelli.is_covered(&grayscale_data, 50) { + low = 3; + high = 252; + } + + if let Some(last) = last_frame { + ocelli.whiten(&ocelli.tune_and_prune(&grayscale_data, &last, low, high)) + } else { + Vec::new() + } + } else if quick { + ocelli.whiten(&ocelli.pick_and_flip(&grayscale_data, frame_count as usize)) + } else { + if ocelli.is_covered(&grayscale_data, 50) { + if let Some(last) = last_frame { + ocelli.chop_and_tack(&last, &grayscale_data, format.width as usize, 30) + } else { + Vec::new() + } + } else { + println!("Camera is not covered. Please cover the camera."); + frame_count = 0; + continue; + } + }; + + let shannon_entropy = ocelli.shannon(&entropy); + + if shannon_entropy >= shannon_threshold { + total_entropy.extend(entropy); + println!( + "Collected {} of {} bytes of entropy (Shannon entropy: {:.3})", + total_entropy.len(), + length, + shannon_entropy + ); + } else { + println!("Rejected entropy for frame {} (Shannon entropy: {:.3})", frame_count, shannon_entropy); + } + + last_frame = Some(grayscale_data); } - // Trim total_entropy to the exact length total_entropy.truncate(length); - // Print elapsed time let elapsed_time = start_time.elapsed(); println!( "Process completed in {:.3} seconds.", elapsed_time.as_secs_f64() ); - // Final Shannon entropy test let final_shannon_entropy = ocelli.shannon(&total_entropy); println!("Final Shannon entropy: {:.3}", final_shannon_entropy); - // Ask if the result should be saved println!("Save result to a file or print as hex string? (file/print):"); let mut input = String::new(); stdin().read_line(&mut input).expect("Failed to read input"); diff --git a/src/lib.rs b/src/lib.rs index 4ce89cd..e328e9a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,19 +5,25 @@ pub struct Ocelli; impl Ocelli { + fn bits_to_bytes(&self, bits: &[u8]) -> Vec { + bits.chunks(8).filter_map(|chunk| { + if chunk.len() == 8 { + Some(chunk.iter().fold(0, |byte, &bit| (byte << 1) | bit)) + } else { + None + } + }).collect() + } + pub fn chop_and_tack(&self, current: &Vec, previous: &Vec, width: usize, minimum_distance: usize) -> Vec { // Extracts entropy from two frames by comparing pixel values in a specific grid pattern. // The resulting entropy is constructed by appending 1s or 0s based on pixel differences. // Algorithm ported from NoiseBasedCamRng by Andika Wasisto https://github.com/awasisto/camrng let mut entropy = Vec::new(); - let mut current_byte = 0u8; - let mut bit_count = 0; - - // Calculate the height of the frame let height = current.len() / width; - // Skip 100 pixels at the top and bottom of the frame + // Define bounds for the grid let start_row = 100; let end_row = height - 100; @@ -25,39 +31,23 @@ impl Ocelli { panic!("Resolution is too small to apply the grid selection with the given offset."); } + // Process the grid within the defined bounds for row in start_row..end_row { - // Skip 100 pixels at the left and right of the frame let row_start = row * width + 100; let row_end = (row + 1) * width - 100; - // Select pixels in the row based on the step size - for pixel_index in (row_start..row_end).step_by(minimum_distance) { - if pixel_index >= current.len() || pixel_index >= previous.len() { - continue; - } - - let c = current[pixel_index]; - let p = previous[pixel_index]; - - if c > p { - current_byte = (current_byte << 1) | 1; // Append '1' - } else if c < p { - current_byte = current_byte << 1; // Append '0' - } else { - continue; // Skip if equal - } - - bit_count += 1; - - if bit_count == 8 { - entropy.push(current_byte); - current_byte = 0; - bit_count = 0; - } - } + entropy.extend((row_start..row_end).step_by(minimum_distance) + .filter(|&pixel_index| pixel_index < current.len() && pixel_index < previous.len()) + .map(|pixel_index| { + let c = current[pixel_index]; + let p = previous[pixel_index]; + (c > p) as u8 - (c < p) as u8 // 1 for c > p, 0 for c < p, skips if equal + }) + .filter(|&bit| bit <= 1)); } - entropy + self.bits_to_bytes(&entropy) + } pub fn pick_and_flip(&self, data: &[u8], current_frame_index: usize) -> Vec { @@ -67,29 +57,32 @@ impl Ocelli { // digital camera image noise for varying lighting conditions," doi: 10.1109/SECON.2015.7132901. let mut entropy = Vec::new(); - let mut current_byte = 0u8; - let mut bit_count = 0; - for &pixel_brightness in data { - if (2..=253).contains(&pixel_brightness) { - let mut lsb = pixel_brightness & 1; - - if current_frame_index % 2 == 0 { - lsb ^= 1; // Flip the bit - } - - current_byte = (current_byte << 1) | lsb; - bit_count += 1; - - if bit_count == 8 { - entropy.push(current_byte); - current_byte = 0; - bit_count = 0; + for &pixel in data { + if (2..=253).contains(&pixel) { // filter bias + let mut lsb = pixel & 1; + if current_frame_index % 2 == 0 { // flip bits + lsb ^= 1; } + entropy.push(lsb); } } - entropy + self.bits_to_bytes(&entropy) + } + + pub fn tune_and_prune(&self, current: &Vec, previous: &Vec, low: u8, high: u8) -> Vec { + // Extracts the least significant bit (LSB) of each pixel brightness while filtering out bias + // It's a combination of methods from the other two entropy extraction algorithms + let mut entropy = Vec::new(); + + for (&c, &p) in current.iter().zip(previous.iter()) { + if c != p && (low..=high).contains(&c) { + entropy.push(c & 1); + } + } + + self.bits_to_bytes(&entropy) } pub fn shannon(&self, data: &Vec) -> f64 { @@ -201,6 +194,31 @@ pub extern "C" fn pick_and_flip( } } +#[no_mangle] +pub extern "C" fn tune_and_prune( + current_ptr: *const u8, + current_len: usize, + previous_ptr: *const u8, + previous_len: usize, + low: u8, + high: u8, + result_ptr: *mut u8, + result_len: &mut usize, +) { + let current = unsafe { slice::from_raw_parts(current_ptr, current_len) }; + let previous = unsafe { slice::from_raw_parts(previous_ptr, previous_len) }; + + let ocelli = Ocelli; + let result = ocelli.tune_and_prune(¤t.to_vec(), &previous.to_vec(), low, high); + + unsafe { + let result_slice = slice::from_raw_parts_mut(result_ptr, result.len()); + result_slice.copy_from_slice(&result); + *result_len = result.len(); + } +} + + #[no_mangle] pub extern "C" fn shannon(data_ptr: *const u8, data_len: usize) -> f64 { let data = unsafe { slice::from_raw_parts(data_ptr, data_len) }; @@ -234,4 +252,4 @@ pub extern "C" fn is_covered(grayscale_ptr: *const u8, grayscale_len: usize, thr let ocelli = Ocelli; ocelli.is_covered(grayscale, threshold) -} +} \ No newline at end of file From 8b277ff53bc81b59fdf41ec3aed29e37ab96a118 Mon Sep 17 00:00:00 2001 From: randogoth Date: Sun, 5 Jan 2025 15:37:42 +0200 Subject: [PATCH 3/3] sequential & bandpass --- src/bin/main.rs | 17 +---------------- src/lib.rs | 46 +++++----------------------------------------- 2 files changed, 6 insertions(+), 57 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 3668ad4..5efefcd 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -28,7 +28,6 @@ fn main() -> Result<(), Box> { } let quick = args.contains(&String::from("-q")); - let pp = args.contains(&String::from("-p")); let camera_index: usize = args[1].parse().expect("Failed to parse camera index as a number"); let length: usize = args[2].parse().expect("Failed to parse entropy length as a number"); @@ -72,21 +71,7 @@ fn main() -> Result<(), Box> { let grayscale_data = frame_to_grayscale(&data); - let entropy: Vec = if pp { - let mut low = 10; - let mut high = 245; - - if ocelli.is_covered(&grayscale_data, 50) { - low = 3; - high = 252; - } - - if let Some(last) = last_frame { - ocelli.whiten(&ocelli.tune_and_prune(&grayscale_data, &last, low, high)) - } else { - Vec::new() - } - } else if quick { + let entropy: Vec = if quick { ocelli.whiten(&ocelli.pick_and_flip(&grayscale_data, frame_count as usize)) } else { if ocelli.is_covered(&grayscale_data, 50) { diff --git a/src/lib.rs b/src/lib.rs index e328e9a..3fb6d22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ impl Ocelli { } - pub fn pick_and_flip(&self, data: &[u8], current_frame_index: usize) -> Vec { + pub fn pick_and_flip(&self, data: &[u8], low: u8, high: u8, current_frame_index: usize) -> Vec { // Extracts the least significant bit (LSB) of each pixel brightness, flipping it based on the frame index. // Generates entropy by combining these bits into bytes. // Algorithm is a simplified version of R. Li, "A True Random Number Generator algorithm from @@ -59,7 +59,7 @@ impl Ocelli { let mut entropy = Vec::new(); for &pixel in data { - if (2..=253).contains(&pixel) { // filter bias + if (low..=high).contains(&pixel) { // filter bias let mut lsb = pixel & 1; if current_frame_index % 2 == 0 { // flip bits lsb ^= 1; @@ -71,20 +71,6 @@ impl Ocelli { self.bits_to_bytes(&entropy) } - pub fn tune_and_prune(&self, current: &Vec, previous: &Vec, low: u8, high: u8) -> Vec { - // Extracts the least significant bit (LSB) of each pixel brightness while filtering out bias - // It's a combination of methods from the other two entropy extraction algorithms - let mut entropy = Vec::new(); - - for (&c, &p) in current.iter().zip(previous.iter()) { - if c != p && (low..=high).contains(&c) { - entropy.push(c & 1); - } - } - - self.bits_to_bytes(&entropy) - } - pub fn shannon(&self, data: &Vec) -> f64 { // Calculates the Shannon entropy of a given byte vector to measure its randomness. // Uses a frequency map to compute probabilities and their contributions to entropy. @@ -178,6 +164,8 @@ pub extern "C" fn chop_and_tack( pub extern "C" fn pick_and_flip( data_ptr: *const u8, data_len: usize, + low: u8, + high: u8, current_frame_index: usize, result_ptr: *mut u8, result_len: &mut usize, @@ -185,31 +173,7 @@ pub extern "C" fn pick_and_flip( let data = unsafe { slice::from_raw_parts(data_ptr, data_len) }; let ocelli = Ocelli; - let result = ocelli.pick_and_flip(data, current_frame_index); - - unsafe { - let result_slice = slice::from_raw_parts_mut(result_ptr, result.len()); - result_slice.copy_from_slice(&result); - *result_len = result.len(); - } -} - -#[no_mangle] -pub extern "C" fn tune_and_prune( - current_ptr: *const u8, - current_len: usize, - previous_ptr: *const u8, - previous_len: usize, - low: u8, - high: u8, - result_ptr: *mut u8, - result_len: &mut usize, -) { - let current = unsafe { slice::from_raw_parts(current_ptr, current_len) }; - let previous = unsafe { slice::from_raw_parts(previous_ptr, previous_len) }; - - let ocelli = Ocelli; - let result = ocelli.tune_and_prune(¤t.to_vec(), &previous.to_vec(), low, high); + let result = ocelli.pick_and_flip(data, low, high, current_frame_index); unsafe { let result_slice = slice::from_raw_parts_mut(result_ptr, result.len());