1use url::form_urlencoded::byte_serialize;
2
3mod algorithm;
4pub mod clients;
5
6const PEER_ID_LENGTH: usize = 20;
7const KEY_LENGTH: usize = 8;
8
9#[derive(Debug, Clone)]
10pub enum RefreshInterval {
11 Never,
12 TimedOrAfterStartedAnnounce,
13 TorrentVolatile,
14 TorrentPersistent,
15}
16
17#[derive(Debug, Clone)]
18pub struct Client {
19 pub name: String,
20 pub key: u32,
21 pub peer_id: String,
22 pub key_refresh_every: Option<u16>,
23 pub query: String,
24 pub user_agent: String,
26 pub accept: String,
27 pub accept_encoding: String,
28 pub accept_language: String, pub connection: Option<String>,
30 pub num_want: u16,
32 pub num_want_on_stop: u16,
33
34 key_algorithm: algorithm::Algorithm, key_pattern: String,
39 key_refresh_on: RefreshInterval,
40 key_uppercase: Option<bool>,
41 peer_url_encode: bool,
42 peer_algorithm: algorithm::Algorithm,
44 peer_pattern: String,
46 peer_prefix: String,
48 peer_refresh_on: RefreshInterval,
49 encoding_exclusion_pattern: String,
51 uppercase_encoded_hex: bool,
53}
54
55impl Default for Client {
56 fn default() -> Self {
57 Client {
58 key_algorithm: algorithm::Algorithm::Hash,
61 key_pattern:String::new(),
62 key_uppercase: None,
63 key_refresh_on: RefreshInterval::TimedOrAfterStartedAnnounce,
64 key_refresh_every: None,
65 peer_algorithm: algorithm::Algorithm::Regex,
67 peer_pattern: String::new(), peer_prefix:String::new(),
68 peer_refresh_on: RefreshInterval::Never,
69 peer_url_encode: false,
70 encoding_exclusion_pattern: r"[A-Za-z0-9-]".to_owned(),
72 uppercase_encoded_hex: false,
73 num_want: 200,
75 num_want_on_stop: 0,
76 query: "info_hash={infohash}&peer_id={peerid}&port={port}&uploaded={uploaded}&downloaded={downloaded}&left={left}&corrupt=0&key={key}&event={event}&numwant={numwant}&compact=1&no_peer_id=1".to_owned(),
78 user_agent: String::with_capacity(64), accept: String::new(),
80 accept_encoding: String::from("gzip"),
81 accept_language: String::with_capacity(5),
82 connection: Some(String::from("Close")),
83 key: 0,
84 peer_id: String::new(),
85 name: String::from("INVALID"),
86 }
87 }
88}
89
90impl Client {
91 pub fn new() -> Client {
92 Client::default()
93 }
94
95 pub fn get_query(&self) -> (String, Vec<(String, String)>) {
109 let mut headers: Vec<(String, String)> = Vec::with_capacity(4);
110 if !self.user_agent.is_empty() {
111 headers.push((String::from("User-Agent"), self.user_agent.clone()));
112 }
113 if !self.accept.is_empty() {
114 headers.push((String::from("Accept"), self.accept.clone()));
115 }
116 if !self.accept_encoding.is_empty() {
117 headers.push((
118 String::from("Accept-Encoding"),
119 self.accept_encoding.clone(),
120 ));
121 }
122 if !self.accept_language.is_empty() {
123 headers.push((
124 String::from("Accept-Language"),
125 self.accept_language.clone(),
126 ));
127 }
128 (self.query.clone(), headers)
129 }
130
131 pub fn generate_key(&mut self) {
133 let key = match &self.key_algorithm {
134 algorithm::Algorithm::Hash => algorithm::hash(false, self.key_uppercase),
135 algorithm::Algorithm::HashNoLeadingZero => algorithm::hash(true, self.key_uppercase),
136 algorithm::Algorithm::DigitRangeTransformedToHexWithoutLeadingZeroes => {
137 algorithm::digit_range_transformed_to_hex_without_leading_zero()
138 }
139 algorithm::Algorithm::Regex => byte_serialize(
140 &algorithm::regex(self.peer_pattern.clone()).as_bytes()[0..KEY_LENGTH],
141 )
142 .collect(),
143 _ => String::with_capacity(KEY_LENGTH),
144 };
145 if let Ok(key) = key.parse::<u32>() {
146 self.key = key;
147 }
148 }
149 pub fn generate_peer_id(&mut self) {
151 let hash = match &self.peer_algorithm {
152 algorithm::Algorithm::Regex => algorithm::regex(self.peer_pattern.clone()), algorithm::Algorithm::RandomPoolWithChecksum => {
154 algorithm::random_pool_with_checksum(&self.peer_prefix, &self.peer_pattern)
155 }
156 _ => String::new(),
157 };
158 self.peer_id = byte_serialize(&hash.as_bytes()[0..PEER_ID_LENGTH]).collect();
159 }
161}
162
163#[cfg(test)]
164mod tests {
165 use crate::{clients::ClientVersion, Client};
166
167 const CLIENT_VERSIONS: [ClientVersion; 62] = [
168 ClientVersion::Bittorrent_7_10_1_43917,
169 ClientVersion::Bittorrent_7_10_3_44359,
170 ClientVersion::Bittorrent_7_10_3_44429,
171 ClientVersion::Deluge_1_3_13,
172 ClientVersion::Deluge_1_3_14,
173 ClientVersion::Deluge_1_3_15,
174 ClientVersion::Deluge_2_0_3,
175 ClientVersion::Leap_2_6_0_1,
176 ClientVersion::Rtorrent_0_9_6_0_13_6,
177 ClientVersion::Transmission_2_82_14160,
178 ClientVersion::Transmission_2_92_14714,
179 ClientVersion::Transmission_2_93,
180 ClientVersion::Transmission_2_94,
181 ClientVersion::Transmission_3_00,
182 ClientVersion::Utorrent_3_2_2_28500,
183 ClientVersion::Utorrent_3_5_0_43916,
184 ClientVersion::Utorrent_3_5_0_44090,
185 ClientVersion::Utorrent_3_5_0_44294,
186 ClientVersion::Utorrent_3_5_1_44332,
187 ClientVersion::Utorrent_3_5_3_44358,
188 ClientVersion::Utorrent_3_5_3_44428,
189 ClientVersion::Utorrent_3_5_4_44498,
190 ClientVersion::Vuze_5_7_5_0,
191 ClientVersion::Qbittorrent_3_3_1,
193 ClientVersion::Qbittorrent_3_3_13,
194 ClientVersion::Qbittorrent_3_3_14,
195 ClientVersion::Qbittorrent_3_3_15,
196 ClientVersion::Qbittorrent_3_3_16,
197 ClientVersion::Qbittorrent_3_3_7,
198 ClientVersion::Qbittorrent_4_0_0,
199 ClientVersion::Qbittorrent_4_0_1,
200 ClientVersion::Qbittorrent_4_0_2,
201 ClientVersion::Qbittorrent_4_0_3,
202 ClientVersion::Qbittorrent_4_0_4,
203 ClientVersion::Qbittorrent_4_1_0,
204 ClientVersion::Qbittorrent_4_1_1,
205 ClientVersion::Qbittorrent_4_1_2,
206 ClientVersion::Qbittorrent_4_1_3,
207 ClientVersion::Qbittorrent_4_1_4,
208 ClientVersion::Qbittorrent_4_1_5,
209 ClientVersion::Qbittorrent_4_1_6,
210 ClientVersion::Qbittorrent_4_1_7,
211 ClientVersion::Qbittorrent_4_1_8,
212 ClientVersion::Qbittorrent_4_1_9,
213 ClientVersion::Qbittorrent_4_2_0,
214 ClientVersion::Qbittorrent_4_2_1,
215 ClientVersion::Qbittorrent_4_2_2,
216 ClientVersion::Qbittorrent_4_2_3,
217 ClientVersion::Qbittorrent_4_2_4,
218 ClientVersion::Qbittorrent_4_2_5,
219 ClientVersion::Qbittorrent_4_3_0_1,
220 ClientVersion::Qbittorrent_4_3_0,
221 ClientVersion::Qbittorrent_4_3_1,
222 ClientVersion::Qbittorrent_4_3_2,
223 ClientVersion::Qbittorrent_4_3_3,
224 ClientVersion::Qbittorrent_4_3_4_1,
225 ClientVersion::Qbittorrent_4_3_5,
226 ClientVersion::Qbittorrent_4_3_6,
227 ClientVersion::Qbittorrent_4_3_8,
228 ClientVersion::Qbittorrent_4_3_9,
229 ClientVersion::Qbittorrent_4_4_2,
230 ClientVersion::Qbittorrent_4_4_3_1,
231 ];
232
233 #[test]
234 fn check_queries() {
235 for cv in crate::tests::CLIENT_VERSIONS {
236 let mut c = Client::new();
237 c.build(cv);
238 let q = c.query;
240 assert!(q.contains("info_hash={infohash}"));
241 assert!(q.contains("peer_id={peerid}"));
242 assert!(q.contains("uploaded={uploaded}"));
243 assert!(q.contains("downloaded={downloaded}"));
244 assert!(q.contains("left={left}"));
245 assert!(q.contains("key={key}"));
246 assert!(q.contains("event={event}"));
247 if !c.name.starts_with("rtorrent") {
248 assert!(q.contains("numwant={numwant}"));
249 }
250 if q.contains("ipv6=") || q.contains("{ipv6}") {
251 assert!(q.contains("ipv6={ipv6}"));
252 }
253 if q.contains("ip=") || q.contains("{ip}") {
254 assert!(q.contains("ip={ip}"));
255 }
256 assert!(!q.contains("&&"));
257 assert!(!q.starts_with('&'));
258 assert!(!q.ends_with('&'));
259 }
260 }
261}