nrot/
lib.rs

1/// rot mode
2#[derive(Copy, Clone)]
3pub enum Mode {
4    /// shift each letter n places to the right
5    Encrypt,
6    /// shift each letter n places to the left
7    Decrypt,
8}
9
10/// Get the value of 'a'
11///
12/// Wheter it use 'A' or 'a' as initial value
13/// to preserve the case.
14///
15fn get_first_alphabet_position(letter: u8) -> u8 {
16    // preserve case
17    if letter.is_ascii_uppercase() {
18        b'A'
19    } else {
20        b'a'
21    }
22}
23
24/// Get the index number from english alphabet order
25///
26fn get_letter_position(letter: u8) -> u8 {
27    let a_position = get_first_alphabet_position(letter);
28    letter - a_position
29}
30
31/// Transform single letter to ROT
32///
33/// The letter after rotated/shifted by n from its initial
34/// value
35///
36/// ```
37/// use nrot::{rot_letter, Mode};
38/// let rotation = 13;
39///
40/// let input = b'a';
41/// let result = b'n';
42/// let encrypted = rot_letter(Mode::Encrypt, input, rotation);
43/// assert_eq!(result, encrypted);
44
45/// let input = b'n';
46/// let result = b'a';
47/// let encrypted = rot_letter(Mode::Decrypt, input, rotation);
48/// assert_eq!(result, encrypted);
49/// ```
50pub fn rot_letter(mode: Mode, letter: u8, rotation: u8) -> u8 {
51    let a_position = get_first_alphabet_position(letter);
52    let letter_position = get_letter_position(letter);
53
54    let shifted_position = match mode {
55        Mode::Encrypt => (letter_position + rotation) % 26,
56        Mode::Decrypt => {
57            if letter_position < rotation {
58                26 - (rotation - letter_position)
59            } else {
60                letter_position - rotation
61            }
62        }
63    };
64
65    a_position + shifted_position
66}
67
68/// Transform any input to rot
69///
70/// ```
71/// use nrot::{rot, Mode};
72/// let rotation = 13;
73///
74/// let input = b"Hello, World!";
75/// let result = b"Uryyb, Jbeyq!";
76/// let encrypted = rot(Mode::Encrypt, input, rotation);
77/// assert_eq!(result.as_slice(), &encrypted);
78///
79/// let input = b"Uryyb, Jbeyq!";
80/// let result = b"Hello, World!";
81/// let encrypted = rot(Mode::Decrypt, input, rotation);
82/// assert_eq!(result.as_slice(), &encrypted);
83/// ```
84pub fn rot(mode: Mode, inputs: &[u8], rotation: u8) -> Vec<u8> {
85    inputs
86        .iter()
87        .map(|&input| {
88            // only apply rot13 to ascii alphabetic characters
89            if input.is_ascii_alphabetic() {
90                rot_letter(mode, input, rotation)
91            } else {
92                input
93            }
94        })
95        .collect()
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn first_alphabet_position() {
104        assert_eq!(65, get_first_alphabet_position(b'A'));
105        assert_eq!(b'A', get_first_alphabet_position(b'A'));
106
107        assert_eq!(97, get_first_alphabet_position(b'a'));
108        assert_eq!(b'a', get_first_alphabet_position(b'a'));
109    }
110
111    #[test]
112    fn letter_position() {
113        assert_eq!(0, get_letter_position(b'A'));
114        assert_eq!(0, get_letter_position(b'a'));
115
116        assert_eq!(25, get_letter_position(b'Z'));
117        assert_eq!(25, get_letter_position(b'z'));
118    }
119
120    #[test]
121    fn rot13_letter_encrypt() {
122        let rotation = 13;
123
124        let encrypted = rot(Mode::Encrypt, b"a", rotation);
125        assert_eq!(b"n".as_slice(), &encrypted);
126
127        let encrypted = rot(Mode::Encrypt, b"A", rotation);
128        assert_eq!(b"N".as_slice(), &encrypted);
129
130        let encrypted = rot(Mode::Encrypt, b"Z", rotation);
131        assert_eq!(b"M".as_slice(), &encrypted);
132    }
133
134    #[test]
135    fn rot13_letter_decrypt() {
136        let rotation = 13;
137
138        let encrypted = rot(Mode::Decrypt, b"n", rotation);
139        assert_eq!(b"a".as_slice(), &encrypted);
140
141        let encrypted = rot(Mode::Decrypt, b"N", rotation);
142        assert_eq!(b"A".as_slice(), &encrypted);
143
144        let encrypted = rot(Mode::Decrypt, b"M", rotation);
145        assert_eq!(b"Z".as_slice(), &encrypted);
146    }
147
148    #[test]
149    fn rot13_encrypt() {
150        let rotation = 13;
151
152        let encrypted = rot(Mode::Encrypt, b"rust", rotation);
153        assert_eq!(b"ehfg".as_slice(), &encrypted);
154
155        let encrypted = rot(Mode::Encrypt, b"Hello, World!", rotation);
156        assert_eq!(b"Uryyb, Jbeyq!".as_slice(), &encrypted);
157    }
158
159    #[test]
160    fn rot13_dencrypt() {
161        let rotation = 13;
162
163        let encrypted = rot(Mode::Decrypt, b"ehfg", rotation);
164        assert_eq!(b"rust".as_slice(), &encrypted);
165
166        let encrypted = rot(Mode::Decrypt, b"Uryyb, Jbeyq!", rotation);
167        assert_eq!(b"Hello, World!".as_slice(), &encrypted);
168    }
169
170    #[test]
171    fn all_rotations_encrypt() {
172        let pairs = vec![(1, b"svtu"), (12, b"dgef"), (25, b"qtrs")];
173
174        for (rotation, expected) in pairs {
175            let encrypted = rot(Mode::Encrypt, b"rust", rotation);
176            assert_eq!(expected.as_slice(), &encrypted);
177        }
178    }
179
180    #[test]
181    fn offsite() {
182        let encrypted = rot(Mode::Encrypt, b"a", 0);
183        assert_eq!(b"a".as_slice(), &encrypted);
184
185        let encrypted = rot(Mode::Encrypt, b"a", 26);
186        assert_eq!(b"a".as_slice(), &encrypted);
187    }
188}