1use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
2
3use crate::ffi::ffi;
4
5pub struct Sha1Hash([u8; 20]);
6
7impl From<[u8; 20]> for Sha1Hash {
8 fn from(value: [u8; 20]) -> Self {
9 Sha1Hash(value)
10 }
11}
12
13#[derive(Debug, Clone, Copy)]
14pub enum InfoHash {
15 V1([u8; 20]),
16 V2([u8; 32]),
17}
18
19impl InfoHash {
20 pub fn as_base64(&self) -> String {
21 match self {
22 InfoHash::V1(v1) => BASE64_URL_SAFE_NO_PAD.encode(v1),
23 InfoHash::V2(v2) => BASE64_URL_SAFE_NO_PAD.encode(v2),
24 }
25 }
26
27 fn from_hex<const N: usize>(hex: &str) -> Result<[u8; N], ()> {
29 if hex.len() != N * 2 {
30 return Err(());
31 }
32 let mut bytes = [0u8; N];
33 for i in 0..N {
34 let hi = hex_char_to_val(hex.as_bytes()[i * 2])?;
35 let lo = hex_char_to_val(hex.as_bytes()[i * 2 + 1])?;
36 bytes[i] = (hi << 4) | lo;
37 }
38 Ok(bytes)
39 }
40
41 fn from_base32(input: &str) -> Result<[u8; 20], ()> {
43 let mut bits = 0u64;
44 let mut bit_count = 0;
45 let mut out = [0u8; 20];
46 let mut byte_index = 0;
47
48 for c in input.chars() {
49 let val = base32_char_to_val(c)?;
50 bits = (bits << 5) | (val as u64);
51 bit_count += 5;
52
53 while bit_count >= 8 {
54 bit_count -= 8;
55 if byte_index >= 20 {
56 return Err(()); }
58 out[byte_index] = ((bits >> bit_count) & 0xFF) as u8;
59 byte_index += 1;
60 }
61 }
62
63 if byte_index != 20 {
64 return Err(()); }
66
67 Ok(out)
68 }
69
70 pub fn from_magnet(magnet_uri: &str) -> Result<Self, ()> {
71 let xt_start = magnet_uri.find("xt=urn:").ok_or(())?;
72 let xt = &magnet_uri[xt_start + 7..];
73 let end = xt.find('&').unwrap_or(xt.len());
74 let urn = &xt[..end];
75
76 if let Some(rest) = urn.strip_prefix("btih:") {
77 if rest.len() == 40 {
78 let bytes = Self::from_hex::<20>(rest)?;
79 return Ok(InfoHash::V1(bytes));
80 } else if rest.len() == 32 {
81 let bytes = Self::from_base32(rest)?;
82 return Ok(InfoHash::V1(bytes));
83 } else {
84 return Err(());
85 }
86 } else if let Some(rest) = urn.strip_prefix("btmh:") {
87 if let Some(rest) = rest.strip_prefix("1220") {
88 let bytes = Self::from_hex::<32>(rest)?;
89 return Ok(InfoHash::V2(bytes));
90 } else {
91 return Err(());
92 }
93 }
94
95 Err(())
96 }
97}
98
99fn hex_char_to_val(c: u8) -> Result<u8, ()> {
101 match c {
102 b'0'..=b'9' => Ok(c - b'0'),
103 b'a'..=b'f' => Ok(c - b'a' + 10),
104 b'A'..=b'F' => Ok(c - b'A' + 10),
105 _ => Err(()),
106 }
107}
108
109fn base32_char_to_val(c: char) -> Result<u8, ()> {
111 match c {
112 'A'..='Z' => Ok((c as u8) - b'A'),
113 '2'..='7' => Ok((c as u8) - b'2' + 26),
114 _ => Err(()),
115 }
116}
117
118impl std::hash::Hash for InfoHash {
119 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
120 match self {
121 InfoHash::V1(v1) => v1.hash(state),
122 InfoHash::V2(v2) => v2.hash(state),
123 }
124 }
125}
126
127impl PartialEq for InfoHash {
128 fn eq(&self, other: &Self) -> bool {
129 match (self, other) {
130 (InfoHash::V1(lhs), InfoHash::V1(rhs)) => lhs == rhs,
131 (InfoHash::V2(lhs), InfoHash::V2(rhs)) => lhs == rhs,
132 _ => false,
133 }
134 }
135}
136
137impl Eq for InfoHash {}
138
139impl From<ffi::InfoHashCpp> for InfoHash {
140 fn from(value: ffi::InfoHashCpp) -> Self {
141 match value.version {
142 1 => InfoHash::V1(value.inner[..20].try_into().unwrap()),
143 2 => InfoHash::V2(value.inner),
144 _ => unreachable!(),
145 }
146 }
147}