blob: 749c86e64a0e0853e1b60b146fd2c59ced442f38 [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.
//
#include "formats/bmff/offset_adjustment.h"
#include <cstdint>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "formats/bmff/box_header.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/bytes/writer.h"
#include "riegeli/endian/endian_reading.h"
#include "riegeli/endian/endian_writing.h"
namespace credentio {
namespace {
absl::StatusOr<uint64_t> ReadVariableSize(riegeli::Reader& reader,
uint8_t size) {
if (size == 0) return 0;
if (size == 4) {
uint32_t val;
if (!riegeli::ReadBigEndian<uint32_t>(reader, val)) {
return absl::DataLossError("Failed to read 4 byte integer");
}
return val;
}
if (size == 8) {
uint64_t val;
if (!riegeli::ReadBigEndian<uint64_t>(reader, val)) {
return absl::DataLossError("Failed to read 8 byte integer");
}
return val;
}
return absl::DataLossError(absl::StrCat("invalid byte_size: ", size));
}
absl::Status WriteVariableSize(uint64_t value, uint8_t size,
riegeli::Writer& writer) {
if (size == 0) return absl::OkStatus();
if (size == 4) {
if (value > 0xffffffff) {
return absl::InvalidArgumentError("Value too large for 4 bytes");
}
if (!riegeli::WriteBigEndian<uint32_t>(static_cast<uint32_t>(value),
writer)) {
return writer.status();
}
return absl::OkStatus();
}
if (size == 8) {
if (!riegeli::WriteBigEndian<uint64_t>(value, writer)) {
return writer.status();
}
return absl::OkStatus();
}
return absl::InvalidArgumentError(absl::StrCat("invalid byte_size: ", size));
}
} // namespace
absl::StatusOr<IlocBox> ParseIloc(riegeli::Reader& reader,
const BmffBoxHeader& header) {
IlocBox box;
uint8_t offset_and_length_size;
if (!riegeli::ReadBigEndian<uint8_t>(reader, offset_and_length_size)) {
return absl::DataLossError("Failed to read offset and length size");
}
box.offset_size = offset_and_length_size >> 4;
box.length_size = offset_and_length_size & 0xf;
uint8_t base_offset_and_index_size;
if (!riegeli::ReadBigEndian<uint8_t>(reader, base_offset_and_index_size)) {
return absl::DataLossError("Failed to read base offset and index size");
}
box.base_offset_size = base_offset_and_index_size >> 4;
box.index_size = base_offset_and_index_size & 0xf;
uint32_t item_count = 0;
if (header.version < 2) {
uint16_t count;
if (!riegeli::ReadBigEndian<uint16_t>(reader, count)) {
return absl::DataLossError("Failed to read item count");
}
item_count = count;
} else if (header.version == 2) {
if (!riegeli::ReadBigEndian<uint32_t>(reader, item_count)) {
return absl::DataLossError("Failed to read item count");
}
}
box.items.reserve(item_count);
for (uint32_t i = 0; i < item_count; ++i) {
IlocItem item;
if (header.version < 2) {
uint16_t item_id;
if (!riegeli::ReadBigEndian<uint16_t>(reader, item_id)) {
return absl::DataLossError("Failed to read item ID");
}
item.item_id = item_id;
} else if (header.version == 2) {
if (!riegeli::ReadBigEndian<uint32_t>(reader, item.item_id)) {
return absl::DataLossError("Failed to read item ID");
}
}
if (header.version == 1 || header.version == 2) {
if (!riegeli::ReadBigEndian<uint16_t>(reader, item.construction_method)) {
return absl::DataLossError(
"Failed to read reserved and construction method");
}
}
if (!riegeli::ReadBigEndian<uint16_t>(reader, item.data_reference_index)) {
return absl::DataLossError("Failed to read data reference index");
}
ABSL_ASSIGN_OR_RETURN(item.base_offset,
ReadVariableSize(reader, box.base_offset_size));
uint16_t extent_count;
if (!riegeli::ReadBigEndian<uint16_t>(reader, extent_count)) {
return absl::DataLossError("Failed to read extent count");
}
item.extents.reserve(extent_count);
for (int j = 0; j < extent_count; ++j) {
IlocExtent extent;
if ((header.version == 1 || header.version == 2) && box.index_size > 0) {
ABSL_ASSIGN_OR_RETURN(extent.index,
ReadVariableSize(reader, box.index_size));
}
ABSL_ASSIGN_OR_RETURN(extent.offset,
ReadVariableSize(reader, box.offset_size));
ABSL_ASSIGN_OR_RETURN(extent.length,
ReadVariableSize(reader, box.length_size));
item.extents.push_back(extent);
}
box.items.push_back(item);
}
return box;
}
absl::Status SerializeIloc(const IlocBox& box, const BmffBoxHeader& header,
riegeli::Writer& writer) {
uint8_t offset_and_length_size = (box.offset_size << 4) | box.length_size;
if (!riegeli::WriteBigEndian<uint8_t>(offset_and_length_size, writer)) {
return writer.status();
}
uint8_t base_offset_and_index_size =
(box.base_offset_size << 4) | box.index_size;
if (!riegeli::WriteBigEndian<uint8_t>(base_offset_and_index_size, writer)) {
return writer.status();
}
if (header.version < 2) {
if (box.items.size() > 0xffff) {
return absl::InvalidArgumentError("Too many items for version < 2");
}
if (!riegeli::WriteBigEndian<uint16_t>(
static_cast<uint16_t>(box.items.size()), writer)) {
return writer.status();
}
} else if (header.version == 2) {
if (!riegeli::WriteBigEndian<uint32_t>(
static_cast<uint32_t>(box.items.size()), writer)) {
return writer.status();
}
}
for (const auto& item : box.items) {
if (header.version < 2) {
if (item.item_id > 0xffff) {
return absl::InvalidArgumentError("Item ID too large for version < 2");
}
if (!riegeli::WriteBigEndian<uint16_t>(
static_cast<uint16_t>(item.item_id), writer)) {
return writer.status();
}
} else if (header.version == 2) {
if (!riegeli::WriteBigEndian<uint32_t>(item.item_id, writer)) {
return writer.status();
}
}
if (header.version == 1 || header.version == 2) {
if (!riegeli::WriteBigEndian<uint16_t>(item.construction_method,
writer)) {
return writer.status();
}
}
if (!riegeli::WriteBigEndian<uint16_t>(item.data_reference_index, writer)) {
return writer.status();
}
ABSL_RETURN_IF_ERROR(
WriteVariableSize(item.base_offset, box.base_offset_size, writer));
if (item.extents.size() > 0xffff) {
return absl::InvalidArgumentError("Too many extents");
}
if (!riegeli::WriteBigEndian<uint16_t>(
static_cast<uint16_t>(item.extents.size()), writer)) {
return writer.status();
}
for (const auto& extent : item.extents) {
if ((header.version == 1 || header.version == 2) && box.index_size > 0) {
ABSL_RETURN_IF_ERROR(
WriteVariableSize(extent.index, box.index_size, writer));
}
ABSL_RETURN_IF_ERROR(
WriteVariableSize(extent.offset, box.offset_size, writer));
ABSL_RETURN_IF_ERROR(
WriteVariableSize(extent.length, box.length_size, writer));
}
}
return absl::OkStatus();
}
void AdjustOffsets(IlocBox& box, int64_t adjustment) {
for (auto& item : box.items) {
if (item.data_reference_index == 0) {
uint16_t method = item.construction_method & 0xf;
if (method == 0) {
if (box.base_offset_size > 0) {
// The `extent.offset` is a relative offset. We must add it to the
// base_offset to find the absolute position of the data.
item.base_offset += adjustment;
} else if (box.offset_size > 0) {
// The `extent.offset` is an absolute offset. We must add the
// adjustment to it directly.
for (auto& extent : item.extents) {
extent.offset += adjustment;
}
}
}
}
}
}
absl::StatusOr<StcoBox> ParseStco(riegeli::Reader& reader) {
StcoBox box;
uint32_t entry_count;
if (!riegeli::ReadBigEndian<uint32_t>(reader, entry_count)) {
return absl::DataLossError("Failed to read entry count");
}
box.offsets.reserve(entry_count);
for (uint32_t i = 0; i < entry_count; ++i) {
uint32_t offset;
if (!riegeli::ReadBigEndian<uint32_t>(reader, offset)) {
return absl::DataLossError("Failed to read offset");
}
box.offsets.push_back(offset);
}
return box;
}
absl::Status SerializeStco(const StcoBox& box, riegeli::Writer& writer) {
if (box.offsets.size() > 0xffffffff) {
return absl::InvalidArgumentError("Too many offsets");
}
if (!riegeli::WriteBigEndian<uint32_t>(
static_cast<uint32_t>(box.offsets.size()), writer)) {
return writer.status();
}
for (uint32_t offset : box.offsets) {
if (!riegeli::WriteBigEndian<uint32_t>(offset, writer)) {
return writer.status();
}
}
return absl::OkStatus();
}
void AdjustOffsets(StcoBox& box, int64_t adjustment) {
for (auto& offset : box.offsets) {
offset += adjustment;
}
}
absl::StatusOr<Co64Box> ParseCo64(riegeli::Reader& reader) {
Co64Box box;
uint32_t entry_count;
if (!riegeli::ReadBigEndian<uint32_t>(reader, entry_count)) {
return absl::DataLossError("Failed to read entry count");
}
box.offsets.reserve(entry_count);
for (uint32_t i = 0; i < entry_count; ++i) {
uint64_t offset;
if (!riegeli::ReadBigEndian<uint64_t>(reader, offset)) {
return absl::DataLossError("Failed to read offset");
}
box.offsets.push_back(offset);
}
return box;
}
absl::Status SerializeCo64(const Co64Box& box, riegeli::Writer& writer) {
if (box.offsets.size() > 0xffffffff) {
return absl::InvalidArgumentError("Too many offsets");
}
if (!riegeli::WriteBigEndian<uint32_t>(
static_cast<uint32_t>(box.offsets.size()), writer)) {
return writer.status();
}
for (uint64_t offset : box.offsets) {
if (!riegeli::WriteBigEndian<uint64_t>(offset, writer)) {
return writer.status();
}
}
return absl::OkStatus();
}
void AdjustOffsets(Co64Box& box, int64_t adjustment) {
for (auto& offset : box.offsets) {
offset += adjustment;
}
}
absl::StatusOr<SaioBox> ParseSaio(riegeli::Reader& reader,
const BmffBoxHeader& header) {
SaioBox box;
if ((header.flags[2] & 1) == 1) {
if (!riegeli::ReadBigEndian<uint32_t>(reader, box.aux_info_type)) {
return absl::DataLossError("Failed to read aux_info_type");
}
if (!riegeli::ReadBigEndian<uint32_t>(reader,
box.aux_info_type_parameter)) {
return absl::DataLossError("Failed to read aux_info_type_parameter");
}
}
uint32_t entry_count;
if (!riegeli::ReadBigEndian<uint32_t>(reader, entry_count)) {
return absl::DataLossError("Failed to read entry count");
}
box.offsets.reserve(entry_count);
for (uint32_t i = 0; i < entry_count; ++i) {
if (header.version == 0) {
uint32_t offset;
if (!riegeli::ReadBigEndian<uint32_t>(reader, offset)) {
return absl::DataLossError("Failed to read offset");
}
box.offsets.push_back(offset);
} else {
uint64_t offset;
if (!riegeli::ReadBigEndian<uint64_t>(reader, offset)) {
return absl::DataLossError("Failed to read offset");
}
box.offsets.push_back(offset);
}
}
return box;
}
absl::Status SerializeSaio(const SaioBox& box, const BmffBoxHeader& header,
riegeli::Writer& writer) {
if ((header.flags[2] & 1) == 1) {
if (!riegeli::WriteBigEndian<uint32_t>(box.aux_info_type, writer)) {
return writer.status();
}
if (!riegeli::WriteBigEndian<uint32_t>(box.aux_info_type_parameter,
writer)) {
return writer.status();
}
}
if (box.offsets.size() > 0xffffffff) {
return absl::InvalidArgumentError("Too many offsets");
}
if (!riegeli::WriteBigEndian<uint32_t>(
static_cast<uint32_t>(box.offsets.size()), writer)) {
return writer.status();
}
for (uint64_t offset : box.offsets) {
if (header.version == 0) {
if (offset > 0xffffffff) {
return absl::InvalidArgumentError("Offset too large for version 0");
}
if (!riegeli::WriteBigEndian<uint32_t>(static_cast<uint32_t>(offset),
writer)) {
return writer.status();
}
} else {
if (!riegeli::WriteBigEndian<uint64_t>(offset, writer)) {
return writer.status();
}
}
}
return absl::OkStatus();
}
void AdjustOffsets(SaioBox& box, int64_t adjustment) {
for (auto& offset : box.offsets) {
offset += adjustment;
}
}
} // namespace credentio