diff --git a/taglib/matroska/ebml/ebmlutils.h b/taglib/matroska/ebml/ebmlutils.h index abbec1a5..61839b86 100644 --- a/taglib/matroska/ebml/ebmlutils.h +++ b/taglib/matroska/ebml/ebmlutils.h @@ -59,18 +59,27 @@ namespace TagLib { unsigned long long randomUID(); + // A size with all value bits set is reserved for "unknown size" (see + // https://datatracker.ietf.org/doc/rfc8794/ section 6.2), so a size length + // of n bytes can hold sizes up to 2^(7n) - 2. constexpr int minSize(uint64_t data) { - if(data <= 0x7Fu) + if(data < 0x7Fu) return 1; - if(data <= 0x3FFFu) + if(data < 0x3FFFu) return 2; - if(data <= 0x1FFFFFu) + if(data < 0x1FFFFFu) return 3; - if(data <= 0xFFFFFFFu) + if(data < 0xFFFFFFFu) return 4; - if(data <= 0x7FFFFFFFFu) + if(data < 0x7FFFFFFFFu) return 5; + if(data < 0x3FFFFFFFFFFu) + return 6; + if(data < 0x1FFFFFFFFFFFFu) + return 7; + if(data < 0xFFFFFFFFFFFFFFu) + return 8; return 0; } diff --git a/tests/test_matroska.cpp b/tests/test_matroska.cpp index bf0bc875..783bd6b1 100644 --- a/tests/test_matroska.cpp +++ b/tests/test_matroska.cpp @@ -134,6 +134,7 @@ #include "matroskachapteredition.h" #include "matroskachapters.h" #include "matroskasimpletag.h" +#include "ebml/ebmlutils.h" #include "plainfile.h" #include "tfilestream.h" #include @@ -186,6 +187,8 @@ class TestMatroska : public CppUnit::TestFixture CPPUNIT_TEST(testAttachedFileDataReadOnDemand); CPPUNIT_TEST(testSaveUnrequestedAttachedFileData); CPPUNIT_TEST(testSegmentTitleWithoutAudioProperties); + CPPUNIT_TEST(testElementSizeLength); + CPPUNIT_TEST(testSaveElementSizeAtLengthLimit); CPPUNIT_TEST_SUITE_END(); public: @@ -1927,6 +1930,45 @@ public: CPPUNIT_ASSERT_EQUAL(String(""), noTitle.tag()->title()); } + void testElementSizeLength() + { + for(int length = 1; length <= 8; ++length) { + // 2^(7n) - 1 is the "unknown size" value of a size length of n bytes. + const uint64_t unknownSize = (1ULL << (7 * length)) - 1; + CPPUNIT_ASSERT_EQUAL(length, EBML::minSize(unknownSize - 1)); + if(length < 8) { + CPPUNIT_ASSERT_EQUAL(length + 1, EBML::minSize(unknownSize)); + } + } + CPPUNIT_ASSERT_EQUAL(1, EBML::minSize(0)); + CPPUNIT_ASSERT_EQUAL(0, EBML::minSize(0xFFFFFFFFFFFFFFULL)); + CPPUNIT_ASSERT_EQUAL(0, EBML::minSize(0xFFFFFFFFFFFFFFFFULL)); + } + + void testSaveElementSizeAtLengthLimit() + { + // A title of 127 or 16383 bytes gives its string element a size equal to + // the "unknown size" value of a size length of 1 or 2 bytes. + for(const unsigned int length : {126U, 127U, 128U, 16382U, 16383U, 16384U}) { + ScopedFileCopy copy("no-tags", ".mka"); + string newname = copy.fileName(); + const String title(string(length, 'a')); + { + Matroska::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + f.tag(true)->setTitle(title); + CPPUNIT_ASSERT(f.save()); + } + { + Matroska::File f(newname.c_str(), true, AudioProperties::Accurate); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.tag(false)); + CPPUNIT_ASSERT_EQUAL(title, f.tag()->title()); + CPPUNIT_ASSERT_EQUAL(444, f.audioProperties()->lengthInMilliseconds()); + } + } + } + void testUnknownSizeSegment() { ScopedFileCopy copy("no-tags", ".mka");