Commit d3072e9cbc756dc86f35c067a3e8075c0538ef5f

Authored by Wiebe Cazemier
1 parent 367579cc

Refactor SIMD/SSE

FlashMQTests/tst_maintests.cpp
... ... @@ -78,11 +78,13 @@ private slots:
78 78  
79 79 void test_sse_split();
80 80  
81   - void test_validUtf8();
  81 + void test_validUtf8Generic();
82 82 void test_validUtf8Sse();
83 83  
84 84 void testPacketInt16Parse();
85 85  
  86 + void testTopicsMatch();
  87 +
86 88 };
87 89  
88 90 MainTests::MainTests()
... ... @@ -600,7 +602,8 @@ void MainTests::test_acl_patterns_clientid()
600 602  
601 603 void MainTests::test_sse_split()
602 604 {
603   - Utils data;
  605 + SimdUtils data;
  606 + std::vector<std::string> output;
604 607  
605 608 std::list<std::string> topics;
606 609 topics.push_back("one/two/threeabcasdfasdf/koe");
... ... @@ -619,50 +622,51 @@ void MainTests::test_sse_split()
619 622  
620 623 for (const std::string &t : topics)
621 624 {
622   - QCOMPARE(*data.splitTopic(t), splitToVector(t, '/'));
  625 + data.splitTopic(t, output);
  626 + QCOMPARE(output, splitToVector(t, '/'));
623 627 }
624 628 }
625 629  
626   -void MainTests::test_validUtf8()
  630 +void MainTests::test_validUtf8Generic()
