| // Copyright (c) 2016 The WebM project authors. All Rights Reserved. | |
| // | |
| // Use of this source code is governed by a BSD-style license | |
| // that can be found in the LICENSE file in the root of the source | |
| // tree. An additional intellectual property rights grant can be found | |
| // in the file PATENTS. All contributing project authors may | |
| // be found in the AUTHORS file in the root of the source tree. | |
| #include "webm/webm_parser.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "test_utils/mock_callback.h" | |
| #include "webm/buffer_reader.h" | |
| #include "webm/status.h" | |
| using testing::_; | |
| using testing::DoDefault; | |
| using testing::InSequence; | |
| using testing::NotNull; | |
| using testing::Return; | |
| using webm::BufferReader; | |
| using webm::Ebml; | |
| using webm::ElementMetadata; | |
| using webm::Id; | |
| using webm::Info; | |
| using webm::kUnknownElementPosition; | |
| using webm::kUnknownElementSize; | |
| using webm::kUnknownHeaderSize; | |
| using webm::MockCallback; | |
| using webm::Status; | |
| using webm::WebmParser; | |
| namespace { | |
| class WebmParserTest : public testing::Test {}; | |
| TEST_F(WebmParserTest, InvalidId) { | |
| BufferReader reader = { | |
| 0x00, // IDs cannot start with 0x00. | |
| }; | |
| MockCallback callback; | |
| { | |
| InSequence dummy; | |
| EXPECT_CALL(callback, OnEbml(_, _)).Times(0); | |
| EXPECT_CALL(callback, OnSegmentBegin(_, NotNull())).Times(0); | |
| EXPECT_CALL(callback, OnSegmentEnd(_)).Times(0); | |
| } | |
| WebmParser parser; | |
| Status status = parser.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kInvalidElementId, status.code); | |
| } | |
| TEST_F(WebmParserTest, InvalidSize) { | |
| BufferReader reader = { | |
| 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). | |
| 0x00, // Size must have 1+ bits set in the first byte. | |
| }; | |
| MockCallback callback; | |
| { | |
| InSequence dummy; | |
| EXPECT_CALL(callback, OnEbml(_, _)).Times(0); | |
| EXPECT_CALL(callback, OnSegmentBegin(_, NotNull())).Times(0); | |
| EXPECT_CALL(callback, OnSegmentEnd(_)).Times(0); | |
| } | |
| WebmParser parser; | |
| Status status = parser.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kInvalidElementSize, status.code); | |
| } | |
| TEST_F(WebmParserTest, DefaultParse) { | |
| BufferReader reader = { | |
| 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). | |
| 0x80, // Size = 0. | |
| 0x18, 0x53, 0x80, 0x67, // ID = 0x18538067 (Segment). | |
| 0x80, // Size = 0. | |
| }; | |
| MockCallback callback; | |
| { | |
| InSequence dummy; | |
| ElementMetadata metadata = {Id::kEbml, 5, 0, 0}; | |
| const Ebml ebml{}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1); | |
| metadata = {Id::kSegment, 5, 0, 5}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnSegmentBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnSegmentEnd(metadata)).Times(1); | |
| } | |
| WebmParser parser; | |
| Status status = parser.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kOkCompleted, status.code); | |
| } | |
| TEST_F(WebmParserTest, UnknownElement) { | |
| BufferReader reader = { | |
| 0x80, // ID = 0x80. | |
| 0x80, // Size = 0. | |
| }; | |
| MockCallback callback; | |
| { | |
| InSequence dummy; | |
| ElementMetadata metadata = {static_cast<Id>(0x80), 2, 0, 0}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnUnknownElement(metadata, NotNull(), NotNull())) | |
| .Times(1); | |
| } | |
| WebmParser parser; | |
| Status status = parser.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kOkCompleted, status.code); | |
| } | |
| TEST_F(WebmParserTest, SeekEbml) { | |
| BufferReader reader = { | |
| 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). | |
| 0x87, // Size = 7. | |
| 0x42, 0x86, // ID = 0x4286 (EBMLVersion). | |
| 0x10, 0x00, 0x00, 0x01, // Size = 1. | |
| 0x02, // Body (value = 2). | |
| }; | |
| std::uint64_t num_to_skip = 5; // Skip the starting EBML element metadata. | |
| std::uint64_t num_actually_skipped = 0; | |
| reader.Skip(num_to_skip, &num_actually_skipped); | |
| EXPECT_EQ(num_to_skip, num_actually_skipped); | |
| MockCallback callback; | |
| { | |
| InSequence dummy; | |
| ElementMetadata metadata = {Id::kEbmlVersion, 6, 1, num_to_skip}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| metadata = {Id::kEbml, kUnknownHeaderSize, kUnknownElementSize, | |
| kUnknownElementPosition}; | |
| Ebml ebml{}; | |
| ebml.ebml_version.Set(2, true); | |
| EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1); | |
| } | |
| WebmParser parser; | |
| parser.DidSeek(); | |
| Status status = parser.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kOkCompleted, status.code); | |
| EXPECT_EQ(reader.size(), reader.Position()); | |
| } | |
| TEST_F(WebmParserTest, SeekSegment) { | |
| BufferReader reader = { | |
| 0x18, 0x53, 0x80, 0x67, // ID = 0x18538067 (Segment). | |
| 0x85, // Size = 5. | |
| 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info). | |
| 0x80, // Size = 0. | |
| }; | |
| std::uint64_t num_to_skip = 5; // Skip the starting Segment element metadata. | |
| std::uint64_t num_actually_skipped = 0; | |
| reader.Skip(num_to_skip, &num_actually_skipped); | |
| EXPECT_EQ(num_to_skip, num_actually_skipped); | |
| MockCallback callback; | |
| { | |
| InSequence dummy; | |
| ElementMetadata metadata = {Id::kInfo, 5, 0, num_to_skip}; | |
| const Info info{}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnInfo(metadata, info)).Times(1); | |
| metadata = {Id::kSegment, kUnknownHeaderSize, kUnknownElementSize, | |
| kUnknownElementPosition}; | |
| EXPECT_CALL(callback, OnSegmentEnd(metadata)).Times(1); | |
| } | |
| WebmParser parser; | |
| parser.DidSeek(); | |
| Status status = parser.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kOkCompleted, status.code); | |
| EXPECT_EQ(reader.size(), reader.Position()); | |
| } | |
| TEST_F(WebmParserTest, SeekVoid) { | |
| BufferReader reader = { | |
| 0xEC, // ID = 0xEC (Void). | |
| 0x81, // Size = 0. | |
| 0xEC, // ID = 0xEC (Void). | |
| 0x81, // Size = 1. | |
| 0x00, // Body. | |
| 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). | |
| 0x80, // Size = 0. | |
| }; | |
| std::uint64_t num_to_skip = 2; // Skip the first Void element. | |
| std::uint64_t num_actually_skipped = 0; | |
| reader.Skip(num_to_skip, &num_actually_skipped); | |
| EXPECT_EQ(num_to_skip, num_actually_skipped); | |
| MockCallback callback; | |
| { | |
| InSequence dummy; | |
| ElementMetadata metadata = {Id::kVoid, 2, 1, num_to_skip}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnVoid(metadata, &reader, NotNull())).Times(1); | |
| metadata = {Id::kEbml, 5, 0, 5}; | |
| const Ebml ebml{}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1); | |
| } | |
| WebmParser parser; | |
| parser.DidSeek(); | |
| Status status = parser.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kOkCompleted, status.code); | |
| EXPECT_EQ(reader.size(), reader.Position()); | |
| } | |
| TEST_F(WebmParserTest, SwapAfterFailedParse) { | |
| BufferReader reader = { | |
| 0x00, // Invalid ID. | |
| 0xEC, // ID = 0xEC (Void). | |
| 0x81, // Size = 1. | |
| 0x00, // Body. | |
| }; | |
| MockCallback expect_nothing; | |
| EXPECT_CALL(expect_nothing, OnElementBegin(_, _)).Times(0); | |
| MockCallback expect_void; | |
| { | |
| InSequence dummy; | |
| ElementMetadata metadata = {Id::kVoid, 2, 1, 1}; | |
| EXPECT_CALL(expect_void, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(expect_void, OnVoid(metadata, &reader, NotNull())).Times(1); | |
| } | |
| WebmParser parser1; | |
| Status status = parser1.Feed(&expect_nothing, &reader); | |
| EXPECT_EQ(Status::kInvalidElementId, status.code); | |
| EXPECT_EQ(static_cast<std::uint64_t>(1), reader.Position()); | |
| // After swapping, the parser should retain its failed state and not consume | |
| // more data. | |
| WebmParser parser2; | |
| parser2.Swap(&parser1); | |
| status = parser2.Feed(&expect_nothing, &reader); | |
| EXPECT_EQ(Status::kInvalidElementId, status.code); | |
| EXPECT_EQ(static_cast<std::uint64_t>(1), reader.Position()); | |
| // parser1 should be a fresh/new parser after the swap, so parsing should | |
| // succeed. | |
| status = parser1.Feed(&expect_void, &reader); | |
| EXPECT_EQ(Status::kOkCompleted, status.code); | |
| EXPECT_EQ(reader.size(), reader.Position()); | |
| } | |
| TEST_F(WebmParserTest, Swap) { | |
| BufferReader reader = { | |
| 0xEC, // ID = 0xEC (Void). | |
| 0x81, // Size = 1. | |
| 0x00, // Body. | |
| 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML). | |
| 0x80, // Size = 0. | |
| }; | |
| MockCallback callback; | |
| { | |
| InSequence dummy; | |
| ElementMetadata metadata = {Id::kVoid, 2, 1, 0}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnVoid(metadata, &reader, NotNull())) | |
| .WillOnce(Return(Status(Status::kOkPartial))) | |
| .WillOnce(DoDefault()); | |
| metadata = {Id::kEbml, 5, 0, 3}; | |
| EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1); | |
| EXPECT_CALL(callback, OnEbml(metadata, Ebml{})).Times(1); | |
| } | |
| WebmParser parser1; | |
| Status status = parser1.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kOkPartial, status.code); | |
| WebmParser parser2; | |
| swap(parser1, parser2); | |
| status = parser2.Feed(&callback, &reader); | |
| EXPECT_EQ(Status::kOkCompleted, status.code); | |
| EXPECT_EQ(reader.size(), reader.Position()); | |
| } | |
| } // namespace |