torrust_tracker_primitives/
info_hash.rs

1use std::hash::{DefaultHasher, Hash, Hasher};
2use std::ops::{Deref, DerefMut};
3use std::panic::Location;
4
5use thiserror::Error;
6use zerocopy::FromBytes;
7
8/// `BitTorrent` Info Hash v1
9#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
10pub struct InfoHash {
11    data: aquatic_udp_protocol::InfoHash,
12}
13
14pub const INFO_HASH_BYTES_LEN: usize = 20;
15
16impl InfoHash {
17    /// Create a new `InfoHash` from a byte slice.
18    ///
19    /// # Panics
20    ///
21    /// Will panic if byte slice does not contains the exact amount of bytes need for the `InfoHash`.
22    #[must_use]
23    pub fn from_bytes(bytes: &[u8]) -> Self {
24        let data = aquatic_udp_protocol::InfoHash::read_from(bytes).expect("it should have the exact amount of bytes");
25
26        Self { data }
27    }
28
29    /// Returns the `InfoHash` internal byte array.
30    #[must_use]
31    pub fn bytes(&self) -> [u8; 20] {
32        self.0
33    }
34
35    /// Returns the `InfoHash` as a hex string.
36    #[must_use]
37    pub fn to_hex_string(&self) -> String {
38        self.to_string()
39    }
40}
41
42impl Default for InfoHash {
43    fn default() -> Self {
44        Self {
45            data: aquatic_udp_protocol::InfoHash(Default::default()),
46        }
47    }
48}
49
50impl From<aquatic_udp_protocol::InfoHash> for InfoHash {
51    fn from(data: aquatic_udp_protocol::InfoHash) -> Self {
52        Self { data }
53    }
54}
55
56impl Deref for InfoHash {
57    type Target = aquatic_udp_protocol::InfoHash;
58
59    fn deref(&self) -> &Self::Target {
60        &self.data
61    }
62}
63
64impl DerefMut for InfoHash {
65    fn deref_mut(&mut self) -> &mut Self::Target {
66        &mut self.data
67    }
68}
69
70impl Ord for InfoHash {
71    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
72        self.0.cmp(&other.0)
73    }
74}
75
76impl PartialOrd<InfoHash> for InfoHash {
77    fn partial_cmp(&self, other: &InfoHash) -> Option<std::cmp::Ordering> {
78        Some(self.cmp(other))
79    }
80}
81
82impl std::fmt::Display for InfoHash {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        let mut chars = [0u8; 40];
85        binascii::bin2hex(&self.0, &mut chars).expect("failed to hexlify");
86        write!(f, "{}", std::str::from_utf8(&chars).unwrap())
87    }
88}
89
90impl std::str::FromStr for InfoHash {
91    type Err = binascii::ConvertError;
92
93    fn from_str(s: &str) -> Result<Self, Self::Err> {
94        let mut i = Self::default();
95        if s.len() != 40 {
96            return Err(binascii::ConvertError::InvalidInputLength);
97        }
98        binascii::hex2bin(s.as_bytes(), &mut i.0)?;
99        Ok(i)
100    }
101}
102
103impl std::convert::From<&[u8]> for InfoHash {
104    fn from(data: &[u8]) -> InfoHash {
105        assert_eq!(data.len(), 20);
106        let mut ret = Self::default();
107        ret.0.clone_from_slice(data);
108        ret
109    }
110}
111
112/// for testing
113impl std::convert::From<&DefaultHasher> for InfoHash {
114    fn from(data: &DefaultHasher) -> InfoHash {
115        let n = data.finish().to_le_bytes();
116        let bytes = [
117            n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7], n[0], n[1], n[2], n[3], n[4], n[5], n[6], n[7], n[0], n[1], n[2],
118            n[3],
119        ];
120        let data = aquatic_udp_protocol::InfoHash(bytes);
121        Self { data }
122    }
123}
124
125impl std::convert::From<&i32> for InfoHash {
126    fn from(n: &i32) -> InfoHash {
127        let n = n.to_le_bytes();
128        let bytes = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, n[0], n[1], n[2], n[3]];
129        let data = aquatic_udp_protocol::InfoHash(bytes);
130        Self { data }
131    }
132}
133
134impl std::convert::From<[u8; 20]> for InfoHash {
135    fn from(bytes: [u8; 20]) -> Self {
136        let data = aquatic_udp_protocol::InfoHash(bytes);
137        Self { data }
138    }
139}
140
141/// Errors that can occur when converting from a `Vec<u8>` to an `InfoHash`.
142#[derive(Error, Debug)]
143pub enum ConversionError {
144    /// Not enough bytes for infohash. An infohash is 20 bytes.
145    #[error("not enough bytes for infohash: {message} {location}")]
146    NotEnoughBytes {
147        location: &'static Location<'static>,
148        message: String,
149    },
150    /// Too many bytes for infohash. An infohash is 20 bytes.
151    #[error("too many bytes for infohash: {message} {location}")]
152    TooManyBytes {
153        location: &'static Location<'static>,
154        message: String,
155    },
156}
157
158impl TryFrom<Vec<u8>> for InfoHash {
159    type Error = ConversionError;
160
161    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
162        if bytes.len() < INFO_HASH_BYTES_LEN {
163            return Err(ConversionError::NotEnoughBytes {
164                location: Location::caller(),
165                message: format! {"got {} bytes, expected {}", bytes.len(), INFO_HASH_BYTES_LEN},
166            });
167        }
168        if bytes.len() > INFO_HASH_BYTES_LEN {
169            return Err(ConversionError::TooManyBytes {
170                location: Location::caller(),
171                message: format! {"got {} bytes, expected {}", bytes.len(), INFO_HASH_BYTES_LEN},
172            });
173        }
174        Ok(Self::from_bytes(&bytes))
175    }
176}
177
178impl serde::ser::Serialize for InfoHash {
179    fn serialize<S: serde::ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
180        let mut buffer = [0u8; 40];
181        let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap();
182        let str_out = std::str::from_utf8(bytes_out).unwrap();
183        serializer.serialize_str(str_out)
184    }
185}
186
187impl<'de> serde::de::Deserialize<'de> for InfoHash {
188    fn deserialize<D: serde::de::Deserializer<'de>>(des: D) -> Result<Self, D::Error> {
189        des.deserialize_str(InfoHashVisitor)
190    }
191}
192
193struct InfoHashVisitor;
194
195impl<'v> serde::de::Visitor<'v> for InfoHashVisitor {
196    type Value = InfoHash;
197
198    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        write!(formatter, "a 40 character long hash")
200    }
201
202    fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
203        if v.len() != 40 {
204            return Err(serde::de::Error::invalid_value(
205                serde::de::Unexpected::Str(v),
206                &"a 40 character long string",
207            ));
208        }
209
210        let mut res = InfoHash::default();
211
212        if binascii::hex2bin(v.as_bytes(), &mut res.0).is_err() {
213            return Err(serde::de::Error::invalid_value(
214                serde::de::Unexpected::Str(v),
215                &"a hexadecimal string",
216            ));
217        };
218        Ok(res)
219    }
220}