1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
//! `Scrape` response for the HTTP tracker [`scrape`](crate::servers::http::v1::requests::scrape::Scrape) request.
//!
//! Data structures and logic to build the `scrape` response.
use std::borrow::Cow;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use bip_bencode::{ben_int, ben_map, BMutAccess};
use crate::tracker::ScrapeData;
/// The `Scrape` response for the HTTP tracker.
///
/// ```rust
/// use torrust_tracker::servers::http::v1::responses::scrape::Bencoded;
/// use torrust_tracker::shared::bit_torrent::info_hash::InfoHash;
/// use torrust_tracker::tracker::torrent::SwarmMetadata;
/// use torrust_tracker::tracker::ScrapeData;
///
/// let info_hash = InfoHash([0x69; 20]);
/// let mut scrape_data = ScrapeData::empty();
/// scrape_data.add_file(
/// &info_hash,
/// SwarmMetadata {
/// complete: 1,
/// downloaded: 2,
/// incomplete: 3,
/// },
/// );
///
/// let response = Bencoded::from(scrape_data);
///
/// let bytes = response.body();
///
/// // cspell:disable-next-line
/// let expected_bytes = b"d5:filesd20:iiiiiiiiiiiiiiiiiiiid8:completei1e10:downloadedi2e10:incompletei3eeee";
///
/// assert_eq!(
/// String::from_utf8(bytes).unwrap(),
/// String::from_utf8(expected_bytes.to_vec()).unwrap()
/// );
/// ```
#[derive(Debug, PartialEq, Default)]
pub struct Bencoded {
/// The scrape data to be bencoded.
scrape_data: ScrapeData,
}
impl Bencoded {
/// Returns the bencoded representation of the `Scrape` struct.
///
/// # Panics
///
/// Will return an error if it can't access the bencode as a mutable `BDictAccess`.
#[must_use]
pub fn body(&self) -> Vec<u8> {
let mut scrape_list = ben_map!();
let scrape_list_mut = scrape_list.dict_mut().unwrap();
for (info_hash, value) in &self.scrape_data.files {
scrape_list_mut.insert(
Cow::from(info_hash.bytes().to_vec()),
ben_map! {
"complete" => ben_int!(i64::from(value.complete)),
"downloaded" => ben_int!(i64::from(value.downloaded)),
"incomplete" => ben_int!(i64::from(value.incomplete))
},
);
}
(ben_map! {
"files" => scrape_list
})
.encode()
}
}
impl From<ScrapeData> for Bencoded {
fn from(scrape_data: ScrapeData) -> Self {
Self { scrape_data }
}
}
impl IntoResponse for Bencoded {
fn into_response(self) -> Response {
(StatusCode::OK, self.body()).into_response()
}
}
#[cfg(test)]
mod tests {
mod scrape_response {
use crate::servers::http::v1::responses::scrape::Bencoded;
use crate::shared::bit_torrent::info_hash::InfoHash;
use crate::tracker::torrent::SwarmMetadata;
use crate::tracker::ScrapeData;
fn sample_scrape_data() -> ScrapeData {
let info_hash = InfoHash([0x69; 20]);
let mut scrape_data = ScrapeData::empty();
scrape_data.add_file(
&info_hash,
SwarmMetadata {
complete: 1,
downloaded: 2,
incomplete: 3,
},
);
scrape_data
}
#[test]
fn should_be_converted_from_scrape_data() {
let response = Bencoded::from(sample_scrape_data());
assert_eq!(
response,
Bencoded {
scrape_data: sample_scrape_data()
}
);
}
#[test]
fn should_be_bencoded() {
let response = Bencoded {
scrape_data: sample_scrape_data(),
};
let bytes = response.body();
// cspell:disable-next-line
let expected_bytes = b"d5:filesd20:iiiiiiiiiiiiiiiiiiiid8:completei1e10:downloadedi2e10:incompletei3eeee";
assert_eq!(
String::from_utf8(bytes).unwrap(),
String::from_utf8(expected_bytes.to_vec()).unwrap()
);
}
}
}