add new qloud-c-sdk component

This commit is contained in:
mculover666
2022-03-25 10:06:56 +08:00
parent 565cd29e94
commit a3ac2e56d8
166 changed files with 35027 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
file(GLOB src_cos ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(src_services ${src_services} ${src_cos} PARENT_SCOPE)
file(GLOB src_cos_download_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/cos_download_sample.c)
add_executable(cos_download_sample ${src_cos_download_sample})
target_link_libraries(cos_download_sample ${libsdk})

View File

@@ -0,0 +1,98 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file cos_download_sample.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-10-27
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-10-27 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_cos.h"
#include "utils_log.h"
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc = 0;
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
IotCosDownloadParams connect_params = {
.url = "http://localhost", // your cos url
.file_size = 0, // your cos file size
.offset = 0,
.is_fragmentation = false,
.is_https_enabled = false,
};
uint8_t buf[1024];
void *handle = IOT_COS_DownloadInit(&connect_params);
if (!handle) {
goto exit;
}
while (!IOT_COS_DownloadIsFinished(handle)) {
rc = IOT_COS_DownloadFetch(handle, buf, sizeof(buf), 5000);
if (rc < 0) {
break;
}
}
IOT_COS_DownloadDeinit(handle);
exit:
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,261 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file cos_download.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-10-25
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-10-25 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "qcloud_iot_cos.h"
/**
* @brief COS request download header.
*
*/
#define HTTP_COS_DOWNLOAD_REQUEST_HEADER_LEN 256
/**
* @brief Download handle.
*
*/
typedef struct {
IotCosDownloadParams params;
void *http_client;
IotHTTPRequestParams http_request;
int download_size;
int is_first_run;
} HTTPCosDownloadHandle;
/**
* @brief Connect cos http server.
*
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
* @return 0 for success. others @see IotReturnCode
*/
static int _cos_download_connect(HTTPCosDownloadHandle *handle)
{
IotHTTPConnectParams connect_params = {
.url = handle->params.url,
.port = handle->params.is_https_enabled ? "443" : "80",
.ca_crt = NULL, // TODO: support cert
};
return IOT_HTTP_Connect(handle->http_client, &connect_params);
}
/**
* @brief Construct cos download request header.
*
* @param[out] header pointer to request header
* @param[in] is_fragmentation http fragmentation
* @param[in] begin_byte download begin byte
* @param[in] end_byte download end byte
* @return > 0 for header len, others fail
*/
static int _cos_download_request_header_construct(char *header, int is_fragmentation, int begin_byte, int end_byte)
{
int len = HAL_Snprintf(header, HTTP_COS_DOWNLOAD_REQUEST_HEADER_LEN,
"Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
"Accept-Encoding:gzip, deflate\r\nRange:bytes=%d-%d\r\n",
begin_byte, end_byte);
if (len <= 0) {
return QCLOUD_ERR_BUF_TOO_SHORT;
}
if (is_fragmentation) {
const char *keep_alive = "Connection:keep-alive\r\n";
int keep_alive_len = strlen(keep_alive);
if (HTTP_COS_DOWNLOAD_REQUEST_HEADER_LEN - len - 1 <= keep_alive_len) {
return QCLOUD_ERR_BUF_TOO_SHORT;
}
strncpy(header + len, keep_alive, keep_alive_len);
header[len + keep_alive_len] = '\0';
len += keep_alive_len;
}
return len;
}
/**
* @brief Request cos download.
*
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
* @param[in] max_len max_len for buffer to download in fragmentation
* @return 0 for success. others @see IotReturnCode
*/
static int _cos_download_request(HTTPCosDownloadHandle *handle, int max_len)
{
int rc, begin_byte, end_byte = 0;
begin_byte = handle->download_size;
#define min_http(x, y) (((x) < (y)) ? (x) : (y))
end_byte = handle->params.is_fragmentation
? begin_byte + min_http(max_len, handle->params.file_size - handle->download_size) - 1
: handle->params.file_size - 1;
#undef min_http
rc = _cos_download_request_header_construct(handle->http_request.header, handle->params.is_fragmentation,
begin_byte, end_byte);
if (rc <= 0) {
return rc;
}
handle->http_request.url = handle->params.url;
handle->http_request.method = IOT_HTTP_METHOD_GET;
handle->http_request.content_length = 0;
handle->http_request.content = handle->http_request.content_type = NULL;
return IOT_HTTP_Request(handle->http_client, &handle->http_request);
}
/**
* @brief HTTP recv data.
*
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
* @param[out] buf buf to store received data
* @param[in] buf_len buf_len
* @param[in] timeout_ms read socket timeout
* @return >= 0 for recv data len. others @see IotReturnCode
*/
static int _cos_download_recv_data(HTTPCosDownloadHandle *handle, uint8_t *buf, int buf_len, uint32_t timeout_ms)
{
int rc = IOT_HTTP_Recv(handle->http_client, buf, buf_len, timeout_ms);
if (rc > 0) {
handle->download_size += rc;
}
return rc;
}
/**************************************************************************************
* API
**************************************************************************************/
/**
* @brief Init cos download handle.
*
* @param[in] params @see IotCosDownloadParams
* @return pointer to cos download handle
*/
void *IOT_COS_DownloadInit(IotCosDownloadParams *params)
{
POINTER_SANITY_CHECK(params, NULL);
HTTPCosDownloadHandle *handle = HAL_Malloc(sizeof(HTTPCosDownloadHandle));
if (!handle) {
goto exit;
}
handle->http_client = IOT_HTTP_Init();
if (!handle->http_client) {
goto exit;
}
handle->http_request.header = HAL_Malloc(HTTP_COS_DOWNLOAD_REQUEST_HEADER_LEN);
if (!handle->http_request.header) {
goto exit;
}
handle->params = *params;
handle->download_size = params->offset;
if (_cos_download_connect(handle)) {
goto exit;
}
handle->is_first_run = 1;
return handle;
exit:
if (handle) {
HAL_Free(handle->http_request.header);
IOT_HTTP_Deinit(handle->http_client);
HAL_Free(handle);
}
return NULL;
}
/**
* @brief Fetch data from cos.
*
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
* @param[out] buf buffer to store data
* @param[in] buf_len buffer length
* @param timeout_ms timeout for fetching
* @return >= 0 for recv data len. others @see IotReturnCode
*/
int IOT_COS_DownloadFetch(void *handle, uint8_t *buf, uint32_t buf_len, uint32_t timeout_ms)
{
POINTER_SANITY_CHECK(handle, QCLOUD_ERR_INVAL);
int rc = 0;
HTTPCosDownloadHandle *download_handle = (HTTPCosDownloadHandle *)handle;
// download finish
if (IOT_COS_DownloadIsFinished(handle)) {
return 0;
}
if (download_handle->is_first_run) {
rc = _cos_download_request(download_handle, buf_len);
if (rc) {
Log_e("cos request failed %d", rc);
return rc;
}
download_handle->is_first_run = 0;
return _cos_download_recv_data(download_handle, buf, buf_len, timeout_ms);
}
if (download_handle->params.is_fragmentation && IOT_HTTP_IsRecvFinished(download_handle->http_client)) {
rc = _cos_download_request(download_handle, buf_len);
if (rc) {
Log_e("cos request failed %d", rc);
return rc;
}
}
return _cos_download_recv_data(download_handle, buf, buf_len, timeout_ms);
}
/**
* @brief Is download finished.
*
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
* @return true for finished
*/
int IOT_COS_DownloadIsFinished(void *handle)
{
POINTER_SANITY_CHECK(handle, QCLOUD_ERR_INVAL);
HTTPCosDownloadHandle *download_handle = (HTTPCosDownloadHandle *)handle;
return download_handle->download_size == download_handle->params.file_size;
}
/**
* @brief Deinit cos download.
*
* @param[in,out] handle pointer to cos download handle, @see HTTPCosDownloadHandle
*/
void IOT_COS_DownloadDeinit(void *handle)
{
POINTER_SANITY_CHECK_RTN(handle);
HTTPCosDownloadHandle *download_handle = (HTTPCosDownloadHandle *)handle;
IOT_HTTP_Disconnect(download_handle->http_client);
IOT_HTTP_Deinit(download_handle->http_client);
HAL_Free(download_handle->http_request.header);
HAL_Free(download_handle);
}

View File

@@ -0,0 +1,10 @@
file(GLOB src_dynreg ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(src_services ${src_services} ${src_dynreg} PARENT_SCOPE)
file(GLOB src_dynreg_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/dynreg_sample.c)
add_executable(dynreg_sample ${src_dynreg_sample})
target_link_libraries(dynreg_sample ${libsdk})
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,93 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file cos_download_sample.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-10-27
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-10-27 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_common.h"
#include "utils_log.h"
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc = 0;
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
DeviceInfo device_info;
// 1. get device info
rc = HAL_GetDevInfo((void *)&device_info);
if (rc) {
Log_e("get device info failed: %d", rc);
return rc;
}
// 2. do dynreg
rc = IOT_DynReg_Device(&device_info);
if (rc) {
Log_e("dynreg error");
goto exit;
}
// 3. get device secret just save it. then you can do mqtt
HAL_SetDevInfo(&device_info);
Log_i("device secret : %s", device_info.device_secret);
exit:
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,162 @@
/**
* @file dynreg.c
* @author {hubert} ({hubertxxu@tencent.com})
* @brief
* @version 1.0
* @date 2022-01-26
*
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @par Change Log:
* <table>
* Date Version Author Description
* 2022-01-26 1.0 hubertxxu first commit
* </table>
*/
#include "qcloud_iot_common.h"
#include "utils_json.h"
#include "utils_base64.h"
#include "mbedtls/aes.h"
#define DYN_RESPONSE_BUFF_LEN (256)
#define DECODE_BUFF_LEN (256)
#define UTILS_AES_BLOCK_LEN (16)
/**
* @brief aes cbc handle
*
* @return int 0 is success other is error.
*/
static int _aes_cbc(uint8_t *input_data, uint32_t input_data_len, uint8_t *output_data, uint32_t output_data_len,
DeviceInfo *device_info)
{
int i;
uint8_t iv[16] = {0};
char key[UTILS_AES_BLOCK_LEN + 1] = {0};
for (i = 0; i < 16; i++) {
iv[i] = '0';
}
strncpy(key, device_info->product_secret, UTILS_AES_BLOCK_LEN);
mbedtls_aes_context aes_ctx;
mbedtls_aes_init(&aes_ctx);
mbedtls_aes_setkey_dec(&aes_ctx, (const unsigned char *)key, 128);
if (mbedtls_aes_crypt_cbc(&aes_ctx, MBEDTLS_AES_DECRYPT, input_data_len, iv, (unsigned char *)input_data,
(unsigned char *)output_data)) {
return QCLOUD_ERR_FAILURE;
}
mbedtls_aes_free(&aes_ctx);
return QCLOUD_RET_SUCCESS;
}
/**
* @brief parse device register response buffer
*
* @param response_buf
* @param response_len
* @return int 0 success other error
*/
static int _parse_response_result(char *response_buf, int response_len, DeviceInfo *device_info)
{
int rc = 0;
UtilsJsonValue value;
char base64_decode_buf[DECODE_BUFF_LEN] = {0};
size_t olen = 0;
// 1. find payload
rc = utils_json_value_get("Response.Payload", strlen("Response.Payload"), response_buf, response_len, &value);
if (rc) {
Log_e("can not find payload. json : %s", response_buf);
goto exit;
}
// 2. base64 decode
rc = utils_base64decode((uint8_t *)base64_decode_buf, DECODE_BUFF_LEN, &olen, (const uint8_t *)value.value,
value.value_len);
if (rc) {
Log_e("base64 decode error.");
goto exit;
}
// 3. aes cbc
olen = olen + (16 - olen % 16);
rc = _aes_cbc((uint8_t *)base64_decode_buf, (uint32_t)olen, (uint8_t *)base64_decode_buf, DECODE_BUFF_LEN,
device_info);
if (rc) {
Log_e("aes cbc error. ");
goto exit;
}
// 4. find psk
uint32_t encryption_type = 0;
rc = utils_json_get_uint32("encryptionType", strlen("encryptionType"), base64_decode_buf, DECODE_BUFF_LEN,
&encryption_type);
if (rc) {
Log_e("parse encryptionType error. json : %s", base64_decode_buf);
goto exit;
}
rc = utils_json_value_get("psk", strlen("psk"), base64_decode_buf, DECODE_BUFF_LEN, &value);
if (rc) {
Log_e("parse psk error. json : %s", base64_decode_buf);
goto exit;
}
// 5. copy psk to device info
strncpy(device_info->device_secret, value.value, value.value_len);
exit:
return rc;
}
/**
* @brief dynreg device, get device secret or device cert file and private key file from iot platform
*
* @param[in] params @see DeviceInfo
* @return 0 is success other is failed
*/
int IOT_DynReg_Device(DeviceInfo *device_info)
{
int rc = 0;
POINTER_SANITY_CHECK(device_info, QCLOUD_ERR_INVAL);
if (strlen(device_info->product_secret) < UTILS_AES_BLOCK_LEN) {
Log_e("product key illegal");
rc = QCLOUD_ERR_FAILURE;
goto exit;
}
char dynreg_buf[DYN_RESPONSE_BUFF_LEN] = {0};
/* constructor dynreg http body */
int request_body_len =
HAL_Snprintf(dynreg_buf, DYN_RESPONSE_BUFF_LEN, "{\"ProductId\":\"%s\",\"DeviceName\":\"%s\"}",
device_info->product_id, device_info->device_name);
HttpSignedParams params = {
.host = DYN_REG_SERVER_URL,
.uri = DYN_REG_URI_PATH,
.need_recv = true,
.recv_timeout_ms = 2000,
.secretkey = device_info->product_secret,
};
rc = IOT_HTTP_Signed_Request(&params, dynreg_buf, request_body_len, (uint8_t *)dynreg_buf, DYN_RESPONSE_BUFF_LEN);
if (rc < 0) {
goto exit;
}
rc = _parse_response_result(dynreg_buf, rc, device_info);
exit:
return rc;
}

View File

@@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
DerivePointerAlignment: true
PointerAlignment: Left
SortIncludes: true
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash

View File

@@ -0,0 +1,43 @@
/**
* @file test_dynreg.cc
* @author {hubert} ({hubertxxu@tencent.com})
* @brief
* @version 1.0
* @date 2022-01-26
*
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @par Change Log:
* <table>
* Date Version Author Description
* 2022-01-26 1.0 hubertxxu first commit
* </table>
*/
#include "gtest/gtest.h"
#include "mqtt_client_test.h"
#include "qcloud_iot_common.h"
namespace mqtt_client_unittest {
TEST_F(MqttClientTest, dynreg_test) {
DeviceInfo device_info;
ASSERT_EQ(HAL_GetDevInfo(reinterpret_cast<void *>(&device_info)), 0);
strncpy(device_info.device_name, "dynreg_test", 12);
ASSERT_EQ(IOT_DynReg_Device(&device_info), 0);
ASSERT_EQ(HAL_SetDevInfo(reinterpret_cast<void *>(&device_info)), 0);
}
} // namespace mqtt_client_unittest

View File

@@ -0,0 +1,2 @@
file(GLOB src_http_client ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(src_services ${src_services} ${src_http_client} PARENT_SCOPE)

View File

@@ -0,0 +1,623 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file http_client.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-10-22
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-10-22 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "qcloud_iot_http_client.h"
#include "network_interface.h"
/**
* @brief Http data.
*
*/
typedef struct {
const char *data;
int data_len;
} HTTPData;
/**
* @brief Only focus on response code and content,
* @ref https://datatracker.ietf.org/doc/html/rfc7231
*
*/
typedef struct {
int status_code; /**< https://en.wikipedia.org/wiki/List_of_HTTP_status_codes */
int content_length; /**< content length */
uint8_t *content_buf; /**< content buffer */
int content_buf_len; /**< content buffer length */
int recv_len; /**< already recv length */
int need_recv_len; /**< need recv length */
int is_chunked; /**< support chunked */
} IotHTTPResponseData;
/**
* @brief HTTP client.
*
*/
typedef struct {
IotHTTPResponseData response;
IotNetwork network;
} IotHTTPClient;
/**************************************************************************************
* network
**************************************************************************************/
/**
* @brief Connect with host.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] host http host
* @param[in] port http port, 80(http)/443(https)
* @param[in] ca_crt https ca
* @return 0 for success. others @see IotReturnCode
*/
static int _http_client_connect(IotHTTPClient *client, const char *host, const char *port, const char *ca_crt)
{
int rc = 0;
client->network.type = IOT_NETWORK_TYPE_TCP;
#if !defined(AUTH_WITH_NO_TLS) && defined(AUTH_MODE_CERT)
client->network.ssl_connect_params.ca_crt = ca_crt;
client->network.ssl_connect_params.ca_crt_len = strlen(ca_crt);
client->network.ssl_connect_params.cert_file = NULL;
client->network.ssl_connect_params.key_file = NULL;
client->network.ssl_connect_params.timeout_ms = HTTPS_READ_TIMEOUT_MS;
#endif
client->network.host = host;
client->network.port = port;
rc = qcloud_iot_network_init(&(client->network));
return rc ? rc : client->network.connect(&(client->network));
}
/**
* @brief Disconnect with host.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
*/
static void _http_client_disconnect(IotHTTPClient *client)
{
return client->network.disconnect(&(client->network));
}
/**
* @brief Send data to http server.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] buf buffer tot send
* @param[in] len buffer length
* @return 0 for success. others @see IotReturnCode
*/
static int _http_client_send(IotHTTPClient *client, char *buf, uint32_t len)
{
size_t written_len;
return client->network.write(&(client->network), (uint8_t *)buf, len, HTTP_WRITE_TIMEOUT_MS, &written_len);
}
/**
* @brief Recv data from http server.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[out] buf
* @param[in] len
* @param[in] timeout_ms
* @return >0 for read data length, others @see IotReturnCode
*/
static int _http_client_recv(IotHTTPClient *client, uint8_t *buf, uint32_t len, uint32_t timeout_ms)
{
size_t read_len;
int rc = client->network.read(&client->network, buf, len, timeout_ms, &read_len);
switch (rc) {
case QCLOUD_ERR_TCP_NOTHING_TO_READ:
return QCLOUD_RET_SUCCESS;
default:
return rc ? rc : read_len;
}
}
/**************************************************************************************
* request
**************************************************************************************/
/**
* @brief Parse url and get host & path.
*
* @param[in] url url str
* @param[out] host pointer to host str and length. @see HTTPData
* @param[out] path pointer to path str and length. @see HTTPData
* @return 0 for success. others @see IotReturnCode
*/
static int _http_client_parse_url(const char *url, HTTPData *host, HTTPData *path)
{
// start from: http:// or https://
char *host_ptr = (char *)strstr(url, "://");
if (!host_ptr) {
return QCLOUD_ERR_HTTP_PARSE;
}
host->data = host_ptr + 3;
path->data = strchr(host->data, '/');
if (!path->data) {
host->data_len = url + strlen(url) - host->data;
path->data_len = 1;
path->data = "/";
return QCLOUD_RET_SUCCESS;
}
host->data_len = path->data - host->data;
char *fragment_ptr = strchr(host_ptr, '#');
path->data_len = fragment_ptr ? fragment_ptr - path->data : strlen(path->data);
return QCLOUD_RET_SUCCESS;
}
/**
* @brief Send http request line.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] params params needed to send request to http server, @see IotHTTPRequestParams
* @return int
*/
static int _http_client_send_request_line(IotHTTPClient *client, const IotHTTPRequestParams *params)
{
int len, rc, buf_len;
char *buf;
HTTPData host, path;
/**
* @brief order @see IotHTTPMethod
*
*/
const char *method_str[] = {"GET", "POST", "PUT", "DELETE", "HEAD"};
rc = _http_client_parse_url(params->url, &host, &path);
if (rc) {
Log_e("http parse url failed %d", rc);
return rc;
}
buf_len = path.data_len + host.data_len + 128;
buf = HAL_Malloc(buf_len);
if (!buf) {
Log_e("http malloc request line failed %d", rc);
return QCLOUD_ERR_MALLOC;
}
len = HAL_Snprintf(buf, buf_len, "%s %.*s HTTP/1.1\r\nHost:%.*s\r\n", method_str[params->method], path.data_len,
path.data, host.data_len, host.data);
rc = _http_client_send(client, buf, len);
HAL_Free(buf);
return rc;
}
/**
* @brief Send http request content(optional).
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] params params needed to send request to http server, @see IotHTTPRequestParams
* @return int
*/
static int _http_client_send_request_content(IotHTTPClient *client, const IotHTTPRequestParams *params)
{
int rc = 0, len, buf_len;
char *buf;
buf_len = params->content_type ? 32 + strlen(params->content_type) : 32;
buf = HAL_Malloc(buf_len);
if (!buf) {
Log_e("http malloc request line failed %d", rc);
return QCLOUD_ERR_MALLOC;
}
len = HAL_Snprintf(buf, buf_len, "Content-Length:%d\r\n", params->content_length);
rc = _http_client_send(client, buf, len);
if (rc) {
goto exit;
}
if (params->content_type) {
len = HAL_Snprintf(buf, buf_len, "Content-Type:%s\r\n", params->content_type);
rc = _http_client_send(client, buf, len);
if (rc) {
goto exit;
}
}
rc = _http_client_send(client, "\r\n", 2);
if (rc) {
goto exit;
}
HAL_Free(buf);
return _http_client_send(client, (char *)params->content, params->content_length);
exit:
HAL_Free(buf);
Log_e("send request content failed %d", rc);
return rc;
}
/**
* @brief Send http request to http server.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] params @see IotHTTPRequestParams
* @return 0 for success. others @see IotReturnCode
*/
static int _http_client_send_request(IotHTTPClient *client, const IotHTTPRequestParams *params)
{
int rc;
// 1. send request line
rc = _http_client_send_request_line(client, params);
if (rc) {
Log_e("http send request line failed, rc=%d", rc);
return rc;
}
// 2. send request header
if (params->header) {
rc = _http_client_send(client, (char *)params->header, strlen(params->header));
if (rc) {
Log_e("write failed %d", rc);
return rc;
}
}
if (!params->content || !params->content_length) { // no payload body
return _http_client_send(client, "\r\n", 2);
}
// 3. send payload body
return _http_client_send_request_content(client, params);
}
/**************************************************************************************
* response
**************************************************************************************/
/**
* @brief Recv data chunked.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] timeout_ms timeout for recv
* @return length of content data recv stored in content buf
*/
static int _http_client_chunked_recv(IotHTTPClient *client, uint32_t timeout_ms)
{
uint8_t *buf = client->response.content_buf;
int buf_len = client->response.content_buf_len;
IotHTTPResponseData *response = &client->response;
char *crlf_pointer = NULL;
char *find_from = (char *)buf;
int rc = 0, read_size = 0;
int read_size_flag = 1;
Timer read_timer;
HAL_Timer_CountdownMs(&read_timer, timeout_ms);
do {
while (1) {
// find \r\n from find_from, start with beginning
crlf_pointer = strstr(find_from, "\r\n");
if (crlf_pointer) {
break;
}
if (HAL_Timer_Expired(&read_timer)) {
return 0;
}
// try to read more to found \r\n
rc = _http_client_recv(client, buf + response->recv_len, buf_len - response->recv_len, 100);
if (rc < 0) {
return response->recv_len;
}
response->recv_len += rc;
}
// read size before \r\n
if (read_size_flag) {
*crlf_pointer = '\0';
read_size = strtoul(find_from, NULL, 16);
response->recv_len -= crlf_pointer - find_from + 2;
if (!read_size) { // last chunk
response->need_recv_len = 0;
return response->recv_len;
}
memmove(find_from, crlf_pointer + 2, buf + response->recv_len - (uint8_t *)find_from);
read_size_flag = false;
continue;
}
// remove /r/n and set find from
response->recv_len -= 2;
memmove(crlf_pointer, crlf_pointer + 2, buf + response->recv_len - (uint8_t *)crlf_pointer);
find_from += read_size;
read_size_flag = true;
} while (!HAL_Timer_Expired(&read_timer));
return 0;
}
/**
* @brief Recv data according content length.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] offset offset of content buf
* @param[in] timeout_ms timeout for recv
* @return length of content data recv stored in content buf
*/
static int _http_client_content_length_recv(IotHTTPClient *client, int offset, uint32_t timeout_ms)
{
int rc, len = 0;
uint8_t *buf = client->response.content_buf;
int buf_len = client->response.content_buf_len;
IotHTTPResponseData *response = &client->response;
if (response->need_recv_len <= 0) {
// return offset for data already received
return offset;
}
// need recv length may much longger than buffer length
len = response->need_recv_len > buf_len - offset ? buf_len - offset : response->need_recv_len;
rc = _http_client_recv(client, buf + offset, len, timeout_ms);
if (rc <= 0) {
return offset ? offset : rc;
}
response->recv_len += rc;
response->need_recv_len -= rc;
return rc + offset;
}
/**
* @brief Recv content data.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] offset offset of content buf
* @param[in] timeout_ms timeout for recv
* @return length of content data recv stored in content buf
*/
static int _http_client_recv_content(IotHTTPClient *client, int offset, uint32_t timeout_ms)
{
return client->response.is_chunked ? _http_client_chunked_recv(client, timeout_ms)
: _http_client_content_length_recv(client, offset, timeout_ms);
}
/**
* @brief Recv response from http server.
*
* @param[in,out] client pointer to http client. @see IotHTTPClient
* @param[in] timeout_ms timeout for recv
* @return 0 for success. others @see IotReturnCode
*/
static int _http_client_recv_response(IotHTTPClient *client, uint32_t timeout_ms)
{
Timer timer;
char *content_length, *body_end;
int rc, len = 0;
char *buf = (char *)client->response.content_buf;
int buf_len = client->response.content_buf_len;
IotHTTPResponseData *response = &client->response;
memset(buf, 0, buf_len);
HAL_Timer_CountdownMs(&timer, timeout_ms);
// 1. found body end
while (NULL == (body_end = strstr(buf, "\r\n\r\n"))) {
// timeout
if (HAL_Timer_Expired(&timer)) {
return QCLOUD_ERR_HTTP_TIMEOUT;
}
// timeout 100ms for header less than buff len
rc = _http_client_recv(client, (uint8_t *)buf + len, buf_len - len - 1, 100);
if (rc < 0) {
Log_e("read failed, rc %d", rc);
return rc;
}
len += rc;
}
body_end += 4; // \r\n\r\n
len = len - (body_end - buf); // body length
// 2. get response code
response->status_code = atoi(buf + 9);
switch (response->status_code) {
case 403:
return QCLOUD_ERR_HTTP_AUTH;
case 404:
return QCLOUD_ERR_HTTP_NOT_FOUND;
default:
if (response->status_code < 200 || response->status_code >= 400) {
Log_w("HTTP status code %d", response->status_code);
return QCLOUD_ERR_HTTP;
}
break;
}
// 3. parse header
// content length
content_length = strstr(buf, "Content-Length");
if (content_length) {
response->is_chunked = 0;
response->content_length = atoi(content_length + strlen("Content-Length: "));
response->need_recv_len = response->content_length - len;
goto recv_content;
}
// chunked
if (strstr(buf, "Transfer-Encoding: chunked")) {
response->is_chunked = 1;
response->need_recv_len = 1; // means always need recv
goto recv_content;
}
Log_e("Could not parse header");
response->content_length = -1;
return QCLOUD_ERR_HTTP;
// 4. remove data and receive body data
recv_content:
memmove(buf, body_end, len);
response->recv_len = len;
return _http_client_recv_content(client, len, HAL_Timer_Remain(&timer));
}
/**************************************************************************************
* API
**************************************************************************************/
/**
* @brief Malloc http client.
*
* @return pointer to http client
*/
void *IOT_HTTP_Init(void)
{
return HAL_Malloc(sizeof(IotHTTPClient));
}
/**
* @brief Free http client.
*
*/
void IOT_HTTP_Deinit(void *client)
{
HAL_Free(client);
}
/**
* @brief Connect http server.
*
* @param[in,out] client pointer to http client
* @param[in] params params needed to connect http server, @see IotHTTPConnectParams
* @return 0 for success. others @see IotReturnCode
*/
int IOT_HTTP_Connect(void *client, IotHTTPConnectParams *params)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
IotHTTPClient *http_client = (IotHTTPClient *)client;
memset(http_client, 0, sizeof(IotHTTPClient));
int rc;
// parse url
HTTPData host, path;
rc = _http_client_parse_url(params->url, &host, &path);
if (rc) {
Log_e("http parse url failed %d", rc);
return rc;
}
// copy host
char *host_str = HAL_Malloc(host.data_len + 1);
if (!host_str) {
return QCLOUD_ERR_MALLOC;
}
strncpy(host_str, host.data, host.data_len);
host_str[host.data_len] = '\0';
// http connect
rc = _http_client_connect(http_client, host_str, params->port, params->ca_crt);
HAL_Free(host_str);
return rc;
}
/**
* @brief Request http server.
*
* @param[in,out] client pointer to http client
* @param[in] params params needed to send request to http server, @see IotHTTPRequestParams
* @return 0 for success. others @see IotReturnCode
*/
int IOT_HTTP_Request(void *client, IotHTTPRequestParams *params)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
return _http_client_send_request((IotHTTPClient *)client, params);
}
/**
* @brief Send data to http server.
*
* @param[in,out] client pointer to http client
* @param[in] data data to send
* @param[out] data_len data len
* @return 0 for success. others @see IotReturnCode
*/
int IOT_HTTP_Send(void *client, uint8_t *data, int data_len)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
return _http_client_send((IotHTTPClient *)client, (char *)data, data_len);
}
/**
* @brief Recv data from http server.
*
* @param[in,out] client pointer to http client
* @param[out] buf buffer to store recv data
* @param[in] buf_len buffer len
* @param timeout_ms timeout for recv
* @return >= 0 for recv data len. others @see IotReturnCode
*/
int IOT_HTTP_Recv(void *client, uint8_t *buf, int buf_len, uint32_t timeout_ms)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
IotHTTPClient *http_client = (IotHTTPClient *)client;
http_client->response.content_buf = buf;
http_client->response.content_buf_len = buf_len;
return http_client->response.need_recv_len ? _http_client_recv_content(http_client, 0, timeout_ms)
: _http_client_recv_response(http_client, timeout_ms);
}
/**
* @brief Check is recv finished.
*
* @param[in,out] client pointer to http client
* @return true for finished.
*/
int IOT_HTTP_IsRecvFinished(void *client)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
IotHTTPClient *http_client = (IotHTTPClient *)client;
return http_client->response.need_recv_len == 0;
}
/**
* @brief Disconnect http server.
*
* @param[in,out] client pointer to http client
*/
void IOT_HTTP_Disconnect(void *client)
{
POINTER_SANITY_CHECK_RTN(client);
_http_client_disconnect((IotHTTPClient *)client);
}

View File

