use std::path::PathBuf;
use serde::{Deserialize, Serialize};
use crate::model_store::sha256_file;
pub const PROVIDER_DLL_NAMES: &[&str] = &["DirectML.dll", "onnxruntime_providers_cuda.dll"];
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LoadedModule {
pub path: String,
pub size_bytes: u64,
pub sha256: Option<String>,
pub reason: Option<String>,
}
impl LoadedModule {
pub fn is_fingerprinted(&self) -> bool {
self.size_bytes > 0
&& self.sha256.as_deref().is_some_and(is_sha256_hex)
&& self.reason.is_none()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrtLinkInfo {
pub base_dir: String,
pub file_name: String,
pub size_bytes: u64,
}
impl OrtLinkInfo {
pub fn from_build_env() -> Option<Self> {
let base_dir = option_env!("RAPID_STRUCTURE_ORT_LINK_DIR")?;
let file_name = option_env!("RAPID_STRUCTURE_ORT_LINK_NAME")?;
let size_bytes: u64 = option_env!("RAPID_STRUCTURE_ORT_LINK_BYTES")?
.parse()
.ok()?;
Some(Self {
base_dir: base_dir.to_string(),
file_name: file_name.to_string(),
size_bytes,
})
}
pub fn locate(&self) -> Result<PathBuf, String> {
let base = PathBuf::from(&self.base_dir);
let entries = std::fs::read_dir(&base).map_err(|error| {
format!(
"read_dir({}) failed: {error}; the ort binary cache recorded at build time is not \
readable",
base.display()
)
})?;
let mut candidates: Vec<PathBuf> = Vec::new();
for entry in entries {
let entry = entry.map_err(|error| format!("read_dir entry failed: {error}"))?;
let candidate = entry.path().join(&self.file_name);
if candidate.is_file() {
candidates.push(candidate);
}
}
if candidates.is_empty() {
return Err(format!(
"no `{}` found under {} ({} entries scanned)",
self.file_name,
base.display(),
std::fs::read_dir(&base).map(|it| it.count()).unwrap_or(0)
));
}
let mut observed: Vec<(PathBuf, u64)> = Vec::new();
for candidate in &candidates {
let size = std::fs::metadata(candidate)
.map(|metadata| metadata.len())
.map_err(|error| format!("metadata({}) failed: {error}", candidate.display()))?;
if size == self.size_bytes {
return Ok(candidate.clone());
}
observed.push((candidate.clone(), size));
}
Err(format!(
"the linked `{}` ({} bytes, recorded at build time) was not found in {}; candidates: \
{}",
self.file_name,
self.size_bytes,
base.display(),
observed
.iter()
.map(|(path, size)| format!("{}={size}", path.display()))
.collect::<Vec<_>>()
.join(", ")
))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProviderDll {
pub name: String,
pub path: String,
pub size_bytes: Option<u64>,
pub loaded: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrtRuntimeFingerprint {
pub api_version: Option<String>,
pub runtime_source: String,
pub runtime_module: LoadedModule,
pub link: Option<OrtLinkInfo>,
pub link_reason: Option<String>,
pub provider_dlls: Vec<ProviderDll>,
pub provider_reason: Option<String>,
}
impl OrtRuntimeFingerprint {
pub fn is_complete(&self) -> bool {
self.api_version
.as_deref()
.is_some_and(|value| !value.trim().is_empty())
&& self.runtime_module.is_fingerprinted()
}
pub fn summary(&self) -> String {
format!(
"api={} source={} path={} bytes={} sha256={}",
self.api_version.as_deref().unwrap_or("<unknown>"),
self.runtime_source,
self.runtime_module.path,
self.runtime_module.size_bytes,
self.runtime_module.sha256.as_deref().unwrap_or("<unknown>")
)
}
}
pub fn is_sha256_hex(value: &str) -> bool {
value.len() == 64 && value.bytes().all(|b| b.is_ascii_hexdigit())
}
pub fn ort_runtime_fingerprint() -> OrtRuntimeFingerprint {
let api_version =
crate::runtime::provider::ort_runtime_version().filter(|value| !value.trim().is_empty());
let loaded_module = module_file_name("onnxruntime.dll").ok().map(|path| {
let module = fingerprint_file(path);
("loaded_module".to_string(), module)
});
let link = OrtLinkInfo::from_build_env();
let (static_module, static_reason) = match link.as_ref() {
Some(info) => match info.locate() {
Ok(path) => (Some(fingerprint_file(path)), None),
Err(reason) => (None, Some(reason)),
},
None => (None, None),
};
let executable = std::env::current_exe().map(fingerprint_file).ok();
let mut link_reason: Option<String> = None;
let (runtime_source, runtime_module) = if let Some((source, module)) = loaded_module {
(source, module)
} else if let Some(module) = static_module {
("static_link".to_string(), module)
} else if let Some(executable) = executable {
link_reason = Some(static_reason.unwrap_or_else(|| {
if link.is_none() {
"no build-time ORT link information was recorded; the runtime is statically \
linked into the executable, so the fingerprint falls back to the executable file"
.to_string()
} else {
"the linked static library could not be located; the fingerprint falls back to the \
executable file, which still identifies the process that linked it"
.to_string()
}
}));
("executable".to_string(), executable)
} else {
(
"unavailable".to_string(),
LoadedModule {
path: String::new(),
size_bytes: 0,
sha256: None,
reason: Some(
"neither a loaded onnxruntime.dll, the linked static library recorded at build \
time, nor the current executable could be resolved"
.to_string(),
),
},
)
};
let (provider_dlls, provider_reason) = collect_provider_dlls();
OrtRuntimeFingerprint {
api_version,
runtime_source,
runtime_module,
link,
link_reason,
provider_dlls,
provider_reason,
}
}
fn fingerprint_file(path: PathBuf) -> LoadedModule {
let display = path.display().to_string();
let metadata = match std::fs::metadata(&path) {
Ok(metadata) => metadata,
Err(error) => {
return LoadedModule {
path: display.clone(),
size_bytes: 0,
sha256: None,
reason: Some(format!("metadata({display}) failed: {error}")),
};
}
};
if !metadata.is_file() {
return LoadedModule {
path: display.clone(),
size_bytes: metadata.len(),
sha256: None,
reason: Some(format!("{display} is not a regular file")),
};
}
let (sha256, reason) = match sha256_file(&path) {
Ok(hash) => (Some(hash), None),
Err(error) => (
None,
Some(format!("sha256_file({display}) failed: {error}")),
),
};
LoadedModule {
path: display,
size_bytes: metadata.len(),
sha256,
reason,
}
}
fn collect_provider_dlls() -> (Vec<ProviderDll>, Option<String>) {
let mut roots: Vec<PathBuf> = Vec::new();
let mut reason: Option<String> = None;
match std::env::current_exe() {
Ok(exe) => {
if let Some(dir) = exe.parent() {
roots.push(dir.to_path_buf());
}
}
Err(error) => reason = Some(format!("current_exe() failed: {error}")),
}
if let Ok(cwd) = std::env::current_dir()
&& !roots.contains(&cwd)
{
roots.push(cwd);
}
let mut found: Vec<ProviderDll> = Vec::new();
for name in PROVIDER_DLL_NAMES {
let loaded = module_file_name(name).is_ok();
let mut on_disk: Option<PathBuf> = None;
for root in &roots {
let candidate = root.join(name);
if candidate.is_file() {
on_disk = Some(candidate);
break;
}
}
let resolved = on_disk.or_else(|| module_file_name(name).ok());
let Some(path) = resolved else {
continue;
};
let size_bytes = std::fs::metadata(&path).ok().map(|metadata| metadata.len());
found.push(ProviderDll {
name: (*name).to_string(),
path: path.display().to_string(),
size_bytes,
loaded,
});
}
found.sort_by(|a, b| a.name.cmp(&b.name));
found.dedup_by(|a, b| a.name == b.name);
(found, reason)
}
fn module_file_name(dll_name: &str) -> Result<PathBuf, String> {
let wide: Vec<u16> = dll_name.encode_utf16().chain(std::iter::once(0)).collect();
let handle = unsafe { GetModuleHandleW(wide.as_ptr()) };
if handle.is_null() {
let code = unsafe { GetLastError() };
return Err(format!(
"GetModuleHandleW(\"{dll_name}\") failed with Win32 error {code}: the module is not \
loaded in this process"
));
}
let mut buffer = vec![0_u16; 32_768];
let written = unsafe {
GetModuleFileNameW(
handle,
buffer.as_mut_ptr(),
u32::try_from(buffer.len()).unwrap_or(u32::MAX),
)
};
if written == 0 {
let code = unsafe { GetLastError() };
return Err(format!(
"GetModuleFileNameW(\"{dll_name}\") failed with Win32 error {code}"
));
}
let written = written as usize;
if written >= buffer.len() {
return Err(format!(
"GetModuleFileNameW(\"{dll_name}\") truncated: the path needs more than {} UTF-16 \
units",
buffer.len()
));
}
let path = String::from_utf16_lossy(&buffer[..written]);
if path.is_empty() {
return Err(format!(
"GetModuleFileNameW(\"{dll_name}\") returned an empty path"
));
}
Ok(PathBuf::from(path))
}
#[link(name = "kernel32")]
unsafe extern "system" {
fn GetModuleHandleW(module_name: *const u16) -> *mut core::ffi::c_void;
fn GetModuleFileNameW(
module: *mut core::ffi::c_void,
file_name: *mut u16,
capacity: u32,
) -> u32;
fn GetLastError() -> u32;
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::{OrtLinkInfo, is_sha256_hex, ort_runtime_fingerprint};
#[test]
fn fingerprint_is_either_complete_or_explains_itself() {
let fingerprint = ort_runtime_fingerprint();
if fingerprint.is_complete() {
assert_fingerprint_fields(&fingerprint);
return;
}
let reason = fingerprint
.runtime_module
.reason
.clone()
.or_else(|| fingerprint.link_reason.clone())
.expect("an incomplete fingerprint must carry a locating reason");
assert!(
!reason.trim().is_empty(),
"the locating reason must not be blank"
);
let missing = fingerprint.api_version.is_none()
|| fingerprint.runtime_module.size_bytes == 0
|| fingerprint.runtime_module.sha256.is_none();
assert!(
missing,
"is_complete() reported false but every field looks present: {fingerprint:?}"
);
eprintln!(
"ONNX Runtime fingerprint is incomplete in this environment (reason: {reason}); \
fields were not fabricated"
);
}
fn assert_fingerprint_fields(fingerprint: &super::OrtRuntimeFingerprint) {
let version = fingerprint
.api_version
.as_deref()
.expect("complete fingerprint has a version");
assert!(
!version.trim().is_empty(),
"the version string must not be blank"
);
assert!(
Path::new(&fingerprint.runtime_module.path).exists(),
"the reported runtime path must exist: {}",
fingerprint.runtime_module.path
);
assert!(
fingerprint.runtime_module.size_bytes > 0,
"the runtime module must not be empty"
);
let sha = fingerprint
.runtime_module
.sha256
.as_deref()
.expect("complete fingerprint has a hash");
assert!(
is_sha256_hex(sha),
"the hash must be 64 hex characters, got {sha:?}"
);
assert!(
!fingerprint.runtime_source.is_empty(),
"the report must say where the runtime identity came from"
);
eprintln!("ONNX Runtime fingerprint: {}", fingerprint.summary());
for dll in &fingerprint.provider_dlls {
eprintln!(
" provider dll: {} bytes={:?} loaded={}",
dll.path, dll.size_bytes, dll.loaded
);
}
}
#[test]
fn fingerprint_serialises_for_reports() {
let fingerprint = ort_runtime_fingerprint();
let value = serde_json::to_value(&fingerprint).expect("fingerprint must serialise");
for field in [
"api_version",
"runtime_source",
"runtime_module",
"link",
"provider_dlls",
] {
assert!(
value.get(field).is_some(),
"the report must carry the `{field}` field"
);
}
let round_trip: super::OrtRuntimeFingerprint =
serde_json::from_value(value).expect("fingerprint must deserialise");
assert_eq!(round_trip, fingerprint);
}
#[test]
fn sha256_hex_detection_is_strict() {
assert!(is_sha256_hex(&"a".repeat(64)));
assert!(is_sha256_hex(&"0123456789abcdef".repeat(4)));
assert!(!is_sha256_hex(&"a".repeat(63)));
assert!(!is_sha256_hex(&"a".repeat(65)));
assert!(!is_sha256_hex(&"z".repeat(64)));
assert!(!is_sha256_hex(""));
}
#[test]
fn build_time_link_information_locates_the_static_library() {
let Some(info) = OrtLinkInfo::from_build_env() else {
panic!(
"no build-time ORT link information was recorded; build.rs must capture \
DEP_ORT_SYS_LINK so the fingerprint can name the linked static library"
);
};
assert!(info.size_bytes > 0, "the linked library must be non-empty");
assert!(
info.file_name.ends_with(".lib"),
"expected a static import library, got {:?}",
info.file_name
);
let path = info.locate().unwrap_or_else(|reason| {
panic!("the linked static library must be locatable: {reason}")
});
let size = std::fs::metadata(&path)
.expect("the located library must be readable")
.len();
assert_eq!(
size, info.size_bytes,
"the located library size must match the build-time record"
);
}
}