File Coverage

blib/lib/Protocol/BitTorrent/Message.pm
Criterion Covered Total %
statement 59 66 89.3
branch 7 14 50.0
condition 1 3 33.3
subroutine 18 21 85.7
pod 5 8 62.5
total 90 112 80.3


line stmt bran cond sub pod time code
1             package Protocol::BitTorrent::Message;
2             {
3             $Protocol::BitTorrent::Message::VERSION = '0.004';
4             }
5 1     1   19770 use strict;
  1         2  
  1         34  
6 1     1   4 use warnings FATAL => 'all', NONFATAL => 'redefine';
  1         2  
  1         38  
7              
8 1     1   468 use Protocol::BitTorrent::Message::Keepalive;
  1         2  
  1         24  
9 1     1   469 use Protocol::BitTorrent::Message::Choke;
  1         2  
  1         24  
10 1     1   435 use Protocol::BitTorrent::Message::Unchoke;
  1         2  
  1         25  
11 1     1   474 use Protocol::BitTorrent::Message::Interested;
  1         2  
  1         25  
12 1     1   434 use Protocol::BitTorrent::Message::Uninterested;
  1         2  
  1         26  
13 1     1   469 use Protocol::BitTorrent::Message::Have;
  1         2  
  1         25  
14 1     1   484 use Protocol::BitTorrent::Message::Bitfield;
  1         2  
  1         27  
15 1     1   483 use Protocol::BitTorrent::Message::Request;
  1         2  
  1         29  
16 1     1   488 use Protocol::BitTorrent::Message::Piece;
  1         2  
  1         30  
17 1     1   446 use Protocol::BitTorrent::Message::Cancel;
  1         2  
  1         33  
18 1     1   482 use Protocol::BitTorrent::Message::Port;
  1         3  
  1         549  
19              
20             =head1 NAME
21              
22             Protocol::BitTorrent::Message - base class for BitTorrent messages
23              
24             =head1 VERSION
25              
26             version 0.004
27              
28             =head1 SYNOPSIS
29              
30             use Protocol::BitTorrent::Message;
31             $sock->read(my $buf, 4096);
32             while(my $msg = Protocol::BitTorrent::Message->new_from_buffer(\$buf)) {
33             $self->handle_message($msg);
34             }
35              
36             =head1 DESCRIPTION
37              
38             See L and L for
39             usage information.
40              
41             =cut
42              
43             =head1 METHODS
44              
45             =cut
46              
47             =head2 new
48              
49             Base method for instantiation, returns a blessed object.
50              
51             =cut
52              
53             sub new {
54 1     1 1 3 my $self = bless {}, shift;
55 1         9 $self;
56             }
57              
58             =head2 new_from_buffer
59              
60             Returns an instance of a L subclass, or undef if
61             insufficient data has been provided in the buffer.
62              
63             Takes a single scalar ref as parameter - this should be a reference to the scalar
64             buffer containing data to be parsed. Removes packet data from this buffer if
65             parsing was successful.
66              
67             =cut
68              
69             sub new_from_buffer {
70 11     11 1 6418 my $class = shift;
71 11         25 my $buffer = shift;
72 11 50 33     78 return undef unless defined $buffer && length $$buffer >= 4;
73              
74             # First item is the length (excluding 4-byte length field)
75 11         46 my ($len) = unpack 'N1', substr $$buffer, 0, 4;
76              
77             # Keepalive messages just contain the 4-byte length, no other data
78 11 100       30 unless($len) {
79 1         4 substr $$buffer, 0, 4, '';
80 1         14 return Protocol::BitTorrent::Message::Keepalive->new;
81             }
82              
83             # If we don't have enough data, bail out until we get more
84 10 50       30 return undef unless length $$buffer >= $len;
85              
86             # At this point we can modify our section of the buffer with impunity since we
87             # know that we have at least one packet. Drop the length first.
88 10         19 substr $$buffer, 0, 4, '';
89 10         28 my ($type_id) = unpack 'C1', substr $$buffer, 0, 1, '';
90 10 50       31 my $class_name = $class->class_name_by_type($type_id)
91             or die sprintf "Invalid type [%02x] detected", $type_id;
92              
93             # Drop the type byte from our length calculations
94 10         16 --$len;
95              
96             # Should probably check for valid buffer lengths, better to keep this in the subclass though?
97             # die "Bad buffer: " . join ' ', map sprintf('%02x', ord), split //, $$buffer if $len != 12 && $type_id == 6 && length $$buffer >= 12;
98 10 100       150 return $class_name->new_from_data($len ? substr $$buffer, 0, $len, '' : '');
99             }
100              
101             {
102              
103             my %type_for_id = (
104             0 => 'choke',
105             1 => 'unchoke',
106             2 => 'interested',
107             3 => 'uninterested',
108             4 => 'have',
109             5 => 'bitfield',
110             6 => 'request',
111             7 => 'piece',
112             8 => 'cancel',
113             9 => 'port',
114             );
115              
116             my %id_for_type = reverse %type_for_id;
117              
118             =head2 class_name_by_type
119              
120             Returns the class name for the given type (as taken from a BitTorrent network packet).
121              
122             =cut
123              
124             sub class_name_by_type {
125 10     10 1 19 my ($self, $type) = @_;
126 10         57 return __PACKAGE__ . '::' . ucfirst $type_for_id{$type};
127             }
128              
129             sub type_id {
130 0     0 0 0 my $self = shift;
131 0 0       0 my $type = $self->type or die "No type for $self";
132 0 0       0 die "No ID found for [$type] on $self" unless exists $id_for_type{$type};
133 0         0 return $id_for_type{$type};
134             }
135              
136             }
137              
138             =head2 as_string
139              
140             Returns a stringified version of this message.
141              
142             =cut
143              
144             sub as_string {
145 8     8 1 17 my $self = shift;
146 8         38 return sprintf '%s, %d bytes', $self->type, $self->packet_length;
147             }
148              
149 10     10 0 81 sub packet_length { 0 }
150              
151             =head2 type
152              
153             Returns the type for this message - stub method for base class, should be overridden
154             in subclasses.
155              
156             =cut
157              
158 0     0 1   sub type { 'unknown' }
159              
160             sub as_data {
161 0     0 0   my $self = shift;
162 0           return pack 'N1C1', 1, $self->type_id;
163             }
164              
165             1;
166              
167             __END__