Skip to content

Commit

Permalink
Add a torrent announce subcommand
Browse files Browse the repository at this point in the history
This command makes use of a partially-implemented UDP tracker client to
announce an infohash and list the response.

type: added
  • Loading branch information
atomgardner committed Feb 10, 2023
1 parent f13e4db commit af9e13d
Show file tree
Hide file tree
Showing 16 changed files with 1,205 additions and 9 deletions.
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 a torrent to trackers and print returned peers:"
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, 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
19 changes: 19 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub(crate) enum Error {
FileSearch { source: ignore::Error },
#[snafu(display("Invalid glob: {}", source))]
GlobParse { source: globset::Error },
#[snafu(display("Failed to parse host and port: {}", source))]
HostPortParse { source: HostPortParseError },
#[snafu(display("Failed to serialize torrent info dictionary: {}", source))]
InfoSerialize { source: bendy::serde::Error },
#[snafu(display("Input target empty"))]
Expand All @@ -54,6 +56,8 @@ pub(crate) enum Error {
source: bendy::serde::Error,
input: InputTarget,
},
#[snafu(display("A peer source is required"))]
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 All @@ -66,6 +70,8 @@ pub(crate) enum Error {
input: InputTarget,
source: MetainfoError,
},
#[snafu(display("Network I/O error: {}", source))]
Network { source: io::Error },
#[snafu(display("Failed to invoke opener: {}", source))]
OpenerInvoke { source: io::Error },
#[snafu(display("Opener failed: {}", exit_status))]
Expand Down Expand Up @@ -136,6 +142,19 @@ pub(crate) enum Error {
SymlinkRoot { root: PathBuf },
#[snafu(display("Failed to retrieve system time: {}", source))]
SystemTime { source: SystemTimeError },
#[snafu(display("Failed to resolve UDP tracker `{}`: {}.", host_port, source))]
TrackerDnsResolution {
host_port: HostPort,
source: io::Error,
},
#[snafu(display("UDP tracker resolved `{}` to no useable addresses.", host_port))]
TrackerNoHosts { host_port: HostPort },
#[snafu(display("Connection to UDP tracker `{}` timed out.", host_port))]
TrackerTimeout { host_port: HostPort },
#[snafu(display("Malformed response from UDP tracker."))]
TrackerResponse,
#[snafu(display("Response from UDP tracker wrong length: got {}; want {}.", got, want))]
TrackerResponseLength { want: usize, got: usize },
#[snafu(display(
"Feature `{}` cannot be used without passing the `--unstable` flag",
feature
Expand Down
44 changes: 42 additions & 2 deletions src/host_port.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,34 @@ use crate::common::*;

#[derive(Debug, PartialEq, Clone)]
pub(crate) struct HostPort {
host: Host,
port: u16,
pub host: Host,
pub port: u16,
}

impl HostPort {
pub(crate) fn from_url(url: &Url) -> Option<Self> {
match (url.host(), url.port()) {
(Some(host), Some(port)) => Some(HostPort {
host: host.to_owned(),
port,
}),
_ => None,
}
}
}

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())
}
}

impl FromStr for HostPort {
Expand Down Expand Up @@ -156,4 +182,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::from_url(&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::from_url(&url).is_none());
}
}
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

0 comments on commit af9e13d

Please sign in to comment.