Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gzip filter: make sure the stream ends properly #3025

Merged
merged 32 commits into from
Apr 20, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d3c3ded
Fix improper gzip end stream
dio Apr 7, 2018
86e4b86
Review comments: remove finish() and prefer loop
dio Apr 9, 2018
9a0d529
Move reset
dio Apr 9, 2018
d0c4239
Fix tests
dio Apr 9, 2018
74063b9
Add death tests
dio Apr 9, 2018
d8239b3
Remove new lines
dio Apr 9, 2018
56b5e88
Fix typo
dio Apr 9, 2018
6552468
Remove flush()
dio Apr 11, 2018
767fb60
Remove require_finish flag
dio Apr 11, 2018
2b568b9
Add flipOrder helper
dio Apr 11, 2018
dff37df
Fix format
dio Apr 11, 2018
9b010f0
Merge remote-tracking branch 'upstream/master'
dio Apr 11, 2018
d44d929
Use finish()
dio Apr 11, 2018
2d85fe8
Merge remote-tracking branch 'upstream/master'
dio Apr 14, 2018
5400b19
Call finish on end_stream
dio Apr 14, 2018
5a486f2
Simplify flipOrder
dio Apr 14, 2018
8a9ccce
Flush and compress each frame
dio Apr 15, 2018
059441e
Use one buffer only
dio Apr 15, 2018
2fd4876
Remove reset
dio Apr 15, 2018
3713794
Use enum instead of boolean
dio Apr 17, 2018
467df23
Implement the new compressor interface
dio Apr 17, 2018
467af66
Update tests
dio Apr 17, 2018
d8f923e
Merge remote-tracking branch 'upstream/master'
dio Apr 17, 2018
ebd3e6d
Add test of compress-decompress with different memory levels
dio Apr 17, 2018
a291b5c
Add test with multiple slices of buffer
dio Apr 17, 2018
4a1dd67
Dynamically allocate the fragments
dio Apr 18, 2018
3f3a175
Lambda capture 'this' is not used
dio Apr 18, 2018
9ef74ad
Remove debugging assertion
dio Apr 19, 2018
0523291
Merge remote-tracking branch 'upstream/master'
dio Apr 19, 2018
e10d6aa
Add comments
dio Apr 19, 2018
b36f2fc
Remove superfluous string constructions
dio Apr 19, 2018
ac6ec76
Comment revision
dio Apr 20, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions source/common/compressor/zlib_compressor_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,28 @@ void ZlibCompressorImpl::updateOutput(Buffer::Instance& output_buffer) {
zstream_ptr_->next_out = chunk_char_ptr_.get();
}

void ZlibCompressorImpl::reset() {
const bool result = deflateReset(zstream_ptr_.get());
RELEASE_ASSERT(result == Z_OK);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need some death tests to exercise this logic.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Will try to have it.

}

