Skip to main content

zbase32/
lib.rs

1//! z-base-32: a human-oriented base-32 encoding.
2//!
3//! # Examples
4//!
5//! ```rust
6//! use zbase32::{decode, encode, DecodeError};
7//!
8//! assert_eq!(encode(b"hello"), "pb1sa5dx");
9//! assert_eq!(decode("pb1sa5dx"), Ok(b"hello".to_vec()));
10//!
11//! // Invalid characeter in input string results in an DecodeError
12//! assert_eq!(decode("bar#"), Err(DecodeError));
13//! ```
14
15#[cfg(feature = "python")]
16mod python;
17
18#[cfg(test)]
19#[macro_use]
20extern crate quickcheck;
21
22use std::{error::Error, fmt};
23
24const ALPHABET: &[u8] = b"ybndrfg8ejkmcpqxot1uwisza345h769";
25const INVERSE_ALPHABET: [i8; 123] = [
26    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
27    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
28    -1, 18, -1, 25, 26, 27, 30, 29, 7, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
29    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
30    -1, 24, 1, 12, 3, 8, 5, 6, 28, 21, 9, 10, -1, 11, 2, 16, 13, 14, 4, 22, 17, 19, -1, 20, 15, 0,
31    23,
32];
33
34/// Invalid characted encountered during decoding
35#[derive(Debug, PartialEq, Eq)]
36pub struct DecodeError;
37
38impl Error for DecodeError {}
39
40impl fmt::Display for DecodeError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        write!(f, "DecodeError: Non-zbase32 digit found")
43    }
44}
45
46/// Encode input into a z-base-32 encoded [`String`]
47///
48/// # Examples
49///
50/// ```rust
51/// use zbase32::encode;
52///
53/// assert_eq!(encode(b"hello"), "pb1sa5dx");
54/// ```
55pub fn encode(input: impl AsRef<[u8]>) -> String {
56    let input = input.as_ref();
57    let mut result = Vec::new();
58    let chunks = input.chunks(5);
59
60    for chunk in chunks {
61        let buf = {
62            let mut buf = [0u8; 5];
63            for (i, &b) in chunk.iter().enumerate() {
64                buf[i] = b;
65            }
66            buf
67        };
68        result.push(ALPHABET[((buf[0] & 0xF8) >> 3) as usize]);
69        result.push(ALPHABET[((buf[0] & 0x07) << 2 | (buf[1] & 0xC0) >> 6) as usize]);
70        result.push(ALPHABET[((buf[1] & 0x3E) >> 1) as usize]);
71        result.push(ALPHABET[((buf[1] & 0x01) << 4 | (buf[2] & 0xF0) >> 4) as usize]);
72        result.push(ALPHABET[((buf[2] & 0x0F) << 1 | (buf[3] & 0x80) >> 7) as usize]);
73        result.push(ALPHABET[((buf[3] & 0x7C) >> 2) as usize]);
74        result.push(ALPHABET[((buf[3] & 0x03) << 3 | (buf[4] & 0xE0) >> 5) as usize]);
75        result.push(ALPHABET[(buf[4] & 0x1F) as usize]);
76    }
77
78    let expected_len = (input.len() as f32 * 8.0 / 5.0).ceil() as usize;
79    for _ in 0..(result.len() - expected_len) {
80        result.pop();
81    }
82    // SAFETY: `result` contains only bytes from `ALPHABET` which contains a fixed
83    // subset ACSII encoded bytes that are also valid UTF-8.
84    unsafe { String::from_utf8_unchecked(result) }
85}
86
87/// Decode input into a new [`Vec`]
88///
89/// # Errors
90///
91/// Return [`DecodeError`] when there is an invalid character in the input
92///
93/// # Examples
94///
95/// ```rust
96/// use zbase32::{decode, DecodeError};
97///
98/// assert_eq!(decode("pb1sa5dx"), Ok(b"hello".to_vec()));
99///
100/// // Invalid characeter in input string results in an `DecodeError`
101/// assert_eq!(decode("bar#"), Err(DecodeError));
102/// ```
103pub fn decode(input: &str) -> Result<Vec<u8>, DecodeError> {
104    let mut result = Vec::new();
105    for chunk in input.as_bytes().chunks(8) {
106        let buf = {
107            let mut buf = [0u8; 8];
108            for (i, &ch) in chunk.iter().enumerate() {
109                match INVERSE_ALPHABET.get(ch as usize) {
110                    Some(-1) | None => return Err(DecodeError),
111                    Some(x) => buf[i] = *x as u8,
112                }
113            }
114            buf
115        };
116        result.push((buf[0] << 3) | (buf[1] >> 2));
117        result.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4));
118        result.push((buf[3] << 4) | (buf[4] >> 1));
119        result.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3));
120        result.push((buf[6] << 5) | buf[7]);
121    }
122
123    for _ in 0..(result.len() - input.len() * 5 / 8) {
124        result.pop();
125    }
126    Ok(result)
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132    #[test]
133    fn simple_encode() {
134        assert_eq!(encode(b"asdasd"), "cf3seamuco".to_string());
135    }
136
137    #[test]
138    fn encode_str() {
139        assert_eq!(encode("asdasd"), "cf3seamuco".to_string());
140    }
141
142    #[test]
143    fn encode_string() {
144        let string = String::from("asdasd");
145        assert_eq!(encode(string), "cf3seamuco".to_string());
146    }
147
148    #[test]
149    fn simple_decode() {
150        assert_eq!(decode("cf3seamu"), Ok(b"asdas".to_vec()));
151    }
152
153    #[test]
154    fn encode_decode() {
155        assert_eq!(decode(&encode(b"foo")).unwrap(), b"foo");
156    }
157
158    #[test]
159    fn invalid_decode() {
160        assert_eq!(decode("bar#"), Err(DecodeError));
161    }
162
163    quickcheck! {
164        fn prop(input: Vec<u8>) -> bool {
165            decode(&encode(&input)).unwrap() == input
166
167        }
168    }
169
170    quickcheck! {
171        #[allow(unused_must_use)]
172        fn not_panic(input: String) -> bool {
173            decode(&input);
174            true
175        }
176    }
177}