File Coverage

lib/Net/BitTorrent/Protocol/BEP15.pm
Criterion Covered Total %
statement 132 132 100.0
branch 52 52 100.0
condition 4 4 100.0
subroutine 26 26 100.0
pod 15 16 93.7
total 229 230 99.5


line stmt bran cond sub pod time code
1             package Net::BitTorrent::Protocol::BEP15;
2             our $VERSION = "1.5.3";
3 1     1   656 use strict;
  1         1  
  1         26  
4 1     1   3 use warnings;
  1         1  
  1         29  
5 1     1   487 use Type::Utils;
  1         20921  
  1         7  
6 1     1   1331 use Type::Params qw[compile];
  1         39597  
  1         8  
7 1     1   199 use Types::Standard qw[slurpy Dict ArrayRef Optional Maybe Int Str Enum];
  1         1  
  1         4  
8 1     1   690 use Carp qw[carp];
  1         1  
  1         39  
9 1     1   4 use vars qw[@EXPORT_OK %EXPORT_TAGS];
  1         1  
  1         48  
10 1     1   3 use Exporter qw[];
  1         2  
  1         84  
11             *import = *import = *Exporter::import;
12             %EXPORT_TAGS = (
13             build => [
14             qw[ build_connect_request build_connect_reply
15             build_announce_request build_announce_reply
16             build_scrape_request build_scrape_reply
17             build_error_reply
18             ]
19             ],
20             parse => [
21             qw[ parse_connect_request parse_connect_reply
22             parse_announce_request parse_announce_reply
23             parse_scrape_request parse_scrape_reply
24             parse_error_reply
25             parse_request parse_reply
26             ]
27             ],
28             types => [
29             qw[ $CONNECT $ANNOUNCE $SCRAPE $ERROR
30             $NONE $COMPLETED $STARTED $STOPPED ]
31             ]
32             );
33             @EXPORT_OK = sort map { @$_ = sort @$_; @$_ } values %EXPORT_TAGS;
34             $EXPORT_TAGS{'all'} = \@EXPORT_OK;
35 1     1   453 use Digest::SHA qw[sha1];
  1         2320  
  1         87  
36 1     1   380 use Net::BitTorrent::Protocol::BEP23 qw[compact_ipv4 uncompact_ipv4];
  1         1  
  1         1439  
