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