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 { 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>(bytes: T) -> Result; } #[derive(Debug)] pub enum InfoHashFromHashError { InvalidLength { expected: Option, 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, 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::().as_str()) } } impl ToHex for HybridInfoHash { fn encode_hex>(&self) -> T { match self { Self::V1(info) => info.encode_hex(), Self::V2(info) => info.encode_hex(), } } fn encode_hex_upper>(&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>(hex: T) -> Result { 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>(bytes: T) -> Result { 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 = 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 = 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::().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::().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::().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::().as_str(), "Failed to assert that info hash (v2) could be truncated to v1 hash without changing" ); } }