@@ -0,0 +1,2 @@
file(GLOB src_http_signed ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(src_services ${src_services} ${src_http_signed} PARENT_SCOPE)

View File

@@ -0,0 +1,278 @@
/**
* @file http_signed.c
* @author {hubert} ({hubertxxu@tencent.com})
* @brief
* @version 1.0
* @date 2022-01-21
*
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @par Change Log:
* <table>
* Date Version Author Description
* 2022-01-21 1.0 hubertxxu first commit
* </table>
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "utils_base64.h"
#include "qcloud_iot_http_signed.h"
/**
* @brief http signed request header.
*
*/
#define HTTP_SIGNED_REQUEST_HEADER_LEN (1024)
/**
* @brief http signed request header signed length
*
*/
#define HTTP_SIGNED_STRING_BUFFER_LEN (256)
/**
* @brief http signed request url length
*
*/
#define HTTP_SIGNED_URL_LEN (128)
#define QCLOUD_SHA256_RESULT_LEN (32)
#define QCLOUD_SHA1_RESULT_LEN (20)
typedef struct {
HttpSignedParams params;
void *http_client;
IotHTTPRequestParams http_request;
char *sign_string;
char *url;
} HTTPSignedHandle;
/**
* @brief get rand
*
* @param[in, out] seed
* @return int
*/
static int _get_http_header_nonce(void)
{
srand(HAL_Timer_CurrentSec());
return rand();
}
/**
* @brief Connect http server.
*
* @param[in,out] handle pointer to http signed handle, @see HTTPSignedHandle
* @return 0 for success. others @see IotReturnCode
*/
static int _http_signed_connect(HTTPSignedHandle *handle)
{
IotHTTPConnectParams connect_params = {
.url = handle->url,
.port = "80",
.ca_crt = NULL, // TODO: support cert
};
return IOT_HTTP_Connect(handle->http_client, &connect_params);
}
/**
* @brief http signed header construct
*
* @param[in,out] handle pointer to http signed handle, @see HTTPSignedHandle
* @param request_body_buf
* @param request_body_buf_len
* @return 0 if success
*/
static void _http_signed_upload_header_construct(HTTPSignedHandle *handle, const char *request_body_buf,
int request_body_buf_len)
{
/**
* @brief http signed header format
*
*/
#define QCLOUD_HTTP_HEADER_FORMAT \
"Accept: %s*/*\r\n" \
"X-TC-Algorithm: %s\r\n" \
"X-TC-Timestamp: %d\r\n" \
"X-TC-Nonce: %d\r\n" \
"X-TC-Signature: %s\r\n"
#define QCLOUD_SUPPORT_HMACSHA1 "hmacsha1"
#define QCLOUD_SUPPORT_RSASHA256 "rsa-sha256"
#define QCLOUD_SUPPORT_ACCEPT_HEADER "application/json;"
char sign[QCLOUD_SHA1_RESULT_LEN] = {0};
char signout[QCLOUD_SHA1_RESULT_LEN * 2] = {0};
char request_buf_sha1[QCLOUD_SHA1_RESULT_LEN * 2 + 1] = {0};
size_t olen = 0;
int nonce = _get_http_header_nonce();
uint32_t timestamp = HAL_Timer_CurrentSec();
memset(handle->sign_string, 0, HTTP_SIGNED_STRING_BUFFER_LEN);
/* cal hmac sha1 */
utils_sha1_hex((const uint8_t *)request_body_buf, request_body_buf_len, (uint8_t *)request_buf_sha1);
/* create sign string */
HAL_Snprintf(handle->sign_string, HTTP_SIGNED_STRING_BUFFER_LEN, "%s\n%s\n%s\n%s\n%s\n%d\n%d\n%s", "POST",
handle->params.host, handle->params.uri, "", QCLOUD_SUPPORT_HMACSHA1, timestamp, nonce,
request_buf_sha1);
utils_hmac_sha1_hex(handle->sign_string, strlen(handle->sign_string), (uint8_t *)handle->params.secretkey,
strlen(handle->params.secretkey), sign);
/* base64 encode */
utils_base64encode((uint8_t *)signout, QCLOUD_SHA1_RESULT_LEN * 2, &olen, (const uint8_t *)sign,
QCLOUD_SHA1_RESULT_LEN);
HAL_Snprintf(handle->http_request.header, HTTP_SIGNED_REQUEST_HEADER_LEN, QCLOUD_HTTP_HEADER_FORMAT,
QCLOUD_SUPPORT_ACCEPT_HEADER, QCLOUD_SUPPORT_HMACSHA1, timestamp, nonce, signout);
}
/**
* @brief http signed init.
*
* @param params @see HttpSignedParams
* @return pointer to http signed @see HTTPSignedHandle
*/
void *_http_signed_init(HttpSignedParams *params)
{
HTTPSignedHandle *handle = HAL_Malloc(sizeof(HTTPSignedHandle));
if (!handle) {
goto exit;
}
handle->http_client = IOT_HTTP_Init();
if (!handle->http_client) {
goto exit;
}
handle->http_request.header = HAL_Malloc(HTTP_SIGNED_REQUEST_HEADER_LEN);
if (!handle->http_request.header) {
goto exit;
}
handle->sign_string = HAL_Malloc(HTTP_SIGNED_STRING_BUFFER_LEN);
if (!handle->sign_string) {
goto exit;
}
handle->url = HAL_Malloc(HTTP_SIGNED_URL_LEN);
if (!handle->url) {
goto exit;
}
handle->params = *params;
memset(handle->url, 0, HTTP_SIGNED_URL_LEN);
HAL_Snprintf(handle->url, HTTP_SIGNED_URL_LEN, "%s://%s%s", "http", handle->params.host, handle->params.uri);
return handle;
exit:
if (handle) {
HAL_Free(handle->http_request.header);
HAL_Free(handle->sign_string);
HAL_Free(handle->url);
IOT_HTTP_Deinit(handle->http_client);
HAL_Free(handle);
}
return NULL;
}
/**
* @brief send to server
*
* @param[in] handle pointer to http signed @see HTTPSignedHandle
* @param[in] upload_buf need send data
* @param[in] upload_len send length
* @return int 0 for success. others @see IotReturnCode
*/
int _http_signed_upload(HTTPSignedHandle *handle, const char *upload_buf, size_t upload_len)
{
int rc = 0;
rc = _http_signed_connect(handle);
if (rc) {
return rc;
}
_http_signed_upload_header_construct(handle, upload_buf, upload_len);
handle->http_request.url = handle->url;
handle->http_request.method = IOT_HTTP_METHOD_POST;
handle->http_request.content_type = "application/json;charset=utf-8";
handle->http_request.content = (char *)upload_buf;
handle->http_request.content_length = upload_len;
rc = IOT_HTTP_Request(handle->http_client, &handle->http_request);
return rc;
}
/**
* @brief http signed deinit
*
* @param[in] handle pointer to http signed @see HTTPSignedHandle
*/
static void _http_signed_deinit(HTTPSignedHandle *signed_handle)
{
POINTER_SANITY_CHECK_RTN(signed_handle);
IOT_HTTP_Disconnect(signed_handle->http_client);
IOT_HTTP_Deinit(signed_handle->http_client);
HAL_Free(signed_handle->http_request.header);
HAL_Free(signed_handle->sign_string);
HAL_Free(signed_handle->url);
HAL_Free(signed_handle);
}
/**
* @brief post message and recv response
*
* @param params @see HttpSignedParams
* @param request_buf request buffer
* @param request_buf_len request buffer length
* @param response_buf response buffer if need recv
* @param response_buf_len response buffer length if need recv
* @return int if need recv return recv length else return 0 is success
*/
int IOT_HTTP_Signed_Request(HttpSignedParams *params, const char *request_buf, size_t request_buf_len,
uint8_t *response_buf, int response_buf_len)
{
int rc = 0;
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(request_buf, QCLOUD_ERR_INVAL);
if (params->need_recv) {
POINTER_SANITY_CHECK(response_buf, QCLOUD_ERR_INVAL);
}
HTTPSignedHandle *handle = _http_signed_init(params);
if (!handle) {
Log_e("http signed init error.");
goto exit;
}
rc = _http_signed_upload(handle, request_buf, request_buf_len);
if (rc) {
Log_e("http signed upload error.");
goto exit;
}
if (params->need_recv) {
rc = IOT_HTTP_Recv(handle->http_client, response_buf, response_buf_len, params->recv_timeout_ms);
}
exit:
_http_signed_deinit(handle);
return rc;
}

View File

@@ -0,0 +1,14 @@
file(GLOB src_log_upload ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(inc_log_upload ${CMAKE_CURRENT_SOURCE_DIR}/inc/)
set(src_services ${src_services} ${src_log_upload} PARENT_SCOPE)
set(inc_services ${inc_services} ${inc_log_upload} PARENT_SCOPE)
file(GLOB src_log_upload_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/log_upload_sample.c)
add_executable(log_upload_sample ${src_log_upload_sample})
target_link_libraries(log_upload_sample ${libsdk})
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,215 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client.h
* @brief mqtt client internel api
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-31
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-31 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_LOG_UPLOAD_INC_LOG_UPLOAD_H_
#define IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_LOG_UPLOAD_INC_LOG_UPLOAD_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "qcloud_iot_log_upload.h"
#include "utils_list.h"
#include "utils_base64.h"
#include "utils_hmac.h"
/**
* @brief To check log http server return msg or not
*
*/
#define LOG_CHECK_HTTP_RET_CODE
/**
* @brief enable log upload debug or not
*
*/
// #define LOG_UPLOAD_DEBUG
/**
* @brief http recv length
*
*/
#define HTTP_RET_JSON_LENGTH 256
/**
* @brief wait http recv time out
*
*/
#define HTTP_WAIT_RET_TIMEOUT_MS 1000
/**
* @brief do immediate log update if buffer is lower than this threshold (about two max log item)
*
*/
#define LOG_LOW_BUFFER_THRESHOLD (LOG_UPLOAD_BUFFER_SIZE / 4)
/**
* @brief sing key length
*
*/
#define SIGN_KEY_SIZE (24)
#ifdef LOG_UPLOAD_DEBUG
#define UPLOAD_DBG(fmt, ...) HAL_Printf(">>LOG-DBG>>%s(%d): " fmt "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define UPLOAD_DBG(...)
#endif
#define UPLOAD_ERR(fmt, ...) HAL_Printf(">>LOG-ERR>>%s(%d): " fmt "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
/**
* @brief clear log upload buffer
*
*/
void log_upload_clear_buffer(void);
/**
* @brief set log upload in comm err state
*
* @param state true or false
*/
void log_upload_set_upload_log_in_comm_err(bool state);
/**
* @brief change log upload level
*
* @param level
*/
void log_upload_set_log_upload_level(LogLevel level);
/**
* @brief get log upload level
*
* @return LogLevel
*/
LogLevel log_upload_get_log_upload_level(void);
/**
* @brief log upload buffer init
*
* @param init_params @see LogUploadInitParams
* @return void* if success return @see LogUploadBuffer
*/
void *log_upload_buffer_init(LogUploadInitParams *init_params);
/**
* @brief deinit log buffer handle
*
* @param upload_buffer_handle
*/
void log_upload_buffer_deinit(void *upload_buffer_handle);
/**
* @brief Get the log buffer write index object
*
* @param log_upload_buffer_handle
* @return uint32_t
*/
uint32_t get_log_buffer_write_index(void *log_upload_buffer_handle);
/**
* @brief Get the log buffer head len object
*
* @param log_upload_buffer_handle
* @return uint32_t
*/
uint32_t get_log_buffer_head_len(void *log_upload_buffer_handle);
/**
* @brief check log buffer is empty or not
*
* @param log_upload_buffer_handle
* @return true empty
* @return false not empty
*/
bool have_data_need_update(void *log_upload_buffer_handle);
/**
* @brief When a new log appears during the reporting period, move the log to the specified position in the buffer
*
*/
void remove_log_from_buffer(void *log_upload_buffer_handle, uint32_t need_remove_size);
/**
* @brief copy log buffer head
*
* @param[out] dst Destination address
* @param log_upload_buffer_handle
*/
void copy_log_buffer_header(char *dst, void *log_upload_buffer_handle);
/**
* @brief lock log buffer
*
*/
void log_buffer_lock(void);
/**
* @brief unlock log buffer
*
*/
void log_buffer_unlock(void);
/**
* @brief post data to tencent cloud over http
*
* @param[in] post_buf need post data buffer
* @param[in] post_size need post data buffer length
* @return @see IotReturnCode
*/
int post_one_http_to_server(void *handle, char *post_buf, size_t post_size);
/**
* @brief Get the log buffer object
*
* @param log_upload_buffer_handle
* @return char*
*/
char *get_log_buffer(void *log_upload_buffer_handle);
/**
* @brief push data to log buffer
*
* @param[in] log_content need push data
* @return int if 0 success else QCLOUD_ERR_FAILURE
*/
int append_data_to_log_buffer(const char *log_content);
/**
* @brief init log upload module
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int log_upload_init(void *client);
/**
* @brief Get the log buffer size object
*
* @return uint32_t log buffer size
*/
uint32_t get_log_buffer_size(void);
#ifdef __cplusplus
}
#endif
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_LOG_UPLOAD_INC_LOG_UPLOAD_H_

View File

@@ -0,0 +1,220 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file broadcast_sample.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_common.h"
#include "utils_log.h"
/**
* @brief MQTT event callback, @see MQTTEventHandleFun
*
* @param[in] client pointer to mqtt client
* @param[in] handle_context context
* @param[in] msg msg
*/
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
{
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
uintptr_t packet_id = (uintptr_t)msg->msg;
switch (msg->event_type) {
case MQTT_EVENT_UNDEF:
Log_i("undefined event occur.");
break;
case MQTT_EVENT_DISCONNECT:
Log_i("MQTT disconnect.");
break;
case MQTT_EVENT_RECONNECT:
Log_i("MQTT reconnect.");
break;
case MQTT_EVENT_PUBLISH_RECEIVED:
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
break;
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_NACK:
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_NACK:
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_SUCCESS:
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_TIMEOUT:
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_NACK:
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
break;
default:
Log_i("Should NOT arrive here.");
break;
}
}
/**
* @brief Setup MQTT construct parameters.
*
* @param[in,out] initParams @see MQTTInitParams
* @param[in] device_info @see DeviceInfo
*/
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
{
init_params->device_info = device_info;
init_params->command_timeout = QCLOUD_IOT_MQTT_COMMAND_TIMEOUT;
init_params->keep_alive_interval_ms = QCLOUD_IOT_MQTT_KEEP_ALIVE_INTERNAL;
init_params->auto_connect_enable = 1;
init_params->event_handle.h_fp = _mqtt_event_handler;
init_params->event_handle.context = NULL;
}
static void _setup_log_upload_init_params(LogUploadInitParams *init_params, DeviceInfo *device_info)
{
init_params->device_name = device_info->device_name;
init_params->product_id = device_info->product_id;
init_params->sign_key = device_info->device_secret;
init_params->log_buffer_size = LOG_UPLOAD_BUFFER_SIZE;
init_params->save_log_filename = "./tmp/upload-fail-save.log";
init_params->read_func = HAL_File_Read;
init_params->save_func = HAL_File_Save;
init_params->del_func = HAL_File_Del;
init_params->get_size_func = HAL_File_Get_Size;
}
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc;
int loop_cnt = 5;
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
func.log_upload = IOT_Log_Upload_AppendToUploadBuffer;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
DeviceInfo device_info;
rc = HAL_GetDevInfo((void *)&device_info);
if (rc) {
Log_e("get device info failed: %d", rc);
return rc;
}
// init connection
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
_setup_connect_init_params(&init_params, &device_info);
// init log upload
LogUploadInitParams log_upload_init_params = DEFAULT_LOG_UPLOAD_INIT_PARAMS;
_setup_log_upload_init_params(&log_upload_init_params, &device_info);
IOT_Log_Upload_InitPre(&log_upload_init_params);
// create MQTT client and connect with server
void *client = IOT_MQTT_Construct(&init_params);
if (client) {
Log_i("Cloud Device Construct Success");
rc = IOT_Log_Upload_Init(client);
if (rc) {
Log_e("Log upload init failed: %d", rc);
}
IOT_Log_Upload(true);
} else {
Log_e("MQTT Construct failed!");
return QCLOUD_ERR_FAILURE;
}
do {
Log_d("log upload test debug %d...", loop_cnt);
Log_i("log upload test info %d...", loop_cnt);
Log_w("log upload test waring %d...", loop_cnt);
Log_e("log upload test error %d...", loop_cnt);
rc = IOT_MQTT_Yield(client, 200);
IOT_Log_Upload(rc ? true : false);
} while (loop_cnt--);
// If you have to report some urgent messages, you can call this function
IOT_Log_Upload(true);
rc = IOT_MQTT_Destroy(&client);
IOT_Log_Upload_Deinit();
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,741 @@
/**
* @file log_buffer.c
* @author {hubert} ({hubertxxu@tencent.com})
* @brief
* @version 1.0
* @date 2022-01-23
*
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @par Change Log:
* <table>
* Date Version Author Description
* 2022-01-23 1.0 hubertxxu first commit
* </table>
*/
#include "log_upload.h"
typedef struct {
void *lock_buf;
char *log_buffer;
const char *host;
uint32_t log_buffer_size;
uint32_t log_buffer_head_len;
uint32_t write_index;
char sign_key[SIGN_KEY_SIZE + 1];
} LogUploadBuffer;
static LogUploadBuffer *sg_log_upload_buffer_handle = NULL;
// ----------------------------------------------------------------------------
// Common
// ----------------------------------------------------------------------------
/**
* @brief reset log upload buffer
*
* @param[in,out] log_upload_buffer_handle handle of log upload buffer
*/
static void _reset_log_buffer(LogUploadBuffer *log_upload_buffer_handle)
{
log_upload_buffer_handle->write_index = log_upload_buffer_handle->log_buffer_head_len;
memset(log_upload_buffer_handle->log_buffer + log_upload_buffer_handle->log_buffer_head_len, 0,
log_upload_buffer_handle->log_buffer_size - log_upload_buffer_handle->log_buffer_head_len);
}
static LogUploadBuffer *_log_upload_buffer_init_pre(LogUploadInitParams *init_params)
{
LogUploadBuffer *handle = (LogUploadBuffer *)HAL_Malloc(sizeof(LogUploadBuffer));
if (!handle) {
UPLOAD_ERR("log upload buffer handle malloc error.");
goto err_exit;
}
memset(handle, 0, sizeof(LogUploadBuffer));
handle->lock_buf = HAL_MutexCreate();
if (!handle->lock_buf) {
UPLOAD_ERR("mutex create failed");
goto err_exit;
}
handle->log_buffer_size = init_params->log_buffer_size;
handle->log_buffer = HAL_Malloc(handle->log_buffer_size);
if (!handle->log_buffer) {
UPLOAD_ERR("malloc log buffer failed");
goto err_exit;
}
memset(handle->log_buffer, 0, handle->log_buffer_size);
/*init sign key*/
int key_len = strlen(init_params->sign_key);
if (!key_len) {
UPLOAD_ERR("invalid sign key length");
goto err_exit;
}
memcpy(handle->sign_key, init_params->sign_key, key_len > SIGN_KEY_SIZE ? SIGN_KEY_SIZE : key_len);
sg_log_upload_buffer_handle = handle;
return handle;
err_exit:
if (handle) {
HAL_MutexDestroy(handle->lock_buf);
HAL_Free(handle);
}
return NULL;
}
/**
* @brief deinit log buffer handle
*
* @param upload_buffer_handle
*/
void log_upload_buffer_deinit(void *upload_buffer_handle)
{
LogUploadBuffer *handle = (LogUploadBuffer *)upload_buffer_handle;
if (!handle) {
return;
}
HAL_MutexLock(handle->lock_buf);
_reset_log_buffer(handle);
HAL_Free(handle->log_buffer);
handle->log_buffer = NULL;
HAL_MutexUnlock(handle->lock_buf);
HAL_MutexDestroy(handle->lock_buf);
handle->lock_buf = NULL;
HAL_Free(handle);
sg_log_upload_buffer_handle = NULL;
}
/**
* @brief Get the log buffer write index object
*
* @param log_upload_buffer_handle
* @return uint32_t
*/
uint32_t get_log_buffer_write_index(void *log_upload_buffer_handle)
{
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
HAL_MutexLock(handle->lock_buf);
uint32_t write_index = handle->write_index;
HAL_MutexUnlock(handle->lock_buf);
return write_index;
}
/**
* @brief Get the log buffer object
*
* @param log_upload_buffer_handle
* @return char*
*/
char *get_log_buffer(void *log_upload_buffer_handle)
{
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
return handle->log_buffer;
}
/**
* @brief Get the log buffer head len object
*
* @param log_upload_buffer_handle
* @return uint32_t
*/
uint32_t get_log_buffer_head_len(void *log_upload_buffer_handle)
{
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
return handle->log_buffer_head_len;
}
/**
* @brief check log buffer is empty or not
*
* @param log_upload_buffer_handle
* @return true empty
* @return false not empty
*/
bool have_data_need_update(void *log_upload_buffer_handle)
{
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
return handle->write_index == handle->log_buffer_head_len;
}
/**
* @brief When a new log appears during the reporting period, move the log to the specified position in the buffer
*
*/
void remove_log_from_buffer(void *log_upload_buffer_handle, uint32_t need_remove_size)
{
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
if (need_remove_size > handle->write_index || need_remove_size > handle->log_buffer_size) {
return;
}
HAL_MutexLock(handle->lock_buf);
memmove(handle->log_buffer + handle->log_buffer_head_len, handle->log_buffer + need_remove_size,
handle->write_index - need_remove_size);
handle->write_index = handle->write_index - need_remove_size + handle->log_buffer_head_len;
memset(handle->log_buffer + handle->write_index, 0, handle->log_buffer_size - handle->write_index);
HAL_MutexUnlock(handle->lock_buf);
}
/**
* @brief copy log buffer head
*
* @param[out] dst Destination address
* @param log_upload_buffer_handle
*/
void copy_log_buffer_header(char *dst, void *log_upload_buffer_handle)
{
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
memcpy(dst, handle->log_buffer, handle->log_buffer_head_len);
}
/**
* @brief clear log upload buffer
*
*/
void log_upload_clear_buffer(void)
{
if (!sg_log_upload_buffer_handle) {
UPLOAD_ERR("log upload buffer not init.");
return;
}
HAL_MutexLock(sg_log_upload_buffer_handle->lock_buf);
_reset_log_buffer(sg_log_upload_buffer_handle);
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
}
/**
* @brief Get the log buffer size object
*
* @return uint32_t log buffer size
*/
uint32_t get_log_buffer_size(void)
{
if (!sg_log_upload_buffer_handle) {
UPLOAD_ERR("log upload buffer not init.");
return 0;
}
return sg_log_upload_buffer_handle->log_buffer_size;
}
/**
* @brief
*
*/
void log_buffer_lock(void)
{
HAL_MutexLock(sg_log_upload_buffer_handle->lock_buf);
}
/**
* @brief
*
*/
void log_buffer_unlock(void)
{
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
}
// ----------------------------------------------------------------------------
// New log fromat by json
// ----------------------------------------------------------------------------
#ifdef LOG_UPLOAD_TYPE_JSON
/**
* @brief log upload buffer init
*
* @param init_params @see LogUploadInitParams
* @return void* if success return @see LogUploadBuffer
*/
void *log_upload_buffer_init(LogUploadInitParams *init_params)
{
LogUploadBuffer *handle = _log_upload_buffer_init_pre(init_params);
if (!handle) {
return NULL;
}
handle->log_buffer_head_len = HAL_Snprintf(handle->log_buffer, handle->log_buffer_size,
"{\"DeviceName\":\"%s\",\"ProductId\":\"%s\",\"Message\":[",
init_params->device_name, init_params->product_id);
handle->write_index = handle->log_buffer_head_len;
handle->host = init_params->host;
_reset_log_buffer(handle);
return handle;
}
#ifdef LOG_CHECK_HTTP_RET_CODE
/**
* @brief get http respose result
*
* @param[in] json http response buffer
* @param[in] json_len http response buffer length
* @return true log report success
* @return false log report error
*/
static bool _get_json_ret_code(char *json, size_t json_len)
{
int rc = 0;
UtilsJsonValue resposse;
// get response
rc = utils_json_value_get("Response.Error", strlen("Response.Error"), json, json_len, &resposse);
if (rc) {
return true;
}
return false;
}
#endif
/**
* @brief when upload. append endchar in the upload buffer ending
*
* @param[in,out] log_buf upload buffer
* @param[in] log_size upload buffer index
*/
static void _append_endchar_to_upload_buffer(char *log_buf, size_t log_size)
{
log_buf[log_size - 2] = ']';
log_buf[log_size - 1] = '}';
}
/**
* @brief when upload. remove endchar in the upload buffer ending
*
* @param[in,out] log_buf upload buffer
* @param[in] log_size upload buffer index
*/
static void _remove_endchar_from_upload_buffer(char *log_buf, size_t log_size)
{
log_buf[log_size - 2] = ',';
log_buf[log_size - 1] = ' ';
}
/**
* @brief post data to tencent cloud over http
*
* @param[in] post_buf need post data buffer
* @param[in] post_size need post data buffer length
* @return @see IotReturnCode
*/
int post_one_http_to_server(void *log_upload_buffer_handle, char *post_buf, size_t post_size)
{
int rc = 0;
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
if (!post_size) {
return 0;
}
_append_endchar_to_upload_buffer(post_buf, post_size);
HttpSignedParams params = {
.host = handle->host ? handle->host : LOG_UPLOAD_SERVER_URL,
.uri = LOG_UPLOAD_URI_PATH,
.secretkey = handle->sign_key,
#ifdef LOG_CHECK_HTTP_RET_CODE
.need_recv = true,
.recv_timeout_ms = HTTP_WAIT_RET_TIMEOUT_MS,
#else
.need_recv = false,
.recv_timeout_ms = 0,
#endif
};
#ifdef LOG_CHECK_HTTP_RET_CODE
char buf[HTTP_RET_JSON_LENGTH] = {0};
rc = IOT_HTTP_Signed_Request(&params, post_buf, post_size, (uint8_t *)buf, HTTP_RET_JSON_LENGTH);
if (rc < 0) {
UPLOAD_ERR("IOT_HTTP_Signed_Request failed, rc = %d", rc);
goto exit;
}
if (rc == 0) {
UPLOAD_ERR("HTTP Recv length = %d", rc);
rc = QCLOUD_ERR_HTTP;
goto exit;
}
buf[rc - 1] = '\0'; // json_parse relies on a string
if (strlen(buf) > 0 && _get_json_ret_code(buf, rc)) {
UPLOAD_DBG("Log server return SUCCESS: %s", buf);
rc = QCLOUD_RET_SUCCESS;
} else {
UPLOAD_ERR("Log server return FAIL: %s", buf);
rc = QCLOUD_ERR_HTTP;
}
#else
rc = IOT_HTTP_Signed_Request(&params, post_buf, post_size, NULL, 0);
#endif
exit:
_remove_endchar_from_upload_buffer(post_buf, post_size);
UPLOAD_DBG("%ld data have be post to server. rc : %d", post_size, rc);
return rc;
}
/**
* @brief push data to log buffer
*
* @param[in] log_content need push data
* @return int if 0 success else QCLOUD_ERR_FAILURE
*/
int append_data_to_log_buffer(const char *log_content)
{
uint16_t log_size = strlen(log_content);
if (HAL_MutexTryLock(sg_log_upload_buffer_handle->lock_buf) != 0) {
UPLOAD_ERR("trylock buffer failed!");
return QCLOUD_ERR_FAILURE;
}
if ((sg_log_upload_buffer_handle->write_index + log_size + 5) > sg_log_upload_buffer_handle->log_buffer_size) {
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
UPLOAD_ERR("log upload buffer is not enough!");
return QCLOUD_ERR_FAILURE;
}
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\"';
int index = 0;
while (index < (log_size - 2)) {
// replace may overflow buffer size
if ((sg_log_upload_buffer_handle->write_index + 5) > sg_log_upload_buffer_handle->log_buffer_size) {
break;
}
switch (log_content[index]) {
case '\"':
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\"';
break;
case '\\':
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
break;
case '/':
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '/';
break;
case '\b':
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 'b';
break;
case '\f':
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 'f';
break;
case '\n':
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 'n';
break;
case '\r':
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 'r';
break;
case '\t':
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\\';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = 't';
break;
default:
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] =
log_content[index];
}
index++;
}
/* replace \r\n to \", add as delimiter */
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = '\"';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = ',';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index++] = ' ';
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
return QCLOUD_RET_SUCCESS;
}
#else
// ----------------------------------------------------------------------------
// Old log format by text
// ----------------------------------------------------------------------------
/* log post header format */
#define TIMESTAMP_SIZE (10)
#define SIGNATURE_SIZE (40)
#define CTRL_BYTES_SIZE (4)
// LOG_BUF_FIXED_HEADER_SIZE = 112
#define LOG_BUF_FIXED_HEADER_SIZE \
(SIGNATURE_SIZE + CTRL_BYTES_SIZE + MAX_SIZE_OF_PRODUCT_ID + MAX_SIZE_OF_DEVICE_NAME + TIMESTAMP_SIZE)
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
#include "mbedtls/aes.h"
/**
* @brief header of encrypt
*
*/
static char sg_head_content[128] = {0};
/**
* @brief log data encrypt by aes cbc
*
* @param handle
* @param buf need encrypt data
* @param datalen need encrypt data length
* @return int
*/
static int _log_aes_cbc_encrypt(LogUploadBuffer *handle, uint8_t *buf, uint32_t datalen)
{
int ret = QCLOUD_RET_SUCCESS;
mbedtls_aes_context ctx;
unsigned char iv[16];
memset(iv, '0', 16);
mbedtls_aes_init(&ctx);
ret = mbedtls_aes_setkey_enc(&ctx, (uint8_t *)handle->sign_key, 128);
if (ret) {
UPLOAD_ERR("Set encry key err,ret:%d", ret);
ret = QCLOUD_ERR_FAILURE;
goto exit;
}
ret = mbedtls_aes_crypt_cbc(&ctx, MBEDTLS_AES_ENCRYPT, datalen, iv, buf, buf);
if (ret) {
Log_e("encrypt err,ret:%d", ret);
ret = QCLOUD_ERR_FAILURE;
goto exit;
}
mbedtls_aes_free(&ctx);
return QCLOUD_RET_SUCCESS;
exit:
mbedtls_aes_free(&ctx);
return ret;
}
#endif
/**
* @brief log upload buffer init
*
* @param init_params @see LogUploadInitParams
* @return void* if success return @see LogUploadBuffer
*/
void *log_upload_buffer_init(LogUploadInitParams *init_params)
{
LogUploadBuffer *handle = _log_upload_buffer_init_pre(init_params);
if (!handle) {
return NULL;
}
handle->log_buffer_head_len = LOG_BUF_FIXED_HEADER_SIZE;
memset(handle->log_buffer, '#', handle->log_buffer_head_len);
handle->log_buffer[SIGNATURE_SIZE] = 'P';
memcpy(handle->log_buffer + SIGNATURE_SIZE + CTRL_BYTES_SIZE, init_params->product_id, MAX_SIZE_OF_PRODUCT_ID);
memcpy(handle->log_buffer + SIGNATURE_SIZE + CTRL_BYTES_SIZE + MAX_SIZE_OF_PRODUCT_ID, init_params->device_name,
strlen(init_params->device_name));
handle->write_index = handle->log_buffer_head_len;
handle->host = init_params->host;
_reset_log_buffer(handle);
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
HAL_Snprintf(sg_head_content, 128,
"Accept:application/json;*/*\r\n"
"X-TC-IotEncryType:%c\r\n"
"X-TC-IotCid:%s%s\r\n",
handle->log_buffer[SIGNATURE_SIZE], init_params->product_id, init_params->device_name);
#endif
return handle;
}
#ifdef LOG_CHECK_HTTP_RET_CODE
/**
* @brief get http respose result
*
* @param[in] json http response buffer
* @param[in] json_len http response buffer length
* @return true log report success
* @return false log report error
*/
static bool _get_json_ret_code(char *json, size_t json_len)
{
int rc = 0;
uint32_t ret_code;
rc = utils_json_get_uint32("Retcode", strlen("Retcode"), json, json_len, &ret_code);
if (!rc) {
if (!ret_code) {
return true;
}
}
return false;
}
#endif
/**
* @brief update timestamp add signature post buffer
*
*/
static void _update_time_and_signature(LogUploadBuffer *handle, char *post_buf, size_t post_size)
{
char timestamp[TIMESTAMP_SIZE + 1] = {0};
char signature[SIGNATURE_SIZE + 1] = {0};
uint32_t now_time = HAL_Timer_CurrentSec();
/* record the timestamp for this log uploading */
HAL_Snprintf(timestamp, TIMESTAMP_SIZE + 1, "%010d", now_time);
memcpy(post_buf + sg_log_upload_buffer_handle->log_buffer_head_len - TIMESTAMP_SIZE, timestamp, strlen(timestamp));
/* signature of this log uploading */
utils_hmac_sha1(post_buf + SIGNATURE_SIZE, post_size - SIGNATURE_SIZE, (uint8_t *)handle->sign_key,
strlen(handle->sign_key), signature);
memcpy(post_buf, signature, SIGNATURE_SIZE);
}
/**
* @brief post data to tencent cloud over http
*
* @param[in] post_buf need post data buffer
* @param[in] post_size need post data buffer length
* @return @see IotReturnCode
*/
int post_one_http_to_server(void *log_upload_buffer_handle, char *post_buf, size_t post_size)
{
int rc = 0;
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
char *log_encrypt_buffer = NULL;
#endif
LogUploadBuffer *handle = (LogUploadBuffer *)log_upload_buffer_handle;
_update_time_and_signature(handle, post_buf, post_size);
void *http_client = IOT_HTTP_Init();
if (!http_client) {
UPLOAD_ERR("http client init error");
goto exit;
}
IotHTTPConnectParams connect_params = {
.url = handle->host ? handle->host : LOG_UPLOAD_OLD_SERVER_URL,
.port = LOG_UPLOAD_OLD_SERVER_PORT,
.ca_crt = NULL, // TODO: support cert
};
rc = IOT_HTTP_Connect(http_client, &connect_params);
if (rc) {
UPLOAD_ERR("http connect error.");
goto exit;
}
IotHTTPRequestParams request = {
.url = handle->host ? handle->host : LOG_UPLOAD_OLD_SERVER_URL,
.method = IOT_HTTP_METHOD_POST,
.content_length = post_size,
.content_type = "text/plain;charset=utf-8",
.content = post_buf,
.header = "Accept:application/json;*/*\r\n",
};
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
/*ANSI X.923: zero-padding with last byte as padlen */
int padlen = 16 - post_size % 16;
int datalen = post_size + padlen;
log_encrypt_buffer = HAL_Malloc(datalen + 1);
if (!log_encrypt_buffer) {
UPLOAD_ERR("malloc log encrypt buffer error. malloc length : %d", datalen);
rc = QCLOUD_ERR_MALLOC;
goto exit;
}
memset(log_encrypt_buffer, 0, datalen + 1);
memcpy(log_encrypt_buffer, post_buf, post_size);
log_encrypt_buffer[datalen - 1] = padlen;
// aes cbc
rc = _log_aes_cbc_encrypt(handle, (uint8_t *)log_encrypt_buffer, datalen);
if (QCLOUD_RET_SUCCESS != rc) {
UPLOAD_ERR("log encrypt failed, ret = %d", rc);
goto exit;
}
request.header = sg_head_content;
request.content_type = "application/octet-stream";
request.content_length = datalen;
request.content = log_encrypt_buffer;
#endif
rc = IOT_HTTP_Request(http_client, &request);
// 2. check rc
if (rc) {
UPLOAD_ERR("http post error.");
goto exit;
}
#ifdef LOG_CHECK_HTTP_RET_CODE
char buf[HTTP_RET_JSON_LENGTH] = {0};
rc = IOT_HTTP_Recv(http_client, (uint8_t *)buf, HTTP_RET_JSON_LENGTH, HTTP_WAIT_RET_TIMEOUT_MS);
if (rc < 0) {
UPLOAD_ERR("qcloud_http_recv_data failed, rc = %d", rc);
} else if (rc == 0) {
UPLOAD_ERR("HTTP Recv length == %d", rc);
rc = QCLOUD_ERR_HTTP_TIMEOUT;
} else {
buf[rc - 1] = '\0'; // json_parse relies on a string
if (strlen(buf) > 0 && _get_json_ret_code(buf, rc)) {
UPLOAD_DBG("Log server return SUCCESS: %s", buf);
rc = QCLOUD_RET_SUCCESS;
} else {
UPLOAD_ERR("Log server return FAIL: %s", buf);
rc = QCLOUD_ERR_HTTP;
}
}
#endif
exit:
if (http_client) {
IOT_HTTP_Disconnect(http_client);
IOT_HTTP_Deinit(http_client);
}
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
if (log_encrypt_buffer) {
HAL_Free(log_encrypt_buffer);
log_encrypt_buffer = NULL;
}
#endif
UPLOAD_DBG("%ld data have be post to server. rc : %d", post_size, rc);
return rc;
}
/**
* @brief push data to log buffer
*
* @param[in] log_content need push data
* @return int if 0 success else QCLOUD_ERR_FAILURE
*/
int append_data_to_log_buffer(const char *log_content)
{
uint16_t log_size = strlen(log_content);
if (HAL_MutexTryLock(sg_log_upload_buffer_handle->lock_buf) != 0) {
UPLOAD_ERR("trylock buffer failed!");
return QCLOUD_ERR_FAILURE;
}
if ((sg_log_upload_buffer_handle->write_index + log_size + 1) > sg_log_upload_buffer_handle->log_buffer_size) {
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
UPLOAD_ERR("log upload buffer is not enough! remain : %d, need upload length: %d",
sg_log_upload_buffer_handle->log_buffer_size - sg_log_upload_buffer_handle->write_index, log_size);
return -1;
}
memcpy(sg_log_upload_buffer_handle->log_buffer + sg_log_upload_buffer_handle->write_index, log_content, log_size);
sg_log_upload_buffer_handle->write_index += log_size;
/* replace \r\n to \n\f as delimiter */
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index - 1] = '\f';
sg_log_upload_buffer_handle->log_buffer[sg_log_upload_buffer_handle->write_index - 2] = '\n';
HAL_MutexUnlock(sg_log_upload_buffer_handle->lock_buf);
return QCLOUD_RET_SUCCESS;
}
#endif

View File

@@ -0,0 +1,183 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file log_mqtt.c
* @brief
* @author hubertxxu (hubertxxu@tencent.com)
* @version 1.0
* @date 2022-01-05
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2022-01-05 <td>1.0 <td>hubertxxu <td>first commit
* </table>
*/
#include "log_upload.h"
/**
* @brief LogLevelResultInfo handle
*
*/
typedef struct {
bool result_recv_ok;
uint32_t log_level;
} LogLevelResultInfo;
/**
* @brief MQTTLogUploadHandle handle
*
*/
typedef struct {
void *mqtt_client;
Timer time_update_timer;
} MQTTLogUploadHandle;
static MQTTLogUploadHandle sg_mqtt_log_upload_handle;
/**
* @brief
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see LogLevelResultInfo
*/
static void _log_mqtt_message_callback(void *client, const MQTTMessage *message, void *usr_data)
{
int rc;
LogLevelResultInfo *result = (LogLevelResultInfo *)usr_data;
Log_d("Receive log result message:%.*s", message->payload_len, message->payload_str);
// get log level
rc = utils_json_get_uint32("log_level", strlen("log_level"), message->payload_str, message->payload_len,
&result->log_level);
if (rc) {
return;
}
switch (result->log_level) {
case LOG_LEVEL_DISABLE:
log_upload_clear_buffer();
log_upload_set_upload_log_in_comm_err(true);
#ifdef LOG_UPLOAD_AES_ENCRYPT_POST
// when use aes encrypt. no need report log
log_upload_set_log_upload_level(LOG_LEVEL_DISABLE);
UPLOAD_DBG("disable log upload.");
#else
log_upload_set_log_upload_level(LOG_LEVEL_ERROR);
UPLOAD_DBG("Upload log level change to: %d", LOG_LEVEL_ERROR);
#endif
break;
case LOG_LEVEL_ERROR:
case LOG_LEVEL_WARN:
case LOG_LEVEL_INFO:
case LOG_LEVEL_DEBUG:
if (result->log_level < log_upload_get_log_upload_level()) {
log_upload_clear_buffer();
}
log_upload_set_upload_log_in_comm_err(false);
log_upload_set_log_upload_level((LogLevel)result->log_level);
UPLOAD_DBG("Upload log level change to: %d", result->log_level);
break;
default:
UPLOAD_ERR("Invalid log level: %d", result->log_level);
break;
}
result->result_recv_ok = true;
}
/**
* @brief Check and subscribe log result topic.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
static int _log_upload_topic_check_and_sub(void *client, const char *topic)
{
int rc = 0;
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
LogLevelResultInfo *log_level_info = (LogLevelResultInfo *)HAL_Malloc(sizeof(LogLevelResultInfo));
if (!log_level_info) {
return QCLOUD_ERR_MALLOC;
}
log_level_info->result_recv_ok = false;
sub_params.on_message_handler = _log_mqtt_message_callback;
sub_params.qos = QOS1;
sub_params.user_data = log_level_info;
sub_params.user_data_free = HAL_Free;
rc = IOT_MQTT_SubscribeSync(client, topic, &sub_params);
if (rc) {
HAL_Free(log_level_info);
}
return rc;
}
/**
* @brief Publish get log level message to log topic.
*
* @param[in,out] client pointer to mqtt client
* @return >= 0 for success
*/
static int _mqtt_get_log_level_publish(void *client)
{
static int sg_get_log_level_token_count = 0;
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC];
char payload[128] = {0};
HAL_Snprintf(topic_name, sizeof(topic_name), "$log/operation/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
HAL_Snprintf(payload, sizeof(payload), "{\"type\": \"get_log_level\",\"clientToken\": \"get_log_level-%u\"}",
sg_get_log_level_token_count++);
PublishParams pub_params = DEFAULT_PUB_PARAMS;
pub_params.qos = QOS0;
pub_params.payload = payload;
pub_params.payload_len = strlen(payload);
return IOT_MQTT_Publish(client, topic_name, &pub_params);
}
/**
* @brief init log upload module
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int log_upload_init(void *client)
{
int rc = 0;
char log_upload_topic[MAX_SIZE_OF_CLOUD_TOPIC];
/* subscribe the log topic: "$log/operation/result/${productId}/${deviceName}" */
HAL_Snprintf(log_upload_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$log/operation/result/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
rc = _log_upload_topic_check_and_sub(client, log_upload_topic);
if (rc) {
return rc;
}
rc = _mqtt_get_log_level_publish(client);
if (rc < 0) {
return rc;
}
sg_mqtt_log_upload_handle.mqtt_client = client;
return rc;
}

View File

