From 722eff83e5c4e19b97b945e023be25263346546f Mon Sep 17 00:00:00 2001 From: Your Name <2436441402@qq.com> Date: Wed, 4 Oct 2023 21:19:01 +0800 Subject: [PATCH] test --- .../app_test/test_mqttclient/Command.png | Bin 0 -> 41928 bytes .../app_test/test_mqttclient/Compile.png | Bin 0 -> 45879 bytes .../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 | 416 +++++++++ .../app_test/test_mqttclient/MQTTPacket.h | 133 +++ .../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/Result.png | Bin 0 -> 18847 bytes .../app_test/test_mqttclient/StackTrace.h | 78 ++ .../app_test/test_mqttclient/mqttclient.c | 867 ++++++++++++++++++ .../app_test/test_mqttclient/mqttclient.h | 218 +++++ .../app_test/test_mqttclient/readme.md | 67 ++ .../test_mqttclient/test_mqttclient.c | 68 ++ .../test_mqttclient/test_mqttclient.h | 39 + .../app_test/test_mqttclient/transport.c | 103 +++ .../app_test/test_mqttclient/transport.h | 43 + .../XiZi_IIoT/lib/newlib/time_syscalls.c | 5 +- 28 files changed, 3682 insertions(+), 1 deletion(-) create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/Command.png create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/Compile.png 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/Result.png create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/StackTrace.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/mqttclient.c create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/mqttclient.h create mode 100644 APP_Framework/Applications/app_test/test_mqttclient/readme.md 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 diff --git a/APP_Framework/Applications/app_test/test_mqttclient/Command.png b/APP_Framework/Applications/app_test/test_mqttclient/Command.png new file mode 100644 index 0000000000000000000000000000000000000000..8394a2fa3d4a080ee9b06b9b2ab28047e2dcce36 GIT binary patch literal 41928 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 qY07XxkoRjt}{LP&1JMHw;5b+<{igJGd literal 0 HcmV?d00001 diff --git a/APP_Framework/Applications/app_test/test_mqttclient/Compile.png b/APP_Framework/Applications/app_test/test_mqttclient/Compile.png new file mode 100644 index 0000000000000000000000000000000000000000..4a0d7472cacd838e914431a84c9d8fcd7514145c GIT binary patch literal 45879 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 + +/** + * 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..7c1fe506e --- /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..dbc9e5d8a --- /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, int* 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..9f6317725 --- /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..f7bd0d165 --- /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..9c3f958bb --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.c @@ -0,0 +1,416 @@ +/******************************************************************************* + * 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){ + printf("buflen小于接收报文需要的长度"); + goto exit; + } + if (rem_len && ((*getfn)(buf + len, rem_len) != rem_len)){ + printf("读取的消息长度和负载长度不一致"); + 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..df6f15513 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/MQTTPacket.h @@ -0,0 +1,133 @@ +/******************************************************************************* + * 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 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..d62dddb85 --- /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, int* 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..236791f42 --- /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..383ca0d2f --- /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[], int 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, int* count, int 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..5b1ca2866 --- /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[], int 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, int* count, int 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..15bb15daf --- /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..1644ae518 --- /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..0f8db7b69 --- /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..b0e427d57 --- /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/Result.png b/APP_Framework/Applications/app_test/test_mqttclient/Result.png new file mode 100644 index 0000000000000000000000000000000000000000..e765f07511e57594ba82660b8f1e54f643537170 GIT binary patch literal 18847 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 arPf>e&M!0m2l#QNZK{{mm9iB~9{n$E?9DX* literal 0 HcmV?d00001 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..c65a2ef43 --- /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/mqttclient.c b/APP_Framework/Applications/app_test/test_mqttclient/mqttclient.c new file mode 100644 index 000000000..97d519237 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/mqttclient.c @@ -0,0 +1,867 @@ +#include +#include "mqttclient.h" +#include "transport.h" +#include "MQTTPacket.h" + + +#include "string.h" +#include "sockets.h" + +#include "lwip/opt.h" + +#include "lwip/sys.h" +#include "lwip/api.h" + +#include "lwip/sockets.h" + + + +#include + +/******************************* 全局变量声明 ************************************/ +/* + * 当我们在写应用程序的时候,可能需要用到一些全局变量。 + */ +//extern QueueHandle_t MQTT_Data_Queue; + + +//定义用户消息结构体 +MQTT_USER_MSG mqtt_user_msg; + +int32_t MQTT_socket = 0; + + +void deliverMessage(MQTTString *TopicName,MQTTMessage *msg,MQTT_USER_MSG *mqtt_user_msg); + +/************************************************************************ +** 函数名称: MQTTConnect +** 函数功能: 初始化客户端并登录服务器 +** 入口参数: int32_t sock:网络描述符 +** 出口参数: >=0:发送成功 <0:发送失败 +** 备 注: +************************************************************************/ +uint8_t MQTTConnect(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) + { + printf("无法连接,错误代码是: %d!\n", connack_rc); + return Connect_NOK; + } + else + { + printf("用户名与密钥验证成功,MQTT连接成功!\n"); + return Connect_OK; + } + } + else + printf("MQTT连接无响应!\n"); + return Connect_NOTACK; +} + + +/************************************************************************ +** 函数名称: MQTTPingReq +** 函数功能: 发送MQTT心跳包 +** 入口参数: 无 +** 出口参数: >=0:发送成功 <0:发送失败 +** 备 注: +************************************************************************/ +int32_t MQTTPingReq(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; + int conutbk = 0; + uint8_t buf[100]; + int32_t buflen = sizeof(buf); + MQTTString topicString = MQTTString_initializer; + int32_t len; + int 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; +} + + +/************************************************************************ +** 函数名称: UserMsgCtl +** 函数功能: 用户消息处理函数 +** 入口参数: MQTT_USER_MSG *msg:消息结构体指针 +** 出口参数: 无 +** 备 注: +************************************************************************/ +void UserMsgCtl(MQTT_USER_MSG *msg) +{ + //这里处理数据只是打印,用户可以在这里添加自己的处理方式 + printf("*****收到订阅的消息!******\n"); + //返回后处理消息 + switch(msg->msgqos) + { + case 0: + printf("MQTT>>消息质量:QoS0\n"); + break; + case 1: + printf("MQTT>>消息质量:QoS1\n"); + break; + case 2: + printf("MQTT>>消息质量:QoS2\n"); + break; + default: + printf("MQTT>>错误的消息质量\n"); + break; + } + printf("MQTT>>消息主题:%s\n",msg->topic); + //printf("MQTT>>消息类容:%s\n",msg->msg); + printf("MQTT>>消息长度:%d\n",msg->msglenth); + //Process_msg(msg->msg, msg->msglenth); + //处理完后销毁数据 + msg->valid = 0; +} + +/************************************************************************ +** 函数名称: GetNextPackID +** 函数功能: 产生下一个数据包ID +** 入口参数: 无 +** 出口参数: uint16_t packetid:产生的ID +** 备 注: +************************************************************************/ +uint16_t GetNextPackID(void) +{ + static uint16_t pubpacketid = 0; + return pubpacketid++; +} + +/************************************************************************ +** 函数名称: mqtt_msg_publish +** 函数功能: 用户推送消息 +** 入口参数: MQTT_USER_MSG *msg:消息结构体指针 +** 出口参数: >=0:发送成功 <0:发送失败 +** 备 注: +************************************************************************/ +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; + int32_t len; + uint8_t *buf=(uint8_t*)x_malloc(MSG_MAX_LEN*sizeof(uint8_t)); + if(buf==NULL){ + printf("申请内存失败:Publish"); + return -1; + } + int32_t buflen = MSG_MAX_LEN*sizeof(uint8_t); + 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) + { + free(buf); + return 0; + } + + //等级1 + if(qos == QOS1) + { + free(buf); + //等待PUBACK + if(WaitForPacket(sock,PUBACK,5) < 0) + return -3; + return 1; + + } + //等级2 + if(qos == QOS2) + { + //等待PUBREC + if(WaitForPacket(sock,PUBREC,5) < 0){ + free(buf); + return -3; + } + //发送PUBREL + len = MQTTSerialize_pubrel(buf, buflen,0, packetidbk); + if(len == 0){ + free(buf); + return -4; + } + + if(transport_sendPacketBuffer(buf, len) < 0){ + free(buf); + return -6; + } + //等待PUBCOMP + if(WaitForPacket(sock,PUBREC,5) < 0){ + free(buf); + return -7; + } + free(buf); + return 2; + } + //等级错误 + free(buf); + return -8; +} + +/************************************************************************ +** 函数名称: ReadPacketTimeout +** 函数功能: 阻塞读取MQTT数据 +** 入口参数: int32_t sock:网络描述符 +** uint8_t *buf:数据缓存区 +** int32_t buflen:缓冲区大小 +** mqtt_user_msg uint32_t timeout:超时时间--0-表示直接查询,没有数据立即返回 +** 出口参数: -1:错误,其他--包类型 +** 备 注: +************************************************************************/ +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; + } + //读取TCP/IP事件 + return MQTTPacket_read(buf, buflen, transport_getdata); +} + + +/************************************************************************ +** 函数名称: deliverMessage +** 函数功能: 接受服务器发来的消息 +** 入口参数: MQTTMessage *msg:MQTT消息结构体 +** MQTT_USER_MSG *mqtt_user_msg:用户接受结构体 +** MQTTString *TopicName:主题 +** 出口参数: 无 +** 备 注: +************************************************************************/ +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; +} + + +/************************************************************************ +** 函数名称: mqtt_pktype_ctl +** 函数功能: 根据包类型进行处理 +** 入口参数: uint8_t packtype:包类型 +** 出口参数: 无 +** 备 注: +************************************************************************/ +void mqtt_pktype_ctl(uint8_t packtype,uint8_t *buf,uint32_t buflen) +{ + MQTTMessage msg; + int32_t rc; + MQTTString receivedTopic; + uint32_t len; + switch(packtype) + { + case PUBLISH: + //拆析PUBLISH消息 + if(MQTTDeserialize_publish(&msg.dup,(int*)&msg.qos, &msg.retained, &msg.id, &receivedTopic, + (unsigned char **)&msg.payload, (int*)&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) + { + lw_print("返回后确定消息"); + //返回后处理消息 + 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; + } + +} + +/************************************************************************ +** 函数名称: WaitForPacket +** 函数功能: 等待特定的数据包 +** 入口参数: int32_t sock:网络描述符 +** uint8_t packettype:包类型 +** uint8_t times:等待次数 +** 出口参数: >=0:等到了特定的包 <0:没有等到特定的包 +** 备 注: +************************************************************************/ +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); + uint8_t *buf=(uint8_t*)x_malloc(MSG_MAX_LEN*sizeof(uint8_t)); + if(buf==NULL){ + printf("申请内存失败:WaitForPacket"); + return -1; + } + int32_t buflen = MSG_MAX_LEN*sizeof(uint8_t); + do + { + //读取数据包 + type = ReadPacketTimeout(sock,buf,buflen,2); + if(type != -1) + { + mqtt_pktype_ctl(type,buf,buflen); + free(mqtt_user_msg.msg); + } + n++; + }while((type != packettype)&&(n < times)); + //收到期望的包 + if(type == packettype) + return 0; + else + return -1; + + free(buf); +} + + + +void ClientConnect(void) +{ + char* host_ip; + +#ifdef LWIP_DNS + ip4_addr_t dns_ip; + netconn_gethostbyname(HOST_NAME, &dns_ip); + host_ip = ip_ntoa(&dns_ip); + printf("host name : %s , host_ip : %s\n",HOST_NAME,host_ip); +#else + host_ip = HOST_NAME; +#endif +MQTT_START: + + //创建网络连接 + lw_print("1.开始连接对应云平台的服务器...\n"); + lw_print("服务器IP地址:%s,端口号:%0d!\n",host_ip,HOST_PORT); + while(1) + { + //连接服务器 + MQTT_socket = transport_open((int8_t*)host_ip,HOST_PORT); + //如果连接服务器成功 + if(MQTT_socket >= 0) + { + printf("连接云平台服务器成功!\n"); + break; + } + printf("连接云平台服务器失败,等待3秒再尝试重新连接!\n"); + //等待3秒 + UserTaskDelay(20000); + //sleep(3); + } + + lw_print("2.MQTT用户名与密钥验证登录...\n"); + //MQTT用户名与密钥验证登录 + if(MQTTConnect() != Connect_OK) + { + //重连服务器 + lw_print("MQTT用户名与密钥验证登录失败...\n"); + //关闭链接 + transport_close(); + goto MQTT_START; + } + + //订阅消息 + printf("3.开始订阅消息...\n"); + //订阅消息 + if(MQTTSubscribe(MQTT_socket,(char *)TOPIC,QOS1) < 0) + { + //重连服务器 + lw_print("客户端订阅消息失败...\n"); + //关闭链接 + transport_close(); + goto MQTT_START; + } + + //无限循环 + lw_print("4.开始循环接收订阅的消息...\n"); + +} + + + + + + + + + +int32_t TestBlockMem(uint8_t* buf, int32_t count) +{ + int idx; + for(int idx=0;idx0) + { + rc=recv(MQTT_socket,block[rcTime],64*1024,0); + if(rc<0){ + return -1; + } + rcTime++; + } + int total_size=0; + //合并数据 + for(int i=0;i0) + { + ret_code=recv(MQTT_socket,buf,buflen,0); + if(ret_time%10==0){ + printf("第几次接收:%d \n",ret_time); + printf("接收大小:%d",ret_code); + } + //ret_code=recv(MQTT_socket,buf,buflen,0); + if(ret_code<0){ + printf("接收剩余消息出错"); + goto CLOSE; + } + ret_time--; + buf[buflen]='\0'; + ret_file=PrivWrite(fd_t,buf,ret_code); + + //printf("剩余消息:%s \n",buf); + } + + PrivClose(fd_t); + printf("接收消息完成"); + goto CLOSE; + } + } + +CLOSE: + free(buf); + //关闭链接 + transport_close(); + //重新链接服务器 + goto MQTT_START; +} + +void *MQTTSend(void*arg){ + int32_t ret_code; + uint8_t no_mqtt_msg_exchange=1; + uint32_t curtick; + uint8_t res; + int32_t type; + struct timeval tv; + + char* test="This is the publish"; + ClientConnect(); + while(1){ + ret_code=MQTTMsgPublish(MQTT_socket,(char*)TOPIC,QOS1,(uint8_t*)test); + if(ret_code>=0){ + no_mqtt_msg_exchange=0; + PrivTaskDelay(500); + } + } +} + +void mqtt_send_thread(){ + int32_t ret_code; + uint8_t no_mqtt_msg_exchange = 1; + uint32_t curtick; + uint8_t res; +} + +void TcpSocketConfigParam(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(tcp_ip_str, ip_str); + if(port) + tcp_socket_port = port; + return; + } + + if(sscanf(ip_str, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4)) { + printf("config ip %s\n", ip_str); + strcpy(tcp_ip_str, ip_str); + } +} + +void LwipConfig(){ + lwip_config_tcp(0,tcp_demo_ipaddr,tcp_demo_netmask,tcp_demo_gwaddr); +} + +void testTimerFunc(void*arg){ + if(MQTTPingReq(MQTT_socket) < 0) + { + //重连服务器 + printf("发送保持活性ping失败....\n"); + } + //心跳成功 + printf("发送保持活性ping作为心跳成功....\n"); +} + +void TestTimer(){ + char* name="first timer \n"; + int32 ret=KCreateTimer(name,testTimerFunc,0,100,TIMER_TRIGGER_PERIODIC); + KTimerStartRun(ret); +} + + +void TestFile(){ + int ret=0; + char w_buf[]="hello flilesystem"; + char r_buf[20]; + int fd_t=PrivOpen("/hello.txt",O_RDWR|O_CREAT); + if(fd_t>0){ + ret=PrivWrite(fd_t,w_buf,strlen(w_buf)); + if(ret<0){ + printf("fd = %d write hello world failed.\n",fd_t); + return; + } + PrivClose(fd_t); + fd_t = PrivOpen("/hello.txt", O_RDONLY); + ret = PrivRead(fd_t, r_buf, 20); + if(ret > 0){ + printf("read len %d, r_buf %s\n",ret,r_buf); + } + PrivClose(fd_t); + } +} \ No newline at end of file diff --git a/APP_Framework/Applications/app_test/test_mqttclient/mqttclient.h b/APP_Framework/Applications/app_test/test_mqttclient/mqttclient.h new file mode 100644 index 000000000..cd04f1b83 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/mqttclient.h @@ -0,0 +1,218 @@ +#ifndef __MALLOC_H +#define __MALLOC_H + +#include +#include "lwipopts.h" +#include +#include +#include +#define MSG_MAX_LEN 1024 +#define MSG_TOPIC_LEN 50 +#define KEEPLIVE_TIME 50 +#define MQTT_VERSION 4 +#define TIMER_TRIGGER_ONCE (1 << 0) +#define TIMER_TRIGGER_PERIODIC (1 << 1) +#undef LWIP_DNS + +#ifdef LWIP_DNS +#define HOST_NAME "mqtt.heclouds.com" //服务器域名 +#else +#define HOST_NAME "192.168.56.137" //服务器IP地址 +#endif +//#define HOST_NAME "192.168.56.137" + + +//#define HOST_IP "129.204.201.235" +#define HOST_PORT 1883 //由于是TCP连接,端口必须是1883 + +#define CLIENT_ID "518725049" //随机的id +#define USER_NAME "hzw" //用户名 +#define PASSWORD "124578" //秘钥 + +#define TOPIC "temp" //订阅的主题 + +#define TEST_MESSAGE "test_message" //发送测试消息 + +#define BLOCK_NUM 10 //内存池数量 + +#define LWIP_IP_ADDR {'192','168','56','3'} +#define LWIP_NETMASK {'255','255','255','0'} +#define LWIP_GATEWAY {'192','168','56','1'} + +static char tcp_demo_ipaddr[] = {192, 168, 56, 3}; +static char tcp_demo_netmask[] = {255, 255, 255, 0}; +static char tcp_demo_gwaddr[] = {192, 168, 56, 1}; + +static uint16_t tcp_socket_port = 8888; +static char tcp_ip_str[128] = {0}; + +static uint8 *block[BLOCK_NUM]; // 内存池指针 +static GatherMemType memG; + +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; //消息 + 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; + uint8_t msglen; +} mqtt_recv_msg_t, *p_mqtt_recv_msg_t, mqtt_send_msg_t, *p_mqtt_send_msg_t; + + +void mqtt_thread( void *pvParameters); + +/************************************************************************ +** 函数名称: my_mqtt_send_pingreq +** 函数功能: 发送MQTT心跳包 +** 入口参数: 无 +** 出口参数: >=0:发送成功 <0:发送失败 +** 备 注: +************************************************************************/ +int32_t MQTTPingReq(int32_t sock); + +/************************************************************************ +** 函数名称: MQTTConnect +** 函数功能: 登录服务器 +** 入口参数: int32_t sock:网络描述符 +** 出口参数: Connect_OK:登陆成功 其他:登陆失败 +** 备 注: +************************************************************************/ +uint8_t MQTTConnect(void); + +/************************************************************************ +** 函数名称: MQTTSubscribe +** 函数功能: 订阅消息 +** 入口参数: int32_t sock:套接字 +** int8_t *topic:主题 +** enum QoS pos:消息质量 +** 出口参数: >=0:发送成功 <0:发送失败 +** 备 注: +************************************************************************/ +int32_t MQTTSubscribe(int32_t sock,char *topic,enum QoS pos); + +/************************************************************************ +** 函数名称: UserMsgCtl +** 函数功能: 用户消息处理函数 +** 入口参数: MQTT_USER_MSG *msg:消息结构体指针 +** 出口参数: 无 +** 备 注: +************************************************************************/ +void UserMsgCtl(MQTT_USER_MSG *msg); + +/************************************************************************ +** 函数名称: GetNextPackID +** 函数功能: 产生下一个数据包ID +** 入口参数: 无 +** 出口参数: uint16_t packetid:产生的ID +** 备 注: +************************************************************************/ +uint16_t GetNextPackID(void); + +/************************************************************************ +** 函数名称: mqtt_msg_publish +** 函数功能: 用户推送消息 +** 入口参数: MQTT_USER_MSG *msg:消息结构体指针 +** 出口参数: >=0:发送成功 <0:发送失败 +** 备 注: +************************************************************************/ +//int32_t MQTTMsgPublish(int32_t sock, char *topic, int8_t qos, int8_t retained,uint8_t* msg,uint32_t msg_len); +int32_t MQTTMsgPublish(int32_t sock, char *topic, int8_t qos, uint8_t* msg); +/************************************************************************ +** 函数名称: ReadPacketTimeout +** 函数功能: 阻塞读取MQTT数据 +** 入口参数: int32_t sock:网络描述符 +** uint8_t *buf:数据缓存区 +** int32_t buflen:缓冲区大小 +** uint32_t timeout:超时时间--0-表示直接查询,没有数据立即返回 +** 出口参数: -1:错误,其他--包类型 +** 备 注: +************************************************************************/ +int32_t ReadPacketTimeout(int32_t sock,uint8_t *buf,int32_t buflen,uint32_t timeout); + +/************************************************************************ +** 函数名称: mqtt_pktype_ctl +** 函数功能: 根据包类型进行处理 +** 入口参数: uint8_t packtype:包类型 +** 出口参数: 无 +** 备 注: +************************************************************************/ +void mqtt_pktype_ctl(uint8_t packtype,uint8_t *buf,uint32_t buflen); + +/************************************************************************ +** 函数名称: WaitForPacket +** 函数功能: 等待特定的数据包 +** 入口参数: int32_t sock:网络描述符 +** uint8_t packettype:包类型 +** uint8_t times:等待次数 +** 出口参数: >=0:等到了特定的包 <0:没有等到特定的包 +** 备 注: +************************************************************************/ +int32_t WaitForPacket(int32_t sock,uint8_t packettype,uint8_t times); + + +/*测试动态内存池的分块*/ +int32_t TestBlockMem(uint8_t* buf, int32_t count); + +/*接收MQTT消息,是测试的赛题*/ +void *MQTTRecv(void*arg); + +/*发送MQTT报文*/ +void *MQTTSend(void*arg); + +/*封装lwip_config_tcp,使得开发板和主机上的运行在虚拟机上的ip网段一致*/ +void LwipConfig(); + +/*从socketdemo那拿来作为测试*/ +void TcpSocketConfigParam(char *ip_str); + +/*测试写入文件功能*/ +void TestFile(); + +/*测试定时器功能*/ +void TestTimer(); + +/*接收MQTT报文的第一个包。抛弃了MQTT库要求的检查buflen必须大于负载长度,以及必须读取完整的报文。*/ +int32_t MQTTRead(unsigned char* buf, int buflen, int (*getfn)(unsigned char*, int)); + +#endif + + + 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..62a6e12c1 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/readme.md @@ -0,0 +1,67 @@ +## 基于矽璓已实现的Lwip,在ARM上实现MQTT Client订阅功能 + +队伍:oscar2 + +## 1. 简介 +使用paho的标准MQTT库文件,以及使用网上提供的使用了lwip的mqtt文件。mqttclient.c文件为参考网上的代码库。随后修改为连接参数。 + +IP:192.168.56.137 +port:1883 +订阅主题:temp + +lwip设置 +ip:192.168.56.3 +netmask:255.255.255.0 +gateway:192.168.56.1 + + +## 2. 订阅函数说明 + +//数据交互结构体 +typedef struct __MQTTMessage +{ + uint32_t qos; //消息质量 + uint8_t retained; //遗嘱标志 + uint8_t dup; //重复分发标志 + uint16_t id; //数据包id + uint8_t type; //数据包类型 + void *payload; //消息负载 + int32_t payloadlen; //负载长度 +}MQTTMessage; + + +//用户接收消息结构体 +typedef struct __MQTT_MSG +{ + uint8_t msgqos; //消息质量 + uint8_t *msg; //消息 + uint32_t msglenth; //消息长度 + uint8_t topic[MSG_TOPIC_LEN]; //主题 + uint16_t packetid; //消息ID + uint8_t valid; //标明消息是否有效 +}MQTT_USER_MSG; + + +void MQTT_Recv(void *parameter) +用于接收消息的函数。在测试接收消息时,接收数个字符的数据可以的。题目要求的是发送640kb的报文,分为十次接收,每次接收64kb。向paho的接收报文的参数传入transport函数,接收失败。遇到了一些问题。 +第一个问题:mqtt的报文头包含负载长度。本题发送640kb,但每次接收时只接受64kb,那么会导致负载长度和实际接收长度不一致,接收失败。所以需要放弃标准mqtt库提供的函数。重新写一个MQTT_Read函数。分十次的接受一个完整的MQTT报文的方法为,第一个包在接收时,不按照mqtt库要求的检查负载长度和实际接收长度是否一致,将接收的包含报文头的第一个数据包的按照原先的步骤,拆解。剩余的数据包,就可以直接接收。 +第二个问题:640KB超过arm开发板的内存。ShowMemory命令显示413664字节。不到640kb,因此将数据写入文件。 +最后的问题:revc函数的缓冲区似乎没有64kb。使用setsocketopt函数设置缓冲区大小失败。所以一次接收64kb似乎也不大行,测试了多次,结果是缓冲区大小不足。当然可能是因为上我编程的失误。所以直接设置为1kb。分为64次接收。 + +以上问题也可能是我个人的编程错误导致的。 + +## 3. 测试程序说明 +测试了函数的订阅以及接收信息的功能。使用——————命令,该命令首先设置ip和端口,保证主机的ip和开发板的ip的网段一致。连接主机上的emqx服务器,订阅主题"temp"。最后MQTTX向主题发布640KB的消息,开发板接收消息并且写入文件。最后查看写入文件的大小。 + +## 4. 运行结果(##需结合运行测试截图按步骤说明##) + + +![image](Compile.png) +添加Lwip,SD等选项,编译edu-arm32成功。 + +![image](Command.png) +使用MQTTSocketRecv命令,设置IP信息,开启连接,并且订阅主题temp,等待消息发送。 + +![image](Result.png) +MQTTX发送消息后,接收消息,并将输入存入storeMsg.txt文件,接收完成后查看文件大小。 + 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..cc7be34ee --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.c @@ -0,0 +1,68 @@ +#include +#include "test_mqttclient.h" + +/* +* Copyright (c) 2023 AIIT XUOS Lab +* XiUOS is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* See the Mulan PSL v2 for more details. +*/ + + +void MQTTSendTest(int argc, char *argv[]) +{ + + if(argc >= 2) { + lw_print("lw: [%s] target ip %s\n", __func__, argv[1]); + TcpSocketConfigParam(argv[1]); + } + LwipConfig(); +#ifdef ADD_XIZI_FEATURES + pthread_attr_t attr; + attr.schedparam.sched_priority = LWIP_TCP_DEMO_TASK_PRIO; + attr.stacksize = LWIP_TCP_DEMO_TASK_STACK_SIZE; +#endif +#ifdef ADD_NUTTX_FEATURES + pthread_attr_t attr = PTHREAD_ATTR_INITIALIZER; + attr.priority = LWIP_TCP_DEMO_TASK_PRIO; + attr.stacksize = LWIP_TCP_DEMO_TASK_STACK_SIZE; +#endif + + PrivTaskCreate(&tcp_client_task, &attr, &MQTTSend, NULL); + PrivTaskStartup(&tcp_client_task); +} + + +void MQTTRecvTest(int argc, char *argv[]) +{ + if(argc >= 2) { + lw_print("lw: [%s] target ip %s\n", __func__, argv[1]); + TcpSocketConfigParam(argv[1]); + } + +#ifdef ADD_XIZI_FEATURES + lwip_config_tcp(0, tcp_demo_ipaddr, tcp_demo_netmask, tcp_demo_gwaddr); + + pthread_attr_t attr; + attr.schedparam.sched_priority = LWIP_TCP_DEMO_TASK_PRIO; + attr.stacksize = LWIP_TCP_DEMO_TASK_STACK_SIZE; +#endif + +#ifdef ADD_NUTTX_FEATURES + pthread_attr_t attr = PTHREAD_ATTR_INITIALIZER; + attr.priority = LWIP_TCP_DEMO_TASK_PRIO; + attr.stacksize = LWIP_TCP_DEMO_TASK_STACK_SIZE; +#endif + PrivTaskCreate(&tcp_server_task, &attr, &MQTTRecv, NULL); + PrivTaskStartup(&tcp_server_task); +} + +PRIV_SHELL_CMD_FUNCTION(TestTimer,a Timer test sample, PRIV_SHELL_CMD_MAIN_ATTR); +PRIV_SHELL_CMD_FUNCTION(TestFile,a filesystem test sample using sd card, PRIV_SHELL_CMD_MAIN_ATTR); +PRIV_SHELL_CMD_FUNCTION(MQTTRecvTest, a tcp receive sample, PRIV_SHELL_CMD_MAIN_ATTR); +PRIV_SHELL_CMD_FUNCTION(MQTTSendTest, Implement mqtt_client, PRIV_SHELL_CMD_MAIN_ATTR); \ No newline at end of file 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..d8c89eee3 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/test_mqttclient.h @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2023 AIIT XUOS Lab +* XiUOS is licensed under Mulan PSL v2. +* You can use this software according to the terms and conditions of the Mulan PSL v2. +* You may obtain a copy of Mulan PSL v2 at: +* http://license.coscl.org.cn/MulanPSL2 +* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +* See the Mulan PSL v2 for more details. +*/ + +#include "mqttclient.h" +#define LWIP_TCP_DEMO_TASK_PRIO 20 +#define LWIP_TCP_DEMO_TASK_STACK_SIZE 4096 + +#ifdef ADD_XIZI_FEATURES +#include +#include +#include "lwip/sys.h" +#endif + + +#ifdef ADD_NUTTX_FEATURES +#include +#include +#include +#include "stdio.h" +#endif + +static pthread_t tcp_client_task; +static pthread_t tcp_server_task; + +/*测试发送消息*/ +void MQTTSendTest(int argc, char *argv[]); + +/*测试接收消息*/ +void MQTTRecvTest(int argc, char *argv[]); + 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..654d7f103 --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/transport.c @@ -0,0 +1,103 @@ +#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) +{ + int rc; + //这个函数在这里不阻塞 + rc = recv(mysock, buf, count, 0); + return rc; +} + + + +/************************************************************************ +** 函数名称: transport_open +** 函数功能: 打开一个接口,并且和服务器 建立连接 +** 入口参数: char* servip:服务器域名 +** int port:端口号 +** 出口参数: <0打开连接失败 +************************************************************************/ +int32_t transport_open(int8_t* servip, int32_t port) +{ + int32_t *sock = (int32_t*)&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; + } + //连接成功,设置缓冲区大小为70kb,测试结果为设置无效 + int nSendBuf=70*1024; + setsockopt(*sock,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,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..6fb046e7b --- /dev/null +++ b/APP_Framework/Applications/app_test/test_mqttclient/transport.h @@ -0,0 +1,43 @@ +#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/Ubiquitous/XiZi_IIoT/lib/newlib/time_syscalls.c b/Ubiquitous/XiZi_IIoT/lib/newlib/time_syscalls.c index 10f1efcb6..568be99c7 100644 --- a/Ubiquitous/XiZi_IIoT/lib/newlib/time_syscalls.c +++ b/Ubiquitous/XiZi_IIoT/lib/newlib/time_syscalls.c @@ -16,7 +16,10 @@ time_t time(time_t *t) { - NULL_PARAM_CHECK(t); + if(t==NULL){ + return 0; + } + //NULL_PARAM_CHECK(t); time_t current = 0; #ifdef RESOURCES_RTC