| // 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/bmff_binding_hasher.h" | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/base/nullability.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "bindings/binding_hasher.h" | |
| #include "formats/bmff/box_header.h" | |
| #include "formats/bmff/box_matcher.h" | |
| #include "proto/bmff_based_hash_assertion.pb.h" | |
| #include "riegeli/bytes/reader.h" | |
| #include "utils/byte_writers.h" | |
| namespace credentio { | |
| namespace { | |
| std::string Uint64ToBytes(uint64_t value) { | |
| std::vector<uint8_t> bytes; | |
| bytes.reserve(sizeof(uint64_t)); | |
| WriteUint64NetworkOrder(value, &bytes); | |
| return std::string(reinterpret_cast<const char*>(bytes.data()), bytes.size()); | |
| } | |
| absl_nullable std::unique_ptr<BindingHasher> BuildBmffV2Hasher( | |
| const BmffBasedHashAssertion& bmff_hash, riegeli::Reader& file_contents, | |
| BindingHasherTracker& tracker) { | |
| // Build box matchers from the assertion. | |
| std::vector<BMFFBoxMatcher> matchers; | |
| matchers.reserve(bmff_hash.exclusions_size()); | |
| for (const auto& bmff_range : bmff_hash.exclusions()) { | |
| matchers.emplace_back(bmff_range, &file_contents); | |
| } | |
| // Match boxes and build exclusion and insertion chunks for the hasher. | |
| std::vector<BindingHasher::Chunk> chunks; | |
| absl::Status iterator_status = IterateOverBmffBoxes( | |
| file_contents, | |
| [&chunks, &matchers](const BmffBoxHeader& box) -> absl::StatusOr<bool> { | |
| std::vector<BMFFBoxMatcher::Range> exclusions; | |
| bool is_entire_box_excluded = false; | |
| for (const auto& matcher : matchers) { | |
| if (matcher.Matches(box)) { | |
| exclusions = matcher.MatchedRanges(box); | |
| is_entire_box_excluded = matcher.IsMatchingEntireBox(); | |
| break; | |
| } | |
| } | |
| if (!is_entire_box_excluded && box.IsRootBox()) { | |
| chunks.push_back({.op = BindingHasher::Chunk::Op::kInsertion, | |
| .offset = box.start, | |
| .additional_data = Uint64ToBytes(box.start)}); | |
| } | |
| for (const auto& exclusion : exclusions) { | |
| chunks.push_back({.op = BindingHasher::Chunk::Op::kExclusion, | |
| .offset = exclusion.offset, | |
| .length = exclusion.length}); | |
| } | |
| return true; | |
| }); | |
| if (!iterator_status.ok()) { | |
| tracker.RecordGeneralError(iterator_status); | |
| return nullptr; | |
| } | |
| return BindingHasher::Create(bmff_hash.alg(), chunks, tracker); | |
| } | |
| } // namespace | |
| std::optional<std::string> BmffBindingHasher::Digest( | |
| riegeli::Reader& input) const { | |
| auto hasher = BuildBmffV2Hasher(assertion_, input, tracker_); | |
| if (hasher == nullptr) { | |
| return std::nullopt; | |
| } | |
| return hasher->Digest(input, tracker_); | |
| } | |
| } // namespace credentio |