ulid/
base32.rs

1use core::fmt;
2
3/// Length of a string-encoded Ulid
4pub const ULID_LEN: usize = 26;
5
6const ALPHABET: &[u8; 32] = b"0123456789ABCDEFGHJKMNPQRSTVWXYZ";
7
8const NO_VALUE: u8 = 255;
9const LOOKUP: [u8; 256] = [
10    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
11    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
12    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255,
13    255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17, 255, 18, 19, 255, 20, 21, 255, 22, 23, 24,
14    25, 26, 255, 27, 28, 29, 30, 31, 255, 255, 255, 255, 255, 255, 10, 11, 12, 13, 14, 15, 16, 17,
15    255, 18, 19, 255, 20, 21, 255, 22, 23, 24, 25, 26, 255, 27, 28, 29, 30, 31, 255, 255, 255, 255,
16    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
17    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
18    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
19    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
20    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
21    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
22    255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
23];
24
25/// Generator code for `LOOKUP`
26#[cfg(test)]
27#[test]
28fn test_lookup_table() {
29    let mut lookup = [NO_VALUE; 256];
30    for (i, &c) in ALPHABET.iter().enumerate() {
31        lookup[c as usize] = i as u8;
32        if !(c as char).is_numeric() {
33            //lowercase
34            lookup[(c + 32) as usize] = i as u8;
35        }
36    }
37    assert_eq!(LOOKUP, lookup);
38}
39
40/// An error that can occur when encoding a base32 string
41#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
42pub enum EncodeError {
43    /// The length of the provided buffer is not large enough
44    BufferTooSmall,
45}
46
47#[cfg(feature = "std")]
48impl std::error::Error for EncodeError {}
49
50impl fmt::Display for EncodeError {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
52        let text = match *self {
53            EncodeError::BufferTooSmall => "buffer too small",
54        };
55        write!(f, "{}", text)
56    }
57}
58
59/// Encode a u128 value to a given buffer. The provided buffer should be at least `ULID_LEN` long.
60#[deprecated(
61    since = "1.2.0",
62    note = "Use the infallible `encode_to_array` instead."
63)]
64pub fn encode_to(mut value: u128, buffer: &mut [u8]) -> Result<usize, EncodeError> {
65    // NOTE: This function can't be made const because mut refs aren't allowed for some reason
66
67    if buffer.len() < ULID_LEN {
68        return Err(EncodeError::BufferTooSmall);
69    }
70
71    for i in 0..ULID_LEN {
72        buffer[ULID_LEN - 1 - i] = ALPHABET[(value & 0x1f) as usize];
73        value >>= 5;
74    }
75
76    Ok(ULID_LEN)
77}
78
79/// Encode a u128 value to a given buffer.
80pub fn encode_to_array(mut value: u128, buffer: &mut [u8; ULID_LEN]) {
81    // NOTE: This function can't be made const because mut refs aren't allowed for some reason
82
83    for i in 0..ULID_LEN {
84        buffer[ULID_LEN - 1 - i] = ALPHABET[(value & 0x1f) as usize];
85        value >>= 5;
86    }
87}
88
89#[cfg(feature = "std")]
90pub fn encode(value: u128) -> String {
91    let mut buffer: [u8; ULID_LEN] = [0; ULID_LEN];
92
93    encode_to_array(value, &mut buffer);
94
95    String::from_utf8(buffer.to_vec()).expect("unexpected failure in base32 encode for ulid")
96}
97
98/// An error that can occur when decoding a base32 string
99#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
100pub enum DecodeError {
101    /// The length of the string does not match the expected length
102    InvalidLength,
103    /// A non-base32 character was found
104    InvalidChar,
105}
106
107#[cfg(feature = "std")]
108impl std::error::Error for DecodeError {}
109
110impl fmt::Display for DecodeError {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
112        let text = match *self {
113            DecodeError::InvalidLength => "invalid length",
114            DecodeError::InvalidChar => "invalid character",
115        };
116        write!(f, "{}", text)
117    }
118}
119
120pub const fn decode(encoded: &str) -> Result<u128, DecodeError> {
121    if encoded.len() != ULID_LEN {
122        return Err(DecodeError::InvalidLength);
123    }
124
125    let mut value: u128 = 0;
126
127    let bytes = encoded.as_bytes();
128
129    // Manual for loop because Range::iter() isn't const
130    let mut i = 0;
131    while i < ULID_LEN {
132        let val = LOOKUP[bytes[i] as usize];
133        if val != NO_VALUE {
134            value = (value << 5) | val as u128;
135        } else {
136            return Err(DecodeError::InvalidChar);
137        }
138        i += 1;
139    }
140
141    Ok(value)
142}
143
144#[cfg(all(test, feature = "std"))]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn test_valid() {
150        let val = 0x41414141414141414141414141414141;
151        assert_eq!(decode("21850M2GA1850M2GA1850M2GA1").unwrap(), val);
152        assert_eq!(encode(val), "21850M2GA1850M2GA1850M2GA1");
153
154        let val = 0x4d4e385051444a59454234335a413756;
155        let enc = "2D9RW50MA499CMAGHM6DD42DTP";
156        let lower = enc.to_lowercase();
157        assert_eq!(encode(val), enc);
158        assert_eq!(decode(enc).unwrap(), val);
159        assert_eq!(decode(&lower).unwrap(), val);
160    }
161
162    #[test]
163    fn test_length() {
164        assert_eq!(encode(0xffffffffffffffffffffffffffffffff).len(), ULID_LEN);
165        assert_eq!(encode(0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f).len(), ULID_LEN);
166        assert_eq!(encode(0x00000000000000000000000000000000).len(), ULID_LEN);
167
168        assert_eq!(decode(""), Err(DecodeError::InvalidLength));
169        assert_eq!(
170            decode("2D9RW50MA499CMAGHM6DD42DT"),
171            Err(DecodeError::InvalidLength)
172        );
173        assert_eq!(
174            decode("2D9RW50MA499CMAGHM6DD42DTPP"),
175            Err(DecodeError::InvalidLength)
176        );
177    }
178
179    #[test]
180    fn test_chars() {
181        for ref c in encode(0xffffffffffffffffffffffffffffffff).bytes() {
182            assert!(ALPHABET.contains(c));
183        }
184        for ref c in encode(0x0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f).bytes() {
185            assert!(ALPHABET.contains(c));
186        }
187        for ref c in encode(0x00000000000000000000000000000000).bytes() {
188            assert!(ALPHABET.contains(c));
189        }
190
191        assert_eq!(
192            decode("2D9RW50[A499CMAGHM6DD42DTP"),
193            Err(DecodeError::InvalidChar)
194        );
195        assert_eq!(
196            decode("2D9RW50LA499CMAGHM6DD42DTP"),
197            Err(DecodeError::InvalidChar)
198        );
199        assert_eq!(
200            decode("2D9RW50IA499CMAGHM6DD42DTP"),
201            Err(DecodeError::InvalidChar)
202        );
203    }
204}