1#[cfg(target_os = "macos")]
16pub fn mach_time() -> u64 {
17 unsafe extern "C" {
18 fn mach_absolute_time() -> u64;
19 }
20 unsafe { mach_absolute_time() }
23}
24
25#[cfg(not(target_os = "macos"))]
26pub fn mach_time() -> u64 {
27 use std::sync::OnceLock;
28 use std::time::Instant;
29 static EPOCH: OnceLock<Instant> = OnceLock::new();
30 let epoch = EPOCH.get_or_init(Instant::now);
31 epoch.elapsed().as_nanos() as u64
32}
33
34fn pack_bits_into_bytes(bits: &[u8]) -> Vec<u8> {
42 let mut bytes = Vec::with_capacity(bits.len() / 8);
43 for chunk in bits.chunks_exact(8) {
44 let mut byte = 0u8;
45 for (i, &bit) in chunk.iter().enumerate() {
46 byte |= bit << (7 - i);
47 }
48 bytes.push(byte);
49 }
50 bytes
51}
52
53pub fn extract_lsbs_u64(deltas: &[u64]) -> Vec<u8> {
57 let bits: Vec<u8> = deltas.iter().map(|d| (d & 1) as u8).collect();
58 pack_bits_into_bytes(&bits)
59}
60
61pub fn extract_lsbs_i64(deltas: &[i64]) -> Vec<u8> {
65 let bits: Vec<u8> = deltas.iter().map(|d| (d & 1) as u8).collect();
66 pack_bits_into_bytes(&bits)
67}
68
69pub fn command_exists(name: &str) -> bool {
75 std::process::Command::new("which")
76 .arg(name)
77 .stdout(std::process::Stdio::null())
78 .stderr(std::process::Stdio::null())
79 .status()
80 .map(|s| s.success())
81 .unwrap_or(false)
82}
83
84fn run_command_output(program: &str, args: &[&str]) -> Option<std::process::Output> {
89 run_command_output_timeout(program, args, 30_000)
90}
91
92pub fn run_command_output_timeout(
97 program: &str,
98 args: &[&str],
99 timeout_ms: u64,
100) -> Option<std::process::Output> {
101 use std::io::Read;
102 use std::process::{Command, Stdio};
103 use std::thread;
104 use std::time::{Duration, Instant};
105
106 let mut child = Command::new(program)
107 .args(args)
108 .stdin(Stdio::null())
109 .stdout(Stdio::piped())
110 .stderr(Stdio::null())
111 .spawn()
112 .ok()?;
113
114 let mut stdout_reader = child.stdout.take().map(|mut stdout| {
117 thread::spawn(move || {
118 let mut buf = Vec::new();
119 let _ = stdout.read_to_end(&mut buf);
120 buf
121 })
122 });
123
124 let start = Instant::now();
125 let timeout = Duration::from_millis(timeout_ms.max(1));
126
127 loop {
128 match child.try_wait() {
129 Ok(Some(status)) => {
130 let stdout = stdout_reader
131 .take()
132 .and_then(|h| h.join().ok())
133 .unwrap_or_default();
134 let output = std::process::Output {
135 status,
136 stdout,
137 stderr: Vec::new(),
138 };
139 return output.status.success().then_some(output);
140 }
141 Ok(None) => {
142 if start.elapsed() >= timeout {
143 let _ = child.kill();
144 let _ = child.wait();
145 if let Some(reader) = stdout_reader.take() {
146 let _ = reader.join();
147 }
148 return None;
149 }
150 thread::sleep(Duration::from_millis(10));
151 }
152 Err(_e) => {
153 let _ = child.kill();
154 let _ = child.wait();
155 if let Some(reader) = stdout_reader.take() {
156 let _ = reader.join();
157 }
158 return None;
159 }
160 }
161 }
162}
163
164pub fn run_command(program: &str, args: &[&str]) -> Option<String> {
170 let output = run_command_output(program, args)?;
171 Some(String::from_utf8_lossy(&output.stdout).into_owned())
172}
173
174pub fn run_command_raw(program: &str, args: &[&str]) -> Option<Vec<u8>> {
179 run_command_output(program, args).map(|o| o.stdout)
180}
181
182pub fn run_command_raw_timeout(program: &str, args: &[&str], timeout_ms: u64) -> Option<Vec<u8>> {
184 run_command_output_timeout(program, args, timeout_ms).map(|o| o.stdout)
185}
186
187pub fn capture_camera_gray_frame(timeout_ms: u64, device_index: Option<u32>) -> Option<Vec<u8>> {
194 match device_index {
195 Some(n) => {
196 let input = format!("{n}:none");
197 capture_camera_with_inputs(timeout_ms, &[&input])
198 }
199 None => {
200 capture_camera_with_inputs(timeout_ms, &["default:none", "0:none", "1:none", "0:0"])
201 }
202 }
203}
204
205fn capture_camera_with_inputs(timeout_ms: u64, inputs: &[&str]) -> Option<Vec<u8>> {
206 for &input in inputs {
207 let args = [
208 "-hide_banner",
209 "-loglevel",
210 "error",
211 "-nostdin",
212 "-f",
213 "avfoundation",
214 "-framerate",
215 "30",
216 "-i",
217 input,
218 "-frames:v",
219 "1",
220 "-f",
221 "rawvideo",
222 "-pix_fmt",
223 "gray",
224 "pipe:1",
225 ];
226 if let Some(frame) = run_command_raw_timeout("ffmpeg", &args, timeout_ms)
227 && !frame.is_empty()
228 {
229 return Some(frame);
230 }
231 }
232 None
233}
234
235#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
245#[inline(always)]
246pub fn read_cntvct() -> u64 {
247 let val: u64;
248 unsafe {
251 std::arch::asm!("mrs {}, cntvct_el0", out(reg) val, options(nostack, nomem));
252 }
253 val
254}
255
256#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
257#[inline(always)]
258pub fn read_cntvct() -> u64 {
259 0
260}
261
262#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
274pub fn probe_jit_instruction_safe(mrs_instr: u32) -> bool {
275 let pid = unsafe { libc::fork() };
277 if pid < 0 {
278 return false; }
280 if pid == 0 {
281 let page = unsafe {
283 libc::mmap(
284 std::ptr::null_mut(),
285 4096,
286 libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
287 libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | 0x0800, -1,
289 0,
290 )
291 };
292 if page == libc::MAP_FAILED {
293 unsafe { libc::_exit(1) };
294 }
295 unsafe {
296 libc::pthread_jit_write_protect_np(0);
297 let code = page as *mut u32;
298 code.write(mrs_instr);
299 code.add(1).write(0xD65F03C0u32); libc::pthread_jit_write_protect_np(1);
301 core::arch::asm!("dc cvau, {p}", "ic ivau, {p}", p = in(reg) page, options(nostack));
302 core::arch::asm!("dsb ish", "isb", options(nostack));
303 }
304 type FnPtr = unsafe extern "C" fn() -> u64;
305 let fn_ptr: FnPtr = unsafe { std::mem::transmute(page) };
306 let _val = unsafe { fn_ptr() };
307 unsafe {
308 libc::munmap(page, 4096);
309 libc::_exit(0); }
311 }
312 let mut status: libc::c_int = 0;
314 let ret = unsafe { libc::waitpid(pid, &mut status, 0) };
315 if ret < 0 {
316 return false;
317 }
318 libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0
320}
321
322#[inline]
332pub fn xor_fold_u64(v: u64) -> u8 {
333 let b = v.to_le_bytes();
334 b[0] ^ b[1] ^ b[2] ^ b[3] ^ b[4] ^ b[5] ^ b[6] ^ b[7]
335}
336
337pub fn extract_timing_entropy(timings: &[u64], n_samples: usize) -> Vec<u8> {
350 if timings.len() < 2 {
351 return Vec::new();
352 }
353
354 let deltas: Vec<u64> = timings
355 .windows(2)
356 .map(|w| w[1].wrapping_sub(w[0]))
357 .collect();
358
359 let xored: Vec<u64> = deltas.windows(2).map(|w| w[0] ^ w[1]).collect();
361
362 let mut raw: Vec<u8> = xored.iter().map(|&x| xor_fold_u64(x)).collect();
364 raw.truncate(n_samples);
365 raw
366}
367
368pub fn pack_nibbles(nibbles: impl Iterator<Item = u8>, max_bytes: usize) -> Vec<u8> {
377 let mut output = Vec::with_capacity(max_bytes);
378 let mut buf: u8 = 0;
379 let mut count: u8 = 0;
380
381 for nibble in nibbles {
382 if count == 0 {
383 buf = nibble << 4;
384 count = 1;
385 } else {
386 buf |= nibble;
387 output.push(buf);
388 count = 0;
389 if output.len() >= max_bytes {
390 break;
391 }
392 }
393 }
394
395 if count == 1 && output.len() < max_bytes {
397 output.push(buf);
398 }
399
400 output.truncate(max_bytes);
401 output
402}
403
404pub fn extract_delta_bytes_i64(deltas: &[i64], n_samples: usize) -> Vec<u8> {
413 let xor_deltas: Vec<i64> = if deltas.len() >= 2 {
415 deltas.windows(2).map(|w| w[0] ^ w[1]).collect()
416 } else {
417 Vec::new()
418 };
419
420 let mut entropy = Vec::with_capacity(n_samples);
421
422 for d in deltas {
424 for &b in &d.to_le_bytes() {
425 entropy.push(b);
426 }
427 if entropy.len() >= n_samples {
428 entropy.truncate(n_samples);
429 return entropy;
430 }
431 }
432
433 for d in &xor_deltas {
435 for &b in &d.to_le_bytes() {
436 entropy.push(b);
437 }
438 if entropy.len() >= n_samples {
439 break;
440 }
441 }
442
443 entropy.truncate(n_samples);
444 entropy
445}
446
447#[cfg(target_os = "macos")]
457pub fn extract_timing_entropy_debiased(timings: &[u64], n_samples: usize) -> Vec<u8> {
458 if timings.len() < 4 {
459 return Vec::new();
460 }
461
462 let deltas: Vec<u64> = timings
463 .windows(2)
464 .map(|w| w[1].wrapping_sub(w[0]))
465 .collect();
466
467 let mut debiased_bits: Vec<u8> = Vec::with_capacity(deltas.len() / 2);
469 for pair in deltas.chunks_exact(2) {
470 if pair[0] != pair[1] {
471 debiased_bits.push(if pair[0] < pair[1] { 1 } else { 0 });
472 }
473 }
474
475 let mut bytes = Vec::with_capacity(n_samples);
477 for chunk in debiased_bits.chunks(8) {
478 if chunk.len() < 8 {
479 break;
480 }
481 let mut byte = 0u8;
482 for (i, &bit) in chunk.iter().enumerate() {
483 byte |= bit << (7 - i);
484 }
485 bytes.push(byte);
486 if bytes.len() >= n_samples {
487 break;
488 }
489 }
490 bytes.truncate(n_samples);
491 bytes
492}
493
494pub fn extract_timing_entropy_variance(timings: &[u64], n_samples: usize) -> Vec<u8> {
504 if timings.len() < 4 {
505 return Vec::new();
506 }
507
508 let deltas: Vec<u64> = timings
509 .windows(2)
510 .map(|w| w[1].wrapping_sub(w[0]))
511 .collect();
512
513 let variance: Vec<u64> = deltas.windows(2).map(|w| w[1].wrapping_sub(w[0])).collect();
514
515 let xored: Vec<u64> = variance.windows(2).map(|w| w[0] ^ w[1]).collect();
516
517 let mut raw: Vec<u8> = xored.iter().map(|&x| xor_fold_u64(x)).collect();
518 raw.truncate(n_samples);
519 raw
520}
521
522#[cfg(test)]
523mod tests {
524 use super::*;
525
526 #[test]
531 fn extract_lsbs_u64_basic() {
532 let deltas = vec![2, 3, 4, 5, 6, 7, 8, 9];
534 let bytes = extract_lsbs_u64(&deltas);
535 assert_eq!(bytes.len(), 1);
536 assert_eq!(bytes[0], 0b01010101);
537 }
538
539 #[test]
540 fn extract_lsbs_i64_basic() {
541 let deltas = vec![2i64, 3, 4, 5, 6, 7, 8, 9];
542 let bytes = extract_lsbs_i64(&deltas);
543 assert_eq!(bytes.len(), 1);
544 assert_eq!(bytes[0], 0b01010101);
545 }
546
547 #[test]
548 fn extract_lsbs_u64_empty() {
549 let bytes = extract_lsbs_u64(&[]);
550 assert!(bytes.is_empty());
551 }
552
553 #[test]
554 fn extract_lsbs_i64_empty() {
555 let bytes = extract_lsbs_i64(&[]);
556 assert!(bytes.is_empty());
557 }
558
559 #[test]
560 fn extract_lsbs_u64_all_odd() {
561 let deltas = vec![1u64, 3, 5, 7, 9, 11, 13, 15];
563 let bytes = extract_lsbs_u64(&deltas);
564 assert_eq!(bytes[0], 0xFF);
565 }
566
567 #[test]
568 fn extract_lsbs_u64_all_even() {
569 let deltas = vec![0u64, 2, 4, 6, 8, 10, 12, 14];
571 let bytes = extract_lsbs_u64(&deltas);
572 assert_eq!(bytes[0], 0x00);
573 }
574
575 #[test]
576 fn extract_lsbs_partial_byte() {
577 let deltas = vec![1u64, 0, 1, 0, 1];
579 let bytes = extract_lsbs_u64(&deltas);
580 assert!(bytes.is_empty());
581 }
582
583 #[test]
584 fn extract_lsbs_u64_i64_agree() {
585 let u_deltas = vec![1u64, 2, 3, 4, 5, 6, 7, 8];
587 let i_deltas = vec![1i64, 2, 3, 4, 5, 6, 7, 8];
588 assert_eq!(extract_lsbs_u64(&u_deltas), extract_lsbs_i64(&i_deltas));
589 }
590
591 #[test]
596 fn pack_bits_empty() {
597 let bits: Vec<u8> = vec![];
598 let bytes = pack_bits_into_bytes(&bits);
599 assert!(bytes.is_empty());
600 }
601
602 #[test]
603 fn pack_bits_full_byte() {
604 let bits = vec![1, 0, 1, 0, 1, 0, 1, 0];
605 let bytes = pack_bits_into_bytes(&bits);
606 assert_eq!(bytes, vec![0b10101010]);
607 }
608
609 #[test]
614 fn mach_time_is_monotonic() {
615 let t1 = mach_time();
616 let t2 = mach_time();
617 assert!(t2 >= t1);
618 }
619
620 #[test]
625 fn pack_nibbles_basic() {
626 let nibbles = vec![0x0A_u8, 0x0B];
627 let bytes = pack_nibbles(nibbles.into_iter(), 10);
628 assert_eq!(bytes.len(), 1);
629 assert_eq!(bytes[0], 0xAB);
630 }
631
632 #[test]
633 fn pack_nibbles_empty() {
634 let bytes = pack_nibbles(std::iter::empty(), 10);
635 assert!(bytes.is_empty());
636 }
637
638 #[test]
639 fn pack_nibbles_odd_count() {
640 let nibbles = vec![0x0C_u8, 0x0D, 0x0E];
641 let bytes = pack_nibbles(nibbles.into_iter(), 10);
642 assert_eq!(bytes.len(), 2);
643 assert_eq!(bytes[0], 0xCD);
644 assert_eq!(bytes[1], 0xE0); }
646
647 #[test]
648 fn pack_nibbles_respects_max() {
649 let nibbles = vec![0x01_u8, 0x02, 0x03, 0x04, 0x05, 0x06];
650 let bytes = pack_nibbles(nibbles.into_iter(), 2);
651 assert_eq!(bytes.len(), 2);
652 }
653
654 #[test]
659 fn extract_delta_bytes_empty() {
660 let bytes = extract_delta_bytes_i64(&[], 10);
661 assert!(bytes.is_empty());
662 }
663
664 #[test]
665 fn extract_delta_bytes_single_delta() {
666 let deltas = vec![0x0102030405060708i64];
667 let bytes = extract_delta_bytes_i64(&deltas, 8);
668 assert_eq!(bytes, 0x0102030405060708i64.to_le_bytes().to_vec());
670 }
671
672 #[test]
673 fn extract_delta_bytes_truncated() {
674 let deltas = vec![0x0102030405060708i64];
675 let bytes = extract_delta_bytes_i64(&deltas, 4);
676 assert_eq!(bytes.len(), 4);
677 assert_eq!(bytes, &0x0102030405060708i64.to_le_bytes()[..4]);
678 }
679
680 #[test]
681 fn extract_delta_bytes_with_xor_mixing() {
682 let deltas = vec![100i64, 200];
684 let bytes = extract_delta_bytes_i64(&deltas, 24);
685 assert_eq!(bytes.len(), 24);
687 }
688
689 #[test]
690 fn extract_delta_bytes_respects_n_samples() {
691 let deltas: Vec<i64> = (1..=100).collect();
692 let bytes = extract_delta_bytes_i64(&deltas, 50);
693 assert_eq!(bytes.len(), 50);
694 }
695
696 #[test]
701 fn xor_fold_u64_zero() {
702 assert_eq!(xor_fold_u64(0), 0);
703 }
704
705 #[test]
706 fn xor_fold_u64_identical_bytes() {
707 assert_eq!(xor_fold_u64(0x0101010101010101), 0);
709 }
710
711 #[test]
712 fn xor_fold_u64_single_byte() {
713 assert_eq!(xor_fold_u64(0xFF), 0xFF);
714 }
715
716 #[test]
717 fn xor_fold_u64_two_bytes() {
718 assert_eq!(xor_fold_u64(0xBB_00_00_00_00_00_00_AA), 0xAA ^ 0xBB);
720 }
721
722 #[test]
723 fn xor_fold_u64_max() {
724 assert_eq!(xor_fold_u64(u64::MAX), 0);
726 }
727
728 #[test]
733 fn extract_timing_entropy_basic() {
734 let timings = vec![100, 110, 105, 120, 108, 130, 112, 125];
735 let result = extract_timing_entropy(&timings, 4);
736 assert!(!result.is_empty());
737 assert!(result.len() <= 4);
738 }
739
740 #[test]
741 fn extract_timing_entropy_too_few_samples() {
742 assert!(extract_timing_entropy(&[], 10).is_empty());
743 assert!(extract_timing_entropy(&[42], 10).is_empty());
744 }
745
746 #[test]
747 fn extract_timing_entropy_exactly_two_timings() {
748 let result = extract_timing_entropy(&[100, 200], 10);
750 assert!(result.is_empty());
751 }
752
753 #[test]
754 fn extract_timing_entropy_exactly_three_timings() {
755 let result = extract_timing_entropy(&[100, 200, 150], 10);
757 assert_eq!(result.len(), 1);
758 }
759
760 #[test]
761 fn extract_timing_entropy_truncates_to_n_samples() {
762 let timings: Vec<u64> = (0..100).collect();
763 let result = extract_timing_entropy(&timings, 5);
764 assert_eq!(result.len(), 5);
765 }
766
767 #[test]
768 fn extract_timing_entropy_constant_timings() {
769 let timings = vec![42u64; 20];
771 let result = extract_timing_entropy(&timings, 10);
772 assert!(result.iter().all(|&b| b == 0));
773 }
774
775 #[test]
780 fn run_command_echo() {
781 let out = run_command("echo", &["hello"]);
782 assert!(out.is_some());
783 assert_eq!(out.unwrap().trim(), "hello");
784 }
785
786 #[test]
787 fn run_command_nonexistent() {
788 let out = run_command("/nonexistent/binary", &[]);
789 assert!(out.is_none());
790 }
791
792 #[test]
793 fn run_command_raw_echo() {
794 let out = run_command_raw("echo", &["bytes"]);
795 assert!(out.is_some());
796 assert!(out.unwrap().starts_with(b"bytes"));
797 }
798
799 #[test]
800 fn run_command_failing_status() {
801 let out = run_command("false", &[]);
803 assert!(out.is_none());
804 }
805
806 #[test]
807 fn run_command_raw_failing_status() {
808 let out = run_command_raw("false", &[]);
809 assert!(out.is_none());
810 }
811
812 #[test]
813 fn run_command_empty_output() {
814 let out = run_command("true", &[]);
816 assert!(out.is_some());
817 assert!(out.unwrap().is_empty());
818 }
819
820 #[test]
821 fn command_exists_true() {
822 assert!(command_exists("echo"));
823 }
824
825 #[test]
826 fn command_exists_false() {
827 assert!(!command_exists("nonexistent_binary_xyz_12345"));
828 }
829
830 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
835 #[test]
836 fn debiased_extraction_basic() {
837 let timings: Vec<u64> = (0..200).map(|i| 100 + (i * 7 + i * i) % 50).collect();
838 let result = extract_timing_entropy_debiased(&timings, 10);
839 assert!(result.len() <= 10);
840 }
841
842 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
843 #[test]
844 fn debiased_extraction_too_few() {
845 assert!(extract_timing_entropy_debiased(&[1, 2, 3], 10).is_empty());
846 assert!(extract_timing_entropy_debiased(&[], 10).is_empty());
847 }
848
849 #[cfg(all(target_os = "macos", target_arch = "aarch64"))]
850 #[test]
851 fn debiased_extraction_constant_input() {
852 let timings = vec![42u64; 100];
853 let result = extract_timing_entropy_debiased(&timings, 10);
854 assert!(result.is_empty());
855 }
856
857 #[test]
862 fn variance_extraction_basic() {
863 let timings: Vec<u64> = (0..100).map(|i| 100 + (i * 7 + i * i) % 50).collect();
864 let result = extract_timing_entropy_variance(&timings, 10);
865 assert!(!result.is_empty());
866 assert!(result.len() <= 10);
867 }
868
869 #[test]
870 fn variance_extraction_too_few() {
871 assert!(extract_timing_entropy_variance(&[1, 2, 3], 10).is_empty());
872 }
873}