Skip to main content

openentropy_core/sources/frontier/
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
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
37/// Entropy source from GPU/CPU memory domain crossing timing.
38pub struct IOSurfaceCrossingSource;
39
40/// IOSurface framework FFI (macOS only).
41#[cfg(target_os = "macos")]
42mod iosurface {
43    use std::ffi::c_void;
44
45    // CFDictionary/CFNumber/CFString types (all opaque pointers).
46    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    // IOSurface lock options.
56    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    // IOSurface property keys — linked from the IOSurface framework.
97    #[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    // kCFNumberSInt32Type = 3
108    const K_CF_NUMBER_SINT32_TYPE: CFIndex = 3;
109
110    /// Perform one IOSurface create/lock/write/read/unlock/destroy cycle.
111    /// Returns the high-resolution timing of the cycle, or None on failure.
112    pub fn crossing_cycle(iteration: usize) -> Option<u64> {
113        unsafe {
114            // Build IOSurface properties dictionary.
115            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; // 'BGRA'
131
132            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            // Create IOSurface (crosses into kernel / GPU memory controller).
140            let surface = IOSurfaceCreate(dict as CFDictionaryRef);
141            CFRelease(dict as CFTypeRef);
142            if surface.is_null() {
143                return None;
144            }
145
146            // Capture high-resolution timestamp around the lock/write/unlock cycle.
147            // This crosses CPU→fabric→GPU memory controller clock domains.
148            let t0 = super::mach_time();
149
150            // Lock for write (crosses clock domains).
151            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            // Write pattern to surface memory (CPU domain write).
158            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                // Write a pattern that varies per iteration to prevent optimization.
163                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            // Unlock write (flushes CPU caches, crosses back).
171            IOSurfaceUnlock(surface, 0, std::ptr::null_mut());
172
173            let t1 = super::mach_time();
174
175            // Lock for read (cross-domain coherence).
176            IOSurfaceLock(surface, K_IOSURFACE_LOCK_READ_ONLY, std::ptr::null_mut());
177
178            // Read back (may hit different cache/memory path).
179            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            // Destroy (returns memory to GPU memory controller pool).
190            CFRelease(surface as CFTypeRef);
191
192            // Combine write-cycle and read-cycle timings for maximum jitter capture.
193            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    /// Helper: set an integer value in a CFMutableDictionary.
200    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    /// Check if IOSurface is available by trying to create one.
217    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                // IOSurface cycle crosses CPU→fabric→GPU memory controller domains.
252                // The returned value captures write/read timing jitter directly.
253                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] // Requires IOSurface framework
278    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}