use crate::source::{EntropySource, Platform, SourceCategory, SourceInfo};
#[cfg(target_os = "macos")]
use crate::sources::helpers::extract_timing_entropy;
#[cfg(target_os = "macos")]
use crate::sources::helpers::mach_time;
static SMC_HIGHVAR_TIMING_INFO: SourceInfo = SourceInfo {
name: "smc_highvar_timing",
description: "SMC thermistor ADC + fuel gauge I2C bus — CV=64–66%, 8× outliers",
physics: "Targets two SMC keys with 8× higher CV than all others: TC0P (CPU proximity \
NTC thermistor, analog ADC conversion phase-alignment jitter, mean=4723 ticks, \
CV=63.9%, range=[4059,33893]) and B0RM (battery fuel gauge IC over SMBus/I2C, \
bimodal fast-cache vs slow I2C-timeout, mean=3594, CV=66.0%, \
range=[3226,35826]). On MacBook: B0RM reads live Li-ion electrochemical \
coulomb-counter noise. On Mac mini: I2C bus timeout randomness from SMC \
polling absent battery device. Both keys interleaved to mix analog ADC \
entropy with I2C bus entropy. Standard SMC keys (TCXC, TG0P, PSTR, etc.) \
all show CV=6\u{2013}14% — these two are structural outliers.",
category: SourceCategory::Sensor,
platform: Platform::MacOS,
requirements: &[],
entropy_rate_estimate: 2.5,
composite: false,
is_fast: false,
};
pub struct SMCHighVarTimingSource;
#[cfg(target_os = "macos")]
mod smc_hv {
use std::ffi::c_void;
pub type IOReturn = i32;
pub type MachPort = u32;
#[link(name = "IOKit", kind = "framework")]
unsafe extern "C" {
pub fn IOServiceGetMatchingService(main_port: MachPort, matching: *const c_void) -> u32;
pub fn IOServiceMatching(name: *const i8) -> *mut c_void;
pub fn IOServiceOpen(service: u32, task: u32, kind: u32, connect: *mut u32) -> IOReturn;
pub fn IOConnectCallStructMethod(
connection: u32,
selector: u32,
input: *const c_void,
input_size: usize,
output: *mut c_void,
output_size: *mut usize,
) -> IOReturn;
pub fn IOServiceClose(connect: u32) -> IOReturn;
pub fn IOObjectRelease(obj: u32) -> IOReturn;
}
#[link(name = "c")]
unsafe extern "C" {
pub fn mach_task_self() -> u32;
}
pub const K_IO_MAIN_PORT_DEFAULT: MachPort = 0;
pub const SMC_CMD_READ_BYTES: u8 = 5;
pub fn encode_key(k: &[u8; 4]) -> u32 {
((k[0] as u32) << 24) | ((k[1] as u32) << 16) | ((k[2] as u32) << 8) | (k[3] as u32)
}
#[repr(C)]
pub struct SMCParam {
pub key: u32,
pub vers: [u8; 6],
pub p_limit_data: [u8; 12],
pub key_info: [u8; 9],
pub result: u8,
pub status: u8,
pub data8: u8,
pub data32: u32,
pub bytes: [u8; 32],
}
impl SMCParam {
pub fn new(key: u32) -> Self {
Self {
key,
vers: [0; 6],
p_limit_data: [0; 12],
key_info: [0; 9],
result: 0,
status: 0,
data8: SMC_CMD_READ_BYTES,
data32: 0,
bytes: [0; 32],
}
}
}
}
#[cfg(target_os = "macos")]
impl EntropySource for SMCHighVarTimingSource {
fn info(&self) -> &SourceInfo {
&SMC_HIGHVAR_TIMING_INFO
}
fn is_available(&self) -> bool {
use smc_hv::*;
let svc = unsafe {
IOServiceGetMatchingService(
K_IO_MAIN_PORT_DEFAULT,
IOServiceMatching(c"AppleSMC".as_ptr()),
)
};
if svc != 0 {
unsafe { IOObjectRelease(svc) };
true
} else {
false
}
}
fn collect(&self, n_samples: usize) -> Vec<u8> {
use smc_hv::*;
use std::ffi::c_void;
let svc = unsafe {
IOServiceGetMatchingService(
K_IO_MAIN_PORT_DEFAULT,
IOServiceMatching(c"AppleSMC".as_ptr()),
)
};
if svc == 0 {
return Vec::new();
}
let mut conn: u32 = 0;
let kr = unsafe { IOServiceOpen(svc, mach_task_self(), 0, &mut conn) };
unsafe { IOObjectRelease(svc) };
if kr != 0 {
return Vec::new();
}
let tc0p = encode_key(b"TC0P");
let b0rm = encode_key(b"B0RM");
let raw = n_samples * 3 + 32;
let mut timings = Vec::with_capacity(raw * 2);
for _ in 0..4 {
let inp = SMCParam::new(tc0p);
let mut out = SMCParam::new(tc0p);
let mut out_sz = std::mem::size_of::<SMCParam>();
unsafe {
IOConnectCallStructMethod(
conn,
5,
&inp as *const _ as *const c_void,
std::mem::size_of::<SMCParam>(),
&mut out as *mut _ as *mut c_void,
&mut out_sz,
);
}
let _ = inp.result;
}
for _ in 0..raw {
let mut inp = SMCParam::new(tc0p);
let mut out_tc = SMCParam::new(tc0p);
let mut out_sz = std::mem::size_of::<SMCParam>();
let t0 = mach_time();
unsafe {
IOConnectCallStructMethod(
conn,
5,
&inp as *const _ as *const c_void,
std::mem::size_of::<SMCParam>(),
&mut out_tc as *mut _ as *mut c_void,
&mut out_sz,
);
}
let t_tc = mach_time().wrapping_sub(t0);
inp = SMCParam::new(b0rm);
let mut out_b0 = SMCParam::new(b0rm);
out_sz = std::mem::size_of::<SMCParam>();
let t1 = mach_time();
unsafe {
IOConnectCallStructMethod(
conn,
5,
&inp as *const _ as *const c_void,
std::mem::size_of::<SMCParam>(),
&mut out_b0 as *mut _ as *mut c_void,
&mut out_sz,
);
}
let t_b0 = mach_time().wrapping_sub(t1);
if t_tc < 1_200_000 {
timings.push(t_tc);
}
if t_b0 < 1_200_000 {
timings.push(t_b0);
}
}
unsafe { IOServiceClose(conn) };
extract_timing_entropy(&timings, n_samples)
}
}
#[cfg(not(target_os = "macos"))]
impl EntropySource for SMCHighVarTimingSource {
fn info(&self) -> &SourceInfo {
&SMC_HIGHVAR_TIMING_INFO
}
fn is_available(&self) -> bool {
false
}
fn collect(&self, _: usize) -> Vec<u8> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn info() {
let src = SMCHighVarTimingSource;
assert_eq!(src.info().name, "smc_highvar_timing");
assert!(matches!(src.info().category, SourceCategory::Sensor));
assert_eq!(src.info().platform, Platform::MacOS);
assert!(!src.info().composite);
}
#[test]
#[cfg(target_os = "macos")]
fn is_available_on_macos_with_smc() {
let _ = SMCHighVarTimingSource.is_available(); }
#[test]
#[ignore]
fn collects_bimodal_smc_outliers() {
let data = SMCHighVarTimingSource.collect(32);
assert!(!data.is_empty());
}
}