#include "./matroskaseekinfo.h" #include "./matroskaid.h" #include "../diagnostics.h" #include "../exceptions.h" #include #include #include using namespace std; using namespace CppUtilities; namespace TagParser { /*! * \class TagParser::MatroskaSeekInfo * \brief The MatroskaSeekInfo class helps parsing and making "SeekHead"-elements. */ /*! * \brief Shifts all offsets greater or equal than \a start by \a amount bytes. */ void MatroskaSeekInfo::shift(std::uint64_t start, std::int64_t amount) { for (auto &info : m_info) { if (get<1>(info) >= start) { if (amount > 0) { get<1>(info) += static_cast(amount); } else { get<1>(info) -= static_cast(-amount); } } } } /*! * \brief Parses the specified \a seekHeadElement and populates info() with the gathered information. * \throws Throws ios_base::failure when an IO error occurs. * \throws Throws Failure or a derived exception when a parsing error occurs. * \remarks * - The object does not take ownership over the specified \a seekHeadElement. * - Possibly previously parsed info() is not cleared. So subsequent calls can be used to gather seek * information from multiple seek head elements. Use clear() manually if that is not wanted. * - If the specified \a seekHeadElement references another seek head element the referenced seek head * element is parsed as well. One can set \a maxIndirection to 0 to prevent that or even increase the value * to allow following references even more deeply. References to elements which have already been visited * are never followed, though. */ void MatroskaSeekInfo::parse(EbmlElement *seekHeadElement, Diagnostics &diag, size_t maxIndirection) { static const string context("parsing \"SeekHead\"-element"); m_seekHeadElements.emplace_back(seekHeadElement); for (EbmlElement *seekElement = seekHeadElement->firstChild(), *seekIdElement, *seekPositionElement; seekElement; seekElement = seekElement->nextSibling()) { seekElement->parse(diag); switch (seekElement->id()) { case MatroskaIds::Seek: seekIdElement = seekPositionElement = nullptr; for (auto *seekElementChild = seekElement->firstChild(); seekElementChild; seekElementChild = seekElementChild->nextSibling()) { seekElementChild->parse(diag); switch (seekElementChild->id()) { case MatroskaIds::SeekID: if (seekIdElement) { diag.emplace_back(DiagLevel::Warning, "The \"Seek\"-element contains multiple \"SeekID\"-elements. Surplus elements will be ignored.", context); } seekIdElement = seekElementChild; break; case MatroskaIds::SeekPosition: if (seekPositionElement) { diag.emplace_back(DiagLevel::Warning, "The \"Seek\"-element contains multiple \"SeekPosition\"-elements. Surplus elements will be ignored.", context); } seekPositionElement = seekElementChild; break; case EbmlIds::Crc32: case EbmlIds::Void: break; default: diag.emplace_back(DiagLevel::Warning, "The element \"" % seekElementChild->idToString() + "\" within the \"Seek\" element is not a \"SeekID\"-element nor a \"SeekPosition\"-element and will be ignored.", context); } } if (!seekIdElement || !seekPositionElement) { diag.emplace_back(DiagLevel::Warning, "The \"Seek\"-element does not contain a \"SeekID\"- and a \"SeekPosition\"-element.", context); break; } m_info.emplace_back(seekIdElement->readUInteger(), seekPositionElement->readUInteger()); // follow possibly referenced seek head element if (m_info.back().first == MatroskaIds::SeekHead) { const auto startOffset = m_info.back().second; if (!maxIndirection) { diag.emplace_back(DiagLevel::Warning, argsToString("Not following reference by \"Seek\"-element at ", seekElement->startOffset(), " which points to another \"SeekHead\"-element at ", startOffset, '.'), context); break; } auto visited = false; for (const auto *const visitedSeekHeadElement : m_seekHeadElements) { if (visitedSeekHeadElement->startOffset() == startOffset) { diag.emplace_back(DiagLevel::Warning, argsToString("The \"Seek\"-element at ", seekElement->startOffset(), " contains a loop to the \"SeekHead\"-element at ", visitedSeekHeadElement->startOffset(), '.'), context); visited = true; break; } } if (visited) { break; } m_additionalSeekHeadElements.emplace_back(make_unique(seekHeadElement->container(), startOffset)); parse(m_additionalSeekHeadElements.back().get(), diag, maxIndirection - 1); } break; case EbmlIds::Crc32: case EbmlIds::Void: break; default: diag.emplace_back( DiagLevel::Warning, "The element " % seekElement->idToString() + " is not a seek element and will be ignored.", context); } } if (m_info.empty()) { diag.emplace_back(DiagLevel::Warning, "No seek information found.", context); } } /*! * \brief Writes a "SeekHead" element for the current instance to the specified \a stream. * \param stream Specifies the stream to write the "SeekHead" element to. * \throws Throws ios_base::failure when an IO error occurs. * \throws Throws Failure or a derived exception when a making error occurs. */ void MatroskaSeekInfo::make(ostream &stream, Diagnostics &diag) { CPP_UTILITIES_UNUSED(diag) std::uint64_t totalSize = 0; char buff0[8]; char buff1[8]; char buff2[2]; std::uint8_t sizeLength0, sizeLength1; // calculate size for (const auto &info : m_info) { // "Seek" element + "SeekID" element + "SeekPosition" element totalSize += 2u + 1u + (2u + 1u + EbmlElement::calculateIdLength(info.first)) + (2u + 1u + EbmlElement::calculateUIntegerLength(info.second)); } // write ID and size BE::getBytes(static_cast(MatroskaIds::SeekHead), buff0); stream.write(buff0, 4); sizeLength0 = EbmlElement::makeSizeDenotation(totalSize, buff0); stream.write(buff0, sizeLength0); // write entries for (const auto &info : m_info) { // make values sizeLength0 = EbmlElement::makeId(info.first, buff0); sizeLength1 = EbmlElement::makeUInteger(info.second, buff1); // "Seek" header BE::getBytes(static_cast(MatroskaIds::Seek), buff2); stream.write(buff2, 2); stream.put(static_cast(0x80 | (2 + 1 + sizeLength0 + 2 + 1 + sizeLength1))); // "SeekID" element BE::getBytes(static_cast(MatroskaIds::SeekID), buff2); stream.write(buff2, 2); stream.put(static_cast(0x80 | sizeLength0)); stream.write(buff0, sizeLength0); // "SeekPosition" element BE::getBytes(static_cast(MatroskaIds::SeekPosition), buff2); stream.write(buff2, 2); stream.put(static_cast(0x80 | sizeLength1)); stream.write(buff1, sizeLength1); } } /*! * \brief Returns the minimal number of bytes written when calling the make() method. * \remarks The returned value gets invalidated when the object is mutated. */ std::uint64_t MatroskaSeekInfo::minSize() const { std::uint64_t maxTotalSize = m_info.size() * (2 + 1 + 2 + 1 + 1 + 2 + 1 + 1); return 4 + EbmlElement::calculateSizeDenotationLength(maxTotalSize) + maxTotalSize; } /*! * \brief Returns the maximal number of bytes written when calling the make() method. * \remarks The returned value gets invalidated when the object is mutated. */ std::uint64_t MatroskaSeekInfo::maxSize() const { std::uint64_t maxTotalSize = m_info.size() * (2 + 1 + 2 + 1 + 4 + 2 + 1 + 8); return 4 + EbmlElement::calculateSizeDenotationLength(maxTotalSize) + maxTotalSize; } /*! * \brief Returns the number of bytes which will be written when calling the make() method. * \remarks The returned value gets invalidated when the object is mutated. */ std::uint64_t MatroskaSeekInfo::actualSize() const { std::uint64_t totalSize = 0; for (const auto &info : m_info) { // "Seek" element + "SeekID" element + "SeekPosition" element totalSize += 2u + 1u + (2 + 1 + EbmlElement::calculateIdLength(get<0>(info))) + (2u + 1u + EbmlElement::calculateUIntegerLength(get<1>(info))); } return totalSize += 4u + EbmlElement::calculateSizeDenotationLength(totalSize); } /*! * \brief Pushes the specified \a offset of an element with the specified \a id to the info. * * If there is an existing entry with the same \a id and \a index the existing entry will be * updated and no new entry created. * * \returns Returns an indication whether the actualSize() has changed. */ bool MatroskaSeekInfo::push(unsigned int index, EbmlElement::IdentifierType id, std::uint64_t offset) { unsigned int currentIndex = 0; for (auto &entry : info()) { if (get<0>(entry) == id) { if (index == currentIndex) { bool sizeUpdated = EbmlElement::calculateUIntegerLength(get<1>(entry)) != EbmlElement::calculateUIntegerLength(offset); get<1>(entry) = offset; return sizeUpdated; } ++currentIndex; } } info().emplace_back(id, offset); return true; } /*! * \brief Resets the object to its initial state. */ void MatroskaSeekInfo::clear() { m_seekHeadElements.clear(); m_additionalSeekHeadElements.clear(); m_info.clear(); } } // namespace TagParser