// 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 "gtest/gtest.h" #include "puffin/src/puff_reader.h" #include "puffin/src/puff_writer.h" #include "puffin/src/unittest_common.h" namespace puffin { namespace { void TestLiteralLength(size_t length) { Buffer buf(length + 10); PuffData pd; BufferPuffWriter pw(buf.data(), buf.size()); // We need to insert a metadata otherwise it will fail. pd.type = PuffData::Type::kBlockMetadata; pd.length = 1; ASSERT_TRUE(pw.Insert(pd)); BufferPuffReader pr(buf.data(), buf.size()); ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kBlockMetadata); ASSERT_EQ(pd.length, 1); // We insert |length| bytes. pd.type = PuffData::Type::kLiterals; pd.length = length; pd.read_fn = [](uint8_t* buffer, size_t count) { std::fill(buffer, buffer + count, 10); return true; }; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(pw.Flush()); pd.type = PuffData::Type::kLenDist; pd.distance = 1; pd.length = 3; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(pr.GetNext(&pd)); if (length == 0) { // If length is zero, then nothing should've been inserted. ASSERT_EQ(pd.type, PuffData::Type::kLenDist); } else { // We have to see |length| bytes. ASSERT_EQ(pd.type, PuffData::Type::kLiterals); ASSERT_EQ(pd.length, length); for (size_t i = 0; i < pd.length; i++) { uint8_t byte; pd.read_fn(&byte, 1); EXPECT_EQ(byte, 10); } } } } // namespace // Testing read/write from/into a puff buffer using |PuffReader|/|PuffWriter|. TEST(PuffIOTest, InputOutputTest) { Buffer buf(100); BufferPuffReader pr(buf.data(), buf.size()); BufferPuffWriter pw(buf.data(), buf.size()); BufferPuffWriter epw(nullptr, 0); uint8_t block = 123; { PuffData pd; pd.type = PuffData::Type::kBlockMetadata; pd.block_metadata[0] = 0xCC; // header memcpy(&pd.block_metadata[1], &block, sizeof(block)); pd.length = sizeof(block) + 1; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(epw.Insert(pd)); ASSERT_TRUE(pw.Flush()); ASSERT_TRUE(epw.Flush()); } { PuffData pd; ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kBlockMetadata); ASSERT_EQ(pd.length, sizeof(block) + 1); ASSERT_EQ(pd.block_metadata[0], 0xCC); ASSERT_EQ(pd.block_metadata[1], block); } { PuffData pd; pd.type = PuffData::Type::kLenDist; pd.distance = 321; pd.length = 3; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(epw.Insert(pd)); pd.length = 127; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(epw.Insert(pd)); pd.length = 258; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(epw.Insert(pd)); ASSERT_TRUE(pw.Flush()); ASSERT_TRUE(epw.Flush()); pd.length = 259; ASSERT_FALSE(pw.Insert(pd)); ASSERT_FALSE(epw.Insert(pd)); } { PuffData pd; ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kLenDist); ASSERT_EQ(pd.distance, 321); ASSERT_EQ(pd.length, 3); ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kLenDist); ASSERT_EQ(pd.length, 127); ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kLenDist); ASSERT_EQ(pd.length, 258); } { PuffData pd; pd.type = PuffData::Type::kEndOfBlock; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(epw.Insert(pd)); ASSERT_TRUE(pw.Flush()); ASSERT_TRUE(epw.Flush()); } { PuffData pd; ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kEndOfBlock); } { PuffData pd; pd.type = PuffData::Type::kBlockMetadata; block = 123; pd.block_metadata[0] = 0xCC; // header memcpy(&pd.block_metadata[1], &block, sizeof(block)); pd.length = sizeof(block) + 1; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(epw.Insert(pd)); ASSERT_TRUE(pw.Flush()); ASSERT_TRUE(epw.Flush()); } { PuffData pd; ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kBlockMetadata); ASSERT_EQ(pd.length, sizeof(block) + 1); ASSERT_EQ(pd.block_metadata[0], 0xCC); ASSERT_EQ(pd.block_metadata[1], block); } uint8_t tmp[] = {1, 2, 100}; { PuffData pd; size_t index = 0; pd.type = PuffData::Type::kLiterals; pd.length = 3; pd.read_fn = [&tmp, &index](uint8_t* buffer, size_t count) { if (count > 3 - index) return false; if (buffer != nullptr) { memcpy(buffer, &tmp[index], count); } index += count; return true; }; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(pw.Flush()); // We have to refresh the read_fn function for the second insert. index = 0; ASSERT_TRUE(epw.Insert(pd)); ASSERT_TRUE(epw.Flush()); } { PuffData pd; pd.type = PuffData::Type::kLiteral; pd.byte = 10; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(epw.Insert(pd)); ASSERT_TRUE(pw.Flush()); ASSERT_TRUE(epw.Flush()); } uint8_t tmp3[3]; { PuffData pd; ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kLiterals); ASSERT_EQ(pd.length, 3); ASSERT_TRUE(pd.read_fn(tmp3, 3)); ASSERT_FALSE(pd.read_fn(tmp3, 1)); ASSERT_EQ(0, memcmp(tmp3, tmp, 3)); } { PuffData pd; ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kLiterals); ASSERT_EQ(pd.length, 1); ASSERT_TRUE(pd.read_fn(tmp3, 1)); ASSERT_EQ(tmp3[0], 10); ASSERT_FALSE(pd.read_fn(tmp3, 2)); } { PuffData pd; pd.type = PuffData::Type::kEndOfBlock; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(epw.Insert(pd)); ASSERT_TRUE(pw.Flush()); ASSERT_TRUE(epw.Flush()); } { PuffData pd; ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kEndOfBlock); } ASSERT_EQ(buf.size() - pr.BytesLeft(), pw.Size()); ASSERT_EQ(buf.size() - pr.BytesLeft(), epw.Size()); } // Testing metadata boundary. TEST(PuffIOTest, MetadataBoundaryTest) { PuffData pd; Buffer buf(3); BufferPuffWriter pw(buf.data(), buf.size()); // Block metadata takes two + varied bytes, so on a thre byte buffer, only one // bytes is left for the varied part of metadata. pd.type = PuffData::Type::kBlockMetadata; pd.length = 2; ASSERT_FALSE(pw.Insert(pd)); pd.length = 0; // length should be at least 1. ASSERT_FALSE(pw.Insert(pd)); pd.length = 1; ASSERT_TRUE(pw.Insert(pd)); Buffer puff_buffer = {0x00, 0x03, 0x02, 0x00, 0x00}; BufferPuffReader pr(puff_buffer.data(), puff_buffer.size()); ASSERT_FALSE(pr.GetNext(&pd)); } TEST(PuffIOTest, InvalidCopyLengthsDistanceTest) { PuffData pd; Buffer puff_buffer(20); BufferPuffWriter pw(puff_buffer.data(), puff_buffer.size()); // Invalid Lenght values. pd.type = PuffData::Type::kLenDist; pd.distance = 1; pd.length = 0; EXPECT_FALSE(pw.Insert(pd)); pd.length = 1; EXPECT_FALSE(pw.Insert(pd)); pd.length = 2; EXPECT_FALSE(pw.Insert(pd)); pd.length = 3; EXPECT_TRUE(pw.Insert(pd)); pd.length = 259; EXPECT_FALSE(pw.Insert(pd)); pd.length = 258; EXPECT_TRUE(pw.Insert(pd)); // Invalid distance values. pd.length = 3; pd.distance = 0; EXPECT_FALSE(pw.Insert(pd)); pd.distance = 1; EXPECT_TRUE(pw.Insert(pd)); pd.distance = 32769; EXPECT_FALSE(pw.Insert(pd)); pd.distance = 32768; EXPECT_TRUE(pw.Insert(pd)); // First three bytes header, four bytes value lit/len, and four bytes // invalid lit/len. puff_buffer = {0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0xFF, 0x82, 0x00, 0x00}; BufferPuffReader pr(puff_buffer.data(), puff_buffer.size()); EXPECT_TRUE(pr.GetNext(&pd)); EXPECT_EQ(pd.type, PuffData::Type::kBlockMetadata); EXPECT_TRUE(pr.GetNext(&pd)); EXPECT_EQ(pd.type, PuffData::Type::kLenDist); EXPECT_FALSE(pr.GetNext(&pd)); } TEST(PuffIOTest, InvalidCopyLenghtDistanceBoundaryTest) { PuffData pd; Buffer puff_buffer(5); pd.type = PuffData::Type::kLenDist; pd.distance = 1; pd.length = 129; for (size_t i = 1; i < 2; i++) { BufferPuffWriter pw(puff_buffer.data(), i); EXPECT_FALSE(pw.Insert(pd)); } pd.length = 130; for (size_t i = 1; i < 3; i++) { BufferPuffWriter pw(puff_buffer.data(), i); EXPECT_FALSE(pw.Insert(pd)); } // First three bytes header, three bytes value lit/len. puff_buffer = {0x00, 0x00, 0xFF, 0xFF, 0x80, 0x00}; BufferPuffReader pr(puff_buffer.data(), puff_buffer.size()); EXPECT_TRUE(pr.GetNext(&pd)); EXPECT_EQ(pd.type, PuffData::Type::kBlockMetadata); EXPECT_FALSE(pr.GetNext(&pd)); } TEST(PuffIOTest, LiteralsTest) { TestLiteralLength(0); TestLiteralLength(1); TestLiteralLength(2); TestLiteralLength(126); TestLiteralLength(127); TestLiteralLength(128); } // Testing maximum literals length. TEST(PuffIOTest, MaxLiteralsTest) { Buffer buf((1 << 16) + 127 + 20); PuffData pd; BufferPuffWriter pw(buf.data(), buf.size()); // We need to insert a metadata otherwise it will fail. pd.type = PuffData::Type::kBlockMetadata; pd.length = 1; ASSERT_TRUE(pw.Insert(pd)); pd.type = PuffData::Type::kLiterals; pd.length = (1 << 16); pd.read_fn = [](uint8_t* buffer, size_t count) { std::fill(buffer, buffer + count, 10); return true; }; ASSERT_TRUE(pw.Insert(pd)); ASSERT_TRUE(pw.Flush()); BufferPuffReader pr(buf.data(), buf.size()); ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kBlockMetadata); ASSERT_EQ(pd.length, 1); ASSERT_TRUE(pr.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kLiterals); ASSERT_EQ(pd.length, 1 << 16); for (size_t i = 0; i < pd.length; i++) { uint8_t byte; pd.read_fn(&byte, 1); ASSERT_EQ(byte, 10); } BufferPuffWriter pw2(buf.data(), buf.size()); pd.type = PuffData::Type::kBlockMetadata; pd.length = 1; ASSERT_TRUE(pw2.Insert(pd)); pd.type = PuffData::Type::kLiteral; pd.length = 1; pd.byte = 12; // We have to be able to fill 65663 bytes. for (size_t i = 0; i < ((1 << 16) + 127); i++) { ASSERT_TRUE(pw2.Insert(pd)); } // If we add one more, then it should have been flushed. pd.byte = 13; ASSERT_TRUE(pw2.Insert(pd)); ASSERT_TRUE(pw2.Flush()); // Now read it back. BufferPuffReader pr2(buf.data(), buf.size()); ASSERT_TRUE(pr2.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kBlockMetadata); // Now we should read on kLiterals with lenght 1 << 16 and just one literal // after that. ASSERT_TRUE(pr2.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kLiterals); ASSERT_EQ(pd.length, (1 << 16) + 127); for (size_t i = 0; i < pd.length; i++) { uint8_t byte; pd.read_fn(&byte, 1); ASSERT_EQ(byte, 12); } ASSERT_TRUE(pr2.GetNext(&pd)); ASSERT_EQ(pd.type, PuffData::Type::kLiterals); ASSERT_EQ(pd.length, 1); uint8_t byte; pd.read_fn(&byte, 1); ASSERT_EQ(byte, 13); } } // namespace puffin