// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "packager/media/formats/webm/webm_parser.h" #include #include #include "packager/media/formats/webm/cluster_builder.h" #include "packager/media/formats/webm/webm_constants.h" using ::testing::InSequence; using ::testing::Return; using ::testing::ReturnNull; using ::testing::StrEq; using ::testing::StrictMock; using ::testing::_; namespace shaka { namespace media { enum { kBlockCount = 5 }; class MockWebMParserClient : public WebMParserClient { public: virtual ~MockWebMParserClient() {} // WebMParserClient methods. MOCK_METHOD1(OnListStart, WebMParserClient*(int)); MOCK_METHOD1(OnListEnd, bool(int)); MOCK_METHOD2(OnUInt, bool(int, int64_t)); MOCK_METHOD2(OnFloat, bool(int, double)); MOCK_METHOD3(OnBinary, bool(int, const uint8_t*, int)); MOCK_METHOD2(OnString, bool(int, const std::string&)); }; class WebMParserTest : public testing::Test { protected: StrictMock client_; }; static std::unique_ptr CreateCluster(int block_count) { ClusterBuilder cb; cb.SetClusterTimecode(0); for (int i = 0; i < block_count; i++) { uint8_t data[] = {0x00}; cb.AddSimpleBlock(0, i, 0, data, sizeof(data)); } return cb.Finish(); } static void CreateClusterExpectations(int block_count, bool is_complete_cluster, MockWebMParserClient* client) { InSequence s; EXPECT_CALL(*client, OnListStart(kWebMIdCluster)).WillOnce(Return(client)); EXPECT_CALL(*client, OnUInt(kWebMIdTimecode, 0)) .WillOnce(Return(true)); for (int i = 0; i < block_count; i++) { EXPECT_CALL(*client, OnBinary(kWebMIdSimpleBlock, _, _)) .WillOnce(Return(true)); } if (is_complete_cluster) EXPECT_CALL(*client, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); } TEST_F(WebMParserTest, EmptyCluster) { const uint8_t kEmptyCluster[] = { 0x1F, 0x43, 0xB6, 0x75, 0x80 // CLUSTER (size = 0) }; int size = sizeof(kEmptyCluster); InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); WebMListParser parser(kWebMIdCluster, &client_); EXPECT_EQ(size, parser.Parse(kEmptyCluster, size)); EXPECT_TRUE(parser.IsParsingComplete()); } TEST_F(WebMParserTest, EmptyClusterInSegment) { const uint8_t kBuffer[] = { 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5) 0x1F, 0x43, 0xB6, 0x75, 0x80, // CLUSTER (size = 0) }; int size = sizeof(kBuffer); InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_)); EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true)); WebMListParser parser(kWebMIdSegment, &client_); EXPECT_EQ(size, parser.Parse(kBuffer, size)); EXPECT_TRUE(parser.IsParsingComplete()); } // Test the case where a non-list child element has a size // that is beyond the end of the parent. TEST_F(WebMParserTest, ChildNonListLargerThanParent) { const uint8_t kBuffer[] = { 0x1F, 0x43, 0xB6, 0x75, 0x81, // CLUSTER (size = 1) 0xE7, 0x81, 0x01, // Timecode (size=1, value=1) }; InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); WebMListParser parser(kWebMIdCluster, &client_); EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer))); EXPECT_FALSE(parser.IsParsingComplete()); } // Test the case where a list child element has a size // that is beyond the end of the parent. TEST_F(WebMParserTest, ChildListLargerThanParent) { const uint8_t kBuffer[] = { 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 5) 0x1F, 0x43, 0xB6, 0x75, 0x81, 0x11 // CLUSTER (size = 1) }; InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_)); WebMListParser parser(kWebMIdSegment, &client_); EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer))); EXPECT_FALSE(parser.IsParsingComplete()); } // Expecting to parse a Cluster, but get a Segment. TEST_F(WebMParserTest, ListIdDoesNotMatch) { const uint8_t kBuffer[] = { 0x18, 0x53, 0x80, 0x67, 0x80, // SEGMENT (size = 0) }; WebMListParser parser(kWebMIdCluster, &client_); EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer))); EXPECT_FALSE(parser.IsParsingComplete()); } TEST_F(WebMParserTest, InvalidElementInList) { const uint8_t kBuffer[] = { 0x18, 0x53, 0x80, 0x67, 0x82, // SEGMENT (size = 2) 0xAE, 0x80, // TrackEntry (size = 0) }; InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_)); WebMListParser parser(kWebMIdSegment, &client_); EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer))); EXPECT_FALSE(parser.IsParsingComplete()); } // Test specific case of InvalidElementInList to verify EBMLHEADER within // known-sized cluster causes parse error. TEST_F(WebMParserTest, InvalidEBMLHeaderInCluster) { const uint8_t kBuffer[] = { 0x1F, 0x43, 0xB6, 0x75, 0x85, // CLUSTER (size = 5) 0x1A, 0x45, 0xDF, 0xA3, 0x80, // EBMLHEADER (size = 0) }; InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); WebMListParser parser(kWebMIdCluster, &client_); EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer))); EXPECT_FALSE(parser.IsParsingComplete()); } // Verify that EBMLHEADER ends a preceding "unknown"-sized CLUSTER. TEST_F(WebMParserTest, UnknownSizeClusterFollowedByEBMLHeader) { const uint8_t kBuffer[] = { 0x1F, 0x43, 0xB6, 0x75, 0xFF, // CLUSTER (size = unknown; really 0 due to:) 0x1A, 0x45, 0xDF, 0xA3, 0x80, // EBMLHEADER (size = 0) }; InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); WebMListParser parser(kWebMIdCluster, &client_); // List parse should consume the CLUSTER but not the EBMLHEADER. EXPECT_EQ(5, parser.Parse(kBuffer, sizeof(kBuffer))); EXPECT_TRUE(parser.IsParsingComplete()); } TEST_F(WebMParserTest, VoidAndCRC32InList) { const uint8_t kBuffer[] = { 0x18, 0x53, 0x80, 0x67, 0x99, // SEGMENT (size = 25) 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3) 0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3) 0x1F, 0x43, 0xB6, 0x75, 0x8A, // CLUSTER (size = 10) 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3) 0xBF, 0x83, 0x00, 0x00, 0x00, // CRC32 (size = 3) }; int size = sizeof(kBuffer); InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&client_)); EXPECT_CALL(client_, OnListStart(kWebMIdCluster)).WillOnce(Return(&client_)); EXPECT_CALL(client_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true)); WebMListParser parser(kWebMIdSegment, &client_); EXPECT_EQ(size, parser.Parse(kBuffer, size)); EXPECT_TRUE(parser.IsParsingComplete()); } TEST_F(WebMParserTest, ParseListElementWithSingleCall) { std::unique_ptr cluster(CreateCluster(kBlockCount)); CreateClusterExpectations(kBlockCount, true, &client_); WebMListParser parser(kWebMIdCluster, &client_); EXPECT_EQ(cluster->size(), parser.Parse(cluster->data(), cluster->size())); EXPECT_TRUE(parser.IsParsingComplete()); } TEST_F(WebMParserTest, ParseListElementWithMultipleCalls) { std::unique_ptr cluster(CreateCluster(kBlockCount)); CreateClusterExpectations(kBlockCount, true, &client_); const uint8_t* data = cluster->data(); int size = cluster->size(); int default_parse_size = 3; WebMListParser parser(kWebMIdCluster, &client_); int parse_size = std::min(default_parse_size, size); while (size > 0) { int result = parser.Parse(data, parse_size); ASSERT_GE(result, 0); ASSERT_LE(result, parse_size); if (result == 0) { // The parser needs more data so increase the parse_size a little. EXPECT_FALSE(parser.IsParsingComplete()); parse_size += default_parse_size; parse_size = std::min(parse_size, size); continue; } parse_size = default_parse_size; data += result; size -= result; EXPECT_EQ((size == 0), parser.IsParsingComplete()); } EXPECT_TRUE(parser.IsParsingComplete()); } TEST_F(WebMParserTest, Reset) { InSequence s; std::unique_ptr cluster(CreateCluster(kBlockCount)); // First expect all but the last block. CreateClusterExpectations(kBlockCount - 1, false, &client_); // Now expect all blocks. CreateClusterExpectations(kBlockCount, true, &client_); WebMListParser parser(kWebMIdCluster, &client_); // Send slightly less than the full cluster so all but the last block is // parsed. int result = parser.Parse(cluster->data(), cluster->size() - 1); EXPECT_GT(result, 0); EXPECT_LT(result, cluster->size()); EXPECT_FALSE(parser.IsParsingComplete()); parser.Reset(); // Now parse a whole cluster to verify that all the blocks will get parsed. EXPECT_EQ(cluster->size(), parser.Parse(cluster->data(), cluster->size())); EXPECT_TRUE(parser.IsParsingComplete()); } // Test the case where multiple clients are used for different lists. TEST_F(WebMParserTest, MultipleClients) { const uint8_t kBuffer[] = { 0x18, 0x53, 0x80, 0x67, 0x94, // SEGMENT (size = 20) 0x16, 0x54, 0xAE, 0x6B, 0x85, // TRACKS (size = 5) 0xAE, 0x83, // TRACKENTRY (size = 3) 0xD7, 0x81, 0x01, // TRACKNUMBER (size = 1) 0x1F, 0x43, 0xB6, 0x75, 0x85, // CLUSTER (size = 5) 0xEC, 0x83, 0x00, 0x00, 0x00, // Void (size = 3) }; int size = sizeof(kBuffer); StrictMock c1_; StrictMock c2_; StrictMock c3_; InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(Return(&c1_)); EXPECT_CALL(c1_, OnListStart(kWebMIdTracks)).WillOnce(Return(&c2_)); EXPECT_CALL(c2_, OnListStart(kWebMIdTrackEntry)).WillOnce(Return(&c3_)); EXPECT_CALL(c3_, OnUInt(kWebMIdTrackNumber, 1)).WillOnce(Return(true)); EXPECT_CALL(c2_, OnListEnd(kWebMIdTrackEntry)).WillOnce(Return(true)); EXPECT_CALL(c1_, OnListEnd(kWebMIdTracks)).WillOnce(Return(true)); EXPECT_CALL(c1_, OnListStart(kWebMIdCluster)).WillOnce(Return(&c2_)); EXPECT_CALL(c1_, OnListEnd(kWebMIdCluster)).WillOnce(Return(true)); EXPECT_CALL(client_, OnListEnd(kWebMIdSegment)).WillOnce(Return(true)); WebMListParser parser(kWebMIdSegment, &client_); EXPECT_EQ(size, parser.Parse(kBuffer, size)); EXPECT_TRUE(parser.IsParsingComplete()); } // Test the case where multiple clients are used for different lists. TEST_F(WebMParserTest, InvalidClient) { const uint8_t kBuffer[] = { 0x18, 0x53, 0x80, 0x67, 0x85, // SEGMENT (size = 20) 0x16, 0x54, 0xAE, 0x6B, 0x80, // TRACKS (size = 5) }; InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdSegment)).WillOnce(ReturnNull()); WebMListParser parser(kWebMIdSegment, &client_); EXPECT_EQ(-1, parser.Parse(kBuffer, sizeof(kBuffer))); EXPECT_FALSE(parser.IsParsingComplete()); } TEST_F(WebMParserTest, ReservedIds) { const uint8_t k1ByteReservedId[] = {0xFF, 0x81}; const uint8_t k2ByteReservedId[] = {0x7F, 0xFF, 0x81}; const uint8_t k3ByteReservedId[] = {0x3F, 0xFF, 0xFF, 0x81}; const uint8_t k4ByteReservedId[] = {0x1F, 0xFF, 0xFF, 0xFF, 0x81}; const uint8_t* kBuffers[] = {k1ByteReservedId, k2ByteReservedId, k3ByteReservedId, k4ByteReservedId}; for (size_t i = 0; i < arraysize(kBuffers); i++) { int id; int64_t element_size; int buffer_size = 2 + i; EXPECT_EQ(buffer_size, WebMParseElementHeader(kBuffers[i], buffer_size, &id, &element_size)); EXPECT_EQ(id, kWebMReservedId); EXPECT_EQ(element_size, 1); } } TEST_F(WebMParserTest, ReservedSizes) { const uint8_t k1ByteReservedSize[] = {0xA3, 0xFF}; const uint8_t k2ByteReservedSize[] = {0xA3, 0x7F, 0xFF}; const uint8_t k3ByteReservedSize[] = {0xA3, 0x3F, 0xFF, 0xFF}; const uint8_t k4ByteReservedSize[] = {0xA3, 0x1F, 0xFF, 0xFF, 0xFF}; const uint8_t k5ByteReservedSize[] = {0xA3, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF}; const uint8_t k6ByteReservedSize[] = {0xA3, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; const uint8_t k7ByteReservedSize[] = {0xA3, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; const uint8_t k8ByteReservedSize[] = {0xA3, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; const uint8_t* kBuffers[] = {k1ByteReservedSize, k2ByteReservedSize, k3ByteReservedSize, k4ByteReservedSize, k5ByteReservedSize, k6ByteReservedSize, k7ByteReservedSize, k8ByteReservedSize}; for (size_t i = 0; i < arraysize(kBuffers); i++) { int id; int64_t element_size; int buffer_size = 2 + i; EXPECT_EQ(buffer_size, WebMParseElementHeader(kBuffers[i], buffer_size, &id, &element_size)); EXPECT_EQ(id, 0xA3); EXPECT_EQ(element_size, kWebMUnknownSize); } } TEST_F(WebMParserTest, ZeroPaddedStrings) { const uint8_t kBuffer[] = { 0x1A, 0x45, 0xDF, 0xA3, 0x91, // EBMLHEADER (size = 17) 0x42, 0x82, 0x80, // DocType (size = 0) 0x42, 0x82, 0x81, 0x00, // DocType (size = 1) "" 0x42, 0x82, 0x81, 'a', // DocType (size = 1) "a" 0x42, 0x82, 0x83, 'a', 0x00, 0x00 // DocType (size = 3) "a" }; int size = sizeof(kBuffer); InSequence s; EXPECT_CALL(client_, OnListStart(kWebMIdEBMLHeader)) .WillOnce(Return(&client_)); EXPECT_CALL(client_, OnString(kWebMIdDocType, StrEq(""))) .WillOnce(Return(true)); EXPECT_CALL(client_, OnString(kWebMIdDocType, StrEq(""))) .WillOnce(Return(true)); EXPECT_CALL(client_, OnString(kWebMIdDocType, StrEq("a"))) .WillOnce(Return(true)); EXPECT_CALL(client_, OnString(kWebMIdDocType, StrEq("a"))) .WillOnce(Return(true)); EXPECT_CALL(client_, OnListEnd(kWebMIdEBMLHeader)).WillOnce(Return(true)); WebMListParser parser(kWebMIdEBMLHeader, &client_); EXPECT_EQ(size, parser.Parse(kBuffer, size)); EXPECT_TRUE(parser.IsParsingComplete()); } } // namespace media } // namespace shaka