use std::hash::{BuildHasher, Hasher, RandomState};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::SystemTime;
static COUNTER: AtomicU64 = AtomicU64::new(0);
pub(crate) struct RandomName {
name: String,
}
impl RandomName {
#[cfg_attr(feature = "uuid", allow(dead_code))]
pub fn new(prefix: &str) -> Self {
let pid = std::process::id();
let entropy = RandomState::new().build_hasher().finish();
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(std::time::Duration::from_secs(0));
let (secs, subsec_nanos) = (now.as_secs(), now.subsec_nanos());
Self {
name: format!("{prefix}{pid:x}{secs:x}{subsec_nanos:x}{entropy:x}{counter:x}"),
}
}
pub fn as_str(&self) -> &str {
self.name.as_str()
}
}
impl AsRef<str> for RandomName {
fn as_ref(&self) -> &str {
self.as_str()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_random_name() {
let first = RandomName::new("test");
let second = RandomName::new("test");
assert!(first.as_str().starts_with("test"));
assert!(second.as_str().starts_with("test"));
assert_ne!(first.as_str(), second.as_str());
}
#[test]
fn names_are_unique_and_prefixed_over_many_iterations() {
use std::collections::HashSet;
const ITERATIONS: usize = 10_000;
let mut seen = HashSet::with_capacity(ITERATIONS);
for _ in 0..ITERATIONS {
let name = RandomName::new("px_");
assert!(
name.as_str().starts_with("px_"),
"missing prefix: {}",
name.as_str()
);
assert!(
seen.insert(name.as_str().to_string()),
"duplicate name generated: {}",
name.as_str()
);
}
}
#[test]
fn separator_bearing_affixes_are_unsafe() {
assert!(crate::affix_is_safe("ok_"));
assert!(crate::affix_is_safe(".log"));
assert!(crate::affix_is_safe("")); assert!(crate::affix_is_safe(".."));
assert!(!crate::affix_is_safe("../"));
assert!(!crate::affix_is_safe("a/b"));
assert!(!crate::affix_is_safe("/etc/passwd"));
#[cfg(windows)]
assert!(!crate::affix_is_safe("a\\b"));
#[cfg(not(windows))]
assert!(crate::affix_is_safe("a\\b"));
}
}