tinkerforge_async/
base58.rs1use 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#[derive(Debug, Copy, Clone)]
132pub enum Base58Error {
133 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
159pub trait Base58 {
161 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 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}