1use std::net::SocketAddr;
2use std::os::fd::{AsFd, AsRawFd};
3use std::time::Duration;
4
5use tokio::net::UdpSocket;
6
7use irontide_core::Id20;
8
9use crate::compact::{parse_compact_peers, parse_compact_peers6};
10use crate::error::{Error, Result};
11
12fn apply_dscp_udp(socket: &UdpSocket, dscp: u8, is_ipv6: bool) {
14 if dscp == 0 {
15 return;
16 }
17 let tos = (dscp as u32) << 2;
18 let fd = socket.as_fd().as_raw_fd();
19 let result = unsafe {
20 if is_ipv6 {
21 libc::setsockopt(
22 fd,
23 libc::IPPROTO_IPV6,
24 libc::IPV6_TCLASS,
25 &(tos as libc::c_int) as *const _ as *const libc::c_void,
26 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
27 )
28 } else {
29 libc::setsockopt(
30 fd,
31 libc::IPPROTO_IP,
32 libc::IP_TOS,
33 &(tos as libc::c_int) as *const _ as *const libc::c_void,
34 std::mem::size_of::<libc::c_int>() as libc::socklen_t,
35 )
36 }
37 };
38 if result != 0 {
39 tracing::debug!(
40 dscp,
41 "failed to set DSCP on UDP tracker socket: {}",
42 std::io::Error::last_os_error()
43 );
44 }
45}
46use crate::{AnnounceRequest, AnnounceResponse, ScrapeInfo};
47
48#[derive(Debug, Clone, PartialEq, Eq)]
55pub enum UdpTrackerOption {
56 EndOfOptions,
58 Nop,
60 UrlData(String),
62 Unknown {
64 option_type: u8,
66 data: Vec<u8>,
68 },
69}
70
71fn parse_udp_options(data: &[u8]) -> Vec<UdpTrackerOption> {
77 let mut options = Vec::new();
78 let mut pos = 0;
79 while pos < data.len() {
80 let opt_type = data[pos];
81 pos += 1;
82 match opt_type {
83 0x00 => {
84 options.push(UdpTrackerOption::EndOfOptions);
85 break;
86 }
87 0x01 => {
88 options.push(UdpTrackerOption::Nop);
89 }
90 _ => {
91 if pos >= data.len() {
93 break;
94 }
95 let length = if opt_type < 0x80 {
96 let l = data[pos] as usize;
97 pos += 1;
98 l
99 } else {
100 if pos.checked_add(1).is_none_or(|end| end >= data.len()) {
101 break;
102 }
103 let l =
104 u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
105 pos += 2;
106 l
107 };
108 if pos.checked_add(length).is_none_or(|end| end > data.len()) {
109 break;
110 }
111 let value = &data[pos..pos + length];
112 pos += length;
113 match opt_type {
114 0x02 => {
115 if let Ok(url) = std::str::from_utf8(value) {
116 options
117 .push(UdpTrackerOption::UrlData(url.to_owned()));
118 }
119 }
120 _ => {
121 options.push(UdpTrackerOption::Unknown {
122 option_type: opt_type,
123 data: value.to_vec(),
124 });
125 }
126 }
127 }
128 }
129 }
130 options
131}
132
133const CONNECT_MAGIC: u64 = 0x0417_2710_1980;
135const ACTION_CONNECT: u32 = 0;
136const ACTION_ANNOUNCE: u32 = 1;
137const ACTION_SCRAPE: u32 = 2;
138
139const UDP_TIMEOUT: Duration = Duration::from_secs(15);
141
142#[derive(Clone)]
144pub struct UdpTracker {
145 timeout: Duration,
146 dscp: u8,
147}
148
149#[derive(Debug, Clone)]
151pub struct UdpAnnounceResponse {
152 pub response: AnnounceResponse,
154 pub transaction_id: u32,
156 pub options: Vec<UdpTrackerOption>,
158}
159
160#[derive(Debug, Clone)]
162pub struct UdpScrapeResponse {
163 pub results: Vec<ScrapeInfo>,
165 pub transaction_id: u32,
167}
168
169impl UdpTracker {
170 pub fn new() -> Self {
172 UdpTracker {
173 timeout: UDP_TIMEOUT,
174 dscp: 0,
175 }
176 }
177
178 pub fn with_timeout(mut self, timeout: Duration) -> Self {
180 self.timeout = timeout;
181 self
182 }
183
184 pub fn with_dscp(mut self, dscp: u8) -> Self {
186 self.dscp = dscp;
187 self
188 }
189
190 pub fn build_connect_request(transaction_id: u32) -> [u8; 16] {
192 let mut buf = [0u8; 16];
193 buf[0..8].copy_from_slice(&CONNECT_MAGIC.to_be_bytes());
194 buf[8..12].copy_from_slice(&ACTION_CONNECT.to_be_bytes());
195 buf[12..16].copy_from_slice(&transaction_id.to_be_bytes());
196 buf
197 }
198
199 pub fn parse_connect_response(data: &[u8], expected_transaction_id: u32) -> Result<u64> {
201 if data.len() < 16 {
202 return Err(Error::UdpProtocol(format!(
203 "connect response too short: {} bytes",
204 data.len()
205 )));
206 }
207
208 let action = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
209 if action != ACTION_CONNECT {
210 return Err(Error::UdpProtocol(format!(
211 "expected action 0 (connect), got {action}"
212 )));
213 }
214
215 let transaction_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
216 if transaction_id != expected_transaction_id {
217 return Err(Error::UdpProtocol(format!(
218 "transaction ID mismatch: expected {expected_transaction_id}, got {transaction_id}"
219 )));
220 }
221
222 let connection_id = u64::from_be_bytes([
223 data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15],
224 ]);
225
226 Ok(connection_id)
227 }
228
229 pub fn build_announce_request(
231 connection_id: u64,
232 transaction_id: u32,
233 req: &AnnounceRequest,
234 ) -> Vec<u8> {
235 let mut buf = Vec::with_capacity(98);
236 buf.extend_from_slice(&connection_id.to_be_bytes());
237 buf.extend_from_slice(&ACTION_ANNOUNCE.to_be_bytes());
238 buf.extend_from_slice(&transaction_id.to_be_bytes());
239 buf.extend_from_slice(req.info_hash.as_bytes());
240 buf.extend_from_slice(req.peer_id.as_bytes());
241 buf.extend_from_slice(&req.downloaded.to_be_bytes());
242 buf.extend_from_slice(&req.left.to_be_bytes());
243 buf.extend_from_slice(&req.uploaded.to_be_bytes());
244 buf.extend_from_slice(&(req.event as u32).to_be_bytes());
245 buf.extend_from_slice(&0u32.to_be_bytes()); buf.extend_from_slice(&0u32.to_be_bytes()); buf.extend_from_slice(&req.num_want.unwrap_or(-1i32).to_be_bytes());
248 buf.extend_from_slice(&req.port.to_be_bytes());
249 buf
250 }
251
252 pub fn parse_announce_response(
254 data: &[u8],
255 expected_transaction_id: u32,
256 ) -> Result<UdpAnnounceResponse> {
257 if data.len() < 20 {
258 return Err(Error::UdpProtocol(format!(
259 "announce response too short: {} bytes",
260 data.len()
261 )));
262 }
263
264 let action = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
265 if action != ACTION_ANNOUNCE {
266 if action == 3 && data.len() > 8 {
268 let msg = String::from_utf8_lossy(&data[8..]);
269 return Err(Error::TrackerError(msg.into_owned()));
270 }
271 return Err(Error::UdpProtocol(format!(
272 "expected action 1 (announce), got {action}"
273 )));
274 }
275
276 let transaction_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
277 if transaction_id != expected_transaction_id {
278 return Err(Error::UdpProtocol(format!(
279 "transaction ID mismatch: expected {expected_transaction_id}, got {transaction_id}"
280 )));
281 }
282
283 let interval = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
284 let leechers = u32::from_be_bytes([data[12], data[13], data[14], data[15]]);
285 let seeders = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
286
287 let peer_data = &data[20..];
290 let peer_size = 6; let num_peers = peer_data.len() / peer_size;
292 let peers_end = num_peers * peer_size;
293 let peers = parse_compact_peers(&peer_data[..peers_end])?;
294 let options = if peers_end < peer_data.len() {
295 parse_udp_options(&peer_data[peers_end..])
296 } else {
297 Vec::new()
298 };
299
300 Ok(UdpAnnounceResponse {
301 response: AnnounceResponse {
302 interval,
303 seeders: Some(seeders),
304 leechers: Some(leechers),
305 peers,
306 },
307 transaction_id,
308 options,
309 })
310 }
311
312 pub fn parse_announce_response_v6(
317 data: &[u8],
318 expected_transaction_id: u32,
319 ) -> Result<UdpAnnounceResponse> {
320 if data.len() < 20 {
321 return Err(Error::UdpProtocol(format!(
322 "announce response too short: {} bytes",
323 data.len()
324 )));
325 }
326
327 let action = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
328 if action != ACTION_ANNOUNCE {
329 if action == 3 && data.len() > 8 {
330 let msg = String::from_utf8_lossy(&data[8..]);
331 return Err(Error::TrackerError(msg.into_owned()));
332 }
333 return Err(Error::UdpProtocol(format!(
334 "expected action 1 (announce), got {action}"
335 )));
336 }
337
338 let transaction_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
339 if transaction_id != expected_transaction_id {
340 return Err(Error::UdpProtocol(format!(
341 "transaction ID mismatch: expected {expected_transaction_id}, got {transaction_id}"
342 )));
343 }
344
345 let interval = u32::from_be_bytes([data[8], data[9], data[10], data[11]]);
346 let leechers = u32::from_be_bytes([data[12], data[13], data[14], data[15]]);
347 let seeders = u32::from_be_bytes([data[16], data[17], data[18], data[19]]);
348
349 let peer_data = &data[20..];
352 let peer_size = 18; let num_peers = peer_data.len() / peer_size;
354 let peers_end = num_peers * peer_size;
355 let peers = parse_compact_peers6(&peer_data[..peers_end])?;
356 let options = if peers_end < peer_data.len() {
357 parse_udp_options(&peer_data[peers_end..])
358 } else {
359 Vec::new()
360 };
361
362 Ok(UdpAnnounceResponse {
363 response: AnnounceResponse {
364 interval,
365 seeders: Some(seeders),
366 leechers: Some(leechers),
367 peers,
368 },
369 transaction_id,
370 options,
371 })
372 }
373
374 pub async fn announce(
376 &self,
377 tracker_addr: &str,
378 req: &AnnounceRequest,
379 ) -> Result<UdpAnnounceResponse> {
380 let addr: SocketAddr = match tracker_addr.parse() {
382 Ok(sa) => sa,
383 Err(_) => tokio::net::lookup_host(tracker_addr)
384 .await
385 .map_err(|e| {
386 Error::InvalidUrl(format!("DNS lookup failed for {tracker_addr}: {e}"))
387 })?
388 .next()
389 .ok_or_else(|| Error::InvalidUrl(format!("no addresses for {tracker_addr}")))?,
390 };
391
392 let bind_addr = if addr.is_ipv6() {
393 "[::]:0"
394 } else {
395 "0.0.0.0:0"
396 };
397 let socket = UdpSocket::bind(bind_addr).await?;
398 apply_dscp_udp(&socket, self.dscp, addr.is_ipv6());
399 socket.connect(addr).await?;
400
401 let txn_id = generate_transaction_id();
403 let connect_req = Self::build_connect_request(txn_id);
404 socket.send(&connect_req).await?;
405
406 let mut buf = [0u8; 2048];
407 let n = tokio::time::timeout(self.timeout, socket.recv(&mut buf))
408 .await
409 .map_err(|_| Error::Timeout)??;
410
411 let connection_id = Self::parse_connect_response(&buf[..n], txn_id)?;
412
413 let txn_id2 = generate_transaction_id();
415 let announce_req = Self::build_announce_request(connection_id, txn_id2, req);
416 socket.send(&announce_req).await?;
417
418 let n = tokio::time::timeout(self.timeout, socket.recv(&mut buf))
419 .await
420 .map_err(|_| Error::Timeout)??;
421
422 if addr.is_ipv6() {
423 Self::parse_announce_response_v6(&buf[..n], txn_id2)
424 } else {
425 Self::parse_announce_response(&buf[..n], txn_id2)
426 }
427 }
428
429 pub fn build_scrape_request(
433 connection_id: u64,
434 transaction_id: u32,
435 info_hashes: &[Id20],
436 ) -> Vec<u8> {
437 let mut buf = Vec::with_capacity(16 + 20 * info_hashes.len());
438 buf.extend_from_slice(&connection_id.to_be_bytes());
439 buf.extend_from_slice(&ACTION_SCRAPE.to_be_bytes());
440 buf.extend_from_slice(&transaction_id.to_be_bytes());
441 for hash in info_hashes {
442 buf.extend_from_slice(hash.as_bytes());
443 }
444 buf
445 }
446
447 pub fn parse_scrape_response(
451 data: &[u8],
452 expected_transaction_id: u32,
453 ) -> Result<UdpScrapeResponse> {
454 if data.len() < 8 {
455 return Err(Error::UdpProtocol(format!(
456 "scrape response too short: {} bytes",
457 data.len()
458 )));
459 }
460
461 let action = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
462 if action == 3 && data.len() > 8 {
463 let msg = String::from_utf8_lossy(&data[8..]);
464 return Err(Error::TrackerError(msg.into_owned()));
465 }
466 if action != ACTION_SCRAPE {
467 return Err(Error::UdpProtocol(format!(
468 "expected action 2 (scrape), got {action}"
469 )));
470 }
471
472 let transaction_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
473 if transaction_id != expected_transaction_id {
474 return Err(Error::UdpProtocol(format!(
475 "transaction ID mismatch: expected {expected_transaction_id}, got {transaction_id}"
476 )));
477 }
478
479 let payload = &data[8..];
480 if !payload.len().is_multiple_of(12) {
481 return Err(Error::UdpProtocol(format!(
482 "scrape payload not divisible by 12: {} bytes",
483 payload.len()
484 )));
485 }
486
487 let mut results = Vec::with_capacity(payload.len() / 12);
488 for chunk in payload.chunks_exact(12) {
489 let complete = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
490 let downloaded = u32::from_be_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]);
491 let incomplete = u32::from_be_bytes([chunk[8], chunk[9], chunk[10], chunk[11]]);
492 results.push(ScrapeInfo {
493 complete,
494 incomplete,
495 downloaded,
496 });
497 }
498
499 Ok(UdpScrapeResponse {
500 results,
501 transaction_id,
502 })
503 }
504
505 pub async fn scrape(
507 &self,
508 tracker_addr: &str,
509 info_hashes: &[Id20],
510 ) -> Result<UdpScrapeResponse> {
511 let addr: SocketAddr = match tracker_addr.parse() {
513 Ok(sa) => sa,
514 Err(_) => tokio::net::lookup_host(tracker_addr)
515 .await
516 .map_err(|e| {
517 Error::InvalidUrl(format!("DNS lookup failed for {tracker_addr}: {e}"))
518 })?
519 .next()
520 .ok_or_else(|| Error::InvalidUrl(format!("no addresses for {tracker_addr}")))?,
521 };
522
523 let bind_addr = if addr.is_ipv6() {
524 "[::]:0"
525 } else {
526 "0.0.0.0:0"
527 };
528 let socket = UdpSocket::bind(bind_addr).await?;
529 apply_dscp_udp(&socket, self.dscp, addr.is_ipv6());
530 socket.connect(addr).await?;
531
532 let txn_id = generate_transaction_id();
534 let connect_req = Self::build_connect_request(txn_id);
535 socket.send(&connect_req).await?;
536
537 let mut buf = [0u8; 2048];
538 let n = tokio::time::timeout(self.timeout, socket.recv(&mut buf))
539 .await
540 .map_err(|_| Error::Timeout)??;
541
542 let connection_id = Self::parse_connect_response(&buf[..n], txn_id)?;
543
544 let txn_id2 = generate_transaction_id();
546 let scrape_req = Self::build_scrape_request(connection_id, txn_id2, info_hashes);
547 socket.send(&scrape_req).await?;
548
549 let n = tokio::time::timeout(self.timeout, socket.recv(&mut buf))
550 .await
551 .map_err(|_| Error::Timeout)??;
552
553 Self::parse_scrape_response(&buf[..n], txn_id2)
554 }
555}
556
557impl Default for UdpTracker {
558 fn default() -> Self {
559 Self::new()
560 }
561}
562
563fn generate_transaction_id() -> u32 {
564 use std::time::SystemTime;
565 SystemTime::now()
566 .duration_since(SystemTime::UNIX_EPOCH)
567 .unwrap_or_default()
568 .subsec_nanos()
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574 use crate::AnnounceEvent;
575 use irontide_core::Id20;
576
577 fn test_request() -> AnnounceRequest {
578 AnnounceRequest {
579 info_hash: Id20::from_hex("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d").unwrap(),
580 peer_id: Id20::from_hex("0102030405060708091011121314151617181920").unwrap(),
581 port: 6881,
582 uploaded: 0,
583 downloaded: 0,
584 left: 1000000,
585 event: AnnounceEvent::Started,
586 num_want: Some(50),
587 compact: true,
588 i2p_destination: None,
589 }
590 }
591
592 #[test]
593 fn connect_request_format() {
594 let req = UdpTracker::build_connect_request(12345);
595 assert_eq!(req.len(), 16);
596 assert_eq!(
598 u64::from_be_bytes(req[0..8].try_into().unwrap()),
599 CONNECT_MAGIC
600 );
601 assert_eq!(u32::from_be_bytes(req[8..12].try_into().unwrap()), 0);
603 assert_eq!(u32::from_be_bytes(req[12..16].try_into().unwrap()), 12345);
605 }
606
607 #[test]
608 fn connect_response_parse() {
609 let mut resp = [0u8; 16];
610 resp[0..4].copy_from_slice(&0u32.to_be_bytes()); resp[4..8].copy_from_slice(&12345u32.to_be_bytes()); resp[8..16].copy_from_slice(&99999u64.to_be_bytes()); let conn_id = UdpTracker::parse_connect_response(&resp, 12345).unwrap();
615 assert_eq!(conn_id, 99999);
616 }
617
618 #[test]
619 fn connect_response_wrong_txn() {
620 let mut resp = [0u8; 16];
621 resp[0..4].copy_from_slice(&0u32.to_be_bytes());
622 resp[4..8].copy_from_slice(&12345u32.to_be_bytes());
623 resp[8..16].copy_from_slice(&99999u64.to_be_bytes());
624
625 assert!(UdpTracker::parse_connect_response(&resp, 99999).is_err());
626 }
627
628 #[test]
629 fn announce_request_format() {
630 let req = test_request();
631 let data = UdpTracker::build_announce_request(42, 100, &req);
632 assert_eq!(data.len(), 98);
633
634 assert_eq!(u64::from_be_bytes(data[0..8].try_into().unwrap()), 42);
636 assert_eq!(u32::from_be_bytes(data[8..12].try_into().unwrap()), 1);
638 assert_eq!(u32::from_be_bytes(data[12..16].try_into().unwrap()), 100);
640 assert_eq!(u16::from_be_bytes(data[96..98].try_into().unwrap()), 6881);
642 }
643
644 #[test]
645 fn announce_response_parse() {
646 let mut resp = Vec::new();
647 resp.extend_from_slice(&1u32.to_be_bytes()); resp.extend_from_slice(&42u32.to_be_bytes()); resp.extend_from_slice(&1800u32.to_be_bytes()); resp.extend_from_slice(&5u32.to_be_bytes()); resp.extend_from_slice(&10u32.to_be_bytes()); resp.extend_from_slice(&[192, 168, 1, 1, 0x1A, 0xE1]);
654
655 let parsed = UdpTracker::parse_announce_response(&resp, 42).unwrap();
656 assert_eq!(parsed.response.interval, 1800);
657 assert_eq!(parsed.response.seeders, Some(10));
658 assert_eq!(parsed.response.leechers, Some(5));
659 assert_eq!(parsed.response.peers.len(), 1);
660 assert_eq!(parsed.response.peers[0].to_string(), "192.168.1.1:6881");
661 }
662
663 #[test]
664 fn announce_response_error() {
665 let mut resp = Vec::new();
666 resp.extend_from_slice(&3u32.to_be_bytes()); resp.extend_from_slice(&42u32.to_be_bytes()); resp.extend_from_slice(b"torrent not found");
669
670 let result = UdpTracker::parse_announce_response(&resp, 42);
671 assert!(result.is_err());
672 }
673
674 #[test]
675 fn connect_response_too_short() {
676 assert!(UdpTracker::parse_connect_response(&[0u8; 10], 0).is_err());
677 }
678
679 #[test]
680 fn scrape_request_format() {
681 let hash1 = Id20::from_hex("aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d").unwrap();
682 let hash2 = Id20::from_hex("0102030405060708091011121314151617181920").unwrap();
683 let data = UdpTracker::build_scrape_request(42, 100, &[hash1, hash2]);
684 assert_eq!(data.len(), 16 + 40); assert_eq!(u64::from_be_bytes(data[0..8].try_into().unwrap()), 42);
686 assert_eq!(u32::from_be_bytes(data[8..12].try_into().unwrap()), 2); assert_eq!(u32::from_be_bytes(data[12..16].try_into().unwrap()), 100);
688 assert_eq!(&data[16..36], hash1.as_bytes());
689 assert_eq!(&data[36..56], hash2.as_bytes());
690 }
691
692 #[test]
693 fn scrape_response_parse() {
694 let mut resp = Vec::new();
695 resp.extend_from_slice(&2u32.to_be_bytes()); resp.extend_from_slice(&42u32.to_be_bytes()); resp.extend_from_slice(&10u32.to_be_bytes());
699 resp.extend_from_slice(&50u32.to_be_bytes());
700 resp.extend_from_slice(&3u32.to_be_bytes());
701
702 let parsed = UdpTracker::parse_scrape_response(&resp, 42).unwrap();
703 assert_eq!(parsed.results.len(), 1);
704 assert_eq!(parsed.results[0].complete, 10);
705 assert_eq!(parsed.results[0].downloaded, 50);
706 assert_eq!(parsed.results[0].incomplete, 3);
707 }
708
709 #[test]
710 fn scrape_response_multiple_hashes() {
711 let mut resp = Vec::new();
712 resp.extend_from_slice(&2u32.to_be_bytes());
713 resp.extend_from_slice(&42u32.to_be_bytes());
714 resp.extend_from_slice(&10u32.to_be_bytes());
716 resp.extend_from_slice(&50u32.to_be_bytes());
717 resp.extend_from_slice(&3u32.to_be_bytes());
718 resp.extend_from_slice(&20u32.to_be_bytes());
720 resp.extend_from_slice(&100u32.to_be_bytes());
721 resp.extend_from_slice(&5u32.to_be_bytes());
722
723 let parsed = UdpTracker::parse_scrape_response(&resp, 42).unwrap();
724 assert_eq!(parsed.results.len(), 2);
725 assert_eq!(parsed.results[1].complete, 20);
726 assert_eq!(parsed.results[1].downloaded, 100);
727 assert_eq!(parsed.results[1].incomplete, 5);
728 }
729
730 #[test]
731 fn scrape_response_wrong_action() {
732 let mut resp = Vec::new();
733 resp.extend_from_slice(&1u32.to_be_bytes()); resp.extend_from_slice(&42u32.to_be_bytes());
735
736 let result = UdpTracker::parse_scrape_response(&resp, 42);
737 assert!(result.is_err());
738 }
739
740 #[test]
741 fn scrape_response_too_short() {
742 let result = UdpTracker::parse_scrape_response(&[0u8; 4], 0);
743 assert!(result.is_err());
744 }
745
746 #[test]
747 fn announce_response_v6_parse() {
748 use std::net::Ipv6Addr;
749 let mut resp = Vec::new();
750 resp.extend_from_slice(&1u32.to_be_bytes()); resp.extend_from_slice(&42u32.to_be_bytes()); resp.extend_from_slice(&1800u32.to_be_bytes()); resp.extend_from_slice(&5u32.to_be_bytes()); resp.extend_from_slice(&10u32.to_be_bytes()); let ip: Ipv6Addr = "2001:db8::1".parse().unwrap();
757 resp.extend_from_slice(&ip.octets());
758 resp.extend_from_slice(&6881u16.to_be_bytes());
759
760 let parsed = UdpTracker::parse_announce_response_v6(&resp, 42).unwrap();
761 assert_eq!(parsed.response.interval, 1800);
762 assert_eq!(parsed.response.peers.len(), 1);
763 assert_eq!(
764 parsed.response.peers[0],
765 "[2001:db8::1]:6881".parse::<SocketAddr>().unwrap()
766 );
767 }
768
769 #[test]
770 fn udp_tracker_dscp_builder() {
771 let tracker = UdpTracker::new().with_dscp(0x2E);
772 assert_eq!(tracker.dscp, 0x2E);
773 }
774
775 #[test]
776 fn udp_tracker_default_no_dscp() {
777 let tracker = UdpTracker::new();
778 assert_eq!(tracker.dscp, 0);
779 }
780
781 #[test]
784 fn parse_udp_options_empty() {
785 let options = parse_udp_options(&[]);
786 assert!(options.is_empty());
787 }
788
789 #[test]
790 fn parse_udp_options_end_of_options() {
791 let options = parse_udp_options(&[0x00]);
792 assert_eq!(options, vec![UdpTrackerOption::EndOfOptions]);
793 }
794
795 #[test]
796 fn parse_udp_options_nop_and_end() {
797 let options = parse_udp_options(&[0x01, 0x00]);
798 assert_eq!(
799 options,
800 vec![UdpTrackerOption::Nop, UdpTrackerOption::EndOfOptions]
801 );
802 }
803
804 #[test]
805 fn parse_udp_options_url_data() {
806 let mut data = vec![0x02, 11];
808 data.extend_from_slice(b"example.com");
809 let options = parse_udp_options(&data);
810 assert_eq!(
811 options,
812 vec![UdpTrackerOption::UrlData("example.com".to_owned())]
813 );
814 }
815
816 #[test]
817 fn parse_udp_options_unknown_type() {
818 let data = vec![0x03, 3, 0xAA, 0xBB, 0xCC];
820 let options = parse_udp_options(&data);
821 assert_eq!(
822 options,
823 vec![UdpTrackerOption::Unknown {
824 option_type: 0x03,
825 data: vec![0xAA, 0xBB, 0xCC],
826 }]
827 );
828 }
829
830 #[test]
831 fn parse_udp_options_two_byte_length() {
832 let payload = b"hello";
834 let mut data = vec![0x80];
835 data.extend_from_slice(&(payload.len() as u16).to_be_bytes());
836 data.extend_from_slice(payload);
837 let options = parse_udp_options(&data);
838 assert_eq!(
839 options,
840 vec![UdpTrackerOption::Unknown {
841 option_type: 0x80,
842 data: payload.to_vec(),
843 }]
844 );
845 }
846
847 #[test]
848 fn announce_response_with_trailing_options() {
849 let mut resp = Vec::new();
854 resp.extend_from_slice(&1u32.to_be_bytes()); resp.extend_from_slice(&42u32.to_be_bytes()); resp.extend_from_slice(&60u32.to_be_bytes()); resp.extend_from_slice(&1u32.to_be_bytes()); resp.extend_from_slice(&2u32.to_be_bytes()); resp.extend_from_slice(&[10, 0, 0, 1, 0x1F, 0x90]);
861 resp.extend_from_slice(&[192, 168, 1, 1, 0x1A, 0xE1]);
863 resp.extend_from_slice(&[0x02, 0x02]); resp.extend_from_slice(b"te");
866 resp.push(0x00); assert_eq!(resp.len(), 37); let parsed = UdpTracker::parse_announce_response(&resp, 42).unwrap();
870 assert_eq!(parsed.response.interval, 60);
871 assert_eq!(parsed.response.seeders, Some(2));
872 assert_eq!(parsed.response.leechers, Some(1));
873 assert_eq!(parsed.response.peers.len(), 2);
874 assert_eq!(parsed.response.peers[0].to_string(), "10.0.0.1:8080");
875 assert_eq!(parsed.response.peers[1].to_string(), "192.168.1.1:6881");
876 assert_eq!(
877 parsed.options,
878 vec![
879 UdpTrackerOption::UrlData("te".to_owned()),
880 UdpTrackerOption::EndOfOptions,
881 ]
882 );
883 }
884
885 #[test]
886 fn announce_response_no_options() {
887 let mut resp = Vec::new();
889 resp.extend_from_slice(&1u32.to_be_bytes()); resp.extend_from_slice(&42u32.to_be_bytes()); resp.extend_from_slice(&1800u32.to_be_bytes()); resp.extend_from_slice(&5u32.to_be_bytes()); resp.extend_from_slice(&10u32.to_be_bytes()); resp.extend_from_slice(&[192, 168, 1, 1, 0x1A, 0xE1]);
896
897 let parsed = UdpTracker::parse_announce_response(&resp, 42).unwrap();
898 assert_eq!(parsed.response.peers.len(), 1);
899 assert!(parsed.options.is_empty());
900 }
901}