use crate::consts::PEER_ID_PREFIX;
use crate::util::{PacketError, TryFromBuf};
use bytes::{Buf, Bytes};
use data_encoding::{DecodeError, BASE32, HEXLOWER_PERMISSIVE};
use rand::{
distr::{Alphanumeric, Distribution, StandardUniform},
Rng,
};
use std::borrow::Cow;
use std::fmt;
use std::str::FromStr;
use thiserror::Error;
use url::Url;
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub(crate) struct InfoHash([u8; InfoHash::LENGTH]);
impl InfoHash {
const LENGTH: usize = 20;
pub(crate) fn from_hex(s: &str) -> Result<InfoHash, InfoHashError> {
HEXLOWER_PERMISSIVE
.decode(s.as_bytes())
.map_err(InfoHashError::InvalidHex)?
.try_into()
}
pub(crate) fn from_base32(s: &str) -> Result<InfoHash, InfoHashError> {
BASE32
.decode(s.as_bytes())
.map_err(InfoHashError::InvalidBase32)?
.try_into()
}
pub(crate) fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
pub(crate) fn add_query_param(&self, url: &mut Url) {
add_bytes_query_param(url, "info_hash", &self.0);
}
}
impl fmt::Display for InfoHash {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for b in self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
impl FromStr for InfoHash {
type Err = InfoHashError;
fn from_str(s: &str) -> Result<InfoHash, InfoHashError> {
if s.len() == 32 {
InfoHash::from_base32(s)
} else {
InfoHash::from_hex(s)
}
}
}
impl TryFrom<Vec<u8>> for InfoHash {
type Error = InfoHashError;
fn try_from(bs: Vec<u8>) -> Result<InfoHash, InfoHashError> {
match bs.try_into() {
Ok(barray) => Ok(InfoHash(barray)),
Err(bs) => Err(InfoHashError::InvalidLength(bs.len())),
}
}
}
impl TryFromBuf for InfoHash {
fn try_from_buf(buf: &mut Bytes) -> Result<InfoHash, PacketError> {
if buf.len() >= InfoHash::LENGTH {
let mut data = [0u8; InfoHash::LENGTH];
buf.copy_to_slice(&mut data);
Ok(InfoHash(data))
} else {
Err(PacketError::Short)
}
}
}
#[derive(Copy, Clone, Debug, Eq, Error, PartialEq)]
pub(crate) enum InfoHashError {
#[error("info hash is invalid hexadecimal")]
InvalidHex(#[source] DecodeError),
#[error("info hash is invalid base32")]
InvalidBase32(#[source] DecodeError),
#[error("info hash is {0} bytes long, expected 20")]
InvalidLength(usize),
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct LocalPeer {
pub(crate) id: PeerId,
pub(crate) key: Key,
pub(crate) port: u16,
}
impl LocalPeer {
pub(crate) fn generate<R: Rng>(mut rng: R) -> LocalPeer {
let id = PeerId::generate(PEER_ID_PREFIX, &mut rng);
let key = rng.random::<Key>();
let port = rng.random_range::<u16, _>(1025..=65535);
LocalPeer { id, key, port }
}
}
impl fmt::Display for LocalPeer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"id = {}, key = {}, port = {}",
self.id, self.key, self.port
)
}
}
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
pub(crate) struct PeerId([u8; PeerId::LENGTH]);
impl PeerId {
const LENGTH: usize = 20;
pub(crate) fn generate<R: Rng>(prefix: &str, rng: &mut R) -> PeerId {
let bs = prefix.as_bytes();
PeerId(std::array::from_fn(|i| {
bs.get(i)
.copied()
.unwrap_or_else(|| Alphanumeric.sample(rng))
}))
}
pub(crate) fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
pub(crate) fn add_query_param(&self, url: &mut Url) {
add_bytes_query_param(url, "peer_id", &self.0);
}
}
impl fmt::Display for PeerId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", Bytes::from(self.0.to_vec()))
}
}
impl From<&[u8; 20]> for PeerId {
fn from(bs: &[u8; 20]) -> PeerId {
PeerId(*bs)
}
}
impl TryFrom<&[u8]> for PeerId {
type Error = PeerIdError;
fn try_from(bs: &[u8]) -> Result<PeerId, PeerIdError> {
match bs.try_into() {
Ok(barray) => Ok(PeerId(barray)),
Err(_) => Err(PeerIdError(bs.len())),
}
}
}
impl TryFrom<Vec<u8>> for PeerId {
type Error = PeerIdError;
fn try_from(bs: Vec<u8>) -> Result<PeerId, PeerIdError> {
match bs.try_into() {
Ok(barray) => Ok(PeerId(barray)),
Err(bs) => Err(PeerIdError(bs.len())),
}
}
}
impl TryFromBuf for PeerId {
fn try_from_buf(buf: &mut Bytes) -> Result<PeerId, PacketError> {
if buf.len() >= PeerId::LENGTH {
let mut data = [0u8; PeerId::LENGTH];
buf.copy_to_slice(&mut data);
Ok(PeerId(data))
} else {
Err(PacketError::Short)
}
}
}
#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)]
#[error(
"invalid length for peer id: expected {len} bytes, got {0}",
len = PeerId::LENGTH
)]
pub(crate) struct PeerIdError(usize);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct Key(u32);
impl fmt::Display for Key {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<u32> for Key {
fn from(key: u32) -> Key {
Key(key)
}
}
impl From<Key> for u32 {
fn from(key: Key) -> u32 {
key.0
}
}
impl Distribution<Key> for StandardUniform {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Key {
Key(StandardUniform.sample(rng))
}
}
fn add_bytes_query_param(url: &mut Url, key: &str, value: &[u8]) {
static SENTINEL: &str = "ADD_BYTES_QUERY_PARAM";
url.query_pairs_mut()
.encoding_override(Some(&|s| {
if s == SENTINEL {
Cow::from(value.to_vec())
} else {
Cow::from(s.as_bytes())
}
}))
.append_pair(key, SENTINEL)
.encoding_override(None);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hex_info_hash() {
let info_hash = "28C55196F57753C40ACEB6FB58617E6995A7EDDB"
.parse::<InfoHash>()
.unwrap();
assert_eq!(
info_hash.as_bytes(),
b"\x28\xC5\x51\x96\xF5\x77\x53\xC4\x0A\xCE\xB6\xFB\x58\x61\x7E\x69\x95\xA7\xED\xDB"
);
assert_eq!(
info_hash.to_string(),
"28c55196f57753c40aceb6fb58617e6995a7eddb"
);
}
#[test]
fn test_base32_info_hash() {
let info_hash = "XBIUOS3U6ZONDH4YDRZDLEHD4UQCIK4X"
.parse::<InfoHash>()
.unwrap();
assert_eq!(
info_hash.as_bytes(),
b"\xb8\x51\x47\x4b\x74\xf6\x5c\xd1\x9f\x98\x1c\x72\x35\x90\xe3\xe5\x20\x24\x2b\x97",
);
assert_eq!(
info_hash.to_string(),
"b851474b74f65cd19f981c723590e3e520242b97"
);
}
#[test]
fn test_add_query_param() {
let info_hash = "28C55196F57753C40ACEB6FB58617E6995A7EDDB"
.parse::<InfoHash>()
.unwrap();
let mut url = Url::parse("http://tracker.example.com:8080/announce?here=there").unwrap();
info_hash.add_query_param(&mut url);
assert_eq!(url.as_str(), "http://tracker.example.com:8080/announce?here=there&info_hash=%28%C5Q%96%F5wS%C4%0A%CE%B6%FBXa%7Ei%95%A7%ED%DB");
}
#[test]
fn test_generate_peer_id() {
let peer_id = PeerId::generate("-PRE-123-", &mut rand::rng());
assert_eq!(peer_id.as_bytes().len(), 20);
let s = std::str::from_utf8(peer_id.as_bytes()).unwrap();
let suffix = s.strip_prefix("-PRE-123-").unwrap();
for ch in suffix.chars() {
assert!(ch.is_ascii_alphanumeric());
}
assert_eq!(peer_id.to_string(), format!("b{s:?}"));
}
#[test]
fn test_generate_peer_id_long_prefix() {
let peer_id = PeerId::generate("-PRE-123-abcdefghijé-", &mut rand::rng());
assert_eq!(peer_id.as_bytes(), b"-PRE-123-abcdefghij\xC3");
assert_eq!(peer_id.to_string(), "b\"-PRE-123-abcdefghij\\xc3\"");
}
}