throw std::invalid_argument("Codec: data must not be nullptr");
}
uint64_t len = data->computeChainDataLength();
- if (len == 0) {
- return IOBuf::create(0);
- }
if (len > maxUncompressedLength()) {
throw std::runtime_error("Codec: uncompressed length too large");
}
std::string Codec::compress(const StringPiece data) {
const uint64_t len = data.size();
- if (len == 0) {
- return "";
- }
if (len > maxUncompressedLength()) {
throw std::runtime_error("Codec: uncompressed length too large");
}
}
uint64_t Codec::maxCompressedLength(uint64_t uncompressedLength) const {
- if (uncompressedLength == 0) {
- return 0;
- }
return doMaxCompressedLength(uncompressedLength);
}
const folly::IOBuf* data,
Optional<uint64_t> uncompressedLength) const {
auto const compressedLength = data->computeChainDataLength();
- if (uncompressedLength == uint64_t(0) || compressedLength == 0) {
- if (uncompressedLength.value_or(0) != 0 || compressedLength != 0) {
+ if (compressedLength == 0) {
+ if (uncompressedLength.value_or(0) != 0) {
throw std::runtime_error("Invalid uncompressed length");
}
return 0;
ByteRange& input,
MutableByteRange& output,
StreamCodec::FlushOp flushOp) {
- if (state_ == State::RESET && input.empty()) {
- if (flushOp == StreamCodec::FlushOp::NONE) {
- return false;
- }
- if (flushOp == StreamCodec::FlushOp::END &&
- uncompressedLength().value_or(0) != 0) {
- throw std::runtime_error("Codec: invalid uncompressed length");
- }
- return true;
+ if (state_ == State::RESET && input.empty() &&
+ flushOp == StreamCodec::FlushOp::END &&
+ uncompressedLength().value_or(0) != 0) {
+ throw std::runtime_error("Codec: invalid uncompressed length");
}
+
if (!uncompressedLength() && needsDataLength()) {
throw std::runtime_error("Codec: uncompressed length required");
}
void resetCStream();
void resetDStream();
- size_t decodeVarint(ByteRange& input);
+ bool decodeAndCheckVarint(ByteRange& input);
bool flushVarintBuffer(MutableByteRange& output);
void resetVarintBuffer();
*
* If there are too many bytes and the varint is not valid, throw a
* runtime_error.
- * Returns the decoded size or 0 if more bytes are needed.
+ *
+ * If the uncompressed length was provided and a decoded varint does not match
+ * the provided length, throw a runtime_error.
+ *
+ * Returns true if the varint was successfully decoded and matches the
+ * uncompressed length if provided, and false if more bytes are needed.
*/
-size_t LZMA2StreamCodec::decodeVarint(ByteRange& input) {
+bool LZMA2StreamCodec::decodeAndCheckVarint(ByteRange& input) {
if (input.empty()) {
- return 0;
+ return false;
}
size_t const numBytesToCopy =
std::min(kMaxVarintLength64 - varintBufferPos_, input.size());
if (ret.hasValue()) {
size_t const varintSize = rangeSize - range.size();
input.advance(varintSize - varintBufferPos_);
- return ret.value();
+ if (uncompressedLength() && *uncompressedLength() != ret.value()) {
+ throw std::runtime_error("LZMA2StreamCodec: invalid uncompressed length");
+ }
+ return true;
} else if (ret.error() == DecodeVarintError::TooManyBytes) {
throw std::runtime_error("LZMA2StreamCodec: invalid uncompressed length");
} else {
// Too few bytes
input.advance(numBytesToCopy);
varintBufferPos_ += numBytesToCopy;
- return 0;
+ return false;
}
}
if (needDecodeSize_) {
// Try decoding the varint. If the input does not contain the entire varint,
// buffer the input. If the varint can not be decoded, fail.
- size_t const size = decodeVarint(input);
- if (!size) {
+ if (!decodeAndCheckVarint(input)) {
return false;
}
- if (uncompressedLength() && *uncompressedLength() != size) {
- throw std::runtime_error("LZMA2StreamCodec: invalid uncompressed length");
- }
needDecodeSize_ = false;
}
protected:
void SetUp() override {
auto tup = GetParam();
- uncompressedLength_ = uint64_t(1) << std::tr1::get<0>(tup);
+ int lengthLog = std::tr1::get<0>(tup);
+ // Small hack to test empty data
+ uncompressedLength_ =
+ (lengthLog < 0) ? 0 : uint64_t(1) << std::tr1::get<0>(tup);
chunks_ = std::tr1::get<1>(tup);
codec_ = getCodec(std::tr1::get<2>(tup));
}
void CompressionTest::runSimpleIOBufTest(const DataHolder& dh) {
const auto original = split(IOBuf::wrapBuffer(dh.data(uncompressedLength_)));
const auto compressed = split(codec_->compress(original.get()));
+ EXPECT_LE(
+ compressed->computeChainDataLength(),
+ codec_->maxCompressedLength(uncompressedLength_));
if (!codec_->needsUncompressedLength()) {
auto uncompressed = codec_->uncompress(compressed.get());
EXPECT_EQ(uncompressedLength_, uncompressed->computeChainDataLength());
reinterpret_cast<const char*>(dh.data(uncompressedLength_).data()),
uncompressedLength_);
const auto compressed = codec_->compress(original);
+ EXPECT_LE(
+ compressed.length(), codec_->maxCompressedLength(uncompressedLength_));
+
if (!codec_->needsUncompressedLength()) {
auto uncompressed = codec_->uncompress(compressed);
EXPECT_EQ(uncompressedLength_, uncompressed.length());
CompressionTest,
CompressionTest,
testing::Combine(
- testing::Values(0, 1, 12, 22, 25, 27),
+ testing::Values(-1, 0, 1, 12, 22, 25, 27),
testing::Values(1, 2, 3, 8, 65),
testing::ValuesIn(availableCodecs())));
}
TEST_P(StreamingUnitTest, maxCompressedLength) {
- EXPECT_EQ(0, codec_->maxCompressedLength(0));
for (uint64_t const length : {1, 10, 100, 1000, 10000, 100000, 1000000}) {
EXPECT_GE(codec_->maxCompressedLength(length), length);
}
auto const empty = IOBuf::create(0);
EXPECT_EQ(uint64_t(0), codec_->getUncompressedLength(empty.get()));
EXPECT_EQ(uint64_t(0), codec_->getUncompressedLength(empty.get(), 0));
+ EXPECT_ANY_THROW(codec_->getUncompressedLength(empty.get(), 1));
auto const data = IOBuf::wrapBuffer(randomDataHolder.data(100));
auto const compressed = codec_->compress(data.get());
- EXPECT_ANY_THROW(codec_->getUncompressedLength(data.get(), 0));
if (auto const length = codec_->getUncompressedLength(data.get())) {
EXPECT_EQ(100, *length);
}
TEST_P(StreamingUnitTest, emptyData) {
ByteRange input{};
- auto buffer = IOBuf::create(1);
+ auto buffer = IOBuf::create(codec_->maxCompressedLength(0));
buffer->append(buffer->capacity());
- MutableByteRange output{};
+ MutableByteRange output;
// Test compressing empty data in one pass
if (!codec_->needsDataLength()) {
+ output = {buffer->writableData(), buffer->length()};
EXPECT_TRUE(
codec_->compressStream(input, output, StreamCodec::FlushOp::END));
}
codec_->resetStream(0);
- EXPECT_TRUE(codec_->compressStream(input, output, StreamCodec::FlushOp::END));
output = {buffer->writableData(), buffer->length()};
EXPECT_TRUE(codec_->compressStream(input, output, StreamCodec::FlushOp::END));
- EXPECT_EQ(buffer->length(), output.size());
+
+ // Test uncompressing the compressed empty data is equivalent to the empty
+ // string
+ {
+ size_t compressedSize = buffer->length() - output.size();
+ auto const compressed =
+ IOBuf::copyBuffer(buffer->writableData(), compressedSize);
+ auto inputRange = compressed->coalesce();
+ codec_->resetStream(0);
+ output = {buffer->writableData(), buffer->length()};
+ EXPECT_TRUE(codec_->uncompressStream(
+ inputRange, output, StreamCodec::FlushOp::END));
+ EXPECT_EQ(output.size(), buffer->length());
+ }
// Test compressing empty data with multiple calls to compressStream()
- codec_->resetStream(0);
- output = {};
- EXPECT_FALSE(codec_->compressStream(input, output));
- EXPECT_TRUE(
- codec_->compressStream(input, output, StreamCodec::FlushOp::FLUSH));
- EXPECT_TRUE(codec_->compressStream(input, output, StreamCodec::FlushOp::END));
- codec_->resetStream(0);
- output = {buffer->writableData(), buffer->length()};
- EXPECT_FALSE(codec_->compressStream(input, output));
- EXPECT_TRUE(
- codec_->compressStream(input, output, StreamCodec::FlushOp::FLUSH));
- EXPECT_TRUE(codec_->compressStream(input, output, StreamCodec::FlushOp::END));
- EXPECT_EQ(buffer->length(), output.size());
+ {
+ auto largeBuffer = IOBuf::create(codec_->maxCompressedLength(0) * 2);
+ largeBuffer->append(largeBuffer->capacity());
+ codec_->resetStream(0);
+ output = {largeBuffer->writableData(), largeBuffer->length()};
+ EXPECT_FALSE(codec_->compressStream(input, output));
+ EXPECT_TRUE(
+ codec_->compressStream(input, output, StreamCodec::FlushOp::FLUSH));
+ EXPECT_TRUE(
+ codec_->compressStream(input, output, StreamCodec::FlushOp::END));
+ }
// Test uncompressing empty data
output = {};