File Coverage

lib/Net/BitTorrent/Transport/TCP.pm
Criterion Covered Total %
statement 20 20 100.0
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 27 27 100.0


line stmt bran cond sub pod time code
1 19     19   302 use v5.40;
  19         82  
2 19     19   122 use feature 'class';
  19         45  
  19         3237  
3 19     19   188 no warnings 'experimental::class';
  19         52  
  19         1399  
4 19     19   125 use Net::BitTorrent::Emitter;
  19         40  
  19         2263  
5             class Net::BitTorrent::Transport::TCP v2.0.0 : isa(Net::BitTorrent::Emitter) {
6 19     19   128 use IO::Select;
  19         41  
  19         1110  
7 19     19   114 use Errno;
  19         52  
  19         18363  
8             field $socket : param : reader;
9             field $write_buffer = '';
10             field $connecting : param = 1;
11             field $filter : reader = undef;
12             ADJUST {
13             if ( $socket && $socket->opened ) {
14             $socket->blocking(0);
15             }
16             }
17              
18             method clear_listeners ($event) {
19              
20             # This will need to be updated to clear $on from Emitter if needed
21             # but Emitter currently doesn't provide a way to clear.
22             # For now, let's keep it but it might be broken until Emitter is improved.
23             }
24              
25             method set_filter ($f) {
26             $filter = $f;
27             }
28              
29             method send_data ($data) {
30             if ( $filter && $filter->can('encrypt_data') && $filter->state eq 'PAYLOAD' ) {
31             $data = $filter->encrypt_data($data);
32             }
33              
34             # warn " [DEBUG] TCP::send_data: " . length($data) . " bytes\n";
35             $write_buffer .= $data;
36             $self->_flush_write_buffer();
37             return length $data;
38             }
39              
40             method send_raw ($data) {
41              
42             # warn " [DEBUG] TCP::send_raw: " . length($data) . " bytes\n";
43             $write_buffer .= $data;
44             $self->_flush_write_buffer();
45             return length $data;
46             }
47              
48             method _flush_write_buffer () {
49             return unless length $write_buffer;
50             return if $connecting;
51             my $sent = $socket->syswrite($write_buffer);
52             if ( defined $sent && $sent > 0 ) {
53             substr( $write_buffer, 0, $sent, '' );
54             }
55             elsif ( !defined $sent && $! != Errno::EWOULDBLOCK && $! != Errno::EAGAIN ) {
56             $self->_emit( log => " [DEBUG] TCP write error: $!\n", level => 'debug' );
57             $self->_emit('disconnected');
58             }
59             }
60              
61             method tick () {
62             return unless $socket && $socket->opened;
63             if ($connecting) {
64             my $sel = IO::Select->new($socket);
65             if ( $sel->can_write(0) ) {
66              
67             # Check for actual connection success
68 19     19   171 use Socket qw[SOL_SOCKET SO_ERROR];
  19         58  
  19         23861  
69             my $error = $socket->getsockopt( SOL_SOCKET, SO_ERROR );
70             if ( $error == 0 ) {
71             $connecting = 0;
72              
73             # warn " [DEBUG] TCP connection established to " . $socket->peerhost . ":" . $socket->peerport . "\n";
74             $self->_emit('connected');
75             }
76             else {
77             $! = $error;
78             $self->_emit(
79             log => " [DEBUG] TCP connection failed to " . $socket->peerhost . ":" . $socket->peerport . ": $!\n",
80             level => 'debug'
81             );
82             $self->_emit('disconnected');
83             return;
84             }
85             }
86             else {
87             return;
88             }
89             }
90              
91             # If we have a filter, it might have data to send (handshake)
92             if ( $filter && $filter->can('write_buffer') ) {
93             my $f_buf = $filter->write_buffer();
94             if ( length $f_buf ) {
95             $write_buffer .= $f_buf;
96             }
97             }
98             $self->_flush_write_buffer();
99             my $len = $socket->sysread( my $buffer, 65535 );
100             if ( defined $len && $len > 0 ) {
101              
102             # warn " [DEBUG] TCP::tick received $len bytes\n";
103             if ($filter) {
104             my $decrypted = $filter->receive_data($buffer);
105             if ( $filter->state eq 'PLAINTEXT_FALLBACK' ) {
106             $self->_emit( log => " [DEBUG] Transport filter requested plaintext fallback\n", level => 'debug' );
107             my $leftover = $filter->buffer_in;
108             $filter = undef;
109             $self->_emit( 'filter_failed', $leftover );
110             $self->receive_data($leftover);
111             return;
112             }
113             elsif ( $filter->state eq 'FAILED' ) {
114             $self->_emit( log => " [ERROR] Transport filter handshake FAILED\n", level => 'error' );
115             my $leftover = $filter->buffer_in;
116             $filter = undef;
117             $self->_emit( 'filter_failed', $leftover );
118              
119             # We don't call receive_data($leftover) here because it might be MSE garbage
120             return;
121             }
122             if ( defined $decrypted && length $decrypted ) {
123             $self->receive_data($decrypted);
124             }
125              
126             # After receiving, filter might have more to send
127             my $f_buf = $filter->write_buffer();
128             if ( length $f_buf ) {
129             $write_buffer .= $f_buf;
130             $self->_flush_write_buffer();
131             }
132             }
133             else {
134             $self->receive_data($buffer);
135             }
136             }
137             elsif ( defined $len && $len == 0 ) {
138             $self->_emit( log => " [DEBUG] TCP remote closed connection\n", level => 'debug' );
139             $self->_emit('disconnected');
140             }
141             elsif ( !defined $len && $! != Errno::EWOULDBLOCK && $! != Errno::EAGAIN ) {
142             $self->_emit( log => " [DEBUG] TCP read error: $!\n", level => 'debug' );
143             $self->_emit('disconnected');
144             }
145             }
146              
147             method receive_data ($data) {
148             $self->_emit( 'data', $data );
149             }
150              
151             method close () {
152             if ( $socket && $socket->opened ) {
153             $socket->close();
154             }
155             }
156              
157             method state () {
158             return $socket && $socket->opened ? 'CONNECTED' : 'CLOSED';
159             }
160             } 1;