use std::fmt;
use super::error::MetainfoError;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct InfoHashV1(pub [u8; 20]);
impl InfoHashV1 {
pub fn from_bytes(bytes: [u8; 20]) -> Self {
Self(bytes)
}
pub fn from_info_bytes(info_bytes: &[u8]) -> Self {
use sha1::{Digest, Sha1};
let mut hasher = Sha1::new();
hasher.update(info_bytes);
let hash: [u8; 20] = hasher.finalize().into();
Self(hash)
}
pub fn from_hex(s: &str) -> Result<Self, MetainfoError> {
if s.len() != 40 {
return Err(MetainfoError::InvalidInfoHashLength);
}
let bytes = hex_decode(s).ok_or(MetainfoError::InvalidInfoHashLength)?;
let mut arr = [0u8; 20];
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
pub fn as_bytes(&self) -> &[u8; 20] {
&self.0
}
pub fn to_hex(&self) -> String {
hex_encode(&self.0)
}
pub fn url_encode(&self) -> String {
self.0.iter().fold(String::with_capacity(60), |mut s, b| {
use std::fmt::Write;
let _ = write!(s, "%{:02x}", b);
s
})
}
}
impl fmt::Debug for InfoHashV1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "InfoHashV1({})", self.to_hex())
}
}
impl fmt::Display for InfoHashV1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct InfoHashV2(pub [u8; 32]);
impl InfoHashV2 {
pub fn from_bytes(bytes: [u8; 32]) -> Self {
Self(bytes)
}
pub fn from_info_bytes(info_bytes: &[u8]) -> Self {
use sha2::{Digest, Sha256};
let mut hasher = Sha256::new();
hasher.update(info_bytes);
let hash: [u8; 32] = hasher.finalize().into();
Self(hash)
}
pub fn from_hex(s: &str) -> Result<Self, MetainfoError> {
if s.len() != 64 {
return Err(MetainfoError::InvalidInfoHashLength);
}
let bytes = hex_decode(s).ok_or(MetainfoError::InvalidInfoHashLength)?;
let mut arr = [0u8; 32];
arr.copy_from_slice(&bytes);
Ok(Self(arr))
}
pub fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
pub fn to_hex(&self) -> String {
hex_encode(&self.0)
}
}
impl fmt::Debug for InfoHashV2 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "InfoHashV2({})", self.to_hex())
}
}
impl fmt::Display for InfoHashV2 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum InfoHash {
V1([u8; 20]),
V2([u8; 32]),
Hybrid {
v1: InfoHashV1,
v2: InfoHashV2,
},
}
impl InfoHash {
pub fn from_v1_bytes(bytes: &[u8]) -> Result<Self, MetainfoError> {
if bytes.len() != 20 {
return Err(MetainfoError::InvalidInfoHashLength);
}
let mut arr = [0u8; 20];
arr.copy_from_slice(bytes);
Ok(InfoHash::V1(arr))
}
pub fn from_v2_bytes(bytes: &[u8]) -> Result<Self, MetainfoError> {
if bytes.len() != 32 {
return Err(MetainfoError::InvalidInfoHashLength);
}
let mut arr = [0u8; 32];
arr.copy_from_slice(bytes);
Ok(InfoHash::V2(arr))
}
pub fn hybrid(v1: InfoHashV1, v2: InfoHashV2) -> Self {
InfoHash::Hybrid { v1, v2 }
}
pub fn from_hex(s: &str) -> Result<Self, MetainfoError> {
let bytes = hex_decode(s).ok_or(MetainfoError::InvalidInfoHashLength)?;
match bytes.len() {
20 => Self::from_v1_bytes(&bytes),
32 => Self::from_v2_bytes(&bytes),
_ => Err(MetainfoError::InvalidInfoHashLength),
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
InfoHash::V1(arr) => arr,
InfoHash::V2(arr) => arr,
InfoHash::Hybrid { v1, .. } => v1.as_bytes(),
}
}
pub fn is_v1(&self) -> bool {
matches!(self, InfoHash::V1(_))
}
pub fn is_v2(&self) -> bool {
matches!(self, InfoHash::V2(_))
}
pub fn is_hybrid(&self) -> bool {
matches!(self, InfoHash::Hybrid { .. })
}
pub fn v1_hash(&self) -> Option<InfoHashV1> {
match self {
InfoHash::V1(arr) => Some(InfoHashV1(*arr)),
InfoHash::Hybrid { v1, .. } => Some(*v1),
InfoHash::V2(_) => None,
}
}
pub fn v2_hash(&self) -> Option<InfoHashV2> {
match self {
InfoHash::V2(arr) => Some(InfoHashV2(*arr)),
InfoHash::Hybrid { v2, .. } => Some(*v2),
InfoHash::V1(_) => None,
}
}
pub fn to_hex(&self) -> String {
hex_encode(self.as_bytes())
}
pub fn url_encode(&self) -> String {
match self {
InfoHash::V1(arr) => InfoHashV1(*arr).url_encode(),
InfoHash::Hybrid { v1, .. } => v1.url_encode(),
InfoHash::V2(_) => {
String::new()
}
}
}
}
impl fmt::Debug for InfoHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InfoHash::V1(_) => write!(f, "InfoHash::V1({})", self.to_hex()),
InfoHash::V2(_) => write!(f, "InfoHash::V2({})", self.to_hex()),
InfoHash::Hybrid { v1, v2 } => {
write!(
f,
"InfoHash::Hybrid {{ v1: {}, v2: {} }}",
v1.to_hex(),
v2.to_hex()
)
}
}
}
}
impl fmt::Display for InfoHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.to_hex())
}
}
impl From<InfoHashV1> for InfoHash {
fn from(hash: InfoHashV1) -> Self {
InfoHash::V1(hash.0)
}
}
impl From<InfoHashV2> for InfoHash {
fn from(hash: InfoHashV2) -> Self {
InfoHash::V2(hash.0)
}
}
fn hex_encode(bytes: &[u8]) -> String {
bytes
.iter()
.fold(String::with_capacity(bytes.len() * 2), |mut s, b| {
use std::fmt::Write;
let _ = write!(s, "{:02x}", b);
s
})
}
fn hex_decode(s: &str) -> Option<Vec<u8>> {
if s.len() % 2 != 0 {
return None;
}
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
.collect()
}