Skip to main content

openentropy_core/sources/gpu/
iosurface_crossing.rs

1//! IOSurface GPU/CPU memory domain crossing — multi-clock-domain coherence entropy.
2//!
3//! IOSurface is shared memory between GPU and CPU. Writing from one domain and
4//! reading from another crosses multiple clock boundaries:
5//!   CPU → fabric → GPU memory controller → GPU cache
6//!
7//! Each domain transition adds independent timing noise from cache coherence
8//! traffic, fabric arbitration, and cross-clock-domain synchronization.
9//!
10//! Uses direct IOSurface framework FFI — no external process spawning.
11//! Each create/lock/write/unlock/destroy cycle completes in microseconds.
12//!
13
14use 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
40/// Entropy source from GPU/CPU memory domain crossing timing.
41pub struct IOSurfaceCrossingSource;
42
43/// IOSurface framework FFI (macOS only).
44#[cfg(target_os = "macos")]
45mod iosurface {
46    use std::ffi::c_void;
47
48    // CFDictionary/CFNumber/CFString types (all opaque pointers).
49    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    // IOSurface lock options.
59    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    // IOSurface property keys — linked from the IOSurface framework.
100    #[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    // kCFNumberSInt32Type = 3
111    const K_CF_NUMBER_SINT32_TYPE: CFIndex = 3;
112
113    /// Perform one IOSurface create/lock/write/read/unlock/destroy cycle.
114    /// Returns the high-resolution timing of the cycle, or None on failure.
115    pub fn crossing_cycle(iteration: usize) -> Option<u64> {
116        unsafe {
117            // Build IOSurface properties dictionary.
118            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; // 'BGRA'
134
135            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            // Create IOSurface (crosses into kernel / GPU memory controller).
143            let surface = IOSurfaceCreate(dict as CFDictionaryRef);
144            CFRelease(dict as CFTypeRef);
145            if surface.is_null() {
146                return None;
147            }
148
149            // Capture high-resolution timestamp around the lock/write/unlock cycle.
150            // This crosses CPU→fabric→GPU memory controller clock domains.
151            let t0 = super::mach_time();
152
153            // Lock for write (crosses clock domains).
154            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            // Write pattern to surface memory (CPU domain write).
161            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                // Write a pattern that varies per iteration to prevent optimization.
166                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            // Unlock write (flushes CPU caches, crosses back).
174            IOSurfaceUnlock(surface, 0, std::ptr::null_mut());
175
176            let t1 = super::mach_time();
177
178            // Lock for read (cross-domain coherence).
179            IOSurfaceLock(surface, K_IOSURFACE_LOCK_READ_ONLY, std::ptr::null_mut());
180
181            // Re-obtain base address after re-lock — the kernel may have
182            // remapped the surface between unlock and lock.
183            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            // Destroy (returns memory to GPU memory controller pool).
195            CFRelease(surface as CFTypeRef);
196
197            // Combine write-cycle and read-cycle timings for maximum jitter capture.
198            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    /// Helper: set an integer value in a CFMutableDictionary.
205    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    /// Check if IOSurface is available by trying to create one.
222    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                // IOSurface cycle crosses CPU→fabric→GPU memory controller domains.
258                // The returned value captures write/read timing jitter directly.
259                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] // Requires IOSurface framework
284    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}