| // Copyright 2026 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // | |
| #include "claim/manifest_box_assembler.h" | |
| #include <cstdint> | |
| #include <optional> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/container/flat_hash_map.h" | |
| #include "absl/container/flat_hash_set.h" | |
| #include "absl/log/die_if_null.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_macros.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/strings/substitute.h" | |
| #include "claim/manifest_box.h" | |
| #include "claim/manifest_store_builder.h" | |
| #include "constants/ingredient_relationships.h" | |
| #include "constants/labels.h" | |
| #include "constants/spec_version.h" | |
| #include "proto/actions_assertion.pb.h" | |
| #include "proto/assertion.pb.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_assertion.pb.h" | |
| #include "proto/ingredient_validation_result.pb.h" | |
| #include "proto/multi_asset_hash_assertion.pb.h" | |
| #include "utils/cbor_padding.h" | |
| namespace credentio { | |
| namespace { | |
| absl::Status ValidateRequest(const GenerateManifestStoreRequest& request) { | |
| if (request.hard_binding().binding_case() == HardBinding::BINDING_NOT_SET) { | |
| return absl::InvalidArgumentError( | |
| "The request does not have a hard binding."); | |
| } | |
| for (const auto& gathered : request.gathered_assertions()) { | |
| if (gathered.label().empty()) { | |
| return absl::InvalidArgumentError( | |
| "Gathered assertion must have a non-empty label."); | |
| } | |
| if (gathered.content_type() == | |
| GatheredAssertion::CONTENT_TYPE_UNSPECIFIED) { | |
| return absl::InvalidArgumentError( | |
| "Gathered assertion content type must be specified."); | |
| } | |
| } | |
| return absl::OkStatus(); | |
| } | |
| void ConvertActionMetadata(const ActionMetadata& action_metadata, | |
| ActionItem* action_item) { | |
| if (action_metadata.has_software_agent()) { | |
| *action_item->mutable_software_agent() = action_metadata.software_agent(); | |
| } | |
| if (action_metadata.has_description()) { | |
| action_item->set_description(action_metadata.description()); | |
| } | |
| if (action_metadata.has_multiple_instances()) { | |
| action_item->mutable_parameters()->set_multiple_instances( | |
| action_metadata.multiple_instances()); | |
| } | |
| } | |
| absl::StatusOr<Assertion> ConvertIngredientAssertion( | |
| const IngredientInput& ingredient, absl::string_view label, | |
| absl::string_view relationship, | |
| const std::optional<IngredientManifestUris>& ingredient_uris, | |
| const std::optional<IngredientValidationResults>& | |
| ingredient_validation_results, | |
| const std::optional<std::string>& digital_source_type) { | |
| Assertion assertion; | |
| // Convert Assertion box fields. | |
| assertion.set_label(label); | |
| auto* ingredient_v3 = assertion.mutable_ingredient_v3(); | |
| // Convert Ingredient Assertion V3 fields. | |
| if (ingredient.metadata().has_title()) { | |
| ingredient_v3->set_title(ingredient.metadata().title()); | |
| } | |
| if (ingredient.metadata().has_media_type()) { | |
| ingredient_v3->set_media_type(ingredient.metadata().media_type()); | |
| } else if (ingredient.ingredient_validation_results().has_media_type()) { | |
| ingredient_v3->set_media_type( | |
| ingredient.ingredient_validation_results().media_type()); | |
| } | |
| ingredient_v3->set_relationship(relationship); | |
| if (ingredient.metadata().has_instance_id()) { | |
| ingredient_v3->set_instance_id(ingredient.metadata().instance_id()); | |
| } | |
| if (ingredient.metadata().has_description()) { | |
| ingredient_v3->set_description(ingredient.metadata().description()); | |
| } | |
| if (ingredient.metadata().has_informational_uri()) { | |
| ingredient_v3->set_informational_uri( | |
| ingredient.metadata().informational_uri()); | |
| } | |
| if (ingredient_uris.has_value()) { | |
| // The ingredient URIs provided by the ManifestStoreBuilder should always | |
| // have the active manifest and claim signature URIs set. | |
| if (ingredient_uris->active_manifest.url().empty()) { | |
| return absl::InternalError( | |
| "Should not happen. Ingredient URI missing active manifest."); | |
| } | |
| *ingredient_v3->mutable_active_manifest() = | |
| ingredient_uris->active_manifest; | |
| if (ingredient_uris->claim_signature.url().empty()) { | |
| return absl::InternalError( | |
| "Should not happen. Ingredient URI missing claim signature."); | |
| } | |
| *ingredient_v3->mutable_claim_signature() = | |
| ingredient_uris->claim_signature; | |
| } | |
| if (ingredient_validation_results.has_value()) { | |
| *ingredient_v3->mutable_validation_results() = | |
| ingredient_validation_results.value(); | |
| } | |
| if (digital_source_type.has_value()) { | |
| ingredient_v3->set_digital_source_type(*digital_source_type); | |
| } | |
| return assertion; | |
| } | |
| // An ingredient listed in a GenerateManifestStoreRequest. | |
| class Ingredient { | |
| public: | |
| explicit Ingredient(std::string label, const IngredientInput* input) | |
| : label_(std::move(label)), input_(*ABSL_DIE_IF_NULL(input)) {} | |
| absl::Status SetRelationship(absl::string_view relationship) { | |
| if (relationship_.empty()) { | |
| relationship_ = relationship; | |
| } else if (relationship != relationship_) { | |
| return absl::InvalidArgumentError( | |
| absl::Substitute("Inconsistent use of ingredient ($0 and $1)", | |
| relationship_, relationship)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::string_view label() const { return label_; } | |
| absl::string_view relationship() const { return relationship_; } | |
| const IngredientInput& input() const { return input_; } | |
| private: | |
| const std::string label_; | |
| const IngredientInput& input_; | |
| absl::string_view relationship_; | |
| }; | |
| // Creates an assertion label per | |
| // https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_multiple_instances. | |
| std::string CreateLabel(absl::string_view base_label, int index) { | |
| if (index == 0) { | |
| return std::string(base_label); | |
| } | |
| return absl::StrCat(base_label, "__", index); | |
| } | |
| class PartLabelProvider { | |
| public: | |
| absl::StatusOr<std::string> GetLabel(PartBinding::BindingCase binding_case) { | |
| absl::string_view base_label; | |
| switch (binding_case) { | |
| case PartBinding::kBmffBasedHash: | |
| base_label = kBmffBasedHashAssertionV3PartLabel; | |
| break; | |
| case PartBinding::kBoxHash: | |
| base_label = kBoxesHashAssertionPartLabel; | |
| break; | |
| case PartBinding::kDataHash: | |
| base_label = kDataHashAssertionPartLabel; | |
| break; | |
| default: | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Unsupported assertion type: ", binding_case)); | |
| } | |
| return CreateLabel(base_label, assertion_type_to_count_[base_label]++); | |
| } | |
| private: | |
| absl::flat_hash_map<absl::string_view, int32_t> assertion_type_to_count_; | |
| }; | |
| absl::Status AddMultiAssetHashToManifestBox( | |
| const GenerateManifestStoreRequest& request, ManifestBox& manifest_box) { | |
| if (request.hard_binding().part_bindings_size() == 0) { | |
| // Doesn't have any multi-asset hash assertion information. | |
| return absl::OkStatus(); | |
| } | |
| Assertion& multi_asset_hash_assertion = | |
| manifest_box.multi_part_assertion.emplace(); | |
| multi_asset_hash_assertion.set_label(kMultiAssetHashAssertionLabel); | |
| PartLabelProvider part_label_provider; | |
| for (const auto& part : request.hard_binding().part_bindings()) { | |
| Assertion& part_hash_assertion = | |
| manifest_box.part_hash_assertions.emplace_back(); | |
| ABSL_ASSIGN_OR_RETURN(std::string label, | |
| part_label_provider.GetLabel(part.binding_case())); | |
| part_hash_assertion.set_label(std::move(label)); | |
| switch (part.binding_case()) { | |
| case PartBinding::kBmffBasedHash: | |
| *part_hash_assertion.mutable_bmff_based_hash() = part.bmff_based_hash(); | |
| break; | |
| case PartBinding::kBoxHash: | |
| *part_hash_assertion.mutable_boxes_hash() = part.box_hash(); | |
| PadBoxesHashAssertion(*part_hash_assertion.mutable_boxes_hash()); | |
| break; | |
| case PartBinding::kDataHash: | |
| *part_hash_assertion.mutable_data_hash() = part.data_hash(); | |
| PadDataHashAssertion(*part_hash_assertion.mutable_data_hash()); | |
| break; | |
| default: | |
| return absl::InvalidArgumentError( | |
| absl::StrCat("Unsupported assertion type: ", part.binding_case())); | |
| } | |
| MultiAssetPart& multi_asset_part = | |
| *multi_asset_hash_assertion.mutable_multi_asset_hash()->add_parts(); | |
| multi_asset_part.mutable_location()->set_byte_offset( | |
| part.location().byte_offset()); | |
| multi_asset_part.mutable_location()->set_length(part.location().length()); | |
| multi_asset_part.mutable_hash_assertion()->set_url(absl::StrCat( | |
| "self#jumbf=c2pa.assertions/", part_hash_assertion.label())); | |
| multi_asset_part.set_is_optional(part.is_optional()); | |
| } | |
| PadMultiAssetHashAssertion( | |
| *multi_asset_hash_assertion.mutable_multi_asset_hash()); | |
| return absl::OkStatus(); | |
| } | |
| } // namespace | |
| // A single manifest box assembly operation. | |
| class StandardManifestBoxAssembler::AssembleOp { | |
| public: | |
| explicit AssembleOp(const StandardManifestBoxAssembler& assembler, | |
| const GenerateManifestStoreRequest& request) | |
| : assembler_(assembler), request_(request) { | |
| ingredients_.reserve(request.ingredients_size()); | |
| for (int i = 0; i < request.ingredients_size(); ++i) { | |
| ingredients_.push_back( | |
| Ingredient(CreateLabel(kIngredientAssertionV3Label, i), | |
| &request.ingredients(i))); | |
| } | |
| } | |
| absl::StatusOr<ManifestBox> Run() { | |
| ABSL_RETURN_IF_ERROR(ValidateRequest(request_)); | |
| ManifestBox manifest_box = {.manifest_store_builder = ManifestStoreBuilder( | |
| &assembler_.hash_factory_)}; | |
| manifest_box.label = absl::StrCat( | |
| "urn:c2pa:", assembler_.uuid_generator_.Generate().ToString()); | |
| if (request_.has_instance_id()) { | |
| manifest_box.instance_id = request_.instance_id(); | |
| } else { | |
| manifest_box.instance_id = | |
| assembler_.uuid_generator_.Generate().ToString(); | |
| } | |
| // Convert hard binding assertion. | |
| { | |
| Assertion& hard_binding = manifest_box.hard_binding_assertion; | |
| if (request_.hard_binding().has_bmff_based_hash()) { | |
| hard_binding.set_label(kBmffBasedHashAssertionV3Label); | |
| *hard_binding.mutable_bmff_based_hash() = | |
| request_.hard_binding().bmff_based_hash(); | |
| } else if (request_.hard_binding().has_box_hash()) { | |
| hard_binding.set_label(kBoxesHashAssertionLabel); | |
| *hard_binding.mutable_boxes_hash() = request_.hard_binding().box_hash(); | |
| PadBoxesHashAssertion(*hard_binding.mutable_boxes_hash()); | |
| } else if (request_.hard_binding().has_data_hash()) { | |
| hard_binding.set_label(kDataHashAssertionLabel); | |
| *hard_binding.mutable_data_hash() = request_.hard_binding().data_hash(); | |
| PadDataHashAssertion(*hard_binding.mutable_data_hash()); | |
| } else if (request_.hard_binding().has_collection_data_hash()) { | |
| hard_binding.set_label(kCollectionDataHashAssertionLabel); | |
| *hard_binding.mutable_collection_data_hash() = | |
| request_.hard_binding().collection_data_hash(); | |
| } | |
| } | |
| ABSL_RETURN_IF_ERROR( | |
| AddMultiAssetHashToManifestBox(request_, manifest_box)); | |
| // Process inception action. | |
| Assertion& actions_assertion = manifest_box.actions_assertion; | |
| ActionsAssertion& actions = *actions_assertion.mutable_actions(); | |
| std::optional<int> opened_ingredient_index = std::nullopt; | |
| { | |
| actions_assertion.set_label(kActionsAssertionV2Label); | |
| if (request_.actions().has_all_actions_included()) { | |
| actions.set_all_actions_included( | |
| request_.actions().all_actions_included()); | |
| } | |
| if (request_.actions().has_created()) { | |
| ABSL_RETURN_IF_ERROR(ConvertActionWithInputIngredients( | |
| "c2pa.created", request_.actions().created(), | |
| actions.add_actions())); | |
| } else if (request_.actions().has_opened()) { | |
| OpenedAction opened_action = request_.actions().opened(); | |
| ABSL_RETURN_IF_ERROR( | |
| ConvertOpenedAction(opened_action, actions.add_actions())); | |
| opened_ingredient_index = opened_action.ingredient_index(); | |
| } else { | |
| return absl::InvalidArgumentError( | |
| "missing inception action (created or opened)"); | |
| } | |
| } | |
| // Process remaining actions. | |
| for (const auto& action : request_.actions().actions()) { | |
| switch (action.action_case()) { | |
| case Action::kAddedText: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.addedText", action.added_text(), actions.add_actions())); | |
| break; | |
| case Action::kAdjustedColor: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.adjustedColor", action.adjusted_color(), | |
| actions.add_actions())); | |
| break; | |
| case Action::kChangedSpeed: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.changedSpeed", action.changed_speed(), | |
| actions.add_actions())); | |
| break; | |
| case Action::kConverted: | |
| ABSL_RETURN_IF_ERROR(ConvertSimpleAction( | |
| "c2pa.converted", action.converted(), actions.add_actions())); | |
| break; | |
| case Action::kCropped: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.cropped", action.cropped(), actions.add_actions())); | |
| break; | |
| case Action::kDeleted: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.deleted", action.deleted(), actions.add_actions())); | |
| break; | |
| case Action::kDrawing: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.drawing", action.drawing(), actions.add_actions())); | |
| break; | |
| case Action::kDubbed: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.dubbed", action.dubbed(), actions.add_actions())); | |
| break; | |
| case Action::kEdited: | |
| ABSL_RETURN_IF_ERROR(ConvertActionWithInputIngredients( | |
| "c2pa.edited", action.edited(), actions.add_actions())); | |
| break; | |
| case Action::kEditedMetadata: | |
| ABSL_RETURN_IF_ERROR(ConvertSimpleAction("c2pa.edited.metadata", | |
| action.edited_metadata(), | |
| actions.add_actions())); | |
| break; | |
| case Action::kEnhanced: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.enhanced", action.enhanced(), actions.add_actions())); | |
| break; | |
| case Action::kFiltered: | |
| ABSL_RETURN_IF_ERROR(ConvertActionWithInputIngredients( | |
| "c2pa.filtered", action.filtered(), actions.add_actions())); | |
| break; | |
| case Action::kOrientation: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.orientation", action.orientation(), actions.add_actions())); | |
| break; | |
| case Action::kPlaced: | |
| ABSL_RETURN_IF_ERROR( | |
| ConvertPlacedAction(action.placed(), actions.add_actions())); | |
| break; | |
| case Action::kRepackaged: | |
| ABSL_RETURN_IF_ERROR(ConvertRepackagedAction( | |
| "c2pa.repackaged", action.repackaged(), actions.add_actions(), | |
| opened_ingredient_index)); | |
| break; | |
| case Action::kResized: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.resized", action.resized(), actions.add_actions())); | |
| break; | |
| case Action::kTranscoded: | |
| ABSL_RETURN_IF_ERROR(ConvertTranscodedAction( | |
| "c2pa.transcoded", action.transcoded(), actions.add_actions(), | |
| opened_ingredient_index)); | |
| break; | |
| case Action::kTrimmed: | |
| ABSL_RETURN_IF_ERROR(ConvertDigitalSourceTypeAction( | |
| "c2pa.trimmed", action.trimmed(), actions.add_actions())); | |
| break; | |
| case Action::kUnknown: | |
| ABSL_RETURN_IF_ERROR(ConvertSimpleAction( | |
| "c2pa.unknown", action.unknown(), actions.add_actions())); | |
| break; | |
| case Action::ACTION_NOT_SET: | |
| return absl::InvalidArgumentError("empty action"); | |
| } | |
| } | |
| // Add redaction actions from ingredients. | |
| absl::flat_hash_set<absl::string_view> seen_redaction_uris; | |
| for (const auto& ingredient : ingredients_) { | |
| if (ingredient.input().has_ingredient_validation_results()) { | |
| const IngredientValidationResultProto& result = | |
| ingredient.input().ingredient_validation_results(); | |
| for (const auto& redaction : result.redactions()) { | |
| // Currently, duplicate manifests are skipped in order of iteration | |
| // over `ingredients_`. | |
| if (!seen_redaction_uris.insert(redaction.uri()).second) { | |
| continue; | |
| } | |
| ActionItem* action = actions.add_actions(); | |
| action->set_action("c2pa.redacted"); | |
| action->set_reason(redaction.reason()); | |
| action->mutable_parameters()->set_redacted_uri(redaction.uri()); | |
| } | |
| } | |
| } | |
| // Import ingredients. | |
| for (const auto& ingredient : ingredients_) { | |
| ABSL_RETURN_IF_ERROR( | |
| ImportIngredient(ingredient, manifest_box.ingredient_assertions, | |
| manifest_box.manifest_store_builder)); | |
| } | |
| // Copy gathered assertions. | |
| manifest_box.gathered_assertions.assign( | |
| request_.gathered_assertions().begin(), | |
| request_.gathered_assertions().end()); | |
| return manifest_box; | |
| } | |
| private: | |
| absl::StatusOr<Ingredient*> GetIngredient(int ingredient_index) { | |
| if (ingredient_index < 0 || ingredient_index >= ingredients_.size()) { | |
| return absl::InvalidArgumentError( | |
| absl::Substitute("ingredient index out of range [0, $0]: $1", | |
| ingredients_.size(), ingredient_index)); | |
| } | |
| return &ingredients_[ingredient_index]; | |
| } | |
| absl::Status AddIngredientToAction(int ingredient_index, | |
| absl::string_view relationship, | |
| ActionItem* action_item) { | |
| ABSL_ASSIGN_OR_RETURN(Ingredient * ingredient, | |
| GetIngredient(ingredient_index)); | |
| ABSL_RETURN_IF_ERROR(ingredient->SetRelationship(relationship)); | |
| action_item->mutable_parameters()->add_ingredients()->set_url( | |
| absl::StrCat(kAssertionStoreUrlPrefix, ingredient->label())); | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ConvertOpenedAction(const OpenedAction& action, | |
| ActionItem* action_item) { | |
| ABSL_RETURN_IF_ERROR( | |
| ConvertSimpleAction("c2pa.opened", action, action_item)); | |
| if (!action.has_ingredient_index()) { | |
| return absl::InvalidArgumentError( | |
| "Opened action requires an ingredient index."); | |
| } | |
| return AddIngredientToAction(action.ingredient_index(), | |
| kIngredientRelationshipParentOf, action_item); | |
| } | |
| absl::Status ConvertPlacedAction(const PlacedAction& action, | |
| ActionItem* action_item) { | |
| ABSL_RETURN_IF_ERROR( | |
| ConvertSimpleAction("c2pa.placed", action, action_item)); | |
| if (action.ingredient_indexes_size() == 0) { | |
| return absl::InvalidArgumentError("missing ingredients in placed action"); | |
| } | |
| for (int ingredient_index : action.ingredient_indexes()) { | |
| ABSL_RETURN_IF_ERROR(AddIngredientToAction( | |
| ingredient_index, kIngredientRelationshipComponentOf, action_item)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ConvertTranscodedAction( | |
| absl::string_view name, const TranscodedAction& action, | |
| ActionItem* action_item, std::optional<int> opened_ingredient_index) { | |
| ABSL_RETURN_IF_ERROR( | |
| ConvertDigitalSourceTypeAction(name, action, action_item)); | |
| if (opened_ingredient_index.has_value()) { | |
| // We add this redundant ingredient reference to satisfy 2.1 validators, | |
| // which require an ingredient reference in c2pa.transcoded and | |
| // c2pa.repackaged actions. | |
| return AddIngredientToAction(*opened_ingredient_index, | |
| kIngredientRelationshipParentOf, | |
| action_item); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status ConvertRepackagedAction( | |
| absl::string_view name, const RepackagedAction& action, | |
| ActionItem* action_item, std::optional<int> opened_ingredient_index) { | |
| if (!opened_ingredient_index.has_value()) { | |
| return absl::InvalidArgumentError( | |
| absl::Substitute("$0 action requires an `opened` action.", name)); | |
| } | |
| ABSL_RETURN_IF_ERROR(ConvertSimpleAction(name, action, action_item)); | |
| // We add this redundant ingredient reference to satisfy 2.1 validators, | |
| // which require an ingredient reference in c2pa.transcoded and | |
| // c2pa.repackaged actions. | |
| return AddIngredientToAction(*opened_ingredient_index, | |
| kIngredientRelationshipParentOf, action_item); | |
| } | |
| // CreatedAction, EditedAction, FilteredAction. | |
| template <typename T> | |
| absl::Status ConvertActionWithInputIngredients(absl::string_view name, | |
| const T& action, | |
| ActionItem* action_item) { | |
| ABSL_RETURN_IF_ERROR( | |
| ConvertDigitalSourceTypeAction(name, action, action_item)); | |
| for (int ingredient_index : action.input_ingredient_indexes()) { | |
| ABSL_RETURN_IF_ERROR(AddIngredientToAction( | |
| ingredient_index, kIngredientRelationshipInputTo, action_item)); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| template <typename T> | |
| absl::Status ConvertDigitalSourceTypeAction(absl::string_view name, | |
| const T& action, | |
| ActionItem* action_item) { | |
| ABSL_RETURN_IF_ERROR(ConvertSimpleAction(name, action, action_item)); | |
| if (action.has_digital_source_type()) { | |
| action_item->set_digital_source_type(action.digital_source_type()); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| template <typename T> | |
| absl::Status ConvertSimpleAction(absl::string_view name, const T& action, | |
| ActionItem* action_item) { | |
| action_item->set_action(name); | |
| if (action.has_action_metadata()) { | |
| ConvertActionMetadata(action.action_metadata(), action_item); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| // Imports an ingredient's manifest store and creates an ingredient | |
| // assertion. | |
| absl::Status ImportIngredient(const Ingredient& ingredient, | |
| std::vector<Assertion>& ingredient_assertions, | |
| ManifestStoreBuilder& manifest_store_builder) { | |
| if (ingredient.relationship().empty()) { | |
| return absl::InvalidArgumentError( | |
| "ingredient is not referenced by any action."); | |
| } | |
| std::optional<IngredientManifestUris> ingredient_manifest_uris; | |
| std::optional<IngredientValidationResults> ingredient_validation_results; | |
| std::optional<std::string> digital_source_type; | |
| if (ingredient.input().has_ingredient_validation_results()) { | |
| const IngredientValidationResultProto& result = | |
| ingredient.input().ingredient_validation_results(); | |
| if (result.has_legacy_ingredient_metadata()) { | |
| // Treat ingredients with unsupported manifests like ingredients without | |
| // C2PA at all. | |
| } else if (result.has_manifest_store_bytes()) { | |
| if (!result.has_ingredient_validation_results()) { | |
| return absl::InvalidArgumentError( | |
| "Ingredient manifest store validation results " | |
| "cannot be empty."); | |
| } | |
| ABSL_ASSIGN_OR_RETURN(ingredient_manifest_uris, | |
| manifest_store_builder.AddIngredientManifestStore( | |
| result.manifest_store_bytes()), | |
| _ << "adding ingredient manifest store"); | |
| ingredient_validation_results = result.ingredient_validation_results(); | |
| } else if (result.has_digital_source_type() && | |
| assembler_.spec_version_ >= SpecVersion::kC2pa_2_4) { | |
| digital_source_type = result.digital_source_type(); | |
| } | |
| } | |
| ABSL_ASSIGN_OR_RETURN( | |
| Assertion ingredient_assertion, | |
| ConvertIngredientAssertion( | |
| ingredient.input(), ingredient.label(), ingredient.relationship(), | |
| ingredient_manifest_uris, ingredient_validation_results, | |
| digital_source_type)); | |
| ingredient_assertions.push_back(ingredient_assertion); | |
| return absl::OkStatus(); | |
| } | |
| const StandardManifestBoxAssembler& assembler_; | |
| const GenerateManifestStoreRequest& request_; | |
| std::vector<Ingredient> ingredients_; | |
| }; | |
| absl::StatusOr<ManifestBox> StandardManifestBoxAssembler::Assemble( | |
| const GenerateManifestStoreRequest& request) const { | |
| AssembleOp op(*this, request); | |
| return op.Run(); | |
| } | |
| } // namespace credentio |