use bitvec::prelude::*;
use const_str::{split, strip_suffix, unwrap};
use std::net::Ipv4Addr;
macro_rules! wordlist {
($file:expr) => {
split!(unwrap!(strip_suffix!(include_str!($file), "\n")), "\n")
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ip3(String, String, String);
impl std::fmt::Display for Ip3 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.0, self.1, self.2)
}
}
impl From<(String, String, String)> for Ip3 {
fn from(words: (String, String, String)) -> Self {
Self(words.0, words.1, words.2)
}
}
impl From<(&str, &str, &str)> for Ip3 {
fn from(words: (&str, &str, &str)) -> Self {
Self(
words.0.to_string(),
words.1.to_string(),
words.2.to_string(),
)
}
}
impl From<Ip3> for String {
fn from(ip: Ip3) -> Self {
format!("{}", ip)
}
}
impl From<String> for Ip3 {
fn from(ip: String) -> Self {
let mut ip = ip.split('.');
let word1 = ip.next().unwrap();
let word2 = ip.next().unwrap();
let word3 = ip.next().unwrap();
Self(word1.to_string(), word2.to_string(), word3.to_string())
}
}
impl From<Ipv4Addr> for Ip3 {
fn from(ip: Ipv4Addr) -> Self {
ipv4_to_ip3(ip)
}
}
impl From<Ip3> for Ipv4Addr {
fn from(ip: Ip3) -> Self {
ip3_to_ipv4(&ip)
}
}
pub const WORDLIST_EN: [&str; 2048] = wordlist!("../wordlists/english.txt");
#[cfg(feature = "en")]
pub const WORDLIST: [&str; 2048] = WORDLIST_EN;
pub fn word_to_bytes(word: &str) -> BitVec {
let index_in_wordlist = WORDLIST
.iter()
.position(|&x| x == word)
.expect("Word not found in wordlist");
index_in_wordlist.view_bits::<Lsb0>()[..11].to_bitvec()
}
pub fn in_word_list(word: &str) -> bool {
WORDLIST.contains(&word)
}
pub fn ip3_to_ipv4(words: &Ip3) -> Ipv4Addr {
assert!(in_word_list(&words.0), "{} not in wordlist", words.0);
assert!(in_word_list(&words.1), "{} not in wordlist", words.1);
assert!(in_word_list(&words.2), "{} not in wordlist", words.2);
let word1_bytes = word_to_bytes(&words.0);
let word2_bytes = word_to_bytes(&words.1);
let word3_bytes = word_to_bytes(&words.2);
let mut bytes = bitvec![u64,Lsb0;];
bytes.extend(&word1_bytes);
bytes.extend(&word2_bytes);
bytes.extend(&word3_bytes);
bytes.pop();
let ip_int: u32 = bytes.load();
Ipv4Addr::from(ip_int)
}
pub fn ipv4_to_ip3(ip: Ipv4Addr) -> Ip3 {
let ip_int = u32::from(ip);
let bytes = ip_int.view_bits::<Lsb0>();
let mut bytes = bytes[..32].to_bitvec();
let word1_bytes = bytes.drain(..11).collect::<BitVec>();
let word2_bytes = bytes.drain(..11).collect::<BitVec>();
let word3_bytes = bytes.drain(..10).collect::<BitVec>();
let word1: &str = WORDLIST[word1_bytes.load::<usize>()];
let word2: &str = WORDLIST[word2_bytes.load::<usize>()];
let word3: &str = WORDLIST[word3_bytes.load::<usize>()];
(word1.to_string(), word2.to_string(), word3.to_string()).into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_word_to_bytes() {
assert_eq!(word_to_bytes("abandon").as_raw_slice(), [0]);
assert_eq!(word_to_bytes("zoo").as_raw_slice(), [2047]);
}
#[test]
fn test_words_to_ip() {
assert_eq!(
ip3_to_ipv4(&("abandon", "abandon", "abandon").into()),
Ipv4Addr::new(0, 0, 0, 0)
);
assert_eq!(
ip3_to_ipv4(&("ability", "abandon", "display").into()),
Ipv4Addr::new(127, 0, 0, 1)
);
assert_eq!(
ip3_to_ipv4(&("cage", "advice", "above").into()),
Ipv4Addr::new(1, 1, 1, 1)
)
}
#[test]
fn test_ip_to_words() {
assert_eq!(
ipv4_to_ip3(Ipv4Addr::new(0, 0, 0, 0)),
("abandon", "abandon", "abandon").into()
);
assert_eq!(
ipv4_to_ip3(Ipv4Addr::new(127, 0, 0, 1)),
("ability", "abandon", "display").into()
);
assert_eq!(
ipv4_to_ip3(Ipv4Addr::new(1, 1, 1, 1)),
("cage", "advice", "above").into()
)
}
}