Skip to content

Commit

Permalink
test(http): [torrust#159] add test for missing announce req params in…
Browse files Browse the repository at this point in the history
… http tracker
  • Loading branch information
josecelano committed Jan 23, 2023
1 parent 7fa8ec8 commit 62dbffa
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 45 deletions.
3 changes: 3 additions & 0 deletions cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"Avicora",
"Azureus",
"bencode",
"bencoded",
"binascii",
"Bitflu",
"bools",
Expand Down Expand Up @@ -39,6 +40,7 @@
"nanos",
"nextest",
"nocapture",
"numwant",
"oneshot",
"ostr",
"Pando",
Expand All @@ -62,6 +64,7 @@
"Torrentstorm",
"torrust",
"torrustracker",
"trackerid",
"typenum",
"Unamed",
"untuple",
Expand Down
10 changes: 8 additions & 2 deletions src/http/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
//! Tracker HTTP/HTTPS Protocol:
//!
//! <https://wiki.theory.org/BitTorrentSpecification#Tracker_HTTP.2FHTTPS_Protocol>
//! <https://wiki.theory.org/BitTorrent_Tracker_Protocol>
//! Original specification in BEP 3 (section "Trackers"):
//!
//! <https://www.bittorrent.org/beps/bep_0003.html>
//!
//! Other resources:
//!
//! - <https://wiki.theory.org/BitTorrentSpecification#Tracker_HTTP.2FHTTPS_Protocol>
//! - <https://wiki.theory.org/BitTorrent_Tracker_Protocol>
//!
pub mod error;
pub mod filters;
Expand Down
61 changes: 55 additions & 6 deletions tests/http/asserts.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
use reqwest::Response;

use super::responses::Announce;

pub async fn assert_internal_server_error(response: Response) {
assert_eq!(response.status(), 200);
/* cspell:disable-next-line */
assert_eq!(response.text().await.unwrap(), "d14:failure reason21:internal server errore");
}
use crate::http::responses::Error;

pub async fn assert_empty_announce_response(response: Response) {
assert_eq!(response.status(), 200);
Expand All @@ -19,3 +14,57 @@ pub async fn assert_announce_response(response: Response, expected_announce_resp
let announce_response: Announce = serde_bencode::from_str(&response.text().await.unwrap()).unwrap();
assert_eq!(announce_response, *expected_announce_response);
}

pub async fn assert_is_announce_response(response: Response) {
assert_eq!(response.status(), 200);
let body = response.text().await.unwrap();
let _announce_response: Announce = serde_bencode::from_str(&body)
.unwrap_or_else(|_| panic!("response body should be a valid announce response, got \"{}\"", &body));
}

// Error responses

pub async fn assert_internal_server_error_response(response: Response) {
assert_eq!(response.status(), 200);
let body = response.text().await.unwrap();
let error_response: Error = serde_bencode::from_str(&body).unwrap_or_else(|_| {
panic!(
"response body should be a valid bencoded string for the 'internal server' error, got \"{}\"",
&body
)
});
let expected_error_response = Error {
failure_reason: "internal server error".to_string(),
};
assert_eq!(error_response, expected_error_response);
}

pub async fn assert_invalid_info_hash_error_response(response: Response) {
assert_eq!(response.status(), 200);
let body = response.text().await.unwrap();
let error_response: Error = serde_bencode::from_str(&body).unwrap_or_else(|_| {
panic!(
"response body should be a valid bencoded string for the 'invalid info_hash' error, got \"{}\"",
&body
)
});
let expected_error_response = Error {
failure_reason: "info_hash is either missing or invalid".to_string(),
};
assert_eq!(error_response, expected_error_response);
}

pub async fn assert_invalid_peer_id_error_response(response: Response) {
assert_eq!(response.status(), 200);
let body = response.text().await.unwrap();
let error_response: Error = serde_bencode::from_str(&body).unwrap_or_else(|_| {
panic!(
"response body should be a valid bencoded string for the 'invalid peer id' error, got \"{}\"",
&body
)
});
let expected_error_response = Error {
failure_reason: "peer_id is either missing or invalid".to_string(),
};
assert_eq!(error_response, expected_error_response);
}
146 changes: 116 additions & 30 deletions tests/http/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ impl fmt::Display for AnnounceQuery {
///
/// <https://wiki.theory.org/BitTorrentSpecification#Tracker_HTTP.2FHTTPS_Protocol>
///
/// Some parameters are not implemented yet.
/// Some parameters in the specification are not implemented in this tracker yet.
impl AnnounceQuery {
/// It builds the URL query component for the announce request.
///
Expand All @@ -38,35 +38,11 @@ impl AnnounceQuery {
///
/// <https://github.com/seanmonstar/reqwest/issues/1613>
pub fn build(&self) -> String {
let mut params = vec![
(
"info_hash",
percent_encoding::percent_encode(&self.info_hash, NON_ALPHANUMERIC).to_string(),
),
("peer_addr", self.peer_addr.to_string()),
("downloaded", self.downloaded.to_string()),
("uploaded", self.uploaded.to_string()),
(
"peer_id",
percent_encoding::percent_encode(&self.peer_id, NON_ALPHANUMERIC).to_string(),
),
("port", self.port.to_string()),
("left", self.left.to_string()),
];

if let Some(event) = &self.event {
params.push(("event", event.to_string()));
}

if let Some(compact) = &self.compact {
params.push(("compact", compact.to_string()));
}
self.params().to_string()
}

params
.iter()
.map(|param| format!("{}={}", param.0, param.1))
.collect::<Vec<String>>()
.join("&")
pub fn params(&self) -> AnnounceQueryParams {
AnnounceQueryParams::from(self)
}
}

Expand Down Expand Up @@ -138,7 +114,117 @@ impl AnnounceQueryBuilder {
self
}

pub fn into(self) -> AnnounceQuery {
pub fn query(self) -> AnnounceQuery {
self.announce_query
}
}

/// It contains all the GET parameters that can be used in a HTTP Announce request.
///
/// Sample Announce URL with all the GET parameters (mandatory and optional):
///
/// ```text
/// http://127.0.0.1:7070/announce?
/// info_hash=%9C8B%22%13%E3%0B%FF%21%2B0%C3%60%D2o%9A%02%13d%22 (mandatory)
/// peer_addr=192.168.1.88
/// downloaded=0
/// uploaded=0
/// peer_id=%2DqB00000000000000000 (mandatory)
/// port=17548 (mandatory)
/// left=0
/// event=completed
/// compact=0
/// ```
pub struct AnnounceQueryParams {
pub info_hash: Option<String>,
pub peer_addr: Option<String>,
pub downloaded: Option<String>,
pub uploaded: Option<String>,
pub peer_id: Option<String>,
pub port: Option<String>,
pub left: Option<String>,
pub event: Option<String>,
pub compact: Option<String>,
}

impl std::fmt::Display for AnnounceQueryParams {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut params = vec![];

if let Some(info_hash) = &self.info_hash {
params.push(("info_hash", info_hash));
}
if let Some(peer_addr) = &self.peer_addr {
params.push(("peer_addr", peer_addr));
}
if let Some(downloaded) = &self.downloaded {
params.push(("downloaded", downloaded));
}
if let Some(uploaded) = &self.uploaded {
params.push(("uploaded", uploaded));
}
if let Some(peer_id) = &self.peer_id {
params.push(("peer_id", peer_id));
}
if let Some(port) = &self.port {
params.push(("port", port));
}
if let Some(left) = &self.left {
params.push(("left", left));
}
if let Some(event) = &self.event {
params.push(("event", event));
}
if let Some(compact) = &self.compact {
params.push(("compact", compact));
}

let query = params
.iter()
.map(|param| format!("{}={}", param.0, param.1))
.collect::<Vec<String>>()
.join("&");

write!(f, "{query}")
}
}

impl AnnounceQueryParams {
pub fn from(announce_query: &AnnounceQuery) -> Self {
let event = announce_query.event.as_ref().map(std::string::ToString::to_string);
let compact = announce_query.compact.as_ref().map(std::string::ToString::to_string);

Self {
info_hash: Some(percent_encoding::percent_encode(&announce_query.info_hash, NON_ALPHANUMERIC).to_string()),
peer_addr: Some(announce_query.peer_addr.to_string()),
downloaded: Some(announce_query.downloaded.to_string()),
uploaded: Some(announce_query.uploaded.to_string()),
peer_id: Some(percent_encoding::percent_encode(&announce_query.peer_id, NON_ALPHANUMERIC).to_string()),
port: Some(announce_query.port.to_string()),
left: Some(announce_query.left.to_string()),
event,
compact,
}
}

pub fn remove_optional_params(&mut self) {
// todo: make them optional with the Option<...> in the AnnounceQuery struct
// if they are really optional. SO that we can crete a minimal AnnounceQuery
// instead of removing the optional params afterwards.
//
// The original specification on:
// <https://www.bittorrent.org/beps/bep_0003.html>
// says only `ip` and `event` are optional.
//
// On <https://wiki.theory.org/BitTorrentSpecification#Tracker_Request_Parameters>
// says only `ip`, `numwant`, `key` and `trackerid` are optional.
//
// but the server is responding if all these params are not included.
self.peer_addr = None;
self.downloaded = None;
self.uploaded = None;
self.left = None;
self.event = None;
self.compact = None;
}
}
6 changes: 6 additions & 0 deletions tests/http/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,9 @@ pub struct DictionaryPeer {
pub peer_id: String,
pub port: u16,
}

#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct Error {
#[serde(rename = "failure reason")]
pub failure_reason: String,
}
Loading

0 comments on commit 62dbffa

Please sign in to comment.