use crate::source::{EntropySource, Platform, Requirement, SourceCategory, SourceInfo};
#[cfg(target_os = "macos")]
use crate::sources::helpers::extract_timing_entropy;
static NVME_IOKIT_SENSORS_INFO: SourceInfo = SourceInfo {
name: "nvme_iokit_sensors",
description: "NVMe controller sensor polling via IOKit with CNTVCT clock domain crossing timestamps",
physics: "Reads NVMe controller properties (temperature, SMART counters) via IOKit C API, \
forcing clock domain crossings between the CPU\u{2019}s 24 MHz crystal and the \
NVMe controller\u{2019}s independent PLL (Apple ANS2/ANS3). CNTVCT_EL0 timestamps \
before/after each IOKit call capture the beat between CPU and NVMe clock domains. \
Entropy arises from PLL thermal noise (VCO Johnson-Nyquist), IOKit kernel path \
traversal variance, and SMART counter deltas between consecutive polls.",
category: SourceCategory::IO,
platform: Platform::MacOS,
requirements: &[Requirement::AppleSilicon, Requirement::IOKit],
entropy_rate_estimate: 3.0,
composite: false,
is_fast: true,
};
pub struct NvmeIokitSensorsSource;
#[cfg(target_os = "macos")]
mod iokit {
use crate::sources::helpers::read_cntvct;
use std::ffi::{CString, c_char, c_void};
type IOReturn = i32;
#[allow(non_camel_case_types)]
type mach_port_t = u32;
#[allow(non_camel_case_types)]
type io_iterator_t = u32;
#[allow(non_camel_case_types)]
type io_object_t = u32;
#[allow(non_camel_case_types)]
type io_registry_entry_t = u32;
type CFTypeRef = *const c_void;
type CFStringRef = *const c_void;
type CFAllocatorRef = *const c_void;
type CFMutableDictionaryRef = *mut c_void;
type CFDictionaryRef = *const c_void;
const K_IO_MAIN_PORT_DEFAULT: mach_port_t = 0;
const K_CF_ALLOCATOR_DEFAULT: CFAllocatorRef = std::ptr::null();
const K_CF_STRING_ENCODING_UTF8: u32 = 0x08000100;
#[link(name = "IOKit", kind = "framework")]
#[allow(clashing_extern_declarations)]
unsafe extern "C" {
fn IOServiceMatching(name: *const c_char) -> CFMutableDictionaryRef;
fn IOServiceGetMatchingServices(
main_port: mach_port_t,
matching: CFDictionaryRef,
existing: *mut io_iterator_t,
) -> IOReturn;
fn IOIteratorNext(iterator: io_iterator_t) -> io_object_t;
fn IORegistryEntryCreateCFProperties(
entry: io_registry_entry_t,
properties: *mut CFMutableDictionaryRef,
allocator: CFAllocatorRef,
options: u32,
) -> IOReturn;
fn IORegistryEntryCreateCFProperty(
entry: io_registry_entry_t,
key: CFStringRef,
allocator: CFAllocatorRef,
options: u32,
) -> CFTypeRef;
fn IOObjectRelease(object: io_object_t) -> IOReturn;
}
#[link(name = "CoreFoundation", kind = "framework")]
unsafe extern "C" {
fn CFRelease(cf: CFTypeRef);
fn CFDictionaryGetCount(dict: CFDictionaryRef) -> isize;
fn CFStringCreateWithCString(
alloc: CFAllocatorRef,
c_str: *const c_char,
encoding: u32,
) -> CFStringRef;
}
const NVME_SERVICE_CLASSES: &[&str] = &[
"AppleANS3CGv2Controller",
"AppleANS2Controller",
"IONVMeController",
"AppleANS3NVMeController",
];
const NVME_PROPERTY_KEYS: &[&str] = &[
"Temperature",
"Data Units Read",
"Data Units Written",
"Host Read Commands",
"Host Write Commands",
"Media and Data Integrity Errors",
"Power On Hours",
];
pub fn probe_nvme_service(class_name: &str) -> u64 {
let c_name = match CString::new(class_name) {
Ok(s) => s,
Err(_) => return 0,
};
let counter_before = read_cntvct();
let matching = unsafe { IOServiceMatching(c_name.as_ptr()) };
if matching.is_null() {
return read_cntvct().wrapping_sub(counter_before);
}
let mut iterator: io_iterator_t = 0;
let kr = unsafe {
IOServiceGetMatchingServices(K_IO_MAIN_PORT_DEFAULT, matching, &mut iterator)
};
if kr != 0 {
return read_cntvct().wrapping_sub(counter_before);
}
let service = unsafe { IOIteratorNext(iterator) };
if service != 0 {
let mut props: CFMutableDictionaryRef = std::ptr::null_mut();
let kr = unsafe {
IORegistryEntryCreateCFProperties(service, &mut props, K_CF_ALLOCATOR_DEFAULT, 0)
};
if kr == 0 && !props.is_null() {
let count = unsafe { CFDictionaryGetCount(props as CFDictionaryRef) };
std::hint::black_box(count);
unsafe { CFRelease(props as CFTypeRef) };
}
for key_name in NVME_PROPERTY_KEYS {
let c_key = match CString::new(*key_name) {
Ok(s) => s,
Err(_) => continue,
};
unsafe {
let cf_key = CFStringCreateWithCString(
K_CF_ALLOCATOR_DEFAULT,
c_key.as_ptr(),
K_CF_STRING_ENCODING_UTF8,
);
if !cf_key.is_null() {
let val = IORegistryEntryCreateCFProperty(
service,
cf_key,
K_CF_ALLOCATOR_DEFAULT,
0,
);
std::hint::black_box(val);
if !val.is_null() {
CFRelease(val);
}
CFRelease(cf_key);
}
}
}
unsafe {
IOObjectRelease(service);
}
}
unsafe {
IOObjectRelease(iterator);
}
read_cntvct().wrapping_sub(counter_before)
}
pub fn has_nvme_services() -> bool {
for class in NVME_SERVICE_CLASSES {
let c_name = match CString::new(*class) {
Ok(s) => s,
Err(_) => continue,
};
unsafe {
let matching = IOServiceMatching(c_name.as_ptr());
if matching.is_null() {
continue;
}
let mut iter: io_iterator_t = 0;
let kr = IOServiceGetMatchingServices(K_IO_MAIN_PORT_DEFAULT, matching, &mut iter);
if kr == 0 {
let svc = IOIteratorNext(iter);
IOObjectRelease(iter);
if svc != 0 {
IOObjectRelease(svc);
return true;
}
}
}
}
false
}
pub fn service_classes() -> &'static [&'static str] {
NVME_SERVICE_CLASSES
}
}
impl EntropySource for NvmeIokitSensorsSource {
fn info(&self) -> &SourceInfo {
&NVME_IOKIT_SENSORS_INFO
}
fn is_available(&self) -> bool {
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
iokit::has_nvme_services()
}
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
false
}
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
#[cfg(not(all(target_os = "macos", target_arch = "aarch64")))]
{
let _ = n_samples;
Vec::new()
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
let classes = iokit::service_classes();
if classes.is_empty() {
return Vec::new();
}
let raw_count = n_samples * 4 + 64;
let mut timings: Vec<u64> = Vec::with_capacity(raw_count);
for i in 0..raw_count {
let class = classes[i % classes.len()];
let duration = iokit::probe_nvme_service(class);
timings.push(duration);
}
extract_timing_entropy(&timings, n_samples)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = NvmeIokitSensorsSource;
assert_eq!(src.name(), "nvme_iokit_sensors");
assert_eq!(src.info().category, SourceCategory::IO);
assert!(!src.info().composite);
}
#[test]
fn physics_mentions_nvme() {
let src = NvmeIokitSensorsSource;
assert!(src.info().physics.contains("NVMe"));
assert!(src.info().physics.contains("CNTVCT_EL0"));
assert!(src.info().physics.contains("SMART"));
}
#[test]
#[ignore] fn collects_bytes() {
let src = NvmeIokitSensorsSource;
if src.is_available() {
let data = src.collect(64);
assert!(!data.is_empty());
assert!(data.len() <= 64);
}
}
}