File Coverage

blib/lib/Net/BitTorrent/Protocol/BEP10.pm
Criterion Covered Total %
statement 17 17 100.0
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 23 23 100.0


line stmt bran cond sub pod time code
1 23     23   233096 use v5.40;
  23         89  
2 23     23   191 use feature 'class', 'try';
  23         66  
  23         3210  
3 23     23   1299 no warnings 'experimental::class', 'experimental::try';
  23         58  
  23         3796  
4 23     23   11755 class Net::BitTorrent::Protocol::BEP10 v2.0.0 : isa(Net::BitTorrent::Protocol::BEP52) {
  23         75  
  23         1288  
5 23     23   2023 use Net::BitTorrent::Protocol::BEP03::Bencode qw[bencode bdecode];
  23         84  
  23         6726  
6             field $local_extensions : param : reader = {};
7             field $remote_extensions : reader = {};
8             field $remote_version : reader = undef;
9             field $remote_ip : reader = undef;
10             field $metadata_size : param : reader = 0;
11             field $remote_extensions_received : reader = 0;
12              
13             # Message ID for extended messages
14 23     23   203 use constant EXTENDED => 20;
  23         44  
  23         36934  
15              
16             method send_ext_handshake () {
17             my $data = { m => $local_extensions, v => 'Net::BitTorrent ' . ( $Net::BitTorrent::VERSION // $Net::BitTorrent::Protocol::BEP10::VERSION ) };
18             $data->{metadata_size} = $metadata_size if $metadata_size > 0;
19             my $payload = bencode($data);
20             $self->send_message( EXTENDED, pack( 'C a*', 0, $payload ) );
21             }
22              
23             method send_ext_message ( $name, $payload ) {
24             my $id = $remote_extensions->{$name};
25             if ( !defined $id ) {
26             $self->_emit( log => "Remote does not support extension: $name", level => 'fatal' );
27             return;
28             }
29             $self->send_message( EXTENDED, pack( 'C a*', $id, $payload ) );
30             }
31              
32             method _handle_message ( $id, $payload ) {
33             if ( $id == EXTENDED ) {
34             return if length($payload) < 1;
35             my $ext_id = unpack( 'C', substr( $payload, 0, 1, '' ) );
36             if ( $ext_id == 0 ) {
37             $self->_handle_ext_handshake($payload);
38             }
39             else {
40             my $name = $self->_lookup_local_extension($ext_id);
41             if ($name) {
42             $self->_emit( extended_message => $name, $payload );
43             }
44             else {
45             $self->_emit( log => " [DEBUG] Received unknown extended message ID: $ext_id\n", level => 'debug' ) if $self->debug;
46             }
47             }
48             }
49             else {
50             $self->SUPER::_handle_message( $id, $payload );
51             }
52             }
53              
54             method _handle_ext_handshake ($payload) {
55             my $data;
56             try {
57             my @res = bdecode( $payload, 1 );
58             if ( @res > 2 ) { # Dictionary returned as key-value list + leftover
59             pop @res; # Discard leftover
60             $data = {@res};
61             }
62             else {
63             $data = $res[0];
64             }
65             }
66             catch ($e) {
67             $self->_emit( log => " [ERROR] Malformed extended handshake: $e\n", level => 'error' );
68             return;
69             }
70             if ( ref $data ne 'HASH' ) {
71             $self->_emit( log => " [ERROR] Malformed extended handshake: data is not a hash\n", level => 'error' );
72             return;
73             }
74             $remote_extensions = $data->{m} || {};
75             if ( $self->debug ) {
76             $self->_emit(
77             log => " [DEBUG] Remote extensions: " . join( ", ", map {"$_=$remote_extensions->{$_}"} keys %$remote_extensions ) . "\n",
78             level => 'debug'
79             );
80             }
81             $remote_version = $data->{v} if exists $data->{v};
82             $remote_ip = $data->{yourip} if exists $data->{yourip};
83             $metadata_size = $data->{metadata_size} if exists $data->{metadata_size};
84             $remote_extensions_received = 1;
85             $self->_emit( ext_handshake => $data );
86             }
87              
88             method _lookup_local_extension ($id) {
89             for my $name ( keys %$local_extensions ) {
90             return $name if $local_extensions->{$name} == $id;
91             }
92             return undef;
93             }
94              
95             method _lookup_remote_extension ($id) {
96             for my $name ( keys %$remote_extensions ) {
97             return $name if $remote_extensions->{$name} == $id;
98             }
99             return undef;
100             }
101             } 1;