| // 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. | |
| // | |
| #ifndef THIRD_PARTY_CREDENTIO_FORMATS_BMFF_BOX_HEADER_H_ | |
| #define THIRD_PARTY_CREDENTIO_FORMATS_BMFF_BOX_HEADER_H_ | |
| #include <array> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <string> | |
| #include "absl/base/no_destructor.h" | |
| #include "absl/container/flat_hash_set.h" | |
| #include "absl/functional/function_ref.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/match.h" | |
| #include "absl/strings/string_view.h" | |
| #include "formats/byte_range.h" | |
| #include "riegeli/bytes/reader.h" | |
| #include "riegeli/bytes/writer.h" | |
| namespace credentio { | |
| // A BMFF box header for reading. | |
| struct BmffBoxHeader { | |
| uint64_t start; // Absolute offset of the box header within input. | |
| uint64_t box_size; // Includes header and version/flags when present. | |
| uint64_t header_size; // Size of the header. | |
| // Size of version and flags, if it is a full box (atom). | |
| uint64_t version_and_flags_size = 0; | |
| std::string type; // The type of the box. | |
| std::string xpath; // The XPath of the box, like "/moov[1]/pssh". | |
| std::string user_type = ""; // Only present in UUID. | |
| // Version and flags only present in a full box (atom) or UUID. | |
| // Check `version_and_flags_size > 0` to determine if these are present. | |
| uint8_t version = 0; | |
| std::string flags = ""; | |
| // Returns true if the box is at the root level (has no parent). | |
| bool IsRootBox() const { | |
| return xpath.empty() || !absl::StrContains(xpath.substr(1), "/"); | |
| } | |
| // Returns true if the box is a Full Box (contains version and flags). | |
| bool IsFullBox() const { return version_and_flags_size > 0; } | |
| // Returns true if the box is a container box that can have sub-boxes. | |
| bool IsContainerBox() const; | |
| }; | |
| inline bool operator==(const BmffBoxHeader& a, const BmffBoxHeader& b) { | |
| return a.start == b.start && a.box_size == b.box_size && | |
| a.header_size == b.header_size && | |
| a.version_and_flags_size == b.version_and_flags_size && | |
| a.type == b.type && a.xpath == b.xpath && a.user_type == b.user_type && | |
| a.version == b.version && a.flags == b.flags; | |
| } | |
| // A BMFF box header for writing. | |
| struct BmffBox { | |
| std::string type; | |
| std::string user_type; | |
| uint8_t version = 0; | |
| std::string flags; | |
| uint64_t data_size = 0; | |
| }; | |
| // Reads a BMFF box header from the input reader. | |
| // Returns the parsed header or an error status. | |
| absl::StatusOr<BmffBoxHeader> ReadBmffBoxHeader(riegeli::Reader& input); | |
| // Writes the header of the given BMFF box and returns the number of written | |
| // bytes. | |
| absl::StatusOr<int64_t> WriteBmffBoxHeader(const BmffBox& box, | |
| riegeli::Writer& destination); | |
| // Returns the size of the encoded BMFF box header. | |
| int64_t BmffBoxHeaderSize(const BmffBox& box); | |
| // Returns true if the given box type is a Full Box. | |
| inline bool IsBmffFullBox(absl::string_view type); | |
| // The function signature for processing a BMFF box. The function is called for | |
| // each box in a BMFF file. | |
| using BmffBoxProcessor = | |
| absl::FunctionRef<absl::StatusOr<bool>(const BmffBoxHeader&)>; | |
| // Iterates over all BMFF boxes in the given reader, including sub-boxes in a | |
| // depth-first traversal, calling the given `processor` function for each box. | |
| // The method will seek to position 0 in the reader before iterating. | |
| // | |
| // The `processor` function controls the iteration by returning an | |
| // `absl::StatusOr<bool>`: | |
| // - Return `true` to continue iterating over subsequent boxes. | |
| // - Return `false` to stop iterating early. `IterateOverBmffBoxes` will stop | |
| // and return `absl::OkStatus()`. | |
| // - Return an error status to abort iteration immediately and propagate the | |
| // error back to the caller. | |
| // | |
| // The processor can also affect iteration by seeking the reader: | |
| // - For container boxes, sub-box iteration will start reading from wherever | |
| // the processor leaves the reader. | |
| // - To skip processing sub-boxes of a container box, the processor can seek | |
| // the reader to the end of the current box (`box.start + box.box_size`). | |
| // - The iterator will always reset the reader position to the end of the | |
| // current box before proceeding to the next sibling box. | |
| absl::Status IterateOverBmffBoxes(riegeli::Reader& reader, | |
| BmffBoxProcessor processor); | |
| // Reads all BMFF box headers from the given input, including sub-boxes. | |
| absl::StatusOr<std::vector<BmffBoxHeader>> ReadBmffBoxHeaders( | |
| riegeli::Reader& input); | |
| // Locates the insertion point for the C2PA manifest in the given reader. | |
| absl::StatusOr<ByteRange> LocateManifestInsertionPoint(riegeli::Reader& reader); | |
| namespace credentio_internal { | |
| // This list mirrors the C2PA SDK's implementation: | |
| // https://github.com/contentauth/c2pa-rs/blob/main/sdk/src/asset_handlers/bmff_io.rs#L58. | |
| constexpr std::array<std::string_view, 80> kFullBoxTypes = { | |
| "pdin", "mvhd", "tkhd", "mdhd", "hdlr", "nmhd", "elng", "stsd", "stdp", | |
| "stts", "ctts", "cslg", "stss", "stsh", "stdp", "elst", "dref", "stsz", | |
| "stz2", "stsc", "stco", "co64", "padb", "subs", "saiz", "saio", "mehd", | |
| "trex", "mfhd", "tfhd", "trun", "tfra", "mfro", "tfdt", "leva", "trep", | |
| "assp", "sbgp", "sgpd", "csgp", "cprt", "tsel", "kind", "meta", "xml ", | |
| "bxml", "iloc", "pitm", "ipro", "infe", "iinf", "iref", "ipma", "schm", | |
| "fiin", "fpar", "fecr", "gitn", "fire", "stri", "stsg", "stvi", "csch", | |
| "sidx", "ssix", "prft", "srpp", "vmhd", "smhd", "srat", "chnl", "dmix", | |
| "txtC", "mime", "uri ", "uriI", "hmhd", "sthd", "vvhd", "medc"}; | |
| constexpr bool AllTypesHaveSize4() { | |
| for (const std::string_view type : kFullBoxTypes) { | |
| if (type.size() != 4) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // Requires that `type` is exactly 4 bytes long. | |
| inline uint32_t TypeAsUint32(absl::string_view type) { | |
| uint32_t ret; | |
| std::memcpy(&ret, type.data(), sizeof(ret)); | |
| return ret; | |
| } | |
| } // namespace credentio_internal | |
| bool IsBmffFullBox(absl::string_view type) { | |
| static_assert( | |
| credentio_internal::AllTypesHaveSize4(), | |
| "The following algorithm works only if all types are 4 bytes long."); | |
| static const absl::NoDestructor<absl::flat_hash_set<uint32_t>> | |
| kFullBoxTypesSet([]() { | |
| absl::flat_hash_set<uint32_t> ret; | |
| ret.reserve(credentio_internal::kFullBoxTypes.size()); | |
| for (const std::string_view type : credentio_internal::kFullBoxTypes) { | |
| ret.insert(credentio_internal::TypeAsUint32(type)); | |
| } | |
| return ret; | |
| }()); | |
| return type.size() == 4 && | |
| kFullBoxTypesSet->contains(credentio_internal::TypeAsUint32(type)); | |
| } | |
| } // namespace credentio | |
| #endif // THIRD_PARTY_CREDENTIO_FORMATS_BMFF_BOX_HEADER_H_ |