@@ -0,0 +1,539 @@
/**
* @file log_upload.c
* @author {hubert} ({hubertxxu@tencent.com})
* @brief
* @version 1.0
* @date 2022-01-24
*
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @par Change Log:
* <table>
* Date Version Author Description
* 2022-01-24 1.0 hubertxxu first commit
* </table>
*/
#include "log_upload.h"
#ifdef LOG_UPLOAD_TYPE_JSON
#define LOG_DELIMITER "\", "
#else
#define LOG_DELIMITER "\n\f"
#endif
/**
* @brief data structure for log upload
*
*/
typedef struct {
bool upload_only_in_comm_err;
LogLevel log_level;
Timer upload_timer;
void *upload_buffer_handle;
const char *save_log_filename;
LogSaveFunc save_func;
LogReadFunc read_func;
LogDelFunc del_func;
LogGetSizeFunc get_size_func;
bool log_save_enabled;
bool log_client_init_done;
} QcloudIoTLogUpload;
static QcloudIoTLogUpload *sg_log_uploader_client;
// ----------------------------------------------------------------------------
// Internal API
// ----------------------------------------------------------------------------
/**
* @brief
*
* @return QcloudIoTLogUpload* @see QcloudIoTLogUpload
*/
static QcloudIoTLogUpload *_get_log_uploader_client(void)
{
if (!sg_log_uploader_client) {
return NULL;
}
if (!sg_log_uploader_client->log_client_init_done) {
return NULL;
}
return sg_log_uploader_client;
}
/**
* @brief
*
* @param client @see QcloudIoTLogUpload
*/
static void _set_log_uploader_client(void *client)
{
sg_log_uploader_client = client;
}
/**
* @brief set log upload in comm err state
*
* @param state true or false
*/
void log_upload_set_upload_log_in_comm_err(bool state)
{
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
POINTER_SANITY_CHECK_RTN(log_uploader_handle);
log_uploader_handle->upload_only_in_comm_err = state;
}
/**
* @brief change log upload level
*
* @param level
*/
void log_upload_set_log_upload_level(LogLevel level)
{
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
POINTER_SANITY_CHECK_RTN(log_uploader_handle);
log_uploader_handle->log_level = level;
}
/**
* @brief get log upload level
*
* @return LogLevel
*/
LogLevel log_upload_get_log_upload_level(void)
{
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
POINTER_SANITY_CHECK(log_uploader_handle, LOG_LEVEL_DISABLE);
return log_uploader_handle->log_level;
}
/**
* @brief check if need force upload
*
* @param[in] force_upload
* @return true need force upload
* @return false not needed force upload
*/
static bool _check_force_upload(bool force_upload)
{
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
if (!log_uploader_handle) {
return false;
}
/* 1: check if have data to upload */
if (have_data_need_update(log_uploader_handle->upload_buffer_handle)) {
return false;
}
uint32_t write_index = get_log_buffer_write_index(log_uploader_handle->upload_buffer_handle);
bool is_low_buffer = (get_log_buffer_size() - write_index) < LOG_LOW_BUFFER_THRESHOLD ? true : false;
/* 2. check if upload only in comm err */
if (!force_upload && log_uploader_handle->upload_only_in_comm_err) {
/* buffer is low but we couldn't upload now, reset buffer */
if (is_low_buffer) {
log_upload_clear_buffer();
}
HAL_Timer_CountdownMs(&log_uploader_handle->upload_timer, LOG_UPLOAD_INTERVAL_MS);
return false;
}
/* 3: force upload is true, handle it right now */
if (force_upload) {
return true;
}
/* 4 : check if buffer is low */
if (is_low_buffer) {
/* buffer is low, handle it right now */
return true;
}
/*5: check if timeout, handle it right now*/
return HAL_Timer_Expired(&log_uploader_handle->upload_timer);
}
/**
* @brief save log to file or flash
*
* @param[in] log_buf need saved buffer
* @param[in] log_size need saved buffer length
* @return @see IotReturnCode
*/
static int _save_log(char *log_buf, size_t log_size)
{
int rc = 0;
size_t write_size, current_size;
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
POINTER_SANITY_CHECK(log_uploader_handle, QCLOUD_ERR_INVAL);
if (!log_size) {
UPLOAD_ERR("nothing need to save. log_size : %ld", log_size);
return -1;
}
current_size = log_uploader_handle->get_size_func(log_uploader_handle->save_log_filename);
/* overwrite the previous saved log to avoid too many saved logs */
if ((current_size + log_size) > MAX_LOG_SAVE_SIZE) {
UPLOAD_ERR("overwrite the previous saved log. %ld", current_size);
rc = log_uploader_handle->del_func(log_uploader_handle->save_log_filename);
if (rc) {
Log_e("fail to delete previous log");
}
}
UPLOAD_DBG("saved data [%ld]", log_size);
write_size = log_uploader_handle->save_func(log_uploader_handle->save_log_filename, log_buf, log_size);
if (write_size != log_size) {
Log_e("fail to save log. RC %ld - log size %ld", write_size, log_size);
}
return write_size != log_size ? -1 : 0;
}
static uint32_t _buffer_get_at_most_message(char *post_buf)
{
uint32_t possible_size = 0;
char *next_log_buf = NULL;
size_t delimiter_len = strlen(LOG_DELIMITER);
while (possible_size < MAX_HTTP_LOG_POST_SIZE) {
/* locate the delimiter */
next_log_buf = strstr(post_buf + possible_size, LOG_DELIMITER);
if (!next_log_buf) {
return 0;
}
possible_size = (size_t)(next_log_buf - post_buf + delimiter_len);
/* end of log */
if (next_log_buf[delimiter_len] == 0 && possible_size < MAX_HTTP_LOG_POST_SIZE) {
break;
}
}
return possible_size;
}
/**
* @brief report these to the server
*
* @param post_buf
* @param post_size
* @param is_log_buffer
* @return int
*/
static int _post_log_buffer_to_cloud(QcloudIoTLogUpload *log_uploader_handle, char *post_buf, size_t post_size,
bool is_log_buffer)
{
int rc = 0;
uint32_t log_buffer_head_len = get_log_buffer_head_len(log_uploader_handle->upload_buffer_handle);
size_t orig_post_size = post_size;
size_t post_payload, possible_size, actual_post_payload = 0;
do {
// 1. get message in buffer
possible_size = _buffer_get_at_most_message(post_buf);
if (!possible_size) {
UPLOAD_ERR("Upload size should not be 0! Total sent: %ld. Left: %ld",
actual_post_payload + log_buffer_head_len, post_size);
if (is_log_buffer) {
log_upload_clear_buffer();
}
return QCLOUD_RET_SUCCESS;
}
if (is_log_buffer && (possible_size > get_log_buffer_size() || possible_size > orig_post_size ||
possible_size <= log_buffer_head_len)) {
log_upload_clear_buffer();
UPLOAD_ERR("possible size (%ld) is too large.", possible_size);
return QCLOUD_RET_SUCCESS;
}
// 2. post to server
rc = post_one_http_to_server(log_uploader_handle->upload_buffer_handle, post_buf, possible_size);
if (rc) {
UPLOAD_ERR("Send log failed. Total sent: %ld. Left: %ld", actual_post_payload + log_buffer_head_len,
post_size);
// when send failed save log in file.
if (log_uploader_handle->log_save_enabled && is_log_buffer) {
_save_log(post_buf + log_buffer_head_len, possible_size - log_buffer_head_len);
}
}
// 3. remove have be post message
memmove(post_buf + log_buffer_head_len, post_buf + possible_size, post_size - possible_size);
post_payload = possible_size - log_buffer_head_len;
post_size -= post_payload;
actual_post_payload += post_payload;
memset(post_buf + post_size, 0, orig_post_size - post_size);
UPLOAD_DBG("post log %d OK. Total sent: %d. Left: %d ", possible_size,
actual_post_payload + log_buffer_head_len, post_size);
} while (post_size > log_buffer_head_len);
if (is_log_buffer) {
remove_log_from_buffer(log_uploader_handle->upload_buffer_handle, orig_post_size);
}
return rc;
}
/**
* @brief handle log to save flash or file
*
* @return @see IotReturnCode
*/
static int _handle_saved_log(void)
{
int rc = QCLOUD_RET_SUCCESS;
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
if (!log_uploader_handle) {
return QCLOUD_ERR_FAILURE;
}
size_t whole_log_size = log_uploader_handle->get_size_func(log_uploader_handle->save_log_filename);
if (!whole_log_size) {
return rc;
}
uint32_t log_buffer_head_len = get_log_buffer_head_len(log_uploader_handle->upload_buffer_handle);
size_t buf_size = whole_log_size + log_buffer_head_len + 1;
char *log_buf = HAL_Malloc(buf_size);
if (!log_buf) {
UPLOAD_ERR("Malloc failed, size: %ld", buf_size);
return QCLOUD_ERR_FAILURE;
}
/* read the whole log to buffer */
size_t read_len = log_uploader_handle->read_func(log_uploader_handle->save_log_filename,
log_buf + log_buffer_head_len, whole_log_size);
if (read_len != whole_log_size) {
Log_e("fail to read whole saved log. Size: %ld - read: %ld", whole_log_size, read_len);
rc = QCLOUD_ERR_FAILURE;
goto exit;
}
/* copy header from global log buffer */
size_t upload_size = whole_log_size + log_buffer_head_len;
copy_log_buffer_header(log_buf, log_uploader_handle->upload_buffer_handle);
log_buf[buf_size - 1] = 0;
/* post server */
rc = _post_log_buffer_to_cloud(log_uploader_handle, log_buf, upload_size, false);
if (rc == QCLOUD_RET_SUCCESS || rc == QCLOUD_ERR_INVAL || rc == QCLOUD_ERR_HTTP) {
UPLOAD_DBG("handle saved log done! Size: %ld. ", whole_log_size);
log_uploader_handle->del_func(log_uploader_handle->save_log_filename);
}
exit:
HAL_Free(log_buf);
return rc;
}
/**
* @brief append need report log to log upload buffer
*
* @param[in] log_content data of need to report
*/
static void _log_upload_append_to_log_buffer(int log_level, const char *log_content)
{
int rc = 0;
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
// POINTER_SANITY_CHECK_RTN(log_uploader_handle);
if (!log_uploader_handle) {
return;
}
// if not necessary. just give up upload.
if (log_level > log_uploader_handle->log_level) {
return;
}
uint16_t log_size = strlen(log_content);
if (!log_size) {
UPLOAD_ERR("log_size is not 0!");
return;
}
rc = append_data_to_log_buffer(log_content);
if (rc) {
/* log buffer is full. upload data right now */
HAL_Timer_CountdownMs(&log_uploader_handle->upload_timer, 0);
}
}
/**
* @brief upload data to tencent cloud
*
* @param force_upload if necessary
* @return @see IotReturnCode
*/
static int _do_log_upload(bool force_upload)
{
int rc;
static bool sg_need_handle_saved_log = true;
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
POINTER_SANITY_CHECK(log_uploader_handle, QCLOUD_ERR_INVAL);
/* step1: handle previously saved log */
if (log_uploader_handle->log_save_enabled && sg_need_handle_saved_log) {
rc = _handle_saved_log();
if (!rc) {
sg_need_handle_saved_log = false;
}
}
/* step2: check if have data to upload */
if (!_check_force_upload(force_upload)) {
return QCLOUD_RET_SUCCESS;
}
// log_buffer_lock();
/* step3: post log buffer data to cloud */
uint32_t upload_log_size = get_log_buffer_write_index(log_uploader_handle->upload_buffer_handle);
rc = _post_log_buffer_to_cloud(log_uploader_handle, get_log_buffer(log_uploader_handle->upload_buffer_handle),
upload_log_size, true);
if (rc) {
sg_need_handle_saved_log = true;
}
HAL_Timer_CountdownMs(&log_uploader_handle->upload_timer, LOG_UPLOAD_INTERVAL_MS);
// log_buffer_unlock();
return QCLOUD_RET_SUCCESS;
}
/**
* @brief init log upload previously
*
* @param[in] init_params @see LogUploadInitParams
* @return @see IotReturnCode
*/
static int log_upload_init_pre(LogUploadInitParams *init_params)
{
if (_get_log_uploader_client()) {
UPLOAD_DBG("log upload initialized.");
return QCLOUD_RET_SUCCESS;
}
QcloudIoTLogUpload *log_uploader_handle = HAL_Malloc(sizeof(QcloudIoTLogUpload));
if (!log_uploader_handle) {
UPLOAD_ERR("allocate for log client failed");
goto err_exit;
}
log_uploader_handle->upload_only_in_comm_err = false;
/* all the call back functions are necessary to handle log save and re-upload*/
if (init_params->save_func && init_params->read_func && init_params->del_func && init_params->get_size_func &&
init_params->save_log_filename) {
log_uploader_handle->save_log_filename = init_params->save_log_filename;
log_uploader_handle->save_func = init_params->save_func;
log_uploader_handle->read_func = init_params->read_func;
log_uploader_handle->del_func = init_params->del_func;
log_uploader_handle->get_size_func = init_params->get_size_func;
log_uploader_handle->log_save_enabled = true;
} else {
log_uploader_handle->log_save_enabled = false;
}
log_uploader_handle->upload_buffer_handle = log_upload_buffer_init(init_params);
if (!log_uploader_handle->upload_buffer_handle) {
goto err_exit;
}
_set_log_uploader_client(log_uploader_handle);
log_uploader_handle->log_level = LOG_LEVEL_DEBUG;
log_uploader_handle->log_client_init_done = true;
return QCLOUD_RET_SUCCESS;
err_exit:
if (log_uploader_handle) {
log_upload_buffer_deinit(log_uploader_handle->upload_buffer_handle);
HAL_Free(log_uploader_handle);
log_uploader_handle = NULL;
}
return QCLOUD_ERR_FAILURE;
}
/**
* @brief stop log upload add release resources
*
* @return @see IotReturnCode
*/
static int log_upload_deinit(void)
{
QcloudIoTLogUpload *log_uploader_handle = _get_log_uploader_client();
POINTER_SANITY_CHECK(log_uploader_handle, QCLOUD_ERR_INVAL);
log_upload_buffer_deinit(log_uploader_handle->upload_buffer_handle);
HAL_Free(log_uploader_handle);
log_uploader_handle = NULL;
return QCLOUD_RET_SUCCESS;
}
// ----------------------------------------------------------------------------
// External API
// ----------------------------------------------------------------------------
/**
* @brief init log upload previously
*
* @param[in] init_params @see LogUploadInitParams
* @return @see IotReturnCode
*/
int IOT_Log_Upload_InitPre(LogUploadInitParams *init_params)
{
POINTER_SANITY_CHECK(init_params, QCLOUD_ERR_INVAL);
STRING_PTR_SANITY_CHECK(init_params->product_id, QCLOUD_ERR_INVAL);
STRING_PTR_SANITY_CHECK(init_params->device_name, QCLOUD_ERR_INVAL);
STRING_PTR_SANITY_CHECK(init_params->sign_key, QCLOUD_ERR_INVAL);
return log_upload_init_pre(init_params);
}
/**
* @brief init log upload module
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int IOT_Log_Upload_Init(void *client)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
return log_upload_init(client);
}
/**
* @brief stop log upload add release resources
*
* @return @see IotReturnCode
*/
int IOT_Log_Upload_Deinit(void)
{
return log_upload_deinit();
}
/**
* @brief do log upload
*
* @param force_upload if necessary
* @return @see IotReturnCode
*/
int IOT_Log_Upload(bool force_upload)
{
return _do_log_upload(force_upload);
}
/**
* @brief append need report log to log upload buffer
*
* @param[in] log_content data of need to report
*/
void IOT_Log_Upload_AppendToUploadBuffer(int log_level, const char *log_content)
{
POINTER_SANITY_CHECK_RTN(log_content);
_log_upload_append_to_log_buffer(log_level, log_content);
}

View File

@@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
DerivePointerAlignment: true
PointerAlignment: Left
SortIncludes: true
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash

View File

@@ -0,0 +1,82 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file test_broadcast.cc
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <iostream>
#include <string>
#include "gtest/gtest.h"
#include "mqtt_client_test.h"
#include "qcloud_iot_common.h"
namespace mqtt_client_unittest {
/**
* @brief Test log upload
*
*/
TEST_F(MqttClientTest, log_upload_test) {
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
func.log_upload = IOT_Log_Upload_AppendToUploadBuffer;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
LogUploadInitParams log_upload_init_params = DEFAULT_LOG_UPLOAD_INIT_PARAMS;
log_upload_init_params.device_name = device_info.device_name;
log_upload_init_params.product_id = device_info.product_id;
log_upload_init_params.sign_key = device_info.device_secret;
log_upload_init_params.log_buffer_size = LOG_UPLOAD_BUFFER_SIZE;
log_upload_init_params.save_log_filename = "./tmp/upload-fail-save.log";
log_upload_init_params.read_func = HAL_File_Read;
log_upload_init_params.save_func = HAL_File_Save;
log_upload_init_params.del_func = HAL_File_Del;
log_upload_init_params.get_size_func = HAL_File_Get_Size;
ASSERT_EQ(IOT_Log_Upload_InitPre(&log_upload_init_params), 0);
uint32_t loop_cnt = 10;
int rc = 0;
do {
Log_d("log upload test debug %d...", loop_cnt);
Log_i("log upload test info %d...", loop_cnt);
Log_w("log upload test waring %d...", loop_cnt);
Log_e("log upload test error %d...", loop_cnt);
rc = IOT_MQTT_Yield(client, 200);
ASSERT_EQ(IOT_Log_Upload(rc ? true : false), 0);
} while (loop_cnt--);
ASSERT_EQ(IOT_Log_Upload_Deinit(), 0);
func.log_upload = NULL;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
}
} // namespace mqtt_client_unittest

View File

@@ -0,0 +1,16 @@
file(GLOB src_mqtt_client ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(inc_mqtt_client ${CMAKE_CURRENT_SOURCE_DIR}/inc/)
set(src_services ${src_services} ${src_mqtt_client} PARENT_SCOPE)
set(inc_services ${inc_services} ${inc_mqtt_client} PARENT_SCOPE)
file(GLOB src_mqtt_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/mqtt_sample.c)
add_executable(mqtt_sample ${src_mqtt_sample})
target_link_libraries(mqtt_sample ${libsdk})
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
set(inc_mqtt_client_test ${CMAKE_CURRENT_SOURCE_DIR}/test)
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
set(inc_test ${inc_test} ${inc_mqtt_client_test} PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,446 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client.h
* @brief mqtt client internel api
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-31
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-31 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_INC_MQTT_CLIENT_H_
#define IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_INC_MQTT_CLIENT_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "qcloud_iot_mqtt_client.h"
#include "mqtt_packet.h"
#include "network_interface.h"
#include "utils_list.h"
#include "utils_base64.h"
#include "utils_hmac.h"
/**
* @brief Packet id, random from [1 - 65536]
*
*/
#define MAX_PACKET_ID (65535)
/**
* @brief Max size of conn Id
*
*/
#define MAX_CONN_ID_LEN (6)
/**
* @brief Max number of topic subscribed
*
*/
#define MAX_MESSAGE_HANDLERS (10)
/**
* @brief Max number in repub list
*
*/
#define MAX_REPUB_NUM (20)
/**
* @brief Minimal wait interval when reconnect
*
*/
#define MIN_RECONNECT_WAIT_INTERVAL (1000)
/**
* @brief Minimal MQTT timeout value
*
*/
#define MIN_COMMAND_TIMEOUT (500)
/**
* @brief Maxmal MQTT timeout value
*
*/
#define MAX_COMMAND_TIMEOUT (20000)
/**
* @brief Minimal TLS handshaking timeout value (unit: ms)
*
*/
#define QCLOUD_IOT_TLS_HANDSHAKE_TIMEOUT (5 * 1000)
/**
* @brief Enable repeat packet id filter
*
*/
#define MQTT_RMDUP_MSG_ENABLED
/**
* @brief Connect status of mqtt server
*
*/
typedef enum {
NOTCONNECTED = 0,
CONNECTED = 1,
} ConnStatus;
/**
* @brief mqtt connection mode
*
*/
typedef enum {
FIRST_CONNECT = 0,
RECONNECT = 1,
} ConnMode;
/*
* @brief Subscribe status of topic.
*
*/
typedef enum {
SUB_ACK_NOT_RECEIVED = 0,
SUB_ACK_RECEIVED = 1,
} SubStatus;
/**
* @brief data structure for topic subscription handle
*/
typedef struct {
char *topic_filter; /**< topic name, wildcard filter is supported */
SubStatus status; /**< status of sub handle */
SubscribeParams params; /**< params needed to subscribe */
} SubTopicHandle;
/**
* @brief MQTT QCloud IoT Client structure
*
*/
typedef struct {
DeviceInfo *device_info; /**< device info needed to connect server */
uint16_t next_packet_id; /**< MQTT random packet id */
uint32_t command_timeout_ms; /**< MQTT command timeout, unit:ms */
uint8_t write_buf[QCLOUD_IOT_MQTT_TX_BUF_LEN]; /**< MQTT write buffer */
uint8_t read_buf[QCLOUD_IOT_MQTT_RX_BUF_LEN]; /**< MQTT read buffer */
size_t write_buf_size; /**< size of MQTT write buffer */
size_t read_buf_size; /**< size of MQTT read buffer */
MQTTEventHandler event_handle; /**< callback for MQTT event */
uint8_t auto_connect_enable; /**< enable auto connection or not */
uint8_t default_subscribe; /**< no subscribe packet send, only add subhandle */
void *lock_generic; /**< mutex/lock for this client struture */
void *lock_write_buf; /**< mutex/lock for write buffer */
void *list_pub_wait_ack; /**< puback waiting list */
void *list_sub_wait_ack; /**< suback waiting list */
char host_addr[HOST_STR_LENGTH]; /**< MQTT server host */
MQTTGetNextHostIp get_next_host_ip; // get host ip
const char *main_host;
const char *backup_host; // if host not connect will try this.
IotNetwork network_stack; /**< MQTT network stack */
MQTTPacketConnectOption options; /**< handle to connection parameters */
char conn_id[MAX_CONN_ID_LEN]; /**< connect id */
SubTopicHandle sub_handles[MAX_MESSAGE_HANDLERS]; /**< subscription handle array */
Timer ping_timer; /**< MQTT ping timer */
Timer reconnect_delay_timer; /**< MQTT reconnect delay timer */
uint8_t was_manually_disconnected; /**< was disconnect by server or device */
uint8_t is_ping_outstanding; /**< 1 = ping request is sent while ping response not arrived yet */
uint32_t current_reconnect_wait_interval; /**< unit:ms */
uint8_t is_connected; /**< is connected or not */
uint32_t counter_network_disconnected; /**< number of disconnection*/
#ifdef MQTT_RMDUP_MSG_ENABLED
#define MQTT_MAX_REPEAT_BUF_LEN 10
uint16_t repeat_packet_id_buf[MQTT_MAX_REPEAT_BUF_LEN]; /**< repeat packet id buffer */
unsigned int current_packet_id_cnt; /**< index of packet id buffer */
#endif
} QcloudIotClient;
/**
* @brief topic publish info
*
*/
typedef struct {
uint8_t *buf; /**< msg buffer */
uint32_t len; /**< msg length */
uint16_t packet_id; /**< packet id */
Timer pub_start_time; /**< timer for puback waiting */
} QcloudIotPubInfo;
/**
* @brief topic subscribe/unsubscribe info
*
*/
typedef struct {
uint8_t *buf; /**< msg buffer */
uint16_t len; /**< msg length */
MQTTPacketType type; /**< type: sub or unsub */
uint16_t packet_id; /**< packet id */
Timer sub_start_time; /**< timer for suback waiting */
SubTopicHandle handler; /**< handle of topic subscribed(unsubscribed) */
} QcloudIotSubInfo;
/**************************************************************************************
* common
**************************************************************************************/
/**
* @brief Get the next packet id object.
*
* @param[in,out] client pointer to mqtt client
* @return packet id
*/
uint16_t get_next_packet_id(QcloudIotClient *client);
/**
* @brief Get the next conn id object.
*
* @param[out] conn_id buffer to save conn id
*/
void get_next_conn_id(char *conn_id);
/**
* @brief Set the client conn state object.
*
* @param[in,out] client pointer to mqtt client
* @param[in] connected connect status, @see ConnStatus
*/
void set_client_conn_state(QcloudIotClient *client, uint8_t connected);
/**
* @brief Get the client conn state object.
*
* @param[in,out] client
* @return @see ConnStatus
*/
uint8_t get_client_conn_state(QcloudIotClient *client);
/**
* @brief Send mqtt packet, timeout = command_timeout_ms.
*
* @param[in,out] client pointer to mqtt client
* @param[in] length length of data to be sent, data is saved in client write_buf
* @return @see IotReturnCode
*/
int send_mqtt_packet(QcloudIotClient *client, size_t length);
/**************************************************************************************
* connect
**************************************************************************************/
/**
* @brief Connect MQTT server.
*
* @param[in,out] client pointer to mqtt client
* @param[in] mode when connect error. Choose different connection strategies according to the mode
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_connect(QcloudIotClient *client, ConnMode mode);
/**
* @brief Reconnect MQTT server.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_attempt_reconnect(QcloudIotClient *client);
/**
* @brief Disconnect MQTT server.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_disconnect(QcloudIotClient *client);
/**
* @brief Serialize and send pingreq packet.
*
* @param[in,out] client pointer to mqtt client
* @param[in] try_times if failed, retry times
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_pingreq(QcloudIotClient *client, int try_times);
/**************************************************************************************
* publish
**************************************************************************************/
/**
* @brief Serialize and send publish packet.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] topic_name topic to publish
* @param[in] params publish params
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
*/
int qcloud_iot_mqtt_publish(QcloudIotClient *client, const char *topic_name, const PublishParams *params);
/**
* @brief Deserialize publish packet and deliver_message.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] params publish params
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
*/
int qcloud_iot_mqtt_handle_publish(QcloudIotClient *client);
/**
* @brief Deserialize puback packet.
*
* @param[in,out] client pointer to mqtt_client
* @return 0 for success.
*/
int qcloud_iot_mqtt_handle_puback(QcloudIotClient *client);
/**
* @brief Process puback waiting timout.
*
* @param[in,out] client pointer to mqtt_client
*/
void qcloud_iot_mqtt_check_pub_timeout(QcloudIotClient *client);
/**************************************************************************************
* subscribe
**************************************************************************************/
/**
* @brief Serialize and send subscribe packet.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic to subscribe
* @param[in] params subscribe params
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
*/
int qcloud_iot_mqtt_subscribe(QcloudIotClient *client, const char *topic_filter, const SubscribeParams *params);
/**
* @brief Deserialize suback packet and return sub result.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_handle_suback(QcloudIotClient *client);
/**
* @brief Serialize and send unsubscribe packet.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic to unsubscribe
* @return >=0 packet id, < 0 for failed @see IotReturnCode
*/
int qcloud_iot_mqtt_unsubscribe(QcloudIotClient *client, const char *topic_filter);
/**
* @brief Deserialize unsuback packet and remove from list.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_handle_unsuback(QcloudIotClient *client);
/**
* @brief Process suback waiting timeout.
*
* @param[in,out] client pointer to mqtt client
*/
void qcloud_iot_mqtt_check_sub_timeout(QcloudIotClient *client);
/**
* @brief Resubscribe topic when reconnect.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_resubscribe(QcloudIotClient *client);
/**
* @brief Return if topic is sub ready.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter
* @return true for ready
* @return false for not ready
*/
bool qcloud_iot_mqtt_is_sub_ready(QcloudIotClient *client, const char *topic_filter);
/**
* @brief Get usr data, usr should handle lock/unlock usrdata itself in callback and caller.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter
* @return NULL or user data
*/
void *qcloud_iot_mqtt_get_subscribe_usr_data(QcloudIotClient *client, const char *topic_filter);
/**
* @brief Clear sub handle array.
*
* @param[in,out] client pointer to mqtt client
*/
void qcloud_iot_mqtt_sub_handle_array_clear(QcloudIotClient *client);
/**
* @brief Clear suback wait list and clear sub handle.
*
* @param[in,out] client pointer to mqtt client
*/
void qcloud_iot_mqtt_suback_wait_list_clear(QcloudIotClient *client);
/**************************************************************************************
* yield
**************************************************************************************/
/**
* @brief Check connection and keep alive state, read/handle MQTT message in synchronized way.
*
* @param[in,out] client pointer to mqtt client
* @param[in] timeout_ms timeout value (unit: ms) for this operation
*
* @return QCLOUD_RET_SUCCESS when success, QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT when try reconnecting, others @see
* IotReturnCode
*/
int qcloud_iot_mqtt_yield(QcloudIotClient *client, uint32_t timeout_ms);
/**
* @brief Wait read specific mqtt packet, such as connack.
*
* @param[in,out] client pointer to mqtt client
* @param[in] packet_type packet type except to read
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_wait_for_read(QcloudIotClient *client, uint8_t packet_type);
#ifdef __cplusplus
}
#endif
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_INC_MQTT_CLIENT_H_

View File

@@ -0,0 +1,306 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_sample.c
* @brief a simple sample for mqtt client
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-31
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-31 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_common.h"
#include "utils_log.h"
/**
* @brief MQTT event callback, @see MQTTEventHandleFun
*
* @param[in] client pointer to mqtt client
* @param[in] handle_context context
* @param[in] msg msg
*/
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
{
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
uintptr_t packet_id = (uintptr_t)msg->msg;
switch (msg->event_type) {
case MQTT_EVENT_UNDEF:
Log_i("undefined event occur.");
break;
case MQTT_EVENT_DISCONNECT:
Log_i("MQTT disconnect.");
break;
case MQTT_EVENT_RECONNECT:
Log_i("MQTT reconnect.");
break;
case MQTT_EVENT_PUBLISH_RECEIVED:
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
break;
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_NACK:
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_NACK:
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_SUCCESS:
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_TIMEOUT:
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_NACK:
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
break;
default:
Log_i("Should NOT arrive here.");
break;
}
}
/**
* @brief Setup MQTT construct parameters.
*
* @param[in,out] initParams @see MQTTInitParams
* @param[in] device_info @see DeviceInfo
*/
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
{
init_params->device_info = device_info;
init_params->command_timeout = QCLOUD_IOT_MQTT_COMMAND_TIMEOUT;
init_params->keep_alive_interval_ms = QCLOUD_IOT_MQTT_KEEP_ALIVE_INTERNAL;
init_params->auto_connect_enable = 1;
init_params->event_handle.h_fp = _mqtt_event_handler;
init_params->event_handle.context = NULL;
}
/**
* @brief Publish a test mqtt message.
*
* @param[in, out] client pointer to mqtt client
* @param[in] topic_keyword topic keyword
* @param[in] qos qos to publish
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
static int _publish_test_msg(void *client, const char *topic_keyword, QoS qos)
{
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
DeviceInfo *dev_info = IOT_MQTT_GetDeviceInfo(client);
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/%s", STRING_PTR_PRINT_SANITY_CHECK(dev_info->product_id),
STRING_PTR_PRINT_SANITY_CHECK(dev_info->device_name), STRING_PTR_PRINT_SANITY_CHECK(topic_keyword));
static int test_count = 0;
PublishParams pub_params = DEFAULT_PUB_PARAMS;
pub_params.qos = qos;
char publish_content[128] = {0};
HAL_Snprintf(publish_content, sizeof(publish_content), "{\"action\": \"publish_test\", \"count\": \"%d\"}",
test_count++);
pub_params.payload = publish_content;
pub_params.payload_len = strlen(publish_content);
return IOT_MQTT_Publish(client, topic_name, &pub_params);
}
/**
* @brief Callback when MQTT msg arrives @see OnMessageHandler
*
* @param[in, out] client pointer to mqtt client
* @param[in] message publish message from server
* @param[in] usr_data user data of SubscribeParams, @see SubscribeParams
*/
static void _on_message_callback(void *client, const MQTTMessage *message, void *user_data)
{
if (!message) {
return;
}
Log_i("Receive Message With topicName:%.*s, payload:%.*s", message->topic_len,
STRING_PTR_PRINT_SANITY_CHECK(message->topic_name), message->payload_len,
STRING_PTR_PRINT_SANITY_CHECK(message->payload_str));
}
/**
* @brief Subscribe MQTT topic and wait for sub result.
*
* @param[in, out] client pointer to mqtt client
* @param[in] topic_keyword topic keyword
* @param[in] qos qos to subscribe
* @return @see IotReturnCode
*/
static int _subscribe_topic_wait_result(void *client, char *topic_keyword, QoS qos)
{
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
DeviceInfo *dev_info = IOT_MQTT_GetDeviceInfo(client);
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/%s", STRING_PTR_PRINT_SANITY_CHECK(dev_info->product_id),
STRING_PTR_PRINT_SANITY_CHECK(dev_info->device_name), STRING_PTR_PRINT_SANITY_CHECK(topic_keyword));
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
sub_params.qos = qos;
sub_params.on_message_handler = _on_message_callback;
return IOT_MQTT_SubscribeSync(client, topic_name, &sub_params);
}
/**
* @brief Unsubscribe MQTT topic.
*
* @param[in, out] client pointer to mqtt client
* @param[in] topic_keyword topic keyword
* @return @see IotReturnCode
*/
static int _unsubscribe_topic(void *client, char *topic_keyword)
{
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
DeviceInfo *dev_info = IOT_MQTT_GetDeviceInfo(client);
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/%s", STRING_PTR_PRINT_SANITY_CHECK(dev_info->product_id),
STRING_PTR_PRINT_SANITY_CHECK(dev_info->device_name), STRING_PTR_PRINT_SANITY_CHECK(topic_keyword));
int rc = IOT_MQTT_Unsubscribe(client, topic_name);
if (rc < 0) {
Log_e("MQTT unsubscribe FAILED: %d", rc);
return rc;
}
return IOT_MQTT_Yield(client, 500); // wait for unsuback
}
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc;
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
DeviceInfo device_info;
rc = HAL_GetDevInfo((void *)&device_info);
if (rc) {
Log_e("get device info failed: %d", rc);
return rc;
}
// init connection
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
_setup_connect_init_params(&init_params, &device_info);
// create MQTT client and connect with server
void *client = IOT_MQTT_Construct(&init_params);
if (client) {
Log_i("Cloud Device Construct Success");
} else {
Log_e("MQTT Construct failed!");
return QCLOUD_ERR_FAILURE;
}
// subscribe normal topics and wait result
rc = _subscribe_topic_wait_result(client, "data", QOS0);
if (rc) {
Log_e("Client Subscribe Topic Failed: %d", rc);
return rc;
}
do {
rc = _publish_test_msg(client, "data", QOS1);
if (rc < 0) {
Log_e("client publish topic failed :%d.", rc);
}
rc = IOT_MQTT_Yield(client, 2000);
if (rc == QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT) {
HAL_SleepMs(1000);
continue;
} else if (rc != QCLOUD_RET_SUCCESS && rc != QCLOUD_RET_MQTT_RECONNECTED) {
Log_e("exit with error: %d", rc);
break;
}
} while (!sg_main_exit);
rc = _unsubscribe_topic(client, "data");
rc |= IOT_MQTT_Destroy(&client);
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,592 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-28
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-07 <td>1.1 <td>fancyxu <td>support user host for unittest
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#include "mqtt_client.h"
#define MQTT_VERSION_3_1_1 4
// 20 for timestamp length & delimiter
#define MAX_MQTT_CONNECT_USR_NAME_LEN (MAX_SIZE_OF_CLIENT_ID + QCLOUD_IOT_DEVICE_SDK_APPID_LEN + MAX_CONN_ID_LEN + 20)
#define MAX_MQTT_CONNECT_PASSWORD_LEN (51)
/**************************************************************************************
* static method
**************************************************************************************/
/**
* @brief Get random packet id, when start.
*
* @return packet id
*/
static uint16_t _get_random_start_packet_id(void)
{
srand(HAL_Timer_CurrentSec());
return rand() % 65536 + 1;
}
/**
* @brief Init list_pub_wait_ack and list_sub_wait_ack
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
static int _mqtt_client_list_init(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
UtilsListFunc func = {
.list_malloc = HAL_Malloc,
.list_free = HAL_Free,
.list_lock_init = HAL_MutexCreate,
.list_lock_deinit = HAL_MutexDestroy,
.list_lock = HAL_MutexLock,
.list_unlock = HAL_MutexUnlock,
};
client->list_pub_wait_ack = utils_list_create(func, MAX_REPUB_NUM);
if (!client->list_pub_wait_ack) {
Log_e("create pub wait list failed.");
goto error;
}
client->list_sub_wait_ack = utils_list_create(func, MAX_MESSAGE_HANDLERS);
if (!client->list_sub_wait_ack) {
Log_e("create sub wait list failed.");
goto error;
}
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
error:
utils_list_destroy(client->list_pub_wait_ack);
client->list_pub_wait_ack = NULL;
utils_list_destroy(client->list_sub_wait_ack);
client->list_sub_wait_ack = NULL;
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
/**
* @brief Init network, tcp(with AUTH_WITH_NO_TLS) or tls.
*
* @param[in,out] client pointer to mqtt client
*/
static void _mqtt_client_network_init(QcloudIotClient *client, const MQTTInitParams *params)
{
HAL_Snprintf(client->host_addr, HOST_STR_LENGTH, "%s.%s", client->device_info->product_id,
params->host ? params->host : QCLOUD_IOT_MQTT_DIRECT_DOMAIN);
client->main_host = params->host;
client->backup_host = params->backup_host;
client->get_next_host_ip = params->get_next_host_ip;
#ifndef AUTH_WITH_NO_TLS
// device param for TLS connection
#ifdef AUTH_MODE_CERT
Log_d("cert file: %s", STRING_PTR_PRINT_SANITY_CHECK(client->device_info->cert_file));
Log_d("key file: %s", STRING_PTR_PRINT_SANITY_CHECK(client->device_info->key_file));
client->network_stack.ssl_connect_params.cert_file = client->device_info->cert_file;
client->network_stack.ssl_connect_params.key_file = client->device_info->key_file;
client->network_stack.ssl_connect_params.ca_crt = iot_ca_get();
client->network_stack.ssl_connect_params.ca_crt_len = strlen(client->network_stack.ssl_connect_params.ca_crt);
#else
client->network_stack.ssl_connect_params.psk = (char *)client->device_info->device_secret_decode;
client->network_stack.ssl_connect_params.psk_length = client->device_info->device_secret_decode_len;
client->network_stack.ssl_connect_params.psk_id = client->device_info->client_id;
client->network_stack.ssl_connect_params.ca_crt = NULL;
client->network_stack.ssl_connect_params.ca_crt_len = 0;
#endif
client->network_stack.host = client->host_addr;
client->network_stack.port = MQTT_SERVER_PORT_TLS;
client->network_stack.ssl_connect_params.timeout_ms = client->command_timeout_ms > QCLOUD_IOT_TLS_HANDSHAKE_TIMEOUT
? client->command_timeout_ms
: QCLOUD_IOT_TLS_HANDSHAKE_TIMEOUT;
client->network_stack.type = IOT_NETWORK_TYPE_TLS;
#else
client->network_stack.host = client->host_addr;
client->network_stack.port = MQTT_SERVER_PORT_NO_TLS;
client->network_stack.type = IOT_NETWORK_TYPE_TCP;
#endif
qcloud_iot_network_init(&(client->network_stack));
}
/**
* @brief Init mqtt connection options.
*
* @param[in,out] client pointer to mqtt client
* @param[in] params mqtt init params, @see MQTTInitParams
* @return int
*/
static int _mqtt_client_connect_option_init(QcloudIotClient *client, const MQTTInitParams *params)
{
IOT_FUNC_ENTRY;
int rc = 0;
long cur_timesec = 0;
client->options.mqtt_version = MQTT_VERSION_3_1_1;
// Upper limit of keep alive interval is (11.5 * 60) seconds
client->options.client_id = client->device_info->client_id;
client->options.keep_alive_interval =
params->keep_alive_interval_ms / 1000 > 690 ? 690 : params->keep_alive_interval_ms / 1000;
client->options.clean_session = params->clean_session;
// calculate user name & password
client->options.username = (char *)HAL_Malloc(MAX_MQTT_CONNECT_USR_NAME_LEN);
if (!client->options.username) {
Log_e("malloc username failed!");
rc = QCLOUD_ERR_MALLOC;
goto error;
}
cur_timesec =
(MAX_ACCESS_EXPIRE_TIMEOUT <= 0) ? 0x7fffffffL : (HAL_Timer_CurrentSec() + MAX_ACCESS_EXPIRE_TIMEOUT / 1000);
get_next_conn_id(client->conn_id);
HAL_Snprintf(client->options.username, MAX_MQTT_CONNECT_USR_NAME_LEN, "%s;%s;%s;%ld", client->options.client_id,
QCLOUD_IOT_DEVICE_SDK_APPID, client->conn_id, cur_timesec);
#if defined(AUTH_WITH_NO_TLS) && defined(AUTH_MODE_KEY)
char sign[41] = {0};
client->options.password = (char *)HAL_Malloc(MAX_MQTT_CONNECT_PASSWORD_LEN);
if (!client->options.password) {
Log_e("malloc password failed!");
rc = QCLOUD_ERR_MALLOC;
goto error;
}
utils_hmac_sha1(client->options.username, strlen(client->options.username),
client->device_info->device_secret_decode, client->device_info->device_secret_decode_len, sign);
HAL_Snprintf(client->options.password, MAX_MQTT_CONNECT_PASSWORD_LEN, "%s;hmacsha1", sign);
#endif
IOT_FUNC_EXIT_RC(rc);
error:
HAL_Free(client->options.username);
client->options.username = NULL;
HAL_Free(client->options.password);
client->options.password = NULL;
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Init mqtt client.
*
* @param[in,out] client pointer to mqtt client
* @param[in] params mqtt init params, @see MQTTInitParams
* @return @see IotReturnCode
*
* @note
* 1. init device info.
* 2. init mutex and var
* 3. init list @see _mqtt_client_list_init
* 4. init connect option @see _mqtt_client_connect_option_init
* 5. init network @see _mqtt_client_network_init
*/
static int _qcloud_iot_mqtt_client_init(QcloudIotClient *client, const MQTTInitParams *params)
{
IOT_FUNC_ENTRY;
int rc = 0;
memset(client, 0x0, sizeof(QcloudIotClient));
// set device info
client->device_info = params->device_info;
rc = HAL_Snprintf(client->device_info->client_id, MAX_SIZE_OF_CLIENT_ID, "%s%s", client->device_info->product_id,
client->device_info->device_name);
#ifdef AUTH_MODE_KEY
utils_base64decode(client->device_info->device_secret_decode, MAX_SIZE_OF_DECODE_PSK_LENGTH,
&client->device_info->device_secret_decode_len, (uint8_t *)client->device_info->device_secret,
strlen(client->device_info->device_secret));
#endif
Log_i("SDK_Ver: %s, Product_ID: %s, Device_Name: %s", QCLOUD_IOT_DEVICE_SDK_VERSION,
client->device_info->product_id, client->device_info->device_name);
// command timeout should be in range [MIN_COMMAND_TIMEOUT, MAX_COMMAND_TIMEOUT]
client->command_timeout_ms =
(params->command_timeout < MIN_COMMAND_TIMEOUT)
? MIN_COMMAND_TIMEOUT
: (params->command_timeout > MAX_COMMAND_TIMEOUT ? MAX_COMMAND_TIMEOUT : params->command_timeout);
// packet id, random from [1 - 65536]
client->next_packet_id = _get_random_start_packet_id();
client->write_buf_size = QCLOUD_IOT_MQTT_TX_BUF_LEN;
client->read_buf_size = QCLOUD_IOT_MQTT_RX_BUF_LEN;
client->event_handle = params->event_handle;
client->auto_connect_enable = params->auto_connect_enable;
client->default_subscribe = params->default_subscribe;
client->lock_generic = HAL_MutexCreate();
if (!client->lock_generic) {
goto error;
}
client->lock_write_buf = HAL_MutexCreate();
if (!client->lock_write_buf) {
goto error;
}
rc = _mqtt_client_list_init(client);
if (rc) {
goto error;
}
rc = _mqtt_client_connect_option_init(client, params);
if (rc) {
goto error;
}
_mqtt_client_network_init(client, params);
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
error:
HAL_MutexDestroy(client->lock_generic);
client->lock_generic = NULL;
HAL_MutexDestroy(client->lock_write_buf);
client->lock_write_buf = NULL;
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
/**
* @brief Deinit mqtt client.
*
* @param[in,out] client pointer to mqtt client
*/
static void _qcloud_iot_mqtt_client_deinit(QcloudIotClient *client)
{
HAL_Free(client->options.username);
HAL_Free(client->options.password);
HAL_MutexDestroy(client->lock_generic);
HAL_MutexDestroy(client->lock_write_buf);
qcloud_iot_mqtt_sub_handle_array_clear(client);
qcloud_iot_mqtt_suback_wait_list_clear(client);
utils_list_destroy(client->list_pub_wait_ack);
utils_list_destroy(client->list_sub_wait_ack);
Log_i("release mqtt client resources");
}
/**************************************************************************************
* API
**************************************************************************************/
/**
* @brief Create MQTT client and connect to MQTT server.
*
* @param[in] params MQTT init parameters
* @return a valid MQTT client handle when success, or NULL otherwise
*/
void *IOT_MQTT_Construct(const MQTTInitParams *params)
{
POINTER_SANITY_CHECK(params, NULL);
POINTER_SANITY_CHECK(params->device_info, NULL);
STRING_PTR_SANITY_CHECK(params->device_info->product_id, NULL);
STRING_PTR_SANITY_CHECK(params->device_info->device_name, NULL);
#ifdef AUTH_MODE_CERT
STRING_PTR_SANITY_CHECK(params->device_info->dev_cert_file_name, NULL);
STRING_PTR_SANITY_CHECK(params->device_info->dev_key_file_name, NULL);
#else
STRING_PTR_SANITY_CHECK(params->device_info->device_secret, NULL);
#endif
int rc = 0;
QcloudIotClient *client = NULL;
// create and init MQTTClient
client = (QcloudIotClient *)HAL_Malloc(sizeof(QcloudIotClient));
if (!client) {
Log_e("malloc MQTTClient failed");
return NULL;
}
rc = _qcloud_iot_mqtt_client_init(client, params);
if (rc) {
Log_e("mqtt init failed: %d", rc);
goto exit;
}
if (!params->connect_when_construct) {
return client;
}
rc = qcloud_iot_mqtt_connect(client, FIRST_CONNECT);
if (rc) {
Log_e("mqtt connect with id: %s failed: %d", STRING_PTR_PRINT_SANITY_CHECK(client->conn_id), rc);
goto exit;
}
Log_i("mqtt connect with id: %s success", client->conn_id);
return client;
exit:
_qcloud_iot_mqtt_client_deinit(client);
HAL_Free(client);
return NULL;
}
/**
* @brief Connect Mqtt server if not connect.
*
* @param[in,out] client pointer to mqtt client pointer, should using the pointer of IOT_MQTT_Construct return.
* @return @see IotReturnCode
*/
int IOT_MQTT_Connect(void *client)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
if (!get_client_conn_state(client)) {
IOT_FUNC_EXIT_RC(qcloud_iot_mqtt_connect(client, FIRST_CONNECT));
}
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Close connection and destroy MQTT client.
*
* @param[in,out] client pointer to mqtt client pointer, should using the pointer of IOT_MQTT_Construct return.
* @return @see IotReturnCode
*/
int IOT_MQTT_Destroy(void **client)
{
POINTER_SANITY_CHECK(*client, QCLOUD_ERR_INVAL);
QcloudIotClient *mqtt_client = (QcloudIotClient *)(*client);
int rc = qcloud_iot_mqtt_disconnect(mqtt_client);
if (rc) {
// disconnect network stack by force
mqtt_client->network_stack.disconnect(&(mqtt_client->network_stack));
set_client_conn_state(mqtt_client, NOTCONNECTED);
}
_qcloud_iot_mqtt_client_deinit(mqtt_client);
HAL_Free(*client);
*client = NULL;
Log_i("mqtt release!");
return rc;
}
/**
* @brief Check connection and keep alive state, read/handle MQTT packet in synchronized way.
*
* @param[in,out] client pointer to mqtt client
* @param[in] timeout_ms timeout value (unit: ms) for this operation
* @return QCLOUD_RET_SUCCESS when success, QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT when try reconnecting, others @see
* IotReturnCode
*/
int IOT_MQTT_Yield(void *client, uint32_t timeout_ms)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
return qcloud_iot_mqtt_yield(mqtt_client, timeout_ms);
}
/**
* @brief Publish MQTT message.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_name topic to publish
* @param[in] params @see PublishParams
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_MQTT_Publish(void *client, const char *topic_name, const PublishParams *params)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
STRING_PTR_SANITY_CHECK(topic_name, QCLOUD_ERR_INVAL);
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
if (!get_client_conn_state(client)) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
}
if (strlen(topic_name) > MAX_SIZE_OF_CLOUD_TOPIC) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MAX_TOPIC_LENGTH);
}
if (QOS2 == params->qos) {
Log_e("QoS2 is not supported currently");
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_QOS_NOT_SUPPORT);
}
return qcloud_iot_mqtt_publish(mqtt_client, topic_name, params);
}
/**
* @brief Subscribe MQTT topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter to subscribe
* @param[in] params @see SubscribeParams
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_MQTT_Subscribe(void *client, const char *topic_filter, const SubscribeParams *params)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
if (!get_client_conn_state(client) && !mqtt_client->default_subscribe) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
}
if (strlen(topic_filter) > MAX_SIZE_OF_CLOUD_TOPIC) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MAX_TOPIC_LENGTH);
}
if (QOS2 == params->qos) {
Log_e("QoS2 is not supported currently");
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_QOS_NOT_SUPPORT);
}
return qcloud_iot_mqtt_subscribe(mqtt_client, topic_filter, params);
}
/**
* @brief Unsubscribe MQTT topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter to unsubscribe
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_MQTT_Unsubscribe(void *client, const char *topic_filter)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
if (!get_client_conn_state(client)) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
}
if (strlen(topic_filter) > MAX_SIZE_OF_CLOUD_TOPIC) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MAX_TOPIC_LENGTH);
}
return qcloud_iot_mqtt_unsubscribe(mqtt_client, topic_filter);
}
/**
* @brief Check if MQTT topic has been subscribed or not
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter to subscribe
* @return true already subscribed
* @return false not ready
*/
bool IOT_MQTT_IsSubReady(void *client, const char *topic_filter)
{
POINTER_SANITY_CHECK(client, false);
STRING_PTR_SANITY_CHECK(topic_filter, false);
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
return qcloud_iot_mqtt_is_sub_ready(mqtt_client, topic_filter);
}
/**
* @brief Get user data in subscribe.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter to subscribe
* @return NULL or user data
*/
void *IOT_MQTT_GetSubUsrData(void *client, const char *topic_filter)
{
POINTER_SANITY_CHECK(client, NULL);
STRING_PTR_SANITY_CHECK(topic_filter, NULL);
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
return qcloud_iot_mqtt_get_subscribe_usr_data(mqtt_client, topic_filter);
}
/**
* @brief Subscribe and wait sub ready.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter to subscribe
* @param[in] params @see SubscribeParams
* @return @see IotReturnCode
*/
int IOT_MQTT_SubscribeSync(void *client, const char *topic_filter, const SubscribeParams *params)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(params, QCLOUD_ERR_INVAL);
STRING_PTR_SANITY_CHECK(topic_filter, QCLOUD_ERR_INVAL);
int rc;
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
int cnt_sub = mqtt_client->command_timeout_ms / QCLOUD_IOT_MQTT_YIELD_TIMEOUT;
if (IOT_MQTT_IsSubReady(client, topic_filter)) {
// if already sub, free the user data
if (params->user_data_free) {
params->user_data_free(params->user_data);
}
return QCLOUD_RET_SUCCESS;
}
rc = IOT_MQTT_Subscribe(client, topic_filter, params);
if (rc < 0) {
Log_e("topic subscribe failed: %d, cnt: %d", rc, cnt_sub);
return rc;
}
while (cnt_sub-- >= 0 && rc >= 0 && !IOT_MQTT_IsSubReady(client, topic_filter)) {
/**
* @brief wait for subscription result
*
*/
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
}
return IOT_MQTT_IsSubReady(client, topic_filter) ? QCLOUD_RET_SUCCESS : QCLOUD_ERR_FAILURE;
}
/**
* @brief Check if MQTT is connected.
*
* @param[in,out] client pointer to mqtt client
* @return true connected
* @return false no connected
*/
bool IOT_MQTT_IsConnected(void *client)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
return get_client_conn_state(mqtt_client);
}
/**
* @brief Get device info using to connect mqtt server.
*
* @param[in,out] client pointer to mqtt client
* @return @see DeviceInfo
*/
DeviceInfo *IOT_MQTT_GetDeviceInfo(void *client)
{
POINTER_SANITY_CHECK(client, NULL);
QcloudIotClient *mqtt_client = (QcloudIotClient *)client;
return mqtt_client->device_info;
}

