| // 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_ |