627 631 {
628 632 char m[16];
629 633  
630   - QVERIFY(isValidUtf8(""));
631   - QVERIFY(isValidUtf8("ƀ"));
632   - QVERIFY(isValidUtf8("Hello"));
  634 + QVERIFY(isValidUtf8Generic(""));
  635 + QVERIFY(isValidUtf8Generic("ƀ"));
  636 + QVERIFY(isValidUtf8Generic("Hello"));
633 637  
634 638 std::memset(m, 0, 16);
635   - QVERIFY(!isValidUtf8(std::string(m, 16)));
  639 + QVERIFY(!isValidUtf8Generic(std::string(m, 16)));
636 640  
637   - QVERIFY(isValidUtf8("Straƀe")); // two byte chars
638   - QVERIFY(isValidUtf8("StraƀeHelloHelloHelloHelloHelloHello")); // two byte chars
639   - QVERIFY(isValidUtf8("HelloHelloHelloHelloHelloHelloHelloHelloStraƀeHelloHelloHelloHelloHelloHello")); // two byte chars
  641 + QVERIFY(isValidUtf8Generic("Straƀe")); // two byte chars
  642 + QVERIFY(isValidUtf8Generic("StraƀeHelloHelloHelloHelloHelloHello")); // two byte chars
  643 + QVERIFY(isValidUtf8Generic("HelloHelloHelloHelloHelloHelloHelloHelloStraƀeHelloHelloHelloHelloHelloHello")); // two byte chars
640 644  
641 645 std::memset(m, 0, 16);
642 646 m[0] = 'a';
643 647 m[1] = 13; // is \r
644   - QVERIFY(!isValidUtf8(std::string(m, 16)));
  648 + QVERIFY(!isValidUtf8Generic(std::string(m, 16)));
645 649  
646 650 const std::string unicode_ballet_shoes("🩰");
647 651 QVERIFY(unicode_ballet_shoes.length() == 4);
648   - QVERIFY(isValidUtf8(unicode_ballet_shoes));
  652 + QVERIFY(isValidUtf8Generic(unicode_ballet_shoes));
649 653  
650 654 const std::string unicode_ballot_box("☐");
651 655 QVERIFY(unicode_ballot_box.length() == 3);
652   - QVERIFY(isValidUtf8(unicode_ballot_box));
  656 + QVERIFY(isValidUtf8Generic(unicode_ballot_box));
653 657  
654 658 std::memset(m, 0, 16);
655 659 m[0] = 0b11000001; // Start 2 byte char
656 660 m[1] = 0b00000001; // Next byte doesn't start with 1, which is wrong
657 661 std::string a(m, 2);
658   - QVERIFY(!isValidUtf8(a));
  662 + QVERIFY(!isValidUtf8Generic(a));
659 663  
660 664 std::memset(m, 0, 16);
661 665 m[0] = 0b11100001; // Start 3 byte char
662 666 m[1] = 0b10100001;
663 667 m[2] = 0b00000001; // Next byte doesn't start with 1, which is wrong
664 668 std::string b(m, 3);
665   - QVERIFY(!isValidUtf8(b));
  669 + QVERIFY(!isValidUtf8Generic(b));
666 670  
667 671 std::memset(m, 0, 16);
668 672 m[0] = 0b11110001; // Start 4 byte char
... ... @@ -670,7 +674,7 @@ void MainTests::test_validUtf8()
670 674 m[2] = 0b10100001;
671 675 m[3] = 0b00000001; // Next byte doesn't start with 1, which is wrong
672 676 std::string c(m, 4);
673   - QVERIFY(!isValidUtf8(c));
  677 + QVERIFY(!isValidUtf8Generic(c));
674 678  
675 679 std::memset(m, 0, 16);
676 680 m[0] = 0b11110001; // Start 4 byte char
... ... @@ -678,18 +682,18 @@ void MainTests::test_validUtf8()
678 682 m[2] = 0b00100001; // Doesn't start with 1: invalid.
679 683 m[3] = 0b10000001;
680 684 std::string d(m, 4);
681   - QVERIFY(!isValidUtf8(d));
  685 + QVERIFY(!isValidUtf8Generic(d));
682 686  
683 687 // Upper ASCII, invalid
684 688 std::memset(m, 0, 16);
685 689 m[0] = 127;
686 690 std::string e(m, 1);
687   - QVERIFY(!isValidUtf8(e));
  691 + QVERIFY(!isValidUtf8Generic(e));
688 692 }
689 693  
690 694 void MainTests::test_validUtf8Sse()
691 695 {
692   - Utils data;
  696 + SimdUtils data;
693 697  
694 698 char m[16];
695 699  
... ... @@ -776,6 +780,33 @@ void MainTests::testPacketInt16Parse()
776 780 }
777 781 }
778 782  
  783 +void MainTests::testTopicsMatch()
  784 +{
  785 + QVERIFY(topicsMatch("#", ""));
  786 + QVERIFY(topicsMatch("#", "asdf/b/sdf"));
  787 + QVERIFY(topicsMatch("#", "+/b/sdf"));
  788 + QVERIFY(topicsMatch("#", "/one/two/asdf"));
  789 + QVERIFY(topicsMatch("#", "/one/two/asdf/"));
  790 + QVERIFY(topicsMatch("+/+/+/+/+", "/one/two/asdf/"));
  791 + QVERIFY(topicsMatch("+/+/#", "/one/two/asdf/"));
  792 + QVERIFY(topicsMatch("+/+/#", "/1234567890abcdef/two/asdf/"));
  793 + QVERIFY(topicsMatch("+/+/#", "/1234567890abcdefg/two/asdf/"));
  794 + QVERIFY(topicsMatch("+/+/#", "/1234567890abcde/two/asdf/"));
  795 + QVERIFY(topicsMatch("+/+/#", "1234567890abcde//two/asdf/"));
  796 +
  797 + QVERIFY(!topicsMatch("+/santa", "/one/two/asdf/"));
  798 + QVERIFY(!topicsMatch("+/+/+/+/", "/one/two/asdf/a"));
  799 + QVERIFY(!topicsMatch("+/one/+/+/", "/one/two/asdf/a"));
  800 +
  801 + QVERIFY(topicsMatch("$SYS/cow", "$SYS/cow"));
  802 + QVERIFY(topicsMatch("$SYS/cow/+", "$SYS/cow/bla"));
  803 + QVERIFY(topicsMatch("$SYS/#", "$SYS/broker/clients/connected"));
  804 +
  805 + QVERIFY(!topicsMatch("$SYS/cow/+", "$SYS/cow/bla/foobar"));
  806 + QVERIFY(!topicsMatch("#", "$SYS/cow"));
  807 +
  808 +}
  809 +
779 810 QTEST_GUILESS_MAIN(MainTests)
780 811  
781 812 #include "tst_maintests.moc"
... ...
main.cpp
... ... @@ -84,14 +84,11 @@ int main(int argc, char *argv[])
84 84 check<std::runtime_error>(register_signal_handers());
85 85  
86 86 std::string sse = "without SSE support";
87   -#ifdef __SSE2__
88   - sse = "with SSE2 support";
89   -#endif
90 87 #ifdef __SSE4_2__
91 88 sse = "with SSE4.2 support";
92 89 #endif
93 90 #ifdef NDEBUG
94   - logger->logf(LOG_NOTICE, "Starting FlashMQ version %s, release build.", VERSION, sse.c_str());
  91 + logger->logf(LOG_NOTICE, "Starting FlashMQ version %s, release build %s.", VERSION, sse.c_str());
