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

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"
);
}
}