Skip to main content

openentropy_core/sources/timing/
dram_row_buffer.rs

1//! DRAM row buffer hit/miss timing entropy source.
2
3use rand::Rng;
4
5use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
6use crate::sources::helpers::{extract_timing_entropy, mach_time};
7
8/// Measures DRAM row buffer hit/miss timing by accessing random locations in a
9/// large (32 MB) buffer that exceeds L2/L3 cache capacity. The exact timing of
10/// each access depends on physical address mapping, row buffer state from all
11/// system activity, memory controller scheduling, and DRAM refresh
12/// interference.
13pub struct DRAMRowBufferSource;
14
15static DRAM_ROW_BUFFER_INFO: SourceInfo = SourceInfo {
16    name: "dram_row_buffer",
17    description: "DRAM row buffer hit/miss timing from random memory accesses",
18    physics: "Measures DRAM row buffer hit/miss timing by accessing different memory rows. \
19              DRAM is organized into rows of capacitor cells. Accessing an open row (hit) \
20              is fast; accessing a different row requires precharge + activate (miss), \
21              which is slower. The exact timing depends on: physical address mapping, \
22              row buffer state from ALL system activity, memory controller scheduling, \
23              and DRAM refresh interference.",
24    category: SourceCategory::Timing,
25    platform: Platform::Any,
26    requirements: &[],
27    entropy_rate_estimate: 3.0,
28    composite: false,
29    is_fast: false,
30};
31
32impl EntropySource for DRAMRowBufferSource {
33    fn info(&self) -> &SourceInfo {
34        &DRAM_ROW_BUFFER_INFO
35    }
36
37    fn is_available(&self) -> bool {
38        true
39    }
40
41    fn collect(&self, n_samples: usize) -> Vec<u8> {
42        const BUF_SIZE: usize = 32 * 1024 * 1024; // 32 MB — exceeds L2/L3 cache
43
44        // We need ~(n_samples + 2) XOR'd deltas, which requires ~(n_samples + 4)
45        // raw timings. Oversample 4x to give XOR-folding more to work with.
46        let num_accesses = n_samples * 4 + 64;
47
48        // Allocate a large buffer and touch it to ensure pages are backed.
49        let mut buffer: Vec<u8> = vec![0u8; BUF_SIZE];
50        for i in (0..BUF_SIZE).step_by(4096) {
51            buffer[i] = i as u8;
52        }
53
54        let mut rng = rand::rng();
55        let mut timings = Vec::with_capacity(num_accesses);
56
57        for _ in 0..num_accesses {
58            // Access two distant random locations per measurement to amplify
59            // row buffer miss timing variation.
60            let idx1 = rng.random_range(0..BUF_SIZE);
61            let idx2 = rng.random_range(0..BUF_SIZE);
62
63            let t0 = mach_time();
64            // SAFETY: idx1 and idx2 are bounded by BUF_SIZE via random_range.
65            // read_volatile prevents the compiler from eliding the accesses.
66            let _v1 = unsafe { std::ptr::read_volatile(&buffer[idx1]) };
67            let _v2 = unsafe { std::ptr::read_volatile(&buffer[idx2]) };
68            let t1 = mach_time();
69
70            timings.push(t1.wrapping_sub(t0));
71        }
72
73        // Prevent the buffer from being optimized away.
74        std::hint::black_box(&buffer);
75
76        extract_timing_entropy(&timings, n_samples)
77    }
78}
79
80// ---------------------------------------------------------------------------
81// Tests
82// ---------------------------------------------------------------------------
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    #[ignore] // Run with: cargo test -- --ignored
90    fn dram_row_buffer_collects_bytes() {
91        let src = DRAMRowBufferSource;
92        assert!(src.is_available());
93        let data = src.collect(128);
94        assert!(!data.is_empty());
95        assert!(data.len() <= 128);
96        // Sanity: not all bytes should be identical.
97        if data.len() > 1 {
98            let first = data[0];
99            assert!(data.iter().any(|&b| b != first), "all bytes were identical");
100        }
101    }
102
103    #[test]
104    fn source_info_category() {
105        assert_eq!(DRAMRowBufferSource.info().category, SourceCategory::Timing);
106    }
107
108    #[test]
109    fn source_info_name() {
110        assert_eq!(DRAMRowBufferSource.name(), "dram_row_buffer");
111    }
112}