blob: 2b1588d71e47cb9b29ad9f602fed817393b64b92 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 <zircon/assert.h>
#include <algorithm>
#include <fbl/algorithm.h>
#include <fbl/array.h>
#include <src/lib/chunked-compression/streaming-chunked-compressor.h>
#include <zxtest/zxtest.h>
namespace chunked_compression {
namespace {
void RandomFill(uint8_t* data, size_t len) {
size_t off = 0;
size_t rounded_len = fbl::round_down(len, sizeof(int));
for (off = 0; off < rounded_len; off += sizeof(int)) {
*reinterpret_cast<int*>(data + off) = rand();
}
ZX_ASSERT(off == rounded_len);
for (; off < len; ++off) {
data[off] = static_cast<uint8_t>(rand());
}
}
void VerifyData(const uint8_t* compressed_data, size_t compressed_len, unsigned expected_frames,
size_t expected_uncompressed_size) {
SeekTable table;
HeaderReader reader;
ASSERT_EQ(reader.Parse(compressed_data, compressed_len, compressed_len, &table), kStatusOk);
ASSERT_EQ(table.Entries().size(), expected_frames);
size_t decompressed_size_total = 0;
// Include metadata in compressed size
size_t compressed_size_total =
kChunkArchiveSeekTableOffset + table.Entries().size() * sizeof(SeekTableEntry);
for (unsigned i = 0; i < table.Entries().size(); ++i) {
SeekTableEntry entry = table.Entries()[i];
decompressed_size_total += entry.decompressed_size;
compressed_size_total += entry.compressed_size;
}
EXPECT_EQ(decompressed_size_total, expected_uncompressed_size);
EXPECT_EQ(compressed_size_total, compressed_len);
}
} // namespace
TEST(StreamingChunkedCompressorTest, ComputeOutputSizeLimit_Minimum) {
StreamingChunkedCompressor compressor;
// There should always be enough bytes for at least the metadata and one seek table entry.
ASSERT_GE(compressor.ComputeOutputSizeLimit(1u),
kChunkArchiveSeekTableOffset + sizeof(SeekTableEntry));
}
TEST(StreamingChunkedCompressorTest, Compress_Zeroes_Short) {
size_t len = 8192ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
memset(data.get(), 0x00, len);
StreamingChunkedCompressor compressor;
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
ASSERT_EQ(compressor.Update(data.get(), len), kStatusOk);
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 1u, len);
}
TEST(StreamingChunkedCompressorTest, Compress_Random_Short) {
size_t len = 8192ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
RandomFill(data.get(), len);
StreamingChunkedCompressor compressor;
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
ASSERT_EQ(compressor.Update(data.get(), len), kStatusOk);
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 1u, len);
}
TEST(StreamingChunkedCompressorTest, Compress_Zeroes_Long) {
// 3 data frames, last one partial
size_t len = (2 * CompressionParams::MinChunkSize()) + 42ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
memset(data.get(), 0x00, len);
StreamingChunkedCompressor compressor;
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
ASSERT_EQ(compressor.Update(data.get(), len), kStatusOk);
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 3u, len);
}
TEST(StreamingChunkedCompressorTest, Compress_Zeroes_Long_RandomUpdateSizes) {
// 3 data frames, last one partial
size_t len = (2 * CompressionParams::MinChunkSize()) + 42ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
memset(data.get(), 0x00, len);
StreamingChunkedCompressor compressor;
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
size_t off = 0;
while (off < len) {
constexpr size_t kMaxChunkSize = 8192;
size_t chunk = (rand() % kMaxChunkSize) + 1;
if (chunk > len - off) {
chunk = len - off;
}
ASSERT_EQ(compressor.Update(data.get() + off, chunk), kStatusOk);
off += chunk;
}
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 3u, len);
}
TEST(StreamingChunkedCompressorTest, Compress_Random_Long) {
// 3 data frames, last one partial
size_t len = (2 * CompressionParams::MinChunkSize()) + 42ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
RandomFill(data.get(), len);
StreamingChunkedCompressor compressor;
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
ASSERT_EQ(compressor.Update(data.get(), len), kStatusOk);
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 3u, len);
}
TEST(StreamingChunkedCompressorTest, Compress_Random_Long_RandomUpdateSizes) {
// 3 data frames, last one partial
size_t len = (2 * CompressionParams::MinChunkSize()) + 42ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
RandomFill(data.get(), len);
StreamingChunkedCompressor compressor;
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
size_t off = 0;
while (off < len) {
constexpr size_t kMaxChunkSize = 8192;
size_t chunk = (rand() % kMaxChunkSize) + 1;
if (chunk > len - off) {
chunk = len - off;
}
ASSERT_EQ(compressor.Update(data.get() + off, chunk), kStatusOk);
off += chunk;
}
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 3u, len);
}
TEST(StreamingChunkedCompressorTest, Compress_ReuseCompressor) {
StreamingChunkedCompressor compressor;
{
size_t len = 8192ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
memset(data.get(), 0x00, len);
size_t compressed_limit = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[compressed_limit], compressed_limit);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
ASSERT_EQ(compressor.Update(data.get(), len), kStatusOk);
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 1u, len);
}
{
size_t len = 8192ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
// Set with different input data.
memset(data.get(), 0xac, len);
size_t compressed_limit = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[compressed_limit], compressed_limit);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
ASSERT_EQ(compressor.Update(data.get(), len), kStatusOk);
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 1u, len);
}
}
TEST(StreamingChunkedCompressorTest, Compress_UpdateCalledBeforeInit) {
size_t len = 8192ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
memset(data.get(), 0x00, len);
StreamingChunkedCompressor compressor;
ASSERT_EQ(compressor.Update(data.get(), len), kStatusErrBadState);
}
TEST(StreamingChunkedCompressorTest, Compress_FinalCalledBeforeInit) {
size_t len = 8192ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
memset(data.get(), 0x00, len);
StreamingChunkedCompressor compressor;
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusErrBadState);
}
TEST(StreamingChunkedCompressorTest, Compress_FinalCalledEarly) {
size_t len = 8192ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
memset(data.get(), 0x00, len);
StreamingChunkedCompressor compressor;
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
// All but the last byte was processed
ASSERT_EQ(compressor.Update(data.get(), len - 1), kStatusOk);
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusErrBadState);
// Process the last byte
ASSERT_EQ(compressor.Update(data.get() + len - 1, 1), kStatusOk);
// Now Final() should be successful
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 1u, len);
}
TEST(StreamingChunkedCompressorTest, Compress_Update_TooManyBytes) {
size_t len = 8192ul;
fbl::Array<uint8_t> data(new uint8_t[len], len);
memset(data.get(), 0x00, len);
StreamingChunkedCompressor compressor;
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
ASSERT_EQ(compressor.Update(data.get(), len), kStatusOk);
// Processing any extra bytes should fail
uint8_t byte = 0x0;
ASSERT_EQ(compressor.Update(&byte, sizeof(byte)), kStatusErrInvalidArgs);
// Calling Final() should still succeed
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, 1u, len);
}
TEST(StreamingChunkedCompressorTest, Compress_MaxSeekTableEntries) {
size_t len = 8192ul * kChunkArchiveMaxFrames;
fbl::Array<uint8_t> buf(new uint8_t[8192ul], 8192ul);
memset(buf.get(), 0x00, buf.size());
CompressionParams params = {.chunk_size = 8192ul};
StreamingChunkedCompressor compressor(params);
size_t output_len = compressor.ComputeOutputSizeLimit(len);
fbl::Array<uint8_t> compressed_data(new uint8_t[output_len], output_len);
ASSERT_EQ(compressor.Init(len, compressed_data.get(), compressed_data.size()), kStatusOk);
size_t consumed = 0;
while (consumed < len) {
size_t to_consume = std::min(buf.size(), len - consumed);
ASSERT_EQ(compressor.Update(buf.get(), to_consume), kStatusOk);
consumed += to_consume;
}
size_t compressed_len;
ASSERT_EQ(compressor.Final(&compressed_len), kStatusOk);
VerifyData(compressed_data.get(), compressed_len, kChunkArchiveMaxFrames, len);
}
} // namespace chunked_compression