intermodal/src/host_port.rs
Casey Rodarmor 57a358e458
Allow creating magnet links with imdl torrent link
Magnet links can now be created from a metainfo file with:

    imdl torrent link --input METAINFO

type: added
2020-04-07 19:01:27 -07:00

151 lines
3.3 KiB
Rust

use crate::common::*;
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct HostPort {
host: Host,
port: u16,
}
impl FromStr for HostPort {
type Err = HostPortParseError;
fn from_str(text: &str) -> Result<Self, Self::Err> {
let socket_address_re = Regex::new(
r"(?x)
^
(?P<host>.*?)
:
(?P<port>\d+?)
$
",
)
.unwrap();
if let Some(captures) = socket_address_re.captures(text) {
let host_text = captures.name("host").unwrap().as_str();
let port_text = captures.name("port").unwrap().as_str();
let host = Host::parse(&host_text).context(host_port_parse_error::Host {
text: text.to_owned(),
})?;
let port = port_text
.parse::<u16>()
.context(host_port_parse_error::Port {
text: text.to_owned(),
})?;
Ok(Self { host, port })
} else {
Err(HostPortParseError::PortMissing {
text: text.to_owned(),
})
}
}
}
impl Display for HostPort {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}:{}", self.host, self.port)
}
}
#[derive(Serialize, Deserialize)]
struct Tuple(String, u16);
impl From<&HostPort> for Tuple {
fn from(node: &HostPort) -> Self {
let host = match &node.host {
Host::Domain(domain) => domain.to_string(),
Host::Ipv4(ipv4) => ipv4.to_string(),
Host::Ipv6(ipv6) => ipv6.to_string(),
};
Self(host, node.port)
}
}
impl Serialize for HostPort {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Tuple::from(self).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for HostPort {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let tuple = Tuple::deserialize(deserializer)?;
let host = if tuple.0.contains(':') {
Host::parse(&format!("[{}]", tuple.0))
} else {
Host::parse(&tuple.0)
}
.map_err(|error| D::Error::custom(format!("Failed to parse node host: {}", error)))?;
Ok(HostPort {
host,
port: tuple.1,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
fn case(host: Host, port: u16, text: &str, bencode: &str) {
let node = HostPort { host, port };
let parsed: HostPort = text.parse().expect(&format!("Failed to parse {}", text));
assert_eq!(parsed, node);
let ser = bendy::serde::to_bytes(&node).unwrap();
assert_eq!(
ser,
bencode.as_bytes(),
"Unexpected serialization: {} != {}",
String::from_utf8_lossy(&ser),
bencode,
);
let de = bendy::serde::from_bytes::<HostPort>(&ser).unwrap();
assert_eq!(de, node);
}
#[test]
fn test_domain() {
case(
Host::Domain("imdl.com".to_owned()),
12,
"imdl.com:12",
"l8:imdl.comi12ee",
);
}
#[test]
fn test_ipv4() {
case(
Host::Ipv4(Ipv4Addr::new(1, 2, 3, 4)),
100,
"1.2.3.4:100",
"l7:1.2.3.4i100ee",
);
}
#[test]
fn test_ipv6() {
case(
Host::Ipv6(Ipv6Addr::new(
0x1234, 0x5678, 0x9ABC, 0xDEF0, 0x1234, 0x5678, 0x9ABC, 0xDEF0,
)),
65000,
"[1234:5678:9abc:def0:1234:5678:9abc:def0]:65000",
"l39:1234:5678:9abc:def0:1234:5678:9abc:def0i65000ee",
);
}
}