kryptos/ciphers/
substitution.rs

1use common::ALPHABET;
2
3/// Substitution Cipher
4///
5/// The struct is generated through the new() function.
6///
7pub struct Substitution {
8    key: &'static str,
9}
10
11impl Substitution {
12    /// Initializes a substitution cipher with a supplied substitute alphabet.
13    ///
14    /// # Examples
15    ///
16    /// ```
17    /// use kryptos::ciphers::substitution::Substitution;
18    ///
19    /// let s = Substitution::new("RBQIZDJCFMELOPAVNHYGWKTUXS").unwrap();
20    /// ```
21    ///
22    /// # Errors
23    ///
24    /// Will return Err() if the alphabet is not 26 unique alphabetic characters.
25    ///
26    pub fn new(key: &'static str) -> Result<Self, String> {
27        if key.chars().count() != 26 {
28            return Err(String::from("Key is not the correct length"));
29        }
30
31        let mut alphabet_check = String::new();
32        for c in key.chars() {
33            if c.is_alphabetic() {
34                if alphabet_check.contains(c) {
35                    return Err(String::from("Key alphabet must be unique"));
36                }
37
38                alphabet_check.push(c);
39                continue;
40            } else {
41                return Err(String::from("Key must be alphabetic"));
42            }
43        }
44
45        Ok(Substitution { key })
46    }
47
48    /// Enciphers a message with a substitution cipher.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// use kryptos::ciphers::substitution::*;
54    ///
55    /// let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
56    /// assert_eq!(
57    ///     "Ye weg fbpq cqkdqh iqccntqc",
58    ///     s.encipher("Do you like secret messages").unwrap()
59    /// );
60    /// ```
61    ///
62    /// # Panics
63    ///
64    /// Will panic if there is a missing character or invalid character.
65    ///
66    pub fn encipher(&self, plaintext: &str) -> Result<String, &'static str> {
67        Ok(plaintext
68            .chars()
69            .map(|c| match c as u8 {
70                65..=90 => {
71                    let index = ALPHABET.chars().position(|i| i == c).unwrap();
72                    self.key
73                        .chars()
74                        .nth(index)
75                        .expect("Something is wrong with the key alphabet")
76                }
77                97..=122 => {
78                    let index = ALPHABET
79                        .chars()
80                        .position(|i| i == char::from(c as u8 - 97 + 65))
81                        .unwrap();
82                    (self
83                        .key
84                        .chars()
85                        .nth(index)
86                        .expect("Something is wrong with the key alphabet")
87                        as u8
88                        - 65
89                        + 97) as char
90                }
91                _ => c,
92            })
93            .collect::<String>())
94    }
95
96    /// Deciphers a message with a substitution cipher.
97    ///
98    /// # Examples
99    ///
100    /// ```
101    /// use kryptos::ciphers::substitution::*;
102    ///
103    /// let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
104    /// assert_eq!(
105    ///     "Do you like secret messages",
106    ///     s.decipher("Ye weg fbpq cqkdqh iqccntqc").unwrap()
107    /// );
108    /// ```
109    ///
110    /// # Panics
111    ///
112    /// Will panic if there is a missing character or invalid character.
113    ///
114    pub fn decipher(&self, plaintext: &str) -> Result<String, &'static str> {
115        Ok(plaintext
116            .chars()
117            .map(|c| match c as u8 {
118                65..=90 => {
119                    let index = self.key.chars().position(|i| i == c).unwrap();
120                    ALPHABET
121                        .chars()
122                        .nth(index)
123                        .expect("Something is wrong with the key alphabet")
124                }
125                97..=122 => {
126                    let index = self
127                        .key
128                        .chars()
129                        .position(|i| i == char::from(c as u8 - 97 + 65))
130                        .unwrap();
131                    (ALPHABET
132                        .chars()
133                        .nth(index)
134                        .expect("Something is wrong with the key alphabet")
135                        as u8
136                        - 65
137                        + 97) as char
138                }
139                _ => c,
140            })
141            .collect::<String>())
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::Substitution;
148
149    #[test]
150    fn unique_alphabet() {
151        assert!(Substitution::new("RBQIZDJCFMELOPAVNHYGWKTUXS").is_ok());
152    }
153
154    #[test]
155    fn non_unique_alphabet() {
156        assert!(Substitution::new("ABCDEFGHIJKLMNOPQRSTUVWXAZ").is_err());
157    }
158
159    #[test]
160    fn non_alphabetic() {
161        assert!(Substitution::new("RBQI1DJCFMELOPAVNHYGWKTUXS").is_err());
162    }
163
164    #[test]
165    fn too_small_alphabet() {
166        assert!(Substitution::new("ABC").is_err());
167    }
168
169    #[test]
170    fn too_large_alphabet() {
171        assert!(Substitution::new("ABCDEFGHIJKLMNOPQRSTUVWXYZA").is_err());
172    }
173
174    #[test]
175    fn encipher() {
176        let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
177        assert_eq!(
178            "Ye weg fbpq cqkdqh iqccntqc",
179            s.encipher("Do you like secret messages").unwrap()
180        );
181    }
182
183    #[test]
184    fn with_punctuation() {
185        let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
186        assert_eq!(
187            "Ye weg fbpq cqkdqh iqccntqc?",
188            s.encipher("Do you like secret messages?").unwrap()
189        );
190    }
191
192    #[test]
193    fn with_unicode() {
194        let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
195        assert_eq!(
196            "Ye weg fbpq cqkdqh 🖤 iqccntqc",
197            s.encipher("Do you like secret 🖤 messages").unwrap()
198        );
199    }
200
201    #[test]
202    fn decipher() {
203        let s = Substitution::new("NAKYQRTXBZPFIVEJSDCHGOUMWL").unwrap();
204        assert_eq!(
205            "Do you like secret messages",
206            s.decipher("Ye weg fbpq cqkdqh iqccntqc").unwrap()
207        );
208    }
209}