| // 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 "validator/ingredient_result_converter.h" | |
| #include <cstddef> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/container/flat_hash_set.h" | |
| #include "absl/hash/hash.h" | |
| #include "absl/status/status_macros.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/strip.h" | |
| #include "google/protobuf/repeated_ptr_field.h" | |
| #include "proto/assertion.pb.h" | |
| #include "proto/generator_info.pb.h" | |
| #include "proto/hashed_uri.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 "proto/validation_result.pb.h" | |
| #include "proto/validation_status.pb.h" | |
| #include "validator/ingredient_result.h" | |
| #include "validator/manifest_consumer.h" | |
| #include "validator/result.h" | |
| namespace credentio { | |
| namespace { | |
| bool IsLegacyManifestLabel(absl::string_view manifest_label) { | |
| return !absl::StartsWith(manifest_label, "urn:c2pa"); | |
| } | |
| std::string GetAssertionUri(absl::string_view manifest_label, | |
| const Assertion& assertion) { | |
| return absl::StrCat("self#jumbf=/c2pa/", manifest_label, "/c2pa.assertions/", | |
| assertion.label()); | |
| } | |
| std::optional<std::string> ExtractTargetManifestLabel( | |
| const IngredientAssertionV3& ingredient_v3) { | |
| if (ingredient_v3.has_active_manifest()) { | |
| absl::string_view url = ingredient_v3.active_manifest().url(); | |
| if (absl::ConsumePrefix(&url, "self#jumbf=/c2pa/")) { | |
| return std::string(url); | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| struct ValidationStatusHash { | |
| size_t operator()(const ValidationStatus& status) const { | |
| return absl::HashOf(status.code(), status.url()); | |
| } | |
| }; | |
| struct ValidationStatusEqual { | |
| bool operator()(const ValidationStatus& lhs, | |
| const ValidationStatus& rhs) const { | |
| return lhs.code() == rhs.code() && lhs.url() == rhs.url(); | |
| } | |
| }; | |
| using ValidationStatusHashSet = | |
| absl::flat_hash_set<ValidationStatus, ValidationStatusHash, | |
| ValidationStatusEqual>; | |
| ValidationStatusHashSet GetIngredientAssertionValidationStatus( | |
| const Assertion& assertion) { | |
| ValidationStatusHashSet statuses; | |
| if (assertion.has_ingredient_v3()) { | |
| const ValidationStatusSet& assertion_results = | |
| assertion.ingredient_v3().validation_results().active_manifest(); | |
| statuses.insert(assertion_results.successes().begin(), | |
| assertion_results.successes().end()); | |
| statuses.insert(assertion_results.failures().begin(), | |
| assertion_results.failures().end()); | |
| statuses.insert(assertion_results.informationals().begin(), | |
| assertion_results.informationals().end()); | |
| } | |
| return statuses; | |
| } | |
| bool IsHardBindingSuccessCode(absl::string_view code) { | |
| return code == "com.google.assertion.dataHash.match" || | |
| code == "com.google.assertion.bmffHash.match" || | |
| code == "com.google.assertion.boxesHash.match" || | |
| code == "com.google.assertion.collectionHash.match" || | |
| code == "assertion.dataHash.match" || | |
| code == "assertion.bmffHash.match" || | |
| code == "assertion.boxesHash.match" || | |
| code == "assertion.collectionHash.match"; | |
| } | |
| google::protobuf::RepeatedPtrField<ValidationStatus> ComputeStatusDeltas( | |
| const google::protobuf::RepeatedPtrField<ValidationStatus>& current, | |
| const ValidationStatusHashSet& assertion) { | |
| google::protobuf::RepeatedPtrField<ValidationStatus> deltas; | |
| for (const auto& status : current) { | |
| if (!assertion.contains(status) || | |
| IsHardBindingSuccessCode(status.code())) { | |
| ValidationStatus* delta = deltas.Add(); | |
| delta->set_code(status.code()); | |
| delta->set_url(status.url()); | |
| } | |
| } | |
| return deltas; | |
| } | |
| ValidationStatusSet ComputeValidationDeltas( | |
| const ValidationStatusSet& current_results, | |
| const ValidationStatusHashSet& assertion_results) { | |
| ValidationStatusSet status_set_deltas; | |
| *status_set_deltas.mutable_successes() = | |
| ComputeStatusDeltas(current_results.successes(), assertion_results); | |
| *status_set_deltas.mutable_failures() = | |
| ComputeStatusDeltas(current_results.failures(), assertion_results); | |
| *status_set_deltas.mutable_informationals() = | |
| ComputeStatusDeltas(current_results.informationals(), assertion_results); | |
| return status_set_deltas; | |
| } | |
| struct ReferencingIngredientAssertion { | |
| const Assertion* assertion; | |
| std::string assertion_uri; | |
| }; | |
| } // namespace | |
| absl::StatusOr<std::unique_ptr<IngredientValidationResultProto>> | |
| IngredientResultConverter::ConvertProto( | |
| const ValidationResultProto& validation_result) const { | |
| auto result = std::make_unique<IngredientValidationResultProto>(); | |
| if (!validation_result.media_type().empty()) { | |
| result->set_media_type(validation_result.media_type()); | |
| } | |
| if (validation_result.active_manifest().has_validation()) { | |
| *result->mutable_ingredient_validation_results() | |
| ->mutable_active_manifest() = | |
| validation_result.active_manifest().validation(); | |
| } | |
| if (!validation_result.spec_version().empty()) { | |
| result->mutable_ingredient_validation_results()->set_spec_version( | |
| validation_result.spec_version()); | |
| } | |
| if (!validation_result.trust_list_uri().empty()) { | |
| result->mutable_ingredient_validation_results()->set_trust_list_uri( | |
| validation_result.trust_list_uri()); | |
| } | |
| // Populate logging_metadata. | |
| auto* active_manifest_info = | |
| result->mutable_logging_metadata()->mutable_active_manifest(); | |
| if (validation_result.active_manifest().claim().has_claim_generator_info()) { | |
| *active_manifest_info->mutable_claim_generator() = | |
| validation_result.active_manifest().claim().claim_generator_info(); | |
| } | |
| if (validation_result.active_manifest().claim().has_signature_info()) { | |
| *active_manifest_info->mutable_signature() = | |
| validation_result.active_manifest().claim().signature_info(); | |
| } | |
| // Populate legacy_ingredient_metadata. | |
| if (IsLegacyManifestLabel(validation_result.active_manifest().label())) { | |
| auto* legacy_metadata = result->mutable_legacy_ingredient_metadata(); | |
| legacy_metadata->set_is_legacy_ingredient(true); | |
| legacy_metadata->add_unsupported_manifests( | |
| validation_result.active_manifest().label()); | |
| } | |
| // Populate ingredient_deltas. | |
| absl::flat_hash_map<std::string, std::vector<ReferencingIngredientAssertion>> | |
| referencing_assertions; | |
| auto process_manifest = [&](const Manifest& m) { | |
| for (const auto& a : m.assertions()) { | |
| if (a.has_ingredient_v3()) { | |
| auto target_label = ExtractTargetManifestLabel(a.ingredient_v3()); | |
| if (target_label.has_value()) { | |
| referencing_assertions[*target_label].push_back( | |
| {.assertion = &a, | |
| .assertion_uri = GetAssertionUri(m.label(), a)}); | |
| } | |
| } | |
| } | |
| }; | |
| process_manifest(validation_result.active_manifest()); | |
| for (const auto& m : validation_result.ingredient_manifests()) { | |
| process_manifest(m); | |
| } | |
| for (const auto& m : validation_result.ingredient_manifests()) { | |
| auto it = referencing_assertions.find(m.label()); | |
| if (it != referencing_assertions.end()) { | |
| for (const auto& ref : it->second) { | |
| auto* delta = result->mutable_ingredient_validation_results() | |
| ->add_ingredient_deltas(); | |
| delta->set_ingredient_assertion_uri(ref.assertion_uri); | |
| *delta->mutable_validation_deltas() = ComputeValidationDeltas( | |
| m.validation(), | |
| GetIngredientAssertionValidationStatus(*ref.assertion)); | |
| if (IsLegacyManifestLabel(m.label())) { | |
| auto* legacy_metadata = result->mutable_legacy_ingredient_metadata(); | |
| legacy_metadata->add_unsupported_manifests(m.label()); | |
| legacy_metadata->add_unsupported_ingredient_assertion_uris( | |
| ref.assertion_uri); | |
| } | |
| } | |
| } else { | |
| if (IsLegacyManifestLabel(m.label())) { | |
| auto* legacy_metadata = result->mutable_legacy_ingredient_metadata(); | |
| legacy_metadata->add_unsupported_manifests(m.label()); | |
| } | |
| } | |
| } | |
| // Let the consumer populate the fields the converter does not derive itself, | |
| // e.g. digital_source_type. | |
| if (consumer_ != nullptr) { | |
| ABSL_RETURN_IF_ERROR(consumer_->OnManifestValidated( | |
| validation_result.active_manifest(), /*is_active_manifest=*/true)); | |
| for (const auto& m : validation_result.ingredient_manifests()) { | |
| ABSL_RETURN_IF_ERROR( | |
| consumer_->OnManifestValidated(m, /*is_active_manifest=*/false)); | |
| } | |
| ABSL_RETURN_IF_ERROR( | |
| consumer_->OnIngredientValidationResultComplete(result.get())); | |
| } | |
| return result; | |
| } | |
| absl::StatusOr<std::unique_ptr<IngredientResult>> | |
| IngredientResultConverter::Convert(ValidationResult validation_result) const { | |
| ABSL_ASSIGN_OR_RETURN(std::unique_ptr<IngredientValidationResultProto> proto, | |
| ConvertProto(validation_result.proto())); | |
| if (!validation_result.manifest_store_bytes().empty()) { | |
| proto->set_manifest_store_bytes( | |
| validation_result.release_manifest_store_bytes()); | |
| } | |
| return std::make_unique<IngredientResult>(std::move(proto)); | |
| } | |
| } // namespace credentio |