| // 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::IsOk; |
| 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); |
| ASSERT_THAT(header_or, IsOk()); |
| |
| 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 |