blob: 83d042cc2da083d81658da7d4ff5711cbe99a462 [file] [edit]
// 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