Compare commits

...

20 Commits
0.7.7 ... pr22

Author SHA1 Message Date
hsaturn
da3ec41d71 Update network-tests.ino
Remove useless code
2022-01-10 05:27:15 +01:00
hsaturn
5f7b4537c8 Enhance PR#22 2022-01-05 02:03:42 +01:00
hsaturn
cce4fecac6 Fix test local_client_should_unregister_when_destroyed 2022-01-05 02:02:18 +01:00
hsaturn
737e217172 fix type return value 2022-01-05 02:01:22 +01:00
hsaturn
8fe3517894 added packed attribute for enums 2022-01-05 02:00:02 +01:00
hsaturn
710503663a better type management 2022-01-05 01:59:16 +01:00
hsaturn
a5b8afc0bd Added some ifdef for debugging purposes 2022-01-05 01:56:45 +01:00
terror
f1d3a15498 added reply to message subscribe and unsubscribe 2021-09-25 15:44:09 +03:00
hsaturn
dfd5983715 Rewrite comments and added hudge payload test 2021-09-20 01:57:40 +02:00
hsaturn
4dcc6a6cf4 Minor changes 2021-09-19 12:40:04 +02:00
hsaturn
b58f3e3d67 Merge branch 'issue_2_broken_large_payloads' into main 2021-09-17 22:35:28 +02:00
hsaturn
a6b3540cb8 Fix issue_2 : Broken large payloads 2021-09-17 22:32:00 +02:00
hsaturn
ccbf42f81b howto added for building and running tests 2021-09-17 19:58:24 +02:00
hsaturn
d39c58d8f5 Fix issue_2 broken payload 2021-09-17 19:51:32 +02:00
hsaturn
36dde2c063 Fix for debugging for other platform than ESP 2021-08-09 11:24:27 +02:00
hsaturn
64a05bb60b Release 0.7.9 2021-08-09 10:47:18 +02:00
hsaturn
bb89fc5284 Release 0.7.8 2021-08-09 10:45:17 +02:00
hsaturn
c1fd1bc907 Remove pending length test 2021-08-09 10:44:19 +02:00
hsaturn
d919188eb0 Merge pull request #15 from bxparks/epoxyfix
Fix unit tests for EpoxyDuino
2021-08-09 10:39:26 +02:00
Brian Park
88c7d552cb Fix unit tests for EpoxyDuino 2021-08-08 19:37:04 -07:00
16 changed files with 555 additions and 120 deletions

View File

@@ -12,7 +12,7 @@ TinyMqtt is a small, fast and capable Mqtt Broker and Client for Esp8266 / Esp32
## Features
- Very (very !!) fast broker I saw it re-sent 1000 topics per second for two
- Very fast broker I saw it re-sent 1000 topics per second for two
clients that had subscribed (payload ~15 bytes ESP8266). No topic lost.
The max I've seen was 2k msg/s (1 client 1 subscription)
- Act as as a mqtt broker and/or a mqtt client

View File

@@ -6,7 +6,7 @@
"type": "git",
"url": "https://github.com/hsaturn/TinyMqtt.git"
},
"version": "0.7.7",
"version": "0.8.0",
"exclude": "",
"examples": "examples/*/*.ino",
"frameworks": "arduino",

View File

@@ -1,5 +1,5 @@
name=TinyMqtt
version=0.7.7
version=0.8.0
author=Francois BIOT, HSaturn, <hsaturn@gmail.com>
maintainer=Francois BIOT, HSaturn, <hsaturn@gmail.com>
sentence=A tiny broker and client library for MQTT messaging.

View File

@@ -38,11 +38,12 @@
4. Simple _FMT mechanism ala printf, but without the typeunsafetyness
and no internal buffers for replaceable stream printing
*/
#pragma once
#ifndef ARDUINO_STREAMING
#define ARDUINO_STREAMING
#if defined(ARDUINO) && ARDUINO >= 100
#if (defined(ARDUINO) && ARDUINO >= 100) || defined(EPOXY_DUINO)
#include "Arduino.h"
#else
#ifndef STREAMING_CONSOLE
@@ -154,7 +155,7 @@ template<typename T>
inline Print &operator <<(Print &obj, const _BASED<T> &arg)
{ obj.print(arg.val, arg.base); return obj; }
#if ARDUINO >= 18
#if ARDUINO >= 18 || defined(EPOXY_DUINO)
// Specialization for class _FLOAT
// Thanks to Michael Margolis for suggesting a way
// to accommodate Arduino 0018's floating point precision

View File

