1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! Parses Base58 encoded brick and bricklet uids.
use std::{
    error::Error,
    fmt::{Display, Formatter},
};

use const_str::to_char_array;

const ALPHABET: [char; 58] = to_char_array!("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ");

const ERROR_INVALID_CHAR: &str = "UID contains an invalid character";
const ERROR_TOO_BIG: &str = "UID is too big to fit into a u64";
const ERROR_EMPTY: &str = "UID is empty or a value that mapped to zero";

///Error type of Base58 parser.
#[derive(Debug, Copy, Clone)]
pub enum Base58Error {
    ///Is returned if the parse finds an invalid character. Contains the character and it's index in the string.
    InvalidCharacter,
    UidTooBig,
    UidEmpty,
}

impl Display for Base58Error {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        match *self {
            Base58Error::InvalidCharacter => write!(f, "{}", ERROR_INVALID_CHAR),
            Base58Error::UidTooBig => write!(f, "{}", ERROR_TOO_BIG),
            Base58Error::UidEmpty => write!(f, "{}", ERROR_EMPTY),
        }
    }
}

impl Error for Base58Error {
    fn description(&self) -> &str {
        match *self {
            Base58Error::InvalidCharacter => ERROR_INVALID_CHAR,
            Base58Error::UidTooBig => ERROR_TOO_BIG,
            Base58Error::UidEmpty => ERROR_EMPTY,
        }
    }
}

///A trait which adds Base58 parsing capabilities to strings. The alphabet used is "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ".
pub trait Base58 {
    /// Parse this string as Base58 encoded uid. Returns an error if a character is found, that is not part of the used alphabet.
    fn base58_to_u32(&self) -> Result<u32, Base58Error>;
}

impl Base58 for str {
    fn base58_to_u32(&self) -> Result<u32, Base58Error> {
        let mut result_u64: u64 = 0;
        for character in self.chars() {
            match ALPHABET.iter().enumerate().find(|(_, c)| **c == character).map(|(i, _)| i) {
                None => return Err(Base58Error::InvalidCharacter),
                Some(i) => {
                    result_u64 = result_u64
                        .checked_mul(ALPHABET.len() as u64)
                        .ok_or(Base58Error::UidTooBig)?
                        .checked_add(i as u64)
                        .ok_or(Base58Error::UidTooBig)?;
                }
            }
        }

        let result = if result_u64 > u32::max_value().into() {
            let value1 = result_u64 & 0xFF_FF_FF_FF;
            let value2 = (result_u64 >> 32) & 0xFF_FF_FF_FF;
            ((value1 & 0x00_00_0F_FF)
                | (value1 & 0x0F_00_00_00) >> 12
                | (value2 & 0x00_00_00_3F) << 16
                | (value2 & 0x00_0F_00_00) << 6
                | (value2 & 0x3F_00_00_00) << 2) as u32
        } else {
            result_u64 as u32
        };
        if result == 0 {
            Err(Base58Error::UidEmpty)
        } else {
            Ok(result)
        }
    }
}

impl Base58 for String {
    fn base58_to_u32(&self) -> Result<u32, Base58Error> {
        self.as_str().base58_to_u32()
    }
}

pub fn u32_to_base58(mut id: u32) -> Box<str> {
    let radix = ALPHABET.len() as u32;
    // u32::MAX needs 6 digits
    let mut buffer = [0 as char; 6];
    let mut ptr = 0;
    while id > 0 {
        let digit = id % radix;
        buffer[ptr] = ALPHABET[digit as usize];
        id = id / radix;
        ptr += 1;
    }
    buffer[..ptr].iter().rev().collect::<String>().into_boxed_str()
}

#[cfg(test)]
mod test {
    use crate::base58::{u32_to_base58, Base58};

    #[test]
    fn test_parse_address() {
        assert_eq!(130221, "EHc".base58_to_u32().unwrap());
        assert_eq!(130221, "111111111111111111111111111111111111111111111111EHc".base58_to_u32().unwrap());
        assert_eq!(u32::MAX, "7xwQ9g".base58_to_u32().unwrap());
    }

    #[test]
    fn test_format_address() {
        assert_eq!("EHc", &u32_to_base58(130221).to_string());
        assert_eq!("7xwQ9g", &u32_to_base58(u32::MAX).to_string());
    }
}