37             #
38             our $CONNECTION_ID = 4497486125440; # 0x41727101980
39              
40             # Actions
41             our $CONNECT = 0;
42             our $ANNOUNCE = 1;
43             our $SCRAPE = 2;
44             our $ERROR = 3;
45              
46             # Events
47             our $NONE = 0;
48             our $COMPLETED = 1;
49             our $STARTED = 2;
50             our $STOPPED = 3;
51              
52             # Build functions
53             sub build_connect_request {
54 1     1 1 501 CORE::state $check = compile(slurpy Dict [transaction_id => Int]);
55 1         8423 my ($args) = $check->(@_);
56 1         86 return pack 'Q>ll', $CONNECTION_ID, $CONNECT, $args->{transaction_id};
57             }
58              
59             sub build_connect_reply {
60 1     1 1 4 CORE::state $check
61             = compile(slurpy Dict [transaction_id => Int, connection_id => Int]);
62 1         8475 my ($args) = $check->(@_);
63             return pack 'llQ>', $CONNECT, $args->{transaction_id},
64 1         142 $args->{connection_id};
65             }
66              
67             sub build_announce_request {
68 8     8 1 13 CORE::state $check = compile(
69             slurpy Dict [
70             connection_id => Int,
71             transaction_id => Int,
72             info_hash => Str,
73             peer_id => Str,
74             downloaded => Int,
75             left => Int,
76             uploaded => Int,
77             event => Enum [$NONE, $COMPLETED, $STARTED, $STOPPED],
78             ip => Optional [Str], # Default: 0
79             key => Int,
80             num_want => Optional [Int], # Default: -1
81             port => Int,
82             request_string => Optional [Str],
83             authentication => Optional [ArrayRef]
84             ]
85             );
86 8         37824 my ($args) = $check->(@_);
87             my $data = pack 'Q>NN a20a20 Q>Q>Q>N a4 Nl>n',
88             $args->{connection_id}, $ANNOUNCE, $args->{transaction_id},
89             $args->{info_hash}, $args->{peer_id},
90             $args->{downloaded}, $args->{left}, $args->{uploaded}, $args->{event},
91             (defined $args->{ip} ?
92             $args->{ip} =~ m[\.] ?
93             (pack("C4", split(/\./, $args->{ip})))
94             : pack 'N',
95             0
96             : pack 'N',
97             0
98             ),
99 8 100 100     844 $args->{key}, ($args->{num_want} // -1), $args->{port};
    100          
100 8         8 my $ext = 0;
101 8 100       14 $ext ^= 1 if defined $args->{authentication};
102 8 100       17 $ext ^= 2 if defined $args->{request_string};
103 8         16 $data .= pack 'n', $ext;
104 8 100       12 if (defined $args->{authentication}) {
105             $data .= pack('ca*',
106             length($args->{authentication}[0]),
107 2         6 $args->{authentication}[0]);
108 2         36 $data .= pack('a8', sha1($data, sha1($args->{authentication}[1])));
109             }
110             $data
111             .= pack('ca*', length($args->{request_string}),
112             $args->{request_string})
113 8 100       15 if defined $args->{request_string};
114 8         46 $data;
115             }
116              
117             sub build_announce_reply {
118 2     2 1 7 CORE::state $check = compile(slurpy Dict [
119             transaction_id => Int,
120             interval => Int,
121             leechers => Int,
122             seeders => Int,
123             peers => ArrayRef [Maybe [ArrayRef]]
124             ]
125             );
126 2         19041 my ($args) = $check->(@_);
127             pack 'NNNNNa*',
128             $ANNOUNCE,
129 8         12 (map { $args->{$_} } qw[transaction_id interval leechers seeders]),
130 2   100     196 (compact_ipv4(@{$args->{peers}}) // '');
  2         8  
131             }
132              
133             sub build_scrape_request {
134 1     1 1 5 CORE::state $check = compile(slurpy Dict [connection_id => Int,
135             transaction_id => Int,
136             info_hash => ArrayRef [Str]
137             ]
138             );
139 1         10918 my ($args) = $check->(@_);
140             return pack 'Q>NN(a20)*',
141             $args->{connection_id}, $SCRAPE, $args->{transaction_id},
142 1         108 @{$args->{info_hash}};
  1         9  
143             }
144              
145             sub build_scrape_reply {
146 1     1 0 5 CORE::state $check = compile(
147             slurpy Dict [
148             transaction_id => Int,
149             scrape =>
150             ArrayRef [
151             Dict [downloaded => Int, incomplete => Int, complete => Int]
152             ]
153             ]
154             );
155 1         20429 my ($args) = $check->(@_);
156 1         237 CORE::state $keys = [qw[complete downloaded incomplete]];
157 1         6 my $data = pack 'NN', $SCRAPE, $args->{transaction_id};
158 1         2 for my $scrape (@{$args->{scrape}}) {
  1         3  
159 1         1 for my $key (@$keys) {
160 3         6 $data .= pack 'N', $scrape->{$key};
161             }
162             }
163 1         16 $data;
164             }
165              
166             sub build_error_reply {
167 1     1 1 5 CORE::state $check = compile(slurpy Dict [transaction_id => Int,
168             'failure reason' => Str
169             ]
170             );
171 1         7014 my ($args) = $check->(@_);
172             return pack 'NNa*', $ERROR,
173 1         82 map { $args->{$_} } qw[transaction_id], 'failure reason';
  2         11  
174             }
175              
176             # Parse functions
177             sub parse_connect_request {
178 5     5 1 8 my ($data) = @_;
179 5 100       15 if (length $data < 16) {
180 1         8 return {fatal => 0, error => 'Not enough data'};
181             }
182 4         13 my ($cid, $action, $tid) = unpack 'Q>ll', $data;
183 4 100       10 if ($cid != $CONNECTION_ID) {
184 1         6 return {fatal => 1, error => 'Incorrect connection id'};
185             }
186 3 100       7 if ($action != $CONNECT) {
187 1         6 return {fatal => 1,
188             error => 'Incorrect action for connect request'
189             };
190             }
191 2         14 return {transaction_id => $tid, action => $action, connection_id => $cid};
192             }
193              
194             sub parse_connect_reply {
195 4     4 1 6 my ($data) = @_;
196 4 100       9 if (length $data < 16) {
197 1         5 return {fatal => 0, error => 'Not enough data'};
198             }
199 3         7 my ($action, $tid, $cid) = unpack 'llQ>', $data;
200 3 100       9 if ($action != $CONNECT) {
201 1         6 return {fatal => 1,
202             error => 'Incorrect action for connect request'
203             };
204             }
205 2         17 return {transaction_id => $tid, action => $action, connection_id => $cid};
206             }
207              
208             sub parse_announce_request {
209 6     6 1 552 my ($data) = @_;
210 6 100       14 if (length $data < 16) {
211 1         5 return {fatal => 0, error => 'Not enough data'};
212             }
213 5         28 my ($cid, $action, $tid,
214             #
215             $info_hash, $peer_id,
216             #
217             $downloaded, $left, $uploaded, $event,
218             #
219             $ip,
220             #
221             $key, $num_want, $port, $ext, $ext_data
222             )
223             = unpack 'Q>NN a20a20 Q>Q>Q>N a4 Nl>nna*',
224             $data;
225 5 100       14 if ($action != $ANNOUNCE) {
226 1         5 return {fatal => 1,
227             error => 'Incorrect action for announce request'
228             };
229             }
230 4         31 my $retval = {connection_id => $cid,
231             action => $action,
232             transaction_id => $tid,
233             info_hash => $info_hash,
234             peer_id => $peer_id,
235             downloaded => $downloaded,
236             left => $left,
237             uploaded => $uploaded,
238             event => $event,
239             ip => $ip,
240             key => $key,
241             num_want => $num_want,
242             port => $port,
243             ip => (join(".", unpack("C4", $ip)))
244             };
245 4 100       14 ($retval->{authentication}[0], $retval->{authentication}[1], $ext_data)
246             = unpack 'c/aa8a*', $ext_data
247             if $ext & 1;
248 4 100       10 $retval->{request_string} = unpack 'c/a', $ext_data if $ext & 2;
249 4         39 $retval;
250             }
251              
252             sub parse_announce_reply {
253 4     4 1 5 my ($data) = @_;
254 4         11 my ($action, $transaction_id, $interval, $leechers, $seeders, $peers)
255             = unpack 'NNNNNa*', $data;
256 4 100       12 return if $action != $ANNOUNCE;
257 3         9 return {action => $action,
258             transaction_id => $transaction_id,
259             interval => $interval,
260             leechers => $leechers,
261             seeders => $seeders,
262             peers => [uncompact_ipv4 $peers]
263             };
264             }
265              
266             sub parse_scrape_request {
267 3     3 1 3 my ($data) = @_;
268 3         9 my ($connection_id, $action, $transaction_id, $infohash)
269             = unpack 'Q>NNa*', $data;
270 3 100       12 return if $action != $SCRAPE;
271 2         16 return {action => $action,
272             connection_id => $connection_id,
273             transaction_id => $transaction_id,
274             info_hash => [unpack '(a20)*', $infohash]
275             };
276             }
277              
278             sub parse_scrape_reply {
279 4     4 1 6 my ($data) = @_;
280 4         14 my ($action, $transaction_id, @etc) = unpack 'NN(NNN)*', $data;
281 4 100       37 return if $action != $SCRAPE;
282 3         4 CORE::state $keys = [qw[complete downloaded incomplete]];
283 3         3 my @scrape;
284 3         8 while (my @next_n = splice @etc, 0, 3) {
285 2         4 push @scrape, {map { $keys->[$_] => $next_n[$_] } 0 .. $#next_n};
  6         15  
286             }
287 3         22 return {action => $action,
288             transaction_id => $transaction_id,
289             scrape => [@scrape]
290             };
291             }
292              
293             sub parse_error_reply {
294 3     3 1 4 my ($data) = @_;
295 3         9 my ($action, $transaction_id, $failure_reason) = unpack 'NNa*', $data;
296 3 100       10 return if $action != $ERROR;
297 2         10 return {transaction_id => $transaction_id,
298             'failure reason' => $failure_reason
299             };
300             }
301              
302             sub parse_request {
303 4     4 1 7 CORE::state $check = compile(Str);
304 4         538 my ($data) = $check->(@_);
305 4         29 my ($connection_id, $action) = unpack 'Q>N', $data;
306 4 100       11 return parse_connect_request($data) if $action == $CONNECT;
307 3 100       7 return parse_announce_request($data) if $action == $ANNOUNCE;
308 2 100       6 return parse_scrape_request($data) if $action == $SCRAPE;
309 1         3 return;
310             }
311              
312             sub parse_reply {
313 5     5 1 553 CORE::state $check = compile(Str);
314 5         461 my ($data) = $check->(@_);
315 5         35 my ($action) = unpack 'NN', $data;
316 5 100       13 return parse_connect_reply($data) if $action == $CONNECT;
317 4 100       10 return parse_announce_reply($data) if $action == $ANNOUNCE;
318 3 100       6 return parse_scrape_reply($data) if $action == $SCRAPE;
319 2 100       4 return parse_error_reply($data) if $action == $ERROR;
320 1         3 return;
321             }
322             1;
323              
324             =pod
325              
326             =head1 NAME
327              
328             Net::BitTorrent::Protocol::BEP15 - Packet Utilities for BEP15, the UDP Tracker Protocol
329              
330             =head1 Synopsis
331              
332             use Net::BitTorrent::Protocol::BEP15 qw[:all];
333              
334             # Tell them we want to connect...
335             my $handshake = build_connect_request(255);
336              
337             # ...send to tracker and get reply...
338             my ($transaction_id, $connection_id) = parse_connect_reply( $reply );
339              
340             =head1 Description
341              
342             What would BitTorrent be without packets? TCP noise, mostly.
343              
344             For similar work and the specifications behind these packets, move on down to
345             the L section.
346              
347             =head1 Importing from Net::BitTorrent::Protocol::BEP15
348              
349             There are two tags available for import. To get them both in one go, use the
350             C<:all> tag.
351              
352             =over
353              
354             =item C<:build>
355              
356             These create packets ready-to-send to trackers. See
357             L.
358              
359             =item C<:parse>
360              
361             These are used to parse unknown data into sensible packets. The same packet
362             types we can build, we can also parse. You may want to use this to write your
363             own UDP tracker. See L.
364              
365             =back
366              
367             =head2 Building Functions
368              
369             =over
370              
371             =item C
372              
373             Creates a request for a connection id. The provided C should
374             be a random 32-bit integer.
375              
376             =item C
377              
378             Creates a reply for a connection request. The C should match
379             the value sent from the client. The C is sent with every packet
380             to identify the client.
381              
382             =item C
383              
384             Creates a packet suited to announce with the tracker. The following keys are
385             required:
386              
387             =over
388              
389             =item C
390              
391             This is the same C returned by the tracker when you sent a
392             connection request.
393              
394             =item C
395              
396             This is defined by you. It's a random integer which will be returned by the
397             tracker in response to this packet.
398              
399             =item C
400              
401             This is the packed info hash of the torrent.
402              
403             =item C
404              
405             This is your client's peer id.
406              
407             =item C
408              
409             The amount of data you have downloaded so far this session.
410              
411             =item C
412              
413             The amount of data you have left to download before complete.
414              
415             =item C
416              
417             The amount of data you have uploaded to other peers in this session.
418              
419             =item C
420              
421             This value is either C<$NONE>, C<$COMPLETED>, C<$STARTED>, or C<$STOPPED>.
422             C<$NONE> is sent when you're simply reannouncing after a certain interval.
423              
424             All of these are imported with the C<:types> or C<:all> tags.
425              
426             =item C
427              
428             A unique key that is randomized by the client. Unlike the C
429             which is generated for every packet, this value should be kept per-session.
430              
431             =item C
432              
433             The port you're listening on.
434              
435             =back
436              
437             ...and the following are optional. Some have default values:
438              
439             =over
440              
441             =item C
442              
443             The request string extension is meant to allow torrent creators pass along
444             cookies back to the tracker. This can be useful for authenticating that a
445             torrent is allowed to be tracked by a tracker for instance. It could also be
446             used to authenticate users by generating torrents with unique tokens in the
447             tracker URL for each user.
448              
449             Typically this starts with "/announce" The bittorrent client is not expected
450             to append query string arguments for stats reporting, like "uploaded" and
451             "downloaded" since this is already reported in the udp tracker protocol.
452             However, the client is free to add arguments as extensions.
453              
454             =item C
455              
456             This is a list which contains a username and password. This function then
457             correctly hashes the password to sent over the wire.
458              
459             =item C
460              
461             Your ip address. By default, this is C<0> which tells the tracker to use the
462             sender of this udp packet.
463              
464             =item C
465              
466             The maximum number of peers you want in the reply. The default is C<-1> which
467             lets the tracker decide.
468              
469             =back
470              
471             =item C
472              
473             Creates a packet a UDP tracker would sent in reply to an announce packet from
474             a client. The following are required: C, the C at
475             which the client should reannounce, the number of C and C,
476             as well as a list of C for the given infohash.
477              
478             =item C
479              
480             Creates a packet for a client to request basic data about a number of
481             torrents. Up to about 74 torrents can be scraped at once. A full scrape can't
482             be done with this protocol.
483              
484             You must provide: the tracker provided C, a C,
485             and a list in C.
486              
487             =item C
488              
489             Creates a packet for a tracker to sent in reply to a scrape request. You must
490             provide the client defined C and a list of hashes as C
491             data. The hashes contain integers for the following: C,
492             C, and C.
493              
494             =item C
495              
496             Creates a packet to be sent to the client in case of an error. You must
497             provide a C and C.
498              
499             =back
500              
501             =head2 Parsing Functions
502              
503             These are the parsing counterparts for the C functions.
504              
505             When the packet is invalid, a hash reference is returned with C and
506             C keys. The value in C is a string describing what went wrong.
507              
508             Return values for valid packets are explained below.
509              
510             =over
511              
512             =item C
513              
514             This will automatically call the correct parsing function for you. When you
515             aren't exactly sure what the data is.
516              
517             =item C
518              
519             This will automatically call the correct parsing function for you. When you
520             aren't exactly sure what the data is. This would be use in you're writing a
521             UDP tracker yourself.
522              
523             =item C
524              
525             Returns the parsed transaction id.
526              
527             =item C
528              
529             Parses the reply for a connect request. Returns the original transaction id
530             and the new connection id.
531              
532             =item C
533              
534             Returns C, C, C, C,
535             C, C, C, C, C, C, C,
536             C, C.
537              
538             Optionally, this packet might also contian C and
539             C values.
540              
541             =item C
542              
543             Returns the C, the C at which you should
544             re-announce, the current number of C and C, and an inflated
545             list of C.
546              
547             =item C
548              
549             Returns the C, C, and an C which may
550             contain multiple infohashes depending on the request.
551              
552             =item C
553              
554             Returns C and list of hashes in C. The scrape hashes
555             contain C, C and C keys.
556              
557             =item C
558              
559             Returns C and C.
560              
561             =back
562              
563             =head1 See Also
564              
565             http://bittorrent.org/beps/bep_0015.html - UDP Tracker Protocol for BitTorrent
566              
567             =head1 Author
568              
569             Sanko Robinson - http://sankorobinson.com/
570              
571             CPAN ID: SANKO
572              
573             =head1 License and Legal
574              
575             Copyright (C) 2016 by Sanko Robinson
576              
577             This program is free software; you can redistribute it and/or modify it under
578             the terms of
579             L.
580             See the F file included with this distribution or
581             L
582             for clarification.
583              
584             When separated from the distribution, all original POD documentation is
585             covered by the
586             L.
587             See the
588             L.
589              
590             Neither this module nor the L is affiliated with BitTorrent,
591             Inc.
592              
593             =cut