| // 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/extractor.h" |
| |
| #include <sys/types.h> |
| |
| #include <cstdint> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/status_macros.h" |
| #include "absl/status/status_matchers.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/string_view.h" |
| #include "formats/asset_box.h" |
| #include "formats/byte_range.h" |
| #include "formats/extractor_result.h" |
| #include "formats/jpeg/testing/app_segment_creator.h" |
| #include "formats/jpeg/testing/jpeg_creator.h" |
| #include "formats/jpeg/testing/jumbf_creator.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "riegeli/bytes/string_reader.h" |
| #include "testing/jumbf_utils.h" |
| |
| namespace credentio { |
| namespace { |
| |
| using ::absl_testing::StatusIs; |
| using ::testing::Eq; |
| using ::testing::HasSubstr; |
| |
| #ifndef ASSERT_OK_AND_ASSIGN |
| #define ASSERT_OK_AND_ASSIGN_CONCAT2(x, y) x##y |
| #define ASSERT_OK_AND_ASSIGN_CONCAT(x, y) ASSERT_OK_AND_ASSIGN_CONCAT2(x, y) |
| |
| #define ASSERT_OK_AND_ASSIGN(lhs, rexpr) \ |
| ASSERT_OK_AND_ASSIGN_IMPL(lhs, rexpr, __COUNTER__) |
| |
| #define ASSERT_OK_AND_ASSIGN_IMPL(lhs, rexpr, id) \ |
| auto ASSERT_OK_AND_ASSIGN_CONCAT(status_or_, id) = (rexpr); \ |
| ASSERT_THAT(ASSERT_OK_AND_ASSIGN_CONCAT(status_or_, id), \ |
| ::absl_testing::IsOk()); \ |
| lhs = std::move(*ASSERT_OK_AND_ASSIGN_CONCAT(status_or_, id)) |
| #endif |
| |
| std::vector<AssetBox> CreateBoxes( |
| std::vector<std::pair<std::string, uint32_t>> expected) { |
| uint64_t offset = 0; |
| std::vector<AssetBox> boxes; |
| for (const auto& name_and_size : expected) { |
| boxes.push_back(AssetBox{ |
| .identifier = name_and_size.first, |
| .byte_range = {.offset = offset, .length = name_and_size.second}, |
| }); |
| offset += name_and_size.second; |
| } |
| return boxes; |
| } |
| |
| struct PayloadInput { |
| std::string payload; |
| JumbfCreatorParams jumbf_creator_params; |
| AppSegmentParams app_segment_params; |
| }; |
| |
| struct TestInput { |
| std::string name; |
| std::vector<PayloadInput> payload = {}; |
| JpegParams jpeg_params = {}; |
| void (*modify_jumbf_boxes)(std::vector<AppSegmentParams>*) = nullptr; |
| void (*modify_app_segments)(JpegParams*) = nullptr; |
| |
| absl::StatusOr<ExtractorResult> expected = |
| absl::InvalidArgumentError("Unsupported hard binding type"); |
| }; |
| |
| class ExtractorTest : public testing::TestWithParam<TestInput> { |
| private: |
| std::vector<AppSegmentParams> PopulateJumbfBoxes(TestInput input) { |
| std::vector<AppSegmentParams> jumbf_boxes; |
| jumbf_boxes.reserve(input.payload.size()); |
| |
| for (const auto& payload : input.payload) { |
| JumbfCreatorParams jumbf_params = payload.jumbf_creator_params; |
| jumbf_params.payload = payload.payload; |
| |
| AppSegmentParams app_segment_params = payload.app_segment_params; |
| app_segment_params.payload = CreateJumbf(jumbf_params); |
| |
| jumbf_boxes.push_back(app_segment_params); |
| } |
| if (input.modify_jumbf_boxes != nullptr) { |
| input.modify_jumbf_boxes(&jumbf_boxes); |
| } |
| return jumbf_boxes; |
| } |
| |
| absl::StatusOr<JpegParams> PopulateAppSegments( |
| TestInput input, std::vector<AppSegmentParams> jumbf_boxes) { |
| JpegParams result = input.jpeg_params; |
| |
| result.payload.clear(); |
| result.payload.reserve(jumbf_boxes.size()); |
| |
| uint16_t en = 0; |
| for (auto& jumbf_box : jumbf_boxes) { |
| if (!jumbf_box.en.has_value()) { |
| jumbf_box.en = ++en; |
| } |
| |
| ABSL_ASSIGN_OR_RETURN(auto segments, CreateAppSegments(jumbf_box)); |
| for (const auto& segment : segments) { |
| result.payload.push_back(segment); |
| } |
| } |
| |
| if (input.modify_app_segments != nullptr) { |
| input.modify_app_segments(&result); |
| } |
| return result; |
| } |
| |
| protected: |
| absl::StatusOr<std::string> CreateImage(TestInput input) { |
| ABSL_ASSIGN_OR_RETURN( |
| JpegParams params, |
| PopulateAppSegments(input, PopulateJumbfBoxes(input))); |
| return CreateJpeg(params); |
| } |
| }; |
| |
| TEST_P(ExtractorTest, Extract) { |
| ASSERT_OK_AND_ASSIGN(auto image, CreateImage(GetParam())); |
| riegeli::StringReader input(image); |
| |
| absl::StatusOr<std::string> extractor_result = |
| JpegExtractor().ExtractManifestStore(input); |
| |
| if (GetParam().expected.ok()) { |
| ASSERT_OK_AND_ASSIGN(const std::string& manifest_store, extractor_result); |
| ASSERT_OK_AND_ASSIGN(const auto& expected, GetParam().expected); |
| EXPECT_THAT(manifest_store, Eq(expected.manifest_store)); |
| } else { |
| EXPECT_THAT(extractor_result, |
| StatusIs(GetParam().expected.status().code(), |
| HasSubstr(GetParam().expected.status().message()))); |
| } |
| } |
| |
| TEST_P(ExtractorTest, ExtractWorksWhenPrefixPadded) { |
| ASSERT_OK_AND_ASSIGN(auto image, CreateImage(GetParam())); |
| std::string contents = "padding" + image; |
| riegeli::StringReader input(contents); |
| |
| ASSERT_TRUE(input.Seek(7)); |
| absl::StatusOr<std::string> extractor_result = |
| JpegExtractor().ExtractManifestStore(input); |
| |
| if (GetParam().expected.ok()) { |
| ASSERT_OK_AND_ASSIGN(const std::string& manifest_store, extractor_result); |
| ASSERT_OK_AND_ASSIGN(const auto& expected, GetParam().expected); |
| EXPECT_THAT(manifest_store, Eq(expected.manifest_store)); |
| } else { |
| EXPECT_THAT(extractor_result, |
| StatusIs(GetParam().expected.status().code(), |
| HasSubstr(GetParam().expected.status().message()))); |
| } |
| } |
| |
| TEST_P(ExtractorTest, ExtractManifestStoreLocation) { |
| ASSERT_OK_AND_ASSIGN(auto image, CreateImage(GetParam())); |
| riegeli::StringReader input(image); |
| |
| absl::StatusOr<std::optional<ByteRange>> result = |
| JpegExtractor().ExtractManifestStoreLocation(input, {}); |
| |
| if (GetParam().expected.ok()) { |
| ASSERT_OK_AND_ASSIGN(auto manifest_store_location, result); |
| ASSERT_OK_AND_ASSIGN(const auto& expected, GetParam().expected); |
| EXPECT_THAT(manifest_store_location, |
| Eq(expected.asset_byte_info.manifest_store_location)); |
| } else { |
| EXPECT_THAT(result, |
| StatusIs(GetParam().expected.status().code(), |
| HasSubstr(GetParam().expected.status().message()))); |
| } |
| } |
| |
| TEST_P(ExtractorTest, ExtractBoxes) { |
| ASSERT_OK_AND_ASSIGN(auto image, CreateImage(GetParam())); |
| riegeli::StringReader input(image); |
| |
| absl::StatusOr<std::vector<AssetBox>> result = |
| JpegExtractor().ExtractBoxes(input, {}); |
| |
| if (GetParam().expected.ok()) { |
| ASSERT_OK_AND_ASSIGN(const auto& boxes, result); |
| ASSERT_OK_AND_ASSIGN(const auto& expected, GetParam().expected); |
| EXPECT_THAT(boxes, Eq(expected.asset_byte_info.boxes)); |
| } else { |
| EXPECT_THAT(result, |
| StatusIs(GetParam().expected.status().code(), |
| HasSubstr(GetParam().expected.status().message()))); |
| } |
| } |
| |
| TEST_P(ExtractorTest, ExtractManifestStoreLocationWithSuffix) { |
| ASSERT_OK_AND_ASSIGN(auto image, CreateImage(GetParam())); |
| std::string contents = image + "suffix"; |
| riegeli::StringReader input(contents); |
| |
| absl::StatusOr<std::optional<ByteRange>> result = |
| JpegExtractor().ExtractManifestStoreLocation( |
| input, {.end_offset = static_cast<int64_t>(contents.size() - 6)}); |
| |
| if (GetParam().expected.ok()) { |
| ASSERT_OK_AND_ASSIGN(auto manifest_store_location, result); |
| ASSERT_OK_AND_ASSIGN(const auto& expected, GetParam().expected); |
| EXPECT_THAT(manifest_store_location, |
| Eq(expected.asset_byte_info.manifest_store_location)); |
| } else { |
| EXPECT_THAT(result, |
| StatusIs(GetParam().expected.status().code(), |
| HasSubstr(GetParam().expected.status().message()))); |
| } |
| } |
| |
| TEST_P(ExtractorTest, ExtractBoxesWithSuffix) { |
| ASSERT_OK_AND_ASSIGN(auto image, CreateImage(GetParam())); |
| std::string contents = image + "suffix"; |
| riegeli::StringReader input(contents); |
| |
| absl::StatusOr<std::vector<AssetBox>> result = JpegExtractor().ExtractBoxes( |
| input, {.end_offset = static_cast<int64_t>(contents.size()) - 6}); |
| |
| if (GetParam().expected.ok()) { |
| ASSERT_OK_AND_ASSIGN(const auto& boxes, result); |
| ASSERT_OK_AND_ASSIGN(const auto& expected, GetParam().expected); |
| EXPECT_THAT(boxes, Eq(expected.asset_byte_info.boxes)); |
| } else { |
| EXPECT_THAT(result, |
| StatusIs(GetParam().expected.status().code(), |
| HasSubstr(GetParam().expected.status().message()))); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| ImageEncoding, ExtractorTest, |
| testing::Values<TestInput>( |
| TestInput{ |
| .name = "InvalidBeginning", |
| .payload = {}, |
| .jpeg_params = {.start_marker = 0x0102}, |
| .expected = absl::InvalidArgumentError("Invalid JPEG marker: 258"), |
| }, |
| TestInput{ |
| .name = "InvalidEnding", |
| .payload = {}, |
| .jpeg_params = {.end_markers = 0x0304}, |
| .expected = absl::InvalidArgumentError("Invalid JPEG marker: 772"), |
| }, |
| TestInput{ |
| .name = "NoSegments", |
| .payload = {}, |
| .jpeg_params = {}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "NoApp11Segments", |
| .payload = {{.payload = "test", |
| .app_segment_params = {.marker = 0xffe2}}}, |
| .jpeg_params = {}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }), |
| [](const testing::TestParamInfo<ExtractorTest::ParamType>& info) { |
| return info.param.name; |
| }); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Segments, ExtractorTest, |
| testing::Values<TestInput>( |
| TestInput{ |
| .name = "OnlyApp2Segment", |
| .payload = {{ |
| .payload = "test", |
| .app_segment_params = {.marker = 0xffe2}, |
| }}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "IncorrectJpegExtensionType", |
| .payload = {{ |
| .payload = "test", |
| .app_segment_params = {.cl = 0101}, |
| }}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "InvalidSegmentId", |
| .payload = {{ |
| .payload = "test", |
| .app_segment_params = {.en = 0x0000}, |
| }}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "SegmentsOutOfOrder", |
| .payload = |
| { |
| { |
| .payload = |
| "This is a much longer string of test to ensure " |
| "that there is enough data to expect a C2PA " |
| "manifest store within the segment payload.", |
| .app_segment_params = {.max_size = 150}, |
| }, |
| }, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| ASSERT_EQ(params->payload.size(), 2); |
| |
| auto temp = params->payload[0]; |
| params->payload[0] = params->payload[1]; |
| params->payload[1] = temp; |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "IncompleteSegment", |
| .payload = |
| { |
| { |
| .payload = "longer test", |
| .app_segment_params = {.max_size = 50}, |
| }, |
| }, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| ASSERT_EQ(params->payload.size(), 2); |
| params->payload.pop_back(); |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "SplitSegment", |
| .payload = |
| { |
| { |
| .payload = "longer test", |
| .app_segment_params = {.max_size = 50}, |
| }, |
| { |
| .payload = "test", |
| .app_segment_params = {.marker = 0xffe6}, |
| }, |
| }, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| ASSERT_EQ(params->payload.size(), 3); |
| auto temp = params->payload[1]; |
| params->payload[1] = params->payload[2]; |
| params->payload[2] = temp; |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }), |
| [](const testing::TestParamInfo<ExtractorTest::ParamType>& info) { |
| return info.param.name; |
| }); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| JumbfBoxes, ExtractorTest, |
| testing::Values<TestInput>( |
| TestInput{ |
| .name = "TooFewBytesToDetermineIfC2pa", |
| .payload = {{.payload = "test"}}, |
| .modify_jumbf_boxes = |
| [](std::vector<AppSegmentParams>* params) { |
| ASSERT_EQ(params->size(), 1); |
| params->at(0).payload = params->at(0).payload.substr(0, 30); |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "IncorrectSuperBoxTbox", |
| .payload = {{ |
| .payload = "test", |
| .jumbf_creator_params = {.super_box_identifier = 0x12345678}, |
| }}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "InvalidUuidHigh", |
| .payload = {{ |
| .payload = "test", |
| .jumbf_creator_params = {.uuid_high = 0x0000000000000000}, |
| }}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "InvalidUuidLow", |
| .payload = {{ |
| .payload = "test", |
| .jumbf_creator_params = {.uuid_low = 0x0000000000000000}, |
| }}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "InvalidToggles", |
| .payload = {{ |
| .payload = "test", |
| .jumbf_creator_params = {.toggles = 0b00110000}, |
| }}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "InvalidLabel", |
| .payload = {{ |
| .payload = "test", |
| .jumbf_creator_params = {.label = "demo"}, |
| }}, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "ForceSizeIntoXlBox", |
| .payload = {{ |
| .payload = "test", |
| .jumbf_creator_params = {.force_xlbox = true}, |
| }}, |
| .expected = |
| ExtractorResult{ |
| .manifest_store = CreateJumbf({.payload = "test", |
| .force_xlbox = true}), |
| .asset_byte_info = |
| { |
| .manifest_store_location = ByteRange{.offset = 2, |
| .length = 78}, |
| .boxes = CreateBoxes({ |
| {"SOI", /*size=*/2}, |
| {"C2PA", /*size=*/78}, |
| {"EOI", /*size=*/2}, |
| }), |
| }, |
| }, |
| }, |
| TestInput{ |
| .name = "IncorrectLboxSize", |
| .payload = {{ |
| .payload = "longer test", |
| .app_segment_params = {.max_size = 50}, |
| }}, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| ASSERT_EQ(params->payload.size(), 2); |
| params->payload.pop_back(); |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "IncorrectXlboxSize", |
| .payload = {{ |
| .payload = "This is a much longer string of test to ensure " |
| "that there is enough data to expect a C2PA " |
| "manifest store within the segment payload.", |
| .jumbf_creator_params = {.force_xlbox = true}, |
| .app_segment_params = {.max_size = 100}, |
| }}, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| ASSERT_EQ(params->payload.size(), 3); |
| params->payload.pop_back(); |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }), |
| [](const testing::TestParamInfo<ExtractorTest::ParamType>& info) { |
| return info.param.name; |
| }); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| C2paManifestStore, ExtractorTest, |
| testing::Values<TestInput>( |
| TestInput{ |
| .name = "OnlyManifestStore", |
| .payload = {{.payload = "test"}}, |
| .expected = |
| ExtractorResult{ |
| .manifest_store = CreateJumbf({.payload = "test"}), |
| .asset_byte_info = |
| { |
| .manifest_store_location = ByteRange{.offset = 2, |
| .length = 62}, |
| .boxes = CreateBoxes({ |
| {"SOI", /*size=*/2}, |
| {"C2PA", /*size=*/62}, |
| {"EOI", /*size=*/2}, |
| }), |
| }, |
| }, |
| }, |
| TestInput{ |
| .name = "SandwichedManifestStore", |
| .payload = |
| { |
| { |
| .payload = "app3", |
| .app_segment_params = {.marker = 0xffe3}, |
| }, |
| {.payload = "test"}, |
| { |
| .payload = "app6", |
| .app_segment_params = {.marker = 0xffe6}, |
| }, |
| }, |
| .expected = |
| ExtractorResult{ |
| .manifest_store = CreateJumbf({.payload = "test"}), |
| .asset_byte_info = |
| { |
| .manifest_store_location = ByteRange{.offset = 64, |
| .length = 62}, |
| .boxes = CreateBoxes({ |
| {"SOI", /*size=*/2}, |
| {"APP3", /*size=*/62}, |
| {"C2PA", /*size=*/62}, |
| {"APP6", /*size=*/62}, |
| {"EOI", /*size=*/2}, |
| }), |
| }, |
| }, |
| }, |
| TestInput{ |
| .name = "ManifestStoreAcrossTwoSegments", |
| .payload = |
| { |
| { |
| .payload = "longer test", |
| .app_segment_params = {.max_size = 50}, |
| }, |
| }, |
| .expected = |
| ExtractorResult{ |
| .manifest_store = CreateJumbf({.payload = "longer test"}), |
| .asset_byte_info = |
| { |
| .manifest_store_location = ByteRange{.offset = 2, |
| .length = 89}, |
| .boxes = CreateBoxes({ |
| {"SOI", /*size=*/2}, |
| {"C2PA", /*size=*/89}, |
| {"EOI", /*size=*/2}, |
| }), |
| }, |
| }, |
| }, |
| TestInput{ |
| .name = "ManifestStoreAcrossTwoSegmentsDifferentTbox", |
| .payload = |
| { |
| { |
| .payload = "longer test", |
| .app_segment_params = {.max_size = 50}, |
| }, |
| }, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| ASSERT_EQ(params->payload.size(), 2); |
| // lowest byte of tbox is at 19, normally 0x62, change it |
| params->payload[1][19] = 0x69; |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "ManifestStoreAcrossTwoSegmentsDifferentLbox", |
| .payload = |
| { |
| { |
| .payload = "longer test", |
| .app_segment_params = {.max_size = 50}, |
| }, |
| }, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| ASSERT_EQ(params->payload.size(), 2); |
| // lowest byte of lbox is at 15, normally 0x39, change it |
| params->payload[1][15] = 0x47; |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "ManifestStoreAcrossTwoSegmentsDifferentXlbox", |
| .payload = |
| { |
| { |
| .payload = "longer test", |
| .jumbf_creator_params = {.force_xlbox = true}, |
| .app_segment_params = {.max_size = 60}, |
| }, |
| }, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| ASSERT_EQ(params->payload.size(), 2); |
| // lowest byte of xlbox is at 27, normally 0x49, change it |
| params->payload[1][27] = 0x57; |
| }, |
| .expected = absl::NotFoundError("No manifest store found"), |
| }, |
| TestInput{ |
| .name = "TwoManifestStores", |
| .payload = |
| { |
| {.payload = "store 1"}, |
| {.payload = "store 2"}, |
| }, |
| .expected = absl::NotFoundError("Multiple manifest stores found"), |
| }, |
| TestInput{ |
| .name = "TwoManifestStoresThenOthers", |
| .payload = |
| {{.payload = "store 1"}, |
| {.payload = "store 2"}, |
| {.payload = "app8", .app_segment_params = {.marker = 0xffe8}}, |
| {.payload = "app9", .app_segment_params = {.marker = 0xffe9}}}, |
| .expected = absl::NotFoundError("Multiple manifest stores found"), |
| }, |
| // If there are multiple manifest stores, but one of them is invalid |
| // JUMBF we should still return the valid one. |
| TestInput{ |
| .name = "TwoManifestStoresOneInvalid", |
| .payload = |
| { |
| {.payload = "store 1"}, |
| { |
| .payload = "store 2", |
| .app_segment_params = {.max_size = 50}, |
| }, |
| }, |
| .modify_app_segments = |
| [](JpegParams* params) { |
| // Remove the last segment of the second manifest store to |
| // invalidate it |
| ASSERT_EQ(params->payload.size(), 3); |
| params->payload.pop_back(); |
| }, |
| .expected = |
| ExtractorResult{ |
| .manifest_store = CreateJumbf({.payload = "store 1"}), |
| .asset_byte_info = |
| { |
| .manifest_store_location = ByteRange{.offset = 2, |
| .length = 65}, |
| .boxes = CreateBoxes({ |
| {"SOI", /*size=*/2}, |
| {"C2PA", /*size=*/65}, |
| {"APP11", /*size=*/52}, |
| {"EOI", /*size=*/2}, |
| }), |
| }, |
| }, |
| }, |
| TestInput{ |
| .name = "MultiSegmentC2paAndMultiSegmentMiscApp11", |
| .payload = |
| { |
| { |
| .payload = |
| "This is a much longer string of test to ensure " |
| "that there is enough data to expect a C2PA " |
| "manifest store within the segment payload.", |
| .app_segment_params = {.max_size = 100}, |
| }, |
| { |
| .payload = |
| "This is a much longer string of test to ensure " |
| "that there is enough data to expect a C2PA " |
| "manifest store within the segment payload.", |
| .app_segment_params = {.cl = 0x1111, .max_size = 100}, |
| }, |
| }, |
| .expected = |
| ExtractorResult{ |
| .manifest_store = CreateJumbf( |
| {.payload = |
| "This is a much longer string of test to ensure " |
| "that there is enough data to expect a C2PA " |
| "manifest store within the segment payload."}), |
| .asset_byte_info = |
| { |
| .manifest_store_location = ByteRange{.offset = 2, |
| .length = 230}, |
| .boxes = CreateBoxes({ |
| {"SOI", /*size=*/2}, |
| {"C2PA", /*size=*/230}, |
| {"APP11", /*size=*/102}, |
| {"APP11", /*size=*/102}, |
| {"APP11", /*size=*/26}, |
| {"EOI", /*size=*/2}, |
| }), |
| }, |
| }, |
| }), |
| [](const testing::TestParamInfo<ExtractorTest::ParamType>& info) { |
| return info.param.name; |
| }); |
| |
| TEST(JpegExtractorTest, IsManifestStorePayloadMissingJpegElements) { |
| EXPECT_FALSE(JpegExtractor().MightBeC2paManifestStore( |
| CreateStartOfManifestStorePayload())); |
| } |
| |
| TEST(JpegExtractorTest, IsManifestStoreIncorrectExtensionType) { |
| EXPECT_FALSE(JpegExtractor().MightBeC2paManifestStore( |
| CreateStartOfManifestStorePayload( |
| {0x12, 0x34, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01}))); |
| } |
| |
| TEST(JpegExtractorTest, IsManifestStore) { |
| EXPECT_TRUE(JpegExtractor().MightBeC2paManifestStore( |
| CreateStartOfManifestStorePayload( |
| {0x4a, 0x50, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01}))); |
| } |
| |
| } // namespace |
| } // namespace credentio |