use async_generic::async_generic;
use base16ct::lower::encode as base16_encode;
use crate::hex_string::HexString;
use crate::random::randomized;
use crate::{Error, STORAGE_KEY_LENGTH};
use super::Identity;
use super::storage::{Storage, StorageState};
pub type Ingredients = (
usize,
phf::Map<&'static str, &'static str>,
&'static [&'static str],
&'static [&'static str],
);
pub struct Population<'dom> {
pub domain: &'dom str,
pub secret: &'dom [u8],
pub ingredients: &'static Ingredients,
}
impl<'dom> Population<'dom> {
#[async_generic]
#[allow(unused_assignments)]
pub fn identity(
&self,
identifier: &str,
state: &mut impl StorageState,
) -> Result<Identity<'_>, Error> {
let storage = self.storage_object(identifier);
let mut offset = 0usize;
if _async {
offset = state.digest_offset_async(self.domain, &storage).await?;
} else {
offset = state.digest_offset(self.domain, &storage)?;
}
let friendly_name = self.friendly_name(&storage, offset);
Ok(Identity {
domain: self.domain,
friendly_name,
storage,
})
}
fn storage_object(&self, identifier: &str) -> Storage {
let mut hasher = blake3::Hasher::new_keyed(self.secret[..32].try_into().unwrap());
hasher.update(identifier.as_bytes());
let output = hasher.finalize();
let mut buf = [0; 64];
let bytes = base16_encode(output.as_bytes(), &mut buf).unwrap();
Storage::from(bytes)
}
fn friendly_name(&self, storage: &Storage, digest_offset: usize) -> String {
let (_population_size, prefixes, _colors, _animals) = self.ingredients;
let prefix = prefixes.get(storage.key.as_str()).cloned().unwrap();
let animals = self.color_animals(storage);
let (color, animal) = animals.get(digest_offset).unwrap();
format!("{prefix}-{color}-{animal}")
}
fn color_animals(&self, storage: &Storage) -> Vec<(&str, &str)> {
let (population_size, _prefixes, colors, animals) = self.ingredients;
let required_color_animals = *population_size as u32 / 16u32.pow(STORAGE_KEY_LENGTH as u32);
let colors = self.randomize(colors, storage, false);
let animals_per_color = required_color_animals.div_ceil(colors.len() as u32);
let animals = self
.randomize(animals, storage, true)
.into_iter()
.take(animals_per_color as usize)
.collect::<Vec<_>>();
let mut results = vec![];
for color in colors {
for &animal in &animals {
results.push((color, animal))
}
}
results
}
fn randomize<'a>(&self, words: &'a [&str], storage: &Storage, reverse: bool) -> Vec<&'a str> {
let mut buf = [0; 64];
let pop_seed = base16_encode(&self.secret[..32], &mut buf).unwrap();
let pop_seed: u16 = HexString::<4>::from(&pop_seed[..4]).into();
let store_seed = match STORAGE_KEY_LENGTH {
3 => {
let mut key_bytes = storage.key.as_str().as_bytes().to_vec(); key_bytes.push(b"0"[0]); key_bytes
}
_ => unimplemented!(),
};
let store_seed: u16 = HexString::<4>::from(&store_seed[..4]).into();
let rng_seed = ((pop_seed as u32) << 16) + (store_seed as u32);
let mut rng_seed = ((rng_seed as u64) << 32) + (rng_seed as u64);
if reverse {
rng_seed = rng_seed.reverse_bits();
}
randomized(words, rng_seed)
}
}
#[cfg(test)]
mod tests {
use std::time::Instant;
use super::*;
use crate::identity::{storage::RemoteStore, tests::*};
#[test]
fn test_distinct_names() -> Result<(), Error> {
let test_identity_count: usize = std::env::var_os("IDENTITY_COUNT")
.map(|s| s.into_string().unwrap().parse().unwrap())
.unwrap_or(16);
let brazilian = Population {
domain: "br",
secret: b"0123456789abcdef0123456789abcdef",
ingredients: &PERFUME_INGREDIENTS,
};
let mut store = RemoteStore {
bridge: MockBridge::default(),
};
let start = Instant::now();
let identities: Vec<Identity> = (0..test_identity_count)
.map(|_| {
let ident = random_hex_string::<12>();
brazilian.identity(ident.as_str(), &mut store).unwrap()
})
.collect();
let stop = Instant::now();
println!(
"generated {} identities in {:?}",
identities.len(),
stop - start
);
let mut pairs = vec![];
for i in 0..identities.len() {
for j in 0..identities.len() {
if i < j {
pairs.push((i, j));
}
}
}
for (i1, i2) in &pairs {
assert_ne!(identities[*i1].friendly_name, identities[*i2].friendly_name);
}
println!("compared {} names for uniqueness", pairs.len());
let mut diff_scores: Vec<(u8, (usize, usize))> = vec![];
for (i1, i2) in &pairs {
let name1: Vec<&str> = identities[*i1].friendly_name.split('-').collect();
let name2: Vec<&str> = identities[*i2].friendly_name.split('-').collect();
match (name1.as_slice(), name2.as_slice()) {
([prefix1, color1, animal1], [prefix2, color2, animal2]) => {
let score: [u8; 3] = [
if prefix1 == prefix2 { 0 } else { 1 },
if color1 == color2 { 0 } else { 1 },
if animal1 == animal2 { 0 } else { 1 },
];
diff_scores.push((score.iter().sum(), (*i1, *i2)));
}
_ => unimplemented!(),
}
}
diff_scores.sort_by(|a, b| a.0.cmp(&b.0));
let lowest_diff_score = diff_scores.first().unwrap().0;
let least_diff_pairs: Vec<(u8, (usize, usize))> = diff_scores
.into_iter()
.take_while(|s| s.0 == lowest_diff_score)
.collect();
println!(
"{} differences with {} common components",
least_diff_pairs.len(),
3 - lowest_diff_score
);
let mut least_diff_names = least_diff_pairs
.iter()
.map(|p| {
let name1 = &identities[p.1.0].friendly_name;
let name2 = &identities[p.1.1].friendly_name;
(name1, name2)
})
.collect::<Vec<_>>();
least_diff_names.sort();
println!("most similar names: {least_diff_names:#?}",);
Ok(())
}
}