`ctf` plugin: add `ctf::src::MsgIter` class
authorSimon Marchi <simon.marchi@efficios.com>
Wed, 22 May 2024 16:00:59 +0000 (12:00 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Wed, 4 Sep 2024 19:05:14 +0000 (15:05 -0400)
Original patch by Simon.

Philippe's changes over original patch (no functional change intended):

• Conform to the Babeltrace 2 coding style for new C++11 code and to
  some naming conventions of the common part of the `ctf` plugin.

• Add many public and internal comments.

• Rename `Quirks` class to `MsgIterQuirks`.

  Also, rename the quirks to the generic `pktEndDefClkValZero`,
  `eventRecordDefClkValGtNextPktBeginDefClkVal`, and
  `eventRecordDefClkValLtPktBeginDefClkVal` names to decouple this API
  from specific tracers.

• Remove `MsgIterItemVisitor`.

  Instead, MsgIter::_handleItem() dispatches the item to a specific
  handling method based on its numeric type.

  Move the relevant members of `MsgIterItemVisitor` into
  `MsgIter` itself.

• In `_StackFrame`, rename everything named "field" to "sub-field" so as
  to remove some ambiguity.

• Add MsgIter::_stackTop*() helper methods which defer to the
  corresponding method on the top stack frame.

• Add MsgIter::_stackPush() and MsgIter::_stackPop() helper methods.

• Add MsgIter::_addMsgToQueue() helper method.

• Only log item details when the `TRACE` level is enabled.

• Handle `PktMagicNumberItem`: validate the magic number value.

• Handle `MetadataStreamUuidItem`: validate the UUID.

  The `MsgIter` constructor now accepts an `expectedMetadataStreamUuid`
  parameter (optional UUID) which is the expected metadata stream UUID
  to compare to.

• Handle variable-length integer field items, adding the
  MsgIter::_handleUIntFieldItem() and MsgIter::_handleSIntFieldItem()
  helper method templates to do so.

• Handle BLOB field items.

  A new data member, `_mCurBlobFieldDataOffset`, tracks the current BLOB
  field data offset to write to when handling the next
  `BlobFieldSectionItem` (we can't append to a BLOB field like when
  handling `StrFieldSubstrItem`).

• Handle optional field items, adding a member
  to `MsgIter::_StackFrame::_Field`.

• In MsgIter::_StackFrame::goToNextSubField(), unconditionally increment
  `_mSubFieldIndex`: curSubField() doesn't care about `_mSubFieldIndex`
  for the variant/option field cases anyway.

  Therefore, make curSubFieldAndGoToNextSubField() reuse curSubField()
  and goToNextSubField() to remove some almost duplicate code.

• Handle UTF-16 and UTF-32 string data.

Change-Id: If2dd82b43f4d19a16ec4420c13c9a3686034766d
Reviewed-on: https://review.lttng.org/c/babeltrace/+/8227
Reviewed-by: Philippe Proulx <eeppeliteloop@gmail.com>
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Reviewed-on: https://review.lttng.org/c/babeltrace/+/12716

src/Makefile.am
src/cpp-common/bt2c/uuid.hpp
src/plugins/ctf/common/src/msg-iter.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/msg-iter.hpp [new file with mode: 0644]

index 1fffd088c84936c27297a87ef12f244dbc5acc33..dcbc87009f275b02201ee65143ee19b9a2fc4281 100644 (file)
@@ -703,6 +703,8 @@ plugins_ctf_babeltrace_plugin_ctf_la_SOURCES = \
        plugins/ctf/common/src/metadata/ctf-ir.hpp \
        plugins/ctf/common/src/metadata/tsdl/metadata-stream-decoder.cpp \
        plugins/ctf/common/src/metadata/tsdl/metadata-stream-decoder.hpp \
+       plugins/ctf/common/src/msg-iter.cpp \
+       plugins/ctf/common/src/msg-iter.hpp \
        plugins/ctf/common/src/msg-iter/msg-iter.cpp \
        plugins/ctf/common/src/msg-iter/msg-iter.hpp \
        plugins/ctf/common/src/null-cp-finder.hpp \
@@ -749,7 +751,8 @@ plugins_ctf_babeltrace_plugin_ctf_la_LDFLAGS = \
 plugins_ctf_babeltrace_plugin_ctf_la_LIBADD = \
        plugins/ctf/common/metadata/libctf-parser.la \
        plugins/ctf/common/metadata/libctf-ast.la \
-       plugins/common/param-validation/libparam-validation.la
+       plugins/common/param-validation/libparam-validation.la \
+       cpp-common/libcpp-common.la
 
 if BABELTRACE_BUILD_WITH_MINGW
 plugins_ctf_babeltrace_plugin_ctf_la_LIBADD += -lws2_32
index 30f301447a2be7bbde5a38ba71e980d70c070c80..3bc1d7a492869d8db4c77e2d4b5dbd6471ca4b12 100644 (file)
@@ -248,6 +248,11 @@ inline UuidView::operator Uuid() const noexcept
     return Uuid {*this};
 }
 
+static inline std::string format_as(const bt2c::Uuid& uuid)
+{
+    return uuid.str();
+}
+
 } /* namespace bt2c */
 
 #endif /* BABELTRACE_CPP_COMMON_BT2C_UUID_HPP */