95 92 #else
96 93 logger->logf(LOG_NOTICE, "Starting FlashMQ version %s, debug build %s.", VERSION, sse.c_str());
97 94 #endif
... ...
mainapp.cpp
... ... @@ -340,10 +340,11 @@ void MainApp::publishStatsOnDollarTopic()
340 340  
341 341 void MainApp::publishStat(const std::string &topic, uint64_t n)
342 342 {
343   - std::vector<std::string> *subtopics = utils.splitTopic(topic);
  343 + std::vector<std::string> subtopics;
  344 + splitTopic(topic, subtopics);
344 345 const std::string payload = std::to_string(n);
345 346 Publish p(topic, payload, 0);
346   - subscriptionStore->queuePacketAtSubscribers(*subtopics, p, true);
  347 + subscriptionStore->queuePacketAtSubscribers(subtopics, p, true);
347 348 subscriptionStore->setRetainedMessage(topic, payload, 0);
348 349 }
349 350  
... ...
mainapp.h
... ... @@ -41,7 +41,6 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;.
41 41 #include "timer.h"
42 42 #include "scopedsocket.h"
43 43 #include "oneinstancelock.h"
44   -#include "threadlocalutils.h"
45 44  
46 45 #define VERSION "0.7.0"
47 46  
... ... @@ -65,7 +64,6 @@ class MainApp
65 64 std::mutex quitMutex;
66 65 std::string fuzzFilePath;
67 66 OneInstanceLock oneInstanceLock;
68   - Utils utils;
69 67  
70 68 Logger *logger = Logger::getInstance();
71 69  
... ...
mqttpacket.cpp
... ... @@ -23,9 +23,9 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;.
23 23  
24 24 #include "utils.h"
25 25  
26   -#include "threadlocalutils.h"
27   -
28   -thread_local Utils utils;
  26 +// We can void constant reallocation of space for parsed subtopics by using this. But, beware to only use it during handling of the current
  27 +// packet. Don't access it for a stored packet, because then it will have changed.
  28 +thread_local std::vector<std::string> gSubtopics;
29 29  
30 30 RemainingLength::RemainingLength()
31 31 {
... ... @@ -106,7 +106,8 @@ MqttPacket::MqttPacket(const Publish &amp;publish) :
106 106 }
107 107  
108 108 this->topic = publish.topic;
109   - this->subtopics = utils.splitTopic(this->topic);
  109 + this->subtopics = &gSubtopics;
  110 + splitTopic(this->topic, gSubtopics);
110 111  
111 112 packetType = PacketType::PUBLISH;
112 113 this->qos = publish.qos;
... ... @@ -299,7 +300,7 @@ void MqttPacket::handleConnect()
299 300 }
300 301  
301 302 // The specs don't really say what to do when client id not UTF8, so including here.
302   - if (!utils.isValidUtf8(client_id) || !utils.isValidUtf8(username) || !utils.isValidUtf8(password) || !utils.isValidUtf8(will_topic))
  303 + if (!isValidUtf8(client_id) || !isValidUtf8(username) || !isValidUtf8(password) || !isValidUtf8(will_topic))
303 304 {
304 305 ConnAck connAck(ConnAckReturnCodes::MalformedUsernameOrPassword);
305 306 MqttPacket response(connAck);
... ... @@ -419,7 +420,7 @@ void MqttPacket::handleSubscribe()
419 420 uint16_t topicLength = readTwoBytesToUInt16();
420 421 std::string topic(readBytes(topicLength), topicLength);
421 422  
422   - if (topic.empty() || !utils.isValidUtf8(topic))
  423 + if (topic.empty() || !isValidUtf8(topic))
423 424 throw ProtocolError("Subscribe topic not valid UTF-8.");
424 425  
425 426 if (!isValidSubscribePath(topic))
... ... @@ -438,6 +439,7 @@ void MqttPacket::handleSubscribe()
438 439 SubAck subAck(packet_id, subs_reponse_codes);
439 440 MqttPacket response(subAck);
440 441 sender->writeMqttPacket(response);
  442 + this->subtopics = nullptr;
441 443 }
442 444  
443 445 void MqttPacket::handleUnsubscribe()
... ... @@ -454,7 +456,7 @@ void MqttPacket::handleUnsubscribe()
454 456 uint16_t topicLength = readTwoBytesToUInt16();
455 457 std::string topic(readBytes(topicLength), topicLength);
456 458  
457   - if (topic.empty() || !utils.isValidUtf8(topic))
  459 + if (topic.empty() || !isValidUtf8(topic))
