blob: 4d5f3aa5d66da41124ccc544a2ada949e52eebb4 [file] [edit]
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#include "formats/tiff/reader.h"
#include <sys/types.h>
#include <cstdint>
#include <optional>
#include <string>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "formats/tiff/constants.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "riegeli/bytes/string_reader.h"
#include "riegeli/bytes/string_writer.h"
#include "riegeli/endian/endian_writing.h"
namespace credentio {
namespace {
using ::absl_testing::IsOk;
using ::absl_testing::StatusIs;
using ::testing::HasSubstr;
TEST(TiffImageFileDirectoryEntryTest, RangeOfCount) {
TiffImageFileDirectoryEntry entry{.offset = 100};
// tag (2) + type (2) = 4
EXPECT_EQ(entry.RangeOfCount().offset, 104);
EXPECT_EQ(entry.RangeOfCount().length, 4);
}
TEST(TiffImageFileDirectoryEntryTest, RangeOfValueInline) {
TiffImageFileDirectoryEntry entry{
.offset = 100, .type = kTiffTypeShort, .count = 1, .value_or_offset = 42};
// Inline value is at offset + 2 (tag) + 2 (type) + 4 (count) = 108
EXPECT_EQ(entry.RangeOfValue().offset, 108);
EXPECT_EQ(entry.RangeOfValue().length, 2);
}
TEST(TiffImageFileDirectoryEntryTest, RangeOfValueAtOffset) {
TiffImageFileDirectoryEntry entry{
.offset = 100, .type = kTiffTypeLong, .count = 2, .value_or_offset = 500};
// 2 samples of Long (4 bytes each) = 8 bytes. > 4 bytes, so it's at an
// offset.
EXPECT_EQ(entry.RangeOfValue().offset, 500);
EXPECT_EQ(entry.RangeOfValue().length, 8);
}
TEST(IterateOverImageFileDirectoriesTest, SingleIfd) {
std::string buffer;
riegeli::StringWriter writer(&buffer);
riegeli::WriteBigEndian<uint32_t>(kTiffBigEndianHeader, writer);
riegeli::WriteBigEndian<uint32_t>(8, writer); // Offset to first IFD
riegeli::WriteBigEndian<uint16_t>(1, writer); // count
riegeli::WriteBigEndian<uint16_t>(0x0100, writer); // tag
riegeli::WriteBigEndian<uint16_t>(3, writer); // type
riegeli::WriteBigEndian<uint32_t>(1, writer); // count
riegeli::WriteBigEndian<uint32_t>(10, writer); // value
riegeli::WriteBigEndian<uint32_t>(0, writer); // next IFD
writer.Close();
riegeli::StringReader reader(buffer);
absl::flat_hash_set<uint32_t> visited_offsets;
int ifd_count = 0;
EXPECT_THAT(
IterateOverImageFileDirectories(
reader, /*known_endianness=*/std::nullopt, visited_offsets,
[&](const TiffImageFileDirectory& ifd, TiffEndianness endianness) {
ifd_count++;
EXPECT_EQ(ifd.entries.size(), 1);
EXPECT_EQ(ifd.entries[0].tag, 0x0100);
return true;
}),
IsOk());
EXPECT_EQ(ifd_count, 1);
}
TEST(IterateOverImageFileDirectoriesTest, StopEarly) {
std::string buffer;
riegeli::StringWriter writer(&buffer);
riegeli::WriteBigEndian<uint32_t>(kTiffBigEndianHeader, writer);
riegeli::WriteBigEndian<uint32_t>(8, writer); // Offset to first IFD
// First IFD (starts at 8):
riegeli::WriteBigEndian<uint16_t>(1, writer); // count
riegeli::WriteBigEndian<uint16_t>(0x0100, writer); // tag
riegeli::WriteBigEndian<uint16_t>(3, writer); // type
riegeli::WriteBigEndian<uint32_t>(1, writer); // count
riegeli::WriteBigEndian<uint32_t>(10, writer); // value
riegeli::WriteBigEndian<uint32_t>(26,
writer); // next IFD (8 + 2 + 12 + 4 = 26)
// Second IFD (starts at 26):
riegeli::WriteBigEndian<uint16_t>(1, writer); // count
riegeli::WriteBigEndian<uint16_t>(0x0101, writer); // tag
riegeli::WriteBigEndian<uint16_t>(3, writer); // type
riegeli::WriteBigEndian<uint32_t>(1, writer); // count
riegeli::WriteBigEndian<uint32_t>(20, writer); // value
riegeli::WriteBigEndian<uint32_t>(0, writer); // next IFD
writer.Close();
riegeli::StringReader reader(buffer);
absl::flat_hash_set<uint32_t> visited_offsets;
int ifd_count = 0;
EXPECT_THAT(IterateOverImageFileDirectories(
reader, /*known_endianness=*/std::nullopt, visited_offsets,
[&](const TiffImageFileDirectory& ifd,
TiffEndianness endianness) -> absl::StatusOr<bool> {
ifd_count++;
EXPECT_EQ(ifd.entries.size(), 1);
EXPECT_EQ(ifd.entries[0].tag, 0x0100);
return false; // Stop early!
}),
IsOk());
EXPECT_EQ(ifd_count, 1);
}
TEST(IterateOverImageFileDirectoriesTest, InfiniteLoop) {
std::string buffer;
riegeli::StringWriter writer(&buffer);
riegeli::WriteBigEndian<uint32_t>(kTiffBigEndianHeader, writer);
riegeli::WriteBigEndian<uint32_t>(8, writer); // Offset to first IFD
riegeli::WriteBigEndian<uint16_t>(0, writer); // count
riegeli::WriteBigEndian<uint32_t>(8, writer); // Points back to self
writer.Close();
riegeli::StringReader reader(buffer);
absl::flat_hash_set<uint32_t> visited_offsets;
EXPECT_THAT(IterateOverImageFileDirectories(
reader, /*known_endianness=*/std::nullopt, visited_offsets,
[](const auto&, auto) { return true; }),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("infinite loop detected")));
}
TEST(IterateOverImageFileDirectoriesTest, KnownEndianness) {
std::string buffer;
riegeli::StringWriter writer(&buffer);
// No header, straight to IFD offset
riegeli::WriteBigEndian<uint32_t>(4, writer); // Offset to first IFD
riegeli::WriteBigEndian<uint16_t>(1, writer); // count
riegeli::WriteBigEndian<uint16_t>(0x0100, writer); // tag
riegeli::WriteBigEndian<uint16_t>(3, writer); // type
riegeli::WriteBigEndian<uint32_t>(1, writer); // count
riegeli::WriteBigEndian<uint32_t>(10, writer); // value
riegeli::WriteBigEndian<uint32_t>(0, writer); // next IFD
writer.Close();
riegeli::StringReader reader(buffer);
absl::flat_hash_set<uint32_t> visited_offsets;
int ifd_count = 0;
EXPECT_THAT(
IterateOverImageFileDirectories(
reader, TiffEndianness::kBigEndian, visited_offsets,
[&](const TiffImageFileDirectory& ifd, TiffEndianness endianness) {
ifd_count++;
EXPECT_EQ(endianness, TiffEndianness::kBigEndian);
EXPECT_EQ(ifd.entries.size(), 1);
EXPECT_EQ(ifd.entries[0].tag, 0x0100);
return true;
}),
IsOk());
EXPECT_EQ(ifd_count, 1);
}
TEST(IterateOverImageFileDirectoriesTest, MissingFirstIfdOffset) {
std::string buffer;
riegeli::StringWriter writer(&buffer);
riegeli::WriteBigEndian<uint32_t>(kTiffBigEndianHeader, writer);
// Missing the offset!
writer.Close();
riegeli::StringReader reader(buffer);
absl::flat_hash_set<uint32_t> visited_offsets;
EXPECT_THAT(
IterateOverImageFileDirectories(reader, /*known_endianness=*/std::nullopt,
visited_offsets,
[](const auto&, auto) { return true; }),
StatusIs(absl::StatusCode::kDataLoss, HasSubstr("failed to read value")));
}
TEST(IterateOverImageFileDirectoriesTest, MissingIfdCount) {
std::string buffer;
riegeli::StringWriter writer(&buffer);
riegeli::WriteBigEndian<uint32_t>(kTiffBigEndianHeader, writer);
riegeli::WriteBigEndian<uint32_t>(8, writer); // Offset to first IFD
// File ends here, size is 8.
writer.Close();
riegeli::StringReader reader(buffer);
absl::flat_hash_set<uint32_t> visited_offsets;
EXPECT_THAT(
IterateOverImageFileDirectories(reader, /*known_endianness=*/std::nullopt,
visited_offsets,
[](const auto&, auto) { return true; }),
StatusIs(absl::StatusCode::kDataLoss, HasSubstr("failed to read value")));
}
} // namespace
} // namespace credentio