From 274759aafb156a9e2e45ae7106c9fa6195a9a5fb Mon Sep 17 00:00:00 2001 From: huang <1085210385@qq.com> Date: Wed, 27 Sep 2023 14:02:20 +0800 Subject: [PATCH] Merge branch '2023_open_source_contest' of https://gitlink.org.cn/hc0014/xiuos into 2023_open_source_contest --- APP_Framework/Applications/app_test/Makefile | 3 +- .../app_test/test_mqttclient/MQTTConnect.h | 137 ++++ .../test_mqttclient/MQTTConnectClient.c | 214 +++++ .../test_mqttclient/MQTTConnectServer.c | 148 ++++ .../test_mqttclient/MQTTDeserializePublish.c | 107 +++ .../app_test/test_mqttclient/MQTTFormat.c | 262 +++++++ .../app_test/test_mqttclient/MQTTFormat.h | 37 + .../app_test/test_mqttclient/MQTTPacket.c | 412 ++++++++++ .../app_test/test_mqttclient/MQTTPacket.h | 134 ++++ .../app_test/test_mqttclient/MQTTPublish.h | 38 + .../test_mqttclient/MQTTSerializePublish.c | 169 ++++ .../app_test/test_mqttclient/MQTTSubscribe.h | 39 + .../test_mqttclient/MQTTSubscribeClient.c | 137 ++++ .../test_mqttclient/MQTTSubscribeServer.c | 112 +++ .../test_mqttclient/MQTTUnsubscribe.h | 38 + .../test_mqttclient/MQTTUnsubscribeClient.c | 106 +++ .../test_mqttclient/MQTTUnsubscribeServer.c | 102 +++ .../app_test/test_mqttclient/Makefile | 14 + .../app_test/test_mqttclient/README.md | 15 + .../app_test/test_mqttclient/StackTrace.h | 78 ++ .../test_mqttclient/test_mqttclient.c | 728 ++++++++++++++++++ .../test_mqttclient/test_mqttclient.h | 75 ++ .../app_test/test_mqttclient/transport.c | 102 +++ .../app_test/test_mqttclient/transport.h | 45 ++ .../app_test/test_mqttclient/图片1.png | Bin 0 -> 19570 bytes .../app_test/test_mqttclient/图片2.png | Bin 0 -> 14573 bytes .../app_test/test_mqttclient/图片3.png | Bin 0 -> 29378 bytes APP_Framework/lib/cJSON/Makefile | 1 + APP_Framework/lib/cJSON/cJSON_Process.c | 85 ++ APP_Framework/lib/cJSON/cJSON_Process.h | 23 + 30 files changed, 3360 insertions(+), 1 deletion(-) create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTConnect.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTConnectClient.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTConnectServer.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTDeserializePublish.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTFormat.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTFormat.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTPublish.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTSerializePublish.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribe.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribeClient.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribeServer.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribe.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribeClient.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribeServer.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/Makefile create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/README.md create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/StackTrace.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/transport.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/transport.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/图片1.png create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/图片2.png create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/图片3.png create mode 100644 APP_Framework/lib/cJSON/cJSON_Process.c create mode 100644 APP_Framework/lib/cJSON/cJSON_Process.h diff --git a/APP_Framework/Applications/app_test/Makefile b/APP_Framework/Applications/app_test/Makefile index 6b0c1ea96..fd1906309 100644 --- a/APP_Framework/Applications/app_test/Makefile +++ b/APP_Framework/Applications/app_test/Makefile @@ -24,7 +24,7 @@ endif ifeq ($(CONFIG_ADD_XIZI_FEATURES),y) SRC_FILES := test_shell.c - + ifeq ($(CONFIG_USER_TEST_ADC),y) SRC_FILES += test_adc.c endif @@ -130,6 +130,7 @@ ifeq ($(CONFIG_ADD_XIZI_FEATURES),y) endif ifeq ($(CONFIG_USER_TEST_MQTTCLIENT),y) + SRC_DIR:= test_mqttclient SRC_FILES += endif diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnect.h b/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnect.h new file mode 100644 index 000000000..4d247a3c2 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnect.h @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2014, 2017 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 + * Xiang Rong - 442039 Add makefile to Embedded C client + * Ian Craggs - fix for issue #64, bit order in connack response + *******************************************************************************/ + +#ifndef MQTTCONNECT_H_ +#define MQTTCONNECT_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + + +typedef union +{ + unsigned char all; /**< all connect flags */ +#if defined(REVERSED) + struct + { + unsigned int username : 1; /**< 3.1 user name */ + unsigned int password : 1; /**< 3.1 password */ + unsigned int willRetain : 1; /**< will retain setting */ + unsigned int willQoS : 2; /**< will QoS value */ + unsigned int will : 1; /**< will flag */ + unsigned int cleansession : 1; /**< clean session flag */ + unsigned int : 1; /**< unused */ + } bits; +#else + struct + { + unsigned int : 1; /**< unused */ + unsigned int cleansession : 1; /**< cleansession flag */ + unsigned int will : 1; /**< will flag */ + unsigned int willQoS : 2; /**< will QoS value */ + unsigned int willRetain : 1; /**< will retain setting */ + unsigned int password : 1; /**< 3.1 password */ + unsigned int username : 1; /**< 3.1 user name */ + } bits; +#endif +} MQTTConnectFlags; /**< connect flags byte */ + + + +/** + * Defines the MQTT "Last Will and Testament" (LWT) settings for + * the connect packet. + */ +typedef struct +{ + /** The eyecatcher for this structure. must be MQTW. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** The LWT topic to which the LWT message will be published. */ + MQTTString topicName; + /** The LWT payload. */ + MQTTString message; + /** + * The retained flag for the LWT message (see MQTTAsync_message.retained). + */ + unsigned char retained; + /** + * The quality of service setting for the LWT message (see + * MQTTAsync_message.qos and @ref qos). + */ + char qos; +} MQTTPacket_willOptions; + + +#define MQTTPacket_willOptions_initializer { {'M', 'Q', 'T', 'W'}, 0, {NULL, {0, NULL}}, {NULL, {0, NULL}}, 0, 0 } + + +typedef struct +{ + /** The eyecatcher for this structure. must be MQTC. */ + char struct_id[4]; + /** The version number of this structure. Must be 0 */ + int struct_version; + /** Version of MQTT to be used. 3 = 3.1 4 = 3.1.1 + */ + unsigned char MQTTVersion; + MQTTString clientID; + unsigned short keepAliveInterval; + unsigned char cleansession; + unsigned char willFlag; + MQTTPacket_willOptions will; + MQTTString username; + MQTTString password; +} MQTTPacket_connectData; + +typedef union +{ + unsigned char all; /**< all connack flags */ +#if defined(REVERSED) + struct + { + unsigned int reserved : 7; /**< unused */ + unsigned int sessionpresent : 1; /**< session present flag */ + } bits; +#else + struct + { + unsigned int sessionpresent : 1; /**< session present flag */ + unsigned int reserved: 7; /**< unused */ + } bits; +#endif +} MQTTConnackFlags; /**< connack flags byte */ + +#define MQTTPacket_connectData_initializer { {'M', 'Q', 'T', 'C'}, 0, 4, {NULL, {0, NULL}}, 60, 1, 0, \ + MQTTPacket_willOptions_initializer, {NULL, {0, NULL}}, {NULL, {0, NULL}} } + +DLLExport int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options); +DLLExport int MQTTDeserialize_connect(MQTTPacket_connectData* data, unsigned char* buf, int len); + +DLLExport int MQTTSerialize_connack(unsigned char* buf, int buflen, unsigned char connack_rc, unsigned char sessionPresent); +DLLExport int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen); + +DLLExport int MQTTSerialize_disconnect(unsigned char* buf, int buflen); +DLLExport int MQTTSerialize_pingreq(unsigned char* buf, int buflen); + +#endif /* MQTTCONNECT_H_ */ diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnectClient.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnectClient.c new file mode 100644 index 000000000..5f3cc2963 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnectClient.c @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + +/** + * Determines the length of the MQTT connect packet that would be produced using the supplied connect options. + * @param options the options to be used to build the connect packet + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_connectLength(MQTTPacket_connectData* options) +{ + int len = 0; + + FUNC_ENTRY; + + if (options->MQTTVersion == 3) + len = 12; /* variable depending on MQTT or MQIsdp */ + else if (options->MQTTVersion == 4) + len = 10; + + len += MQTTstrlen(options->clientID)+2; + if (options->willFlag) + len += MQTTstrlen(options->will.topicName)+2 + MQTTstrlen(options->will.message)+2; + if (options->username.cstring || options->username.lenstring.data) + len += MQTTstrlen(options->username)+2; + if (options->password.cstring || options->password.lenstring.data) + len += MQTTstrlen(options->password)+2; + + FUNC_EXIT_RC(len); + return len; +} + + +/** + * Serializes the connect options into the buffer. + * @param buf the buffer into which the packet will be serialized + * @param len the length in bytes of the supplied buffer + * @param options the options to be used to build the connect packet + * @return serialized length, or error if 0 + */ +int MQTTSerialize_connect(unsigned char* buf, int buflen, MQTTPacket_connectData* options) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + MQTTConnectFlags flags = {0}; + int len = 0; + int rc = -1; + + FUNC_ENTRY; + if (MQTTPacket_len(len = MQTTSerialize_connectLength(options)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = CONNECT; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, len); /* write remaining length */ + + if (options->MQTTVersion == 4) + { + writeCString(&ptr, "MQTT"); + writeChar(&ptr, (char) 4); + } + else + { + writeCString(&ptr, "MQIsdp"); + writeChar(&ptr, (char) 3); + } + + flags.all = 0; + flags.bits.cleansession = options->cleansession; + flags.bits.will = (options->willFlag) ? 1 : 0; + if (flags.bits.will) + { + flags.bits.willQoS = options->will.qos; + flags.bits.willRetain = options->will.retained; + } + + if (options->username.cstring || options->username.lenstring.data) + flags.bits.username = 1; + if (options->password.cstring || options->password.lenstring.data) + flags.bits.password = 1; + + writeChar(&ptr, flags.all); + writeInt(&ptr, options->keepAliveInterval); + writeMQTTString(&ptr, options->clientID); + if (options->willFlag) + { + writeMQTTString(&ptr, options->will.topicName); + writeMQTTString(&ptr, options->will.message); + } + if (flags.bits.username) + writeMQTTString(&ptr, options->username); + if (flags.bits.password) + writeMQTTString(&ptr, options->password); + + rc = ptr - buf; + + exit: FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Deserializes the supplied (wire) buffer into connack data - return code + * @param sessionPresent the session present flag returned (only for MQTT 3.1.1) + * @param connack_rc returned integer value of the connack return code + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param len the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_connack(unsigned char* sessionPresent, unsigned char* connack_rc, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + MQTTConnackFlags flags = {0}; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != CONNACK) + goto exit; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + if (enddata - curdata < 2) + goto exit; + + flags.all = readChar(&curdata); + *sessionPresent = flags.bits.sessionpresent; + *connack_rc = readChar(&curdata); + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a 0-length packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @param packettype the message type + * @return serialized length, or error if 0 + */ +int MQTTSerialize_zero(unsigned char* buf, int buflen, unsigned char packettype) +{ + MQTTHeader header = {0}; + int rc = -1; + unsigned char *ptr = buf; + + FUNC_ENTRY; + if (buflen < 2) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = packettype; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 0); /* write remaining length */ + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a disconnect packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @return serialized length, or error if 0 + */ +int MQTTSerialize_disconnect(unsigned char* buf, int buflen) +{ + return MQTTSerialize_zero(buf, buflen, DISCONNECT); +} + + +/** + * Serializes a disconnect packet into the supplied buffer, ready for writing to a socket + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer, to avoid overruns + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pingreq(unsigned char* buf, int buflen) +{ + return MQTTSerialize_zero(buf, buflen, PINGREQ); +} diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnectServer.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnectServer.c new file mode 100644 index 000000000..07c7cb537 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTConnectServer.c @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#include "StackTrace.h" +#include "MQTTPacket.h" +#include + +#define min(a, b) ((a < b) ? a : b) + + +/** + * Validates MQTT protocol name and version combinations + * @param protocol the MQTT protocol name as an MQTTString + * @param version the MQTT protocol version number, as in the connect packet + * @return correct MQTT combination? 1 is true, 0 is false + */ +int MQTTPacket_checkVersion(MQTTString* protocol, int version) +{ + int rc = 0; + + if (version == 3 && memcmp(protocol->lenstring.data, "MQIsdp", + min(6, protocol->lenstring.len)) == 0) + rc = 1; + else if (version == 4 && memcmp(protocol->lenstring.data, "MQTT", + min(4, protocol->lenstring.len)) == 0) + rc = 1; + return rc; +} + + +/** + * Deserializes the supplied (wire) buffer into connect data structure + * @param data the connect data structure to be filled out + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param len the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_connect(MQTTPacket_connectData* data, unsigned char* buf, int len) +{ + MQTTHeader header = {0}; + MQTTConnectFlags flags = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = &buf[len]; + int rc = 0; + MQTTString Protocol; + int version; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != CONNECT) + goto exit; + + curdata += MQTTPacket_decodeBuf(curdata, &mylen); /* read remaining length */ + + if (!readMQTTLenString(&Protocol, &curdata, enddata) || + enddata - curdata < 0) /* do we have enough data to read the protocol version byte? */ + goto exit; + + version = (int)readChar(&curdata); /* Protocol version */ + /* If we don't recognize the protocol version, we don't parse the connect packet on the + * basis that we don't know what the format will be. + */ + if (MQTTPacket_checkVersion(&Protocol, version)) + { + flags.all = readChar(&curdata); + data->cleansession = flags.bits.cleansession; + data->keepAliveInterval = readInt(&curdata); + if (!readMQTTLenString(&data->clientID, &curdata, enddata)) + goto exit; + data->willFlag = flags.bits.will; + if (flags.bits.will) + { + data->will.qos = flags.bits.willQoS; + data->will.retained = flags.bits.willRetain; + if (!readMQTTLenString(&data->will.topicName, &curdata, enddata) || + !readMQTTLenString(&data->will.message, &curdata, enddata)) + goto exit; + } + if (flags.bits.username) + { + if (enddata - curdata < 3 || !readMQTTLenString(&data->username, &curdata, enddata)) + goto exit; /* username flag set, but no username supplied - invalid */ + if (flags.bits.password && + (enddata - curdata < 3 || !readMQTTLenString(&data->password, &curdata, enddata))) + goto exit; /* password flag set, but no password supplied - invalid */ + } + else if (flags.bits.password) + goto exit; /* password flag set without username - invalid */ + rc = 1; + } +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes the connack packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param connack_rc the integer connack return code to be used + * @param sessionPresent the MQTT 3.1.1 sessionPresent flag + * @return serialized length, or error if 0 + */ +int MQTTSerialize_connack(unsigned char* buf, int buflen, unsigned char connack_rc, unsigned char sessionPresent) +{ + MQTTHeader header = {0}; + int rc = 0; + unsigned char *ptr = buf; + MQTTConnackFlags flags = {0}; + + FUNC_ENTRY; + if (buflen < 2) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = CONNACK; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */ + + flags.all = 0; + flags.bits.sessionpresent = sessionPresent; + writeChar(&ptr, flags.all); + writeChar(&ptr, connack_rc); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTDeserializePublish.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTDeserializePublish.c new file mode 100644 index 000000000..5014c46d3 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTDeserializePublish.c @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#include "StackTrace.h" +#include "MQTTPacket.h" +#include + +#define min(a, b) ((a < b) ? 1 : 0) + +/** + * Deserializes the supplied (wire) buffer into publish data + * @param dup returned integer - the MQTT dup flag + * @param qos returned integer - the MQTT QoS value + * @param retained returned integer - the MQTT retained flag + * @param packetid returned integer - the MQTT packet identifier + * @param topicName returned MQTTString - the MQTT topic in the publish + * @param payload returned byte buffer - the MQTT publish payload + * @param payloadlen returned integer - the length of the MQTT payload + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success + */ +int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName, + unsigned char** payload, int32_t* payloadlen, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != PUBLISH) + goto exit; + *dup = header.bits.dup; + *qos = header.bits.qos; + *retained = header.bits.retain; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + if (!readMQTTLenString(topicName, &curdata, enddata) || + enddata - curdata < 0) /* do we have enough data to read the protocol version byte? */ + goto exit; + + if (*qos > 0) + *packetid = readInt(&curdata); + + *payloadlen = enddata - curdata; + *payload = curdata; + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Deserializes the supplied (wire) buffer into an ack + * @param packettype returned integer - the MQTT packet type + * @param dup returned integer - the MQTT dup flag + * @param packetid returned integer - the MQTT packet identifier + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_ack(unsigned char* packettype, unsigned char* dup, unsigned short* packetid, unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + *dup = header.bits.dup; + *packettype = header.bits.type; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + if (enddata - curdata < 2) + goto exit; + *packetid = readInt(&curdata); + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTFormat.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTFormat.c new file mode 100644 index 000000000..2eff31f89 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTFormat.c @@ -0,0 +1,262 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#include "StackTrace.h" +#include "MQTTPacket.h" + +#include + + +const char* MQTTPacket_names[] = +{ + "RESERVED", "CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC", "PUBREL", + "PUBCOMP", "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", + "PINGREQ", "PINGRESP", "DISCONNECT" +}; + + +const char* MQTTPacket_getName(unsigned short packetid) +{ + return MQTTPacket_names[packetid]; +} + + +int MQTTStringFormat_connect(char* strbuf, int strbuflen, MQTTPacket_connectData* data) +{ + int strindex = 0; + + strindex = snprintf(strbuf, strbuflen, + "CONNECT MQTT version %d, client id %.*s, clean session %d, keep alive %d", + (int)data->MQTTVersion, data->clientID.lenstring.len, data->clientID.lenstring.data, + (int)data->cleansession, data->keepAliveInterval); + if (data->willFlag) + strindex += snprintf(&strbuf[strindex], strbuflen - strindex, + ", will QoS %d, will retain %d, will topic %.*s, will message %.*s", + data->will.qos, data->will.retained, + data->will.topicName.lenstring.len, data->will.topicName.lenstring.data, + data->will.message.lenstring.len, data->will.message.lenstring.data); + if (data->username.lenstring.data && data->username.lenstring.len > 0) + strindex += snprintf(&strbuf[strindex], strbuflen - strindex, + ", user name %.*s", data->username.lenstring.len, data->username.lenstring.data); + if (data->password.lenstring.data && data->password.lenstring.len > 0) + strindex += snprintf(&strbuf[strindex], strbuflen - strindex, + ", password %.*s", data->password.lenstring.len, data->password.lenstring.data); + return strindex; +} + + +int MQTTStringFormat_connack(char* strbuf, int strbuflen, unsigned char connack_rc, unsigned char sessionPresent) +{ + int strindex = snprintf(strbuf, strbuflen, "CONNACK session present %d, rc %d", sessionPresent, connack_rc); + return strindex; +} + + +int MQTTStringFormat_publish(char* strbuf, int strbuflen, unsigned char dup, int qos, unsigned char retained, + unsigned short packetid, MQTTString topicName, unsigned char* payload, int payloadlen) +{ + int strindex = snprintf(strbuf, strbuflen, + "PUBLISH dup %d, QoS %d, retained %d, packet id %d, topic %.*s, payload length %d, payload %.*s", + dup, qos, retained, packetid, + (topicName.lenstring.len < 20) ? topicName.lenstring.len : 20, topicName.lenstring.data, + payloadlen, (payloadlen < 20) ? payloadlen : 20, payload); + return strindex; +} + + +int MQTTStringFormat_ack(char* strbuf, int strbuflen, unsigned char packettype, unsigned char dup, unsigned short packetid) +{ + int strindex = snprintf(strbuf, strbuflen, "%s, packet id %d", MQTTPacket_names[packettype], packetid); + if (dup) + strindex += snprintf(strbuf + strindex, strbuflen - strindex, ", dup %d", dup); + return strindex; +} + + +int MQTTStringFormat_subscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, int count, + MQTTString topicFilters[], int requestedQoSs[]) +{ + return snprintf(strbuf, strbuflen, + "SUBSCRIBE dup %d, packet id %d count %d topic %.*s qos %d", + dup, packetid, count, + topicFilters[0].lenstring.len, topicFilters[0].lenstring.data, + requestedQoSs[0]); +} + + +int MQTTStringFormat_suback(char* strbuf, int strbuflen, unsigned short packetid, int count, int* grantedQoSs) +{ + return snprintf(strbuf, strbuflen, + "SUBACK packet id %d count %d granted qos %d", packetid, count, grantedQoSs[0]); +} + + +int MQTTStringFormat_unsubscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]) +{ + return snprintf(strbuf, strbuflen, + "UNSUBSCRIBE dup %d, packet id %d count %d topic %.*s", + dup, packetid, count, + topicFilters[0].lenstring.len, topicFilters[0].lenstring.data); +} + + +#if defined(MQTT_CLIENT) +char* MQTTFormat_toClientString(char* strbuf, int strbuflen, unsigned char* buf, int buflen) +{ + int index = 0; + int rem_length = 0; + MQTTHeader header = {0}; + int strindex = 0; + + header.byte = buf[index++]; + index += MQTTPacket_decodeBuf(&buf[index], &rem_length); + + switch (header.bits.type) + { + + case CONNACK: + { + unsigned char sessionPresent, connack_rc; + if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, buflen) == 1) + strindex = MQTTStringFormat_connack(strbuf, strbuflen, connack_rc, sessionPresent); + } + break; + case PUBLISH: + { + unsigned char dup, retained, *payload; + unsigned short packetid; + int qos, payloadlen; + MQTTString topicName = MQTTString_initializer; + if (MQTTDeserialize_publish(&dup, &qos, &retained, &packetid, &topicName, + &payload, &payloadlen, buf, buflen) == 1) + strindex = MQTTStringFormat_publish(strbuf, strbuflen, dup, qos, retained, packetid, + topicName, payload, payloadlen); + } + break; + case PUBACK: + case PUBREC: + case PUBREL: + case PUBCOMP: + { + unsigned char packettype, dup; + unsigned short packetid; + if (MQTTDeserialize_ack(&packettype, &dup, &packetid, buf, buflen) == 1) + strindex = MQTTStringFormat_ack(strbuf, strbuflen, packettype, dup, packetid); + } + break; + case SUBACK: + { + unsigned short packetid; + int maxcount = 1, count = 0; + int grantedQoSs[1]; + if (MQTTDeserialize_suback(&packetid, maxcount, &count, grantedQoSs, buf, buflen) == 1) + strindex = MQTTStringFormat_suback(strbuf, strbuflen, packetid, count, grantedQoSs); + } + break; + case UNSUBACK: + { + unsigned short packetid; + if (MQTTDeserialize_unsuback(&packetid, buf, buflen) == 1) + strindex = MQTTStringFormat_ack(strbuf, strbuflen, UNSUBACK, 0, packetid); + } + break; + case PINGREQ: + case PINGRESP: + case DISCONNECT: + strindex = snprintf(strbuf, strbuflen, "%s", MQTTPacket_names[header.bits.type]); + break; + } + return strbuf; +} +#endif + +#if defined(MQTT_SERVER) +char* MQTTFormat_toServerString(char* strbuf, int strbuflen, unsigned char* buf, int buflen) +{ + int index = 0; + int rem_length = 0; + MQTTHeader header = {0}; + int strindex = 0; + + header.byte = buf[index++]; + index += MQTTPacket_decodeBuf(&buf[index], &rem_length); + + switch (header.bits.type) + { + case CONNECT: + { + MQTTPacket_connectData data; + int rc; + if ((rc = MQTTDeserialize_connect(&data, buf, buflen)) == 1) + strindex = MQTTStringFormat_connect(strbuf, strbuflen, &data); + } + break; + case PUBLISH: + { + unsigned char dup, retained, *payload; + unsigned short packetid; + int qos, payloadlen; + MQTTString topicName = MQTTString_initializer; + if (MQTTDeserialize_publish(&dup, &qos, &retained, &packetid, &topicName, + &payload, &payloadlen, buf, buflen) == 1) + strindex = MQTTStringFormat_publish(strbuf, strbuflen, dup, qos, retained, packetid, + topicName, payload, payloadlen); + } + break; + case PUBACK: + case PUBREC: + case PUBREL: + case PUBCOMP: + { + unsigned char packettype, dup; + unsigned short packetid; + if (MQTTDeserialize_ack(&packettype, &dup, &packetid, buf, buflen) == 1) + strindex = MQTTStringFormat_ack(strbuf, strbuflen, packettype, dup, packetid); + } + break; + case SUBSCRIBE: + { + unsigned char dup; + unsigned short packetid; + int maxcount = 1, count = 0; + MQTTString topicFilters[1]; + int requestedQoSs[1]; + if (MQTTDeserialize_subscribe(&dup, &packetid, maxcount, &count, + topicFilters, requestedQoSs, buf, buflen) == 1) + strindex = MQTTStringFormat_subscribe(strbuf, strbuflen, dup, packetid, count, topicFilters, requestedQoSs);; + } + break; + case UNSUBSCRIBE: + { + unsigned char dup; + unsigned short packetid; + int maxcount = 1, count = 0; + MQTTString topicFilters[1]; + if (MQTTDeserialize_unsubscribe(&dup, &packetid, maxcount, &count, topicFilters, buf, buflen) == 1) + strindex = MQTTStringFormat_unsubscribe(strbuf, strbuflen, dup, packetid, count, topicFilters); + } + break; + case PINGREQ: + case PINGRESP: + case DISCONNECT: + strindex = snprintf(strbuf, strbuflen, "%s", MQTTPacket_names[header.bits.type]); + break; + } + strbuf[strbuflen] = '\0'; + return strbuf; +} +#endif diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTFormat.h b/APP_Framework/Applications/app_test/test_mqttclient/MQTTFormat.h new file mode 100644 index 000000000..47b0c4143 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTFormat.h @@ -0,0 +1,37 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#if !defined(MQTTFORMAT_H) +#define MQTTFORMAT_H + +#include "StackTrace.h" +#include "MQTTPacket.h" + +const char* MQTTPacket_getName(unsigned short packetid); +int MQTTStringFormat_connect(char* strbuf, int strbuflen, MQTTPacket_connectData* data); +int MQTTStringFormat_connack(char* strbuf, int strbuflen, unsigned char connack_rc, unsigned char sessionPresent); +int MQTTStringFormat_publish(char* strbuf, int strbuflen, unsigned char dup, int qos, unsigned char retained, + unsigned short packetid, MQTTString topicName, unsigned char* payload, int payloadlen); +int MQTTStringFormat_ack(char* strbuf, int strbuflen, unsigned char packettype, unsigned char dup, unsigned short packetid); +int MQTTStringFormat_subscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, int count, + MQTTString topicFilters[], int requestedQoSs[]); +int MQTTStringFormat_suback(char* strbuf, int strbuflen, unsigned short packetid, int count, int* grantedQoSs); +int MQTTStringFormat_unsubscribe(char* strbuf, int strbuflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]); +char* MQTTFormat_toClientString(char* strbuf, int strbuflen, unsigned char* buf, int buflen); +char* MQTTFormat_toServerString(char* strbuf, int strbuflen, unsigned char* buf, int buflen); + +#endif diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.c new file mode 100644 index 000000000..4f1f95a78 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.c @@ -0,0 +1,412 @@ +/******************************************************************************* + * Copyright (c) 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 + * Sergio R. Caprile - non-blocking packet read functions for stream transport + *******************************************************************************/ + +#include "StackTrace.h" +#include "MQTTPacket.h" + +#include + +/** + * Encodes the message length according to the MQTT algorithm + * @param buf the buffer into which the encoded data is written + * @param length the length to be encoded + * @return the number of bytes written to buffer + */ +int MQTTPacket_encode(unsigned char* buf, int length) +{ + int rc = 0; + + FUNC_ENTRY; + do + { + char d = length % 128; + length /= 128; + /* if there are more digits to encode, set the top bit of this digit */ + if (length > 0) + d |= 0x80; + buf[rc++] = d; + } while (length > 0); + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Decodes the message length according to the MQTT algorithm + * @param getcharfn pointer to function to read the next character from the data source + * @param value the decoded length returned + * @return the number of bytes read from the socket + */ +int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value) +{ + unsigned char c; + int multiplier = 1; + int len = 0; +#define MAX_NO_OF_REMAINING_LENGTH_BYTES 4 + + FUNC_ENTRY; + *value = 0; + do + { + int rc = MQTTPACKET_READ_ERROR; + + if (++len > MAX_NO_OF_REMAINING_LENGTH_BYTES) + { + rc = MQTTPACKET_READ_ERROR; /* bad data */ + goto exit; + } + rc = (*getcharfn)(&c, 1); + if (rc != 1) + goto exit; + *value += (c & 127) * multiplier; + multiplier *= 128; + } while ((c & 128) != 0); +exit: + FUNC_EXIT_RC(len); + return len; +} + + +int MQTTPacket_len(int rem_len) +{ + rem_len += 1; /* header byte */ + + /* now remaining_length field */ + if (rem_len < 128) + rem_len += 1; + else if (rem_len < 16384) + rem_len += 2; + else if (rem_len < 2097151) + rem_len += 3; + else + rem_len += 4; + return rem_len; +} + + +static unsigned char* bufptr; + +int bufchar(unsigned char* c, int count) +{ + int i; + + for (i = 0; i < count; ++i) + *c = *bufptr++; + return count; +} + + +int MQTTPacket_decodeBuf(unsigned char* buf, int* value) +{ + bufptr = buf; + return MQTTPacket_decode(bufchar, value); +} + + +/** + * Calculates an integer from two bytes read from the input buffer + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @return the integer value calculated + */ +int readInt(unsigned char** pptr) +{ + unsigned char* ptr = *pptr; + int len = 256*(*ptr) + (*(ptr+1)); + *pptr += 2; + return len; +} + + +/** + * Reads one character from the input buffer. + * @param pptr pointer to the input buffer - incremented by the number of bytes used & returned + * @return the character read + */ +char readChar(unsigned char** pptr) +{ + char c = **pptr; + (*pptr)++; + return c; +} + + +/** + * Writes one character to an output buffer. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param c the character to write + */ +void writeChar(unsigned char** pptr, char c) +{ + **pptr = c; + (*pptr)++; +} + + +/** + * Writes an integer as 2 bytes to an output buffer. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param anInt the integer to write + */ +void writeInt(unsigned char** pptr, int anInt) +{ + **pptr = (unsigned char)(anInt / 256); + (*pptr)++; + **pptr = (unsigned char)(anInt % 256); + (*pptr)++; +} + + +/** + * Writes a "UTF" string to an output buffer. Converts C string to length-delimited. + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param string the C string to write + */ +void writeCString(unsigned char** pptr, const char* string) +{ + int len = strlen(string); + writeInt(pptr, len); + memcpy(*pptr, string, len); + *pptr += len; +} + + +int getLenStringLen(char* ptr) +{ + int len = 256*((unsigned char)(*ptr)) + (unsigned char)(*(ptr+1)); + return len; +} + + +void writeMQTTString(unsigned char** pptr, MQTTString mqttstring) +{ + if (mqttstring.lenstring.len > 0) + { + writeInt(pptr, mqttstring.lenstring.len); + memcpy(*pptr, mqttstring.lenstring.data, mqttstring.lenstring.len); + *pptr += mqttstring.lenstring.len; + } + else if (mqttstring.cstring) + writeCString(pptr, mqttstring.cstring); + else + writeInt(pptr, 0); +} + + +/** + * @param mqttstring the MQTTString structure into which the data is to be read + * @param pptr pointer to the output buffer - incremented by the number of bytes used & returned + * @param enddata pointer to the end of the data: do not read beyond + * @return 1 if successful, 0 if not + */ +int readMQTTLenString(MQTTString* mqttstring, unsigned char** pptr, unsigned char* enddata) +{ + int rc = 0; + + FUNC_ENTRY; + /* the first two bytes are the length of the string */ + if (enddata - (*pptr) > 1) /* enough length to read the integer? */ + { + mqttstring->lenstring.len = readInt(pptr); /* increments pptr to point past length */ + if (&(*pptr)[mqttstring->lenstring.len] <= enddata) + { + mqttstring->lenstring.data = (char*)*pptr; + *pptr += mqttstring->lenstring.len; + rc = 1; + } + } + mqttstring->cstring = NULL; + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Return the length of the MQTTstring - C string if there is one, otherwise the length delimited string + * @param mqttstring the string to return the length of + * @return the length of the string + */ +int MQTTstrlen(MQTTString mqttstring) +{ + int rc = 0; + + if (mqttstring.cstring) + rc = strlen(mqttstring.cstring); + else + rc = mqttstring.lenstring.len; + return rc; +} + + +/** + * Compares an MQTTString to a C string + * @param a the MQTTString to compare + * @param bptr the C string to compare + * @return boolean - equal or not + */ +int MQTTPacket_equals(MQTTString* a, char* bptr) +{ + int alen = 0, + blen = 0; + char *aptr; + + if (a->cstring) + { + aptr = a->cstring; + alen = strlen(a->cstring); + } + else + { + aptr = a->lenstring.data; + alen = a->lenstring.len; + } + blen = strlen(bptr); + + return (alen == blen) && (strncmp(aptr, bptr, alen) == 0); +} + + +/** + * Helper function to read packet data from some source into a buffer + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param getfn pointer to a function which will read any number of bytes from the needed source + * @return integer MQTT packet type, or -1 on error + * @note the whole message must fit into the caller's buffer + */ +int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int)) +{ + int rc = -1; + MQTTHeader header = {0}; + int len = 0; + int rem_len = 0; + + /* 1. read the header byte. This has the packet type in it */ + if ((*getfn)(buf, 1) != 1) + goto exit; + + len = 1; + /* 2. read the remaining length. This is variable in itself */ + MQTTPacket_decode(getfn, &rem_len); + len += MQTTPacket_encode(buf + 1, rem_len); /* put the original remaining length back into the buffer */ + + /* 3. read the rest of the buffer using a callback to supply the rest of the data */ + if((rem_len + len) > buflen) + goto exit; + if (rem_len && ((*getfn)(buf + len, rem_len) != rem_len)) + goto exit; + + header.byte = buf[0]; + rc = header.bits.type; +exit: + return rc; +} + +/** + * Decodes the message length according to the MQTT algorithm, non-blocking + * @param trp pointer to a transport structure holding what is needed to solve getting data from it + * @param value the decoded length returned + * @return integer the number of bytes read from the socket, 0 for call again, or -1 on error + */ +static int MQTTPacket_decodenb(MQTTTransport *trp) +{ + unsigned char c; + int rc = MQTTPACKET_READ_ERROR; + + FUNC_ENTRY; + if(trp->len == 0){ /* initialize on first call */ + trp->multiplier = 1; + trp->rem_len = 0; + } + do { + int frc; + if (trp->len >= MAX_NO_OF_REMAINING_LENGTH_BYTES) + goto exit; + if ((frc=(*trp->getfn)(trp->sck, &c, 1)) == -1) + goto exit; + if (frc == 0){ + rc = 0; + goto exit; + } + ++(trp->len); + trp->rem_len += (c & 127) * trp->multiplier; + trp->multiplier *= 128; + } while ((c & 128) != 0); + rc = trp->len; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + +/** + * Helper function to read packet data from some source into a buffer, non-blocking + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param trp pointer to a transport structure holding what is needed to solve getting data from it + * @return integer MQTT packet type, 0 for call again, or -1 on error + * @note the whole message must fit into the caller's buffer + */ +int MQTTPacket_readnb(unsigned char* buf, int buflen, MQTTTransport *trp) +{ + int rc = -1, frc; + MQTTHeader header = {0}; + + switch(trp->state){ + default: + trp->state = 0; + /*FALLTHROUGH*/ + case 0: + /* read the header byte. This has the packet type in it */ + if ((frc=(*trp->getfn)(trp->sck, buf, 1)) == -1) + goto exit; + if (frc == 0) + return 0; + trp->len = 0; + ++trp->state; + /*FALLTHROUGH*/ + /* read the remaining length. This is variable in itself */ + case 1: + if((frc=MQTTPacket_decodenb(trp)) == MQTTPACKET_READ_ERROR) + goto exit; + if(frc == 0) + return 0; + trp->len = 1 + MQTTPacket_encode(buf + 1, trp->rem_len); /* put the original remaining length back into the buffer */ + if((trp->rem_len + trp->len) > buflen) + goto exit; + ++trp->state; + /*FALLTHROUGH*/ + case 2: + if(trp->rem_len){ + /* read the rest of the buffer using a callback to supply the rest of the data */ + if ((frc=(*trp->getfn)(trp->sck, buf + trp->len, trp->rem_len)) == -1) + goto exit; + if (frc == 0) + return 0; + trp->rem_len -= frc; + trp->len += frc; + if(trp->rem_len) + return 0; + } + header.byte = buf[0]; + rc = header.bits.type; + break; + } + +exit: + trp->state = 0; + return rc; +} + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.h b/APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.h new file mode 100644 index 000000000..a1c5038d8 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.h @@ -0,0 +1,134 @@ +/******************************************************************************* + * Copyright (c) 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 + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ +#include + +#ifndef MQTTPACKET_H_ +#define MQTTPACKET_H_ + +#if defined(__cplusplus) /* If this is a C++ compiler, use C linkage */ +extern "C" { +#endif + +#if defined(WIN32_DLL) || defined(WIN64_DLL) + #define DLLImport __declspec(dllimport) + #define DLLExport __declspec(dllexport) +#elif defined(LINUX_SO) + #define DLLImport extern + #define DLLExport __attribute__ ((visibility ("default"))) +#else + #define DLLImport + #define DLLExport +#endif + +enum errors +{ + MQTTPACKET_BUFFER_TOO_SHORT = -2, + MQTTPACKET_READ_ERROR = -1, + MQTTPACKET_READ_COMPLETE +}; + +enum msgTypes +{ + CONNECT = 1, CONNACK, PUBLISH, PUBACK, PUBREC, PUBREL, + PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK, + PINGREQ, PINGRESP, DISCONNECT +}; + +/** + * Bitfields for the MQTT header byte. + */ +typedef union +{ + unsigned char byte; /**< the whole byte */ +#if defined(REVERSED) + struct + { + unsigned int type : 4; /**< message type nibble */ + unsigned int dup : 1; /**< DUP flag bit */ + unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */ + unsigned int retain : 1; /**< retained flag bit */ + } bits; +#else + struct + { + unsigned int retain : 1; /**< retained flag bit */ + unsigned int qos : 2; /**< QoS value, 0, 1 or 2 */ + unsigned int dup : 1; /**< DUP flag bit */ + unsigned int type : 4; /**< message type nibble */ + } bits; +#endif +} MQTTHeader; + +typedef struct +{ + int len; + char* data; +} MQTTLenString; + +typedef struct +{ + char* cstring; + MQTTLenString lenstring; +} MQTTString; + +#define MQTTString_initializer {NULL, {0, NULL}} + +int MQTTstrlen(MQTTString mqttstring); + +#include "MQTTConnect.h" +#include "MQTTPublish.h" +#include "MQTTSubscribe.h" +#include "MQTTUnsubscribe.h" +#include "MQTTFormat.h" + +DLLExport int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char type, unsigned char dup, unsigned short packetid); +DLLExport int MQTTDeserialize_ack(unsigned char* packettype, unsigned char* dup, unsigned short* packetid, unsigned char* buf, int buflen); + +int MQTTPacket_len(int rem_len); +DLLExport int MQTTPacket_equals(MQTTString* a, char* b); + +DLLExport int MQTTPacket_encode(unsigned char* buf, int length); +int MQTTPacket_decode(int (*getcharfn)(unsigned char*, int), int* value); +int MQTTPacket_decodeBuf(unsigned char* buf, int* value); + +int readInt(unsigned char** pptr); +char readChar(unsigned char** pptr); +void writeChar(unsigned char** pptr, char c); +void writeInt(unsigned char** pptr, int anInt); +int readMQTTLenString(MQTTString* mqttstring, unsigned char** pptr, unsigned char* enddata); +void writeCString(unsigned char** pptr, const char* string); +void writeMQTTString(unsigned char** pptr, MQTTString mqttstring); + +DLLExport int MQTTPacket_read(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int)); + +typedef struct { + int (*getfn)(void *, unsigned char*, int); /* must return -1 for error, 0 for call again, or the number of bytes read */ + void *sck; /* pointer to whatever the system may use to identify the transport */ + int multiplier; + int rem_len; + int len; + char state; +}MQTTTransport; + +int MQTTPacket_readnb(unsigned char* buf, int buflen, MQTTTransport *trp); + +#ifdef __cplusplus /* If this is a C++ compiler, use C linkage */ +} +#endif + + +#endif /* MQTTPACKET_H_ */ diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTPublish.h b/APP_Framework/Applications/app_test/test_mqttclient/MQTTPublish.h new file mode 100644 index 000000000..88aca927e --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTPublish.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 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 + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTPUBLISH_H_ +#define MQTTPUBLISH_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid, + MQTTString topicName, unsigned char* payload, int payloadlen); + +DLLExport int MQTTDeserialize_publish(unsigned char* dup, int* qos, unsigned char* retained, unsigned short* packetid, MQTTString* topicName, + unsigned char** payload, int32_t* payloadlen, unsigned char* buf, int len); + +DLLExport int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid); +DLLExport int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid); +DLLExport int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid); + +#endif /* MQTTPUBLISH_H_ */ diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTSerializePublish.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTSerializePublish.c new file mode 100644 index 000000000..77a58b54a --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTSerializePublish.c @@ -0,0 +1,169 @@ +/******************************************************************************* + * Copyright (c) 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 - fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=453144 + *******************************************************************************/ + +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + + +/** + * Determines the length of the MQTT publish packet that would be produced using the supplied parameters + * @param qos the MQTT QoS of the publish (packetid is omitted for QoS 0) + * @param topicName the topic name to be used in the publish + * @param payloadlen the length of the payload to be sent + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_publishLength(int qos, MQTTString topicName, int payloadlen) +{ + int len = 0; + + len += 2 + MQTTstrlen(topicName) + payloadlen; + if (qos > 0) + len += 2; /* packetid */ + return len; +} + + +/** + * Serializes the supplied publish data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param dup integer - the MQTT dup flag + * @param qos integer - the MQTT QoS value + * @param retained integer - the MQTT retained flag + * @param packetid integer - the MQTT packet identifier + * @param topicName MQTTString - the MQTT topic in the publish + * @param payload byte buffer - the MQTT publish payload + * @param payloadlen integer - the length of the MQTT payload + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_publish(unsigned char* buf, int buflen, unsigned char dup, int qos, unsigned char retained, unsigned short packetid, + MQTTString topicName, unsigned char* payload, int payloadlen) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_publishLength(qos, topicName, payloadlen)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.bits.type = PUBLISH; + header.bits.dup = dup; + header.bits.qos = qos; + header.bits.retain = retained; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeMQTTString(&ptr, topicName); + + if (qos > 0) + writeInt(&ptr, packetid); + + memcpy(ptr, payload, payloadlen); + ptr += payloadlen; + + rc = ptr - buf; + +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Serializes the ack packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param type the MQTT packet type + * @param dup the MQTT dup flag + * @param packetid the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_ack(unsigned char* buf, int buflen, unsigned char packettype, unsigned char dup, unsigned short packetid) +{ + MQTTHeader header = {0}; + int rc = 0; + unsigned char *ptr = buf; + + FUNC_ENTRY; + if (buflen < 4) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.bits.type = packettype; + header.bits.dup = dup; + header.bits.qos = (packettype == PUBREL) ? 1 : 0; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */ + writeInt(&ptr, packetid); + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes a puback packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_puback(unsigned char* buf, int buflen, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBACK, 0, packetid); +} + + +/** + * Serializes a pubrel packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pubrel(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBREL, dup, packetid); +} + + +/** + * Serializes a pubrel packet into the supplied buffer. + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @return serialized length, or error if 0 + */ +int MQTTSerialize_pubcomp(unsigned char* buf, int buflen, unsigned short packetid) +{ + return MQTTSerialize_ack(buf, buflen, PUBCOMP, 0, packetid); +} + + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribe.h b/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribe.h new file mode 100644 index 000000000..4b702bd59 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribe.h @@ -0,0 +1,39 @@ +/******************************************************************************* + * Copyright (c) 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 + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTSUBSCRIBE_H_ +#define MQTTSUBSCRIBE_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[], int32_t requestedQoSs[]); + +DLLExport int MQTTDeserialize_subscribe(unsigned char* dup, unsigned short* packetid, + int maxcount, int* count, MQTTString topicFilters[], int requestedQoSs[], unsigned char* buf, int len); + +DLLExport int MQTTSerialize_suback(unsigned char* buf, int buflen, unsigned short packetid, int count, int* grantedQoSs); + +DLLExport int MQTTDeserialize_suback(unsigned short* packetid, int maxcount, int32_t* count, int32_t grantedQoSs[], unsigned char* buf, int len); + + +#endif /* MQTTSUBSCRIBE_H_ */ diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribeClient.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribeClient.c new file mode 100644 index 000000000..dc131882d --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribeClient.c @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + +/** + * Determines the length of the MQTT subscribe packet that would be produced using the supplied parameters + * @param count the number of topic filter strings in topicFilters + * @param topicFilters the array of topic filter strings to be used in the publish + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_subscribeLength(int count, MQTTString topicFilters[]) +{ + int i; + int len = 2; /* packetid */ + + for (i = 0; i < count; ++i) + len += 2 + MQTTstrlen(topicFilters[i]) + 1; /* length + topic + req_qos */ + return len; +} + + +/** + * Serializes the supplied subscribe data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied bufferr + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @param count - number of members in the topicFilters and reqQos arrays + * @param topicFilters - array of topic filter names + * @param requestedQoSs - array of requested QoS + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_subscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, int count, + MQTTString topicFilters[], int32_t requestedQoSs[]) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = 0; + int i = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_subscribeLength(count, topicFilters)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = SUBSCRIBE; + header.bits.dup = dup; + header.bits.qos = 1; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeInt(&ptr, packetid); + + for (i = 0; i < count; ++i) + { + writeMQTTString(&ptr, topicFilters[i]); + writeChar(&ptr, requestedQoSs[i]); + } + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + + +/** + * Deserializes the supplied (wire) buffer into suback data + * @param packetid returned integer - the MQTT packet identifier + * @param maxcount - the maximum number of members allowed in the grantedQoSs array + * @param count returned integer - number of members in the grantedQoSs array + * @param grantedQoSs returned array of integers - the granted qualities of service + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_suback(unsigned short* packetid, int maxcount, int32_t* count, int32_t grantedQoSs[], unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != SUBACK) + goto exit; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + if (enddata - curdata < 2) + goto exit; + + *packetid = readInt(&curdata); + + *count = 0; + while (curdata < enddata) + { + if (*count > maxcount) + { + rc = -1; + goto exit; + } + grantedQoSs[(*count)++] = readChar(&curdata); + } + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribeServer.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribeServer.c new file mode 100644 index 000000000..5579645fe --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTSubscribeServer.c @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + + +/** + * Deserializes the supplied (wire) buffer into subscribe data + * @param dup integer returned - the MQTT dup flag + * @param packetid integer returned - the MQTT packet identifier + * @param maxcount - the maximum number of members allowed in the topicFilters and requestedQoSs arrays + * @param count - number of members in the topicFilters and requestedQoSs arrays + * @param topicFilters - array of topic filter names + * @param requestedQoSs - array of requested QoS + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTDeserialize_subscribe(unsigned char* dup, unsigned short* packetid, int maxcount, int* count, MQTTString topicFilters[], + int requestedQoSs[], unsigned char* buf, int buflen) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = -1; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != SUBSCRIBE) + goto exit; + *dup = header.bits.dup; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + *packetid = readInt(&curdata); + + *count = 0; + while (curdata < enddata) + { + if (!readMQTTLenString(&topicFilters[*count], &curdata, enddata)) + goto exit; + if (curdata >= enddata) /* do we have enough data to read the req_qos version byte? */ + goto exit; + requestedQoSs[*count] = readChar(&curdata); + (*count)++; + } + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes the supplied suback data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @param count - number of members in the grantedQoSs array + * @param grantedQoSs - array of granted QoS + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_suback(unsigned char* buf, int buflen, unsigned short packetid, int count, int* grantedQoSs) +{ + MQTTHeader header = {0}; + int rc = -1; + unsigned char *ptr = buf; + int i; + + FUNC_ENTRY; + if (buflen < 2 + count) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = SUBACK; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2 + count); /* write remaining length */ + + writeInt(&ptr, packetid); + + for (i = 0; i < count; ++i) + writeChar(&ptr, grantedQoSs[i]); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribe.h b/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribe.h new file mode 100644 index 000000000..355ca9a42 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribe.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 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 + * Xiang Rong - 442039 Add makefile to Embedded C client + *******************************************************************************/ + +#ifndef MQTTUNSUBSCRIBE_H_ +#define MQTTUNSUBSCRIBE_H_ + +#if !defined(DLLImport) + #define DLLImport +#endif +#if !defined(DLLExport) + #define DLLExport +#endif + +DLLExport int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]); + +DLLExport int MQTTDeserialize_unsubscribe(unsigned char* dup, unsigned short* packetid, int max_count, int* count, MQTTString topicFilters[], + unsigned char* buf, int len); + +DLLExport int MQTTSerialize_unsuback(unsigned char* buf, int buflen, unsigned short packetid); + +DLLExport int MQTTDeserialize_unsuback(unsigned short* packetid, unsigned char* buf, int len); + +#endif /* MQTTUNSUBSCRIBE_H_ */ diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribeClient.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribeClient.c new file mode 100644 index 000000000..e7ec53021 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribeClient.c @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + +/** + * Determines the length of the MQTT unsubscribe packet that would be produced using the supplied parameters + * @param count the number of topic filter strings in topicFilters + * @param topicFilters the array of topic filter strings to be used in the publish + * @return the length of buffer needed to contain the serialized version of the packet + */ +int MQTTSerialize_unsubscribeLength(int count, MQTTString topicFilters[]) +{ + int i; + int len = 2; /* packetid */ + + for (i = 0; i < count; ++i) + len += 2 + MQTTstrlen(topicFilters[i]); /* length + topic*/ + return len; +} + + +/** + * Serializes the supplied unsubscribe data into the supplied buffer, ready for sending + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @param dup integer - the MQTT dup flag + * @param packetid integer - the MQTT packet identifier + * @param count - number of members in the topicFilters array + * @param topicFilters - array of topic filter names + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_unsubscribe(unsigned char* buf, int buflen, unsigned char dup, unsigned short packetid, + int count, MQTTString topicFilters[]) +{ + unsigned char *ptr = buf; + MQTTHeader header = {0}; + int rem_len = 0; + int rc = -1; + int i = 0; + + FUNC_ENTRY; + if (MQTTPacket_len(rem_len = MQTTSerialize_unsubscribeLength(count, topicFilters)) > buflen) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + + header.byte = 0; + header.bits.type = UNSUBSCRIBE; + header.bits.dup = dup; + header.bits.qos = 1; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, rem_len); /* write remaining length */; + + writeInt(&ptr, packetid); + + for (i = 0; i < count; ++i) + writeMQTTString(&ptr, topicFilters[i]); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Deserializes the supplied (wire) buffer into unsuback data + * @param packetid returned integer - the MQTT packet identifier + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return error code. 1 is success, 0 is failure + */ +int MQTTDeserialize_unsuback(unsigned short* packetid, unsigned char* buf, int buflen) +{ + unsigned char type = 0; + unsigned char dup = 0; + int rc = 0; + + FUNC_ENTRY; + rc = MQTTDeserialize_ack(&type, &dup, packetid, buf, buflen); + if (type == UNSUBACK) + rc = 1; + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribeServer.c b/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribeServer.c new file mode 100644 index 000000000..42b6102a7 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTUnsubscribeServer.c @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ + +#include "MQTTPacket.h" +#include "StackTrace.h" + +#include + + +/** + * Deserializes the supplied (wire) buffer into unsubscribe data + * @param dup integer returned - the MQTT dup flag + * @param packetid integer returned - the MQTT packet identifier + * @param maxcount - the maximum number of members allowed in the topicFilters and requestedQoSs arrays + * @param count - number of members in the topicFilters and requestedQoSs arrays + * @param topicFilters - array of topic filter names + * @param buf the raw buffer data, of the correct length determined by the remaining length field + * @param buflen the length in bytes of the data in the supplied buffer + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTDeserialize_unsubscribe(unsigned char* dup, unsigned short* packetid, int maxcount, int* count, MQTTString topicFilters[], + unsigned char* buf, int len) +{ + MQTTHeader header = {0}; + unsigned char* curdata = buf; + unsigned char* enddata = NULL; + int rc = 0; + int mylen = 0; + + FUNC_ENTRY; + header.byte = readChar(&curdata); + if (header.bits.type != UNSUBSCRIBE) + goto exit; + *dup = header.bits.dup; + + curdata += (rc = MQTTPacket_decodeBuf(curdata, &mylen)); /* read remaining length */ + enddata = curdata + mylen; + + *packetid = readInt(&curdata); + + *count = 0; + while (curdata < enddata) + { + if (!readMQTTLenString(&topicFilters[*count], &curdata, enddata)) + goto exit; + (*count)++; + } + + rc = 1; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + +/** + * Serializes the supplied unsuback data into the supplied buffer, ready for sending + * @param buf the buffer into which the packet will be serialized + * @param buflen the length in bytes of the supplied buffer + * @param packetid integer - the MQTT packet identifier + * @return the length of the serialized data. <= 0 indicates error + */ +int MQTTSerialize_unsuback(unsigned char* buf, int buflen, unsigned short packetid) +{ + MQTTHeader header = {0}; + int rc = 0; + unsigned char *ptr = buf; + + FUNC_ENTRY; + if (buflen < 2) + { + rc = MQTTPACKET_BUFFER_TOO_SHORT; + goto exit; + } + header.byte = 0; + header.bits.type = UNSUBACK; + writeChar(&ptr, header.byte); /* write header */ + + ptr += MQTTPacket_encode(ptr, 2); /* write remaining length */ + + writeInt(&ptr, packetid); + + rc = ptr - buf; +exit: + FUNC_EXIT_RC(rc); + return rc; +} + + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/Makefile b/APP_Framework/Applications/app_test/test_mqttclient/Makefile new file mode 100644 index 000000000..437645fde --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/Makefile @@ -0,0 +1,14 @@ +SRC_FILES += test_mqttclient.c\ + MQTTPacket.c\ + MQTTConnectClient.c \ + MQTTConnectServer.c \ + MQTTDeserializePublish.c \ + MQTTFormat.c \ + MQTTSerializePublish.c \ + MQTTSubscribeClient.c \ + MQTTSubscribeServer.c \ + MQTTUnsubscribeClient.c \ + MQTTUnsubscribeServer.c \ + transport.c + +include $(KERNEL_ROOT)/compiler.mk \ No newline at end of file diff --git a/APP_Framework/Applications/app_test/test_mqttclient/README.md b/APP_Framework/Applications/app_test/test_mqttclient/README.md new file mode 100644 index 000000000..2aeca82e6 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/README.md @@ -0,0 +1,15 @@ +本代码实现了MQTT对服务器订阅主体并发送信息功能 + +测试流程为: + +首先执行setip命令,设置设备ip地址 + +![图片2](.\图片2.png) + +随后执行“MqttSocketSendTest 服务器ip”命令,订阅主题并发送test信息 + +![图片1](.\图片1.png) + +最终服务器接收到信息,如图所示 + +![图片3](.\图片3.png) diff --git a/APP_Framework/Applications/app_test/test_mqttclient/StackTrace.h b/APP_Framework/Applications/app_test/test_mqttclient/StackTrace.h new file mode 100644 index 000000000..2808a0d18 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/StackTrace.h @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 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 - fix for bug #434081 + *******************************************************************************/ + +#ifndef STACKTRACE_H_ +#define STACKTRACE_H_ + +#include +#define NOSTACKTRACE 1 + +#if defined(NOSTACKTRACE) +#define FUNC_ENTRY +#define FUNC_ENTRY_NOLOG +#define FUNC_ENTRY_MED +#define FUNC_ENTRY_MAX +#define FUNC_EXIT +#define FUNC_EXIT_NOLOG +#define FUNC_EXIT_MED +#define FUNC_EXIT_MAX +#define FUNC_EXIT_RC(x) +#define FUNC_EXIT_MED_RC(x) +#define FUNC_EXIT_MAX_RC(x) + +#else + +#if defined(WIN32) +#define inline __inline +#define FUNC_ENTRY StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MINIMUM) +#define FUNC_ENTRY_NOLOG StackTrace_entry(__FUNCTION__, __LINE__, -1) +#define FUNC_ENTRY_MED StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MEDIUM) +#define FUNC_ENTRY_MAX StackTrace_entry(__FUNCTION__, __LINE__, TRACE_MAXIMUM) +#define FUNC_EXIT StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MINIMUM) +#define FUNC_EXIT_NOLOG StackTrace_exit(__FUNCTION__, __LINE__, -1) +#define FUNC_EXIT_MED StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MEDIUM) +#define FUNC_EXIT_MAX StackTrace_exit(__FUNCTION__, __LINE__, NULL, TRACE_MAXIMUM) +#define FUNC_EXIT_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MINIMUM) +#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MEDIUM) +#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__FUNCTION__, __LINE__, &x, TRACE_MAXIMUM) +#else +#define FUNC_ENTRY StackTrace_entry(__func__, __LINE__, TRACE_MINIMUM) +#define FUNC_ENTRY_NOLOG StackTrace_entry(__func__, __LINE__, -1) +#define FUNC_ENTRY_MED StackTrace_entry(__func__, __LINE__, TRACE_MEDIUM) +#define FUNC_ENTRY_MAX StackTrace_entry(__func__, __LINE__, TRACE_MAXIMUM) +#define FUNC_EXIT StackTrace_exit(__func__, __LINE__, NULL, TRACE_MINIMUM) +#define FUNC_EXIT_NOLOG StackTrace_exit(__func__, __LINE__, NULL, -1) +#define FUNC_EXIT_MED StackTrace_exit(__func__, __LINE__, NULL, TRACE_MEDIUM) +#define FUNC_EXIT_MAX StackTrace_exit(__func__, __LINE__, NULL, TRACE_MAXIMUM) +#define FUNC_EXIT_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MINIMUM) +#define FUNC_EXIT_MED_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MEDIUM) +#define FUNC_EXIT_MAX_RC(x) StackTrace_exit(__func__, __LINE__, &x, TRACE_MAXIMUM) + +void StackTrace_entry(const char* name, int line, int trace); +void StackTrace_exit(const char* name, int line, void* return_value, int trace); + +void StackTrace_printStack(FILE* dest); +char* StackTrace_get(unsigned long); + +#endif + +#endif + + + + +#endif /* STACKTRACE_H_ */ diff --git a/APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.c b/APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.c new file mode 100644 index 000000000..c316935d5 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.c @@ -0,0 +1,728 @@ +#include +#include "test_mqttclient.h" +#include +#ifdef ADD_XIZI_FEATURES +#include +#include +#include "lwip/sys.h" +#include "lwip/api.h" + +#include "MQTTPacket.h" +#include "MQTTSubscribe.h" +#include "transport.h" +#include + +#endif + +#ifdef ADD_NUTTX_FEATURES +#include +#include +#include +#include "stdio.h" +#endif + + +#define MQTT_DEMO_BUF_SIZE 65535 +#define MQTT_DEMO_SEND_TIMES 20 +#define LWIP_MQTT_DEMO_TASK_STACK_SIZE 4096 +#define LWIP_MQTT_DEMO_TASK_PRIO 20 + +static char mqtt_demo_ipaddr[] = {192, 168, 130, 77}; +static char mqtt_demo_netmask[] = {255, 255, 254, 0}; +static char mqtt_demo_gwaddr[] = {192, 168, 130, 1}; + +static pthread_t mqtt_client_task; +static pthread_t mqtt_server_task; + +static uint16_t mqtt_socket_port = 1883; +static char mqtt_ip_str[128] = {192,168,100,1}; + +void MqttSocketConfigParam(char *ip_str) +{ + int ip1, ip2, ip3, ip4, port = 0; + + if(ip_str == NULL) + return; + + if(sscanf(ip_str, "%d.%d.%d.%d:%d", &ip1, &ip2, &ip3, &ip4, &port)) { + printf("config ip %s port %d\n", ip_str, port); + strcpy(mqtt_ip_str, ip_str); + if(port) + mqtt_socket_port = port; + return; + } + + if(sscanf(ip_str, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4)) { + printf("config ip %s\n", ip_str); + strcpy(mqtt_ip_str, ip_str); + } +} + +MQTT_USER_MSG mqtt_user_msg; + +uint8_t MQTT_Connect(void) +{ + MQTTPacket_connectData data = MQTTPacket_connectData_initializer; + uint8_t buf[200]; + int buflen = sizeof(buf); + int len = 0; + data.clientID.cstring = CLIENT_ID; //随机 + data.keepAliveInterval = KEEPLIVE_TIME; //保持活跃 + data.username.cstring = USER_NAME; //用户名 + data.password.cstring = PASSWORD; //密钥 + data.MQTTVersion = MQTT_VERSION; //3表示3.1版本,4表示3.11版本 + data.cleansession = 1; + //组装消息 + len = MQTTSerialize_connect((unsigned char *)buf, buflen, &data); + //发送消息 + transport_sendPacketBuffer(buf, len); + + /* 等待连接响应 */ + if (MQTTPacket_read(buf, buflen, transport_getdata) == CONNACK) + { + unsigned char sessionPresent, connack_rc; + if (MQTTDeserialize_connack(&sessionPresent, &connack_rc, buf, buflen) != 1 || connack_rc != 0) + { + lw_print("无法连接,错误代码是: %d!\n", connack_rc); + return Connect_NOK; + } + else + { + lw_print("用户名与密钥验证成功,MQTT连接成功!\n"); + return Connect_OK; + } + } + else + lw_print("MQTT连接无响应!\n"); + return Connect_NOTACK; +} + + +int32_t MQTT_PingReq(int32_t sock) +{ + int32_t len; + uint8_t buf[200]; + int32_t buflen = sizeof(buf); + fd_set readfd; + struct timeval tv; + tv.tv_sec = 5; + tv.tv_usec = 0; + + FD_ZERO(&readfd); + FD_SET(sock,&readfd); + + len = MQTTSerialize_pingreq(buf, buflen); + transport_sendPacketBuffer(buf, len); + + //等待可读事件 + if(select(sock+1,&readfd,NULL,NULL,&tv) == 0) + return -1; + + //有可读事件 + if(FD_ISSET(sock,&readfd) == 0) + return -2; + + if(MQTTPacket_read(buf, buflen, transport_getdata) != PINGRESP) + return -3; + + return 0; + +} + + +/************************************************************************ +** 函数名称: MQTTSubscribe +** 函数功能: 订阅消息 +** 入口参数: int32_t sock:套接字 +** int8_t *topic:主题 +** enum QoS pos:消息质量 +** 出口参数: >=0:发送成功 <0:发送失败 +** 备 注: +************************************************************************/ +int32_t MQTTSubscribe(int32_t sock,char *topic,enum QoS pos) +{ + static uint32_t PacketID = 0; + uint16_t packetidbk = 0; + int32_t conutbk = 0; + uint8_t buf[100]; + int32_t buflen = sizeof(buf); + MQTTString topicString = MQTTString_initializer; + int32_t len; + int32_t req_qos,qosbk; + + fd_set readfd; + struct timeval tv; + tv.tv_sec = 2; + tv.tv_usec = 0; + + FD_ZERO(&readfd); + FD_SET(sock,&readfd); + + //复制主题 + topicString.cstring = (char *)topic; + //订阅质量 + req_qos = pos; + + //串行化订阅消息 + len = MQTTSerialize_subscribe(buf, buflen, 0, PacketID++, 1, &topicString, &req_qos); + //发送TCP数据 + if(transport_sendPacketBuffer(buf, len) < 0) + return -1; + + //等待可读事件--等待超时 + if(select(sock+1,&readfd,NULL,NULL,&tv) == 0) + return -2; + //有可读事件--没有可读事件 + if(FD_ISSET(sock,&readfd) == 0) + return -3; + + //等待订阅返回--未收到订阅返回 + if(MQTTPacket_read(buf, buflen, transport_getdata) != SUBACK) + return -4; + + //拆订阅回应包 + if(MQTTDeserialize_suback(&packetidbk,1, &conutbk, &qosbk, buf, buflen) != 1) + return -5; + + //检测返回数据的正确性 + if((qosbk == 0x80)||(packetidbk != (PacketID-1))) + return -6; + + //订阅成功 + return 0; +} + +int32_t ReadPacketTimeout(int32_t sock,uint8_t *buf,int32_t buflen,uint32_t timeout) +{ + fd_set readfd; + struct timeval tv; + if(timeout != 0) + { + tv.tv_sec = timeout; + tv.tv_usec = 0; + FD_ZERO(&readfd); + FD_SET(sock,&readfd); + + + if(select(sock+1,&readfd,NULL,NULL,&tv) == 0) + return -1; + + if(FD_ISSET(sock,&readfd) == 0) + return -1; + } + + return MQTTPacket_read(buf, buflen, transport_getdata); +} + +void deliverMessage(MQTTString *TopicName,MQTTMessage *msg,MQTT_USER_MSG *mqtt_user_msg) +{ + //消息质量 + mqtt_user_msg->msgqos = msg->qos; + //保存消息 + memcpy(mqtt_user_msg->msg,msg->payload,msg->payloadlen); + mqtt_user_msg->msg[msg->payloadlen] = 0; + //保存消息长度 + mqtt_user_msg->msglenth = msg->payloadlen; + //消息主题 + memcpy((char *)mqtt_user_msg->topic,TopicName->lenstring.data,TopicName->lenstring.len); + mqtt_user_msg->topic[TopicName->lenstring.len] = 0; + //消息ID + mqtt_user_msg->packetid = msg->id; + //标明消息合法 + mqtt_user_msg->valid = 1; +} + +void UserMsgCtl(MQTT_USER_MSG *msg) +{ + //这里处理数据只是打印,用户可以在这里添加自己的处理方式 + lw_print("****收到订阅的消息******\n"); + //���غ�����Ϣ + switch(msg->msgqos) + { + case 0: + lw_print("MQTT>>消息质量QoS0\n"); + break; + case 1: + lw_print("MQTT>>消息质量QoS1\n"); + break; + case 2: + lw_print("MQTT>>消息质量QoS2\n"); + break; + default: + lw_print("MQTT>>错误的消息质量\n"); + break; + } + lw_print("MQTT>>消息主题:%s\n",msg->topic); + lw_print("MQTT>>消息内容:%s\n",msg->msg); + lw_print("MQTT>>消息长度:%d\n",msg->msglenth); + Proscess(msg->msg); + //处理完后销毁数据 + msg->valid = 0; +} + + +void mqtt_pktype_ctl(uint8_t packtype,uint8_t *buf,uint32_t buflen) +{ + MQTTMessage msg; + int32_t rc; + MQTTString receivedTopic; + uint32_t len; + lw_print("packtype:%d\n",packtype); + switch(packtype) + { + case PUBLISH: + + if(MQTTDeserialize_publish(&msg.dup,(int*)&msg.qos, &msg.retained, &msg.id, &receivedTopic, + (unsigned char **)&msg.payload, &msg.payloadlen, buf, buflen) != 1) + return; + + deliverMessage(&receivedTopic,&msg,&mqtt_user_msg); + + + if(msg.qos == QOS0) + { + //QOS0-不需要ACK + //直接处理数据 + UserMsgCtl(&mqtt_user_msg); + return; + } + //发送PUBACK消息 + if(msg.qos == QOS1) + { + len =MQTTSerialize_puback(buf,buflen,mqtt_user_msg.packetid); + if(len == 0) + return; + //发送返回 + if(transport_sendPacketBuffer(buf,len)<0) + return; + //返回后处理消息 + UserMsgCtl(&mqtt_user_msg); + return; + } + + //对于质量2,只需要发送PUBREC就可以了 + if(msg.qos == QOS2) + { + len = MQTTSerialize_ack(buf, buflen, PUBREC, 0, mqtt_user_msg.packetid); + if(len == 0) + return; + //发送返回 + transport_sendPacketBuffer(buf,len); + } + break; + case PUBREL: + //解析包数据,必须包ID相同才可以 + rc = MQTTDeserialize_ack(&msg.type,&msg.dup, &msg.id, buf,buflen); + if((rc != 1)||(msg.type != PUBREL)||(msg.id != mqtt_user_msg.packetid)) + return ; + //收到PUBREL,需要处理并抛弃数据 + if(mqtt_user_msg.valid == 1) + { + //返回后处理消息 + UserMsgCtl(&mqtt_user_msg); + } + //串行化PUBCMP消息 + len = MQTTSerialize_pubcomp(buf,buflen,msg.id); + if(len == 0) + return; + //发送返回--PUBCOMP + transport_sendPacketBuffer(buf,len); + break; + case PUBACK://等级1客户端推送数据后,服务器返回 + break; + case PUBREC://等级2客户端推送数据后,服务器返回 + break; + case PUBCOMP://等级2客户端推送PUBREL后,服务器返回 + break; + default: + break; + } +} + + +static void *MqttSocketRecvTask(void *arg) +{ +MQTT_START: + lw_print("Recv begin**********\n"); + int fd = -1, clientfd; + int recv_len; + int ret; + char *recv_buf; + struct sockaddr_in mqtt_addr; + socklen_t addr_len; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + lw_print("Socket error\n"); + return NULL; + } + + struct sockaddr_in mqtt_sock; + mqtt_sock.sin_family = AF_INET; + mqtt_sock.sin_port = htons(mqtt_socket_port); + mqtt_sock.sin_addr.s_addr = inet_addr(mqtt_ip_str); + + memset(&(mqtt_sock.sin_zero), 0, sizeof(mqtt_sock.sin_zero)); + + ret = connect(fd, (struct sockaddr *)&mqtt_sock, sizeof(struct sockaddr)); + + if (ret < 0) { + lw_print("Unable to connect %s:%d = %d\n", mqtt_ip_str, mqtt_socket_port, ret); + close(fd); + return NULL; + } + + lw_print("MQTT connect %s:%d success, begin to verify username and password.\n", mqtt_ip_str, mqtt_socket_port); + + if(MQTT_Connect() != Connect_OK) + { + lw_print("MQTT verify failed.\n"); + shutdown(fd, SHUT_WR); + recv(fd, NULL, (size_t)0, 0); + close(fd); + PrivTaskDelay(1000); + goto MQTT_START; + } + + lw_print("MQTT subscribe begin.\n"); + if(MQTTSubscribe(fd,(char *)TOPIC,QOS1) < 0) + { + lw_print("MQTT subscribe failed.\n"); + shutdown(fd, SHUT_WR); + recv(fd, NULL, (size_t)0, 0); + close(fd); + return NULL; + } + + lw_print("subscribe success.\n"); + + fd_set readfd; + uint8_t no_mqtt_msg_exchange = 1; + uint8_t buf[MSG_MAX_LEN]; + int32_t buflen = sizeof(buf); + int32_t type; + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 10; + + int32_t curtick=0; + + while(1) + { + // lw_print("waiting********\n"); + curtick +=1; + no_mqtt_msg_exchange = 1; + + FD_ZERO(&readfd); + FD_SET(fd,&readfd); + select(fd+1,&readfd,NULL,NULL,&tv); + + if(FD_ISSET(fd,&readfd) != 0) + { + + type = ReadPacketTimeout(fd,buf,buflen,0); + if(type != -1) + { + lw_print("ctl***********\n"); + mqtt_pktype_ctl(type,buf,buflen); + + no_mqtt_msg_exchange = 0; + + } + } + + if( curtick >(2*10000)) + { + curtick =0; + //判断是否有数据交换 + if(no_mqtt_msg_exchange == 0) + { + //如果有数据交换,这次就不需要发送PING消息 + continue; + } + + if(MQTT_PingReq(fd) < 0) + { + //重连服务器 + lw_print("发送保持活性ping失败....\n"); + goto CLOSE; + } + + + lw_print("发送保持活性ping作为心跳成功....\n"); + + no_mqtt_msg_exchange = 0; + } + } + +CLOSE: + lw_print("MQTT subscribe failed.\n"); + shutdown(fd, SHUT_WR); + recv(fd, NULL, (size_t)0, 0); + close(fd); + return NULL; + + +} + + + +void MqttSocketRecvTest(int argc, char *argv[]) +{ + if(argc >= 2) { + lw_print("lw: [%s] target ip %s\n", __func__, argv[1]); + MqttSocketConfigParam(argv[1]); + } + + + // ip4_addr_t dns_ip; + // netconn_gethostbyname(HOST_NAME, &dns_ip); + // char* host_ip = ip_ntoa(&dns_ip); + // lw_print("host name : %s , host_ip : %s\n",HOST_NAME,host_ip); + // MqttSocketConfigParam(host_ip); + + +#ifdef ADD_XIZI_FEATURES + lwip_config_tcp(0, mqtt_demo_ipaddr, mqtt_demo_netmask, mqtt_demo_gwaddr); + + pthread_attr_t attr; + attr.schedparam.sched_priority = LWIP_MQTT_DEMO_TASK_PRIO; + attr.stacksize = LWIP_MQTT_DEMO_TASK_STACK_SIZE; +#endif + +#ifdef ADD_NUTTX_FEATURES + pthread_attr_t attr = PTHREAD_ATTR_INITIALIZER; + attr.priority = LWIP_mqtt_DEMO_TASK_PRIO; + attr.stacksize = LWIP_mqtt_DEMO_TASK_STACK_SIZE; +#endif + + PrivTaskCreate(&mqtt_server_task, &attr, &MqttSocketRecvTask, NULL); + PrivTaskStartup(&mqtt_server_task); +} + +PRIV_SHELL_CMD_FUNCTION(MqttSocketRecvTest, a tcp send sample, PRIV_SHELL_CMD_MAIN_ATTR); + + +typedef struct +{ + uint8_t humi_high8bit; //ԭʼ���ݣ�ʪ�ȸ�8λ + uint8_t humi_low8bit; //ԭʼ���ݣ�ʪ�ȵ�8λ + uint8_t temp_high8bit; //ԭʼ���ݣ��¶ȸ�8λ + uint8_t temp_low8bit; //ԭʼ���ݣ��¶ȸ�8λ + uint8_t check_sum; //У��� + double humidity; //ʵ��ʪ�� + double temperature; //ʵ���¶� +} DHT11_Data_TypeDef; + + +uint16_t GetNextPackID(void) +{ + static uint16_t pubpacketid = 0; + return pubpacketid++; +} + +int32_t WaitForPacket(int32_t sock,uint8_t packettype,uint8_t times) +{ + int32_t type; + uint8_t buf[MSG_MAX_LEN]; + uint8_t n = 0; + int32_t buflen = sizeof(buf); + do + { + //读取数据包 + type = ReadPacketTimeout(sock,buf,buflen,2); + if(type != -1) + mqtt_pktype_ctl(type,buf,buflen); + n++; + }while((type != packettype)&&(n < times)); + //收到期望的包 + if(type == packettype) + return 0; + else + return -1; +} + +int32_t MQTTMsgPublish(int32_t sock, char *topic, int8_t qos, uint8_t* msg) +{ + int8_t retained = 0; //保留标志位 + uint32_t msg_len; //数据长度 + uint8_t buf[MSG_MAX_LEN]; + int32_t buflen = sizeof(buf),len; + MQTTString topicString = MQTTString_initializer; + uint16_t packid = 0,packetidbk; + + //填充主题 + topicString.cstring = (char *)topic; + + //填充数据包ID + if((qos == QOS1)||(qos == QOS2)) + { + packid = GetNextPackID(); + } + else + { + qos = QOS0; + retained = 0; + packid = 0; + } + + msg_len = strlen((char *)msg); + + //推送消息 + len = MQTTSerialize_publish(buf, buflen, 0, qos, retained, packid, topicString, (unsigned char*)msg, msg_len); + if(len <= 0) + return -1; + if(transport_sendPacketBuffer(buf, len) < 0) + return -2; + + //质量等级0,不需要返回 + if(qos == QOS0) + { + return 0; + } + + //等级1 + if(qos == QOS1) + { + //等待PUBACK + if(WaitForPacket(sock,PUBACK,5) < 0) + return -3; + return 1; + + } + //等级2 + if(qos == QOS2) + { + //等待PUBREC + if(WaitForPacket(sock,PUBREC,5) < 0) + return -3; + //发送PUBREL + len = MQTTSerialize_pubrel(buf, buflen,0, packetidbk); + if(len == 0) + return -4; + if(transport_sendPacketBuffer(buf, len) < 0) + return -6; + //等待PUBCOMP + if(WaitForPacket(sock,PUBREC,5) < 0) + return -7; + return 2; + } + //等级错误 + return -8; +} + + +static void *MqttSocketSendTask(void *arg) +{ + + int fd = -1, clientfd; + int recv_len; + int ret; + char *recv_buf; + struct sockaddr_in mqtt_addr; + socklen_t addr_len; + + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + lw_print("Socket error\n"); + return NULL; + } + + struct sockaddr_in mqtt_sock; + mqtt_sock.sin_family = AF_INET; + mqtt_sock.sin_port = htons(mqtt_socket_port); + mqtt_sock.sin_addr.s_addr = inet_addr(mqtt_ip_str); + + memset(&(mqtt_sock.sin_zero), 0, sizeof(mqtt_sock.sin_zero)); + + ret = connect(fd, (struct sockaddr *)&mqtt_sock, sizeof(struct sockaddr)); + + if (ret < 0) { + lw_print("Unable to connect %s:%d = %d\n", mqtt_ip_str, mqtt_socket_port, ret); + close(fd); + return NULL; + } + + lw_print("MQTT connect %s:%d success, begin to verify hostname and password.\n", mqtt_ip_str, mqtt_socket_port); + + if(MQTT_Connect() != Connect_OK) + { + lw_print("MQTT verify failed.\n"); + shutdown(fd, SHUT_WR); + recv(fd, NULL, (size_t)0, 0); + close(fd); + return NULL; + } + + lw_print("MQTT subscribe begin.\n"); + if(MQTTSubscribe(fd,(char *)TOPIC,QOS1) < 0) + { + lw_print("MQTT subscribe failed.\n"); + shutdown(fd, SHUT_WR); + recv(fd, NULL, (size_t)0, 0); + close(fd); + return NULL; + } + + lw_print("subscribe success.\n"); + + + + uint8_t no_mqtt_msg_exchange = 1; + uint32_t curtick=0; + uint8_t res; + + cJSON* cJSON_Data = NULL; + cJSON_Data = cJSON_Data_Init(); + DHT11_Data_TypeDef* recv_data; + + double a,b; + while(1) + { + curtick+=1; + char* p ="Hello,here is hc"; + ret = MQTTMsgPublish(fd,(char*)TOPIC,QOS0,(uint8_t*)p); + if(ret >= 0) + { + no_mqtt_msg_exchange = 0; + PrivTaskDelay(1000); + } + } +} + + +void MqttSocketSendTest(int argc, char *argv[]) +{ + if(argc >= 2) { + lw_print("lw: [%s] target ip %s\n", __func__, argv[1]); + MqttSocketConfigParam(argv[1]); + } + + + // ip4_addr_t dns_ip; + // netconn_gethostbyname(HOST_NAME, &dns_ip); + // char* host_ip = ip_ntoa(&dns_ip); + // lw_print("host name : %s , host_ip : %s\n",HOST_NAME,host_ip); + // MqttSocketConfigParam(host_ip); + + +#ifdef ADD_XIZI_FEATURES + lwip_config_tcp(0, mqtt_demo_ipaddr, mqtt_demo_netmask, mqtt_demo_gwaddr); + + pthread_attr_t attr; + attr.schedparam.sched_priority = LWIP_MQTT_DEMO_TASK_PRIO; + attr.stacksize = LWIP_MQTT_DEMO_TASK_STACK_SIZE; +#endif + +#ifdef ADD_NUTTX_FEATURES + pthread_attr_t attr = PTHREAD_ATTR_INITIALIZER; + attr.priority = LWIP_mqtt_DEMO_TASK_PRIO; + attr.stacksize = LWIP_mqtt_DEMO_TASK_STACK_SIZE; +#endif + + PrivTaskCreate(&mqtt_client_task, &attr, &MqttSocketSendTask, NULL); + PrivTaskStartup(&mqtt_client_task); +} + +PRIV_SHELL_CMD_FUNCTION(MqttSocketSendTest, a tcp send sample, PRIV_SHELL_CMD_MAIN_ATTR); + diff --git a/APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.h b/APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.h new file mode 100644 index 000000000..1d9e85566 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.h @@ -0,0 +1,75 @@ +#include + +#define MSG_MAX_LEN 1024 +#define MSG_TOPIC_LEN 50 +#define KEEPLIVE_TIME 650 +#define MQTT_VERSION 4 + +#ifdef LWIP_DNS +#define HOST_NAME "iot-06z00im0uwa0ki2.mqtt.iothub.aliyuncs.com" //服务器域名 +#else +#define HOST_NAME "iot-06z00im0uwa0ki2.mqtt.iothub.aliyuncs.com" //服务器IP地址 +#endif + + +//#define HOST_IP "129.204.201.235" +#define HOST_PORT 1883 //由于是TCP连接,端口必须是1883 + +#define CLIENT_ID "iw3rn3pa11K.test|securemode=2,signmethod=hmacsha256,timestamp=1689296035604|" //随机的id +#define USER_NAME "test&iw3rn3pa11K" //用户名 +#define PASSWORD "7b948d22fe46f0f63d1a403376d26e7cb298abc227d29e44311d7040307a71f8" //秘钥 + +// #define CLIENT_ID "hc123456789" //随机的id +// #define USER_NAME "xiuos" //用户名 +// #define PASSWORD "xiuos" //秘钥 + +#define TOPIC "/iw3rn3pa11K/test/user/Test" //订阅的主题 + +#define TEST_MESSAGE "test_message" //发送测试消息 + +enum QoS +{ QOS0 = 0, + QOS1, + QOS2 +}; + +enum MQTT_Connect +{ + Connect_OK = 0, + Connect_NOK, + Connect_NOTACK +}; + +//数据交互结构体 +typedef struct __MQTTMessage +{ + uint32_t qos; + uint8_t retained; + uint8_t dup; + uint16_t id; + uint8_t type; + void *payload; + int32_t payloadlen; +}MQTTMessage; + +//用户接收消息结构体 +typedef struct __MQTT_MSG +{ + uint8_t msgqos; //消息质量 + uint8_t msg[MSG_MAX_LEN]; //消息 + uint32_t msglenth; //消息长度 + uint8_t topic[MSG_TOPIC_LEN]; //主题 + uint16_t packetid; //消息ID + uint8_t valid; //标明消息是否有效 +}MQTT_USER_MSG; + +//发送消息结构体 +typedef struct +{ + int8_t topic[MSG_TOPIC_LEN]; + int8_t qos; + int8_t retained; + + uint8_t msg[MSG_MAX_LEN]; + uint8_t msglen; +} mqtt_recv_msg_t, *p_mqtt_recv_msg_t, mqtt_send_msg_t, *p_mqtt_send_msg_t; \ No newline at end of file diff --git a/APP_Framework/Applications/app_test/test_mqttclient/transport.c b/APP_Framework/Applications/app_test/test_mqttclient/transport.c new file mode 100644 index 000000000..d2c187091 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/transport.c @@ -0,0 +1,102 @@ +#include "transport.h" +#include "lwip/opt.h" +#include "lwip/arch.h" +#include "lwip/api.h" +#include "lwip/inet.h" +#include "lwip/sockets.h" +#include "string.h" + +static int mysock; + +/************************************************************************ +** ��������: transport_sendPacketBuffer +** ��������: ��TCP��ʽ�������� +** ��ڲ���: unsigned char* buf�����ݻ����� +** int buflen�����ݳ��� +** ���ڲ���: <0��������ʧ�� +************************************************************************/ +int32_t transport_sendPacketBuffer( uint8_t* buf, int32_t buflen) +{ + int32_t rc; + rc = write(mysock, buf, buflen); + return rc; +} + +/************************************************************************ +** ��������: transport_getdata +** ��������: �������ķ�ʽ����TCP���� +** ��ڲ���: unsigned char* buf�����ݻ����� +** int count�����ݳ��� +** ���ڲ���: <=0��������ʧ�� +************************************************************************/ +int transport_getdata(unsigned char* buf, int count) +{ + int32_t rc; + + rc = recv(mysock, buf, count, 0); + lw_print("get data : %lx\n",rc); + return rc; +} + + + +/************************************************************************ +** ��������: transport_open +** ��������: ��һ���ӿڣ����Һͷ����� �������� +** ��ڲ���: char* servip:���������� +** int port:�˿ں� +** ���ڲ���: <0������ʧ�� +************************************************************************/ +// int32_t transport_open(int8_t* servip, int32_t port) +// { +// int32_t *sock = &mysock; +// int32_t ret; +// // int32_t opt; +// struct sockaddr_in addr; + +// //��ʼ����������Ϣ +// memset(&addr,0,sizeof(addr)); +// addr.sin_len = sizeof(addr); +// addr.sin_family = AF_INET; +// //��д�������˿ں� +// addr.sin_port = PP_HTONS(port); +// //��д������IP��ַ +// addr.sin_addr.s_addr = inet_addr((const char*)servip); + +// //����SOCK +// *sock = socket(AF_INET,SOCK_STREAM,0); +// //���ӷ����� +// ret = connect(*sock,(struct sockaddr*)&addr,sizeof(addr)); +// if(ret != 0) +// { +// //�ر����� +// close(*sock); +// //����ʧ�� +// return -1; +// } +// //���ӳɹ�,���ó�ʱʱ��1000ms +// // opt = 1000; +// // setsockopt(*sock,SOL_SOCKET,SO_RCVTIMEO,&opt,sizeof(int)); + +// //�����׽��� +// return *sock; +// } + + +/************************************************************************ +** ��������: transport_close +** ��������: �ر��׽��� +** ��ڲ���: unsigned char* buf�����ݻ����� +** int buflen�����ݳ��� +** ���ڲ���: <0��������ʧ�� +************************************************************************/ +int32_t transport_close(void) +{ + + int rc; +// rc = close(mysock); + rc = shutdown(mysock, SHUT_WR); + rc = recv(mysock, NULL, (size_t)0, 0); + rc = close(mysock); + return rc; +} diff --git a/APP_Framework/Applications/app_test/test_mqttclient/transport.h b/APP_Framework/Applications/app_test/test_mqttclient/transport.h new file mode 100644 index 000000000..a11ef1923 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/transport.h @@ -0,0 +1,45 @@ +#ifndef __TRANSPORT_H +#define __TRANSPORT_H + +#include + + +/************************************************************************ +** ��������: transport_sendPacketBuffer +** ��������: ��TCP��ʽ�������� +** ��ڲ���: unsigned char* buf�����ݻ����� +** int buflen�����ݳ��� +** ���ڲ���: <0��������ʧ�� +************************************************************************/ +int32_t transport_sendPacketBuffer( uint8_t* buf, int32_t buflen); + +/************************************************************************ +** ��������: transport_getdata +** ��������: �������ķ�ʽ����TCP���� +** ��ڲ���: unsigned char* buf�����ݻ����� +** int count�����ݳ��� +** ���ڲ���: <=0��������ʧ�� +************************************************************************/ +int transport_getdata(unsigned char* buf, int count); + +/************************************************************************ +** ��������: transport_open +** ��������: ��һ���ӿڣ����Һͷ����� �������� +** ��ڲ���: char* servip:���������� +** int port:�˿ں� +** ���ڲ���: <0������ʧ�� +************************************************************************/ +int32_t transport_open(int8_t* servip, int32_t port); + +/************************************************************************ +** ��������: transport_close +** ��������: �ر��׽��� +** ��ڲ���: unsigned char* buf�����ݻ����� +** int buflen�����ݳ��� +** ���ڲ���: <0��������ʧ�� +************************************************************************/ +int32_t transport_close(void); + + + +#endif diff --git a/APP_Framework/Applications/app_test/test_mqttclient/图片1.png b/APP_Framework/Applications/app_test/test_mqttclient/图片1.png new file mode 100644 index 0000000000000000000000000000000000000000..7eafe69eb81c25864771c6c15b8833346ff6036d GIT binary patch literal 19570 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 QTNcPOp00i_>zopr05^)lY5)KL literal 0 HcmV?d00001 diff --git a/APP_Framework/Applications/app_test/test_mqttclient/图片2.png b/APP_Framework/Applications/app_test/test_mqttclient/图片2.png new file mode 100644 index 0000000000000000000000000000000000000000..eb573250b46cee9a6ffc5354ab6e041b24026929 GIT binary patch literal 14573 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 literal 0 HcmV?d00001 diff --git a/APP_Framework/Applications/app_test/test_mqttclient/图片3.png b/APP_Framework/Applications/app_test/test_mqttclient/图片3.png new file mode 100644 index 0000000000000000000000000000000000000000..688acb4f1f728734894ae821525eb3565de98561 GIT binary patch literal 29378 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 PUNpFDda2|W$MF9H9$cq) literal 0 HcmV?d00001 diff --git a/APP_Framework/lib/cJSON/Makefile b/APP_Framework/lib/cJSON/Makefile index 79f1fc37d..8d2e20e45 100755 --- a/APP_Framework/lib/cJSON/Makefile +++ b/APP_Framework/lib/cJSON/Makefile @@ -1,3 +1,4 @@ SRC_FILES := cJSON.c +SRC_FILES += cJSON_Process.c include $(KERNEL_ROOT)/compiler.mk diff --git a/APP_Framework/lib/cJSON/cJSON_Process.c b/APP_Framework/lib/cJSON/cJSON_Process.c new file mode 100644 index 000000000..12490a10b --- /dev/null +++ b/APP_Framework/lib/cJSON/cJSON_Process.c @@ -0,0 +1,85 @@ +#include "cJSON_Process.h" +#include + + + + + +cJSON* cJSON_Data_Init(void) +{ + cJSON* cJSON_Root = NULL; //json根节点 + + + cJSON_Root = cJSON_CreateObject(); /*创建项目*/ + if(NULL == cJSON_Root) + { + return NULL; + } + cJSON_AddStringToObject(cJSON_Root, NAME, DEFAULT_NAME); /*添加元素 键值对*/ + cJSON_AddNumberToObject(cJSON_Root, TEMP_NUM, DEFAULT_TEMP_NUM); + cJSON_AddNumberToObject(cJSON_Root, HUM_NUM, DEFAULT_HUM_NUM); + + char* p = cJSON_Print(cJSON_Root); /*p 指向的字符串是json格式的*/ + + p = NULL; + + return cJSON_Root; + +} +uint8_t cJSON_Update(const cJSON * const object,const char * const string,void *d) +{ + cJSON* node = NULL; //json根节点 + node = cJSON_GetObjectItem(object,string); + if(node == NULL) + return 0; + if(cJSON_IsBool(node)) + { + int *b = (int*)d; + + cJSON_GetObjectItem(object,string)->type = *b ? cJSON_True : cJSON_False; + + return 1; + } + else if(cJSON_IsString(node)) + { + cJSON_GetObjectItem(object,string)->valuestring = (char*)d; + + return 1; + } + else if(cJSON_IsNumber(node)) + { + double *num = (double*)d; + + cJSON_GetObjectItem(object,string)->valuedouble = (double)*num; + + return 1; + } + else + return 1; +} + +void Proscess(void* data) +{ + + cJSON *root,*json_name,*json_temp_num,*json_hum_num; + root = cJSON_Parse((char*)data); //解析成json形式 + + json_name = cJSON_GetObjectItem( root , NAME); //获取键值内容 + json_temp_num = cJSON_GetObjectItem( root , TEMP_NUM ); + json_hum_num = cJSON_GetObjectItem( root , HUM_NUM ); + + lw_print("name:%s\n temp_num:%f\n hum_num:%f\n", + json_name->valuestring, + json_temp_num->valuedouble, + json_hum_num->valuedouble); + + cJSON_Delete(root); //释放内存 +} + + + + + + + + diff --git a/APP_Framework/lib/cJSON/cJSON_Process.h b/APP_Framework/lib/cJSON/cJSON_Process.h new file mode 100644 index 000000000..5dcdd3bf5 --- /dev/null +++ b/APP_Framework/lib/cJSON/cJSON_Process.h @@ -0,0 +1,23 @@ +#ifndef _CJSON_PROCESS_H_ +#define _CJSON_PROCESS_H_ +#include "cJSON.h" +#include "stdint.h" + + +#define NAME "name" +#define TEMP_NUM "temp" +#define HUM_NUM "hum" + +#define DEFAULT_NAME "fire" +#define DEFAULT_TEMP_NUM 25.0 +#define DEFAULT_HUM_NUM 50.0 + + +#define UPDATE_SUCCESS 1 +#define UPDATE_FAIL 0 + +cJSON* cJSON_Data_Init(void); +uint8_t cJSON_Update(const cJSON * const object,const char * const string,void * d); +void Proscess(void* data); +#endif +