PahoMqttCpp
MQTT C++ Client for POSIX and Windows
Loading...
Searching...
No Matches
topic_matcher.h
Go to the documentation of this file.
1
7
8/*******************************************************************************
9 * Copyright (c) 2022-2025 Frank Pagliughi <fpagliughi@mindspring.com>
10 *
11 * All rights reserved. This program and the accompanying materials
12 * are made available under the terms of the Eclipse Public License v2.0
13 * and Eclipse Distribution License v1.0 which accompany this distribution.
14 *
15 * The Eclipse Public License is available at
16 * http://www.eclipse.org/legal/epl-v20.html
17 * and the Eclipse Distribution License is available at
18 * http://www.eclipse.org/org/documents/edl-v10.php.
19 *
20 * Contributors:
21 * Frank Pagliughi - initial implementation and documentation
22 *******************************************************************************/
23
24#ifndef __mqtt_topic_matcher_h
25#define __mqtt_topic_matcher_h
26
27#include <forward_list>
28#include <initializer_list>
29#include <map>
30#include <memory>
31#include <string>
32#include <vector>
33
34#include "mqtt/topic.h"
35#include "mqtt/types.h"
36
37namespace mqtt {
38
40
114template <typename T>
116{
117public:
119 using mapped_type = T;
120 using value_type = std::pair<key_type, mapped_type>;
123
124 using value_ptr = std::unique_ptr<value_type>;
125 using mapped_ptr = std::unique_ptr<mapped_type>;
126
127private:
131 struct node
132 {
133 using ptr_t = std::unique_ptr<node>;
134 using map_t = std::map<string, ptr_t>;
135
137 value_ptr content;
139 map_t children;
140
142 static ptr_t create() { return std::make_unique<node>(); }
144 bool empty() const { return !content && children.empty(); }
145
147 void prune() {
148 for (auto& child : children) {
149 child.second->prune();
150 }
151
152 for (auto child = children.cbegin(); child != children.cend();) {
153 if (child->second->empty()) {
154 child = children.erase(child);
155 }
156 else {
157 ++child;
158 }
159 }
160 }
161 };
162 using node_ptr = typename node::ptr_t;
163 using node_map = typename node::map_t;
164
166 node_ptr root_;
167
168public:
170 class iterator
171 {
173 value_type* pval_;
175 std::vector<node*> nodes_;
176
177 void next() {
178 // If there are no nodes left to search, we're done.
179 if (nodes_.empty()) {
180 pval_ = nullptr;
181 return;
182 }
183
184 // Get the next node to search.
185 auto snode = std::move(nodes_.back());
186 nodes_.pop_back();
187
188 // Push the children onto the stack for later
189 for (auto const& child : snode->children) {
190 nodes_.push_back(child.second.get());
191 }
192
193 // If there's a value in this node, use it;
194 // otherwise keep looking.
195 pval_ = snode->content.get();
196 if (!pval_)
197 this->next();
198 }
199
200 friend class topic_matcher;
201
202 iterator(value_type* pval) : pval_{pval} {}
203 iterator(node* root) : pval_{nullptr} {
204 nodes_.push_back(root);
205 next();
206 }
207
208 public:
213 reference operator*() noexcept { return *pval_; }
218 const_reference operator*() const noexcept { return *pval_; }
223 value_type* operator->() noexcept { return pval_; }
228 const value_type* operator->() const noexcept { return pval_; }
233 iterator operator++(int) noexcept {
234 auto tmp = *this;
235 this->next();
236 return tmp;
237 }
238
242 iterator& operator++() noexcept {
243 this->next();
244 return *this;
245 }
246
253 bool operator!=(const iterator& other) const noexcept { return pval_ != other.pval_; }
254 };
255
257 class const_iterator : public iterator
258 {
259 using base = iterator;
260
261 friend class topic_matcher;
262 const_iterator(iterator it) : base(it) {}
263
264 public:
269 const_reference operator*() const noexcept { return base::operator*(); }
274 const value_type* operator->() const noexcept { return base::operator->(); }
275 };
276
281 class match_iterator
282 {
284 struct search_node
285 {
287 node* node_;
289 std::forward_list<string> fields_;
291 bool first_;
292
293 search_node(node* nd, const std::forward_list<string>& sy, bool first = false)
294 : node_{nd}, fields_{sy}, first_{first} {}
295 search_node(node* nd, std::forward_list<string>&& sy, bool first = false)
296 : node_{nd}, fields_{std::move(sy)}, first_{first} {}
297 };
298
300 value_type* pval_;
302 std::vector<search_node> nodes_;
303
311 void next() {
312 pval_ = nullptr;
313
314 // If there are no nodes left to search, we're done.
315 if (nodes_.empty())
316 return;
317
318 // Get the next node to search.
319 auto snode = std::move(nodes_.back());
320 nodes_.pop_back();
321
322 const auto map_end = snode.node_->children.end();
323 typename node_map::iterator child;
324
325 // If we're at the end of the topic fields, we either have a value,
326 // or need to move on to the next node to search.
327 if (snode.fields_.empty()) {
328 pval_ = snode.node_->content.get();
329 if (!pval_) {
330 // ...but a '#' matches the parent topic
331 if ((child = snode.node_->children.find("#")) != map_end) {
332 pval_ = child->second->content.get();
333 return;
334 }
335 this->next();
336 }
337 return;
338 }
339
340 // Get the next field of the topic to search
341 auto field = std::move(snode.fields_.front());
342 snode.fields_.pop_front();
343
344 // Look for an exact match
345 if ((child = snode.node_->children.find(field)) != map_end) {
346 nodes_.push_back({child->second.get(), snode.fields_});
347 }
348
349 // Topics starting with '$' don't match wildcards in the first field
350 // MQTT v5 Spec, Section 4.7.2:
351 // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901246
352
353 if (!snode.first_ || field.empty() || field[0] != '$') {
354 // Look for a single-field wildcard match
355 if ((child = snode.node_->children.find("+")) != map_end) {
356 nodes_.push_back({child->second.get(), snode.fields_});
357 }
358
359 // Look for a terminating match
360 if ((child = snode.node_->children.find("#")) != map_end) {
361 // By definition, a '#' is a terminating leaf
362 pval_ = child->second->content.get();
363 return;
364 }
365 }
366
367 this->next();
368 }
369
370 friend class topic_matcher;
371
372 match_iterator() : pval_{nullptr} {}
373 match_iterator(value_type* pval) : pval_{pval} {}
374 match_iterator(node* root, const string& topic) : pval_{nullptr} {
375 auto v = topic::split(topic);
376 std::forward_list<string> fields{v.begin(), v.end()};
377 nodes_.push_back(search_node{root, std::move(fields), true});
378 next();
379 }
380
381 public:
386 reference operator*() noexcept { return *pval_; }
391 const_reference operator*() const noexcept { return *pval_; }
396 value_type* operator->() noexcept { return pval_; }
401 const value_type* operator->() const noexcept { return pval_; }
406 match_iterator operator++(int) noexcept {
407 auto tmp = *this;
408 this->next();
409 return tmp;
410 }
411
415 match_iterator& operator++() noexcept {
416 this->next();
417 return *this;
418 }
419
426 bool operator!=(const match_iterator& other) const noexcept {
427 return pval_ != other.pval_;
428 }
429 };
430
434 class const_match_iterator : public match_iterator
435 {
436 using base = match_iterator;
437
438 friend class topic_matcher;
439 const_match_iterator(match_iterator it) : base(it) {}
440
441 public:
446 const_reference operator*() const noexcept { return base::operator*(); }
451 const value_type* operator->() const noexcept { return base::operator->(); }
452 };
453
457 topic_matcher() : root_(node::create()) {}
472 topic_matcher(std::initializer_list<value_type> lst) : root_(node::create()) {
473 for (const auto& v : lst) {
474 insert(v);
475 }
476 }
477
482 bool empty() const { return root_.empty(); }
487 void insert(value_type&& val) {
488 auto nd = root_.get();
489 auto fields = topic::split(val.first);
490
491 for (const auto& field : fields) {
492 auto it = nd->children.find(field);
493 if (it == nd->children.end()) {
494 nd->children[field] = node::create();
495 it = nd->children.find(field);
496 }
497 nd = it->second.get();
498 }
499 nd->content = std::make_unique<value_type>(std::move(val));
500 }
501
506 void insert(const value_type& val) {
507 value_type v{val};
508 this->insert(std::move(v));
509 }
510
518 mapped_ptr remove(const key_type& filter) {
519 auto nd = root_.get();
520 auto fields = topic::split(filter);
521
522 for (auto& field : fields) {
523 auto it = nd->children.find(field);
524 if (it == nd->children.end())
525 return mapped_ptr{};
526
527 nd = it->second.get();
528 }
529 value_ptr valpair;
530 nd->content.swap(valpair);
531
532 return (valpair) ? std::make_unique<mapped_type>(valpair->second) : mapped_ptr{};
533 }
534
537 void prune() { root_->prune(); }
542 iterator begin() { return iterator{root_.get()}; }
547 iterator end() { return iterator{static_cast<value_type*>(nullptr)}; }
552 const_iterator end() const noexcept {
553 return const_iterator{static_cast<value_type*>(nullptr)};
554 }
555
559 const_iterator cbegin() const { return const_iterator{root_.get()}; }
564 const_iterator cend() const noexcept { return end(); }
570 iterator find(const key_type& filter) {
571 auto nd = root_.get();
572 auto fields = topic::split(filter);
573
574 for (auto& field : fields) {
575 auto it = nd->children.find(field);
576 if (it == nd->children.end())
577 return end();
578 nd = it->second.get();
579 }
580 return iterator{nd->content.get()};
581 }
582
588 const_iterator find(const key_type& filter) const {
589 return const_cast<topic_matcher*>(this)->find(filter);
590 }
591
596 match_iterator matches(const string& topic) { return match_iterator(root_.get(), topic); }
602 const_match_iterator matches(const string& topic) const {
603 return match_iterator(root_.get(), topic);
604 }
605
613 const_match_iterator matches_end() const noexcept { return match_iterator{}; }
622 const_match_iterator matches_cend() const noexcept { return match_iterator{}; }
629 bool has_match(const string& topic) { return matches(topic) != matches_cend(); }
630};
631
633} // namespace mqtt
634
635#endif // __mqtt_topic_matcher_h
Definition topic_matcher.h:258
const value_type * operator->() const noexcept
Definition topic_matcher.h:274
const_reference operator*() const noexcept
Definition topic_matcher.h:269
friend class topic_matcher
Definition topic_matcher.h:261
Definition topic_matcher.h:435
const value_type * operator->() const noexcept
Definition topic_matcher.h:451
const_reference operator*() const noexcept
Definition topic_matcher.h:446
friend class topic_matcher
Definition topic_matcher.h:438
Definition topic_matcher.h:171
const value_type * operator->() const noexcept
Definition topic_matcher.h:228
iterator operator++(int) noexcept
Definition topic_matcher.h:233
reference operator*() noexcept
Definition topic_matcher.h:213
const_reference operator*() const noexcept
Definition topic_matcher.h:218
iterator & operator++() noexcept
Definition topic_matcher.h:242
bool operator!=(const iterator &other) const noexcept
Definition topic_matcher.h:253
friend class topic_matcher
Definition topic_matcher.h:200
value_type * operator->() noexcept
Definition topic_matcher.h:223
Definition topic_matcher.h:282
reference operator*() noexcept
Definition topic_matcher.h:386
match_iterator & operator++() noexcept
Definition topic_matcher.h:415
const_reference operator*() const noexcept
Definition topic_matcher.h:391
bool operator!=(const match_iterator &other) const noexcept
Definition topic_matcher.h:426
value_type * operator->() noexcept
Definition topic_matcher.h:396
match_iterator operator++(int) noexcept
Definition topic_matcher.h:406
const value_type * operator->() const noexcept
Definition topic_matcher.h:401
friend class topic_matcher
Definition topic_matcher.h:370
value_type reference
Definition topic_matcher.h:121
std::unique_ptr< value_type > value_ptr
Definition topic_matcher.h:124
const_match_iterator matches(const string &topic) const
Definition topic_matcher.h:602
bool empty() const
Definition topic_matcher.h:482
void insert(const value_type &val)
Definition topic_matcher.h:506
const_iterator cend() const noexcept
Definition topic_matcher.h:564
string key_type
Definition topic_matcher.h:118
void insert(value_type &&val)
Definition topic_matcher.h:487
mapped_ptr remove(const key_type &filter)
Definition topic_matcher.h:518
std::pair< key_type, mapped_type > value_type
Definition topic_matcher.h:120
iterator end()
Definition topic_matcher.h:547
const_iterator end() const noexcept
Definition topic_matcher.h:552
const_match_iterator matches_end() const noexcept
Definition topic_matcher.h:613
T mapped_type
Definition topic_matcher.h:119
iterator begin()
Definition topic_matcher.h:542
match_iterator matches(const string &topic)
Definition topic_matcher.h:596
const_iterator find(const key_type &filter) const
Definition topic_matcher.h:588
const_iterator cbegin() const
Definition topic_matcher.h:559
iterator find(const key_type &filter)
Definition topic_matcher.h:570
topic_matcher(std::initializer_list< value_type > lst)
Definition topic_matcher.h:472
void prune()
Definition topic_matcher.h:537
bool has_match(const string &topic)
Definition topic_matcher.h:629
const_match_iterator matches_cend() const noexcept
Definition topic_matcher.h:622
const value_type & const_reference
Definition topic_matcher.h:122
std::unique_ptr< mapped_type > mapped_ptr
Definition topic_matcher.h:125
topic_matcher()
Definition topic_matcher.h:457
Definition topic.h:54
static std::vector< std::string > split(const std::string &topic)
Definition async_client.h:60
std::string string
Definition types.h:43