|  | // 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 <fbl/array.h> | 
|  | #include <src/lib/chunked-compression/chunked-archive.h> | 
|  | #include <src/lib/chunked-compression/chunked-compressor.h> | 
|  | #include <src/lib/chunked-compression/chunked-decompressor.h> | 
|  | #include <src/lib/chunked-compression/status.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()); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, Decompress_EmptyArchive) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | fbl::Array<uint8_t> buf(new uint8_t[kChunkArchiveMinHeaderSize], kChunkArchiveMinHeaderSize); | 
|  | HeaderWriter writer; | 
|  | ASSERT_EQ(HeaderWriter::Create(buf.get(), buf.size(), 0, &writer), kStatusOk); | 
|  | ASSERT_EQ(writer.Finalize(), kStatusOk); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf; | 
|  | size_t decompressed_size; | 
|  | ASSERT_EQ( | 
|  | ChunkedDecompressor::DecompressBytes(buf.get(), buf.size(), &out_buf, &decompressed_size), | 
|  | kStatusOk); | 
|  | EXPECT_EQ(decompressed_size, 0ul); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, Decompress_SingleFrame_Zeroes) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t len = 8192ul; | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | memset(data.get(), 0x00, len); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf; | 
|  | size_t decompressed_size; | 
|  | ASSERT_EQ(ChunkedDecompressor::DecompressBytes(compressed_data.get(), compressed_len, &out_buf, | 
|  | &decompressed_size), | 
|  | kStatusOk); | 
|  | EXPECT_EQ(decompressed_size, 8192l); | 
|  | EXPECT_BYTES_EQ(data.get(), out_buf.get(), len); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, Decompress_SingleFrame_Random) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t len = 8192ul; | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf; | 
|  | size_t decompressed_size; | 
|  | ASSERT_EQ(ChunkedDecompressor::DecompressBytes(compressed_data.get(), compressed_len, &out_buf, | 
|  | &decompressed_size), | 
|  | kStatusOk); | 
|  | EXPECT_EQ(decompressed_size, 8192l); | 
|  | EXPECT_BYTES_EQ(data.get(), out_buf.get(), len); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, Decompress_MultiFrame_Zeroes) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | // 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); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf; | 
|  | size_t decompressed_size; | 
|  | ASSERT_EQ(ChunkedDecompressor::DecompressBytes(compressed_data.get(), compressed_len, &out_buf, | 
|  | &decompressed_size), | 
|  | kStatusOk); | 
|  | EXPECT_EQ(decompressed_size, len); | 
|  | EXPECT_BYTES_EQ(data.get(), out_buf.get(), len); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, Decompress_MultiFrame_Random) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | // 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); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf; | 
|  | size_t decompressed_size; | 
|  | ASSERT_EQ(ChunkedDecompressor::DecompressBytes(compressed_data.get(), compressed_len, &out_buf, | 
|  | &decompressed_size), | 
|  | kStatusOk); | 
|  | EXPECT_EQ(decompressed_size, len); | 
|  | EXPECT_BYTES_EQ(data.get(), out_buf.get(), len); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, DecompressFrame_MultiFrame_Zeroes) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 3 data frames, last one partial | 
|  | size_t len = (2 * chunk_size) + 42ul; | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | memset(data.get(), 0x00, len); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | // Frame 0 | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  |  | 
|  | size_t bytes_written; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  |  | 
|  | EXPECT_EQ(bytes_written, chunk_size); | 
|  | EXPECT_BYTES_EQ(data.get(), out_buf.get(), bytes_written); | 
|  |  | 
|  | // Frame 1 | 
|  | frame_start = compressed_data.get() + table.Entries()[1].compressed_offset; | 
|  | frame_length = table.Entries()[1].compressed_size; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 1, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  |  | 
|  | EXPECT_EQ(bytes_written, chunk_size); | 
|  | EXPECT_BYTES_EQ(data.get() + chunk_size, out_buf.get(), bytes_written); | 
|  |  | 
|  | // Frame 2 | 
|  | frame_start = compressed_data.get() + table.Entries()[2].compressed_offset; | 
|  | frame_length = table.Entries()[2].compressed_size; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 2, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  |  | 
|  | EXPECT_EQ(bytes_written, 42ul); | 
|  | EXPECT_BYTES_EQ(data.get() + (2 * chunk_size), out_buf.get(), bytes_written); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, DecompressFrame_MultiFrame_Random) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 3 data frames, last one partial | 
|  | size_t len = (2 * chunk_size) + 42ul; | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | // Frame 0 | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  |  | 
|  | size_t bytes_written; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  |  | 
|  | EXPECT_EQ(bytes_written, chunk_size); | 
|  | EXPECT_BYTES_EQ(data.get(), out_buf.get(), bytes_written); | 
|  |  | 
|  | // Frame 1 | 
|  | frame_start = compressed_data.get() + table.Entries()[1].compressed_offset; | 
|  | frame_length = table.Entries()[1].compressed_size; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 1, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  |  | 
|  | EXPECT_EQ(bytes_written, chunk_size); | 
|  | EXPECT_BYTES_EQ(data.get() + chunk_size, out_buf.get(), bytes_written); | 
|  |  | 
|  | // Frame 2 | 
|  | frame_start = compressed_data.get() + table.Entries()[2].compressed_offset; | 
|  | frame_length = table.Entries()[2].compressed_size; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 2, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  |  | 
|  | EXPECT_EQ(bytes_written, 42ul); | 
|  | EXPECT_BYTES_EQ(data.get() + (2 * chunk_size), out_buf.get(), bytes_written); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, DecompressFrame_BadFrameNumber) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 3 data frames, last one partial | 
|  | size_t len = (2 * chunk_size) + 42ul; | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  |  | 
|  | size_t bytes_written; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 3, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusErrInvalidArgs); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, DecompressFrame_BadOffset) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 3 data frames, last one partial | 
|  | size_t len = (2 * chunk_size) + 42ul; | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  |  | 
|  | size_t bytes_written; | 
|  | // Starting at 1 byte past the actual frame start. | 
|  | // This looks like a corrupt frame to the decompressor. | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start + 1, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusErrIoDataIntegrity); | 
|  | // Starting at 1 byte before the actual frame start. | 
|  | // This looks like a corrupt frame to the decompressor. | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start - 1, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusErrIoDataIntegrity); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, DecompressFrame_SmallBuffer) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 3 data frames, last one partial | 
|  | size_t len = (2 * chunk_size) + 42ul; | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | fbl::Array<uint8_t> compressed_data; | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(ChunkedCompressor::CompressBytes(data.get(), len, &compressed_data, &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_GE(compressed_data.size(), compressed_len); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  |  | 
|  | size_t bytes_written; | 
|  | // Compressed buffer 1 byte too small | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start, frame_length - 1, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusErrBufferTooSmall); | 
|  | // Decompressed buffer 1 byte too small | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size() - 1, &bytes_written), | 
|  | kStatusErrBufferTooSmall); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, DecompressFrame_CorruptFirstByteInFrame_Checksum) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 2 data frames | 
|  | size_t len = (2 * chunk_size); | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | CompressionParams params; | 
|  | params.frame_checksum = true; | 
|  | ChunkedCompressor compressor(params); | 
|  |  | 
|  | size_t compressed_limit = compressor.ComputeOutputSizeLimit(len); | 
|  | fbl::Array<uint8_t> compressed_data(new uint8_t[compressed_limit], compressed_limit); | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(compressor.Compress(data.get(), len, compressed_data.get(), compressed_limit, | 
|  | &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_LE(compressed_len, compressed_limit); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  |  | 
|  | // Invert the first byte of the first frame. | 
|  | frame_start[0] ^= 0xff; | 
|  |  | 
|  | size_t bytes_written; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusErrIoDataIntegrity); | 
|  |  | 
|  | frame_start = compressed_data.get() + table.Entries()[1].compressed_offset; | 
|  | frame_length = table.Entries()[1].compressed_size; | 
|  |  | 
|  | // Second frame still decompresses. | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 1, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, DecompressFrame_CorruptLastByteInFrame_Checksum) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 2 data frames | 
|  | size_t len = (2 * chunk_size); | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | CompressionParams params; | 
|  | params.frame_checksum = true; | 
|  | ChunkedCompressor compressor(params); | 
|  |  | 
|  | size_t compressed_limit = compressor.ComputeOutputSizeLimit(len); | 
|  | fbl::Array<uint8_t> compressed_data(new uint8_t[compressed_limit], compressed_limit); | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(compressor.Compress(data.get(), len, compressed_data.get(), compressed_limit, | 
|  | &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_LE(compressed_len, compressed_limit); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  |  | 
|  | // Invert the last byte of the first frame. | 
|  | frame_start[frame_length - 1] ^= 0xff; | 
|  |  | 
|  | size_t bytes_written; | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusErrIoDataIntegrity); | 
|  |  | 
|  | frame_start = compressed_data.get() + table.Entries()[1].compressed_offset; | 
|  | frame_length = table.Entries()[1].compressed_size; | 
|  |  | 
|  | // Second frame still decompresses. | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 1, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, DecompressFrame_CorruptFirstByteInFrame_NoChecksum) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 2 data frames | 
|  | size_t len = (2 * chunk_size); | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | CompressionParams params; | 
|  | ChunkedCompressor compressor(params); | 
|  |  | 
|  | size_t compressed_limit = compressor.ComputeOutputSizeLimit(len); | 
|  | fbl::Array<uint8_t> compressed_data(new uint8_t[compressed_limit], compressed_limit); | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(compressor.Compress(data.get(), len, compressed_data.get(), compressed_limit, | 
|  | &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_LE(compressed_len, compressed_limit); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  |  | 
|  | // Invert the first byte of the first frame. | 
|  | frame_start[0] ^= 0xff; | 
|  |  | 
|  | size_t bytes_written; | 
|  | // Even though we have no checksum, this should always be detected as corruption, because | 
|  | // ZSTD will not be able to interpret the frame header. | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 0, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusErrIoDataIntegrity); | 
|  |  | 
|  | frame_start = compressed_data.get() + table.Entries()[1].compressed_offset; | 
|  | frame_length = table.Entries()[1].compressed_size; | 
|  |  | 
|  | // Second frame still decompresses. | 
|  | EXPECT_EQ(decompressor.DecompressFrame(table, 1, frame_start, frame_length, out_buf.get(), | 
|  | out_buf.size(), &bytes_written), | 
|  | kStatusOk); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, Decompress_CorruptHeader) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 2 data frames | 
|  | size_t len = (2 * chunk_size); | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | CompressionParams params; | 
|  | ChunkedCompressor compressor(params); | 
|  |  | 
|  | size_t compressed_limit = compressor.ComputeOutputSizeLimit(len); | 
|  | fbl::Array<uint8_t> compressed_data(new uint8_t[compressed_limit], compressed_limit); | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(compressor.Compress(data.get(), len, compressed_data.get(), compressed_limit, | 
|  | &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_LE(compressed_len, compressed_limit); | 
|  |  | 
|  | // Invert a random byte in the header. | 
|  | size_t header_len = HeaderWriter::MetadataSizeForNumFrames(2); | 
|  | compressed_data[rand() % header_len] ^= 0xff; | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | // We check for *inequality* with kStatusOk, because some types of corruptions may be | 
|  | // signalled differently. (For example if the number of frames reported by the header was | 
|  | // corrupted, the library can't distinguish between the client passing too small of a header, | 
|  | // or the header being corrupted). | 
|  | EXPECT_NE(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table), kStatusOk); | 
|  | } | 
|  |  | 
|  | TEST(ChunkedDecompressorTest, RawDecompressFrame_WrongDecompressionLength) { | 
|  | srand(zxtest::Runner::GetInstance()->random_seed()); | 
|  | size_t chunk_size = CompressionParams::MinChunkSize(); | 
|  | // 2 data frames | 
|  | size_t len = (2 * chunk_size); | 
|  | fbl::Array<uint8_t> data(new uint8_t[len], len); | 
|  | RandomFill(data.get(), len); | 
|  |  | 
|  | CompressionParams params; | 
|  | ChunkedCompressor compressor(params); | 
|  |  | 
|  | size_t compressed_limit = compressor.ComputeOutputSizeLimit(len); | 
|  | fbl::Array<uint8_t> compressed_data(new uint8_t[compressed_limit], compressed_limit); | 
|  | size_t compressed_len; | 
|  | ASSERT_EQ(compressor.Compress(data.get(), len, compressed_data.get(), compressed_limit, | 
|  | &compressed_len), | 
|  | kStatusOk); | 
|  | ASSERT_LE(compressed_len, compressed_limit); | 
|  |  | 
|  | fbl::Array<uint8_t> out_buf(new uint8_t[chunk_size], chunk_size); | 
|  |  | 
|  | SeekTable table; | 
|  | HeaderReader reader; | 
|  | ASSERT_OK(reader.Parse(compressed_data.get(), compressed_len, compressed_len, &table)); | 
|  |  | 
|  | ChunkedDecompressor decompressor; | 
|  |  | 
|  | uint8_t* frame_start = compressed_data.get() + table.Entries()[0].compressed_offset; | 
|  | size_t frame_length = table.Entries()[0].compressed_size; | 
|  | size_t output_len = table.Entries()[0].decompressed_size; | 
|  |  | 
|  | size_t bytes_written; | 
|  | // First frame uses the correct table outputs. | 
|  | EXPECT_EQ(decompressor.DecompressFrame(frame_start, frame_length, out_buf.get(), | 
|  | output_len, &bytes_written), | 
|  | kStatusOk); | 
|  |  | 
|  | frame_start = compressed_data.get() + table.Entries()[1].compressed_offset; | 
|  | frame_length = table.Entries()[1].compressed_size; | 
|  | size_t wrong_output_len = table.Entries()[1].decompressed_size + 1; | 
|  |  | 
|  | // Second frame takes an incorrect table output, so we cannot verify that the entire frame was | 
|  | // decompressed as expected. | 
|  | EXPECT_EQ(decompressor.DecompressFrame(frame_start, frame_length, out_buf.get(), | 
|  | wrong_output_len, &bytes_written), | 
|  | kStatusErrIoDataIntegrity); | 
|  | } | 
|  |  | 
|  | }  // namespace chunked_compression |