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