View File

@@ -0,0 +1,130 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client_common.c
* @brief common api
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-28
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#include "mqtt_client.h"
/**
* @brief Get the next packet id object.
*
* @param[in,out] client pointer to mqtt client
* @return packet id
*/
uint16_t get_next_packet_id(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
HAL_MutexLock(client->lock_generic);
client->next_packet_id = (uint16_t)((MAX_PACKET_ID == client->next_packet_id) ? 1 : (client->next_packet_id + 1));
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT_RC(client->next_packet_id);
}
/**
* @brief Get the next conn id object.
*
* @param[out] conn_id buffer to save conn id
*/
void get_next_conn_id(char *conn_id)
{
int i;
srand(HAL_Timer_CurrentSec());
for (i = 0; i < MAX_CONN_ID_LEN - 1; i++) {
int flag = rand() % 3;
switch (flag) {
case 0:
conn_id[i] = (rand() % 26) + 'a';
break;
case 1:
conn_id[i] = (rand() % 26) + 'A';
break;
case 2:
conn_id[i] = (rand() % 10) + '0';
break;
}
}
conn_id[MAX_CONN_ID_LEN - 1] = '\0';
}
/**
* @brief Set the client conn state object.
*
* @param[in,out] client pointer to mqtt client
* @param[in] connected connect status, @see ConnStatus
*/
void set_client_conn_state(QcloudIotClient *client, uint8_t connected)
{
HAL_MutexLock(client->lock_generic);
client->is_connected = connected;
HAL_MutexUnlock(client->lock_generic);
}
/**
* @brief Get the client conn state object.
*
* @param[in,out] client
* @return @see ConnStatus
*/
uint8_t get_client_conn_state(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
uint8_t is_connected = 0;
HAL_MutexLock(client->lock_generic);
is_connected = client->is_connected;
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT_RC(is_connected);
}
/**
* @brief Send mqtt packet, timeout = command_timeout_ms.
*
* @param[in,out] client pointer to mqtt client
* @param[in] length length of data to be sent, data is saved in client write_buf
* @return @see IotReturnCode
*/
int send_mqtt_packet(QcloudIotClient *client, size_t length)
{
IOT_FUNC_ENTRY;
int rc = QCLOUD_RET_SUCCESS;
size_t sent_len = 0;
if (length >= client->write_buf_size) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_BUF_TOO_SHORT);
}
rc = client->network_stack.write(&(client->network_stack), client->write_buf, length, client->command_timeout_ms,
&sent_len);
rc = QCLOUD_ERR_TCP_WRITE_TIMEOUT == rc ? QCLOUD_ERR_MQTT_REQUEST_TIMEOUT : rc;
IOT_FUNC_EXIT_RC(rc);
}

View File

