openentropy_core/sources/frontier/
kqueue_events.rs1#[cfg(any(
4 target_os = "macos",
5 target_os = "freebsd",
6 target_os = "netbsd",
7 target_os = "openbsd"
8))]
9use std::sync::Arc;
10#[cfg(any(
11 target_os = "macos",
12 target_os = "freebsd",
13 target_os = "netbsd",
14 target_os = "openbsd"
15))]
16use std::sync::atomic::{AtomicBool, Ordering};
17#[cfg(any(
18 target_os = "macos",
19 target_os = "freebsd",
20 target_os = "netbsd",
21 target_os = "openbsd"
22))]
23use std::thread;
24
25use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
26#[cfg(any(
27 target_os = "macos",
28 target_os = "freebsd",
29 target_os = "netbsd",
30 target_os = "openbsd"
31))]
32use crate::sources::helpers::extract_timing_entropy;
33#[cfg(any(
34 target_os = "macos",
35 target_os = "freebsd",
36 target_os = "netbsd",
37 target_os = "openbsd"
38))]
39use crate::sources::helpers::mach_time;
40
41#[derive(Debug, Clone)]
54pub struct KqueueEventsConfig {
55 pub num_file_watchers: usize,
63
64 pub num_timers: usize,
72
73 pub num_sockets: usize,
81
82 pub timeout_ms: u32,
90}
91
92impl Default for KqueueEventsConfig {
93 fn default() -> Self {
94 Self {
95 num_file_watchers: 4,
96 num_timers: 8,
97 num_sockets: 4,
98 timeout_ms: 1,
99 }
100 }
101}
102
103#[derive(Default)]
135pub struct KqueueEventsSource {
136 pub config: KqueueEventsConfig,
138}
139
140static KQUEUE_EVENTS_INFO: SourceInfo = SourceInfo {
141 name: "kqueue_events",
142 description: "Kqueue event multiplexing timing from timers, files, and sockets",
143 physics: "Registers diverse kqueue event types (timers, file watchers, socket monitors) \
144 and measures kevent() notification timing. Timer events capture kernel timer \
145 coalescing and interrupt jitter. File watchers exercise VFS/APFS notification \
146 paths. Socket events capture mbuf allocator timing. Multiple simultaneous watchers \
147 create knote lock contention and dispatch queue interference. The combination of \
148 independent event sources produces high min-entropy.",
149 category: SourceCategory::IPC,
150 platform: Platform::MacOS,
151 requirements: &[],
152 entropy_rate_estimate: 2500.0,
153 composite: false,
154};
155
156impl EntropySource for KqueueEventsSource {
157 fn info(&self) -> &SourceInfo {
158 &KQUEUE_EVENTS_INFO
159 }
160
161 fn is_available(&self) -> bool {
162 cfg!(any(
163 target_os = "macos",
164 target_os = "freebsd",
165 target_os = "netbsd",
166 target_os = "openbsd"
167 ))
168 }
169
170 #[cfg(any(
171 target_os = "macos",
172 target_os = "freebsd",
173 target_os = "netbsd",
174 target_os = "openbsd"
175 ))]
176 fn collect(&self, n_samples: usize) -> Vec<u8> {
177 let raw_count = n_samples * 4 + 64;
178 let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
179
180 let kq = unsafe { libc::kqueue() };
182 if kq < 0 {
183 return Vec::new();
184 }
185
186 let mut changes: Vec<libc::kevent> = Vec::new();
187 let mut cleanup_fds: Vec<i32> = Vec::new();
188
189 for i in 0..self.config.num_timers {
191 let interval_ms = 1 + (i % 10);
192 let mut ev: libc::kevent = unsafe { std::mem::zeroed() };
193 ev.ident = i;
194 ev.filter = libc::EVFILT_TIMER;
195 ev.flags = libc::EV_ADD | libc::EV_ENABLE;
196 ev.fflags = 0;
197 ev.data = interval_ms as isize;
198 ev.udata = std::ptr::null_mut();
199 changes.push(ev);
200 }
201
202 for _i in 0..self.config.num_sockets {
204 let mut sv: [i32; 2] = [0; 2];
205 let ret =
206 unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_STREAM, 0, sv.as_mut_ptr()) };
207 if ret == 0 {
208 cleanup_fds.push(sv[0]);
209 cleanup_fds.push(sv[1]);
210
211 let mut ev: libc::kevent = unsafe { std::mem::zeroed() };
212 ev.ident = sv[0] as usize;
213 ev.filter = libc::EVFILT_READ;
214 ev.flags = libc::EV_ADD | libc::EV_ENABLE;
215 ev.udata = std::ptr::null_mut();
216 changes.push(ev);
217
218 let byte = [0xAAu8];
219 unsafe {
220 libc::write(sv[1], byte.as_ptr() as *const _, 1);
221 }
222
223 let mut ev2: libc::kevent = unsafe { std::mem::zeroed() };
224 ev2.ident = sv[1] as usize;
225 ev2.filter = libc::EVFILT_WRITE;
226 ev2.flags = libc::EV_ADD | libc::EV_ENABLE;
227 ev2.udata = std::ptr::null_mut();
228 changes.push(ev2);
229 }
230 }
231
232 let mut temp_files: Vec<(i32, std::path::PathBuf)> = Vec::new();
234 for i in 0..self.config.num_file_watchers {
235 let path = std::env::temp_dir().join(format!("oe_kq_{i}_{}", std::process::id()));
236 if std::fs::write(&path, b"entropy").is_ok() {
237 let path_str = path.to_str().unwrap_or("");
238 let c_path = match std::ffi::CString::new(path_str) {
239 Ok(c) => c,
240 Err(_) => continue, };
242 let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDONLY) };
243 if fd >= 0 {
244 let mut ev: libc::kevent = unsafe { std::mem::zeroed() };
245 ev.ident = fd as usize;
246 ev.filter = libc::EVFILT_VNODE;
247 ev.flags = libc::EV_ADD | libc::EV_ENABLE | libc::EV_CLEAR;
248 ev.fflags = libc::NOTE_WRITE | libc::NOTE_ATTRIB;
249 ev.udata = std::ptr::null_mut();
250 changes.push(ev);
251 temp_files.push((fd, path));
252 }
253 }
254 }
255
256 if !changes.is_empty() {
258 unsafe {
259 libc::kevent(
260 kq,
261 changes.as_ptr(),
262 changes.len() as i32,
263 std::ptr::null_mut(),
264 0,
265 std::ptr::null(),
266 );
267 }
268 }
269
270 let stop = Arc::new(AtomicBool::new(false));
272 let stop2 = stop.clone();
273 let socket_write_fds: Vec<i32> = cleanup_fds.iter().skip(1).step_by(2).copied().collect();
274 let file_paths: Vec<std::path::PathBuf> =
275 temp_files.iter().map(|(_, p)| p.clone()).collect();
276
277 let poker = thread::spawn(move || {
278 let byte = [0xBBu8];
279 while !stop2.load(Ordering::Relaxed) {
280 for &fd in &socket_write_fds {
281 unsafe {
282 libc::write(fd, byte.as_ptr() as *const _, 1);
283 }
284 }
285 for path in &file_paths {
286 let _ = std::fs::write(path, b"poke");
287 }
288 std::thread::sleep(std::time::Duration::from_micros(500));
289 }
290 });
291
292 let timeout = libc::timespec {
294 tv_sec: 0,
295 tv_nsec: self.config.timeout_ms as i64 * 1_000_000,
296 };
297 let mut events: Vec<libc::kevent> =
298 vec![unsafe { std::mem::zeroed() }; changes.len().max(16)];
299
300 let socket_read_fds: Vec<i32> = cleanup_fds.iter().step_by(2).copied().collect();
301
302 for _ in 0..raw_count {
303 let t0 = mach_time();
304
305 let n = unsafe {
306 libc::kevent(
307 kq,
308 std::ptr::null(),
309 0,
310 events.as_mut_ptr(),
311 events.len() as i32,
312 &timeout,
313 )
314 };
315
316 let t1 = mach_time();
317
318 if n > 0 {
320 let mut drain = [0u8; 64];
321 for &fd in &socket_read_fds {
322 unsafe {
323 libc::read(fd, drain.as_mut_ptr() as *mut _, drain.len());
324 }
325 }
326 }
327
328 timings.push(t1.wrapping_sub(t0));
329 }
330
331 stop.store(true, Ordering::Relaxed);
333 let _ = poker.join();
334
335 for (fd, path) in &temp_files {
337 unsafe {
338 libc::close(*fd);
339 }
340 let _ = std::fs::remove_file(path);
341 }
342 for &fd in &cleanup_fds {
343 unsafe {
344 libc::close(fd);
345 }
346 }
347 unsafe {
348 libc::close(kq);
349 }
350
351 extract_timing_entropy(&timings, n_samples)
352 }
353
354 #[cfg(not(any(
355 target_os = "macos",
356 target_os = "freebsd",
357 target_os = "netbsd",
358 target_os = "openbsd"
359 )))]
360 fn collect(&self, _n_samples: usize) -> Vec<u8> {
361 Vec::new()
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368
369 #[test]
370 fn info() {
371 let src = KqueueEventsSource::default();
372 assert_eq!(src.name(), "kqueue_events");
373 assert_eq!(src.info().category, SourceCategory::IPC);
374 assert!(!src.info().composite);
375 }
376
377 #[test]
378 fn default_config() {
379 let config = KqueueEventsConfig::default();
380 assert_eq!(config.num_file_watchers, 4);
381 assert_eq!(config.num_timers, 8);
382 assert_eq!(config.num_sockets, 4);
383 assert_eq!(config.timeout_ms, 1);
384 }
385
386 #[test]
387 fn custom_config() {
388 let src = KqueueEventsSource {
389 config: KqueueEventsConfig {
390 num_file_watchers: 2,
391 num_timers: 4,
392 num_sockets: 2,
393 timeout_ms: 5,
394 },
395 };
396 assert_eq!(src.config.num_timers, 4);
397 }
398
399 #[test]
400 #[ignore] fn collects_bytes() {
402 let src = KqueueEventsSource::default();
403 if src.is_available() {
404 let data = src.collect(64);
405 assert!(!data.is_empty());
406 assert!(data.len() <= 64);
407 }
408 }
409}