已合并
feat:添加专用热点网关模块,支持专用热点设备配网--coap交互模块 #224
移动-吴昊创建于 27 天前
feat:添加专用热点网关模块,支持专用热点设备配网--coap交互模块 #224
已合并
共 6 个文件变更+1239-0
| @@ -0,0 +1,291 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) 2024-2026 China Mobile (Hangzhou) Information Technology Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +// mac地址每段的字符数 | ||
| 36 | + | ||
| 37 | + | ||
| 38 | + | ||
| 39 | + | ||
| 40 | +/* ===== 响应构建辅助 ===== */ | ||
| 41 | + | ||
| 42 | +/** | ||
| 43 | + * @brief 构建包含 errcode 的 JSON 响应对象 | ||
| 44 | + * @param errcode 错误码 | ||
| 45 | + * @return JSON 对象,调用方用 IotcJsonDelete 释放;失败返回 NULL | ||
| 46 | + */ | ||
| 47 | +static IotcJson* DedapBuildErrcodeJson(int32_t errcode) | ||
| 48 | +{ | ||
| 49 | + IotcJson *respJson = UtilsJsonCreateErrcode(errcode); | ||
| 50 | + if (respJson == NULL) { | ||
| 51 | + IOTC_LOGE("dedap:api create errcode resp json failed"); | ||
| 52 | + } | ||
| 53 | + return respJson; | ||
| 54 | +} | ||
| 55 | + | ||
| 56 | +/** | ||
| 57 | + * @brief 检查 MAC 地址是否为标准冒号分隔格式。 | ||
| 58 | + * @param mac 待检查的 MAC 地址字符串。 | ||
| 59 | + * @return true 表示格式合法,false 表示格式非法。 | ||
| 60 | + */ | ||
| 61 | +static bool DedapIsValidMac(const char *mac) | ||
| 62 | +{ | ||
| 63 | + if (mac == NULL || strlen(mac) != IOTC_MAC_STR_MAX_LEN - 1) { | ||
| 64 | + return false; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + for (uint32_t i = 0; i < IOTC_MAC_STR_MAX_LEN - 1; i++) { | ||
| 68 | + if ((i + 1) % IOTC_MAC_OCTET_STR_LEN == 0) { | ||
| 69 | + if (mac[i] != ':') { | ||
| 70 | + return false; | ||
| 71 | + } | ||
| 72 | + } else if (!isxdigit((unsigned char)mac[i])) { | ||
| 73 | + return false; | ||
| 74 | + } | ||
| 75 | + } | ||
| 76 | + return true; | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +/* ===== 公共解析函数 ===== */ | ||
| 80 | + | ||
| 81 | +/** | ||
| 82 | + * @brief 解析 confignet/request 请求参数 | ||
| 83 | + * @details 解析 JSON 中的 mac、connRequest、rssi,填充 IotcSubDevDiscoveredParam。 | ||
| 84 | + * 供 DedapCoapDispatchHandler 和 dedap_coap_server.c 共用。 | ||
| 85 | + * @param inData JSON 字符串 | ||
| 86 | + * @param inLen JSON 长度 | ||
| 87 | + * @param outParam 输出参数(调用方提供缓冲区) | ||
| 88 | + * @return 0 成功,非 0 失败 | ||
| 89 | + */ | ||
| 90 | +int32_t DedapParseConfignetParam(const char *inData, uint32_t inLen, IotcSubDevDiscoveredParam *outParam) | ||
| 91 | +{ | ||
| 92 | + CHECK_RETURN_LOGE(inData != NULL && inLen > 0 && outParam != NULL, | ||
| 93 | + IOTC_ERR_PARAM_INVALID, "dedap:parse confignet invalid params"); | ||
| 94 | + | ||
| 95 | + /* 解析 JSON 请求体 */ | ||
| 96 | + IotcJson *reqJson = IotcJsonParseWithLen(inData, inLen); | ||
| 97 | + if (reqJson == NULL) { | ||
| 98 | + IOTC_LOGW("dedap:parse confignet json failed"); | ||
| 99 | + return IOTC_ADAPTER_JSON_ERR_PARSE; | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + /* 提取字段 */ | ||
| 103 | + const char *mac = IotcJsonGetStr(IotcJsonGetObj(reqJson, STR_JSON_MAC)); | ||
| 104 | + const char *connRequest = IotcJsonGetStr(IotcJsonGetObj(reqJson, STR_JSON_CONN_REQUEST)); | ||
| 105 | + | ||
| 106 | + IotcJson *rssiObj = IotcJsonGetObj(reqJson, STR_JSON_RSSI); | ||
| 107 | + int64_t rssiVal = 0; | ||
| 108 | + if (rssiObj == NULL || IotcJsonGetNum(rssiObj, &rssiVal) != IOTC_OK) { | ||
| 109 | + IOTC_LOGE("dedap:confignet invalid rssi"); | ||
| 110 | + IotcJsonDelete(reqJson); | ||
| 111 | + return IOTC_ERR_PARAM_INVALID; | ||
| 112 | + } | ||
| 113 | + int32_t rssi = (int32_t)rssiVal; | ||
| 114 | + | ||
| 115 | + if (mac == NULL || connRequest == NULL) { | ||
| 116 | + IOTC_LOGE("dedap:confignet missing required fields"); | ||
| 117 | + IotcJsonDelete(reqJson); | ||
| 118 | + return IOTC_ERR_PARAM_INVALID; | ||
| 119 | + } | ||
| 120 | + if (!DedapIsValidMac(mac)) { | ||
| 121 | + IOTC_LOGE("dedap:confignet invalid mac"); | ||
| 122 | + IotcJsonDelete(reqJson); | ||
| 123 | + return IOTC_CORE_DEDAP_ERR_CONNREQUEST_INVALID; | ||
| 124 | + } | ||
| 125 | + | ||
| 126 | + IOTC_LOGI("dedap:confignet request mac=%s rssi=%d", mac, rssi); | ||
| 127 | + | ||
| 128 | + /* 解析 connRequest */ | ||
| 129 | + int32_t ret = DedapParseConnRequest(connRequest, strlen(connRequest), outParam); | ||
| 130 | + if (ret != IOTC_OK) { | ||
| 131 | + IOTC_LOGE("dedap:parse connRequest failed %d", ret); | ||
| 132 | + IotcJsonDelete(reqJson); | ||
| 133 | + return ret; | ||
| 134 | + } | ||
| 135 | + | ||
| 136 | + /* 填充 mac 和 rssi */ | ||
| 137 | + if (strncpy_s(outParam->mac, sizeof(outParam->mac), mac, sizeof(outParam->mac) - 1) != EOK) { | ||
| 138 | + IotcJsonDelete(reqJson); | ||
| 139 | + return IOTC_ERR_SECUREC_STRCPY; | ||
| 140 | + } | ||
| 141 | + outParam->rssi = rssi; | ||
| 142 | + outParam->linkType = IOTC_LINK_WIFI; | ||
| 143 | + outParam->isRegistered = false; | ||
| 144 | + | ||
| 145 | + IotcJsonDelete(reqJson); | ||
| 146 | + return IOTC_OK; | ||
| 147 | +} | ||
| 148 | + | ||
| 149 | +/* ===== URI 处理函数 ===== */ | ||
| 150 | + | ||
| 151 | +/** | ||
| 152 | + * @brief 处理 confignet/request 核心逻辑(解析 + 会话更新 + 上报) | ||
| 153 | + * @details 解析 JSON payload 中的设备信息,更新设备会话,发布 REPORT_SUBDEV 事件到 bridge。 | ||
| 154 | + * @param inData 输入 JSON 字符串 | ||
| 155 | + * @param inLen 输入数据长度 | ||
| 156 | + * @param addr 设备地址(必填) | ||
| 157 | + * @return 0 成功,非0 失败 | ||
| 158 | + */ | ||
| 159 | +int32_t DedapHandleConfignetRequest(const char *inData, uint32_t inLen, const SocketAddr *addr) | ||
| 160 | +{ | ||
| 161 | + CHECK_RETURN_LOGE(inData != NULL && inLen > 0 && addr != NULL, | ||
| 162 | + IOTC_ERR_PARAM_INVALID, "dedap:process confignet invalid params"); | ||
| 163 | + | ||
| 164 | + IotcSubDevDiscoveredParam param = {0}; | ||
| 165 | + int32_t ret = DedapParseConfignetParam(inData, inLen, ¶m); | ||
| 166 | + if (ret != IOTC_OK) { | ||
| 167 | + return ret; | ||
| 168 | + } | ||
| 169 | + | ||
| 170 | + DedapDeviceSession *sess = DedapFindSessionByMac(param.mac); | ||
| 171 | + if (sess == NULL) { | ||
| 172 | + sess = DedapAllocSession(); | ||
| 173 | + if (sess == NULL) { | ||
| 174 | + IOTC_LOGE("dedap:no free session slot for device mac=%s", param.mac); | ||
| 175 | + return IOTC_ADAPTER_MEM_ERR_MALLOC; | ||
| 176 | + } | ||
| 177 | + if (strncpy_s(sess->mac, sizeof(sess->mac), param.mac, sizeof(sess->mac) - 1) != EOK) { | ||
| 178 | + DedapFreeSession(sess); | ||
| 179 | + return IOTC_ERR_SECUREC_STRCPY; | ||
| 180 | + } | ||
| 181 | + } | ||
| 182 | + | ||
| 183 | + /* 存储从 connRequest 解析出的数据 */ | ||
| 184 | + if (strncpy_s(sess->deviceName, sizeof(sess->deviceName), | ||
| 185 | + param.deviceName, sizeof(sess->deviceName) - 1) != EOK || | ||
| 186 | + strncpy_s(sess->prodId, sizeof(sess->prodId), param.prodId, sizeof(sess->prodId) - 1) != EOK || | ||
| 187 | + strncpy_s(sess->snSuffix, sizeof(sess->snSuffix), param.snSuffix, sizeof(sess->snSuffix) - 1) != EOK) { | ||
| 188 | + IOTC_LOGE("dedap:copy confignet session data failed"); | ||
| 189 | + DedapFreeSession(sess); | ||
| 190 | + return IOTC_ERR_SECUREC_STRCPY; | ||
| 191 | + } | ||
| 192 | + sess->protoVer = (uint8_t)param.protoVer; | ||
| 193 | + sess->cfgCapability = param.cfgCapabilitySet; | ||
| 194 | + sess->p = param.p; | ||
| 195 | + | ||
| 196 | + /* 记录设备地址(用于后续 SPEKE 协商),注意:addr需要存储主机序,底层coap 收发会自动转换为对应字节序 */ | ||
| 197 | + if (memcpy_s(&sess->deviceAddr, sizeof(SocketAddr), addr, sizeof(SocketAddr)) != EOK) { | ||
| 198 | + IOTC_LOGE("dedap:copy confignet device address failed"); | ||
| 199 | + DedapFreeSession(sess); | ||
| 200 | + return IOTC_ERR_SECUREC_MEMCPY; | ||
| 201 | + } | ||
| 202 | + sess->deviceAddr.port = DEDAP_DEVICE_COAP_SERVER_PORT; | ||
| 203 | + | ||
| 204 | + IOTC_LOGI("dedap:publishing confignet request to bridge, device mac=%s", param.mac); | ||
| 205 | + DedapPublishReportSubDev(¶m, false); | ||
| 206 | + return IOTC_OK; | ||
| 207 | +} | ||
| 208 | + | ||
| 209 | +/** | ||
| 210 | + * @brief 处理 confignet/request 请求(API 路径) | ||
| 211 | + * @details 将 srcIp 转换为 SocketAddr,调用 DedapHandleConfignetRequest 完成处理。 | ||
| 212 | + * @param srcIp 请求来源 IP 地址字符串 | ||
| 213 | + * @param inData 输入 JSON 字符串 | ||
| 214 | + * @param inLen 输入数据长度 | ||
| 215 | + * @return JSON 响应对象,调用方用 IotcJsonDelete 释放;失败返回 NULL | ||
| 216 | + */ | ||
| 217 | +static IotcJson* DedapApiConfignetRequestHandler(const char *srcIp, const char *inData, uint32_t inLen) | ||
| 218 | +{ | ||
| 219 | + CHECK_RETURN_LOGE(srcIp != NULL, NULL, "dedap:api confignet srcIp is NULL"); | ||
| 220 | + | ||
| 221 | + SocketAddr addr = {0}; | ||
| 222 | + int32_t ret = IotcIpv4SstrToHost(srcIp, &addr.addr); | ||
| 223 | + if (ret != IOTC_OK) { | ||
| 224 | + IOTC_LOGE("dedap:api confignet invalid srcIp=%s[%x]", srcIp, addr.addr); | ||
| 225 | + return DedapBuildErrcodeJson(ret); | ||
| 226 | + } | ||
| 227 | + | ||
| 228 | + IOTC_LOGI("dedap:api confignet request, srcIp=%s", srcIp); | ||
| 229 | + ret = DedapHandleConfignetRequest(inData, inLen, &addr); | ||
| 230 | + return DedapBuildErrcodeJson(ret); | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +/** | ||
| 234 | + * @brief dedap 模块 CoAP 请求分发处理 | ||
| 235 | + * @details 根据 URI 路径分发到对应处理逻辑,返回 JSON 对象。 | ||
| 236 | + * 调用方决定如何使用返回值(序列化或 CoAP 发送)。 | ||
| 237 | + * 内部使用,供 DedapApiCoapRecvHandler 调用。 | ||
| 238 | + * | ||
| 239 | + * 支持的 URI 路径: | ||
| 240 | + * - STR_URI_PATH_DEDAP_CONFIGNET_REQUEST : 设备配网请求,解析 connRequest 并发布到 bridge | ||
| 241 | + * | ||
| 242 | + * @param uriPath 请求 URI 路径(不含前导 '/') | ||
| 243 | + * @param srcIp 请求来源 IP 地址字符串(当前仅用于日志) | ||
| 244 | + * @param inData 输入数据(JSON 字符串) | ||
| 245 | + * @param inLen 输入数据长度 | ||
| 246 | + * @return JSON 响应对象(调用方负责 IotcJsonDelete);未知 URI 或失败返回 NULL | ||
| 247 | + */ | ||
| 248 | +IotcJson* DedapCoapDispatchHandler(const char *uriPath, const char *srcIp, const char *inData, uint32_t inLen) | ||
| 249 | +{ | ||
| 250 | + CHECK_RETURN_LOGE(uriPath != NULL, NULL, "dedap:dispatch uriPath is NULL"); | ||
| 251 | + CHECK_RETURN_LOGE(inData != NULL, NULL, "dedap:dispatch inData is NULL"); | ||
| 252 | + | ||
| 253 | + IOTC_LOGI("[enter]DedapCoapDispatchHandler uri=%s srcIp=%s inLen=%u", | ||
| 254 | + uriPath, srcIp ? srcIp : "(null)", inLen); | ||
| 255 | + | ||
| 256 | + if (strcmp(uriPath, STR_URI_PATH_DEDAP_CONFIGNET_REQUEST) == 0) { | ||
| 257 | + return DedapApiConfignetRequestHandler(srcIp, inData, inLen); | ||
| 258 | + } | ||
| 259 | + | ||
| 260 | + IOTC_LOGW("dedap:dispatch unknown uri path: %s", uriPath); | ||
| 261 | + return NULL; | ||
| 262 | +} | ||
| 263 | + | ||
| 264 | +/** | ||
| 265 | + * @brief 外部 API 入口:接收并处理 dedap 模块 CoAP 请求,返回 JSON 字符串 | ||
| 266 | + * @details 封装 DedapCoapDispatchHandler,将返回的 JSON 对象序列化为字符串。 | ||
| 267 | + * 返回的字符串由 cJSON 分配,调用方须用 IotcJsonFreePrint 释放。 | ||
| 268 | + * | ||
| 269 | + * @param uriPath 请求 URI 路径(不含前导 '/') | ||
| 270 | + * @param srcIp 请求来源 IP 地址字符串(当前仅用于日志) | ||
| 271 | + * @param inData 输入数据(JSON 字符串) | ||
| 272 | + * @param inLen 输入数据长度 | ||
| 273 | + * @return JSON 响应字符串,调用方用 IotcJsonFreePrint 释放;失败返回 NULL | ||
| 274 | + */ | ||
| 275 | +char* DedapApiCoapRecvHandler(const char *uriPath, const char *srcIp, const char *inData, uint32_t inLen) | ||
| 276 | +{ | ||
| 277 | + IotcJson *result = DedapCoapDispatchHandler(uriPath, srcIp, inData, inLen); | ||
| 278 | + if (result == NULL) { | ||
| 279 | + return NULL; | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + char *str = IotcJsonPrint(result); | ||
| 283 | + IotcJsonDelete(result); | ||
| 284 | + | ||
| 285 | + if (str == NULL) { | ||
| 286 | + IOTC_LOGE("dedap:api print json failed"); | ||
| 287 | + } else { | ||
| 288 | + IOTC_LOGI("dedap:api resp len=%zu", strlen(str)); | ||
| 289 | + } | ||
| 290 | + return str; | ||
| 291 | +} | ||
| @@ -0,0 +1,136 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) 2024-2026 China Mobile (Hangzhou) Information Technology Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | +/** | ||
| 33 | + * @brief 构造并发送已启动pending请求的CoAP报文。 | ||
| 34 | + * @param endpoint CoAP端点。 | ||
| 35 | + * @param request 请求描述。 | ||
| 36 | + * @param sess 目标设备会话。 | ||
| 37 | + * @return 0表示发送成功,非0表示发送失败且已取消pending请求。 | ||
| 38 | + */ | ||
| 39 | +static int32_t DedapSendPendingCoapRequest(CoapEndpoint *endpoint, const DedapCoapRequest *request, | ||
| 40 | + DedapDeviceSession *sess) | ||
| 41 | +{ | ||
| 42 | + CoapOption options[] = { | ||
| 43 | + { COAP_OPTION_TYPE_URI_PATH, { (const uint8_t *)request->uri, (uint32_t)strlen(request->uri) } }, | ||
| 44 | + }; | ||
| 45 | + | ||
| 46 | + CoapData data = {0}; | ||
| 47 | + if (request->payload != NULL && request->payloadLen > 0) { | ||
| 48 | + data.data = (uint8_t *)request->payload; | ||
| 49 | + data.len = request->payloadLen; | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + CoapClientReqParam param = { | ||
| 53 | + .type = COAP_MSG_TYPE_CON, | ||
| 54 | + .code = request->code, | ||
| 55 | + .opNum = 1, | ||
| 56 | + .options = options, | ||
| 57 | + .payload = (request->payload != NULL && request->payloadLen > 0) ? &data : NULL, | ||
| 58 | + .payloadBuilder = NULL, | ||
| 59 | + .payloadUserData = NULL, | ||
| 60 | + .respHandler = request->respHandler, | ||
| 61 | + .preSize = 0, | ||
| 62 | + }; | ||
| 63 | + | ||
| 64 | + CoapPacket packet; | ||
| 65 | + int32_t ret = CoapClientSendReq(endpoint, ¶m, &sess->deviceAddr, &packet); | ||
| 66 | + if (ret != IOTC_OK) { | ||
| 67 | + DedapCancelPendingRequest(sess); | ||
| 68 | + IOTC_LOGW("dedap:coap client send req failed %d", ret); | ||
| 69 | + return ret; | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + IOTC_LOGI("dedap:coap client %s sent uri=%s", | ||
| 73 | + request->code == COAP_METHOD_TYPE_POST ? "post" : "get", request->uri); | ||
| 74 | + return IOTC_OK; | ||
| 75 | +} | ||
| 76 | + | ||
| 77 | +/** | ||
| 78 | + * @brief 启动pending请求并发送CoAP报文。 | ||
| 79 | + * @details 直接注册业务回调到CoAP端点的reqList,不使用中间映射层。 | ||
| 80 | + * @param request 请求描述。 | ||
| 81 | + * @param sess 目标设备会话。 | ||
| 82 | + * @return 0表示发送成功,非0表示参数非法、pending请求启动失败、端点未初始化或发送失败。 | ||
| 83 | + */ | ||
| 84 | +int32_t DedapCoapClientSendReq(const DedapCoapRequest *request, DedapDeviceSession *sess) | ||
| 85 | +{ | ||
| 86 | + DedapCtx *ctx = GetDedapCtx(); | ||
| 87 | + CHECK_RETURN_LOGE(ctx->initialized && ctx->coapStack.endpoint != NULL && request != NULL && | ||
| 88 | + request->uri != NULL && sess != NULL, IOTC_ERR_PARAM_INVALID, "param invalid"); | ||
| 89 | + | ||
| 90 | + int32_t ret = DedapStartPendingRequest(sess, request->request); | ||
| 91 | + if (ret != IOTC_OK) { | ||
| 92 | + return ret; | ||
| 93 | + } | ||
| 94 | + return DedapSendPendingCoapRequest(ctx->coapStack.endpoint, request, sess); | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +/** | ||
| 98 | + * @brief 校验CoAP响应并解析JSON载荷。 | ||
| 99 | + * @param resp CoAP响应报文。 | ||
| 100 | + * @param respJson 输出的JSON对象,成功后由调用方释放。 | ||
| 101 | + * @return 0成功,非0失败。 | ||
| 102 | + */ | ||
| 103 | +int32_t DedapCoapClientParseRespJson(const CoapPacket *resp, IotcJson **respJson) | ||
| 104 | +{ | ||
| 105 | + if (respJson == NULL) { | ||
| 106 | + return IOTC_ERR_PARAM_INVALID; | ||
| 107 | + } | ||
| 108 | + *respJson = NULL; | ||
| 109 | + if (resp == NULL || resp->payload.data == NULL || resp->payload.len == 0) { | ||
| 110 | + return IOTC_ERR_PARAM_INVALID; | ||
| 111 | + } | ||
| 112 | + if (COAP_CODE_CLASS(resp->header.code) != COAP_CODE_CLASS_SUCC_RESP) { | ||
| 113 | + return DedapMapCoapCodeToBusinessError(resp->header.code); | ||
| 114 | + } | ||
| 115 | + *respJson = IotcJsonParseWithLen((const char *)resp->payload.data, resp->payload.len); | ||
| 116 | + return *respJson == NULL ? IOTC_CORE_DEDAP_ERR_PROTOCOL_PARSE : IOTC_OK; | ||
| 117 | +} | ||
| 118 | + | ||
| 119 | +/** | ||
| 120 | + * @brief 获取CoAP端点指针。 | ||
| 121 | + * @return CoAP端点指针;端点尚未创建时返回NULL。 | ||
| 122 | + */ | ||
| 123 | +CoapEndpoint *DedapCoapClientGetEndpoint(void) | ||
| 124 | +{ | ||
| 125 | + return GetDedapCtx()->coapStack.endpoint; | ||
| 126 | +} | ||
| 127 | + | ||
| 128 | +/** | ||
| 129 | + * @brief 检查CoAP客户端是否已初始化。 | ||
| 130 | + * @return true表示已初始化,false表示上下文或端点尚未初始化。 | ||
| 131 | + */ | ||
| 132 | +bool DedapCoapClientIsInitialized(void) | ||
| 133 | +{ | ||
| 134 | + DedapCtx *ctx = GetDedapCtx(); | ||
| 135 | + return ctx->initialized && ctx->coapStack.endpoint != NULL; | ||
| 136 | +} | ||
| @@ -0,0 +1,178 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) 2024-2026 China Mobile (Hangzhou) Information Technology Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | +/** | ||
| 17 | + * @file dedap_coap_enc.c | ||
| 18 | + * @brief dedap CoAP payload 加解密 handler。 | ||
| 19 | + * @details 在 CoAP session 层注册收发 tail handler,自动对业务报文进行 | ||
| 20 | + * SPEKE 会话密钥加/解密,与 softap 模块保持一致的处理模式。 | ||
| 21 | + * SPEKE 协商报文自动跳过(此时 speke session 尚未创建完成)。 | ||
| 22 | + */ | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + | ||
| 36 | +/** | ||
| 37 | + * @brief 发送侧加密 handler。 | ||
| 38 | + * @details 在 CoAP 报文发出前,查找对应的设备会话,若 SPEKE 已完成则加密 payload。 | ||
| 39 | + * 加密流程:SpekeEncryptData → CoapUtilsReplacePayload 替换原始 payload。 | ||
| 40 | + * @param msg 待发送的CoAP消息。 | ||
| 41 | + * @param buf 消息编码缓冲区。 | ||
| 42 | + * @param info 包含目标设备地址的附加信息。 | ||
| 43 | + * @return SESS_CODE_CONTINUE表示继续发送,SESS_CODE_ERR表示参数、加密或载荷替换失败。 | ||
| 44 | + */ | ||
| 45 | +SessCode DedapCoapMsgSendEncryptProcess(SessMsg *msg, UtilsBuffer *buf, SessAddtlInfo *info) | ||
| 46 | +{ | ||
| 47 | + CHECK_RETURN_LOGW(msg != NULL && buf != NULL && buf->buffer != NULL && | ||
| 48 | + buf->len != 0 && buf->size >= buf->len && info != NULL && info->addr != NULL, | ||
| 49 | + SESS_CODE_ERR, "param invalid"); | ||
| 50 | + | ||
| 51 | + CoapPacket *pkt = (CoapPacket *)msg; | ||
| 52 | + | ||
| 53 | + /* 通过目标地址查找设备会话 */ | ||
| 54 | + DedapDeviceSession *sess = DedapFindSessionByAddr(info->addr); | ||
| 55 | + if (sess == NULL) { | ||
| 56 | + /* 未找到会话(可能是初始报文),跳过加密 */ | ||
| 57 | + IOTC_LOGD("dedap:encrypt handler: no session found for addr, skip encrypt"); | ||
| 58 | + return SESS_CODE_CONTINUE; | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + /* SPEKE 协商报文走明文,跳过加密(与 softap 模块 plainUri 白名单逻辑一致) */ | ||
| 62 | + char uriBuf[COAP_URI_MAX_LEN] = {0}; | ||
| 63 | + if (CoapUtilsGetUriPath(pkt, uriBuf, sizeof(uriBuf)) == IOTC_OK && | ||
| 64 | + (strcmp(uriBuf, STR_URI_SPEKE) == 0 || strcmp(uriBuf, STR_URI_SPEKE_V2) == 0)) { | ||
| 65 | + IOTC_LOGD("dedap:encrypt handler: SPEKE uri, skip encrypt"); | ||
| 66 | + return SESS_CODE_CONTINUE; | ||
| 67 | + } | ||
| 68 | + | ||
| 69 | + /* SPEKE 会话尚未创建或协商未完成,跳过加密(SPEKE 报文走明文) */ | ||
| 70 | + if (sess->speke == NULL || !sess->spekeReady) { | ||
| 71 | + IOTC_LOGD("dedap:encrypt handler: speke not ready for mac=%s, skip encrypt", sess->mac); | ||
| 72 | + return SESS_CODE_CONTINUE; | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + /* GET 等请求无 payload,无需加密 */ | ||
| 76 | + if (pkt->payload.len == 0) { | ||
| 77 | + IOTC_LOGD("dedap:encrypt handler: empty payload for mac=%s, skip encrypt", sess->mac); | ||
| 78 | + return SESS_CODE_CONTINUE; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + uint8_t *encData = NULL; | ||
| 82 | + uint32_t encLen = 0; | ||
| 83 | + uint32_t plainLen = pkt->payload.len; | ||
| 84 | + | ||
| 85 | + /* 使用 SPEKE 协商出的会话密钥加密 payload */ | ||
| 86 | + int32_t ret = SpekeEncryptData(sess->speke, pkt->payload.data, plainLen, &encData, &encLen); | ||
| 87 | + if (ret != IOTC_OK || encData == NULL) { | ||
| 88 | + IOTC_LOGE("dedap:encrypt handler: SpekeEncryptData failed %d mac=%s", ret, sess->mac); | ||
| 89 | + return SESS_CODE_ERR; | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + CoapData payloadEnc = {encData, encLen}; | ||
| 93 | + | ||
| 94 | + /* 将加密后的密文替换掉原始 CoAP 报文中的明文 payload */ | ||
| 95 | + ret = CoapUtilsReplacePayload(pkt, buf, &payloadEnc); | ||
| 96 | + IotcFree(encData); | ||
| 97 | + | ||
| 98 | + if (ret != IOTC_OK) { | ||
| 99 | + IOTC_LOGE("dedap:encrypt handler: CoapUtilsReplacePayload failed %d mac=%s", ret, sess->mac); | ||
| 100 | + return SESS_CODE_ERR; | ||
| 101 | + } | ||
| 102 | + | ||
| 103 | + IOTC_LOGI("dedap:payload encrypted, mac=%s, plain=%u -> cipher=%u", | ||
| 104 | + sess->mac, plainLen, encLen); | ||
| 105 | + return SESS_CODE_CONTINUE; | ||
| 106 | +} | ||
| 107 | + | ||
| 108 | +/** | ||
| 109 | + * @brief 接收侧解密 handler。 | ||
| 110 | + * @details 收到 CoAP 报文后,查找对应的设备会话,若 SPEKE 已完成则解密 payload。 | ||
| 111 | + * 解密流程:SpekeDecryptData → CoapUtilsReplacePayload 替换原始 payload。 | ||
| 112 | + * @param msg 收到的CoAP消息。 | ||
| 113 | + * @param buf 消息解码缓冲区。 | ||
| 114 | + * @param info 包含来源设备地址的附加信息。 | ||
| 115 | + * @return SESS_CODE_CONTINUE表示继续处理,SESS_CODE_ERR表示参数、解密或载荷替换失败。 | ||
| 116 | + */ | ||
| 117 | +SessCode DedapCoapMsgRecvDecryptProcess(SessMsg *msg, UtilsBuffer *buf, SessAddtlInfo *info) | ||
| 118 | +{ | ||
| 119 | + CHECK_RETURN_LOGW(msg != NULL && buf != NULL && buf->buffer != NULL && | ||
| 120 | + buf->len != 0 && buf->size >= buf->len && info != NULL && info->addr != NULL, | ||
| 121 | + SESS_CODE_ERR, "param invalid"); | ||
| 122 | + | ||
| 123 | + CoapPacket *pkt = (CoapPacket *)msg; | ||
| 124 | + | ||
| 125 | + /* 通过源地址查找设备会话 */ | ||
| 126 | + DedapDeviceSession *sess = DedapFindSessionByAddr(info->addr); | ||
| 127 | + if (sess == NULL) { | ||
| 128 | + IOTC_LOGD("dedap:decrypt handler: no session found for addr, skip decrypt"); | ||
| 129 | + return SESS_CODE_CONTINUE; | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + /* SPEKE 协商报文走明文,跳过解密(与 softap 模块 plainUri 白名单逻辑一致) */ | ||
| 133 | + char uriBuf[COAP_URI_MAX_LEN] = {0}; | ||
| 134 | + if (CoapUtilsGetUriPath(pkt, uriBuf, sizeof(uriBuf)) == IOTC_OK && | ||
| 135 | + (strcmp(uriBuf, STR_URI_SPEKE) == 0 || strcmp(uriBuf, STR_URI_SPEKE_V2) == 0)) { | ||
| 136 | + IOTC_LOGD("dedap:decrypt handler: SPEKE uri, skip decrypt"); | ||
| 137 | + return SESS_CODE_CONTINUE; | ||
| 138 | + } | ||
| 139 | + | ||
| 140 | + /* SPEKE 会话尚未创建或协商未完成,跳过解密 */ | ||
| 141 | + if (sess->speke == NULL || !sess->spekeReady) { | ||
| 142 | + IOTC_LOGD("dedap:decrypt handler: speke not ready for mac=%s, skip decrypt", sess->mac); | ||
| 143 | + return SESS_CODE_CONTINUE; | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + /* 响应无 payload(如 ACK),无需解密 */ | ||
| 147 | + if (pkt->payload.len == 0) { | ||
| 148 | + IOTC_LOGD("dedap:decrypt handler: empty payload for mac=%s, skip decrypt", sess->mac); | ||
| 149 | + return SESS_CODE_CONTINUE; | ||
| 150 | + } | ||
| 151 | + | ||
| 152 | + uint8_t *decData = NULL; | ||
| 153 | + uint32_t decLen = 0; | ||
| 154 | + uint32_t cipherLen = pkt->payload.len; | ||
| 155 | + | ||
| 156 | + /* 使用 SPEKE 协商出的会话密钥解密 payload */ | ||
| 157 | + int32_t ret = SpekeDecryptData(sess->speke, pkt->payload.data, cipherLen, &decData, &decLen); | ||
| 158 | + if (ret != IOTC_OK || decData == NULL || decLen == 0) { | ||
| 159 | + IOTC_LOGE("dedap:decrypt handler: SpekeDecryptData failed %d/%u mac=%s", | ||
| 160 | + ret, decLen, sess->mac); | ||
| 161 | + return SESS_CODE_ERR; | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + CoapData payloadDec = {decData, decLen}; | ||
| 165 | + | ||
| 166 | + /* 将解密后的明文替换掉原始 CoAP 报文中的密文 payload */ | ||
| 167 | + ret = CoapUtilsReplacePayload(pkt, buf, &payloadDec); | ||
| 168 | + IotcFree(decData); | ||
| 169 | + | ||
| 170 | + if (ret != IOTC_OK) { | ||
| 171 | + IOTC_LOGE("dedap:decrypt handler: CoapUtilsReplacePayload failed %d mac=%s", ret, sess->mac); | ||
| 172 | + return SESS_CODE_ERR; | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + IOTC_LOGI("dedap:payload decrypted, mac=%s, cipher=%u -> plain=%u", | ||
| 176 | + sess->mac, cipherLen, decLen); | ||
| 177 | + return SESS_CODE_CONTINUE; | ||
| 178 | +} | ||
| @@ -0,0 +1,158 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) 2024-2026 China Mobile (Hangzhou) Information Technology Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | +/** | ||
| 34 | + * @brief 发送CoAP响应 | ||
| 35 | + * @param endpoint CoAP端点 | ||
| 36 | + * @param req 原始请求报文 | ||
| 37 | + * @param addr 响应目标地址 | ||
| 38 | + * @param errcode 响应错误码 | ||
| 39 | + * @return IOTC_OK表示响应已发送;否则返回JSON创建、响应构建或发送的错误码。 | ||
| 40 | + */ | ||
| 41 | +static int32_t DedapUdpSendResp(CoapEndpoint *endpoint, const CoapPacket *req, | ||
| 42 | + const SocketAddr *addr, int32_t errcode) | ||
| 43 | +{ | ||
| 44 | + IOTC_LOGI("dedap:send response, errcode=%d", errcode); | ||
| 45 | + IotcJson *respJson = IotcJsonCreate(); | ||
| 46 | + if (respJson == NULL) { | ||
| 47 | + IOTC_LOGE("dedap:create resp json failed"); | ||
| 48 | + return IOTC_ADAPTER_JSON_ERR_CREATE; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + int32_t ret = IotcJsonAddNum2Obj(respJson, STR_JSON_ERR_CODE, (int64_t)errcode); | ||
| 52 | + if (ret != IOTC_OK) { | ||
| 53 | + IOTC_LOGW("dedap:add errcode to resp json failed %d", ret); | ||
| 54 | + IotcJsonDelete(respJson); | ||
| 55 | + return ret; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + CoapServerRespParam respParam = {0}; | ||
| 59 | + ret = CoapServerBuildDefaultRespParam(&respParam, req, respJson); | ||
| 60 | + if (ret != IOTC_OK) { | ||
| 61 | + IOTC_LOGW("dedap:build resp param failed %d", ret); | ||
| 62 | + IotcJsonDelete(respJson); | ||
| 63 | + return ret; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + CoapPacket packet = {0}; | ||
| 67 | + ret = CoapServerSendResp(endpoint, &respParam, addr, &packet); | ||
| 68 | + if (ret != IOTC_OK) { | ||
| 69 | + IOTC_LOGW("dedap:send coap resp failed %d", ret); | ||
| 70 | + } | ||
| 71 | + | ||
| 72 | + IotcJsonDelete(respJson); | ||
| 73 | + return ret; | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +/** | ||
| 77 | + * @brief 处理设备配网请求广播 | ||
| 78 | + * @details POST /confignet/request,完成解析、会话更新和事件上报 | ||
| 79 | + * @param endpoint CoAP端点 | ||
| 80 | + * @param req 请求报文 | ||
| 81 | + * @param addr 请求来源地址(设备IP) | ||
| 82 | + * @param userData 用户数据 | ||
| 83 | + * @return 无返回值;参数或请求载荷非法时提前返回。 | ||
| 84 | + */ | ||
| 85 | +static void DedapConfignetRequestHandler(CoapEndpoint *endpoint, const CoapPacket *req, | ||
| 86 | + const SocketAddr *addr, DedapUserData *userData) | ||
| 87 | +{ | ||
| 88 | + NOT_USED(userData); | ||
| 89 | + | ||
| 90 | + CHECK_V_RETURN_LOGE(endpoint != NULL && req != NULL && addr != NULL, | ||
| 91 | + "invalid parameters: endpoint=%p req=%p addr=%p", | ||
| 92 | + (void*)endpoint, (void*)req, (void*)addr); | ||
| 93 | + CHECK_V_RETURN_LOGE(req->payload.data != NULL, "req->payload.data is NULL"); | ||
| 94 | + CHECK_V_RETURN_LOGE(req->payload.len > 0, "payload length is 0"); | ||
| 95 | + | ||
| 96 | + int32_t ret = DedapHandleConfignetRequest((const char *)req->payload.data, | ||
| 97 | + req->payload.len, addr); | ||
| 98 | + ret = DedapUdpSendResp(endpoint, req, addr, ret); | ||
| 99 | + if (ret != IOTC_OK) { | ||
| 100 | + IOTC_LOGW("dedap:send response failed %d", ret); | ||
| 101 | + } | ||
| 102 | +} | ||
| 103 | + | ||
| 104 | +/* ===== CoAP资源注册表 ===== */ | ||
| 105 | + | ||
| 106 | +static const CoapResource g_dedapResources[] = { | ||
| 107 | + { | ||
| 108 | + .method = UTILS_BIT(COAP_METHOD_TYPE_POST), | ||
| 109 | + .uri = STR_URI_PATH_DEDAP_CONFIGNET_REQUEST, | ||
| 110 | + .checker = NULL, | ||
| 111 | + .handler = (CoapServerReqHandler)DedapConfignetRequestHandler, | ||
| 112 | + }, | ||
| 113 | +}; | ||
| 114 | + | ||
| 115 | +/** | ||
| 116 | + * @brief 初始化dedap CoAP接收处理器 | ||
| 117 | + * @details 注册CoAP资源Handler到dedap的CoAP端点,接收专用热点设备coap请求 | ||
| 118 | + * @return 0成功,非0失败 | ||
| 119 | + */ | ||
| 120 | +int32_t DedapCoapHandlerRegister(void) | ||
| 121 | +{ | ||
| 122 | + DedapCtx *ctx = GetDedapCtx(); | ||
| 123 | + if (ctx->coapStack.endpoint == NULL) { | ||
| 124 | + IOTC_LOGE("dedap:coap endpoint not initialized"); | ||
| 125 | + return IOTC_ERR_NOT_INIT; | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + int32_t ret = CoapServerAddResource(ctx->coapStack.endpoint, g_dedapResources, | ||
| 129 | + ARRAY_SIZE(g_dedapResources)); | ||
| 130 | + if (ret != IOTC_OK) { | ||
| 131 | + IOTC_LOGE("dedap:add coap server resources failed %d", ret); | ||
| 132 | + return ret; | ||
| 133 | + } | ||
| 134 | + | ||
| 135 | + IOTC_LOGI("dedap:coap handler registered, resources=%u", (uint32_t)ARRAY_SIZE(g_dedapResources)); | ||
| 136 | + /* 打印已注册的资源 */ | ||
| 137 | + for (uint32_t i = 0; i < ARRAY_SIZE(g_dedapResources); i++) { | ||
| 138 | + IOTC_LOGI("[%u] %s (method=0x%x, handler=%p)", | ||
| 139 | + i, g_dedapResources[i].uri, g_dedapResources[i].method, g_dedapResources[i].handler); | ||
| 140 | + } | ||
| 141 | + return IOTC_OK; | ||
| 142 | +} | ||
| 143 | + | ||
| 144 | +/** | ||
| 145 | + * @brief 反初始化dedap CoAP接收处理器 | ||
| 146 | + * @details 注销所有CoAP资源Handler | ||
| 147 | + * @return 无返回值;端点未初始化时仅记录完成日志。 | ||
| 148 | + */ | ||
| 149 | +void DedapCoapHandlerUnregister(void) | ||
| 150 | +{ | ||
| 151 | + DedapCtx *ctx = GetDedapCtx(); | ||
| 152 | + if (ctx->coapStack.endpoint != NULL) { | ||
| 153 | + for (uint32_t i = 0; i < ARRAY_SIZE(g_dedapResources); i++) { | ||
| 154 | + CoapServerRemoveResource(ctx->coapStack.endpoint, &g_dedapResources[i]); | ||
| 155 | + } | ||
| 156 | + } | ||
| 157 | + IOTC_LOGI("dedap:coap handler unregister done"); | ||
| 158 | +} | ||
| @@ -0,0 +1,193 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) 2024-2026 China Mobile (Hangzhou) Information Technology Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +// 每个字节对应的十六进制字符数 | ||
| 24 | + | ||
| 25 | + | ||
| 26 | +// 每个十六进制数字占用的bit数 | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +// 十六进制字母的起始值 | ||
| 30 | + | ||
| 31 | + | ||
| 32 | +/** | ||
| 33 | + * @brief 将单个hex字符转换为数值 | ||
| 34 | + * @param c hex字符('0'-'9','A'-'F','a'-'f') | ||
| 35 | + * @return 转换后的4位值,非法字符返回0 | ||
| 36 | + */ | ||
| 37 | +uint8_t DedapHexCharToUint4(char c) | ||
| 38 | +{ | ||
| 39 | + if (c >= '0' && c <= '9') { | ||
| 40 | + return (uint8_t)(c - '0'); | ||
| 41 | + } | ||
| 42 | + if (c >= 'A' && c <= 'F') { | ||
| 43 | + return (uint8_t)(c - 'A' + HEX_ALPHA_BASE_VALUE); | ||
| 44 | + } | ||
| 45 | + if (c >= 'a' && c <= 'f') { | ||
| 46 | + return (uint8_t)(c - 'a' + HEX_ALPHA_BASE_VALUE); | ||
| 47 | + } | ||
| 48 | + return 0; | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +/** | ||
| 52 | + * @brief 将2个hex字符转换为uint8_t | ||
| 53 | + * @param hex 2个hex字符(不含\0) | ||
| 54 | + * @return 转换后的值 | ||
| 55 | + */ | ||
| 56 | +uint8_t DedapHexByteToUint8(const char hex[2]) | ||
| 57 | +{ | ||
| 58 | + return (DedapHexCharToUint4(hex[0]) << BITS_PER_HEX_DIGIT) | DedapHexCharToUint4(hex[1]); | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +/** | ||
| 62 | + * @brief 定位connRequest中的固定数据部分。 | ||
| 63 | + * @param connRequest connRequest字符串。 | ||
| 64 | + * @param connReqLen connRequest长度。 | ||
| 65 | + * @param dataStart 输出的固定数据起始地址。 | ||
| 66 | + * @param deviceNameLen 输出的设备名长度。 | ||
| 67 | + * @return 0成功,非0失败。 | ||
| 68 | + */ | ||
| 69 | +static int32_t DedapLocateConnRequestData(const char *connRequest, uint32_t connReqLen, | ||
| 70 | + const char **dataStart, uint32_t *deviceNameLen) | ||
| 71 | +{ | ||
| 72 | + if (connReqLen < DEDAP_CFGNET_REQUEST_MIN_LEN || | ||
| 73 | + memcmp(connRequest, DEDAP_CONNREQUEST_PREFIX, DEDAP_CONNREQUEST_PREFIX_LEN) != 0) { | ||
| 74 | + return IOTC_CORE_DEDAP_ERR_CONNREQUEST_INVALID; | ||
| 75 | + } | ||
| 76 | + uint32_t nameLimit = DEDAP_CONNREQUEST_PREFIX_LEN + DEDAP_CONNREQUEST_DEVICE_NAME_MAX_LEN; | ||
| 77 | + const char *secondDash = NULL; | ||
| 78 | + for (uint32_t i = DEDAP_CONNREQUEST_PREFIX_LEN; i < connReqLen && i <= nameLimit; i++) { | ||
| 79 | + if (connRequest[i] == DEDAP_CONNREQUEST_SEPARATOR) { | ||
| 80 | + secondDash = &connRequest[i]; | ||
| 81 | + *deviceNameLen = i - DEDAP_CONNREQUEST_PREFIX_LEN; | ||
| 82 | + break; | ||
| 83 | + } | ||
| 84 | + } | ||
| 85 | + if (secondDash == NULL || *deviceNameLen < DEDAP_CONNREQUEST_DEVICE_NAME_MIN_LEN || | ||
| 86 | + *deviceNameLen > DEDAP_CONNREQUEST_DEVICE_NAME_MAX_LEN) { | ||
| 87 | + return IOTC_CORE_DEDAP_ERR_CONNREQUEST_INVALID; | ||
| 88 | + } | ||
| 89 | + uint32_t dataLen = connReqLen - (uint32_t)(secondDash - connRequest) - 1; | ||
| 90 | + if (dataLen < DEDAP_CONNREQ_DATA_MIN_LEN) { | ||
| 91 | + return IOTC_CORE_DEDAP_ERR_CONNREQUEST_INVALID; | ||
| 92 | + } | ||
| 93 | + *dataStart = secondDash + 1; | ||
| 94 | + return IOTC_OK; | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +/** | ||
| 98 | + * @brief 校验connRequest固定数据字段。 | ||
| 99 | + * @param dataStart 固定数据起始地址。 | ||
| 100 | + * @param protoVer 输出的协议版本。 | ||
| 101 | + * @return 0成功,非0失败。 | ||
| 102 | + */ | ||
| 103 | +static int32_t DedapValidateConnRequestData(const char *dataStart, IotcProtoVer *protoVer) | ||
| 104 | +{ | ||
| 105 | + char version = dataStart[DEDAP_CONNREQ_PROTOVER_OFFSET]; | ||
| 106 | + if (version != '1' && version != '2') { | ||
| 107 | + return IOTC_CORE_DEDAP_ERR_CONNREQUEST_INVALID; | ||
| 108 | + } | ||
| 109 | + *protoVer = version == '1' ? IOTC_PROTO_VER_1_0 : IOTC_PROTO_VER_2_0; | ||
| 110 | + for (uint32_t i = 0; i < DEDAP_CONNREQ_PID_LEN; i++) { | ||
| 111 | + if (!isalnum((unsigned char)dataStart[DEDAP_CONNREQ_PID_OFFSET + i])) { | ||
| 112 | + return IOTC_CORE_DEDAP_ERR_CONNREQUEST_INVALID; | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + for (uint32_t i = 0; i < DEDAP_CONNREQ_SN_LEN; i++) { | ||
| 116 | + if (!isalnum((unsigned char)dataStart[DEDAP_CONNREQ_SN_OFFSET + i])) { | ||
| 117 | + return IOTC_CORE_DEDAP_ERR_CONNREQUEST_INVALID; | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + const char *hexData = dataStart + DEDAP_CONNREQ_HEX_OFFSET; | ||
| 121 | + for (uint32_t i = 0; i < DEDAP_CONNREQ_HEX_LEN * HEX_CHARS_PER_BYTE; i++) { | ||
| 122 | + if (!isxdigit((unsigned char)hexData[i])) { | ||
| 123 | + return IOTC_CORE_DEDAP_ERR_CONNREQUEST_INVALID; | ||
| 124 | + } | ||
| 125 | + } | ||
| 126 | + return IOTC_OK; | ||
| 127 | +} | ||
| 128 | + | ||
| 129 | +/** | ||
| 130 | + * @brief 将connRequest字段复制到解析结果。 | ||
| 131 | + * @param connRequest connRequest字符串。 | ||
| 132 | + * @param dataStart 固定数据起始地址。 | ||
| 133 | + * @param deviceNameLen 设备名长度。 | ||
| 134 | + * @param result 解析结果。 | ||
| 135 | + * @return 0成功,非0失败。 | ||
| 136 | + */ | ||
| 137 | +static int32_t DedapCopyConnRequestFields(const char *connRequest, const char *dataStart, | ||
| 138 | + uint32_t deviceNameLen, IotcSubDevDiscoveredParam *result) | ||
| 139 | +{ | ||
| 140 | + if (memcpy_s(result->deviceName, sizeof(result->deviceName), | ||
| 141 | + connRequest + DEDAP_CONNREQUEST_PREFIX_LEN, deviceNameLen) != EOK) { | ||
| 142 | + return IOTC_ERR_SECUREC_MEMCPY; | ||
| 143 | + } | ||
| 144 | + result->deviceName[deviceNameLen] = '\0'; | ||
| 145 | + if (memcpy_s(result->prodId, sizeof(result->prodId), dataStart + DEDAP_CONNREQ_PID_OFFSET, | ||
| 146 | + DEDAP_CONNREQ_PID_LEN) != EOK) { | ||
| 147 | + return IOTC_ERR_SECUREC_MEMCPY; | ||
| 148 | + } | ||
| 149 | + result->prodId[DEDAP_CONNREQ_PID_LEN] = '\0'; | ||
| 150 | + if (memcpy_s(result->snSuffix, sizeof(result->snSuffix), dataStart + DEDAP_CONNREQ_SN_OFFSET, | ||
| 151 | + DEDAP_CONNREQ_SN_LEN) != EOK) { | ||
| 152 | + return IOTC_ERR_SECUREC_MEMCPY; | ||
| 153 | + } | ||
| 154 | + result->snSuffix[DEDAP_CONNREQ_SN_LEN] = '\0'; | ||
| 155 | + const char *hexData = dataStart + DEDAP_CONNREQ_HEX_OFFSET; | ||
| 156 | + result->cfgCapabilitySet = DedapHexByteToUint8(hexData); | ||
| 157 | + result->p = DedapHexByteToUint8(hexData + DEDAP_CONNREQ_HEX_LEN); | ||
| 158 | + return IOTC_OK; | ||
| 159 | +} | ||
| 160 | + | ||
| 161 | +/** | ||
| 162 | + * @brief 解析connRequest字段到IotcSubDevDiscoveredParam | ||
| 163 | + * @details 格式:"Oc-" + 设备名(<=10字节) + "-" + protoVer(1字符) + PID(5字符) + SN后缀(4字符) + 配网能力(hex 2字符) + p(hex 2字符) | ||
| 164 | + * 示例:"Oc-cmcc-21234500017000" → deviceName="cmcc", protoVer='2', prodId="12345", snSuffix="0001", | ||
| 165 | + * cfgCapability=0x70, p=0x00 | ||
| 166 | + * @param connRequest connRequest字符串 | ||
| 167 | + * @param connReqLen connRequest长度 | ||
| 168 | + * @param result 输出:解析结果 | ||
| 169 | + * @return 0成功,非0失败 | ||
| 170 | + */ | ||
| 171 | +int32_t DedapParseConnRequest(const char *connRequest, uint32_t connReqLen, IotcSubDevDiscoveredParam *result) | ||
| 172 | +{ | ||
| 173 | + if (connRequest == NULL || result == NULL) { | ||
| 174 | + return IOTC_ERR_PARAM_INVALID; | ||
| 175 | + } | ||
| 176 | + const char *dataStart = NULL; | ||
| 177 | + uint32_t deviceNameLen = 0; | ||
| 178 | + int32_t ret = DedapLocateConnRequestData(connRequest, connReqLen, &dataStart, &deviceNameLen); | ||
| 179 | + if (ret == IOTC_OK) { | ||
| 180 | + ret = DedapValidateConnRequestData(dataStart, &result->protoVer); | ||
| 181 | + } | ||
| 182 | + if (ret == IOTC_OK) { | ||
| 183 | + ret = DedapCopyConnRequestFields(connRequest, dataStart, deviceNameLen, result); | ||
| 184 | + } | ||
| 185 | + if (ret != IOTC_OK) { | ||
| 186 | + IOTC_LOGE("dedap:parse connRequest failed %d", ret); | ||
| 187 | + return ret; | ||
| 188 | + } | ||
| 189 | + IOTC_LOGI("dedap:parsed connRequest - deviceName=%s protoVer=%u prodId=%s snSuffix=%s cfgCap=0x%02X p=0x%02X", | ||
| 190 | + result->deviceName, result->protoVer, result->prodId, result->snSuffix, | ||
| 191 | + result->cfgCapabilitySet, result->p); | ||
| 192 | + return IOTC_OK; | ||
| 193 | +} | ||
| @@ -0,0 +1,283 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) 2024-2026 China Mobile (Hangzhou) Information Technology Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * @brief 检查配网响应中的业务结果。 | ||
| 31 | + * @param respJson 响应JSON。 | ||
| 32 | + * @param codeKey 业务结果字段名。 | ||
| 33 | + * @param operation 操作名称。 | ||
| 34 | + * @param sess 设备会话。 | ||
| 35 | + * @return 0表示成功,非0表示业务失败。 | ||
| 36 | + */ | ||
| 37 | +static int32_t DedapCheckProvisionBusinessResult(IotcJson *respJson, const char *codeKey, | ||
| 38 | + const char *operation, const DedapDeviceSession *sess) | ||
| 39 | +{ | ||
| 40 | + IotcJson *codeObj = IotcJsonGetObj(respJson, codeKey); | ||
| 41 | + if (codeObj == NULL) { | ||
| 42 | + IOTC_LOGE("dedap:%s response missing %s, mac=%s", operation, codeKey, sess->mac); | ||
| 43 | + return IOTC_CORE_DEDAP_ERR_PROTOCOL_PARSE; | ||
| 44 | + } | ||
| 45 | + int64_t code = 0; | ||
| 46 | + if (IotcJsonGetNum(codeObj, &code) != IOTC_OK) { | ||
| 47 | + IOTC_LOGE("dedap:%s response invalid %s, mac=%s", operation, codeKey, sess->mac); | ||
| 48 | + return IOTC_CORE_DEDAP_ERR_PROTOCOL_PARSE; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + if (code == 0) { | ||
| 52 | + return IOTC_OK; | ||
| 53 | + } | ||
| 54 | + const char *desc = IotcJsonGetStr(IotcJsonGetObj(respJson, STR_JSON_DESCRIPTION)); | ||
| 55 | + IOTC_LOGE("dedap:%s failed code=%lld description=%s mac=%s", | ||
| 56 | + operation, (long long)code, desc == NULL ? "" : desc, sess->mac); | ||
| 57 | + return (int32_t)code; | ||
| 58 | +} | ||
| 59 | + | ||
| 60 | +/** | ||
| 61 | + * @brief 获取HubCfg响应JSON。 | ||
| 62 | + * @param resp CoAP响应报文。 | ||
| 63 | + * @param respJson 输出的JSON对象。 | ||
| 64 | + * @return 0成功,非0失败。 | ||
| 65 | + */ | ||
| 66 | +static int32_t DedapGetHubCfgRespJson(const CoapPacket *resp, IotcJson **respJson) | ||
| 67 | +{ | ||
| 68 | + int32_t ret = DedapCoapClientParseRespJson(resp, respJson); | ||
| 69 | + return ret == IOTC_ERR_PARAM_INVALID ? IOTC_CORE_DEDAP_ERR_PROTOCOL_PARSE : ret; | ||
| 70 | +} | ||
| 71 | + | ||
| 72 | +/** | ||
| 73 | + * @brief hubCfg响应回调。 | ||
| 74 | + * @param resp CoAP响应报文。 | ||
| 75 | + * @param addr 响应来源地址。 | ||
| 76 | + * @param userData 回调用户数据。 | ||
| 77 | + * @param timeout 是否超时。 | ||
| 78 | + * @return 无返回值;会话不存在、超时或响应校验失败时提前返回。 | ||
| 79 | + */ | ||
| 80 | +static void DedapProvisionHubCfgRespHandler(const CoapPacket *resp, const SocketAddr *addr, | ||
| 81 | + DedapUserData *userData, bool timeout) | ||
| 82 | +{ | ||
| 83 | + NOT_USED(userData); | ||
| 84 | + if (timeout) { | ||
| 85 | + IOTC_LOGW("dedap:ignore coap native hub cfg timeout"); | ||
| 86 | + return; | ||
| 87 | + } | ||
| 88 | + DedapDeviceSession *sess = DedapCompletePendingRequest(addr, DEDAP_REQUEST_HUB_CFG); | ||
| 89 | + if (sess == NULL) { | ||
| 90 | + IOTC_LOGE("dedap:hub cfg response pending session not found"); | ||
| 91 | + return; | ||
| 92 | + } | ||
| 93 | + IotcJson *respJson = NULL; | ||
| 94 | + int32_t ret = DedapGetHubCfgRespJson(resp, &respJson); | ||
| 95 | + if (ret == IOTC_OK) { | ||
| 96 | + ret = DedapCheckProvisionBusinessResult(respJson, STR_JSON_ERR_CODE, "hub cfg", sess); | ||
| 97 | + } | ||
| 98 | + IotcJsonDelete(respJson); | ||
| 99 | + if (ret != IOTC_OK) { | ||
| 100 | + DedapAbortSession(sess, ret); | ||
| 101 | + return; | ||
| 102 | + } | ||
| 103 | + IOTC_LOGI("dedap:hub cfg confirmed mac=%s", sess->mac); | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +/** | ||
| 107 | + * @brief 获取ProvisionPSK响应JSON。 | ||
| 108 | + * @param resp CoAP响应报文。 | ||
| 109 | + * @param respJson 输出的JSON对象。 | ||
| 110 | + * @return 0成功,非0失败。 | ||
| 111 | + */ | ||
| 112 | +static int32_t DedapGetProvisionPskRespJson(const CoapPacket *resp, IotcJson **respJson) | ||
| 113 | +{ | ||
| 114 | + int32_t ret = DedapCoapClientParseRespJson(resp, respJson); | ||
| 115 | + return ret == IOTC_ERR_PARAM_INVALID ? IOTC_CORE_DEDAP_ERR_PROTOCOL_PARSE : ret; | ||
| 116 | +} | ||
| 117 | + | ||
| 118 | +/** | ||
| 119 | + * @brief provisionPSK响应回调。 | ||
| 120 | + * @param resp CoAP响应报文。 | ||
| 121 | + * @param addr 响应来源地址。 | ||
| 122 | + * @param userData 回调用户数据。 | ||
| 123 | + * @param timeout 是否超时。 | ||
| 124 | + * @return 无返回值;会话不存在、超时或响应处理失败时中止流程。 | ||
| 125 | + */ | ||
| 126 | +static void DedapProvisionPskRespHandler(const CoapPacket *resp, const SocketAddr *addr, | ||
| 127 | + DedapUserData *userData, bool timeout) | ||
| 128 | +{ | ||
| 129 | + NOT_USED(userData); | ||
| 130 | + if (timeout) { | ||
| 131 | + IOTC_LOGW("dedap:ignore coap native provision psk timeout"); | ||
| 132 | + return; | ||
| 133 | + } | ||
| 134 | + DedapDeviceSession *sess = DedapCompletePendingRequest(addr, DEDAP_REQUEST_PROVISION_PSK); | ||
| 135 | + if (sess == NULL) { | ||
| 136 | + IOTC_LOGE("dedap:provision psk response pending session not found"); | ||
| 137 | + return; | ||
| 138 | + } | ||
| 139 | + IotcJson *respJson = NULL; | ||
| 140 | + int32_t ret = DedapGetProvisionPskRespJson(resp, &respJson); | ||
| 141 | + if (ret == IOTC_OK) { | ||
| 142 | + ret = DedapCheckProvisionBusinessResult(respJson, STR_JSON_CODE, "provision psk", sess); | ||
| 143 | + } | ||
| 144 | + IotcJsonDelete(respJson); | ||
| 145 | + if (ret == IOTC_OK) { | ||
| 146 | + ret = DedapProvisionSendHubCfg(sess); | ||
| 147 | + } | ||
| 148 | + if (ret != IOTC_OK) { | ||
| 149 | + DedapAbortSession(sess, ret); | ||
| 150 | + } | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +/** | ||
| 154 | + * @brief 构造HubCfg请求JSON。 | ||
| 155 | + * @param sess 设备会话。 | ||
| 156 | + * @param outJson 输出的JSON对象。 | ||
| 157 | + * @return 0成功,非0失败。 | ||
| 158 | + */ | ||
| 159 | +static int32_t DedapBuildHubCfgJson(const DedapDeviceSession *sess, IotcJson **outJson) | ||
| 160 | +{ | ||
| 161 | + IotcJson *json = IotcJsonCreate(); | ||
| 162 | + if (json == NULL) { | ||
| 163 | + return IOTC_ADAPTER_JSON_ERR_CREATE; | ||
| 164 | + } | ||
| 165 | + int32_t ret = IotcJsonAddStr2Obj(json, STR_JSON_DOMAIN_ID, sess->domainId); | ||
| 166 | + if (ret == IOTC_OK) { | ||
| 167 | + ret = IotcJsonAddStr2Obj(json, STR_JSON_DEVID, sess->devId); | ||
| 168 | + } | ||
| 169 | + if (ret == IOTC_OK) { | ||
| 170 | + ret = IotcJsonAddStr2Obj(json, STR_NETINFO_SSID, sess->wifiSetupSsid); | ||
| 171 | + } | ||
| 172 | + if (ret == IOTC_OK) { | ||
| 173 | + ret = IotcJsonAddStr2Obj(json, STR_NETINFO_PASSWORD, sess->wifiSetupPassword); | ||
| 174 | + } | ||
| 175 | + if (ret == IOTC_OK) { | ||
| 176 | + ret = IotcJsonAddNum2Obj(json, STR_JSON_CHANNEL, (int64_t)sess->wifiSetupChannel); | ||
| 177 | + } | ||
| 178 | + if (ret == IOTC_OK) { | ||
| 179 | + ret = IotcJsonAddStr2Obj(json, STR_JSON_BSSID, sess->wifiSetupBssid); | ||
| 180 | + } | ||
| 181 | + if (ret != IOTC_OK) { | ||
| 182 | + IotcJsonDelete(json); | ||
| 183 | + return ret; | ||
| 184 | + } | ||
| 185 | + *outJson = json; | ||
| 186 | + return IOTC_OK; | ||
| 187 | +} | ||
| 188 | + | ||
| 189 | +/** | ||
| 190 | + * @brief 构造ProvisionPSK请求JSON。 | ||
| 191 | + * @param sess 设备会话。 | ||
| 192 | + * @param outJson 输出的JSON对象。 | ||
| 193 | + * @return 0成功,非0失败。 | ||
| 194 | + */ | ||
| 195 | +static int32_t DedapBuildProvisionPskJson(const DedapDeviceSession *sess, IotcJson **outJson) | ||
| 196 | +{ | ||
| 197 | + IotcJson *json = IotcJsonCreate(); | ||
| 198 | + if (json == NULL) { | ||
| 199 | + return IOTC_ADAPTER_JSON_ERR_CREATE; | ||
| 200 | + } | ||
| 201 | + const IotcPskInfo *psk = &sess->pskInfo; | ||
| 202 | + int32_t ret = IotcJsonAddStr2Obj(json, STR_JSON_DOMAIN_ID, sess->domainId); | ||
| 203 | + if (ret == IOTC_OK && psk->pskId[0] != '\0') { | ||
| 204 | + ret = IotcJsonAddStr2Obj(json, STR_JSON_PSK_ID, psk->pskId); | ||
| 205 | + } | ||
| 206 | + if (ret == IOTC_OK && psk->psk[0] != '\0') { | ||
| 207 | + ret = IotcJsonAddStr2Obj(json, STR_JSON_PSK, psk->psk); | ||
| 208 | + } | ||
| 209 | + if (ret == IOTC_OK && psk->valid[0] != '\0') { | ||
| 210 | + ret = IotcJsonAddStr2Obj(json, STR_JSON_VALID, psk->valid); | ||
| 211 | + } | ||
| 212 | + if (ret != IOTC_OK) { | ||
| 213 | + IotcJsonDelete(json); | ||
| 214 | + return ret; | ||
| 215 | + } | ||
| 216 | + *outJson = json; | ||
| 217 | + return IOTC_OK; | ||
| 218 | +} | ||
| 219 | + | ||
| 220 | +/** | ||
| 221 | + * @brief 序列化并发送配网JSON请求。 | ||
| 222 | + * @param sess 设备会话。 | ||
| 223 | + * @param uri 请求URI。 | ||
| 224 | + * @param json 请求JSON。 | ||
| 225 | + * @param handler 响应回调。 | ||
| 226 | + * @param request 请求类型。 | ||
| 227 | + * @return 0成功,非0表示序列化、pending请求启动或发送失败。 | ||
| 228 | + */ | ||
| 229 | +static int32_t DedapSendProvisionJson(DedapDeviceSession *sess, const char *uri, IotcJson *json, | ||
| 230 | + CoapClientRespHandler handler, DedapRequestType request) | ||
| 231 | +{ | ||
| 232 | + char *jsonStr = IotcJsonPrint(json); | ||
| 233 | + if (jsonStr == NULL) { | ||
| 234 | + return IOTC_CORE_COMM_UTILS_ERR_JSON_MALLOC_PRINT; | ||
| 235 | + } | ||
| 236 | + DedapCoapRequest coapRequest = { | ||
| 237 | + .uri = uri, | ||
| 238 | + .code = COAP_METHOD_TYPE_POST, | ||
| 239 | + .payload = (const uint8_t *)jsonStr, | ||
| 240 | + .payloadLen = (uint32_t)strlen(jsonStr), | ||
| 241 | + .respHandler = handler, | ||
| 242 | + .request = request, | ||
| 243 | + }; | ||
| 244 | + int32_t ret = DedapCoapClientSendReq(&coapRequest, sess); | ||
| 245 | + IotcJsonFreePrint(jsonStr); | ||
| 246 | + return ret; | ||
| 247 | +} | ||
| 248 | + | ||
| 249 | +/** | ||
| 250 | + * @brief 下发hubCfg(WiFi网络凭证)到设备。 | ||
| 251 | + * @param sess 设备会话。 | ||
| 252 | + * @return 0成功,非0失败。 | ||
| 253 | + */ | ||
| 254 | +int32_t DedapProvisionSendHubCfg(DedapDeviceSession *sess) | ||
| 255 | +{ | ||
| 256 | + CHECK_RETURN_LOGE(sess != NULL, IOTC_ERR_PARAM_INVALID, "param invalid"); | ||
| 257 | + IotcJson *json = NULL; | ||
| 258 | + int32_t ret = DedapBuildHubCfgJson(sess, &json); | ||
| 259 | + if (ret == IOTC_OK) { | ||
| 260 | + ret = DedapSendProvisionJson(sess, DEDAP_URI_PATH_HUB_CFG, json, | ||
| 261 | + (CoapClientRespHandler)DedapProvisionHubCfgRespHandler, DEDAP_REQUEST_HUB_CFG); | ||
| 262 | + } | ||
| 263 | + IotcJsonDelete(json); | ||
| 264 | + return ret; | ||
| 265 | +} | ||
| 266 | + | ||
| 267 | +/** | ||
| 268 | + * @brief 下发provisionPSK(认证凭证)到设备。 | ||
| 269 | + * @param sess 设备会话。 | ||
| 270 | + * @return 0成功,非0失败。 | ||
| 271 | + */ | ||
| 272 | +int32_t DedapProvisionSendPsk(DedapDeviceSession *sess) | ||
| 273 | +{ | ||
| 274 | + CHECK_RETURN_LOGE(sess != NULL, IOTC_ERR_PARAM_INVALID, "param invalid"); | ||
| 275 | + IotcJson *json = NULL; | ||
| 276 | + int32_t ret = DedapBuildProvisionPskJson(sess, &json); | ||
| 277 | + if (ret == IOTC_OK) { | ||
| 278 | + ret = DedapSendProvisionJson(sess, DEDAP_URI_PATH_PROVISION_PSK, json, | ||
| 279 | + (CoapClientRespHandler)DedapProvisionPskRespHandler, DEDAP_REQUEST_PROVISION_PSK); | ||
| 280 | + } | ||
| 281 | + IotcJsonDelete(json); | ||
| 282 | + return ret; | ||
| 283 | +} | ||