2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
46#include <core/DynamicLoader.h>
47#include <iot/mqtt/Topic.h>
49#ifndef DOXYGEN_SHOULD_SKIP_THIS
51#include "nlohmann/json-schema.hpp"
57#pragma GCC diagnostic push
59#if __has_warning
("-Wcovered-switch-default")
60#pragma GCC diagnostic ignored "-Wcovered-switch-default"
62#if __has_warning
("-Wnrvo")
63#pragma GCC diagnostic ignored "-Wnrvo"
65#if __has_warning
("-Wsuggest-override")
66#pragma GCC diagnostic ignored "-Wsuggest-override"
68#if __has_warning
("-Wmissing-noreturn")
69#pragma GCC diagnostic ignored "-Wmissing-noreturn"
71#if __has_warning
("-Wdeprecated-copy-with-user-provided-dtor")
72#pragma GCC diagnostic ignored "-Wdeprecated-copy-with-user-provided-dtor"
78#pragma GCC diagnostic pop
82#include <log/Logger.h>
84#include <nlohmann/json.hpp>
94#include "mapping-schema.json.h"
107 for (
void* pluginHandle : pluginHandles) {
108 core::DynamicLoader::dlClose(pluginHandle);
119 for (
void* handle : pluginHandles) {
120 core::DynamicLoader::dlClose(handle);
129 }
catch (
const std::exception& e) {
130 throw std::runtime_error(
"Validating JSON failed: Mapping JSON = " + mappingJson.dump(4) +
"\n" + e.what());
136 if (mappingJson.empty()) {
141 }
catch (
const std::exception& e) {
142 throw std::runtime_error(
"Patching JSON with default patch failed: Default patch = " + defaultPatch.dump(4) +
"\n" + e.what());
145 bool mustReconnect =
this->mappingJson[
"connection"] != oldMappingJson[
"connection"];
147 if (mappingJson[
"mapping"].contains(
"plugins")) {
148 VLOG(1) <<
"Loading plugins ...";
149 for (
const nlohmann::json& pluginJson : mappingJson[
"mapping"][
"plugins"]) {
150 const std::string plugin = pluginJson;
152 void* handle = core::DynamicLoader::dlOpen(plugin);
154 if (handle !=
nullptr) {
155 pluginHandles.push_back(handle);
157 VLOG(1) <<
" Loading plugin: " << plugin <<
" ...";
159 const std::vector<mqtt::lib::Function>* loadedFunctions =
160 static_cast<std::vector<mqtt::lib::Function>*>(core::DynamicLoader::dlSym(handle,
"functions"));
161 if (loadedFunctions !=
nullptr) {
162 VLOG(1) <<
" Registering inja 'none void callbacks'";
163 for (
const mqtt::lib::Function& function : *loadedFunctions) {
164 VLOG(1) <<
" " << function.name;
166 if (function.numArgs >= 0) {
167 injaEnvironment->add_callback(function.name, function.numArgs, function.function);
169 injaEnvironment->add_callback(function.name, function.function);
172 VLOG(1) <<
" Registering inja 'none void callbacks done'";
174 VLOG(1) <<
" No inja none 'void callbacks found' in plugin " << plugin;
177 const std::vector<mqtt::lib::VoidFunction>* loadedVoidFunctions =
178 static_cast<std::vector<mqtt::lib::VoidFunction>*>(core::DynamicLoader::dlSym(handle,
"voidFunctions"));
179 if (loadedVoidFunctions !=
nullptr) {
180 VLOG(1) <<
" Registering inja 'void callbacks'";
181 for (
const mqtt::lib::VoidFunction& voidFunction : *loadedVoidFunctions) {
182 VLOG(1) <<
" " << voidFunction.name;
184 if (voidFunction.numArgs >= 0) {
185 injaEnvironment->add_void_callback(voidFunction.name, voidFunction.numArgs, voidFunction.function);
187 injaEnvironment->add_void_callback(voidFunction.name, voidFunction.function);
190 VLOG(1) <<
" Registering inja 'void callbacks' done";
192 VLOG(1) <<
" No inja 'void callbacks' found in plugin " << plugin;
195 VLOG(1) <<
" Loading plugin done: " << plugin;
197 VLOG(1) <<
" Error loading plugin: " << plugin;
198 throw std::runtime_error(
"Error loading plugin '" + plugin +
"': " + core::DynamicLoader::dlError());
202 VLOG(1) <<
"Loading plugins done";
205 return mustReconnect;
213 return mappingJson[
"connection"][
"client_id"];
217 return mappingJson[
"connection"][
"keep_alive"];
221 const nlohmann::json& connectionJson = mappingJson[
"connection"];
223 return std::make_tuple(connectionJson[
"clean_session"],
224 connectionJson[
"will_topic"],
225 connectionJson[
"will_message"],
226 connectionJson[
"will_qos"],
227 connectionJson[
"will_retain"],
228 connectionJson[
"username"],
229 connectionJson[
"password"]);
233 std::list<iot::mqtt::Topic> topicList;
235 extractSubscriptions(mappingJson[
"mapping"],
"", topicList);
242 if (mappingJson.contains(
"mapping") && !mappingJson[
"mapping"].empty()) {
245 if (!matchingTopicLevel.empty()) {
246 const nlohmann::json& subscription = matchingTopicLevel[
"subscription"];
248 if (subscription.contains(
"static")) {
249 VLOG(1) <<
"Topic mapping found for:";
250 VLOG(1) <<
" Type: static";
251 VLOG(1) <<
" Topic: " << publish.getTopic();
252 VLOG(1) <<
" Message: " << publish.getMessage();
253 VLOG(1) <<
" QoS: " <<
static_cast<uint16_t>(publish.getQoS());
254 VLOG(1) <<
" Retain: " << publish.getRetain();
259 if (subscription.contains(
"value")) {
260 VLOG(1) <<
"Topic mapping found for:";
261 VLOG(1) <<
" Type: value";
262 VLOG(1) <<
" Topic: " << publish.getTopic();
263 VLOG(1) <<
" Message: " << publish.getMessage();
264 VLOG(1) <<
" QoS: " <<
static_cast<uint16_t>(publish.getQoS());
265 VLOG(1) <<
" Retain: " << publish.getRetain();
268 json[
"message"] = publish.getMessage();
273 if (subscription.contains(
"json")) {
274 VLOG(1) <<
"Topic mapping found for:";
275 VLOG(1) <<
" Type: json";
276 VLOG(1) <<
" Topic: " << publish.getTopic();
277 VLOG(1) <<
" Message: " << publish.getMessage();
278 VLOG(1) <<
" QoS: " <<
static_cast<uint16_t>(publish.getQoS());
279 VLOG(1) <<
" Retain: " << publish.getRetain();
283 json[
"message"] = nlohmann::json::parse(publish.getMessage());
286 }
catch (
const nlohmann::json::parse_error& e) {
287 VLOG(1) <<
" Parsing message into json failed: " << publish.getMessage();
288 VLOG(1) <<
" What: " << e.what() <<
'\n'
289 <<
" Exception Id: " << e.id <<
'\n'
290 <<
" Byte position of error: " << e.byte;
296 return mappedPublishes;
308 const std::string& topic,
309 std::list<iot::mqtt::Topic>& topicList) {
310 const std::string name = topicLevelJson[
"name"];
312 if (topicLevelJson.contains(
"subscription")) {
313 const uint8_t qoS = topicLevelJson[
"subscription"][
"qos"];
315 topicList.emplace_back(topic + ((topic.empty() || topic ==
"/") && !name.empty() ?
"" :
"/") + name, qoS);
318 if (topicLevelJson.contains(
"topic_level")) {
319 extractSubscriptions(topicLevelJson, topic + ((topic.empty() || topic ==
"/") && !name.empty() ?
"" :
"/") + name, topicList);
324 MqttMapper::extractSubscriptions(
const nlohmann::json& mappingJson,
const std::string& topic, std::list<iot::mqtt::Topic>& topicList) {
325 if (mappingJson.contains(
"topic_level")) {
326 const nlohmann::json& topicLevels = mappingJson[
"topic_level"];
328 if (topicLevels.is_object()) {
331 for (
const nlohmann::json& topicLevel : topicLevels) {
332 extractSubscription(topicLevel, topic, topicList);
341 if (topicLevel.is_object()) {
342 const std::string::size_type slashPosition = topic.find(
'/');
343 const std::string topicLevelName = topic.substr(0, slashPosition);
345 if (topicLevel[
"name"] == topicLevelName || topicLevel[
"name"] ==
"+" || topicLevel[
"name"] ==
"#") {
346 if (slashPosition == std::string::npos) {
347 foundTopicLevel = topicLevel;
348 }
else if (topicLevel.contains(
"topic_level")) {
352 }
else if (topicLevel.is_array()) {
353 for (
const nlohmann::json& topicLevelEntry : topicLevel) {
354 foundTopicLevel = findMatchingTopicLevel(topicLevelEntry, topic);
356 if (!foundTopicLevel.empty()) {
362 return foundTopicLevel;
367 const std::string& mappingTemplate = templateMapping[
"mapping_template"];
368 const std::string& mappedTopic = templateMapping[
"mapped_topic"];
373 json[
"mapped_topic"] = renderedTopic;
375 VLOG(1) <<
" Mapped topic template: " << mappedTopic;
376 VLOG(1) <<
" -> " << renderedTopic;
381 VLOG(1) <<
" Mapped message template: " << mappingTemplate;
382 VLOG(1) <<
" -> " << renderedMessage;
384 const nlohmann::json& suppressions = templateMapping[
"suppressions"];
385 const bool retain = templateMapping[
"retain"];
387 if (suppressions.empty() || std::find(suppressions.begin(), suppressions.end(), renderedMessage) == suppressions.end() ||
388 (retain && renderedMessage.empty())) {
389 const uint8_t qoS = templateMapping[
"qos"];
390 const double delay = templateMapping[
"delay"];
392 VLOG(1) <<
" Send mapping:" << (delay > 0 ?
" delayed" :
"");
393 VLOG(1) <<
" Topic: " << renderedTopic;
394 VLOG(1) <<
" Message: " << renderedMessage <<
"";
395 VLOG(1) <<
" QoS: " <<
static_cast<
int>(qoS);
396 VLOG(1) <<
" retain: " << retain;
397 VLOG(1) <<
" Delay: " << delay;
401 VLOG(1) <<
" Rendered message: '" << renderedMessage <<
"' in suppression list:";
402 for (
const nlohmann::json& item : suppressions) {
403 VLOG(1) <<
" '" << item.get<std::string>() <<
"'";
405 VLOG(1) <<
" Send mapping: suppressed";
408 VLOG(1) <<
" Message template rendering failed: " << mappingTemplate <<
" : " << json.dump();
409 VLOG(1) <<
" What: " << e.what();
414 VLOG(1) <<
" Topic template rendering failed: " << mappingTemplate <<
" : " << json.dump();
415 VLOG(1) <<
" What: " << e.what();
425 json[
"topic"] = publish.getTopic();
426 json[
"qos"] = publish.getQoS();
427 json[
"retain"] = publish.getRetain();
428 json[
"package_identifier"] = publish.getPacketIdentifier();
431 VLOG(1) <<
" Render data: " << json.dump();
433 if (templateMapping.is_object()) {
436 for (
const nlohmann::json& concreteTemplateMapping : templateMapping) {
437 getMappedTemplate(concreteTemplateMapping, json, mappedPublishes);
440 }
catch (
const nlohmann::json::exception& e) {
441 VLOG(1) <<
"JSON Exception during Render data:\n" << e.what();
448 if (staticMapping.is_object()) {
450 }
else if (staticMapping.is_array()) {
451 for (
const nlohmann::json& concreteStaticMapping : staticMapping) {
452 getMappedMessage(concreteStaticMapping, publish, mappedPublishes);
458 const std::string& topic,
const std::string& message, uint8_t qoS,
bool retain,
double delay,
MappedPublishes& mappedPublishes) {
459 VLOG(1) <<
" Mapped topic:";
460 VLOG(1) <<
" -> " << topic;
461 VLOG(1) <<
" Mapped message:";
462 VLOG(1) <<
" -> " << message;
463 VLOG(1) <<
" Send mapping:" << (delay > 0 ?
" delayed" :
"");
464 VLOG(1) <<
" Topic: " << topic;
465 VLOG(1) <<
" Message: " << message;
466 VLOG(1) <<
" QoS: " <<
static_cast<
int>(qoS);
467 VLOG(1) <<
" retain: " << retain;
468 VLOG(1) <<
" Delay: " << delay;
471 std::get<0>(mappedPublishes).emplace_back(0, topic, message, qoS,
false, retain);
473 std::get<1>(mappedPublishes).push_back({delay, iot::
mqtt::
packets::Publish(0, topic, message, qoS,
false, retain)});
480 const nlohmann::json& messageMapping = staticMapping[
"message_mapping"];
482 VLOG(1) <<
" Message mapping: " << messageMapping.dump();
484 if (messageMapping.is_object()) {
485 if (messageMapping[
"message"] == publish.getMessage()) {
486 getMappedMessage(staticMapping[
"mapped_topic"],
487 messageMapping[
"mapped_message"],
488 staticMapping[
"qos"],
489 staticMapping[
"retain"],
490 staticMapping[
"delay"],
493 VLOG(1) <<
" no matching mapped message found";
496 const nlohmann::json::const_iterator matchedMessageMappingIterator =
497 std::find_if(messageMapping.begin(), messageMapping.end(), [&publish](
const nlohmann::json& messageMappingCandidat) {
498 return messageMappingCandidat[
"message"] == publish.getMessage();
501 if (matchedMessageMappingIterator != messageMapping.end()) {
502 getMappedMessage(staticMapping[
"mapped_topic"],
503 (*matchedMessageMappingIterator)[
"mapped_message"],
504 staticMapping[
"qos"],
505 staticMapping[
"retain"],
506 staticMapping[
"delay"],
509 VLOG(1) <<
" no matching mapped message found";
Class for changing the configuration.
std::string render(std::string_view input, const json &data)
static void extractSubscription(const nlohmann::json &topicLevelJson, const std::string &topic, std::list< iot::mqtt::Topic > &topicList)
void getTemplateMappings(const nlohmann::json &templateMapping, nlohmann::json &json, const iot::mqtt::packets::Publish &publish, MappedPublishes &mappedPublishes) const
std::string getClientId() const
std::tuple< bool, std::string, std::string, uint8_t, bool, std::string, std::string > ConnectParameter
std::list< iot::mqtt::Topic > extractSubscriptions() const
MappedPublishes getMappings(const iot::mqtt::packets::Publish &publish)
nlohmann::json mappingJson
uint16_t getKeepAlive() const
bool setMapping(nlohmann::json mappingJson)
static const nlohmann::json validate(const nlohmann::json &json)
ConnectParameter getConnectPayload() const
static const nlohmann::json_schema::json_validator validator
std::list< void * > pluginHandles
static const nlohmann::json validate(const nlohmann::json &json, nlohmann::json_schema::basic_error_handler &err)
nlohmann::json mappingJsonUnpatched
static const std::string mappingJsonSchemaString
static void getMappedMessage(const std::string &topic, const std::string &message, uint8_t qoS, bool retain, double delay, MappedPublishes &mappedPublishes)
const nlohmann::json & getMapping() const
void getMappedTemplate(const nlohmann::json &templateMapping, nlohmann::json &json, MappedPublishes &mappedPublishes) const
std::tuple< std::vector< iot::mqtt::packets::Publish >, std::vector< ScheduledPublish > > MappedPublishes
inja::Environment * injaEnvironment
static void getStaticMappings(const nlohmann::json &staticMapping, const iot::mqtt::packets::Publish &publish, MappedPublishes &mappedPublishes)
static const std::string & getSchema()
static void getMappedMessage(const nlohmann::json &staticMapping, const iot::mqtt::packets::Publish &publish, MappedPublishes &mappedPublishes)
nlohmann::json findMatchingTopicLevel(const nlohmann::json &topicLevel, const std::string &topic) const
json validate(const json &, error_handler &, const json_uri &initial_uri=json_uri("#")) const
json validate(const json &) const
void default_string_format_check(const std::string &format, const std::string &value)
const SourceLocation location
const std::string message