use crate::mtp::{ObjectHandle, StorageId};
use std::collections::HashMap;
const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
fn token(wpd_id: &str) -> u64 {
let mut h = FNV_OFFSET;
for b in wpd_id.as_bytes() {
h ^= u64::from(*b);
h = h.wrapping_mul(FNV_PRIME);
}
h | 0x8000_0000_0000_0000
}
#[derive(Debug, Default)]
pub(crate) struct IdMap {
reverse: HashMap<u64, String>,
}
impl IdMap {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn object(&mut self, wpd_id: &str) -> ObjectHandle {
ObjectHandle(self.intern(wpd_id))
}
pub(crate) fn storage(&mut self, wpd_id: &str) -> StorageId {
StorageId(self.intern(wpd_id))
}
pub(crate) fn object_id(&self, handle: ObjectHandle) -> Option<&str> {
self.reverse.get(&handle.0).map(String::as_str)
}
pub(crate) fn storage_id(&self, storage: StorageId) -> Option<&str> {
self.reverse.get(&storage.0).map(String::as_str)
}
fn intern(&mut self, wpd_id: &str) -> u64 {
let t = token(wpd_id);
match self.reverse.get(&t) {
Some(existing) => debug_assert_eq!(existing, wpd_id, "WPD id token collision"),
None => {
self.reverse.insert(t, wpd_id.to_string());
}
}
t
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_is_deterministic_across_calls_and_instances() {
assert_eq!(token("s10001"), token("s10001"));
let mut a = IdMap::new();
let mut b = IdMap::new();
assert_eq!(a.storage("s10001"), b.storage("s10001"));
assert_eq!(a.object("o2C"), b.object("o2C"));
}
#[test]
fn distinct_strings_get_distinct_tokens() {
let ids = [
"DEVICE", "s10001", "s10002", "o2C", "oA", "Download", "DCIM",
];
let mut seen = std::collections::HashSet::new();
for id in ids {
assert!(seen.insert(token(id)), "token collision for {id}");
}
}
#[test]
fn tokens_avoid_the_reserved_sentinels() {
for id in ["DEVICE", "s10001", "o2C", "", "ALL", "0"] {
let t = token(id);
assert_ne!(t, ObjectHandle::ROOT.0, "{id} collided with ROOT");
assert_ne!(t, ObjectHandle::ALL.0, "{id} collided with ALL");
assert_ne!(t, StorageId::ALL.0, "{id} collided with StorageId::ALL");
assert_ne!(t, 0);
assert_ne!(t & 0x8000_0000_0000_0000, 0);
}
}
#[test]
fn round_trips_token_back_to_string() {
let mut map = IdMap::new();
let storage = map.storage("s10001");
let obj = map.object("o2C");
assert_eq!(map.storage_id(storage), Some("s10001"));
assert_eq!(map.object_id(obj), Some("o2C"));
let dev_as_obj = map.object("DEVICE");
let dev_as_storage = map.storage("DEVICE");
assert_eq!(dev_as_obj.0, dev_as_storage.0);
}
#[test]
fn unknown_token_resolves_to_none() {
let map = IdMap::new();
assert_eq!(map.object_id(ObjectHandle(0x8000_0000_dead_beef)), None);
assert_eq!(map.object_id(ObjectHandle::ROOT), None);
assert_eq!(map.storage_id(StorageId::ALL), None);
}
#[test]
fn reinterning_is_idempotent() {
let mut map = IdMap::new();
let a = map.object("o2C");
let b = map.object("o2C");
assert_eq!(a, b);
}
}