Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a torrent announce subcommand #510

Merged
merged 1 commit into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ default-run = "imdl"

[features]
default = []
bench = ["rand"]
bench = []

[dependencies]
ansi_term = "0.12.0"
Expand All @@ -29,6 +29,7 @@ lexiclean = "0.0.1"
libc = "0.2.0"
log = "0.4.8"
md5 = "0.7.0"
rand = "0.7.3"
open = "1.4.0"
pretty_assertions = "0.6.0"
pretty_env_logger = "0.4.0"
Expand Down Expand Up @@ -65,10 +66,6 @@ features = ["default", "wrap_help"]
version = "2.1.1"
features = ["serde"]

[dependencies.rand]
version = "0.7.3"
optional = true

[dev-dependencies]
criterion = "0.3.0"
temptree = "0.0.0"
Expand Down
4 changes: 4 additions & 0 deletions bin/gen/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ examples:
text: "BitTorrent metainfo related functionality is under the `torrent` subcommand:"
code: "imdl torrent --help"

- command: imdl torrent announce
text: "Announce the infohash to all trackers in the supplied `.torrent` file, and print the peer lists that come back:"
code: "imdl torrent announce --input foo.torrent"

- command: imdl torrent create
text: "Intermodal can be used to create `.torrent` files:"
code: "imdl torrent create --input foo"
Expand Down
6 changes: 4 additions & 2 deletions src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ pub(crate) use std::{
hash::Hash,
io::{self, BufRead, BufReader, Cursor, Read, Write},
iter::{self, Sum},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs, UdpSocket},
num::{ParseFloatError, ParseIntError, TryFromIntError},
ops::{AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
path::{self, Path, PathBuf},
process::ExitStatus,
str::{self, FromStr},
string::FromUtf8Error,
sync::Once,
time::{SystemTime, SystemTimeError},
time::{Duration, SystemTime, SystemTimeError},
usize,
};

Expand All @@ -31,6 +32,7 @@ pub(crate) use ignore::WalkBuilder;
pub(crate) use indicatif::{ProgressBar, ProgressStyle};
pub(crate) use lexiclean::Lexiclean;
pub(crate) use libc::EXIT_FAILURE;
pub(crate) use rand::Rng;
pub(crate) use regex::{Regex, RegexSet};
pub(crate) use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer};
pub(crate) use serde_hex::SerHex;
Expand All @@ -52,7 +54,7 @@ pub(crate) use url::{Host, Url};
pub(crate) use log::trace;

// modules
pub(crate) use crate::{consts, error, host_port_parse_error, magnet_link_parse_error};
pub(crate) use crate::{consts, error, host_port_parse_error, magnet_link_parse_error, tracker};

// functions
pub(crate) use crate::xor_args::xor_args;
Expand Down
39 changes: 39 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ pub(crate) enum Error {
source: bendy::serde::Error,
input: InputTarget,
},
#[snafu(display("Torrent metainfo does not specify any usable trackers"))]
MetainfoMissingTrackers,
#[snafu(display("Failed to serialize torrent metainfo: {}", source))]
MetainfoSerialize { source: bendy::serde::Error },
#[snafu(display("Failed to decode metainfo bencode from {}: {}", input, error))]
Expand Down Expand Up @@ -136,6 +138,43 @@ pub(crate) enum Error {
SymlinkRoot { root: PathBuf },
#[snafu(display("Failed to retrieve system time: {}", source))]
SystemTime { source: SystemTimeError },
#[snafu(display("Compact peer list is not the expected length"))]
TrackerCompactPeerList,
#[snafu(display("Tracker exchange to `udp://{}` timed out.", tracker_addr))]
TrackerExchange { tracker_addr: SocketAddr },
#[snafu(display(
"Cannot connect to tracker `{}`: URL does not specify a valid host port",
tracker_url
))]
TrackerHostPort {
source: HostPortParseError,
tracker_url: Url,
},
#[snafu(display("Tracker client cannot announce without a connection id"))]
TrackerNoConnectionId,
#[snafu(display("Tracker resolved to no useable addresses"))]
TrackerNoHosts,
#[snafu(display("Malformed response from tracker"))]
TrackerResponse,
#[snafu(display("Response from tracker has wrong length: got {}; want {}", got, want))]
TrackerResponseLength { want: usize, got: usize },
#[snafu(display("Tracker failed to send datagram: {}", source))]
TrackerSend { source: io::Error },
#[snafu(display("Failed to resolve socket addrs: {}", source))]
TrackerSocketAddrs { source: io::Error },
#[snafu(display(
"Cannot connect to tracker `{}`: only UDP trackers are supported",
tracker_url
))]
TrackerUdpOnly { tracker_url: Url },
#[snafu(display("Failed to bind to UDP socket: {}", source))]
UdpSocketBind { source: io::Error },
#[snafu(display("Failed to connect to `udp://{}`: {}", addr, source))]
UdpSocketConnect { addr: SocketAddr, source: io::Error },
#[snafu(display("Failed to get local UDP socket address: {}", source))]
UdpSocketLocalAddress { source: io::Error },
#[snafu(display("Failed to set read timeout: {}", source))]
UdpSocketReadTimeout { source: io::Error },
#[snafu(display(
"Feature `{}` cannot be used without passing the `--unstable` flag",
feature
Expand Down
50 changes: 50 additions & 0 deletions src/host_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,42 @@ impl Display for HostPort {
}
}

