Skip to content

Commit 670b38d

Browse files
committed
TopicMatcher matching to parent when using '#' wildcard.
1 parent 55f7fec commit 670b38d

6 files changed

Lines changed: 33 additions & 20 deletions

File tree

include/mqtt/topic_matcher.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,22 +312,28 @@ class topic_matcher
312312
auto snode = std::move(nodes_.back());
313313
nodes_.pop_back();
314314

315+
const auto map_end = snode.node_->children.end();
316+
typename node_map::iterator child;
317+
315318
// If we're at the end of the topic fields, we either have a value,
316319
// or need to move on to the next node to search.
317320
if (snode.fields_.empty()) {
318321
pval_ = snode.node_->content.get();
319-
if (!pval_)
322+
if (!pval_) {
323+
// ...but a '#' matches the parent topic
324+
if ((child = snode.node_->children.find("#")) != map_end) {
325+
pval_ = child->second->content.get();
326+
return;
327+
}
320328
this->next();
329+
}
321330
return;
322331
}
323332

324333
// Get the next field of the topic to search
325334
auto field = std::move(snode.fields_.front());
326335
snode.fields_.pop_front();
327336

328-
typename node_map::iterator child;
329-
const auto map_end = snode.node_->children.end();
330-
331337
// Look for an exact match
332338
if ((child = snode.node_->children.find(field)) != map_end) {
333339
nodes_.push_back({child->second.get(), snode.fields_});

src/topic.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,10 @@ bool topic_filter::matches(const string& topic) const
109109
{
110110
auto n = fields_.size();
111111

112-
// Edge case of topic_filter("")
112+
// Spec: All Topic Names and Topic Filters MUST be at least one
113+
// character long [MQTT-4.7.3-1]
114+
// So, an empty filter can't match anything, and is technically an
115+
// error.
113116
if (n == 0) {
114117
return false;
115118
}
@@ -118,7 +121,7 @@ bool topic_filter::matches(const string& topic) const
118121
auto nt = topic_fields.size();
119122

120123
// Filter can't match a topic that is shorter
121-
if (n > nt) {
124+
if (n > nt && !(n == nt + 1 && fields_[n - 1] == "#")) {
122125
return false;
123126
}
124127

@@ -140,6 +143,9 @@ bool topic_filter::matches(const string& topic) const
140143
if (fields_[i] == "#") {
141144
break;
142145
}
146+
if (i == nt && i < n - 1) {
147+
return fields_[i + 1] == "#";
148+
}
143149
if (fields_[i] != "+" && fields_[i] != topic_fields[i]) {
144150
return false;
145151
}

test/unit/test_async_client.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -578,8 +578,7 @@ TEST_CASE("async_client publish 5 args", "[client]")
578578

579579
const void* payload{PAYLOAD.data()};
580580
const size_t payload_size{PAYLOAD.size()};
581-
delivery_token_ptr token_pub{
582-
cli.publish(TOPIC, payload, payload_size, GOOD_QOS, RETAINED)
581+
delivery_token_ptr token_pub{cli.publish(TOPIC, payload, payload_size, GOOD_QOS, RETAINED)
583582
};
584583
REQUIRE(token_pub);
585584
token_pub->wait_for(TIMEOUT);
@@ -738,8 +737,8 @@ TEST_CASE("async_client subscribe many topics 2 args_single", "[client]")
738737
cli.connect()->wait();
739738
REQUIRE(cli.is_connected());
740739

741-
mqtt::const_string_collection_ptr TOPIC_1_COLL{mqtt::string_collection::create({"TOPIC0"}
742-
)};
740+
mqtt::const_string_collection_ptr TOPIC_1_COLL{mqtt::string_collection::create({"TOPIC0"})
741+
};
743742
iasync_client::qos_collection GOOD_QOS_1_COLL{0};
744743
try {
745744
cli.subscribe(TOPIC_1_COLL, GOOD_QOS_1_COLL)->wait_for(TIMEOUT);

test/unit/test_async_client_v3.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,8 @@ class async_client_v3_test : public CppUnit::TestFixture
534534

535535
// NOTE: Only tokens for messages with QOS=1 and QOS=2 are kept. That's
536536
// why the vector's size does not account for QOS=0 message tokens
537-
std::vector<mqtt::delivery_token_ptr> tokens_pending{cli.get_pending_delivery_tokens(
538-
)};
537+
std::vector<mqtt::delivery_token_ptr> tokens_pending{cli.get_pending_delivery_tokens()
538+
};
539539
CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(tokens_pending.size()));
540540

541541
mqtt::token_ptr token_disconn{cli.disconnect()};
@@ -831,8 +831,7 @@ class async_client_v3_test : public CppUnit::TestFixture
831831
CPPUNIT_ASSERT(cli.is_connected());
832832

833833
mqtt::test::dummy_action_listener listener;
834-
mqtt::token_ptr token_sub{
835-
cli.subscribe(TOPIC_COLL, GOOD_QOS_COLL, &CONTEXT, listener)
834+
mqtt::token_ptr token_sub{cli.subscribe(TOPIC_COLL, GOOD_QOS_COLL, &CONTEXT, listener)
836835
};
837836
CPPUNIT_ASSERT(token_sub);
838837
token_sub->wait_for(TIMEOUT);

test/unit/test_topic.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ TEST_CASE("topic matches", "[topic_filter]")
232232
REQUIRE(filt.matches("my/topic/name"));
233233
REQUIRE(filt.matches("my/topic/id"));
234234
REQUIRE(filt.matches("my/topic/name/and/id"));
235+
REQUIRE(filt.matches("my/topic"));
235236

236237
REQUIRE(!filt.matches("my/other/name"));
237238
REQUIRE(!filt.matches("my/other/id"));
@@ -244,14 +245,14 @@ TEST_CASE("topic matches", "[topic_filter]")
244245
SECTION("should_match")
245246
{
246247
REQUIRE(topic_filter{"foo/bar"}.matches("foo/bar"));
247-
REQUIRE(
248-
topic_filter{
249-
"foo/+",
250-
}
251-
.matches("foo/bar")
252-
);
248+
REQUIRE(topic_filter{
249+
"foo/+",
250+
}
251+
.matches("foo/bar"));
253252
REQUIRE(topic_filter{"foo/+/baz"}.matches("foo/bar/baz"));
254253
REQUIRE(topic_filter{"foo/+/#"}.matches("foo/bar/baz"));
254+
REQUIRE(topic_filter("foo/bar/#").matches("foo/bar/baz"));
255+
REQUIRE(topic_filter("foo/bar/#").matches("foo/bar"));
255256
REQUIRE(topic_filter{"A/B/+/#"}.matches("A/B/B/C"));
256257
REQUIRE(topic_filter{"#"}.matches("foo/bar/baz"));
257258
REQUIRE(topic_filter{"#"}.matches("/foo/bar"));

test/unit/test_topic_matcher.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ TEST_CASE("matcher matches", "[topic_matcher]")
6969
REQUIRE((topic_matcher<int>{{"foo/+", 42}}.has_match("foo/bar")));
7070
REQUIRE((topic_matcher<int>{{"foo/+/baz", 42}}.has_match("foo/bar/baz")));
7171
REQUIRE((topic_matcher<int>{{"foo/+/#", 42}}.has_match("foo/bar/baz")));
72+
REQUIRE((topic_matcher<int>{{"foo/bar/#", 42}}.has_match("foo/bar/baz")));
73+
REQUIRE((topic_matcher<int>{{"foo/bar/#", 42}}.has_match("foo/bar")));
7274
REQUIRE((topic_matcher<int>{{"A/B/+/#", 42}}.has_match("A/B/B/C")));
7375
REQUIRE((topic_matcher<int>{{"#", 42}}.has_match("foo/bar/baz")));
7476
REQUIRE((topic_matcher<int>{{"#", 42}}.has_match("/foo/bar")));

0 commit comments

Comments
 (0)