已合并
feat:添加专用热点网关模块,支持专用热点设备配网--coap交互模块 #224
feat:添加专用热点网关模块,支持专用热点设备配网--coap交互模块 #224
已合并
移动-吴昊创建于 27 天前
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+#include <ctype.h>
17+#include <stdbool.h>
18+#include <stdint.h>
19+#include <string.h>
20+#include <stdlib.h>
21+#include "comm_def.h"
22+#include "iotc_errcode.h"
23+#include "iotc_json.h"
24+#include "iotc_log.h"
25+#include "iotc_event.h"
26+#include "iotc_event_params.h"
27+#include "event_bus.h"
28+#include "securec.h"
29+#include "utils_assert.h"
30+#include "utils_json.h"
31+#include "dedap_connrequest.h"
32+#include "dedap_device_session.h"
33+#include "dedap_coap_apiserver.h"
34+ 
35+// mac地址每段的字符数
36+#define IOTC_MAC_HEX_CHAR_NUM 2U
37+#define IOTC_MAC_SEPARATOR_LEN 1U
38+#define IOTC_MAC_OCTET_STR_LEN (IOTC_MAC_HEX_CHAR_NUM + IOTC_MAC_SEPARATOR_LEN)
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, &param);
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(&param, 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+#include <string.h>
17+#include "iotc_errcode.h"
18+#include "iotc_log.h"
19+#include "iotc_os.h"
20+#include "securec.h"
21+#include "utils_assert.h"
22+#include "utils_common.h"
23+#include "coap_codec_udp.h"
24+#include "coap_endpoint.h"
25+#include "coap_endpoint_event_source.h"
26+#include "trans_buffer_inner.h"
27+#include "sched_event_loop.h"
28+#include "dedap_ctx.h"
29+#include "dedap_device_session.h"
30+#include "dedap_coap_client.h"
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, &param, &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+#include "security_speke.h"
25+#include "iotc_log.h"
26+#include "iotc_mem.h"
27+#include "iotc_errcode.h"
28+#include "utils_assert.h"
29+#include "coap_codec_utils.h"
30+#include "comm_def.h"
31+#include "securec.h"
32+#include "dedap_ctx.h"
33+#include "dedap_device_session.h"
34+#include "dedap_coap_enc.h"
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+#include <string.h>
17+#include "iotc_log.h"
18+#include "iotc_errcode.h"
19+#include "iotc_json.h"
20+#include "iotc_socket.h"
21+#include "iotc_network.h"
22+#include "utils_assert.h"
23+#include "utils_common.h"
24+#include "securec.h"
25+#include "coap_endpoint_server.h"
26+#include "coap_codec_utils.h"
27+#include "event_bus.h"
28+#include "comm_def.h"
29+#include "dedap_ctx.h"
30+#include "dedap_coap_apiserver.h"
31+#include "dedap_coap_server.h"
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+#include <ctype.h>
17+#include <string.h>
18+#include "iotc_errcode.h"
19+#include "iotc_log.h"
20+#include "securec.h"
21+#include "dedap_connrequest.h"
22+ 
23+// 每个字节对应的十六进制字符数
24+#define HEX_CHARS_PER_BYTE 2U
25+ 
26+// 每个十六进制数字占用的bit数
27+#define BITS_PER_HEX_DIGIT 4U
28+ 
29+// 十六进制字母的起始值
30+#define HEX_ALPHA_BASE_VALUE 10U
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+#include <string.h>
17+#include "iotc_log.h"
18+#include "iotc_errcode.h"
19+#include "iotc_json.h"
20+#include "utils_assert.h"
21+#include "utils_common.h"
22+#include "securec.h"
23+#include "comm_def.h"
24+#include "dedap_ctx.h"
25+#include "dedap_coap_client.h"
26+#include "dedap_device_session.h"
27+#include "dedap_provision.h"
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+}