use crate::piv::session::PcscSession;
use crate::piv::PivBackend;
use crate::store::constants::OBJECT_ID_ZERO;
use anyhow::Result;
const MAX_SLOTS: usize = 256;
const PROBE_MAX: usize = 4095;
pub fn measure_free_nvm(reader: &str, mgmt_key: &str, verbose: bool) -> Result<usize> {
let mut session = PcscSession::open(reader)?;
let mut empty_slots = Vec::new();
for i in 0..MAX_SLOTS {
let id = OBJECT_ID_ZERO + i as u32;
if session.try_get_data_size(id)?.unwrap_or(0) == 0 {
empty_slots.push(i);
}
}
if verbose {
eprintln!(
" measure_free_nvm: {} empty slots out of {MAX_SLOTS}.",
empty_slots.len()
);
}
let mut total = 0usize;
let mut filled: Vec<usize> = Vec::new();
for &slot in &empty_slots {
let id = OBJECT_ID_ZERO + slot as u32;
let size = dichotomy_fill(&mut session, mgmt_key, id)?;
if verbose {
eprintln!(" slot {slot:3}: {size} bytes");
}
if size == 0 {
if verbose {
eprintln!(" NVM full — stopping early.");
}
break;
}
total += size;
filled.push(slot);
}
for &slot in &filled {
let id = OBJECT_ID_ZERO + slot as u32;
session.authenticate_management_key(mgmt_key)?;
session.try_put_data(id, &[])?;
}
if verbose {
eprintln!(" measure_free_nvm: total free = {total} bytes.");
}
Ok(total)
}
fn dichotomy_fill(session: &mut PcscSession, mgmt_key: &str, id: u32) -> Result<usize> {
let mut lo = 1usize;
let mut hi = PROBE_MAX;
let mut committed = 0usize;
while lo <= hi {
let mid = (lo + hi) / 2;
session.authenticate_management_key(mgmt_key)?;
if session.try_put_data(id, &vec![0xEEu8; mid])? {
committed = mid;
lo = mid + 1;
} else {
if committed > 0 {
session.authenticate_management_key(mgmt_key)?;
session.try_put_data(id, &vec![0xEEu8; committed])?;
}
hi = mid - 1;
}
}
Ok(committed)
}
const YUBIKEY_NVM_BYTES: usize = 51_200;
pub struct NvmUsage {
pub store_bytes: usize,
pub other_bytes: usize,
pub free_bytes: usize,
}
const KNOWN_PIV_OBJECTS: &[u32] = &[
0x5F_C102, 0x5F_C100, 0x5F_C101, 0x5F_C105, 0x5F_C10A, 0x5F_C10B, 0x5F_C10C, 0x5F_C103, 0x5F_C106, 0x5F_C108, 0x5F_C109, 0x5F_C10D, 0x5F_C10E, 0x5F_C10F, 0x5F_C110, 0x5F_C111, 0x5F_C112, 0x5F_C113, 0x5F_C114, 0x5F_C115, 0x5F_C116, 0x5F_C117, 0x5F_C118, 0x5F_C119, 0x5F_C11A, 0x5F_C11B, 0x5F_C11C, 0x5F_C11D, 0x5F_C11E, 0x5F_C11F, 0x5F_C120, 0x5F_C121, ];
pub fn scan_nvm(
reader: &str,
piv: &dyn PivBackend,
store_object_ids: &std::collections::HashSet<u32>,
) -> Result<NvmUsage> {
let mut store_bytes = 0usize;
let mut other_bytes = 0usize;
for i in 0u32..256 {
let id = OBJECT_ID_ZERO + i;
if let Some(size) = piv.object_size(reader, id)? {
if store_object_ids.contains(&id) {
store_bytes += size;
}
}
}
for &id in KNOWN_PIV_OBJECTS {
if let Some(size) = piv.object_size(reader, id)? {
other_bytes += size;
}
}
let used = store_bytes + other_bytes;
let free_bytes = YUBIKEY_NVM_BYTES.saturating_sub(used);
Ok(NvmUsage {
store_bytes,
other_bytes,
free_bytes,
})
}