From 2d2bd98c146fa0584729db9af8345e1ddde539f2 Mon Sep 17 00:00:00 2001 From: Ian Craggs Date: Thu, 5 Jun 2014 16:36:19 +0100 Subject: [PATCH] MQTT 3.1.1 support - sessionPresent for MQTTClient Bug: 436049 --- src/MQTTAsync.h | 9 +- src/MQTTClient.c | 19 +- src/MQTTClient.h | 38 +++- src/MQTTPacket.h | 20 +- src/MQTTPacketOut.c | 2 +- test/test_mqtt4sync.c | 498 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 568 insertions(+), 18 deletions(-) create mode 100644 test/test_mqtt4sync.c diff --git a/src/MQTTAsync.h b/src/MQTTAsync.h index e1e3afcb..47c4cfe5 100644 --- a/src/MQTTAsync.h +++ b/src/MQTTAsync.h @@ -23,7 +23,7 @@ * @cond MQTTAsync_main * @mainpage Asynchronous MQTT client library for C * - * © Copyright IBM Corp. 2009, 2013 + * © Copyright IBM Corp. 2009, 2014 * * @brief An Asynchronous MQTT client library for C. * @@ -333,6 +333,13 @@ typedef struct MQTTAsync_message message; char* destinationName; } pub; + /* For connect, the server connected to, MQTT version used, and sessionPresent flag */ + struct + { + char* serverURI; + int MQTTVersion; + int sessionPresent; + } connect; } alt; } MQTTAsync_successData; diff --git a/src/MQTTClient.c b/src/MQTTClient.c index ea894ca5..d09e49be 100644 --- a/src/MQTTClient.c +++ b/src/MQTTClient.c @@ -762,6 +762,7 @@ int MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_connectOptions* o { MQTTClients* m = handle; int rc = SOCKET_ERROR; + int sessionPresent = 0; FUNC_ENTRY; if (m->ma && !running) @@ -890,6 +891,8 @@ int MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_connectOptions* o m->c->connected = 1; m->c->good = 1; m->c->connect_state = 0; + if (MQTTVersion == 4) + sessionPresent = connack->flags.bits.sessionPresent; if (m->c->cleansession) rc = MQTTClient_cleanSession(m->c); if (m->c->outboundMsgs->count > 0) @@ -910,9 +913,17 @@ int MQTTClient_connectURIVersion(MQTTClient handle, MQTTClient_connectOptions* o m->pack = NULL; } } - exit: - if (rc != MQTTCLIENT_SUCCESS) + if (rc == MQTTCLIENT_SUCCESS) + { + if (options->struct_version == 4) /* means we have to fill out return values */ + { + options->returned.serverURI = serverURI; + options->returned.MQTTVersion = MQTTVersion; + options->returned.sessionPresent = sessionPresent; + } + } + else { Thread_unlock_mutex(mqttclient_mutex); MQTTClient_disconnect1(handle, 0, 0, (MQTTVersion == 3)); /* not "internal" because we don't want to call connection lost */ @@ -1046,7 +1057,7 @@ int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) if (strncmp(options->struct_id, "MQTC", 4) != 0 || (options->struct_version != 0 && options->struct_version != 1 && options->struct_version != 2 - && options->struct_version != 3)) + && options->struct_version != 3 && options->struct_version != 4)) { rc = MQTTCLIENT_BAD_STRUCTURE; goto exit; @@ -1298,6 +1309,8 @@ int MQTTClient_subscribe(MQTTClient handle, char* topic, int qos) FUNC_ENTRY; rc = MQTTClient_subscribeMany(handle, 1, &topic, &qos); + if (qos == MQTT_BAD_SUBSCRIBE) /* addition for MQTT 3.1.1 - error code from subscribe */ + rc = MQTT_BAD_SUBSCRIBE; FUNC_EXIT_RC(rc); return rc; } diff --git a/src/MQTTClient.h b/src/MQTTClient.h index eeb48077..3acef54b 100644 --- a/src/MQTTClient.h +++ b/src/MQTTClient.h @@ -38,7 +38,7 @@ * @endcond * @cond MQTTClient_main * @mainpage MQTT Client library for C - * © Copyright IBM Corp. 2009, 2013 + * © Copyright IBM Corp. 2009, 2014 * * @brief An MQTT client library in C. * @@ -173,6 +173,10 @@ * MQTT version to connect with: 3.1.1 */ #define MQTTVERSION_3_1_1 4 +/** + * Bad return code from subscribe, as defined in the 3.1.1 specification + */ +#define MQTT_BAD_SUBSCRIBE 0x80 /** * A handle representing an MQTT client. A valid client handle is available @@ -504,11 +508,12 @@ typedef struct { /** The eyecatcher for this structure. must be MQTC. */ char struct_id[4]; - /** The version number of this structure. Must be 0, 1, 2 or 3. - * 0 signifies no SSL options and no serverURIs - * 1 signifies no serverURIs - * 2 signifies no MQTTVersion - */ + /** The version number of this structure. Must be 0, 1, 2, 3 or 4. + * 0 signifies no SSL options and no serverURIs + * 1 signifies no serverURIs + * 2 signifies no MQTTVersion + * 3 signifies no returned values + */ int struct_version; /** The "keep alive" interval, measured in seconds, defines the maximum time * that should pass without communication between the client and the server @@ -599,15 +604,24 @@ typedef struct */ char** serverURIs; /** - * Sets the version of MQTT to be used on the connect. - * MQTTVERSION_DEFAULT (0) = default: start with 3.1.1, and if that fails, fall back to 3.1 - * MQTTVERSION_3_1 (3) = only try version 3.1 - * MQTTVERSION_3_1_1 (4) = only try version 3.1.1 - */ + * Sets the version of MQTT to be used on the connect. + * MQTTVERSION_DEFAULT (0) = default: start with 3.1.1, and if that fails, fall back to 3.1 + * MQTTVERSION_3_1 (3) = only try version 3.1 + * MQTTVERSION_3_1_1 (4) = only try version 3.1.1 + */ int MQTTVersion; + /** + * Returned from the connect when the MQTT version used to connect is 3.1.1 + */ + struct + { + char* serverURI; /**< the serverURI connected to */ + int MQTTVersion; /**< the MQTT version used to connect with */ + int sessionPresent; /**< if the MQTT version is 3.1.1, the value of sessionPresent returned in the connack */ + } returned; } MQTTClient_connectOptions; -#define MQTTClient_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 3, 60, 1, 1, NULL, NULL, NULL, 30, 20, NULL, 0, NULL, 0} +#define MQTTClient_connectOptions_initializer { {'M', 'Q', 'T', 'C'}, 4, 60, 1, 1, NULL, NULL, NULL, 30, 20, NULL, 0, NULL, 0} /** * MQTTClient_libraryInfo is used to store details relating to the currently used diff --git a/src/MQTTPacket.h b/src/MQTTPacket.h index 01b9eddf..49043aad 100644 --- a/src/MQTTPacket.h +++ b/src/MQTTPacket.h @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2009, 2013 IBM Corp. + * Copyright (c) 2009, 2014 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -13,6 +13,7 @@ * Contributors: * Ian Craggs - initial API and implementation and/or initial documentation * Ian Craggs, Allan Stockdill-Mander - SSL updates + * Ian Craggs - MQTT 3.1.1 support *******************************************************************************/ #if !defined(MQTTPACKET_H) @@ -120,6 +121,23 @@ typedef struct typedef struct { Header header; /**< MQTT header byte */ + union + { + unsigned char all; /**< all connack flags */ +#if defined(REVERSED) + struct + { + unsigned int reserved : 7; /**< message type nibble */ + bool sessionPresent : 1; /**< was a session found on the server? */ + } bits; +#else + struct + { + bool sessionPresent : 1; /**< was a session found on the server? */ + unsigned int reserved : 7; /**< message type nibble */ + } bits; +#endif + } flags; /**< connack flags byte */ char rc; /**< connack return code */ } Connack; diff --git a/src/MQTTPacketOut.c b/src/MQTTPacketOut.c index 06039515..b6843654 100644 --- a/src/MQTTPacketOut.c +++ b/src/MQTTPacketOut.c @@ -123,7 +123,7 @@ void* MQTTPacket_connack(unsigned char aHeader, char* data, int datalen) FUNC_ENTRY; pack->header.byte = aHeader; - readChar(&curdata); /* reserved byte */ + pack->flags.all = readChar(&curdata); pack->rc = readChar(&curdata); FUNC_EXIT; return pack; diff --git a/test/test_mqtt4sync.c b/test/test_mqtt4sync.c new file mode 100644 index 00000000..b6bae8da --- /dev/null +++ b/test/test_mqtt4sync.c @@ -0,0 +1,498 @@ +/******************************************************************************* + * Copyright (c) 2009, 2014 IBM Corp. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * and Eclipse Distribution License v1.0 which accompany this distribution. + * + * The Eclipse Public License is available at + * http://www.eclipse.org/legal/epl-v10.html + * and the Eclipse Distribution License is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * Contributors: + * Ian Craggs - initial API and implementation and/or initial documentation + * Ian Craggs - MQTT 3.1.1 support + *******************************************************************************/ + + +/** + * @file + * MQTT 3.1.1 Tests for the synchronous Paho MQTT C client + */ + + +/* +#if !defined(_RTSHEADER) + #include +#endif +*/ + +#include "MQTTClient.h" +#include +#include + +#if !defined(_WINDOWS) + #include + #include + #include + #include +#else +#include +#include +#define MAXHOSTNAMELEN 256 +#define EAGAIN WSAEWOULDBLOCK +#define EINTR WSAEINTR +#define EINPROGRESS WSAEINPROGRESS +#define EWOULDBLOCK WSAEWOULDBLOCK +#define ENOTCONN WSAENOTCONN +#define ECONNRESET WSAECONNRESET +#define setenv(a, b, c) _putenv_s(a, b) +#endif + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +void usage() +{ + printf("help!!\n"); + exit(-1); +} + +struct Options +{ + char* connection; /**< connection to system under test. */ + char** haconnections; + int hacount; + int verbose; + int test_no; + int iterations; +} options = +{ + "tcp://m2m.eclipse.org:1883", + NULL, + 0, + 0, + 0, + 1, +}; + +void getopts(int argc, char** argv) +{ + int count = 1; + + while (count < argc) + { + if (strcmp(argv[count], "--test_no") == 0) + { + if (++count < argc) + options.test_no = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--connection") == 0) + { + if (++count < argc) + { + options.connection = argv[count]; + printf("\nSetting connection to %s\n", options.connection); + } + else + usage(); + } + else if (strcmp(argv[count], "--haconnections") == 0) + { + if (++count < argc) + { + char* tok = strtok(argv[count], " "); + options.hacount = 0; + options.haconnections = malloc(sizeof(char*) * 5); + while (tok) + { + options.haconnections[options.hacount] = malloc(strlen(tok) + 1); + strcpy(options.haconnections[options.hacount], tok); + options.hacount++; + tok = strtok(NULL, " "); + } + } + else + usage(); + } + else if (strcmp(argv[count], "--iterations") == 0) + { + if (++count < argc) + options.iterations = atoi(argv[count]); + else + usage(); + } + else if (strcmp(argv[count], "--verbose") == 0) + { + options.verbose = 1; + printf("\nSetting verbose on\n"); + } + count++; + } +} + + +#define LOGA_DEBUG 0 +#define LOGA_INFO 1 +#include +#include +#include +void MyLog(int LOGA_level, char* format, ...) +{ + static char msg_buf[256]; + va_list args; + struct timeb ts; + + struct tm *timeinfo; + + if (LOGA_level == LOGA_DEBUG && options.verbose == 0) + return; + + ftime(&ts); + timeinfo = localtime(&ts.time); + strftime(msg_buf, 80, "%Y%m%d %H%M%S", timeinfo); + + sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm); + + va_start(args, format); + vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args); + va_end(args); + + printf("%s\n", msg_buf); + fflush(stdout); +} + + +#if defined(WIN32) || defined(_WINDOWS) +#define mqsleep(A) Sleep(1000*A) +#define START_TIME_TYPE DWORD +static DWORD start_time = 0; +START_TIME_TYPE start_clock(void) +{ + return GetTickCount(); +} +#elif defined(AIX) +#define mqsleep sleep +#define START_TIME_TYPE struct timespec +START_TIME_TYPE start_clock(void) +{ + static struct timespec start; + clock_gettime(CLOCK_REALTIME, &start); + return start; +} +#else +#define mqsleep sleep +#define START_TIME_TYPE struct timeval +/* TODO - unused - remove? static struct timeval start_time; */ +START_TIME_TYPE start_clock(void) +{ + struct timeval start_time; + gettimeofday(&start_time, NULL); + return start_time; +} +#endif + + +#if defined(WIN32) +long elapsed(START_TIME_TYPE start_time) +{ + return GetTickCount() - start_time; +} +#elif defined(AIX) +#define assert(a) +long elapsed(struct timespec start) +{ + struct timespec now, res; + + clock_gettime(CLOCK_REALTIME, &now); + ntimersub(now, start, res); + return (res.tv_sec)*1000L + (res.tv_nsec)/1000000L; +} +#else +long elapsed(START_TIME_TYPE start_time) +{ + struct timeval now, res; + + gettimeofday(&now, NULL); + timersub(&now, &start_time, &res); + return (res.tv_sec)*1000 + (res.tv_usec)/1000; +} +#endif + + +#define assert(a, b, c, d) myassert(__FILE__, __LINE__, a, b, c, d) +#define assert1(a, b, c, d, e) myassert(__FILE__, __LINE__, a, b, c, d, e) + +int tests = 0; +int failures = 0; +FILE* xml; +START_TIME_TYPE global_start_time; +char output[3000]; +char* cur_output = output; + + +void write_test_result() +{ + long duration = elapsed(global_start_time); + + fprintf(xml, " time=\"%ld.%.3ld\" >\n", duration / 1000, duration % 1000); + if (cur_output != output) + { + fprintf(xml, "%s", output); + cur_output = output; + } + fprintf(xml, "\n"); +} + + +void myassert(char* filename, int lineno, char* description, int value, char* format, ...) +{ + ++tests; + if (!value) + { + va_list args; + + ++failures; + MyLog(LOGA_INFO, "Assertion failed, file %s, line %d, description: %s\n", filename, lineno, description); + + va_start(args, format); + vprintf(format, args); + va_end(args); + + cur_output += sprintf(cur_output, "file %s, line %d \n", + description, filename, lineno); + } + else + MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description); +} + + +/********************************************************************* + +Test1: sessionPresent + +*********************************************************************/ +int test1(struct Options options) +{ + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + MQTTClient_willOptions wopts = MQTTClient_willOptions_initializer; + int rc = 0; + char* test_topic = "C client test1"; + + fprintf(xml, "message = "will message"; + opts.will->qos = 1; + opts.will->retained = 0; + opts.will->topicName = "will topic"; + opts.will = NULL; + + /* Connect cleansession */ + opts.cleansession = 1; + MyLog(LOGA_DEBUG, "Connecting"); + rc = MQTTClient_connect(c, &opts); + assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + if (rc != MQTTCLIENT_SUCCESS) + goto exit; + + assert("Correct serverURI returned", strcmp(opts.returned.serverURI, options.connection) == 0, "serverURI was %s", + opts.returned.serverURI); + assert("Correct MQTTVersion returned", opts.returned.MQTTVersion == 4, "MQTTVersion was %d", + opts.returned.MQTTVersion); + assert("Correct sessionPresent returned", opts.returned.sessionPresent == 0, "sessionPresent was %d", + opts.returned.sessionPresent); + + rc = MQTTClient_disconnect(c, 0); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + /* Connect again, non-cleansession */ + opts.cleansession = 0; + rc = MQTTClient_connect(c, &opts); + assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + assert("Correct serverURI returned", strcmp(opts.returned.serverURI, options.connection) == 0, "serverURI was %s", + opts.returned.serverURI); + assert("Correct MQTTVersion returned", opts.returned.MQTTVersion == 4, "MQTTVersion was %d", + opts.returned.MQTTVersion); + assert("Correct sessionPresent returned", opts.returned.sessionPresent == 0, "sessionPresent was %d", + opts.returned.sessionPresent); + + rc = MQTTClient_disconnect(c, 0); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + /* Connect again, non-cleansession */ + opts.cleansession = 0; + rc = MQTTClient_connect(c, &opts); + assert("Connect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + assert("Correct serverURI returned", strcmp(opts.returned.serverURI, options.connection) == 0, "serverURI was %s", + opts.returned.serverURI); + assert("Correct MQTTVersion returned", opts.returned.MQTTVersion == 4, "MQTTVersion was %d", + opts.returned.MQTTVersion); + assert("Correct sessionPresent returned", opts.returned.sessionPresent == 1, "sessionPresent was %d", + opts.returned.sessionPresent); + rc = MQTTClient_disconnect(c, 0); + assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc); + + MQTTClient_destroy(&c); + +exit: + MyLog(LOGA_INFO, "TEST1: test %s. %d tests run, %d failures.", + (failures == 0) ? "passed" : "failed", tests, failures); + write_test_result(); + return failures; +} + + +/********************************************************************* + +Test2: 0x80 return code from subscribe + +*********************************************************************/ +volatile int test2_arrivedcount = 0; +int test2_deliveryCompleted = 0; +MQTTClient_message test2_pubmsg = MQTTClient_message_initializer; + +void test2_deliveryComplete(void* context, MQTTClient_deliveryToken dt) +{ + ++test2_deliveryCompleted; +} + +int test2_messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m) +{ + ++test2_arrivedcount; + MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.", + test2_arrivedcount, topicName, m->payloadlen, (char*)(m->payload)); + MQTTClient_free(topicName); + MQTTClient_freeMessage(&m); + return 1; +} + +int test2(struct Options options) +{ + char* testname = "test2"; + int subsqos = 2; + MQTTClient c; + MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer; + int rc = 0; + char* test_topic = "C client test2"; + char* topics[2] = {"test_topic", "nosubscribe"}; + int qoss[2] = {2, 2}; + + fprintf(xml, "\n", (int)(ARRAY_SIZE(tests) - 1)); + + setenv("MQTT_C_CLIENT_TRACE", "ON", 1); + setenv("MQTT_C_CLIENT_TRACE_LEVEL", "ERROR", 1); + + getopts(argc, argv); + + for (i = 0; i < options.iterations; ++i) + { + if (options.test_no == 0) + { /* run all the tests */ + for (options.test_no = 1; options.test_no < ARRAY_SIZE(tests); ++options.test_no) + rc += tests[options.test_no](options); /* return number of failures. 0 = test succeeded */ + } + else + rc = tests[options.test_no](options); /* run just the selected test */ + } + + if (rc == 0) + MyLog(LOGA_INFO, "verdict pass"); + else + MyLog(LOGA_INFO, "verdict fail"); + + fprintf(xml, "\n"); + fclose(xml); + return rc; +}