blob: 4d9c2b460e13a7b8ed119f42fea886747f3baa9c [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 "assertion/parse_assertion.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/strings/string_view.h"
#include "cbor/parse.h"
#include "google/protobuf/struct.pb.h"
#include "nlohmann/json.hpp"
#include "proto/actions_assertion.cbor.h"
#include "proto/actions_assertion.pb.h"
#include "proto/ai_disclosure_assertion.cbor.h"
#include "proto/ai_disclosure_assertion.pb.h"
#include "proto/assertion.pb.h"
#include "proto/asset_reference_assertion.cbor.h"
#include "proto/asset_reference_assertion.pb.h"
#include "proto/asset_types_assertion.cbor.h"
#include "proto/asset_types_assertion.pb.h"
#include "proto/bmff_based_hash_assertion.cbor.h"
#include "proto/bmff_based_hash_assertion.pb.h"
#include "proto/boxes_hash_assertion.cbor.h"
#include "proto/boxes_hash_assertion.pb.h"
#include "proto/collection_data_hash_assertion.cbor.h"
#include "proto/data_hash_assertion.cbor.h"
#include "proto/data_hash_assertion.pb.h"
#include "proto/environmental_sustainability_assertion.cbor.h"
#include "proto/hashed_uri.pb.h"
#include "proto/ingredient_assertion.cbor.h"
#include "proto/ingredient_assertion.pb.h"
#include "proto/metadata_assertion.pb.h"
#include "proto/multi_asset_hash_assertion.cbor.h"
#include "proto/repository_receipt_assertion.pb.h"
#include "proto/soft_binding_assertion.cbor.h"
#include "proto/soft_binding_assertion.pb.h"
#include "proto/thumbnail_assertion.pb.h"
namespace credentio {
namespace {
using Json = ::nlohmann::json;
absl::Status ParseListValueHelper(const Json& json,
google::protobuf::ListValue* proto);
absl::Status ParseValueHelper(const Json& json, google::protobuf::Value* proto);
absl::Status ParseStructHelper(const Json& json,
google::protobuf::Struct* proto);
absl::Status ParseListValueHelper(const Json& json,
google::protobuf::ListValue* proto) {
for (const auto& wrapped_value : json.items()) {
// The parsed JSON has a redundant object wrapper for each list item.
// We only need the beginning item as it contains the index and the
// actual value.
const auto& [_, value] = wrapped_value.value().items().begin();
google::protobuf::Value* proto_value = proto->add_values();
ABSL_RETURN_IF_ERROR(ParseValueHelper(value, proto_value));
}
return absl::OkStatus();
}
absl::Status ParseValueHelper(const Json& json,
google::protobuf::Value* proto) {
if (json.is_null()) {
proto->set_null_value(google::protobuf::NULL_VALUE);
return absl::OkStatus();
}
if (json.is_number()) {
proto->set_number_value(json.get<double>());
return absl::OkStatus();
}
if (json.is_string()) {
proto->set_string_value(json.get<std::string>());
return absl::OkStatus();
}
if (json.is_boolean()) {
proto->set_bool_value(json.get<bool>());
return absl::OkStatus();
}
if (json.is_object()) {
return ParseStructHelper(json, proto->mutable_struct_value());
}
if (json.is_array()) {
return ParseListValueHelper(json, proto->mutable_list_value());
}
return absl::InvalidArgumentError("Should not happen");
}
absl::Status ParseStructHelper(const Json& json,
google::protobuf::Struct* proto) {
for (const auto& [key, value] : json.items()) {
google::protobuf::Value proto_value;
ABSL_RETURN_IF_ERROR(ParseValueHelper(value, &proto_value));
proto->mutable_fields()->insert({key, proto_value});
}
return absl::OkStatus();
}
template <typename T>
absl::Status ParseAssertionHelper(absl::string_view assertion, T* proto) {
ABSL_ASSIGN_OR_RETURN(auto item, cbor::Parse(assertion));
ABSL_ASSIGN_OR_RETURN(auto map, item->AsMap());
return cbor::ToProto(map, proto);
}
template <typename T>
void ClearPaddingFields(T* proto) {
proto->clear_pad();
proto->clear_pad2();
}
std::optional<std::string> GetJsonString(const Json& obj,
absl::string_view key) {
if (auto it = obj.find(key); it != obj.end() && it->is_string()) {
return it->get<std::string>();
}
return std::nullopt;
}
const Json* GetJsonObject(const Json& obj, absl::string_view key) {
if (auto it = obj.find(key); it != obj.end() && it->is_object()) {
return &(*it);
}
return nullptr;
}
} // namespace
#define C2PA_DEFINE_CBOR_ASSERTION_PARSER(ProtoType) \
absl::Status ParseAssertion(absl::string_view assertion, ProtoType* proto) { \
return ParseAssertionHelper(assertion, proto); \
}
C2PA_DEFINE_CBOR_ASSERTION_PARSER(ActionsAssertion)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(BmffBasedHashAssertion)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(IngredientAssertionV3)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(AssetReferenceAssertion)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(AssetTypesAssertion)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(BoxesHashAssertion)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(MultiAssetHashAssertion)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(CollectionDataHashAssertion)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(AiDisclosureAssertion)
C2PA_DEFINE_CBOR_ASSERTION_PARSER(EnvironmentalSustainabilityAssertion)
#undef C2PA_DEFINE_CBOR_ASSERTION_PARSER
absl::Status ParseAssertion(absl::string_view assertion,
ActionsAssertionV1* proto) {
ABSL_RETURN_IF_ERROR(ParseAssertionHelper(assertion, proto));
// Move v1 parameters field `ingredient` to v2 field `ingredients`.
for (auto& action : *proto->mutable_actions()) {
auto* params = action.mutable_parameters();
if (params->has_ingredient()) {
params->add_ingredients()->Swap(params->mutable_ingredient());
params->clear_ingredient();
}
}
return absl::OkStatus();
}
absl::Status ParseAssertion(absl::string_view assertion,
DataHashAssertion* proto) {
ABSL_RETURN_IF_ERROR(ParseAssertionHelper(assertion, proto));
// Clear irrelevant padding fields (for internal generator use only).
ClearPaddingFields(proto);
return absl::OkStatus();
}
absl::Status ParseAssertion(absl::string_view assertion,
SoftBindingAssertion* proto) {
ABSL_RETURN_IF_ERROR(ParseAssertionHelper(assertion, proto));
// Clear irrelevant padding fields (for internal generator use only).
ClearPaddingFields(proto);
return absl::OkStatus();
}
absl::Status ParseAssertion(absl::string_view assertion,
RepositoryReceiptAssertion* proto) {
auto json = Json::parse(assertion, nullptr, false);
if (json.is_discarded()) {
return absl::InvalidArgumentError("JSON parsing failed");
}
if (!json.is_object()) {
return absl::InvalidArgumentError("Assertion is not a JSON object");
}
if (const Json* repo_json = GetJsonObject(json, "repository")) {
auto* repository = proto->mutable_repository();
if (auto uri = GetJsonString(*repo_json, "uri")) {
repository->set_uri(*std::move(uri));
}
if (auto manifest_id = GetJsonString(*repo_json, "manifestId")) {
repository->set_manifest_id(*std::move(manifest_id));
}
}
if (const Json* anchor_json = GetJsonObject(json, "anchor")) {
auto* anchor = proto->mutable_anchor();
if (auto uri = GetJsonString(*anchor_json, "uri")) {
anchor->set_uri(*std::move(uri));
}
if (const Json* params = GetJsonObject(*anchor_json, "parameters")) {
ABSL_RETURN_IF_ERROR(
ParseStructHelper(*params, anchor->mutable_parameters()));
}
if (const Json* proof = GetJsonObject(*anchor_json, "proof")) {
ABSL_RETURN_IF_ERROR(ParseStructHelper(*proof, anchor->mutable_proof()));
}
}
return absl::OkStatus();
}
ThumbnailAssertion ParseThumbnailAssertion(
absl::string_view media_type, absl::string_view data,
std::optional<absl::string_view> file_name) {
ThumbnailAssertion proto;
proto.set_media_type(media_type);
proto.set_data(data);
if (file_name.has_value()) {
proto.set_file_name(*file_name);
}
return proto;
}
absl::Status ParseStruct(absl::string_view content,
google::protobuf::Struct* proto) {
Json result = Json::parse(content.begin(), content.end(), /*cb=*/nullptr,
/*allow_exceptions=*/false);
if (result.is_discarded()) {
return absl::InvalidArgumentError("Invalid JSON: could not parse");
}
return ParseStructHelper(result, proto);
}
} // namespace credentio