| // 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/jpeg/box_header.h" | |
| #include <cstdint> | |
| #include <string> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/strings/str_format.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "riegeli/bytes/string_reader.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::StatusIs; | |
| using ::testing::Range; | |
| using ::testing::TestParamInfo; | |
| using BoxHeaderTest = testing::TestWithParam<uint32_t>; | |
| INSTANTIATE_TEST_SUITE_P(MarkerTests, BoxHeaderTest, | |
| Range(static_cast<uint32_t>(0x0000feff), | |
| static_cast<uint32_t>(0x00010000)), | |
| [](const TestParamInfo<uint32_t>& info) { | |
| return absl::StrFormat( | |
| "%04x", static_cast<uint16_t>(info.param)); | |
| }); | |
| TEST_P(BoxHeaderTest, ConsumeJpegBoxHeaderTest) { | |
| std::string data("\0\0\0\4\1\1", 6); | |
| data[0] = static_cast<uint8_t>((GetParam() & 0x0000ff00) >> 8); | |
| data[1] = static_cast<uint8_t>((GetParam() & 0x000000ff)); | |
| riegeli::StringReader<> input(data); | |
| if (GetParam() < 0xff00 || GetParam() == 0xffff) { | |
| // Invalid marker. | |
| EXPECT_THAT(ConsumeJpegBoxHeader(input), | |
| StatusIs(absl::StatusCode::kInvalidArgument)); | |
| return; | |
| } | |
| auto header_or = ConsumeJpegBoxHeader(input); | |
| ABSL_ASSERT_OK(header_or); | |
| JpegBoxHeader header = *header_or; | |
| EXPECT_EQ(header.offset, 0); | |
| EXPECT_EQ(header.type, GetParam()); | |
| if (GetParam() == 0xff01 || (GetParam() >= 0xffd0 && GetParam() <= 0xffd9)) { | |
| EXPECT_EQ(header.size, 2); | |
| } else { | |
| EXPECT_EQ(header.size, 6); // Length + 2 for the marker | |
| } | |
| } | |
| TEST(BoxHeaderTest, ConsumeJpegBoxHeaderEmptyInput) { | |
| std::string data(""); | |
| riegeli::StringReader<> input(data); | |
| EXPECT_THAT(ConsumeJpegBoxHeader(input), | |
| StatusIs(absl::StatusCode::kDataLoss, | |
| ::testing::HasSubstr("Failed to read type"))); | |
| } | |
| TEST(BoxHeaderTest, ConsumeJpegBoxHeaderShortInputForSize) { | |
| std::string data("\xff\xe1\x00", 3); | |
| riegeli::StringReader<> input(data); | |
| EXPECT_THAT(ConsumeJpegBoxHeader(input), | |
| StatusIs(absl::StatusCode::kDataLoss, | |
| ::testing::HasSubstr("Failed to read size"))); | |
| } | |
| TEST_P(BoxHeaderTest, ConvertMarkerToLabelTest) { | |
| uint16_t marker = static_cast<uint16_t>(GetParam() & 0x0000ffff); | |
| JpegBoxHeader header = {.type = marker}; | |
| switch (marker) { | |
| case 0xfeff: | |
| EXPECT_EQ(header.label(), "feff"); | |
| return; | |
| case 0xff01: | |
| EXPECT_EQ(header.label(), "TEM"); | |
| return; | |
| case 0xffc4: | |
| EXPECT_EQ(header.label(), "DHT"); | |
| return; | |
| case 0xffc8: | |
| EXPECT_EQ(header.label(), "JPG"); | |
| return; | |
| case 0xffcc: | |
| EXPECT_EQ(header.label(), "DAC"); | |
| return; | |
| case 0xffd8: | |
| EXPECT_EQ(header.label(), "SOI"); | |
| return; | |
| case 0xffd9: | |
| EXPECT_EQ(header.label(), "EOI"); | |
| return; | |
| case 0xffda: | |
| EXPECT_EQ(header.label(), "SOS"); | |
| return; | |
| case 0xffdb: | |
| EXPECT_EQ(header.label(), "DQT"); | |
| return; | |
| case 0xffdc: | |
| EXPECT_EQ(header.label(), "DNL"); | |
| return; | |
| case 0xffdd: | |
| EXPECT_EQ(header.label(), "DRI"); | |
| return; | |
| case 0xffde: | |
| EXPECT_EQ(header.label(), "DHP"); | |
| return; | |
| case 0xffdf: | |
| EXPECT_EQ(header.label(), "EXP"); | |
| return; | |
| case 0xfffe: | |
| EXPECT_EQ(header.label(), "COM"); | |
| return; | |
| case 0xffff: | |
| EXPECT_EQ(header.label(), "ffff"); | |
| return; | |
| } | |
| if ((marker & 0x00f0) == 0x00c0) { | |
| EXPECT_EQ(header.label(), absl::StrFormat("SOF%d", (marker & 0x000f))); | |
| return; | |
| } | |
| if (marker >= 0xffd0 && marker <= 0xffd7) { | |
| EXPECT_EQ(header.label(), absl::StrFormat("RST%d", (marker & 0x000f))); | |
| return; | |
| } | |
| if ((marker & 0x00f0) == 0x00e0) { | |
| EXPECT_EQ(header.label(), absl::StrFormat("APP%d", (marker & 0x000f))); | |
| return; | |
| } | |
| if (marker >= 0xfff0 && marker <= 0xfffd) { | |
| EXPECT_EQ(header.label(), absl::StrFormat("JPG%d", (marker & 0x000f))); | |
| return; | |
| } | |
| EXPECT_EQ(header.label(), "RES"); | |
| } | |
| TEST(BoxHeaderTest, PrintToJpegBoxHeader) { | |
| JpegBoxHeader header = {.type = 0xffe1, .offset = 10, .size = 20}; | |
| EXPECT_EQ(testing::PrintToString(header), | |
| "{type: 65505, offset: 10, size: 20, label: \"APP1\"}"); | |
| } | |
| } // namespace | |
| } // namespace credentio |