| // 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 "claim/manifest_box_assembler.h" | |
| #include <memory> | |
| #include <string> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/strings/string_view.h" | |
| #include "claim/manifest_box.h" | |
| #include "claim/manifest_store_builder.h" | |
| #include "constants/labels.h" | |
| #include "constants/spec_version.h" | |
| #include "crypto/default/hasher.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "jumbf/test_utils.h" | |
| #include "proto/assertion.pb.h" | |
| #include "proto/bmff_based_hash_assertion.pb.h" | |
| #include "proto/generator.pb.h" | |
| #include "proto/generator_info.pb.h" | |
| #include "proto/ingredient_assertion.pb.h" | |
| #include "proto/ingredient_validation_result.pb.h" | |
| #include "proto/manifest.pb.h" | |
| #include "proto/signature_info.pb.h" | |
| #include "testing/proto_test_utils.h" | |
| #include "uuid/uuid.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::StatusIs; | |
| using ::credentio_testing::EqualsProto; | |
| using ::credentio_testing::ParseTextProtoOrDie; | |
| using ::testing::ElementsAre; | |
| using ::testing::HasSubstr; | |
| using ::testing::IsEmpty; | |
| using ::testing::Optional; | |
| using ::testing::Pointwise; | |
| constexpr auto kUuid = | |
| Uuid::FromStringOrDie("03020100-0504-0706-0908-0f0e0d0c0b0a"); | |
| constexpr auto kInstanceId = | |
| Uuid::FromStringOrDie("12345678-1234-1234-1234-123456789012"); | |
| std::string CreateSimpleManifestStoreBytes() { | |
| std::string cbor_box = jumbf::EncodeCborBox("cbor_box"); | |
| std::string signature_box = jumbf::EncodeSuperBox( | |
| kClaimSignatureUuid, kClaimSignatureLabel, {cbor_box}); | |
| std::string test_manifest = jumbf::EncodeSuperBox( | |
| kStandardManifestUuid, "urn:c2pa:11111111-1111-1111-1111-111111111111", | |
| {signature_box}); | |
| return jumbf::EncodeSuperBox(kManifestStoreUuid, kManifestStoreLabel, | |
| {test_manifest}); | |
| } | |
| std::string CreateLegacyManifestStoreBytes() { | |
| std::string cbor_box = jumbf::EncodeCborBox("cbor_box"); | |
| std::string signature_box = jumbf::EncodeSuperBox( | |
| kClaimSignatureUuid, kClaimSignatureLabel, {cbor_box}); | |
| std::string test_manifest = jumbf::EncodeSuperBox( | |
| kStandardManifestUuid, "urn:uuid:legacy-c2pa", {signature_box}); | |
| return jumbf::EncodeSuperBox(kManifestStoreUuid, kManifestStoreLabel, | |
| {test_manifest}); | |
| } | |
| class FakeUuidGenerator : public UuidGenerator { | |
| public: | |
| Uuid Generate() const override { | |
| if (first_) { | |
| first_ = false; | |
| return kUuid; | |
| } | |
| return kInstanceId; | |
| } | |
| private: | |
| mutable bool first_ = true; | |
| }; | |
| class ManifestAssemblerTest : public testing::Test { | |
| protected: | |
| ManifestAssemblerTest() { | |
| manifest_assembler_ = std::make_unique<StandardManifestBoxAssembler>( | |
| &uuid_generator_, &hash_factory_); | |
| } | |
| // Recreates the assembler targeting the given C2PA spec version. | |
| void SetSpecVersion(SpecVersion spec_version) { | |
| manifest_assembler_ = std::make_unique<StandardManifestBoxAssembler>( | |
| &uuid_generator_, &hash_factory_, spec_version); | |
| } | |
| FakeUuidGenerator uuid_generator_; | |
| Sha256HasherFactory hash_factory_; | |
| std::unique_ptr<ManifestBoxAssembler> manifest_assembler_; | |
| }; | |
| TEST_F(ManifestAssemblerTest, NoHardBindingInvalid) { | |
| GenerateManifestStoreRequest request; | |
| EXPECT_THAT( | |
| manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("hard binding"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateBmffBasedHardBinding) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { alg: "some alg" hash: "some hash" } } | |
| actions { created {} } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { alg: "some alg" hash: "some hash" } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { actions { action: "c2pa.created" } } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_)}; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateActionsAssertionWithAllActionsIncluded) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { alg: "some alg" hash: "some hash" } } | |
| actions { | |
| created {} | |
| all_actions_included: true | |
| } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { alg: "some alg" hash: "some hash" } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| all_actions_included: true | |
| actions { action: "c2pa.created" } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_)}; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, UsesInstanceIdFromRequest) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { alg: "some alg" hash: "some hash" } } | |
| actions { created {} } | |
| instance_id: "11111111-1111-1111-1111-111111111111" | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .instance_id = "11111111-1111-1111-1111-111111111111", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { alg: "some alg" hash: "some hash" } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { actions { action: "c2pa.created" } } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, expected.instance_id); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateBoxesBasedHardBinding) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { created {} } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.boxes" | |
| boxes_hash { | |
| alg: "some alg" | |
| boxes { | |
| names: "some name" | |
| names: "other name" | |
| hash: "some hash" | |
| pad: "" | |
| pad2: "" | |
| } | |
| } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { actions { action: "c2pa.created" } } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateDataBasedHardBinding) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| data_hash { | |
| alg: "some alg" | |
| hash: "some hash" | |
| exclusions { start: 1 length: 10 } | |
| } | |
| } | |
| actions { created {} } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.data" | |
| data_hash { | |
| alg: "some alg" | |
| hash: "some hash" | |
| exclusions { start: 1 length: 10 } | |
| pad: "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" | |
| } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { actions { action: "c2pa.created" } } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateCollectionDataHashBasedHardBinding) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| collection_data_hash { | |
| alg: "some alg" | |
| uris { uri: "some uri" hash: "some hash" type: "some type" } | |
| zip_central_directory_hash: "some hash" | |
| } | |
| } | |
| actions { created {} } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.collection.data" | |
| collection_data_hash { | |
| uris { uri: "some uri" hash: "some hash" type: "some type" } | |
| alg: "some alg" | |
| zip_central_directory_hash: "some hash" | |
| } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { actions { action: "c2pa.created" } } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, UnspecifiedInceptionActionInvalid) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("missing inception action"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, EmptyAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions {} | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| )pb"); | |
| EXPECT_THAT( | |
| manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("empty action"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, PlacedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| placed { | |
| action_metadata { description: "test placed description" } | |
| ingredient_indexes: 1 | |
| ingredient_indexes: 2 | |
| } | |
| } | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| ingredients { metadata { title: "ingredient1" } } | |
| ingredients { metadata { title: "ingredient2" } } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.placed" | |
| description: "test placed description" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__2" | |
| } | |
| } | |
| } | |
| } | |
| )pb")); | |
| EXPECT_THAT( | |
| manifest_box.ingredient_assertions, | |
| ElementsAre( | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "ingredient0" relationship: "parentOf" } | |
| )pb"), | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3__1" | |
| ingredient_v3 { title: "ingredient1" relationship: "componentOf" } | |
| )pb"), | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3__2" | |
| ingredient_v3 { title: "ingredient2" relationship: "componentOf" } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, LegacyManifestInIngredients) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| placed { | |
| action_metadata { description: "test placed description" } | |
| ingredient_indexes: 1 | |
| ingredient_indexes: 2 | |
| } | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "ingredient0" } | |
| ingredient_validation_results { | |
| manifest_store_bytes: "" | |
| ingredient_validation_results { | |
| active_manifest { successes { code: "test" } } | |
| } | |
| legacy_ingredient_metadata { | |
| is_legacy_ingredient: true | |
| unsupported_manifests: "unsupported manifest" | |
| } | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "ingredient1" } | |
| ingredient_validation_results { | |
| manifest_store_bytes: "" | |
| ingredient_validation_results { | |
| active_manifest { successes { code: "test" } } | |
| } | |
| legacy_ingredient_metadata { | |
| is_legacy_ingredient: true | |
| unsupported_manifests: "unsupported manifest" | |
| } | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "ingredient2" } | |
| ingredient_validation_results { | |
| manifest_store_bytes: "" | |
| ingredient_validation_results { | |
| active_manifest { successes { code: "test" } } | |
| } | |
| } | |
| } | |
| )pb"); | |
| std::string ingredient0_manifest_store_bytes = | |
| CreateLegacyManifestStoreBytes(); | |
| std::string ingredient1_manifest_store_bytes = | |
| CreateLegacyManifestStoreBytes(); | |
| std::string ingredient2_manifest_store_bytes = | |
| CreateSimpleManifestStoreBytes(); | |
| request.mutable_ingredients(0) | |
| ->mutable_ingredient_validation_results() | |
| ->set_manifest_store_bytes(ingredient0_manifest_store_bytes); | |
| request.mutable_ingredients(1) | |
| ->mutable_ingredient_validation_results() | |
| ->set_manifest_store_bytes(ingredient1_manifest_store_bytes); | |
| request.mutable_ingredients(2) | |
| ->mutable_ingredient_validation_results() | |
| ->set_manifest_store_bytes(ingredient2_manifest_store_bytes); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.placed" | |
| description: "test placed description" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__2" | |
| } | |
| } | |
| } | |
| } | |
| )pb")); | |
| EXPECT_THAT( | |
| manifest_box.ingredient_assertions, | |
| ElementsAre( | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "ingredient0" relationship: "parentOf" } | |
| )pb"), | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3__1" | |
| ingredient_v3 { title: "ingredient1" relationship: "componentOf" } | |
| )pb"), | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3__2" | |
| ingredient_v3 { | |
| title: "ingredient2" | |
| relationship: "componentOf" | |
| validation_results { | |
| active_manifest { successes { code: "test" } } | |
| } | |
| active_manifest { | |
| url: "self#jumbf=/c2pa/urn:c2pa:11111111-1111-1111-1111-111111111111" | |
| hash: "7#\034)n*\250\307>)(\263\336K\024\334R\002\'Q\252^\347\361\000\021T\334\256\320pf" | |
| } | |
| claim_signature { | |
| url: "self#jumbf=/c2pa/urn:c2pa:11111111-1111-1111-1111-111111111111/c2pa.signature" | |
| hash: "\017b6\027\253\306\235Q\350\246\311y\203sr@\256VE\267S\0137>\027#\377+\311U!l" | |
| } | |
| } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, DigitalSourceTypeSetForUnknownProvenance) { | |
| SetSpecVersion(SpecVersion::kC2pa_2_4); | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { opened { ingredient_index: 0 } } | |
| ingredients { | |
| metadata { title: "ingredient0" } | |
| ingredient_validation_results { | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" | |
| } | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT( | |
| manifest_box.ingredient_assertions, ElementsAre(EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "ingredient0" | |
| relationship: "parentOf" | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" | |
| } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, DigitalSourceTypeIgnoredBeforeSpec24) { | |
| SetSpecVersion(SpecVersion::kC2pa_2_2); | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { opened { ingredient_index: 0 } } | |
| ingredients { | |
| metadata { title: "ingredient0" } | |
| ingredient_validation_results { | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" | |
| } | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.ingredient_assertions, ElementsAre(EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "ingredient0" relationship: "parentOf" } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, | |
| DigitalSourceTypeNotSetForIngredientWithoutDigitalSourceType) { | |
| SetSpecVersion(SpecVersion::kC2pa_2_4); | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { opened { ingredient_index: 0 } } | |
| ingredients { | |
| metadata { title: "ingredient0" } | |
| ingredient_validation_results {} | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.ingredient_assertions, ElementsAre(EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "ingredient0" relationship: "parentOf" } | |
| )pb"))); | |
| ASSERT_EQ(manifest_box.ingredient_assertions.size(), 1); | |
| EXPECT_FALSE(manifest_box.ingredient_assertions[0] | |
| .ingredient_v3() | |
| .has_digital_source_type()); | |
| } | |
| TEST_F(ManifestAssemblerTest, | |
| DigitalSourceTypeIgnoredIfManifestStoreBytesPresent) { | |
| SetSpecVersion(SpecVersion::kC2pa_2_4); | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { opened { ingredient_index: 0 } } | |
| ingredients { | |
| metadata { title: "ingredient0" } | |
| ingredient_validation_results { | |
| manifest_store_bytes: "" | |
| ingredient_validation_results { | |
| active_manifest { successes { code: "test" } } | |
| } | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" | |
| } | |
| } | |
| )pb"); | |
| std::string ingredient0_manifest_store_bytes = | |
| CreateSimpleManifestStoreBytes(); | |
| request.mutable_ingredients(0) | |
| ->mutable_ingredient_validation_results() | |
| ->set_manifest_store_bytes(ingredient0_manifest_store_bytes); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT( | |
| manifest_box.ingredient_assertions, ElementsAre(EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "ingredient0" | |
| relationship: "parentOf" | |
| validation_results { active_manifest { successes { code: "test" } } } | |
| active_manifest { | |
| url: "self#jumbf=/c2pa/urn:c2pa:11111111-1111-1111-1111-111111111111" | |
| hash: "7#\034)n*\250\307>)(\263\336K\024\334R\002\'Q\252^\347\361\000\021T\334\256\320pf" | |
| } | |
| claim_signature { | |
| url: "self#jumbf=/c2pa/urn:c2pa:11111111-1111-1111-1111-111111111111/c2pa.signature" | |
| hash: "\017b6\027\253\306\235Q\350\246\311y\203sr@\256VE\267S\0137>\027#\377+\311U!l" | |
| } | |
| } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, PlacedActionWithSharedIngredients) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| placed { | |
| action_metadata { multiple_instances: true } | |
| ingredient_indexes: 1 | |
| ingredient_indexes: 2 | |
| ingredient_indexes: 1 | |
| } | |
| } | |
| actions { placed { ingredient_indexes: 1 } } | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| ingredients { metadata { title: "ingredient1" } } | |
| ingredients { metadata { title: "ingredient2" } } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.placed" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__2" | |
| } | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| multiple_instances: true | |
| } | |
| } | |
| actions { | |
| action: "c2pa.placed" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| } | |
| } | |
| } | |
| )pb")); | |
| EXPECT_THAT( | |
| manifest_box.ingredient_assertions, | |
| ElementsAre( | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "ingredient0" relationship: "parentOf" } | |
| )pb"), | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3__1" | |
| ingredient_v3 { title: "ingredient1" relationship: "componentOf" } | |
| )pb"), | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3__2" | |
| ingredient_v3 { title: "ingredient2" relationship: "componentOf" } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, | |
| PlacedActionWithSharedIngredientsWithSharedRedactions) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| placed { | |
| action_metadata { multiple_instances: true } | |
| ingredient_indexes: 1 | |
| ingredient_indexes: 2 | |
| ingredient_indexes: 1 | |
| } | |
| } | |
| actions { placed { ingredient_indexes: 1 } } | |
| } | |
| ingredients { | |
| metadata { title: "ingredient0" } | |
| ingredient_validation_results { | |
| redactions { uri: "uri1" reason: "reason1" } | |
| redactions { uri: "uri2" reason: "reason2" } | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "ingredient1" } | |
| ingredient_validation_results { | |
| redactions { uri: "uri2" reason: "reason2" } | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "ingredient2" } | |
| ingredient_validation_results { | |
| redactions { uri: "uri2" reason: "reason2" } | |
| } | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.placed" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__2" | |
| } | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| multiple_instances: true | |
| } | |
| } | |
| actions { | |
| action: "c2pa.placed" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.redacted" | |
| reason: "reason1" | |
| parameters { redacted_uri: "uri1" } | |
| } | |
| actions { | |
| action: "c2pa.redacted" | |
| reason: "reason2" | |
| parameters { redacted_uri: "uri2" } | |
| } | |
| } | |
| )pb")); | |
| EXPECT_THAT( | |
| manifest_box.ingredient_assertions, | |
| ElementsAre( | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "ingredient0" relationship: "parentOf" } | |
| )pb"), | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3__1" | |
| ingredient_v3 { title: "ingredient1" relationship: "componentOf" } | |
| )pb"), | |
| EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3__2" | |
| ingredient_v3 { title: "ingredient2" relationship: "componentOf" } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, PlacedActionWithoutIngredients) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| placed { action_metadata { description: "test placed description" } } | |
| } | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("missing ingredients in placed action"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, PlacedActionWithInvalidIngredientIndex) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| placed { | |
| action_metadata { description: "test placed description" } | |
| ingredient_indexes: 1 | |
| } | |
| } | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("ingredient index out of range"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, RepackagedActionWithoutOpenedIngredient) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created {} | |
| actions { | |
| repackaged { | |
| action_metadata { description: "test repackaged description" } | |
| } | |
| } | |
| } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("requires an `opened` action"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, TranscodedActionWithoutOpenedIngredient) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created {} | |
| actions { | |
| transcoded { | |
| action_metadata { description: "test transcoded description" } | |
| } | |
| } | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { action: "c2pa.created" } | |
| actions { | |
| action: "c2pa.transcoded" | |
| description: "test transcoded description" | |
| } | |
| } | |
| )pb")); | |
| } | |
| TEST_F(ManifestAssemblerTest, RepackagedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| repackaged { | |
| action_metadata { description: "test repackaged description" } | |
| } | |
| } | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.repackaged" | |
| description: "test repackaged description" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| )pb")); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, ElementsAre(EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "ingredient0" relationship: "parentOf" } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, TranscodedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| transcoded { | |
| action_metadata { description: "test transcoded description" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT( | |
| manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.transcoded" | |
| description: "test transcoded description" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| )pb")); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, ElementsAre(EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "ingredient0" relationship: "parentOf" } | |
| )pb"))); | |
| } | |
| // Tests simple actions with no ingredient references or other custom fields. | |
| TEST_F(ManifestAssemblerTest, SimpleActions) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created {} | |
| actions { added_text { action_metadata { description: "added_text" } } } | |
| actions { converted { action_metadata { description: "converted" } } } | |
| actions { | |
| edited_metadata { action_metadata { description: "edited_metadata" } } | |
| } | |
| actions { unknown { action_metadata { description: "unknown" } } } | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { action: "c2pa.created" } | |
| actions { action: "c2pa.addedText" description: "added_text" } | |
| actions { action: "c2pa.converted" description: "converted" } | |
| actions { | |
| action: "c2pa.edited.metadata" | |
| description: "edited_metadata" | |
| } | |
| actions { action: "c2pa.unknown" description: "unknown" } | |
| } | |
| )pb")); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, IsEmpty()); | |
| } | |
| // Tests actions with digital source type. | |
| TEST_F(ManifestAssemblerTest, DigitalSourceTypeActions) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created {} | |
| actions { | |
| added_text { | |
| action_metadata { description: "added_text" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| adjusted_color { | |
| action_metadata { description: "adjusted_color" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| changed_speed { | |
| action_metadata { description: "changed_speed" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| cropped { | |
| action_metadata { description: "cropped" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| deleted { | |
| action_metadata { description: "deleted" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| drawing { | |
| action_metadata { description: "drawing" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| dubbed { | |
| action_metadata { description: "dubbed" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| edited { | |
| action_metadata { description: "edited" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| enhanced { | |
| action_metadata { description: "enhanced" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| filtered { | |
| action_metadata { description: "filtered" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| orientation { | |
| action_metadata { description: "orientation" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| resized { | |
| action_metadata { description: "resized" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| actions { | |
| trimmed { | |
| action_metadata { description: "trimmed" } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT( | |
| manifest_box.actions_assertion, EqualsProto(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { action: "c2pa.created" } | |
| actions { | |
| action: "c2pa.addedText" | |
| description: "added_text" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.adjustedColor" | |
| description: "adjusted_color" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.changedSpeed" | |
| description: "changed_speed" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.cropped" | |
| description: "cropped" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.deleted" | |
| description: "deleted" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.drawing" | |
| description: "drawing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.dubbed" | |
| description: "dubbed" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.edited" | |
| description: "edited" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.enhanced" | |
| description: "enhanced" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.filtered" | |
| description: "filtered" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.orientation" | |
| description: "orientation" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.resized" | |
| description: "resized" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| actions { | |
| action: "c2pa.trimmed" | |
| description: "trimmed" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/humanEdits" | |
| } | |
| } | |
| )pb")); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, IsEmpty()); | |
| } | |
| TEST_F(ManifestAssemblerTest, ConflictingIngredientRelationships) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| placed { | |
| action_metadata { description: "test placed description" } | |
| ingredient_indexes: 0 | |
| } | |
| } | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("Inconsistent use of ingredient"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasCreatedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent {} | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.created" | |
| software_agent {} | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasCreatedActionWithInputToIngredients) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent {} | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" | |
| input_ingredient_indexes: 0 | |
| input_ingredient_indexes: 1 | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient 1" } } | |
| ingredients { metadata { title: "test ingredient 2" } } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.created" | |
| software_agent {} | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = | |
| { | |
| ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient 1" | |
| relationship: "inputTo" | |
| } | |
| )pb"), | |
| ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3__1" | |
| ingredient_v3 { | |
| title: "test ingredient 2" | |
| relationship: "inputTo" | |
| } | |
| )pb"), | |
| }, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, EditedActionWithInputToIngredient) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| edited { | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia" | |
| input_ingredient_indexes: 1 | |
| } | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient 0" } } | |
| ingredients { metadata { title: "test ingredient 1" } } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.edited" | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = | |
| { | |
| ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient 0" | |
| relationship: "parentOf" | |
| } | |
| )pb"), | |
| ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3__1" | |
| ingredient_v3 { | |
| title: "test ingredient 1" | |
| relationship: "inputTo" | |
| } | |
| )pb"), | |
| }, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, FilteredActionWithInputToIngredient) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| filtered { | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia" | |
| input_ingredient_indexes: 1 | |
| } | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient 0" } } | |
| ingredients { metadata { title: "test ingredient 1" } } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.filtered" | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = | |
| { | |
| ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient 0" | |
| relationship: "parentOf" | |
| } | |
| )pb"), | |
| ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3__1" | |
| ingredient_v3 { | |
| title: "test ingredient 1" | |
| relationship: "inputTo" | |
| } | |
| )pb"), | |
| }, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, ParentIngredientReferencedAsInputTo) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { ingredient_index: 0 } | |
| actions { | |
| edited { | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia" | |
| input_ingredient_indexes: 0 | |
| } | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient 0" } } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("Inconsistent use of ingredient"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, OpenedActionWithoutIngredientIndex) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| } | |
| } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("requires an ingredient index."))); | |
| } | |
| TEST_F(ManifestAssemblerTest, OpenedActionWithInvalidIngredientIndex) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 1 | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient" } } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("ingredient index out of range"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, OpenedActionWithUnreferencedIngredient) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 1 | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient" } } | |
| ingredients { metadata { title: "test ingredient at index 1" } } | |
| )pb"); | |
| EXPECT_THAT( | |
| manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("ingredient is not referenced by any action"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasSimpleOpenedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient" } } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| software_agent {} | |
| description: "opened for testing" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "test ingredient" relationship: "parentOf" } | |
| )pb")}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasOpenedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| } | |
| } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| software_agent {} | |
| description: "opened for testing" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| relationship: "parentOf" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| } | |
| )pb")}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasC2paOpenedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| } | |
| ingredient_validation_results { | |
| manifest_store_bytes: "" | |
| ingredient_validation_results { | |
| active_manifest { successes { code: "test" } } | |
| } | |
| } | |
| } | |
| )pb"); | |
| std::string ingredient_manifest_store_bytes = | |
| CreateSimpleManifestStoreBytes(); | |
| request.mutable_ingredients(0) | |
| ->mutable_ingredient_validation_results() | |
| ->set_manifest_store_bytes(ingredient_manifest_store_bytes); | |
| ManifestStoreBuilder manifest_store_builder(&hash_factory_); | |
| ABSL_ASSERT_OK(manifest_store_builder.AddIngredientManifestStore( | |
| request.ingredients(0) | |
| .ingredient_validation_results() | |
| .manifest_store_bytes())); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| software_agent {} | |
| description: "opened for testing" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| relationship: "parentOf" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| active_manifest { | |
| url: "self#jumbf=/c2pa/urn:c2pa:11111111-1111-1111-1111-111111111111" | |
| hash: "7#\034)n*\250\307>)(\263\336K\024\334R\002\'Q\252^\347\361\000\021T\334\256\320pf" | |
| } | |
| claim_signature { | |
| url: "self#jumbf=/c2pa/urn:c2pa:11111111-1111-1111-1111-111111111111/c2pa.signature" | |
| hash: "\017b6\027\253\306\235Q\350\246\311y\203sr@\256VE\267S\0137>\027#\377+\311U!l" | |
| } | |
| validation_results { active_manifest { successes { code: "test" } } } | |
| } | |
| )pb")}, | |
| .manifest_store_builder = manifest_store_builder, | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasC2paOpenedActionNoManifestStoreBytes) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| } | |
| ingredient_validation_results {} | |
| } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| software_agent {} | |
| description: "opened for testing" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| relationship: "parentOf" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| } | |
| )pb")}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasC2paOpenedActionMissingValidationResults) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| } | |
| ingredient_validation_results { manifest_store_bytes: "" } | |
| } | |
| )pb"); | |
| std::string ingredient_manifest_store_bytes = | |
| CreateSimpleManifestStoreBytes(); | |
| request.mutable_ingredients(0) | |
| ->mutable_ingredient_validation_results() | |
| ->set_manifest_store_bytes(ingredient_manifest_store_bytes); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("validation results cannot be empty"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, MediaTypeFromIngredientValidationResults) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "test ingredient" } | |
| ingredient_validation_results { media_type: "video/mp4" } | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_THAT(manifest_box.ingredient_assertions, ElementsAre(EqualsProto(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| relationship: "parentOf" | |
| } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, CreatesWithMultiPartAssertion) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| bmff_based_hash {} | |
| part_bindings { | |
| bmff_based_hash {} | |
| location { byte_offset: 0 length: 10 } | |
| } | |
| part_bindings { | |
| bmff_based_hash {} | |
| location { byte_offset: 10 length: 10 } | |
| is_optional: true | |
| } | |
| } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent {} | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash {} | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.created" | |
| software_agent {} | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"), | |
| .part_hash_assertions = {ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3.part" | |
| bmff_based_hash {} | |
| )pb"), | |
| ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3.part__1" | |
| bmff_based_hash {} | |
| )pb")}, | |
| .multi_part_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.multi-asset" | |
| multi_asset_hash { | |
| parts { | |
| location { byte_offset: 0 length: 10 } | |
| hash_assertion { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.bmff.v3.part" | |
| } | |
| is_optional: false | |
| } | |
| parts { | |
| location { byte_offset: 10 length: 10 } | |
| hash_assertion { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.bmff.v3.part__1" | |
| } | |
| is_optional: true | |
| } | |
| pad: "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" | |
| pad2: "" | |
| } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| EXPECT_EQ(manifest_box.label, expected.label); | |
| EXPECT_EQ(manifest_box.instance_id, kInstanceId.ToString()); | |
| EXPECT_THAT(manifest_box.hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box.actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box.ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| EXPECT_THAT(manifest_box.part_hash_assertions, | |
| Pointwise(EqualsProto(), expected.part_hash_assertions)); | |
| EXPECT_THAT(manifest_box.multi_part_assertion, | |
| Optional(EqualsProto(*expected.multi_part_assertion))); | |
| } | |
| TEST_F(ManifestAssemblerTest, EmptyGatheredAssertionLabelInvalid) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent {} | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| gathered_assertions { label: "" data: "{}" content_type: CONTENT_TYPE_JSON } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("non-empty label"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, InvalidGatheredAssertionContentTypeInvalid) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent {} | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| gathered_assertions { | |
| label: "com.example.gathered" | |
| data: "{}" | |
| content_type: CONTENT_TYPE_UNSPECIFIED | |
| } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("content type must be specified"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateGatheredAssertions) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash {} } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent {} | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| gathered_assertions { | |
| label: "com.example.gathered.1" | |
| data: "{\"foo\": 1}" | |
| content_type: CONTENT_TYPE_JSON | |
| } | |
| gathered_assertions { | |
| label: "com.example.gathered.2" | |
| data: "{\"bar\": 2}" | |
| content_type: CONTENT_TYPE_JSON | |
| } | |
| )pb"); | |
| auto manifest_box_or = manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box_or); | |
| const ManifestBox& manifest_box = *manifest_box_or; | |
| ASSERT_EQ(manifest_box.gathered_assertions.size(), 2); | |
| EXPECT_THAT(manifest_box.gathered_assertions, | |
| Pointwise(EqualsProto(), request.gathered_assertions())); | |
| } | |
| } // namespace | |
| } // namespace credentio |