openentropy_core/sources/frontier/
audio_pll_timing.rs1use crate::source::{EntropySource, Platform, Requirement, SourceCategory, SourceInfo};
14#[cfg(target_os = "macos")]
15use crate::sources::helpers::extract_timing_entropy;
16
17static AUDIO_PLL_TIMING_INFO: SourceInfo = SourceInfo {
18 name: "audio_pll_timing",
19 description: "Audio PLL clock jitter from CoreAudio device property queries",
20 physics: "Rapidly queries CoreAudio device properties (sample rate, latency) that \
21 cross the audio PLL / CPU clock domain boundary. The audio subsystem\u{2019}s \
22 PLL has thermally-driven phase noise from VCO transistor Johnson-Nyquist \
23 noise, charge pump shot noise, and crystal reference jitter. Each query \
24 timing captures the instantaneous phase relationship between these \
25 independent clock domains.",
26 category: SourceCategory::Thermal,
27 platform: Platform::MacOS,
28 requirements: &[Requirement::AudioUnit],
29 entropy_rate_estimate: 4000.0,
30 composite: false,
31};
32
33pub struct AudioPLLTimingSource;
35
36#[cfg(target_os = "macos")]
38mod coreaudio {
39 #[repr(C)]
40 pub struct AudioObjectPropertyAddress {
41 pub m_selector: u32,
42 pub m_scope: u32,
43 pub m_element: u32,
44 }
45
46 pub const AUDIO_HARDWARE_PROPERTY_DEFAULT_OUTPUT_DEVICE: u32 = 0x644F7574; pub const AUDIO_DEVICE_PROPERTY_NOMINAL_SAMPLE_RATE: u32 = 0x6E737274; pub const AUDIO_DEVICE_PROPERTY_ACTUAL_SAMPLE_RATE: u32 = 0x61737264; pub const AUDIO_DEVICE_PROPERTY_LATENCY: u32 = 0x6C746E63; pub const AUDIO_OBJECT_PROPERTY_SCOPE_GLOBAL: u32 = 0x676C6F62; pub const AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN: u32 = 0;
52 pub const AUDIO_DEVICE_PROPERTY_SCOPE_OUTPUT: u32 = 0x6F757470; pub const AUDIO_OBJECT_SYSTEM_OBJECT: u32 = 1;
54
55 #[link(name = "CoreAudio", kind = "framework")]
56 unsafe extern "C" {
57 pub fn AudioObjectGetPropertyData(
58 object_id: u32,
59 address: *const AudioObjectPropertyAddress,
60 qualifier_data_size: u32,
61 qualifier_data: *const std::ffi::c_void,
62 data_size: *mut u32,
63 data: *mut std::ffi::c_void,
64 ) -> i32;
65 }
66
67 pub fn get_default_output_device() -> u32 {
69 let addr = AudioObjectPropertyAddress {
70 m_selector: AUDIO_HARDWARE_PROPERTY_DEFAULT_OUTPUT_DEVICE,
71 m_scope: AUDIO_OBJECT_PROPERTY_SCOPE_GLOBAL,
72 m_element: AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN,
73 };
74 let mut device: u32 = 0;
75 let mut size: u32 = std::mem::size_of::<u32>() as u32;
76 let status = unsafe {
80 AudioObjectGetPropertyData(
81 AUDIO_OBJECT_SYSTEM_OBJECT,
82 &addr,
83 0,
84 std::ptr::null(),
85 &mut size,
86 &mut device as *mut u32 as *mut std::ffi::c_void,
87 )
88 };
89 if status == 0 { device } else { 0 }
90 }
91
92 pub fn query_device_property(device: u32, selector: u32, scope: u32) -> std::time::Duration {
94 let addr = AudioObjectPropertyAddress {
95 m_selector: selector,
96 m_scope: scope,
97 m_element: AUDIO_OBJECT_PROPERTY_ELEMENT_MAIN,
98 };
99 let mut data = [0u8; 8];
100 let mut size: u32 = 8;
101
102 let t0 = std::time::Instant::now();
103 unsafe {
107 AudioObjectGetPropertyData(
108 device,
109 &addr,
110 0,
111 std::ptr::null(),
112 &mut size,
113 data.as_mut_ptr() as *mut std::ffi::c_void,
114 );
115 }
116 t0.elapsed()
117 }
118}
119
120impl EntropySource for AudioPLLTimingSource {
121 fn info(&self) -> &SourceInfo {
122 &AUDIO_PLL_TIMING_INFO
123 }
124
125 fn is_available(&self) -> bool {
126 #[cfg(target_os = "macos")]
127 {
128 coreaudio::get_default_output_device() != 0
129 }
130 #[cfg(not(target_os = "macos"))]
131 {
132 false
133 }
134 }
135
136 fn collect(&self, n_samples: usize) -> Vec<u8> {
137 #[cfg(not(target_os = "macos"))]
138 {
139 let _ = n_samples;
140 Vec::new()
141 }
142
143 #[cfg(target_os = "macos")]
144 {
145 let device = coreaudio::get_default_output_device();
146 if device == 0 {
147 return Vec::new();
148 }
149
150 let raw_count = n_samples * 4 + 64;
151 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
152
153 let selectors = [
156 (
157 coreaudio::AUDIO_DEVICE_PROPERTY_ACTUAL_SAMPLE_RATE,
158 coreaudio::AUDIO_OBJECT_PROPERTY_SCOPE_GLOBAL,
159 ),
160 (
161 coreaudio::AUDIO_DEVICE_PROPERTY_LATENCY,
162 coreaudio::AUDIO_DEVICE_PROPERTY_SCOPE_OUTPUT,
163 ),
164 (
165 coreaudio::AUDIO_DEVICE_PROPERTY_NOMINAL_SAMPLE_RATE,
166 coreaudio::AUDIO_OBJECT_PROPERTY_SCOPE_GLOBAL,
167 ),
168 ];
169
170 for i in 0..raw_count {
171 let (sel, scope) = selectors[i % selectors.len()];
172 let elapsed = coreaudio::query_device_property(device, sel, scope);
173 timings.push(elapsed.as_nanos() as u64);
174 }
175
176 extract_timing_entropy(&timings, n_samples)
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn info() {
187 let src = AudioPLLTimingSource;
188 assert_eq!(src.name(), "audio_pll_timing");
189 assert_eq!(src.info().category, SourceCategory::Thermal);
190 assert!(!src.info().composite);
191 }
192
193 #[test]
194 #[cfg(target_os = "macos")]
195 #[ignore] fn collects_bytes() {
197 let src = AudioPLLTimingSource;
198 if src.is_available() {
199 let data = src.collect(64);
200 assert!(!data.is_empty());
201 assert!(data.len() <= 64);
202 }
203 }
204}