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