blob: 4c9d99765b1f342a0c9959001bb1e168d7fd5775 [file] [log] [blame]
// Copyright 2019 The Chromium 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 <stddef.h>
#include <stdint.h>
#include <memory>
#include "base/base_paths.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/memory_mapped_file.h"
#include "base/path_service.h"
#include "media/parsers/vp8_parser.h"
#include "media/parsers/webp_parser.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media {
namespace {
constexpr size_t kWebPFileAndVp8ChunkHeaderSizeInBytes = 20u;
// clang-format off
constexpr uint8_t kLossyWebPFileHeader[] = {
'R', 'I', 'F', 'F',
0x0c, 0x00, 0x00, 0x00, // == 12 (little endian)
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0x00, 0x00, 0x00, 0x00 // == 0
};
constexpr base::span<const uint8_t> kLossyWebPEncodedData(
kLossyWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes);
constexpr base::span<const uint8_t> kInvalidWebPEncodedDataSize(
kLossyWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes - 5u);
constexpr uint8_t kLosslessWebPFileHeader[] = {
'R', 'I', 'F', 'F',
0x0c, 0x00, 0x00, 0x00,
'W', 'E', 'B', 'P',
'V', 'P', '8', 'L',
0x00, 0x00, 0x00, 0x00
};
constexpr base::span<const uint8_t> kLosslessWebPEncodedData(
kLosslessWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes);
constexpr uint8_t kExtendedWebPFileHeader[] = {
'R', 'I', 'F', 'F',
0x0c, 0x00, 0x00, 0x00,
'W', 'E', 'B', 'P',
'V', 'P', '8', 'X',
0x00, 0x00, 0x00, 0x00
};
constexpr base::span<const uint8_t> kExtendedWebPEncodedData(
kExtendedWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes);
constexpr uint8_t kUnknownWebPFileHeader[] = {
'R', 'I', 'F', 'F',
0x0c, 0x00, 0x00, 0x00,
'W', 'E', 'B', 'P',
'V', 'P', '8', '~',
0x00, 0x00, 0x00, 0x00
};
constexpr base::span<const uint8_t> kUnknownWebPEncodedData(
kUnknownWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes);
constexpr uint8_t kInvalidRiffWebPFileHeader[] = {
'X', 'I', 'F', 'F',
0x0c, 0x00, 0x00, 0x00,
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0x00, 0x00, 0x00, 0x00
};
constexpr base::span<const uint8_t> kInvalidRiffWebPEncodedData(
kInvalidRiffWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes);
constexpr uint8_t kInvalidOddFileSizeInWebPFileHeader[] = {
'R', 'I', 'F', 'F',
0x0d, 0x00, 0x00, 0x00, // == 13 (Invalid: should be even)
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0x00, 0x00, 0x00, 0x00,
0x00
};
constexpr base::span<const uint8_t> kInvalidOddFileSizeInHeaderWebPEncodedData(
kInvalidOddFileSizeInWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes + 1u); // Match the reported size
constexpr uint8_t kInvalidLargerThanLimitFileSizeInWebPFileHeader[] = {
'R', 'I', 'F', 'F',
0xfe, 0xff, 0xff, 0xff, // == 2^32 - 2 (Invalid: should be <= 2^32 - 10)
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0x00, 0x00, 0x00, 0x00
};
constexpr base::span<const uint8_t>
kInvalidLargerThanLimitFileSizeInHeaderWebPEncodedData(
kInvalidLargerThanLimitFileSizeInWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes);
constexpr uint8_t kInvalidLargerFileSizeInWebPFileHeader[] = {
'R', 'I', 'F', 'F',
0x10, 0x00, 0x00, 0x00, // == 16 (Invalid: should be 12)
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0x00, 0x00, 0x00, 0x00
};
constexpr base::span<const uint8_t>
kInvalidLargerFileSizeInHeaderWebPEncodedData(
kInvalidLargerFileSizeInWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes);
constexpr uint8_t kInvalidKeyFrameSizeInWebPFileHeader[] = {
'R', 'I', 'F', 'F',
0x0c, 0x00, 0x00, 0x00, // == 12
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0xc8, 0x00, 0x00, 0x00 // == 200 (Invalid: should be 0)
};
constexpr base::span<const uint8_t> kInvalidKeyFrameSizeInWebPEncodedData(
kInvalidKeyFrameSizeInWebPFileHeader,
kWebPFileAndVp8ChunkHeaderSizeInBytes);
constexpr uint8_t kMismatchingOddVp8FrameSizeAndDataSize[] = {
'R', 'I', 'F', 'F',
0x12, 0x00, 0x00, 0x00, // == 18
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0x03, 0x00, 0x00, 0x00, // == 3
0x11, 0xa0, 0x23, 0x00, // Valid padding byte
0xfa, 0xcc // Should not exist.
};
constexpr base::span<const uint8_t>
kMismatchingOddVp8FrameSizeAndDataSizeEncodedData(
kMismatchingOddVp8FrameSizeAndDataSize,
kWebPFileAndVp8ChunkHeaderSizeInBytes + 6u);
constexpr uint8_t kMismatchingEvenVp8FrameSizeAndDataSize[] = {
'R', 'I', 'F', 'F',
0x12, 0x00, 0x00, 0x00, // == 18
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0x04, 0x00, 0x00, 0x00, // == 4
0x11, 0xa0, 0x23, 0x12,
0xfc, 0xcd // Should not exist.
};
constexpr base::span<const uint8_t>
kMismatchingEvenVp8FrameSizeAndDataSizeEncodedData(
kMismatchingEvenVp8FrameSizeAndDataSize,
kWebPFileAndVp8ChunkHeaderSizeInBytes + 6u);
constexpr uint8_t kInvalidPaddingByteInVp8DataChunk[] = {
'R', 'I', 'F', 'F',
0x10, 0x00, 0x00, 0x00, // == 16
'W', 'E', 'B', 'P',
'V', 'P', '8', ' ',
0x03, 0x00, 0x00, 0x00, // == 3
0x11, 0xa0, 0x23, 0xff // Invalid: last byte should be 0
};
constexpr base::span<const uint8_t>
kInvalidPaddingByteInVp8DataChunkEncodedData(
kInvalidPaddingByteInVp8DataChunk,
kWebPFileAndVp8ChunkHeaderSizeInBytes + 4u);
// clang-format on
} // namespace
TEST(WebPParserTest, WebPImageFileValidator) {
// Verify that only lossy WebP formats pass.
ASSERT_TRUE(IsLossyWebPImage(kLossyWebPEncodedData));
// Verify that lossless, extended, and unknown WebP formats fail.
ASSERT_FALSE(IsLossyWebPImage(kLosslessWebPEncodedData));
ASSERT_FALSE(IsLossyWebPImage(kExtendedWebPEncodedData));
ASSERT_FALSE(IsLossyWebPImage(kUnknownWebPEncodedData));
// Verify that invalid WebP file headers and sizes fail.
ASSERT_FALSE(IsLossyWebPImage(kInvalidRiffWebPEncodedData));
ASSERT_FALSE(IsLossyWebPImage(kInvalidWebPEncodedDataSize));
}
TEST(WebPParserTest, ParseLossyWebP) {
base::FilePath data_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir));
base::FilePath file_path = data_dir.AppendASCII("media")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("red_green_gradient_lossy.webp");
base::MemoryMappedFile stream;
ASSERT_TRUE(stream.Initialize(file_path))
<< "Couldn't open stream file: " << file_path.MaybeAsASCII();
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(base::span<const uint8_t>(stream.data(), stream.length()));
ASSERT_TRUE(result);
ASSERT_TRUE(result->IsKeyframe());
ASSERT_TRUE(result->data);
// Original image is 3000x3000.
ASSERT_EQ(3000u, result->width);
ASSERT_EQ(3000u, result->height);
}
TEST(WebPParserTest, ParseLosslessWebP) {
base::FilePath data_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir));
base::FilePath file_path =
data_dir.AppendASCII("media")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("yellow_pink_gradient_lossless.webp");
base::MemoryMappedFile stream;
ASSERT_TRUE(stream.Initialize(file_path))
<< "Couldn't open stream file: " << file_path.MaybeAsASCII();
// Should fail because WebP parser does not parse lossless webp images.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(base::span<const uint8_t>(stream.data(), stream.length()));
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseExtendedWebP) {
base::FilePath data_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir));
base::FilePath file_path = data_dir.AppendASCII("media")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("bouncy_ball.webp");
base::MemoryMappedFile stream;
ASSERT_TRUE(stream.Initialize(file_path))
<< "Couldn't open stream file: " << file_path.MaybeAsASCII();
// Should fail because WebP parser does not parse extended webp images.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(base::span<const uint8_t>(stream.data(), stream.length()));
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithUnknownFormat) {
// Should fail when the specifier byte at position 16 holds anything but ' '.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kUnknownWebPEncodedData);
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithInvalidHeaders) {
// Should fail because the header is an invalid WebP container.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kInvalidRiffWebPEncodedData);
ASSERT_FALSE(result);
// Should fail because the header has an invalid size.
result = ParseWebPImage(kInvalidWebPEncodedDataSize);
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithInvalidOddSizeInHeader) {
// Should fail because the size reported in the header is odd.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kInvalidOddFileSizeInHeaderWebPEncodedData);
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithInvalidLargerThanLimitSizeInHeader) {
// Should fail because the size reported in the header is larger than
// 2^32 - 10 per the WebP spec.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kInvalidLargerThanLimitFileSizeInHeaderWebPEncodedData);
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithInvalidFileSizeInHeader) {
// Should fail because the size reported in the header does not match the
// actual data size.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kInvalidLargerFileSizeInHeaderWebPEncodedData);
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithEmptyVp8KeyFrameAndIncorrectKeyFrameSize) {
// Should fail because the reported VP8 key frame size is larger than the
// the existing data.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kInvalidKeyFrameSizeInWebPEncodedData);
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithMismatchingVp8FrameAndDataSize) {
// Should fail because the reported VP8 key frame size (even or odd) does not
// match the encoded data's size.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kMismatchingOddVp8FrameSizeAndDataSizeEncodedData);
ASSERT_FALSE(result);
result = ParseWebPImage(kMismatchingEvenVp8FrameSizeAndDataSizeEncodedData);
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithInvalidPaddingByteInVp8DataChunk) {
// Should fail because the reported VP8 key frame size is odd and the added
// padding byte is not 0.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kInvalidPaddingByteInVp8DataChunkEncodedData);
ASSERT_FALSE(result);
}
TEST(WebPParserTest, ParseWebPWithEmptyVp8KeyFrame) {
// Should fail because the VP8 parser is passed a data chunk of size 0.
std::unique_ptr<Vp8FrameHeader> result =
ParseWebPImage(kLossyWebPEncodedData);
ASSERT_FALSE(result);
}
} // namespace media