topic_matcher.h 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. /////////////////////////////////////////////////////////////////////////////
  2. /// @file topic_matcher.h
  3. /// Declaration of MQTT topic_matcher class
  4. /// @date April 23, 2022
  5. /// @author Frank Pagliughi
  6. /////////////////////////////////////////////////////////////////////////////
  7. /*******************************************************************************
  8. * Copyright (c) 2022-2024 Frank Pagliughi <fpagliughi@mindspring.com>
  9. *
  10. * All rights reserved. This program and the accompanying materials
  11. * are made available under the terms of the Eclipse Public License v1.0
  12. * and Eclipse Distribution License v1.0 which accompany this distribution.
  13. *
  14. * The Eclipse Public License is available at
  15. * http://www.eclipse.org/legal/epl-v10.html
  16. * and the Eclipse Distribution License is available at
  17. * http://www.eclipse.org/org/documents/edl-v10.php.
  18. *
  19. * Contributors:
  20. * Frank Pagliughi - initial implementation and documentation
  21. *******************************************************************************/
  22. #ifndef __mqtt_topic_matcher_h
  23. #define __mqtt_topic_matcher_h
  24. #include <string>
  25. #include "mqtt/types.h"
  26. #include "mqtt/topic.h"
  27. #include <forward_list>
  28. #include <initializer_list>
  29. #include <map>
  30. #include <memory>
  31. #include <vector>
  32. // The make_unique<>() template functions from the original std proposal:
  33. // https://isocpp.org/files/papers/N3656.txt
  34. #if __cplusplus < 201402L
  35. namespace std {
  36. template <class T>
  37. struct _Unique_if {
  38. typedef unique_ptr<T> _Single_object;
  39. };
  40. template <class T>
  41. struct _Unique_if<T[]> {
  42. typedef unique_ptr<T[]> _Unknown_bound;
  43. };
  44. template <class T, size_t N>
  45. struct _Unique_if<T[N]> {
  46. typedef void _Known_bound;
  47. };
  48. template <class T, class... Args>
  49. typename _Unique_if<T>::_Single_object make_unique(Args&&... args) {
  50. return unique_ptr<T>(new T(std::forward<Args>(args)...));
  51. }
  52. template <class T>
  53. typename _Unique_if<T>::_Unknown_bound make_unique(size_t n) {
  54. typedef typename remove_extent<T>::type U;
  55. return unique_ptr<T>(new U[n]());
  56. }
  57. template <class T, class... Args>
  58. typename _Unique_if<T>::_Known_bound make_unique(Args&&...) = delete;
  59. } // namespace std
  60. #endif
  61. namespace mqtt {
  62. /////////////////////////////////////////////////////////////////////////////
  63. /**
  64. * A collection of MQTT topic filters mapped to arbitrary values.
  65. *
  66. * This can be used to get an iterator to all filters in the collection that
  67. * match a topic. A typical use case might be to match incoming messages to
  68. * specific callback functions based on topics, such as
  69. *
  70. * To test against a single filter, see
  71. * [`TopicFilter`](crate::TopicFilter). This collection is more commonly
  72. * used when there are a number of filters and each needs to be associated
  73. * with a particular action or piece of data. Note, however, that a single
  74. * incoming topic could match against several items in the collection. For
  75. * example, the topic:
  76. *
  77. * @code
  78. * data/temperature/engine
  79. * @endcode
  80. *
  81. * Could match against the filters:
  82. * @code
  83. * data/temperature/engine
  84. * data/temperature/#
  85. * data/+/engine
  86. * @endcode
  87. *
  88. * Thus, the collection gives an iterator for the items matching a topic.
  89. *
  90. * A common use for this would be to store callbacks to process incoming
  91. * messages based on topics.
  92. *
  93. * This code was adapted from the Eclipse Python `MQTTMatcher` class:
  94. *
  95. *<https://github.com/eclipse/paho.mqtt.python/blob/master/src/paho/mqtt/matcher.py>
  96. *
  97. * which use a prefix tree (trie) to store the values.
  98. *
  99. * For example, if you had a `topic_mapper<int>` and you inserted:
  100. * @code
  101. * topic_matcher<int> tm{
  102. * {"some/random/topic", 42},
  103. * {"some/#", 99},
  104. * {"some/+/topic", 33}
  105. * };
  106. * @endcode
  107. *
  108. * The collection would be built like:
  109. * @code
  110. * "some" -> <null>
  111. * "random" -> <null>
  112. * "topic" -> <42>
  113. * "#" -> <99>
  114. * "+" -> <null>
  115. * "topic" -> <33>
  116. * @endcode
  117. *
  118. * Note that the collection has two types of iterators. The basic `iterator`
  119. * is a normal C++ iterator over *all* the items in the collection. It will
  120. * visit every node in the collection and produce all items. This is not the
  121. * typical use case for the collection, but can be used for diagnostics,
  122. * etc, to show the full contents of the collection.
  123. *
  124. * The more common use case is the `match_iterator`, returned by the
  125. * `topic_matcher::matches(string)` method. This is an optimized search
  126. * iterator for finding all the filters and values that match the specified
  127. * topic string.
  128. */
  129. template <typename T>
  130. class topic_matcher
  131. {
  132. public:
  133. using key_type = string;
  134. using mapped_type = T;
  135. using value_type = std::pair<key_type, mapped_type>;
  136. using reference = value_type;
  137. using const_reference = const value_type&;
  138. using value_ptr = std::unique_ptr<value_type>;
  139. using mapped_ptr = std::unique_ptr<mapped_type>;
  140. private:
  141. /**
  142. * The nodes of the collection.
  143. */
  144. struct node {
  145. using ptr_t = std::unique_ptr<node>;
  146. using map_t = std::map<string, ptr_t>;
  147. /** The value that matches the topic at this node, if any */
  148. value_ptr content;
  149. /** Child nodes mapped by the next field of the topic */
  150. map_t children;
  151. /** Creates a new, empty node */
  152. static ptr_t create() { return std::make_unique<node>(); }
  153. /** Determines if this node is empty (no content or children) */
  154. bool empty() const { return !content && children.empty(); }
  155. /** Removes the empty nodes under this one. */
  156. void prune() {
  157. for (auto& child : children) {
  158. child.second->prune();
  159. }
  160. for (auto child = children.cbegin(); child != children.cend();) {
  161. if (child->second->empty()) {
  162. child = children.erase(child);
  163. }
  164. else {
  165. ++child;
  166. }
  167. }
  168. }
  169. };
  170. using node_ptr = typename node::ptr_t;
  171. using node_map = typename node::map_t;
  172. /** The root node of the collection */
  173. node_ptr root_;
  174. public:
  175. /** Generic iterator over all items in the collection. */
  176. class iterator
  177. {
  178. /** The last-found value */
  179. value_type* pval_;
  180. /** The nodes still to be checked, used as a stack */
  181. std::vector<node*> nodes_;
  182. void next() {
  183. // If there are no nodes left to search, we're done.
  184. if (nodes_.empty()) {
  185. pval_ = nullptr;
  186. return;
  187. }
  188. // Get the next node to search.
  189. auto snode = std::move(nodes_.back());
  190. nodes_.pop_back();
  191. // Push the children onto the stack for later
  192. for (auto const& child : snode->children) {
  193. nodes_.push_back(child.second.get());
  194. }
  195. // If there's a value in this node, use it;
  196. // otherwise keep looking.
  197. pval_ = snode->content.get();
  198. if (!pval_) this->next();
  199. }
  200. friend class topic_matcher;
  201. iterator(value_type* pval) : pval_{pval} {}
  202. iterator(node* root) : pval_{nullptr} {
  203. nodes_.push_back(root);
  204. next();
  205. }
  206. public:
  207. /**
  208. * Gets a reference to the current value.
  209. * @return A reference to the current value.
  210. */
  211. reference operator*() noexcept { return *pval_; }
  212. /**
  213. * Gets a const reference to the current value.
  214. * @return A const reference to the current value.
  215. */
  216. const_reference operator*() const noexcept { return *pval_; }
  217. /**
  218. * Get a pointer to the current value.
  219. * @return A pointer to the current value.
  220. */
  221. value_type* operator->() noexcept { return pval_; }
  222. /**
  223. * Get a const pointer to the current value.
  224. * @return A const pointer to the current value.
  225. */
  226. const value_type* operator->() const noexcept { return pval_; }
  227. /**
  228. * Postfix increment operator.
  229. * @return An iterator pointing to the previous matching item.
  230. */
  231. iterator operator++(int) noexcept {
  232. auto tmp = *this;
  233. this->next();
  234. return tmp;
  235. }
  236. /**
  237. * Prefix increment operator.
  238. * @return An iterator pointing to the next matching item.
  239. */
  240. iterator& operator++() noexcept {
  241. this->next();
  242. return *this;
  243. }
  244. /**
  245. * Compares two iterators to see if they don't refer to the same
  246. * node.
  247. *
  248. * @param other The other iterator to compare against this one.
  249. * @return @em true if they don't match, @em false if they do
  250. */
  251. bool operator!=(const iterator& other) const noexcept {
  252. return pval_ != other.pval_;
  253. }
  254. };
  255. /** A const iterator over all itemsin the collection. */
  256. class const_iterator : public iterator
  257. {
  258. using base = iterator;
  259. friend class topic_matcher;
  260. const_iterator(iterator it) : base(it) {}
  261. public:
  262. /**
  263. * Gets a const reference to the current value.
  264. * @return A const reference to the current value.
  265. */
  266. const_reference operator*() const noexcept { return base::operator*(); }
  267. /**
  268. * Get a const pointer to the current value.
  269. * @return A const pointer to the current value.
  270. */
  271. const value_type* operator->() const noexcept { return base::operator->(); }
  272. };
  273. /**
  274. * Iterator that efficiently searches the collection for topic
  275. * matches.
  276. */
  277. class match_iterator
  278. {
  279. /** Information about a node that needs to be searched. */
  280. struct search_node {
  281. /** The current node being searched. */
  282. node* node_;
  283. /** The fields of the topic still to be searched. */
  284. std::forward_list<string> fields_;
  285. /** Whether this is the first/root node */
  286. bool first_;
  287. search_node(node* nd, const std::forward_list<string>& sy, bool first = false)
  288. : node_{nd}, fields_{sy}, first_{first} {}
  289. search_node(node* nd, std::forward_list<string>&& sy, bool first = false)
  290. : node_{nd}, fields_{std::move(sy)}, first_{first} {}
  291. };
  292. /** The last-found value */
  293. value_type* pval_;
  294. /** The nodes still to be checked, used as a stack */
  295. std::vector<search_node> nodes_;
  296. /**
  297. * Move the next iterator to the next value, or to end(), if none
  298. * left.
  299. *
  300. * This will keep recursing until it finds a matching node that
  301. * contains a value or it reaches the end.
  302. */
  303. void next() {
  304. pval_ = nullptr;
  305. // If there are no nodes left to search, we're done.
  306. if (nodes_.empty()) return;
  307. // Get the next node to search.
  308. auto snode = std::move(nodes_.back());
  309. nodes_.pop_back();
  310. // If we're at the end of the topic fields, we either have a value,
  311. // or need to move on to the next node to search.
  312. if (snode.fields_.empty()) {
  313. pval_ = snode.node_->content.get();
  314. if (!pval_) this->next();
  315. return;
  316. }
  317. // Get the next field of the topic to search
  318. auto field = std::move(snode.fields_.front());
  319. snode.fields_.pop_front();
  320. typename node_map::iterator child;
  321. const auto map_end = snode.node_->children.end();
  322. // Look for an exact match
  323. if ((child = snode.node_->children.find(field)) != map_end) {
  324. nodes_.push_back({child->second.get(), snode.fields_});
  325. }
  326. // Topics starting with '$' don't match wildcards in the first field
  327. // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901246
  328. if (!snode.first_ || field.empty() || field[0] != '$') {
  329. // Look for a single-field wildcard match
  330. if ((child = snode.node_->children.find("+")) != map_end) {
  331. nodes_.push_back({child->second.get(), snode.fields_});
  332. }
  333. // Look for a terminating match
  334. if ((child = snode.node_->children.find("#")) != map_end) {
  335. // By definition, a '#' is a terminating leaf
  336. pval_ = child->second->content.get();
  337. return;
  338. }
  339. }
  340. this->next();
  341. }
  342. friend class topic_matcher;
  343. match_iterator() : pval_{nullptr} {}
  344. match_iterator(value_type* pval) : pval_{pval} {}
  345. match_iterator(node* root, const string& topic) : pval_{nullptr} {
  346. auto v = topic::split(topic);
  347. std::forward_list<string> fields{v.begin(), v.end()};
  348. nodes_.push_back(search_node{root, std::move(fields), true});
  349. next();
  350. }
  351. public:
  352. /**
  353. * Gets a reference to the current value.
  354. * @return A reference to the current value.
  355. */
  356. reference operator*() noexcept { return *pval_; }
  357. /**
  358. * Gets a const reference to the current value.
  359. * @return A const reference to the current value.
  360. */
  361. const_reference operator*() const noexcept { return *pval_; }
  362. /**
  363. * Get a pointer to the current value.
  364. * @return A pointer to the current value.
  365. */
  366. value_type* operator->() noexcept { return pval_; }
  367. /**
  368. * Get a const pointer to the current value.
  369. * @return A const pointer to the current value.
  370. */
  371. const value_type* operator->() const noexcept { return pval_; }
  372. /**
  373. * Postfix increment operator.
  374. * @return An iterator pointing to the previous matching item.
  375. */
  376. match_iterator operator++(int) noexcept {
  377. auto tmp = *this;
  378. this->next();
  379. return tmp;
  380. }
  381. /**
  382. * Prefix increment operator.
  383. * @return An iterator pointing to the next matching item.
  384. */
  385. match_iterator& operator++() noexcept {
  386. this->next();
  387. return *this;
  388. }
  389. /**
  390. * Compares two iterators to see if they don't refer to the same
  391. * node.
  392. *
  393. * @param other The other iterator to compare against this one.
  394. * @return @em true if they don't match, @em false if they do
  395. */
  396. bool operator!=(const match_iterator& other) const noexcept {
  397. return pval_ != other.pval_;
  398. }
  399. };
  400. /**
  401. * A const match iterator.
  402. */
  403. class const_match_iterator : public match_iterator
  404. {
  405. using base = match_iterator;
  406. friend class topic_matcher;
  407. const_match_iterator(match_iterator it) : base(it) {}
  408. public:
  409. /**
  410. * Gets a const reference to the current value.
  411. * @return A const reference to the current value.
  412. */
  413. const_reference operator*() const noexcept { return base::operator*(); }
  414. /**
  415. * Get a const pointer to the current value.
  416. * @return A const pointer to the current value.
  417. */
  418. const value_type* operator->() const noexcept { return base::operator->(); }
  419. };
  420. /**
  421. * Creates new, empty collection.
  422. */
  423. topic_matcher() : root_(node::create()) {}
  424. /**
  425. * Creates a new collection with a list of key/value pairs.
  426. *
  427. * This can be used to create a connection from a table of entries, as
  428. * key/value pairs, like:
  429. *
  430. * topic_matcher<int> matcher {
  431. * { "#", -1 },
  432. * { "some/random/topic", 42 },
  433. * { "some/#", 99 }
  434. * }
  435. *
  436. * @param lst The list of key/value pairs to populate the collection.
  437. */
  438. topic_matcher(std::initializer_list<value_type> lst) : root_(node::create()) {
  439. for (const auto& v : lst) {
  440. insert(v);
  441. }
  442. }
  443. /**
  444. * Determines if the collection is empty.
  445. * @return @em true if the collection is empty, @em false if it contains
  446. * any filters.
  447. */
  448. bool empty() const { return root_.empty(); }
  449. /**
  450. * Inserts a new key/value pair into the collection.
  451. * @param val The value to place in the collection.
  452. */
  453. void insert(value_type&& val) {
  454. auto nd = root_.get();
  455. auto fields = topic::split(val.first);
  456. for (const auto& field : fields) {
  457. auto it = nd->children.find(field);
  458. if (it == nd->children.end()) {
  459. nd->children[field] = node::create();
  460. it = nd->children.find(field);
  461. }
  462. nd = it->second.get();
  463. }
  464. nd->content = std::make_unique<value_type>(std::move(val));
  465. }
  466. /**
  467. * Inserts a new value into the collection.
  468. * @param key The topic/filter entry
  469. * @param val The value to associate with that entry.
  470. */
  471. void insert(const value_type& val) {
  472. value_type v{val};
  473. this->insert(std::move(v));
  474. }
  475. /**
  476. * Removes an entry from the collection.
  477. *
  478. * This removes the value from the internal node, but leaves the node in
  479. * the collection, even if it is empty.
  480. * @param filter The topic filter to remove.
  481. * @return A unique pointer to the value, if any.
  482. */
  483. mapped_ptr remove(const key_type& filter) {
  484. auto nd = root_.get();
  485. auto fields = topic::split(filter);
  486. for (auto& field : fields) {
  487. auto it = nd->children.find(field);
  488. if (it == nd->children.end()) return mapped_ptr{};
  489. nd = it->second.get();
  490. }
  491. value_ptr valpair;
  492. nd->content.swap(valpair);
  493. return (valpair) ? std::make_unique<mapped_type>(valpair->second) : mapped_ptr{};
  494. }
  495. /**
  496. * Removes the empty nodes in the collection.
  497. */
  498. void prune() { root_->prune(); }
  499. /**
  500. * Gets an iterator to the full collection of filters.
  501. * @return An iterator to the full collection of filters.
  502. */
  503. iterator begin() { return iterator{root_.get()}; }
  504. /**
  505. * Gets an iterator to the end of the collection of filters.
  506. * @return An iterator to the end of collection of filters.
  507. */
  508. iterator end() { return iterator{static_cast<value_type*>(nullptr)}; }
  509. /**
  510. * Gets an iterator to the end of the collection of filters.
  511. * @return An iterator to the end of collection of filters.
  512. */
  513. const_iterator end() const noexcept {
  514. return const_iterator{static_cast<value_type*>(nullptr)};
  515. }
  516. /**
  517. * Gets a const iterator to the full collection of filters.
  518. * @return A const iterator to the full collection of filters.
  519. */
  520. const_iterator cbegin() const { return const_iterator{root_.get()}; }
  521. /**
  522. * Gets a const iterator to the end of the collection of filters.
  523. * @return A const iterator to the end of collection of filters.
  524. */
  525. const_iterator cend() const noexcept { return end(); }
  526. /**
  527. * Gets a pointer to the value at the requested key.
  528. * @param filter The topic filter entry to find.
  529. * @return An iterator to the value if found, @em end() if not found.
  530. */
  531. iterator find(const key_type& filter) {
  532. auto nd = root_.get();
  533. auto fields = topic::split(filter);
  534. for (auto& field : fields) {
  535. auto it = nd->children.find(field);
  536. if (it == nd->children.end()) return end();
  537. nd = it->second.get();
  538. }
  539. return iterator{nd->content.get()};
  540. }
  541. /**
  542. * Gets a const pointer to the value at the requested key.
  543. * @param filter The topic filter entry to find.
  544. * @return A const pointer to the value if found, @em nullptr if not
  545. * found.
  546. */
  547. const_iterator find(const key_type& filter) const {
  548. return const_cast<topic_matcher*>(this)->find(filter);
  549. }
  550. /**
  551. * Gets an match_iterator that can find the matches to the topic.
  552. * @param topic The topic to search for matches.
  553. * @return An iterator that can find the matches to the topic.
  554. */
  555. match_iterator matches(const string& topic) {
  556. return match_iterator(root_.get(), topic);
  557. }
  558. /**
  559. * Gets a const iterator that can find the matches to the topic.
  560. * @param topic The topic to search for matches.
  561. * @return A const iterator that can find the matches to the topic.
  562. */
  563. const_match_iterator matches(const string& topic) const {
  564. return match_iterator(root_.get(), topic);
  565. }
  566. /**
  567. * Gets an iterator for the end of the collection.
  568. *
  569. * This simply returns an empty/null iterator which we can use to signal
  570. * the end of the collection.
  571. *
  572. * @return An empty/null iterator indicating the end of the collection.
  573. */
  574. const_match_iterator matches_end() const noexcept { return match_iterator{}; }
  575. /**
  576. * Gets an iterator for the end of the collection.
  577. *
  578. * This simply returns an empty/null iterator which we can use to signal
  579. * the end of the collection.
  580. *
  581. * @return An empty/null iterator indicating the end of the collection.
  582. */
  583. const_match_iterator matches_cend() const noexcept { return match_iterator{}; }
  584. /**
  585. * Determines if there are any matches for the specified topic.
  586. * @param topic The topic to search for matches.
  587. * @return Whether there are any matches for the topic in the
  588. * collection.
  589. */
  590. bool has_match(const string& topic) { return matches(topic) != matches_cend(); }
  591. };
  592. /////////////////////////////////////////////////////////////////////////////
  593. } // namespace mqtt
  594. #endif // __mqtt_topic_matcher_h