File Coverage

blib/lib/Net/BitTorrent/DHT/Node.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Net::BitTorrent::DHT::Node;
2 2     2   11 use Moose;
  2         4  
  2         22  
3 2     2   14819 use Moose::Util::TypeConstraints;
  2         4  
  2         21  
4 2     2   3761 use AnyEvent;
  2         5  
  2         55  
5 2     2   13 use Net::BitTorrent::Protocol qw[:dht :compact];
  2         4  
  2         481  
6 2     2   13 use feature qw[state];
  2         5  
  2         183  
7 2     2   12 use Scalar::Util;
  2         4  
  2         89  
8 2     2   1001 use Types::Standard qw[Bool Int Ref Str];
  0            
  0            
9             our $VERSION = 'v1.0.2';
10             eval $VERSION;
11             #
12             sub BUILD {1}
13              
14             #
15             has port => (isa => Int, is => 'ro', required => 1);
16             has host => (isa => Str, is => 'ro', required => 1);
17             has sockaddr => (isa => Str, is => 'ro', required => 1, lazy_build => 1);
18              
19             sub _build_sockaddr {
20             Net::BitTorrent::DHT::sockaddr($_[0]->host, $_[0]->port);
21             }
22             has ipv6 => (isa => Bool, is => 'ro', lazy_build => 1);
23             sub _build_ipv6 { length shift->sockaddr == 28 }
24             for my $dir (qw[in out]) {
25             has 'announce_peer_token_'
26             . $dir => (isa => 'HashRef[Str]',
27             is => 'ro',
28             traits => ['Hash'],
29             handles => {'_set_announce_peer_token_' . $dir => 'set',
30             '_get_announce_peer_token_' . $dir => 'get',
31             '_del_announce_peer_token_' . $dir => 'delete',
32             'has_announce_peer_token_' . $dir => 'defined'
33             },
34             default => sub { {} }
35             );
36             }
37             has v => (isa => Str, is => 'ro', writer => '_v', predicate => 'has_v');
38             has bucket => (isa => 'Net::BitTorrent::DHT::Bucket',
39             is => 'ro',
40             writer => 'assign_bucket',
41             weak_ref => 1,
42             predicate => 'has_bucket'
43             );
44             has routing_table => (isa => 'Net::BitTorrent::DHT::RoutingTable',
45             is => 'ro',
46             predicate => 'has_routing_table',
47             writer => '_routing_table',
48             weak_ref => 1,
49             lazy_build => 1,
50             handles => [qw[send dht tracker]]
51             );
52             around send => sub {
53             my ($code, $self, $packet, $reply) = @_;
54             $code->($self, $self, $packet, !!$reply);
55             };
56             has nodeid => (isa => 'Bit::Vector',
57             is => 'ro',
58             writer => '_set_nodeid',
59             predicate => 'has_nodeid'
60             );
61             after _set_nodeid => sub {
62             $_[0]->routing_table->assign_node($_[0]);
63             $_[0]->routing_table->del_node($_[0]) if !$_[0]->has_bucket;
64             };
65             has outstanding_requests => (isa => 'HashRef[HashRef]',
66             is => 'ro',
67             traits => ['Hash'],
68             handles => {add_request => 'set',
69             get_request => 'get',
70             del_request => 'delete',
71             expire_request => 'delete',
72             is_expecting => 'defined'
73             },
74             init_arg => undef,
75             default => sub { {} }
76             );
77             after expire_request => sub { shift->inc_fail };
78             around add_request => sub {
79             my ($code, $self, $tid, $args) = @_;
80             Scalar::Util::weaken $self;
81             $args->{'timeout'} //= AE::timer(
82             20, 0,
83             sub {
84             $self->expire_request($tid) if $self; # May ((poof)) $self
85             }
86             );
87             $code->($self, $tid, $args);
88             };
89             has ping_timer => (
90             isa => Ref, # ArrayRef|EV::Timer
91             builder => '_build_ping_timer',
92             is => 'ro',
93             init_arg => undef,
94             writer => '_ping_timer'
95             );
96              
97             sub _build_ping_timer {
98             my ($self) = @_;
99             Scalar::Util::weaken $self;
100             AE::timer(60 * 10, 60 * 10, sub { $self->ping if $self });
101             }
102             has seen => (
103             isa => Int,
104             is => 'ro',
105             lazy_build => 1,
106             init_arg => undef,
107             writer => '_set_seen',
108             handles => {
109             touch => sub { shift->_set_seen(time) },
110             active => sub {
111             return time - shift->seen <= 15 * 60;
112             }
113             }
114             );
115             for my $type (qw[get_peers find_node announce_peer]) {
116             has 'prev_'
117             . $type => (isa => 'HashRef[Int]',
118             is => 'ro',
119             lazy_build => 1,
120             builder => '_build_prev_X',
121             init_arg => undef,
122             traits => ['Hash'],
123             handles => {
124             'get_prev_' . $type => 'get',
125             'set_prev_' . $type => 'set',
126             'defined_prev_' . $type => 'defined'
127             }
128             );
129             }
130             sub _build_prev_X { {} }
131             after BUILD => sub {
132             my ($self) = @_;
133             Scalar::Util::weaken $self;
134             $self->_ping_timer(AE::timer(rand(30), 0, sub { $self->ping }));
135             };
136             has birth => (is => 'ro',
137             isa => Int,
138             init_arg => undef,
139             lazy_build => 1
140             );
141             sub _build_birth {time}
142              
143             sub ping {
144             my ($self) = @_;
145             state $tid = 'a';
146             my $packet = build_ping_query('p_' . $tid,
147             pack('H*', $self->dht->nodeid->to_Hex));
148             my $sent = $self->send($packet);
149             return $self->inc_fail() if !$sent;
150             $self->add_request('p_' . $tid, {type => 'ping'});
151             $tid++;
152             }
153              
154             sub _reply_ping {
155             my ($self, $tid) = @_;
156             my $packet
157             = build_ping_reply($tid, pack('H*', $self->dht->nodeid->to_Hex));
158             my $sent = $self->send($packet, 1);
159             $self->inc_fail() if !$sent;
160             return $sent;
161             }
162              
163             sub find_node {
164             my ($self, $target) = @_;
165             return
166             if $self->defined_prev_find_node($target->to_Hex)
167             && $self->get_prev_find_node($target->to_Hex) > time - (60 * 15);
168             state $tid = 'a';
169             my $packet =
170             build_find_node_query('fn_' . $tid,
171             pack('H*', $self->dht->nodeid->to_Hex),
172             pack('H*', $target->to_Hex));
173             my $sent = $self->send($packet);
174             return $self->inc_fail() if !$sent;
175             $self->add_request('fn_' . $tid,
176             {type => 'find_node', target => $target});
177             $tid++;
178             $self->set_prev_find_node($target->to_Hex, time);
179             }
180              
181             sub _reply_find_node {
182             my ($self, $tid, $target) = @_;
183             my $nodes
184             = compact_ipv4(
185             map { [$_->host, $_->port] }
186             @{$self->routing_table->nearest_bucket($target)->nodes});
187             return if !$nodes;
188             my $packet = build_find_node_reply($tid, pack('H*', $target->to_Hex),
189             $nodes);
190             my $sent = $self->send($packet, 1);
191             $self->inc_fail() if !$sent;
192             return $sent;
193             }
194              
195             sub get_peers {
196             my ($self, $info_hash) = @_;
197             return
198             if $self->defined_prev_get_peers($info_hash->to_Hex)
199             && $self->get_prev_get_peers($info_hash->to_Hex) > time - (60 * 15);
200             state $tid = 'a';
201             my $packet =
202             build_get_peers_query('gp_' . $tid,
203             pack('H*', $self->dht->nodeid->to_Hex),
204             pack('H*', $info_hash->to_Hex)
205             );
206             my $sent = $self->send($packet);
207             return $self->inc_fail() if !$sent;
208             $self->add_request('gp_' . $tid,
209             {type => 'get_peers', info_hash => $info_hash});
210             $tid++;
211             $self->set_prev_get_peers($info_hash->to_Hex, time);
212             }
213              
214             sub _reply_get_peers {
215             my ($self, $tid, $id) = @_;
216             if (!$self->has_announce_peer_token_out($id->to_Hex)) {
217             state $announce_peer_token = 'aa';
218             $announce_peer_token = 'aa' if length $announce_peer_token == 3;
219             $self->_set_announce_peer_token_out($id->to_Hex,
220             $announce_peer_token++);
221             }
222             my $nodes
223             = compact_ipv4(
224             map { [$_->host, $_->port] }
225             @{$self->routing_table->nearest_bucket($id)->nodes});
226             my @values = grep { defined $_ } map {
227             Net::BitTorrent::Protocol::BEP23::Compact::compact_ipv4(
228             [$_->[0], $_->[1]])
229             } @{$self->tracker->get_peers($id) || []};
230             return if (!@values && !$nodes);
231             my $packet =
232             build_get_peers_reply($tid,
233             $id->to_Hex,
234             \@values,
235             $nodes,
236             $self->_get_announce_peer_token_out(
237             $id->to_Hex
238             )
239             );
240             my $sent = $self->send($packet, 1);
241             $self->inc_fail() if !$sent;
242             return $sent;
243             }
244              
245             sub announce_peer {
246             my ($self, $info_hash, $port) = @_;
247             return
248             if $self->defined_prev_announce_peer($info_hash->to_Hex)
249             && $self->get_prev_announce_peer($info_hash->to_Hex)
250             > time - (60 * 30);
251             return if !$self->has_announce_peer_token_in($info_hash->to_Hex);
252             state $tid = 'a';
253             my $packet =
254             build_announce_peer_query('an_' . $tid,
255             pack('H*', $self->dht->nodeid->to_Hex),
256             pack('H*', $info_hash->to_Hex),
257             $self->_get_announce_peer_token_in(
258             $info_hash->to_Hex
259             ),
260             $port
261             );
262             my $sent = $self->send($packet);
263             return $self->inc_fail() if !$sent;
264             $self->add_request('an_' . $tid,
265             {type => 'announce_peer', info_hash => $info_hash});
266             $tid++;
267             $self->set_prev_announce_peer($info_hash->to_Hex, time);
268             }
269              
270             sub _reply_announce_peer {
271             my ($self, $tid, $info_hash, $a_ref) = @_;
272             my $packet;
273             if ((!$self->has_announce_peer_token_out($info_hash->to_Hex))
274             || ($self->_get_announce_peer_token_out($info_hash->to_Hex) ne
275             $a_ref->{'token'})
276             )
277             { $packet =
278             build_error_reply($tid,
279             [203,
280             'Incorrect write token in announce_peer'
281             ]
282             );
283             }
284             elsif (
285             !$self->tracker->add_peer($info_hash, [$self->host, $a_ref->{'port'}]
286             )
287             )
288             { $packet = build_error_reply($tid,
289             [202, 'Failed to add peer to tracker']);
290             }
291             else {
292             $packet = build_announce_peer_reply($tid,
293             pack('H*', $self->dht->nodeid->to_Hex));
294             }
295             my $sent = $self->send($packet, 1);
296             $self->inc_fail() if !$sent;
297             return $sent;
298             }
299             has fail => (
300             isa => Int,
301             traits => ['Counter'],
302             default => 0,
303             is => 'ro',
304             handles => {inc_fail => 'inc'},
305             init_arg => undef,
306             trigger => sub {
307             my ($self, $new, $old) = @_;
308             $self->routing_table->del_node($self)
309             if $new == ($self->has_bucket ? 5 : 1);
310             }
311             );
312             1;
313              
314             =pod
315              
316             =head1 NAME
317              
318             Net::BitTorrent::DHT::Node - A single node in a DHT routing table
319              
320             =head1 Description
321              
322             TODO
323              
324             =head1 Author
325              
326             Sanko Robinson <sanko@cpan.org> - http://sankorobinson.com/
327              
328             CPAN ID: SANKO
329              
330             =head1 License and Legal
331              
332             Copyright (C) 2008-2014 by Sanko Robinson <sanko@cpan.org>
333              
334             This program is free software; you can redistribute it and/or modify it under
335             the terms of
336             L<The Artistic License 2.0|http://www.perlfoundation.org/artistic_license_2_0>.
337             See the F<LICENSE> file included with this distribution or
338             L<notes on the Artistic License 2.0|http://www.perlfoundation.org/artistic_2_0_notes>
339             for clarification.
340              
341             When separated from the distribution, all original POD documentation is
342             covered by the
343             L<Creative Commons Attribution-Share Alike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/us/legalcode>.
344             See the
345             L<clarification of the CCA-SA3.0|http://creativecommons.org/licenses/by-sa/3.0/us/>.
346              
347             Neither this module nor the L<Author|/Author> is affiliated with BitTorrent,
348             Inc.
349              
350             =cut