blob: 8b77193090f266abb76ebcfcc683059a2433f5dc [file]
// 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_