//
// Copyright 2024 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Decompress_unittest.cpp: Unit tests for the |(Compress/Decompress)*Blob| functions.

#ifdef UNSAFE_BUFFERS_BUILD
#    pragma allow_unsafe_buffers
#endif

#include <gtest/gtest.h>

#include "libANGLE/angletypes.h"

namespace angle
{
namespace
{
class DecompressTest : public ::testing::Test
{
  protected:
    void SetUp() override
    {
        constexpr size_t kTestDataSize = 100'000;

        mTestData.resize(kTestDataSize);
        for (size_t i = 0; i < kTestDataSize; ++i)
        {
            mTestData[i] = static_cast<uint8_t>(i);
        }

        ASSERT_TRUE(CompressBlob(mTestData.size(), mTestData.data(), &mCompressedData));
    }

    void setCompressedDataLastDWord(uint32_t value)
    {
        ASSERT(IsLittleEndian());
        ASSERT(mCompressedData.size() > sizeof(value));
        memcpy(mCompressedData.data() + mCompressedData.size() - sizeof(value), &value,
               sizeof(value));
    }

    bool decompress(size_t compressedSize, size_t maxUncompressedDataSize)
    {
        return DecompressBlob(mCompressedData.data(), compressedSize, maxUncompressedDataSize,
                              &mUncompressedData);
    }

    bool checkUncompressedData()
    {
        return (mTestData.size() == mUncompressedData.size()) &&
               (memcmp(mTestData.data(), mUncompressedData.data(), mTestData.size()) == 0);
    }

    std::vector<uint8_t> mTestData;
    MemoryBuffer mCompressedData;
    MemoryBuffer mUncompressedData;
};

// Tests that decompressing full data has no errors.
TEST_F(DecompressTest, FullData)
{
    EXPECT_TRUE(decompress(mCompressedData.size(), mTestData.size()));
    EXPECT_TRUE(checkUncompressedData());
}

// Tests expected failure if |maxUncompressedDataSize| is less than actual uncompressed size.
TEST_F(DecompressTest, InsufficientMaxUncompressedDataSize)
{
    EXPECT_FALSE(decompress(mCompressedData.size(), mTestData.size() - 1));
}

// Tests expected failure if try to decompress partial compressed data.
TEST_F(DecompressTest, UnexpectedPartialData)
{
    // Use this to avoid |maxUncompressedDataSize| affecting the test.
    constexpr size_t kMaxUncompressedDataSize = std::numeric_limits<size_t>::max();

    EXPECT_FALSE(decompress(mCompressedData.size() - 1, kMaxUncompressedDataSize));
}

// Tests expected failure if try to decompress corrupted data.
TEST_F(DecompressTest, CorruptedData)
{
    // Corrupt the compressed data.
    const size_t corruptIndex = mCompressedData.size() / 2;
    mCompressedData[corruptIndex] ^= 255;

    EXPECT_FALSE(decompress(mCompressedData.size(), mTestData.size()));
}

// Tests expected failures if try to decompress data with the corrupted last dword.
TEST_F(DecompressTest, CorruptedLastDWord)
{
    // Last dword stores decompressed data size and not the actual compressed data. This dword must
    // match the decompressed size. Decompress should fail if it is not the case.

    // Use this to avoid |maxUncompressedDataSize| affecting the test.
    constexpr size_t kMaxUncompressedDataSize = std::numeric_limits<size_t>::max();

    // Try to decompress with decreased size in the last dword.
    setCompressedDataLastDWord(static_cast<uint32_t>(mTestData.size() - 1));
    EXPECT_FALSE(decompress(mCompressedData.size(), kMaxUncompressedDataSize));

    // Try to decompress with increased size in the last dword.
    setCompressedDataLastDWord(static_cast<uint32_t>(mTestData.size() + 1));
    EXPECT_FALSE(decompress(mCompressedData.size(), kMaxUncompressedDataSize));

    // Try to decompress with the last dword set to 0.
    setCompressedDataLastDWord(0);
    EXPECT_FALSE(decompress(mCompressedData.size(), kMaxUncompressedDataSize));

    // Decompress with the last dword set to the correct size should succeed.
    setCompressedDataLastDWord(static_cast<uint32_t>(mTestData.size()));
    EXPECT_TRUE(decompress(mCompressedData.size(), kMaxUncompressedDataSize));
    EXPECT_TRUE(checkUncompressedData());
}

}  // anonymous namespace
}  // namespace angle