458 460 throw ProtocolError("Subscribe topic not valid UTF-8.");
459 461  
460 462 sender->getThreadData()->getSubscriptionStore()->removeSubscription(sender, topic);
... ... @@ -485,9 +487,10 @@ void MqttPacket::handlePublish()
485 487 throw ProtocolError("Duplicate flag is set for QoS 0 packet. This is illegal.");
486 488  
487 489 topic = std::string(readBytes(variable_header_length), variable_header_length);
488   - subtopics = utils.splitTopic(topic);
  490 + subtopics = &gSubtopics;
  491 + splitTopic(topic, gSubtopics);
489 492  
490   - if (!utils.isValidUtf8(topic, true))
  493 + if (!isValidUtf8(topic, true))
491 494 {
492 495 logger->logf(LOG_WARNING, "Client '%s' published a message with invalid UTF8 or +/# in it. Dropping.", sender->repr().c_str());
493 496 return;
... ... @@ -546,6 +549,7 @@ void MqttPacket::handlePublish()
546 549 // For the existing clients, we can just write the same packet back out, with our small alterations.
547 550 sender->getThreadData()->getSubscriptionStore()->queuePacketAtSubscribers(*subtopics, *this);
548 551 }
  552 + this->subtopics = nullptr;
549 553 }
550 554  
551 555 void MqttPacket::handlePubAck()
... ... @@ -678,6 +682,10 @@ const std::string &amp;MqttPacket::getTopic() const
678 682 return this->topic;
679 683 }
680 684  
  685 +/**
  686 + * @brief MqttPacket::getSubtopics returns a pointer to the parsed subtopics. Use with care!
  687 + * @return a pointer to a vector of subtopics that will be overwritten the next packet!
  688 + */
681 689 const std::vector<std::string> *MqttPacket::getSubtopics() const
682 690 {
683 691 return this->subtopics;
... ...
mqttpacket.h
... ... @@ -48,7 +48,7 @@ class MqttPacket
48 48 #endif
49 49  
50 50 std::string topic;
51   - std::vector<std::string> *subtopics; // comes from local thread storage. See std::vector<std::string> *ThreadData::splitTopic(std::string &topic)
  51 + std::vector<std::string> *subtopics = nullptr;
52 52 std::vector<char> bites;
53 53 size_t fixed_header_length = 0; // if 0, this packet does not contain the bytes of the fixed header.
54 54 RemainingLength remainingLength;
... ...
threadlocalutils.cpp
  1 +#ifdef __SSE4_2__
  2 +
1 3 #include "threadlocalutils.h"
2 4  
3 5 #include <cstring>
4 6 #include <cassert>
5 7  
6   -Utils::Utils() :
  8 +SimdUtils::SimdUtils() :
7 9 subtopicParseMem(TOPIC_MEMORY_LENGTH),
8 10 topicCopy(TOPIC_MEMORY_LENGTH)
9 11 {
... ... @@ -11,15 +13,14 @@ Utils::Utils() :
11 13 }
12 14  
13 15 /**
14   - * @brief ThreadData::splitTopic uses SSE4.2 to detect the '/' chars, 16 chars at a time, and returns a pointer to thread-local memory.
15   - * @param topic string is altered: some extra space is reserved.
16   - * @return Pointer to thread-owned vector of subtopics.
17   - *
18   - * Because it returns a pointer to the thread-local vector, only the current thread should touch it.
  16 + * @brief SimdUtils::splitTopic uses SSE4.2 to detect the '/' chars, 16 chars at a time, and returns a pointer to thread-local memory.
  17 + * @param topic
  18 + * @param output is cleared and emplaced in. You can give it members from the Utils class, to avoid re-allocation.
  19 + * @return
19 20 */
20   -std::vector<std::string> *Utils::splitTopic(const std::string &topic)
  21 +std::vector<std::string> *SimdUtils::splitTopic(const std::string &topic, std::vector<std::string> &output)
21 22 {
22   - subtopics.clear();
  23 + output.clear();
23 24  
24 25 const int s = topic.size();
25 26 std::memcpy(topicCopy.data(), topic.c_str(), s+1);
... ... @@ -41,16 +42,16 @@ std::vector&lt;std::string&gt; *Utils::splitTopic(const std::string &amp;topic)
41 42  
42 43 if (index < 16 || n >= s)
43 44 {
44   - subtopics.emplace_back(subtopicParseMem.data(), carryi);
  45 + output.emplace_back(subtopicParseMem.data(), carryi);
45 46 carryi = 0;
46 47 n++;
47 48 }
48 49 }
49 50  
50   - return &subtopics;
  51 + return &output;
51 52 }
52 53  
53   -bool Utils::isValidUtf8(const std::string &s, bool alsoCheckInvalidPublishChars)
  54 +bool SimdUtils::isValidUtf8(const std::string &s, bool alsoCheckInvalidPublishChars)
