tinkerforge_async/
base58.rs

1//! Parses Base58 encoded brick and bricklet uids.
2use std::fmt::Debug;
3use std::{
4    error::Error,
5    fmt::{Display, Formatter},
6    str::FromStr,
7};
8
9use byteorder::{ByteOrder, LittleEndian};
10use const_str::to_char_array;
11
12use crate::byte_converter::{FromByteSlice, ToBytes};
13
14const ALPHABET: [char; 58] = to_char_array!("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ");
15
16const ERROR_INVALID_CHAR: &str = "UID contains an invalid character";
17const ERROR_TOO_BIG: &str = "UID is too big to fit into a u64";
18const ERROR_EMPTY: &str = "UID is empty or a value that mapped to zero";
19
20#[derive(Copy, Clone, Eq, PartialEq, Hash, Default, Ord, PartialOrd)]
21pub struct Uid(u32);
22
23impl Uid {
24    #[inline]
25    pub(crate) fn zero() -> Uid {
26        Uid(0)
27    }
28}
29
30impl From<u32> for Uid {
31    fn from(value: u32) -> Self {
32        Uid(value)
33    }
34}
35
36impl From<Uid> for u32 {
37    fn from(value: Uid) -> Self {
38        value.0
39    }
40}
41
42impl FromStr for Uid {
43    type Err = Base58Error;
44
45    fn from_str(s: &str) -> Result<Self, Self::Err> {
46        Ok(Self(s.base58_to_u32()?))
47    }
48}
49
50impl Display for Uid {
51    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
52        f.write_str(&u32_to_base58(self.0))
53    }
54}
55
56impl Debug for Uid {
57    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58        f.write_str(&u32_to_base58(self.0))
59    }
60}
61
62impl ToBytes for Uid {
63    fn to_le_byte_vec(num: Uid) -> Vec<u8> {
64        let mut buf = vec![0; 4];
65        LittleEndian::write_u32(&mut buf, num.0);
66        buf
67    }
68
69    fn write_to_slice(self, target: &mut [u8]) {
70        LittleEndian::write_u32(target, self.0);
71    }
72}
73
74impl FromByteSlice for Uid {
75    fn from_le_byte_slice(bytes: &[u8]) -> Uid {
76        Uid(LittleEndian::read_u32(bytes))
77    }
78
79    fn bytes_expected() -> usize {
80        4
81    }
82}
83
84#[cfg(feature = "serde")]
85mod serde {
86    use std::{fmt::Formatter, str::FromStr};
87
88    use serde::{
89        de::{Error, Visitor},
90        Deserialize, Deserializer, Serialize, Serializer,
91    };
92
93    use crate::base58::Uid;
94
95    struct UidVisitor;
96
97    impl<'de> Visitor<'de> for UidVisitor {
98        type Value = Uid;
99
100        fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
101            formatter.write_str("a string a tinkerforge uid in base58 format")
102        }
103        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
104        where
105            E: Error,
106        {
107            Uid::from_str(v).map_err(|error| E::custom(format!("Cannot parse {v} as uid: {error}")))
108        }
109    }
110
111    impl<'de> Deserialize<'de> for Uid {
112        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
113        where
114            D: Deserializer<'de>,
115        {
116            deserializer.deserialize_str(UidVisitor)
117        }
118    }
119
120    impl Serialize for Uid {
121        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122        where
123            S: Serializer,
124        {
125            serializer.serialize_str(&self.to_string())
126        }
127    }
128}
129
130///Error type of Base58 parser.
131#[derive(Debug, Copy, Clone)]
132pub enum Base58Error {
133    ///Is returned if the parse finds an invalid character. Contains the character and it's index in the string.
134    InvalidCharacter,
135    UidTooBig,
136    UidEmpty,
137}
138
139impl Display for Base58Error {
140    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
141        match *self {
142            Base58Error::InvalidCharacter => write!(f, "{}", ERROR_INVALID_CHAR),
143            Base58Error::UidTooBig => write!(f, "{}", ERROR_TOO_BIG),
144            Base58Error::UidEmpty => write!(f, "{}", ERROR_EMPTY),
145        }
146    }
147}
148
149impl Error for Base58Error {
150    fn description(&self) -> &str {
151        match *self {
152            Base58Error::InvalidCharacter => ERROR_INVALID_CHAR,
153            Base58Error::UidTooBig => ERROR_TOO_BIG,
154            Base58Error::UidEmpty => ERROR_EMPTY,
155        }
156    }
157}
158
159///A trait which adds Base58 parsing capabilities to strings. The alphabet used is "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ".
160pub trait Base58 {
161    /// Parse this string as Base58 encoded uid. Returns an error if a character is found, that is not part of the used alphabet.
162    fn base58_to_u32(&self) -> Result<u32, Base58Error>;
163}
164
165impl Base58 for str {
166    fn base58_to_u32(&self) -> Result<u32, Base58Error> {
167        let mut result_u64: u64 = 0;
168        for character in self.chars() {
169            match ALPHABET.iter().enumerate().find(|(_, c)| **c == character).map(|(i, _)| i) {
170                None => return Err(Base58Error::InvalidCharacter),
171                Some(i) => {
172                    result_u64 = result_u64
173                        .checked_mul(ALPHABET.len() as u64)
174                        .ok_or(Base58Error::UidTooBig)?
175                        .checked_add(i as u64)
176                        .ok_or(Base58Error::UidTooBig)?;
177                }
178            }
179        }
180
181        let result = if result_u64 > u32::max_value().into() {
182            let value1 = result_u64 & 0xFF_FF_FF_FF;
183            let value2 = (result_u64 >> 32) & 0xFF_FF_FF_FF;
184            ((value1 & 0x00_00_0F_FF)
185                | (value1 & 0x0F_00_00_00) >> 12
186                | (value2 & 0x00_00_00_3F) << 16
187                | (value2 & 0x00_0F_00_00) << 6
188                | (value2 & 0x3F_00_00_00) << 2) as u32
189        } else {
190            result_u64 as u32
191        };
192        if result == 0 {
193            Err(Base58Error::UidEmpty)
194        } else {
195            Ok(result)
196        }
197    }
198}
199
200impl Base58 for String {
201    fn base58_to_u32(&self) -> Result<u32, Base58Error> {
202        self.as_str().base58_to_u32()
203    }
204}
205
206pub fn u32_to_base58(mut id: u32) -> Box<str> {
207    let radix = ALPHABET.len() as u32;
208    // u32::MAX needs 6 digits
209    let mut buffer = [0 as char; 6];
210    let mut ptr = 0;
211    while id > 0 {
212        let digit = id % radix;
213        buffer[ptr] = ALPHABET[digit as usize];
214        id = id / radix;
215        ptr += 1;
216    }
217    buffer[..ptr].iter().rev().collect::<String>().into_boxed_str()
218}
219
220#[cfg(test)]
221mod test {
222    use crate::base58::{u32_to_base58, Base58};
223
224    #[test]
225    fn test_parse_address() {
226        assert_eq!(130221, "EHc".base58_to_u32().unwrap());
227        assert_eq!(130221, "111111111111111111111111111111111111111111111111EHc".base58_to_u32().unwrap());
228        assert_eq!(u32::MAX, "7xwQ9g".base58_to_u32().unwrap());
229    }
230
231    #[test]
232    fn test_format_address() {
233        assert_eq!("EHc", &u32_to_base58(130221).to_string());
234        assert_eq!("7xwQ9g", &u32_to_base58(u32::MAX).to_string());
235    }
236}