@@ -6,13 +6,14 @@ use std::net::{SocketAddr, ToSocketAddrs};
66use std:: time:: Duration ;
77use tokio:: net:: UdpSocket ;
88use tokio:: time:: { timeout, Instant } ;
9- use tracing:: { debug, info } ;
9+ use tracing:: debug;
1010
1111use crate :: file:: TorrentMeta ;
1212
1313const PROTOCOL_ID : u64 = 0x41727101980 ;
1414const ACTION_CONNECT : u32 = 0 ;
1515const ACTION_ANNOUNCE : u32 = 1 ;
16+ #[ allow( dead_code) ]
1617const ACTION_SCRAPE : u32 = 2 ;
1718const ACTION_ERROR : u32 = 3 ;
1819
@@ -39,6 +40,16 @@ pub struct UdpAnnounceResponse {
3940 pub peers : Vec < UdpPeer > ,
4041}
4142
43+ pub struct AnnounceOptions {
44+ pub torrent_meta : TorrentMeta ,
45+ pub peer_id : [ u8 ; 20 ] ,
46+ pub port : u16 ,
47+ pub uploaded : u64 ,
48+ pub downloaded : u64 ,
49+ pub left : u64 ,
50+ pub event : u32 ,
51+ }
52+
4253impl UdpTracker {
4354 pub fn new ( url : String ) -> Self {
4455 Self {
@@ -50,29 +61,35 @@ impl UdpTracker {
5061
5162 pub async fn announce (
5263 & mut self ,
53- torrent_meta : & TorrentMeta ,
54- peer_id : & [ u8 ; 20 ] ,
55- port : u16 ,
56- uploaded : u64 ,
57- downloaded : u64 ,
58- left : u64 ,
59- event : u32 ,
64+ announce_options : & AnnounceOptions ,
6065 ) -> Result < UdpAnnounceResponse > {
6166 // Check if we need to connect/reconnect
62- if self . connection_id . is_none ( )
63- || self . last_connect . map_or ( true , |t| t. elapsed ( ) > Duration :: from_secs ( 60 ) )
67+ if self . connection_id . is_none ( )
68+ || self
69+ . last_connect
70+ . map_or_else ( || true , |t| t. elapsed ( ) > Duration :: from_secs ( 60 ) )
6471 {
6572 self . connect ( ) . await ?;
6673 }
6774
68- let connection_id = self . connection_id . ok_or_else ( || anyhow ! ( "No connection ID" ) ) ?;
69-
75+ let connection_id = self
76+ . connection_id
77+ . ok_or_else ( || anyhow ! ( "No connection ID" ) ) ?;
78+
7079 let socket = UdpSocket :: bind ( "0.0.0.0:0" ) . await ?;
7180 let addr = self . parse_udp_url ( ) ?;
7281 // info!("Using UDP tracker at {}", addr);
73-
82+
7483 let transaction_id: u32 = rand:: thread_rng ( ) . gen ( ) ;
75-
84+
85+ let torrent_meta = & announce_options. torrent_meta ;
86+ let peer_id = & announce_options. peer_id ;
87+ let port = announce_options. port ;
88+ let uploaded = announce_options. uploaded ;
89+ let downloaded = announce_options. downloaded ;
90+ let left = announce_options. left ;
91+ let event = announce_options. event ;
92+
7693 // Build announce request
7794 let mut request = Vec :: new ( ) ;
7895 request. write_u64 :: < BigEndian > ( connection_id) ?;
@@ -95,16 +112,16 @@ impl UdpTracker {
95112 // Receive response with timeout
96113 let mut buf = [ 0u8 ; 1024 ] ;
97114 let ( len, _) = timeout ( Duration :: from_secs ( 15 ) , socket. recv_from ( & mut buf) ) . await ??;
98-
115+
99116 self . parse_announce_response ( & buf[ ..len] , transaction_id)
100117 }
101118
102119 async fn connect ( & mut self ) -> Result < ( ) > {
103120 let socket = UdpSocket :: bind ( "0.0.0.0:0" ) . await ?;
104121 let addr = self . parse_udp_url ( ) ?;
105-
122+
106123 let transaction_id: u32 = rand:: thread_rng ( ) . gen ( ) ;
107-
124+
108125 // Build connect request
109126 let mut request = Vec :: new ( ) ;
110127 request. write_u64 :: < BigEndian > ( PROTOCOL_ID ) ?;
@@ -117,70 +134,81 @@ impl UdpTracker {
117134 // Receive response with timeout
118135 let mut buf = [ 0u8 ; 16 ] ;
119136 let ( len, _) = timeout ( Duration :: from_secs ( 15 ) , socket. recv_from ( & mut buf) ) . await ??;
120-
137+
121138 if len < 16 {
122139 return Err ( anyhow ! ( "Connect response too short: {} bytes" , len) ) ;
123140 }
124141
125142 let mut cursor = Cursor :: new ( & buf[ ..len] ) ;
126143 let action = cursor. read_u32 :: < BigEndian > ( ) ?;
127144 let response_transaction_id = cursor. read_u32 :: < BigEndian > ( ) ?;
128-
145+
129146 if action == ACTION_ERROR {
130147 let error_msg = String :: from_utf8_lossy ( & buf[ 8 ..len] ) ;
131148 return Err ( anyhow ! ( "Tracker error: {}" , error_msg) ) ;
132149 }
133-
150+
134151 if action != ACTION_CONNECT {
135152 return Err ( anyhow ! ( "Invalid action in connect response: {}" , action) ) ;
136153 }
137-
154+
138155 if response_transaction_id != transaction_id {
139156 return Err ( anyhow ! ( "Transaction ID mismatch in connect response" ) ) ;
140157 }
141158
142159 self . connection_id = Some ( cursor. read_u64 :: < BigEndian > ( ) ?) ;
143160 self . last_connect = Some ( Instant :: now ( ) ) ;
144-
145- debug ! ( "UDP tracker connected with connection_id: {:?}" , self . connection_id) ;
161+
162+ debug ! (
163+ "UDP tracker connected with connection_id: {:?}" ,
164+ self . connection_id
165+ ) ;
146166 Ok ( ( ) )
147167 }
148168
149169 fn parse_udp_url ( & self ) -> Result < SocketAddr > {
150170 if !self . url . starts_with ( "udp://" ) {
151171 return Err ( anyhow ! ( "Invalid UDP tracker URL: {}" , self . url) ) ;
152172 }
153-
173+
154174 let url_without_scheme = & self . url [ 6 ..] ; // Remove "udp://"
155-
175+
156176 // Split at the first '/' to separate hostname:port from path
157- let host_port = url_without_scheme. split ( '/' ) . next ( )
177+ let host_port = url_without_scheme
178+ . split ( '/' )
179+ . next ( )
158180 . ok_or_else ( || anyhow ! ( "Invalid UDP tracker URL format: {}" , self . url) ) ?;
159-
160- let addr = host_port. to_socket_addrs ( ) ?. next ( )
181+
182+ let addr = host_port
183+ . to_socket_addrs ( ) ?
184+ . next ( )
161185 . ok_or_else ( || anyhow ! ( "Could not resolve UDP tracker address: {}" , host_port) ) ?;
162-
186+
163187 Ok ( addr)
164188 }
165189
166- fn parse_announce_response ( & self , data : & [ u8 ] , expected_transaction_id : u32 ) -> Result < UdpAnnounceResponse > {
190+ fn parse_announce_response (
191+ & self ,
192+ data : & [ u8 ] ,
193+ expected_transaction_id : u32 ,
194+ ) -> Result < UdpAnnounceResponse > {
167195 if data. len ( ) < 20 {
168196 return Err ( anyhow ! ( "Announce response too short: {} bytes" , data. len( ) ) ) ;
169197 }
170198
171199 let mut cursor = Cursor :: new ( data) ;
172200 let action = cursor. read_u32 :: < BigEndian > ( ) ?;
173201 let transaction_id = cursor. read_u32 :: < BigEndian > ( ) ?;
174-
202+
175203 if action == ACTION_ERROR {
176204 let error_msg = String :: from_utf8_lossy ( & data[ 8 ..] ) ;
177205 return Err ( anyhow ! ( "Tracker error: {}" , error_msg) ) ;
178206 }
179-
207+
180208 if action != ACTION_ANNOUNCE {
181209 return Err ( anyhow ! ( "Invalid action in announce response: {}" , action) ) ;
182210 }
183-
211+
184212 if transaction_id != expected_transaction_id {
185213 return Err ( anyhow ! ( "Transaction ID mismatch in announce response" ) ) ;
186214 }
@@ -192,16 +220,21 @@ impl UdpTracker {
192220 let mut peers = Vec :: new ( ) ;
193221 let remaining_bytes = data. len ( ) - 20 ;
194222 let peer_count = remaining_bytes / 6 ; // Each peer is 6 bytes (4 IP + 2 port)
195-
223+
196224 for _ in 0 ..peer_count {
197225 let mut ip = [ 0u8 ; 4 ] ;
198226 cursor. read_exact ( & mut ip) ?;
199227 let port = cursor. read_u16 :: < BigEndian > ( ) ?;
200-
228+
201229 peers. push ( UdpPeer { ip, port } ) ;
202230 }
203231
204- debug ! ( "UDP announce response: {} seeders, {} leechers, {} peers" , seeders, leechers, peers. len( ) ) ;
232+ debug ! (
233+ "UDP announce response: {} seeders, {} leechers, {} peers" ,
234+ seeders,
235+ leechers,
236+ peers. len( )
237+ ) ;
205238
206239 Ok ( UdpAnnounceResponse {
207240 action,
@@ -227,13 +260,23 @@ pub async fn request_udp_peers(
227260 port : u16 ,
228261) -> Result < UdpAnnounceResponse > {
229262 let mut tracker = UdpTracker :: new ( tracker_url. to_string ( ) ) ;
230-
263+
231264 let uploaded = 0 ;
232265 let downloaded = 0 ;
233266 let left = torrent_meta. torrent_file . info . length . unwrap_or ( 0 ) as u64 ;
234267 let event = 2 ; // started event
235-
236- tracker. announce ( torrent_meta, peer_id, port, uploaded, downloaded, left, event) . await
268+
269+ let announce_options = AnnounceOptions {
270+ torrent_meta : torrent_meta. clone ( ) ,
271+ peer_id : * peer_id,
272+ port,
273+ uploaded,
274+ downloaded,
275+ left,
276+ event,
277+ } ;
278+
279+ tracker. announce ( & announce_options) . await
237280}
238281
239282#[ cfg( test) ]
0 commit comments