angry_purple_tiger/
lib.rs

1use std::{fmt, str::FromStr};
2mod words;
3
4#[derive(Debug, PartialEq)]
5pub struct AnimalName {
6    adjective: &'static str,
7    color: &'static str,
8    animal: &'static str,
9}
10
11impl fmt::Display for AnimalName {
12    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
13        f.write_fmt(format_args!(
14            "{}-{}-{}",
15            self.adjective, self.color, self.animal
16        ))
17    }
18}
19
20impl PartialEq<&str> for AnimalName {
21    fn eq(&self, other: &&str) -> bool {
22        let mut split = (*other).split('-');
23        for entry in &[self.adjective, self.color, self.animal] {
24            // if we don't get a split fragment or the fragment isn't the entry
25            // we're not equal
26            if split.next().map_or_else(|| true, |s| &s != entry) {
27                return false;
28            }
29        }
30        true
31    }
32}
33
34fn str_to_animal_name(s: &str) -> AnimalName {
35    let digest = hex_digest(s);
36    AnimalName {
37        adjective: words::ADJECTIVES[digest[0]],
38        color: words::COLORS[digest[1]],
39        animal: words::ANIMALS[digest[2]],
40    }
41}
42
43impl FromStr for AnimalName {
44    type Err = std::convert::Infallible;
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        Ok(str_to_animal_name(s))
47    }
48}
49#[cfg(feature = "helium_crypto")]
50impl From<helium_crypto::PublicKey> for AnimalName {
51    fn from(pubkey: helium_crypto::PublicKey) -> Self {
52        str_to_animal_name(&pubkey.to_string())
53    }
54}
55
56fn hex_digest(s: &str) -> [usize; 3] {
57    let digest = md5::compute(s);
58    let mut result = [0usize; 3];
59    compress(digest.0.len() / 3, &digest.0, &mut result);
60    result
61}
62
63fn compress(size: usize, bytes: &[u8], dest: &mut [usize]) {
64    if bytes.len() >= (2 * size) {
65        dest[0] = bytes[0..size].iter().fold(0u8, |acc, b| acc ^ b) as usize;
66        compress(size, &bytes[size..], &mut dest[1..])
67    } else {
68        dest[0] = bytes.iter().fold(0u8, |acc, b| acc ^ b) as usize;
69    }
70}
71
72#[cfg(test)]
73mod test {
74    use super::AnimalName;
75
76    #[test]
77    fn basic() {
78        let known = "112CuoXo7WCcp6GGwDNBo6H5nKXGH45UNJ39iEefdv2mwmnwdFt8";
79        let animal_name = known.parse::<AnimalName>().expect("animal name");
80        assert_eq!(animal_name, "feisty-glass-dalmatian")
81    }
82
83    #[test]
84    #[cfg(feature = "helium-crypto")]
85    fn from_public_key() {
86        use std::str::FromStr;
87        let known = helium_crypto::PublicKey::from_str(
88            "112CuoXo7WCcp6GGwDNBo6H5nKXGH45UNJ39iEefdv2mwmnwdFt8",
89        )
90        .expect("public key");
91        let animal_name: AnimalName = known.into();
92        assert_eq!(animal_name, "feisty-glass-dalmatian")
93    }
94}