Add `ctf::src::Ctf2MetadataStreamParser` class (JSON text sequence)
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Tue, 21 May 2024 17:57:03 +0000 (13:57 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Wed, 4 Sep 2024 19:05:14 +0000 (15:05 -0400)
This patch adds `ctf::src::Ctf2MetadataStreamParser` which inherits
`ctf::src::MetadataStreamParser` to implement a CTF 2 metadata stream
(JSON text sequence) parser.

The parser uses `Ctf2JsonAnyFragmentValReq` to validate each fragment,
and also performs other validation at the JSON level.

Signed-off-by: Philippe Proulx <eeppeliteloop@gmail.com>
Change-Id: I29a29e589b9de998a2fadf7be96871902e3fd76a
Reviewed-on: https://review.lttng.org/c/babeltrace/+/12736

18 files changed:
src/Makefile.am
src/cpp-common/bt2c/make-span.hpp
src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.hpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.hpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/fcs-with-role.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/fcs-with-role.hpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/key-fc-types.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/key-fc-types.hpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/normalize-field-locs.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/normalize-field-locs.hpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.hpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/utils.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/utils.hpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.hpp [new file with mode: 0644]

index 383f9778294420cadb0bb9506d61e17258f149da..9a943ce583bc83d274997051693c6854397afcaa 100644 (file)
@@ -703,10 +703,26 @@ plugins_ctf_babeltrace_plugin_ctf_la_SOURCES = \
        plugins/ctf/common/src/item-seq/medium.hpp \
        plugins/ctf/common/src/metadata/ctf-ir.cpp \
        plugins/ctf/common/src/metadata/ctf-ir.hpp \
+       plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.cpp \
+       plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.hpp \
+       plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.cpp \
+       plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.hpp \
+       plugins/ctf/common/src/metadata/json/fcs-with-role.cpp \
+       plugins/ctf/common/src/metadata/json/fcs-with-role.hpp \
+       plugins/ctf/common/src/metadata/json/key-fc-types.cpp \
+       plugins/ctf/common/src/metadata/json/key-fc-types.hpp \
+       plugins/ctf/common/src/metadata/json/normalize-field-locs.cpp \
+       plugins/ctf/common/src/metadata/json/normalize-field-locs.hpp \
+       plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.cpp \
+       plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.hpp \
        plugins/ctf/common/src/metadata/json/strings.cpp \
        plugins/ctf/common/src/metadata/json/strings.hpp \
+       plugins/ctf/common/src/metadata/json/utils.cpp \
+       plugins/ctf/common/src/metadata/json/utils.hpp \
        plugins/ctf/common/src/metadata/json/val-req.cpp \
        plugins/ctf/common/src/metadata/json/val-req.hpp \
+       plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.cpp \
+       plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.hpp \
        plugins/ctf/common/src/metadata/metadata-stream-parser.cpp \
        plugins/ctf/common/src/metadata/metadata-stream-parser.hpp \
        plugins/ctf/common/src/metadata/normalize-clk-offset.cpp \
index 2270a8fba43115ba772c3255389271e1b25fed28..4815d67157317c964be40240fc403784dfa83d61 100644 (file)
@@ -17,6 +17,12 @@ inline constexpr bt2s::span<T> makeSpan(T * const ptr, const size_t count) noexc
     return nonstd::make_span(ptr, count);
 }
 
+template <class T>
+inline constexpr bt2s::span<T> makeSpan(T *first, T *last) noexcept
+{
+    return nonstd::make_span(first, last);
+}
+
 } /* namespace bt2c */
 
 #endif /* BABELTRACE_CPP_COMMON_BT2C_MAKE_SPAN_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.cpp b/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.cpp
