diff --git a/Cargo.toml b/Cargo.toml index 135b75e..bf62e94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,11 @@ name = "ocelli" version = "0.1.0" edition = "2021" +[lib] +name = "ocelli" +path = "src/lib.rs" + [dependencies] -chrono = "0.4.39" -opencv = "0.93.5" +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..b0607b2 --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,144 @@ +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 whitening 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 = 4.0; + let mut frame_count = 0; + + while total_entropy.len() < length { + // Capture first frame + let (data1, _) = stream.next().expect("Failed to capture frame"); + let grayscale_data1 = frame_to_grayscale(&data1); + + if ocelli.is_covered(&grayscale_data1, 50) { + // Skip the first 10 frames + while frame_count < 10 { + let _ = stream.next().expect("Failed to capture frame"); + frame_count += 1; + } + + let entropy: Vec; + + if quick { + // Quicker capture using Pick and Flip + entropy = ocelli.pick_and_flip(&grayscale_data1, frame_count as usize); + } else { + // 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); + } + + 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 (Shannon entropy: {:.3})", shannon_entropy); + } + } else { + println!("Camera is not covered. Please cover the camera."); + frame_count = 0; + } + } + + // 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(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c90df95 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,137 @@ +use std::collections::{HashMap, HashSet}; + +pub struct Ocelli; + +impl Ocelli { + + pub fn chop_and_tack(&self, current: &Vec, previous: &Vec, width: usize, minimum_distance: usize) -> Vec { + 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 + let start_row = 100; + let end_row = height - 100; + + if start_row >= end_row || width <= 200 { + panic!("Resolution is too small to apply the grid selection with the given offset."); + } + + 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 + } + + pub fn pick_and_flip(&self, data: &[u8], current_frame_index: usize) -> Vec { + 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; + } + } + } + + entropy + } + + pub fn shannon(&self, data: &Vec) -> f64 { + let mut frequency_map = HashMap::new(); + let data_len = data.len(); + + for &byte in data { + *frequency_map.entry(byte).or_insert(0) += 1; + } + + frequency_map.values().fold(0.0, |entropy, &count| { + let probability = count as f64 / data_len as f64; + entropy - probability * probability.log2() + }) + } + + pub fn whiten(&self, entropy: &[u8]) -> Vec { + let mut whitened_entropy = Vec::new(); + let mut current_byte = 0u8; + let mut bit_count = 0; + + for byte in entropy { + for i in (0..8).step_by(2) { + let bit1 = (byte >> (7 - i)) & 1; + let bit2 = (byte >> (6 - i)) & 1; + + match (bit1, bit2) { + (0, 1) => { + current_byte = (current_byte << 1) | 0; + bit_count += 1; + } + (1, 0) => { + current_byte = (current_byte << 1) | 1; + bit_count += 1; + } + _ => {} + } + + if bit_count == 8 { + whitened_entropy.push(current_byte); + current_byte = 0; + bit_count = 0; + } + } + } + + whitened_entropy + } + + pub fn is_covered(&self, grayscale: &[u8], threshold: usize) -> bool { + let unique_values: HashSet<_> = grayscale.iter().copied().collect(); + // println!("UV: {:?}", unique_values); + unique_values.len() < threshold + } +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index d66e84e..0000000 --- a/src/main.rs +++ /dev/null @@ -1,275 +0,0 @@ -use opencv::prelude::*; -use opencv::videoio::{VideoCapture, CAP_V4L}; -use opencv::imgproc; -use opencv::core; -use std::collections::HashSet; -use std::collections::HashMap; -use std::env; -use std::time::Instant; -use std::fs::File; -use std::io::Write; -use chrono::Local; - -struct Ocelli; - -impl Ocelli { - fn chop_and_tack(&self, current: &Vec, previous: &Vec, width: usize, minimum_distance: usize) -> Vec { - 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 - let start_row = 100; - let end_row = height - 100; - - if start_row >= end_row || width <= 200 { - panic!("Resolution is too small to apply the grid selection with the given offset."); - } - - 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 - } - - fn pick_and_flip(&self, data: &Vec, current_frame_index: usize) -> Vec { - let mut entropy = Vec::new(); - let mut current_byte = 0u8; - let mut bit_count = 0; - - // Iterate through each pixel in the array - for &pixel_brightness in data { - // Check if the brightness is within the valid range [2, 253] - if (2..=253).contains(&pixel_brightness) { - // Extract the least significant bit (LSB) - let mut lsb = pixel_brightness & 1; - - // If the frame index is even, flip the bit - if current_frame_index % 2 == 0 { - lsb ^= 1; // Flip the bit (0 -> 1, 1 -> 0) - } - - // Add the bit to the current byte - current_byte = (current_byte << 1) | lsb; - bit_count += 1; - - // If we have 8 bits, push the byte to the entropy vector - if bit_count == 8 { - entropy.push(current_byte); - current_byte = 0; - bit_count = 0; - } - } - } - - entropy - } - - - /// Apply Van Neumann whitening to a vector of entropy bits - fn whiten(&self, entropy: &Vec) -> Vec { - let mut whitened_entropy = Vec::new(); - let mut current_byte = 0u8; - let mut bit_count = 0; - - // Iterate through the entropy bytes and process bits in pairs - for byte in entropy { - for i in (0..8).step_by(2) { - let bit1 = (byte >> (7 - i)) & 1; - let bit2 = (byte >> (6 - i)) & 1; - - // Apply Van Neumann rules - match (bit1, bit2) { - (0, 1) => { - current_byte = (current_byte << 1) | 0; // Append '0' - bit_count += 1; - } - (1, 0) => { - current_byte = (current_byte << 1) | 1; // Append '1' - bit_count += 1; - } - _ => {} // Discard (0,0) and (1,1) pairs - } - - // If we have a full byte, push it to the output - if bit_count == 8 { - whitened_entropy.push(current_byte); - current_byte = 0; - bit_count = 0; - } - } - } - - whitened_entropy - } - - /// Calculates the Shannon entropy of binary data - fn shannon(&self, data: &Vec) -> f64 { - let mut frequency_map = HashMap::new(); - let data_len = data.len(); - - for &byte in data { - *frequency_map.entry(byte).or_insert(0) += 1; - } - - frequency_map.values().fold(0.0, |entropy, &count| { - let probability = count as f64 / data_len as f64; - entropy - probability * probability.log2() - }) - } - - /// Determines if the camera is covered based on the unique grayscale values - fn is_covered(&self, grayscale: &Vec, threshold: usize) -> bool { - let unique_values: HashSet<_> = grayscale.iter().copied().collect(); - unique_values.len() < threshold - } -} - -fn main() -> opencv::Result<()> { - let args: Vec = env::args().collect(); - if args.len() < 3 { - eprintln!("Usage: {} [-w]", args[0]); - std::process::exit(1); - } - - let camera_index: i32 = 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 whiten_flag = args.contains(&String::from("-w")); - - let mut cam = VideoCapture::new(camera_index, CAP_V4L)?; - if !cam.is_opened()? { - panic!("Failed to open the camera with index {}", camera_index); - } - - // Capture a single frame to determine resolution - let mut frame = core::Mat::default(); - cam.read(&mut frame)?; - if frame.empty() { - panic!("Failed to capture a frame."); - } - - let width = frame.cols() as usize; - let height = frame.rows() as usize; - - println!( - "Camera index: {}\nCamera resolution detected: {}x{}", - camera_index, width, height - ); - - let ocelli = Ocelli; - - // Convert the frame to grayscale and check if the camera is covered - let mut gray_frame = core::Mat::default(); - imgproc::cvt_color(&frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0)?; - let grayscale_data = gray_frame.data_bytes().expect("Failed to get grayscale data").to_vec(); - - let uncovered = !ocelli.is_covered(&grayscale_data, 50); - - println!( - "Camera is {}covered.", - if uncovered { - "un" - } else { - "" - } - ); - - let mut total_entropy = Vec::new(); - let shannon_threshold = 4.0; - let mut previous_frame_data = grayscale_data.clone(); - - let start_time = Instant::now(); - let mut frame_index = 1; - - while total_entropy.len() < length { - cam.read(&mut frame)?; - let mut gray_frame = core::Mat::default(); - imgproc::cvt_color(&frame, &mut gray_frame, imgproc::COLOR_BGR2GRAY, 0)?; - let current_frame_data = gray_frame.data_bytes().expect("Failed to get grayscale data").to_vec(); - - let mut entropy: Vec = Vec::new(); - - if uncovered { - entropy = ocelli.pick_and_flip(¤t_frame_data, frame_index); - frame_index = frame_index + 1; - } else { - entropy = ocelli.chop_and_tack(¤t_frame_data, &previous_frame_data, width, 30); - previous_frame_data = current_frame_data; - } - - if whiten_flag { - entropy = ocelli.whiten(&entropy); - } - - let shannon_entropy = ocelli.shannon(&entropy); - - if shannon_entropy >= shannon_threshold { - total_entropy.extend(entropy); - } else { - println!( - "Rejected entropy array (Shannon entropy: {:.3}). Retrying...", - shannon_entropy - ); - } - - println!( - "Collected {} of {} bytes of entropy...", - total_entropy.len(), - length - ); - } - - let total_shannon_entropy = ocelli.shannon(&total_entropy); - - let elapsed_time = start_time.elapsed(); - println!( - "Process completed in {:.3} seconds.\nShannon Entropy {:.3}.", - elapsed_time.as_secs_f64(), - total_shannon_entropy - ); - - // Save the generated entropy to a binary file - let timestamp = Local::now().format("%Y%m%d_%H%M%S").to_string(); - let method = if uncovered { "pick_and_flip" } else { "chop_and_tack" }; - let whitened = if whiten_flag { "_whitened" } else { "" }; - let filename = format!("{}{}_{}.bin", method, whitened, 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); - - Ok(()) -}