| // 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/extractor.h" | |
| #include <string> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/strings/string_view.h" | |
| #include "formats/byte_range.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "riegeli/bytes/string_reader.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::IsOkAndHolds; | |
| using ::absl_testing::StatusIs; | |
| using ::testing::AllOf; | |
| using ::testing::Eq; | |
| using ::testing::Field; | |
| using ::testing::HasSubstr; | |
| using ::testing::Optional; | |
| constexpr unsigned char kId3TagWithC2paManifestStore[] = { | |
| 'I', 'D', '3', // ID3 file identifier | |
| 0x03, 0x00, // Version 2.3 | |
| 0x00, // Flags | |
| 0x00, 0x00, 0x00, 0x3E, // Tag size = 62 (52 + 10) | |
| 'G', 'E', 'O', 'B', // Frame ID | |
| 0x00, 0x00, 0x00, 0x34, // Frame Size = 52 | |
| 0x00, 0x00, // Frame flags | |
| 0x01, // Text encoding [UTF-16] | |
| // MIME type | |
| 'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'c', '2', 'p', | |
| 'a', 0x00, | |
| // File name [UTF-16LE] | |
| 0xFF, 0xFE, 'f', 0x00, 'o', 0x00, 'o', 0x00, 0x00, 0x00, | |
| // Content description [UTF-16LE] | |
| 0xFF, 0xFE, 'b', 0x00, 'a', 0x00, 'r', 0x00, 0x00, 0x00, | |
| // Encapsulated object | |
| 'm', 'a', 'n', 'i', 'f', 'e', 's', 't', ' ', 's', 't', 'o', 'r', 'e'}; | |
| constexpr unsigned char kId3TagWithMultipleC2paManifestStores[] = { | |
| 'I', 'D', '3', // ID3 file identifier | |
| 0x03, 0x00, // Version 2.3 | |
| 0x00, // Flags | |
| 0x00, 0x00, 0x00, 0x7C, // Tag size = 124 (52 + 10 + 52 + 10) | |
| // Frame 1 | |
| 'G', 'E', 'O', 'B', // Frame ID | |
| 0x00, 0x00, 0x00, 0x34, // Frame Size = 52 | |
| 0x00, 0x00, // Frame flags | |
| 0x01, // Text encoding [UTF-16] | |
| // MIME type | |
| 'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'c', '2', 'p', | |
| 'a', 0x00, | |
| // File name [UTF-16LE] | |
| 0xFF, 0xFE, 'f', 0x00, 'o', 0x00, 'o', 0x00, 0x00, 0x00, | |
| // Content description [UTF-16LE] | |
| 0xFF, 0xFE, 'b', 0x00, 'a', 0x00, 'r', 0x00, 0x00, 0x00, | |
| // Encapsulated object | |
| 'm', 'a', 'n', 'i', 'f', 'e', 's', 't', ' ', 's', 't', 'o', 'r', 'e', | |
| // Frame 2 | |
| 'G', 'E', 'O', 'B', // Frame ID | |
| 0x00, 0x00, 0x00, 0x34, // Frame Size = 52 | |
| 0x00, 0x00, // Frame flags | |
| 0x01, // Text encoding [UTF-16] | |
| // MIME type | |
| 'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'c', '2', 'p', | |
| 'a', 0x00, | |
| // File name [UTF-16LE] | |
| 0xFF, 0xFE, 'f', 0x00, 'o', 0x00, 'o', 0x00, 0x00, 0x00, | |
| // Content description [UTF-16LE] | |
| 0xFF, 0xFE, 'b', 0x00, 'a', 0x00, 'r', 0x00, 0x00, 0x00, | |
| // Encapsulated object | |
| 'm', 'a', 'n', 'i', 'f', 'e', 's', 't', ' ', 's', 't', 'o', 'r', 'e'}; | |
| constexpr unsigned char kId3TagWithC2paManifestStoreDeprecatedMimeType[] = { | |
| 'I', 'D', '3', // ID3 file identifier | |
| 0x03, 0x00, // Version 2.3 | |
| 0x00, // Flags | |
| 0x00, 0x00, 0x00, 0x4F, // Tag size = 79 (69 + 10) | |
| 'G', 'E', 'O', 'B', // Frame ID | |
| 0x00, 0x00, 0x00, 0x45, // Frame Size = 69 | |
| 0x00, 0x00, // Frame flags | |
| 0x01, // Text encoding [UTF-16] | |
| // MIME type | |
| 'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'x', '-', 'c', | |
| '2', 'p', 'a', '-', 'm', 'a', 'n', 'i', 'f', 'e', 's', 't', '-', 's', 't', | |
| 'o', 'r', 'e', 0x00, | |
| // File name [UTF-16LE] | |
| 0xFF, 0xFE, 'f', 0x00, 'o', 0x00, 'o', 0x00, 0x00, 0x00, | |
| // Content description [UTF-16LE] | |
| 0xFF, 0xFE, 'b', 0x00, 'a', 0x00, 'r', 0x00, 0x00, 0x00, | |
| // Encapsulated object | |
| 'm', 'a', 'n', 'i', 'f', 'e', 's', 't', ' ', 's', 't', 'o', 'r', 'e'}; | |
| TEST(Id3ExtractorTest, | |
| ExtractManifestStoreFromGeobFrameWithDeprecatedMimeType) { | |
| std::string mp3_file_contents = | |
| std::string(reinterpret_cast<const char*>( | |
| kId3TagWithC2paManifestStoreDeprecatedMimeType), | |
| sizeof(kId3TagWithC2paManifestStoreDeprecatedMimeType)); | |
| riegeli::StringReader<> input(mp3_file_contents); | |
| Id3Extractor extractor; | |
| auto result = extractor.ExtractManifestStore(input); | |
| ABSL_ASSERT_OK(result); | |
| EXPECT_THAT(*result, HasSubstr("manifest store")); | |
| } | |
| TEST(Id3ExtractorTest, ExtractManifestStore) { | |
| std::string mp3_file_contents = | |
| std::string(reinterpret_cast<const char*>(kId3TagWithC2paManifestStore), | |
| sizeof(kId3TagWithC2paManifestStore)); | |
| riegeli::StringReader<> input(mp3_file_contents); | |
| Id3Extractor extractor; | |
| auto result = extractor.ExtractManifestStore(input); | |
| ABSL_ASSERT_OK(result); | |
| EXPECT_THAT(*result, HasSubstr("manifest store")); | |
| } | |
| TEST(Id3ExtractorTest, | |
| ExtractManifestStoreThrowsErrorIfMultipleC2paManifestStoresFound) { | |
| std::string mp3_file_contents = std::string( | |
| reinterpret_cast<const char*>(kId3TagWithMultipleC2paManifestStores), | |
| sizeof(kId3TagWithMultipleC2paManifestStores)); | |
| riegeli::StringReader<> input(mp3_file_contents); | |
| Id3Extractor extractor; | |
| EXPECT_THAT(extractor.ExtractManifestStore(input), | |
| StatusIs(absl::StatusCode::kNotFound, | |
| HasSubstr("Multiple manifest stores found"))); | |
| } | |
| TEST(Id3ExtractorTest, ExtractManifestStoreLocation) { | |
| std::string mp3_file_contents = | |
| std::string(reinterpret_cast<const char*>(kId3TagWithC2paManifestStore), | |
| sizeof(kId3TagWithC2paManifestStore)); | |
| riegeli::StringReader<> input(mp3_file_contents); | |
| Id3Extractor extractor; | |
| EXPECT_THAT( | |
| extractor.ExtractManifestStoreLocation(input, {.requires_c2pa = true}), | |
| IsOkAndHolds(Optional(AllOf(Field(&ByteRange::offset, Eq(58)), | |
| Field(&ByteRange::length, Eq(14)))))); | |
| } | |
| TEST(Id3ExtractorTest, ExtractBoxesUnimplemented) { | |
| riegeli::StringReader<> input("test_content"); | |
| Id3Extractor extractor; | |
| EXPECT_THAT(extractor.ExtractBoxes(input, {.requires_c2pa = true}), | |
| StatusIs(absl::StatusCode::kUnimplemented)); | |
| } | |
| } // namespace | |
| } // namespace credentio |