use fluent_uri::{ParseError as UriParseError, Uri};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::str::FromStr;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum PeerSource {
DHT,
PEX,
LSD,
Tracker(Tracker),
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Tracker {
scheme: TrackerScheme,
url: Uri<String>,
}
impl Tracker {
pub fn new(url: &str) -> Result<Tracker, TrackerError> {
let url = Uri::parse(url.to_string())?;
Tracker::from_url(&url)
}
pub fn from_url(url: &Uri<String>) -> Result<Tracker, TrackerError> {
Ok(Tracker {
scheme: TrackerScheme::from_str(url.scheme().as_str())?,
url: url.clone(),
})
}
pub fn to_peer_source(&self) -> PeerSource {
PeerSource::from_tracker(self)
}
pub fn scheme(&self) -> &TrackerScheme {
&self.scheme
}
pub fn url(&self) -> &str {
self.url.as_str()
}
}
impl<'de> Deserialize<'de> for Tracker {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Tracker::new(&s).map_err(serde::de::Error::custom)
}
}
impl Serialize for Tracker {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.url.serialize(serializer)
}
}
impl FromStr for Tracker {
type Err = TrackerError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum TrackerScheme {
Websocket,
Http,
Udp,
#[cfg(feature = "unknown_tracker_scheme")]
Unknown(String),
}
impl FromStr for TrackerScheme {
type Err = TrackerError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"http" | "https" => Ok(Self::Http),
"ws" => Ok(Self::Websocket),
"udp" => Ok(Self::Udp),
#[cfg(feature = "unknown_tracker_scheme")]
_ => Ok(Self::Unknown(s.to_string())),
#[cfg(not(feature = "unknown_tracker_scheme"))]
_ => Err(TrackerError::InvalidScheme {
scheme: s.to_string(),
}),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum TrackerError {
InvalidURL { source: UriParseError },
InvalidScheme { scheme: String },
}
impl std::fmt::Display for TrackerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TrackerError::InvalidURL { source } => write!(f, "Invalid URL: {source}"),
TrackerError::InvalidScheme { scheme } => write!(f, "Invalid scheme: {scheme}"),
}
}
}
impl std::error::Error for TrackerError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
TrackerError::InvalidURL { source } => Some(source),
TrackerError::InvalidScheme { scheme: _ } => None,
}
}
}
impl<Input> From<(UriParseError, Input)> for TrackerError {
fn from(e: (UriParseError, Input)) -> TrackerError {
TrackerError::InvalidURL { source: e.0 }
}
}
impl PeerSource {
pub fn new(url: &str) -> Result<PeerSource, TrackerError> {
Ok(Tracker::new(url)?.to_peer_source())
}
pub fn from_url(url: &Uri<String>) -> Result<PeerSource, TrackerError> {
Ok(Tracker::from_url(url)?.to_peer_source())
}
pub fn from_tracker(tracker: &Tracker) -> PeerSource {
PeerSource::Tracker(tracker.clone())
}
}
pub trait TryIntoTracker {
fn try_into_tracker(&self) -> Result<Tracker, TrackerError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg(not(feature = "unknown_tracker_scheme"))]
fn fail_unknown_tracker_scheme() {
let tracker = Tracker::new("wtf://example.com:8000/announce");
assert!(tracker.is_err());
assert_eq!(
tracker.unwrap_err(),
TrackerError::InvalidScheme {
scheme: "wtf".to_string()
},
);
}
#[test]
fn fail_urlencoded_tracker() {
let tracker = Tracker::new("http%3F%2A%2A127.0.0.1:8000%2Aannounce");
assert!(tracker.is_err());
}
#[test]
fn parse_ipv4_literal() {
let tracker = Tracker::new("http://127.0.0.1:8000/announce");
assert!(tracker.is_ok());
}
#[test]
fn parse_ipv6_literal() {
let tracker = Tracker::new("http://[::1]:8000/announce");
assert!(tracker.is_ok());
}
}