diff --git a/Cargo.lock b/Cargo.lock index f060e2f..d9de10a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,6 +618,12 @@ dependencies = [ "serde", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "deranged" version = "0.3.11" @@ -1364,6 +1370,7 @@ name = "librqbit-core" version = "3.8.0" dependencies = [ "anyhow", + "data-encoding", "directories", "hex 0.4.3", "itertools", diff --git a/crates/librqbit_core/Cargo.toml b/crates/librqbit_core/Cargo.toml index 10b031a..1e7e24e 100644 --- a/crates/librqbit_core/Cargo.toml +++ b/crates/librqbit_core/Cargo.toml @@ -25,6 +25,8 @@ clone_to_owned = { path = "../clone_to_owned", package = "librqbit-clone-to-owne itertools = "0.12" directories = "5" tokio-util = "0.7.10" +data-encoding = "2.6.0" + [dev-dependencies] serde_json = "1" diff --git a/crates/librqbit_core/src/hash_id.rs b/crates/librqbit_core/src/hash_id.rs index d8bd20b..e412a95 100644 --- a/crates/librqbit_core/src/hash_id.rs +++ b/crates/librqbit_core/src/hash_id.rs @@ -1,6 +1,6 @@ -use std::{cmp::Ordering, str::FromStr}; - +use data_encoding::BASE32; use serde::{Deserialize, Deserializer, Serialize}; +use std::{cmp::Ordering, str::FromStr}; #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Id(pub [u8; N]); @@ -69,11 +69,32 @@ impl FromStr for Id { fn from_str(s: &str) -> Result { let mut out = [0u8; N]; - if s.len() != N * 2 { - anyhow::bail!("expected a hex string of length {}", N * 2) - }; - hex::decode_to_slice(s, &mut out)?; - Ok(Id(out)) + let base32_encoded_size = (N as f64 / 5f64).ceil() as usize * 8; + if s.len() == N * 2 { + hex::decode_to_slice(s, &mut out)?; + Ok(Id(out)) + // try decode as base32 + } else if s.len() == base32_encoded_size { + match BASE32.decode(s.as_bytes()) { + Ok(decoded) => { + out.copy_from_slice(&decoded); + Ok(Id(out)) + } + Err(err) => { + anyhow::bail!( + "fail to decode base32 string {}: {}", + s, + err + ) + } + } + } else { + anyhow::bail!( + "expected a hex string of length {} or {}", + N * 2, + base32_encoded_size + ); + } } } @@ -184,4 +205,13 @@ mod tests { let str = "06f04cc728bef957a658876ef807f0514e4d715392969998efef584d2c3e435e"; let _ih = Id32::from_str(str).unwrap(); } + + #[test] + fn test_id20_base32_encoded_from_str() { + let str = "Z7QRDHYSJCA4U4HXGBXTFYUSDFGIRQMV"; + let ih1 = Id20::from_str(str).unwrap(); + let s2 = "cfe1119f124881ca70f7306f32e292194c88c195"; + let ih2 = Id20::from_str(s2).unwrap(); + assert_eq!(ih1, ih2); + } }