blob: d5e9d5ab01a54dcdcc13e0fb825efa4e748ca436 [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/id3/reader.h"
#include <sys/types.h>
#include <cstdint>
#include <string>
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "formats/id3/id3.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/endian/endian_reading.h"
namespace credentio {
constexpr uint32_t kId3TagHeaderSize = 10;
constexpr uint32_t kId3FrameHeaderSize = 10;
absl::StatusOr<Id3Header> IterateOverId3Frames(riegeli::Reader& reader,
Id3FrameProcessor processor) {
if (!reader.SupportsSize() || !reader.Size().has_value()) {
return absl::InvalidArgumentError("reader does not support size");
}
uint64_t beginning_offset = reader.pos();
ABSL_ASSIGN_OR_RETURN(Id3Header id3_header, Id3Header::Parse(reader));
uint64_t id3_tag_end_offset =
beginning_offset + kId3TagHeaderSize + id3_header.tag_size;
if (id3_tag_end_offset > reader.Size().value()) {
return absl::InvalidArgumentError("tag end offset exceeds input size");
}
uint64_t current_offset = reader.pos();
while (current_offset + kId3FrameHeaderSize < id3_tag_end_offset) {
// Peek the next frame ID to see if the ID is valid.
uint32_t frame_id;
if (!reader.Seek(current_offset)) {
return reader.StatusOrAnnotate(
absl::DataLossError("Failed to seek to current offset"));
}
if (!riegeli::ReadBigEndian<uint32_t>(reader, frame_id)) {
return reader.StatusOrAnnotate(
absl::DataLossError("Failed to read frame ID for peeking"));
}
// Check if the frame ID is all NULL bytes. This indicates the start of
// padding at the end of the ID3v2 tag.
if (frame_id == 0) {
break;
}
if (!reader.Seek(current_offset)) {
return reader.StatusOrAnnotate(
absl::DataLossError("Failed to seek back to current offset"));
}
Id3Frame frame;
frame.offset = current_offset;
// Read the ID3 frame header, which is 10 bytes.
if (!reader.Read(4, frame.id)) {
return reader.StatusOrAnnotate(
absl::DataLossError("Failed to read frame ID"));
}
uint32_t size;
if (!riegeli::ReadBigEndian<uint32_t>(reader, size)) {
return reader.StatusOrAnnotate(
absl::DataLossError("Failed to read frame size"));
}
if (id3_header.major_version == 4) {
if ((size & 0x80808080) != 0) {
return reader.StatusOrAnnotate(absl::DataLossError(
"Invalid synchsafe integer in ID3v2.4 frame size"));
}
size = ((size >> 24) & 0x7f) << 21 | ((size >> 16) & 0x7f) << 14 |
((size >> 8) & 0x7f) << 7 | (size & 0x7f);
}
std::string flags;
if (!reader.Read(2, flags)) {
return reader.StatusOrAnnotate(
absl::DataLossError("Failed to read frame flags"));
}
const uint64_t remaining_tag_size =
id3_tag_end_offset - current_offset - kId3FrameHeaderSize;
if (size > remaining_tag_size) {
return reader.StatusOrAnnotate(
absl::DataLossError("ID3 frame size exceeds remaining tag size"));
}
frame.length = static_cast<uint64_t>(size) + kId3FrameHeaderSize;
ABSL_ASSIGN_OR_RETURN(bool continue_iteration, processor(frame));
if (!continue_iteration) {
break;
}
// Seek to the end of the frame for the next iteration.
if (!reader.Seek(frame.offset + frame.length)) {
return reader.StatusOrAnnotate(
absl::DataLossError("Failed to seek to end of frame"));
}
current_offset = reader.pos();
}
return id3_header;
}
} // namespace credentio