base64_lib/
lib.rs

1use std::collections::HashMap;
2
3/// Returns a base64 encoded string.
4///
5/// # Arguments
6///
7/// * `bytes` - A Vec<u8> with bytes to be encoded.
8///
9/// # Example
10///
11/// ```
12/// let input_vector: Vec<u8> = String::from("Hello World").into_bytes();
13/// let result_string: String = base64_lib::encode(&input_vector);
14/// ```
15// TESTED
16pub fn encode(bytes: &Vec<u8>) -> String {
17    return encode_with_alphabet(&bytes, &String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="));
18}
19
20/// Returns a byte vector containing decoded data.
21///
22/// # Arguments
23///
24/// * `base64_string` - A base64 encoded string.
25///
26/// # Example
27///
28/// ```
29/// let input_string: String = String::from("SGVsbG8gV29ybGQ=");
30/// let result_vector: Vec<u8> = base64_lib::decode(&input_string);
31/// ```
32// TESTED
33pub fn decode(base64_string: &String) -> Vec<u8> {
34    return decode_with_alphabet(&base64_string, &String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="));
35}
36
37/// Returns a base64 encoded string.
38///
39/// # Arguments
40///
41/// * `bytes` - A Vec<u8> with bytes to be encoded.
42/// * `alphabet` - A custom alphabet.  Contains 64 unique characters.
43///
44/// # Example
45///
46/// ```
47/// let input_vector: Vec<u8> = String::from("Hello World").into_bytes();
48/// let alphabet: String = String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
49/// let result_string: String = base64_lib::encode_with_alphabet(&input_vector, &alphabet);
50/// ```
51// TESTED
52pub fn encode_with_alphabet(bytes: &Vec<u8>, alphabet: &String) -> String {
53    if bytes.len() == 0 {
54        return String::from("");
55    }
56
57    if !validate_alphabet(&alphabet) {
58        panic!("Invalid alphabet!");
59    }
60
61    let pad_char: char = get_pad_char(&alphabet);
62    let fixed_alphabet: String = remove_pad_char(&alphabet);
63    let lookup_table = get_encode_lookup(&fixed_alphabet);
64    let pads: usize = get_number_of_pads(&bytes);
65
66    let mut buffer: Vec<u8> = vec![0; bytes.len() + pads];
67    let mut output: String = String::from("");
68
69    for i in 0..bytes.len() {
70        buffer[i] = bytes[i];
71    }
72
73    let mut i = 0;
74    while i < buffer.len() {
75        let mut number: usize = 0;
76        let mut segment: usize;
77        let mask: usize = 0x3f;
78
79        number += buffer[i] as usize;
80        number = number << 8;
81        number += buffer[i+1] as usize;
82        number = number << 8;
83        number += buffer[i+2] as usize;
84
85        if i == buffer.len() - 3 && pads != 0 {
86                if pads == 1 {
87                    segment = (number >> 18) & mask;
88                    output.push(lookup_table[segment]);
89                    segment = (number >> 12) & mask;
90                    output.push(lookup_table[segment]);
91                    segment = (number >> 6) & mask;
92                    output.push(lookup_table[segment]);
93                    output.push(pad_char);
94                } else if pads == 2 {
95                    segment = (number >> 18) & mask;
96                    output.push(lookup_table[segment]);
97                    segment = (number >> 12) & mask;
98                    output.push(lookup_table[segment]);
99                    output.push(pad_char);
100                    output.push(pad_char);
101                } else {
102                    panic!("pads had invalid value???");
103                }
104            } else {
105                segment = (number >> 18) & mask;
106                output.push(lookup_table[segment]);
107                segment = (number >> 12) & mask;
108                output.push(lookup_table[segment]);
109                segment = (number >> 6) & mask;
110                output.push(lookup_table[segment]);
111                segment = number & mask;
112                output.push(lookup_table[segment]);
113            }
114
115        i = i + 3;
116    }
117
118    return output;
119}
120
121/// Returns a byte vector containing decoded data.
122///
123/// # Arguments
124///
125/// * `base64_string` - A base64 encoded string.
126/// * `alphabet` - A custom alphabet.  Contains 64 unique characters.
127///
128/// # Example
129///
130/// ```
131/// let input_string: String = String::from("SGVsbG8gV29ybGQ=");
132/// let alphabet: String = String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
133/// let result_vector: Vec<u8> = base64_lib::decode_with_alphabet(&input_string, &alphabet);
134/// ```
135// TESTED
136pub fn decode_with_alphabet(base64_string: &String, alphabet: &String) -> Vec<u8> {
137    if base64_string == "" {
138        let empty_vec: Vec<u8> = vec![];
139        return empty_vec;
140    }
141
142    if !validate_alphabet(&alphabet) {
143        panic!("Invalid alphabet!");
144    }
145
146    let pad_char: char = get_pad_char(&alphabet);
147    let fixed_alphabet: String = remove_pad_char(&alphabet);
148    let decode_lookup: HashMap<char, usize> = get_decode_lookup(&fixed_alphabet, &pad_char);
149    let base64_vector: Vec<char> = base64_string.chars().collect();
150    let mut output_vector: Vec<u8> = vec![];
151    let pad_count: usize = get_pad_count(&base64_vector, &pad_char);
152
153    let mut i: usize = 0;
154    while i < base64_vector.len() {
155        let mut number: usize = 0;
156
157        number += match decode_lookup.get(&base64_vector[i]) {
158            Some(value) => *value,
159            None => panic!("Didn't find that key! {}", &base64_vector[i])
160        };
161        number = number << 6;
162        number += match decode_lookup.get(&base64_vector[i+1]) {
163            Some(value) => *value,
164            None => panic!("Didn't find that key! {}", &base64_vector[i+1])
165        };
166        number = number << 6;
167        number += match decode_lookup.get(&base64_vector[i+2]) {
168            Some(value) => *value,
169            None => panic!("Didn't find that key! {}", &base64_vector[i+2])
170        };
171        number = number << 6;
172        number += match decode_lookup.get(&base64_vector[i+3]) {
173            Some(value) => *value,
174            None => panic!("Didn't find that key! {}", &base64_vector[i+3])
175        };
176
177        if i != base64_vector.len() - 4 {
178          output_vector.push(((number & 0xff0000) >> 16) as u8);
179          output_vector.push(((number & 0x00ff00) >> 8) as u8);
180          output_vector.push((number & 0x0000ff) as u8);
181        } else {
182          if pad_count == 0 {
183            output_vector.push(((number & 0xff0000) >> 16) as u8);
184            output_vector.push(((number & 0x00ff00) >> 8) as u8);
185            output_vector.push((number & 0x0000ff) as u8);
186          } else if pad_count == 1 {
187            output_vector.push(((number & 0xff0000) >> 16) as u8);
188            output_vector.push(((number & 0x00ff00) >> 8) as u8);
189          } else if pad_count == 2 {
190            output_vector.push(((number & 0xff0000) >> 16) as u8);
191          } else {
192            panic!("Invalid pad_count");
193          }
194        }
195
196        i = i + 4;
197    }
198
199    return output_vector;
200}
201
202// TESTED - Need some failure test cases.
203fn get_encode_lookup(alphabet: &String) -> Vec<char> {
204    let lookup: Vec<char> = alphabet.chars().collect();
205  
206    return lookup;
207}
208
209// TESTED
210fn get_decode_lookup(alphabet: &String, pad_char: &char) -> HashMap<char, usize> {
211    let alphabet_vector: Vec<char> = alphabet.chars().collect();
212    let mut reverse_lookup = HashMap::new();
213
214    let mut i: usize = 0;
215
216    for c in alphabet_vector {
217        reverse_lookup.insert(c, i);
218        i = i + 1;
219    }
220
221    reverse_lookup.insert(*pad_char, 0);
222
223    return reverse_lookup;
224}
225
226// TESTED
227fn remove_pad_char(alphabet: &String) -> String {
228    let mut alphabet_vector: Vec<char> = alphabet.chars().collect();
229    let index = alphabet_vector.len() - 1;
230    let mut result: String = String::from("");
231
232    alphabet_vector.remove(index);
233
234    for c in alphabet_vector {
235        result.push(c);
236    }
237
238    return result;
239}
240
241// TESTED
242fn get_number_of_pads(bytes: &Vec<u8>) -> usize {
243
244    if bytes.len() % 3 != 0 {
245        return 3 - (bytes.len() % 3);
246    }
247
248    return 0;
249}
250
251// TESTED
252fn get_pad_char(alphabet: &String) -> char {
253    // Assume last char of alphabet is the pad char.
254    let alphabet_vector: Vec<char> = alphabet.chars().collect();
255
256    return alphabet_vector[alphabet_vector.len() - 1];
257}
258
259// TESTED
260fn get_pad_count(bytes: &Vec<char>, pad_char: &char) -> usize {
261    let mut pad_count = 0;
262    let mut i: usize = 0;
263
264    while i < bytes.len() {
265        if bytes[i] == *pad_char {
266            pad_count += 1;
267        }
268        i = i + 1;
269    }
270
271    return pad_count;
272}
273
274// TESTED
275fn validate_alphabet(alphabet: &String) -> bool {
276    let alphabet_vector: Vec<char> = alphabet.chars().collect();
277
278    if alphabet_vector.len() != 65 {
279        println!("Invalid alphabet length! {}", alphabet_vector.len());
280        return false;
281    }
282
283    let mut lookup_map: HashMap<char, bool> = HashMap::new();
284
285    for c in &alphabet_vector {
286        if lookup_map.contains_key(c) {
287            println!("Duplicate key! {}", &c);
288            return false;
289        } else {
290            lookup_map.insert(*c, true);
291        }
292    }
293
294    return true;
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    // Generators for test functions.
302    fn get_plain_strings() -> Vec<String> {
303        let plain_strings: Vec<String> = vec![
304            String::from("Foo Bar"),
305            String::from("This string\nhas newlines and\ttabs"),
306            String::from("mañana"),
307            String::from("Iñtërnâtiônàlizætiøn☃💩"),
308            String::from("🇺🇸🇺🇸")
309        ];
310
311        return plain_strings;
312    }
313
314    // Uses alphabet #0. (Standard alphabet)
315    fn get_base64_strings() -> Vec<String> {
316        let base64_strings: Vec<String> = vec![
317            String::from("Rm9vIEJhcg=="),
318            String::from("VGhpcyBzdHJpbmcKaGFzIG5ld2xpbmVzIGFuZAl0YWJz"),
319            String::from("bWHDsWFuYQ=="),
320            String::from("ScOxdMOrcm7DonRpw7Ruw6BsaXrDpnRpw7hu4piD8J+SqQ=="),
321            String::from("8J+HuvCfh7jwn4e68J+HuA==")
322        ];
323
324        return base64_strings;
325    }
326
327    // Uses alphabet #2. (Nonstandard alphabet)
328    fn get_nonstandard_base64_strings() -> Vec<String> {
329        let nonstandard_base64_strings: Vec<String> = vec![
330            String::from("РёэоЗДИбЬа++"),
331            String::from("ХЁбиЬсБтЭЖИиЫёЬЙЪЁЕтЗЁщеЭцриЫёХтЗЁЕнЩАефШЦИт"),
332            String::from("ЫЦЖГлЦЕнШП++"),
333            String::from("СЬНрЭЛНкЬёыГзжРипыРнпъБлЪЧкГижРипыбншивГьИюСйП++"),
334            String::from("ьИюЖноВЯбыгпжшЮъьИюЖнА++")
335        ];
336
337        return nonstandard_base64_strings;
338    }
339
340    // Alphabet #0 is the standard alphabet. All others are nonstandard.
341    fn get_alphabets() -> Vec<String> {
342        let alphabet_strings: Vec<String> = vec![
343            // Valid alphabets.
344            String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="),
345            String::from("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/="),
346            String::from("АБВГДЕЁЖЗИЙКЛМНОПРСТФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстфхцчшщъыьэюя+"),
347            String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZабвгдеёжзийклмнопрстyфхцчшщъыьэюя01234+"),
348            // Invalid alphabets.
349            String::from("Foo"),
350            String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/#$%"),
351            String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123abcdefghijklmnopqrstuvwxyz0123456789+/")
352        ];
353
354        return alphabet_strings;
355    }
356
357    mod encode_tests {
358        use super::*;
359
360        // encode() tests.
361        #[test]
362        fn should_encode_empty_vector_correctly() {
363            let actual = encode(&vec![]);
364            assert!(actual == String::from(""));
365        }
366
367        #[test]
368        fn should_encode_plain_strings_correctly() {
369            let plain_strings = get_plain_strings();
370            let base64_strings = get_base64_strings();
371
372            let mut i: usize = 0;
373            while i < plain_strings.len() {
374                let test_vector = &plain_strings[i].as_bytes().to_vec();
375                let actual = encode(&test_vector);
376                assert_eq!(actual, base64_strings[i]);
377                i = i + 1;
378            }
379        }
380    }
381
382    mod decode_tests {
383        use super::*;
384
385        // decode() tests.
386        #[test]
387        fn decode_empty_string_should_return_empty_vector() {
388            let actual: Vec<u8> = decode(&String::from(""));
389            assert_eq!(actual, []);
390        }
391
392        #[test]
393        fn decode_should_decode_base64_strings_correctly() {
394            let base64_strings = get_base64_strings();
395            let plain_strings = get_plain_strings();
396
397            let mut i: usize = 0;
398            while i < base64_strings.len() {
399                let test_string = &base64_strings[i];
400                let actual: String = match String::from_utf8(decode(&test_string)) {
401                    Ok(s) => s,
402                    Err(e) => panic!("Invalid utf-8 sequence: {:?}", e),
403                };
404
405                assert_eq!(actual, plain_strings[i]);
406                i = i + 1;
407            }
408        }
409    }
410    
411    mod encode_with_alphabet_tests {
412        use super::*;
413
414        // encode_with_alphabet(...) tests.
415        // Using alphabet #2.
416        #[test]
417        fn encode_with_alphabet_should_encode_empty_vector_correctly() {
418            let alphabet_vector: Vec<String> = get_alphabets();
419            let actual = encode_with_alphabet(&vec![], &alphabet_vector[2]);
420            assert!(actual == String::from(""));
421        }
422
423        #[test]
424        fn encode_with_alphabet_should_encode_plain_strings_correctly() {
425            let alphabet_vector: Vec<String> = get_alphabets();
426            let plain_strings = get_plain_strings();
427            let nonstandard_base64_strings = get_nonstandard_base64_strings();
428
429            let mut i: usize = 0;
430            while i < plain_strings.len() {
431                let test_vector = &plain_strings[i].as_bytes().to_vec();
432                let actual = encode_with_alphabet(&test_vector, &alphabet_vector[2]);
433                assert_eq!(actual, nonstandard_base64_strings[i]);
434                i = i + 1;
435            }
436        }
437    }
438
439    mod decode_with_alphabet_tests {
440        use super::*;
441
442        // decode_with_alphabet(...) tests.
443        // Using alphabet #2.
444        #[test]
445        fn decode_with_alphabet_should_decode_empty_string_correctly() {
446            let alphabet_vector: Vec<String> = get_alphabets();
447            let actual: Vec<u8> = decode_with_alphabet(&String::from(""), &alphabet_vector[2]);
448            assert_eq!(actual, []);
449        }
450
451        #[test]
452        fn decode_with_alphabet_should_decode_base64_strings_correctly() {
453            let alphabet_vector: Vec<String> = get_alphabets();
454            let nonstandard_base64_strings = get_nonstandard_base64_strings();
455            let plain_strings = get_plain_strings();
456
457            let mut i: usize = 0;
458            while i < nonstandard_base64_strings.len() {
459                let test_string = &nonstandard_base64_strings[i];
460                let actual: String = match String::from_utf8(decode_with_alphabet(&test_string, &alphabet_vector[2])) {
461                    Ok(s) => s,
462                    Err(e) => panic!("Invalid utf-8 sequence: {:?}", e),
463                };
464
465                println!("actual = {}, plain_strings[{}] = {}", actual, i, plain_strings[i]);
466
467                assert_eq!(actual, plain_strings[i]);
468                i = i + 1;
469            }
470        }
471    }
472
473    mod get_encode_lookup_tests {
474        use super::*;
475
476        #[test]
477        fn should_give_expected_output() {
478            let test_string: String = String::from("Test");
479            let expected: Vec<char> = vec!['T', 'e', 's', 't'];
480
481            assert_eq!(expected, get_encode_lookup(&test_string));
482        }
483    }
484
485    mod get_decode_lookup_tests {
486        use super::*;
487
488        #[test]
489        fn should_build_decode_lookup_correctly() {
490            let mut test_data: HashMap<char, usize> = HashMap::new();
491
492            test_data.insert('A', 0);
493            test_data.insert('B', 1);
494            test_data.insert('C', 2);
495            test_data.insert('+', 0);
496
497            let test_alphabet: String = String::from("ABC");
498            let pad_char: char = '+';
499
500            assert_eq!(test_data, get_decode_lookup(&test_alphabet, &pad_char));
501        }
502    }
503
504    // Helper function tests.
505    mod get_pad_char_tests {
506        use super::*;
507
508        #[test]
509        fn get_pad_char_should_return_correct_char() {
510            let alphabet = String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
511            assert_eq!(get_pad_char(&alphabet), '=');
512        }
513
514        #[test]
515        fn get_pad_char_should_return_correct_char_nonstandard_alphabet() {
516            let alphabet = String::from("АБВГДЕЁЖЗИЙКЛМНОПРСТФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстфхцчшщъыьэюя+");
517            assert_eq!(get_pad_char(&alphabet), '+');
518        }
519    }
520
521    mod remove_pad_char_tests {
522        use super::*;
523
524        #[test]
525        fn remove_pad_char_should_return_modified_alphabet_standard() {
526            let alphabet = String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=");
527            let expected = String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
528            assert_eq!(remove_pad_char(&alphabet), expected);
529        }
530
531        #[test]
532        fn remove_pad_char_should_return_modified_alphabet_nonstandard() {
533            let alphabet = String::from("АБВГДЕЁЖЗИЙКЛМНОПРСТФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстфхцчшщъыьэюя+");
534            let expected = String::from("АБВГДЕЁЖЗИЙКЛМНОПРСТФХЦЧШЩЪЫЬЭЮЯабвгдеёжзийклмнопрстфхцчшщъыьэюя");
535            assert_eq!(remove_pad_char(&alphabet), expected);
536        }
537    }
538
539    mod get_number_of_pads_tests {
540        use super::*;
541
542        #[test]
543        fn get_number_of_pads_should_return_zero() {
544            let test_vec = String::from("Foo").into_bytes();
545            assert!(get_number_of_pads(&test_vec) == 0);
546        }
547
548        #[test]
549        fn get_number_of_pads_should_return_one() {
550            let test_vec = String::from("Fooox").into_bytes();
551            assert!(get_number_of_pads(&test_vec) == 1);
552        }
553
554        #[test]
555        fn get_number_of_pads_should_return_two() {
556            let test_vec = String::from("Foo Bar").into_bytes();
557            assert!(get_number_of_pads(&test_vec) == 2);
558        }
559    }    
560
561    mod get_pad_count_tests {
562        use super::*;
563
564        #[test]
565        fn get_pad_count_should_return_zero() {
566            let input: Vec<char> = String::from("FooBar").chars().collect();
567            let pad_char: char = '=';
568            let actual: usize = get_pad_count(&input, &pad_char);
569
570            assert_eq!(actual, 0);
571        }
572
573        #[test]
574        fn get_pad_count_should_return_one() {
575            let input: Vec<char> = String::from("FooBar=").chars().collect();
576            let pad_char: char = '=';
577            let actual: usize = get_pad_count(&input, &pad_char);
578
579            assert_eq!(actual, 1);
580        }
581
582        #[test]
583        fn get_pad_count_should_return_two() {
584            let input: Vec<char> = String::from("FooBar==").chars().collect();
585            let pad_char: char = '=';
586            let actual: usize = get_pad_count(&input, &pad_char);
587
588            assert_eq!(actual, 2);
589        }
590    }
591
592    mod validate_alphabet_tests {
593        use super::*;
594
595        #[test]
596        fn validate_alphabet_should_return_true_with_standard_alphabet() {
597            let alphabet_vector: Vec<String> = get_alphabets();
598            assert_eq!(validate_alphabet(&alphabet_vector[0]), true);
599        }
600
601        #[test]
602        fn validate_alphabet_should_return_true_with_nonstandard_alphabet() {
603            let alphabet_vector: Vec<String> = get_alphabets();
604            assert_eq!(validate_alphabet(&alphabet_vector[2]), true);
605        }
606
607        #[test]
608        fn validate_alphabet_should_return_false_with_short_alphabet() {
609            let alphabet_vector: Vec<String> = get_alphabets();
610            assert_eq!(validate_alphabet(&alphabet_vector[4]), false);
611        }
612
613        #[test]
614        fn validate_alphabet_should_return_false_with_long_alphabet() {
615            let alphabet_vector: Vec<String> = get_alphabets();
616            assert_eq!(validate_alphabet(&alphabet_vector[5]), false);
617        }
618
619        #[test]
620        fn validate_alphabet_should_return_false_with_nonunique_symbols() {
621            let alphabet_vector: Vec<String> = get_alphabets();
622            assert_eq!(validate_alphabet(&alphabet_vector[6]), false);
623        }
624    }
625}