Compare commits

..

3 commits
0.1.0 ... app

Author SHA1 Message Date
randogoth
8b277ff53b sequential & bandpass 2025-01-05 15:37:42 +02:00
randogoth
92fd5531e9 updated method and sequential read 2025-01-05 15:15:37 +02:00
randogoth
982603e64f recovered main.rs 2025-01-01 20:43:00 +02:00
3 changed files with 174 additions and 54 deletions

View file

@ -6,6 +6,8 @@ edition = "2021"
[lib] [lib]
name = "ocelli" name = "ocelli"
path = "src/lib.rs" path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies] [dependencies]
v4l = "0.14"
chrono = "0.4"
image = "0.25.5"

136
src/bin/main.rs Normal file
View file

@ -0,0 +1,136 @@
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, Read, Write};
use chrono::Local;
use std::time::Instant;
fn frame_to_grayscale(data: &[u8]) -> Vec<u8> {
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<u8>
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect();
if args.len() < 3 {
eprintln!("Usage: {} <camera index> <entropy length in bytes>", args[0]);
std::process::exit(1);
}
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");
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;
let mut last_frame = None;
while total_entropy.len() < length {
let (data, _) = stream.next().expect("Failed to capture frame");
frame_count += 1;
if frame_count <= 30 {
continue; // Skip the first 30 frames
}
let grayscale_data = frame_to_grayscale(&data);
let entropy: Vec<u8> = 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);
}
total_entropy.truncate(length);
let elapsed_time = start_time.elapsed();
println!(
"Process completed in {:.3} seconds.",
elapsed_time.as_secs_f64()
);
let final_shannon_entropy = ocelli.shannon(&total_entropy);
println!("Final Shannon entropy: {:.3}", final_shannon_entropy);
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(())
}

View file

@ -5,19 +5,25 @@ pub struct Ocelli;
impl Ocelli { impl Ocelli {
fn bits_to_bytes(&self, bits: &[u8]) -> Vec<u8> {
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<u8>, previous: &Vec<u8>, width: usize, minimum_distance: usize) -> Vec<u8> { pub fn chop_and_tack(&self, current: &Vec<u8>, previous: &Vec<u8>, width: usize, minimum_distance: usize) -> Vec<u8> {
// Extracts entropy from two frames by comparing pixel values in a specific grid pattern. // 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. // 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 // Algorithm ported from NoiseBasedCamRng by Andika Wasisto https://github.com/awasisto/camrng
let mut entropy = Vec::new(); 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; 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 start_row = 100;
let end_row = height - 100; let end_row = height - 100;
@ -25,71 +31,44 @@ impl Ocelli {
panic!("Resolution is too small to apply the grid selection with the given offset."); 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 { 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_start = row * width + 100;
let row_end = (row + 1) * width - 100; let row_end = (row + 1) * width - 100;
// Select pixels in the row based on the step size entropy.extend((row_start..row_end).step_by(minimum_distance)
for pixel_index in (row_start..row_end).step_by(minimum_distance) { .filter(|&pixel_index| pixel_index < current.len() && pixel_index < previous.len())
if pixel_index >= current.len() || pixel_index >= previous.len() { .map(|pixel_index| {
continue;
}
let c = current[pixel_index]; let c = current[pixel_index];
let p = previous[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
if c > p { })
current_byte = (current_byte << 1) | 1; // Append '1' .filter(|&bit| bit <= 1));
} else if c < p {
current_byte = current_byte << 1; // Append '0'
} else {
continue; // Skip if equal
} }
bit_count += 1; self.bits_to_bytes(&entropy)
if bit_count == 8 {
entropy.push(current_byte);
current_byte = 0;
bit_count = 0;
}
}
} }
entropy pub fn pick_and_flip(&self, data: &[u8], low: u8, high: u8, current_frame_index: usize) -> Vec<u8> {
}
pub fn pick_and_flip(&self, data: &[u8], current_frame_index: usize) -> Vec<u8> {
// Extracts the least significant bit (LSB) of each pixel brightness, flipping it based on the frame index. // 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. // Generates entropy by combining these bits into bytes.
// Algorithm is a simplified version of R. Li, "A True Random Number Generator algorithm from // Algorithm is a simplified version of R. Li, "A True Random Number Generator algorithm from
// digital camera image noise for varying lighting conditions," doi: 10.1109/SECON.2015.7132901. // digital camera image noise for varying lighting conditions," doi: 10.1109/SECON.2015.7132901.
let mut entropy = Vec::new(); let mut entropy = Vec::new();
let mut current_byte = 0u8;
let mut bit_count = 0;
for &pixel_brightness in data { for &pixel in data {
if (2..=253).contains(&pixel_brightness) { if (low..=high).contains(&pixel) { // filter bias
let mut lsb = pixel_brightness & 1; let mut lsb = pixel & 1;
if current_frame_index % 2 == 0 { // flip bits
if current_frame_index % 2 == 0 { lsb ^= 1;
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;
} }
entropy.push(lsb);
} }
} }
entropy self.bits_to_bytes(&entropy)
} }
pub fn shannon(&self, data: &Vec<u8>) -> f64 { pub fn shannon(&self, data: &Vec<u8>) -> f64 {
@ -185,6 +164,8 @@ pub extern "C" fn chop_and_tack(
pub extern "C" fn pick_and_flip( pub extern "C" fn pick_and_flip(
data_ptr: *const u8, data_ptr: *const u8,
data_len: usize, data_len: usize,
low: u8,
high: u8,
current_frame_index: usize, current_frame_index: usize,
result_ptr: *mut u8, result_ptr: *mut u8,
result_len: &mut usize, result_len: &mut usize,
@ -192,7 +173,7 @@ pub extern "C" fn pick_and_flip(
let data = unsafe { slice::from_raw_parts(data_ptr, data_len) }; let data = unsafe { slice::from_raw_parts(data_ptr, data_len) };
let ocelli = Ocelli; let ocelli = Ocelli;
let result = ocelli.pick_and_flip(data, current_frame_index); let result = ocelli.pick_and_flip(data, low, high, current_frame_index);
unsafe { unsafe {
let result_slice = slice::from_raw_parts_mut(result_ptr, result.len()); let result_slice = slice::from_raw_parts_mut(result_ptr, result.len());
@ -201,6 +182,7 @@ pub extern "C" fn pick_and_flip(
} }
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn shannon(data_ptr: *const u8, data_len: usize) -> f64 { pub extern "C" fn shannon(data_ptr: *const u8, data_len: usize) -> f64 {
let data = unsafe { slice::from_raw_parts(data_ptr, data_len) }; let data = unsafe { slice::from_raw_parts(data_ptr, data_len) };