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