diff --git a/src/plugins/ctf/common/src/msg-iter.cpp b/src/plugins/ctf/common/src/msg-iter.cpp
new file mode 100644 (file)
index 0000000..36e2088
--- /dev/null
@@ -0,0 +1,904 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2022 Simon Marchi <simon.marchi@efficios.com>
+ * Copyright (c) 2015-2024 Philippe Proulx <pproulx@efficios.com>
+ */
+
+#include <algorithm>
+
+#include "common/assert.h"
+#include "common/common.h"
+#include "cpp-common/bt2/message.hpp"
+#include "cpp-common/bt2/trace-ir.hpp"
+#include "cpp-common/bt2c/aliases.hpp"
+#include "cpp-common/bt2c/call.hpp"
+#include "cpp-common/bt2c/fmt.hpp"
+#include "cpp-common/vendor/fmt/format.h"
+
+#include "item-seq/item.hpp"
+#include "msg-iter.hpp"
+#include "plugins/ctf/common/src/metadata/ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+using namespace bt2c::literals::datalen;
+
+MsgIter::MsgIter(const bt2::SelfMessageIterator selfMsgIter, const ctf::src::TraceCls& traceCls,
+                 bt2s::optional<bt2c::Uuid> expectedMetadataStreamUuid, const bt2::Stream stream,
+                 Medium::UP medium, const MsgIterQuirks& quirks, const bt2c::Logger& parentLogger) :
+    _mLogger {parentLogger, "PLUGIN/CTF/MSG-ITER"},
+    _mSelfMsgIter {selfMsgIter}, _mStream {stream},
+    _mExpectedMetadataStreamUuid {std::move(expectedMetadataStreamUuid)}, _mQuirks {quirks},
+    _mItemSeqIter {std::move(medium), traceCls, _mLogger}, _mUnicodeConv {_mLogger},
+    _mLoggingVisitor {"Handling item", _mLogger}
+{
+    BT_CPPLOGD("Created CTF plugin message iterator: "
+               "addr={}, trace-cls-addr={}, log-level={}",
+               fmt::ptr(this), fmt::ptr(&traceCls), _mLogger.level());
+}
+
+bt2::ConstMessage::Shared MsgIter::next()
+{
+    BT_CPPLOGD("Getting next message: addr={}", fmt::ptr(this));
+
+    if (_mIsDone) {
+        return bt2::ConstMessage::Shared {};
+    }
+
+    /*
+     * Return any message that's already in the queue (one iteration of
+     * the underlying item sequence iterator may yield more than one
+     * message, but we return one at a time).
+     */
+    if (auto msg = this->_releaseNextMsg()) {
+        return msg;
+    }
+
+    try {
+        while (true) {
+            /*
+             * Get the next item from the underlying item
+             * sequence iterator.
+             */
+            if (const auto item = _mItemSeqIter.next()) {
+                /* Handle item if needed */
+                if (!_mSkipItemsUntilScopeEndItem || item->isScopeEnd()) {
+                    this->_handleItem(*item);
+
+                    if (auto msg = this->_releaseNextMsg()) {
+                        return msg;
+                    }
+                }
+            } else {
+                /* No more items: this is the end! */
+                break;
+            }
+        }
+
+        /* We're done! */
+        _mIsDone = true;
+        return _mSelfMsgIter.createStreamEndMessage(_mStream);
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_APPEND_CAUSE_AND_RETHROW("Failed to create the next message: addr={}",
+                                            fmt::ptr(this));
+    }
+}
+
+void MsgIter::_handleItem(const Item& item)
+{
+    /* Log item details */
+    if (_mLogger.wouldLogT()) {
+        item.accept(_mLoggingVisitor);
+    }
+
+    /* Defer to specific handler */
+    switch (item.type()) {
+    case Item::Type::PktBegin:
+        this->_handleItem(item.asPktBegin());
+        break;
+    case Item::Type::PktEnd:
+        this->_handleItem(item.asPktEnd());
+        break;
+    case Item::Type::ScopeBegin:
+        this->_handleItem(item.asScopeBegin());
+        break;
+    case Item::Type::ScopeEnd:
+        this->_handleItem(item.asScopeEnd());
+        break;
+    case Item::Type::PktContentEnd:
+        this->_handleItem(item.asPktContentEnd());
+        break;
+    case Item::Type::EventRecordEnd:
+        this->_handleItem(item.asEventRecordEnd());
+        break;
+    case Item::Type::PktMagicNumber:
+        this->_handleItem(item.asPktMagicNumber());
+        break;
+    case Item::Type::MetadataStreamUuid:
+        this->_handleItem(item.asMetadataStreamUuid());
+        break;
+    case Item::Type::DataStreamInfo:
+        this->_handleItem(item.asDataStreamInfo());
+        break;
+    case Item::Type::PktInfo:
+        this->_handleItem(item.asPktInfo());
+        break;
+    case Item::Type::EventRecordInfo:
+        this->_handleItem(item.asEventRecordInfo());
+        break;
+    case Item::Type::FixedLenBitArrayField:
+    case Item::Type::FixedLenBitMapField:
+        this->_handleItem(item.asFixedLenBitArrayField());
+        break;
+    case Item::Type::FixedLenBoolField:
+        this->_handleItem(item.asFixedLenBoolField());
+        break;
+    case Item::Type::FixedLenSIntField:
+        this->_handleItem(item.asFixedLenSIntField());
+        break;
+    case Item::Type::FixedLenUIntField:
+        this->_handleItem(item.asFixedLenUIntField());
+        break;
+    case Item::Type::FixedLenFloatField:
+        this->_handleItem(item.asFixedLenFloatField());
+        break;
+    case Item::Type::VarLenSIntField:
+        this->_handleItem(item.asVarLenSIntField());
+        break;
+    case Item::Type::VarLenUIntField:
+        this->_handleItem(item.asVarLenUIntField());
+        break;
+    case Item::Type::NullTerminatedStrFieldBegin:
+        this->_handleItem(item.asNullTerminatedStrFieldBegin());
+        break;
+    case Item::Type::NullTerminatedStrFieldEnd:
+        this->_handleItem(item.asNullTerminatedStrFieldEnd());
+        break;
+    case Item::Type::RawData:
+        this->_handleItem(item.asRawData());
+        break;
+    case Item::Type::StructFieldBegin:
+        this->_handleItem(item.asStructFieldBegin());
+        break;
+    case Item::Type::StructFieldEnd:
+        this->_handleItem(item.asStructFieldEnd());
+        break;
+    case Item::Type::StaticLenArrayFieldBegin:
+        this->_handleItem(item.asStaticLenArrayFieldBegin());
+        break;
+    case Item::Type::StaticLenArrayFieldEnd:
+    case Item::Type::DynLenArrayFieldEnd:
+        this->_handleItem(item.asArrayFieldEnd());
+        break;
+    case Item::Type::DynLenArrayFieldBegin:
+        this->_handleItem(item.asDynLenArrayFieldBegin());
+        break;
+    case Item::Type::StaticLenBlobFieldBegin:
+        this->_handleItem(item.asStaticLenBlobFieldBegin());
+        break;
+    case Item::Type::StaticLenBlobFieldEnd:
+    case Item::Type::DynLenBlobFieldEnd:
+        this->_handleItem(item.asBlobFieldEnd());
+        break;
+    case Item::Type::DynLenBlobFieldBegin:
+        this->_handleItem(item.asDynLenBlobFieldBegin());
+        break;
+    case Item::Type::StaticLenStrFieldBegin:
+    case Item::Type::DynLenStrFieldBegin:
+        this->_handleItem(item.asNonNullTerminatedStrFieldBegin());
+        break;
+    case Item::Type::StaticLenStrFieldEnd:
+    case Item::Type::DynLenStrFieldEnd:
+        this->_handleItem(item.asNonNullTerminatedStrFieldEnd());
+        break;
+    case Item::Type::VariantFieldWithSIntSelBegin:
+    case Item::Type::VariantFieldWithUIntSelBegin:
+        this->_handleItem(item.asVariantFieldBegin());
+        break;
+    case Item::Type::VariantFieldWithSIntSelEnd:
+    case Item::Type::VariantFieldWithUIntSelEnd:
+        this->_handleItem(item.asVariantFieldEnd());
+        break;
+    case Item::Type::OptionalFieldWithBoolSelBegin:
+    case Item::Type::OptionalFieldWithSIntSelBegin:
+    case Item::Type::OptionalFieldWithUIntSelBegin:
+        this->_handleItem(item.asOptionalFieldBegin());
+        break;
+    case Item::Type::OptionalFieldWithBoolSelEnd:
+    case Item::Type::OptionalFieldWithSIntSelEnd:
+    case Item::Type::OptionalFieldWithUIntSelEnd:
+        this->_handleItem(item.asOptionalFieldEnd());
+        break;
+    default:
+        BT_CPPLOGT("Skipping item.");
+        return;
+    }
+}
+
+void MsgIter::_handleItem(const PktBeginItem&)
+{
+    BT_ASSERT_DBG(!_mCurPkt);
+    this->_curPkt(_mStream.createPacket());
+}
+
+bt2::Message::Shared MsgIter::_createPktEndMsgAndUpdateCurDefClkVal()
+{
+    BT_ASSERT_DBG(_mCurPkt);
+
+    if (_mPktEndDefClkVal) {
+        const auto pktEndDefClkValZeroBug = _mQuirks.pktEndDefClkValZero && _mPktBeginDefClkVal &&
+                                            _mPktEndDefClkVal && *_mPktBeginDefClkVal != 0 &&
+                                            *_mPktEndDefClkVal == 0;
+        const auto eventRecordDefClkValGtNextPktBeginDefClkValBug =
+            _mQuirks.eventRecordDefClkValGtNextPktBeginDefClkVal && _mCurDefClkVal &&
+            _mPktEndDefClkVal && *_mPktEndDefClkVal < _mCurDefClkVal;
+        const auto anyBug =
+            pktEndDefClkValZeroBug || eventRecordDefClkValGtNextPktBeginDefClkValBug;
+        const auto defClkVal = anyBug ? *_mCurDefClkVal : *_mPktEndDefClkVal;
+
+        if (!anyBug) {
+            _mCurDefClkVal = _mPktEndDefClkVal;
+        }
+
+        return _mSelfMsgIter.createPacketEndMessage(*_mCurPkt, defClkVal);
+    } else {
+        return _mSelfMsgIter.createPacketEndMessage(*_mCurPkt);
+    }
+}
+
+void MsgIter::_handleItem(const PktEndItem&)
+{
+    BT_ASSERT_DBG(!_mCurMsg);
+    BT_ASSERT_DBG(_mCurPkt);
+
+    /* Emit a packet beginning message now if required to fix a quirk */
+    if (_mDelayPktBeginMsgEmission) {
+        this->_emitDelayedPktBeginMsg(_mPktEndDefClkVal);
+    }
+
+    /* Emit a packet end message */
+    this->_addMsgToQueue(this->_createPktEndMsgAndUpdateCurDefClkVal());
+
+    /* No more current packet */
+    this->_resetCurPkt();
+}
+
+void MsgIter::_handleItem(const ScopeBeginItem& item)
+{
+    BT_ASSERT(_mStack.empty());
+    BT_ASSERT(!_mCurScopeField);
+
+    /* Handle specific scope */
+    switch (item.scope()) {
+    case Scope::PktHeader:
+        /* Nothing needed from the packet header: fast-forward */
+        _mSkipItemsUntilScopeEndItem = true;
+        break;
+    case Scope::PktCtx:
+    {
+        BT_ASSERT_DBG(_mCurPkt);
+
+        if (const auto pktCtxField = _mCurPkt->contextField()) {
+            _mCurScopeField = pktCtxField;
+        } else {
+            /* Nothing needed from the packet context: fast-forward */
+            _mSkipItemsUntilScopeEndItem = true;
+        }
+
+        break;
+    }
+    case Scope::EventRecordHeader:
+        /* Nothing needed from the event record header: fast-forward */
+        _mSkipItemsUntilScopeEndItem = true;
+        break;
+    case Scope::CommonEventRecordCtx:
+    {
+        BT_ASSERT_DBG(_mCurMsg);
+
+        if (const auto commonCtxField = _mCurMsg->asEvent().event().commonContextField()) {
+            _mCurScopeField = commonCtxField;
+        } else {
+            /* Nothing needed from the common context: fast-forward */
+            _mSkipItemsUntilScopeEndItem = true;
+        }
+
+        break;
+    }
+    case Scope::SpecEventRecordCtx:
+    {
+        BT_ASSERT_DBG(_mCurMsg);
+
+        if (const auto specCtxField = _mCurMsg->asEvent().event().specificContextField()) {
+            _mCurScopeField = specCtxField;
+        } else {
+            /* Nothing needed from the specific context: fast-forward */
+            _mSkipItemsUntilScopeEndItem = true;
+        }
+
+        break;
+    }
+    case Scope::EventRecordPayload:
+    {
+        BT_ASSERT_DBG(_mCurMsg);
+
+        if (const auto payloadField = _mCurMsg->asEvent().event().payloadField()) {
+            _mCurScopeField = payloadField;
+        } else {
+            /* Nothing needed from the payload: fast-forward */
+            _mSkipItemsUntilScopeEndItem = true;
+        }
+
+        break;
+    }
+    default:
+        bt_common_abort();
+    }
+}
+
+void MsgIter::_handleItem(const ScopeEndItem&)
+{
+    /*
+     * The last stack frame was removed by the `StructFieldEndItem`
+     * handler.
+     */
+    BT_ASSERT_DBG(_mStack.empty());
+
+    /* No more current scope root field */
+    _mCurScopeField.reset();
+
+    /* Reset this flag */
+    _mSkipItemsUntilScopeEndItem = false;
+}
+
+void MsgIter::_handleItem(const PktContentEndItem&)
+{
+    BT_ASSERT_DBG(_mCurPkt);
+}
+
+void MsgIter::_handleItem(const EventRecordEndItem&)
+{
+    BT_ASSERT_DBG(_mStack.empty());
+    BT_ASSERT_DBG(_mCurMsg);
+
+    /* Emit current message (move to message queue) */
+    _mMsgs.emplace(std::move(_mCurMsg));
+    BT_ASSERT_DBG(!_mCurMsg);
+}
+
+void MsgIter::_handleItem(const PktMagicNumberItem& item)
+{
+    if (!item.isValid()) {
+        BT_CPPLOGE_APPEND_CAUSE_AND_THROW(
+            bt2c::Error, "Invalid packet magic number: val={:#x}, expected-val={:#x}", item.val(),
+            item.expectedVal());
+    }
+}
+
+void MsgIter::_handleItem(const MetadataStreamUuidItem& item)
+{
+    BT_ASSERT_DBG(_mExpectedMetadataStreamUuid);
+
+    if (item.uuid() != *_mExpectedMetadataStreamUuid) {
+        BT_CPPLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                          "Invalid metadata stream UUID: uuid={}, expected-uuid={}",
+                                          item.uuid(), *_mExpectedMetadataStreamUuid);
+    }
+}
+
+void MsgIter::_handleItem(const DataStreamInfoItem& item)
+{
+    /*
+     * `_mItemSeqIter` doesn't care about contiguous packets from the
+     * same medium belonging to different data streams, but this message
+     * iterator does because it manages a single libbabeltrace2 stream.
+     */
+    BT_ASSERT_DBG(item.cls());
+
+    if (item.cls()->id() != _mStream.cls().id()) {
+        BT_CPPLOGE_APPEND_CAUSE_AND_THROW(
+            bt2c::Error,
+            "Two contiguous packets belong to data streams having different classes: "
+            "expected-data-stream-class-class-id={}, data-stream-class-id={}",
+            item.cls()->id(), _mStream.cls().id());
+    }
+
+    if (item.id() && *item.id() != _mStream.id()) {
+        BT_CPPLOGE_APPEND_CAUSE_AND_THROW(
+            bt2c::Error,
+            "Two contiguous packets belong to different data streams: "
+            "expected-data-stream-id={}, data-stream-id={}",
+            *item.id(), _mStream.id());
+    }
+
+    if (!_mEmittedStreamBeginMsg) {
+        this->_addMsgToQueue(_mSelfMsgIter.createStreamBeginningMessage(_mStream));
+        _mEmittedStreamBeginMsg = true;
+    }
+}
+
+bt2::Message::Shared MsgIter::_createInitDiscEventsMsg(const _OptUll& prevPktEndDefClkVal)
+{
+    if (_mStream.cls().discardedEventsHaveDefaultClockSnapshots()) {
+        /*
+         * We know there was a previous packet since we can't reach this
+         * point for the first packet.
+         */
+        BT_ASSERT_DBG(prevPktEndDefClkVal);
+        return _mSelfMsgIter.createDiscardedEventsMessage(_mStream, *prevPktEndDefClkVal,
+                                                          *_mPktEndDefClkVal);
+    } else {
+        return _mSelfMsgIter.createDiscardedEventsMessage(_mStream);
+    }
+}
+
+bt2::Message::Shared MsgIter::_createInitDiscPktsMsg(const _OptUll& prevPktEndDefClkVal)
+{
+    if (_mStream.cls().discardedPacketsHaveDefaultClockSnapshots()) {
+        /*
+         * We know there was a previous packet since we can't reach this
+         * point for the first packet.
+         */
+        BT_ASSERT_DBG(prevPktEndDefClkVal);
+        return _mSelfMsgIter.createDiscardedPacketsMessage(_mStream, *prevPktEndDefClkVal,
+                                                           *_mPktBeginDefClkVal);
+    } else {
+        return _mSelfMsgIter.createDiscardedPacketsMessage(_mStream);
+    }
+}
+
+void MsgIter::_emitPktBeginMsg(const _OptUll& defClkVal)
+{
+    BT_ASSERT_DBG(_mCurPkt);
+
+    /* Add new message to queue */
+    this->_addMsgToQueue(bt2c::call([this, &defClkVal] {
+        if (defClkVal) {
+            _mCurDefClkVal = defClkVal;
+            return _mSelfMsgIter.createPacketBeginningMessage(*_mCurPkt, *defClkVal);
+        } else {
+            return _mSelfMsgIter.createPacketBeginningMessage(*_mCurPkt);
+        }
+    }));
+}
+
+void MsgIter::_emitDelayedPktBeginMsg(const _OptUll& otherDefClkVal)
+{
+    BT_ASSERT_DBG(_mDelayPktBeginMsgEmission);
+
+    /* Reset the flag */
+    _mDelayPktBeginMsgEmission = false;
+
+    /*
+     * Only fix the beginning timestamp of the packet if it's larger
+     * than the timestamp of its first event record.
+     *
+     * Emit a packet beginning message now.
+     */
+    this->_emitPktBeginMsg(bt2c::call([this, &otherDefClkVal]() -> _OptUll {
+        if (_mPktBeginDefClkVal && otherDefClkVal) {
+            return std::min(*_mPktBeginDefClkVal, *otherDefClkVal);
+        } else if (_mPktBeginDefClkVal) {
+            return _mPktBeginDefClkVal;
+        } else if (otherDefClkVal) {
+            return otherDefClkVal;
+        }
+
+        return bt2s::nullopt;
+    }));
+}
+
+void MsgIter::_handleItem(const PktInfoItem& item)
+{
+    /*
+     * Save the packet beginning and end timestamps.
+     *
+     * Also keep the end timestamp of the previous packet: we might need
+     * it if there are discarded event records.
+     */
+    const auto prevPktEndDefClkVal = _mPktEndDefClkVal;
+
+    _mPktBeginDefClkVal = item.beginDefClkVal();
+    _mPktEndDefClkVal = item.endDefClkVal();
+
+    /*
+     * Emit a discarded events message if the count of discarded event
+     * records went up since the previous packet.
+     *
+     * For the first packet, `_mCurDiscErCounterSnap` isn't set: we
+     * don't have anything to compare to.
+     */
+    {
+        const auto& discErCounterSnap = item.discEventRecordCounterSnap();
+
+        if (_mCurDiscErCounterSnap) {
+            /*
+             * If the previous packet of this same stream had a discarded
+             * event record counter snapshot, then this one must have one
+             * too.
+             */
+            BT_ASSERT_DBG(discErCounterSnap);
+
+            // TODO: handle `*discErCounterSnap` being <= `*_mCurDiscErCounterSnap`
+            if (*discErCounterSnap > *_mCurDiscErCounterSnap) {
+                /* Create and initialize the message */
+                auto msg = this->_createInitDiscEventsMsg(prevPktEndDefClkVal);
+
+                /* Set its count */
+                msg->asDiscardedEvents().count(*discErCounterSnap - *_mCurDiscErCounterSnap);
+
+                /* Add to queue */
+                this->_addMsgToQueue(std::move(msg));
+            }
+        }
+
+        /* Set new current discarded event record counter snapshot */
+        _mCurDiscErCounterSnap = discErCounterSnap;
+    }
+
+    /*
+     * Emit a discarded packets message if there's a gap between the
+     * previous packet sequence number and the sequence number of this
+     * new packet.
+     */
+    {
+        const auto& seqNum = item.seqNum();
+
+        if (_mCurPktSeqNum) {
+            /*
+             * If the previous packet of this same stream had a sequence
+             * number, then this one must have one too.
+             */
+            BT_ASSERT_DBG(seqNum);
+
+            // TODO: handle `*seqNum` being <= `*_mCurPktSeqNum`
+            if (*_mCurPktSeqNum + 1 < *seqNum) {
+                /* Create and initialize the message */
+                const auto msg = this->_createInitDiscPktsMsg(prevPktEndDefClkVal);
+
+                /* Set its count */
+                msg->asDiscardedPackets().count(*seqNum - *_mCurPktSeqNum - 1);
+
+                /* Add to queue */
+                this->_addMsgToQueue(std::move(msg));
+            }
+        }
+
+        /* Set new packet sequence number */
+        _mCurPktSeqNum = seqNum;
+    }
+
+    /* There's no pending message */
+    BT_ASSERT_DBG(!_mCurMsg);
+
+    /*
+     * Depending on a quirk to handle, emit a packet beginning message
+     * now or delay said emission.
+     */
+    if (_mQuirks.eventRecordDefClkValLtPktBeginDefClkVal) {
+        _mDelayPktBeginMsgEmission = true;
+    } else {
+        /* No quirk to handle: emit the message now */
+        this->_emitPktBeginMsg(_mPktBeginDefClkVal);
+    }
+}
+
+bt2::Message::Shared MsgIter::_createEventMsg(const bt2::EventClass cls, const _OptUll& defClkVal)
+{
+    if (defClkVal) {
+        if (_mCurPkt) {
+            return _mSelfMsgIter.createEventMessage(cls, *_mCurPkt, *defClkVal);
+        } else {
+            return _mSelfMsgIter.createEventMessage(cls, _mStream, *defClkVal);
+        }
+    } else {
+        if (_mCurPkt) {
+            return _mSelfMsgIter.createEventMessage(cls, *_mCurPkt);
+        } else {
+            return _mSelfMsgIter.createEventMessage(cls, _mStream);
+        }
+    }
+}
+
+void MsgIter::_handleItem(const EventRecordInfoItem& item)
+{
+    // TODO: Test having a trace with only event record headers
+    BT_ASSERT_DBG(item.cls());
+    BT_ASSERT_DBG(item.cls()->libCls());
+    BT_ASSERT_DBG(!_mCurMsg);
+
+    /* Emit a packet beginning message now if required to fix a quirk */
+    if (_mDelayPktBeginMsgEmission) {
+        this->_emitDelayedPktBeginMsg(item.defClkVal());
+    }
+
+    /* Update the default clock value if needed */
+    if (item.defClkVal()) {
+        _mCurDefClkVal = *item.defClkVal();
+    }
+
+    /*
+     * Set as current message.
+     *
+     * The following items will gradually fill this message.
+     *
+     * This message will be emitted (added to the message queue) when
+     * handling the next `EventRecordEndItem`.
+     */
+    _mCurMsg = this->_createEventMsg(*item.cls()->libCls(), item.defClkVal());
+}
+
+void MsgIter::_handleItem(const FixedLenBitArrayFieldItem& item)
+{
+    if (_ignoreFieldItem(item)) {
+        return;
+    }
+
+    this->_stackTopCurSubFieldAndGoToNextSubField().asBitArray().valueAsInteger(item.uIntVal());
+}
+
+void MsgIter::_handleItem(const FixedLenBoolFieldItem& item)
+{
+    if (_ignoreFieldItem(item)) {
+        return;
+    }
+
+    this->_stackTopCurSubFieldAndGoToNextSubField().asBool().value(item.val());
+}
+
+void MsgIter::_handleItem(const FixedLenSIntFieldItem& item)
+{
+    this->_handleSIntFieldItem(item);
+}
+
+void MsgIter::_handleItem(const FixedLenUIntFieldItem& item)
+{
+    this->_handleUIntFieldItem(item);
+}
+
+void MsgIter::_handleItem(const FixedLenFloatFieldItem& item)
+{
+    const auto field = this->_stackTopCurSubFieldAndGoToNextSubField();
+
+    if (item.cls().len() == 32_bits) {
+        field.asSinglePrecisionReal().value(item.val());
+    } else {
+        BT_ASSERT_DBG(item.cls().len() == 64_bits);
+        field.asDoublePrecisionReal().value(item.val());
+    }
+}
+
+void MsgIter::_handleItem(const VarLenSIntFieldItem& item)
+{
+    this->_handleSIntFieldItem(item);
+}
+
+void MsgIter::_handleItem(const VarLenUIntFieldItem& item)
+{
+    this->_handleUIntFieldItem(item);
+}
+
+void MsgIter::_handleStrFieldBeginItem(const FieldItem& item)
+{
+    this->_stackTopCurSubField().asString().value("");
+    _mHaveNullChar = false;
+    _mUtf16NullCpFinder = NullCpFinder<2> {};
+    _mUtf32NullCpFinder = NullCpFinder<4> {};
+    _mStrBuf.clear();
+    _mCurStrFieldEncoding = item.cls().asStr().encoding();
+}
+
+void MsgIter::_handleStrFieldEndItem()
+{
+    switch (_mCurStrFieldEncoding) {
+    case StrEncoding::Utf16Be:
+    case StrEncoding::Utf16Le:
+    case StrEncoding::Utf32Be:
+    case StrEncoding::Utf32Le:
+    {
+        /* Convert to UTF-8 */
+        const auto utf8Str = bt2c::call([this] {
+            bt2c::ConstBytes inBytes {_mStrBuf.begin(), _mStrBuf.end()};
+
+            switch (_mCurStrFieldEncoding) {
+            case StrEncoding::Utf16Be:
+                return _mUnicodeConv.utf8FromUtf16Be(inBytes);
+            case StrEncoding::Utf16Le:
+                return _mUnicodeConv.utf8FromUtf16Le(inBytes);
+            case StrEncoding::Utf32Be:
+                return _mUnicodeConv.utf8FromUtf32Be(inBytes);
+            case StrEncoding::Utf32Le:
+                return _mUnicodeConv.utf8FromUtf32Le(inBytes);
+            default:
+                bt_common_abort();
+            }
+        });
+        const auto endIt =
+            !utf8Str.empty() && utf8Str.back() == 0 ? utf8Str.end() - 1 : utf8Str.end();
+
+        /* Append */
+        this->_stackTopCurSubField().asString().append(
+            reinterpret_cast<const char *>(utf8Str.data()), endIt - utf8Str.begin());
+    }
+
+    default:
+        break;
+    }
+
+    this->_stackTopGoToNextSubField();
+}
+
+void MsgIter::_handleItem(const NullTerminatedStrFieldBeginItem& item)
+{
+    this->_handleStrFieldBeginItem(item);
+}
+
+void MsgIter::_handleItem(const NullTerminatedStrFieldEndItem&)
+{
+    this->_handleStrFieldEndItem();
+}
+
+void MsgIter::_handleBlobRawDataItem(const RawDataItem& item)
+{
+    std::memcpy(&this->_stackTopCurSubField().asBlob().data()[_mCurBlobFieldDataOffset],
+                item.data().begin(), item.data().size());
+    _mCurBlobFieldDataOffset += item.data().size();
+}
+
+void MsgIter::_handleStrRawDataItem(const RawDataItem& item)
+{
+    if (_mHaveNullChar) {
+        /* No more text data */
+        return;
+    }
+
+    if (_mCurStrFieldEncoding == StrEncoding::Utf8) {
+        /* Try to find the first U+0000 codepoint */
+        const auto endIt = std::find(item.data().begin(), item.data().end(), 0);
+        _mHaveNullChar = endIt != item.data().end();
+
+        /* Append to current string field */
+        this->_stackTopCurSubField().asString().append(
+            reinterpret_cast<const char *>(item.data().data()), endIt - item.data().begin());
+    } else {
+        /* Try to find the first U+0000 codepoint */
+        auto endIt = item.data().end();
+        const auto afterNullCpIt = bt2c::call([this, &item] {
+            if (_mCurStrFieldEncoding == StrEncoding::Utf16Be ||
+                _mCurStrFieldEncoding == StrEncoding::Utf16Le) {
+                return _mUtf16NullCpFinder.findNullCp(item.data());
+            } else {
+                BT_ASSERT_DBG(_mCurStrFieldEncoding == StrEncoding::Utf32Be ||
+                              _mCurStrFieldEncoding == StrEncoding::Utf32Le);
+                return _mUtf32NullCpFinder.findNullCp(item.data());
+            }
+        });
+
+        if (afterNullCpIt) {
+            /* Found U+0000 */
+            endIt = *afterNullCpIt;
+            _mHaveNullChar = true;
+        }
+
+        /* Append to current string buffer */
+        _mStrBuf.insert(_mStrBuf.end(), item.data().begin(), endIt);
+    }
+}
+
+void MsgIter::_handleItem(const RawDataItem& item)
+{
+    if (this->_stackTopCurSubField().isString()) {
+        this->_handleStrRawDataItem(item);
+    } else {
+        BT_ASSERT_DBG(this->_stackTopCurSubField().isBlob());
+        this->_handleBlobRawDataItem(item);
+    }
+}
+
+void MsgIter::_handleItem(const StructFieldBeginItem&)
+{
+    if (_mStack.empty()) {
+        /* This is the root field of the current scope */
+        BT_ASSERT_DBG(_mCurScopeField);
+        this->_stackPush(*_mCurScopeField);
+    } else {
+        /* Use sub-field */
+        this->_stackPush(this->_stackTopCurSubFieldAndGoToNextSubField().asStructure());
+    }
+}
+
+void MsgIter::_handleItem(const StructFieldEndItem&)
+{
+    this->_stackPop();
+}
+
+void MsgIter::_handleItem(const StaticLenArrayFieldBeginItem&)
+{
+    this->_stackPush(this->_stackTopCurSubFieldAndGoToNextSubField().asArray());
+}
+
+void MsgIter::_handleItem(const DynLenArrayFieldBeginItem& item)
+{
+    auto arrayField = this->_stackTopCurSubFieldAndGoToNextSubField().asDynamicArray();
+
+    arrayField.length(item.len());
+    this->_stackPush(arrayField);
+}
+
+void MsgIter::_handleItem(const ArrayFieldEndItem&)
+{
+    this->_stackPop();
+}
+
+void MsgIter::_handleItem(const StaticLenBlobFieldBeginItem&)
+{
+    _mCurBlobFieldDataOffset = 0;
+}
+
+void MsgIter::_handleItem(const DynLenBlobFieldBeginItem& item)
+{
+    this->_stackTopCurSubField().asDynamicBlob().length(item.len().bytes());
+    _mCurBlobFieldDataOffset = 0;
+}
+
+void MsgIter::_handleItem(const BlobFieldEndItem&)
+{
+    this->_stackTopGoToNextSubField();
+}
+
+void MsgIter::_handleItem(const NonNullTerminatedStrFieldBeginItem& item)
+{
+    this->_handleStrFieldBeginItem(item);
+}
+
+void MsgIter::_handleItem(const NonNullTerminatedStrFieldEndItem&)
+{
+    this->_handleStrFieldEndItem();
+}
+
+void MsgIter::_handleItem(const VariantFieldBeginItem& item)
+{
+    auto field = this->_stackTopCurSubFieldAndGoToNextSubField().asVariant();
+
+    field.selectOption(item.selectedOptIndex());
+    this->_stackPush(field);
+}
+
+void MsgIter::_handleItem(const VariantFieldEndItem&)
+{
+    this->_stackPop();
+}
+
+void MsgIter::_handleItem(const OptionalFieldBeginItem& item)
+{
+    auto field = this->_stackTopCurSubFieldAndGoToNextSubField().asOption();
+
+    field.hasField(item.isEnabled());
+    this->_stackPush(field);
+}
+
+void MsgIter::_handleItem(const OptionalFieldEndItem&)
+{
+    this->_stackPop();
+}
+
+void MsgIter::_addMsgToQueue(bt2::ConstMessage::Shared msg)
+{
+    _mMsgs.emplace(std::move(msg));
+}
+
+bt2::ConstMessage::Shared MsgIter::_releaseNextMsg()
+{
+    if (_mMsgs.empty()) {
+        return bt2::ConstMessage::Shared {};
+    }
+
+    auto msg = std::move(_mMsgs.front());
+
+    _mMsgs.pop();
+    return msg;
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/msg-iter.hpp b/src/plugins/ctf/common/src/msg-iter.hpp
new file mode 100644 (file)
index 0000000..13fa881
--- /dev/null
@@ -0,0 +1,598 @@
+/*
+ * SPDX-License-Identifier: MIT
+ *
+ * Copyright (c) 2022-2024 EfficiOS, Inc
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_MSG_ITER_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_MSG_ITER_HPP
+
+#include <queue>
+#include <stack>
+
+#include <babeltrace2/babeltrace.h>
+
+#include "common/assert.h"
+#include "cpp-common/bt2/message.hpp"
+#include "cpp-common/bt2/self-message-iterator.hpp"
+#include "cpp-common/bt2/trace-ir.hpp"
+#include "cpp-common/bt2c/aliases.hpp"
+#include "cpp-common/bt2c/unicode-conv.hpp"
+
+#include "item-seq/item-seq-iter.hpp"
+#include "item-seq/item-visitor.hpp"
+#include "item-seq/logging-item-visitor.hpp"
+#include "null-cp-finder.hpp"
+#include "plugins/ctf/common/src/metadata/ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Various quirks that a CTF message iterator can work around.
+ */
+struct MsgIterQuirks final
+{
+    /* Packet end timestamps set to zero */
+    bool pktEndDefClkValZero = false;
+
+    /*
+     * Timestamp of last event record of a packet is greater than the
+     * beginning timestamp of the next packet.
+     */
+    bool eventRecordDefClkValGtNextPktBeginDefClkVal = false;
+
+    /*
+     * Timestamp of the first event record of a packet is less than the
+     * beginning timestamp of its packet.
+     */
+    bool eventRecordDefClkValLtPktBeginDefClkVal = false;
+};
+
+/*
+ * CTF message iterator.
+ *
+ * Such an iterator essentially converts the items of an underlying item
+ * sequence iterator to corresponding libbabeltrace2 messages.
+ *
+ * Therefore, as a user, you provide:
+ *
+ * • A medium, which provides data stream data to the iterator.
+ *
+ * • A CTF IR trace class, which describes how to decode said data.
+ *
+ * • A libbabeltrace2 self message iterator and stream, which the
+ *   iterator needs to create libbabeltrace2 messages.
+ *
+ * A CTF message iterator may automatically fix some common quirks
+ * (see `MsgIterQuirks`).
+ */
+class MsgIter final
+{
+public:
+    /*
+     * Builds a CTF message iterator, using `traceCls` and `medium` to
+     * decode a data stream identified by `stream`, and `selfMsgIter`
+     * and `stream` to create libbabeltrace2 messages.
+     *
+     * `quirks` indicates which quirks to fix.
+     *
+     * It's guaranteed that this constructor doesn't throw
+     * `bt2c::TryAgain` or a medium error.
+     */
+    explicit MsgIter(bt2::SelfMessageIterator selfMsgIter, const ctf::src::TraceCls& traceCls,
+                     bt2s::optional<bt2c::Uuid> expectedMetadataStreamUuid, bt2::Stream stream,
+                     Medium::UP medium, const MsgIterQuirks& quirks,
+                     const bt2c::Logger& parentLogger);
+
+    /* Disable copy/move operations */
+    MsgIter(const MsgIter&) = delete;
+    MsgIter& operator=(const MsgIter&) = delete;
+
+    /*
+     * Advances the iterator to the next message, returning:
+     *
+     * A libbabeltrace2 message:
+     *     Said next message.
+     *
+     * `bt2s::nullopt`:
+     *     The iterator is ended.
+     *
+     * May throw whatever Medium::buf() may throw as well
+     * as `bt2c::Error`.
+     */
+    bt2::ConstMessage::Shared next();
+
+private:
+    /* An optional `unsigned long long` value */
+    using _OptUll = bt2s::optional<unsigned long long>;
+
+    /* Single frame of a message iterator stack */
+    class _StackFrame
+    {
+    public:
+        explicit _StackFrame(const bt2::StructureField field) noexcept :
+            _mFieldType {_FieldType::Struct}, _mField {field}
+        {
+        }
+
+        explicit _StackFrame(const bt2::VariantField field) noexcept :
+            _mFieldType {_FieldType::Variant}, _mField(field)
+        {
+        }
+
+        explicit _StackFrame(const bt2::OptionField field) noexcept :
+            _mFieldType {_FieldType::Option}, _mField(field)
+        {
+        }
+
+        explicit _StackFrame(const bt2::ArrayField field) noexcept :
+            _mFieldType(_FieldType::Array), _mField(field)
+        {
+        }
+
+        bt2::StructureField structureField() const noexcept
+        {
+            BT_ASSERT_DBG(_mFieldType == _FieldType::Struct);
+            return _mField.structure;
+        }
+
+        bt2::VariantField variantField() const noexcept
+        {
+            BT_ASSERT_DBG(_mFieldType == _FieldType::Variant);
+            return _mField.variant;
+        }
+
+        bt2::OptionField optionField() const noexcept
+        {
+            BT_ASSERT_DBG(_mFieldType == _FieldType::Option);
+            return _mField.option;
+        }
+
+        bt2::ArrayField arrayField() const noexcept
+        {
+            BT_ASSERT_DBG(_mFieldType == _FieldType::Array);
+            return _mField.array;
+        }
+
+        unsigned long long subFieldIndex() const noexcept
+        {
+            return _mSubFieldIndex;
+        }
+
+        void goToNextSubField() noexcept
+        {
+            /*
+             * We unconditionally increment `_mSubFieldIndex`, even if
+             * the current field is a variant/option field, because
+             * curSubField() doesn't care about `_mSubFieldIndex` for
+             * those cases anyway.
+             *
+             * In practice, `_mSubFieldIndex` will reach one with a
+             * variant/option field, but curSubField() will never be
+             * called with `_mSubFieldIndex` being something else
+             * than zero.
+             */
+            ++_mSubFieldIndex;
+        }
+
+        bt2::Field curSubField() noexcept
+        {
+            switch (_mFieldType) {
+            case _FieldType::Struct:
+                BT_ASSERT_DBG(_mSubFieldIndex < _mField.structure.cls().length());
+                return _mField.structure[_mSubFieldIndex];
+
+            case _FieldType::Variant:
+                BT_ASSERT_DBG(_mSubFieldIndex == 0);
+                return _mField.variant.selectedOptionField();
+
+            case _FieldType::Option:
+                BT_ASSERT_DBG(_mSubFieldIndex == 0);
+                BT_ASSERT_DBG(_mField.option.hasField());
+                return *_mField.option.field();
+
+            case _FieldType::Array:
+                BT_ASSERT_DBG(_mSubFieldIndex < _mField.array.length());
+                return _mField.array[_mSubFieldIndex];
+
+            default:
+                bt_common_abort();
+            }
+        }
+
+        bt2::Field curSubFieldAndGoToNextSubField() noexcept
+        {
+            const auto field = this->curSubField();
+
+            this->goToNextSubField();
+            return field;
+        }
+
+    private:
+        /* Selector of `_mField` below */
+        enum class _FieldType
+        {
+            /* Selects `structure` */
+            Struct = 1,
+
+            /* Selects `variant` */
+            Variant,
+
+            /* Selects `option` */
+            Option,
+
+            /* Selects `array` */
+            Array,
+        } _mFieldType;
+
+        /* Field of this frame, selected by `_mFieldType` above */
+        union _Field
+        {
+            explicit _Field(const bt2::StructureField field) noexcept
+            {
+                new (&structure) bt2::StructureField {field};
+            }
+
+            explicit _Field(const bt2::VariantField field) noexcept
+            {
+                new (&variant) bt2::VariantField {field};
+            }
+
+            explicit _Field(const bt2::OptionField field) noexcept
+            {
+                new (&option) bt2::OptionField {field};
+            }
+
+            explicit _Field(const bt2::ArrayField field) noexcept
+            {
+                new (&array) bt2::ArrayField {field};
+            }
+
+            static_assert(std::is_trivially_destructible<bt2::StructureField>::value,
+                          "`bt2::StructureField` is trivially destructible.");
+            static_assert(std::is_trivially_destructible<bt2::VariantField>::value,
+                          "`bt2::VariantField` is trivially destructible.");
+            static_assert(std::is_trivially_destructible<bt2::OptionField>::value,
+                          "`bt2::OptionField` is trivially destructible.");
+            static_assert(std::is_trivially_destructible<bt2::ArrayField>::value,
+                          "`bt2::ArrayField` is trivially destructible.");
+
+            bt2::StructureField structure;
+            bt2::VariantField variant;
+            bt2::OptionField option;
+            bt2::ArrayField array;
+        } _mField;
+
+        /*
+         * Index of, depending on `_mFieldType` above:
+         *
+         * `_FieldType::Struct`:
+         *     The current member of `_mField.structure`.
+         *
+         * `_FieldType::Array`:
+         *     The current element field of `_mField.array`.
+         *
+         * `_FieldType::Variant`:
+         * `_FieldType::Option`:
+         *     Not applicable.
+         */
+        unsigned long long _mSubFieldIndex = 0;
+    };
+
+    /*
+     * Returns whether or not to ignore the field of `item`.
+     */
+    static bool _ignoreFieldItem(const FieldItem& item) noexcept
+    {
+        return !item.cls().libCls();
+    }
+
+    /*
+     * Handles the item `item`, changing the state accordingly, possibly
+     * adding one or more messages to `_mMsgs`.
+     */
+    void _handleItem(const Item& item);
+
+    /* Specific item handlers below */
+    void _handleItem(const ArrayFieldEndItem& item);
+    void _handleItem(const BlobFieldEndItem& item);
+    void _handleItem(const DataStreamInfoItem& item);
+    void _handleItem(const DynLenArrayFieldBeginItem& item);
+    void _handleItem(const DynLenBlobFieldBeginItem& item);
+    void _handleItem(const EventRecordEndItem& item);
+    void _handleItem(const EventRecordInfoItem& item);
+    void _handleItem(const FixedLenBitArrayFieldItem& item);
+    void _handleItem(const FixedLenBoolFieldItem& item);
+    void _handleItem(const FixedLenFloatFieldItem& item);
+    void _handleItem(const FixedLenSIntFieldItem& item);
+    void _handleItem(const FixedLenUIntFieldItem& item);
+    void _handleItem(const MetadataStreamUuidItem& item);
+    void _handleItem(const NonNullTerminatedStrFieldBeginItem& item);
+    void _handleItem(const NonNullTerminatedStrFieldEndItem& item);
+    void _handleItem(const NullTerminatedStrFieldBeginItem& item);
+    void _handleItem(const NullTerminatedStrFieldEndItem& item);
+    void _handleItem(const OptionalFieldBeginItem& item);
+    void _handleItem(const OptionalFieldEndItem& item);
+    void _handleItem(const PktBeginItem& item);
+    void _handleItem(const PktContentEndItem& item);
+    void _handleItem(const PktEndItem& item);
+    void _handleItem(const PktInfoItem& item);
+    void _handleItem(const PktMagicNumberItem& item);
+    void _handleBlobRawDataItem(const RawDataItem& item);
+    void _handleStrRawDataItem(const RawDataItem& item);
+    void _handleItem(const RawDataItem& item);
+    void _handleItem(const ScopeBeginItem& item);
+    void _handleItem(const ScopeEndItem& item);
+    void _handleItem(const StaticLenArrayFieldBeginItem& item);
+    void _handleItem(const StaticLenBlobFieldBeginItem& item);
+    void _handleItem(const StructFieldBeginItem& item);
+    void _handleItem(const StructFieldEndItem& item);
+    void _handleItem(const VariantFieldBeginItem& item);
+    void _handleItem(const VariantFieldEndItem& item);
+    void _handleItem(const VarLenSIntFieldItem& item);
+    void _handleItem(const VarLenUIntFieldItem& item);
+    void _handleStrFieldBeginItem(const FieldItem& item);
+    void _handleStrFieldEndItem();
+
+    template <typename ItemT>
+    void _handleUIntFieldItem(const ItemT& item)
+    {
+        if (_ignoreFieldItem(item)) {
+            return;
+        }
+
+        const auto field = this->_stackTopCurSubFieldAndGoToNextSubField();
+
+        field.asUnsignedInteger().value(item.val());
+    }
+
+    template <typename ItemT>
+    void _handleSIntFieldItem(const ItemT& item)
+    {
+        if (_ignoreFieldItem(item)) {
+            return;
+        }
+
+        const auto field = this->_stackTopCurSubFieldAndGoToNextSubField();
+
+        field.asSignedInteger().value(item.val());
+    }
+
+    /*
+     * Calls goToNextSubField() for the top stack frame.
+     */
+    void _stackTopGoToNextSubField()
+    {
+        BT_ASSERT_DBG(!_mStack.empty());
+        _mStack.top().goToNextSubField();
+    }
+
+    /*
+     * Returns curSubField() for the top stack frame.
+     */
+    bt2::Field _stackTopCurSubField()
+    {
+        BT_ASSERT_DBG(!_mStack.empty());
+        return _mStack.top().curSubField();
+    }
+
+    /*
+     * Returns curSubFieldAndGoToNextSubField() for the top stack frame.
+     */
+    bt2::Field _stackTopCurSubFieldAndGoToNextSubField()
+    {
+        BT_ASSERT_DBG(!_mStack.empty());
+        return _mStack.top().curSubFieldAndGoToNextSubField();
+    }
+
+    /*
+     * Pushes a stack frame managing `field` on the stack.
+     */
+    template <typename FieldT>
+    void _stackPush(const FieldT field)
+    {
+        _mStack.push(_StackFrame {field});
+    }
+
+    /*
+     * Removes the top stack frame.
+     */
+    void _stackPop()
+    {
+        BT_ASSERT_DBG(!_mStack.empty());
+        _mStack.pop();
+    }
+
+    /*
+     * Sets the current packet to `pkt`.
+     */
+    void _curPkt(bt2::Packet::Shared pkt)
+    {
+        BT_ASSERT_DBG(!_mCurPkt);
+        _mCurPkt = std::move(pkt);
+    }
+
+    /*
+     * Resets the current packet.
+     */
+    void _resetCurPkt()
+    {
+        BT_ASSERT_DBG(_mCurPkt);
+        _mCurPkt.reset();
+    }
+
+    /*
+     * Creates and returns an initial discarded events message, not
+     * setting any specific count.
+     */
+    bt2::Message::Shared _createInitDiscEventsMsg(const _OptUll& prevPktEndDefClkVal);
+
+    /*
+     * Creates and returns an initial discarded packets message, not
+     * setting any specific count.
+     */
+    bt2::Message::Shared _createInitDiscPktsMsg(const _OptUll& prevPktEndDefClkVal);
+
+    /*
+     * Creates a packet end message and, if needed, updates the current
+     * timestamp.
+     */
+    bt2::Message::Shared _createPktEndMsgAndUpdateCurDefClkVal();
+
+    /*
+     * Creates an event message using the class `cls` and having
+     * `defClkVal` as its timestamp.
+     */
+    bt2::Message::Shared _createEventMsg(bt2::EventClass cls, const _OptUll& defClkVal);
+
+    /*
+     * Emits a packet beginning message having `defClkVal`, if set, as
+     * its default clock snapshot.
+     */
+    void _emitPktBeginMsg(const _OptUll& defClkVal);
+
+    /*
+     * Emits a delayed packet beginning message, considering the other
+     * timestamp `otherDefClkVal`.
+     */
+    void _emitDelayedPktBeginMsg(const _OptUll& otherDefClkVal);
+
+    /*
+     * Adds the message `msg` to the message queue.
+     */
+    void _addMsgToQueue(bt2::ConstMessage::Shared msg);
+
+    /*
+     * Returns one of:
+     *
+     * A shared libbabeltrace2 message:
+     *     The next available message from the message queue, removing
+     *     it from the queue.
+     *
+     * `bt2s::nullopt`:
+     *     The message queue is empty.
+     */
+    bt2::ConstMessage::Shared _releaseNextMsg();
+
+    /* Logger */
+    bt2c::Logger _mLogger;
+
+    /* libbabeltrace2 self message iterator to create messages (weak) */
+    bt2::SelfMessageIterator _mSelfMsgIter;
+
+    /* Corresponding libbabeltrace2 stream */
+    bt2::Stream _mStream;
+
+    /* Expected metadata stream UUID, if any */
+    bt2s::optional<bt2c::Uuid> _mExpectedMetadataStreamUuid;
+
+    /* Quirks to fix */
+    MsgIterQuirks _mQuirks;
+
+    /* Underlying item sequence iterator to decode the data stream */
+    ItemSeqIter _mItemSeqIter;
+
+    /* Whether or not the iterator is ended */
+    bool _mIsDone = false;
+
+    /*
+     * Queue of already created messages.
+     *
+     * We're using a queue instead of keeping a single message because
+     * because one item (from `_mItemSeqIter`) may correspond to more
+     * than one libbabeltrace2 message.
+     */
+    std::queue<bt2::ConstMessage::Shared> _mMsgs;
+
+    /* Stack */
+    std::stack<_StackFrame> _mStack;
+
+    /* Root field of current scope */
+    bt2::OptionalBorrowedObject<bt2::StructureField> _mCurScopeField;
+
+    /*
+     * Whether or not to skip items until reaching the end of the
+     * current scope.
+     */
+    bool _mSkipItemsUntilScopeEndItem = false;
+
+    /*
+     * If set: a message that we're building, that's not yet ready to be
+     * returned.
+     */
+    bt2::Message::Shared _mCurMsg;
+
+    /*
+     * If set: the current packet.
+     *
+     * Set while handling a `PktBeginItem` and reset while handling a
+     * `PktEndItem`.
+     */
+    bt2::Packet::Shared _mCurPkt;
+
+    /* Current packet sequence number, if any */
+    _OptUll _mCurPktSeqNum;
+
+    /* Current default clock value, if any */
+    _OptUll _mCurDefClkVal;
+
+    /* Current discarded event record counter snapshot, if any */
+    _OptUll _mCurDiscErCounterSnap;
+
+    /*
+     * Values of the beginning and end timestamps of the current packet.
+     *
+     * Set while handling a `PktInfoItem` and reset while handling a
+     * `PktEndItem`.
+     */
+    _OptUll _mPktBeginDefClkVal;
+    _OptUll _mPktEndDefClkVal;
+
+    /*
+     * Whether or not a stream beginning message was provided to the
+     * user.
+     */
+    bool _mEmittedStreamBeginMsg = false;
+
+    /*
+     * Whether or not to delay the emission of a packet beginning
+     * message.
+     */
+    bool _mDelayPktBeginMsgEmission = false;
+
+    /*
+     * Whether or not, while processing `RawDataItem` items, we got a
+     * null character.
+     */
+    bool _mHaveNullChar = false;
+
+    /* Null codepoint finders for UTF-16 and UTF-32 */
+    NullCpFinder<2> _mUtf16NullCpFinder;
+    NullCpFinder<4> _mUtf32NullCpFinder;
+
+    /* Unicode converter to decode UTF-16 and UTF-32 strings */
+    bt2c::UnicodeConv _mUnicodeConv;
+
+    /* Buffer holding the string to convert to UTF-8 */
+    std::vector<std::uint8_t> _mStrBuf;
+
+    /*
+     * Current BLOB field data offset while processing BLOB field
+     * section items.
+     */
+    std::size_t _mCurBlobFieldDataOffset = 0;
+
+    /*
+     * Current string field encoding, if any.
+     */
+    StrEncoding _mCurStrFieldEncoding = StrEncoding::Utf8;
+
+    /* Helper to log items */
+    LoggingItemVisitor _mLoggingVisitor;
+};
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_MSG_ITER_HPP */
This page took 0.038924 seconds and 4 git commands to generate.