| // 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 "generator/generator_impl.h" | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/log/absl_check.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/cord.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/synchronization/notification.h" | |
| #include "assertion/assertion_cbor_encoder.h" | |
| #include "async/async_context.h" | |
| #include "claim/claim_cbor_encoder.h" | |
| #include "claim/generator.h" | |
| #include "claim/local_signer.h" | |
| #include "claim/manifest_box_assembler.h" | |
| #include "claim/signer.h" | |
| #include "crypto/algorithms.h" | |
| #include "crypto/crypto_read_handler.h" | |
| #include "crypto/crypto_write_handler.h" | |
| #include "crypto/default/default_crypto_read_handler.h" | |
| #include "crypto/default/hasher.h" | |
| #include "formats/bmff/hasher.h" | |
| #include "generator/generator.h" | |
| #include "generator/manifest_store_transformer.h" | |
| #include "gmock/gmock.h" | |
| #include "google/protobuf/util/message_differencer.h" | |
| #include "gtest/gtest.h" | |
| #include "jumbf/box_builder.h" | |
| #include "jumbf/constants.h" | |
| #include "proto/bmff_based_hash_assertion.pb.h" | |
| #include "proto/boxes_hash_assertion.pb.h" | |
| #include "proto/collection_data_hash_assertion.pb.h" | |
| #include "proto/data_hash_assertion.pb.h" | |
| #include "proto/generator.pb.h" | |
| #include "proto/generator_info.pb.h" | |
| #include "proto/hashed_uri.pb.h" | |
| #include "proto/ingredient_validation_result.pb.h" | |
| #include "proto/manifest.pb.h" | |
| #include "proto/signature_info.pb.h" | |
| #include "proto/validation_result.pb.h" | |
| #include "salt/salt_generator.h" | |
| #include "testing/create_test_crypto_write_handler.h" | |
| #include "testing/manifest_store_test_utils.h" | |
| #include "testing/proto_test_utils.h" | |
| #include "uuid/uuid.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::IsOk; | |
| using ::absl_testing::StatusIs; | |
| using ::credentio_testing::ParseTextProtoOrDie; | |
| using ::testing::HasSubstr; | |
| using ::testing::Not; | |
| MATCHER_P(EqualsProtoIgnoringTimestampAndOrdering, expected, "") { | |
| google::protobuf::util::MessageDifferencer differencer; | |
| differencer.set_repeated_field_comparison( | |
| google::protobuf::util::MessageDifferencer::AS_SET); | |
| differencer.IgnoreField( | |
| SignatureInfo::descriptor()->FindFieldByName("timestamping_time")); | |
| std::string diff; | |
| differencer.ReportDifferencesToString(&diff); | |
| bool match = differencer.Compare(expected, arg); | |
| if (!match) { | |
| *result_listener << "\nDifference:\n" << diff; | |
| } | |
| return match; | |
| } | |
| MATCHER_P(EqualsProtoIgnoringHashTimestampAndOrdering, expected, "") { | |
| google::protobuf::util::MessageDifferencer differencer; | |
| differencer.set_repeated_field_comparison( | |
| google::protobuf::util::MessageDifferencer::AS_SET); | |
| differencer.IgnoreField(HashedUri::descriptor()->FindFieldByName("hash")); | |
| differencer.IgnoreField( | |
| SignatureInfo::descriptor()->FindFieldByName("timestamping_time")); | |
| std::string diff; | |
| differencer.ReportDifferencesToString(&diff); | |
| bool match = differencer.Compare(expected, arg); | |
| if (!match) { | |
| *result_listener << "\nDifference:\n" << diff; | |
| } | |
| return match; | |
| } | |
| absl::StatusOr<std::unique_ptr<CryptoReadHandler>> | |
| CreateTrustAnyoneCryptoReadHandlerForTest() { | |
| return CreateDefaultCryptoReadHandler({ | |
| .trust_environment = CryptoReadHandler::TrustEnvironment::kAny, | |
| }); | |
| } | |
| class MockManifestStoreTransformer : public ManifestStoreTransformer { | |
| public: | |
| MOCK_METHOD(absl::Status, Transform, (jumbf::BuiltSuperBox * box), | |
| (const, override)); | |
| }; | |
| constexpr Uuid kUuid = | |
| Uuid::FromStringOrDie("03020100-0504-0706-0908-0f0e0d0c0b0a"); | |
| constexpr Uuid kUuid_2 = | |
| Uuid::FromStringOrDie("12345678-1234-1234-1234-123456789012"); | |
| class FakeUuidGenerator : public UuidGenerator { | |
| public: | |
| FakeUuidGenerator() : uuids_({kUuid, kUuid_2}) {} | |
| Uuid Generate() const override { | |
| if (index_ >= uuids_.size()) { | |
| index_ = 0; | |
| } | |
| return uuids_[index_++]; | |
| } | |
| private: | |
| std::vector<Uuid> uuids_; | |
| mutable int index_ = 0; | |
| }; | |
| absl::StatusOr<GenerateManifestStoreResponse> GenerateManifestStore( | |
| ManifestStoreGenerator& generator, GenerateManifestStoreRequest request) { | |
| absl::Notification done; | |
| absl::StatusOr<GenerateManifestStoreResponse> result; | |
| generator.GenerateManifestStore( | |
| std::move(request), AsyncContext(), | |
| [&result, &done](absl::StatusOr<GenerateManifestStoreResponse> res) { | |
| result = std::move(res); | |
| done.Notify(); | |
| }); | |
| done.WaitForNotification(); | |
| return result; | |
| } | |
| absl::StatusOr<ManifestStoreTemplate> GenerateManifestStoreTemplate( | |
| ManifestStoreGenerator& generator, GenerateManifestStoreRequest request) { | |
| absl::Notification done; | |
| absl::StatusOr<ManifestStoreTemplate> result; | |
| generator.GenerateManifestStoreTemplate( | |
| std::move(request), AsyncContext(), | |
| [&result, &done](absl::StatusOr<ManifestStoreTemplate> res) { | |
| result = std::move(res); | |
| done.Notify(); | |
| }); | |
| done.WaitForNotification(); | |
| return result; | |
| } | |
| absl::StatusOr<FinalizeManifestStoreResponse> FinalizeManifestStore( | |
| ManifestStoreGenerator& generator, FinalizeManifestStoreRequest request) { | |
| absl::Notification done; | |
| absl::StatusOr<FinalizeManifestStoreResponse> result; | |
| generator.FinalizeManifestStore( | |
| std::move(request), AsyncContext(), | |
| [&result, &done](absl::StatusOr<FinalizeManifestStoreResponse> res) { | |
| result = std::move(res); | |
| done.Notify(); | |
| }); | |
| done.WaitForNotification(); | |
| return result; | |
| } | |
| class ManifestStoreGeneratorTest : public testing::Test { | |
| protected: | |
| ManifestStoreGeneratorTest() { | |
| absl::StatusOr<std::unique_ptr<CryptoWriteHandler>> crypto_writer = | |
| CreateTestCryptoWriteHandler({}); | |
| ABSL_CHECK_OK(crypto_writer); | |
| crypto_writer_ = std::move(*crypto_writer); | |
| generator_info_.set_name("Test generator"); | |
| generator_info_.set_spec_version("2.2.0"); | |
| absl::StatusOr<std::unique_ptr<CryptoReadHandler>> crypto_reader = | |
| CreateTrustAnyoneCryptoReadHandlerForTest(); | |
| ABSL_CHECK_OK(crypto_reader); | |
| crypto_reader_ = std::move(*crypto_reader); | |
| claim_signer_ = std::make_unique<LocalClaimSigner>(crypto_writer_.get(), | |
| &hash_factory_); | |
| store_generator_ = CreateGenerator(); | |
| } | |
| std::unique_ptr<ManifestStoreGenerator> CreateGenerator( | |
| const ManifestStoreTransformer* manifest_store_transformer = nullptr) { | |
| absl::StatusOr<std::unique_ptr<ManifestBoxAssembler>> manifest_assembler = | |
| CreateManifestAssembler({.uuid_generator = &uuid_generator_, | |
| .hash_factory = &hash_factory_}); | |
| ABSL_CHECK_OK(manifest_assembler); | |
| absl::StatusOr<std::unique_ptr<ManifestEncoder>> manifest_encoder = | |
| CreateManifestEncoder({ | |
| .generator_info = generator_info_, | |
| .claim_signer = claim_signer_.get(), | |
| .assertion_cbor_encoder = &GetAssertionCborEncoder(), | |
| .claim_cbor_encoder = &GetClaimCborEncoder(), | |
| .hash_factory = &hash_factory_, | |
| .salt_generator = &SaltGenerator::NoOp(), | |
| }); | |
| ABSL_CHECK_OK(manifest_encoder); | |
| return std::make_unique<ManifestStoreGeneratorImpl>( | |
| std::move(*manifest_assembler), std::move(*manifest_encoder), | |
| manifest_store_transformer); | |
| } | |
| FakeUuidGenerator uuid_generator_; | |
| std::unique_ptr<ClaimSigner> claim_signer_; | |
| std::unique_ptr<CryptoWriteHandler> crypto_writer_; | |
| std::unique_ptr<CryptoReadHandler> crypto_reader_; | |
| Sha256HasherFactory hash_factory_; | |
| GeneratorInfo generator_info_; | |
| std::unique_ptr<ManifestStoreGenerator> store_generator_; | |
| }; | |
| TEST_F(ManifestStoreGeneratorTest, Generate) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"); | |
| PartialValidationResultProto expected_validated_result = ParseTextProtoOrDie( | |
| R"pb( | |
| active_manifest { | |
| label: "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a" | |
| claim { | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| claim_generator_info { | |
| name: "Test generator" | |
| spec_version: "2.2.0" | |
| } | |
| signature_info { | |
| cert_serial_number: "02" | |
| issuer: "OU=Test Unit,O=Test Org,CN=Test Claim Signer" | |
| certificate_issuer: "OU=Test Unit,O=Test Org,CN=Test Root CA" | |
| timestamping_time {} | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.boxes" | |
| algorithm: "sha256" | |
| hash: ")\255`[2\265\253\340\256(\265I\216_\222m\302@V\255O\204\017\316bb2\201\317\243\233\204" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" | |
| algorithm: "sha256" | |
| hash: "\331\271\373\3228\310\335>y\263\334\342\335\367\211Q\360\325\227\r\345$]\214\005\3047[\r#1\312" | |
| } | |
| default_algorithm: "sha256" | |
| label: "c2pa.claim.v2" | |
| } | |
| assertions { | |
| label: "c2pa.hash.boxes" | |
| boxes_hash { | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| alg: "some alg" | |
| } | |
| } | |
| assertions { | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.created" | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| software_agent { name: "test generator" } | |
| } | |
| } | |
| } | |
| validation { | |
| successes { | |
| code: "timeStamp.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "timeStamp.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "signingCredential.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.insideValidity" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.boxes" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.actions.v2" | |
| } | |
| informationals { | |
| code: "signingCredential.ocsp.skipped" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| } | |
| } | |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.boxes" | |
| )pb"); | |
| absl::StatusOr<GenerateManifestStoreResponse> generated = | |
| GenerateManifestStore(*store_generator_, request); | |
| ABSL_ASSERT_OK(generated); | |
| EXPECT_TRUE(generated->is_timestamped()); | |
| absl::StatusOr<std::unique_ptr<PartialValidationResultProto>> validated = | |
| ValidateManifestStore(generated->manifest_store()); | |
| ABSL_ASSERT_OK(validated); | |
| EXPECT_THAT(**validated, EqualsProtoIgnoringTimestampAndOrdering( | |
| expected_validated_result)); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, GenerateWithGatheredAssertions) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| gathered_assertions { | |
| label: "com.example.gathered" | |
| data: "{\"test_key\": \"test_val\"}" | |
| content_type: CONTENT_TYPE_JSON | |
| } | |
| )pb"); | |
| absl::StatusOr<GenerateManifestStoreResponse> generated = | |
| GenerateManifestStore(*store_generator_, request); | |
| ABSL_ASSERT_OK(generated); | |
| EXPECT_TRUE(generated->is_timestamped()); | |
| absl::StatusOr<Claim> claim = ValidateClaim(generated->manifest_store()); | |
| ABSL_ASSERT_OK(claim); | |
| ASSERT_EQ(claim->gathered_assertions_size(), 1); | |
| EXPECT_EQ(claim->gathered_assertions(0).url(), | |
| "self#jumbf=c2pa.assertions/com.example.gathered"); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, | |
| GenerateTemplateAndFinalizeWithGatheredAssertions) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| GatheredAssertion* gathered = request.add_gathered_assertions(); | |
| gathered->set_label("com.example.gathered"); | |
| gathered->set_data("{\"test_key\": \"test_val\"}"); | |
| gathered->set_content_type(GatheredAssertion::CONTENT_TYPE_JSON); | |
| // Step 1: Generate template. | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| // Step 2: Update hash. | |
| std::string new_hash(HashSizeBytes(HashAlgorithm::kSha256), '\1'); | |
| data_hash.set_hash(new_hash); | |
| // Step 3: Finalize manifest store. | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| absl::StatusOr<FinalizeManifestStoreResponse> finalized = | |
| FinalizeManifestStore(*store_generator_, finalize_request); | |
| ABSL_ASSERT_OK(finalized); | |
| EXPECT_TRUE(finalized->is_timestamped()); | |
| EXPECT_EQ(manifest_store_template->manifest_store_size(), | |
| finalized->manifest_store().size()); | |
| absl::StatusOr<Claim> claim = ValidateClaim(finalized->manifest_store()); | |
| ABSL_ASSERT_OK(claim); | |
| ASSERT_EQ(claim->gathered_assertions_size(), 1); | |
| EXPECT_EQ(claim->gathered_assertions(0).url(), | |
| "self#jumbf=c2pa.assertions/com.example.gathered"); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, GenerateWithC2paOpenedAction) { | |
| SimpleManifestStoreOptions options; | |
| options.uuid_generator = &uuid_generator_; | |
| absl::StatusOr<std::string> parent_manifest_store_bytes = | |
| CreateSimpleManifestStoreBytes(options); | |
| ABSL_ASSERT_OK(parent_manifest_store_bytes); | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "test ingredient" } | |
| ingredient_validation_results { | |
| manifest_store_bytes: "" | |
| ingredient_validation_results { | |
| active_manifest { successes { code: "assertion.dataHash.match" } } | |
| } | |
| } | |
| } | |
| )pb"); | |
| request.mutable_ingredients(0) | |
| ->mutable_ingredient_validation_results() | |
| ->set_manifest_store_bytes(*parent_manifest_store_bytes); | |
| PartialValidationResultProto expected_validated_result = ParseTextProtoOrDie( | |
| R"pb( | |
| active_manifest { | |
| label: "urn:c2pa:12345678-1234-1234-1234-123456789012" | |
| claim { | |
| claim_generator_info { | |
| name: "Test generator" | |
| spec_version: "2.2.0" | |
| } | |
| signature_info { | |
| cert_serial_number: "02" | |
| issuer: "OU=Test Unit,O=Test Org,CN=Test Claim Signer" | |
| certificate_issuer: "OU=Test Unit,O=Test Org,CN=Test Root CA" | |
| timestamping_time {} | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.boxes" | |
| algorithm: "sha256" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| algorithm: "sha256" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" | |
| algorithm: "sha256" | |
| } | |
| default_algorithm: "sha256" | |
| label: "c2pa.claim.v2" | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| } | |
| assertions { | |
| label: "c2pa.hash.boxes" | |
| boxes_hash { | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| alg: "some alg" | |
| } | |
| } | |
| assertions { | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient" | |
| relationship: "parentOf" | |
| active_manifest { | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a" | |
| } | |
| claim_signature { | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| validation_results { | |
| active_manifest { | |
| successes { code: "assertion.dataHash.match" } | |
| } | |
| } | |
| } | |
| } | |
| assertions { | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| description: "opened for testing" | |
| software_agent { name: "test generator" } | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| validation { | |
| successes { | |
| code: "timeStamp.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.signature" | |
| } | |
| successes { | |
| code: "timeStamp.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.signature" | |
| } | |
| successes { | |
| code: "signingCredential.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.insideValidity" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.signature" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.assertions/c2pa.hash.boxes" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.assertions/c2pa.actions.v2" | |
| } | |
| successes { | |
| code: "ingredient.claimSignature.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| informationals { | |
| code: "signingCredential.ocsp.skipped" | |
| url: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.signature" | |
| } | |
| } | |
| } | |
| ingredient_manifests { | |
| label: "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a" | |
| claim { | |
| claim_generator_info { name: "Test C2PA Generator" } | |
| signature_info { | |
| cert_serial_number: "02" | |
| issuer: "OU=Test Unit,O=Test Org,CN=Test Claim Signer" | |
| certificate_issuer: "OU=Test Unit,O=Test Org,CN=Test Root CA" | |
| timestamping_time {} | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.boxes" | |
| algorithm: "sha256" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" | |
| algorithm: "sha256" | |
| } | |
| default_algorithm: "sha256" | |
| label: "c2pa.claim.v2" | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| } | |
| assertions { | |
| label: "c2pa.hash.boxes" | |
| boxes_hash { | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| alg: "some alg" | |
| } | |
| } | |
| assertions { | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.created" | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| software_agent { name: "test generator" } | |
| } | |
| } | |
| } | |
| validation { | |
| successes { | |
| code: "timeStamp.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "timeStamp.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "signingCredential.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.insideValidity" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.boxes" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.actions.v2" | |
| } | |
| informationals { | |
| code: "signingCredential.ocsp.skipped" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| } | |
| } | |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:12345678-1234-1234-1234-123456789012/c2pa.assertions/c2pa.hash.boxes" | |
| )pb"); | |
| absl::StatusOr<GenerateManifestStoreResponse> generated = | |
| GenerateManifestStore(*store_generator_, request); | |
| ABSL_ASSERT_OK(generated); | |
| absl::StatusOr<std::unique_ptr<PartialValidationResultProto>> validated = | |
| ValidateManifestStore(generated->manifest_store()); | |
| ABSL_ASSERT_OK(validated); | |
| EXPECT_THAT(**validated, EqualsProtoIgnoringHashTimestampAndOrdering( | |
| expected_validated_result)); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, | |
| GenerateC2paOpenedActionMissingManifestStoreBytes) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| instance_id: "03020100-0504-0706-0908-0f0e0d0c0b0a" | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "test ingredient" } | |
| ingredient_validation_results {} | |
| } | |
| )pb"); | |
| PartialValidationResultProto expected_validated_result = ParseTextProtoOrDie( | |
| R"pb( | |
| active_manifest { | |
| label: "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a" | |
| claim { | |
| claim_generator_info { | |
| name: "Test generator" | |
| spec_version: "2.2.0" | |
| } | |
| signature_info { | |
| cert_serial_number: "02" | |
| issuer: "OU=Test Unit,O=Test Org,CN=Test Claim Signer" | |
| certificate_issuer: "OU=Test Unit,O=Test Org,CN=Test Root CA" | |
| timestamping_time {} | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.boxes" | |
| algorithm: "sha256" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| algorithm: "sha256" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" | |
| algorithm: "sha256" | |
| } | |
| default_algorithm: "sha256" | |
| label: "c2pa.claim.v2" | |
| instance_id: "03020100-0504-0706-0908-0f0e0d0c0b0a" | |
| } | |
| assertions { | |
| label: "c2pa.hash.boxes" | |
| boxes_hash { | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| alg: "some alg" | |
| } | |
| } | |
| assertions { | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "test ingredient" relationship: "parentOf" } | |
| } | |
| assertions { | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| description: "opened for testing" | |
| software_agent { name: "test generator" } | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| } | |
| validation { | |
| successes { | |
| code: "timeStamp.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "timeStamp.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "signingCredential.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.insideValidity" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.boxes" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.actions.v2" | |
| } | |
| informationals { | |
| code: "signingCredential.ocsp.skipped" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| informationals { | |
| code: "ingredient.unknownProvenance" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.boxes" | |
| )pb"); | |
| absl::StatusOr<GenerateManifestStoreResponse> generated = | |
| GenerateManifestStore(*store_generator_, request); | |
| ABSL_ASSERT_OK(generated); | |
| absl::StatusOr<std::unique_ptr<PartialValidationResultProto>> validated = | |
| ValidateManifestStore(generated->manifest_store()); | |
| ABSL_ASSERT_OK(validated); | |
| EXPECT_THAT(**validated, EqualsProtoIgnoringHashTimestampAndOrdering( | |
| expected_validated_result)); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, | |
| GenerateC2paOpenedActionMissingValidationResultsInvalid) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { title: "test ingredient" } | |
| ingredient_validation_results { | |
| manifest_store_bytes: "test manifest store bytes" | |
| } | |
| } | |
| )pb"); | |
| EXPECT_THAT(GenerateManifestStore(*store_generator_, request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("validation results cannot be empty"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, GenerateMissingHardBindingInvalid) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"); | |
| EXPECT_THAT( | |
| GenerateManifestStore(*store_generator_, request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("hard binding"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, GenerateMissingStartActionInvalid) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| )pb"); | |
| EXPECT_THAT(GenerateManifestStore(*store_generator_, request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("missing inception action"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, GenerateTemplateWithBmffBasedHash) { | |
| BmffBasedHashAssertion bmff_based_hash = | |
| GetBmffHashTemplate(HashAlgorithm::kSha256); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_bmff_based_hash() = bmff_based_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| // Step 1: Generate template. | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| // Step 2: Update hash. | |
| std::string new_hash(HashSizeBytes(HashAlgorithm::kSha256), '\1'); | |
| bmff_based_hash.set_hash(new_hash); | |
| // Step 3: Finalize manifest store. | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_bmff_based_hash() = | |
| bmff_based_hash; | |
| absl::StatusOr<FinalizeManifestStoreResponse> finalized = | |
| FinalizeManifestStore(*store_generator_, finalize_request); | |
| ABSL_ASSERT_OK(finalized); | |
| EXPECT_TRUE(finalized->is_timestamped()); | |
| EXPECT_EQ(manifest_store_template->manifest_store_size(), | |
| finalized->manifest_store().size()); | |
| EXPECT_THAT(ValidateManifestStore(finalized->manifest_store()), IsOk()); | |
| EXPECT_THAT(finalized->manifest_store(), HasSubstr(new_hash)); | |
| }; | |
| TEST_F(ManifestStoreGeneratorTest, GenerateTemplateWithDataHash) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| // Step 1: Generate template. | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| // Step 2: Update hash. | |
| std::string new_hash(HashSizeBytes(HashAlgorithm::kSha256), '\1'); | |
| data_hash.set_hash(new_hash); | |
| // Step 3: Finalize manifest store. | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| absl::StatusOr<FinalizeManifestStoreResponse> finalized = | |
| FinalizeManifestStore(*store_generator_, finalize_request); | |
| ABSL_ASSERT_OK(finalized); | |
| EXPECT_TRUE(finalized->is_timestamped()); | |
| EXPECT_EQ(manifest_store_template->manifest_store_size(), | |
| finalized->manifest_store().size()); | |
| EXPECT_THAT(ValidateManifestStore(finalized->manifest_store()), IsOk()); | |
| EXPECT_THAT(finalized->manifest_store(), HasSubstr(new_hash)); | |
| }; | |
| TEST_F(ManifestStoreGeneratorTest, GenerateTemplateWithBoxesHash) { | |
| BoxesHashAssertion box_hash; | |
| BoxHash* box = box_hash.add_boxes(); | |
| box->add_names("some name"); | |
| box->set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_box_hash() = box_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| // Step 1: Generate template. | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| // Step 2: Update boxes hash. | |
| std::string new_hash(HashSizeBytes(HashAlgorithm::kSha256), '\1'); | |
| box_hash.mutable_boxes(0)->set_hash(new_hash); | |
| // Step 3: Finalize manifest store. | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_box_hash() = box_hash; | |
| absl::StatusOr<FinalizeManifestStoreResponse> finalized = | |
| FinalizeManifestStore(*store_generator_, finalize_request); | |
| ABSL_ASSERT_OK(finalized); | |
| EXPECT_TRUE(finalized->is_timestamped()); | |
| EXPECT_EQ(manifest_store_template->manifest_store_size(), | |
| finalized->manifest_store().size()); | |
| EXPECT_THAT(ValidateManifestStore(finalized->manifest_store()), IsOk()); | |
| EXPECT_THAT(finalized->manifest_store(), HasSubstr(new_hash)); | |
| }; | |
| TEST_F(ManifestStoreGeneratorTest, GenerateTemplateWithCollectionDataHash) { | |
| CollectionDataHashAssertion collection_data_hash; | |
| collection_data_hash.set_zip_central_directory_hash( | |
| std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| collection_data_hash.set_alg("sha256"); | |
| UriHashedData* uri_hash = collection_data_hash.add_uris(); | |
| uri_hash->set_uri("some uri"); | |
| uri_hash->set_type("some type"); | |
| uri_hash->set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| // Step 1: Generate template. | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| // Step 2: Update hash. | |
| std::string new_central_dir_hash(HashSizeBytes(HashAlgorithm::kSha256), '\1'); | |
| collection_data_hash.set_zip_central_directory_hash(new_central_dir_hash); | |
| std::string new_uri_hash(HashSizeBytes(HashAlgorithm::kSha256), '\2'); | |
| collection_data_hash.mutable_uris(0)->set_hash(new_uri_hash); | |
| // Step 3: Finalize manifest store. | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| absl::StatusOr<FinalizeManifestStoreResponse> finalized = | |
| FinalizeManifestStore(*store_generator_, finalize_request); | |
| ABSL_ASSERT_OK(finalized); | |
| EXPECT_TRUE(finalized->is_timestamped()); | |
| EXPECT_EQ(manifest_store_template->manifest_store_size(), | |
| finalized->manifest_store().size()); | |
| EXPECT_THAT(ValidateManifestStore(finalized->manifest_store()), IsOk()); | |
| EXPECT_THAT(finalized->manifest_store(), HasSubstr(new_central_dir_hash)); | |
| EXPECT_THAT(finalized->manifest_store(), HasSubstr(new_uri_hash)); | |
| }; | |
| TEST_F(ManifestStoreGeneratorTest, EmptyGeneratorContext) { | |
| BmffBasedHashAssertion bmff_based_hash = | |
| GetBmffHashTemplate(HashAlgorithm::kSha256); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_hard_binding()->mutable_bmff_based_hash() = | |
| bmff_based_hash; | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("empty"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, FailParsingGeneratorContext) { | |
| BmffBasedHashAssertion bmff_based_hash = | |
| GetBmffHashTemplate(HashAlgorithm::kSha256); | |
| FinalizeManifestStoreRequest finalize_request; | |
| finalize_request.mutable_manifest_store_template()->set_generator_context( | |
| "invalid generator context"); | |
| *finalize_request.mutable_hard_binding()->mutable_bmff_based_hash() = | |
| bmff_based_hash; | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("Failed to parse the generator_context"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, HashBindingMissing) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| EXPECT_THAT( | |
| FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("hard binding"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, HashBindingMismatchDataHash) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_box_hash() = | |
| ParseTextProtoOrDie(R"pb( | |
| boxes { hash: "some hash" } | |
| )pb"); | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The hard binding in the finalize request " | |
| "must match the original request"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, HashBindingMismatchBoxesHash) { | |
| BoxesHashAssertion box_hash; | |
| BoxHash* box = box_hash.add_boxes(); | |
| box->add_names("some name"); | |
| box->set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_box_hash() = box_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| ParseTextProtoOrDie(R"pb( | |
| hash: "some hash" | |
| )pb"); | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The hard binding in the finalize request " | |
| "must match the original request"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, HashBindingMismatchBmffBasedHash) { | |
| BmffBasedHashAssertion bmff_based_hash = | |
| GetBmffHashTemplate(HashAlgorithm::kSha256); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_bmff_based_hash() = bmff_based_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| ParseTextProtoOrDie(R"pb( | |
| hash: "some hash" | |
| )pb"); | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The hard binding in the finalize request " | |
| "must match the original request"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, HashBindingMismatchCollectionDataHash) { | |
| CollectionDataHashAssertion collection_data_hash = ParseTextProtoOrDie(R"pb( | |
| uris { uri: "some uri" hash: "some hash" type: "some type" } | |
| alg: "sha256" | |
| zip_central_directory_hash: "some hash" | |
| )pb"); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| ParseTextProtoOrDie(R"pb( | |
| hash: "some hash" | |
| )pb"); | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The hard binding in the finalize request " | |
| "must match the original request"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, DataHashByteSizeAlwaysMatch) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| DataRange* exclusion = data_hash.add_exclusions(); | |
| exclusion->set_start(0); | |
| exclusion->set_length(0); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| data_hash.set_pad("additional padding"); | |
| data_hash.mutable_exclusions(0)->set_start(10); | |
| data_hash.mutable_exclusions(0)->set_length(4294967296); | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| absl::StatusOr<FinalizeManifestStoreResponse> generated = | |
| FinalizeManifestStore(*store_generator_, finalize_request); | |
| ABSL_ASSERT_OK(generated); | |
| EXPECT_THAT(generated->manifest_store(), | |
| Not(HasSubstr("additional padding"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, DataHashByteSizeMismatchDueToDifferentName) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| DataRange* exclusion = data_hash.add_exclusions(); | |
| exclusion->set_start(0); | |
| exclusion->set_length(0); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| data_hash.clear_pad(); // Padding should add it back. | |
| data_hash.mutable_exclusions(0)->set_start(10); | |
| data_hash.mutable_exclusions(0)->set_length(4294967296); | |
| data_hash.set_name("additional name"); | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The final hard binding differs from template " | |
| "in unsupported ways"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, BoxesHashByteSizeMismatch) { | |
| BoxesHashAssertion box_hash; | |
| BoxHash* box = box_hash.add_boxes(); | |
| box->add_names("some name"); | |
| box->set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_box_hash() = box_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| box_hash.mutable_boxes(0)->add_names("additional name"); | |
| *finalize_request.mutable_hard_binding()->mutable_box_hash() = box_hash; | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The final hard binding differs from template " | |
| "in unsupported ways"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, BmffBasedHashByteSizeMismatch) { | |
| BmffBasedHashAssertion bmff_based_hash = | |
| GetBmffHashTemplate(HashAlgorithm::kSha256); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_bmff_based_hash() = bmff_based_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| bmff_based_hash.add_exclusions()->set_xpath("additional exclusion"); | |
| *finalize_request.mutable_hard_binding()->mutable_bmff_based_hash() = | |
| bmff_based_hash; | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The final hard binding differs from template " | |
| "in unsupported ways"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, | |
| CollectionDataHashByteSizeMismatchDueToDifferentUriLength) { | |
| CollectionDataHashAssertion collection_data_hash = ParseTextProtoOrDie(R"pb( | |
| uris { uri: "some uri" hash: "some hash" type: "some type" } | |
| alg: "sha256" | |
| zip_central_directory_hash: "some hash" | |
| )pb"); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| // The URI is different in length. | |
| collection_data_hash.mutable_uris(0)->set_uri("updated uri"); | |
| *finalize_request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The final hard binding differs from template " | |
| "in unsupported ways"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, | |
| CollectionDataHashByteSizeMismatchDueToDifferentUriType) { | |
| CollectionDataHashAssertion collection_data_hash = ParseTextProtoOrDie(R"pb( | |
| uris { uri: "some uri" hash: "some hash" type: "some type" } | |
| alg: "sha256" | |
| zip_central_directory_hash: "some hash" | |
| )pb"); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| collection_data_hash.mutable_uris(0)->set_type("updated type"); | |
| *finalize_request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The final hard binding differs from template " | |
| "in unsupported ways"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, | |
| CollectionDataHashByteSizeMismatchDueToDifferentUriCount) { | |
| CollectionDataHashAssertion collection_data_hash = ParseTextProtoOrDie(R"pb( | |
| uris { uri: "some uri" hash: "some hash" type: "some type" } | |
| alg: "sha256" | |
| zip_central_directory_hash: "some hash" | |
| )pb"); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| UriHashedData* new_uri = collection_data_hash.add_uris(); | |
| new_uri->set_uri("new uri"); | |
| new_uri->set_hash("new hash"); | |
| new_uri->set_type("new type"); | |
| *finalize_request.mutable_hard_binding()->mutable_collection_data_hash() = | |
| collection_data_hash; | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The final hard binding differs from template " | |
| "in unsupported ways"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, PartBindingByteSizeMismatch) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_hard_binding()->add_part_bindings()->mutable_data_hash() = | |
| data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| data_hash.set_name("additional name"); | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| request.hard_binding().data_hash(); | |
| *finalize_request.mutable_hard_binding() | |
| ->add_part_bindings() | |
| ->mutable_data_hash() = data_hash; | |
| EXPECT_THAT( | |
| FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| HasSubstr( | |
| "The part binding differs from template in unsupported ways."))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, PartBindingCountMismatch) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_hard_binding()->add_part_bindings()->mutable_data_hash() = | |
| data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| request.hard_binding().data_hash(); | |
| // Different number of part_bindings. | |
| EXPECT_THAT(FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The number of part bindings differs between " | |
| "template and finalize."))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, HardBindingNotSet) { | |
| // Create a template with no hard binding in the generate request. | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"); | |
| // Generate a template (this will fail as hard_binding is required). | |
| EXPECT_THAT( | |
| GenerateManifestStoreTemplate(*store_generator_, request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("hard binding"))); | |
| // To test the BINDING_NOT_SET case in FinalizeManifestStore, we need to | |
| // manually create a generator_context with no hard binding. | |
| GenerateManifestStoreRequest template_request = request; | |
| template_request.clear_hard_binding(); | |
| FinalizeManifestStoreRequest finalize_request; | |
| finalize_request.mutable_manifest_store_template()->set_generator_context( | |
| template_request.SerializeAsString()); | |
| *finalize_request.mutable_hard_binding() = HardBinding(); | |
| EXPECT_THAT( | |
| FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| HasSubstr("The hard binding in the finalize request must be set"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, ManifestStoreSizeLimitExceeded) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| options { max_manifest_store_size_bytes: 1000 } | |
| )pb"); | |
| EXPECT_THAT(GenerateManifestStore(*store_generator_, request), | |
| StatusIs(absl::StatusCode::kFailedPrecondition, | |
| HasSubstr("manifest store too large"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, ManifestStoreSizeLimitNotExceeded) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| options { max_manifest_store_size_bytes: 16384 } | |
| )pb"); | |
| ABSL_EXPECT_OK(GenerateManifestStore(*store_generator_, request)); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, PartBindingBmffBasedHashSizeMismatch) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| BmffBasedHashAssertion bmff_based_hash = | |
| GetBmffHashTemplate(HashAlgorithm::kSha256); | |
| *request.mutable_hard_binding() | |
| ->add_part_bindings() | |
| ->mutable_bmff_based_hash() = bmff_based_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| request.hard_binding().data_hash(); | |
| bmff_based_hash.add_exclusions()->set_xpath("additional exclusion"); | |
| *finalize_request.mutable_hard_binding() | |
| ->add_part_bindings() | |
| ->mutable_bmff_based_hash() = bmff_based_hash; | |
| EXPECT_THAT( | |
| FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| HasSubstr( | |
| "The part binding differs from template in unsupported ways."))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, PartBindingBoxesHashSizeMismatch) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| BoxesHashAssertion box_hash; | |
| BoxHash* box = box_hash.add_boxes(); | |
| box->add_names("some name"); | |
| box->set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| *request.mutable_hard_binding()->add_part_bindings()->mutable_box_hash() = | |
| box_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| request.hard_binding().data_hash(); | |
| box_hash.mutable_boxes(0)->add_names("additional name"); | |
| *finalize_request.mutable_hard_binding() | |
| ->add_part_bindings() | |
| ->mutable_box_hash() = box_hash; | |
| EXPECT_THAT( | |
| FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| HasSubstr( | |
| "The part binding differs from template in unsupported ways."))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, PartBindingDataHashSizeMismatch) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| *request.mutable_hard_binding()->add_part_bindings()->mutable_data_hash() = | |
| data_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| request.hard_binding().data_hash(); | |
| data_hash.set_name("additional name"); | |
| *finalize_request.mutable_hard_binding() | |
| ->add_part_bindings() | |
| ->mutable_data_hash() = data_hash; | |
| EXPECT_THAT( | |
| FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| HasSubstr( | |
| "The part binding differs from template in unsupported ways."))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, PartBindingMatchingSizes) { | |
| DataHashAssertion data_hash; | |
| data_hash.set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| data_hash.set_pad(""); | |
| GenerateManifestStoreRequest request; | |
| // Hard binding | |
| *request.mutable_hard_binding()->mutable_data_hash() = data_hash; | |
| // Part binding 1: DataHash | |
| *request.mutable_hard_binding()->add_part_bindings()->mutable_data_hash() = | |
| data_hash; | |
| // Part binding 2: BmffBasedHash | |
| BmffBasedHashAssertion bmff_based_hash = | |
| GetBmffHashTemplate(HashAlgorithm::kSha256); | |
| *request.mutable_hard_binding() | |
| ->add_part_bindings() | |
| ->mutable_bmff_based_hash() = bmff_based_hash; | |
| // Part binding 3: BoxesHash | |
| BoxesHashAssertion box_hash; | |
| BoxHash* box = box_hash.add_boxes(); | |
| box->add_names("some name"); | |
| box->set_hash(std::string(HashSizeBytes(HashAlgorithm::kSha256), '\0')); | |
| *request.mutable_hard_binding()->add_part_bindings()->mutable_box_hash() = | |
| box_hash; | |
| *request.mutable_actions() = ParseTextProtoOrDie(R"pb( | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| )pb"); | |
| absl::StatusOr<ManifestStoreTemplate> manifest_store_template = | |
| GenerateManifestStoreTemplate(*store_generator_, request); | |
| ABSL_ASSERT_OK(manifest_store_template); | |
| FinalizeManifestStoreRequest finalize_request; | |
| *finalize_request.mutable_manifest_store_template() = | |
| *manifest_store_template; | |
| // Set the same hard binding and part bindings in the finalize request. | |
| *finalize_request.mutable_hard_binding() = request.hard_binding(); | |
| // Modify the hashes to ensure finalization works. | |
| std::string new_hash(HashSizeBytes(HashAlgorithm::kSha256), '\1'); | |
| finalize_request.mutable_hard_binding()->mutable_data_hash()->set_hash( | |
| new_hash); | |
| finalize_request.mutable_hard_binding() | |
| ->mutable_part_bindings(0) | |
| ->mutable_data_hash() | |
| ->set_hash(new_hash); | |
| finalize_request.mutable_hard_binding() | |
| ->mutable_part_bindings(1) | |
| ->mutable_bmff_based_hash() | |
| ->set_hash(new_hash); | |
| finalize_request.mutable_hard_binding() | |
| ->mutable_part_bindings(2) | |
| ->mutable_box_hash() | |
| ->mutable_boxes(0) | |
| ->set_hash(new_hash); | |
| ABSL_EXPECT_OK(FinalizeManifestStore(*store_generator_, finalize_request)); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, | |
| GenerateNegativeMaxManifestStoreSizeBytesFails) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| hard_binding { | |
| data_hash { alg: "sha256" hash: "00000000000000000000000000000000" } | |
| } | |
| options { max_manifest_store_size_bytes: -1 } | |
| )pb"); | |
| EXPECT_THAT( | |
| GenerateManifestStore(*store_generator_, request), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| HasSubstr("max_manifest_store_size_bytes must be non-negative"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, NegativeMaxBytesToShiftFails) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { created { action_metadata { description: "Test Action" } } } | |
| hard_binding { | |
| data_hash { alg: "sha256" hash: "00000000000000000000000000000000" } | |
| } | |
| options { max_bytes_to_shift: -1 } | |
| )pb"); | |
| EXPECT_THAT(GenerateManifestStore(*store_generator_, request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("max_bytes_to_shift must be non-negative"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, NonNegativeMaxBytesToShiftSucceeds) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { created { action_metadata { description: "Test Action" } } } | |
| hard_binding { | |
| data_hash { alg: "sha256" hash: "00000000000000000000000000000000" } | |
| } | |
| options { max_bytes_to_shift: 0 } | |
| )pb"); | |
| ABSL_EXPECT_OK(GenerateManifestStore(*store_generator_, request)); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, FinalizeNegativeManifestStoreSizeFails) { | |
| FinalizeManifestStoreRequest finalize_request; | |
| finalize_request.mutable_manifest_store_template()->set_generator_context( | |
| "context"); | |
| finalize_request.mutable_manifest_store_template()->set_manifest_store_size( | |
| -1); | |
| *finalize_request.mutable_hard_binding()->mutable_data_hash() = | |
| ParseTextProtoOrDie(R"pb(alg: "sha256" | |
| hash: "00000000000000000000000000000000")pb"); | |
| EXPECT_THAT( | |
| FinalizeManifestStore(*store_generator_, finalize_request), | |
| StatusIs( | |
| absl::StatusCode::kInvalidArgument, | |
| HasSubstr("manifest_store_size in template must be non-negative"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, ManifestStoreTransformerSuccess) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| hard_binding { | |
| data_hash { alg: "sha256" hash: "00000000000000000000000000000000" } | |
| } | |
| )pb"); | |
| MockManifestStoreTransformer mock_transformer; | |
| EXPECT_CALL(mock_transformer, Transform(testing::NotNull())) | |
| .WillOnce([](jumbf::BuiltSuperBox* box) { | |
| EXPECT_THAT(box, testing::NotNull()); | |
| EXPECT_FALSE(box->Peek().empty()); | |
| return absl::OkStatus(); | |
| }); | |
| std::unique_ptr<ManifestStoreGenerator> generator = | |
| CreateGenerator(&mock_transformer); | |
| ABSL_EXPECT_OK(GenerateManifestStore(*generator, request)); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, ManifestStoreTransformerModifiesSuperBox) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { created { action_metadata { description: "Test Action" } } } | |
| hard_binding { | |
| data_hash { alg: "sha256" hash: "00000000000000000000000000000000" } | |
| } | |
| )pb"); | |
| jumbf::SuperBoxBuilder builder(jumbf::kCborBoxTypeUuid, {}); | |
| ABSL_CHECK_OK(builder.AddContent(jumbf::kCborBoxType, | |
| absl::Cord("custom transformed data"))); | |
| absl::StatusOr<jumbf::BuiltSuperBox> transformed_box = | |
| std::move(builder).Finalize(); | |
| ABSL_ASSERT_OK(transformed_box); | |
| std::string expected_transformed_store(transformed_box->Peek()); | |
| MockManifestStoreTransformer mock_transformer; | |
| EXPECT_CALL(mock_transformer, Transform(testing::NotNull())) | |
| .WillOnce([transformed_box = *std::move(transformed_box)]( | |
| jumbf::BuiltSuperBox* box) mutable { | |
| EXPECT_THAT(box, testing::NotNull()); | |
| EXPECT_FALSE(box->Peek().empty()); | |
| *box = std::move(transformed_box); | |
| return absl::OkStatus(); | |
| }); | |
| std::unique_ptr<ManifestStoreGenerator> generator = | |
| CreateGenerator(&mock_transformer); | |
| absl::StatusOr<GenerateManifestStoreResponse> response = | |
| GenerateManifestStore(*generator, request); | |
| ABSL_ASSERT_OK(response); | |
| EXPECT_EQ(response->manifest_store(), expected_transformed_store); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, ManifestStoreTransformerErrorPropagates) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { created { action_metadata { description: "Test Action" } } } | |
| hard_binding { | |
| data_hash { alg: "sha256" hash: "00000000000000000000000000000000" } | |
| } | |
| )pb"); | |
| MockManifestStoreTransformer mock_transformer; | |
| EXPECT_CALL(mock_transformer, Transform(testing::NotNull())) | |
| .WillOnce(testing::Return(absl::InternalError("transform failed"))); | |
| std::unique_ptr<ManifestStoreGenerator> generator = | |
| CreateGenerator(&mock_transformer); | |
| EXPECT_THAT( | |
| GenerateManifestStore(*generator, request), | |
| StatusIs(absl::StatusCode::kInternal, HasSubstr("transform failed"))); | |
| } | |
| TEST_F(ManifestStoreGeneratorTest, | |
| ManifestStoreTransformerTemplateErrorPropagates) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| actions { created { action_metadata { description: "Test Action" } } } | |
| hard_binding { | |
| data_hash { alg: "sha256" hash: "00000000000000000000000000000000" } | |
| } | |
| )pb"); | |
| MockManifestStoreTransformer mock_transformer; | |
| EXPECT_CALL(mock_transformer, Transform(testing::NotNull())) | |
| .WillOnce( | |
| testing::Return(absl::InternalError("transform template failed"))); | |
| std::unique_ptr<ManifestStoreGenerator> generator = | |
| CreateGenerator(&mock_transformer); | |
| EXPECT_THAT(GenerateManifestStoreTemplate(*generator, request), | |
| StatusIs(absl::StatusCode::kInternal, | |
| HasSubstr("transform template failed"))); | |
| } | |
| } // namespace | |
| } // namespace credentio |