54 55 {
55 56 const int len = s.size();
56 57  
... ... @@ -136,3 +137,5 @@ bool Utils::isValidUtf8(const std::string &amp;s, bool alsoCheckInvalidPublishChars)
136 137  
137 138 return true;
138 139 }
  140 +
  141 +#endif
... ...
threadlocalutils.h
1 1 #ifndef THREADLOCALUTILS_H
2 2 #define THREADLOCALUTILS_H
3 3  
  4 +#ifdef __SSE4_2__
  5 +
4 6 #include <vector>
5 7 #include <string>
6 8 #include <immintrin.h>
7 9  
8 10 #define TOPIC_MEMORY_LENGTH 65560
9 11  
10   -/**
11   - * @brief The Utils class have utility functions that make use of pre-allocated memory. Use with thread_local or create per thread manually.
12   - */
13   -class Utils
  12 +
  13 +
  14 +class SimdUtils
14 15 {
15   - std::vector<std::string> subtopics;
16 16 std::vector<char> subtopicParseMem;
17 17 std::vector<char> topicCopy;
18 18 __m128i slashes = _mm_set1_epi8('/');
... ... @@ -23,11 +23,13 @@ class Utils
23 23 __m128i plus = _mm_set1_epi8('+');
24 24  
25 25 public:
26   - Utils();
  26 + SimdUtils();
27 27  
28   - std::vector<std::string> *splitTopic(const std::string &topic);
  28 + std::vector<std::string> *splitTopic(const std::string &topic, std::vector<std::string> &output);
29 29 bool isValidUtf8(const std::string &s, bool alsoCheckInvalidPublishChars = false);
30 30 };
31 31  
  32 +#endif
  33 +
32 34  
33 35 #endif // THREADLOCALUTILS_H
... ...
utils.cpp
... ... @@ -34,6 +34,12 @@ License along with FlashMQ. If not, see &lt;https://www.gnu.org/licenses/&gt;.
34 34 #include "logger.h"
35 35 #include "evpencodectxmanager.h"
36 36  
  37 +
  38 +#ifdef __SSE4_2__
  39 +#include "threadlocalutils.h"
  40 +thread_local SimdUtils simdUtils;
  41 +#endif
  42 +
