From f9c1d77e96f3edd0b150c68f96526b36356c8c6a Mon Sep 17 00:00:00 2001 From: "Evil.2000" Date: Tue, 7 Jan 2025 20:49:03 +0100 Subject: [PATCH 1/2] Added support for prefixing the topic when connecting in bridge mode. --- .clang-format | 72 ++---------------------- .gitignore | 6 ++ bridgeconfig.cpp | 19 +++---- bridgeconfig.h | 1 + client.cpp | 8 +++ configfileparser.cpp | 8 +++ mqttpacket.cpp | 127 ++++++++++++++++++++++++++++++++++++++++++- mqttpacket.h | 3 + types.h | 1 + 9 files changed, 165 insertions(+), 80 deletions(-) diff --git a/.clang-format b/.clang-format index 0ba7d384..fa3dcfb6 100644 --- a/.clang-format +++ b/.clang-format @@ -2,32 +2,7 @@ Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -4 -AlignAfterOpenBracket: BlockIndent -AlignArrayOfStructures: None -AlignConsecutiveAssignments: - Enabled: false - AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false - PadOperators: true -AlignConsecutiveBitFields: - Enabled: false - AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false - PadOperators: false -AlignConsecutiveDeclarations: - Enabled: false - AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false - PadOperators: false -AlignConsecutiveMacros: - Enabled: false - AcrossEmptyLines: false - AcrossComments: false - AlignCompound: false - PadOperators: false +AlignAfterOpenBracket: Align AlignEscapedNewlines: Right AlignOperands: Align AlignTrailingComments: false @@ -68,7 +43,6 @@ BraceWrapping: SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None -BreakBeforeConceptDeclarations: Always #BreakBeforeBraces: Allman BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false @@ -80,7 +54,6 @@ BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 180 CommentPragmas: '^ IWYU pragma:' -QualifierAlignment: Leave CompactNamespaces: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 @@ -88,11 +61,8 @@ Cpp11BracedListStyle: true DeriveLineEnding: true DerivePointerAlignment: false DisableFormat: false -EmptyLineAfterAccessModifier: Never -EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false -PackConstructorInitializers: Never -BasedOnStyle: '' +BasedOnStyle: GNU ConstructorInitializerAllOnOneLineOrOnePerLine: false AllowAllConstructorInitializersOnNextLine: true FixNamespaceComments: true @@ -100,39 +70,30 @@ ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH -IfMacros: - - KJ_IF_MAYBE IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 SortPriority: 0 - CaseSensitive: false - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 SortPriority: 0 - CaseSensitive: false - Regex: '.*' Priority: 1 SortPriority: 0 - CaseSensitive: false IncludeIsMainRegex: '(Test)?$' IncludeIsMainSourceRegex: '' -IndentAccessModifiers: false IndentCaseLabels: false IndentCaseBlocks: false IndentGotoLabels: true IndentPPDirectives: None IndentExternBlock: AfterExternBlock -IndentRequiresClause: true IndentWidth: 4 IndentWrappedFunctionNames: false -InsertBraces: false InsertTrailingCommas: None JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: true -LambdaBodyIndentation: Signature MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 2 @@ -146,61 +107,36 @@ PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 -PenaltyBreakOpenParenthesis: 0 PenaltyBreakString: 1000 PenaltyBreakTemplateDeclaration: 10 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 -PenaltyIndentedWhitespace: 0 PointerAlignment: Right -PPIndentWidth: -1 -ReferenceAlignment: Pointer ReflowComments: false -RemoveBracesLLVM: false -RequiresClausePosition: OwnLine -SeparateDefinitionBlocks: Leave -ShortNamespaceLines: 1 -SortIncludes: Never +SortIncludes: false SortJavaStaticImport: Before SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true -SpaceBeforeCaseColon: false SpaceBeforeCpp11BracedList: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements -SpaceBeforeParensOptions: - AfterControlStatements: true - AfterForeachMacros: true - AfterFunctionDefinitionName: false - AfterFunctionDeclarationName: false - AfterIfMacros: true - AfterOverloadedOperator: false - AfterRequiresInClause: false - AfterRequiresInExpression: false - BeforeNonEmptyParentheses: false -SpaceAroundPointerQualifiers: Default SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyBlock: false SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 -SpacesInAngles: Never +SpacesInAngles: false SpacesInConditionalStatement: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false -SpacesInLineCommentPrefix: - Minimum: 1 - Maximum: -1 SpacesInParentheses: false SpacesInSquareBrackets: false SpaceBeforeSquareBrackets: false BitFieldColonSpacing: Both Standard: Latest -StatementAttributeLikeMacros: - - Q_EMIT StatementMacros: - Q_UNUSED - QT_REQUIRE_VERSION diff --git a/.gitignore b/.gitignore index 5c6bd1f8..b66e42cb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,9 @@ FlashMQBuild* *.swp compile_commands.json .clangd/ +/cmake-build-debug/ +/.idea/ +/cmake-build-debug-remote/ +/cmake-build-release/ +/cmake-build-release-remote/ +.* \ No newline at end of file diff --git a/bridgeconfig.cpp b/bridgeconfig.cpp index 765a35e9..7bd0dea3 100644 --- a/bridgeconfig.cpp +++ b/bridgeconfig.cpp @@ -182,17 +182,16 @@ void BridgeConfig::isValid() setClientId(); } -bool BridgeConfig::operator ==(const BridgeConfig &other) const +bool BridgeConfig::operator==(const BridgeConfig &other) const { - return this->address == other.address && this->port == other.port && this->inet_protocol == other.inet_protocol && this->tlsMode == other.tlsMode - && this->caFile == other.caFile && this->caDir == other.caDir && this->protocolVersion == other.protocolVersion - && this->bridgeProtocolBit == other.bridgeProtocolBit && this->keepalive == other.keepalive && this->clientidPrefix == other.clientidPrefix - && this->publishes == other.publishes && this->subscribes == other.subscribes && this->local_username == other.local_username - && this->remote_username == other.remote_username && this->remote_password == other.remote_password && this->remoteCleanStart == other.remoteCleanStart - && this->localCleanStart == other.localCleanStart && this->remoteSessionExpiryInterval == other.remoteSessionExpiryInterval - && this->localSessionExpiryInterval == other.localSessionExpiryInterval && this->remoteRetainAvailable == other.remoteRetainAvailable - && this->useSavedClientId == other.useSavedClientId && this->maxOutgoingTopicAliases == other.maxOutgoingTopicAliases - && this->maxIncomingTopicAliases == other.maxIncomingTopicAliases && this->tcpNoDelay == other.tcpNoDelay; + return this->address == other.address && this->port == other.port && this->inet_protocol == other.inet_protocol && this->tlsMode == other.tlsMode && + this->caFile == other.caFile && this->caDir == other.caDir && this->protocolVersion == other.protocolVersion && this->bridgeProtocolBit == other.bridgeProtocolBit && + this->keepalive == other.keepalive && this->clientidPrefix == other.clientidPrefix && this->topicPrefix == other.topicPrefix && this->publishes == other.publishes && + this->subscribes == other.subscribes && this->local_username == other.local_username && this->remote_username == other.remote_username && + this->remote_password == other.remote_password && this->remoteCleanStart == other.remoteCleanStart && this->localCleanStart == other.localCleanStart && + this->remoteSessionExpiryInterval == other.remoteSessionExpiryInterval && this->localSessionExpiryInterval == other.localSessionExpiryInterval && + this->remoteRetainAvailable == other.remoteRetainAvailable && this->useSavedClientId == other.useSavedClientId && + this->maxOutgoingTopicAliases == other.maxOutgoingTopicAliases && this->maxIncomingTopicAliases == other.maxIncomingTopicAliases && this->tcpNoDelay == other.tcpNoDelay; } bool BridgeConfig::operator !=(const BridgeConfig &other) const diff --git a/bridgeconfig.h b/bridgeconfig.h index dc7f715d..5cb02aa2 100644 --- a/bridgeconfig.h +++ b/bridgeconfig.h @@ -54,6 +54,7 @@ class BridgeConfig uint16_t maxOutgoingTopicAliases = 0; bool useSavedClientId = false; bool remoteRetainAvailable = true; + std::optional topicPrefix; std::vector subscribes; std::vector publishes; std::weak_ptr owner; diff --git a/client.cpp b/client.cpp index c965ea82..6b124f9c 100644 --- a/client.cpp +++ b/client.cpp @@ -366,6 +366,14 @@ PacketDropReason Client::writeMqttPacketAndBlameThisClient( MqttPacket *p = copyFactory.getOptimumPacket(max_qos, this->protocolVersion, topic_alias, skip_topic, subscriptionIdentifier); + // If client is a bridge, add the topic prefix, if set. + if (getBridgeState() && getBridgeState()->c.topicPrefix) + p->addTopicPrefix(getBridgeState()->c.topicPrefix.value()); + else + p->removeTopicPrefix(); + + logger->log(LOG_DEBUG) << "writeMqttPacketAndBlameThisClient(): " << p->getTopic(); + assert(static_cast(p->getQos()) == static_cast(max_qos)); assert(PublishCopyFactory::getPublishLayoutCompareKey(this->protocolVersion, p->getQos()) == PublishCopyFactory::getPublishLayoutCompareKey(p->getProtocolVersion(), p->getQos())); diff --git a/configfileparser.cpp b/configfileparser.cpp index b893b841..f2e38fcf 100644 --- a/configfileparser.cpp +++ b/configfileparser.cpp @@ -256,6 +256,7 @@ ConfigFileParser::ConfigFileParser(const std::string &path) : validBridgeKeys.insert("remote_retain_available"); validBridgeKeys.insert("local_clean_start"); validBridgeKeys.insert("local_session_expiry_interval"); + validBridgeKeys.insert("topic_prefix"); validBridgeKeys.insert("subscribe"); validBridgeKeys.insert("publish"); validBridgeKeys.insert("clientid_prefix"); @@ -582,6 +583,13 @@ void ConfigFileParser::loadFile(bool test) } curBridge->localSessionExpiryInterval = newVal; } + if (testKeyValidity(key, "topic_prefix", validBridgeKeys)) + { + if (!isValidUtf8(value) || !isValidSubscribePath(value)) + throw ConfigFileException(formatString("Path '%s' is not a valid subscribe match", value.c_str())); + + curBridge->topicPrefix = value; + } if (testKeyValidity(key, "subscribe", validBridgeKeys)) { if (!isValidUtf8(value) || !isValidSubscribePath(value)) diff --git a/mqttpacket.cpp b/mqttpacket.cpp index 0ab36bc4..7a7dd677 100644 --- a/mqttpacket.cpp +++ b/mqttpacket.cpp @@ -1261,10 +1261,14 @@ void MqttPacket::handleConnAck(std::shared_ptr &sender) { const uint8_t real_qos = std::min(data.max_qos, sub.qos); - logger->log(LOG_DEBUG) << "Bridge '" << sender->repr() << "' subscribing remotely to '" << sub.topic << "', QoS=" + std::string topic = bridgeState->c.topicPrefix.value_or("")+sub.topic; + + logger->log(LOG_DEBUG) << "Bridge '" << sender->repr() << "' subscribing remotely to '" << topic << "', QoS=" << static_cast(real_qos) << "."; - Subscribe s(this->getProtocolVersion(), session->getNextPacketIdLocked(), sub.topic, real_qos); + // TODO: Add prefix to sub.topic + + Subscribe s(this->getProtocolVersion(), session->getNextPacketIdLocked(), topic, real_qos); s.noLocal = true; s.retainAsPublished = true; MqttPacket subPacket(s); @@ -1279,6 +1283,8 @@ void MqttPacket::handleConnAck(std::shared_ptr &sender) logger->log(LOG_DEBUG) << "Bridge '" << sender->repr() << "' subscribing locally to '" << pub.topic << "', QoS=" << static_cast(pub.qos) << "."; + // TODO: Add prefix to pub.topic + const std::vector subtopics = splitTopic(pub.topic); store->addSubscription(sender, subtopics, pub.qos, true, true, 0); } @@ -1958,6 +1964,10 @@ void MqttPacket::handlePublish(std::shared_ptr &sender) << ". Retain=" << publishData.retain << ". Dup=" << duplicate << ". Alias=" << publishData.topicAlias << "."; #endif + // If sender is a bridge, remove the topic prefix, if set. + if (sender->getBridgeState() && sender->getBridgeState()->c.topicPrefix) + removeTopicPrefix(sender->getBridgeState()->c.topicPrefix.value()); + ThreadGlobals::getThreadData()->receivedMessageCounter.inc(); Authentication &authentication = *ThreadGlobals::getAuth(); @@ -2052,6 +2062,119 @@ void MqttPacket::handlePublish(std::shared_ptr &sender) #endif } +void MqttPacket::removeTopicPrefix() { + removeTopicPrefix(publishData.topicPrefix); +} + +/** + * When a packet is received from the bridge, we should remove the topic prefix, if some is set. + * Removing the topic prefix by mangling the bites field. The MQTT fixed header and MQTT topic header are being removed from the beginning of the bites field. Then, the topic + * prefix is being removed. After that, the new MQTT topic header (which is just the length of the topic) are recalculated and inserted at the beginning, as well as the remaining + * length header field, which is part of the MQTT fixed header. After that, some fields (remainingLength, fixed_header_length, payloadStart) must be reset to the correct values, + * because they changed due to removing the prefix from the topic. + * changed while + * @param topicPrefix + */ +void MqttPacket::removeTopicPrefix(const std::string &topicPrefix) +{ + if (topicPrefix.empty()) + return; + if (publishData.topic.compare(0, topicPrefix.length(), topicPrefix) == 0) + { + auto topic_header_length = 2; + + // Remove all headers from bites so only original topic name an payload is left + bites.erase(bites.begin(), bites.begin() + fixed_header_length + topic_header_length); + // Remove topic prefix from bites and class fields + bites.erase(bites.begin(), bites.begin() + topicPrefix.length()); + publishData.topic.erase(0, topicPrefix.length()); + publishData.topicPrefix.clear(); + publishData.resplitTopic(); + + // Insert length of new topic (always 2 bytes) + bites.insert(bites.begin(), topic_header_length, 0); + pos = 0; + auto new_topic_length = publishData.topic.length(); + writeUint16(new_topic_length); + + // Calculate the remaining length header field (see https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718023) + remainingLength = bites.size(); + + if (fixed_header_length > 0) + { + bites.insert(bites.begin(), remainingLength.getLen(), 0); + pos = 0; + writeVariableByteInt(remainingLength); + + // Finally add the original first byte + bites.insert(bites.begin(), first_byte); + + // Update fixed_header_length, it may have changed after adding the remaining_length_header_field + fixed_header_length = 1 + remainingLength.getLen(); + } + + payloadStart = fixed_header_length + topic_header_length + new_topic_length; + // Take care if QoS is set, as then 2 bytes are in front of the payload. + if (publishData.qos > 0) + payloadStart+=2; + pos = bites.size(); + } +} + +/** + * When a packet is sent to a bridge, then the topic prefix schould be added. + * Adding the topic prefix by mangling the bites field. The MQTT fixed header and MQTT topic header are being removed from the beginning of the bites field. Then, the topic + * prefix is being added to the beginning. After that, the new MQTT topic header (which is just the length of the topic) are recalculated and inserted at the beginning, as well + * as the remaining length header field, which is part of the MQTT fixed header. After that, some fields (remainingLength, fixed_header_length, payloadStart) must be reset to + * the correct values, because they changed due to adding the prefix to the topic. + * @param topicPrefix + */ +void MqttPacket::addTopicPrefix(const std::string &topicPrefix) +{ + if (publishData.topic.compare(0, topicPrefix.length(), topicPrefix) != 0) + { + publishData.topicPrefix = topicPrefix; + publishData.topic.insert(0, topicPrefix); + publishData.resplitTopic(); + + auto topic_header_length = 2; + + // Remove all headers from bites so only original topic name an payload is left + bites.erase(bites.begin(), bites.begin() + fixed_header_length + topic_header_length); + // Inject topic prefix + bites.insert(bites.begin(), topicPrefix.begin(), topicPrefix.end()); + + // Insert length of new topic (always 2 bytes) + bites.insert(bites.begin(), topic_header_length, 0); + pos = 0; + auto new_topic_length = publishData.topic.length(); + writeUint16(new_topic_length); + + // Calculate the remaining length header field (see https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718023) + remainingLength = bites.size(); + + // When message is generated by plugin, there is no fixed header already. + if (fixed_header_length > 0) + { + bites.insert(bites.begin(), remainingLength.getLen(), 0); + pos = 0; + writeVariableByteInt(remainingLength); + + // Finally add the original first byte + bites.insert(bites.begin(), first_byte); + + // Update fixed_header_length, it may have changed after adding the remaining_length_header_field + fixed_header_length = 1 + remainingLength.getLen(); + } + + payloadStart = fixed_header_length + topic_header_length + new_topic_length; + // Take care if QoS is set, as then 2 bytes are in front of the payload. + if (publishData.qos > 0) + payloadStart+=2; + pos = bites.size(); + } +} + void MqttPacket::parsePubAckData() { setPosToDataStart(); diff --git a/mqttpacket.h b/mqttpacket.h index 34dcce4d..263fade0 100644 --- a/mqttpacket.h +++ b/mqttpacket.h @@ -142,6 +142,9 @@ class MqttPacket void handlePing(std::shared_ptr &sender); void parsePublishData(std::shared_ptr &sender); void handlePublish(std::shared_ptr &sender); + void removeTopicPrefix(); + void removeTopicPrefix(const std::string& topicPrefix); + void addTopicPrefix(const std::string& topicPrefix); void parsePubAckData(); void handlePubAck(std::shared_ptr &sender); PubRecData parsePubRecData(); diff --git a/types.h b/types.h index 3cb8f4d1..d41d80f5 100644 --- a/types.h +++ b/types.h @@ -230,6 +230,7 @@ class Publish std::string client_id; std::string username; std::string topic; + std::string topicPrefix; std::string payload; private: NoCopy> subtopics; From b9491615e5323aef01906fc1553e59452190d228 Mon Sep 17 00:00:00 2001 From: "Evil.2000" Date: Tue, 7 Jan 2025 22:39:25 +0100 Subject: [PATCH 2/2] Extended flashmq.conf manpage with option topic_prefix. --- man/flashmq.conf.5 | 27 ++++++++++++++++++++++++++- man/flashmq.conf.5.dbk5 | 30 ++++++++++++++++++++++++++++++ man/flashmq.conf.5.html | 30 ++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/man/flashmq.conf.5 b/man/flashmq.conf.5 index 7c3aa375..e29018c0 100644 --- a/man/flashmq.conf.5 +++ b/man/flashmq.conf.5 @@ -5,7 +5,7 @@ \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac -.TH flashmq.conf 5 "Dec 23 2024" "" "" +.TH flashmq.conf 5 "Jan 07 2025" "" "" .SH NAME flashmq.conf \- FlashMQ configuration file format .SH SYNOPSIS @@ -518,6 +518,31 @@ The QoS value is like any subscription at a server. Messages received by the oth Default: \fI0\fR .TP +\*(T<\fB\m[green]topic_prefix\m[] \fI\m[cyan]prefix\m[]\fR\fR\*(T> +When sending messages to a remote server, the topic defined in \fB\m[green]subscribe\fR\m[] or \fB\m[green]publish\fR\m[] will be prepended with \fI\m[cyan]prefix\m[]\fR. This gives the ability to publish all topics from this broker under a common topic on the remote site. + +The difference in using \fB\m[green]topic_prefix\fR\m[] instead of adding it manually to \fB\m[green]subscribe\fR\m[] or \fB\m[green]publish\fR\m[] is, that the \fI\m[cyan]prefix\m[]\fR is being dynamically added or removed when messages are sent or received to/from the broker. This applies to all \fB\m[green]subscribe\fR\m[] or \fB\m[green]publish\fR\m[] options at once. + +Always add a trailing slash (/) to the prefix! Otherwise something like \fItopic1/\fR on the local site becomes \fIprefixtopic1/\fR on the remote site and this is probably not intended. + +Example 1: Publish all messages in all local topics to the remote system under topic \fIbridge1/\fR and receive all messages from the remote system in topic \fIbridge1/\fR in local. For instance, the local topic \fIhome/sensor1/temp\fR becomes \fIbridge1/home/sensor1/temp\fR on the remote site and vice versa. + +Example 2: Publish all messages from local \fItopic1/\fR to the remote system under \fIbridge1/topic1/\fR and receive all messages from the remote system under \fIbridge1/topic2/\fR to local \fItopic2/\fR. +.PP +.nf +.in +7 +\m[blue]# Example 1:\m[] +\m[green]topic_prefix \m[]\m[cyan]bridge1/\m[] +\m[green]publish \m[]\m[cyan]#\m[] +\m[green]subscribe \m[]\m[cyan]#\m[] +\m[blue]# Example 2:\m[] +\m[green]topic_prefix \m[]\m[cyan]bridge1/\m[] +\m[green]publish \m[]\m[cyan]topic1/#\m[] +\m[green]subscribe \m[]\m[cyan]topic2/#\m[] + +.in +.fi +.TP \*(T<\fB\m[green]local_username\m[] \fI\m[cyan]username\m[]\fR\fR\*(T> Username as seen by the local FlashMQ's plugin or ACL checks. This is not always necessary. .TP diff --git a/man/flashmq.conf.5.dbk5 b/man/flashmq.conf.5.dbk5 index 00cee2c4..d5edbcd2 100644 --- a/man/flashmq.conf.5.dbk5 +++ b/man/flashmq.conf.5.dbk5 @@ -1057,6 +1057,36 @@ listen { + + prefix + + + When sending messages to a remote server, the topic defined in or will be prepended with prefix. This gives the ability to publish all topics from this broker under a common topic on the remote site. + + + The difference in using instead of adding it manually to or is, that the prefix is being dynamically added or removed when messages are sent or received to/from the broker. This applies to all or options at once. + + + Always add a trailing slash (/) to the prefix! Otherwise something like topic1/ on the local site becomes prefixtopic1/ on the remote site and this is probably not intended. + + + Example 1: Publish all messages in all local topics to the remote system under topic bridge1/ and receive all messages from the remote system in topic bridge1/ in local. For instance, the local topic home/sensor1/temp becomes bridge1/home/sensor1/temp on the remote site and vice versa. + + + Example 2: Publish all messages from local topic1/ to the remote system under bridge1/topic1/ and receive all messages from the remote system under bridge1/topic2/ to local topic2/. + + + + + username diff --git a/man/flashmq.conf.5.html b/man/flashmq.conf.5.html index 799dfea6..87800c73 100644 --- a/man/flashmq.conf.5.html +++ b/man/flashmq.conf.5.html @@ -1267,6 +1267,36 @@ +
≥ v1.7.0
topic_prefix prefix#
+
+

+ When sending messages to a remote server, the topic defined in subscribe or publish will be prepended with prefix. This gives the ability to publish all topics from this broker under a common topic on the remote site. +

+

+ The difference in using topic_prefix instead of adding it manually to subscribe or publish is, that the prefix is being dynamically added or removed when messages are sent or received to/from the broker. This applies to all subscribe or publish options at once. +

+

+ Always add a trailing slash (/) to the prefix! Otherwise something like topic1/ on the local site becomes prefixtopic1/ on the remote site and this is probably not intended. +

+

+ Example 1: Publish all messages in all local topics to the remote system under topic bridge1/ and receive all messages from the remote system in topic bridge1/ in local. For instance, the local topic home/sensor1/temp becomes bridge1/home/sensor1/temp on the remote site and vice versa. +

+

+ Example 2: Publish all messages from local topic1/ to the remote system under bridge1/topic1/ and receive all messages from the remote system under bridge1/topic2/ to local topic2/. +

+
# Example 1:
+topic_prefix bridge1/
+publish #
+subscribe #
+
+# Example 2:
+topic_prefix bridge1/
+publish topic1/#
+subscribe topic2/#
+
+ + +
≥ v1.7.0
local_username username#