Add `ctf::src::Ctf2MetadataStreamParser` class (JSON text sequence)
authorPhilippe Proulx <eeppeliteloop@gmail.com>
Sun, 19 Jun 2022 01:28:48 +0000 (21:28 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Tue, 23 Aug 2022 16:06:16 +0000 (12:06 -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

Change-Id: I9ab6b95a17f96a11d8c9db2baa27e28099b0dbcc

src/plugins/ctf/common/src/metadata/json/Makefile.am
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/json-fcs-with-role.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/json-fcs-with-role.hpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/scope-fc-from-json-val.cpp [new file with mode: 0644]
src/plugins/ctf/common/src/metadata/json/scope-fc-from-json-val.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]

index bee94fe6f6419eada2a621253782544d2ecc604a..da6440f55926dbcdcc14969c806494e8ae7d473a 100644 (file)
@@ -3,4 +3,8 @@
 noinst_LTLIBRARIES = libctf-src-metadata-json.la
 
 libctf_src_metadata_json_la_SOURCES = \
-       val-req.cpp val-req.hpp
+       val-req.cpp val-req.hpp \
+       utils.cpp utils.hpp \
+       ctf-2-metadata-stream-parser.cpp ctf-2-metadata-stream-parser.hpp \
+       scope-fc-from-json-val.cpp scope-fc-from-json-val.hpp \
+       json-fcs-with-role.cpp json-fcs-with-role.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..174290d
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+ * Copyright (c) 2022 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define BT_LOG_TAG  "PLUGIN/CTF/CTF-2-META-STREAM-PARSER"
+#define BT_CLOG_CFG _mLogCfg
+#include "cpp-common/cfg-logging-error-reporting.hpp"
+
+#include <sstream>
+
+#include "common/assert.h"
+#include "cpp-common/parse-json-as-val.hpp"
+#include "cpp-common/bt2-value-from-json-val.hpp"
+#include "ctf-2-metadata-stream-parser.hpp"
+#include "scope-fc-from-json-val.hpp"
+#include "json-fcs-with-role.hpp"
+#include "../../../metadata/json/strings.hpp"
+#include "utils.hpp"
+
+namespace ctf {
+namespace src {
+
+namespace bt2c = bt2_common;
+namespace strings = json_strings;
+
+static inline bt2c::JsonObjVal::UP createDefClkOffsetJsonObjVal()
+{
+    bt2c::JsonObjVal::Container entries;
+
+    entries.insert(std::make_pair(strings::seconds, bt2c::createJsonVal(0LL, bt2c::TextLoc {})));
+    entries.insert(std::make_pair(strings::cycles, bt2c::createJsonVal(0ULL, bt2c::TextLoc {})));
+    return bt2c::createJsonVal(std::move(entries), bt2c::TextLoc {});
+}
+
+Ctf2MetadataStreamParser::Ctf2MetadataStreamParser(const ClkClsCfg& clkClsCfg,
+                                                   bt_self_component * const selfComp,
+                                                   const bt2c::LogCfg& logCfg) :
+    MetadataStreamParser {clkClsCfg, selfComp},
+    _mFragmentValReq {logCfg, bt2c::TextLocStrFmt::OFFSET},
+    _mDefClkOffsetVal {createDefClkOffsetJsonObjVal()}, _mLogCfg {logCfg}
+{
+}
+
+MetadataStreamParser::ParseRet Ctf2MetadataStreamParser::parse(const ClkClsCfg& clkClsCfg,
+                                                               bt_self_component * const selfComp,
+                                                               const uint8_t * const begin,
+                                                               const uint8_t * const end,
+                                                               const bt2_common::LogCfg& logCfg)
+{
+    Ctf2MetadataStreamParser parser {clkClsCfg, selfComp, logCfg};
+
+    parser.parseSection(begin, end);
+
+    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 `begin` to
+         * `end` contains the whole metadata stream.
+         */
+        BT_CLOGE_APPEND_CAUSE_AND_THROW_EX(
+            logCfg, bt2::Error, "Missing data stream class fragment in metadata stream.");
+    }
+
+    return std::make_pair(parser.releaseTraceCls(), parser.metadataStreamUuid());
+}
+
+void Ctf2MetadataStreamParser::_parseSection(const std::uint8_t * const begin,
+                                             const std::uint8_t * const end)
+{
+    this->_parseFragments(begin, end);
+}
+
+void Ctf2MetadataStreamParser::_parseFragments(const std::uint8_t * const begin,
+                                               const std::uint8_t * const end)
+{
+    BT_ASSERT(begin);
+    BT_ASSERT(end);
+    BT_ASSERT(end >= begin);
+
+    auto fragmentBegin = begin;
+    const auto curSectionOffsetInStream = _mCurOffsetInStream;
+
+    while (true) {
+        /* Find the beginning pointer of the current JSON fragment */
+        while (fragmentBegin != end && *fragmentBegin == 30) {
+            /* Skip RS byte */
+            ++fragmentBegin;
+        }
+
+        _mCurOffsetInStream =
+            curSectionOffsetInStream + bt2c::DataLen::fromBytes(fragmentBegin - begin);
+
+        if (fragmentBegin == end) {
+            /* We're done */
+            return;
+        }
+
+        /* Find the end pointer of the current JSON fragment */
+        auto fragmentEnd = fragmentBegin;
+
+        while (fragmentEnd != end && *fragmentEnd != 30) {
+            /* Skip non-RS byte */
+            ++fragmentEnd;
+        }
+
+        if (fragmentBegin == fragmentEnd) {
+            BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error, "[%s] Expecting a fragment.",
+                                            textLocStr(this->_loc(begin, fragmentBegin)).c_str());
+        }
+
+        /* Parse one fragment */
+        _mCurOffsetInStream =
+            curSectionOffsetInStream + bt2c::DataLen::fromBytes(fragmentBegin - begin);
+        this->_parseFragment(fragmentBegin, fragmentEnd);
+
+        /* Go to next fragment */
+        fragmentBegin = fragmentEnd;
+        ++_mCurFragmentIndex;
+    }
+
+    /* Adjust offset in metadat stream for the next section to parse */
+    _mCurOffsetInStream = curSectionOffsetInStream + bt2c::DataLen::fromBytes(end - begin);
+}
+
+void Ctf2MetadataStreamParser::_parseFragment(const std::uint8_t * const begin,
+                                              const std::uint8_t * const end)
+{
+    try {
+        this->_handleFragment(bt2c::parseJson(
+            reinterpret_cast<const char *>(begin), reinterpret_cast<const char *>(end),
+            _mCurOffsetInStream.bytes(), _mLogCfg, bt2c::TextLocStrFmt::OFFSET));
+    } catch (const bt2c::Error&) {
+        BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid fragment #%zu.",
+                                          textLocStr(this->_loc(begin, begin)).c_str(),
+                                          _mCurFragmentIndex + 1);
+    }
+}
+
+void Ctf2MetadataStreamParser::_handleFragment(bt2c::JsonVal::UP jsonFragment)
+{
+    /* Validate the fragment */
+    _mFragmentValReq.validate(*jsonFragment);
+
+    /* Get type */
+    auto& jsonFragmentObj = jsonFragment->asObj();
+    auto& type = jsonFragmentObj.rawStrVal(strings::type);
+
+    /* Specific preamble fragment case */
+    if (_mCurFragmentIndex == 0) {
+        if (type != strings::preamble) {
+            BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error, "[%s] Expecting the preamble fragment.",
+                                            textLocStr(jsonFragmentObj).c_str());
+        }
+
+        /* 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 == strings::preamble) {
+        BT_ASSERT(_mCurFragmentIndex > 0);
+        BT_CLOGE_APPEND_CAUSE_AND_THROW(
+            bt2c::Error,
+            "[%s] Preamble fragment must be the first fragment of the metadata stream.",
+            textLocStr(jsonFragmentObj).c_str());
+    } else if (type == strings::traceCls) {
+        this->_handleTraceClsFragment(std::move(jsonFragment));
+    } else if (type == strings::clkCls) {
+        this->_handleClkClsFragment(jsonFragmentObj);
+    } else if (type == strings::dataStreamCls) {
+        this->_handleDataStreamClsFragment(std::move(jsonFragment));
+    } else {
+        BT_ASSERT(type == strings::eventRecordCls);
+        this->_handleEventRecordClsFragment(jsonFragmentObj);
+    }
+}
+
+void Ctf2MetadataStreamParser::_validatePktHeaderFcRoles(const bt2c::JsonObjVal& jsonPktHeaderFc)
+{
+    /*
+     * 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 the
+     *   packet header scope structure field class.
+     *
+     * • There's only one such FC within the whole packet header scope
+     *   field class.
+     */
+    {
+        const auto jsonFcs = jsonFcsWithRole(jsonPktHeaderFc, {strings::pktMagicNumber}, false);
+
+        if (jsonFcs.size() > 1) {
+            BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                bt2c::Error,
+                "[%s] Packet header field class may contain zero or one field classes having the role `%s`, not %zu.",
+                textLocStr(**jsonFcs.begin()).c_str(), strings::pktMagicNumber, jsonFcs.size());
+        }
+
+        if (!jsonFcs.empty()) {
+            auto& jsonFc = (*jsonFcs.begin())->asObj();
+            auto& jsonType = jsonFc[strings::type]->asStr();
+
+            if (*jsonType != strings::fixedLenUInt && *jsonType != strings::fixedLenUEnum) {
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error,
+                    "[%s] Unexpected type of field class having the `%s` role with the `%s` type: "
+                    "expecting `%s` or `%s`.",
+                    textLocStr(jsonType).c_str(), strings::pktMagicNumber, (*jsonType).c_str(),
+                    strings::fixedLenUInt, strings::fixedLenUEnum);
+            }
+
+            auto& jsonLen = jsonFc[strings::len]->asUInt();
+
+            if (*jsonLen != 32) {
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error,
+                    "[%s] Unexpected `%s` property of fixed-length unsigned integer field class having the `%s` role: "
+                    "expecting 32, not %llu.",
+                    textLocStr(jsonLen).c_str(), strings::len, strings::pktMagicNumber, *jsonLen);
+            }
+        }
+    }
+
+    /*
+     * 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 jsonFcs = jsonFcsWithRole(jsonPktHeaderFc, {}, true);
+
+        if (!jsonFcs.empty() && !_mMetadataStreamUuid) {
+            BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                bt2c::Error,
+                "[%s] Static-length BLOB field class has the role `%s`, "
+                "but the preamble fragment of the metadata stream has no `%s` property.",
+                textLocStr(**jsonFcs.begin()).c_str(), strings::metadataStreamUuid, strings::uuid);
+        }
+    }
+}
+
+void Ctf2MetadataStreamParser::_validateTraceClsFragmentRoles(const bt2c::JsonObjVal& jsonFragment)
+{
+    const auto jsonPktHeaderFc = jsonFragment[strings::pktHeaderFc];
+
+    if (!jsonPktHeaderFc) {
+        /*
+         * Nothing to validate without a packet header scope field
+         * class.
+         */
+        return;
+    }
+
+    try {
+        this->_validatePktHeaderFcRoles(jsonPktHeaderFc->asObj());
+    } catch (const bt2c::Error&) {
+        BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid packet header field class.",
+                                          textLocStr(*jsonPktHeaderFc).c_str());
+    }
+}
+
+void Ctf2MetadataStreamParser::_handleTraceClsFragment(bt2c::JsonVal::UP jsonFragment)
+{
+    auto& jsonFragmentObj = jsonFragment->asObj();
+
+    /* Validate field roles */
+    try {
+        this->_validateTraceClsFragmentRoles(jsonFragmentObj);
+    } catch (const bt2c::Error&) {
+        BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid trace class fragment.",
+                                          textLocStr(jsonFragmentObj).c_str());
+    }
+
+    /* Check for trace class uniqueness */
+    if (_mTraceCls) {
+        BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error, "[%s] Duplicate trace class fragment.",
+                                        textLocStr(jsonFragmentObj).c_str());
+    }
+
+    /* Create the current trace class */
+    auto pktHeaderFc =
+        this->_scopeFcOfJsonVal(jsonFragmentObj, strings::pktHeaderFc,
+                                ir::FieldLocScope::PKT_HEADER, &jsonFragmentObj, nullptr, nullptr);
+
+    _mTraceCls =
+        createTraceCls(uuidOfObj(jsonFragmentObj), bt2ValueOfObj(jsonFragmentObj, strings::env),
+                       std::move(pktHeaderFc), userAttrsOfObj(jsonFragmentObj));
+
+    /*
+     * An LTTng clock always has a Unix epoch origin even though the
+     * class indicates otherwise.
+     */
+    const auto env = _mTraceCls->env();
+
+    if (env) {
+        const auto entry = (*env)["tracer_name"];
+        static constexpr auto lttngPrefix = "lttng";
+
+        if (entry && entry->isString() &&
+            entry->asString().value().substr(0, strlen(lttngPrefix)) == lttngPrefix) {
+            /* Save this to adjust future clock classes */
+            _mIsLttng = true;
+
+            /* Adjust existing clock classes */
+            for (auto& nameClkClsPair : _mClkClasses) {
+                nameClkClsPair.second->originIsUnixEpoch(true);
+            }
+        }
+    }
+
+    _mJsonTraceCls = std::move(jsonFragment);
+}
+
+void Ctf2MetadataStreamParser::_handleClkClsFragment(const bt2c::JsonObjVal& jsonFragment)
+{
+    /* Name */
+    auto name = jsonFragment.rawStrVal(strings::name);
+
+    if (_mClkClasses.find(name) != _mClkClasses.end()) {
+        BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                        "[%s] Duplicate clock class fragment named `%s`.",
+                                        textLocStr(jsonFragment).c_str(), name.c_str());
+    }
+
+    /* Description */
+    nonstd::optional<std::string> descr;
+    const auto jsonDescrVal = jsonFragment[strings::descr];
+
+    if (jsonDescrVal) {
+        descr = *jsonDescrVal->asStr();
+    }
+
+    /* Offset */
+    auto& jsonOffsetVal = jsonFragment.val(strings::offset, *_mDefClkOffsetVal);
+    const auto jsonOffsetSecsVal = jsonOffsetVal[strings::seconds];
+    auto offsetSeconds = 0LL;
+
+    if (jsonOffsetSecsVal) {
+        offsetSeconds = rawIntValFromJsonIntVal<long long>(*jsonOffsetSecsVal);
+    }
+
+    const auto offsetCycles = jsonOffsetVal.rawVal(strings::cycles, 0ULL);
+
+    /* Create corresponding clock class */
+    auto clkCls = createClkCls(name, jsonFragment.rawUIntVal(strings::freq),
+                               ir::ClkOffset {offsetSeconds, offsetCycles},
+                               _mIsLttng || jsonFragment.rawVal(strings::originIsUnixEpoch, true),
+                               std::move(descr), jsonFragment.rawVal(strings::precision, 0ULL),
+                               uuidOfObj(jsonFragment), userAttrsOfObj(jsonFragment));
+
+    /* Add to map of clock classes */
+    _mClkClasses.emplace(std::make_pair(std::move(name), std::move(clkCls)));
+}
+
+static inline nonstd::optional<std::pair<std::string, bt2c::TextLoc>>
+optStrOfObjWithLoc(const bt2c::JsonObjVal& jsonObjVal, const std::string& propName)
+{
+    const auto jsonVal = jsonObjVal[propName];
+
+    if (jsonVal) {
+        return std::make_pair(*jsonVal->asStr(), jsonVal->loc());
+    }
+
+    return nonstd::nullopt;
+}
+
+static inline std::string fullClsId(const unsigned long long id,
+                                    const nonstd::optional<std::string>& ns,
+                                    const nonstd::optional<std::string>& name)
+{
+    std::ostringstream ss;
+
+    ss << id;
+
+    if (ns || name) {
+        ss << " (";
+
+        if (ns) {
+            ss << '`' << *ns << '`';
+
+            if (name) {
+                ss << '/';
+            }
+        }
+
+        if (name) {
+            ss << '`' << *name << '`';
+        }
+
+        ss << ')';
+    }
+
+    return ss.str();
+}
+
+template <typename ClsT>
+std::string fullClsId(const ClsT& cls)
+{
+    return fullClsId(cls.id(), cls.ns(), cls.name());
+}
+
+void Ctf2MetadataStreamParser::_validateDefClkTsRoles(const bt2c::JsonObjVal& jsonScopeFc,
+                                                      const bool hasDefClkCls)
+{
+    const auto jsonFcs =
+        jsonFcsWithRole(jsonScopeFc, {strings::defClkTs, strings::pktEndDefClkTs}, false);
+
+    if (!jsonFcs.empty() && !hasDefClkCls) {
+        BT_CLOGE_APPEND_CAUSE_AND_THROW(
+            bt2c::Error,
+            "[%s] Invalid unsigned integer field class having the `%s` or `%s` role because its containing data stream class fragment has no default clock class (missing `%s` property).",
+            textLocStr(**jsonFcs.begin()).c_str(), strings::defClkTs, strings::pktEndDefClkTs,
+            strings::defClkClsName);
+    }
+}
+
+void Ctf2MetadataStreamParser::_validateDataStreamClsFragmentRoles(
+    const bt2c::JsonObjVal& jsonFragment, const bool hasDefClkCls)
+{
+    const auto jsonPktCtxFc = jsonFragment[strings::pktCtxFc];
+
+    if (jsonPktCtxFc) {
+        try {
+            this->_validateDefClkTsRoles(jsonPktCtxFc->asObj(), hasDefClkCls);
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid packet context field class.",
+                                              textLocStr(*jsonPktCtxFc).c_str());
+        }
+    }
+
+    const auto jsonEventRecordHeaderFc = jsonFragment[strings::eventRecordHeaderFc];
+
+    if (jsonEventRecordHeaderFc) {
+        try {
+            this->_validateDefClkTsRoles(jsonEventRecordHeaderFc->asObj(), hasDefClkCls);
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid event record header field class.",
+                                              textLocStr(*jsonEventRecordHeaderFc).c_str());
+        }
+    }
+}
+
+void Ctf2MetadataStreamParser::_handleDataStreamClsFragment(bt2c::JsonVal::UP jsonFragment)
+{
+    this->_ensureExistingTraceCls();
+
+    /* ID */
+    auto& jsonFragmentObj = jsonFragment->asObj();
+    const auto id = jsonFragmentObj.rawVal(strings::id, 0ULL);
+
+    if ((*_mTraceCls)[id]) {
+        BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                        "[%s] Duplicate data stream class fragment with ID %llu.",
+                                        textLocStr(jsonFragmentObj).c_str(), id);
+    }
+
+    /* Default clock class name */
+    const auto defClkClsName = optStrOfObjWithLoc(jsonFragmentObj, strings::defClkClsName);
+    ClkCls::SP defClkCls;
+
+    if (defClkClsName) {
+        const auto it = _mClkClasses.find(defClkClsName->first);
+
+        if (it == _mClkClasses.end()) {
+            BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                bt2c::Error, "[%s] `%s` doesn't name an existing clock class fragment.",
+                textLocStr(defClkClsName->second).c_str(), defClkClsName->first.c_str());
+        }
+
+        defClkCls = it->second;
+    }
+
+    /* Namespace and name */
+    const auto ns = optStrOfObj(jsonFragmentObj, strings::ns);
+    const auto name = optStrOfObj(jsonFragmentObj, strings::name);
+
+    /* Validate field roles and create data stream class */
+    try {
+        /* Validate field roles */
+        this->_validateDataStreamClsFragmentRoles(jsonFragmentObj, defClkClsName.has_value());
+
+        /* Create data stream class */
+        auto pktCtxFc = this->_dataStreamClsScopeFcOfJsonVal(jsonFragmentObj, strings::pktCtxFc,
+                                                             ir::FieldLocScope::PKT_CTX);
+        auto eventRecordHeaderFc = this->_dataStreamClsScopeFcOfJsonVal(
+            jsonFragmentObj, strings::eventRecordHeaderFc, ir::FieldLocScope::EVENT_RECORD_HEADER);
+        auto commonEventRecordCtxFc =
+            this->_dataStreamClsScopeFcOfJsonVal(jsonFragmentObj, strings::eventRecordCommonCtxFc,
+                                                 ir::FieldLocScope::EVENT_RECORD_COMMON_CTX);
+        auto dataStreamCls =
+            createDataStreamCls(id, ns, name, std::move(pktCtxFc), std::move(eventRecordHeaderFc),
+                                std::move(commonEventRecordCtxFc), std::move(defClkCls),
+                                userAttrsOfObj(jsonFragmentObj));
+
+        /* Map data stream class to its JSON value */
+        BT_ASSERT(_mJsonDataStreamClasses.find(dataStreamCls.get()) ==
+                  _mJsonDataStreamClasses.end());
+        _mJsonDataStreamClasses.emplace(
+            std::make_pair(dataStreamCls.get(), std::move(jsonFragment)));
+
+        /* Add data stream class to current trace class */
+        _mTraceCls->addDataStreamCls(std::move(dataStreamCls));
+    } catch (const bt2c::Error&) {
+        BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid data stream class fragment %s.",
+                                          textLocStr(jsonFragmentObj).c_str(),
+                                          fullClsId(id, ns, name).c_str());
+    }
+}
+
+void Ctf2MetadataStreamParser::_handleEventRecordClsFragment(const bt2c::JsonObjVal& jsonFragment)
+{
+    this->_ensureExistingTraceCls();
+
+    /* Data stream class ID */
+    const auto jsonDataStreamClsIdVal = jsonFragment[strings::dataStreamClsId];
+    const auto dataStreamClsId = jsonDataStreamClsIdVal ? *jsonDataStreamClsIdVal->asUInt() : 0ULL;
+    auto dataStreamCls = (*_mTraceCls)[dataStreamClsId];
+
+    if (!dataStreamCls) {
+        BT_CLOGE_APPEND_CAUSE_AND_THROW(
+            bt2c::Error, "[%s] No data stream class fragment exists with ID %llu.",
+            textLocStr(jsonDataStreamClsIdVal ? *jsonDataStreamClsIdVal : jsonFragment).c_str(),
+            dataStreamClsId);
+    }
+
+    /* ID */
+    const auto jsonIdVal = jsonFragment[strings::id];
+    const auto id = jsonIdVal ? *jsonIdVal->asUInt() : 0ULL;
+
+    if ((*dataStreamCls)[id]) {
+        BT_CLOGE_APPEND_CAUSE_AND_THROW(
+            bt2c::Error,
+            "[%s] Duplicate event record class fragment with ID %llu within data stream class fragment %s.",
+            textLocStr(jsonIdVal ? *jsonIdVal : jsonFragment).c_str(), id,
+            fullClsId(*dataStreamCls).c_str());
+    }
+
+    /* Create event record class */
+    const auto ns = optStrOfObj(jsonFragment, strings::ns);
+    const auto name = optStrOfObj(jsonFragment, strings::name);
+
+    try {
+        auto specCtxFc = this->_eventRecordClsScopeFcOfJsonVal(
+            jsonFragment, strings::specCtxFc, ir::FieldLocScope::EVENT_RECORD_SPEC_CTX,
+            *dataStreamCls);
+        auto payloadFc = this->_eventRecordClsScopeFcOfJsonVal(
+            jsonFragment, strings::payloadFc, ir::FieldLocScope::EVENT_RECORD_PAYLOAD,
+            *dataStreamCls);
+        auto eventRecordCls = createEventRecordCls(
+            id, ns, name, std::move(specCtxFc), std::move(payloadFc), userAttrsOfObj(jsonFragment));
+
+        /* Add event record class to data stream class */
+        dataStreamCls->addEventRecordCls(std::move(eventRecordCls));
+    } catch (const bt2c::Error&) {
+        BT_CLOGE_APPEND_CAUSE_AND_RETHROW(
+            "[%s] Invalid event record class fragment %s (for data stream class fragment %s).",
+            textLocStr(jsonFragment).c_str(), fullClsId(id, ns, name).c_str(),
+            fullClsId(*dataStreamCls).c_str());
+    }
+}
+
+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 ir::FieldLocScope scope,
+                                                   const bt2c::JsonVal * const jsonTraceCls,
+                                                   const bt2c::JsonVal * const jsonDataStreamCls,
+                                                   const bt2c::JsonVal * const jsonEventRecordCls)
+{
+    const auto jsonFcVal = jsonVal[key];
+
+    if (!jsonFcVal) {
+        return nullptr;
+    }
+
+    try {
+        return scopeFcFromJsonVal(
+            jsonFcVal->asObj(), static_cast<const bt2c::JsonObjVal *>(jsonTraceCls),
+            static_cast<const bt2c::JsonObjVal *>(jsonDataStreamCls),
+            static_cast<const bt2c::JsonObjVal *>(jsonEventRecordCls), _mLogCfg);
+    } catch (const bt2c::Error&) {
+        BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid %s scope.", textLocStr(jsonVal).c_str(),
+                                          scopeStr(scope));
+    }
+}
+
+Fc::UP Ctf2MetadataStreamParser::_eventRecordClsScopeFcOfJsonVal(
+    const bt2c::JsonObjVal& jsonEventRecordCls, const std::string& key,
+    const ir::FieldLocScope scope, const DataStreamCls& dataStreamCls)
+{
+    return this->_scopeFcOfJsonVal(jsonEventRecordCls, key, scope, _mJsonTraceCls.get(),
+                                   _mJsonDataStreamClasses[&dataStreamCls].get(),
+                                   &jsonEventRecordCls);
+}
+
+Fc::UP
+Ctf2MetadataStreamParser::_dataStreamClsScopeFcOfJsonVal(const bt2c::JsonObjVal& jsonDataStreamCls,
+                                                         const std::string& key,
+                                                         const ir::FieldLocScope scope)
+{
+    return this->_scopeFcOfJsonVal(jsonDataStreamCls, key, scope, _mJsonTraceCls.get(),
+                                   &jsonDataStreamCls, nullptr);
+}
+
+} /* 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..fd75905
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2022 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef _CTF_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP
+#define _CTF_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP
+
+#include <cstdint>
+#include <memory>
+#include <sstream>
+#include <babeltrace2/babeltrace.h>
+
+#include "cpp-common/uuid.hpp"
+#include "cpp-common/data-len.hpp"
+#include "cpp-common/optional.hpp"
+#include "../ctf-ir.hpp"
+#include "../metadata-stream-parser.hpp"
+#include "cpp-common/log-cfg.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.
+ */
+class Ctf2MetadataStreamParser final : public MetadataStreamParser
+{
+public:
+    /*
+     * Builds a CTF 2 metadata stream parser.
+     *
+     * If `selfComp` isn't `nullptr`, then the parser uses it each time
+     * you call parseSection() to finalize the current trace class.
+     */
+    explicit Ctf2MetadataStreamParser(const ClkClsCfg& clkClsCfg, bt_self_component *selfComp,
+                                      const bt2_common::LogCfg& logCfg);
+
+    /*
+     * Parses the whole CTF 2 metadata stream between `begin` and `end`
+     * 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 `bt2_common::Error` otherwise.
+     */
+    static ParseRet parse(const ClkClsCfg& clkClsCfg, bt_self_component *selfComp,
+                          const uint8_t *begin, const uint8_t *end,
+                          const bt2_common::LogCfg& logCfg);
+
+private:
+    void _parseSection(const uint8_t *begin, const uint8_t *end) override;
+
+    /*
+     * Parses one or more complete fragments between `begin` and `end`,
+     * updating the internal state on success, or appending a cause to
+     * the error of the current thread and throwing `bt2_common::Error`
+     * otherwise.
+     */
+    void _parseFragments(const std::uint8_t *begin, const std::uint8_t *end);
+
+    /*
+     * Parses the JSON fragment between `begin` (included) and `end`
+     * (excluded), updating the internal state on success, or appending
+     * a cause to the error of the current thread and throwing
+     * `bt2_common::Error` on failure.
+     */
+    void _parseFragment(const std::uint8_t *begin, const std::uint8_t *end);
+
+    /*
+     * Handles the JSON fragment `jsonFragment`, updating the internal
+     * state on success, or appending a cause to the error of the
+     * current thread and throwing `bt2_common::Error` on failure.
+     */
+    void _handleFragment(bt2_common::JsonVal::UP jsonFragment);
+
+    /*
+     * Validates the field roles of the JSON packet header field class
+     * `jsonPktHeaderFc`.
+     */
+    void _validatePktHeaderFcRoles(const bt2_common::JsonObjVal& jsonPktHeaderFc);
+
+    /*
+     * Validates the field roles of the JSON trace class fragment
+     * `jsonFragment`.
+     */
+    void _validateTraceClsFragmentRoles(const bt2_common::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 `bt2_common::Error` on
+     * failure.
+     */
+    void _handleTraceClsFragment(bt2_common::JsonVal::UP 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 `bt2_common::Error` on
+     * failure.
+     */
+    void _handleClkClsFragment(const bt2_common::JsonObjVal& jsonFragment);
+
+    /*
+     * Validates that the JSON scope field class `jsonScopeFc` doesn't
+     * contain a JSON unsigned integer field class having a default
+     * clock timestamp role if `hasDefClkCls` is false.
+     */
+    void _validateDefClkTsRoles(const bt2_common::JsonObjVal& jsonScopeFc, bool hasDefClkCls);
+
+    /*
+     * Validates that the JSON data stream class fragment `jsonFragment`
+     * doesn't contain a JSON unsigned integer field class having a
+     * default clock timestamp role if `hasDefClkCls` is false.
+     */
+    void _validateDataStreamClsFragmentRoles(const bt2_common::JsonObjVal& jsonFragment,
+                                             bool hasDefClkCls);
+
+    /*
+     * 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 `bt2_common::Error`
+     * on failure.
+     */
+    void _handleDataStreamClsFragment(bt2_common::JsonVal::UP jsonFragment);
+
+    /*
+     * 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 `bt2_common::Error`
+     * on failure.
+     */
+    void _handleEventRecordClsFragment(const bt2_common::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 the JSON trace class fragment value
+     *     `jsonTraceCls`, the JSON data stream class fragment value
+     *     `jsonDataStreamCls` and the JSON event record class fragment
+     *     value `jsonEventRecordCls` as the conversion context.
+     */
+    Fc::UP _scopeFcOfJsonVal(const bt2_common::JsonObjVal& jsonVal, const std::string& key,
+                             ir::FieldLocScope scope, const bt2_common::JsonVal *jsonTraceCls,
+                             const bt2_common::JsonVal *jsonDataStreamCls,
+                             const bt2_common::JsonVal *jsonEventRecordCls);
+
+    /*
+     * 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 JSON trace class fragment
+     *     value `_mJsonTraceCls`, the data stream class `dataStreamCls`
+     *     and the JSON event record class fragment value
+     *     `jsonEventRecordCls` as the conversion context.
+     */
+    Fc::UP _eventRecordClsScopeFcOfJsonVal(const bt2_common::JsonObjVal& jsonEventRecordCls,
+                                           const std::string& key, ir::FieldLocScope scope,
+                                           const DataStreamCls& dataStreamCls);
+
+    /*
+     * 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 JSON trace class fragment value
+     *     `_mJsonTraceCls`, the JSON data stream class fragment value
+     *     `jsonDataStreamCls` as the conversion context.
+     */
+    Fc::UP _dataStreamClsScopeFcOfJsonVal(const bt2_common::JsonObjVal& jsonDataStreamCls,
+                                          const std::string& key, ir::FieldLocScope scope);
+
+    /*
+     * 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 bt2_common::JsonObjVal& jsonTraceCls,
+                                     const std::string& key);
+
+    /*
+     * Returns a text location with an offset of `at` relative to
+     * `begin`, also considering `_mCurOffsetInStream`.
+     */
+    bt2_common::TextLoc _loc(const std::uint8_t * const begin,
+                             const std::uint8_t * const at) const noexcept
+    {
+        return bt2_common::TextLoc {_mCurOffsetInStream.bytes() + (at - begin)};
+    }
+
+private:
+    /* Current offset within the whole metadata stream */
+    bt2_common::DataLen _mCurOffsetInStream = bt2_common::DataLen::fromBytes(0);
+
+    /* Current fragment index */
+    std::size_t _mCurFragmentIndex = 0;
+
+    /* Fragment requirement */
+    Ctf2JsonAnyFragmentValReq _mFragmentValReq;
+
+    /* Default clock offset JSON value */
+    bt2_common::JsonObjVal::UP _mDefClkOffsetVal;
+
+    /*
+     * Map of clock class name 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 a 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;
+
+    /*
+     * fcFromJsonVal() needs some JSON context (trace class, data stream
+     * class, and event record class) to validate the length/selector
+     * field classes (dependencies) of dependent (dynamic-length,
+     * optional, and variant) field classes.
+     *
+     * The `_mJsonDataStreamClasses` member keeps a mapping of CTF IR
+     * data stream classes (owned by `*_mTraceCls` below) to their
+     * original JSON value. The `_mJsonTraceCls` member is possibly the
+     * JSON value of `*_mTraceCls` (`*_mTraceCls` may exist without any
+     * JSON value equivalent).
+     *
+     * When _handleEventRecordClsFragment() calls fcFromJsonVal(),
+     * it passes:
+     *
+     * `jsonTraceCls` parameter:
+     *     `_mJsonTraceCls.get()`
+     *
+     * `jsonDataStreamClsCls` parameter:
+     *     The corresponding JSON value within
+     *     `_mJsonDataStreamClasses`.
+     *
+     * `jsonEventRecordCls` parameter:
+     *     The current JSON event record class fragment value.
+     *
+     * Those three JSON values constitute what's needed to find any
+     * dependency. fcFromJsonVal() uses JSON values instead of CTF IR
+     * objects because:
+     *
+     * • Finding a dependency within the current scope requires the JSON
+     *   value, as the corresponding CTF IR object is currently being
+     *   built and many members of CTF IR objects are immutable.
+     *
+     *   Using only JSON values makes it possible to apply the same
+     *   scope traversing strategy, whether it's the current one or a
+     *   previous one.
+     *
+     * • For text parsing error messages, the JSON values contain their
+     *   original location within the metadata stream.
+     *
+     *   Using a CTF IR trace class and CTF IR data stream classes would
+     *   require keeping mappings of CTF IR field classes to text
+     *   locations.
+     */
+    std::unordered_map<const DataStreamCls *, bt2_common::JsonVal::UP> _mJsonDataStreamClasses;
+    bt2_common::JsonVal::UP _mJsonTraceCls;
+
+    /*  True if the tracer is LTTng (see _handleTraceClsFragment()) */
+    bool _mIsLttng = false;
+
+    /* Logging configuration */
+    bt2_common::LogCfg _mLogCfg;
+};
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* _CTF_SRC_METADATA_JSON_CTF_2_METADATA_STREAM_PARSER_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/json-fcs-with-role.cpp b/src/plugins/ctf/common/src/metadata/json/json-fcs-with-role.cpp
new file mode 100644 (file)
index 0000000..cad454f
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2022 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include "common/assert.h"
+#include "../../../metadata/json/strings.hpp"
+#include "utils.hpp"
+#include "json-fcs-with-role.hpp"
+
+namespace ctf {
+namespace src {
+
+namespace bt2c = bt2_common;
+
+namespace {
+
+namespace strings = json_strings;
+
+const bt2c::JsonArrayVal *rolesOfJsonObj(const bt2c::JsonObjVal& jsonObj) noexcept
+{
+    const auto jsonRoles = jsonObj[strings::roles];
+
+    if (!jsonRoles) {
+        /* No roles */
+        return nullptr;
+    }
+
+    return &jsonRoles->asArray();
+}
+
+void jsonFcsWithRole(const bt2c::JsonVal& jsonFc, const std::unordered_set<std::string>& roleNames,
+                     const bool withMetadataStreamUuidRole,
+                     std::unordered_set<const bt2c::JsonVal *>& set)
+{
+    auto& jsonFcObj = jsonFc.asObj();
+
+    /* Type */
+    auto& type = jsonFcObj.rawStrVal(strings::type);
+
+    if (type == strings::fixedLenUInt || type == strings::fixedLenUEnum ||
+        type == strings::varLenUInt || type == strings::varLenUEnum) {
+        /* Unsigned integer field class */
+        const auto jsonRoles = rolesOfJsonObj(jsonFcObj);
+
+        if (!jsonRoles) {
+            /* No roles */
+            return;
+        }
+
+        for (auto& jsonRole : *jsonRoles) {
+            if (roleNames.find(*jsonRole->asStr()) != roleNames.end()) {
+                set.insert(&jsonFc);
+                return;
+            }
+        }
+    } else if (type == strings::staticLenBlob) {
+        /* Static-length BLOB field class */
+        const auto jsonRoles = rolesOfJsonObj(jsonFcObj);
+
+        if (jsonRoles && !jsonRoles->isEmpty() && withMetadataStreamUuidRole) {
+            /* The only valid role is the metadata stream UUID */
+            set.insert(&jsonFc);
+        }
+    } else if (type == strings::structure) {
+        const auto jsonMemberClasses = jsonFcObj[strings::memberClasses];
+
+        if (!jsonMemberClasses) {
+            return;
+        }
+
+        for (auto& jsonMemberCls : jsonMemberClasses->asArray()) {
+            jsonFcsWithRole(*jsonMemberCls->asObj()[strings::fc], roleNames,
+                            withMetadataStreamUuidRole, set);
+        }
+    } else if (type == strings::staticLenArray || type == strings::dynLenArray) {
+        jsonFcsWithRole(*jsonFcObj[strings::elemFc], roleNames, withMetadataStreamUuidRole, set);
+    } else if (type == strings::optional) {
+        jsonFcsWithRole(*jsonFcObj[strings::fc], roleNames, withMetadataStreamUuidRole, set);
+    } else if (type == strings::variant) {
+        auto& jsonOpts = jsonFcObj[strings::opts]->asArray();
+
+        for (auto& jsonOpt : jsonOpts) {
+            jsonFcsWithRole(*jsonOpt->asObj()[strings::fc], roleNames, withMetadataStreamUuidRole,
+                            set);
+        }
+    }
+}
+
+} /* namespace */
+
+std::unordered_set<const bt2c::JsonVal *>
+jsonFcsWithRole(const bt2c::JsonVal& jsonFc, const std::unordered_set<std::string>& roleNames,
+                const bool withMetadataStreamUuidRole)
+{
+    std::unordered_set<const bt2c::JsonVal *> set;
+
+    jsonFcsWithRole(jsonFc, roleNames, withMetadataStreamUuidRole, set);
+    return set;
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/json-fcs-with-role.hpp b/src/plugins/ctf/common/src/metadata/json/json-fcs-with-role.hpp
new file mode 100644 (file)
index 0000000..be31b52
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef _CTF_SRC_METADATA_JSON_JSON_FCS_WITH_ROLE_HPP
+#define _CTF_SRC_METADATA_JSON_JSON_FCS_WITH_ROLE_HPP
+
+#include <unordered_set>
+#include <string>
+
+#include "cpp-common/json-val.hpp"
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Returns a set of all the JSON field class values having at least one
+ * role amongst `roleNames` and/or the "metadata stream UUID" role if
+ * `withMetadataStreamUuidRole` is true for a JSON static-length BLOB
+ * field class.
+ */
+std::unordered_set<const bt2_common::JsonVal *>
+jsonFcsWithRole(const bt2_common::JsonVal& jsonFc, const std::unordered_set<std::string>& roleNames,
+                bool withMetadataStreamUuidRole);
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* _CTF_SRC_METADATA_JSON_JSON_FCS_WITH_ROLE_HPP */
diff --git a/src/plugins/ctf/common/src/metadata/json/scope-fc-from-json-val.cpp b/src/plugins/ctf/common/src/metadata/json/scope-fc-from-json-val.cpp
new file mode 100644 (file)
index 0000000..b5ff1f7
--- /dev/null
@@ -0,0 +1,1287 @@
+/*
+ * Copyright (c) 2022 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define BT_LOG_TAG  "PLUGIN/CTF/CTF-2-META-STREAM-PARSER"
+#define BT_CLOG_CFG _mLogCfg
+#include "cpp-common/cfg-logging-error-reporting.hpp"
+
+#include <set>
+
+#include "common/assert.h"
+#include "scope-fc-from-json-val.hpp"
+#include "../../../metadata/json/strings.hpp"
+#include "utils.hpp"
+#include "val-req.hpp"
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+namespace bt2c = bt2_common;
+namespace strings = json_strings;
+
+namespace {
+
+/*
+ * Helper containing context to implement scopeFcFromJsonVal().
+ */
+class ScopeFcFromJsonVal final
+{
+public:
+    explicit ScopeFcFromJsonVal(const bt2c::JsonObjVal& jsonFc,
+                                const bt2c::JsonObjVal * const jsonTraceCls,
+                                const bt2c::JsonObjVal * const jsonDataStreamCls,
+                                const bt2c::JsonObjVal * const jsonEventRecordCls,
+                                const bt2c::LogCfg& logCfg) :
+        _mJsonTraceCls {jsonTraceCls},
+        _mJsonDataStreamCls {jsonDataStreamCls},
+        _mJsonEventRecordCls {jsonEventRecordCls}, _mLogCfg {logCfg}
+    {
+        /*
+         * Immediately convert the JSON field class value to a CTF IR
+         * field class.
+         */
+        _mFc = this->_fcFromJsonVal(jsonFc);
+    }
+
+    Fc::UP releaseFc() noexcept
+    {
+        return std::move(_mFc);
+    }
+
+private:
+    /* Set of JSON field class values */
+    using _JsonFcSet = std::set<const bt2c::JsonVal *>;
+
+    /* JSON dependency (field class) value type */
+    enum class _JsonDepType
+    {
+        BOOL,
+        UINT,
+        SINT,
+    };
+
+    /*
+     * Creates and returns a field class from the JSON field class value
+     * `jsonFc`.
+     */
+    Fc::UP _fcFromJsonVal(const bt2c::JsonVal& jsonFc)
+    {
+        auto& jsonFcObj = jsonFc.asObj();
+
+        /* Type */
+        auto& type = jsonFcObj.rawStrVal(strings::type);
+
+        /* User attributes */
+        auto userAttrs = userAttrsOfObj(jsonFcObj);
+
+        /* Defer to specific method */
+        if (type == strings::fixedLenBitArray || type == strings::fixedLenBool ||
+            type == strings::fixedLenUInt || type == strings::fixedLenSInt ||
+            type == strings::fixedLenUEnum || type == strings::fixedLenSEnum ||
+            type == strings::fixedLenFloat) {
+            return this->_fcFromJsonFixedLenBitArrayFc(jsonFcObj, type, std::move(userAttrs));
+        } else if (type == strings::varLenUInt || type == strings::varLenSInt ||
+                   type == strings::varLenUEnum || type == strings::varLenSEnum) {
+            return this->_fcFromJsonVarLenIntFc(jsonFcObj, type, std::move(userAttrs));
+        } else if (type == strings::nullTerminatedStr) {
+            return createNullTerminatedStrFc(std::move(userAttrs));
+        } else if (type == strings::staticLenStr || type == strings::dynLenStr) {
+            return this->_fcFromJsonNonNullTerminatedStrFc(jsonFcObj, type, std::move(userAttrs));
+        } else if (type == strings::staticLenBlob || type == strings::dynLenBlob) {
+            return this->_fcFromJsonBlobFc(jsonFcObj, type, std::move(userAttrs));
+        } else if (type == strings::staticLenArray || type == strings::dynLenArray) {
+            return this->_fcFromJsonArrayFc(jsonFcObj, type, std::move(userAttrs));
+        } else if (type == strings::structure) {
+            return this->_fcFromJsonStructFc(jsonFcObj, std::move(userAttrs));
+        } else if (type == strings::optional) {
+            return this->_fcFromJsonOptionalFc(jsonFcObj, std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::variant);
+            return this->_fcFromJsonVariantFc(jsonFcObj, std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Creates and returns the set of unsigned integer field roles of
+     * the JSON unsigned integer field class value `jsonFc`.
+     */
+    static ir::UIntFieldRoles _uIntFieldRolesOfJsonUIntFc(const bt2c::JsonObjVal& jsonFc)
+    {
+        ir::UIntFieldRoles roles;
+        const auto jsonRoles = jsonFc[strings::roles];
+
+        if (!jsonRoles) {
+            /* No roles */
+            return roles;
+        }
+
+        for (auto& jsonRole : jsonRoles->asArray()) {
+            auto& roleName = *jsonRole->asStr();
+
+            if (roleName == strings::dataStreamClsId) {
+                roles.insert(ir::UIntFieldRole::DATA_STREAM_CLS_ID);
+            } else if (roleName == strings::dataStreamId) {
+                roles.insert(ir::UIntFieldRole::DATA_STREAM_ID);
+            } else if (roleName == strings::pktMagicNumber) {
+                roles.insert(ir::UIntFieldRole::PKT_MAGIC_NUMBER);
+            } else if (roleName == strings::defClkTs) {
+                roles.insert(ir::UIntFieldRole::DEF_CLK_TS);
+            } else if (roleName == strings::discEventRecordCounterSnap) {
+                roles.insert(ir::UIntFieldRole::DISC_EVENT_RECORD_COUNTER_SNAP);
+            } else if (roleName == strings::pktContentLen) {
+                roles.insert(ir::UIntFieldRole::PKT_CONTENT_LEN);
+            } else if (roleName == strings::pktTotalLen) {
+                roles.insert(ir::UIntFieldRole::PKT_TOTAL_LEN);
+            } else if (roleName == strings::pktEndDefClkTs) {
+                roles.insert(ir::UIntFieldRole::PKT_END_DEF_CLK_TS);
+            } else if (roleName == strings::pktSeqNum) {
+                roles.insert(ir::UIntFieldRole::PKT_SEQ_NUM);
+            } else {
+                BT_ASSERT(roleName == strings::eventRecordClsId);
+                roles.insert(ir::UIntFieldRole::EVENT_RECORD_CLS_ID);
+            }
+        }
+
+        return roles;
+    }
+
+    /*
+     * Creates and returns an integer range set from the JSON integer
+     * range set value `jsonIntRangeSet`.
+     */
+    template <typename ValT>
+    static IntRangeSet<ValT>
+    _intRangeSetFromJsonIntRangeSet(const bt2c::JsonArrayVal& jsonIntRangeSet)
+    {
+        typename IntRangeSet<ValT>::Set ranges;
+
+        for (auto& jsonRange : jsonIntRangeSet) {
+            auto& jsonRangeArray = jsonRange->asArray();
+
+            ranges.insert(typename IntRangeSet<ValT>::Range {
+                rawIntValFromJsonIntVal<ValT>(jsonRangeArray[0]),
+                rawIntValFromJsonIntVal<ValT>(jsonRangeArray[1])});
+        }
+
+        return IntRangeSet<ValT> {std::move(ranges)};
+    }
+
+    /*
+     * Creates and returns the enumeration field class mappings of the
+     * JSON enumeration field class value `jsonFc`.
+     */
+    template <typename EnumFcT>
+    static typename EnumFcT::Mappings _enumFcMappingsOfJsonEnumFc(const bt2c::JsonObjVal& jsonFc)
+    {
+        typename EnumFcT::Mappings mappings;
+        const auto jsonMappings = jsonFc[strings::mappings];
+
+        for (auto& keyJsonIntRangesPair : jsonMappings->asObj()) {
+            mappings.insert(std::make_pair(keyJsonIntRangesPair.first,
+                                           _intRangeSetFromJsonIntRangeSet<typename EnumFcT::Val>(
+                                               keyJsonIntRangesPair.second->asArray())));
+        }
+
+        return mappings;
+    }
+
+    /*
+     * Creates and returns a fixed-length unsigned enumeration field
+     * class from the JSON fixed-length unsigned enumeration field class
+     * value `jsonFc` and the other parameters.
+     */
+    static Fc::UP _fcFromJsonFixedLenUEnumFc(const bt2c::JsonObjVal& jsonFc,
+                                             const unsigned int align, const bt2c::DataLen len,
+                                             const ir::ByteOrder byteOrder,
+                                             const ir::DispBase prefDispBase,
+                                             ir::UIntFieldRoles&& roles,
+                                             ir::OptUserAttrs&& userAttrs)
+    {
+        /* Mappings */
+        auto mappings = _enumFcMappingsOfJsonEnumFc<FixedLenUEnumFc>(jsonFc);
+
+        /* Create field class */
+        return createFixedLenUEnumFc(align, len, byteOrder, std::move(mappings), prefDispBase,
+                                     std::move(roles), std::move(userAttrs));
+    }
+
+    /*
+     * Creates and returns a fixed-length unsigned integer field class
+     * from the JSON fixed-length unsigned integer field class value
+     * `jsonFc` and the other parameters.
+     */
+    static Fc::UP _fcFromJsonFixedLenUIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                                            const unsigned int align, const bt2c::DataLen len,
+                                            const ir::ByteOrder byteOrder,
+                                            const ir::DispBase prefDispBase,
+                                            ir::OptUserAttrs&& userAttrs)
+    {
+        /* Roles */
+        auto roles = _uIntFieldRolesOfJsonUIntFc(jsonFc);
+
+        /* Create field class */
+        if (type == strings::fixedLenUInt) {
+            return createFixedLenUIntFc(align, len, byteOrder, prefDispBase, std::move(roles),
+                                        std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::fixedLenUEnum);
+            return _fcFromJsonFixedLenUEnumFc(jsonFc, align, len, byteOrder, prefDispBase,
+                                              std::move(roles), std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Creates and returns a fixed-length signed enumeration field class
+     * from the JSON fixed-length signed enumeration field class value
+     * `jsonFc` and the other parameters.
+     */
+    static Fc::UP _fcFromJsonFixedLenSEnumFc(const bt2c::JsonObjVal& jsonFc,
+                                             const unsigned int align, const bt2c::DataLen len,
+                                             const ir::ByteOrder byteOrder,
+                                             const ir::DispBase prefDispBase,
+                                             ir::OptUserAttrs&& userAttrs)
+    {
+        /* Mappings */
+        auto mappings = _enumFcMappingsOfJsonEnumFc<FixedLenSEnumFc>(jsonFc);
+
+        /* Create field class */
+        return createFixedLenSEnumFc(align, len, byteOrder, std::move(mappings), prefDispBase,
+                                     std::move(userAttrs));
+    }
+
+    /*
+     * Creates and returns a fixed-length signed integer field class
+     * from the JSON fixed-length signed integer field class value
+     * `jsonFc` and the other parameters.
+     */
+    static Fc::UP _fcFromJsonFixedLenSIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                                            const unsigned int align, const bt2c::DataLen len,
+                                            const ir::ByteOrder byteOrder,
+                                            const ir::DispBase prefDispBase,
+                                            ir::OptUserAttrs&& userAttrs)
+    {
+        if (type == strings::fixedLenSInt) {
+            return createFixedLenSIntFc(align, len, byteOrder, prefDispBase, std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::fixedLenSEnum);
+            return _fcFromJsonFixedLenSEnumFc(jsonFc, align, len, byteOrder, prefDispBase,
+                                              std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Returns the preferred display base of the JSON integer field
+     * class value `jsonFc`.
+     */
+    static ir::DispBase _prefDispBaseOfJsonIntFc(const bt2c::JsonObjVal& jsonFc) noexcept
+    {
+        return static_cast<ir::DispBase>(jsonFc.rawVal(strings::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.
+     */
+    static Fc::UP _fcFromJsonFixedLenIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                                           const unsigned int align, const bt2c::DataLen len,
+                                           const ir::ByteOrder byteOrder,
+                                           ir::OptUserAttrs&& userAttrs)
+    {
+        /* Preferred display base */
+        const auto prefDispBase = _prefDispBaseOfJsonIntFc(jsonFc);
+
+        /* Create field class */
+        if (type == strings::fixedLenUInt || type == strings::fixedLenUEnum) {
+            return _fcFromJsonFixedLenUIntFc(jsonFc, type, align, len, byteOrder, prefDispBase,
+                                             std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::fixedLenSInt || type == strings::fixedLenSEnum);
+            return _fcFromJsonFixedLenSIntFc(jsonFc, type, align, len, byteOrder, prefDispBase,
+                                             std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Returns the length of the JSON field class value `jsonFc`.
+     */
+    static unsigned long long _lenOfJsonFc(const bt2c::JsonObjVal& jsonFc) noexcept
+    {
+        return jsonFc.rawUIntVal(strings::len);
+    }
+
+    /*
+     * 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.
+     */
+    static Fc::UP _fcFromJsonFixedLenBitArrayFc(const bt2c::JsonObjVal& jsonFc,
+                                                const std::string& type,
+                                                ir::OptUserAttrs&& userAttrs)
+    {
+        /* Alignment */
+        const auto align = jsonFc.rawVal(strings::align, 1ULL);
+
+        /* Length */
+        const auto len = bt2c::DataLen::fromBits(_lenOfJsonFc(jsonFc));
+
+        /* Byte order */
+        const auto byteOrder = jsonFc.rawStrVal(strings::byteOrder) == strings::littleEndian ?
+                                   ir::ByteOrder::LITTLE :
+                                   ir::ByteOrder::BIG;
+
+        /* Create field class */
+        if (type == strings::fixedLenBitArray) {
+            return createFixedLenBitArrayFc(align, len, byteOrder, std::move(userAttrs));
+        } else if (type == strings::fixedLenBool) {
+            return createFixedLenBoolFc(align, len, byteOrder, std::move(userAttrs));
+        } else if (type == strings::fixedLenUInt || type == strings::fixedLenSInt ||
+                   type == strings::fixedLenUEnum || type == strings::fixedLenSEnum) {
+            return _fcFromJsonFixedLenIntFc(jsonFc, type, align, len, byteOrder,
+                                            std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::fixedLenFloat);
+            return createFixedLenFloatFc(align, len, byteOrder, std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Creates and returns a variable-length unsigned enumeration field
+     * class from the JSON variable-length unsigned enumeration field
+     * class value `jsonFc` and the other parameters.
+     */
+    static Fc::UP _fcFromJsonVarLenUEnumFc(const bt2c::JsonObjVal& jsonFc,
+                                           const ir::DispBase prefDispBase,
+                                           ir::UIntFieldRoles&& roles, ir::OptUserAttrs&& userAttrs)
+    {
+        /* Mappings */
+        auto mappings = _enumFcMappingsOfJsonEnumFc<VarLenUEnumFc>(jsonFc);
+
+        /* Create field class */
+        return createVarLenUEnumFc(std::move(mappings), prefDispBase, std::move(roles),
+                                   std::move(userAttrs));
+    }
+
+    /*
+     * Creates and returns a variable-length unsigned integer field
+     * class from the JSON variable-length unsigned integer field class
+     * value `jsonFc` and the other parameters.
+     */
+    static Fc::UP _fcFromJsonVarLenUIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                                          const ir::DispBase prefDispBase,
+                                          ir::OptUserAttrs&& userAttrs)
+    {
+        /* Roles */
+        auto roles = _uIntFieldRolesOfJsonUIntFc(jsonFc);
+
+        /* Create field class */
+        if (type == strings::varLenUInt) {
+            return createVarLenUIntFc(prefDispBase, std::move(roles), std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::varLenUEnum);
+            return _fcFromJsonVarLenUEnumFc(jsonFc, prefDispBase, std::move(roles),
+                                            std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Creates and returns a variable-length signed enumeration field
+     * class from the JSON variable-length signed enumeration field
+     * class value `jsonFc` and the other parameters.
+     */
+    static Fc::UP _fcFromJsonVarLenSEnumFc(const bt2c::JsonObjVal& jsonFc,
+                                           const ir::DispBase prefDispBase,
+                                           ir::OptUserAttrs&& userAttrs)
+    {
+        /* Mappings */
+        auto mappings = _enumFcMappingsOfJsonEnumFc<VarLenSEnumFc>(jsonFc);
+
+        /* Create field class */
+        return createVarLenSEnumFc(std::move(mappings), prefDispBase, std::move(userAttrs));
+    }
+
+    /*
+     * Creates and returns a variable-length signed integer field class
+     * from the JSON variable-length signed integer field class value
+     * `jsonFc` and the other parameters.
+     */
+    static Fc::UP _fcFromJsonVarLenSIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                                          const ir::DispBase prefDispBase,
+                                          ir::OptUserAttrs&& userAttrs)
+    {
+        if (type == strings::fixedLenSInt) {
+            return createVarLenSIntFc(prefDispBase, std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::varLenSEnum);
+            return _fcFromJsonVarLenSEnumFc(jsonFc, prefDispBase, std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Creates and returns a variable-length integer field class from
+     * the JSON variable-length integer field class value `jsonFc` and
+     * the other parameters.
+     */
+    static Fc::UP _fcFromJsonVarLenIntFc(const bt2c::JsonObjVal& jsonFc, const std::string& type,
+                                         ir::OptUserAttrs&& userAttrs)
+    {
+        /* Preferred display base */
+        const auto prefDispBase = _prefDispBaseOfJsonIntFc(jsonFc);
+
+        if (type == strings::varLenUInt || type == strings::varLenUEnum) {
+            return _fcFromJsonVarLenUIntFc(jsonFc, type, prefDispBase, std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::varLenSInt || type == strings::varLenSEnum);
+            return _fcFromJsonVarLenSIntFc(jsonFc, type, prefDispBase, std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Creates and returns the field location of the JSON field class
+     * value `jsonFc` from its `key` property.
+     */
+    static std::pair<FieldLoc, bt2c::TextLoc> _fieldLocOfJsonFc(const bt2c::JsonObjVal& jsonFc,
+                                                                const std::string& key)
+    {
+        auto& jsonLoc = jsonFc[key]->asArray();
+
+        /* Scope */
+        const auto scope = [&jsonLoc] {
+            auto& scopeName = *(*jsonLoc.begin())->asStr();
+
+            if (scopeName == strings::pktHeader) {
+                return ir::FieldLocScope::PKT_HEADER;
+            } else if (scopeName == strings::pktCtx) {
+                return ir::FieldLocScope::PKT_CTX;
+            } else if (scopeName == strings::eventRecordHeader) {
+                return ir::FieldLocScope::EVENT_RECORD_HEADER;
+            } else if (scopeName == strings::eventRecordCommonCtx) {
+                return ir::FieldLocScope::EVENT_RECORD_COMMON_CTX;
+            } else if (scopeName == strings::eventRecordSpecCtx) {
+                return ir::FieldLocScope::EVENT_RECORD_SPEC_CTX;
+            } else {
+                BT_ASSERT(scopeName == strings::eventRecordPayload);
+                return ir::FieldLocScope::EVENT_RECORD_PAYLOAD;
+            }
+        }();
+
+        /* Items */
+        FieldLoc::Items items;
+
+        std::transform(jsonLoc.begin() + 1, jsonLoc.end(), std::back_inserter(items),
+                       [](const bt2c::JsonVal::UP& jsonItem) {
+                           return *jsonItem->asStr();
+                       });
+
+        /* Create field location */
+        return {createFieldLoc(scope, std::move(items)), jsonLoc.loc()};
+    }
+
+    /*
+     * Creates and returns a dynamic-length string field class from the
+     * JSON dynamic-length string field class value `jsonFc` and from
+     * `userAttrs`.
+     */
+    Fc::UP _fcFromJsonDynLenStrFc(const bt2c::JsonObjVal& jsonFc, ir::OptUserAttrs&& userAttrs)
+    {
+        /* Length field location */
+        auto fieldLocTextLocPair = _fieldLocOfJsonFc(jsonFc, strings::lenFieldLoc);
+
+        try {
+            this->_validateLenJsonDeps(jsonFc, fieldLocTextLocPair.first,
+                                       fieldLocTextLocPair.second);
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid dynamic-length string field class.",
+                                              textLocStr(jsonFc).c_str());
+        }
+
+        /* Create field class */
+        return createDynLenStrFc(std::move(fieldLocTextLocPair.first), std::move(userAttrs));
+    }
+
+    /*
+     * Creates and returns a non-null-terminated string field class from
+     * the JSON non-null-terminated string field class value `jsonFc`
+     * and the other parameters.
+     */
+    Fc::UP _fcFromJsonNonNullTerminatedStrFc(const bt2c::JsonObjVal& jsonFc,
+                                             const std::string& type, ir::OptUserAttrs&& userAttrs)
+    {
+        if (type == strings::staticLenStr) {
+            return createStaticLenStrFc(_lenOfJsonFc(jsonFc), std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::dynLenStr);
+            return this->_fcFromJsonDynLenStrFc(jsonFc, std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Creates and returns a static-length BLOB field class from the
+     * JSON static-length BLOB field class value `jsonFc` and the other
+     * parameters.
+     */
+    static Fc::UP _fcFromJsonStaticLenBlobFc(const bt2c::JsonObjVal& jsonFc,
+                                             const char * const mediaType,
+                                             ir::OptUserAttrs&& userAttrs)
+    {
+        /* Has metadata stream UUID role? */
+        const auto jsonRoles = jsonFc[strings::roles];
+        const auto hasMetadataStreamUuidRole = jsonRoles && jsonRoles->asArray().size() > 0;
+
+        /* Create field class */
+        return createStaticLenBlobFc(_lenOfJsonFc(jsonFc), mediaType, hasMetadataStreamUuidRole,
+                                     std::move(userAttrs));
+    }
+
+    /*
+     * Creates and returns a dynamic-length BLOB field class from the
+     * JSON dynamic-length BLOB field class value `jsonFc` and the other
+     * parameters.
+     */
+    Fc::UP _fcFromJsonDynLenBlobFc(const bt2c::JsonObjVal& jsonFc, const char * const mediaType,
+                                   ir::OptUserAttrs&& userAttrs)
+    {
+        /* Length field location */
+        auto fieldLocTextLocPair = _fieldLocOfJsonFc(jsonFc, strings::lenFieldLoc);
+
+        try {
+            this->_validateLenJsonDeps(jsonFc, fieldLocTextLocPair.first,
+                                       fieldLocTextLocPair.second);
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid dynamic-length BLOB field class.",
+                                              textLocStr(jsonFc).c_str());
+        }
+
+        /* Create field class */
+        return createDynLenBlobFc(std::move(fieldLocTextLocPair.first), mediaType,
+                                  std::move(userAttrs));
+    }
+
+    /*
+     * 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,
+                             ir::OptUserAttrs&& userAttrs)
+    {
+        /* Media type */
+        const auto mediaType = jsonFc.rawVal(strings::mediaType, BlobFc::defaultMediaType);
+
+        /* Create field class */
+        if (type == strings::staticLenBlob) {
+            return this->_fcFromJsonStaticLenBlobFc(jsonFc, mediaType, std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::dynLenBlob);
+            return this->_fcFromJsonDynLenBlobFc(jsonFc, mediaType, std::move(userAttrs));
+        }
+    }
+
+    /*
+     * Creates and returns a dynamic-length array field class from the
+     * JSON dynamic-length array field class value `jsonFc` and the
+     * other parameters.
+     */
+    Fc::UP _fcFromJsonDynLenArrayFc(const bt2c::JsonObjVal& jsonFc, Fc::UP elemFc,
+                                    const unsigned int minAlign, ir::OptUserAttrs&& userAttrs)
+    {
+        /* Length field location */
+        auto fieldLocTextLocPair = _fieldLocOfJsonFc(jsonFc, strings::lenFieldLoc);
+
+        try {
+            this->_validateLenJsonDeps(jsonFc, fieldLocTextLocPair.first,
+                                       fieldLocTextLocPair.second);
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid dynamic-length array field class.",
+                                              textLocStr(jsonFc).c_str());
+        }
+
+        /* Create field class */
+        return createDynLenArrayFc(std::move(fieldLocTextLocPair.first), std::move(elemFc),
+                                   minAlign, std::move(userAttrs));
+    }
+
+    /*
+     * Returns the minimum alignment of the JSON field class value
+     * `jsonFc`.
+     */
+    static unsigned long long _minAlignOfJsonFc(const bt2c::JsonObjVal& jsonFc) noexcept
+    {
+        return jsonFc.rawVal(strings::minAlign, 1ULL);
+    }
+
+    /*
+     * In this order:
+     *
+     * 1. Marks the underlying field class at the index `index` of the
+     *    JSON compound field class `jsonFc` as being currently
+     *    visited.
+     *
+     * 2. Calls `func()`.
+     *
+     * 3. Cancels 1.
+     */
+    template <typename FuncT>
+    void _withinCompoundFc(const bt2c::JsonObjVal& jsonFc, const std::size_t index, FuncT&& func)
+    {
+        BT_ASSERT(_mCompoundFcIndexes.find(&jsonFc) == _mCompoundFcIndexes.end());
+        _mCompoundFcIndexes.emplace(std::make_pair(&jsonFc, index));
+        func();
+        _mCompoundFcIndexes.erase(&jsonFc);
+    }
+
+    /*
+     * In this order:
+     *
+     * 1. Marks the JSON compound field class `jsonFc` as being
+     *    currently visited.
+     *
+     * 2. Calls `func()`.
+     *
+     * 3. Cancels 1.
+     */
+    template <typename FuncT>
+    void _withinCompoundFc(const bt2c::JsonObjVal& jsonFc, FuncT&& func)
+    {
+        this->_withinCompoundFc(jsonFc, 0, std::forward<FuncT>(func));
+    }
+
+    /*
+     * Creates and returns am 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,
+                              ir::OptUserAttrs&& userAttrs)
+    {
+        /* Element field class */
+        Fc::UP elemFc;
+
+        try {
+            this->_withinCompoundFc(jsonFc, [&jsonFc, &elemFc, this] {
+                elemFc = this->_fcFromJsonVal(*jsonFc[strings::elemFc]);
+            });
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid array field class.",
+                                              textLocStr(jsonFc).c_str());
+        }
+
+        /* Minimum alignment */
+        const auto minAlign = this->_minAlignOfJsonFc(jsonFc);
+
+        /* Create field class */
+        if (type == strings::staticLenArray) {
+            return createStaticLenArrayFc(_lenOfJsonFc(jsonFc), std::move(elemFc), minAlign,
+                                          std::move(userAttrs));
+        } else {
+            BT_ASSERT(type == strings::dynLenArray);
+            return this->_fcFromJsonDynLenArrayFc(jsonFc, std::move(elemFc), minAlign,
+                                                  std::move(userAttrs));
+        }
+    }
+
+    /*
+     * 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, ir::OptUserAttrs&& userAttrs)
+    {
+        /* Minimum alignment */
+        const auto minAlign = this->_minAlignOfJsonFc(jsonFc);
+
+        /* Member classes */
+        StructFc::MemberClasses memberClasses;
+        const auto jsonMemberClasses = jsonFc[strings::memberClasses];
+
+        try {
+            if (jsonMemberClasses) {
+                for (auto& jsonMemberCls : jsonMemberClasses->asArray()) {
+                    auto& jsonMemberClsObj = jsonMemberCls->asObj();
+                    auto& name = jsonMemberClsObj.rawStrVal(strings::name);
+
+                    try {
+                        memberClasses.emplace_back(createStructFieldMemberCls(
+                            name, this->_fcFromJsonVal(*jsonMemberClsObj[strings::fc]),
+                            userAttrsOfObj(jsonMemberClsObj)));
+                    } catch (const bt2c::Error&) {
+                        BT_CLOGE_APPEND_CAUSE_AND_RETHROW(
+                            "[%s] Invalid structure field member class `%s`.",
+                            textLocStr(*jsonMemberCls).c_str(), name.c_str());
+                    }
+                }
+            }
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid structure field class.",
+                                              textLocStr(jsonFc).c_str());
+        }
+
+        /* Create field class */
+        return createStructFc(std::move(memberClasses), minAlign, std::move(userAttrs));
+    }
+
+    /*
+     * Creates and returns an optional field class from the JSON
+     * optional (with an integer selector) field class value `jsonFc`
+     * and the other parameters.
+     */
+    template <typename OptionalFcT, typename IntRangeSetValReqT>
+    Fc::UP _optionalWithIntSelFcFromJsonOptionalFc(const bt2c::JsonObjVal& jsonFc,
+                                                   const char * const signednessWord,
+                                                   FieldLoc&& fieldLoc, Fc::UP fc,
+                                                   ir::OptUserAttrs&& userAttrs)
+    {
+        auto& jsonSelFieldRanges = *jsonFc[strings::selFieldRanges];
+
+        try {
+            IntRangeSetValReqT {_mLogCfg, bt2c::TextLocStrFmt::OFFSET}.validate(jsonSelFieldRanges);
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW(
+                "[%s] Invalid selector field ranges: expecting %s integer ranges.",
+                textLocStr(jsonSelFieldRanges).c_str(), signednessWord);
+        }
+
+        return createOptionalFc(std::move(fc), std::move(fieldLoc),
+                                this->_intRangeSetFromJsonIntRangeSet<typename OptionalFcT::SelVal>(
+                                    jsonSelFieldRanges.asArray()),
+                                std::move(userAttrs));
+    }
+
+    /*
+     * 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, ir::OptUserAttrs&& userAttrs)
+    {
+        try {
+            /* Selector field location */
+            auto fieldLocTextLocPair = _fieldLocOfJsonFc(jsonFc, strings::selFieldLoc);
+
+            /* Optional field class */
+            Fc::UP fc;
+
+            this->_withinCompoundFc(jsonFc, [&jsonFc, &fc, this] {
+                fc = this->_fcFromJsonVal(*jsonFc[strings::fc]);
+            });
+
+            /* Get dependencies */
+            const auto jsonDeps =
+                this->_findJsonDeps(jsonFc, fieldLocTextLocPair.first, fieldLocTextLocPair.second);
+            const auto jsonDepType = this->_jsonDepType(**jsonDeps.begin());
+
+            if (jsonDepType == _JsonDepType::BOOL) {
+                return createOptionalFc(std::move(fc), std::move(fieldLocTextLocPair.first),
+                                        std::move(userAttrs));
+            } else {
+                if (jsonDepType == _JsonDepType::UINT) {
+                    return this->_optionalWithIntSelFcFromJsonOptionalFc<
+                        OptionalWithUIntSelFc, Ctf2JsonUIntRangeSetValReq>(
+                        jsonFc, "unsigned", std::move(fieldLocTextLocPair.first), std::move(fc),
+                        std::move(userAttrs));
+                } else {
+                    BT_ASSERT(jsonDepType == _JsonDepType::SINT);
+                    return this->_optionalWithIntSelFcFromJsonOptionalFc<
+                        OptionalWithSIntSelFc, Ctf2JsonSIntRangeSetValReq>(
+                        jsonFc, "signed", std::move(fieldLocTextLocPair.first), std::move(fc),
+                        std::move(userAttrs));
+                }
+            }
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid optional field class.",
+                                              textLocStr(jsonFc).c_str());
+        }
+    }
+
+    /*
+     * Creates and returns a variant field class from the JSON variant
+     * field class value `jsonFc` and from the other parameters (generic
+     * version).
+     */
+    template <typename VariantFcT, typename IntRangeSetValReqT>
+    Fc::UP _variantFcFromJsonVariantFc(const bt2c::JsonObjVal& jsonFc,
+                                       const char * const signednessWord, FieldLoc&& fieldLoc,
+                                       ir::OptUserAttrs&& userAttrs)
+    {
+        auto& jsonOpts = jsonFc[strings::opts]->asArray();
+        typename VariantFcT::Opts opts;
+
+        for (auto it = jsonOpts.begin(); it != jsonOpts.end(); ++it) {
+            auto& jsonOpt = (*it)->asObj();
+            auto& jsonSelFieldRanges = *jsonOpt[strings::selFieldRanges];
+
+            /* Validate selector field ranges */
+            try {
+                IntRangeSetValReqT {_mLogCfg, bt2c::TextLocStrFmt::OFFSET}.validate(
+                    jsonSelFieldRanges);
+            } catch (const bt2c::Error&) {
+                BT_CLOGE_APPEND_CAUSE_AND_RETHROW(
+                    "[%s] Invalid selector field ranges: expecting %s integer ranges.",
+                    textLocStr(jsonSelFieldRanges).c_str(), signednessWord);
+            }
+
+            Fc::UP optFc;
+
+            /* Create field class of option */
+            this->_withinCompoundFc(jsonFc, it - jsonOpts.begin(), [this, &jsonOpt, &optFc] {
+                optFc = this->_fcFromJsonVal(*jsonOpt[strings::fc]);
+            });
+
+            /* Create option */
+            auto opt = createVariantFcOpt(
+                std::move(optFc),
+                this->_intRangeSetFromJsonIntRangeSet<typename VariantFcT::SelVal>(
+                    jsonSelFieldRanges.asArray()),
+                optStrOfObj(jsonOpt, strings::name), userAttrsOfObj(jsonOpt));
+
+            /* Append option */
+            opts.emplace_back(std::move(opt));
+        }
+
+        return createVariantFc(std::move(opts), std::move(fieldLoc), std::move(userAttrs));
+    }
+
+    /*
+     * 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, ir::OptUserAttrs&& userAttrs)
+    {
+        try {
+            /* Selector field location */
+            auto fieldLocTextLocPair = _fieldLocOfJsonFc(jsonFc, strings::selFieldLoc);
+
+            const auto jsonDeps =
+                this->_findJsonDeps(jsonFc, fieldLocTextLocPair.first, fieldLocTextLocPair.second);
+            const auto jsonDepType = this->_jsonDepType(**jsonDeps.begin());
+
+            if (jsonDepType == _JsonDepType::UINT) {
+                return this
+                    ->_variantFcFromJsonVariantFc<VariantWithUIntSelFc, Ctf2JsonUIntRangeSetValReq>(
+                        jsonFc, "unsigned", std::move(fieldLocTextLocPair.first),
+                        std::move(userAttrs));
+            } else if (jsonDepType == _JsonDepType::SINT) {
+                return this
+                    ->_variantFcFromJsonVariantFc<VariantWithSIntSelFc, Ctf2JsonSIntRangeSetValReq>(
+                        jsonFc, "signed", std::move(fieldLocTextLocPair.first),
+                        std::move(userAttrs));
+            } else {
+                BT_ASSERT(jsonDepType == _JsonDepType::BOOL);
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error,
+                    "[%s] Selector field location %s locates one or more boolean field classes: "
+                    "expecting unsigned or signed integer field classes.",
+                    textLocStr(fieldLocTextLocPair.second).c_str(),
+                    this->_fieldLocStr(fieldLocTextLocPair.first, fieldLocTextLocPair.first.end())
+                        .c_str());
+            }
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid variant field class.",
+                                              textLocStr(jsonFc).c_str());
+        }
+    }
+
+    /*
+     * Returns a string representation of `fieldLoc`, considering all
+     * its path items until `end` (excluded).
+     */
+    static std::string _fieldLocStr(const FieldLoc& fieldLoc,
+                                    const FieldLoc::Items::const_iterator end)
+    {
+        std::ostringstream ss;
+
+        ss << '[' << scopeStr(fieldLoc.scope());
+
+        for (auto it = fieldLoc.begin(); it != end; ++it) {
+            ss << ", `" << *it << '`';
+        }
+
+        ss << ']';
+        return ss.str();
+    }
+
+    /*
+     * Adds to `jsonDeps` the dependencies of `jsonDependentFc`, using
+     * the field location `fieldLoc`, from `jsonBaseFc` and the field
+     * location item iterator `fieldLocIt`.
+     *
+     * Returns `true` if `jsonDependentFc` isn't reached yet (safe to
+     * continue to find dependencies).
+     */
+    bool _findJsonDeps(const bt2c::JsonObjVal& jsonBaseFc, const bt2c::JsonObjVal& jsonDependentFc,
+                       const FieldLoc& fieldLoc, const FieldLoc::Items::const_iterator fieldLocIt,
+                       _JsonFcSet& jsonDeps) const
+    {
+        /* Type */
+        auto& type = jsonBaseFc.rawStrVal(strings::type);
+
+        if (type == strings::fixedLenBool || type == strings::fixedLenUInt ||
+            type == strings::fixedLenUEnum || type == strings::fixedLenSInt ||
+            type == strings::fixedLenSEnum || type == strings::varLenUInt ||
+            type == strings::varLenUEnum || type == strings::varLenSInt ||
+            type == strings::varLenSEnum) {
+            if (fieldLocIt != fieldLoc.end()) {
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, "[%s] Cannot reach anything beyond a scalar field class for %s.",
+                    textLocStr(jsonBaseFc).c_str(),
+                    this->_fieldLocStr(fieldLoc, fieldLocIt + 1).c_str());
+            }
+
+            jsonDeps.insert(&jsonBaseFc);
+            return true;
+        } else if (type == strings::structure) {
+            if (fieldLocIt == fieldLoc.end()) {
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                    bt2c::Error, "[%s] Field location must not locate a structure field class.",
+                    textLocStr(jsonBaseFc).c_str());
+            }
+
+            /* Find the member class named `*fieldLocIt` */
+            const auto jsonMemberClasses = jsonBaseFc[strings::memberClasses];
+
+            if (jsonMemberClasses) {
+                for (auto& jsonMemberCls : jsonMemberClasses->asArray()) {
+                    auto& jsonMemberClsObj = jsonMemberCls->asObj();
+                    const auto jsonMemberClsFc = jsonMemberClsObj[strings::fc];
+
+                    if (jsonMemberClsFc == &jsonDependentFc) {
+                        /* Reached the dependent field class */
+                        return false;
+                    }
+
+                    if (jsonMemberClsObj.rawStrVal(strings::name) != *fieldLocIt) {
+                        continue;
+                    }
+
+                    return this->_findJsonDeps(jsonMemberClsObj[strings::fc]->asObj(),
+                                               jsonDependentFc, fieldLoc, fieldLocIt + 1, jsonDeps);
+                }
+            }
+
+            /* Member class not found */
+            BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                            "[%s] At field location %s: "
+                                            "no structure field member class named `%s`.",
+                                            textLocStr(jsonBaseFc).c_str(),
+                                            this->_fieldLocStr(fieldLoc, fieldLocIt).c_str(),
+                                            fieldLocIt->c_str());
+        } else if (type == strings::staticLenArray || type == strings::dynLenArray) {
+            if (_mCompoundFcIndexes.find(&jsonBaseFc) == _mCompoundFcIndexes.end()) {
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                                "[%s] At field location %s: "
+                                                "unreachable array field element.",
+                                                textLocStr(jsonBaseFc).c_str(),
+                                                this->_fieldLocStr(fieldLoc, fieldLocIt).c_str());
+            }
+
+            const auto jsonElemFc = jsonBaseFc[strings::elemFc];
+
+            if (jsonElemFc == &jsonDependentFc) {
+                /* Reached the dependent field class */
+                return false;
+            }
+
+            return this->_findJsonDeps(jsonElemFc->asObj(), jsonDependentFc, fieldLoc, fieldLocIt,
+                                       jsonDeps);
+        } else if (type == strings::optional) {
+            if (_mCompoundFcIndexes.find(&jsonBaseFc) == _mCompoundFcIndexes.end()) {
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                                "[%s] At field location %s: "
+                                                "unreachable optional field.",
+                                                textLocStr(jsonBaseFc).c_str(),
+                                                this->_fieldLocStr(fieldLoc, fieldLocIt).c_str());
+            }
+
+            const auto jsonOptionalFc = jsonBaseFc[strings::fc];
+
+            if (jsonOptionalFc == &jsonDependentFc) {
+                /* Reached the dependent field class */
+                return false;
+            }
+
+            return this->_findJsonDeps(jsonOptionalFc->asObj(), jsonDependentFc, fieldLoc,
+                                       fieldLocIt, jsonDeps);
+        } else if (type == strings::variant) {
+            auto& jsonOpts = jsonBaseFc[strings::opts]->asArray();
+            const auto curOptIndexIt = _mCompoundFcIndexes.find(&jsonBaseFc);
+
+            if (curOptIndexIt == _mCompoundFcIndexes.end()) {
+                /*
+                 * Not currently visiting this JSON variant field class
+                 * value: consider all options.
+                 */
+                for (auto& jsonOpt : jsonOpts) {
+                    auto& jsonOptObj = jsonOpt->asObj();
+                    const auto jsonOptFc = jsonOptObj[strings::fc];
+
+                    if (jsonOptFc == &jsonDependentFc) {
+                        /* Reached the dependent field class */
+                        return false;
+                    }
+
+                    if (!this->_findJsonDeps(jsonOptFc->asObj(), jsonDependentFc, fieldLoc,
+                                             fieldLocIt, jsonDeps)) {
+                        /* Reached the dependent field class */
+                        return false;
+                    }
+                }
+            } else {
+                /*
+                 * Currently visiting this JSON variant field class
+                 * value: consider only the currently visited option.
+                 */
+                const auto jsonOptFc = jsonOpts[curOptIndexIt->second].asObj()[strings::fc];
+
+                if (jsonOptFc == &jsonDependentFc) {
+                    /* Reached the dependent field class */
+                    return false;
+                }
+
+                return this->_findJsonDeps(jsonOptFc->asObj(), jsonDependentFc, fieldLoc,
+                                           fieldLocIt, jsonDeps);
+            }
+
+            return true;
+        } else {
+            BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                            "[%s] At field location %s: "
+                                            "unexpected field class with type `%s`.",
+                                            textLocStr(jsonBaseFc).c_str(),
+                                            this->_fieldLocStr(fieldLoc, fieldLocIt).c_str(),
+                                            type.c_str());
+        }
+    }
+
+    static _JsonDepType _jsonDepType(const bt2c::JsonVal& jsonFc)
+    {
+        auto& type = jsonFc.asObj().rawStrVal(strings::type);
+
+        if (type == strings::fixedLenBool) {
+            return _JsonDepType::BOOL;
+        } else if (type == strings::fixedLenUInt || type == strings::fixedLenUEnum ||
+                   type == strings::varLenUInt || type == strings::varLenUEnum) {
+            return _JsonDepType::UINT;
+        } else {
+            BT_ASSERT(type == strings::fixedLenSInt || type == strings::fixedLenSEnum ||
+                      type == strings::varLenSInt || type == strings::varLenSEnum);
+            return _JsonDepType::SINT;
+        }
+    };
+
+    const bt2c::JsonObjVal& _jsonScopeFc(const FieldLoc& fieldLoc,
+                                         const bt2c::TextLoc& fieldLocLoc) const
+    {
+        const auto jsonScopeFc = [this, &fieldLoc, &fieldLocLoc] {
+            switch (fieldLoc.scope()) {
+            case ir::FieldLocScope::PKT_HEADER:
+                if (!_mJsonTraceCls) {
+                    BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                                    "[%s] Missing trace class fragment.",
+                                                    textLocStr(fieldLocLoc).c_str());
+                }
+
+                return (*_mJsonTraceCls)[strings::pktHeaderFc];
+            case ir::FieldLocScope::PKT_CTX:
+            case ir::FieldLocScope::EVENT_RECORD_HEADER:
+            case ir::FieldLocScope::EVENT_RECORD_COMMON_CTX:
+                if (!_mJsonDataStreamCls) {
+                    BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                                    "[%s] Missing data stream class fragment.",
+                                                    textLocStr(fieldLocLoc).c_str());
+                }
+
+                switch (fieldLoc.scope()) {
+                case ir::FieldLocScope::PKT_CTX:
+                    return (*_mJsonDataStreamCls)[strings::pktCtxFc];
+                case ir::FieldLocScope::EVENT_RECORD_HEADER:
+                    return (*_mJsonDataStreamCls)[strings::eventRecordHeaderFc];
+                case ir::FieldLocScope::EVENT_RECORD_COMMON_CTX:
+                    return (*_mJsonDataStreamCls)[strings::eventRecordCommonCtxFc];
+                default:
+                    bt_common_abort();
+                }
+            case ir::FieldLocScope::EVENT_RECORD_SPEC_CTX:
+            case ir::FieldLocScope::EVENT_RECORD_PAYLOAD:
+                if (!_mJsonEventRecordCls) {
+                    BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                                    "[%s] Missing event record class fragment.",
+                                                    textLocStr(fieldLocLoc).c_str());
+                }
+
+                if (fieldLoc.scope() == ir::FieldLocScope::EVENT_RECORD_SPEC_CTX) {
+                    return (*_mJsonEventRecordCls)[strings::specCtxFc];
+                } else {
+                    BT_ASSERT(fieldLoc.scope() == ir::FieldLocScope::EVENT_RECORD_PAYLOAD);
+                    return (*_mJsonEventRecordCls)[strings::payloadFc];
+                }
+            default:
+                bt_common_abort();
+            }
+        }();
+
+        if (!jsonScopeFc) {
+            BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error, "[%s] Missing scope field class.",
+                                            textLocStr(fieldLocLoc).c_str());
+        }
+
+        return jsonScopeFc->asObj();
+    }
+
+    /*
+     * Finds the dependencies of `jsonDependentFc` using the field
+     * location `fieldLoc`.
+     *
+     * This method only considers JSON boolean and integer field class
+     * values as dependencies, throwing `bt2c::TextParseError` when it
+     * finds anything else.
+     *
+     * This method doesn't add to the returned set JSON field class
+     * values which occur after `jsonDependentFc` .
+     *
+     * This method also throws if:
+     *
+     * • `fieldLoc` is invalid.
+     * • `fieldLoc` locates JSON field class values.
+     * • `fieldLoc` doesn't locate any JSON field class value.
+     */
+    _JsonFcSet _findJsonDeps(const bt2c::JsonObjVal& jsonDependentFc, const FieldLoc& fieldLoc,
+                             const bt2c::TextLoc& fieldLocLoc) const
+    {
+        try {
+            /* Find dependencies and returns them */
+            _JsonFcSet jsonDeps;
+
+            this->_findJsonDeps(this->_jsonScopeFc(fieldLoc, fieldLocLoc), jsonDependentFc,
+                                fieldLoc, fieldLoc.begin(), jsonDeps);
+
+            /* Validate that `jsonDeps` contains at least one item */
+            if (jsonDeps.empty()) {
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                                "[%s] Field location doesn't locate anything.",
+                                                textLocStr(fieldLocLoc).c_str());
+            }
+
+            /*
+             * Validate that all the items of `jsonDeps` have the same
+             * type.
+             */
+            {
+                const auto expectedType = this->_jsonDepType(**jsonDeps.begin());
+
+                for (const auto jsonFc : jsonDeps) {
+                    if (this->_jsonDepType(*jsonFc) != expectedType) {
+                        BT_CLOGE_APPEND_CAUSE_AND_THROW(
+                            bt2c::Error,
+                            "[%s] Field location locates field classes having different types.",
+                            textLocStr(fieldLocLoc).c_str());
+                    }
+                }
+            }
+
+            /* Return the set */
+            return jsonDeps;
+        } catch (const bt2c::Error&) {
+            BT_CLOGE_APPEND_CAUSE_AND_RETHROW("[%s] Invalid field location %s.",
+                                              textLocStr(fieldLocLoc).c_str(),
+                                              this->_fieldLocStr(fieldLoc, fieldLoc.end()).c_str());
+        }
+    }
+
+    /*
+     * Validates the dependencies of the JSON dynamic-length field class
+     * value `jsonDependentFc` as located by `fieldLoc`.
+     */
+    void _validateLenJsonDeps(const bt2c::JsonObjVal& jsonDependentFc, const FieldLoc& fieldLoc,
+                              const bt2c::TextLoc& fieldLocLoc) const
+    {
+        const auto jsonDeps = this->_findJsonDeps(jsonDependentFc, fieldLoc, fieldLocLoc);
+
+        BT_ASSERT(!jsonDeps.empty());
+
+        if (this->_jsonDepType(**jsonDeps.begin()) != _JsonDepType::UINT) {
+            try {
+                BT_CLOGE_APPEND_CAUSE_AND_THROW(bt2c::Error,
+                                                "[%s] Expecting an unsigned integer field class.",
+                                                textLocStr(**jsonDeps.begin()).c_str());
+            } catch (const bt2c::Error&) {
+                BT_CLOGE_APPEND_CAUSE_AND_RETHROW(
+                    "[%s] Invalid field location %s.", textLocStr(fieldLocLoc).c_str(),
+                    this->_fieldLocStr(fieldLoc, fieldLoc.end()).c_str());
+            }
+        }
+    }
+
+    /* Current JSON trace class value, or `nullptr` if none */
+    const bt2c::JsonObjVal *_mJsonTraceCls;
+
+    /* Current JSON data stream class value, or `nullptr` if none */
+    const bt2c::JsonObjVal *_mJsonDataStreamCls;
+
+    /* Current JSON event record class value, or `nullptr` if none */
+    const bt2c::JsonObjVal *_mJsonEventRecordCls;
+
+    /*
+     * Map of JSON compound field class value to the index of the
+     * currently visited immediate underlying field class, that is:
+     *
+     * For a JSON variant field class value V:
+     *     Index of the option of V containing the field class currently
+     *     being visited.
+     *
+     * For a JSON array field class value V:
+     * For a JSON optional field class value V:
+     *     0: if V is part of the map, then its element/optional field
+     *     class is currently being visited.
+     *
+     * This is used to provide a visiting context to _findJsonDeps() so
+     * as to follow the correct JSON variant field class option value as
+     * well as to validate dependencies.
+     *
+     * 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 _findJsonDeps() is currently visiting [9] to find its
+     * dependencies, then the map would contain:
+     *
+     *     [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 bt2c::JsonVal *, std::size_t> _mCompoundFcIndexes;
+
+    /* Resulting field class */
+    Fc::UP _mFc;
+
+    /* Logging configuration */
+    bt2_common::LogCfg _mLogCfg;
+};
+
+} /* namespace */
+
+Fc::UP scopeFcFromJsonVal(const bt2c::JsonObjVal& jsonFc,
+                          const bt2c::JsonObjVal * const jsonTraceCls,
+                          const bt2c::JsonObjVal * const jsonDataStreamCls,
+                          const bt2c::JsonObjVal * const jsonEventRecordCls,
+                          const bt2c::LogCfg& logCfg)
+{
+    ScopeFcFromJsonVal visitor {jsonFc, jsonTraceCls, jsonDataStreamCls, jsonEventRecordCls,
+                                logCfg};
+
+    return visitor.releaseFc();
+}
+
+} /* namespace src */
+} /* namespace ctf */
diff --git a/src/plugins/ctf/common/src/metadata/json/scope-fc-from-json-val.hpp b/src/plugins/ctf/common/src/metadata/json/scope-fc-from-json-val.hpp
new file mode 100644 (file)
index 0000000..5fa8396
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2022 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef _CTF_SRC_METADATA_JSON_SCOPE_FC_FROM_JSON_VAL_HPP
+#define _CTF_SRC_METADATA_JSON_SCOPE_FC_FROM_JSON_VAL_HPP
+
+#include "cpp-common/json-val.hpp"
+#include "cpp-common/log-cfg.hpp"
+#include "../ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+Fc::UP scopeFcFromJsonVal(const bt2_common::JsonObjVal& jsonFc,
+                          const bt2_common::JsonObjVal *jsonTraceCls,
+                          const bt2_common::JsonObjVal *jsonDataStreamCls,
+                          const bt2_common::JsonObjVal *jsonEventRecordCls,
+                          const bt2_common::LogCfg& logCfg);
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* _CTF_SRC_METADATA_JSON_SCOPE_FC_FROM_JSON_VAL_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..3cbbbda
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <array>
+
+#include "cpp-common/bt2-value-from-json-val.hpp"
+#include "utils.hpp"
+#include "../../../metadata/json/strings.hpp"
+
+namespace ctf {
+namespace src {
+
+namespace bt2c = bt2_common;
+
+nonstd::optional<bt2c::Uuid> uuidOfObj(const bt2c::JsonObjVal& jsonObjVal)
+{
+    const auto jsonUuidVal = jsonObjVal[json_strings::uuid];
+
+    if (!jsonUuidVal) {
+        return nonstd::nullopt;
+    }
+
+    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()};
+}
+
+nonstd::optional<bt2::MapValue::Shared> bt2ValueOfObj(const bt2c::JsonObjVal& jsonObjVal,
+                                                      const std::string& key)
+{
+    const auto jsonUserAttrsVal = jsonObjVal[key];
+
+    if (!jsonUserAttrsVal) {
+        return nonstd::nullopt;
+    }
+
+    return bt2c::bt2ValueFromJsonVal(*jsonUserAttrsVal)->asMap().shared();
+}
+
+} /* 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..dd073a8
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2022 Philippe Proulx <pproulx@efficios.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef _CTF_SRC_METADATA_JSON_UTILS_HPP
+#define _CTF_SRC_METADATA_JSON_UTILS_HPP
+
+#include <string>
+#include <sstream>
+
+#include "common/common.h"
+#include "cpp-common/json-val.hpp"
+#include "cpp-common/optional.hpp"
+#include "cpp-common/uuid.hpp"
+#include "cpp-common/bt2/value.hpp"
+#include "cpp-common/text-loc.hpp"
+#include "cpp-common/text-loc-str.hpp"
+#include "../../../metadata/json/strings.hpp"
+#include "../../metadata/ctf-ir.hpp"
+
+namespace ctf {
+namespace src {
+
+/*
+ * Returns the UUID of the JSON object value `jsonObjVal`, or
+ * `nonstd::nullopt` if there's no such property.
+ */
+nonstd::optional<bt2_common::Uuid> uuidOfObj(const bt2_common::JsonObjVal& jsonObjVal);
+
+/*
+ * Returns the object of the JSON object value `jsonObjVal` having the
+ * key `key` as a libbabeltrace2 value object, or `nonstd::nullopt` if
+ * there's no such key.
+ */
+nonstd::optional<bt2::MapValue::Shared> bt2ValueOfObj(const bt2_common::JsonObjVal& jsonObjVal,
+                                                      const std::string& key);
+
+/*
+ * Returns the user attributes of the JSON object value `jsonObjVal`, or
+ * `nonstd::nullopt` if there's no such property.
+ */
+static inline nonstd::optional<bt2::MapValue::Shared>
+userAttrsOfObj(const bt2_common::JsonObjVal& jsonObjVal)
+{
+    return bt2ValueOfObj(jsonObjVal, json_strings::userAttrs);
+}
+
+/*
+ * Returns the raw integer value from the JSON unsigned or signed
+ * integer value `jsonIntVal`, casted as `ValT`.
+ */
+template <typename ValT>
+ValT rawIntValFromJsonIntVal(const bt2_common::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`.
+ */
+static inline nonstd::optional<std::string> optStrOfObj(const bt2_common::JsonObjVal& jsonObjVal,
+                                                        const char * const propName)
+{
+    const auto jsonVal = jsonObjVal[propName];
+
+    if (jsonVal) {
+        return *jsonVal->asStr();
+    }
+
+    return nonstd::nullopt;
+}
+
+static inline const char *scopeStr(const ir::FieldLocScope scope) noexcept
+{
+    switch (scope) {
+    case ir::FieldLocScope::PKT_HEADER:
+        return "packet header";
+    case ir::FieldLocScope::PKT_CTX:
+        return "packet context";
+    case ir::FieldLocScope::EVENT_RECORD_HEADER:
+        return "event record header";
+    case ir::FieldLocScope::EVENT_RECORD_COMMON_CTX:
+        return "common event record context";
+    case ir::FieldLocScope::EVENT_RECORD_SPEC_CTX:
+        return "specific event record context";
+    case ir::FieldLocScope::EVENT_RECORD_PAYLOAD:
+        return "event record payload";
+    default:
+        bt_common_abort();
+    }
+}
+
+static inline std::string textLocStr(const bt2_common::TextLoc& loc)
+{
+    return bt2_common::textLocStr(loc, bt2_common::TextLocStrFmt::OFFSET);
+}
+
+static inline std::string textLocStr(const bt2_common::JsonVal& val)
+{
+    return textLocStr(val.loc());
+}
+
+} /* namespace src */
+} /* namespace ctf */
+
+#endif /* _CTF_SRC_METADATA_JSON_UTILS_HPP */
This page took 0.051642 seconds and 5 git commands to generate.