37 43 std::list<std::string> split(const std::string &input, const char sep, size_t max, bool keep_empty_parts)
38 44 {
39 45 std::list<std::string> list;
... ... @@ -60,8 +66,16 @@ bool topicsMatch(const std::string &amp;subscribeTopic, const std::string &amp;publishTo
60 66 if (!subscribeTopic.empty() && !publishTopic.empty() && publishTopic[0] == '$' && subscribeTopic[0] != '$')
61 67 return false;
62 68  
63   - const std::vector<std::string> subscribeParts = splitToVector(subscribeTopic, '/');
64   - const std::vector<std::string> publishParts = splitToVector(publishTopic, '/');
  69 + std::vector<std::string> subscribeParts;
  70 + std::vector<std::string> publishParts;
  71 +
  72 +#ifdef __SSE4_2__
  73 + simdUtils.splitTopic(subscribeTopic, subscribeParts);
  74 + simdUtils.splitTopic(publishTopic, publishParts);
  75 +#else
  76 + splitToVector(subscribeTopic, subscribeParts, '/');
  77 + splitToVector(publishTopic, publishParts, '/');
  78 +#endif
65 79  
66 80 auto subscribe_itr = subscribeParts.begin();
67 81 auto publish_itr = publishParts.begin();
... ... @@ -84,7 +98,7 @@ bool topicsMatch(const std::string &amp;subscribeTopic, const std::string &amp;publishTo
84 98 return result;
85 99 }
86 100  
87   -bool isValidUtf8(const std::string &s, bool alsoCheckInvalidPublishChars)
  101 +bool isValidUtf8Generic(const std::string &s, bool alsoCheckInvalidPublishChars)
88 102 {
89 103 int multibyte_remain = 0;
90 104 int cur_code_point = 0;
... ... @@ -146,6 +160,15 @@ bool isValidUtf8(const std::string &amp;s, bool alsoCheckInvalidPublishChars)
146 160 return multibyte_remain == 0;
147 161 }
148 162  
  163 +bool isValidUtf8(const std::string &s, bool alsoCheckInvalidPublishChars)
  164 +{
  165 +#ifdef __SSE4_2__
  166 + return simdUtils.isValidUtf8(s, alsoCheckInvalidPublishChars);
  167 +#else
  168 + return isValidUtf8Generic(s, alsoCheckInvalidPublishChars);
  169 +#endif
  170 +}
  171 +
149 172 bool strContains(const std::string &s, const std::string &needle)
150 173 {
151 174 return s.find(needle) != std::string::npos;
... ... @@ -209,25 +232,39 @@ bool containsDangerousCharacters(const std::string &amp;s)
209 232 return false;
210 233 }
211 234  
212   -const std::vector<std::string> splitToVector(const std::string &input, const char sep, size_t max, bool keep_empty_parts)
  235 +void splitTopic(const std::string &topic, std::vector<std::string> &output)
  236 +{
  237 +#ifdef __SSE4_2__
  238 + simdUtils.splitTopic(topic, output);
  239 +#else
  240 + splitToVector(topic, output, '/');
  241 +#endif
  242 +}
  243 +
  244 +void splitToVector(const std::string &input, std::vector<std::string> &output, const char sep, size_t max, bool keep_empty_parts)
213 245 {
214 246 const auto subtopic_count = std::count(input.begin(), input.end(), '/') + 1;
215 247  
216   - std::vector<std::string> result;
217   - result.reserve(subtopic_count);
  248 + output.reserve(subtopic_count);
218 249 size_t start = 0;
219 250 size_t end;
220 251  
221 252 const auto npos = std::string::npos;
222 253  
223   - while (result.size() < max && (end = input.find(sep, start)) != npos)
  254 + while (output.size() < max && (end = input.find(sep, start)) != npos)
224 255 {
225 256 if (start != end || keep_empty_parts)
226   - result.push_back(input.substr(start, end - start));
  257 + output.push_back(input.substr(start, end - start));
227 258 start = end + 1; // increase by length of seperator.
228 259 }
229 260 if (start != input.size() || keep_empty_parts)
230   - result.push_back(input.substr(start, npos));
  261 + output.push_back(input.substr(start, npos));
  262 +}
  263 +
  264 +const std::vector<std::string> splitToVector(const std::string &input, const char sep, size_t max, bool keep_empty_parts)
  265 +{
  266 + std::vector<std::string> result;
  267 + splitToVector(input, result, sep, max, keep_empty_parts);
231 268 return result;
232 269 }
233 270  
... ...
... ... @@ -46,9 +46,12 @@ template&lt;typename T&gt; int check(int rc)
46 46  
47 47 std::list<std::string> split(const std::string &input, const char sep, size_t max = std::numeric_limits<int>::max(), bool keep_empty_parts = true);
48 48 const std::vector<std::string> splitToVector(const std::string &input, const char sep, size_t max = std::numeric_limits<int>::max(), bool keep_empty_parts = true);
  49 +void splitToVector(const std::string &input, std::vector<std::string> &output, const char sep, size_t max = std::numeric_limits<int>::max(), bool keep_empty_parts = true);
  50 +void splitTopic(const std::string &topic, std::vector<std::string> &output);
49 51  
50 52 bool topicsMatch(const std::string &subscribeTopic, const std::string &publishTopic);
51 53  
  54 +bool isValidUtf8Generic(const std::string &s, bool alsoCheckInvalidPublishChars = false);
52 55 bool isValidUtf8(const std::string &s, bool alsoCheckInvalidPublishChars = false);
53 56  
54 57 bool strContains(const std::string &s, const std::string &needle);
... ...