new file mode 100644 (file)
index 0000000..9941685
--- /dev/null
@@ -0,0 +1,589 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "common/assert.h"
+#include "cpp-common/bt2c/contains.hpp"
+#include "cpp-common/bt2c/logging.hpp"
+#include "cpp-common/bt2s/optional.hpp"
+
+#include "../ctf-ir.hpp"
+#include "ctf-2-fc-builder.hpp"
+#include "strings.hpp"
+#include "utils.hpp"
+
+namespace ctf {
+namespace src {
+namespace {
+
+/*
+ * Creates and returns the set of unsigned integer field roles of the
+ * JSON unsigned integer field class value `jsonFc`.
+ */
+UIntFieldRoles uIntFieldRolesOfJsonUIntFc(const bt2c::JsonObjVal& jsonFc)
+{
+    UIntFieldRoles roles;
+    const auto jsonRoles = jsonFc[jsonstr::roles];
+
+    if (!jsonRoles) {
+        /* No roles */
+        return roles;
+    }
+
+    for (auto& jsonRole : jsonRoles->asArray()) {
+        auto& roleName = *jsonRole->asStr();
+
+        if (roleName == jsonstr::dataStreamClsId) {
+            roles.insert(UIntFieldRole::DataStreamClsId);
+        } else if (roleName == jsonstr::dataStreamId) {
+            roles.insert(UIntFieldRole::DataStreamId);
+        } else if (roleName == jsonstr::pktMagicNumber) {
+            roles.insert(UIntFieldRole::PktMagicNumber);
+        } else if (roleName == jsonstr::defClkTs) {
+            roles.insert(UIntFieldRole::DefClkTs);
+        } else if (roleName == jsonstr::discEventRecordCounterSnap) {
+            roles.insert(UIntFieldRole::DiscEventRecordCounterSnap);
+        } else if (roleName == jsonstr::pktContentLen) {
+            roles.insert(UIntFieldRole::PktContentLen);
+        } else if (roleName == jsonstr::pktTotalLen) {
+            roles.insert(UIntFieldRole::PktTotalLen);
+        } else if (roleName == jsonstr::pktEndDefClkTs) {
+            roles.insert(UIntFieldRole::PktEndDefClkTs);
+        } else if (roleName == jsonstr::pktSeqNum) {
+            roles.insert(UIntFieldRole::PktSeqNum);
+        } else {
+            BT_ASSERT(roleName == jsonstr::eventRecordClsId);
+            roles.insert(UIntFieldRole::EventRecordClsId);
+        }
+    }
+
+    return roles;
+}
+
+/*
+ * Creates and returns an integer range set (of type
+ * `IntRangeSet<ValT>`) from the JSON integer range set
+ * value `jsonIntRangeSet`.
+ */
+template <typename ValT>
+IntRangeSet<ValT> intRangeSetFromJsonIntRangeSet(const bt2c::JsonArrayVal& jsonIntRangeSet)
+{
+    typename IntRangeSet<ValT>::Set ranges;
+
+    for (auto& jsonRange : jsonIntRangeSet) {
+        auto& jsonRangeArray = jsonRange->asArray();
+
+        BT_ASSERT(jsonRangeArray.size() == 2);
+        ranges.emplace(
+            IntRangeSet<ValT>::Range::makeTemp(rawIntValFromJsonIntVal<ValT>(jsonRangeArray[0]),
+                                               rawIntValFromJsonIntVal<ValT>(jsonRangeArray[1])));
+    }
+
+    return IntRangeSet<ValT> {std::move(ranges)};
+}
+
+/*
+ * Creates and returns the integer field class mappings (of type
+ * `IntFcT::Mappings`) of the JSON integer field class value `jsonFc`.
+ */
+template <typename IntFcT>
+typename IntFcT::Mappings intFcMappingsOfJsonIntFc(const bt2c::JsonObjVal& jsonFc)
+{
+    typename IntFcT::Mappings mappings;
+
+    if (const auto jsonMappings = jsonFc[jsonstr::mappings]) {
+        /* At least one mapping */
+        for (auto& keyJsonIntRangesPair : jsonMappings->asObj()) {
+            mappings.insert(std::make_pair(keyJsonIntRangesPair.first,
+                                           intRangeSetFromJsonIntRangeSet<typename IntFcT::Val>(
+                                               keyJsonIntRangesPair.second->asArray())));
+        }
+    }
+
+    return mappings;
+}
+
+/*
+ * Returns the preferred display base of the JSON integer field class
+ * value `jsonFc`.
+ */
+DispBase prefDispBaseOfJsonIntFc(const bt2c::JsonObjVal& jsonFc) noexcept
+{
+    return static_cast<DispBase>(jsonFc.rawVal(jsonstr::prefDispBase, 10ULL));
+}
+
+/*
+ * Creates and returns a fixed-length integer field class from the JSON
+ * fixed-length integer field class value `jsonFc` and the
+ * other parameters.
+ */
+Fc::UP fcFromJsonFixedLenIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                               const unsigned int align, const bt2c::DataLen len,
+                               const ByteOrder byteOrder, const BitOrder bitOrder, OptAttrs&& attrs)
+{
+    /* Preferred display base */
+    const auto prefDispBase = prefDispBaseOfJsonIntFc(jsonFc);
+
+    /* Create field class */
+    if (type == jsonstr::fixedLenUInt) {
+        return createFixedLenUIntFc(jsonFc.loc(), align, len, byteOrder, bitOrder, prefDispBase,
+                                    intFcMappingsOfJsonIntFc<FixedLenUIntFc>(jsonFc),
+                                    uIntFieldRolesOfJsonUIntFc(jsonFc), std::move(attrs));
+    } else {
+        BT_ASSERT(type == jsonstr::fixedLenSInt);
+        return createFixedLenSIntFc(jsonFc.loc(), align, len, byteOrder, bitOrder, prefDispBase,
+                                    intFcMappingsOfJsonIntFc<FixedLenSIntFc>(jsonFc),
+                                    std::move(attrs));
+    }
+}
+
+/*
+ * Returns the length of the JSON field class value `jsonFc`.
+ */
+unsigned long long lenOfJsonFc(const bt2c::JsonObjVal& jsonFc) noexcept
+{
+    return jsonFc.rawUIntVal(jsonstr::len);
+}
+
+/*
+ * Creates and returns the fixed-length bit map field class flags (of type
+ * of the JSON integer field class value `jsonFc`.
+ */
+FixedLenBitMapFc::Flags fixedLenBitMapFlagsOfJsonFixedLenBitMapFc(const bt2c::JsonObjVal& jsonFc)
+{
+    using Val = FixedLenBitMapFc::Flags::value_type::second_type::Val;
+
+    FixedLenBitMapFc::Flags flags;
+    const auto jsonFlags = jsonFc[jsonstr::flags];
+
+    for (auto& keyJsonIntRangesPair : jsonFlags->asObj()) {
+        flags.insert(std::make_pair(
+            keyJsonIntRangesPair.first,
+            intRangeSetFromJsonIntRangeSet<Val>(keyJsonIntRangesPair.second->asArray())));
+    }
+
+    return flags;
+}
+
+/*
+ * Creates and returns a fixed-length bit array field class from the
+ * JSON fixed-length bit array field class value `jsonFc` and the other
+ * parameters.
+ */
+Fc::UP fcFromJsonFixedLenBitArrayFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                                    OptAttrs&& attrs)
+{
+    /* Alignment */
+    const auto align = jsonFc.rawVal(jsonstr::align, 1ULL);
+
+    /* Length */
+    const auto len = bt2c::DataLen::fromBits(lenOfJsonFc(jsonFc));
+
+    /* Byte order */
+    const auto byteOrder = jsonFc.rawStrVal(jsonstr::byteOrder) == jsonstr::littleEndian ?
+                               ByteOrder::Little :
+                               ByteOrder::Big;
+
+    /* Bit order */
+    const auto bitOrder = bt2c::call([&jsonFc, byteOrder] {
+        const auto bitOrderStr = optStrOfObj(jsonFc, jsonstr::bitOrder);
+
+        if (!bitOrderStr) {
+            return byteOrder == ByteOrder::Little ? BitOrder::FirstToLast : BitOrder::LastToFirst;
+        }
+
+        return *bitOrderStr == jsonstr::ftl ? BitOrder::FirstToLast : BitOrder::LastToFirst;
+    });
+
+    /* Create field class */
+    if (type == jsonstr::fixedLenBitArray) {
+        return createFixedLenBitArrayFc(jsonFc.loc(), align, len, byteOrder, bitOrder,
+                                        std::move(attrs));
+    } else if (type == jsonstr::fixedLenBitMap) {
+        return createFixedLenBitMapFc(jsonFc.loc(), align, len, byteOrder,
+                                      fixedLenBitMapFlagsOfJsonFixedLenBitMapFc(jsonFc), bitOrder,
+                                      std::move(attrs));
+    } else if (type == jsonstr::fixedLenBool) {
+        return createFixedLenBoolFc(jsonFc.loc(), align, len, byteOrder, bitOrder,
+                                    std::move(attrs));
+    } else if (type == jsonstr::fixedLenUInt || type == jsonstr::fixedLenSInt) {
+        return fcFromJsonFixedLenIntFc(jsonFc, type, align, len, byteOrder, bitOrder,
+                                       std::move(attrs));
+    } else {
+        BT_ASSERT(type == jsonstr::fixedLenFloat);
+        return createFixedLenFloatFc(jsonFc.loc(), align, len, byteOrder, bitOrder,
+                                     std::move(attrs));
+    }
+}
+
+/*
+ * Creates and returns a variable-length integer field class from the
+ * JSON variable-length integer field class value `jsonFc` and the
+ * other parameters.
+ */
+Fc::UP fcFromJsonVarLenIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                             OptAttrs&& attrs)
+{
+    /* Preferred display base */
+    const auto prefDispBase = prefDispBaseOfJsonIntFc(jsonFc);
+
+    if (type == jsonstr::varLenUInt) {
+        return createVarLenUIntFc(jsonFc.loc(), prefDispBase,
+                                  intFcMappingsOfJsonIntFc<VarLenUIntFc>(jsonFc),
+                                  uIntFieldRolesOfJsonUIntFc(jsonFc), std::move(attrs));
+    } else {
+        BT_ASSERT(type == jsonstr::varLenSInt);
+        return createVarLenSIntFc(jsonFc.loc(), prefDispBase,
+                                  intFcMappingsOfJsonIntFc<VarLenSIntFc>(jsonFc), std::move(attrs));
+    }
+}
+
+/*
+ * Creates and returns the field location of the JSON field class value
+ * `jsonFc` from its `key` property.
+ */
+FieldLoc fieldLocOfJsonFc(const bt2c::JsonObjVal& jsonFc, const std::string& key)
+{
+    auto& jsonLoc = jsonFc[key]->asObj();
+
+    /* Origin (scope) */
+    const auto origin = bt2c::call([&jsonLoc]() -> bt2s::optional<Scope> {
+        const auto jsonOrig = jsonLoc[jsonstr::origin];
+
+        if (!jsonOrig) {
+            return bt2s::nullopt;
+        }
+
+        auto& scopeName = *jsonOrig->asStr();
+
+        if (scopeName == jsonstr::pktHeader) {
+            return Scope::PktHeader;
+        } else if (scopeName == jsonstr::pktCtx) {
+            return Scope::PktCtx;
+        } else if (scopeName == jsonstr::eventRecordHeader) {
+            return Scope::EventRecordHeader;
+        } else if (scopeName == jsonstr::eventRecordCommonCtx) {
+            return Scope::CommonEventRecordCtx;
+        } else if (scopeName == jsonstr::eventRecordSpecCtx) {
+            return Scope::SpecEventRecordCtx;
+        } else {
+            BT_ASSERT(scopeName == jsonstr::eventRecordPayload);
+            return Scope::EventRecordPayload;
+        }
+    });
+
+    /* Path */
+    FieldLoc::Items items;
+
+    {
+        auto& jsonPath = jsonLoc[jsonstr::path]->asArray();
+
+        for (auto& jsonItem : jsonPath) {
+            if (jsonItem->isNull()) {
+                /* `null` becomes `bt2s::nullopt` */
+                items.emplace_back(bt2s::nullopt);
+            } else {
+                items.push_back(*jsonItem->asStr());
+            }
+        }
+    }
+
+    /* Create field location */
+    return createFieldLoc(jsonFc.loc(), origin, std::move(items));
+}
+
+/*
+ * Creates and returns a string field class from the JSON string field
+ * class value `jsonFc` and the other parameters.
+ */
+Fc::UP fcFromJsonStrFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, OptAttrs&& attrs)
+{
+    const auto encoding = bt2c::call([&jsonFc] {
+        const auto encodingStr = optStrOfObj(jsonFc, jsonstr::encoding);
+
+        if (!encodingStr || encodingStr == jsonstr::utf8) {
+            return StrEncoding::Utf8;
+        } else if (encodingStr == jsonstr::utf16Be) {
+            return StrEncoding::Utf16Be;
+        } else if (encodingStr == jsonstr::utf16Le) {
+            return StrEncoding::Utf16Le;
+        } else if (encodingStr == jsonstr::utf32Be) {
+            return StrEncoding::Utf32Be;
+        } else {
+            BT_ASSERT(encodingStr == jsonstr::utf32Le);
+            return StrEncoding::Utf32Le;
+        }
+    });
+
+    if (type == jsonstr::nullTerminatedStr) {
+        return createNullTerminatedStrFc(jsonFc.loc(), encoding, std::move(attrs));
+    } else if (type == jsonstr::staticLenStr) {
+        return createStaticLenStrFc(jsonFc.loc(), lenOfJsonFc(jsonFc), encoding, std::move(attrs));
+    } else {
+        BT_ASSERT(type == jsonstr::dynLenStr);
+        return createDynLenStrFc(jsonFc.loc(), fieldLocOfJsonFc(jsonFc, jsonstr::lenFieldLoc),
+                                 encoding, std::move(attrs));
+    }
+}
+
+/*
+ * Creates and returns a static-length BLOB field class from the JSON
+ * static-length BLOB field class value `jsonFc` and the other
+ * parameters.
+ */
+Fc::UP fcFromJsonStaticLenBlobFc(const bt2c::JsonObjVal& jsonFc, const char * const mediaType,
+                                 OptAttrs&& attrs)
+{
+    /* Has metadata stream UUID role? */
+    const auto jsonRoles = jsonFc[jsonstr::roles];
+    const auto hasMetadataStreamUuidRole = jsonRoles && jsonRoles->asArray().size() > 0;
+
+    /* Create field class */
+    return createStaticLenBlobFc(jsonFc.loc(), lenOfJsonFc(jsonFc), mediaType,
+                                 hasMetadataStreamUuidRole, std::move(attrs));
+}
+
+/*
+ * Creates and returns a BLOB field class from the JSON BLOB field class
+ * value `jsonFc` and the other parameters.
+ */
+Fc::UP fcFromJsonBlobFc(const bt2c::JsonObjVal& jsonFc, const std::string& type, OptAttrs&& attrs)
+{
+    /* Media type */
+    const auto mediaType = jsonFc.rawVal(jsonstr::mediaType, ir::defaultBlobMediaType);
+
+    /* Create field class */
+    if (type == jsonstr::staticLenBlob) {
+        return fcFromJsonStaticLenBlobFc(jsonFc, mediaType, std::move(attrs));
+    } else {
+        BT_ASSERT(type == jsonstr::dynLenBlob);
+        return createDynLenBlobFc(jsonFc.loc(), fieldLocOfJsonFc(jsonFc, jsonstr::lenFieldLoc),
+                                  mediaType, std::move(attrs));
+    }
+}
+
+/*
+ * Returns the minimum alignment of the JSON field class value `jsonFc`.
+ */
+unsigned long long minAlignOfJsonFc(const bt2c::JsonObjVal& jsonFc) noexcept
+{
+    return jsonFc.rawVal(jsonstr::minAlign, 1ULL);
+}
+
+} /* namespace */
+
+Ctf2FcBuilder::Ctf2FcBuilder(const bt2c::Logger& parentLogger) :
+    _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-FC-BUILDER"}
+{
+}
+
+Fc::UP Ctf2FcBuilder::buildFcFromJsonVal(const bt2c::JsonVal& jsonFc) const
+{
+    if (jsonFc.isStr()) {
+        /* Field class alias reference */
+        return this->_aliasedFc(*jsonFc.asStr(), jsonFc.loc());
+    }
+
+    auto& jsonFcObj = jsonFc.asObj();
+
+    /* Type */
+    auto& type = jsonFcObj.rawStrVal(jsonstr::type);
+
+    /* Attributes */
+    auto attrs = attrsOfObj(jsonFcObj);
+
+    /* Defer to specific method */
+    if (type == jsonstr::fixedLenBitArray || type == jsonstr::fixedLenBitMap ||
+        type == jsonstr::fixedLenBool || type == jsonstr::fixedLenUInt ||
+        type == jsonstr::fixedLenSInt || type == jsonstr::fixedLenFloat) {
+        return fcFromJsonFixedLenBitArrayFc(jsonFcObj, type, std::move(attrs));
+    } else if (type == jsonstr::varLenUInt || type == jsonstr::varLenSInt) {
+        return fcFromJsonVarLenIntFc(jsonFcObj, type, std::move(attrs));
+    } else if (type == jsonstr::nullTerminatedStr || type == jsonstr::staticLenStr ||
+               type == jsonstr::dynLenStr) {
+        return fcFromJsonStrFc(jsonFcObj, type, std::move(attrs));
+    } else if (type == jsonstr::staticLenBlob || type == jsonstr::dynLenBlob) {
+        return fcFromJsonBlobFc(jsonFcObj, type, std::move(attrs));
+    } else if (type == jsonstr::staticLenArray || type == jsonstr::dynLenArray) {
+        return this->_fcFromJsonArrayFc(jsonFcObj, type, std::move(attrs));
+    } else if (type == jsonstr::structure) {
+        return this->_fcFromJsonStructFc(jsonFcObj, std::move(attrs));
+    } else if (type == jsonstr::optional) {
+        return this->_fcFromJsonOptionalFc(jsonFcObj, std::move(attrs));
+    } else {
+        BT_ASSERT(type == jsonstr::variant);
+        return this->_fcFromJsonVariantFc(jsonFcObj, std::move(attrs));
+    }
+}
+
+void Ctf2FcBuilder::addFcAlias(std::string name, Fc::UP fc, const bt2c::TextLoc& nameLoc)
+{
+    /* Check for duplicate */
+    if (bt2c::contains(_mFcAliases, name)) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, nameLoc,
+                                                   "Duplicate field class alias named `{}`.", name);
+    }
+
+    /* Add to map */
+    _mFcAliases.emplace(std::move(name), std::move(fc));
+}
+
+Fc::UP Ctf2FcBuilder::_aliasedFc(const std::string& name, const bt2c::TextLoc& loc) const
+{
+    /* Check if the field class alias exists */
+    const auto it = _mFcAliases.find(name);
+
+    if (it == _mFcAliases.end()) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, loc,
+                                                   "Cannot find field class alias `{}`.", name);
+    }
+
+    /* Return a clone of the field class alias */
+    return it->second->clone();
+}
+
+Fc::UP Ctf2FcBuilder::_fcFromJsonArrayFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                                         OptAttrs&& attrs) const
+{
+    /* Element field class */
+    Fc::UP elemFc;
+
+    try {
+        elemFc = this->buildFcFromJsonVal(*jsonFc[jsonstr::elemFc]);
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFc.loc(), "Invalid array field class.");
+    }
+
+    /* Minimum alignment */
+    const auto minAlign = minAlignOfJsonFc(jsonFc);
+
+    /* Create field class */
+    if (type == jsonstr::staticLenArray) {
+        return createStaticLenArrayFc(jsonFc.loc(), lenOfJsonFc(jsonFc), std::move(elemFc),
+                                      minAlign, false, std::move(attrs));
+    } else {
+        BT_ASSERT(type == jsonstr::dynLenArray);
+        return createDynLenArrayFc(jsonFc.loc(), fieldLocOfJsonFc(jsonFc, jsonstr::lenFieldLoc),
+                                   std::move(elemFc), minAlign, std::move(attrs));
+    }
+}
+
+Fc::UP Ctf2FcBuilder::_fcFromJsonStructFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& attrs) const
+{
+    /* Minimum alignment */
+    const auto minAlign = minAlignOfJsonFc(jsonFc);
+
+    /* Member classes */
+    StructFc::MemberClasses memberClasses;
+    const auto jsonMemberClasses = jsonFc[jsonstr::memberClasses];
+
+    try {
+        if (jsonMemberClasses) {
+            for (auto& jsonMemberCls : jsonMemberClasses->asArray()) {
+                auto& jsonMemberClsObj = jsonMemberCls->asObj();
+                auto& name = jsonMemberClsObj.rawStrVal(jsonstr::name);
+
+                try {
+                    memberClasses.emplace_back(createStructFieldMemberCls(
+                        name, this->buildFcFromJsonVal(*jsonMemberClsObj[jsonstr::fc]),
+                        attrsOfObj(jsonMemberClsObj)));
+                } catch (const bt2c::Error&) {
+                    BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                        jsonMemberClsObj.loc(), "Invalid structure field member class `{}`.", name);
+                }
+            }
+        }
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFc.loc(),
+                                                     "Invalid structure field class.");
+    }
+
+    /* Create field class */
+    return createStructFc(jsonFc.loc(), std::move(memberClasses), minAlign, std::move(attrs));
+}
+
+Fc::UP Ctf2FcBuilder::_fcFromJsonOptionalFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& attrs) const
+{
+    try {
+        /*
+         * Create optional field class.
+         *
+         * The existence of the `selector-field-ranges` property
+         * determines the expected field class type:
+         *
+         * Property exists:
+         *     Class of optional fields with an integer selector.
+         *
+         * Property doesn't exist:
+         *     Class of optional fields with a boolean selector.
+         *
+         * Note that we just trust the metadata at this point so there's
+         * no validation: this can be done later with fcDepTypes().
+         *
+         * Also, if the optional field class has instances having an
+         * integer selector, we can't know the signedness of such a
+         * selector at this point. Therefore we enforce an unsigned
+         * integer range set and will possibly cast to signed integer
+         * ranges later once we know the selector signedness.
+         */
+        const auto jsonSelFieldRanges = jsonFc[jsonstr::selFieldRanges];
+        auto fieldLoc = fieldLocOfJsonFc(jsonFc, jsonstr::selFieldLoc);
+        auto fc = this->buildFcFromJsonVal(*jsonFc[jsonstr::fc]);
+
+        if (jsonSelFieldRanges) {
+            /* Expected integer selector */
+            return createOptionalFc(jsonFc.loc(), std::move(fc), std::move(fieldLoc),
+                                    intRangeSetFromJsonIntRangeSet<OptionalWithUIntSelFc::SelVal>(
+                                        jsonSelFieldRanges->asArray()),
+                                    std::move(attrs));
+        } else {
+            /* Expected boolean selector */
+            return createOptionalFc(jsonFc.loc(), std::move(fc), std::move(fieldLoc),
+                                    std::move(attrs));
+        }
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFc.loc(), "Invalid optional field class.");
+    }
+}
+
+Fc::UP Ctf2FcBuilder::_fcFromJsonVariantFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& attrs) const
+{
+    try {
+        auto& jsonOpts = jsonFc[jsonstr::opts]->asArray();
+
+        /*
+         * We can't know the signedness of the selector at this point.
+         * Therefore we enforce an unsigned integer range set and will
+         * possibly cast to signed integer ranges later once we know the
+         * selector signedness.
+         */
+        VariantWithUIntSelFc::Opts opts;
+
+        for (auto it = jsonOpts.begin(); it != jsonOpts.end(); ++it) {
+            auto& jsonOpt = (*it)->asObj();
+
+            /* Create and append option */
+            try {
+                opts.emplace_back(
+                    createVariantFcOpt(this->buildFcFromJsonVal(*jsonOpt[jsonstr::fc]),
+                                       intRangeSetFromJsonIntRangeSet<VariantWithUIntSelFc::SelVal>(
+                                           jsonOpt[jsonstr::selFieldRanges]->asArray()),
+                                       optStrOfObj(jsonOpt, jsonstr::name), attrsOfObj(jsonOpt)));
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                    jsonFc.loc(), "Invalid variant field class option #{}.", it - jsonOpts.begin());
+            }
+        }
+
+        /* Create field class */
+        return createVariantFc(jsonFc.loc(), std::move(opts),
+                               fieldLocOfJsonFc(jsonFc, jsonstr::selFieldLoc), std::move(attrs));
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFc.loc(), "Invalid variant field class.");
+    }
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.hpp b/src/plugins/ctf/common/src/metadata/json/ctf-2-fc-builder.hpp
new file mode 100644 (file)
index 0000000..aab4ece
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_FC_BUILDER_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_FC_BUILDER_HPP
+
+#include <string>
+#include <unordered_map>
+
+#include "cpp-common/bt2c/json-val.hpp"
+#include "cpp-common/bt2c/logging.hpp"
+#include "cpp-common/bt2c/text-loc.hpp"
+
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Field class builder for CTF 2.
+ *
+ * An instance keeps a map of field class alias names to field
+ * class aliases.
+ *
+ * Build a field class from an equivalent CTF 2 JSON value
+ * with buildFcFromJsonVal().
+ *
+ * Add a field class alias with addFcAlias().
+ */
+class Ctf2FcBuilder final
+{
+public:
+    /*
+     * Builds a field class builder without any initial field
+     * class alias.
+     */
+    explicit Ctf2FcBuilder(const bt2c::Logger& parentLogger);
+
+    /*
+     * Builds and returns a field class from the CTF 2 JSON (string
+     * (alias) or object) value `jsonFc`, or appends a cause to the
+     * error of the current thread and throws `bt2c::Error` on error.
+     */
+    Fc::UP buildFcFromJsonVal(const bt2c::JsonVal& jsonFc) const;
+
+    /*
+     * Adds a field class alias `fc` named `name`, or appends a cause to
+     * the error of the current thread and throws `bt2c::Error`
+     * on error.
+     */
+    void addFcAlias(std::string name, Fc::UP fc, const bt2c::TextLoc& nameLoc);
+
+private:
+    /*
+     * Returns a clone of the field class alias named `name`.
+     */
+    Fc::UP _aliasedFc(const std::string& name, const bt2c::TextLoc& loc) const;
+
+    /*
+     * Creates and returns an array field class from the JSON array
+     * field class value `jsonFc` and the other parameters.
+     */
+    Fc::UP _fcFromJsonArrayFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                              OptAttrs&& userAttrs) const;
+
+    /*
+     * Creates and returns a structure field class from the JSON
+     * structure field class value `jsonFc` and from `userAttrs`.
+     */
+    Fc::UP _fcFromJsonStructFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& userAttrs) const;
+
+    /*
+     * Creates and returns an optional field class from the JSON
+     * optional field class value `jsonFc` and from `userAttrs`.
+     */
+    Fc::UP _fcFromJsonOptionalFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& userAttrs) const;
+
+    /*
+     * Creates and returns a variant field class from the JSON variant
+     * field class value `jsonFc` and from `userAttrs`.
+     */
+    Fc::UP _fcFromJsonVariantFc(const bt2c::JsonObjVal& jsonFc, OptAttrs&& userAttrs) const;
+
+    /* Map of alias name to aliased field classes */
+    std::unordered_map<std::string, Fc::UP> _mFcAliases;
+
+    /* Logger */
+    bt2c::Logger _mLogger;
+};
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_FC_BUILDER_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.cpp b/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.cpp
new file mode 100644 (file)
index 0000000..c1e6d8d
--- /dev/null
@@ -0,0 +1,820 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <sstream>
+
+#include "common/assert.h"
+#include "cpp-common/bt2c/contains.hpp"
+#include "cpp-common/bt2c/exc.hpp"
+#include "cpp-common/bt2c/join.hpp"
+#include "cpp-common/bt2c/json-val.hpp"
+#include "cpp-common/bt2c/logging.hpp"
+#include "cpp-common/bt2c/make-span.hpp"
+#include "cpp-common/bt2c/parse-json-as-val.hpp"
+#include "cpp-common/bt2s/string-view.hpp"
+
+#include "ctf-2-metadata-stream-parser.hpp"
+#include "fcs-with-role.hpp"
+#include "normalize-field-locs.hpp"
+#include "plugins/ctf/common/src/metadata/ctf-ir.hpp"
+#include "resolve-fcs-with-int-sel.hpp"
+#include "strings.hpp"
+#include "utils.hpp"
+#include "validate-scope-fc-roles.hpp"
+
+namespace ctf {
+namespace src {
+
+Ctf2MetadataStreamParser::Ctf2MetadataStreamParser(
+    const bt2::OptionalBorrowedObject<bt2::SelfComponent> selfComp, const ClkClsCfg& clkClsCfg,
+    const bt2c::Logger& parentLogger) :
+    MetadataStreamParser {selfComp, clkClsCfg},
+    _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-META-STREAM-PARSER"}, _mFragmentValReq {_mLogger},
+    _mDefClkOffsetVal {bt2c::call([] {
+        bt2c::JsonObjVal::Container entries;
+
+        entries.insert(
+            std::make_pair(jsonstr::seconds, bt2c::createJsonVal(0LL, bt2c::TextLoc {})));
+        entries.insert(
+            std::make_pair(jsonstr::cycles, bt2c::createJsonVal(0ULL, bt2c::TextLoc {})));
+        return bt2c::createJsonVal(std::move(entries), bt2c::TextLoc {});
+    })},
+    _mFcBuilder {_mLogger}
+{
+}
+
+MetadataStreamParser::ParseRet
+Ctf2MetadataStreamParser::parse(const bt2::OptionalBorrowedObject<bt2::SelfComponent> selfComp,
+                                const ClkClsCfg& clkClsCfg, const bt2c::ConstBytes buffer,
+                                const bt2c::Logger& parentLogger)
+{
+    Ctf2MetadataStreamParser parser {selfComp, clkClsCfg, parentLogger};
+
+    parser.parseSection(buffer);
+
+    if (!parser.traceCls() || parser.traceCls()->dataStreamClasses().empty()) {
+        /*
+         * CTF 2 requires that a metadata stream contains at least one
+         * data stream class fragment: `parser.traceCls()`, if it
+         * exists, doesn't at this point and we know that `buffer`
+         * contains the whole metadata stream.
+         */
+        BT_CPPLOGE_APPEND_CAUSE_AND_THROW_SPEC(
+            parser._mLogger, bt2::Error, "Missing data stream class fragment in metadata stream.");
+    }
+
+    return {parser.releaseTraceCls(), parser.metadataStreamUuid()};
+}
+
+void Ctf2MetadataStreamParser::_parseSection(const bt2c::ConstBytes buffer)
+{
+    this->_parseFragments(buffer);
+}
+
+void Ctf2MetadataStreamParser::_parseFragments(const bt2c::ConstBytes buffer)
+{
+    BT_ASSERT(buffer.data());
+
+    auto fragmentBegin = buffer.begin();
+    const auto curSectionOffsetInStream = _mCurOffsetInStream;
+
+    while (true) {
+        /* Find the beginning pointer of the current JSON fragment */
+        while (fragmentBegin != buffer.end() && *fragmentBegin == 30) {
+            /* Skip RS byte */
+            ++fragmentBegin;
+        }
+
+        _mCurOffsetInStream =
+            curSectionOffsetInStream + bt2c::DataLen::fromBytes(fragmentBegin - buffer.begin());
+
+        if (fragmentBegin == buffer.end()) {
+            /* We're done */
+            return;
+        }
+
+        /* Find the end pointer of the current JSON fragment */
+        auto fragmentEnd = fragmentBegin;
+
+        while (fragmentEnd != buffer.end() && *fragmentEnd != 30) {
+            /* Skip non-RS byte */
+            ++fragmentEnd;
+        }
+
+        if (fragmentBegin == fragmentEnd) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, this->_loc(buffer, fragmentBegin), "Expecting a fragment.");
+        }
+
+        /* Parse one fragment */
+        _mCurOffsetInStream =
+            curSectionOffsetInStream + bt2c::DataLen::fromBytes(fragmentBegin - buffer.begin());
+        this->_parseFragment(bt2c::makeSpan(fragmentBegin, fragmentEnd));
+
+        /* Go to next fragment */
+        fragmentBegin = fragmentEnd;
+        ++_mCurFragmentIndex;
+    }
+
+    /* Adjust offset in metadata stream for the next section to parse */
+    _mCurOffsetInStream = curSectionOffsetInStream + bt2c::DataLen::fromBytes(buffer.size());
+}
+
+void Ctf2MetadataStreamParser::_parseFragment(const bt2c::ConstBytes buffer)
+{
+    try {
+        this->_handleFragment(*bt2c::parseJson(
+            bt2s::string_view {reinterpret_cast<const char *>(buffer.data()), buffer.size()},
+            _mCurOffsetInStream.bytes(), _mLogger));
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+            this->_loc(buffer, buffer.begin()), "Invalid fragment #{}.", _mCurFragmentIndex + 1);
+    }
+}
+
+namespace {
+
+/*
+ * Returns the UUID of the JSON object value `jsonObjVal`, or
+ * `bt2s::nullopt` if there's no such property.
+ */
+bt2s::optional<bt2c::Uuid> uuidOfObj(const bt2c::JsonObjVal& jsonObjVal)
+{
+    if (const auto jsonUuidVal = jsonObjVal[jsonstr::uuid]) {
+        std::array<bt2c::Uuid::Val, bt2c::Uuid::size()> uuid;
+        auto& jsonArrayUuidVal = jsonUuidVal->asArray();
+
+        std::transform(jsonArrayUuidVal.begin(), jsonArrayUuidVal.end(), uuid.begin(),
+                       [](const bt2c::JsonVal::UP& jsonUuidElemVal) {
+                           return static_cast<bt2c::Uuid::Val>(*jsonUuidElemVal->asUInt());
+                       });
+        return bt2c::Uuid {uuid.data()};
+    }
+
+    return bt2s::nullopt;
+}
+
+} /* namespace */
+
+void Ctf2MetadataStreamParser::_handleFragment(const bt2c::JsonVal& jsonFragment)
+{
+    /* Validate the fragment */
+    _mFragmentValReq.validate(jsonFragment);
+
+    /* Get type */
+    auto& jsonFragmentObj = jsonFragment.asObj();
+    auto& type = jsonFragmentObj.rawStrVal(jsonstr::type);
+
+    /* Specific preamble fragment case */
+    if (_mCurFragmentIndex == 0) {
+        if (type != jsonstr::preamble) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, jsonFragmentObj.loc(),
+                                                       "Expecting the preamble fragment.");
+        }
+
+        /* Possibly set the metadata stream UUID */
+        _mMetadataStreamUuid = uuidOfObj(jsonFragmentObj);
+
+        /*
+         * Nothing more to do with the preamble fragment, but it
+         * must exist!
+         */
+        return;
+    }
+
+    /* Defer to specific method */
+    if (type == jsonstr::preamble) {
+        BT_ASSERT(_mCurFragmentIndex > 0);
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+            bt2c::Error, jsonFragmentObj.loc(),
+            "Preamble fragment must be the first fragment of the metadata stream.");
+    } else if (type == jsonstr::fcAlias) {
+        this->_handleFcAliasFragment(jsonFragmentObj);
+    } else if (type == jsonstr::traceCls) {
+        this->_handleTraceClsFragment(jsonFragmentObj);
+    } else if (type == jsonstr::clkCls) {
+        this->_handleClkClsFragment(jsonFragmentObj);
+    } else if (type == jsonstr::dataStreamCls) {
+        this->_handleDataStreamClsFragment(jsonFragmentObj);
+    } else {
+        BT_ASSERT(type == jsonstr::eventRecordCls);
+        this->_handleEventRecordClsFragment(jsonFragmentObj);
+    }
+}
+
+void Ctf2MetadataStreamParser::_handleFcAliasFragment(const bt2c::JsonObjVal& jsonFragment)
+{
+    auto& jsonNameVal = jsonFragment[jsonstr::name]->asStr();
+
+    try {
+        _mFcBuilder.addFcAlias(*jsonNameVal,
+                               _mFcBuilder.buildFcFromJsonVal(*jsonFragment[jsonstr::fc]),
+                               jsonNameVal.loc());
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFragment.loc(),
+                                                     "Invalid field class alias fragment.");
+    }
+}
+
+void Ctf2MetadataStreamParser::_validateScopeFcRoles(const Fc& fc,
+                                                     const UIntFieldRoles& allowedRoles,
+                                                     const bool allowMetadataStreamUuidRole)
+{
+    validateScopeFcRoles(fc, allowedRoles, allowMetadataStreamUuidRole, _mLogger);
+}
+
+void Ctf2MetadataStreamParser::_validatePktHeaderFcRoles(const Fc& pktHeaderFc)
+{
+    /* Validate allowed roles */
+    this->_validateScopeFcRoles(pktHeaderFc,
+                                {UIntFieldRole::PktMagicNumber, UIntFieldRole::DataStreamClsId,
+                                 UIntFieldRole::DataStreamId},
+                                true);
+
+    /*
+     * Validate that, if an unsigned integer field class FC has a
+     * "packet magic number" role:
+     *
+     * • FC is a 32-bit fixed-length unsigned integer field class.
+     *
+     * • FC is the field class of the first member class of the packet
+     *   header scope structure field class.
+     *
+     * • There's only one such field class within the whole packet
+     *   header scope field class.
+     */
+    {
+        const auto fcs = fcsWithRole(pktHeaderFc, {UIntFieldRole::PktMagicNumber}, false);
+
+        if (fcs.size() > 1) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, (*fcs.begin())->loc(),
+                "Packet header field class may contain zero or one field class having the role `{}`, not {}.",
+                jsonstr::pktMagicNumber, fcs.size());
+        }
+
+        if (!fcs.empty()) {
+            auto& magicFc = **fcs.begin();
+
+            if (&pktHeaderFc.asStruct()[0].fc() != &magicFc) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, magicFc.loc(),
+                    "A field class having the `{}` role must be the first class of the first member class "
+                    "of the packet header field class.",
+                    jsonstr::pktMagicNumber);
+            }
+
+            if (!magicFc.isFixedLenUInt()) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, magicFc.loc(),
+                    "Unexpected type of field class having the `{}` role: "
+                    "expecting `{}`.",
+                    jsonstr::pktMagicNumber, jsonstr::fixedLenUInt);
+            }
+
+            auto& magicUIntFc = magicFc.asFixedLenUInt();
+
+            if (*magicUIntFc.len() != 32) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, magicFc.loc(),
+                    "Unexpected `{}` property of fixed-length unsigned integer field class having the `{}` role: "
+                    "expecting 32, not {}.",
+                    jsonstr::len, jsonstr::pktMagicNumber, *magicUIntFc.len());
+            }
+        }
+    }
+
+    /*
+     * Validate that, if there's at least one static-length BLOB field
+     * class having the "metadata stream UUID" role, then the metadata
+     * stream has a UUID.
+     */
+    {
+        const auto fcs = fcsWithRole(pktHeaderFc, {}, true);
+
+        if (!fcs.empty() && !_mMetadataStreamUuid) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, (*fcs.begin())->loc(),
+                "Static-length BLOB field class has the role `{}`, "
+                "but the preamble fragment of the metadata stream has no `{}` property.",
+                jsonstr::metadataStreamUuid, jsonstr::uuid);
+        }
+    }
+}
+
+namespace {
+
+/*
+ * A namespace, name, and UID tuple.
+ */
+struct NsNameUid final
+{
+    bt2s::optional<std::string> ns;
+    bt2s::optional<std::string> name;
+    bt2s::optional<std::string> uid;
+};
+
+/*
+ * Returns the namespace, name, and UID of the JSON object `jsonObj`.
+ */
+NsNameUid nsNameUidOfObj(const bt2c::JsonObjVal& jsonObj)
+{
+    return NsNameUid {
+        optStrOfObj(jsonObj, jsonstr::ns),
+        optStrOfObj(jsonObj, jsonstr::name),
+        optStrOfObj(jsonObj, jsonstr::uid),
+    };
+}
+
+} /* namespace */
+
+void Ctf2MetadataStreamParser::_handleTraceClsFragment(const bt2c::JsonObjVal& jsonFragment)
+{
+    /* Check for trace class uniqueness */
+    if (_mTraceCls) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, jsonFragment.loc(),
+                                                   "Duplicate trace class fragment.");
+    }
+
+    /* Namespace, name, and UID */
+    auto nsNameUid = nsNameUidOfObj(jsonFragment);
+
+    /* Create the packet header field class and validate it */
+    auto pktHeaderFc =
+        this->_scopeFcOfJsonVal(jsonFragment, jsonstr::pktHeaderFc, Scope::PktHeader);
+
+    if (pktHeaderFc) {
+        try {
+            this->_validatePktHeaderFcRoles(*pktHeaderFc);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(pktHeaderFc->loc(),
+                                                         "Invalid packet header field class.");
+        }
+    }
+
+    /* Create the trace class */
+    _mTraceCls = createTraceCls(std::move(nsNameUid.ns), std::move(nsNameUid.name),
+                                std::move(nsNameUid.uid), bt2ValueOfObj(jsonFragment, jsonstr::env),
+                                std::move(pktHeaderFc), attrsOfObj(jsonFragment));
+}
+
+void Ctf2MetadataStreamParser::_handleClkClsFragment(const bt2c::JsonObjVal& jsonFragment)
+{
+    /* ID */
+    auto id = jsonFragment.rawStrVal(jsonstr::id);
+
+    if (bt2c::contains(_mClkClasses, id)) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+            bt2c::Error, jsonFragment.loc(), "Duplicate clock class fragment with ID `{}`.", id);
+    }
+
+    /* Namespace, name, and UID */
+    auto nsNameUid = nsNameUidOfObj(jsonFragment);
+
+    /* Create corresponding clock class */
+    auto clkCls = createClkCls(
+        id, jsonFragment.rawUIntVal(jsonstr::freq), std::move(nsNameUid.ns),
+        std::move(nsNameUid.name), std::move(nsNameUid.uid), bt2c::call([this, &jsonFragment] {
+            auto& jsonOffsetVal = jsonFragment.val(jsonstr::offsetFromOrigin, *_mDefClkOffsetVal);
+
+            return ClkOffset {
+                bt2c::call([&jsonOffsetVal] {
+                    if (const auto jsonOffsetSecsVal = jsonOffsetVal[jsonstr::seconds]) {
+                        return rawIntValFromJsonIntVal<long long>(*jsonOffsetSecsVal);
+                    }
+
+                    return 0LL;
+                }),
+                jsonOffsetVal.rawVal(jsonstr::cycles, 0ULL),
+            };
+        }),
+        bt2c::call([&jsonFragment]() -> bt2s::optional<ClkOrigin> {
+            if (const auto jsonOriginVal = jsonFragment[jsonstr::origin]) {
+                if (jsonOriginVal->isStr()) {
+                    /* Unix epoch */
+                    BT_ASSERT(*jsonOriginVal->asStr() == jsonstr::unixEpoch);
+                    return ClkOrigin {};
+                } else {
+                    /* Custom */
+                    auto& jsonOriginObjVal = jsonOriginVal->asObj();
+
+                    /* Create clock origin */
+                    auto nsNameUid = nsNameUidOfObj(jsonOriginObjVal);
+
+                    BT_ASSERT(nsNameUid.name);
+                    BT_ASSERT(nsNameUid.uid);
+                    return ClkOrigin {std::move(nsNameUid.ns), std::move(*nsNameUid.name),
+                                      std::move(*nsNameUid.uid)};
+                }
+            }
+
+            return bt2s::nullopt;
+        }),
+        optStrOfObj(jsonFragment, jsonstr::descr),
+        bt2c::call([&jsonFragment]() -> bt2s::optional<unsigned long long> {
+            if (const auto jsonPrecision = jsonFragment[jsonstr::precision]) {
+                return *jsonPrecision->asUInt();
+            }
+
+            return bt2s::nullopt;
+        }),
+        bt2c::call([&jsonFragment]() -> bt2s::optional<unsigned long long> {
+            if (const auto jsonAccuracy = jsonFragment[jsonstr::accuracy]) {
+                return *jsonAccuracy->asUInt();
+            }
+
+            return bt2s::nullopt;
+        }),
+        attrsOfObj(jsonFragment));
+
+    /* Add to map of clock classes */
+    _mClkClasses.emplace(std::make_pair(std::move(id), std::move(clkCls)));
+}
+
+namespace {
+
+/*
+ * Returns the "full class ID" string of `id`, `ns`, `name`, and `uid`.
+ */
+std::string fullClsIdStr(const unsigned long long id, const bt2s::optional<std::string>& ns,
+                         const bt2s::optional<std::string>& name,
+                         const bt2s::optional<std::string>& uid)
+{
+    std::ostringstream ss;
+
+    ss << id;
+
+    if (ns || name || uid) {
+        std::vector<std::string> parts;
+
+        if (ns) {
+            parts.push_back(*ns);
+        }
+
+        if (name) {
+            parts.push_back(*name);
+        }
+
+        if (uid) {
+            parts.push_back(*uid);
+        }
+
+        ss << fmt::format(" ({})", bt2c::join(parts, "/"));
+    }
+
+    return ss.str();
+}
+
+/*
+ * Returns the "full class ID" string of `id` and `nsNameUid`.
+ */
+std::string fullClsIdStr(const unsigned long long id, const NsNameUid& nsNameUid)
+{
+    return fullClsIdStr(id, nsNameUid.ns, nsNameUid.name, nsNameUid.uid);
+}
+
+/*
+ * Returns the "full class ID" string of the class object `cls`.
+ */
+template <typename ClsT>
+std::string fullClsIdStr(const ClsT& cls)
+{
+    return fullClsIdStr(cls.id(), cls.ns(), cls.name(), cls.uid());
+}
+
+} /* namespace */
+
+void Ctf2MetadataStreamParser::_validateClkTsRoles(const Fc& fc, const bool allowClkTsRole)
+{
+    const auto fcs =
+        fcsWithRole(fc, {UIntFieldRole::DefClkTs, UIntFieldRole::PktEndDefClkTs}, false);
+
+    if (!fcs.empty() && !allowClkTsRole) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+            bt2c::Error, (*fcs.begin())->loc(),
+            "Invalid unsigned integer field class having the `{}` or `{}` role because "
+            "its containing data stream class fragment has no default clock class "
+            "(missing `{}` property).",
+            jsonstr::defClkTs, jsonstr::pktEndDefClkTs, jsonstr::defClkClsId);
+    }
+}
+
+void Ctf2MetadataStreamParser::_validateDataStreamClsRoles(const Fc * const pktCtxFc,
+                                                           const Fc * const eventRecordHeaderFc,
+                                                           const Fc * const commonEventRecordCtxFc,
+                                                           const bool allowClkTsRole)
+{
+    if (pktCtxFc) {
+        try {
+            this->_validateScopeFcRoles(*pktCtxFc,
+                                        {UIntFieldRole::PktTotalLen, UIntFieldRole::PktContentLen,
+                                         UIntFieldRole::DefClkTs, UIntFieldRole::PktEndDefClkTs,
+                                         UIntFieldRole::DiscEventRecordCounterSnap,
+                                         UIntFieldRole::PktSeqNum},
+                                        false);
+            this->_validateClkTsRoles(*pktCtxFc, allowClkTsRole);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(pktCtxFc->loc(),
+                                                         "Invalid packet context field class.");
+        }
+    }
+
+    if (eventRecordHeaderFc) {
+        try {
+            this->_validateScopeFcRoles(*eventRecordHeaderFc,
+                                        {UIntFieldRole::DefClkTs, UIntFieldRole::EventRecordClsId},
+                                        false);
+            this->_validateClkTsRoles(*eventRecordHeaderFc, allowClkTsRole);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                eventRecordHeaderFc->loc(), "Invalid event record header field class.");
+        }
+    }
+
+    if (commonEventRecordCtxFc) {
+        try {
+            /* No roles allowed */
+            this->_validateScopeFcRoles(*commonEventRecordCtxFc, {}, false);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                commonEventRecordCtxFc->loc(), "Invalid common event record context field class.");
+        }
+    }
+}
+
+namespace {
+
+/*
+ * A string and text location pair.
+ */
+struct StrAndLoc final
+{
+    std::string str;
+    bt2c::TextLoc loc;
+};
+
+/*
+ * Returns the string value and text location of the property `propName`
+ * within `jsonObjVal`, or `bt2s::nullopt` if there's no such property.
+ */
+bt2s::optional<StrAndLoc> optStrOfObjWithLoc(const bt2c::JsonObjVal& jsonObjVal,
+                                             const std::string& propName)
+{
+    if (const auto jsonVal = jsonObjVal[propName]) {
+        return StrAndLoc {*jsonVal->asStr(), jsonVal->loc()};
+    }
+
+    return bt2s::nullopt;
+}
+
+} /* namespace */
+
+void Ctf2MetadataStreamParser::_handleDataStreamClsFragment(const bt2c::JsonObjVal& jsonFragment)
+{
+    this->_ensureExistingTraceCls();
+
+    /* ID */
+    const auto id = jsonFragment.rawVal(jsonstr::id, 0ULL);
+
+    if ((*_mTraceCls)[id]) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+            bt2c::Error, jsonFragment.loc(),
+            "Duplicate data stream class fragment with numeric ID {}.", id);
+    }
+
+    /* Default clock class */
+    auto defClkCls = bt2c::call([this, &jsonFragment]() -> ClkCls::SP {
+        if (const auto defClkClsId = optStrOfObjWithLoc(jsonFragment, jsonstr::defClkClsId)) {
+            const auto it = _mClkClasses.find(defClkClsId->str);
+
+            if (it == _mClkClasses.end()) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, defClkClsId->loc,
+                    "`{}` doesn't identify an existing clock class fragment.", defClkClsId->str);
+            }
+
+            return it->second;
+        }
+
+        return {};
+    });
+
+    /* Namespace, name, and UID */
+    const auto nsNameUid = nsNameUidOfObj(jsonFragment);
+
+    /* Create data stream class */
+    try {
+        /* Create field classes */
+        auto pktCtxFc =
+            this->_dataStreamClsScopeFcOfJsonVal(jsonFragment, jsonstr::pktCtxFc, Scope::PktCtx);
+        auto eventRecordHeaderFc = this->_dataStreamClsScopeFcOfJsonVal(
+            jsonFragment, jsonstr::eventRecordHeaderFc, Scope::EventRecordHeader, pktCtxFc.get());
+        auto commonEventRecordCtxFc = this->_dataStreamClsScopeFcOfJsonVal(
+            jsonFragment, jsonstr::eventRecordCommonCtxFc, Scope::CommonEventRecordCtx,
+            pktCtxFc.get(), eventRecordHeaderFc.get());
+
+        /* Validate roles */
+        this->_validateDataStreamClsRoles(pktCtxFc.get(), eventRecordHeaderFc.get(),
+                                          commonEventRecordCtxFc.get(),
+                                          static_cast<bool>(defClkCls));
+
+        /* Create and add data stream class to current trace class */
+        _mTraceCls->addDataStreamCls(createDataStreamCls(
+            id, nsNameUid.ns, nsNameUid.name, nsNameUid.uid, std::move(pktCtxFc),
+            std::move(eventRecordHeaderFc), std::move(commonEventRecordCtxFc), std::move(defClkCls),
+            attrsOfObj(jsonFragment)));
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonFragment.loc(),
+                                                     "Invalid data stream class fragment {}.",
+                                                     fullClsIdStr(id, nsNameUid));
+    }
+}
+
+void Ctf2MetadataStreamParser::_validateEventRecordClsRoles(const Fc * const specCtxFc,
+                                                            const Fc * const payloadFc)
+{
+    if (specCtxFc) {
+        try {
+            /* No roles allowed */
+            this->_validateScopeFcRoles(*specCtxFc, {}, false);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(specCtxFc->loc(),
+                                                         "Invalid specific context field class.");
+        }
+    }
+
+    if (payloadFc) {
+        try {
+            /* No roles allowed */
+            this->_validateScopeFcRoles(*payloadFc, {}, false);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(payloadFc->loc(),
+                                                         "Invalid payload field class.");
+        }
+    }
+}
+
+void Ctf2MetadataStreamParser::_handleEventRecordClsFragment(const bt2c::JsonObjVal& jsonFragment)
+{
+    this->_ensureExistingTraceCls();
+
+    /* Data stream class ID */
+    auto dataStreamCls = bt2c::call([this, &jsonFragment] {
+        const auto jsonDataStreamClsIdVal = jsonFragment[jsonstr::dataStreamClsId];
+        const auto dataStreamClsId =
+            jsonDataStreamClsIdVal ? *jsonDataStreamClsIdVal->asUInt() : 0ULL;
+
+        if (auto dataStreamCls = (*_mTraceCls)[dataStreamClsId]) {
+            return dataStreamCls;
+        }
+
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+            bt2c::Error,
+            jsonDataStreamClsIdVal ? jsonDataStreamClsIdVal->loc() : jsonFragment.loc(),
+            "No data stream class fragment exists with numeric ID {}.", dataStreamClsId);
+    });
+
+    /* ID */
+    const auto id = bt2c::call([this, &jsonFragment, &dataStreamCls] {
+        const auto jsonIdVal = jsonFragment[jsonstr::id];
+        const auto id = jsonIdVal ? *jsonIdVal->asUInt() : 0ULL;
+
+        if ((*dataStreamCls)[id]) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, jsonIdVal ? jsonIdVal->loc() : jsonFragment.loc(),
+                "Duplicate event record class fragment with numeric ID {} within data stream class fragment {}.",
+                id, fullClsIdStr(*dataStreamCls));
+        }
+
+        return id;
+    });
+
+    /* Create event record class */
+    const auto nsNameUid = nsNameUidOfObj(jsonFragment);
+
+    try {
+        /* Create field classes */
+        auto specCtxFc = this->_eventRecordClsScopeFcOfJsonVal(
+            jsonFragment, jsonstr::specCtxFc, Scope::SpecEventRecordCtx, *dataStreamCls);
+        auto payloadFc = this->_eventRecordClsScopeFcOfJsonVal(jsonFragment, jsonstr::payloadFc,
+                                                               Scope::EventRecordPayload,
+                                                               *dataStreamCls, specCtxFc.get());
+
+        /* Validate roles */
+        this->_validateEventRecordClsRoles(specCtxFc.get(), payloadFc.get());
+
+        /* Create and add event record class to data stream class */
+        dataStreamCls->addEventRecordCls(createEventRecordCls(
+            id, nsNameUid.ns, nsNameUid.name, nsNameUid.uid, std::move(specCtxFc),
+            std::move(payloadFc), attrsOfObj(jsonFragment)));
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+            jsonFragment.loc(),
+            "Invalid event record class fragment {} (for data stream class fragment {}).",
+            fullClsIdStr(id, nsNameUid), fullClsIdStr(*dataStreamCls));
+    }
+}
+
+void Ctf2MetadataStreamParser::_ensureExistingTraceCls()
+{
+    if (_mTraceCls) {
+        /* Already initialized */
+        return;
+    }
+
+    /* Create a default CTF 2 trace class */
+    _mTraceCls = createTraceCls();
+}
+
+Fc::UP Ctf2MetadataStreamParser::_scopeFcOfJsonVal(const bt2c::JsonObjVal& jsonVal,
+                                                   const std::string& key, const Scope scope,
+                                                   const Fc *pktHeaderFc, const Fc *pktCtxFc,
+                                                   const Fc *eventRecordHeaderFc,
+                                                   const Fc *commonEventRecordCtxFc,
+                                                   const Fc *specEventRecordCtxFc,
+                                                   const Fc *eventRecordPayloadFc)
+{
+    const auto jsonFcVal = jsonVal[key];
+
+    if (!jsonFcVal) {
+        /* Scope doesn't exist */
+        return nullptr;
+    }
+
+    try {
+        /* Build field class */
+        auto fc = _mFcBuilder.buildFcFromJsonVal(*jsonFcVal);
+
+        /* Make sure it's a structure field class */
+        if (!fc->isStruct()) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, fc->loc(),
+                                                       "Expecting a structure field class.");
+        }
+
+        /* Normalize field locations (relative → absolute) */
+        normalizeFieldLocs(*fc, scope, _mLogger);
+
+        /*
+         * Resolve `OptionalWithUIntSel` and `VariantWithUIntSel`
+         * field classes.
+         */
+        switch (scope) {
+        case Scope::PktHeader:
+            pktHeaderFc = fc.get();
+            break;
+        case Scope::PktCtx:
+            pktCtxFc = fc.get();
+            break;
+        case Scope::EventRecordHeader:
+            eventRecordHeaderFc = fc.get();
+            break;
+        case Scope::CommonEventRecordCtx:
+            commonEventRecordCtxFc = fc.get();
+            break;
+        case Scope::SpecEventRecordCtx:
+            specEventRecordCtxFc = fc.get();
+            break;
+        case Scope::EventRecordPayload:
+            eventRecordPayloadFc = fc.get();
+            break;
+        default:
+            bt_common_abort();
+        }
+
+        resolveFcsWithIntSel(*fc, scope, pktHeaderFc, pktCtxFc, eventRecordHeaderFc,
+                             commonEventRecordCtxFc, specEventRecordCtxFc, eventRecordPayloadFc,
+                             _mLogger);
+
+        /* Done! */
+        return fc;
+    } catch (const bt2c::Error&) {
+        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(jsonVal.loc(), "Invalid {} scope.",
+                                                     scopeStr(scope));
+    }
+}
+
+Fc::UP Ctf2MetadataStreamParser::_eventRecordClsScopeFcOfJsonVal(
+    const bt2c::JsonObjVal& jsonEventRecordCls, const std::string& key, const Scope scope,
+    const DataStreamCls& dataStreamCls, const Fc * const specEventRecordCtxFc,
+    const Fc * const eventRecordPayloadFc)
+{
+    return this->_scopeFcOfJsonVal(jsonEventRecordCls, key, scope, _mTraceCls->pktHeaderFc(),
+                                   dataStreamCls.pktCtxFc(), dataStreamCls.eventRecordHeaderFc(),
+                                   dataStreamCls.commonEventRecordCtxFc(), specEventRecordCtxFc,
+                                   eventRecordPayloadFc);
+}
+
+Fc::UP Ctf2MetadataStreamParser::_dataStreamClsScopeFcOfJsonVal(
+    const bt2c::JsonObjVal& jsonDataStreamCls, const std::string& key, const Scope scope,
+    const Fc * const pktCtxFc, const Fc * const eventRecordHeaderFc,
+    const Fc * const commonEventRecordCtxFc)
+{
+    return this->_scopeFcOfJsonVal(jsonDataStreamCls, key, scope, _mTraceCls->pktHeaderFc(),
+                                   pktCtxFc, eventRecordHeaderFc, commonEventRecordCtxFc);
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.hpp b/src/plugins/ctf/common/src/metadata/json/ctf-2-metadata-stream-parser.hpp
new file mode 100644 (file)
index 0000000..841d9ec
--- /dev/null
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP
+
+#include <cstdint>
+
+#include <babeltrace2/babeltrace.h>
+
+#include "../ctf-ir.hpp"
+#include "../metadata-stream-parser.hpp"
+#include "ctf-2-fc-builder.hpp"
+#include "val-req.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * CTF 2 metadata stream (JSON text sequence) parser.
+ *
+ * Build an instance of `Ctf2MetadataStreamParser`, and then call
+ * parseSection() as often as needed with one or more _complete_
+ * CTF 2 fragments.
+ *
+ * You may also call the static Ctf2MetadataStreamParser::parse() method
+ * to parse a whole CTF 2 metadata stream.
+ */
+class Ctf2MetadataStreamParser final : public MetadataStreamParser
+{
+public:
+    /*
+     * Builds a CTF 2 metadata stream parser.
+     *
+     * If `selfComp` exists, then the parser uses it each time you call
+     * parseSection() to finalize the current trace class.
+     */
+    explicit Ctf2MetadataStreamParser(bt2::OptionalBorrowedObject<bt2::SelfComponent> selfComp,
+                                      const ClkClsCfg& clkClsCfg, const bt2c::Logger& parentLogger);
+
+    /*
+     * Parses the whole CTF 2 metadata stream in `buffer` and returns
+     * the resulting trace class and optional metadata stream UUID on
+     * success, or appends a cause to the error of the current thread
+     * and throws `bt2c::Error` otherwise.
+     */
+    static ParseRet parse(bt2::OptionalBorrowedObject<bt2::SelfComponent> selfComp,
+                          const ClkClsCfg& clkClsCfg, bt2c::ConstBytes buffer,
+                          const bt2c::Logger& parentLogger);
+
+private:
+    void _parseSection(bt2c::ConstBytes buffer) override;
+
+    /*
+     * Parses one or more complete fragments in `buffer`, updating the
+     * internal state on success, or appending a cause to the error of
+     * the current thread and throwing `bt2c::Error` otherwise.
+     */
+    void _parseFragments(bt2c::ConstBytes buffer);
+
+    /*
+     * Parses the JSON fragment in `buffer`, updating the internal state
+     * on success, or appending a cause to the error of the current
+     * thread and throwing `bt2c::Error` on failure.
+     */
+    void _parseFragment(bt2c::ConstBytes buffer);
+
+    /*
+     * Handles the JSON fragment `jsonFragment`, updating the internal
+     * state on success, or appending a cause to the error of the
+     * current thread and throwing `bt2c::Error` on failure.
+     */
+    void _handleFragment(const bt2c::JsonVal& jsonFragment);
+
+    /*
+     * Handles the JSON field class alias fragment `jsonFragment`,
+     * updating the internal state on success, or appending a cause to
+     * the error of the current thread and throwing `bt2c::Error`
+     * on failure.
+     */
+    void _handleFcAliasFragment(const bt2c::JsonObjVal& jsonFragment);
+
+    /*
+     * Forwards to validateScopeFcRoles() using the logger of
+     * this parser..
+     *
+     * Appends a cause to the error of the current thread and throwing
+     * `bt2c::Error` on failure.
+     */
+    void _validateScopeFcRoles(const Fc& fc, const UIntFieldRoles& allowedRoles,
+                               bool allowMetadataStreamUuidRole);
+
+    /*
+     * Validates the field roles of the packet header field class `fc`.
+     */
+    void _validatePktHeaderFcRoles(const Fc& fc);
+
+    /*
+     * Validates the field roles of the JSON trace class fragment
+     * `jsonFragment`.
+     */
+    void _validateTraceClsFragmentRoles(const bt2c::JsonObjVal& jsonFragment);
+
+    /*
+     * Handles the JSON trace class fragment `jsonFragment`, updating
+     * the internal state on success, or appending a cause to the error
+     * of the current thread and throwing `bt2c::Error` on
+     * failure.
+     */
+    void _handleTraceClsFragment(const bt2c::JsonObjVal& jsonFragment);
+
+    /*
+     * Handles the JSON clock class fragment `jsonFragment`, updating
+     * the internal state on success, or appending a cause to the error
+     * of the current thread and throwing `bt2c::Error` on
+     * failure.
+     */
+    void _handleClkClsFragment(const bt2c::JsonObjVal& jsonFragment);
+
+    /*
+     * Validates that the field class `fc` doesn't contain an unsigned
+     * integer field class having a default clock timestamp role if
+     * `allowDefClkTsRole` is false.
+     */
+    void _validateClkTsRoles(const Fc& fc, bool allowClkTsRole);
+
+    /*
+     * Validates the roles of the packet context, event record header,
+     * and common event record context field classes `pktCtxFc`,
+     * `eventRecordHeaderFc`, and `commonEventRecordCtxFc`.
+     */
+    void _validateDataStreamClsRoles(const Fc *pktCtxFc, const Fc *eventRecordHeaderFc,
+                                     const Fc *commonEventRecordCtxFc, bool allowDefClkTsRole);
+
+    /*
+     * Handles the JSON data stream class fragment `jsonFragment`,
+     * updating the internal state on success, or appending a cause to
+     * the error of the current thread and throwing `bt2c::Error`
+     * on failure.
+     */
+    void _handleDataStreamClsFragment(const bt2c::JsonObjVal& jsonFragment);
+
+    /*
+     * Validates the roles of specific event record context and event
+     * record payload field classes `specCtxFc` and `payloadFc`.
+     */
+    void _validateEventRecordClsRoles(const Fc *specCtxFc, const Fc *payloadFc);
+
+    /*
+     * Handles the JSON event record class fragment `jsonFragment`,
+     * updating the internal state on success, or appending a cause to
+     * the error of the current thread and throwing `bt2c::Error`
+     * on failure.
+     */
+    void _handleEventRecordClsFragment(const bt2c::JsonObjVal& jsonFragment);
+
+    /*
+     * Ensures that `*_mTraceCls` exists.
+     */
+    void _ensureExistingTraceCls();
+
+    /*
+     * If a JSON value has the key `key` in `jsonVal`:
+     *     Returns `nullptr`.
+     *
+     * Otherwise:
+     *     Converts `*jsonVal[key]` to a scope field class and returns
+     *     it, considering `pktHeaderFc`, `pktCtxFc`,
+     *     `eventRecordHeaderFc`, `commonEventRecordCtxFc`,
+     *     `specEventRecordCtxFc`, and `eventRecordPayloadFc` as the
+     *     current packet header, packet context, event record header,
+     *     common event record context, specific event record context,
+     *     and event record payload field classes.
+     */
+    Fc::UP _scopeFcOfJsonVal(const bt2c::JsonObjVal& jsonVal, const std::string& key, Scope scope,
+                             const Fc *pktHeaderFc = nullptr, const Fc *pktCtxFc = nullptr,
+                             const Fc *eventRecordHeaderFc = nullptr,
+                             const Fc *commonEventRecordCtxFc = nullptr,
+                             const Fc *specEventRecordCtxFc = nullptr,
+                             const Fc *eventRecordPayloadFc = nullptr);
+
+    /*
+     * If a JSON value has the key `key` in the JSON event record class
+     * fragment value `jsonVal`:
+     *     Returns `nullptr`.
+     *
+     * Otherwise:
+     *     Converts `*jsonEventRecordCls[key]` to a scope field class
+     *     and returns it, considering the current packet header field
+     *     class, the field classes of `dataStreamCls`, and
+     *     `specEventRecordCtxFc` and `eventRecordPayloadFc` as the
+     *     current specific event record context and event record
+     *     payload field classes.
+     */
+    Fc::UP _eventRecordClsScopeFcOfJsonVal(const bt2c::JsonObjVal& jsonEventRecordCls,
+                                           const std::string& key, Scope scope,
+                                           const DataStreamCls& dataStreamCls,
+                                           const Fc *specEventRecordCtxFc = nullptr,
+                                           const Fc *eventRecordPayloadFc = nullptr);
+
+    /*
+     * If a JSON value has the key `key` in the JSON data stream class
+     * fragment value `jsonDataStreamCls`:
+     *     Returns `nullptr`.
+     *
+     * Otherwise:
+     *     Converts `*jsonDataStreamCls[key]` to a field class and
+     *     returns it, considering the current packet header field
+     *     class, and `pktCtxFc`, `eventRecordHeaderFc`, and
+     *     `commonEventRecordCtxFc` as the current packet context, event
+     *     record header, and common event record context field classes.
+     */
+    Fc::UP _dataStreamClsScopeFcOfJsonVal(const bt2c::JsonObjVal& jsonDataStreamCls,
+                                          const std::string& key, Scope scope,
+                                          const Fc *pktCtxFc = nullptr,
+                                          const Fc *eventRecordHeaderFc = nullptr,
+                                          const Fc *commonEventRecordCtxFc = nullptr);
+
+    /*
+     * If a JSON value has the key `key` in the JSON trace class
+     * fragment value `jsonTraceCls`:
+     *     Returns `nullptr`.
+     *
+     * Otherwise:
+     *     Converts `*jsonTraceCls[key]` to a field class and returns
+     *     it, considering the JSON trace class fragment value
+     *     `jsonTraceCls` as the conversion context.
+     */
+    Fc::UP _traceClsScopeFcOfJsonVal(const bt2c::JsonObjVal& jsonTraceCls, const std::string& key);
+
+    /*
+     * Returns a text location with an offset of `at` relative to the
+     * `buffer.begin()`, also considering `_mCurOffsetInStream`.
+     *
+     * `at` must be within `buffer`.
+     */
+    bt2c::TextLoc _loc(bt2c::ConstBytes buffer,
+                       const bt2c::ConstBytes::const_iterator at) const noexcept
+    {
+        BT_ASSERT_DBG(at >= buffer.begin());
+        BT_ASSERT_DBG(at < buffer.end());
+        return bt2c::TextLoc {_mCurOffsetInStream.bytes() + (at - buffer.begin())};
+    }
+
+private:
+    /* Logging configuration */
+    bt2c::Logger _mLogger;
+
+    /* Current offset within the whole metadata stream */
+    bt2c::DataLen _mCurOffsetInStream = bt2c::DataLen::fromBytes(0);
+
+    /* Current fragment index */
+    std::size_t _mCurFragmentIndex = 0;
+
+    /* Fragment requirement */
+    Ctf2JsonAnyFragmentValReq _mFragmentValReq;
+
+    /* Default clock offset JSON value */
+    bt2c::JsonObjVal::UP _mDefClkOffsetVal;
+
+    /*
+     * Map of clock class ID to clock class object.
+     *
+     * Clock class fragments "float" in a CTF 2 metadata stream, in that
+     * they aren't used yet, but could be afterwards through an ID
+     * reference within a data stream class.
+     *
+     * This map stores them until the parser needs one for a data
+     * stream class.
+     */
+    std::unordered_map<std::string, ClkCls::SP> _mClkClasses;
+
+    /* Field class builder, which keeps track of field class aliases */
+    Ctf2FcBuilder _mFcBuilder;
+};
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/fcs-with-role.cpp b/src/plugins/ctf/common/src/metadata/json/fcs-with-role.cpp
new file mode 100644 (file)
index 0000000..5fef6fe
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "cpp-common/bt2c/contains.hpp"
+
+#include "fcs-with-role.hpp"
+
+namespace ctf {
+namespace src {
+namespace {
+
+/*
+ * Helper of fcsWithRole().
+ */
+class Finder final : public ConstFcVisitor
+{
+public:
+    explicit Finder(const UIntFieldRoles& roles, const bool withMetadataStreamUuidRole) :
+        _mRoles {&roles}, _mWithMetadataStreamUuidRole {withMetadataStreamUuidRole}
+    {
+    }
+
+    std::unordered_set<const Fc *> takeFcs() noexcept
+    {
+        return std::move(_mFcs);
+    }
+
+private:
+    void visit(const FixedLenUIntFc& fc) override
+    {
+        this->_tryAddUIntFc(fc);
+    }
+
+    void visit(const VarLenUIntFc& fc) override
+    {
+        this->_tryAddUIntFc(fc);
+    }
+
+    void visit(const StaticLenBlobFc& fc) override
+    {
+        if (_mWithMetadataStreamUuidRole && fc.hasMetadataStreamUuidRole()) {
+            _mFcs.emplace(&fc);
+        }
+    }
+
+    void visit(const StaticLenArrayFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const DynLenArrayFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const StructFc& fc) override
+    {
+        for (auto& memberCls : fc) {
+            memberCls.fc().accept(*this);
+        }
+    }
+
+    void visit(const OptionalWithBoolSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const OptionalWithUIntSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const OptionalWithSIntSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const VariantWithUIntSelFc& fc) override
+    {
+        this->_visitVariantFc(fc);
+    }
+
+    void visit(const VariantWithSIntSelFc& fc) override
+    {
+        this->_visitVariantFc(fc);
+    }
+
+    template <typename UIntFcT>
+    void _tryAddUIntFc(const UIntFcT& fc)
+    {
+        for (const auto role : fc.roles()) {
+            if (bt2c::contains(*_mRoles, role)) {
+                _mFcs.emplace(&fc);
+            }
+        }
+    }
+
+    void _visit(const ArrayFc& fc)
+    {
+        fc.elemFc().accept(*this);
+    }
+
+    void _visit(const OptionalFc& fc)
+    {
+        fc.fc().accept(*this);
+    }
+
+    template <typename VarFcT>
+    void _visitVariantFc(const VarFcT& fc)
+    {
+        for (auto& opt : fc) {
+            opt.fc().accept(*this);
+        }
+    }
+
+    const UIntFieldRoles *_mRoles;
+    bool _mWithMetadataStreamUuidRole;
+    std::unordered_set<const Fc *> _mFcs;
+};
+
+} /* namespace */
+
+std::unordered_set<const Fc *> fcsWithRole(const Fc& fc, const UIntFieldRoles& roles,
+                                           const bool withMetadataStreamUuidRole)
+{
+    Finder finder {roles, withMetadataStreamUuidRole};
+
+    fc.accept(finder);
+    return finder.takeFcs();
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/fcs-with-role.hpp b/src/plugins/ctf/common/src/metadata/json/fcs-with-role.hpp
new file mode 100644 (file)
index 0000000..690d152
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_FCS_WITH_ROLE_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_FCS_WITH_ROLE_HPP
+
+#include <unordered_set>
+
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Returns a set of all the field classes having at least one role
+ * amongst `roles` and/or the "metadata stream UUID" role if
+ * `withMetadataStreamUuidRole` is true for a static-length BLOB
+ * field class.
+ */
+std::unordered_set<const Fc *> fcsWithRole(const Fc& fc, const UIntFieldRoles& roles,
+                                           bool withMetadataStreamUuidRole);
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_FCS_WITH_ROLE_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/key-fc-types.cpp b/src/plugins/ctf/common/src/metadata/json/key-fc-types.cpp
new file mode 100644 (file)
index 0000000..79df873
--- /dev/null
@@ -0,0 +1,582 @@
+/*
+ * Copyright (c) 2023-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+
+#include "common/assert.h"
+#include "common/common.h"
+#include "cpp-common/bt2c/contains.hpp"
+#include "cpp-common/bt2c/logging.hpp"
+#include "cpp-common/bt2c/text-loc-str.hpp"
+
+#include "key-fc-types.hpp"
+#include "strings.hpp"
+#include "utils.hpp"
+
+namespace ctf {
+namespace src {
+namespace {
+
+/*
+ * Helper of keyFcTypes().
+ */
+class KeyFcTypesFinder final : public ConstFcVisitor
+{
+private:
+    /* Current scope field classes */
+    struct _Ctx final
+    {
+        const Fc *pktHeaderFc;
+        const Fc *pktCtxFc;
+        const Fc *eventRecordHeaderFc;
+        const Fc *commonEventRecordCtxFc;
+        const Fc *specEventRecordCtxFc;
+        const Fc *eventRecordPayloadFc;
+    };
+
+    /* Set of const field classes */
+    using _ConstFcSet = std::unordered_set<const Fc *>;
+
+public:
+    explicit KeyFcTypesFinder(const Scope scope, const Fc * const pktHeaderFc,
+                              const Fc * const pktCtxFc, const Fc * const eventRecordHeaderFc,
+                              const Fc * const commonEventRecordCtxFc,
+                              const Fc * const specEventRecordCtxFc,
+                              const Fc * const eventRecordPayloadFc,
+                              const bt2c::Logger& parentLogger) :
+        _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-FC-DEP-TYPES"},
+        _mScope {scope}, _mCtx {pktHeaderFc,          pktCtxFc,
+                                eventRecordHeaderFc,  commonEventRecordCtxFc,
+                                specEventRecordCtxFc, eventRecordPayloadFc}
+    {
+    }
+
+    /*
+     * Moves the resulting field class dependency type map to
+     * the caller.
+     */
+    KeyFcTypes takeKeyFcTypes() noexcept
+    {
+        return std::move(_mKeyFcTypes);
+    }
+
+private:
+    void visit(const StaticLenArrayFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const DynLenArrayFc& fc) override
+    {
+        this->_addDynLenKeyFcType(fc);
+        this->_visit(fc);
+    }
+
+    void visit(const DynLenStrFc& fc) override
+    {
+        this->_addDynLenKeyFcType(fc);
+    }
+
+    void visit(const DynLenBlobFc& fc) override
+    {
+        this->_addDynLenKeyFcType(fc);
+    }
+
+    void visit(const StructFc& fc) override
+    {
+        for (auto& memberCls : fc) {
+            try {
+                memberCls.fc().accept(*this);
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                    memberCls.fc().loc(), "Invalid structure field member class.");
+            }
+        }
+    }
+
+    void visit(const OptionalWithBoolSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const OptionalWithUIntSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const OptionalWithSIntSelFc&) override
+    {
+        /* Doesn't exist at this point */
+        bt_common_abort();
+    }
+
+    void visit(const VariantWithUIntSelFc& fc) override
+    {
+        this->_addOptionalOrVariantKeyFcType(fc);
+
+        for (auto optIt = fc.begin(); optIt != fc.end(); ++optIt) {
+            try {
+                this->_withinCompoundFc(fc, optIt - fc.begin(), [this, &optIt] {
+                    optIt->fc().accept(*this);
+                });
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(optIt->fc().loc(),
+                                                             "Invalid variant field class option.");
+            }
+        }
+    }
+
+    void visit(const VariantWithSIntSelFc&) override
+    {
+        /* Doesn't exist at this point */
+        bt_common_abort();
+    }
+
+    void _visit(const ArrayFc& fc)
+    {
+        try {
+            this->_withinCompoundFc(fc, [this, &fc] {
+                fc.elemFc().accept(*this);
+            });
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                fc.loc(), "Invalid element field class of array field class.");
+        }
+    }
+
+    void _visit(const OptionalFc& fc)
+    {
+        const auto keyFcType = this->_addOptionalOrVariantKeyFcType(fc);
+
+        if (fc.isOptionalWithBoolSel() && keyFcType != KeyFcType::Bool) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, fc.loc(),
+                "Expecting a class of optional fields with a boolean selector field "
+                "because the `{}` property is absent.",
+                jsonstr::selFieldRanges);
+        }
+
+        try {
+            this->_withinCompoundFc(fc, [this, &fc] {
+                fc.fc().accept(*this);
+            });
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                fc.loc(), "Invalid field class of optional field class.");
+        }
+    }
+
+    /*
+     * Adds the key field class type of the dynamic-length field class
+     * `fc` to `_mKeyFcTypes`.
+     */
+    template <typename FcT>
+    void _addDynLenKeyFcType(FcT& fc)
+    {
+        this->_validateDynLenKeyFcType(this->_addKeyFcs(fc, fc.lenFieldLoc()), fc.lenFieldLoc());
+    }
+
+    /*
+     * Adds the key field class type of the optional or variant field
+     * class `fc` to `_mKeyFcTypes` and returns said type.
+     */
+    template <typename FcT>
+    KeyFcType _addOptionalOrVariantKeyFcType(FcT& fc)
+    {
+        return this->_keyFcType(**this->_addKeyFcs(fc, fc.selFieldLoc()).begin());
+    }
+
+    /*
+     * Adds the key field class type of `fc` to `_mKeyFcTypes` and
+     * returns the dependencies of `fc`.
+     */
+    template <typename FcT>
+    _ConstFcSet _addKeyFcs(FcT& fc, const FieldLoc& fieldLoc)
+    {
+        auto keyFcs = this->_findKeyFcs(fc, fieldLoc);
+
+        _mKeyFcTypes.emplace(std::make_pair(&fc, this->_keyFcType(**keyFcs.begin())));
+        return keyFcs;
+    }
+
+    /*
+     * Adds to `keyFcs` the key field classes of `dependentFc`, using
+     * the field location `fieldLoc`, from `baseFc` and the field
+     * location item iterator `fieldLocIt`.
+     *
+     * Returns `true` if `dependentFc` isn't reached yet (safe to
+     * continue to find key field classes).
+     */
+    bool _findKeyFcs(const Fc& baseFc, const Fc& dependentFc, const FieldLoc& fieldLoc,
+                     const FieldLoc::Items::const_iterator fieldLocIt, _ConstFcSet& keyFcs) const
+    {
+        if (baseFc.isFixedLenBool() || baseFc.isInt()) {
+            if (fieldLocIt != fieldLoc.end()) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, baseFc.loc(),
+                    "Cannot reach anything beyond a scalar field class for {}.",
+                    absFieldLocStr(fieldLoc, fieldLocIt + 1));
+            }
+
+            keyFcs.insert(&baseFc);
+            return true;
+        } else if (baseFc.isStruct()) {
+            if (fieldLocIt == fieldLoc.end()) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, baseFc.loc(),
+                    "Field location must not locate a structure field class.");
+            }
+
+            /* Find the member class named `**fieldLocIt` */
+            for (auto& memberCls : baseFc.asStruct()) {
+                if (&memberCls.fc() == &dependentFc) {
+                    /* Reached the dependent field class */
+                    return false;
+                }
+
+                if (memberCls.name() != **fieldLocIt) {
+                    continue;
+                }
+
+                return this->_findKeyFcs(memberCls.fc(), dependentFc, fieldLoc, fieldLocIt + 1,
+                                         keyFcs);
+            }
+
+            /* Member class not found */
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, baseFc.loc(),
+                "At field location {}: no structure field member class named `{}`.",
+                absFieldLocStr(fieldLoc, fieldLocIt), **fieldLocIt);
+        } else if (baseFc.isArray()) {
+            if (!bt2c::contains(_mCompoundFcIndexes, &baseFc)) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, baseFc.loc(),
+                    "At field location {}: unreachable array field element.",
+                    absFieldLocStr(fieldLoc, fieldLocIt));
+            }
+
+            auto& elemFc = baseFc.asArray().elemFc();
+
+            if (&elemFc == &dependentFc) {
+                /* Reached the dependent field class */
+                return false;
+            }
+
+            return this->_findKeyFcs(elemFc, dependentFc, fieldLoc, fieldLocIt, keyFcs);
+        } else if (baseFc.isOptional()) {
+            if (!bt2c::contains(_mCompoundFcIndexes, &baseFc)) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, baseFc.loc(), "At field location {}: unreachable optional field.",
+                    absFieldLocStr(fieldLoc, fieldLocIt));
+            }
+
+            auto& optionalFc = baseFc.asOptional().fc();
+
+            if (&optionalFc == &dependentFc) {
+                /* Reached the dependent field class */
+                return false;
+            }
+
+            return this->_findKeyFcs(optionalFc, dependentFc, fieldLoc, fieldLocIt, keyFcs);
+        } else if (baseFc.isVariant()) {
+            auto& opts = baseFc.asVariantWithUIntSel().opts();
+            const auto curOptIndexIt = _mCompoundFcIndexes.find(&baseFc);
+
+            if (curOptIndexIt == _mCompoundFcIndexes.end()) {
+                /*
+                 * Not currently visiting this variant field class:
+                 * consider all options.
+                 */
+                for (auto& opt : opts) {
+                    if (&opt.fc() == &dependentFc) {
+                        /* Reached the dependent field class */
+                        return false;
+                    }
+
+                    if (!this->_findKeyFcs(opt.fc(), dependentFc, fieldLoc, fieldLocIt, keyFcs)) {
+                        /* Reached the dependent field class */
+                        return false;
+                    }
+                }
+            } else {
+                /*
+                 * Currently visiting this variant field class: consider
+                 * only the currently visited option.
+                 */
+                auto& optFc = opts[curOptIndexIt->second].fc();
+
+                if (&optFc == &dependentFc) {
+                    /* Reached the dependent field class */
+                    return false;
+                }
+
+                return this->_findKeyFcs(optFc, dependentFc, fieldLoc, fieldLocIt, keyFcs);
+            }
+
+            return true;
+        } else {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, baseFc.loc(), "At field location {}: unexpected type of field class.",
+                absFieldLocStr(fieldLoc, fieldLocIt));
+        }
+    }
+
+    /*
+     * Returns the dependency type from the type of `fc`.
+     */
+    static KeyFcType _keyFcType(const Fc& fc) noexcept
+    {
+        if (fc.isFixedLenBool()) {
+            return KeyFcType::Bool;
+        } else if (fc.isUInt()) {
+            return KeyFcType::UInt;
+        } else {
+            BT_ASSERT(fc.isSInt());
+            return KeyFcType::SInt;
+        }
+    };
+
+    /*
+     * Returns the field class (within `_mCtx`) of the scope
+     * of `fieldLoc`.
+     */
+    const Fc& _scopeFc(const FieldLoc& fieldLoc) const
+    {
+        /* Validate the scope first */
+        if (static_cast<int>(*fieldLoc.origin()) > static_cast<int>(_mScope)) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, fieldLoc.loc(),
+                "A field within a {} field cannot depend on another field "
+                "within a {} field (unreachable).",
+                scopeStr(_mScope), scopeStr(*fieldLoc.origin()));
+        }
+
+        /* Retrieve scope field class from `_mCtx` */
+        const auto scopeFc = bt2c::call([this, &fieldLoc] {
+            switch (*fieldLoc.origin()) {
+            case Scope::PktHeader:
+                return _mCtx.pktHeaderFc;
+            case Scope::PktCtx:
+                return _mCtx.pktCtxFc;
+            case Scope::EventRecordHeader:
+                return _mCtx.eventRecordHeaderFc;
+            case Scope::CommonEventRecordCtx:
+                return _mCtx.commonEventRecordCtxFc;
+            case Scope::SpecEventRecordCtx:
+                return _mCtx.specEventRecordCtxFc;
+            case Scope::EventRecordPayload:
+                return _mCtx.eventRecordPayloadFc;
+            default:
+                bt_common_abort();
+            }
+        });
+
+        if (!scopeFc) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(bt2c::Error, fieldLoc.loc(),
+                                                       "Missing required {} field class.",
+                                                       scopeStr(*fieldLoc.origin()));
+        }
+
+        return *scopeFc;
+    }
+
+    /*
+     * Finds the key field classes of `dependentFc` using the field
+     * location `fieldLoc`.
+     *
+     * This method only considers boolean and integer field classes as
+     * key field classes, throwing `bt2c::Error` when it finds
+     * anything else.
+     *
+     * This method doesn't add to the returned set field classes which
+     * occur after `dependentFc` .
+     *
+     * This method also throws if:
+     *
+     * • `fieldLoc` is invalid anyhow.
+     *
+     * • `fieldLoc` locates field classes having different key field
+     *   class types.
+     *
+     * • `fieldLoc` doesn't locate any field class.
+     */
+    _ConstFcSet _findKeyFcs(const Fc& dependentFc, const FieldLoc& fieldLoc) const
+    {
+        try {
+            /* Find key field classes and return them */
+            _ConstFcSet keyFcs;
+
+            this->_findKeyFcs(this->_scopeFc(fieldLoc), dependentFc, fieldLoc, fieldLoc.begin(),
+                              keyFcs);
+
+            /* Validate that `keyFcs` contains at least one item */
+            if (keyFcs.empty()) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, fieldLoc.loc(), "Field location doesn't locate anything.");
+            }
+
+            /*
+             * Validate that all the items of `keyFcs` have the
+             * same type.
+             */
+            {
+                const auto expectedType = this->_keyFcType(**keyFcs.begin());
+
+                for (const auto fc : keyFcs) {
+                    if (this->_keyFcType(*fc) != expectedType) {
+                        BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                            bt2c::Error, fieldLoc.loc(),
+                            "Field location locates field classes having different types "
+                            "([{}] and [{}]).",
+                            bt2c::textLocStr((*keyFcs.begin())->loc(), _mLogger.textLocStrFmt()),
+                            bt2c::textLocStr(fc->loc(), _mLogger.textLocStrFmt()));
+                    }
+                }
+            }
+
+            /* Return the set */
+            return keyFcs;
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(fieldLoc.loc(),
+                                                         "Invalid field location {}.",
+                                                         absFieldLocStr(fieldLoc, fieldLoc.end()));
+        }
+    }
+
+    /*
+     * Validates the key field class type `keyFcType` for some
+     * dynamic-length field class.
+     */
+    void _validateDynLenKeyFcType(const _ConstFcSet& keyFcs, const FieldLoc& fieldLoc) const
+    {
+        BT_ASSERT(!keyFcs.empty());
+
+        if (this->_keyFcType(**keyFcs.begin()) != KeyFcType::UInt) {
+            try {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, (*keyFcs.begin())->loc(),
+                    "Expecting an unsigned integer field class.");
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                    fieldLoc.loc(), "Invalid field location {}.",
+                    absFieldLocStr(fieldLoc, fieldLoc.end()));
+            }
+        }
+    }
+
+    /*
+     * In this order:
+     *
+     * 1. Marks the underlying field class at the index `index` of the
+     *    compound field class `fc` as being currently visited.
+     *
+     * 2. Calls `func()`.
+     *
+     * 3. Cancels 1.
+     */
+    template <typename FuncT>
+    void _withinCompoundFc(const Fc& fc, const std::size_t index, FuncT&& func)
+    {
+        BT_ASSERT(!bt2c::contains(_mCompoundFcIndexes, &fc));
+        _mCompoundFcIndexes.emplace(std::make_pair(&fc, index));
+        func();
+        _mCompoundFcIndexes.erase(&fc);
+    }
+
+    /*
+     * In this order:
+     *
+     * 1. Marks the compound field class `fc` as being
+     *    currently visited.
+     *
+     * 2. Calls `func()`.
+     *
+     * 3. Cancels 1.
+     */
+    template <typename FuncT>
+    void _withinCompoundFc(const Fc& fc, FuncT&& func)
+    {
+        this->_withinCompoundFc(fc, 0, std::forward<FuncT>(func));
+    }
+
+    bt2c::Logger _mLogger;
+    Scope _mScope;
+    _Ctx _mCtx;
+
+    /* Result */
+    KeyFcTypes _mKeyFcTypes;
+
+    /*
+     * Map of compound field classes to the index of the currently
+     * visited immediate underlying field class, that is:
+     *
+     * For a variant field class F:
+     *     Index of the option of F containing the field class currently
+     *     being visited.
+     *
+     * For an array field class F:
+     * For an optional field class F:
+     *     0: if F is part of the map, then its element/optional field
+     *     class is currently being visited.
+     *
+     * This is used to provide a visiting context to _findKeyFcs() so as
+     * to follow the correct variant field class option as well as to
+     * validate key field classes.
+     *
+     * For example:
+     *
+     *     Root: Structure FC                                       [0]
+     *       `len`: Fixed-length unsigned integer FC                [1]
+     *       `meow`: Dynamic-length array FC                        [2]
+     *         Element FC: Structure FC                             [3]
+     *           `tag`: Fixed-length signed integer FC              [4]
+     *           `val`: Variant FC                                  [5]
+     *             `boss`: Null-terminated string FC                [6]
+     *             `zoom`: Structure FC                             [7]
+     *               `len`: Variable-length unsigned integer FC     [8]
+     *               `data`: Dynamic-length BLOB FC                 [9]
+     *             `line6`: Structure FC                            [10]
+     *               `len`: Fixed-length unsigned integer FC        [11]
+     *
+     * If _findKeyFcs() is currently visiting [9] to find its
+     * dependencies, then the map would contain:
+     *
+     *     [2] → 0     (visiting current element of `/meow`)
+     *     [5] → 1     (visiting second option (`zoom`) of `/meow/val`)
+     *
+     * This means that, if the length field location of [9] is
+     * `/meow/val/len`, then we must only consider the `zoom` option,
+     * not the `line6` one, even though both contain a member class
+     * named `len`.
+     */
+    std::unordered_map<const Fc *, std::size_t> _mCompoundFcIndexes;
+};
+
+} /* namespace */
+
+KeyFcTypes keyFcTypes(const Fc& scopeFc, const Scope scope, const Fc * const pktHeaderFc,
+                      const Fc * const pktCtxFc, const Fc * const eventRecordHeaderFc,
+                      const Fc * const commonEventRecordCtxFc,
+                      const Fc * const specEventRecordCtxFc, const Fc * const eventRecordPayloadFc,
+                      const bt2c::Logger& parentLogger)
+{
+    KeyFcTypesFinder finder {scope,
+                             pktHeaderFc,
+                             pktCtxFc,
+                             eventRecordHeaderFc,
+                             commonEventRecordCtxFc,
+                             specEventRecordCtxFc,
+                             eventRecordPayloadFc,
+                             parentLogger};
+
+    scopeFc.accept(finder);
+    return finder.takeKeyFcTypes();
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/key-fc-types.hpp b/src/plugins/ctf/common/src/metadata/json/key-fc-types.hpp
new file mode 100644 (file)
index 0000000..8b3dadb
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2023-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_KEY_FC_TYPES_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_KEY_FC_TYPES_HPP
+
+#include <unordered_map>
+
+#include "cpp-common/bt2c/logging.hpp"
+#include "cpp-common/vendor/wise-enum/wise_enum.h"
+
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/* clang-format off */
+
+/* Key (length/selector) field class type */
+WISE_ENUM_CLASS(KeyFcType,
+    Bool,
+    UInt,
+    SInt
+)
+
+/* clang-format on */
+
+/* Map of dependent field class to key field class type */
+using KeyFcTypes = std::unordered_map<const Fc *, KeyFcType>;
+
+/*
+ * Returns the (validated) key types of all the dependent field classes
+ * within `scopeFc`.
+ *
+ * `scopeFc` is a scope field class for the scope `scope`.
+ *
+ * `pktHeaderFc`, `pktCtxFc`, `eventRecordHeaderFc`,
+ * `commonEventRecordCtxFc`, `specEventRecordCtxFc`, and
+ * `eventRecordPayloadFc` are the current packet header, packet context,
+ * event record header, common event record context, specific event
+ * record context, and event record payload field classes. Any of them
+ * may be `nullptr`.
+ *
+ * The field locations within `scopeFc` must be absolute.
+ *
+ * Appends one or more causes to the error of the current thread and
+ * throws `bt2c::Error` when any field location in `scopeFc` is invalid.
+ */
+KeyFcTypes keyFcTypes(const Fc& scopeFc, Scope scope, const Fc *pktHeaderFc, const Fc *pktCtxFc,
+                      const Fc *eventRecordHeaderFc, const Fc *commonEventRecordCtxFc,
+                      const Fc *specEventRecordCtxFc, const Fc *eventRecordPayloadFc,
+                      const bt2c::Logger& parentLogger);
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_KEY_FC_TYPES_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.cpp b/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.cpp
new file mode 100644 (file)
index 0000000..e65028e
--- /dev/null
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2023-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "common/assert.h"
+#include "common/common.h"
+#include "cpp-common/bt2c/logging.hpp"
+
+#include "normalize-field-locs.hpp"
+
+namespace ctf {
+namespace src {
+namespace {
+
+/*
+ * Helper of normalizeFieldLocs().
+ */
+class Normalizer final : public FcVisitor
+{
+public:
+    explicit Normalizer(const Scope scope, const bt2c::Logger& parentLogger) :
+        _mScope {scope}, _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-NORMALIZE-FIELD-LOCS"}
+    {
+    }
+
+private:
+    void visit(StaticLenArrayFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(DynLenArrayFc& fc) override
+    {
+        fc.lenFieldLoc(this->_normalizeFieldLoc(fc.lenFieldLoc()));
+        this->_visit(fc);
+    }
+
+    void visit(DynLenStrFc& fc) override
+    {
+        fc.lenFieldLoc(this->_normalizeFieldLoc(fc.lenFieldLoc()));
+    }
+
+    void visit(DynLenBlobFc& fc) override
+    {
+        fc.lenFieldLoc(this->_normalizeFieldLoc(fc.lenFieldLoc()));
+    }
+
+    void visit(StructFc& fc) override
+    {
+        for (auto& memberCls : fc) {
+            _mMemberNames.push_back(&memberCls.name());
+
+            try {
+                memberCls.fc().accept(*this);
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                    memberCls.fc().loc(), "Invalid structure field member class.");
+            }
+
+            _mMemberNames.pop_back();
+        }
+    }
+
+    void visit(OptionalWithBoolSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(OptionalWithUIntSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(OptionalWithSIntSelFc&) override
+    {
+        /* Doesn't exist at this point */
+        bt_common_abort();
+    }
+
+    void visit(VariantWithUIntSelFc& fc) override
+    {
+        fc.selFieldLoc(this->_normalizeFieldLoc(fc.selFieldLoc()));
+
+        for (auto& opt : fc) {
+            try {
+                opt.fc().accept(*this);
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(opt.fc().loc(),
+                                                             "Invalid variant field class option.");
+            }
+        }
+    }
+
+    void visit(VariantWithSIntSelFc&) override
+    {
+        /* Doesn't exist at this point */
+        bt_common_abort();
+    }
+
+    void _visit(ArrayFc& fc)
+    {
+        try {
+            fc.elemFc().accept(*this);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                fc.loc(), "Invalid element field class of array field class.");
+        }
+    }
+
+    void _visit(OptionalFc& fc)
+    {
+        fc.selFieldLoc(this->_normalizeFieldLoc(fc.selFieldLoc()));
+
+        try {
+            fc.fc().accept(*this);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                fc.loc(), "Invalid field class of optional field class.");
+        }
+    }
+
+    /*
+     * Returns the normalized version of `fieldLoc`.
+     */
+    FieldLoc _normalizeFieldLoc(const FieldLoc& fieldLoc) const
+    {
+        if (fieldLoc.origin()) {
+            /* Already absolute */
+            return fieldLoc;
+        }
+
+        /*
+         * First remove non-leading "parent" field location items.
+         *
+         * For example (using a file system notation):
+         *
+         * • `../../meow/mix/../glue/all` → `../../meow/glue/all`.
+         * • `hello/../../world` → `../world`.
+         */
+        const auto tmpItems = bt2c::call([&fieldLoc] {
+            FieldLoc::Items retItems;
+
+            for (auto& item : fieldLoc.items()) {
+                if (retItems.empty()) {
+                    retItems.push_back(item);
+                } else {
+                    if (item) {
+                        retItems.push_back(item);
+                    } else {
+                        /* Go back to known parent */
+                        retItems.pop_back();
+                    }
+                }
+            }
+
+            return retItems;
+        });
+
+        /*
+         * Create absolute field location items.
+         *
+         * Example 1: with leading "parent" items
+         * ──────────────────────────────────────
+         * Given the temporary items `../meow/mix` and the current
+         * member names `/red/blue/green` (`green` being the name of the
+         * dependent field class having the location `fieldLoc`), then
+         * after the loop below:
+         *
+         *     /red/blue/green
+         *          ▲
+         *          memberNameEndIt
+         *
+         *     ../meow/mix
+         *        ▲
+         *        tmpItemIt
+         *
+         * Final, absolute items:
+         *
+         *     /red/meow/mix
+         *
+         * Example 2: without leading "parent" items
+         * ─────────────────────────────────────────
+         * Given the temporary items `meow/mix` and the current member
+         * names `/red/blue/green` (`green` being the name of the
+         * dependent field class having the location `fieldLoc`), then
+         * after the loop below:
+         *
+         *     /red/blue/green
+         *               ▲
+         *               memberNameEndIt
+         *
+         *     meow/mix
+         *     ▲
+         *     tmpItemIt
+         *
+         * Final, absolute items:
+         *
+         *     /red/blue/meow/mix
+         */
+        BT_ASSERT(!_mMemberNames.empty());
+
+        auto tmpItemIt = tmpItems.begin();
+        auto memberNameEndIt = _mMemberNames.end() - 1;
+
+        for (; tmpItemIt != tmpItems.end(); ++tmpItemIt) {
+            if (*tmpItemIt) {
+                /* End of leading "parent" items */
+                break;
+            }
+
+            if (memberNameEndIt == _mMemberNames.begin()) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, fieldLoc.loc(),
+                    "Invalid field location: too many \"parent\" path items.");
+            }
+
+            --memberNameEndIt;
+        }
+
+        FieldLoc::Items items;
+
+        for (auto memberNameIt = _mMemberNames.begin(); memberNameIt != memberNameEndIt;
+             ++memberNameIt) {
+            items.push_back(**memberNameIt);
+        }
+
+        for (; tmpItemIt != tmpItems.end(); ++tmpItemIt) {
+            items.push_back(*tmpItemIt);
+        }
+
+        /* Create absolute field location */
+        return createFieldLoc(fieldLoc.loc(), _mScope, std::move(items));
+    }
+
+    std::vector<const std::string *> _mMemberNames;
+    Scope _mScope;
+    bt2c::Logger _mLogger;
+};
+
+} /* namespace */
+
+void normalizeFieldLocs(Fc& scopeFc, const Scope scope, const bt2c::Logger& parentLogger)
+{
+    Normalizer normalizer {scope, parentLogger};
+
+    scopeFc.accept(normalizer);
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.hpp b/src/plugins/ctf/common/src/metadata/json/normalize-field-locs.hpp
new file mode 100644 (file)
index 0000000..1df9f38
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2023-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_NORMALIZE_FIELD_LOCS_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_NORMALIZE_FIELD_LOCS_HPP
+
+#include "cpp-common/bt2c/logging.hpp"
+
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Converts all the relative field locations in `scopeFc` into absolute
+ * field locations.
+ *
+ * `scopeFc` is a scope field class for the scope `scope`.
+ *
+ * ┌────────────────────────────────────────────────────────────────┐
+ * │ IMPORTANT: This function doesn't guarantee that the normalized │
+ * │ field locations are valid.                                     │
+ * └────────────────────────────────────────────────────────────────┘
+ *
+ * Appends one or more causes to the error of the current thread using
+ * and throws `bt2c::Error` when any relative field location in
+ * `scopeFc` is invalid.
+ */
+void normalizeFieldLocs(Fc& scopeFc, Scope scope, const bt2c::Logger& parentLogger);
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_NORMALIZE_FIELD_LOCS_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.cpp b/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.cpp
new file mode 100644 (file)
index 0000000..2d09452
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2023-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <unordered_map>
+#include <utility>
+
+#include "common/assert.h"
+#include "cpp-common/bt2c/logging.hpp"
+
+#include "key-fc-types.hpp"
+#include "resolve-fcs-with-int-sel.hpp"
+
+namespace ctf {
+namespace src {
+namespace {
+
+SIntRangeSet sIntRangeSetFromUIntRangeSet(const UIntRangeSet& uIntRanges)
+{
+    SIntRangeSet::Set sIntRanges;
+
+    for (auto& uIntRange : uIntRanges) {
+        sIntRanges.emplace(SIntRangeSet::Range {static_cast<SIntRangeSet::Val>(uIntRange.lower()),
+                                                static_cast<SIntRangeSet::Val>(uIntRange.upper())});
+    }
+
+    return SIntRangeSet {std::move(sIntRanges)};
+}
+
+/*
+ * Helper of resolveFcsWithIntSel().
+ */
+class Resolver final : public FcVisitor
+{
+public:
+    explicit Resolver(const KeyFcTypes& keyFcTypes, const bt2c::Logger& parentLogger) :
+        _mKeyFcTypes {&keyFcTypes}, _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-RES-FCS-WITH-INT-SEL"}
+    {
+    }
+
+private:
+    void visit(StaticLenArrayFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(DynLenArrayFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(StructFc& fc) override
+    {
+        for (auto& memberCls : fc) {
+            try {
+                memberCls.fc(this->_resolveFc(memberCls.takeFc()));
+                memberCls.fc().accept(*this);
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                    memberCls.fc().loc(), "Invalid structure field member class.");
+            }
+        }
+    }
+
+    void visit(OptionalWithBoolSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(OptionalWithUIntSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(OptionalWithSIntSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(VariantWithUIntSelFc& fc) override
+    {
+        this->_visitVariantFc(fc);
+    }
+
+    void visit(VariantWithSIntSelFc& fc) override
+    {
+        this->_visitVariantFc(fc);
+    }
+
+    void _visit(ArrayFc& fc)
+    {
+        try {
+            fc.elemFc(this->_resolveFc(fc.takeElemFc()));
+            fc.elemFc().accept(*this);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                fc.loc(), "Invalid element field class of array field class.");
+        }
+    }
+
+    void _visit(OptionalFc& fc)
+    {
+        try {
+            fc.fc(this->_resolveFc(fc.takeFc()));
+            fc.fc().accept(*this);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                fc.loc(), "Invalid field class of optional field class.");
+        }
+    }
+
+    template <typename VarFcT>
+    void _visitVariantFc(VarFcT& fc)
+    {
+        for (auto& opt : fc) {
+            try {
+                opt.fc(this->_resolveFc(opt.takeFc()));
+                opt.fc().accept(*this);
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(opt.fc().loc(),
+                                                             "Invalid variant field class option.");
+            }
+        }
+    }
+
+    Fc::UP _resolveFc(Fc::UP fc)
+    {
+        if (!fc->isOptionalWithUIntSel() && !fc->isVariantWithUIntSel()) {
+            /* Not a candidate for replacement: return as is */
+            return fc;
+        }
+
+        const auto keyFcType = _mKeyFcTypes->at(fc.get());
+
+        if (keyFcType == KeyFcType::UInt) {
+            /* Type of `*fc` is already correct: return as is */
+            return fc;
+        }
+
+        BT_ASSERT(keyFcType == KeyFcType::SInt);
+
+        /*
+         * The goal here is to create an `OptionalWithSIntSel` or a
+         * `VariantWithSIntSel` field class from an
+         * `OptionalWithUIntSel` or a `VariantWithUIntSel` field class,
+         * stealing (moving) as much as possible from `*fc` as it will
+         * be discarded anyway (explaining all the take*()
+         * method calls).
+         *
+         * sIntRangeSetFromUIntRangeSet() converts the selector field
+         * ranges from unsigned to signed.
+         */
+        if (fc->isOptionalWithUIntSel()) {
+            auto& optionalFc = fc->asOptionalWithUIntSel();
+
+            return createOptionalFc(
+                optionalFc.loc(), optionalFc.takeFc(), optionalFc.takeSelFieldLoc(),
+                sIntRangeSetFromUIntRangeSet(optionalFc.selFieldRanges()), optionalFc.takeAttrs());
+        } else {
+            BT_ASSERT(fc->isVariantWithUIntSel());
+
+            auto& varFc = fc->asVariantWithUIntSel();
+            VariantWithSIntSelFc::Opts newOpts;
+
+            for (auto& opt : varFc) {
+                newOpts.emplace_back(createVariantFcOpt(
+                    opt.takeFc(), sIntRangeSetFromUIntRangeSet(opt.selFieldRanges()),
+                    opt.takeName(), opt.takeAttrs()));
+            }
+
+            return createVariantFc(varFc.loc(), std::move(newOpts), varFc.takeSelFieldLoc(),
+                                   varFc.takeAttrs());
+        }
+    }
+
+    const KeyFcTypes *_mKeyFcTypes;
+    bt2c::Logger _mLogger;
+};
+
+} /* namespace */
+
+void resolveFcsWithIntSel(Fc& scopeFc, const Scope scope, const Fc * const pktHeaderFc,
+                          const Fc * const pktCtxFc, const Fc * const eventRecordHeaderFc,
+                          const Fc * const commonEventRecordCtxFc,
+                          const Fc * const specEventRecordCtxFc,
+                          const Fc * const eventRecordPayloadFc, const bt2c::Logger& logger)
+{
+    const auto theKeyFcTypes =
+        keyFcTypes(scopeFc, scope, pktHeaderFc, pktCtxFc, eventRecordHeaderFc,
+                   commonEventRecordCtxFc, specEventRecordCtxFc, eventRecordPayloadFc, logger);
+    Resolver resolver {theKeyFcTypes, logger};
+
+    scopeFc.accept(resolver);
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.hpp b/src/plugins/ctf/common/src/metadata/json/resolve-fcs-with-int-sel.hpp
new file mode 100644 (file)
index 0000000..280919c
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2023-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_RESOLVE_FCS_WITH_INT_SEL_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_RESOLVE_FCS_WITH_INT_SEL_HPP
+
+#include "cpp-common/bt2c/logging.hpp"
+
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Replaces all the field classes within `scopeFc` having the type
+ * `FcType::OptionalWithUIntSel` or `FcType::VariantWithUIntSel` with
+ * the correct versions, keeping their children as is.
+ *
+ * `scopeFc` is a scope field class for the scope `scope`.
+ *
+ * Also validates the dynamic-length field classes.
+ *
+ * This function uses keyFcTypes() behind the scenes, therefore all the
+ * preconditions of the latter apply.
+ *
+ * Appends one or more causes to the error of the current thread and
+ * throws `bt2c::Error` when any field class in `scopeFc` is invalid.
+ */
+void resolveFcsWithIntSel(Fc& scopeFc, Scope scope, const Fc *pktHeaderFc, const Fc *pktCtxFc,
+                          const Fc *eventRecordHeaderFc, const Fc *commonEventRecordCtxFc,
+                          const Fc *specEventRecordCtxFc, const Fc *eventRecordPayloadFc,
+                          const bt2c::Logger& logger);
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_RESOLVE_FCS_WITH_INT_SEL_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/utils.cpp b/src/plugins/ctf/common/src/metadata/json/utils.cpp
new file mode 100644 (file)
index 0000000..d6010b1
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <sstream>
+
+#include "cpp-common/bt2c/bt2-value-from-json-val.hpp"
+
+#include "utils.hpp"
+
+namespace ctf {
+namespace src {
+
+bt2::MapValue::Shared bt2ValueOfObj(const bt2c::JsonObjVal& jsonObjVal, const std::string& key)
+{
+    if (const auto jsonUserAttrsVal = jsonObjVal[key]) {
+        return bt2c::bt2ValueFromJsonVal(*jsonUserAttrsVal)->asMap().shared();
+    }
+
+    return bt2::MapValue::Shared {};
+}
+
+std::string absFieldLocStr(const FieldLoc& fieldLoc, const FieldLoc::Items::const_iterator end)
+{
+    std::ostringstream ss;
+
+    BT_ASSERT(fieldLoc.origin());
+    ss << '[' << scopeStr(*fieldLoc.origin());
+
+    for (auto it = fieldLoc.begin(); it != end; ++it) {
+        BT_ASSERT(*it);
+        ss << fmt::format(", `{}`", **it);
+    }
+
+    ss << ']';
+    return ss.str();
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/utils.hpp b/src/plugins/ctf/common/src/metadata/json/utils.hpp
new file mode 100644 (file)
index 0000000..e783ed4
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_UTILS_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_UTILS_HPP
+
+#include <string>
+
+#include "common/common.h"
+#include "cpp-common/bt2/value.hpp"
+#include "cpp-common/bt2c/json-val.hpp"
+
+#include "../ctf-ir.hpp"
+#include "strings.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Returns the object of the JSON object value `jsonObjVal` having the
+ * key `key` as a libbabeltrace2 value object, or `bt2s::nullopt` if
+ * there's no such key.
+ */
+bt2::MapValue::Shared bt2ValueOfObj(const bt2c::JsonObjVal& jsonObjVal, const std::string& key);
+
+/*
+ * Returns the attributes of the JSON object value `jsonObjVal`, or
+ * `bt2s::nullopt` if there's no such property.
+ */
+inline bt2::MapValue::Shared attrsOfObj(const bt2c::JsonObjVal& jsonObjVal)
+{
+    return bt2ValueOfObj(jsonObjVal, jsonstr::attrs);
+}
+
+/*
+ * Returns the raw integer value from the JSON unsigned or signed
+ * integer value `jsonIntVal`, casted as `ValT`.
+ */
+template <typename ValT>
+ValT rawIntValFromJsonIntVal(const bt2c::JsonVal& jsonIntVal) noexcept
+{
+    if (jsonIntVal.isUInt()) {
+        return static_cast<ValT>(*jsonIntVal.asUInt());
+    } else {
+        return static_cast<ValT>(*jsonIntVal.asSInt());
+    }
+}
+
+/*
+ * Returns the optional raw string value from the property named
+ * `propName` within `jsonObjVal`.
+ */
+inline bt2s::optional<std::string> optStrOfObj(const bt2c::JsonObjVal& jsonObjVal,
+                                               const char * const propName)
+{
+    const auto jsonVal = jsonObjVal[propName];
+
+    if (jsonVal) {
+        return *jsonVal->asStr();
+    }
+
+    return bt2s::nullopt;
+}
+
+inline const char *scopeStr(const Scope scope) noexcept
+{
+    switch (scope) {
+    case Scope::PktHeader:
+        return "packet header";
+    case Scope::PktCtx:
+        return "packet context";
+    case Scope::EventRecordHeader:
+        return "event record header";
+    case Scope::CommonEventRecordCtx:
+        return "common event record context";
+    case Scope::SpecEventRecordCtx:
+        return "specific event record context";
+    case Scope::EventRecordPayload:
+        return "event record payload";
+    default:
+        bt_common_abort();
+    }
+}
+
+/*
+ * Returns a string representation of `fieldLoc`, considering all its
+ * path items until `end` (excluded).
+ *
+ * `fieldLoc` must be an absolute field location.
+ */
+std::string absFieldLocStr(const FieldLoc& fieldLoc, FieldLoc::Items::const_iterator end);
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_UTILS_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.cpp b/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.cpp
new file mode 100644 (file)
index 0000000..0102eb1
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2022-2023 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "cpp-common/bt2c/contains.hpp"
+
+#include "strings.hpp"
+#include "validate-scope-fc-roles.hpp"
+
+namespace ctf {
+namespace src {
+namespace {
+
+const char *validScopeNamesForRole(const UIntFieldRole role) noexcept
+{
+    switch (role) {
+    case UIntFieldRole::PktMagicNumber:
+    case UIntFieldRole::DataStreamClsId:
+    case UIntFieldRole::DataStreamId:
+        return "a packet header";
+    case UIntFieldRole::DefClkTs:
+        return "a packet context or an event record header";
+    case UIntFieldRole::PktTotalLen:
+    case UIntFieldRole::PktContentLen:
+    case UIntFieldRole::PktEndDefClkTs:
+    case UIntFieldRole::DiscEventRecordCounterSnap:
+    case UIntFieldRole::PktSeqNum:
+        return "a packet context";
+    case UIntFieldRole::EventRecordClsId:
+        return "an event record header";
+    default:
+        bt_common_abort();
+    }
+}
+
+const char *uIntFcRoleJsonStr(const UIntFieldRole role) noexcept
+{
+    switch (role) {
+    case UIntFieldRole::PktMagicNumber:
+        return jsonstr::pktMagicNumber;
+    case UIntFieldRole::DataStreamClsId:
+        return jsonstr::dataStreamClsId;
+    case UIntFieldRole::DataStreamId:
+        return jsonstr::dataStreamId;
+    case UIntFieldRole::PktTotalLen:
+        return jsonstr::pktTotalLen;
+    case UIntFieldRole::PktContentLen:
+        return jsonstr::pktContentLen;
+    case UIntFieldRole::DefClkTs:
+        return jsonstr::defClkTs;
+    case UIntFieldRole::PktEndDefClkTs:
+        return jsonstr::pktEndDefClkTs;
+    case UIntFieldRole::DiscEventRecordCounterSnap:
+        return jsonstr::discEventRecordCounterSnap;
+    case UIntFieldRole::PktSeqNum:
+        return jsonstr::pktSeqNum;
+    case UIntFieldRole::EventRecordClsId:
+        return jsonstr::eventRecordClsId;
+    default:
+        bt_common_abort();
+    }
+}
+
+/*
+ * Helper of validateScopeFcRoles().
+ */
+class Validator final : public ConstFcVisitor
+{
+public:
+    explicit Validator(const UIntFieldRoles& allowedRoles, const bool allowMetadataStreamUuidRole,
+                       const bt2c::Logger& parentLogger) :
+        _mLogger {parentLogger, "PLUGIN/CTF/CTF-2-VALIDATE-SCOPE-FC-ROLES"},
+        _mAllowedRoles {&allowedRoles}, _mAllowMetadataStreamUuidRole {allowMetadataStreamUuidRole}
+    {
+    }
+
+private:
+    void visit(const FixedLenUIntFc& fc) override
+    {
+        this->_validateUIntFc(fc);
+    }
+
+    void visit(const VarLenUIntFc& fc) override
+    {
+        this->_validateUIntFc(fc);
+    }
+
+    void visit(const StaticLenBlobFc& fc) override
+    {
+        if (fc.asStaticLenBlob().hasMetadataStreamUuidRole() && !_mAllowMetadataStreamUuidRole) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, fc.loc(),
+                "Static-length BLOB field class may not have the `{}` role here "
+                "(only valid within a packet header field class).",
+                jsonstr::metadataStreamUuid);
+        }
+    }
+
+    void visit(const StaticLenArrayFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const DynLenArrayFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const StructFc& fc) override
+    {
+        for (auto& memberCls : fc) {
+            try {
+                memberCls.fc().accept(*this);
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                    memberCls.fc().loc(), "Invalid structure field member class.");
+            }
+        }
+    }
+
+    void visit(const OptionalWithBoolSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const OptionalWithUIntSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const OptionalWithSIntSelFc& fc) override
+    {
+        this->_visit(fc);
+    }
+
+    void visit(const VariantWithUIntSelFc& fc) override
+    {
+        this->_visitVariantFc(fc);
+    }
+
+    void visit(const VariantWithSIntSelFc& fc) override
+    {
+        this->_visitVariantFc(fc);
+    }
+
+    template <typename UIntFcT>
+    void _validateUIntFc(const UIntFcT& fc) const
+    {
+        for (const auto role : fc.roles()) {
+            if (!bt2c::contains(*_mAllowedRoles, role)) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, fc.loc(),
+                    "Unsigned integer field class may not have the `{}` role here "
+                    "(only valid within {} field class).",
+                    uIntFcRoleJsonStr(role), validScopeNamesForRole(role));
+            }
+        }
+    }
+
+    void _visit(const ArrayFc& fc)
+    {
+        try {
+            fc.elemFc().accept(*this);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                fc.loc(), "Invalid element field class of array field class.");
+        }
+    }
+
+    void _visit(const OptionalFc& fc)
+    {
+        try {
+            fc.fc().accept(*this);
+        } catch (const bt2c::Error&) {
+            BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(
+                fc.loc(), "Invalid field class of optional field class.");
+        }
+    }
+
+    template <typename VarFcT>
+    void _visitVariantFc(const VarFcT& fc)
+    {
+        for (auto& opt : fc) {
+            try {
+                opt.fc().accept(*this);
+            } catch (const bt2c::Error&) {
+                BT_CPPLOGE_TEXT_LOC_APPEND_CAUSE_AND_RETHROW(opt.fc().loc(),
+                                                             "Invalid variant field class option.");
+            }
+        }
+    }
+
+    bt2c::Logger _mLogger;
+    const UIntFieldRoles *_mAllowedRoles;
+    bool _mAllowMetadataStreamUuidRole;
+};
+
+} /* namespace */
+
+void validateScopeFcRoles(const Fc& fc, const UIntFieldRoles& allowedRoles,
+                          const bool allowMetadataStreamUuidRole, const bt2c::Logger& parentLogger)
+{
+    Validator validator {allowedRoles, allowMetadataStreamUuidRole, parentLogger};
+
+    fc.accept(validator);
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.hpp b/src/plugins/ctf/common/src/metadata/json/validate-scope-fc-roles.hpp
new file mode 100644 (file)
index 0000000..b3510fc
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2022-2024 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_VALIDATE_SCOPE_FC_ROLES_HPP
+#define BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_VALIDATE_SCOPE_FC_ROLES_HPP
+
+#include "cpp-common/bt2c/logging.hpp"
+
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Validates that:
+ *
+ * • If any unsigned integer field class within `fc` has one or more
+ *   roles, then those roles are part of the `allowedRoles` set.
+ *
+ * • If any static-length BLOB field class within `fc` has the metadata
+ *   stream UUID role, then `allowMetadataStreamUuidRole` is true.
+ *
+ * Appends one or more causes to the error of the current thread and
+ * throws `bt2c::Error` when `fc` is invalid.
+ */
+void validateScopeFcRoles(const Fc& fc, const UIntFieldRoles& allowedRoles,
+                          bool allowMetadataStreamUuidRole, const bt2c::Logger& parentLogger);
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* BABELTRACE_PLUGINS_CTF_COMMON_SRC_METADATA_JSON_VALIDATE_SCOPE_FC_ROLES_HPP */
This page took 0.058622 seconds and 4 git commands to generate.