File Coverage

blib/lib/Protocol/WebSocket/Frame.pm
Criterion Covered Total %
statement 168 172 97.6
branch 82 92 89.1
condition 28 40 70.0
subroutine 25 26 96.1
pod 16 18 88.8
total 319 348 91.6


line stmt bran cond sub pod time code
1             package Protocol::WebSocket::Frame;
2              
3 13     13   2387 use strict;
  13         29  
  13         384  
4 13     13   70 use warnings;
  13         26  
  13         336  
5              
6 13     13   64 use Config;
  13         26  
  13         510  
7 13     13   3620 use Encode ();
  13         78650  
  13         329  
8 13     13   132 use Scalar::Util 'readonly';
  13         29  
  13         786  
9              
10 13     13   75 use constant MAX_RAND_INT => 2**32;
  13         27  
  13         830  
11 13     13   74 use constant MATH_RANDOM_SECURE => eval "require Math::Random::Secure;";
  13         26  
  13         732  
12              
13             our $MAX_PAYLOAD_SIZE = 65536;
14              
15             our %TYPES = (
16             continuation => 0x00,
17             text => 0x01,
18             binary => 0x02,
19             ping => 0x09,
20             pong => 0x0a,
21             close => 0x08
22             );
23              
24             sub new {
25 71     71 1 23062 my $class = shift;
26 71 50       166 $class = ref $class if ref $class;
27 71         100 my $buffer;
28              
29 71 100       154 if (@_ == 1) {
30 3         7 $buffer = shift @_;
31             }
32             else {
33 68         165 my %args = @_;
34 68         153 $buffer = delete $args{buffer};
35             }
36              
37 71         161 my $self = {@_};
38 71         112 bless $self, $class;
39              
40 71 100       166 $buffer = '' unless defined $buffer;
41              
42 71 100       217 if (Encode::is_utf8($buffer)) {
43 9         27 $self->{buffer} = Encode::encode('UTF-8', $buffer);
44             }
45             else {
46 62         124 $self->{buffer} = $buffer;
47             }
48              
49 71 100 66     884 if (defined($self->{type}) && defined($TYPES{$self->{type}})) {
50 18         45 $self->opcode($TYPES{$self->{type}});
51             }
52              
53 71   100     288 $self->{version} ||= 'draft-ietf-hybi-17';
54              
55 71         119 $self->{fragments} = [];
56              
57 71   50     240 $self->{max_fragments_amount} ||= 128;
58 71 100 33     261 $self->{max_payload_size} ||= $MAX_PAYLOAD_SIZE unless exists $self->{max_payload_size};
59              
60 71         232 return $self;
61             }
62              
63             sub version {
64 191     191 1 239 my $self = shift;
65              
66 191         550 return $self->{version};
67             }
68              
69             sub append {
70 176     176 1 1259 my $self = shift;
71              
72 176 100       400 return unless defined $_[0];
73              
74 174         400 $self->{buffer} .= $_[0];
75 174 100       351 $_[0] = '' unless readonly $_[0];
76              
77 174         253 return $self;
78             }
79              
80             sub next {
81 27     27 1 3686 my $self = shift;
82              
83 27         49 my $bytes = $self->next_bytes;
84 27 100       84 return unless defined $bytes;
85              
86 13         45 return Encode::decode('UTF-8', $bytes);
87             }
88              
89             sub fin {
90             @_ > 1 ? $_[0]->{fin} =
91             $_[1]
92             : defined($_[0]->{fin}) ? $_[0]->{fin}
93 347 100   347 1 814 : 1;
    100          
94             }
95 157 50   157 0 305 sub rsv { @_ > 1 ? $_[0]->{rsv} = $_[1] : $_[0]->{rsv} }
96              
97             sub opcode {
98             @_ > 1 ? $_[0]->{opcode} =
99             $_[1]
100             : defined($_[0]->{opcode}) ? $_[0]->{opcode}
101 141 100   141 1 3394 : 1;
    100          
102             }
103 539 100   539 1 1029 sub masked { @_ > 1 ? $_[0]->{masked} = $_[1] : $_[0]->{masked} }
104              
105 7     7 1 2249 sub is_ping { $_[0]->opcode == 9 }
106 7     7 1 2176 sub is_pong { $_[0]->opcode == 10 }
107 7     7 1 2683 sub is_close { $_[0]->opcode == 8 }
108 6     6 1 48 sub is_continuation { $_[0]->opcode == 0 }
109 7     7 1 2211 sub is_text { $_[0]->opcode == 1 }
110 7     7 1 2293 sub is_binary { $_[0]->opcode == 2 }
111              
112             sub next_bytes {
113 53     53 1 96 my $self = shift;
114              
115 53 100 66     98 if ( $self->version eq 'draft-hixie-75'
116             || $self->version eq 'draft-ietf-hybi-00')
117             {
118 20 100       59 if ($self->{buffer} =~ s/^\xff\x00//) {
119 1         6 $self->opcode(8);
120 1         4 return '';
121             }
122              
123 19 100       92 return unless $self->{buffer} =~ s/^[^\x00]*\x00(.*?)\xff//s;
124              
125 10         48 return $1;
126             }
127              
128 33 100       86 return unless length $self->{buffer} >= 2;
129              
130 28         69 while (length $self->{buffer}) {
131 157         240 my $hdr = substr($self->{buffer}, 0, 1);
132              
133 157         437 my @bits = split //, unpack("B*", $hdr);
134              
135 157         332 $self->fin($bits[0]);
136 157         428 $self->rsv([@bits[1 .. 3]]);
137              
138 157         254 my $opcode = unpack('C', $hdr) & 0b00001111;
139              
140 157         196 my $offset = 1; # FIN,RSV[1-3],OPCODE
141              
142 157         258 my $payload_len = unpack 'C', substr($self->{buffer}, 1, 1);
143              
144 157         212 my $masked = ($payload_len & 0b10000000) >> 7;
145 157         292 $self->masked($masked);
146              
147 157         182 $offset += 1; # + MASKED,PAYLOAD_LEN
148              
149 157         185 $payload_len = $payload_len & 0b01111111;
150 157 100       292 if ($payload_len == 126) {
    100          
151 1 50       3 return unless length($self->{buffer}) >= $offset + 2;
152              
153 1         4 $payload_len = unpack 'n', substr($self->{buffer}, $offset, 2);
154              
155 1         1 $offset += 2;
156             }
157             elsif ($payload_len > 126) {
158 5 50       15 return unless length($self->{buffer}) >= $offset + 4;
159              
160 40         70 my $bits = join '', map { unpack 'B*', $_ } split //,
161 5         18 substr($self->{buffer}, $offset, 8);
162              
163             # Most significant bit must be 0.
164             # And here is a crazy way of doing it %)
165 5         26 $bits =~ s{^.}{0};
166              
167             # Can we handle 64bit numbers?
168 5 50 33     199 if ($Config{ivsize} <= 4 || $Config{longsize} < 8 || $] < 5.010) {
      33        
169 0         0 $bits = substr($bits, 32);
170 0         0 $payload_len = unpack 'N', pack 'B*', $bits;
171             }
172             else {
173 5         25 $payload_len = unpack 'Q>', pack 'B*', $bits;
174             }
175              
176 5         12 $offset += 8;
177             }
178              
179 157 100 100     422 if ($self->{max_payload_size} && $payload_len > $self->{max_payload_size}) {
180 2         4 $self->{buffer} = '';
181 2         21 die "Payload is too big. "
182             . "Deny big message ($payload_len) "
183             . "or increase max_payload_size ($self->{max_payload_size})";
184             }
185              
186 155         175 my $mask;
187 155 100       221 if ($self->masked) {
188 6 50       15 return unless length($self->{buffer}) >= $offset + 4;
189              
190 6         12 $mask = substr($self->{buffer}, $offset, 4);
191 6         8 $offset += 4;
192             }
193              
194 155 50       280 return if length($self->{buffer}) < $offset + $payload_len;
195              
196 155         287 my $payload = substr($self->{buffer}, $offset, $payload_len);
197              
198 155 100       223 if ($self->masked) {
199 6         11 $payload = $self->_mask($payload, $mask);
200             }
201              
202 155         259 substr($self->{buffer}, 0, $offset + $payload_len, '');
203              
204             # Injected control frame
205 155 100 100     174 if (@{$self->{fragments}} && $opcode & 0b1000) {
  155         379  
206 1         2 $self->opcode($opcode);
207 1         5 return $payload;
208             }
209              
210 154 100       235 if ($self->fin) {
211 21 100       29 if (@{$self->{fragments}}) {
  21         36  
212 4         8 $self->opcode(shift @{$self->{fragments}});
  4         7  
213             }
214             else {
215 17         32 $self->opcode($opcode);
216             }
217 21         33 $payload = join '', @{$self->{fragments}}, $payload;
  21         148  
218 21         46 $self->{fragments} = [];
219 21         94 return $payload;
220             }
221             else {
222              
223             # Remember first fragment opcode
224 133 100       152 if (!@{$self->{fragments}}) {
  133         206  
225 5         8 push @{$self->{fragments}}, $opcode;
  5         11  
226             }
227              
228 133         152 push @{$self->{fragments}}, $payload;
  133         220  
229              
230             die "Too many fragments"
231 133 100       150 if @{$self->{fragments}} > $self->{max_fragments_amount};
  133         372  
232             }
233             }
234              
235 3         10 return;
236             }
237              
238             sub to_bytes {
239 42     42 1 131 my $self = shift;
240              
241 42 100 66     71 if ( $self->version eq 'draft-hixie-75'
242             || $self->version eq 'draft-ietf-hybi-00')
243             {
244 4 100 100     14 if ($self->{type} && $self->{type} eq 'close') {
245 1         4 return "\xff\x00";
246             }
247              
248 3         17 return "\x00" . $self->{buffer} . "\xff";
249             }
250              
251 38 100 100     128 if ($self->{max_payload_size} && length $self->{buffer} > $self->{max_payload_size}) {
252 2         16 die "Payload is too big. "
253             . "Send shorter messages or increase max_payload_size";
254             }
255              
256              
257 36         53 my $rsv_set = 0;
258 36 100 66     108 if ( $self->{rsv} && ref( $self->{rsv} ) eq 'ARRAY' ) {
259 7         9 for my $i ( 0 .. @{ $self->{rsv} } - 1 ) {
  7         19  
260 21         36 $rsv_set += $self->{rsv}->[$i] * ( 1 << ( 6 - $i ) );
261             }
262             }
263              
264 36         53 my $string = '';
265 36         63 my $opcode = $self->opcode;
266 36 100       83 $string .= pack 'C', ($opcode | $rsv_set | ($self->fin ? 128 : 0));
267              
268 36         66 my $payload_len = length($self->{buffer});
269 36 100       73 if ($payload_len <= 125) {
    100          
270 29 100       48 $payload_len |= 0b10000000 if $self->masked;
271 29         59 $string .= pack 'C', $payload_len;
272             }
273             elsif ($payload_len <= 0xffff) {
274 1 50       9 $string .= pack 'C', 126 + ($self->masked ? 128 : 0);
275 1         3 $string .= pack 'n', $payload_len;
276             }
277             else {
278 6 50       12 $string .= pack 'C', 127 + ($self->masked ? 128 : 0);
279              
280             # Shifting by an amount >= to the system wordsize is undefined
281 6 50       107 $string .= pack 'N', $Config{ivsize} <= 4 ? 0 : $payload_len >> 32;
282 6         19 $string .= pack 'N', ($payload_len & 0xffffffff);
283             }
284              
285 36 100       69 if ($self->masked) {
286              
287             my $mask = $self->{mask}
288             || (
289 7   66     86 MATH_RANDOM_SECURE
290             ? Math::Random::Secure::irand(MAX_RAND_INT)
291             : int(rand(MAX_RAND_INT))
292             );
293              
294 7         24 $mask = pack 'N', $mask;
295              
296 7         12 $string .= $mask;
297 7         21 $string .= $self->_mask($self->{buffer}, $mask);
298             }
299             else {
300 29         183 $string .= $self->{buffer};
301             }
302              
303 36         183 return $string;
304             }
305              
306             sub to_string {
307 0     0 0 0 my $self = shift;
308              
309 0         0 die 'DO NOT USE';
310             }
311              
312             sub _mask {
313 13     13   20 my $self = shift;
314 13         32 my ($payload, $mask) = @_;
315              
316 13         45 $mask = $mask x (int(length($payload) / 4) + 1);
317 13         27 $mask = substr($mask, 0, length($payload));
318 13         32 $payload = "$payload" ^ $mask;
319              
320 13         30 return $payload;
321             }
322              
323             sub max_payload_size {
324 8     8 1 16 my $self = shift;
325              
326 8         32 return $self->{max_payload_size};
327             }
328              
329             1;
330             __END__