@@ -0,0 +1,279 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client_connect.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-28
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#include "mqtt_client.h"
/**
* @brief switch to domain mode
*
*/
typedef enum {
HOST_DOMAIN = 1,
BACKUP_DOMAIN = 0,
} DomainMode;
/**
* @brief Serialize and send connect packet.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
static int _mqtt_connect(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
uint8_t session_present, connack_rc;
int rc, packet_len;
// TCP or TLS network connect
rc = client->network_stack.connect(&(client->network_stack));
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
// send MQTT CONNECT packet
HAL_MutexLock(client->lock_write_buf);
packet_len = mqtt_connect_packet_serialize(client->write_buf, client->write_buf_size, &client->options);
if (packet_len > 0) {
rc = send_mqtt_packet(client, packet_len);
} else {
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
}
HAL_MutexUnlock(client->lock_write_buf);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
// recv MQTT CONNACK packet
rc = qcloud_iot_mqtt_wait_for_read(client, CONNACK);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
rc = mqtt_connack_packet_deserialize(client->read_buf, client->read_buf_size, &session_present, &connack_rc);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
if (CONNACK_CONNECTION_ACCEPTED != connack_rc) {
IOT_FUNC_EXIT_RC(connack_rc);
}
// set connect state
set_client_conn_state(client, CONNECTED);
HAL_MutexLock(client->lock_generic);
client->was_manually_disconnected = client->is_ping_outstanding = 0;
HAL_Timer_Countdown(&client->ping_timer, client->options.keep_alive_interval);
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief switch domain
*
* @param[in,out] client pointer to mqtt client
* @param[in] mode @see DomainMode
* @return @see IotReturnCode
*/
static void _mqtt_switch_domain(QcloudIotClient *client, DomainMode mode)
{
if (mode == BACKUP_DOMAIN && client->backup_host) {
HAL_Snprintf(client->host_addr, HOST_STR_LENGTH, "%s.%s", client->device_info->product_id, client->backup_host);
client->network_stack.host = client->host_addr;
} else if (mode == HOST_DOMAIN) {
HAL_Snprintf(client->host_addr, HOST_STR_LENGTH, "%s.%s", client->device_info->product_id, client->main_host);
client->network_stack.host = client->host_addr;
}
}
/**
* @brief set connect host
*
* @param[in,out] client pointer to mqtt client
* @param[in] srv_ip server ip string
* @return @see IotReturnCode
*/
static int _mqtt_set_srv_ip(QcloudIotClient *client, char *srv_ip)
{
int size = HAL_Snprintf(client->host_addr, HOST_STR_LENGTH, "%s", srv_ip);
if (size < 0 || size > HOST_STR_LENGTH - 1) {
Log_e("gen host name failed: %d", size);
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
} else {
client->network_stack.host = client->host_addr;
Log_i("using HOST IP : %s", srv_ip);
}
return QCLOUD_RET_SUCCESS;
}
/**
* @brief Connect MQTT server.
*
* @param[in,out] client pointer to mqtt client
* @param[in] mode when connect error. Choose different connection strategies according to the mode
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_connect(QcloudIotClient *client, ConnMode mode)
{
IOT_FUNC_ENTRY;
int rc = 0;
// check connection state first
if (get_client_conn_state(client)) {
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_ALREADY_CONNECTED);
}
int connect_fail_cnt = 0;
int max_connect_fail_switch_cnt = (mode == FIRST_CONNECT) ? 3 : 1;
while ((rc = _mqtt_connect(client))) {
client->network_stack.disconnect(&(client->network_stack));
connect_fail_cnt++;
if (connect_fail_cnt == max_connect_fail_switch_cnt && client->backup_host) {
_mqtt_switch_domain(client, BACKUP_DOMAIN);
continue;
}
if (connect_fail_cnt > max_connect_fail_switch_cnt) {
if (!client->get_next_host_ip) {
break;
}
char *srv_ip = client->get_next_host_ip();
if (!srv_ip) {
break;
}
_mqtt_set_srv_ip(client, srv_ip);
}
}
// In reconnection mode, the connection is successfully switched back to the primary domain name
if (!rc && connect_fail_cnt && mode == RECONNECT) {
_mqtt_switch_domain(client, HOST_DOMAIN);
}
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Reconnect MQTT server.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_attempt_reconnect(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int rc = 0;
Log_i("attempt to reconnect...");
if (get_client_conn_state(client)) {
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_ALREADY_CONNECTED);
}
rc = qcloud_iot_mqtt_connect(client, RECONNECT);
if (!get_client_conn_state(client)) {
IOT_FUNC_EXIT_RC(rc);
}
#if 0 // clean session is 0, user don't need resubscribe only if mqtt server error.
if (!client->options.clean_session) {
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_RECONNECTED);
}
#endif
rc = qcloud_iot_mqtt_resubscribe(client);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_RECONNECTED);
}
/**
* @brief Disconnect MQTT server.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_disconnect(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int rc, packet_len = 0;
if (!get_client_conn_state(client)) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
}
HAL_MutexLock(client->lock_write_buf);
packet_len = mqtt_disconnect_packet_serialize(client->write_buf, client->write_buf_size);
if (packet_len > 0) {
rc = send_mqtt_packet(client, packet_len);
} else {
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
}
HAL_MutexUnlock(client->lock_write_buf);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
client->network_stack.disconnect(&(client->network_stack));
set_client_conn_state(client, NOTCONNECTED);
client->was_manually_disconnected = 1;
Log_i("mqtt disconnect!");
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Serialize and send pingreq packet.
*
* @param[in,out] client pointer to mqtt client
* @param[in] try_times if failed, retry times
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_pingreq(QcloudIotClient *client, int try_times)
{
IOT_FUNC_ENTRY;
int rc, packet_len, i = 0;
HAL_MutexLock(client->lock_write_buf);
packet_len = mqtt_pingreq_packet_serialize(client->write_buf, client->write_buf_size);
if (packet_len > 0) {
do {
rc = send_mqtt_packet(client, packet_len);
} while (rc && (i++ < try_times));
} else {
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
}
HAL_MutexUnlock(client->lock_write_buf);
IOT_FUNC_EXIT_RC(rc);
}

View File

@@ -0,0 +1,397 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client_publish.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-28
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#include "mqtt_client.h"
/**
* @brief Push pub info to list for republish.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] packet_len packet len of publish packet
* @param[in] packet_id packet id
* @param[out] node list node
* @return @see IotReturnCode
*/
static int _push_pub_info_to_list(QcloudIotClient *client, int packet_len, uint16_t packet_id, void **node)
{
IOT_FUNC_ENTRY;
void * list = client->list_pub_wait_ack;
QcloudIotPubInfo *repub_info = NULL;
// construct republish info
repub_info = (QcloudIotPubInfo *)HAL_Malloc(sizeof(QcloudIotPubInfo) + packet_len);
if (!repub_info) {
Log_e("memory malloc failed!");
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
repub_info->buf = (uint8_t *)repub_info + sizeof(QcloudIotPubInfo);
repub_info->len = packet_len;
repub_info->packet_id = packet_id;
memcpy(repub_info->buf, client->write_buf, packet_len); // save the whole packet
HAL_Timer_CountdownMs(&repub_info->pub_start_time, client->command_timeout_ms);
// push republish info to list
*node = utils_list_push(list, repub_info);
if (!*node) {
HAL_Free(repub_info);
Log_e("list push failed! Check the list len!");
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Check pub wait list timeout.
*
* @param[in,out] list pointer to pub wait ack list.
* @param[in] node pointer to list node
* @param[in] val pointer to value, @see QcloudIotPubInfo
* @param[in] usr_data @see QcloudIotClient
* @return @see UtilsListResult
*/
static UtilsListResult _pub_wait_list_process_check_timeout(void *list, void *node, void *val, void *usr_data)
{
IOT_FUNC_ENTRY;
MQTTEventMsg msg;
QcloudIotPubInfo *repub_info = (QcloudIotPubInfo *)val;
QcloudIotClient * client = (QcloudIotClient *)usr_data;
// check the request if timeout or not
if (HAL_Timer_Remain(&repub_info->pub_start_time) > 0) {
IOT_FUNC_EXIT_RC(LIST_TRAVERSE_CONTINUE);
}
// notify timeout event
if (client->event_handle.h_fp) {
msg.event_type = MQTT_EVENT_PUBLISH_TIMEOUT;
msg.msg = (void *)(uintptr_t)repub_info->packet_id;
client->event_handle.h_fp(client, client->event_handle.context, &msg);
}
utils_list_remove(list, node);
IOT_FUNC_EXIT_RC(LIST_TRAVERSE_CONTINUE);
}
/**
* @brief Remove info from pub wait list.
*
* @param[in,out] list pointer to pub wait ack list.
* @param[in] node pointer to list node
* @param[in] val pointer to value, @see QcloudIotPubInfo
* @param[in] usr_data pointer to packet id
* @return @see UtilsListResult
*/
static UtilsListResult _pub_wait_list_process_remove_info(void *list, void *node, void *val, void *usr_data)
{
IOT_FUNC_ENTRY;
QcloudIotPubInfo *repub_info = (QcloudIotPubInfo *)val;
if (repub_info->packet_id == *((uint16_t *)usr_data)) {
utils_list_remove(list, node);
IOT_FUNC_EXIT_RC(LIST_TRAVERSE_BREAK);
}
IOT_FUNC_EXIT_RC(LIST_TRAVERSE_CONTINUE);
}
/**
* @brief Remove node signed with packet id from publish ACK wait list.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] packet_id packet id
*/
static void _remove_pub_info_from_list(QcloudIotClient *client, uint16_t packet_id)
{
utils_list_process(client->list_pub_wait_ack, LIST_HEAD, _pub_wait_list_process_remove_info, &packet_id);
}
/**
* @brief Check if topic match
*
* @param[in] topic_filter topic name filter, wildcard is supported
* @param[in] topic_name topic name, no wildcard
* @param[in] topic_name_len length of topic name
* @return 1 for matched, 0 for no matched
*
* @note assume topic filter and name is in correct format;
* # can only be at end;
* + and # can only be next to separator.
*/
static uint8_t _is_topic_matched(char *topic_filter, const char *topic_name, uint16_t topic_name_len)
{
char *curf = topic_filter;
char *curn = (char *)topic_name;
char *curn_end = curn + topic_name_len;
while (*curf && curn < curn_end) {
if (*curn == '/' && *curf != '/')
break;
if (*curf != '+' && *curf != '#' && *curf != *curn)
break;
if (*curf == '+') { // skip until we meet the next separator, or end of string
char *nextpos = curn + 1;
while (nextpos < curn_end && *nextpos != '/') nextpos = ++curn + 1;
} else if (*curf == '#')
curn = curn_end - 1; // skip until end of string
curf++;
curn++;
};
return (curn == curn_end) && (*curf == '\0');
}
/**
* @brief deliver the message to user callback
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message to deliver, @see MQTTMessage
*/
static void _deliver_message(QcloudIotClient *client, MQTTMessage *message)
{
int i;
MQTTEventMsg msg;
HAL_MutexLock(client->lock_generic);
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
if (client->sub_handles[i].topic_filter &&
_is_topic_matched((char *)client->sub_handles[i].topic_filter, message->topic_name, message->topic_len)) {
if (client->sub_handles[i].params.on_message_handler) {
HAL_MutexUnlock(client->lock_generic);
// if found, then handle it, then return
client->sub_handles[i].params.on_message_handler(client, message,
client->sub_handles[i].params.user_data);
return;
}
}
}
HAL_MutexUnlock(client->lock_generic);
/* Message handler not found for topic */
/* May be we do not care change FAILURE use SUCCESS*/
Log_d("no matching any topic, call default handle function");
if (client->event_handle.h_fp) {
msg.event_type = MQTT_EVENT_PUBLISH_RECEIVED;
msg.msg = message;
client->event_handle.h_fp(client, client->event_handle.context, &msg);
}
}
#ifdef MQTT_RMDUP_MSG_ENABLED
/**
* @brief Check if packet id repeat.
*
* @param[in,out] client pointer to mqtt client
* @param[in] packet_id packet_id
* @return < 0 for failed.
*/
static int _get_packet_id_repeat_buf(QcloudIotClient *client, uint16_t packet_id)
{
int i;
for (i = 0; i < MQTT_MAX_REPEAT_BUF_LEN; ++i) {
if (packet_id == client->repeat_packet_id_buf[i]) {
return packet_id;
}
}
return -1;
}
/**
* @brief Add packet id, if buf full, then overwrite.
*
* @param[in,out] client pointer to mqtt client
* @param[in] packet_id packet_id
*/
static void _add_packet_id_to_repeat_buf(QcloudIotClient *client, uint16_t packet_id)
{
if (_get_packet_id_repeat_buf(client, packet_id) >= 0) {
return;
}
client->repeat_packet_id_buf[client->current_packet_id_cnt++] = packet_id;
client->current_packet_id_cnt %= MQTT_MAX_REPEAT_BUF_LEN;
}
#endif
/**
* @brief Serialize and send publish packet.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] topic_name topic to publish
* @param[in] params publish params
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
*/
int qcloud_iot_mqtt_publish(QcloudIotClient *client, const char *topic_name, const PublishParams *params)
{
IOT_FUNC_ENTRY;
int rc, packet_len;
MQTTPublishFlags flags;
void * node = NULL;
uint16_t packet_id = 0;
if (params->qos > QOS0) {
packet_id = get_next_packet_id(client);
}
Log_d("publish qos=%d|packet_id=%d|topic_name=%s|payload=%s", params->qos, packet_id, topic_name,
STRING_PTR_PRINT_SANITY_CHECK((char *)params->payload));
flags.dup = params->dup;
flags.qos = params->qos;
flags.retain = params->retain;
// serialize packet
HAL_MutexLock(client->lock_write_buf);
packet_len = mqtt_publish_packet_serialize(client->write_buf, client->write_buf_size, &flags, packet_id, topic_name,
params->payload, params->payload_len);
if (packet_len < 0) {
HAL_MutexUnlock(client->lock_write_buf);
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
IOT_FUNC_EXIT_RC(rc);
}
if (params->qos > QOS0) {
rc = _push_pub_info_to_list(client, packet_len, packet_id, &node);
if (rc) {
Log_e("push publish info failed!");
HAL_MutexUnlock(client->lock_write_buf);
IOT_FUNC_EXIT_RC(rc);
}
}
// send the publish packet
rc = send_mqtt_packet(client, packet_len);
HAL_MutexUnlock(client->lock_write_buf);
if (rc) {
if (params->qos > QOS0) {
utils_list_remove(client->list_pub_wait_ack, node);
}
IOT_FUNC_EXIT_RC(rc);
}
IOT_FUNC_EXIT_RC(packet_id);
}
/**
* @brief Deserialize publish packet and deliver_message.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] params publish params
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
*/
int qcloud_iot_mqtt_handle_publish(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int rc, packet_len = 0;
MQTTPublishFlags flags;
MQTTMessage msg;
rc = mqtt_publish_packet_deserialize(client->read_buf, client->read_buf_size, &flags, &msg.packet_id,
&msg.topic_name, &msg.topic_len, &msg.payload, &msg.payload_len);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
msg.qos = flags.qos;
if (QOS0 == msg.qos) {
// No further processing required for QOS0
_deliver_message(client, &msg);
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
// only Qos 1 is support
#ifdef MQTT_RMDUP_MSG_ENABLED
// check if packet_id has been received before
if (_get_packet_id_repeat_buf(client, msg.packet_id) < 0)
#endif
{
// deliver to msg callback
_deliver_message(client, &msg);
}
#ifdef MQTT_RMDUP_MSG_ENABLED
// just add packet id
_add_packet_id_to_repeat_buf(client, msg.packet_id);
#endif
// reply with puback
HAL_MutexLock(client->lock_write_buf);
packet_len = mqtt_puback_packet_serialize(client->write_buf, client->write_buf_size, msg.packet_id);
if (packet_len > 0) {
rc = send_mqtt_packet(client, packet_len);
} else {
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
}
HAL_MutexUnlock(client->lock_write_buf);
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Deserialize puback packet.
*
* @param[in,out] client pointer to mqtt_client
* @return 0 for success.
*/
int qcloud_iot_mqtt_handle_puback(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int rc;
uint16_t packet_id;
MQTTEventMsg msg;
rc = mqtt_puback_packet_deserialize(client->read_buf, client->read_buf_size, &packet_id);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
_remove_pub_info_from_list(client, packet_id);
/* notify this event to user callback */
if (client->event_handle.h_fp) {
msg.event_type = MQTT_EVENT_PUBLISH_SUCCESS;
msg.msg = (void *)(uintptr_t)packet_id;
client->event_handle.h_fp(client, client->event_handle.context, &msg);
}
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Process puback waiting timout.
*
* @param[in,out] client pointer to mqtt_client
*/
void qcloud_iot_mqtt_check_pub_timeout(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
utils_list_process(client->list_pub_wait_ack, LIST_HEAD, _pub_wait_list_process_check_timeout, client);
IOT_FUNC_EXIT;
}

View File

@@ -0,0 +1,643 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client_subscribe.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-28
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#include "mqtt_client.h"
/**
* @brief Context of subscribe
*
*/
typedef struct {
uint16_t packet_id;
SubTopicHandle handler;
} QcloudIotSubConext;
/**
* @brief Free topic_filter and user_data
*
* @param[in] handler subtopic handle
*/
static void _clear_sub_handle(SubTopicHandle *handler)
{
if (handler->topic_filter) {
HAL_Free(handler->topic_filter);
handler->topic_filter = NULL;
}
if (handler->params.user_data && handler->params.user_data_free) {
handler->params.user_data_free(handler->params.user_data);
handler->params.user_data = NULL;
}
}
/**
* @brief Push node to subscribe(unsubscribe) ACK wait list.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] packet_len packet len of publish packet
* @param[in] packet_id packet id
* @param[in] type mqtt packet type SUBSCRIBE or UNSUBSCRIBE
* @param[in] handler subtopic handle
* @param[out] node node to push to list
* @return @see IotReturnCode
*/
static int _push_sub_info_to_list(QcloudIotClient *client, int packet_len, uint16_t packet_id, MQTTPacketType type,
const SubTopicHandle *handler, void **node)
{
IOT_FUNC_ENTRY;
void *list = client->list_sub_wait_ack;
QcloudIotSubInfo *sub_info = NULL;
sub_info = (QcloudIotSubInfo *)HAL_Malloc(sizeof(QcloudIotSubInfo) + packet_len);
if (!sub_info) {
Log_e("memory malloc failed!");
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
sub_info->buf = (uint8_t *)sub_info + sizeof(QcloudIotSubInfo);
sub_info->len = packet_len;
sub_info->type = type;
sub_info->packet_id = packet_id;
sub_info->handler = *handler;
memcpy(sub_info->buf, client->write_buf, packet_len);
HAL_Timer_CountdownMs(&sub_info->sub_start_time, client->command_timeout_ms);
*node = utils_list_push(list, sub_info);
if (!*node) {
HAL_Free(sub_info);
Log_e("list push failed! Check the list len!");
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Check pub wait list timeout.
*
* @param[in,out] list pointer to pub wait ack list.
* @param[in] node pointer to list node
* @param[in] val pointer to value, @see QcloudIotPubInfo
* @param[in] usr_data @see QcloudIotClient
* @return @see UtilsListResult
*/
static UtilsListResult _sub_wait_list_process_pop_info(void *list, void *node, void *val, void *usr_data)
{
QcloudIotSubConext *sub_context = (QcloudIotSubConext *)usr_data;
QcloudIotSubInfo *sub_info = (QcloudIotSubInfo *)val;
if (sub_info->packet_id == sub_context->packet_id) {
memcpy(&sub_context->handler, &sub_info->handler, sizeof(SubTopicHandle));
utils_list_remove(list, node);
return LIST_TRAVERSE_BREAK;
}
return LIST_TRAVERSE_CONTINUE;
}
/**
* @brief Check sub wait list timeout.
*
* @param[in,out] list pointer to sub wait ack list.
* @param[in] node pointer to list node
* @param[in] val pointer to value, @see QcloudIotSubInfo
* @param[in] usr_data @see QcloudIotClient
* @return @see UtilsListResult
*/
static UtilsListResult _sub_wait_list_process_check_timeout(void *list, void *node, void *val, void *usr_data)
{
MQTTEventMsg msg;
QcloudIotSubInfo *sub_info = (QcloudIotSubInfo *)val;
QcloudIotClient *client = (QcloudIotClient *)usr_data;
// check the request if timeout or not
if (HAL_Timer_Remain(&sub_info->sub_start_time) > 0) {
return LIST_TRAVERSE_CONTINUE;
}
// notify timeout event
if (client->event_handle.h_fp) {
msg.event_type = SUBSCRIBE == sub_info->type ? MQTT_EVENT_SUBSCRIBE_TIMEOUT : MQTT_EVENT_UNSUBSCRIBE_TIMEOUT;
msg.msg = (void *)(uintptr_t)sub_info->packet_id;
if (sub_info->handler.params.on_sub_event_handler) {
sub_info->handler.params.on_sub_event_handler(client, MQTT_EVENT_SUBSCRIBE_TIMEOUT,
sub_info->handler.params.user_data);
}
client->event_handle.h_fp(client, client->event_handle.context, &msg);
}
_clear_sub_handle(&sub_info->handler);
utils_list_remove(list, node);
return LIST_TRAVERSE_CONTINUE;
}
/**
* @brief Clear sub wait list.
*
* @param[in,out] list pointer to sub wait list.
* @param[in] node pointer to list node
* @param[in] val pointer to value, @see QcloudIotSubInfo
* @param[in] usr_data null
* @return @see UtilsListResult
*/
static UtilsListResult _sub_wait_list_process_clear(void *list, void *node, void *val, void *usr_data)
{
QcloudIotSubInfo *sub_info = (QcloudIotSubInfo *)val;
if (sub_info->type == UNSUBSCRIBE) {
_clear_sub_handle(&sub_info->handler);
}
utils_list_remove(list, node);
return LIST_TRAVERSE_CONTINUE;
}
/**
* @brief Pop node signed with packet id from subscribe ACK wait list, and return the sub handler
*
* @param[in,out] client pointer to mqtt_client
* @param[in] packet_id packet id
* @param[out] sub_handle @see SubTopicHandle
*/
static void _pop_sub_info_from_list(QcloudIotClient *client, uint16_t packet_id, SubTopicHandle *sub_handle)
{
QcloudIotSubConext sub_context = {.packet_id = packet_id};
utils_list_process(client->list_sub_wait_ack, LIST_HEAD, _sub_wait_list_process_pop_info, &sub_context);
memcpy(sub_handle, &sub_context.handler, sizeof(SubTopicHandle));
}
/**
* @brief Remove sub handle when unsubscribe.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] topic_filter topic to remove
* @return true topic exist
* @return false topic no exist
*/
static bool _remove_sub_handle_from_array(QcloudIotClient *client, const char *topic_filter)
{
int i;
bool topic_exists = false;
// remove from message handler array
HAL_MutexLock(client->lock_generic);
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
// notify this event to topic subscriber
if (client->sub_handles[i].params.on_sub_event_handler) {
client->sub_handles[i].params.on_sub_event_handler(client, MQTT_EVENT_UNSUBSCRIBE,
client->sub_handles[i].params.user_data);
}
_clear_sub_handle(&client->sub_handles[i]);
// we don't want to break here, if the same topic is registered*with 2 callbacks.Unlikely scenario
topic_exists = true;
}
}
HAL_MutexUnlock(client->lock_generic);
return topic_exists;
}
/**
* @brief Add sub handle when subscribe.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] sub_handle sub_handle to be add to array
* @return true topic exist
* @return false topic no exist
*/
static int _add_sub_handle_to_array(QcloudIotClient *client, const SubTopicHandle *sub_handle)
{
IOT_FUNC_ENTRY;
int i, i_free = -1;
HAL_MutexLock(client->lock_generic);
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
if (client->sub_handles[i].topic_filter) {
if (!strcmp(client->sub_handles[i].topic_filter, sub_handle->topic_filter)) {
i_free = i;
// free the memory before
_clear_sub_handle(&client->sub_handles[i]);
Log_w("Identical topic found: %s", sub_handle->topic_filter);
break;
}
} else {
i_free = i_free == -1 ? i : i_free;
}
}
if (-1 == i_free) {
Log_e("NO more @sub_handles space!");
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
client->sub_handles[i_free] = *sub_handle;
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Set sub handle status.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] topic_filter topic to set status
* @param[in] status @see SubStatus
*/
static void _set_sub_handle_status_to_array(QcloudIotClient *client, const char *topic_filter, SubStatus status)
{
IOT_FUNC_ENTRY;
int i;
HAL_MutexLock(client->lock_generic);
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
if (client->sub_handles[i].topic_filter) {
if (!strcmp(client->sub_handles[i].topic_filter, topic_filter)) {
client->sub_handles[i].status = status;
break;
}
}
}
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT;
}
/**
* @brief Serialize and send subscribe packet.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic to subscribe
* @param[in] params subscribe params
* @return >=0 for packet id, < 0 for failed @see IotReturnCode
*/
int qcloud_iot_mqtt_subscribe(QcloudIotClient *client, const char *topic_filter, const SubscribeParams *params)
{
IOT_FUNC_ENTRY;
int rc, packet_len, qos = params->qos;
uint16_t packet_id;
char *topic_filter_stored;
void *node = NULL;
SubTopicHandle sub_handle;
// topic filter should be valid in the whole sub life
topic_filter_stored = HAL_Malloc(strlen(topic_filter) + 1);
if (!topic_filter_stored) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MALLOC);
}
strncpy(topic_filter_stored, topic_filter, strlen(topic_filter) + 1);
sub_handle.topic_filter = topic_filter_stored;
sub_handle.params = *params;
sub_handle.status = client->default_subscribe ? SUB_ACK_RECEIVED : SUB_ACK_NOT_RECEIVED;
// add sub handle first to process
rc = _add_sub_handle_to_array(client, &sub_handle);
if (rc) {
goto exit;
}
if (client->default_subscribe) {
return 0;
}
packet_id = get_next_packet_id(client);
Log_d("subscribe topic_name=%s|packet_id=%d", topic_filter_stored, packet_id);
// serialize packet
HAL_MutexLock(client->lock_write_buf);
packet_len = mqtt_subscribe_packet_serialize(client->write_buf, client->write_buf_size, packet_id, 1,
&topic_filter_stored, &qos);
if (packet_len < 0) {
HAL_MutexUnlock(client->lock_write_buf);
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
goto exit;
}
// add node into sub ack wait list
rc = _push_sub_info_to_list(client, packet_len, packet_id, SUBSCRIBE, &sub_handle, &node);
if (rc) {
HAL_MutexUnlock(client->lock_write_buf);
goto exit;
}
// send packet
rc = send_mqtt_packet(client, packet_len);
HAL_MutexUnlock(client->lock_write_buf);
if (rc) {
utils_list_remove(client->list_sub_wait_ack, node);
goto exit;
}
IOT_FUNC_EXIT_RC(packet_id);
exit:
_remove_sub_handle_from_array(client, topic_filter_stored);
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Deserialize suback packet and return sub result.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_handle_suback(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int rc, count = 0;
uint16_t packet_id = 0;
int granted_qos;
MQTTEventMsg msg = {MQTT_EVENT_SUBSCRIBE_SUCCESS, NULL};
SubTopicHandle sub_handle = {0};
MQTTEventType event_type = MQTT_EVENT_SUBSCRIBE_SUCCESS;
rc = mqtt_suback_packet_deserialize(client->read_buf, client->read_buf_size, 1, &count, &packet_id, &granted_qos);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
msg.msg = (void *)(uintptr_t)packet_id;
// pop sub info and get sub handle
_pop_sub_info_from_list(client, packet_id, &sub_handle);
if (!sub_handle.topic_filter) {
Log_w("can't get sub handle from list!");
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS); // in case of resubscribe when reconnect
}
// check return code in SUBACK packet: 0x00(QOS0, SUCCESS),0x01(QOS1, SUCCESS),0x02(QOS2,SUCCESS),0x80(Failure)
if (0x80 == granted_qos) {
msg.event_type = MQTT_EVENT_SUBSCRIBE_NACK;
event_type = MQTT_EVENT_SUBSCRIBE_NACK;
Log_e("MQTT SUBSCRIBE failed, packet_id: %u topic: %s", packet_id, sub_handle.topic_filter);
_remove_sub_handle_from_array(client, sub_handle.topic_filter);
rc = QCLOUD_ERR_MQTT_SUB;
}
if (MQTT_EVENT_SUBSCRIBE_SUCCESS == msg.event_type) {
_set_sub_handle_status_to_array(client, sub_handle.topic_filter, SUB_ACK_RECEIVED);
}
// notify this event to user callback
if (client->event_handle.h_fp) {
client->event_handle.h_fp(client, client->event_handle.context, &msg);
}
// notify this event to topic subscriber
if (sub_handle.params.on_sub_event_handler) {
sub_handle.params.on_sub_event_handler(client, event_type, sub_handle.params.user_data);
}
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Serialize and send unsubscribe packet.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic to unsubscribe
* @return >=0 packet id, < 0 for failed @see IotReturnCode
*/
int qcloud_iot_mqtt_unsubscribe(QcloudIotClient *client, const char *topic_filter)
{
IOT_FUNC_ENTRY;
int rc, packet_len;
uint16_t packet_id;
void *node = NULL;
SubTopicHandle sub_handle;
memset(&sub_handle, 0, sizeof(SubTopicHandle));
// remove from sub handle
if (!_remove_sub_handle_from_array(client, topic_filter)) {
Log_w("subscription does not exists: %s", topic_filter);
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_UNSUB_FAIL);
}
// topic filter should be valid in the whole sub life
char *topic_filter_stored = HAL_Malloc(strlen(topic_filter) + 1);
if (!topic_filter_stored) {
Log_e("malloc failed");
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
strncpy(topic_filter_stored, topic_filter, strlen(topic_filter) + 1);
sub_handle.topic_filter = topic_filter_stored;
packet_id = get_next_packet_id(client);
Log_d("unsubscribe topic_name=%s|packet_id=%d", topic_filter_stored, packet_id);
HAL_MutexLock(client->lock_write_buf);
packet_len = mqtt_unsubscribe_packet_serialize(client->write_buf, client->write_buf_size, packet_id, 1,
&topic_filter_stored);
if (packet_len < 0) {
HAL_MutexUnlock(client->lock_write_buf);
rc = packet_len == MQTT_ERR_SHORT_BUFFER ? QCLOUD_ERR_BUF_TOO_SHORT : QCLOUD_ERR_FAILURE;
goto exit;
}
// add node into sub ack wait list
rc = _push_sub_info_to_list(client, packet_len, packet_id, UNSUBSCRIBE, &sub_handle, &node);
if (rc) {
Log_e("push unsubscribe info failed!");
HAL_MutexUnlock(client->lock_write_buf);
goto exit;
}
/* send the unsubscribe packet */
rc = send_mqtt_packet(client, packet_len);
HAL_MutexUnlock(client->lock_write_buf);
if (rc) {
utils_list_remove(client->list_sub_wait_ack, node);
goto exit;
}
IOT_FUNC_EXIT_RC(packet_id);
exit:
_clear_sub_handle(&sub_handle);
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Deserialize unsuback packet and remove from list.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_handle_unsuback(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int rc;
uint16_t packet_id = 0;
SubTopicHandle sub_handle = {0};
MQTTEventMsg msg;
rc = mqtt_unsuback_packet_deserialize(client->read_buf, client->read_buf_size, &packet_id);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
_pop_sub_info_from_list(client, packet_id, &sub_handle);
_clear_sub_handle(&sub_handle);
if (client->event_handle.h_fp) {
msg.event_type = MQTT_EVENT_UNSUBSCRIBE_SUCCESS;
msg.msg = (void *)(uintptr_t)packet_id;
client->event_handle.h_fp(client, client->event_handle.context, &msg);
}
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Process suback waiting timeout.
*
* @param[in,out] client pointer to mqtt client
*/
void qcloud_iot_mqtt_check_sub_timeout(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
utils_list_process(client->list_sub_wait_ack, LIST_HEAD, _sub_wait_list_process_check_timeout, client);
IOT_FUNC_EXIT;
}
/**
* @brief Resubscribe topic when reconnect.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_resubscribe(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int rc, packet_len, qos, itr = 0;
uint16_t packet_id;
SubTopicHandle *sub_handle;
for (itr = 0; itr < MAX_MESSAGE_HANDLERS; itr++) {
sub_handle = &client->sub_handles[itr];
if (!sub_handle->topic_filter) {
continue;
}
packet_id = get_next_packet_id(client);
Log_d("subscribe topic_name=%s|packet_id=%d", sub_handle->topic_filter, packet_id);
HAL_MutexLock(client->lock_write_buf);
qos = sub_handle->params.qos;
packet_len = mqtt_subscribe_packet_serialize(client->write_buf, client->write_buf_size, packet_id, 1,
&sub_handle->topic_filter, &qos);
if (packet_len < 0) {
HAL_MutexUnlock(client->lock_write_buf);
continue;
}
// send packet
rc = send_mqtt_packet(client, packet_len);
HAL_MutexUnlock(client->lock_write_buf);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
}
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Return if topic is sub ready.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter
* @return true for ready
* @return false for not ready
*/
bool qcloud_iot_mqtt_is_sub_ready(QcloudIotClient *client, const char *topic_filter)
{
IOT_FUNC_ENTRY;
int i = 0;
HAL_MutexLock(client->lock_generic);
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
HAL_MutexUnlock(client->lock_generic);
return client->sub_handles[i].status == SUB_ACK_RECEIVED;
}
}
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT_RC(false);
}
/**
* @brief Get usr data, usr should handle lock/unlock usrdata itself in callback and caller.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic_filter topic filter
* @return NULL or user data
*/
void *qcloud_iot_mqtt_get_subscribe_usr_data(QcloudIotClient *client, const char *topic_filter)
{
IOT_FUNC_ENTRY;
int i = 0;
HAL_MutexLock(client->lock_generic);
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
if ((client->sub_handles[i].topic_filter && !strcmp(client->sub_handles[i].topic_filter, topic_filter)) ||
strstr(topic_filter, "/#") || strstr(topic_filter, "/+")) {
HAL_MutexUnlock(client->lock_generic);
return client->sub_handles[i].params.user_data;
}
}
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT_RC(NULL);
}
/**
* @brief Clear sub handle array.
*
* @param[in,out] client pointer to mqtt client
*/
void qcloud_iot_mqtt_sub_handle_array_clear(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int i;
for (i = 0; i < MAX_MESSAGE_HANDLERS; ++i) {
/* notify this event to topic subscriber */
if (client->sub_handles[i].topic_filter && client->sub_handles[i].params.on_sub_event_handler) {
client->sub_handles[i].params.on_sub_event_handler(client, MQTT_EVENT_CLIENT_DESTROY,
client->sub_handles[i].params.user_data);
}
_clear_sub_handle(&client->sub_handles[i]);
}
IOT_FUNC_EXIT;
}
/**
* @brief Clear suback wait list and clear sub handle.
*
* @param[in,out] client pointer to mqtt client
*/
void qcloud_iot_mqtt_suback_wait_list_clear(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
utils_list_process(client->list_sub_wait_ack, LIST_HEAD, _sub_wait_list_process_clear, client);
IOT_FUNC_EXIT;
}

View File

@@ -0,0 +1,554 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client_yield.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-05-28
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-05-28 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>fix code standard of IotReturnCode and QcloudIotClient
* </table>
*/
#include "mqtt_client.h"
/**
* @brief Remain waiting time after MQTT data is received (unit: ms)
*
*/
#define QCLOUD_IOT_MQTT_MAX_REMAIN_WAIT_MS (100)
/**
* @brief Read one byte from network for mqtt packet header except remaining length.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] timeout_ms timeout to read
* @param[out] packet_type packet type to get
* @param[in,out] pptr pointer to buf pointer
* @return @see IotReturnCode
*/
static int _read_packet_header(QcloudIotClient *client, uint32_t timeout_ms, uint8_t *packet_type, uint8_t **pptr)
{
int rc = 0;
size_t read_len = 0;
// refresh timeout
timeout_ms = timeout_ms <= 0 ? 1 : timeout_ms;
rc = client->network_stack.read(&(client->network_stack), *pptr, 1, timeout_ms, &read_len);
if (rc == QCLOUD_ERR_SSL_NOTHING_TO_READ || rc == QCLOUD_ERR_TCP_NOTHING_TO_READ) {
return QCLOUD_ERR_MQTT_NOTHING_TO_READ;
}
// get packet type
*packet_type = (**pptr & 0xf0) >> 4;
(*pptr)++;
return rc;
}
/**
* @brief Read 1~4 bytes from network for mqtt packet header remaining length.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] timeout_ms timeout to read
* @param[out] plen read len
* @param[in,out] pptr pointer to buf pointer
* @return int
*/
static int _read_packet_remaining_len(QcloudIotClient *client, uint32_t timeout_ms, uint32_t *plen, uint8_t **pptr)
{
IOT_FUNC_ENTRY;
uint8_t *buf = *pptr;
uint8_t c = 0;
uint32_t multiplier = 1;
uint32_t count = 0;
uint32_t len = 0;
size_t rlen = 0;
// refresh timeout
timeout_ms = timeout_ms <= 0 ? 1 : timeout_ms;
timeout_ms += QCLOUD_IOT_MQTT_MAX_REMAIN_WAIT_MS;
do {
if (++count > MAX_NO_OF_REMAINING_LENGTH_BYTES) {
/* bad data */
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_PACKET_READ)
}
if (client->network_stack.read(&(client->network_stack), buf, 1, timeout_ms, &rlen)) {
/* The value argument is the important value. len is just used temporarily
* and never used by the calling function for anything else */
IOT_FUNC_EXIT_RC(QCLOUD_ERR_FAILURE);
}
c = *buf++;
len += (c & 127) * multiplier;
multiplier *= 128;
} while (c & 128);
if (plen) {
*plen = len;
}
*pptr += count;
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Discard the packet for mqtt read buf not enough.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] timeout_ms timeout to read
* @param[in] rem_len remaining length to read
*/
static void _discard_packet_for_short_buf(QcloudIotClient *client, uint32_t timeout_ms, uint32_t rem_len)
{
int rc;
size_t bytes_to_be_read, total_bytes_read = 0;
size_t read_len;
timeout_ms = timeout_ms <= 0 ? 1 : timeout_ms;
timeout_ms += QCLOUD_IOT_MQTT_MAX_REMAIN_WAIT_MS;
bytes_to_be_read = client->read_buf_size;
do {
rc = client->network_stack.read(&(client->network_stack), client->read_buf, bytes_to_be_read, timeout_ms,
&read_len);
if (rc) {
break;
}
total_bytes_read += read_len;
bytes_to_be_read =
(rem_len - total_bytes_read) >= client->read_buf_size ? client->read_buf_size : rem_len - total_bytes_read;
} while (total_bytes_read < rem_len);
}
/**
* @brief Read rem_len bytes from network for mqtt packet payload.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] timeout_ms timeout to read
* @param[in] rem_len length to read
* @param[out] buf buffer to save payload
* @return @see IotReturnCode
*/
static int _read_packet_payload(QcloudIotClient *client, uint32_t timeout_ms, uint32_t rem_len, uint8_t *buf)
{
int rc;
size_t read_len = 0;
timeout_ms = timeout_ms <= 0 ? 1 : timeout_ms;
timeout_ms += QCLOUD_IOT_MQTT_MAX_REMAIN_WAIT_MS;
rc = client->network_stack.read(&(client->network_stack), buf, rem_len, timeout_ms, &read_len);
if (rc) {
return rc;
}
if (read_len != rem_len) {
#ifdef AUTH_WITH_NO_TLS
return QCLOUD_ERR_SSL_READ_TIMEOUT;
#else
return QCLOUD_ERR_TCP_READ_TIMEOUT;
#endif
}
return QCLOUD_RET_SUCCESS;
}
/**
* @brief Read MQTT packet from network stack
*
* @param[in,out] client pointer to mqtt_client
* @param[in,out] timer timeout timer
* @param[out] packet_type MQTT packet type
* @return @see IotReturnCode
*
* @note
* 1. read 1st byte in fixed header and check if valid
* 2. read the remaining length
* 3. read payload according to remaining length
*/
static int _read_mqtt_packet(QcloudIotClient *client, Timer *timer, uint8_t *packet_type)
{
IOT_FUNC_ENTRY;
int rc = 0;
uint32_t rem_len = 0, packet_len = 0;
uint8_t *packet_read_buf = client->read_buf;
// 1. read 1st byte in fixed header and check if valid
rc = _read_packet_header(client, HAL_Timer_Remain(timer), packet_type, &packet_read_buf);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
// 2. read the remaining length
rc = _read_packet_remaining_len(client, HAL_Timer_Remain(timer), &rem_len, &packet_read_buf);
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
// if read buffer is not enough to read the remaining length, discard the packet
packet_len = packet_read_buf - client->read_buf + rem_len;
if (packet_len >= client->read_buf_size) {
_discard_packet_for_short_buf(client, HAL_Timer_Remain(timer), rem_len);
Log_e("MQTT Recv buffer not enough: %lu < %d", client->read_buf_size, rem_len);
IOT_FUNC_EXIT_RC(QCLOUD_ERR_BUF_TOO_SHORT);
}
// 3. read payload according to remaining length
if (rem_len > 0) {
rc = _read_packet_payload(client, HAL_Timer_Remain(timer), rem_len, packet_read_buf);
}
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Reset ping counter & ping timer.
*
* @param[in,out] client pointer to mqtt_client
*/
static void _handle_pingresp_packet(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
HAL_MutexLock(client->lock_generic);
client->is_ping_outstanding = 0;
HAL_Timer_Countdown(&client->ping_timer, client->options.keep_alive_interval);
HAL_MutexUnlock(client->lock_generic);
IOT_FUNC_EXIT;
}
/**
* @brief Read a mqtt packet from network and handle it.
*
* @param[in,out] client pointer to mqtt_client
* @param[in] timer timer for operation
* @param[out] packet_type packet type of packet read
* @return @see IotReturnCode
*/
static int _cycle_for_read(QcloudIotClient *client, Timer *timer, uint8_t *packet_type)
{
IOT_FUNC_ENTRY;
int rc;
/* read the socket, see what work is due */
rc = _read_mqtt_packet(client, timer, packet_type);
if (QCLOUD_ERR_MQTT_NOTHING_TO_READ == rc) {
/* Nothing to read, not a cycle failure */
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
if (rc) {
IOT_FUNC_EXIT_RC(rc);
}
switch (*packet_type) {
case CONNACK:
case PUBREC:
case PUBREL:
case PUBCOMP:
case PINGRESP:
break;
case PUBACK:
rc = qcloud_iot_mqtt_handle_puback(client);
break;
case SUBACK:
rc = qcloud_iot_mqtt_handle_suback(client);
break;
case UNSUBACK:
rc = qcloud_iot_mqtt_handle_unsuback(client);
break;
case PUBLISH:
rc = qcloud_iot_mqtt_handle_publish(client);
break;
default:
// Either unknown packet type or failure occurred should not happen
IOT_FUNC_EXIT_RC(QCLOUD_ERR_RX_MESSAGE_INVAL);
break;
}
switch (*packet_type) {
// Recv below msgs are all considered as PING OK
case PUBACK:
case SUBACK:
case UNSUBACK:
case PINGRESP:
_handle_pingresp_packet(client);
break;
// Recv downlink pub means link is OK but we still need to send PING request
case PUBLISH:
HAL_MutexLock(client->lock_generic);
client->is_ping_outstanding = 0;
HAL_MutexUnlock(client->lock_generic);
break;
}
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Set reconnect wait interval if auto conect is enable.
*
* @param[in,out] client pointer to mqtt client
*/
static void _set_reconnect_wait_interval(QcloudIotClient *client)
{
client->counter_network_disconnected++;
if (client->auto_connect_enable) {
srand(HAL_Timer_CurrentSec());
// range: 1000 - 2000 ms, in 10ms unit
client->current_reconnect_wait_interval = (rand() % 100 + 100) * 10;
HAL_Timer_CountdownMs(&(client->reconnect_delay_timer), client->current_reconnect_wait_interval);
}
}
/**
* @brief Handle disconnect if connection is error.
*
* @param[in,out] client pointer to mqtt client
*/
static void _handle_disconnect(QcloudIotClient *client)
{
int rc;
MQTTEventMsg msg;
if (!get_client_conn_state(client)) {
_set_reconnect_wait_interval(client);
return;
}
rc = qcloud_iot_mqtt_disconnect(client);
// disconnect network stack by force
if (rc) {
client->network_stack.disconnect(&(client->network_stack));
set_client_conn_state(client, NOTCONNECTED);
}
Log_e("disconnect MQTT for some reasons..");
// notify event
if (client->event_handle.h_fp) {
msg.event_type = MQTT_EVENT_DISCONNECT;
msg.msg = NULL;
client->event_handle.h_fp(client, client->event_handle.context, &msg);
}
// exceptional disconnection
client->was_manually_disconnected = 0;
_set_reconnect_wait_interval(client);
}
/**
* @brief Handle reconnect.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
static int _handle_reconnect(QcloudIotClient *client)
{
IOT_FUNC_ENTRY;
int rc = QCLOUD_RET_MQTT_RECONNECTED;
MQTTEventMsg msg;
// reconnect control by delay timer (increase interval exponentially )
if (!HAL_Timer_Expired(&(client->reconnect_delay_timer))) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT);
}
rc = qcloud_iot_mqtt_attempt_reconnect(client);
if (QCLOUD_RET_MQTT_RECONNECTED == rc) {
Log_e("attempt to reconnect success.");
// notify event
if (client->event_handle.h_fp) {
msg.event_type = MQTT_EVENT_RECONNECT;
msg.msg = NULL;
client->event_handle.h_fp(client, client->event_handle.context, &msg);
}
IOT_FUNC_EXIT_RC(rc);
}
Log_e("attempt to reconnect failed, errCode: %d", rc);
client->current_reconnect_wait_interval *= 2;
if (MAX_RECONNECT_WAIT_INTERVAL < client->current_reconnect_wait_interval) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_RECONNECT_TIMEOUT);
}
HAL_Timer_CountdownMs(&(client->reconnect_delay_timer), client->current_reconnect_wait_interval);
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT);
}
/**
* @brief Handle MQTT keep alive (hearbeat with server).
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
static int _mqtt_keep_alive(QcloudIotClient *client)
{
#define MQTT_PING_RETRY_TIMES 2
#define MQTT_PING_SEND_RETRY_TIMES 3
IOT_FUNC_ENTRY;
int rc = 0;
if (0 == client->options.keep_alive_interval) {
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
if (!HAL_Timer_Expired(&client->ping_timer)) {
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
if (client->is_ping_outstanding >= MQTT_PING_RETRY_TIMES) {
// reaching here means we haven't received any MQTT packet for a long time (keep_alive_interval)
Log_e("Fail to recv MQTT msg. Something wrong with the connection.");
_handle_disconnect(client);
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
}
// there is no ping outstanding - send one
rc = qcloud_iot_mqtt_pingreq(client, MQTT_PING_SEND_RETRY_TIMES);
if (rc) {
// if sending a PING fails, propably the connection is not OK and we decide to disconnect and begin reconnection
// attempts
Log_e("Fail to send PING request. Something wrong with the connection.");
_handle_disconnect(client);
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
}
// start a timer to wait for PINGRESP from server
HAL_MutexLock(client->lock_generic);
client->is_ping_outstanding++;
HAL_Timer_CountdownMs(&client->ping_timer, client->command_timeout_ms);
HAL_MutexUnlock(client->lock_generic);
Log_d("PING request %u has been sent...", client->is_ping_outstanding);
IOT_FUNC_EXIT_RC(QCLOUD_RET_SUCCESS);
}
/**
* @brief Check connection and keep alive state, read/handle MQTT message in synchronized way.
*
* @param[in,out] client pointer to mqtt client
* @param[in] timeout_ms timeout value (unit: ms) for this operation
*
* @return QCLOUD_RET_SUCCESS when success, QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT when try reconnecting, others @see
* IotReturnCode
*/
int qcloud_iot_mqtt_yield(QcloudIotClient *client, uint32_t timeout_ms)
{
IOT_FUNC_ENTRY;
int rc = QCLOUD_RET_SUCCESS;
uint8_t packet_type;
Timer timer;
// 1. check if manually disconnect
if (!get_client_conn_state(client) && client->was_manually_disconnected == 1) {
IOT_FUNC_EXIT_RC(QCLOUD_RET_MQTT_MANUALLY_DISCONNECTED);
}
// 2. check connection state and if auto reconnect is enabled
if (!get_client_conn_state(client) && client->auto_connect_enable == 0) {
IOT_FUNC_EXIT_RC(QCLOUD_ERR_MQTT_NO_CONN);
}
// 3. main loop for packet reading/handling and keep alive maintainance
HAL_Timer_CountdownMs(&timer, timeout_ms);
while (!HAL_Timer_Expired(&timer)) {
// handle reconnect
if (!get_client_conn_state(client)) {
if (client->current_reconnect_wait_interval > MAX_RECONNECT_WAIT_INTERVAL) {
rc = QCLOUD_ERR_MQTT_RECONNECT_TIMEOUT;
break;
}
rc = _handle_reconnect(client);
continue;
}
// read and handle packet
rc = _cycle_for_read(client, &timer, &packet_type);
switch (rc) {
case QCLOUD_RET_SUCCESS:
// check list of wait publish ACK to remove node that is ACKED or timeout
qcloud_iot_mqtt_check_pub_timeout(client);
// check list of wait subscribe(or unsubscribe) ACK to remove node that is ACKED or timeout
qcloud_iot_mqtt_check_sub_timeout(client);
rc = _mqtt_keep_alive(client);
if (rc) {
IOT_FUNC_EXIT_RC(client->auto_connect_enable ? QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT : rc);
}
break;
case QCLOUD_ERR_SSL_READ_TIMEOUT:
case QCLOUD_ERR_SSL_READ:
case QCLOUD_ERR_TCP_PEER_SHUTDOWN:
case QCLOUD_ERR_TCP_READ_TIMEOUT:
case QCLOUD_ERR_TCP_READ_FAIL:
Log_e("network read failed, rc: %d. MQTT Disconnect.", rc);
_handle_disconnect(client);
IOT_FUNC_EXIT_RC(client->auto_connect_enable ? QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT
: QCLOUD_ERR_MQTT_NO_CONN);
break;
default: // others, just return
IOT_FUNC_EXIT_RC(rc);
}
}
IOT_FUNC_EXIT_RC(rc);
}
/**
* @brief Wait read specific mqtt packet, such as connack.
*
* @param[in,out] client pointer to mqtt client
* @param[in] packet_type packet type except to read
* @return @see IotReturnCode
*/
int qcloud_iot_mqtt_wait_for_read(QcloudIotClient *client, uint8_t packet_type)
{
IOT_FUNC_ENTRY;
int rc;
uint8_t read_packet_type = 0;
Timer timer;
HAL_Timer_CountdownMs(&timer, client->command_timeout_ms);
do {
if (HAL_Timer_Expired(&timer)) {
rc = QCLOUD_ERR_MQTT_REQUEST_TIMEOUT;
break;
}
rc = _cycle_for_read(client, &timer, &read_packet_type);
} while (QCLOUD_RET_SUCCESS == rc && read_packet_type != packet_type);
IOT_FUNC_EXIT_RC(rc);
}

View File

@@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
DerivePointerAlignment: true
PointerAlignment: Left
SortIncludes: true
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash

View File

@@ -0,0 +1,64 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file test_mqtt_client.cc
* @brief unittest for mqtt client
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-07
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-07 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>support tls test
* <tr><td>2021-07-12 <td>1.1 <td>fancyxu <td>fix connect twice in 5s error
* </table>
*/
#include "mqtt_client_test.h"
namespace mqtt_client_unittest {
void MqttClientTest::SetUp() {
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
ASSERT_EQ(HAL_GetDevInfo(reinterpret_cast<void *>(&device_info)), 0);
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
init_params.device_info = &device_info;
init_params.command_timeout = QCLOUD_IOT_MQTT_COMMAND_TIMEOUT;
init_params.keep_alive_interval_ms = QCLOUD_IOT_MQTT_KEEP_ALIVE_INTERNAL;
init_params.auto_connect_enable = 1;
init_params.event_handle.h_fp = NULL;
init_params.event_handle.context = NULL;
HAL_SleepMs(5000); // for iot hub can not connect twice in 5 s
client = IOT_MQTT_Construct(&init_params);
ASSERT_NE(client, nullptr);
}
void MqttClientTest::TearDown() {
IOT_MQTT_Destroy(&client);
utils_log_deinit();
}
} // namespace mqtt_client_unittest

View File

@@ -0,0 +1,53 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file mqtt_client_test.h
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_TEST_MQTT_CLIENT_TEST_H_
#define IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_TEST_MQTT_CLIENT_TEST_H_
#include "gtest/gtest.h"
#include "qcloud_iot_common.h"
namespace mqtt_client_unittest {
/**
* @brief test fixture of mqtt client
*
*/
class MqttClientTest : public testing::Test {
protected:
void SetUp() override;
void TearDown() override;
void *client = NULL;
DeviceInfo device_info;
};
} // namespace mqtt_client_unittest
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_COMMON_MQTT_CLIENT_TEST_MQTT_CLIENT_TEST_H_

View File

@@ -0,0 +1,155 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file test_mqtt_client.cc
* @brief unittest for mqtt client
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-07
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-07 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2021-07-08 <td>1.1 <td>fancyxu <td>support tls test
* <tr><td>2021-07-12 <td>1.1 <td>fancyxu <td>fix connect twice in 5s error
* <tr><td>2021-07-18 <td>1.2 <td>fancyxu <td>remove MqttClientTest for common use
* </table>
*/
#include <iostream>
#include <string>
#include "mqtt_client_test.h"
namespace mqtt_client_unittest {
/**
* @brief Test subscribe.
*
*/
TEST_F(MqttClientTest, subscribe) {
int wait_cnt;
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/data", device_info.product_id, device_info.device_name);
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
/**
* @brief QOS0
*
*/
wait_cnt = 10;
sub_params.qos = QOS0;
ASSERT_GE(IOT_MQTT_Subscribe(client, topic_name, &sub_params), 0);
while (!IOT_MQTT_IsSubReady(client, topic_name) && (wait_cnt > 0)) {
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
wait_cnt--;
}
ASSERT_NE(wait_cnt, 0);
ASSERT_GE(IOT_MQTT_Unsubscribe(client, topic_name), 0);
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
/**
* @brief QOS1
*
*/
wait_cnt = 10;
sub_params.qos = QOS1;
ASSERT_GE(IOT_MQTT_Subscribe(client, topic_name, &sub_params), 0);
while (!IOT_MQTT_IsSubReady(client, topic_name) && (wait_cnt > 0)) {
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
wait_cnt--;
}
ASSERT_NE(wait_cnt, 0);
ASSERT_GE(IOT_MQTT_Unsubscribe(client, topic_name), 0);
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
/**
* @brief sub sync
*
*/
ASSERT_GE(IOT_MQTT_SubscribeSync(client, topic_name, &sub_params), 0);
ASSERT_GE(IOT_MQTT_Unsubscribe(client, topic_name), 0);
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
}
/**
* @brief Test publish.
*
*/
TEST_F(MqttClientTest, publish) {
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/data", device_info.product_id, device_info.device_name);
char topic_content[] = "{\"action\": \"publish_test\", \"count\": \"0\"}";
PublishParams pub_params = DEFAULT_PUB_PARAMS;
pub_params.payload = topic_content;
pub_params.payload_len = strlen(topic_content);
/**
* @brief QOS0
*
*/
pub_params.qos = QOS0;
ASSERT_GE(IOT_MQTT_Publish(client, topic_name, &pub_params), 0);
/**
* @brief QOS1
*
*/
pub_params.qos = QOS1;
ASSERT_GE(IOT_MQTT_Publish(client, topic_name, &pub_params), 0);
}
/**
* @brief Test clean session.
*
*/
TEST_F(MqttClientTest, clean_session) {
IOT_MQTT_Destroy(&client);
HAL_SleepMs(5000); // for iot hub can not connect twice in 5 s
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
char topic_name[MAX_SIZE_OF_CLOUD_TOPIC] = {0};
ASSERT_EQ(HAL_GetDevInfo(reinterpret_cast<void *>(&device_info)), 0);
HAL_Snprintf(topic_name, sizeof(topic_name), "%s/%s/data", device_info.product_id, device_info.device_name);
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
init_params.device_info = &device_info;
init_params.clean_session = 0;
client = IOT_MQTT_Construct(&init_params);
ASSERT_NE(client, nullptr);
ASSERT_GE(IOT_MQTT_SubscribeSync(client, topic_name, &sub_params), 0);
IOT_MQTT_Destroy(&client);
HAL_SleepMs(5000);
init_params.clean_session = 0;
init_params.connect_when_construct = 0;
init_params.default_subscribe = 1;
client = IOT_MQTT_Construct(&init_params);
ASSERT_NE(client, nullptr);
ASSERT_GE(IOT_MQTT_SubscribeSync(client, topic_name, &sub_params), 0);
ASSERT_EQ(IOT_MQTT_Connect(client), 0);
}
} // namespace mqtt_client_unittest

View File

@@ -0,0 +1,11 @@
file(GLOB src_ota_mqtt ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(src_services ${src_services} ${src_ota_mqtt} PARENT_SCOPE)
file(GLOB src_ota_mqtt_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/ota_mqtt_sample.c)
add_executable(ota_mqtt_sample ${src_ota_mqtt_sample})
target_link_libraries(ota_mqtt_sample ${libsdk})
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,266 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file broadcast_sample.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_common.h"
#include "utils_log.h"
/**
* @brief MQTT event callback, @see MQTTEventHandleFun
*
* @param[in] client pointer to mqtt client
* @param[in] handle_context context
* @param[in] msg msg
*/
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
{
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
uintptr_t packet_id = (uintptr_t)msg->msg;
switch (msg->event_type) {
case MQTT_EVENT_UNDEF:
Log_i("undefined event occur.");
break;
case MQTT_EVENT_DISCONNECT:
Log_i("MQTT disconnect.");
break;
case MQTT_EVENT_RECONNECT:
Log_i("MQTT reconnect.");
break;
case MQTT_EVENT_PUBLISH_RECEIVED:
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
break;
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_NACK:
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_NACK:
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_SUCCESS:
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_TIMEOUT:
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_NACK:
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
break;
default:
Log_i("Should NOT arrive here.");
break;
}
}
/**
* @brief Setup MQTT construct parameters.
*
* @param[in,out] initParams @see MQTTInitParams
* @param[in] device_info @see DeviceInfo
*/
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
{
init_params->device_info = device_info;
init_params->command_timeout = QCLOUD_IOT_MQTT_COMMAND_TIMEOUT;
init_params->keep_alive_interval_ms = QCLOUD_IOT_MQTT_KEEP_ALIVE_INTERNAL;
init_params->auto_connect_enable = 1;
init_params->event_handle.h_fp = _mqtt_event_handler;
init_params->event_handle.context = NULL;
}
// ----------------------------------------------------------------------------
// OTA function
// ----------------------------------------------------------------------------
int _firmware_download(void *client, char *buf, int buf_len, UtilsJsonValue url, UtilsJsonValue md5sum,
uint32_t file_size, const char *version)
{
Log_i("downloading firmware:url=%.*s|md5sum=%.*s|file_size=%u", url.value_len, url.value, md5sum.value_len,
md5sum.value, file_size);
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 0, version);
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 50, version);
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 100, version);
return 0;
}
void _firmware_burn(void *client, char *buf, int buf_len, const char *version)
{
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN, 0, version);
IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS, 0, version);
IOT_OTA_ReportVersion(client, buf, buf_len, version);
}
// ----------------------------------------------------------------------------
// OTA callback
// ----------------------------------------------------------------------------
void _update_firmware_callback(UtilsJsonValue version, UtilsJsonValue url, UtilsJsonValue md5sum, uint32_t file_size,
void *usr_data)
{
char firmware_version[64] = {0};
char buf[256];
int buf_len = sizeof(buf);
Log_i("recv firmware: version=%.*s|url=%.*s|md5sum=%.*s|file_size=%u", version.value_len, version.value,
url.value_len, url.value, md5sum.value_len, md5sum.value, file_size);
strncpy(firmware_version, version.value, version.value_len);
int rc = _firmware_download(usr_data, buf, buf_len, url, md5sum, file_size, firmware_version);
if (rc) {
IOT_OTA_ReportProgress(usr_data, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_FAIL, 0, firmware_version);
return;
}
_firmware_burn(usr_data, buf, buf_len, firmware_version);
}
void _report_version_reply_callback(int result_code, void *usr_data)
{
Log_i("recv code=%d", result_code);
}
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc;
char buf[1024];
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
DeviceInfo device_info;
rc = HAL_GetDevInfo((void *)&device_info);
if (rc) {
Log_e("get device info failed: %d", rc);
return rc;
}
// init connection
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
_setup_connect_init_params(&init_params, &device_info);
// create MQTT client and connect with server
void *client = IOT_MQTT_Construct(&init_params);
if (client) {
Log_i("Cloud Device Construct Success");
} else {
Log_e("MQTT Construct failed!");
return QCLOUD_ERR_FAILURE;
}
IotOTAUpdateCallback ota_callback = {
.update_firmware_callback = _update_firmware_callback,
.report_version_reply_callback = _report_version_reply_callback,
};
rc = IOT_OTA_Init(client, ota_callback, client);
if (rc) {
Log_e("OTA init failed!, rc=%d", rc);
return rc;
}
rc = IOT_OTA_ReportVersion(client, buf, sizeof(buf), QCLOUD_IOT_DEVICE_SDK_VERSION);
if (rc) {
Log_e("OTA report version failed!, rc=%d", rc);
return rc;
}
do {
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
switch (rc) {
case QCLOUD_RET_SUCCESS:
break;
case QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT:
continue;
case QCLOUD_RET_MQTT_RECONNECTED:
break;
default:
Log_e("Exit loop caused of errCode:%d", rc);
goto exit;
}
} while (!sg_main_exit);
exit:
IOT_OTA_Deinit(client);
rc = IOT_MQTT_Destroy(&client);
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,310 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file ota_mqtt.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-10-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-10-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "qcloud_iot_ota.h"
/**
* @brief Context of OTA update topic, callback and user data.
*
*/
typedef struct {
IotOTAUpdateCallback callback;
void * usr_data;
} OTAUpdateContext;
/**
* @brief OTA update type.
*
*/
typedef enum {
OTA_UPDATE_TYPE_REPORT_VERSION_RSP = 0,
OTA_UPDATE_TYPE_UPDATE_FIRMWARE,
} OTAUpdateType;
/**
* @brief Parse payload and callback.
*
* @param[in] type @see OTAUpdateType
* @param[in] message message from cloud
* @param[in] callback callback for user
* @param[in,out] usr_data user data used in callback
*/
static void _parse_update_payload_and_callback(OTAUpdateType type, const MQTTMessage *message,
const IotOTAUpdateCallback *callback, void *usr_data)
{
int rc, result_code;
uint32_t file_size = 0;
UtilsJsonValue version, md5sum, url, value_file_size, value_result_code;
// callback
switch (type) {
case OTA_UPDATE_TYPE_REPORT_VERSION_RSP:
if (!callback->report_version_reply_callback) {
return;
}
rc = utils_json_value_get("result_code", strlen("result_code"), message->payload_str, message->payload_len,
&value_result_code);
if (rc) {
goto error;
}
rc = utils_json_value_data_get(value_result_code, UTILS_JSON_VALUE_TYPE_INT32, &result_code);
if (rc) {
goto error;
}
callback->report_version_reply_callback(result_code, usr_data);
break;
case OTA_UPDATE_TYPE_UPDATE_FIRMWARE:
if (!callback->update_firmware_callback) {
return;
}
rc = utils_json_value_get("version", strlen("version"), message->payload_str, message->payload_len,
&version);
rc |= utils_json_value_get("url", strlen("url"), message->payload_str, message->payload_len, &url);
rc |= utils_json_value_get("md5sum", strlen("md5sum"), message->payload_str, message->payload_len, &md5sum);
rc |= utils_json_value_get("file_size", strlen("file_size"), message->payload_str, message->payload_len,
&value_file_size);
rc |= utils_json_value_data_get(value_file_size, UTILS_JSON_VALUE_TYPE_UINT32, &file_size);
if (rc) {
goto error;
}
callback->update_firmware_callback(version, url, md5sum, file_size, usr_data);
break;
default:
break;
}
return;
error:
Log_e("invalid format of payload!");
return;
}
/**
* @brief Mqtt message callback for property topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see OTAUpdateContext
*/
static void _ota_mqtt_message_callback(void *client, const MQTTMessage *message, void *usr_data)
{
const char *ota_update_str[] = {
"report_version_rsp", // OTA_UPDATE_TYPE_REPORT_VERSION_RSP
"update_firmware", // OTA_UPDATE_TYPE_UPDATE_FIRMWARE
};
int rc, i = 0;
OTAUpdateContext *ota_update_context = (OTAUpdateContext *)usr_data;
UtilsJsonValue update_type;
Log_d("receive ota message:%.*s", message->payload_len, message->payload_str);
rc = utils_json_value_get("type", strlen("type"), message->payload_str, message->payload_len, &update_type);
if (rc) {
Log_e("invalid ota message!");
return;
}
for (i = OTA_UPDATE_TYPE_REPORT_VERSION_RSP; i <= OTA_UPDATE_TYPE_UPDATE_FIRMWARE; i++) {
if (!strncmp(update_type.value, ota_update_str[i], update_type.value_len)) {
Log_d("callback ota message!");
_parse_update_payload_and_callback(i, message, &ota_update_context->callback, ota_update_context->usr_data);
}
}
}
/**
* @brief Check if topic already subscribed, if not then subscribe.
*
* @param[in,out] client pointer to mqtt client
* @param callback @see IotOTAUpdateCallback
* @param usr_data usr data in callback
* @return @see IotReturnCode
*/
static int _ota_mqtt_update_topic_check_and_sub(void *client, IotOTAUpdateCallback callback, void *usr_data)
{
int rc = 0;
char ota_result_topic[MAX_SIZE_OF_CLOUD_TOPIC];
HAL_Snprintf(ota_result_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$ota/update/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
OTAUpdateContext *ota_update_context = (OTAUpdateContext *)HAL_Malloc(sizeof(OTAUpdateContext));
if (!ota_update_context) {
return QCLOUD_ERR_MALLOC;
}
ota_update_context->callback = callback;
ota_update_context->usr_data = usr_data;
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
sub_params.on_message_handler = _ota_mqtt_message_callback;
sub_params.qos = QOS1;
sub_params.user_data = ota_update_context;
sub_params.user_data_free = HAL_Free;
rc = IOT_MQTT_SubscribeSync(client, ota_result_topic, &sub_params);
if (rc) {
HAL_Free(ota_update_context);
}
return rc;
}
/**
* @brief Unsubscribe topic.
*
* @param[in,out] client pointer to mqtt client
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
static int _ota_mqtt_update_topic_unsubscribe(void *client)
{
char ota_result_topic[MAX_SIZE_OF_CLOUD_TOPIC];
HAL_Snprintf(ota_result_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$ota/update/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
return IOT_MQTT_Unsubscribe(client, ota_result_topic);
}
/**
* @brief Publish to OTA report topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] qos @see QoS
* @param[in] payload payload of mqtt packet
* @param[in] payload_len payload len
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
static int _ota_mqtt_publish(void *client, QoS qos, const char *payload, int payload_len)
{
char ota_report_topic[MAX_SIZE_OF_CLOUD_TOPIC];
HAL_Snprintf(ota_report_topic, sizeof(ota_report_topic), "$ota/report/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
PublishParams pub_params = DEFAULT_PUB_PARAMS;
pub_params.qos = qos;
pub_params.payload = (void *)payload;
pub_params.payload_len = payload_len;
return IOT_MQTT_Publish(client, ota_report_topic, &pub_params);
}
/**
* @brief OTA init, subscribe update topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] callback @see IotOTAUpdateCallback
* @param[in] usr_data usr data used in callback
* @return 0 for success, or err code (<0) @see IotReturnCode
*/
int IOT_OTA_Init(void *client, IotOTAUpdateCallback callback, void *usr_data)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
return _ota_mqtt_update_topic_check_and_sub(client, callback, usr_data);
}
/**
* @brief OTA deinit, unsubscribe update topic.
*
* @param[in,out] client pointer to mqtt client
*/
void IOT_OTA_Deinit(void *client)
{
POINTER_SANITY_CHECK_RTN(client);
_ota_mqtt_update_topic_unsubscribe(client);
}
/**
* @brief Report upgrade progress.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] report_type @see IotOTAReportType
* @param[in] progress progress using in IOT_OTA_REPORT_TYPE_DOWNLOADING
* @param[in] version update firmware version
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_OTA_ReportProgress(void *client, char *buf, int buf_len, IotOTAReportType report_type, int progress,
const char *version)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
NUMBERIC_SANITY_CHECK(buf_len, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(version, QCLOUD_ERR_INVAL);
/**
* @brief order @see IotOTAReportType
*
*/
const char *ota_state[] = {"downloading", "burning", "done", "fail", "fail", "fail", "fail", "fail"};
int result_code[] = {0, 0, 0, -1, -2, -3, -4, -5};
const char *result_msg[] = {"", "", "", "timeout", "file not exit", "auth fail", "md5 not match", "upgrade fail"};
int len;
switch (report_type) {
case IOT_OTA_REPORT_TYPE_DOWNLOADING:
len = HAL_Snprintf(buf, buf_len,
"{\"type\":\"report_progress\",\"report\":{\"progress\":{\"state\":\"%s\",\"percent\":"
"\"%d\",\"result_code\":\"%d\",\"result_msg\":\"\"},\"version\":\"%s\"}}",
ota_state[report_type], progress, result_code[report_type], version);
break;
case IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN:
case IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS:
case IOT_OTA_REPORT_TYPE_DOWNLOAD_TIMEOUT:
case IOT_OTA_REPORT_TYPE_FILE_NOT_EXIST:
case IOT_OTA_REPORT_TYPE_AUTH_FAIL:
case IOT_OTA_REPORT_TYPE_MD5_NOT_MATCH:
case IOT_OTA_REPORT_TYPE_UPGRADE_FAIL:
len = HAL_Snprintf(buf, buf_len,
"{\"type\":\"report_progress\",\"report\":{\"progress\":{\"state\":\"%s\",\"result_"
"code\":\"%d\",\"result_msg\":\"%s\"},\"version\":\"%s\"}}",
ota_state[report_type], result_code[report_type], result_msg[report_type], version);
break;
default:
return QCLOUD_ERR_INVAL;
}
return _ota_mqtt_publish(client, QOS0, buf, len);
}
/**
* @brief Report current firmware version.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] version current firmware version
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_OTA_ReportVersion(void *client, char *buf, int buf_len, const char *version)
{
int len = HAL_Snprintf(buf, buf_len, "{\"type\":\"report_version\",\"report\":{\"version\":\"%s\"}}",
STRING_PTR_PRINT_SANITY_CHECK(version));
return _ota_mqtt_publish(client, QOS0, buf, len);
}

View File

@@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
DerivePointerAlignment: true
PointerAlignment: Left
SortIncludes: true
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash

View File

@@ -0,0 +1,72 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file test_ota_mqtt.cc
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-10-19
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-10-19 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <iostream>
#include <string>
#include "gtest/gtest.h"
#include "mqtt_client_test.h"
#include "qcloud_iot_common.h"
namespace mqtt_client_unittest {
void _update_firmware_callback(UtilsJsonValue version, UtilsJsonValue url, UtilsJsonValue md5sum, uint32_t file_size,
void *usr_data) {
Log_i("recv firmware: version=%.*s|url=%.*s|md5sum=%.*s|file_size=%u", version.value_len, version.value,
url.value_len, url.value, md5sum.value_len, md5sum.value, file_size);
}
void _report_version_reply_callback(int result_code, void *usr_data) { Log_i("recv code=%d", result_code); }
/**
* @brief Test ota mqtt.
*
*/
TEST_F(MqttClientTest, ota_mqtt) {
char buf[256];
int buf_len = sizeof(buf);
const char *version = "1.0.0";
IotOTAUpdateCallback ota_callback = {
.update_firmware_callback = _update_firmware_callback,
.report_version_reply_callback = _report_version_reply_callback,
};
ASSERT_EQ(IOT_OTA_Init(client, ota_callback, client), 0);
ASSERT_GE(IOT_OTA_ReportVersion(client, buf, buf_len, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 0, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOADING, 100, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_BEGIN, 0, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_SUCCESS, 0, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_DOWNLOAD_TIMEOUT, 0, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_FILE_NOT_EXIST, 0, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_AUTH_FAIL, 0, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_MD5_NOT_MATCH, 0, version), 0);
ASSERT_GE(IOT_OTA_ReportProgress(client, buf, buf_len, IOT_OTA_REPORT_TYPE_UPGRADE_FAIL, 0, version), 0);
IOT_OTA_Deinit(client);
}
} // namespace mqtt_client_unittest

