openentropy_core/sources/gpu/
iosurface_crossing.rs1use crate::source::{EntropySource, Platform, Requirement, SourceCategory, SourceInfo};
15#[cfg(target_os = "macos")]
16use crate::sources::helpers::extract_timing_entropy;
17#[cfg(target_os = "macos")]
18use crate::sources::helpers::mach_time;
19#[cfg(target_os = "macos")]
20use std::sync::OnceLock;
21
22static IOSURFACE_CROSSING_INFO: SourceInfo = SourceInfo {
23 name: "iosurface_crossing",
24 description: "IOSurface GPU/CPU memory domain crossing coherence jitter",
25 physics: "Times the round-trip latency of IOSurface create/lock/write/unlock/destroy \
26 cycles that cross multiple clock domain boundaries: CPU \u{2192} system fabric \
27 \u{2192} GPU memory controller \u{2192} GPU cache \u{2192} back. Each boundary adds \
28 independent timing noise from cache coherence protocol arbitration, fabric \
29 interconnect scheduling, and cross-clock-domain synchronizer metastability. \
30 The combined multi-domain crossing creates high entropy from physically \
31 independent noise sources.",
32 category: SourceCategory::GPU,
33 platform: Platform::MacOS,
34 requirements: &[Requirement::IOSurface],
35 entropy_rate_estimate: 2.5,
36 composite: false,
37 is_fast: true,
38};
39
40pub struct IOSurfaceCrossingSource;
42
43#[cfg(target_os = "macos")]
45mod iosurface {
46 use std::ffi::c_void;
47
48 type CFDictionaryRef = *const c_void;
50 type CFMutableDictionaryRef = *mut c_void;
51 type CFStringRef = *const c_void;
52 type CFNumberRef = *const c_void;
53 type CFAllocatorRef = *const c_void;
54 type CFTypeRef = *const c_void;
55 type CFIndex = isize;
56 type IOSurfaceRef = *mut c_void;
57
58 const K_IOSURFACE_LOCK_READ_ONLY: u32 = 1;
60
61 #[link(name = "IOSurface", kind = "framework")]
62 unsafe extern "C" {
63 fn IOSurfaceCreate(properties: CFDictionaryRef) -> IOSurfaceRef;
64 fn IOSurfaceLock(surface: IOSurfaceRef, options: u32, seed: *mut u32) -> i32;
65 fn IOSurfaceUnlock(surface: IOSurfaceRef, options: u32, seed: *mut u32) -> i32;
66 fn IOSurfaceGetBaseAddress(surface: IOSurfaceRef) -> *mut c_void;
67 fn IOSurfaceGetAllocSize(surface: IOSurfaceRef) -> usize;
68 }
69
70 #[link(name = "CoreFoundation", kind = "framework")]
71 unsafe extern "C" {
72 static kCFAllocatorDefault: CFAllocatorRef;
73
74 fn CFDictionaryCreateMutable(
75 allocator: CFAllocatorRef,
76 capacity: CFIndex,
77 key_callbacks: *const c_void,
78 value_callbacks: *const c_void,
79 ) -> CFMutableDictionaryRef;
80
81 fn CFDictionarySetValue(
82 dict: CFMutableDictionaryRef,
83 key: *const c_void,
84 value: *const c_void,
85 );
86
87 fn CFNumberCreate(
88 allocator: CFAllocatorRef,
89 the_type: CFIndex,
90 value_ptr: *const c_void,
91 ) -> CFNumberRef;
92
93 fn CFRelease(cf: CFTypeRef);
94
95 static kCFTypeDictionaryKeyCallBacks: c_void;
96 static kCFTypeDictionaryValueCallBacks: c_void;
97 }
98
99 #[link(name = "IOSurface", kind = "framework")]
101 unsafe extern "C" {
102 static kIOSurfaceWidth: CFStringRef;
103 static kIOSurfaceHeight: CFStringRef;
104 static kIOSurfaceBytesPerElement: CFStringRef;
105 static kIOSurfaceBytesPerRow: CFStringRef;
106 static kIOSurfaceAllocSize: CFStringRef;
107 static kIOSurfacePixelFormat: CFStringRef;
108 }
109
110 const K_CF_NUMBER_SINT32_TYPE: CFIndex = 3;
112
113 pub fn crossing_cycle(iteration: usize) -> Option<u64> {
116 unsafe {
117 let dict = CFDictionaryCreateMutable(
119 kCFAllocatorDefault,
120 6,
121 std::ptr::addr_of!(kCFTypeDictionaryKeyCallBacks).cast(),
122 std::ptr::addr_of!(kCFTypeDictionaryValueCallBacks).cast(),
123 );
124 if dict.is_null() {
125 return None;
126 }
127
128 let width: i32 = 64;
129 let height: i32 = 64;
130 let bpe: i32 = 4;
131 let bpr: i32 = width * bpe;
132 let alloc_size: i32 = bpr * height;
133 let pixel_format: i32 = 0x42475241; set_dict_int(dict, kIOSurfaceWidth, width);
136 set_dict_int(dict, kIOSurfaceHeight, height);
137 set_dict_int(dict, kIOSurfaceBytesPerElement, bpe);
138 set_dict_int(dict, kIOSurfaceBytesPerRow, bpr);
139 set_dict_int(dict, kIOSurfaceAllocSize, alloc_size);
140 set_dict_int(dict, kIOSurfacePixelFormat, pixel_format);
141
142 let surface = IOSurfaceCreate(dict as CFDictionaryRef);
144 CFRelease(dict as CFTypeRef);
145 if surface.is_null() {
146 return None;
147 }
148
149 let t0 = super::mach_time();
152
153 let lock_result = IOSurfaceLock(surface, 0, std::ptr::null_mut());
155 if lock_result != 0 {
156 CFRelease(surface as CFTypeRef);
157 return None;
158 }
159
160 let base = IOSurfaceGetBaseAddress(surface);
162 if !base.is_null() {
163 let size = IOSurfaceGetAllocSize(surface);
164 let slice = std::slice::from_raw_parts_mut(base as *mut u8, size);
165 let pattern = (iteration as u8).wrapping_mul(0x37).wrapping_add(0xA5);
167 for (j, byte) in slice.iter_mut().enumerate() {
168 *byte = pattern.wrapping_add(j as u8);
169 }
170 std::hint::black_box(&slice[0]);
171 }
172
173 IOSurfaceUnlock(surface, 0, std::ptr::null_mut());
175
176 let t1 = super::mach_time();
177
178 IOSurfaceLock(surface, K_IOSURFACE_LOCK_READ_ONLY, std::ptr::null_mut());
180
181 let read_base = IOSurfaceGetBaseAddress(surface);
184 if !read_base.is_null() {
185 let size = IOSurfaceGetAllocSize(surface);
186 let slice = std::slice::from_raw_parts(read_base as *const u8, size);
187 std::hint::black_box(slice[iteration % size]);
188 }
189
190 IOSurfaceUnlock(surface, K_IOSURFACE_LOCK_READ_ONLY, std::ptr::null_mut());
191
192 let t2 = super::mach_time();
193
194 CFRelease(surface as CFTypeRef);
196
197 let write_timing = t1.wrapping_sub(t0);
199 let read_timing = t2.wrapping_sub(t1);
200 Some(write_timing ^ read_timing.rotate_left(32))
201 }
202 }
203
204 unsafe fn set_dict_int(dict: CFMutableDictionaryRef, key: CFStringRef, value: i32) {
206 let num = unsafe {
207 CFNumberCreate(
208 kCFAllocatorDefault,
209 K_CF_NUMBER_SINT32_TYPE,
210 &value as *const i32 as *const c_void,
211 )
212 };
213 if !num.is_null() {
214 unsafe {
215 CFDictionarySetValue(dict, key, num);
216 CFRelease(num as CFTypeRef);
217 }
218 }
219 }
220
221 pub fn is_available() -> bool {
223 crossing_cycle(0).is_some()
224 }
225}
226
227impl EntropySource for IOSurfaceCrossingSource {
228 fn info(&self) -> &SourceInfo {
229 &IOSURFACE_CROSSING_INFO
230 }
231
232 fn is_available(&self) -> bool {
233 #[cfg(target_os = "macos")]
234 {
235 static IOSURFACE_AVAILABLE: OnceLock<bool> = OnceLock::new();
236 *IOSURFACE_AVAILABLE.get_or_init(iosurface::is_available)
237 }
238 #[cfg(not(target_os = "macos"))]
239 {
240 false
241 }
242 }
243
244 fn collect(&self, n_samples: usize) -> Vec<u8> {
245 #[cfg(not(target_os = "macos"))]
246 {
247 let _ = n_samples;
248 Vec::new()
249 }
250
251 #[cfg(target_os = "macos")]
252 {
253 let raw_count = n_samples * 4 + 64;
254 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
255
256 for i in 0..raw_count {
257 if let Some(cycle_timing) = iosurface::crossing_cycle(i) {
260 timings.push(cycle_timing);
261 }
262 }
263
264 extract_timing_entropy(&timings, n_samples)
265 }
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn info() {
275 let src = IOSurfaceCrossingSource;
276 assert_eq!(src.name(), "iosurface_crossing");
277 assert_eq!(src.info().category, SourceCategory::GPU);
278 assert!(!src.info().composite);
279 }
280
281 #[test]
282 #[cfg(target_os = "macos")]
283 #[ignore] fn collects_bytes() {
285 let src = IOSurfaceCrossingSource;
286 if src.is_available() {
287 let data = src.collect(64);
288 assert!(!data.is_empty());
289 assert!(data.len() <= 64);
290 let unique: std::collections::HashSet<u8> = data.iter().copied().collect();
291 assert!(unique.len() > 1, "Expected variation in collected bytes");
292 }
293 }
294}