File Coverage

blib/lib/Net/WebSocket/PMCE/deflate/Data.pm
Criterion Covered Total %
statement 74 82 90.2
branch 10 18 55.5
condition 8 18 44.4
subroutine 17 19 89.4
pod 4 4 100.0
total 113 141 80.1


line stmt bran cond sub pod time code
1             package Net::WebSocket::PMCE::deflate::Data;
2              
3 3     3   1087 use strict;
  3         6  
  3         61  
4 3     3   12 use warnings;
  3         4  
  3         62  
5              
6 3     3   11 use parent qw( Net::WebSocket::PMCE::Data );
  3         6  
  3         21  
7              
8 3     3   986 use Net::WebSocket::FrameTypeName ();
  3         7  
  3         44  
9 3     3   308 use Net::WebSocket::Message ();
  3         5  
  3         51  
10 3     3   659 use Net::WebSocket::PMCE::deflate::Constants ();
  3         23  
  3         77  
11              
12             use constant {
13 3         2816 _ZLIB_SYNC_TAIL => "\0\0\xff\xff",
14             _DEBUG => 0,
15 3     3   23 };
  3         4  
16              
17             =head2 I->new( %OPTS )
18              
19             %OPTS is:
20              
21             =over
22              
23             =item C - optional; the number of window bits to use
24             for compressing messages. This should correspond with the local endpoint’s
25             behavior; i.e., for a server, this should match the C
26             extension parameter in the WebSocket handshake.
27              
28             =item C - optional; the number of window bits to use
29             for decompressing messages. This should correspond with the remote peer’s
30             behavior; i.e., for a server, this should match the C
31             extension parameter in the WebSocket handshake.
32              
33             =item C - corresponds to either the
34             C or C parameter,
35             to match the local endpoint’s role. When this flag is set, the object
36             will do a full flush at the end of each C call.
37              
38             =back
39              
40             =cut
41              
42             sub new {
43 3     3 1 2305 my ($class, %opts) = @_;
44              
45             #Validate deflate_max_window_bits/inflate_max_window_bits?
46              
47 3         7 my $compress_func = '_compress_';
48 3 100       23 $compress_func .= $opts{'deflate_no_context_takeover'} ? 'full' : 'sync';
49 3         7 $compress_func .= '_flush_chomp';
50              
51 3         7 $opts{'final_frame_compress_func'} = $compress_func;
52              
53 3         11 return bless \%opts, $class;
54             }
55              
56             #----------------------------------------------------------------------
57              
58             =head2 $msg = I->create_message( FRAME_TYPE, PAYLOAD )
59              
60             Creates an unfragmented, compressed message. The message will be an
61             instance of L.
62              
63             FRAME_TYPE can be either C or C (for Net::WebSocket’s
64             default frame classes) or full package names (e.g., to use a custom
65             frame class).
66              
67             This method cannot be called while a streamer object has yet to create its
68             final frame.
69              
70             B This function alters PAYLOAD.
71              
72             =cut
73              
74             sub create_message {
75 0     0 1 0 my ($self, $frame_type) = @_; #$_[2] = payload
76              
77 0 0       0 die "A streamer is active!" if $self->{'_streamer_mode'};
78              
79 0         0 my $compress_func = $self->{'final_frame_compress_func'};
80              
81 0         0 my $payload_sr = \($self->$compress_func( $_[2] ));
82              
83 0         0 my $frame_class = Net::WebSocket::FrameTypeName::get_module($frame_type);
84              
85 0         0 return Net::WebSocket::Message->new(
86             $frame_class->new(
87             payload => $payload_sr,
88             rsv => $self->INITIAL_FRAME_RSV(),
89             $self->FRAME_MASK_ARGS(),
90             ),
91             );
92             }
93              
94             #----------------------------------------------------------------------
95              
96             =head2 $msg = I->create_streamer( FRAME_TYPE )
97              
98             FRAME_TYPE can be either C or C (for Net::WebSocket’s
99             default frame classes) or full package names (e.g., to use a custom
100             frame class).
101              
102             Returns an instance of L based
103             on this object.
104              
105             =cut
106              
107             sub create_streamer {
108 5     5 1 1327 my ($self, $frame_type) = @_;
109              
110 5         27 $self->{'_streamer_mode'} = 1;
111              
112 5         1312 require Net::WebSocket::PMCE::deflate::Data::Streamer;
113              
114 5         23 my $frame_class = Net::WebSocket::FrameTypeName::get_module($frame_type);
115              
116 5         35 return Net::WebSocket::PMCE::deflate::Data::Streamer->new($self, $frame_class);
117             }
118              
119             #----------------------------------------------------------------------
120              
121             =head2 $decompressed = I->decompress( COMPRESSED_PAYLOAD )
122              
123             Decompresses the given string and returns the result.
124              
125             B This function alters COMPRESSED_PAYLOAD, such that
126             it’s probably not useful afterward.
127              
128             =cut
129              
130             #cf. RFC 7692, 7.2.2
131             sub decompress {
132 5     5 1 11 my ($self) = @_; #$_[1] = payload
133              
134 5   66     33 $self->{'i'} ||= $self->_create_inflate_obj();
135              
136 5         7 _DEBUG && _debug(sprintf "inflating: %v.02x\n", $_[1]);
137              
138 5         15 $_[1] .= _ZLIB_SYNC_TAIL;
139              
140 5         5551 my $status = $self->{'i'}->inflate($_[1], my $v);
141 5 50       20 die $status if $status != Compress::Raw::Zlib::Z_OK();
142              
143 5         23 _DEBUG && _debug(sprintf "inflate output: [%v.02x]\n", $v);
144              
145 5         9526 return $v;
146             }
147              
148             #----------------------------------------------------------------------
149              
150             my $_payload_sr;
151              
152             #cf. RFC 7692, 7.2.1
153             #Use for non-final fragments.
154             sub _compress_non_final_fragment {
155 50   66 50   201 $_[0]->{'d'} ||= $_[0]->_create_deflate_obj();
156              
157 50         119 return $_[0]->_compress( $_[1] );
158             }
159              
160             #Preserves sliding window to the next message.
161             #Use for final fragments when deflate_no_context_takeover is OFF
162             sub _compress_sync_flush_chomp {
163 2   33 2   6 $_[0]->{'d'} ||= $_[0]->_create_deflate_obj();
164              
165 2         6 return _chomp_0000ffff_or_die( $_[0]->_compress( $_[1], Compress::Raw::Zlib::Z_SYNC_FLUSH() ) );
166             }
167              
168             #Flushes the sliding window.
169             #Use for final fragments when deflate_no_context_takeover is ON
170             sub _compress_full_flush_chomp {
171 3   33 3   11 $_[0]->{'d'} ||= $_[0]->_create_deflate_obj();
172              
173 3         16 return _chomp_0000ffff_or_die( $_[0]->_compress( $_[1], Compress::Raw::Zlib::Z_FULL_FLUSH() ) );
174             }
175              
176             sub _chomp_0000ffff_or_die {
177 5 50   5   29 if ( rindex( $_[0], _ZLIB_SYNC_TAIL ) == length($_[0]) - 4 ) {
178 5         14 substr($_[0], -4) = q<>;
179             }
180             else {
181 0         0 die sprintf('deflate/flush didn’t end with expected SYNC tail (00.00.ff.ff): %v.02x', $_[0]);
182             }
183              
184 5         15 return $_[0];
185             }
186              
187             sub _compress {
188 55     55   153 my ($self) = @_; # $_[1] = payload; $_[2] = flush method
189              
190 55         93 $_payload_sr = \$_[1];
191              
192 55         73 _DEBUG && _debug(sprintf "to deflate: [%v.02x]", $$_payload_sr);
193              
194 55         65 my $out;
195              
196 55         46923 my $dstatus = $self->{'d'}->deflate( $$_payload_sr, $out );
197 55 50       211 die "deflate: $dstatus" if $dstatus != Compress::Raw::Zlib::Z_OK();
198              
199 55         220 _DEBUG && _debug(sprintf "post-deflate output: [%v.02x]", $out);
200              
201 55 100       102 if ($_[2]) {
202 5         304 $dstatus = $self->{'d'}->flush($out, $_[2]);
203 5 50       27 die "deflate flush: $dstatus" if $dstatus != Compress::Raw::Zlib::Z_OK();
204              
205 5         25 undef $self->{'_streamer_mode'};
206              
207 5         6 _DEBUG && _debug(sprintf "post-flush output: [%v.02x]", $out);
208             }
209              
210             #NB: The RFC directs at this point that:
211             #
212             #If the resulting data does not end with an empty DEFLATE block
213             #with no compression (the "BTYPE" bits are set to 00), append an
214             #empty DEFLATE block with no compression to the tail end.
215             #
216             #… but I don’t know the protocol well enough to detect that??
217             #
218             #NB:
219             #> perl -MCompress::Raw::Zlib -e' my $deflate = Compress::Raw::Zlib::Deflate->new( -WindowBits => -8, -AppendOutput => 1, -Level => Compress::Raw::Zlib::Z_NO_COMPRESSION ); $deflate->deflate( "", my $out ); $deflate->flush( $out, Compress::Raw::Zlib::Z_SYNC_FLUSH()); print $out' | xxd
220             #00000000: 0000 00ff ff .....
221              
222             # if ( $_[2] == Compress::Raw::Zlib::Z_FULL_FLUSH() ) {
223             # if ( substr($out, -4) eq _ZLIB_SYNC_TAIL ) {
224             # substr($out, -4) = q<>;
225             # }
226             # else {
227             # die sprintf('deflate/flush didn’t end with expected SYNC tail (00.00.ff.ff): %v.02x', $out);
228             # }
229             # }
230              
231 55         149 return $out;
232             }
233              
234             #----------------------------------------------------------------------
235              
236             sub _create_inflate_obj {
237 3     3   7 my ($self) = @_;
238              
239 3   33     16 my $window_bits = $self->{'inflate_max_window_bits'} || ( Net::WebSocket::PMCE::deflate::Constants::VALID_MAX_WINDOW_BITS() )[-1];
240              
241 3         18 require Compress::Raw::Zlib;
242              
243 3         21 my ($inflate, $istatus) = Compress::Raw::Zlib::Inflate->new(
244             -WindowBits => -$window_bits,
245             -AppendOutput => 1,
246             );
247 3 50       1010 die "Inflate: $istatus" if $istatus != Compress::Raw::Zlib::Z_OK();
248              
249 3         33 return $inflate;
250             }
251              
252             sub _create_deflate_obj {
253 3     3   9 my ($self) = @_;
254              
255 3   33     20 my $window_bits = $self->{'deflate_max_window_bits'} || ( Net::WebSocket::PMCE::deflate::Constants::VALID_MAX_WINDOW_BITS() )[-1];
256              
257 3         1537 require Compress::Raw::Zlib;
258              
259 3         12243 my ($deflate, $dstatus) = Compress::Raw::Zlib::Deflate->new(
260             -WindowBits => -$window_bits,
261             -AppendOutput => 1,
262             );
263 3 50       1638 die "Deflate: $dstatus" if $dstatus != Compress::Raw::Zlib::Z_OK();
264              
265 3         80 return $deflate;
266             }
267              
268             sub _debug {
269 0     0     print STDERR "$_[0]$/";
270             }
271              
272             1;