View File

@@ -0,0 +1,11 @@
file(GLOB src_system_mqtt ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(src_services ${src_services} ${src_system_mqtt} PARENT_SCOPE)
file(GLOB src_system_mqtt_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/system_mqtt_sample.c)
add_executable(system_mqtt_sample ${src_system_mqtt_sample})
target_link_libraries(system_mqtt_sample ${libsdk})
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,210 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file broadcast_sample.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2022-01-26 <td>1.1 <td>hubert <td>add serverip
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_common.h"
#include "utils_log.h"
/**
* @brief MQTT event callback, @see MQTTEventHandleFun
*
* @param[in] client pointer to mqtt client
* @param[in] handle_context context
* @param[in] msg msg
*/
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
{
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
uintptr_t packet_id = (uintptr_t)msg->msg;
switch (msg->event_type) {
case MQTT_EVENT_UNDEF:
Log_i("undefined event occur.");
break;
case MQTT_EVENT_DISCONNECT:
Log_i("MQTT disconnect.");
break;
case MQTT_EVENT_RECONNECT:
Log_i("MQTT reconnect.");
break;
case MQTT_EVENT_PUBLISH_RECEIVED:
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
break;
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_NACK:
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_NACK:
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_SUCCESS:
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_TIMEOUT:
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_NACK:
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
break;
default:
Log_i("Should NOT arrive here.");
break;
}
}
/**
* @brief Setup MQTT construct parameters.
*
* @param[in,out] initParams @see MQTTInitParams
* @param[in] device_info @see DeviceInfo
*/
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
{
init_params->device_info = device_info;
init_params->command_timeout = QCLOUD_IOT_MQTT_COMMAND_TIMEOUT;
init_params->keep_alive_interval_ms = QCLOUD_IOT_MQTT_KEEP_ALIVE_INTERNAL;
init_params->auto_connect_enable = 1;
init_params->event_handle.h_fp = _mqtt_event_handler;
init_params->event_handle.context = NULL;
init_params->host = QCLOUD_IOT_MQTT_DIRECT_DOMAIN;
init_params->backup_host = "your_backup_host_domain.com";
}
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc;
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
DeviceInfo device_info;
rc = HAL_GetDevInfo((void *)&device_info);
if (rc) {
Log_e("get device info failed: %d", rc);
return rc;
}
// init connection
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
_setup_connect_init_params(&init_params, &device_info);
// create MQTT client and connect with server
void *client = IOT_MQTT_Construct(&init_params);
if (client) {
Log_i("Cloud Device Construct Success");
} else {
Log_e("MQTT Construct failed!");
return QCLOUD_ERR_FAILURE;
}
uint32_t time_stamp = 0;
rc = IOT_Sys_GetTime(client, &time_stamp);
if (rc) {
Log_e("Get system time failed!, rc=%d", rc);
} else {
Log_d("Get system time success, time %d", time_stamp);
}
rc = IOT_Sys_SyncNTPTime(client);
if (rc) {
Log_e("Get system ntp time failed!, rc=%d", rc);
}
char server_ip[256] = {0};
rc = IOT_Sys_GetServerIp(client, server_ip);
if (rc) {
Log_e("Get system server ip failed!, rc=%d", rc);
} else {
Log_i("Get system server ip list success. server ip: %s", server_ip);
}
rc = IOT_MQTT_Destroy(&client);
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,354 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file system_mqtt.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-24
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-24 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2022-01-26 <td>1.1 <td>hubert <td>add serverip
* </table>
*/
#include "qcloud_iot_system.h"
#define MAX_SYSTEM_INFO_SIZE 256
#define IP_MAX_NUM 10
#define ONE_IP_MAX_LEN 24
#define IP_LIST_MAX_LEN (IP_MAX_NUM * ONE_IP_MAX_LEN)
#define SYS_MQTT_GET_RESOURCE_TIME "{\"type\":\"get\",\"resource\":[\"time\"]}"
#define SYS_MQTT_GET_RESOURCE_SERVERIP "{\"type\":\"get\",\"resource\":[\"serverip\"]}"
typedef enum {
RESOURCE_TIME = 0,
RESOURCE_IP,
} SysResourceType;
/**
* @brief data structure for system time service
*/
typedef struct {
bool result_recv_ok;
// time
struct {
uint32_t time;
uint64_t ntptime1;
uint64_t ntptime2;
} time;
// server ip
struct {
char ip_list[IP_LIST_MAX_LEN];
int ip_len;
} server_ip;
SysResourceType wait_type;
uint64_t result_recv_time;
} SystemResultInfo;
/**
* @brief
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see SystemResultInfo
*/
static void _system_mqtt_message_callback(void *client, const MQTTMessage *message, void *usr_data)
{
int rc = 0;
SystemResultInfo *result = (SystemResultInfo *)usr_data;
UtilsJsonValue value;
Log_d("Receive system result message:%.*s", message->payload_len, message->payload_str);
if (strstr(message->payload_str, "\"time\"")) {
// get time
rc = utils_json_value_get("time", strlen("time"), message->payload_str, message->payload_len, &value);
if (rc) {
return;
}
rc = utils_json_value_data_get(value, UTILS_JSON_VALUE_TYPE_UINT32, &result->time.time);
if (rc) {
return;
}
// get ntptime1
rc = utils_json_value_get("ntptime1", strlen("ntptime1"), message->payload_str, message->payload_len, &value);
if (rc) {
return;
}
rc = utils_json_value_data_get(value, UTILS_JSON_VALUE_TYPE_UINT64, &result->time.ntptime1);
if (rc) {
return;
}
// get ntptime2
rc = utils_json_value_get("ntptime2", strlen("ntptime2"), message->payload_str, message->payload_len, &value);
if (rc) {
return;
}
rc = utils_json_value_data_get(value, UTILS_JSON_VALUE_TYPE_UINT64, &result->time.ntptime2);
if (rc) {
result->time.ntptime1 = result->time.time * 1000;
result->time.ntptime2 = result->time.time * 1000;
}
}
if (strstr(message->payload_str, "\"serverip\"")) {
// get serverip
rc = utils_json_value_get("serverip", strlen("serverip"), message->payload_str, message->payload_len, &value);
if (rc) {
return;
}
result->server_ip.ip_len = value.value_len > IP_LIST_MAX_LEN ? IP_LIST_MAX_LEN : value.value_len;
memset(result->server_ip.ip_list, 0, IP_LIST_MAX_LEN);
strncpy(result->server_ip.ip_list, value.value, result->server_ip.ip_len);
}
result->result_recv_time = HAL_Timer_CurrentMs();
result->result_recv_ok = true;
}
/**
* @brief Check and subscribe system result topic.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
static int _system_mqtt_result_topic_check_and_sub(void *client, const char *topic)
{
int rc = 0;
// 1. check topic is sub ready
if (IOT_MQTT_IsSubReady(client, topic)) {
return rc;
}
// 2. subscribe
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
SystemResultInfo *system_info = (SystemResultInfo *)HAL_Malloc(sizeof(SystemResultInfo));
if (!system_info) {
return QCLOUD_ERR_MALLOC;
}
system_info->result_recv_ok = false;
sub_params.on_message_handler = _system_mqtt_message_callback;
sub_params.qos = QOS1;
sub_params.user_data = system_info;
sub_params.user_data_free = HAL_Free;
rc = IOT_MQTT_SubscribeSync(client, topic, &sub_params);
if (rc) {
HAL_Free(system_info);
}
return rc;
}
/**
* @brief Publish get time message to system topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic system result topic
* @return >= 0 for success
*/
static int _system_mqtt_get_resource_publish(void *client, const char *topic, SysResourceType type)
{
char pub_topic_name[MAX_SIZE_OF_CLOUD_TOPIC];
HAL_Snprintf(pub_topic_name, sizeof(pub_topic_name), "$sys/operation/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
SystemResultInfo *result = IOT_MQTT_GetSubUsrData(client, topic);
if (!result) {
return QCLOUD_ERR_FAILURE;
}
result->wait_type = type;
result->result_recv_ok = false;
PublishParams pub_params = DEFAULT_PUB_PARAMS;
pub_params.qos = QOS0;
switch (type) {
case RESOURCE_TIME:
pub_params.payload = SYS_MQTT_GET_RESOURCE_TIME;
pub_params.payload_len = strlen(SYS_MQTT_GET_RESOURCE_TIME);
break;
case RESOURCE_IP:
pub_params.payload = SYS_MQTT_GET_RESOURCE_SERVERIP;
pub_params.payload_len = strlen(SYS_MQTT_GET_RESOURCE_SERVERIP);
default:
break;
}
return IOT_MQTT_Publish(client, pub_topic_name, &pub_params);
}
/**
* @brief Wait system result, timeout @see QCLOUD_IOT_MQTT_WAIT_ACK_TIMEOUT
*
* @param[in,out] client pointer to mqtt client
* @param[in] topic system result topic
* @param[out] time time from system result topic
* @return @see IotReturnCode
*/
static int _system_mqtt_result_wait(void *client, const char *topic, void *user_data)
{
int rc = 0;
Timer wait_result_timer;
SystemResultInfo *result;
HAL_Timer_CountdownMs(&wait_result_timer, QCLOUD_IOT_MQTT_WAIT_ACK_TIMEOUT);
result = IOT_MQTT_GetSubUsrData(client, topic);
if (!result) {
return QCLOUD_ERR_FAILURE;
}
result->result_recv_ok = false;
while (!rc && !HAL_Timer_Expired(&wait_result_timer)) {
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
if (result->result_recv_ok) {
switch (result->wait_type) {
case RESOURCE_TIME:
*((uint32_t *)user_data) = result->time.time;
break;
case RESOURCE_IP:
strncpy(user_data, result->server_ip.ip_list, result->server_ip.ip_len);
result->server_ip.ip_len = 0;
default:
break;
}
return QCLOUD_RET_SUCCESS;
}
}
return QCLOUD_ERR_MQTT_REQUEST_TIMEOUT;
}
static int _system_mqtt_get_resource(void *client, SysResourceType type, void *user_data)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(user_data, QCLOUD_ERR_INVAL);
int rc = 0;
char system_result_topic[MAX_SIZE_OF_CLOUD_TOPIC];
HAL_Snprintf(system_result_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$sys/operation/result/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
rc = _system_mqtt_result_topic_check_and_sub(client, system_result_topic);
if (rc) {
return rc;
}
rc = _system_mqtt_get_resource_publish(client, system_result_topic, type);
if (rc < 0) {
return rc;
}
return _system_mqtt_result_wait(client, system_result_topic, user_data);
}
/**
* @brief Get time from system result topic
*
* @param[in,out] client pointer to mqtt client
* @param[out] time time from system result topic
* @return @see IotReturnCode
*/
int IOT_Sys_GetTime(void *client, uint32_t *time)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(time, QCLOUD_ERR_INVAL);
return _system_mqtt_get_resource(client, RESOURCE_TIME, time);
}
/**
* @brief Get ntp time from system result topic and set to system.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int IOT_Sys_SyncNTPTime(void *client)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
int rc = 0;
uint32_t time_get = 0;
uint64_t local_publish_before, local_ntptime = 0;
SystemResultInfo *result = NULL;
char system_result_topic[MAX_SIZE_OF_CLOUD_TOPIC];
HAL_Snprintf(system_result_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$sys/operation/result/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
rc = _system_mqtt_result_topic_check_and_sub(client, system_result_topic);
if (rc) {
return rc;
}
// prepare for publish
result = IOT_MQTT_GetSubUsrData(client, system_result_topic);
if (!result) {
return -1;
}
local_publish_before = HAL_Timer_CurrentMs();
// publish and wait
rc = _system_mqtt_get_resource_publish(client, system_result_topic, RESOURCE_TIME);
if (rc < 0) {
return rc;
}
rc = _system_mqtt_result_wait(client, system_result_topic, &time_get);
if (rc) {
return rc;
}
local_ntptime =
(result->time.ntptime2 + result->time.ntptime1 + result->result_recv_time - local_publish_before) / 2;
rc = HAL_Timer_SetSystimeSec(time_get);
if (rc) {
Log_e("set systime sec failed, timestamp %d sec, please check permission or other ret:%d", time_get, rc);
} else {
Log_i("set systime sec success, timestamp %d sec", time_get);
}
rc = HAL_Timer_SetSystimeMs(local_ntptime);
if (rc) {
Log_e("set systime ms failed, timestamp %lld, please check permission or other ret :%d", local_ntptime, rc);
} else {
Log_i("set systime ms success, timestamp %lld ms", local_ntptime);
}
return rc;
}
/**
* @brief Get serverip from system result topic
*
* @param[in,out] client pointer to mqtt client
* @param[out] server_ip serverip from system result topic
* @return @see IotReturnCode
*/
int IOT_Sys_GetServerIp(void *client, char *server_ip)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(server_ip, QCLOUD_ERR_INVAL);
return _system_mqtt_get_resource(client, RESOURCE_IP, server_ip);
}

View File

@@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
DerivePointerAlignment: true
PointerAlignment: Left
SortIncludes: true
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash

View File

@@ -0,0 +1,51 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file test_broadcast.cc
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* <tr><td>2022-01-26 <td>1.1 <td>hubert <td>add serverip test
* </table>
*/
#include <iostream>
#include <string>
#include "gtest/gtest.h"
#include "mqtt_client_test.h"
#include "qcloud_iot_common.h"
namespace mqtt_client_unittest {
/**
* @brief Test system mqtt.
*
*/
TEST_F(MqttClientTest, system_mqtt) {
uint32_t time_stamp = 0;
char server_ip[256] = {0};
ASSERT_EQ(IOT_Sys_GetTime(client, &time_stamp), 0);
ASSERT_EQ(IOT_Sys_SyncNTPTime(client), 0);
ASSERT_EQ(IOT_Sys_GetServerIp(client, server_ip), 0);
}
} // namespace mqtt_client_unittest

View File