impl TryFrom<&Url> for HostPort {
type Error = HostPortParseError;

fn try_from(url: &Url) -> Result<Self, HostPortParseError> {
match (url.host(), url.port()) {
(Some(host), Some(port)) => Ok(HostPort {
host: host.to_owned(),
port,
}),
(Some(_), None) => Err(HostPortParseError::PortMissing {
text: url.as_str().to_owned(),
}),
(None, Some(_)) => Err(HostPortParseError::HostMissing {
text: url.as_str().to_owned(),
}),
(None, None) => Err(HostPortParseError::HostPortMissing {
text: url.as_str().to_owned(),
}),
}
}
}

impl ToSocketAddrs for HostPort {
type Iter = std::vec::IntoIter<SocketAddr>;

fn to_socket_addrs(&self) -> io::Result<Self::Iter> {
let address = match &self.host {
Host::Domain(domain) => return (domain.clone(), self.port).to_socket_addrs(),
Host::Ipv4(address) => IpAddr::V4(*address),
Host::Ipv6(address) => IpAddr::V6(*address),
};

Ok(vec![SocketAddr::new(address, self.port)].into_iter())
}
}

#[derive(Serialize, Deserialize)]
struct Tuple(String, u16);

Expand Down Expand Up @@ -156,4 +192,18 @@ mod tests {
"l39:1234:5678:9abc:def0:1234:5678:9abc:def0i65000ee",
);
}

#[test]
fn test_from_url() {
let url = Url::parse("udp://imdl.io:12345").unwrap();
let host_port = HostPort::try_from(&url).unwrap();
assert_eq!(host_port.host, Host::Domain("imdl.io".into()));
assert_eq!(host_port.port, 12345);
}

#[test]
fn test_from_url_no_port() {
let url = Url::parse("udp://imdl.io").unwrap();
assert!(HostPort::try_from(&url).is_err());
}
}
4 changes: 4 additions & 0 deletions src/host_port_parse_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ pub(crate) enum HostPortParseError {
Port { text: String, source: ParseIntError },
#[snafu(display("Port missing: `{}`", text))]
PortMissing { text: String },
#[snafu(display("Host missing: `{}`", text))]
HostMissing { text: String },
#[snafu(display("Host and port missing: `{}`", text))]
HostPortMissing { text: String },
}
6 changes: 6 additions & 0 deletions src/infohash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ impl From<Sha1Digest> for Infohash {
}
}

impl From<Infohash> for [u8; 20] {
fn from(infohash: Infohash) -> Self {
infohash.inner.bytes()
}
}

impl From<Infohash> for Sha1Digest {
fn from(infohash: Infohash) -> Sha1Digest {
infohash.inner
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ mod style;
mod subcommand;
mod table;
mod torrent_summary;
mod tracker;
mod use_color;
mod verifier;
mod walker;
Expand Down
3 changes: 3 additions & 0 deletions src/subcommand/torrent.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::common::*;

mod announce;
mod create;
mod link;
mod piece_length;
Expand All @@ -14,6 +15,7 @@ mod verify;
about("Subcommands related to the BitTorrent protocol.")
)]
pub(crate) enum Torrent {
Announce(announce::Announce),
Create(create::Create),
Link(link::Link),
#[structopt(alias = "piece-size")]
Expand All @@ -26,6 +28,7 @@ pub(crate) enum Torrent {
impl Torrent {
pub(crate) fn run(self, env: &mut Env, options: &Options) -> Result<(), Error> {
match self {
Self::Announce(announce) => announce.run(env),
Self::Create(create) => create.run(env, options),
Self::Link(link) => link.run(env),
Self::PieceLength(piece_length) => piece_length.run(env),
Expand Down
Loading