tinkerforge/
base58.rs

1//! Parses Base58 encoded brick and bricklet uids.
2use std::{
3    error::Error,
4    fmt::{Display, Formatter},
5};
6
7const ALPHABET: &str = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ";
8
9const ERROR_INVALID_CHAR: &str = "UID contains an invalid character";
10const ERROR_TOO_BIG: &str = "UID is too big to fit into a u64";
11const ERROR_EMPTY: &str = "UID is empty or a value that mapped to zero";
12
13///Error type of Base58 parser.
14#[derive(Debug, Copy, Clone)]
15pub enum Base58Error {
16    ///Is returned if the parse finds an invalid character. Contains the character and it's index in the string.
17    InvalidCharacter,
18    UidTooBig,
19    UidEmpty,
20}
21
22impl Display for Base58Error {
23    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
24        match *self {
25            Base58Error::InvalidCharacter => write!(f, "{}", ERROR_INVALID_CHAR),
26            Base58Error::UidTooBig => write!(f, "{}", ERROR_TOO_BIG),
27            Base58Error::UidEmpty => write!(f, "{}", ERROR_EMPTY),
28        }
29    }
30}
31
32impl Error for Base58Error {
33    fn description(&self) -> &str {
34        match *self {
35            Base58Error::InvalidCharacter => ERROR_INVALID_CHAR,
36            Base58Error::UidTooBig => ERROR_TOO_BIG,
37            Base58Error::UidEmpty => ERROR_EMPTY,
38        }
39    }
40}
41
42///A trait which adds Base58 parsing capabilities to strings. The alphabet used is "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ".
43pub trait Base58 {
44    /// Parse this string as Base58 encoded uid. Returns an error if a character is found, that is not part of the used alphabet.
45    fn base58_to_u32(&self) -> Result<u32, Base58Error>;
46}
47
48impl Base58 for str {
49    fn base58_to_u32(&self) -> Result<u32, Base58Error> {
50        let mut result_u64: u64 = 0;
51        let radix: u64 = ALPHABET.len() as u64;
52        let mut digit: u32 = 0;
53        // Remove 1s from the left as they are leading zeros in base 58
54        let filtered = self.as_bytes().iter().skip_while(|c| **c == '1' as u8).collect::<Vec<&u8>>();
55        for (_idx, &&character) in filtered.iter().enumerate().rev() {
56            match ALPHABET.as_bytes().iter().enumerate().find(|(_i, c)| **c == character).map(|(i, _c)| i) {
57                None => return Err(Base58Error::InvalidCharacter),
58                Some(i) => {
59                    if digit > 0 && radix.pow(digit - 1) > (u64::max_value() / radix) {
60                        return Err(Base58Error::UidTooBig); //pow overflow
61                    }
62                    let opt = radix.pow(digit).checked_mul(i as u64);
63                    if opt.is_none() {
64                        return Err(Base58Error::UidTooBig); //mul overflow
65                    }
66                    if u64::max_value() - opt.unwrap() < result_u64 {
67                        return Err(Base58Error::UidTooBig); //add overflow
68                    }
69                    result_u64 += opt.unwrap();
70                }
71            }
72            digit += 1;
73        }
74
75        let result = if result_u64 > u32::max_value().into() {
76            let value1 = result_u64 & 0xFF_FF_FF_FF;
77            let value2 = (result_u64 >> 32) & 0xFF_FF_FF_FF;
78            ((value1 & 0x00_00_0F_FF)
79                | (value1 & 0x0F_00_00_00) >> 12
80                | (value2 & 0x00_00_00_3F) << 16
81                | (value2 & 0x00_0F_00_00) << 6
82                | (value2 & 0x3F_00_00_00) << 2) as u32
83        } else {
84            result_u64 as u32
85        };
86        if result == 0 {
87            Err(Base58Error::UidEmpty)
88        } else {
89            Ok(result)
90        }
91    }
92}
93
94impl Base58 for String {
95    fn base58_to_u32(&self) -> Result<u32, Base58Error> { self.as_str().base58_to_u32() }
96}