@@ -0,0 +1,14 @@
file(GLOB src_system_mqtt ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(inc_system_mqtt ${CMAKE_CURRENT_SOURCE_DIR}/inc/)
set(src_services ${src_services} ${src_system_mqtt} PARENT_SCOPE)
set(inc_services ${inc_services} ${inc_system_mqtt} PARENT_SCOPE)
file(GLOB src_data_template_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/data_template_sample.c)
add_executable(data_template_sample ${src_data_template_sample})
target_link_libraries(data_template_sample ${libsdk})
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,194 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file data_template.h
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-08-23
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-08-23 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_EXPLORER_DATA_TEMPLATE_INC_DATA_TEMPLATE_H_
#define IOT_HUB_DEVICE_C_SDK_SERVICES_EXPLORER_DATA_TEMPLATE_INC_DATA_TEMPLATE_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "qcloud_iot_common.h"
#include "qcloud_iot_data_template.h"
/**************************************************************************************
* common
**************************************************************************************/
/**
* @brief Type of data template(property/event/action).
*
*/
typedef enum {
DATA_TEMPLATE_TYPE_PROPERTY = 0,
DATA_TEMPLATE_TYPE_EVENT,
DATA_TEMPLATE_TYPE_ACTION,
} DataTemplateType;
/**
* @brief Context of data template, callback and user data.
*
*/
typedef struct {
union {
PropertyMessageCallback property_callback;
EventMessageCallback event_callback;
ActionMessageCallback action_callback;
};
void *usr_data;
} DataTemplateContext;
/**
* @brief Check if topic already subscribed, if not then subscribe.
*
* @param[in,out] client pointer to mqtt client
* @param[in] type @see DataTemplateType
* @param[in] on_message_handler message handle of topic
* @param[in] context @see DataTemplateContext
* @return @see IotReturnCode
*/
int data_template_topic_check_and_sub(void *client, DataTemplateType type, OnMessageHandler on_message_handler,
DataTemplateContext context);
/**
* @brief Unsubscribe data template topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] type @see DataTemplateType
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_topic_unsubscribe(void *client, DataTemplateType type);
/**
* @brief Publish to data template topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] type @see DataTemplateType
* @param[in] qos @see QoS
* @param[in] payload payload of mqtt packet
* @param[in] payload_len payload len
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_publish(void *client, DataTemplateType type, QoS qos, const char *payload, int payload_len);
/**************************************************************************************
* property
**************************************************************************************/
typedef enum {
PROPERTY_UP_METHOD_TYPE_REPORT = 0,
PROPERTY_UP_METHOD_TYPE_REPORT_INFO,
PROPERTY_UP_METHOD_TYPE_GET_STATUS,
PROPERTY_UP_METHOD_TYPE_CLEAR_CONTROL,
PROPERTY_UP_METHOD_TYPE_CONTROL_REPLY,
} PropertyUpMethodType;
typedef union {
const char *json;
int code;
struct {
int code;
UtilsJsonValue client_token;
} control_reply;
} PropertyPublishParams;
/**
* @brief Mqtt message callback for property topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see DataTemplateContext
*/
void data_template_property_message_handler(void *client, const MQTTMessage *message, void *usr_data);
/**
* @brief Publish message to property topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] publish_type @see PropertyUpMethodType
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] params @see PropertyPublishParams
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_property_publish(void *client, PropertyUpMethodType publish_type, char *buf, int buf_len,
PropertyPublishParams params);
/**************************************************************************************
* event
**************************************************************************************/
/**
* @brief Mqtt message callback for event topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see DataTemplateContext
*/
void data_template_event_message_handler(void *client, const MQTTMessage *message, void *usr_data);
/**
* @brief Publish message to event topic.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] data @see IotDataTemplateEventData
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_event_reply_publish(void *client, char *buf, int buf_len, IotDataTemplateEventData data);
/**************************************************************************************
* action
**************************************************************************************/
/**
* @brief Mqtt message callback for action topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see DataTemplateContext
*/
void data_template_action_message_handler(void *client, const MQTTMessage *message, void *usr_data);
/**
* @brief Publish message to event topic.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] reply @see IotDataTemplateActionReply
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_action_reply_publish(void *client, char *buf, int buf_len, IotDataTemplateActionReply reply);
#ifdef __cplusplus
}
#endif
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_EXPLORER_DATA_TEMPLATE_INC_DATA_TEMPLATE_H_

View File

@@ -0,0 +1,314 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file data_template_sample.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-09-27
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-09-27 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_common.h"
#include "qcloud_iot_explorer.h"
#include "utils_log.h"
/**
* @brief MQTT event callback, @see MQTTEventHandleFun
*
* @param[in] client pointer to mqtt client
* @param[in] handle_context context
* @param[in] msg msg
*/
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
{
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
uintptr_t packet_id = (uintptr_t)msg->msg;
switch (msg->event_type) {
case MQTT_EVENT_UNDEF:
Log_i("undefined event occur.");
break;
case MQTT_EVENT_DISCONNECT:
Log_i("MQTT disconnect.");
break;
case MQTT_EVENT_RECONNECT:
Log_i("MQTT reconnect.");
break;
case MQTT_EVENT_PUBLISH_RECEIVED:
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
break;
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_NACK:
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_NACK:
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_SUCCESS:
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_TIMEOUT:
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_NACK:
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
break;
default:
Log_i("Should NOT arrive here.");
break;
}
}
/**
* @brief Setup MQTT construct parameters.
*
* @param[in,out] initParams @see MQTTInitParams
* @param[in] device_info @see DeviceInfo
*/
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
{
init_params->device_info = device_info;
init_params->command_timeout = QCLOUD_IOT_MQTT_COMMAND_TIMEOUT;
init_params->keep_alive_interval_ms = QCLOUD_IOT_MQTT_KEEP_ALIVE_INTERNAL;
init_params->auto_connect_enable = 1;
init_params->event_handle.h_fp = _mqtt_event_handler;
init_params->event_handle.context = NULL;
}
// ----------------------------------------------------------------------------
// Data template callback
// ----------------------------------------------------------------------------
static void _method_control_callback(UtilsJsonValue client_token, UtilsJsonValue params, void *usr_data)
{
char buf[256];
Log_i("recv msg[%.*s]: params=%.*s", client_token.value_len, client_token.value, params.value_len, params.value);
IOT_DataTemplate_PropertyControlReply(usr_data, buf, sizeof(buf), 0, client_token);
}
static void _method_report_reply_callback(UtilsJsonValue client_token, int code, void *usr_data)
{
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
}
static void _method_get_status_reply_callback(UtilsJsonValue client_token, int code, UtilsJsonValue reported,
UtilsJsonValue control, void *usr_data)
{
char buf[256];
Log_i("recv msg[%.*s]: code=%d|reported=%.*s|control=%.*s", client_token.value_len, client_token.value, code,
reported.value_len, STRING_PTR_PRINT_SANITY_CHECK(reported.value), control.value_len,
STRING_PTR_PRINT_SANITY_CHECK(control.value));
IOT_DataTemplate_PropertyClearControl(usr_data, buf, sizeof(buf));
}
static void _method_report_info_reply_callback(UtilsJsonValue client_token, int code, void *usr_data)
{
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
}
static void _method_clear_control_reply_callback(UtilsJsonValue client_token, int code, void *usr_data)
{
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
}
static void _method_event_reply_callback(UtilsJsonValue client_token, int code, void *usr_data)
{
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
}
static void _method_action_callback(UtilsJsonValue client_token, UtilsJsonValue action_id, UtilsJsonValue params,
void *usr_data)
{
char buf[256];
Log_i("recv msg[%.*s]: action_id=%.*s|params=%.*s", client_token.value_len, client_token.value, action_id.value_len,
action_id.value, params.value_len, params.value);
IotDataTemplateActionReply reply = {
.code = 0,
.client_token = client_token,
.response = "{\"err_code\":0}",
};
IOT_DataTemplate_ActionReply(usr_data, buf, sizeof(buf), reply);
}
// ----------------------------------------------------------------------------
// Data template upstream
// ----------------------------------------------------------------------------
static void _cycle_report(void *client)
{
char buf[256];
const char *report_property = "{\"power_switch\":0}";
IotDataTemplateEventData event_data = {
.event_id = "status_report",
.type = IOT_DATA_TEMPLATE_EVENT_TYPE_INFO,
.params = "{\"status\":0,\"message\":\"ok\"}",
};
static Timer sg_cycle_report_timer;
if (HAL_Timer_Expired(&sg_cycle_report_timer)) {
IOT_DataTemplate_PropertyReport(client, buf, sizeof(buf), report_property);
IOT_DataTemplate_EventPost(client, buf, sizeof(buf), event_data);
HAL_Timer_Countdown(&sg_cycle_report_timer, 500);
}
}
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc;
char buf[1024];
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
DeviceInfo device_info;
rc = HAL_GetDevInfo((void *)&device_info);
if (rc) {
Log_e("get device info failed: %d", rc);
return rc;
}
// init connection
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
_setup_connect_init_params(&init_params, &device_info);
// create MQTT client and connect with server
void *client = IOT_MQTT_Construct(&init_params);
if (client) {
Log_i("Cloud Device Construct Success");
} else {
Log_e("MQTT Construct failed!");
return QCLOUD_ERR_FAILURE;
}
// subscribe normal topics and wait result
IotDataTemplateCallback callback = {
.property_callback = {.method_control_callback = _method_control_callback,
.method_clear_control_reply_callback = _method_clear_control_reply_callback,
.method_get_status_reply_callback = _method_get_status_reply_callback,
.method_report_info_reply_callback = _method_report_info_reply_callback,
.method_report_reply_callback = _method_report_reply_callback},
.event_callback = {.method_event_reply_callback = _method_event_reply_callback},
.action_callback = {.method_action_callback = _method_action_callback},
};
rc = IOT_DataTemplate_Init(client, callback, client);
if (rc) {
Log_e("Client Subscribe Topic Failed: %d", rc);
return rc;
}
const char *report_info =
"{\"module_hardinfo\":\"ESP8266\",\"module_softinfo\":\"V1.0\", \"fw_ver\":\"4.0.0\", "
"\"imei\":\"11-22-33-44\",\"lat\":\"22.546015\",\"lon\":\"113.941125\",\"mac\":\"11:22:33:44:55:66\",\"device_"
"label\":{\"append_"
"info\":\"your self defined info\"}}";
IOT_DataTemplate_PropertyReportInfo(client, buf, sizeof(buf), report_info);
IOT_DataTemplate_PropertyGetStatus(client, buf, sizeof(buf));
do {
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
switch (rc) {
case QCLOUD_RET_SUCCESS:
break;
case QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT:
continue;
case QCLOUD_RET_MQTT_RECONNECTED:
IOT_DataTemplate_PropertyGetStatus(client, buf, sizeof(buf));
break;
default:
Log_e("Exit loop caused of errCode:%d", rc);
goto exit;
}
_cycle_report(client);
} while (!sg_main_exit);
exit:
IOT_DataTemplate_Deinit(client);
rc |= IOT_MQTT_Destroy(&client);
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,95 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file data_template_action.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-09-26
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-09-26 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "data_template.h"
/**
* @brief Mqtt message callback for action topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see DataTemplateContext
*/
void data_template_action_message_handler(void *client, const MQTTMessage *message, void *usr_data)
{
DataTemplateContext *data_template_context = (DataTemplateContext *)usr_data;
int rc = 0;
UtilsJsonValue method, client_token, action_id, params;
Log_d("receive action message:%.*s", message->payload_len, message->payload_str);
rc = utils_json_value_get("method", strlen("method"), message->payload_str, message->payload_len, &method);
if (rc) {
return;
}
if (!strncmp(method.value, "action", method.value_len)) {
if (data_template_context->action_callback.method_action_callback) {
rc = utils_json_value_get("clientToken", strlen("clientToken"), message->payload_str, message->payload_len,
&client_token);
if (rc) {
goto error;
}
rc = utils_json_value_get("actionId", strlen("actionId"), message->payload_str, message->payload_len,
&action_id);
if (rc) {
goto error;
}
rc = utils_json_value_get("params", strlen("params"), message->payload_str, message->payload_len, &params);
if (rc) {
goto error;
}
data_template_context->action_callback.method_action_callback(client_token, action_id, params,
data_template_context->usr_data);
}
}
return;
error:
Log_e("invalid format of payload!");
}
/**
* @brief Publish message to event topic.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] reply @see IotDataTemplateActionReply
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_action_reply_publish(void *client, char *buf, int buf_len, IotDataTemplateActionReply reply)
{
int len =
HAL_Snprintf(buf, buf_len, "{\"method\":\"action_reply\",\"clientToken\":\"%.*s\",\"code\":%d,\"response\":%s}",
reply.client_token.value_len, reply.client_token.value, reply.code, reply.response);
return data_template_publish(client, DATA_TEMPLATE_TYPE_ACTION, QOS0, buf, len);
}

View File

@@ -0,0 +1,199 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file data_template.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-08-22
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-08-22 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "qcloud_iot_data_template.h"
#include "data_template.h"
/**
* @brief Check and subscribe data template topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] callback @see IotDataTemplateCallback
* @param[in] usr_data usr data used in callback
* @return 0 for success, or err code (<0) @see IotReturnCode
*/
int IOT_DataTemplate_Init(void *client, IotDataTemplateCallback callback, void *usr_data)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
int rc = 0;
DataTemplateContext data_tempale_context;
data_tempale_context.usr_data = usr_data;
data_tempale_context.property_callback = callback.property_callback;
rc |= data_template_topic_check_and_sub(client, DATA_TEMPLATE_TYPE_PROPERTY, data_template_property_message_handler,
data_tempale_context);
// if (callback.event_callback.method_event_reply_callback) {
// data_tempale_context.event_callback = callback.event_callback;
// rc |= data_template_topic_check_and_sub(client, DATA_TEMPLATE_TYPE_EVENT, data_template_event_message_handler,
// data_tempale_context);
// }
// if (callback.action_callback.method_action_callback) {
// data_tempale_context.action_callback = callback.action_callback;
// rc |= data_template_topic_check_and_sub(client, DATA_TEMPLATE_TYPE_ACTION, data_template_action_message_handler,
// data_tempale_context);
// }
return rc;
}
/**
* @brief Unsubscribe data template topic.
*
* @param[in,out] client pointer to mqtt client
*/
void IOT_DataTemplate_Deinit(void *client)
{
POINTER_SANITY_CHECK_RTN(client);
data_template_topic_unsubscribe(client, DATA_TEMPLATE_TYPE_PROPERTY);
data_template_topic_unsubscribe(client, DATA_TEMPLATE_TYPE_EVENT);
data_template_topic_unsubscribe(client, DATA_TEMPLATE_TYPE_ACTION);
}
/**
* @brief Report property.
*
* @param[in,out] client pointer to mqtt client
* @param[in] buf buffer for message
* @param[in] buf_len buffer length
* @param[in] params params constructed with property
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_DataTemplate_PropertyReport(void *client, char *buf, int buf_len, const char *params)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
PropertyPublishParams publish_params = {.json = params};
return data_template_property_publish(client, PROPERTY_UP_METHOD_TYPE_REPORT, buf, buf_len, publish_params);
}
/**
* @brief Get control message offline.
*
* @param[in,out] client pointer to mqtt client
* @param[in] buf buffer for message
* @param[in] buf_len buffer length
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_DataTemplate_PropertyGetStatus(void *client, char *buf, int buf_len)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
PropertyPublishParams publish_params = {0};
return data_template_property_publish(client, PROPERTY_UP_METHOD_TYPE_GET_STATUS, buf, buf_len, publish_params);
}
/**
* @brief Report device info.
*
* @param[in,out] client pointer to mqtt client
* @param[in] buf buffer for message
* @param[in] buf_len buffer length
* @param[in] params params constructed with device info
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_DataTemplate_PropertyReportInfo(void *client, char *buf, int buf_len, const char *params)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
PropertyPublishParams publish_params = {.json = params};
return data_template_property_publish(client, PROPERTY_UP_METHOD_TYPE_REPORT_INFO, buf, buf_len, publish_params);
}
/**
* @brief Clear control message offline.
*
* @param[in,out] client pointer to mqtt client
* @param[in] buf buffer for message
* @param[in] buf_len buffer length
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_DataTemplate_PropertyClearControl(void *client, char *buf, int buf_len)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
PropertyPublishParams publish_params = {0};
return data_template_property_publish(client, PROPERTY_UP_METHOD_TYPE_CLEAR_CONTROL, buf, buf_len, publish_params);
}
/**
* @brief Reply control message.
*
* @param[in,out] client pointer to mqtt client
* @param[in] buf buffer for message
* @param[in] buf_len buffer length
* @param[in] code 0 for success
* @param[in] client_token client token of control message
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_DataTemplate_PropertyControlReply(void *client, char *buf, int buf_len, int code, UtilsJsonValue client_token)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
PropertyPublishParams publish_params = {.control_reply.code = code, .control_reply.client_token = client_token};
return data_template_property_publish(client, PROPERTY_UP_METHOD_TYPE_CONTROL_REPLY, buf, buf_len, publish_params);
}
/**
* @brief Post event.
*
* @param[in,out] client pointer to mqtt client
* @param[in] buf buffer for message
* @param[in] buf_len buffer length
* @param[in] data @see IotDataTemplateEventData
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_DataTemplate_EventPost(void *client, char *buf, int buf_len, IotDataTemplateEventData data)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
return data_template_event_reply_publish(client, buf, buf_len, data);
}
/**
* @brief Reply action message.
*
* @param[in,out] client pointer to mqtt client
* @param[in] buf buffer for message
* @param[in] buf_len buffer length
* @param[in] reply @see IotDataTemplateActionReply
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_DataTemplate_ActionReply(void *client, char *buf, int buf_len, IotDataTemplateActionReply reply)
{
return data_template_action_reply_publish(client, buf, buf_len, reply);
}

View File

@@ -0,0 +1,101 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file data_template_event.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-09-26
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-09-26 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "data_template.h"
/**
* @brief Mqtt message callback for event topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see DataTemplateContext
*/
void data_template_event_message_handler(void *client, const MQTTMessage *message, void *usr_data)
{
DataTemplateContext *data_template_context = (DataTemplateContext *)usr_data;
int rc, code = 0;
UtilsJsonValue method, client_token, value_code;
Log_d("receive event message:%.*s", message->payload_len, message->payload_str);
rc = utils_json_value_get("method", strlen("method"), message->payload_str, message->payload_len, &method);
if (rc) {
return;
}
if (!strncmp(method.value, "event_reply", method.value_len)) {
if (data_template_context->event_callback.method_event_reply_callback) {
rc = utils_json_value_get("clientToken", strlen("clientToken"), message->payload_str, message->payload_len,
&client_token);
if (rc) {
goto error;
}
rc = utils_json_value_get("code", strlen("code"), message->payload_str, message->payload_len, &value_code);
if (rc) {
goto error;
}
rc = utils_json_value_data_get(value_code, UTILS_JSON_VALUE_TYPE_INT32, &code);
if (rc) {
goto error;
}
data_template_context->event_callback.method_event_reply_callback(client_token, code,
data_template_context->usr_data);
}
}
return;
error:
Log_e("invalid format of payload!");
}
/**
* @brief Publish message to event topic.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] data @see IotDataTemplateEventData
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_event_reply_publish(void *client, char *buf, int buf_len, IotDataTemplateEventData data)
{
const char *event_type[] = {
"info", // IOT_DATA_TEMPLATE_EVENT_TYPE_INFO
"alert", // IOT_DATA_TEMPLATE_EVENT_TYPE_ALERT
"fault", // IOT_DATA_TEMPLATE_EVENT_TYPE_FAULT
};
static uint32_t token_num = 0;
int len = HAL_Snprintf(
buf, buf_len,
"{\"method\":\"event_post\",\"clientToken\":\"event-%u\",\"eventId\":\"%s\",\"type\":\"%s\",\"params\":%s}",
token_num++, data.event_id, event_type[data.type], data.params);
return data_template_publish(client, DATA_TEMPLATE_TYPE_EVENT, QOS0, buf, len);
}

View File

@@ -0,0 +1,141 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file data_template_mqtt.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-10-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-10-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "qcloud_iot_data_template.h"
#include "data_template.h"
/**
* @brief Direction(upstream/downstream) for topic.
*
*/
typedef enum {
DATA_TEMPLATE_DIRECTION_UP = 0,
DATA_TEMPLATE_DIRECTION_DOWN,
} DataTemplateDirection;
/**
* @brief Generate topic string.
*
* @param[out] buf buffer for topic name
* @param[in] buf_len buffer length
* @param[in] direction @see DataTemplateDirection
* @param[in] type @see DataTemplateType
* @param[in] product_id product id of device
* @param[in] device_name device name of device
* @return > 0 for length of topic name, others for fail.
*/
static int _data_template_topic_generate(char *buf, int buf_len, DataTemplateDirection direction, DataTemplateType type,
const char *product_id, const char *device_name)
{
const char *topic_method[] = {"property", "event", "action"};
// $thing/down/property/C283SMY3W3/test1
return HAL_Snprintf(buf, buf_len, "$thing/%s/%s/%s/%s", direction ? "down" : "up", topic_method[type],
STRING_PTR_PRINT_SANITY_CHECK(product_id), STRING_PTR_PRINT_SANITY_CHECK(device_name));
}
/**
* @brief Check if topic already subscribed, if not then subscribe.
*
* @param[in,out] client pointer to mqtt client
* @param[in] type @see DataTemplateType
* @param[in] on_message_handler message handle of topic
* @param[in] context @see DataTemplateContext
* @return @see IotReturnCode
*/
int data_template_topic_check_and_sub(void *client, DataTemplateType type, OnMessageHandler on_message_handler,
DataTemplateContext context)
{
char data_template_topic[MAX_SIZE_OF_CLOUD_TOPIC];
_data_template_topic_generate(data_template_topic, MAX_SIZE_OF_CLOUD_TOPIC, DATA_TEMPLATE_DIRECTION_DOWN, type,
IOT_MQTT_GetDeviceInfo(client)->product_id,
IOT_MQTT_GetDeviceInfo(client)->device_name);
int rc = 0;
DataTemplateContext *data_template_context = HAL_Malloc(sizeof(DataTemplateContext));
if (!data_template_context) {
return QCLOUD_ERR_MALLOC;
}
*data_template_context = context;
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
sub_params.on_message_handler = on_message_handler;
sub_params.qos = QOS1;
sub_params.user_data = data_template_context;
sub_params.user_data_free = HAL_Free;
rc = IOT_MQTT_SubscribeSync(client, data_template_topic, &sub_params);
if (rc) {
Log_e("subscribe topic %s failed!", data_template_topic);
HAL_Free(data_template_context);
}
return rc;
}
/**
* @brief Unsubscribe data template topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] type @see DataTemplateType
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_topic_unsubscribe(void *client, DataTemplateType type)
{
char data_template_topic[MAX_SIZE_OF_CLOUD_TOPIC];
_data_template_topic_generate(data_template_topic, MAX_SIZE_OF_CLOUD_TOPIC, DATA_TEMPLATE_DIRECTION_DOWN, type,
IOT_MQTT_GetDeviceInfo(client)->product_id,
IOT_MQTT_GetDeviceInfo(client)->device_name);
return IOT_MQTT_Unsubscribe(client, data_template_topic);
}
/**
* @brief Publish to data template topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] type @see DataTemplateType
* @param[in] qos @see QoS
* @param[in] payload payload of mqtt packet
* @param[in] payload_len payload len
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_publish(void *client, DataTemplateType type, QoS qos, const char *payload, int payload_len)
{
NUMBERIC_SANITY_CHECK(payload_len, QCLOUD_ERR_BUF_TOO_SHORT);
char data_template_topic[MAX_SIZE_OF_CLOUD_TOPIC];
_data_template_topic_generate(data_template_topic, MAX_SIZE_OF_CLOUD_TOPIC, DATA_TEMPLATE_DIRECTION_UP, type,
IOT_MQTT_GetDeviceInfo(client)->product_id,
IOT_MQTT_GetDeviceInfo(client)->device_name);
PublishParams pub_params = DEFAULT_PUB_PARAMS;
pub_params.qos = qos;
pub_params.payload = (void *)payload;
pub_params.payload_len = payload_len;
return IOT_MQTT_Publish(client, data_template_topic, &pub_params);
}

View File

@@ -0,0 +1,214 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file data_template_property.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-08-23
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-08-23 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "qcloud_iot_data_template.h"
#include "data_template.h"
/**
* @brief Down method type.
*
*/
typedef enum {
PROPERTY_DOWN_METHOD_TYPE_CONTROL = 0,
PROPERTY_DOWN_METHOD_TYPE_REPORT_REPLY,
PROPERTY_DOWN_METHOD_TYPE_GET_STATUS_REPLY,
PROPERTY_DOWN_METHOD_TYPE_REPORT_INFO_REPLY,
PROPERTY_DOWN_METHOD_TYPE_CLEAR_CONTROL_REPLY,
} PropertyDownMethodType;
/**
* @brief Parse payload and callback.
*
* @param[in] type @see PropertyDownMethodType
* @param[in] message message from cloud
* @param[in] callback callback for user
* @param[in,out] usr_data user data used in callback
*/
static void _parse_method_payload_and_callback(PropertyDownMethodType type, const MQTTMessage *message,
const PropertyMessageCallback *callback, void *usr_data)
{
int rc = 0, code;
UtilsJsonValue client_token, value_code, params, reported, control;
// get client token
rc = utils_json_value_get("clientToken", strlen("clientToken"), message->payload_str, message->payload_len,
&client_token);
if (rc) {
goto error;
}
// get code
if (PROPERTY_DOWN_METHOD_TYPE_REPORT_REPLY == type || PROPERTY_DOWN_METHOD_TYPE_GET_STATUS_REPLY == type ||
PROPERTY_DOWN_METHOD_TYPE_REPORT_INFO_REPLY == type || PROPERTY_DOWN_METHOD_TYPE_CLEAR_CONTROL_REPLY == type) {
rc = utils_json_value_get("code", strlen("code"), message->payload_str, message->payload_len, &value_code);
if (rc) {
goto error;
}
rc = utils_json_value_data_get(value_code, UTILS_JSON_VALUE_TYPE_INT32, &code);
if (rc) {
goto error;
}
}
// callback
switch (type) {
case PROPERTY_DOWN_METHOD_TYPE_CONTROL:
if (callback->method_control_callback) {
rc = utils_json_value_get("params", strlen("params"), message->payload_str, message->payload_len,
&params);
if (rc) {
goto error;
}
callback->method_control_callback(client_token, params, usr_data);
}
break;
case PROPERTY_DOWN_METHOD_TYPE_REPORT_REPLY:
if (callback->method_report_reply_callback) {
callback->method_report_reply_callback(client_token, code, usr_data);
}
break;
case PROPERTY_DOWN_METHOD_TYPE_GET_STATUS_REPLY:
if (callback->method_get_status_reply_callback) {
reported.value = NULL;
reported.value_len = 0;
control.value = NULL;
control.value_len = 0;
rc = utils_json_value_get("data.reported", strlen("data.reported"), message->payload_str,
message->payload_len, &reported);
rc &= utils_json_value_get("data.control", strlen("data.control"), message->payload_str,
message->payload_len, &control);
if (rc) {
goto error;
}
callback->method_get_status_reply_callback(client_token, code, reported, control, usr_data);
}
break;
case PROPERTY_DOWN_METHOD_TYPE_REPORT_INFO_REPLY:
if (callback->method_report_info_reply_callback) {
callback->method_report_info_reply_callback(client_token, code, usr_data);
}
break;
case PROPERTY_DOWN_METHOD_TYPE_CLEAR_CONTROL_REPLY:
if (callback->method_clear_control_reply_callback) {
callback->method_clear_control_reply_callback(client_token, code, usr_data);
}
break;
default:
break;
}
return;
error:
Log_e("invalid format of payload!");
return;
}
/**
* @brief Mqtt message callback for property topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see DataTemplateContext
*/
void data_template_property_message_handler(void *client, const MQTTMessage *message, void *usr_data)
{
const char *property_down_method_str[] = {
"control", // PROPERTY_DOWN_METHOD_TYPE_CONTROL
"report_reply", // PROPERTY_DOWN_METHOD_TYPE_REPORT_REPLY
"get_status_reply", // PROPERTY_DOWN_METHOD_TYPE_GET_STATUS_REPLY
"report_info_reply", // PROPERTY_DOWN_METHOD_TYPE_REPORT_INFO_REPLY
"clear_control_reply", // PROPERTY_DOWN_METHOD_TYPE_CLEAR_CONTROL_REPLY
};
int rc, i = 0;
DataTemplateContext *data_template_context = (DataTemplateContext *)usr_data;
UtilsJsonValue method;
Log_d("receive property message:%.*s", message->payload_len, message->payload_str);
rc = utils_json_value_get("method", strlen("method"), message->payload_str, message->payload_len, &method);
if (rc) {
return;
}
for (i = PROPERTY_DOWN_METHOD_TYPE_CONTROL; i <= PROPERTY_DOWN_METHOD_TYPE_CLEAR_CONTROL_REPLY; i++) {
if (!strncmp(method.value, property_down_method_str[i], method.value_len)) {
_parse_method_payload_and_callback(i, message, &data_template_context->property_callback,
data_template_context->usr_data);
}
}
}
/**
* @brief Publish message to property topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] publish_type @see PropertyUpMethodType
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] params @see PropertyPublishParams
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int data_template_property_publish(void *client, PropertyUpMethodType publish_type, char *buf, int buf_len,
PropertyPublishParams params)
{
static uint32_t token_num = 0;
int len = 0;
switch (publish_type) {
case PROPERTY_UP_METHOD_TYPE_REPORT:
len = HAL_Snprintf(buf, buf_len, "{\"method\":\"report\",\"clientToken\":\"property-%u\",\"params\":%s}",
token_num++, params.json);
break;
case PROPERTY_UP_METHOD_TYPE_REPORT_INFO:
len =
HAL_Snprintf(buf, buf_len, "{\"method\":\"report_info\",\"clientToken\":\"property-%u\",\"params\":%s}",
token_num++, params.json);
break;
case PROPERTY_UP_METHOD_TYPE_GET_STATUS:
len =
HAL_Snprintf(buf, buf_len, "{\"method\":\"get_status\",\"clientToken\":\"property-%u\"}", token_num++);
break;
case PROPERTY_UP_METHOD_TYPE_CLEAR_CONTROL:
len = HAL_Snprintf(buf, buf_len, "{\"method\":\"clear_control\",\"clientToken\":\"property-%u\"}",
token_num++);
break;
case PROPERTY_UP_METHOD_TYPE_CONTROL_REPLY:
len = HAL_Snprintf(buf, buf_len, "{\"method\":\"control_reply\",\"clientToken\":\"%.*s\",\"code\":%d}",
params.control_reply.client_token.value_len, params.control_reply.client_token.value,
params.control_reply.code);
break;
default:
return QCLOUD_ERR_FAILURE;
}
return data_template_publish(client, DATA_TEMPLATE_TYPE_PROPERTY, QOS0, buf, len);
}

View File

@@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
DerivePointerAlignment: true
PointerAlignment: Left
SortIncludes: true
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash

View File

@@ -0,0 +1,156 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file test_data_template.cc
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-09-29
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-09-29 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <iostream>
#include <string>
#include "gtest/gtest.h"
#include "mqtt_client_test.h"
#include "qcloud_iot_common.h"
#include "qcloud_iot_explorer.h"
namespace mqtt_client_unittest {
static void _method_control_callback(UtilsJsonValue client_token, UtilsJsonValue params, void *usr_data) {
char buf[256];
Log_i("recv msg[%.*s]: params=%.*s", client_token.value_len, client_token.value, params.value_len, params.value);
IOT_DataTemplate_PropertyControlReply(usr_data, buf, sizeof(buf), 0, client_token);
}
static void _method_report_reply_callback(UtilsJsonValue client_token, int code, void *usr_data) {
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
}
static void _method_get_status_reply_callback(UtilsJsonValue client_token, int code, UtilsJsonValue reported,
UtilsJsonValue control, void *usr_data) {
char buf[256];
Log_i("recv msg[%.*s]: code=%d|reported=%.*s|control=%.*s", client_token.value_len, client_token.value, code,
reported.value_len, STRING_PTR_PRINT_SANITY_CHECK(reported.value), control.value_len,
STRING_PTR_PRINT_SANITY_CHECK(control.value));
IOT_DataTemplate_PropertyClearControl(usr_data, buf, sizeof(buf));
}
static void _method_report_info_reply_callback(UtilsJsonValue client_token, int code, void *usr_data) {
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
}
static void _method_clear_control_reply_callback(UtilsJsonValue client_token, int code, void *usr_data) {
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
}
static void _method_event_reply_callback(UtilsJsonValue client_token, int code, void *usr_data) {
Log_i("recv msg[%.*s]: code=%d", client_token.value_len, client_token.value, code);
}
static void _method_action_callback(UtilsJsonValue client_token, UtilsJsonValue action_id, UtilsJsonValue params,
void *usr_data) {
char buf[256];
Log_i("recv msg[%.*s]: action_id=%.*s|params=%.*s", client_token.value_len, client_token.value, action_id.value_len,
action_id.value, params.value_len, params.value);
IotDataTemplateActionReply reply = {
.client_token = client_token,
.code = 0,
.response = "{\"err_code\":0}",
};
ASSERT_GE(IOT_DataTemplate_ActionReply(usr_data, buf, sizeof(buf), reply), 0);
}
/**
* @brief Test data template property.
*
*/
TEST_F(MqttClientTest, data_template_property) {
char buf[1024];
IotDataTemplateCallback callback = {
.property_callback = {.method_control_callback = _method_control_callback,
.method_report_reply_callback = _method_report_reply_callback,
.method_get_status_reply_callback = _method_get_status_reply_callback,
.method_report_info_reply_callback = _method_report_info_reply_callback,
.method_clear_control_reply_callback = _method_clear_control_reply_callback},
.event_callback = {.method_event_reply_callback = NULL},
.action_callback = {.method_action_callback = NULL},
};
const char *report_info =
"{\"module_hardinfo\":\"ESP8266\",\"module_softinfo\":\"V1.0\", \"fw_ver\":\"4.0.0\", "
"\"imei\":\"11-22-33-44\",\"lat\":\"22.546015\",\"lon\":\"113.941125\",\"mac\":\"11:22:33:44:55:66\",\"device_"
"label\":{\"append_"
"info\":\"your self defined info\"}}";
ASSERT_EQ(IOT_DataTemplate_Init(client, callback, client), 0);
ASSERT_EQ(IOT_DataTemplate_PropertyReportInfo(client, buf, sizeof(buf), report_info), 0);
ASSERT_EQ(IOT_DataTemplate_PropertyGetStatus(client, buf, sizeof(buf)), 0);
IOT_DataTemplate_Deinit(client);
}
/**
* @brief Test data template event.
*
*/
TEST_F(MqttClientTest, data_template_event) {
char buf[1024];
IotDataTemplateCallback callback = {
.property_callback = {0},
.event_callback = {.method_event_reply_callback = _method_event_reply_callback},
.action_callback = {.method_action_callback = _method_action_callback},
};
const char *report_property = "{\"power_switch\":0}";
IotDataTemplateEventData event_data = {
.event_id = "status_report",
.type = IOT_DATA_TEMPLATE_EVENT_TYPE_INFO,
.params = "{\"status\":0,\"message\":\"ok\"}",
};
ASSERT_EQ(IOT_DataTemplate_Init(client, callback, client), 0);
ASSERT_GE(IOT_DataTemplate_EventPost(client, buf, sizeof(buf), event_data), 0);
IOT_DataTemplate_Deinit(client);
}
/**
* @brief Test data template action.
*
*/
TEST_F(MqttClientTest, data_template_action) {
char buf[1024];
IotDataTemplateCallback callback = {
.property_callback = {0},
.event_callback = {.method_event_reply_callback = NULL},
.action_callback = {.method_action_callback = _method_action_callback},
};
ASSERT_EQ(IOT_DataTemplate_Init(client, callback, client), 0);
IOT_DataTemplate_Deinit(client);
}
} // namespace mqtt_client_unittest

View File

@@ -0,0 +1,17 @@
file(GLOB src_service_mqtt
${CMAKE_CURRENT_SOURCE_DIR}/src/*.c
${CMAKE_CURRENT_SOURCE_DIR}/src/file_manage/*.c
)
set(inc_service_mqtt ${CMAKE_CURRENT_SOURCE_DIR}/inc/)
set(src_services ${src_services} ${src_service_mqtt} PARENT_SCOPE)
set(inc_services ${inc_services} ${inc_service_mqtt} PARENT_SCOPE)
file(GLOB src_file_manage_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/file_manage_sample.c)
add_executable(file_manage_sample ${src_file_manage_sample})
target_link_libraries(file_manage_sample ${libsdk})
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,99 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file service_mqtt.h
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2022-01-11
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2022-01-11 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#ifndef IOT_HUB_DEVICE_C_SDK_SERVICES_EXPLORER_SERVICE_MQTT_INC_SERVICE_MQTT_H_
#define IOT_HUB_DEVICE_C_SDK_SERVICES_EXPLORER_SERVICE_MQTT_INC_SERVICE_MQTT_H_
#include "qcloud_iot_common.h"
#include "utils_list.h"
/**
* @brief Service type, only file manage supportted now.
*
*/
typedef enum {
SERVICE_TYPE_FILE_MANAGE = 0,
} ServiceType;
/**
* @brief Register to server list.
*
*/
typedef struct {
ServiceType type;
const char ** method_list;
int method_num;
OnMessageHandler message_handle;
void * usr_data;
void (*user_data_free)(void *);
} ServiceRegisterParams;
/**
* @brief Subscribe service mqtt.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int service_mqtt_init(void *client);
/**
* @brief If no service in the service list, then unsubscribe.
*
* @param[in,out] client pointer to mqtt client
*/
void service_mqtt_deinit(void *client);
/**
* @brief Publish to service mqtt topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] qos @see QoS
* @param[in] payload payload of mqtt packet
* @param[in] payload_len payload len
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int service_mqtt_publish(void *client, QoS qos, const char *payload, int payload_len);
/**
* @brief Register server handler to server list.
*
* @param[in,out] client pointer to mqtt client
* @param[in] params @see ServiceRegisterParams
* @return @see IotReturnCode
*/
int service_mqtt_service_register(void *client, const ServiceRegisterParams *params);
/**
* @brief Unregister server handler from server list.
*
* @param[in,out] client pointer to mqtt client
* @param[in] type @see ServiceType
*/
void service_mqtt_service_unregister(void *client, ServiceType type);
#endif // IOT_HUB_DEVICE_C_SDK_SERVICES_EXPLORER_SERVICE_MQTT_INC_SERVICE_MQTT_H_

View File

@@ -0,0 +1,349 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file broadcast_sample.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_explorer.h"
#include "utils_log.h"
/**
* @brief MQTT event callback, @see MQTTEventHandleFun
*
* @param[in] client pointer to mqtt client
* @param[in] handle_context context
* @param[in] msg msg
*/
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
{
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
uintptr_t packet_id = (uintptr_t)msg->msg;
switch (msg->event_type) {
case MQTT_EVENT_UNDEF:
Log_i("undefined event occur.");
break;
case MQTT_EVENT_DISCONNECT:
Log_i("MQTT disconnect.");
break;
case MQTT_EVENT_RECONNECT:
Log_i("MQTT reconnect.");
break;
case MQTT_EVENT_PUBLISH_RECEIVED:
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
break;
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_NACK:
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_NACK:
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_SUCCESS:
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_TIMEOUT:
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_NACK:
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
break;
default:
Log_i("Should NOT arrive here.");
break;
}
}
/**
* @brief Setup MQTT construct parameters.
*
* @param[in,out] initParams @see MQTTInitParams
* @param[in] device_info @see DeviceInfo
*/
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
{
init_params->device_info = device_info;
init_params->command_timeout = QCLOUD_IOT_MQTT_COMMAND_TIMEOUT;
init_params->keep_alive_interval_ms = QCLOUD_IOT_MQTT_KEEP_ALIVE_INTERNAL;
init_params->auto_connect_enable = 1;
init_params->event_handle.h_fp = _mqtt_event_handler;
init_params->event_handle.context = NULL;
}
// ----------------------------------------------------------------------------
// file list function
// ----------------------------------------------------------------------------
static IotFileManageFileInfo sg_file_list[5];
static void _file_info_init(IotFileManageFileInfo *file_info, UtilsJsonValue file_name, UtilsJsonValue file_type,
UtilsJsonValue version)
{
strncpy(file_info->file_name, file_name.value, file_name.value_len);
strncpy(file_info->file_version, version.value, version.value_len);
file_info->file_type = IOT_FileManage_GetFileType(file_type.value, file_type.value_len);
}
static void _add_file_info_to_list(IotFileManageFileInfo *file_info)
{
int i_free = -1;
for (int i = 0; i < sizeof(sg_file_list) / sizeof(sg_file_list[0]); i++) {
if (!strcmp(sg_file_list[i].file_name, file_info->file_name)) {
memcpy(sg_file_list + i, file_info, sizeof(IotFileManageFileInfo));
return;
}
if (sg_file_list[i].file_name[0] == '\0' && i_free == -1) {
i_free = i;
}
}
if (i_free != -1) {
memcpy(sg_file_list + i_free, file_info, sizeof(IotFileManageFileInfo));
}
}
static void _del_file_info_to_list(IotFileManageFileInfo *file_info)
{
for (int i = 0; i < sizeof(sg_file_list) / sizeof(sg_file_list[0]); i++) {
if (!strcmp(sg_file_list[i].file_name, file_info->file_name)) {
sg_file_list[i].file_name[0] = '\0';
}
}
}
// ----------------------------------------------------------------------------
// file function
// ----------------------------------------------------------------------------
static int _file_download(void *client, char *buf, int buf_len, UtilsJsonValue url, UtilsJsonValue md5sum,
uint32_t file_size, IotFileManageFileInfo *file_info)
{
Log_i("downloading file:url=%.*s|md5sum=%.*s|file_size=%u", url.value_len, url.value, md5sum.value_len,
md5sum.value, file_size);
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_DOWNLOADING, 0, file_info->file_name,
file_info->file_version);
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_DOWNLOADING, 50, file_info->file_name,
file_info->file_version);
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_DOWNLOADING, 100, file_info->file_name,
file_info->file_version);
return 0;
}
static int _file_upgrade(void *client, char *buf, int buf_len, IotFileManageFileInfo *file_info)
{
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_BEGIN, 0, file_info->file_name,
file_info->file_version);
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_SUCCESS, 0, file_info->file_name,
file_info->file_version);
_add_file_info_to_list(file_info);
return 0;
}
static int _file_del(void *client, char *buf, int buf_len, IotFileManageFileInfo *file_info)
{
_del_file_info_to_list(file_info);
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_DEL_SUCCESS, 0, file_info->file_name,
file_info->file_version);
return 0;
}
// ----------------------------------------------------------------------------
// OTA callback
// ----------------------------------------------------------------------------
static void _file_manage_update_file_callback(UtilsJsonValue file_name, UtilsJsonValue file_type,
UtilsJsonValue version, UtilsJsonValue url, UtilsJsonValue md5sum,
uint32_t file_size, void *usr_data)
{
IotFileManageFileInfo file_info = {0};
char buf[256];
int buf_len = sizeof(buf);
Log_i("recv file: file_name=%.*s|type=%.*s|version=%.*s|url=%.*s|md5sum=%.*s|file_size=%u", file_name.value_len,
file_name.value, file_type.value_len, file_type.value, version.value_len, version.value, url.value_len,
url.value, md5sum.value_len, md5sum.value, file_size);
_file_info_init(&file_info, file_name, file_type, version);
int rc = _file_download(usr_data, buf, buf_len, url, md5sum, file_size, &file_info);
if (rc) {
IOT_FileManage_Report(usr_data, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_FAIL, 0, file_info.file_name,
file_info.file_version);
return;
}
_file_upgrade(usr_data, buf, buf_len, &file_info);
IOT_FileManage_ReportFileList(usr_data, buf, buf_len, sg_file_list, sizeof(sg_file_list) / sizeof(sg_file_list[0]));
}
static void _file_manage_del_file_callback(UtilsJsonValue file_name, UtilsJsonValue file_type, UtilsJsonValue version,
void *usr_data)
{
char buf[256];
int buf_len = sizeof(buf);
IotFileManageFileInfo file_info = {0};
_file_info_init(&file_info, file_name, file_type, version);
_file_del(usr_data, buf, buf_len, &file_info);
IOT_FileManage_ReportFileList(usr_data, buf, buf_len, sg_file_list, sizeof(sg_file_list) / sizeof(sg_file_list[0]));
}
static void _file_manage_report_file_version_reponse_callback(UtilsJsonValue file_list, int result_code, void *usr_data)
{
Log_i("recv file_list=%.*s|result_code=%d", file_list.value_len, file_list.value, result_code);
}
static void _file_manage_request_file_url_response_callback(UtilsJsonValue url, UtilsJsonValue file_token,
int result_code, void *usr_data)
{
Log_i("recv response: url=%.*s|file_token=%.*s|result_code=%d", url.value_len, url.value, file_token.value_len,
file_token.value, result_code);
}
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc;
char buf[1024];
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
DeviceInfo device_info;
rc = HAL_GetDevInfo((void *)&device_info);
if (rc) {
Log_e("get device info failed: %d", rc);
return rc;
}
// init connection
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
_setup_connect_init_params(&init_params, &device_info);
// create MQTT client and connect with server
void *client = IOT_MQTT_Construct(&init_params);
if (client) {
Log_i("Cloud Device Construct Success");
} else {
Log_e("MQTT Construct failed!");
goto exit;
}
IotFileManageCallback file_manage_callback = {
.update_file_callback = _file_manage_update_file_callback,
.del_file_callback = _file_manage_del_file_callback,
.report_file_version_reponse_callback = _file_manage_report_file_version_reponse_callback,
.request_file_url_response_callback = _file_manage_request_file_url_response_callback,
};
rc = IOT_FileManage_Init(client, file_manage_callback, client);
if (rc) {
Log_e("OTA init failed!, rc=%d", rc);
goto exit;
}
rc = IOT_FileManage_ReportFileList(client, buf, sizeof(buf), sg_file_list,
sizeof(sg_file_list) / sizeof(sg_file_list[0]));
if (rc) {
Log_e("OTA report version failed!, rc=%d", rc);
goto exit;
}
do {
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
switch (rc) {
case QCLOUD_RET_SUCCESS:
break;
case QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT:
continue;
case QCLOUD_RET_MQTT_RECONNECTED:
break;
default:
Log_e("Exit loop caused of errCode:%d", rc);
goto exit;
}
} while (!sg_main_exit);
exit:
IOT_FileManage_Deinit(client);
rc = IOT_MQTT_Destroy(&client);
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,435 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file file_manage.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2022-01-11
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2022-01-11 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "qcloud_iot_file_manage.h"
#include "service_mqtt.h"
/**
* @brief Context of file manage, callback and user data.
*
*/
typedef struct {
IotFileManageCallback callback;
void *usr_data;
} FileManageContext;
/**
* @brief File manage down stream message type.
*
*/
typedef enum {
FILE_MANAGE_DOWN_MESSAGE_TYPE_UPDATE = 0,
FILE_MANAGE_DOWN_MESSAGE_TYPE_DEL,
FILE_MANAGE_DOWN_MESSAGE_TYPE_REPORT_VERSION_RESPONSE,
FILE_MANAGE_DOWN_MESSAGE_TYPE_POST_REQUEST_RESPONSE,
} FileManageDownMessageType;
/**
* @brief Method string of down stream message. Order @see FileManageDownMessageType.
*
*/
static const char *sg_file_manage_method_str[] = {
"update_resource", // FILE_MANAGE_DOWN_MESSAGE_TYPE_UPDATE
"del_resource", // FILE_MANAGE_DOWN_MESSAGE_TYPE_DEL
"report_version_rsp", // FILE_MANAGE_DOWN_MESSAGE_TYPE_REPORT_VERSION_RESPONSE
"request_url_resp", // FILE_MANAGE_DOWN_MESSAGE_TYPE_POST_REQUEST_RESPONSE
};
/**
* @brief Method string of down stream message. Order @see IotFileManageFileType.
*
*/
static const char *sg_file_manage_file_type_str[] = {
"FILE", // IOT_FILE_MANAGE_FILE_TYPE_FILE
"AUDIO", // IOT_FILE_MANAGE_FILE_TYPE_AUDIO
"VOICE", // IOT_FILE_MANAGE_FILE_TYPE_VOICE
"VIDEO", // IOT_FILE_MANAGE_FILE_TYPE_VIDEO
};
/**
* @brief Handle file mange down stream message.
*
* @param[in] type @see FileManageDownMessageType
* @param[in] message @see MQTTMessage
* @param[in] callback @see IotFileManageCallback
* @param[in,out] usr_data user define in IOT_FileManage_Init
*/
static void _parse_update_payload_and_callback(FileManageDownMessageType type, const MQTTMessage *message,
const IotFileManageCallback *callback, void *usr_data)
{
int rc, result_code;
uint32_t file_size = 0;
UtilsJsonValue file_name, file_type, version, md5sum, url, value_result_code, file_token, file_list = {0};
// callback
switch (type) {
case FILE_MANAGE_DOWN_MESSAGE_TYPE_UPDATE:
if (!callback->update_file_callback) {
return;
}
rc = utils_json_value_get("resource_name", sizeof("resource_name") - 1, message->payload_str,
message->payload_len, &file_name);
rc |= utils_json_value_get("resource_type", sizeof("resource_type") - 1, message->payload_str,
message->payload_len, &file_type);
rc |= utils_json_value_get("version", sizeof("version") - 1, message->payload_str, message->payload_len,
&version);
rc |= utils_json_value_get("url", sizeof("url") - 1, message->payload_str, message->payload_len, &url);
rc |= utils_json_value_get("md5sum", sizeof("md5sum") - 1, message->payload_str, message->payload_len,
&md5sum);
rc |= utils_json_get_uint32("file_size", sizeof("file_size") - 1, message->payload_str,
message->payload_len, &file_size);
if (rc) {
goto error;
}
callback->update_file_callback(file_name, file_type, version, url, md5sum, file_size, usr_data);
break;
case FILE_MANAGE_DOWN_MESSAGE_TYPE_DEL:
if (!callback->del_file_callback) {
return;
}
rc = utils_json_value_get("resource_name", sizeof("resource_name") - 1, message->payload_str,
message->payload_len, &file_name);
rc |= utils_json_value_get("resource_type", sizeof("resource_type") - 1, message->payload_str,
message->payload_len, &file_type);
rc |= utils_json_value_get("version", sizeof("version") - 1, message->payload_str, message->payload_len,
&version);
if (rc) {
goto error;
}
callback->del_file_callback(file_name, file_type, version, usr_data);
break;
case FILE_MANAGE_DOWN_MESSAGE_TYPE_REPORT_VERSION_RESPONSE:
if (!callback->report_file_version_reponse_callback) {
return;
}
// maybe no resource
utils_json_value_get("resource_list", sizeof("resource_list") - 1, message->payload_str,
message->payload_len, &file_list);
rc = utils_json_value_get("result_code", sizeof("result_code") - 1, message->payload_str,
message->payload_len, &value_result_code);
rc |= utils_json_value_data_get(value_result_code, UTILS_JSON_VALUE_TYPE_INT32, &result_code);
if (rc) {
goto error;
}
callback->report_file_version_reponse_callback(file_list, result_code, usr_data);
break;
case FILE_MANAGE_DOWN_MESSAGE_TYPE_POST_REQUEST_RESPONSE:
if (!callback->request_file_url_response_callback) {
return;
}
rc = utils_json_value_get("resource_url", sizeof("resource_url") - 1, message->payload_str,
message->payload_len, &url);
rc |= utils_json_value_get("resource_token", sizeof("resource_token") - 1, message->payload_str,
message->payload_len, &file_token);
rc |= utils_json_value_get("result_code", sizeof("result_code") - 1, message->payload_str,
message->payload_len, &value_result_code);
rc |= utils_json_value_data_get(value_result_code, UTILS_JSON_VALUE_TYPE_INT32, &result_code);
if (rc) {
goto error;
}
callback->request_file_url_response_callback(file_list, file_token, result_code, usr_data);
break;
default:
break;
}
return;
error:
Log_e("invalid format of payload!");
return;
}
/**
* @brief Mqtt message callback for file manage.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message message from topic
* @param[in,out] usr_data pointer to @see FileManageContext
*/
static void _file_manage_message_callback(void *client, const MQTTMessage *message, void *usr_data)
{
const char *file_manage_method_str[] = {
"update_resource", // FILE_MANAGE_DOWN_MESSAGE_TYPE_UPDATE
"del_resource", // FILE_MANAGE_DOWN_MESSAGE_TYPE_DEL
"report_version_rsp", // FILE_MANAGE_DOWN_MESSAGE_TYPE_REPORT_VERSION_RESPONSE
"request_url_resp", // FILE_MANAGE_DOWN_MESSAGE_TYPE_POST_REQUEST_RESPONSE
};
int rc, i = 0;
FileManageContext *file_manage_context = (FileManageContext *)usr_data;
UtilsJsonValue method;
Log_d("receive file manage message:%.*s", message->payload_len, message->payload_str);
rc = utils_json_value_get("method", sizeof("method") - 1, message->payload_str, message->payload_len, &method);
if (rc) {
Log_e("invalid file manage message!");
return;
}
for (i = FILE_MANAGE_DOWN_MESSAGE_TYPE_UPDATE; i <= FILE_MANAGE_DOWN_MESSAGE_TYPE_POST_REQUEST_RESPONSE; i++) {
if (!strncmp(method.value, file_manage_method_str[i], method.value_len)) {
Log_d("callback file manage message!");
_parse_update_payload_and_callback(i, message, &file_manage_context->callback,
file_manage_context->usr_data);
}
}
}
/**
* @brief File manage init, register handler to server list.
*
* @param[in,out] client pointer to mqtt client
* @param[in] callback @see IotFileManageCallback
* @param[in] usr_data usr data used in callback
* @return 0 for success, or err code (<0) @see IotReturnCode
*/
int IOT_FileManage_Init(void *client, IotFileManageCallback callback, void *usr_data)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
int rc = service_mqtt_init(client);
if (rc) {
return rc;
}
FileManageContext *file_manage_context = (FileManageContext *)HAL_Malloc(sizeof(FileManageContext));
if (!file_manage_context) {
return QCLOUD_ERR_MALLOC;
}
file_manage_context->callback = callback;
file_manage_context->usr_data = usr_data;
ServiceRegisterParams params = {
.type = SERVICE_TYPE_FILE_MANAGE,
.method_list = sg_file_manage_method_str,
.method_num = sizeof(sg_file_manage_method_str) / sizeof(sg_file_manage_method_str[0]),
.message_handle = _file_manage_message_callback,
.usr_data = file_manage_context,
.user_data_free = HAL_Free,
};
rc = service_mqtt_service_register(client, &params);
if (rc) {
HAL_Free(file_manage_context);
}
return rc;
}
/**
* @brief File manage deinit, unregister handler from server list.
*
* @param[in,out] client pointer to mqtt client
*/
void IOT_FileManage_Deinit(void *client)
{
POINTER_SANITY_CHECK_RTN(client);
service_mqtt_service_unregister(client, SERVICE_TYPE_FILE_MANAGE);
service_mqtt_deinit(client);
}
/**
* @brief Report file manage message.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] report_type @see IotFileManageReportType
* @param[in] progress progress using in IOT_FILE_MANAGE_REPORT_TYPE_DOWNLOADING
* @param[in] file_name_or_token token using in post event;file name using in other event
* @param[in] version file version
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_FileManage_Report(void *client, char *buf, int buf_len, IotFileManageReportType report_type, int progress,
const char *file_name_or_token, const char *version)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
NUMBERIC_SANITY_CHECK(buf_len, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(file_name_or_token, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(version, QCLOUD_ERR_INVAL);
/**
* @brief order @see IotOTAReportType
*
*/
const char *state_string[] = {
"downloading", "burning", "done", "fail", "fail", "fail", "fail",
"fail", "fail", "done", "fail", "done", "fail",
};
int result_code[] = {0, 0, 0, -1, -2, -3, -4, -5, -6, 0, -7, 0, -8};
const char *result_msg[] = {
"",
"",
"",
"timeout",
"file not exit",
"auth fail",
"md5 not match",
"upgrade fail",
"space not enough",
"",
"del fail",
"",
"post fail",
};
int len;
switch (report_type) {
case IOT_FILE_MANAGE_REPORT_TYPE_DOWNLOADING:
len = HAL_Snprintf(
buf, buf_len,
"{\"method\":\"report_progress\",\"report\":{\"progress\":{\"resource_name\":\"%s\",\"state\":\"%s\","
"\"percent\":\"%d\",\"result_code\":\"%d\",\"result_msg\":\"\"},\"version\":\"%s\"}}",
file_name_or_token, state_string[report_type], progress, result_code[report_type], version);
break;
case IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_BEGIN:
case IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_SUCCESS:
case IOT_FILE_MANAGE_REPORT_TYPE_DOWNLOAD_TIMEOUT:
case IOT_FILE_MANAGE_REPORT_TYPE_FILE_NOT_EXIST:
case IOT_FILE_MANAGE_REPORT_TYPE_AUTH_FAIL:
case IOT_FILE_MANAGE_REPORT_TYPE_MD5_NOT_MATCH:
case IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_FAIL:
case IOT_FILE_MANAGE_REPORT_TYPE_SPACE_NOT_ENOUGH:
len = HAL_Snprintf(buf, buf_len,
"{\"method\":\"report_result\",\"report\":{\"progress\":{\"resource_name\":\"%s\","
"\"state\":\"%s\",\"result_code\":\"%d\",\"result_msg\":\"%s\"},\"version\":\"%s\"}}",
file_name_or_token, state_string[report_type], result_code[report_type],
result_msg[report_type], version);
break;
case IOT_FILE_MANAGE_REPORT_TYPE_DEL_SUCCESS:
case IOT_FILE_MANAGE_REPORT_TYPE_DEL_FAIL:
len = HAL_Snprintf(buf, buf_len,
"{\"method\":\"del_result\",\"report\":{\"progress\":{\"resource_name\":\"%s\","
"\"state\":\"%s\",\"result_code\":\"%d\", \"result_msg\":\"%s\"}, \"version\":\"%s\"}}",
file_name_or_token, state_string[report_type], result_code[report_type],
result_msg[report_type], version);
break;
case IOT_FILE_MANAGE_REPORT_TYPE_POST_SUCCESS:
case IOT_FILE_MANAGE_REPORT_TYPE_POST_FAIL:
len = HAL_Snprintf(buf, buf_len,
"{\"method\":\"report_post_result\",\"report\":{\"progress\":{\"resource_token\":\"%s\","
"\"state\":\"%s\",\"result_code\":\"%d\", \"result_msg\":\"%s\"}}}",
file_name_or_token, state_string[report_type], result_code[report_type],
result_msg[report_type]);
break;
default:
return QCLOUD_ERR_INVAL;
}
return service_mqtt_publish(client, QOS0, buf, len);
}
/**
* @brief Report file list to server mqtt topic.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] file_list file list of @see IotFileManageFileInfo; file name which is "\0" means invalid.
* @param[in] max_num max num of file list.
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_FileManage_ReportFileList(void *client, char *buf, int buf_len, const IotFileManageFileInfo file_list[],
int max_num)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
NUMBERIC_SANITY_CHECK(buf_len, QCLOUD_ERR_INVAL);
int len = HAL_Snprintf(buf, buf_len, "{\"method\":\"report_version\",\"report\":{\"resource_list\":[");
for (int i = 0; i < max_num; i++) {
if (file_list[i].file_type == IOT_FILE_MANAGE_FILE_TYPE_UNKOWN) {
continue;
}
if (file_list[i].file_name[0] != '\0') {
len += HAL_Snprintf(buf + len, buf_len - len,
"{\"resource_name\":\"%s\",\"version\":\"%s\",\"resource_type\":\"%s\"},",
file_list[i].file_name, file_list[i].file_version,
sg_file_manage_file_type_str[file_list[i].file_type]);
}
}
if (buf[len - 1] != '[') {
len--; // remove the last ','
}
len += HAL_Snprintf(buf + len, buf_len - len, "]}}");
return service_mqtt_publish(client, QOS0, buf, len);
}
/**
* @brief Request url to upload.
*
* @param[in,out] client pointer to mqtt client
* @param[out] buf publish message buffer
* @param[in] buf_len buffer len
* @param[in] file_info file to upload, @see IotFileManageFileInfo
* @param[in] request_id user defined, to keep unique.
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int IOT_FileManage_PostRequest(void *client, char *buf, int buf_len, const IotFileManageFileInfo *file_info,
int request_id)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(buf, QCLOUD_ERR_INVAL);
NUMBERIC_SANITY_CHECK(buf_len, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(file_info, QCLOUD_ERR_INVAL);
NUMBERIC_SANITY_CHECK(file_info->file_type, QCLOUD_ERR_INVAL);
int len = HAL_Snprintf(buf, buf_len,
"{\"method\":\"request_url\",\"request_id\":\"%d\","
"\"report\":{\"resource_name\":\"%s\",\"version\":\"%s\",\"resource_type\":\"%s\"}}",
request_id, file_info->file_name, file_info->file_version,
sg_file_manage_file_type_str[file_info->file_type]);
return service_mqtt_publish(client, QOS0, buf, len);
}
/**
* @brief Get file type according to string. @see sg_file_manage_file_type_str.
*
* @param[in] file_type file type string
* @param[in] len string length
* @return @see IotFileManageFileType
*/
IotFileManageFileType IOT_FileManage_GetFileType(const char *file_type, int len)
{
for (int i = 0; i < sizeof(sg_file_manage_file_type_str) / sizeof(sg_file_manage_file_type_str[0]); i++) {
if (len != strlen(sg_file_manage_file_type_str[i])) {
continue;
}
if (!strncmp(file_type, sg_file_manage_file_type_str[i], len)) {
return i;
}
}
return IOT_FILE_MANAGE_FILE_TYPE_UNKOWN;
}

