| // 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 "bindings/validator.h" |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <iterator> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/base/nullability.h" |
| #include "absl/container/flat_hash_map.h" |
| #include "absl/functional/function_ref.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 "bindings/bmff_hash_hard_binding_validator.h" |
| #include "bindings/boxes_hash_hard_binding_validator.h" |
| #include "bindings/collection_data_hash_hard_binding_validator.h" |
| #include "bindings/data_hash_hard_binding_validator.h" |
| #include "constants/status_codes.h" |
| #include "formats/asset_box.h" |
| #include "formats/bmff/assessor.h" |
| #include "formats/bmff/box_header.h" |
| #include "formats/byte_range.h" |
| #include "formats/format.h" |
| #include "formats/registry.h" |
| #include "jumbf/uri.h" |
| #include "proto/assertion.pb.h" |
| #include "proto/bmff_based_hash_assertion.pb.h" |
| #include "proto/hashed_uri.pb.h" |
| #include "proto/manifest.pb.h" |
| #include "proto/multi_asset_hash_assertion.pb.h" |
| #include "proto/validation_result.pb.h" |
| #include "proto/validation_status.pb.h" |
| #include "riegeli/bytes/reader.h" |
| #include "utils/dual_status_tracker.h" |
| #include "utils/status_tracker.h" |
| #include "utils/two_stage_status_tracker.h" |
| #include "utils/uri.h" |
| #include "validator/result.h" |
| #include "validator/validation_result_internal.h" |
| |
| namespace credentio { |
| |
| namespace { |
| |
| struct OffsetLocator { |
| int64_t offset; |
| int64_t length; |
| }; |
| |
| struct BmffLocator { |
| std::string path; |
| }; |
| |
| absl::StatusOr<std::variant<OffsetLocator, BmffLocator>> GetLocator( |
| const MultiAssetPartLocation& part) { |
| if (!part.has_byte_offset() && !part.has_length() && !part.has_bmff_box()) { |
| return absl::InvalidArgumentError("no location specified"); |
| } |
| if (part.has_bmff_box() && (part.has_byte_offset() || part.has_length())) { |
| return absl::InvalidArgumentError( |
| "bmff box cannot be used with byte range locator"); |
| } |
| if (part.has_bmff_box()) { |
| return BmffLocator{.path = std::string(part.bmff_box())}; |
| } |
| if (!part.has_byte_offset() || !part.has_length()) { |
| return absl::InvalidArgumentError( |
| "byte range locator must have both offset and length"); |
| } |
| return OffsetLocator{.offset = part.byte_offset(), .length = part.length()}; |
| } |
| |
| std::unique_ptr<PartialValidationResultProto> CopyManifestLabels( |
| const PartialValidationResultProto& source) { |
| auto target = std::make_unique<PartialValidationResultProto>(); |
| target->set_hard_binding_uri(source.hard_binding_uri()); |
| target->set_multi_asset_hash_uri(source.multi_asset_hash_uri()); |
| |
| target->mutable_active_manifest()->set_label( |
| source.active_manifest().label()); |
| for (const Manifest& manifest : source.ingredient_manifests()) { |
| target->add_ingredient_manifests()->set_label(manifest.label()); |
| } |
| return target; |
| } |
| |
| absl::StatusOr<std::unique_ptr<ValidationResultProto>> |
| CompleteValidationResults( |
| std::unique_ptr<PartialValidationResultProto> target, |
| std::unique_ptr<PartialValidationResultProto> source) { |
| target->mutable_active_manifest()->mutable_validation()->MergeFrom( |
| std::move(*source->mutable_active_manifest()->mutable_validation())); |
| |
| absl::flat_hash_map<absl::string_view, ValidationStatusSet> source_results; |
| for (const Manifest& source_manifest : source->ingredient_manifests()) { |
| source_results[source_manifest.label()] = |
| std::move(source_manifest.validation()); |
| } |
| for (Manifest& target_manifest : *target->mutable_ingredient_manifests()) { |
| if (source_results.contains(target_manifest.label())) { |
| target_manifest.mutable_validation()->MergeFrom( |
| std::move(source_results[target_manifest.label()])); |
| } |
| } |
| return MakeFullValidationResult(std::move(target)); |
| } |
| |
| // Validates the parts of a multi-asset hash assertion. This ensures that the |
| // parts do not overlap and that the total size of the required parts does not |
| // exceed the size of the asset. Only problems accessing the files return as |
| // non-OK status, other errors are recorded in the status tracker. |
| absl::Status ValidateParts(const MultiAssetHashAssertion& assertion, |
| absl::string_view assertion_uri, int64_t asset_size, |
| StatusTracker& tracker) { |
| int64_t next_expected_offset = 0; |
| int64_t minimum_required_size = 0; |
| for (const auto& part : assertion.parts()) { |
| absl::StatusOr<std::variant<OffsetLocator, BmffLocator>> locator = |
| GetLocator(part.location()); |
| if (!locator.ok()) { |
| tracker.RecordFailure( |
| FailureStatusCode::kAssertionMultiAssetHashMalformed, |
| {.url = assertion_uri}); |
| return absl::OkStatus(); |
| } |
| if (std::holds_alternative<BmffLocator>(*locator)) { |
| // Contains a BMFF locator, cannot ensure bytes included based on offsets. |
| return absl::OkStatus(); |
| } |
| OffsetLocator location = std::get<OffsetLocator>(*locator); |
| if (location.offset != next_expected_offset) { |
| tracker.RecordFailure( |
| FailureStatusCode::kAssertionMultiAssetHashMalformed, |
| {.url = assertion_uri}); |
| return absl::OkStatus(); |
| } |
| next_expected_offset = location.offset + location.length; |
| if (!part.is_optional()) { |
| minimum_required_size += location.length; |
| } |
| } |
| if (minimum_required_size > asset_size) { |
| tracker.RecordFailure( |
| FailureStatusCode::kAssertionMultiAssetHashMissingPart, |
| {.url = assertion_uri}); |
| } |
| return absl::OkStatus(); |
| } |
| |
| } // namespace |
| |
| absl::StatusOr<ContentBindingValidator::Structure> |
| ContentBindingValidator::ExtractAssetStructure(riegeli::Reader& contents, |
| const Format* format, |
| int64_t end_offset) const { |
| ContentBindingValidator::Structure structure{ |
| .asset = ByteRange{.offset = contents.pos(), |
| .length = end_offset - contents.pos()}}; |
| |
| absl::StatusOr<const Format*> inferred_format; |
| if (format == nullptr) { |
| inferred_format = format_registry_->GetFormat(contents); |
| if (!inferred_format.ok()) { |
| if (structure.asset.offset == 0) { |
| // Invalid format on the first asset therefore unsupported format. |
| return inferred_format.status(); |
| } |
| return structure; |
| } |
| format = *inferred_format; |
| } |
| |
| absl::StatusOr<std::vector<AssetBox>> boxes = |
| format->extractor()->ExtractBoxes(contents, {.requires_c2pa = false}); |
| if (boxes.ok() && !boxes->empty()) { |
| ABSL_ASSIGN_OR_RETURN( |
| int64_t end_of_asset_offset, |
| PopulateStructureFromBoxes(*std::move(boxes), structure)); |
| |
| // Only assets which contain boxes can support multiple assets (by finding |
| // the c2pa.after box). Navigate to the start of the next potential asset |
| // and continue to extract the structure. |
| if (!contents.Seek(end_of_asset_offset) || |
| contents.pos() != end_of_asset_offset) { |
| return contents.StatusOrAnnotate( |
| absl::InternalError("failed to seek to end of asset")); |
| } |
| return structure; |
| } |
| |
| // The determined format does not support multiple assets at this point, |
| // attempt to find the manifest store location and treat the remainder of |
| // the file as the last asset. |
| absl::StatusOr<std::optional<ByteRange>> manifest_store_location = |
| format->extractor()->ExtractManifestStoreLocation( |
| contents, {.requires_c2pa = false, .end_offset = end_offset}); |
| if (manifest_store_location.ok() && manifest_store_location->has_value()) { |
| structure.manifest_store_location = *std::move(manifest_store_location); |
| } |
| |
| return structure; |
| } |
| |
| absl::StatusOr<int64_t> ContentBindingValidator::PopulateStructureFromBoxes( |
| std::vector<AssetBox> boxes, |
| ContentBindingValidator::Structure& structure) const { |
| if (boxes.empty()) { |
| return absl::InvalidArgumentError("no boxes found"); |
| } |
| |
| structure.boxes = std::move(boxes); |
| |
| const AssetBox* last_box = nullptr; |
| for (const AssetBox& box : structure.boxes) { |
| last_box = &box; |
| |
| if (box.identifier == "C2PA") { |
| structure.manifest_store_location = ByteRange{ |
| .offset = box.byte_range.offset, .length = box.byte_range.length}; |
| } |
| } |
| |
| if (last_box != nullptr && last_box->identifier == "c2pa.after") { |
| structure.asset.length = |
| last_box->byte_range.offset - structure.asset.offset; |
| } |
| |
| return structure.asset.offset + structure.asset.length; |
| } |
| |
| absl::StatusOr<std::vector<ContentBindingValidator::Structure>> |
| ContentBindingValidator::GetStructure(riegeli::Reader& contents, |
| const Format& format) const { |
| std::vector<ContentBindingValidator::Structure> structures; |
| |
| if (!contents.SupportsSize() || !contents.Size().has_value()) { |
| return absl::InvalidArgumentError("asset does not support size"); |
| } |
| |
| if (contents.Size() == 0) { |
| return absl::InvalidArgumentError("asset has no contents"); |
| } |
| |
| if (!contents.Seek(0) || contents.pos() != 0) { |
| return contents.StatusOrAnnotate( |
| absl::InternalError("failed to seek to start of asset")); |
| } |
| |
| uint64_t end_offset = *contents.Size(); |
| bool at_end_of_file = false; |
| while (!at_end_of_file) { |
| // Format is always inferred for non-first parts. |
| const Format* part_format = structures.empty() ? &format : nullptr; |
| ABSL_ASSIGN_OR_RETURN( |
| ContentBindingValidator::Structure structure, |
| ExtractAssetStructure(contents, part_format, end_offset)); |
| |
| at_end_of_file = |
| (structure.asset.offset + structure.asset.length) == end_offset; |
| |
| structures.push_back(std::move(structure)); |
| } |
| |
| return structures; |
| } |
| |
| absl::StatusOr<std::unique_ptr<ValidationResultProto>> |
| ContentBindingValidator::Validate(riegeli::Reader& contents, |
| const Format& format, |
| std::unique_ptr<PartialValidationResultProto> |
| partial_validation_result) const { |
| std::unique_ptr<PartialValidationResultProto> result = |
| CopyManifestLabels(*partial_validation_result); |
| ABSL_ASSIGN_OR_RETURN( |
| std::unique_ptr<DualStatusTracker> tracker, |
| DualStatusTracker::FromPartialValidationResult(&*result)); |
| bool assertion_in_ingredient_manifest = tracker->WritesToIngredientManifest(); |
| TwoStageStatusTracker staging_tracker(*tracker); |
| |
| ABSL_ASSIGN_OR_RETURN( |
| std::vector<ContentBindingValidator::Structure> asset_structure, |
| GetStructure(contents, format)); |
| auto assertion_fetcher = [&](absl::string_view uri) { |
| return GetAssertion(partial_validation_result.get(), uri); |
| }; |
| // Getting the structure causes the Tell to be at the end of the file, reset. |
| if (!contents.Seek(0) || contents.pos() != 0) { |
| return contents.StatusOrAnnotate( |
| absl::InternalError("failed to seek to start of asset")); |
| } |
| ABSL_RETURN_IF_ERROR(ValidateAssertion( |
| contents, assertion_fetcher, |
| partial_validation_result->hard_binding_uri(), asset_structure, |
| /*requires_c2pa=*/true, staging_tracker, |
| assertion_in_ingredient_manifest)); |
| |
| if (!staging_tracker.HasFailuresInStage1() || |
| partial_validation_result->multi_asset_hash_uri().empty()) { |
| // Success or no multi-asset hard binding, finished. |
| staging_tracker.WriteStage1(); |
| return CompleteValidationResults(std::move(partial_validation_result), |
| std::move(result)); |
| } |
| |
| staging_tracker.MoveStage1ToStage2(); |
| |
| // Validate the multi-asset hard binding assertion. |
| if (!contents.Seek(0) || contents.pos() != 0) { |
| return contents.StatusOrAnnotate( |
| absl::InternalError("failed to seek to start of asset")); |
| } |
| ABSL_RETURN_IF_ERROR(ValidateAssertion( |
| contents, assertion_fetcher, |
| partial_validation_result->multi_asset_hash_uri(), asset_structure, |
| /*requires_c2pa=*/true, staging_tracker, |
| assertion_in_ingredient_manifest)); |
| if (staging_tracker.HasFailuresInStage1()) { |
| // Failed, add all the staged codes to the result. |
| staging_tracker.WriteStage2(); |
| } |
| staging_tracker.WriteStage1(); |
| return CompleteValidationResults(std::move(partial_validation_result), |
| std::move(result)); |
| } |
| |
| absl::Status ContentBindingValidator::ValidateAssertion( |
| riegeli::Reader& contents, AssertionFetcherRef assertion_fetcher, |
| absl::string_view assertion_uri, |
| std::vector<ContentBindingValidator::Structure> asset_structure, |
| bool requires_c2pa, TwoStageStatusTracker& tracker, |
| bool assertion_in_ingredient_manifest, int64_t end_offset) const { |
| const Assertion* absl_nullable assertion = assertion_fetcher(assertion_uri); |
| if (assertion == nullptr) { |
| return absl::InvalidArgumentError( |
| absl::StrCat("missing assertion: ", assertion_uri)); |
| } |
| |
| switch (assertion->assertion_case()) { |
| case Assertion::kBoxesHash: |
| if (asset_structure.empty()) { |
| return absl::InvalidArgumentError( |
| "asset structure cannot be determined"); |
| } |
| return ValidateBoxesHash(contents, assertion_uri, assertion->boxes_hash(), |
| asset_structure, requires_c2pa, tracker, |
| end_offset); |
| case Assertion::kDataHash: |
| return ValidateDataHash( |
| contents, assertion_uri, assertion->data_hash(), |
| asset_structure.empty() |
| ? std::nullopt |
| : asset_structure.front().manifest_store_location, |
| requires_c2pa, tracker, assertion_in_ingredient_manifest, end_offset); |
| case Assertion::kCollectionDataHash: |
| return ValidateCollectionDataHash(contents, assertion_uri, |
| assertion->collection_data_hash(), |
| requires_c2pa, tracker); |
| case Assertion::kBmffBasedHash: |
| return ValidateBmffHash(contents, assertion_uri, |
| assertion->bmff_based_hash(), requires_c2pa, |
| tracker); |
| case Assertion::kMultiAssetHash: |
| return ValidateMultiAssetHash( |
| contents, assertion_uri, assertion->multi_asset_hash(), |
| asset_structure, requires_c2pa, assertion_fetcher, tracker, |
| assertion_in_ingredient_manifest); |
| default: |
| return absl::InvalidArgumentError( |
| absl::StrCat("assertion is not a hard binding assertion: ", |
| assertion->assertion_case())); |
| } |
| } |
| |
| absl::Status ContentBindingValidator::ValidateDataHash( |
| riegeli::Reader& contents, absl::string_view assertion_uri, |
| const DataHashAssertion& assertion, |
| std::optional<ByteRange> manifest_store_location, bool requires_c2pa, |
| StatusTracker& tracker, bool assertion_in_ingredient_manifest, |
| int64_t end_offset) const { |
| DataHashHardBindingValidator().Validate( |
| contents, manifest_store_location, assertion, assertion_uri, tracker, |
| assertion_in_ingredient_manifest, contents.pos(), end_offset); |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ContentBindingValidator::ValidateCollectionDataHash( |
| riegeli::Reader& contents, absl::string_view assertion_uri, |
| const CollectionDataHashAssertion& assertion, bool requires_c2pa, |
| StatusTracker& tracker) const { |
| CollectionDataHashHardBindingValidator().Validate(contents, assertion, |
| assertion_uri, tracker); |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ContentBindingValidator::ValidateBoxesHash( |
| riegeli::Reader& contents, absl::string_view assertion_uri, |
| const BoxesHashAssertion& assertion, |
| std::vector<ContentBindingValidator::Structure> remaining_structure, |
| bool requires_c2pa, StatusTracker& tracker, int64_t end_offset) const { |
| if (assertion.boxes().empty()) { |
| // No boxes in assertion. |
| tracker.RecordFailure(FailureStatusCode::kAssertionBoxesHashMalformed, |
| {.url = assertion_uri}); |
| return absl::OkStatus(); |
| } |
| for (const auto& box : assertion.boxes()) { |
| if (box.names().empty()) { |
| // Boxes Assertion has no names. |
| tracker.RecordFailure(FailureStatusCode::kAssertionBoxesHashMalformed, |
| {.url = assertion_uri}); |
| return absl::OkStatus(); |
| } |
| } |
| bool assertion_has_c2pa_after = |
| *assertion.boxes().rbegin()->names().rbegin() == "c2pa.after"; |
| |
| if (remaining_structure.empty()) { |
| // No Asset Data. |
| tracker.RecordFailure(FailureStatusCode::kAssertionBoxesHashMismatch, |
| {.url = assertion_uri}); |
| return absl::OkStatus(); |
| } |
| std::vector<AssetBox> asset_boxes = remaining_structure.front().boxes; |
| if (asset_boxes.empty()) { |
| // No boxes in asset. |
| tracker.RecordFailure(FailureStatusCode::kAssertionBoxesHashMismatch, |
| {.url = assertion_uri}); |
| return absl::OkStatus(); |
| } |
| bool extraction_has_c2pa_after = |
| asset_boxes.back().identifier == "c2pa.after"; |
| |
| if (!assertion_has_c2pa_after && extraction_has_c2pa_after) { |
| int64_t part_idx = 1; |
| while (part_idx < remaining_structure.size() && |
| !remaining_structure[part_idx].boxes.empty()) { |
| // Remove the c2pa.after box. |
| asset_boxes.pop_back(); |
| |
| // Add the boxes from the next asset. |
| Structure& next_asset = remaining_structure[part_idx]; |
| asset_boxes.reserve(asset_boxes.size() + next_asset.boxes.size()); |
| asset_boxes.insert(asset_boxes.end(), next_asset.boxes.begin(), |
| next_asset.boxes.end()); |
| |
| // Increment the index to the next asset. |
| ++part_idx; |
| } |
| } |
| |
| BoxesHashHardBindingValidator().Validate(contents, asset_boxes, assertion, |
| assertion_uri, tracker); |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ContentBindingValidator::ValidateBmffHash( |
| riegeli::Reader& contents, absl::string_view assertion_uri, |
| const BmffBasedHashAssertion& assertion, bool requires_c2pa, |
| StatusTracker& tracker) const { |
| BmffHashHardBindingValidator().Validate(contents, assertion, assertion_uri, |
| tracker); |
| return absl::OkStatus(); |
| } |
| |
| absl::Status ContentBindingValidator::ValidateMultiAssetHash( |
| riegeli::Reader& contents, absl::string_view assertion_uri, |
| const MultiAssetHashAssertion& assertion, |
| std::vector<ContentBindingValidator::Structure> asset_structure, |
| bool requires_c2pa, AssertionFetcherRef assertion_fetcher, |
| TwoStageStatusTracker& tracker, |
| bool assertion_in_ingredient_manifest) const { |
| bool recorded_multi_asset_failure = false; |
| int64_t max_offset_validated = 0; |
| |
| if (!contents.SupportsSize() || !contents.Size().has_value()) { |
| return absl::InvalidArgumentError( |
| "multi-asset hash requires an asset with a known size"); |
| } |
| uint64_t asset_size = *contents.Size(); |
| |
| // multi-asset hash should drop all `c2pa.after` boxes |
| for (auto& structure : asset_structure) { |
| if (!structure.boxes.empty()) { |
| if (structure.boxes.back().identifier == "c2pa.after") { |
| structure.boxes.pop_back(); |
| } |
| } |
| } |
| |
| ABSL_RETURN_IF_ERROR( |
| ValidateParts(assertion, assertion_uri, asset_size, tracker)); |
| if (tracker.HasFailuresInStage1()) { |
| // No need to validate if we already have failures. |
| return absl::OkStatus(); |
| } |
| |
| TwoStageStatusTracker multi_asset_tracker( |
| *static_cast<StatusTracker*>(&tracker)); |
| |
| for (int part_index = 0; part_index < assertion.parts_size(); ++part_index) { |
| const auto& part = assertion.parts(part_index); |
| ABSL_ASSIGN_OR_RETURN(auto locator, GetLocator(part.location())); |
| |
| std::optional<int64_t> offset = std::nullopt; |
| std::optional<int64_t> length = std::nullopt; |
| |
| if (std::holds_alternative<OffsetLocator>(locator)) { |
| OffsetLocator location = std::get<OffsetLocator>(locator); |
| offset = location.offset; |
| length = location.length; |
| } else { |
| BmffLocator location = std::get<BmffLocator>(locator); |
| |
| if (!contents.Seek(0) || contents.pos() != 0) { |
| return contents.StatusOrAnnotate( |
| absl::InternalError("failed to seek to start of asset")); |
| } |
| ABSL_ASSIGN_OR_RETURN(bool is_supported, |
| BmffAssessor().IsSupported(contents)); |
| if (!is_supported) { |
| return absl::InvalidArgumentError( |
| "Uses a BMFF locator, but file is not a BMFF file"); |
| } |
| ABSL_RETURN_IF_ERROR(IterateOverBmffBoxes( |
| contents, [&location, &offset, &length](const BmffBoxHeader& header) { |
| if (header.xpath == location.path) { |
| offset = header.start; |
| length = header.box_size; |
| return false; // Terminate loop |
| } |
| return true; // Continue |
| })); |
| if (!offset.has_value()) { |
| // Did not find the location |
| multi_asset_tracker.RecordFailure( |
| FailureStatusCode::kAssertionMultiAssetHashMalformed, |
| {.url = assertion_uri}); |
| recorded_multi_asset_failure = true; |
| } |
| } |
| |
| if (!offset.has_value() || !length.has_value()) { |
| multi_asset_tracker.RecordFailure( |
| FailureStatusCode::kAssertionMultiAssetHashMissingPart, |
| {.url = assertion_uri, |
| .explanation = |
| absl::Substitute("missing location for part $0 of $1", |
| part_index + 1, assertion.parts_size())}); |
| recorded_multi_asset_failure = true; |
| if (part.is_optional()) { |
| multi_asset_tracker.MoveStage1ToStage2(); |
| } else { |
| multi_asset_tracker.WriteStage1(); |
| } |
| requires_c2pa = false; |
| continue; |
| } |
| |
| if (!contents.Seek(*offset) || contents.pos() != *offset || |
| (*offset + *length) > asset_size) { |
| multi_asset_tracker.RecordFailure( |
| FailureStatusCode::kAssertionMultiAssetHashMissingPart, |
| {.url = assertion_uri, |
| .explanation = |
| absl::Substitute("missing data for part $0 of $1", |
| part_index + 1, assertion.parts_size())}); |
| if (part.is_optional()) { |
| multi_asset_tracker.MoveStage1ToStage2(); |
| } else { |
| multi_asset_tracker.WriteStage1(); |
| } |
| requires_c2pa = false; |
| continue; |
| } |
| |
| ABSL_ASSIGN_OR_RETURN(absl::string_view manifest_label, |
| GetManifestLabelFromAbsoluteUri(assertion_uri)); |
| ABSL_ASSIGN_OR_RETURN(std::string part_hash_assertion_path, |
| jumbf::UriResolver::GetAbsolutePathFromUri( |
| part.hash_assertion().url(), |
| absl::StrCat("/c2pa/", manifest_label))); |
| std::string part_hash_assertion_uri = |
| absl::StrCat("self#jumbf=", part_hash_assertion_path); |
| |
| std::vector<ContentBindingValidator::Structure> part_structure = |
| (asset_structure.size() - 1) < part_index |
| ? std::vector<ContentBindingValidator::Structure>{} |
| : std::vector<ContentBindingValidator::Structure>( |
| asset_structure.begin() + part_index, asset_structure.end()); |
| ABSL_RETURN_IF_ERROR( |
| ValidateAssertion(contents, assertion_fetcher, part_hash_assertion_uri, |
| part_structure, requires_c2pa, multi_asset_tracker, |
| assertion_in_ingredient_manifest, *offset + *length)); |
| |
| if (part.is_optional()) { |
| if (multi_asset_tracker.HasFailuresInStage1()) { |
| // Optional, but failed, move to stage 2. |
| multi_asset_tracker.MoveStage1ToStage2(); |
| } else { |
| // Optional, but succeeded, write. |
| multi_asset_tracker.WriteStage1(); |
| max_offset_validated = |
| std::max(max_offset_validated, *offset + *length); |
| } |
| } else { |
| if (multi_asset_tracker.HasFailuresInStage1()) { |
| // Encountered a failure, but not optional, write all status codes. |
| multi_asset_tracker.WriteStage2(); |
| } |
| multi_asset_tracker.WriteStage1(); |
| max_offset_validated = std::max(max_offset_validated, *offset + *length); |
| } |
| |
| // We only need to ensure C2PA exists on the first part. |
| requires_c2pa = false; |
| } |
| |
| if (!recorded_multi_asset_failure && max_offset_validated != asset_size) { |
| // Less than the whole file was validated, if the whole file was read it's |
| // a mismatch, otherwise it's malformed. |
| multi_asset_tracker.RecordFailure( |
| contents.pos() == asset_size |
| ? FailureStatusCode::kAssertionMultiAssetHashMismatch |
| : FailureStatusCode::kAssertionMultiAssetHashMalformed, |
| {.url = assertion_uri}); |
| recorded_multi_asset_failure = true; |
| } |
| |
| // Write all status codes to the tracker. |
| multi_asset_tracker.WriteStage1(); |
| if (tracker.HasFailuresInStage1()) { |
| // Failures were added during validation, write all stage two codes. |
| multi_asset_tracker.WriteStage2(); |
| if (!recorded_multi_asset_failure) { |
| tracker.RecordFailure(FailureStatusCode::kAssertionMultiAssetHashMismatch, |
| {.url = assertion_uri}); |
| } |
| } else { |
| tracker.RecordSuccess(SuccessStatusCode::kAssertionMultiAssetHashMatch, |
| {.url = assertion_uri}); |
| } |
| return absl::OkStatus(); |
| } |
| |
| } // namespace credentio |