@@ -1,13 +1,9 @@
#include "TinyMqtt.h"
#include <sstream>
void outstring(const char* prefix, const char*p, uint16_t len)
{
return;
Serial << prefix << "='";
while(len--) Serial << (char)*p++;
Serial << '\'' << endl;
}
#ifdef EPOXY_DUINO
std::map<MqttMessage::Type, int> MqttClient::counters;
#endif
MqttBroker::MqttBroker(uint16_t port)
{
@@ -38,7 +34,11 @@ MqttClient::MqttClient(MqttBroker* parent, TcpClient* new_client)
#else
client = new WiFiClient(*new_client);
#endif
alive = millis()+5000; // client expires after 5s if no CONNECT msg
#ifdef EPOXY_DUINO
alive = millis()+500000;
#else
alive = millis()+5000; // TODO MAGIC client expires after 5s if no CONNECT msg
#endif
}
MqttClient::MqttClient(MqttBroker* parent, const std::string& id)
@@ -59,7 +59,7 @@ void MqttClient::close(bool bSendDisconnect)
{
debug("close " << id().c_str());
mqtt_connected = false;
if (client)
if (client) // connected to a remote broker
{
if (bSendDisconnect and client->connected())
{
@@ -72,13 +72,19 @@ void MqttClient::close(bool bSendDisconnect)
if (parent)
{
parent->removeClient(this);
parent=nullptr;
parent = nullptr;
}
}
void MqttClient::connect(MqttBroker* parentBroker)
{
close();
parent = parentBroker;
}
void MqttClient::connect(std::string broker, uint16_t port, uint16_t ka)
{
debug("cnx: closing");
debug("MqttClient::connect");
keep_alive = ka;
close();
if (client) delete client;
@@ -184,7 +190,7 @@ MqttError MqttBroker::subscribe(const Topic& topic, uint8_t qos)
return MqttNowhereToSend;
}
MqttError MqttBroker::publish(const MqttClient* source, const Topic& topic, const MqttMessage& msg) const
MqttError MqttBroker::publish(const MqttClient* source, const Topic& topic, MqttMessage& msg) const
{
MqttError retval = MqttOk;
@@ -244,7 +250,11 @@ void MqttClient::clientAlive(uint32_t more_seconds)
{
if (keep_alive)
{
#ifdef EPOXY_DUINO
alive=millis()+500000;
#else
alive=millis()+1000*(keep_alive+more_seconds);
#endif
}
else
alive=0;
@@ -389,16 +399,18 @@ MqttError MqttClient::sendTopic(const Topic& topic, MqttMessage::Type type, uint
return msg.sendTo(this);
}
long MqttClient::counter=0;
void MqttClient::processMessage(const MqttMessage* mesg)
void MqttClient::processMessage(MqttMessage* mesg)
{
counter++;
#ifdef TINY_MQTT_DEBUG
if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::Type::PingResp)
{
#ifdef NOT_ESP_CORE
Serial << "---> INCOMING " << _HEX(mesg->type()) << " client(" << (dbg_ptr)client << ':' << clientId << ") mem=" << " ESP.getFreeHeap() "<< endl;
#else
Serial << "---> INCOMING " << _HEX(mesg->type()) << " client(" << (dbg_ptr)client << ':' << clientId << ") mem=" << ESP.getFreeHeap() << endl;
#endif
// mesg->hexdump("Incoming");
mesg->hexdump("Incoming");
}
#endif
auto header = mesg->getVHeader();
@@ -406,7 +418,11 @@ if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::T
uint16_t len;
bool bclose=true;
switch(mesg->type() & 0XF0)
#ifdef EPOXY_DUINO
counters[mesg->type()]++;
#endif
switch(mesg->type())
{
case MqttMessage::Type::Connect:
if (mqtt_connected)
@@ -436,11 +452,9 @@ if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::T
if (mqtt_flags & FlagWill) // Will topic
{
mesg->getString(payload, len); // Will Topic
outstring("WillTopic", payload, len);
payload += len;
mesg->getString(payload, len); // Will Message
outstring("WillMessage", payload, len);
payload += len;
}
// FIXME forgetting credential is allowed (security hole)
@@ -457,7 +471,9 @@ if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::T
payload += len;
}
#ifdef TINY_MQTT_DEBUG
Serial << "Connected client:" << clientId.c_str() << ", keep alive=" << keep_alive << '.' << endl;
#endif
bclose = false;
mqtt_connected=true;
{
@@ -507,18 +523,25 @@ if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::T
payload = header+2;
debug("un/subscribe loop");
std::string qoss;
while(payload < mesg->end())
{
mesg->getString(payload, len); // Topic
debug( " topic (" << std::string(payload, len) << ')');
outstring(" un/subscribes", payload, len);
// subscribe(Topic(payload, len));
Topic topic(payload, len);
payload += len;
if ((mesg->type() & 0XF0) == MqttMessage::Type::Subscribe)
if (mesg->type() == MqttMessage::Type::Subscribe)
{
uint8_t qos = *payload++;
if (qos != 0) debug("Unsupported QOS" << qos << endl);
if (qos != 0)
{
debug("Unsupported QOS" << qos << endl);
qoss.push_back(0x80);
}
else
qoss.push_back(qos);
subscriptions.insert(topic);
}
else
@@ -530,7 +553,12 @@ if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::T
}
debug("end loop");
bclose = false;
// TODO SUBACK
MqttMessage ack(mesg->type() == MqttMessage::Type::Subscribe ? MqttMessage::Type::SubAck : MqttMessage::Type::UnSuback);
ack.add(header[0]);
ack.add(header[1]);
ack.add(qoss.c_str(), qoss.size(), false);
ack.sendTo(this);
}
break;
@@ -540,9 +568,12 @@ if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::T
break;
case MqttMessage::Type::Publish:
#ifdef TINY_MQTT_DEBUG
Serial << "publish " << mqtt_connected << '/' << (long) client << endl;
#endif
if (mqtt_connected or client == nullptr)
{
uint8_t qos = mesg->type() & 0x6;
uint8_t qos = mesg->flags();
payload = header;
mesg->getString(payload, len);
Topic published(payload, len);
@@ -554,8 +585,12 @@ if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::T
// TODO reset DUP
// TODO reset RETAIN
if (client==nullptr) // internal MqttClient receives publish
if (parent==nullptr or client==nullptr) // internal MqttClient receives publish
{
#ifdef TINY_MQTT_DEBUG
Serial << (isSubscribedTo(published) ? "not" : "") << " subscribed.\n";
Serial << "has " << (callback ? "" : "no ") << " callback.\n";
#endif
if (callback and isSubscribedTo(published))
{
callback(this, published, payload, len); // TODO send the real payload
@@ -584,10 +619,12 @@ if (mesg->type() != MqttMessage::Type::PingReq && mesg->type() != MqttMessage::T
};
if (bclose)
{
#ifdef TINY_MQTT_DEBUG
Serial << "*************** Error msg 0x" << _HEX(mesg->type());
mesg->hexdump("-------ERROR ------");
dump();
Serial << endl;
#endif
close();
}
else
@@ -610,6 +647,7 @@ MqttError MqttClient::publish(const Topic& topic, const char* payload, size_t pa
msg.add(topic);
msg.add(payload, pay_length, false);
msg.complete();
if (parent)
{
return parent->publish(this, topic, msg);
@@ -621,7 +659,7 @@ MqttError MqttClient::publish(const Topic& topic, const char* payload, size_t pa
}
// republish a received publish if it matches any in subscriptions
MqttError MqttClient::publishIfSubscribed(const Topic& topic, const MqttMessage& msg)
MqttError MqttClient::publishIfSubscribed(const Topic& topic, MqttMessage& msg)
{
MqttError retval=MqttOk;
@@ -633,6 +671,10 @@ MqttError MqttClient::publishIfSubscribed(const Topic& topic, const MqttMessage&
else
{
processMessage(&msg);
#ifdef TINY_MQTT_DEBUG
Serial << "Should call the callback ?\n";
#endif
// callback(this, topic, nullptr, 0); // TODO Payload
}
}
@@ -661,29 +703,23 @@ void MqttMessage::incoming(char in_byte)
switch(state)
{
case FixedHeader:
size=0;
size=MaxBufferLength;
state = Length;
break;
case Length:
if (size==0)
if (size==MaxBufferLength)
size = in_byte & 0x7F;
else if (size<128)
size += static_cast<uint16_t>(in_byte & 0x7F)<<7;
else
state = Error; // Really don't want to handle msg with length > 16k
size += static_cast<uint16_t>(in_byte & 0x7F)<<7;
if (size > MaxBufferLength)
{
state = Error;
}
else if ((in_byte & 0x80) == 0)
{
vheader = buffer.length();
if (size==0)
state = Complete;
else if (size > 500) // TODO magic
{
state = Error;
}
else
{
buffer.reserve(size);
@@ -705,12 +741,14 @@ void MqttMessage::incoming(char in_byte)
break;
case Complete:
default:
#ifdef TINY_MQTT_DEBUG
Serial << "Spurious " << _HEX(in_byte) << endl;
hexdump("spurious");
#endif
reset();
break;
}
if (buffer.length() > MaxBufferLength) // TODO magic 256 ?
if (buffer.length() > MaxBufferLength)
{
debug("Too long " << state);
reset();
@@ -721,36 +759,33 @@ void MqttMessage::add(const char* p, size_t len, bool addLength)
{
if (addLength)
{
buffer.reserve(buffer.length()+addLength+2);
buffer.reserve(buffer.length()+2);
incoming(len>>8);
incoming(len & 0xFF);
}
while(len--) incoming(*p++);
}
void MqttMessage::encodeLength(char* msb, int length) const
void MqttMessage::encodeLength()
{
do
if (state != Complete)
{
uint8_t encoded(length & 0x7F);
length >>=7;
if (length) encoded |= 0x80;
*msb++ = encoded;
} while (length);
int length = buffer.size()-3; // 3 = 1 byte for header + 2 bytes for pre-reserved length field.
buffer[1] = 0x80 | (length & 0x7F);
buffer[2] = (length >> 7);
vheader = 3;
// We could check that buffer[2] < 128 (end of length encoding)
state = Complete;
}
};
void MqttMessage::complete()
{
encodeLength(&buffer[1], buffer.size()-2);
state = Complete;
}
MqttError MqttMessage::sendTo(MqttClient* client) const
MqttError MqttMessage::sendTo(MqttClient* client)
{
if (buffer.size())
{
debug("sending " << buffer.size() << " bytes");
encodeLength(&buffer[1], buffer.size()-2);
encodeLength();
// hexdump("snd");
client->write(&buffer[0], buffer.size());
}
@@ -764,6 +799,8 @@ MqttError MqttMessage::sendTo(MqttClient* client) const
void MqttMessage::hexdump(const char* prefix) const
{
(void)prefix;
#ifdef TINY_MQTT_DEBUG
uint16_t addr=0;
const int bytes_per_row = 8;
const char* hex_to_str = " | ";
@@ -799,4 +836,5 @@ void MqttMessage::hexdump(const char* prefix) const
}
Serial << endl;
#endif
}

View File

@@ -29,7 +29,7 @@
// #define TINY_MQTT_DEBUG
#ifdef TINY_MQTT_DEBUG
#define debug(what) { Serial << __LINE__ << ' ' << what << endl; delay(100); }
#define debug(what) { Serial << (int)__LINE__ << ' ' << what << endl; delay(100); }
#else
#define debug(what) {}
#endif
@@ -42,7 +42,7 @@
using TcpServer = WiFiServer;
#endif
enum MqttError
enum __attribute__((packed)) MqttError
{
MqttOk = 0,
MqttNowhereToSend=1,
@@ -64,9 +64,9 @@ class Topic : public IndexedString
class MqttClient;
class MqttMessage
{
const uint16_t MaxBufferLength = 4096; //hard limit: 16k
const uint16_t MaxBufferLength = 4096; //hard limit: 16k due to size decoding
public:
enum Type
enum __attribute__((packed)) Type
{
Unknown = 0,
Connect = 0x10,
@@ -81,7 +81,7 @@ class MqttMessage
PingResp = 0xD0,
Disconnect = 0xE0
};
enum State
enum __attribute__((packed)) State
{
FixedHeader=0,
Length=1,
@@ -101,8 +101,7 @@ class MqttMessage
void add(const Topic& t) { add(t.str()); }
const char* end() const { return &buffer[0]+buffer.size(); }
const char* getVHeader() const { return &buffer[vheader]; }
uint16_t length() const { return buffer.size(); }
void complete();
void complete() { encodeLength(); }
void reset();
@@ -112,24 +111,27 @@ class MqttMessage
Type type() const
{
return state == Complete ? static_cast<Type>(buffer[0]) : Unknown;
return state == Complete ? static_cast<Type>(buffer[0] & 0xF0) : Unknown;
}
uint8_t flags() const { return static_cast<uint8_t>(buffer[0] & 0x0F); }
void create(Type type)
{
buffer=(char)type;
buffer+='\0'; // reserved for msg length
vheader=2;
buffer=(decltype(buffer)::value_type)type;
buffer+='\0'; // reserved for msg length byte 1/2
buffer+='\0'; // reserved for msg length byte 2/2 (fixed)
vheader=3; // Should never change
size=0;
state=Create;
}
MqttError sendTo(MqttClient*) const;
MqttError sendTo(MqttClient*);
void hexdump(const char* prefix=nullptr) const;
private:
void encodeLength(char* msb, int length) const;
void encodeLength();
mutable std::string buffer; // mutable -> sendTo()
std::string buffer;
uint8_t vheader;
uint16_t size; // bytes left to receive
State state;
@@ -139,7 +141,7 @@ class MqttBroker;
class MqttClient
{
using CallBack = void (*)(const MqttClient* source, const Topic& topic, const char* payload, size_t payload_length);
enum Flags
enum __attribute__((packed)) Flags
{
FlagUserName = 128,
FlagPassword = 64,
@@ -160,6 +162,8 @@ class MqttClient
void connect(MqttBroker* parent);
void connect(std::string broker, uint16_t port, uint16_t keep_alive = 10);
// TODO it seems that connected returns true in tcp mode even if
// no negociation occured (only if tcp link is established)
bool connected() { return
(parent!=nullptr and client==nullptr) or
(client and client->connected()); }
@@ -172,7 +176,14 @@ class MqttClient
/** Should be called in main loop() */
void loop();
void close(bool bSendDisconnect=true);
void setCallback(CallBack fun) {callback=fun; };
void setCallback(CallBack fun)
{
callback=fun;
#ifdef TINY_MQTT_DEBUG
Serial << "Callback set to " << (long)fun << endl;
if (callback) callback(this, "test/topic", "value", 5);
#endif
};
// Publish from client to the world
MqttError publish(const Topic&, const char* payload, size_t pay_length);
@@ -191,6 +202,8 @@ class MqttClient
void dump(std::string indent="")
{
(void)indent;
#ifdef TINY_MQTT_DEBUG
uint32_t ms=millis();
Serial << indent << "+-- " << '\'' << clientId.c_str() << "' " << (connected() ? " ON " : " OFF");
Serial << ", alive=" << alive << '/' << ms << ", ka=" << keep_alive << ' ';
@@ -200,6 +213,7 @@ class MqttClient
bool c = false;
Serial << " [";
for(auto s: subscriptions)
(void)indent;
{
if (c) Serial << ", ";
Serial << s.str().c_str();
@@ -208,12 +222,16 @@ class MqttClient
Serial << ']';
}
Serial << endl;
#endif
}
/** Count the number of messages that have been sent **/
static long counter;
#ifdef EPOXY_DUINO
static std::map<MqttMessage::Type, int> counters; // Number of processed messages
#endif
private:
// event when tcp/ip link established (real or fake)
static void onConnect(void * client_ptr, TcpClient*);
#ifdef TCP_ASYNC
static void onData(void* client_ptr, TcpClient*, void* data, size_t len);
@@ -224,10 +242,10 @@ class MqttClient
friend class MqttBroker;
MqttClient(MqttBroker* parent, TcpClient* client);
// republish a received publish if topic matches any in subscriptions
MqttError publishIfSubscribed(const Topic& topic, const MqttMessage& msg);
MqttError publishIfSubscribed(const Topic& topic, MqttMessage& msg);
void clientAlive(uint32_t more_seconds);
void processMessage(const MqttMessage* message);
void processMessage(MqttMessage* message);
bool mqtt_connected = false;
char mqtt_flags;
@@ -240,7 +258,7 @@ class MqttClient
// (this is the case when MqttBroker isn't used except here)
MqttBroker* parent=nullptr; // connection to local broker
TcpClient* client=nullptr; // connection to mqtt client or to remote broker
TcpClient* client=nullptr; // connection to remote broker
std::set<Topic> subscriptions;
std::string clientId;
CallBack callback = nullptr;
@@ -248,7 +266,7 @@ class MqttClient
class MqttBroker
{
enum State
enum __attribute__((packed)) State
{
Disconnected, // Also the initial state
Connecting, // connect and sends a fake publish to avoid circular cnx
@@ -284,7 +302,7 @@ class MqttBroker
{ return compareString(auth_password, password, len); }
MqttError publish(const MqttClient* source, const Topic& topic, const MqttMessage& msg) const;
MqttError publish(const MqttClient* source, const Topic& topic, MqttMessage& msg) const;
MqttError subscribe(const Topic& topic, uint8_t qos);

7
tests/howto Normal file
View File

@@ -0,0 +1,7 @@
cd TinyMqtt/tests/../..
git clone https://github.com/hsaturn/EspMock.git
git clone https://github.com/bxparks/AUnit.git
git clone https://github.com/bxparks/EpoxyDuino.git
cd TinyMqtt/tests
make
make runtests

View File

@@ -1,6 +1,11 @@
# 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 <optimized out>)
CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics
APP_NAME := local-tests
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP
ARDUINO_LIB_DIRS := ../../../EspMock/libraries

View File

@@ -1,3 +1,4 @@
#include <Arduino.h>
#include <AUnit.h>
#include <TinyMqtt.h>
#include <map>
@@ -30,12 +31,10 @@ void onPublish(const MqttClient* srce, const Topic& topic, const char* payload,
test(local_client_should_unregister_when_destroyed)
{
return;
assertEqual(broker.clientsCount(), (size_t)0);
{
MqttClient client;
assertEqual(broker.clientsCount(), (size_t)0); // Ensure client is not yet connected
client.connect("127.0.0.1", 1883);
MqttClient client(&broker);
assertEqual(broker.clientsCount(), (size_t)1); // Ensure client is now connected
}
assertEqual(broker.clientsCount(), (size_t)0);
@@ -67,8 +66,8 @@ test(local_publish_should_be_dispatched)
publisher.publish("a/c");
assertEqual(published.size(), (size_t)1); // 1 client has received something
assertTrue(published[""]["a/b"] == 1);
assertTrue(published[""]["a/c"] == 2);
assertEqual(published[""]["a/b"], 1);
assertEqual(published[""]["a/c"], 2);
}
test(local_publish_should_be_dispatched_to_local_clients)
@@ -90,10 +89,10 @@ test(local_publish_should_be_dispatched_to_local_clients)
publisher.publish("a/c");
assertEqual(published.size(), (size_t)2); // 2 clients have received something
assertTrue(published["A"]["a/b"] == 1);
assertTrue(published["A"]["a/c"] == 1);
assertTrue(published["B"]["a/b"] == 1);
assertTrue(published["B"]["a/c"] == 0);
assertEqual(published["A"]["a/b"], 1);
assertEqual(published["A"]["a/c"], 1);
assertEqual(published["B"]["a/b"], 1);
assertEqual(published["B"]["a/c"], 0);
}
test(local_unsubscribe)
@@ -113,7 +112,7 @@ test(local_unsubscribe)
publisher.publish("a/b");
publisher.publish("a/b");
assertTrue(published[""]["a/b"] == 1); // Only one publish has been received
assertEqual(published[""]["a/b"], 1); // Only one publish has been received
}
test(local_nocallback_when_destroyed)
@@ -142,7 +141,7 @@ void setup() {
Serial.begin(115200);
while(!Serial);
Serial.println("=============[ NO WIFI CONNECTION TinyMqtt TESTS ]========================");
Serial.println("=============[ LOCAL TinyMqtt TESTS ]========================");
}
void loop() {

View File

@@ -0,0 +1,13 @@
# 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 <optimized out>)
CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics
APP_NAME := network-tests
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP
ARDUINO_LIB_DIRS := ../../../EspMock/libraries
EPOXY_CORE := EPOXY_CORE_ESP8266
include ../../../EpoxyDuino/EpoxyDuino.mk

View File

@@ -0,0 +1,331 @@
#include <Arduino.h>
#include <AUnit.h>
#include <TinyMqtt.h>
#include <map>
/**
* TinyMqtt network unit tests.
*
* No wifi connection unit tests.
* Checks with a local broker. Clients must connect to the local broker
**/
using namespace std;
String toString(const IPAddress& ip)
{
return String(ip[0])+'.'+String(ip[1])+'.'+String(ip[2])+'.'+String(ip[3]);
}
MqttBroker broker(1883);
std::map<std::string, std::map<Topic, int>> published; // map[client_id] => map[topic] = count
char* lastPayload = nullptr;
size_t lastLength;
void start_servers(int n, bool early_accept = true)
{
ESP8266WiFiClass::resetInstances();
ESP8266WiFiClass::earlyAccept = early_accept;
while(n)
{
ESP8266WiFiClass::selectInstance(n--);
WiFi.mode(WIFI_STA);
WiFi.begin("fake_ssid", "fake_pwd");
}
}
void onPublish(const MqttClient* srce, const Topic& topic, const char* payload, size_t length)
{
if (srce)
published[srce->id()][topic]++;
if (lastPayload) free(lastPayload);
lastPayload = strdup(payload);
lastLength = length;
}
test(network_single_broker_begin)
{
assertEqual(WiFi.status(), WL_CONNECTED);
MqttBroker broker(1883);
broker.begin();
// TODO Nothing is tested here !
}
test(suback)
{
start_servers(2, true);
assertEqual(WiFi.status(), WL_CONNECTED);
MqttBroker broker(1883);
broker.begin();
IPAddress broker_ip = WiFi.localIP();
ESP8266WiFiClass::selectInstance(2);
MqttClient client;
client.connect(broker_ip.toString().c_str(), 1883);
broker.loop();
assertTrue(broker.clientsCount() == 1);
assertTrue(client.connected());
MqttClient::counters[MqttMessage::Type::SubAck] = 0;
client.subscribe("a/b");
// TODO how to avoid these loops ???
broker.loop();
client.loop();
assertEqual(MqttClient::counters[MqttMessage::Type::SubAck], 1);
}
test(network_client_to_broker_connexion)
{
start_servers(2, true);
assertEqual(WiFi.status(), WL_CONNECTED);
MqttBroker broker(1883);
broker.begin();
IPAddress broker_ip = WiFi.localIP();
ESP8266WiFiClass::selectInstance(2);
MqttClient client;
client.connect(broker_ip.toString().c_str(), 1883);
broker.loop();
assertTrue(broker.clientsCount() == 1);
assertTrue(client.connected());
}
test(network_one_client_one_broker_publish_and_subscribe_through_network)
{
start_servers(2, true);
published.clear();
assertEqual(WiFi.status(), WL_CONNECTED);
MqttBroker broker(1883);
broker.begin();
IPAddress ip_broker = WiFi.localIP();
// We have a 2nd ESP in order to test through wifi (opposed to local)
ESP8266WiFiClass::selectInstance(2);
MqttClient client;
client.connect(ip_broker.toString().c_str(), 1883);
broker.loop();
assertTrue(client.connected());
client.setCallback(onPublish);
client.subscribe("a/b");
client.publish("a/b", "ab");
for (int i =0; i<2; i++)
{
client.loop();
broker.loop();
}
assertEqual(published.size(), (size_t)1);
assertEqual((int)lastLength, (int)2); // sizeof(ab)
}
test(network_one_client_one_broker_hudge_publish_and_subscribe_through_network)
{
start_servers(2, true);
published.clear();
assertEqual(WiFi.status(), WL_CONNECTED);
MqttBroker broker(1883);
broker.begin();
IPAddress ip_broker = WiFi.localIP();
// We have a 2nd ESP in order to test through wifi (opposed to local)
ESP8266WiFiClass::selectInstance(2);
MqttClient client;
client.connect(ip_broker.toString().c_str(), 1883);
broker.loop();
assertTrue(client.connected());
std::string sent;
for(int i=0; i<200; i++)
sent += char('0'+i%10);
client.setCallback(onPublish);
client.subscribe("a/b");
client.publish("a/b", sent.c_str());
for (int i =0; i<2; i++)
{
client.loop();
broker.loop();
}
assertEqual(published.size(), (size_t)1);
assertEqual((unsigned int)lastLength, (unsigned int)sent.size());
}
test(network_client_should_unregister_when_destroyed)
{
assertEqual(broker.clientsCount(), (size_t)0);
{
MqttClient client(&broker);
assertEqual(broker.clientsCount(), (size_t)1);
}
assertEqual(broker.clientsCount(), (size_t)0);
}
// THESE TESTS ARE IN LOCAL MODE
// WE HAVE TO CONVERT THEM TO WIFI MODE (pass through virtual TCP link)
test(network_connect)
{
assertEqual(broker.clientsCount(), (size_t)0);
MqttClient client(&broker);
assertTrue(client.connected());
assertEqual(broker.clientsCount(), (size_t)1);
}
test(network_publish_should_be_dispatched)
{
published.clear();
assertEqual(broker.clientsCount(), (size_t)0);
MqttClient subscriber(&broker);
subscriber.subscribe("a/b");
subscriber.subscribe("a/c");
subscriber.setCallback(onPublish);
MqttClient publisher(&broker);
publisher.publish("a/b");
publisher.publish("a/c");
publisher.publish("a/c");
assertEqual(published.size(), (size_t)1); // 1 client has received something
assertEqual(published[""]["a/b"], 1);
assertEqual(published[""]["a/c"], 2);
}
test(network_publish_should_be_dispatched_to_clients)
{
published.clear();
assertEqual(broker.clientsCount(), (size_t)0);
MqttClient subscriber_a(&broker, "A");
subscriber_a.setCallback(onPublish);
subscriber_a.subscribe("a/b");
subscriber_a.subscribe("a/c");
MqttClient subscriber_b(&broker, "B");
subscriber_b.setCallback(onPublish);
subscriber_b.subscribe("a/b");
MqttClient publisher(&broker);
publisher.publish("a/b"); // A and B should receive this
publisher.publish("a/c"); // A should receive this
assertEqual(published.size(), (size_t)2); // 2 clients have received something
assertEqual(published["A"]["a/b"], 1);
assertEqual(published["A"]["a/c"], 1);
assertEqual(published["B"]["a/b"], 1);
assertEqual(published["B"]["a/c"], 0);
}
test(network_unsubscribe)
{
published.clear();
assertEqual(broker.clientsCount(), (size_t)0);
MqttClient subscriber(&broker);
subscriber.setCallback(onPublish);
subscriber.subscribe("a/b");
MqttClient publisher(&broker);
publisher.publish("a/b"); // This publish is received
subscriber.unsubscribe("a/b");
publisher.publish("a/b"); // Those one, no (unsubscribed)
publisher.publish("a/b");
assertEqual(published[""]["a/b"], 1); // Only one publish has been received
}
test(network_nocallback_when_destroyed)
{
published.clear();
assertEqual(broker.clientsCount(), (size_t)0);
MqttClient publisher(&broker);
{
MqttClient subscriber(&broker);
subscriber.setCallback(onPublish);
subscriber.subscribe("a/b");
publisher.publish("a/b");
}
publisher.publish("a/b");
assertEqual(published.size(), (size_t)1); // Only one publish has been received
}
test(network_small_payload)
{
published.clear();
const char* payload="abcd";
MqttClient subscriber(&broker);
subscriber.setCallback(onPublish);
subscriber.subscribe("a/b");
MqttClient publisher(&broker);
publisher.publish("a/b", payload, strlen(payload)); // This publish is received
// coming from MqttClient::publish(...)
assertEqual(payload, lastPayload);
assertEqual(lastLength, (size_t)4);
}
test(network_hudge_payload)
{
const char* payload="This payload is hudge, just because its length exceeds 127. Thus when encoding length, we have to encode it on two bytes at min. This should not prevent the message from being encoded and decoded successfully !";
MqttClient subscriber(&broker);
subscriber.setCallback(onPublish);
subscriber.subscribe("a/b");
MqttClient publisher(&broker);
publisher.publish("a/b", payload); // This publish is received
// onPublish should have filled lastPayload and lastLength
assertEqual(payload, lastPayload);
assertEqual(lastLength, strlen(payload));
assertEqual(strcmp(payload, lastPayload), 0);
}
//----------------------------------------------------------------------------
// setup() and loop()
void setup() {
/* delay(1000);
Serial.begin(115200);
while(!Serial);
*/
Serial.println("=============[ FAKE NETWORK TinyMqtt TESTS ]========================");
WiFi.mode(WIFI_STA);
WiFi.begin("network", "password");
}
void loop() {
aunit::TestRunner::run();
if (Serial.available()) ESP.reset();
}

View File

@@ -1,6 +1,11 @@
# 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 <optimized out>)
CXXFLAGS = -Wextra -Wall -std=gnu++11 -fno-exceptions -fno-threadsafe-statics
APP_NAME := nowifi-tests
ARDUINO_LIBS := AUnit AceCommon AceTime TinyMqtt EspMock ESP8266WiFi ESPAsyncTCP
ARDUINO_LIB_DIRS := ../../../EspMock/libraries

View File

@@ -1,3 +1,4 @@
#include <Arduino.h>
#include <AUnit.h>
#include <TinyMqtt.h>
#include <map>
@@ -131,7 +132,7 @@ test(nowifi_nocallback_when_destroyed)
assertEqual(published.size(), (size_t)1); // Only one publish has been received
}
test(nowifi_payload_nullptr)
test(nowifi_small_payload)
{
published.clear();
@@ -149,6 +150,22 @@ test(nowifi_payload_nullptr)
assertEqual(lastLength, (size_t)4);
}
test(nowifi_hudge_payload)
{
const char* payload="This payload is hudge, just because its length exceeds 127. Thus when encoding length, we have to encode it on two bytes at min. This should not prevent the message from being encoded and decoded successfully !";
MqttClient subscriber(&broker);
subscriber.setCallback(onPublish);
subscriber.subscribe("a/b");
MqttClient publisher(&broker);
publisher.publish("a/b", payload); // This publish is received
// onPublish should have filled lastPayload and lastLength
assertEqual(payload, lastPayload);
assertEqual(lastLength, strlen(payload));
}
//----------------------------------------------------------------------------
// setup() and loop()
void setup() {

View File

@@ -1,3 +1,4 @@
#include <Arduino.h>
#include <AUnit.h>
#include <StringIndexer.h>
#include <map>