View File

@@ -0,0 +1,364 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file sevice_mqtt.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2022-01-11
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2022-01-11 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "service_mqtt.h"
/**
* @brief Direction(upstream/downstream) for topic.
*
*/
typedef enum {
SERVICE_TOPIC_DIRECTION_UP = 0,
SERVICE_TOPIC_DIRECTION_DOWN,
} ServiceTopicDirection;
/**
* @brief Context of service mqtt, callback and user data.
*
*/
typedef struct {
void *service_list;
void *usr_data;
} ServiceMqttContext;
/**
* @brief Service list conext.
*
*/
typedef struct {
const char *method;
int method_len;
const MQTTMessage *message;
} ServiceListContext;
/**
* @brief Generate topic string.
*
* @param[out] buf buffer for topic name
* @param[in] buf_len buffer length
* @param[in] direction @see ServiceTopicDirection
* @param[in] product_id product id of device
* @param[in] device_name device name of device
* @return > 0 for length of topic name, others for fail.
*/
static int _service_mqtt_topic_generate(char *buf, int buf_len, ServiceTopicDirection direction, const char *product_id,
const char *device_name)
{
return HAL_Snprintf(buf, buf_len, "$thing/%s/service/%s/%s", direction ? "down" : "up",
STRING_PTR_PRINT_SANITY_CHECK(product_id), STRING_PTR_PRINT_SANITY_CHECK(device_name));
}
// ----------------------------------------------------------------------------
// server mqtt context
// ----------------------------------------------------------------------------
/**
* @brief Malloc service context and service list.
*
* @return @see ServiceMqttContext
*/
static ServiceMqttContext *_service_mqtt_context_malloc(void)
{
ServiceMqttContext *context = (ServiceMqttContext *)HAL_Malloc(sizeof(ServiceMqttContext));
if (!context) {
return NULL;
}
UtilsListFunc func = {
.list_malloc = HAL_Malloc,
.list_free = HAL_Free,
.list_lock_init = HAL_MutexCreate,
.list_lock = HAL_MutexLock,
.list_unlock = HAL_MutexUnlock,
.list_lock_deinit = HAL_MutexDestroy,
};
context->service_list = utils_list_create(func, 10);
if (!context->service_list) {
HAL_Free(context);
context = NULL;
}
return context;
}
/**
* @brief Free service context and service list.
*
* @param usr_data @see ServiceMqttContext
*/
static void _service_mqtt_context_free(void *usr_data)
{
ServiceMqttContext *context = (ServiceMqttContext *)usr_data;
utils_list_destroy(context->service_list);
HAL_Free(context);
}
/**
* @brief Get service mqtt context from client.
*
* @param[in,out] client pointer to mqtt client
* @return @see ServiceMqttContext
*/
static ServiceMqttContext *_service_mqtt_context_get(void *client)
{
char service_mqtt_topic[MAX_SIZE_OF_CLOUD_TOPIC];
_service_mqtt_topic_generate(service_mqtt_topic, MAX_SIZE_OF_CLOUD_TOPIC, SERVICE_TOPIC_DIRECTION_DOWN,
IOT_MQTT_GetDeviceInfo(client)->product_id,
IOT_MQTT_GetDeviceInfo(client)->device_name);
return (ServiceMqttContext *)IOT_MQTT_GetSubUsrData(client, service_mqtt_topic);
}
// ----------------------------------------------------------------------------
// server list
// ----------------------------------------------------------------------------
/**
* @brief Unregister server from server list.
*
* @param[in] list pointer to server list
* @param[in] node pointer tot server list node
* @param[in] val @see ServiceRegisterParams
* @param[in] usr_data @see ServiceType
* @return @see UtilsListResult
*/
static UtilsListResult _server_list_unregister_callback(void *list, void *node, void *val, void *usr_data)
{
ServiceRegisterParams *params = (ServiceRegisterParams *)val;
ServiceType type = *(ServiceType *)usr_data;
if (params->type == type) {
if (params->user_data_free) {
params->user_data_free(params->usr_data);
}
utils_list_remove(list, node);
return LIST_TRAVERSE_BREAK;
}
return LIST_TRAVERSE_CONTINUE;
}
/**
* @brief Callback server handler from server list.
*
* @param[in] list pointer to server list
* @param[in] node pointer tot server list node
* @param[in] val @see ServiceRegisterParams
* @param[in] usr_data @see ServiceListContext
* @return @see UtilsListResult
*/
static UtilsListResult _server_list_process_message_callback(void *list, void *node, void *val, void *usr_data)
{
ServiceRegisterParams *params = (ServiceRegisterParams *)val;
ServiceListContext *context = (ServiceListContext *)usr_data;
for (int i = 0; i < params->method_num; i++) {
int len = strlen(params->method_list[i]);
if (len == context->method_len && !strncmp(context->method, params->method_list[i], len)) {
params->message_handle(usr_data, context->message, params->usr_data);
return LIST_TRAVERSE_BREAK;
}
}
return LIST_TRAVERSE_CONTINUE;
}
// ----------------------------------------------------------------------------
// mqtt
// ----------------------------------------------------------------------------
/**
* @brief Handle server mqtt topic down stream message.
*
* @param[in,out] client pointer to mqtt client
* @param[in] message @see MQTTMessage
* @param[in] usr_data @see ServiceMqttContext
*/
static void _service_mqtt_message_handle(void *client, const MQTTMessage *message, void *usr_data)
{
Log_d("receive service message:%.*s", message->payload_len, message->payload_str);
UtilsJsonValue method;
int rc = utils_json_value_get("method", sizeof("method") - 1, message->payload_str, message->payload_len, &method);
if (rc) {
return;
}
// process list
ServiceListContext server_list_context = {
.method = method.value,
.method_len = method.value_len,
.message = message,
};
void *list = ((ServiceMqttContext *)usr_data)->service_list;
utils_list_process(list, LIST_HEAD, _server_list_process_message_callback, &server_list_context);
}
/**
* @brief Check if topic already subscribed, if not then subscribe.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int _service_mqtt_topic_check_and_sub(void *client)
{
char service_mqtt_topic[MAX_SIZE_OF_CLOUD_TOPIC];
_service_mqtt_topic_generate(service_mqtt_topic, MAX_SIZE_OF_CLOUD_TOPIC, SERVICE_TOPIC_DIRECTION_DOWN,
IOT_MQTT_GetDeviceInfo(client)->product_id,
IOT_MQTT_GetDeviceInfo(client)->device_name);
int rc = 0;
ServiceMqttContext *context = _service_mqtt_context_malloc();
if (!context) {
return QCLOUD_ERR_MALLOC;
}
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
sub_params.on_message_handler = _service_mqtt_message_handle;
sub_params.qos = QOS1;
sub_params.user_data = context;
sub_params.user_data_free = _service_mqtt_context_free;
rc = IOT_MQTT_SubscribeSync(client, service_mqtt_topic, &sub_params);
if (rc) {
_service_mqtt_context_free(context);
Log_e("subscribe topic %s failed!", service_mqtt_topic);
}
return rc;
}
/**
* @brief Unsubscribe service mqtt topic.
*
* @param[in,out] client pointer to mqtt client
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int _service_mqtt_unsubscribe(void *client)
{
char service_mqtt_topic[MAX_SIZE_OF_CLOUD_TOPIC];
_service_mqtt_topic_generate(service_mqtt_topic, MAX_SIZE_OF_CLOUD_TOPIC, SERVICE_TOPIC_DIRECTION_DOWN,
IOT_MQTT_GetDeviceInfo(client)->product_id,
IOT_MQTT_GetDeviceInfo(client)->device_name);
return IOT_MQTT_Unsubscribe(client, service_mqtt_topic);
}
// ----------------------------------------------------------------------------
// api
// ----------------------------------------------------------------------------
/**
* @brief Subscribe service mqtt.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int service_mqtt_init(void *client)
{
return _service_mqtt_topic_check_and_sub(client);
}
/**
* @brief If no service in the service list, then unsubscribe.
*
* @param[in,out] client pointer to mqtt client
*/
void service_mqtt_deinit(void *client)
{
ServiceMqttContext *context = _service_mqtt_context_get(client);
if (!context) {
return;
}
if (!utils_list_len_get(context->service_list)) {
_service_mqtt_unsubscribe(client);
}
}
/**
* @brief Publish to service mqtt topic.
*
* @param[in,out] client pointer to mqtt client
* @param[in] qos @see QoS
* @param[in] payload payload of mqtt packet
* @param[in] payload_len payload len
* @return packet id (>=0) when success, or err code (<0) @see IotReturnCode
*/
int service_mqtt_publish(void *client, QoS qos, const char *payload, int payload_len)
{
char service_mqtt_topic[MAX_SIZE_OF_CLOUD_TOPIC];
_service_mqtt_topic_generate(service_mqtt_topic, MAX_SIZE_OF_CLOUD_TOPIC, SERVICE_TOPIC_DIRECTION_UP,
IOT_MQTT_GetDeviceInfo(client)->product_id,
IOT_MQTT_GetDeviceInfo(client)->device_name);
PublishParams pub_params = DEFAULT_PUB_PARAMS;
pub_params.qos = qos;
pub_params.payload = (void *)payload;
pub_params.payload_len = payload_len;
return IOT_MQTT_Publish(client, service_mqtt_topic, &pub_params);
}
/**
* @brief Register server handler to server list.
*
* @param[in,out] client pointer to mqtt client
* @param[in] params @see ServiceRegisterParams
* @return @see IotReturnCode
*/
int service_mqtt_service_register(void *client, const ServiceRegisterParams *params)
{
ServiceMqttContext *context = _service_mqtt_context_get(client);
if (!context) {
return QCLOUD_ERR_FAILURE;
}
ServiceRegisterParams *register_params = (ServiceRegisterParams *)HAL_Malloc(sizeof(ServiceRegisterParams));
if (!register_params) {
return QCLOUD_ERR_MALLOC;
}
memcpy(register_params, params, sizeof(ServiceRegisterParams));
void *node = utils_list_push(context->service_list, register_params);
if (!node) {
HAL_Free(register_params);
return QCLOUD_ERR_MALLOC;
}
return QCLOUD_RET_SUCCESS;
}
/**
* @brief Unregister server handler from server list.
*
* @param[in,out] client pointer to mqtt client
* @param[in] type @see ServiceType
*/
void service_mqtt_service_unregister(void *client, ServiceType type)
{
ServiceMqttContext *context = _service_mqtt_context_get(client);
if (!context) {
return;
}
utils_list_process(context->service_list, LIST_HEAD, _server_list_unregister_callback, &type);
}

View File

@@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
DerivePointerAlignment: true
PointerAlignment: Left
SortIncludes: true
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash

View File

@@ -0,0 +1,132 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2022 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file test_ota_mqtt.cc
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2022-01-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2022-01-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <iostream>
#include <string>
#include "gtest/gtest.h"
#include "mqtt_client_test.h"
#include "qcloud_iot_explorer.h"
namespace mqtt_client_unittest {
static void _file_manage_update_file_callback(UtilsJsonValue file_name, UtilsJsonValue file_type,
UtilsJsonValue version, UtilsJsonValue url, UtilsJsonValue md5sum,
uint32_t file_size, void *usr_data) {
Log_i("recv update file: file_name=%.*s|type=%.*s|version=%.*s|url=%.*s|md5sum=%.*s|file_size=%u",
file_name.value_len, file_name.value, file_type.value_len, file_type.value, version.value_len, version.value,
url.value_len, url.value, md5sum.value_len, md5sum.value, file_size);
}
static void _file_manage_del_file_callback(UtilsJsonValue file_name, UtilsJsonValue file_type, UtilsJsonValue version,
void *usr_data) {
Log_i("recv del file: file_name=%.*s|type=%.*s|version=%.*s|", file_name.value_len, file_name.value,
file_type.value_len, file_type.value, version.value_len, version.value);
}
static void _file_manage_report_file_version_reponse_callback(UtilsJsonValue file_list, int result_code,
void *usr_data) {
Log_i("recv file version: file_list=%.*s|result_code=%d", file_list, result_code);
}
static void _file_manage_request_file_url_response_callback(UtilsJsonValue url, UtilsJsonValue file_token,
int result_code, void *usr_data) {
Log_i("recv request response: url=%.*s|file_token=%.*s|result_code=%d", url.value_len, url.value,
file_token.value_len, file_token.value, result_code);
}
/**
* @brief Test file manage.
*
*/
TEST_F(MqttClientTest, file_manage) {
char buf[256];
int buf_len = sizeof(buf);
IotFileManageFileInfo file_info = {0};
const char *file_name = "test.txt";
const char *version = "1.0.0";
strncpy(file_info.file_name, file_name, strlen(file_name));
strncpy(file_info.file_version, version, strlen(version));
file_info.file_type = IOT_FILE_MANAGE_FILE_TYPE_FILE;
IotFileManageCallback file_manage_callback = {
.update_file_callback = _file_manage_update_file_callback,
.del_file_callback = _file_manage_del_file_callback,
.report_file_version_reponse_callback = _file_manage_report_file_version_reponse_callback,
.request_file_url_response_callback = _file_manage_request_file_url_response_callback,
};
ASSERT_EQ(IOT_FileManage_Init(client, file_manage_callback, client), 0);
ASSERT_GE(IOT_FileManage_ReportFileList(client, buf, buf_len, NULL, 0), 0);
ASSERT_GE(IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_DOWNLOADING, 0, file_name, version),
0);
ASSERT_GE(
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_BEGIN, 100, file_name, version),
0);
ASSERT_GE(
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_SUCCESS, 0, file_name, version),
0);
ASSERT_GE(
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_DOWNLOAD_TIMEOUT, 0, file_name, version),
0);
ASSERT_GE(
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_FILE_NOT_EXIST, 0, file_name, version),
0);
ASSERT_GE(IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_AUTH_FAIL, 0, file_name, version),
0);
ASSERT_GE(
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_MD5_NOT_MATCH, 0, file_name, version), 0);
ASSERT_GE(
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_UPGRADE_FAIL, 0, file_name, version), 0);
ASSERT_GE(
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_SPACE_NOT_ENOUGH, 0, file_name, version),
0);
ASSERT_GE(IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_DEL_SUCCESS, 0, file_name, version),
0);
ASSERT_GE(IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_DEL_FAIL, 0, file_name, version),
0);
ASSERT_GE(IOT_FileManage_PostRequest(client, buf, buf_len, &file_info, 0), 0);
ASSERT_EQ(IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT), 0);
ASSERT_GE(
IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_POST_SUCCESS, 0, file_name, version), 0);
ASSERT_GE(IOT_FileManage_Report(client, buf, buf_len, IOT_FILE_MANAGE_REPORT_TYPE_POST_FAIL, 0, file_name, version),
0);
ASSERT_EQ(IOT_FileManage_GetFileType("FILE", sizeof("FILE") - 1), IOT_FILE_MANAGE_FILE_TYPE_FILE);
ASSERT_EQ(IOT_FileManage_GetFileType("AUDIO", sizeof("AUDIO") - 1), IOT_FILE_MANAGE_FILE_TYPE_AUDIO);
ASSERT_EQ(IOT_FileManage_GetFileType("VOICE", sizeof("VOICE") - 1), IOT_FILE_MANAGE_FILE_TYPE_VOICE);
ASSERT_EQ(IOT_FileManage_GetFileType("VIDEO", sizeof("VIDEO") - 1), IOT_FILE_MANAGE_FILE_TYPE_VIDEO);
ASSERT_EQ(IOT_FileManage_GetFileType("TEXT", sizeof("TEXT") - 1), IOT_FILE_MANAGE_FILE_TYPE_UNKOWN);
IOT_FileManage_Deinit(client);
}
} // namespace mqtt_client_unittest

View File

@@ -0,0 +1,14 @@
file(GLOB src_broadcast ${CMAKE_CURRENT_SOURCE_DIR}/src/*.c)
set(inc_broadcast ${CMAKE_CURRENT_SOURCE_DIR}/inc)
set(src_services ${src_services} ${src_broadcast} PARENT_SCOPE)
set(inc_services ${inc_services} ${inc_broadcast} PARENT_SCOPE)
file(GLOB src_broadcast_sample ${CMAKE_CURRENT_SOURCE_DIR}/sample/broadcast_sample.c)
add_executable(broadcast_sample ${src_broadcast_sample})
target_link_libraries(broadcast_sample ${libsdk})
if( ${CONFIG_IOT_TEST} STREQUAL "ON")
file(GLOB src_unit_test ${CMAKE_CURRENT_SOURCE_DIR}/test/*.cc)
set(src_test ${src_test} ${src_unit_test} PARENT_SCOPE)
endif()

View File

@@ -0,0 +1,220 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file broadcast_sample.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "qcloud_iot_common.h"
#include "qcloud_iot_hub.h"
#include "utils_log.h"
/**
* @brief MQTT event callback, @see MQTTEventHandleFun
*
* @param[in] client pointer to mqtt client
* @param[in] handle_context context
* @param[in] msg msg
*/
static void _mqtt_event_handler(void *client, void *handle_context, MQTTEventMsg *msg)
{
MQTTMessage *mqtt_message = (MQTTMessage *)msg->msg;
uintptr_t packet_id = (uintptr_t)msg->msg;
switch (msg->event_type) {
case MQTT_EVENT_UNDEF:
Log_i("undefined event occur.");
break;
case MQTT_EVENT_DISCONNECT:
Log_i("MQTT disconnect.");
break;
case MQTT_EVENT_RECONNECT:
Log_i("MQTT reconnect.");
break;
case MQTT_EVENT_PUBLISH_RECEIVED:
Log_i("topic message arrived but without any related handle: topic=%.*s, topic_msg=%.*s",
mqtt_message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(mqtt_message->topic_name),
mqtt_message->payload_len, STRING_PTR_PRINT_SANITY_CHECK((char *)mqtt_message->payload));
break;
case MQTT_EVENT_SUBSCRIBE_SUCCESS:
Log_i("subscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_TIMEOUT:
Log_i("subscribe wait ack timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_SUBSCRIBE_NACK:
Log_i("subscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_SUCCESS:
Log_i("unsubscribe success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_TIMEOUT:
Log_i("unsubscribe timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_UNSUBSCRIBE_NACK:
Log_i("unsubscribe nack, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_SUCCESS:
Log_i("publish success, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_TIMEOUT:
Log_i("publish timeout, packet-id=%u", (unsigned int)packet_id);
break;
case MQTT_EVENT_PUBLISH_NACK:
Log_i("publish nack, packet-id=%u", (unsigned int)packet_id);
break;
default:
Log_i("Should NOT arrive here.");
break;
}
}
/**
* @brief Setup MQTT construct parameters.
*
* @param[in,out] initParams @see MQTTInitParams
* @param[in] device_info @see DeviceInfo
*/
static void _setup_connect_init_params(MQTTInitParams *init_params, DeviceInfo *device_info)
{
init_params->device_info = device_info;
init_params->command_timeout = QCLOUD_IOT_MQTT_COMMAND_TIMEOUT;
init_params->keep_alive_interval_ms = QCLOUD_IOT_MQTT_KEEP_ALIVE_INTERNAL;
init_params->auto_connect_enable = 1;
init_params->event_handle.h_fp = _mqtt_event_handler;
init_params->event_handle.context = NULL;
}
/**
* @brief Callback when MQTT msg arrives @see OnMessageHandler
*
* @param[in, out] client pointer to mqtt client
* @param[in] message publish message from server
* @param[in] usr_data user data of SubscribeParams, @see SubscribeParams
*/
static void _on_broadcast_arrived_callback(void *client, const char *msg, int msg_len, void *usr_data)
{
Log_i("Receive broadcast message:%.*s, usr data:%d", msg_len, STRING_PTR_PRINT_SANITY_CHECK(msg), *(int *)usr_data);
(*(int *)usr_data)++;
}
// ----------------------------------------------------------------------------
// Main
// ----------------------------------------------------------------------------
static int sg_main_exit = 0;
#ifdef __linux__
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
static void _main_exit(int sig)
{
Log_e("demo exit by signal:%d\n", sig);
sg_main_exit = 1;
}
#endif
int main(int argc, char **argv)
{
#ifdef __linux__
signal(SIGINT, _main_exit);
#endif
int rc;
// init log level
LogHandleFunc func = {0};
func.log_malloc = HAL_Malloc;
func.log_free = HAL_Free;
func.log_get_current_time_str = HAL_Timer_Current;
func.log_printf = HAL_Printf;
utils_log_init(func, LOG_LEVEL_DEBUG, 2048);
DeviceInfo device_info;
rc = HAL_GetDevInfo((void *)&device_info);
if (rc) {
Log_e("get device info failed: %d", rc);
return rc;
}
// init connection
MQTTInitParams init_params = DEFAULT_MQTT_INIT_PARAMS;
_setup_connect_init_params(&init_params, &device_info);
// create MQTT client and connect with server
void *client = IOT_MQTT_Construct(&init_params);
if (client) {
Log_i("Cloud Device Construct Success");
} else {
Log_e("MQTT Construct failed!");
return QCLOUD_ERR_FAILURE;
}
// subscribe normal topics and wait result
static int test_usr_data = 0;
rc = IOT_Broadcast_Init(client, _on_broadcast_arrived_callback, &test_usr_data);
if (rc) {
Log_e("Client Subscribe Topic Failed: %d", rc);
return rc;
}
do {
rc = IOT_MQTT_Yield(client, QCLOUD_IOT_MQTT_YIELD_TIMEOUT);
if (rc == QCLOUD_ERR_MQTT_ATTEMPTING_RECONNECT) {
HAL_SleepMs(1000);
continue;
} else if (rc != QCLOUD_RET_SUCCESS && rc != QCLOUD_RET_MQTT_RECONNECTED) {
Log_e("exit with error: %d", rc);
break;
}
} while (!sg_main_exit);
rc = IOT_Broadcast_Deinit(client);
rc |= IOT_MQTT_Destroy(&client);
utils_log_deinit();
return rc;
}

View File

@@ -0,0 +1,120 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file broadcast.c
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include "qcloud_iot_broadcast.h"
/**
* @brief Context of broadcast message.
*
*/
typedef struct {
OnBroadcastArrivedCallback callback; /**< callback to handle message */
void * usr_data; /**< usr data using in callback */
} QcloudIotBroadcastContext;
/**
* @brief Callback for broadcast topic message
*
* @param[in,out] client pointer to mqtt client
* @param[in] message broadcast message @see MQTTMessage
* @param[in,out] context @see QcloudIotBroadcastContext
*/
static void _broadcast_message_cb(void *client, const MQTTMessage *message, void *context)
{
QcloudIotBroadcastContext *broadcast_context = (QcloudIotBroadcastContext *)context;
Log_d("topic=%.*s", message->topic_len, STRING_PTR_PRINT_SANITY_CHECK(message->topic_name));
Log_i("len=%u, topic_msg=%.*s", message->payload_len, message->payload_len,
STRING_PTR_PRINT_SANITY_CHECK(message->payload_str));
if (broadcast_context->callback) {
broadcast_context->callback(client, message->payload_str, message->payload_len, broadcast_context->usr_data);
}
}
/**
* @brief Subscribe broadcast topic with callback.
*
* @param[in,out] client pointer to mqtt client
* @param[in] callback callback to handle message
* @param[in] usr_data usr data using in callback
* @return @see IotReturnCode
*/
int IOT_Broadcast_Init(void *client, OnBroadcastArrivedCallback callback, void *usr_data)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
POINTER_SANITY_CHECK(callback, QCLOUD_ERR_INVAL);
int rc = 0;
char broadcast_topic[MAX_SIZE_OF_CLOUD_TOPIC];
SubscribeParams sub_params = DEFAULT_SUB_PARAMS;
/**
* @brief Using static for only one broardcast topic can be subscribed to.
*
*/
QcloudIotBroadcastContext *broadcast_context =
(QcloudIotBroadcastContext *)HAL_Malloc(sizeof(QcloudIotBroadcastContext));
if (!broadcast_context) {
return QCLOUD_ERR_MALLOC;
}
broadcast_context->callback = callback;
broadcast_context->usr_data = usr_data;
HAL_Snprintf(broadcast_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$broadcast/rxd/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
sub_params.on_message_handler = _broadcast_message_cb;
sub_params.qos = QOS1;
sub_params.user_data = broadcast_context;
sub_params.user_data_free = HAL_Free;
rc = IOT_MQTT_SubscribeSync(client, broadcast_topic, &sub_params);
if (rc) {
HAL_Free(broadcast_context);
}
return rc;
}
/**
* @brief Unsubscribe broadcast topic.
*
* @param[in,out] client pointer to mqtt client
* @return @see IotReturnCode
*/
int IOT_Broadcast_Deinit(void *client)
{
POINTER_SANITY_CHECK(client, QCLOUD_ERR_INVAL);
char broadcast_topic[MAX_SIZE_OF_CLOUD_TOPIC];
HAL_Snprintf(broadcast_topic, MAX_SIZE_OF_CLOUD_TOPIC, "$broadcast/rxd/%s/%s",
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->product_id),
STRING_PTR_PRINT_SANITY_CHECK(IOT_MQTT_GetDeviceInfo(client)->device_name));
return IOT_MQTT_Unsubscribe(client, broadcast_topic) > 0 ? QCLOUD_RET_SUCCESS : QCLOUD_ERR_FAILURE;
}

View File

@@ -0,0 +1,8 @@
Language: Cpp
BasedOnStyle: Google
ColumnLimit: 120
DerivePointerAlignment: true
PointerAlignment: Left
SortIncludes: true
IncludeBlocks: Preserve
IndentPPDirectives: AfterHash

View File

@@ -0,0 +1,62 @@
/**
* @copyright
*
* Tencent is pleased to support the open source community by making IoT Hub available.
* Copyright(C) 2018 - 2021 THL A29 Limited, a Tencent company.All rights reserved.
*
* Licensed under the MIT License(the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://opensource.org/licenses/MIT
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*
* @file test_broadcast.cc
* @brief
* @author fancyxu (fancyxu@tencent.com)
* @version 1.0
* @date 2021-07-18
*
* @par Change Log:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-07-18 <td>1.0 <td>fancyxu <td>first commit
* </table>
*/
#include <iostream>
#include <string>
#include "gtest/gtest.h"
#include "mqtt_client_test.h"
#include "qcloud_iot_common.h"
#include "qcloud_iot_hub.h"
namespace mqtt_client_unittest {
/**
* @brief Callback when MQTT msg arrives @see OnMessageHandler
*
* @param[in, out] client pointer to mqtt client
* @param[in] message publish message from server
* @param[in] usr_data user data of SubscribeParams, @see SubscribeParams
*/
void on_broadcast_arrived_callback(void *client, const char *msg, int msg_len, void *usr_data) {
Log_i("Receive broadcast message:%.*s, usr data:%d", msg_len, STRING_PTR_PRINT_SANITY_CHECK(msg),
*(reinterpret_cast<int *>(usr_data)));
*(reinterpret_cast<int *>(usr_data)) += 1;
}
/**
* @brief Test broadcast.
*
*/
TEST_F(MqttClientTest, broadcast) {
int usr_data = 0;
ASSERT_EQ(IOT_Broadcast_Init(client, on_broadcast_arrived_callback, &usr_data), 0);
ASSERT_EQ(IOT_Broadcast_Deinit(client), 0);
}
} // namespace mqtt_client_unittest