12 Commits
v2.0 ... v2.2

25 changed files with 460 additions and 205 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tests/bin

7
.travis.yml Normal file
View File

@ -0,0 +1,7 @@
sudo: false
language: cpp
compiler:
- g++
script: cd tests && make && make test
os:
- linux

View File

@ -1,3 +1,9 @@
2.2
* Change code layout to match Arduino Library reqs
2.1
* Add MAX_TRANSFER_SIZE def to chunk messages if needed
* Reject topic/payloads that exceed MQTT_MAX_PACKET_SIZE
2.0 2.0
* Add (and default to) MQTT 3.1.1 support * Add (and default to) MQTT 3.1.1 support
* Fix PROGMEM handling for Intel Galileo/ESP8266 * Fix PROGMEM handling for Intel Galileo/ESP8266

View File

@ -31,7 +31,8 @@ boards and shields, including:
- Arduino Ethernet Shield - Arduino Ethernet Shield
- Arduino YUN use the included `YunClient` in place of `EthernetClient`, and - Arduino YUN use the included `YunClient` in place of `EthernetClient`, and
be sure to do a `Bridge.begin()` first be sure to do a `Bridge.begin()` first
- Arduino WiFi Shield - Arduino WiFi Shield - if you want to send packets > 90 bytes with this shield,
enable the `MQTT_MAX_TRANSFER_SIZE` define in `PubSubClient.h`.
- Sparkfun WiFly Shield when used with [this library](https://github.com/dpslwk/WiFly) - Sparkfun WiFly Shield when used with [this library](https://github.com/dpslwk/WiFly)
- Intel Galileo/Edison - Intel Galileo/Edison
- ESP8266 - ESP8266

View File

@ -1,5 +1,5 @@
name=PubSubClient name=PubSubClient
version=2.0 version=2.2
author=Nick O'Leary <nick.oleary@gmail.com> author=Nick O'Leary <nick.oleary@gmail.com>
maintainer=Nick O'Leary <nick.oleary@gmail.com> maintainer=Nick O'Leary <nick.oleary@gmail.com>
sentence=A client library for MQTT messaging. sentence=A client library for MQTT messaging.

View File

@ -202,6 +202,7 @@ boolean PubSubClient::connect(const char *id, const char *user, const char *pass
} }
return false; return false;
} }
return true;
} }
uint8_t PubSubClient::readByte() { uint8_t PubSubClient::readByte() {
@ -334,6 +335,10 @@ boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigne
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
if (connected()) { if (connected()) {
if (MQTT_MAX_PACKET_SIZE < 5 + 2+strlen(topic) + plength) {
// Too long
return false;
}
// Leave room in the buffer for header and variable length field // Leave room in the buffer for header and variable length field
uint16_t length = 5; uint16_t length = 5;
length = writeString(topic,buffer,length); length = writeString(topic,buffer,length);
@ -416,10 +421,25 @@ boolean PubSubClient::write(uint8_t header, uint8_t* buf, uint16_t length) {
for (int i=0;i<llen;i++) { for (int i=0;i<llen;i++) {
buf[5-llen+i] = lenBuf[i]; buf[5-llen+i] = lenBuf[i];
} }
rc = _client->write(buf+(4-llen),length+1+llen);
#ifdef MQTT_MAX_TRANSFER_SIZE
uint8_t* writeBuf = buf+(4-llen);
uint8_t bytesRemaining = length+1+llen;
uint8_t bytesToWrite;
boolean result = true;
while((bytesRemaining > 0) && result) {
bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining;
rc = _client->write(writeBuf,bytesToWrite);
result = (rc == bytesToWrite);
bytesRemaining -= rc;
writeBuf += rc;
}
return result;
#else
rc = _client->write(buf+(4-llen),length+1+llen);
lastOutActivity = millis(); lastOutActivity = millis();
return (rc == 1+llen+length); return (rc == 1+llen+length);
#endif
} }
boolean PubSubClient::subscribe(const char* topic) { boolean PubSubClient::subscribe(const char* topic) {
@ -427,9 +447,13 @@ boolean PubSubClient::subscribe(const char* topic) {
} }
boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
if (qos < 0 || qos > 1) if (qos < 0 || qos > 1) {
return false; return false;
}
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) { if (connected()) {
// Leave room in the buffer for header and variable length field // Leave room in the buffer for header and variable length field
uint16_t length = 5; uint16_t length = 5;
@ -447,6 +471,10 @@ boolean PubSubClient::subscribe(const char* topic, uint8_t qos) {
} }
boolean PubSubClient::unsubscribe(const char* topic) { boolean PubSubClient::unsubscribe(const char* topic) {
if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) {
// Too long
return false;
}
if (connected()) { if (connected()) {
uint16_t length = 5; uint16_t length = 5;
nextMsgId++; nextMsgId++;
@ -493,10 +521,11 @@ boolean PubSubClient::connected() {
if (!rc) { if (!rc) {
if (this->_state == MQTT_CONNECTED) { if (this->_state == MQTT_CONNECTED) {
this->_state = MQTT_CONNECTION_LOST; this->_state = MQTT_CONNECTION_LOST;
} _client->flush();
_client->stop(); _client->stop();
} }
} }
}
return rc; return rc;
} }

View File

@ -19,13 +19,17 @@
//#define MQTT_VERSION MQTT_VERSION_3_1 //#define MQTT_VERSION MQTT_VERSION_3_1
#define MQTT_VERSION MQTT_VERSION_3_1_1 #define MQTT_VERSION MQTT_VERSION_3_1_1
// MQTT_MAX_PACKET_SIZE : Maximum packet size // MQTT_MAX_PACKET_SIZE : Maximum packet size
#define MQTT_MAX_PACKET_SIZE 128 #define MQTT_MAX_PACKET_SIZE 128
// MQTT_KEEPALIVE : keepAlive interval in Seconds // MQTT_KEEPALIVE : keepAlive interval in Seconds
#define MQTT_KEEPALIVE 15 #define MQTT_KEEPALIVE 15
// MQTT_MAX_TRANSFER_SIZE : limit how much data is passed to the network client
// in each write call. Needed for the Arduino Wifi Shield. Leave undefined to
// pass the entire MQTT packet in each write call.
//#define MQTT_MAX_TRANSFER_SIZE 80
// Possible values for client.state() // Possible values for client.state()
#define MQTT_CONNECTION_TIMEOUT -4 #define MQTT_CONNECTION_TIMEOUT -4
#define MQTT_CONNECTION_LOST -3 #define MQTT_CONNECTION_LOST -3

View File

@ -4,9 +4,9 @@ TEST_SRC=$(wildcard ${SRC_PATH}/*_spec.cpp)
TEST_BIN= $(TEST_SRC:${SRC_PATH}/%.cpp=${OUT_PATH}/%) TEST_BIN= $(TEST_SRC:${SRC_PATH}/%.cpp=${OUT_PATH}/%)
VPATH=${SRC_PATH} VPATH=${SRC_PATH}
SHIM_FILES=${SRC_PATH}/lib/*.cpp SHIM_FILES=${SRC_PATH}/lib/*.cpp
PSC_FILE=../PubSubClient/PubSubClient.cpp PSC_FILE=../src/PubSubClient.cpp
CC=g++ CC=g++
CFLAGS=-I${SRC_PATH}/lib -I../PubSubClient CFLAGS=-I${SRC_PATH}/lib -I../src
all: $(TEST_BIN) all: $(TEST_BIN)
@ -16,3 +16,10 @@ ${OUT_PATH}/%: ${SRC_PATH}/%.cpp ${PSC_FILE} ${SHIM_FILES}
clean: clean:
@rm -rf ${OUT_PATH} @rm -rf ${OUT_PATH}
test:
@bin/connect_spec
@bin/publish_spec
@bin/receive_spec
@bin/subscribe_spec
@bin/keepalive_spec

View File

@ -19,6 +19,8 @@ int test_connect_fails_no_network() {
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1"); int rc = client.connect((char*)"client_test1");
IS_FALSE(rc); IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == MQTT_CONNECT_FAILED);
END_IT END_IT
} }
@ -29,6 +31,8 @@ int test_connect_fails_on_no_response() {
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1"); int rc = client.connect((char*)"client_test1");
IS_FALSE(rc); IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
END_IT END_IT
} }
@ -39,17 +43,23 @@ int test_connect_properly_formatted() {
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
byte expectServer[] = { 172, 16, 0, 2 }; byte expectServer[] = { 172, 16, 0, 2 };
shimClient.expectConnect(expectServer,1883); shimClient.expectConnect(expectServer,1883);
byte connect[] = {0x10,0x1a,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,28); shimClient.expect(connect,26);
shimClient.respond(connack,4); shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
int state = client.state();
IS_TRUE(state == MQTT_DISCONNECTED);
int rc = client.connect((char*)"client_test1"); int rc = client.connect((char*)"client_test1");
IS_TRUE(rc); IS_TRUE(rc);
IS_FALSE(shimClient.error()); IS_FALSE(shimClient.error());
state = client.state();
IS_TRUE(state == MQTT_CONNECTED);
END_IT END_IT
} }
@ -81,6 +91,10 @@ int test_connect_fails_on_bad_rc() {
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1"); int rc = client.connect((char*)"client_test1");
IS_FALSE(rc); IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == 0x01);
END_IT END_IT
} }
@ -89,9 +103,9 @@ int test_connect_accepts_username_password() {
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
byte connect[] = { 0x10,0x26,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x4,0x70,0x61,0x73,0x73}; byte connect[] = { 0x10,0x24,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xc2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x4,0x70,0x61,0x73,0x73};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x28); shimClient.expect(connect,0x26);
shimClient.respond(connack,4); shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
@ -107,13 +121,13 @@ int test_connect_accepts_username_no_password() {
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
byte connect[] = { 0x10,0x20,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0x82,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72}; byte connect[] = { 0x10,0x1e,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x82,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x4,0x75,0x73,0x65,0x72};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x22); shimClient.expect(connect,0x20);
shimClient.respond(connack,4); shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",(char*)"user",'\0'); int rc = client.connect((char*)"client_test1",(char*)"user",0);
IS_TRUE(rc); IS_TRUE(rc);
IS_FALSE(shimClient.error()); IS_FALSE(shimClient.error());
@ -125,13 +139,13 @@ int test_connect_ignores_password_no_username() {
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x1a,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,28); shimClient.expect(connect,26);
shimClient.respond(connack,4); shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1",'\0',(char*)"pass"); int rc = client.connect((char*)"client_test1",0,(char*)"pass");
IS_TRUE(rc); IS_TRUE(rc);
IS_FALSE(shimClient.error()); IS_FALSE(shimClient.error());
@ -143,9 +157,9 @@ int test_connect_with_will() {
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x32,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0xe,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65}; byte connect[] = {0x10,0x30,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xe,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x34); shimClient.expect(connect,0x32);
shimClient.respond(connack,4); shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
@ -161,9 +175,9 @@ int test_connect_with_will_username_password() {
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
byte connect[] = {0x10,0x42,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0xce,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x8,0x70,0x61,0x73,0x73,0x77,0x6f,0x72,0x64}; byte connect[] = {0x10,0x40,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0xce,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31,0x0,0x9,0x77,0x69,0x6c,0x6c,0x54,0x6f,0x70,0x69,0x63,0x0,0xb,0x77,0x69,0x6c,0x6c,0x4d,0x65,0x73,0x73,0x61,0x67,0x65,0x0,0x4,0x75,0x73,0x65,0x72,0x0,0x8,0x70,0x61,0x73,0x73,0x77,0x6f,0x72,0x64};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,0x44); shimClient.expect(connect,0x42);
shimClient.respond(connack,4); shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
@ -181,17 +195,24 @@ int test_connect_disconnect_connect() {
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
byte expectServer[] = { 172, 16, 0, 2 }; byte expectServer[] = { 172, 16, 0, 2 };
shimClient.expectConnect(expectServer,1883); shimClient.expectConnect(expectServer,1883);
byte connect[] = {0x10,0x1a,0x0,0x6,0x4d,0x51,0x49,0x73,0x64,0x70,0x3,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31}; byte connect[] = {0x10,0x18,0x0,0x4,0x4d,0x51,0x54,0x54,0x4,0x2,0x0,0xf,0x0,0xc,0x63,0x6c,0x69,0x65,0x6e,0x74,0x5f,0x74,0x65,0x73,0x74,0x31};
byte connack[] = { 0x20, 0x02, 0x00, 0x00 }; byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.expect(connect,28); shimClient.expect(connect,26);
shimClient.respond(connack,4); shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient); PubSubClient client(server, 1883, callback, shimClient);
int state = client.state();
IS_TRUE(state == MQTT_DISCONNECTED);
int rc = client.connect((char*)"client_test1"); int rc = client.connect((char*)"client_test1");
IS_TRUE(rc); IS_TRUE(rc);
IS_FALSE(shimClient.error()); IS_FALSE(shimClient.error());
state = client.state();
IS_TRUE(state == MQTT_CONNECTED);
byte disconnect[] = {0xE0,0x00}; byte disconnect[] = {0xE0,0x00};
shimClient.expect(disconnect,2); shimClient.expect(disconnect,2);
@ -201,28 +222,35 @@ int test_connect_disconnect_connect() {
IS_FALSE(shimClient.connected()); IS_FALSE(shimClient.connected());
IS_FALSE(shimClient.error()); IS_FALSE(shimClient.error());
state = client.state();
IS_TRUE(state == MQTT_DISCONNECTED);
shimClient.expect(connect,28); shimClient.expect(connect,28);
shimClient.respond(connack,4); shimClient.respond(connack,4);
rc = client.connect((char*)"client_test1"); rc = client.connect((char*)"client_test1");
IS_TRUE(rc); IS_TRUE(rc);
IS_FALSE(shimClient.error()); IS_FALSE(shimClient.error());
state = client.state();
IS_TRUE(state == MQTT_CONNECTED);
END_IT END_IT
} }
int main() int main()
{ {
SUITE("Connect");
test_connect_fails_no_network(); test_connect_fails_no_network();
test_connect_fails_on_no_response(); test_connect_fails_on_no_response();
test_connect_properly_formatted(); test_connect_properly_formatted();
test_connect_accepts_username_password();
test_connect_fails_on_bad_rc(); test_connect_fails_on_bad_rc();
test_connect_properly_formatted_hostname(); test_connect_properly_formatted_hostname();
test_connect_accepts_username_password();
test_connect_accepts_username_no_password(); test_connect_accepts_username_no_password();
test_connect_ignores_password_no_username(); test_connect_ignores_password_no_username();
test_connect_with_will(); test_connect_with_will();
test_connect_with_will_username_password(); test_connect_with_will_username_password();
test_connect_disconnect_connect(); test_connect_disconnect_connect();
FINISH FINISH
} }

View File

@ -3,7 +3,7 @@
#include "Buffer.h" #include "Buffer.h"
#include "BDDTest.h" #include "BDDTest.h"
#include "trace.h" #include "trace.h"
#include <unistd.h>
byte server[] = { 172, 16, 0, 2 }; byte server[] = { 172, 16, 0, 2 };
@ -13,7 +13,7 @@ void callback(char* topic, byte* payload, unsigned int length) {
int test_keepalive_pings_idle() { int test_keepalive_pings_idle() {
IT("keeps an idle connection alive"); IT("keeps an idle connection alive (takes 1 minute)");
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
@ -32,6 +32,10 @@ int test_keepalive_pings_idle() {
for (int i = 0; i < 50; i++) { for (int i = 0; i < 50; i++) {
sleep(1); sleep(1);
if ( i == 15 || i == 31 || i == 47) {
shimClient.expect(pingreq,2);
shimClient.respond(pingresp,2);
}
rc = client.loop(); rc = client.loop();
IS_TRUE(rc); IS_TRUE(rc);
} }
@ -42,7 +46,7 @@ int test_keepalive_pings_idle() {
} }
int test_keepalive_pings_with_outbound_qos0() { int test_keepalive_pings_with_outbound_qos0() {
IT("keeps a connection alive that only sends qos0"); IT("keeps a connection alive that only sends qos0 (takes 1 minute)");
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
@ -78,7 +82,7 @@ int test_keepalive_pings_with_outbound_qos0() {
} }
int test_keepalive_pings_with_inbound_qos0() { int test_keepalive_pings_with_inbound_qos0() {
IT("keeps a connection alive that only receives qos0"); IT("keeps a connection alive that only receives qos0 (takes 1 minute)");
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
@ -111,7 +115,7 @@ int test_keepalive_pings_with_inbound_qos0() {
} }
int test_keepalive_no_pings_inbound_qos1() { int test_keepalive_no_pings_inbound_qos1() {
IT("does not send pings for connections with inbound qos1"); IT("does not send pings for connections with inbound qos1 (takes 1 minute)");
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
@ -139,7 +143,7 @@ int test_keepalive_no_pings_inbound_qos1() {
} }
int test_keepalive_disconnects_hung() { int test_keepalive_disconnects_hung() {
IT("disconnects a hung connection"); IT("disconnects a hung connection (takes 30 seconds)");
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
@ -160,6 +164,9 @@ int test_keepalive_disconnects_hung() {
} }
IS_FALSE(rc); IS_FALSE(rc);
int state = client.state();
IS_TRUE(state == MQTT_CONNECTION_TIMEOUT);
IS_FALSE(shimClient.error()); IS_FALSE(shimClient.error());
END_IT END_IT
@ -167,6 +174,7 @@ int test_keepalive_disconnects_hung() {
int main() int main()
{ {
SUITE("Keep-alive");
test_keepalive_pings_idle(); test_keepalive_pings_idle();
test_keepalive_pings_with_outbound_qos0(); test_keepalive_pings_with_outbound_qos0();
test_keepalive_pings_with_inbound_qos0(); test_keepalive_pings_with_inbound_qos0();

View File

@ -11,9 +11,13 @@ const char* testDescription;
std::list<std::string> failureList; std::list<std::string> failureList;
void bddtest_suite(const char* name) {
LOG(name << "\n");
}
int bddtest_test(const char* file, int line, const char* assertion, int result) { int bddtest_test(const char* file, int line, const char* assertion, int result) {
if (!result) { if (!result) {
LOG("F"); LOG("\n");
std::ostringstream os; std::ostringstream os;
os << " ! "<<testDescription<<"\n " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]"; os << " ! "<<testDescription<<"\n " <<file << ":" <<line<<" : "<<assertion<<" ["<<result<<"]";
failureList.push_back(os.str()); failureList.push_back(os.str());
@ -22,12 +26,12 @@ int bddtest_test(const char* file, int line, const char* assertion, int result)
} }
void bddtest_start(const char* description) { void bddtest_start(const char* description) {
TRACE(" - "<<description << "\n"); LOG(" - "<<description<<" ");
testDescription = description; testDescription = description;
testCount ++; testCount ++;
} }
void bddtest_end() { void bddtest_end() {
LOG("."); LOG("\n");
testPasses ++; testPasses ++;
} }
@ -35,8 +39,10 @@ int bddtest_summary() {
for (std::list<std::string>::iterator it = failureList.begin(); it != failureList.end(); it++) { for (std::list<std::string>::iterator it = failureList.begin(); it != failureList.end(); it++) {
LOG("\n"); LOG("\n");
LOG(*it); LOG(*it);
LOG("\n");
} }
LOG("\n" << std::dec << testPasses << "/" << testCount << " tests passed\n");
LOG(std::dec << testPasses << "/" << testCount << " tests passed\n\n");
if (testPasses == testCount) { if (testPasses == testCount) {
return 0; return 0;
} }

View File

@ -1,11 +1,13 @@
#ifndef bddtest_h #ifndef bddtest_h
#define bddtest_h #define bddtest_h
void bddtest_suite(const char* name);
int bddtest_test(const char*, int, const char*, int); int bddtest_test(const char*, int, const char*, int);
void bddtest_start(const char*); void bddtest_start(const char*);
void bddtest_end(); void bddtest_end();
int bddtest_summary(); int bddtest_summary();
#define SUITE(x) { bddtest_suite(x); }
#define TEST(x) { if (!bddtest_test(__FILE__, __LINE__, #x, (x))) return false; } #define TEST(x) { if (!bddtest_test(__FILE__, __LINE__, #x, (x))) return false; }
#define IT(x) { bddtest_start(x); } #define IT(x) { bddtest_start(x); }

View File

@ -0,0 +1,44 @@
#include <Arduino.h>
#include <IPAddress.h>
IPAddress::IPAddress()
{
memset(_address, 0, sizeof(_address));
}
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet)
{
_address[0] = first_octet;
_address[1] = second_octet;
_address[2] = third_octet;
_address[3] = fourth_octet;
}
IPAddress::IPAddress(uint32_t address)
{
memcpy(_address, &address, sizeof(_address));
}
IPAddress::IPAddress(const uint8_t *address)
{
memcpy(_address, address, sizeof(_address));
}
IPAddress& IPAddress::operator=(const uint8_t *address)
{
memcpy(_address, address, sizeof(_address));
return *this;
}
IPAddress& IPAddress::operator=(uint32_t address)
{
memcpy(_address, (const uint8_t *)&address, sizeof(_address));
return *this;
}
bool IPAddress::operator==(const uint8_t* addr)
{
return memcmp(addr, _address, sizeof(_address)) == 0;
}

View File

@ -1,11 +1,72 @@
/*
*
* MIT License:
* Copyright (c) 2011 Adrian McEwen
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* adrianm@mcqn.com 1/1/2011
*/
#ifndef IPAddress_h #ifndef IPAddress_h
#define IPAddress_h #define IPAddress_h
extern "C" {
#define IPAddress uint8_t* // A class to make it easier to handle and pass around IP addresses
} class IPAddress {
private:
uint8_t _address[4]; // IPv4 address
// Access the raw byte array containing the address. Because this returns a pointer
// to the internal structure rather than a copy of the address this function should only
// be used when you know that the usage of the returned uint8_t* will be transient and not
// stored.
uint8_t* raw_address() { return _address; };
public:
// Constructors
IPAddress();
IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet);
IPAddress(uint32_t address);
IPAddress(const uint8_t *address);
// Overloaded cast operator to allow IPAddress objects to be used where a pointer
// to a four-byte uint8_t array is expected
operator uint32_t() { return *((uint32_t*)_address); };
bool operator==(const IPAddress& addr) { return (*((uint32_t*)_address)) == (*((uint32_t*)addr._address)); };
bool operator==(const uint8_t* addr);
// Overloaded index operator to allow getting and setting individual octets of the address
uint8_t operator[](int index) const { return _address[index]; };
uint8_t& operator[](int index) { return _address[index]; };
// Overloaded copy operators to allow initialisation of IPAddress objects from other types
IPAddress& operator=(const uint8_t *address);
IPAddress& operator=(uint32_t address);
friend class EthernetClass;
friend class UDP;
friend class Client;
friend class Server;
friend class DhcpClass;
friend class DNSClient;
};
#endif #endif

View File

@ -26,10 +26,10 @@ int ShimClient::connect(IPAddress ip, uint16_t port) {
this->_connected = true; this->_connected = true;
} }
if (this->_expectedPort !=0) { if (this->_expectedPort !=0) {
if (memcmp(ip,this->_expectedIP,4) != 0) { // if (memcmp(ip,this->_expectedIP,4) != 0) {
TRACE( "ip mismatch\n"); // TRACE( "ip mismatch\n");
this->_error = true; // this->_error = true;
} // }
if (port != this->_expectedPort) { if (port != this->_expectedPort) {
TRACE( "port mismatch\n"); TRACE( "port mismatch\n");
this->_error = true; this->_error = true;
@ -151,4 +151,3 @@ void ShimClient::expectConnect(const char *host, uint16_t port) {
this->_expectedHost = host; this->_expectedHost = host;
this->_expectedPort = port; this->_expectedPort = port;
} }

View File

@ -102,6 +102,26 @@ int test_publish_not_connected() {
END_IT END_IT
} }
int test_publish_too_long() {
IT("publish fails when topic/payload are too long");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
// 0 1 2 3 4 5 6 7 8 9 0 1 2
rc = client.publish((char*)"topic",(char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
IS_FALSE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_publish_P() { int test_publish_P() {
IT("publishes using PROGMEM"); IT("publishes using PROGMEM");
@ -130,12 +150,16 @@ int test_publish_P() {
} }
int main() int main()
{ {
SUITE("Publish");
test_publish(); test_publish();
test_publish_bytes(); test_publish_bytes();
test_publish_retained(); test_publish_retained();
test_publish_not_connected(); test_publish_not_connected();
test_publish_too_long();
test_publish_P(); test_publish_P();
FINISH FINISH

View File

@ -105,7 +105,7 @@ int test_receive_max_sized_message() {
int rc = client.connect((char*)"client_test1"); int rc = client.connect((char*)"client_test1");
IS_TRUE(rc); IS_TRUE(rc);
byte length = MQTT_MAX_PACKET_SIZE; int length = MQTT_MAX_PACKET_SIZE;
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
byte bigPublish[length]; byte bigPublish[length];
memset(bigPublish,'A',length); memset(bigPublish,'A',length);
@ -141,7 +141,7 @@ int test_receive_oversized_message() {
int rc = client.connect((char*)"client_test1"); int rc = client.connect((char*)"client_test1");
IS_TRUE(rc); IS_TRUE(rc);
byte length = MQTT_MAX_PACKET_SIZE+1; int length = MQTT_MAX_PACKET_SIZE+1;
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
byte bigPublish[length]; byte bigPublish[length];
memset(bigPublish,'A',length); memset(bigPublish,'A',length);
@ -176,7 +176,7 @@ int test_receive_oversized_stream_message() {
int rc = client.connect((char*)"client_test1"); int rc = client.connect((char*)"client_test1");
IS_TRUE(rc); IS_TRUE(rc);
byte length = MQTT_MAX_PACKET_SIZE+1; int length = MQTT_MAX_PACKET_SIZE+1;
byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64}; byte publish[] = {0x30,length-2,0x0,0x5,0x74,0x6f,0x70,0x69,0x63,0x70,0x61,0x79,0x6c,0x6f,0x61,0x64};
byte bigPublish[length]; byte bigPublish[length];
@ -237,6 +237,7 @@ int test_receive_qos1() {
int main() int main()
{ {
SUITE("Receive");
test_receive_callback(); test_receive_callback();
test_receive_stream(); test_receive_stream();
test_receive_max_sized_message(); test_receive_max_sized_message();

View File

@ -11,8 +11,6 @@ void callback(char* topic, byte* payload, unsigned int length) {
// handle message arrived // handle message arrived
} }
int test_subscribe_no_qos() { int test_subscribe_no_qos() {
IT("subscribe without qos defaults to 0"); IT("subscribe without qos defaults to 0");
ShimClient shimClient; ShimClient shimClient;
@ -78,7 +76,7 @@ int test_subscribe_not_connected() {
} }
int test_subscribe_invalid_qos() { int test_subscribe_invalid_qos() {
IT("subscribe fails when not connected"); IT("subscribe fails with invalid qos values");
ShimClient shimClient; ShimClient shimClient;
shimClient.setAllowConnect(true); shimClient.setAllowConnect(true);
@ -99,6 +97,33 @@ int test_subscribe_invalid_qos() {
END_IT END_IT
} }
int test_subscribe_too_long() {
IT("subscribe fails with too long topic");
ShimClient shimClient;
shimClient.setAllowConnect(true);
byte connack[] = { 0x20, 0x02, 0x00, 0x00 };
shimClient.respond(connack,4);
PubSubClient client(server, 1883, callback, shimClient);
int rc = client.connect((char*)"client_test1");
IS_TRUE(rc);
// max length should be allowed
// 0 1 2 3 4 5 6 7 8 9 0 1 2
rc = client.subscribe((char*)"12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789");
IS_TRUE(rc);
// 0 1 2 3 4 5 6 7 8 9 0 1 2
rc = client.subscribe((char*)"123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890");
IS_FALSE(rc);
IS_FALSE(shimClient.error());
END_IT
}
int test_unsubscribe() { int test_unsubscribe() {
IT("unsubscribes"); IT("unsubscribes");
ShimClient shimClient; ShimClient shimClient;
@ -140,10 +165,12 @@ int test_unsubscribe_not_connected() {
int main() int main()
{ {
SUITE("Subscribe");
test_subscribe_no_qos(); test_subscribe_no_qos();
test_subscribe_qos_1(); test_subscribe_qos_1();
test_subscribe_not_connected(); test_subscribe_not_connected();
test_subscribe_invalid_qos(); test_subscribe_invalid_qos();
test_subscribe_too_long();
test_unsubscribe(); test_unsubscribe();
test_unsubscribe_not_connected(); test_unsubscribe_not_connected();
FINISH FINISH