// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <string>
#include <vector>
#include "gtest/gtest.h"
#include "puffin/src/bit_reader.h"
#include "puffin/src/bit_writer.h"
#include "puffin/src/include/puffin/common.h"
#include "puffin/src/include/puffin/huffer.h"
#include "puffin/src/include/puffin/puffer.h"
#include "puffin/src/include/puffin/utils.h"
#include "puffin/src/memory_stream.h"
#include "puffin/src/puff_reader.h"
#include "puffin/src/puff_writer.h"
#include "puffin/src/puffin_stream.h"
#include "puffin/src/sample_generator.h"
#include "puffin/src/set_errors.h"
#include "puffin/src/unittest_common.h"
namespace puffin {
using std::vector;
using std::string;
class PuffinTest : public ::testing::Test {
public:
// Utility for decompressing a puff stream.
bool DecompressPuff(const uint8_t* puff_buf,
size_t* puff_size,
uint8_t* out_buf,
size_t* out_size) {
BufferPuffReader puff_reader(static_cast<const uint8_t*>(puff_buf),
*puff_size);
auto start = static_cast<uint8_t*>(out_buf);
PuffData pd;
Error error;
while (puff_reader.BytesLeft() != 0) {
TEST_AND_RETURN_FALSE(puff_reader.GetNext(&pd, &error));
switch (pd.type) {
case PuffData::Type::kLiteral:
*start = pd.byte;
start++;
case PuffData::Type::kLiterals:
pd.read_fn(start, pd.length);
start += pd.length;
break;
case PuffData::Type::kLenDist: {
while (pd.length-- > 0) {
*start = *(start - pd.distance);
start++;
}
break;
}
case PuffData::Type::kBlockMetadata:
break;
case PuffData::Type::kEndOfBlock:
break;
default:
LOG(ERROR) << "Invalid block data type";
break;
}
}
*out_size = start - static_cast<uint8_t*>(out_buf);
*puff_size = *puff_size - puff_reader.BytesLeft();
return true;
}
bool PuffDeflate(const uint8_t* comp_buf,
size_t comp_size,
uint8_t* puff_buf,
size_t puff_size,
Error* error) const {
BufferBitReader bit_reader(comp_buf, comp_size);
BufferPuffWriter puff_writer(puff_buf, puff_size);
TEST_AND_RETURN_FALSE(
puffer_.PuffDeflate(&bit_reader, &puff_writer, nullptr, error));
TEST_AND_RETURN_FALSE_SET_ERROR(comp_size == bit_reader.Offset(),
Error::kInvalidInput);
TEST_AND_RETURN_FALSE_SET_ERROR(puff_size == puff_writer.Size(),
Error::kInvalidInput);
return true;
}
bool HuffDeflate(const uint8_t* puff_buf,
size_t puff_size,
uint8_t* comp_buf,
size_t comp_size,
Error* error) const {
BufferPuffReader puff_reader(puff_buf, puff_size);
BufferBitWriter bit_writer(comp_buf, comp_size);
TEST_AND_RETURN_FALSE(
huffer_.HuffDeflate(&puff_reader, &bit_writer, error));
TEST_AND_RETURN_FALSE_SET_ERROR(comp_size == bit_writer.Size(),
Error::kInvalidInput);
TEST_AND_RETURN_FALSE_SET_ERROR(puff_reader.BytesLeft() == 0,
Error::kInvalidInput);
return true;
}
// Puffs |compressed| into |out_puff| and checks its equality with
// |expected_puff|.
void TestPuffDeflate(const Buffer& compressed,
const Buffer& expected_puff,
Buffer* out_puff) {
out_puff->resize(expected_puff.size());
auto comp_size = compressed.size();
auto puff_size = out_puff->size();
Error error;
ASSERT_TRUE(PuffDeflate(compressed.data(), comp_size, out_puff->data(),
puff_size, &error));
ASSERT_EQ(puff_size, expected_puff.size());
out_puff->resize(puff_size);
ASSERT_EQ(expected_puff, *out_puff);
}
// Should fail when trying to puff |compressed|.
void FailPuffDeflate(const Buffer& compressed,
Error expected_error,
Buffer* out_puff) {
out_puff->resize(compressed.size() * 2 + 10);
auto comp_size = compressed.size();
auto puff_size = out_puff->size();
Error error;
ASSERT_FALSE(PuffDeflate(compressed.data(), comp_size, out_puff->data(),
puff_size, &error));
ASSERT_EQ(error, expected_error);
}
// Huffs |puffed| into |out_huff| and checks its equality with
// |expected_huff|.|
void TestHuffDeflate(const Buffer& puffed,
const Buffer& expected_huff,
Buffer* out_huff) {
out_huff->resize(expected_huff.size());
auto huff_size = out_huff->size();
auto puffed_size = puffed.size();
Error error;
ASSERT_TRUE(HuffDeflate(puffed.data(), puffed_size, out_huff->data(),
huff_size, &error));
ASSERT_EQ(expected_huff, *out_huff);
}
// Should fail while huffing |puffed|
void FailHuffDeflate(const Buffer& puffed,
Error expected_error,
Buffer* out_compress) {
out_compress->resize(puffed.size());
auto comp_size = out_compress->size();
auto puff_size = puffed.size();
Error error;
ASSERT_TRUE(HuffDeflate(puffed.data(), puff_size, out_compress->data(),
comp_size, &error));
ASSERT_EQ(error, expected_error);
}
// Decompresses from |puffed| into |uncompress| and checks its equality with
// |original|.
void Decompress(const Buffer& puffed,
const Buffer& original,
Buffer* uncompress) {
uncompress->resize(original.size());
auto uncomp_size = uncompress->size();
auto puffed_size = puffed.size();
ASSERT_TRUE(DecompressPuff(
puffed.data(), &puffed_size, uncompress->data(), &uncomp_size));
ASSERT_EQ(puffed_size, puffed.size());
ASSERT_EQ(uncomp_size, original.size());
uncompress->resize(uncomp_size);
ASSERT_EQ(original, *uncompress);
}
void CheckSample(const Buffer original,
const Buffer compressed,
const Buffer puffed) {
Buffer puff, uncompress, huff;
TestPuffDeflate(compressed, puffed, &puff);
TestHuffDeflate(puffed, compressed, &huff);
Decompress(puffed, original, &uncompress);
}
void CheckBitExtentsPuffAndHuff(const Buffer& deflate_buffer,
const vector<BitExtent>& deflate_extents,
const Buffer& puff_buffer,
const vector<ByteExtent>& puff_extents) {
std::shared_ptr<Puffer> puffer(new Puffer());
auto deflate_stream = MemoryStream::CreateForRead(deflate_buffer);
ASSERT_TRUE(deflate_stream->Seek(0));
vector<ByteExtent> out_puff_extents;
uint64_t puff_size;
ASSERT_TRUE(FindPuffLocations(deflate_stream, deflate_extents,
&out_puff_extents, &puff_size));
EXPECT_EQ(puff_size, puff_buffer.size());
EXPECT_EQ(out_puff_extents, puff_extents);
auto src_puffin_stream =
PuffinStream::CreateForPuff(std::move(deflate_stream), puffer,
puff_size, deflate_extents, puff_extents);
Buffer out_puff_buffer(puff_buffer.size());
ASSERT_TRUE(src_puffin_stream->Read(out_puff_buffer.data(),
out_puff_buffer.size()));
EXPECT_EQ(out_puff_buffer, puff_buffer);
std::shared_ptr<Huffer> huffer(new Huffer());
Buffer out_deflate_buffer;
deflate_stream = MemoryStream::CreateForWrite(&out_deflate_buffer);
src_puffin_stream =
PuffinStream::CreateForHuff(std::move(deflate_stream), huffer,
puff_size, deflate_extents, puff_extents);
ASSERT_TRUE(
src_puffin_stream->Write(puff_buffer.data(), puff_buffer.size()));
EXPECT_EQ(out_deflate_buffer, deflate_buffer);
}
protected:
Puffer puffer_;
Huffer huffer_;
};
// Tests a simple buffer with uncompressed deflate block.
TEST_F(PuffinTest, UncompressedTest) {
CheckSample(kRaw1, kDeflate1, kPuff1);
}
// Tests a simple buffer with uncompressed deflate block with length zero.
TEST_F(PuffinTest, ZeroLengthUncompressedTest) {
CheckSample(kRaw1_1, kDeflate1_1, kPuff1_1);
}
// Tests a dynamically compressed buffer with only one literal.
TEST_F(PuffinTest, CompressedOneTest) {
CheckSample(kRaw2, kDeflate2, kPuff2);
}
// Tests deflate of an empty buffer.
TEST_F(PuffinTest, EmptyTest) {
CheckSample(kRaw3, kDeflate3, kPuff3);
}
// Tests a simple buffer with compress deflate block using fixed Huffman table.
TEST_F(PuffinTest, FixedCompressedTest) {
CheckSample(kRaw4, kDeflate4, kPuff4);
}
// Tests a compressed deflate block using dynamic Huffman table.
TEST_F(PuffinTest, DynamicHuffmanTest) {
CheckSample(kRaw10, kDeflate10, kPuff10);
}
// Tests an uncompressed deflate block with invalid LEN/NLEN.
TEST_F(PuffinTest, PuffDeflateFailedTest) {
Buffer puffed;
FailPuffDeflate(kDeflate5, Error::kInvalidInput, &puffed);
}
// Tests puffing a block with invalid block header.
TEST_F(PuffinTest, PuffDeflateHeaderFailedTest) {
Buffer puffed;
FailPuffDeflate(kDeflate6, Error::kInvalidInput, &puffed);
}
// Tests puffing a block with final block bit unset so it returns
// Error::kInsufficientInput.
TEST_F(PuffinTest, PuffDeflateNoFinalBlockBitTest) {
CheckSample(kRaw7, kDeflate7, kPuff7);
}
TEST_F(PuffinTest, MultipleDeflateBufferNoFinabBitsTest) {
CheckSample(kRaw7_2, kDeflate7_2, kPuff7_2);
}
TEST_F(PuffinTest, MultipleDeflateBufferOneFinalBitTest) {
CheckSample(kRaw7_3, kDeflate7_3, kPuff7_3);
}
TEST_F(PuffinTest, MultipleDeflateBufferBothFinalBitTest) {
CheckSample(kRaw7_4, kDeflate7_4, kPuff7_4);
}
// TODO(ahassani): Add unittests for Failhuff too.
TEST_F(PuffinTest, BitExtentPuffAndHuffTest) {
CheckBitExtentsPuffAndHuff(kDeflate11, kSubblockDeflateExtents11, kPuff11,
kPuffExtents11);
}
// TODO(ahassani): add tests for:
// TestPatchingEmptyTo9
// TestPatchingNoDeflateTo9
// TODO(ahassani): Change tests data if you decided to compress the header of
// the patch.
} // namespace puffin