5
5
//! Announce request:
6
6
//!
7
7
//! ```text
8
- //! cargo run --bin udp_tracker_client 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
8
+ //! cargo run --bin udp_tracker_client announce 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
9
9
//! ```
10
10
//!
11
11
//! Announce response:
20
20
//! "123.123.123.123:51289"
21
21
//! ],
22
22
//! }
23
- /// ````
23
+ //! ```
24
+ //!
25
+ //! Scrape request:
26
+ //!
27
+ //! ```text
28
+ //! cargo run --bin udp_tracker_client scrape 127.0.0.1:6969 9c38422213e30bff212b30c360d26f9a02136422 | jq
29
+ //! ```
30
+ //!
31
+ //! Scrape response:
32
+ //!
33
+ //! ```json
34
+ //! {
35
+ //! "transaction_id": -888840697,
36
+ //! "torrent_stats": [
37
+ //! {
38
+ //! "completed": 0,
39
+ //! "leechers": 0,
40
+ //! "seeders": 0
41
+ //! },
42
+ //! {
43
+ //! "completed": 0,
44
+ //! "leechers": 0,
45
+ //! "seeders": 0
46
+ //! }
47
+ //! ]
48
+ //! }
49
+ //! ```
24
50
use std:: net:: { Ipv4Addr , SocketAddr } ;
25
51
use std:: str:: FromStr ;
26
52
27
53
use anyhow:: Context ;
28
54
use aquatic_udp_protocol:: common:: InfoHash ;
29
- use aquatic_udp_protocol:: Response :: { AnnounceIpv4 , AnnounceIpv6 } ;
55
+ use aquatic_udp_protocol:: Response :: { AnnounceIpv4 , AnnounceIpv6 , Scrape } ;
30
56
use aquatic_udp_protocol:: {
31
57
AnnounceEvent , AnnounceRequest , ConnectRequest , ConnectionId , NumberOfBytes , NumberOfPeers , PeerId , PeerKey , Port , Response ,
32
- TransactionId ,
58
+ ScrapeRequest , TransactionId ,
33
59
} ;
34
60
use clap:: { Parser , Subcommand } ;
35
61
use log:: { debug, LevelFilter } ;
@@ -55,6 +81,12 @@ enum Command {
55
81
#[ arg( value_parser = parse_info_hash) ]
56
82
info_hash : TorrustInfoHash ,
57
83
} ,
84
+ Scrape {
85
+ #[ arg( value_parser = parse_socket_addr) ]
86
+ tracker_socket_addr : SocketAddr ,
87
+ #[ arg( value_parser = parse_info_hash, num_args = 1 ..=74 , value_delimiter = ' ' ) ]
88
+ info_hashes : Vec < TorrustInfoHash > ,
89
+ } ,
58
90
}
59
91
60
92
#[ tokio:: main]
@@ -65,29 +97,23 @@ async fn main() -> anyhow::Result<()> {
65
97
66
98
// Configuration
67
99
let local_port = ASSIGNED_BY_OS ;
100
+ let local_bind_to = format ! ( "0.0.0.0:{local_port}" ) ;
68
101
let transaction_id = RANDOM_TRANSACTION_ID ;
69
- let bind_to = format ! ( "0.0.0.0:{local_port}" ) ;
70
102
71
103
// Bind to local port
72
- debug ! ( "Binding to: {bind_to }" ) ;
73
- let udp_client = UdpClient :: bind ( & bind_to ) . await ;
104
+ debug ! ( "Binding to: {local_bind_to }" ) ;
105
+ let udp_client = UdpClient :: bind ( & local_bind_to ) . await ;
74
106
let bound_to = udp_client. socket . local_addr ( ) . unwrap ( ) ;
75
107
debug ! ( "Bound to: {bound_to}" ) ;
76
108
109
+ let transaction_id = TransactionId ( transaction_id) ;
110
+
77
111
let response = match args. command {
78
112
Command :: Announce {
79
113
tracker_socket_addr,
80
114
info_hash,
81
115
} => {
82
- debug ! ( "Connecting to remote: udp://{tracker_socket_addr}" ) ;
83
-
84
- udp_client. connect ( & tracker_socket_addr. to_string ( ) ) . await ;
85
-
86
- let udp_tracker_client = UdpTrackerClient { udp_client } ;
87
-
88
- let transaction_id = TransactionId ( transaction_id) ;
89
-
90
- let connection_id = send_connection_request ( transaction_id, & udp_tracker_client) . await ;
116
+ let ( connection_id, udp_tracker_client) = connect ( & tracker_socket_addr, udp_client, transaction_id) . await ;
91
117
92
118
send_announce_request (
93
119
connection_id,
@@ -98,6 +124,13 @@ async fn main() -> anyhow::Result<()> {
98
124
)
99
125
. await
100
126
}
127
+ Command :: Scrape {
128
+ tracker_socket_addr,
129
+ info_hashes,
130
+ } => {
131
+ let ( connection_id, udp_tracker_client) = connect ( & tracker_socket_addr, udp_client, transaction_id) . await ;
132
+ send_scrape_request ( connection_id, transaction_id, info_hashes, & udp_tracker_client) . await
133
+ }
101
134
} ;
102
135
103
136
match response {
@@ -123,7 +156,19 @@ async fn main() -> anyhow::Result<()> {
123
156
let pretty_json = serde_json:: to_string_pretty ( & json) . unwrap ( ) ;
124
157
println ! ( "{pretty_json}" ) ;
125
158
}
126
- _ => println ! ( "{response:#?}" ) ,
159
+ Scrape ( scrape) => {
160
+ let json = json ! ( {
161
+ "transaction_id" : scrape. transaction_id. 0 ,
162
+ "torrent_stats" : scrape. torrent_stats. iter( ) . map( |torrent_scrape_statistics| json!( {
163
+ "seeders" : torrent_scrape_statistics. seeders. 0 ,
164
+ "completed" : torrent_scrape_statistics. completed. 0 ,
165
+ "leechers" : torrent_scrape_statistics. leechers. 0 ,
166
+ } ) ) . collect:: <Vec <_>>( ) ,
167
+ } ) ;
168
+ let pretty_json = serde_json:: to_string_pretty ( & json) . unwrap ( ) ;
169
+ println ! ( "{pretty_json}" ) ;
170
+ }
171
+ _ => println ! ( "{response:#?}" ) , // todo: serialize to JSON all responses.
127
172
}
128
173
129
174
Ok ( ( ) )
@@ -150,12 +195,31 @@ fn setup_logging(level: LevelFilter) {
150
195
debug ! ( "logging initialized." ) ;
151
196
}
152
197
153
- fn parse_socket_addr ( s : & str ) -> anyhow:: Result < SocketAddr > {
154
- s. parse ( ) . with_context ( || format ! ( "failed to parse socket address: `{s}`" ) )
198
+ fn parse_socket_addr ( socket_addr_str : & str ) -> anyhow:: Result < SocketAddr > {
199
+ socket_addr_str
200
+ . parse ( )
201
+ . with_context ( || format ! ( "failed to parse socket address: `{socket_addr_str}`" ) )
155
202
}
156
203
157
- fn parse_info_hash ( s : & str ) -> anyhow:: Result < TorrustInfoHash > {
158
- TorrustInfoHash :: from_str ( s) . map_err ( |e| anyhow:: Error :: msg ( format ! ( "failed to parse info-hash `{s}`: {e:?}" ) ) )
204
+ fn parse_info_hash ( info_hash_str : & str ) -> anyhow:: Result < TorrustInfoHash > {
205
+ TorrustInfoHash :: from_str ( info_hash_str)
206
+ . map_err ( |e| anyhow:: Error :: msg ( format ! ( "failed to parse info-hash `{info_hash_str}`: {e:?}" ) ) )
207
+ }
208
+
209
+ async fn connect (
210
+ tracker_socket_addr : & SocketAddr ,
211
+ udp_client : UdpClient ,
212
+ transaction_id : TransactionId ,
213
+ ) -> ( ConnectionId , UdpTrackerClient ) {
214
+ debug ! ( "Connecting to tracker: udp://{tracker_socket_addr}" ) ;
215
+
216
+ udp_client. connect ( & tracker_socket_addr. to_string ( ) ) . await ;
217
+
218
+ let udp_tracker_client = UdpTrackerClient { udp_client } ;
219
+
220
+ let connection_id = send_connection_request ( transaction_id, & udp_tracker_client) . await ;
221
+
222
+ ( connection_id, udp_tracker_client)
159
223
}
160
224
161
225
async fn send_connection_request ( transaction_id : TransactionId , client : & UdpTrackerClient ) -> ConnectionId {
@@ -207,3 +271,29 @@ async fn send_announce_request(
207
271
208
272
response
209
273
}
274
+
275
+ async fn send_scrape_request (
276
+ connection_id : ConnectionId ,
277
+ transaction_id : TransactionId ,
278
+ info_hashes : Vec < TorrustInfoHash > ,
279
+ client : & UdpTrackerClient ,
280
+ ) -> Response {
281
+ debug ! ( "Sending scrape request with transaction id: {transaction_id:#?}" ) ;
282
+
283
+ let scrape_request = ScrapeRequest {
284
+ connection_id,
285
+ transaction_id,
286
+ info_hashes : info_hashes
287
+ . iter ( )
288
+ . map ( |torrust_info_hash| InfoHash ( torrust_info_hash. bytes ( ) ) )
289
+ . collect ( ) ,
290
+ } ;
291
+
292
+ client. send ( scrape_request. into ( ) ) . await ;
293
+
294
+ let response = client. receive ( ) . await ;
295
+
296
+ debug ! ( "scrape request response:\n {response:#?}" ) ;
297
+
298
+ response
299
+ }
0 commit comments