You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
219 lines
7.1 KiB
Rust
219 lines
7.1 KiB
Rust
pub mod v1;
|
|
pub mod v2;
|
|
|
|
use hex::{FromHex, FromHexError, ToHex};
|
|
use std::convert::TryInto;
|
|
use std::error::Error;
|
|
use std::fmt::{Display, Formatter};
|
|
use std::iter;
|
|
pub use v1::InfoHash as InfoHashV1;
|
|
pub use v2::InfoHash as InfoHashV2;
|
|
|
|
pub trait InfoHashCapable: ToHex + FromHex<Error = InfoHashFromHashError> {
|
|
fn version(&self) -> u32;
|
|
fn to_bytes(&self) -> Box<[u8]>;
|
|
fn to_bytes_truncated(&self) -> [u8; 20] {
|
|
self.to_bytes()[0..20].try_into().unwrap()
|
|
}
|
|
|
|
/// Creates a V1 style info hash from this info hash
|
|
fn to_v1(&self) -> InfoHashV1;
|
|
fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, InfoHashFromBytesError>;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum InfoHashFromHashError {
|
|
InvalidLength {
|
|
expected: Option<usize>,
|
|
actual: usize,
|
|
},
|
|
HexEncodingError(FromHexError),
|
|
}
|
|
|
|
impl Error for InfoHashFromHashError {}
|
|
|
|
impl Display for InfoHashFromHashError {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::HexEncodingError(error) => write!(f, "Input is invalid hex: {}", error),
|
|
Self::InvalidLength { expected, actual } => write!(
|
|
f,
|
|
"Input has invalid length of {} expected {}",
|
|
actual,
|
|
expected
|
|
.map(|x| format!("{}", x))
|
|
.unwrap_or("40 or 64".to_string())
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum InfoHashFromBytesError {
|
|
InvalidLength {
|
|
expected: Option<usize>,
|
|
actual: usize,
|
|
},
|
|
}
|
|
|
|
impl Error for InfoHashFromBytesError {}
|
|
|
|
impl Display for InfoHashFromBytesError {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::InvalidLength { expected, actual } => write!(
|
|
f,
|
|
"Input has invalid length of {} expected {}",
|
|
actual,
|
|
expected
|
|
.map(|x| format!("{}", x))
|
|
.unwrap_or("20 or 32".to_string())
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum HybridInfoHash {
|
|
V1(InfoHashV1),
|
|
V2(InfoHashV2),
|
|
}
|
|
|
|
impl Display for HybridInfoHash {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(self.encode_hex::<String>().as_str())
|
|
}
|
|
}
|
|
|
|
impl ToHex for HybridInfoHash {
|
|
fn encode_hex<T: iter::FromIterator<char>>(&self) -> T {
|
|
match self {
|
|
Self::V1(info) => info.encode_hex(),
|
|
Self::V2(info) => info.encode_hex(),
|
|
}
|
|
}
|
|
|
|
fn encode_hex_upper<T: iter::FromIterator<char>>(&self) -> T {
|
|
match self {
|
|
Self::V1(info) => info.encode_hex_upper(),
|
|
Self::V2(info) => info.encode_hex_upper(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromHex for HybridInfoHash {
|
|
type Error = InfoHashFromHashError;
|
|
|
|
fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
|
|
let hex = hex.as_ref();
|
|
Ok(match hex.len() {
|
|
64 => Self::V2(InfoHashV2::from_hex(hex)?),
|
|
40 => Self::V1(InfoHashV1::from_hex(hex)?),
|
|
len => {
|
|
return Err(InfoHashFromHashError::InvalidLength {
|
|
actual: len,
|
|
expected: None,
|
|
});
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
impl InfoHashCapable for HybridInfoHash {
|
|
fn version(&self) -> u32 {
|
|
match self {
|
|
Self::V1(_) => 1,
|
|
Self::V2(_) => 2,
|
|
}
|
|
}
|
|
|
|
fn to_bytes(&self) -> Box<[u8]> {
|
|
match self {
|
|
Self::V1(info) => info.to_bytes(),
|
|
Self::V2(info) => info.to_bytes(),
|
|
}
|
|
}
|
|
|
|
fn to_v1(&self) -> InfoHashV1 {
|
|
match self {
|
|
Self::V1(info) => info.to_v1(),
|
|
Self::V2(info) => info.to_v1(),
|
|
}
|
|
}
|
|
|
|
fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self, InfoHashFromBytesError> {
|
|
let bytes_ref = bytes.as_ref();
|
|
match bytes_ref.len() {
|
|
20 => Ok(Self::V1(InfoHashV1::from_bytes(bytes)?)),
|
|
32 => Ok(Self::V2(InfoHashV2::from_bytes(bytes)?)),
|
|
len => Err(InfoHashFromBytesError::InvalidLength {
|
|
expected: None,
|
|
actual: len,
|
|
}),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use crate::infohash::{HybridInfoHash, InfoHashCapable};
|
|
use hex::{FromHex, ToHex};
|
|
|
|
#[test]
|
|
fn conversion() {
|
|
let info_hash_bytes: Vec<u8> = vec![
|
|
0x0a, 0xfe, 0x96, 0xa8, 0x62, 0x37, 0x8e, 0x7f, 0x69, 0x9d, 0x54, 0x6b, 0x7b, 0x8f,
|
|
0xe6, 0xfc, 0x47, 0xd0, 0xe2, 0x4a,
|
|
];
|
|
let info_hash =
|
|
HybridInfoHash::from_bytes(&info_hash_bytes).expect("Failed to parse info hash (v1)");
|
|
assert_eq!(1, info_hash.version());
|
|
assert_eq!(info_hash_bytes, info_hash.to_bytes().to_vec(), "Failed to assert that info hash (v1) could be converted to byte format and back without changing");
|
|
|
|
let info_hash_bytes: Vec<u8> = vec![
|
|
0xa3, 0x1f, 0xe9, 0x65, 0x6f, 0xc8, 0xd3, 0xa4, 0x59, 0xe6, 0x23, 0xdc, 0x82, 0x04,
|
|
0xe6, 0xd0, 0x26, 0x8f, 0x8d, 0xf5, 0x6d, 0x73, 0x4d, 0xac, 0x3c, 0xa3, 0x26, 0x2e,
|
|
0xdb, 0x5d, 0xb8, 0x83,
|
|
];
|
|
let info_hash =
|
|
HybridInfoHash::from_bytes(&info_hash_bytes).expect("Failed to parse info hash (v2)");
|
|
assert_eq!(info_hash_bytes, info_hash.to_bytes().to_vec(), "Failed to assert that info hash (v2) could be converted to byte format and back without changing");
|
|
}
|
|
|
|
#[test]
|
|
fn hex_conversion() {
|
|
let info_hash_hex = "0afe96a862378e7f699d546b7b8fe6fc47d0e24a";
|
|
let info_hash =
|
|
HybridInfoHash::from_hex(info_hash_hex).expect("Failed to parse info hash (v1)");
|
|
assert_eq!(1, info_hash.version());
|
|
assert_eq!(info_hash_hex, info_hash.encode_hex::<String>().as_str(), "Failed to assert that info hash (v1) could be converted to hex format and back without changing");
|
|
|
|
let info_hash_hex = "a31fe9656fc8d3a459e623dc8204e6d0268f8df56d734dac3ca3262edb5db883";
|
|
let info_hash =
|
|
HybridInfoHash::from_hex(info_hash_hex).expect("Failed to parse info hash (v2)");
|
|
assert_eq!(2, info_hash.version());
|
|
assert_eq!(info_hash_hex, info_hash.encode_hex::<String>().as_str(), "Failed to assert that info hash (v2) could be converted to hex format and back without changing");
|
|
}
|
|
|
|
#[test]
|
|
fn truncation() {
|
|
let info_hash_hex = "0afe96a862378e7f699d546b7b8fe6fc47d0e24a";
|
|
let info_hash =
|
|
HybridInfoHash::from_hex(info_hash_hex).expect("Failed to parse info hash (v1)");
|
|
assert_eq!(1, info_hash.version());
|
|
assert_eq!(
|
|
info_hash_hex,
|
|
info_hash.to_v1().encode_hex::<String>().as_str(),
|
|
"Failed to assert that info hash (v1) could be truncated to v1 hash without changing"
|
|
);
|
|
|
|
let info_hash_hex = "a31fe9656fc8d3a459e623dc8204e6d0268f8df56d734dac3ca3262edb5db883";
|
|
let info_hash =
|
|
HybridInfoHash::from_hex(info_hash_hex).expect("Failed to parse info hash (v2)");
|
|
assert_eq!(
|
|
&info_hash_hex[0..40],
|
|
info_hash.to_v1().encode_hex::<String>().as_str(),
|
|
"Failed to assert that info hash (v2) could be truncated to v1 hash without changing"
|
|
);
|
|
}
|
|
}
|