void ZlibCompressorImpl::finish(Buffer::Instance& output_buffer) {
// Once the Z_FINISH parameter is provided, deflate() will begin to complete the compressed output
// stream. However depending on how much output space is provided, deflate() may have to be called
// several times until it has provided the complete compressed stream, even after it has consumed
// all of the input. The flush parameter must continue to be Z_FINISH for those subsequent calls.
// Ref: https://zlib.net/zlib_how.html.
const int result = deflate(zstream_ptr_.get(), Z_FINISH);
updateOutput(output_buffer);
if (result == Z_OK || result == Z_BUF_ERROR) {
finish(output_buffer);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: prefer to do this in a loop vs recursively (even though I know the recursion depth will be small).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could do something similar to deflateNext():

while (deflateNext(Z_FINISH)) {
    if (zstream_ptr_->avail_out == 0) {
      updateOutput(output_buffer);
    }
  }

... and then handle the logic inside deflateNext(..):

const int result = deflate(zstream_ptr_.get(), flush_state);
  
  switch (flush_state) {  
  case Z_FINISH:
    if (result != Z_OK && result != Z_BUF_ERROR) { 
      RELEASE_ASSERT(result == Z_STREAM_END);
      return false;
    }

  default:
    if (result == Z_BUF_ERROR && zstream_ptr_->avail_in == 0) {
      return false; // This means that zlib needs more input, so stop here.
    }
    RELEASE_ASSERT(result == Z_OK);
  }

  return true;

Just a suggestion, but it is up to you :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. This is nicer. 👍

} else {
// While Z_OK, Z_BUF_ERROR (covered in above case) and Z_STREAM_END are the acceptable values,
// the Z_STREAM_ERROR is only possible if the stream is not initialized properly. Ref:
// https://zlib.net/zlib_how.html.
RELEASE_ASSERT(result == Z_STREAM_END);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important to have testing coverage here too.

}
}

} // namespace Compressor
} // namespace Envoy
12 changes: 12 additions & 0 deletions source/common/compressor/zlib_compressor_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@ class ZlibCompressorImpl : public Compressor {
*/
uint64_t checksum();

/**
* This is required to re-init the stream after calling finish(_).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think deflateReset() does not deallocate memory. It just reset the counters and returns the stream results. Can you add a line to the comment explaining this?
From zlib manual:

This function is equivalent to deflateEnd followed by deflateInit, but does 
not free and reallocate the internal compression state. The stream will leave 
the compression level and any other attributes that may have been 
set unchanged.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it.

*/
void reset();

/**
* Completes the compressed output stream. To indicate that the currently pointed data is the last
* chunk of input data to compress.
* @param output_buffer supplies the buffer to output compressed data.
*/
void finish(Buffer::Instance& output_buffer);

// Compressor
void compress(const Buffer::Instance& input_buffer, Buffer::Instance& output_buffer) override;

Expand Down
7 changes: 7 additions & 0 deletions source/common/decompressor/zlib_decompressor_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ void ZlibDecompressorImpl::decompress(const Buffer::Instance& input_buffer,

bool ZlibDecompressorImpl::inflateNext() {
const int result = inflate(zstream_ptr_.get(), Z_NO_FLUSH);
if (result == Z_STREAM_END) {
// Z_FINISH informs inflate to not maintain a sliding window if the stream completes, which
// reduces inflate's memory footprint. Ref: https://www.zlib.net/manual.html.
inflate(zstream_ptr_.get(), Z_FINISH);
return false;
}

if (result == Z_BUF_ERROR && zstream_ptr_->avail_in == 0) {
return false; // This means that zlib needs more input, so stop here.
}
Expand Down
4 changes: 4 additions & 0 deletions source/common/http/filter/gzip_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,15 @@ FilterDataStatus GzipFilter::encodeData(Buffer::Instance& data, bool end_stream)

if (end_stream) {
compressor_.flush(compressed_data_);
compressor_.finish(compressed_data_);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't look at this code much at all, but it seems like flush/finish finish can be logically combined? Shouldn't finish implicitly flush before finishing?

}

if (compressed_data_.length()) {
data.drain(n_data);
data.move(compressed_data_);

// Reset is required to re-init the compressor's z_stream.
compressor_.reset();
return Http::FilterDataStatus::Continue;
}

Expand Down
18 changes: 18 additions & 0 deletions test/common/compressor/zlib_compressor_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ TEST_F(ZlibCompressorImplTest, CompressWithReducedInternalMemory) {
compressed_slices[num_comp_slices - 1].len_);
// FOOTER four-byte sequence (sync flush)
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));

compressor.finish(output_buffer);
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));
}

/**
Expand Down Expand Up @@ -142,6 +145,12 @@ TEST_F(ZlibCompressorImplTest, CompressWithContinuesFlush) {
compressed_slices[num_comp_slices - 1].len_);
// FOOTER four-byte sequence (sync flush)
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));

compressor.finish(output_buffer);
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));

// Re-init the compressor.
compressor.reset();
}
}

Expand Down Expand Up @@ -181,6 +190,9 @@ TEST_F(ZlibCompressorImplTest, CompressSmallInputMemory) {
compressed_slices[num_comp_slices - 1].len_);
// FOOTER four-byte sequence (sync flush)
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));

compressor.finish(output_buffer);
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));
}

/**
Expand Down Expand Up @@ -241,6 +253,9 @@ TEST_F(ZlibCompressorImplTest, CompressMoveFlushAndRepeat) {
compressed_slices[num_comp_slices - 1].len_);
// FOOTER four-byte sequence (sync flush)
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));

compressor.finish(output_buffer);
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));
}

/**
Expand Down Expand Up @@ -279,6 +294,9 @@ TEST_F(ZlibCompressorImplTest, CompressWithNotCommonParams) {
compressed_slices[num_comp_slices - 1].len_);
// FOOTER four-byte sequence (sync flush)
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));

compressor.finish(output_buffer);
EXPECT_EQ("0000ffff", footer_hex_str.substr(footer_hex_str.size() - 8, 10));
}

} // namespace
Expand Down