cipher_utils/
alphabet.rs

1use std::ops::RangeBounds;
2
3#[derive(PartialEq, Eq, Hash, Clone, Debug)]
4pub struct Alphabet {
5    characters: Vec<char>,
6    cased: bool,
7}
8
9impl Default for Alphabet {
10    fn default() -> Self {
11        Self::caseless("ABCDEFGHIJKLMNOPQRSTUVWXYZ").unwrap()
12    }
13}
14
15impl Alphabet {
16    pub fn cased(alphabet: &str) -> anyhow::Result<Self> {
17        let mut chars = alphabet.chars().collect::<Vec<_>>();
18        chars.dedup();
19        if chars.len() != alphabet.len() {
20            anyhow::bail!("Duplicate letter in alphabet: {alphabet}");
21        }
22
23        if alphabet.len() != 26 {
24            anyhow::bail!("Invalid alphabet length: {alphabet}");
25        }
26
27        if alphabet.chars().any(|letter| !letter.is_alphabetic()) {
28            anyhow::bail!("Invalid character found in alphabet: {alphabet}");
29        }
30
31        Ok(Self { characters: chars, cased: true })
32    }
33
34    pub fn caseless(alphabet: &str) -> anyhow::Result<Self> {
35        let alphabet = alphabet.to_uppercase();
36        let mut chars = alphabet.chars().collect::<Vec<_>>();
37        chars.dedup();
38        if chars.len() != alphabet.len() {
39            anyhow::bail!("Duplicate letter in alphabet: {alphabet}");
40        }
41
42        if alphabet.len() != 26 {
43            anyhow::bail!("Invalid alphabet length: {alphabet}");
44        }
45
46        if alphabet.chars().any(|letter| !letter.is_alphabetic()) {
47            anyhow::bail!("Invalid character found in alphabet: {alphabet}");
48        }
49
50        Ok(Self { characters: chars, cased: false })
51    }
52
53    /// Generates an alphabet from a string of text. The created alphabet represents the unique characters
54    /// of the given text in the order they appear.
55    ///
56    /// This is equivalent to calling `text.alphabet()` from the `Analyze` trait.
57    ///
58    /// # Parameters
59    /// - `text` - The text to get the alphabet of.
60    ///
61    /// # Returns
62    /// The generated alphabet
63    ///
64    /// # Performance
65    /// This is `O(n)` for a given text of length `n`.
66    pub fn of_cased(text: &str) -> Self {
67        let mut characters = Vec::new();
68        for character in text.chars() {
69            if !characters.contains(&character) {
70                characters.push(character);
71            }
72        }
73        Self { characters, cased: true }
74    }
75
76    pub fn from_ascii_range<R: RangeBounds<u8> + IntoIterator<Item = u8>>(range: R) -> anyhow::Result<Self> {
77        if range.end_bound() != std::ops::Bound::Included(&127) && range.end_bound() != std::ops::Bound::Excluded(&128) {
78            anyhow::bail!("Error creating alphabet from ASCII range: Upper bound must be at most 127.");
79        }
80
81        let mut characters = Vec::new();
82        for code in range {
83            characters.push(code as char);
84        }
85
86        Ok(Self { characters, cased: true })
87    }
88
89    /// Returns the expected index of coincidence for a truly random string of characters of
90    /// this alphabet with infinite length.
91    ///
92    /// # Returns
93    /// The index of coincidence
94    pub fn random_index_of_coincidence(&self) -> f64 {
95        1f64 / self.characters.len() as f64
96    }
97
98    pub fn characters(&self) -> &[char] {
99        &self.characters
100    }
101
102    pub fn index_of(&self, mut character: char) -> Option<AlphabetIndex> {
103        if !self.cased {
104            character = character.to_ascii_uppercase();
105        }
106        self.characters.iter().position(|char| char == &character).map(|index| AlphabetIndex(index as u8 + 1))
107    }
108
109    pub fn letter_at(&self, index: AlphabetIndex) -> &char {
110        self.characters.get(*(index - 1) as usize).unwrap()
111    }
112
113    pub fn union(&self, other: &Self) -> Self {
114        Alphabet::of_cased(&self.characters.iter().chain(other.characters.iter()).collect::<String>())
115    }
116
117    pub fn shift(&self, shift: u8) -> Self {
118        let mut characters = String::new();
119        for index in 1..=26 {
120            let alphabet_index = AlphabetIndex::new(index).unwrap();
121            characters.push(*self.letter_at(alphabet_index + shift));
122        }
123        Alphabet::caseless(&characters).unwrap()
124    }
125}
126
127lazy_static::lazy_static! {
128    pub static ref LOWERCASE_LETTERS: Alphabet = Alphabet::of_cased("abcdefghijklmnopqrstuvwxyz");
129    pub static ref CAPITAL_LETTERS: Alphabet = Alphabet::of_cased("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
130    pub static ref LETTERS: Alphabet = CAPITAL_LETTERS.union(&LOWERCASE_LETTERS);
131    pub static ref NUMBERS: Alphabet = Alphabet::of_cased("1234567890");
132    pub static ref LETTERS_AND_NUMBERS: Alphabet = LETTERS.union(&NUMBERS);
133    pub static ref BASE_64: Alphabet = LETTERS_AND_NUMBERS.union(&Alphabet::of_cased("+/"));
134    pub static ref ASCII: Alphabet = Alphabet::from_ascii_range(0..128).unwrap();
135}
136
137/// A wrapper around a `u8` that denotes a valid "alphabet index"; That is, a number that's always in `[1, 26]`.
138/// `AlphabetIndex` provides safety by performing bounds checks upon creation and conciseness by allowing addition
139/// and subtraction to be performed mod 26 with operator overloading.
140#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
141pub struct AlphabetIndex(u8);
142
143impl AlphabetIndex {
144    pub fn new(index: u8) -> anyhow::Result<Self> {
145        if !(1..=26).contains(&index) {
146            anyhow::bail!("Alphabet index out of range: {index}")
147        }
148
149        Ok(Self(index))
150    }
151}
152
153impl std::ops::Deref for AlphabetIndex {
154    type Target = u8;
155
156    fn deref(&self) -> &Self::Target {
157        &self.0
158    }
159}
160
161impl std::ops::AddAssign<i32> for AlphabetIndex {
162    fn add_assign(&mut self, rhs: i32) {
163        *self = AlphabetIndex((self.0 + rhs as u8) % 26)
164    }
165}
166
167impl std::ops::Add<AlphabetIndex> for AlphabetIndex {
168    type Output = AlphabetIndex;
169
170    fn add(self, rhs: AlphabetIndex) -> Self::Output {
171        AlphabetIndex((self.0 + rhs.0) % 26)
172    }
173}
174
175impl std::ops::Add<u32> for AlphabetIndex {
176    type Output = AlphabetIndex;
177
178    fn add(self, rhs: u32) -> Self::Output {
179        AlphabetIndex((self.0 + rhs as u8) % 26)
180    }
181}
182
183impl std::ops::Add<u8> for AlphabetIndex {
184    type Output = AlphabetIndex;
185
186    fn add(self, rhs: u8) -> Self::Output {
187        AlphabetIndex((self.0 + rhs) % 26)
188    }
189}
190
191impl std::ops::Add<i32> for AlphabetIndex {
192    type Output = AlphabetIndex;
193
194    fn add(self, rhs: i32) -> Self::Output {
195        AlphabetIndex((self.0 + rhs as u8) % 26)
196    }
197}
198
199impl std::ops::Sub<AlphabetIndex> for AlphabetIndex {
200    type Output = AlphabetIndex;
201
202    fn sub(self, rhs: AlphabetIndex) -> Self::Output {
203        AlphabetIndex(((self.0 as i32 - rhs.0 as i32 + 26) % 26) as u8)
204    }
205}
206
207impl std::ops::Sub<u32> for AlphabetIndex {
208    type Output = AlphabetIndex;
209
210    fn sub(self, rhs: u32) -> Self::Output {
211        AlphabetIndex(((self.0 as i32 - rhs as i32 + 26) % 26) as u8)
212    }
213}