| // 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 <cassert> | |
| #include <cstdint> | |
| #include "src/ebml_parser.h" | |
| #include "src/master_parser.h" | |
| #include "src/segment_parser.h" | |
| #include "src/unknown_parser.h" | |
| #include "webm/element.h" | |
| namespace webm { | |
| // Parses WebM EBML documents (i.e. level-0 WebM elements). | |
| class WebmParser::DocumentParser { | |
| public: | |
| // Resets the parser after a seek to a new position in the reader. | |
| void DidSeek() { | |
| PrepareForNextChild(); | |
| did_seek_ = true; | |
| state_ = State::kBegin; | |
| } | |
| // Feeds the parser; will return Status::kOkCompleted when the reader returns | |
| // Status::kEndOfFile, but only if the parser has already completed parsing | |
| // its child elements. | |
| Status Feed(Callback* callback, Reader* reader) { | |
| assert(callback != nullptr); | |
| assert(reader != nullptr); | |
| Callback* const original_callback = callback; | |
| if (action_ == Action::kSkip) { | |
| callback = &skip_callback_; | |
| } | |
| Status status; | |
| std::uint64_t num_bytes_read; | |
| while (true) { | |
| switch (state_) { | |
| case State::kBegin: { | |
| child_metadata_.header_size = 0; | |
| child_metadata_.position = reader->Position(); | |
| state_ = State::kReadingChildId; | |
| continue; | |
| } | |
| case State::kReadingChildId: { | |
| assert(child_parser_ == nullptr); | |
| status = id_parser_.Feed(callback, reader, &num_bytes_read); | |
| child_metadata_.header_size += num_bytes_read; | |
| if (!status.completed_ok()) { | |
| if (status.code == Status::kEndOfFile && | |
| reader->Position() == child_metadata_.position) { | |
| state_ = State::kEndReached; | |
| continue; | |
| } | |
| return status; | |
| } | |
| state_ = State::kReadingChildSize; | |
| continue; | |
| } | |
| case State::kReadingChildSize: { | |
| assert(child_parser_ == nullptr); | |
| status = size_parser_.Feed(callback, reader, &num_bytes_read); | |
| child_metadata_.header_size += num_bytes_read; | |
| if (!status.completed_ok()) { | |
| return status; | |
| } | |
| child_metadata_.id = id_parser_.id(); | |
| child_metadata_.size = size_parser_.size(); | |
| state_ = State::kValidatingChildSize; | |
| continue; | |
| } | |
| case State::kValidatingChildSize: { | |
| assert(child_parser_ == nullptr); | |
| if (child_metadata_.id == Id::kSegment) { | |
| child_parser_ = &segment_parser_; | |
| did_seek_ = false; | |
| state_ = State::kGettingAction; | |
| continue; | |
| } else if (child_metadata_.id == Id::kEbml) { | |
| child_parser_ = &ebml_parser_; | |
| did_seek_ = false; | |
| state_ = State::kGettingAction; | |
| continue; | |
| } | |
| Ancestory ancestory; | |
| if (did_seek_ && Ancestory::ById(child_metadata_.id, &ancestory)) { | |
| assert(!ancestory.empty()); | |
| assert(ancestory.id() == Id::kSegment || | |
| ancestory.id() == Id::kEbml); | |
| if (ancestory.id() == Id::kSegment) { | |
| child_parser_ = &segment_parser_; | |
| } else { | |
| child_parser_ = &ebml_parser_; | |
| } | |
| child_parser_->InitAfterSeek(ancestory.next(), child_metadata_); | |
| child_metadata_.id = ancestory.id(); | |
| child_metadata_.header_size = kUnknownHeaderSize; | |
| child_metadata_.size = kUnknownElementSize; | |
| child_metadata_.position = kUnknownElementPosition; | |
| did_seek_ = false; | |
| action_ = Action::kRead; | |
| state_ = State::kReadingChildBody; | |
| continue; | |
| } | |
| if (child_metadata_.id == Id::kVoid) { | |
| child_parser_ = &void_parser_; | |
| } else { | |
| if (child_metadata_.size == kUnknownElementSize) { | |
| return Status(Status::kIndefiniteUnknownElement); | |
| } | |
| child_parser_ = &unknown_parser_; | |
| } | |
| state_ = State::kGettingAction; | |
| continue; | |
| } | |
| case State::kGettingAction: { | |
| assert(child_parser_ != nullptr); | |
| status = callback->OnElementBegin(child_metadata_, &action_); | |
| if (!status.completed_ok()) { | |
| return status; | |
| } | |
| if (action_ == Action::kSkip) { | |
| callback = &skip_callback_; | |
| if (child_metadata_.size != kUnknownElementSize) { | |
| child_parser_ = &skip_parser_; | |
| } | |
| } | |
| state_ = State::kInitializingChildParser; | |
| continue; | |
| } | |
| case State::kInitializingChildParser: { | |
| assert(child_parser_ != nullptr); | |
| status = child_parser_->Init(child_metadata_, child_metadata_.size); | |
| if (!status.completed_ok()) { | |
| return status; | |
| } | |
| state_ = State::kReadingChildBody; | |
| continue; | |
| } | |
| case State::kReadingChildBody: { | |
| assert(child_parser_ != nullptr); | |
| status = child_parser_->Feed(callback, reader, &num_bytes_read); | |
| if (!status.completed_ok()) { | |
| return status; | |
| } | |
| if (child_parser_->GetCachedMetadata(&child_metadata_)) { | |
| state_ = State::kValidatingChildSize; | |
| } else { | |
| child_metadata_.header_size = 0; | |
| state_ = State::kReadingChildId; | |
| } | |
| PrepareForNextChild(); | |
| callback = original_callback; | |
| child_metadata_.position = reader->Position(); | |
| continue; | |
| } | |
| case State::kEndReached: { | |
| return Status(Status::kOkCompleted); | |
| } | |
| } | |
| } | |
| } | |
| private: | |
| // Parsing states for the finite-state machine. | |
| enum class State { | |
| /* clang-format off */ | |
| // State Transitions to state When | |
| kBegin, // kReadingChildId done | |
| kReadingChildId, // kReadingChildSize done | |
| // kEndReached EOF | |
| kReadingChildSize, // kValidatingChildSize done | |
| kValidatingChildSize, // kGettingAction done | |
| kGettingAction, // kInitializingChildParser done | |
| kInitializingChildParser, // kReadingChildBody done | |
| kReadingChildBody, // kValidatingChildSize cached metadata | |
| // kReadingChildId otherwise | |
| kEndReached, // No transitions from here | |
| /* clang-format on */ | |
| }; | |
| // The parser for parsing child element Ids. | |
| IdParser id_parser_; | |
| // The parser for parsing child element sizes. | |
| SizeParser size_parser_; | |
| // The parser for Id::kEbml elements. | |
| EbmlParser ebml_parser_; | |
| // The parser for Id::kSegment child elements. | |
| SegmentParser segment_parser_; | |
| // The parser for Id::kVoid child elements. | |
| VoidParser void_parser_; | |
| // The parser used when skipping elements (if the element's size is known). | |
| SkipParser skip_parser_; | |
| // The parser used for unknown children. | |
| UnknownParser unknown_parser_; | |
| // The callback used when skipping elements. | |
| SkipCallback skip_callback_; | |
| // The parser that is parsing the current child element. | |
| ElementParser* child_parser_ = nullptr; | |
| // Metadata for the current child being parsed. | |
| ElementMetadata child_metadata_ = {}; | |
| // Action for the current child being parsed. | |
| Action action_ = Action::kRead; | |
| // True if a seek was performed and the parser needs to handle it. | |
| bool did_seek_ = false; | |
| // The current state of the finite state machine. | |
| State state_ = State::kBegin; | |
| // Resets state in preparation for parsing a child element. | |
| void PrepareForNextChild() { | |
| id_parser_ = {}; | |
| size_parser_ = {}; | |
| child_parser_ = nullptr; | |
| action_ = Action::kRead; | |
| } | |
| }; | |
| // We have to explicitly declare a destructor (even if it's just defaulted) | |
| // because using the pimpl idiom with std::unique_ptr requires it. See Herb | |
| // Sutter's GotW #100 for further explanation. | |
| WebmParser::~WebmParser() = default; | |
| WebmParser::WebmParser() : parser_(new DocumentParser) {} | |
| void WebmParser::DidSeek() { | |
| parser_->DidSeek(); | |
| parsing_status_ = Status(Status::kOkPartial); | |
| } | |
| Status WebmParser::Feed(Callback* callback, Reader* reader) { | |
| assert(callback != nullptr); | |
| assert(reader != nullptr); | |
| if (parsing_status_.is_parsing_error()) { | |
| return parsing_status_; | |
| } | |
| parsing_status_ = parser_->Feed(callback, reader); | |
| return parsing_status_; | |
| } | |
| void WebmParser::Swap(WebmParser* other) { | |
| assert(other != nullptr); | |
| parser_.swap(other->parser_); | |
| std::swap(parsing_status_, other->parsing_status_); | |
| } | |
| void swap(WebmParser& left, WebmParser& right) { left.Swap(&right); } | |
| } // namespace webm |