1use crate::common::alphabet::{self, Alphabet};
32use crate::common::cipher::Cipher;
33use crate::common::keygen::cyclic_keystream;
34use crate::common::substitute;
35
36#[rustfmt::skip]
37const SUBSTITUTION_TABLE: [[usize; 26]; 13] = [
38 [13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
39 [14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 13, 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
40 [15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 13, 14, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
41 [16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 13, 14, 15, 10, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
42 [17, 18, 19, 20, 21, 22, 23, 24, 25, 13, 14, 15, 16, 9, 10, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7, 8],
43 [18, 19, 20, 21, 22, 23, 24, 25, 13, 14, 15, 16, 17, 8, 9, 10, 11, 12, 0, 1, 2, 3, 4, 5, 6, 7],
44 [19, 20, 21, 22, 23, 24, 25, 13, 14, 15, 16, 17, 18, 7, 8, 9, 10, 11, 12, 0, 1, 2, 3, 4, 5, 6],
45 [20, 21, 22, 23, 24, 25, 13, 14, 15, 16, 17, 18, 19, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2, 3, 4, 5],
46 [21, 22, 23, 24, 25, 13, 14, 15, 16, 17, 18, 19, 20, 5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2, 3, 4],
47 [22, 23, 24, 25, 13, 14, 15, 16, 17, 18, 19, 20, 21, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2, 3],
48 [23, 24, 25, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 1, 2],
49 [24, 25, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 1],
50 [25, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0],
51];
52
53pub struct Porta {
57 key: String,
58}
59
60impl Cipher for Porta {
61 type Key = String;
62 type Algorithm = Porta;
63
64 fn new(key: String) -> Porta {
71 if key.is_empty() {
72 panic!("The key is empty.");
73 }
74 if !alphabet::STANDARD.is_valid(&key) {
75 panic!("The key contains a non-alphabetic symbol.");
76 }
77
78 Porta { key }
79 }
80
81 fn encrypt(&self, message: &str) -> Result<String, &'static str> {
94 Ok(substitute::key_substitution(
95 message,
96 &cyclic_keystream(&self.key, message),
97 |mi, ki| SUBSTITUTION_TABLE[ki / 2][mi],
98 ))
99 }
100
101 fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
114 self.encrypt(ciphertext)
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn encrypt() {
124 let message = "attackatdawn";
125 let porta = Porta::new("lemon".into());
126 assert_eq!(porta.encrypt(message).unwrap(), "seauvppaxtel");
127 }
128
129 #[test]
130 fn decrypt() {
131 let ciphertext = "seauvppaxtel";
132 let porta = Porta::new("lemon".into());
133 assert_eq!(porta.decrypt(ciphertext).unwrap(), "attackatdawn");
134 }
135
136 #[test]
137 fn mixed_case() {
138 let message = "Attack at Dawn!";
139 let porta = Porta::new("lemon".into());
140 let ciphertext = porta.encrypt(message).unwrap();
141 let decrypted = porta.decrypt(&ciphertext).unwrap();
142
143 assert_eq!(decrypted, message);
144 }
145
146 #[test]
147 fn with_utf8() {
148 let message = "Peace 🗡️ Freedom and Liberty!";
149 let porta = Porta::new("utfeightisfun".into());
150 let ciphertext = porta.encrypt(message).unwrap();
151 let decrypted = porta.decrypt(&ciphertext).unwrap();
152
153 assert_eq!(decrypted, message);
154 }
155
156 #[test]
157 fn valid_key() {
158 Porta::new("LeMon".into());
159 }
160
161 #[test]
162 #[should_panic]
163 fn key_with_symbols() {
164 Porta::new("!em@n".into());
165 }
166
167 #[test]
168 #[should_panic]
169 fn key_with_whitespace() {
170 Porta::new("wow this key is a real lemon".into());
171 }
172}