diff --git a/src/StringIndexer.cpp b/src/StringIndexer.cpp index a535fed..45144d8 100644 --- a/src/StringIndexer.cpp +++ b/src/StringIndexer.cpp @@ -1,3 +1,4 @@ #include "StringIndexer.h" -std::map StringIndexer::strings; +StringIndexer::Strings StringIndexer::strings; + diff --git a/src/StringIndexer.h b/src/StringIndexer.h index bdb2da4..ba51e9b 100644 --- a/src/StringIndexer.h +++ b/src/StringIndexer.h @@ -1,6 +1,9 @@ // vim: ts=2 sw=2 expandtab #pragma once +#include #include +#include +#include "TinyString.h" #include #include @@ -10,9 +13,11 @@ */ class StringIndexer { + private: + class StringCounter { - std::string str; + TinyString str; uint8_t used=0; friend class StringIndexer; @@ -29,13 +34,13 @@ class StringIndexer public: using index_t = uint8_t; - static const std::string& str(const index_t& index) - { - static std::string dummy; - const auto& it=strings.find(index); - if (it == strings.end()) return dummy; - return it->second.str; - } + static const TinyString& str(const index_t& index) + { + static TinyString dummy; + const auto& it=strings.find(index); + if (it == strings.end()) return dummy; + return it->second.str; + } static void use(const index_t& index) { @@ -77,7 +82,7 @@ class StringIndexer { if (strings.find(index)==strings.end()) { - strings[index].str = std::string(str, len); + strings[index].str = TinyString(str, len); strings[index].used++; // Serial << "Creating index " << index << " for (" << strings[index].str.c_str() << ") len=" << len << endl; return index; @@ -86,7 +91,9 @@ class StringIndexer return 0; // TODO out of indexes } - static std::map strings; + using Strings = std::unordered_map; + + static Strings strings; }; class IndexedString @@ -103,7 +110,7 @@ class IndexedString index=StringIndexer::strToIndex(str, len); } - IndexedString(const std::string& str) : IndexedString(str.c_str(), str.length()) {}; + IndexedString(const TinyString& str) : IndexedString(str.c_str(), str.length()) {}; ~IndexedString() { StringIndexer::release(index); } @@ -124,7 +131,7 @@ class IndexedString return i1.index == i2.index; } - const std::string& str() const { return StringIndexer::str(index); } + const TinyString& str() const { return StringIndexer::str(index); } const StringIndexer::index_t& getIndex() const { return index; } diff --git a/src/TinyMqtt.cpp b/src/TinyMqtt.cpp index 9767f97..bda2846 100644 --- a/src/TinyMqtt.cpp +++ b/src/TinyMqtt.cpp @@ -52,7 +52,7 @@ MqttClient::MqttClient(MqttBroker* local_broker, TcpClient* new_client) #endif } -MqttClient::MqttClient(MqttBroker* local_broker, const std::string& id) +MqttClient::MqttClient(MqttBroker* local_broker, const TinyString& id) : local_broker(local_broker), clientId(id) { alive = 0; @@ -97,7 +97,7 @@ void MqttClient::connect(MqttBroker* local) local_broker = local; } -void MqttClient::connect(std::string broker, uint16_t port, uint16_t ka) +void MqttClient::connect(TinyString broker, uint16_t port, uint16_t ka) { debug("MqttClient::connect_to_host " << broker << ':' << port); keep_alive = ka; @@ -128,7 +128,7 @@ void MqttBroker::addClient(MqttClient* client) clients.push_back(client); } -void MqttBroker::connect(const std::string& host, uint16_t port) +void MqttBroker::connect(const TinyString& host, uint16_t port) { debug("MqttBroker::connect"); if (remote_broker == nullptr) remote_broker = new MqttClient; @@ -461,7 +461,7 @@ void MqttClient::processMessage(MqttMessage* mesg) // ClientId mesg->getString(payload, len); - clientId = std::string(payload, len); + clientId = TinyString(payload, len); payload += len; if (mqtt_flags & FlagWill) // Will topic @@ -539,11 +539,11 @@ void MqttClient::processMessage(MqttMessage* mesg) payload = header+2; debug("un/subscribe loop"); - std::string qoss; + TinyString qoss; while(payload < mesg->end()) { mesg->getString(payload, len); // Topic - debug( " topic (" << std::string(payload, len) << ')'); + debug( " topic (" << TinyString(payload, len) << ')'); // subscribe(Topic(payload, len)); Topic topic(payload, len); @@ -597,7 +597,7 @@ void MqttClient::processMessage(MqttMessage* mesg) #if TINY_MQTT_DEBUG Console << "Received Publish (" << published.str().c_str() << ") size=" << (int)len << endl; #endif - // << '(' << std::string(payload, len).c_str() << ')' << " msglen=" << mesg->length() << endl; + // << '(' << TinyString(payload, len).c_str() << ')' << " msglen=" << mesg->length() << endl; if (qos) payload+=2; // ignore packet identifier if any len=mesg->end()-payload; // TODO reset DUP @@ -889,7 +889,7 @@ void MqttMessage::hexdump(const char* prefix) const (void)prefix; #if TINY_MQTT_DEBUG if (TinyMqtt::debug<2) return; - static std::map tts={ + static std::map tts={ { Connect, "Connect" }, { ConnAck, "Connack" }, { Publish, "Publish" }, @@ -902,7 +902,7 @@ void MqttMessage::hexdump(const char* prefix) const { PingResp, "Pingresp" }, { Disconnect, "Disconnect" } }; - std::string t("Unknown"); + TinyString t("Unknown"); Type typ=static_cast(buffer[0] & 0xF0); if (tts.find(typ) != tts.end()) t=tts[typ]; @@ -919,7 +919,7 @@ void MqttMessage::hexdump(const char* prefix) const const char* hex_to_str = " | "; const char* separator = hex_to_str; const char* half_sep = " - "; - std::string ascii; + TinyString ascii; Console << prefix << " size(" << buffer.size() << "), state=" << state << endl; diff --git a/src/TinyMqtt.h b/src/TinyMqtt.h index c67f236..6791ff3 100644 --- a/src/TinyMqtt.h +++ b/src/TinyMqtt.h @@ -71,9 +71,10 @@ enum __attribute__((packed)) MqttError class Topic : public IndexedString { public: + Topic(const TinyString& m) : IndexedString(m){} Topic(const char* s, uint8_t len) : IndexedString(s,len){} Topic(const char* s) : Topic(s, strlen(s)) {} - Topic(const std::string s) : Topic(s.c_str(), s.length()){}; + // Topic(const TinyString s) : Topic(s.c_str(), s.length()){}; const char* c_str() const { return str().c_str(); } @@ -122,7 +123,7 @@ class MqttMessage void incoming(char byte); void add(char byte) { incoming(byte); } void add(const char* p, size_t len, bool addLength=true ); - void add(const std::string& s) { add(s.c_str(), s.length()); } + void add(const TinyString& s) { add(s.c_str(), s.length()); } void add(const Topic& t) { add(t.str()); } const char* end() const { return &buffer[0]+buffer.size(); } const char* getVHeader() const { return &buffer[vheader]; } @@ -156,7 +157,7 @@ class MqttMessage private: void encodeLength(); - std::string buffer; + TinyString buffer; uint8_t vheader; uint16_t size; // bytes left to receive State state; @@ -181,13 +182,13 @@ class MqttClient /** Constructor. Broker is the adress of a local broker if not null If you want to connect elsewhere, leave broker null and use connect() **/ - MqttClient(MqttBroker* broker = nullptr, const std::string& id = TINY_MQTT_DEFAULT_CLIENT_ID); - MqttClient(const std::string& id) : MqttClient(nullptr, id){} + MqttClient(MqttBroker* broker = nullptr, const TinyString& id = TINY_MQTT_DEFAULT_CLIENT_ID); + MqttClient(const TinyString& id) : MqttClient(nullptr, id){} ~MqttClient(); void connect(MqttBroker* local_broker); - void connect(std::string broker, uint16_t port, uint16_t keep_alive = 10); + void connect(TinyString broker, uint16_t port, uint16_t keep_alive = 10); // TODO it seems that connected returns true in tcp mode even if // no negociation occurred @@ -202,8 +203,8 @@ class MqttClient if (tcp_client) tcp_client->write(buf, length); } - const std::string& id() const { return clientId; } - void id(const std::string& new_id) { clientId = new_id; } + const TinyString& id() const { return clientId; } + void id(const TinyString& new_id) { clientId = new_id; } /** Should be called in main loop() */ void loop(); @@ -221,7 +222,7 @@ class MqttClient MqttError publish(const Topic&, const char* payload, size_t pay_length); MqttError publish(const Topic& t, const char* payload) { return publish(t, payload, strlen(payload)); } MqttError publish(const Topic& t, const String& s) { return publish(t, s.c_str(), s.length()); } - MqttError publish(const Topic& t, const std::string& s) { return publish(t,s.c_str(),s.length());} + MqttError publish(const Topic& t, const TinyString& s) { return publish(t,s.c_str(),s.length());} MqttError publish(const Topic& t) { return publish(t, nullptr, 0);}; MqttError subscribe(Topic topic, uint8_t qos=0); @@ -232,7 +233,7 @@ class MqttClient // TODO seems to be useless bool isLocal() const { return tcp_client == nullptr; } - void dump(std::string indent="") + void dump(TinyString indent="") { (void)indent; #if TINY_MQTT_DEBUG @@ -298,7 +299,7 @@ class MqttClient TcpClient* tcp_client=nullptr; // connection to remote broker std::set subscriptions; - std::string clientId; + TinyString clientId; CallBack callback = nullptr; }; @@ -318,12 +319,12 @@ class MqttBroker void begin() { server->begin(); } void loop(); - void connect(const std::string& host, uint16_t port=1883); + void connect(const TinyString& host, uint16_t port=1883); bool connected() const { return state == Connected; } size_t clientsCount() const { return clients.size(); } - void dump(std::string indent="") + void dump(TinyString indent="") { for(auto client: clients) client->dump(indent); diff --git a/src/TinyString.cpp b/src/TinyString.cpp new file mode 100644 index 0000000..d114016 --- /dev/null +++ b/src/TinyString.cpp @@ -0,0 +1,160 @@ +#include "TinyString.h" +#include + +const char* TinyString::emptyString = ""; + +TinyString::TinyString(const char* buffer, uint16_t s) +{ + dup(buffer, s); +} + +TinyString::TinyString(int i, int base) +{ + reserve(sizeof(int)*8+1); + itoa(i, str, base); + size_ = strlen(str); + free_ -= size_; +} + +TinyString::TinyString(const TinyString& m) +{ + copy(m); +} + +TinyString& TinyString::operator=(const TinyString& m) +{ + copy(m); + return *this; +} + +TinyString& TinyString::operator+=(const char c) +{ + push_back(c); + return *this; +} + +TinyString& TinyString::operator +=(int i) +{ + reserve(size_ + sizeof(int)*3+1); + itoa(i, str + size_, 10); + int8_t sz = strlen(str+size_); + size_ += sz; + free_ -= sz; + return *this; +} + +void TinyString::concat(const char* buf, uint16_t len) +{ + reserve(size_ + len + 1); + strcpy(str + size_, buf); + size_ += len; + free_ -= len; +} + +void TinyString::push_back(const char c) +{ + reserve(size_+1, extent); + str[size_++] = c; + str[size_] = 0; + free_--; +} + +void TinyString::erase(uint16_t pos, uint16_t size) +{ + if (size == npos) size = size_; + if (pos > size_) return; + if (pos + size > size_) size = size_ - pos; + memmove(str+pos, str+pos+size, size_ - pos + 1); + if (size_ == size) + { + clear(); + } + else + { + size_ -= size; + free_ += size; + } +} + +void TinyString::clear() +{ + if (size_) + free(str); + str = const_cast(emptyString); // Dangerous str must be left untouched when size_ == 0 + size_ = 0; +} + +TinyString& TinyString::operator = (const char c) +{ + dup(&c, 1); + return *this; +} + +TinyString TinyString::substr(uint16_t pos, uint16_t size) +{ + if (size == npos) size = size_; + if (pos > size_) return TinyString(); + if (pos + size > size_) size = size_ - pos; + return TinyString(str+pos, size); +} + +bool TinyString::starts_with(const char* buf, uint16_t size) const +{ + const_iterator it(str); + while(size and it != end() and (*it == *buf)) + { + it++; + buf++; + size--; + } + return size == 0; +} + +int TinyString::compare(const char* s, uint16_t len) const +{ + if (len > size_) + return memcmp(str, s, size_ + 1); + else + return memcmp(str, s, len + 1); +} + +void TinyString::reserve(uint16_t sz, uint8_t extent) +{ + if (sz == 0) + { + clear(); + return; + } + if (size_ == 0) + { + free_ = sz + extent; + str = static_cast(malloc(sz + extent)); + return; + } + if ((sz > size_ + free_) or (extent > size_ + free_ - sz)) + { + free_ = sz + extent - size_; + str = static_cast(realloc(str, sz + extent)); + } +} + +void TinyString::collect() +{ + if (size_ > 0 and free_ > 1) + { + str = static_cast(realloc(str, size_ + 1)); + free_ = 1; + } +} + +void TinyString::dup(const char* buffer, uint16_t sz, uint8_t extent) +{ + reserve(sz + 1, extent); + memcpy(str, buffer, sz); + str[sz] = 0; + if (size_ > sz) + free_ += size_ - sz; + else + free_ -= sz - size_; + size_ = sz; +} diff --git a/src/TinyString.h b/src/TinyString.h new file mode 100644 index 0000000..8961407 --- /dev/null +++ b/src/TinyString.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include +#include + +#pragma pack(push, 1) + +class TinyString +{ + public: + using value_type = char; + static constexpr uint16_t npos = std::numeric_limits::max(); + + TinyString() = default; + TinyString(int, int base=10); + TinyString(const TinyString&); + TinyString(const char*, uint16_t size); + TinyString(const char* s) : TinyString(s, strlen(s)){}; + TinyString& operator= (const TinyString&); + ~TinyString() { clear(); } + + int compare(const char* buf, uint16_t len) const; + int compare(const char* buf) const { return compare(buf, strlen(buf)); } + + friend bool operator == (const TinyString& l, const TinyString& r) { return l.compare(r) == 0; } + + friend bool operator < (const TinyString& l, const TinyString& r) { return l.compare(r) <0; } + + const char* c_str() const { return str; } + uint16_t length() const { return size_; } + uint16_t size() const { return length(); } + void concat(const char* buf, uint16_t len); + + bool starts_with(const char* buf, uint16_t len) const; + bool starts_with(const char* buf) const { return starts_with(buf, strlen(buf)); } + + TinyString substr(uint16_t pos, uint16_t len = npos); + + char& operator[](uint16_t index) const { assert(index < size_); return str[index]; } + TinyString& operator = (const char c); + TinyString& operator +=(const char c); + TinyString& operator +=(const char* buf) { concat(buf, strlen(buf)); return *this; } + TinyString& operator +=(const TinyString& s) { concat(s.str, s.size_); return *this; } + TinyString& operator +=(int32_t); + + operator const char*() const { return str; } + + void reserve(uint16_t size) { reserve(size, 0); } + + void erase(uint16_t pos, uint16_t size = npos); + + void dup(const char* buffer, uint16_t size, uint8_t extent = 4); + + void push_back(const char c); + void clear(); + + using const_iterator = const char*; + using iterator = char*; + const_iterator cbegin() const { return begin(); } + const_iterator cend() const { return end(); } + iterator begin() const { return str; } + iterator end() const { return str + size_; } + + uint16_t capacity() const { return size_ + free_; } + void collect(); // Save memory + + private: + void reserve(uint16_t new_size, uint8_t extent); + void copy(const TinyString& t) { dup(t.str, t.size_); }; + + char* str = const_cast(emptyString); + uint16_t size_ = 0; // if size_ == 0 no allocation, but str = emptyString + uint8_t free_ = 0; // malloc(str) = size_ + free_ + + static const char* emptyString; + const uint8_t extent = 8; +}; + +#pragma pack(pop) diff --git a/tests/Makefile b/tests/Makefile index 87b61dd..5580c4d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,21 +1,29 @@ +SUB= + tests: @set -e; \ for i in $(SUB)*-tests/Makefile; do \ - $(MAKE) -C $$(dirname $$i) -j clean; \ echo '==== Making:' $$(dirname $$i); \ $(MAKE) -C $$(dirname $$i) -j; \ done +valgrind: + @set -e; \ + $(MAKE) tests; \ + for i in $(SUB)*-tests/Makefile; do \ + echo '==== Running:' $$(dirname $$i); \ + valgrind $$(dirname $$i)/$$(dirname $$i).out; \ + done + debugtest: @set -e; \ $(MAKE) clean; \ $(MAKE) -C debug-mode -j; \ debug-mode/debug-tests.out -runtests: debugtest - @$(MAKE) clean - @$(MAKE) tests +runtests: @set -e; \ + $(MAKE) tests; \ for i in $(SUB)*-tests/Makefile; do \ echo '==== Running:' $$(dirname $$i); \ $$(dirname $$i)/$$(dirname $$i).out; \ diff --git a/tests/classbind-tests/classbind-tests.ino b/tests/classbind-tests/classbind-tests.ino index cc53977..bd00c2d 100644 --- a/tests/classbind-tests/classbind-tests.ino +++ b/tests/classbind-tests/classbind-tests.ino @@ -340,7 +340,7 @@ void setup() { while(!Serial); */ - Serial.println("=============[ FAKE NETWORK TinyMqtt TESTS ]========================"); + Serial.println("=============[ TinyMqtt class-bind TESTS ]========================"); WiFi.mode(WIFI_STA); WiFi.begin("network", "password"); diff --git a/tests/local-tests/Makefile b/tests/local-tests/Makefile index e1c23a9..1fe9d48 100644 --- a/tests/local-tests/Makefile +++ b/tests/local-tests/Makefile @@ -1,10 +1,7 @@ # See https://github.com/bxparks/EpoxyDuino for documentation about this # Makefile to compile and run Arduino programs natively on Linux or MacOS. -EXTRA_CXXFLAGS=-g3 -O0 - -# Remove flto flag from EpoxyDuino (too many ) -CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics +include ../Makefile.opts APP_NAME := local-tests ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP TinyConsole diff --git a/tests/local-tests/local-tests.ino b/tests/local-tests/local-tests.ino index 75305b0..6e5139d 100644 --- a/tests/local-tests/local-tests.ino +++ b/tests/local-tests/local-tests.ino @@ -16,7 +16,7 @@ using namespace std; MqttBroker broker(1883); -std::map> published; // map[client_id] => map[topic] = count +std::map> published; // map[client_id] => map[topic] = count const char* lastPayload; size_t lastLength; @@ -40,7 +40,7 @@ test(local_client_should_unregister_when_destroyed) assertEqual(broker.clientsCount(), (size_t)0); } -test(local_client_do_not_disconnect_after_publishing) +test(local_client_do_not_disconnect_after_publishing_and_long_inactivity) { EpoxyTest::set_millis(0); MqttBroker broker(1883); @@ -56,11 +56,16 @@ test(local_client_do_not_disconnect_after_publishing) sender.publish("test", "value"); broker.loop(); - EpoxyTest::add_seconds(60); + EpoxyTest::add_seconds(600); client.loop(); sender.loop(); broker.loop(); + sender.publish("test", "value"); + broker.loop(); + sender.loop(); + broker.loop(); + assertEqual(broker.clientsCount(), (size_t)2); assertEqual(sender.connected(), true); assertEqual(client.connected(), true); diff --git a/tests/network-tests/Makefile b/tests/network-tests/Makefile index 94f4d89..f3ef323 100644 --- a/tests/network-tests/Makefile +++ b/tests/network-tests/Makefile @@ -1,10 +1,10 @@ # See https://github.com/bxparks/EpoxyDuino for documentation about this # Makefile to compile and run Arduino programs natively on Linux or MacOS. -EXTRA_CXXFLAGS=-g3 -O0 +include ../Makefile.opts # Remove flto flag from EpoxyDuino (too many ) -CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics +# CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics APP_NAME := network-tests ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP TinyConsole diff --git a/tests/network-tests/network-tests.ino b/tests/network-tests/network-tests.ino index b758eda..3e1ca00 100644 --- a/tests/network-tests/network-tests.ino +++ b/tests/network-tests/network-tests.ino @@ -78,7 +78,7 @@ String toString(const IPAddress& ip) MqttBroker broker(1883); -std::map> published; // map[client_id] => map[topic] = count +std::map> published; // map[client_id] => map[topic] = count char* lastPayload = nullptr; size_t lastLength; @@ -463,4 +463,5 @@ void loop() { aunit::TestRunner::run(); if (Serial.available()) ESP.reset(); + published.clear(); // Avoid crash in unit tests due to exit handlers } diff --git a/tests/nowifi-tests/Makefile b/tests/nowifi-tests/Makefile index 7a0ccfb..4ad4f03 100644 --- a/tests/nowifi-tests/Makefile +++ b/tests/nowifi-tests/Makefile @@ -1,10 +1,7 @@ # See https://github.com/bxparks/EpoxyDuino for documentation about this # Makefile to compile and run Arduino programs natively on Linux or MacOS. -EXTRA_CXXFLAGS=-g3 -O0 - -# Remove flto flag from EpoxyDuino (too many ) -CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics +include ../Makefile.opts APP_NAME := nowifi-tests ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP TinyConsole diff --git a/tests/nowifi-tests/nowifi-tests.ino b/tests/nowifi-tests/nowifi-tests.ino index 7bb06e9..bfd2084 100644 --- a/tests/nowifi-tests/nowifi-tests.ino +++ b/tests/nowifi-tests/nowifi-tests.ino @@ -15,7 +15,7 @@ using namespace std; MqttBroker broker(1883); -std::map> published; // map[client_id] => map[topic] = count +std::map> published; // map[client_id] => map[topic] = count char* lastPayload = nullptr; size_t lastLength; @@ -279,4 +279,5 @@ void loop() { aunit::TestRunner::run(); if (Serial.available()) ESP.reset(); + published.clear(); // Avoid crash at exit handlers } diff --git a/tests/string-indexer-tests/Makefile b/tests/string-indexer-tests/Makefile index a6c4dc0..a38285f 100644 --- a/tests/string-indexer-tests/Makefile +++ b/tests/string-indexer-tests/Makefile @@ -1,7 +1,7 @@ # See https://github.com/bxparks/EpoxyDuino for documentation about this # Makefile to compile and run Arduino programs natively on Linux or MacOS. -EXTRA_CXXFLAGS=-g3 -O0 +include ../Makefile.opts APP_NAME := string-indexer-tests ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsync TinyConsole diff --git a/tests/string-indexer-tests/string-indexer-tests.ino b/tests/string-indexer-tests/string-indexer-tests.ino index 5cb02f6..7ae54ec 100644 --- a/tests/string-indexer-tests/string-indexer-tests.ino +++ b/tests/string-indexer-tests/string-indexer-tests.ino @@ -84,7 +84,7 @@ test(indexer_indexed_operator_eq) test(indexer_get_string) { - std::string sone("one"); + TinyString sone("one"); IndexedString one(sone); assertTrue(sone==one.str()); diff --git a/tests/topic-tests/Makefile b/tests/topic-tests/Makefile index a15c2e6..0159cf4 100644 --- a/tests/topic-tests/Makefile +++ b/tests/topic-tests/Makefile @@ -1,7 +1,7 @@ # See https://github.com/bxparks/EpoxyDuino for documentation about this # Makefile to compile and run Arduino programs natively on Linux or MacOS. -EXTRA_CXXFLAGS=-g3 -O0 +include ../Makefile.opts APP_NAME := topic-tests ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsync TinyConsole