blob: d6df981839a59c2a1d53f3afed958adb33363854 [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 <zircon/types.h>
#include <fbl/algorithm.h>
#include <fbl/array.h>
#include <src/lib/chunked-compression/chunked-archive.h>
#include <zxtest/zxtest.h>
#include "test-utils.h"
namespace chunked_compression {
TEST(HeaderWriter, ZeroState) {
size_t sz = kChunkArchiveMinHeaderSize;
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 0, &writer), kStatusOk);
ASSERT_EQ(writer.Finalize(), kStatusOk);
SeekTable header;
HeaderReader reader;
ASSERT_EQ(reader.Parse(buf.get(), buf.size(), buf.size(), &header), kStatusOk);
EXPECT_EQ(header.DecompressedSize(), 0ul);
EXPECT_EQ(header.Entries().size(), 0ul);
EXPECT_EQ(header.CompressedSize(), sz);
EXPECT_EQ(header.SerializedHeaderSize(), sz);
}
TEST(HeaderWriter, OneEntry) {
size_t sz = kChunkArchiveMinHeaderSize + sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 1, &writer), kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 0ul,
.decompressed_size = 256ul,
.compressed_offset = sz,
.compressed_size = 100,
}),
kStatusOk);
ASSERT_EQ(writer.Finalize(), kStatusOk);
SeekTable header;
HeaderReader reader;
ASSERT_EQ(reader.Parse(buf.get(), buf.size(), sz + 100, &header), kStatusOk);
EXPECT_EQ(header.CompressedSize(), sz + 100);
EXPECT_EQ(header.DecompressedSize(), 256ul);
EXPECT_EQ(header.SerializedHeaderSize(), sz);
ASSERT_EQ(header.Entries().size(), 1ul);
const SeekTableEntry& entry = header.Entries()[0];
ASSERT_EQ(entry.decompressed_offset, 0ul);
ASSERT_EQ(entry.decompressed_size, 256ul);
ASSERT_EQ(entry.compressed_offset, sz);
ASSERT_EQ(entry.compressed_size, 100ul);
}
TEST(HeaderWriter, TwoEntries) {
size_t sz = kChunkArchiveMinHeaderSize + 2 * sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 2, &writer), kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 0ul,
.decompressed_size = 256ul,
.compressed_offset = sz,
.compressed_size = 120ul,
}),
kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 256ul,
.decompressed_size = 100ul,
// Note the compressed frames are non-contiguous (the second starts at 2000)
.compressed_offset = 2000ul,
.compressed_size = 40ul,
}),
kStatusOk);
ASSERT_EQ(writer.Finalize(), kStatusOk);
SeekTable header;
HeaderReader reader;
ASSERT_EQ(reader.Parse(buf.get(), buf.size(), 2040ul, &header), kStatusOk);
// Compressed size should be the end of the last frame.
EXPECT_EQ(header.CompressedSize(), 2040ul);
EXPECT_EQ(header.DecompressedSize(), 356ul);
EXPECT_EQ(header.SerializedHeaderSize(), sz);
ASSERT_EQ(header.Entries().size(), 2ul);
const SeekTableEntry& entry1 = header.Entries()[0];
ASSERT_EQ(entry1.decompressed_offset, 0ul);
ASSERT_EQ(entry1.decompressed_size, 256ul);
ASSERT_EQ(entry1.compressed_offset, sz);
ASSERT_EQ(entry1.compressed_size, 120ul);
const SeekTableEntry& entry2 = header.Entries()[1];
ASSERT_EQ(entry2.decompressed_offset, 256ul);
ASSERT_EQ(entry2.decompressed_size, 100ul);
ASSERT_EQ(entry2.compressed_offset, 2000ul);
ASSERT_EQ(entry2.compressed_size, 40ul);
}
TEST(HeaderWriter, MaxEntries) {
size_t sz = HeaderWriter::MetadataSizeForNumFrames(kChunkArchiveMaxFrames);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), kChunkArchiveMaxFrames, &writer),
kStatusOk);
}
TEST(HeaderWriter, FinalizeCalledEarly) {
size_t sz = kChunkArchiveMinHeaderSize + sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 1, &writer), kStatusOk);
ASSERT_EQ(writer.Finalize(), kStatusErrBadState);
}
TEST(HeaderWriter, TooManyEntriesWritten) {
size_t sz = kChunkArchiveMinHeaderSize + sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 1, &writer), kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 0ul,
.decompressed_size = 256ul,
.compressed_offset = sz,
.compressed_size = 112ul,
}),
kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 256ul,
.decompressed_size = 100ul,
.compressed_offset = 2000ul,
.compressed_size = 40ul,
}),
kStatusErrBadState);
}
// Write_Invalid_I* tests verify the invariants documented in the header during writing.
TEST(HeaderWriter, Write_Invalid_I0_DecompressedDataStartsAbove0) {
size_t sz = kChunkArchiveMinHeaderSize + sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 1, &writer), kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 1ul,
.decompressed_size = 255ul,
.compressed_offset = sz,
.compressed_size = 112ul,
}),
kStatusErrInvalidArgs);
}
TEST(HeaderWriter, Write_Invalid_I1_CompressedDataOverlapsHeader) {
size_t sz = kChunkArchiveMinHeaderSize + sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 1, &writer), kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 0ul,
.decompressed_size = 256ul,
.compressed_offset = sz - 1ul,
.compressed_size = 112ul,
}),
kStatusErrInvalidArgs);
}
TEST(HeaderWriter, Write_Invalid_I2_NonContigDecompressedFrames) {
size_t sz = kChunkArchiveMinHeaderSize + 2 * sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 2, &writer), kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 0ul,
.decompressed_size = 256ul,
.compressed_offset = sz,
.compressed_size = 2ul,
}),
kStatusOk);
ASSERT_EQ(writer.AddEntry({
// Gap between frames
.decompressed_offset = 257ul,
.decompressed_size = 99ul,
.compressed_offset = sz + 2ul,
.compressed_size = 10ul,
}),
kStatusErrInvalidArgs);
}
TEST(HeaderWriter, Write_Invalid_I3_NonMonotonicCompressedFrames) {
size_t sz = kChunkArchiveMinHeaderSize + 2 * sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 2, &writer), kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 0ul,
.decompressed_size = 256ul,
.compressed_offset = sz,
.compressed_size = 100ul,
}),
kStatusOk);
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 256ul,
.decompressed_size = 100ul,
.compressed_offset = sz + 99ul,
.compressed_size = 2ul,
}),
kStatusErrInvalidArgs);
}
TEST(HeaderWriter, Write_Invalid_I4_ZeroLengthFrames) {
size_t sz = kChunkArchiveMinHeaderSize + sizeof(SeekTableEntry);
fbl::Array<uint8_t> buf(new uint8_t[sz], sz);
HeaderWriter writer;
ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 1, &writer), kStatusOk);
// Decompressed frame
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 0ul,
.decompressed_size = 0ul,
.compressed_offset = sz,
.compressed_size = 52ul,
}),
kStatusErrInvalidArgs);
// Compressed frame
ASSERT_EQ(writer.AddEntry({
.decompressed_offset = 0ul,
.decompressed_size = 256ul,
.compressed_offset = sz,
.compressed_size = 0ul,
}),
kStatusErrInvalidArgs);
}
} // namespace chunked_compression