1use bitvec::prelude::*;
2use eetf::convert::TryAsRef;
3use eetf::{Atom, Binary, List, Term};
4use num_traits::ToPrimitive;
5use std::collections::HashMap;
6use std::ops::{Deref, DerefMut};
7use std::time::{Duration, SystemTime, UNIX_EPOCH};
8use tracing::warn;
9
10use crate::types::{Hash, PublicKey};
11
12pub trait Typename {
14 fn typename(&self) -> &'static str;
17}
18
19pub fn get_unix_secs_now() -> u32 {
21 SystemTime::now().duration_since(UNIX_EPOCH).as_ref().map(Duration::as_secs).unwrap_or(0) as u32
22}
23
24pub fn get_unix_millis_now() -> u64 {
25 SystemTime::now().duration_since(UNIX_EPOCH).as_ref().map(Duration::as_millis).unwrap_or(0) as u64
26}
27
28pub fn get_unix_nanos_now() -> u128 {
29 SystemTime::now().duration_since(UNIX_EPOCH).as_ref().map(Duration::as_nanos).unwrap_or(0)
30}
31
32#[deprecated(note = "Use bcat() for raw binary keys instead of base58")]
36pub fn pk_hex(pk: &[u8]) -> String {
37 bs58::encode(pk).into_string()
38}
39
40pub fn decode_base58_array<const N: usize>(s: &str) -> Option<[u8; N]> {
43 bs58::decode(s).into_vec().ok().and_then(|bytes| bytes.try_into().ok())
44}
45
46pub fn decode_base58_pk(s: &str) -> Option<PublicKey> {
48 decode_base58_array::<48>(s).map(PublicKey::from)
49}
50
51pub fn decode_base58_hash(s: &str) -> Option<Hash> {
53 decode_base58_array::<32>(s).map(Hash::from)
54}
55
56#[inline]
59pub fn bcat(slices: &[&[u8]]) -> Vec<u8> {
60 slices.iter().flat_map(|&s| s).copied().collect()
61}
62
63pub fn hexdump(data: &[u8]) -> String {
65 let mut out = String::new();
66 for (i, chunk) in data.chunks(16).enumerate() {
67 let address = i * 16;
68 let offset_str = format!("{address:08X}");
70
71 let mut hex_bytes = String::new();
73 for b in chunk {
74 hex_bytes.push_str(&format!("{:02X} ", b));
75 }
76 while hex_bytes.len() < 48 {
78 hex_bytes.push(' ');
79 }
80
81 let ascii: String = chunk.iter().map(|&b| if (32..=126).contains(&b) { b as char } else { '.' }).collect();
83
84 out.push_str(&format!("{offset_str} {hex_bytes} {ascii}\n"));
85 }
86 if out.ends_with('\n') {
87 out.pop();
88 }
89 out
90}
91
92pub fn ascii(input: &str) -> String {
94 input
95 .chars()
96 .filter(|&c| {
97 let code = c as u32;
98 code == 32
99 || (123..=126).contains(&code)
100 || (('!' as u32)..=('@' as u32)).contains(&code)
101 || (('[' as u32)..=('_' as u32)).contains(&code)
102 || (('0' as u32)..=('9' as u32)).contains(&code)
103 || (('A' as u32)..=('Z' as u32)).contains(&code)
104 || (('a' as u32)..=('z' as u32)).contains(&code)
105 })
106 .collect()
107}
108
109pub fn is_ascii_clean(input: &str) -> bool {
110 ascii(input) == input
111}
112
113pub fn alphanumeric(input: &str) -> String {
114 input.chars().filter(|c| c.is_ascii_alphanumeric()).collect()
115}
116
117pub fn is_alphanumeric(input: &str) -> bool {
118 alphanumeric(input) == input
119}
120
121pub fn url(url: &str) -> String {
123 url.trim_end_matches('/').to_string()
124}
125
126pub fn url_with(url: &str, path: &str) -> String {
128 format!("{}{}", url, path)
129}
130
131pub trait TermExt {
135 fn as_atom(&self) -> Option<&Atom>;
136 fn get_integer(&self) -> Option<i128>;
137 fn get_binary(&self) -> Option<&[u8]>;
138 fn get_list(&self) -> Option<&[Term]>;
139 fn get_string(&self) -> Option<String>;
140 fn get_term_map(&self) -> Option<TermMap>;
141 fn parse_list<T, E>(&self, parser: impl Fn(&[u8]) -> Result<T, E>) -> Vec<T>
142 where
143 E: std::fmt::Display;
144}
145
146impl TermExt for Term {
147 fn as_atom(&self) -> Option<&Atom> {
148 TryAsRef::<Atom>::try_as_ref(self)
149 }
150
151 fn get_integer(&self) -> Option<i128> {
152 match self {
153 Term::FixInteger(i) => Some(i.value as i128),
154 Term::BigInteger(bi) => bi.value.to_i128(),
155 _ => None,
156 }
157 }
158
159 fn get_binary(&self) -> Option<&[u8]> {
160 TryAsRef::<Binary>::try_as_ref(self).map(|b| b.bytes.as_slice())
161 }
162
163 fn get_list(&self) -> Option<&[Term]> {
164 TryAsRef::<List>::try_as_ref(self).map(|l| l.elements.as_slice())
165 }
166
167 fn get_string(&self) -> Option<String> {
168 if let Term::ByteList(bl) = self {
170 std::str::from_utf8(&bl.bytes).ok().map(|s| s.to_owned())
171 } else if let Term::Binary(b) = self {
172 std::str::from_utf8(&b.bytes).ok().map(|s| s.to_owned())
173 } else if let Term::Atom(a) = self {
174 Some(a.name.clone())
175 } else {
176 None
177 }
178 }
179
180 fn get_term_map(&self) -> Option<TermMap> {
181 match self {
182 Term::Map(m) => Some(TermMap(m.map.clone())),
183 _ => None,
184 }
185 }
186
187 fn parse_list<T, E>(&self, parser: impl Fn(&[u8]) -> Result<T, E>) -> Vec<T>
188 where
189 E: std::fmt::Display,
190 {
191 self.get_list().map(|list| parse_list(list, parser)).unwrap_or_default()
192 }
193}
194
195#[derive(Default, Clone, Debug)]
199pub struct TermMap(pub HashMap<Term, Term>);
200
201impl Deref for TermMap {
202 type Target = HashMap<Term, Term>;
203 fn deref(&self) -> &Self::Target {
204 &self.0
205 }
206}
207
208impl DerefMut for TermMap {
209 fn deref_mut(&mut self) -> &mut Self::Target {
210 &mut self.0
211 }
212}
213
214impl TermMap {
215 pub fn get_term_map(&self, key: &str) -> Option<Self> {
216 self.0.get(&Term::Atom(Atom::from(key))).and_then(TermExt::get_term_map)
217 }
218
219 pub fn get_binary<'a, A>(&'a self, key: &str) -> Option<A>
220 where
221 A: TryFrom<&'a [u8]>,
222 {
223 self.0.get(&Term::Atom(Atom::from(key))).and_then(TermExt::get_binary).and_then(|b| A::try_from(b).ok())
224 }
225
226 pub fn get_integer<I>(&self, key: &str) -> Option<I>
227 where
228 I: TryFrom<i128>,
229 {
230 self.0.get(&Term::Atom(Atom::from(key))).and_then(TermExt::get_integer).and_then(|b| I::try_from(b).ok())
231 }
232
233 pub fn get_list(&self, key: &str) -> Option<&[Term]> {
234 self.0.get(&Term::Atom(Atom::from(key))).and_then(TermExt::get_list)
235 }
236
237 pub fn get_atom(&self, key: &str) -> Option<&Atom> {
238 self.0.get(&Term::Atom(Atom::from(key))).and_then(TermExt::as_atom)
239 }
240
241 pub fn get_string(&self, key: &str) -> Option<String> {
242 self.0.get(&Term::Atom(Atom::from(key))).and_then(TermExt::get_string)
243 }
244
245 pub fn parse_list<T, E>(&self, key: &str, parser: impl Fn(&[u8]) -> Result<T, E>) -> Vec<T>
246 where
247 E: std::fmt::Display,
248 {
249 self.0.get(&Term::Atom(Atom::from(key))).map(|term| term.parse_list(parser)).unwrap_or_default()
250 }
251
252 pub fn into_term(self) -> Term {
253 Term::Map(eetf::Map { map: self.0 })
254 }
255}
256
257#[deprecated(since = "0.1.0", note = "Use list_of_binaries_to_vecpak instead")]
260pub fn eetf_list_of_binaries(list_of_binaries: Vec<Vec<u8>>) -> Result<Vec<u8>, eetf::EncodeError> {
261 let elements: Vec<Term> = list_of_binaries.into_iter().map(|bytes| Term::from(Binary { bytes })).collect();
262 let term = Term::from(List::from(elements));
263 let mut out = Vec::new();
264 term.encode(&mut out)?;
265 Ok(out)
266}
267
268pub fn list_of_binaries_to_vecpak(list_of_binaries: Vec<Vec<u8>>) -> Vec<u8> {
269 use crate::vecpak::{Term as VecpakTerm, encode};
270 let elements: Vec<VecpakTerm> = list_of_binaries.into_iter().map(VecpakTerm::Binary).collect();
271 encode(VecpakTerm::List(elements))
272}
273
274pub fn bitvec_to_bin(mask: &BitVec<u8, Msb0>) -> Vec<u8> {
275 mask.as_raw_slice().to_vec()
276}
277
278pub fn bin_to_bitvec(bytes: Vec<u8>) -> BitVec<u8, Msb0> {
279 BitVec::from_vec(bytes)
280}
281
282pub fn get_bits_percentage(mask: &BitVec<u8, Msb0>, total_count: usize) -> f64 {
284 if total_count == 0 {
285 return 0.0;
286 }
287 let true_bits = mask.count_ones();
288 (true_bits as f64) / (total_count as f64)
289}
290pub fn format_duration(total_seconds: u32) -> String {
311 if total_seconds < 60 {
312 return format!("{}s", total_seconds);
313 }
314
315 let minutes = total_seconds / 60;
316 let seconds = total_seconds % 60;
317
318 if minutes < 60 {
319 return format!("{}m {}s", minutes, seconds);
320 }
321
322 let hours = minutes / 60;
323 let minutes = minutes % 60;
324
325 if hours < 24 {
326 return format!("{}h {}m", hours, minutes);
327 }
328
329 let days = hours / 24;
330 let hours = hours % 24;
331
332 if days < 30 {
333 return format!("{}d {}h", days, hours);
334 }
335
336 let months = days / 30; let days = days % 30;
338
339 if months < 12 {
340 return format!("{}mo {}d", months, days);
341 }
342
343 let years = months / 12;
344 let months = months % 12;
345
346 format!("{}y {}mo {}d", years, months, days)
347}
348
349pub fn parse_list<T, E>(list: &[Term], parser: impl Fn(&[u8]) -> Result<T, E>) -> Vec<T>
351where
352 E: std::fmt::Display,
353{
354 list.iter()
355 .filter_map(|term| {
356 term.get_binary().and_then(|bytes| parser(bytes).map_err(|e| warn!("Failed to parse item: {}", e)).ok())
357 })
358 .collect()
359}
360
361pub fn serialize_list<T, E>(items: &[T], serializer: impl Fn(&T) -> Result<Vec<u8>, E>) -> Option<Term>
363where
364 E: std::fmt::Display,
365{
366 let terms: Result<Vec<_>, _> =
367 items.iter().map(|item| serializer(item).map(|bytes| Term::Binary(Binary::from(bytes)))).collect();
368 terms.ok().map(|terms| Term::List(List::from(terms)))
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn hexdump_basic() {
377 let s = hexdump(&[0x41, 0x00, 0x7F]);
378 assert!(s.starts_with("00000000 "));
379 assert!(s.contains("41 00 7F"));
380 assert!(s.ends_with("A.."));
381 }
382
383 #[test]
384 fn string_helpers() {
385 assert!(is_ascii_clean("AZaz09_-!"));
386 assert!(!is_ascii_clean("hi🙂"));
387 assert_eq!(alphanumeric("Abc-123"), "Abc123");
388 assert!(is_alphanumeric("abc123"));
389 assert!(!is_alphanumeric("a_b"));
390 }
391
392 #[test]
393 fn ext_and_urls() {
394 assert_eq!(url("http://a/b/"), "http://a/b");
395 assert_eq!(url("http://a/b"), "http://a/b");
396 assert_eq!(url_with("http://a/b", "/c"), "http://a/b/c");
397 }
398
399 #[test]
400 fn bitvec_roundtrip_prefix() {
401 let mut mask = BitVec::<u8, Msb0>::new();
402 mask.extend([true, false, true, true, false, false, false, true, true]);
403 let bytes = bitvec_to_bin(&mask);
404 assert_eq!(bytes.len(), 2);
405 let bools = bin_to_bitvec(bytes.clone());
406 assert_eq!(&bools[..mask.len()], &mask[..]);
407 for i in mask.len()..8 * bytes.len() {
408 assert!(!bools[i]);
409 }
410 }
411
412 #[test]
413 fn bcat_concatenates_slices() {
414 let pk = [0x01, 0x02, 0x03];
415 let result = bcat(&[b"prefix:", &pk, b":suffix"]);
416 assert_eq!(result, b"prefix:\x01\x02\x03:suffix");
417 }
418
419 #[test]
420 fn bcat_empty() {
421 let result = bcat(&[]);
422 assert_eq!(result, b"");
423 }
424
425 #[test]
426 fn bcat_builds_keys() {
427 let pk = [0xAA, 0xBB, 0xCC];
428 let result = bcat(&[b"bic:coin:balance:", &pk, b":AMA"]);
429 assert_eq!(result, b"bic:coin:balance:\xAA\xBB\xCC:AMA");
430 }
431
432 #[test]
433 fn test_decode_base58_pk() {
434 let test_pk = PublicKey::from([0u8; 48]);
436 let encoded = bs58::encode(&test_pk).into_string();
437 let decoded = decode_base58_pk(&encoded);
438 assert_eq!(decoded, Some(test_pk));
439
440 assert_eq!(decode_base58_pk("not-valid-base58!"), None);
442
443 let wrong_size = [0u8; 32];
445 let encoded_wrong = bs58::encode(&wrong_size).into_string();
446 assert_eq!(decode_base58_pk(&encoded_wrong), None);
447 }
448
449 #[test]
450 fn test_decode_base58_hash() {
451 let test_hash = Hash::from([0xFF; 32]);
453 let encoded = bs58::encode(&test_hash).into_string();
454 let decoded = decode_base58_hash(&encoded);
455 assert_eq!(decoded, Some(test_hash));
456
457 let wrong_size = [0u8; 48];
459 let encoded_wrong = bs58::encode(&wrong_size).into_string();
460 assert_eq!(decode_base58_hash(&encoded_wrong), None);
461 }
462
463 #[test]
464 fn test_decode_base58_array() {
465 let test_bytes: [u8; 16] = [0x12; 16];
467 let encoded = bs58::encode(&test_bytes).into_string();
468 let decoded: Option<[u8; 16]> = decode_base58_array(&encoded);
469 assert_eq!(decoded, Some(test_bytes));
470 }
471}