From 59416164b888edb75182054f28b7bb24e02a3c35 Mon Sep 17 00:00:00 2001 From: "freddy.li" Date: Wed, 8 Dec 2021 13:39:23 +0800 Subject: [PATCH] update quecsdk --- app/demo_quec_at/cmdTest.c | 417 ++- app/demo_quec_openc/coap/main_coap.c | 198 ++ app/demo_quec_openc/{ => mqtt}/Ql_iotMain.c | 656 ++-- .../other/Ql_iotMain_netcheck.c | 201 ++ app/demo_quec_python/Ql_iotMain.py.bak | Bin 4413 -> 0 bytes app/demo_quec_python/Ql_iotMain_Temp.py | 205 -- app/demo_quec_python/example1.py | 83 + .../{Ql_iotMain.py => example2.py} | 197 +- app/demo_quec_python/example3.py | 149 + cloud/Ql_iotApi.h | 151 +- cloud/common/ql_iotCmdBus.c | 277 ++ cloud/common/ql_iotCmdBus.h | 49 + cloud/common/ql_iotCmdLan.c | 85 + cloud/common/ql_iotCmdLan.h | 18 + cloud/common/ql_iotCmdLoc.c | 325 ++ cloud/common/ql_iotCmdLoc.h | 9 + cloud/common/ql_iotCmdOTA.c | 889 +++++ cloud/common/ql_iotCmdOTA.h | 115 + cloud/common/ql_iotCmdSys.c | 573 ++++ cloud/common/ql_iotCmdSys.h | 53 + cloud/common/ql_iotConfig.c | 681 ++++ cloud/common/ql_iotConfig.h | 94 + cloud/common/ql_iotConn.c | 724 ++++ cloud/common/ql_iotConn.h | 62 + cloud/common/ql_iotDp.c | 399 +++ cloud/common/ql_iotDp.h | 84 + cloud/common/ql_iotSecure.c | 312 ++ cloud/common/ql_iotSecure.h | 17 + cloud/common/ql_iotTtlv.c | 962 ++++++ cloud/common/ql_iotTtlv.h | 9 + cloud/python/modquecIot.c | 1849 +++++++++++ driverLayer/Qhal_types.h | 1 + driverLayer/qhal_Dev.c | 25 +- driverLayer/qhal_Dev.h | 34 +- driverLayer/qhal_Socket.c | 39 +- driverLayer/qhal_Socket.h | 4 +- driverLayer/qhal_property.c | 2 +- driverLayer/qhal_property.h | 1 - kernel/Quos_kernel.c | 50 + kernel/Quos_kernel.h | 53 +- kernel/quos_SupportTool.c | 535 +++ kernel/quos_SupportTool.h | 277 +- kernel/quos_aes.c | 555 ++++ kernel/quos_aes.h | 110 +- kernel/quos_base64.c | 115 + kernel/quos_base64.h | 20 +- kernel/quos_cjson.c | 2934 +++++++++++++++++ kernel/quos_cjson.h | 434 ++- kernel/quos_coap.c | 824 +++++ kernel/quos_coap.h | 428 ++- kernel/quos_config.h | 135 +- kernel/quos_dataStore.c | 416 +++ kernel/quos_dataStore.h | 34 +- kernel/quos_event.c | 121 + kernel/quos_fifo.c | 235 ++ kernel/quos_fifo.h | 60 +- kernel/quos_http.c | 650 ++++ kernel/quos_http.h | 112 +- kernel/quos_log.c | 29 + kernel/quos_log.h | 100 +- kernel/quos_lwm2m.c | 182 + kernel/quos_md5.c | 274 ++ kernel/quos_mqtt.c | 602 ++++ kernel/quos_mqtt.h | 101 +- kernel/quos_net.c | 315 ++ kernel/quos_net.h | 21 +- kernel/quos_sha1.c | 216 ++ kernel/quos_sha256.c | 258 ++ kernel/quos_signal.c | 83 + kernel/quos_socket.c | 792 +++++ kernel/quos_socket.h | 192 +- kernel/quos_swTimer.c | 351 ++ kernel/quos_swTimer.h | 64 +- kernel/quos_sysTick.c | 255 ++ kernel/quos_twll.c | 183 + kernel/quos_twll.h | 60 +- platform/ASR_lib/lib/libquecsdk_in.a | Bin 804296 -> 0 bytes platform/Unisoc_lib/lib/libquecsdk_in.a | Bin 846406 -> 0 bytes quecsdk.mk | 48 +- 79 files changed, 20178 insertions(+), 1995 deletions(-) create mode 100644 app/demo_quec_openc/coap/main_coap.c rename app/demo_quec_openc/{ => mqtt}/Ql_iotMain.c (68%) create mode 100644 app/demo_quec_openc/other/Ql_iotMain_netcheck.c delete mode 100644 app/demo_quec_python/Ql_iotMain.py.bak delete mode 100644 app/demo_quec_python/Ql_iotMain_Temp.py create mode 100644 app/demo_quec_python/example1.py rename app/demo_quec_python/{Ql_iotMain.py => example2.py} (33%) create mode 100644 app/demo_quec_python/example3.py create mode 100644 cloud/common/ql_iotCmdBus.c create mode 100644 cloud/common/ql_iotCmdBus.h create mode 100644 cloud/common/ql_iotCmdLan.c create mode 100644 cloud/common/ql_iotCmdLan.h create mode 100644 cloud/common/ql_iotCmdLoc.c create mode 100644 cloud/common/ql_iotCmdLoc.h create mode 100644 cloud/common/ql_iotCmdOTA.c create mode 100644 cloud/common/ql_iotCmdOTA.h create mode 100644 cloud/common/ql_iotCmdSys.c create mode 100644 cloud/common/ql_iotCmdSys.h create mode 100644 cloud/common/ql_iotConfig.c create mode 100644 cloud/common/ql_iotConfig.h create mode 100644 cloud/common/ql_iotConn.c create mode 100644 cloud/common/ql_iotConn.h create mode 100644 cloud/common/ql_iotDp.c create mode 100644 cloud/common/ql_iotDp.h create mode 100644 cloud/common/ql_iotSecure.c create mode 100644 cloud/common/ql_iotSecure.h create mode 100644 cloud/common/ql_iotTtlv.c create mode 100644 cloud/common/ql_iotTtlv.h create mode 100644 cloud/python/modquecIot.c create mode 100644 kernel/Quos_kernel.c create mode 100644 kernel/quos_SupportTool.c create mode 100644 kernel/quos_aes.c create mode 100644 kernel/quos_base64.c create mode 100644 kernel/quos_cjson.c create mode 100644 kernel/quos_coap.c create mode 100644 kernel/quos_dataStore.c create mode 100644 kernel/quos_event.c create mode 100644 kernel/quos_fifo.c create mode 100644 kernel/quos_http.c create mode 100644 kernel/quos_log.c create mode 100644 kernel/quos_lwm2m.c create mode 100644 kernel/quos_md5.c create mode 100644 kernel/quos_mqtt.c create mode 100644 kernel/quos_net.c create mode 100644 kernel/quos_sha1.c create mode 100644 kernel/quos_sha256.c create mode 100644 kernel/quos_signal.c create mode 100644 kernel/quos_socket.c create mode 100644 kernel/quos_swTimer.c create mode 100644 kernel/quos_sysTick.c create mode 100644 kernel/quos_twll.c delete mode 100644 platform/ASR_lib/lib/libquecsdk_in.a delete mode 100644 platform/Unisoc_lib/lib/libquecsdk_in.a diff --git a/app/demo_quec_at/cmdTest.c b/app/demo_quec_at/cmdTest.c index 10a2d92..815acc5 100644 --- a/app/demo_quec_at/cmdTest.c +++ b/app/demo_quec_at/cmdTest.c @@ -8,64 +8,78 @@ ***************************************************************************/ #include "ql_iotAt.h" #include "qhal_Socket.h" +#include "qhal_atCmd.h" #define APP_AT LL_DBG -typedef struct __appSock -{ - TWLLHead_T head; - pointer_t sockFd; -} appSock_T; -static TWLLHead_T *appSockHead = NULL; -#define APP_AT_SOCKET_PORT 4321 +#define APP_AT_SOCKET_PORT 39999 +#define APP_AT_RECV_MAX 5000 void cmdTestSocketSend(pointer_t sockFd, const quint8_t *data, quint32_t len); typedef struct { char *cmd; - qint32_t (*cb)(QIot_atAction_e action, char *retBuf, quint32_t retMaxLen, quint8_t count, char *arg[]); + void (*cb)(qhal_atcmd_t *cmd); } atCmdTable_t; static atCmdTable_t table[] = { - {"QIOTREG", Ql_iotAtQIOTREG}, - {"QIOTSEND", Ql_iotAtQIOTTransTx}, - {"QIOTRD", Ql_iotAtQIOTTransRx}, - {"QIOTCFG", Ql_iotAtQIOTCFG}, - {"QIOTMODELTD", Ql_iotAtQIOTModelTx}, - {"QIOTMODELRD", Ql_iotAtQIOTModelRx}, - {"QIOTMCUVER", Ql_iotAtQIOTMCUVER}, - {"QIOTUPDATE", Ql_iotAtQIOTUPDATE}, - {"QIOTINFO", Ql_iotAtQIOTINFO}, - {"QIOTOTARD", Ql_iotAtQIOTOTARD}, - {"QIOTSTATE", Ql_iotAtQIOTSTATE}, - {"QIOTLOCCFG", Ql_iotAtQIOTLOCCFG}, - {"QIOTLOCRPT", Ql_iotAtQIOTLOCRPT}, + {"QIOTREG", Qhal_atCmdIotAtQIOTREG}, + {"QIOTSEND", Qhal_atCmdIotAtQIOTTransTx}, + {"QIOTRD", Qhal_atCmdIotAtQIOTTransRx}, + {"QIOTCFG", Qhal_atCmdIotAtQIOTCFG}, + {"QIOTMODELTD", Qhal_atCmdIotAtQIOTModelTx}, + {"QIOTMODELRD", Qhal_atCmdIotAtQIOTModelRx}, + {"QIOTMCUVER", Qhal_atCmdIotAtQIOTMCUVER}, + {"QIOTUPDATE", Qhal_atCmdIotAtQIOTUPDATE}, + {"QIOTINFO", Qhal_atCmdIotAtQIOTINFO}, + {"QIOTOTARD", Qhal_atCmdIotAtQIOTOTARD}, + {"QIOTSTATE", Qhal_atCmdIotAtQIOTSTATE}, + {"QIOTLOCIN", Qhal_atCmdIotAtQIOTLOCIN}, + {"QIOTLOCEXT", Qhal_atCmdIotAtQIOTLOCEXT}, + {"QIOTOTAREQ", Qhal_atCmdIotAtQIOTOTARequest}, + #ifdef QUEC_ENABLE_HTTP_OTA + {"QFOTAUP", Qhal_atCmdIotAtQFOTAUP}, + {"QFOTACFG", Qhal_atCmdIotAtQFOTACFG}, + #endif + #ifdef QUEC_ENABLE_GATEWAY + {"QIOTSUBCONN", Qhal_atCmdIotAtQIOTSUBCONN}, + {"QIOTSUBDISCONN", Qhal_atCmdIotAtQIOTSUBDISCONN}, + {"QIOTSUBRD", Qhal_atCmdIotAtQIOTSUBRD}, + {"QIOTSUBSEND", Qhal_atCmdIotAtQIOTSUBSEND}, + {"QIOTSUBTSLRD", Qhal_atCmdIotAtQIOTSUBTSLRD}, + {"QIOTSUBTSLTD", Qhal_atCmdIotAtQIOTSUBTSLTD}, +// {"QIOTSUBINFO", Qhal_atCmdIotAtQIOTSUBINFO}, + {"QIOTSUBHTB", Qhal_atCmdIotAtQIOTSUBHTB}, + #endif {NULL, NULL}}; + +#define AT_SOCKECT_MASK "AT" /************************************************************************** ** 功能 @brief : AT透传模式 ** 输入 @param : ** 输出 @retval: ***************************************************************************/ -static char *cmdTestPassMode(pointer_t sockFd, int dataLen) +static quint32_t cmdTestPassMode(pointer_t sockFd, int dataLen,char **buf) { int ret; int bufLen = 0; - char *buf = malloc(dataLen); - if(NULL == buf) + qint32_t timeoutSum = 10; + *buf = HAL_MALLOC(dataLen); + if(NULL == *buf) { Quos_logPrintf(APP_AT, LL_ERR, "malloc fail"); - return NULL; + return 0; } do { if(SOCKET_FD_INVALID == sockFd) { - ret = read(STDIN_FILENO, buf+bufLen, dataLen-bufLen); + ret = read(STDIN_FILENO, *buf+bufLen, dataLen-bufLen); } else { - ret = read(sockFd, buf+bufLen, dataLen-bufLen); + ret = read(sockFd, *buf+bufLen, dataLen-bufLen); } if(ret < 0) { @@ -73,29 +87,105 @@ static char *cmdTestPassMode(pointer_t sockFd, int dataLen) break; } bufLen += ret; - if(bufLen == dataLen) + if(bufLen >= dataLen) + { + break; + } + sleep(1); + if(timeoutSum > 0) + { + timeoutSum--; + } + else { - return buf; + return 0; } } while (1); - return NULL; + return bufLen; +} + +/************************************************************************** +** 功能 @brief : AT命令参数提取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void atCmd_argExtract(char *src, qhal_atcmd_t *cmd) +{ + char *args[QHAL_AT_PARAM_MAX]; + quint32_t argNum = 0; + char *p = src; + do + { + qbool isStr = FALSE; + args[argNum] = p; + if(*p == '"') + { + while ((p = HAL_STRSTR(p+1,"\""))) + { + p++; + if(*p == '\0' || *p == ',') + { + isStr = TRUE; + break; + } + } + } + else + { + p = HAL_STRSTR(p,","); + } + if(p && *p == ',') + { + *p++ = '\0'; + } + if(isStr) + { + cmd->params[argNum].type = QHAL_AT_TYPE_STRING; + cmd->params[argNum].val = (quint8_t *)Quos_stringRemoveMarks(args[argNum]); + cmd->params[argNum].len = HAL_STRLEN(args[argNum]); + } + else + { + if(Quos_strIsUInt(args[argNum], HAL_STRLEN(args[argNum]),NULL) == TRUE) + { + cmd->params[argNum].type = QHAL_AT_TYPE_INT; + cmd->params[argNum].val = (quint8_t *)args[argNum]; + } + else + { + cmd->params[argNum].type = QHAL_AT_TYPE_RAW; + cmd->params[argNum].val = (quint8_t *)args[argNum]; + cmd->params[argNum].len = HAL_STRLEN(args[argNum]); + } + } + argNum++; + }while (argNum < QHAL_AT_PARAM_MAX && p && *p != '\0'); + cmd->param_count = argNum; } + /************************************************************************** ** 功能 @brief : AT命令解析 ** 输入 @param : ** 输出 @retval: ***************************************************************************/ -static void atCmdAnalyze(pointer_t sockFd, char *buf) +static void atCmdAnalyze(pointer_t sockFd, char *buf,int len) { #define AT_HEAD "AT+" - if (0 != strncasecmp(buf, AT_HEAD, HAL_STRLEN(AT_HEAD)) || '\n' != buf[HAL_STRLEN(buf) - 1]) + if (0 != HAL_STRNCASECMP(buf, AT_HEAD, HAL_STRLEN(AT_HEAD))) + { + return; + } + else if('\n' != buf[len - 1]) { + cmdTestSocketSend(sockFd, (const quint8_t *)"ERROR\r\n", HAL_STRLEN("ERROR\r\n")); return; } - buf[HAL_STRLEN(buf) - 1] = 0; - if (buf[HAL_STRLEN(buf) - 1] == '\r') + buf[len - 1] = 0; + len--; + if (buf[len - 1] == '\r') { - buf[HAL_STRLEN(buf) - 1] = 0; + buf[len - 1] = 0; + len--; } buf += HAL_STRLEN(AT_HEAD); quint32_t i = 0; @@ -107,30 +197,21 @@ static void atCmdAnalyze(pointer_t sockFd, char *buf) continue; } buf += HAL_STRLEN(table[i].cmd); - char retBuf[QIOT_AT_BUFFER_MAX] = {0}; - char *words[200]; - quint32_t size = 0; - QIot_atAction_e action = QIOT_AT_ACTION_UNKOWN; + qhal_atcmd_t cmd; + cmd.param_count = 0; + cmd.sockFd = sockFd; + cmd.action = QIOT_AT_ACTION_UNKOWN; if ('=' == buf[0]) { if ('?' == buf[1] && '\0' == buf[2]) { Quos_logPrintf(APP_AT, LL_DBG, "AT test"); - action = QIOT_AT_ACTION_TEST; + cmd.action = QIOT_AT_ACTION_TEST; } else if ('\0' != buf[1]) { - quint32_t i; - size = Quos_stringSplit(buf + 1, words, 200, ",", TRUE); - for (i = 0; i < size; i++) - { - while (' ' == words[i][0]) - { - words[i] = &words[i][1]; - } - Quos_stringRemoveMarks(words[i]); - } - action = QIOT_AT_ACTION_WRITE; + atCmd_argExtract(buf + 1, &cmd); + cmd.action = QIOT_AT_ACTION_WRITE; } else { @@ -140,177 +221,151 @@ static void atCmdAnalyze(pointer_t sockFd, char *buf) else if ('?' == buf[0] && '\0' == buf[1]) { Quos_logPrintf(APP_AT, LL_DBG, "AT read"); - action = QIOT_AT_ACTION_READ; + cmd.action = QIOT_AT_ACTION_READ; } else if ('\0' == buf[0]) { - action = QIOT_AT_ACTION_EXEC; + cmd.action = QIOT_AT_ACTION_EXEC; } - - if (QIOT_AT_ACTION_UNKOWN != action) + if (QIOT_AT_ACTION_UNKOWN != cmd.action) { - qint32_t ret; char *passData = NULL; - if((HAL_STRCMP("QIOTSEND",table[i].cmd) == 0 && 2 == size) || - (HAL_STRCMP("QIOTMODELTD",table[i].cmd) == 0 && (2 == size || 3 == size))) - { - Quos_logPrintf(APP_AT, LL_ERR, "need wait data,len:%d",atoi(words[1])); - cmdTestSocketSend(sockFd, (const quint8_t *)"> ", HAL_STRLEN("> ")); - passData = cmdTestPassMode(sockFd,atoi(words[1])); - if(passData) - { - words[size++] = passData; - } - } - ret = table[i].cb(action, retBuf, sizeof(retBuf), size, words); - if (ret == 0) + uint32_t length = 0; + if (((HAL_STRCMP("QIOTSEND",table[i].cmd) == 0) && 2 == cmd.param_count) || + ((HAL_STRCMP("QIOTMODELTD",table[i].cmd) == 0) && (2 == cmd.param_count ||(3 == cmd.param_count && cmd.params[2].type == QHAL_AT_TYPE_INT)))) { - cmdTestSocketSend(sockFd, (const quint8_t *)"OK\r\n", HAL_STRLEN("OK\r\n")); + length = HAL_ATOI((const char *)cmd.params[1].val); } - else if (ret < 0) +#ifdef QUEC_ENABLE_GATEWAY + else if ((HAL_STRCMP("QIOTSUBTSLTD",table[i].cmd) == 0 && ((4 == cmd.param_count && cmd.params[3].type == QHAL_AT_TYPE_INT) || 3 == cmd.param_count)) || + (HAL_STRCMP("QIOTSUBSEND",table[i].cmd) == 0 && 3 == cmd.param_count)) { - cmdTestSocketSend(sockFd, (const quint8_t *)"ERROR\r\n", HAL_STRLEN("ERROR\r\n")); + length = HAL_ATOI((const char *)cmd.params[2].val); } - else +#endif + if (0 != length) { - cmdTestSocketSend(sockFd, (const quint8_t *)retBuf, ret); - cmdTestSocketSend(sockFd, (const quint8_t *)"\r\n\r\nOK\r\n", HAL_STRLEN("\r\n\r\nOK\r\n")); + Quos_logPrintf(APP_AT, LL_ERR, "need wait data,len:%d",length); + cmdTestSocketSend(sockFd, (const quint8_t *)"> ", HAL_STRLEN("> ")); + quint32_t passDataLen = cmdTestPassMode(sockFd,(int)length,&passData); + if(passDataLen) + { + cmd.params[cmd.param_count].type = QHAL_AT_TYPE_PASS; + cmd.params[cmd.param_count].val = (quint8_t*)passData; + cmd.params[cmd.param_count].len = passDataLen; + cmd.param_count++; + } + else + { + cmdTestSocketSend(sockFd, (const quint8_t *)"ERROR\r\n", HAL_STRLEN("ERROR\r\n")); + if(passData) + { + HAL_FREE(passData); + } + i++; + continue; + } } + table[i].cb(&cmd); if(passData) { - free(passData); + HAL_FREE(passData); } } - i++; - } -} -/************************************************************************** -** 功能 @brief : -** 输入 @param : -** 输出 @retval: -***************************************************************************/ -static void *cmdTestTask(void *arg) -{ - fd_set rset; - UNUSED(arg); - while (1) - { - qint32_t bufLen; - quint8_t buf[1024]; - FD_ZERO(&rset); - FD_SET(STDIN_FILENO, &rset); - int result = select(STDIN_FILENO + 1, &rset, NULL, NULL, NULL); - if (result > 0 && (bufLen = read(STDIN_FILENO, buf, sizeof(buf))) > 0) + else { - buf[bufLen] = 0; - atCmdAnalyze(SOCKET_FD_INVALID, (char *)buf); + cmdTestSocketSend(sockFd, (const quint8_t *)"ERROR\r\n", HAL_STRLEN("ERROR\r\n")); } + i++; } - return NULL; } + /************************************************************************** -** 功能 @brief : +** 功能 @brief : TCP客户端数据发送 ** 输入 @param : ** 输出 @retval: ***************************************************************************/ void cmdTestSocketSend(pointer_t sockFd, const quint8_t *data, quint32_t len) { - TWLLHead_T *temp, *next; + if(NULL == data || 0 == len) + { + return ; + } Quos_logPrintf(APP_AT, LL_DBG, "data[%d]:\n%s", len, data); if (SOCKET_FD_INVALID == sockFd) { - TWLIST_FOR_DATA(appSockHead, temp, next) + void *chlList[100]; + quint32_t count = Quos_socketGetChlFdList(SOCKET_TYPE_TCP_CLI, (void*)AT_SOCKECT_MASK, chlList,sizeof(chlList)/sizeof(chlList[0])); + while (count--) { - appSock_T *listTemp = __GET_STRUCT_BY_ELEMENT(temp, appSock_T, head); - sockFd = listTemp->sockFd; + quint8_t *newData = HAL_MEMDUP(data, len); + if(newData) + { + Quos_socketTxDisorder(chlList[count],NULL,(quint8_t*)newData, len); + } } } - if(data && len) + else { - send(sockFd, data, len, 0); + Quos_socketChlInfoNode_t *node = (Quos_socketChlInfoNode_t *)Quos_socketGetChlFd(sockFd,SOCKET_TYPE_TCP_CLI); + if(node) + { + quint8_t *newData = HAL_MEMDUP(data, len); + if(newData) + { + Quos_socketTxDisorder(node,NULL,(quint8_t*)newData, len); + } + } } } + /************************************************************************** -** 功能 @brief : +** 功能 @brief : TCP客户端数据接收 ** 输入 @param : ** 输出 @retval: ***************************************************************************/ -static void *cmdTestSocketRecvTask(void *arg) +static qbool FUNCTION_ATTR_ROM cmdTestSocketRecv(void *chlFd, const void *peer, quint32_t peerSize, Quos_socketRecvDataNode_t *recvData) { - pointer_t sockFd = (pointer_t)arg; - while (1) + UNUSED(peer); + UNUSED(peerSize); + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + if(NULL == recvData) { - fd_set rset; - FD_ZERO(&rset); - FD_SET(sockFd, &rset); - int result = select(sockFd + 1, &rset, NULL, NULL, NULL); - if (result < 0) - { - break; - } - else if (result > 0) - { - quint8_t buf[1024]; - qint32_t bufLen = read(sockFd, buf, sizeof(buf)); - if (bufLen > 0) - { - buf[bufLen] = '\0'; - Quos_logPrintf(APP_AT, LL_DBG, "recv:%s", buf); - atCmdAnalyze(sockFd, (char *)buf); - } - else - { - break; - } - } - } - Quos_logPrintf(APP_AT, LL_DBG, "fd[%ld] disconnect", sockFd); - TWLLHead_T *temp, *next; - TWLIST_FOR_DATA(appSockHead, temp, next) - { - appSock_T *listTemp = __GET_STRUCT_BY_ELEMENT(temp, appSock_T, head); - if (listTemp->sockFd == sockFd) - { - Quos_twllHeadDelete(&appSockHead, temp); - free(listTemp); - } + Quos_logPrintf(APP_AT, LL_DBG,"socket disconnect"); + return TRUE; } - return NULL; + Quos_logPrintf(APP_AT, LL_DBG,"recv data:%.*s",recvData->bufLen,recvData->Buf); + atCmdAnalyze(chlNode->sockFd, (char *)recvData->Buf,recvData->bufLen); + return TRUE; } + /************************************************************************** -** 功能 @brief : +** 功能 @brief : 局域网监听新的TCP客户端连接 ** 输入 @param : ** 输出 @retval: ***************************************************************************/ -static void *cmdTestSocketTask(void *arg) +static qbool FUNCTION_ATTR_ROM cmdTestClientListen(void *chlFd, const void *peer, quint32_t peerSize, Quos_socketRecvDataNode_t *recvData) { - quint32_t len; - pointer_t fd = (pointer_t)arg; - Quos_logPrintf(APP_AT, LL_DBG, "fd[%ld]", fd); - - struct sockaddr_in client; - bzero(&client, sizeof(client)); - len = sizeof(client); - - while (1) + UNUSED(chlFd); + UNUSED(peer); + UNUSED(peerSize); + if(NULL == recvData) { - pthread_t pthreadId; - pointer_t appAtFd = accept(fd, (struct sockaddr *)&client, &len); - if (0 != pthread_create(&pthreadId, NULL, (void *)cmdTestSocketRecvTask, (void *)(pointer_t)appAtFd)) - { - close(appAtFd); - break; - } - appSock_T *listNew = (appSock_T *)malloc(sizeof(appSock_T)); - listNew->sockFd = appAtFd; - if (listNew == NULL) - { - break; - } - Quos_twllHeadAdd(&appSockHead, &listNew->head); + return FALSE; } - return NULL; + Quos_socketChlInfoNode_t chlInfo; + HAL_MEMCPY(&chlInfo,recvData->Buf,recvData->bufLen); + Quos_logPrintf(APP_AT, LL_DBG,"new cliend sockFd:"PRINTF_FD,chlInfo.sockFd); + chlInfo.io.send = Qhal_sockWrite; + chlInfo.send.txCnt = 1; + chlInfo.send.timeout = 2000; + chlInfo.recvDataFunc = cmdTestSocketRecv; + chlInfo.io.close = Qhal_sockClose; + chlInfo.param = AT_SOCKECT_MASK; + Quos_socketChannelAdd(NULL,chlInfo); + return TRUE; } + /************************************************************************** ** 功能 @brief : ** 输入 @param : @@ -318,11 +373,17 @@ static void *cmdTestSocketTask(void *arg) ***************************************************************************/ void CmdTestInit(void) { - pthread_t pthreadId; - pthread_t pthreadSocketId; - quint8_t sockType; - pointer_t fd = Qhal_tcpServerInit(&sockType, APP_AT_SOCKET_PORT, 1); - Quos_logPrintf(APP_AT, LL_DBG, "fd[%ld]", fd); - pthread_create(&pthreadSocketId, NULL, (void *)cmdTestSocketTask, (void *)fd); - pthread_create(&pthreadId, NULL, (void *)cmdTestTask, NULL); + Quos_socketChlInfoNode_t chlInfo; + HAL_MEMSET(&chlInfo, 0, sizeof(Quos_socketChlInfoNode_t)); + chlInfo.sockFd = Qhal_tcpServerInit(&chlInfo.type, APP_AT_SOCKET_PORT, 100); + Quos_logPrintf(APP_AT, LL_DBG, "fd:" PRINTF_FD, chlInfo.sockFd); + if (SOCKET_FD_INVALID == chlInfo.sockFd) + { + Quos_logPrintf(APP_AT, LL_ERR, "listening port failed"); + return; + } + chlInfo.send.txCnt = 1; + chlInfo.send.timeout = 2000; + chlInfo.recvDataFunc = cmdTestClientListen; + Quos_socketChannelAdd(NULL, chlInfo); } diff --git a/app/demo_quec_openc/coap/main_coap.c b/app/demo_quec_openc/coap/main_coap.c new file mode 100644 index 0000000..8c606d3 --- /dev/null +++ b/app/demo_quec_openc/coap/main_coap.c @@ -0,0 +1,198 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "Ql_iotApi.h" +#include "ql_iotSecure.h" +#include "Qhal_driver.h" +#include "ql_iotDp.h" +void *coapFd = NULL; +quint8_t token19_0_0[8]; + +#define DEV_UUID "868626047808496" +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void CoapSocketRecvNodeCb(void *chlFd, const void *sendData, const void *recvData) +{ + printf("CoapSocketRecvNodeCb recvData:%p\n", recvData); + if (recvData) + { + Coap_Message_t *msg = (Coap_Message_t *)recvData; + printf("code:%s\n", COAP_HEAD_CODE_STRING(msg->head.code)); + } + UNUSED(chlFd); + UNUSED(sendData); +} +/************************************************************************** +** 功能 @brief : coap协议测试 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool CoapAuthRecvHandle(void *chlFd, const Coap_Message_t *coapMsg, Coap_Message_t *retCoapMsg) +{ + UNUSED(chlFd); + UNUSED(coapMsg); + printf("CoapAuthRecvHandle coapMsg:%p\n", coapMsg); + if (COAP_HCODE_DELETE == coapMsg->head.code) + { + printf("CoapRecvNotify:%s\n", COAP_HEAD_CODE_STRING(COAP_HCODE_DELETE)); + Quos_coapHeadSet(retCoapMsg, COAP_HTYPE_ACK, COAP_HCODE_DELETED_202, coapMsg->head.mid, coapMsg->head.tokenLen, coapMsg->token); + return TRUE; + } + else if (COAP_HCODE_PUT == coapMsg->head.code) + { + char *uri = Quos_coapOptionGetPath(coapMsg); + printf("CoapRecvNotify:%s url[%s]\n", COAP_HEAD_CODE_STRING(COAP_HCODE_PUT), uri); + HAL_FREE(uri); + Quos_coapHeadSet(retCoapMsg, COAP_HTYPE_ACK, COAP_HCODE_CHANGED_204, coapMsg->head.mid, coapMsg->head.tokenLen, coapMsg->token); + return TRUE; + } + else if (COAP_HCODE_POST == coapMsg->head.code) + { + printf("CoapRecvNotify:%s\n", COAP_HEAD_CODE_STRING(COAP_HCODE_POST)); + Quos_coapHeadSet(retCoapMsg, COAP_HTYPE_ACK, COAP_HCODE_EMPTY, coapMsg->head.mid, 0, NULL); + Coap_Message_t bsEndMsg; + HAL_MEMSET(&bsEndMsg, 0, sizeof(Coap_Message_t)); + Quos_coapHeadSet(&bsEndMsg, COAP_HTYPE_CON, COAP_HCODE_CHANGED_204, 0, coapMsg->head.tokenLen, coapMsg->token); + Quos_coapMsgSend(chlFd, NULL, &bsEndMsg, CoapSocketRecvNodeCb, FALSE); + return TRUE; + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : coap协议测试 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool CoapConnRecvHandle(void *chlFd, const Coap_Message_t *coapMsg, Coap_Message_t *retCoapMsg) +{ + UNUSED(chlFd); + UNUSED(coapMsg); + printf("CoapConnRecvHandle coapMsg:%p\n", coapMsg); + + if (COAP_HCODE_GET == coapMsg->head.code) + { + char *path = Quos_coapOptionGetPath(coapMsg); + printf("path[%s]\n", path); + if (0 == HAL_STRCMP(path, "/19/0/0")) + { + HAL_MEMCPY(token19_0_0, coapMsg->token, coapMsg->head.tokenLen); + printf("save observe(19/0/0) token\n"); + } + Quos_coapHeadSet(retCoapMsg, COAP_HTYPE_ACK, COAP_HCODE_CONTENT_205, coapMsg->head.mid, coapMsg->head.tokenLen, coapMsg->token); + return TRUE; + } + else if (COAP_HCODE_PUT == coapMsg->head.code) + { + char *path = Quos_coapOptionGetPath(coapMsg); + printf("path[%s]\n", path); + if (0 == HAL_STRCMP(path, "/19/1/0")) + { + printf("recv bus data\n"); + Quos_logHexDumpData(coapMsg->payload.val, coapMsg->payload.len); + Quos_coapHeadSet(retCoapMsg, COAP_HTYPE_ACK, COAP_HCODE_CHANGED_204, coapMsg->head.mid, coapMsg->head.tokenLen, coapMsg->token); + return TRUE; + } + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +extern quint32_t ql_iotDpFormat(quint8_t **buf, quint16_t pId, quint16_t cmd, const quint8_t *payload, quint32_t payloadLen); +void CoapTestDataNotify(void) +{ + static quint16_t id = 0; + char *srcData = "hello world,quecthing"; + + Coap_Message_t msg; + HAL_MEMSET(&msg, 0, sizeof(Coap_Message_t)); + Quos_coapHeadSet(&msg, COAP_HTYPE_NON, COAP_HCODE_CONTENT_205, 0, 8, token19_0_0); + Quos_coapOptionSetNumber(&msg, COAP_OTYPE_OBSERVE, 0); + Quos_coapOptionSetNumber(&msg, COAP_OTYPE_CONTENT_TYPE, COAP_OCTYPE_APP_OCTET_STREAM); + quint8_t *pkg = NULL; + quint32_t pkgLen = ql_iotDpFormat(&pkg, ++id, 0x0024, (quint8_t *)srcData, HAL_STRLEN(srcData)); + if (pkg) + { + Quos_coapPayloadSet(&msg, pkg, pkgLen); + Quos_coapMsgSend(coapFd, NULL, &msg, NULL, FALSE); + } +} +/************************************************************************** +** 功能 @brief : 程序初始化入口 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +int main(void) +{ + /* 初始化quecsdk */ + Ql_iotInit(); + +#if 1 + if (FALSE == Quos_coapInit(&coapFd, "220.180.239.212:8258", CoapAuthRecvHandle)) + { + printf("Quos_coapInit fail\n"); + return -1; + } + + Coap_Message_t coapMsg; + Quos_coapHeadSet(&coapMsg, COAP_HTYPE_CON, COAP_HCODE_POST, 0, 8, NULL); + char *payload = Ql_iotSecureGenCoapAuthPayload("p1145q", "WjJRSEp1c2x1ckU2", DEV_UUID); + Quos_coapPayloadSet(&coapMsg, payload, HAL_STRLEN(payload)); + Quos_coapMsgSend(coapFd, "/bs", &coapMsg, CoapSocketRecvNodeCb, FALSE); +#elif 0 + if (FALSE == Quos_coapInit(&coapFd, "220.180.239.212:8257", CoapConnRecvHandle)) + { + printf("Quos_coapInit fail\n"); + return -1; + } + + char *data = "LZ3oAdAUpizTf0YXID7XWMeYYuNPgqNxByo/ISFiJRQCuV9om2ApZwE4ZLEIfQe7"; + char *ds = Ql_iotSecureDecodeDs("WjJRSEp1c2x1ckU2", (quint8_t *)data, HAL_STRLEN(data)); + printf("ds:%s\n", ds); + char *ep = Ql_iotSecureGenCoapConnEndpoint("p1145q", "WjJRSEp1c2x1ckU2", DEV_UUID, ds); + HAL_FREE(ds); + printf("ep:%s\n", ep); + char *option = HAL_MALLOC(HAL_STRLEN("rd?b=U&lwm2m=1.0<=300000&ep=") + HAL_STRLEN(ep) + 1); + HAL_SPRINTF(option, "%s%s", "rd?b=U&lwm2m=1.0<=300000&ep=", ep); + HAL_FREE(ep); + Coap_Message_t coapMsg; + Quos_coapHeadSet(&coapMsg, COAP_HTYPE_CON, COAP_HCODE_POST, 0, 8, NULL); + Quos_coapOptionSetPath(&coapMsg, option); + Quos_coapPayloadSet(&coapMsg, NULL, 0); + Quos_coapMsgSend(coapFd, NULL, &coapMsg, CoapSocketRecvNodeCb, FALSE); +#else + if (FALSE == Quos_coapInit(&coapFd, "221.229.214.202:5683", CoapConnRecvHandle)) + { + printf("Quos_coapInit fail\n"); + return -1; + } + + char *payload = HAL_MALLOC(56); + HAL_STRCPY(payload, ";rt=\"oma.lwm2m\",,,,,"); + char *option = HAL_MALLOC(HAL_STRLEN("rd?b=U&lwm2m=1.0<=86400&ep=") + HAL_STRLEN(DEV_UUID) + 1); + HAL_SPRINTF(option, "%s%s", "rd?b=U&lwm2m=1.0<=300000&ep=", DEV_UUID); + Coap_Message_t coapMsg; + Quos_coapHeadSet(&coapMsg, COAP_HTYPE_CON, COAP_HCODE_POST, 0, 8, NULL); + Quos_coapOptionSetPath(&coapMsg, option); + Quos_coapPayloadSet(&coapMsg, payload, HAL_STRLEN(payload)); + Quos_coapMsgSend(coapFd, NULL, &coapMsg, CoapSocketRecvNodeCb, FALSE); +#endif + + while (1) + { + QIot_state_e status = Ql_iotGetWorkState(); + printf("work status:%d\r\n", status); + sleep(10); + CoapTestDataNotify(); + } +} diff --git a/app/demo_quec_openc/Ql_iotMain.c b/app/demo_quec_openc/mqtt/Ql_iotMain.c similarity index 68% rename from app/demo_quec_openc/Ql_iotMain.c rename to app/demo_quec_openc/mqtt/Ql_iotMain.c index ee5f829..f553da3 100644 --- a/app/demo_quec_openc/Ql_iotMain.c +++ b/app/demo_quec_openc/mqtt/Ql_iotMain.c @@ -1,313 +1,343 @@ -/************************************************************************* -** 创建人 @author : 吴健超 JCWu -** 版本 @version : V1.0.0 原始版本 -** 日期 @date : -** 功能 @brief : -** 硬件 @hardware:任何ANSI-C平台 -** 其他 @other : -***************************************************************************/ -#include "Ql_iotApi.h" -#include "Qhal_driver.h" -#define QIOT_MQTT_REGISTER_URL "http://iot-south.quectel.com:2883" -#define QIOT_MQTT_PRODUCT_KEY "p1115X" -#define QIOT_MQTT_PRODUCT_SECRET "d2c5Q1FsVWpwT1k3" - -#define QIOT_COAP_BOOTSTRAP_URL "coap://220.180.239.212:8416" -#define QIOT_COAP_PRODUCT_KEY "MXJac1VyQUV1emZj" -#define QIOT_COAP_PRODUCT_SECRET "NlZGU3JUR0pYRGlR" - -#define QIOT_MCU_COMPONENT_NO "MCU" -#define QIOT_MCU_VERSION "1" - -#define LAPP_MAIN LL_DBG - - -/************************************************************************** -** 功能 @brief : -** 输入 @param : -** 输出 @retval: -***************************************************************************/ -void FUNCTION_ATTR_ROM Ql_iotTtlvHandle(const void *ttlvHead) -{ - quint32_t count = Ql_iotTtlvCountGet(ttlvHead); - quint32_t i; - for(i=0;iparm; + *tout = TRUE; +} + +/************************************************************************** +** 功能 @brief : 统计丢包率 阻塞 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void ql_lossrate_task(void) +{ + quint16_t i = 0, j = 0; + quint16_t mode = 1; + quint32_t responseTime; + Systick_T pretime, nextime; + quint64_t averageTime = 0; + qbool timeout = FALSE; + float lossrate = 0.0, averageTime_f = 0.0; + while (i < QLDEMO_NETCHECKTIMES) + { + i++; + Quos_swTimerStart(&QIot_netcheckt, "NET", SWT_ONE_SECOND * 5, 0, ql_iotNetcheckTimeoutCB, &timeout); + Ql_iotCmdBusPassTransSend(mode, (quint8_t *)QLDEMO_NETCHECKSTR, sizeof(QLDEMO_NETCHECKSTR)); + timeout = FALSE; + pretime = Quos_sysTickGet(); + do + { + usleep(1000); + } while ((ql_transSend_flag == TRANSDATA_NONACK) && (!timeout)); + switch (ql_transSend_flag) + { + case TRANSDATA_SUCCEED: + { + j++; + nextime = Quos_sysTickGet(); + responseTime = Quos_sysTickdiff(pretime, nextime, TRUE); + averageTime += responseTime; + break; + } + case TRANSDATA_FAILED: + + case TRANSDATA_NONACK: /* timeout */ + { + HAL_PRINTF("connection failed. check connection\r\n"); + break; + } + default: + + break; + } + Quos_swTimerDelete(QIot_netcheckt); + ql_transSend_flag = TRANSDATA_NONACK; + sleep(1); + } + if (j) + { + averageTime /= j; + } + averageTime_f = averageTime; + averageTime_f /= 1000; + if (i) + { + lossrate = (float)(i - j) / (float)i; + } + else + lossrate = 1.0; + printf("message sended %d times, pkg lossrate is %.3f%%.\r\n", i, lossrate * 100); + printf("average responsetime is %.3f seconds.\r\n", averageTime_f); +} + +/************************************************************************** +** 功能 @brief : 程序初始化入口 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +int FUNCTION_ATTR_ROM main(void) +{ + /* 初始化quecsdk */ + Ql_iotInit(); + /* 注册事件回调函数 */ + Ql_iotConfigSetEventCB(Ql_iotEventCB); + /* 配置产品信息*/ + Ql_iotConfigSetProductinfo(QIOT_MQTT_PRODUCT_KEY, QIOT_MQTT_PRODUCT_SECRET); + /* 配置服务器信息,可选,默认连接MQTT生产环境服务器 */ + Ql_iotConfigSetServer(QIOT_PPROTOCOL_MQTT, QIOT_MQTT_REGISTER_URL); + /* 启动云平台连接 */ + Ql_iotConfigSetConnmode(QIOT_CONNMODE_REQ); + while (1) + { + QIot_state_e status = Ql_iotGetWorkState(); + printf("work status:%d\r\n", status); + ql_lossrate_task(); + sleep(100); + } +} diff --git a/app/demo_quec_python/Ql_iotMain.py.bak b/app/demo_quec_python/Ql_iotMain.py.bak deleted file mode 100644 index 554d6840045234ccaef1efea9c0c26b81f12cbb8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4413 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 4) - humidity = (humidity/(1 << 20)) * 100.0 - print("current humidity is {0}%".format(humidity)) - temperature = ((r_data[2] & 0xf) << 16) | ( - r_data[3] << 8) | r_data[4] - temperature = (temperature * 200.0 / (1 << 20)) - 50 - print("current temperature is {0}掳C".format(temperature)) - return humidity, temperature - - - def sensor_init(self): - # calibration - self.write_data([self.AHT10_CALIBRATION_CMD, 0x08, 0x00]) - time.sleep_ms(300) # at last 300ms - pass - - def ath10_reset(self): - self.write_data([self.AHT10_RESET_CMD]) - time.sleep_ms(20) # at last 20ms - - def trigger_measurement(self): - # Trigger data conversion - self.write_data([self.AHT10_START_MEASURMENT_CMD, 0x33, 0x00]) - time.sleep_ms(200) # at last delay 75ms - # check has success - r_data = self.read_data(6) - # check bit7 - if (r_data[0] >> 7) != 0x0: - print("Conversion has error") - return None - else: - return self.aht10_transformation_temperature(r_data[1:6]) - - -def i2c_aht10_test(): - ath_dev = aht10class() - ath_dev.aht10_init() - - # 娴嬭瘯鍗佹 - for i in range(10): - ath_dev.Trigger_measurement() - time.sleep(1) - - -class Quecthing: - def __init__(self): - """ 鍒濆鍖杚ucsdk """ - quecIot.init() - """ 娉ㄥ唽浜嬩欢鍥炶皟鍑芥暟 """ - quecIot.setEventCB(self.eventCB) - """ 閰嶇疆浜у搧淇℃伅""" - quecIot.setProductinfo("p1116a", "UHg1dTRBRVh3MkVG") - """ 閰嶇疆鏈嶅姟鍣ㄤ俊鎭紝鍙夛紝榛樿杩炴帴MQTT鐢熶骇鐜鏈嶅姟鍣 """ - quecIot.setServer(1,"http://iot-south.quectel.com:2883") - """ 閰嶇疆lifetime锛屽彲閫夛紝MQTT榛樿涓120 """ - quecIot.setLifetime(120) - """ 閰嶇疆澶栭儴MCU鏍囪瘑鍙峰拰鐗堟湰鍙凤紝鍙夛紝濡傛病鏈夊閮∕CU鍒欎笉闇瑕侀厤缃 """ - quecIot.setMcuVersion("MCU1", "1_0_0") - - """ 鍒涘缓瀹氭椂鍣ㄤ换鍔 """ - ostimer = osTimer() - """ 姣忓崄绉掕皟鐢ㄤ竴娆″洖璋冨嚱鏁 """ - ostimer.start(10000, 1, self.mainTask) - - """" 鍒涘缓gpio瀵硅薄 """ - self.gpio1 = Pin(Pin.GPIO1, Pin.OUT, Pin.PULL_DISABLE, 0) - - """ 鍒濆鍖朼ht10 """ - self.ath_dev = aht10class() - self.ath_dev.aht10_init() - - """ 璁剧疆鑷姩浼戠湢妯″紡 """ - pm.autosleep(1) - - """ 鍚姩浜戝钩鍙拌繛鎺 """ - quecIot.setConnmode(1) - - @staticmethod - def eventCB(data): - print("\r\n{},{}\r\n".format(str(data[0]), str(data[1]))) - if len(data) == 3: - print(data[2]) - """ - 娴嬭瘯鍙戦佺墿妯″瀷鏁版嵁 - 鍚戝钩鍙板彂閫佹暟鎹渶瑕佸湪杩斿洖3, 10200涔嬪悗杩涜 - """ - if 1 == data[0] and 10422 == data[1]: - quecIot.setConnmode(0) - exit(0) - elif 3 == data[0] and 10200 == data[1]: - global data_trans_ready - data_trans_ready = True - - - """ 鍙戦乥ool鍨嬫暟鎹""" - quecIot.phymodelReport(1, {2: True}) - """ 鍙戦佹暟鍊 """ - # 鏁存暟 - quecIot.phymodelReport(1, {3: 123}) - # 娴偣鏁 - quecIot.phymodelReport(1, {9: 123.123}) - """ 鍙戦乤rray """ - quecIot.phymodelReport(1, {4: [1, 2, 3]}) - """ 鍙戦佺粨鏋勪綋 """ - quecIot.phymodelReport(1, {6: {8: 1.0, 7: 1.0}}) - - """ 鍙戦佷腑鏂,閫忎紶鏁版嵁鍜岀墿妯″瀷鏁版嵁鏃犳硶鍚屾椂鍦ㄥ钩鍙拌皟璇 """ - - """ - bytes_temp = bytes('涓枃'.encode('utf-8')) - quecIot.passTransSend(1, bytes_temp) - """ - - def mainTask(self, argv): - global data_trans_ready - global gpio_toggle_flag - """ - 涓讳换鍔,涓婃姤鐢垫睜鐢靛帇 - """ - if data_trans_ready: - print('main task') - """ 鑾峰彇鐢靛帇 """ - battery_vol = get_battery_vol() - - temperature = self.ath_dev.trigger_measurement() - """ 鍙戦佺墿妯″瀷鏁版嵁 """ - quecIot.phymodelReport(0, {110 : battery_vol}) - quecIot.phymodelReport(0, {111 : temperature}) - - """ 缈昏浆寮曡剼鐢靛钩 """ - # gpio_toggle_flag = not gpio_toggle_flag - # self.gpio1.write(int(gpio_toggle_flag)) - - - -if __name__ == '__main__': - print('\r\n**********\r\n') - print(DEMO_VERSION) - quecthing = Quecthing() diff --git a/app/demo_quec_python/example1.py b/app/demo_quec_python/example1.py new file mode 100644 index 0000000..48ebbe3 --- /dev/null +++ b/app/demo_quec_python/example1.py @@ -0,0 +1,83 @@ +# 璁剧疆GPIO楂樼數骞冲苟璁剧疆瀹氭椂鍣ㄦ寚瀹氭椂闀垮悗鎷変綆GPIO + +import quecIot +from machine import Timer +from machine import Pin + +timer1 = None +TimerCB = None +Gpio_relay = None +remaining_time =0 + +class Quecthing: + + def __init__(self): + global Gpio_relay + global timer1 + global TimerCB + + """ 鍒濆鍖杚ucsdk """ + quecIot.init() + """ 娉ㄥ唽浜嬩欢鍥炶皟鍑芥暟 """ + quecIot.setEventCB(self.eventCB) + """ 閰嶇疆浜у搧淇℃伅""" + quecIot.setProductinfo("p1117y", "OFZCcWt1TjVUeUdn") + """ 鍚姩浜戝钩鍙拌繛鎺 """ + quecIot.setConnmode(1) + timer1 = Timer(Timer.Timer1) + TimerCB = self.timerTask + Gpio_relay = Pin(Pin.GPIO10, Pin.OUT, Pin.PULL_DISABLE, 0) + + @staticmethod + def eventCB(data): + global Gpio_relay + global timer1 + global TimerCB + global remaining_time + + print(str(data[0]) + "," + str(data[1])) + if len(data) == 3: + print(data[2]) + + if 5 == data[0] and 10210 == data[1]: + model = data[2] + print(model.keys()) + model_keys = list(model.keys()) + for cmdId in model_keys: + value = model.get(cmdId); + print("ctrl cmdId:"+str(cmdId)) + print(value) + if 2 == cmdId: + Gpio_relay.write(1) + remaining_time = value + timer1.stop() + timer1.start(period=60 * 1000,mode=Timer.PERIODIC, callback=TimerCB) + + elif 5 == data[0] and 10211 == data[1]: + res_data = dict() + msg = data[2] + pkgId = msg[0] + + for cmdId in msg[1]: + if 1 == cmdId: + res_data[cmdId]=Gpio_relay.read() + elif 2 == cmdId: + res_data[cmdId]=remaining_time + quecIot.phymodelAck(0, pkgId, res_data) + print("read") + print(res_data) + + def timerTask(self, t): + global Gpio_relay + global remaining_time + remaining_time =remaining_time-1 + if 0 == remaining_time: + Gpio_relay.write(0) + res_data = dict() + res_data[1]=Gpio_relay.read() + quecIot.phymodelReport(1,res_data) + print("timeout") + + +if __name__ == '__main__': + Quecthing() diff --git a/app/demo_quec_python/Ql_iotMain.py b/app/demo_quec_python/example2.py similarity index 33% rename from app/demo_quec_python/Ql_iotMain.py rename to app/demo_quec_python/example2.py index 2bc2d9a..a5e7f36 100644 --- a/app/demo_quec_python/Ql_iotMain.py +++ b/app/demo_quec_python/example2.py @@ -1,145 +1,52 @@ -# Copyright 2020 - 2021 quectel -# -*- coding: utf-8 -*- -# @Time : 2021-03-26 -# @Author : evan.li -# @File : Ql_iotMain.py -# @Brief : quecthing -# @revise : -# 2021-05-26 add get_battery_vol - -import quecIot -import misc -import utime as time -import pm -import osTimer -import uos -from machine import Pin - -DEMO_VERSION = '21060101' - - -data_trans_ready = False -gpio_toggle_flag = False - -def get_battery_vol(): - """ - 鑾峰彇鐢垫睜鐢靛帇锛屽崟浣峬V - """ - return misc.Power.getVbatt() - -""" - 妯$粍OTA鍗囩骇鍜宲ython 鑴氭湰鍗囩骇 -""" -def ota_event(event): - if 7 == event[0] and 10700 == event[1]: - """ 妫鏌ョ粍浠舵爣蹇, 缁勪欢鏍囧織鍦ㄥ钩鍙板垱寤 """ - if "SCRIPT" in event[2]: - quecIot.otaAction(1) - elif "IMEI" in event[2]: - quecIot.otaAction(1) - elif 7 == event[0] and 10701 == event[1]: - pass - elif 7 == event[0] and 10703 == event[1]: - if "SCRIPT" in event[2]: - - uos.rename('usr/demo_quecthing.py', 'usr/demo_quecthing.bk') - uos.rename('usr/qiot_ota.bin', 'usr/demo_quecthing.py') - - """ 璁剧疆鐗堟湰, 涓婃姤鍗囩骇鎴愬姛""" - quecIot.setMcuVersion('SCRIPT', 'v2') - elif "IMEI" in event[2]: - pass - - -class Quecthing: - def __init__(self): - """ 鍒濆鍖杚ucsdk """ - quecIot.init() - """ 娉ㄥ唽浜嬩欢鍥炶皟鍑芥暟 """ - quecIot.setEventCB(self.eventCB) - """ 閰嶇疆浜у搧淇℃伅""" - quecIot.setProductinfo("p1116a", "UHg1dTRBRVh3MkVG") - """ 閰嶇疆鏈嶅姟鍣ㄤ俊鎭紝鍙夛紝榛樿杩炴帴MQTT鐢熶骇鐜鏈嶅姟鍣 """ - quecIot.setServer(1,"http://iot-south.quectel.com:2883") - """ 閰嶇疆lifetime锛屽彲閫夛紝MQTT榛樿涓120 """ - quecIot.setLifetime(120) - """ 閰嶇疆澶栭儴MCU鏍囪瘑鍙峰拰鐗堟湰鍙凤紝鍙夛紝濡傛病鏈夊閮∕CU鍒欎笉闇瑕侀厤缃 """ - quecIot.setMcuVersion("MCU1", "1_0_0") - - """ 鍒涘缓瀹氭椂鍣ㄤ换鍔 """ - ostimer = osTimer() - """ 姣忎竴鐧剧璋冪敤涓娆″洖璋冨嚱鏁 """ - ostimer.start(100000, 1, self.mainTask) - - """" 鍒涘缓gpio瀵硅薄 """ - self.gpio1 = Pin(Pin.GPIO1, Pin.OUT, Pin.PULL_DISABLE, 0) - - - """ 璁剧疆鑷姩浼戠湢妯″紡 """ - pm.autosleep(1) - - """ 鍚姩浜戝钩鍙拌繛鎺 """ - quecIot.setConnmode(1) - - @staticmethod - def eventCB(data): - print("\r\n{},{}\r\n".format(str(data[0]), str(data[1]))) - if len(data) == 3: - print(data[2]) - - ota_event(data) - if 1 == data[0] and 10422 == data[1]: - quecIot.setConnmode(0) - exit(0) - elif 3 == data[0] and 10200 == data[1]: - """ - 娴嬭瘯鍙戦佺墿妯″瀷鏁版嵁 - 鍚戝钩鍙板彂閫佹暟鎹渶瑕佸湪杩斿洖3, 10200涔嬪悗杩涜 - """ - global data_trans_ready - data_trans_ready = True - - - """ 鍙戦乥ool鍨嬫暟鎹""" - quecIot.phymodelReport(1, {2: True}) - """ 鍙戦佹暟鍊 """ - # 鏁存暟 - quecIot.phymodelReport(1, {3: 123}) - # 娴偣鏁 - quecIot.phymodelReport(1, {9: 123.123}) - """ 鍙戦乤rray """ - quecIot.phymodelReport(1, {4: [1, 2, 3]}) - """ 鍙戦佺粨鏋勪綋 """ - quecIot.phymodelReport(1, {6: {8: 1.0, 7: 1.0}}) - - """ 鍙戦佷腑鏂,閫忎紶鏁版嵁鍜岀墿妯″瀷鏁版嵁鏃犳硶鍚屾椂鍦ㄥ钩鍙拌皟璇 """ - - """ - bytes_temp = bytes('涓枃'.encode('utf-8')) - quecIot.passTransSend(1, bytes_temp) - """ - - def mainTask(self, argv): - global data_trans_ready - global gpio_toggle_flag - """ - 涓讳换鍔,涓婃姤鐢垫睜鐢靛帇 - """ - if data_trans_ready: - print('main task') - """ 鑾峰彇鐢靛帇 """ - battery_vol = get_battery_vol() - - """ 鍙戦佺墿妯″瀷鏁版嵁 """ - quecIot.phymodelReport(0, {110 : battery_vol}) - - """ 缈昏浆寮曡剼鐢靛钩 """ - # gpio_toggle_flag = not gpio_toggle_flag - # self.gpio1.write(int(gpio_toggle_flag)) - - - -if __name__ == '__main__': - print('\r\n**********\r\n') - print(DEMO_VERSION) - quecthing = Quecthing() +# -*- coding: utf-8 -*- +# @Time : 2021-03-26 +# @Author : evan.li +# @File : main.py +# @Brief : quecthing +# @revise : +# 2021-05-26 add get_battery_vol + +import quecIot + + +class Quecthing: + def __init__(self): + """ 鍒濆鍖杚ucsdk """ + quecIot.init() + """ 娉ㄥ唽浜嬩欢鍥炶皟鍑芥暟 """ + quecIot.setEventCB(self.eventCB) + """ 閰嶇疆浜у搧淇℃伅""" + quecIot.setProductinfo("p1115X", "d2c5Q1FsVWpwT1k3") + """ 閰嶇疆鏈嶅姟鍣ㄤ俊鎭紝鍙夛紝榛樿杩炴帴MQTT鐢熶骇鐜鏈嶅姟鍣 """ + quecIot.setServer(1,"http://iot-south.quectel.com:2883") + """ 閰嶇疆lifetime锛屽彲閫夛紝MQTT榛樿涓120 """ + quecIot.setLifetime(120) + """ 閰嶇疆澶栭儴MCU鏍囪瘑鍙峰拰鐗堟湰鍙凤紝鍙夛紝濡傛病鏈夊閮∕CU鍒欎笉闇瑕侀厤缃 """ + quecIot.setMcuVersion("MCU1", "1_0_0") + """ 鍚姩浜戝钩鍙拌繛鎺 """ + quecIot.setConnmode(1) + + @staticmethod + def eventCB(data): + print(str(data[0]) + "," + str(data[1]) + "\r\n") + if len(data) == 3: + print(data[2]) + """ + 娴嬭瘯鍙戦佺墿妯″瀷鏁版嵁 + 鍚戝钩鍙板彂閫佹暟鎹渶瑕佸湪杩斿洖3, 10200涔嬪悗杩涜 + """ + if 3 == data[0] and 10200 == data[1]: + """ 鍙戦乥ool鍨嬫暟鎹""" + quecIot.phymodelReport(1, {2: True}) + """ 鍙戦佹暟鍊 """ + # 鏁存暟 + quecIot.phymodelReport(1, {3: 123}) + # 娴偣鏁 + quecIot.phymodelReport(1, {9: 123.123}) + """ 鍙戦乤rray """ + quecIot.phymodelReport(1, {4: [1, 2, 3]}) + """ 鍙戦佺粨鏋勪綋 """ + quecIot.phymodelReport(1, {6: {8: 1.0, 7: 1.0}}) + +if __name__ == '__main__': + Quecthing() diff --git a/app/demo_quec_python/example3.py b/app/demo_quec_python/example3.py new file mode 100644 index 0000000..887cb0b --- /dev/null +++ b/app/demo_quec_python/example3.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# @Time : 2021-03-26 +# @Author : evan.li +# @File : main.py +# @Brief : quecthing +# @revise : +# 2021-05-26 add get_battery_vol + +import quecIot +from machine import Timer + +class Quecthing: + def __init__(self): + """ 鍒濆鍖杚ucsdk """ + quecIot.init() + """ 娉ㄥ唽浜嬩欢鍥炶皟鍑芥暟 """ + quecIot.setEventCB(self.eventCB) + """ 閰嶇疆浜у搧淇℃伅""" + quecIot.setProductinfo("p111HL", "WVF1aHYyMlVXVUl4") + """ 閰嶇疆鏈嶅姟鍣ㄤ俊鎭紝鍙夛紝榛樿杩炴帴MQTT鐢熶骇鐜鏈嶅姟鍣 """ + quecIot.setServer(1,"http://iot-south.quectel.com:2883") + """ 閰嶇疆lifetime锛屽彲閫夛紝MQTT榛樿涓120 """ + quecIot.setLifetime(120) + """ 閰嶇疆澶栭儴MCU鏍囪瘑鍙峰拰鐗堟湰鍙凤紝鍙夛紝濡傛病鏈夊閮∕CU鍒欎笉闇瑕侀厤缃 """ + quecIot.setMcuVersion("MCU1", "1_0_0") + """ 鍚姩浜戝钩鍙拌繛鎺 """ + quecIot.setConnmode(1) + + """ 閰嶇疆瀛愯澶囧洖璋冨嚱鏁 """ + quecIot.subDevSetEventCB(subDevEventCB) + + def subDev_PassTransMode(self): + """ 瀛愯澶囧彂璧疯璇佸埌骞冲彴 """ + quecIot.subDevConn("p111HM", "VzY3dGo2UEF5eDE5", "8EEC4B66AEE8", 0, 120) + """ 瀛愯澶囩櫥闄嗗埌骞冲彴 """ + """ 鑻ュ瓙璁惧宸茶璇佸埌骞冲彴锛屼箣鍚庤皟鐢ㄧ櫥闄嗘帴鍙f椂闇瑕佸皢璁よ瘉寰楀埌鐨刣s淇℃伅鏀惧埌鏂规硶涓紱濡備笅锛 + self.__ds = "1234" + quecIot.subDevConn("p111HM", "VzY3dGo2UEF5eDE5", "8EEC4B66AEE8", self.__ds, 0, 120) + """ + def passTranDev_recvDs(self,ds): + self.__ptDs = ds + print("device id 8EEC4B66AEE8 product key: p111HM, receive ds:"+str(ds)) + + def passTranDev_timerCB(self): + quecIot.subDevHTB("p111HM", "8EEC4B66AEE8") + ptTimer.stop() + ptTimer.start(period=60 * 1000,mode=Timer.PERIODIC, callback=self.passTranDev_timerCB) + + def passTranDev_connSuccess(self): + quecIot.subDevPassTransSend("p111HM", "8EEC4B66AEE8", "123456") + ptTimer = Timer(Timer.ptTimer) + ptTimer.start(period=60 * 1000,mode=Timer.PERIODIC, callback=self.passTranDev_timerCB) + + def passTranDev_timerStop(self): + ptTimer.stop() + + def subDev_TslMode(self): + """ 瀛愯澶囧彂璧疯璇佸埌骞冲彴 """ + quecIot.subDevConn("p111HN", "Vm9pcmR2Mzd4cXB0", "8EEC4B66AEE9", 0, 120) + """ 瀛愯澶囩櫥闄嗗埌骞冲彴 """ + """ 鑻ュ瓙璁惧宸茶璇佸埌骞冲彴锛屼箣鍚庤皟鐢ㄧ櫥闄嗘帴鍙f椂闇瑕佸皢璁よ瘉寰楀埌鐨刣s淇℃伅鏀惧埌鏂规硶涓紱濡備笅锛 + self.__ds = "1234" + quecIot.subDevConn("p111HN", "Vm9pcmR2Mzd4cXB0", "8EEC4B66AEE9", self.__ds, 0, 120) + """ + + def tslDev_recvDs(self,ds): + self.__tslDs = ds + print("device id 8EEC4B66AEE9 product key: p111HN, receive ds:"+str(ds)) + + def tslDev_timerCB(self): + quecIot.subDevHTB("p111HM", "8EEC4B66AEE8") + tslTimer.stop() + tslTimer.start(period=60 * 1000,mode=Timer.PERIODIC, callback=self.tslDev_timerCB) + + def tslDev_timerStop(self): + tslTimer.stop() + + def tslDev_connSuccess(self): + """ 鍙戦乥ool鍨嬫暟鎹""" + quecIot.subDevTslReport("p111HN", "8EEC4B66AEE9", {2: True}) + """ 鍙戦佹暟鍊 """ + # 鏁存暟 + quecIot.subDevTslReport("p111HN", "8EEC4B66AEE9", {3: 123}) + # 娴偣鏁 + quecIot.subDevTslReport("p111HN", "8EEC4B66AEE9", {9: 123.123}) + """ 鍙戦乤rray """ + quecIot.subDevTslReport("p111HN", "8EEC4B66AEE9", {4: [1, 2, 3]}) + """ 鍙戦佺粨鏋勪綋 """ + quecIot.subDevTslReport("p111HN", "8EEC4B66AEE9", {6: {8: 1.0, 7: 1.0}}) + tslTimer = Timer(Timer.tslTimer) + tslTimer.start(period=60 * 1000,mode=Timer.PERIODIC, callback=self.tslDev_timerCB) + + + def subDevEventCB(self,data): + print(data[0]+","+data[1]+","+str(data[2])+","+str(data[3])) + if len(data) == 5: + print(data[4]) + if 1 == data[2] and 10200 == data[3]: + if 5 == len(data): + if "p111HM" == data[0]and "8EEC4B66AEE8" == data[1]: + self.passTranDev_recvDs(data[4]) + elif "p111HN" == data[0] and "8EEC4B66AEE9" == data[1]: + self.tslDev_recvDs(data[4]) + else: + print("register platform error:" + data[3]) + + if 2 == data[2] and 10200 == data[3]: + if "p111HM" == data[0]and "8EEC4B66AEE8" == data[1]: + self.passTranDev_connSuccess() + elif "p111HN" == data[0] and "8EEC4B66AEE9" == data[1]: + self.tslDev_connSuccess() + + if 6 == data[2] and 10200 == data[3]: + if "p111HM" == data[0]and "8EEC4B66AEE8" == data[1]: + self.passTranDev_timerStop() + elif "p111HN" == data[0] and "8EEC4B66AEE9" == data[1]: + self.tslDev_timerStop() + + + @staticmethod + def eventCB(data): + print(str(data[0]) + "," + str(data[1]) + "\r\n") + + if len(data) == 3: + print(data[2]) + """ + 娴嬭瘯鍙戦佺墿妯″瀷鏁版嵁 + 鍚戝钩鍙板彂閫佹暟鎹渶瑕佸湪杩斿洖3, 10200涔嬪悗杩涜 + """ + if 3 == data[0] and 10200 == data[1]: + """ 閫忎紶瀛愯澶 """ + Quecthing.subDev_PassTransMode() + """" 鐗╂ā鍨嬪瓙璁惧 """ + Quecthing.subDev_TslMode() + + """ 鍙戦乥ool鍨嬫暟鎹""" + quecIot.phymodelReport(1, {2: True}) + """ 鍙戦佹暟鍊 """ + # 鏁存暟 + quecIot.phymodelReport(1, {3: 123}) + # 娴偣鏁 + quecIot.phymodelReport(1, {9: 123.123}) + """ 鍙戦乤rray """ + quecIot.phymodelReport(1, {4: [1, 2, 3]}) + """ 鍙戦佺粨鏋勪綋 """ + quecIot.phymodelReport(1, {6: {8: 1.0, 7: 1.0}}) + +if __name__ == '__main__': + Quecthing() diff --git a/cloud/Ql_iotApi.h b/cloud/Ql_iotApi.h index 7adad67..8211e17 100644 --- a/cloud/Ql_iotApi.h +++ b/cloud/Ql_iotApi.h @@ -1,7 +1,33 @@ #ifndef __QIOT_API_H__ #define __QIOT_API_H__ +#if 1 #include "Quos_kernel.h" +#else +#include "../driverLayer/Qhal_types.h" +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==QUOS_cJSON_String and type == QUOS_cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==QUOS_cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +#endif enum { QIOT_ATEVENT_TYPE_AUTH = 1, @@ -12,10 +38,12 @@ enum QIOT_ATEVENT_TYPE_LOGOUT = 6, QIOT_ATEVENT_TYPE_OTA = 7, QIOT_ATEVENT_TYPE_SERVER = 8, + QIOT_ATEVENT_TYPE_UNAUTH = 9, }; enum { QIOT_AUTH_SUCC = 10200, /* 设备认证成功 */ + QIOT_AUTH_ERR_DMP_INSIDE = 10404, /* DMP内部接口调用失败 */ QIOT_AUTH_ERR_REQDATA = 10420, /* 请求数据错误(连接失败)*/ QIOT_AUTH_ERR_DONE = 10422, /* 设备已认证(连接失败)*/ QIOT_AUTH_ERR_PKPS_INVALID = 10423, /* 没有找到产品信息(连接失败)*/ @@ -26,6 +54,8 @@ enum QIOT_AUTH_ERR_PK_CHANGE = 10430, /* PK发生改变 */ QIOT_AUTH_ERR_DK_ILLEGAL = 10431, /* DK不合法 */ QIOT_AUTH_ERR_PK_VER_NOCOR = 10432, /* PK与认证版本不匹配 */ + QIOT_AUTH_ERR_PSWORD = 10436, /* 登录用户名错误 */ + QIOT_AUTH_ERR_DEVICE_INFO = 10438, /* 没有找到设备信息 */ QIOT_AUTH_ERR_DEVICE_INSIDE = 10450, /* 设备内部错误(连接失败)*/ QIOT_AUTH_ERR_SERVER_NOTFOUND = 10466, /* 引导服务器地址未找到(连接失败)*/ QIOT_AUTH_ERR_FAIL = 10500, /* 设备认证失败(系统发生未知异常)*/ @@ -34,12 +64,18 @@ enum enum { QIOT_CONN_SUCC = 10200, /* 接入成功 */ + QIOT_CONN_ERR_DMP_INSIDE = 10404, /* DMP内部接口调用失败 */ QIOT_CONN_ERR_DS_INVALID = 10430, /* 设备密钥不正确(连接失败)*/ QIOT_CONN_ERR_DEVICE_FORBID = 10431, /* 设备被禁用(连接失败)*/ + QIOT_CONN_ERR_PSWORD = 10436, /* 登录用户名错误 */ + QIOT_CONN_ERR_DS = 10437, /* 设备DS错误 */ + QIOT_CONN_ERR_DEVICE_INFO = 10438, /* 没有找到设备信息 */ QIOT_CONN_ERR_DEVICE_INSIDE = 10450, /* 设备内部错误(连接失败)*/ QIOT_CONN_ERR_VERSION_NOTFOUND = 10471, /* 实现方案版本不支持(连接失败)*/ QIOT_CONN_ERR_PING = 10473, /* 接入心跳异常 */ QIOT_CONN_ERR_NET = 10474, /* 网络异常 */ + QIOT_CONN_ERR_SERVER_CHANGE = 10475, /* 服务器发生改变 */ + QIOT_CONN_ERR_AP = 10476, /* 连接AP异常 */ QIOT_CONN_ERR_UNKNOW = 10500, /* 接入失败(系统发生未知异常)*/ }; enum @@ -55,12 +91,18 @@ enum QIOT_SEND_ERR_PHYMODEL = 10310, /* 物模型数据发送失败 */ QIOT_SEND_SUCC_LOC = 10220, /* 定位数据发送成功 */ QIOT_SEND_ERR_FAIL_LOC = 10320, /* 定位数据发送失败 */ + QIOT_SEND_SUCC_STATE = 10230, /* 状态数据发送成功 */ + QIOT_SEND_ERR_STATE = 10330, /* 状态数据发送失败 */ + QIOT_SEND_SUCC_INFO = 10240, /* 设备信息发送成功 */ + QIOT_SEND_ERR_INFO = 10340, /* 设备信息发送失败 */ }; enum { QIOT_RECV_SUCC_TRANS = 10200, /* 收到透传数据 */ QIOT_RECV_SUCC_PHYMODEL_RECV = 10210, /* 收到物模型下发数据 */ QIOT_RECV_SUCC_PHYMODEL_REQ = 10211, /* 收到物模型请求数据 */ + QIOT_RECV_SUCC_SUB_STATE_REQ = 10220, /* 收到子设备状态请求数据 */ + QIOT_RECV_SUCC_SUB_INFO_REQ = 10230, /* 收到子设备信息请求数据 */ QIOT_RECV_ERR_BUFFER = 10473, /* 接收失败,收到数据但长度超过模组buffer限制,AT非缓存模式下有效*/ QIOT_RECV_ERR_LIMIT = 10428, /* 数据接收失败,设备被限制消息通信,缓存模式下有效 */ }; @@ -77,20 +119,28 @@ enum QIOT_OTA_UPDATING = 10704, /* 包更新中 */ QIOT_OTA_UPDATE_OK = 10705, /* 包更新完成 */ QIOT_OTA_UPDATE_FAIL = 10706, /* 包更新失败 */ + QIOT_OTA_UPDATE_FLAG = 10707, /* 首个设备操作结果广播 */ }; enum { QIOT_SERVER_ERRCODE_RATE_LIMIT = 10428, QIOT_SERVER_ERRCODE_QUANTITY_LIMIT = 10429, }; - +enum +{ + QIOT_SUB_DEV_ERR_No_ASSOCIATION = 10440, /* 子设备与当前网关没有关联关系 */ + QIOT_SUB_DEV_ERR_ALREADY_CONN = 10441, /* 子设备重复登录 */ + QIOT_SUB_DEV_ERR_UNLOGIN = 10442, /* 子设备未登录 */ +}; /* ql_iotDp.h */ typedef enum { QIOT_DPCMD_TYPE_SYS = 0, /* sys类型命令 */ QIOT_DPCMD_TYPE_BUS, /* 业务数据类型命令*/ QIOT_DPCMD_TYPE_OTA, /* OTA类型命令 */ + QIOT_DPCMD_TYPE_LAN, /* LAN类型命令 */ } QIot_dpCmdType_e; + typedef enum { QIOT_DPDATA_TYPE_BOOL = 0, @@ -123,17 +173,26 @@ void *Ql_iotTtlvIdGetStruct(const void *ttlvHead, quint16_t id); qbool Ql_iotTtlvIdAddBool(void **ttlvHead, quint16_t id, qbool value); qbool Ql_iotTtlvIdAddInt(void **ttlvHead, quint16_t id, qint64_t num); qbool Ql_iotTtlvIdAddFloat(void **ttlvHead, quint16_t id, double num); -qbool Ql_iotTtlvIdAddByte(void **ttlvHead, quint16_t id, quint8_t *data, quint32_t len); +qbool Ql_iotTtlvIdAddByte(void **ttlvHead, quint16_t id, const quint8_t *data, quint32_t len); +qbool Ql_iotTtlvIdAddString(void **ttlvHead, quint16_t id, const char *data); qbool Ql_iotTtlvIdAddStruct(void **ttlvHead, quint16_t id, void *vStruct); -#define Ql_iotTtlvIdAddString(ttlvHead, id, data) Ql_iotTtlvIdAddByte(ttlvHead, id, (quint8_t *)data, HAL_STRLEN(data)) +//#define Ql_iotTtlvIdAddString(ttlvHead, id, data) Ql_iotTtlvIdAddByte(ttlvHead, id, (quint8_t *)data, HAL_STRLEN(data)) +void *Ql_iotJson2Ttlv(const cJSON *json); +cJSON *Ql_iotTtlv2Json(const void *ttlvHead); /* ql_iotCmdBus.h */ qbool Ql_iotCmdBusPassTransSend(quint16_t mode, quint8_t *payload, quint32_t len); qbool Ql_iotCmdBusPhymodelReport(quint16_t mode, const void *ttlvHead); qbool Ql_iotCmdBusPhymodelAck(quint16_t mode, quint16_t pkgId, const void *ttlvHead); -qbool Ql_iotCmdBusLocReport(void); + +/* ql_iotCmdLoc.h */ +qbool Ql_iotCmdBusLocReportInside(void *titleTtlv); +qbool Ql_iotCmdBusLocReportOutside(void *nmeaTtlv); +void *Ql_iotLocGetData(const void *titleTtlv); +void *Ql_iotLocGetSupList(void); /* ql_iotCmdOTA.h */ +qbool Ql_iotCmdOtaRequest(quint32_t mode); qbool Ql_iotCmdOtaAction(quint8_t action); quint32_t Ql_iotCmdOtaMcuFWDataRead(quint32_t startAddr, quint8_t data[], quint32_t maxLen); @@ -163,15 +222,25 @@ enum QIOT_DPID_INFO_LAC = 8, /* 位置区代码 */ QIOT_DPID_INFO_PHONE_NUM = 9, /* phone号 */ QIOT_DPID_INFO_SIM_NUM = 10, /* SIM号 */ - QIOT_DPID_INFO_SDK_VER = 11, /* IOT SDK版本号*/ + QIOT_DPID_INFO_SDK_VER = 11, /* quecthingSDK版本号*/ QIOT_DPID_INFO_LOC_SUPLIST = 12, /* 定位功能支持列表 */ + QIOT_DPIO_INFO_DP_VER = 13, /* 数据协议版本 */ + QIOT_DPIO_INFO_CP_VER = 14, /* 通信协议版本号 */ QIOT_DPID_INFO_MAX, }; qbool Ql_iotCmdSysStatusReport(quint16_t ids[], quint32_t size); qbool Ql_iotCmdSysDevInfoReport(quint16_t ids[], quint32_t size); - +void *Ql_iotSysGetDevStatus(quint16_t ids[], quint32_t size); +void *Ql_iotSysGetDevInfo(quint16_t ids[], quint32_t size); +qbool Ql_iotCmdBindcodeReport(quint8_t bindcode[], quint32_t len); /* ql_iotConn.h */ +typedef enum +{ + QIOT_DPAPP_M2M = (1 << 0), + QIOT_DPAPP_SUBDEV = (1 << 1), + QIOT_DPAPP_LANPHONE = (1 << 2), +} QIot_dpAppType_e; /* ql_iotConfig.h */ typedef enum @@ -188,45 +257,65 @@ typedef enum typedef enum { QIOT_STATE_UNINITIALIZE = 0, - QIOT_STATE_INITIALIZED, - QIOT_STATE_AUTHENTICATING, - QIOT_STATE_AUTHENTICATED, - QIOT_STATE_AUTHENTICATE_FAILED, - QIOT_STATE_CONNECTING, - QIOT_STATE_CONNECTED, - QIOT_STATE_CONNECT_FAIL, - QIOT_STATE_SUBSCRIBED, - QIOT_STATE_SUBSCRIBE_FAIL, - QIOT_STATE_DISCONNECTING, - QIOT_STATE_DISCONNECTED, - QIOT_STATE_DISCONNECT_FAIL, + QIOT_STATE_INITIALIZED = 1, + QIOT_STATE_AUTHENTICATING = 2, + QIOT_STATE_AUTHENTICATED = 3, + QIOT_STATE_AUTHENTICATE_FAILED = 4, + QIOT_STATE_CONNECTING = 5, + QIOT_STATE_CONNECTED = 6, + QIOT_STATE_CONNECT_FAIL = 7, + QIOT_STATE_SUBSCRIBED = 8, + QIOT_STATE_SUBSCRIBE_FAIL = 9, + QIOT_STATE_DISCONNECTING = 10, + QIOT_STATE_DISCONNECTED = 11, + QIOT_STATE_DISCONNECT_FAIL = 12, } QIot_state_e; -void Ql_iotInit(void); +qbool Ql_iotInit(void); qbool Ql_iotConfigSetConnmode(QIot_connMode_e mode); QIot_connMode_e Ql_iotConfigGetConnmode(void); -qbool Ql_iotConfigSetPdpContextId(quint8_t contextID); -quint8_t Ql_iotConfigGetPdpContextId(void); +qbool Ql_iotConfigSetProductinfo(const char *pk, const char *ps); +void Ql_iotConfigGetProductinfo(char **pk, char **ps, char **ver); qbool Ql_iotConfigSetServer(QIot_protocolType_t type, const char *server_url); void Ql_iotConfigGetServer(QIot_protocolType_t *type, char **server_url); qbool Ql_iotConfigSetProductinfo(const char *pk, const char *ps); void Ql_iotConfigGetProductinfo(char **pk, char **ps, char **ver); qbool Ql_iotConfigSetLifetime(quint32_t lifetime); quint32_t Ql_iotConfigGetLifetime(void); -qbool Ql_iotConfigAppendAppVersion(const char *appVer); /* 对APP层只有openC方案可用 */ +qbool Ql_iotConfigSetPdpContextId(quint8_t contextID); +quint8_t Ql_iotConfigGetPdpContextId(void); +qbool Ql_iotConfigSetSessionFlag(qbool flag); +qbool Ql_iotConfigGetSessionFlag(void); +qbool Ql_iotConfigSetAppVersion(const char *appVer); /* 对APP层只有openC方案可用 */ char *Ql_iotConfigGetSoftVersion(void); qbool Ql_iotConfigSetMcuVersion(const char *compno, const char *version); quint32_t Ql_iotConfigGetMcuVersion(const char *compno, char **version); void Ql_iotConfigSetEventCB(void (*eventCb)(quint32_t event, qint32_t errcode, const void *value, quint32_t valLen)); QIot_state_e Ql_iotGetWorkState(void); +qbool Ql_iotConfigSetDkDs(const char *dk, const char *ds); +qbool Ql_iotConfigGetDkDs(char **dk, char **ds); -/* ql_iotLocator.h */ -qbool Ql_iotLocatorConfigSet(const void *ttlvHead); -void *Ql_iotLocatorConfigGet(void); -void *Ql_iotLocatorDataGet(const void *titleTtlv); -char *Ql_iotLocatorTtlv2String(const void *ttlv); -qbool Ql_iotLocatoTitleClashCheck(const void *ttlvHead); -qbool Ql_iotLocatoTitleSupportCheck(const void *ttlvHead); - -quint32_t Quos_stringSplit(char *src, char **words, quint32_t maxSize, char *delim, qbool keepEmptyParts); +/* ql_fotaConfig.h */ +#ifdef QUEC_ENABLE_HTTP_OTA +void Ql_iotConfigSetHttpOtaEventCb(void (*eventCb)(quint32_t event, qint32_t errcode, const void *value, quint32_t valLen)); +qbool Ql_iotConfigSetHttpOtaProductInfo(const char *pk, const char *ps); +void Ql_iotConfigGetHttpOtaProductInfo(char **pk, char **ps); +qbool Ql_iotConfigSetHttpOtaTls(qbool tls); +qbool Ql_iotConfigGetHttpOtaTls(void); +qbool Ql_iotConfigSetHttpOtaServer(const char *server_url); +void Ql_iotConfigGetHttpOtaServer(char **server_url); +qbool Ql_iotConfigSetHttpOtaUp(quint8_t battery, quint8_t upmode, const char *url); +void Ql_iotConfigGetHttpOtaUp(quint8_t *battery, quint8_t *upmode, char **url); +#endif +/* ql_iotGwDev.h */ +#ifdef QUEC_ENABLE_GATEWAY +void Ql_iotConfigSetSubDevEventCB(void (*eventCb)(quint32_t event, qint32_t errcode, const char *subPk, const char *subDk, const void *value, quint32_t valLen)); +qbool Ql_iotSubDevConn(const char *subPk, const char *subPs, const char *subDk, const char *subDs, quint8_t sessionType, quint16_t keepalive); +qbool Ql_iotSubDevDisconn(const char *subPk, const char *subDk); +qbool Ql_iotSubDevPassTransSend(const char *subPk, const char *subDk, quint8_t *payload, quint16_t payloadlen); +qbool Ql_iotSubDevTslReport(const char *subPk, const char *subDk, const void *ttlvHead); +qbool Ql_iotSubDevTslAck(const char *subPk, const char *subDk, quint16_t pkgId, const void *ttlvHead); +qbool Ql_iotSubDevDeauth(const char *subPk, const char *subPs, const char *subDk, const char *subDs); +qbool Ql_iotSubDevHTB(const char *subPk, const char *subDk); +#endif #endif \ No newline at end of file diff --git a/cloud/common/ql_iotCmdBus.c b/cloud/common/ql_iotCmdBus.c new file mode 100644 index 0000000..368dee7 --- /dev/null +++ b/cloud/common/ql_iotCmdBus.c @@ -0,0 +1,277 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : 2020-12-25 +** 功能 @brief : dmp业务指令,适配DMP2.2.0 +** 硬件 @hardware: +** 其他 @other : +***************************************************************************/ +#include "ql_iotCmdBus.h" +#include "ql_iotCmdSys.h" +#include "ql_iotCmdLoc.h" +#include "ql_iotDp.h" +#include "ql_iotTtlv.h" +#include "ql_iotConfig.h" +#include "Qhal_driver.h" + +Ql_iotCmdBusInfo_t QIot_busInfo; + +/************************************************************************** +** 功能 @brief : 透传数据发送结果 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotCmdBusPassTransSendCB(void *chlFd, const void *sendData, const void *recvData) +{ + UNUSED(chlFd); + UNUSED(sendData); + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_SEND, recvData ? QIOT_SEND_SUCC_TRANS : QIOT_SEND_ERR_TRANS, NULL, 0); +} +/************************************************************************** +** 功能 @brief : 透传数据发送 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdBusPassTransSend(quint16_t mode, quint8_t *payload, quint32_t len) +{ + if (0 == len) + { + return FALSE; + } + return Ql_iotDpSendCommonReq(QIOT_DPAPP_M2M | QIOT_DPAPP_LANPHONE,NULL, 0, mode, QIOT_DPCMD_PASS_EVENT, payload, len, ql_iotCmdBusPassTransSendCB); +} +/************************************************************************** +** 功能 @brief : 透传数据接收 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdBusPassTransRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + UNUSED(app); + UNUSED(endPoint); + UNUSED(pkgId); + QIot_buffer_t *recvBuf = HAL_MALLOC(sizeof(QIot_buffer_t) + payloadLen); + if (NULL == recvBuf) + { + Quos_logPrintf(QUEC_BUS, LL_ERR, "mcf recvBuf"); + return; + } + recvBuf->type = QIOT_RECV_SUCC_TRANS; + recvBuf->val.len = payloadLen; + HAL_MEMCPY(recvBuf->val.buf, payload, payloadLen); + if (FALSE == Quos_eventPost(QIOT_ATEVENT_TYPE_RECV, recvBuf)) + { + HAL_FREE(recvBuf); + } +} +/************************************************************************** +** 功能 @brief : 物模型数据发送结果 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotCmdBusPhymodelReportCB(void *chlFd, const void *sendData, const void *recvData) +{ + UNUSED(chlFd); + UNUSED(sendData); + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_SEND, recvData ? QIOT_SEND_SUCC_PHYMODEL : QIOT_SEND_ERR_PHYMODEL, NULL, 0); +} +/************************************************************************** +** 功能 @brief : 物模型数据上报 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdBusPhymodelReport(quint16_t mode, const void *ttlvHead) +{ + if (NULL == ttlvHead) + { + return FALSE; + } + return Ql_iotDpSendTtlvReq(QIOT_DPAPP_M2M|QIOT_DPAPP_LANPHONE,NULL, 0, mode, QIOT_DPCMD_TSL_EVENT, ttlvHead, ql_iotCmdBusPhymodelReportCB); +} +/************************************************************************** +** 功能 @brief : 物模型数据应答 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdBusPhymodelAck(quint16_t mode, quint16_t pkgId, const void *ttlvHead) +{ + UNUSED(mode); + return Ql_iotDpSendTtlvRsp(QIOT_DPAPP_M2M,NULL, QIOT_DPCMD_TSL_RSP, pkgId, ttlvHead); +} +/************************************************************************** +** 功能 @brief : 物模型数据上报 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdBusPhymodelReportHex(quint16_t mode, quint8_t *buf, quint32_t len) +{ + return Ql_iotDpSendCommonReq(QIOT_DPAPP_M2M|QIOT_DPAPP_LANPHONE, NULL, 0, mode, QIOT_DPCMD_TSL_EVENT, buf, len, ql_iotCmdBusPhymodelReportCB); +} +/************************************************************************** +** 功能 @brief : 物模型数据应答 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdBusPhymodelAckHex(quint16_t mode, quint16_t pkgId, quint8_t *buf, quint32_t len) +{ + UNUSED(mode); + return Ql_iotDpSendCommonRsp(QIOT_DPAPP_M2M,NULL, QIOT_DPCMD_TSL_RSP, pkgId, buf, len); +} +/************************************************************************** +** 功能 @brief : 物模型数据接收 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Ql_iotCmdBusPhymodelWriteRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + UNUSED(app); + UNUSED(endPoint); + void *ttlvHead = Ql_iotTtlvUnformat(payload, payloadLen); + if (NULL == ttlvHead) + { + Ql_iotCmdSysExceReport(app,endPoint, QIOT_SERVER_ERRCODE_UNFORMAT_FAIL, pkgId); + return; + } + Ql_iotTtlvFree(&ttlvHead); + + QIot_buffer_t *recvBuf = NULL; + recvBuf = HAL_MALLOC(sizeof(QIot_buffer_t) + payloadLen); + if (NULL == recvBuf) + { + Quos_logPrintf(QUEC_BUS, LL_ERR, "mcf recvBuf"); + return; + } + HAL_MEMCPY(recvBuf->val.buf, payload, payloadLen); + recvBuf->val.len = payloadLen; + recvBuf->type = QIOT_RECV_SUCC_PHYMODEL_RECV; + if (FALSE == Quos_eventPost(QIOT_ATEVENT_TYPE_RECV, recvBuf)) + { + HAL_FREE(recvBuf); + } +} +/************************************************************************** +** 功能 @brief : 物模型数据请求 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Ql_iotCmdBusPhymodelReqRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + UNUSED(app); + UNUSED(endPoint); + quint16_t *ids = (quint16_t *)HAL_MALLOC(sizeof(quint16_t) * (payloadLen / 2 + 1)); + if (ids) + { + quint32_t idNum; + ids[0] = pkgId; + for (idNum = 0; idNum < payloadLen / 2; idNum++) + { + ids[idNum + 1] = _ARRAY01_U16(payload + idNum * 2); + } + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_RECV, QIOT_RECV_SUCC_PHYMODEL_REQ, ids, idNum); + HAL_FREE(ids); + } +} +/************************************************************************** +** 功能 @brief : 业务事件处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void ql_iotCmdBusEventNotify(qint32_t event, void *arg) +{ + switch (event) + { + case QIOT_ATEVENT_TYPE_RECV: + { + QIot_buffer_t *recvBuf = (QIot_buffer_t *)arg; + #ifdef QUEC_ENABLE_AT + if (QIot_busInfo.recvIsBuffer) + { + if (QIOT_RECV_SUCC_TRANS == recvBuf->type && Quos_twllHeadGetNodeCount(QIot_busInfo.recvTransData) < QIOT_RECV_NODE_TRANS_MAX) + { + Quos_twllHeadAdd(&QIot_busInfo.recvTransData, &recvBuf->head); + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_RECV, QIOT_RECV_SUCC_TRANS, NULL, 0); + } + else if (QIOT_RECV_SUCC_PHYMODEL_RECV == recvBuf->type && Quos_twllHeadGetNodeCount(QIot_busInfo.recvPhymodelData) < QIOT_RECV_NODE_MODEL_MAX) + { + if(QIot_busInfo.dataFormat) + { + void *ttlvHead = Ql_iotTtlvUnformat(recvBuf->val.buf, recvBuf->val.len); + if (NULL == ttlvHead) + { + Quos_logPrintf(QUEC_BUS, LL_ERR, "not ttlv format"); + return; + } + cJSON *root = Ql_iotTtlv2Json(ttlvHead); + Ql_iotTtlvFree(&ttlvHead); + if (NULL == root) + { + Quos_logPrintf(QUEC_BUS, LL_ERR, "ttlv to json error"); + return; + } + char *jsonData = cJSON_PrintUnformatted(root); + int jsonLen = HAL_STRLEN(jsonData); + QIot_buffer_t *jsonBuf = HAL_MALLOC(sizeof(QIot_buffer_t) + jsonLen); + if (NULL == jsonBuf) + { + Quos_logPrintf(QUEC_BUS, LL_ERR, "mcf jsonBuf"); + cJSON_Delete(root); + return; + } + HAL_MEMCPY(jsonBuf->val.buf, jsonData, jsonLen); + jsonBuf->val.len = jsonLen; + jsonBuf->type = QIOT_RECV_SUCC_PHYMODEL_RECV; + cJSON_Delete(root); + HAL_FREE(recvBuf); + Quos_twllHeadAdd(&QIot_busInfo.recvPhymodelData, &jsonBuf->head); + } + else + { + Quos_twllHeadAdd(&QIot_busInfo.recvPhymodelData, &recvBuf->head); + } + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_RECV, QIOT_RECV_SUCC_PHYMODEL_RECV, NULL, 0); + } + else + { + HAL_FREE(recvBuf); + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_RECV, QIOT_RECV_ERR_LIMIT, NULL, 0); + } + } + else + #endif + { + if (QIOT_RECV_SUCC_TRANS == recvBuf->type) + { + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_RECV, QIOT_RECV_SUCC_TRANS, recvBuf->val.buf, recvBuf->val.len); + } + else if (QIOT_RECV_SUCC_PHYMODEL_RECV == recvBuf->type) + { + #ifdef QUEC_ENABLE_AT + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_RECV, QIOT_RECV_SUCC_PHYMODEL_RECV, recvBuf->val.buf, recvBuf->val.len); + #else + void *ttlv = Ql_iotTtlvUnformat(recvBuf->val.buf, recvBuf->val.len); + if (ttlv) + { + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_RECV, QIOT_RECV_SUCC_PHYMODEL_RECV, ttlv, 0); + Ql_iotTtlvFree(&ttlv); + } + #endif + } + HAL_FREE(recvBuf); + } + break; + } + default: + break; + } +} +/************************************************************************** +** 功能 @brief : 业务任务初始化 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdBusInit(void) +{ + HAL_MEMSET(&QIot_busInfo, 0, sizeof(QIot_busInfo)); + qint32_t event[] = {QIOT_ATEVENT_TYPE_RECV}; + Quos_eventCbReg(event, sizeof(event) / sizeof(event[0]), ql_iotCmdBusEventNotify); +} \ No newline at end of file diff --git a/cloud/common/ql_iotCmdBus.h b/cloud/common/ql_iotCmdBus.h new file mode 100644 index 0000000..c111107 --- /dev/null +++ b/cloud/common/ql_iotCmdBus.h @@ -0,0 +1,49 @@ +#ifndef __QIOT_CMDBUS_H__ +#define __QIOT_CMDBUS_H__ +#include "Ql_iotApi.h" + +#define QIOT_RECV_NODE_TRANS_MAX 10 +#define QIOT_RECV_NODE_MODEL_MAX 10 + +#define QIOT_SEND_ERR_STRING(X) \ + ( \ + (X == QIOT_SEND_SUCC_TRANS) ? "SEND_SUCC_TRANS" : (X == QIOT_SEND_SUCC_PHYMODEL) ? "SEND_SUCC_PHYMODEL" \ + : (X == QIOT_SEND_ERR_TRANS) ? "SEND_ERR_TRANS" \ + : (X == QIOT_SEND_ERR_PHYMODEL) ? "SEND_ERR_PHYMODEL" \ + : "Unknown") +#define QIOT_RECV_ERR_STRING(X) \ + ( \ + (X == QIOT_RECV_SUCC_TRANS) ? "RECV_SUCC_TRANS" : (X == QIOT_RECV_SUCC_PHYMODEL_RECV) ? "RECV_SUCC_PHYMODEL_RECV" \ + : (X == QIOT_RECV_SUCC_PHYMODEL_REQ) ? "RECV_SUCC_PHYMODEL_REQ" \ + : (X == QIOT_RECV_ERR_BUFFER) ? "RECV_ERR_BUFFER" \ + : (X == QIOT_RECV_ERR_LIMIT) ? "RECV_ERR_LIMIT" \ + : "Unknown") + +typedef struct +{ + TWLLHead_T head; + int type; + struct + { + quint32_t len; + quint8_t buf[1]; + } val; +} QIot_buffer_t; + +typedef struct +{ + qbool recvIsBuffer; + TWLLHead_T *recvTransData; + TWLLHead_T *recvPhymodelData; + qbool dataFormat; +} Ql_iotCmdBusInfo_t; +extern Ql_iotCmdBusInfo_t QIot_busInfo; +void Ql_iotCmdBusInit(void); + +void Ql_iotCmdBusPassTransRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +void Ql_iotCmdBusPhymodelWriteRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +void Ql_iotCmdBusPhymodelReqRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); + +qbool Ql_iotCmdBusPhymodelReportHex(quint16_t mode, quint8_t *buf, quint32_t len); +qbool Ql_iotCmdBusPhymodelAckHex(quint16_t mode, quint16_t pkgId, quint8_t *buf, quint32_t len); +#endif \ No newline at end of file diff --git a/cloud/common/ql_iotCmdLan.c b/cloud/common/ql_iotCmdLan.c new file mode 100644 index 0000000..07349ae --- /dev/null +++ b/cloud/common/ql_iotCmdLan.c @@ -0,0 +1,85 @@ +/* + * @Author: your name + * @Date: 2021-11-04 19:23:58 + * @LastEditTime: 2021-11-05 11:56:19 + * @LastEditors: Please set LastEditors + * @Description: In User Settings Edit + * @FilePath: \QuecCSDK\cloud\common\ql_iotCmdLan.c + */ +#include "ql_iotCmdLan.h" +#include "ql_iotConfig.h" +#include "ql_iotDp.h" +#include "Qhal_driver.h" + +#ifdef QUEC_ENABLE_LAN + +#ifndef QIOT_LAN_TIMEOUT +#define QIOT_LAN_TIMEOUT 5 * SWT_ONE_SECOND +#endif + +static void *lanChl = NULL; +/************************************************************************** +** 功能 @brief : 设备上报PK、MAC内容 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM ql_iotLanDevDiscover(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + UNUSED(payload); + UNUSED(payloadLen); + void *ttlvHead = NULL; + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_LAN_MAC, QIot_userdata.deviceInfo.deviceKey); + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_LAN_PK, QIot_userdata.productInfo.productKey); + Ql_iotDpSendTtlvRsp(app, endPoint, QIOT_DPCMD_LAN_DISCOVER_RSP, pkgId, ttlvHead); + Ql_iotTtlvFree(&ttlvHead); +} + + +/************************************************************************** +** 功能 @brief : LAN接收数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM ql_iotLanRecvData(void *chlFd, const void *peer, quint32_t peerSize, Quos_socketRecvDataNode_t *recvData) +{ + UNUSED(chlFd); + UNUSED(peer); + UNUSED(peerSize); + /* 在此缺少将peer加入通信总线管理 */ + if (recvData) + { + Ql_iotDpHandle(QIOT_DPAPP_LANPHONE, NULL, recvData->Buf, recvData->bufLen); + } + return TRUE; +} + +/************************************************************************** +** 功能 @brief : LAN初始化 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdLanInit(void) +{ + Quos_socketChlInfoNode_t chlInfo; + HAL_MEMSET(&chlInfo, 0, sizeof(Quos_socketChlInfoNode_t)); + chlInfo.sockFd = Qhal_udpInit(&chlInfo.type, QIOT_LAN_PORT, NULL, 0, NULL); + Quos_logPrintf(QUEC_LAN, LL_DBG, "LANFd:" PRINTF_FD, chlInfo.sockFd); + if (SOCKET_FD_INVALID == chlInfo.sockFd) + { + Quos_logPrintf(QUEC_LAN, LL_ERR, "Listening on LAN port failed"); + return FALSE; + } + chlInfo.io.send = Qhal_sockWrite; + chlInfo.send.txCnt = 1; + chlInfo.send.timeout = QIOT_LAN_TIMEOUT; + chlInfo.recvDataFunc = ql_iotLanRecvData; + chlInfo.io.close = Qhal_sockClose; + lanChl = Quos_socketChannelAdd(NULL, chlInfo); + if (NULL == lanChl) + { + Quos_logPrintf(QUEC_LAN, LL_ERR, "add socket Channel fail"); + return FALSE; + } + return TRUE; +} +#endif diff --git a/cloud/common/ql_iotCmdLan.h b/cloud/common/ql_iotCmdLan.h new file mode 100644 index 0000000..54593f9 --- /dev/null +++ b/cloud/common/ql_iotCmdLan.h @@ -0,0 +1,18 @@ +#ifndef __QIOT_LAN_H__ +#define __QIOT_LAN_H__ +#include "Ql_iotApi.h" +#ifdef QUEC_ENABLE_LAN + +#define QIOT_LAN_PORT 40000 + +/* LAN TTLV ID */ +enum +{ + QIOT_DPID_LAN_MAC = 1, /* MAC */ + QIOT_DPID_LAN_PK = 2, /* PK */ +}; + +qbool Ql_iotCmdLanInit(void); +void ql_iotLanDevDiscover(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +#endif +#endif diff --git a/cloud/common/ql_iotCmdLoc.c b/cloud/common/ql_iotCmdLoc.c new file mode 100644 index 0000000..7102da7 --- /dev/null +++ b/cloud/common/ql_iotCmdLoc.c @@ -0,0 +1,325 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : +** 硬件 @hardware: +** 其他 @other : +***************************************************************************/ +#include "ql_iotCmdLoc.h" +#include "ql_iotCmdSys.h" +#include "ql_iotConfig.h" +#include "ql_iotDp.h" +#include "ql_iotTtlv.h" +#include "Qhal_driver.h" + +#define QIOT_LOCATOR_SEPARATOR ";" + +/************************************************************************** +** 功能 @brief : 判断组合是否冲突 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotLocatoTitleClashCheck(const void *ttlvHead) +{ + quint32_t titleCnt = 0, titleSum = Ql_iotTtlvCountGet(ttlvHead); + for (titleCnt = 0; titleCnt < titleSum; titleCnt++) + { + char *value = Ql_iotTtlvNodeGetString(Ql_iotTtlvNodeGet(ttlvHead, titleCnt, NULL, NULL)); + if(NULL == value) + { + Quos_logPrintf(QUEC_LOC, LL_ERR, "title is null"); + return FALSE; + } + if (0 == HAL_STRCMP(value, QIOT_LOC_SUPPORT_AUTO) && 1 != titleSum) + { + Quos_logPrintf(QUEC_LOC, LL_ERR, "title[%s] nonuniqueness", value); + return FALSE; + } + else + { + quint32_t cnt1 = 0; + for (cnt1 = 0; cnt1 < titleSum; cnt1++) + { + char *value1 = Ql_iotTtlvNodeGetString(Ql_iotTtlvNodeGet(ttlvHead, cnt1, NULL, NULL)); + if(NULL == value1) + { + Quos_logPrintf(QUEC_LOC, LL_ERR, "title is null"); + return FALSE; + } + if (cnt1 != titleCnt && (HAL_STRSTR(value1, value) || HAL_STRSTR(value, value1))) + { + Quos_logPrintf(QUEC_LOC, LL_ERR, "title[%d][%s] is clash with No[%d][%s]", titleCnt, value, cnt1, value1); + return FALSE; + } + } + } + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 判断组合是否都支持 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotLocatoTitleSupportCheck(const void *ttlvHead) +{ + char *words[100]; + quint32_t listCnt = Qhal_propertyLocSupList(words, sizeof(words) / sizeof(words[0])); + quint32_t titleCnt = 0, titleSum = Ql_iotTtlvCountGet(ttlvHead); + for (titleCnt = 0; titleCnt < titleSum; titleCnt++) + { + char *title = Ql_iotTtlvNodeGetString(Ql_iotTtlvNodeGet(ttlvHead, titleCnt, NULL, NULL)); + if (NULL == title) + { + Quos_logPrintf(QUEC_LOC, LL_ERR, "title is empty"); + return FALSE; + } + quint32_t count; + for (count = 0; count < listCnt; count++) + { + if (0 == HAL_STRCMP(words[count], title)) + { + break; + } + } + if (count >= listCnt) + { + Quos_logPrintf(QUEC_LOC, LL_ERR, "title[%s] is no support", title); + return FALSE; + } + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotLocatorTtlv2String(const void *ttlv) +{ + quint32_t sum = Ql_iotTtlvCountGet(ttlv); + quint32_t cnt; + quint32_t len = 0; + for (cnt = 0; cnt < sum; cnt++) + { + char *locType = Ql_iotTtlvNodeGetString(Ql_iotTtlvNodeGet(ttlv, cnt, NULL, NULL)); + if (locType) + { + len += HAL_STRLEN(locType) + HAL_STRLEN(QIOT_LOCATOR_SEPARATOR); + } + } + char *buf; + if (0 == len || (buf = HAL_MALLOC(len + 1)) == NULL) + { + return NULL; + } + + for (cnt = 0, len = 0; cnt < sum; cnt++) + { + char *locType = Ql_iotTtlvNodeGetString(Ql_iotTtlvNodeGet(ttlv, cnt, NULL, NULL)); + if (locType) + { + len += HAL_SPRINTF(buf + len, "%s%s", locType, QIOT_LOCATOR_SEPARATOR); + } + } + return buf; +} +/************************************************************************** +** 功能 @brief : 计算NMEA语句CRC +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static quint8_t FUNCTION_ATTR_ROM ql_iotLocNmeaCrc(char *str, quint32_t len) +{ + quint8_t crc = 0; + if (NULL == str) + { + return 0; + } + while (len--) + { + crc ^= *str++; + } + return crc; +} +/************************************************************************** +** 功能 @brief : 获取LBS的模拟NMEA语句 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM ql_iotLocatorLbsNmeaRead(void **ttlv) +{ +#define IOT_LBSNMEA_FORMAT "$LBS,%u,%02u,%u,%u,%d,0*" + Qhal_propertyNet_t master, neibh[6]; + quint32_t count = 0; + if (FALSE == Qhal_propertyNetGet(&master, &neibh, sizeof(neibh) / sizeof(neibh[0]), &count) || + count > sizeof(neibh) / sizeof(neibh[0])) + { + return FALSE; + } + + char buf[100]; + quint32_t len = HAL_SPRINTF(buf, IOT_LBSNMEA_FORMAT, master.mcc, master.mnc, master.lac, master.cellid, master.rssi); + len += HAL_SPRINTF(buf + len, "%02d", ql_iotLocNmeaCrc(buf + 1, len - 2)); + buf[len] = 0; + Ql_iotTtlvIdAddString(ttlv, 0, buf); + while (count--) + { + quint32_t len = HAL_SPRINTF(buf, IOT_LBSNMEA_FORMAT, neibh[count].mcc, neibh[count].mnc, neibh[count].lac, neibh[count].cellid, neibh[count].rssi); + len += HAL_SPRINTF(buf + len, "%02d", ql_iotLocNmeaCrc(buf + 1, len - 2)); + buf[len] = 0; + Ql_iotTtlvIdAddString(ttlv, 0, buf); + } + return TRUE; +} + +/************************************************************************** +** 功能 @brief : 根据需求获取定位数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotLocGetData(const void *titleTtlv) +{ + void *locDataTtlv = NULL; + quint32_t titleCnt = 0; + char *locType; + if (FALSE == Ql_iotLocatoTitleClashCheck(titleTtlv) || FALSE == Ql_iotLocatoTitleSupportCheck(titleTtlv)) + { + return NULL; + } + while ((locType = Ql_iotTtlvNodeGetString(Ql_iotTtlvNodeGet(titleTtlv, titleCnt++, NULL, NULL))) != NULL) + { + if (0 == HAL_STRCMP(locType, QIOT_LOC_SUPPORT_AUTO)) + { + if (FALSE == Qhal_propertyGnssRawDataRead(&locDataTtlv, "GGA")) + { + ql_iotLocatorLbsNmeaRead(&locDataTtlv); + } + } + else if (0 == HAL_STRCMP(locType, QIOT_LOC_SUPPORT_LBS)) + { + ql_iotLocatorLbsNmeaRead(&locDataTtlv); + } + else + { + Qhal_propertyGnssRawDataRead(&locDataTtlv, locType); + } + } + return locDataTtlv; +} +/************************************************************************** +** 功能 @brief : 定位数据发送结果 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotCmdBusLocReportCB(void *chlFd, const void *sendData, const void *recvData) +{ + UNUSED(chlFd); + UNUSED(sendData); + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_SEND, recvData ? QIOT_SEND_SUCC_LOC : QIOT_SEND_ERR_FAIL_LOC, NULL, 0); +} + +/************************************************************************** +** 功能 @brief : 主动上报模组定位数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdBusLocReportInside(void *titleTtlv) +{ + qbool ret = FALSE; + void *nmeaTtlv = Ql_iotLocGetData(titleTtlv); + if (NULL == nmeaTtlv) + { + Quos_logPrintf(QUEC_BUS, LL_ERR, "nmeaTtlv is null"); + return FALSE; + } + char *buf = Ql_iotLocatorTtlv2String(nmeaTtlv); + Ql_iotTtlvFree(&nmeaTtlv); + + void *reportTtlv = NULL; + Ql_iotTtlvIdAddString(&reportTtlv, 2, buf); + HAL_FREE(buf); + ret = Ql_iotDpSendTtlvReq(QIOT_DPAPP_M2M,NULL, 0, 1, QIOT_DPCMD_LOC_REPORT, reportTtlv, ql_iotCmdBusLocReportCB); + Ql_iotTtlvFree(&reportTtlv); + return ret; +} +/************************************************************************** +** 功能 @brief : 主动上报外部定位数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdBusLocReportOutside(void *nmeaTtlv) +{ + qbool ret = FALSE; + if (NULL == nmeaTtlv) + { + Quos_logPrintf(QUEC_BUS, LL_ERR, "nmeaTtlv is null"); + return FALSE; + } + char *buf = Ql_iotLocatorTtlv2String(nmeaTtlv); + void *reportTtlv = NULL; + Ql_iotTtlvIdAddString(&reportTtlv, 2, buf); + HAL_FREE(buf); + ret = Ql_iotDpSendTtlvReq(QIOT_DPAPP_M2M,NULL, 0, 1, QIOT_DPCMD_LOC_REPORT, reportTtlv, ql_iotCmdBusLocReportCB); + Ql_iotTtlvFree(&reportTtlv); + return ret; +} + +/************************************************************************** +** 功能 @brief : 应答平台查询的实时内置定位数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdLocDataReqRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + void *titleTtlv = NULL; + void *ttlvHead = Ql_iotTtlvUnformat(payload, payloadLen); + char *cfgData = Ql_iotTtlvIdGetString(ttlvHead, 1); + if (cfgData) + { + char *words[100]; + quint32_t count = Quos_stringSplit(cfgData,HAL_STRLEN(cfgData), words, sizeof(words) / sizeof(words[0]), QIOT_LOCATOR_SEPARATOR, FALSE); + quint32_t i; + for (i = 0; i < count; i++) + { + Ql_iotTtlvIdAddString(&titleTtlv, 0, words[i]); + } + } + Ql_iotTtlvFree(&ttlvHead); + if (NULL == titleTtlv || FALSE == Ql_iotLocatoTitleClashCheck(titleTtlv)) + { + Ql_iotTtlvFree(&titleTtlv); + Ql_iotCmdSysExceReport(app,endPoint,QIOT_SERVER_ERRCODE_UNFORMAT_FAIL, pkgId); + } + else + { + void *locDataTtlv = Ql_iotLocGetData(titleTtlv); + Ql_iotTtlvFree(&titleTtlv); + char *buf = Ql_iotLocatorTtlv2String(locDataTtlv); + Ql_iotTtlvFree(&locDataTtlv); + + /* 以下locDataTtlv作用已变 */ + Ql_iotTtlvIdAddString(&locDataTtlv, 2, buf); + HAL_FREE(buf); + Ql_iotDpSendTtlvRsp(app,endPoint, QIOT_DPCMD_LOC_RSP, pkgId, locDataTtlv); + Ql_iotTtlvFree(&locDataTtlv); + } +} +/************************************************************************** +** 功能 @brief : 查询当前模组支持的内置定位至此类型 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotLocGetSupList(void) +{ + char *words[100]; + quint32_t count = Qhal_propertyLocSupList(words, sizeof(words) / sizeof(words[0])); + quint32_t num; + void *titleTtlv = NULL; + for (num = 0; num < count; num++) + { + Ql_iotTtlvIdAddString(&titleTtlv, 0, words[num]); + } + return titleTtlv; +} \ No newline at end of file diff --git a/cloud/common/ql_iotCmdLoc.h b/cloud/common/ql_iotCmdLoc.h new file mode 100644 index 0000000..3a77093 --- /dev/null +++ b/cloud/common/ql_iotCmdLoc.h @@ -0,0 +1,9 @@ +#ifndef __QL_IOT_LOCATOR_H__ +#define __QL_IOT_LOCATOR_H__ +#include "Ql_iotApi.h" + +char *Ql_iotLocatorTtlv2String(const void *ttlv); +qbool Ql_iotLocatoTitleClashCheck(const void *ttlvHead); + +void FUNCTION_ATTR_ROM Ql_iotCmdLocDataReqRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +#endif \ No newline at end of file diff --git a/cloud/common/ql_iotCmdOTA.c b/cloud/common/ql_iotCmdOTA.c new file mode 100644 index 0000000..c146029 --- /dev/null +++ b/cloud/common/ql_iotCmdOTA.c @@ -0,0 +1,889 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : 2020-12-25 +** 功能 @brief : dmp OTA,适配DMP2.2.0 +** 硬件 @hardware: +** 其他 @other : +***************************************************************************/ +#include "ql_iotCmdOTA.h" +#include "ql_iotConn.h" +#include "ql_iotDp.h" +#include "ql_iotTtlv.h" +#include "ql_iotConfig.h" +#include "Qhal_driver.h" + +Ql_iotCmdOtaInfo_t QIot_otaInfo; +static void *QIot_otaRuntimer = NULL; +static void *QIot_otaCachetimer = NULL; +static pointer_t QIot_otaFileFd = SOCKET_FD_INVALID; +static QIot_otaStatus_e QIot_otaCacheStatus = QIOT_OTA_STATUS_NOPLAN; +static Quos_socketChlInfoNode_t *sockInfo = NULL; +/************************************************************************** +** 功能 @brief : 升级过程事件上报 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM ql_iotCmdOtaStatusReport(QIot_otaStatus_e code) +{ + qbool ret = FALSE; + void *ttlvHead = NULL; + Quos_logPrintf(QUEC_OTA, LL_DBG, "event:%s", QIOT_OTA_STATUS_STRING(code)); + Ql_iotTtlvIdAddInt(&ttlvHead, QIOT_DPID_OTA_STATUS, code); + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_OTA_MESSAGE, QIOT_OTA_STATUS_STRING(code)); + Ql_iotTtlvIdAddInt(&ttlvHead, QIOT_DPID_OTA_COMPONENT_TYPE, QIot_otaInfo.componentType); + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_OTA_COMPONENT_NO, QIot_otaInfo.componentNo); + if(QIOT_OTA_EXTERN_SHA256 == QIot_otaInfo.extraMess && QIOT_OTA_STATUS_SUBMITOTA == code) + { + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_OTA_DOWN_SIGN,"sha256"); + } + if (QIOT_OTA_STATUS_UPDATESUCCESS == code || QIOT_OTA_STATUS_UPDATEERROR == code) + { + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_OTA_MODULE_VER, Qhal_softversionGet()); + char *version = NULL; + if (0 != Ql_iotConfigGetMcuVersion(NULL, &version)) + { + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_OTA_MCU_VER, version); + } + } + //ret = Ql_iotDpSendTtlvOta(QIOT_DPCMD_OTA_EVENT, ttlvHead); + ret = Ql_iotDpSendTtlvReq(QIOT_DPAPP_M2M, NULL, 0, 2, QIOT_DPCMD_OTA_EVENT, ttlvHead, NULL); + Ql_iotTtlvFree(&ttlvHead); + return ret; +} +/************************************************************************** +** 功能 @brief : OTA请求 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdOtaRequest(quint32_t mode) +{ + qbool ret = FALSE; + void *ttlvHead = NULL; + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_OTA_MODULE_VER, Qhal_softversionGet()); + char *oldVer = NULL; + if (Ql_iotConfigGetMcuVersion(NULL, &oldVer) != 0) + { + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_OTA_MCU_VER, oldVer); + } + //ret = Ql_iotDpSendTtlvOta(QIOT_DPCMD_OTA_REQUEST, ttlvHead); + ret = Ql_iotDpSendTtlvReq(QIOT_DPAPP_M2M, NULL, 0, 2, QIOT_DPCMD_OTA_REQUEST, ttlvHead, NULL); + Ql_iotTtlvFree(&ttlvHead); + if(ret && mode >= QIOT_OTA_EXTERN_MAX) + { + ret = FALSE; + } + QIot_otaInfo.extraMess = mode; + return ret; +} + +/************************************************************************** +** 功能 @brief : 等待URL超时 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotOtaWaitUrlTimeout(void *swtimer) +{ + Quos_logPrintf(QUEC_OTA, LL_ERR, "timeout"); + Quos_swTimerTimeoutSet(swtimer, SWT_SUSPEND); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); +} +/************************************************************************** +** 功能 @brief : HTTP下载超时 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotOtaWaitDownloadTimeout(void *swtimer) +{ + Quos_logPrintf(QUEC_OTA, LL_ERR, "timeout"); + Quos_swTimerTimeoutSet(swtimer, SWT_SUSPEND); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADERROR); +} +/************************************************************************** +** 功能 @brief : 更新超时 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotOtaWaitUpdateTimeout(void *swtimer) +{ + Quos_logPrintf(QUEC_OTA, LL_ERR, "timeout"); + Quos_swTimerTimeoutSet(swtimer, SWT_SUSPEND); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); +} +/************************************************************************** +** 功能 @brief : 下载中通知 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotOtaDownloading(void *swtimer) +{ + UNUSED(swtimer); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADING); +} +#if QUEC_ENABLE_QTH_OTA +/************************************************************************** +** 功能 @brief : 进入OTA更新 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotOtaUpdate(void *swtimer) +{ + quint32_t delayTime = 0; + delayTime = Qhal_devOtaNotify(QIOT_FILE_OTA, QIot_otaInfo.otaFileInfo.size); + if (delayTime) + { + Quos_swTimerTimeoutSet(swtimer, delayTime); + Quos_swTimerCBSet(swtimer, ql_iotOtaWaitUpdateTimeout); + } + else + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); + Quos_swTimerTimeoutSet(swtimer, SWT_SUSPEND); + } +} +#endif +/************************************************************************** +** 功能 @brief : OTA文件下载通知 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM ql_iotOtaDownloadCB(qint32_t httpCode, char *retHeader, quint8_t *recvBuf, quint32_t recvLen) +{ + UNUSED(retHeader); + UNUSED(recvBuf); + Quos_logPrintf(QUEC_CONN, LL_DBG, "httpCode:%d recvLen:%u", httpCode, recvLen); + if (200 != httpCode && 206 != httpCode) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADERROR); + return FALSE; + } + if (0 == recvLen) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADING); + } + else + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADSUCCESS); + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 下载文件 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotOtaDownload(void) +{ + QIot_otaInfo.currentPiece.size = QIot_otaInfo.otaFileInfo.size - QIot_otaInfo.currentPiece.startAddr < QIot_otaInfo.currentPiece.size ? QIot_otaInfo.otaFileInfo.size - QIot_otaInfo.currentPiece.startAddr : QIot_otaInfo.currentPiece.size; +#ifndef QHAL_DEV_OTA_ENABLE + char rawHeader[100]; + HttpReqData_t reqData; + HAL_MEMSET(&reqData, 0, sizeof(reqData)); + Quos_logPrintf(QUEC_OTA, LL_DBG, "start:%u len:%u", QIot_otaInfo.currentPiece.startAddr, QIot_otaInfo.currentPiece.size); + if (0 != QIot_otaInfo.currentPiece.startAddr || 0 != QIot_otaInfo.currentPiece.size) + { + reqData.rawHeaders = rawHeader; + HAL_SPRINTF(rawHeader, "Accept-Ranges: bytes\r\nRange: bytes=%u-%u\r\n", QIot_otaInfo.currentPiece.startAddr, QIot_otaInfo.currentPiece.startAddr + QIot_otaInfo.currentPiece.size - 1); + } + if (FALSE == Quos_httpGetDownload((void **)(&sockInfo), QIot_otaInfo.otaFileInfo.downloadUrl, ql_iotOtaDownloadCB, &reqData, QIOT_FILE_OTA, 0)) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADERROR); + } +#else + qhal_devOta_e ret = Qhal_devOtaDownload(QIot_otaInfo.otaFileInfo.downloadUrl, QIot_otaInfo.currentPiece.startAddr, QIot_otaInfo.currentPiece.size, QIOT_FILE_OTA, QIot_otaInfo.componentType); + Quos_swTimerTimeoutSet(QIot_otaRuntimer, SWT_SUSPEND); + if (QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType && (QHAL_OTA_UPDATE_SUCC == ret || QHAL_OTA_UPDATE_FAIL == ret)) + { + Quos_logPrintf(QUEC_OTA, LL_ERR, "invalid download result"); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); + return; + } + switch (ret) + { + case QHAL_OTA_DOWN_START: + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADING); + break; + case QHAL_OTA_DOWN_SUCC: + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADSUCCESS); + break; + case QHAL_OTA_DOWN_FAIL: + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADERROR); + break; + case QHAL_OTA_UPDATE_SUCC: + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATESUCCESS); + break; + case QHAL_OTA_UPDATE_FAIL: + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); + break; + default: + break; + } +#endif +} + +/************************************************************************** +** 功能 @brief : MCU 版本确认 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdOtaMcuVersionModify(const char *compNo, const char *version) +{ + Quos_logPrintf(QUEC_OTA, LL_DBG, "version cur[%s:%s] new[%s:%s] componentType:%d currStatus:%s", compNo, version, QIot_otaInfo.componentNo, QIot_otaInfo.targetVersion, QIot_otaInfo.componentType, QIOT_OTA_STATUS_STRING(QIot_otaInfo.currStatus)); + QIot_otaStatus_e status = QIot_otaInfo.currStatus | QIot_otaCacheStatus; + switch (status) + { + case QIOT_OTA_STATUS_NOPLAN: + case QIOT_OTA_STATUS_REVICEPLAN: + case QIOT_OTA_STATUS_REFUSEDOTA: + case QIOT_OTA_STATUS_UPDATESUCCESS: + case QIOT_OTA_STATUS_UPDATEERROR: + break; + case QIOT_OTA_STATUS_SUBMITOTA: + case QIOT_OTA_STATUS_DOWNLOADSTART: + case QIOT_OTA_STATUS_DOWNLOADING: + case QIOT_OTA_STATUS_DOWNLOADERROR: + case QIOT_OTA_STATUS_DOWNLOADSUCCESS: + case QIOT_OTA_STATUS_UPDATING: + if (QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType && 0 == HAL_STRCMP(compNo, QIot_otaInfo.componentNo)) + { + if (status < QIOT_OTA_STATUS_DOWNLOADSUCCESS && status >= QIOT_OTA_STATUS_DOWNLOADSTART && NULL != sockInfo && Quos_socketCheckChlFd(sockInfo) && TRUE == sockInfo->valid) + { + Qhal_sockClose(sockInfo->sockFd, sockInfo->type); + } + Quos_swTimerTimeoutSet(QIot_otaRuntimer, SWT_SUSPEND); + status = (0 == HAL_STRCMP(version, QIot_otaInfo.targetVersion)) ? QIOT_OTA_STATUS_UPDATESUCCESS : QIOT_OTA_STATUS_UPDATEERROR; + Quos_logPrintf(QUEC_OTA, LL_DBG, "status:%d", status); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)status); + } + default: + break; + } +} +/************************************************************************** +** 功能 @brief : MCU数据读取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotCmdOtaMcuFWDataRead(quint32_t startAddr, quint8_t data[], quint32_t maxLen) +{ + if (QIOT_OTA_STATUS_DOWNLOADSUCCESS != QIot_otaInfo.currStatus || + startAddr < QIot_otaInfo.currentPiece.startAddr || startAddr >= QIot_otaInfo.currentPiece.startAddr + QIot_otaInfo.currentPiece.size || + 0 == maxLen) + { + Quos_logPrintf(QUEC_OTA, LL_ERR, "currStatus:%s startAddr:%d/%d size:%d", QIOT_OTA_STATUS_STRING(QIot_otaInfo.currStatus), startAddr, QIot_otaInfo.currentPiece.startAddr, QIot_otaInfo.currentPiece.size); + return 0; + } + Quos_swTimerTimeoutSet(QIot_otaRuntimer, QIOT_OTA_WAIT_READ); + if (QIot_otaFileFd == SOCKET_FD_INVALID) + { + QIot_otaFileFd = Qhal_fileOpen(QIOT_FILE_OTA, TRUE); + } + if (SOCKET_FD_INVALID != QIot_otaFileFd) + { + quint32_t offset = startAddr - QIot_otaInfo.currentPiece.startAddr; + maxLen = QIot_otaInfo.currentPiece.size - offset < maxLen ? QIot_otaInfo.currentPiece.size - offset : maxLen; + Quos_logPrintf(QUEC_OTA, LL_DBG, "offset:%d size:%d maxLen:%d", offset, QIot_otaInfo.currentPiece.size, maxLen); + maxLen = Qhal_fileRead(QIot_otaFileFd, offset, data, maxLen); + if (0 == maxLen) + { + Quos_logPrintf(QUEC_OTA, LL_ERR, "file read len=%d", maxLen); + } + + return maxLen; + } + else + { + Quos_logPrintf(QUEC_OTA, LL_ERR, "open file fail fileFd:" PRINTF_LD, QIot_otaFileFd); + } + return 0; +} +/************************************************************************** +** 功能 @brief : 升级确认 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdOtaAction(quint8_t action) +{ + qbool ret = FALSE; + Quos_logPrintf(QUEC_OTA, LL_DBG, "action:%u currStatus:%s", action, QIOT_OTA_STATUS_STRING(QIot_otaInfo.currStatus)); + switch (action) + { + case 0: + /* 模组把状态保存起来 */ + if (QIOT_OTA_STATUS_REVICEPLAN == QIot_otaInfo.currStatus) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_REFUSEDOTA); + ret = TRUE; + if (TRUE == QIot_otaInfo.mutilPlansMode) + { + quint32_t flag = 0; + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_UPDATE_FLAG, &flag, sizeof(flag)); + } + } + break; + case 1: + if (QIOT_OTA_STATUS_REVICEPLAN == QIot_otaInfo.currStatus) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_SUBMITOTA); + ret = TRUE; + if (TRUE == QIot_otaInfo.mutilPlansMode) + { + quint32_t flag = 0; + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_UPDATE_FLAG, &flag, sizeof(flag)); + } + } + else if (QIOT_OTA_STATUS_SUBMITOTA == QIot_otaInfo.currStatus) + { + ret = TRUE; + } + break; + case 2: + if (QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType && QIot_otaInfo.otaFileInfo.size > 0 && QIOT_OTA_STATUS_DOWNLOADSUCCESS == QIot_otaInfo.currStatus) + { + if (QIot_otaInfo.currentPiece.startAddr + QIot_otaInfo.currentPiece.size < QIot_otaInfo.otaFileInfo.size) + { + QIot_otaInfo.currentPiece.startAddr += QIot_otaInfo.currentPiece.size; + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADSTART); + ret = TRUE; + } + } + break; + case 3: + if (QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType) + { + if (QIOT_OTA_STATUS_DOWNLOADSUCCESS == QIot_otaInfo.currStatus) + { + if (QIot_otaInfo.currentPiece.startAddr + QIot_otaInfo.currentPiece.size >= QIot_otaInfo.otaFileInfo.size) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATING); + ret = TRUE; + } + } + else if (QIOT_OTA_STATUS_UPDATING == QIot_otaInfo.currStatus) + { + ret = TRUE; + } + } + break; + default: + break; + } + return ret; +} +/************************************************************************** +** 功能 @brief : OTA 组件处理 +** 输入 @param : ttlvHead:ttlv链表头 mode:FALSE:首次或单个 TRUE: 多个组件 +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM ql_iotOtaPlanHandle(void *ttlvHead) +{ + qint64_t componentType = 0; + char *componentNo = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_COMPONENT_NO); + char *sourceVersion = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_SOURCE_VER); + char *targetVersion = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_TARGET_VER); + if (NULL == componentNo || NULL == targetVersion || QIOT_COMPVER_MAXSIZE < HAL_STRLEN(targetVersion) || FALSE == Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_COMPONENT_TYPE, &componentType)) // || FALSE == Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_COMPONENT_TYPE, &componentType) + { + Quos_logPrintf(QUEC_OTA, LL_DBG, "info invalid"); + return FALSE; + } + char *tmpBuf = HAL_MALLOC(HAL_STRLEN(componentNo) + HAL_STRLEN(sourceVersion) + HAL_STRLEN(targetVersion) + 4 * 11 + 11 + 1); + if (tmpBuf) + { + qint64_t batteryLimit, minSignalIntensity, useSpace; + int64_t planMode; + HAL_SPRINTF(tmpBuf, "\"%s\",\"%s\",\"%s\",%u,%d,%u", (char *)componentNo, + sourceVersion ? sourceVersion : (char *)"", + (char *)targetVersion, + Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_BATTERY_LIMIT, &batteryLimit) ? (quint32_t)batteryLimit : 0, + Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_MIN_SIGNAL, &minSignalIntensity) ? (qint32_t)minSignalIntensity : 0, + Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_USE_SPACE, &useSpace) ? (quint32_t)useSpace : 0); + + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_TASK_NOTIFY, tmpBuf, HAL_STRLEN(tmpBuf)); + HAL_SNPRINTF(QIot_otaInfo.componentNo, sizeof(QIot_otaInfo.componentNo), "%s", componentNo); + QIot_otaInfo.componentType = componentType; + if (TRUE == Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_HANDLE_TYPE, &planMode) && QIOT_COMPTYPE_MODULE == componentType) + { + Ql_iotCmdOtaAction(1); + } + HAL_FREE(tmpBuf); + } + return TRUE; +} + +/************************************************************************** +** 功能 @brief : OTA任务下发 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdOtaNotify(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + UNUSED(app); + UNUSED(endPoint); + UNUSED(pkgId); + Quos_logPrintf(QUEC_OTA, LL_DBG, "recv task"); + if (QIOT_OTA_STATUS_NOPLAN != QIot_otaInfo.currStatus && QIOT_OTA_STATUS_REVICEPLAN != QIot_otaInfo.currStatus) + { + Quos_logPrintf(QUEC_OTA, LL_ERR, "current is %s and no restart", QIOT_OTA_STATUS_STRING(QIot_otaInfo.currStatus)); + return; + } + void *ttlvHead = Ql_iotTtlvUnformat(payload, payloadLen); + void *mutilPlans = Ql_iotTtlvIdGetStruct(ttlvHead, QIOT_DPID_OTA_TASK_INFO); + int count = 0; + if (mutilPlans != NULL) + { + count = Ql_iotTtlvCountGet(mutilPlans); + if (count > 20) + { + Ql_iotTtlvFree(&ttlvHead); + return; + } + int count_i = 0; + QIot_otaInfo.mutilPlansMode = TRUE; + for (count_i = 0; count_i < count; count_i++) + { + void *planStruct = Ql_iotTtlvNodeGet(mutilPlans, count_i, NULL, NULL); + void *childNode = Ql_iotTtlvNodeGetStruct(planStruct); + if (FALSE == ql_iotOtaPlanHandle(childNode)) + continue; + } + } + else + { + QIot_otaInfo.mutilPlansMode = FALSE; + ql_iotOtaPlanHandle(ttlvHead); + } + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_REVICEPLAN); + Ql_iotTtlvFree(&ttlvHead); + return; +} +#if QUEC_ENABLE_QTH_OTA==0 +/************************************************************************** +** 功能 @brief : 多固件下发 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool ql_iotCmdOtaFwList(quint8_t *payload, quint32_t len, qbool *isMutilFileInfo) +{ + qint64_t componentType; + void *ttlvHead = Ql_iotTtlvUnformat(payload, len); + + char *componentNo = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_COMPONENT_NO); + char *targetVersion = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_TARGET_VER); + if (NULL == componentNo || NULL == targetVersion || FALSE == Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_COMPONENT_TYPE, &componentType)) + { + goto exit; + } + char *filemd5 = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_DOWN_MD5); + char *filecrc = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_DOWN_CRC); + char *filesha256 = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_DOWN_SHA256); + char *download_url = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_DOWN_URL); + char *infoArray = Ql_iotTtlvIdGetStruct(ttlvHead, QIOT_DPID_OTA_DOWN_INFO); + if (infoArray != NULL) + { + if (NULL == filemd5 && NULL == filecrc && NULL == filesha256 && NULL == download_url && QIOT_COMPTYPE_MODULE == componentType) + { + quint32_t count = Ql_iotTtlvCountGet(infoArray); + if (count > 5) + { + goto exit; + } + *isMutilFileInfo = TRUE; + QIot_otaFilePublicInfo_t otaInfoArray[QIOT_OTA_FILEINFO_MAX_SIZE]; + HAL_MEMSET(otaInfoArray, 0, sizeof(QIot_otaFilePublicInfo_t) * QIOT_OTA_FILEINFO_MAX_SIZE); + quint32_t array_i = 0; + for (array_i = 0; array_i < count; array_i++) + { + char *info = Ql_iotTtlvNodeGet(infoArray, array_i, NULL, NULL); + void *childNode = Ql_iotTtlvNodeGetStruct(info); + if (info != NULL) + { + int64_t idex = 0; + char *info_url = Ql_iotTtlvIdGetString(childNode, QIOT_DPID_OTA_DOWN_URL); + char *info_filemd5 = Ql_iotTtlvIdGetString(childNode, QIOT_DPID_OTA_DOWN_MD5); + qint64_t info_filesize; + if (NULL == info_url || NULL == info_filemd5 || + FALSE == Ql_iotTtlvIdGetInt(childNode, QIOT_DPID_OTA_DOWN_INFO_IDEX, (int64_t *)&idex) || + FALSE == Ql_iotTtlvIdGetInt(childNode, QIOT_DPID_OTA_DOWN_SIZE, &info_filesize)) + { + Quos_logPrintf(QUEC_OTA, LL_DBG, "info invalid"); + goto exit; + } + otaInfoArray[array_i].idex = idex & 0xFF; + otaInfoArray[array_i].downloadUrl = HAL_MALLOC(HAL_STRLEN(info_url) + 1); + HAL_SNPRINTF(otaInfoArray[array_i].downloadUrl, HAL_STRLEN(info_url) + 1, "%s",info_url); + HAL_SNPRINTF(otaInfoArray[array_i].md5, sizeof(otaInfoArray[array_i].md5), "%s", info_filemd5); + otaInfoArray[array_i].size = info_filesize; + } + else + { + goto exit; + } + } + quint32_t timeout = Qhal_devOtaNotify(otaInfoArray, array_i); + for (array_i = 0; array_i < count; array_i++) + HAL_FREE(otaInfoArray[array_i].downloadUrl); + Quos_swTimerTimeoutSet(QIot_otaRuntimer, timeout); + Quos_swTimerCBSet(QIot_otaRuntimer, ql_iotOtaWaitUpdateTimeout); + } + } + Ql_iotTtlvFree(&ttlvHead); + return TRUE; +exit: + Ql_iotTtlvFree(&ttlvHead); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); + return FALSE; +} +#endif +/************************************************************************** +** 功能 @brief : OTA URL下发 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdOtaFwInfo(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + UNUSED(app); + UNUSED(endPoint); + UNUSED(pkgId); + Quos_logPrintf(QUEC_OTA, LL_DBG, "recv url"); + if (QIOT_OTA_STATUS_NOPLAN != QIot_otaInfo.currStatus && QIOT_OTA_STATUS_SUBMITOTA != QIot_otaInfo.currStatus) + { + Quos_logPrintf(QUEC_OTA, LL_ERR, "current is %s and no repeat download", QIOT_OTA_STATUS_STRING(QIot_otaInfo.currStatus)); + return; + } + Quos_swTimerTimeoutSet(QIot_otaRuntimer, SWT_SUSPEND); /* 收到URL,关闭等待URL超时定时器 */ + HAL_FREE(QIot_otaInfo.otaFileInfo.downloadUrl); + QIot_otaInfo.otaFileInfo.downloadUrl = NULL; + #if QUEC_ENABLE_QTH_OTA==0 + qbool isMutilFileInfo = FALSE; + ql_iotCmdOtaFwList(payload, payloadLen, &isMutilFileInfo); + if (isMutilFileInfo == TRUE) + { + return; + } + #endif + void *ttlvHead = Ql_iotTtlvUnformat(payload, payloadLen); + qint64_t filesize, componentType; + char *filemd5 = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_DOWN_MD5); + char *filecrc = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_DOWN_CRC); + char *filesha256 = NULL; + if (QIOT_OTA_EXTERN_SHA256 == QIot_otaInfo.extraMess) + { + filesha256 = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_DOWN_SHA256); + } + char *componentNo = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_COMPONENT_NO); + char *download_url = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_DOWN_URL); + char *targetVersion = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_OTA_TARGET_VER); + if (NULL == filemd5 || NULL == filecrc || NULL == componentNo || NULL == download_url || NULL == targetVersion || + (QIOT_OTA_EXTERN_SHA256 == QIot_otaInfo.extraMess && NULL == filesha256) || + FALSE == Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_DOWN_SIZE, &filesize) || + FALSE == Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_OTA_COMPONENT_TYPE, &componentType) || + QIOT_MD5_MAXSIZE != HAL_STRLEN(filemd5) || + QIOT_COMPNO_MAXSIZE < HAL_STRLEN(componentNo) || + QIOT_COMPVER_MAXSIZE < HAL_STRLEN(targetVersion) || + (QIot_otaInfo.otaFileInfo.downloadUrl = HAL_STRDUP((char *)download_url)) == NULL) + { + Quos_logPrintf(QUEC_OTA, LL_DBG, "info invalid"); + Ql_iotTtlvFree(&ttlvHead); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); + return; + } + HAL_SPRINTF(QIot_otaInfo.componentNo, "%s", componentNo); + HAL_SPRINTF(QIot_otaInfo.targetVersion, "%s", targetVersion); + QIot_otaInfo.componentType = (QIot_otaComptype_e)componentType; + QIot_otaInfo.otaFileInfo.size = filesize; + HAL_SPRINTF(QIot_otaInfo.otaFileInfo.md5, "%s", filemd5); + QIot_otaInfo.crc = HAL_STRTOUL(filecrc, NULL, 16); + if (filesha256 != NULL) + { + HAL_SPRINTF(QIot_otaInfo.sha256, "%s", filesha256); + } + Ql_iotTtlvFree(&ttlvHead); + Qhal_propertyDev_t dInfo; + HAL_MEMSET(&dInfo, 0, sizeof(Qhal_propertyDev_t)); + Qhal_propertyDevGet(&dInfo); + if (QIOT_COMPTYPE_MODULE == QIot_otaInfo.componentType && dInfo.flashFree < QIot_otaInfo.otaFileInfo.size) + { + Quos_logPrintf(QUEC_OTA, LL_ERR, "no enough space"); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); + return; + } + + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADSTART); + QIot_otaInfo.currentPiece.startAddr = 0; + QIot_otaInfo.currentPiece.size = dInfo.flashFree > QIot_otaInfo.otaFileInfo.size ? QIot_otaInfo.otaFileInfo.size : dInfo.flashFree; +} +/************************************************************************** +** 功能 @brief : 缓存状态上报 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotCmdOtaCacheReport(void *swtimer) +{ + UNUSED(swtimer); + Quos_logPrintf(QUEC_OTA, LL_DBG, "cache status report:%s.", QIOT_OTA_STATUS_STRING(QIot_otaCacheStatus)); + if (QIOT_OTA_STATUS_NOPLAN != QIot_otaCacheStatus) + { + if (TRUE == ql_iotCmdOtaStatusReport(QIot_otaCacheStatus)) + { + QIot_otaCacheStatus = QIOT_OTA_STATUS_NOPLAN; + } + } +} + +/************************************************************************** +** 功能 @brief : OTA事件处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void ql_iotCmdOtaEventNotify(qint32_t event, void *arg) +{ + static quint8_t downLoadFailCount = 0; + static qbool downloadStartFlag = TRUE; + switch (event) + { + case QIOT_ATEVENT_TYPE_OTA: + { + QIot_otaStatus_e status = (QIot_otaStatus_e)(pointer_t)arg; + Quos_logPrintf(QUEC_OTA, LL_DBG, "event:%s status:[%d]%s", QIOT_ATEVENT_TYPE_STRING(event), status, QIOT_OTA_STATUS_STRING(status)); + QIot_otaInfo.currStatus = status; + Ql_iotDSKVSave(); + switch (status) + { + case QIOT_OTA_STATUS_NOPLAN: + Quos_swTimerTimeoutSet(QIot_otaRuntimer, SWT_SUSPEND); + HAL_FREE(QIot_otaInfo.otaFileInfo.downloadUrl); + QIot_otaInfo.otaFileInfo.downloadUrl = NULL; + QIot_otaInfo.otaFileInfo.size = 0; + QIot_otaInfo.currentPiece.startAddr = 0; + QIot_otaInfo.currentPiece.size = 0; + Ql_iotDSKVSave(); + if (QIot_otaFileFd != SOCKET_FD_INVALID) + { + Qhal_fileClose(QIot_otaFileFd); + QIot_otaFileFd = SOCKET_FD_INVALID; + } + Qhal_fileErase(QIOT_FILE_OTA); + break; + case QIOT_OTA_STATUS_REVICEPLAN: + Quos_swTimerTimeoutSet(QIot_otaRuntimer, SWT_SUSPEND); + break; + case QIOT_OTA_STATUS_SUBMITOTA: + Quos_swTimerTimeoutSet(QIot_otaRuntimer, QIOT_OTA_WAIT_URL_TIMEOUT); + Quos_swTimerCBSet(QIot_otaRuntimer, ql_iotOtaWaitUrlTimeout); + break; + case QIOT_OTA_STATUS_REFUSEDOTA: + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_NOPLAN); + break; + case QIOT_OTA_STATUS_DOWNLOADSTART: + if (downloadStartFlag) + { + if (QIOT_COMPTYPE_MODULE == QIot_otaInfo.componentType) + { + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_START, NULL, 0); + } + else if (QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType) + { + char tmpBuf[QIOT_COMPNO_MAXSIZE + 10 + QIOT_MD5_MAXSIZE + 10 + 7 + QUOS_SHA256_DIGEST_LENGTH * 2 + 1]; + HAL_SPRINTF(tmpBuf, "\"%.*s\",%u,\"%s\",%u", QIOT_COMPNO_MAXSIZE, QIot_otaInfo.componentNo, QIot_otaInfo.otaFileInfo.size, QIot_otaInfo.otaFileInfo.md5, QIot_otaInfo.crc); + if (QIOT_OTA_EXTERN_SHA256 == QIot_otaInfo.extraMess) + { + HAL_SPRINTF(tmpBuf + HAL_STRLEN(tmpBuf), ",\"%s\"", QIot_otaInfo.sha256); + } + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_START, tmpBuf, HAL_STRLEN(tmpBuf)); + } + downloadStartFlag = FALSE; + } + else + { + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_DOWNLOADING, NULL, 0); + } + #if QUEC_ENABLE_QTH_OTA == 0 + if (QIOT_COMPTYPE_MODULE == QIot_otaInfo.componentType) + { + quint32_t timeout = Qhal_devOtaNotify(&(QIot_otaInfo.otaFileInfo), 1); + Quos_swTimerTimeoutSet(QIot_otaRuntimer, timeout); + Quos_swTimerCBSet(QIot_otaRuntimer, ql_iotOtaWaitUpdateTimeout); + } + else + { + #endif + Quos_swTimerTimeoutSet(QIot_otaRuntimer, QIOT_OTA_DOWNLOADING_NOTIFY); + Quos_swTimerCBSet(QIot_otaRuntimer, ql_iotOtaWaitDownloadTimeout); + ql_iotOtaDownload(); + #if QUEC_ENABLE_QTH_OTA == 0 + } + #endif + break; + case QIOT_OTA_STATUS_DOWNLOADING: + Quos_swTimerTimeoutSet(QIot_otaRuntimer, QIOT_OTA_DOWNLOADING_NOTIFY); + Quos_swTimerCBSet(QIot_otaRuntimer, ql_iotOtaDownloading); + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_DOWNLOADING, NULL, 0); + break; + case QIOT_OTA_STATUS_DOWNLOADERROR: + /* 连续下载QIOT_OTA_DOWNLOAD_FAIL_COUNT_MAX次失败则上报更新失败 */ + downLoadFailCount++; + Quos_logPrintf(QUEC_OTA, LL_ERR, "downLoadFailCount:%d", downLoadFailCount); + if (downLoadFailCount > QIOT_OTA_DOWNLOAD_FAIL_COUNT_MAX) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATEERROR); + return; + } + else + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_NOPLAN); + } + break; + case QIOT_OTA_STATUS_DOWNLOADSUCCESS: + Quos_swTimerTimeoutSet(QIot_otaRuntimer, SWT_SUSPEND); + if (QIOT_COMPTYPE_MODULE == QIot_otaInfo.componentType) + { + char md5String[33]; + if (FALSE == Quos_fileMd5(QIOT_FILE_OTA, QIot_otaInfo.otaFileInfo.size, md5String) || 0 != HAL_STRCMP(QIot_otaInfo.otaFileInfo.md5, md5String)) + { + Quos_logPrintf(QUEC_OTA, LL_ERR, "md5 calc:%s ser:%s", md5String, QIot_otaInfo.otaFileInfo.md5); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADERROR); + } + else + { + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_DOWNLOADED, NULL, 0); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATING); + } + } + else if (QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType && QIot_otaInfo.otaFileInfo.size > 0) + { + Quos_swTimerTimeoutSet(QIot_otaRuntimer, QIOT_OTA_WAIT_READ); + Quos_swTimerCBSet(QIot_otaRuntimer, ql_iotOtaWaitUpdateTimeout); + char tmpBuf[QIOT_COMPNO_MAXSIZE + 3 * 10 + 5 + 1]; + HAL_SPRINTF(tmpBuf, "\"%.*s\",%u,%u,%u", QIOT_COMPNO_MAXSIZE, QIot_otaInfo.componentNo, QIot_otaInfo.otaFileInfo.size, QIot_otaInfo.currentPiece.startAddr, QIot_otaInfo.currentPiece.size); + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_DOWNLOADED, tmpBuf, HAL_STRLEN(tmpBuf)); + } + break; + case QIOT_OTA_STATUS_UPDATING: + Quos_swTimerTimeoutSet(QIot_otaRuntimer, SWT_SUSPEND); + if (QIOT_COMPTYPE_MODULE == QIot_otaInfo.componentType) + { + #if QUEC_ENABLE_QTH_OTA + Quos_swTimerTimeoutSet(QIot_otaRuntimer, QIOT_OTA_UPDATE_DELAY); + Quos_swTimerCBSet(QIot_otaRuntimer, ql_iotOtaUpdate); + #endif + } + else if (QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType) + { + Quos_swTimerTimeoutSet(QIot_otaRuntimer, QIOT_OTA_UPDATE_TIMEOUT); + Quos_swTimerCBSet(QIot_otaRuntimer, ql_iotOtaWaitUpdateTimeout); + } + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, QIOT_OTA_UPDATING, NULL, 0); + break; + case QIOT_OTA_STATUS_UPDATESUCCESS: + case QIOT_OTA_STATUS_UPDATEERROR: + Quos_swTimerTimeoutSet(QIot_otaRuntimer, SWT_SUSPEND); + downLoadFailCount = 0; + downloadStartFlag = TRUE; + Ql_iotUrcEventCB(QIOT_ATEVENT_TYPE_OTA, status == QIOT_OTA_STATUS_UPDATESUCCESS ? QIOT_OTA_UPDATE_OK : QIOT_OTA_UPDATE_FAIL, NULL, 0); + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_NOPLAN); + break; + default: + return; + } + if (QIOT_OTA_STATUS_NOPLAN != status) + { + if (FALSE == ql_iotCmdOtaStatusReport(status)) + { + Quos_logPrintf(QUEC_OTA, LL_DBG, "report status fail,cache status:%s.", QIOT_OTA_STATUS_STRING(status)); + QIot_otaCacheStatus = status; + } + else + { + Quos_swTimerDelete(QIot_otaCachetimer); + QIot_otaCacheStatus = QIOT_OTA_STATUS_NOPLAN; + } + } + break; + } + case QIOT_ATEVENT_TYPE_SUBCRIBE: + { + switch ((qint32_t)(pointer_t)arg) + { + case QIOT_SUBCRIBE_SUCC: + { + Quos_logPrintf(QUEC_OTA, LL_DBG, "cache status:%s.", QIOT_OTA_STATUS_STRING(QIot_otaCacheStatus)); + if (QIOT_OTA_STATUS_NOPLAN != QIot_otaCacheStatus) + { + Quos_swTimerStart(&QIot_otaCachetimer, "Cache", 0, 1, ql_iotCmdOtaCacheReport, NULL); + } + break; + } + default: + break; + } + break; + } + default: + break; + } +} + +/************************************************************************** +** 功能 @brief : OTA状态恢复 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdOtaStatusRecovery(void) +{ + Quos_logPrintf(QUEC_OTA, LL_DBG, "ota status:%s.", QIOT_OTA_STATUS_STRING(QIot_otaInfo.currStatus)); +#ifdef QHAL_DEV_OTA_ENABLE + if (QIOT_OTA_STATUS_DOWNLOADSTART == QIot_otaInfo.currStatus && QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, 0 == HAL_STRCMP(QIot_otaInfo.targetVersion, QIot_userdata.softversion) ? (void *)QIOT_OTA_STATUS_UPDATESUCCESS : (void *)QIOT_OTA_STATUS_UPDATEERROR); + return; + } +#endif + switch (QIot_otaInfo.currStatus) + { + case QIOT_OTA_STATUS_NOPLAN: + break; + case QIOT_OTA_STATUS_SUBMITOTA: + case QIOT_OTA_STATUS_DOWNLOADSTART: + case QIOT_OTA_STATUS_DOWNLOADING: + case QIOT_OTA_STATUS_DOWNLOADSUCCESS: + if (QIOT_COMPTYPE_MODULE == QIot_otaInfo.componentType && 0 == HAL_STRCMP(QIot_otaInfo.targetVersion, QIot_userdata.softversion)) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATESUCCESS); + } + else + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADERROR); + } + break; + case QIOT_OTA_STATUS_DOWNLOADERROR: + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_DOWNLOADERROR); + break; + case QIOT_OTA_STATUS_UPDATING: + if (QIOT_COMPTYPE_MODULE == QIot_otaInfo.componentType) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, 0 == HAL_STRCMP(QIot_otaInfo.targetVersion, QIot_userdata.softversion) ? (void *)QIOT_OTA_STATUS_UPDATESUCCESS : (void *)QIOT_OTA_STATUS_UPDATEERROR); + } + else if (QIOT_COMPTYPE_MCU == QIot_otaInfo.componentType) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_UPDATING); + } + break; + default: + Quos_eventPost(QIOT_ATEVENT_TYPE_OTA, (void *)QIOT_OTA_STATUS_NOPLAN); + break; + } +} + +/************************************************************************** +** 功能 @brief : OTA任务初始化 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdOtaInit(void) +{ + HAL_MEMSET(&QIot_otaInfo, 0, sizeof(QIot_otaInfo)); + qint32_t event[] = {QIOT_ATEVENT_TYPE_OTA, QIOT_ATEVENT_TYPE_SUBCRIBE, QIOT_ATEVENT_TYPE_LOGOUT}; + Quos_eventCbReg(event, sizeof(event) / sizeof(event[0]), ql_iotCmdOtaEventNotify); + Quos_swTimerStart(&QIot_otaRuntimer, "OTA", SWT_SUSPEND, 0, NULL, NULL); + Qhal_fileErase(QIOT_FILE_OTA); +} diff --git a/cloud/common/ql_iotCmdOTA.h b/cloud/common/ql_iotCmdOTA.h new file mode 100644 index 0000000..13a29da --- /dev/null +++ b/cloud/common/ql_iotCmdOTA.h @@ -0,0 +1,115 @@ +#ifndef __QIOT_CMDOTA_H__ +#define __QIOT_CMDOTA_H__ +#include "Ql_iotApi.h" +#include "Qhal_driver.h" + +#define QIOT_COMPNO_MAXSIZE (32) /* 组件名称最大长度 */ +#define QIOT_COMPVER_MAXSIZE (64) /* 组件版本最大长度 */ +#define QIOT_OTA_WAIT_URL_TIMEOUT (30 * SWT_ONE_SECOND) /* 确认升级等待获取URL超时时间 */ +#define QIOT_OTA_WAIT_READ (60 * SWT_ONE_SECOND) /* SOTA升级模组通知MCU下载完成,MCU超时不读取数据将判为升级失败 */ +#define QIOT_OTA_UPDATE_TIMEOUT (20 * SWT_ONE_MINUTE) +#define QIOT_OTA_DOWNLOADING_NOTIFY (5 * SWT_ONE_SECOND) +#define QIOT_OTA_DOWNLOAD_FAIL_COUNT_MAX 5 /* 最大允许下载失败次数 */ +#define QIOT_OTA_UPDATE_DELAY (5 * SWT_ONE_SECOND) /* 模组下载完成,延缓进入升级,保留几秒完成状态上报 */ +/* OTA升级TTLV ID */ +enum +{ + QIOT_DPID_OTA_DOWN_URL = 1, /* ota升级资源包下载地址 */ + QIOT_DPID_OTA_DOWN_SIZE = 2, /* Ota升级资源包大小 */ + QIOT_DPID_OTA_DOWN_MD5 = 3, /* Ota升级资源包md5值 */ + QIOT_DPID_OTA_COMPONENT_NO = 4, /* 组件标识 */ + QIOT_DPID_OTA_SOURCE_VER = 5, /* 源版本 */ + QIOT_DPID_OTA_TARGET_VER = 6, /* 目标版本 */ + QIOT_DPID_OTA_COMPONENT_TYPE = 7, /* 组件类型 */ + QIOT_DPID_OTA_BATTERY_LIMIT = 8, /* Ota升级最小电量 */ + QIOT_DPID_OTA_USE_SPACE = 9, /* ota升级需要磁盘空间 */ + QIOT_DPID_OTA_MIN_SIGNAL = 10, /* Ota升级最小信号强度 */ + QIOT_DPID_OTA_SUBMIT = 11, /* 是否进行ota升级 */ + QIOT_DPID_OTA_DELAY_TIME = 12, /* Ota升级下次协商时间 */ + QIOT_DPID_OTA_STATUS = 13, /* Ota升级状态 */ + QIOT_DPID_OTA_MESSAGE = 14, /* Ota升级状态信息 */ + QIOT_DPID_OTA_DOWN_CRC = 15, /* Ota升级资源包CRC值 */ + QIOT_DPID_OTA_DOWN_SHA256 = 16, /* Ota升级资源包SHA256值 */ + QIOT_DPID_OTA_DOWN_SIGN = 19, /* 额外需要的文件签名类型 */ + QIOT_DPID_OTA_DOWN_INFO = 20, /* Ota多固件资源信息 */ + QIOT_DPID_OTA_MODULE_VER = 25, /* 模组版本 */ + QIOT_DPID_OTA_MCU_VER = 26, /* MCU版本 */ + QIOT_DPID_OTA_HANDLE_TYPE = 27, /* 升级控制类型 */ + QIOT_DPID_OTA_TASK_INFO = 28, /* Ota多组件升级任务信息 */ + QIOT_DPID_OTA_DOWN_INFO_IDEX = 30, /* Ota多固件资源信息idex */ +}; +typedef enum +{ + QIOT_OTA_STATUS_NOPLAN = 0, /* 空闲 */ + QIOT_OTA_STATUS_REVICEPLAN = 1, /* 收到任务 */ + QIOT_OTA_STATUS_SUBMITOTA = 2, /* 收到确认 */ + QIOT_OTA_STATUS_REFUSEDOTA = 3, /* 收到取消 */ + QIOT_OTA_STATUS_DOWNLOADSTART = 4, /* 下载开始 */ + QIOT_OTA_STATUS_DOWNLOADING = 5, /* 下载中 */ + QIOT_OTA_STATUS_DOWNLOADERROR = 6, /* 下载失败 */ + QIOT_OTA_STATUS_DOWNLOADSUCCESS = 8, /* 下载成功 */ + QIOT_OTA_STATUS_UPDATING = 9, /* 更新中 */ + QIOT_OTA_STATUS_UPDATESUCCESS = 11, /* 更新成功 */ + QIOT_OTA_STATUS_UPDATEERROR = 12, /* 更新失败 */ +} QIot_otaStatus_e; + +#define QIOT_OTA_STATUS_STRING(X) \ + ( \ + (X == QIOT_OTA_STATUS_NOPLAN) ? "NOPLAN" : (X == QIOT_OTA_STATUS_REVICEPLAN) ? "REVICEPLAN" \ + : (X == QIOT_OTA_STATUS_SUBMITOTA) ? "SUBMITOTA" \ + : (X == QIOT_OTA_STATUS_REFUSEDOTA) ? "REFUSEDOTA" \ + : (X == QIOT_OTA_STATUS_DOWNLOADSTART) ? "DOWNLOADSTART" \ + : (X == QIOT_OTA_STATUS_DOWNLOADING) ? "DOWNLOADING" \ + : (X == QIOT_OTA_STATUS_DOWNLOADERROR) ? "DOWNLOADERROR" \ + : (X == QIOT_OTA_STATUS_DOWNLOADSUCCESS) ? "DOWNLOADSUCCESS" \ + : (X == QIOT_OTA_STATUS_UPDATING) ? "UPDATING" \ + : (X == QIOT_OTA_STATUS_UPDATESUCCESS) ? "UPDATESUCCESS" \ + : (X == QIOT_OTA_STATUS_UPDATEERROR) ? "UPDATEERROR" \ + : "Unknown") +#define QIOT_OTA_ERR_STRING(X) \ + ( \ + (X == QIOT_OTA_TASK_NOTIFY) ? "OTA_TASK_NOTIFY" : (X == QIOT_OTA_START) ? "OTA_START" \ + : (X == QIOT_OTA_DOWNLOADING) ? "OTA_DOWNLOADING" \ + : (X == QIOT_OTA_DOWNLOADED) ? "OTA_DOWNLOADED" \ + : (X == QIOT_OTA_UPDATING) ? "OTA_UPDATING" \ + : (X == QIOT_OTA_UPDATE_OK) ? "OTA_UPDATE_OK" \ + : (X == QIOT_OTA_UPDATE_FAIL) ? "OTA_UPDATE_FAIL" \ + : (X == QIOT_OTA_UPDATE_FLAG) ? "QIOT_OTA_UPDATE_FLAG" \ + :"Unknown") +enum +{ + QIOT_OTA_EXTERN_NONE = 0, + QIOT_OTA_EXTERN_SHA256 = 1, + QIOT_OTA_EXTERN_MAX, +}; + +typedef enum +{ + QIOT_COMPTYPE_MODULE = 0, + QIOT_COMPTYPE_MCU, +} QIot_otaComptype_e; + +typedef struct +{ + QIot_otaComptype_e componentType; /* 组件类型 */ + char componentNo[QIOT_COMPNO_MAXSIZE + 1]; /* 组件名称最长32bytes*/ + char targetVersion[QIOT_COMPVER_MAXSIZE + 1]; /* 版本号最长50bytes */ + qbool mutilPlansMode; /* 是否为多组件 */ + QIot_otaStatus_e currStatus; + quint32_t extraMess; + quint32_t crc; + char sha256[QUOS_SHA256_DIGEST_LENGTH * 2 + 1]; + struct + { + quint32_t startAddr; + quint32_t size; + } currentPiece; + QIot_otaFilePublicInfo_t otaFileInfo; +} Ql_iotCmdOtaInfo_t; +extern Ql_iotCmdOtaInfo_t QIot_otaInfo; +void Ql_iotCmdOtaInit(void); +void Ql_iotCmdOtaStatusRecovery(void); +void Ql_iotCmdOtaNotify(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +void Ql_iotCmdOtaFwInfo(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +void Ql_iotCmdOtaMcuVersionModify(const char *compNo, const char *version); +#endif diff --git a/cloud/common/ql_iotCmdSys.c b/cloud/common/ql_iotCmdSys.c new file mode 100644 index 0000000..3571a4e --- /dev/null +++ b/cloud/common/ql_iotCmdSys.c @@ -0,0 +1,573 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : dmp Syc,适配DMP2.2.0 +** 硬件 @hardware: +** 其他 @other : +***************************************************************************/ +#include "ql_iotCmdSys.h" +#include "ql_iotDp.h" +#include "ql_iotTtlv.h" +#include "ql_iotConfig.h" +#include "ql_iotCmdLoc.h" +#include "Qhal_driver.h" +#ifdef QUEC_ENABLE_GATEWAY +#include "ql_iotGwDev.h" +#endif + +enum +{ + QIOT_DEVICE_MANAGE_IP_SET = 0x01, + QIOT_DEVICE_MANAGE_IP_CLEAR = 0x02, + QIOT_DEVICE_MANAGE_REAUTH = 0x04, + QIOT_DEVICE_MANAGE_PKPS_SET = 0x08, + QIOT_DEVICE_MANAGE_PKPS_CLEAR = 0x10, + QIOT_DEVICE_MANAGE_NEW_DS = 0x20, +}; + +/************************************************************************** +** 功能 @brief : 将设备状态赋值ttlv格式,使用完需要Ql_iotTtlvFree()释放资源 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotSysGetDevStatus(quint16_t ids[], quint32_t size) +{ + void *ttlvHead = NULL; + Qhal_propertyDev_t dInfo; + Qhal_propertyNet_t nInfo; + HAL_MEMSET(&dInfo, 0, sizeof(Qhal_propertyDev_t)); + HAL_MEMSET(&nInfo, 0, sizeof(Qhal_propertyNet_t)); + Qhal_propertyDevGet(&dInfo); + Qhal_propertyNetGet(&nInfo, NULL, 0, NULL); + quint32_t i; + for (i = 0; i < size; i++) + { + switch (ids[i]) + { + case QIOT_DPID_STATUS_BATTERY: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], dInfo.cellLevel); + break; + case QIOT_DPID_STATUS_VOLTAGE: + Ql_iotTtlvIdAddFloat(&ttlvHead, ids[i], dInfo.cellVoltage); + break; + case QIOT_DPID_STATUS_SIGNAL: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], nInfo.rssi); + break; + case QIOT_DPID_STATUS_FLASHFREE: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], dInfo.flashFree); + break; + case QIOT_DPID_STATUS_RSRP: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], nInfo.rsrp); + break; + case QIOT_DPID_STATUS_RSRQ: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], nInfo.rsrq); + break; + case QIOT_DPID_STATUS_SNR: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], nInfo.snr); + break; + default: + break; + } + } + return ttlvHead; +} +/************************************************************************** +** 功能 @brief : 设备状态读取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdSysStatusRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + quint16_t ids[QIOT_DPID_STATUS_MAX]; + quint16_t i; + for (i = 0; i < payloadLen - 1 && i / 2 < QIOT_DPID_STATUS_MAX; i += 2) + { + ids[i / 2] = _ARRAY01_U16(&payload[i]); + } + void *ttlvHead = Ql_iotSysGetDevStatus(ids, i / 2); + Ql_iotDpSendTtlvRsp(app, endPoint, QIOT_DPCMD_STATUS_RSP, pkgId, ttlvHead); + Ql_iotTtlvFree(&ttlvHead); +} +/************************************************************************** +** 功能 @brief : 设备状态上报 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdSysStatusReport(quint16_t ids[], quint32_t size) +{ + qbool ret = FALSE; + void *ttlvHead = NULL; + Quos_logPrintf(QUEC_SYS, LL_DBG, "sysStatus report"); + if ((ttlvHead = Ql_iotSysGetDevStatus(ids, size)) != NULL) + { + ret = Ql_iotDpSendTtlvReq(QIOT_DPAPP_M2M, NULL, 0, 1, QIOT_DPCMD_STATUS_EVENT, ttlvHead, NULL); + Ql_iotTtlvFree(&ttlvHead); + } + return ret; +} +/************************************************************************** +** 功能 @brief : 将设备信息赋值ttlv格式,使用完需要Ql_iotTtlvFree()释放资源 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotSysGetDevInfo(quint16_t ids[], quint32_t size) +{ + void *ttlvHead = NULL; + Qhal_propertyDev_t dInfo; + Qhal_propertyNet_t nInfo; + Qhal_propertySim_t sInfo; + HAL_MEMSET(&dInfo, 0, sizeof(Qhal_propertyDev_t)); + HAL_MEMSET(&nInfo, 0, sizeof(Qhal_propertyNet_t)); + HAL_MEMSET(&sInfo, 0, sizeof(Qhal_propertySim_t)); + Qhal_propertyDevGet(&dInfo); + Qhal_propertyNetGet(&nInfo, NULL, 0, NULL); + Qhal_propertySimGet(&sInfo); + quint32_t i; + for (i = 0; i < size; i++) + { + switch (ids[i]) + { + case QIOT_DPID_INFO_MODEL_TYPE: + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], dInfo.modelType); + break; + case QIOT_DPID_INFO_MODEL_VER: + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], QIot_userdata.softversion); + break; + case QIOT_DPID_INFO_MCU_VER: + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], QIot_userdata.mcuVerList); + break; + case QIOT_DPID_INFO_CELLID: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], nInfo.cellid); + break; + case QIOT_DPID_INFO_ICCID: + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], sInfo.iccid); + break; + case QIOT_DPID_INFO_MCC: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], nInfo.mcc); + break; + case QIOT_DPID_INFO_MNC: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], nInfo.mnc); + break; + case QIOT_DPID_INFO_LAC: + Ql_iotTtlvIdAddInt(&ttlvHead, ids[i], nInfo.lac); + break; + case QIOT_DPID_INFO_PHONE_NUM: + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], sInfo.phoneid); + break; + case QIOT_DPID_INFO_SIM_NUM: + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], sInfo.imsi); + break; + case QIOT_DPID_INFO_SDK_VER: + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], QIOT_SDK_VERSION); + break; + case QIOT_DPID_INFO_LOC_SUPLIST: + { + void *titleTtlv = Ql_iotLocGetSupList(); + char *titleStr = Ql_iotLocatorTtlv2String(titleTtlv); + Ql_iotTtlvFree(&titleTtlv); + Ql_iotTtlvIdAddString(&ttlvHead, QIOT_DPID_INFO_LOC_SUPLIST, titleStr); + HAL_FREE(titleStr); + break; + } + case QIOT_DPIO_INFO_DP_VER: + { + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], QIOT_DATA_PROTOCOL_VER); + break; + } + case QIOT_DPIO_INFO_CP_VER: + { + Ql_iotTtlvIdAddString(&ttlvHead, ids[i], QIOT_COM_PROTOCOL_VER); + break; + } + default: + break; + } + } + return ttlvHead; +} +/************************************************************************** +** 功能 @brief : 设备信息读取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdSysDevInfoRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + quint16_t ids[QIOT_DPID_INFO_MAX]; + quint16_t i; + for (i = 0; i < payloadLen - 1 && i / 2 < QIOT_DPID_INFO_MAX; i += 2) + { + ids[i / 2] = _ARRAY01_U16(&payload[i]); + } + void *ttlvHead = Ql_iotSysGetDevInfo(ids, i / 2); + Ql_iotDpSendTtlvRsp(app, endPoint, QIOT_DPCMD_INFO_RSP, pkgId, ttlvHead); + Ql_iotTtlvFree(&ttlvHead); +} +/************************************************************************** +** 功能 @brief : 设备信息上报 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdSysDevInfoReport(quint16_t ids[], quint32_t size) +{ + qbool ret = FALSE; + void *ttlvHead = NULL; + Quos_logPrintf(QUEC_SYS, LL_DBG, "devinfo report"); + if ((ttlvHead = Ql_iotSysGetDevInfo(ids, size)) != NULL) + { + ret = Ql_iotDpSendTtlvReq(QIOT_DPAPP_M2M, NULL, 0, 1, QIOT_DPCMD_INFO_EVENT, ttlvHead, NULL); + Ql_iotTtlvFree(&ttlvHead); + } + return ret; +} +/************************************************************************** +** 功能 @brief : 上报绑定信息,仅在有局域网通信时有效 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotCmdBindcodeReport(quint8_t bindcode[], quint32_t len) +{ + qbool ret = FALSE; + void *ttlvHead = NULL; + Quos_logPrintf(QUEC_SYS, LL_DBG, "bindcode report"); + Ql_iotTtlvIdAddByte(&ttlvHead, QIOT_DPID_DEV_BINDCODE, bindcode, len); + + if (ttlvHead) + { + ret = Ql_iotDpSendTtlvReq(QIOT_DPAPP_M2M, NULL, 0, 1, QIOT_DPCMD_DEV_BINDCODE_WRITE, ttlvHead, NULL); + Ql_iotTtlvFree(&ttlvHead); + } + return ret; +} +/************************************************************************** +** 功能 @brief : 上报错误信息 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdSysExceReport(QIot_dpAppType_e app, const char *endPoint, qint32_t errcode, quint16_t pkgId) +{ + void *ttlvHead = NULL; + Quos_logPrintf(QUEC_SYS, LL_WARN, "ecode:%s pkgid:%u", QIOT_SERVER_ERRCODE_STRING(errcode), pkgId); + Ql_iotTtlvIdAddInt(&ttlvHead, QIOT_DPID_EXCE_ERRCODE, errcode); + Ql_iotTtlvIdAddInt(&ttlvHead, QIOT_DPID_EXCE_PKGID, pkgId); + Ql_iotDpSendTtlvReq(app, endPoint, 0, 1, QIOT_DPCMD_EXCE_EVENT, ttlvHead, NULL); + Ql_iotTtlvFree(&ttlvHead); +} + +/************************************************************************** +** 功能 @brief : 设备管理命令处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Ql_iotCmdSysTerManage(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + UNUSED(app); + UNUSED(endPoint); + UNUSED(pkgId); + qint32_t errcode = 0; + quint16_t flag = 0; + quint8_t event = 0; + qbool need_save = FALSE; + //Qiot_productType pt = QIot_userdata.productType; + void *ttlvHead = Ql_iotTtlvUnformat(payload, payloadLen); + if (NULL == ttlvHead) + { + return; + } + + /**************************************************设备认证信息提取******************************************************************************/ + char *ds = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_DEV_SECRET); + if (ds && HAL_STRLEN(ds)) + { + HAL_SNPRINTF(QIot_userdata.connectProduct->secret, sizeof(QIot_userdata.connectProduct->secret), "%s", ds); + Ql_iotDSKVSave(); + event = QIOT_ATEVENT_TYPE_AUTH; + errcode = QIOT_AUTH_SUCC; + } + uint8_t *sessionKey = NULL; + quint32_t sessionKeyLen = Ql_iotTtlvIdGetByte(ttlvHead, QIOT_DPID_DEV_SESSIONKEY, &sessionKey); + if (sessionKey && sessionKeyLen) + { + SHA256_ctx_t sha256_ctx; + quint8_t sha256Data[QUOS_SHA256_DIGEST_LENGTH]; + Quos_logHexDump(QUEC_SYS, LL_DUMP, "sessionKey", sessionKey, sessionKeyLen); + HAL_MEMCPY(QIot_userdata.sessionInfo.key, sessionKey, sizeof(QIot_userdata.sessionInfo.key)); + Quos_sha256init(&sha256_ctx); + Quos_sha256update(&sha256_ctx, (const quint8_t *)QIot_userdata.sessionInfo.key, sizeof(QIot_userdata.sessionInfo.key)); + Quos_sha256finish(&sha256_ctx, sha256Data); + HAL_MEMCPY(QIot_userdata.sessionInfo.iv, sha256Data, sizeof(QIot_userdata.sessionInfo.iv)); + QIot_userdata.sessionInfo.usable = TRUE; + } + /*******************************************************设备控制命令提取****************************************************************************/ + void *dnsInfo = Ql_iotTtlvIdGetStruct(ttlvHead, QIOT_DPID_DEV_DNS); + if (dnsInfo) + { + HAL_MEMSET(&QIot_userdata.productInfoCache, 0x00, sizeof(QIot_userdata.productInfoCache)); + + char *domain = Ql_iotTtlvIdGetString(dnsInfo, QIOT_DPID_DEV_DOMAIN); + char *ip = Ql_iotTtlvIdGetString(dnsInfo, QIOT_DPID_DEV_IP); + qint64_t port = 0; + qbool ret = Ql_iotTtlvIdGetInt(dnsInfo, QIOT_DPID_DEV_PORT, &port); + /* 域名 ip 端口号等3个信息需要同时下发,否则认为无效 */ + if (domain && ip && ret) + { + if (HAL_STRLEN(domain) && HAL_STRLEN(ip) && port <= 65535) + { + urlAnalyze_t urlA; + + if (FALSE == Quos_urlAnalyze(QIot_userdata.connectProduct->serverUrl, &urlA)) + { + ret = TRUE; + } + + if (0 == HAL_STRCMP(urlA.hostname, domain) && port == urlA.port) + { + ret = FALSE; + } + if (ret == TRUE) + { + HAL_SNPRINTF(QIot_userdata.productInfoCache.serverUrl, sizeof(QIot_userdata.productInfoCache.serverUrl), "%s:%d", domain, (quint16_t)port); + HAL_SNPRINTF(QIot_userdata.productInfoCache.serverIp, sizeof(QIot_userdata.productInfoCache.serverIp), "%s", ip); + QIot_userdata.connectProduct = &QIot_userdata.productInfoCache; + flag |= QIOT_DEVICE_MANAGE_IP_SET; + event = QIOT_ATEVENT_TYPE_CONN; + errcode = QIOT_CONN_ERR_SERVER_CHANGE; + } + } + /* 下发内容为空,清除云平台下发的服务器信息 */ + else if (0 == HAL_STRLEN(domain) && 0 == HAL_STRLEN(ip) && 0 == port) + { + HAL_MEMSET(QIot_userdata.productInfoCloud.serverUrl, 0x00, sizeof(QIot_userdata.productInfoCloud.serverUrl)); + HAL_MEMSET(QIot_userdata.productInfoCloud.serverIp, 0x00, sizeof(QIot_userdata.productInfoCloud.serverIp)); + //QIot_userdata.connectProduct = &QIot_userdata.productInfo; + flag |= QIOT_DEVICE_MANAGE_IP_CLEAR; + QIot_userdata.connectProduct = &QIot_userdata.productInfoCache; + need_save = TRUE; + } + } + } + char *pk = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_DEV_PK); + char *ps = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_DEV_PS); + if (pk && ps) + { + quint16_t pk_len = HAL_STRLEN(pk); + quint16_t ps_len = HAL_STRLEN(ps); + if (pk_len && ps_len) + { + if (0 != HAL_STRCMP(pk, QIot_userdata.connectProduct->productKey) || 0 != HAL_STRCMP(ps, QIot_userdata.connectProduct->productSecret)) + { + HAL_SNPRINTF(QIot_userdata.productInfoCache.productKey, sizeof(QIot_userdata.productInfoCache.productKey), "%s", pk); + HAL_SNPRINTF(QIot_userdata.productInfoCache.productSecret, sizeof(QIot_userdata.productInfoCache.productSecret), "%s", ps); + flag |= QIOT_DEVICE_MANAGE_PKPS_SET; + event = QIOT_ATEVENT_TYPE_CONN; + errcode = QIOT_CONN_ERR_SERVER_CHANGE; + QIot_userdata.connectProduct = &QIot_userdata.productInfoCache; + } + } + else if (0 == ps_len && 0 == pk_len) + { + /* 清除pk ps */ + HAL_MEMSET(QIot_userdata.productInfoCloud.productKey, 0x00, sizeof(QIot_userdata.productInfoCloud.productKey)); + HAL_MEMSET(QIot_userdata.productInfoCloud.productSecret, 0x00, sizeof(QIot_userdata.productInfoCloud.productSecret)); + flag |= QIOT_DEVICE_MANAGE_PKPS_CLEAR; + QIot_userdata.connectProduct = &QIot_userdata.productInfoCache; + need_save = TRUE; + } + } + + if ((flag & (QIOT_DEVICE_MANAGE_PKPS_SET | QIOT_DEVICE_MANAGE_IP_SET)) == QIOT_DEVICE_MANAGE_PKPS_SET) + { + /* 平台仅设置了pk ps */ + if (HAL_STRLEN(QIot_userdata.productInfoCloud.serverUrl)) + { + HAL_SNPRINTF(QIot_userdata.connectProduct->serverUrl, sizeof(QIot_userdata.connectProduct->serverUrl), "%s", QIot_userdata.productInfoCloud.serverUrl); + HAL_SNPRINTF(QIot_userdata.connectProduct->serverIp, sizeof(QIot_userdata.connectProduct->serverIp), "%s", QIot_userdata.productInfoCloud.serverIp); + } + else + { + HAL_SNPRINTF(QIot_userdata.connectProduct->serverUrl, sizeof(QIot_userdata.connectProduct->serverUrl), "%s", QIot_userdata.productInfo.serverUrl); + HAL_SNPRINTF(QIot_userdata.connectProduct->serverIp, sizeof(QIot_userdata.connectProduct->serverIp), "%s", QIot_userdata.productInfo.serverIp); + } + } + else if ((flag & (QIOT_DEVICE_MANAGE_PKPS_SET | QIOT_DEVICE_MANAGE_IP_SET)) == QIOT_DEVICE_MANAGE_IP_SET) + { + /* 平台仅设置了DNS */ + if (HAL_STRLEN(QIot_userdata.productInfoCloud.productKey)) + { + HAL_SNPRINTF(QIot_userdata.connectProduct->productKey, sizeof(QIot_userdata.connectProduct->productKey), "%s", QIot_userdata.productInfoCloud.productKey); + HAL_SNPRINTF(QIot_userdata.connectProduct->productSecret, sizeof(QIot_userdata.connectProduct->productSecret), "%s", QIot_userdata.productInfoCloud.productSecret); + } + else + { + HAL_SNPRINTF(QIot_userdata.connectProduct->productKey, sizeof(QIot_userdata.connectProduct->productKey), "%s", QIot_userdata.productInfo.productKey); + HAL_SNPRINTF(QIot_userdata.connectProduct->productSecret, sizeof(QIot_userdata.connectProduct->productSecret), "%s", QIot_userdata.productInfo.productSecret); + } + } + else if (flag == (QIOT_DEVICE_MANAGE_PKPS_CLEAR | QIOT_DEVICE_MANAGE_IP_CLEAR)) + { + /* 若平台同时清除DNS与pk ps信息,则需要在此切换连接平台对象为本地 */ + QIot_userdata.connectProduct = &QIot_userdata.productInfo; + event = QIOT_ATEVENT_TYPE_CONN; + errcode = QIOT_CONN_ERR_SERVER_CHANGE; + need_save = TRUE; + } + + qbool reauth_flag = FALSE; + qbool ret = Ql_iotTtlvIdGetBool(ttlvHead, QIOT_DPID_DEV_REAUTH, &reauth_flag); + char *new_ds = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_DEV_NEW_SECRET); + + if (ret && reauth_flag) + { + /* 重新认证标志下发未TRUE, 清除认证信息--若未下发pk ps dns,则清除当前连接对象 */ + flag |= QIOT_DEVICE_MANAGE_REAUTH; + HAL_MEMSET(QIot_userdata.connectProduct->secret, 0x00, sizeof(QIot_userdata.connectProduct->secret)); + event = QIOT_ATEVENT_TYPE_CONN; + errcode = QIOT_CONN_ERR_SERVER_CHANGE; + } + else if (new_ds && HAL_STRLEN(new_ds)) + { + if (0 != HAL_STRCMP(new_ds, QIot_userdata.connectProduct->secret)) + { + /* 新的ds信息保存到即将连接的对象里,--若未下发pk ps dns,则覆盖当前连接中ds信息 */ + HAL_SNPRINTF(QIot_userdata.connectProduct->secret, sizeof(QIot_userdata.connectProduct->secret), "%s", new_ds); + + event = QIOT_ATEVENT_TYPE_CONN; + errcode = QIOT_CONN_ERR_SERVER_CHANGE; + need_save = TRUE; + } + } + else if (0 != (flag & (QIOT_DEVICE_MANAGE_PKPS_SET | QIOT_DEVICE_MANAGE_IP_SET))) + { + /* 若平台下发了pk ps 或dns,且没有下发重新认证标志与新的ds数据,设备需要从其他地方拷贝ds到当前缓存信息对象中 */ + if (HAL_STRLEN(QIot_userdata.productInfoCloud.secret)) + { + HAL_SNPRINTF(QIot_userdata.connectProduct->secret, sizeof(QIot_userdata.connectProduct->secret), "%s", QIot_userdata.productInfoCloud.secret); + } + else + { + HAL_SNPRINTF(QIot_userdata.connectProduct->secret, sizeof(QIot_userdata.connectProduct->secret), "%s", QIot_userdata.productInfo.secret); + } + + event = QIOT_ATEVENT_TYPE_CONN; + errcode = QIOT_CONN_ERR_SERVER_CHANGE; + } + + if (need_save) + { + if (QIot_userdata.connectProduct != &QIot_userdata.productInfoCache) + { + Ql_iotDSKVSave(); + } + } + + if (errcode != 0) + { + if (errcode != QIOT_AUTH_SUCC) + { + Quos_mqttDeinit(QIot_userdata.m2mCtx); + QIot_userdata.m2mCtx = NULL; + } + /* 设置完新的域名后,是直接连接DMP还是重新发起认证 */ + Quos_eventPost(event, (void *)(long)errcode); + } + + Ql_iotTtlvFree(&ttlvHead); +} +/************************************************************************** +** 功能 @brief : 设备管理命令处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Ql_iotCmdSysExceWrite(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen) +{ + UNUSED(app); + UNUSED(endPoint); + UNUSED(pkgId); + void *ttlvHead = Ql_iotTtlvUnformat(payload, payloadLen); + if (NULL == ttlvHead) + { + return; + } + qint64_t errcode = 0, errPkgId = 0; + qbool retErrCode = Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_EXCE_ERRCODE, &errcode); + qbool retErrPkgId = Ql_iotTtlvIdGetInt(ttlvHead, QIOT_DPID_EXCE_PKGID, &errPkgId); + Quos_logPrintf(QUEC_SYS, LL_INFO, "code:[%d]%s pkgid:%u", (int)errcode, retErrCode ? QIOT_SERVER_ERRCODE_STRING(errcode) : "unknown", retErrPkgId ? (int)errPkgId : 0); + if (retErrCode && errcode > 10200) + { + switch (errcode) + { + case QIOT_AUTH_ERR_DONE: + case QIOT_AUTH_ERR_PKPS_INVALID: + case QIOT_AUTH_ERR_PAYLOAD_INVALID: + case QIOT_AUTH_ERR_SIGN_INVALID: + case QIOT_AUTH_ERR_VERSION_INVALID: + case QIOT_AUTH_ERR_HASH_INVALID: + case QIOT_AUTH_ERR_PK_CHANGE: + case QIOT_AUTH_ERR_DK_ILLEGAL: + case QIOT_AUTH_ERR_PK_VER_NOCOR: + Quos_mqttDeinit(QIot_userdata.m2mCtx); + QIot_userdata.m2mCtx = NULL; + Quos_eventPost(QIOT_ATEVENT_TYPE_AUTH, (void *)(pointer_t)errcode); + break; +#ifdef QUEC_ENABLE_GATEWAY + case QIOT_SUB_DEV_ERR_UNLOGIN: + { + char *dk = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_EXCE_DK); + char *pk = Ql_iotTtlvIdGetString(ttlvHead, QIOT_DPID_EXCE_PK); + if (pk && dk) + { + Ql_gatewayDeviceExceWrite(dk, pk, errcode); + } + break; + } +#endif + case QIOT_SERVER_ERRCODE_RATE_LIMIT: + case QIOT_SERVER_ERRCODE_QUANTITY_LIMIT: + default: + Quos_eventPost(QIOT_ATEVENT_TYPE_SERVER, (void *)(pointer_t)errcode); + break; + } + } + Ql_iotTtlvFree(&ttlvHead); +} +/************************************************************************** +** 功能 @brief : sys事件处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void ql_iotCmdSysEventNotify(qint32_t event, void *arg) +{ + qint32_t errcode = (qint32_t)(pointer_t)arg; + switch (event) + { + case QIOT_ATEVENT_TYPE_SUBCRIBE: + if (QIOT_SUBCRIBE_SUCC == errcode) + { + quint16_t statusIds[] = {QIOT_DPID_STATUS_BATTERY, + QIOT_DPID_STATUS_VOLTAGE, + QIOT_DPID_STATUS_SIGNAL, + QIOT_DPID_STATUS_FLASHFREE}; + quint16_t infoIds[] = {QIOT_DPID_INFO_MODEL_TYPE, + QIOT_DPID_INFO_MODEL_VER, + QIOT_DPID_INFO_MCU_VER, + QIOT_DPID_INFO_ICCID, + QIOT_DPID_INFO_SDK_VER, + QIOT_DPID_INFO_LOC_SUPLIST, + QIOT_DPIO_INFO_DP_VER, + QIOT_DPIO_INFO_CP_VER}; + Ql_iotCmdSysStatusReport(statusIds, sizeof(statusIds) / sizeof(statusIds[0])); + Ql_iotCmdSysDevInfoReport(infoIds, sizeof(infoIds) / sizeof(infoIds[0])); + } + break; + } +} +/************************************************************************** +** 功能 @brief : Sys任务初始化 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotCmdSysInit(void) +{ + qint32_t event[] = {QIOT_ATEVENT_TYPE_SUBCRIBE}; + Quos_eventCbReg(event, sizeof(event) / sizeof(event[0]), ql_iotCmdSysEventNotify); +} \ No newline at end of file diff --git a/cloud/common/ql_iotCmdSys.h b/cloud/common/ql_iotCmdSys.h new file mode 100644 index 0000000..db9cd0c --- /dev/null +++ b/cloud/common/ql_iotCmdSys.h @@ -0,0 +1,53 @@ +#ifndef __QIOT_CMDSYS_H__ +#define __QIOT_CMDSYS_H__ +#include "Ql_iotApi.h" + +enum +{ + QIOT_DPID_EXCE_ERRCODE = 1, /* 异常错误码 */ + QIOT_DPID_EXCE_PKGID = 2, /* 异常包ID */ + QIOT_DPID_EXCE_PK = 3, /* 异常设备pk */ + QIOT_DPID_EXCE_DK = 4, /* 异常设备dk */ +}; + +enum +{ + QIOT_DPID_DEV_DNS = 1, /* DNS信息 */ + QIOT_DPID_DEV_DOMAIN = 2, /* DNS域名 */ + QIOT_DPID_DEV_IP = 3, /* DNS的ip */ + QIOT_DPID_DEV_PORT = 4, /* DNS的端口 */ + QIOT_DPID_DEV_SECRET = 5, /* 设备的秘钥 */ + QIOT_DPID_DEV_SESSIONKEY = 6, /* 会话sessionKey */ + QIOT_DPID_DEV_PK = 7, /* product key */ + QIOT_DPID_DEV_PS = 8, /* product Secret */ + QIOT_DPID_DEV_REAUTH = 9, /* 认证标识 */ + QIOT_DPID_DEV_NEW_SECRET = 10, /* 新的设备密钥 */ + QIOT_DPID_DEV_BINDCODE = 11, /* 设备绑定信息bindcode上报,仅在有局域网通信时有效 */ +}; + +enum +{ + QIOT_SERVER_ERRCODE_PROTOCOL = 1, + QIOT_SERVER_ERRCODE_LEN = 2, + QIOT_SERVER_ERRCODE_CRC = 3, + QIOT_SERVER_ERRCODE_CMD = 4, + QIOT_SERVER_ERRCODE_UNFORMAT_FAIL = 5, +}; + +#define QIOT_SERVER_ERRCODE_STRING(X) \ + ( \ + (X == QIOT_SERVER_ERRCODE_PROTOCOL) ? "EXCE_ERRCODE_PROTOCOL" : (X == QIOT_SERVER_ERRCODE_LEN) ? "EXCE_ERRCODE_LEN" \ + : (X == QIOT_SERVER_ERRCODE_CRC) ? "EXCE_ERRCODE_CRC" \ + : (X == QIOT_SERVER_ERRCODE_CMD) ? "EXCE_ERRCODE_CMD" \ + : (X == QIOT_SERVER_ERRCODE_UNFORMAT_FAIL) ? "EXCE_ERRCODE_UNFORMAT_FAIL" \ + : (X == QIOT_SERVER_ERRCODE_RATE_LIMIT) ? "EXCE_ERRCODE_RATE_LIMIT" \ + : (X == QIOT_SERVER_ERRCODE_QUANTITY_LIMIT) ? "EXCE_ERRCODE_QUANTITY_LIMIT" \ + : "Unknown") + +void Ql_iotCmdSysInit(void); +void Ql_iotCmdSysExceReport(QIot_dpAppType_e app, const char *endPoint, qint32_t errcode, quint16_t pkgId); +void Ql_iotCmdSysStatusRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +void Ql_iotCmdSysDevInfoRecv(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +void Ql_iotCmdSysTerManage(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +void Ql_iotCmdSysExceWrite(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +#endif \ No newline at end of file diff --git a/cloud/common/ql_iotConfig.c b/cloud/common/ql_iotConfig.c new file mode 100644 index 0000000..86bc774 --- /dev/null +++ b/cloud/common/ql_iotConfig.c @@ -0,0 +1,681 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : quecthing公共信息 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "ql_iotConfig.h" +#include "ql_iotCmdOTA.h" +#include "ql_iotCmdBus.h" +#include "ql_iotCmdSys.h" +#include "ql_iotSecure.h" +#include "ql_iotConn.h" +#include "ql_iotCmdLan.h" +#ifdef QUEC_ENABLE_HTTP_OTA +#include "ql_fotaConfig.h" +#endif +#ifdef QUEC_ENABLE_GATEWAY +#include "ql_iotDMSub.h" +#endif +#include "Qhal_driver.h" + +QIot_userData_t QIot_userdata; + +static const dsKeyValue_t QIotDSKV[] = + { +#ifdef QUEC_ENABLE_AT + {0x00, FALSE, &QIot_userdata.connMode, sizeof(QIot_userdata.connMode)}, + {0x06, FALSE, &QIot_userdata.deviceInfo.contextID, sizeof(QIot_userdata.deviceInfo.contextID)}, + {0x11, FALSE, &QIot_userdata.lifetime, sizeof(QIot_userdata.lifetime)}, + {0x12, TRUE, QIot_userdata.mcuVerList, sizeof(QIot_userdata.mcuVerList)}, + {0x14, FALSE, &QIot_userdata.sessionInfo.flag, sizeof(QIot_userdata.sessionInfo.flag)}, + {0x0F, TRUE, QIot_userdata.deviceInfo.deviceKey, sizeof(QIot_userdata.deviceInfo.deviceKey)}, +#endif + {0x04, TRUE, QIot_userdata.productInfo.productKey, sizeof(QIot_userdata.productInfo.productKey)}, + {0x05, TRUE, QIot_userdata.productInfo.productSecret, sizeof(QIot_userdata.productInfo.productSecret)}, + {0x02, TRUE, QIot_userdata.productInfo.serverUrl, sizeof(QIot_userdata.productInfo.serverUrl)}, + {0x10, TRUE, QIot_userdata.productInfo.secret, sizeof(QIot_userdata.productInfo.secret)}, + + {0x30, TRUE, QIot_otaInfo.componentNo, sizeof(QIot_otaInfo.componentNo)}, + {0x31, TRUE, QIot_otaInfo.targetVersion, sizeof(QIot_otaInfo.targetVersion)}, + {0x32, FALSE, &QIot_otaInfo.componentType, sizeof(QIot_otaInfo.componentType)}, + {0x33, FALSE, &QIot_otaInfo.currStatus, sizeof(QIot_otaInfo.currStatus)}, + {0x34, FALSE, &QIot_otaInfo.extraMess, sizeof(QIot_otaInfo.extraMess)}, +#ifdef QUEC_ENABLE_AT + {0x50, FALSE, &QIot_busInfo.recvIsBuffer, sizeof(QIot_busInfo.recvIsBuffer)}, + {0x51, FALSE, &QIot_busInfo.dataFormat, sizeof(QIot_busInfo.dataFormat)}, +#endif + {0x07, TRUE, QIot_userdata.productInfo.serverIp, sizeof(QIot_userdata.productInfo.serverIp)}, + {0x08, TRUE, QIot_userdata.productInfoCloud.serverUrl, sizeof(QIot_userdata.productInfoCloud.serverUrl)}, + {0x09, TRUE, QIot_userdata.productInfoCloud.serverIp, sizeof(QIot_userdata.productInfoCloud.serverIp)}, + {0x0A, TRUE, QIot_userdata.productInfoCloud.productKey, sizeof(QIot_userdata.productInfoCloud.productKey)}, + {0x0B, TRUE, QIot_userdata.productInfoCloud.productSecret, sizeof(QIot_userdata.productInfoCloud.productSecret)}, + {0x0C, TRUE, QIot_userdata.productInfoCloud.secret, sizeof(QIot_userdata.productInfoCloud.secret)}, +#ifdef QUEC_ENABLE_HTTP_OTA + {0xF0, TRUE, Qota_userdata.productKey, sizeof(Qota_userdata.productKey)}, + {0xF1, TRUE, Qota_userdata.productSecret, sizeof(Qota_userdata.productSecret)}, + {0xF2, TRUE, Qota_userdata.serverUrl, sizeof(Qota_userdata.serverUrl)}, + {0xF3, TRUE, Qota_userdata.uid, sizeof(Qota_userdata.uid)}, + {0xF4, FALSE, &Qota_userdata.tlsMode, sizeof(Qota_userdata.tlsMode)}, + {0xF5, FALSE, &Qota_userdata.status, sizeof(Qota_userdata.status)}, + {0xF6, TRUE, Qota_userdata.targetVersion, sizeof(Qota_userdata.targetVersion)}, + {0xF7, FALSE, &Qota_userdata.tokenOvertime, sizeof(Qota_userdata.tokenOvertime)}, + {0xF8, TRUE, Qota_userdata.token, sizeof(Qota_userdata.token)}, +#endif + {0, FALSE, NULL, 0}}; +/************************************************************************** +** 功能 @brief : Quec IOT初始化,需要在使用IOT服务前调用 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotInit(void) +{ + HAL_MEMSET(&QIot_userdata, 0, sizeof(QIot_userdata)); + Quos_kernelInit(); + if (FALSE == Qhal_beforeMain()) + { + return FALSE; + } + Qhal_propertyDev_t dInfo; + Qhal_propertyDevGet(&dInfo); + Ql_iotConnInit(); + Ql_iotCmdBusInit(); + Ql_iotCmdSysInit(); + Ql_iotCmdOtaInit(); +#ifdef QUEC_ENABLE_LAN + Ql_iotCmdLanInit(); +#endif + char key[QUOS_AES_KEYLEN]; + HAL_MEMSET(key, '1', QUOS_AES_KEYLEN); + HAL_STRCPY(key, Qhal_devUuidGet()); + if (FALSE == Quos_dsKvRead(QIOT_FILE_CONFIG, QIotDSKV, key)) + { + Quos_logPrintf(QUEC_IOT, LL_DBG, "read dskv with aes fail"); + qbool kvret = Quos_dsKvRead(QIOT_FILE_CONFIG, QIotDSKV, NULL); + Quos_logPrintf(QUEC_IOT, LL_DBG, "read dskv without aes %s", _BOOL2STR(kvret)); + } + QIot_userdata.deviceInfo.contextID = dInfo.pdpCxtIdMin; +#ifdef QUEC_ENABLE_AT + if (0 == HAL_STRLEN(QIot_userdata.deviceInfo.deviceKey)) +#endif + { + HAL_SPRINTF(QIot_userdata.deviceInfo.deviceKey, "%s", Qhal_devUuidGet()); + } + if (0 == HAL_STRLEN(QIot_userdata.productInfo.serverUrl)) + { + HAL_SPRINTF(QIot_userdata.productInfo.serverUrl, "%s", QIOT_DMP_SERVERURL_MQTT_DEFAULT); + HAL_SPRINTF(QIot_userdata.productInfo.serverIp, "%s", QIOT_DMP_IP_MQTT_DEFAULT); + } + else if (NULL != HAL_STRSTR(QIot_userdata.productInfo.serverUrl, QIOT_DMP_SERVERURL_HTTP_DEFAULT)) + { + HAL_SPRINTF(QIot_userdata.productInfo.serverUrl, "%s", QIOT_DMP_SERVERURL_MQTT_DEFAULT); + HAL_SPRINTF(QIot_userdata.productInfo.serverIp, "%s", QIOT_DMP_IP_MQTT_DEFAULT); + } + else if (NULL != HAL_STRSTR(QIot_userdata.productInfo.serverUrl, QIOT_DMP_SERVERURL_HTTPS_DEFAULT)) + { + HAL_SPRINTF(QIot_userdata.productInfo.serverUrl, "%s", QIOT_DMP_SERVERURL_MQTTS_DEFAULT); + HAL_SPRINTF(QIot_userdata.productInfo.serverIp, "%s", QIOT_DMP_IP_MQTT_DEFAULT); + } + if (0 == QIot_userdata.lifetime) + { + QIot_userdata.lifetime = 120; + } + /* 若云平台设置过服务器信息,则需要使用云平台设置的信息连接平台,不能切换到本地设置的信息中 */ + if (HAL_STRLEN(QIot_userdata.productInfoCloud.serverUrl)) + { + QIot_userdata.connectProduct = &QIot_userdata.productInfoCloud; + Quos_logPrintf(QUEC_IOT, LL_INFO, "connect server obj cloud"); + } + else + { + Quos_logPrintf(QUEC_IOT, LL_INFO, "connect server obj local"); + QIot_userdata.connectProduct = &QIot_userdata.productInfo; + } + + Ql_iotConfigSetAppVersion(NULL); + Ql_iotCmdOtaStatusRecovery(); + +#ifdef QUEC_ENABLE_HTTP_OTA + Ql_fotaStatusRecovery(); +#endif + QIot_userdata.workState = QIOT_STATE_INITIALIZED; + if (QIOT_CONNMODE_REQ == QIot_userdata.connMode) + { + QIot_userdata.connMode = QIOT_CONNMODE_IDLE; + } + else if (QIOT_CONNMODE_AUTO == QIot_userdata.connMode) + { + Quos_netOpen(); + } + +#ifdef QUEC_ENABLE_GATEWAY + Ql_iotSubDevManagerInit(); + extern void Ql_gatewayDevicePackSubDisconnMsg(const char *pk, const char *dk, quint16_t pkgId); + Ql_iotDMSubDevDeleteAPIregister(QIOT_DEV_SUBDEV_DIRECT, Ql_gatewayDevicePackSubDisconnMsg); +#endif + //xjin.gao 20211206 adaptation//depot10/quecthing/quecthingSDK/2.9.0/ Initialize quecSDK Thread, detect events + Qhal_quecsdk_init(); + + Quos_logPrintf(QUEC_IOT, LL_INFO, "DK \t:%s", QIot_userdata.deviceInfo.deviceKey); + Quos_logPrintf(QUEC_IOT, LL_INFO, "quecthingSDK\t:%s", QIOT_SDK_VERSION); + Quos_logPrintf(QUEC_IOT, LL_INFO, "firmware VER\t:%s", QIot_userdata.softversion); + Quos_logPrintf(QUEC_IOT, LL_INFO, "MCU VER\t:%s", QIot_userdata.mcuVerList); + Quos_logPrintf(QUEC_IOT, LL_INFO, "connmode\t:%s", QIOT_CONN_MODE_STRING(QIot_userdata.connMode)); + Quos_logPrintf(QUEC_IOT, LL_INFO, "contextID\t:%u", QIot_userdata.deviceInfo.contextID); + Quos_logPrintf(QUEC_IOT, LL_INFO, "sertype\t:%s", "MQTT"); + Quos_logPrintf(QUEC_IOT, LL_INFO, "serUrl\t:%s", QIot_userdata.productInfo.serverUrl); + Quos_logPrintf(QUEC_IOT, LL_INFO, "PK \t:%s", QIot_userdata.productInfo.productKey); + /*Quos_logPrintf(QUEC_IOT, LL_DBG, "PS\t:%s", QIot_userdata.productInfo.productSecret);*/ + Quos_logPrintf(QUEC_IOT, LL_INFO, "authVer\t:%s", Ql_iotSecureVerGet()); + /*Quos_logPrintf(QUEC_IOT, LL_INFO, "ds\t:%s", QIot_userdata.deviceInfo.secret);*/ + Quos_logPrintf(QUEC_IOT, LL_INFO, "lifetime\t:%u", QIot_userdata.lifetime); + Quos_logPrintf(QUEC_IOT, LL_INFO, "sessionFlag\t:%d", QIot_userdata.sessionInfo.flag); + Quos_logPrintf(QUEC_IOT, LL_INFO, "buffer mode\t:%s", _BOOL2STR(QIot_busInfo.recvIsBuffer)); + return TRUE; +} +/************************************************************************** +** 功能 @brief : 保存配置信息 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotDSKVSave(void) +{ + char key[QUOS_AES_KEYLEN]; + HAL_MEMSET(key, '1', QUOS_AES_KEYLEN); + HAL_STRCPY(key, Qhal_devUuidGet()); + return Quos_dsKvWrite(QIOT_FILE_CONFIG, QIotDSKV, key); +} +/************************************************************************** +** 功能 @brief : 产品配置-连接模式配置 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConfigSetConnmode(QIot_connMode_e mode) +{ +#ifndef QUEC_ENABLE_AT + if (QIOT_CONNMODE_AUTO == mode) + { + return FALSE; + } +#endif + /* 判断当前系统服务器地址(url)以及其他配置是否为空 */ + if (QIOT_CONNMODE_IDLE != mode && + (mode > QIOT_CONNMODE_AUTO || + (HAL_STRLEN(QIot_userdata.productInfo.productKey) == 0 || + HAL_STRLEN(QIot_userdata.productInfo.productSecret) == 0 || + HAL_STRLEN(QIot_userdata.productInfo.serverUrl) == 0))) + { + return FALSE; + } + + if (mode != QIot_userdata.connMode) + { + QIot_connMode_e oldStatue = QIot_userdata.connMode; + QIot_userdata.connMode = mode; +#ifdef QUEC_ENABLE_AT + Ql_iotDSKVSave(); +#endif + if (QIOT_CONNMODE_IDLE == mode) + { + QIot_userdata.sessionInfo.usable = FALSE; + QIot_userdata.connFailCnt = 0; + Quos_netClose(); + } + else if(QIOT_CONNMODE_IDLE == oldStatue) + { + Quos_netOpen(); + } + } + return TRUE; +} + +/************************************************************************** +** 功能 @brief : 产品配置-连接模式获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +QIot_connMode_e Ql_iotConfigGetConnmode(void) +{ + return QIot_userdata.connMode; +} +/************************************************************************** +** 功能 @brief : 产品配置-PDP contextID配置 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool Ql_iotConfigSetPdpContextId(quint8_t contextID) +{ + Qhal_propertyDev_t dInfo; + + Qhal_propertyDevGet(&dInfo); + if (contextID > dInfo.pdpCxtIdMax || contextID < dInfo.pdpCxtIdMin || QIOT_CONNMODE_IDLE != Ql_iotConfigGetConnmode()) + { + return FALSE; + } + + if (contextID != QIot_userdata.deviceInfo.contextID) + { + QIot_userdata.deviceInfo.contextID = contextID; +#ifdef QUEC_ENABLE_AT + Ql_iotDSKVSave(); +#endif + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 产品配置-PDP contextID获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint8_t Ql_iotConfigGetPdpContextId(void) +{ + return QIot_userdata.deviceInfo.contextID; +} +/************************************************************************** +** 功能 @brief : 产品配置-服务器信息配置 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConfigSetServer(QIot_protocolType_t type, const char *server_url) +{ + qbool needsave = FALSE; + if (QIOT_CONNMODE_IDLE != Ql_iotConfigGetConnmode() || NULL == server_url || 0 == HAL_STRLEN(server_url)) + { + return FALSE; + } + if (QIOT_PPROTOCOL_MQTT != type) + { + return FALSE; + } + if (NULL != HAL_STRSTR(server_url, QIOT_DMP_SERVERURL_HTTP_DEFAULT)) + { + server_url = QIOT_DMP_SERVERURL_MQTT_DEFAULT; + } + else if (NULL != HAL_STRSTR(server_url, QIOT_DMP_SERVERURL_HTTPS_DEFAULT)) + { + server_url = QIOT_DMP_SERVERURL_MQTTS_DEFAULT; + } + if (HAL_STRLEN(server_url) < sizeof(QIot_userdata.productInfo.serverUrl) && 0 != HAL_STRCMP(QIot_userdata.productInfo.serverUrl, server_url)) + { + HAL_SPRINTF(QIot_userdata.productInfo.serverUrl, "%s", server_url); + /* 设置新的域名与当前本地保存域名不一致清除本地域名解析得到IP地址 */ + HAL_MEMSET(QIot_userdata.productInfo.serverIp, 0x00, sizeof(QIot_userdata.productInfo.serverIp)); + needsave = TRUE; + } + if (needsave) + { + HAL_MEMSET(&QIot_userdata.productInfoCache, 0x00, sizeof(Qiot_productInfo_t)); + HAL_MEMSET(&QIot_userdata.productInfoCloud, 0x00, sizeof(Qiot_productInfo_t)); + QIot_userdata.connectProduct = &QIot_userdata.productInfo; + QIot_userdata.productInfo.secret[0] = 0; + Ql_iotDSKVSave(); + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 产品配置-服务器信息获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Ql_iotConfigGetServer(QIot_protocolType_t *type, char **server_url) +{ + if (type) + { + *type = QIOT_PPROTOCOL_MQTT; + } + if (server_url) + { + if (HAL_STRLEN(QIot_userdata.productInfoCloud.serverUrl)) + { + *server_url = QIot_userdata.productInfoCloud.serverUrl; + } + else + { + *server_url = QIot_userdata.productInfo.serverUrl; + } + } +} +/************************************************************************** +** 功能 @brief : 产品配置-产品信息配置 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConfigSetProductinfo(const char *pk, const char *ps) +{ + qbool needsave = FALSE; + + if (QIOT_CONNMODE_IDLE != Ql_iotConfigGetConnmode() || pk == NULL || ps == NULL || 0 == HAL_STRLEN(pk) || 0 == HAL_STRLEN(ps)) + { + return FALSE; + } + + if (HAL_STRLEN(pk) < sizeof(QIot_userdata.productInfo.productKey) && 0 != HAL_STRCMP(QIot_userdata.productInfo.productKey, pk)) + { + HAL_SPRINTF(QIot_userdata.productInfo.productKey, "%s", pk); + needsave = TRUE; + } + if (HAL_STRLEN(ps) < sizeof(QIot_userdata.productInfo.productSecret) && 0 != HAL_STRCMP(QIot_userdata.productInfo.productSecret, ps)) + { + HAL_SPRINTF(QIot_userdata.productInfo.productSecret, "%s", ps); + needsave = TRUE; + } + if (needsave) + { + HAL_MEMSET(&QIot_userdata.productInfoCache, 0x00, sizeof(Qiot_productInfo_t)); + HAL_MEMSET(&QIot_userdata.productInfoCloud, 0x00, sizeof(Qiot_productInfo_t)); + QIot_userdata.connectProduct = &QIot_userdata.productInfo; + QIot_userdata.productInfo.secret[0] = 0; + Ql_iotDSKVSave(); + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 产品配置-产品信息获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotConfigGetProductinfo(char **pk, char **ps, char **ver) +{ + if (pk) + { + if (HAL_STRLEN(QIot_userdata.productInfoCloud.productKey)) + { + *pk = QIot_userdata.productInfoCloud.productKey; + } + else + { + *pk = QIot_userdata.productInfo.productKey; + } + } + if (ps) + { + if (HAL_STRLEN(QIot_userdata.productInfoCloud.productSecret)) + { + *ps = QIot_userdata.productInfoCloud.productSecret; + } + else + { + *ps = QIot_userdata.productInfo.productSecret; + } + } + if (ver) + *ver = Ql_iotSecureVerGet(); +} +/************************************************************************** +** 功能 @brief : 产品配置-设备生命周期配置 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConfigSetLifetime(quint32_t lifetime) +{ + if (QIOT_CONNMODE_IDLE != Ql_iotConfigGetConnmode() || lifetime == 0 || lifetime > 65535) + { + return FALSE; + } + + if (QIot_userdata.lifetime != lifetime) + { + QIot_userdata.lifetime = lifetime; +#ifdef QUEC_ENABLE_AT + Ql_iotDSKVSave(); +#endif + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 产品配置-设备生命周期获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotConfigGetLifetime(void) +{ + return QIot_userdata.lifetime; +} +/************************************************************************** +** 功能 @brief : 产品配置-sessionKey配置 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool Ql_iotConfigSetSessionFlag(qbool flag) +{ + if (QIOT_CONNMODE_IDLE != Ql_iotConfigGetConnmode()) + { + return FALSE; + } + if (flag != QIot_userdata.sessionInfo.flag) + { + QIot_userdata.sessionInfo.flag = flag; +#ifdef QUEC_ENABLE_AT + Ql_iotDSKVSave(); +#endif + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 产品配置-sessionKey获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool Ql_iotConfigGetSessionFlag(void) +{ + return QIot_userdata.sessionInfo.flag; +} +/************************************************************************** +** 功能 @brief : 增加APP软件版本,在启用quecthing连接之前先配置 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConfigSetAppVersion(const char *appVer) +{ + HAL_FREE(QIot_userdata.softversion); + if (NULL == appVer) + { + QIot_userdata.softversion = HAL_STRDUP(Qhal_softversionGet()); + } + else + { + quint32_t len = HAL_STRLEN(Qhal_softversionGet()) + HAL_STRLEN(appVer); + QIot_userdata.softversion = HAL_MALLOC(len + 1); + if (QIot_userdata.softversion) + { + HAL_SPRINTF(QIot_userdata.softversion, "%s%s", Qhal_softversionGet(), appVer); + } + } + Quos_logPrintf(QUEC_IOT, LL_INFO, "new soft version:%s", QIot_userdata.softversion); + return NULL == QIot_userdata.softversion ? FALSE : TRUE; +} +/************************************************************************** +** 功能 @brief : 获取版本号 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotConfigGetSoftVersion(void) +{ + return QIot_userdata.softversion; +} +/************************************************************************** +** 功能 @brief : 设置MCU版本号 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConfigSetMcuVersion(const char *compno, const char *version) +{ + qbool ret = FALSE; + if (0 == HAL_STRLEN(compno) || HAL_STRLEN(compno) > QIOT_COMPNO_MAXSIZE || HAL_STRLEN(version) > QIOT_COMPVER_MAXSIZE) + { + return ret; + } + char *oldVersion; + qint32_t oldLen = Quos_keyValueExtract(QIot_userdata.mcuVerList, compno, QIOT_MCUVERSION_STRING_SEP, &oldVersion, QIOT_MCUVERSION_STRING_ENDSTR); + if ((0 >= oldLen && 0 != HAL_STRLEN(version)) || (0 < oldLen && 0 != HAL_STRCMP(oldVersion, version))) + { + ret = Quos_keyValueInsert(QIot_userdata.mcuVerList, sizeof(QIot_userdata.mcuVerList), compno, QIOT_MCUVERSION_STRING_SEP, version, QIOT_MCUVERSION_STRING_ENDSTR); +#ifdef QUEC_ENABLE_AT + Ql_iotDSKVSave(); +#endif + } + else + { + ret = TRUE; + } + + Ql_iotCmdOtaMcuVersionModify(compno, version); + return ret; +} +/************************************************************************** +** 功能 @brief : 获取MCU版本号 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotConfigGetMcuVersion(const char *compno, char **version) +{ + if (version) + { + if (NULL == compno) + { + *version = QIot_userdata.mcuVerList; + return HAL_STRLEN(QIot_userdata.mcuVerList); + } + else + { + return Quos_keyValueExtract(QIot_userdata.mcuVerList, compno, QIOT_MCUVERSION_STRING_SEP, version, QIOT_MCUVERSION_STRING_ENDSTR); + } + } + else + { + return 0; + } +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Ql_iotUrcEventCB(quint32_t event, qint32_t errcode, const void *value, quint32_t valLen) +{ +#ifdef QUEC_ENABLE_AT + extern void Ql_iotAtEventCB(quint32_t event, qint32_t errcode, const void *value, quint32_t valLen); + Ql_iotAtEventCB(event, errcode, value, valLen); +#else + if (QIot_userdata.eventCB) + { + QIot_userdata.eventCB(event, errcode, value, valLen); + } +#endif +} +/************************************************************************** +** 功能 @brief : 注册事件回调函数 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +#ifndef QUEC_ENABLE_AT +void FUNCTION_ATTR_ROM Ql_iotConfigSetEventCB(void (*eventCb)(quint32_t event, qint32_t errcode, const void *value, quint32_t valLen)) +{ + QIot_userdata.eventCB = eventCb; +} +#endif +#ifdef QUEC_ENABLE_GATEWAY +/************************************************************************** +** 功能 @brief : 注册子设备事件回调函数 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotConfigSetSubDevEventCB(void (*eventCb)(quint32_t event, qint32_t errcode, const char *subPk, const char *subDk ,const void *value, quint32_t valLen)) +{ + QIot_userdata.SubDevEventCB = eventCb; +} +#endif +/************************************************************************** +** 功能 @brief : 获取当前工作状态 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +QIot_state_e FUNCTION_ATTR_ROM Ql_iotGetWorkState(void) +{ + return QIot_userdata.workState; +} +/************************************************************************** +** 功能 @brief : 设置DK和DS, +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConfigSetDkDs(const char *dk, const char *ds) +{ + if (QIOT_CONNMODE_IDLE != Ql_iotConfigGetConnmode()) + { + return FALSE; + } + if (HAL_STRLEN(dk) > QIOT_DK_MAXSIZE || HAL_STRLEN(ds) > QIOT_DS_MAXSIZE) + { + Quos_logPrintf(QUEC_IOT, LL_ERR, "dk or ds size over"); + return FALSE; + } + qint32_t len = HAL_STRLEN(dk); + while (len--) + { + if (!(__IS_LETTER(dk[len]) || __IS_DIGIT(dk[len]))) + return FALSE; + } + len = HAL_STRLEN(ds); + while (len--) + { + if (!(__IS_LETTER(ds[len]) || __IS_DIGIT(ds[len]))) + return FALSE; + } + qbool needSave = FALSE; + if (0 == HAL_STRLEN(dk)) /* 若新设DK为空且旧DK也是通过本API设置时,将清空用户配置采用默认DK重新认证 */ + { + if (0 != HAL_STRLEN(ds) || 0 == HAL_STRCMP(QIot_userdata.deviceInfo.deviceKey, Qhal_devUuidGet())) + { + return FALSE; + } + HAL_SPRINTF(QIot_userdata.deviceInfo.deviceKey, "%s", Qhal_devUuidGet()); + HAL_MEMSET(QIot_userdata.productInfo.secret, 0, sizeof(QIot_userdata.productInfo.secret)); + needSave = TRUE; + } + else if (0 == HAL_STRCMP(Qhal_devUuidGet(), dk)) + { + return FALSE; + } + else if (0 != HAL_STRCMP(QIot_userdata.deviceInfo.deviceKey, dk)) + { + HAL_SPRINTF(QIot_userdata.deviceInfo.deviceKey, "%s", dk); + HAL_SPRINTF(QIot_userdata.productInfo.secret, "%s", ds ? ds : ""); + needSave = TRUE; + } + else if ((NULL == ds && HAL_STRLEN(QIot_userdata.productInfo.secret) > 0) || (ds && 0 != HAL_STRCMP(QIot_userdata.productInfo.secret, ds))) + { + HAL_SPRINTF(QIot_userdata.productInfo.secret, "%s", ds ? ds : ""); + needSave = TRUE; + } + + if (needSave) + { + Ql_iotDSKVSave(); + } + return TRUE; +} + +/************************************************************************** +** 功能 @brief : 获取dk和ds,只有dk是外部设置的才允许查询 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConfigGetDkDs(char **dk, char **ds) +{ + if (0 == HAL_STRCMP(QIot_userdata.deviceInfo.deviceKey, Qhal_devUuidGet())) + { + return FALSE; + } + if (dk) + { + *dk = QIot_userdata.deviceInfo.deviceKey; + } + if (ds) + { + *ds = QIot_userdata.connectProduct->secret; + } + return TRUE; +} diff --git a/cloud/common/ql_iotConfig.h b/cloud/common/ql_iotConfig.h new file mode 100644 index 0000000..d8b9890 --- /dev/null +++ b/cloud/common/ql_iotConfig.h @@ -0,0 +1,94 @@ +#ifndef __QIOT_COMMON_H__ +#define __QIOT_COMMON_H__ +#include "Ql_iotApi.h" + +#define QUEC_BUS LL_DBG +#define QUEC_LAN LL_DBG +#define QUEC_OTA LL_DBG +#define QUEC_SYS LL_DBG +#define QUEC_IOT LL_DBG +#define QUEC_CONN LL_DBG +#define QUEC_DP LL_DBG +#define QUEC_LOC LL_DBG +#define QUEC_SECURE LL_ERR +#define QUEC_AT LL_DBG + +#define QIOT_DATA_PROTOCOL_VER "1.3.0" /* 数据协议版本 */ +#define QIOT_COM_PROTOCOL_VER "3.0.0" /* 通信协议版本 */ +#define QIOT_SDK_VERSION "2.9.0" +#define QIOT_DMP_SERVERURL_MQTT_DEFAULT "iot-south.quectel.com:1883" +#define QIOT_DMP_SERVERURL_MQTTS_DEFAULT "mqtts://iot-south.quectel.com:8883" +#define QIOT_DMP_SERVERURL_HTTP_DEFAULT "iot-south.quectel.com:2883" +#define QIOT_DMP_SERVERURL_HTTPS_DEFAULT "https://iot-south.quectel.com:2884" +#define QIOT_DMP_IP_MQTT_DEFAULT "106.14.246.239" + +#define QIOT_MCUVERSION_STRING_ENDSTR ";" +#define QIOT_MCUVERSION_STRING_SEP ":" +#define QIOT_PK_MAXSIZE (32) +#define QIOT_PS_MAXSIZE (32) +#define QIOT_DK_MAXSIZE (16) +#define QIOT_DS_MAXSIZE (32) +#define QIOT_M2M_CLIENTID_MAXSIZE (2 + QIOT_PK_MAXSIZE + QIOT_DK_MAXSIZE) + +#define QIOT_OTA_FILEINFO_MAX_SIZE (5) +#define QIOT_ATEVENT_TYPE_STRING(X) \ + ( \ + (X == QUOS_SYSTEM_EVENT_NETWORK) ? "QUOS_SYSTEM_EVENT_NETWORK" \ + : (X == QIOT_ATEVENT_TYPE_AUTH) ? "ATEVENT_TYPE_AUTH" \ + : (X == QIOT_ATEVENT_TYPE_CONN) ? "ATEVENT_TYPE_CONN" \ + : (X == QIOT_ATEVENT_TYPE_SUBCRIBE) ? "ATEVENT_TYPE_SUBCRIBE" \ + : (X == QIOT_ATEVENT_TYPE_SEND) ? "ATEVENT_TYPE_SEND" \ + : (X == QIOT_ATEVENT_TYPE_RECV) ? "ATEVENT_TYPE_RECV" \ + : (X == QIOT_ATEVENT_TYPE_LOGOUT) ? "ATEVENT_TYPE_LOGOUT" \ + : (X == QIOT_ATEVENT_TYPE_OTA) ? "ATEVENT_TYPE_OTA" \ + : (X == QIOT_ATEVENT_TYPE_SERVER) ? "ATEVENT_TYPE_SERVER" \ + : "Unknown") + +typedef struct +{ + char serverUrl[QUOS_DNS_HOSTNANE_MAX_LENGHT]; + char productKey[QIOT_PK_MAXSIZE + 1]; + char productSecret[QIOT_PS_MAXSIZE + 1]; + char serverIp[QUOS_IP_ADDR_MAX_LEN]; + char secret[QIOT_DS_MAXSIZE + 1]; /* 2 == encryptType有效 */ +} Qiot_productInfo_t; + +typedef struct +{ + char key[16]; + char iv[16]; + qbool flag; + qbool usable; +} Qiot_sessionInfo_t; +typedef struct +{ + void *m2mCtx; + qbool netIsConn; /* 网络是否可以通信 */ + QIot_state_e workState; /* 工作状态,用于AT指令查询状态 */ + QIot_connMode_e connMode; /* IOT连接模式 */ + quint8_t connFailCnt; + Qiot_productInfo_t *connectProduct; + Qiot_productInfo_t productInfo; + Qiot_productInfo_t productInfoCloud; + Qiot_productInfo_t productInfoCache; + struct + { + quint8_t contextID; + char deviceKey[QIOT_DK_MAXSIZE + 1]; + } deviceInfo; + Qiot_sessionInfo_t sessionInfo; + quint32_t lifetime; + char *softversion; + char mcuVerList[512]; +#ifndef QUEC_ENABLE_AT + void (*eventCB)(quint32_t event, qint32_t errcode, const void *value, quint32_t valLen); +#endif +#ifdef QUEC_ENABLE_GATEWAY + void (*SubDevEventCB)(quint32_t event, qint32_t errcode, const char *subPk, const char * subDk, const void *value, quint32_t valLen); +#endif +} QIot_userData_t; + +extern QIot_userData_t QIot_userdata; +qbool Ql_iotDSKVSave(void); +void Ql_iotUrcEventCB(quint32_t event, qint32_t errcode, const void *value, quint32_t valLen); +#endif \ No newline at end of file diff --git a/cloud/common/ql_iotConn.c b/cloud/common/ql_iotConn.c new file mode 100644 index 0000000..7452502 --- /dev/null +++ b/cloud/common/ql_iotConn.c @@ -0,0 +1,724 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : 2021-01-08 +** 功能 @brief : DMP接入 +** 硬件 @hardware: +** 其他 @other : +***************************************************************************/ +#include "ql_iotConn.h" +#include "ql_iotConfig.h" +#include "ql_iotSecure.h" +#include "ql_iotDp.h" +#include "Qhal_driver.h" +#include "quos_net.h" +#ifdef QUEC_ENABLE_GATEWAY +#include "ql_iotDMSub.h" +#include "ql_iotGwDev.h" +#endif +#include "ql_iotTtlv.h" + +enum +{ + QL_IOT_ERRCODE_LEVEL_FIRST = 0, + QL_IOT_ERRCODE_LEVEL_SECOND, + QL_IOT_ERRCODE_LEVEL_THIRD, + QL_IOT_ERRCODE_LEVEL_FOURTH, + QL_IOT_ERRCODE_LEVEL_FIFTH, + QL_IOT_ERRCODE_LEVEL_MAX, +}; +const quint32_t ql_errTypeTimeout[] = {SWT_ONE_SECOND, SWT_ONE_SECOND * 30, SWT_ONE_MINUTE * 5, SWT_ONE_MINUTE * 10, SWT_SUSPEND}; +typedef struct +{ + quint32_t errcode : 24; + quint32_t errType : 8; +} ql_errcodeType; + +const ql_errcodeType ql_errcodeAuth[] = + { + { + QIOT_AUTH_ERR_UNKNOWN, + QL_IOT_ERRCODE_LEVEL_FIRST, + }, + { + QIOT_AUTH_ERR_DONE, + QL_IOT_ERRCODE_LEVEL_FOURTH, + }, + { + QIOT_AUTH_ERR_PKPS_INVALID, + QL_IOT_ERRCODE_LEVEL_SECOND, + }, + { + QIOT_AUTH_ERR_PAYLOAD_INVALID, + QL_IOT_ERRCODE_LEVEL_SECOND, + }, + { + QIOT_AUTH_ERR_SIGN_INVALID, + QL_IOT_ERRCODE_LEVEL_SECOND, + }, + { + QIOT_AUTH_ERR_VERSION_INVALID, + QL_IOT_ERRCODE_LEVEL_FOURTH, + }, + { + QIOT_AUTH_ERR_HASH_INVALID, + QL_IOT_ERRCODE_LEVEL_SECOND, + }, + { + QIOT_AUTH_ERR_PK_CHANGE, + QL_IOT_ERRCODE_LEVEL_FOURTH, + }, + { + QIOT_AUTH_ERR_DEVICE_INSIDE, + QL_IOT_ERRCODE_LEVEL_FIRST, + }, + { + QIOT_AUTH_ERR_SERVER_NOTFOUND, + QL_IOT_ERRCODE_LEVEL_FIRST, + }, + { + QIOT_AUTH_ERR_FAIL, + QL_IOT_ERRCODE_LEVEL_FIRST, + }, + { + 0xFFFFFF, + 0, + }, +}; + +const ql_errcodeType ql_errcodeConn[] = + { + { + QIOT_CONN_ERR_DS_INVALID, + QL_IOT_ERRCODE_LEVEL_FIFTH, + }, + { + QIOT_CONN_ERR_DEVICE_FORBID, + QL_IOT_ERRCODE_LEVEL_FOURTH, + }, + { + QIOT_CONN_ERR_DEVICE_INSIDE, + QL_IOT_ERRCODE_LEVEL_FIRST, + }, + { + QIOT_CONN_ERR_VERSION_NOTFOUND, + QL_IOT_ERRCODE_LEVEL_FOURTH, + }, + { + QIOT_CONN_ERR_PING, + QL_IOT_ERRCODE_LEVEL_FIRST, + }, + { + QIOT_CONN_ERR_NET, + QL_IOT_ERRCODE_LEVEL_FIRST, + }, + { + QIOT_CONN_ERR_SERVER_CHANGE, + QL_IOT_ERRCODE_LEVEL_FIRST, + }, + { + 0xFFFFFF, + 0, + }, +}; + + +typedef struct +{ + char *topicType; + quint16_t *cmd; +} QIot_connCmdOutType_t; +quint16_t QIot_cmdOutSys[] = {QIOT_DPCMD_STATUS_RSP, QIOT_DPCMD_STATUS_EVENT, QIOT_DPCMD_INFO_RSP, QIOT_DPCMD_INFO_EVENT, QIOT_DPCMD_EXCE_EVENT, + QIOT_DPCMD_SUB_AUTH, QIOT_DPCMD_SUB_AUTH_RSP, QIOT_DPCMD_SUB_LOGIN, QIOT_DPCMD_SUB_LOGIN_RSP, + QIOT_DPCMD_SUB_LOGOUT, QIOT_DPCMD_SUB_LOGOUT_RSP, QIOT_DPCMD_SUB_UNAUTH_EVENT, QIOT_DPCMD_SUB_UNAUTH_EVENT_RSP, + QIOT_DPCMD_SUB_OFFLINE_EVENT, 0}; +quint16_t QIot_cmdOutBus[] = {QIOT_DPCMD_TSL_RSP, QIOT_DPCMD_TSL_EVENT, QIOT_DPCMD_PASS_EVENT, QIOT_DPCMD_LOC_REPORT, QIOT_DPCMD_LOC_RSP, 0}; +quint16_t QIot_cmdOutOta[] = {QIOT_DPCMD_OTA_COMFIRM, QIOT_DPCMD_OTA_EVENT, QIOT_DPCMD_OTA_REQUEST, 0}; +QIot_connCmdOutType_t QIot_connCmdOutType[] = { + {.topicType = "sys", .cmd = QIot_cmdOutSys}, + {.topicType = "bus", .cmd = QIot_cmdOutBus}, + {.topicType = "ota", .cmd = QIot_cmdOutOta}}; + +#define QIOT_SUB_TOPIC_ROOT "q/1/d/" /* 根订阅topic */ +#define QIOT_PUB_TOPIC_ROOT "q/2/d/" /* 根发布topic */ + +static void *QIot_RunTimer = NULL; +static void ql_iotConnStart(void); + +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void ql_iotConnServerInfoCheck(void) +{ + urlAnalyze_t urlA; + if (FALSE == Quos_urlAnalyze(QIot_userdata.connectProduct->serverUrl, &urlA)) + { + Quos_logPrintf(QUEC_CONN, LL_DBG, "get ip from url and ip manage"); + return; + } + /* 若ip发生改变,则需要触发保存,若当前连接服务器信息为缓存区,则不需要进行保存,需要等到连接DMP平台成功以后才可以保存 */ + char ip[QUOS_IP_ADDR_MAX_LEN]; + if (Quos_netHostnameValidIpGet(urlA.hostname, ip) && 0 != HAL_STRCMP(ip, QIot_userdata.connectProduct->serverIp) && QIot_userdata.connectProduct != &QIot_userdata.productInfoCache) + { + HAL_MEMCPY(QIot_userdata.connectProduct->serverIp, ip, QUOS_IP_ADDR_MAX_LEN); + Quos_logPrintf(QUEC_CONN, LL_DBG, "ip is changeed, save new ip:%s", QIot_userdata.connectProduct->serverIp); + Ql_iotDSKVSave(); + } +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void ql_iotConnEventErrcodeDeal(void) +{ + if (QIot_userdata.connectProduct != &QIot_userdata.productInfoCache) + { + return; + } + + /* 切换连接对象 */ + if (QIot_userdata.connFailCnt > 3) + { + if (0 != HAL_STRLEN(QIot_userdata.productInfoCloud.serverUrl)) + { + QIot_userdata.connectProduct = &QIot_userdata.productInfoCloud; + } + else + { + QIot_userdata.connectProduct = &QIot_userdata.productInfo; + } + QIot_userdata.connFailCnt = 0; + } +} +/************************************************************************** +** 功能 @brief : 连接失败,获取设置定时器超时时间 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static quint32_t ql_iotConnTimerTimeoutGet(const ql_errcodeType *errcodeType, qint32_t errcode) +{ + qint32_t timeout = SWT_ONE_SECOND; + quint8_t idx = 0; + + if (errcodeType != NULL) + { + while (errcodeType[idx].errcode != 0xFFFFFF) + { + if (errcodeType[idx].errcode == errcode) + { + if (QL_IOT_ERRCODE_LEVEL_FIFTH == errcodeType[idx].errType) + { + return ql_errTypeTimeout[QL_IOT_ERRCODE_LEVEL_FIFTH]; + } + timeout = ql_errTypeTimeout[errcodeType[idx].errType]; + + break; + } + idx++; + } + } + + /* 若连续失败超过6次,则强制设置连接间隔为30分钟,并重新打开网络 */ + if (++QIot_userdata.connFailCnt > 6) + { + timeout = SWT_ONE_MINUTE * 30; + return timeout; + } + /* 若连接间隔为1秒则需要对连接时间间隔加一个随机时间,随机时间范围是:-500ms~1500ms,防止同一时间过多设备上线 */ + else if (timeout == SWT_ONE_SECOND) + { + qint16_t rand = (Qhal_randomGet() % 2000); + + timeout += rand - 500; + } + /* 连接失败判断当前是否为cache连接对象,若是则需要切换连接对象为cloud或本地 */ + ql_iotConnEventErrcodeDeal(); + + return timeout * QIot_userdata.connFailCnt; +} +/************************************************************************** +** 功能 @brief : 根据命令查找topic类型 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static char *FUNCTION_ATTR_ROM ql_iotConnTopicTypeByCmdGet(quint16_t cmd) +{ + quint32_t i; + for (i = 0; i < sizeof(QIot_connCmdOutType) / sizeof(QIot_connCmdOutType[0]); i++) + { + quint32_t j = 0; + while (QIot_connCmdOutType[i].cmd[j]) + { + if (QIot_connCmdOutType[i].cmd[j++] == cmd) + { + return QIot_connCmdOutType[i].topicType; + } + } + } + return NULL; +} +/************************************************************************** +** 功能 @brief : 接入定时响应 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotConnTimeoutCB(void *swtimer) +{ + if (QIot_userdata.connFailCnt > 6) + { + Quos_netOpen(); + QIot_userdata.connFailCnt = 0; + return; + } + Quos_swTimerTimeoutSet(swtimer, SWT_SUSPEND); + ql_iotConnStart(); +} +/************************************************************************** +** 功能 @brief : MQTT事件回调 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotConnEventCb(void *chlFd, qint32_t result) +{ + Quos_logPrintf(QUEC_CONN, LL_DBG, "chlFd[%p] result:%s", chlFd, MQTT_ERR_STRING(result)); + switch (result) + { + case QUOS_MQTT_OK_CONNECT: + break; + case QUOS_MQTT_ERR_CONNECT: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_UNKNOW); + break; + case QUOS_MQTT_ERR_INSIDE: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_DEVICE_INSIDE); + break; + case QUOS_MQTT_ERR_PING: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_PING); + break; + case QUOS_MQTT_ERR_NET: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_NET); + break; + case QUOS_MQTT_UNNACCEPTABLE_PROTOCOL: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_VERSION_NOTFOUND); + break; + case QUOS_MQTT_CLIENTID_REJECTED: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_DS_INVALID); + break; + case QUOS_MQTT_SERVER_UNAVAILABLE: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_DS_INVALID); + break; + case QUOS_MQTT_BAD_USERNAME_OR_PASSWORD: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_DS_INVALID); + break; + case QUOS_MQTT_NOT_AUTHORIZED: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_DS_INVALID); + break; + case QUOS_MQTT_OK_SUBSCRIBE: + if (QIOT_STATE_CONNECTING == QIot_userdata.workState) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_SUCC); + Quos_eventPost(QIOT_ATEVENT_TYPE_SUBCRIBE, (void *)QIOT_SUBCRIBE_SUCC); + } + break; + case QUOS_MQTT_ERR_SUBSCRI: + Quos_eventPost(QIOT_ATEVENT_TYPE_SUBCRIBE, (void *)QIOT_SUBCRIBE_ERR); + break; + default: + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_UNKNOW); + break; + } +} +/************************************************************************** +** 功能 @brief : MQTT底层接收接口 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotConnRecv(MQTTString *topicName, quint8_t *payload, quint32_t payloadlen) +{ + Quos_logPrintf(QUEC_CONN, LL_DBG, "topic:%.*s", topicName->lenstring.len, topicName->lenstring.data); + Quos_logHexDump(QUEC_CONN, LL_DUMP, "recv", payload, payloadlen); +#ifdef QUEC_ENABLE_GATEWAY + QIot_Subdev_t *subdev = NULL; +#endif + char * p; + if((p = HAL_STRSTR(topicName->lenstring.data,QIot_userdata.deviceInfo.deviceKey))) + { + if (Ql_iotConfigGetSessionFlag() && QIot_userdata.sessionInfo.usable) + { + payloadlen = Ql_iotSecureDecryptPayload(payload, payloadlen, QIot_userdata.sessionInfo.key, QIot_userdata.sessionInfo.iv); + Quos_logHexDump(QUEC_CONN, LL_DUMP, "Decrypt data", payload, payloadlen); + } + #if 0 + if (FALSE == Ql_iotDpRawDataPickup(payload, payloadlen, &pkg)) + { + Quos_logPrintf(QUEC_CONN, LL_ERR, "data pickup fail: %.*s", payloadlen, payload); + return; + } + #endif + } +#ifdef QUEC_ENABLE_GATEWAY + else + { + subdev = Ql_iotDMSubDevFindByEndPoint(topicName->lenstring.data); + if (NULL == subdev) + { + Quos_logPrintf(QUEC_CONN, LL_ERR, "subdev not connected: %.*s", topicName->lenstring.len, topicName->lenstring.data); + return; + } + else if (subdev->m2msessionInfo.usable) + { + payloadlen = Ql_iotSecureDecryptPayload(payload, payloadlen, subdev->m2msessionInfo.key, subdev->m2msessionInfo.iv); + Quos_logHexDump(QUEC_CONN, LL_DUMP, "Decrypt data", payload, payloadlen); + } + #if 0 + if (FALSE == Ql_iotDpRawDataPickup(payload, payloadlen, &pkg)) + { + return; + } + #endif + } +#endif + char *cliId = HAL_STRSTR(topicName->lenstring.data, QIOT_SUB_TOPIC_ROOT); + if (NULL == cliId || 0 == payloadlen) + { + return; + } + cliId += HAL_STRLEN(QIOT_SUB_TOPIC_ROOT); + char *words[10]; + quint32_t count = Quos_stringSplit(cliId, topicName->lenstring.len - (cliId - topicName->lenstring.data), words, sizeof(words) / sizeof(words[0]), "/", FALSE); + if (2 == count || 3 == count) + { + topicName->lenstring.len = HAL_SPRINTF(topicName->lenstring.data, "%s", words[0]); + if (3 == count) + { + topicName->lenstring.len += HAL_SPRINTF(topicName->lenstring.data + topicName->lenstring.len, "/%s", words[2]); + } +#ifdef QUEC_ENABLE_GATEWAY + if (NULL == subdev) + Ql_iotDpHandle(QIOT_DPAPP_M2M, topicName->lenstring.data, payload, payloadlen); + else +#endif + Ql_iotDpHandle(QIOT_DPAPP_SUBDEV, topicName->lenstring.data, payload, payloadlen); + } +} +/************************************************************************** +** 功能 @brief : mqtt发送 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotConnSend(const char *endPoint, quint16_t mode, quint16_t cmd, quint16_t srcpkgID, quint16_t pkgId, const quint8_t *payload, quint32_t payloadLen, socketRecvNodeCb_f recvCB) +{ +#ifndef QUEC_ENABLE_GATEWAY + UNUSED(srcpkgID); +#endif + static quint16_t m2mPkgId = 0; + if (NULL == QIot_userdata.m2mCtx || mode > 2) + { + return FALSE; + } + qbool isAck = TRUE; + char *topicType = ql_iotConnTopicTypeByCmdGet(cmd); + if (NULL == topicType) + { + return FALSE; + } + + char clientId[QIOT_M2M_CLIENTID_MAXSIZE + 1]; + HAL_SPRINTF(clientId, "qd%s%s", QIot_userdata.connectProduct->productKey, QIot_userdata.deviceInfo.deviceKey); + if (0 == pkgId) + { + isAck = FALSE; + if(NULL == endPoint || 0 == HAL_STRNCMP(endPoint, clientId, HAL_STRLEN(clientId))) + { + pkgId = Ql_iotDpPkgIdUpdate(&m2mPkgId); + } +#ifdef QUEC_ENABLE_GATEWAY + else + { + QIot_Subdev_t *subdev = Ql_iotDMSubDevFindByEndPoint(endPoint); + if (NULL != subdev) + { + pkgId = Ql_iotDpPkgIdUpdate(&subdev->m2mpkgId); + } + } +#endif + } +#ifdef QUEC_ENABLE_GATEWAY + if (0 != srcpkgID) + { + if (!Ql_iotDMSubDevAddAckmap(endPoint, pkgId, srcpkgID, 0)) + { + return FALSE; + } + } +#endif + char pubTopic[sizeof(QIOT_PUB_TOPIC_ROOT) + QIOT_M2M_CLIENTID_MAXSIZE * 2 + 1 + 3 + 1] = {0}; + char *next = NULL; +#ifdef QUEC_ENABLE_GATEWAY + QIot_Subdev_t *subdev = NULL; +#endif + if (NULL == endPoint) /* local与m2m交互消息 */ + { + HAL_SPRINTF(pubTopic, QIOT_PUB_TOPIC_ROOT "%s/%s", clientId, topicType); + } + else if ((next = HAL_STRSTR(endPoint, "/"))) /* APP消息 */ + { + HAL_SPRINTF(pubTopic, QIOT_PUB_TOPIC_ROOT "%.*s/%s/%s", (int)(next - endPoint), endPoint, topicType, next + 1); + } + else /* 子设备消息 */ + { +#ifdef QUEC_ENABLE_GATEWAY + if (0 == HAL_STRNCMP(topicType, "bus", 3)) + { + subdev = Ql_iotDMSubDevFindByEndPoint(endPoint); + HAL_SPRINTF(pubTopic, QIOT_PUB_TOPIC_ROOT "%s/%s", endPoint, topicType); + } + else + { +#endif + HAL_SPRINTF(pubTopic, QIOT_PUB_TOPIC_ROOT "%s/%s", clientId, topicType); +#ifdef QUEC_ENABLE_GATEWAY + } +#endif + } + + quint8_t *pkg = NULL; + payloadLen = Ql_iotDpFormat(&pkg, pkgId, cmd, payload, payloadLen); + Quos_logPrintf(QUEC_CONN, LL_DBG, "len:%u mode:%d topic:%s", payloadLen, mode, pubTopic); + if (0 == payloadLen) + { + return FALSE; + } + qbool ret = FALSE; +#ifdef QUEC_ENABLE_GATEWAY + if (NULL != subdev && TRUE == subdev->m2msessionInfo.usable) + { + quint8_t *outData = NULL; + payloadLen = Ql_iotSecureEncryptPayload(pkg, payloadLen, &outData, subdev->m2msessionInfo.key, subdev->m2msessionInfo.iv); + HAL_FREE(pkg); + if (outData) + { + ret = Quos_mqttPublish(QIot_userdata.m2mCtx, pubTopic, NULL, mode, outData, payloadLen, recvCB, isAck) < 0 ? FALSE : TRUE; + HAL_FREE(outData); + } + } + else if (NULL == subdev && Ql_iotConfigGetSessionFlag() && QIot_userdata.sessionInfo.usable) /* 主设备使用加密 */ + { + quint8_t *outData = NULL; + payloadLen = Ql_iotSecureEncryptPayload(pkg, payloadLen, &outData, QIot_userdata.sessionInfo.key, QIot_userdata.sessionInfo.iv); + HAL_FREE(pkg); + if (outData) + { + ret = Quos_mqttPublish(QIot_userdata.m2mCtx, pubTopic, NULL, mode, outData, payloadLen, recvCB, isAck) < 0 ? FALSE : TRUE; + HAL_FREE(outData); + } + } +#else + if (Ql_iotConfigGetSessionFlag() && QIot_userdata.sessionInfo.usable) /* 主设备使用加密 */ + { + quint8_t *outData = NULL; + payloadLen = Ql_iotSecureEncryptPayload(pkg, payloadLen, &outData, QIot_userdata.sessionInfo.key, QIot_userdata.sessionInfo.iv); + HAL_FREE(pkg); + if (outData) + { + ret = Quos_mqttPublish(QIot_userdata.m2mCtx, pubTopic, NULL, mode, outData, payloadLen, recvCB, isAck) < 0 ? FALSE : TRUE; + HAL_FREE(outData); + } + } +#endif + else + { + ret = Quos_mqttPublish(QIot_userdata.m2mCtx, pubTopic, NULL, mode, pkg, payloadLen, recvCB, isAck) < 0 ? FALSE : TRUE; + HAL_FREE(pkg); + } + return ret; +} + +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotConnStart(void) +{ + urlAnalyze_t urlA; + Quos_logPrintf(QUEC_CONN, LL_DBG, "netIsConn:%s connMode:%s m2mCtx:%p", _BOOL2STR(QIot_userdata.netIsConn), QIOT_CONN_MODE_STRING(QIot_userdata.connMode), QIot_userdata.m2mCtx); + if (TRUE != QIot_userdata.netIsConn || QIOT_CONNMODE_IDLE == QIot_userdata.connMode || NULL != QIot_userdata.m2mCtx) + { + return; + } + if (FALSE == Quos_urlAnalyze(QIot_userdata.connectProduct->serverUrl, &urlA)) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_AUTH, (void *)QIOT_AUTH_ERR_SERVER_NOTFOUND); + } + else if (0 == HAL_STRLEN(QIot_userdata.productInfo.productKey) || 0 == HAL_STRLEN(QIot_userdata.productInfo.productSecret)) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_AUTH, (void *)QIOT_AUTH_ERR_PKPS_INVALID); + } + else + { + Quos_netHostnameSetDefault(urlA.hostname, QIot_userdata.connectProduct->serverIp); + qbool isToAuth = (0 == HAL_STRLEN(QIot_userdata.connectProduct->secret)); + char *password = NULL; + QIot_userdata.sessionInfo.usable = FALSE; + if (isToAuth) + { + password = Ql_iotSecureGenMqttAuthData(QIot_userdata.connectProduct->productKey, QIot_userdata.connectProduct->productSecret, QIot_userdata.deviceInfo.deviceKey); + } + else + { + password = Ql_iotSecureGenMqttConnData(QIot_userdata.connectProduct->productSecret, QIot_userdata.connectProduct->secret); + } + int subQos = 1; + char clientId[QIOT_M2M_CLIENTID_MAXSIZE + 1]; + char subTopicRoot[sizeof(QIOT_SUB_TOPIC_ROOT) + sizeof(clientId) + 3]; + HAL_SPRINTF(clientId, "qd%s%s", QIot_userdata.connectProduct->productKey, QIot_userdata.deviceInfo.deviceKey); + HAL_SPRINTF(subTopicRoot, QIOT_SUB_TOPIC_ROOT "%s/+", clientId); + char *subTopic[1]; + subTopic[0] = subTopicRoot; + qbool ret = Quos_mqttInit(&QIot_userdata.m2mCtx, QIot_userdata.connectProduct->serverUrl, clientId, NULL, password, QIot_userdata.lifetime, 1, subTopic, &subQos, ql_iotConnEventCb, ql_iotConnRecv); + HAL_FREE(password); + if (TRUE == ret) + { + QIot_userdata.workState = isToAuth ? QIOT_STATE_AUTHENTICATING : QIOT_STATE_CONNECTING; + } + else + { + Quos_eventPost(isToAuth ? QIOT_ATEVENT_TYPE_AUTH : QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_DEVICE_INSIDE); + } + } +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotConnStop(void) +{ + Quos_logPrintf(QUEC_CONN, LL_DBG, "netIsConn:%s connMode:%s m2mCtx:%p", _BOOL2STR(QIot_userdata.netIsConn), QIOT_CONN_MODE_STRING(QIot_userdata.connMode), QIot_userdata.m2mCtx); + if (QIot_userdata.m2mCtx) + { + Quos_mqttDeinit(QIot_userdata.m2mCtx); + QIot_userdata.m2mCtx = NULL; + + if (QIOT_CONNMODE_IDLE == QIot_userdata.connMode && TRUE == QIot_userdata.netIsConn) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_LOGOUT, (void *)QIOT_LOGOUT_SUCC); + QIot_userdata.workState = QIOT_STATE_DISCONNECTED; + } + else if (QIOT_CONNMODE_IDLE != QIot_userdata.connMode && TRUE == QIot_userdata.netIsConn) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_NET); + } + } + Quos_swTimerTimeoutSet(QIot_RunTimer, SWT_SUSPEND); +} + +/************************************************************************** +** 功能 @brief : conn事件处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void ql_iotConnEventNotify(qint32_t event, void *arg) +{ + qint32_t errcode = (qint32_t)(pointer_t)arg; + Quos_logPrintf(QUEC_CONN, LL_DBG, "type:%s errcode:%d connMode:%s netIsConn:%s", QIOT_ATEVENT_TYPE_STRING(event), errcode, QIOT_CONN_MODE_STRING(QIot_userdata.connMode), _BOOL2STR(QIot_userdata.netIsConn)); + switch (event) + { + case QIOT_ATEVENT_TYPE_AUTH: + QIot_userdata.workState = QIOT_AUTH_SUCC == errcode ? QIOT_STATE_AUTHENTICATED : QIOT_STATE_AUTHENTICATE_FAILED; + if (QIOT_AUTH_SUCC == errcode) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_SUCC); + Quos_eventPost(QIOT_ATEVENT_TYPE_SUBCRIBE, (void *)QIOT_SUBCRIBE_SUCC); + ql_iotConnServerInfoCheck(); + QIot_userdata.connFailCnt = 0; + } + else + { + Quos_swTimerTimeoutSet(QIot_RunTimer, ql_iotConnTimerTimeoutGet(ql_errcodeAuth, errcode)); + } + break; + case QIOT_ATEVENT_TYPE_CONN: + { + QIot_userdata.workState = QIOT_CONN_SUCC == errcode ? QIOT_STATE_CONNECTED : QIOT_STATE_CONNECT_FAIL; + + if (QIOT_CONN_SUCC == errcode) + { + QIot_userdata.connFailCnt = 0; + Quos_swTimerTimeoutSet(QIot_RunTimer, SWT_SUSPEND); + ql_iotConnServerInfoCheck(); + if (QIot_userdata.connectProduct == &QIot_userdata.productInfoCache) + { + /* 若当前连接服务器信息为缓存区,则需要将数据复制到cloud信息区,并执行保存 */ + HAL_MEMCPY(&QIot_userdata.productInfoCloud, QIot_userdata.connectProduct, sizeof(Qiot_productInfo_t)); + Ql_iotDSKVSave(); + } + } + else + { + if (QIOT_CONN_ERR_DS_INVALID == errcode) + { + HAL_MEMSET(QIot_userdata.connectProduct->secret, 0, sizeof(QIot_userdata.connectProduct->secret)); + Ql_iotDSKVSave(); + } +#ifdef QUEC_ENABLE_GATEWAY + Ql_iotDMSubDevBusDisconnNotify(); +#endif + Quos_swTimerTimeoutSet(QIot_RunTimer, ql_iotConnTimerTimeoutGet(ql_errcodeConn, errcode)); + } + break; + } + case QIOT_ATEVENT_TYPE_SUBCRIBE: + switch (errcode) + { + case QIOT_SUBCRIBE_SUCC: + QIot_userdata.workState = QIOT_STATE_SUBSCRIBED; + break; + default: + QIot_userdata.workState = QIOT_STATE_SUBSCRIBE_FAIL; + Quos_swTimerTimeoutSet(QIot_RunTimer, ql_iotConnTimerTimeoutGet(NULL, errcode)); + break; + } + break; + case QIOT_ATEVENT_TYPE_LOGOUT: + break; + case QIOT_ATEVENT_TYPE_SERVER: + break; + case QUOS_SYSTEM_EVENT_NETWORK: + if(QUOS_SEVENT_NET_CONNTIMEOUT == errcode) + { + Quos_eventPost(QIOT_ATEVENT_TYPE_CONN, (void *)QIOT_CONN_ERR_AP); + } + else if(QUOS_SEVENT_NET_CONNECTED == errcode && FALSE == QIot_userdata.netIsConn) + { + QIot_userdata.netIsConn = TRUE; + ql_iotConnStart(); + } + else if(QUOS_SEVENT_NET_DISCONNECT == errcode && TRUE == QIot_userdata.netIsConn) + { + ql_iotConnStop(); + QIot_userdata.netIsConn = FALSE; +#ifdef QUEC_ENABLE_GATEWAY + Ql_iotDMSubDevBusDisconnNotify(); +#endif + } + return; + default: + return; + } + Ql_iotUrcEventCB(event, errcode, NULL, 0); +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotConnInit(void) +{ + Quos_swTimerStart(&QIot_RunTimer, "quec run", SWT_SUSPEND, 0, ql_iotConnTimeoutCB, NULL); + qint32_t event[] = {QIOT_ATEVENT_TYPE_AUTH, QIOT_ATEVENT_TYPE_CONN, QIOT_ATEVENT_TYPE_SUBCRIBE, QIOT_ATEVENT_TYPE_LOGOUT, QIOT_ATEVENT_TYPE_SERVER, QUOS_SYSTEM_EVENT_NETWORK}; + Quos_eventCbReg(event, sizeof(event) / sizeof(event[0]), ql_iotConnEventNotify); +} diff --git a/cloud/common/ql_iotConn.h b/cloud/common/ql_iotConn.h new file mode 100644 index 0000000..c6dcb45 --- /dev/null +++ b/cloud/common/ql_iotConn.h @@ -0,0 +1,62 @@ +/* + * @Author: your name + * @Date: 2021-11-04 19:23:58 + * @LastEditTime: 2021-11-10 08:52:02 + * @LastEditors: Please set LastEditors + * @Description: In User Settings Edit + * @FilePath: \QuecCSDK\cloud\common\ql_iotConn.h + */ +#ifndef __QIOT_CONN_H__ +#define __QIOT_CONN_H__ +#include "Ql_iotApi.h" + +#define QIOT_CONN_NET_ERRTIME_MAX 6 /* 连续网络异常次数,重启网络 */ +#define QIOT_AUTH_ERR_STRING(X) \ + ( \ + (X == QIOT_AUTH_SUCC) ? "AUTH_SUCC" : (X == QIOT_AUTH_ERR_REQDATA) ? "AUTH_ERR_REQDATA" \ + : (X == QIOT_AUTH_ERR_DONE) ? "AUTH_ERR_DONE" \ + : (X == QIOT_AUTH_ERR_PKPS_INVALID) ? "AUTH_ERR_PKPS_INVALID" \ + : (X == QIOT_AUTH_ERR_PAYLOAD_INVALID) ? "AUTH_ERR_PAYLOAD_INVALID" \ + : (X == QIOT_AUTH_ERR_SIGN_INVALID) ? "AUTH_ERR_SIGN_INVALID" \ + : (X == QIOT_AUTH_ERR_VERSION_INVALID) ? "AUTH_ERR_VERSION_INVALID" \ + : (X == QIOT_AUTH_ERR_HASH_INVALID) ? "AUTH_ERR_HASH_INVALID" \ + : (X == QIOT_AUTH_ERR_PK_CHANGE) ? "AUTH_ERR_PK_CHANGE" \ + : (X == QIOT_AUTH_ERR_DK_ILLEGAL) ? "AUTH_ERR_DK_ILLEGAL" \ + : (X == QIOT_AUTH_ERR_PK_VER_NOCOR) ? "AUTH_ERR_PK_VER_NOCOR" \ + : (X == QIOT_AUTH_ERR_DEVICE_INSIDE) ? "AUTH_ERR_DEVICE_INSIDE" \ + : (X == QIOT_AUTH_ERR_SERVER_NOTFOUND) ? "AUTH_ERR_SERVER_NOTFOUND" \ + : (X == QIOT_AUTH_ERR_FAIL) ? "AUTH_ERR_FAIL" \ + : (X == QIOT_AUTH_ERR_UNKNOWN) ? "AUTH_ERR_UNKNOWN" \ + : "Unknown") + +#define QIOT_CONN_ERR_STRING(X) \ + ( \ + (X == QIOT_CONN_SUCC) ? "CONN_SUCC" : (X == QIOT_CONN_ERR_DS_INVALID) ? "CONN_ERR_DS_INVALID" \ + : (X == QIOT_CONN_ERR_DEVICE_FORBID) ? "CONN_ERR_DEVICE_FORBID" \ + : (X == QIOT_CONN_ERR_DEVICE_INSIDE) ? "CONN_ERR_DEVICE_INSIDE" \ + : (X == QIOT_CONN_ERR_VERSION_NOTFOUND) ? "CONN_ERR_VERSION_NOTFOUND" \ + : (X == QIOT_CONN_ERR_PING) ? "CONN_ERR_PING" \ + : (X == QIOT_CONN_ERR_NET) ? "CONN_ERR_NET" \ + : (X == QIOT_CONN_ERR_SERVER_CHANGE) ? "QIOT_CONN_ERR_SERVER_CHANGE" \ + : (X == QIOT_CONN_ERR_UNKNOW) ? "CONN_ERR_UNKNOW" \ + : "Unknown") + +#define QIOT_SUBCRIBE_ERR_STRING(X) \ + ( \ + (X == QIOT_SUBCRIBE_SUCC) ? "SUBCRIBE_SUCC" : (X == QIOT_SUBCRIBE_ERR) ? "SUBCRIBE_ERR" \ + : "Unknown") + +#define QIOT_LOGOUT_ERR_STRING(X) \ + ( \ + (X == QIOT_LOGOUT_SUCC) ? "LOGOUT_SUCC" : "Unknown") + +#define QIOT_CONN_MODE_STRING(X) \ + ( \ + (X == QIOT_CONNMODE_IDLE) ? "CONNMODE_IDLE" : (X == QIOT_CONNMODE_REQ) ? "CONNMODE_REQ" \ + : (X == QIOT_CONNMODE_AUTO) ? "CONNMODE_AUTO" \ + : "Unknown") +void Ql_iotConnInit(void); +qbool Ql_iotConnSend(const char *endPoint, quint16_t mode, quint16_t cmd, quint16_t srcpkgId, quint16_t pkgId, const quint8_t *payload, quint32_t payloadLen, socketRecvNodeCb_f recvCB); +#ifdef QUEC_ENABLE_GATEWAY +#endif +#endif \ No newline at end of file diff --git a/cloud/common/ql_iotDp.c b/cloud/common/ql_iotDp.c new file mode 100644 index 0000000..d309e38 --- /dev/null +++ b/cloud/common/ql_iotDp.c @@ -0,0 +1,399 @@ +#include "ql_iotDp.h" +#include "ql_iotTtlv.h" +#include "ql_iotConfig.h" +#include "ql_iotCmdBus.h" +#include "ql_iotCmdOTA.h" +#include "ql_iotCmdSys.h" +#include "ql_iotCmdLan.h" +#include "ql_iotCmdLoc.h" +#include "ql_iotConn.h" +#ifdef QUEC_ENABLE_GATEWAY +#include "ql_iotGwDev.h" +#endif + +/* 协议坐标 */ +#define DP_POS_VER_1 0 +#define DP_POS_VER_2 1 +#define DP_POS_LEN_1 2 +#define DP_POS_LEN_2 3 +#define DP_POS_SUM 4 +#define DP_POS_PID_1 5 +#define DP_POS_PID_2 6 +#define DP_POS_CMD_1 7 +#define DP_POS_CMD_2 8 +#define DP_POS_DATA 9 + +/* 协议版本 */ +#define DP_VER_HEADER 0XAA /* 协议版本固定头部 */ +#define DP_VER_CURRENT 0XAA /* 当前协议版本 */ +#define DP_VER_ESC 0X55 /* 协议版本转义符号 */ + +QIot_cmdTable_t QIot_cmdTableLocal[] = + { + {.cmd = QIOT_DPCMD_TSL_REQ, .cmdHandle = Ql_iotCmdBusPhymodelReqRecv}, /* 物模型状态获取 */ + {.cmd = QIOT_DPCMD_TSL_WRITE, .cmdHandle = Ql_iotCmdBusPhymodelWriteRecv}, /* 物模型数据下发 */ + {.cmd = QIOT_DPCMD_PASS_WRITE, .cmdHandle = Ql_iotCmdBusPassTransRecv}, /* 透传数据下发 */ + {.cmd = QIOT_DPCMD_LOC_REQ, .cmdHandle = Ql_iotCmdLocDataReqRecv}, /* 获取实时定位信息 */ + {.cmd = QIOT_DPCMD_OTA_NOTIFY, .cmdHandle = Ql_iotCmdOtaNotify}, /* OTA升级任务通知 */ + {.cmd = QIOT_DPCMD_OTA_FW_INFO, .cmdHandle = Ql_iotCmdOtaFwInfo}, /* 固件信息下发 */ + {.cmd = QIOT_DPCMD_STATUS_REQ, .cmdHandle = Ql_iotCmdSysStatusRecv}, /* 设备状态获取 */ + {.cmd = QIOT_DPCMD_INFO_REQ, .cmdHandle = Ql_iotCmdSysDevInfoRecv}, /* 模组信息获取 */ + {.cmd = QIOT_DPCMD_EXCE_WRITE, .cmdHandle = Ql_iotCmdSysExceWrite}, /* 平台异常通知到设备 */ + {.cmd = QIOT_DPCMD_DEV_CONFIG_WRITE, .cmdHandle = Ql_iotCmdSysTerManage}, /* 设备配置命令 */ +#ifdef QUEC_ENABLE_GATEWAY + {.cmd = QIOT_DPCMD_SUB_AUTH_RSP, .cmdHandle = Ql_gatewayDeviceSubAuthResponse}, /* 子设备认证--平台回复 */ + {.cmd = QIOT_DPCMD_SUB_LOGIN_RSP, .cmdHandle = Ql_gatewayDeviceSubLoginResponse}, /* 子设备登陆--平台回复 */ + {.cmd = QIOT_DPCMD_SUB_UNAUTH_EVENT_RSP, .cmdHandle = Ql_gatewayDeviceSubUnauthResponse}, /* 子设备注销--平台回复 */ + {.cmd = QIOT_DPCMD_SUB_LOGOUT_RSP, .cmdHandle = Ql_gatewayDeviceSubLogoutResponse}, /* 子设备登出平台响应 */ + {.cmd = QIOT_DPCMD_SUB_OFFLINE_EVENT, .cmdHandle = Ql_gatewayDeviceSubOfflineEvent}, /* 子设备下线事件 */ +#endif +}; +#ifdef QUEC_ENABLE_LAN +QIot_cmdTable_t QIot_cmdTableLan[] = + { + {.cmd = QIOT_DPCMD_LAN_DISCOVER_REQ, .cmdHandle = ql_iotLanDevDiscover}, /* 发现局域网下设备 */ + //{.cmd = QIOT_DPCMD_LAN_AUTH_COND_REQ, .cmdHandle = NULL}, /* 局域网认证前置条件请求 */ + //{.cmd = QIOT_DPCMD_LAN_AUTH_SIGN_REQ, .cmdHandle = NULL}, /* 局域网认证签名请求 */ +}; +#endif +#ifdef QUEC_ENABLE_GATEWAY +QIot_cmdTable_t QIot_cmdTableSubDev[] = + { + {.cmd = QIOT_DPCMD_TSL_REQ, .cmdHandle = Ql_gatewayDeviceSubTslDataRead}, /* 物模型状态获取 */ + {.cmd = QIOT_DPCMD_TSL_WRITE, .cmdHandle = Ql_gatewayDeviceSubTslDataWrite}, /* 物模型数据下发 */ + {.cmd = QIOT_DPCMD_PASS_WRITE, .cmdHandle = Ql_gatewayDeviceSubPassTransData}, /* 透传数据下发 */ + //{.cmd = QIOT_DPCMD_LOC_REQ, .cmdHandle = NULL}, /* 获取实时定位信息 */ + {.cmd = QIOT_DPCMD_STATUS_REQ, .cmdHandle = Ql_gatewayDeviceSubStateRead}, /* 设备状态获取 */ + {.cmd = QIOT_DPCMD_INFO_REQ, .cmdHandle = Ql_gatewayDeviceSubInfoRead}, /* 模组信息获取 */ + + //{.cmd = QIOT_DPCMD_STATUS_EVENT, .cmdHandle = Ql_gatewayDeviceSubStateReport}, /* 子设备状态上报 */ + //{.cmd = QIOT_DPCMD_INFO_EVENT, .cmdHandle = Ql_gatewayDeviceSubInfoReport}, /* 子设备模组信息上报 */ + //{.cmd = QIOT_GATEWAY_SUB_FIND_GATEWAY, .cmdHandle = Ql_gatewayDeviceSubReciveBroadMsg}, /* 子设备广播查找网关 */ + //{.cmd = QIOT_GATEWAY_SUB_CONN_GATEWAY, .cmdHandle = Ql_gatewayDeviceSubConnGatwayMsg}, /* 子设备连接网关 */ + //{.cmd = QIOT_GATEWAY_SUB_AUTH, .cmdHandle = Ql_gatewayDeviceSubAuthMsg}, /* 子设备发起认证 */ + //{.cmd = QIOT_GATEWAY_SUB_LOGIN, .cmdHandle = Ql_gatewayDeviceSubConnMsg}, /* 子设备发起登录平台 */ + //{.cmd = QIOT_GATEWAY_SUB_LOGOUT_EVENT, .cmdHandle = Ql_gatewayDeviceSubDisconnMsg}, /* 子设备发起下线 */ + //{.cmd = QIOT_GATEWAY_SUB_UNAUTH_EVENT, .cmdHandle = Ql_gatewayDeviceSubUnauthMsg}, /* 子设备发起注销 */ + //{.cmd = QIOT_DPCMD_PASS_EVENT, .cmdHandle = Ql_gatewayDeviceSubPassTransReport}, /* 子设备上传透传数据 */ + //{.cmd = QIOT_DPCMD_TSL_EVENT, .cmdHandle = Ql_gatewayDeviceSubPhymodelReport}, /* 子设备上报物模型数据 */ + //{.cmd = QIOT_GATEWAY_SUB_READ_STATE, .cmdHandle = Ql_gatewayDeviceStateQuery}, /* 子设备查询网关状态 */ + +}; +#endif +QIot_dpAppSend_t QIot_dpAppSend[] = + { + {.app = QIOT_DPAPP_M2M, .send = Ql_iotConnSend}, +#ifdef QUEC_ENABLE_GATEWAY +//{.app = QIOT_DPAPP_SUBDEV, .send = Ql_iotConnSubSendUart}, +#endif +}; +/************************************************************************** +** 功能 @brief : 合并数据包,并进行0x55转换 +** 输入 @param : +** 输出 @retval: 输出数据长度 +** 备注 @remark: 这样处理主要是为了减少malloc次数 +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotDpFormat(quint8_t **buf, quint16_t pId, quint16_t cmd, const quint8_t *payload, quint32_t payloadLen) +{ + quint8_t head[DP_POS_DATA]; + head[DP_POS_VER_1] = DP_VER_HEADER; + head[DP_POS_VER_2] = DP_VER_CURRENT; + _U16_ARRAY01(payloadLen + DP_POS_DATA - DP_POS_SUM, &head[DP_POS_LEN_1]); + _U16_ARRAY01(pId, &head[DP_POS_PID_1]); + _U16_ARRAY01(cmd, &head[DP_POS_CMD_1]); + head[DP_POS_SUM] = (quint8_t)Quos_crcCalculate(0, &head[DP_POS_PID_1], DP_POS_DATA - DP_POS_PID_1); + head[DP_POS_SUM] = (quint8_t)Quos_crcCalculate(head[DP_POS_SUM], payload, payloadLen); + quint32_t escCount = DP_POS_DATA; + quint32_t i; + for (i = DP_POS_VER_2; i < DP_POS_CMD_2; i++) + { + if (DP_VER_HEADER == head[i] && (DP_VER_CURRENT == head[i + 1] || DP_VER_ESC == head[i + 1])) + { + escCount++; + } + } + if (DP_VER_HEADER == head[DP_POS_CMD_2] && (0 == payloadLen || DP_VER_ESC == payload[0])) + { + escCount++; + } + escCount += payloadLen; + if (payloadLen > 0) + { + for (i = 0; i < payloadLen - 1; i++) + { + if (DP_VER_HEADER == payload[i] && (DP_VER_CURRENT == payload[i + 1] || DP_VER_ESC == payload[i + 1])) + { + escCount++; + } + } + if (DP_VER_HEADER == payload[payloadLen - 1]) + { + escCount++; + } + } + + quint8_t *pkgBuf = HAL_MALLOC(escCount); + if (NULL == pkgBuf) + { + return 0; + } + escCount = 0; + pkgBuf[escCount++] = DP_VER_HEADER; + for (i = DP_POS_VER_2; i < DP_POS_CMD_2; i++) + { + pkgBuf[escCount++] = head[i]; + if (DP_VER_HEADER == head[i] && (DP_VER_CURRENT == head[i + 1] || DP_VER_ESC == head[i + 1])) + { + pkgBuf[escCount++] = DP_VER_ESC; + } + } + pkgBuf[escCount++] = head[DP_POS_CMD_2]; + if (DP_VER_HEADER == head[DP_POS_CMD_2] && (0 == payloadLen || DP_VER_ESC == pkgBuf[0])) + { + pkgBuf[escCount++] = DP_VER_ESC; + } + if (payloadLen > 0) + { + for (i = 0; i < payloadLen - 1; i++) + { + pkgBuf[escCount++] = payload[i]; + if (DP_VER_HEADER == payload[i] && (DP_VER_CURRENT == payload[i + 1] || DP_VER_ESC == payload[i + 1])) + { + pkgBuf[escCount++] = DP_VER_ESC; + } + } + pkgBuf[escCount++] = payload[payloadLen - 1]; + if (DP_VER_HEADER == payload[payloadLen - 1]) + { + pkgBuf[escCount++] = DP_VER_ESC; + } + } + *buf = pkgBuf; + Quos_logHexDump(QUEC_DP, LL_DUMP, "send pkg", pkgBuf, escCount); + return escCount; +} +/************************************************************************** +** 功能 @brief : 将DP数据流转成DP结构体 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +QIot_dpPackage_t FUNCTION_ATTR_ROM Ql_iotDpRaw2Package(const quint8_t *buf, quint32_t len) +{ + QIot_dpPackage_t pkg; + pkg.head = _ARRAY01_U16(&buf[DP_POS_VER_1]); + pkg.sum = buf[DP_POS_SUM]; + pkg.pkgId = _ARRAY01_U16(&buf[DP_POS_PID_1]); + pkg.cmd = _ARRAY01_U16(&buf[DP_POS_CMD_1]); + pkg.payloadLen = len - DP_POS_DATA; + if (pkg.payloadLen > 0) + { + pkg.payload = (quint8_t *)&buf[DP_POS_DATA]; + } + else + { + pkg.payload = NULL; + } + + return pkg; +} +/************************************************************************** +** 功能 @brief : 从原始完整数据包中提取协议包,结束后buf内容可能会改变 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotDpRawDataPickup(quint8_t *buf, quint32_t len, QIot_dpPackage_t *pkg) +{ + if (len < DP_POS_DATA || DP_VER_HEADER != buf[0] || DP_VER_CURRENT != buf[1]) + { + return FALSE; + } + quint16_t pkgLen = 0, offset = DP_POS_LEN_1; + quint32_t i; + for (i = offset; i < len; i++) + { + if (DP_VER_HEADER != buf[i - 1] || DP_VER_ESC != buf[i]) + { + buf[offset++] = buf[i]; + } + if (DP_POS_SUM == offset) + { + pkgLen = _ARRAY01_U16(&buf[DP_POS_LEN_1]); + } + else if (pkgLen + DP_POS_SUM == offset) + { + quint16_t pkgId = _ARRAY01_U16(&buf[DP_POS_PID_1]); + if (0x0000 == pkgId || 0xFFFF == pkgId) + { + quint16_t cmd = _ARRAY01_U16(&buf[DP_POS_CMD_1]); + Quos_logPrintf(QUEC_DP, LL_ERR, "pkgid[0x%04X] cmd[0x%04X]", pkgId, cmd); + } + else if ((quint8_t)Quos_crcCalculate(0, buf + DP_POS_PID_1, offset - DP_POS_PID_1) == buf[DP_POS_SUM]) + { + Quos_logHexDump(QUEC_DP, LL_DUMP, "packet", buf, offset); + if (pkg) + { + *pkg = Ql_iotDpRaw2Package(buf, offset); + } + return TRUE; + } + break; + } + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : 检查命令字是否在支持列表 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +QIot_dpCmdHandle_f FUNCTION_ATTR_ROM Ql_iotDpCmdIn(quint16_t cmd, QIot_cmdTable_t table[], quint32_t size) +{ + quint32_t i; + for (i = 0; i < size; i++) + { + if (cmd == table[i].cmd) + { + return table[i].cmdHandle; + } + } + return NULL; +} +/************************************************************************** +** 功能 @brief : 数据接收处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotDpHandle(QIot_dpAppType_e app, const char *endPoint, quint8_t *payload, quint32_t payloadLen) +{ + Quos_logPrintf(QUEC_DP, LL_DBG, "app:%d ep:%s", app, endPoint); + char clientId[QIOT_M2M_CLIENTID_MAXSIZE + 1]; + HAL_SPRINTF(clientId, "qd%s%s", QIot_userdata.connectProduct->productKey, QIot_userdata.deviceInfo.deviceKey); + QIot_dpCmdHandle_f cmdHandle = NULL; + QIot_dpPackage_t dpPkg; + + if (NULL == payload || FALSE == Ql_iotDpRawDataPickup(payload, payloadLen, &dpPkg)) + { + Quos_logPrintf(QUEC_CONN, LL_ERR, "data pickup fail: %.*s", payloadLen, payload); + return; + } + if (NULL == endPoint || 0 == HAL_STRNCMP(endPoint, clientId, HAL_STRLEN(clientId))) // TODO + { + cmdHandle = Ql_iotDpCmdIn(dpPkg.cmd, QIot_cmdTableLocal, sizeof(QIot_cmdTableLocal) / sizeof(QIot_cmdTableLocal[0])); + } +#ifdef QUEC_ENABLE_LAN + else if (QIOT_DPAPP_LANPHONE == app) + { + cmdHandle = Ql_iotDpCmdIn(dpPkg.cmd, QIot_cmdTableLan, sizeof(QIot_cmdTableLan) / sizeof(QIot_cmdTableLan[0])); + } +#endif +#ifdef QUEC_ENABLE_GATEWAY + else + { + cmdHandle = Ql_iotDpCmdIn(dpPkg.cmd, QIot_cmdTableSubDev, sizeof(QIot_cmdTableSubDev) / sizeof(QIot_cmdTableSubDev[0])); + } +#endif + if (cmdHandle) + { + cmdHandle(app, endPoint, dpPkg.pkgId, dpPkg.payload, dpPkg.payloadLen); + } +} + +/************************************************************************** +** 功能 @brief : 更新pkgid +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint16_t FUNCTION_ATTR_ROM Ql_iotDpPkgIdUpdate(quint16_t *pkgId) +{ + (*pkgId)++; + if (0 == (*pkgId) || 0xFFFF == (*pkgId)) + { + *pkgId = 1; + } + return *pkgId; +} + +/************************************************************************** +** 功能 @brief : 发送通用请求数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM ql_iotDpSendCommon(QIot_dpAppType_e app, const char *endPoint, quint16_t mode, quint16_t cmd, quint16_t srcPkgId, quint16_t pkgId, const quint8_t *payload, quint32_t payloadLen, socketRecvNodeCb_f recvCB) +{ + UNUSED(srcPkgId); + qbool ret = FALSE; + quint32_t i; + for (i = 0; i < sizeof(QIot_dpAppSend) / sizeof(QIot_dpAppSend[0]); i++) + { + if (app & QIot_dpAppSend[i].app) + { + if ((QIot_dpAppSend[i].send)(endPoint, mode, cmd, srcPkgId, pkgId, payload, payloadLen, recvCB)) + { + ret = TRUE; + } + } + } + return ret; +} +/************************************************************************** +** 功能 @brief : 发送TTLV数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM ql_iotDpSendTtlv(QIot_dpAppType_e app, const char *endPoint, quint16_t mode, quint16_t cmd, quint16_t srcPkgId, quint16_t pkgId, const void *ttlvHead, socketRecvNodeCb_f recvCB) +{ + qbool ret = FALSE; + quint32_t len = Ql_iotTtlvFormatLen(ttlvHead); + quint8_t *buf = NULL; + if (len) + { + if ((buf = HAL_MALLOC(len)) == NULL) + { + return FALSE; + } + len = Ql_iotTtlvFormat(ttlvHead, buf); + } + ret = ql_iotDpSendCommon(app, endPoint, mode, cmd, srcPkgId, pkgId, buf, len, recvCB); + if (buf) + { + HAL_FREE(buf); + } + return ret; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotDpSendCommonReq(QIot_dpAppType_e app, const char *endPoint, quint16_t srcPkgId, quint16_t mode, quint16_t cmd, const quint8_t *payload, quint32_t payloadLen, socketRecvNodeCb_f recvCB) +{ + return ql_iotDpSendCommon(app, endPoint, mode, cmd, srcPkgId, 0, payload, payloadLen, recvCB); +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool Ql_iotDpSendCommonRsp(QIot_dpAppType_e app, const char *endPoint, quint16_t cmd, quint16_t pkgId, const quint8_t *payload, quint32_t payloadLen) +{ + return ql_iotDpSendCommon(app, endPoint, 0, cmd, 0, pkgId, payload, payloadLen, NULL); +} + +/************************************************************************** +** 功能 @brief : 发送TTLV请求数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotDpSendTtlvReq(QIot_dpAppType_e app, const char *endPoint, quint16_t srcPkgId, quint16_t mode, quint16_t cmd, const void *ttlvHead, socketRecvNodeCb_f recvCB) +{ + return ql_iotDpSendTtlv(app, endPoint, mode, cmd, srcPkgId, 0, ttlvHead, recvCB); +} +/************************************************************************** +** 功能 @brief : 发送TTLV应答数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotDpSendTtlvRsp(QIot_dpAppType_e app, const char *endPoint, quint16_t cmd, quint16_t pkgId, const void *ttlvHead) +{ + return ql_iotDpSendTtlv(app, endPoint, 0, cmd, 0, pkgId, ttlvHead, NULL); +} diff --git a/cloud/common/ql_iotDp.h b/cloud/common/ql_iotDp.h new file mode 100644 index 0000000..bc2203c --- /dev/null +++ b/cloud/common/ql_iotDp.h @@ -0,0 +1,84 @@ +#ifndef __QUOS_DATA_PACKET_H__ +#define __QUOS_DATA_PACKET_H__ +#include "Ql_iotApi.h" + +enum +{ + QIOT_DPCMD_TSL_REQ = 0X0011, /* 物模型状态获取 */ + QIOT_DPCMD_TSL_RSP = 0X0012, /* 物模型状态上报-回复 */ + QIOT_DPCMD_TSL_WRITE = 0X0013, /* 物模型数据下发 */ + QIOT_DPCMD_TSL_EVENT = 0X0014, /* 物模型数据上报 */ + QIOT_DPCMD_PASS_WRITE = 0X0023, /* 透传数据下发 */ + QIOT_DPCMD_PASS_EVENT = 0X0024, /* 透传数据上发 */ + QIOT_DPCMD_STATUS_REQ = 0X0031, /* 设备状态获取 */ + QIOT_DPCMD_STATUS_RSP = 0X0032, /* 设备状态上报-回复 */ + QIOT_DPCMD_STATUS_EVENT = 0X0034, /* 设备状态上报 */ + QIOT_DPCMD_INFO_REQ = 0X0041, /* 模组信息获取 */ + QIOT_DPCMD_INFO_RSP = 0X0042, /* 模组信息上报-回复 */ + QIOT_DPCMD_INFO_EVENT = 0X0044, /* 模组信息上报 */ + QIOT_DPCMD_EXCE_WRITE = 0X00A3, /* 异常通知 */ + QIOT_DPCMD_EXCE_EVENT = 0X00A4, /* 异常通知 */ + QIOT_DPCMD_DEV_MANAGER_WRITE = 0X00B3, /* 设备管理命令 */ + QIOT_DPCMD_DEV_CONFIG_WRITE = 0X00B4, /* 设备配置命令 */ + QIOT_DPCMD_DEV_BINDCODE_WRITE = 0X00B5, /* 设备上报绑定信息,仅在有局域网通信时有效 */ + QIOT_DPCMD_OTA_NOTIFY = 0X0111, /* OTA升级任务通知 */ + QIOT_DPCMD_OTA_COMFIRM = 0X0112, /* OTA确认-OTA升级任务通知确认是否要升级 */ + QIOT_DPCMD_OTA_FW_INFO = 0X0113, /* 固件信息下发 */ + QIOT_DPCMD_OTA_EVENT = 0X0114, /* OTA过程状态上报 */ + QIOT_DPCMD_OTA_REQUEST = 0X115, /* OTA请求 */ + QIOT_DPCMD_LOC_CFG_WRITE = 0X0121, /* 设置定位信息上报内容,不再支持 */ + QIOT_DPCMD_LOC_REPORT = 0X0122, /* 定位信息上报 */ + QIOT_DPCMD_LOC_REQ = 0X0123, /* 获取实时定位信息 */ + QIOT_DPCMD_LOC_RSP = 0X0124, /* 获取实时定位信息-回复 */ + QIOT_DPCMD_LAN_SEND_ACK = 0X7000, /* 应答局域网通信的请求包,相当于mqtt puback */ + QIOT_DPCMD_LAN_DISCOVER_REQ = 0X7001, /* 发现局域网下设备 */ + QIOT_DPCMD_LAN_DISCOVER_RSP = 0X7002, /* 应答发现局域网下设备 */ + QIOT_DPCMD_LAN_AUTH_COND_REQ = 0X7003, /* 局域网认证前置条件请求 */ + QIOT_DPCMD_LAN_AUTH_COND_RSP = 0X7004, /* 局域网认证前置条件应答 */ + QIOT_DPCMD_LAN_AUTH_SIGN_REQ = 0X7005, /* 局域网认证签名请求 */ + QIOT_DPCMD_LAN_AUTH_SIGN_RSP = 0X7006, /* 局域网认证签名应答 */ + QIOT_DPCMD_SUB_AUTH = 0x00C0, /* 子设备认证指令 */ + QIOT_DPCMD_SUB_AUTH_RSP = 0x00C1, /* 子设备认证回复 */ + QIOT_DPCMD_SUB_LOGIN = 0x00C2, /* 子设备登陆指令 */ + QIOT_DPCMD_SUB_LOGIN_RSP = 0x00C3, /* 子设备登陆返回 */ + QIOT_DPCMD_SUB_LOGOUT = 0x00C4, /* 子设备登出 */ + QIOT_DPCMD_SUB_LOGOUT_RSP = 0x00C5, /* 子设备登出回复 */ + QIOT_DPCMD_SUB_UNAUTH_EVENT = 0x00C6, /* 子设备注销 */ + QIOT_DPCMD_SUB_UNAUTH_EVENT_RSP = 0x00C7, /* 子设备注销返回 */ + QIOT_DPCMD_SUB_OFFLINE_EVENT = 0x00C8, /* 子设备下线通知 */ +}; + +typedef struct +{ + quint16_t head; + quint8_t sum; + quint16_t pkgId; + quint16_t cmd; + quint16_t payloadLen; + quint8_t *payload; +} QIot_dpPackage_t; + +typedef qbool (*QIot_dpAppSend_ftemp)(const char *endPoint, QIot_dpPackage_t dpPkg, socketRecvNodeCb_f recvCB); +typedef qbool (*QIot_dpAppSend_f)(const char *endPoint, quint16_t mode, quint16_t cmd, quint16_t srcpkgId, quint16_t pkgId, const quint8_t *payload, quint32_t payloadLen, socketRecvNodeCb_f recvCB); +typedef struct +{ + QIot_dpAppType_e app; + QIot_dpAppSend_f send; +} QIot_dpAppSend_t; + +typedef void (*QIot_dpCmdHandle_f)(QIot_dpAppType_e app, const char *endPoint, quint16_t pkgId, quint8_t *payload, quint16_t payloadLen); +typedef struct +{ + quint16_t cmd; + QIot_dpCmdHandle_f cmdHandle; +} QIot_cmdTable_t; + +quint32_t Ql_iotDpFormat(quint8_t **buf, quint16_t pId, quint16_t cmd, const quint8_t *payload, quint32_t payloadLen); +quint16_t Ql_iotDpPkgIdUpdate(quint16_t *pkgId); +qbool Ql_iotDpRawDataPickup(quint8_t *buf, quint32_t len, QIot_dpPackage_t *pkg); +void Ql_iotDpHandle(QIot_dpAppType_e app, const char *endPoint, quint8_t *payload, quint32_t payloadLen); +qbool Ql_iotDpSendCommonReq(QIot_dpAppType_e app, const char *endPoint, quint16_t srcPkgId, quint16_t mode, quint16_t cmd, const quint8_t *payload, quint32_t payloadLen, socketRecvNodeCb_f recvCB); +qbool Ql_iotDpSendCommonRsp(QIot_dpAppType_e app, const char *endPoint, quint16_t cmd, quint16_t pkgId, const quint8_t *payload, quint32_t payloadLen); +qbool Ql_iotDpSendTtlvReq(QIot_dpAppType_e app, const char *endPoint, quint16_t srcPkgId, quint16_t mode, quint16_t cmd, const void *ttlvHead, socketRecvNodeCb_f recvCB); +qbool Ql_iotDpSendTtlvRsp(QIot_dpAppType_e app, const char *endPoint, quint16_t cmd, quint16_t pkgId, const void *ttlvHead); +#endif diff --git a/cloud/common/ql_iotSecure.c b/cloud/common/ql_iotSecure.c new file mode 100644 index 0000000..217aded --- /dev/null +++ b/cloud/common/ql_iotSecure.c @@ -0,0 +1,312 @@ +/************************************************************************* +** 鍒涘缓浜 @author : 鍚村仴瓒 JCWu +** 鐗堟湰 @version : V1.0.0 鍘熷鐗堟湰 +** 鏃ユ湡 @date : 2020-12-25 +** 鍔熻兘 @brief : dmp瀹夊叏绠楁硶 +** 纭欢 @hardware锛 +** 鍏朵粬 @other 锛 +***************************************************************************/ +#include "ql_iotSecure.h" +#include "ql_iotConfig.h" +#include "Qhal_driver.h" + +#define QIOT_MQTT_AUTH_VERSION "3" +#define QIOT_COAP_AUTH_VERSION "2" + +/************************************************************************** +** 鍔熻兘 @brief : 鑾峰彇璁よ瘉鍗忚鐗堟湰鍙 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotSecureVerGet(void) +{ + return QIOT_MQTT_AUTH_VERSION; +} +/************************************************************************** +** 鍔熻兘 @brief : 鐢熸垚鎺ュ叆鐨勭鍚 +** 杈撳叆 @param : +** 杈撳嚭 @retval: 杩斿洖鍊奸渶瑕佸閮‵REE鍐呭瓨 +***************************************************************************/ +char FUNCTION_ATTR_ROM *ql_iotSecureGenConnSign(const char *ps, const char *ds, const char *random) +{ + /* 鍒嗛厤鏈澶uffer */ + quint32_t maxLen = HAL_STRLEN(ds) + 1 + HAL_STRLEN(ps) + 1 + HAL_STRLEN(random) + 1; + maxLen = __GET_MAX(maxLen, QUOS_BASE64_DSTDATA_LEN(QUOS_SHA256_DIGEST_LENGTH)); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "keystr maxLen:%d", maxLen); + char *bodyStr = HAL_MALLOC(maxLen); + if (NULL == bodyStr) + { + Quos_logPrintf(QUEC_SECURE, LL_ERR, "mcf bodyStr fail"); + return NULL; + } + /* 绗竴姝ワ細璁惧绛惧悕 SIGN = BASE64(SHA256(DS;PS;RAND)) */ + /* DS;PS;RAND */ + HAL_SPRINTF(bodyStr, "%s;%s;%s", ds, ps, random); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "bodyStr:[%ld]%s", HAL_STRLEN(bodyStr), bodyStr); + + /* SHA256(DS;PS;RAND) */ + SHA256_ctx_t sha256_ctx; + quint8_t sha256Data[QUOS_SHA256_DIGEST_LENGTH]; + Quos_sha256init(&sha256_ctx); + Quos_sha256update(&sha256_ctx, (const quint8_t *)bodyStr, HAL_STRLEN(bodyStr)); + Quos_sha256finish(&sha256_ctx, sha256Data); + Quos_logHexDump(QUEC_SECURE, LL_DBG, "sha256Data", sha256Data, QUOS_SHA256_DIGEST_LENGTH); + + /* BASE64(SHA256(DS;PS;RAND)) */ + Quos_base64Encrypt(sha256Data, QUOS_SHA256_DIGEST_LENGTH, (quint8_t *)bodyStr); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "Sign:[%ld]%s", HAL_STRLEN(bodyStr), bodyStr); + return bodyStr; +} +/************************************************************************** +** 鍔熻兘 @brief : 鐢熸垚璁惧璁よ瘉瀵嗛挜 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +char FUNCTION_ATTR_ROM *ql_iotSecureGenAuthKey(const char *pk, const char *ps, const char *dk, const char *random) +{ + UNUSED(pk); + quint32_t maxLen = HAL_STRLEN(dk) + 1 + __GET_MAX(HAL_STRLEN(ps), QUOS_BASE64_DSTDATA_LEN(QUOS_SHA256_DIGEST_LENGTH)) + 1 + HAL_STRLEN(random) + 1; + maxLen = __BYTE_TO_ALIGN(maxLen + 1, QUOS_AES_BLOCKLEN); + maxLen = QUOS_BASE64_DSTDATA_LEN(maxLen); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "keystr maxLen:%d", maxLen); + char *bodyStr = HAL_MALLOC(maxLen); + if (NULL == bodyStr) + { + Quos_logPrintf(QUEC_SECURE, LL_ERR, "mcf bodyStr fail"); + return NULL; + } + + /* 绗竴姝ワ細璁惧绛惧悕 SIGN = BASE64(SHA256(DK;PS;RAND)) */ + /* DK;PS;RAND */ + HAL_SPRINTF(bodyStr, "%s;%s;%s", dk, ps, random); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "bodyStr:[%ld]%s", HAL_STRLEN(bodyStr), bodyStr); + /* SHA256(DK;PS;RAND) */ + SHA256_ctx_t sha256_ctx; + quint8_t sha256Data[QUOS_SHA256_DIGEST_LENGTH]; + Quos_sha256init(&sha256_ctx); + Quos_sha256update(&sha256_ctx, (const quint8_t *)bodyStr, HAL_STRLEN(bodyStr)); + Quos_sha256finish(&sha256_ctx, sha256Data); + Quos_logHexDump(QUEC_SECURE, LL_DBG, "sha256Data", sha256Data, QUOS_SHA256_DIGEST_LENGTH); + + HAL_SPRINTF(bodyStr, "%s;%s;", dk, random); + /* BASE64(SHA256(DK;PS;RAND)) */ + Quos_base64Encrypt(sha256Data, QUOS_SHA256_DIGEST_LENGTH, (quint8_t *)bodyStr + HAL_STRLEN(bodyStr)); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "aes srcData:[%ld]%s", HAL_STRLEN(bodyStr), bodyStr); + + /* 绗簩姝ワ紝瀵笵ATA璇锋眰浣撹繘琛孉ES绠楁硶鍔犲瘑鐒跺悗杩涜BASE64缂栫爜 BASE64锛圓ES(PS,DATA璇锋眰浣)锛 */ + /* PS浣跨敤SHA256鍔犲瘑锛岀敓鎴32瀛楄妭鐨勬暟鎹,iv鍋忕Щ閲忎负sha256(ps)鐨勫乏杈16Byte */ + Quos_sha256init(&sha256_ctx); + Quos_sha256update(&sha256_ctx, (const quint8_t *)ps, HAL_STRLEN(ps)); + Quos_sha256finish(&sha256_ctx, sha256Data); + + maxLen = HAL_STRLEN(bodyStr); + AES_ctx_t aes_ctx; + Quos_logHexDump(QUEC_SECURE, LL_DBG, "IV", sha256Data, QUOS_SHA256_DIGEST_LENGTH); + Quos_aesInitCtxIv(&aes_ctx, (const char *)ps, (const char *)sha256Data); + Quos_aesPadding((quint8_t *)bodyStr, (quint8_t *)bodyStr, maxLen); + maxLen = Quos_aesCbcEncrypt(&aes_ctx, bodyStr, __BYTE_TO_ALIGN(maxLen + 1, QUOS_AES_BLOCKLEN)); + Quos_logHexDump(QUEC_SECURE, LL_DBG, "aes dstdata", bodyStr, maxLen); + + /* BASE64锛圓ES(PS,DATA璇锋眰浣)锛 */ + quint32_t offset = QUOS_BASE64_DSTDATA_LEN(maxLen) - maxLen; + HAL_MEMMOVE(bodyStr + offset, bodyStr, maxLen); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "offset:%d", offset); + Quos_base64Encrypt((quint8_t *)bodyStr + offset, maxLen, (quint8_t *)bodyStr); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "bodyStr:[%ld]%s", HAL_STRLEN(bodyStr), bodyStr); + return bodyStr; +} +/************************************************************************** +** 鍔熻兘 @brief : 鐢熸垚MQTT璁惧杩炴帴password +** 杈撳叆 @param : +** 杈撳嚭 @retval: 杩斿洖鍊奸渶瑕佸閮‵REE鍐呭瓨 +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotSecureGenMqttConnData(const char *ps, const char *ds) +{ + char random[16 + 1]; + HAL_MEMSET(random, 0, sizeof(random)); + Quos_RandomGen((quint8_t *)random, sizeof(random) - 1); + char *sign = ql_iotSecureGenConnSign(ps, ds, random); + if (sign) + { + char *passWord = HAL_MALLOC(1 + 1 + 4 + 1 + HAL_STRLEN(random) + 1 + HAL_STRLEN(sign) + 1); + if (passWord) + { + /* 鎷兼帴passWord */ + HAL_SPRINTF((char *)passWord, "1;%04d;%s;%s", Ql_iotConfigGetSessionFlag(), random, sign); + } + HAL_FREE(sign); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "passWord:%s", passWord); + return passWord; + } + return NULL; +} +/************************************************************************** +** 鍔熻兘 @brief : 鐢熸垚MQTT璁惧璁よ瘉password +** 杈撳叆 @param : +** 杈撳嚭 @retval: 杩斿洖鍊奸渶瑕佸閮‵REE鍐呭瓨 +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotSecureGenMqttAuthData(const char *pk, const char *ps, const char *dk) +{ + char random[16 + 1]; + HAL_MEMSET(random, 0, sizeof(random)); + Quos_RandomGen((quint8_t *)random, sizeof(random) - 1); + char *bodyStr = ql_iotSecureGenAuthKey(pk, ps, dk, random); + if (bodyStr) + { + char *passWord = HAL_MALLOC(1 + 1 + 4 + 1 + HAL_STRLEN(pk) + 1 + HAL_STRLEN(bodyStr) + 1); + if (passWord) + { + /* 鎷兼帴passWord */ + HAL_SPRINTF((char *)passWord, "0;%04d;%s;%s", Ql_iotConfigGetSessionFlag(), pk, bodyStr); + } + HAL_FREE(bodyStr); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "passWord:[%ld]%s",HAL_STRLEN(passWord), passWord); + return passWord; + } + return NULL; +} +/************************************************************************** +** 鍔熻兘 @brief : 鐢熸垚COAP璁惧杩炴帴password +** 杈撳叆 @param : +** 杈撳嚭 @retval: 杩斿洖鍊奸渶瑕佸閮‵REE鍐呭瓨 +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotSecureGenCoapConnEndpoint(const char *pk, const char *ps, const char *dk, const char *ds) +{ + char random[16 + 1]; + HAL_MEMSET(random, 0, sizeof(random)); + Quos_RandomGen((quint8_t *)random, sizeof(random) - 1); + char *sign = ql_iotSecureGenConnSign(ps, ds, random); + if (sign) + { + char *passWord = HAL_MALLOC(HAL_STRLEN(pk) + 1 + HAL_STRLEN(dk) + 1 + HAL_STRLEN(random) + 1 + HAL_STRLEN(sign) + 1 + HAL_STRLEN(QIOT_COAP_AUTH_VERSION) + 1); + if (passWord) + { + /* 鎷兼帴passWord */ + HAL_SPRINTF((char *)passWord, "%s;%s;%s;%s;%s", pk, dk, random, sign, QIOT_COAP_AUTH_VERSION); + } + HAL_FREE(sign); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "passWord:%s", passWord); + return passWord; + } + return NULL; +} +/************************************************************************** +** 鍔熻兘 @brief : 鐢熸垚coap璁惧璁よ瘉password +** 杈撳叆 @param : +** 杈撳嚭 @retval: 杩斿洖鍊奸渶瑕佸閮‵REE鍐呭瓨 +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotSecureGenCoapAuthPayload(const char *pk, const char *ps, const char *dk) +{ + char random[16 + 1]; + HAL_MEMSET(random, 0, sizeof(random)); + Quos_RandomGen((quint8_t *)random, sizeof(random) - 1); + char *bodyStr = ql_iotSecureGenAuthKey(pk, ps, dk, random); + if (bodyStr) + { + char *passWord = HAL_MALLOC(HAL_STRLEN(pk) + 1 + HAL_STRLEN(bodyStr) + 1 + HAL_STRLEN(QIOT_COAP_AUTH_VERSION) + 1); + if (passWord) + { + /* 鎷兼帴passWord */ + HAL_SPRINTF((char *)passWord, "%s;%s;%s", pk, bodyStr, QIOT_COAP_AUTH_VERSION); + } + HAL_FREE(bodyStr); + Quos_logPrintf(QUEC_SECURE, LL_DBG, "passWord:%s", passWord); + return passWord; + } + return NULL; +} +/************************************************************************** +** 鍔熻兘 @brief : ds瑙e瘑鎻愬彇 +** 杈撳叆 @param : +** 杈撳嚭 @retval: 杩斿洖鍊奸渶瑕佸閮‵REE鍐呭瓨 +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotSecureDecodeDs(const char *ps, const quint8_t *encryData, quint32_t len) +{ + quint8_t *ds = HAL_MALLOC(len + 1); + if (NULL == ds) + { + return NULL; + } + len = Quos_base64Decrypt(encryData, len, ds); + if (len % QUOS_AES_BLOCKLEN != 0) + { + HAL_FREE(ds); + return NULL; + } + quint8_t sha256Data[QUOS_SHA256_DIGEST_LENGTH]; + SHA256_ctx_t sha256_ctx; + Quos_sha256init(&sha256_ctx); + Quos_sha256update(&sha256_ctx, (const quint8_t *)ps, HAL_STRLEN(ps)); + Quos_sha256finish(&sha256_ctx, sha256Data); + + AES_ctx_t aes_ctx; + Quos_aesInitCtxIv(&aes_ctx, (const char *)ps, (const char *)sha256Data); + Quos_aesCbcDecrypt(&aes_ctx, ds, len); + len = Quos_aesPaddingBack(ds, len); + ds[len] = 0; + return (char *)ds; +} +char FUNCTION_ATTR_ROM *Ql_iotSecureDecodeSessionKey(const char *ds, const quint8_t *encryData, quint32_t len) +{ + if (len % QUOS_AES_BLOCKLEN != 0) + { + return NULL; + } + quint8_t *sessionKey = HAL_MALLOC(len + 1); + if (NULL == sessionKey) + { + return NULL; + } + + HAL_MEMCPY(sessionKey, encryData, len); + + quint8_t sha256Data[QUOS_SHA256_DIGEST_LENGTH]; + SHA256_ctx_t sha256_ctx; + Quos_sha256init(&sha256_ctx); + Quos_sha256update(&sha256_ctx, (const quint8_t *)ds, HAL_STRLEN(ds)); + Quos_sha256finish(&sha256_ctx, sha256Data); + AES_ctx_t aes_ctx; + Quos_aesInitCtxIv(&aes_ctx, (const char *)ds, (const char *)sha256Data); + Quos_aesCbcDecrypt(&aes_ctx, sessionKey, len); + len = Quos_aesPaddingBack(sessionKey, len); + sessionKey[len] = 0; + return (char *)sessionKey; +} +/************************************************************************** +** 鍔熻兘 @brief : 涓婅鏁版嵁鍔犲瘑 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotSecureEncryptPayload(quint8_t *data, quint32_t len, uint8_t **outData, const char *key, const char *iv) +{ + /* 涓婁笅琛屾秷鎭殑鏃跺欙紝濡傛灉寮鍚痵essionkey鐨勮瘽锛岄渶瑕佺敤sessionkey浣滀负key鍋歛es鍔犲瘑锛岄偅涔坰essionkey鐨剆ha256宸﹁竟16浣嶅氨鏄痠v鍋忕Щ閲 */ + AES_ctx_t aes_ctx; + Quos_aesInitCtxIv(&aes_ctx, key, iv); + quint32_t encryptLen = __BYTE_TO_ALIGN(len + 1, QUOS_AES_BLOCKLEN); + *outData = HAL_MALLOC(encryptLen); + if (*outData) + { + Quos_aesPadding((quint8_t *)*outData, (quint8_t *)data, len); + Quos_aesCbcEncrypt(&aes_ctx, (void *)*outData, encryptLen); + } + return encryptLen; +} + +/************************************************************************** +** 鍔熻兘 @brief : 涓嬭鏁版嵁瑙e瘑 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotSecureDecryptPayload(quint8_t *data, quint32_t len, const char *key, const char *iv) +{ + if (len % QUOS_AES_BLOCKLEN != 0) + { + return 0; + } + /* 涓婁笅琛屾秷鎭殑鏃跺欙紝濡傛灉寮鍚痵essionkey鐨勮瘽锛岄渶瑕佺敤sessionkey浣滀负key鍋歛es鍔犲瘑锛岄偅涔坰essionkey鐨剆ha256宸﹁竟16浣嶅氨鏄痠v鍋忕Щ閲 */ + AES_ctx_t aes_ctx; + Quos_aesInitCtxIv(&aes_ctx, key, iv); + Quos_aesCbcDecrypt(&aes_ctx, (void *)data, len); + len = Quos_aesPaddingBack(data, len); + return len; +} \ No newline at end of file diff --git a/cloud/common/ql_iotSecure.h b/cloud/common/ql_iotSecure.h new file mode 100644 index 0000000..caf8ac8 --- /dev/null +++ b/cloud/common/ql_iotSecure.h @@ -0,0 +1,17 @@ +#ifndef __QIOT_SECURE_H__ +#define __QIOT_SECURE_H__ +#include "Ql_iotApi.h" + +char *Ql_iotSecureVerGet(void); +char *Ql_iotSecureGenMqttConnData(const char *ps, const char *ds); +char *Ql_iotSecureGenMqttAuthData(const char *pk, const char *ps, const char *dk); +char *Ql_iotSecureGenCoapConnEndpoint(const char *pk, const char *ps, const char *dk, const char *ds); +char *Ql_iotSecureGenCoapAuthPayload(const char *pk, const char *ps, const char *dk); +char *Ql_iotSecureDecodeDs(const char *ps, const quint8_t *encryData, quint32_t len); +quint32_t Ql_iotSecureEncryptPayload(quint8_t *data, quint32_t len, uint8_t **outData, const char *key, const char *iv); +quint32_t Ql_iotSecureDecryptPayload(quint8_t *data, quint32_t len, const char *key, const char *iv); +char *ql_iotSecureGenAuthKey(const char *pk, const char *ps, const char *dk, const char *random); +char *ql_iotSecureGenConnSign(const char *ps, const char *ds, const char *random); + +char *Ql_iotSecureDecodeSessionKey(const char *ds, const quint8_t *encryData, quint32_t len); +#endif \ No newline at end of file diff --git a/cloud/common/ql_iotTtlv.c b/cloud/common/ql_iotTtlv.c new file mode 100644 index 0000000..59ce594 --- /dev/null +++ b/cloud/common/ql_iotTtlv.c @@ -0,0 +1,962 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : +** 硬件 @hardware: +** 其他 @other : +***************************************************************************/ +#include "ql_iotTtlv.h" + +#define QUEC_TTLV LL_DBG +/* 数据类型 */ +enum +{ + DP_TTLV_TYPE_BOOL_FALSE = 0, /* 布尔值false */ + DP_TTLV_TYPE_BOOL_TRUE, /* 布尔值true */ + DP_TTLV_TYPE_ENUM_NUM, /* 枚举和数值 */ + DP_TTLV_TYPE_BYTE, /* 二进制数据 */ + DP_TTLV_TYPE_STRUCT, /* 结构体 */ +}; + +typedef struct +{ + TWLLHead_T head; + quint16_t id; + QIot_dpDataType_e type; + union + { + qbool vbool; + double floatNum; + qint64_t intNum; + struct + { + quint8_t *val; + quint16_t len; + } vbytes; + void *vstructHead; + } value; +} QIot_ttlv_t; + +/************************************************************************** +** 功能 @brief : 释放TTLV节点值内存 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotTtlvFreeNodeValue(QIot_ttlv_t node) +{ + if (QIOT_DPDATA_TYPE_BYTE == node.type) + { + HAL_FREE(node.value.vbytes.val); + } + else if (QIOT_DPDATA_TYPE_STRUCT == node.type) + { + Ql_iotTtlvFree(&node.value.vstructHead); + } +} +/************************************************************************** +** 功能 @brief : 释放TTLV链表内存 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Ql_iotTtlvFree(void **ttlvHead) +{ + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((*(TWLLHead_T **)ttlvHead), temp, next) + { + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT(temp, QIot_ttlv_t, head); + Quos_twllHeadDelete((TWLLHead_T **)ttlvHead, &node->head); + ql_iotTtlvFreeNodeValue(*node); + HAL_FREE(node); + } + *ttlvHead = NULL; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotTtlvCountGet(const void *ttlvHead) +{ + return Quos_twllHeadGetNodeCount((TWLLHead_T *)ttlvHead); +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotTtlvNodeGet(const void *ttlvHead, quint16_t index, quint16_t *id, QIot_dpDataType_e *type) +{ + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)ttlvHead, temp, next) + { + if (0 == index) + { + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT(temp, QIot_ttlv_t, head); + if (id) + { + *id = node->id; + } + if (type) + { + *type = node->type; + } + return &node->head; + } + index--; + } + return NULL; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvNodeGetBool(const void *ttlvNode, qbool *value) +{ + if (NULL == ttlvNode) + { + return FALSE; + } + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT((TWLLHead_T *)ttlvNode, QIot_ttlv_t, head); + if (value && QIOT_DPDATA_TYPE_BOOL == node->type) + { + *value = node->value.vbool; + return TRUE; + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvNodeGetInt(const void *ttlvNode, qint64_t *value) +{ + if (NULL == ttlvNode) + { + return FALSE; + } + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT((TWLLHead_T *)ttlvNode, QIot_ttlv_t, head); + if (value && QIOT_DPDATA_TYPE_INT == node->type) + { + *value = (int)node->value.intNum; + return TRUE; + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvNodeGetFloat(const void *ttlvNode, double *value) +{ + if (NULL == ttlvNode) + { + return FALSE; + } + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT((TWLLHead_T *)ttlvNode, QIot_ttlv_t, head); + if (value && QIOT_DPDATA_TYPE_FLOAT == node->type) + { + *value = node->value.floatNum; + return TRUE; + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotTtlvNodeGetString(const void *ttlvNode) +{ + if (NULL == ttlvNode) + { + return NULL; + } + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT((TWLLHead_T *)ttlvNode, QIot_ttlv_t, head); + if (QIOT_DPDATA_TYPE_BYTE == node->type && node->value.vbytes.val && HAL_STRLEN(node->value.vbytes.val)) + { + return (char *)node->value.vbytes.val; + } + return NULL; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotTtlvNodeGetByte(const void *ttlvNode, quint8_t **value) +{ + if (NULL == ttlvNode) + { + return FALSE; + } + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT((TWLLHead_T *)ttlvNode, QIot_ttlv_t, head); + if (value && QIOT_DPDATA_TYPE_BYTE == node->type) + { + *value = node->value.vbytes.val; + return node->value.vbytes.len; + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotTtlvNodeGetStruct(const void *ttlvNode) +{ + if (NULL == ttlvNode) + { + return NULL; + } + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT((TWLLHead_T *)ttlvNode, QIot_ttlv_t, head); + if (QIOT_DPDATA_TYPE_STRUCT == node->type) + { + return node->value.vstructHead; + } + return NULL; +} +/************************************************************************** +** 功能 @brief : 根据ID查找TTLV节点 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static QIot_ttlv_t FUNCTION_ATTR_ROM *ql_iotTtlvValIdGet(const void *ttlvHead, quint16_t id) +{ + if (0 == id) + { + return NULL; + } + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)ttlvHead, temp, next) + { + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT(temp, QIot_ttlv_t, head); + if (id == node->id) + { + return node; + } + } + return NULL; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvIdGetBool(const void *ttlvHead, quint16_t id, qbool *value) +{ + QIot_ttlv_t *node = ql_iotTtlvValIdGet(ttlvHead, id); + return node ? Ql_iotTtlvNodeGetBool(&node->head, value) : FALSE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvIdGetInt(const void *ttlvHead, quint16_t id, qint64_t *value) +{ + QIot_ttlv_t *node = ql_iotTtlvValIdGet(ttlvHead, id); + return node ? Ql_iotTtlvNodeGetInt(&node->head, value) : FALSE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvIdGetFloat(const void *ttlvHead, quint16_t id, double *value) +{ + QIot_ttlv_t *node = ql_iotTtlvValIdGet(ttlvHead, id); + return node ? Ql_iotTtlvNodeGetFloat(&node->head, value) : FALSE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +char FUNCTION_ATTR_ROM *Ql_iotTtlvIdGetString(const void *ttlvHead, quint16_t id) +{ + QIot_ttlv_t *node = ql_iotTtlvValIdGet(ttlvHead, id); + return node ? Ql_iotTtlvNodeGetString(&node->head) : NULL; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotTtlvIdGetByte(const void *ttlvHead, quint16_t id, quint8_t **value) +{ + QIot_ttlv_t *node = ql_iotTtlvValIdGet(ttlvHead, id); + return node ? Ql_iotTtlvNodeGetByte(&node->head, value) : 0; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotTtlvIdGetStruct(const void *ttlvHead, quint16_t id) +{ + QIot_ttlv_t *node = ql_iotTtlvValIdGet(ttlvHead, id); + return node ? Ql_iotTtlvNodeGetStruct(&node->head) : NULL; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static QIot_ttlv_t FUNCTION_ATTR_ROM *ql_iotTtlvGetNew(void **ttlvHead, quint16_t id, QIot_dpDataType_e type) +{ + QIot_ttlv_t *node = NULL; + if (0 == id || (node = ql_iotTtlvValIdGet(*ttlvHead, id)) == NULL) + { + node = HAL_MALLOC(sizeof(QIot_ttlv_t)); + if (NULL == node) + { + return NULL; + } + HAL_MEMSET(node, 0, sizeof(QIot_ttlv_t)); + node->id = id; + Quos_twllHeadAdd((TWLLHead_T **)ttlvHead, &node->head); + } + ql_iotTtlvFreeNodeValue(*node); + node->type = type; + return node; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvIdAddBool(void **ttlvHead, quint16_t id, qbool value) +{ + QIot_ttlv_t *node = ql_iotTtlvGetNew(ttlvHead, id, QIOT_DPDATA_TYPE_BOOL); + if (NULL == node) + { + return FALSE; + } + node->value.vbool = value; + return TRUE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvIdAddInt(void **ttlvHead, quint16_t id, qint64_t value) +{ + QIot_ttlv_t *node = ql_iotTtlvGetNew(ttlvHead, id, QIOT_DPDATA_TYPE_INT); + if (NULL == node) + { + return FALSE; + } + node->value.intNum = value; + return TRUE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvIdAddFloat(void **ttlvHead, quint16_t id, double value) +{ + QIot_ttlv_t *node = ql_iotTtlvGetNew(ttlvHead, id, QIOT_DPDATA_TYPE_FLOAT); + if (NULL == node) + { + return FALSE; + } + node->value.floatNum = value; + return TRUE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvIdAddByte(void **ttlvHead, quint16_t id, const quint8_t *data, quint32_t len) +{ + quint8_t *temp = HAL_MALLOC(len + 1); /* 预留1byte,在字符串类型时存储结束符 */ + if (NULL == temp) + { + return FALSE; + } + QIot_ttlv_t *node = ql_iotTtlvGetNew(ttlvHead, id, QIOT_DPDATA_TYPE_BYTE); + if (NULL == node) + { + return FALSE; + } + HAL_MEMCPY(temp, data, len); + temp[len] = 0; + node->value.vbytes.val = temp; + node->value.vbytes.len = len; + return TRUE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool Ql_iotTtlvIdAddString(void **ttlvHead, quint16_t id, const char *data) +{ + return Ql_iotTtlvIdAddByte(ttlvHead, id, (const quint8_t *)data, HAL_STRLEN(data)); +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Ql_iotTtlvIdAddStruct(void **ttlvHead, quint16_t id, void *vStruct) +{ + QIot_ttlv_t *node = ql_iotTtlvGetNew(ttlvHead, id, QIOT_DPDATA_TYPE_STRUCT); + if (NULL == node) + { + return FALSE; + } + node->value.vstructHead = vStruct; + return TRUE; +} +/************************************************************************** +** 功能 @brief : 判断TTLV链表是否是数组结构体 +** 输入 @param : +** 输出 @retval: FALSE:链表错误 +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM ql_iotTtlvIsStructArray(const void *ttlvHead, qbool *isArray) +{ + quint32_t arraySize = 0; + if (isArray) + { + *isArray = FALSE; + } + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)ttlvHead, temp, next) + { + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT(temp, QIot_ttlv_t, head); + if (0 == node->id) + { + if (isArray) + { + *isArray = TRUE; + } + arraySize++; + } + else if (arraySize) + { + return FALSE; + } + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 提取第一个TTLV +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static quint32_t FUNCTION_ATTR_ROM ql_iotTtlvUnformatFirst(const quint8_t *buffer, quint32_t len, QIot_ttlv_t *node) +{ + if (len < 2) + { + return 0; + } + HAL_MEMSET(node, 0, sizeof(QIot_ttlv_t)); + node->id = _ARRAY01_U16(&buffer[0]); + quint8_t ttlvtype = node->id & 0x07; + node->id >>= 3; + switch (ttlvtype) + { + case DP_TTLV_TYPE_BOOL_FALSE: + case DP_TTLV_TYPE_BOOL_TRUE: + { + node->type = QIOT_DPDATA_TYPE_BOOL; + node->value.vbool = DP_TTLV_TYPE_BOOL_TRUE == ttlvtype ? TRUE : FALSE; + return 2; + } + case DP_TTLV_TYPE_ENUM_NUM: + { + if (len < 3) + { + return 0; + } + qbool negative = (buffer[2] >> 7) ? TRUE : FALSE; + quint8_t amp = (buffer[2] >> 3) & 0x0F; + quint8_t tmpLen = ((buffer[2]) & 0x07) + 1; + quint8_t offset; + if (len < (quint32_t)(3 + tmpLen)) + { + return 0; + } + qint64_t value = 0; + for (offset = 0; offset < tmpLen; offset++) + { + value <<= 8; + value |= buffer[offset + 3]; + } + if (negative) + { + value = 0 - value; + } + if (amp) + { + node->type = QIOT_DPDATA_TYPE_FLOAT; + node->value.floatNum = (double)value; + while (amp--) + { + node->value.floatNum /= 10.0; + } + } + else + { + node->type = QIOT_DPDATA_TYPE_INT; + node->value.intNum = value; + } + return 3 + tmpLen; + } + case DP_TTLV_TYPE_BYTE: + { + if (len < 4) + { + return 0; + } + node->type = QIOT_DPDATA_TYPE_BYTE; + node->value.vbytes.len = _ARRAY01_U16(&buffer[2]); + if (len < (quint32_t)(4 + node->value.vbytes.len)) + { + return 0; + } + node->value.vbytes.val = HAL_MALLOC(node->value.vbytes.len + 1); + if (NULL == node->value.vbytes.val) + { + return 0; + } + HAL_MEMCPY(node->value.vbytes.val, &buffer[4], node->value.vbytes.len); + node->value.vbytes.val[node->value.vbytes.len] = 0; + return 4 + node->value.vbytes.len; + } + case DP_TTLV_TYPE_STRUCT: + { + if (len < 4) + { + return 0; + } + quint16_t count = _ARRAY01_U16(&buffer[2]); + quint32_t offset = 4; + node->type = QIOT_DPDATA_TYPE_STRUCT; + while (count--) + { + QIot_ttlv_t *subNode = HAL_MALLOC(sizeof(QIot_ttlv_t)); + if (NULL == subNode) + { + return 0; + } + quint32_t nodeLen = ql_iotTtlvUnformatFirst(&buffer[offset], len - offset, subNode); + if (nodeLen) + { + Quos_twllHeadAdd((TWLLHead_T **)&node->value.vstructHead, &subNode->head); + } + else + { + HAL_FREE(subNode); + Ql_iotTtlvFree(&node->value.vstructHead); + return 0; + } + offset += nodeLen; + } + if (FALSE == ql_iotTtlvIsStructArray(node->value.vstructHead, NULL)) + { + Ql_iotTtlvFree(&node->value.vstructHead); + return 0; + } + return offset; + } + default: + return 0; + } +} +/************************************************************************** +** 功能 @brief : 解压TTLV数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotTtlvUnformat(const quint8_t *buffer, quint32_t len) +{ + void *ttlvHead = NULL; + quint32_t offset; + for (offset = 0; offset < len;) + { + QIot_ttlv_t *node = HAL_MALLOC(sizeof(QIot_ttlv_t)); + if (NULL == node) + { + return NULL; + } + quint32_t nodeLen = ql_iotTtlvUnformatFirst(&buffer[offset], len - offset, node); + if (nodeLen) + { + offset += nodeLen; + Quos_twllHeadAdd((TWLLHead_T **)&ttlvHead, &node->head); + } + else + { + HAL_FREE(node); + Ql_iotTtlvFree((void **)&ttlvHead); + return NULL; + } + } + if (FALSE == ql_iotTtlvIsStructArray(ttlvHead, NULL)) + { + Ql_iotTtlvFree(&ttlvHead); + } + return (void *)ttlvHead; +} +/************************************************************************** +** 功能 @brief : 计算构建TTLV数据包数据长度 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotTtlvFormatLen(const void *ttlvHead) +{ + quint32_t pkgLen = 0; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)ttlvHead, temp, next) + { + quint8_t tmpBuf[8]; + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT(temp, QIot_ttlv_t, head); + pkgLen += 2; + switch (node->type) + { + case QIOT_DPDATA_TYPE_BOOL: /*do none*/ + break; + case QIOT_DPDATA_TYPE_INT: + { + pkgLen += 1; + qint64_t value = node->value.intNum; + if (value < 0) + { + value = 0 - value; + } + pkgLen += Quos_intPushArray((quint64_t)value, tmpBuf); + break; + } + case QIOT_DPDATA_TYPE_FLOAT: + { + pkgLen += 1; + double value = node->value.floatNum; + if (value < 0) + { + value = 0 - value; + } + quint8_t amp = 0; + while (0 != (value - (quint64_t)value) && amp < 0x0F) + { + value *= 10.0; + amp++; + } + pkgLen += Quos_intPushArray((quint64_t)value, tmpBuf); + break; + } + case QIOT_DPDATA_TYPE_BYTE: + { + pkgLen += 2 + node->value.vbytes.len; + break; + } + case QIOT_DPDATA_TYPE_STRUCT: + { + pkgLen += 2 + Ql_iotTtlvFormatLen(node->value.vstructHead); + break; + } + } + } + return pkgLen; +} +/************************************************************************** +** 功能 @brief : 构建TTLV数据包数 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Ql_iotTtlvFormat(const void *ttlvHead, quint8_t *retBuf) +{ + quint32_t pkgLen = 0; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)ttlvHead, temp, next) + { + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT(temp, QIot_ttlv_t, head); + _U16_ARRAY01(node->id << 3, &retBuf[pkgLen]); + switch (node->type) + { + case QIOT_DPDATA_TYPE_BOOL: + { + retBuf[pkgLen + 1] |= node->value.vbool ? DP_TTLV_TYPE_BOOL_TRUE : DP_TTLV_TYPE_BOOL_FALSE; + pkgLen += 2; + break; + } + case QIOT_DPDATA_TYPE_INT: + { + qint64_t value = node->value.intNum; + if (value < 0) + { + retBuf[pkgLen + 2] = 1 << 7; + value = 0 - value; + } + else + { + retBuf[pkgLen + 2] = 0; + } + quint8_t tmpLen = Quos_intPushArray((quint64_t)value, &retBuf[pkgLen + 3]); + retBuf[pkgLen + 1] |= DP_TTLV_TYPE_ENUM_NUM; + retBuf[pkgLen + 2] |= (tmpLen - 1); + pkgLen += 3 + tmpLen; + break; + } + case QIOT_DPDATA_TYPE_FLOAT: + { + double value = node->value.floatNum; + if (value < 0) + { + retBuf[pkgLen + 2] = 1 << 7; + value = 0 - value; + } + else + { + retBuf[pkgLen + 2] = 0; + } + + quint8_t amp = 0; + while (0 != (value - (quint64_t)value) && amp < 0x0F) + { + value *= 10.0; + amp++; + } + quint8_t tmpLen = Quos_intPushArray((quint64_t)value, &retBuf[pkgLen + 3]); + retBuf[pkgLen + 1] |= DP_TTLV_TYPE_ENUM_NUM; + retBuf[pkgLen + 2] |= (amp << 3) | (tmpLen - 1); + pkgLen += 3 + tmpLen; + break; + } + case QIOT_DPDATA_TYPE_BYTE: + { + retBuf[pkgLen + 1] |= DP_TTLV_TYPE_BYTE; + _U16_ARRAY01(node->value.vbytes.len, &retBuf[pkgLen + 2]); + HAL_MEMCPY(&retBuf[pkgLen + 4], node->value.vbytes.val, node->value.vbytes.len); + pkgLen += 4 + node->value.vbytes.len; + break; + } + case QIOT_DPDATA_TYPE_STRUCT: + { + quint16_t count = Quos_twllHeadGetNodeCount(node->value.vstructHead); + retBuf[pkgLen + 1] |= DP_TTLV_TYPE_STRUCT; + pkgLen += 2; + _U16_ARRAY01(count, &retBuf[pkgLen]); + pkgLen += 2; + pkgLen += Ql_iotTtlvFormat(node->value.vstructHead, &retBuf[pkgLen]); + break; + } + } + } + return pkgLen; +} + +#define QL_IOTJSON_BYTE_MARK "\\x" +/************************************************************************** +** 功能 @brief : JSON转TTLV +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM ql_iotJson2Ttlv(const cJSON *json, void **ttlvHead) +{ + qbool ret = FALSE; + quint32_t id = 0; + if (json->string && FALSE == Quos_strIsUInt(json->string, HAL_STRLEN(json->string), &id)) + { + return; + } + Quos_logPrintf(QUEC_TTLV,LL_DBG,"string[%s] id[%d] type[%d]",json->string,id,json->type); + switch (json->type) + { + case QUOS_cJSON_False: + ret = Ql_iotTtlvIdAddBool(ttlvHead, id, FALSE); + break; + case QUOS_cJSON_True: + ret = Ql_iotTtlvIdAddBool(ttlvHead, id, TRUE); + break; + case QUOS_cJSON_Number: + ret = Ql_iotTtlvIdAddFloat(ttlvHead, id, json->valuedouble); + break; + case QUOS_cJSON_String: + case QUOS_cJSON_Raw: + { + if (0 == HAL_STRNCMP(json->valuestring, QL_IOTJSON_BYTE_MARK, HAL_STRLEN(QL_IOTJSON_BYTE_MARK))) + { + quint16_t vaildLen = HAL_STRLEN(json->valuestring + 2); + quint8_t *tempBuf = HAL_MALLOC(vaildLen / 2); + if (tempBuf) + { + if (vaildLen / 2 == Quos_str2Hex(json->valuestring + 2, tempBuf)) + { + ret = Ql_iotTtlvIdAddByte(ttlvHead, id, tempBuf, vaildLen / 2); + } + HAL_FREE(tempBuf); + } + } + else if (0 == HAL_STRNCMP(json->valuestring, "\\\\", HAL_STRLEN("\\\\"))) + { + ret = Ql_iotTtlvIdAddString(ttlvHead, id, json->valuestring + 1); + } + else + { + ret = Ql_iotTtlvIdAddString(ttlvHead, id, json->valuestring); + } + } + + break; + case QUOS_cJSON_Array: + case QUOS_cJSON_Object: + { + void *ttlvChild = NULL; + cJSON *child = json->child; + while (child) + { + ql_iotJson2Ttlv(child, &ttlvChild); + if(NULL == ttlvChild) + { + break; + } + child = child->next; + } + if (ttlvChild && ql_iotTtlvIsStructArray(ttlvChild, NULL)) + { + ret = Ql_iotTtlvIdAddStruct(ttlvHead, id, ttlvChild); + } + break; + } + default: + break; + } + if(FALSE == ret) + { + Ql_iotTtlvFree(ttlvHead); + } +} +/************************************************************************** +** 功能 @brief : JSON字符串转TTLV +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Ql_iotJson2Ttlv(const cJSON *json) +{ + if(NULL == json) + { + return NULL; + } + void *ttlvHead = NULL; + cJSON *child = json->child; + while (child) + { + ql_iotJson2Ttlv(child, &ttlvHead); + if(NULL == ttlvHead) + { + break; + } + child = child->next; + } + return ttlvHead; +} +/************************************************************************** +** 功能 @brief : TTLV转JSON字符串 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +cJSON FUNCTION_ATTR_ROM *Ql_iotTtlv2Json(const void *ttlvHead) +{ + qbool isArray; + cJSON *jsonRoot = NULL; + if (NULL == ttlvHead || FALSE == ql_iotTtlvIsStructArray(ttlvHead, &isArray)) + { + return NULL; + } + Quos_logPrintf(QUEC_TTLV,LL_DBG,"isArray:%s",_BOOL2STR(isArray)); + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)ttlvHead, temp, next) + { + QIot_ttlv_t *node = __GET_STRUCT_BY_ELEMENT(temp, QIot_ttlv_t, head); + char id[10]; + HAL_SPRINTF(id, "%d", node->id); + cJSON *jsonNode = NULL; + switch (node->type) + { + case QIOT_DPDATA_TYPE_BOOL: + jsonNode = cJSON_CreateBool(node->value.vbool); + break; + case QIOT_DPDATA_TYPE_INT: + jsonNode = cJSON_CreateNumber(node->value.intNum); + break; + case QIOT_DPDATA_TYPE_FLOAT: + jsonNode = cJSON_CreateNumber(node->value.floatNum); + break; + case QIOT_DPDATA_TYPE_BYTE: + { + quint16_t i; + for (i = 0; i < node->value.vbytes.len; i++) + { + if (node->value.vbytes.val[i] < 0x20 || node->value.vbytes.val[i] > 0x7E) + { + char *tempBuf = HAL_MALLOC(node->value.vbytes.len * 2 + HAL_STRLEN(QL_IOTJSON_BYTE_MARK) + 1); + if (tempBuf) + { + HAL_SPRINTF(tempBuf, "%s", QL_IOTJSON_BYTE_MARK); + Quos_hex2Str(node->value.vbytes.val, node->value.vbytes.len, tempBuf + HAL_STRLEN(tempBuf), TRUE); + jsonNode = cJSON_CreateString(tempBuf); + HAL_FREE(tempBuf); + break; + } + } + } + if (i >= node->value.vbytes.len) + { + if(0 == HAL_STRNCMP(node->value.vbytes.val, "\\", HAL_STRLEN("\\"))) + { + char *tempBuf = HAL_MALLOC(node->value.vbytes.len+1+1); + if (tempBuf) + { + HAL_SPRINTF(tempBuf, "\\%s", node->value.vbytes.val); + jsonNode = cJSON_CreateString(tempBuf); + HAL_FREE(tempBuf); + break; + } + } + else + { + jsonNode = cJSON_CreateString((const char*)node->value.vbytes.val); + } + } + } + break; + case QIOT_DPDATA_TYPE_STRUCT: + jsonNode = Ql_iotTtlv2Json(node->value.vstructHead); + break; + default: + break; + } + if (NULL == jsonNode) + { + cJSON_Delete(jsonRoot); + jsonRoot = NULL; + break; + } + else if (isArray) + { + if (NULL == jsonRoot) + { + jsonRoot = cJSON_CreateArray(); + } + cJSON_AddItemToArray(jsonRoot, jsonNode); + } + else + { + if (NULL == jsonRoot) + { + jsonRoot = cJSON_CreateObject(); + } + cJSON_AddItemToObject(jsonRoot, id, jsonNode); + } + } + return jsonRoot; +} diff --git a/cloud/common/ql_iotTtlv.h b/cloud/common/ql_iotTtlv.h new file mode 100644 index 0000000..d02350c --- /dev/null +++ b/cloud/common/ql_iotTtlv.h @@ -0,0 +1,9 @@ +#ifndef __QL_IOTTTLV_H__ +#define __QL_IOTTTLV_H__ +#include "Ql_iotApi.h" + +void *Ql_iotTtlvUnformat(const quint8_t *buffer, quint32_t len); +quint32_t Ql_iotTtlvFormatLen(const void *ttlvHead); +quint32_t Ql_iotTtlvFormat(const void *ttlvHead, quint8_t *retBuf); + +#endif \ No newline at end of file diff --git a/cloud/python/modquecIot.c b/cloud/python/modquecIot.c new file mode 100644 index 0000000..55ae3de --- /dev/null +++ b/cloud/python/modquecIot.c @@ -0,0 +1,1849 @@ +/************************************************************************* +** @author : +** @version : +** @date : +** @brief : 娉ㄦ剰鏂囦欢浣跨敤 UTF-8 缂栫爜 +** @hardware : +** @other : +***************************************************************************/ +#include "Ql_iotApi.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objtuple.h" +#include "py/objstr.h" +#include "py/runtime.h" +#include "py/mperrno.h" +#include "py/mpz.h" +#include "py/objint.h" +#include "mpconfigport.h" +//#include "helios_os.h" + +extern quint32_t Quos_stringSplit(char *src, quint32_t srcLen, char **words, quint32_t maxSize, const char *delim, qbool keepEmptyParts); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdBusPassTransSend(mp_obj_t mp_mode, mp_obj_t mp_data) +{ + int mode = mp_obj_get_int(mp_mode); + mp_buffer_info_t data = {0}; + mp_get_buffer_raise(mp_data, &data, MP_BUFFER_READ); + if (FALSE == Ql_iotCmdBusPassTransSend(mode, (quint8_t *)data.buf, data.len)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotCmdBusPassTransSend_obj, qpy_Ql_iotCmdBusPassTransSend); + +//mpz -> 64bit integer for 32bit builds +static qint64_t mpz_to_64bit_int(const mp_obj_int_t *arg, bool is_signed) +{ + //see mpz_as_int_checked + const qint64_t maxCalcThreshold = is_signed ? 140737488355327 : 281474976710655; + + const mpz_t *i = &arg->mpz; + if (!is_signed && i->neg) + { + mp_raise_TypeError(MP_ERROR_TEXT("Source integer must be unsigned")); + } + + short unsigned int *d = i->dig + i->len; + qint64_t val = 0; + + while (d-- > i->dig) + { + if (val > maxCalcThreshold) + { + mp_raise_ValueError(MP_ERROR_TEXT("Value too large for 64bit integer")); + } + val = (val << MPZ_DIG_SIZE) | *d; + } + + if (i->neg) + { + val = -val; + } + return val; +} + +static qint64_t mp_obj_get_lint(mp_const_obj_t arg) +{ + if (arg == mp_const_false) + { + return 0u; + } + else if (arg == mp_const_true) + { + return 1u; + } + else if (MP_OBJ_IS_SMALL_INT(arg)) + { + return MP_OBJ_SMALL_INT_VALUE(arg); + } + else if (MP_OBJ_IS_TYPE(arg, &mp_type_int)) + { + return mpz_to_64bit_int((mp_obj_int_t *)arg, 1); + } + else + { + mp_raise_TypeError(MP_ERROR_TEXT("unsigned integer")); + } + return 0u; +} + +static qbool phy_dict_handle(mp_obj_t mp_data, void **ttlvHead); +static qbool phy_list_handle(mp_obj_t mp_data, void **ttlvHead) +{ + size_t len = 0; + mp_obj_t *items = NULL; + mp_obj_list_get(mp_data, &len, &items); + if (len == 0) + { + return FALSE; + } + for (quint32_t i = 0; i < len; i++) + { + mp_obj_t id = mp_obj_new_int(0); + mp_obj_t value = items[i]; + if (mp_obj_is_bool(value)) + { + Ql_iotTtlvIdAddBool(ttlvHead, mp_obj_get_int(id), value == mp_const_true ? TRUE : FALSE); + } + else if (mp_obj_is_int(value)) + { + Ql_iotTtlvIdAddInt(ttlvHead, mp_obj_get_int(id), mp_obj_get_lint(value)); + } + else if (mp_obj_is_float(value)) + { + Ql_iotTtlvIdAddFloat(ttlvHead, mp_obj_get_int(id), mp_obj_get_float(value)); + } + else if (mp_obj_is_str(value) || mp_obj_is_type(value, &mp_type_bytearray) || mp_obj_is_type(value, &mp_type_bytes)) + { + mp_buffer_info_t data = {0}; + mp_get_buffer_raise(value, &data, MP_BUFFER_READ); + Ql_iotTtlvIdAddByte(ttlvHead, mp_obj_get_int(id), data.buf, data.len); + } + else if (mp_obj_is_type(value, &mp_type_dict)) + { + void *ttlvStructHead = NULL; + phy_dict_handle(value, &ttlvStructHead); + Ql_iotTtlvIdAddStruct(ttlvHead, mp_obj_get_int(id), ttlvStructHead); + } + else if (mp_obj_is_type(value, &mp_type_list) || mp_obj_is_type(value, &mp_type_tuple)) + { + void *ttlvStructHead = NULL; + phy_list_handle(value, &ttlvStructHead); + Ql_iotTtlvIdAddStruct(ttlvHead, mp_obj_get_int(id), ttlvStructHead); + } + else + { + return FALSE; + } + } + return TRUE; +} + +static qbool phy_dict_handle(mp_obj_t mp_data, void **ttlvHead) +{ + quint32_t index = 0; + mp_map_t *map = NULL; + mp_obj_t id = 0; + mp_obj_t value = 0; + if (!mp_obj_is_type(mp_data, &mp_type_dict)) + { + return FALSE; + } + + map = mp_obj_dict_get_map(mp_data); + if (map == NULL) + { + return FALSE; + } + + for (; index < map->alloc; index++) + { + id = map->table[index].key; + value = map->table[index].value; + + if (id == MP_OBJ_NULL) + { + if (index + (quint32_t)1 < map->alloc) + { + continue; + } + else if (index > 0) + { + return TRUE; + } + else + { + return FALSE; + } + } + if (mp_obj_is_bool(value)) + { + Ql_iotTtlvIdAddBool(ttlvHead, mp_obj_get_int(id), value == mp_const_true ? TRUE : FALSE); + } + else if (mp_obj_is_int(value)) + { + Ql_iotTtlvIdAddInt(ttlvHead, mp_obj_get_int(id), mp_obj_get_lint(value)); + } + else if (mp_obj_is_float(value)) + { + Ql_iotTtlvIdAddFloat(ttlvHead, mp_obj_get_int(id), mp_obj_get_float(value)); + } + else if (mp_obj_is_str(value) || mp_obj_is_type(value, &mp_type_bytearray) || mp_obj_is_type(value, &mp_type_bytes)) + { + mp_buffer_info_t data = {0}; + mp_get_buffer_raise(value, &data, MP_BUFFER_READ); + Ql_iotTtlvIdAddByte(ttlvHead, mp_obj_get_int(id), data.buf, data.len); + } + else if (mp_obj_is_type(value, &mp_type_dict)) + { + void *ttlvStructHead = NULL; + phy_dict_handle(value, &ttlvStructHead); + Ql_iotTtlvIdAddStruct(ttlvHead, mp_obj_get_int(id), ttlvStructHead); + } + else if (mp_obj_is_type(value, &mp_type_list) || mp_obj_is_type(value, &mp_type_tuple)) + { + void *ttlvStructHead = NULL; + phy_list_handle(value, &ttlvStructHead); + Ql_iotTtlvIdAddStruct(ttlvHead, mp_obj_get_int(id), ttlvStructHead); + } + else + { + return FALSE; + } + } + return TRUE; +} + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdBusPhymodelReport(mp_obj_t mp_mode, mp_obj_t mp_data) +{ + int mode = mp_obj_get_int(mp_mode); + mp_obj_t ret = mp_const_true; + void *ttlvHead = NULL; + if (phy_dict_handle(mp_data, &ttlvHead)) + { + if (FALSE == Ql_iotCmdBusPhymodelReport(mode, ttlvHead)) + { + ret = mp_const_false; + } + } + else + { + ret = mp_const_false; + } + Ql_iotTtlvFree(&ttlvHead); + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotCmdBusPhymodelReport_obj, qpy_Ql_iotCmdBusPhymodelReport); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdBusPhymodelAck(mp_obj_t mp_mode, mp_obj_t mp_pkgid, mp_obj_t mp_data) +{ + int mode = mp_obj_get_int(mp_mode); + int pkgid = mp_obj_get_int(mp_pkgid); + mp_obj_t ret = mp_const_true; + void *ttlvHead = NULL; + if (phy_dict_handle(mp_data, &ttlvHead)) + { + if (FALSE == Ql_iotCmdBusPhymodelAck(mode, (quint16_t)pkgid, ttlvHead)) + { + ret = mp_const_false; + } + } + else + { + ret = mp_const_false; + } + Ql_iotTtlvFree(&ttlvHead); + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(qpy_Ql_iotCmdBusPhymodelAck_obj, qpy_Ql_iotCmdBusPhymodelAck); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdOtaAction(mp_obj_t mp_action) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + int action = mp_obj_get_int(mp_action); + if (FALSE == Ql_iotCmdOtaAction(action)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdOtaAction_obj, qpy_Ql_iotCmdOtaAction); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdOtaRequest(mp_obj_t mp_mode) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + int mode = mp_obj_get_int(mp_mode); + if (FALSE == Ql_iotCmdOtaRequest(mode)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdOtaRequest_obj, qpy_Ql_iotCmdOtaRequest); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdOtaMcuFWDataRead(mp_obj_t mp_addr, mp_obj_t mp_len) +{ + quint32_t addr = mp_obj_get_int(mp_addr); + quint32_t len = mp_obj_get_int(mp_len); + // 2021-06-18 闄愬埗SOTA璇诲彇闀垮害銆傚洜涓鸿鍙栭暱搴﹁繃闀匡紝浼氬鑷存ā缁刣ump銆傝屼笖璇诲彇瓒呰繃鍥轰欢澶у皬鐨勯暱搴︿篃浼氬厛琛岀敵璇峰唴瀛橈紝瀹规槗dump銆 + if (Ql_iotGetWorkState() == 0 || len == 0 /* || len > Helios_GetAvailableMemorySize() / 3*/) + { + return mp_const_none; + } + void *data = NULL; + quint32_t ret = 0; + + data = malloc(len); + if (data == NULL) + { + mp_raise_OSError(MP_ENOMEM); + return mp_const_none; + } + if ((ret = Ql_iotCmdOtaMcuFWDataRead(addr, (quint8_t *)data, len)) > 0) + { + mp_obj_t retData = mp_obj_new_bytes((const unsigned char *)data, ret); + free(data); + return retData; + } + free(data); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotCmdOtaMcuFWDataRead_obj, qpy_Ql_iotCmdOtaMcuFWDataRead); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdSysGetDevStatus(mp_obj_t id_list) +{ + size_t count = 0; + mp_obj_t *items = NULL; + mp_obj_get_array(id_list, &count, &items); + count = count < QIOT_DPID_STATUS_MAX ? count : QIOT_DPID_STATUS_MAX; + mp_obj_t struct_dict = mp_obj_new_dict(count); + quint16_t ids[QIOT_DPID_STATUS_MAX]; + size_t i; + for (i = 0; i < count; i++) + { + ids[i] = mp_obj_get_int(items[i]); + } + void *statusTtlv = Ql_iotSysGetDevStatus(ids, count); + + i = 0; + void *node; + quint16_t id; + QIot_dpDataType_e type; + while ((node = Ql_iotTtlvNodeGet(statusTtlv, i++, &id, &type))) + { + switch (type) + { + case QIOT_DPDATA_TYPE_BOOL: + { + qbool value; + if (Ql_iotTtlvNodeGetBool(node, &value)) + { + mp_obj_dict_store(struct_dict, mp_obj_new_int(id), mp_obj_new_bool(value)); + } + break; + } + case QIOT_DPDATA_TYPE_INT: + { + qint64_t value; + if (Ql_iotTtlvNodeGetInt(node, &value)) + { + mp_obj_dict_store(struct_dict, mp_obj_new_int(id), mp_obj_new_int(value)); + } + break; + } + case QIOT_DPDATA_TYPE_FLOAT: + { + double value; + if (Ql_iotTtlvNodeGetFloat(node, &value)) + { + mp_obj_dict_store(struct_dict, mp_obj_new_int(id), mp_obj_new_float(value)); + } + break; + } + case QIOT_DPDATA_TYPE_BYTE: + { + quint8_t *value; + quint32_t len = Ql_iotTtlvNodeGetByte(node, &value); + if (len) + { + mp_obj_dict_store(struct_dict, mp_obj_new_int(id), mp_obj_new_str((const char *)value, len)); + } + break; + } + case QIOT_DPDATA_TYPE_STRUCT: + break; + } + } + + Ql_iotTtlvFree(&statusTtlv); + return struct_dict; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdSysGetDevStatus_obj, qpy_Ql_iotCmdSysGetDevStatus); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdSysGetDevInfo(mp_obj_t id_list) +{ + size_t count = 0; + mp_obj_t *items = NULL; + mp_obj_get_array(id_list, &count, &items); + count = count < QIOT_DPID_INFO_MAX ? count : QIOT_DPID_INFO_MAX; + mp_obj_t struct_dict = mp_obj_new_dict(count); + + quint16_t ids[QIOT_DPID_INFO_MAX]; + size_t i; + for (i = 0; i < count; i++) + { + ids[i] = mp_obj_get_int(items[i]); + } + void *statusTtlv = Ql_iotSysGetDevInfo(ids, count); + + i = 0; + void *node; + quint16_t id; + QIot_dpDataType_e type; + while ((node = Ql_iotTtlvNodeGet(statusTtlv, i++, &id, &type))) + { + switch (type) + { + case QIOT_DPDATA_TYPE_BOOL: + { + qbool value; + if (Ql_iotTtlvNodeGetBool(node, &value)) + { + mp_obj_dict_store(struct_dict, mp_obj_new_int(id), mp_obj_new_bool(value)); + } + break; + } + case QIOT_DPDATA_TYPE_INT: + { + qint64_t value; + if (Ql_iotTtlvNodeGetInt(node, &value)) + { + mp_obj_dict_store(struct_dict, mp_obj_new_int(id), mp_obj_new_int(value)); + } + break; + } + case QIOT_DPDATA_TYPE_FLOAT: + { + double value; + if (Ql_iotTtlvNodeGetFloat(node, &value)) + { + mp_obj_dict_store(struct_dict, mp_obj_new_int(id), mp_obj_new_float(value)); + } + break; + } + case QIOT_DPDATA_TYPE_BYTE: + { + quint8_t *value; + quint32_t len = Ql_iotTtlvNodeGetByte(node, &value); + if (len) + { + mp_obj_dict_store(struct_dict, mp_obj_new_int(id), mp_obj_new_str((const char *)value, len)); + } + break; + } + case QIOT_DPDATA_TYPE_STRUCT: + break; + } + } + + Ql_iotTtlvFree(&statusTtlv); + return struct_dict; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdSysGetDevInfo_obj, qpy_Ql_iotCmdSysGetDevInfo); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdSysStatusReport(mp_obj_t id_list) +{ + qbool ret = FALSE; + size_t count = 0; + mp_obj_t *items = NULL; + quint16_t *ids = NULL; + mp_obj_get_array(id_list, &count, &items); + if (count && (ids = malloc(count * sizeof(sizeof(quint16_t)))) != NULL) + { + size_t i; + for (i = 0; i < count; i++) + { + ids[i] = mp_obj_get_int(items[i]); + } + ret = Ql_iotCmdSysStatusReport(ids, count); + free(ids); + } + return mp_obj_new_bool(ret ? TRUE : FALSE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdSysStatusReport_obj, qpy_Ql_iotCmdSysStatusReport); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdSysDevInfoReport(mp_obj_t id_list) +{ + qbool ret = FALSE; + size_t count = 0; + mp_obj_t *items = NULL; + quint16_t *ids = NULL; + mp_obj_get_array(id_list, &count, &items); + if (count && (ids = malloc(count * sizeof(sizeof(quint16_t)))) != NULL) + { + size_t i; + for (i = 0; i < count; i++) + { + ids[i] = mp_obj_get_int(items[i]); + } + ret = Ql_iotCmdSysDevInfoReport(ids, count); + free(ids); + } + return mp_obj_new_bool(ret ? TRUE : FALSE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdSysDevInfoReport_obj, qpy_Ql_iotCmdSysDevInfoReport); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotInit(void) +{ + if (Ql_iotGetWorkState() == 0) + { + if (TRUE == Ql_iotInit()) + { + return mp_const_true; + } + return mp_const_false; + } + else + { + Ql_iotConfigSetConnmode(0); + } + return mp_const_true; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotInit_obj, qpy_Ql_iotInit); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetConnmode(mp_obj_t mode) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + if (FALSE == Ql_iotConfigSetConnmode(mp_obj_get_int(mode))) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotConfigSetConnmode_obj, qpy_Ql_iotConfigSetConnmode); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetConnmode(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + QIot_connMode_e mode = Ql_iotConfigGetConnmode(); + return mp_obj_new_int(mode); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetConnmode_obj, qpy_Ql_iotConfigGetConnmode); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetPdpContextId(mp_obj_t context_id) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_false; + } + if (FALSE == Ql_iotConfigSetPdpContextId((quint8_t)mp_obj_get_int(context_id))) + { + return mp_const_false; + } + return mp_const_true; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotConfigSetPdpContextId_obj, qpy_Ql_iotConfigSetPdpContextId); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetPdpContextId(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + quint8_t contextid = Ql_iotConfigGetPdpContextId(); + return mp_obj_new_int(contextid); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetPdpContextId_obj, qpy_Ql_iotConfigGetPdpContextId); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetServer(mp_obj_t type, mp_obj_t server) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t serverinfo = {0}; + mp_get_buffer_raise(server, &serverinfo, MP_BUFFER_READ); + if (FALSE == Ql_iotConfigSetServer(mp_obj_get_int(type), (const char *)serverinfo.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotConfigSetServer_obj, qpy_Ql_iotConfigSetServer); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetServer(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + int type = 0; + char *server = NULL; + + Ql_iotConfigGetServer((QIot_protocolType_t *)&type, &server); + mp_obj_t url_info[2] = + { + mp_obj_new_int(type), + mp_obj_new_str(server, strlen(server)), + }; + return mp_obj_new_tuple(2, url_info); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetServer_obj, qpy_Ql_iotConfigGetServer); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetProductinfo(mp_obj_t product_key, mp_obj_t product_secret) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t keyinfo = {0}; + mp_buffer_info_t secretinfo = {0}; + mp_get_buffer_raise(product_key, &keyinfo, MP_BUFFER_READ); + mp_get_buffer_raise(product_secret, &secretinfo, MP_BUFFER_READ); + if (FALSE == Ql_iotConfigSetProductinfo((const char *)keyinfo.buf, (const char *)secretinfo.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotConfigSetProductinfo_obj, qpy_Ql_iotConfigSetProductinfo); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetProductinfo(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + char *product_key = NULL; + char *product_secret = NULL; + char *product_ver = NULL; + + Ql_iotConfigGetProductinfo(&product_key, &product_secret, &product_ver); + mp_obj_t product_info[3] = + { + mp_obj_new_str(product_key, strlen(product_key)), + mp_obj_new_str(product_secret, strlen(product_secret)), + mp_obj_new_str(product_ver, strlen(product_ver))}; + return mp_obj_new_tuple(3, product_info); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetProductinfo_obj, qpy_Ql_iotConfigGetProductinfo); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetLifetime(mp_obj_t life_time) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + int lifetime = mp_obj_get_int(life_time); + if (FALSE == Ql_iotConfigSetLifetime(lifetime)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotConfigSetLifetime_obj, qpy_Ql_iotConfigSetLifetime); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetLifetime(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + quint32_t lifetime = Ql_iotConfigGetLifetime(); + return mp_obj_new_int_from_uint(lifetime); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetLifetime_obj, qpy_Ql_iotConfigGetLifetime); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetSessionFlag(mp_obj_t sessionFlag) +{ + if (Ql_iotGetWorkState() == 0 || FALSE == mp_obj_is_bool(sessionFlag)) + { + return mp_obj_new_bool(FALSE); + } + if (FALSE == Ql_iotConfigSetSessionFlag(mp_const_true == sessionFlag ? TRUE : FALSE)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotConfigSetSessionFlag_obj, qpy_Ql_iotConfigSetSessionFlag); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetSessionFlag(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + return mp_obj_new_bool(Ql_iotConfigGetSessionFlag()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetSessionFlag_obj, qpy_Ql_iotConfigGetSessionFlag); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetSoftVersion(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + char *ver = Ql_iotConfigGetSoftVersion(); + return mp_obj_new_str(ver, strlen(ver)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetSoftVersion_obj, qpy_Ql_iotConfigGetSoftVersion); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetMcuVersion(mp_obj_t mp_compno, mp_obj_t mp_version) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t mcu_compno = {0}; + mp_buffer_info_t mcu_version = {0}; + mp_get_buffer_raise(mp_compno, &mcu_compno, MP_BUFFER_READ); + mp_get_buffer_raise(mp_version, &mcu_version, MP_BUFFER_READ); + if (FALSE == Ql_iotConfigSetMcuVersion((const char *)mcu_compno.buf, (const char *)mcu_version.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotConfigSetMcuVersion_obj, qpy_Ql_iotConfigSetMcuVersion); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetMcuVersion(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + char *temp = NULL; + char *oldVer = NULL; + mp_obj_t objList = mp_obj_new_list(0, NULL); + Ql_iotConfigGetMcuVersion(NULL, &oldVer); + temp = malloc(strlen(oldVer) + 1); + if (temp) + { + char *words[100]; + sprintf(temp, "%s", oldVer); + quint32_t count = Quos_stringSplit(temp, strlen(temp), words, sizeof(words) / sizeof(words[0]), ";", FALSE); + while (count--) + { + char *wordsnode[2]; + qint32_t nodeLen = Quos_stringSplit(words[count], strlen(words[count]), wordsnode, sizeof(wordsnode) / sizeof(wordsnode[0]), ":", FALSE); + if (2 == nodeLen) + { + mp_obj_t node[] = + { + mp_obj_new_str(wordsnode[0], strlen(wordsnode[0])), + mp_obj_new_str(wordsnode[1], strlen(wordsnode[1]))}; + mp_obj_list_append(objList, mp_obj_new_tuple(sizeof(node) / sizeof(node[0]), node)); + } + } + free(temp); + } + return objList; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetMcuVersion_obj, qpy_Ql_iotConfigGetMcuVersion); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetDkDs(mp_obj_t dk, mp_obj_t ds) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t dkData = {0}, dsData = {0}; + mp_get_buffer_raise(dk, &dkData, MP_BUFFER_READ); + mp_get_buffer_raise(ds, &dsData, MP_BUFFER_READ); + if (FALSE == Ql_iotConfigSetDkDs((const char *)dkData.buf, (const char *)dsData.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotConfigSetDkDs_obj, qpy_Ql_iotConfigSetDkDs); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetDkDs(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + char *dk, *ds; + if (FALSE == Ql_iotConfigGetDkDs(&dk, &ds)) + { + return mp_const_none; + } + mp_obj_t dkds[2] = + { + mp_obj_new_str(dk, strlen(dk)), + mp_obj_new_str(ds, strlen(ds))}; + return mp_obj_new_tuple(2, dkds); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetDkDs_obj, qpy_Ql_iotConfigGetDkDs); +/************************************************************************** +** 鍔熻兘 @brief : +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +static qbool ttlv_dict_handle(const void *ttlv_head, quint32_t count, mp_obj_t node_dict); +static qbool ttlv_array_handle(const void *ttlv_head, quint32_t count, mp_obj_t node_list) +{ + for (quint32_t i = 0; i < count; i++) + { + uint16_t id = 0; + int type = 0; + void *node = Ql_iotTtlvNodeGet(ttlv_head, i, &id, (QIot_dpDataType_e *)&type); + if (node) + { + switch (type) + { + case QIOT_DPDATA_TYPE_BOOL: + { + qbool bool_value; + Ql_iotTtlvNodeGetBool(node, &bool_value); + mp_obj_list_append(node_list, mp_obj_new_bool(bool_value)); + break; + } + case QIOT_DPDATA_TYPE_INT: + { + qint64_t num_value; + Ql_iotTtlvNodeGetInt(node, &num_value); + mp_obj_list_append(node_list, mp_obj_new_int_from_ll((long long)num_value)); + break; + } + case QIOT_DPDATA_TYPE_FLOAT: + { + double num_value; + Ql_iotTtlvNodeGetFloat(node, &num_value); + mp_obj_list_append(node_list, mp_obj_new_float((mp_float_t)num_value)); + break; + } + case QIOT_DPDATA_TYPE_BYTE: + { + quint8_t *value; + quint32_t len = Ql_iotTtlvNodeGetByte(node, &value); + mp_obj_list_append(node_list, mp_obj_new_bytes(value, len)); + break; + } + case QIOT_DPDATA_TYPE_STRUCT: + { + quint32_t struct_count = Ql_iotTtlvCountGet(node); + uint16_t temp_id = 0; + int temp_type = 0; + Ql_iotTtlvNodeGet(Ql_iotTtlvNodeGetStruct(node), 0, &temp_id, (QIot_dpDataType_e *)&temp_type); + if (temp_id == 0) + { + mp_obj_t struct_list = mp_obj_new_list(0, NULL); + ttlv_array_handle(Ql_iotTtlvNodeGetStruct(node), struct_count, struct_list); + mp_obj_list_append(node_list, struct_list); + } + else + { + mp_obj_t struct_dict = mp_obj_new_dict(struct_count); + ttlv_dict_handle(Ql_iotTtlvNodeGetStruct(node), struct_count, struct_dict); + mp_obj_list_append(node_list, struct_dict); + } + break; + } + default: + return FALSE; + break; + } + } + } + return TRUE; +} +static qbool ttlv_dict_handle(const void *ttlv_head, quint32_t count, mp_obj_t node_dict) +{ + for (quint32_t i = 0; i < count; i++) + { + uint16_t id = 0; + int type = 0; + void *node = Ql_iotTtlvNodeGet(ttlv_head, i, &id, (QIot_dpDataType_e *)&type); + if (node) + { + switch (type) + { + case QIOT_DPDATA_TYPE_BOOL: + { + qbool bool_value; + Ql_iotTtlvNodeGetBool(node, &bool_value); + mp_obj_dict_store(node_dict, mp_obj_new_int(id), mp_obj_new_bool(bool_value)); + break; + } + case QIOT_DPDATA_TYPE_INT: + { + qint64_t num_value; + Ql_iotTtlvNodeGetInt(node, &num_value); + mp_obj_dict_store(node_dict, mp_obj_new_int(id), mp_obj_new_int_from_ll((long long)num_value)); + break; + } + case QIOT_DPDATA_TYPE_FLOAT: + { + double num_value; + Ql_iotTtlvNodeGetFloat(node, &num_value); + mp_obj_dict_store(node_dict, mp_obj_new_int(id), mp_obj_new_float((mp_float_t)num_value)); + break; + } + case QIOT_DPDATA_TYPE_BYTE: + { + quint8_t *value; + quint32_t len = Ql_iotTtlvNodeGetByte(node, &value); + mp_obj_dict_store(node_dict, mp_obj_new_int(id), mp_obj_new_bytes(value, len)); + break; + } + case QIOT_DPDATA_TYPE_STRUCT: + { + quint32_t struct_count = Ql_iotTtlvCountGet(Ql_iotTtlvNodeGetStruct(node)); + uint16_t temp_id = 0; + int temp_type = 0; + Ql_iotTtlvNodeGet(Ql_iotTtlvNodeGetStruct(node), 0, &temp_id, (QIot_dpDataType_e *)&temp_type); + if (temp_id == 0) + { + mp_obj_t struct_list = mp_obj_new_list(0, NULL); + ttlv_array_handle(Ql_iotTtlvNodeGetStruct(node), struct_count, struct_list); + mp_obj_dict_store(node_dict, mp_obj_new_int(id), struct_list); + } + else + { + mp_obj_t struct_dict = mp_obj_new_dict(struct_count); + ttlv_dict_handle(Ql_iotTtlvNodeGetStruct(node), struct_count, struct_dict); + mp_obj_dict_store(node_dict, mp_obj_new_int(id), struct_dict); + } + break; + } + default: + return FALSE; + break; + } + } + } + return TRUE; +} + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +static c_callback_t *Ql_iotUrcEventCB = NULL; +static void ql_iotEventCB(quint32_t event, qint32_t errcode, const void *valueT, quint32_t valLen) +{ + if (NULL == Ql_iotUrcEventCB || (event == 7 && errcode == 10702)) + { + return; + } + if (NULL == valueT) + { + mp_obj_t tuple[] = + { + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode)}; + mp_sched_schedule_ex(Ql_iotUrcEventCB, mp_obj_new_tuple(2, tuple)); + } + else if (QIOT_ATEVENT_TYPE_RECV == event && QIOT_RECV_SUCC_PHYMODEL_RECV == errcode) + { + quint32_t count = Ql_iotTtlvCountGet(valueT); + mp_obj_t node_dict = mp_obj_new_dict(count); + if (ttlv_dict_handle(valueT, count, node_dict)) + { + mp_obj_t tuple[] = + { + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode), + node_dict}; + mp_sched_schedule_ex(Ql_iotUrcEventCB, mp_obj_new_tuple(3, tuple)); + } + } + else if (QIOT_ATEVENT_TYPE_RECV == event && QIOT_RECV_SUCC_PHYMODEL_REQ == errcode) + { + quint16_t pkgId = *(quint16_t *)valueT; + quint16_t *ids = (quint16_t *)((quint8_t *)valueT + sizeof(quint16_t)); + mp_obj_t req_list = mp_obj_new_list(0, NULL); + mp_obj_t req_list_temp = mp_obj_new_list(0, NULL); + mp_obj_list_append(req_list, mp_obj_new_int((mp_int_t)pkgId)); + for (quint32_t i = 0; i < valLen; i++) + { + quint16_t modelId = ids[i]; + mp_obj_list_append(req_list_temp, mp_obj_new_int((mp_int_t)modelId)); + } + mp_obj_list_append(req_list, req_list_temp); + mp_obj_t tuple[] = + { + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode), + req_list}; + mp_sched_schedule_ex(Ql_iotUrcEventCB, mp_obj_new_tuple(3, tuple)); + } + else + { + mp_obj_t tuple[] = + { + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode), + mp_obj_new_bytes(valueT, valLen)}; + mp_sched_schedule_ex(Ql_iotUrcEventCB, mp_obj_new_tuple(3, tuple)); + } +} +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetEventCB(mp_obj_t event_urc_cb) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + static c_callback_t cb = {0}; + Ql_iotUrcEventCB = &cb; + mp_sched_schedule_callback_register(Ql_iotUrcEventCB, event_urc_cb); + Ql_iotConfigSetEventCB(ql_iotEventCB); + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotConfigSetEventCB_obj, qpy_Ql_iotConfigSetEventCB); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotGetWorkState(void) +{ + quint32_t state = Ql_iotGetWorkState(); + return mp_obj_new_int_from_uint(state); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotGetWorkState_obj, qpy_Ql_iotGetWorkState); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdGetLocSupList(void) +{ + mp_obj_t supList = mp_obj_new_list(0, NULL); + void *titleTtlv = Ql_iotLocGetSupList(); + quint32_t count = Ql_iotTtlvCountGet(titleTtlv); + quint32_t i; + for (i = 0; i < count; i++) + { + char *str = Ql_iotTtlvNodeGetString(Ql_iotTtlvNodeGet(titleTtlv, i, NULL, NULL)); + if (str) + { + mp_obj_list_append(supList, mp_obj_new_str(str, strlen(str))); + } + } + Ql_iotTtlvFree(&titleTtlv); + + return supList; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotCmdGetLocSupList_obj, qpy_Ql_iotCmdGetLocSupList); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdGetLocData(mp_obj_t mp_title) +{ + mp_obj_t locDatas = mp_obj_new_list(0, NULL); + if (mp_obj_is_type(mp_title, &mp_type_list) || mp_obj_is_type(mp_title, &mp_type_tuple)) + { + quint32_t i; + size_t len = 0; + void *titleTtlv = NULL; + mp_obj_t *items = NULL; + mp_buffer_info_t itemstr = {0}; + mp_obj_list_get(mp_title, &len, &items); + for (i = 0; i < len; i++) + { + mp_get_buffer_raise(items[i], &itemstr, MP_BUFFER_READ); + Ql_iotTtlvIdAddString(&titleTtlv, 0, itemstr.buf); + } + void *nmeaTtlv = Ql_iotLocGetData(titleTtlv); + Ql_iotTtlvFree(&titleTtlv); + + quint32_t count = Ql_iotTtlvCountGet(nmeaTtlv); + for (i = 0; i < count; i++) + { + char *str = Ql_iotTtlvNodeGetString(Ql_iotTtlvNodeGet(nmeaTtlv, i, NULL, NULL)); + if (str) + { + mp_obj_list_append(locDatas, mp_obj_new_str(str, strlen(str))); + } + } + Ql_iotTtlvFree(&nmeaTtlv); + } + return locDatas; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdGetLocData_obj, qpy_Ql_iotCmdGetLocData); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdBusLocReportInside(mp_obj_t mp_title) +{ + mp_obj_t ret = mp_const_false; + if (mp_obj_is_type(mp_title, &mp_type_list) || mp_obj_is_type(mp_title, &mp_type_tuple)) + { + quint8_t i; + size_t len = 0; + void *titleTtlv = NULL; + mp_obj_t *items = NULL; + mp_buffer_info_t itemstr = {0}; + mp_obj_list_get(mp_title, &len, &items); + for (i = 0; i < len; i++) + { + mp_get_buffer_raise(items[i], &itemstr, MP_BUFFER_READ); + Ql_iotTtlvIdAddString(&titleTtlv, 0, itemstr.buf); + } + if (Ql_iotCmdBusLocReportInside(titleTtlv)) + { + ret = mp_const_true; + } + Ql_iotTtlvFree(&titleTtlv); + } + else + { + ret = mp_const_false; + } + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdBusLocReportInside_obj, qpy_Ql_iotCmdBusLocReportInside); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotCmdBusLocReportOutside(mp_obj_t mp_nmea) +{ + mp_obj_t ret = mp_const_false; + if (mp_obj_is_type(mp_nmea, &mp_type_list) || mp_obj_is_type(mp_nmea, &mp_type_tuple)) + { + quint8_t i; + size_t len = 0; + void *titleTtlv = NULL; + mp_obj_t *items = NULL; + mp_buffer_info_t itemstr = {0}; + mp_obj_list_get(mp_nmea, &len, &items); + for (i = 0; i < len; i++) + { + mp_get_buffer_raise(items[i], &itemstr, MP_BUFFER_READ); + Ql_iotTtlvIdAddString(&titleTtlv, 0, itemstr.buf); + } + if (Ql_iotCmdBusLocReportOutside(titleTtlv)) + { + ret = mp_const_true; + } + Ql_iotTtlvFree(&titleTtlv); + } + else + { + ret = mp_const_false; + } + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotCmdBusLocReportOutside_obj, qpy_Ql_iotCmdBusLocReportOutside); +#ifdef QUEC_ENABLE_HTTP_OTA +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetHttpOtaProductInfo(mp_obj_t product_key, mp_obj_t product_secret) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t keyinfo = {0}; + mp_buffer_info_t secretinfo = {0}; + mp_get_buffer_raise(product_key, &keyinfo, MP_BUFFER_READ); + mp_get_buffer_raise(product_secret, &secretinfo, MP_BUFFER_READ); + if (FALSE == Ql_iotConfigSetHttpOtaProductInfo((const char *)keyinfo.buf, (const char *)secretinfo.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotConfigSetHttpOtaProductInfo_obj, qpy_Ql_iotConfigSetHttpOtaProductInfo); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetHttpOtaProductInfo(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + char *product_key = NULL; + char *product_secret = NULL; + + Ql_iotConfigGetHttpOtaProductInfo(&product_key, &product_secret); + mp_obj_t product_info[2] = + { + mp_obj_new_str(product_key, strlen(product_key)), + mp_obj_new_str(product_secret, strlen(product_secret)), + }; + return mp_obj_new_tuple(2, product_info); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetHttpOtaProductInfo_obj, qpy_Ql_iotConfigGetHttpOtaProductInfo); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetHttpOtaTls(mp_obj_t mp_tls) +{ + if (Ql_iotGetWorkState() == 0 || FALSE == mp_obj_is_bool(mp_tls)) + { + return mp_obj_new_bool(FALSE); + } + if (FALSE == Ql_iotConfigSetHttpOtaTls(mp_const_true == mp_tls ? TRUE : FALSE)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotConfigSetHttpOtaTls_obj, qpy_Ql_iotConfigSetHttpOtaTls); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetHttpOtaTls(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + return mp_obj_new_bool(Ql_iotConfigGetHttpOtaTls()); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetHttpOtaTls_obj, qpy_Ql_iotConfigGetHttpOtaTls); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetHttpOtaServer(mp_obj_t mp_server) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t serverinfo = {0}; + mp_get_buffer_raise(mp_server, &serverinfo, MP_BUFFER_READ); + if (FALSE == Ql_iotConfigSetHttpOtaServer((const char *)serverinfo.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotConfigSetHttpOtaServer_obj, qpy_Ql_iotConfigSetHttpOtaServer); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetHttpOtaServer(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + char *server = NULL; + + Ql_iotConfigGetHttpOtaServer(&server); + return mp_obj_new_str(server, strlen(server)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetHttpOtaServer_obj, qpy_Ql_iotConfigGetHttpOtaServer); + +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetHttpOtaUp(mp_obj_t mp_battery, mp_obj_t mp_upmode, mp_obj_t mp_url) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + int battery = mp_obj_get_int(mp_battery); + int upmode = mp_obj_get_int(mp_upmode); + mp_buffer_info_t url = {0}; + mp_get_buffer_raise(mp_url, &url, MP_BUFFER_READ); + if (FALSE == Ql_iotConfigSetHttpOtaUp(battery, upmode, (const char *)url.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(qpy_Ql_iotConfigSetHttpOtaUp_obj, qpy_Ql_iotConfigSetHttpOtaUp); +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigGetHttpOtaUp(void) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_const_none; + } + quint8_t battery, upmode; + char *url = NULL; + + Ql_iotConfigGetHttpOtaUp(&battery, &upmode, &url); + mp_obj_t info[3] = + { + mp_obj_new_int(battery), + mp_obj_new_int(upmode), + mp_obj_new_str(url, strlen(url)), + }; + return mp_obj_new_tuple(3, info); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(qpy_Ql_iotConfigGetHttpOtaUp_obj, qpy_Ql_iotConfigGetHttpOtaUp); + +/************************************************************************** +** 鍔熻兘 @brief : +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +static mp_obj_t Ql_iotUrcHttpOtaEventCB = NULL; +static void ql_iotHttpOTAEventCB(quint32_t event, qint32_t errcode, const void *valueT, quint32_t valLen) +{ + if (NULL == Ql_iotUrcHttpOtaEventCB) + { + return; + } + if (NULL == valueT) + { + mp_obj_t tuple[] = + { + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode)}; + mp_sched_schedule(Ql_iotUrcHttpOtaEventCB, mp_obj_new_tuple(2, tuple)); + } + else + { + mp_obj_t tuple[] = + { + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode), + mp_obj_new_bytes(valueT, valLen)}; + mp_sched_schedule(Ql_iotUrcHttpOtaEventCB, mp_obj_new_tuple(3, tuple)); + } +} +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotConfigSetHttpOtaEventCb(mp_obj_t event_urc_cb) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + Ql_iotUrcHttpOtaEventCB = event_urc_cb; + Ql_iotConfigSetHttpOtaEventCb(ql_iotHttpOTAEventCB); + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotConfigSetHttpOtaEventCb_obj, qpy_Ql_iotConfigSetHttpOtaEventCb); +#endif +#ifdef QUEC_ENABLE_GATEWAY +/************************************************************************** +** @brief : 浣跨敤鍥炶皟鍑芥暟灏嗘暟鎹粍鍖呭苟鎶婃暟鎹彂閫佸埌Python搴旂敤灞 +** @param : +** @retval: +***************************************************************************/ +static mp_obj_t Ql_iotSubDevUrcEventCB = NULL; +static void ql_iotSubDevEventCB(quint32_t event, qint32_t errcode, const char *subPk, const char *subDk, const void *valueT, quint32_t valLen) +{ + if (NULL == Ql_iotSubDevUrcEventCB) + { + return; + } + if (NULL == valueT) + { + mp_obj_t tuple[] = + { + mp_obj_new_bytes((const quint8_t*)subPk, strlen(subDk)), + mp_obj_new_bytes((const quint8_t*)subDk, strlen(subDk)), + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode)}; + mp_sched_schedule(Ql_iotSubDevUrcEventCB, mp_obj_new_tuple(4, tuple)); + } + else if (QIOT_ATEVENT_TYPE_RECV == event && QIOT_RECV_SUCC_PHYMODEL_RECV == errcode) + { + quint32_t count = Ql_iotTtlvCountGet(valueT); + mp_obj_t node_dict = mp_obj_new_dict(count); + if (ttlv_dict_handle(valueT, count, node_dict)) + { + mp_obj_t tuple[] = + { + mp_obj_new_bytes((const quint8_t*)subPk, strlen(subPk)), + mp_obj_new_bytes((const quint8_t*)subDk, strlen(subDk)), + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode), + node_dict}; + mp_sched_schedule(Ql_iotSubDevUrcEventCB, mp_obj_new_tuple(5, tuple)); + } + } + else if (QIOT_ATEVENT_TYPE_RECV == event && QIOT_RECV_SUCC_PHYMODEL_REQ == errcode) + { + quint16_t pkgId = *(quint16_t *)valueT; + quint16_t *ids = (quint16_t *)((quint8_t *)valueT + sizeof(quint16_t)); + mp_obj_t req_list = mp_obj_new_list(0, NULL); + mp_obj_t req_list_temp = mp_obj_new_list(0, NULL); + mp_obj_list_append(req_list, mp_obj_new_int((mp_int_t)pkgId)); + for (quint32_t i = 0; i < valLen; i++) + { + quint16_t modelId = ids[i]; + mp_obj_list_append(req_list_temp, mp_obj_new_int((mp_int_t)modelId)); + } + mp_obj_list_append(req_list, req_list_temp); + mp_obj_t tuple[] = + { + mp_obj_new_bytes((const quint8_t*)subPk, strlen(subPk)), + mp_obj_new_bytes((const quint8_t*)subDk, strlen(subDk)), + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode), + req_list}; + mp_sched_schedule(Ql_iotSubDevUrcEventCB, mp_obj_new_tuple(5, tuple)); + } + else + { + mp_obj_t tuple[] = + { + mp_obj_new_bytes((const quint8_t*)subPk, strlen(subPk)), + mp_obj_new_bytes((const quint8_t*)subDk, strlen(subDk)), + mp_obj_new_int_from_uint(event), + mp_obj_new_int_from_uint(errcode), + mp_obj_new_bytes(valueT, valLen)}; + mp_sched_schedule(Ql_iotSubDevUrcEventCB, mp_obj_new_tuple(5, tuple)); + } +} +/************************************************************************** +** @brief : 娉ㄥ唽瀛愯澶囦簨浠跺洖璋冨嚱鏁 +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotSubDevSetEventCB(mp_obj_t event_sub_dev_urc_cb) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + Ql_iotSubDevUrcEventCB = event_sub_dev_urc_cb; + Ql_iotConfigSetSubDevEventCB(ql_iotSubDevEventCB); + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(qpy_Ql_iotSubDevSetEventCB_obj, qpy_Ql_iotSubDevSetEventCB); +/************************************************************************** +** @brief : 鍙戣捣瀛愯澶囪繛鎺 +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotSubDevConn(size_t n, const mp_obj_t *mp_data) +{ + if (Ql_iotGetWorkState() == 0 + || !mp_obj_is_str(mp_data[0])|| !mp_obj_is_str(mp_data[1])|| !mp_obj_is_str(mp_data[2])) + { + printf("false...\n"); + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t pro_keyinfo = {0}; + mp_buffer_info_t pro_secretinfo = {0}; + mp_buffer_info_t dev_keyinfo = {0}; + mp_buffer_info_t dev_secretinfo = {0}; + int session_type_val; + int keepalive_val; + mp_get_buffer_raise(mp_data[0], &pro_keyinfo, MP_BUFFER_READ); + mp_get_buffer_raise(mp_data[1], &pro_secretinfo, MP_BUFFER_READ); + mp_get_buffer_raise(mp_data[2], &dev_keyinfo, MP_BUFFER_READ); + if(5 == n) + { + session_type_val = mp_obj_get_int(mp_data[3]); + keepalive_val = mp_obj_get_int(mp_data[4]); + } + else + { + mp_get_buffer_raise(mp_data[3], &dev_secretinfo, MP_BUFFER_READ); + session_type_val = mp_obj_get_int(mp_data[4]); + keepalive_val = mp_obj_get_int(mp_data[5]); + } + + if (FALSE == Ql_iotSubDevConn((const char *)pro_keyinfo.buf, (const char *)pro_secretinfo.buf, (const char *)dev_keyinfo.buf, (const char *)dev_secretinfo.buf, session_type_val, keepalive_val)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(qpy_Ql_iotSubDevConn_obj, 5,6, qpy_Ql_iotSubDevConn); +/************************************************************************** +** @brief : 鍙戣捣瀛愯澶囩櫥鍑 +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotSubDevDisconn(mp_obj_t product_key, mp_obj_t device_key) +{ + if (Ql_iotGetWorkState() == 0) + { + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t pro_key = {0}; + mp_buffer_info_t dev_key = {0}; + mp_get_buffer_raise(product_key, &pro_key, MP_BUFFER_READ); + mp_get_buffer_raise(device_key, &dev_key, MP_BUFFER_READ); + if (FALSE == Ql_iotSubDevDisconn((const char *)pro_key.buf, (const char *)dev_key.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotSubDevDisconn_obj, qpy_Ql_iotSubDevDisconn); +/************************************************************************** +** @brief : 鍙戣捣瀛愯澶囨敞閿 +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotSubDevDeauth(size_t n, const mp_obj_t *mp_data) +{ + if (Ql_iotGetWorkState() == 0 || + !mp_obj_is_str(mp_data[0])|| !mp_obj_is_str(mp_data[1])|| !mp_obj_is_str(mp_data[2])|| !mp_obj_is_str(mp_data[3])) + { + return mp_obj_new_bool(FALSE); + } + mp_buffer_info_t pro_key = {0}; + mp_buffer_info_t pro_secret = {0}; + mp_buffer_info_t dev_key = {0}; + mp_buffer_info_t dev_secret = {0}; + mp_get_buffer_raise(mp_data[0], &pro_key, MP_BUFFER_READ); + mp_get_buffer_raise(mp_data[1], &pro_secret, MP_BUFFER_READ); + mp_get_buffer_raise(mp_data[2], &dev_key, MP_BUFFER_READ); + mp_get_buffer_raise(mp_data[3], &dev_secret, MP_BUFFER_READ); + if (FALSE == Ql_iotSubDevDeauth((const char *)pro_key.buf, (const char *)pro_secret.buf, (const char *)dev_key.buf, (const char *)dev_secret.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(qpy_Ql_iotSubDevDeauth_obj, 4, 4, qpy_Ql_iotSubDevDeauth); +/************************************************************************** +** @brief : 涓婅瀛愯澶囬忎紶鏁版嵁 +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotSubDevPassTransSend(mp_obj_t product_key, mp_obj_t device_key, mp_obj_t mp_data) +{ + mp_buffer_info_t data = {0}; + mp_buffer_info_t pro_key = {0}; + mp_buffer_info_t dev_key = {0}; + mp_get_buffer_raise(product_key, &pro_key, MP_BUFFER_READ); + mp_get_buffer_raise(device_key, &dev_key, MP_BUFFER_READ); + mp_get_buffer_raise(mp_data, &data, MP_BUFFER_READ); + if (FALSE == Ql_iotSubDevPassTransSend((const char *)pro_key.buf, (const char *)dev_key.buf, (quint8_t *)data.buf, data.len)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(qpy_Ql_iotSubDevPassTransSend_obj, qpy_Ql_iotSubDevPassTransSend); +/************************************************************************** +** @brief : 涓婅瀛愯澶囩墿妯″瀷鏁版嵁 +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotSubDevTslReport(mp_obj_t product_key, mp_obj_t device_key, mp_obj_t mp_data) +{ + mp_obj_t ret = mp_const_true; + void *ttlvHead = NULL; + if (phy_dict_handle(mp_data, &ttlvHead)) + { + mp_buffer_info_t pro_key = {0}; + mp_buffer_info_t dev_key = {0}; + mp_get_buffer_raise(product_key, &pro_key, MP_BUFFER_READ); + mp_get_buffer_raise(device_key, &dev_key, MP_BUFFER_READ); + if (FALSE == Ql_iotSubDevTslReport((const char *)pro_key.buf, (const char *)dev_key.buf, ttlvHead)) + { + ret = mp_const_false; + } + } + else + { + ret = mp_const_false; + } + Ql_iotTtlvFree(&ttlvHead); + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(qpy_Ql_iotSubDevTslReport_obj, qpy_Ql_iotSubDevTslReport); +/************************************************************************** +** @brief : 瀛愯澶囧洖澶嶇墿妯″瀷鏌ヨ +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotSubDevTslAck(size_t n, const mp_obj_t *mp_data) +{ + if (!mp_obj_is_str(mp_data[0])|| !mp_obj_is_str(mp_data[1])|| !mp_obj_is_int(mp_data[2])) + { + return mp_obj_new_bool(FALSE); + } + int pkgid = mp_obj_get_int(mp_data[2]); + mp_obj_t ret = mp_const_true; + void *ttlvHead = NULL; + if (phy_dict_handle(mp_data[3], &ttlvHead)) + { + mp_buffer_info_t pro_key = {0}; + mp_buffer_info_t dev_key = {0}; + mp_get_buffer_raise(mp_data[0], &pro_key, MP_BUFFER_READ); + mp_get_buffer_raise(mp_data[1], &dev_key, MP_BUFFER_READ); + if (FALSE == Ql_iotSubDevTslAck((const char *)pro_key.buf, (const char *)dev_key.buf, (quint16_t)pkgid, ttlvHead)) + { + ret = mp_const_false; + } + } + else + { + ret = mp_const_false; + } + Ql_iotTtlvFree(&ttlvHead); + return ret; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(qpy_Ql_iotSubDevTslAck_obj, 4, 4, qpy_Ql_iotSubDevTslAck); +/************************************************************************** +** @brief : 瀛愯澶囧彂閫佸績璺冲寘 +** @param : +** @retval: +***************************************************************************/ +STATIC mp_obj_t qpy_Ql_iotSubDevHTB(mp_obj_t product_key, mp_obj_t device_key) +{ + mp_buffer_info_t pro_key = {0}; + mp_buffer_info_t dev_key = {0}; + mp_get_buffer_raise(product_key, &pro_key, MP_BUFFER_READ); + mp_get_buffer_raise(device_key, &dev_key, MP_BUFFER_READ); + + if (FALSE == Ql_iotSubDevHTB((const char *)pro_key.buf, (const char *)dev_key.buf)) + { + return mp_obj_new_bool(FALSE); + } + return mp_obj_new_bool(TRUE); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(qpy_Ql_iotSubDevHTB_obj, qpy_Ql_iotSubDevHTB); +#endif +/************************************************************************** +** @brief : +** @param : +** @retval: +***************************************************************************/ +STATIC const mp_rom_map_elem_t mp_module_quecIot_globals_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_quecIot)}, + {MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&qpy_Ql_iotInit_obj)}, + {MP_ROM_QSTR(MP_QSTR_setConnmode), MP_ROM_PTR(&qpy_Ql_iotConfigSetConnmode_obj)}, + {MP_ROM_QSTR(MP_QSTR_getConnmode), MP_ROM_PTR(&qpy_Ql_iotConfigGetConnmode_obj)}, + {MP_ROM_QSTR(MP_QSTR_setEventCB), MP_ROM_PTR(&qpy_Ql_iotConfigSetEventCB_obj)}, + {MP_ROM_QSTR(MP_QSTR_getWorkState), MP_ROM_PTR(&qpy_Ql_iotGetWorkState_obj)}, + {MP_ROM_QSTR(MP_QSTR_setProductinfo), MP_ROM_PTR(&qpy_Ql_iotConfigSetProductinfo_obj)}, + {MP_ROM_QSTR(MP_QSTR_getProductinfo), MP_ROM_PTR(&qpy_Ql_iotConfigGetProductinfo_obj)}, + {MP_ROM_QSTR(MP_QSTR_setServer), MP_ROM_PTR(&qpy_Ql_iotConfigSetServer_obj)}, + {MP_ROM_QSTR(MP_QSTR_getServer), MP_ROM_PTR(&qpy_Ql_iotConfigGetServer_obj)}, + {MP_ROM_QSTR(MP_QSTR_setLifetime), MP_ROM_PTR(&qpy_Ql_iotConfigSetLifetime_obj)}, + {MP_ROM_QSTR(MP_QSTR_getLifetime), MP_ROM_PTR(&qpy_Ql_iotConfigGetLifetime_obj)}, + {MP_ROM_QSTR(MP_QSTR_setSessionFlag), MP_ROM_PTR(&qpy_Ql_iotConfigSetSessionFlag_obj)}, + {MP_ROM_QSTR(MP_QSTR_getSessionFlag), MP_ROM_PTR(&qpy_Ql_iotConfigGetSessionFlag_obj)}, + {MP_ROM_QSTR(MP_QSTR_setPdpContextId), MP_ROM_PTR(&qpy_Ql_iotConfigSetPdpContextId_obj)}, + {MP_ROM_QSTR(MP_QSTR_getPdpContextId), MP_ROM_PTR(&qpy_Ql_iotConfigGetPdpContextId_obj)}, + {MP_ROM_QSTR(MP_QSTR_getSoftVersion), MP_ROM_PTR(&qpy_Ql_iotConfigGetSoftVersion_obj)}, + {MP_ROM_QSTR(MP_QSTR_setMcuVersion), MP_ROM_PTR(&qpy_Ql_iotConfigSetMcuVersion_obj)}, + {MP_ROM_QSTR(MP_QSTR_getMcuVersion), MP_ROM_PTR(&qpy_Ql_iotConfigGetMcuVersion_obj)}, + {MP_ROM_QSTR(MP_QSTR_setDkDs), MP_ROM_PTR(&qpy_Ql_iotConfigSetDkDs_obj)}, + {MP_ROM_QSTR(MP_QSTR_getDkDs), MP_ROM_PTR(&qpy_Ql_iotConfigGetDkDs_obj)}, + + {MP_ROM_QSTR(MP_QSTR_passTransSend), MP_ROM_PTR(&qpy_Ql_iotCmdBusPassTransSend_obj)}, + {MP_ROM_QSTR(MP_QSTR_phymodelReport), MP_ROM_PTR(&qpy_Ql_iotCmdBusPhymodelReport_obj)}, + {MP_ROM_QSTR(MP_QSTR_phymodelAck), MP_ROM_PTR(&qpy_Ql_iotCmdBusPhymodelAck_obj)}, + {MP_ROM_QSTR(MP_QSTR_getDevStatus), MP_ROM_PTR(&qpy_Ql_iotCmdSysGetDevStatus_obj)}, + {MP_ROM_QSTR(MP_QSTR_getDevInfo), MP_ROM_PTR(&qpy_Ql_iotCmdSysGetDevInfo_obj)}, + {MP_ROM_QSTR(MP_QSTR_statusReport), MP_ROM_PTR(&qpy_Ql_iotCmdSysStatusReport_obj)}, + {MP_ROM_QSTR(MP_QSTR_devInfoReport), MP_ROM_PTR(&qpy_Ql_iotCmdSysDevInfoReport_obj)}, + + {MP_ROM_QSTR(MP_QSTR_getLocSupList), MP_ROM_PTR(&qpy_Ql_iotCmdGetLocSupList_obj)}, + {MP_ROM_QSTR(MP_QSTR_getLocData), MP_ROM_PTR(&qpy_Ql_iotCmdGetLocData_obj)}, + {MP_ROM_QSTR(MP_QSTR_locReportInside), MP_ROM_PTR(&qpy_Ql_iotCmdBusLocReportInside_obj)}, + {MP_ROM_QSTR(MP_QSTR_locReportOutside), MP_ROM_PTR(&qpy_Ql_iotCmdBusLocReportOutside_obj)}, + + {MP_ROM_QSTR(MP_QSTR_otaRequest), MP_ROM_PTR(&qpy_Ql_iotCmdOtaRequest_obj)}, + {MP_ROM_QSTR(MP_QSTR_otaAction), MP_ROM_PTR(&qpy_Ql_iotCmdOtaAction_obj)}, + {MP_ROM_QSTR(MP_QSTR_mcuFWDataRead), MP_ROM_PTR(&qpy_Ql_iotCmdOtaMcuFWDataRead_obj)}, +#ifdef QUEC_ENABLE_HTTP_OTA + {MP_ROM_QSTR(MP_QSTR_setHttpOtaProductInfo), MP_ROM_PTR(&qpy_Ql_iotConfigSetHttpOtaProductInfo_obj)}, + {MP_ROM_QSTR(MP_QSTR_getHttpOtaProductInfo), MP_ROM_PTR(&qpy_Ql_iotConfigGetHttpOtaProductInfo_obj)}, + {MP_ROM_QSTR(MP_QSTR_setHttpOtaTls), MP_ROM_PTR(&qpy_Ql_iotConfigSetHttpOtaTls_obj)}, + {MP_ROM_QSTR(MP_QSTR_getHttpOtaTls), MP_ROM_PTR(&qpy_Ql_iotConfigGetHttpOtaTls_obj)}, + {MP_ROM_QSTR(MP_QSTR_setHttpOtaServer), MP_ROM_PTR(&qpy_Ql_iotConfigSetHttpOtaServer_obj)}, + {MP_ROM_QSTR(MP_QSTR_getHttpOtaServer), MP_ROM_PTR(&qpy_Ql_iotConfigGetHttpOtaServer_obj)}, + {MP_ROM_QSTR(MP_QSTR_setHttpOtaUp), MP_ROM_PTR(&qpy_Ql_iotConfigSetHttpOtaUp_obj)}, + {MP_ROM_QSTR(MP_QSTR_getHttpOtaUp), MP_ROM_PTR(&qpy_Ql_iotConfigGetHttpOtaUp_obj)}, + {MP_ROM_QSTR(MP_QSTR_setHttpOtaEventCb), MP_ROM_PTR(&qpy_Ql_iotConfigSetHttpOtaEventCb_obj)}, +#endif +#ifdef QUEC_ENABLE_GATEWAY + {MP_ROM_QSTR(MP_QSTR_subDevSetEventCB), MP_ROM_PTR(&qpy_Ql_iotSubDevSetEventCB_obj)}, + {MP_ROM_QSTR(MP_QSTR_subDevConn), MP_ROM_PTR(&qpy_Ql_iotSubDevConn_obj)}, + {MP_ROM_QSTR(MP_QSTR_subDevDisconn), MP_ROM_PTR(&qpy_Ql_iotSubDevDisconn_obj)}, + {MP_ROM_QSTR(MP_QSTR_subDevDeauth), MP_ROM_PTR(&qpy_Ql_iotSubDevDeauth_obj)}, + {MP_ROM_QSTR(MP_QSTR_subDevPassTransSend), MP_ROM_PTR(&qpy_Ql_iotSubDevPassTransSend_obj)}, + {MP_ROM_QSTR(MP_QSTR_subDevTslReport), MP_ROM_PTR(&qpy_Ql_iotSubDevTslReport_obj)}, + {MP_ROM_QSTR(MP_QSTR_subDevTslAck), MP_ROM_PTR(&qpy_Ql_iotSubDevTslAck_obj)}, + {MP_ROM_QSTR(MP_QSTR_subDevHTB), MP_ROM_PTR(&qpy_Ql_iotSubDevHTB_obj)}, +#endif +}; +STATIC MP_DEFINE_CONST_DICT(mp_module_quecIot_globals, mp_module_quecIot_globals_table); + +const mp_obj_module_t mp_module_quecIot = { + .base = {&mp_type_module}, + .globals = (mp_obj_dict_t *)&mp_module_quecIot_globals, +}; diff --git a/driverLayer/Qhal_types.h b/driverLayer/Qhal_types.h index 326d4e6..10b0486 100644 --- a/driverLayer/Qhal_types.h +++ b/driverLayer/Qhal_types.h @@ -40,6 +40,7 @@ typedef unsigned char qbool; #define FUNCTION_ATTR_RAM +#define QUEC_ENABLE_QTH_OTA 1 #define HAL_MEMCPY(a,b,l) memcpy((quint8_t*)(a),(quint8_t*)(b),l) #define HAL_MEMCMP(a,b,l) memcmp((quint8_t*)(a),(quint8_t*)(b),l) #define HAL_MEMSET(a,b,l) memset((void *)a, (int)b, (size_t)l) diff --git a/driverLayer/qhal_Dev.c b/driverLayer/qhal_Dev.c index 07c2163..dd3ade9 100644 --- a/driverLayer/qhal_Dev.c +++ b/driverLayer/qhal_Dev.c @@ -49,6 +49,7 @@ static Systick_T RtcTime = {0,0}; HAL_LOCK_DEF(static, lockMallocId) +//HAL_LOCK_DEF(static, lockKernelId) /************************************************************************** ** 功能 @brief : 看门狗喂狗 ** 输入 @param : @@ -343,12 +344,13 @@ void qhal_MainTask(void *argv) Helios_Thread_Delete(Helios_Thread_GetID()); return; } - Ql_iotInit(); + //Ql_iotInit(); + Quos_logPrintf(HAL_DEV, LL_DBG," qhal_MainTask\r\n"); while (1) { quint32_t msg = 1; quint32_t idletime = Quos_kernelTask(); - Quos_logPrintf(HAL_DEV, LL_DBG,"exec:%u\r\n", idletime); + Quos_logPrintf(HAL_DEV, LL_DBG," idletime exec:%u\r\n", idletime); if(idletime) { Helios_MsgQ_Get(msg_quos_task, (void *)&msg, sizeof(quint32_t), idletime); @@ -366,7 +368,7 @@ qbool FUNCTION_ATTR_ROM Qhal_quecsdk_init(void) qhal_main_ref.entry = qhal_MainTask; qhal_main_ref.priority = QHAL_APP_TASK_PRIORITY; qhal_main_ref.stack_size = 1024 * 64; - if(0 >= Helios_Thread_Create(&qhal_main_ref)) + if(0 == Helios_Thread_Create(&qhal_main_ref)) { Helios_Debug_Output("[INIT]Helios_Thread_Create fail"); return FALSE; @@ -381,6 +383,7 @@ qbool FUNCTION_ATTR_ROM Qhal_quecsdk_init(void) qbool FUNCTION_ATTR_ROM Qhal_beforeMain(void) { HAL_LOCK_INIT(lockMallocId); + //HAL_LOCK_INIT(lockKernelId); #if defined (PLAT_Unisoc) Quos_logPrintf(HAL_DEV, LL_DBG,"********** This is PLAT_Unisoc **********"); #elif defined (PLAT_ASR) @@ -390,6 +393,22 @@ qbool FUNCTION_ATTR_ROM Qhal_beforeMain(void) #endif return TRUE; } + +/************************************************************************** +** 鍔熻兘 @brief : 閫鍑轰换鍔℃寕璧锋ā寮 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Qhal_KernelResume(void) +{ + quint32_t msg = 1; + if(msg_quos_task) + { + Helios_MsgQ_Put(msg_quos_task, (void *)&msg, sizeof(quint32_t), HELIOS_NO_WAIT); + } + //HAL_UNLOCK(lockKernelId); +} + /************************************************************************** ** 功能 @brief : 退出低功耗模式 ** 输入 @param : diff --git a/driverLayer/qhal_Dev.h b/driverLayer/qhal_Dev.h index f83a665..2a5e462 100644 --- a/driverLayer/qhal_Dev.h +++ b/driverLayer/qhal_Dev.h @@ -12,16 +12,23 @@ typedef enum DEV_RESTART_HW_FAULT, } DevRestartReason_e; +#define QIOT_MD5_MAXSIZE (32) +typedef struct +{ + uint8_t idex; + char md5[QIOT_MD5_MAXSIZE + 1]; + char *downloadUrl; + quint32_t size; +} QIot_otaFilePublicInfo_t; #define DEV_RESTART_REASON_STRING(X) \ ( \ - (X == DEV_RESTART_FACTORY) ? "DEV_RESTART_FACTORY" : \ - (X == DEV_RESTART_SEGMENTFAULT) ? "DEV_RESTART_SEGMENTFAULT" : \ - (X == DEV_RESTART_NORMAL) ? "DEV_RESTART_NORMAL" : \ - (X == DEV_RESTART_OTA) ? "DEV_RESTART_OTA" : \ - (X == DEV_RESTART_WDT) ? "DEV_RESTART_WDT" : \ - (X == DEV_RESTART_NET_EXCP) ? "DEV_RESTART_NET_EXCP" : \ - (X == DEV_RESTART_HW_FAULT) ? "DEV_RESTART_HW_FAULT" : \ - "Unknown") + (X == DEV_RESTART_FACTORY) ? "DEV_RESTART_FACTORY" : (X == DEV_RESTART_SEGMENTFAULT) ? "DEV_RESTART_SEGMENTFAULT" \ + : (X == DEV_RESTART_NORMAL) ? "DEV_RESTART_NORMAL" \ + : (X == DEV_RESTART_OTA) ? "DEV_RESTART_OTA" \ + : (X == DEV_RESTART_WDT) ? "DEV_RESTART_WDT" \ + : (X == DEV_RESTART_NET_EXCP) ? "DEV_RESTART_NET_EXCP" \ + : (X == DEV_RESTART_HW_FAULT) ? "DEV_RESTART_HW_FAULT" \ + : "Unknown") void Qhal_devFeeddog(void); qbool Qhal_rtcInit(void); @@ -33,10 +40,17 @@ void Qhal_devRestart(void); qbool Qhal_beforeMain(void); char *Qhal_devUuidGet(void); quint32_t Qhal_randomGet(void); -#ifdef AT_ENABLE_QUEC +#ifdef QUEC_ENABLE_AT void Qhal_urcReport(const quint8_t *data, quint32_t len); #endif +#if QUEC_ENABLE_QTH_OTA quint32_t Qhal_devOtaNotify(const char *filename, quint32_t fileSize); +#else +quint32_t Qhal_devOtaNotify(QIot_otaFilePublicInfo_t info[], quint32_t size); +#endif +void Qhal_KernelResume(void); void Qhal_netOpen(quint32_t *timeout); void Qhal_netClose(void); -#endif \ No newline at end of file + +qbool FUNCTION_ATTR_ROM Qhal_quecsdk_init(void); +#endif diff --git a/driverLayer/qhal_Socket.c b/driverLayer/qhal_Socket.c index 5f54f0e..a2e8647 100644 --- a/driverLayer/qhal_Socket.c +++ b/driverLayer/qhal_Socket.c @@ -190,7 +190,7 @@ static void qhal_SockTcpRecvTask(void *arg) bufLen = read(sockFd, tcp_buf, sizeof(tcp_buf)); if (bufLen > 0) { - Quos_socketIORx(sockFd, SOCKET_TYPE_TCP_CLI, NULL, tcp_buf, bufLen); + Quos_socketIORx(sockFd, SOCKET_TYPE_TCP_CLI, NULL, 0, tcp_buf, bufLen); } else { @@ -199,7 +199,7 @@ static void qhal_SockTcpRecvTask(void *arg) } } Quos_logPrintf(HAL_SOCK, LL_DBG, "sockFd:" PRINTF_FD " len:%d result %d error:%d", sockFd, (int)bufLen, result, (int)errno); - Quos_socketIORx(sockFd, SOCKET_TYPE_TCP_CLI, NULL, NULL, 0); + Quos_socketIORx(sockFd, SOCKET_TYPE_TCP_CLI, NULL, 0, NULL, 0); Helios_Thread_Delete(Helios_Thread_GetID()); } @@ -309,7 +309,7 @@ pointer_t FUNCTION_ATTR_ROM Qhal_tcpClientInit(quint8_t *type, const char *hostn flag = fcntl(sockFd, F_GETFL, 0); //获取文件的flags值。 fcntl(sockFd, F_SETFL, flag | ~O_NONBLOCK); //设置成阻塞模式; lwip_freeaddrinfo(dns); - if(0 >= Helios_Thread_Create(&TcpRecvTask)) + if(0 == Helios_Thread_Create(&TcpRecvTask)) { close(sockFd); return SOCKET_FD_INVALID; @@ -345,7 +345,7 @@ static void qhal_SockTcpTlsRecvTask(void *arg) int len = mbedtls_ssl_read(&sockSslCtx->ssl_ctx, buf, sizeof(buf)); if (len > 0) { - Quos_socketIORx((pointer_t)sockSslCtx, SOCKET_TYPE_TCP_SSL_CLI, NULL, buf, len); + Quos_socketIORx((pointer_t)sockSslCtx, SOCKET_TYPE_TCP_SSL_CLI, NULL, 0, buf, len); } else if (MBEDTLS_ERR_SSL_WANT_READ != len && MBEDTLS_ERR_SSL_WANT_WRITE != len) { @@ -354,7 +354,7 @@ static void qhal_SockTcpTlsRecvTask(void *arg) } Quos_logPrintf(HAL_TCP, LL_INFO,"tcp q"); - Quos_socketIORx((pointer_t)sockSslCtx, SOCKET_TYPE_TCP_SSL_CLI, NULL, NULL, 0); + Quos_socketIORx((pointer_t)sockSslCtx, SOCKET_TYPE_TCP_SSL_CLI, NULL, 0, NULL, 0); HAL_FREE(sockSslCtx); Helios_Thread_Delete(Helios_Thread_GetID()); } @@ -470,7 +470,7 @@ pointer_t FUNCTION_ATTR_ROM Qhal_tcpSslClientInit(quint8_t *type, const char *ho TcpTlsRecvTask.entry = qhal_SockTcpTlsRecvTask; TcpTlsRecvTask.priority = QHAL_APP_TASK_PRIORITY+2; TcpTlsRecvTask.stack_size = 1024*10; - if(0 >= Helios_Thread_Create(&TcpTlsRecvTask)) + if(0 == Helios_Thread_Create(&TcpTlsRecvTask)) { Quos_logPrintf(HAL_TLS, LL_ERR, "pthread fail"); goto exit; @@ -508,3 +508,30 @@ pointer_t FUNCTION_ATTR_ROM Qhal_udpSslInit(quint8_t *type, quint16_t l_port, co UNUSED(r_port); return SOCKET_FD_INVALID; } + +/************************************************************************** +** 锟斤拷锟斤拷 @brief : 锟斤拷锟斤拷URL锟斤拷锟斤拷为IP +** 锟斤拷锟斤拷 @param : hostname: 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷 + retAddr锟斤拷锟斤拷锟斤拷锟斤拷锟侥碉拷锟绞絀P锟叫憋拷锟斤拷锟斤拷锟絀P锟斤拷锟饺诧拷锟斤拷锟斤拷46 + addrMax锟斤拷锟斤拷锟缴斤拷锟杰碉拷IP锟斤拷锟斤拷 +** 锟斤拷锟 @retval: 锟斤拷锟斤拷锟斤拷实锟斤拷锟杰斤拷锟斤拷锟斤拷IP锟斤拷锟斤拷 +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Qhal_dns2IPGet(const char *hostname, quint8_t **retAddr, quint32_t addrMax) +{ + struct hostent *host = NULL; + if (NULL == (host = gethostbyname(hostname))) + { + return 0; + } + quint32_t i; + for (i = 0; i < addrMax; i++) + { + if (NULL == host->h_addr_list[i]) + { + break; + } + HAL_STRCPY(retAddr[i], inet_ntoa(*(struct in_addr *)host->h_addr_list[i])); + } + return i; +} + diff --git a/driverLayer/qhal_Socket.h b/driverLayer/qhal_Socket.h index 775cade..0533175 100644 --- a/driverLayer/qhal_Socket.h +++ b/driverLayer/qhal_Socket.h @@ -19,4 +19,6 @@ pointer_t Qhal_tcpSslClientInit(quint8_t *type, const char *hostname, quint16_t pointer_t Qhal_udpSslInit(quint8_t *type, quint16_t l_port, const char *hostname, quint16_t r_port); qbool Qhal_sockWrite(pointer_t sockFd, quint8_t type, const void *peer, const quint8_t *buf, quint16_t bufLen, qbool *isSending); void Qhal_sockClose(pointer_t sockFd, quint8_t type); -#endif \ No newline at end of file + +quint32_t FUNCTION_ATTR_ROM Qhal_dns2IPGet(const char *hostname, quint8_t **retAddr, quint32_t addrMax); +#endif diff --git a/driverLayer/qhal_property.c b/driverLayer/qhal_property.c index d8998c2..f210215 100644 --- a/driverLayer/qhal_property.c +++ b/driverLayer/qhal_property.c @@ -217,7 +217,7 @@ quint32_t Qhal_propertyLocSupList(char **words, quint32_t maxSize) { UNUSED(maxSize); quint32_t i = 0; - words[i++] = QIOT_LOC_SUPPORT_NONE; + words[i++] = "NONE"; // words[i++] = QIOT_LOC_SUPPORT_AUTO; words[i++] = QIOT_LOC_SUPPORT_LBS; return i; diff --git a/driverLayer/qhal_property.h b/driverLayer/qhal_property.h index 2650a1d..f2b36d2 100644 --- a/driverLayer/qhal_property.h +++ b/driverLayer/qhal_property.h @@ -6,7 +6,6 @@ extern "C" { #endif -#define QIOT_LOC_SUPPORT_NONE "NONE" #define QIOT_LOC_SUPPORT_AUTO "AUTO" #define QIOT_LOC_SUPPORT_LBS "LBS" typedef struct diff --git a/kernel/Quos_kernel.c b/kernel/Quos_kernel.c new file mode 100644 index 0000000..ba345c5 --- /dev/null +++ b/kernel/Quos_kernel.c @@ -0,0 +1,50 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "Quos_kernel.h" +#include "Qhal_driver.h" + +static qbool QuosKernelIsResume = FALSE; +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_kernelInit(void) +{ + HAL_LOCK_INIT(lockLogId); + Quos_swTimerInit(); + Quos_sysTickInit(); + Quos_socketInit(); + Quos_signalInit(); +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_kernelResume(void) +{ + Qhal_KernelResume(); + QuosKernelIsResume = TRUE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_kernelTask(void) +{ + QuosKernelIsResume = FALSE; + quint32_t swTimerIdle = Quos_swTimerTask(); + qbool sigRet = Quos_signalTask(); + quint32_t sockIdle = Quos_socketTask(); + + swTimerIdle = sigRet ? 0 : (swTimerIdle < sockIdle ? swTimerIdle : sockIdle); + return QuosKernelIsResume ? 0 : swTimerIdle; +} diff --git a/kernel/Quos_kernel.h b/kernel/Quos_kernel.h index 743831d..6d759e3 100644 --- a/kernel/Quos_kernel.h +++ b/kernel/Quos_kernel.h @@ -1,27 +1,28 @@ -#ifndef __QUOS_KERNEL_H__ -#define __QUOS_KERNEL_H__ -#include "quos_log.h" -#include "quos_signal.h" -#include "quos_event.h" -#include "quos_SupportTool.h" -#include "quos_swTimer.h" -#include "quos_sysTick.h" -#include "quos_twll.h" -#include "quos_aes.h" -#include "quos_md5.h" -#include "quos_sha1.h" -#include "quos_sha256.h" -#include "quos_base64.h" -#include "quos_dataStore.h" -#include "quos_socket.h" -#include "quos_http.h" -#include "quos_mqtt.h" -#include "quos_coap.h" -#include "quos_lwm2m.h" -#include "quos_fifo.h" -#include "quos_net.h" -#include "quos_cjson.h" - -void Quos_kernelInit(void); -quint32_t Quos_kernelTask(void); +#ifndef __QUOS_KERNEL_H__ +#define __QUOS_KERNEL_H__ +#include "quos_log.h" +#include "quos_signal.h" +#include "quos_event.h" +#include "quos_SupportTool.h" +#include "quos_swTimer.h" +#include "quos_sysTick.h" +#include "quos_twll.h" +#include "quos_aes.h" +#include "quos_md5.h" +#include "quos_sha1.h" +#include "quos_sha256.h" +#include "quos_base64.h" +#include "quos_dataStore.h" +#include "quos_socket.h" +#include "quos_http.h" +#include "quos_mqtt.h" +#include "quos_coap.h" +#include "quos_lwm2m.h" +#include "quos_fifo.h" +#include "quos_net.h" +#include "quos_cjson.h" + +void Quos_kernelInit(void); +quint32_t Quos_kernelTask(void); +void Quos_kernelResume(void); #endif \ No newline at end of file diff --git a/kernel/quos_SupportTool.c b/kernel/quos_SupportTool.c new file mode 100644 index 0000000..1e42bc7 --- /dev/null +++ b/kernel/quos_SupportTool.c @@ -0,0 +1,535 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 工具类API和宏定义 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_SupportTool.h" +#include "quos_md5.h" +#include "Qhal_driver.h" +/************************************************************************** +** 功能 @brief : 得到value以exp为底的指数. +** 输入 @param : value > 0 exp > 1 +** 输出 @retval: 以exp为底的指数值 +***************************************************************************/ +quint8_t FUNCTION_ATTR_ROM Quos_convertToExp(quint32_t value, quint32_t exp) +{ + quint8_t count = 0; + if (exp <= 1) + { + return 0; + } + + while (value) + { + value = value / exp; + count++; + } + return count - 1; +} + +/************************************************************************** +** 功能 @brief : 将字节流转为字符串 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_hex2Str(quint8_t hex[], quint32_t hexLen, char *retStr, qbool isUpper) +{ + char Hex2strLBuf[] = "0123456789abcdef"; + char Hex2strUBuf[] = "0123456789ABCDEF"; + quint32_t i; + if (isUpper) + { + for (i = 0; i < hexLen; i++) + { + retStr[i << 1] = Hex2strUBuf[hex[i] >> 4]; + retStr[(i << 1) + 1] = Hex2strUBuf[hex[i] & 0x0F]; + } + } + else + { + for (i = 0; i < hexLen; i++) + { + retStr[i << 1] = Hex2strLBuf[hex[i] >> 4]; + retStr[(i << 1) + 1] = Hex2strLBuf[hex[i] & 0x0F]; + } + } + retStr[i << 1] = 0; + return hexLen * 2; +} + +/************************************************************************** +** 功能 @brief : 将字符串转为字节流 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_str2Hex(void *srcStr, quint8_t retHex[]) +{ + quint16_t i; + char *str = (char *)srcStr; + quint16_t strLen = HAL_STRLEN(str); + for (i = 0; i < strLen; i++) + { + if (str[i] >= 'A' && str[i] <= 'F') + { + if (i % 2) + retHex[i >> 1] |= (str[i] - 'A' + 10); + else + retHex[i >> 1] = (str[i] - 'A' + 10) << 4; + } + else if (str[i] >= 'a' && str[i] <= 'f') + { + if (i % 2) + retHex[i >> 1] |= (str[i] - 'a' + 10); + else + retHex[i >> 1] = (str[i] - 'a' + 10) << 4; + } + else if (str[i] >= '0' && str[i] <= '9') + { + if (i % 2) + retHex[i >> 1] |= (str[i] - '0'); + else + retHex[i >> 1] = (str[i] - '0') << 4; + } + else + { + return 0; + } + } + return (i + 1) / 2; +} + +/************************************************************************** +** 功能 @brief : crc计算 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_crcCalculate(quint32_t crc, const void *buf, quint32_t len) +{ + quint8_t *dat = (quint8_t *)buf; + while (len--) + { + crc += *dat++; + } + return crc; +} +/************************************************************************** +** 功能 @brief : KEY-VALUE提取数据 +** 输入 @param : srcStr:key-value键值对源字符串 + keyword关键字,包括key-value分隔符 + dstStr输出buffer + dstLenMax 输出buffer最大长度,value值超出这长度视为无效 + endStr key-value结束分隔符,srcStr为多组kv时,endStr必须为非NULL + eg:Quos_keyValueExtract("qq:1;ww:2;ee:3","ww:",val,30,";")提取key为ww的val是"2" +** 输出 @retval: +***************************************************************************/ +qint32_t FUNCTION_ATTR_ROM Quos_keyValueExtract(char *srcStr, const char *keyword, const char *separator, char **dstStr, const char *endStr) +{ + if (NULL == srcStr || NULL == keyword || NULL == separator || NULL == dstStr) + { + return -1; + } + char *head = (char *)srcStr; + while (NULL != (head = HAL_STRSTR(head, keyword))) + { + if (HAL_STRNCMP(head + HAL_STRLEN(keyword), separator, HAL_STRLEN(separator))) + { + /* do no */ + } + else if (head != srcStr && NULL == endStr) + { + return 0; + } + else if (head == srcStr || ((quint32_t)(head - srcStr) >= (quint32_t)HAL_STRLEN(endStr) && 0 == HAL_STRNCMP(&head[0 - HAL_STRLEN(endStr)], endStr, HAL_STRLEN(endStr)))) + { + break; + } + head++; + } + if (NULL == head) + { + return -1; + } + char *tail = NULL; + head += HAL_STRLEN(keyword) + HAL_STRLEN(separator); + if (endStr) + { + tail = HAL_STRSTR(head, endStr); + } + qint32_t valueLen = tail ? (quint32_t)(tail - head) : HAL_STRLEN(head); + *dstStr = head; + return valueLen; +} +/************************************************************************** +** 功能 @brief : KEY-VALUE增删改 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_keyValueInsert(char *srcStr, quint32_t maxLen, const char *keyword, const char *separator, const char *value, char *endStr) +{ + if (NULL == srcStr || NULL == keyword || 0 == HAL_STRLEN(keyword) || NULL == separator) + { + return FALSE; + } + char *head = srcStr; + + /* 查找keyword是否存在 */ + while (NULL != (head = HAL_STRSTR(head, keyword))) + { + if (0 != HAL_STRNCMP(head + HAL_STRLEN(keyword), separator, HAL_STRLEN(separator))) /* 并非完整keyword,如何在字段中abcd:1查找keyword为abc, 由于匹配到abc后面并非是:,所以匹配错误 */ + { + head++; + } + else if (head != srcStr && NULL == endStr) + { + srcStr[0] = 0; + head = NULL; + break; + } + else if (head == srcStr || ((quint32_t)(head - srcStr) >= (quint32_t)HAL_STRLEN(endStr) && 0 == HAL_STRNCMP(&head[0 - HAL_STRLEN(endStr)], endStr, HAL_STRLEN(endStr)))) + { + qint32_t valueLen = HAL_STRLEN(head); + if (endStr) + { + char *tail = HAL_STRSTR(head + HAL_STRLEN(keyword) + HAL_STRLEN(separator), endStr); + while (tail) + { + tail += HAL_STRLEN(endStr); + valueLen = tail - head; + if (0 == HAL_STRLEN(tail) || 0 != HAL_STRNCMP(tail, endStr, HAL_STRLEN(endStr))) + { + break; + } + } + } + HAL_MEMMOVE(head, head + valueLen, HAL_STRLEN(head + valueLen) + 1); + } + else + { + head++; + } + } + if (endStr && HAL_STRLEN(srcStr) >= HAL_STRLEN(endStr) && 0 == HAL_STRNCMP(srcStr + HAL_STRLEN(srcStr) - HAL_STRLEN(endStr), endStr, HAL_STRLEN(endStr))) + { + srcStr[HAL_STRLEN(srcStr) - HAL_STRLEN(endStr)] = 0; + } + + if (value && HAL_STRLEN(value) > 0) + { + if (HAL_STRLEN(srcStr) + HAL_STRLEN(keyword) + HAL_STRLEN(separator) + HAL_STRLEN(value) + HAL_STRLEN(endStr) >= maxLen) + { + return FALSE; + } + HAL_SPRINTF(srcStr + HAL_STRLEN(srcStr), "%s%s%s%s", (HAL_STRLEN(srcStr) > 0 && endStr) ? endStr : "", keyword, separator, value); + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 字符串分解 +** 输入 @param : srcStr源字符串,内容将会改变 + words分解字符串指针 + maxSize最大支持分解提取个数 + delim分隔符字符串 +** 输出 @retval: 分解提取的个数 +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_stringSplit(char *src, quint32_t srcLen, char **words, quint32_t maxSize, const char *delim, qbool keepEmptyParts) +{ + char *start = src; + quint32_t num = 0; + if (NULL == delim) + { + return 0; + } + quint32_t delimLen = HAL_STRLEN(delim); + while (maxSize > num && src && (quint32_t)(src-start) 1 && '0' == src[0])) + { + return FALSE; + } + if (value) + { + *value = 0; + } + for (i = 0; i < len; i++) + { + if (src[i] < '0' || src[i] > '9') + { + return FALSE; + } + else if (value) + { + *value = (*value) * 10 + src[i] - '0'; + } + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : url解析 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_urlAnalyze(const char *url, urlAnalyze_t *result) +{ + char *tempUrl; + quint8_t i; + HAL_MEMSET(result, 0, sizeof(urlAnalyze_t)); + if (NULL == url || 0 == HAL_STRLEN(url)) + { + return FALSE; + } + if (HAL_STRSTR(url, "s://") || HAL_STRSTR(url, "S://")) + { + result->isSecure = TRUE; + } + else + { + result->isSecure = FALSE; + } + + tempUrl = HAL_STRSTR(url, "://"); + tempUrl = tempUrl ? (tempUrl + HAL_STRLEN("://")) : (char *)url; + + i = 0; + while ('\0' != *tempUrl && ':' != *tempUrl && '/' != *tempUrl && i < QUOS_DNS_HOSTNANE_MAX_LENGHT) + { + result->hostname[i++] = *tempUrl++; + } + if (QUOS_DNS_HOSTNANE_MAX_LENGHT == i || i < 4 || (NULL == HAL_STRCHR(result->hostname, '.') && NULL == HAL_STRCHR(result->hostname, ':'))) + { + return FALSE; + } + result->port = 0; + if (':' == *tempUrl) + { + result->port = HAL_ATOI(tempUrl + 1); + } + tempUrl = HAL_STRSTR(tempUrl, "/"); + result->path = tempUrl ? tempUrl + 1 : NULL; + return TRUE; +} +/************************************************************************** +** 功能 @brief : IP字符串转数值 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_ip2Int(const char *ipStr) +{ + quint32_t ipInt = 0; + quint8_t dots = 0; + quint32_t secValue = 0; + if (NULL == ipStr || *ipStr < '0' || *ipStr > '9') + { + return 0; + } + while (ipStr && *ipStr >= '0' && *ipStr <= '9') + { + secValue = secValue * 10 + (*ipStr++) - '0'; + if (secValue > 255) + { + return 0; + } + if (*ipStr == '.') + { + ipStr++; + dots++; + ipInt = ipInt * 256 + secValue; + secValue = 0; + } + else if (*ipStr == 0 && 3 == dots) + { + return ipInt * 256 + secValue; + } + } + return 0; +} + +/************************************************************************** +** 功能 @brief : strtoul函数重构 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint64_t FUNCTION_ATTR_RAM Quos_strtoul(const char *cp, char **endp, quint32_t base) +{ + unsigned long result = 0, value; + if (!base) + { + base = 10; + if (*cp == '0') + { + base = 8; + cp++; + if ((__TO_LOWER(*cp) == 'x') && __IS_XDIGIT(cp[1])) + { + cp++; + base = 16; + } + } + } + else if (base == 16) + { + if (cp[0] == '0' && __TO_LOWER(cp[1]) == 'x') + cp += 2; + } + while (__IS_XDIGIT(*cp) && (value = __IS_DIGIT(*cp) ? *cp - '0' : __TO_LOWER(*cp) - 'a' + 10) < base) + { + result = result * base + value; + cp++; + } + if (endp) + *endp = (char *)cp; + return result; +} +qint64_t FUNCTION_ATTR_RAM Quos_strtol(const char *cp, char **endp, quint32_t base) +{ + if (*cp == '-') + return -Quos_strtoul(cp + 1, endp, base); + return Quos_strtoul(cp, endp, base); +} +/************************************************************************** +** 功能 @brief : 计算文件MD5 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_fileMd5(const char *filename, quint32_t fileLen, char md5[32 + 1]) +{ + md5_state_t md5State; + quint32_t i; + Quos_md5Init(&md5State); + pointer_t fileFd = Qhal_fileOpen(filename, TRUE); + if (SOCKET_FD_INVALID == fileFd) + { + return FALSE; + } + for (i = 0; i < fileLen;) + { + quint8_t tmpBuf[256]; + quint16_t len = i + sizeof(tmpBuf) > fileLen ? fileLen - i : sizeof(tmpBuf); + if (len != Qhal_fileRead(fileFd, i, tmpBuf, len)) + { + Qhal_fileClose(fileFd); + return FALSE; + } + i += len; + Quos_md5Append(&md5State, (const quint8_t *)tmpBuf, len); + Qhal_devFeeddog(); + } + Qhal_fileClose(fileFd); + quint8_t md5Hex[16]; + HAL_MEMSET(md5Hex, 0, sizeof(md5Hex)); + Quos_md5Finish(&md5State, md5Hex); + Quos_hex2Str(md5Hex, sizeof(md5Hex), md5, FALSE); + return TRUE; +} + +/************************************************************************** +** 功能 @brief : 数值压缩push到数组 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_intPushArray(quint64_t intValue, quint8_t *array) +{ + quint32_t i; + quint8_t temp[8]; + _U64_ARRAY01234567(intValue, temp); + for (i = 0; i < sizeof(temp) - 1; i++) + { + if (0 != temp[i]) + { + break; + } + } + HAL_MEMCPY(array, &temp[i], sizeof(temp) - i); + return sizeof(temp) - i; +} +/************************************************************************** + ** 功能 @brief : 字符串若存在双引号则去掉,strVal内容将会改变 + ** 输入 @param : + ** 输出 @retval: + ***************************************************************************/ +char FUNCTION_ATTR_ROM *Quos_stringRemoveMarks(char *strVal) +{ + quint32_t len = HAL_STRLEN(strVal); + if ('"' == strVal[0] && '"' == strVal[len - 1]) + { + quint32_t i; + for (i = 0; i < len - 2; i++) + { + strVal[i] = strVal[i + 1]; + } + strVal[len - 2] = 0; + } + return strVal; +} +/************************************************************************** +** 功能 @brief : 生成随机码,暂定0-9a-zA-Z,后期优化使其适配指定目标 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_RandomGen(quint8_t *random, quint32_t len) +{ + quint32_t i; + for (i = 0; i < len; i++) + { + random[i] = Qhal_randomGet() % (10 + 26 + 26); + if (random[i] < 10) + random[i] += '0'; + else if (random[i] < 10 + 26) + random[i] += 'A' - 10; + else + random[i] += 'a' - 36; + } +} +/************************************************************************** +** 功能 @brief : 浮点数转为无符号整数,并返回小数位数 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint8_t FUNCTION_ATTR_ROM Quos_doubleToUInt(double value,quint64_t *IntValue,qbool *negative) +{ + if(value < 0) + { + value = 0 - value; + *negative = TRUE; + } + else + { + *negative = FALSE; + } + char strBuf[22]; + int valueBufLen = HAL_SNPRINTF(strBuf,sizeof(strBuf),"%1.15g",value); + char *decimalPoint = HAL_STRSTR(strBuf,"."); + quint8_t decimals = 0; + if(decimalPoint != NULL) + { + decimals = valueBufLen - (decimalPoint - strBuf) - 1; + HAL_MEMMOVE(decimalPoint,decimalPoint+1,decimals); + strBuf[valueBufLen-1] = '\0'; + } + *IntValue = HAL_STRTOUL(strBuf, NULL, 10); + return decimals; +} diff --git a/kernel/quos_SupportTool.h b/kernel/quos_SupportTool.h index fbe2451..272f9d0 100644 --- a/kernel/quos_SupportTool.h +++ b/kernel/quos_SupportTool.h @@ -1,137 +1,140 @@ -#ifndef __QUOS_SUPPORTTOOL_H__ -#define __QUOS_SUPPORTTOOL_H__ -#include "quos_config.h" -#ifdef __cplusplus -extern "C" -{ -#endif - - typedef struct - { - char *path; /* path指向url的地址 */ - char hostname[QUOS_DNS_HOSTNANE_MAX_LENGHT]; - qbool isSecure; - quint16_t port; - } urlAnalyze_t; - -#define __ENDIANCHANGE(x) ((sizeof(x) == 2) ? (quint16_t)((x >> 8) | (x << 8)) : ((sizeof(x) == 4) ? (quint32_t)((((x) >> 24) & 0x000000FF) | (((x) >> 8) & 0x0000FF00) | (((x) << 8) & 0x00FF0000) | (((x) << 24) & 0xFF000000)) : (x))) - -/* 字节对齐 */ -#define __BYTE_TO_ALIGN(X, Y) ((X) % (Y) ? ((X) + (Y) - (X) % (Y)) : (X)) - -/*大小写转换 */ -#define __TO_UPPER(X) ((X) & (~0x20)) -#define __TO_LOWER(X) ((X) | 0x20) - -#define __IS_DIGIT(X) ('0' <= (X) && (X) <= '9') -#define __IS_XDIGIT(X) (('0' <= (X) && (X) <= '9') || ('a' <= (X) && (X) <= 'f') || ('A' <= (X) && (X) <= 'F')) - -/*求最大值和最小值 */ -#define __GET_MAX(x, y) (((x) > (y)) ? (x) : (y)) -#define __GET_MIN(x, y) (((x) < (y)) ? (x) : (y)) - -/*得到一个field在结构体(struct)中的偏移量 */ -#define __GET_POS_ELEMENT(type, field) ((pointer_t) & (((type *)0)->field)) -/*得到一个结构体中field所占用的字节数 */ -#define __GET_SIZE_ELEMENT(type, field) sizeof(((type *)0)->field) -/*根据元素地址得到结构体 */ -#define __GET_STRUCT_BY_ELEMENT(ptr, type, field) ((type *)((char *)ptr - __GET_POS_ELEMENT(type, field))) -/*返回一个比X小的最接近的n的倍数 */ -#define __GET_SMALL_N(x, n) ((x) / (n) * (n)) - -/*返回一个比X大的最接近的n的倍数 */ -#define __GET_BIG_N(x, n) (((x) + (n)-1) / (n) * (n)) - -/* 转换宏为字符串 */ -#define _MACRO2STR_1(s) #s -#define _MACRO2STR_2(s) _MACRO2STR_1(s) - -/* 字符串拼接 */ -#define _STRCAT_STR_1(A, B) A##B -#define _STRCAT_STR_2(A, B) _STRCAT_STR_1(A, B) - -#define _BOOL2STR(X) ((X) ? "TRUE" : "FALSE") /* qbool 转字符串 */ -#define _STR2BOOL(X) (0 == HAL_STRCASECMP(X, "TRUE") ? TRUE : FALSE) - -#define _DATA2BOOL(X, Y) (((X >> Y) & 1) ? TRUE : FALSE) - -#define _ARRAY01_U16(ARRAY) (((quint16_t)(((quint8_t *)(ARRAY))[0]) << 8) | \ - ((quint16_t)(((quint8_t *)(ARRAY))[1]) << 0)) - -#define _ARRAY10_U16(ARRAY) (((quint16_t)(((quint8_t *)(ARRAY))[1]) << 8) | \ - ((quint16_t)(((quint8_t *)(ARRAY))[0]) << 0)) - -#define _ARRAY0123_U32(ARRAY) (((quint32_t)(((quint8_t *)(ARRAY))[0]) << 24) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[1]) << 16) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[2]) << 8) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[3]) << 0)) - -#define _ARRAY1032_U32(ARRAY) (((quint32_t)(((quint8_t *)(ARRAY))[1]) << 24) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[0]) << 16) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[3]) << 8) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[2]) << 0)) - -#define _ARRAY3210_U32(ARRAY) (((quint32_t)(((quint8_t *)(ARRAY))[3]) << 24) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[2]) << 16) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[1]) << 8) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[0]) << 0)) - -#define _ARRAY2301_U32(ARRAY) (((quint32_t)(((quint8_t *)(ARRAY))[2]) << 24) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[3]) << 16) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[0]) << 8) | \ - ((quint32_t)(((quint8_t *)(ARRAY))[1]) << 0)) - -#define _ARRAY76543210_U64(ARRAY) (((quint64_t)(((quint8_t *)(ARRAY))[7]) << 56) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[6]) << 48) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[5]) << 40) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[4]) << 32) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[3]) << 24) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[2]) << 16) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[1]) << 8) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[0]) << 0)) - -#define _ARRAY012345678_U64(ARRAY) (((quint64_t)(((quint8_t *)(ARRAY))[0]) << 56) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[1]) << 48) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[2]) << 40) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[3]) << 32) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[4]) << 24) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[4]) << 16) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[6]) << 8) | \ - ((quint64_t)(((quint8_t *)(ARRAY))[7]) << 0)) - -#define _U16_ARRAY01(INT, ARRAY) (((quint8_t *)ARRAY)[0] = ((INT) >> 8) & 0xFF, ((quint8_t *)ARRAY)[1] = (INT)&0xFF) -#define _U16_ARRAY10(INT, ARRAY) (((quint8_t *)ARRAY)[0] = (INT)&0xFF, ((quint8_t *)ARRAY)[1] = ((INT) >> 8) & 0xFF) -#define _U32_ARRAY0123(INT, ARRAY) (((quint8_t *)ARRAY)[0] = ((INT) >> 24) & 0xFF, ((quint8_t *)ARRAY)[1] = ((INT) >> 16) & 0xFF, ((quint8_t *)ARRAY)[2] = ((INT) >> 8) & 0xFF, ((quint8_t *)ARRAY)[3] = (INT)&0xFF) -#define _U32_ARRAY3210(INT, ARRAY) (((quint8_t *)ARRAY)[0] = (INT)&0xFF, ((quint8_t *)ARRAY)[1] = ((INT) >> 8) & 0xFF, ((quint8_t *)ARRAY)[2] = ((INT) >> 16) & 0xFF, ((quint8_t *)ARRAY)[3] = ((INT) >> 24) & 0xFF) -#define _U64_ARRAY01234567(INT, ARRAY) (((quint8_t *)ARRAY)[0] = ((INT) >> 56) & 0xFF, ((quint8_t *)ARRAY)[1] = ((INT) >> 48) & 0xFF, ((quint8_t *)ARRAY)[2] = ((INT) >> 40) & 0xFF, ((quint8_t *)ARRAY)[3] = ((INT) >> 32) & 0xFF, \ - ((quint8_t *)ARRAY)[4] = ((INT) >> 24) & 0xFF, ((quint8_t *)ARRAY)[5] = ((INT) >> 16) & 0xFF, ((quint8_t *)ARRAY)[6] = ((INT) >> 8) & 0xFF, ((quint8_t *)ARRAY)[7] = ((INT) >> 0) & 0xFF) -#define _U64_ARRAY76543210(INT, ARRAY) (((quint8_t*)ARRAY)[0] = (INT)&0xFF,((quint8_t*)ARRAY)[1] = ((INT)>>8)&0xFF,((quint8_t*)ARRAY)[2] = ((INT)>>16)&0xFF,((quint8_t*)ARRAY)[3] = ((INT)>>24)&0xFF, \ - (((quint8_t*)ARRAY)[4] = ((INT)>>32)&0xFF,((quint8_t*)ARRAY)[5] = ((INT)>>40)&0xFF,((quint8_t*)ARRAY)[6] = ((INT)>>48)&0xFF,((quint8_t*)ARRAY)[7] = ((INT)>>56)&0xFF) - -#define IP2STR "%hhu.%hhu.%hhu.%hhu" -#define IP2STR_(IP) (quint8_t)((IP) >> 24), (quint8_t)((IP) >> 16), (quint8_t)((IP) >> 8), (quint8_t)((IP) >> 0) - -#define TIME_SEC2UTC "%08d.%02d.%02d" -#define TIME_SEC2UTC_(SEC) (quint32_t)(SEC / 3600), (quint8_t)(SEC % 3600 / 60), (quint8_t)(SEC % 60) - - quint32_t Quos_hex2Str(quint8_t hex[], quint32_t hexLen, char *retStr, qbool isUpper); - quint32_t Quos_str2Hex(void *srcStr, quint8_t RetHex[]); - quint8_t Quos_convertToExp(quint32_t value, quint32_t exp); - quint32_t Quos_crcCalculate(quint32_t crc, void *buf, quint32_t len); - qint32_t Quos_keyValueExtract(char *srcStr, const char *keyword, const char *separator, char **dstStr, const char *endStr); - qbool Quos_keyValueInsert(char *srcStr, quint32_t maxLen, const char *keyword, const char *separator, const char *value, char *endStr); - quint32_t Quos_stringSplit(char *src, char **words, quint32_t maxSize, char *delim, qbool keepEmptyParts); - qbool Quos_strIsUInt(char *src, quint32_t len, quint32_t *value); - qbool Quos_urlAnalyze(const char *url, urlAnalyze_t *result); - quint32_t Quos_ip2Int(const char *ipStr); - quint64_t Quos_strtoul(const char *cp, char **endp, quint32_t base); - qint64_t Quos_strtol(const char *cp, char **endp, quint32_t base); - qbool Quos_fileMd5(const char *filename, quint32_t fileLen, char md5[]); - quint32_t Quos_intPushArray(quint64_t intValue,quint8_t *array); - char *Quos_stringRemoveMarks(char *strVal); - -#ifdef __cplusplus -} -#endif - -#endif +#ifndef __QUOS_SUPPORTTOOL_H__ +#define __QUOS_SUPPORTTOOL_H__ +#include "quos_config.h" +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct + { + char *path; /* path指向url的地址 */ + char hostname[QUOS_DNS_HOSTNANE_MAX_LENGHT]; + qbool isSecure; + quint16_t port; + } urlAnalyze_t; + +#define __ENDIANCHANGE(x) ((sizeof(x) == 2) ? (quint16_t)((x >> 8) | (x << 8)) : ((sizeof(x) == 4) ? (quint32_t)((((x) >> 24) & 0x000000FF) | (((x) >> 8) & 0x0000FF00) | (((x) << 8) & 0x00FF0000) | (((x) << 24) & 0xFF000000)) : (x))) + +/* 字节对齐 */ +#define __BYTE_TO_ALIGN(X, Y) ((X) % (Y) ? ((X) + (Y) - (X) % (Y)) : (X)) + +/*大小写转换 */ +#define __TO_UPPER(X) ((X) & (~0x20)) +#define __TO_LOWER(X) ((X) | 0x20) + +#define __IS_LETTER(X) (('A' <= (X) && (X) <= 'Z') || ('a' <= (X) && (X) <= 'z')) +#define __IS_DIGIT(X) ('0' <= (X) && (X) <= '9') +#define __IS_XDIGIT(X) (('0' <= (X) && (X) <= '9') || ('a' <= (X) && (X) <= 'f') || ('A' <= (X) && (X) <= 'F')) + +/*求最大值和最小值 */ +#define __GET_MAX(x, y) (((x) > (y)) ? (x) : (y)) +#define __GET_MIN(x, y) (((x) < (y)) ? (x) : (y)) + +/*得到一个field在结构体(struct)中的偏移量 */ +#define __GET_POS_ELEMENT(type, field) ((pointer_t) & (((type *)0)->field)) +/*得到一个结构体中field所占用的字节数 */ +#define __GET_SIZE_ELEMENT(type, field) sizeof(((type *)0)->field) +/*根据元素地址得到结构体 */ +#define __GET_STRUCT_BY_ELEMENT(ptr, type, field) ((type *)((char *)ptr - __GET_POS_ELEMENT(type, field))) +/*返回一个比X小的最接近的n的倍数 */ +#define __GET_SMALL_N(x, n) ((x) / (n) * (n)) + +/*返回一个比X大的最接近的n的倍数 */ +#define __GET_BIG_N(x, n) (((x) + (n)-1) / (n) * (n)) + +/* 转换宏为字符串 */ +#define _MACRO2STR_1(s) #s +#define _MACRO2STR_2(s) _MACRO2STR_1(s) + +/* 字符串拼接 */ +#define _STRCAT_STR_1(A, B) A##B +#define _STRCAT_STR_2(A, B) _STRCAT_STR_1(A, B) + +#define _BOOL2STR(X) ((X) ? "TRUE" : "FALSE") /* qbool 转字符串 */ +#define _STR2BOOL(X) (0 == HAL_STRNCASECMP(X, "TRUE", __GET_MAX(HAL_STRLEN(X), HAL_STRLEN("TRUE"))) ? TRUE : FALSE) +#define _STR2PRINT(X) (NULL == X ? "null" : X) +#define _DATA2BOOL(X, Y) (((X >> Y) & 1) ? TRUE : FALSE) + +#define _ARRAY01_U16(ARRAY) (((quint16_t)(((quint8_t *)(ARRAY))[0]) << 8) | \ + ((quint16_t)(((quint8_t *)(ARRAY))[1]) << 0)) + +#define _ARRAY10_U16(ARRAY) (((quint16_t)(((quint8_t *)(ARRAY))[1]) << 8) | \ + ((quint16_t)(((quint8_t *)(ARRAY))[0]) << 0)) + +#define _ARRAY0123_U32(ARRAY) (((quint32_t)(((quint8_t *)(ARRAY))[0]) << 24) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[1]) << 16) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[2]) << 8) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[3]) << 0)) + +#define _ARRAY1032_U32(ARRAY) (((quint32_t)(((quint8_t *)(ARRAY))[1]) << 24) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[0]) << 16) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[3]) << 8) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[2]) << 0)) + +#define _ARRAY3210_U32(ARRAY) (((quint32_t)(((quint8_t *)(ARRAY))[3]) << 24) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[2]) << 16) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[1]) << 8) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[0]) << 0)) + +#define _ARRAY2301_U32(ARRAY) (((quint32_t)(((quint8_t *)(ARRAY))[2]) << 24) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[3]) << 16) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[0]) << 8) | \ + ((quint32_t)(((quint8_t *)(ARRAY))[1]) << 0)) + +#define _ARRAY76543210_U64(ARRAY) (((quint64_t)(((quint8_t *)(ARRAY))[7]) << 56) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[6]) << 48) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[5]) << 40) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[4]) << 32) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[3]) << 24) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[2]) << 16) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[1]) << 8) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[0]) << 0)) + +#define _ARRAY012345678_U64(ARRAY) (((quint64_t)(((quint8_t *)(ARRAY))[0]) << 56) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[1]) << 48) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[2]) << 40) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[3]) << 32) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[4]) << 24) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[4]) << 16) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[6]) << 8) | \ + ((quint64_t)(((quint8_t *)(ARRAY))[7]) << 0)) + +#define _U16_ARRAY01(INT, ARRAY) (((quint8_t *)ARRAY)[0] = ((INT) >> 8) & 0xFF, ((quint8_t *)ARRAY)[1] = (INT)&0xFF) +#define _U16_ARRAY10(INT, ARRAY) (((quint8_t *)ARRAY)[0] = (INT)&0xFF, ((quint8_t *)ARRAY)[1] = ((INT) >> 8) & 0xFF) +#define _U32_ARRAY0123(INT, ARRAY) (((quint8_t *)ARRAY)[0] = ((INT) >> 24) & 0xFF, ((quint8_t *)ARRAY)[1] = ((INT) >> 16) & 0xFF, ((quint8_t *)ARRAY)[2] = ((INT) >> 8) & 0xFF, ((quint8_t *)ARRAY)[3] = (INT)&0xFF) +#define _U32_ARRAY3210(INT, ARRAY) (((quint8_t *)ARRAY)[0] = (INT)&0xFF, ((quint8_t *)ARRAY)[1] = ((INT) >> 8) & 0xFF, ((quint8_t *)ARRAY)[2] = ((INT) >> 16) & 0xFF, ((quint8_t *)ARRAY)[3] = ((INT) >> 24) & 0xFF) +#define _U64_ARRAY01234567(INT, ARRAY) (((quint8_t *)ARRAY)[0] = ((INT) >> 56) & 0xFF, ((quint8_t *)ARRAY)[1] = ((INT) >> 48) & 0xFF, ((quint8_t *)ARRAY)[2] = ((INT) >> 40) & 0xFF, ((quint8_t *)ARRAY)[3] = ((INT) >> 32) & 0xFF, \ + ((quint8_t *)ARRAY)[4] = ((INT) >> 24) & 0xFF, ((quint8_t *)ARRAY)[5] = ((INT) >> 16) & 0xFF, ((quint8_t *)ARRAY)[6] = ((INT) >> 8) & 0xFF, ((quint8_t *)ARRAY)[7] = ((INT) >> 0) & 0xFF) +#define _U64_ARRAY76543210(INT, ARRAY) (((quint8_t*)ARRAY)[0] = (INT)&0xFF,((quint8_t*)ARRAY)[1] = ((INT)>>8)&0xFF,((quint8_t*)ARRAY)[2] = ((INT)>>16)&0xFF,((quint8_t*)ARRAY)[3] = ((INT)>>24)&0xFF, \ + (((quint8_t*)ARRAY)[4] = ((INT)>>32)&0xFF,((quint8_t*)ARRAY)[5] = ((INT)>>40)&0xFF,((quint8_t*)ARRAY)[6] = ((INT)>>48)&0xFF,((quint8_t*)ARRAY)[7] = ((INT)>>56)&0xFF) + +#define IP2STR "%hhu.%hhu.%hhu.%hhu" +#define IP2STR_(IP) (quint8_t)((IP) >> 24), (quint8_t)((IP) >> 16), (quint8_t)((IP) >> 8), (quint8_t)((IP) >> 0) + +#define TIME_SEC2UTC "%08d.%02d.%02d" +#define TIME_SEC2UTC_(SEC) (quint32_t)(SEC / 3600), (quint8_t)(SEC % 3600 / 60), (quint8_t)(SEC % 60) + + quint32_t Quos_hex2Str(quint8_t hex[], quint32_t hexLen, char *retStr, qbool isUpper); + quint32_t Quos_str2Hex(void *srcStr, quint8_t RetHex[]); + quint8_t Quos_convertToExp(quint32_t value, quint32_t exp); + quint32_t Quos_crcCalculate(quint32_t crc, const void *buf, quint32_t len); + qint32_t Quos_keyValueExtract(char *srcStr, const char *keyword, const char *separator, char **dstStr, const char *endStr); + qbool Quos_keyValueInsert(char *srcStr, quint32_t maxLen, const char *keyword, const char *separator, const char *value, char *endStr); + quint32_t Quos_stringSplit(char *src, quint32_t srcLen, char **words, quint32_t maxSize, const char *delim, qbool keepEmptyParts); + qbool Quos_strIsUInt(char *src, quint32_t len, quint32_t *value); + qbool Quos_urlAnalyze(const char *url, urlAnalyze_t *result); + quint32_t Quos_ip2Int(const char *ipStr); + quint64_t Quos_strtoul(const char *cp, char **endp, quint32_t base); + qint64_t Quos_strtol(const char *cp, char **endp, quint32_t base); + qbool Quos_fileMd5(const char *filename, quint32_t fileLen, char md5[]); + quint32_t Quos_intPushArray(quint64_t intValue, quint8_t *array); + char *Quos_stringRemoveMarks(char *strVal); + void Quos_RandomGen(quint8_t *random, quint32_t len); + quint8_t Quos_doubleToUInt(double value, quint64_t *IntValue, qbool *negative); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/kernel/quos_aes.c b/kernel/quos_aes.c new file mode 100644 index 0000000..ae54d2b --- /dev/null +++ b/kernel/quos_aes.c @@ -0,0 +1,555 @@ +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are QUOS_AES128, QUOS_AES192, QUOS_AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For QUOS_AES192/256 the key size is proportionally larger. + +*/ + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include "quos_aes.h" +#if (SDK_ENABLE_AES == 1) +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +/* The number of columns comprising a state in AES. This is a constant in AES. Value=4 */ +#define Nb 4 + +#if defined(QUOS_AES256) && (QUOS_AES256 == 1) +#define Nk 8 +#define Nr 14 +#elif defined(QUOS_AES192) && (QUOS_AES192 == 1) +#define Nk 6 +#define Nr 12 +#else +#define Nk 4 /* The number of 32 bit words in a key. */ +#define Nr 10 /* The number of rounds in AES Cipher. */ +#endif + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +/* state - array holding the intermediate results during decryption. */ +typedef quint8_t state_t[4][4]; + +/* The lookup-tables are marked const so they can be placed in read-only storage instead of RAM */ +/* The numbers below can be computed dynamically trading ROM for RAM - */ +/* This can be useful in (embedded) bootloader applications, where ROM is often limited. */ +static const quint8_t sbox[256] = { + /*0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16}; + +static const quint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d}; + +/* The round constant word array, Rcon[i], contains the values given by */ +/* x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) */ +static const quint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36}; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used 鈥 up to rcon[10]-for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +/* +static quint8_t getSBoxValue(quint8_t num) +{ + return sbox[num]; +} +*/ +#define getSBoxValue(num) (sbox[(num)]) +/* +static quint8_t getSBoxInvert(quint8_t num) +{ + return rsbox[num]; +} +*/ +#define getSBoxInvert(num) (rsbox[(num)]) + +/* This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. */ +static void KeyExpansion(quint8_t *RoundKey, const char *Key) +{ + unsigned i, j, k; + quint8_t tempa[4]; /* Used for the column/row operations */ + + /* The first round key is the key itself. */ + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + /* All other round keys are found from the previous round keys. */ + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0] = RoundKey[k + 0]; + tempa[1] = RoundKey[k + 1]; + tempa[2] = RoundKey[k + 2]; + tempa[3] = RoundKey[k + 3]; + } + + if (i % Nk == 0) + { + /* This function shifts the 4 bytes in a word to the left once. */ + /* [a0,a1,a2,a3] becomes [a1,a2,a3,a0] */ + + /* Function RotWord() */ + { + k = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = k; + } + + /* SubWord() is a function that takes a four-byte input word and */ + /* applies the S-box to each of the four bytes to produce an output word. */ + + /* Function Subword() */ + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i / Nk]; + } +#if defined(QUOS_AES256) && (QUOS_AES256 == 1) + if (i % Nk == 4) + { + /* Function Subword() */ + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } +#endif + j = i * 4; + k = (i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +void Quos_aesInitCtx(AES_ctx_t *ctx, const char *key) +{ + KeyExpansion(ctx->RoundKey, key); +} + +void Quos_aesInitCtxIv(AES_ctx_t *ctx, const char *key, const char *iv) +{ + KeyExpansion(ctx->RoundKey, key); + HAL_MEMCPY(ctx->Iv, iv, QUOS_AES_BLOCKLEN); +} +void Quos_aesCtxSetIv(AES_ctx_t *ctx, const char *iv) +{ + HAL_MEMCPY(ctx->Iv, iv, QUOS_AES_BLOCKLEN); +} + +/* This function adds the round key to state. */ +/* The round key is added to the state by an XOR function. */ +static void AddRoundKey(quint8_t round, state_t *state, quint8_t *RoundKey) +{ + quint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +/* The SubBytes Function Substitutes the values in the */ +/* state matrix with values in an S-box. */ +static void SubBytes(state_t *state) +{ + quint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +/* The ShiftRows() function shifts the rows in the state to the left. */ +/* Each row is shifted with different offset. */ +/* Offset = Row number. So the first row is not shifted. */ +static void ShiftRows(state_t *state) +{ + quint8_t temp; + + /* Rotate first row 1 columns to left */ + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + /* Rotate second row 2 columns to left */ + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + /* Rotate third row 3 columns to left */ + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +static quint8_t xtime(quint8_t x) +{ + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); +} + +/* MixColumns function mixes the columns of the state matrix */ +static void MixColumns(state_t *state) +{ + quint8_t i; + quint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3]; + Tm = (*state)[i][0] ^ (*state)[i][1]; + Tm = xtime(Tm); + (*state)[i][0] ^= Tm ^ Tmp; + Tm = (*state)[i][1] ^ (*state)[i][2]; + Tm = xtime(Tm); + (*state)[i][1] ^= Tm ^ Tmp; + Tm = (*state)[i][2] ^ (*state)[i][3]; + Tm = xtime(Tm); + (*state)[i][2] ^= Tm ^ Tmp; + Tm = (*state)[i][3] ^ t; + Tm = xtime(Tm); + (*state)[i][3] ^= Tm ^ Tmp; + } +} + +/* Multiply is used to multiply numbers in the field GF(2^8) */ + +#define Multiply(x, y) \ + (((y & 1) * x) ^ \ + ((y >> 1 & 1) * xtime(x)) ^ \ + ((y >> 2 & 1) * xtime(xtime(x))) ^ \ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x)))))) + +/* MixColumns function mixes the columns of the state matrix. */ +/* The method used to multiply may be difficult to understand for the inexperienced. */ +/* Please use the references to gain more information. */ +static void InvMixColumns(state_t *state) +{ + int i; + quint8_t a, b, c, d; + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + +/* The SubBytes Function Substitutes the values in the */ +/* state matrix with values in an S-box. */ +static void InvSubBytes(state_t *state) +{ + quint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +static void InvShiftRows(state_t *state) +{ + quint8_t temp; + + /* Rotate first row 1 columns to right */ + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + /* Rotate second row 2 columns to right */ + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + /* Rotate third row 3 columns to right */ + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} + +/* Cipher is the main function that encrypts the PlainText. */ +static void Cipher(state_t *state, quint8_t *RoundKey) +{ + quint8_t round = 0; + + /* Add the First round key to the state before starting the rounds. */ + AddRoundKey(0, state, RoundKey); + + /* There will be Nr rounds. */ + /* The first Nr-1 rounds are identical. */ + /* These Nr-1 rounds are executed in the loop below. */ + for (round = 1; round < Nr; ++round) + { + SubBytes(state); + ShiftRows(state); + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + + /* The last round is given below. */ + /* The MixColumns function is not here in the last round. */ + SubBytes(state); + ShiftRows(state); + AddRoundKey(Nr, state, RoundKey); +} + +static void InvCipher(state_t *state, quint8_t *RoundKey) +{ + quint8_t round = 0; + + /* Add the First round key to the state before starting the rounds. */ + AddRoundKey(Nr, state, RoundKey); + + /* There will be Nr rounds. */ + /* The first Nr-1 rounds are identical. */ + /* These Nr-1 rounds are executed in the loop below. */ + for (round = (Nr - 1); round > 0; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + InvMixColumns(state); + } + + /* The last round is given below. */ + /* The MixColumns function is not here in the last round. */ + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(0, state, RoundKey); +} + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ + +quint32_t Quos_aesEcbEncrypt(AES_ctx_t *ctx, const quint8_t *buf, quint32_t length) +{ + /* The next function call encrypts the PlainText with the Key using AES algorithm. */ + quint32_t i; + for (i = 0; i < length; i += QUOS_AES_BLOCKLEN) + { + Cipher((state_t *)buf, ctx->RoundKey); + buf += QUOS_AES_BLOCKLEN; + } + return i; +} + +void Quos_aesEcbDecrypt(AES_ctx_t *ctx, const quint8_t *buf, quint32_t length) +{ + /* The next function call decrypts the PlainText with the Key using AES algorithm. */ + quint32_t i; + for (i = 0; i < length; i += QUOS_AES_BLOCKLEN) + { + InvCipher((state_t *)buf, ctx->RoundKey); + buf += QUOS_AES_BLOCKLEN; + } +} + +static void XorWithIv(quint8_t *buf, quint8_t *Iv) +{ + quint8_t i; + for (i = 0; i < QUOS_AES_BLOCKLEN; ++i) /* The block in AES is always 128bit no matter the key size */ + { + buf[i] ^= Iv[i]; + } +} + +quint32_t Quos_aesCbcEncrypt(AES_ctx_t *ctx, void *buf, quint32_t length) +{ + quint32_t i; + quint8_t *data = (quint8_t *)buf; + quint8_t *Iv = ctx->Iv; + for (i = 0; i < length; i += QUOS_AES_BLOCKLEN) + { + XorWithIv(data, Iv); + Cipher((state_t *)data, ctx->RoundKey); + Iv = data; + data += QUOS_AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + HAL_MEMCPY(ctx->Iv, Iv, QUOS_AES_BLOCKLEN); + return i; +} + +void Quos_aesCbcDecrypt(AES_ctx_t *ctx, void *buf, quint32_t length) +{ + quint16_t i; + quint8_t *data = (quint8_t *)buf; + quint8_t storeNextIv[QUOS_AES_BLOCKLEN]; + for (i = 0; i < length; i += QUOS_AES_BLOCKLEN) + { + HAL_MEMCPY(storeNextIv, data, QUOS_AES_BLOCKLEN); + InvCipher((state_t *)data, ctx->RoundKey); + XorWithIv(data, ctx->Iv); + HAL_MEMCPY(ctx->Iv, storeNextIv, QUOS_AES_BLOCKLEN); + data += QUOS_AES_BLOCKLEN; + } +} + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void Quos_aesCtrXCrypt(AES_ctx_t *ctx, void *buf, quint32_t length) +{ + quint8_t buffer[QUOS_AES_BLOCKLEN]; + quint8_t *tempBuf = (quint8_t *)buf; + quint16_t i; + int bi; + for (i = 0, bi = QUOS_AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == QUOS_AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + HAL_MEMCPY(buffer, ctx->Iv, QUOS_AES_BLOCKLEN); + Cipher((state_t *)buffer, ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (QUOS_AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will owerflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + ctx->Iv[bi] += 1; + break; + } + bi = 0; + } + tempBuf[i] = (tempBuf[i] ^ buffer[bi]); + } +} +/************************************************************************** +** 鍔熻兘 @brief : pcks#7濉厖 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +void Quos_aesPadding(quint8_t *dest, quint8_t *src, quint32_t srcLen) +{ + quint32_t offset = QUOS_AES_BLOCKLEN - srcLen % QUOS_AES_BLOCKLEN; + HAL_MEMMOVE(dest, src, srcLen); + HAL_MEMSET(dest + srcLen, offset, offset); +} +/************************************************************************** +** 鍔熻兘 @brief : pcks#7鍙嶅~鍏 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +quint32_t Quos_aesPaddingBack(quint8_t *src, quint32_t srcLen) +{ + quint8_t i = 0; + if (0 == srcLen || (srcLen % QUOS_AES_BLOCKLEN) != 0 || src[srcLen - 1] > QUOS_AES_BLOCKLEN || 0 == src[srcLen - 1]) + { + return srcLen; + } + for (i = 1; i < QUOS_AES_BLOCKLEN; i++) + { + if (src[srcLen - 1 - i] != src[srcLen - i]) + { + break; + } + } + if (i == src[srcLen - 1]) + { + HAL_MEMSET(src + srcLen - i, 0, i); + return srcLen - i; + } + else + { + return srcLen; + } +} +#endif \ No newline at end of file diff --git a/kernel/quos_aes.h b/kernel/quos_aes.h index a9828d2..27d43e3 100644 --- a/kernel/quos_aes.h +++ b/kernel/quos_aes.h @@ -1,56 +1,56 @@ -#ifndef _AES_H_ -#define _AES_H_ -#include "quos_config.h" - -#if (SDK_ENABLE_AES == 1) -#define QUOS_AES128 1 -/*#define QUOS_AES192 1 */ -/*#define QUOS_AES256 1 */ - -#define QUOS_AES_BLOCKLEN 16 /*Block length in bytes AES is 128b block only */ - -#if defined(QUOS_AES256) && (QUOS_AES256 == 1) -#define QUOS_AES_KEYLEN 32 -#define QUOS_AES_keyExpSize 240 -#elif defined(QUOS_AES192) && (QUOS_AES192 == 1) -#define QUOS_AES_KEYLEN 24 -#define QUOS_AES_keyExpSize 208 -#else -#define QUOS_AES_KEYLEN 16 /* Key length in bytes */ -#define QUOS_AES_keyExpSize 176 -#endif - -typedef struct -{ - quint8_t RoundKey[QUOS_AES_keyExpSize]; - quint8_t Iv[QUOS_AES_BLOCKLEN]; -} AES_ctx_t; - -void Quos_aesInitCtx(AES_ctx_t *ctx, const char *key); -void Quos_aesInitCtxIv(AES_ctx_t *ctx, const char *key, const char *iv); -void Quos_aesCtxSetIv(AES_ctx_t *ctx, const char *iv); - -/* buffer size MUST be mutile of QUOS_AES_BLOCKLEN; */ -/* you need only Quos_aesInitCtx as IV is not used in ECB */ -/* NB: ECB is considered insecure for most uses */ -quint32_t Quos_aesEcbEncrypt(AES_ctx_t *ctx, const quint8_t *buf, quint32_t length); -void Quos_aesEcbDecrypt(AES_ctx_t *ctx, const quint8_t *buf, quint32_t length); - -/* buffer size MUST be mutile of QUOS_AES_BLOCKLEN; */ -/* Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme */ -/* NOTES: you need to set IV in ctx via Quos_aesInitCtxIv() or Quos_aesCtxSetIv() */ -/* no IV should ever be reused with the same key */ -void Quos_aesCbcEncrypt(AES_ctx_t *ctx, void *buf, quint32_t length); -void Quos_aesCbcDecrypt(AES_ctx_t *ctx, void *buf, quint32_t length); - -/* Same function for encrypting as for decrypting. */ -/* IV is incremented for every block, and used after encryption as XOR-compliment for output */ -/* Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme */ -/* NOTES: you need to set IV in ctx with Quos_aesInitCtxIv() or Quos_aesCtxSetIv() */ -/* no IV should ever be reused with the same key */ -void Quos_aesCtrXCrypt(AES_ctx_t *ctx, void *buf, quint32_t length); - -void Quos_aesPadding(quint8_t *dest, quint8_t *src, quint32_t srcLen); -quint32_t Quos_aesPaddingBack(quint8_t *src,quint32_t srcLen); -#endif /*_AES_H_ */ +#ifndef _AES_H_ +#define _AES_H_ +#include "quos_config.h" + +#if (SDK_ENABLE_AES == 1) +#define QUOS_AES128 1 +/*#define QUOS_AES192 1 */ +/*#define QUOS_AES256 1 */ + +#define QUOS_AES_BLOCKLEN 16 /*Block length in bytes AES is 128b block only */ + +#if defined(QUOS_AES256) && (QUOS_AES256 == 1) +#define QUOS_AES_KEYLEN 32 +#define QUOS_AES_keyExpSize 240 +#elif defined(QUOS_AES192) && (QUOS_AES192 == 1) +#define QUOS_AES_KEYLEN 24 +#define QUOS_AES_keyExpSize 208 +#else +#define QUOS_AES_KEYLEN 16 /* Key length in bytes */ +#define QUOS_AES_keyExpSize 176 +#endif + +typedef struct +{ + quint8_t RoundKey[QUOS_AES_keyExpSize]; + quint8_t Iv[QUOS_AES_BLOCKLEN]; +} AES_ctx_t; + +void Quos_aesInitCtx(AES_ctx_t *ctx, const char *key); +void Quos_aesInitCtxIv(AES_ctx_t *ctx, const char *key, const char *iv); +void Quos_aesCtxSetIv(AES_ctx_t *ctx, const char *iv); + +/* buffer size MUST be mutile of QUOS_AES_BLOCKLEN; */ +/* you need only Quos_aesInitCtx as IV is not used in ECB */ +/* NB: ECB is considered insecure for most uses */ +quint32_t Quos_aesEcbEncrypt(AES_ctx_t *ctx, const quint8_t *buf, quint32_t length); +void Quos_aesEcbDecrypt(AES_ctx_t *ctx, const quint8_t *buf, quint32_t length); + +/* buffer size MUST be mutile of QUOS_AES_BLOCKLEN; */ +/* Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme */ +/* NOTES: you need to set IV in ctx via Quos_aesInitCtxIv() or Quos_aesCtxSetIv() */ +/* no IV should ever be reused with the same key */ +quint32_t Quos_aesCbcEncrypt(AES_ctx_t *ctx, void *buf, quint32_t length); +void Quos_aesCbcDecrypt(AES_ctx_t *ctx, void *buf, quint32_t length); + +/* Same function for encrypting as for decrypting. */ +/* IV is incremented for every block, and used after encryption as XOR-compliment for output */ +/* Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme */ +/* NOTES: you need to set IV in ctx with Quos_aesInitCtxIv() or Quos_aesCtxSetIv() */ +/* no IV should ever be reused with the same key */ +void Quos_aesCtrXCrypt(AES_ctx_t *ctx, void *buf, quint32_t length); + +void Quos_aesPadding(quint8_t *dest, quint8_t *src, quint32_t srcLen); +quint32_t Quos_aesPaddingBack(quint8_t *src,quint32_t srcLen); +#endif /*_AES_H_ */ #endif \ No newline at end of file diff --git a/kernel/quos_base64.c b/kernel/quos_base64.c new file mode 100644 index 0000000..caa184c --- /dev/null +++ b/kernel/quos_base64.c @@ -0,0 +1,115 @@ +/************************************************************************* +** 鍒涘缓浜 @author : 鍚村仴瓒 JCWu +** 鐗堟湰 @version : V1.0.0 鍘熷鐗堟湰 +** 鏃ユ湡 @date : +** 鍔熻兘 @brief : base64鍔犺В瀵 +** 纭欢 @hardware锛氫换浣旳NSI-C骞冲彴 +** 鍏朵粬 @other 锛 +***************************************************************************/ +#include "quos_base64.h" +#include "Quos_kernel.h" +#if (SDK_ENABLE_BASE64 == 1) +static char baseCode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/************************************************************************** +** 鍔熻兘 @brief : base64鍔犲瘑 +** 杈撳叆 @param : dstData:鍔犲瘑鍚庢暟鎹紝闀垮害蹇呴』涓哄師鏁版嵁闀垮害鐨4/3+2 +** 杈撳嚭 @retval: +***************************************************************************/ +quint16_t FUNCTION_ATTR_ROM Quos_base64Encrypt(const quint8_t *srcData, quint16_t srcLen, quint8_t *dstData) +{ + quint16_t i = 0; + Quos_logHexDump(LSDK_ENCRP, LL_DUMP, "base64 encrypt src data", srcData, srcLen); + while (srcLen) + { + dstData[i++] = baseCode[(srcData[0] >> 2) & 0x3F]; + if (srcLen > 2) + { + dstData[i++] = baseCode[((srcData[0] & 0x03) << 4) | (srcData[1] >> 4)]; + dstData[i++] = baseCode[((srcData[1] & 0x0F) << 2) | (srcData[2] >> 6)]; + dstData[i++] = baseCode[srcData[2] & 0x3F]; + srcLen -= 3; + } + else if (1 == srcLen) + { + dstData[i++] = baseCode[(srcData[0] & 3) << 4]; + dstData[i++] = '='; + dstData[i++] = '='; + srcLen = 0; + } + else if (2 == srcLen) + { + dstData[i++] = baseCode[((srcData[0] & 3) << 4) | (srcData[1] >> 4)]; + dstData[i++] = baseCode[(srcData[1] & 0x0F) << 2]; + dstData[i++] = '='; + srcLen = 0; + } + srcData += 3; + } + dstData[i] = 0; + Quos_logPrintf(LSDK_ENCRP, LL_DBG, "base64 encrypt dst data:%s", dstData); + return i; +} +/************************************************************************** +** 鍔熻兘 @brief : base64瑙e瘑 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +quint16_t FUNCTION_ATTR_ROM Quos_base64Decrypt(const quint8_t *srcData, quint16_t srcLen, quint8_t *dstData) +{ + quint16_t i; + quint16_t dstLen = 0; + Quos_logPrintf(LSDK_ENCRP, LL_DBG, "base64 decrypt src data:%s", srcData); + if (NULL == srcData || NULL == dstData || 0 == srcLen) + { + return 0; + } + for (i = 0; i < srcLen; i += 4) + { + quint8_t k; + quint8_t baseData[4]; + HAL_MEMSET(baseData, 0, sizeof(baseData)); + for (k = 0; k < 64; k++) + { + if (srcData[i] == baseCode[k]) + { + baseData[0] = k; + break; + } + } + for (k = 0; k < 64; k++) + { + if (srcData[i + 1] == baseCode[k]) + { + baseData[1] = k; + break; + } + } + for (k = 0; k < 64; k++) + { + if (srcData[i + 2] == baseCode[k]) + { + baseData[2] = k; + break; + } + } + for (k = 0; k < 64; k++) + { + if (srcData[i + 3] == baseCode[k]) + { + baseData[3] = k; + break; + } + } + dstData[dstLen++] = ((baseData[0] << 2) & 0xFC) | ((baseData[1] >> 4) & 0x03); + if (srcData[i + 2] == '=') + break; + dstData[dstLen++] = ((baseData[1] << 4) & 0xF0) | ((baseData[2] >> 2) & 0x0F); + if (srcData[i + 3] == '=') + break; + dstData[dstLen++] = ((baseData[2] << 6) & 0xF0) | (baseData[3] & 0x3F); + } + Quos_logHexDump(LSDK_ENCRP, LL_DUMP, "base64 decrypt dst data", dstData, dstLen); + return dstLen; +} +#endif \ No newline at end of file diff --git a/kernel/quos_base64.h b/kernel/quos_base64.h index 8ddcd98..0ff9936 100644 --- a/kernel/quos_base64.h +++ b/kernel/quos_base64.h @@ -1,10 +1,10 @@ -#ifndef __QUOS_BASE64_H__ -#define __QUOS_BASE64_H__ -#include "quos_config.h" - -#define QUOS_BASE64_DSTDATA_LEN(SRCLEN) (((SRCLEN+2)/3 << 2) + 1) -#if (SDK_ENABLE_BASE64 == 1) -quint16_t Quos_base64Encrypt(quint8_t *srcData, quint16_t srcLen, quint8_t *dstData); -quint16_t Quos_base64Decrypt(quint8_t *srcData, quint16_t srcLen, quint8_t *dstData); -#endif -#endif +#ifndef __QUOS_BASE64_H__ +#define __QUOS_BASE64_H__ +#include "quos_config.h" + +#define QUOS_BASE64_DSTDATA_LEN(SRCLEN) (((SRCLEN+2)/3 << 2) + 1) +#if (SDK_ENABLE_BASE64 == 1) +quint16_t Quos_base64Encrypt(const quint8_t *srcData, quint16_t srcLen, quint8_t *dstData); +quint16_t Quos_base64Decrypt(const quint8_t *srcData, quint16_t srcLen, quint8_t *dstData); +#endif +#endif diff --git a/kernel/quos_cjson.c b/kernel/quos_cjson.c new file mode 100644 index 0000000..2e14961 --- /dev/null +++ b/kernel/quos_cjson.c @@ -0,0 +1,2934 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ +/* disable warnings about old C89 functions in MSVC */ +#include "quos_cjson.h" +#if (SDK_ENABLE_JSON ==1 ) +#include "quos_SupportTool.h" +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#define NAN 0.0/0.0 +#endif +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 14 + +typedef struct { + const unsigned char *json; + quint32_t position; +} error; +static error global_error = { NULL, 0 }; + +const char * cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +char * cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +double cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 14) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +const char* cJSON_Version(void) +{ + static char version[15]; + HAL_SPRINTF(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; __TO_LOWER(*string1) == __TO_LOWER(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return __TO_LOWER(*string1) - __TO_LOWER(*string2); +} + +/* HAL_STRLEN of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(void) +{ + cJSON* node = HAL_MALLOC(sizeof(cJSON)); + if (node) + { + HAL_MEMSET(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +void cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & QUOS_cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & QUOS_cJSON_IsReference) && (item->valuestring != NULL)) + { + HAL_FREE(item->valuestring); + } + if (!(item->type & QUOS_cJSON_StringIsConst) && (item->string != NULL)) + { + HAL_FREE(item->string); + } + HAL_FREE(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + quint32_t length; + quint32_t offset; + quint32_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + quint32_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return FALSE; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = HAL_STRTOD((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return FALSE; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = QUOS_cJSON_Number; + + input_buffer->offset += (quint32_t)(after_end - number_c_string); + return TRUE; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +double cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +char* cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not QUOS_cJSON_String or is QUOS_cJSON_IsReference, it should not set valuestring */ + if (!(object->type & QUOS_cJSON_String) || (object->type & QUOS_cJSON_IsReference)) + { + return NULL; + } + if (HAL_STRLEN(valuestring) <= HAL_STRLEN(object->valuestring)) + { + HAL_STRCPY(object->valuestring, valuestring); + return object->valuestring; + } + copy = HAL_STRDUP(valuestring); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + HAL_FREE(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + quint32_t length; + quint32_t offset; + quint32_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, quint32_t needed) +{ + unsigned char *newbuffer = NULL; + quint32_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + /* otherwise reallocate manually */ + newbuffer = HAL_MALLOC(newsize); + if (!newbuffer) + { + HAL_FREE(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + if (newbuffer) + { + HAL_MEMCPY(newbuffer, p->buffer, p->offset + 1); + } + HAL_FREE(p->buffer); + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += HAL_STRLEN((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + quint32_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return FALSE; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = HAL_SPRINTF((char*)number_buffer, "null"); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = HAL_SPRINTF((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((HAL_SSCANF((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = HAL_SPRINTF((char*)number_buffer, "%1.17g", d); + } + } + + /* HAL_SPRINTF failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return FALSE; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (quint32_t)length + sizeof("")); + if (output_pointer == NULL) + { + return FALSE; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((quint32_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (quint32_t)length; + + return TRUE; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + quint32_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + quint32_t allocation_length = 0; + quint32_t skipped_bytes = 0; + while (((quint32_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((quint32_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((quint32_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (quint32_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = HAL_MALLOC(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = QUOS_cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (quint32_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return TRUE; + +fail: + if (output != NULL) + { + HAL_FREE(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (quint32_t)(input_pointer - input_buffer->content); + } + + return FALSE; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + quint32_t output_length = 0; + /* numbers of additional characters needed for escaping */ + quint32_t escape_characters = 0; + + if (output_buffer == NULL) + { + return FALSE; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return FALSE; + } + HAL_STRCPY((char*)output, "\"\""); + + return TRUE; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (quint32_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return FALSE; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + HAL_MEMCPY(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return TRUE; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + HAL_SPRINTF((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return TRUE; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (HAL_STRNCMP((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +cJSON * cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + quint32_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = HAL_STRLEN(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +cJSON * cJSON_ParseWithLengthOpts(const char *value, quint32_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0 }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + + item = cJSON_New_Item(); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +cJSON * cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +cJSON * cJSON_ParseWithLength(const char *value, quint32_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format) +{ + static const quint32_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + HAL_MEMSET(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = HAL_MALLOC(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + printed = HAL_MALLOC(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + HAL_MEMCPY(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + HAL_FREE(buffer->buffer); + + return printed; + +fail: + if (buffer->buffer != NULL) + { + HAL_FREE(buffer->buffer); + } + + if (printed != NULL) + { + HAL_FREE(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +char * cJSON_Print(const cJSON *item) +{ + return (char*)print(item, TRUE); +} + +char * cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, FALSE); +} + +char * cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0}; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = HAL_MALLOC((quint32_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (quint32_t)prebuffer; + p.offset = 0; + p.noalloc = FALSE; + p.format = fmt; + + if (!print_value(item, &p)) + { + HAL_FREE(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +cJSON_bool cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0}; + + if ((length < 0) || (buffer == NULL)) + { + return FALSE; + } + + p.buffer = (unsigned char*)buffer; + p.length = (quint32_t)length; + p.offset = 0; + p.noalloc = TRUE; + p.format = format; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return FALSE; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (HAL_STRNCASECMP((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = QUOS_cJSON_NULL; + input_buffer->offset += 4; + return TRUE; + } + /* FALSE */ + if (can_read(input_buffer, 5) && (HAL_STRNCASECMP((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = QUOS_cJSON_False; + input_buffer->offset += 5; + return TRUE; + } + /* TRUE */ + if (can_read(input_buffer, 4) && (HAL_STRNCASECMP((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = QUOS_cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return TRUE; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return FALSE; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return FALSE; + } + + switch ((item->type) & 0xFF) + { + case QUOS_cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return FALSE; + } + HAL_STRCPY((char*)output, "null"); + return TRUE; + + case QUOS_cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return FALSE; + } + HAL_STRCPY((char*)output, "false"); + return TRUE; + + case QUOS_cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return FALSE; + } + HAL_STRCPY((char*)output, "true"); + return TRUE; + + case QUOS_cJSON_Number: + return print_number(item, output_buffer); + + case QUOS_cJSON_Raw: + { + quint32_t raw_length = 0; + if (item->valuestring == NULL) + { + return FALSE; + } + + raw_length = HAL_STRLEN(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return FALSE; + } + HAL_MEMCPY(output, item->valuestring, raw_length); + return TRUE; + } + + case QUOS_cJSON_String: + return print_string(item, output_buffer); + + case QUOS_cJSON_Array: + return print_array(item, output_buffer); + + case QUOS_cJSON_Object: + return print_object(item, output_buffer); + + default: + return FALSE; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= QUOS_CJSON_NESTING_LIMIT) + { + return FALSE; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = QUOS_cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return TRUE; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return FALSE; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + quint32_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return FALSE; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return FALSE; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return FALSE; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (quint32_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return FALSE; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return FALSE; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return TRUE; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= QUOS_CJSON_NESTING_LIMIT) + { + return FALSE; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = QUOS_cJSON_Object; + item->child = head; + + input_buffer->offset++; + return TRUE; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return FALSE; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + quint32_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return FALSE; + } + + /* Compose the output: */ + length = (quint32_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return FALSE; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + quint32_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return FALSE; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return FALSE; + } + update_offset(output_buffer); + + length = (quint32_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return FALSE; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return FALSE; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((quint32_t)(output_buffer->format ? 1 : 0) + (quint32_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return FALSE; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return FALSE; + } + if (output_buffer->format) + { + quint32_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return TRUE; +} + +/* Get Array size/item / object item. */ +int cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + quint32_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, quint32_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +cJSON * cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (quint32_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (HAL_STRCMP(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +cJSON * cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, FALSE); +} + +cJSON * cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, TRUE); +} + +cJSON_bool cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(); + if (reference == NULL) + { + return NULL; + } + + HAL_MEMCPY(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= QUOS_cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return FALSE; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return TRUE; +} + +/* Add item to array/object. */ +cJSON_bool cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = QUOS_cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return FALSE; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | QUOS_cJSON_StringIsConst; + } + else + { + new_key = HAL_STRDUP(string); + if (new_key == NULL) + { + return FALSE; + } + + new_type = item->type & ~QUOS_cJSON_StringIsConst; + } + + if (!(item->type & QUOS_cJSON_StringIsConst) && (item->string != NULL)) + { + HAL_FREE(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +cJSON_bool cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, FALSE); +} + +/* Add an item to an object with constant string as key */ +cJSON_bool cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, TRUE); +} + +cJSON_bool cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return FALSE; + } + + return add_item_to_array(array, create_reference(item)); +} + +cJSON_bool cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return FALSE; + } + + return add_item_to_object(object, string, create_reference(item), FALSE); +} + +cJSON* cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, FALSE)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +cJSON* cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, FALSE)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +cJSON* cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, FALSE)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +cJSON* cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, FALSE)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +cJSON* cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, FALSE)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +cJSON* cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, FALSE)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +cJSON* cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, FALSE)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +cJSON* cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, FALSE)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +cJSON* cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, FALSE)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +cJSON * cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +cJSON * cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (quint32_t)which)); +} + +void cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +cJSON * cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +cJSON * cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +void cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +void cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +cJSON_bool cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return FALSE; + } + + after_inserted = get_array_item(array, (quint32_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return TRUE; +} + +cJSON_bool cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return FALSE; + } + + if (replacement == item) + { + return TRUE; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return TRUE; +} + +cJSON_bool cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return FALSE; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (quint32_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return FALSE; + } + + /* replace the name in the replacement */ + if (!(replacement->type & QUOS_cJSON_StringIsConst) && (replacement->string != NULL)) + { + HAL_FREE(replacement->string); + } + replacement->string = HAL_STRDUP(string); + replacement->type &= ~QUOS_cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +cJSON_bool cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, FALSE); +} + +cJSON_bool cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, TRUE); +} + +/* Create basic types: */ +cJSON * cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(); + if(item) + { + item->type = QUOS_cJSON_NULL; + } + + return item; +} + +cJSON * cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(); + if(item) + { + item->type = QUOS_cJSON_True; + } + + return item; +} + +cJSON * cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(); + if(item) + { + item->type = QUOS_cJSON_False; + } + + return item; +} + +cJSON * cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(); + if(item) + { + item->type = boolean ? QUOS_cJSON_True : QUOS_cJSON_False; + } + + return item; +} + +cJSON * cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(); + if(item) + { + item->type = QUOS_cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +cJSON * cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(); + if(item) + { + item->type = QUOS_cJSON_String; + item->valuestring = HAL_STRDUP(string); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +cJSON * cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(); + if (item != NULL) + { + item->type = QUOS_cJSON_String | QUOS_cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +cJSON * cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(); + if (item != NULL) { + item->type = QUOS_cJSON_Object | QUOS_cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +cJSON * cJSON_CreateArrayReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(); + if (item != NULL) { + item->type = QUOS_cJSON_Array | QUOS_cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +cJSON * cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(); + if(item) + { + item->type = QUOS_cJSON_Raw; + item->valuestring = HAL_STRDUP(raw); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +cJSON * cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(); + if(item) + { + item->type=QUOS_cJSON_Array; + } + + return item; +} + +cJSON * cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(); + if (item) + { + item->type = QUOS_cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +cJSON * cJSON_CreateIntArray(const int *numbers, int count) +{ + quint32_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + for(i = 0; a && (i < (quint32_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + a->child->prev = n; + + return a; +} + +cJSON * cJSON_CreateFloatArray(const float *numbers, int count) +{ + quint32_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (quint32_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + a->child->prev = n; + + return a; +} + +cJSON * cJSON_CreateDoubleArray(const double *numbers, int count) +{ + quint32_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0;a && (i < (quint32_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + a->child->prev = n; + + return a; +} + +cJSON * cJSON_CreateStringArray(const char *const *strings, int count) +{ + quint32_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (quint32_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + a->child->prev = n; + + return a; +} + +/* Duplication */ +cJSON * cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~QUOS_cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = HAL_STRDUP(item->valuestring); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&QUOS_cJSON_StringIsConst) ? item->string : HAL_STRDUP(item->string); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, TRUE); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +void cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +cJSON_bool cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xFF) == QUOS_cJSON_Invalid; +} + +cJSON_bool cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xFF) == QUOS_cJSON_False; +} + +cJSON_bool cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xff) == QUOS_cJSON_True; +} + + +cJSON_bool cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & (QUOS_cJSON_True | QUOS_cJSON_False)) != 0; +} +cJSON_bool cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xFF) == QUOS_cJSON_NULL; +} + +cJSON_bool cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xFF) == QUOS_cJSON_Number; +} + +cJSON_bool cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xFF) == QUOS_cJSON_String; +} + +cJSON_bool cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xFF) == QUOS_cJSON_Array; +} + +cJSON_bool cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xFF) == QUOS_cJSON_Object; +} + +cJSON_bool cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return FALSE; + } + + return (item->type & 0xFF) == QUOS_cJSON_Raw; +} + +cJSON_bool cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) + { + return FALSE; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case QUOS_cJSON_False: + case QUOS_cJSON_True: + case QUOS_cJSON_NULL: + case QUOS_cJSON_Number: + case QUOS_cJSON_String: + case QUOS_cJSON_Raw: + case QUOS_cJSON_Array: + case QUOS_cJSON_Object: + break; + + default: + return FALSE; + } + + /* identical objects are equal */ + if (a == b) + { + return TRUE; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case QUOS_cJSON_False: + case QUOS_cJSON_True: + case QUOS_cJSON_NULL: + return TRUE; + + case QUOS_cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return TRUE; + } + return FALSE; + + case QUOS_cJSON_String: + case QUOS_cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return FALSE; + } + if (HAL_STRCMP(a->valuestring, b->valuestring) == 0) + { + return TRUE; + } + + return FALSE; + + case QUOS_cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return FALSE; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return FALSE; + } + + return TRUE; + } + + case QUOS_cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + QUOS_cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return FALSE; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return FALSE; + } + } + + /* doing this twice, once on a and b to prevent TRUE comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + QUOS_cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return FALSE; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return FALSE; + } + } + + return TRUE; + } + + default: + return FALSE; + } +} + +#endif \ No newline at end of file diff --git a/kernel/quos_cjson.h b/kernel/quos_cjson.h index 7a0a13d..c5c896c 100644 --- a/kernel/quos_cjson.h +++ b/kernel/quos_cjson.h @@ -1,220 +1,214 @@ -/* - Copyright (c) 2009-2017 Dave Gamble and cJSON contributors - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. -*/ - -#ifndef cJSON__h -#define cJSON__h - -#ifdef __cplusplus -extern "C" -{ -#endif -#include "quos_config.h" -/* cJSON Types: */ -#define QUOS_cJSON_Invalid (0) -#define QUOS_cJSON_False (1 << 0) -#define QUOS_cJSON_True (1 << 1) -#define QUOS_cJSON_NULL (1 << 2) -#define QUOS_cJSON_Number (1 << 3) -#define QUOS_cJSON_String (1 << 4) -#define QUOS_cJSON_Array (1 << 5) -#define QUOS_cJSON_Object (1 << 6) -#define QUOS_cJSON_Raw (1 << 7) /* raw json */ - -#define QUOS_cJSON_IsReference 256 -#define QUOS_cJSON_StringIsConst 512 - -/* The cJSON structure: */ -typedef struct cJSON -{ - /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ - struct cJSON *next; - struct cJSON *prev; - /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ - struct cJSON *child; - - /* The type of the item, as above. */ - int type; - - /* The item's string, if type==QUOS_cJSON_String and type == QUOS_cJSON_Raw */ - char *valuestring; - /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ - int valueint; - /* The item's number, if type==QUOS_cJSON_Number */ - double valuedouble; - - /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ - char *string; -} cJSON; - -typedef qbool cJSON_bool; - -/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. - * This is to prevent stack overflows. */ -#ifndef QUOS_CJSON_NESTING_LIMIT -#define QUOS_CJSON_NESTING_LIMIT 1000 -#endif - -/* returns the version of cJSON as a string */ -const char* cJSON_Version(void); -/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ -cJSON * cJSON_Parse(const char *value); -cJSON * cJSON_ParseWithLength(const char *value, quint32_t buffer_length); -/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ -/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ -cJSON * cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); -cJSON * cJSON_ParseWithLengthOpts(const char *value, quint32_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); - -/* Render a cJSON entity to text for transfer/storage. */ -char * cJSON_Print(const cJSON *item); -/* Render a cJSON entity to text for transfer/storage without any formatting. */ -char * cJSON_PrintUnformatted(const cJSON *item); -/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ -char * cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); -/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ -/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ -cJSON_bool cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); -/* Delete a cJSON entity and all subentities. */ -void cJSON_Delete(cJSON *item); - -/* Returns the number of items in an array (or object). */ -int cJSON_GetArraySize(const cJSON *array); -/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ -cJSON * cJSON_GetArrayItem(const cJSON *array, int index); -/* Get item "string" from object. Case insensitive. */ -cJSON * cJSON_GetObjectItem(const cJSON * const object, const char * const string); -cJSON * cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); -cJSON_bool cJSON_HasObjectItem(const cJSON *object, const char *string); -/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ -const char * cJSON_GetErrorPtr(void); - -/* Check item type and return its value */ -char * cJSON_GetStringValue(const cJSON * const item); -double cJSON_GetNumberValue(const cJSON * const item); - -/* These functions check the type of an item */ -cJSON_bool cJSON_IsInvalid(const cJSON * const item); -cJSON_bool cJSON_IsFalse(const cJSON * const item); -cJSON_bool cJSON_IsTrue(const cJSON * const item); -cJSON_bool cJSON_IsBool(const cJSON * const item); -cJSON_bool cJSON_IsNull(const cJSON * const item); -cJSON_bool cJSON_IsNumber(const cJSON * const item); -cJSON_bool cJSON_IsString(const cJSON * const item); -cJSON_bool cJSON_IsArray(const cJSON * const item); -cJSON_bool cJSON_IsObject(const cJSON * const item); -cJSON_bool cJSON_IsRaw(const cJSON * const item); - -/* These calls create a cJSON item of the appropriate type. */ -cJSON * cJSON_CreateNull(void); -cJSON * cJSON_CreateTrue(void); -cJSON * cJSON_CreateFalse(void); -cJSON * cJSON_CreateBool(cJSON_bool boolean); -cJSON * cJSON_CreateNumber(double num); -cJSON * cJSON_CreateString(const char *string); -/* raw json */ -cJSON * cJSON_CreateRaw(const char *raw); -cJSON * cJSON_CreateArray(void); -cJSON * cJSON_CreateObject(void); - -/* Create a string where valuestring references a string so - * it will not be freed by cJSON_Delete */ -cJSON * cJSON_CreateStringReference(const char *string); -/* Create an object/array that only references it's elements so - * they will not be freed by cJSON_Delete */ -cJSON * cJSON_CreateObjectReference(const cJSON *child); -cJSON * cJSON_CreateArrayReference(const cJSON *child); - -/* These utilities create an Array of count items. - * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ -cJSON * cJSON_CreateIntArray(const int *numbers, int count); -cJSON * cJSON_CreateFloatArray(const float *numbers, int count); -cJSON * cJSON_CreateDoubleArray(const double *numbers, int count); -cJSON * cJSON_CreateStringArray(const char *const *strings, int count); - -/* Append item to the specified array/object. */ -cJSON_bool cJSON_AddItemToArray(cJSON *array, cJSON *item); -cJSON_bool cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); -/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. - * WARNING: When this function was used, make sure to always check that (item->type & QUOS_cJSON_StringIsConst) is zero before - * writing to `item->string` */ -cJSON_bool cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); -/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ -cJSON_bool cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); -cJSON_bool cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); - -/* Remove/Detach items from Arrays/Objects. */ -cJSON * cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); -cJSON * cJSON_DetachItemFromArray(cJSON *array, int which); -void cJSON_DeleteItemFromArray(cJSON *array, int which); -cJSON * cJSON_DetachItemFromObject(cJSON *object, const char *string); -cJSON * cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); -void cJSON_DeleteItemFromObject(cJSON *object, const char *string); -void cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); - -/* Update array items. */ -cJSON_bool cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ -cJSON_bool cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); -cJSON_bool cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); -cJSON_bool cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); -cJSON_bool cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); - -/* Duplicate a cJSON item */ -cJSON * cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); -/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will - * need to be released. With recurse!=0, it will duplicate any children connected to the item. - * The item->next and ->prev pointers are always zero on return from Duplicate. */ -/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. - * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ -cJSON_bool cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); - -/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. - * The input pointer json cannot point to a read-only address area, such as a string constant, - * but should point to a readable and writable adress area. */ -void cJSON_Minify(char *json); - -/* Helper functions for creating and adding items to an object at the same time. - * They return the added item or NULL on failure. */ -cJSON* cJSON_AddNullToObject(cJSON * const object, const char * const name); -cJSON* cJSON_AddTrueToObject(cJSON * const object, const char * const name); -cJSON* cJSON_AddFalseToObject(cJSON * const object, const char * const name); -cJSON* cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); -cJSON* cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); -cJSON* cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); -cJSON* cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); -cJSON* cJSON_AddObjectToObject(cJSON * const object, const char * const name); -cJSON* cJSON_AddArrayToObject(cJSON * const object, const char * const name); - -/* When assigning an integer value, it needs to be propagated to valuedouble too. */ -#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) -/* helper for the cJSON_SetNumberValue macro */ -double cJSON_SetNumberHelper(cJSON *object, double number); -#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) -/* Change the valuestring of a QUOS_cJSON_String object, only takes effect when type of object is QUOS_cJSON_String */ -char* cJSON_SetValuestring(cJSON *object, const char *valuestring); - -/* Macro for iterating over an array or object */ -#define QUOS_cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) - -#ifdef __cplusplus -} -#endif - -#endif +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#include "quos_config.h" +#if (SDK_ENABLE_JSON ==1 ) +/* cJSON Types: */ +#define QUOS_cJSON_Invalid (0) +#define QUOS_cJSON_False (1 << 0) +#define QUOS_cJSON_True (1 << 1) +#define QUOS_cJSON_NULL (1 << 2) +#define QUOS_cJSON_Number (1 << 3) +#define QUOS_cJSON_String (1 << 4) +#define QUOS_cJSON_Array (1 << 5) +#define QUOS_cJSON_Object (1 << 6) +#define QUOS_cJSON_Raw (1 << 7) /* raw json */ + +#define QUOS_cJSON_IsReference 256 +#define QUOS_cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==QUOS_cJSON_String and type == QUOS_cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==QUOS_cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef qbool cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef QUOS_CJSON_NESTING_LIMIT +#define QUOS_CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +const char* cJSON_Version(void); +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +cJSON * cJSON_Parse(const char *value); +cJSON * cJSON_ParseWithLength(const char *value, quint32_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +cJSON * cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +cJSON * cJSON_ParseWithLengthOpts(const char *value, quint32_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +char * cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +char * cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +char * cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +cJSON_bool cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +void cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +int cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +cJSON * cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +cJSON * cJSON_GetObjectItem(const cJSON * const object, const char * const string); +cJSON * cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +cJSON_bool cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +const char * cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +char * cJSON_GetStringValue(const cJSON * const item); +double cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +cJSON_bool cJSON_IsInvalid(const cJSON * const item); +cJSON_bool cJSON_IsFalse(const cJSON * const item); +cJSON_bool cJSON_IsTrue(const cJSON * const item); +cJSON_bool cJSON_IsBool(const cJSON * const item); +cJSON_bool cJSON_IsNull(const cJSON * const item); +cJSON_bool cJSON_IsNumber(const cJSON * const item); +cJSON_bool cJSON_IsString(const cJSON * const item); +cJSON_bool cJSON_IsArray(const cJSON * const item); +cJSON_bool cJSON_IsObject(const cJSON * const item); +cJSON_bool cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +cJSON * cJSON_CreateNull(void); +cJSON * cJSON_CreateTrue(void); +cJSON * cJSON_CreateFalse(void); +cJSON * cJSON_CreateBool(cJSON_bool boolean); +cJSON * cJSON_CreateNumber(double num); +cJSON * cJSON_CreateString(const char *string); +/* raw json */ +cJSON * cJSON_CreateRaw(const char *raw); +cJSON * cJSON_CreateArray(void); +cJSON * cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +cJSON * cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +cJSON * cJSON_CreateObjectReference(const cJSON *child); +cJSON * cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +cJSON * cJSON_CreateIntArray(const int *numbers, int count); +cJSON * cJSON_CreateFloatArray(const float *numbers, int count); +cJSON * cJSON_CreateDoubleArray(const double *numbers, int count); +cJSON * cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +cJSON_bool cJSON_AddItemToArray(cJSON *array, cJSON *item); +cJSON_bool cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & QUOS_cJSON_StringIsConst) is zero before + * writing to `item->string` */ +cJSON_bool cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +cJSON_bool cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +cJSON_bool cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +cJSON * cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +cJSON * cJSON_DetachItemFromArray(cJSON *array, int which); +void cJSON_DeleteItemFromArray(cJSON *array, int which); +cJSON * cJSON_DetachItemFromObject(cJSON *object, const char *string); +cJSON * cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +void cJSON_DeleteItemFromObject(cJSON *object, const char *string); +void cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +cJSON_bool cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +cJSON_bool cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +cJSON_bool cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +cJSON_bool cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +cJSON_bool cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +cJSON * cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +cJSON_bool cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable adress area. */ +void cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +cJSON* cJSON_AddNullToObject(cJSON * const object, const char * const name); +cJSON* cJSON_AddTrueToObject(cJSON * const object, const char * const name); +cJSON* cJSON_AddFalseToObject(cJSON * const object, const char * const name); +cJSON* cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +cJSON* cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +cJSON* cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +cJSON* cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +cJSON* cJSON_AddObjectToObject(cJSON * const object, const char * const name); +cJSON* cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +double cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a QUOS_cJSON_String object, only takes effect when type of object is QUOS_cJSON_String */ +char* cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* Macro for iterating over an array or object */ +#define QUOS_cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) +#endif + +#endif diff --git a/kernel/quos_coap.c b/kernel/quos_coap.c new file mode 100644 index 0000000..94d173b --- /dev/null +++ b/kernel/quos_coap.c @@ -0,0 +1,824 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : coap通信管理 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_coap.h" +#if (SDK_ENABLE_COAP == 1) +#include "Quos_kernel.h" +#include "Qhal_driver.h" + +#ifndef QUOS_COAP_SEND_TIMEOUT +#define QUOS_COAP_SEND_TIMEOUT 5 * SWT_ONE_SECOND +#endif +#ifndef QUOS_COAP_RESEND_TIME +#define QUOS_COAP_RESEND_TIME 3 +#endif +#define QUOS_COAP_VERSION 0x01 + +typedef struct +{ + quint16_t mid; + coapRecvNotify_f notifyCB; + void *peer; +} coapSock_t; +typedef struct +{ + TWLLHead_T head; + coapOptionType_t type; + quint16_t len; + quint8_t val[1]; +} coap_optionNode_t; +/************************************************************************** +** 功能 @brief : coap message 头设置 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_coapHeadSet(Coap_Message_t *coapMsg, coapHeadType_t type, coapHeadCode_t code, quint16_t mid, quint32_t tokenLen, const quint8_t *token) +{ + coapMsg->head.ver = QUOS_COAP_VERSION; + coapMsg->head.type = type; + coapMsg->head.tokenLen = tokenLen > sizeof(coapMsg->token) ? sizeof(coapMsg->token) : tokenLen; + coapMsg->head.code = code; + coapMsg->head.mid = mid; + if (coapMsg->head.tokenLen) + { + if (NULL == token) + { + Systick_T tm = Quos_sysTickGet(); + _U16_ARRAY01(mid, coapMsg->token); + _U32_ARRAY0123(tm.sec, coapMsg->token + 2); + _U16_ARRAY01(tm.ms, coapMsg->token + 6); + } + else + { + HAL_MEMCPY(coapMsg->token, token, coapMsg->head.tokenLen); + } + } +} + +/************************************************************************** +** 功能 @brief : 找出options数组的元素id比参考id大但差值最小的节点 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static coap_optionNode_t FUNCTION_ATTR_ROM *quos_coapOptionIdDeltaMinGet(const TWLLHead_T *optionsHead, const coap_optionNode_t *referNode) +{ + qbool findNext = FALSE; + coap_optionNode_t *option = NULL; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)optionsHead, temp, next) + { + coap_optionNode_t *node = __GET_STRUCT_BY_ELEMENT(temp, coap_optionNode_t, head); + if (NULL == referNode) + { + if (NULL == option) + { + option = node; + } + else if (option->type > node->type) + { + option = node; + } + } + else if (node->type == referNode->type) + { + if (node == referNode) + { + findNext = TRUE; + } + else if (findNext) + { + return node; + } + } + else if (node->type > referNode->type && (NULL == option || node->type < option->type)) + { + option = node; + } + } + return option; +} +/************************************************************************** +** 功能 @brief : coap message option 不透明类型数据添加 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_coapOptionSetOpaque(Coap_Message_t *coapMsg, coapOptionType_t type, const void *val, quint16_t valLen) +{ + coap_optionNode_t *node = HAL_MALLOC(__GET_POS_ELEMENT(coap_optionNode_t, val) + valLen); + if (NULL == node) + { + return FALSE; + } + HAL_MEMSET(node, 0, sizeof(coap_optionNode_t)); + node->type = type; + HAL_MEMCPY(node->val, (quint8_t *)val, valLen); + node->len = valLen; + Quos_twllHeadAdd((TWLLHead_T **)&coapMsg->optionsHead, &node->head); + return TRUE; +} +/************************************************************************** +** 功能 @brief : coap message option 不透明类型数据获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_coapOptionGetOpaque(Coap_Message_t *coapMsg, coapOptionType_t type, const void **val, quint16_t *valLen) +{ + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)coapMsg->optionsHead, temp, next) + { + coap_optionNode_t *node = __GET_STRUCT_BY_ELEMENT(temp, coap_optionNode_t, head); + if (type == node->type) + { + if (val) + { + *val = node->val; + } + if (valLen) + { + *valLen = node->len; + } + return TRUE; + } + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : coap message option 数值类型数据添加 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_coapOptionSetNumber(Coap_Message_t *coapMsg, coapOptionType_t type, quint32_t number) +{ + quint16_t valLen = 0; + quint32_t temp = number; + do + { + valLen++; + temp >>= 8; + } while (temp > 0); + + coap_optionNode_t *node = HAL_MALLOC(__GET_POS_ELEMENT(coap_optionNode_t, val) + valLen); + if (NULL == node) + { + return FALSE; + } + HAL_MEMSET(node, 0, sizeof(coap_optionNode_t)); + node->type = type; + node->len = valLen; + while (valLen--) + { + node->val[valLen] = number & 0xFF; + number >>= 8; + } + Quos_twllHeadAdd((TWLLHead_T **)&coapMsg->optionsHead, &node->head); + return TRUE; +} +/************************************************************************** +** 功能 @brief : coap message option 数值类型数据获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_coapOptionGetNumber(Coap_Message_t *coapMsg, coapOptionType_t type, quint32_t *number) +{ + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)coapMsg->optionsHead, temp, next) + { + coap_optionNode_t *node = __GET_STRUCT_BY_ELEMENT(temp, coap_optionNode_t, head); + if (type == node->type) + { + if (number) + { + quint16_t i; + *number = 0; + for (i = 0; i < node->len; i++) + { + *number |= (quint32_t)node->val[i] << (i * 8); + } + } + return TRUE; + } + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : 设置payload +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_coapPayloadSet(Coap_Message_t *coapMsg, const void *val, quint16_t valLen) +{ + coapMsg->payload.val = (void *)val; + coapMsg->payload.len = valLen; +} +/************************************************************************** +** 功能 @brief : PATH 转成option结构体 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_coapOptionSetPath(Coap_Message_t *coapMsg, const char *path) +{ + char *tmpPath = (char *)path; + if (NULL == path) + { + return TRUE; + } + while ('\0' != tmpPath[0] && '?' != tmpPath[0]) + { + if (tmpPath[0] == '/') + tmpPath++; + else + { + quint32_t i = 0; + while ('\0' != tmpPath[i] && tmpPath[i] != '/' && tmpPath[i] != '?') + i++; + if (FALSE == Quos_coapOptionSetOpaque(coapMsg, COAP_OTYPE_URI_PATH, tmpPath, i)) + { + return FALSE; + } + tmpPath += i; + } + } + if ('?' == tmpPath[0]) + tmpPath++; + while ('\0' != tmpPath[0]) + { + if (tmpPath[0] == '&') + tmpPath++; + else + { + quint32_t i = 0; + while (tmpPath[i] != 0 && tmpPath[i] != '&') + i++; + if (FALSE == Quos_coapOptionSetOpaque(coapMsg, COAP_OTYPE_URI_QUERY, tmpPath, i)) + { + return FALSE; + } + tmpPath += i; + } + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : 转成option结构体转成path +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +char FUNCTION_ATTR_ROM *Quos_coapOptionGetPath(const Coap_Message_t *coapMsg) +{ + quint16_t len = 0; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)coapMsg->optionsHead, temp, next) + { + coap_optionNode_t *node = __GET_STRUCT_BY_ELEMENT(temp, coap_optionNode_t, head); + if (COAP_OTYPE_URI_PATH == node->type || COAP_OTYPE_URI_QUERY == node->type) + { + len += node->len + 1; + } + } + char *path = NULL; + if (0 == len || (path = HAL_MALLOC(len + 1)) == NULL) + { + return NULL; + } + len = 0; + TWLIST_FOR_DATA((TWLLHead_T *)coapMsg->optionsHead, temp, next) + { + coap_optionNode_t *node = __GET_STRUCT_BY_ELEMENT(temp, coap_optionNode_t, head); + if (COAP_OTYPE_URI_PATH == node->type) + { + HAL_SPRINTF(path + len, "/%.*s", node->len, node->val); + len += node->len + 1; + } + } + qbool isFirst = TRUE; + TWLIST_FOR_DATA((TWLLHead_T *)coapMsg->optionsHead, temp, next) + { + coap_optionNode_t *node = __GET_STRUCT_BY_ELEMENT(temp, coap_optionNode_t, head); + if (COAP_OTYPE_URI_QUERY == node->type) + { + if (isFirst) + { + HAL_SPRINTF(path + len, "?%.*s", node->len, node->val); + isFirst = FALSE; + } + else + { + HAL_SPRINTF(path + len, "&%.*s", node->len, node->val); + } + len += node->len + 1; + } + } + return path; +} +/************************************************************************** +** 功能 @brief : coap option原型转十六进制流 +** 输入 @param : buffer:非空时将option格式化成16进制到buffer,buffer必须足够大防止溢出 +** 输出 @retval: 返回option格式化成16进制字节长度 +***************************************************************************/ +static quint16_t FUNCTION_ATTR_ROM quos_coapoptionFormat(const coap_optionNode_t *opt, coap_optionNode_t *referNode, quint8_t *buffer) +{ + quint16_t len = 0; + quint16_t delta = opt->type - (referNode ? referNode->type : 0); + if (delta > 268) + { + if (buffer) + { + buffer[0] = 14 << 4; + buffer[1] = (delta - 269) >> 8; + buffer[2] = (delta - 269) & 0xFF; + } + len = 3; + } + else if (delta > 12) + { + if (buffer) + { + buffer[0] = 13 << 4; + buffer[1] = delta - 13; + } + len = 2; + } + else + { + if (buffer) + { + buffer[0] = delta << 4; + } + len = 1; + } + if (opt->len > 268) + { + if (buffer) + { + buffer[0] |= 14; + buffer[len] = (opt->len - 269) >> 8; + buffer[len + 1] = (opt->len - 269) & 0xFF; + } + len += 2; + } + else if (opt->len > 12) + { + if (buffer) + { + buffer[0] |= 13; + buffer[len] = opt->len - 13; + } + len += 1; + } + else + { + if (buffer) + { + buffer[0] |= opt->len; + } + } + if (buffer) + { + HAL_MEMCPY(buffer + len, opt->val, opt->len); + } + len += opt->len; + Quos_logPrintf(LSDK_COAP, LL_DBG, "coap option format len:%u", len); + if (buffer) + { + Quos_logHexDump(LSDK_COAP, LL_DUMP, "option head buf", buffer, len - opt->len); + } + return len; +} +/************************************************************************** +** 功能 @brief : 将十六进制流解析成option结构体 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static quint16_t FUNCTION_ATTR_ROM quos_coapoptionUnformat(const quint8_t *buffer, quint16_t bufLen, coap_optionNode_t **option, coap_optionNode_t *referNode) +{ + if (NULL == buffer || 0 == bufLen) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "param fail"); + return 0; + } + quint16_t i = 0; + quint16_t delta = (buffer[i] >> 4) & 0x0F; + quint16_t optLen = buffer[i] & 0x0F; + if (15 == delta || 15 == optLen) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "delta[%u] or optLen[%u] is invalid", delta, optLen); + return 0; + } + i++; + + if (i + (delta > 12 ? (delta - 12) : 0) + (optLen > 12 ? (optLen - 12) : 0) > bufLen) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "buflen isn't enough for delta and len"); + return 0; + } + if (13 == delta) + { + delta = 13 + buffer[i++]; + } + else if (14 == delta) + { + delta = 269 + buffer[i] * 256 + buffer[i + 1]; + i += 2; + } + if (13 == optLen) + { + optLen = 13 + buffer[i++]; + } + else if (14 == optLen) + { + optLen = 269 + buffer[i] * 256 + buffer[i + 1]; + i += 2; + } + if (i + optLen > bufLen) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "buflen isn't enough for option value"); + return 0; + } + if (option) + { + *option = HAL_MALLOC(__GET_POS_ELEMENT(coap_optionNode_t, val) + optLen); + if (*option) + { + (*option)->type = delta + (referNode ? referNode->type : 0); + (*option)->len = optLen; + HAL_MEMCPY((*option)->val, &buffer[i], optLen); + } + } + return i + optLen; +} +/************************************************************************** +** 功能 @brief : 释放Coap_Message_t内存 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_coapMessageFree(Coap_Message_t *coapMsg) +{ + while (coapMsg->optionsHead) + { + coap_optionNode_t *node = __GET_STRUCT_BY_ELEMENT(coapMsg->optionsHead, coap_optionNode_t, head); + Quos_twllHeadDelete((TWLLHead_T **)&coapMsg->optionsHead, coapMsg->optionsHead); + HAL_FREE(node); + } + HAL_FREE(coapMsg->payload.val); + HAL_MEMSET(coapMsg, 0, sizeof(Coap_Message_t)); +} +/************************************************************************** +** 功能 @brief : 计算coap数据包长度 +** 输入 @param : coapMsg:coap消息结构体 +** 输出 @retval: +***************************************************************************/ +static quint16_t FUNCTION_ATTR_ROM quos_coapMessageFormat_len(const Coap_Message_t *coapMsg) +{ + quint16_t pkgLen = sizeof(coapMsg->head) + coapMsg->head.tokenLen; + coap_optionNode_t *referNode = NULL; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)coapMsg->optionsHead, temp, next) + { + coap_optionNode_t *node = quos_coapOptionIdDeltaMinGet(coapMsg->optionsHead, referNode); + if (NULL == node) + { + break; + } + pkgLen += quos_coapoptionFormat(node, referNode, NULL); + referNode = node; + } + if (coapMsg->payload.len) + { + pkgLen += 1 + coapMsg->payload.len; + } + Quos_logPrintf(LSDK_COAP, LL_DBG, "coap msg format len:%u", pkgLen); + return pkgLen; +} + +/************************************************************************** +** 功能 @brief : 将coap消息结构体格式化成十六进制字节流 +** 输入 @param : coapMsg:coap消息结构体,buffer:内部分配空间缓存格式化后的字节流 +** 输出 @retval: 格式化后字节流长度 +***************************************************************************/ +static quint16_t FUNCTION_ATTR_ROM quos_coapMessageFormat(const Coap_Message_t *coapMsg, quint8_t **buffer) +{ + quint16_t len = quos_coapMessageFormat_len(coapMsg); + quint8_t *buf = HAL_MALLOC(len); + if (NULL == buf) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "format malloc fail"); + return 0; + } + *buffer = buf; + len = 0; + + buf[len++] = (coapMsg->head.ver << 6) | (coapMsg->head.type << 4) | coapMsg->head.tokenLen; + buf[len++] = coapMsg->head.code; + buf[len++] = coapMsg->head.mid >> 8; + buf[len++] = coapMsg->head.mid & 0xFF; + + HAL_MEMCPY(buf + len, coapMsg->token, coapMsg->head.tokenLen); + len += coapMsg->head.tokenLen; + + coap_optionNode_t *refNode = NULL; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA((TWLLHead_T *)coapMsg->optionsHead, temp, next) + { + coap_optionNode_t *node = quos_coapOptionIdDeltaMinGet(coapMsg->optionsHead, refNode); + if (NULL == node) + { + break; + } + len += quos_coapoptionFormat(node, refNode, buf + len); + refNode = node; + } + + if (coapMsg->payload.len) + { + buf[len++] = 0xFF; + HAL_MEMCPY(buf + len, coapMsg->payload.val, coapMsg->payload.len); + len += coapMsg->payload.len; + } + return len; +} + +/************************************************************************** +** 功能 @brief : 检查coap十六进制流是否时有效coap数据 +** 输入 @param : buffer:coap十六进制流 +** 输出 @retval: 返回option有效个数,<0时代表buffer非法coap数据 +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_coapMessageCheck(const quint8_t *buffer, quint16_t len, quint16_t *optSize) +{ + quint16_t i = 4; + if (optSize) + *optSize = 0; + if (NULL == buffer || 0 == len || len < (i + (buffer[0] & 0xF))) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "param is invalid buffer:%p len:%u tokenLen:%u", buffer, len, buffer[0] & 0xF); + return FALSE; + } + i += buffer[0] & 0xF; + while (1) + { + if (i == len) + { + if (optSize) + { + Quos_logPrintf(LSDK_COAP, LL_DBG, "optsize:%u", *optSize); + } + return TRUE; + } + if (0xFF == buffer[i]) + { + return i + 1 < len ? TRUE : FALSE; + } + quint16_t optlen = quos_coapoptionUnformat(buffer + i, len - i, NULL, NULL); + if (0 == optlen) + { + return FALSE; + } + i += optlen; + if (optSize) + (*optSize) += 1; + } +} +/************************************************************************** +** 功能 @brief : 将十六进制流转成coap结构体 +** 输入 @param : buffer:源数据十六进制流,coap:转换后的coap结构体 +** 输出 @retval: TRUE:成功 FLASE:无效coap数据 +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_coapMessageUnformat(const quint8_t *buffer, quint16_t bufLen, Coap_Message_t *coapMsg) +{ + quint16_t optSize = 0; + Quos_logHexDump(LSDK_COAP, LL_DUMP, "source", (void *)buffer, bufLen); + if (NULL == coapMsg || FALSE == quos_coapMessageCheck(buffer, bufLen, &optSize)) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "param is invalid"); + return FALSE; + } + quint16_t offset = 1; + HAL_MEMSET(coapMsg, 0, sizeof(Coap_Message_t)); + coapMsg->head.ver = (buffer[0] >> 6) & 0x03; + coapMsg->head.type = (buffer[0] >> 4) & 0x03; + coapMsg->head.tokenLen = (buffer[0] >> 0) & 0x0F; + coapMsg->head.code = buffer[offset++]; + coapMsg->head.mid = ((quint16_t)buffer[offset] << 8) + (quint16_t)buffer[offset + 1]; + offset += 2; + if (bufLen < offset + coapMsg->head.tokenLen) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "buflen[%u] isn't enough to token[%u+%u]", bufLen, offset, coapMsg->head.tokenLen); + return FALSE; + } + HAL_MEMCPY(coapMsg->token, &buffer[offset], coapMsg->head.tokenLen); + offset += coapMsg->head.tokenLen; + + Quos_logPrintf(LSDK_COAP, LL_DBG, "ver[%u] type[%s] code[%s] mid[%u]", coapMsg->head.ver, COAP_HEAD_TYPE_STRING(coapMsg->head.type), COAP_HEAD_CODE_STRING(coapMsg->head.code), coapMsg->head.mid); + Quos_logHexDump(LSDK_COAP, LL_DUMP, "token", coapMsg->token, coapMsg->head.tokenLen); + + coap_optionNode_t *referNode = NULL; + while (1) + { + quint16_t optLen; + coap_optionNode_t *option; + if (offset == bufLen) + { + return TRUE; + } + else if (0xFF == buffer[offset]) + { + offset++; + if (bufLen > offset) + { + coapMsg->payload.len = bufLen - offset; + coapMsg->payload.val = HAL_MALLOC(coapMsg->payload.len); + if (NULL == coapMsg->payload.val) + { + break; + } + HAL_MEMCPY(coapMsg->payload.val, buffer + offset, coapMsg->payload.len); + return TRUE; + } + else + { + break; + } + } + else if ((optLen = quos_coapoptionUnformat(buffer + offset, bufLen - offset, &option, referNode)) == 0 && NULL == option) + { + break; + } + referNode = option; + offset += optLen; + Quos_twllHeadAdd((TWLLHead_T **)&coapMsg->optionsHead, &option->head); + Quos_logPrintf(LSDK_COAP, LL_DBG, "option type[%s]", COAP_OPTION_TYPE_STRING(option->type)); + Quos_logHexDump(LSDK_COAP, LL_DUMP, "option val", option->val, option->len); + } + Quos_logPrintf(LSDK_COAP, LL_ERR, "unformat fail"); + Quos_coapMessageFree(coapMsg); + return FALSE; +} +/************************************************************************** +** 功能 @brief : coap接收数据处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_coapSocketRecv(void *chlFd, const void *peer, quint32_t peerSize, Quos_socketRecvDataNode_t *recvData) +{ + UNUSED(peer); + UNUSED(peerSize); + + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + coapSock_t *coapSock = (coapSock_t *)chlNode->param; + if (NULL == recvData) + { + coapSock->notifyCB(chlFd, NULL, NULL); + return FALSE; + } + Quos_logHexDump(LSDK_COAP, LL_DUMP, "coap recv", recvData->Buf, recvData->bufLen); + Coap_Message_t coapMsg; + HAL_MEMSET(&coapMsg, 0, sizeof(Coap_Message_t)); + if (FALSE == quos_coapMessageUnformat(recvData->Buf, recvData->bufLen, &coapMsg)) + { + return FALSE; + } + if (COAP_HTYPE_ACK == coapMsg.head.type) + { + Quos_socketTxAck(chlFd, coapSock->peer, coapMsg.head.mid, &coapMsg); + } + else + { + Coap_Message_t retCoapMsg; + HAL_MEMSET(&retCoapMsg, 0, sizeof(Coap_Message_t)); + Quos_coapHeadSet(&retCoapMsg, COAP_HTYPE_ACK, COAP_HCODE_PRECONDITION_FAILED_412, coapMsg.head.mid, coapMsg.head.tokenLen, coapMsg.token); + if (TRUE == coapSock->notifyCB(chlFd, &coapMsg, &retCoapMsg)) + { + Quos_coapMsgSend(chlFd, NULL, &retCoapMsg, NULL, TRUE); + } + } + + Quos_coapMessageFree(&coapMsg); + return TRUE; +} +/************************************************************************** +** 功能 @brief : coap 资源释放 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_coapSocketParamFree(void *param) +{ + coapSock_t *coapSock = (coapSock_t *)param; + HAL_FREE(coapSock->peer); + HAL_FREE(coapSock); +} +/************************************************************************** +** 功能 @brief : coap初始化 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_coapInit(void **chlFdPoint, const char *url, coapRecvNotify_f notifyCb) +{ + urlAnalyze_t urlA; + if (NULL == url) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "param invalid:url[%p]", url); + return FALSE; + } + if (FALSE == (Quos_urlAnalyze(url, &urlA))) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "url analyze fail"); + return FALSE; + } + urlA.port = urlA.port ? urlA.port : (urlA.isSecure ? 5684 : 5683); + + coapSock_t *coapSock = HAL_MALLOC(sizeof(coapSock_t)); + if (NULL == coapSock) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "malloc coapSock fail"); + return FALSE; + } + HAL_MEMSET(coapSock, 0, sizeof(coapSock_t)); + coapSock->notifyCB = notifyCb; + coapSock->mid = 1; + Quos_socketChlInfoNode_t chlInfo; + HAL_MEMSET(&chlInfo, 0, sizeof(Quos_socketChlInfoNode_t)); + if (urlA.isSecure) + { +#if (SDK_ENABLE_TLS == 1) + chlInfo.sockFd = Qhal_udpSslInit(&chlInfo.type, 0, urlA.hostname, urlA.port); +#else + chlInfo.sockFd = SOCKET_FD_INVALID; +#endif + } + else + { + chlInfo.sockFd = Qhal_udpInit(&chlInfo.type, 0, urlA.hostname, urlA.port, &coapSock->peer); + } + if (SOCKET_FD_INVALID == chlInfo.sockFd) + { + Quos_logPrintf(LSDK_COAP, LL_ERR, "coap conneted fail:%s[%u]", urlA.hostname, urlA.port); + return FALSE; + } + + chlInfo.io.send = Qhal_sockWrite; + chlInfo.send.txCnt = QUOS_COAP_RESEND_TIME; + chlInfo.send.timeout = QUOS_COAP_SEND_TIMEOUT; + chlInfo.recvDataFunc = quos_coapSocketRecv; + chlInfo.io.close = Qhal_sockClose; + chlInfo.paramFree = quos_coapSocketParamFree; + chlInfo.param = coapSock; + + void *chlFd = Quos_socketChannelAdd(chlFdPoint, chlInfo); + if (NULL == chlFd) + { + Qhal_sockClose(chlInfo.sockFd, chlInfo.type); + HAL_FREE(coapSock->peer); + HAL_FREE(coapSock); + Quos_logPrintf(LSDK_COAP, LL_ERR, "add socket Channel fail"); + return FALSE; + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : coap message消息发送,回调recvCB()->recvData返回Coap_Message_t + 消息指针 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_coapMsgSend(void *chlFd, const char *path, Coap_Message_t *coapMsg, socketRecvNodeCb_f recvCB, qbool isAck) +{ + if (NULL == chlFd) + { + Quos_coapMessageFree(coapMsg); + Quos_logPrintf(LSDK_COAP, LL_ERR, "param invalid;chlFd[%p]", chlFd); + return FALSE; + } + + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + coapSock_t *coapSock = (coapSock_t *)chlNode->param; + if (COAP_HTYPE_CON == coapMsg->head.type || COAP_HTYPE_NON == coapMsg->head.type) + { + coapMsg->head.mid = ++coapSock->mid; + } + if (FALSE == Quos_coapOptionSetPath(coapMsg, path)) + { + Quos_coapMessageFree(coapMsg); + Quos_logPrintf(LSDK_COAP, LL_ERR, "path to option fail"); + return FALSE; + } + quint8_t *output = NULL; + quint16_t outLen = quos_coapMessageFormat(coapMsg, &output); + Quos_coapMessageFree(coapMsg); + if (0 == outLen) + { + return FALSE; + } + else if (isAck) + { + return Quos_socketTxDisorder(chlFd, coapSock->peer, output, outLen); + } + else + { + return Quos_socketTx(chlFd, coapSock->peer, 0, 0, NULL, recvCB, coapSock->mid, output, outLen, NULL); + } +} +#endif \ No newline at end of file diff --git a/kernel/quos_coap.h b/kernel/quos_coap.h index 29f3590..07dfa19 100644 --- a/kernel/quos_coap.h +++ b/kernel/quos_coap.h @@ -1,216 +1,212 @@ -#ifndef __QUOS_COAP_H__ -#define __QUOS_COAP_H__ -#include "quos_config.h" -#include "quos_twll.h" -#include "quos_socket.h" -#if (SDK_ENABLE_COAP == 1) - -typedef enum -{ - COAP_HTYPE_CON, /* confirmables */ - COAP_HTYPE_NON, /* non-confirmables */ - COAP_HTYPE_ACK, /* acknowledgements */ - COAP_HTYPE_RST /* reset */ -} coapHeadType_t; -#define COAP_HEAD_TYPE_STRING(X) \ - ( \ - (X == COAP_HTYPE_CON) ? "COAP_HTYPE_CON" : (X == COAP_HTYPE_NON) ? "COAP_HTYPE_NON" \ - : (X == COAP_HTYPE_ACK) ? "COAP_HTYPE_ACK" \ - : (X == COAP_HTYPE_RST) ? "COAP_HTYPE_RST" \ - : "Unknown") -typedef enum -{ -#define COAP_HEAD_CODE(X, Y) (((X) << 5) + Y) - COAP_HCODE_GET = COAP_HEAD_CODE(0, 1), - COAP_HCODE_POST = COAP_HEAD_CODE(0, 2), - COAP_HCODE_PUT = COAP_HEAD_CODE(0, 3), - COAP_HCODE_DELETE = COAP_HEAD_CODE(0, 4), - - COAP_HCODE_CREATED_201 = COAP_HEAD_CODE(2, 1), /* CREATED */ - COAP_HCODE_DELETED_202 = COAP_HEAD_CODE(2, 2), /* DELETED */ - COAP_HCODE_VALID_203 = COAP_HEAD_CODE(2, 3), /* NOT_MODIFIED */ - COAP_HCODE_CHANGED_204 = COAP_HEAD_CODE(2, 4), /* CHANGED */ - COAP_HCODE_CONTENT_205 = COAP_HEAD_CODE(2, 5), /* OK */ - - COAP_HCODE_BAD_REQUEST_400 = COAP_HEAD_CODE(4, 0), /* BAD_REQUEST */ - COAP_HCODE_UNAUTHORIZED_401 = COAP_HEAD_CODE(4, 1), /* UNAUTHORIZED */ - COAP_HCODE_BAD_OPTION_402 = COAP_HEAD_CODE(4, 2), /* BAD_OPTION */ - COAP_HCODE_FORBIDDEN_403 = COAP_HEAD_CODE(4, 3), /* FORBIDDEN */ - COAP_HCODE_NOT_FOUND_404 = COAP_HEAD_CODE(4, 4), /* NOT_FOUND */ - COAP_HCODE_METHOD_NOT_ALLOWED_405 = COAP_HEAD_CODE(4, 5), /* METHOD_NOT_ALLOWED */ - COAP_HCODE_NOT_ACCEPTABLE_406 = COAP_HEAD_CODE(4, 6), /* NOT_ACCEPTABLE */ - COAP_HCODE_PRECONDITION_FAILED_412 = COAP_HEAD_CODE(4, 12), /* BAD_REQUEST */ - COAP_HCODE_REQUEST_ENTITY_TOO_LARGE_413 = COAP_HEAD_CODE(4, 13), /* REQUEST_ENTITY_TOO_LARGE */ - COAP_HCODE_UNSUPPORTED_MEDIA_TYPE_415 = COAP_HEAD_CODE(4, 15), /* UNSUPPORTED_MEDIA_TYPE */ - - COAP_HCODE_INTERNAL_SERVER_ERROR_500 = COAP_HEAD_CODE(5, 0), /* INTERNAL_SERVER_ERROR */ - COAP_HCODE_NOT_IMPLEMENTED_501 = COAP_HEAD_CODE(5, 1), /* NOT_IMPLEMENTED */ - COAP_HCODE_BAD_GATEWAY_502 = COAP_HEAD_CODE(5, 2), /* BAD_GATEWAY */ - COAP_HCODE_SERVICE_UNAVAILABLE_503 = COAP_HEAD_CODE(5, 3), /* SERVICE_UNAVAILABLE */ - COAP_HCODE_GATEWAY_TIMEOUT_504 = COAP_HEAD_CODE(5, 4), /* GATEWAY_TIMEOUT */ - COAP_HCODE_PROXYING_NOT_SUPPORTED_505 = COAP_HEAD_CODE(5, 5), /* PROXYING_NOT_SUPPORTED */ -} coapHeadCode_t; -#define COAP_HEAD_CODE_STRING(X) \ - ( \ - (X == COAP_HCODE_GET) ? "COAP_HCODE_GET" : (X == COAP_HCODE_POST) ? "COAP_HCODE_POST" \ - : (X == COAP_HCODE_PUT) ? "COAP_HCODE_PUT" \ - : (X == COAP_HCODE_DELETE) ? "COAP_HCODE_DELETE" \ - : (X == COAP_HCODE_CREATED_201) ? "COAP_HCODE_CREATED_201" \ - : (X == COAP_HCODE_DELETED_202) ? "COAP_HCODE_DELETED_202" \ - : (X == COAP_HCODE_VALID_203) ? "COAP_HCODE_VALID_203" \ - : (X == COAP_HCODE_CHANGED_204) ? "COAP_HCODE_CHANGED_204" \ - : (X == COAP_HCODE_CONTENT_205) ? "COAP_HCODE_CONTENT_205" \ - : (X == COAP_HCODE_BAD_REQUEST_400) ? "COAP_HCODE_BAD_REQUEST_400" \ - : (X == COAP_HCODE_UNAUTHORIZED_401) ? "COAP_HCODE_UNAUTHORIZED_401" \ - : (X == COAP_HCODE_BAD_OPTION_402) ? "COAP_HCODE_BAD_OPTION_402" \ - : (X == COAP_HCODE_FORBIDDEN_403) ? "COAP_HCODE_FORBIDDEN_403" \ - : (X == COAP_HCODE_NOT_FOUND_404) ? "COAP_HCODE_NOT_FOUND_404" \ - : (X == COAP_HCODE_METHOD_NOT_ALLOWED_405) ? "COAP_HCODE_METHOD_NOT_ALLOWED_405" \ - : (X == COAP_HCODE_NOT_ACCEPTABLE_406) ? "COAP_HCODE_NOT_ACCEPTABLE_406" \ - : (X == COAP_HCODE_PRECONDITION_FAILED_412) ? "COAP_HCODE_PRECONDITION_FAILED_412" \ - : (X == COAP_HCODE_REQUEST_ENTITY_TOO_LARGE_413) ? "COAP_HCODE_REQUEST_ENTITY_TOO_LARGE_413" \ - : (X == COAP_HCODE_UNSUPPORTED_MEDIA_TYPE_415) ? "COAP_HCODE_UNSUPPORTED_MEDIA_TYPE_415" \ - : (X == COAP_HCODE_INTERNAL_SERVER_ERROR_500) ? "COAP_HCODE_INTERNAL_SERVER_ERROR_500" \ - : (X == COAP_HCODE_NOT_IMPLEMENTED_501) ? "COAP_HCODE_NOT_IMPLEMENTED_501" \ - : (X == COAP_HCODE_BAD_GATEWAY_502) ? "COAP_HCODE_BAD_GATEWAY_502" \ - : (X == COAP_HCODE_SERVICE_UNAVAILABLE_503) ? "COAP_HCODE_SERVICE_UNAVAILABLE_503" \ - : (X == COAP_HCODE_GATEWAY_TIMEOUT_504) ? "COAP_HCODE_GATEWAY_TIMEOUT_504" \ - : (X == COAP_HCODE_PROXYING_NOT_SUPPORTED_505) ? "COAP_HCODE_PROXYING_NOT_SUPPORTED_505" \ - : "Unknown") -/* CoAP header options */ -typedef enum -{ - COAP_OTYPE_IF_MATCH = 1, /* 0-8 B */ - COAP_OTYPE_URI_HOST = 3, /* 1-255 B */ - COAP_OTYPE_ETAG = 4, /* 1-8 B */ - COAP_OTYPE_IF_NONE_MATCH = 5, /* 0 B */ - COAP_OTYPE_OBSERVE = 6, /* 0-3 B */ - COAP_OTYPE_URI_PORT = 7, /* 0-2 B */ - COAP_OTYPE_LOCATION_PATH = 8, /* 0-255 B */ - COAP_OTYPE_URI_PATH = 11, /* 0-255 B */ - COAP_OTYPE_CONTENT_TYPE = 12, /* 0-2 B */ - COAP_OTYPE_MAX_AGE = 14, /* 0-4 B */ - COAP_OTYPE_URI_QUERY = 15, /* 0-270 B */ - COAP_OTYPE_ACCEPT = 17, /* 0-2 B */ - COAP_OTYPE_TOKEN = 19, /* 1-8 B */ - COAP_OTYPE_LOCATION_QUERY = 20, /* 1-270 B */ - COAP_OTYPE_BLOCK2 = 23, /* 1-3 B */ - COAP_OTYPE_BLOCK1 = 27, /* 1-3 B */ - COAP_OTYPE_SIZE = 28, /* 0-4 B */ - COAP_OTYPE_PROXY_URI = 35, /* 1-270 B */ -} coapOptionType_t; -#define COAP_OPTION_TYPE_STRING(X) \ - ( \ - (X == COAP_OTYPE_IF_MATCH) ? "OTYPE_IF_MATCH" : (X == COAP_OTYPE_URI_HOST) ? "OTYPE_URI_HOST" \ - : (X == COAP_OTYPE_ETAG) ? "OTYPE_ETAG" \ - : (X == COAP_OTYPE_IF_NONE_MATCH) ? "OTYPE_IF_NONE_MATCH" \ - : (X == COAP_OTYPE_OBSERVE) ? "OTYPE_OBSERVE" \ - : (X == COAP_OTYPE_URI_PORT) ? "OTYPE_URI_PORT" \ - : (X == COAP_OTYPE_LOCATION_PATH) ? "OTYPE_LOCATION_PATH" \ - : (X == COAP_OTYPE_URI_PATH) ? "OTYPE_URI_PATH" \ - : (X == COAP_OTYPE_CONTENT_TYPE) ? "OTYPE_CONTENT_TYPE" \ - : (X == COAP_OTYPE_MAX_AGE) ? "OTYPE_MAX_AGE" \ - : (X == COAP_OTYPE_URI_QUERY) ? "OTYPE_URI_QUERY" \ - : (X == COAP_OTYPE_ACCEPT) ? "OTYPE_ACCEPT" \ - : (X == COAP_OTYPE_TOKEN) ? "OTYPE_TOKEN" \ - : (X == COAP_OTYPE_LOCATION_QUERY) ? "OTYPE_LOCATION_QUERY" \ - : (X == COAP_OTYPE_BLOCK2) ? "OTYPE_BLOCK2" \ - : (X == COAP_OTYPE_BLOCK1) ? "OTYPE_BLOCK1" \ - : (X == COAP_OTYPE_SIZE) ? "OTYPE_SIZE" \ - : (X == COAP_OTYPE_PROXY_URI) ? "OTYPE_PROXY_URI" \ - : "Unknown") - -/* CoAP Content-Types */ -typedef enum -{ - COAP_OCTYPE_TEXT_PLAIN = 0, - COAP_OCTYPE_TEXT_XML = 1, - COAP_OCTYPE_TEXT_CSV = 2, - COAP_OCTYPE_TEXT_HTML = 3, - COAP_OCTYPE_IMAGE_GIF = 21, - COAP_OCTYPE_IMAGE_JPEG = 22, - COAP_OCTYPE_IMAGE_PNG = 23, - COAP_OCTYPE_IMAGE_TIFF = 24, - COAP_OCTYPE_AUDIO_RAW = 25, - COAP_OCTYPE_VIDEO_RAW = 26, - COAP_OCTYPE_APP_LINK_FORMAT = 40, - COAP_OCTYPE_APP_XML = 41, - COAP_OCTYPE_APP_OCTET_STREAM = 42, - COAP_OCTYPE_APP_RDF_XML = 43, - COAP_OCTYPE_APP_SOAP_XML = 44, - COAP_OCTYPE_APP_ATOM_XML = 45, - COAP_OCTYPE_APP_XMPP_XML = 46, - COAP_OCTYPE_APP_EXI = 47, - COAP_OCTYPE_APP_FASTINFOSET = 48, - COAP_OCTYPE_APP_SOAP_FASTINFOSET = 49, - COAP_OCTYPE_APP_JSON = 50, - COAP_OCTYPE_APP_X_OBIX_BINARY = 51, -} coapOptionContentType_t; -#define COAP_OPTION_CONTENT_TYPE_STRING(X) \ - ( \ - (X == COAP_OCTYPE_TEXT_PLAIN) ? "OCTYPE_TEXT_PLAIN" : (X == COAP_OCTYPE_TEXT_XML) ? "OCTYPE_TEXT_XML" \ - : (X == COAP_OCTYPE_TEXT_CSV) ? "OCTYPE_TEXT_CSV" \ - : (X == COAP_OCTYPE_TEXT_HTML) ? "OCTYPE_TEXT_HTML" \ - : (X == COAP_OCTYPE_IMAGE_GIF) ? "OCTYPE_IMAGE_GIF" \ - : (X == COAP_OCTYPE_IMAGE_JPEG) ? "OCTYPE_IMAGE_JPEG" \ - : (X == COAP_OCTYPE_IMAGE_PNG) ? "OCTYPE_IMAGE_PNG" \ - : (X == COAP_OCTYPE_IMAGE_TIFF) ? "OCTYPE_IMAGE_TIFF" \ - : (X == COAP_OCTYPE_AUDIO_RAW) ? "OCTYPE_AUDIO_RAW" \ - : (X == COAP_OCTYPE_VIDEO_RAW) ? "OCTYPE_VIDEO_RAW" \ - : (X == COAP_OCTYPE_APP_LINK_FORMAT) ? "OCTYPE_APP_LINK_FORMAT" \ - : (X == COAP_OCTYPE_APP_XML) ? "OCTYPE_APP_XML" \ - : (X == COAP_OCTYPE_APP_OCTET_STREAM) ? "OCTYPE_APP_OCTET_STREAM" \ - : (X == COAP_OCTYPE_APP_RDF_XML) ? "OCTYPE_APP_RDF_XML" \ - : (X == COAP_OCTYPE_APP_SOAP_XML) ? "OCTYPE_APP_SOAP_XML" \ - : (X == COAP_OCTYPE_APP_ATOM_XML) ? "OCTYPE_APP_ATOM_XML" \ - : (X == COAP_OCTYPE_APP_XMPP_XML) ? "OCTYPE_APP_XMPP_XML" \ - : (X == COAP_OCTYPE_APP_EXI) ? "OCTYPE_APP_EXI" \ - : (X == COAP_OCTYPE_APP_FASTINFOSET) ? "OCTYPE_APP_FASTINFOSET" \ - : (X == COAP_OCTYPE_APP_SOAP_FASTINFOSET) ? "OCTYPE_APP_SOAP_FASTINFOSET" \ - : (X == COAP_OCTYPE_APP_JSON) ? "OCTYPE_APP_JSON" \ - : (X == COAP_OCTYPE_APP_X_OBIX_BINARY) ? "OCTYPE_APP_X_OBIX_BINARY" \ - : "Unknown") - -typedef struct -{ - TWLLHead_T head; - coapOptionType_t type; - quint16_t len; - quint8_t val[1]; -} coap_optionNode_t; - -typedef struct -{ - quint32_t ver : 2; - coapHeadType_t type : 2; - quint32_t tkl : 4; - coapHeadCode_t code : 8; - quint32_t mid : 16; -} coap_MessageHead_t; - -typedef struct -{ - coap_MessageHead_t head; - quint8_t token[8]; - TWLLHead_T *optionsHead; - struct - { - quint16_t len; - quint8_t *val; - } payload; -} Coap_Message_t; - -typedef qbool (*coapRecvNotify_f)(void *chlFd, const Coap_Message_t *coapMsg, Coap_Message_t *retCoapMsg); - -quint16_t Quos_coapMessageFormat(const Coap_Message_t *coapMsg, quint8_t **buffer); -qbool Quos_coapMessageUnformat(const quint8_t *buffer, quint16_t bufLen, Coap_Message_t *coapMsg); -void Quos_coapHeadSet(Coap_Message_t *coapMsg, coapHeadType_t type, coapHeadCode_t code, quint16_t mid, quint32_t tkl, quint8_t *token); -char *Quos_coapOptionGetPath(TWLLHead_T *optionsHead); -qbool Quos_coapOptionFromPath(TWLLHead_T **optionHead, const char *path); -qbool Quos_coapOptionSetOpaque(TWLLHead_T **optionHead, coapOptionType_t type, void *val, quint16_t valLen); -qbool Quos_coapOptionSetNumber(TWLLHead_T **optionHead, coapOptionType_t type, quint32_t number); -void Quos_coapMessageFree(Coap_Message_t *coapMsg); -qbool Quos_coapInit(void **chlFdPoint, const char *url, coapRecvNotify_f notifyCb); -qbool Quos_coapMessageSend(void *chlFd, const char *path, Coap_Message_t *coapMsg, socketRecvNodeCb_f recvCB); -#endif -#endif +#ifndef __QUOS_COAP_H__ +#define __QUOS_COAP_H__ +#include "quos_config.h" +#include "quos_twll.h" +#include "quos_socket.h" +#if (SDK_ENABLE_COAP == 1) + +typedef enum +{ + COAP_HTYPE_CON, /* confirmables */ + COAP_HTYPE_NON, /* non-confirmables */ + COAP_HTYPE_ACK, /* acknowledgements */ + COAP_HTYPE_RST /* reset */ +} coapHeadType_t; +#define COAP_HEAD_TYPE_STRING(X) \ + ( \ + (X == COAP_HTYPE_CON) ? "COAP_HTYPE_CON" : (X == COAP_HTYPE_NON) ? "COAP_HTYPE_NON" \ + : (X == COAP_HTYPE_ACK) ? "COAP_HTYPE_ACK" \ + : (X == COAP_HTYPE_RST) ? "COAP_HTYPE_RST" \ + : "Unknown") +typedef enum +{ +#define COAP_HEAD_CODE(X, Y) (((X) << 5) + Y) + COAP_HCODE_EMPTY = COAP_HEAD_CODE(0, 0), + COAP_HCODE_GET = COAP_HEAD_CODE(0, 1), + COAP_HCODE_POST = COAP_HEAD_CODE(0, 2), + COAP_HCODE_PUT = COAP_HEAD_CODE(0, 3), + COAP_HCODE_DELETE = COAP_HEAD_CODE(0, 4), + + COAP_HCODE_CREATED_201 = COAP_HEAD_CODE(2, 1), /* CREATED */ + COAP_HCODE_DELETED_202 = COAP_HEAD_CODE(2, 2), /* DELETED */ + COAP_HCODE_VALID_203 = COAP_HEAD_CODE(2, 3), /* NOT_MODIFIED */ + COAP_HCODE_CHANGED_204 = COAP_HEAD_CODE(2, 4), /* CHANGED */ + COAP_HCODE_CONTENT_205 = COAP_HEAD_CODE(2, 5), /* OK */ + + COAP_HCODE_BAD_REQUEST_400 = COAP_HEAD_CODE(4, 0), /* BAD_REQUEST */ + COAP_HCODE_UNAUTHORIZED_401 = COAP_HEAD_CODE(4, 1), /* UNAUTHORIZED */ + COAP_HCODE_BAD_OPTION_402 = COAP_HEAD_CODE(4, 2), /* BAD_OPTION */ + COAP_HCODE_FORBIDDEN_403 = COAP_HEAD_CODE(4, 3), /* FORBIDDEN */ + COAP_HCODE_NOT_FOUND_404 = COAP_HEAD_CODE(4, 4), /* NOT_FOUND */ + COAP_HCODE_METHOD_NOT_ALLOWED_405 = COAP_HEAD_CODE(4, 5), /* METHOD_NOT_ALLOWED */ + COAP_HCODE_NOT_ACCEPTABLE_406 = COAP_HEAD_CODE(4, 6), /* NOT_ACCEPTABLE */ + COAP_HCODE_PRECONDITION_FAILED_412 = COAP_HEAD_CODE(4, 12), /* BAD_REQUEST */ + COAP_HCODE_REQUEST_ENTITY_TOO_LARGE_413 = COAP_HEAD_CODE(4, 13), /* REQUEST_ENTITY_TOO_LARGE */ + COAP_HCODE_UNSUPPORTED_MEDIA_TYPE_415 = COAP_HEAD_CODE(4, 15), /* UNSUPPORTED_MEDIA_TYPE */ + + COAP_HCODE_INTERNAL_SERVER_ERROR_500 = COAP_HEAD_CODE(5, 0), /* INTERNAL_SERVER_ERROR */ + COAP_HCODE_NOT_IMPLEMENTED_501 = COAP_HEAD_CODE(5, 1), /* NOT_IMPLEMENTED */ + COAP_HCODE_BAD_GATEWAY_502 = COAP_HEAD_CODE(5, 2), /* BAD_GATEWAY */ + COAP_HCODE_SERVICE_UNAVAILABLE_503 = COAP_HEAD_CODE(5, 3), /* SERVICE_UNAVAILABLE */ + COAP_HCODE_GATEWAY_TIMEOUT_504 = COAP_HEAD_CODE(5, 4), /* GATEWAY_TIMEOUT */ + COAP_HCODE_PROXYING_NOT_SUPPORTED_505 = COAP_HEAD_CODE(5, 5), /* PROXYING_NOT_SUPPORTED */ +} coapHeadCode_t; +#define COAP_HEAD_CODE_STRING(X) \ + ( \ + (X == COAP_HCODE_EMPTY) ? "COAP_HCODE_EMPTY" : (X == COAP_HCODE_GET) ? "COAP_HCODE_GET" \ + : (X == COAP_HCODE_POST) ? "COAP_HCODE_POST" \ + : (X == COAP_HCODE_PUT) ? "COAP_HCODE_PUT" \ + : (X == COAP_HCODE_DELETE) ? "COAP_HCODE_DELETE" \ + : (X == COAP_HCODE_CREATED_201) ? "COAP_HCODE_CREATED_201" \ + : (X == COAP_HCODE_DELETED_202) ? "COAP_HCODE_DELETED_202" \ + : (X == COAP_HCODE_VALID_203) ? "COAP_HCODE_VALID_203" \ + : (X == COAP_HCODE_CHANGED_204) ? "COAP_HCODE_CHANGED_204" \ + : (X == COAP_HCODE_CONTENT_205) ? "COAP_HCODE_CONTENT_205" \ + : (X == COAP_HCODE_BAD_REQUEST_400) ? "COAP_HCODE_BAD_REQUEST_400" \ + : (X == COAP_HCODE_UNAUTHORIZED_401) ? "COAP_HCODE_UNAUTHORIZED_401" \ + : (X == COAP_HCODE_BAD_OPTION_402) ? "COAP_HCODE_BAD_OPTION_402" \ + : (X == COAP_HCODE_FORBIDDEN_403) ? "COAP_HCODE_FORBIDDEN_403" \ + : (X == COAP_HCODE_NOT_FOUND_404) ? "COAP_HCODE_NOT_FOUND_404" \ + : (X == COAP_HCODE_METHOD_NOT_ALLOWED_405) ? "COAP_HCODE_METHOD_NOT_ALLOWED_405" \ + : (X == COAP_HCODE_NOT_ACCEPTABLE_406) ? "COAP_HCODE_NOT_ACCEPTABLE_406" \ + : (X == COAP_HCODE_PRECONDITION_FAILED_412) ? "COAP_HCODE_PRECONDITION_FAILED_412" \ + : (X == COAP_HCODE_REQUEST_ENTITY_TOO_LARGE_413) ? "COAP_HCODE_REQUEST_ENTITY_TOO_LARGE_413" \ + : (X == COAP_HCODE_UNSUPPORTED_MEDIA_TYPE_415) ? "COAP_HCODE_UNSUPPORTED_MEDIA_TYPE_415" \ + : (X == COAP_HCODE_INTERNAL_SERVER_ERROR_500) ? "COAP_HCODE_INTERNAL_SERVER_ERROR_500" \ + : (X == COAP_HCODE_NOT_IMPLEMENTED_501) ? "COAP_HCODE_NOT_IMPLEMENTED_501" \ + : (X == COAP_HCODE_BAD_GATEWAY_502) ? "COAP_HCODE_BAD_GATEWAY_502" \ + : (X == COAP_HCODE_SERVICE_UNAVAILABLE_503) ? "COAP_HCODE_SERVICE_UNAVAILABLE_503" \ + : (X == COAP_HCODE_GATEWAY_TIMEOUT_504) ? "COAP_HCODE_GATEWAY_TIMEOUT_504" \ + : (X == COAP_HCODE_PROXYING_NOT_SUPPORTED_505) ? "COAP_HCODE_PROXYING_NOT_SUPPORTED_505" \ + : "Unknown") +/* CoAP header options */ +typedef enum +{ + COAP_OTYPE_IF_MATCH = 1, /* 0-8 B */ + COAP_OTYPE_URI_HOST = 3, /* 1-255 B */ + COAP_OTYPE_ETAG = 4, /* 1-8 B */ + COAP_OTYPE_IF_NONE_MATCH = 5, /* 0 B */ + COAP_OTYPE_OBSERVE = 6, /* 0-3 B */ + COAP_OTYPE_URI_PORT = 7, /* 0-2 B */ + COAP_OTYPE_LOCATION_PATH = 8, /* 0-255 B */ + COAP_OTYPE_URI_PATH = 11, /* 0-255 B */ + COAP_OTYPE_CONTENT_TYPE = 12, /* 0-2 B */ + COAP_OTYPE_MAX_AGE = 14, /* 0-4 B */ + COAP_OTYPE_URI_QUERY = 15, /* 0-270 B */ + COAP_OTYPE_ACCEPT = 17, /* 0-2 B */ + COAP_OTYPE_TOKEN = 19, /* 1-8 B */ + COAP_OTYPE_LOCATION_QUERY = 20, /* 1-270 B */ + COAP_OTYPE_BLOCK2 = 23, /* 1-3 B */ + COAP_OTYPE_BLOCK1 = 27, /* 1-3 B */ + COAP_OTYPE_SIZE = 28, /* 0-4 B */ + COAP_OTYPE_PROXY_URI = 35, /* 1-270 B */ +} coapOptionType_t; +#define COAP_OPTION_TYPE_STRING(X) \ + ( \ + (X == COAP_OTYPE_IF_MATCH) ? "OTYPE_IF_MATCH" : (X == COAP_OTYPE_URI_HOST) ? "OTYPE_URI_HOST" \ + : (X == COAP_OTYPE_ETAG) ? "OTYPE_ETAG" \ + : (X == COAP_OTYPE_IF_NONE_MATCH) ? "OTYPE_IF_NONE_MATCH" \ + : (X == COAP_OTYPE_OBSERVE) ? "OTYPE_OBSERVE" \ + : (X == COAP_OTYPE_URI_PORT) ? "OTYPE_URI_PORT" \ + : (X == COAP_OTYPE_LOCATION_PATH) ? "OTYPE_LOCATION_PATH" \ + : (X == COAP_OTYPE_URI_PATH) ? "OTYPE_URI_PATH" \ + : (X == COAP_OTYPE_CONTENT_TYPE) ? "OTYPE_CONTENT_TYPE" \ + : (X == COAP_OTYPE_MAX_AGE) ? "OTYPE_MAX_AGE" \ + : (X == COAP_OTYPE_URI_QUERY) ? "OTYPE_URI_QUERY" \ + : (X == COAP_OTYPE_ACCEPT) ? "OTYPE_ACCEPT" \ + : (X == COAP_OTYPE_TOKEN) ? "OTYPE_TOKEN" \ + : (X == COAP_OTYPE_LOCATION_QUERY) ? "OTYPE_LOCATION_QUERY" \ + : (X == COAP_OTYPE_BLOCK2) ? "OTYPE_BLOCK2" \ + : (X == COAP_OTYPE_BLOCK1) ? "OTYPE_BLOCK1" \ + : (X == COAP_OTYPE_SIZE) ? "OTYPE_SIZE" \ + : (X == COAP_OTYPE_PROXY_URI) ? "OTYPE_PROXY_URI" \ + : "Unknown") + +/* CoAP Content-Types */ +typedef enum +{ + COAP_OCTYPE_TEXT_PLAIN = 0, + COAP_OCTYPE_TEXT_XML = 1, + COAP_OCTYPE_TEXT_CSV = 2, + COAP_OCTYPE_TEXT_HTML = 3, + COAP_OCTYPE_IMAGE_GIF = 21, + COAP_OCTYPE_IMAGE_JPEG = 22, + COAP_OCTYPE_IMAGE_PNG = 23, + COAP_OCTYPE_IMAGE_TIFF = 24, + COAP_OCTYPE_AUDIO_RAW = 25, + COAP_OCTYPE_VIDEO_RAW = 26, + COAP_OCTYPE_APP_LINK_FORMAT = 40, + COAP_OCTYPE_APP_XML = 41, + COAP_OCTYPE_APP_OCTET_STREAM = 42, + COAP_OCTYPE_APP_RDF_XML = 43, + COAP_OCTYPE_APP_SOAP_XML = 44, + COAP_OCTYPE_APP_ATOM_XML = 45, + COAP_OCTYPE_APP_XMPP_XML = 46, + COAP_OCTYPE_APP_EXI = 47, + COAP_OCTYPE_APP_FASTINFOSET = 48, + COAP_OCTYPE_APP_SOAP_FASTINFOSET = 49, + COAP_OCTYPE_APP_JSON = 50, + COAP_OCTYPE_APP_X_OBIX_BINARY = 51, +} coapOptionContentType_t; +#define COAP_OPTION_CONTENT_TYPE_STRING(X) \ + ( \ + (X == COAP_OCTYPE_TEXT_PLAIN) ? "OCTYPE_TEXT_PLAIN" : (X == COAP_OCTYPE_TEXT_XML) ? "OCTYPE_TEXT_XML" \ + : (X == COAP_OCTYPE_TEXT_CSV) ? "OCTYPE_TEXT_CSV" \ + : (X == COAP_OCTYPE_TEXT_HTML) ? "OCTYPE_TEXT_HTML" \ + : (X == COAP_OCTYPE_IMAGE_GIF) ? "OCTYPE_IMAGE_GIF" \ + : (X == COAP_OCTYPE_IMAGE_JPEG) ? "OCTYPE_IMAGE_JPEG" \ + : (X == COAP_OCTYPE_IMAGE_PNG) ? "OCTYPE_IMAGE_PNG" \ + : (X == COAP_OCTYPE_IMAGE_TIFF) ? "OCTYPE_IMAGE_TIFF" \ + : (X == COAP_OCTYPE_AUDIO_RAW) ? "OCTYPE_AUDIO_RAW" \ + : (X == COAP_OCTYPE_VIDEO_RAW) ? "OCTYPE_VIDEO_RAW" \ + : (X == COAP_OCTYPE_APP_LINK_FORMAT) ? "OCTYPE_APP_LINK_FORMAT" \ + : (X == COAP_OCTYPE_APP_XML) ? "OCTYPE_APP_XML" \ + : (X == COAP_OCTYPE_APP_OCTET_STREAM) ? "OCTYPE_APP_OCTET_STREAM" \ + : (X == COAP_OCTYPE_APP_RDF_XML) ? "OCTYPE_APP_RDF_XML" \ + : (X == COAP_OCTYPE_APP_SOAP_XML) ? "OCTYPE_APP_SOAP_XML" \ + : (X == COAP_OCTYPE_APP_ATOM_XML) ? "OCTYPE_APP_ATOM_XML" \ + : (X == COAP_OCTYPE_APP_XMPP_XML) ? "OCTYPE_APP_XMPP_XML" \ + : (X == COAP_OCTYPE_APP_EXI) ? "OCTYPE_APP_EXI" \ + : (X == COAP_OCTYPE_APP_FASTINFOSET) ? "OCTYPE_APP_FASTINFOSET" \ + : (X == COAP_OCTYPE_APP_SOAP_FASTINFOSET) ? "OCTYPE_APP_SOAP_FASTINFOSET" \ + : (X == COAP_OCTYPE_APP_JSON) ? "OCTYPE_APP_JSON" \ + : (X == COAP_OCTYPE_APP_X_OBIX_BINARY) ? "OCTYPE_APP_X_OBIX_BINARY" \ + : "Unknown") + +typedef struct +{ + quint32_t ver : 2; + coapHeadType_t type : 2; + quint32_t tokenLen : 4; + coapHeadCode_t code : 8; + quint32_t mid : 16; +} coap_MessageHead_t; + +typedef struct +{ + coap_MessageHead_t head; + quint8_t token[8]; + void *optionsHead; + struct + { + quint16_t len; + void *val; + } payload; +} Coap_Message_t; + +typedef qbool (*coapRecvNotify_f)(void *chlFd, const Coap_Message_t *coapMsg, Coap_Message_t *retCoapMsg); + +void Quos_coapHeadSet(Coap_Message_t *coapMsg, coapHeadType_t type, coapHeadCode_t code, quint16_t mid, quint32_t tokenLen, const quint8_t *token); +char *Quos_coapOptionGetPath(const Coap_Message_t *coapMsg); +qbool Quos_coapOptionSetPath(Coap_Message_t *coapMsg, const char *path); +qbool Quos_coapOptionSetOpaque(Coap_Message_t *coapMsg, coapOptionType_t type, const void *val, quint16_t valLen); +qbool Quos_coapOptionGetOpaque(Coap_Message_t *coapMsg, coapOptionType_t type, const void **val, quint16_t *valLen); +qbool Quos_coapOptionSetNumber(Coap_Message_t *coapMsg, coapOptionType_t type, quint32_t number); +qbool Quos_coapOptionGetNumber(Coap_Message_t *coapMsg, coapOptionType_t type, quint32_t *number); +void Quos_coapPayloadSet(Coap_Message_t *coapMsg, const void *val, quint16_t valLen); +void Quos_coapMessageFree(Coap_Message_t *coapMsg); +qbool Quos_coapInit(void **chlFdPoint, const char *url, coapRecvNotify_f notifyCb); +qbool Quos_coapMsgSend(void *chlFd, const char *path, Coap_Message_t *coapMsg, socketRecvNodeCb_f recvCB, qbool isAck); + +#endif +#endif diff --git a/kernel/quos_config.h b/kernel/quos_config.h index 4982a84..100958d 100644 --- a/kernel/quos_config.h +++ b/kernel/quos_config.h @@ -1,52 +1,83 @@ -/* - * @Author: your name - * @Date: 2021-06-21 10:50:46 - * @LastEditTime: 2021-07-07 19:17:00 - * @LastEditors: Please set LastEditors - * @Description: In User Settings Edit - * @FilePath: \quecthing_pythonSDK\components\quecsdk\kernel\quos_config.h - */ -#ifndef __QUOS_CONFIG_H__ -#define __QUOS_CONFIG_H__ -#include "Qhal_types.h" - -#define SOCKET_FD_INVALID ((pointer_t)-1) -#define QUOS_DNS_HOSTNANE_MAX_LENGHT (64) /* DNS规定域名不能超过63字符*/ -#define QUOS_IP_ADDR_MAX_LEN (46) - -enum -{ - QUOS_SYSTEM_EVENT_NETCONNECTED = -1, - QUOS_SYSTEM_EVENT_NETDISCONNECT = -2, -}; -/* SDK 支持功能配置 */ -#define SDK_ENABLE_TLS 1 -#define SDK_ENABLE_HTTP 1 -#define SDK_ENABLE_LWM2M 0 -#define SDK_ENABLE_COAP 0 -#define SDK_ENABLE_MQTT 1 -#define SDK_ENABLE_SHA1 1 -#define SDK_ENABLE_SHA256 1 -#define SDK_ENABLE_MD5 1 -#define SDK_ENABLE_BASE64 1 -#define SDK_ENABLE_AES 1 -#define SDK_ENABLE_EVENT 1 -#define SDK_ENABLE_SIGNAL 1 -#define SDK_ENABLE_TIMER 1 -#define SDK_ENABLE_JSON 1 -#define SDK_ENABLE_DATASAFE 1 -#define SDK_ENABLE_LOGDUMP 0 - -/* LOG模块 PRINTF等级配置 */ -#define LSDK_STORE LL_ERR -#define LSDK_COAP LL_ERR -#define LSDK_ENCRP LL_ERR -#define LSDK_EVENT LL_DBG -#define LSDK_HTTP LL_DBG -#define LSDK_MQTT LL_DBG -#define LSDK_SIG LL_DBG -#define LSDK_SOCK LL_DBG -#define LSDK_TIMER LL_DBG -#define LSDK_LWM2M LL_ERR -#define LSDK_NET LL_ERR -#endif +#ifndef __QUOS_CONFIG_H__ +#define __QUOS_CONFIG_H__ +#include "Qhal_types.h" + +#define SOCKET_FD_INVALID ((pointer_t)-1) +#define QUOS_DNS_HOSTNANE_MAX_LENGHT (64) /* DNS规定域名不能超过63字符*/ +#define QUOS_IP_ADDR_MAX_LEN (46) +enum +{ + QUOS_SYSTEM_EVENT_NETWORK = -1, +}; +enum +{ + QUOS_SEVENT_NET_CONNECTED = 0, + QUOS_SEVENT_NET_DISCONNECT = 1, + QUOS_SEVENT_NET_CONNTIMEOUT = 2, +}; +/* SDK 支持功能配置 */ +#ifndef SDK_ENABLE_TLS +#define SDK_ENABLE_TLS 1 +#endif +#ifndef SDK_ENABLE_HTTP +#define SDK_ENABLE_HTTP 1 +#endif +#ifndef SDK_ENABLE_LWM2M +#define SDK_ENABLE_LWM2M 0 +#endif +#ifndef SDK_ENABLE_COAP +#define SDK_ENABLE_COAP 0 +#endif +#ifndef SDK_ENABLE_MQTT +#define SDK_ENABLE_MQTT 1 +#endif +#ifndef SDK_ENABLE_SHA1 +#define SDK_ENABLE_SHA1 0 +#endif +#ifndef SDK_ENABLE_SHA256 +#define SDK_ENABLE_SHA256 1 +#endif +#ifndef SDK_ENABLE_MD5 +#define SDK_ENABLE_MD5 1 +#endif +#ifndef SDK_ENABLE_BASE64 +#define SDK_ENABLE_BASE64 1 +#endif +#ifndef SDK_ENABLE_AES +#define SDK_ENABLE_AES 1 +#endif +#ifndef SDK_ENABLE_EVENT +#define SDK_ENABLE_EVENT 1 +#endif +#ifndef SDK_ENABLE_FIFO +#define SDK_ENABLE_FIFO 0 +#endif +#ifndef SDK_ENABLE_SIGNAL +#define SDK_ENABLE_SIGNAL 1 +#endif +#ifndef SDK_ENABLE_TIMER +#define SDK_ENABLE_TIMER 1 +#endif +#ifndef SDK_ENABLE_JSON +#define SDK_ENABLE_JSON 1 +#endif +#ifndef SDK_ENABLE_DATASAFE +#define SDK_ENABLE_DATASAFE 1 +#endif +#ifndef SDK_ENABLE_TWLL +#define SDK_ENABLE_TWLL 1 +#endif + +/* LOG模块 PRINTF等级配置 */ +#define LSDK_STORE LL_DBG +#define LSDK_COAP LL_DBG +#define LSDK_ENCRP LL_DBG +#define LSDK_EVENT LL_DBG +#define LSDK_HTTP LL_DBG +#define LSDK_MQTT LL_DBG +#define LSDK_SIG LL_DBG +#define LSDK_SOCK LL_DBG +#define LSDK_TIMER LL_DBG +#define LSDK_LWM2M LL_DBG +#define LSDK_NET LL_DBG +#endif diff --git a/kernel/quos_dataStore.c b/kernel/quos_dataStore.c new file mode 100644 index 0000000..e0ff04e --- /dev/null +++ b/kernel/quos_dataStore.c @@ -0,0 +1,416 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 文件或FLASH安全备份保存 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_dataStore.h" +#include "Quos_kernel.h" +#include "Qhal_driver.h" +#if (SDK_ENABLE_DATASAFE == 1) + +#define FILE_DSNAME_BAK_HEAD "_BAK" + +#ifndef QUOS_FILE_NAME_MAXLENGHT +#define QUOS_FILE_NAME_MAXLENGHT 256 /* 文件名最大长度 */ +#endif + +typedef struct +{ + quint32_t index; + quint8_t crc; + quint16_t len; + quint8_t buf[1]; /* 数据开始位置 */ +} SafeFlashHeadData_t; + +/************************************************************************** +** 功能 @brief : 安全写数据 +** 输入 @param : bak + buf:待写入的数据 + bufLen:待写入数据的长度 + aesKey必须是QUOS_AES_KEYLEN字节 +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_dsWrite(const char *filename, const void *buf, quint16_t bufLen, const char *aesKey) +{ + Quos_logPrintf(LSDK_STORE, LL_DBG, "filename[%s] bufLen=%u", filename, bufLen); + Quos_logHexDump(LSDK_STORE, LL_DUMP, "buf", buf, bufLen); + + if (NULL == filename || buf == NULL || 0 == bufLen) + { + return FALSE; + } + char *fileNew = (char *)filename; + char filenameBak[QUOS_FILE_NAME_MAXLENGHT + sizeof(FILE_DSNAME_BAK_HEAD)]; + HAL_SPRINTF(filenameBak, "%s", filename); + char *point = filenameBak + HAL_STRLEN(filenameBak); + while (point != filenameBak && '.' != *point) + { + point--; + } + if (point == filenameBak) + { + HAL_SPRINTF(filenameBak + HAL_STRLEN(filenameBak), "%s", FILE_DSNAME_BAK_HEAD); + } + else + { + HAL_MEMMOVE(point + HAL_STRLEN(FILE_DSNAME_BAK_HEAD), point, HAL_STRLEN(FILE_DSNAME_BAK_HEAD) + 1); + HAL_MEMCPY(point, FILE_DSNAME_BAK_HEAD, HAL_STRLEN(FILE_DSNAME_BAK_HEAD)); + } + + SafeFlashHeadData_t sfHD[2]; + HAL_MEMSET(sfHD, 0, sizeof(sfHD)); + pointer_t fileFd = Qhal_fileOpen(filename, TRUE); + pointer_t fileBakFd = Qhal_fileOpen(filenameBak, TRUE); + if (SOCKET_FD_INVALID != fileFd) + { + Qhal_fileRead(fileFd, 0, &sfHD[0], __GET_POS_ELEMENT(SafeFlashHeadData_t, buf)); + } + if (SOCKET_FD_INVALID != fileBakFd) + { + Qhal_fileRead(fileBakFd, 0, &sfHD[1], __GET_POS_ELEMENT(SafeFlashHeadData_t, buf)); + } + + Quos_logPrintf(LSDK_STORE, LL_DBG, "filename[%s] Index:%u len:%u crc:0x%02X", filename, sfHD[0].index, sfHD[0].len, sfHD[0].crc); + Quos_logPrintf(LSDK_STORE, LL_DBG, "filenameBak[%s] Index:%u len:%u crc:0x%02X", filenameBak, sfHD[1].index, sfHD[1].len, sfHD[1].crc); + + if (0xFFFFFFFF == sfHD[0].index || 0xFFFF == sfHD[0].len) + { + HAL_MEMSET(&sfHD[0], 0, sizeof(sfHD[0])); + } + if (0xFFFFFFFF == sfHD[1].index || 0xFFFF == sfHD[1].len) + { + HAL_MEMSET(&sfHD[1], 0, sizeof(sfHD[1])); + } + + quint32_t maxLen = sfHD[0].len > sfHD[1].len ? sfHD[0].len : sfHD[1].len; + maxLen = maxLen > (quint32_t)__BYTE_TO_ALIGN(bufLen + 1, QUOS_AES_BLOCKLEN) ? maxLen : (quint32_t)__BYTE_TO_ALIGN(bufLen + 1, QUOS_AES_BLOCKLEN); + SafeFlashHeadData_t *newSfHD = (SafeFlashHeadData_t *)HAL_MALLOC(__GET_POS_ELEMENT(SafeFlashHeadData_t, buf) + maxLen); + if (newSfHD) + { + if (sfHD[0].index <= sfHD[1].index) + { + if (sfHD[1].len == 0 || + sfHD[1].len != Qhal_fileRead(fileBakFd, __GET_POS_ELEMENT(SafeFlashHeadData_t, buf), newSfHD->buf, sfHD[1].len) || + sfHD[1].crc != (quint8_t)Quos_crcCalculate(0, newSfHD->buf, sfHD[1].len)) + { + Quos_logPrintf(LSDK_STORE, LL_ERR, "newest data in flash1 is invaild"); + newSfHD->index = sfHD[0].index + 1; + fileNew = filenameBak; + } + else + { + newSfHD->index = sfHD[1].index + 1; + } + } + else + { + if (sfHD[0].len == 0 || + sfHD[0].len != Qhal_fileRead(fileFd, __GET_POS_ELEMENT(SafeFlashHeadData_t, buf), newSfHD->buf, sfHD[0].len) || + sfHD[0].crc != (quint8_t)Quos_crcCalculate(0, newSfHD->buf, sfHD[0].len)) + { + Quos_logPrintf(LSDK_STORE, LL_ERR, "newest data in flash0 is invaild"); + newSfHD->index = sfHD[1].index + 1; + } + else + { + newSfHD->index = sfHD[0].index + 1; + fileNew = filenameBak; + } + } + } + /*else + { + Quos_logPrintf(LSDK_STORE, LL_WARN, "malloc sfHD fail so cancel crc old data"); + newSfHD = (SafeFlashHeadData_t *)HAL_MALLOC(__GET_POS_ELEMENT(SafeFlashHeadData_t, buf) + __BYTE_TO_ALIGN(bufLen,QUOS_AES_BLOCKLEN)); + if (NULL == newSfHD) + { + Quos_logPrintf(LSDK_STORE, LL_ERR, "mcf sfHD"); + return FALSE; + } + if (sfHD[0].index <= sfHD[1].index) + { + newSfHD->index = sfHD[1].index + 1; + } + else + { + newSfHD->index = sfHD[0].index + 1; + fileNew = filenameBak; + } + }*/ + if (SOCKET_FD_INVALID != fileFd) + { + Qhal_fileClose(fileFd); + } + if (SOCKET_FD_INVALID != fileBakFd) + { + Qhal_fileClose(fileBakFd); + } + if (NULL == newSfHD) + { + return FALSE; + } + + HAL_MEMCPY(newSfHD->buf, buf, bufLen); + newSfHD->len = bufLen; + if (aesKey) + { + AES_ctx_t aes_ctx; + Quos_aesInitCtx(&aes_ctx, aesKey); + Quos_aesPadding(newSfHD->buf, newSfHD->buf, newSfHD->len); + newSfHD->len = Quos_aesEcbEncrypt(&aes_ctx, (const quint8_t *)newSfHD->buf, __BYTE_TO_ALIGN(newSfHD->len + 1, QUOS_AES_BLOCKLEN)); + } + + newSfHD->crc = (quint8_t)Quos_crcCalculate(0, newSfHD->buf, newSfHD->len); + Quos_logPrintf(LSDK_STORE, LL_DBG, "fileNew[%s] Index:%u len:%u crc:0x%02X", fileNew, newSfHD->index, newSfHD->len, newSfHD->crc); + Qhal_fileErase(fileNew); + + qbool ret = FALSE; + fileFd = Qhal_fileOpen(fileNew, FALSE); + if (SOCKET_FD_INVALID != fileFd) + { + ret = Qhal_fileWrite(fileFd, 0, newSfHD, __GET_POS_ELEMENT(SafeFlashHeadData_t, buf) + newSfHD->len); + Qhal_fileClose(fileFd); + } + else + { + Quos_logPrintf(LSDK_STORE, LL_ERR, "fileNew[%s] open fail", fileNew); + } + + HAL_FREE(newSfHD); + return ret; +} +/************************************************************************** +** 功能 @brief : 安全读数据 +** 输入 @param : addr:保存数据的起始地址 + buf:待读取的数据缓冲区 + bufLen:待读取数据的长度 + aesKey必须是QUOS_AES_KEYLEN字节 +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_dsRead(const char *filename, void **buf, const char *aesKey) +{ + quint32_t ret = 0; + Quos_logPrintf(LSDK_STORE, LL_DBG, "filename[%s]", filename); + *buf = NULL; + if (NULL == filename) + { + return 0; + } + char filenameBak[QUOS_FILE_NAME_MAXLENGHT + sizeof(FILE_DSNAME_BAK_HEAD)]; + HAL_SPRINTF(filenameBak, "%s", filename); + char *point = filenameBak + HAL_STRLEN(filenameBak); + while (point != filenameBak && '.' != *point) + { + point--; + } + if (point == filenameBak) + { + HAL_SPRINTF(filenameBak + HAL_STRLEN(filenameBak), "%s", FILE_DSNAME_BAK_HEAD); + } + else + { + HAL_MEMMOVE(point + HAL_STRLEN(FILE_DSNAME_BAK_HEAD), point, HAL_STRLEN(FILE_DSNAME_BAK_HEAD) + 1); + HAL_MEMCPY(point, FILE_DSNAME_BAK_HEAD, HAL_STRLEN(FILE_DSNAME_BAK_HEAD)); + } + + SafeFlashHeadData_t sfHD[2]; + HAL_MEMSET(sfHD, 0, sizeof(sfHD)); + pointer_t fileFd = Qhal_fileOpen(filename, TRUE); + pointer_t fileBakFd = Qhal_fileOpen(filenameBak, TRUE); + if (SOCKET_FD_INVALID != fileFd) + { + Qhal_fileRead(fileFd, 0, &sfHD[0], __GET_POS_ELEMENT(SafeFlashHeadData_t, buf)); + } + if (SOCKET_FD_INVALID != fileBakFd) + { + Qhal_fileRead(fileBakFd, 0, &sfHD[1], __GET_POS_ELEMENT(SafeFlashHeadData_t, buf)); + } + Quos_logPrintf(LSDK_STORE, LL_DBG, "filename[%s] Index:%u len:%u crc:0x%02X", filename, sfHD[0].index, sfHD[0].len, sfHD[0].crc); + Quos_logPrintf(LSDK_STORE, LL_DBG, "filenameBak[%s] Index:%u len:%u crc:0x%02X", filenameBak, sfHD[1].index, sfHD[1].len, sfHD[1].crc); + + if (0xFFFFFFFF == sfHD[0].index) + { + HAL_MEMSET(&sfHD[0], 0, sizeof(sfHD[0])); + } + if (0xFFFFFFFF == sfHD[1].index) + { + HAL_MEMSET(&sfHD[1], 0, sizeof(sfHD[1])); + } + if ((0 == sfHD[0].len && 0 == sfHD[1].len) || (*buf = HAL_MALLOC(sfHD[0].len > sfHD[1].len ? sfHD[0].len : sfHD[1].len)) == NULL) + { + Quos_logPrintf(LSDK_STORE, LL_ERR, "mcf buf %u:%u", sfHD[0].len, sfHD[1].len); + } + else if (sfHD[0].index > sfHD[1].index) + { + if (sfHD[0].len != 0 && + sfHD[0].len == Qhal_fileRead(fileFd, __GET_POS_ELEMENT(SafeFlashHeadData_t, buf), *buf, sfHD[0].len) && + sfHD[0].crc == (quint8_t)Quos_crcCalculate(0, *buf, sfHD[0].len)) + { + ret = sfHD[0].len; + Quos_logPrintf(LSDK_STORE, LL_DBG, "index[0] data is newest"); + } + else if (sfHD[1].len != 0 && + sfHD[1].len == Qhal_fileRead(fileBakFd, __GET_POS_ELEMENT(SafeFlashHeadData_t, buf), *buf, sfHD[1].len) && + sfHD[1].crc == (quint8_t)Quos_crcCalculate(0, *buf, sfHD[1].len)) + { + ret = sfHD[1].len; + Quos_logPrintf(LSDK_STORE, LL_DBG, "index[1] data is newest because index[0] data was abnormal"); + } + } + else + { + if (sfHD[1].len != 0 && + sfHD[1].len == Qhal_fileRead(fileBakFd, __GET_POS_ELEMENT(SafeFlashHeadData_t, buf), *buf, sfHD[1].len) && + sfHD[1].crc == (quint8_t)Quos_crcCalculate(0, *buf, sfHD[1].len)) + { + ret = sfHD[1].len; + Quos_logPrintf(LSDK_STORE, LL_DBG, "index[1] data is newest"); + } + else if (sfHD[0].len != 0 && + sfHD[0].len == Qhal_fileRead(fileFd, __GET_POS_ELEMENT(SafeFlashHeadData_t, buf), *buf, sfHD[0].len) && + sfHD[0].crc == (quint8_t)Quos_crcCalculate(0, *buf, sfHD[0].len)) + { + ret = sfHD[0].len; + Quos_logPrintf(LSDK_STORE, LL_DBG, "index[0] data is newest because index[1] data was abnormal"); + } + } + if (SOCKET_FD_INVALID != fileFd) + { + Qhal_fileClose(fileFd); + } + if (SOCKET_FD_INVALID != fileBakFd) + { + Qhal_fileClose(fileBakFd); + } + if (ret == 0 && NULL != *buf) + { + HAL_FREE(*buf); + *buf = NULL; + } + if (aesKey) + { + AES_ctx_t aes_ctx; + Quos_aesInitCtx(&aes_ctx, aesKey); + Quos_aesEcbDecrypt(&aes_ctx, *buf, ret); + ret = Quos_aesPaddingBack(*buf, ret); + } + return ret; +} +#endif + +typedef struct +{ +#define DATA_STORE_INFO_HEAD 0x56473830 + quint32_t head; + quint8_t dat[1]; /* StoragerDataNode_t的元素组合 */ +} dsKeyValueInfo_t; +/************************************************************************** +** 功能 @brief : 从Flash读取KEY-VALUE类数据 +** 输入 @param : aesKey必须是QUOS_AES_KEYLEN字节 +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_dsKvRead(const char *filename, const dsKeyValue_t keyValueNode[], const char *aesKey) +{ + dsKeyValueInfo_t *dataInfo = NULL; + + quint32_t len = Quos_dsRead(filename, (void **)&dataInfo, aesKey); + if (NULL == dataInfo || dataInfo->head != DATA_STORE_INFO_HEAD || len <= sizeof(dataInfo->head)) + { + HAL_FREE(dataInfo); + Quos_logPrintf(LSDK_STORE, LL_ERR, "read store data fail"); + return FALSE; + } + len -= sizeof(dataInfo->head); + quint16_t i; + for (i = 0; i < len; i += 3) + { + quint16_t j = 0; + while (keyValueNode[j].dat) + { + if (dataInfo->dat[i] == keyValueNode[j].id) + { + quint16_t nodeLen = _ARRAY01_U16(&dataInfo->dat[i + 1]); + HAL_MEMSET(keyValueNode[j].dat, 0, keyValueNode[j].maxLen); + if (nodeLen > 0 && nodeLen <= keyValueNode[j].maxLen) + { + HAL_MEMCPY(keyValueNode[j].dat, &dataInfo->dat[i + 3], nodeLen); + Quos_logPrintf(LSDK_STORE, LL_DBG, "id[0x%02X] %.*s", keyValueNode[j].id, nodeLen, (char *)keyValueNode[j].dat); + Quos_logHexDump(LSDK_STORE, LL_DUMP, "", keyValueNode[j].dat, nodeLen); + } + + i += nodeLen; + break; + } + j++; + } + } + HAL_FREE(dataInfo); + return TRUE; +} +/************************************************************************** +** 功能 @brief : 写入存储类数据到Flash +** 输入 @param : aesKey必须是QUOS_AES_KEYLEN字节 +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_dsKvWrite(const char *filename, const dsKeyValue_t keyValueNode[], const char *aesKey) +{ + dsKeyValueInfo_t *dataInfo; + quint16_t datLen = 0; + quint8_t nodeCount = 0; + while (keyValueNode[nodeCount].dat) + { + if (keyValueNode[nodeCount].isString) + { + datLen += 3 + HAL_STRLEN(keyValueNode[nodeCount].dat); + } + else + { + datLen += 3 + keyValueNode[nodeCount].maxLen; + } + nodeCount++; + } + Quos_logPrintf(LSDK_STORE, LL_ERR, "nodeCount[%u] datLen[%u]", nodeCount, datLen); + dataInfo = (dsKeyValueInfo_t *)HAL_MALLOC(__GET_POS_ELEMENT(dsKeyValueInfo_t, dat) + datLen); + if (NULL == dataInfo) + { + Quos_logPrintf(LSDK_STORE, LL_ERR, "mcf dataInfo"); + return FALSE; + } + dataInfo->head = DATA_STORE_INFO_HEAD; + nodeCount = 0; + datLen = 0; + while (keyValueNode[nodeCount].dat) + { + quint16_t nodeLen; + if (keyValueNode[nodeCount].isString) + { + nodeLen = HAL_STRLEN(keyValueNode[nodeCount].dat); + } + else + { + nodeLen = keyValueNode[nodeCount].maxLen; + } + + dataInfo->dat[datLen++] = keyValueNode[nodeCount].id; + _U16_ARRAY01(nodeLen, &dataInfo->dat[datLen]); + datLen += 2; + if (nodeLen > 0) + { + HAL_MEMCPY(&dataInfo->dat[datLen], keyValueNode[nodeCount].dat, nodeLen); + } + datLen += nodeLen; + Quos_logPrintf(LSDK_STORE, LL_ERR, "id[0x%02X]", keyValueNode[nodeCount].id); + Quos_logHexDump(LSDK_STORE, LL_DUMP, "", keyValueNode[nodeCount].dat, nodeLen); + nodeCount++; + } + if (FALSE == Quos_dsWrite(filename, dataInfo, __GET_POS_ELEMENT(dsKeyValueInfo_t, dat) + datLen, aesKey)) + { + HAL_FREE(dataInfo); + return FALSE; + } + HAL_FREE(dataInfo); + return TRUE; +} diff --git a/kernel/quos_dataStore.h b/kernel/quos_dataStore.h index 22399b0..4e9566c 100644 --- a/kernel/quos_dataStore.h +++ b/kernel/quos_dataStore.h @@ -1,18 +1,18 @@ -#ifndef __QUOS_DATA_STORE_H__ -#define __QUOS_DATA_STORE_H__ -#include "quos_config.h" - -#if (SDK_ENABLE_DATASAFE == 1) -qbool Quos_dsWrite(const char *filename,void *buf,quint16_t bufLen); -quint32_t Quos_dsRead(const char *filename, void **buf); -#endif -typedef struct -{ - quint8_t id; - qbool isString; - void *dat; - quint16_t maxLen; -}dsKeyValue_t; -qbool Quos_dsKvRead(const char *filename,const dsKeyValue_t keyValueNode[]); -qbool Quos_dsKvWrite(const char *filename,const dsKeyValue_t keyValueNode[]); +#ifndef __QUOS_DATA_STORE_H__ +#define __QUOS_DATA_STORE_H__ +#include "quos_config.h" + +#if (SDK_ENABLE_DATASAFE == 1) +qbool Quos_dsWrite(const char *filename, const void *buf, quint16_t bufLen, const char *aesKey); +quint32_t Quos_dsRead(const char *filename, void **buf, const char *aesKey); +#endif +typedef struct +{ + quint8_t id; + qbool isString; + void *dat; + quint16_t maxLen; +} dsKeyValue_t; +qbool Quos_dsKvRead(const char *filename, const dsKeyValue_t keyValueNode[], const char *aesKey); +qbool Quos_dsKvWrite(const char *filename, const dsKeyValue_t keyValueNode[], const char *aesKey); #endif \ No newline at end of file diff --git a/kernel/quos_event.c b/kernel/quos_event.c new file mode 100644 index 0000000..ed4bd2f --- /dev/null +++ b/kernel/quos_event.c @@ -0,0 +1,121 @@ +/************************************************************************* +** 源码未经检录,使用需谨慎 +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 事件分发管理 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_event.h" +#include "Quos_kernel.h" +#if (SDK_ENABLE_EVENT == 1) +typedef struct +{ + qint32_t event; + void *arg; +}EventArg_t; + +typedef struct +{ + TWLLHead_T head; + qint32_t eventId; + quint32_t cbArraySize; + EventCB_f *cbArray; +} EventNode_t; +static TWLLHead_T *EventList = NULL; + +/************************************************************************** +** 功能 @brief : 事件注册 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_eventCbReg(const qint32_t eventArray[], quint32_t eventCnt, EventCB_f eventCb) +{ + quint32_t i; + for (i = 0; i < eventCnt; i++) + { + EventNode_t *newNode = NULL; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA(EventList, temp, next) + { + EventNode_t *node = __GET_STRUCT_BY_ELEMENT(temp, EventNode_t, head); + if (node->eventId == eventArray[i]) + { + newNode = node; + break; + } + } + if (NULL == newNode) + { + newNode = HAL_MALLOC(sizeof(EventNode_t)); + if (NULL == newNode) + { + continue; + } + HAL_MEMSET(newNode, 0, sizeof(EventNode_t)); + Quos_twllHeadAdd(&EventList, &newNode->head); + newNode->eventId = eventArray[i]; + } + + quint32_t cbId; + for (cbId = 0; cbId < newNode->cbArraySize; cbId++) + { + if (newNode->cbArray[cbId] == eventCb) + { + break; + } + } + if (cbId == newNode->cbArraySize) + { + EventCB_f *newCbArray = HAL_MALLOC(sizeof(EventCB_f) * (newNode->cbArraySize + 1)); + if (newCbArray) + { + HAL_MEMCPY(newCbArray, newNode->cbArray, newNode->cbArraySize * sizeof(EventCB_f)); + newCbArray[newNode->cbArraySize++] = eventCb; + HAL_FREE(newNode->cbArray); + newNode->cbArray = newCbArray; + Quos_logPrintf(LSDK_EVENT, LL_DBG, "add event[%d] Cb[%p] ok", newNode->eventId, eventCb); + } + } + } +} +/************************************************************************** +** 功能 @brief : 事件分发处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_eventHandle(const void *arg, quint32_t argLen) +{ + UNUSED(argLen); + EventArg_t *eventArg = (EventArg_t*)arg; + Quos_logPrintf(LSDK_EVENT, LL_DBG, "event:%d", eventArg->event); + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA(EventList, temp, next) + { + EventNode_t *node = __GET_STRUCT_BY_ELEMENT(temp, EventNode_t, head); + if (eventArg->event == node->eventId) + { + quint32_t i; + for ( i= 0; i < node->cbArraySize; i++) + { + node->cbArray[i](eventArg->event,eventArg->arg); + } + break; + } + } +} +/************************************************************************** +** 功能 @brief : 事件post +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_eventPost(qint32_t event,void *arg) +{ + EventArg_t eventArg; + eventArg.event = event; + eventArg.arg = arg; + Quos_logPrintf(LSDK_EVENT, LL_DBG, "event:%d", event); + return Quos_signalSet(&eventArg, sizeof(eventArg), quos_eventHandle); +} +#endif \ No newline at end of file diff --git a/kernel/quos_fifo.c b/kernel/quos_fifo.c new file mode 100644 index 0000000..5a7c452 --- /dev/null +++ b/kernel/quos_fifo.c @@ -0,0 +1,235 @@ +锘/************************************************************************* +** 鍒涘缓浜 @author : 鍚村仴瓒 +** 鐗堟湰 @version : V 1.0.0 鍘熷鐗堟湰 +** 鏃ユ湡 @date : 2021.1.10 +** 鍔熻兘 @brief : 瀹炵幇FIFO鐜殑璇诲啓涓庢仮澶嶆寚瀹氶暱搴 +** 纭欢 @hardware锛氫换浣旳NSI-C骞冲彴 +** 鍏朵粬 @other 锛 +***************************************************************************/ +#include "quos_fifo.h" + +#if (SDK_ENABLE_FIFO ==1) +/************************************************************************** +** 鍔熻兘 @brief : 鍒濆鍖朏IFO鐜殑澶у皬锛屽繀椤诲湪浣跨敤FIFO鐜箣鍓嶈皟鐢 +** 杈撳叆 @param : buf锛歠ifo缂撳啿鍖烘寚閽堬紝size锛欶IFO鐜殑澶у皬 +** 杈撳嚭 @retval: +***************************************************************************/ +FifoDat_T FUNCTION_ATTR_ROM Quos_fifoInit(quint8_t *buf, quint32_t size) +{ + FifoDat_T fifo; + fifo.head = 0; + fifo.tail = 0; + fifo.bufSize = size; + fifo.fifoBuf = buf; + return fifo; +} +/************************************************************************** +** 鍔熻兘 @brief : 璁$畻FIFO鐜腑宸茬敤绌洪棿澶у皬 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_RAM Quos_fifoUsedLen(FifoDat_T *fifo) +{ + quint32_t head = fifo->head; + quint32_t tail = fifo->tail; + if (NULL == fifo) + { + return 0; + } + return (head <= tail) ? (tail - head) : (fifo->bufSize - head + tail); +} +/************************************************************************** +** 鍔熻兘 @brief : 璁$畻FIFO鐜腑鍓╀綑绌洪棿澶у皬 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_RAM Quos_fifoFreeLen(FifoDat_T *fifo) +{ + quint32_t head = fifo->head; + quint32_t tail = fifo->tail; + if (NULL == fifo) + { + return 0; + } + return (head > tail) ? (head - tail) : (fifo->bufSize - tail + head); +} + +/************************************************************************** +** 鍔熻兘 @brief : 娓呯┖缂撳啿鍖 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_fifoClear(FifoDat_T *fifo) +{ + fifo->head = fifo->tail; +} +/************************************************************************** +** 鍔熻兘 @brief : 鍒犻櫎缂撳啿鍖 +** 杈撳叆 @param : +** 杈撳嚭 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_fifoDelete(FifoDat_T *fifo) +{ + fifo->head = 0; + fifo->tail = 0; + fifo->bufSize = 0; + fifo->fifoBuf = NULL; +} +/************************************************************************** +** 鍔熻兘 @brief : 鍚戞寚瀹欶IFO鐜腑鍐欏叆鎸囧畾闀垮害鏁版嵁锛岃嫢FIFO鐜湭鐢ㄧ┖闂翠笉瓒筹紝鍒欏啓婊′负姝 +** 杈撳叆 @param : +** 杈撳嚭 @retval: 杩斿洖鎴愬姛鍐欏叆鐨勯暱搴 +***************************************************************************/ +quint32_t FUNCTION_ATTR_RAM Quos_fifoWrite(FifoDat_T *fifo, void *buf, quint32_t len) +{ + quint32_t i, freeLen; + quint8_t *data = (quint8_t *)buf; + if (fifo == NULL || fifo->fifoBuf == NULL) + { + return 0; + } + freeLen = Quos_fifoFreeLen(fifo); /* fifo鐜腑鏈敤澶у皬 */ + len = (freeLen > len) ? len : freeLen; + for (i = 0; i < len; i++) + { + fifo->fifoBuf[fifo->tail++] = *data++; + if (fifo->tail == fifo->bufSize) + fifo->tail = 0; + } + return len; +} +/************************************************************************** +** 鍔熻兘 @brief : 璇诲彇浠嶧IFO鐜殑澶撮儴鍋忕Щoffset鐨勪竴涓暟鎹,璇诲彇鍚庝笉浼氬垹闄ゅご閮 +** 杈撳叆 @param : offset:鍋忕Щ閲 +** 杈撳嚭 @retval: 杩斿洖鎴愬姛涓庡惁 +***************************************************************************/ +qint8_t FUNCTION_ATTR_RAM Quos_fifoCheckOffset(FifoDat_T *fifo, void *buf, quint32_t offset) +{ + quint32_t index; + quint8_t *data = (quint8_t *)buf; + if (fifo == NULL || fifo->fifoBuf == NULL || buf == NULL) + { + return -1; + } + if (Quos_fifoUsedLen(fifo) <= offset) /* fifo鐜腑鏈夋晥闀垮害涓嶈冻 */ + return -1; + + if (fifo->bufSize - fifo->head > offset) + { + index = fifo->head + offset; + } + else + { + index = offset - (fifo->bufSize - fifo->head); + } + *data = fifo->fifoBuf[index]; + return 0; +} +/************************************************************************** +** 鍔熻兘 @brief : 璇诲彇浠嶧IFO鐜殑澶撮儴鍋忕Щoffset寮濮嬬殑len涓暟鎹,涓嶄細浼氬垹闄よ鍙栧悗鐨勬暟鎹 +** 杈撳叆 @param : offset:鍋忕Щ閲 +** 杈撳嚭 @retval: 杩斿洖鎴愬姛涓庡惁 +***************************************************************************/ +quint32_t FUNCTION_ATTR_RAM Quos_fifoReadByte_noDel(FifoDat_T *fifo, void *buf, quint32_t offset, quint32_t len) +{ + quint32_t index; + quint32_t usedLen = Quos_fifoUsedLen(fifo); + quint8_t *data = (quint8_t *)buf; + if (fifo == NULL || fifo->fifoBuf == NULL || buf == NULL || 0 == usedLen || 0 == len) + { + return 0; + } + len = usedLen < len ? usedLen : len; + for (index = 0; index < len; index++) + { + if (fifo->bufSize - fifo->head > offset + index) + { + data[index] = fifo->fifoBuf[fifo->head + offset + index]; + } + else + { + data[index] = fifo->fifoBuf[offset + index - (fifo->bufSize - fifo->head)]; + } + } + return len; +} +/************************************************************************** +** 鍔熻兘 @brief : 璇诲彇浠嶧IFO鐜殑澶撮儴len涓暟鎹,浼氬垹闄よ鍙栧悗鐨勬暟鎹 +** 杈撳叆 @param : +** 杈撳嚭 @retval: 杩斿洖鎴愬姛涓庡惁 +***************************************************************************/ +quint32_t FUNCTION_ATTR_RAM Quos_fifoReadByte_del(FifoDat_T *fifo, void *buf, quint32_t len) +{ + quint32_t index; + quint32_t usedLen = Quos_fifoUsedLen(fifo); + quint8_t *data = (quint8_t *)buf; + if (fifo == NULL || fifo->fifoBuf == NULL || buf == NULL || 0 == usedLen || 0 == len) + { + return 0; + } + len = usedLen < len ? usedLen : len; + for (index = 0; index < len; index++) + { + data[index] = fifo->fifoBuf[fifo->head++]; + if (fifo->head == fifo->bufSize) + fifo->head = 0; + } + return len; +} +/************************************************************************** +** 鍔熻兘 @brief : 鍒犻櫎FIFO棣栭儴鐨凬涓暟鎹 +** 杈撳叆 @param : +** 杈撳嚭 @retval: NULL +***************************************************************************/ +void FUNCTION_ATTR_RAM Quos_fifoDeleteNByte(FifoDat_T *fifo, quint32_t len) +{ + if (fifo == NULL || fifo->fifoBuf == NULL) + { + return; + } + if (Quos_fifoUsedLen(fifo) < len) + { + fifo->head = fifo->tail; + } + else + { + fifo->head = (fifo->bufSize - fifo->head > len) ? (fifo->head + len) : (len - (fifo->bufSize - fifo->head)); + } +} +/************************************************************************** +** 鍔熻兘 @brief : 姣旇緝瀛楄妭娴 +** 杈撳叆 @param : +** 杈撳嚭 @retval: NULL +***************************************************************************/ +qint32_t FUNCTION_ATTR_RAM Quos_fifoStrstr(FifoDat_T *fifo, quint32_t offset, void *buf, quint32_t bufLen) +{ + quint8_t *data = (quint8_t *)buf; + if (fifo == NULL || fifo->fifoBuf == NULL || buf == NULL) + { + return -1; + } + quint32_t usedLen = Quos_fifoUsedLen(fifo); + quint32_t start = 0; + quint32_t index; + for (index = offset; index < usedLen; index++) + { + if (((fifo->bufSize - fifo->head > index) && (data[start] == fifo->fifoBuf[fifo->head + index])) || + ((fifo->bufSize - fifo->head <= index) && (data[start] == fifo->fifoBuf[index - (fifo->bufSize - fifo->head)]))) + { + start++; + if (start == bufLen) + { + return index + 1 - bufLen; + } + } + else + { + start = 0; + index = offset; + offset++; + } + } + return -1; +} + +#endif \ No newline at end of file diff --git a/kernel/quos_fifo.h b/kernel/quos_fifo.h index ac38ea9..d84175d 100644 --- a/kernel/quos_fifo.h +++ b/kernel/quos_fifo.h @@ -1,33 +1,27 @@ -#ifndef __FIFO_H__ -#define __FIFO_H__ - -#include "quos_config.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - - typedef struct - { - quint32_t head; - quint32_t tail; - quint32_t bufSize; - quint8_t *fifoBuf; - } FifoDat_T; - FifoDat_T Quos_fifoInit(quint8_t *buf, quint32_t size); - quint32_t Quos_fifoUsedLen(FifoDat_T *fifo); - quint32_t Quos_fifoFreeLen(FifoDat_T *fifo); - void Quos_fifoClear(FifoDat_T *fifo); - void Quos_fifoDelete(FifoDat_T *fifo); - quint32_t Quos_fifoWrite(FifoDat_T *fifo, void *buf, quint32_t len); - qint8_t Quos_fifoCheckOffset(FifoDat_T *fifo, void *buf, quint32_t offset); - quint32_t Quos_fifoReadByte_noDel(FifoDat_T *fifo, void *buf, quint32_t offset, quint32_t len); - quint32_t Quos_fifoReadByte_del(FifoDat_T *fifo, void *buf, quint32_t len); - void Quos_fifoDeleteNByte(FifoDat_T *fifo, quint32_t len); - qint32_t Quos_fifoStrstr(FifoDat_T *fifo, quint32_t offset, void *buf, quint32_t bufLen); -#ifdef __cplusplus -} -#endif - -#endif +#ifndef __FIFO_H__ +#define __FIFO_H__ + +#include "quos_config.h" +#if (SDK_ENABLE_FIFO == 1) + +typedef struct +{ + quint32_t head; + quint32_t tail; + quint32_t bufSize; + quint8_t *fifoBuf; +} FifoDat_T; +FifoDat_T Quos_fifoInit(quint8_t *buf, quint32_t size); +quint32_t Quos_fifoUsedLen(FifoDat_T *fifo); +quint32_t Quos_fifoFreeLen(FifoDat_T *fifo); +void Quos_fifoClear(FifoDat_T *fifo); +void Quos_fifoDelete(FifoDat_T *fifo); +quint32_t Quos_fifoWrite(FifoDat_T *fifo, void *buf, quint32_t len); +qint8_t Quos_fifoCheckOffset(FifoDat_T *fifo, void *buf, quint32_t offset); +quint32_t Quos_fifoReadByte_noDel(FifoDat_T *fifo, void *buf, quint32_t offset, quint32_t len); +quint32_t Quos_fifoReadByte_del(FifoDat_T *fifo, void *buf, quint32_t len); +void Quos_fifoDeleteNByte(FifoDat_T *fifo, quint32_t len); +qint32_t Quos_fifoStrstr(FifoDat_T *fifo, quint32_t offset, void *buf, quint32_t bufLen); + +#endif +#endif diff --git a/kernel/quos_http.c b/kernel/quos_http.c new file mode 100644 index 0000000..f372cbc --- /dev/null +++ b/kernel/quos_http.c @@ -0,0 +1,650 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : Http通信管理 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_http.h" +#if (SDK_ENABLE_HTTP == 1) +#include "Quos_kernel.h" +#include "Qhal_driver.h" +#ifndef QUOS_HTTP_TIMEOUT +#define QUOS_HTTP_TIMEOUT 10 * SWT_ONE_SECOND +#endif +#ifndef QUOS_HTTP_FILE_PIECE +#define QUOS_HTTP_FILE_PIECE 1024 +#endif +#define HTTP_DEFAULT_HEADER "%s /%s HTTP/1.1\r\nHost:%s:%d\r\n%s" + +enum +{ + HTTP_BODY_TYPE_INVALID = -3, + HTTP_BODY_TYPE_NOMASK = -2, + HTTP_BODY_TYPE_CHUNKED = -1, + HTTP_BODY_TYPE_CONTENTLENGHT, +}; +typedef struct +{ + httpEventCB_f eventCB; + char *sendFilename; + char *recvFilename; + qint32_t httpCode; + qint32_t bodyType; + char *retHeader; + quint32_t bodyLen; + quint32_t bodyOffset; + quint32_t chunkedBlockLen; + pointer_t fileFd; +} HttpSocket_t; + +/************************************************************************** +** 功能 @brief : http socket connet结果 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_httpSockConnctCB(void *chlFd, qbool result) +{ + Quos_logPrintf(LSDK_HTTP, LL_DBG, "chlFd[%p] result:%s", chlFd, _BOOL2STR(result)); + if (result == FALSE && NULL != chlFd) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + ((HttpSocket_t *)(chlNode->param))->eventCB(QUOS_HTTP_CODE_ERR_NET, NULL, NULL, 0); + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : HTTP send CB +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_httpSendCB(void *chlFd, const void *sendData, const void *recvData) +{ + UNUSED(sendData); + if (NULL == recvData) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + HttpSocket_t *httpSocket = (HttpSocket_t *)chlNode->param; + httpSocket->eventCB(QUOS_HTTP_CODE_ERR_NET, NULL, NULL, 0); + Quos_socketChannelDel((void *)chlNode); + } +} +/************************************************************************** +** 功能 @brief : HTTP 使用TCP发送 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_httpSendResult(void *chlFd, const void *sendData, qbool result) +{ + Quos_logPrintf(LSDK_HTTP, LL_DBG, "chlFd[%p] result:%s", chlFd, _BOOL2STR(result)); + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + Quos_socketSendDataNode_t *sendNode = (Quos_socketSendDataNode_t *)sendData; + HttpSocket_t *httpSocket = (HttpSocket_t *)chlNode->param; + if (TRUE == result) + { + if (SOCKET_FD_INVALID == httpSocket->fileFd) + { + httpSocket->fileFd = Qhal_fileOpen(httpSocket->sendFilename, TRUE); + if (SOCKET_FD_INVALID == httpSocket->fileFd) + { + Quos_socketChannelDel((void *)chlNode); + return FALSE; + } + } + if (httpSocket->bodyLen > httpSocket->bodyOffset) + { + quint32_t len = httpSocket->bodyLen - httpSocket->bodyOffset; + len = len > QUOS_HTTP_FILE_PIECE ? QUOS_HTTP_FILE_PIECE : len; + quint8_t *buf = HAL_MALLOC(len); + if (NULL == buf) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "mcf buf to file piece len:%u", len); + } + else if (Qhal_fileRead(httpSocket->fileFd, httpSocket->bodyOffset, buf, len) != len) + { + HAL_FREE(buf); + Quos_logPrintf(LSDK_HTTP, LL_ERR, "read file piece len fail"); + } + else if (FALSE == Quos_socketTx(chlFd, NULL, sendNode->sendCnt, sendNode->sendTimeout, (socketsendNodeCb_f)sendNode->sendCB, (socketRecvNodeCb_f)sendNode->recvCB, sendNode->pkgId, buf, len, NULL)) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "add file piece to socket sendlist fail"); + } + else + { + httpSocket->bodyOffset += len; + Quos_logPrintf(LSDK_HTTP, LL_DBG, "sendFile:%u/%u", httpSocket->bodyOffset, httpSocket->bodyLen); + return TRUE; + } + } + else + { + Qhal_fileClose(httpSocket->fileFd); + httpSocket->fileFd = SOCKET_FD_INVALID; + quint8_t *buf = HAL_MALLOC(sizeof(QUOS_HTTP_MULTIPART_NODE_END)); + if (NULL == buf) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "mcf buf to multipart end mask"); + return FALSE; + } + HAL_SPRINTF((char *)buf, "%s", QUOS_HTTP_MULTIPART_NODE_END); + if (FALSE == Quos_socketTx(chlFd, NULL, sendNode->sendCnt, sendNode->sendTimeout, NULL, (socketRecvNodeCb_f)sendNode->recvCB, sendNode->pkgId, buf, HAL_STRLEN(buf), NULL)) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "add multipart end mask to socket sendlist fail"); + } + else + { + Quos_logPrintf(LSDK_HTTP, LL_INFO, "send multipart end mask"); + return TRUE; + } + } + } + Quos_socketChannelDel(chlFd); + return FALSE; +} +/************************************************************************** +** 功能 @brief : http接收数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_httpRecvData(void *chlFd, const void *peer, quint32_t peerSize, Quos_socketRecvDataNode_t *recvData) +{ + UNUSED(peer); + UNUSED(peerSize); + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + HttpSocket_t *httpSocket = (HttpSocket_t *)chlNode->param; + /* socket被对端断开,返回bufLen=0 */ + if (NULL == recvData) + { + /* 如果body类型不指定且不用保存为文件,则以socket断开作为接收完成标识 */ + Quos_logPrintf(LSDK_HTTP, LL_ERR, "recv buf len= 0"); + if (NULL == httpSocket->recvFilename && HTTP_BODY_TYPE_NOMASK == httpSocket->bodyType && httpSocket->bodyLen > 0) + { + httpSocket->eventCB(httpSocket->httpCode, httpSocket->retHeader, chlNode->unformTemp.buf, httpSocket->bodyLen); + } + + /* 如果在下载文件过程网络断开,则通知应用层网络异常和已接收到的字节数,方便应用层做断点续传 */ + else if (NULL != httpSocket->recvFilename && HTTP_BODY_TYPE_INVALID != httpSocket->bodyType && httpSocket->bodyLen > 0) + { + httpSocket->eventCB(QUOS_HTTP_CODE_ERR_NET, httpSocket->retHeader, (quint8_t *)httpSocket->recvFilename, httpSocket->bodyLen); + } + else + { + httpSocket->eventCB(QUOS_HTTP_CODE_ERR_NET, httpSocket->retHeader, NULL, 0); + } + if (SOCKET_FD_INVALID != httpSocket->fileFd) + { + Qhal_fileClose(httpSocket->fileFd); + httpSocket->fileFd = SOCKET_FD_INVALID; + } + return FALSE; + } + Quos_logHexDump(LSDK_HTTP, LL_DUMP, "RECV", recvData->Buf, recvData->bufLen); + /* 先将接收数据放入缓冲区内 */ + if (recvData->bufLen + chlNode->unformTemp.offset + 1 > chlNode->unformTemp.bufLen) + { + quint8_t *tempBuf = HAL_MALLOC(recvData->bufLen + chlNode->unformTemp.offset + 1); + if (NULL == tempBuf) + { + httpEventCB_f cb = httpSocket->eventCB; + Quos_socketChannelDel(chlFd); + cb(QUOS_HTTP_CODE_ERR_RAM, NULL, NULL, 0); + return FALSE; + } + HAL_MEMCPY(tempBuf, chlNode->unformTemp.buf, chlNode->unformTemp.offset); + HAL_FREE(chlNode->unformTemp.buf); + chlNode->unformTemp.buf = tempBuf; + chlNode->unformTemp.bufLen = recvData->bufLen + chlNode->unformTemp.offset + 1; + } + HAL_MEMCPY(chlNode->unformTemp.buf + chlNode->unformTemp.offset, recvData->Buf, recvData->bufLen); + chlNode->unformTemp.offset += recvData->bufLen; + chlNode->unformTemp.buf[chlNode->unformTemp.offset] = 0; + + /* 处理HTTP 返回HEADER */ + if (HTTP_BODY_TYPE_INVALID == httpSocket->bodyType) + { + char *headSplit = HAL_STRSTR(chlNode->unformTemp.buf, "\r\n\r\n"); + if (NULL == headSplit) + { + Quos_logPrintf(LSDK_HTTP, LL_DBG, "http head find invalid"); + return FALSE; + } + headSplit+=2; + HAL_MEMSET(headSplit, 0, HAL_STRLEN("\r\n")); + + if (0 != HAL_STRNCMP(chlNode->unformTemp.buf, "HTTP/1.1 ", HAL_STRLEN("HTTP/1.1 "))) + { + httpEventCB_f cb = httpSocket->eventCB; + char *retHeader = httpSocket->retHeader; + httpSocket->retHeader = NULL; + Quos_socketChannelDel(chlFd); + cb(QUOS_HTTP_CODE_ERR_DATA, retHeader, NULL, 0); + HAL_FREE(retHeader); + return FALSE; + } + + httpSocket->bodyLen = 0; + httpSocket->chunkedBlockLen = 0; + httpSocket->httpCode = HAL_ATOI((char *)chlNode->unformTemp.buf + HAL_STRLEN("HTTP/1.1 ")); + + /* 查找body格式类型 */ + char *pdata; + if ((pdata = HAL_STRSTR(chlNode->unformTemp.buf, "Content-Length: ")) != NULL) + { + httpSocket->bodyType = (qint32_t)HAL_ATOI(pdata + HAL_STRLEN("Content-Length: ")); + } + else if ((pdata = HAL_STRSTR(chlNode->unformTemp.buf, "Transfer-Encoding: chunked\r\n")) != NULL) + { + httpSocket->bodyType = HTTP_BODY_TYPE_CHUNKED; + } + else + { + httpSocket->bodyType = HTTP_BODY_TYPE_NOMASK; + } + + /*if ((pdata = HAL_STRSTR(chlNode->unformTemp.buf, "Content-Range: bytes ")) != NULL) + { + httpSocket->bodyOffset = HAL_ATOI(pdata + HAL_STRLEN("Content-Range: bytes ")); + }*/ + /* 把http 返回header返回给应用层 */ + quint32_t headerLen = headSplit - (char *)chlNode->unformTemp.buf; + Quos_logPrintf(LSDK_HTTP, LL_DBG, "find http head ok,code:%d bodyType:%d bodyOffset:%u header len:%u", httpSocket->httpCode, httpSocket->bodyType, httpSocket->bodyOffset, headerLen); + Quos_logPrintf(LSDK_HTTP, LL_DBG, "%s", chlNode->unformTemp.buf); + httpSocket->retHeader = HAL_MALLOC(headerLen + 1); + if (NULL == httpSocket->retHeader) + { + Quos_socketChannelDel(chlFd); + return TRUE; + } + /* 删掉缓冲区中header内容 */ + HAL_MEMCPY(httpSocket->retHeader, chlNode->unformTemp.buf, headerLen); + httpSocket->retHeader[headerLen] = 0; + chlNode->unformTemp.offset -= headerLen + HAL_STRLEN("\r\n"); + HAL_MEMMOVE(chlNode->unformTemp.buf, headSplit + HAL_STRLEN("\r\n"), chlNode->unformTemp.offset); + chlNode->unformTemp.buf[chlNode->unformTemp.offset] = 0; + if (NULL != httpSocket->recvFilename) + { + if (FALSE == httpSocket->eventCB(httpSocket->httpCode, httpSocket->retHeader, (quint8_t *)httpSocket->recvFilename, httpSocket->bodyLen)) + { + Quos_socketChannelDel(chlFd); + return TRUE; + } + else if ((httpSocket->fileFd = Qhal_fileOpen(httpSocket->recvFilename, FALSE)) == SOCKET_FD_INVALID) + { + Quos_socketChannelDel(chlFd); + return TRUE; + } + } + } + + if (HTTP_BODY_TYPE_NOMASK == httpSocket->bodyType) + { + if (NULL != httpSocket->recvFilename) + { + Qhal_fileWrite(httpSocket->fileFd, httpSocket->bodyOffset + httpSocket->bodyLen, chlNode->unformTemp.buf, chlNode->unformTemp.offset); + httpSocket->bodyLen += chlNode->unformTemp.offset; + chlNode->unformTemp.offset = 0; + } + else + { + httpSocket->bodyLen = chlNode->unformTemp.offset; + } + } + else if (HTTP_BODY_TYPE_CHUNKED == httpSocket->bodyType) + { + if (NULL != httpSocket->recvFilename) + { + char *temp = (char *)chlNode->unformTemp.buf; + while (1) + { + if (httpSocket->chunkedBlockLen == 0) + { + if (temp[0] == '\r') + temp++; + if (temp[0] == '\n') + temp++; + if (HAL_STRSTR(temp, "\r\n") == NULL) + { + Quos_logPrintf(LSDK_HTTP, LL_DBG, "no found block len head"); + break; + } + httpSocket->chunkedBlockLen = HAL_STRTOUL(temp, NULL, 16); + Quos_logPrintf(LSDK_HTTP, LL_DBG, "chunkedBlockLen:%u", httpSocket->chunkedBlockLen); + if (0 == httpSocket->chunkedBlockLen) + { + HttpSocket_t temp; + temp.eventCB = httpSocket->eventCB; + temp.httpCode = httpSocket->httpCode; + temp.retHeader = httpSocket->retHeader; + temp.recvFilename = httpSocket->recvFilename; + temp.bodyLen = httpSocket->bodyLen; + httpSocket->retHeader = NULL; + httpSocket->recvFilename = NULL; + if (SOCKET_FD_INVALID != httpSocket->fileFd) + { + Qhal_fileClose(httpSocket->fileFd); + httpSocket->fileFd = SOCKET_FD_INVALID; + } + Quos_socketChannelDel(chlFd); + temp.eventCB(temp.httpCode, temp.retHeader, (quint8_t *)temp.recvFilename, temp.bodyLen); + HAL_FREE(temp.retHeader); + HAL_FREE(temp.recvFilename); + return TRUE; + } + temp = HAL_STRSTR(temp, "\r\n") + HAL_STRLEN("\r\n"); + } + + quint32_t validLen = chlNode->unformTemp.offset - (temp - (char *)chlNode->unformTemp.buf) > httpSocket->chunkedBlockLen ? httpSocket->chunkedBlockLen : chlNode->unformTemp.offset - (temp - (char *)chlNode->unformTemp.buf); + if (validLen == 0) + { + break; + } + Qhal_fileWrite(httpSocket->fileFd, httpSocket->bodyOffset + httpSocket->bodyLen, temp, validLen); + httpSocket->bodyLen += validLen; + httpSocket->chunkedBlockLen -= validLen; + temp += validLen; + } + if (temp != (char *)chlNode->unformTemp.buf) + { + chlNode->unformTemp.offset = chlNode->unformTemp.offset - (temp - (char *)chlNode->unformTemp.buf); + HAL_MEMMOVE(chlNode->unformTemp.buf, temp, chlNode->unformTemp.offset); + chlNode->unformTemp.buf[chlNode->unformTemp.offset] = 0; + } + return FALSE; + } + else + { + Quos_logPrintf(LSDK_HTTP, LL_DBG, "body:%u\n%s ", chlNode->unformTemp.offset, chlNode->unformTemp.buf); + if (HAL_STRSTR(chlNode->unformTemp.buf, "\r\n0\r\n\r\n")) + { + quint32_t pieceLen; + char *temp = (char *)chlNode->unformTemp.buf; + + while ((pieceLen = HAL_STRTOUL(temp, NULL, 16)) > 0) + { + Quos_logPrintf(LSDK_HTTP, LL_DBG, "pieceLen:%u bodyLen=%u", pieceLen, httpSocket->bodyLen); + temp = HAL_STRSTR(temp, "\r\n") + HAL_STRLEN("\r\n"); + HAL_MEMMOVE(chlNode->unformTemp.buf + httpSocket->bodyLen, temp, pieceLen); + httpSocket->bodyLen += pieceLen; + chlNode->unformTemp.buf[httpSocket->bodyLen] = 0; + temp += pieceLen + HAL_STRLEN("\r\n"); + } + + httpSocket->eventCB(httpSocket->httpCode, httpSocket->retHeader, chlNode->unformTemp.buf, httpSocket->bodyLen); + Quos_socketChannelDel(chlFd); + return TRUE; + } + } + } + else if (HTTP_BODY_TYPE_CONTENTLENGHT <= httpSocket->bodyType) + { + if (NULL != httpSocket->recvFilename) + { + Qhal_fileWrite(httpSocket->fileFd, httpSocket->bodyOffset + httpSocket->bodyLen, chlNode->unformTemp.buf, chlNode->unformTemp.offset); + httpSocket->bodyLen += chlNode->unformTemp.offset; + Quos_logPrintf(LSDK_HTTP, LL_DBG, "recv piece:%u len:%u", chlNode->unformTemp.offset, httpSocket->bodyLen); + chlNode->unformTemp.offset = 0; + if (httpSocket->bodyLen >= (quint32_t)httpSocket->bodyType) + { + if (SOCKET_FD_INVALID != httpSocket->fileFd) + { + Qhal_fileClose(httpSocket->fileFd); + httpSocket->fileFd = SOCKET_FD_INVALID; + } + httpSocket->eventCB(httpSocket->httpCode, httpSocket->retHeader, (quint8_t *)httpSocket->recvFilename, httpSocket->bodyLen); + Quos_socketChannelDel(chlFd); + return TRUE; + } + } + else + { + httpSocket->bodyLen = chlNode->unformTemp.offset; + if (httpSocket->bodyLen >= (quint32_t)httpSocket->bodyType) + { + httpSocket->eventCB(httpSocket->httpCode, httpSocket->retHeader, chlNode->unformTemp.buf, httpSocket->bodyLen); + Quos_socketChannelDel(chlFd); + return TRUE; + } + } + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : HTTP参数资源释放 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_httpParamFree(void *param) +{ + HttpSocket_t *httpSock = (HttpSocket_t *)param; + if (SOCKET_FD_INVALID != httpSock->fileFd) + { + Qhal_fileClose(httpSock->fileFd); + httpSock->fileFd = SOCKET_FD_INVALID; + } + HAL_FREE(httpSock->sendFilename); + HAL_FREE(httpSock->recvFilename); + HAL_FREE(httpSock->retHeader); + HAL_FREE(httpSock); +} +/************************************************************************** +** 功能 @brief : http request +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_httpRequest(void **httpFd, const char *opt, const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const HttpReqFile_t *reqFile) +{ + urlAnalyze_t urlA; + if (NULL == opt || NULL == url || NULL == eventCB || (NULL != reqData && 0 != reqData->payloadLen && NULL == reqData->payload)) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "opt[%s] url[%p] eventCB[%p] payload[%u:%p]", opt, url, eventCB, reqData->payloadLen, reqData->payload); + return FALSE; + } + if (FALSE == (Quos_urlAnalyze(url, &urlA))) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "url invliad"); + return FALSE; + } + urlA.port = urlA.port ? urlA.port : (urlA.isSecure ? 443 : 80); + + HttpSocket_t *httpSock = HAL_MALLOC(sizeof(HttpSocket_t)); + if (NULL == httpSock) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "mcf httpSock"); + return FALSE; + } + HAL_MEMSET(httpSock, 0, sizeof(HttpSocket_t)); + httpSock->fileFd = SOCKET_FD_INVALID; + httpSock->eventCB = eventCB; + httpSock->bodyType = HTTP_BODY_TYPE_INVALID; + if (reqFile) + { + httpSock->bodyOffset = reqFile->offset; + httpSock->bodyLen = reqFile->size; + if (reqFile->txName) + { + httpSock->sendFilename = HAL_STRDUP(reqFile->txName); + if (NULL == httpSock->sendFilename) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "mcf sendFilename"); + HAL_FREE(httpSock); + return FALSE; + } + } + if (reqFile->rxName) + { + httpSock->recvFilename = HAL_STRDUP(reqFile->rxName); + if (NULL == httpSock->recvFilename) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "mcf recvFilename"); + HAL_FREE(httpSock->sendFilename); + HAL_FREE(httpSock); + return FALSE; + } + } + } + + quint16_t len = sizeof(HTTP_DEFAULT_HEADER) + HAL_STRLEN(opt) + HAL_STRLEN(urlA.path) + HAL_STRLEN(urlA.hostname) + 5; + if (reqData) + { + len += HAL_STRLEN(reqData->rawHeaders) + reqData->payloadLen; + } + + if (NULL == reqFile && NULL != reqData && 0 != reqData->payloadLen) + { + len += sizeof(QUOS_HTTP_HEAD_CONTENT_LENGHT) + 5; + } + len += HAL_STRLEN("\r\n"); + quint8_t *buf = HAL_MALLOC(len); + if (NULL == buf) + { + HAL_FREE(httpSock->sendFilename); + HAL_FREE(httpSock->recvFilename); + HAL_FREE(httpSock); + Quos_logPrintf(LSDK_HTTP, LL_ERR, "mcf tcpPayload,len:%u", len); + return FALSE; + } + len = HAL_SPRINTF((char *)buf, HTTP_DEFAULT_HEADER, opt, (urlA.path ? urlA.path : ""), urlA.hostname, urlA.port, ((reqData && reqData->rawHeaders) ? reqData->rawHeaders : "")); + if (NULL == reqFile && NULL != reqData && 0 != reqData->payloadLen) + { + len += HAL_SPRINTF((char *)buf + len, QUOS_HTTP_HEAD_CONTENT_LENGHT, reqData->payloadLen); + } + len += HAL_SPRINTF((char *)buf + len, "\r\n"); + Quos_logPrintf(LSDK_HTTP, LL_DBG, "%s", buf); + if (reqData && 0 != reqData->payloadLen) + { + HAL_MEMCPY(buf + len, reqData->payload, reqData->payloadLen); + len += reqData->payloadLen; + } + + Quos_socketChlInfoNode_t chlInfo; + HAL_MEMSET(&chlInfo, 0, sizeof(Quos_socketChlInfoNode_t)); + if (urlA.isSecure) + { +#if (SDK_ENABLE_TLS == 1) + chlInfo.sockFd = Qhal_tcpSslClientInit(&chlInfo.type, urlA.hostname, urlA.port, &chlInfo.conn.timeout); +#else + chlInfo.sockFd = SOCKET_FD_INVALID; +#endif + } + else + { + chlInfo.sockFd = Qhal_tcpClientInit(&chlInfo.type, urlA.hostname, urlA.port, &chlInfo.conn.timeout); + } + if (chlInfo.conn.timeout) + { + chlInfo.conn.notify = quos_httpSockConnctCB; + } + + if (SOCKET_FD_INVALID == chlInfo.sockFd) + { + HAL_FREE(httpSock->sendFilename); + HAL_FREE(httpSock->recvFilename); + HAL_FREE(httpSock->retHeader); + HAL_FREE(httpSock); + Quos_logPrintf(LSDK_HTTP, LL_ERR, "conn fail:%s[%u]", urlA.hostname, urlA.port); + return FALSE; + } + + chlInfo.io.send = Qhal_sockWrite; + chlInfo.send.txCnt = 1; + chlInfo.send.timeout = QUOS_HTTP_TIMEOUT; + chlInfo.recvDataFunc = quos_httpRecvData; + chlInfo.io.close = Qhal_sockClose; + chlInfo.paramFree = quos_httpParamFree; + chlInfo.param = httpSock; + + void *chlFd = Quos_socketChannelAdd(httpFd, chlInfo); + if (NULL == chlFd) + { + Quos_logPrintf(LSDK_HTTP, LL_ERR, "add socket Channel fail"); + Qhal_sockClose(chlInfo.sockFd, chlInfo.type); + HAL_FREE(httpSock->sendFilename); + HAL_FREE(httpSock->recvFilename); + HAL_FREE(httpSock->retHeader); + HAL_FREE(httpSock); + return FALSE; + } + + if (FALSE == Quos_socketTx(chlFd, NULL, 0, 0, ((NULL == reqFile || FALSE == reqFile->isPostForm) ? NULL : quos_httpSendResult), quos_httpSendCB, 0, buf, len, NULL)) + { + Quos_socketChannelDel(chlFd); + return FALSE; + } + Quos_logPrintf(LSDK_HTTP, LL_DBG, "http socket create ok"); + return TRUE; +} +/************************************************************************** +** 功能 @brief : http get下载文件 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_httpGetDownload(void **httpFd, const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const char *filename, quint32_t offset) +{ + HttpReqFile_t reqFile; + HAL_MEMSET(&reqFile, 0, sizeof(HttpReqFile_t)); + reqFile.rxName = (char *)filename; + reqFile.offset = offset; + return Quos_httpRequest(httpFd, "GET", url, eventCB, reqData, &reqFile); +} +/************************************************************************** +** 功能 @brief : http post表单 +** 输入 @param : reqData.rawHeaders:除了Content-Type和Content-Length外其他自定义的header + reqData.payload:表单的表头数据 + reqData.payloadLen:表单的表头数据长度 +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_httpPostForm(void **httpFd, const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const char *filename, quint32_t fileSize) +{ + HttpReqFile_t reqFile; + HAL_MEMSET(&reqFile, 0, sizeof(HttpReqFile_t)); + HttpReqData_t newReqData = *reqData; + reqFile.txName = (char *)filename; + reqFile.size = fileSize; + reqFile.isPostForm = TRUE; + + quint32_t rawHeaderLen = HAL_STRLEN(reqData->rawHeaders); + rawHeaderLen += sizeof(QUOS_HTTP_CONTENT_TYPE_KEY) + HAL_STRLEN(QUOS_HTTP_CONTENT_TYPE_VALUE_MULTIPART); + rawHeaderLen += sizeof(QUOS_HTTP_HEAD_CONTENT_LENGHT) + 5; + newReqData.rawHeaders = HAL_MALLOC(rawHeaderLen); + if (NULL == newReqData.rawHeaders) + { + return FALSE; + } + HAL_SPRINTF(newReqData.rawHeaders, "%s" QUOS_HTTP_CONTENT_TYPE_KEY QUOS_HTTP_CONTENT_TYPE_VALUE_MULTIPART QUOS_HTTP_HEAD_CONTENT_LENGHT, reqData->rawHeaders, reqData->payloadLen + fileSize + (quint32_t)HAL_STRLEN(QUOS_HTTP_MULTIPART_NODE_END)); + qbool ret = Quos_httpRequest(httpFd, "POST", url, eventCB, &newReqData, &reqFile); + HAL_FREE(newReqData.rawHeaders); + return ret; +} +/************************************************************************** +** 功能 @brief : 获取当前文件下载/上传进度 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_httpFileRate(void *httpFd, quint32_t *bodyLen, qint32_t *bodyType, char **retHeader) +{ + qbool ret = FALSE; + if(Quos_socketGetSockFdType(httpFd, NULL, NULL)) + { + Quos_socketChlInfoNode_t *sockNode = (Quos_socketChlInfoNode_t*)httpFd; + if(sockNode->param) + { + HttpSocket_t *httpSock = (HttpSocket_t *)sockNode->param; + if(bodyLen) + { + *bodyLen = httpSock->bodyLen; + } + if(bodyType) + { + *bodyType = httpSock->bodyType; + } + if(retHeader) + { + *retHeader = httpSock->retHeader; + } + ret = TRUE; + } + } + return ret; +} +#endif \ No newline at end of file diff --git a/kernel/quos_http.h b/kernel/quos_http.h index 8102423..51ad9f8 100644 --- a/kernel/quos_http.h +++ b/kernel/quos_http.h @@ -1,56 +1,56 @@ -#ifndef __QUOS_HTTP_H__ -#define __QUOS_HTTP_H__ -#include "quos_config.h" -#if (SDK_ENABLE_HTTP==1) -#define QUOS_HTTP_MULTIPART_BOUNDARY "450d2e46-73fc11eaad264b91df3ae910" -#define QUOS_HTTP_MULTIPART_NODE_START "--" QUOS_HTTP_MULTIPART_BOUNDARY "\r\n" -#define QUOS_HTTP_MULTIPART_NODE_END "\r\n--" QUOS_HTTP_MULTIPART_BOUNDARY "--\r\n" - -#define QUOS_HTTP_HEAD_CONTENT_LENGHT "Content-Length: %u\r\n" - -#define QUOS_HTTP_CONTENT_TYPE_KEY "Content-Type: " -#define QUOS_HTTP_CONTENT_TYPE_VALUE_JSON "application/json\r\n" -#define QUOS_HTTP_CONTENT_TYPE_VALUE_MULTIPART "multipart/form-data; boundary=" QUOS_HTTP_MULTIPART_BOUNDARY "\r\n" -#define QUOS_HTTP_CONTENT_TYPE_VALUE_OCTET_STREAM "application/octet-stream\r\n" - -#define QUOS_HTTP_CONTENT_DISPOSITION_KEY "Content-Disposition: " - -enum -{ - QUOS_HTTP_CODE_ERR_DATA = -255, - QUOS_HTTP_CODE_ERR_RAM, - QUOS_HTTP_CODE_ERR_NET, -}; - -typedef struct -{ - char *rawHeaders; /* 每项以"\r\n"结束 */ - char *payload; - quint16_t payloadLen; -} HttpReqData_t; - -typedef struct -{ - char *txName; - char *rxName; - quint32_t size; - quint32_t offset; - qbool isPostForm; -} HttpReqFile_t; - -/* 在非下载文件时,只有完成整个HTTP才CB一次 - 在下载文件时,下载提取到header数据时先CB一次,recvLen=0, - 根据CB返回结果判断是否继续,为TRUE时则继续下载文件, - HTTP结束后再CB一次,此时recvLen为下载到的文件大小 */ -typedef qbool (*httpEventCB_f)(qint32_t httpCode, char *retHeader, quint8_t *recvBuf, quint32_t recvLen); - -qbool Quos_httpRequest(const char *opt, const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const HttpReqFile_t *reqFile); -qbool Quos_httpGetDownload(const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const char *filename, quint32_t offset); -qbool Quos_httpPostForm(const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const char *filename, quint32_t fileSize); - -#define Quos_httpGet(URL, EVENTCB, REQDATA) Quos_httpRequest("GET", URL, EVENTCB, REQDATA, NULL) -#define Quos_httpPost(URL, EVENTCB, REQDATA) Quos_httpRequest("POST", URL, EVENTCB, REQDATA, NULL) -#define Quos_httpPut(URL, EVENTCB, REQDATA) Quos_httpRequest("PUT", URL, EVENTCB, REQDATA, NULL) -#define Quos_httpDelete(URL, EVENTCB, REQDATA) Quos_httpRequest("DELETE", URL, EVENTCB, REQDATA, NULL) -#endif -#endif +#ifndef __QUOS_HTTP_H__ +#define __QUOS_HTTP_H__ +#include "quos_config.h" +#if (SDK_ENABLE_HTTP == 1) +#define QUOS_HTTP_MULTIPART_BOUNDARY "450d2e46-73fc11eaad264b91df3ae910" +#define QUOS_HTTP_MULTIPART_NODE_START "--" QUOS_HTTP_MULTIPART_BOUNDARY "\r\n" +#define QUOS_HTTP_MULTIPART_NODE_END "\r\n--" QUOS_HTTP_MULTIPART_BOUNDARY "--\r\n" + +#define QUOS_HTTP_HEAD_CONTENT_LENGHT "Content-Length: %u\r\n" + +#define QUOS_HTTP_CONTENT_TYPE_KEY "Content-Type: " +#define QUOS_HTTP_CONTENT_TYPE_VALUE_JSON "application/json\r\n" +#define QUOS_HTTP_CONTENT_TYPE_VALUE_MULTIPART "multipart/form-data; boundary=" QUOS_HTTP_MULTIPART_BOUNDARY "\r\n" +#define QUOS_HTTP_CONTENT_TYPE_VALUE_OCTET_STREAM "application/octet-stream\r\n" + +#define QUOS_HTTP_CONTENT_DISPOSITION_KEY "Content-Disposition: " + +enum +{ + QUOS_HTTP_CODE_ERR_DATA = -255, + QUOS_HTTP_CODE_ERR_RAM, + QUOS_HTTP_CODE_ERR_NET, +}; + +typedef struct +{ + char *rawHeaders; /* 每项以"\r\n"结束 */ + char *payload; + quint16_t payloadLen; +} HttpReqData_t; + +typedef struct +{ + char *txName; + char *rxName; + quint32_t size; + quint32_t offset; + qbool isPostForm; +} HttpReqFile_t; + +/* 在非下载文件时,只有完成整个HTTP才CB一次 + 在下载文件时,下载提取到header数据时先CB一次,recvLen=0, + 根据CB返回结果判断是否继续,为TRUE时则继续下载文件, + HTTP结束后再CB一次,此时recvLen为下载到的文件大小 */ +typedef qbool (*httpEventCB_f)(qint32_t httpCode, char *retHeader, quint8_t *recvBuf, quint32_t recvLen); + +qbool Quos_httpRequest(void **httpFd, const char *opt, const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const HttpReqFile_t *reqFile); +qbool Quos_httpGetDownload(void **httpFd, const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const char *filename, quint32_t offset); +qbool Quos_httpPostForm(void **httpFd, const char *url, httpEventCB_f eventCB, const HttpReqData_t *reqData, const char *filename, quint32_t fileSize); + +#define Quos_httpGet(URL, EVENTCB, REQDATA) Quos_httpRequest(NULL, "GET", URL, EVENTCB, REQDATA, NULL) +#define Quos_httpPost(URL, EVENTCB, REQDATA) Quos_httpRequest(NULL, "POST", URL, EVENTCB, REQDATA, NULL) +#define Quos_httpPut(URL, EVENTCB, REQDATA) Quos_httpRequest(NULL, "PUT", URL, EVENTCB, REQDATA, NULL) +#define Quos_httpDelete(URL, EVENTCB, REQDATA) Quos_httpRequest(NULL, "DELETE", URL, EVENTCB, REQDATA, NULL) +#endif +#endif diff --git a/kernel/quos_log.c b/kernel/quos_log.c new file mode 100644 index 0000000..8e76751 --- /dev/null +++ b/kernel/quos_log.c @@ -0,0 +1,29 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 统一日志打印API +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_log.h" +HAL_LOCK_DEF(,lockLogId) + +/************************************************************************** +** 功能 @brief : HEXDUMP数据 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_RAM Quos_logHexDumpData(const void *dat, quint16_t len) +{ + quint16_t i; + for (i = 0; i < len; i++) + { + if (i % 20 == 0) + { + HAL_PRINTF("\r\n"); + } + HAL_PRINTF("%02X ", ((quint8_t *)dat)[i]); + } + HAL_PRINTF("\r\n"); +} diff --git a/kernel/quos_log.h b/kernel/quos_log.h index 3e4e9e5..2ad0798 100644 --- a/kernel/quos_log.h +++ b/kernel/quos_log.h @@ -1,49 +1,53 @@ -#ifndef __QUOS_LOG_H__ -#define __QUOS_LOG_H__ -#include "quos_config.h" - -#ifdef __cplusplus -extern "C" -{ -#endif - -#define LL_OFF (0X07) /* 关闭所有日志 */ -#define LL_FAIL (0X06) /* 将导致程序退出的错误及其以上 */ -#define LL_ERR (0X05) /* 发生错误但不会导致程序退出及其以上 */ -#define LL_WARN (0X04) /* 警告级别错误及其以上 */ -#define LL_INFO (0X03) /* 粗粒度级别log及其以上 */ -#define LL_DBG (0X02) /* 所有log */ -#define LL_DUMP (0X01) /* dump数据,仅用于Quos_logHexDump打印 */ - -#ifndef QUOS_LOGL -#define QUOS_LOGL LL_DBG -#endif - -#define Quos_logPrintf(TYPE, LEVEL, format, ...) \ - if (LEVEL >= TYPE && LEVEL >= QUOS_LOGL) \ - { \ - HAL_LOCK(lockLogId); \ - HAL_PRINTF("%s<%s\t> %s[%d] " format "\r\n", Qhal_logHeadString(), #TYPE, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - HAL_UNLOCK(lockLogId); \ - } - -#if (SDK_ENABLE_LOGDUMP ==1 ) -#define Quos_logHexDump(TYPE, LEVEL, HEAD, DAT, DATLEN) \ - if (LEVEL >= TYPE && LEVEL >= QUOS_LOGL) \ - { \ - HAL_LOCK(lockLogId); \ - HAL_PRINTF("%s<%s\t> %s[%d] %s\r\n", Qhal_logHeadString(), #TYPE, __FUNCTION__, __LINE__, HEAD); \ - HAL_PRINTF("*************************** %04d **************************", (quint16_t)(DATLEN)); \ - Quos_logHexDumpData(DAT, DATLEN); \ - HAL_UNLOCK(lockLogId); \ - } - void Quos_logHexDumpData(void *dat, quint16_t len); -#else - #define Quos_logHexDump(TYPE, LEVEL, HEAD, DAT, DATLEN) -#endif - HAL_LOCK_DEF(extern, lockLogId) - extern char *Qhal_logHeadString(void); -#ifdef __cplusplus -} -#endif +#ifndef __QUOS_LOG_H__ +#define __QUOS_LOG_H__ +#include "quos_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define LL_OFF (0X07) /* 关闭所有日志 */ +#define LL_FAIL (0X06) /* 将导致程序退出的错误及其以上 */ +#define LL_ERR (0X05) /* 发生错误但不会导致程序退出及其以上 */ +#define LL_WARN (0X04) /* 警告级别错误及其以上 */ +#define LL_INFO (0X03) /* 粗粒度级别log及其以上 */ +#define LL_DBG (0X02) /* 所有log */ +#define LL_DUMP (0X01) /* dump数据,仅用于Quos_logHexDump打印 */ + +#ifndef QUOS_LOGL +#define QUOS_LOGL LL_ERR +#endif + +#define Quos_logPrintf(TYPE, LEVEL, format, ...) \ + do \ + { \ + if (LEVEL >= TYPE && LEVEL >= QUOS_LOGL) \ + { \ + HAL_LOCK(lockLogId); \ + HAL_PRINTF("%s<%-12s> %s[%d] " format "\r\n", Qhal_logHeadString(), #TYPE, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ + HAL_UNLOCK(lockLogId); \ + } \ + } while (0) + +#define Quos_logHexDump(TYPE, LEVEL, HEAD, DAT, DATLEN) \ + do \ + { \ + if (LEVEL >= TYPE && LEVEL >= QUOS_LOGL) \ + { \ + HAL_LOCK(lockLogId); \ + HAL_PRINTF("%s<%-12s> %s[%d] %s\r\n", Qhal_logHeadString(), #TYPE, __FUNCTION__, __LINE__, HEAD); \ + HAL_PRINTF("*************************** %04d **************************", (quint16_t)(DATLEN)); \ + Quos_logHexDumpData(DAT, DATLEN); \ + HAL_UNLOCK(lockLogId); \ + } \ + } while (0) + + void Quos_logHexDumpData(const void *dat, quint16_t len); + + HAL_LOCK_DEF(extern, lockLogId) + extern char *Qhal_logHeadString(void); +#ifdef __cplusplus +} +#endif #endif \ No newline at end of file diff --git a/kernel/quos_lwm2m.c b/kernel/quos_lwm2m.c new file mode 100644 index 0000000..15c1a8c --- /dev/null +++ b/kernel/quos_lwm2m.c @@ -0,0 +1,182 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : lwm2m bootstrap +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_lwm2m.h" +#if (SDK_ENABLE_LWM2M == 1) +#include "internals.h" +#include "Qhal_driver.h" +extern char *get_server_uri(lwm2m_object_t *objectP, quint16_t secObjInstID); + +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_lwm2mStepTimeCB(void *swTimer) +{ + lwm2m_context_t *ctx = (lwm2m_context_t *)swTimer->parm; + lwm2mUsedata_t *userdata = (lwm2mUsedata_t *)ctx->userData; + time_t stepPeriod = userdata->stepPeriod; + int stepRet = lwm2m_step(ctx, &stepPeriod); + Quos_logPrintf(LSDK_LWM2M, LL_DBG, "lwm2m step period:" PRINTF_FD " stepRet:0x%02X state:%s", stepPeriod, stepRet, STR_STATE(ctx->state)); + if (0 != stepRet && ctx->state == STATE_BOOTSTRAPPING) + { + /* 需要在此重新初始化资源 */ + ctx->state = STATE_INITIAL; + stepPeriod = 10; + } + Quos_swTimerTimeoutSet(swTimer, stepPeriod * SWT_ONE_SECOND); +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *quos_lwm2mInit(char *name, lwm2m_object_t *objects[], quint16_t count, lwm2mUsedata_t *userdata) +{ + if (NULL == userdata) + { + Quos_logPrintf(LSDK_LWM2M, LL_ERR, "userdata param invalid"); + return NULL; + } + lwm2m_context_t *ctx = lwm2m_init(userdata); + if (NULL == ctx) + { + Quos_logPrintf(LSDK_LWM2M, LL_ERR, "init fail"); + HAL_FREE(userdata); + return NULL; + } + if (FALSE == Quos_swTimerStart(&ctx->stepTimer, "lwm2m step", 1, 0, quos_lwm2mStepTimeCB, ctx)) + { + Quos_logPrintf(LSDK_LWM2M, LL_ERR, "step timer start fail"); + HAL_FREE(userdata); + HAL_FREE(ctx); + return NULL; + } + if (COAP_NO_ERROR != lwm2m_configure(ctx, name, NULL, NULL, count, objects)) + { + Quos_logPrintf(LSDK_LWM2M, LL_ERR, "config fail"); + Quos_swTimerDelete(ctx->stepTimer); + HAL_FREE(userdata); + HAL_FREE(ctx); + return NULL; + } + Quos_logPrintf(LSDK_LWM2M, LL_DBG, "lwm2m init ok"); + return (void *)ctx; +} + +/************************************************************************** +** 功能 @brief : coap接收数据处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_lwm2mSocketRecv(void *chlFd, const void *peer, quint32_t peerSize, Quos_socketRecvDataNode_t *recvData) +{ + UNUSED(peer); + UNUSED(peerSize); + Quos_logPrintf(LSDK_LWM2M, LL_DBG, "lwm2m recv chlFd[%p]", chlFd); + + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + lwm2m_context_t *ctx = (lwm2m_context_t *)chlNode->param; + if (NULL == recvData) + { + ctx->state = STATE_INITIAL; + Quos_swTimerTimeoutSet(ctx->stepTimer, 0); + return FALSE; + } + Quos_logHexDump(LSDK_LWM2M, LL_DUMP, "lwm2m recv", recvData->Buf, recvData->bufLen); + lwm2m_handle_packet(ctx, recvData->Buf, recvData->bufLen, chlFd); + Quos_swTimerTimeoutSet(ctx->stepTimer, 0); + return TRUE; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *lwm2m_connect_server(void **fd, quint16_t secObjInstID, void *context) +{ + lwm2m_context_t *ctx = (lwm2m_context_t *)context; + lwm2m_object_t *targetP = (lwm2m_object_t *)LWM2M_LIST_FIND(ctx->objectList, LWM2M_SECURITY_OBJECT_ID); + if (NULL == targetP) + { + Quos_logPrintf(LSDK_LWM2M, LL_ERR, "not found security obj"); + return NULL; + } + char *url = get_server_uri(targetP, secObjInstID); + if (NULL == url) + { + Quos_logPrintf(LSDK_LWM2M, LL_ERR, "server url is empty"); + return NULL; + } + Quos_logPrintf(LSDK_LWM2M, LL_DBG, "url:%s", url); + urlAnalyze_t urlA; + if (FALSE == Quos_urlAnalyze(url, &urlA)) + { + Quos_logPrintf(LSDK_LWM2M, LL_ERR, "url[%s] analyze fail", url); + return NULL; + } + urlA.port = urlA.port ? urlA.port : (urlA.isSecure ? 5684 : 5683); + Quos_socketChlInfoNode_t chlInfo; + HAL_MEMSET(&chlInfo, 0, sizeof(Quos_socketChlInfoNode_t)); + if (urlA.isSecure) + { +#if (SDK_ENABLE_TLS == 1) + chlInfo.sockFd = Qhal_udpSslInit(&chlInfo.type, 0, urlA.hostname, urlA.port); +#else + chlInfo.sockFd = SOCKET_FD_INVALID; +#endif + } + else + { + chlInfo.sockFd = Qhal_udpInit(&chlInfo.type, 0, urlA.hostname, urlA.port, NULL); + } + if (SOCKET_FD_INVALID == chlInfo.sockFd) + { + Quos_logPrintf(LSDK_LWM2M, LL_ERR, "coap conn fail:%s[%u]", urlA.hostname, urlA.port); + return FALSE; + } + + chlInfo.io.send = Qhal_sockWrite; + chlInfo.send.txCnt = COAP_MAX_RETRANSMIT; + chlInfo.send.timeout = COAP_RESPONSE_TIMEOUT * SWT_ONE_SECOND; + chlInfo.recvDataFunc = quos_lwm2mSocketRecv; + chlInfo.io.close = Qhal_sockClose; + chlInfo.param = context; + void *chlFd = Quos_socketChannelAdd(fd, chlInfo); + Quos_logPrintf(LSDK_LWM2M, LL_INFO, "lwm2m chlFd[%p]", chlFd); + if(NULL == chlFd) + { + Qhal_sockClose(chlInfo.sockFd,chlInfo.type); + } + return chlFd; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM lwm2m_close_connection(void *sessionH, void *context) +{ + Quos_logPrintf(LSDK_LWM2M, LL_INFO, "lwm2m close sessionH[%p] context[%p]", sessionH, context); + Quos_socketChannelDel(sessionH); +} + +/************************************************************************** +** 功能 @brief : bootstrap payload +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint16_t FUNCTION_ATTR_ROM get_bootstrap_payload(lwm2m_context_t *context, quint8_t **valP) +{ + lwm2mUsedata_t *userdata = (lwm2mUsedata_t *)context->userData; + *valP = userdata->bs.payload.val; + return userdata->bs.payload.len; +} + +#endif \ No newline at end of file diff --git a/kernel/quos_md5.c b/kernel/quos_md5.c new file mode 100644 index 0000000..bb255a0 --- /dev/null +++ b/kernel/quos_md5.c @@ -0,0 +1,274 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : MD5算法 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_md5.h" +#if (SDK_ENABLE_MD5 == 1) +#define T1 0xd76aa478 +#define T2 0xe8c7b756 +#define T3 0x242070db +#define T4 0xc1bdceee +#define T5 0xf57c0faf +#define T6 0x4787c62a +#define T7 0xa8304613 +#define T8 0xfd469501 +#define T9 0x698098d8 +#define T10 0x8b44f7af +#define T11 0xffff5bb1 +#define T12 0x895cd7be +#define T13 0x6b901122 +#define T14 0xfd987193 +#define T15 0xa679438e +#define T16 0x49b40821 +#define T17 0xf61e2562 +#define T18 0xc040b340 +#define T19 0x265e5a51 +#define T20 0xe9b6c7aa +#define T21 0xd62f105d +#define T22 0x02441453 +#define T23 0xd8a1e681 +#define T24 0xe7d3fbc8 +#define T25 0x21e1cde6 +#define T26 0xc33707d6 +#define T27 0xf4d50d87 +#define T28 0x455a14ed +#define T29 0xa9e3e905 +#define T30 0xfcefa3f8 +#define T31 0x676f02d9 +#define T32 0x8d2a4c8a +#define T33 0xfffa3942 +#define T34 0x8771f681 +#define T35 0x6d9d6122 +#define T36 0xfde5380c +#define T37 0xa4beea44 +#define T38 0x4bdecfa9 +#define T39 0xf6bb4b60 +#define T40 0xbebfbc70 +#define T41 0x289b7ec6 +#define T42 0xeaa127fa +#define T43 0xd4ef3085 +#define T44 0x04881d05 +#define T45 0xd9d4d039 +#define T46 0xe6db99e5 +#define T47 0x1fa27cf8 +#define T48 0xc4ac5665 +#define T49 0xf4292244 +#define T50 0x432aff97 +#define T51 0xab9423a7 +#define T52 0xfc93a039 +#define T53 0x655b59c3 +#define T54 0x8f0ccc92 +#define T55 0xffeff47d +#define T56 0x85845dd1 +#define T57 0x6fa87e4f +#define T58 0xfe2ce6e0 +#define T59 0xa3014314 +#define T60 0x4e0811a1 +#define T61 0xf7537e82 +#define T62 0xbd3af235 +#define T63 0x2ad7d2bb +#define T64 0xeb86d391 + +static void md5_process(md5_state_t *pms, const quint8_t data[64]) +{ + quint32_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2], d = pms->abcd[3]; + quint32_t t; + + quint32_t xbuf[16]; + const quint32_t *X; + static const int w = 1; + + if (*((const quint8_t *)&w)) + { + if (!((data - (const quint8_t *)0) & 3)) + { + X = (const quint32_t *)data; + } + else + { + HAL_MEMCPY(xbuf, data, 64); + X = xbuf; + } + } + else + { + const quint8_t *xp = data; + int i; + X = xbuf; + for (i = 0; i < 16; ++i, xp += 4) + { + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) +#define FF(a, b, c, d, k, s, Ti) \ + t = a + ((b & c) | (~b & d)) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b +#define GG(a, b, c, d, k, s, Ti) \ + t = a + ((b & d) | (c & ~d)) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b +#define HH(a, b, c, d, k, s, Ti) \ + t = a + ((b) ^ (c) ^ (d)) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b +#define II(a, b, c, d, k, s, Ti) \ + t = a + ((c) ^ ((b) | ~(d))) + X[k] + Ti; \ + a = ROTATE_LEFT(t, s) + b + + FF(a, b, c, d, 0, 7, T1); + FF(d, a, b, c, 1, 12, T2); + FF(c, d, a, b, 2, 17, T3); + FF(b, c, d, a, 3, 22, T4); + FF(a, b, c, d, 4, 7, T5); + FF(d, a, b, c, 5, 12, T6); + FF(c, d, a, b, 6, 17, T7); + FF(b, c, d, a, 7, 22, T8); + FF(a, b, c, d, 8, 7, T9); + FF(d, a, b, c, 9, 12, T10); + FF(c, d, a, b, 10, 17, T11); + FF(b, c, d, a, 11, 22, T12); + FF(a, b, c, d, 12, 7, T13); + FF(d, a, b, c, 13, 12, T14); + FF(c, d, a, b, 14, 17, T15); + FF(b, c, d, a, 15, 22, T16); + + GG(a, b, c, d, 1, 5, T17); + GG(d, a, b, c, 6, 9, T18); + GG(c, d, a, b, 11, 14, T19); + GG(b, c, d, a, 0, 20, T20); + GG(a, b, c, d, 5, 5, T21); + GG(d, a, b, c, 10, 9, T22); + GG(c, d, a, b, 15, 14, T23); + GG(b, c, d, a, 4, 20, T24); + GG(a, b, c, d, 9, 5, T25); + GG(d, a, b, c, 14, 9, T26); + GG(c, d, a, b, 3, 14, T27); + GG(b, c, d, a, 8, 20, T28); + GG(a, b, c, d, 13, 5, T29); + GG(d, a, b, c, 2, 9, T30); + GG(c, d, a, b, 7, 14, T31); + GG(b, c, d, a, 12, 20, T32); + + HH(a, b, c, d, 5, 4, T33); + HH(d, a, b, c, 8, 11, T34); + HH(c, d, a, b, 11, 16, T35); + HH(b, c, d, a, 14, 23, T36); + HH(a, b, c, d, 1, 4, T37); + HH(d, a, b, c, 4, 11, T38); + HH(c, d, a, b, 7, 16, T39); + HH(b, c, d, a, 10, 23, T40); + HH(a, b, c, d, 13, 4, T41); + HH(d, a, b, c, 0, 11, T42); + HH(c, d, a, b, 3, 16, T43); + HH(b, c, d, a, 6, 23, T44); + HH(a, b, c, d, 9, 4, T45); + HH(d, a, b, c, 12, 11, T46); + HH(c, d, a, b, 15, 16, T47); + HH(b, c, d, a, 2, 23, T48); + + II(a, b, c, d, 0, 6, T49); + II(d, a, b, c, 7, 10, T50); + II(c, d, a, b, 14, 15, T51); + II(b, c, d, a, 5, 21, T52); + II(a, b, c, d, 12, 6, T53); + II(d, a, b, c, 3, 10, T54); + II(c, d, a, b, 10, 15, T55); + II(b, c, d, a, 1, 21, T56); + II(a, b, c, d, 8, 6, T57); + II(d, a, b, c, 15, 10, T58); + II(c, d, a, b, 6, 15, T59); + II(b, c, d, a, 13, 21, T60); + II(a, b, c, d, 4, 6, T61); + II(d, a, b, c, 11, 10, T62); + II(c, d, a, b, 2, 15, T63); + II(b, c, d, a, 9, 21, T64); + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void Quos_md5Init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = 0xefcdab89; + pms->abcd[2] = 0x98badcfe; + pms->abcd[3] = 0x10325476; +} + +void Quos_md5Append(md5_state_t *pms, const quint8_t *data, quint32_t nbytes) +{ + const quint8_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + quint32_t nbits = (quint32_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) + { + int copy; + if (offset + nbytes > 64) + { + copy = 64 - offset; + } + else + { + copy = nbytes; + } + + HAL_MEMCPY(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + HAL_MEMCPY(pms->buf, p, left); +} + +void Quos_md5Finish(md5_state_t *pms, quint8_t digest[16]) +{ + static const quint8_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + quint8_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (quint8_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + Quos_md5Append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + Quos_md5Append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (quint8_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} +#endif \ No newline at end of file diff --git a/kernel/quos_mqtt.c b/kernel/quos_mqtt.c new file mode 100644 index 0000000..299256e --- /dev/null +++ b/kernel/quos_mqtt.c @@ -0,0 +1,602 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : MQTT通信管理 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_mqtt.h" +#if (SDK_ENABLE_MQTT == 1) +#include "Quos_kernel.h" +#include "Qhal_driver.h" +#ifndef QUOS_MQTT_RESEND_TIME +#define QUOS_MQTT_RESEND_TIME 3 +#endif +#ifndef QUOS_MQTT_SEND_TIMEOUT +#define QUOS_MQTT_SEND_TIMEOUT 2 * SWT_ONE_SECOND +#endif +#ifndef QUOS_MQTT_CONNECT_TIMEOUT +#define QUOS_MQTT_CONNECT_TIMEOUT 5 * SWT_ONE_SECOND +#endif +#ifndef QUOS_MQTT_PING_TIMEOUT +#define QUOS_MQTT_PING_TIMEOUT 5 * SWT_ONE_SECOND +#endif +typedef struct +{ + void *pingTimer; + quint32_t keepAlive; + MqttEventCb_f eventCB; + MqttpublishRecv_f pubRecv; + quint16_t pkgId; + quint16_t qos2RecvId; +} mqttSock_t; + +static void quos_mqttPing(void *swTimer); +/************************************************************************** +** 功能 @brief : MQTT协议通信数据包解析 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_mqttUnform(const quint8_t *buf, quint32_t bufLen, quint32_t *offset, Quos_socketTempData_t *unformTemp) +{ + if (unformTemp->bufLen == 0) + { + unformTemp->buf = (quint8_t *)HAL_MALLOC(5); + if (NULL == unformTemp->buf) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "mcf"); + *offset = bufLen; + return FALSE; + } + unformTemp->offset = 0; + unformTemp->bufLen = 5; + } + + int pkgLen = 0; + quint32_t i; + for (i = 1; i < unformTemp->offset; i++) + { + if ((unformTemp->buf[i] & 0x80) == 0x00) + { + MQTTPacket_decodeBuf(unformTemp->buf + 1, &pkgLen); + pkgLen = MQTTPacket_len(pkgLen); + } + } + for (*offset = 0; *offset < bufLen; (*offset)++) + { + if (unformTemp->offset == 0) + { + quint8_t type = buf[*offset] >> 4; + quint8_t flag = buf[*offset] & 0x0F; + + if ((0 == flag && (CONNECT == type || CONNACK == type || PUBACK == type || PUBREC == type || PUBCOMP == type || SUBACK == type || UNSUBACK == type || PINGREQ == type || PINGRESP == type || DISCONNECT == type)) || + (1 == flag && (PUBREL == type || SUBSCRIBE == type || UNSUBSCRIBE == type)) || + PUBLISH == type) + { + unformTemp->buf[unformTemp->offset++] = buf[*offset]; + Quos_logPrintf(LSDK_MQTT, LL_DBG, "mqtt head type:%X flag:%X", type, flag); + } + } + else if (0 == pkgLen) + { + unformTemp->buf[unformTemp->offset++] = buf[*offset]; + if ((buf[*offset] & 0x80) == 0x00) + { + MQTTPacket_decodeBuf(unformTemp->buf + 1, &pkgLen); + pkgLen = MQTTPacket_len(pkgLen); + if (pkgLen > (int)unformTemp->bufLen) + { + quint8_t *newBuf = (quint8_t *)HAL_MALLOC(pkgLen); + if (newBuf) + { + HAL_MEMCPY(newBuf, unformTemp->buf, unformTemp->offset); + HAL_FREE(unformTemp->buf); + unformTemp->buf = newBuf; + unformTemp->bufLen = pkgLen; + } + else + { + unformTemp->offset = 0; + *offset = bufLen; + return FALSE; + } + } + else if (pkgLen == 2) + { + Quos_logPrintf(LSDK_MQTT, LL_DBG, "mqtt ok head[0x%02X] len[%u]", unformTemp->buf[0], unformTemp->offset); + (*offset)++; + return TRUE; + } + } + else if (unformTemp->offset >= 5) + { + unformTemp->offset = 0; + pkgLen = 0; + } + } + else + { + unformTemp->buf[unformTemp->offset++] = buf[*offset]; + if (unformTemp->offset >= (quint32_t)pkgLen) + { + Quos_logPrintf(LSDK_MQTT, LL_DBG, "mqtt ok head[0x%02X] len[%u]", unformTemp->buf[0], unformTemp->offset); + (*offset)++; + return TRUE; + } + } + } + + return FALSE; +} +/************************************************************************** +** 功能 @brief : m2m publish send ack +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void quos_mqttPublishRecvAck(void *chlFd, const void *sendData, const void *recvData) +{ + UNUSED(chlFd); + UNUSED(sendData); + if (recvData) + { + Quos_logPrintf(LSDK_MQTT, LL_DBG, "publish success"); + } + else + { + Quos_logPrintf(LSDK_MQTT, LL_DBG, "publish fail"); + } +} +/************************************************************************** +** 功能 @brief : m2m publish send +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qint32_t FUNCTION_ATTR_ROM Quos_mqttPublish(const void *chlFd, char *topicString, const void *param, qint32_t qos, void *buf, quint16_t bufLen, socketRecvNodeCb_f recvCB, qbool isAck) +{ + if (NULL == chlFd || NULL == topicString || NULL == buf || 0 == bufLen) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "chlFd[%p] topicString:%s buf:%p bufLen:%u", chlFd, topicString, buf, bufLen); + return -1; + } + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + if (NULL == chlNode->param) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "mqttSock invalid"); + return -1; + } + mqttSock_t *mqttSock = (mqttSock_t *)chlNode->param; + MQTTString topic; + HAL_MEMSET(&topic, 0, sizeof(topic)); + topic.cstring = topicString; + quint16_t len = MQTTPacket_len(MQTTSerialize_publishLength(qos, topic, bufLen)); + quint8_t *pubBuf = HAL_MALLOC(len); + if (NULL == pubBuf) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "mcf pubBuf"); + return -1; + } + len = MQTTSerialize_publish(pubBuf, len, 0, (int)qos, 0, 0 == qos ? 0 : ((++mqttSock->pkgId) == 0 ? (++mqttSock->pkgId) : mqttSock->pkgId), topic, buf, bufLen); + if (isAck) + { + return Quos_socketTxDisorder(chlFd, NULL, pubBuf, len) ? mqttSock->pkgId : -1; + } + else if (0 == qos) + { + return Quos_socketTx(chlFd, NULL, 0, 0, NULL, NULL, 0, pubBuf, len, param) ? mqttSock->pkgId : -1; + } + else + { + return Quos_socketTx(chlFd, NULL, 0, 0, NULL, recvCB ? recvCB : quos_mqttPublishRecvAck, mqttSock->pkgId, pubBuf, len, param) ? mqttSock->pkgId : -1; + } +} + +/************************************************************************** +** 功能 @brief : 解析publish包 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_mqttPublishReslover(quint8_t *srcData, quint32_t srcLen, MQTTString *topicName, quint8_t **payload, int *payloadlen) +{ + int qos; + quint8_t dup = 0; + quint16_t packetid = 0; + quint8_t retained; + if (MQTTDeserialize_publish(&dup, &qos, &retained, &packetid, topicName, payload, payloadlen, srcData, srcLen)) + { + return TRUE; + } + else + { + return FALSE; + } +} +/************************************************************************** +** 功能 @brief : mqtt接收数据处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_mqttRecv(void *chlFd, const void *peer, quint32_t peerSize, Quos_socketRecvDataNode_t *recvData) +{ + UNUSED(peer); + UNUSED(peerSize); + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + mqttSock_t *mqttSock = (mqttSock_t *)chlNode->param; + if (NULL == recvData) + { + Quos_swTimerDelete(mqttSock->pingTimer); + mqttSock->eventCB(chlFd, QUOS_MQTT_ERR_NET); + return FALSE; + } + Quos_logHexDump(LSDK_MQTT, LL_DUMP, "m2m recv", recvData->Buf, recvData->bufLen); + + switch (recvData->Buf[0] >> 4) + { + case PUBLISH: + { + int qos; + quint8_t dup = 0; + quint16_t packetid = 0; + quint8_t retained; + MQTTString topicName; + quint8_t *payload; + int payloadlen; + if (MQTTDeserialize_publish(&dup, &qos, &retained, &packetid, &topicName, &payload, &payloadlen, recvData->Buf, recvData->bufLen)) + { + Quos_logPrintf(LSDK_MQTT, LL_DBG, "dup[%u] qos[%d] retained[%u] packetid[%u] topicName[%.*s] payload[%p] payloadlen[%d]", dup, qos, retained, packetid, topicName.lenstring.len, topicName.lenstring.data, payload, payloadlen); + if (0 == qos) + { + mqttSock->pubRecv(&topicName, payload, (quint32_t)payloadlen); + } + else if (1 == qos) + { + mqttSock->pubRecv(&topicName, payload, payloadlen); + quint16_t len = 4; + quint8_t *buf = HAL_MALLOC(len); + if (buf) + { + MQTTSerialize_ack(buf, len, PUBACK, 0, packetid); + Quos_socketTxDisorder(chlFd, NULL, buf, len); + } + } + else if (2 == qos) + { + if (mqttSock->qos2RecvId != packetid) + { + mqttSock->qos2RecvId = packetid; + mqttSock->pubRecv(&topicName, payload, payloadlen); + } + quint16_t len = 4; + quint8_t *buf = HAL_MALLOC(len); + if (buf) + { + MQTTSerialize_ack(buf, len, PUBREC, dup, packetid); + Quos_socketTxDisorder(chlFd, NULL, buf, len); + } + } + } + break; + } + case CONNACK: + { + Quos_socketTxAck(chlFd, NULL, 0, recvData); + break; + } + case PINGRESP: + Quos_swTimerTimeoutSet(mqttSock->pingTimer, mqttSock->keepAlive - (QUOS_MQTT_RESEND_TIME + 1) * QUOS_MQTT_PING_TIMEOUT); + Quos_swTimerRepeatSet(mqttSock->pingTimer, QUOS_MQTT_RESEND_TIME); + Quos_logPrintf(LSDK_MQTT, LL_INFO, "PINGRESP"); + break; + case SUBACK: + case PUBCOMP: + case PUBACK: + case UNSUBACK: + { + quint8_t type = 0, dup = 0; + quint16_t packetid = 0; + MQTTDeserialize_ack(&type, &dup, &packetid, recvData->Buf, recvData->bufLen); + Quos_socketTxAck(chlFd, NULL, packetid, recvData); + break; + } + case PUBREC: + { + quint16_t len = 4; + quint8_t *buf = HAL_MALLOC(len); + if (buf) + { + quint8_t type = 0, dup = 0; + quint16_t packetid = 0; + MQTTDeserialize_ack(&type, &dup, &packetid, recvData->Buf, recvData->bufLen); + MQTTSerialize_ack(buf, len, PUBREL, dup, packetid); + Quos_socketTxDisorder(chlFd, NULL, buf, len); + } + break; + } + case PUBREL: + { + quint16_t len = 4; + quint8_t *buf = HAL_MALLOC(len); + if (buf) + { + quint8_t type = 0, dup = 0; + quint16_t packetid = 0; + MQTTDeserialize_ack(&type, &dup, &packetid, recvData->Buf, recvData->bufLen); + MQTTSerialize_pubcomp(buf, len, packetid); + Quos_socketTxDisorder(chlFd, NULL, buf, len); + } + break; + } + default: + break; + } + return TRUE; +} +/************************************************************************** +** 功能 @brief : mqtt断开 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_mqttParamFree(void *param) +{ + mqttSock_t *mqttSock = (mqttSock_t *)param; + Quos_swTimerDelete(mqttSock->pingTimer); + HAL_FREE(mqttSock); +} +/************************************************************************** +** 功能 @brief : Mqtt connect应答结果 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_mqttConnectAck(void *chlFd, const void *sendData, const void *recvDataIn) +{ + UNUSED(sendData); + Quos_socketRecvDataNode_t *recvData = (Quos_socketRecvDataNode_t *)recvDataIn; + quint8_t sessionPresent, connack_rc = 0; + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + mqttSock_t *mqttSock = (mqttSock_t *)chlNode->param; + if (NULL == recvData) + { + mqttSock->eventCB(chlFd, QUOS_MQTT_ERR_NET); + Quos_socketChannelDel((void *)chlNode); + } + else if (1 != MQTTDeserialize_connack(&sessionPresent, &connack_rc, (quint8_t *)recvData->Buf, recvData->bufLen)) + { + mqttSock->eventCB(chlFd, QUOS_MQTT_ERR_CONNECT); + Quos_socketChannelDel(chlFd); + } + else if (QUOS_MQTT_CONNECTION_ACCEPTED != connack_rc) + { + mqttSock->eventCB(chlFd, (qint32_t)connack_rc); + Quos_socketChannelDel(chlFd); + } + else if (FALSE == Quos_swTimerStart(&mqttSock->pingTimer, "MQTT ping", (mqttSock->keepAlive - (QUOS_MQTT_RESEND_TIME + 1) * QUOS_MQTT_PING_TIMEOUT) / 2, QUOS_MQTT_RESEND_TIME, quos_mqttPing, chlFd)) + { + mqttSock->eventCB(chlFd, QUOS_MQTT_ERR_INSIDE); + Quos_socketChannelDel(chlFd); + } + else + { + mqttSock->eventCB(chlFd, QUOS_MQTT_OK_CONNECT); + } +} +/************************************************************************** +** 功能 @brief : Mqtt connect&subscribe&ping应答结果 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_mqttSubcribeAck(void *chlFd, const void *sendData, const void *recvDataIn) +{ + Quos_socketRecvDataNode_t *recvData = (Quos_socketRecvDataNode_t *)recvDataIn; + quint16_t packetid; + int count, grantedQoSs[100]; + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + mqttSock_t *mqttSock = (mqttSock_t *)chlNode->param; + UNUSED(sendData); + if (NULL == recvData || + 1 != MQTTDeserialize_suback(&packetid, sizeof(grantedQoSs) / sizeof(grantedQoSs[0]), &count, grantedQoSs, recvData->Buf, recvData->bufLen)) + { + Quos_swTimerDelete(mqttSock->pingTimer); + mqttSock->eventCB(chlFd, QUOS_MQTT_ERR_SUBSCRI); + Quos_socketChannelDel(NULL == recvData ? (void *)chlNode : chlFd); + } + else + { + mqttSock->eventCB(chlFd, QUOS_MQTT_OK_SUBSCRIBE); + } +} +/************************************************************************** +** 功能 @brief : MQTT 心跳 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_mqttPing(void *swTimer) +{ + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)Quos_swTimerParmGet(swTimer); + if (0 == Quos_swTimerRepeatGet(swTimer) && Quos_socketCheckChlFd(chlNode) ) + { + mqttSock_t *mqttSock = (mqttSock_t *)chlNode->param; + mqttSock->eventCB(chlNode, QUOS_MQTT_ERR_PING); + Quos_socketChannelDel((void *)chlNode); + return; + } + quint16_t len = 2; + quint8_t *buf = HAL_MALLOC(len); + Quos_swTimerTimeoutSet(swTimer, QUOS_MQTT_PING_TIMEOUT); + if (NULL == buf) + { + return; + } + Quos_logPrintf(LSDK_MQTT, LL_INFO, "ping"); + MQTTSerialize_pingreq(buf, len); + Quos_socketTxDisorder(chlNode, NULL, buf, len); +} +/************************************************************************** +** 功能 @brief : Mqtt socket connet结果 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_mqttSockConnctCB(void *chlFd, qbool result) +{ + Quos_logPrintf(LSDK_MQTT, LL_DBG, "chlFd[%p] result:%s", chlFd, _BOOL2STR(result)); + if (result == FALSE && NULL != chlFd) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + ((mqttSock_t *)(chlNode->param))->eventCB(chlFd, QUOS_MQTT_ERR_NET); + } + return TRUE; +} + +/************************************************************************** +** 功能 @brief : Mqtt服务初始化 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_mqttInit(void **chlFdPoint, + const char *url, + const char *clientID, + const char *username, + const char *password, + quint16_t keepAlive, + quint8_t topicCount, + char *topicString[], + const int *requestedQoSs, + MqttEventCb_f eventCB, + MqttpublishRecv_f pubRecv) +{ + urlAnalyze_t urlA; + if (NULL == url || NULL == clientID || NULL == password || (0 != topicCount && (NULL == topicString || NULL == requestedQoSs)) || NULL == eventCB || NULL == pubRecv) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "url[%p] clientID[%s] username[%s] password[%s] topicCount[%u] topic[%p] requestedQoSs[%p] eventCB[%p] pubRecv[%p]", + url, clientID, username, password, topicCount, topicString, requestedQoSs, eventCB, pubRecv); + return FALSE; + } + if (FALSE == (Quos_urlAnalyze(url, &urlA))) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "url analyze fail"); + return FALSE; + } + urlA.port = urlA.port ? urlA.port : (urlA.isSecure ? 8883 : 1883); + + mqttSock_t *mqttSock = HAL_MALLOC(sizeof(mqttSock_t)); + if (NULL == mqttSock) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "mcf mqttSock"); + return FALSE; + } + HAL_MEMSET(mqttSock, 0, sizeof(mqttSock_t)); + + Quos_socketChlInfoNode_t chlInfo; + HAL_MEMSET(&chlInfo, 0, sizeof(Quos_socketChlInfoNode_t)); + if (urlA.isSecure) + { +#if (SDK_ENABLE_TLS == 1) + chlInfo.sockFd = Qhal_tcpSslClientInit(&chlInfo.type, urlA.hostname, urlA.port, &chlInfo.conn.timeout); +#else + chlInfo.sockFd = SOCKET_FD_INVALID; +#endif + } + else + { + chlInfo.sockFd = Qhal_tcpClientInit(&chlInfo.type, urlA.hostname, urlA.port, &chlInfo.conn.timeout); + } + if (chlInfo.conn.timeout) + { + chlInfo.conn.notify = quos_mqttSockConnctCB; + } + if (SOCKET_FD_INVALID == chlInfo.sockFd) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "mqtt conn fail:%s[%u]", urlA.hostname, urlA.port); + HAL_FREE(mqttSock); + return FALSE; + } + + chlInfo.io.send = Qhal_sockWrite; + chlInfo.send.txCnt = QUOS_MQTT_RESEND_TIME; + chlInfo.send.timeout = QUOS_MQTT_SEND_TIMEOUT; + chlInfo.recvDataFunc = quos_mqttRecv; + chlInfo.unformFunc = quos_mqttUnform; + chlInfo.io.close = Qhal_sockClose; + chlInfo.paramFree = quos_mqttParamFree; + chlInfo.param = mqttSock; + + if (keepAlive <= (QUOS_MQTT_RESEND_TIME + 1) * QUOS_MQTT_PING_TIMEOUT / SWT_ONE_SECOND) + { + keepAlive = (QUOS_MQTT_RESEND_TIME + 1) * QUOS_MQTT_PING_TIMEOUT / SWT_ONE_SECOND + 1; + } + mqttSock->keepAlive = (quint32_t)keepAlive * SWT_ONE_SECOND; + mqttSock->eventCB = eventCB; + mqttSock->pubRecv = pubRecv; + + void *chlFd = Quos_socketChannelAdd(chlFdPoint, chlInfo); + if (NULL == chlFd) + { + Qhal_sockClose(chlInfo.sockFd, chlInfo.type); + HAL_FREE(mqttSock); + Quos_logPrintf(LSDK_MQTT, LL_ERR, "add socket Channel fail"); + return FALSE; + } + + MQTTPacket_connectData mqttConnData = MQTTPacket_connectData_initializer; + mqttConnData.keepAliveInterval = keepAlive; + mqttConnData.clientID.cstring = (char *)clientID; + mqttConnData.username.cstring = (char *)username; + mqttConnData.password.cstring = (char *)password; + + quint16_t len = MQTTPacket_len(MQTTSerialize_connectLength(&mqttConnData)); + quint8_t *pkg = HAL_MALLOC(len); + if (NULL == pkg) + { + Quos_socketChannelDel(chlFd); + return FALSE; + } + len = MQTTSerialize_connect(pkg, len, &mqttConnData); + if (FALSE == Quos_socketTx(chlFd, NULL, 0, QUOS_MQTT_CONNECT_TIMEOUT, NULL, quos_mqttConnectAck, 0, pkg, len, NULL)) + { + Quos_socketChannelDel(chlFd); + return FALSE; + } + if (topicCount > 0) + { + MQTTString *topic = HAL_MALLOC(sizeof(MQTTString) * topicCount); + if (NULL == topic) + { + Quos_socketChannelDel(chlFd); + return FALSE; + } + HAL_MEMSET(topic, 0, sizeof(MQTTString) * topicCount); + quint8_t i; + for (i = 0; i < topicCount; i++) + { + topic[i].cstring = topicString[i]; + } + + len = MQTTPacket_len(MQTTSerialize_subscribeLength(topicCount, topic)); + pkg = HAL_MALLOC(len); + if (NULL == pkg) + { + HAL_FREE(topic); + Quos_socketChannelDel(chlFd); + return FALSE; + } + len = MQTTSerialize_subscribe(pkg, len, 0, ++mqttSock->pkgId, topicCount, topic, (int *)requestedQoSs); + HAL_FREE(topic); + if (FALSE == Quos_socketTx(chlFd, NULL, 0, QUOS_MQTT_CONNECT_TIMEOUT, NULL, quos_mqttSubcribeAck, mqttSock->pkgId, pkg, len, NULL)) + { + Quos_logPrintf(LSDK_MQTT, LL_ERR, "mcf subscribe pkg"); + Quos_socketChannelDel(chlFd); + return FALSE; + } + } + Quos_logPrintf(LSDK_MQTT, LL_DBG, "mqtt init ok"); + return TRUE; +} +/************************************************************************** +** 功能 @brief : 关闭MQTT服务 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_mqttDeinit(void *chlFd) +{ + Quos_socketChannelDel(chlFd); +} +#endif \ No newline at end of file diff --git a/kernel/quos_mqtt.h b/kernel/quos_mqtt.h index 9a08d12..b714637 100644 --- a/kernel/quos_mqtt.h +++ b/kernel/quos_mqtt.h @@ -1,51 +1,52 @@ -#ifndef __QUOS_MQTT_H__ -#define __QUOS_MQTT_H__ -#include "quos_config.h" -#if (SDK_ENABLE_MQTT == 1) -#include "quos_socket.h" -#include "MQTTPacket.h" - -enum -{ - QUOS_MQTT_ERR_INSIDE = -5, - QUOS_MQTT_ERR_NET = -4, - QUOS_MQTT_ERR_PING = -3, - QUOS_MQTT_ERR_SUBSCRI = -2, - QUOS_MQTT_ERR_CONNECT = -1, - QUOS_MQTT_OK_CONNECT = 0, - QUOS_MQTT_OK_SUBSCRIBE = 100, -}; - -#define MQTT_ERR_STRING(X) \ - ( \ - (X == QUOS_MQTT_ERR_PING) ? "QUOS_MQTT_ERR_PING" : (X == QUOS_MQTT_ERR_SUBSCRI) ? "MQTT_ERR_SUBSCRI" \ - : (X == QUOS_MQTT_ERR_CONNECT) ? "MQTT_ERR_CONNECT" \ - : (X == QUOS_MQTT_ERR_NET) ? "MQTT_ERR_NET" \ - : (X == QUOS_MQTT_OK_CONNECT) ? "MQTT_OK_CONNECT" \ - : (X == QUOS_MQTT_OK_SUBSCRIBE) ? "MQTT_OK_SUBSCRIBE" \ - : (X == QUOS_MQTT_UNNACCEPTABLE_PROTOCOL) ? "MQTT_UNNACCEPTABLE_PROTOCOL" \ - : (X == QUOS_MQTT_CLIENTID_REJECTED) ? "MQTT_CLIENTID_REJECTED" \ - : (X == QUOS_MQTT_SERVER_UNAVAILABLE) ? "MQTT_SERVER_UNAVAILABLE" \ - : (X == QUOS_MQTT_BAD_USERNAME_OR_PASSWORD) ? "MQTT_BAD_USERNAME_OR_PASSWORD" \ - : (X == QUOS_MQTT_NOT_AUTHORIZED) ? "MQTT_NOT_AUTHORIZED" \ - : "Unknown") - -typedef void (*MqttEventCb_f)(void *chlFd, qint32_t event); -typedef void (*MqttpublishRecv_f)(MQTTString *topicName, quint8_t *payload, quint32_t payloadlen); - -qbool Quos_mqttInit(void **chlFdPoint, - const char *url, - const char *clientID, - const char *username, - const char *password, - quint16_t keepAlive, - quint8_t topicCount, - MQTTString *topic, - const int *requestedQoSs, - MqttEventCb_f eventCB, - MqttpublishRecv_f pubRecv); -void Quos_mqttDeinit(void *chlFd); -qint32_t Quos_mqttPublishSend(const void *chlFd, char *topicString, const void *param, qint32_t qos, void *buf, quint16_t bufLen, socketRecvNodeCb_f recvCB); -qbool Quos_mqttPublishReslover(quint8_t *srcData, quint32_t srcLen, MQTTString *topicName, quint8_t **payload, int *payloadlen); -#endif +#ifndef __QUOS_MQTT_H__ +#define __QUOS_MQTT_H__ +#include "quos_config.h" +#if (SDK_ENABLE_MQTT == 1) +#include "quos_socket.h" +#include "MQTTPacket.h" + +enum +{ + QUOS_MQTT_ERR_INSIDE = -5, + QUOS_MQTT_ERR_NET = -4, + QUOS_MQTT_ERR_PING = -3, + QUOS_MQTT_ERR_SUBSCRI = -2, + QUOS_MQTT_ERR_CONNECT = -1, + QUOS_MQTT_OK_CONNECT = 0, + QUOS_MQTT_OK_SUBSCRIBE = 100, +}; + +#define MQTT_ERR_STRING(X) \ + ( \ + (X == QUOS_MQTT_ERR_PING) ? "QUOS_MQTT_ERR_PING" : (X == QUOS_MQTT_ERR_SUBSCRI) ? "MQTT_ERR_SUBSCRI" \ + : (X == QUOS_MQTT_ERR_CONNECT) ? "MQTT_ERR_CONNECT" \ + : (X == QUOS_MQTT_ERR_NET) ? "MQTT_ERR_NET" \ + : (X == QUOS_MQTT_OK_CONNECT) ? "MQTT_OK_CONNECT" \ + : (X == QUOS_MQTT_OK_SUBSCRIBE) ? "MQTT_OK_SUBSCRIBE" \ + : (X == QUOS_MQTT_UNNACCEPTABLE_PROTOCOL) ? "MQTT_UNNACCEPTABLE_PROTOCOL" \ + : (X == QUOS_MQTT_CLIENTID_REJECTED) ? "MQTT_CLIENTID_REJECTED" \ + : (X == QUOS_MQTT_SERVER_UNAVAILABLE) ? "MQTT_SERVER_UNAVAILABLE" \ + : (X == QUOS_MQTT_BAD_USERNAME_OR_PASSWORD) ? "MQTT_BAD_USERNAME_OR_PASSWORD" \ + : (X == QUOS_MQTT_NOT_AUTHORIZED) ? "MQTT_NOT_AUTHORIZED" \ + : "Unknown") + +typedef void (*MqttEventCb_f)(void *chlFd, qint32_t event); +typedef void (*MqttpublishRecv_f)(MQTTString *topicName, quint8_t *payload, quint32_t payloadlen); + +qbool Quos_mqttInit(void **chlFdPoint, + const char *url, + const char *clientID, + const char *username, + const char *password, + quint16_t keepAlive, + quint8_t topicCount, + char *topicString[], + const int *requestedQoSs, + MqttEventCb_f eventCB, + MqttpublishRecv_f pubRecv); +void Quos_mqttDeinit(void *chlFd); + +qint32_t Quos_mqttPublish(const void *chlFd, char *topicString, const void *param, qint32_t qos, void *buf, quint16_t bufLen, socketRecvNodeCb_f recvCB, qbool isAck); +qbool Quos_mqttPublishReslover(quint8_t *srcData, quint32_t srcLen, MQTTString *topicName, quint8_t **payload, int *payloadlen); +#endif #endif \ No newline at end of file diff --git a/kernel/quos_net.c b/kernel/quos_net.c new file mode 100644 index 0000000..05bced8 --- /dev/null +++ b/kernel/quos_net.c @@ -0,0 +1,315 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : 2021-02-09 +** 功能 @brief : 网络管理 +** 硬件 @hardware: +** 其他 @other : +***************************************************************************/ +#include "quos_net.h" +#include "quos_swTimer.h" +#include "quos_event.h" +#include "quos_log.h" +#include "quos_SupportTool.h" +#include "quos_twll.h" +#include "Qhal_driver.h" + +typedef struct +{ + TWLLHead_T head; + char hostname[QUOS_DNS_HOSTNANE_MAX_LENGHT]; + quint8_t defaultIp[QUOS_IP_ADDR_MAX_LEN]; + quint8_t ip[DNS_IP_FROM_HOSTNAME_MAX_NUM][QUOS_IP_ADDR_MAX_LEN]; + qint8_t ip_num; +} Quos_netHostInfo_t; + +static TWLLHead_T *HostInfoHead = NULL; +static void *NetRetryTimer = NULL; +static qbool NetIsConnected = FALSE; +/************************************************************************** +** 功能 @brief : 尝试启动硬件网络连接 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_netRetry(void *swtimer) +{ + quint32_t timeout = 30 * SWT_ONE_SECOND; + Qhal_netOpen(&timeout); + Quos_logPrintf(LSDK_NET, LL_DBG, "net open wait:%dms", timeout); + if (NetIsConnected) + { + /* 在Qhal_netOpen函数执行过程中,网络已连接成功事件可能已经通过函数Quos_netIOStatusNotify已经将NetIsConnected设置为TRUE了,故此处需要判断NetIsConnected是否被置为TRUE */ + } + else if (0 == timeout) + { + if (Quos_eventPost(QUOS_SYSTEM_EVENT_NETWORK, (void *)QUOS_SEVENT_NET_CONNECTED)) + { + Quos_swTimerTimeoutSet(swtimer, SWT_SUSPEND); + NetIsConnected = TRUE; + Quos_logPrintf(LSDK_NET, LL_DBG, "post EVENT_NETCONNECTED ok"); + } + else + { + Quos_logPrintf(LSDK_NET, LL_ERR, "post EVENT_NETCONNECTED fail"); + } + } + else + { + if(0 != Quos_swTimerTimeoutGet(swtimer)) + { + Quos_eventPost(QUOS_SYSTEM_EVENT_NETWORK, (void *)QUOS_SEVENT_NET_CONNTIMEOUT); + } + Quos_swTimerTimeoutSet(swtimer, timeout); + } +} +/************************************************************************** +** 功能 @brief : 打开网络服务 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_netOpen(void) +{ + Quos_logPrintf(LSDK_NET, LL_DBG, "NetRetryTimer:%p", NetRetryTimer); + if (NULL == NetRetryTimer || SWT_SUSPEND == Quos_swTimerTimeoutGet(NetRetryTimer)) + { + if (NetIsConnected && Quos_eventPost(QUOS_SYSTEM_EVENT_NETWORK, (void *)QUOS_SEVENT_NET_DISCONNECT)) + { + NetIsConnected = FALSE; + } + Quos_swTimerStart(&NetRetryTimer, "net", 0, 0, quos_netRetry, NULL); + } +} +/************************************************************************** +** 功能 @brief : 关闭网络服务 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_netClose(void) +{ + if (NetRetryTimer && SWT_SUSPEND == Quos_swTimerTimeoutGet(NetRetryTimer)) + { + if (Quos_eventPost(QUOS_SYSTEM_EVENT_NETWORK, (void *)QUOS_SEVENT_NET_DISCONNECT)) + { + Quos_logPrintf(LSDK_NET, LL_DBG, "post EVENT_NETDISCONNECT ok"); + NetIsConnected = FALSE; + Quos_swTimerDelete(NetRetryTimer); + Qhal_netClose(); + } + else + { + Quos_logPrintf(LSDK_NET, LL_ERR, "post EVENT_NETDISCONNECT fail"); + } + } + else + { + Quos_swTimerDelete(NetRetryTimer); + } +} +/************************************************************************** +** 功能 @brief : 网络状态变化通知 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_netIOStatusNotify(qbool isConn) +{ + Quos_logPrintf(LSDK_NET, LL_DBG, "isConn:%s", _BOOL2STR(isConn)); + if (NULL == NetRetryTimer) + { + return; + } + else if (SWT_SUSPEND == Quos_swTimerTimeoutGet(NetRetryTimer) && FALSE == isConn) + { + if (Quos_eventPost(QUOS_SYSTEM_EVENT_NETWORK, (void *)QUOS_SEVENT_NET_DISCONNECT)) + { + NetIsConnected = FALSE; + Quos_logPrintf(LSDK_NET, LL_INFO, "post EVENT_NETDISCONNECT ok"); + } + else + { + Quos_logPrintf(LSDK_NET, LL_ERR, "post EVENT_NETDISCONNECT fail"); + } + Quos_swTimerTimeoutSet(NetRetryTimer, 0); + } + else if (SWT_SUSPEND != Quos_swTimerTimeoutGet(NetRetryTimer) && TRUE == isConn) + { + if (Quos_eventPost(QUOS_SYSTEM_EVENT_NETWORK, (void *)QUOS_SEVENT_NET_CONNECTED)) + { + Quos_logPrintf(LSDK_NET, LL_DBG, "post EVENT_NETCONNECTED ok"); + NetIsConnected = TRUE; + Quos_swTimerTimeoutSet(NetRetryTimer, SWT_SUSPEND); + } + else + { + Quos_logPrintf(LSDK_NET, LL_ERR, "post EVENT_NETCONNECTED fail"); + } + } +} +/************************************************************************** +** 功能 @brief :根据hostname查找当前节点 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static Quos_netHostInfo_t *quos_hostnameNodeFind(const char *hostname) +{ + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA(HostInfoHead, temp, next) + { + Quos_netHostInfo_t *node = __GET_STRUCT_BY_ELEMENT(temp, Quos_netHostInfo_t, head); + if (0 == HAL_STRCMP(node->hostname, hostname)) + { + Quos_logPrintf(LSDK_NET, LL_DBG, "[%s] node ok", hostname); + return node; + } + } + Quos_logPrintf(LSDK_NET, LL_ERR, "no [%s] node", hostname); + return NULL; +} +/************************************************************************** +** 功能 @brief : 删除域名相关信息管理节点 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Quos_netHostnameDelete(const char *hostname) +{ + Quos_netHostInfo_t *node = quos_hostnameNodeFind(hostname); + if (node) + { + Quos_twllHeadDelete(&HostInfoHead, &node->head); + HAL_FREE(node); + Quos_logPrintf(LSDK_NET, LL_DBG, "[%s] node ok", hostname); + } +} +/************************************************************************** +** 功能 @brief : 增加域名相关信息 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +Quos_netHostInfo_t *Quos_hostnameNodeAdd(const char *hostname) +{ + Quos_netHostInfo_t *node = quos_hostnameNodeFind(hostname); + if (NULL == node && (node = HAL_MALLOC(sizeof(Quos_netHostInfo_t))) != NULL) + { + HAL_MEMSET(node, 0, sizeof(Quos_netHostInfo_t)); + Quos_twllHeadAdd(&HostInfoHead, &node->head); + HAL_SNPRINTF(node->hostname, sizeof(node->hostname), "%s", hostname); + Quos_logPrintf(LSDK_NET, LL_DBG, "[%s] node ok", hostname); + } + + return node; +} +/************************************************************************** +** 功能 @brief :添加域名信息管理节点 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Quos_netHostnameSetDefault(const char *hostname, const char *ip) +{ + Quos_netHostInfo_t *node = Quos_hostnameNodeAdd(hostname); + if (node) + { + if (ip && HAL_STRLEN(ip)) + { + HAL_MEMCPY(node->defaultIp, ip, QUOS_IP_ADDR_MAX_LEN); + } + else + { + HAL_MEMSET(node->defaultIp, 0, QUOS_IP_ADDR_MAX_LEN); + } + Quos_logPrintf(LSDK_NET, LL_DBG, "[%s][%s] ok", hostname, ip); + } +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Quos_netHostnameDnsEnable(const char *hostname) +{ + Quos_netHostInfo_t *node = Quos_hostnameNodeAdd(hostname); + if (node) + { + node->ip_num = HAL_STRLEN(node->defaultIp) ? -1 : 0; + Quos_logPrintf(LSDK_NET, LL_DBG, "[%s] %d ok", hostname, node->ip_num); + } + else + { + Quos_logPrintf(LSDK_NET, LL_ERR, "[%s] enable fail", hostname); + } +} + +/************************************************************************** +** 功能 @brief : 根据DNS获取ip值 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint8_t *Quos_netGetIpFromHostname(const char *hostname) +{ + Quos_netHostInfo_t *node = quos_hostnameNodeFind(hostname); + if (NULL == node || node->ip_num >= DNS_IP_FROM_HOSTNAME_MAX_NUM) + { + Quos_logPrintf(LSDK_NET, LL_ERR, "[%s] vaild", hostname); + return NULL; + } +#if 0 + //xjin.gao 20211206 adaptation//depot10/quecthing/quecthingSDK/2.9.0/ node->ip_num Adapt to the "char", Will not be -1 + if (-1 == node->ip_num) + { + node->ip_num = 0; + Quos_logPrintf(LSDK_NET, LL_DBG, "[%s] default ip[%s]", hostname, node->defaultIp); + return HAL_STRLEN(node->defaultIp) ? node->defaultIp : NULL; + } + else +#endif + if (0 == node->ip_num) + { + /* 使用域名进行DNS解析 */ + quint8_t *ipPtr[DNS_IP_FROM_HOSTNAME_MAX_NUM]; + for (/*NULL*/; node->ip_num < DNS_IP_FROM_HOSTNAME_MAX_NUM; node->ip_num++) + { + ipPtr[(quint8_t)node->ip_num] = node->ip[(quint8_t)node->ip_num]; + } + node->ip_num = (quint8_t)Qhal_dns2IPGet(hostname, ipPtr, node->ip_num); + Quos_logPrintf(LSDK_NET, LL_DBG, "[%s] do dns[%d]", hostname, node->ip_num); + while (node->ip_num--) + { + //xjin.gao 20211206 adaptation//depot10/quecthing/quecthingSDK/2.9.0/ node->ip_num Adapt to the "char", do not print this log + //Quos_logPrintf(LSDK_NET, LL_DBG, "[%s] [%d][%s]", hostname, node->ip_num, node->ip[node->ip_num]); + } + node->ip_num = 0; + } + return HAL_STRLEN(node->ip[(quint8_t)node->ip_num]) ? node->ip[(quint8_t)node->ip_num++] : NULL; +} +/************************************************************************** +** 功能 @brief : 底层连接成功后告知当前域名对应的有效ip序号 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void Quos_netHostnameValidIpNumSet(const char *hostname) +{ + Quos_netHostInfo_t *node = quos_hostnameNodeFind(hostname); + if (node && node->ip_num > 0) + { + HAL_MEMCPY(node->defaultIp, node->ip[(quint8_t)node->ip_num - 1], QUOS_IP_ADDR_MAX_LEN); + Quos_logPrintf(LSDK_NET, LL_DBG, "[%s] set [%d][%s] to default", hostname, node->ip_num - 1, node->defaultIp); + } +} +/************************************************************************** +** 功能 @brief : 根据域名获取有效ip +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool Quos_netHostnameValidIpGet(const char *hostname, char *ip) +{ + Quos_netHostInfo_t *node = quos_hostnameNodeFind(hostname); + + /* 没有找到域名对应的节点,或者当前域名没有发生过解析ip或IP没有变化,故不需要进行替换和保存 */ + if (NULL == node || 0 == HAL_STRLEN(node->defaultIp)) + { + return FALSE; + } + if (ip) + { + HAL_MEMCPY((quint8_t *)ip, node->defaultIp, QUOS_IP_ADDR_MAX_LEN); + } + return TRUE; +} diff --git a/kernel/quos_net.h b/kernel/quos_net.h index 97a9191..bb0997f 100644 --- a/kernel/quos_net.h +++ b/kernel/quos_net.h @@ -1,7 +1,16 @@ -#ifndef __QUOS_NET_H__ -#define __QUOS_NET_H__ -#include "quos_config.h" -void Quos_netOpen(void); -void Quos_netClose(void); -void Quos_netIOStatusNotify(qbool result); +#ifndef __QUOS_NET_H__ +#define __QUOS_NET_H__ +#include "quos_config.h" +void Quos_netOpen(void); +void Quos_netClose(void); +void Quos_netIOStatusNotify(qbool result); + +#define DNS_IP_FROM_HOSTNAME_MAX_NUM (3) /* 从DNS中获取ip个数 */ + +void Quos_netHostnameSetDefault(const char *hostname, const char *ip); +quint8_t *Quos_netGetIpFromHostname(const char *hostname); +void Quos_netHostnameValidIpNumSet(const char *hostname); +qbool Quos_netHostnameValidIpGet(const char *hostname, char *ip); +void Quos_netHostnameDnsEnable(const char *hostname); +void Quos_netHostnameDelete(const char *hostname); #endif \ No newline at end of file diff --git a/kernel/quos_sha1.c b/kernel/quos_sha1.c new file mode 100644 index 0000000..83048d6 --- /dev/null +++ b/kernel/quos_sha1.c @@ -0,0 +1,216 @@ +#include "quos_sha1.h" +#include "Quos_kernel.h" + +#if (SDK_ENABLE_SHA1 == 1) +static const quint32_t K[] = { + 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6}; + +static const quint8_t sha1_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +#define S(x, n) ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n))) + +#define R(t) \ + (temp = W[(t - 3) & 0x0F] ^ W[(t - 8) & 0x0F] ^ W[(t - 14) & 0x0F] ^ W[t & 0x0F], (W[t & 0x0F] = S(temp, 1))) + +#define P(a, b, c, d, e, x, K) \ + do \ + { \ + e += S(a, 5) + F(b, c, d) + K + x; \ + b = S(b, 30); \ + } while (0) + +static void quos_sha1process(SHA1_ctx_t *ctx, const quint8_t data[64]) +{ + quint32_t temp, W[16]; + quint32_t A[5]; + quint32_t i; + for (i = 0; i < 5; i++) + { + A[i] = ctx->state[i]; + } + for (i = 0; i < 16; i++) + { + W[i] = _ARRAY0123_U32(data + 4 * i); + } + +#define F(x, y, z) (z ^ (x & (y ^ z))) + + for (i = 0; i < 16; i++) + { + P(A[0], A[1], A[2], A[3], A[4], W[i], K[i / 20]); + temp = A[4]; + A[4] = A[3]; + A[3] = A[2]; + A[2] = A[1]; + A[1] = A[0]; + A[0] = temp; + } + for (i = 16; i < 20; i++) + { + P(A[0], A[1], A[2], A[3], A[4], R(i), K[i / 20]); + temp = A[4]; + A[4] = A[3]; + A[3] = A[2]; + A[2] = A[1]; + A[1] = A[0]; + A[0] = temp; + } +#undef F + +#define F(x, y, z) (x ^ y ^ z) + + for (i = 20; i < 40; i++) + { + P(A[0], A[1], A[2], A[3], A[4], R(i), K[i / 20]); + temp = A[4]; + A[4] = A[3]; + A[3] = A[2]; + A[2] = A[1]; + A[1] = A[0]; + A[0] = temp; + } + +#undef F + +#define F(x, y, z) ((x & y) | (z & (x | y))) + + for (i = 40; i < 60; i++) + { + P(A[0], A[1], A[2], A[3], A[4], R(i), K[i / 20]); + temp = A[4]; + A[4] = A[3]; + A[3] = A[2]; + A[2] = A[1]; + A[1] = A[0]; + A[0] = temp; + } + +#undef F + +#define F(x, y, z) (x ^ y ^ z) + for (i = 60; i < 80; i++) + { + P(A[0], A[1], A[2], A[3], A[4], R(i), K[i / 20]); + temp = A[4]; + A[4] = A[3]; + A[3] = A[2]; + A[2] = A[1]; + A[1] = A[0]; + A[0] = temp; + } +#undef F + for (i = 0; i < 5; i++) + { + ctx->state[i] += A[i]; + } +} + +void Quos_sha1init(SHA1_ctx_t *ctx) +{ + HAL_MEMSET(ctx, 0, sizeof(SHA1_ctx_t)); + ctx->total[0] = 0; + ctx->total[1] = 0; + ctx->state[0] = 0x67452301; + ctx->state[1] = 0xEFCDAB89; + ctx->state[2] = 0x98BADCFE; + ctx->state[3] = 0x10325476; + ctx->state[4] = 0xC3D2E1F0; +} +void Quos_sha1update(SHA1_ctx_t *ctx, const quint8_t *input, quint32_t ilen) +{ + quint32_t fill; + quint32_t left; + + if (ilen == 0) + { + return; + } + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (quint32_t)ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if (ctx->total[0] < (quint32_t)ilen) + { + ctx->total[1]++; + } + + if (left && ilen >= fill) + { + HAL_MEMCPY((void *)(ctx->buffer + left), input, fill); + quos_sha1process(ctx, ctx->buffer); + input += fill; + ilen -= fill; + left = 0; + } + + while (ilen >= 64) + { + quos_sha1process(ctx, input); + input += 64; + ilen -= 64; + } + + if (ilen > 0) + { + HAL_MEMCPY((void *)(ctx->buffer + left), input, ilen); + } +} +void Quos_sha1key(SHA1_ctx_t *ctx, quint8_t *key, quint8_t key_len) +{ + + quint8_t k_ipad[QUOS_SHA1_KEY_IOPAD_SIZE]; + key_len = key_len > QUOS_SHA1_KEY_IOPAD_SIZE ? QUOS_SHA1_KEY_IOPAD_SIZE : key_len; + HAL_MEMSET(ctx->k_opad, 0, sizeof(ctx->k_opad)); + HAL_MEMCPY(ctx->k_opad, key, key_len); + HAL_MEMSET(k_ipad, 0, sizeof(k_ipad)); + HAL_MEMCPY(k_ipad, key, key_len); + quint8_t i = 0; + for (i = 0; i < QUOS_SHA1_KEY_IOPAD_SIZE; i++) + { + k_ipad[i] ^= 0x36; + ctx->k_opad[i] ^= 0x5c; + } + ctx->isHmac = TRUE; + Quos_sha1update(ctx, k_ipad, QUOS_SHA1_KEY_IOPAD_SIZE); +} +void Quos_sha1finish(SHA1_ctx_t *ctx, quint8_t output[QUOS_SHA1_DIGEST_LENGTH]) +{ + quint32_t last, padn; + quint32_t high, low; + quint8_t msglen[8]; + + high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); + low = (ctx->total[0] << 3); + + _U32_ARRAY0123(high, msglen); + _U32_ARRAY0123(low, msglen + 4); + + last = ctx->total[0] & 0x3F; + padn = (last < 56) ? (56 - last) : (120 - last); + + Quos_sha1update(ctx, sha1_padding, padn); + Quos_sha1update(ctx, msglen, 8); + + _U32_ARRAY0123(ctx->state[0], output); + _U32_ARRAY0123(ctx->state[1], output + 4); + _U32_ARRAY0123(ctx->state[2], output + 8); + _U32_ARRAY0123(ctx->state[3], output + 12); + _U32_ARRAY0123(ctx->state[4], output + 16); + if (ctx->isHmac) + { + SHA1_ctx_t context; + Quos_sha1init(&context); /* init context for 2nd pass */ + Quos_sha1update(&context, ctx->k_opad, QUOS_SHA1_KEY_IOPAD_SIZE); /* start with outer pad */ + Quos_sha1update(&context, output, QUOS_SHA1_DIGEST_LENGTH); /* then results of 1st hash */ + Quos_sha1finish(&context, output); /* finish up 2nd pass */ + } +} + +#endif \ No newline at end of file diff --git a/kernel/quos_sha256.c b/kernel/quos_sha256.c new file mode 100644 index 0000000..d9f9414 --- /dev/null +++ b/kernel/quos_sha256.c @@ -0,0 +1,258 @@ +#include "quos_sha256.h" +#include "Quos_kernel.h" +#if (SDK_ENABLE_SHA256 == 1) + +static const quint32_t K[] = { + 0x428A2F98, + 0x71374491, + 0xB5C0FBCF, + 0xE9B5DBA5, + 0x3956C25B, + 0x59F111F1, + 0x923F82A4, + 0xAB1C5ED5, + 0xD807AA98, + 0x12835B01, + 0x243185BE, + 0x550C7DC3, + 0x72BE5D74, + 0x80DEB1FE, + 0x9BDC06A7, + 0xC19BF174, + 0xE49B69C1, + 0xEFBE4786, + 0x0FC19DC6, + 0x240CA1CC, + 0x2DE92C6F, + 0x4A7484AA, + 0x5CB0A9DC, + 0x76F988DA, + 0x983E5152, + 0xA831C66D, + 0xB00327C8, + 0xBF597FC7, + 0xC6E00BF3, + 0xD5A79147, + 0x06CA6351, + 0x14292967, + 0x27B70A85, + 0x2E1B2138, + 0x4D2C6DFC, + 0x53380D13, + 0x650A7354, + 0x766A0ABB, + 0x81C2C92E, + 0x92722C85, + 0xA2BFE8A1, + 0xA81A664B, + 0xC24B8B70, + 0xC76C51A3, + 0xD192E819, + 0xD6990624, + 0xF40E3585, + 0x106AA070, + 0x19A4C116, + 0x1E376C08, + 0x2748774C, + 0x34B0BCB5, + 0x391C0CB3, + 0x4ED8AA4A, + 0x5B9CCA4F, + 0x682E6FF3, + 0x748F82EE, + 0x78A5636F, + 0x84C87814, + 0x8CC70208, + 0x90BEFFFA, + 0xA4506CEB, + 0xBEF9A3F7, + 0xC67178F2, +}; + +static const quint8_t sha256_padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +#define SHR(x, n) ((x & 0xFFFFFFFF) >> n) +#define ROTR(x, n) (SHR(x, n) | (x << (32 - n))) + +#define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +#define S2(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S3(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) + +#define F0(x, y, z) ((x & y) | (z & (x | y))) +#define F1(x, y, z) (z ^ (x & (y ^ z))) + +#define R(t) W[t] = S1(W[t - 2]) + W[t - 7] + S0(W[t - 15]) + W[t - 16] + +#define P(a, b, c, d, e, f, g, h, x, K) \ + do \ + { \ + quint32_t temp = h + S3(e) + F1(e, f, g) + K + x; \ + d += temp; \ + h = temp + S2(a) + F0(a, b, c); \ + } while (0) + +static void quos_sha256process(SHA256_ctx_t *ctx, const quint8_t data[64]) +{ + quint32_t temp, W[64]; + quint32_t A[8]; + quint32_t i; + + for (i = 0; i < 8; i++) + { + A[i] = ctx->state[i]; + } + + for (i = 0; i < 64; i++) + { + if (i < 16) + { + W[i] = _ARRAY0123_U32(data + 4 * i); + } + else + { + R(i); + } + + P(A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[i], K[i]); + + temp = A[7]; + A[7] = A[6]; + A[6] = A[5]; + A[5] = A[4]; + A[4] = A[3]; + A[3] = A[2]; + A[2] = A[1]; + A[1] = A[0]; + A[0] = temp; + } + for (i = 0; i < 8; i++) + { + ctx->state[i] += A[i]; + } +} + +void Quos_sha256init(SHA256_ctx_t *ctx) +{ + HAL_MEMSET(ctx, 0, sizeof(SHA256_ctx_t)); + ctx->total[0] = 0; + ctx->total[1] = 0; + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; + ctx->is224 = FALSE; +} + +void Quos_sha256update(SHA256_ctx_t *ctx, const quint8_t *input, quint32_t ilen) +{ + quint32_t fill; + quint32_t left; + + if (ilen == 0) + { + return; + } + + left = ctx->total[0] & 0x3F; + fill = 64 - left; + + ctx->total[0] += (quint32_t)ilen; + ctx->total[0] &= 0xFFFFFFFF; + + if (ctx->total[0] < (quint32_t)ilen) + { + ctx->total[1]++; + } + + if (left && ilen >= fill) + { + HAL_MEMCPY((void *)(ctx->buffer + left), input, fill); + quos_sha256process(ctx, ctx->buffer); + input += fill; + ilen -= fill; + left = 0; + } + + while (ilen >= 64) + { + quos_sha256process(ctx, input); + input += 64; + ilen -= 64; + } + + if (ilen > 0) + { + HAL_MEMCPY((void *)(ctx->buffer + left), input, ilen); + } +} + +void Quos_sha256key(SHA256_ctx_t *ctx, quint8_t *key, quint8_t key_len) +{ + quint8_t k_ipad[QUOS_SHA256_KEY_IOPAD_SIZE]; + key_len = key_len > QUOS_SHA256_KEY_IOPAD_SIZE ? QUOS_SHA256_KEY_IOPAD_SIZE : key_len; + HAL_MEMSET(ctx->k_opad, 0, sizeof(ctx->k_opad)); + HAL_MEMCPY(ctx->k_opad, key, key_len); + HAL_MEMSET(k_ipad, 0, sizeof(k_ipad)); + HAL_MEMCPY(k_ipad, key, key_len); + quint8_t i = 0; + for (i = 0; i < QUOS_SHA256_KEY_IOPAD_SIZE; i++) + { + k_ipad[i] ^= 0x36; + ctx->k_opad[i] ^= 0x5c; + } + ctx->isHmac = TRUE; + Quos_sha256update(ctx, k_ipad, QUOS_SHA256_KEY_IOPAD_SIZE); +} + +void Quos_sha256finish(SHA256_ctx_t *ctx, quint8_t output[QUOS_SHA256_DIGEST_LENGTH]) +{ + quint32_t last, padn; + quint32_t high, low; + quint8_t msglen[8]; + + high = (ctx->total[0] >> 29) | (ctx->total[1] << 3); + low = (ctx->total[0] << 3); + + _U32_ARRAY0123(high, msglen); + _U32_ARRAY0123(low, msglen + 4); + + last = ctx->total[0] & 0x3F; + padn = (last < 56) ? (56 - last) : (120 - last); + + Quos_sha256update(ctx, sha256_padding, padn); + Quos_sha256update(ctx, msglen, 8); + + _U32_ARRAY0123(ctx->state[0], output); + _U32_ARRAY0123(ctx->state[1], output + 4); + _U32_ARRAY0123(ctx->state[2], output + 8); + _U32_ARRAY0123(ctx->state[3], output + 12); + _U32_ARRAY0123(ctx->state[4], output + 16); + _U32_ARRAY0123(ctx->state[5], output + 20); + _U32_ARRAY0123(ctx->state[6], output + 24); + + if (FALSE == ctx->is224) + { + _U32_ARRAY0123(ctx->state[7], output + 28); + } + + if (ctx->isHmac) + { + SHA256_ctx_t context; + Quos_sha256init(&context); /* init context for 2nd pass */ + Quos_sha256update(&context, ctx->k_opad, QUOS_SHA256_KEY_IOPAD_SIZE); /* start with outer pad */ + Quos_sha256update(&context, output, QUOS_SHA256_DIGEST_LENGTH); /* then results of 1st hash */ + Quos_sha256finish(&context, output); /* finish up 2nd pass */ + } +} + +#endif \ No newline at end of file diff --git a/kernel/quos_signal.c b/kernel/quos_signal.c new file mode 100644 index 0000000..a23ce00 --- /dev/null +++ b/kernel/quos_signal.c @@ -0,0 +1,83 @@ +/************************************************************************* +** 源码未经检录,使用需谨慎 +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 信号量管理 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_signal.h" +#include "Quos_kernel.h" + +#if (SDK_ENABLE_SIGNAL == 1) +typedef struct +{ + TWLLHead_T head; + SignalCB_f eventCB; + quint32_t argLen; + quint8_t arg[1]; +} SignalInfoNode_t; + +static TWLLHead_T *SignalList = NULL; +HAL_LOCK_DEF(static,lockId) + +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_signalInit(void) +{ + HAL_LOCK_INIT(lockId); +} +/************************************************************************** +** 功能 @brief : 发起信号 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_signalSet(void *arg, quint32_t len, SignalCB_f eventCB) +{ + if (NULL == eventCB) + { + return FALSE; + } + SignalInfoNode_t *node = HAL_MALLOC(__GET_POS_ELEMENT(SignalInfoNode_t, arg) + len); + if (NULL == node) + { + Quos_logPrintf(LSDK_SIG, LL_ERR, "signal node malloc fail"); + return FALSE; + } + node->eventCB = eventCB; + HAL_MEMCPY(node->arg, arg, len); + node->argLen = len; + HAL_LOCK(lockId); + Quos_twllHeadAdd(&SignalList, &node->head); + HAL_UNLOCK(lockId); + Quos_kernelResume(); + return TRUE; +} +/************************************************************************** +** 功能 @brief : 处理信号 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_RAM Quos_signalTask(void) +{ + if (NULL == SignalList) + { + Quos_logPrintf(LSDK_SIG, LL_DBG,"is empty"); + return FALSE; + } + HAL_LOCK(lockId); + SignalInfoNode_t *node = __GET_STRUCT_BY_ELEMENT(SignalList, SignalInfoNode_t, head); + Quos_twllHeadDelete(&SignalList, SignalList); + HAL_UNLOCK(lockId); + Quos_logHexDump(LSDK_SIG, LL_DUMP, "signal get", node->arg, node->argLen); + Quos_logPrintf(LSDK_SIG, LL_DBG, "exec cb[%p]",node->eventCB); + node->eventCB(node->arg, node->argLen); + Quos_logPrintf(LSDK_SIG, LL_DBG, "exec cb[%p] finish",node->eventCB); + HAL_FREE(node); + return TRUE; +} +#endif \ No newline at end of file diff --git a/kernel/quos_socket.c b/kernel/quos_socket.c new file mode 100644 index 0000000..ae7fa99 --- /dev/null +++ b/kernel/quos_socket.c @@ -0,0 +1,792 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 通信socket管理 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_socket.h" +#include "Quos_kernel.h" +static TWLLHead_T *chlInfoListHead = NULL; +HAL_LOCK_DEF(static, lockId) +#ifndef SOCKET_SENDDATA_BYTESIZE_MAX +#define SOCKET_SENDDATA_BYTESIZE_MAX (50 * 1024) +#endif +static void *quos_socketGetChlFd(pointer_t sockFd, quint8_t type); +static qbool quos_socketCheckChlFd(const void *chlFd); +static quint32_t quos_socketSendDataByteSize(void *chlFd); +static qbool quos_socketChannelDel(void *chlFd); +/************************************************************************** +** 功能 @brief : socket通道初始化 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_socketInit(void) +{ + HAL_LOCK_INIT(lockId); +} + +/************************************************************************** +** 功能 @brief : socket connect成功处理 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_socketIOConnResult(pointer_t sockFd, quint8_t type, qbool result) +{ + HAL_LOCK(lockId); + Quos_socketChlInfoNode_t *node = (Quos_socketChlInfoNode_t *)quos_socketGetChlFd(sockFd, type); + if (node && node->valid) + { + Quos_swTimerDelete(node->conn.timer); + if (node->conn.notify) + { + HAL_UNLOCK(lockId); + node->conn.notify((void *)node, result); + HAL_LOCK(lockId); + } + if (FALSE == result) + { + quos_socketChannelDel((void *)node); + } + Quos_kernelResume(); + } + HAL_UNLOCK(lockId); +} +/************************************************************************** +** 功能 @brief : socket connect超时处理,超时删除掉channel +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_socketConnTimeout(void *swTimer) +{ + HAL_LOCK(lockId); + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)Quos_swTimerParmGet(swTimer); + Quos_logPrintf(LSDK_SOCK, LL_ERR, "chlNode[%p] conn timeout", chlNode); + if (FALSE == quos_socketCheckChlFd(chlNode)) + { + HAL_UNLOCK(lockId); + return; + } + if (chlNode->valid) + { + if (chlNode->conn.notify) + { + HAL_UNLOCK(lockId); + chlNode->conn.notify((void *)chlNode, FALSE); + HAL_LOCK(lockId); + } + quos_socketChannelDel((void *)chlNode); + Quos_kernelResume(); + Quos_logPrintf(LSDK_SOCK, LL_INFO, "del sockChlInfo"); + } + HAL_UNLOCK(lockId); +} +/************************************************************************** +** 功能 @brief : socket通道增加 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Quos_socketChannelAdd(void **chlFdPoint, Quos_socketChlInfoNode_t chlInfo) +{ + Quos_logPrintf(LSDK_SOCK, LL_INFO, "sockFd[" PRINTF_FD "] type[%u] sendCnt[%u] sendTimeout[%u] io.send[%p] recvDataFunc[%p] unformFunc[%p] param:%p", + chlInfo.sockFd, chlInfo.type, chlInfo.send.txCnt, chlInfo.send.timeout, chlInfo.io.send, chlInfo.recvDataFunc, chlInfo.unformFunc, chlInfo.param); + + if (SOCKET_FD_INVALID == chlInfo.sockFd || 0 == chlInfo.send.txCnt || 0 == chlInfo.send.timeout) + { + Quos_logPrintf(LSDK_SOCK, LL_ERR, "arg is invalid sockFd:" PRINTF_FD " sendCnt:%u sendTimeout:%u cb:%p timeout:%u", + chlInfo.sockFd, chlInfo.send.txCnt, chlInfo.send.timeout, chlInfo.conn.notify, chlInfo.conn.timeout); + return NULL; + } + HAL_LOCK(lockId); + Quos_socketChlInfoNode_t *node = (Quos_socketChlInfoNode_t *)quos_socketGetChlFd(chlInfo.sockFd, chlInfo.type); + if (node) + { + Quos_logPrintf(LSDK_SOCK, LL_WARN, "find invalid fd in socket channel list"); + quos_socketChannelDel((void *)node); + Quos_kernelResume(); + } + HAL_UNLOCK(lockId); + + Quos_socketChlInfoNode_t *newChlInfo = HAL_MALLOC(sizeof(Quos_socketChlInfoNode_t)); + if (NULL == newChlInfo) + { + Quos_logPrintf(LSDK_SOCK, LL_ERR, "MALLOC newChlInfo fail"); + return NULL; + } + HAL_MEMCPY(newChlInfo, &chlInfo, sizeof(Quos_socketChlInfoNode_t)); + if (newChlInfo->conn.timeout) + { + if (FALSE == Quos_swTimerStart(&newChlInfo->conn.timer, "socket connect", newChlInfo->conn.timeout, 1, quos_socketConnTimeout, (void *)newChlInfo)) + { + Quos_logPrintf(LSDK_SOCK, LL_ERR, "conntime start fail"); + HAL_FREE(newChlInfo); + return NULL; + } + } + if (chlFdPoint) + { + *chlFdPoint = newChlInfo; + } + newChlInfo->self = chlFdPoint; + newChlInfo->valid = TRUE; + HAL_LOCK(lockId); + Quos_twllHeadAdd(&chlInfoListHead, &newChlInfo->head); + Quos_logPrintf(LSDK_SOCK, LL_INFO, "ok, node count:%u", Quos_twllHeadGetNodeCount(chlInfoListHead)); + HAL_UNLOCK(lockId); + Quos_kernelResume(); + return (void *)newChlInfo; +} +/************************************************************************** +** 功能 @brief : socket通道删除 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_socketChannelDel(void *chlFd) +{ + Quos_logPrintf(LSDK_SOCK, LL_INFO, "chlFd[%p]", chlFd); + if (quos_socketCheckChlFd(chlFd)) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + if (chlNode->valid) + { + chlNode->valid = FALSE; + if (chlNode->self) + { + *chlNode->self = NULL; + chlNode->self = NULL; + } + Quos_swTimerDelete(chlNode->conn.timer); + Quos_kernelResume(); + return TRUE; + } + Quos_logPrintf(LSDK_SOCK, LL_DBG, "del chl and send node ok"); + } + return FALSE; +} +qbool FUNCTION_ATTR_ROM Quos_socketChannelDel(void *chlFd) +{ + HAL_LOCK(lockId); + qbool ret = quos_socketChannelDel(chlFd); + HAL_UNLOCK(lockId); + return ret; +} +/************************************************************************** +** 功能 @brief : socket通信 TASK +** 输入 @param : +** 输出 @retval: FALSE:任何通道无数据发送 +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_socketTask(void) +{ + quint32_t idleTime = SWT_SUSPEND; + HAL_LOCK(lockId); + TWLLHead_T *chlTemp, *chlNext; + TWLIST_FOR_DATA(chlInfoListHead, chlTemp, chlNext) + { + Quos_socketChlInfoNode_t *chlNode = __GET_STRUCT_BY_ELEMENT(chlTemp, Quos_socketChlInfoNode_t, head); + qint32_t interval = Quos_sysTickdiff(Quos_sysTickGet(), chlNode->send.beginTime, TRUE); + if (interval < 0) + { + interval = 0; + chlNode->send.beginTime = Quos_sysTickGet(); + } + if (FALSE == chlNode->valid) /*socket通道已被删除,则在此FREE内存*/ + { + Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlNode[%p] memory free", chlNode); + Quos_twllHeadDelete(&chlInfoListHead, &chlNode->head); + if (chlNode->unformTemp.buf) + { + HAL_FREE(chlNode->unformTemp.buf); + } + TWLLHead_T *sendTemp, *sendNext; + TWLIST_FOR_DATA(chlNode->send.disorderList, sendTemp, sendNext) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(sendTemp, Quos_socketSendDataNode_t, head); + Quos_twllHeadDelete(&chlNode->send.disorderList, &sendNode->head); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + } + TWLIST_FOR_DATA(chlNode->send.orderList, sendTemp, sendNext) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(sendTemp, Quos_socketSendDataNode_t, head); + Quos_twllHeadDelete(&chlNode->send.orderList, &sendNode->head); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + } + if (chlNode->io.close) + { + (chlNode->io.close)(chlNode->sockFd, chlNode->type); + } + if (chlNode->paramFree && chlNode->param) + { + chlNode->paramFree(chlNode->param); + } + HAL_FREE(chlNode); + } + else if (chlNode->conn.timer) /* socket connecting中 */ + { + /* do no */ + } + else if ((quint32_t)interval < chlNode->send.sendInterVal) /*处在限速中 */ + { + Quos_logPrintf(LSDK_SOCK, LL_DBG, "send rate limited:%d/%u", interval, chlNode->send.sendInterVal); + if (idleTime > chlNode->send.sendInterVal - interval) + { + idleTime = chlNode->send.sendInterVal - interval; + } + } + else if (0 == chlNode->send.waitIoSendAck && (chlNode->send.disorderList || FALSE == chlNode->send.waitPkgAck)) + { + qbool iosendRet = TRUE; + qbool isSending = FALSE; + if (chlNode->send.disorderList) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(chlNode->send.disorderList, Quos_socketSendDataNode_t, head); + iosendRet = (chlNode->io.send)(chlNode->sockFd, chlNode->type, sendNode->peer, sendNode->Buf, sendNode->bufLen, &isSending); + Quos_logPrintf(LSDK_SOCK, LL_DBG, "disorderList send,sendNode:%p iosendRet:%s isSending:%s", sendNode, _BOOL2STR(iosendRet), _BOOL2STR(isSending)); + chlNode->send.beginTime = Quos_sysTickGet(); + if (iosendRet) + { + if (FALSE == isSending) + { + Quos_twllHeadDelete(&chlNode->send.disorderList, chlNode->send.disorderList); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + idleTime = 0; + } + else + { + chlNode->send.waitIoSendAck = 1; + if (idleTime > sendNode->sendTimeout) + { + idleTime = sendNode->sendTimeout; + } + } + } + else + { + idleTime = 0; + } + } + else if (chlNode->send.orderList && FALSE == chlNode->send.waitPkgAck) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(chlNode->send.orderList, Quos_socketSendDataNode_t, head); + iosendRet = (chlNode->io.send)(chlNode->sockFd, chlNode->type, sendNode->peer, sendNode->Buf, sendNode->bufLen, &isSending); + Quos_logPrintf(LSDK_SOCK, LL_DBG, "orderList send,sendNode:%p iosendRet:%s isSending:%s", sendNode, _BOOL2STR(iosendRet), _BOOL2STR(isSending)); + chlNode->send.beginTime = Quos_sysTickGet(); + if (TRUE == iosendRet) + { + Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlFd[%p] sendNode[%p] io send ok,isSending[%s] sendCnt:%u", chlNode, sendNode, _BOOL2STR(isSending), sendNode->sendCnt); + if (FALSE == isSending && sendNode->sendCB) + { + HAL_UNLOCK(lockId); + qbool sendCbRet = sendNode->sendCB((void *)chlNode, sendNode, TRUE); + HAL_LOCK(lockId); + if (sendCbRet) + { + if (sendNode == __GET_STRUCT_BY_ELEMENT(chlNode->send.orderList, Quos_socketSendDataNode_t, head)) + { + Quos_twllHeadDelete(&chlNode->send.orderList, chlNode->send.orderList); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + } + continue; + } + } + + if (FALSE == isSending && 0 == sendNode->sendCnt) + { + Quos_twllHeadDelete(&chlNode->send.orderList, chlNode->send.orderList); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + idleTime = 0; + } + else + { + if (TRUE == isSending) + { + chlNode->send.waitIoSendAck = 2; + Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlFd[%p] sendNode[%p] need wait sendcb", chlNode, sendNode); + } + if (0 != sendNode->sendCnt) + { + chlNode->send.waitPkgAck = TRUE; + Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlFd[%p] sendNode[%p] need wait pkg ack", chlNode, sendNode); + } + if (idleTime > sendNode->sendTimeout) + { + idleTime = sendNode->sendTimeout; + } + } + } + else + { + idleTime = 0; + } + } + if (FALSE == iosendRet) + { + Quos_logPrintf(LSDK_SOCK, LL_ERR, "chlFd[%p] io send fail", chlNode); + HAL_UNLOCK(lockId); + chlNode->recvDataFunc((void *)chlNode, NULL, 0, NULL); + HAL_LOCK(lockId); + quos_socketChannelDel((void *)chlNode); + } + } + else if (1 == chlNode->send.waitIoSendAck) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(chlNode->send.disorderList, Quos_socketSendDataNode_t, head); + if (interval > (qint32_t)sendNode->sendTimeout) + { + Quos_twllHeadDelete(&chlNode->send.disorderList, chlNode->send.disorderList); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + chlNode->send.waitIoSendAck = 0; + idleTime = 0; + } + } + else if (2 == chlNode->send.waitIoSendAck || TRUE == chlNode->send.waitPkgAck) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(chlNode->send.orderList, Quos_socketSendDataNode_t, head); + qint32_t recvInter = Quos_sysTickdiff(Quos_sysTickGet(), chlNode->send.recvTime, TRUE); + if (interval > (qint32_t)sendNode->sendTimeout && recvInter >= (qint32_t)chlNode->send.timeout / 2) + { + chlNode->send.waitIoSendAck = 0; + chlNode->send.waitPkgAck = FALSE; + if (sendNode->sendCnt > 0) + { + sendNode->sendCnt--; + } + if (0 == sendNode->sendCnt) + { + if (sendNode->recvCB) + { + HAL_UNLOCK(lockId); + sendNode->recvCB((void *)chlNode, sendNode, NULL); + HAL_LOCK(lockId); + } + if (sendNode == __GET_STRUCT_BY_ELEMENT(chlNode->send.orderList, Quos_socketSendDataNode_t, head)) + { + Quos_twllHeadDelete(&chlNode->send.orderList, chlNode->send.orderList); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + } + } + idleTime = 0; + } + else if ((quint32_t)interval < sendNode->sendTimeout) + { + idleTime = sendNode->sendTimeout - interval; + } + else + { + idleTime = chlNode->send.timeout / 2 - recvInter; + } + } + } + HAL_UNLOCK(lockId); + return idleTime; +} +/************************************************************************** +** 功能 @brief : socket添加发送数据到有序发送链表 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_socketTx(const void *chlFd, void *peer, quint8_t sendCnt, quint32_t sendTimeout, socketsendNodeCb_f sendCB, socketRecvNodeCb_f recvCB, + quint32_t pkgId, quint8_t *buf, quint16_t bufLen, const void *param) +{ + qbool ret = FALSE; + Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlFd[%p] sendCnt[%u] sendTimeout[%u] sendCB[%p] recvCB[%p] pkgId[%u] bufLen[%u]", chlFd, sendCnt, sendTimeout, sendCB, recvCB, pkgId, bufLen); + Quos_logHexDump(LSDK_SOCK, LL_DUMP, "send", buf, bufLen); + HAL_LOCK(lockId); + if (quos_socketSendDataByteSize(NULL) < SOCKET_SENDDATA_BYTESIZE_MAX && quos_socketCheckChlFd(chlFd)) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + Quos_socketSendDataNode_t *sendNode; + if (chlNode->valid && chlNode->io.send && (sendNode = HAL_MALLOC(sizeof(Quos_socketSendDataNode_t))) != NULL) + { + HAL_MEMSET(sendNode, 0, sizeof(Quos_socketSendDataNode_t)); + Quos_twllHeadAdd(&chlNode->send.orderList, &sendNode->head); + Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlFd[%p] add sendNode[%p]", chlNode, sendNode); + sendNode->param = (void *)param; + + sendNode->sendCnt = (recvCB) ? ((sendCnt == 0) ? chlNode->send.txCnt : sendCnt) : 0; + sendNode->sendTimeout = sendTimeout ? sendTimeout : chlNode->send.timeout; + sendNode->sendCB = sendCB; + sendNode->recvCB = recvCB; + sendNode->pkgId = pkgId; + sendNode->peer = peer; + sendNode->Buf = buf; + sendNode->bufLen = bufLen; + Quos_kernelResume(); + ret = TRUE; + } + } + if (FALSE == ret && buf) + { + HAL_FREE(buf); + } + HAL_UNLOCK(lockId); + return ret; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_socketTxDisorder(const void *chlFd, void *peer, quint8_t *buf, quint16_t bufLen) +{ + qbool ret = FALSE; + HAL_LOCK(lockId); + Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlFd[%p] bufLen[%u]", chlFd, bufLen); + Quos_logHexDump(LSDK_SOCK, LL_DUMP, "send", buf, bufLen); + if (quos_socketSendDataByteSize(NULL) < SOCKET_SENDDATA_BYTESIZE_MAX && quos_socketCheckChlFd(chlFd)) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + Quos_socketSendDataNode_t *sendNode; + if (chlNode->valid && chlNode->io.send && (sendNode = HAL_MALLOC(sizeof(Quos_socketSendDataNode_t))) != NULL) + { + HAL_MEMSET(sendNode, 0, sizeof(Quos_socketSendDataNode_t)); + Quos_twllHeadAdd(&chlNode->send.disorderList, &sendNode->head); + Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlFd[%p] add sendNode[%p]", chlNode, sendNode); + sendNode->sendTimeout = chlNode->send.timeout; + sendNode->peer = peer; + sendNode->Buf = buf; + sendNode->bufLen = bufLen; + Quos_kernelResume(); + ret = TRUE; + } + } + if (FALSE == ret && buf) + { + HAL_FREE(buf); + } + HAL_UNLOCK(lockId); + return ret; +} +/************************************************************************** +** 功能 @brief : socket发送数据出去完成回调 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_socketIOTxCb(pointer_t sockFd, quint8_t type) +{ + HAL_LOCK(lockId); + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)quos_socketGetChlFd(sockFd, type); + if (chlNode && chlNode->valid) + { + if (1 == chlNode->send.waitIoSendAck && chlNode->send.disorderList) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(chlNode->send.disorderList, Quos_socketSendDataNode_t, head); + Quos_twllHeadDelete(&chlNode->send.disorderList, chlNode->send.disorderList); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + chlNode->send.waitIoSendAck = 0; + } + else if (2 == chlNode->send.waitIoSendAck && chlNode->send.orderList) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(chlNode->send.orderList, Quos_socketSendDataNode_t, head); + qbool sendCbRet = FALSE; + if (sendNode->sendCB) + { + HAL_UNLOCK(lockId); + sendCbRet = sendNode->sendCB((void *)chlNode, sendNode, TRUE); + HAL_LOCK(lockId); + } + if (quos_socketCheckChlFd(chlNode)) + { + if ((sendCbRet || FALSE == chlNode->send.waitPkgAck) && sendNode == __GET_STRUCT_BY_ELEMENT(chlNode->send.orderList, Quos_socketSendDataNode_t, head)) + { + Quos_twllHeadDelete(&chlNode->send.orderList, chlNode->send.orderList); + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + } + chlNode->send.waitIoSendAck = 0; + } + } + else + { + chlNode->send.waitIoSendAck = 0; + } + Quos_kernelResume(); + } + HAL_UNLOCK(lockId); +} +/************************************************************************** +** 功能 @brief : socket通信数据收到发送应答 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_socketTxAck(void *chlFd, const void *peer, quint32_t pkgId, const void *recvData) +{ + Quos_logPrintf(LSDK_SOCK, LL_DBG, "recv chlFd[%p] pkgId[%u]", chlFd, pkgId); + qbool ret = FALSE; + HAL_LOCK(lockId); + if (quos_socketCheckChlFd(chlFd)) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + if (chlNode->valid && chlNode->send.orderList && TRUE == chlNode->send.waitPkgAck) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(chlNode->send.orderList, Quos_socketSendDataNode_t, head); + if (sendNode->pkgId == pkgId && sendNode->peer == peer) + { + Quos_logPrintf(LSDK_SOCK, LL_DBG, "pkgId is match send's"); + chlNode->send.waitIoSendAck = 0; + chlNode->send.waitPkgAck = FALSE; + Quos_twllHeadDelete(&chlNode->send.orderList, chlNode->send.orderList); + HAL_UNLOCK(lockId); + sendNode->recvCB((void *)chlNode, sendNode, recvData); + HAL_LOCK(lockId); + if (quos_socketCheckChlFd(chlNode)) + { + HAL_FREE(sendNode->Buf); + HAL_FREE(sendNode); + } + Quos_kernelResume(); + ret = TRUE; + } + } + } + HAL_UNLOCK(lockId); + return ret; +} +/************************************************************************** +** 功能 @brief : socket添加接收数据到接收链表 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_socketIORx(pointer_t sockFd, quint8_t type, const void *peer, quint32_t peerSize, quint8_t *buf, quint32_t bufLen) +{ + HAL_LOCK(lockId); + Quos_logPrintf(LSDK_SOCK, LL_DBG, "sockFd[" PRINTF_FD "] bufLen[%u]", sockFd, bufLen); + Quos_logHexDump(LSDK_SOCK, LL_DUMP, "recv", buf, bufLen); + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)quos_socketGetChlFd(sockFd, type); + if (chlNode && chlNode->valid) + { + chlNode->send.recvTime = Quos_sysTickGet(); + if (chlNode->recvDataFunc) + { + Quos_socketRecvDataNode_t newNode; + HAL_MEMSET(&newNode, 0, sizeof(Quos_socketRecvDataNode_t)); + if (0 == bufLen) + { + chlNode->io.close = NULL; /* no close repeat */ + HAL_UNLOCK(lockId); + chlNode->recvDataFunc((void *)chlNode, peer, peerSize, NULL); + HAL_LOCK(lockId); + quos_socketChannelDel((void *)chlNode); + Quos_kernelResume(); + } + else if (chlNode->unformFunc) + { + Quos_logPrintf(LSDK_SOCK, LL_DBG, "need to unform before"); + quint32_t dealLen = 0; + while (dealLen < bufLen && quos_socketCheckChlFd(chlNode)) + { + quint32_t offset = 0; + if (FALSE == chlNode->unformFunc(buf + dealLen, bufLen - dealLen, &offset, &chlNode->unformTemp)) + { + Quos_logPrintf(LSDK_SOCK, LL_DBG, "unform fail"); + } + else + { + Quos_logHexDump(LSDK_SOCK, LL_DUMP, "unform ok", chlNode->unformTemp.buf, chlNode->unformTemp.offset); + newNode.bufLen = chlNode->unformTemp.offset; + newNode.Buf = chlNode->unformTemp.buf; + chlNode->unformTemp.offset = 0; + HAL_UNLOCK(lockId); + chlNode->recvDataFunc((void *)chlNode, peer, peerSize, &newNode); + HAL_LOCK(lockId); + } + dealLen += offset; + } + } + else + { + Quos_logPrintf(LSDK_SOCK, LL_DBG, "recvDataFunc direct"); + newNode.bufLen = bufLen; + newNode.Buf = buf; + HAL_UNLOCK(lockId); + chlNode->recvDataFunc((void *)chlNode, peer, peerSize, &newNode); + HAL_LOCK(lockId); + } + } + } + HAL_UNLOCK(lockId); +} + +/************************************************************************** +** 功能 @brief : 获取socket类型 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_socketGetSockFdType(void *chlFd, pointer_t *sockFd, quint8_t *type) +{ + qbool ret = FALSE; + HAL_LOCK(lockId); + if (quos_socketCheckChlFd(chlFd)) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + if (chlNode->valid) + { + if (sockFd) + { + *sockFd = chlNode->sockFd; + } + if (type) + { + *type = chlNode->type; + } + ret = TRUE; + } + } + HAL_UNLOCK(lockId); + return ret; +} +/************************************************************************** +** 功能 @brief : 获取指定节点 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM *quos_socketGetChlFd(pointer_t sockFd, quint8_t type) +{ + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA(chlInfoListHead, temp, next) + { + Quos_socketChlInfoNode_t *chlNode = __GET_STRUCT_BY_ELEMENT(temp, Quos_socketChlInfoNode_t, head); + if (chlNode->sockFd == sockFd && chlNode->type == type) + { + Quos_logPrintf(LSDK_SOCK, LL_DBG, "find chlFd[%p] ok", chlNode); + return chlNode; + } + } + return NULL; +} +void FUNCTION_ATTR_ROM *Quos_socketGetChlFd(pointer_t sockFd, quint8_t type) +{ + HAL_LOCK(lockId); + void *node = quos_socketGetChlFd(sockFd, type); + HAL_UNLOCK(lockId); + return node; +} +/************************************************************************** +** 功能 @brief : 获取指定类型和参数指针匹配的chlFd列表 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t Quos_socketGetChlFdList(quint8_t type, const void *param, void *chlFd[], quint32_t maxSize) +{ + quint32_t count = 0; + HAL_LOCK(lockId); + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA(chlInfoListHead, temp, next) + { + Quos_socketChlInfoNode_t *chlNode = __GET_STRUCT_BY_ELEMENT(temp, Quos_socketChlInfoNode_t, head); + if (chlNode->type == type && chlNode->param == param) + { + chlFd[count++] = chlNode; + if(count == maxSize) + { + break; + } + } + } + HAL_UNLOCK(lockId); + return count; +} +/************************************************************************** +** 功能 @brief : 判断chlFd是否存在 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static qbool FUNCTION_ATTR_ROM quos_socketCheckChlFd(const void *chlFd) +{ + qbool ret = FALSE; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA(chlInfoListHead, temp, next) + { + Quos_socketChlInfoNode_t *chlNode = __GET_STRUCT_BY_ELEMENT(temp, Quos_socketChlInfoNode_t, head); + if (chlNode == (const Quos_socketChlInfoNode_t *)chlFd) + { + ret = TRUE; + break; + } + } + Quos_logPrintf(LSDK_SOCK, LL_DBG, "find chlFd[%p] %s", chlFd, _BOOL2STR(ret)); + return ret; +} +qbool FUNCTION_ATTR_ROM Quos_socketCheckChlFd(const void *chlFd) +{ + HAL_LOCK(lockId); + qbool ret = quos_socketCheckChlFd(chlFd); + HAL_UNLOCK(lockId); + return ret; +} +/************************************************************************** +** 功能 @brief : socket通道链表获取 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +TWLLHead_T FUNCTION_ATTR_ROM *Quos_socketGetChlHead(void) +{ + return chlInfoListHead; +} +/************************************************************************** +** 功能 @brief : 变更socket接收处理回调函数 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_socketInfoModity(void *chlFd, quint8_t sendCnt, quint32_t sendTimeout, socketRecvNotify_f recvDataFunc) +{ + HAL_LOCK(lockId); + qbool ret = quos_socketCheckChlFd(chlFd); + if (ret) + { + Quos_socketChlInfoNode_t *chlNode = (Quos_socketChlInfoNode_t *)chlFd; + chlNode->send.txCnt = sendCnt ? sendCnt : chlNode->send.txCnt; + chlNode->send.timeout = sendTimeout ? sendTimeout : chlNode->send.timeout; + chlNode->recvDataFunc = recvDataFunc ? recvDataFunc : chlNode->recvDataFunc; + Quos_kernelResume(); + } + HAL_UNLOCK(lockId); + return ret; +} +/************************************************************************** +** 功能 @brief : +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static quint32_t FUNCTION_ATTR_ROM quos_socketSendDataByteSize(void *chlFd) +{ + quint32_t byteSize = 0; + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA(chlInfoListHead, temp, next) + { + Quos_socketChlInfoNode_t *chlNode = __GET_STRUCT_BY_ELEMENT(temp, Quos_socketChlInfoNode_t, head); + if (NULL == chlFd || chlNode == (Quos_socketChlInfoNode_t *)chlFd) + { + TWLLHead_T *sendTemp, *sendNext; + quint32_t nodeByteSize = 0; + TWLIST_FOR_DATA(chlNode->send.orderList, sendTemp, sendNext) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(sendTemp, Quos_socketSendDataNode_t, head); + nodeByteSize += sendNode->bufLen; + } + TWLIST_FOR_DATA(chlNode->send.disorderList, sendTemp, sendNext) + { + Quos_socketSendDataNode_t *sendNode = __GET_STRUCT_BY_ELEMENT(sendTemp, Quos_socketSendDataNode_t, head); + nodeByteSize += sendNode->bufLen; + } + byteSize += nodeByteSize; + //Quos_logPrintf(LSDK_SOCK, LL_DBG, "chlFd[%p] byteSize=%u/%u", chlNode, nodeByteSize, byteSize); + } + } + return byteSize; +} +quint32_t FUNCTION_ATTR_ROM Quos_socketSendDataByteSize(void *chlFd) +{ + HAL_LOCK(lockId); + quint32_t size = quos_socketSendDataByteSize(chlFd); + HAL_UNLOCK(lockId); + return size; +} diff --git a/kernel/quos_socket.h b/kernel/quos_socket.h index 42b3497..2cf5125 100644 --- a/kernel/quos_socket.h +++ b/kernel/quos_socket.h @@ -1,95 +1,97 @@ -#ifndef __QUOS_SOCKET_H__ -#define __QUOS_SOCKET_H__ -#include "quos_config.h" -#include "quos_twll.h" -#include "quos_swTimer.h" -typedef struct -{ - quint16_t bufLen; - quint8_t *Buf; - void *peer; -} Quos_socketRecvDataNode_t; /* 接收包节点 */ - -typedef struct -{ - TWLLHead_T head; - quint8_t sendCnt; - quint32_t sendTimeout; - qbool (*sendCB)(void *chlFd, const void *sendData, qbool result); - void (*recvCB)(void *chlFd, const void *sendData, const Quos_socketRecvDataNode_t *recvData); /* 等待接收超时或成功 */ - void *param; - quint32_t pkgId; - quint16_t bufLen; - quint8_t *Buf; -} Quos_socketSendDataNode_t; /* 发送包节点 */ -typedef qbool (*socketsendNodeCb_f)(void *chlFd, const void *sendData, qbool result); -typedef void (*socketRecvNodeCb_f)(void *chlFd, const void *sendData, const Quos_socketRecvDataNode_t *recvData); - -typedef struct -{ - quint8_t *buf; - quint32_t offset; - quint32_t bufLen; -} Quos_socketTempData_t; - -typedef qbool (*socketUnform_f)(quint8_t *buf, quint32_t bufLen, quint32_t *offset, Quos_socketTempData_t *unformTemp); -typedef qbool (*socketRecvNotify_f)(void *chlFd, Quos_socketRecvDataNode_t *recvData); -typedef qbool (*socketConnNotify_f)(void *chlFd, qbool result); -typedef qbool (*socketSendIO_f)(pointer_t sockFd, quint8_t type, const void *peer, const quint8_t *buf, quint16_t bufLen, qbool *isSending); -typedef void (*socketCloseIO_f)(pointer_t sockFd, quint8_t type); -typedef void (*socketParamFree_f)(void *param); -typedef struct -{ - TWLLHead_T head; - pointer_t sockFd; - quint8_t type; - qbool valid; - void *param; - void **self; - socketRecvNotify_f recvDataFunc; - socketUnform_f unformFunc; - Quos_socketTempData_t unformTemp; - socketParamFree_f paramFree; - struct - { - socketSendIO_f send; - socketCloseIO_f close; - void *peer; - } io; /* 此IO类CB里面不允许调用Quos_socket类API,否则会引起死锁 */ - struct - { - socketConnNotify_f notify; - quint32_t timeout; - SWTimer_T *timer; - } conn; - struct - { - quint8_t txCnt; - quint32_t timeout; - quint32_t sendInterVal; /* 数据发送最小间隔,用于限速 */ - Systick_T beginTime; - Systick_T recvTime; - quint8_t waitIoSendAck; - qbool waitPkgAck; - TWLLHead_T *orderList; /* 有序发送列表 */ - TWLLHead_T *disorderList; /* 无序发送列表,必须是无需等待应答数据;有数据会优先于有序发送列表,即使有序发送列表还在发送中,由于不对此类型限制数量,尽量避免使用此类型 */ - } send; -} Quos_socketChlInfoNode_t; - -void Quos_socketInit(void); -void *Quos_socketChannelAdd(void **chlFdPoint, Quos_socketChlInfoNode_t chlInfo); -qbool Quos_socketChannelDel(void *chlFd); -void Quos_socketIOConnResult(pointer_t sockFd, quint8_t type, qbool result); -void Quos_socketIORx(pointer_t sockFd, quint8_t type, const void *peer, quint8_t *Buf, quint32_t bufLen); -void Quos_socketIOTxCb(pointer_t sockFd, quint8_t type); -qbool Quos_socketTx(const void *chlFd, quint8_t sendCnt, quint32_t sendTimeout, socketsendNodeCb_f sendCB, socketRecvNodeCb_f recvCB, - quint32_t pkgId, quint8_t *buf, quint16_t bufLen, const void *param); -qbool Quos_socketTxDisorder(const void *chlFd, quint8_t *buf, quint16_t bufLen); -qbool Quos_socketTxAck(void *chlFd, quint32_t pkgId, const void *recvData); -qbool Quos_socketGetSockFdType(void *chlFd, pointer_t *sockFd, quint8_t *type); -void *Quos_socketGetChlFd(pointer_t sockFd, quint8_t type); -TWLLHead_T *Quos_socketGetChlHead(void); -qbool Quos_socketInfoModity(void *chlFd, quint8_t sendCnt, quint32_t sendTimeout, socketRecvNotify_f recvDataFunc); -quint32_t Quos_socketSendDataByteSize(void *chlFd); -quint32_t Quos_socketTask(void); -#endif +#ifndef __QUOS_SOCKET_H__ +#define __QUOS_SOCKET_H__ +#include "quos_config.h" +#include "quos_twll.h" +#include "quos_swTimer.h" +typedef struct +{ + quint16_t bufLen; + quint8_t *Buf; +} Quos_socketRecvDataNode_t; /* 接收包节点 */ + +typedef qbool (*socketsendNodeCb_f)(void *chlFd, const void *sendData, qbool result); +typedef void (*socketRecvNodeCb_f)(void *chlFd, const void *sendData, const void *recvData); +typedef struct +{ + TWLLHead_T head; + quint8_t sendCnt; + quint16_t bufLen; + quint32_t sendTimeout; + quint32_t pkgId; + quint8_t *Buf; + void *param; + void *peer; + socketsendNodeCb_f sendCB; + socketRecvNodeCb_f recvCB; /* 等待接收超时或成功 */ +} Quos_socketSendDataNode_t; /* 发送包节点 */ + +typedef struct +{ + quint8_t temp; + quint8_t *buf; + quint32_t offset; + quint32_t bufLen; +} Quos_socketTempData_t; + +typedef qbool (*socketUnform_f)(const quint8_t *buf, quint32_t bufLen, quint32_t *offset, Quos_socketTempData_t *unformTemp); +typedef qbool (*socketRecvNotify_f)(void *chlFd, const void *peer, quint32_t peerSize, Quos_socketRecvDataNode_t *recvData); +typedef qbool (*socketConnNotify_f)(void *chlFd, qbool result); +typedef qbool (*socketSendIO_f)(pointer_t sockFd, quint8_t type, const void *peer, const quint8_t *buf, quint16_t bufLen, qbool *isSending); +typedef void (*socketCloseIO_f)(pointer_t sockFd, quint8_t type); +typedef void (*socketParamFree_f)(void *param); +typedef struct +{ + TWLLHead_T head; + pointer_t sockFd; + quint8_t type; + qbool valid; + void *param; + void **self; + socketRecvNotify_f recvDataFunc; + socketUnform_f unformFunc; /* 此回调API内不允许调用Quos_socket类API,否则会引起死锁 */ + Quos_socketTempData_t unformTemp; + socketParamFree_f paramFree; + struct + { + socketSendIO_f send; + socketCloseIO_f close; + } io; /* 此IO类CB里面不允许调用Quos_socket类API,否则会引起死锁 */ + struct + { + socketConnNotify_f notify; + quint32_t timeout; + void *timer; + } conn; + struct + { + quint8_t txCnt; + quint8_t waitIoSendAck; + qbool waitPkgAck; + quint32_t timeout; + quint32_t sendInterVal; /* 数据发送最小间隔,用于限速 */ + Systick_T beginTime; + Systick_T recvTime; + TWLLHead_T *orderList; /* 有序发送列表 */ + TWLLHead_T *disorderList; /* 无序发送列表,必须是无需等待应答数据;有数据会优先于有序发送列表,即使有序发送列表还在发送中 */ + } send; +} Quos_socketChlInfoNode_t; + +void Quos_socketInit(void); +void *Quos_socketChannelAdd(void **chlFdPoint, Quos_socketChlInfoNode_t chlInfo); +qbool Quos_socketChannelDel(void *chlFd); +void Quos_socketIOConnResult(pointer_t sockFd, quint8_t type, qbool result); +void Quos_socketIORx(pointer_t sockFd, quint8_t type, const void *peer, quint32_t peerSize, quint8_t *Buf, quint32_t bufLen); +void Quos_socketIOTxCb(pointer_t sockFd, quint8_t type); +qbool Quos_socketTx(const void *chlFd, void *peer, quint8_t sendCnt, quint32_t sendTimeout, socketsendNodeCb_f sendCB, socketRecvNodeCb_f recvCB, + quint32_t pkgId, quint8_t *buf, quint16_t bufLen, const void *param); +qbool Quos_socketTxDisorder(const void *chlFd, void *peer, quint8_t *buf, quint16_t bufLen); +qbool Quos_socketTxAck(void *chlFd, const void *peer, quint32_t pkgId, const void *recvData); +qbool Quos_socketGetSockFdType(void *chlFd, pointer_t *sockFd, quint8_t *type); +void *Quos_socketGetChlFd(pointer_t sockFd, quint8_t type); +quint32_t Quos_socketGetChlFdList(quint8_t type, const void *param, void *chlFd[], quint32_t maxSize); +qbool Quos_socketCheckChlFd(const void *chlFd); +TWLLHead_T *Quos_socketGetChlHead(void); +qbool Quos_socketInfoModity(void *chlFd, quint8_t sendCnt, quint32_t sendTimeout, socketRecvNotify_f recvDataFunc); +quint32_t Quos_socketSendDataByteSize(void *chlFd); +quint32_t Quos_socketTask(void); +#endif diff --git a/kernel/quos_swTimer.c b/kernel/quos_swTimer.c new file mode 100644 index 0000000..550b725 --- /dev/null +++ b/kernel/quos_swTimer.c @@ -0,0 +1,351 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 软件定时器 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_swTimer.h" +#include "Quos_kernel.h" + +#if (SDK_ENABLE_TIMER == 1) + +typedef struct +{ + TWLLHead_T head; + char *name; + Systick_T endTime; + quint32_t timeout; /* 定时时长,单位ms */ + quint32_t repeat; /* 重复次数 */ + void *parm; + void **self; + void (*timeoutCb)(void *swTimer); +} SWTimer_T; + +#define SWT_FOREVER ((quint32_t)-1) +static TWLLHead_T *SWTimerHead = NULL; +HAL_LOCK_DEF(static, lockId) +/************************************************************************** +** 功能 @brief : 打印定时器 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static void FUNCTION_ATTR_ROM quos_swTimerPrintf(void) +{ + Systick_T now = Quos_sysTickGet(); + TWLLHead_T *temp, *next; + Quos_logPrintf(LSDK_TIMER, LL_INFO, "**list pointer*********endTime*****timeout**repeat*******timerCB***********param****name******(" TIME_SEC2UTC ":%03d)***", TIME_SEC2UTC_(now.sec), now.ms); + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + Quos_logPrintf(LSDK_TIMER, LL_INFO, "%-16p " TIME_SEC2UTC ":%03d %8d %6d %16p %16p %s", timer, TIME_SEC2UTC_(timer->endTime.sec), timer->endTime.ms, timer->timeout, timer->repeat, timer->timeoutCb, timer->parm, timer->name); + } +} +/************************************************************************** +** 功能 @brief : 初始化定时器 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_swTimerInit(void) +{ + HAL_LOCK_INIT(lockId); +} +/************************************************************************** +** 功能 @brief : 启动一个定时器 +** 输入 @param : timeout 定时时长;repeat 定时次数,0为不断重复 +timeoutCb 定时时间到的回调处理函数,此函数需尽量简短, +timeoverCb 定是次数结束调用,为NULL时则无需通知 +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_swTimerStart(void **swTimerP, char *name, quint32_t timeout, quint32_t repeat, const SWTimerCB timeoutCb, void *parm) +{ + SWTimer_T **swTimer = (SWTimer_T **)swTimerP; + Quos_logPrintf(LSDK_TIMER, LL_INFO, "swTimer[%p] *swTimer[%p] name[%s] timeout[%u] repeat[%u] timeoutCb[%p] parm[%p]", swTimer, *swTimer, name, timeout, repeat, timeoutCb, parm); + + if (NULL == timeoutCb && SWT_SUSPEND != timeout) + { + Quos_logPrintf(LSDK_TIMER, LL_ERR, "parm invaild"); + return FALSE; + } + + SWTimer_T *listNew = NULL; + HAL_LOCK(lockId); + if (swTimer && *swTimer) + { + TWLLHead_T *temp, *next; + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if (timer == *swTimer) + { + Quos_logPrintf(LSDK_TIMER, LL_DBG, "swTimer has in list"); + Quos_twllHeadDelete(&SWTimerHead, temp); + listNew = timer; + break; + } + } + } + if (NULL == listNew) + { + listNew = (SWTimer_T *)HAL_MALLOC(sizeof(SWTimer_T)); + if (NULL == listNew) + { + Quos_logPrintf(LSDK_TIMER, LL_ERR, "mcf newTimer"); + if (swTimer) + { + *swTimer = NULL; + } + HAL_UNLOCK(lockId); + return FALSE; + } + HAL_MEMSET(listNew, 0, sizeof(sizeof(SWTimer_T))); + listNew->self = (void **)swTimer; + if (swTimer) + { + *swTimer = (void *)listNew; + } + } + + listNew->name = name; + listNew->endTime = Quos_sysTickAddMs(Quos_sysTickGet(), timeout); + listNew->timeout = timeout; + listNew->repeat = (repeat == 0) ? SWT_FOREVER : repeat; + listNew->parm = parm; + listNew->timeoutCb = timeoutCb; + Quos_twllHeadAdd(&SWTimerHead, &listNew->head); + Quos_kernelResume(); + quos_swTimerPrintf(); + HAL_UNLOCK(lockId); + return TRUE; +} +/************************************************************************** +** 功能 @brief : 删除指定编号定时器 +** 输入 @param : 定时器编号 +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_swTimerDelete(void *swTimerP) +{ + SWTimer_T *swTimer = (SWTimer_T *)swTimerP; + Quos_logPrintf(LSDK_TIMER, LL_INFO, "swTimer[%p]:%s", swTimer, (swTimer && swTimer->name) ? swTimer->name : ""); + TWLLHead_T *temp, *next; + HAL_LOCK(lockId); + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if (timer == swTimer) + { + Quos_logPrintf(LSDK_TIMER, LL_DBG, "timer is del repeat[%u]", timer->repeat); + if (timer->self) + { + *timer->self = NULL; + timer->self = NULL; + } + Quos_twllHeadDelete(&SWTimerHead, temp); + HAL_FREE(timer); + Quos_kernelResume(); + quos_swTimerPrintf(); + break; + } + } + HAL_UNLOCK(lockId); +} + +/************************************************************************** +** 功能 @brief : 变更定时器超时时间 +** 输入 @param : timeout:超时时间 +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_swTimerTimeoutSet(void *swTimerP, quint32_t timeout) +{ + SWTimer_T *swTimer = (SWTimer_T *)swTimerP; + Quos_logPrintf(LSDK_TIMER, LL_DBG, "timeout:%u", timeout); + TWLLHead_T *temp, *next; + HAL_LOCK(lockId); + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if (timer == swTimer) + { + timer->timeout = timeout; + timer->endTime = Quos_sysTickAddMs(Quos_sysTickGet(), timer->timeout); + Quos_kernelResume(); + break; + } + } + HAL_UNLOCK(lockId); +} +/************************************************************************** +** 功能 @brief : 获取定时器超时时间 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_swTimerTimeoutGet(void *swTimerP) +{ + quint32_t timeout = SWT_SUSPEND; + SWTimer_T *swTimer = (SWTimer_T *)swTimerP; + TWLLHead_T *temp, *next; + HAL_LOCK(lockId); + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if (timer == swTimer) + { + timeout = timer->timeout; + break; + } + } + HAL_UNLOCK(lockId); + return timeout; +} +/************************************************************************** +** 功能 @brief : 变更定时器重复次数 +** 输入 @param : repeat:0为永久 +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_swTimerRepeatSet(void *swTimerP, quint32_t repeat) +{ + SWTimer_T *swTimer = (SWTimer_T *)swTimerP; + Quos_logPrintf(LSDK_TIMER, LL_DBG, "repeat:%u", repeat); + TWLLHead_T *temp, *next; + HAL_LOCK(lockId); + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if (timer == swTimer) + { + timer->repeat = (repeat == 0) ? SWT_FOREVER : repeat; + Quos_kernelResume(); + break; + } + } + HAL_UNLOCK(lockId); +} +/************************************************************************** +** 功能 @brief : 获取定时器重复次数 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_swTimerRepeatGet(void *swTimerP) +{ + quint32_t repeat = 0; + SWTimer_T *swTimer = (SWTimer_T *)swTimerP; + TWLLHead_T *temp, *next; + HAL_LOCK(lockId); + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if (timer == swTimer) + { + repeat = timer->repeat; + break; + } + } + HAL_UNLOCK(lockId); + return repeat; +} +/************************************************************************** +** 功能 @brief : 获取定时器外参 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM *Quos_swTimerParmGet(void *swTimerP) +{ + void *param = NULL; + SWTimer_T *swTimer = (SWTimer_T *)swTimerP; + TWLLHead_T *temp, *next; + HAL_LOCK(lockId); + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if (timer == swTimer) + { + param = timer->parm; + } + } + HAL_UNLOCK(lockId); + return param; +} +/************************************************************************** +** 功能 @brief : 变更定时器回调函数 +** 输入 @param : repeat:0为永久 +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_swTimerCBSet(void *swTimerP, const SWTimerCB timeoutCb) +{ + SWTimer_T *swTimer = (SWTimer_T *)swTimerP; + Quos_logPrintf(LSDK_TIMER, LL_DBG, "timeoutCb:%p", timeoutCb); + TWLLHead_T *temp, *next; + HAL_LOCK(lockId); + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if (timer == swTimer) + { + timer->timeoutCb = timeoutCb; + break; + } + } + HAL_UNLOCK(lockId); +} +/************************************************************************** +** 功能 @brief : 检查定时器时间是否已到 +** 输入 @param : +** 输出 @retval: 距离下次响应时长,-1:无定时器 +***************************************************************************/ +quint32_t FUNCTION_ATTR_RAM Quos_swTimerTask(void) +{ + SWTimer_T *listMinTime = NULL; + TWLLHead_T *temp, *next; + HAL_LOCK(lockId); + + TWLIST_FOR_DATA(SWTimerHead, temp, next) + { + SWTimer_T *timer = __GET_STRUCT_BY_ELEMENT(temp, SWTimer_T, head); + if(0 == timer->repeat) + { + if(timer->self) + { + *timer->self = NULL; + timer->self = NULL; + } + Quos_twllHeadDelete(&SWTimerHead, temp); + HAL_FREE(timer); + quos_swTimerPrintf(); + } + else if (SWT_SUSPEND == timer->timeout) /* timeout为-1时为无效定时器 */ + { + // DO NOT + } + else if (NULL == listMinTime || Quos_sysTickdiff(timer->endTime, listMinTime->endTime, TRUE) < 0) + { + listMinTime = timer; + } + } + if(NULL == listMinTime) + { + HAL_UNLOCK(lockId); + Quos_logPrintf(LSDK_TIMER, LL_DBG, "suspend"); + return SWT_SUSPEND; + } + + qint32_t interval = Quos_sysTickdiff(listMinTime->endTime, Quos_sysTickGet(), TRUE); + if(interval > 0) + { + HAL_UNLOCK(lockId); + Quos_logPrintf(LSDK_TIMER, LL_DBG, "interval:%d", interval); + return interval; + } + Quos_logPrintf(LSDK_TIMER, LL_INFO, "%s:%p timeout:%u repeat:%u", listMinTime->name, listMinTime, listMinTime->timeout, listMinTime->repeat); + if (listMinTime->repeat != SWT_FOREVER) + { + listMinTime->repeat--; + } + listMinTime->endTime = Quos_sysTickAddMs(Quos_sysTickGet(), listMinTime->timeout); + HAL_UNLOCK(lockId); + Quos_logPrintf(LSDK_TIMER, LL_DBG, "exec cb[%s]",listMinTime->name); + listMinTime->timeoutCb((void *)listMinTime); + Quos_logPrintf(LSDK_TIMER, LL_DBG, "exec cb[%s] finish",listMinTime->name); + return 0; +} +#endif diff --git a/kernel/quos_swTimer.h b/kernel/quos_swTimer.h index 39decc7..e2e36f3 100644 --- a/kernel/quos_swTimer.h +++ b/kernel/quos_swTimer.h @@ -1,37 +1,29 @@ -#ifndef __QUOS_SWTIMER_H__ -#define __QUOS_SWTIMER_H__ -#include "quos_config.h" -#include "quos_twll.h" -#include "quos_sysTick.h" - -#if (SDK_ENABLE_TIMER == 1) - -#define SWT_SUSPEND ((quint32_t)-1) -#define SWT_ONE_MSEC (1) -#define SWT_ONE_SECOND (1000 * SWT_ONE_MSEC) -#define SWT_ONE_MINUTE (60 * SWT_ONE_SECOND) -#define SWT_ONE_HOUR (60 * SWT_ONE_MINUTE) -#define SWT_ONE_DAY (24 * SWT_ONE_HOUR) - -typedef struct __SWTimer -{ - TWLLHead_T head; - char *name; - Systick_T endTime; - quint32_t timeout; /* 定时时长,单位ms */ - quint32_t repeat; /* 重复次数 */ - void *parm; - void **self; - void (*timeoutCb)(struct __SWTimer *swTimer); -} SWTimer_T; - -typedef void (*SWTimerCB)(SWTimer_T *swTimer); -qbool Quos_swTimerStart(SWTimer_T **swTimer, char *name, quint32_t timeout, quint32_t repeat, const SWTimerCB timeoutCb, void *parm); -void Quos_swTimerDelete(SWTimer_T *SWTimer); -void Quos_swTimerTimeoutSet(SWTimer_T *swTimer, quint32_t timeout); -void Quos_swTimerRepeatSet(SWTimer_T *swTimer, quint32_t repeat); -void Quos_swTimerCBSet(SWTimer_T *swTimer, const SWTimerCB timeoutCb); -quint32_t Quos_swTimerTask(void); - -#endif +#ifndef __QUOS_SWTIMER_H__ +#define __QUOS_SWTIMER_H__ +#include "quos_config.h" +#include "quos_twll.h" +#include "quos_sysTick.h" + +#if (SDK_ENABLE_TIMER == 1) + +#define SWT_SUSPEND ((quint32_t)-1) +#define SWT_ONE_MSEC (1) +#define SWT_ONE_SECOND (1000 * SWT_ONE_MSEC) +#define SWT_ONE_MINUTE (60 * SWT_ONE_SECOND) +#define SWT_ONE_HOUR (60 * SWT_ONE_MINUTE) +#define SWT_ONE_DAY (24 * SWT_ONE_HOUR) + +typedef void (*SWTimerCB)(void *swTimerP); +void Quos_swTimerInit(void); +qbool Quos_swTimerStart(void **swTimerP, char *name, quint32_t timeout, quint32_t repeat, const SWTimerCB timeoutCb, void *parm); +void Quos_swTimerDelete(void *SWTimerP); +void Quos_swTimerTimeoutSet(void *swTimerP, quint32_t timeout); +quint32_t Quos_swTimerTimeoutGet(void *swTimerP); +void Quos_swTimerRepeatSet(void *swTimerP, quint32_t repeat); +quint32_t Quos_swTimerRepeatGet(void *swTimerP); +void *Quos_swTimerParmGet(void *swTimerP); +void Quos_swTimerCBSet(void *swTimerP, const SWTimerCB timeoutCb); +quint32_t Quos_swTimerTask(void); + +#endif #endif \ No newline at end of file diff --git a/kernel/quos_sysTick.c b/kernel/quos_sysTick.c new file mode 100644 index 0000000..f5eaef8 --- /dev/null +++ b/kernel/quos_sysTick.c @@ -0,0 +1,255 @@ +/************************************************************************* +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 系统时钟 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_sysTick.h" +#include "Quos_kernel.h" +#include "Qhal_driver.h" +static qint32_t Timezone = 8 * 3600; +HAL_LOCK_DEF(static, lockId) +static Systick_T SysTickStart = {0, 0}; +/************************************************************************** +** 功能 @brief : 初始化系统时间 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_sysTickInit(void) +{ + Qhal_rtcInit(); + HAL_LOCK_INIT(lockId); + Qhal_rtcGet(&SysTickStart.sec, &SysTickStart.ms); +} +/************************************************************************** +** 功能 @brief : 纠正系统时间 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_sysTickRectify(Systick_T sysTickNew) +{ + HAL_LOCK(lockId); + Systick_T systickNow; + Qhal_rtcGet(&systickNow.sec, &systickNow.ms); + + qint32_t diffVal = Quos_sysTickdiff(sysTickNew, systickNow, TRUE); + if (diffVal < 0) + { + diffVal = 0 - diffVal; + SysTickStart.sec -= diffVal / 1000; + diffVal %= 1000; + if (SysTickStart.ms < (quint32_t)diffVal) + { + SysTickStart.sec--; + SysTickStart.ms += 1000 - diffVal; + } + else + { + SysTickStart.ms -= diffVal; + } + } + else + { + SysTickStart.ms += diffVal %= 1000; + SysTickStart.sec += diffVal / 1000; + if (SysTickStart.ms >= 1000) + { + SysTickStart.sec += SysTickStart.ms / 1000; + SysTickStart.ms %= SysTickStart.ms; + } + } + HAL_UNLOCK(lockId); +} +/************************************************************************** +** 功能 @brief : 获取当前系统运行时间 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +Systick_T FUNCTION_ATTR_ROM Quos_sysTickGet(void) +{ + HAL_LOCK(lockId); + Systick_T SysTickNow; + Qhal_rtcGet(&SysTickNow.sec, &SysTickNow.ms); + SysTickNow.sec -= SysTickStart.sec; + if (SysTickNow.ms < SysTickStart.ms) + { + SysTickNow.sec--; + SysTickNow.ms += 1000; + } + SysTickNow.ms -= SysTickStart.ms; + HAL_UNLOCK(lockId); + return SysTickNow; +} +/************************************************************************** +** 功能 @brief : 获取当前系统运行时间ms +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_sysTickGetMs(void) +{ + Systick_T now = Quos_sysTickGet(); + return (quint32_t)(now.sec * 1000 + now.ms); +} +/************************************************************************** +** 功能 @brief : 获取当前系统运行时间s +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_sysTickGetS(void) +{ + return Quos_sysTickGet().sec; +} + +/************************************************************************** +** 功能 @brief : 计算tick加上ms后时间 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +Systick_T FUNCTION_ATTR_ROM Quos_sysTickAddMs(Systick_T tick, quint32_t ms) +{ + tick.sec += ms / 1000; + tick.ms += ms % 1000; + tick.sec += tick.ms / 1000; + tick.ms = tick.ms % 1000; + return tick; +} + +/************************************************************************** +** 功能 @brief : 比较时间大小 +** 输入 @param : isMs:差值单位TRUE:毫秒,FALSE:秒 +** 输出 @retval: 返回时间差值 +***************************************************************************/ +qint32_t FUNCTION_ATTR_ROM Quos_sysTickdiff(Systick_T time1, Systick_T time2, qbool isMs) +{ + qint32_t interval; + if (time1.sec > time2.sec || (time1.sec == time2.sec && time1.ms >= time2.ms)) + { + if (time1.ms < time2.ms) + { + time1.sec--; + time1.ms += 1000; + } + interval = time1.sec - time2.sec; + if (isMs) + interval = interval * 1000 + (time1.ms - time2.ms); + } + else + { + if (time2.ms < time1.ms) + { + time2.sec--; + time2.ms += 1000; + } + interval = time1.sec - time2.sec; + if (isMs) + interval = interval * 1000 - (time2.ms - time1.ms); + } + return interval; +} +/************************************************************************** +** 功能 @brief : 设置时区 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_timezoneSet(qint32_t tz) +{ + Timezone = tz; +} +/************************************************************************** +** 功能 @brief : 获取时区 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +qint32_t Quos_timezoneGet(void) +{ + return Timezone; +} +/************************************************************************** +** 功能 @brief : 时间戳转成年月日时分秒 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static const quint8_t Days[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; +Quos_tm_t FUNCTION_ATTR_RAM Quos_localtime(quint32_t time) +{ + Quos_tm_t tm; + quint32_t Pass4year; + quint32_t hours_per_year; + time += Timezone; + tm.tm_sec = time % 60; /*取秒时间 */ + time /= 60; + tm.tm_min = time % 60; /*取分钟时间 */ + time /= 60; + Pass4year = time / (1461L * 24L); /*取过去多少个四年,每四年有 1461*24 小时 */ + tm.tm_year = (Pass4year << 2) + 1970; /*计算年份 */ + time %= 1461L * 24L; /*四年中剩下的小时数 */ + /*校正闰年影响的年份,计算一年中剩下的小时数 */ + for (;;) + { + hours_per_year = 365 * 24; /*一年的小时数 */ + if ((tm.tm_year & 3) == 0) + hours_per_year += 24; /*判断闰年,一年则多24小时,即一天 */ + if (time < hours_per_year) + break; + tm.tm_year++; + time -= hours_per_year; + } + + tm.tm_hour = time % 24; /*小时数 */ + time /= 24; /*一年中剩下的天数 */ + time++; /*假定为闰年 */ + /*校正闰年的误差,计算月份,日期 */ + if ((tm.tm_year & 3) == 0) + { + if (time > 60) + time--; + else if (time == 60) + { + tm.tm_mon = 1; + tm.tm_mday = 29; + return tm; + } + } + /*计算月日 */ + for (tm.tm_mon = 0; Days[tm.tm_mon] < time; tm.tm_mon++) + { + time -= Days[tm.tm_mon]; + } + tm.tm_mon += 1; + tm.tm_mday = time; + return tm; +} +/************************************************************************** +** 功能 @brief : 年月日时分秒转成时间戳 +** 输入 @param : +** 输出 @retval: +***************************************************************************/ +static quint32_t mon_yday[2][12] = + { + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}, +}; + +static qbool isleap(quint32_t year) +{ + return (year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0); +} + +quint32_t FUNCTION_ATTR_RAM Quos_mktime(Quos_tm_t tm) +{ + /* 以平年时间计算的秒数 */ + quint32_t result = (tm.tm_year - 1970) * 365 * 24 * 3600 + + (mon_yday[isleap(tm.tm_year) ? 1 : 0][tm.tm_mon - 1] + tm.tm_mday - 1) * 24 * 3600 + + tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec; + /* 加上闰年的秒数 */ + quint32_t i; + for (i = 1970; i < tm.tm_year; i++) + { + if (isleap(i)) + result += 24 * 3600; + } + + return result - Timezone; +} \ No newline at end of file diff --git a/kernel/quos_twll.c b/kernel/quos_twll.c new file mode 100644 index 0000000..1a16c60 --- /dev/null +++ b/kernel/quos_twll.c @@ -0,0 +1,183 @@ +/************************************************************************* +** 源码未经检录,使用需谨慎 +** 创建人 @author : 吴健超 JCWu +** 版本 @version : V1.0.0 原始版本 +** 日期 @date : +** 功能 @brief : 实现双向非循环向链表操作 +** 硬件 @hardware:任何ANSI-C平台 +** 其他 @other : +***************************************************************************/ +#include "quos_twll.h" +#if (SDK_ENABLE_TWLL ==1) +#include "Quos_kernel.h" + +/************************************************************************** +** 功能 @brief : 向链表尾部增加节点 +** 输入 @param : list 链表的首节点地址 node 新链表节点 +** 输出 @retval: 增加的节点指针 +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_twllHeadAdd(TWLLHead_T **list, TWLLHead_T *node) +{ + TWLLHead_T *tempNode; + for (tempNode = *list; NULL != tempNode; tempNode = tempNode->next) + { + if (tempNode == node) + { + return; + } + } + node->next = NULL; + if (*list == NULL) + { + *list = node; + } + else + { + node->prev = (*list)->prev; + (*list)->prev->next = node; + } + (*list)->prev = node; +} +/************************************************************************** +** 功能 @brief : 向链表头部增加节点 +** 输入 @param : list 链表的首节点地址 node 新链表节点 +** 输出 @retval: 增加的节点指针 +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_twllHeadAddFirst(TWLLHead_T **list, TWLLHead_T *node) +{ + node->next = *list; + if(NULL == *list) + { + node->prev = node; + } + else + { + node->prev = (*list)->prev; + (*list)->prev = node; + } + *list = node; +} +/************************************************************************** +** 功能 @brief : 删除指定节点 +** 输入 @param : list 链表的首节点地址 node 待删除链表节点 +** 输出 @retval: +***************************************************************************/ +void FUNCTION_ATTR_ROM Quos_twllHeadDelete(TWLLHead_T **list, TWLLHead_T *node) +{ + TWLLHead_T *tempNode; + for (tempNode = *list; NULL != tempNode; tempNode = tempNode->next) + { + if (tempNode == node) + { + if (node == *list) + { + if (node->next) + { + node->next->prev = (*list)->prev; + } + *list = node->next; + } + else if (node->next) + { + node->next->prev = node->prev; + node->prev->next = node->next; + } + else + { + (*list)->prev = node->prev; + node->prev->next = NULL; + } + node->prev = NULL; + node->next = NULL; + return; + } + } +} +/************************************************************************** +** 功能 @brief : 在指定节点前面插入节点 +** 输入 @param : list 链表的首节点地址 referNode参考节点 node 新链表节点 +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_twllHeadInsertFront(TWLLHead_T **list, TWLLHead_T *referNode, TWLLHead_T *node) +{ + Quos_twllHeadDelete(list, node); + TWLLHead_T *tempNode; + for (tempNode = *list; NULL != tempNode; tempNode = tempNode->next) + { + if (tempNode == referNode) + { + node->next = referNode; + node->prev = referNode->prev; + + if (referNode == *list) + { + *list = node; + } + else + { + referNode->prev->next = node; + } + referNode->prev = node; + return TRUE; + } + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : 在指定节点后面插入节点 +** 输入 @param : list 链表的首节点地址 referNode参考节点 node 新链表节点 +** 输出 @retval: +***************************************************************************/ +qbool FUNCTION_ATTR_ROM Quos_twllHeadInsertBehind(TWLLHead_T **list, TWLLHead_T *referNode, TWLLHead_T *node) +{ + Quos_twllHeadDelete(list, node); + TWLLHead_T *tempNode; + for (tempNode = *list; NULL != tempNode; tempNode = tempNode->next) + { + if (tempNode == referNode) + { + node->next = referNode->next; + node->prev = referNode; + if (referNode->next) + { + referNode->next->prev = node; + } + referNode->next = node; + return TRUE; + } + } + return FALSE; +} +/************************************************************************** +** 功能 @brief : 查找链表第N个节点 +** 输入 @param : list 链表的首节点地址,nodeId 节点编号第一个节点是0 +** 输出 @retval: +***************************************************************************/ +TWLLHead_T FUNCTION_ATTR_ROM *Quos_twllHeadFineNodeByNodeId(TWLLHead_T *list, quint32_t nodeId) +{ + TWLLHead_T *tempNode = list; + while (tempNode && nodeId) + { + nodeId--; + tempNode = tempNode->next; + } + return tempNode; +} +/************************************************************************** +** 功能 @brief : 获取指定链表节点数 +** 输入 @param : list 链表的首节点地址 +** 输出 @retval: +***************************************************************************/ +quint32_t FUNCTION_ATTR_ROM Quos_twllHeadGetNodeCount(TWLLHead_T *list) +{ + quint16_t nodeCnt = 0; + TWLLHead_T *tempNode = list; + while (tempNode) + { + nodeCnt++; + tempNode = tempNode->next; + } + return nodeCnt; +} + +#endif \ No newline at end of file diff --git a/kernel/quos_twll.h b/kernel/quos_twll.h index 1378aca..95e581f 100644 --- a/kernel/quos_twll.h +++ b/kernel/quos_twll.h @@ -1,32 +1,28 @@ -#ifndef __QUOS_TWLL_H__ -#define __QUOS_TWLL_H__ -#include "quos_config.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct __TWLLHead -{ - struct __TWLLHead *prev; - struct __TWLLHead *next; -}TWLLHead_T; -/* 对节点轮询 */ -#define TWLIST_FOR_DATA(LISTHEAD,LISTTEMP,NEXTLIST) \ - for((LISTTEMP) = (LISTHEAD),(NEXTLIST) = (LISTHEAD)?(LISTHEAD)->next:NULL; \ - NULL != (LISTTEMP); \ - (LISTTEMP) = (NEXTLIST),(NEXTLIST) = (NEXTLIST)?(NEXTLIST)->next:NULL) - -void Quos_twllHeadAdd(TWLLHead_T **twList,TWLLHead_T *twNode); -void Quos_twllHeadAddFirst(TWLLHead_T **list, TWLLHead_T *node); -void Quos_twllHeadDelete(TWLLHead_T **twList,TWLLHead_T *twNode); -qbool Quos_twllHeadInsertFront(TWLLHead_T **list, TWLLHead_T *referNode, TWLLHead_T *node); -qbool Quos_twllHeadInsertBehind(TWLLHead_T **list, TWLLHead_T *referNode, TWLLHead_T *node); -TWLLHead_T *Quos_twllHeadFineNodeByDataCmp(TWLLHead_T *list,void* dat,quint16_t offset,quint16_t len); -TWLLHead_T *Quos_twllHeadFineNodeByNodeId(TWLLHead_T *list, quint32_t nodeId); -quint32_t Quos_twllHeadGetNodeCount(TWLLHead_T *list); -#ifdef __cplusplus -} -#endif - -#endif +#ifndef __QUOS_TWLL_H__ +#define __QUOS_TWLL_H__ +#include "quos_config.h" + +#if (SDK_ENABLE_TWLL ==1) + +typedef struct __TWLLHead +{ + struct __TWLLHead *prev; + struct __TWLLHead *next; +}TWLLHead_T; +/* 对节点轮询 */ +#define TWLIST_FOR_DATA(LISTHEAD,LISTTEMP,NEXTLIST) \ + for((LISTTEMP) = (LISTHEAD),(NEXTLIST) = (LISTHEAD)?(LISTHEAD)->next:NULL; \ + NULL != (LISTTEMP); \ + (LISTTEMP) = (NEXTLIST),(NEXTLIST) = (NEXTLIST)?(NEXTLIST)->next:NULL) + +void Quos_twllHeadAdd(TWLLHead_T **twList,TWLLHead_T *twNode); +void Quos_twllHeadAddFirst(TWLLHead_T **list, TWLLHead_T *node); +void Quos_twllHeadDelete(TWLLHead_T **twList,TWLLHead_T *twNode); +qbool Quos_twllHeadInsertFront(TWLLHead_T **list, TWLLHead_T *referNode, TWLLHead_T *node); +qbool Quos_twllHeadInsertBehind(TWLLHead_T **list, TWLLHead_T *referNode, TWLLHead_T *node); +TWLLHead_T *Quos_twllHeadFineNodeByNodeId(TWLLHead_T *list, quint32_t nodeId); +quint32_t Quos_twllHeadGetNodeCount(TWLLHead_T *list); + +#endif + +#endif diff --git a/platform/ASR_lib/lib/libquecsdk_in.a b/platform/ASR_lib/lib/libquecsdk_in.a deleted file mode 100644 index 4bb5608607a05bfa2ee9c8a436aa11fafbd80e71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 804296 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%0AK5Ow4G0! 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 Yj&8eGJxpZz#eZ4iLWy+A2 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%0DLgzsalFNcfNO zl}4i6bQ0;Z#az+scPMuwlfaHmB=R%+9qil6Fzhh<9qhWFVc4^soUQ#1^?8h8*n=WX zJ&?rYcL|C7){=<7ibTHGl29`HKk_wpAYWt0Oxb~aAu{P-C)rL)|E|&>B$56Q3FQyS zi5fpcHGbGml%t-TcmMSt#Ji=&A38-W?aD@Gp;VzxWfKq z#?=oruC748FrOm(h&1~>LOYkD-^m8NkXLLkb|RNKSOJ5}?UVo18>D!cV$#7B!^z^A z;zIFUk>|Zkk0vq1A;NH@XxbU^yA=MN_^SA(_*e0$_^sHM^+Wn9ae_EYTr92<*NQia ze8i9Ge=0sEzAt_)w&VT*y<%~=I8K})E)*NZOU3KNABmrde4LT_X5xH->@N-x$B5HJ zewKlHzY_l}_D-|%apL#H^F)4ho$2_|HgdE0fVf+HS$tbOEdEn;d0asH9I=a7BAzZz z73YfPIE8d;6kaFZDsB}Y7M~Jd6Ay}i7rz!G7|^sQpTz!9pl~k|gS4;0r;xa8aJs@% zNDR{H;%vzmD}1iFLh_3hzD&GI^7RUD6#4x-+H;@64~RdLoF4?|ys?XfJugZA1_^uK z7T+haA0ATtQOWu3aO!;}x!1~uGsGMc<#$%No7jUyc@>HuE_sz0Q#?P<&Gd66UnnkB z{H5YL@kb=)xveDTt>-0wMf|gvjX368Kn}xaaugm&qCTgJ!zCZ9@C0#+*#Cy&|04O{#3PFTPE60R z`klx=m_JCw^&??d6^Z=DiL)g?N8$6utHqndEhLU-_mjwPCyD%?QT$%<6B70KiaZs^ z|4bXsB{8XYSC}7bW8cd25yweBUEx{c_ar}8;RbP)59Zb;xrQVn@d*WL4SoW zBG1Jm;^LK(->mSh;wH(rDSW^9u;foFyi0se@>djoReV$OzbbrK{8;iYNvx~BAz^1a z4&1c69SJ)N#I7XbE5)H=Es1uiBhfBrlgMwS;x87j7B`8zNR;y%vJC4Qg%6S__mKFZ z`cv zxlHlbOMbJsQSm=k`~wOsD*Q5u z_TMkQA^Bev{+oD2@_#A(jmX#eGT&?x`Q(c2CGSqo!*vl7^G~VdLrCb45UWYpHA(Sj zNj_Ixr1;Ck8%X4PJBfVnC1JcU+XAcKSu7=?H;hEPjUh+dk1{d5fMK-%V(~obU8wNI;#$dXPU;+B%i799C5zn%N0IfynsYIUa9z-CBIeV$CMam>h?!uN=ei!X|=ihm`maUG3BzF&~YcW@UF z^%y1AiD!$AB=l}2(f)UlNc*tDPmt)Rr^RO_|GmQd#6L=YP~k)3hmwCzF2?md67!>5 z2%@}f68Yzf?In*Y+*9mBBHaMRkC1$n7?YfDI%a;;#n~k4bB^LyN`8TOvEqLy{#^XM z_!bE}50S|C1Mw5Zx9V#1FA_(J{FDaEJxg3hVr;A?(LdiO(H^%b{tj_F8N>BWgsQ9xKULiJ%KOm9cEhO^0gG7G6RQ$8zOC;L) zZH50X{!2{9v5NV$C6P~O68V&quxqe5QJg0(Cz010F&) z?ZBI-nST!wc9e>v#99(|&mc4KGJS;`Nz|iBTqF6F3B6AgcKcZUOtA}ze0!4V*zZW%qxjL{ z+2U#v`d5+d@JTeu?-PF}K0`upFNu8jlgRfy#UB>`BY9?D#G@Wv#WFGmI~1-Zk^W4D z=ZQ_?rQ$mARubjkL!$hLNTh#~#5mba;vD4<3cpRB3;V>sO8$3+KNJ5Yx!Vu=Q_+tk z@@q?;i;s4Soh9$3a38UZM7h;stvH9oIbl5sdmBjPe}&?&7q^O!iMzyCNTh$0MEZjy z(to1(FU8FMkfZ#LB;tpVu)m6Ijq^r@XOpPU_r!&gHz>SPyioEh6kaD@FZpc>-!5*E z{6U3(CO#_pE^-F0JCd;H50bx4!k+iUzbgJ?k=M?s-$Cp_BA*I{Ys53e`Qo|a#bh?d zHHr4Skwp4?6#p!VcG^pNXlL;dInHs8DE=Sfzr=6EaG6ctN^B$Mi(SO-VsEjZI8Z!Q zJWU)e){2wFGsUyS1>$0{L0l=~%_*jS-xt@3*NGd%ABuO1cZ&~*4~r28`JHB;6d|!| zn=po`A(r=sQE{j^QpB!r;>U|q#OdN3ah}*LULsy2UN7Dz-Y(uJJ|ON9_lkcIUlZRE z-xvQTek6V;hFxnnUl+`J=ZGD|&LY3K&G<@jusB?tDb5iWisy)U@q$UeQd}cmDw_K! zkiWT)0=!xBTg9J=kBCo-yTsp!FN)^=9OU!5!taRYJ^{!ND||%c_cK_(h{*e;8RqwU z$xdQdv6t9K94d|!CyCR<267mF#}U_v=6(an&3$a3xsMIJS@E}uynmYM?-I@ZY6zSA z)xhT^|E>6n_^SA>cu+hn9uYIbHviUQJF!6QF7^=nie=)-qPY(O_VE5{+H3BI02e5{ zSX?fiFE)#ph*yf&h&PG1h?~W&;x9#Wp9RW$LE)FgKZ&ird8P;tuh7@wehD;;Z6^WF^)Y;y=Y>;&);g2LkG~7TbzF#S*bx94N-b z@uIn31^Jl!RlsGEH;U%|6v)l}Dd2UI-z1v*QXs!mVRIh}!cQywy!c!374cQ^E%8sH zxeo>Dk170}7;a_jn<<+6QJ~jBVRK&!!lepVii1USUkc*QeJD7Vo=G|QJ#m3(?ni-q zxxyEU7mGKEw}?L$?-ZXDcZqw%z2YCl*Ti?k_r(vzPsB8=nP_La*hb733&p6|M=TX< zM04K@@|mn~oj6CFCpL?hh*yf&h&PG1h(8wZ6wUoDDCY@C z#81Uz;=jc<8MZz0#V%rZagaDzJWU)eju$723q^BZ4D4u7c%^ue_S zgtsgFsQ63qS@AdG@5O!M+v0oT|A-%opNU_J*_pO{bDs^$?Wk}U(cEVP`9Ot-iX+9@ zqPef8uj4FLc)8dtULsy2UN7Dz-Y(uJJ|Mmzz9jA!|0upI9uyCYN5s#>FU8hbw!Uq} z0>&*_W(U*YeG3&d68h2l-*Nw_{K{#d+IH23Ks-rT1HJ|g)q z#23Yv#n;6H;%B0{9|!5bRoKNj6U#OC<$&1==ZhW1GI4-7L>wX3h-1acqPcGe`JSWj zd7`<02l5LPzDB%Wyi2@Cd_a6y+)WnaJ`M4A;vdAnh<_755kD2vTHEyLVjD4EtQ1cc ztHc^{k~mHLp143X_w&F$b3YGwiR5d=JH@-j`^D|zqv9_`b3YIAc~0RM#h1lH;s@eC z#LvaA#s7$r99w^LUk~!_s&J85BK8+g6Gw~V#fjolax{)-Vw2b`n)`tef2G1ViW|gD z;%4zD;)CKY@mX=N_TRC!QhB7tQ@f zYzKwU7q1q7AZ`$UC_W%QEIuwiDZVW37vB=ieMu5ME)y>lFBaE{*NHzAe=Kej?-!pJe=EKszABpgnqcRj6h0(= zAe#G`5O3~h0yEp%dgqAk#ZF?8*h@6`H6fk3uL(Ru@+qRZuL*KzAye&JR<%>{8Ic{bn~peo>(Rh5Qm87 z{wb6fQ`p=`g)qNZ#rjSc=ZN#fbHww+mEr~BrQ+q{b>dCp55*sgTg7eSL*mcHz2fi1 z*Tgr)gW@6asQ6Fu8}U2wB;Fr|`t=nnMRWfaUU8rJy7-RxzG&_fL;fEr{Hgeb_;1l^ zXWONfm?f5o{l$Uesp3e{+$V-|#wmP;SSQXD&3$9gTd454VuSbt@kVi@xJle9ZWA98 z&HZJ__ZJF3B|ayVu@HT4itxpr-?Cfyf{^y zA)Y6$5HAof5-%687H<=87atOTF76ihh;Ncu>%SxZMf{uit!VCVgI;zAtDh?ticztj zST5Fz<~}y0pP}$o0 zVBb>;KP&!5d|BKtejt7Si$?xzzngq*i9@JPbSe;L&PevMw~5vPplW0 zispVhq`ykx>&2VJ+r-;NbN?Om&HZ=a(~|ENe=C~%?hyZ~!f%N0iGLBlC9xKEaSsET zE@q47emlrJDqJM?68no4;$U&O$d5NN-x=cBqPgD=@(UHdSiC}9CvFwDi4TcC7tQ^3 z$mc1ApA~;2zAGLS4~s{{&%`gq|A?I9Xm@+DlNc3yiV^-^vcz$){4&3%p*hWb5+#wo zgv2Q#6_a9 z8}Z8&HufXjr0`ntD$&#f@i!>EUfd{d6}O4^i`&H~NVMbA;%;$|xKDgtJRrU+9uyCW zM@jUzr}R^K7{yeF;{FSM#Y|DiP&E>zmp-o`JD``lDtNoOrn45#MIx>mQaps z)5}EjTr%eOCWgUg665a@aV?4QVSY~o&F^U#k2g>bt|!s{8^ui|#^+{nD~a~qCf-kC zylxkFkf_IF;u9ps@6+OLauBXlh(#RDYj|E_qD#5g}B`t>&TLVf=f zLOklbD-5E3yLjCm^*RqbDC_kRuWz9~f7kUMbDabB-S zgk2rlfv{&j*8{KvK_|2V&ux(?tEa*xBy==Ud4zsU;Mk*Mc9g%^>i z?+psCCsA+no(rUZnqkg2WvkXKZ<@aVY+7l##P{J&Qw1=WJ`#H)drb0yx0)ME04T1%M(-{$jQvk5A;l#zwErdHVL(OCg&&K zh!c2=^YOW~tQXHF&|-Ww(DCV06k1Fee;v?&PaaomDHHqJp#PeI-YnEYr6$UeQ~vx znl<$eNdGmfROOa3to!e+g|{kkpDas#H<=OY1!aC+lio~L5icxB%-z%O!^z?ksh>!H zzzOmh*s}Zt>L<`1)N&>#w=6!TW$~#kiwCzX9s)5&l=*1z+@*Y{z5MeREZ`>?0Y8~?r7=x^_>8{r+p>RFZVR)1C)O#5XSuo3WB<-metx<9?fx|urj1^@E2Za` z`z|u)dn^6TFZVL&1-%Hn=a>b!};a%9=l+ETRUPM>}MuD z26<0jFuxDB+Bz`5A~-+4U!>$W6DJSYmExOk-yfjzW*;E&k@$qdoM3?Xv#v z#Qs|XWxrhBBgo`_xxBW5Q=0h3IJ!kCjl@2#ZY?~fkP!&DjHd_CTC8Pq!&dM)(_-`N$^tA!peTf{d%zbqWff_k%{*HS%B zlAfQzQs_O-0jkZff@V3rxj15)zqb_V*xAKcLK^Q%eG8-R=k zVjuQ5U#}{q+=}NMhu2+M?r1o_+-WKK)t_tMFPNI&q?G(7znj=EPY>j`I3>Stk>63@ zDCqg#1u6L*Kz^J%Y2TPYetdVQp9W&*lVy(cb)bX&xTci+_P*~pYoX8l#=-gZy-N92 zK*sIxmAIc7YbSqWesnjd=sk>u=tN&hI@H^oV&A`^cL??HH6xXdk*d zZr=mYL$^0VWVVAh;eHA~0J2POWS|%)_hziGFwmwzz7me*=E8C81%{DRwF&o=1O$G5 z+y?_Z3z{#haQgO=31EbLjBe#DmD=JPo<&48+ka9CQ0*yyX<{d&C`(|D-#3_alvC z9{uLi&%ZqEL>tFcR1O$8=;TvQ9Xw?If`yCf7dr{TsOr%*r^lS5kC&ct{f@L)PHaWY z`!qMFWJ&tT(~qSUJ{?NmuBpf#>cZ`UaaKN{Mt=poZ_{QYd=9)g;lwYyB?Zz@28_8URmaW zQ-{6t$i(y)TaAC_?z6kNS)<(n_fG%t@V%eTN346`%whjHTmg-YejmT(ME`QwjlFl6 zEuC4jq1xHsIHv6}cUVEqvBRYw{%cb9ojI|Nv9y?b>)4&6b`IS+aOeBSn%8!#t&D9N zb44uo$w^1QtLS~OBDQ9fkA4J9d3=^yar}GLK}WIi>&D zA4U4ljE!bQ@K3K*_8+{ZA0D05F?RKiO|eKUyrIqJf+wA*XL4}*ezh;Z+3EM?z9&9) z+I+t6{bMC}qxE7#Vq?*MUhMuSCp`N8v37S?#P*H392V>w0}D^@{#teI14o>UqFCfe z2CRJI$;h#c$?Xolf9&Y`!PHq_#lLrqid~7Ceo(M?O!`juHr+@l0g|VDFieoQ-=v+B0)_q)b!qzcv^p?Z4@}VxF*p=0LUMq@~blyA0 zDS2jp38a_D_UsI8DvGtOomo>-vk5)&E7Uq?pRL!GH4h!}GVZ{*?XxqpwkS3e?d_C& zvhRVKu}GUyJ1$mTSOVFK!{G_fCAT%&II)+qy^l@0^H|AR?nSr6y2tuG+HQP7Z2cIg z*X6P4r#r8w{xAN72r)W4)>-eROqJHrhKa7TuXuJ3i(VO@MXmk;wiT$Fg#r zK7ZYp(d_g&xX(TLy~AB&?`yoXo4HjyzHjG?JKa8^ec|Sce(m;U?sR(O&&%xT)?lw2 zcJHSZUPfugf#RL*m!y5;4XhZJKkxGDPxrggid|0h--lNXEiZSb@eduw%BZwh>sYVa zvBgdi>)Q^a1*7fq9%%Wm_C?1zFpVH zddJ!wbI+Txb7)!mzVfojzN&uj>>&q^g9+$^ZEWz#ixfu-#As>V)@71v+hIM!;fT?w|cAB z(P-MJ;a>l=1E<4x=J1WxM_zMx=FD@7F!L3^c{n$gUi|vu+{e?JOZ!JAg!lEpxXG<5 z7~@uK9aB|3|HvpecbA>}xR3X&eG@bHl31JAdh}ayty{!7!;6hBti+yu$$IA`{M`z- zbozUU_O=Ajcm&7_efKlJ0I+k11;ePHi1JB z^B=$QSU5Kv%Z+8`+<2^2#f`_DXlC|}$J}UU=8eZf(aw;jMLR>{MLTCYJ#IV}iJlv4 z8_PT9*0y`n9oVYhSNos;G(D|2)~P0h{U{m>?eN=W@Z)K*bnMTY#`I_|?w7s4M*(_Z zcyYg0`y;hick3IQU&uCcdMunCjSW^?(!M)!y~M6X(%ipc=g#g4As@)%Nu8S1fl&M2NP+n_;^@an#b=PhSI$sAaN)?tp|i5H?q(m-6KNY3?>M*;<;2P%`-ns zrnR~ng?MR^w6hQyNxOk6Zh92LR`f^GZZ(q7KuGX4+uL@>_HN*bw2%0#%%oca`K7vY<%?7@!I5_i$H;s$vh}GnYlL1 zC=(7lnY8yJKa9fGBkbk2>I9LAyUmYlgW5B**6Fa^eben^Dg^wsi!@#VUA-PVlHI|+Y2$iY=NcQRgjp6^Zx zHM#ljRKB>|$>Vgx*CN_Tn8IiJ+~?h{Ov-HzoV$4|O?k>j4e15ZVz>oxIlo4uLH~@k z2yBGIa@A>r(tl&E+k@b4xOP+U$9=)gVosqAjd_z1{*NhmDy&k=>_DVx#a2%1Gtf%z z7Pk|-liSm9?&ftUE@v~nTigMgzz^a63YT-b)sN9|Hg$Wt<%>?HX5Bgnhe2>ET)Q9P zk9)V9!#)acSbHhWy~mn67ou5k9j>6xHaGi1B)kEB{?y-YI2+)XZq_;k!W#};YR=O? zgs@$d>JPcuG>={qR8~VS}$Wy>S8lPGzEecN&|b;3}xK zhuW{>YEHo|kQG7pzGRm&sZC_suM^w86beoO&yt70_ibF&Dd5;R9kLFvFFp?3N~eIW z#j^S;0)wKU2C^xTjk7Xa$`rQ(X1Y~Q!30FlLG&UeiJRi&asU6UwJE|+RmGvU5`|84 zEBx_~gr=9jb6T5E4Q+G_V@S!ZC!D*PC)e#4HahM`cMv=I4YVF-lN@#`gr`w~PYLsh z;hZq{o{jD#*5w=s=E8MmD{w-{gF}tOpSzRUVxPNH*xH}FQ=L4XMN$RP9-@07??@U) zO6!Nwbna@mYcB-2QRr^w@>PeJyHw`dWt#(Jzsxy$3&*fUHR=ZPZ)zuKJ zf$I=qMi;ud99Hi@@EV10TVWxE?a?-E;hffd2-TgE*b%2vYjYGiPegwzetHhe{DS~`R)QnLMvF*qO&2RSv zGr~gYW1RvXla?U*MMaxwi^mvj1sFyq@^8tJ$QF?!70E|0nXyuuRaL9CDE>?m?MMMLGmb6`POwFNugcEp5s%hpHargl-(m{fcA!YMR47C zncF=wZynTUAUv6}FO6(DD|98om%(*!SiRhFKQRZOhY-FOF8Vn0`^?A=Bh0s*7Ze~7 zk1u@XxNR;w=dgFTNoEg982x!MXmePgTkK%q(Xa4i80|h7KM*vsO*Zoxw{GK1%s^Vp zcd6&Xb$g!e^5E%gPChTnccyn+i-7y!EbcFS29(Bh+sbSnY@m$Kb-qBEY4!)NrYw?y z;}l%Cf3sE(-oi5aB772D^lJp%9cGlzM3~QK=JMkGc*sjJ)}o&?(KD@2L-;X7{)`IT z?FK;bcLbv4wDP$u=K5!Z{{&Z100p+o^9Z1=GFCeqCP#`dc8WPld0^-X1vciJR)Mm- zp^#l?nrt#YX(3IthEzkahV5?Vb;&wNhQ`gC2${b)u!kcyxj8J*lriz-%o$x&z%HT< zXDJ5zlsO_$wn8%6IuClZ>jo?HE^yW)D#F3a1Y4DW8FS~Pj4wH-#m$gip-c`~S;V#o zP2sLP6%}?0?oqmrm5v78FPVcu;*YwYN!Cs>=Kl-H`dC?b1D03?&q+8!imc5?ko^#~ zS!!fcyfB&#IZxy6AG*|`IiX9_Z2HfX{?bJHJO}!=mrPGOg}>rHwYHE0gqvB>Z2meM zITf;raCZEzokv*{!i|*e=dfG5l6yIydA)@)Gk({u zqaNQI|2Ui-zw6BS{Se`|C^IAS4kMcc%g4dlVSINMt-J(bz7M`2iU5bk92m=&*$U0%Z@ z!s7_yCTw9Ct0_%--a0+WV8cvEEvl}vZ^EK$Eu1o5h5i@WtBmYLRmDSSS04rISed@&c)%x0--WYhVn2em zSpeobg?pK4+d?i-xbZ;cZsv?+C*!t*X;2j50=ORa%~%w+9Y>ivCCU>vH{UF7<{`EY z&gnS_jd}-M#Oc`!@l&n$K18wnh|`N}e7aWoS-mD>PoZnoF4Bu%#i8@sW%gQ)lyvEx zBfYvKp3ck9?6ni|bgkNE^?V8OAKEOR1n9i>nLYV~)Lb}}UyHK;2yGPlKiJ>tGNyPv zcS7?=R%0|YdK(Q?%k7nupB2vOk~6(yTQ|2OTC}H=l{FF~oK$ph!Z|~;hGh-O;@iK| z6qAlJ+dv6r5R7Vdl0&5DyIEN_fs0^9>p=R!(9d+;arunNDuO(} zbuy)TL$Z2z8kgVJ&5KkO{HVGU)8%+JN5qZG4|V({U*6nikj)|}%gsW~@*+&qHW-Z& z`-3Z7#PR94{OfSS6)Mw;fshp=K!ND+s zb7Y!>6S8;;><{Kb2JH+v6L+V!^!X&Ny`tcp z>BsoA8dDP*GyND4wy_wBTxZ7jfUB&RlOs!o6o2DeA$-=Z$%NII9*;(LIDRURx@;4sg&uZg%k6*&^Sit2n6~hu7Wh5z|FHKa;8j&;+wk7!oPBaK5;8yt0Zu{~1TruP z2mv9)BoM{~5EX@x1Wqs{A%g<7HY!f7RW#I6L7`Rq@>&N({i?6F(pFnKXtCPX_VulH zL~93}Y4NT9eLw5j=j?L`+Ft+nUf*~9*T0f=?)5zDS=cR=wqJu_&PlByCK3WheMO@V0zl5fw54IRRVCi$IMRndJo2qN|Juv!^p9 z%0Y;k5l!M1Ogu{jR?GvK#L-s5DKF|yPh8K$rd#XhA!rh_g5m;hK=AbB4NN{39+Slc zz+4lo0vJsLOd*Jq`37Su6G19P(-89E&EdmPrHLRs6^%e3&SikYSRqizwxW$DVmdrn ztXhd)z6h{Xf-4DYC3rqzqu~;6H(bIU5@cnfjw&|5qt4BMUK7M`oMRPGMFO)Qcv>(* zE4)InBi~1TS_4Y3+llPU4Y^h%knkbOC^8LgjcnldPHRW05W)Ocov&|?^k2Z@Xy&#tK0>qu> zvcN$Cg6ZnjgmS|r$laCra~QbdOj;UmkhG`|H&oEXE8N6n8~ONOle*qbZCmHZ64$$l z!HrL4D3i7!mUa!(hT$>f38vIME5|uXvDt|f@pgeLs4@YMO#CE6LEFZx#X_2qxr)F! z1x-naNN^RQ-EaxF87_gIJ@^;kaug(fS^y&jK0_jw6Xa&Db-^SA=Lt8~nHV{YH4@7S zb49|vfl|=&K-DuVBXL?^n!+@= z5(5pDKBQ(h5vgUME(04?HW`i*VtE))Y=RR2lT46c`sPA{X@KQ~9U`!@4`6D4p@=8h5KsW+# zA`pmkZqYFR^&w&>Jc5K>5?oCL*!5eN zphJB2DeNezmmZ*Pe8<+=(T*?Ky34lqwlsA&Us`r)OILeKo7FUHZKS2GwWE9OrlzKn z#;z?T?GP+!Y246SGP%5TO6laaZLJ#^xi&6xtwy%CH?{RPw^#$@(A?E}SxeXA#_cU# z|4cUV>Rr8-e97C;+u7OC)w8mrqpcJ-XmMMvrx`DImPR<=t%x+Xt?Sv|+0qRfQ^IK} zB1|D%JpG3ZsEQ4}t!+Iet?e>Ewo*fNTVr=*S!a(bA*-t8GD9Idv;x8$k&bOSGWz|4 zZO50L?byLg$0@3CLcuYPZ>lpWXotg&KRCik4%)yn{HFMKgdN+T9QN7hmzajJ)Bpv8 zouop?9u;GI{~0SSnh-cZD%|& zY{*h4n^dU^)j6dqG?rN53^TIBghQ>D8Du~7$p@}t{wCj zlsajs!C-VpAv(w35_VERLnIJf?2HL!k3(RrlN0oJ+fEXj-3bYBMWDh? ze$anu7~D`0z3QYQr0Kz#f>_&0E%hgthMi>prf}HjkE_X_O--B-HTWRUX5@%rQIbj% zM@b)sj2R}Sjxz`2#BmBu<-^kHzOXX~^+ew%jYIa6oMBKCs&G;!>R6fYB$@W$Z`cVE z?uTTt6r6(q%@`7qVy9NncFGM~$Wljy!cG&INu|z3gf%E~F~?~s8#2W-qz|n)J&1d^ z%!qvvLi42vq7s5-QeBLu?usGV&PY}nq8RcHf-@l!n1Tnb-oxa+Xag)_)!6`LPD&Au zsA*x-5dK9KP6?9wWt@kdDyl(OFF-A4nf6$OJp7~4g2*Kk(LNqGz6ys&gs;>|H`IK< zpi<`?LXR|G2t7U(WC-cZq36H2!i+Xk&kEC^cVPthM~0E19S-_8nn9s$8^B?jf%Ji3 zVYcJ*53`3#F*r(*wb5Gcc9I;8_8MmjV(khipHOP3u(OC%IH7Yc5hi{Hmr7Z3!p>Sy z0CqKiIRr4y77H3WsRBgWA}4vLcD;XX>42Rd9;ovx+ii z91$TLOhRmOQgO&3XwTIn6o(5*ZAuIoGuf#W#)cf-$c2+!utH{Zqk^>Rv zF1Dr5zs{736?PUm9N}7ORGr1-RO$;ZkXhJ=7MkWx7B;fvNz|VkmXl1V)C~Qw9N1{A zdC-MA^OVO4F1Hk?SsygA3>+3XNH`5bax!6&vzZCP&N@N`M5pj%%2RT=gXd39CVA!2 zf{b$u!l4&1lNXLxCz)i>P8d)xXlt-JG90Ja$$=P$&q8PDI46}&n>NaH+Bh?q(aE*Q zdkRi6n3y<(3T0T3#go09f;eijofPCciIats9?Wqk&^ zNP9}c;^IBe07@F;I9NFZi?St8q{&7jW;>X9gR`N+tT&2H9kkO(p%tCrg&0f_#jG1F zL`KQOaiWpa9)}!TlbwAQ)0Vo;5D3+k_$oMyu!H- zr&C{Ws`eBb0?B-637jG^x8h{#puLhuC7vU9`+2lLYB0|bJPR=)MshNb&rxhJbO6r! zbA5rhVZC1x$}(;cMzv#)K>nivAdqdYWPgDaCu6#kJOL9-O1P|4=PiFPvSrPKSX-o* z&dCnW9T_aEyTHvI^F&8QwrEL5nhTut*(gq#oD5{@DxZT4Qv8>*A#v=XogGRP7Gl39 z@6q?2xMHGw6H&e}HL-ax_x#@RjY7gDP9_vWwe((aq%(%?43-?|7-M>*2hR?!F|C@g z%KCY%A+uy4m<1Mx2q$kG3C=iH*i0g5)Qn|Z2oyQlTnP9uUeQyfJ_n`0yrZkR+v;lR z?rrO_)~%B@cWe8)EiGG`I=5R}H()tG(1nA!G{KchNB6q;m48pmmQJfXf=hugUq0_{ z-O;kH$67%_yt%QUv8}1Mt+A&Cmj_*|8{4c)TehMsr`6iI5asXL(pia@OJb?JJG)S?jTRD2LBdsx zWkDC?^=>H>i6zZvTl~1qT=8kL8$&meS<6)|$rLnu^tZ)hXyLV0#x~*kfN!K*cJ-zKM@r+3CmJN&`;<~J-r3+nm zDL7KE5?qhY>F(Ir0|rY{($v`2)(xT&MVNz2$IE7vbcIVcg-e!!i_5h+o7#I(rjm{A z9U!|}n|ewb+gcmDThS0D8#ng0H}$l3w3nb)h)A=MCbOcnX>E)j>9g~;^>j5h^;pfg zQr;5n)`i`x*okPr)+nzBBl^;omd^9GboOjt-q?jP)!W|Px~aXTIouR!>@tF}hBZCY z+|j$Ct;OnTY;EIk+|qFwhGp~FGHi52pbl*7xW?y8NM7AMI#%J$+*of%^Jcfl5ivz> zhBCg8O;1N}n+16@TDLTAiw-_8m-lu@=67{9Zr7tQPG?t34~y(7LQ^+%w!t3R!dYuM zp4K+EbawR2SlhY1C(_a0P*rD1iy%u@1c@8)UemftP71A^4C_&_m`8yqjo`3g1dxS_ z)d4#2Z}A?1IVzf4E~{>7X>RV=WLhJxoN?)5$9rq%6!xcdGSxRqJ)1gudC0VE;bGm} zErslC?1}J@VV-E9224Yi_MuD+OpT+j0nr<90C-!ZzJ&yqK+yedx4h!`wGVtmT?*zM!x3!900(b;|en!qt=i0z^ z{_A}=+m#vfd{rqKIrCHFYzy#q+Wd^noq=op*Fh>yCvV-2hj-mR1S&JicP3vOx-RMZ z;0?}=fzSFshg4UjWE5AX${l=HmXB^KGm1}_t<0ENW5nai%iDpK8RM@>IXy*Od3iTz zen#3(|FtZwd79?t$yssJO0I!ocPkVv(#lj zjlAJ!*(`@Mqom#nrX||e7Aji(Nhx+sEVRpVfGEl@;iFaO3z;Sbl4CjF5P~Jr{F+Yg~*#$ZwsMKu=7dOA=`BEWz|;`eentgOP>f zi4r^tkHBZ)csWOD&JkHS9;$(^FGQ-{3_am<7(`K-V$;X?VtSZqCBr^GZJ@bdL2apzA3AdzS!` z{v$lobAHakczs`K&dFKmUB0=`c+P!U7>|RLHv049Y)zNR zTUTjrGFh$0h&HpR;u>iQE|Vm}U13F6PjYb+rR3tt71YZniS(=?qrzt42?qgvu_(({bn9p8L2hR9!oz9)Yz5&ZQ;OyL|; z#0YVL+OMtx-vW(v5#l`Xi8$a7DBeRXz`kF^d6soQ5%eL&M~Mryyo)R=OnR}F3-9Xg zBfVVp;jz5v!+|FH@TgeytyF!;SM<$ReRvN}>W!B|%BctCoJK^sgT%?^wFKXE^Ij@3 zmwezbB7_TxXPb2-`J5ez$U+cB7eo;Sk(J;jiY_RI3Lh-*8e?x@)-xY^q zc|}_6QX`*fq$j|>6A`rRlM8(fq=9Rc->h^*@p8paDfSVOeisq?9#;ArM4ZBYr1Z-~ z=#~9%fxlM%pOycXA|8i{Tu?EU2>oM~mi=y#o}VC*KU?|dDpo0f6%pg$5~V+-@t;xL zNksX+ruektYl?qV{D<=|($Ea&^m$1O$Ezly0u)N82H!xamaUqT## zH(-^&SW)(MMY@ZX=EttgXCo2(E~PJ1yjJl>McLmKdLJMSe1?em?FUN#TIu6T|3m2j z=48r`RFwT!LDQxx`Le$%^6w>$CFd?8%C%eZUd4Y`e2@tF?Bto9w%KCEfZIjZk5mC+Qaq;kN5xY_lp~l9 z#3go^2))IM(-dba)+#PmT&uWVaU&7>WIs;Sy-(@SY5XmUyOqCR>AMvlR(wqHSw-20 z6XklIH1z*M`TwQ(8zS`)}fy;-`_UPk~|~5%IF$CivybKU?_=6lK3n#Mf*5 z`O4p@c%|~MR+N1;q4#dmnEMZ_ob0CwJ>OEfmz4ji(!W)FL-~JJd{^rVr}&`aGm1wQk14*S_y%zt<}1Z_6;CRDq{wS-%F!$j zafD*A;&??`=wbZ1iq(pX6#3OItD1B7%$BHj1%DTP?^S{!^72i~RTagAr zsQ14W`LP7)kYbu5P0Wx#RB@zYk>Xj3C5rr%oANUiXwYZW&tZdSZj@kYg6 zin|r}DIQQfsCd8PBZ^;Fl!I>;ts{D6t7jhNpY9rzbSrA@e#$ZE7H~% z^?zIO`-;yg(wGqA|ET!3;`@sKt!VSQ0dh2ZMod$b>mtxYl`bb@{XI=_w&J;p)ryN0 zS1PViT&LKiNHay$C)Z6t+5#qhgCZ>!k(TQu;DbsZR(wKHu9p!1jM7IHf2{bT;x83{ zr6|`;NcW!7A1d+&2=k>)AYNa{brW!;(nX3i55)Ki#RZBrigLY#_!gzNC~j4}Qt@iV zU5dLEX)TEB{;w;3Q}Nr1G{?mF=M-O3d|B~T#osE@(h%i8RP-kqI!Q4@F-wtFh$uf% zaf;$}#W{-e6=|V}@)s&LDz+%LDRwGeqjpIZKm3~w4ZAH1xL;Qa$ZSy(^G;J1feP5tBT2Zd+z%Nx=uJ1sfqx2j_+BahQHHzyL z<+=|1OO@_Wyi9S2B5fft{q2f(Dt=kn6zZB(q5c*$N`mc)b zD2k0G#HZ+WRF>ipMcP|p`AQY1E6!4s>paBEbsn%u`I{8wdJp_ArRBO0v|RTAcPjq| zMY;Y1zhCLSiu)C5%8BK9T=8i|8f+r}2a3-t(y|lzzgPT|;t56C_u+L-x?+xEp5iFQ z(TX%JMfu5!GZiZoX&Z>~s}(O+Y*eJ3AjWS~{EXr?inJNT_&XHk`V{oNO4EK2b}#SBHc&INy%(qYA7#fgfvR>Jht6=x~R^)UDi zN?)Y7PI056TrWe8HjSvKPmwl&czyUq#k&;0qDXr{jDJG$h~hJfa=narxn2eyS3XQ* zOS<QZyLls9V7Aa0tEK`*0Vd$tO4P2(!pm?F;C5kjEM7iyXeTq93Kc{$$ z;_ZreDAEKG(?6p4ABx{pd{*)MiZo9|`QIqgHWTSTE556EQjw;M7%%%s0*5G_uSnZO zKFk#}6z3{dDlSx9qIkaIMT#2~<$4`@XxE7P87YXY1u>iiaVQHS zOF=0g$_XXYNCTz37>5<4f%Ax{PnBXV5t%GjlzJeum85}EAJlU# zY2bPy>f5XsA)?-Gik-x;F~&iZ`l21KARpLAEHh)DWXw>VcMAS!~TcG@TNuW_~xnGTPUxYL)Z!6DNVXV_Rf1_M~ zBceQ0po``C1rg<_Oamf+sFM9q^N7eFqC$@+LZ`If3>^nZ8_{@a7nDod0rAofDBnFA ze~<{}FDd;p5#{|*X^ZtlxoJp|`>}GLE|!0$(iOxo`b%kfzKZ(vD7}q{!tPdj4-xf~ z=W@^|&*f0h*EIfjM2^dUWgpkr9fIe6;JXj`;|kzLny33`C`Y`I*)=g%J3PvdKK%C-32c4#AD3b59e^`r789^M+Vu4R z4+r|?C4q$63cD?_jP8pA3Bqv?_Y>kwwY<;wMd1J6qY{S!4hL~KWaI;LOfTZ5DZ1dw zO#*{qo>%B7hc{fJ&ZnU&Amwn2SzJASao9#3EkHEVwpn>&3Olj6Uc)F9w z0Mm?|515v6n5N`ir?~QgSPQ=ETz!{=;nf#9&!jI0pL$$MI#S%j(2ZNCa+geg4g7)SpnMYsKGA;6bb@xBs}Emd#pzq`sR!G~ZNI_`Hcx$X5KlSwb4P+c-oN5lb)8$j zs}kg{hTL_o5YUz5`3L{e_6wmQi#>8~`FOtamhTYC$MUd0I3C>c@qV{g-z4Z8sJ{0l z=zARcYE>UjuTg!^Cg^(|`UW~azLlWQs*aVfz@v}%*YO{%Zxz-f99OP$>&y3Hy!8!1 zAIAyn%lX5tFTQ9LeQx?Ipzq6g7UMeRMaTQ~UVSuj{5lR0>I=iW`Z5yqjlkMtk=t32 zbM=K2YJFL@5nmK>VpLJ zje&RT!RHHZ8nCTfAU!kMNdn-g@XlB=vEQclE7I(6{6)SUgHoWdK*-@&tXI zjb^P!eMl13w_f#;PxmSwgz;Xk>!^>O&q0W;++4f}w$v3;8OCo*DBn$xJIM%jtOs3B zLit{A#GZ8EQ$Lq^ZhfzXoOdeQIp?3-qwP`1?Ls~9s+-uyqrOadwlVZYUDX0STlJLB z4f1)RSMKEvuu6@xxXzU;n|$`i#na!nR%BPs0S!2qlHlGDP?x!V` zV@%29>7C&L#KU7|HiySdhZh0)>E``7?mskh$*qTG)@5KPp_$9ZN3iqR8*Nux{gY}->YjVU zE}pdLNX?|h<0A)ZCe=N6vb^s=_3(%_arUvbkylUd-UW|+jP@Nexuo|qkMzCEPE@2p ze|vtbUA-rgvg_s6JGgHb_!*>=p)2?={BY%g@X3pQ<-+HX( zXw9&_N&R_ypL@e;O5PjzqQ8H11pT+*tkLz0to~O|)?QU#hu??o>iXIvXwjEz^6qG; zzT=4<1%r>7-lLA1MJ4EwnmW6Bp)0j#khFMV#5!x`vGW%F3{FW|&G z8@t&}U2?EyH}*6Py@e;PrPna@H0m~e7y8IDSfl;x!|T8LPwbn8saD))6h98`VdR+y zLweOUU$v(*2BODJn}3Y;~jQ^w#2 zk7X@Qug;S44qg_J@@AsEnTzZ%Jc{xTLU{+Fyo02?gHYally`Tf?}e5PQkIFQj&LCPwkwz zexV!#Cys5xTpGR={>yi_Uiw5;ZDDmaYEr$hy6(jy*`2QJSH~)wkmU=#?WjTH_~g_(#9= zhTnb-J6itymv0@Pv^f2(@z8-|7O_7^%%0cNtclAWNBje?OjyQIFabyG!M>Ro2WO!) z6C-!D25!5s6=%mOdn0!L??#WoF**XRT#OleW@L0-`dj6pJv9>|^Qwan^|xM7Q1G<> zt&gVmH{uK)VHs!Q{87=DHtlYlRlleu9Uob++33!HV%X;7hf~()Z$OE{Q#&{Rll}5> zL&|($Voqi&8@n&nFHQ_&L1hCzS@w$5%YrJAyV9(;64_PwdLK3dw({#DJ+m%1)(N~S zVMqYqClALpidi!U>_SXDX}4ijx(^G{ZJyG@zz5gy{tEE0m!MpKL^ly!O=c*N0|9Iz zI8z|R%O-3h$g6g2B6uUI04xU)lCqEkb|>T;Q7~T;xQ;S*=m^Lp6ZY|=1Nq?8UncwB z!FI(llS?Vm`hS3MVAN2qUPD~;gq-Be8Fc4`u}ZhM3T25FVeDnRyqIB;N;4Qdat( znI?GkRBrn5n(hwo?lAmPE(9o6O6(mj#{k7BHYL({(?6X*VDJUIVBjA7`irR~-%pPJ03wqLGG9hG>3bsbEBvOt zfuD5#AkCA2j8fzkn&kVEa9I99z0hK3kiU}f{l%l^?I=EVgZT{ip4{?QQf`0`^N ze9n4CJb@qk$AhT)8U#NDpIeEao73@F14SHyz21c94($~WnSESw- zdeO>_Ktvu1A|cZIFv@d#z&ng&LzIV%hVB9Ho8)0fAndJ&doOq4=MV7NwGgv^EnA^H z37VzMAs_ebFI2_xH+rB43H8pe_RGw^c%f&gC&=H85+y4AZi zXwU#RDGL408g>%$e_=xIM#bEJ9?N~`Nbsams3h-cTKS*H5Bp^}hhwId#O^B~!#!B@LX7nzngAT7ZUw(8(bOB|4v;7gp>!;h?mjz>`v(+YP>EAS0Y zzRfvm4I=rN*)tZf!ySfNw0Zcl+8LWCgyMLT4%|HZrFn1!Bs34}BF&SFc@E)%ZLIY5 z(mancxbFhofHMQ-`dMrqzCij1_z|4+?CY1XdFm152Vc@W{PM=zJfD^3;ro~m!JFp! ztTfL%p!weA$a3g7h1^Z^d|sM|`xx`l^)uS3$_ z_q~wYjGLyj8CRle{B}*6?B|H|HrZxrvM(e0OYo-2HcOLnr=p+2kEH5WBr{F6MVf2~ zhDjED?uCftVZ}SjZiRW1R}QOo1Xy9dnI_|=5R4)Az~iRV8pd{&>E|{C*uZ>c%`@OI zRzGEh_aQQe_YZT2;D?!i+LJj;E17d;!OXwm>OfPRG4s}FYga)k*4k_?Y3(eW0}(Fd ziQ66*-EEN1eC>T1D2*Ao<7O~ig_hx4sUvtEx5riYArZf(dw@I{%;Q#%0q-Ty$Hs zSHY83|6d5O)h8i4={a7ppQ+1dTf?40G@E&7!|I_+24ph=EW&Y5 z5jKJ+MZjDqMF`{0m=wPej5F1_#2Ut0iPo18;C#cq1d)()OYUpnalScUd0cUDBSFk9 z7zx1)yua;c14*!Hg@wn;=e%t8OXw17*jPmJSjpwf4OvM1byXW04-q~4V{g4s6=pw( zu#P_z5!1{(xk?=`L4eI1fHJA$J;?h^b*!<5HA9|t#3GS%P|?p2v~x${*J3@eLnk6f zw|87_*+XS%`viv8Bk*R7=E<~l67&i3WI>lF)6!HNg(dI_vsu<=Xl{pp69qKGwm@bo zP9Yb==k_6z)w@q>V5)aDcv3y~1grOJQ$1`QiMsiANZW5WYpo?17TCiPU!x9t8PZa@ zT(qe4;76M7*5y`i0eH+P8{R!WcnC^HD-mEuJ0%k505tX)+RON@m6f zV}^ggl7zSj#rVxegv5m^Bq~%fvH<){Keh$6hH+k&A{PI`hgWdmVeqr~A#mvf%hQYB z0MZ8w(u%oHJAH6eTJbI5(Fen+#Q<^sbDd&7+M`b%l>~DV=5cz;aHsfL=%)A23l{U! z3;MvIA;tXsfgUzKi{C*!ePCI}|G-Xm{)~^`%PxMeJG@pHzmZ*9E+%yD1()u~-gp__ zeb%_28m>vaEFVrWnFDVHce~mAjyu_9EX{|=XpCJ)&S59hss z>#Ljn6_wg%$2o3`7nk8^)Hu3ppl*s+Jm98u7#-rq<99JU_E8}4c)_+Wgf7D+@C4|$ zdV2OVaF^lZ*W*eNUJj461g7^{mj{SO{(OYQVKx#s9lx6KY6So3+#ZMU58$zv0e<6L z7AUX@of2gBaSqc4BvufN#HwgCZNr0@0?(xkb_$yZpx0on1RDq@Z9PK9X`NUlleS?V zf>rR?27@ZyX#<`iG z%)n}3x*mA6F@cHL#<9e4ZOnu$ujX`_3F3DEmjfOKC*ckWt|IJ%H;u~BgC>ITdNcw7 z*U4eX#xfdK9zc*DGZcYKUlg2hP=YJ*8|RjSx&R(h2LN9&K>`!8rm@6vHDy9iruRs~ z)6|pePGb2YBKtY*Fw%rRcvD}7w3Gzi=Q9xuMc`2>LR&O~p`#`O3F2HHC=4$GC9oG0 zhgY2Af)D*fFiN7+Zk(7BM!JDuO267$`iA{|h%f_EAJFK0@)X4-}(ySq5OK=6t$k$^i z!PR(`u$L1e5?m82>~d%@j^?Qmx(S|YZEFqn&}*<%g7pMy2iGKw{oan?mnCgI(@hYm zdIA&qtgb*TalM<&7ckojdNQ3P2@}-kBf(sF$LbHP><=*b2lyb!TR}4{*Datw4H~5D zWRh+MWo8=L(dcE6Xz#_uU%_KE5?+&F^w0nW#|8f{;UPny9A>pZOg?&`*pkI-B5>gAYcXC6*l$0ns>TLM9y<|buBJZ|4uPIP0S+^8*j9}UVoS+Gby*U4H9^XP@yb~qTy z3f}L03ntWk*}*jy?dJ0b#@V?~haJq_7{pfGVc&!-=Qp&ejva--4+g{Xi~SkKRNyNI zuXXrw%n;`8VF=!b?NzWD1EB=JrQu*jXruE8_2h^Zd)YPsdof5>vdIgz+p@99)y~-r z>y{+gh(Ls(*gw@+j)#@|<=i*K&T$5791Y9cC_1(j2uhUv(71gLHVuHec$$sJMj`x- z@G>PByIfLx{UP0FC{9GSVUEE5WW4=@K)i4%Yn7uZU}2X=L+*{f=ze%Euqv zWVTIU(SlN9sfqN6tyMAA?s2A@3I(JZrd~Ptu!||(O;XCS2MlUt_J{Jfb5kYk|1*ju z4dk$FzKL<&d)$ie--V8^utGBT%O)rx4x&)-E@vwd8{H7tgyttG!z((*&@Hk>3F1%d zmb;wgWTW#4*|IAR%Pso{Vfz-?`Ddd#XiIJc6#y+=j7=b5|KG_hbh2fe2zuNveN~3yuc&|PNDXhI~Zj%9BdxNa`?9`(g_3OCdf@C{5#yHFa;{bAv0b! zY;mx04z{zoAB>2MNNlLW!O20%oiFT3P9b3zf4K2XzU-0W4}PCxA1zOQu!z6rNbV7a z45d((+3*OPkcik>&aHa1yGMmPJIU@&#j@3!Jt2CG7}W{;A}v;(wj=XfbWbHykSC-2 z9SLnlbXin|9(`rhTxu$2s+))Ea(^=J|H5ODjhu?jlgtRmo+tP^&$er2hre}8dhs#V zy2Tw$ms)%`a%s!u#-cpVaJAS$qCvjTvIgE`g9(S#>#oN7R-;AxY3q@B8c0?c>cK%nMzj!f= zsC$N(tuD^^W{S~VlkoD3c~ix+jYajUr3+RrT()%Gy8mx(VZp}3+k6W)xP4>#zsHg- zOQa!m(}L*k2(lqV8wSYYj!g@j<2IM*ZfV=->GEh2_I(_o?+RHBSm$AFwQeQ)Pdj*^ zO(V{@IfUEiP$0#M8^o*HSxWPA6W4Sc9QwkN*+D|a&=zbG($Qq~c4BW2?74vVqx3!V z0XBuuA|f|!6z^M3s#@Mz#|{tXu<35&MjzW+d#W~U($}oa$eZ8XyrkP5)G}7L)Y>Pht&Ff(QvwKF2_gtay0_6;Q zG(>z`GKwd8WO?y*2HEvHbU%k_9(i7*aj%HbwMo|nuXk<;-01%-_B!w_OvwmWKL3O7 zAN}5UpOsw{YBIv*o^+2QoUm_2c{jGLsC?c{a9&0ZWV~hDC27L3y)GiBCkR8vb-cKn zpW%_e#O#aV)ya#fuKE8MS^7UOz#GlYQ{uiMnk>#SSvdaPZ(}*fbKeQR>B{(BLJzjwZ&N#k z&ar;3@tljf55(&fR!9ABC(wy^p9WZdnr#|r^STe3@7 zn|X_1R!MPET))U|ySSy5cPVESn0s`(DD$#TyT~w-1Hbov`up*+G>ct*cuOnSDKgf4 znArY=@!_j7@2~pKGfyad)%b>#{2H}C!c!i`5cekt5J88CwRoeNh$$jd`8>~2?-fMo z>mx!R$0pO=K!o0#h%+#ziO{o~h*KfQK5-urdJial4{;Hm{}55$`-yefkAaAj%cI2c zy5GPO%X*45PR&P%n2?_#Lhm8g3+3an#d1APL^+=#;?#JAh;lxo^ikqe*pDZo+{cJ0 z?@L6K?`0y=y+TC(uMv^|?}*61kBIzs5|PghMC5xD5&Cu!k>76OWZjPf6EAPJGoJ%Q zj}ubDLRi4c)b8xi_DiO8>qhyPOEUD~Zs%h6ufDiO{>ASfb^?DOAba~W5eqKwQ2)#sP+Dydz1rZ|3(?&#jI*BMx4-w^&@rUxr_#27(k&ki&S-x2quf%il z29{#3B3B9Ax1*3a7Y{;|k1C0LrJ_7@MtW%v;Q7kGM6pFt>W%nrrFSUasQ3lNI}{Hp zKBV}l;t|E?6o0As7e%o*jC{^wJ%Q5|7br^mf`75nn-wot+^LBBLXuvN8^|A3`h+4M zcu_xJ3T3~^=PjTMNu#mGYW!s4D9f6swAlAWf7dI$mWY0BRJnHLZzs;i+pvmPEC2IK z->P^!5&g7R`xGA{BL5@ABHhme^lwQc-@j_S-*4o^UN7RsJ}+5rMkuP6)7T*Z7MR*DL>0r8^X97lip$p4QTp9~($n@Pk%b(GQ*h|pWAI92&`m9A8*QT_@d=Bw38H)?!~ z;%4RdDlK+!q5n$K7$?_j{B0V4yQ1tHf^r=s4gHT1L4RNICyKu%VxIgH5nk-uLOvN2 z3gyQWQO`0W7Tj}{UPweamMGRMU-th1zft+J{|C}{lE(bst8#rBzf<%>OAq~D|b zJBg_0S2g}&+Uy->C7QSG(g#2{H1w_o}E0k_gY*!R}uL$2j8hUOe zLeCF1{v}1)zM#Rlbz zy;R6=Q2r+6%RVDm&tFCwdaqIb%_>J5H_YdY8vixLM-_jg_$Ngx-RQ|t9HCfBM7_k$ zDR3TXeAZxxR# ziXBs=7dxiFQ_BBHF$u>!%ay8_tvFN>T_@*tkb<(F9EG=>l@2QwD^66zDMaLFDpn}2 zP+X-b>ucy+uXIGQP4ODV>lJTSyiJk!dbsYmQ}L^c_bEQ9__X3rh{JI{SNt!z#TG?b_ak1`{Xkjo z17*Dr-Ow;$4dTYLVydCl$Y~_+7>46kkyMPsLXi|DgCsMSk%}eJP5A6>}A7 z2aEAx#R-c1?wI_u70*%R=aJ-}uXu^#2E|rIev(Q#+I1q*4ixbg#eT&*6z@{JSMlE! zA6Dd-;7l+3WdLO#Jm8DU|GA>{HySc(dYdieFN^Q&IN6Kz_152Yy@m-&K50@dZU$ zw?pm~rTJ|p%lm<%?7s(^uOO12teCDS>wEBJUko5W-=ti*;ta*viv0eO>zg%->lB+5 zH!I4%7?9hh^c9NNC|<9~k0q(^Ud8(r4=H|K@kzyRD}GmzA67E`uN7Zc{F5TT9cKIo ziXSQR`(pC>?Ikf^u~2c0BELT5d9PM+nWF3;2>yjiU!oXMY*Xx3yj<}r#cLIBQlzai z=5vSQU5fnDk$kxx06wMk5k-FW$avXD5csOna-9I0=TgQ871I?5E9NN{D2`Q}pg39a zY{hdG7bq@LT&lQ6ajjy9Vz=U_6hEWLFE3fXFDmX=yj$^Giq9&_ei)E{LFtzjUs3$6 zqU<{eIk~O?zN7rVEB-?fua-#u$%^#iNQpQhZtQ6-9paNBIvG{k#qUouoKO zQT9CqKVNCt{}4345TyJJ#kq==in8A!;+HBd`(l7@QW`@eh5nUk>!lge=qNF#SAjfAA2reE6v% zLQf}gp5B**KFJrf=|IUw3W&$=<8bBH+EPWZrree?*@5d`5KgmgyqckO)cGC*6EzK z*7g=xBaEC|rFdy+OAB82@+jH}6Pys5U%#ZZu?O!DZon&s-8ioPNrywy!XYZp8JL9g zpVu*tt`MGKOe5mXBsUJRA^1;{n*BCA#^(201=kaGjek=WB1i z&B*rvc+8jiQI2>Jp2^*O4_@s2l+-2&wP0=fpV;``9dmI-}|AM zW6gDLzUL?8dlYhPd)K-7HYDWx3i5UPcQAzAe7jYj+s^IC@8`fh_9 z&uy-A^W6<0Z+)lDHfsUaHwWI$_mJvy^W}Zs5?2|JdC|Y5a&ErdZ;a2fT<7L{3_NeX zS0LX$2r*xrR-^g82|2d6d%W#KB3B34(eQ`=q#U10mx;!LO@rJ&)dBExQ`beUqqcN*PI~tQ^>JBT<6MtDnX9>d}X^rKv(Xz1i6pS zAjkU--tuMP2HHUNpv5OwH?u2-+$bIx>fE>^kV971Jmr6g{#^rE_U~wTcU&Dy(6=BD z_g2AkovZIR336-jjH{a+sm_)A73946uB^uK207+C7T(SGtpt5PEHclI6ZQQ$LErB% z5zm7V^^J#j^#w3M-7MK_T->9?)frY)3`z^L%CTh=SCGkG1ug- zb5&1IC|?`O*9jq(k7aSoR|7fxGfLbxJkvtfQRk{AqD!LgAmr9#F6a4m0X+3(!n2K` zFX}=wKzkCp`glxxF0z!?vw1BPo8$p^qC_6$@a}pnn^Wk-@F_( z&wR{*#ZaeKeiwe@^~`xrU1&sJO|(LuK=)%gO9Rzg#D;E`*lInidT^b6a8J$Ph4$X^ z$f8JcWN*H8hu#0#%5%k{sPDZUU3R~9o3;0%e5*f%oM<(dc2C3AzC)d7u#KDaSgt1)XgpN@jZu#U?hnA=Uu*T9C-dfA1$(OgYWqw6JvUS) zznt;Hy!_&a_vS-Wm+usA%pJ=7Mt|kIKe1=}-`mlB;&^}M&Zq6SKAOzBy>YzrvC)gK zJ2bk^_dwvmYf&#+V-3R&>!`ZP)ec(5hRxR7p`Z004jb0P>!7#S8b9=x`H^{*7eo%7 zdhdPb)DY{&;a68)u)gBy`eD@ll{ay8J$=FYy!GJ-E#(H$R=ZBU_qKB?%gT>rRvmh8 z$7k}^pLh|ePh1GvDq6EXfBowpV~5qQ%Uj=BW7-elh#MYPSNZnLNhcl~9;p~x`~FF% z^6llN!>S%W_5Rz3Prd(6@Kn~|?;n2T)O+s+Ph}VV?67kxXX;lh-`j_~vQGGua-MFk z?y0`J()Sj=aC>n=_3+{E9M2m=Y09+}{O958AAa@Zydzd|!O1|UJ?){&-yP0=(eAA7 zyr%Qn&cN2x*Tz);<#y%-^JE|s7UtKV zRdwgdVELUVo%N9EwckpfZ@>5SeCw1yyCR%cn0bnEp>lh+eabiG&XY;yISW^uEDC2= zWiRrb^84qZuFc`EREA#6T$CBf%MWnW79Bfo7d?Oc)}-_o4xc*lmUU`yVc`>pPrdh+ zgV3-i{O=u{>VNM@w>{-I$NQ%KS`O{L73K&ZRWNEK{^W;;7BAg#$`UIb+kjt$e+>R5 z_;=vBp5(RuQj=WdE5N@9emndg`2Pj}Q}}n`X=HLT{4#j%@oVPCfK6MoX}nqf6eg#6 z5Of_3&~63GAGKfmI$~hImKnf)?R><+8tpiM*js@S+l7qrCCL+nfJ2Kb&`|)@Fk-vP zi2qc;N5iAeYZNF;=Sc!~YmX2@0p9j^4kPRzHMABG3nKA{6LtCIIkd;Oqc=kXdTL?SEvtl4e2w#ZHlR&Nz{?H4I86rd= z#067eX!;nYx158F$_xAwkbE78Wq|4w!%6=z}x>v$4h9A^w*&hh!Hc&UgXEHS<#o6y>@zK-Z&G`8)INu~Uiz2%b z@P+YX|2;{fKScQN@K^xiWN6uMX6z?i4%r+IUHi>Z%-KqN7aaSc5iA-V+tl9oYcO-~ zk(lWeD?#iade-CNGzu?9ET4~Mv@^wFDRmMCupd6-D{R6?gvY(X>)`P)f%@)EVHeS* z!Q1jU$Kg9z?YR4z-Nd#%PUiMG@Gsc)59Gy-Q;w8LM#erW9)Vn} zFCoB}F06YI;I8XXYmQw@>cHHDg;KB>8fS$;iu0Z8gwKY0dS^s1{7WFcZ(cBb5~H2o z8J-kgg$AGx<_AB?yj(F}Q<lg#)NC4U&?U-cj<6?Z*V~U*rmh;g zCiQ~U^HXiqVX$LO&el{}r%7P$@tSK}m>rfxIbPFnIIz5=>Ka1BZEo$y4O^HUHT8y} z8DGa}49k-jS3@)r?bq3ZQFL^>$JE{MxEeyPz?Gwx5yl`L6OD{390U_g*9~Z3&I~CY z?Yx+Wy_!&ce}KW8l!SR225*kVGI%r=i%^`~28tE#=96sRU7vARU$sT92SwSd*hn*)vF?_1^j!h$vI9Q7IlQ9V%x(TK5IiC0r z#$0BJb;|vWIXzZwS4ym@O#@*j{D?R;D)_+|Hw8s5gh#tx0-#5w3F{@eiojV4rqt~H z4D`Xn2p!>Sc;Pc}5FUK|#++#y(zY~=`8BjO0;}R!EBfvJR0dg^5Xsfxq>$q2FxBM* zG1SPw6|0B2sy{#m%N!?31`C{_$s3^4fd=y0;Iln^dnF?Mh-5_90Ex(>&-Erki)b=N zT?s!V7QL$9j>R+X2KdCXkVl^rOV@8tcC|9*CU}q5C5Vg0Gwycyp_;nW*KfxOFmexk zT)W%>#yo`%zilw&yN!U!_PYYx*r znt(n;(TA*0(?nQXf51>23*>ZTx=4B5FD-s+8zvajUY_=0a4dWQ`mF)~AB@>*rkJhP z$bwIEPGT!kf}XR;nWRuXikL&WKL8R`oEriP6AXb;G?mxD)vD`IA=Z^GhWc&>&})!~ zRw{0jp*lG^!$%fG6Y@}Ht*Ov;lQa-f3@dW%&@p>DHaOiu1F~LcFimNfe^hBVE(3uq z_$lx_`goFXT@?bg@K?a|cxT-G@Lu;nSw6n7x3oQ^K3?FJt-A4jGJJ*}-gITAa-9Vlv6F zj5G%?i+EN1#ey9kmcp3*$(-w?lscKP^aE2|@km>i=NEnC@TSYLRx zd`KhlHF8v^UsY%70y7$u(r37A!KMuL~U zhz66gCz(X1o<+`h#N?K;@p6sT9wf)RGTx%^1A(%qLK2Ni`;f6wb+3tIm&W(mLlTjl zZA>a2ZBfBmr_)YbVYabAm(PCpr-e=T z8xv4rN4Tis48x`kGdpZ3wLKRO7Na^GTj{f%*qJ4<10ABK2nabx$*daht$&j3CZi9wn^=E~!@PT4XG@o9 zTNZf=k4_$vw7~Yi*B}$tC0rJM1czDTij1^$xeRfabzIWT&j{^=t)$xLVIZl%jQ~a@ zE^lYxTK{#3-;|MQ-Z61S!V;f(v%M%I;4V=a$MF0N@XyPT>m2gvxO|Uu?h?Ae;{x&?2`~4Ii(O8T_TvIDUOp)DD^;2oh+ap# zOY!m{k&m@w$SV&0DGBl^A|KO{k|3X!An)xlmNz{i9v_f-oF5ndG2M*FkoVcNOF~9IpGl*RcATav#h#-b zC$FR3rg(h?a$k%x>){8oSO(>yIIO#PlQ}Eu~PNSGvmew+(kap?It4K9wPMZ zBUW410V2}hLqz%zaa;@~9<;>c1<|J%j~BU~Q8}I+_K*rM2`*sZuj@hZjZ6s5k%XRp!+6o0JvJH@vY|Dh;* z1tVRaZOS!9ajK$xKZ|&=tAlpo>n5!4B}BAylhU2UV$1rh(qAN^{SGVrG?DG7^pA;! zcz2pO4UfMS-y%Xk--jii_XLR(HL^N)>@~0`z)%XP(zfAe^9W3-+NE-QE zswm&Ng1(0INXz1j9gM$|2>CB79#VW%@d+a2o>5x9bA|kK%70n;uPE|8P|EYYOnx79 zQfcl5?90P=#*H(^yWg?${X^38iO_qV;v(hq2`~8xB(6~CkSJQ3ymiQ>1!2lQT~3V?^e7|@j=DMiAeu~(mz#vMe#Moce^ z1HP=22g>*YZzeA(!TLqeaOXUAq;{lEI(;`1jV~)n-GzN$`PxRfQ+&qnEXi#w?Bf zG|K)}$XBW95gKKmD)1XME#ngCQ#DODFxF#*M*4ZVFn-HAbfAnoKpA&{kLvV28lThn zJB>XW4{Pj$i4OJk(@3W=(&IFqpmC~3dQowlm3^pyOEewQDC@_;m-XX7uniY}cjJ7Y z<8T2HgJFP(`$ahs_myfQ%Eizr^{pWy{|q8B%^_m2l<^PeKS;V5|1{z>2%R|2O1Y5B z)9C??Wg4Y?qz}`y*Z~^18qrrngxz%-#U2#WL>ee|p&l|m1I0enXFk(`K_cq4L}Q4E z^C|N^U>j-Fvt8plBIVfh*HNBY#J08>YULx!{py{`WD6~h@hl#vt(thak5MgIP(`CeBr2kXv znXz^hZ^GNFoDV|s04EDPk4WOv&oNW*40g=4dB&0DsAsYx`SN6TbWQd%T0*MxY3&$E zN3lHi1ecIUKg+4*!p?_a;Y6Q=)#_pOvxvP+SRRXQBKmkN`v315U3OgwxSh#!DQjXP zQ(wD7A8oPqwc^;R?^)0RL_65}7J}i_*9m>JhxQR|eV5|csqbCTd5Ctf^>J?N)OV*t zUnw6mvr@lW!pWS|bq)|@V5z3tU%8#LsLQZ{eIrQ;uudQ!j zf<8WbAFaOq67+oleYA`9<@vMqaqf@5Xn*E2^RuAsKskmn3HrWt=sOPSw!V`f$9mh> z(VaLTZ3kN)pCJ@>>S$LIWma_2)1f6;!ya-HXmX$C6Q=46=L*L^G`dV;N29eHslpxyr_9y6j8TvLO-3~~W z@KS=l2cgdo9`%(W+WOv0(6<)zj~Pd4XYSSJtnX8JuXP&qP~TvOK0X8DPp)l%u^aOh z?gM5Arb$HGJLN7ZGK^WakSzn`Gb82f&;_{~2ZQxs;4`MvKCV^R4nFOxK(y=2XHJ+C zJq3Qumr#{xuvHTw8Vxm&+Y2RVM%jOd`uvD2(@{NHD9vlo4)!6Yd2w>Tg?*Kd0&KbS z_}W~)N4dCa)rdggo7SuzKW@yJfP`;evpPQ@R(aLEwH*Db*l>qN?`jOXLz8xy_+N;1 zjn~wE*>z^<<4C_y8aVwzrw2cZxI>M>++8U_6X}Xy80?MscX~pl*5dFrwYlAmp=)b> z-D%;}P~Tu<$Q?9FA3tb}_U}#&^$U|TFZfBMB>1<;nW1+g6LzLGrd!#T7wb}u&}re# zwW-}rK{HT_5-UP?)TZwl8azE%9GtMbCiJ0Rw>V*EisWIA8OY)5UK~OR(-w!^;lkiT z%XI6b zNRibe`LKv=S6h8e$UOe@h*5e*k#Exm?=0}>EA^`4UQAYg0Xgw z_tV(=KY8Qf^ZWpESpQc`5!1(Rj-`L2$&4=jTSUr}JfCU4T(F-i4I zmQ>H3053bXz2L7H$v#(#|0(d2pP+170gsJj{&S@Z;W2!kPLSR;}kT zXFd^K=)`_)cJgg_>PktO_6LBfJ>E*(jsnc5vQ7c75)lKgkv1Ct%wK0y?sUYH5e;rz zjb&y7$zK1b^sf+&l;7f?*%?(k zK}g=$m9?EGQSCd32N2WYIRojFSL3(C0PJg4a$SzbV2! zJ5=#Mk>Y;=`e%q4We_-o&X9>EOzp6|Bom9X&Ij4dC0a(xGt_-q;YJ)Y3_~pBk{pNAP!{sY?0B~deY$(x;&R%R@jLNnSBmm?}Omds^N{s{1W^2CfKA@ca*pD)vt z8E1Atr{o=pCb}a6)A`wRBD*hK>IpL7 z#C~FD^D}VX2Bk5HIl|^Yw=E+Q=l zdA6L5oW`m=QVqGDjFuvBSq#>!#7aA&0n$Zc4uMifZ)w$za|skW$|@L6m#{*Npd{iN zg4#<&Vh)E{C2@o)xa4>y2xH_>?lw4z28SI=q9vN74=}5z9Rwv2X9rbt{6HxcQHcaQ zcspc+qLKt<)I2Iv9pZ@AXP;Mw%_Fd`F5^58=I{878uiR0u-;??B+zo^WRu&0qZ$KG zAOj0zVBrjGHwK%}q8R)NqW}LLdOpF0=#~Xw|HP7wqxecXKk%-6Pt+tB?cyNi8|~sL zVz1Fa+e}`xcG>cFxaFQGHSR z`b@4DN%XxEiVHtYDRL@HMQWdbD}J@aUM$IArTCrY+>$_I#Y8Rb&4A;3 zA^h#};qz=9vZQ>JI086009J{V)F%KzxZHD@1=mgB>+o3D0H^))0uasg=9h_R336q_ zZZ1m5P8#G@^l&c5&<*2WQ5DxT2`#{dBxnLIhVd3-nMDr&r z3`{qcPoUpjFd5BYdect=F^YXF;PqpTMO8xWc8F4}e8B0aB3uIIU}Pym7Ww2kdMy@~ z#IK2YLS>Tm61-+cqgY%I$R)j4b;;P5WbLoHS=bqJ)~Y3~wHNR|76zu$F_Ta z>xwoZm4Cm}$vo-;E?(Y8`e<`UPQ&@_-&HHx;MTsfea(pT*0wHQv*g?n&M)Qau|s=y zuT*{uE z^(r*gF;)S@*jy7JC(-eJ?o~^duHmYJ=xUM`tt%FzVR>SVV>`Sl*AA|i0Y3O~ z<zyZcaCka~3f^4Q69A|K2ew5*4IOa=a^V`Jn>3lm83-pnXPfv;^;)vfS zj!#dP`BZgOO5U|ZDL38>#>=OOe0fxYbKqFuw~FKC(?s5$)8RocCX6Hc$j8gaQ}mIK z=WvD^3vMGTM&O9wDs~3?z&itd+@0abZw(hXlc6usXWZG_SRQ}4js>iJdlw@0vtQGP z`q!Ff8ow3mn0VR_&~ZZ%Zv}}AH4epbk2YdChtxRXxFIMmC3btY?@M}II))T)Z@3A^ zrN-U$l%EM2nSGM}er!h<2=;tep?*ZcdvEp(`W3(xK@drX3#YS~h#y%bKhnqP^b<7J zX>8QUM_B6N8$x1GBhMS@l^Xdvko0#oUZL??jW=lgvBrBeKBVz^jjw5ZmxxaEnWiz_ zFC`BfVETO9dQh`20fY5H`XF5ibl{zaO+}aTKBZB`I`QYM!v(N z-tTFeuNSFLzN>?BY(Kq6$;UAFoR*iJ?;!VzmghdP)N@eN;!lk9k2GKW_duJtHBf&N z5%SrZ&ezzNh;l~ibn)-QaLoNoDKGxTz{Q#t|32sw|32V4&A)_*Vc+)m>(u;vH9kPZ zZRT-J|6b#38nKO~*!h9Rk2L>NjUL@^eHuApvmbMuBvxwV?^mS9Yn-I9R->g+t~?p2p1@@6>p&#``rsq48;rFKF!6_zn^M>^+T#G=8Fy69A^W z*-wFdUr#L3SgLV|M!6o5K1$Pkmrwadjm;WQ(HPXYL?hqxQ~qL&muuXh@kWjI^~m1< zC?D4Nu*SzUKB@5;jla|QqDH>;=XG^ZqgD3y?Yn-G} zu2ZDTbqYLP^Uu=QMnu1F*La~uxjwfw*u^;<+m-VKp5GcGu7epwTrUjFFV`bbu1DzMyn*^eKlCAs z=#_GymopyLKR|^3GL2F$%HceP_EAbNdtdGM7eT5LV4%1zo5Kg-M;ec zAoPpBBJ|zL`a_S5H^`4e`HqU%1wCTNKwW?6lX5{zxzO9B<>wG#e@N5Ii74lCO>ZEg zygN0$m59O~)bztdw%ZX)B9GBkC9n@VgDp1% zOdsMm$?;lZ9$xbd)Hhg;)IVG*5~kt5Q|=7V?4owC%$JV+{Iux<#-J^ z>*CQFE&$@vGHNtkjY3&3Tz2v+J>^JmMs(`?GicV$4z|8dFr4}(IP{Tk>w5~vPJKQQ z&U)DT_9p17ap=Pk5Y=}uL0=8@rGsY&yInHCcb3bwbQ~w`V9SlvayFlSY5!%PY%r;V zVK@$)^_>CxHb9Q;$9a)m-xCt_aZMBF^LDTgXD7(fM}Of_%H>?bS#B2|erj~Nd_uO% z<=Q}7H_LhnnNG6{1=?x+7N=96zrVWAupW7cwmv>9*mO&Jn_1S)RU`c3)KKiuS!Pa+4g1-IGR|6sHD?qgE^Qkiu#eAccCZi6qo3E( zWlL98CHQ&4o2xp&p#G1nt{yjT9Q?pWj~qEl_}}d3)vE@KsGnCkwl?(d+!40)Wckk}Sp?5+Hip~yZ_nH@e*7DLpbMa4VeS2DiA4Uo+ z+mEYFbZiJe9r|wgyfD9Fu(dYzmCd2j&>4kK_Pw`u)1I8ZJLadg8G*LlUkAR7u-$rh z9tU48k7ew5GW55wxg!U;y277?jZ$U7OTE6mN$pKCEkZ>l{AD+{bIBR=bkNIAT^)E@q-UeBUlPlkUJ&Vu$$;oad_ z?MkIyLxLaAH+#)VgF{) z-)p3nCGdz-%onE(MzVCfAebb(zUhkYA*CuzHW16NXcRzK=^aKlb zzuUV$+cL_`QRc`$>GC=;CaUYnWAd3J{XfyjyX>B+dn%mKIIhQh+2kq8=aH8RW>PQ2 z;h**#MBvNzT~e;(t4Mjg^1S0oUQ5cCJOXJRkJmGUr0J7~FB89lQBNNq$#a z#!EOz&ZD-B+(9@tl7C7fQwUG8tgy}!!k65HWS2j;9guGL_L9m@{vjZx1^>+FqZ^WQ z5HLIMg`(7YNg2;$)dJwH6ka`xt3u>epSEc2QfLsbvG0QZ4r1ymWHO&$Lb=U|yaT5% z!9VkbEZXn{;;+aXLf(s6lzRv90Af1sE{3^Jc=Qa485k{$WUqLa z!KnnD}6%_mYBeaCnXRV!>hX*Hy(AS0e=Qov?$(^~@G8HlPTcSud{1-%6^ zTUud@@XR@?o$i!&`W&erBWCdS8D|{FXY=x?aY2%ue8^MAU0y@u_|j$&qF+wYydgPVw#wG`3my5l$*PwD^sr&vpxgOjo+22SH+pSK};<`AK{|Qj6A4_ zGxbhbVlL~)e!(yX(d>L4%9N?gCehRp$Xbc0OkGy*G~3= zB~-dx47n2Y<%mbN8Q)cy&Cxci6GN6mR2kANUHx{@w;-w$+B}VhybAhd@}!lTn`sXA z3zur<;0K)+F{TBaIf&`!vz(S3N^v=OJ9$f3*vVqx^`Ngt%;3H9mrx7$NEdrtmc@*g zbX|@oX6Fq^&6VrW8qHQR=NMW0nR0>%$(7Pc6-wxYQCcX4ubIr@(W(+fsU+^+D-t1&mxMKLel zpV8q!i6*uK=Cqjj%^}FW!8pZE(HM0v41j?h&k1F6*jxdauMUF%MmUlYmLj^1)oPD% zrMDTZxS&dIj+K0BTr!Bhh}suS>EK&i2^cdae3K5|LzJ=Ny{3$v69iW&oFiKB58a=^ zBr;O~k=~g^rJ#kiJp&P5Zb1N}K4fhvOjLSXa3lf4Akt`V%#^vYcAFbS3L_epKn-ZZ zn4-C0#0CD4iStFMmcwXKabh4i{e~prL^*6B)X3o+LaiLO5bES`4uMT_6mLaCnna&} zDx%xt+@JDD6Pyud2q4<$gENz4 z<2;sMB%9<<0K-QzaGp`}Q&Q9)6~VCtkA3hjRz>~VfF|XS4zm!^Oax{290EHzxCEXT z$P$8bc+?Z6e&=nDX*`7*c@3f)5Q1`;cu|6hn~o&SBLw8|WCEvxD3@x2au{n*^uqvH zjCNZp>3QHtP)!uXG0wDN#r~^PDIIEzNE}fk$H`U_(X!Q5#Ey@8*(%*~pd^ndjcI(F zfw~YT%3%vZcOHW3w@27v4;^zkf-sBftUOXVj#`>sUNg%(4iTM|FhCCH1OU_kgJO!J zbup1*3)_uLF&+bS@B@`*_7(|l8FP3TK-3r5FdVwEiHW!{AH(ES5f_*692_RPA@fv{ zj{y|_=0o?74w8r717T46_dc0CEHLJN;??j&SHdDui=Z6i+wnNBu#-q+G< zM*tzBESWWs#dX3Y{RD=+ui1w&M$VT6wf0t(e8g&eul>y(mIrZC1eRE5*7Hapv^GWity} znUVg-UTg?n0Eg9VCMQ+cUdmo`ERy?q`#}WTh8G9CL#n)ku!A-HTa}M5vzgw$Nig5* zFA5|$e;!OMzP~D7tbl_rC>I@)%ftvD-A+e}S84x%H>wjZbe?*ueg`Q!?iTO(Uzm&gv|^uNug%POsNBOC2wz|UteE9x1( zIl;y<`^-q2RC$kSo_)4p8&6mYTylq^tbV9iz*|=3&8M5}h`6&;f)fgHF=;FeI{#<} zJtPL9#j?G=V%r#Y6LC-HDOWz+<`^6fV4r50vjW~h@;`5KLIs~!6@)^66}^a^XCC{M zqrF{|VN#j2T^6<+XOkD_(jQxGxQ;f25cl`@4@HsFymWY;6rf*xe{5u4>UID*ahR}b?V{za-?V469uh;STdbsLO_!jaF}K?|^P{Ed-$7$yQSV%MxVEk{@D7Dm zFV)t^9CV`^PF=kW^@Q9!Hdy<@Wy@oxObfLxK6keA?fnP6cv+>B^~rNO>pFeGm3Qv{ zO1J8NuM06}0?s|X^XywZkav08KiHlzFqwDzzejTx;7sQ+pGEAzSqJv$UZ3RHK^xEJ z(ZJb=V~_5*yg^Be&+F99>0zR7XPr2svFjATu^l+Wsm%_ophfV zfg|qG?F`@S|I8<>Sa8dekdrQn5jb)i#R+s-)L)v{I{l?7f%&ssIXEA`(dlhM zqdm`y{phJTl#bqK)E3cpx_yg?CfoPdC_~*xqolskMQL^0ijvMdRg@XMld3txk$m1^ zqg+)&!fjvAC{Vu|MDG2|y1UTn*sqCA8gVG}a*aF(E}Y_YB7Q4j?AR8dqw(SmWgyuhDp;#?2akq45!o&uDyE z;~N@3AfikEP1E9AU5Z!f=nyU-fAOL{^u-`OK%@9@f*z&m@mj7{)BO2>@{O7=ze0kh z9|idfH2)k;uhhu>8(5zFG6}iMH2*5izfsfT+lh33CYts?pwsztDEHPAA8Y8@r{({s z@jadXHzLxxa|3-1#b*{xcDJ4`_T_^FP-3 zmFCk=lyW{I?7|Nu;-gxk>ESwkq{eZY&rk1BezxY%(ASJ*J%795#`C= zt3a3`*AZtIyqN&d~TRjc02-SL1gz zUZ(MCjn`@1tnp5b_iDUf;}aU6*7$qOnq= zjC)8Qr)m1HQGTk%85-$7M}CXO(=^g^fc&)@FVfhdQN}x@U!!Rm_dv_I2mG<-->#94 z1RVFow;i}g(@$!oZve-ezi1R+cF=#-^kI$S9|Hc@ns#$M0WCi6K=E-0%6JJBA9o=A z3@A5BECJ;Uw80jZ3>Xy4b1;v8a-}B`!wcgJWivmLBV)AmNZcImD7lc#Cm-G-=>OjVEfP#{~H&Ym{*obWqdu zo?!Y~jr5!#{XLCzh2XgSGmZCY+^$i^U!*^(X&Hw>KdouHO)&rK8sE|Qp2ou(xfdYi z=u$z%XVe9YGzK&d(I|UiLvEC&Wn2b*f~Kcxq(cVv(M^FE)F|UKXgX(*{~e8V%piS} zMj59;%O2UlyEOk^jr5=3edh^{ztQ-DMtV;${cVkOkKh`JFEzR`u_c|XQTEaXJy6rK zmo{W4k_K8DPttgX#&2mnPvcsR00VxE@~p=ExYVS)0184-3pM}$3Z z6Jd|c!%_a#9FI}1jLRrb=8GtA46je0k^@Dz~AGFwya>QQHHM%^p546~a za_5r|I!HwM%Qf9bgdNvvdLt3`Y}NFAMA-GLrhiL>5$|gH10w8{`z!Q&SRbCZe{GEk z-$$y=tz+v>obMR~j2sUJxb7LQPidN@5wg7Y!;D9H&Atbb2}kFG~a zcRs`(C0RetO3MG;o0?rr!VvuTA>z`Iz-!kTY`I}z@|a^31LgQ`#txVgNT|Vor(7*) zTmsQx%h5N;DaUL6XmWg}cgigVZQFoD!`8PxL2fzdbPDKT%l#O%Q{Ot!9FOea(FeBx zG1M3}n!dTeYNtV(PdT>RFA<&k9s})cLtEbi;5+qoI`m;kkujP2UPg54>j51^w1cgW zb9Se`J01EkBt`XohGVC`IUqQW*}>NLF&IvL_c`=oNQ>(0gZxf?n?Y3D%CsEGT=1Rx z9(3sA`yjjh_-x?R*9D@D3UsjTyt%^2hDV5jk!!>R8n z=-Uq^Jii#Cqx!gJ$Ie3?2XKEl-NCRA`EJHOrrhyReuJZ<+G%{3<1Dwc1m_oWESL8q zyB=J(Yv-Z9=Wr67?P8~0kf85N+&6-dr@p?3w!V!C`X>70`<1Ql+5~;v`+2+8SLD!l zM}odvp>HY+)4|sFlLUR~6>45W`*>@!?c1jHRqOn(=3yRcU$o#+A0yvg5pB6g@Tz(i zXgk<)Pir}w??dATn5cto>J#8O&&LwjXV({}CTbtwkztH%v-6kZCFTg50NSdF4zrEZ zAQwc&!QfXSvRpqR%LF?b9!BH75~X7Lc&#|)@}T5RyD+5Lhv)J2^2*kBz2qjgxAL*p z+*FUPo)BR8<~28m*%0v8SK}I2+zd9F=G8SWm^Hn{7_?^cpfxEc1_rG;bI_8r0&T0- zvV?IHEa5`mg#kYrTBG~ySi5ItpBKb-WA(DmV2xFrhKeNao>{tXkH`N zfNZLt5zgV!$N+Xv&TceI`nGwuSMSbhA}^~kYi1y{sh(?CvQ8RYYTT6BXj+*ww$=wi zncy9axEq=8?TA?W5B%dfU_i4OnT@$S`-O7DXD%~G=XR$zm9Cos{OZyVW`ur78aZ-))+@hIzVmqHQyNXP>4($S5!G zn!fDZqCwqQBcscogLW}W0-?Tb*{ErDqkH@AcdKg(>itbaEq|k5+Qp9)|4DPvE?H=o zENPdlsHD3Q`#%2*Va?9+nI5Y$WK=eEeQVj8qS9`oysoGSQ^=i4sJwY_ckKU*!HclqrDC%$1eKKR+bcFn9+@-1(hJ#G1_HQ3rsUsTPl z?W-@4$38V|cuWqH=@&!KKtDH$8(xxsk!|gnKiD@EFOjf#uEgux6>xz76tdo$(x0<8D zR~hpI;NIxPk2_Fs92SzKe03alw?-56;qb~=7X>vq;P(Uz>TvK&9OO6S;0YYuqcSN~ z$C=Ic!90E7x)@z&rBQGi7v-&u5 z9{%C9L{E?hbyDp_#bQIck;KvltYX1^2AV$rF_16U%{~*@2?M+u%tBd^lhABNaS*}` zs%2?o34fP46Rg>Yr5Av3-LSlja$_jSe{55fG{u&_AN0M5gD9QPW@Y<+34)!IeZvAz z`UYEvO+6Oz8~es!qu>n4OW(-P!O>U9#h+i;hBqvawd>iMhobT^1C#6EQq60(jVM~A zQ<%FQ$Agc@KgQ(bE|8Hx54s8xF%`v zI><664M-ZyUu7A6Lz4#cO)_Ir|K!1okdM(VAee6sDdZOGSzamJ?_7K|rEoH4 zs7J0xieG9Jw{<#xc5`fVTC0=7>u1i&?bPSJCm9qX2Yc?OQO{45-X{V>1D#`~@039%X)A-|IctYU7fdvKx zUJ1&npGQko0%0?vO5o9cMC8DKJIHb)o8U_gBoi=@;IM_ji(0AR5eFqC5ave{cyzN$ zz<&wo<&yKL@>)g&aA_c5IM9K57+MsFULMkRXZT0tuEF(%9T#ECKjBx8ubQ{Ft{Aau z&4{X_d73(R+}5J<&JWVjNgY1?jxs~7%a`HFY1Nvl5TD!P_TPqKG6UqsIY@@Ikot@2 zPR_UamAussPV4#DzgN@zHhf*{`JkXsmnYVMgC-T7i&w2&y6kL+g5|3;O0gFX`h8SpsuSCx030`zG!an)Layuz?q-EZj!@)4 zEnBR3bh>tk|%kw(YRe*GPrgiyJgL+~5v_&!3dU~_}uk;g-IqXLpFS-%7 zK9l*0$D#(QY+!Eq>El(1i16cQnO8H#~Xvyan@4pVu;d=7J;NLpfFz zM3)Q1_R2>8N_Za-TQ#su&e^ed^ge32v6nnX+rrf=ROu|(xjq0LLS8Vau6pEZ0ci%^ zGI&{m)g$`aQdK^ACA+cl?{&YI-%gzV_W{Rkop<%Andxd5?rE8UlT$O(r()`Z`BF4+ zW>A~yzZ|~mXJq~S}0{-)td#-R_37w~; zX8LP?_q58k#!0|I|Nm{i@pc`E+^btn;Z!@gK=aWz7QFIp$0bn)o`M{Kdv!Ylec$8r z`y~J5s1O?85!}%i6N*V+_xSv2l0O*D6F;Y=uY0_FhREL;mFKTdu|Qw$^#lnLsn{S4QT~ zm1kv&sB}#09pRhz24|_U;5P6$>&2!0XoWAv#xI_c<^S3{;c%9R?zBnO$Oafi2 z<#~;Bukz@m05|*i`9WE`QMM%U4jMmxE?5w|*H%^IO9u{eWIQ=4dYhIzjEu9f35;{t z<p$hVw)JeRf+$N!Ud;S#%+`EWto zcHODV*-At?_YqOfb|UKkAQAO`n235mMyyu*^s(Mgkw(3rCBjGNw?ydgBBI_e>GULG ziSj+5FEK|WB8n2^=-uE?jYTTkx+5vVwp=qfX=-+Gl&l(SE{4b5eaILaF z6EsfO*sO7Z#^oCM`!waR)OeG|%^IKA_?kxXxrO|vq|q=zAv*OJD0{Q(i@`zJ)?7vEW=Kd9wj(D;VN z_cZ>Eh`c}Uqi&LZ?>jSC!*heOY^_2u|wn48pXF2`EJv+__D&@ zFlm(g2oZW-BchzQh`5DwGceL$YC7Me_$5TNv#bfgbt!8AlHqepKIB(w{Eo&eHEty0 z`oEQkPX7yHDeTbrgyuh^QG830F21F}gPQ+e8k1l<_4FYk1~d*OqMUI=q}ON^Us2E@ zO<$%_d_iINk4OW5s&R`(@dX9{eoa55@llOWYJ5iH?=|kz_^L+kvPJz7jqhvxSmS3J zzt$+eqmV}vOTWb_6O?fQ{X)hCAeu(_5EZP_I7*|8uaK+J^qEAo!vc*|_dX3-H_3~ukk8X7dXTS(G;k2d1LVJrhC?3S4Vu1| zNc;X7AIsINxX!-nJm=5DRakyMvTCHmGchh1e3i+*lk-M@WFBYg5n1Hke(XHz)}P?| zc;qCYYSEfCRhTn5UqaYC$8)G{Z`=f`I%-oKo3&Q8i(`)h8=I8NyUqp6R$|)%eY|kd zqSdYEJ5w>Ma*_*IFI;(c>l!EPNX~M6lg-wM#y6_)_GkrOyE=0%UCp;dRa2X1RxNDD z+pI-+Z?Fbu`yUP%x@9hlE+t`%MjOx#B^2X7kKKxldv+zumg9BJzW+^f!*rfeKn9kJ zX;3s!ALC4owtdS%-;8JnUT+Kyi2N0|29e^9x<(wa#>c$ zVaPely$I!oz+-(e97M}~0msy9>np-|vLCb^?0WnL45z-g9QrblX4m6A9J4<5`8^DM z)Ncp-_$x3HaoYOI6Xdw2k@pxo*m6@6Z5Ol)gA*!tEb$j!pZ;vCx!wp=?7 zoaHXZ9D#Bym+fPh%Qa$l9_ko?8xHRccCgddC+M3TQ1>qC+R0t(6==~-<{BhKdCRKdr^H4Ykj=N7`SQa!wLEz zVmz53_iMcNm?b&XKguw6X*oM74=438n*g#N48Mh(^L#uNh}E|k>2`fN&xbi}%)`(H zeTmmO`4EftdD47<+K7yUa9D{*eW;q0>8PHuvEb%Tj`0?Wc2;b~S8Q{YXhTAbxXu0t_zG*4N2C|la=X3M# zI8^OP8J4oYdV;^sUFkk#x(oIgrl;~yw}1B?ho*YG!_58TC-g+_IJ8iXPo3~y#0d06 z{5#9TX6Wj0*FkgCt=LW5zuO#jS@5#ZO||Y;>&qA)o^14m+-nyR!C+wz~*uQ&rYUonyn$QO8tfH=h zee+8y^XqDQ4so4eM49$4$oONp}QunW@$lJfDVt7~i{t*?G zUGpo3c9m2N>1t}I`RKipnT@Bx7ZkaT^1jGxeDA=@iOCf``^_!$dP{nYJsCI^oYUHo z<1CN!frW@mEB@4-USb^ZmC!C%Nq${8l3LOm$td|Wl2URw;vM?jYg>!D>XWR^pUkg) z;FFG3PwdYM#`3_XdXH5Q_B5847{#0F z0~S199iew;GS2X%E>CDlcpCg?+3&`@VhkPi>LVd@WOm&(=#hDK0jqcS5 z?b&Ax{q)sUJ;rq7z)Zv6F#GT?SNj{wakjE%VQ=|e={SF@awdef*FS^({NIQ?5=w16 zK4gwUuWXnQs`==GobC0GhLASBsP4sxQPy>Y{oJE|GrV^<)o%!wS}x0TsN9^_>uIb- zyr%Z^?k7SUax$3+jeGPC;kaLhVHmLv^tzSD{n{fIoaWI3qGs6 zO3yJWeS3_uf&*otk0NEEJHzCB(RFX_p{~#XbCSQ;9GKf{j+ojLNUH4#xPDmsLANpV zUdv}? z@1k_?^<#HTn>Exz-}0GdSr)#4Flo}GQyMSnS6}ye(I#90M)`YPvwKR?>WY#NnW-JU z#`fext`zR{KeT&X<3wvHS^#prsoRr!Q#z6lR5!ZOlZ^7;cU3o3!#*?ce59l@ZGUoQ z`YT%w`CKnP;Q7M!(WqK)m+z42`*mb~<&)i?M+R1I+T%Oq_3e(l-n((Tc>?}-%pa29 zYfh=$RZ;@&0cfw@54q%5N-Diw^DEt5rELMLuIPhEz%uaNhKh9=7&VeB3SYV7kk9R{ z%-{d|yY8NpoXos*e@TUj5hT5PQQJF_MWu_|-i$0Q{aa*Z>F1HvrJqIGOFxO6ZtblP z_WvWkGk|%xecOv(3HH5Z<=W-T4Qp!Cyy@6A_T=ftAUqR}J=++x91(}(&zAP>nVoK3 zw;gSa7H&CU4E9y7jy zSc9Btmv-R)(;b1zm%FbBnZ>uw3ZUN_<-hAXvvhXPFyE%xIDaYbwk;uZ^p@b8`2S|m z7+!-Mu3{y*t804OS*7kaq)mD==sR#tqZcVWPxp0wui<-go-*L!-E;lrjY&G?r(IVx z9Ex;AC4ba)S;N7IQG8rU*TJN-leQw|252ZTO20d^+Ir!jtMqtllr_oPTHmqj!p0#z zMgzRRM^#%5;PmV|pB~_&&W9xF?YovYc(*QZ^bFcmA8K4yq->dTc^`VAPqw5|oj?ufyE4p7h=-Sd?p~r-qE%PR{ z>5OSswY9%~lx5yB3H{f%=UHnG(%Wyy+q2KwVLe-aCj9L$vx;HiY1UMvEV*=V{p-Dx zFLhaIkZeF-sNOYm<|u1kgDTN$6%L>BX~F$>M{s>q_H8pqZ;KeqjPfamGw*Li+l_Cu zibl$HK%VFRq02^D7WgfYn%*ewI=cW@ak=&NXCHn=nUA&wtcU9>Lyv|RUxfZ!nYU+l zk1KU{kC~c!$TWFwryM@84Od&o;)^EjnpU*F*VwovG_P(;aCj&?5-3?;RNHnq(s%G@@OsdP6y(PH#BBQFN z8&j>G&W<-Qk_T``SmIsVkWyLGb6bbox)|#-jFL0j-hSuE@x+UM$>%IPeEs!@eDx%A zHlp(xhBV&9^Hoj4mviy4gYX+6yX_PwRHY5$f`8fsg8PBG2~Z983B z%+XpYXss0XC8nOVZKBjTEu12CEk+M$ME z${{la(NuZ{ZySyK+^|xND3canl#7<*5$-5xKk6G73PxPL=JWSPT*Zqc?q2iEzk~|= zniIcQ+q=It;92~82 z-DGYI?C95M-p&5s8!l|5qg~8YZw|X}b=_v(!uenMp1u~>h`4XdXfR6t z(o*x$=vs3V*T)2`B&?M%JB?wVcLi{Fm{{v+Fv^mVa!7wWh3$W|;A-4=&~O*j;l8wo z>4TP4$sdn1OULi@Hkl>HO##c(kX-7SiS;;^8>@EW`$X@~9IPYBndRBlyZg=1kkBh$ z_e+~Y>vjc}ZQ8~AnFseXfB1%ykvLlc>n7l297Q5ufA-baZ-)NleedP>BiBi4B*JnT zaF3jwWZkqq-AchYT6!QWWQ_Qt>$*^PucyO%$dmH;^tAmqHh4;IY+P5kslI%dr}X3C zmXHVI-z5jm?Z0Vy#=h$syd~F2>V#cppa-d5q~^*f_xSYm{Z}IQdP%R|C=Dx;)Q3GR3_uaZ<gjqFX54_EPU+`k-qK?>#jUyvm*QdhdaaBdjgKXY22=yz+!_ zPseBB=P+B}i<)e~70~jo>W1He0cwC|5ztnWYX4dpxtGMxQv?yyky6dVPzfpC?i0!OvSAES6*0OS0 z<)UBr`nRjv4iGyhR`l(5%6|RXC0}g_$J!(fQf!gux-PEtcK-B>+Bp(DdHjspui8vq?W(uX@Obkz}m3 z8!9~nPoZ(`6KjXwoll0mR=VXrykU*M($!^@@j2j9tYz~dXR=kbtlG+yZ&F=Vo4lV_ z5NEQ(mc1XzvU(x`Yxd!dt5X_M!Nd2l%3tp)Uls^^G42h?Nuu0B&czbzMEb)=_i${O z=C!Waj{cmIZdJ}c&e~*Ql85zXA(V}$hg*!{Q@e~|W4Z=6zHrcJQDqG8GB&k^UALXN z%e>7~ne8#OVRKSu_`QNrLD(_RD8sD$HrK7Wb)$oSj^tVx zjlUuAIpsL{S{d76v{hkE#q7w_bf?Ajcfoo*ZA9C}#JS713hJrS|De0cD819_Yxy4w z>apBxG>efc@{o-lx9vO*_osARoyS>5ab5}UI>s>9pNs(SB7U5O(`&P&R^~>d{NpaX z$GF>dbGhZd>AK#$>eNQuzk-`EN8*wlBTMzQIK_D__e(iSD!_I@}>SLxIOW!g1Vq^ZRX-%7PXD+a^(Rckd zDX`KXoEn-NtPS20y0jKEFe7u%t-*PrzI;m8-^`;u{+$IoQg>wU_+D*pS6V1H*c56B zy2EUrTWZt08}Z+_r#Z-0@>$YOXmc}=a$sJ_9qt><#WVMz?)7N1|BU$YM#G2i=Bc*l zb9WNX6ndeRy2ISDPoA~?c-GEpsQL637iUsR$JJIXVJZQN_{ej`;OfIGNBZUC8 zm)3mR>+&@`87#LrPY*2Q8BB`S4{vDW>c=|%t8HUn+Xdm-xLfZ%=pLiS3MYRv_^xS3 z@;5s8PWwAxe`U`9jQw{14g5Fzovo+lzp01s&^%Tc&yho34f1Q3LpI_4LDP1`%@1SF zb>*%dp)uBiU;v{;@AYa9u{}7#;v27?$b?{hus8Df^bcNX=`A<**LTUh@%?TudLMdI z*_-Hl@!@~IydGRdtY6uy#*W-ZV=(JCWAk|H&O<(**E(z0y`f>&w}Lqb2JYG##G=aa zpG2yIR`B!4uIYzf={<-?8JX36*o}A0s1w_v=PIKNckH-6jqx~I&YUpHk#?MML>u;8 z_4nIQwcrQc9^Ha(C6v>)GZS+iypJo(Jy48mjWf>PuJ=Qwcm{tmcB zm0}jUtNfg{UBAPOuWff9+zUR>=_6OCQT|bPzFeKJM&6faTFjF^HA}c-MPQLp{&x3= zk!>Mk(BNHLaQ}I|>#X2w5wo}&Z+yPO6V4l5gTm#(>3DzOwf-8}97Ibb1l;q&Zp*bU zbywdu|L&5CVO<&fGb&QLlJ?(mD8)2~dS1cXhD!55pyG>ev--Wrh>B0UYuAmaNa?<; z$ZzG~$LyDb(2AHGi15yJcO;FxMWJ)zk)Ae1Y{~q#$fuxc!Tco|=@l zdLB<3a9ZtYQ`PZ$eH;ky4f%Vpqxc>ZGcroK2P!Fy^cQZ%ds#*KF#fyHFZ|&b4nxFkF!|H=8lh2wz?J$P& ziSvoj^Pzb)xjkRIYw*N5uVI7qi<+M6JN7*gsGRZVoSrYUlJMJ_iFeZ`o^IHO%|PM6 zlTI?eFb}2H7!`-Rw$&OvS9SO+$9L8K*@O3Kmv;Cn4|f0IVA7T7Q9r1$YGx1aG3tt~ zK|RJvCt36O&d)l@8fi_MX_VJkse4+idTTcBKof8`O5z*I=@YDCYb@prW1ILkvKsS` z&GnuEXwkCWk($cdu8zuG-P>xLx~lR1@xiX4%Z#Dnu1fSloSVZ-QtpieD(~+8W#|{7 zQ^LmZJG(f4QGKvr$GFh&(8rOTUtDqcy~zIFfPW%p4$sz`Xp@bQtC61C5yr1-9r*`_ zo-+~o_DbrdNadT$ufFz$hler_7-j1s+iKIg@I7Dr@;6~4ugyPn{{rt9hjkdk-{>~W zLi${0`=hS}(c9HKKxemKe9}CunyOpdwtU&*h3)vPh|tqkV=Yi?^TKay8f(5zS-5;{ zE8Kv%U`=OgUEZ3IW1dRKTB79(7q>3J=Ra1!F=@f7Mdx5Kma%Zj62)p?WwVr~IndgU z&#f$NU5yo6tt;28UEON5k;!E}Yg!k?l{>5T+Y1_?**I~U{X>Drti{XF^grN@{cICX z#I9Hs@rB^gaKLcCfS(jx-F+q?1$&5m4Dj&S>+!M}PaXL_@56AxK#qIhQi0t>u7^JC z4blLg0Z%IhT;5vz2#+mA9%G8fdl@ND^4?T{FPWPjBzclWG}&_pGnl^r@FFvhlRbCi zzh|2qat!vlOxL@#%Hx@coStWxJP7~XgOc%YU>_El@)9JjHL2sd(X&R9vi8v zNsJQ0lX?ooJY$6LrH-e;<5O6t^wdA%zh`1vC%~UN6)oeLEGc=Z7Ha7^QSJMYidFZ9 zXR3$=VC!2Dce89yt((W5AWCz$(9SyfQ1ez<%>*TG>t1{Bxx& z^-r+Kyh{jQ>RT|`+$u!6;r=tuGTzwz9{Mu!%qla58xCYXjGx1w)!=q z(l<*nS*a|r-+FMi;DCEj6kUt@p^`;+5Z4+- z4-$FC`jq2eKALI_RH{}niuS71{%-+y)#4xeCrtD3ge6Na_pqt`GjL6tAI45AdKXUX zbx6vYh>IUd!*~{d4-1}0H2lxvqBY-6%hF?JVZpl!=40p#es1n|aX1H5r zcoD+fCfe|CV`Kj`F$2%4GU=zPl|F+oeeezc>o}X{4-zwQuV9to2dUMp12>H>Kr{w~ zc&1xZAz&Dr5zWqYoXQ*-(OOdrIdGm1L4Kbw?`byB*{L#27Q$#R;=ofOK9L#ae;$u6 zO3mL6(ZW_F-2#~(AsPcGfoc{({_o49j@0~DDA<4`Hd~m20UEFz2QK+PhJsh6W)}_t z`vnL*k673j1ij zq{{X;eS$j2#Yf~YQ;qx%q_Tn3z`|{xM&hTtyTN7YgL(7IGCXV<|2wSjqp?N{A$_Yg1gXO#o7R}199NWfzBR_);G*Qrev%_QsLD|+Jl4LBrXE+5)NKO zH2Sk=-+{i-|0EooiT;*<3J%z6dT>SOdJr5x9W2csCf4^dv_j5f82XSjg2ac=8bMU` z{b9;v)^`{Z1|zb*n^b+z1bH%9sP8SRCO3n;k}TBsR>k@Ria-S#sWx7eb0wXJEGM08XR1W=%_F4;$n#WIXJNE%M~BVaJItqY$E47=f&zv z?@P0DHI%6Oo|iJ5#8$+gkSXW)yp)O5dkBobBC@_~RDJW&##xB0Z@a2VBgiRap}uPs zYct3nl7;%7uUPMbJb)?`&1yUx9p> zEYx?7s>xxH?~;Z3&Q+`ibhs&qtZ%bo-2n1x#G}ReWON`e zq9f;-&8$Rq>1aQtE-Pge)jf(_JEQ8dmAZpqMB>$*gN&v-Q5{FA96s70X(Zdr zh5G^AFLir)mAYy$s}LR54ST z(m`O|25Wv4HSn)Mo!Os=sZ$rzhtYHLl*(C*TmzXib}=1dX%8jl z92zZim&!Q~xyHojOv7Eqye~25;%LroDrX~dHN@xSL!|k0U-an?Sy1`E0CKpr5=j#| zi&=zR-$GPMika`_rw9QmoRzbJiFN^`b z>@~xj#W0U5W|*&>#SDkGp@^!OVZO;MrWuS`b}?7r;PBro#_wZG4~kvXYFuJIRn#Eg z1QvB67;DIs8V&L_v8Z2y@c?3863jM-sHWwf=zm1)!-4t}OnsS+O)EQO^6E4yz%ky& zS{Yq?obYYz7y}Z`NQaCvV}Ls0CK#V1w=!dZZwk$r4CVwxWkx`mu@H>25XFqs0*3L$ zKVZgybJ^&H&PHDY9qQUC^bKK4tOw%~GUeJS^o^>630uLq6H(4Bvn)eA=DGD>!D6yv zwJn1Rvy&~Nipug;u&9^8_zjs-RF-cniz+`3mkwgI>+VxUC0>#7?RzRGU+*VJ@5J(? z-B}4eIdV_ zXNE2?o+Xpd9FU*!AmlRoVCIXMCj-$N$#RpQ0><%(a`)s^unz~5uli#P1LTi2puCD} zmM(;?YK{C7NX1R7-emvTmuT4ns8*KkORl12ZD54R6wCG{H_)XM-V~memz8C& zgYgQYSQc_xHVfQ;!m?92>^u{*jE}TtXJ6D)S@ukFB`x!V!R`N*WzQsAv}`6A^@wV+ z`b_cyGA{<>0>r$>`GEKvW$v9|`~*>Y1!u-@My+f%`fw;?qozRO*ru03Dy~uIu{|D5 zH2q~LQKmnt8kLL}$dpEXR5fZ2)+44Psw?%;rv5kJ1cIZ=)Tf z1l(hrdqnHHV2k;qn7L!1&+L2wN|d=jQsY1+){vGWDsz9N#(@wRixBe;@}_Z%vg}qc zZbp==jg#W&IPlotwd{y-;9IohnncUahH7QmHL4+>2jfXHr6I3T4e7(Z54%emc{^FX z>y%{^!5D)mmT`U^wd@q+KX#iPVHv-ZXI_$MnF-a(vP+a@=Yp}AOtI_|W!Y9R?m*1z zVz<6jS@tI|UP2VhIDd~?*7MI<_J7!W6ZkxXGX_}_Xn{?l@HIzcr zHeG;jv`LySltR)aX#!~yve1@6TA`FhL^ctjlB$Rzi0Gg#I5G@_iZY`9WCl=l7)BjL z6jXFzME!ri=iFyaTB@`B{Qo|4Ke;*go_p@O=bn4+xy$ps@4J=m^tLgAZ!K@ubozVm7XLIfC0HT>lSuh6DH0r2?Mh+(zaFg5;Z=Th(`IhvVWcqzDe2y*22COB05w*{+WqXH6X}%?WEh(o0 zS&I1X%|qX_=dt$-)AexD1zvhMS(si!hogs+h3V_*XmrEAFuheup5$p}Vfy9tHWlyV z^1CQqZuBd@!X7P7m*=Cg)4MhM8B$iK=iEju-^li)L)GcC9$|-a&!RC-3)BJeFdE#4 z>-bMgm#yz=bntA%xzh7#>GRohyI~go4>!oO2f37`nQqcF%48domM-TZ?O9s7oI)$) z9ww=i%&P3A1ARR?iVZL~WDQbK< zDTmg#fOr#;4|uFCsrX!jn)&OH_79T&evm7IeT1aS{Rhd#e7-XQh+-mz`wx=m(GHdY zu^4d{xqk_X_)Kn;Q#T56brNra^xDMp$tpIn8;G4mN)yi~*RqKlfVdX%Bu&70E3alR zjt67Yf~xkBOG>>ajwk1_i7$ZDXAsq=9#5`g6K@0YI+1e9KAvo)AumVOg@^^O&|V(Z zQM?+6_aIJs<#e=(s``H#p#5~Ryah$YF3jS~NPLNzw?X$T65mF&3bBC6Gg4>?*Fwp><5fv{U&Wfo%%#bE zc9fU!yp&2~Q?n2Vo2$Mf9Y0NjCfUI38I*LVY4YoSlRI58Sncml_uQbrt!}8l zZLX-VLacPRwr|a@;B2rB&#Eb2H*8l9W!eqfT^V%433nqc+>P__McdMiHGVgyd)=6Q z8hT+LOH*}S=C^Y!EhPJ~G?iVRnm&>yvd^p7$+3SqO~=Ek{0A~RsEjx!{OM);$giiv zp2V#mOcTj(fO@V#%#(pQm?k~=6%ao{9Lw)>8%svkwVWpprRDx?D&}A*(CZ-EYQ*9K zWO|V3L>x!5l5$|~K<-Awl7-0p6p0rQYgQst<{*I|sj}9A@o8I;IF3qR0pF7Ik!e6; z4dR4{Q7n%|pF!fja`;+cInVpC33(Q{z6#6gH8{Tcp}(QSpR6YJ;SlwI7~Gv9TmoWH zzYoVb6lS1=986nQ(E?Nbg`j$2If%UT++ON;?A-b0S+n&Lb#-rA?g!z;idO@D1u9;K zSiBgSqevV<97nQ}O~8B=xyKOa??A@6`L+Y|yCiYPfs)IR7zZ|E5K9gtvkZw^#G0Fs zc?<0pWr5ANm!T1oK|On^3UaZ=PeT!J%NrHW+yMwlIVCPal34t|KP~rSm__j`K=*)2 zCt~sUk+}tl8xhBmtmHSq97paU#F7Lw_Xi|?fmkyNnLLoxj78!oxSm#u1nuTL@GU7r zrVWV}#0lKl$f&-I1hrfatT(E!0>?2~X>k6i7QShyy)h}gK8CQ0z{`4Z8J^*5Wp}SN zfV-T~V|2U5Dq3rZzA%KyMG3C9?bEEHPDAv)Aw+O2LR4eTx&ujCASA*T+q)cx-+mtb zw)dp@dyADIw2ypZhBxu{q{(g2tziBE#Js;_`JS}Xj{s7v&k-q}cTd_XKHPf_i0={! z!zbkqE~ak-@f*a`@%XVjZS+1gnz*^CXbedH4NABX%~4l3H$emZ5u!$Wg;iLMQu;2I z==)bUH$mdu9Pq7A%b)}dJ&1T?Vx}i?eVVM96Tx#FyYXOB=;y5fT^$g$h@;!~^pnXd z-ly*X;zC3U*XRkiO}48a2UopiFDgKF%6cYHBI~x0tVOd>`YV)PXiDXVQ)}`GL>69n zDLNZ8?9V%Os znp_e$Z!35*0qhuEJm#Rz6vnGS`ZW-!p3jswR!&c|_W|t>*H5+E;1M3K6i7H+IjBvg z;6>1=?n^LUa9J{5Gn7bgt-;7DT8xsL=fRXr3300p%dLV7Km5f>q^Jd@STxZfIIDL+xlGuc?5$ZwmE}s7&ojXVy8Ik61>uJCCk(xOe zfGf3V_JcsRL6JKU^Hy_OjYyNt-}`{L25}Z})-Y)$}FypA~)^BqK$`IuA3 zecanX{F+EOelpp{YBMqVQxWryP|9P@e%jm`AkINlBYVWj<6bOrbG<%ue$Sp@fb0^i?2!f>@LTUWJ$eJ!pX4gCw(3z(vf9*C7qq z6Z6`}zQY;A4YJM7mWe~S!#RUnR;zOINZ_x~a^&SGW*_+;2z7zF!dXVbJoG7x{hGZO z^4UF(Zd6??gu1WqapX|35T3uskt2mQ`zFxazfCA9*Gzqz@3)CMe)pa3$A6nRU-skA zpjY-=33;=+LN>T>CveG1F>K%rnr5z zBCi7C0AgVss?g(x&ECm60vYpDD7p;H3kqqG4<<@lf%pt6eH5|wA(V`zQmi_l?QbTg z7rzM9A5r!@#6{miruYf`n~kBGjyR5VB|in`eB@Rkmi!i(7m)ZGGY)3bIS~FV#FAWO zb|A5p8P2i0k+_4IS;%|`iKm%49T^u)evi!J704_(7ysr%EG`neQBtxFh^0s@WafNi z`jO~DEUX7v4-mBa0qCYM6-Gbhr?3H4wVgc{ieUg!pf5*O&WEG7ZqCeP0hf%SjIR}|P+IjS%74NXb zG8X;*Y-H?2KKWPtEmw_%$;ePSCUYhkq-jR|WlsV}UjB-rJvK3spH!&e_t)9Qi7{-1 zE{L0j$}IHEe$F1PNE{b{#IJ2a!n|50pR*TG_IuISk0DOSZ<@WJaz2aflSJVl|2_ST z!7@}k6VaMIjeYo_tuuHHe5+3O589y{gb&)G8-x$(=NYPZ!RgrY`3=2H_^>TEm%J^w zQyZx`gIc)D&L`W7@32!xY}HREmGu#=ZcU}i_%6PTS24cNi2b?I$i%(FLcDf&qdjvf z@C)%Fh8yj9Ojf+g#pNc$rS8To*XU?#faAeLv zlQR(uKF{G=sJi+J5MLm24JU1_YNra^W+IBecm@fxcU}uFocxyqC9@!A;hnZsv<)bc>mOGTN64_%KcN}rG_l(NA_bNWQwp+QF+ozW8ZQwVMp@q^dqRpun!S0*`0!Y95y5YMI>6JS?l!a(Q<8MVVI1+c{&7$r8Cd%|-fnKI<-*ne>QE zq!pNLkIGVc*6+E@|BJ1&=|0d&-mx6o$VvsCan}{-aC%H zdm5eid&E`nY?valzjMItkHE#2wxT)ULIqC%4-Sle;3nf{9XOVp9xb`mnm}L04OSsX z(2R~(@%^}WDquGT{Kp0}KxH5KF4(O4I(4{L1eyT`Wr!lszoO9C6*?z#=a@8GpU9XI zwu(f?UaN2{NO&|=vqQ1$s6lBa1-Xgla;!8qyYw)9!Pab!qqx&$z0i`q^$dFIWtT~M zGt<_ZItK*}BRLxwRP=Uug>09)+%TGHm9($`1hZk;z8H$<{hi zA1WH5))cUnWxkQ7P;4JL1_B+$WpaMp2<0?T%Mf0EZh`IeBE4pH0`v>~3jxYuANj3c zeSuWpi>iHIb$%$wtG;$E2Vq{0*T-C`elDLgF>rHlANkA~*k$}XP#*z{4y=_GN)S_gge2}={zEp)o{&CL zUbQ|K-B^%jbd&J&5*^C(Z;-F2uMB;iX1<*tmwL$(DAhCfNw)`LxA443s z4-87k*MU&K1Ez%R7bCL(u?Tx8nI(si3EQ%hh0o5^*P)c^9F$V;wkmN627WDy=pZ?A za(Ut#n3J^@%Mg^ptvy36t@Fss3n5w&P?v&PJ7Vz^WPXIivxw`fNL;cQ|F?j!9&!B| zqSkMb|E>JrK#q4H!6U(!K(dG|7Ln;VqD#o+2+X0kF(dYiZ+2Wm%IA}XI_|-KI@17#-V#U)f%u}L?~**Kkf~l zQ7$-97Sb@d()c=tet7@HAg^s4DO(DaEC*^G1etzNcT?2G6|6K>7t0>29 z$UKI`al~AgA_>*N}sjQDWD+y%xCTjTyi1GXFki3n1MKs z=PCKjXBTo8A&%z-H7sfTJ%szM@y7`{kBi>GKQ1oSU`Y+!T<0w?_?Z3pG}7VDevEpP)}ru%8OoK=TE~v8mpJf-W7GBbUsSI5-Tl2A+K3) zeh7~dzDiN{S%uFURuI~6zJzRWdb?-}d?U)IA+r!hTyilGcR~@jBTje|CET_Z{Q(Ke zISVAg=wFJ`?1WBGw2GA;is#)!4v-1?r78Q@X$71hi(Uh|nE(D3j2}T<^fof(<3K5{ zY7!rrc1k_|za0(QZ>JS}s}7VOLQygQy^eZ6LoB`rnS!%0s)!|@LuLmO)yzDG%=^#A zM|u%Uo<`;=B%VM#jn6)L0)3s`d=;5@fc-6^b=vdD^`K}RS}XYhGS%mxuZV>;PT1t3 ziGvA6)POY51a4J|j+>H|eu-7ogzD_xHo@ucmOm85nbi}b>ov_>@8jqI_oF?25}Jo+-mTY8ZS?!bwA54()*{gAkLzJ3lBe9qn^1KR( z_b|iOjv?_`#Br=#gu#+keJh0Vr#dHP)`v56&F9j6g_I6mzV8AJEL`3px!cg_m~)|* zMQ>Ik%7^=K0bX=38U=}A1W9W{fr~kU=rrbRIE>9RqkA-?k;~*4Gf2`n1IF>5ETJBs z3Fn9Ctk2nyLM=w8eq`gfux0;9Qm7$~^#`GDygz}rX+Z%VPl46p97%2gn1m9L+oSEN zIr$@;oRXZ4MWgLpI3_%M95G@sP+&a9a&pcbv2es0BUa_(@&7c7z%>MvZ4_v6J3_#s zjA>{)Gv9V{MvTyUHn3SELu?j+enh?j1jD#W9lQjDxdX78My=j zizv636pNrhOfO!QKN{~_)Q$bnvSQXA?OIL-gc0II(XnOdV;*qqSQwW-!roZK3I#@- zaADzfSK8236^h4PeCsYCf+{&)oWbs~Nxjz+^9X7@Opnzg}aisN? z;f9QnuL`$jaI2$IMtH1=^7qWg%-*%-mYLhqdZ-kOTe(q@KiAjEWP|qCbL7UOL-ir6 zqb3`ucu(gzksP9NK7BF>(7NEi@1o~;)Y9LUdH-?375TW-8 z%|-%G=!U^S=#62a8wM%#n_7dw6S`rLscMmJBS9k~g>D#Rs!axA#5Xo zN7OhNPBq#QgCxWODDx+|JutpL}`JBszqu7tn|i?kcfo8a3buK{)+@Z zB~E`nk!?BH>@j%Iw#5>O!ZwWdLr|{CN&wvV5KDmD0L!s@5`#EJ%I`b_zY@uHia#*O zL?a^b1Wk%h401m7E1uwXNj4JPfe4ld2AQ~1@bv^o1m9~9GI10U^A7(d(B4hJGqxZX zlAju~tqn9Nx)vk}W*|CN-+@6U<|v+BRM9pNFgn&Hwl5FpRfxO25+;r!!foNd1pU4p z_C9I->H^B)1r@s!*?5V5qs~z+=?&M3ltj3BT%#}pS!kRfuneZKw`T!CjU+b`EI?Fm z&D2UQAgK2Xn4-*Z5Cr%51xO4D+I&N}`G&!u`35%cacJ<}WiV{Ykk#p%STWb63Fjc9 z4+PqWCQ5y&V4L;@>0VfB-*1lLO#_@_2_5cSX?fp&+T9427TLCq4BNwSGx z7NS}QQ(LrvpxrM(A`+BQ0|AL_>pZVGfhTClBaBC+MhK@M+SV5Gr>2p_e+drhHJqT< zX4a}hgyaOPCApcPNs{#h%GO_S(?kq#JQ>q!l2HN|%Ss_~ zlnBYqwl;h8L<1uM!{jARB89wHkv+#2h#;iM6^bRGpM)&qOy^7W%>;`jSx=yxe8UgVuM~j|vjPNWU4c zP~ra$lzBdF$>{%O4HP#<1&A2^VAO_1dd2BVO^}l0MuIv-op(t1Ap@zgh~Q43uB*u} z_7fqVDohgrtHgPRIQq5y#9u6%6hy?HfuIgC-YMNky}P1tAB=r&{jF&@=P}LwPZHmT z`5SAhzjy$JuMg%!JyAOW$wFH=N!Df@<1aW4O7-P>sfba%f6hlMb*A9P$W?C0S2Ek#+Exs+58a1T}sE zQ|j>Q3F;8R2mfOs)HAPg!yr?2h@q2UB2+IMX)a)dv?G`l35fU|x<1iR1N^V!L;*uHG7nkIS%T2|Iov#DMXzcDIe0`-X{n_gMD&|LnGRzy z9;RuTH}tQwtC= zF$h?l%%TGhqy#P$U{0W2-$)ShL*9fNItB2IPbxPMPz0Pi2)r8c25Bw@LRL=@az5&y znt&V<{?g^+Itcv{&{<#^CkVMg4#Dtim}ht5v32z2+~(DY>(8T?1H5CM0BC-RFZGS}yFf1N_soR5@qLzr{JAUTs=XeWrAAM_%=k-(Ft zQR?Wj!*@qb^+vx=B%#iso*=Obrc{dj|Pti=3~I1fef}G0!z?>NM1wxg#yE=(c?tIMtMl7(lmyp39M8`lhqNZ zH}7m`_KtKZ6bRPg$rU}@Ridy~2qFheqUsRw7}OqQLYdSfH6(-%V1dOl6><>l)rr)o z_2m@i)o&PNij{_OUTK1^Cpa-%J+(Cu#Mr3DY!px=)fgdLgf&oTDiX<{ay<}{kVFm? z1Vd#wc?%nY7UWs5isx&e6JGj@@GiyH~Fd*^u#Dde{gos8Tcd&+eW>d5C~tfo3- z`5H}FUlEE;0Yz9}piw>rNM%|4Qeq9vlefH5gZFP({p=VG^K8_k@#;FjJ%kMFJ61;d zpx3r!JStT&=^jSpND=-45jS35bD_&GFq8L<_(5WH^NRrd_MdQ%BNUqgig1rXqkN8n z0b3>fwD8=_8c!HBVXp8$p;7Sdr=eUn#<@HNG8hHH^OD?3@S-H^338;FjRe}Gts|9H)#$VCF<2q1S>8CPwhU32TrS zTpxi)6fE>&=NLhT)rhn?!e&Ga5<#yd<@FKPCj~$x@JJ160uJl&k}DB^UB)L0z~fDg&@>T{oIHm%JVuBb36$p$s~js7a&7Lh2`tK} z5F`^3s}ZRu!ZQ&eEP>JFWk}v8*ro>oj%yPCB{*s7dq~0PT6nuKc)u{6*~-CN(~hF- z(}+nTaIHZ$l*=bTT7oUx?X5`L)&&L^1EKNLUN#X-k}CBCDMTF$rsg1`OZY!b2n{n; z0pCKPyc)y28U|A+P+r4aT`QkP0@AVd#!!t$;QYW@T7W3KLMF7iVYqr*57}Jjfl_DgMNsJ%AQ1^V+okqf81vBU1s+)n2~0NvrPzX$22{`^=?#ET%uN7~Y4RHY-xG$u zEeo}Ifre(8Qc42arg`0@1xd%+dti_W<-0lHyE)+NFBEX(3}nxBIv#;$Yr7C>!h~Ij za5Drwh>q2NV2}xIu8GZk9uXvL?j21Mun|z(gW8&qFq!c18AxO!qAEeIhi8Hjcml5X z!iNb);0eZhcqUYRO$09?hW!*aG)5ZjTpe;OC#*-ruoHy)tbIG5ebXU2KTzfQKuSo6 z(NrUyY3+q>#`js{2ThrP6#laqE0NbgY}3F(NGq?qQHcqwdXOzqGfW2>YNUp4GHAkE z5b?Z}fSo2aDoP+r>V#x?m-6KbU_Cl7Z%Czij}}Jhks;|($hOeS0NTR4Q`*FPd!`*+ z!m{qeh|nhi_Zq+wJf>KJ#}!M!T?c3ho>lDg06LJq|5bMwYF=6|10jcyP~Y0FC(v!? ze@2jRfD457+H<8f70Fi!PClSg755@VK?tdC^oMF78=?udHVr^Rx(ql~9wd%>OFYe@rCs2@+1i=f@PBQJ-2aI9-+Vt{3m}x zI(yWl$C{!^9`PzQNx<0$wVp0(ajoJB79*xvJ9?gUb?H`qaF@YjNJc7`|%KEzQ7&3In3)x7pIl^4TB-{$VvvZK?(TL!N|HB0K z8-)b3Bq?uyu2w9;LB$eqUvWZ4C_|Wth-Hi*g@~)W1A|OxGYxFQ3>CrIh?(41^;%_< z?LlVQWEN398A;-pU*qBSD()ciD$7X+oNZDkY;>(j6Y8EuUb5SPEEWZVizQi4aD`$C z4k9MtqzTz1`2k2U%Y&rDZPfuoI>Lj9aES!*>Oe&ThYH#ypk1OCf!dC2TDcblmSC|Y zV1Xp%#cwvCG(l4hY>~=?wg}j&(jXxr;Uxgs zAT_`?IDkwOHX@>n1S)nDQjw5{94H7{B)JLyBZ0iVXL3xpNFMJ$4S6O9$AJ)LC*ewrC}Vbf}pyav0yIH9dWb;j6W8CRif8>Iv!)wTnzGL&QiBTqw!S z1U-mD)c~XkI%o|9b%<)wOszzO%mnKsxskwQH}o8cLJ7*SkwB$xBB(2`9KpzM3ejiN zjVV1-;b8U)L~-80h6(6g;Ohx;5!Hs7;y9y#px-ZG>Rv4%;2ovbK<0#j(-=lc;5E@8 z-fpfG)DyHIl2gMVQ>}=QkHBlYVUVe#T0r2n9gLQzR22Hp>DXW!A{{JYHzGKb7a0(Z zWH>-g!bsFL5hZ1a5T9U%BsUYxL6l7gS>3I8f}@gbAP6byA!M-|wFjAa1`(Pi_&*+= z2?_)}Qjt)OToWS1CTNl5rlSB4NV1+l>-s~U04f}gB11B{fM7ypXd?JFB6t%#E6FB; z=MjaP3HeD3tDayyq8xRZ&{pd~5el=AZb8Haz)=7daIe9= zChH;c zOYo8;Hxj%m$$Em{N^&E?E6|nlW9n5z@FNIaUh8#vJprGWzz_)dbvRm+smrCHo*jU_pJFHg9Xoi^em*J$O^*Zo zMfdYvCItKXC)z!G{{LIMe-OeSL&Vg23PA0$p6=@<#qxWsMu_p$10g|-P`;Qgq$#>_ z)q-bvnlp6>P;Uo7$dq&mX!2@=tWsK(ds5Vrz^WG^a`hp+77slF!4^mT|Cd~NjR zhY#GE#vJQsSX+U402r@HLY90HF=?HDYZ|`kf$3vjKJ--%(gon(LZsM)>Y+Dz>~x%n z9DdQFxkRT7YtY0_x`44|5}nw=qeJ>ZQiw)s1kKYGsyht-7j6eZ$bJwWM#OLu+=eK# zg^4>flNdaXMCc6yO>Q8VgD%L|2k=Yb>JY()V4Wlz`5kWi6iaXrQMfa4NHd8+enH!l zh^R`yFTEEcCZ16|!LuHoiI){mpvgvppCCH8o*iW36-1ozUjtyoIgQ{AL=l*YcN9;c zYoN50`sv`UtB6e_HsBy1UKJQ@w6~TY)BFO>p2bKqN4)6g)7b|!DGOtMlKB4R2_;|2x+g7GQAp+M^gZfMG{Xj$;HzW zicAlOfH@+)yxFq=Nwrv})@lL4I=_G^Y7K)&u*EMxVwf-wICPG{W8NU&R-nuq!ps}w zyEe!XHHP5@l(K6Kvug~oYYek%46$nrvBMl08)a34EW53&RwEa^D~i;RzIZjXFIpok zvZ*mgScz)A8ucQs*6^(lWU)XAp<)8n_9g;00W1M~q(jA2lNJ!D{x%VGB1Rn$5Pk?9 zxV4Fm1Rh^}g%EP&aKgbzNns;_GH429Yz)iT7~(~)v5<#6?2~|{lz2m^#)g1A7fEf( zze4Z|S(HM=$Pp;ji3I{lD6sOosUt)#N!IxjM#-!G8eM^8f@Nu$DXcV*tQja01J@pg z84BcPo5=+x-(uh_1A7eQxs-vfhv5MQawufd<43b$;QMO~E%IOQjG27Vg zJ`Z|w)fl>W%X{s5T|Q}g-emX0`VZ?am2qtd>qjW+*W$+k*62FiZnN3+&OT2@CU@O zJmtu91Hgr!J0dS|tl6&xh_#Gps}Au2MDBsvFK_;%*y5JuQ-}{BK4@YF_<3)gl7@k{ z^^l1kPd?rE0*?<~8|;r49tHj}#3vBnMD+UWmEVTMrxB^w7d<54Si~Ac_Jgaa7uYx2 zRy`tD5ijgR;$g%uB657Z@G26oA-;i_jrP5ehcw?%*o4UMlM9DsCR1r3+hp+O{r~!D z)rY4a&N~LrzjEvtJbii4!+Wq!sDk<0Y4RQ)PwyV&TxFnLFuYsdYj=T?`|E(0uQPZr z@5vR1ak_VFx5x1HV7tkCkaLOQsQeGsS2& z#ZOBKErdr|8a(5UfsW4pxgA|xMsTdNEmwS*^PFS5Zhn4&JKA=$leTM*ck@bJdy<=( zobKiWk)3i!CfB;7k~0KFnvu!LZgJA-u-#EkQMo%7$#T|rS>G97?&c(&i&O4sAzhnH zAzv2C6--Gb@C{_3^@R8XDBXP5PL6gnO7RWSi8MRq;+Jm;T!Q3OH&Y-R$X)GbB%Qq} zcMPhHNX`+tXQMb#3lY|%(1xgGZV_Ovb4hKywYx^LwF04Y!I`~ulcnwmTbqW!_H=M4 z0f&4yhpH$|+A)#et9paXxo$?8RxI~rPEK*>3RjV$+${jhiWKfT!oB`Yn&0bL6;$~w zQT(b>cltCp0}601E2m)j`Gq3uIwun?IG3c{d?zP`5dk3@8I^LkQ8%^j7J?MSbw-ts z&3Ezbz~yeuB#dV{aEnAudCnVGti}L3?^vtdT-2UjiIGXU6-YwJ(aELmL?lOp_s9Y& zI3@fFIOxU}D&0v)R+K~a<6YNTQ0^A8Xog$p?89(D@#I@niezQJI}c-8ya?&VP?eKI zKF*g8l;^wSL6l3|n&*}wIWqMW1)mR)fzC;VomkVeM~lTK^O&{f5tlU44HaXxzm_!s0$@C@KIDhdKFaWTqn235YyO=&VS zxtMcfVlu}aJB3pr6M}FG%&J9lIZceZ#rOl4&EyJqK9XxwZn3kw+@0?fr`$#5Zhnev z9avaa5vNYB?H0}@CM$*MW4pD<)yUcI`pK?40Ta(NVTzHNtP^o4AvFyR<3IZPl4VO4 zLzvLcrko9I5F9pk9n#hhCvIVJh`sSB{@;oDdrR$!HQO_?=BqQ{XBuEdv+R$sp z6rD8?PR*B7as-;e3@G+6%Y>r~<?%?XdRTL6|ER?LCSt*(0>Woj93KF+(<496N`m0Ja0q=|B}2K6n?-R9TQJoi(UZAm%_@fLX9R z&OBRaQkZYKPNyAr{a+GU)bd~{)t*+%As6&j2jEbynUE_>tsQ0{(m8t|gab#Y-ohDK zD`LV*&n$PV=^4sg_k1X$(9N68K{c|Ki2>TMv$^aO%n`@=5$>r{natwEsSnnAat_#ih!=G|tnM7l#kck4zFHB~P zb*BPdm@F-0!hsDT&4v_RbplvjA)XzyiQLGb9D~);s4+X;jB{Og9=1WW?U8{|(=EAT zl=Bl)ZTR1xbFi@Z6cmdO37i<`6+QbTy5CG^Z@D{x3w)u|t~A04N+>2rETmXQTxXox zAC;r-r5Zdu*OE_KS?5266%WferqM`ak|UGVNhdD_^|;Ox@Hk?Ew(~C_!~|L=Y@_7W zLpfBVKgae%j)HvjxG?2rPk?9@`OJxlEpo29!gl_k{N0TCpwR6>YJwK~9I8cLumjR^!IVo>T5tIor$K zbDT-5A&%2bwTptQ(653Dw`!qVu!#SuJlf@CpkYrbcXIjal)K!S2$ZvCQ@yhcATcK7wCkkY;3CvsB8*RJaQ!iR!so9f|h1fQ?Mnt-*{M z110|caw^dqgd5;}vYj_ChburE*JFc-8S7@RMy{?xOrNV0Sxqc;$B^-E*{_8d%T%H9 zj{?>BxgeP;vxF#2W-_j%b#Pvm$n=IQnhbx59{e0Oknen$%QNkCthA0UU=PeeLA#(0 z+{aQgEL`AVaU@b>q%XusAul85<~Z0}mN=alZ7dK-wUbR54S5{=(+DoC&Jz}PXi{lF zhTp}$2im6E$N3HE1_++T`P_j-xq6MQ zVs+z6-8}AccFSnd;i)gH6ZyS7m98F0Va{ZC(rnbKQhsDzSLN2BJxpM*teb@8hpntJ zY<2U^ogCQ*>~wb$QneLbw*m$Nei8ulU?Q=OdaPTk#F%4Tv ztcF}wrns4lImbn3;ySRMx*1*@n8|MG1ksASl`9q3S}XvyVu@HMX?WSmMvUFQR3b0p zPO)rb1IeWU^VhCOxRKKD$o`U>Bp6Cr@(>dFvCY?e;b_n`l;pyaoe?SQDR@ML7IyD=;uEY;7B`J(h#l8Y)dP| z*o&RF6X2)So%g*5`UfFT1j518+Y+xg9|#;Xlg?NuOe#1#VX5J&<=SLWbu4ybatfBS z3hVQXsI}|-y7iQ^+%;P%$)z;M`Jtym+sWrvaSsmaq=vl-Ik;VbyhFmZ@IW{21ADA2 z^v!N^cfQkRJ0&*mD8^wqcE-^7=uE`kvE_g_&ghqgn&;LFxl?OKO`dD9ez*&0xAf#Z zY2_?-$Id~%Ql@S(ToU1(#%iRjq+&cbDS^WhlPNb zCeH0w*u{|F*4=23mw3^|>0dEw5BFmsHo zIGLsH1UW&Ls+*Km%pE}|V7nW(xK-TURAMt|ujaBM2I0JdCCIZ0Y(}AuUsBW)r~`Gu zWBm!#afzzqg_Em;-9Lt%D=r;)7NYZYoo`yU^U#5eABj?SQ7PlGm*&502m6a+X9E1O zGlg;|oeNRRQ;&Fhsz>h$c+}z{{uI>1^2eWvJybUAQq3F=)=kZYCx+waF4;3+`bMr|80nY?zg76@ zhIsB|*Ph_c#q_~4SczkboIadxPH#D7itFPj!}0RAFO3Md*3AGw9ZUn=Y`B@+Rn8NG zfghkTQ{mOYih?uOR#`J?qp+*k&BQTGE^{+cw4Uo+5f?o-uOfv2PNf{Sd?a*c6;UKw zC7Z^;!4y7< zE<~sB3W2sWm6~@puGWpmY6`GgCV=#m2PfF2y9EEaZqq1m`;h|`=LB@DiHWi!;-E5p(*knaMl!aR+F*+pu zw&*T#e~{~3S{}O^IE0H4xkyu+(t9&DGYXp@-UB+=4q|b_f7rl&-WlV5OP2B>7upXB zFJqlL<#=Ts9hc{o2olA|@ue&S9Y&m5uOZSrp(pxFqvzJ7Kb$p?haN>2k>BeEe_pi*ylN+u=X38$aZD)89gQqWxJKm}UQd~NEo*kBum=bCtaw}m z)8L@+h{L&0k1NYpsoCwxGJxsCZOmA#1b9Y+n=tVw7!W*~$i=g-xO$)UgvC(+jOYMT z?mPfoI#ME?zw=}Rf=fF#%)N*t)oD;p9y_o{!xf_IU}2|Q@{d*AVg!d&V?E~Q* zSY?Y_iA=6@KG$%4a0h<0DY+B_Qpa&5w_Tttl}l7?Zfs{ZRkIP@ir!IwfDiq6=o*dt zM_kgeUwCrBW7;_B0K{8a?w%ozG6{CHW?l>w?WXM%;n5vVymWY_y2{$X&xIE8yd<_? zx!P}v8azd-{%BAs8%@k*)na*)U?p#UGL!`!ia7pZXwrOfyHN)v>4E>JxuPs~-wlEtbCGRE544 zpL(m`jI>H^!2w|oczUMY7Vs2@Sv*A59}k-M9h15z2yxP`M;D^!hX{d#fG>nT6Uv_A zmP~L9c$1HS3_hDD^6vefWzNH3d|XalO6pP)1%!HrJVbE1!_-CjEG3~C0{b$XhEU= zf2j4OB1YAERi5V~yqTaP(&c$`sDzr9X%t`JKXB68daNxxVeLErQ?);Fg4$}8|0(5u zEl@5Vlta1xe<-);WaA%I?iV8>a*ygw8C}1Ib4~vov|S)=<3=2it@{B~r5q*GZ=Q?fi=eA$^Q~Q~X ze(G4V)syz~1U9WY&uzr!un(-7-aU^rw~>*C_fPjW(&fz3z47+>7L z@ltIfuc%99R-v_OENlhNhSlyg6xFBPMs^-obpUA~Bp|UsSRmfU(b-N>u&dt8*ASeU z*jt0b=B=&w+JlS++pXk6E&^PH2Zm^c4|vq%s?FSVW%%*&YtN zfaoLsALF$G|7kKD7Y?c1x8o@W#GInWj58T}egSSZoRYhF<%RC@btl|@4BW%2RMnj&CX4ETUTxcxQ^lYJ&DhKohL0^b)uG5S$d)x>~k4s z&SFsmx{IrJ2(k!s&RNO6m`A$0xrK(YS>khOTB=LewA)Z*3!d-CO=iwj{eSz?Uc>0A#)ySsG7@!e*QH*Tx)X45THL{0? zlGUNnHL*s&Jd|uW*}gVBS)AJw7$I}I&yv*}|K%VtRZIp4{M_-eN&1~rE-@&Q6T3&j>f_w&YI8XCJ8S}mYz~Ox=9;vTW;3n&3 zqPRTG=J&&$ykwP&WBky8D_ytP*w0cJapslW#Pclc`~pv!u)_0ELm}QQz!TRL|MLxk zRc=A4Tg5%b8kq>8_XzL=KiByH+~Z{Dmz|m}cP@bATL*h~vT&Hu=8ze~X0U|d$t~nW zqvj{Px_p3T+;X;i@t|GMj>`F%-Ze@R_8?2R|E>SAURde+k7r2uTG4_ z15SAIN8k)FU;ojn6)!v*uk4W*U;mU#W4;XmW=TFn6)4ZOa1TPCJ=*kk80vjWE&ti+ zkDwi{uaMq!mm(&eg>w1NMF+1;Ob}d^wCE=JZkX+ihG2rmr5T``_XRd`@fs`W+Xw?(ARH-?~TM!`|N8+PtfKU`J=G)!W)X(A(AAv%R;kwYjxx z7Z5uKkhVIydItLOGIeifYnP>rmiM-9?{8hz)xWg2cl&;WT)JylQ|~})Q@6HZwRQLI z*)A2kcJAr1nw!twxNdpVD#+1n_4Iah^*4iON7wG=o_@UbEdnavv$uDmSv1~@*U+1H zws&;yvUUu#wejU}AzRhAZeY(2WG~&`Inb(HA{-jG@3jWH`Z{)ZweCuxC9A7-U%!P{ z%=ep^=MQ_glMKcXsT-EA2hq?4GBV zRb9|X$F2xN8KQunj&FPGz8W1^5w^KmUU~26YVJeNyV_tIJ^PLHqGQ!`&=f3bgJ@(E zN5Hh#*xJ*%eP=6;YgJd+G9m@q!R6cgS~s*}i2Cv7ztL`U2h9?i-igLBBxcOJWUTQy zY1&NdE=zl$Vrj?gSyB(1?Ck8OLU&oaJG*y4J33lQ1oIS`eEU>1dZNapJqc+7a=>ic zI=f-SO1sGz(9Z4wU+Fu#yE`q@FpR4ilVo>)yQzRSsSRKIP2FL&sI{+<=@-V=a|&+m z=x<*uyw>;h_gTH$_o{ld_r~TsySsL$+{%(Qa`K zRv%_s$G&EhQ{}@vWcqav^urgZ(Q!7))b!Q6ZlJT%XRH!dnW3##12Srn@!Hs>4uZp_ zEMon(`r%Eq(*yl&3(zX9h4Z)LvesrZCRTS_8)vLaW;HM4aO`gFZ{AMNq_v}6*=gL4 zbqPNb0x@?D^k}-Rx79P{Ref}2)=pSyb6;Sl7kBhD?-}Uq@96C4f_v!R6PnIutbGMQ z=BoM4ov`U%e)OcfnVm2_h0)1?(>M$&G)FddY_I3E4*5Ij7N&%JgvE1UeXaO}4t)gu z6#T0{hjj+2Qq{mnXD3`@4<_E8?fWp3jZ(GqI+WfDC}MAb z2|e1kYbf84eH3ix`gkQm>yd%O39RkfnEfBVk% zXvy|nyF?{46V$SOjgVom2+lRe|KUqRM4Y3>X16WI8Sh~j0 z{JBi4t2OHAYwzyum%W5PiO%9)px)oSZ17f+m93pUp}DJrFa4qsa~5+2H@fQT)|QRg zaV0Z7i5iL-wJvU2;FjRv_QJo(p1|Tq-h%EcrKbS1%18E3!*P!K0i#+BRj+IoqwD#q zK5np#V!F2P!BiU9xwEye&j@jDM^{IiEa4q=aKSK|?bSffF09hc#&!2%WIC`9<(M|Z z>Fw$0k~Q3Dgo`02Jr=si8i!BzZHFf|D^qi5O6sInqfp%k0>I#)cO7O=C1#%~qKbK$ zDX8r)$LAqo4%RvAHl~(WRi*U9jH&9%>dLCB%6X|7y_j6E>zS#!@LdCocEU_r_s#9C zLB4;Wt2K&f$6kg7g!q6Vdb_u~_hMjVn=uz3rCik4-PR8TdpsAOuCouB?Y%qO7vWQn zm!3YiH#K*6YVLYq@$tq*ySoM;)7-YMZe)8qcJ|N3{%?C9wyHq34Rr0~f;kr(UP9Dq zgW|XQd5hCu)}`hh-QGf^>Qlw>1Cy#O&jI;kAZw1Q2tE+@;2Rt~iD$x?60)tLY+bv# z7wGTaWyxypxr%Nm!8dtuq8syBiG28wX=J%In-FjQw6^ZT4njBH^es@ox1mrcEZc$T zez|RXhvz=lVLH-bm=i{`Gzo`7zuG2;9 znUhIKhpKfm$(8M@K0OGj=kXO!2VvI9VLnN#pVh@jcS9eA?dY%!)ezz*Mk0ntY@D3+E>5w zWRA6TM5I6bQi>Dxr_N&ti2FIvO&QwjWjR|CTeEVeEE|#dG{G}Zf8*;xQ&uX(;{l|h zSFaCfc3)Ob?$QzAzQo6Z{bFBaBv$8ShjrN`{Gz(74YkKF>^OnUJsl#l2p*9*%)ezA z{^9a{!<2`|;YE}$%Nc(t{p#dl_nNe8o$D|X%W^9BX61}uhM!Ok+tnQ5NTuerc;Pspq)g=V~vbQSgw^qQxI4yxKYYy#!1|GZyI>E8d2JdHzMoRrdejWavVh|i_RIL6e>rxc6 z{RM~#l(YRBL=B_7gg^(sNfjNB(bhF6Vf_}w1j<=|8={8MUP7Q_<;Uvho1cExU%pA8 zJRBU%tY~`$)_ovgdp(E=l(W4SL=EAY$S~k4CbNp#I z&Lb4)z;dGPjkjJv3EO)BF@bXOz6nu7U}?-d_p$$p9n$C?_WpKARx@a!MYFbR~if6pLl(0seEKdpZ>lqY?q zug|GoMS+erJ=WfI;lI+Sn(n8VaIBdz`k6w%%cq*@r<8tnEI%h!e~#3rjcS1{qh+7a-k2#XL*eNtQh@SLJwDD!dYHIpktjA zqd!OJgZ`cqqhA%HUlpTY6{BAhqhBNRfxlQ2qdzxBf3DDT?bL9tmk{V!>tpomW99X+ z^7>f&^bSFAti~99qtFNX4u54R?WHM3-z4Ic1O0X7H@zBV`7-VUJ;>}N+njFf|~435(868gZN15eG04u0$^O5YZvZ;SD7kI}aa zePAE$G5U*Q^cM+zV9yuD=sRQdoiYB@S`ZxkmUC48t{DHWfEX$Kd_s)AC&s@gM&BEw z?~U>AjnVhV==+6!tWVYNrlXijQ&uJ{&0-`u+RtnJshLIHb#G~&@Ehb&ecmAS z!F;(PMt@_B{>B*p8)Ni0#prK}@xLiXe@l%17NKwPsc!L8OgMO|5taY882xQA`rBjl zw+nsXb#IT+-w~s~BSwEmjQ-9T{hdM|NO5P3{z#1eNUZ!LvGR|^%I}Vq-yJI-jFtO8 z^CNVQbu?CfG*|5&X2p^RjDd)|BjeLEW0Qqomtj}8G3_W#XBOeY9z7l?Br2L_9G5Yg4`NeV~ z3lVMP!@;pWZ>==-c@ttI9}bT7*VYzO&YK4t`EYRXmG8SvIZyhwdI_qn=V_v#y(vBi1m3> zuKjx?C`8J!9<~0?l-ese6pE0(_)d5Xxk^Kd`&9+T&qmxue7_nCZ?8ivKjuLBxW?z-4 zwaMhQzx~#3lV2eGFSXud@|}|3XWbMjzs&lS$=6Bw<<^%XEj>W`G)Xk8O2zsVXz-V4+x9mKSauJv;NiO+lBsi>vxg-9aesZ_U9fcztfs*@<%0q#HuuToev+e>LU5O zt#y(7pmn~<=ScmdR(quUqt-yA{A1Q3lTQi#$E^=X%0FR!%H&ml_gY_$lz+;4I+DN7 z`d%deY3nD*dx84iG<#Y!J3pE|J(^t*&7Q$GIr$!K z_|<7i%SR~8TfXKcMUe6x@9?KBh{9|B-b;YZ&GMwQc^Bvg_;G+Bi_fJ+vi(7}Z(v7| z!G|&|@1slJi~h0Kv;7b6NV$16G|YT=sIBUb%h*SK=Um@fRTCTjCGXgKu1Z=z%*S|Q zCGz2d5YBrqGg{NbhTaXAH#dj*1Pw|27dj1N;vyf8QanBdBCj&aMF8blMFhBkHi$@oEi#$xGpGXM|O7p`uy+L1E z_q(F`laTtRXV{0|=^nmJCgAWz{Dg(yboll9aBuWoWS>m3!wMJ~PUundKB?yhJF2}` z=T-Jq-Y0xLg0>hz=#A*GTth$8sQgrgVX5OEf;AruK0qRS*~GcFyqAJ6 z7L^~mBYX+_CBh#W_=nZnX8?q0{xfl>{L7?u4}l))~0Mz?BBBHIVODu)XIA zvB`Xq5RaZ;B1C&XGWk~wlpkM3zu#aUzwq}K;bz-<$CTsZLgY##1RtJ}6X)YybV6J> z2_g4AgsAr^ zLg+P{5WmI7vsPl78Mm|aeQN5Bddj-cmLEJMeLd+aY^%rgqn~iQZS6C8{v0*sJV=NQ z{UO2~7{rWLiF=-Lg?v9Lhyfz@I1@<2SW7Y+k}wkS;BSt9v9o6!aUlU zLD*{JC*J|vFizooo!${707X8u(_(?{bR#FmMdZYIh@6lMq$1A>Lf~aQa8W1xrv*^j z>9*x4>~CaUn$>f0tk$aw`G;V+m0%`JNyz z?OztqOa8BdJnaNutgG`0?Kd$GzA|3(%y^-{EyQD7WZcj{(Z>N}f1Z5EOaE|m^Psec z@sfUBh51fC=vSp_CsK|J)svQAGqha#4gON^I^!?EzsB&FexO{&6?Q1{!wyA$*rC*W z-_Uv@ALw4g8gXe0i60X0tz(*Ffrrc}z`#bq2!4Ngl3HV6TCqAJBi; z<$m$^XQ_UmJ)QHH07U%GvG|1FH?JGjN@O+YG$O!2JduHt-h0lx^Kl zxExP~41CPMCk*@=A=>+{$vPz4QwVv{rv{sYT#!Ke9FLQ2*LlK3DLiQHSje9-!SkugrJijX9k_T zGYXiVrtOR}u*kqt15Yz>k%7FUW?H|4wwr<^}F_+Juc z!06#P$S<1^<+%oqG59jVq-|B1{327n)WCBLem!BDZCz;c7n$-d1N#jAfXQEF;0=VR zcZ-3aH2C`s{DQ&rZk%-AHuz@^{Gq}B$iUwi{M!cNJIAG8Ndrd`qQ4~u&M>gnz|{tB zF|f@*-i@=~K|UpMd>ga4k%|Ik3*m9xFq zP5##gzGd)f8QNZkfg=e)KgPfmA=aU(gpjw~z$FI1*5q3Zyu{$IF!}ok!S4%%f3U14 zP5#@2=;yNrzF_b_Hu;|#_?Ch17&szR>y0vSqJd=wRvCDjfr|;z?g|6XGx!S(Y%}m8 z1A7QTzu&+s4g4@6`tb?E-{FVPP5yC0^y5hbzi#mVX!0)@_=Qh4YcgKSYRrJZ#`22LBb4 z|GI(C8~CDuKQr)k1OH$kzO(uNYwS+oY^wi1jvt|v5khv2olJ}+TWIV=vWAcu3?_qV zjGZE~L`XuGnj#UE>>?yeB~gkjMPw=X1Viz2}~L zzvsI7a$+7VVrsr6um`h*M2FesBrptBKd+4%~?c z@jLtl&tZlLFRvcc>E^%P)O>SeS<3Ihs#p_aOx16StuevWatt(o)%!={X{MHA2ENGn z1&n{&JncA}h(9q^e;0nq_`{4pZ5~taBfaOadRZ`=srlz8E{w$}k1}=LUD4F^HL(@t zj}rGcm1nql#BrV>o@r`+bMR%#mlD5?t4vL|iSeJ9`jW&q#6OtIa~gl4{1Wlsm{A|n z*>u@VTQ2i}-bWEvF_ot})~38E@q^gf)O4L0-`m{lID?7Dn94IACsRI~cpkn&`BGEw zhnAblvmSR-ewa9;zLa9^&Tj5fA0SinDUX#Ie?M`3Y+|Z@E9{7I*vnMj0p#w8*+YETRGy=Fg7Ie=f5qJBI9c_^%i59GRQ-Y&X=?o4 zjIV00ah!(4txVN#hmSD62ji1WeO2j6;z_3JPsJIOrxGv3C6uowUXPnhXwyAvG7@vZpaSG1G#kj)Mo0!jtzcJr*oKwW- zO||PHUZwn2-Sf2aYwsM-Pay7#$)={8g0oHKOEu>^ z&fCNrOy%2*+bG{nycfSQHQgV0%~Zaux{-0y9LFhODlTOz-`!Y&^81MI#|D(QByNix zP323$ai;Q4H)lD{E5vV_n$J79hVre%AL6H!?;}2lM@;3rjM?eF-$R)ZZ-mV#Z%^C_V<}H0PQrndk0yQ^ zpD{I`mvK9OZK@sLn|mDRH{z?N+L1vww#=JN)w_fEPAo`yY2tDiO?fTi7;I>2J}oc> zpEi~MIrCF}PC>lP)clv@YRcay-j1J8{x$Id{EqUUh=0ZNrsi`Mix=?XDyG^IV{Uhx zhlo3wYDZV>L3t8!3J#_GY2t}E#Z}{%kin-o##t=_6HNWR^HsuS67vr0huP1&Fx0=fLDgK5x74qhH zyE)l$3KN$#HNShX66JM>>tSQc+Yq|`q6D8^4V$2iVh;@3^(Uy92q z-$=X#w^P1{_$xeMD&IB6=hSDFwqA;0IjoKiu{CzZ1XG{8_9q^VQ*o|2O6!?;jcMx{ zH&edLRJ{|#7g0Yw=C<#*f5ja5gS9M>@_* z;?1V=e}FqE-%Gq751X3qPsZE4ZW`t|IZdtK!lv>R#Zr`4BCd+}nVPO62#Rn*Ff$gyq#+j<0Y-&DFm?@6)Eb)t`<~tu>rTi`86}Z;abh{aU$kaWEpNKD* z%5xc=Vv04t2vg-bFc0NLiA!QR%Bz{lj?;*^HRB(~PL%f|?t@8`k02g}<0zkvui*;Z zj5{%7aj)HZuozarTG$j@V`m(M6YvFm72n2<_z51u)A%Q5)V8zrm;>`;ajb+5uoXUv zi8vg`o6Q|(Ch@DLw$CD5O8Hvi4Yv;5M=%k`nN=O&fA0d7eA2&6hskq40{8pHWj`Ke8XQt-A2fwELd*Ty#0Wah2Wxag)%xZew zF_rf|Y)yGr;-NT>@+GF)v)WA1=MKcXO|@q)eq$!+`&_2#Uom^?KGoe`oYz$Sf*5IP zd^N_`H|sb~YvM;u)$fMAC?7yP7)MY(3FqSLj9-e&DSy}0_nS5oe`IRDJMl-nshn3Y zyQ%SIu{O3~d?(`mIEwOTiRTc1W@^2BV|LelCgSs^mh&QBrTo??uRI&xVQRX2P35U; z#yU<*;?Ab>#9}<-M>GCe^AUYtig>Z9`pfVg%HJb?A3vo03*xWwAmt~Cf5Nkr+ZP$E zoe`$ylLPZmUc}V#U7ENuK_j*jyL@qAo_Z{r8J4}US$zDs5o z$GNq<7w0k6-uzgE^1F%4V`a+kC$5i8C~r&L0lS!*&*S)vsrk>tH*k%a#c{S1?=jV$ zukaw{KjY06ygd1^Bv!)vu{n0co|uB8Ox+`#N<7!pd|$?elrJY5Ux|Omi$~iBmROH@G@qq=(QsY z=EOW$1dCy`sdm;SZip?gHMTcZuQv|IDW=xPJEqpd2e=y#^PH&XPep%^KdD?jhk>Q?l9Hv19%e8V?~N@g2(dnrhDx^I^yNmH4u$<-UfQ?)B>BFjbx#^HW}uxGa{Z zyt;W$2B)^E{H-t^pTwD_>MbB%PW+{*_I+oz(R1~PFPUn`-RxOz@kmqi8;#>ApJv{#?=P6Dzl?Yt@o7`}F5%79y*L*ZF&jC~J;XIl zeL3)c%3GTa_5BaVk2UYm{X*iGOttG(e4X*@82^EpSNHdc51Xog6i+bz0^{vF&^Oi7 z{S{N!z9mf6FN1X`Z(!=Y+=}vc_$YS6UZ(0L5vSltQ~Af>49aKYEBG3&Fm+A0o%mz? z0>8%Z@ED%KvzWQ2mnW;KcI7j*e1)(imc>d~74OIT*u=E;K->-=#ctTkRQrdRy1(}{ zaVoB){3BDZSqDtD>svgDKj9_(8}r|%`f5is)-W}FZES=OU>j_YvDgFq;^R2TRJ)$R zH*h0(CDmGM5T zkIk^VsdI1w@fe&-`76Zhi9aOXh2K;D3-L8m>oep1o>@$_D<_u27;I!}xjLJgE*2kS z{1AKsXES~tzJ^P11+Kx(_yO+3-S~~E`JW*E5zkYeA;xQ0X1vYR@)X2-u_<=J$8i$A zhU;-Rp2l;SwXT=Hw7E#%qcXK#>R@ARhHbF}#+jO~m#O7Sp?nCA#;0)#PRCiMrhm&^ zqWd$HZ^vEu1s=rj@D!fGKTPGhY^pt3^crT%6M=a!KNiDMSOF_zbyIn2o66h5)c0IE z5hoKrN&GUtYwGoW6K=P@F z^Et{JNq-<&oLIN>+;Y8LcGcSeSDpS`W3Vwc!*79>QaI5`V)#@CrH^yyenY&fVqHSJ_?l6?xbESR6}Zd8~xBFb1`2xYIYocGwYP zu?G&rC-7;Ui1YCkbo+&9yR9Mq9KXZ^_$~f`r|~SF$1CXE#ByU{EP-XP0#?SFSO**7 z1K0)QurEH2Wc%d;d>vQfTKwEp|A{a00Dg<-%|i4CmDh+fX7tMQn+0@#7u|lI zirs#m(w>cE{UqvPQ+yD+n>jK#y|5o9;{;PX(`0n}pD3P1?Dj*6(0v1o}aY#g4-WO_8{(!{c!*e!zXY&PQvLp z6JN#!_y)d(YjFc^#Sif_+=B=3Tl@h}<2n2j^Xs07Eni_Qfn~4)R>qoG2ODEEbo;v~ z-^0XRFb*HXemEG1<5--4FXDV$Xx7zJ?}*ppCdxk}-h=xoKS}%(o})bH&B~{K6M3)@ zM&jLA9`D5(7=sP41-8bH*adrGA56wUXwQza_RPS!_%bfRH*poN!>{oGevc>c7d(e< z{~68iDsiS;y!mIr?3fFqFdD0)+pk7>8W1|MV=O+2V{j6>{ce3W5SPOW*u?zX zaom15vJG*2d=$H3A|~Nr9FEW73pfu`(eKBzp7?#-jyuuq-=p^YMEomWz{_~ktzP?Y z#%!1q^I;(@j-@dM{r)}8iCbcO?1bIW?f0Yh^(B5BC*Wk9fwS>tT!3%jTeulNz`eL1 ze>E@b^J2V&e`Ds`yz#eUF3gKj7>(8NKCFk0uoHI0Uf2iyenEqXXW(p1MYo@j+P#$c zZCr!z;``|K8&dt9#Jh1H9>hQJFU*kDTmH-#fjKZQ7Q|v$3hQ7!bo&vh9W994q1&HG z<*~#)a1ai|NjMc3n-_K85m(|`+=N?kC+^05==L{Kp5KW7z$@rPc=O4F`LQUL#3+o$ zX7~`c$4=M{dtzUF9Eact9E0OA6&K<%T#jpT18&6+@iW|mr|=A($BTFkGiLMF$8C5! z-i5`n3RXwApOW5ew6j=t##royeQ+QS#ZfpG-F{5U^AhoExCGt)OB%nG*zL!pco*?k z==N(;`FY}tXg?lh?aYK(F+1Lg1+XZV#3+nLw_lU;x&4}CJIXs^EcU=Y*dGVsFnkVQ z!1?I*b5j1r#LI9cuEia=6WxAKn(ip^IrCS2{}6Ae_^C~O!vvL99F=pSQA@f zTkM2gF~vNh`^5Mpj=^Wp?I)#ruM#i94d!9T*^D3JC+PN%()h24Pvg&MZSfVxj( zHq43nunMtm)%L}<*a5p@cN~f%aV$>2x#li?UW`leZTtv7#V_z{JZOE|Ctkt~cX;(PV?n$NOX1zv#C%iNYuEOE#n(kBL&+(XPL)6 z@Hqa2zv7>G2{Ya4O`ip`V=gR%Za*^3r!sLhtcwlt5p$~JJc{v{fCF$aPQmH;BF@Lx zaVf6AHMrY6qyBPu5Wm9{_#?Xg&s6^p;%s@n>2qQ}EQH0eG~SQ(@d0ds?XV*zndj6m z0Nws+ik~DNhtJ@1_yWF(Za+0mw~lxtet;k0cX$kc#9#0a{0npF2EFxT%Z+Y-HpO=l zm%_WTBHoJ+Vk_)`ov}Oi!jb6qYtwut5KqR1xESBYmG~}h!mrGI+W+wg9><^XSNsz% zq1z8m)4ToPWa0c?zM@zLqp%(}!UwSxCSYH5`^72m5aK6s49+q4=z1Gp!zJkUm(%!F z#2auk?!$xlJ)Xdy@i)AsfH%Lam=_D;y;uX?{&Sjt1L9`r_NP;MPm4cQ|5tn*2jU1E zh2wD&F2L9EEnI=?(d}oa`Fu&d55L9l@e;cI>{LHaT@^a2Srpr*Se)!^OA^SK?aSgj?|dev3b#+uu*^ zI7@sUvlmwTCOb|pEPzF@B$mZ!tb%p09yY}XF#-Ex3J$?1aST3#Q*buU!nwJME!O#y8Qx8OG1g%moE!u$6Qzd zi(pACi}kTFy8Ro~zShK@F&2AaAAA}o;&b={&cjrE9hc&J_&)B%{dg3Az$<2oEsVj&*bLiYM@%$zPuA`CCF}{r}aRY9~57F)aspUIO{4@T6f1y*-TMwBq0&`$qEQrO> z?fge|aZAIJ;J7X;N!an!}K7|wUSxm)+xD?+;zaQx*#D0I$Z;5}zU+{0V zXV=>PPymZyX)K5Juo1REw_mBI??~JQlQ0E`qulb*zQ;u`#wl zxBsm2^t8CU-k;**I1ne{RGf)(aJd<)_useyH{)04Bf9^9NANiQg;z1t-Cq4Hm>qLr zS-c0UV0CPU58=c32oAzw_!N%AXYo0ljq`8;y8U#uT$_lu;%B%Azs2wI7d(fzmh+Z3 z8;5%0xX7=w+m8MecYn2dvPB#y=jI2oVES@;URhVSAg+>RgPm$(mq#9#0N zUPiZna2DNPiqdk+!dMi`VFj#;HPP)ye21RDZ?WuzT`?I4;Yb{fGjR^Sg0G?5&scev z6Mu@IMtbw($AvVR9*cQ8B9QMXU9Dsw-?Qg8*d5U-v zPQ{rx2N&S$_!h3f4Y(OU#82=`bo(!BzTXmmk3ZqB_$Rvkn^phL^4|I>fW@#B-h&mf z2G+&~==N_`zK4k0U=Qq#V{kl9!Rh!WzJu%WJ#_m&Yd#+n@5a4&2#?@N{0XmN#tPnY z-)7cTKS1JKSb*|!#1*hA*2GTO6?D0KT* ztDf7xTDtwKrQ5$+cBEbxOu->I3diCkoQjLg%z9oHy8W>guO{A$`|$`K$Di<5{1Y!> zhDu)k%y>KAf%&m8md0{e4;x_%Y>geU3--jv@JaJ_^`pdRa0~f3ETl#3|;(`g{tX#4-2`y8Y5s?*-x)F%{i@>Keb4_-)*UU*I=*7*FC)cn<%> ztC-Ub_9`^;u?=jhB_N8rH#j*v?dc?~WLY zJ#et8{@rf>dHEFaIGk_lz1u6e7?7xx?RXHs!xQ);Ud0U6y!m9o2rP;vF$$xx z8s3KwVk_*1J#mbwYxVIs1*hYSI3E|{VqAf1@I8DVci>Jug=g@0ynq?1d+o`L5tsw> zVnM8dwXp#~$HM47Z$QiGK5szQp}Zb8#Rt)SzJTg=BpzhyT5K4O zM)&ywDxXX|4d>uX_!=%j_c;WbZY}X9+=|EX6#k08<5kRXpVzJ|7=d?U0W69ou>w}c znpg)rU}x-sy)g+>a5z4R3-EP(3s>NJd=EdwPjC-@h3@kXw7!lIyU#sP{1fqcyolE@ zV=Zs}L|_gqjpeWsR>j&_7n`8_JOs_R4{?7Sf+KJYj>joD9bd%xxCr0CcW^as!mYRu z590TD0)NKe@aEdydbka9Vjgs#m!NZPYl~$^?1H_p4<@7g+yvDhMLZTK;Z&T7b8sOp z#s4e&H=s#~EKXD|M!17oLYhZ00 zh(mD{j>Snh6=&ic^q&}#C`ZJevfDHEMCCNcvD^04`#!hm=6nKDZCpi;=T9~w!ug6 zQH;j~OhWg08rpv*5871FT9GG>v_w6E9Su5SP<{RG8l!G zu^Pr;18jy5VF&DtkK;fbiKB4>PR5sU0ltB6;rr-5hePXgC-H9l0Z-#uJdc<08s@L> zt)If^K379|%M!cK)lgi8xE97>8*Go=u@{cVN%%a@!k2LYzJYJyYFv+7a2xK!gLn*2 z;$L_bb2RXlH#e5YN>~T$VM}a_{V*Ayz^8ByzJ!bL4cv?$;3v2Xzrt_uSNt6>;oq3M zp|?Ew@GdNl-LNP2!(<$aBXJx)gU{nEdSeOH}1oOcpOjRZ}bG(1)av;`nd(OVqPqWm9Q$-!$$ZZw!$tLha=2Ix-P}>I0;|C7cmtV;#;@^ z*W-Km4IakhcnZ(qpLi8BH1XP%6|-X*jKa!T4eMeqAQ5quN};9z_LpTfyF4PV10 zxExpECfthq@F0GVC-5wu$DFz+XU})agM}~>OJg~#gjF#H8(=eh2p`5rFa?L;lQ;&S z!6`T!=izExk6UmXeu|&t*LVPr<0-s~86NP~Ll%s{JFx(k!wT34AHY`F4m)Ek_QF0m z2FK$RoQ^Nzd|Znga4UX@pWz+^l{5q(Z+c5$37W~}4vFuUvdgJw_lk2Di>|G`Yu^U%%y`d+=6 ztnZbZDf(XqIauFMG>18k9#Q0e4>_MXO3QDK(ej()wEX5o$Ej{kcAOY zXK4A&Sz3N`j+Wn?ujMyWwfyD+Ex)-)%Wp2x@|$mJ`Ascss<~3jZ?4huo9ngw=0+{Q zxmn9^Zq@Re+qL|rR>~3cQ!T%_OUrNW(ej&nZ6)ZxeOi9=fR^7ptnF_eahyo=n3mr> zq2)JEYx&JHT7L6aEx&nA%WwXnaUMkFamR6Zj40j%IL>y3eVtKJdcj;Z<@b0xvMs{t2VJ~ zKGddjW7}j_zXX=SD6EdPFa{f7OKgiBurtPD4@|-o9D*Zog4ta6jd2>zz}YwtQ*j|K z#$~t;H{urDhC6U4?#8`%1drn>JcDQPJUVt9sQsBR3r1i*EQFEhKF3bel_8G8>ZZ=a zwJ-)7U`tcywYJy+J7aHC=etBq!W0~3M(TH`Z~{)o**FhVaUr_r2~EF>cpYxU9j0FA zcA|UU$fEwY7R&vnUiS{+5mT>!$MKY@^T`=JYv$AU|M8+($Z@WqlTmS`)<0%3OE^vh z<}k~s|0?D)b^I5?NK?nR``kTQ#$p{GQ5bFN^|T6BH|uHpV2s&F{U5Q3*;LOrz?P=A zb6f0S>UFm>#+rKl?SZ{bZU00}GWB|#f?#iq3Di&K#`YBf~A`F#Wy^?l4Dc`{QnNl(s+aH^*rE;}LV5 zwm+USCu;lSS#z?sKVCF-U2p~Mg~zTBGGP{ThPFTEFlTA|V?J|^wm(Lix_&5uWz1A< ze~dO4X!~PzbCI?`#+W+Z8ekJs*BQ;RrMX<&A3K=3?&yrMrmjDFU~f~`A&Hn|>cu1l zhnTu98G)nBt=j%L!PIriWSnO1(DujKrmkD&VXCR?mxZ|4)OE}d3R2+Uz#)b_`GrmmX`VWg?+rxIAk)OA!8Mw^-RJHJ@n)OA%Yj4`w5xqR5f)OA*K zY-vVhaN1%AQ`cRcG1knX=U!rOQ`cdMm}KgDECq*{x-J`mqfA|&jl~IOAwBOCrj6-dM+Z&EoF5J~w-+pM;s9{le7w&HWwR{`&n3wb{;R_vM#5kHwmU zmEY8PYlf-w)k<@ee(%8?qw9HdoW4g>Ku*-}ftxxH4L7H1e=8_;-l<{geACICrRT1g zbM*aJQ|FbR%v3!uM(fPZBeTs#>JMF5E^(X#rp_1Hi%6X(Dw#Szxcz2zUYKa{daY+u z=Yeacj{mxM$*qnv(A=)=XYSB(V(R#=rEO!!^*g4H=d-4c<7P$WKF4|9Jm5Hc%)^?0 zF?mGKQ8JI|eUW)W)0wArd=!^D4ttuvI?fzZ$K4@Q$J-X|19qIb{hBW8{8LKt-+EtC zTIzUm`+?{#1Rj-R{kmO5@4nL1wjnmSHqm^wZ-m^v=pegirl@@Sv3QRPzxX zXTa#O`%jtQIn(>M4IFvpd&UHb0AdYW*`l+Dzd3N#kQIPIR1D#`iFF+`88> z8b8@$9l!2%i(>b>MH|JvPEowf#_M=?uS*oW*CoT$Uh?fUM{4~LyVn_`)P7?3x?&91 z3z|NMy{?SYb)cy@%ADvpO^BPDleK>krlp++4-c7VEs`J||bP z`(~h$Z^X;Z)4IMP-eKyXK0DV2k}l*CzB(@$4#A2 z&k~F+f)Fhy{j zH>F1Qw^dR;Ni9t2>NT`$QcAL|z>4K71||;I2u(UDE^%OddQ-V0!?^;nw>}!jr!-7X z?w{N;CHZ=CZ_LB-$paGl_xpGH8%F;tVT1U@_>|x(^EOAgHC~c7@hL3^_U#s*+$26R zDc)M(Z`|wSAGT$@zA?QO=Z$P>yHWdul-|wb`}Ioc{ZLZMfb<%hM%91K>v|nal=j%P zOaE!YzsK7aPP?>Szn=Y*`^KfD#P>+MLOu7ONN;RQ?W2i_{kv;N)%5OO=&coP(7JZC zv`H8i+zL023>^X2M?U24+W$Sae%yfgHacz+QW6IJdoE4l2K+yX>h|biGicl2ZRWq0 z{rYGx#ea>j-zL2pt^LFIKjsvu=g%^*Ivd2N#C7j&4SqNwu4R91&NOz@e=0Rf?%y}< zdr{MLkyi5q9;Rr>$8$Bna2H?1AFe*6BHv`zaBh)+(jDVp|6dzo9u zCnf4cp6=R9_QreX&V+uPS^u>`18M$;IW_H<&Jx~`=6_kh`tIvMi-C!W|7w!)7fb> zwKwKJCU4r$JH`L2<=*OPl-NJ+|E*abrqx)rfcBxy%PI(i%aYpZ&zJj$sn$@ zDpt~Rcvm=GyEbaqqJG<^x?=5GK057UyHHG{cE$9nr&B+jnO06ayDr(&n(4i17gtTY zxLVr9)zdDnp<-PY_K5E`uvgavT{h_*h5PTgZrzgO2L;FKf+!fqCCBya70<<-H_g9G z5)=Aq%Xni4*i~@%{(bw#`0FV7)<#bt~)o z;Mddbq#NojQk>&@ExjM)-?nao`PM1ct+wlfZl$<`ZWlRv8|lQjk>1;v)bGmZ-@2{f z=xw=U^(>yGzhFMyMsUBcA>6^wH(pUN-w5T4Rhi|pazEdz`gbs&-5V>TGJo*%y`+L* zzC+5VTZnGHtkTc7LH`csvwLeZROSzUzO^a{<~y%^kt(zHWz(OZZ>Rnp%xCx1s;kT& z{Cqo95X_gm)IaMlyT(8IBedb~LD(TM#`z*`a>kl^0 z{vM{Df4tdeTeWNigXOcKwaS9a+d%WR{odxASAYI|`;yO}uiaZKrt$t@sqJqv_5Ask z(LXZzGt+qgpA%FsINub__lU;Zd<#&|_WM^u`J!&q{Uw$AgP-rEP`Egd8NV9Rd9ovIhy zZrha4_Geo!CH3d$iw@;muFoZ6HQpcmd{Lo%Rm%Lcyrosy&(|`PFNY59A$~IIS=c<3 zuetKsdbakJ)t{fQ5BdD%{NBlZNByz zi2S@?A*-!|p5ZD9GL z_2=ia&qjjFo2q$fn5Xn^L;OrZ=v#~ zwO_3dc5kKl}NX zg!1(W4P`wf@$I@@)&`v-ep3`ujPQFG~4h zG~OTl?QuGkZ*L%9XI1v|W!Lp+aQ!7JpS9l~{CrteFSvb=E1zvo+wULMpP$cu^EH^y zev2s4PewfpD~9r&QNA)Nw|ues^Yc9%$``GZS_zH!2fux-L-}&w{m*gPO_lw8Lqqx6 zDqmXXp@E@%C6v!!-X4K`^F#T1q?7N(P`;=@zMg@6yF&R=lu!R%AN=M0B$Tg8AYbo5 zzSE(66O^pV^=#gHvG9jbzFNv>`>Ab@$Mol~zgus)ar_ z@{S0!FFBBJbSPhQL1s8ym>CG?XusZj!`k zyg&HM`+6u}E#-?;CEH)d>CazY`|ai6_GqAdY3r@{I_zZ*m}C(NMl^>9nt~>IL_|8G-gq3FNC2 z%C|qA_SFpKn-^%`v_QVtP`(JQWII3lgTH<4yF~i$`nF%Ce36Q6dra4#zkQz!-Wu-@e*5Ny@|_PX?@U$p+qX58udTj2oL0U~p?nch|LlLW z1Nn}J@@-VUg?{6xXW@}hz7on;M&-8t=IGCF-=$E#*aD7|s`37C6aUDj`)R@b)X@hH z5q>4<*DD^X7m-fAYN2}h(y8}As9p)x)2*)SgFoLcp?a0l$(IzWS0|l(6GQdvx4R;* z&(&Lh7CxTv*?uEDxc=-nztUQc_d@kXs9sv>O=3ACdJ^L;4V7-m1XZ`j3!Pbinxl}K>{yJ+p zTB_Xk)1~_J*Izl+v-PUm7w*uuuon}$_3YoZ)2L^kQ}~15ga=j6{_XESmM@22N&5AA zgzEK3r{3^Ty%g2crOx%i&-Z+&-bB^2&t?3H+$twOcdwUw_2zSeP$ zQ(Etd{lU-obg16Yo_b#K1AeW4_N&pM`4)OqpUeS1fBt-Dg!1)z$NPT0pV6Q1 zv{1e)ip s|9(&-EuWoltSSCEP5*R~^nK`zf%f_Nbo=mzdL=exa4xAW{@~a9KO(S(^#A|> diff --git a/platform/Unisoc_lib/lib/libquecsdk_in.a b/platform/Unisoc_lib/lib/libquecsdk_in.a deleted file mode 100644 index 7ec860f9fa607ac1fc8cd8a358b0d92332aca140..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 846406 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