| File: | blib/lib/App/FileSync/Protocol.pm |
| Coverage: | 89.3% |
| line | stmt | bran | cond | sub | time | code |
|---|---|---|---|---|---|---|
| 1 | package App::FileSync::Protocol; | |||||
| 2 | ||||||
| 3 | # One connection, strictly one request in flight at a time. Control messages | |||||
| 4 | # are JSON on one line; binary payloads are announced by "len" in a header and | |||||
| 5 | # follow it immediately. | |||||
| 6 | ||||||
| 7 | 313 313 313 | 747 276 4224 | use App::FileSync::Platform; | |||
| 8 | ||||||
| 9 | 313 313 313 | 530 237 2477 | use strict; | |||
| 10 | 313 313 313 | 449 240 7012 | use warnings; | |||
| 11 | # Not autodie qw(:all): this class has methods named send, recv and close, and | |||||
| 12 | # autodie installs wrappers under those names in the package, then deletes | |||||
| 13 | # them (taking the methods with them) when the file scope ends. Only the | |||||
| 14 | # builtins this file calls are covered. | |||||
| 15 | 313 313 313 | 568 259 750 | use autodie qw(binmode sysread); | |||
| 16 | ||||||
| 17 | 313 313 313 | 143689 635155 4524 | use Compress::Raw::Zlib (); | |||
| 18 | 313 313 313 | 797 272 2389 | use IO::Handle (); | |||
| 19 | 313 313 313 | 486 261 7613 | use Readonly; | |||
| 20 | 313 313 313 | 589 285 1061 | use Sub::Private { mode => 'enforce' }; | |||
| 21 | ||||||
| 22 | 313 313 313 | 34796 276 1011 | use parent -norequire, 'App::FileSync::Base'; | |||
| 23 | 313 313 313 | 6270 246 3092 | use App::FileSync::Base; | |||
| 24 | ||||||
| 25 | 313 313 313 | 527 288 6845 | use App::FileSync qw(json_encode json_decode); | |||
| 26 | 313 313 313 | 598 319 5234 | use App::FileSync::Args qw(named); | |||
| 27 | 313 313 313 | 521 286 5021 | use App::FileSync::I18N qw(os_error strip_location); | |||
| 28 | ||||||
| 29 | 313 313 313 | 590 327 712 | use version; our $VERSION = version->declare('0.001.1'); | |||
| 30 | ||||||
| 31 | # Payloads are raw-deflated when that makes them smaller. Small payloads and | |||||
| 32 | # already-compressed data are left alone, so the flag costs more than it saves. | |||||
| 33 | our $COMPRESS_MIN = 256; | |||||
| 34 | our $MAX_WIRE = 64 * 1024 * 1024; | |||||
| 35 | our $MAX_INFLATED = 64 * 1024 * 1024; | |||||
| 36 | ||||||
| 37 | # zlib level: the default trade-off between speed and size. | |||||
| 38 | Readonly my $DEFLATE_LEVEL => 6; | |||||
| 39 | ||||||
| 40 | # Bytes asked of the pipe per sysread. | |||||
| 41 | Readonly my $READ_SIZE => 65_536; | |||||
| 42 | ||||||
| 43 | =encoding utf8 | |||||
| 44 | ||||||
| 45 - 110 | =head1 NAME
App::FileSync::Protocol - the line-delimited JSON wire protocol
=head1 SYNOPSIS
my $connection = App::FileSync::Protocol->new(in => $reader, out => $writer);
$connection->send({ cmd => 'hello', proto => 2 });
my $reply = $connection->recv();
$connection->send_data({ cmd => 'chunk', p => $path }, $bytes);
=head1 DESCRIPTION
Each control message is one JSON object on one line. A message announcing a
payload carries C<len> (the bytes on the wire) and, when the payload was
deflated, C<z>; the payload follows the line directly. The connection counts
payload bytes in each direction for the transfer report.
When reading or writing fails because the pipe itself failed (the peer exited,
ssh was refused), L</transport_failed> becomes true, so a caller can show the
transport's own diagnostics instead of a protocol error.
=head1 METHODS
=head2 new
Purpose: wrap a pair of handles as a protocol connection.
Args: C<in> and C<out> (handles), optional C<language>.
Returns: the connection.
Side Effects: puts both handles in binary mode and makes C<out> autoflush.
Usage:
my $connection = App::FileSync::Protocol->new(in => \*STDIN, out => \*STDOUT);
=head3 EXAMPLE
my $connection = App::FileSync::Protocol->new(in => $transport->reader, out => $transport->writer);
=head3 API SPECIFICATION
=head4 INPUT
{
in => { type => 'handle' },
out => { type => 'handle' },
language => { type => 'string', optional => 1 },
}
=head4 OUTPUT
{ type => 'object', isa => 'App::FileSync::Protocol' }
=head3 MESSAGES
Key Meaning Resolution
args.invalid a handle is missing pass in and out
=head3 FORMAL SPECIFICATION
new(in, out) = â¨in, out, buffer ⦠â¨â©, closed ⦠false, sent ⦠0, received ⦠0â©
=cut | |||||
| 111 | ||||||
| 112 | sub new { | |||||
| 113 | 347 | 1101 | my $class = shift; | |||
| 114 | 347 | 3565 | my $args = named(\@_, { | |||
| 115 | in => { type => 'handle' }, | |||||
| 116 | out => { type => 'handle' }, | |||||
| 117 | language => { type => 'string', optional => 1 }, | |||||
| 118 | }, 'Protocol->new'); | |||||
| 119 | ||||||
| 120 | 347 | 4806 | binmode $args->{in}; | |||
| 121 | 347 | 99234 | binmode $args->{out}; | |||
| 122 | 347 | 9881 | $args->{out}->autoflush(1); | |||
| 123 | ||||||
| 124 | return bless { | |||||
| 125 | in => $args->{in}, | |||||
| 126 | out => $args->{out}, | |||||
| 127 | language => $args->{language}, | |||||
| 128 | 347 | 16390 | buffer => '', | |||
| 129 | closed => 0, | |||||
| 130 | transport_failed => 0, | |||||
| 131 | # Payload bytes as they crossed the wire, headers and control | |||||
| 132 | # messages excluded: this is the number a transfer report means. | |||||
| 133 | sent_bytes => 0, | |||||
| 134 | received_bytes => 0, | |||||
| 135 | }, $class; | |||||
| 136 | } | |||||
| 137 | ||||||
| 138 - 180 | =head2 send
Purpose: send one control message.
Args: C<\%message>.
Returns: 1.
Side Effects: writes one line.
Usage:
$connection->send({ cmd => 'plan' });
=head3 EXAMPLE
$connection->send({ ok => 1, off => $offset });
=head3 API SPECIFICATION
=head4 INPUT
[
{ name => 'self', type => 'object' },
{ name => 'message', type => 'hashref' },
]
=head4 OUTPUT
{ type => 'integer', min => 1, max => 1 }
=head3 MESSAGES
Key Meaning Resolution
protocol.closed the connection was closed a caller bug
protocol.write_failed the pipe would not take it see the transport's stderr
=head3 FORMAL SPECIFICATION
pre ¬closed
out' = out â json(message) â "\n"
=cut | |||||
| 181 | ||||||
| 182 | sub send { ## no critic (Subroutines::ProhibitBuiltinHomonyms) | |||||
| 183 | 2988 | 9134 | my ($self, $message) = @_; | |||
| 184 | ||||||
| 185 | 2988 | 4635 | $self->_write(json_encode($message) . "\n"); | |||
| 186 | 2985 | 3403 | return 1; | |||
| 187 | } | |||||
| 188 | ||||||
| 189 - 231 | =head2 recv
Purpose: read one control message.
Args: none.
Returns: the decoded message hashref.
Side Effects: reads from C<in>; blocks until a whole line arrives.
Usage:
my $reply = $connection->recv();
=head3 EXAMPLE
my $hello = $connection->recv();
die unless $hello->{ok};
=head3 API SPECIFICATION
=head4 INPUT
[ { name => 'self', type => 'object' } ]
=head4 OUTPUT
{ type => 'hashref' }
=head3 MESSAGES
Key Meaning Resolution
protocol.malformed the line is not JSON version mismatch or corrupt stream
protocol.not_object the JSON is not an object version mismatch
protocol.eof the peer closed the connection see the peer's log
protocol.read_failed the pipe failed see the transport's stderr
=head3 FORMAL SPECIFICATION
in = line â "\n" â rest â recv = jsonâ»Â¹(line) â§ in' = rest
pre jsonâ»Â¹(line) â OBJECT
=cut | |||||
| 232 | ||||||
| 233 | sub recv { ## no critic (Subroutines::ProhibitBuiltinHomonyms) | |||||
| 234 | 2988 | 3001 | my ($self) = @_; | |||
| 235 | ||||||
| 236 | 2988 | 3965 | my $line = $self->read_line(); | |||
| 237 | 2983 2983 | 2627 5059 | my $message = eval { json_decode($line) }; | |||
| 238 | 2983 | 3459 | $self->_throw('protocol.malformed', { error => strip_location($@) }) if $@; | |||
| 239 | 2983 | 3339 | $self->_throw('protocol.not_object') unless ref($message) eq 'HASH'; | |||
| 240 | ||||||
| 241 | 2983 | 4054 | return $message; | |||
| 242 | } | |||||
| 243 | ||||||
| 244 - 287 | =head2 send_bytes
Purpose: write raw bytes (the payload after a header).
Args: C<$bytes>; empty or undef writes nothing.
Returns: 1.
Side Effects: writes to C<out>. Not counted in L</bytes_sent>; use
L</send_data> for counted payloads.
Usage:
$connection->send_bytes($payload);
=head3 EXAMPLE
$connection->send({ len => length $bytes });
$connection->send_bytes($bytes);
=head3 API SPECIFICATION
=head4 INPUT
[
{ name => 'self', type => 'object' },
{ name => 'bytes', type => 'string', optional => 1 },
]
=head4 OUTPUT
{ type => 'integer', min => 1, max => 1 }
=head3 MESSAGES
Key Meaning Resolution
protocol.closed the connection was closed a caller bug
protocol.write_failed the pipe would not take it see the transport's stderr
=head3 FORMAL SPECIFICATION
out' = out â bytes
=cut | |||||
| 288 | ||||||
| 289 | sub send_bytes { | |||||
| 290 | 280 | 329 | my ($self, $bytes) = @_; | |||
| 291 | ||||||
| 292 | 280 | 983 | $self->_write($bytes) if defined $bytes && length $bytes; | |||
| 293 | 280 | 216 | return 1; | |||
| 294 | } | |||||
| 295 | ||||||
| 296 - 339 | =head2 send_data
Purpose: send a header and its payload, compressing when worthwhile.
Args: C<\%header>, C<$bytes>.
Returns: 1.
Side Effects: writes the header (with C<len> and, if compressed, C<z>) and the
payload; adds the wire length to L</bytes_sent>.
Usage:
$connection->send_data({ cmd => 'chunk', p => $path }, $bytes);
=head3 EXAMPLE
$connection->send_data({ ok => 1, eof => 1 }, $last_chunk);
=head3 API SPECIFICATION
=head4 INPUT
[
{ name => 'self', type => 'object' },
{ name => 'message', type => 'hashref', optional => 1 },
{ name => 'bytes', type => 'string', optional => 1 },
]
=head4 OUTPUT
{ type => 'integer', min => 1, max => 1 }
=head3 MESSAGES
As for L</send>.
=head3 FORMAL SPECIFICATION
let (w, z) = deflate(bytes)
out' = out â json(header â {len ⦠#w} â (z â {z ⦠1})) â "\n" â w
sent' = sent + #w
=cut | |||||
| 340 | ||||||
| 341 | sub send_data { | |||||
| 342 | 280 | 2377 | my ($self, $message, $bytes) = @_; | |||
| 343 | ||||||
| 344 | 280 280 | 249 765 | my ($wire, $compressed) = @{ _deflate(defined $bytes ? $bytes : '') }; | |||
| 345 | ||||||
| 346 | # The header carries the wire length and a "z" flag, so a peer that does | |||||
| 347 | # not know the flag rejects the frame rather than misreading it. | |||||
| 348 | 280 280 | 290 651 | my %header = %{ $message || {} }; | |||
| 349 | 280 | 442 | $header{len} = length $wire; | |||
| 350 | 280 | 359 | $header{z} = 1 if $compressed; | |||
| 351 | ||||||
| 352 | 280 | 465 | $self->send(\%header); | |||
| 353 | 280 | 523 | $self->send_bytes($wire); | |||
| 354 | 280 | 330 | $self->{sent_bytes} += length $wire; | |||
| 355 | ||||||
| 356 | 280 | 498 | return 1; | |||
| 357 | } | |||||
| 358 | ||||||
| 359 - 406 | =head2 read_data
Purpose: read the payload a header announced.
Args: C<\%header> as returned by L</recv>.
Returns: the payload, decompressed if it was sent compressed.
Side Effects: reads C<len> bytes; adds them to L</bytes_received>.
Usage:
my $bytes = $connection->read_data($header);
=head3 EXAMPLE
my $reply = $connection->recv();
my $bytes = $connection->read_data($reply);
=head3 API SPECIFICATION
=head4 INPUT
[
{ name => 'self', type => 'object' },
{ name => 'message', type => 'hashref' },
]
=head4 OUTPUT
{ type => 'string' }
=head3 MESSAGES
Key Meaning Resolution
protocol.bad_length len is not a number or exceeds 64 MiB a peer bug
protocol.inflate the payload cannot be decompressed a corrupt stream
protocol.corrupt the compressed payload is corrupt a corrupt stream
protocol.too_large it would inflate beyond 64 MiB a hostile or buggy peer
=head3 FORMAL SPECIFICATION
pre header.len â 0 .. MAX_WIRE
read_data(h) = inflate(take(h.len, in)) if h.z
= take(h.len, in) otherwise
received' = received + h.len
=cut | |||||
| 407 | ||||||
| 408 | sub read_data { | |||||
| 409 | 280 | 1010 | my ($self, $message) = @_; | |||
| 410 | ||||||
| 411 | 280 | 457 | my $length = defined $message->{len} ? $message->{len} : 0; | |||
| 412 | 280 | 989 | $self->_throw('protocol.bad_length') | |||
| 413 | unless $length =~ /\A\d+\z/ && $length <= $MAX_WIRE; | |||||
| 414 | ||||||
| 415 | 280 | 540 | my $wire = $length ? $self->read_bytes($length) : ''; | |||
| 416 | 280 | 351 | $self->{received_bytes} += length $wire; | |||
| 417 | ||||||
| 418 | 280 | 704 | return $message->{z} ? $self->_inflate($wire) : $wire; | |||
| 419 | } | |||||
| 420 | ||||||
| 421 - 469 | =head2 read_line
Documented with L</read_bytes> below.
=head2 read_bytes
Purpose: read up to the next newline, or exactly C<$length> bytes.
Args: C<read_bytes($length)>.
Returns: the line (without its newline) or the bytes.
Side Effects: consumes them from the buffer, reading from C<in> as needed.
Usage:
my $line = $connection->read_line();
my $bytes = $connection->read_bytes(1024);
=head3 EXAMPLE
my $payload = $connection->read_bytes($header->{len});
=head3 API SPECIFICATION
=head4 INPUT
[
{ name => 'self', type => 'object' },
{ name => 'length', type => 'integer', min => 0 },
]
=head4 OUTPUT
{ type => 'string' }
=head3 MESSAGES
Key Meaning Resolution
protocol.negative a negative length was asked for a caller bug
protocol.eof the peer closed the connection see the peer's log
protocol.read_failed the pipe failed see the transport's stderr
=head3 FORMAL SPECIFICATION
read_line: in = l â "\n" â r â§ "\n" â ran l â result = l â§ in' = r
read_bytes(n): in = b â r â§ #b = n â result = b â§ in' = r
=cut | |||||
| 470 | ||||||
| 471 | sub read_line { | |||||
| 472 | 2988 | 2412 | my ($self) = @_; | |||
| 473 | ||||||
| 474 | 2988 | 2069 | my $position; | |||
| 475 | 2988 | 6713 | $self->_fill() while ($position = index($self->{buffer}, "\n")) < 0; | |||
| 476 | ||||||
| 477 | 2983 | 4548 | my $line = substr($self->{buffer}, 0, $position); | |||
| 478 | 2983 | 3849 | substr($self->{buffer}, 0, $position + 1, ''); | |||
| 479 | ||||||
| 480 | 2983 | 3705 | return $line; | |||
| 481 | } | |||||
| 482 | ||||||
| 483 | sub read_bytes { | |||||
| 484 | 279 | 375 | my ($self, $length) = @_; | |||
| 485 | ||||||
| 486 | 279 | 315 | $self->_throw('protocol.negative') if $length < 0; | |||
| 487 | 279 | 600 | $self->_fill() while length($self->{buffer}) < $length; | |||
| 488 | ||||||
| 489 | 279 | 524 | return substr($self->{buffer}, 0, $length, ''); | |||
| 490 | } | |||||
| 491 | ||||||
| 492 - 532 | =head2 bytes_sent
Documented with L</bytes_received> below.
=head2 bytes_received
Purpose: payload bytes that crossed the wire in each direction.
Args: none.
Returns: a byte count (headers and control messages excluded).
Side Effects: none.
Usage:
printf "sent %d bytes\n", $connection->bytes_sent;
=head3 EXAMPLE
my %summary = (bytes_sent => $connection->bytes_sent, bytes_received => $connection->bytes_received);
=head3 API SPECIFICATION
=head4 INPUT
[ { name => 'self', type => 'object' } ]
=head4 OUTPUT
{ type => 'integer', min => 0 }
=head3 MESSAGES
None.
=head3 FORMAL SPECIFICATION
bytes_sent = sent ; bytes_received = received
=cut | |||||
| 533 | ||||||
| 534 | sub bytes_sent { | |||||
| 535 | 150 | 236 | my ($self) = @_; | |||
| 536 | ||||||
| 537 | 150 | 636 | return $self->{sent_bytes} || 0; | |||
| 538 | } | |||||
| 539 | ||||||
| 540 | sub bytes_received { | |||||
| 541 | 150 | 230 | my ($self) = @_; | |||
| 542 | ||||||
| 543 | 150 | 445 | return $self->{received_bytes} || 0; | |||
| 544 | } | |||||
| 545 | ||||||
| 546 - 583 | =head2 transport_failed
Purpose: whether the last failure was the pipe rather than the protocol.
Args: none.
Returns: 1 after a read or write on the underlying handles failed, or the peer
closed the connection; otherwise 0.
Side Effects: none.
Usage:
my $detail = $connection->transport_failed ? $transport->error_text : undef;
=head3 EXAMPLE
eval { $connection->recv() } or print "pipe failed\n" if $connection->transport_failed;
=head3 API SPECIFICATION
=head4 INPUT
[ { name => 'self', type => 'object' } ]
=head4 OUTPUT
{ type => 'integer', min => 0, max => 1 }
=head3 MESSAGES
None.
=head3 FORMAL SPECIFICATION
transport_failed â â an I/O failure or end of file on in or out
=cut | |||||
| 584 | ||||||
| 585 | sub transport_failed { | |||||
| 586 | 9 | 31 | my ($self) = @_; | |||
| 587 | ||||||
| 588 | 9 | 71 | return $self->{transport_failed} ? 1 : 0; | |||
| 589 | } | |||||
| 590 | ||||||
| 591 - 628 | =head2 close
Purpose: close both handles.
Args: none.
Returns: 1, or nothing if already closed.
Side Effects: closes C<in> and C<out>; later sends raise C<protocol.closed>.
Usage:
$connection->close;
=head3 EXAMPLE
$connection->send({ cmd => 'done' });
$connection->close;
=head3 API SPECIFICATION
=head4 INPUT
[ { name => 'self', type => 'object' } ]
=head4 OUTPUT
{ type => 'integer', optional => 1 }
=head3 MESSAGES
None; close errors are ignored, as the session is over.
=head3 FORMAL SPECIFICATION
closed' = true
=cut | |||||
| 629 | ||||||
| 630 | sub close { ## no critic (Subroutines::ProhibitBuiltinHomonyms, Subroutines::ProhibitAmbiguousNames) | |||||
| 631 | 0 | my ($self) = @_; | ||||
| 632 | ||||||
| 633 | 0 | return if $self->{closed}; | ||||
| 634 | 0 | $self->{closed} = 1; | ||||
| 635 | ||||||
| 636 | # The session is over; a pipe that reports an error on close has nothing | |||||
| 637 | # left to say that a caller could act on, so the result is not checked. | |||||
| 638 | 0 | CORE::close($self->{in}); | ||||
| 639 | 0 | CORE::close($self->{out}); | ||||
| 640 | ||||||
| 641 | 0 | return 1; | ||||
| 642 | } | |||||
| 643 | ||||||
| 644 | # _write | |||||
| 645 | # Purpose: put bytes on the wire. | |||||
| 646 | # Entry: the connection is open. | |||||
| 647 | # Exit: returns 1. | |||||
| 648 | # Side effects: writes to out; on failure marks the transport as failed and | |||||
| 649 | # raises protocol.write_failed. print is outside autodie. | |||||
| 650 | sub _write :Private { | |||||
| 651 | my ($self, $bytes) = @_; | |||||
| 652 | ||||||
| 653 | $self->_throw('protocol.closed') if $self->{closed}; | |||||
| 654 | unless (print { $self->{out} } $bytes) { | |||||
| 655 | my $error = "$!"; | |||||
| 656 | $self->{transport_failed} = 1; | |||||
| 657 | $self->_throw('protocol.write_failed', { error => $error }); | |||||
| 658 | } | |||||
| 659 | ||||||
| 660 | return 1; | |||||
| 661 | 313 313 313 | 173333 294 4198 | } | |||
| 662 | ||||||
| 663 | # _fill | |||||
| 664 | # Purpose: read more bytes from the peer into the buffer. | |||||
| 665 | # Entry: the caller needs more than the buffer holds. | |||||
| 666 | # Exit: returns 1 after appending at least one byte. | |||||
| 667 | # Side effects: on end of file or a read error marks the transport as failed | |||||
| 668 | # (and closed, for end of file) and raises. | |||||
| 669 | sub _fill :Private { | |||||
| 670 | my ($self) = @_; | |||||
| 671 | ||||||
| 672 | my $chunk; | |||||
| 673 | my $read = eval { sysread($self->{in}, $chunk, $READ_SIZE) }; | |||||
| 674 | unless (defined $read) { | |||||
| 675 | my $error = os_error($@); | |||||
| 676 | $self->{transport_failed} = 1; | |||||
| 677 | $self->_throw('protocol.read_failed', { error => $error }); | |||||
| 678 | } | |||||
| 679 | if ($read == 0) { | |||||
| 680 | @{$self}{qw(closed transport_failed)} = (1, 1); | |||||
| 681 | $self->_throw('protocol.eof'); | |||||
| 682 | } | |||||
| 683 | ||||||
| 684 | $self->{buffer} .= $chunk; | |||||
| 685 | return 1; | |||||
| 686 | 313 313 313 | 75260 299 2755 | } | |||
| 687 | ||||||
| 688 | # _deflate | |||||
| 689 | # Purpose: compress a payload when that makes it smaller. | |||||
| 690 | # Entry: $bytes may be empty. | |||||
| 691 | # Exit: returns [ $wire_bytes, $compressed_flag ]. | |||||
| 692 | # Side effects: none. Any zlib failure just sends the bytes as they are. | |||||
| 693 | sub _deflate :Private { | |||||
| 694 | my ($bytes) = @_; | |||||
| 695 | ||||||
| 696 | return [ $bytes, 0 ] if length($bytes) < $COMPRESS_MIN; | |||||
| 697 | ||||||
| 698 | my ($deflater, $status) = Compress::Raw::Zlib::Deflate->new( | |||||
| 699 | -WindowBits => -Compress::Raw::Zlib::MAX_WBITS(), | |||||
| 700 | -Level => $DEFLATE_LEVEL, | |||||
| 701 | ); | |||||
| 702 | my $ok = $deflater && $status == Compress::Raw::Zlib::Z_OK(); | |||||
| 703 | ||||||
| 704 | my ($out, $tail) = ('', ''); | |||||
| 705 | $ok &&= $deflater->deflate($bytes, $out) == Compress::Raw::Zlib::Z_OK(); | |||||
| 706 | $ok &&= $deflater->flush($tail) == Compress::Raw::Zlib::Z_OK(); | |||||
| 707 | $out .= $tail; | |||||
| 708 | ||||||
| 709 | return ($ok && length($out) < length($bytes)) ? [ $out, 1 ] : [ $bytes, 0 ]; | |||||
| 710 | 313 313 313 | 71223 269 2593 | } | |||
| 711 | ||||||
| 712 | # _inflate | |||||
| 713 | # Purpose: decompress a payload sent with "z". | |||||
| 714 | # Entry: $wire is raw deflate data. | |||||
| 715 | # Exit: returns the original bytes. | |||||
| 716 | # Side effects: raises protocol.inflate, protocol.corrupt or protocol.too_large. | |||||
| 717 | sub _inflate :Private { | |||||
| 718 | my ($self, $wire) = @_; | |||||
| 719 | ||||||
| 720 | my ($inflater, $status) = Compress::Raw::Zlib::Inflate->new( | |||||
| 721 | -WindowBits => -Compress::Raw::Zlib::MAX_WBITS(), | |||||
| 722 | ); | |||||
| 723 | $self->_throw('protocol.inflate') | |||||
| 724 | unless $inflater && $status == Compress::Raw::Zlib::Z_OK(); | |||||
| 725 | ||||||
| 726 | my $out = ''; | |||||
| 727 | $status = $inflater->inflate($wire, $out); | |||||
| 728 | $self->_throw('protocol.corrupt') | |||||
| 729 | unless $status == Compress::Raw::Zlib::Z_OK() || $status == Compress::Raw::Zlib::Z_STREAM_END(); | |||||
| 730 | $self->_throw('protocol.too_large') if length($out) > $MAX_INFLATED; | |||||
| 731 | ||||||
| 732 | return $out; | |||||
| 733 | 313 313 313 | 67113 310 2528 | } | |||
| 734 | ||||||
| 735 | 1; | |||||
| 736 | ||||||