File Coverage

blib/lib/Net/BitTorrent/Torrent/Tracker/UDP.pm
Criterion Covered Total %
statement 102 163 62.5
branch 28 64 43.7
condition 4 10 40.0
subroutine 20 26 76.9
pod 3 3 100.0
total 157 266 59.0


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             package Net::BitTorrent::Torrent::Tracker::UDP;
3             {
4 11     11   199707 use strict;
  11         32  
  11         417  
5 11     11   87 use warnings;
  11         24  
  11         369  
6 11     11   59 use Carp qw[carp];
  11         24  
  11         654  
7 11     11   64 use Scalar::Util qw[blessed weaken refaddr];
  11         20  
  11         898  
8 11     11   59 use List::Util qw[sum];
  11         24  
  11         776  
9 11     11   2646 use Socket qw[inet_aton pack_sockaddr_in];
  11         19219  
  11         6587  
10 11     11   63 use lib q[../../../../../lib];
  11         22  
  11         83  
11 11     11   2657 use Net::BitTorrent::Util qw[:compact];
  11         25  
  11         1423  
12 11     11   63 use version qw[qv];
  11         20  
  11         81  
13             our $VERSION_BASE = 50; our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ? q[%.3f_%03d] : q[%.3f]), (version->new(($VERSION_BASE))->numify / 1000), $UNSTABLE_RELEASE);
14             my %REGISTRY = ();
15             my @CONTENTS = \my (%_url, %_tier,
16             %_tid, %_cid,
17             %_outstanding_requests, %_packed_host,
18             %_event, %_peers,
19             %_complete, %_incomplete
20             );
21              
22             sub new {
23 26     26 1 103 my ($class, $args) = @_;
24 26 100       110 if (!$args) {
25 1         329 carp q[Net::[...]Tracker::UDP->new({}) requires params];
26 1         16 return;
27             }
28 25 100 66     380 if ((!$args->{q[URL]}) || ($args->{q[URL]} !~ m[^udp://]i)) {
29 3         418 carp q[Net::[...]Tracker::UDP->new({}) requires a valid URL];
30 3         108 return;
31             }
32 22 100 66     230 if ( (!$args->{q[Tier]})
33             || (!$args->{q[Tier]}->isa(q[Net::BitTorrent::Torrent::Tracker])))
34 3         382 { carp q[Net::[...]Tracker::UDP->new({}) requires a parent Tracker];
35 3         120 return;
36             }
37 19         83 my $self = bless \$args->{q[URL]}, $class;
38 19         229 my ($host, $port, $path)
39             = $args->{q[URL]} =~ m{^udp://([^/:]*)(?::(\d+))?(/.*)$};
40 19 100       59 $port = $port ? $port : 80;
41 19         41 my $packed_host = undef;
42 19 100       152 if ($host
43             !~ m[^(?:(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.]?){4})$])
44 12 50       24992 { my ($name, $aliases, $addrtype, $length, @addrs)
45             = gethostbyname($host)
46             or return;
47 12         63 $packed_host = $addrs[0];
48             }
49 7         42 else { $packed_host = inet_aton($host) }
50 19         26672 $_packed_host{refaddr $self}
51             = pack_sockaddr_in($port, inet_aton($host));
52 19         138 $_url{refaddr $self} = $args->{q[URL]};
53 19         94 $_event{refaddr $self} = q[];
54 19         101 $_tier{refaddr $self} = $args->{q[Tier]};
55 19         74 $_peers{refaddr $self} = q[];
56 19         78 $_complete{refaddr $self} = 0;
57 19         67 $_incomplete{refaddr $self} = 0;
58 19         123 $_tid{refaddr $self} = int(rand() * 26**5);
59 19         123 weaken $_tier{refaddr $self};
60 19         102 weaken($REGISTRY{refaddr $self} = $self);
61 19         195 return $self;
62             }
63              
64             # Accessors | Public
65 4     4 1 1074 sub url { my ($self) = @_; return $_url{refaddr $self}; }
  4         78  
66              
67             # Accessors | Private
68 0     0   0 sub _packed_host { return $_packed_host{refaddr +shift} }
69 0     0   0 sub _tier { return $_tier{refaddr +shift}; }
70 41     41   376 sub _peers { return $_peers{refaddr +shift}; }
71 42     42   197 sub _client { return $_tier{refaddr +shift}->_client }
72              
73             # Methods | Private
74             sub _announce {
75 16     16   49 my ($self, $event) = @_;
76 16 50       102 if (!$_tier{refaddr $self}->_client->_udp) {
77 0         0 carp sprintf q[UDP port is not open];
78 0         0 $_tier{refaddr $self}->_shuffle();
79 0         0 return;
80             }
81 16 100       51 if (defined $event) {
82 15 100       109 if ($event !~ m[^(?:st(?:art|opp)|complet)ed$]) {
83 1         138 carp sprintf q[Invalid event for announce: %s], $event;
84 1         54 return;
85             }
86 14         87 $_event{refaddr $self} = $event;
87             }
88 15         67 my $tid = $self->_generate_token_id();
89 15 50       228 if (not $_cid{refaddr $self}) {
90 15         67 my $packet = pack q[a8NN], ___pack64(4497486125440), 0, $tid;
91 15         152 $_outstanding_requests{refaddr $self}{$tid} = {Timestamp => time,
92             Attempt => 1,
93             Packet => $packet
94             };
95             }
96             else {
97 0 0 0     0 my $packet = pack q[a8NN]
    0 0        
    0          
98             . q[a20 a20 a8 a8 a8 N N N N n],
99             $_cid{refaddr $self}, 1, $tid,
100             pack(q[H*], $_tier{refaddr $self}->_torrent->infohash),
101             $self->_client->peerid(),
102             ___pack64($_tier{refaddr $self}->_torrent->downloaded()),
103             ___pack64(
104             $_tier{refaddr $self}->_torrent->raw_data(1)
105             ->{q[info]}{q[piece length]} * sum(
106             split(q[],
107             unpack(
108             q[b*],
109             ($_tier{refaddr $self}->_torrent->_wanted()
110             || q[]
111             )
112             )
113             )
114             )
115             ),
116             ___pack64($_tier{refaddr $self}->_torrent->uploaded()),
117             ( $_event{refaddr $self} eq q[completed] ? 1
118             : $_event{refaddr $self} eq q[started] ? 2
119             : $_event{refaddr $self} eq q[stopped] ? 3
120             : 0
121             ),
122             0, $^T, 200, $self->_client->_tcp_port
123             || 0;
124 0         0 $_outstanding_requests{refaddr $self}{$tid} = {Timestamp => time,
125             Attempt => 1,
126             Packet => $packet,
127             Retry_ID => q[]
128             };
129             }
130 15         63 $self->_send($tid);
131             }
132              
133             sub _send {
134 21     21   56 my ($self, $tid) = @_;
135 21 50       148 if (!$_tier{refaddr $self}->_client->_udp) {
136 0         0 $self->_client->_socket_open();
137             }
138 21 50       81 return if not $self->_client->_udp;
139 21 50       125 if ($_outstanding_requests{refaddr $self}{$tid}{q[Attempt]} > 8) {
140 0         0 delete $_outstanding_requests{refaddr $self}{$tid};
141 0         0 return;
142             }
143 21 50       119 if (not send($_tier{refaddr $self}->_client->_udp,
144             $_outstanding_requests{refaddr $self}{$tid}{q[Packet]},
145             0,
146             $_packed_host{refaddr $self}
147             )
148             )
149 0         0 { carp sprintf(
150             q[Cannot send %d bytes to %s: [%d] %s],
151             length(
152             $_outstanding_requests{refaddr $self}{$tid}{q[Packet]}
153             ),
154             q[TODO], $^E, $^E
155             );
156 0         0 return;
157             }
158 21 100       153 $_tier{refaddr $self}->_torrent->_event(
159             q[tracker_connect],
160             {Tracker => $self,
161             ($_event{refaddr $self}
162             ? (Event => $_event{refaddr $self})
163             : ()
164             )
165             }
166             );
167 21         152 $_tier{refaddr $self}->_torrent->_event(
168             q[tracker_write],
169             {Tracker => $self,
170             Length => length(
171             $_outstanding_requests{refaddr $self}{$tid}{q[Packet]}
172             )
173             }
174             );
175             $_outstanding_requests{refaddr $self}{$tid}{q[Retry_ID]}
176             = $self->_client->_schedule(
177             {Time =>
178             time + (15 * (2**$_outstanding_requests{refaddr $self}{$tid}
179             {q[Attempt]}
180             )
181             ),
182             Code => sub {
183 6     6   57 $_outstanding_requests{refaddr $self}{$tid}{q[Attempt]}++;
184 6         47 shift->_send($tid);
185             },
186 21         88 Object => $self
187             }
188             );
189 21         120 return 1;
190             }
191              
192             sub _on_data {
193 0     0   0 my ($self, $paddr, $data) = @_;
194 0         0 my ($action, $tid, $packet) = unpack q[NNa*], $data;
195 0         0 $_tier{refaddr $self}->_torrent->_event(q[tracker_read],
196             {Tracker => $self, Length => length($data)});
197 0 0       0 return if not $_outstanding_requests{refaddr $self}{$tid};
198 0         0 my $_request = $_outstanding_requests{refaddr $self}{$tid};
199 0         0 $self->_client->_cancel(
200             $_outstanding_requests{refaddr $self}{$tid}{q[Retry_ID]});
201 0         0 delete $_outstanding_requests{refaddr $self}{$tid};
202 0 0       0 if ($action == 0) {
    0          
    0          
    0          
203              
204 0 0       0 if (length($data) == 16) {
205 0         0 my ($cid) = unpack(q[a8], $packet);
206 0         0 $_cid{refaddr $self} = $cid;
207 0         0 $self->_announce();
208 0         0 return $self;
209             }
210 0         0 return;
211             }
212             elsif ($action == 1) {
213 0 0       0 if (length($data) >= 20) {
214 0         0 my ($min_interval, $leeches, $seeds, $peers)
215             = unpack(q[N N N a*], $packet);
216 0         0 $_peers{refaddr $self} = $peers;
217 0         0 $_complete{refaddr $self} = $seeds;
218 0         0 $_incomplete{refaddr $self} = $leeches;
219 0         0 $_tier{refaddr $self}->_torrent->_event(
220             q[tracker_success],
221             {Tracker => $self,
222             Payload => {
223             complete => $seeds,
224             incomplete => $leeches,
225             peers => $peers,
226             min_interval => $min_interval
227             }
228             }
229             );
230             $self->_client->_schedule(
231             { Time => (time + ( $min_interval
232             ? $min_interval
233             : 1800
234             )
235             ),
236             Code =>
237 0     0   0 sub { return $_tier{refaddr +shift}->_announce() }
238             ,
239 0 0       0 Object => $self
240             }
241             );
242             }
243 0         0 $_event{refaddr $self} = q[];
244 0         0 return $self;
245             }
246             elsif ($action == 2) {
247             }
248             elsif ($action == 3) {
249 0         0 $_tier{refaddr $self}->_torrent->_event(q[tracker_failure],
250             {Tracker => $self,
251             Reason => $packet
252             }
253             );
254             $self->_client->_schedule(
255             {Time => time + 30,
256             Code => sub {
257 0     0   0 my ($s) = @_;
258 0         0 $s->_tier->_shuffle;
259 0         0 return $s->_tier->_announce();
260             },
261 0         0 Object => $self
262             }
263             );
264 0         0 return;
265             }
266             else { }
267 0         0 return;
268             }
269              
270             sub _generate_token_id {
271 15 50   15   50 return if defined $_[1];
272 15         26 my ($self) = @_;
273 15         113 my ($len) = ($_tid{refaddr $self} =~ m[^(\d+)]);
274 15 50       133 $_tid{refaddr $self}
275             = ($_tid{refaddr $self} >= (26**5) ? 0 : ++$_tid{refaddr $self});
276 15         64 return $_tid{refaddr $self};
277             }
278              
279             sub as_string {
280 1     1 1 3 my ($self, $advanced) = @_;
281 1 50       14 my $dump = !$advanced ? $$self : sprintf <<'END',
282             Net::BitTorrent::Torrent::Tracker::UDP
283              
284             URL: %s
285             END
286             $_url{refaddr $self};
287 1 50       10 return defined wantarray ? $dump : print STDERR qq[$dump\n];
288             }
289              
290             sub ___pack64 { # [id://163389]
291 15     15   32 my ($value) = @_;
292 15         24 my $return;
293 15 50       39 if (!eval { $return = pack(q[Q], $value); 1; }) {
  15         56  
  15         50  
294 0         0 require Math::BigInt;
295 0         0 my $i = new Math::BigInt $value;
296 0         0 my ($int1, $int2) = do {
297 0 0       0 if ($i < 0) {
298 0         0 $i = -1 - $i;
299 0         0 (~(int($i / 2**32) % 2**32), ~int($i % 2**32));
300             }
301             else {
302 0         0 (int($i / 2**32) % 2**32, int($i % 2**32));
303             }
304             };
305 0         0 $return = pack(q[NN], $int1, $int2);
306             }
307 15         75 return $return;
308             }
309              
310             sub CLONE {
311 0     0   0 for my $_oID (keys %REGISTRY) {
312 0         0 my $_obj = $REGISTRY{$_oID};
313 0         0 my $_nID = refaddr $_obj;
314 0         0 for (@CONTENTS) {
315 0         0 $_->{$_nID} = $_->{$_oID};
316 0         0 delete $_->{$_oID};
317             }
318 0         0 weaken $_tier{$_nID};
319 0         0 weaken($REGISTRY{$_nID} = $_obj);
320 0         0 delete $REGISTRY{$_oID};
321             }
322 0         0 return 1;
323             }
324             DESTROY {
325 9     9   23 my ($self) = @_;
326 9         34 for (@CONTENTS) { delete $_->{refaddr $self}; }
  90         300  
327 9         43 delete $REGISTRY{refaddr $self};
328 9         208 return 1;
329             }
330             1;
331             }
332              
333             =pod
334              
335             =head1 NAME
336              
337             Net::BitTorrent::Torrent::Tracker::UDP - Single UDP BitTorrent Tracker
338              
339             =head1 Constructor
340              
341             =over 4
342              
343             =item C
344              
345             Creates a C object. This
346             constructor should not be used directly.
347              
348             =back
349              
350             =head1 Methods
351              
352             =over
353              
354             =item C
355              
356             Returns the related UDP 'URL' according to the original metadata.
357              
358             =item C
359              
360             Returns a 'ready to print' dump of the object's data structure. If
361             called in void context, the structure is printed to C.
362             C is a boolean value.
363              
364             =back
365              
366             =head1 BUGS/TODO
367              
368             =over 4
369              
370             =item *
371              
372             This is ALPHA code and as such may not work as expected.
373              
374             =item *
375              
376             Should I pretend UDP uses connections and trigger the 'tracker_connect'
377             callback whenever we send() data just to keep things even?
378              
379             =back
380              
381             =head1 See Also
382              
383             =over
384              
385             =item BEP 15
386              
387             UDP Tracker Protocol for BitTorrent
388             http://bittorrent.org/beps/bep_0015.html
389              
390             =back
391              
392             =head1 Author
393              
394             Sanko Robinson - http://sankorobinson.com/
395              
396             CPAN ID: SANKO
397              
398             =head1 License and Legal
399              
400             Copyright (C) 2008-2009 by Sanko Robinson Esanko@cpan.orgE
401              
402             This program is free software; you can redistribute it and/or modify
403             it under the terms of The Artistic License 2.0. See the F
404             file included with this distribution or
405             http://www.perlfoundation.org/artistic_license_2_0. For
406             clarification, see http://www.perlfoundation.org/artistic_2_0_notes.
407              
408             When separated from the distribution, all POD documentation is covered
409             by the Creative Commons Attribution-Share Alike 3.0 License. See
410             http://creativecommons.org/licenses/by-sa/3.0/us/legalcode. For
411             clarification, see http://creativecommons.org/licenses/by-sa/3.0/us/.
412              
413             Neither this module nor the L is affiliated with
414             BitTorrent, Inc.
415              
416             =for svn $Id: UDP.pm d3c97de 2009-09-12 04:31:46Z sanko@cpan.org $
417              
418             =cut