已关闭
WiFi Only: add verified device SoftAP provisioning runtime #223
faizk创建于 5 天前关闭于 2 天前
WiFi Only: add verified device SoftAP provisioning runtime #223
已关闭
faizk创建于 5 天前关闭于 2 天前
94 个文件变更+6676-208
MBUILD.gn+37-4
@@ -46,16 +46,49 @@ if (iotc_device_level == "mini") {
46iotc_def += ["IOTC_ONECONNECT_VERSION_NAME=\"${iotc_one_connect_version_name}\""]46iotc_def += ["IOTC_ONECONNECT_VERSION_NAME=\"${iotc_one_connect_version_name}\""]
47iotc_def += ["IOTC_ONECONNECT_VERSION_CODE=${iotc_one_connect_version_code}"]47iotc_def += ["IOTC_ONECONNECT_VERSION_CODE=${iotc_one_connect_version_code}"]
48iotc_def += ["SPEKE_PBKDF2_ITERATIONS=${iot_connect_speke_pbkdf2_iterations}"]48iotc_def += ["SPEKE_PBKDF2_ITERATIONS=${iot_connect_speke_pbkdf2_iterations}"]
49-iotc_def += ["SEC_TERMINAL_PSK_MAX_NUM=${sec_terminal_psk_max_num}"]49+if (iot_connect_speke_secret_trace) {
50-iotc_def += ["SEC_ACCESS_PSK_MAX_NUM=${sec_access_psk_real_max_num}"]50+ assert(iot_connect_speke_v2,
51-iotc_def += ["SEC_ACL_MAX_NUM=${sec_acl_max_num}"]51+ "SPEKE secret trace requires the Unified Connect 3.0 SPEKE v2 path")
52+ iotc_def += ["IOTC_CONF_SPEKE_SECRET_TRACE=1"]
53+}
54+iotc_def += ["SEC_TERMINAL_PSK_MAX_NUM=${sec_terminal_psk_max_num}"]
55+iotc_def += ["SEC_ACCESS_PSK_MAX_NUM=${sec_access_psk_real_max_num}"]
56+iotc_def += ["SEC_ACL_MAX_NUM=${sec_acl_max_num}"]
57+if (!iot_connect_databus_support) {
58+ iotc_def += ["IOTC_CONF_DATABUS_SUPPORT=0"]
59+}
60+if (iot_connect_wifi_only_support) {
61+ iotc_def += ["IOTC_CONF_WIFI_ONLY_SUPPORT=1"]
62+ # WiFi Only 配网业务承载 PIN、PSK 与家庭 Wi-Fi 凭据。配置头的历史默认值
63+ # 是开启 CoAP 转储,因此必须显式关闭,不能只依赖“不添加 debug define”。
64+ iotc_def += [
65+ "IOTC_CONF_COAP_DEBUG_DUMP_PACKET=0",
66+ "IOTC_CONF_COAP_DEBUG_DUMP_PAYLOAD=0",
67+ "IOTC_CONF_NET_COAP_DEBUG_DUMP_PACKET=0",
68+ "IOTC_CONF_NET_COAP_DEBUG_DUMP_PAYLOAD=0",
69+ ]
70+ # WS63 设备:socket fd 由 lwip 分配(>=256),libc 的 select/close 不认识 lwip 句柄,
71+ # 适配层须显式走 lwip_select/closesocket,见 adapter/socket/iotc_socket.c
72+ iotc_def += ["IOTC_CONF_ADAPTER_SOCKET_LWIP_FDS=1"]
73+ iotc_def += ["IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP=1"]
74+ iotc_def += ["IOTC_CONF_BROAD_CAST_SUPPORT=1"]
75+}
76+ 
77+if (iot_connect_mutex_lockless) {
78+ # 仅该开关打开的产品使用 lockless ExMutex,其他产品保持默认互斥锁逻辑
79+ iotc_def += ["IOTC_CONF_UTILS_MUTEX_LOCKLESS=1"]
80+}
52 81 
53if (defined(ohos_lite)) {82if (defined(ohos_lite)) {
54 import("//build/lite/config/component/lite_component.gni")83 import("//build/lite/config/component/lite_component.gni")
55 84 
56 if (ohos_kernel_type == "liteos_m") {85 if (ohos_kernel_type == "liteos_m") {
57 if (ohos_build_type == "debug") {86 if (ohos_build_type == "debug") {
58- iotc_def += ["IOTC_CONF_COAP_DEBUG_DUMP_PACKET=1"]87+ # WiFi Only carries provisioning credentials in CoAP payloads. Keep
88+ # packet dumping off even in a debug build for this product profile.
89+ if (!iot_connect_wifi_only_support) {
90+ iotc_def += ["IOTC_CONF_COAP_DEBUG_DUMP_PACKET=1"]
91+ }
59 iotc_def += ["IOTC_CONF_BUILD_TYPE=1"]92 iotc_def += ["IOTC_CONF_BUILD_TYPE=1"]
60 iotc_def += ["IOT_CONF_LOG_DEFAULT_LEVEL=7"]93 iotc_def += ["IOT_CONF_LOG_DEFAULT_LEVEL=7"]
61 } else {94 } else {
Madapter/include/iotc_wifi.h+1-1
@@ -69,7 +69,7 @@ typedef struct {
69 char ssid[IOTC_WIFI_SSID_MAX_LEN];69 char ssid[IOTC_WIFI_SSID_MAX_LEN];
70 uint8_t bssid[IOTC_WIFI_BSSID_LEN];70 uint8_t bssid[IOTC_WIFI_BSSID_LEN];
71 IotcWifiSecurityType securityType;71 IotcWifiSecurityType securityType;
72- uint8_t rssi;72+ int32_t rssi;
73 uint32_t band;73 uint32_t band;
74 uint32_t frequency;74 uint32_t frequency;
75} IotcWifiInfo;75} IotcWifiInfo;
Madapter/kv/oh_lite/iotc_kv.c+13-33
@@ -62,18 +62,6 @@ int32_t IotcKvInit(const char *tag)
62 return IOTC_OK;62 return IOTC_OK;
63}63}
64 64 
65-static int32_t CreateFile(const char *filePath)
66-{
67- IOTC_LOGD("create %s", filePath);
68- int32_t fd = UtilsFileOpen(filePath, O_RDWR_FS | O_CREAT_FS, 0);
69- if (fd < 0) {
70- IOTC_LOGE("open or create error %d", fd);
71- return IOTC_ADAPTER_KV_ERR_CREATE_FILE;
72- }
73- UtilsFileClose(fd);
74- return IOTC_OK;
75-}
76- 
77static bool CheckFileIsExisit(const char *filePath)65static bool CheckFileIsExisit(const char *filePath)
78{66{
79 int32_t fd = UtilsFileOpen(filePath, O_RDONLY_FS, 0);67 int32_t fd = UtilsFileOpen(filePath, O_RDONLY_FS, 0);
@@ -88,14 +76,14 @@ static bool CheckFileIsExisit(const char *filePath)
88static int32_t KvSetValue(const char *filePath, const uint8_t *buf, uint32_t len)76static int32_t KvSetValue(const char *filePath, const uint8_t *buf, uint32_t len)
89{77{
90 IOTC_LOGD("write %s", filePath);78 IOTC_LOGD("write %s", filePath);
91- int32_t fd = UtilsFileOpen(filePath, O_RDWR_FS | O_CREAT_FS, 0);79+ int32_t fd = UtilsFileOpen(filePath, O_RDWR_FS | O_CREAT_FS | O_TRUNC_FS, 0);
92 if (fd < 0) {80 if (fd < 0) {
93 IOTC_LOGE("open file[%s] error", filePath);81 IOTC_LOGE("open file[%s] error", filePath);
94 return IOTC_ADAPTER_KV_ERR_SET_ITEM;82 return IOTC_ADAPTER_KV_ERR_SET_ITEM;
95 }83 }
96 84 
97- int32_t ret = UtilsFileWrite(fd, (uint8_t *)buf, len);85+ int32_t ret = UtilsFileWrite(fd, (const char *)buf, len);
98- if (ret < 0) {86+ if (ret != (int32_t)len) {
99 IOTC_LOGE("write file[%s] error", filePath);87 IOTC_LOGE("write file[%s] error", filePath);
100 UtilsFileClose(fd);88 UtilsFileClose(fd);
101 return IOTC_ADAPTER_KV_ERR_SET_ITEM;89 return IOTC_ADAPTER_KV_ERR_SET_ITEM;
@@ -135,7 +123,7 @@ int32_t IotcKvSetValue(const char *key, const uint8_t *value, uint32_t len)
135static int32_t KvGetValue(const char *filePath, uint8_t *buf, uint32_t *len)123static int32_t KvGetValue(const char *filePath, uint8_t *buf, uint32_t *len)
136{124{
137 IOTC_LOGD("read %s", filePath);125 IOTC_LOGD("read %s", filePath);
138- int32_t fd = UtilsFileOpen(filePath, O_RDWR_FS | O_CREAT_FS, 0);126+ int32_t fd = UtilsFileOpen(filePath, O_RDONLY_FS, 0);
139 if (fd < 0) {127 if (fd < 0) {
140 IOTC_LOGE("open file[%s] error", filePath);128 IOTC_LOGE("open file[%s] error", filePath);
141 return IOTC_ADAPTER_KV_ERR_GET_ITEM;129 return IOTC_ADAPTER_KV_ERR_GET_ITEM;
@@ -154,8 +142,8 @@ static int32_t KvGetValue(const char *filePath, uint8_t *buf, uint32_t *len)
154 return IOTC_ADAPTER_KV_ERR_GET_ITEM;142 return IOTC_ADAPTER_KV_ERR_GET_ITEM;
155 }143 }
156 *len = fileLen;144 *len = fileLen;
157- ret = UtilsFileRead(fd, buf, *len);145+ ret = UtilsFileRead(fd, (char *)buf, *len);
158- if (ret < 0) {146+ if (ret != (int32_t)(*len)) {
159 IOTC_LOGE("read file[%s] error", filePath);147 IOTC_LOGE("read file[%s] error", filePath);
160 UtilsFileClose(fd);148 UtilsFileClose(fd);
161 return IOTC_ADAPTER_KV_ERR_GET_ITEM;149 return IOTC_ADAPTER_KV_ERR_GET_ITEM;
@@ -182,28 +170,24 @@ int32_t IotcKvGetValue(const char *key, uint8_t *buf, uint32_t *len)
182 return KvGetValue((const char *)filePath, buf, len);170 return KvGetValue((const char *)filePath, buf, len);
183}171}
184 172 
185-static uint32_t GetFileLenByName(const char *filePath, uint32_t *len)173+static int32_t GetFileLenByName(const char *filePath, uint32_t *len)
186{174{
187- int32_t fd = UtilsFileOpen(filePath, O_RDWR_FS | O_CREAT_FS, 0);175+ if (!CheckFileIsExisit(filePath)) {
188- if (fd < 0) {176+ *len = 0;
189- IOTC_LOGE("open file[%s] error", filePath);177+ return IOTC_OK;
190- return IOTC_ADAPTER_KV_ERR_OPEN_FILE;
191 }178 }
192- 
193 int32_t ret = UtilsFileStat(filePath, len);179 int32_t ret = UtilsFileStat(filePath, len);
194 if (ret != 0) {180 if (ret != 0) {
195 IOTC_LOGE("stat file[%s] error", filePath);181 IOTC_LOGE("stat file[%s] error", filePath);
196- UtilsFileClose(fd);
197 *len = 0;182 *len = 0;
198 return IOTC_ADAPTER_KV_ERR_STAT_FILE;183 return IOTC_ADAPTER_KV_ERR_STAT_FILE;
199 }184 }
200- UtilsFileClose(fd);
201 return IOTC_OK;185 return IOTC_OK;
202}186}
203 187 
204int32_t IotcKvGetLen(const char *key, uint32_t *len)188int32_t IotcKvGetLen(const char *key, uint32_t *len)
205{189{
206- if ((key == NULL) || (key[0] == '\0')) {190+ if ((key == NULL) || (key[0] == '\0') || (len == NULL)) {
207 IOTC_LOGE("invalid param");191 IOTC_LOGE("invalid param");
208 return IOTC_ERR_PARAM_INVALID;192 return IOTC_ERR_PARAM_INVALID;
209 }193 }
@@ -216,11 +200,7 @@ int32_t IotcKvGetLen(const char *key, uint32_t *len)
216 if (MakeFilePath(key, filePath, sizeof(filePath)) != IOTC_OK) {200 if (MakeFilePath(key, filePath, sizeof(filePath)) != IOTC_OK) {
217 return IOTC_ERR_SECUREC_SPRINTF;201 return IOTC_ERR_SECUREC_SPRINTF;
218 }202 }
219- ret = GetFileLenByName((const char *)filePath, len);203+ return GetFileLenByName((const char *)filePath, len);
220- if (ret != IOTC_OK) {
221- IOTC_LOGW("get fileLen err");
222- }
223- return IOTC_OK;
224}204}
225 205 
226static int32_t KvDelValue(const char *filePath)206static int32_t KvDelValue(const char *filePath)
@@ -262,4 +242,4 @@ int32_t IotcKvDeInit(void)
262 (void)memset_s(g_tag, sizeof(g_tag), 0, sizeof(g_tag));242 (void)memset_s(g_tag, sizeof(g_tag), 0, sizeof(g_tag));
263 g_isInit = false;243 g_isInit = false;
264 return IOTC_OK;244 return IOTC_OK;
265-}245+}
Madapter/wifi/oh_lite/iotc_network.c+55-5
@@ -23,10 +23,46 @@
23#include "iotc_socket.h"23#include "iotc_socket.h"
24#include "iotc_conf.h"24#include "iotc_conf.h"
25#include "iotc_log.h"25#include "iotc_log.h"
26+#if IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP
27+#include "lwip/ip4_addr.h"
28+#include "lwip/netifapi.h"
29+#define IOTC_STA_IFNAME "wlan0"
30+#endif
26 31 
32+/*
33+ * WS63 的 GetLinkedInfo 只查 STA 链路(STA 未使能时必失败),
34+ * SoftAP 场景必须改读 AP 接口的 IPv4 地址。
35+ */
27int32_t IotcGetSoftApIp(char *buf, uint32_t len)36int32_t IotcGetSoftApIp(char *buf, uint32_t len)
28{37{
38+#if !IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP
29 return IotcGetLocalIp(buf, len);39 return IotcGetLocalIp(buf, len);
40+#else
41+ if ((buf == NULL) || (len == 0)) {
42+ IOTC_LOGE("invalid param");
43+ return IOTC_ERR_PARAM_INVALID;
44+ }
45+ 
46+ static const char *const candidates[] = {"ap0", "wlan0"};
47+ for (uint32_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); ++i) {
48+ struct netif *netif = netifapi_netif_find_by_name(candidates[i]);
49+ if (netif == NULL) {
50+ continue;
51+ }
52+ ip4_addr_t ipaddr = {0};
53+ if (netifapi_netif_get_addr(netif, &ipaddr, NULL, NULL) != 0) {
54+ continue;
55+ }
56+ if (ip4_addr_isany_val(ipaddr)) {
57+ continue;
58+ }
59+ const char *ip = IotcInetNtoa(IotcNtohl(ipaddr.addr), buf, len);
60+ if ((ip != NULL) && (ip[0] != '\0')) {
61+ return IOTC_OK;
62+ }
63+ }
64+ return IOTC_ADAPTER_NETWORK_ERR_GET_IP;
65+#endif
30}66}
31 67 
32int32_t IotcGetLocalIp(char *buf, uint32_t len)68int32_t IotcGetLocalIp(char *buf, uint32_t len)
@@ -49,7 +85,8 @@ int32_t IotcGetLocalIp(char *buf, uint32_t len)
49 }85 }
50 isPrint = false;86 isPrint = false;
51 87 
52- const char *ip = IotcInetNtoa(info.ipAddress, buf, len);88+ /* OH Lite HAL returns lwIP's IPv4 value in network byte order. */
89+ const char *ip = IotcInetNtoa(IotcNtohl(info.ipAddress), buf, len);
53 if ((ip == NULL) || (ip[0] == '\0')) {90 if ((ip == NULL) || (ip[0] == '\0')) {
54 return IOTC_ADAPTER_NETWORK_ERR_GET_IP;91 return IOTC_ADAPTER_NETWORK_ERR_GET_IP;
55 }92 }
@@ -91,17 +128,30 @@ int32_t IotcGetBroadcastAddr(char *buf, uint32_t len)
91 return IOTC_ERR_PARAM_INVALID;128 return IOTC_ERR_PARAM_INVALID;
92 }129 }
93#if IOTC_CONF_BROAD_CAST_SUPPORT130#if IOTC_CONF_BROAD_CAST_SUPPORT
131+#if IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP
132+ struct netif *netif = netifapi_netif_find_by_name(IOTC_STA_IFNAME);
133+ ip4_addr_t ipAddress = {0};
134+ ip4_addr_t netMask = {0};
135+ if (netif == NULL || netifapi_netif_get_addr(netif, &ipAddress, &netMask, NULL) != 0 ||
136+ ip4_addr_isany_val(ipAddress) || ip4_addr_isany_val(netMask)) {
137+ return IOTC_ADAPTER_NETWORK_ERR_GET_BROADCAST_IP;
138+ }
139+ uint32_t broadcastIp = (ipAddress.addr & netMask.addr) | (~netMask.addr);
140+ const char *ip = IotcInetNtoa(IotcNtohl(broadcastIp), buf, len);
141+#else
94 IpInfo ipInfo = {0};142 IpInfo ipInfo = {0};
95 WifiErrorCode ret = GetIpInfo(&ipInfo);143 WifiErrorCode ret = GetIpInfo(&ipInfo);
96 if (ret != WIFI_SUCCESS) {144 if (ret != WIFI_SUCCESS) {
97 IOTC_LOGE("get ip info error %d", ret);145 IOTC_LOGE("get ip info error %d", ret);
98- return IOTC_IOTC_NETWORK_ERR_GET_BROADCAST_IP;146+ return IOTC_ADAPTER_NETWORK_ERR_GET_BROADCAST_IP;
99 }147 }
100 uint32_t broadcastIp = (ipInfo.ipAddress & ipInfo.netMask) | (~ipInfo.netMask);148 uint32_t broadcastIp = (ipInfo.ipAddress & ipInfo.netMask) | (~ipInfo.netMask);
101- const char *ip = IotcInetNtoa(broadcastIp, buf, len);149+ /* OH Lite HAL 的 IPv4/掩码均为网络字节序,IotcInetNtoa 接收主机序。 */
150+ const char *ip = IotcInetNtoa(IotcNtohl(broadcastIp), buf, len);
151+#endif
102 if ((ip == NULL) || (ip[0] == '\0')) {152 if ((ip == NULL) || (ip[0] == '\0')) {
103- return IOTC_IOTC_NETWORK_ERR_GET_BROADCAST_IP;153+ return IOTC_ADAPTER_NETWORK_ERR_GET_BROADCAST_IP;
104 }154 }
105#endif155#endif
106 return IOTC_OK;156 return IOTC_OK;
107-}157+}
Madapter/wifi/oh_lite/iotc_wifi.c+247-43
@@ -15,6 +15,7 @@
15#include "iotc_wifi.h"15#include "iotc_wifi.h"
16#include <stdbool.h>16#include <stdbool.h>
17#include <stddef.h>17#include <stddef.h>
18+#include <string.h>
18#include "securec.h"19#include "securec.h"
19#include "wifi_device.h"20#include "wifi_device.h"
20#include "wifi_event.h"21#include "wifi_event.h"
@@ -25,11 +26,23 @@
25#include "iotc_os.h"26#include "iotc_os.h"
26#include "wifi_hotspot.h"27#include "wifi_hotspot.h"
27#include "iotc_log.h"28#include "iotc_log.h"
29+#include "iotc_conf.h"
30+#if IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP
31+#include "lwip/etharp.h"
32+#include "lwip/ip4_addr.h"
33+#include "lwip/netifapi.h"
34+ 
35+/* SoftAP 接口地址与 DHCP 服务由调用方负责(同 WS63 官方 softap 示例):
36+ * 部分 wifiservice 实现只起热点不配地址/不起 dhcps,手机拿不到租约会掉线。 */
37+#define IOTC_SOFTAP_IFNAME_FIRST "ap0"
38+#define IOTC_SOFTAP_IFNAME_SECOND "wlan0"
39+#endif
28 40 
29#define WIFI_SCANNING 141#define WIFI_SCANNING 1
30#define MAX_SCAN_TIMES 442#define MAX_SCAN_TIMES 4
31#define DEF_SCAN_TIMEOUT 1543#define DEF_SCAN_TIMEOUT 15
32#define MS_PER_SECOND 100044#define MS_PER_SECOND 1000
45+#define WIFI_SCAN_RETRY_INTERVAL_MS (2 * MS_PER_SECOND)
33 46 
34static bool g_isRegisterWifiEvent = false;47static bool g_isRegisterWifiEvent = false;
35static bool g_isStaScanSuccess = false;48static bool g_isStaScanSuccess = false;
@@ -38,6 +51,42 @@ static uint16_t g_disconnectReason = 0;
38static bool g_isReasonRefresh = false;51static bool g_isReasonRefresh = false;
39static IotcWifiEvent g_adapterEventHandler = {0};52static IotcWifiEvent g_adapterEventHandler = {0};
40 53 
54+static int32_t RegisterWifiEventToOhos(void);
55+ 
56+static int32_t CopyWifiInfoFromConfig(const WifiDeviceConfig *config, uint8_t *ssidBuf,
57+ uint32_t *ssidBufLen, uint8_t *pwdBuf, uint32_t *pwdBufLen)
58+{
59+ if (config == NULL || config->ssid[0] == '\0') {
60+ return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
61+ }
62+ if (strcpy_s((char *)ssidBuf, *ssidBufLen, config->ssid) != EOK) {
63+ IOTC_LOGW("copy ssid fail");
64+ return IOTC_ERR_SECUREC_STRCPY;
65+ }
66+ *ssidBufLen = strlen(config->ssid);
67+ 
68+ if (config->preSharedKey[0] == '\0') {
69+ *pwdBufLen = 0;
70+ return IOTC_OK;
71+ }
72+ if (strcpy_s((char *)pwdBuf, *pwdBufLen, config->preSharedKey) != EOK) {
73+ IOTC_LOGW("copy password fail");
74+ return IOTC_ERR_SECUREC_STRCPY;
75+ }
76+ *pwdBufLen = strlen(config->preSharedKey);
77+ return IOTC_OK;
78+}
79+ 
80+static const WifiDeviceConfig *GetValidWifiConfig(const WifiDeviceConfig *wifiConfig, uint32_t size)
81+{
82+ for (uint32_t i = 0; i < size && i < WIFI_MAX_CONFIG_SIZE; ++i) {
83+ if (wifiConfig[i].ssid[0] != '\0') {
84+ return &wifiConfig[i];
85+ }
86+ }
87+ return NULL;
88+}
89+ 
41int32_t IotcGetWifiInfo(uint8_t *ssidBuf, uint32_t *ssidBufLen, uint8_t *pwdBuf, uint32_t *pwdBufLen)90int32_t IotcGetWifiInfo(uint8_t *ssidBuf, uint32_t *ssidBufLen, uint8_t *pwdBuf, uint32_t *pwdBufLen)
42{91{
43 if ((ssidBuf == NULL) || (ssidBufLen == NULL) || (pwdBuf == NULL) || (pwdBufLen ==NULL)) {92 if ((ssidBuf == NULL) || (ssidBufLen == NULL) || (pwdBuf == NULL) || (pwdBufLen ==NULL)) {
@@ -57,31 +106,15 @@ int32_t IotcGetWifiInfo(uint8_t *ssidBuf, uint32_t *ssidBufLen, uint8_t *pwdBuf,
57 IOTC_LOGI("get device config fail");106 IOTC_LOGI("get device config fail");
58 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;107 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
59 }108 }
60- if (wifiConfig[0].ssid[0] == '\0') {109+ const WifiDeviceConfig *config = GetValidWifiConfig(wifiConfig, size);
110+ if (config == NULL) {
61 IOTC_LOGW("GetDeviceConfigs fail, no ssid");111 IOTC_LOGW("GetDeviceConfigs fail, no ssid");
112+ (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));
62 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;113 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
63 }114 }
64- 115+ int32_t ret = CopyWifiInfoFromConfig(config, ssidBuf, ssidBufLen, pwdBuf, pwdBufLen);
65- if (strcpy_s((char *)ssidBuf, *ssidBufLen, (char *)wifiConfig[0].ssid) != EOK) {
66- IOTC_LOGW("strcpy fail");
67- (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));
68- return IOTC_ERR_SECUREC_STRCPY;
69- }
70- *ssidBufLen = strlen(wifiConfig[0].ssid);
71- 
72- if (wifiConfig[0].preSharedKey[0] == '\0') {
73- (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));
74- *pwdBufLen = 0;
75- return IOTC_OK;
76- }
77- if (strcpy_s((char *)pwdBuf, *pwdBufLen, (char *)wifiConfig[0].preSharedKey) != EOK) {
78- IOTC_LOGW("strcpy fail");
79- (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));
80- return IOTC_ERR_SECUREC_STRCPY;
81- }
82- *pwdBufLen = strlen(wifiConfig[0].preSharedKey);
83 (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));116 (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));
84- return IOTC_OK;117+ return ret;
85}118}
86 119 
87static int32_t GetWifiConfigFromOhos(WifiDeviceConfig *config)120static int32_t GetWifiConfigFromOhos(WifiDeviceConfig *config)
@@ -94,11 +127,18 @@ static int32_t GetWifiConfigFromOhos(WifiDeviceConfig *config)
94 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;127 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
95 }128 }
96 129 
97- if (memcpy_s(config, sizeof(WifiDeviceConfig), &wifiConfig[0], sizeof(WifiDeviceConfig)) != EOK) {130+ const WifiDeviceConfig *savedConfig = GetValidWifiConfig(wifiConfig, size);
131+ if (savedConfig == NULL) {
132+ IOTC_LOGW("GetDeviceConfigs fail, no ssid");
133+ (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));
134+ return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
135+ }
136+ if (memcpy_s(config, sizeof(WifiDeviceConfig), savedConfig, sizeof(WifiDeviceConfig)) != EOK) {
98 IOTC_LOGW("memcpy error");137 IOTC_LOGW("memcpy error");
99 (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));138 (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));
100 return IOTC_ERR_SECUREC_MEMCPY;139 return IOTC_ERR_SECUREC_MEMCPY;
101 }140 }
141+ IOTC_LOGI("select wifi config netId=%d, ssidLen=%u", config->netId, (uint32_t)strlen(config->ssid));
102 (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));142 (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));
103 return IOTC_OK;143 return IOTC_OK;
104}144}
@@ -108,7 +148,7 @@ static int32_t GetWifiNetworkIdFromOhos(int32_t *netId)
108 WifiDeviceConfig config;148 WifiDeviceConfig config;
109 (void)memset_s(&config, sizeof(WifiDeviceConfig), 0, sizeof(WifiDeviceConfig));149 (void)memset_s(&config, sizeof(WifiDeviceConfig), 0, sizeof(WifiDeviceConfig));
110 150 
111- if (GetWifiConfigFromOhos(&config) != IOTC_OK) {151+ if (GetWifiConfigFromOhos(&config) != IOTC_OK || config.ssid[0] == '\0') {
112 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;152 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
113 }153 }
114 *netId = config.netId;154 *netId = config.netId;
@@ -119,7 +159,9 @@ static int32_t GetWifiNetworkIdFromOhos(int32_t *netId)
119static void ClearWifiConfig(void)159static void ClearWifiConfig(void)
120{160{
121 int32_t netId = 0;161 int32_t netId = 0;
122- (void)GetWifiNetworkIdFromOhos(&netId);162+ if (GetWifiNetworkIdFromOhos(&netId) != IOTC_OK) {
163+ return;
164+ }
123 165 
124 if (RemoveDevice(netId) != WIFI_SUCCESS) {166 if (RemoveDevice(netId) != WIFI_SUCCESS) {
125 IOTC_LOGW("remove wifi config error, netId [%d]", netId);167 IOTC_LOGW("remove wifi config error, netId [%d]", netId);
@@ -187,7 +229,7 @@ int32_t IotcRestartWifi(void)
187static void OnWifiScanStateChangedCallback(int32_t state, int32_t size)229static void OnWifiScanStateChangedCallback(int32_t state, int32_t size)
188{230{
189 IOTC_LOGI("scan state change %d/%u", state, size);231 IOTC_LOGI("scan state change %d/%u", state, size);
190- g_isStaScanSuccess = true;232+ g_isStaScanSuccess = (state == WIFI_STATE_AVAILABLE);
191 int32_t iotcState = state == WIFI_STATE_AVAILABLE ? IOTC_WIFI_EVENT_STATE_OK :233 int32_t iotcState = state == WIFI_STATE_AVAILABLE ? IOTC_WIFI_EVENT_STATE_OK :
192 IOTC_WIFI_EVENT_STATE_ERROR;234 IOTC_WIFI_EVENT_STATE_ERROR;
193 235 
@@ -513,7 +555,8 @@ static int32_t CopyScanWifiResultList(IotcWifiList **scanList, const WifiScanInf
513 for (uint32_t i = 0; i < resSize; i++) {555 for (uint32_t i = 0; i < resSize; i++) {
514 if ((strcpy_s(info[i].ssid, sizeof(info[i].ssid), result[i].ssid) != EOK) ||556 if ((strcpy_s(info[i].ssid, sizeof(info[i].ssid), result[i].ssid) != EOK) ||
515 (memcpy_s(info[i].bssid, sizeof(info[i].bssid), result[i].bssid, sizeof(result[i].bssid)) != EOK)) {557 (memcpy_s(info[i].bssid, sizeof(info[i].bssid), result[i].bssid, sizeof(result[i].bssid)) != EOK)) {
516- IotcFree(info);558+ IotcFree(*scanList);
559+ *scanList = NULL;
517 return IOTC_ERR_SECUREC_MEMCPY;560 return IOTC_ERR_SECUREC_MEMCPY;
518 }561 }
519 info[i].rssi = result[i].rssi;562 info[i].rssi = result[i].rssi;
@@ -713,6 +756,7 @@ static void SetSecurityTypeByScanInfo(WifiDeviceConfig *config, const WifiScanIn
713int32_t IotcConnectWifi(void)756int32_t IotcConnectWifi(void)
714{757{
715 int32_t ret;758 int32_t ret;
759+ bool foundTarget = false;
716 WifiDeviceConfig config;760 WifiDeviceConfig config;
717 (void)memset_s(&config, sizeof(config), 0, sizeof(config));761 (void)memset_s(&config, sizeof(config), 0, sizeof(config));
718 WifiScanInfo info;762 WifiScanInfo info;
@@ -732,7 +776,6 @@ int32_t IotcConnectWifi(void)
732 IOTC_LOGW("get wifi config fail");776 IOTC_LOGW("get wifi config fail");
733 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;777 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
734 }778 }
735- 
736 if (config.ssid[0] == '\0') {779 if (config.ssid[0] == '\0') {
737 IOTC_LOGW("ssid null");780 IOTC_LOGW("ssid null");
738 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;781 return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
@@ -740,15 +783,26 @@ int32_t IotcConnectWifi(void)
740 783 
741 for (uint32_t i = 0; i < MAX_SCAN_TIMES; ++i) {784 for (uint32_t i = 0; i < MAX_SCAN_TIMES; ++i) {
742 if (AdvanceScanWifiByOhos(&config) != IOTC_OK) {785 if (AdvanceScanWifiByOhos(&config) != IOTC_OK) {
743- IOTC_LOGW("advance scan wifi fail");786+ IOTC_LOGW("advance scan wifi fail, attempt %u/%u", i + 1, MAX_SCAN_TIMES);
744- (void)memset_s(&config, sizeof(config), 0, sizeof(config));787+ if (i + 1 < MAX_SCAN_TIMES) {
745- return IOTC_ADAPTER_WIFI_ERR_SCAN;788+ IotcSleepMs(WIFI_SCAN_RETRY_INTERVAL_MS);
789+ }
790+ continue;
746 }791 }
747 if (GetScanWifiResultFromOhos(&config, &info)) {792 if (GetScanWifiResultFromOhos(&config, &info)) {
748 SetSecurityTypeByScanInfo(&config, &info);793 SetSecurityTypeByScanInfo(&config, &info);
794+ foundTarget = true;
749 break;795 break;
750 }796 }
751- IOTC_LOGN("not find target wifi, tyr again");797+ IOTC_LOGN("target wifi not found, attempt %u/%u", i + 1, MAX_SCAN_TIMES);
798+ if (i + 1 < MAX_SCAN_TIMES) {
799+ IotcSleepMs(WIFI_SCAN_RETRY_INTERVAL_MS);
800+ }
801+ }
802+ if (!foundTarget) {
803+ IOTC_LOGW("target wifi not found after %u attempts", MAX_SCAN_TIMES);
804+ (void)memset_s(&config, sizeof(config), 0, sizeof(config));
805+ return IOTC_ADAPTER_WIFI_ERR_SCAN;
752 }806 }
753 ret = ConnectTo(config.netId);807 ret = ConnectTo(config.netId);
754 (void)memset_s(&config, sizeof(config), 0, sizeof(config));808 (void)memset_s(&config, sizeof(config), 0, sizeof(config));
@@ -842,6 +896,17 @@ int32_t IotcDisconnectWifi(void)
842 return IOTC_OK;896 return IOTC_OK;
843}897}
844 898 
899+/* WS63 direct SoftAP uses the device base MAC as BSSID. */
900+extern int32_t IotcPlatformConfigureSoftApMac(bool preserveSta) __attribute__((weak));
901+ 
902+/*
903+ * Optional WS63 hook for binary SSIDs. The OpenHarmony Lite HotspotConfig API
904+ * has no SSID length field, while Unified Connect 3.0 appends binary z/p
905+ * bytes and p may be 0x00. The hook consumes the explicit length atomically
906+ * when enabling the AP; platforms without it must reject embedded NUL bytes.
907+ */
908+extern int32_t IotcPlatformEnableSoftApWithSsidLen(uint8_t ssidLen) __attribute__((weak));
909+ 
845static void CheckWifiStateBeforeStartSoftap(void)910static void CheckWifiStateBeforeStartSoftap(void)
846{911{
847 int32_t ret;912 int32_t ret;
@@ -862,31 +927,82 @@ static void CheckWifiStateBeforeStartSoftap(void)
862 }927 }
863}928}
864 929 
930+#if IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP
931+static struct netif *FindSoftapNetif(void)
932+{
933+ struct netif *netif = netifapi_netif_find_by_name(IOTC_SOFTAP_IFNAME_FIRST);
934+ if (netif == NULL) {
935+ netif = netifapi_netif_find_by_name(IOTC_SOFTAP_IFNAME_SECOND);
936+ }
937+ return netif;
938+}
939+ 
940+static int32_t SetupSoftApIpAndDhcp(void)
941+{
942+ struct netif *netif = FindSoftapNetif();
943+ if (netif == NULL) {
944+ IOTC_LOGW("find softap netif error");
945+ return IOTC_ADAPTER_SOFTAP_ERR_START;
946+ }
947+ 
948+ /* 先停后启:wifiservice 可能已在无地址状态下拉起 dhcps,租约池不可用 */
949+ (void)netifapi_dhcps_stop(netif);
950+ 
951+ ip4_addr_t ipaddr = {0};
952+ ip4_addr_t netmask = {0};
953+ ip4_addr_t gw = {0};
954+ IP4_ADDR(&ipaddr, 192, 168, 43, 1);
955+ IP4_ADDR(&netmask, 255, 255, 255, 0);
956+ IP4_ADDR(&gw, 192, 168, 43, 1);
957+ if (netifapi_netif_set_addr(netif, &ipaddr, &netmask, &gw) != 0) {
958+ IOTC_LOGW("set softap addr error");
959+ return IOTC_ADAPTER_SOFTAP_ERR_START;
960+ }
961+ 
962+ err_t ret = netifapi_dhcps_start(netif, NULL, 0);
963+ if (ret != 0) {
964+ IOTC_LOGW("dhcps start error %d", ret);
965+ return IOTC_ADAPTER_SOFTAP_ERR_START;
966+ }
967+ IOTC_LOGI("softap dhcp server started");
968+ return IOTC_OK;
969+}
970+#endif
971+ 
865int32_t IotcStartSoftAp(const uint8_t *ssid, uint32_t ssidLen, const uint8_t *pwd, uint32_t pwdLen)972int32_t IotcStartSoftAp(const uint8_t *ssid, uint32_t ssidLen, const uint8_t *pwd, uint32_t pwdLen)
866{973{
867- (void)pwd;974+ if (ssid == NULL || ssidLen == 0U || ssidLen >= IOTC_WIFI_SSID_MAX_LEN ||
868- (void)pwdLen;975+ (pwd != NULL && (pwdLen < 8U || pwdLen >= IOTC_WIFI_PWD_MAX_LEN))) {
869- if ((ssid == NULL) || (ssidLen == 0)) {
870 IOTC_LOGW("invalid param");976 IOTC_LOGW("invalid param");
871 return IOTC_ERR_PARAM_INVALID;977 return IOTC_ERR_PARAM_INVALID;
872 }978 }
873 979 
874 CheckWifiStateBeforeStartSoftap();980 CheckWifiStateBeforeStartSoftap();
875 981 
982+ int32_t ret = IOTC_OK;
983+ if (IotcPlatformConfigureSoftApMac != NULL) {
984+ ret = IotcPlatformConfigureSoftApMac(false);
985+ if (ret != IOTC_OK) {
986+ IOTC_LOGW("set softap mac fail %d", ret);
987+ return IOTC_ADAPTER_SOFTAP_ERR_START;
988+ }
989+ }
990+ 
876 HotspotConfig config;991 HotspotConfig config;
877 (void)memset_s(&config, sizeof(HotspotConfig), 0, sizeof(HotspotConfig));992 (void)memset_s(&config, sizeof(HotspotConfig), 0, sizeof(HotspotConfig));
878 993 
879- int32_t ret = strncpy_s(config.ssid, sizeof(config.ssid), (const char *)ssid, ssidLen);994+ ret = memcpy_s(config.ssid, sizeof(config.ssid), ssid, ssidLen);
880 if (ret != EOK) {995 if (ret != EOK) {
881- IOTC_LOGW("strcpy error %u", ssidLen);996+ IOTC_LOGW("copy ssid error %u", ssidLen);
882- return IOTC_ERR_SECUREC_STRCPY;997+ return IOTC_ERR_SECUREC_MEMCPY;
883 }998 }
999+ config.ssid[ssidLen] = '\0';
884 1000 
885- if (pwd != NULL && pwdLen != 0) {1001+ if (pwd != NULL && pwdLen != 0U) {
886- ret = strncpy_s(config.preSharedKey, sizeof(config.preSharedKey), (const char *)pwd, pwdLen);1002+ ret = memcpy_s(config.preSharedKey, sizeof(config.preSharedKey), pwd, pwdLen);
887 if (ret != EOK) {1003 if (ret != EOK) {
888- IOTC_LOGW("strcpy error %u", pwdLen);1004+ IOTC_LOGW("copy password error %u", pwdLen);
889- return IOTC_ERR_SECUREC_STRCPY;1005+ return IOTC_ERR_SECUREC_MEMCPY;
890 }1006 }
891 config.securityType = WIFI_SEC_TYPE_PSK;1007 config.securityType = WIFI_SEC_TYPE_PSK;
892 } else {1008 } else {
@@ -901,12 +1017,30 @@ int32_t IotcStartSoftAp(const uint8_t *ssid, uint32_t ssidLen, const uint8_t *pw
901 }1017 }
902 1018 
903 IOTC_LOGI("start softap");1019 IOTC_LOGI("start softap");
904- ret = EnableHotspot();1020+ if (IotcPlatformEnableSoftApWithSsidLen != NULL) {
1021+ ret = IotcPlatformEnableSoftApWithSsidLen((uint8_t)ssidLen);
1022+ } else {
1023+ /* A string-only platform cannot represent a NUL byte inside the SSID IE. */
1024+ if (memchr(ssid, '\0', ssidLen) != NULL) {
1025+ IOTC_LOGW("binary ssid unsupported by platform");
1026+ return IOTC_ADAPTER_SOFTAP_ERR_START;
1027+ }
1028+ ret = EnableHotspot();
1029+ }
905 if (ret != WIFI_SUCCESS) {1030 if (ret != WIFI_SUCCESS) {
906 IOTC_LOGW("enable hotspot fail %d", ret);1031 IOTC_LOGW("enable hotspot fail %d", ret);
907 return IOTC_ADAPTER_SOFTAP_ERR_START;1032 return IOTC_ADAPTER_SOFTAP_ERR_START;
908 }1033 }
909 1034 
1035+#if IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP
1036+ ret = SetupSoftApIpAndDhcp();
1037+ if (ret != IOTC_OK) {
1038+ IOTC_LOGW("setup softap ip/dhcp fail %d", ret);
1039+ (void)DisableHotspot();
1040+ return ret;
1041+ }
1042+#endif
1043+ 
910 return IOTC_OK;1044 return IOTC_OK;
911}1045}
912 1046 
@@ -927,6 +1061,73 @@ void IotcUnregEventCallback(void)
927 (void)memset_s(&g_adapterEventHandler, sizeof(IotcWifiEvent), 0, sizeof(IotcWifiEvent));1061 (void)memset_s(&g_adapterEventHandler, sizeof(IotcWifiEvent), 0, sizeof(IotcWifiEvent));
928}1062}
929 1063 
1064+#if IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP
1065+typedef struct {
1066+ IotcStationInfo *stations;
1067+ uint32_t stationNum;
1068+ uint32_t resolvedNum;
1069+} SoftapArpResolveArg;
1070+ 
1071+/*
1072+ * WS63 的精简 lwIP 配置关闭了 LWIP_NETIFAPI_DHCPS_IP,GetStationList 只能返回
1073+ * 已关联终端的 MAC,ipAddress 恒为 0。这里在 tcpip 线程中读取 ARP 表,并且仅在
1074+ * ARP MAC 同时存在于驱动的已关联列表时回填 IP,避免仅凭来源 IP 接受未关联终端。
1075+ */
1076+static err_t ResolveSoftapStationIpFromArp(struct netif *softapNetif, void *arg)
1077+{
1078+ SoftapArpResolveArg *resolveArg = (SoftapArpResolveArg *)arg;
1079+ if (softapNetif == NULL || resolveArg == NULL || resolveArg->stations == NULL) {
1080+ return ERR_VAL;
1081+ }
1082+ 
1083+ for (size_t i = 0; i < ARP_TABLE_SIZE; ++i) {
1084+ ip4_addr_t *ipaddr = NULL;
1085+ struct netif *entryNetif = NULL;
1086+ struct eth_addr *ethaddr = NULL;
1087+ if (etharp_get_entry(i, &ipaddr, &entryNetif, &ethaddr) == 0 || entryNetif != softapNetif ||
1088+ ipaddr == NULL || ethaddr == NULL) {
1089+ continue;
1090+ }
1091+ 
1092+ for (uint32_t j = 0; j < resolveArg->stationNum; ++j) {
1093+ IotcStationInfo *station = &resolveArg->stations[j];
1094+ if (station->ip != 0 || memcmp(station->mac, ethaddr->addr, sizeof(station->mac)) != 0) {
1095+ continue;
1096+ }
1097+ station->ip = lwip_ntohl(ip4_addr_get_u32(ipaddr));
1098+ ++resolveArg->resolvedNum;
1099+ break;
1100+ }
1101+ }
1102+ return ERR_OK;
1103+}
1104+ 
1105+static void FillSoftapStationIpFromArp(IotcStationInfo *stations, uint32_t stationNum)
1106+{
1107+ if (stations == NULL || stationNum == 0) {
1108+ return;
1109+ }
1110+ 
1111+ struct netif *netif = FindSoftapNetif();
1112+ if (netif == NULL) {
1113+ IOTC_LOGW("find softap netif for arp error");
1114+ return;
1115+ }
1116+ 
1117+ SoftapArpResolveArg arg = {
1118+ .stations = stations,
1119+ .stationNum = stationNum,
1120+ .resolvedNum = 0,
1121+ };
1122+ err_t ret = netifapi_netif_call_argcb(netif, ResolveSoftapStationIpFromArp, &arg);
1123+ if (ret != ERR_OK) {
1124+ IOTC_LOGW("resolve softap station ip from arp error %d", ret);
1125+ return;
1126+ }
1127+ IOTC_LOGD("resolved softap station ip from arp %u/%u", arg.resolvedNum, stationNum);
1128+}
1129+#endif
1130+ 
930int32_t IotcGetSoftapStationInfo(IotcStationList **staList)1131int32_t IotcGetSoftapStationInfo(IotcStationList **staList)
931{1132{
932 if (staList == NULL || *staList != NULL) {1133 if (staList == NULL || *staList != NULL) {
@@ -958,6 +1159,9 @@ int32_t IotcGetSoftapStationInfo(IotcStationList **staList)
958 return IOTC_ERR_SECUREC_MEMCPY;1159 return IOTC_ERR_SECUREC_MEMCPY;
959 }1160 }
960 }1161 }
1162+#if IOTC_CONF_ADAPTER_WIFI_MANAGE_SOFTAP_IP
1163+ FillSoftapStationIpFromArp(staInfo, size);
1164+#endif
961 (*staList)->num = size;1165 (*staList)->num = size;
962 return IOTC_OK;1166 return IOTC_OK;
963}1167}
@@ -1006,4 +1210,4 @@ int32_t IotcGetWifiMode(void)
1006 return IOTC_WIFI_MODE_STATION;1210 return IOTC_WIFI_MODE_STATION;
1007 }1211 }
1008 return IOTC_WIFI_MODE_INVALID;1212 return IOTC_WIFI_MODE_INVALID;
1009-}1213+}
Mbundle.json+4-1
@@ -29,6 +29,7 @@
29 "iot_connect_ble_support",29 "iot_connect_ble_support",
30 "iot_connect_sle_support",30 "iot_connect_sle_support",
31 "iot_connect_wifi_support",31 "iot_connect_wifi_support",
32+ "iot_connect_wifi_only_support",
32 "iot_connect_eth_support",33 "iot_connect_eth_support",
33 "iot_connect_ailife_support",34 "iot_connect_ailife_support",
34 "iot_connect_mbedtls_v2_support",35 "iot_connect_mbedtls_v2_support",
@@ -37,10 +38,12 @@
37 "iot_connect_databus_support",38 "iot_connect_databus_support",
38 "iot_connect_ble_adv_update_support",39 "iot_connect_ble_adv_update_support",
39 "iot_connect_oh_nearlink_support",40 "iot_connect_oh_nearlink_support",
41+ "iot_connect_speke_secret_trace",
40 "iot_connect_ssl_backend",42 "iot_connect_ssl_backend",
41 "iot_connect_openhitls_include_dirs",43 "iot_connect_openhitls_include_dirs",
42 "iot_connect_openhitls_defines",44 "iot_connect_openhitls_defines",
43- "iot_connect_openhitls_deps"45+ "iot_connect_openhitls_deps",
46+ "iot_connect_mutex_lockless"
44 ],47 ],
45 "rom": "",48 "rom": "",
46 "ram": "",49 "ram": "",
Mcore/device/config/CMakeLists.txt+3-1
@@ -22,6 +22,8 @@ set(IOTC_SRC_LIST
22 ${CMAKE_CURRENT_SOURCE_DIR}/config_register_info.c22 ${CMAKE_CURRENT_SOURCE_DIR}/config_register_info.c
23 ${CMAKE_CURRENT_SOURCE_DIR}/config_revoke.c23 ${CMAKE_CURRENT_SOURCE_DIR}/config_revoke.c
24 ${CMAKE_CURRENT_SOURCE_DIR}/config_revoke_flag.c24 ${CMAKE_CURRENT_SOURCE_DIR}/config_revoke_flag.c
25+ ${CMAKE_CURRENT_SOURCE_DIR}/device_state_store.c
26+ ${CMAKE_CURRENT_SOURCE_DIR}/versioned_store.c
25 PARENT_SCOPE27 PARENT_SCOPE
26)28)
27 29 
@@ -35,4 +37,4 @@ set(IOTC_TEST_INC_LIST
35 ${IOTC_TEST_INC_LIST}37 ${IOTC_TEST_INC_LIST}
36 ${CMAKE_CURRENT_SOURCE_DIR}38 ${CMAKE_CURRENT_SOURCE_DIR}
37 PARENT_SCOPE39 PARENT_SCOPE
38-)40+)
Mcore/device/config/config_revoke.c+35-2
@@ -18,14 +18,47 @@
18#include "iotc_errcode.h"18#include "iotc_errcode.h"
19#include "config_revoke_flag.h"19#include "config_revoke_flag.h"
20#include "config_register_info.h"20#include "config_register_info.h"
21+#include "config_login_info.h"
21#include "config_authinfo.h"22#include "config_authinfo.h"
23+#include "device_state_store.h"
24+#include "sec_conf_info.h"
25+#include "sec_conf_key_cert.h"
26+#include "iotc_conf.h"
22#include "iotc_log.h"27#include "iotc_log.h"
23#include "utils_combo.h"28#include "utils_combo.h"
24 29 
25static void RevokeEventConfigCallback(uint32_t event, void *param, uint32_t len)30static void RevokeEventConfigCallback(uint32_t event, void *param, uint32_t len)
26{31{
32+ (void)event;
33+ (void)param;
34+ (void)len;
35+#if IOTC_CONF_WIFI_ONLY_SUPPORT
36+ /*
37+ * MAIN_RESTORE is synchronous and the framework reboots immediately after
38+ * its subscribers return. Clear the pending HubSetup record and security
39+ * configuration before reboot rather than leaving a partially reset device.
40+ */
41+ int32_t ret = IotcDssFactoryResetRun();
42+ if (ret != IOTC_OK) {
43+ IOTC_LOGW("run WiFi Only factory reset error %d", ret);
44+ }
45+ ret = SecConfInfoFactoryReset();
46+ if (ret != IOTC_OK) {
47+ IOTC_LOGW("clear security config error %d", ret);
48+ }
49+ ret = SecConfKeyCertFactoryReset();
50+ if (ret != IOTC_OK) {
51+ IOTC_LOGW("clear key cert error %d", ret);
52+ }
53+ ret = ConfigClearLoginInfo();
54+ if (ret != IOTC_OK) {
55+ IOTC_LOGW("clear login info error %d", ret);
56+ }
57+#else
58+ int32_t ret;
59+#endif
27 /* After the cloud function is complete, add the factory reset flag */60 /* After the cloud function is complete, add the factory reset flag */
28- int32_t ret = ConfigClearRegisterInfo();61+ ret = ConfigClearRegisterInfo();
29 if (ret != IOTC_OK) {62 if (ret != IOTC_OK) {
30 IOTC_LOGW("clear register info error %d", ret);63 IOTC_LOGW("clear register info error %d", ret);
31 }64 }
@@ -53,4 +86,4 @@ int32_t ConfigRevokeInit(void)
53void ConfigRevokeDeinit(void)86void ConfigRevokeDeinit(void)
54{87{
55 (void)EventBusUnsubscribe(RevokeEventConfigCallback);88 (void)EventBusUnsubscribe(RevokeEventConfigCallback);
56-}89+}
Acore/device/config/device_state_store.c+99-0
@@ -0,0 +1,99 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device 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 "device_state_store.h"
17+ 
18+#include <stddef.h>
19+ 
20+#include "iotc_errcode.h"
21+#include "securec.h"
22+#include "utils_assert.h"
23+ 
24+static int32_t SaveRecord(const char *domain, const void *data, uint32_t len)
25+{
26+ int32_t ret = IotcVsBeginWrite(domain, IOTC_DSS_SCHEMA_VERSION, (const uint8_t *)data, len);
27+ CHECK_RETURN(ret == IOTC_OK, ret);
28+ return IotcVsCommit(domain);
29+}
30+ 
31+static int32_t LoadRecord(const char *domain, void *data, uint32_t len)
32+{
33+ uint32_t actual = len;
34+ int32_t ret = IotcVsLoad(domain, IOTC_DSS_SCHEMA_VERSION, (uint8_t *)data, &actual);
35+ CHECK_RETURN(ret == IOTC_OK, ret);
36+ CHECK_RETURN(actual == len, IOTC_CORE_DEVICE_ERR_STORE_CORRUPT);
37+ return IOTC_OK;
38+}
39+ 
40+static uint32_t BoundedLen(const char *value, uint32_t maxLen)
41+{
42+ /* 本树 securec 未提供 strnlen_s,按 bounded 语义本地实现。 */
43+ uint32_t len = 0;
44+ while (len < maxLen && value[len] != '\0') {
45+ ++len;
46+ }
47+ return len;
48+}
49+ 
50+static int32_t ValidatePending(const IotcDssProvisionPending *pending)
51+{
52+ CHECK_RETURN(pending != NULL, IOTC_ERR_PARAM_INVALID);
53+ CHECK_RETURN(pending->mdidLen > 0 && pending->mdidLen <= IOTC_DSS_MDID_MAX_LEN, IOTC_ERR_PARAM_INVALID);
54+ CHECK_RETURN(BoundedLen(pending->tempDevId, IOTC_DSS_DEV_ID_MAX_LEN + 1) <= IOTC_DSS_DEV_ID_MAX_LEN,
55+ IOTC_ERR_PARAM_INVALID);
56+ CHECK_RETURN(BoundedLen(pending->registerCode, IOTC_DSS_REGISTER_CODE_MAX_LEN + 1) <=
57+ IOTC_DSS_REGISTER_CODE_MAX_LEN, IOTC_ERR_PARAM_INVALID);
58+ CHECK_RETURN(pending->onlineNotifyDone <= 1, IOTC_ERR_PARAM_INVALID);
59+ CHECK_RETURN(pending->state == IOTC_DSS_PENDING_STATE_WAIT_ACTIVATE ||
60+ pending->state == IOTC_DSS_PENDING_STATE_ACTIVATING, IOTC_ERR_PARAM_INVALID);
61+ return IOTC_OK;
62+}
63+ 
64+int32_t IotcDssSaveProvisionPending(const IotcDssProvisionPending *pending)
65+{
66+ int32_t ret = ValidatePending(pending);
67+ CHECK_RETURN(ret == IOTC_OK, ret);
68+ return SaveRecord(IOTC_DSS_DOMAIN_PROVISION_PENDING, pending, sizeof(*pending));
69+}
70+ 
71+int32_t IotcDssLoadProvisionPending(IotcDssProvisionPending *pending)
72+{
73+ CHECK_RETURN(pending != NULL, IOTC_ERR_PARAM_INVALID);
74+ int32_t ret = LoadRecord(IOTC_DSS_DOMAIN_PROVISION_PENDING, pending, sizeof(*pending));
75+ CHECK_RETURN(ret == IOTC_OK, ret);
76+ return ValidatePending(pending);
77+}
78+ 
79+int32_t IotcDssEraseProvisionPending(void)
80+{
81+ return IotcVsErase(IOTC_DSS_DOMAIN_PROVISION_PENDING);
82+}
83+ 
84+int32_t IotcDssMarkOnlineNotifyDone(void)
85+{
86+ IotcDssProvisionPending pending = {0};
87+ int32_t ret = IotcDssLoadProvisionPending(&pending);
88+ if (ret == IOTC_OK && pending.onlineNotifyDone == 0) {
89+ pending.onlineNotifyDone = 1;
90+ ret = IotcDssSaveProvisionPending(&pending);
91+ }
92+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
93+ return ret;
94+}
95+ 
96+int32_t IotcDssFactoryResetRun(void)
97+{
98+ return IotcVsErase(IOTC_DSS_DOMAIN_PROVISION_PENDING);
99+}
Acore/device/config/include/device_state_store.h+79-0
@@ -0,0 +1,79 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device 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+#ifndef IOTC_DEVICE_STATE_STORE_H
17+#define IOTC_DEVICE_STATE_STORE_H
18+ 
19+#include <stdint.h>
20+ 
21+#include "versioned_store.h"
22+ 
23+#ifdef __cplusplus
24+extern "C" {
25+#endif
26+ 
27+/*
28+ * 统一互联 3.0 WiFi Only 配网待完成状态持久化。基于 versioned_store
29+ * 双槽机制,记录带 schema 版本、generation、长度和完整性校验。
30+ *
31+ * schema v2 保存 HubSetup 的原始 MDID、临时 devId、注册码、请求摘要和
32+ * /onlinenotify 完成标记;完成标记复用旧结构的第一个保留字节,保持已落盘
33+ * v2 记录的长度和字段偏移不变。
34+ * Wi-Fi 密码只参与请求摘要,不写入本模块的双槽记录。
35+ *
36+ * Wi-Fi 配置交给 WS63 Wi-Fi 配置存储,安全凭据只经 security service,
37+ * 本模块不直接读写。
38+ */
39+ 
40+#define IOTC_DSS_SCHEMA_VERSION 2
41+ 
42+#define IOTC_DSS_DOMAIN_PROVISION_PENDING "prov_pending"
43+ 
44+#define IOTC_DSS_MDID_MAX_LEN 40
45+#define IOTC_DSS_DEV_ID_MAX_LEN 40
46+#define IOTC_DSS_REGISTER_CODE_MAX_LEN 40
47+ 
48+typedef enum {
49+ IOTC_DSS_PENDING_STATE_WAIT_ACTIVATE = 0,
50+ IOTC_DSS_PENDING_STATE_ACTIVATING,
51+} IotcDssPendingState;
52+ 
53+typedef struct {
54+ uint8_t mdid[IOTC_DSS_MDID_MAX_LEN];
55+ uint8_t mdidLen;
56+ uint8_t onlineNotifyDone;
57+ uint8_t reserved[2];
58+ char tempDevId[IOTC_DSS_DEV_ID_MAX_LEN + 1];
59+ char registerCode[IOTC_DSS_REGISTER_CODE_MAX_LEN + 1];
60+ uint8_t requestHash[32];
61+ uint32_t state;
62+} IotcDssProvisionPending;
63+ 
64+/* Save 为 BeginWrite+Commit 的便利封装。 */
65+int32_t IotcDssSaveProvisionPending(const IotcDssProvisionPending *pending);
66+int32_t IotcDssLoadProvisionPending(IotcDssProvisionPending *pending);
67+int32_t IotcDssEraseProvisionPending(void);
68+ 
69+/* 五次 onlinenotify 全部交给 socket 后原子标记;断电只会看到未完成或完整完成。 */
70+int32_t IotcDssMarkOnlineNotifyDone(void);
71+ 
72+/* 恢复出厂时清除本模块保存的配网待完成记录。 */
73+int32_t IotcDssFactoryResetRun(void);
74+ 
75+#ifdef __cplusplus
76+}
77+#endif
78+ 
79+#endif /* IOTC_DEVICE_STATE_STORE_H */
Acore/device/config/include/versioned_store.h+67-0
@@ -0,0 +1,67 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device 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+#ifndef IOTC_DEVICE_VERSIONED_STORE_H
17+#define IOTC_DEVICE_VERSIONED_STORE_H
18+ 
19+#include <stdint.h>
20+ 
21+#ifdef __cplusplus
22+extern "C" {
23+#endif
24+ 
25+/*
26+ * 双槽版本化记录存储(开发计划 7.2 事务提交规则、W4 开发内容 2/3/6/7)。
27+ *
28+ * 每个域(domain)占用两个 KV 槽位。记录头包含 magic、头部版本、payload
29+ * schema 版本、代际 generation、提交状态、长度和 CRC32。写入流程:
30+ * IotcVsBeginWrite(写 pending 新槽并回读校验)->
31+ * IotcVsCommit(标记 committed、回读校验、最后擦旧槽)
32+ * 或 IotcVsAbort(擦新槽,保留旧值)。
33+ *
34+ * Load 只在 committed 记录中取 generation 较高者,pending 记录不可见。
35+ * 因此 BeginWrite 与外部动作(例如保存 Wi-Fi)之间断电仍加载旧值;Commit
36+ * 标记撕裂时旧 committed 槽仍在,标记成功后即使擦旧槽失败也加载完整新值。
37+ *
38+ * schema 版本不兼容的记录返回 IOTC_CORE_DEVICE_ERR_STORE_SCHEMA,由调用方
39+ * 决定升级迁移或 IotcVsErase 后重建;本模块不静默吞掉不兼容数据。
40+ */
41+ 
42+#define IOTC_VS_MAX_DOMAIN_LEN 24
43+#define IOTC_VS_MAX_PAYLOAD_LEN 512
44+ 
45+/* 加载域内有效记录。bufLen 入参为缓冲区容量,出参为实际长度。 */
46+int32_t IotcVsLoad(const char *domain, uint16_t expectedSchema, uint8_t *buf, uint32_t *bufLen);
47+ 
48+/* 向非 active 槽写入 generation+1 的 pending 记录并回读校验;此时 Load 仍返回旧值。 */
49+int32_t IotcVsBeginWrite(const char *domain, uint16_t schemaVersion, const uint8_t *data, uint32_t len);
50+ 
51+/* 提交:先把 pending 标记为 committed 并校验,再擦除旧槽。 */
52+int32_t IotcVsCommit(const char *domain);
53+ 
54+/* 回滚:擦除 BeginWrite 写入的新槽,保留旧记录。 */
55+int32_t IotcVsAbort(const char *domain);
56+ 
57+/* 擦除域内两个槽位(恢复出厂/不兼容重建使用)。 */
58+int32_t IotcVsErase(const char *domain);
59+ 
60+/* 查询当前有效记录的 generation;无有效记录返回 IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND。 */
61+int32_t IotcVsGetGeneration(const char *domain, uint32_t *generation);
62+ 
63+#ifdef __cplusplus
64+}
65+#endif
66+ 
67+#endif /* IOTC_DEVICE_VERSIONED_STORE_H */
Acore/device/config/versioned_store.c+382-0
@@ -0,0 +1,382 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device 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 "versioned_store.h"
17+ 
18+#include <stdbool.h>
19+#include <stddef.h>
20+#include <string.h>
21+ 
22+#include "iotc_errcode.h"
23+#include "iotc_kv.h"
24+#include "securec.h"
25+#include "utils_assert.h"
26+ 
27+#define IOTC_VS_MAGIC 0x56535243U /* "VSRC" */
28+#define IOTC_VS_HEADER_VERSION 2U
29+#define IOTC_VS_STATE_PENDING 0x50454E44U /* "PEND" */
30+#define IOTC_VS_STATE_COMMITTED 0x434F4D4DU /* "COMM" */
31+#define IOTC_VS_SLOT_COUNT 2
32+#define IOTC_VS_KEY_PREFIX "vs_"
33+#define IOTC_VS_KEY_BUF_LEN 40
34+#define IOTC_VS_RECORD_BUF_LEN (sizeof(IotcVsHeader) + IOTC_VS_MAX_PAYLOAD_LEN)
35+ 
36+typedef struct {
37+ uint32_t magic;
38+ uint16_t headerVersion;
39+ uint16_t schemaVersion;
40+ uint32_t generation;
41+ uint32_t payloadLen;
42+ uint32_t state;
43+ uint32_t crc32;
44+} IotcVsHeader;
45+ 
46+static uint32_t Crc32Update(uint32_t crc, const uint8_t *data, uint32_t len)
47+{
48+ crc = ~crc;
49+ for (uint32_t i = 0; i < len; ++i) {
50+ crc ^= data[i];
51+ for (uint32_t bit = 0; bit < 8; ++bit) {
52+ crc = (crc >> 1) ^ ((crc & 1U) ? 0xEDB88320U : 0U);
53+ }
54+ }
55+ return ~crc;
56+}
57+ 
58+static uint32_t ComputeRecordCrc(const IotcVsHeader *header, const uint8_t *payload)
59+{
60+ /* 覆盖 magic 之后、CRC 字段之前的头字段和整个 payload。 */
61+ uint32_t crc = Crc32Update(0, (const uint8_t *)header + sizeof(uint32_t),
62+ sizeof(IotcVsHeader) - sizeof(uint32_t) - sizeof(uint32_t));
63+ return Crc32Update(crc, payload, header->payloadLen);
64+}
65+ 
66+static int32_t ValidateDomainName(const char *domain)
67+{
68+ CHECK_RETURN(domain != NULL, IOTC_ERR_PARAM_INVALID);
69+ uint32_t domainLen = 0;
70+ while (domainLen <= IOTC_VS_MAX_DOMAIN_LEN && domain[domainLen] != '\0') {
71+ char c = domain[domainLen];
72+ bool valid = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_');
73+ CHECK_RETURN(valid, IOTC_ERR_PARAM_INVALID);
74+ ++domainLen;
75+ }
76+ CHECK_RETURN(domainLen > 0 && domainLen <= IOTC_VS_MAX_DOMAIN_LEN && domain[domainLen] == '\0',
77+ IOTC_ERR_PARAM_INVALID);
78+ return IOTC_OK;
79+}
80+ 
81+static int32_t BuildSlotKey(const char *domain, uint32_t slot, char *key, uint32_t keyLen)
82+{
83+ CHECK_RETURN(key != NULL && slot < IOTC_VS_SLOT_COUNT, IOTC_ERR_PARAM_INVALID);
84+ int32_t ret = ValidateDomainName(domain);
85+ CHECK_RETURN(ret == IOTC_OK, ret);
86+ 
87+ int written = snprintf_s(key, keyLen, keyLen - 1, "%s%s_%c", IOTC_VS_KEY_PREFIX, domain,
88+ (slot == 0) ? 'a' : 'b');
89+ CHECK_RETURN(written > 0, IOTC_ERR_SECUREC_SPRINTF);
90+ return IOTC_OK;
91+}
92+ 
93+/* 读取并校验一个槽位;有效返回 IOTC_OK 并填充 header/payload。 */
94+static int32_t ReadSlot(const char *domain, uint32_t slot, IotcVsHeader *header, uint8_t *payload,
95+ uint32_t payloadCap)
96+{
97+ char key[IOTC_VS_KEY_BUF_LEN] = {0};
98+ int32_t ret = BuildSlotKey(domain, slot, key, sizeof(key));
99+ CHECK_RETURN(ret == IOTC_OK, ret);
100+ 
101+ uint32_t valueLen = 0;
102+ ret = IotcKvGetLen(key, &valueLen);
103+ CHECK_RETURN(ret == IOTC_OK, IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND);
104+ if (valueLen < sizeof(IotcVsHeader) || valueLen > IOTC_VS_RECORD_BUF_LEN) {
105+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
106+ }
107+ 
108+ uint8_t record[IOTC_VS_RECORD_BUF_LEN] = {0};
109+ uint32_t readLen = valueLen;
110+ ret = IotcKvGetValue(key, record, &readLen);
111+ if (ret != IOTC_OK || readLen != valueLen) {
112+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
113+ }
114+ 
115+ IotcVsHeader tmp = {0};
116+ if (memcpy_s(&tmp, sizeof(tmp), record, sizeof(tmp)) != EOK) {
117+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
118+ }
119+ if (tmp.magic != IOTC_VS_MAGIC || tmp.headerVersion != IOTC_VS_HEADER_VERSION ||
120+ (tmp.state != IOTC_VS_STATE_PENDING && tmp.state != IOTC_VS_STATE_COMMITTED) ||
121+ tmp.payloadLen > IOTC_VS_MAX_PAYLOAD_LEN ||
122+ valueLen != sizeof(IotcVsHeader) + tmp.payloadLen) {
123+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
124+ }
125+ if (payload != NULL && tmp.payloadLen > payloadCap) {
126+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
127+ }
128+ if (payload != NULL && memcpy_s(payload, payloadCap, record + sizeof(IotcVsHeader), tmp.payloadLen) != EOK) {
129+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
130+ }
131+ if (ComputeRecordCrc(&tmp, (payload != NULL) ? payload : record + sizeof(IotcVsHeader)) != tmp.crc32) {
132+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
133+ }
134+ if (header != NULL && memcpy_s(header, sizeof(*header), &tmp, sizeof(tmp)) != EOK) {
135+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
136+ }
137+ return IOTC_OK;
138+}
139+ 
140+/* 返回两槽中已提交记录的最高 generation;pending 记录绝不能对 Load 可见。 */
141+static int32_t FindActive(const char *domain, IotcVsHeader *header, uint8_t *payload, uint32_t payloadCap,
142+ uint32_t *slotOut)
143+{
144+ IotcVsHeader headers[IOTC_VS_SLOT_COUNT] = {0};
145+ int32_t rets[IOTC_VS_SLOT_COUNT] = {0};
146+ for (uint32_t slot = 0; slot < IOTC_VS_SLOT_COUNT; ++slot) {
147+ bool wantPayload = (payload != NULL);
148+ rets[slot] = ReadSlot(domain, slot, &headers[slot],
149+ wantPayload ? payload + (uint32_t)slot * payloadCap : NULL, payloadCap);
150+ }
151+ 
152+ int best = -1;
153+ for (uint32_t slot = 0; slot < IOTC_VS_SLOT_COUNT; ++slot) {
154+ if (rets[slot] == IOTC_OK && headers[slot].state == IOTC_VS_STATE_COMMITTED &&
155+ (best < 0 || headers[slot].generation > headers[best].generation)) {
156+ best = (int)slot;
157+ }
158+ }
159+ CHECK_RETURN(best >= 0, IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND);
160+ 
161+ if (header != NULL && memcpy_s(header, sizeof(*header), &headers[best], sizeof(IotcVsHeader)) != EOK) {
162+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
163+ }
164+ if (payload != NULL && payloadCap > 0 &&
165+ memmove_s(payload, payloadCap, payload + (uint32_t)best * payloadCap, headers[best].payloadLen) != EOK) {
166+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
167+ }
168+ if (slotOut != NULL) {
169+ *slotOut = (uint32_t)best;
170+ }
171+ return IOTC_OK;
172+}
173+ 
174+static int32_t EraseSlot(const char *domain, uint32_t slot)
175+{
176+ char key[IOTC_VS_KEY_BUF_LEN] = {0};
177+ int32_t ret = BuildSlotKey(domain, slot, key, sizeof(key));
178+ CHECK_RETURN(ret == IOTC_OK, ret);
179+ uint32_t valueLen = 0;
180+ if (IotcKvGetLen(key, &valueLen) != IOTC_OK) {
181+ return IOTC_OK; /* key 不存在视为已擦除,保证恢复出厂幂等 */
182+ }
183+ if (valueLen == 0) {
184+ return IOTC_OK;
185+ }
186+ ret = IotcKvDelValue(key);
187+ return (ret == IOTC_OK) ? IOTC_OK : IOTC_CORE_DEVICE_ERR_STORE_KV;
188+}
189+ 
190+int32_t IotcVsLoad(const char *domain, uint16_t expectedSchema, uint8_t *buf, uint32_t *bufLen)
191+{
192+ CHECK_RETURN(buf != NULL && bufLen != NULL, IOTC_ERR_PARAM_INVALID);
193+ int32_t domainRet = ValidateDomainName(domain);
194+ CHECK_RETURN(domainRet == IOTC_OK, domainRet);
195+ uint32_t cap = *bufLen;
196+ 
197+ IotcVsHeader header = {0};
198+ uint8_t payload[IOTC_VS_SLOT_COUNT * IOTC_VS_MAX_PAYLOAD_LEN] = {0};
199+ int32_t ret = FindActive(domain, &header, payload, IOTC_VS_MAX_PAYLOAD_LEN, NULL);
200+ CHECK_RETURN(ret == IOTC_OK, ret);
201+ CHECK_RETURN(header.payloadLen <= cap, IOTC_ERR_PARAM_INVALID);
202+ if (header.schemaVersion != expectedSchema) {
203+ IOTC_LOGW("schema mismatch domain=%s have=%u want=%u", domain, header.schemaVersion, expectedSchema);
204+ return IOTC_CORE_DEVICE_ERR_STORE_SCHEMA;
205+ }
206+ if (memcpy_s(buf, cap, payload, header.payloadLen) != EOK) {
207+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
208+ }
209+ *bufLen = header.payloadLen;
210+ return IOTC_OK;
211+}
212+ 
213+int32_t IotcVsBeginWrite(const char *domain, uint16_t schemaVersion, const uint8_t *data, uint32_t len)
214+{
215+ CHECK_RETURN(data != NULL, IOTC_ERR_PARAM_INVALID);
216+ int32_t domainRet = ValidateDomainName(domain);
217+ CHECK_RETURN(domainRet == IOTC_OK, domainRet);
218+ CHECK_RETURN(len > 0 && len <= IOTC_VS_MAX_PAYLOAD_LEN, IOTC_ERR_PARAM_INVALID);
219+ 
220+ IotcVsHeader active = {0};
221+ uint32_t activeSlot = 0;
222+ int32_t ret = FindActive(domain, &active, NULL, 0, &activeSlot);
223+ uint32_t newGeneration = 1;
224+ uint32_t targetSlot = 0;
225+ if (ret == IOTC_OK) {
226+ newGeneration = active.generation + 1U;
227+ targetSlot = (activeSlot == 0) ? 1U : 0U;
228+ } else if (ret == IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND) {
229+ newGeneration = 1;
230+ targetSlot = 0;
231+ } else {
232+ return ret;
233+ }
234+ CHECK_RETURN(newGeneration != 0, IOTC_CORE_DEVICE_ERR_STORE_STATE); /* generation 回绕拒绝 */
235+ 
236+ IotcVsHeader header = {0};
237+ header.magic = IOTC_VS_MAGIC;
238+ header.headerVersion = IOTC_VS_HEADER_VERSION;
239+ header.schemaVersion = schemaVersion;
240+ header.generation = newGeneration;
241+ header.payloadLen = len;
242+ header.state = IOTC_VS_STATE_PENDING;
243+ header.crc32 = ComputeRecordCrc(&header, data);
244+ 
245+ uint8_t record[IOTC_VS_RECORD_BUF_LEN] = {0};
246+ if (memcpy_s(record, sizeof(record), &header, sizeof(header)) != EOK ||
247+ memcpy_s(record + sizeof(header), sizeof(record) - sizeof(header), data, len) != EOK) {
248+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
249+ }
250+ 
251+ char key[IOTC_VS_KEY_BUF_LEN] = {0};
252+ ret = BuildSlotKey(domain, targetSlot, key, sizeof(key));
253+ CHECK_RETURN(ret == IOTC_OK, ret);
254+ ret = IotcKvSetValue(key, record, (uint32_t)sizeof(header) + len);
255+ CHECK_RETURN(ret == IOTC_OK, IOTC_CORE_DEVICE_ERR_STORE_KV);
256+ 
257+ /* 回读校验:失败则擦除新槽,保留旧值。 */
258+ IotcVsHeader verify = {0};
259+ uint8_t verifyPayload[IOTC_VS_MAX_PAYLOAD_LEN] = {0};
260+ ret = ReadSlot(domain, targetSlot, &verify, verifyPayload, sizeof(verifyPayload));
261+ if (ret != IOTC_OK || verify.generation != newGeneration || verify.schemaVersion != schemaVersion ||
262+ verify.payloadLen != len || verify.state != IOTC_VS_STATE_PENDING ||
263+ memcmp(verifyPayload, data, len) != 0) {
264+ (void)EraseSlot(domain, targetSlot);
265+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
266+ }
267+ return IOTC_OK;
268+}
269+ 
270+/* 读取两槽的原始状态;valid[i] 标记槽位是否有完整有效记录。 */
271+static void ReadBothSlots(const char *domain, IotcVsHeader headers[IOTC_VS_SLOT_COUNT],
272+ bool valid[IOTC_VS_SLOT_COUNT])
273+{
274+ for (uint32_t slot = 0; slot < IOTC_VS_SLOT_COUNT; ++slot) {
275+ valid[slot] = (ReadSlot(domain, slot, &headers[slot], NULL, 0) == IOTC_OK);
276+ }
277+}
278+ 
279+int32_t IotcVsCommit(const char *domain)
280+{
281+ int32_t domainRet = ValidateDomainName(domain);
282+ CHECK_RETURN(domainRet == IOTC_OK, domainRet);
283+ IotcVsHeader headers[IOTC_VS_SLOT_COUNT] = {0};
284+ bool valid[IOTC_VS_SLOT_COUNT] = {false};
285+ ReadBothSlots(domain, headers, valid);
286+ 
287+ int pendingSlot = -1;
288+ int activeSlot = -1;
289+ for (uint32_t slot = 0; slot < IOTC_VS_SLOT_COUNT; ++slot) {
290+ if (!valid[slot]) {
291+ continue;
292+ }
293+ if (headers[slot].state == IOTC_VS_STATE_PENDING &&
294+ (pendingSlot < 0 || headers[slot].generation > headers[pendingSlot].generation)) {
295+ pendingSlot = (int)slot;
296+ } else if (headers[slot].state == IOTC_VS_STATE_COMMITTED &&
297+ (activeSlot < 0 || headers[slot].generation > headers[activeSlot].generation)) {
298+ activeSlot = (int)slot;
299+ }
300+ }
301+ if (pendingSlot < 0) {
302+ /* 已提交记录存在时重复 Commit 幂等;全空/全损坏则报告未找到。 */
303+ return (activeSlot >= 0) ? IOTC_OK : IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND;
304+ }
305+ if (activeSlot >= 0) {
306+ CHECK_RETURN(headers[pendingSlot].generation > headers[activeSlot].generation,
307+ IOTC_CORE_DEVICE_ERR_STORE_STATE);
308+ }
309+ 
310+ uint8_t payload[IOTC_VS_MAX_PAYLOAD_LEN] = {0};
311+ IotcVsHeader pending = {0};
312+ int32_t ret = ReadSlot(domain, (uint32_t)pendingSlot, &pending, payload, sizeof(payload));
313+ CHECK_RETURN(ret == IOTC_OK && pending.state == IOTC_VS_STATE_PENDING,
314+ IOTC_CORE_DEVICE_ERR_STORE_CORRUPT);
315+ 
316+ /* 先把新槽改为 committed 并回读确认,再擦旧槽。任何一步断电,Load 只能看到旧值或完整新值。 */
317+ pending.state = IOTC_VS_STATE_COMMITTED;
318+ pending.crc32 = ComputeRecordCrc(&pending, payload);
319+ uint8_t record[IOTC_VS_RECORD_BUF_LEN] = {0};
320+ if (memcpy_s(record, sizeof(record), &pending, sizeof(pending)) != EOK ||
321+ memcpy_s(record + sizeof(pending), sizeof(record) - sizeof(pending), payload, pending.payloadLen) != EOK) {
322+ return IOTC_CORE_DEVICE_ERR_STORE_CORRUPT;
323+ }
324+ char key[IOTC_VS_KEY_BUF_LEN] = {0};
325+ ret = BuildSlotKey(domain, (uint32_t)pendingSlot, key, sizeof(key));
326+ CHECK_RETURN(ret == IOTC_OK, ret);
327+ ret = IotcKvSetValue(key, record, (uint32_t)sizeof(pending) + pending.payloadLen);
328+ CHECK_RETURN(ret == IOTC_OK, IOTC_CORE_DEVICE_ERR_STORE_KV);
329+ 
330+ IotcVsHeader verify = {0};
331+ ret = ReadSlot(domain, (uint32_t)pendingSlot, &verify, NULL, 0);
332+ CHECK_RETURN(ret == IOTC_OK && verify.state == IOTC_VS_STATE_COMMITTED &&
333+ verify.generation == pending.generation, IOTC_CORE_DEVICE_ERR_STORE_CORRUPT);
334+ 
335+ return (activeSlot >= 0) ? EraseSlot(domain, (uint32_t)activeSlot) : IOTC_OK;
336+}
337+ 
338+int32_t IotcVsAbort(const char *domain)
339+{
340+ int32_t domainRet = ValidateDomainName(domain);
341+ CHECK_RETURN(domainRet == IOTC_OK, domainRet);
342+ IotcVsHeader headers[IOTC_VS_SLOT_COUNT] = {0};
343+ bool valid[IOTC_VS_SLOT_COUNT] = {false};
344+ ReadBothSlots(domain, headers, valid);
345+ 
346+ bool found = false;
347+ int32_t result = IOTC_OK;
348+ for (uint32_t slot = 0; slot < IOTC_VS_SLOT_COUNT; ++slot) {
349+ if (valid[slot] && headers[slot].state == IOTC_VS_STATE_PENDING) {
350+ found = true;
351+ if (EraseSlot(domain, slot) != IOTC_OK) {
352+ result = IOTC_CORE_DEVICE_ERR_STORE_KV;
353+ }
354+ }
355+ }
356+ if (found) {
357+ return result;
358+ }
359+ /* 已无 pending 时重复 Abort 幂等。 */
360+ return (valid[0] || valid[1]) ? IOTC_OK : IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND;
361+}
362+ 
363+int32_t IotcVsErase(const char *domain)
364+{
365+ int32_t domainRet = ValidateDomainName(domain);
366+ CHECK_RETURN(domainRet == IOTC_OK, domainRet);
367+ int32_t first = EraseSlot(domain, 0);
368+ int32_t second = EraseSlot(domain, 1);
369+ return (first == IOTC_OK && second == IOTC_OK) ? IOTC_OK : IOTC_CORE_DEVICE_ERR_STORE_KV;
370+}
371+ 
372+int32_t IotcVsGetGeneration(const char *domain, uint32_t *generation)
373+{
374+ CHECK_RETURN(generation != NULL, IOTC_ERR_PARAM_INVALID);
375+ int32_t domainRet = ValidateDomainName(domain);
376+ CHECK_RETURN(domainRet == IOTC_OK, domainRet);
377+ IotcVsHeader active = {0};
378+ int32_t ret = FindActive(domain, &active, NULL, 0, NULL);
379+ CHECK_RETURN(ret == IOTC_OK, ret);
380+ *generation = active.generation;
381+ return IOTC_OK;
382+}
Mcore/device/core_dev.gni+4-2
@@ -37,7 +37,9 @@ iotc_core_device_src = [
37 "$iotc_core_path/device/config/config_hub_info.c",37 "$iotc_core_path/device/config/config_hub_info.c",
38 "$iotc_core_path/device/config/config_revoke_flag.c",38 "$iotc_core_path/device/config/config_revoke_flag.c",
39 "$iotc_core_path/device/config/config_authinfo.c",39 "$iotc_core_path/device/config/config_authinfo.c",
40- "$iotc_core_path/device/config/config_revoke.c",40+ "$iotc_core_path/device/config/config_revoke.c",
41+ "$iotc_core_path/device/config/device_state_store.c",
42+ "$iotc_core_path/device/config/versioned_store.c",
41 "$iotc_core_path/device/service/device_control.c",43 "$iotc_core_path/device/service/device_control.c",
42 "$iotc_core_path/device/service/svc_cloud_setup.c",44 "$iotc_core_path/device/service/svc_cloud_setup.c",
43 "$iotc_core_path/device/service/svc_cloud_login.c",45 "$iotc_core_path/device/service/svc_cloud_login.c",
@@ -47,4 +49,4 @@ iotc_core_device_src = [
47 "$iotc_core_path/device/service/svc_hub_setup.c",49 "$iotc_core_path/device/service/svc_hub_setup.c",
48]50]
49 51 
50-iotc_core_device_def = []52+iotc_core_device_def = []
Mcore/device/service/iotc_dev_svc.c+12-0
@@ -85,6 +85,15 @@ static int32_t DeviceServiceStart(int32_t instanceId, ServiceFinishCallback onFi
85 ret = ConfigInfoInit();85 ret = ConfigInfoInit();
86 if (ret != IOTC_OK) {86 if (ret != IOTC_OK) {
87 IOTC_LOGW("config info init %d", ret);87 IOTC_LOGW("config info init %d", ret);
88+ ProfileReportUnregUplink(DeviceReportCallback);
89+ return ret;
90+ }
91+ 
92+ ret = DeviceServiceRecoverHubBindInfo();
93+ if (ret != IOTC_OK) {
94+ IOTC_LOGW("recover HubSetup info error %d", ret);
95+ ConfigInfoDeinit();
96+ ProfileReportUnregUplink(DeviceReportCallback);
88 return ret;97 return ret;
89 }98 }
90 99 
@@ -179,6 +188,9 @@ int32_t DeviceServiceInit(void)
179 .onGetAuthInfo = GetAuthInfo,188 .onGetAuthInfo = GetAuthInfo,
180 .onRecvBindInfo = DeviceServiceRecvBindingInfo,189 .onRecvBindInfo = DeviceServiceRecvBindingInfo,
181 .onRecvHubBindInfo = DeviceServiceRecvHubBindInfo,190 .onRecvHubBindInfo = DeviceServiceRecvHubBindInfo,
191+ .onPrepareHubBindInfo = DeviceServicePrepareHubBindInfo,
192+ .onCommitHubBindInfo = DeviceServiceCommitHubBindInfo,
193+ .onAbortHubBindInfo = DeviceServiceAbortHubBindInfo,
182 .onRecvAuthInfo = DeviceServiceRecvAuthInfo,194 .onRecvAuthInfo = DeviceServiceRecvAuthInfo,
183 .onRecvProvisionPsk = DeviceServiceRecvProvisionPsk,195 .onRecvProvisionPsk = DeviceServiceRecvProvisionPsk,
184 .onRecvLoginInfo = DeviceServiceRecvLoginInfo,196 .onRecvLoginInfo = DeviceServiceRecvLoginInfo,
Mcore/device/service/svc_hub_setup.c+259-13
@@ -22,6 +22,23 @@
22#include "securec.h"22#include "securec.h"
23#include "comm_def.h"23#include "comm_def.h"
24#include "iotc_svc_dev.h"24#include "iotc_svc_dev.h"
25+#include "iotc_conf.h"
26+#include "iotc_json.h"
27+#include "iotc_md.h"
28+#include "device_state_store.h"
29+ 
30+#define HUB_SETUP_CHANNEL_MIN 1
31+#define HUB_SETUP_CHANNEL_MAX 196
32+#define HUB_SETUP_BSSID_STR_LEN 17
33+ 
34+#if IOTC_CONF_WIFI_ONLY_SUPPORT
35+typedef struct {
36+ bool active;
37+ DevHubInfo hubInfo;
38+} HubSetupTransaction;
39+ 
40+static HubSetupTransaction g_hubSetupTransaction = {0};
41+#endif
25 42 
26static void DevHubInfoClear(DevHubInfo *info)43static void DevHubInfoClear(DevHubInfo *info)
27{44{
@@ -30,53 +47,281 @@ static void DevHubInfoClear(DevHubInfo *info)
30 }47 }
31}48}
32 49 
50+static bool IsHexChar(char value)
51+{
52+ return (value >= '0' && value <= '9') || (value >= 'a' && value <= 'f') ||
53+ (value >= 'A' && value <= 'F');
54+}
55+ 
56+static bool IsValidBssid(const char *bssid)
57+{
58+ if (bssid == NULL || strlen(bssid) != HUB_SETUP_BSSID_STR_LEN) {
59+ return false;
60+ }
61+ for (uint32_t i = 0; i < HUB_SETUP_BSSID_STR_LEN; ++i) {
62+ if ((i + 1U) % 3U == 0U) {
63+ if (bssid[i] != ':') {
64+ return false;
65+ }
66+ } else if (!IsHexChar(bssid[i])) {
67+ return false;
68+ }
69+ }
70+ return true;
71+}
72+ 
73+static int32_t ParseRequiredString(const IotcJson *jsonObj, const char *name, char *value, uint32_t valueLen)
74+{
75+ int32_t ret = UtilsJsonGetString(jsonObj, name, value, valueLen);
76+ CHECK_RETURN(ret == IOTC_OK, ret);
77+ CHECK_RETURN(value[0] != '\0', IOTC_ERR_PARAM_INVALID);
78+ return IOTC_OK;
79+}
80+ 
33static int32_t ParseDeviceHubInfo(const IotcJson *jsonObj, DevHubInfo *hubInfo)81static int32_t ParseDeviceHubInfo(const IotcJson *jsonObj, DevHubInfo *hubInfo)
34{82{
35 if (NULL == jsonObj || NULL == hubInfo) {83 if (NULL == jsonObj || NULL == hubInfo) {
36 IOTC_LOGE("invalid param");84 IOTC_LOGE("invalid param");
37 return IOTC_ERROR;85 return IOTC_ERROR;
38 }86 }
39- int32_t ret = UtilsJsonGetString(jsonObj, STR_JSON_DOMAIN_ID, hubInfo->domainId, sizeof(hubInfo->domainId));87+ int32_t ret = ParseRequiredString(jsonObj, STR_JSON_DOMAIN_ID,
88+ hubInfo->domainId, sizeof(hubInfo->domainId));
40 if (ret != IOTC_OK) {89 if (ret != IOTC_OK) {
41 IOTC_LOGE("get domainId error %d", ret);90 IOTC_LOGE("get domainId error %d", ret);
42 return ret;91 return ret;
43 }92 }
44- ret = UtilsJsonGetString(jsonObj, STR_JSON_DEVID, hubInfo->devId, sizeof(hubInfo->devId));93+ ret = ParseRequiredString(jsonObj, STR_JSON_DEVID, hubInfo->devId, sizeof(hubInfo->devId));
45 if (ret != IOTC_OK) {94 if (ret != IOTC_OK) {
46 IOTC_LOGE("get devId error %d", ret);95 IOTC_LOGE("get devId error %d", ret);
47 return ret;96 return ret;
48 }97 }
49- // wifi Combo 设备才有的参数98+ /* Wi-Fi Only/Combo 设备在 HubSetup 中携带网络参数。 */
50#if defined(IOTC_CONF_WIFI_SUPPORT) && IOTC_CONF_WIFI_SUPPORT99#if defined(IOTC_CONF_WIFI_SUPPORT) && IOTC_CONF_WIFI_SUPPORT
51- ret = UtilsJsonGetString(jsonObj, STR_JSON_CODE, hubInfo->code, sizeof(hubInfo->code));100+ ret = ParseRequiredString(jsonObj, STR_JSON_CODE, hubInfo->code, sizeof(hubInfo->code));
52 if (ret != IOTC_OK) {101 if (ret != IOTC_OK) {
53 IOTC_LOGE("get code error %d", ret);102 IOTC_LOGE("get code error %d", ret);
54 return ret;103 return ret;
55 }104 }
56- ret = UtilsJsonGetString(jsonObj, STR_NETINFO_SSID, hubInfo->ssid, sizeof(hubInfo->ssid));105+ ret = ParseRequiredString(jsonObj, STR_NETINFO_SSID, hubInfo->ssid, sizeof(hubInfo->ssid));
57 if (ret != IOTC_OK) {106 if (ret != IOTC_OK) {
58 IOTC_LOGE("get ssid error %d", ret);107 IOTC_LOGE("get ssid error %d", ret);
59 return ret;108 return ret;
60 }109 }
61- ret = UtilsJsonGetString(jsonObj, STR_NETINFO_PASSWORD, hubInfo->password, sizeof(hubInfo->password));110+ ret = ParseRequiredString(jsonObj, STR_NETINFO_PASSWORD, hubInfo->password, sizeof(hubInfo->password));
62 if (ret != IOTC_OK) {111 if (ret != IOTC_OK) {
63 IOTC_LOGE("get password error %d", ret);112 IOTC_LOGE("get password error %d", ret);
64 return ret;113 return ret;
65 }114 }
66- ret = UtilsJsonGetNum(jsonObj, STR_JSON_CHANNEL, &hubInfo->channel); // 可选参数115+ IotcJson *channelObj = IotcJsonGetObj(jsonObj, STR_JSON_CHANNEL);
67- if (ret != IOTC_OK) {116+ if (channelObj != NULL) {
68- IOTC_LOGW("get channel error %d", ret);117+ ret = UtilsJsonGetNum(jsonObj, STR_JSON_CHANNEL, &hubInfo->channel);
118+ if (ret != IOTC_OK || hubInfo->channel < HUB_SETUP_CHANNEL_MIN ||
119+ hubInfo->channel > HUB_SETUP_CHANNEL_MAX) {
120+ IOTC_LOGE("invalid channel");
121+ return (ret == IOTC_OK) ? IOTC_ERR_PARAM_INVALID : ret;
122+ }
69 }123 }
70- ret = UtilsJsonGetString(jsonObj, STR_JSON_BSSID, hubInfo->bssid, sizeof(hubInfo->bssid)); // 可选参数124+ IotcJson *bssidObj = IotcJsonGetObj(jsonObj, STR_JSON_BSSID);
71- if (ret != IOTC_OK) {125+ if (bssidObj != NULL) {
72- IOTC_LOGW("get bssid error %d", ret);126+ ret = UtilsJsonGetString(jsonObj, STR_JSON_BSSID, hubInfo->bssid, sizeof(hubInfo->bssid));
127+ if (ret != IOTC_OK || !IsValidBssid(hubInfo->bssid)) {
128+ IOTC_LOGE("invalid bssid");
129+ return (ret == IOTC_OK) ? IOTC_ERR_PARAM_INVALID : ret;
130+ }
73 }131 }
74#endif132#endif
75 return IOTC_OK;133 return IOTC_OK;
76}134}
77 135 
136+#if IOTC_CONF_WIFI_ONLY_SUPPORT
137+static int32_t BuildProvisionPending(const DevHubInfo *hubInfo, IotcDssProvisionPending *pending)
138+{
139+ CHECK_RETURN(hubInfo != NULL && pending != NULL, IOTC_ERR_PARAM_INVALID);
140+ uint32_t mdidLen = strlen(hubInfo->domainId);
141+ CHECK_RETURN(mdidLen > 0 && mdidLen <= sizeof(pending->mdid), IOTC_ERR_PARAM_INVALID);
142+ 
143+ (void)memset_s(pending, sizeof(*pending), 0, sizeof(*pending));
144+ if (memcpy_s(pending->mdid, sizeof(pending->mdid), hubInfo->domainId, mdidLen) != EOK ||
145+ strcpy_s(pending->tempDevId, sizeof(pending->tempDevId), hubInfo->devId) != EOK ||
146+ strcpy_s(pending->registerCode, sizeof(pending->registerCode), hubInfo->code) != EOK) {
147+ return IOTC_ERR_SECUREC_MEMCPY;
148+ }
149+ pending->mdidLen = (uint8_t)mdidLen;
150+ pending->state = IOTC_DSS_PENDING_STATE_WAIT_ACTIVATE;
151+ return IotcMdCalc(IOTC_MD_SHA256, (const uint8_t *)hubInfo, sizeof(*hubInfo),
152+ pending->requestHash, sizeof(pending->requestHash));
153+}
154+ 
155+static bool ProvisionPendingMatches(const IotcDssProvisionPending *left,
156+ const IotcDssProvisionPending *right)
157+{
158+ return left->mdidLen == right->mdidLen && left->state == right->state &&
159+ memcmp(left->mdid, right->mdid, left->mdidLen) == 0 &&
160+ strcmp(left->tempDevId, right->tempDevId) == 0 &&
161+ strcmp(left->registerCode, right->registerCode) == 0 &&
162+ memcmp(left->requestHash, right->requestHash, sizeof(left->requestHash)) == 0;
163+}
164+ 
165+static int32_t SyncHubInfo(const IotcDssProvisionPending *pending, const DevHubInfo *requestInfo)
166+{
167+ CHECK_RETURN(pending != NULL, IOTC_ERR_PARAM_INVALID);
168+ DevHubInfo hubInfo = {0};
169+ if (requestInfo != NULL && memcpy_s(&hubInfo, sizeof(hubInfo), requestInfo, sizeof(*requestInfo)) != EOK) {
170+ return IOTC_ERR_SECUREC_MEMCPY;
171+ }
172+ if (requestInfo == NULL) {
173+ if (memcpy_s(hubInfo.domainId, sizeof(hubInfo.domainId), pending->mdid, pending->mdidLen) != EOK ||
174+ strcpy_s(hubInfo.devId, sizeof(hubInfo.devId), pending->tempDevId) != EOK ||
175+ strcpy_s(hubInfo.code, sizeof(hubInfo.code), pending->registerCode) != EOK) {
176+ DevHubInfoClear(&hubInfo);
177+ return IOTC_ERR_SECUREC_MEMCPY;
178+ }
179+ }
180+ 
181+ int32_t firstError = ConfigSaveHubInfo(&hubInfo);
182+ int32_t ret = ConfigSaveDomainId(hubInfo.domainId, strlen(hubInfo.domainId));
183+ if (firstError == IOTC_OK && ret != IOTC_OK) {
184+ firstError = ret;
185+ }
186+ ret = ConfigSaveDevId(hubInfo.devId, strlen(hubInfo.devId));
187+ if (firstError == IOTC_OK && ret != IOTC_OK) {
188+ firstError = ret;
189+ }
190+ DevHubInfoClear(&hubInfo);
191+ return firstError;
192+}
193+ 
194+int32_t DeviceServicePrepareHubBindInfo(const IotcJson *jsonObj, bool *alreadyCommitted)
195+{
196+ CHECK_RETURN_LOGW(jsonObj != NULL && alreadyCommitted != NULL, IOTC_ERR_PARAM_INVALID, "param invalid");
197+ CHECK_RETURN_LOGW(!g_hubSetupTransaction.active, IOTC_CORE_DEVICE_ERR_STORE_STATE,
198+ "HubSetup transaction already active");
199+ *alreadyCommitted = false;
200+ 
201+ DevHubInfo hubInfo = {0};
202+ IotcDssProvisionPending pending = {0};
203+ int32_t ret = ParseDeviceHubInfo(jsonObj, &hubInfo);
204+ if (ret == IOTC_OK) {
205+ ret = BuildProvisionPending(&hubInfo, &pending);
206+ }
207+ if (ret != IOTC_OK) {
208+ DevHubInfoClear(&hubInfo);
209+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
210+ return ret;
211+ }
212+ 
213+ IotcDssProvisionPending active = {0};
214+ ret = IotcDssLoadProvisionPending(&active);
215+ if (ret == IOTC_OK) {
216+ if (!ProvisionPendingMatches(&active, &pending)) {
217+ ret = IOTC_CORE_DEVICE_ERR_STORE_STATE;
218+ } else {
219+ *alreadyCommitted = true;
220+ ret = SyncHubInfo(&active, &hubInfo);
221+ }
222+ } else if (ret == IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND) {
223+ ret = IotcVsBeginWrite(IOTC_DSS_DOMAIN_PROVISION_PENDING, IOTC_DSS_SCHEMA_VERSION,
224+ (const uint8_t *)&pending, sizeof(pending));
225+ if (ret == IOTC_OK) {
226+ g_hubSetupTransaction.active = true;
227+ (void)memcpy_s(&g_hubSetupTransaction.hubInfo, sizeof(g_hubSetupTransaction.hubInfo),
228+ &hubInfo, sizeof(hubInfo));
229+ }
230+ }
231+ 
232+ DevHubInfoClear(&hubInfo);
233+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
234+ (void)memset_s(&active, sizeof(active), 0, sizeof(active));
235+ return ret;
236+}
237+ 
238+int32_t DeviceServiceCommitHubBindInfo(void)
239+{
240+ CHECK_RETURN_LOGW(g_hubSetupTransaction.active, IOTC_CORE_DEVICE_ERR_STORE_STATE,
241+ "no HubSetup transaction");
242+ int32_t ret = IotcVsCommit(IOTC_DSS_DOMAIN_PROVISION_PENDING);
243+ if (ret != IOTC_OK) {
244+ return ret;
245+ }
246+ 
247+ IotcDssProvisionPending pending = {0};
248+ int32_t buildRet = BuildProvisionPending(&g_hubSetupTransaction.hubInfo, &pending);
249+ if (buildRet == IOTC_OK) {
250+ int32_t syncRet = SyncHubInfo(&pending, &g_hubSetupTransaction.hubInfo);
251+ if (syncRet != IOTC_OK) {
252+ /* 双槽记录是权威数据;启动时会再次同步兼容缓存。 */
253+ IOTC_LOGW("sync committed HubSetup cache error %d", syncRet);
254+ }
255+ } else {
256+ IOTC_LOGW("rebuild committed HubSetup cache error %d", buildRet);
257+ }
258+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
259+ DevHubInfoClear(&g_hubSetupTransaction.hubInfo);
260+ g_hubSetupTransaction.active = false;
261+ return IOTC_OK;
262+}
263+ 
264+int32_t DeviceServiceAbortHubBindInfo(void)
265+{
266+ if (!g_hubSetupTransaction.active) {
267+ return IOTC_OK;
268+ }
269+ int32_t ret = IotcVsAbort(IOTC_DSS_DOMAIN_PROVISION_PENDING);
270+ DevHubInfoClear(&g_hubSetupTransaction.hubInfo);
271+ g_hubSetupTransaction.active = false;
272+ return (ret == IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND) ? IOTC_OK : ret;
273+}
274+ 
275+int32_t DeviceServiceRecoverHubBindInfo(void)
276+{
277+ IotcDssProvisionPending pending = {0};
278+ int32_t ret = IotcDssLoadProvisionPending(&pending);
279+ if (ret != IOTC_OK && ret != IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND) {
280+ return ret;
281+ }
282+ if (ret == IOTC_OK) {
283+ ret = SyncHubInfo(&pending, NULL);
284+ CHECK_RETURN(ret == IOTC_OK, ret);
285+ }
286+ 
287+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
288+ return IOTC_OK;
289+}
290+#else
291+int32_t DeviceServicePrepareHubBindInfo(const IotcJson *jsonObj, bool *alreadyCommitted)
292+{
293+ CHECK_RETURN(alreadyCommitted != NULL, IOTC_ERR_PARAM_INVALID);
294+ int32_t ret = DeviceServiceRecvHubBindInfo(jsonObj);
295+ *alreadyCommitted = (ret == IOTC_OK);
296+ return ret;
297+}
298+ 
299+int32_t DeviceServiceCommitHubBindInfo(void)
300+{
301+ return IOTC_OK;
302+}
303+ 
304+int32_t DeviceServiceAbortHubBindInfo(void)
305+{
306+ return IOTC_OK;
307+}
308+ 
309+int32_t DeviceServiceRecoverHubBindInfo(void)
310+{
311+ return IOTC_OK;
312+}
313+#endif
314+ 
78int32_t DeviceServiceRecvHubBindInfo(const IotcJson *jsonObj)315int32_t DeviceServiceRecvHubBindInfo(const IotcJson *jsonObj)
79{316{
317+#if IOTC_CONF_WIFI_ONLY_SUPPORT
318+ bool alreadyCommitted = false;
319+ int32_t ret = DeviceServicePrepareHubBindInfo(jsonObj, &alreadyCommitted);
320+ if (ret != IOTC_OK || alreadyCommitted) {
321+ return ret;
322+ }
323+ return DeviceServiceCommitHubBindInfo();
324+#else
80 CHECK_RETURN_LOGW(jsonObj != NULL, IOTC_ERR_PARAM_INVALID, "param invalid");325 CHECK_RETURN_LOGW(jsonObj != NULL, IOTC_ERR_PARAM_INVALID, "param invalid");
81 DevHubInfo hubInfo = {0};326 DevHubInfo hubInfo = {0};
82 int32_t ret = ParseDeviceHubInfo(jsonObj, &hubInfo);327 int32_t ret = ParseDeviceHubInfo(jsonObj, &hubInfo);
@@ -106,4 +351,5 @@ int32_t DeviceServiceRecvHubBindInfo(const IotcJson *jsonObj)
106 }351 }
107 DevHubInfoClear(&hubInfo);352 DevHubInfoClear(&hubInfo);
108 return IOTC_OK;353 return IOTC_OK;
109-}354+#endif
355+}
Mcore/device/service/svc_hub_setup.h+9-1
@@ -23,7 +23,15 @@ extern "C" {
23 23 
24int32_t DeviceServiceRecvHubBindInfo(const IotcJson *jsonObj);24int32_t DeviceServiceRecvHubBindInfo(const IotcJson *jsonObj);
25 25 
26+int32_t DeviceServicePrepareHubBindInfo(const IotcJson *jsonObj, bool *alreadyCommitted);
27+ 
28+int32_t DeviceServiceCommitHubBindInfo(void);
29+ 
30+int32_t DeviceServiceAbortHubBindInfo(void);
31+ 
32+int32_t DeviceServiceRecoverHubBindInfo(void);
33+ 
26#ifdef __cplusplus34#ifdef __cplusplus
27}35}
28#endif36#endif
29-#endif /* SERVICE_HUB_SETUP_H */37+#endif /* SERVICE_HUB_SETUP_H */
Mcore/infrastructure/define/comm_def.h+3-0
@@ -79,6 +79,8 @@ typedef struct {
79#define STR_URI_PATH_WELL_KNOWN ".well-known"79#define STR_URI_PATH_WELL_KNOWN ".well-known"
80#define STR_URI_PATH_CORE "core"80#define STR_URI_PATH_CORE "core"
81#define STR_URI_QUERY_SEARCH_LOCAL_HUB "st=searchLocalHub" // 发现局域网本地中枢查询参数81#define STR_URI_QUERY_SEARCH_LOCAL_HUB "st=searchLocalHub" // 发现局域网本地中枢查询参数
82+#define STR_URI_DEVICE_INFO "DeviceInfo" // APP查询应用终端设备信息
83+#define STR_URI_ONLINE_NOTIFY "onlinenotify" // 配网完成通知
82#define STR_URI_HUB_SETUP "HubSetup" // APP为设备下发配网&注册信息84#define STR_URI_HUB_SETUP "HubSetup" // APP为设备下发配网&注册信息
83 85 
84#define STR_URI_LOCAL_CONTROL_SESS_MNGR ".sys/sessMngr" // 本地控制会话管理URI86#define STR_URI_LOCAL_CONTROL_SESS_MNGR ".sys/sessMngr" // 本地控制会话管理URI
@@ -284,6 +286,7 @@ typedef struct {
284#define STR_E2E_DATA_CHANGE "e2eDataChange"286#define STR_E2E_DATA_CHANGE "e2eDataChange"
285#define STR_E2E_CONTROL "e2eCtrl"287#define STR_E2E_CONTROL "e2eCtrl"
286#define STR_JSON_ERR_CODE "errcode"288#define STR_JSON_ERR_CODE "errcode"
289+#define STR_JSON_ERR_CODE_V3 "errCode"
287#define DEVICE_STATUS_ONLINE "online"290#define DEVICE_STATUS_ONLINE "online"
288#define DEVICE_STATUS_OFFLINE "offline"291#define DEVICE_STATUS_OFFLINE "offline"
289#define DEVICE_STATUS_INBOX "inbox"292#define DEVICE_STATUS_INBOX "inbox"
Mcore/infrastructure/service/include/iotc_svc_dev.h+10-1
@@ -78,6 +78,7 @@ typedef struct {
78 char devId[DEVICE_ID_MAX_STR_LEN + 1];78 char devId[DEVICE_ID_MAX_STR_LEN + 1];
79 char ssid[IOTC_WIFI_SSID_MAX_LEN + 1];79 char ssid[IOTC_WIFI_SSID_MAX_LEN + 1];
80 char password[IOTC_WIFI_PWD_MAX_LEN + 1];80 char password[IOTC_WIFI_PWD_MAX_LEN + 1];
81+ /* 6 字节 BSSID 的标准文本形式为 XX:XX:XX:XX:XX:XX,共 17 字符。 */
81 char bssid[IOTC_WIFI_BSSID_STR_MAX_LEN];82 char bssid[IOTC_WIFI_BSSID_STR_MAX_LEN];
82 int32_t channel;83 int32_t channel;
83} DevHubInfo;84} DevHubInfo;
@@ -95,6 +96,8 @@ typedef int32_t (*DevGetRegisterInfo)(bool *isExist, DevRegInfo *info);
95typedef int32_t (*DevGetAuthInfo)(bool *isExist, DevAuthInfo *info);96typedef int32_t (*DevGetAuthInfo)(bool *isExist, DevAuthInfo *info);
96typedef int32_t (*DevRecvBindInfo)(const IotcJson *json);97typedef int32_t (*DevRecvBindInfo)(const IotcJson *json);
97typedef int32_t (*DevRecvHubBindInfo)(const IotcJson *json);98typedef int32_t (*DevRecvHubBindInfo)(const IotcJson *json);
99+typedef int32_t (*DevPrepareHubBindInfo)(const IotcJson *json, bool *alreadyCommitted);
100+typedef int32_t (*DevFinishHubBindInfo)(void);
98typedef int32_t (*DevRecvAuthInfo)(const IotcJson *json);101typedef int32_t (*DevRecvAuthInfo)(const IotcJson *json);
99typedef int32_t (*DevRecvProvisionPsk)(const IotcJson *json);102typedef int32_t (*DevRecvProvisionPsk)(const IotcJson *json);
100typedef int32_t (*DevRecvLoginInfo)(const IotcJson *json);103typedef int32_t (*DevRecvLoginInfo)(const IotcJson *json);
@@ -115,6 +118,9 @@ typedef struct {
115 DevGetAuthInfo onGetAuthInfo;118 DevGetAuthInfo onGetAuthInfo;
116 DevRecvBindInfo onRecvBindInfo;119 DevRecvBindInfo onRecvBindInfo;
117 DevRecvHubBindInfo onRecvHubBindInfo;120 DevRecvHubBindInfo onRecvHubBindInfo;
121+ DevPrepareHubBindInfo onPrepareHubBindInfo;
122+ DevFinishHubBindInfo onCommitHubBindInfo;
123+ DevFinishHubBindInfo onAbortHubBindInfo;
118 DevRecvAuthInfo onRecvAuthInfo;124 DevRecvAuthInfo onRecvAuthInfo;
119 DevRecvProvisionPsk onRecvProvisionPsk;125 DevRecvProvisionPsk onRecvProvisionPsk;
120 DevRecvLoginInfo onRecvLoginInfo;126 DevRecvLoginInfo onRecvLoginInfo;
@@ -131,6 +137,9 @@ typedef enum {
131 137 
132int32_t DevSvcProxyRecvBindInfo(const IotcJson *json);138int32_t DevSvcProxyRecvBindInfo(const IotcJson *json);
133int32_t DevSvcProxyRecvHubBindInfo(const IotcJson *json);139int32_t DevSvcProxyRecvHubBindInfo(const IotcJson *json);
140+int32_t DevSvcProxyPrepareHubBindInfo(const IotcJson *json, bool *alreadyCommitted);
141+int32_t DevSvcProxyCommitHubBindInfo(void);
142+int32_t DevSvcProxyAbortHubBindInfo(void);
134int32_t DevSvcProxyCtlGetCharStates(const IotcJson *inArray, IotcJson **outArray);143int32_t DevSvcProxyCtlGetCharStates(const IotcJson *inArray, IotcJson **outArray);
135int32_t DevSvcProxyCtlReportAll(DevReportType type);144int32_t DevSvcProxyCtlReportAll(DevReportType type);
136int32_t DevSvcProxyCtlReportByDevId(DevReportType type, const char *devId);145int32_t DevSvcProxyCtlReportByDevId(DevReportType type, const char *devId);
@@ -151,4 +160,4 @@ void DevSvcProxySetOnlineStatus(bool isOnline);
151#ifdef __cplusplus160#ifdef __cplusplus
152}161}
153#endif162#endif
154-#endif /* IOTC_SERVICE_DEVICE_H */163+#endif /* IOTC_SERVICE_DEVICE_H */
Mcore/infrastructure/service/iotc_svc_dev.c+28-1
@@ -50,6 +50,33 @@ int32_t DevSvcProxyRecvHubBindInfo(const IotcJson *json)
50 return devApi->onRecvHubBindInfo(json);50 return devApi->onRecvHubBindInfo(json);
51}51}
52 52 
53+int32_t DevSvcProxyPrepareHubBindInfo(const IotcJson *json, bool *alreadyCommitted)
54+{
55+ const DevSvcApi *devApi = GetDevSvcApi();
56+ if (devApi == NULL || devApi->onPrepareHubBindInfo == NULL) {
57+ return IOTC_CORE_COMM_FWK_ERR_SERVICE_NO_API;
58+ }
59+ return devApi->onPrepareHubBindInfo(json, alreadyCommitted);
60+}
61+ 
62+int32_t DevSvcProxyCommitHubBindInfo(void)
63+{
64+ const DevSvcApi *devApi = GetDevSvcApi();
65+ if (devApi == NULL || devApi->onCommitHubBindInfo == NULL) {
66+ return IOTC_CORE_COMM_FWK_ERR_SERVICE_NO_API;
67+ }
68+ return devApi->onCommitHubBindInfo();
69+}
70+ 
71+int32_t DevSvcProxyAbortHubBindInfo(void)
72+{
73+ const DevSvcApi *devApi = GetDevSvcApi();
74+ if (devApi == NULL || devApi->onAbortHubBindInfo == NULL) {
75+ return IOTC_CORE_COMM_FWK_ERR_SERVICE_NO_API;
76+ }
77+ return devApi->onAbortHubBindInfo();
78+}
79+ 
53int32_t DevSvcProxyCtlGetCharStates(const IotcJson *inArray, IotcJson **outArray)80int32_t DevSvcProxyCtlGetCharStates(const IotcJson *inArray, IotcJson **outArray)
54{81{
55 const DevSvcApi *devApi = GetDevSvcApi();82 const DevSvcApi *devApi = GetDevSvcApi();
@@ -208,4 +235,4 @@ void DevSvcProxySetOnlineStatus(bool isOnline)
208 }235 }
209 236 
210 devApi->onSetOnlineStatus(isOnline);237 devApi->onSetOnlineStatus(isOnline);
211-}238+}
Mcore/wifi/application/connect/CMakeLists.txt+2-1
@@ -18,6 +18,7 @@ set(IOTC_SRC_LIST
18 ${CMAKE_CURRENT_SOURCE_DIR}/svc_conn_conf.c18 ${CMAKE_CURRENT_SOURCE_DIR}/svc_conn_conf.c
19 ${CMAKE_CURRENT_SOURCE_DIR}/svc_conn_ctx.c19 ${CMAKE_CURRENT_SOURCE_DIR}/svc_conn_ctx.c
20 ${CMAKE_CURRENT_SOURCE_DIR}/svc_conn_net_info.c20 ${CMAKE_CURRENT_SOURCE_DIR}/svc_conn_net_info.c
21+ ${CMAKE_CURRENT_SOURCE_DIR}/svc_conn_online_notify.c
21 ${CMAKE_CURRENT_SOURCE_DIR}/wifi_svc_conn.c22 ${CMAKE_CURRENT_SOURCE_DIR}/wifi_svc_conn.c
22 PARENT_SCOPE23 PARENT_SCOPE
23)24)
@@ -32,4 +33,4 @@ set(IOTC_TEST_INC_LIST
32 ${IOTC_TEST_INC_LIST}33 ${IOTC_TEST_INC_LIST}
33 ${CMAKE_CURRENT_SOURCE_DIR}34 ${CMAKE_CURRENT_SOURCE_DIR}
34 PARENT_SCOPE35 PARENT_SCOPE
35-)36+)
Mcore/wifi/application/connect/svc_conn_action.c+72-9
@@ -24,11 +24,14 @@
24#include "utils_assert.h"24#include "utils_assert.h"
25#include "iotc_errcode.h"25#include "iotc_errcode.h"
26#include "iotc_event.h"26#include "iotc_event.h"
27+#include "iotc_conf.h"
27#include "fwk_main.h"28#include "fwk_main.h"
29+#include "svc_conn_online_notify.h"
28 30 
29#define WIFI_VERIFY_CONNECT_TIMEOUT_MS UTILS_SEC_TO_MS(60)31#define WIFI_VERIFY_CONNECT_TIMEOUT_MS UTILS_SEC_TO_MS(60)
30#define WIFI_STATUS_CHECK_PERIOD UTILS_SEC_TO_MS(1)32#define WIFI_STATUS_CHECK_PERIOD UTILS_SEC_TO_MS(1)
31#define WIFI_AUTO_RECONNECT_TIMEVAL UTILS_SEC_TO_MS(20)33#define WIFI_AUTO_RECONNECT_TIMEVAL UTILS_SEC_TO_MS(20)
34+#define WIFI_INITIAL_CONNECT_DELAY_MS UTILS_SEC_TO_MS(1)
32 35 
33static void WifiVerifyFailedProcess(void)36static void WifiVerifyFailedProcess(void)
34{37{
@@ -47,8 +50,11 @@ static void WifiVerifyFailedProcess(void)
47 IOTC_LOGF("clear net info flag error %d", ret);50 IOTC_LOGF("clear net info flag error %d", ret);
48 }51 }
49 52 
50- /* 复位回退到待配网 */53+ /* 配网阶段连接失败必须执行完整恢出厂,避免遗留已绑定身份进入待配网 */
51- IotcFwkNotifyReset();54+ ret = IotcFwkRestore();
55+ if (ret != IOTC_OK) {
56+ IOTC_LOGW("restore after wifi verify failure error %d", ret);
57+ }
52}58}
53 59 
54static void WifiVerifyConnectTimeoutTimerCallback(int32_t id, void *userData)60static void WifiVerifyConnectTimeoutTimerCallback(int32_t id, void *userData)
@@ -111,6 +117,59 @@ static void WifiReconnectTimerCallback(int32_t id, void *userData)
111 }117 }
112}118}
113 119 
120+static int32_t ScheduleReconnect(ConnectServiceContext *ctx)
121+{
122+ if (ctx->reconnectTimerFd >= 0) {
123+ return IOTC_OK;
124+ }
125+ ctx->reconnectTimerFd = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_REPEAT, WifiReconnectTimerCallback,
126+ WIFI_AUTO_RECONNECT_TIMEVAL, NULL);
127+ if (ctx->reconnectTimerFd < 0) {
128+ IOTC_LOGE("create reconnect timer error %d", ctx->reconnectTimerFd);
129+ return ctx->reconnectTimerFd;
130+ }
131+ return IOTC_OK;
132+}
133+ 
134+static void WifiInitialConnectTimerCallback(int32_t id, void *userData)
135+{
136+ NOT_USED(userData);
137+ ConnectServiceContext *ctx = GetConnSvcCtx();
138+ SchedTimerRemove(id);
139+ ctx->initialConnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
140+ 
141+ int32_t ret = ConnSvcActionConnectWifi();
142+ if (ret == IOTC_OK) {
143+ return;
144+ }
145+ IOTC_LOGW("initial wifi connect error %d", ret);
146+ /*
147+ * A checked network must survive a transient platform-startup race. Keep
148+ * retrying at the normal reconnect cadence instead of failing the CONNECT
149+ * service and preventing the provisioning service from being evaluated.
150+ * A pending HubSetup failure is handled by ConnSvcActionConnectWifi(),
151+ * which clears the unverified network and triggers reset recovery.
152+ */
153+ if (ConfigIsNetInfoChecked()) {
154+ (void)ScheduleReconnect(ctx);
155+ }
156+}
157+ 
158+int32_t ConnSvcScheduleInitialConnect(void)
159+{
160+ ConnectServiceContext *ctx = GetConnSvcCtx();
161+ if (ctx->initialConnectTimerFd >= 0) {
162+ return IOTC_OK;
163+ }
164+ ctx->initialConnectTimerFd = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_ONCE,
165+ WifiInitialConnectTimerCallback, WIFI_INITIAL_CONNECT_DELAY_MS, NULL);
166+ if (ctx->initialConnectTimerFd < 0) {
167+ IOTC_LOGW("add initial wifi connect timer error %d", ctx->initialConnectTimerFd);
168+ return ctx->initialConnectTimerFd;
169+ }
170+ return IOTC_OK;
171+}
172+ 
114static void WifiStatusCheckTimerCallback(int32_t id, void *userData)173static void WifiStatusCheckTimerCallback(int32_t id, void *userData)
115{174{
116 NOT_USED(id);175 NOT_USED(id);
@@ -127,22 +186,26 @@ static void WifiStatusCheckTimerCallback(int32_t id, void *userData)
127 IOTC_LOGI("wifi net disconnect");186 IOTC_LOGI("wifi net disconnect");
128 UTILS_BIT_RESET(ctx->bitMap, CONN_SVC_BIT_MAP_WIFI_CONNECTED);187 UTILS_BIT_RESET(ctx->bitMap, CONN_SVC_BIT_MAP_WIFI_CONNECTED);
129 EventBusPublishSync(IOTC_SDK_AILIFE_EVENT_WIFI_NET_DISCONNECT, NULL, 0);188 EventBusPublishSync(IOTC_SDK_AILIFE_EVENT_WIFI_NET_DISCONNECT, NULL, 0);
189+#if IOTC_CONF_WIFI_ONLY_SUPPORT
190+ SvcConnOnlineNotifyStop();
191+#endif
130 if (!ConfigIsNetInfoChecked()) {192 if (!ConfigIsNetInfoChecked()) {
131 return;193 return;
132 }194 }
133- if (ctx->reconnectTimerFd < 0) {195+ (void)ScheduleReconnect(ctx);
134- ctx->reconnectTimerFd = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_REPEAT, WifiReconnectTimerCallback,
135- WIFI_AUTO_RECONNECT_TIMEVAL, NULL);
136- if (ctx->reconnectTimerFd < 0) {
137- IOTC_LOGE("create reconnect timer error %d", ctx->reconnectTimerFd);
138- }
139- }
140 return;196 return;
141 }197 }
142 198 
143 IOTC_LOGI("wifi net connect");199 IOTC_LOGI("wifi net connect");
144 UTILS_BIT_SET(ctx->bitMap, CONN_SVC_BIT_MAP_WIFI_CONNECTED);200 UTILS_BIT_SET(ctx->bitMap, CONN_SVC_BIT_MAP_WIFI_CONNECTED);
201+ 
145 EventBusPublishSync(IOTC_SDK_AILIFE_EVENT_WIFI_NET_CONNECT, NULL, 0);202 EventBusPublishSync(IOTC_SDK_AILIFE_EVENT_WIFI_NET_CONNECT, NULL, 0);
203+#if IOTC_CONF_WIFI_ONLY_SUPPORT
204+ int32_t notifyRet = SvcConnOnlineNotifyStart();
205+ if (notifyRet != IOTC_OK) {
206+ IOTC_LOGW("start online notify error %d", notifyRet);
207+ }
208+#endif
146 209 
147 if (ctx->reconnectTimerFd >= 0) {210 if (ctx->reconnectTimerFd >= 0) {
148 SchedTimerRemove(ctx->reconnectTimerFd);211 SchedTimerRemove(ctx->reconnectTimerFd);
Mcore/wifi/application/connect/svc_conn_action.h+2-0
@@ -23,6 +23,8 @@ extern "C" {
23 23 
24int32_t ConnSvcActionConnectWifi(void);24int32_t ConnSvcActionConnectWifi(void);
25 25 
26+int32_t ConnSvcScheduleInitialConnect(void);
27+ 
26int32_t ConnSvcStartNetCheckTimer(void);28int32_t ConnSvcStartNetCheckTimer(void);
27 29 
28#ifdef __cplusplus30#ifdef __cplusplus
Mcore/wifi/application/connect/svc_conn_ctx.c+6-1
@@ -27,6 +27,7 @@ int32_t ConnSvcCtxInit(void)
27 ctx->netCheckTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;27 ctx->netCheckTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
28 ctx->verifyTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;28 ctx->verifyTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
29 ctx->reconnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;29 ctx->reconnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
30+ ctx->initialConnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
30 31 
31 return IOTC_OK;32 return IOTC_OK;
32}33}
@@ -49,10 +50,14 @@ void ConnSvcCtxDeinit(void)
49 SchedTimerRemove(ctx->reconnectTimerFd);50 SchedTimerRemove(ctx->reconnectTimerFd);
50 ctx->reconnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;51 ctx->reconnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
51 }52 }
53+ if (ctx->initialConnectTimerFd >= 0) {
54+ SchedTimerRemove(ctx->initialConnectTimerFd);
55+ ctx->initialConnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
56+ }
52}57}
53 58 
54ConnectServiceContext *GetConnSvcCtx(void)59ConnectServiceContext *GetConnSvcCtx(void)
55{60{
56 static ConnectServiceContext ctx;61 static ConnectServiceContext ctx;
57 return &ctx;62 return &ctx;
58-}63+}
Mcore/wifi/application/connect/svc_conn_ctx.h+1-0
@@ -31,6 +31,7 @@ typedef struct {
31 int32_t verifyTimerFd;31 int32_t verifyTimerFd;
32 int32_t netCheckTimerFd;32 int32_t netCheckTimerFd;
33 int32_t reconnectTimerFd;33 int32_t reconnectTimerFd;
34+ int32_t initialConnectTimerFd;
34 ServiceFinishCallback onFinish;35 ServiceFinishCallback onFinish;
35} ConnectServiceContext;36} ConnectServiceContext;
36 37 
Mcore/wifi/application/connect/svc_conn_net_info.c+145-29
@@ -27,38 +27,98 @@
27#include "iotc_svc_dev.h"27#include "iotc_svc_dev.h"
28#include "sched_timer.h"28#include "sched_timer.h"
29 29 
30-#define DELAY_CONNECT_WIFI_INTERVAL 1030+/* HubSetup 响应会在 500 ms 窗口内完成发送,之后再切换到家庭 Wi-Fi。 */
31+#define DELAY_CONNECT_WIFI_INTERVAL 600
31 32 
32-int32_t ParseAndSaveNetCfgWifiInfo(const IotcJson *jsonObj)33+typedef struct {
34+ bool active;
35+ bool hadOldWifi;
36+ bool oldNeedVerify;
37+ bool oldChecked;
38+ bool softapSubscribed;
39+ int32_t delayTimer;
40+ uint8_t oldSsid[IOTC_WIFI_SSID_MAX_LEN + 1];
41+ uint32_t oldSsidLen;
42+ uint8_t oldPassword[IOTC_WIFI_PWD_MAX_LEN + 1];
43+ uint32_t oldPasswordLen;
44+} NetInfoTransaction;
45+ 
46+static NetInfoTransaction g_netInfoTransaction = {
47+ .delayTimer = EVENT_SOURCE_INVALID_TIMER_FD,
48+};
49+ 
50+static void ClearNetInfoTransaction(void)
33{51{
34- CHECK_RETURN_LOGW(jsonObj != NULL, IOTC_ERR_PARAM_INVALID, "param invalid");52+ (void)memset_s(&g_netInfoTransaction, sizeof(g_netInfoTransaction), 0, sizeof(g_netInfoTransaction));
35- char ssid[IOTC_WIFI_SSID_MAX_LEN + 1] = {0};53+ g_netInfoTransaction.delayTimer = EVENT_SOURCE_INVALID_TIMER_FD;
36- char pwd[IOTC_WIFI_PWD_MAX_LEN + 1] = {0};54+}
37- int32_t ret = UtilsJsonGetString(jsonObj, STR_NETINFO_SSID, ssid, sizeof(ssid));55+ 
56+static void SnapshotNetInfo(void)
57+{
58+ ClearNetInfoTransaction();
59+ g_netInfoTransaction.oldNeedVerify = ConfigIsNetInfoNeedVerify();
60+ g_netInfoTransaction.oldChecked = ConfigIsNetInfoChecked();
61+ g_netInfoTransaction.oldSsidLen = IOTC_WIFI_SSID_MAX_LEN;
62+ g_netInfoTransaction.oldPasswordLen = IOTC_WIFI_PWD_MAX_LEN;
63+ g_netInfoTransaction.hadOldWifi = (IotcGetWifiInfo(g_netInfoTransaction.oldSsid,
64+ &g_netInfoTransaction.oldSsidLen, g_netInfoTransaction.oldPassword,
65+ &g_netInfoTransaction.oldPasswordLen) == IOTC_OK);
66+ g_netInfoTransaction.active = true;
67+}
68+ 
69+static int32_t RestoreNetInfo(void)
70+{
71+ int32_t firstError = IOTC_OK;
72+ int32_t ret = g_netInfoTransaction.hadOldWifi ?
73+ IotcSetWifiInfo(g_netInfoTransaction.oldSsid, g_netInfoTransaction.oldSsidLen,
74+ g_netInfoTransaction.oldPassword, g_netInfoTransaction.oldPasswordLen) :
75+ IotcDeleteWifiInfo();
76+ if (ret != IOTC_OK) {
77+ firstError = ret;
78+ }
79+ 
80+ if (g_netInfoTransaction.oldNeedVerify) {
81+ ret = ConfigSetNetInfoNeedVerify();
82+ } else if (g_netInfoTransaction.oldChecked) {
83+ ret = ConfigSetNetInfoChecked();
84+ } else {
85+ ret = ConfigClearNetInfoFlag();
86+ }
87+ if (firstError == IOTC_OK && ret != IOTC_OK) {
88+ firstError = ret;
89+ }
90+ return firstError;
91+}
92+ 
93+static int32_t ParseNetCfgWifiInfo(const IotcJson *jsonObj, char *ssid, uint32_t ssidLen,
94+ char *pwd, uint32_t pwdLen)
95+{
96+ CHECK_RETURN_LOGW(jsonObj != NULL && ssid != NULL && pwd != NULL, IOTC_ERR_PARAM_INVALID, "param invalid");
97+ int32_t ret = UtilsJsonGetString(jsonObj, STR_NETINFO_SSID, ssid, ssidLen);
38 if (ret != IOTC_OK) {98 if (ret != IOTC_OK) {
39 IOTC_LOGE("get ssid error %d", ret);99 IOTC_LOGE("get ssid error %d", ret);
40 return ret;100 return ret;
41 }101 }
42 102 
43- IOTC_LOGI("get ssid %s", ssid);103+ ret = UtilsJsonGetString(jsonObj, STR_NETINFO_PASSWORD, pwd, pwdLen);
44- ret = UtilsJsonGetString(jsonObj, STR_NETINFO_PASSWORD, pwd, sizeof(pwd));
45 if (ret != IOTC_OK) {104 if (ret != IOTC_OK) {
46- (void)memset_s(ssid, sizeof(ssid), 0, sizeof(ssid));
47 IOTC_LOGE("get pwd error %d", ret);105 IOTC_LOGE("get pwd error %d", ret);
48 return ret;106 return ret;
49 }107 }
108+ return IOTC_OK;
109+}
50 110 
51- IOTC_LOGI("get pwd %s", pwd);111+int32_t ParseAndSaveNetCfgWifiInfo(const IotcJson *jsonObj)
52- ret = IotcSetWifiInfo((const uint8_t *)ssid, strlen(ssid), (const uint8_t *)pwd, strlen(pwd));112+{
113+ char ssid[IOTC_WIFI_SSID_MAX_LEN + 1] = {0};
114+ char pwd[IOTC_WIFI_PWD_MAX_LEN + 1] = {0};
115+ int32_t ret = ParseNetCfgWifiInfo(jsonObj, ssid, sizeof(ssid), pwd, sizeof(pwd));
116+ if (ret == IOTC_OK) {
117+ ret = IotcSetWifiInfo((const uint8_t *)ssid, strlen(ssid), (const uint8_t *)pwd, strlen(pwd));
118+ }
53 (void)memset_s(ssid, sizeof(ssid), 0, sizeof(ssid));119 (void)memset_s(ssid, sizeof(ssid), 0, sizeof(ssid));
54 (void)memset_s(pwd, sizeof(pwd), 0, sizeof(pwd));120 (void)memset_s(pwd, sizeof(pwd), 0, sizeof(pwd));
55- if (ret != IOTC_OK) {121+ return ret;
56- IOTC_LOGW("set wifi info error %d", ret);
57- return ret;
58- }
59- 
60- IOTC_LOGI("save net info ok");
61- return IOTC_OK;
62}122}
63 123 
64static void SoftapStopEventCallback(uint32_t event, void *param, uint32_t len)124static void SoftapStopEventCallback(uint32_t event, void *param, uint32_t len)
@@ -77,6 +137,11 @@ static void SoftapStopEventCallback(uint32_t event, void *param, uint32_t len)
77static void DelayConnetWifiCb(int32_t id, void *userData)137static void DelayConnetWifiCb(int32_t id, void *userData)
78{138{
79 NOT_USED(userData);139 NOT_USED(userData);
140+ /* 切换到家庭 Wi-Fi 前主动断开旧链路。 */
141+ int32_t disconnectRet = IotcDisconnectWifi();
142+ if (disconnectRet != IOTC_OK) {
143+ IOTC_LOGW("disconnect provisioning wifi error %d", disconnectRet);
144+ }
80 int32_t ret = ConnSvcActionConnectWifi();145 int32_t ret = ConnSvcActionConnectWifi();
81 if (ret != IOTC_OK) {146 if (ret != IOTC_OK) {
82 IOTC_LOGW("wifi connect error %d", ret);147 IOTC_LOGW("wifi connect error %d", ret);
@@ -85,37 +150,88 @@ static void DelayConnetWifiCb(int32_t id, void *userData)
85 IOTC_LOGN("delay connect Wifi timer cb finish");150 IOTC_LOGN("delay connect Wifi timer cb finish");
86}151}
87 152 
88-int32_t SvcConnSetNetInfo(const IotcJson *json)153+int32_t SvcConnPrepareNetInfo(const IotcJson *json)
89{154{
90 CHECK_RETURN_LOGW(json != NULL, IOTC_ERR_PARAM_INVALID, "param invalid");155 CHECK_RETURN_LOGW(json != NULL, IOTC_ERR_PARAM_INVALID, "param invalid");
156+ CHECK_RETURN_LOGW(!g_netInfoTransaction.active, IOTC_CORE_DEVICE_ERR_STORE_STATE,
157+ "net info transaction already active");
91 158 
92- int32_t ret = ParseAndSaveNetCfgWifiInfo(json);159+ char ssid[IOTC_WIFI_SSID_MAX_LEN + 1] = {0};
160+ char pwd[IOTC_WIFI_PWD_MAX_LEN + 1] = {0};
161+ int32_t ret = ParseNetCfgWifiInfo(json, ssid, sizeof(ssid), pwd, sizeof(pwd));
93 if (ret != IOTC_OK) {162 if (ret != IOTC_OK) {
94- IOTC_LOGW("save net info error %d", ret);163+ goto EXIT;
95- return ret;
96 }164 }
97 165 
166+ SnapshotNetInfo();
98 ret = ConfigSetNetInfoNeedVerify();167 ret = ConfigSetNetInfoNeedVerify();
99 if (ret != IOTC_OK) {168 if (ret != IOTC_OK) {
100 IOTC_LOGW("set net info verify flag error %d", ret);169 IOTC_LOGW("set net info verify flag error %d", ret);
101- /* 不退出仅告警 因为verify flag不影响wifi连接,且此处退出会有wifi数据残留 */170+ goto ROLLBACK;
171+ }
172+ 
173+ ret = IotcSetWifiInfo((const uint8_t *)ssid, strlen(ssid), (const uint8_t *)pwd, strlen(pwd));
174+ if (ret != IOTC_OK) {
175+ IOTC_LOGW("save net info error %d", ret);
176+ goto ROLLBACK;
102 }177 }
103 178 
104 if (IotcGetWifiMode() != IOTC_WIFI_MODE_SOFTAP) {179 if (IotcGetWifiMode() != IOTC_WIFI_MODE_SOFTAP) {
105 /* 异步执行,避免阻塞状态码响应 */180 /* 异步执行,避免阻塞状态码响应 */
106- int32_t delayTimer = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_ONCE, DelayConnetWifiCb,181+ g_netInfoTransaction.delayTimer = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_ONCE, DelayConnetWifiCb,
107 DELAY_CONNECT_WIFI_INTERVAL, NULL);182 DELAY_CONNECT_WIFI_INTERVAL, NULL);
108- if (delayTimer < 0) {183+ if (g_netInfoTransaction.delayTimer < 0) {
109- IOTC_LOGW("connect wifi timer start error %d", delayTimer);184+ ret = g_netInfoTransaction.delayTimer;
110- return delayTimer;185+ IOTC_LOGW("connect wifi timer start error %d", ret);
186+ goto ROLLBACK;
111 }187 }
112 } else {188 } else {
113 ret = EventBusSubscribe(SoftapStopEventCallback, IOTC_CORE_WIFI_EVENT_SOFTAP_STOP);189 ret = EventBusSubscribe(SoftapStopEventCallback, IOTC_CORE_WIFI_EVENT_SOFTAP_STOP);
114 if (ret != IOTC_OK) {190 if (ret != IOTC_OK) {
115 IOTC_LOGW("sub softap stop event error");191 IOTC_LOGW("sub softap stop event error");
116- return ret;192+ goto ROLLBACK;
117 }193 }
194+ g_netInfoTransaction.softapSubscribed = true;
118 }195 }
119- 196+ (void)memset_s(ssid, sizeof(ssid), 0, sizeof(ssid));
197+ (void)memset_s(pwd, sizeof(pwd), 0, sizeof(pwd));
198+ return IOTC_OK;
199+ 
200+ROLLBACK:
201+ (void)RestoreNetInfo();
202+ ClearNetInfoTransaction();
203+EXIT:
204+ (void)memset_s(ssid, sizeof(ssid), 0, sizeof(ssid));
205+ (void)memset_s(pwd, sizeof(pwd), 0, sizeof(pwd));
206+ return ret;
207+}
208+ 
209+int32_t SvcConnCommitNetInfo(void)
210+{
211+ CHECK_RETURN(g_netInfoTransaction.active, IOTC_CORE_DEVICE_ERR_STORE_STATE);
212+ ClearNetInfoTransaction();
120 return IOTC_OK;213 return IOTC_OK;
121}214}
215+ 
216+int32_t SvcConnRollbackNetInfo(void)
217+{
218+ if (!g_netInfoTransaction.active) {
219+ return IOTC_OK;
220+ }
221+ if (g_netInfoTransaction.softapSubscribed) {
222+ (void)EventBusUnsubscribe(SoftapStopEventCallback);
223+ }
224+ if (g_netInfoTransaction.delayTimer >= 0) {
225+ SchedTimerRemove(g_netInfoTransaction.delayTimer);
226+ }
227+ int32_t ret = RestoreNetInfo();
228+ ClearNetInfoTransaction();
229+ return ret;
230+}
231+ 
232+int32_t SvcConnSetNetInfo(const IotcJson *json)
233+{
234+ int32_t ret = SvcConnPrepareNetInfo(json);
235+ CHECK_RETURN(ret == IOTC_OK, ret);
236+ return SvcConnCommitNetInfo();
237+}
Mcore/wifi/application/connect/svc_conn_net_info.h+7-1
@@ -23,7 +23,13 @@ extern "C" {
23 23 
24int32_t SvcConnSetNetInfo(const IotcJson *json);24int32_t SvcConnSetNetInfo(const IotcJson *json);
25 25 
26+int32_t SvcConnPrepareNetInfo(const IotcJson *json);
27+ 
28+int32_t SvcConnCommitNetInfo(void);
29+ 
30+int32_t SvcConnRollbackNetInfo(void);
31+ 
26#ifdef __cplusplus32#ifdef __cplusplus
27}33}
28#endif34#endif
29-#endif /* SERVICE_CONNECT_NET_INFO_H */35+#endif /* SERVICE_CONNECT_NET_INFO_H */
Acore/wifi/application/connect/svc_conn_online_notify.c+232-0
@@ -0,0 +1,232 @@
1+/*
2+ * Unified Connect 3.0 WiFi Only successful-provision broadcast.
3+ * Contract frozen in W1: 255.255.255.255:5683, POST /onlinenotify,
4+ * SHA-256(devId) lower-case full hex, five sends at 100 ms intervals.
5+ */
6+ 
7+#include "svc_conn_online_notify.h"
8+ 
9+#include <stdbool.h>
10+#include <string.h>
11+ 
12+#include "coap_codec_def.h"
13+#include "coap_codec_udp.h"
14+#include "comm_def.h"
15+#include "device_state_store.h"
16+#include "iotc_conf.h"
17+#include "iotc_errcode.h"
18+#include "iotc_json.h"
19+#include "iotc_md.h"
20+#include "iotc_socket.h"
21+#include "sched_timer.h"
22+#include "security_random.h"
23+#include "securec.h"
24+#include "utils_assert.h"
25+#include "utils_common.h"
26+ 
27+#define ONLINE_NOTIFY_ADDR "255.255.255.255"
28+#define ONLINE_NOTIFY_PORT 5683
29+#define ONLINE_NOTIFY_INTERVAL_MS 100
30+#define ONLINE_NOTIFY_REQUIRED_SENDS 5
31+#define ONLINE_NOTIFY_MAX_ATTEMPTS 10
32+#define ONLINE_NOTIFY_PACKET_MAX_LEN 160
33+#define ONLINE_NOTIFY_HASH_LEN IOTC_MD_SHA256_BYTE_LEN
34+#define ONLINE_NOTIFY_HASH_HEX_LEN HEXIFY_LEN(ONLINE_NOTIFY_HASH_LEN)
35+#define COAP_CONTENT_FORMAT_APPLICATION_JSON 50
36+ 
37+typedef struct {
38+ bool active;
39+ int32_t fd;
40+ int32_t timer;
41+ uint32_t successCount;
42+ uint32_t attemptCount;
43+ uint32_t packetLen;
44+ uint8_t packet[ONLINE_NOTIFY_PACKET_MAX_LEN];
45+} OnlineNotifyContext;
46+ 
47+static OnlineNotifyContext g_onlineNotify = {
48+ .fd = -1,
49+ .timer = EVENT_SOURCE_INVALID_TIMER_FD,
50+};
51+ 
52+int32_t SvcConnBuildOnlineNotifyPacket(const char *devId, uint16_t msgId, uint8_t *packet, uint32_t *packetLen)
53+{
54+ CHECK_RETURN(devId != NULL && devId[0] != '\0' && packet != NULL && packetLen != NULL &&
55+ *packetLen > 0, IOTC_ERR_PARAM_INVALID);
56+ 
57+ uint8_t hash[ONLINE_NOTIFY_HASH_LEN] = {0};
58+ char hashHex[ONLINE_NOTIFY_HASH_HEX_LEN + 1] = {0};
59+ int32_t ret = IotcMdCalc(IOTC_MD_SHA256, (const uint8_t *)devId, strlen(devId), hash, sizeof(hash));
60+ if (ret != IOTC_OK || !UtilsHexify(hash, sizeof(hash), hashHex, sizeof(hashHex))) {
61+ (void)memset_s(hash, sizeof(hash), 0, sizeof(hash));
62+ return (ret == IOTC_OK) ? IOTC_CORE_COMM_UTILS_ERR_HEXIFY : ret;
63+ }
64+ 
65+ IotcJson *json = IotcJsonCreate();
66+ if (json == NULL) {
67+ ret = IOTC_ADAPTER_JSON_ERR_CREATE;
68+ goto EXIT;
69+ }
70+ ret = IotcJsonAddStr2Obj(json, STR_JSON_DEVID, hashHex);
71+ if (ret != IOTC_OK) {
72+ IotcJsonDelete(json);
73+ goto EXIT;
74+ }
75+ char *payloadText = IotcJsonPrint(json);
76+ IotcJsonDelete(json);
77+ if (payloadText == NULL) {
78+ ret = IOTC_ADAPTER_JSON_ERR_PRINT;
79+ goto EXIT;
80+ }
81+ 
82+ const uint8_t contentFormat = COAP_CONTENT_FORMAT_APPLICATION_JSON;
83+ const CoapOption options[] = {
84+ { COAP_OPTION_TYPE_URI_PATH,
85+ { (const uint8_t *)STR_URI_ONLINE_NOTIFY, sizeof(STR_URI_ONLINE_NOTIFY) - 1U } },
86+ { COAP_OPTION_TYPE_CONTENT_FORMAT, { &contentFormat, sizeof(contentFormat) } },
87+ };
88+ CoapData payload = { (const uint8_t *)payloadText, strlen(payloadText) };
89+ CoapBuildPacket build = {
90+ .header = { .type = COAP_MSG_TYPE_CON, .tkl = 0, .code = COAP_METHOD_TYPE_POST, .msgId = msgId },
91+ .opNum = ARRAY_SIZE(options),
92+ .options = options,
93+ .payload = &payload,
94+ };
95+ CoapPacket decoded = {0};
96+ CoapBuffer buffer = { .buffer = packet, .len = 0, .size = *packetLen };
97+ ret = CoapUdpEncode(&build, &decoded, &buffer);
98+ IotcJsonFreePrint(payloadText);
99+ if (ret == IOTC_OK) {
100+ *packetLen = buffer.len;
101+ }
102+ 
103+EXIT:
104+ (void)memset_s(hash, sizeof(hash), 0, sizeof(hash));
105+ (void)memset_s(hashHex, sizeof(hashHex), 0, sizeof(hashHex));
106+ return ret;
107+}
108+ 
109+#if IOTC_CONF_WIFI_ONLY_SUPPORT
110+static void OnlineNotifyCleanup(bool removeTimer)
111+{
112+ if (removeTimer && g_onlineNotify.timer >= 0) {
113+ SchedTimerRemove(g_onlineNotify.timer);
114+ }
115+ if (g_onlineNotify.fd >= 0) {
116+ IotcClose(g_onlineNotify.fd);
117+ }
118+ (void)memset_s(&g_onlineNotify, sizeof(g_onlineNotify), 0, sizeof(g_onlineNotify));
119+ g_onlineNotify.fd = -1;
120+ g_onlineNotify.timer = EVENT_SOURCE_INVALID_TIMER_FD;
121+}
122+ 
123+static int32_t OnlineNotifySendOnce(void)
124+{
125+ IotcSockaddrIn target = {
126+ .sinFamily = IOTC_SOCKET_DOMAIN_AF_INET,
127+ .sinPort = IotcHtons(ONLINE_NOTIFY_PORT),
128+ .sinAddr = IotcInetAddr(ONLINE_NOTIFY_ADDR),
129+ };
130+ ++g_onlineNotify.attemptCount;
131+ int32_t sent = IotcSendTo(g_onlineNotify.fd, g_onlineNotify.packet, g_onlineNotify.packetLen,
132+ (const IotcSockaddr *)&target, sizeof(target));
133+ if (sent == (int32_t)g_onlineNotify.packetLen) {
134+ ++g_onlineNotify.successCount;
135+ return IOTC_OK;
136+ }
137+ IOTC_LOGW("online notify send error %d/%u", sent, g_onlineNotify.packetLen);
138+ return IOTC_ERROR;
139+}
140+ 
141+static void OnlineNotifyTimerCallback(int32_t id, void *userData)
142+{
143+ NOT_USED(id);
144+ NOT_USED(userData);
145+ if (!g_onlineNotify.active) {
146+ return;
147+ }
148+ 
149+ (void)OnlineNotifySendOnce();
150+ if (g_onlineNotify.successCount >= ONLINE_NOTIFY_REQUIRED_SENDS) {
151+ int32_t ret = IotcDssMarkOnlineNotifyDone();
152+ if (ret != IOTC_OK) {
153+ IOTC_LOGW("mark online notify done error %d", ret);
154+ }
155+ OnlineNotifyCleanup(true);
156+ } else if (g_onlineNotify.attemptCount >= ONLINE_NOTIFY_MAX_ATTEMPTS) {
157+ IOTC_LOGW("online notify retry exhausted %u/%u", g_onlineNotify.successCount,
158+ g_onlineNotify.attemptCount);
159+ OnlineNotifyCleanup(true);
160+ }
161+}
162+ 
163+int32_t SvcConnOnlineNotifyStart(void)
164+{
165+ if (g_onlineNotify.active) {
166+ return IOTC_OK;
167+ }
168+ 
169+ IotcDssProvisionPending pending = {0};
170+ int32_t ret = IotcDssLoadProvisionPending(&pending);
171+ if (ret == IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND ||
172+ (ret == IOTC_OK && pending.onlineNotifyDone != 0)) {
173+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
174+ return IOTC_OK;
175+ }
176+ if (ret != IOTC_OK || pending.tempDevId[0] == '\0') {
177+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
178+ return (ret == IOTC_OK) ? IOTC_ERR_PARAM_INVALID : ret;
179+ }
180+ 
181+ uint16_t msgId = 0;
182+ ret = SecurityRandom((uint8_t *)&msgId, sizeof(msgId));
183+ if (ret == IOTC_OK) {
184+ g_onlineNotify.packetLen = sizeof(g_onlineNotify.packet);
185+ ret = SvcConnBuildOnlineNotifyPacket(pending.tempDevId, msgId, g_onlineNotify.packet,
186+ &g_onlineNotify.packetLen);
187+ }
188+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
189+ if (ret != IOTC_OK) {
190+ OnlineNotifyCleanup(false);
191+ return ret;
192+ }
193+ 
194+ g_onlineNotify.fd = IotcSocket(IOTC_SOCKET_DOMAIN_AF_INET, IOTC_SOCKET_TYPE_DGRAM,
195+ IOTC_SOCKET_PROTO_UDP);
196+ if (g_onlineNotify.fd < 0) {
197+ ret = g_onlineNotify.fd;
198+ OnlineNotifyCleanup(false);
199+ return ret;
200+ }
201+ ret = IotcSetSocketOpt(g_onlineNotify.fd, IOTC_SOCKET_OPTION_ENABLE_BROADCAST, NULL, 0);
202+ if (ret != IOTC_OK) {
203+ OnlineNotifyCleanup(false);
204+ return ret;
205+ }
206+ 
207+ g_onlineNotify.active = true;
208+ (void)OnlineNotifySendOnce();
209+ g_onlineNotify.timer = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_REPEAT, OnlineNotifyTimerCallback,
210+ ONLINE_NOTIFY_INTERVAL_MS, NULL);
211+ if (g_onlineNotify.timer < 0) {
212+ ret = g_onlineNotify.timer;
213+ OnlineNotifyCleanup(false);
214+ return ret;
215+ }
216+ return IOTC_OK;
217+}
218+ 
219+void SvcConnOnlineNotifyStop(void)
220+{
221+ OnlineNotifyCleanup(true);
222+}
223+#else
224+int32_t SvcConnOnlineNotifyStart(void)
225+{
226+ return IOTC_OK;
227+}
228+ 
229+void SvcConnOnlineNotifyStop(void)
230+{
231+}
232+#endif
Acore/wifi/application/connect/svc_conn_online_notify.h+22-0
@@ -0,0 +1,22 @@
1+/* WiFi Only /onlinenotify packet builder and finite broadcaster. */
2+ 
3+#ifndef SERVICE_CONNECT_ONLINE_NOTIFY_H
4+#define SERVICE_CONNECT_ONLINE_NOTIFY_H
5+ 
6+#include <stdint.h>
7+ 
8+#ifdef __cplusplus
9+extern "C" {
10+#endif
11+ 
12+int32_t SvcConnBuildOnlineNotifyPacket(const char *devId, uint16_t msgId, uint8_t *packet, uint32_t *packetLen);
13+ 
14+int32_t SvcConnOnlineNotifyStart(void);
15+ 
16+void SvcConnOnlineNotifyStop(void);
17+ 
18+#ifdef __cplusplus
19+}
20+#endif
21+ 
22+#endif /* SERVICE_CONNECT_ONLINE_NOTIFY_H */
Mcore/wifi/application/connect/wifi_svc_conn.c+42-5
@@ -30,6 +30,10 @@
30#include "iotc_event.h"30#include "iotc_event.h"
31#include "iotc_wifi.h"31#include "iotc_wifi.h"
32#include "fwk_main.h"32#include "fwk_main.h"
33+#include "iotc_conf.h"
34+#include "device_state_store.h"
35+#include "svc_conn_online_notify.h"
36+#include "securec.h"
33 37 
34static const char *NET_CONNECT_SERVICE_NAME = "CONNECT";38static const char *NET_CONNECT_SERVICE_NAME = "CONNECT";
35 39 
@@ -38,6 +42,7 @@ static void RevokeEventWifiInfoCallback(uint32_t event, void *param, uint32_t le
38 NOT_USED(event);42 NOT_USED(event);
39 NOT_USED(param);43 NOT_USED(param);
40 NOT_USED(len);44 NOT_USED(len);
45+ SvcConnOnlineNotifyStop();
41 if (!ConfigIsNetInfoNeedVerify() && !ConfigIsNetInfoChecked()) {46 if (!ConfigIsNetInfoNeedVerify() && !ConfigIsNetInfoChecked()) {
42 IOTC_LOGI("no wifi info need clear");47 IOTC_LOGI("no wifi info need clear");
43 return;48 return;
@@ -76,6 +81,7 @@ static int32_t StopWifiConnectService(int32_t instanceId, ServiceParam *param)
76 if (ctx->onFinish != NULL) {81 if (ctx->onFinish != NULL) {
77 ctx->onFinish(instanceId);82 ctx->onFinish(instanceId);
78 }83 }
84+ SvcConnOnlineNotifyStop();
79 ConnSvcCtxDeinit();85 ConnSvcCtxDeinit();
80 ConnectConfigInfoDeinit();86 ConnectConfigInfoDeinit();
81 (void)EventBusUnsubscribe(RevokeEventWifiInfoCallback);87 (void)EventBusUnsubscribe(RevokeEventWifiInfoCallback);
@@ -96,13 +102,22 @@ static int32_t StartWifiConnectService(int32_t instanceId, ServiceFinishCallback
96 return ret;102 return ret;
97 }103 }
98 104 
99- if (ConfigIsNetInfoNeedVerify() || ConfigIsNetInfoChecked()) {105+#if IOTC_CONF_WIFI_ONLY_SUPPORT
100- ret = ConnSvcActionConnectWifi();106+ if (ConfigIsNetInfoNeedVerify()) {
107+ IotcDssProvisionPending pending = {0};
108+ ret = IotcDssLoadProvisionPending(&pending);
109+ (void)memset_s(&pending, sizeof(pending), 0, sizeof(pending));
101 if (ret != IOTC_OK) {110 if (ret != IOTC_OK) {
102- IOTC_LOGW("conn svc connect wifi error %d", ret);111+ /* HubSetup Wi-Fi 写入后、pending commit 前断电:不得用半套配置连网。 */
103- return ret;112+ IOTC_LOGW("discard incomplete HubSetup Wi-Fi state %d", ret);
113+ (void)IotcDeleteWifiInfo();
114+ ret = ConfigClearNetInfoFlag();
115+ if (ret != IOTC_OK) {
116+ return ret;
117+ }
104 }118 }
105 }119 }
120+#endif
106 121 
107 ret = EventBusSubscribe(RevokeEventWifiInfoCallback, IOTC_CORE_COMM_EVENT_MAIN_RESTORE);122 ret = EventBusSubscribe(RevokeEventWifiInfoCallback, IOTC_CORE_COMM_EVENT_MAIN_RESTORE);
108 if (ret != IOTC_OK) {123 if (ret != IOTC_OK) {
@@ -116,6 +131,27 @@ static int32_t StartWifiConnectService(int32_t instanceId, ServiceFinishCallback
116 return ret;131 return ret;
117 }132 }
118 133 
134+ if (ConfigIsNetInfoNeedVerify() || ConfigIsNetInfoChecked()) {
135+#if IOTC_CONF_WIFI_ONLY_SUPPORT
136+ /*
137+ * WS63 reports DEVICE_SVC_START before RF/Wi-Fi platform init has
138+ * necessarily completed. Defer the first STA attempt so a transient
139+ * sta_start failure cannot abort the CONNECT service at cold boot.
140+ */
141+ ret = ConnSvcScheduleInitialConnect();
142+ if (ret != IOTC_OK) {
143+ IOTC_LOGW("schedule initial wifi connect error %d", ret);
144+ return ret;
145+ }
146+#else
147+ ret = ConnSvcActionConnectWifi();
148+ if (ret != IOTC_OK) {
149+ IOTC_LOGW("conn svc connect wifi error %d", ret);
150+ return ret;
151+ }
152+#endif
153+ }
154+ 
119 return IOTC_OK;155 return IOTC_OK;
120}156}
121 157 
@@ -158,7 +194,8 @@ int32_t WifiServiceConnectInit(void)
158 194 
159void WifiServiceConnectDeinit(void)195void WifiServiceConnectDeinit(void)
160{196{
197+ SvcConnOnlineNotifyStop();
161 ConnectConfigInfoDeinit();198 ConnectConfigInfoDeinit();
162 ConnSvcCtxDeinit();199 ConnSvcCtxDeinit();
163 ServiceManagerUnregisterInstance(IOTC_SERVICE_ID_WIFI_CONNECT);200 ServiceManagerUnregisterInstance(IOTC_SERVICE_ID_WIFI_CONNECT);
164-}201+}
Mcore/wifi/application/softap/svc_softap_coap.c+68-7
@@ -36,6 +36,7 @@
36#include "iotc_svc_conn.h"36#include "iotc_svc_conn.h"
37#include "iotc_errcode.h"37#include "iotc_errcode.h"
38#include "e2e_ctl_msg.h"38#include "e2e_ctl_msg.h"
39+#include "svc_conn_net_info.h"
39 40 
40#define E2E_CTRL_SEQ_WINDOWS 3041#define E2E_CTRL_SEQ_WINDOWS 30
41#define RETRANS_WAIT_TIME_MS 50042#define RETRANS_WAIT_TIME_MS 500
@@ -132,6 +133,51 @@ static int32_t NetCfgInfoRecvProcess(const char *netInfo, uint32_t len)
132 return IOTC_OK;133 return IOTC_OK;
133}134}
134 135 
136+static int32_t HubSetupInfoRecvProcess(const char *netInfo, uint32_t len)
137+{
138+ IotcJson *jsonObj = IotcJsonParseWithLen(netInfo, len);
139+ if (jsonObj == NULL) {
140+ IOTC_LOGW("parse json error");
141+ return IOTC_ADAPTER_JSON_ERR_PARSE;
142+ }
143+ IotcJson *dataObj = IotcJsonGetObj(jsonObj, STR_JSON_DATA);
144+ if (dataObj == NULL) {
145+ IotcJsonDelete(jsonObj);
146+ IOTC_LOGW("get json data error");
147+ return IOTC_ADAPTER_JSON_ERR_GET_OBJ;
148+ }
149+ 
150+ bool alreadyCommitted = false;
151+ int32_t ret = DevSvcProxyPrepareHubBindInfo(dataObj, &alreadyCommitted);
152+ if (ret != IOTC_OK) {
153+ IotcJsonDelete(jsonObj);
154+ IOTC_LOGW("prepare hub bind info error %d", ret);
155+ return ret;
156+ }
157+ if (alreadyCommitted) {
158+ ret = SvcConnSetNetInfo(dataObj);
159+ IotcJsonDelete(jsonObj);
160+ return ret;
161+ }
162+ 
163+ ret = SvcConnPrepareNetInfo(dataObj);
164+ if (ret != IOTC_OK) {
165+ DevSvcProxyAbortHubBindInfo();
166+ IotcJsonDelete(jsonObj);
167+ return ret;
168+ }
169+ ret = DevSvcProxyCommitHubBindInfo();
170+ if (ret != IOTC_OK) {
171+ DevSvcProxyAbortHubBindInfo();
172+ SvcConnRollbackNetInfo();
173+ IotcJsonDelete(jsonObj);
174+ return ret;
175+ }
176+ ret = SvcConnCommitNetInfo();
177+ IotcJsonDelete(jsonObj);
178+ return ret;
179+}
180+ 
135static void CloseSoftapService(int32_t id, void *userData)181static void CloseSoftapService(int32_t id, void *userData)
136{182{
137 NOT_USED(id);183 NOT_USED(id);
@@ -142,9 +188,9 @@ static void CloseSoftapService(int32_t id, void *userData)
142 }188 }
143}189}
144 190 
145-void SoftapCoapSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req, const SocketAddr *addr, void *userData)191+static void SoftapCoapSetupReqHandlerInner(CoapEndpoint *endpoint, const CoapPacket *req,
192+ const SocketAddr *addr, bool standardHubSetup)
146{193{
147- NOT_USED(userData);
148 CHECK_V_RETURN_LOGW(endpoint != NULL && req != NULL && addr != NULL, "invalid param");194 CHECK_V_RETURN_LOGW(endpoint != NULL && req != NULL && addr != NULL, "invalid param");
149 SoftapServiceContext *ctx = GetSoftapServiceContext();195 SoftapServiceContext *ctx = GetSoftapServiceContext();
150 if (ctx == NULL || UTILS_IS_BIT_SET(ctx->bitMap, SOFTAP_CTX_BIT_MAP_NETCFG_RECVED)) {196 if (ctx == NULL || UTILS_IS_BIT_SET(ctx->bitMap, SOFTAP_CTX_BIT_MAP_NETCFG_RECVED)) {
@@ -152,14 +198,16 @@ void SoftapCoapSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req, co
152 return;198 return;
153 }199 }
154 200 
155- bool isNetCfgFail = false;201+ bool netCfgDone = false;
156- int32_t ret = NetCfgInfoRecvProcess((const char *)req->payload.data, req->payload.len);202+ int32_t ret = standardHubSetup ?
203+ HubSetupInfoRecvProcess((const char *)req->payload.data, req->payload.len) :
204+ NetCfgInfoRecvProcess((const char *)req->payload.data, req->payload.len);
157 if (ret != IOTC_OK) {205 if (ret != IOTC_OK) {
158 IOTC_LOGE("net cfg info process error %d", ret);206 IOTC_LOGE("net cfg info process error %d", ret);
159 } else {207 } else {
160 IOTC_LOGN("net cfg info process ok");208 IOTC_LOGN("net cfg info process ok");
161 UTILS_BIT_SET(ctx->bitMap, SOFTAP_CTX_BIT_MAP_NETCFG_RECVED);209 UTILS_BIT_SET(ctx->bitMap, SOFTAP_CTX_BIT_MAP_NETCFG_RECVED);
162- isNetCfgFail = true;210+ netCfgDone = true;
163 }211 }
164 212 
165 IotcJson *respJson = UtilsJsonCreateErrcode(ret);213 IotcJson *respJson = UtilsJsonCreateErrcode(ret);
@@ -188,7 +236,7 @@ void SoftapCoapSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req, co
188 if (ret != IOTC_OK) {236 if (ret != IOTC_OK) {
189 IOTC_LOGW("send coap resp msg error %d", ret);237 IOTC_LOGW("send coap resp msg error %d", ret);
190 }238 }
191- CHECK_V_RETURN(isNetCfgFail);239+ CHECK_V_RETURN(netCfgDone);
192 if (ctx->stopTimerFd < 0) {240 if (ctx->stopTimerFd < 0) {
193 ctx->stopTimerFd = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_ONCE, CloseSoftapService, RETRANS_WAIT_TIME_MS, NULL);241 ctx->stopTimerFd = SchedTimerAdd(EVENT_SOURCE_TIMER_TYPE_ONCE, CloseSoftapService, RETRANS_WAIT_TIME_MS, NULL);
194 if (ctx->stopTimerFd < 0) {242 if (ctx->stopTimerFd < 0) {
@@ -198,6 +246,19 @@ void SoftapCoapSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req, co
198 }246 }
199}247}
200 248 
249+void SoftapCoapSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req, const SocketAddr *addr, void *userData)
250+{
251+ NOT_USED(userData);
252+ SoftapCoapSetupReqHandlerInner(endpoint, req, addr, false);
253+}
254+ 
255+void SoftapCoapHubSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req,
256+ const SocketAddr *addr, void *userData)
257+{
258+ NOT_USED(userData);
259+ SoftapCoapSetupReqHandlerInner(endpoint, req, addr, true);
260+}
261+ 
201static bool SessMsgProcessCheck(SessMsg *msg, UtilsBuffer *buf, SessAddtlInfo *info)262static bool SessMsgProcessCheck(SessMsg *msg, UtilsBuffer *buf, SessAddtlInfo *info)
202{263{
203 CHECK_RETURN(msg != NULL && buf != NULL && buf->buffer != NULL && buf->len != 0 && buf->size >= buf->len &&264 CHECK_RETURN(msg != NULL && buf != NULL && buf->buffer != NULL && buf->len != 0 && buf->size >= buf->len &&
@@ -547,4 +608,4 @@ void SoftapCoapE2eCtrlHandler(CoapEndpoint *endpoint, const CoapPacket *req, con
547 if (ret != IOTC_OK) {608 if (ret != IOTC_OK) {
548 IOTC_LOGW("send e2e ctrl resp msg error %d", ret);609 IOTC_LOGW("send e2e ctrl resp msg error %d", ret);
549 }610 }
550-}611+}
Mcore/wifi/application/softap/svc_softap_coap.h+4-1
@@ -26,6 +26,9 @@ void SoftapCoapSpekeReqHandler(CoapEndpoint *endpoint, const CoapPacket *req, co
26 26 
27void SoftapCoapSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req, const SocketAddr *addr, void *userData);27void SoftapCoapSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req, const SocketAddr *addr, void *userData);
28 28 
29+void SoftapCoapHubSetupReqHandler(CoapEndpoint *endpoint, const CoapPacket *req,
30+ const SocketAddr *addr, void *userData);
31+ 
29void SoftapCoapE2eCtrlHandler(CoapEndpoint *endpoint, const CoapPacket *req, const SocketAddr *addr, void *userData);32void SoftapCoapE2eCtrlHandler(CoapEndpoint *endpoint, const CoapPacket *req, const SocketAddr *addr, void *userData);
30 33 
31SessCode SoftapCoapMsgRecvPreProcess(SessMsg *msg, UtilsBuffer *buf, SessAddtlInfo *info);34SessCode SoftapCoapMsgRecvPreProcess(SessMsg *msg, UtilsBuffer *buf, SessAddtlInfo *info);
@@ -44,4 +47,4 @@ SessCode SoftapCoapMsgSendFinalProcess(SessMsg *msg, UtilsBuffer *buf, SessAddtl
44}47}
45#endif48#endif
46 49 
47-#endif /* NETCFG_SOFTAP_METHOD_H */50+#endif /* NETCFG_SOFTAP_METHOD_H */
Mcore/wifi/application/softap/svc_softap_coap_provision.c+80-0
@@ -24,6 +24,86 @@
24#include "utils_json.h"24#include "utils_json.h"
25#include "iotc_errcode.h"25#include "iotc_errcode.h"
26#include "iotc_svc_sec.h"26#include "iotc_svc_sec.h"
27+#include "iotc_svc_dev.h"
28+#include "dev_info.h"
29+#include "dev_info_mdl.h"
30+#include "securec.h"
31+#include "wifi_net_info.h"
32+ 
33+#define SOFTAP_DEVICE_INFO_HIV "2.0"
34+ 
35+static IotcJson *SoftapBuildDeviceInfoResp(void)
36+{
37+ IotcJson *respJson = UtilsJsonCreateErrcode(IOTC_OK);
38+ if (respJson == NULL) {
39+ return NULL;
40+ }
41+ 
42+ DevAuthInfo authInfo = {0};
43+ bool isAuthInfoExist = false;
44+ int32_t ret = DevSvcProxyGetAuthInfo(&isAuthInfoExist, &authInfo);
45+ if (ret != IOTC_OK ||
46+ IotcJsonAddStr2Obj(respJson, STR_JSON_DEV_ID, isAuthInfoExist ? authInfo.devId : "") != IOTC_OK) {
47+ (void)memset_s(&authInfo, sizeof(authInfo), 0, sizeof(authInfo));
48+ IotcJsonDelete(respJson);
49+ return NULL;
50+ }
51+ (void)memset_s(&authInfo, sizeof(authInfo), 0, sizeof(authInfo));
52+ 
53+ IotcJson *devInfoJson = MdlBuildDevInfoJson(ModelGetDevInfo());
54+ if (devInfoJson == NULL) {
55+ IotcJsonDelete(respJson);
56+ return NULL;
57+ }
58+ if (IotcJsonReplaceStr2Obj(devInfoJson, STR_JSON_HIV, SOFTAP_DEVICE_INFO_HIV) != IOTC_OK) {
59+ IotcJsonDelete(devInfoJson);
60+ IotcJsonDelete(respJson);
61+ return NULL;
62+ }
63+ char mac[MAC_STR_MAX_LEN] = {0};
64+ ret = GetWifiMacAddrStr(mac, sizeof(mac));
65+ if (ret != IOTC_OK || IotcJsonAddStr2Obj(devInfoJson, STR_JSON_MAC, mac) != IOTC_OK) {
66+ IotcJsonDelete(devInfoJson);
67+ IotcJsonDelete(respJson);
68+ return NULL;
69+ }
70+ ret = IotcJsonAddItem2Obj(respJson, STR_JSON_DEV_INFO, devInfoJson);
71+ if (ret != IOTC_OK) {
72+ IotcJsonDelete(devInfoJson);
73+ IotcJsonDelete(respJson);
74+ return NULL;
75+ }
76+ return respJson;
77+}
78+ 
79+void SoftapCoapDeviceInfoReqHandler(CoapEndpoint *endpoint, const CoapPacket *req,
80+ const SocketAddr *addr, void *userData)
81+{
82+ NOT_USED(userData);
83+ CHECK_V_RETURN_LOGW(endpoint != NULL && req != NULL && addr != NULL, "invalid param");
84+ if (req->payload.len != 0) {
85+ IOTC_LOGW("DeviceInfo request payload must be empty, len=%u", req->payload.len);
86+ return;
87+ }
88+ IOTC_LOGI("DeviceInfo request payload len=0, hiv=%s", SOFTAP_DEVICE_INFO_HIV);
89+ 
90+ IotcJson *respJson = SoftapBuildDeviceInfoResp();
91+ if (respJson == NULL) {
92+ IOTC_LOGW("build DeviceInfo response error");
93+ return;
94+ }
95+ 
96+ CoapServerRespParam respParam;
97+ int32_t ret = CoapServerBuildDefaultRespParam(&respParam, req, respJson);
98+ if (ret == IOTC_OK) {
99+ CoapPacket packet;
100+ ret = CoapServerSendResp(endpoint, &respParam, addr, &packet);
101+ }
102+ IotcJsonDelete(respJson);
103+ if (ret != IOTC_OK) {
104+ IOTC_LOGW("send DeviceInfo response error %d", ret);
105+ }
106+}
27 107 
28void SoftapCoapGetOemProvisionRequestReqHandler(CoapEndpoint *endpoint, const CoapPacket *req,108void SoftapCoapGetOemProvisionRequestReqHandler(CoapEndpoint *endpoint, const CoapPacket *req,
29 const SocketAddr *addr, void *userData)109 const SocketAddr *addr, void *userData)
Mcore/wifi/application/softap/svc_softap_coap_provision.h+4-1
@@ -37,8 +37,11 @@ void SoftapCoapProvisionCertReqHandler(CoapEndpoint *endpoint, const CoapPacket
37void SoftapCoapProvisionPskReqHandler(CoapEndpoint *endpoint, const CoapPacket *req,37void SoftapCoapProvisionPskReqHandler(CoapEndpoint *endpoint, const CoapPacket *req,
38 const SocketAddr *addr, void *userData);38 const SocketAddr *addr, void *userData);
39 39 
40+void SoftapCoapDeviceInfoReqHandler(CoapEndpoint *endpoint, const CoapPacket *req,
41+ const SocketAddr *addr, void *userData);
42+ 
40#ifdef __cplusplus43#ifdef __cplusplus
41}44}
42#endif45#endif
43 46 
44-#endif /* SVC_SOFTAP_COAP_PROVISION_H */47+#endif /* SVC_SOFTAP_COAP_PROVISION_H */
Mcore/wifi/application/softap/svc_softap_ctx.c+6-2
@@ -21,7 +21,7 @@
21 21 
22static SoftapServiceContext *g_softapCtx = NULL;22static SoftapServiceContext *g_softapCtx = NULL;
23 23 
24-SoftapServiceContext *GetSoftapServiceContext()24+SoftapServiceContext *GetSoftapServiceContext(void)
25{25{
26 return g_softapCtx;26 return g_softapCtx;
27}27}
@@ -37,10 +37,14 @@ int32_t SoftapServiceContextInit(void)
37 g_softapCtx->tmoTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;37 g_softapCtx->tmoTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
38 g_softapCtx->startUpTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;38 g_softapCtx->startUpTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
39 g_softapCtx->stopTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;39 g_softapCtx->stopTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
40+ g_softapCtx->sess.staTimer = EVENT_SOURCE_INVALID_TIMER_FD;
41+ for (uint32_t i = 0; i < IOTC_CONF_SOFTAP_MAX_PEER_SESS_NUM; ++i) {
42+ g_softapCtx->sess.peerSess[i].timer = EVENT_SOURCE_INVALID_TIMER_FD;
43+ }
40 return IOTC_OK;44 return IOTC_OK;
41}45}
42 46 
43void SoftapServiceContextDeinit(void)47void SoftapServiceContextDeinit(void)
44{48{
45 UTILS_FREE_2_NULL(g_softapCtx);49 UTILS_FREE_2_NULL(g_softapCtx);
46-}50+}
Mcore/wifi/application/softap/svc_softap_ctx.h+2-2
@@ -82,7 +82,7 @@ typedef struct {
82 SoftapSvcInitParam initParam;82 SoftapSvcInitParam initParam;
83} SoftapServiceContext;83} SoftapServiceContext;
84 84 
85-SoftapServiceContext *GetSoftapServiceContext();85+SoftapServiceContext *GetSoftapServiceContext(void);
86 86 
87int32_t SoftapServiceContextInit(void);87int32_t SoftapServiceContextInit(void);
88 88 
@@ -92,4 +92,4 @@ void SoftapServiceContextDeinit(void);
92}92}
93#endif93#endif
94 94 
95-#endif /* SERVICE_SOFTAP_CTX_H */95+#endif /* SERVICE_SOFTAP_CTX_H */
Mcore/wifi/application/softap/svc_softap_sess.c+12-5
@@ -298,8 +298,9 @@ static int32_t SoftapSessionSetup(SoftapSess *sess)
298static int32_t SoftapCoapEndpointSetup(SoftapSess *sess, const SoftapSvcInitParam *initParam)298static int32_t SoftapCoapEndpointSetup(SoftapSess *sess, const SoftapSvcInitParam *initParam)
299{299{
300 /* uri white list for recv not decrypt or not base64 decode */300 /* uri white list for recv not decrypt or not base64 decode */
301- static const char *plainUri[] = {STR_URI_SPEKE};301+ /* DeviceInfo is an empty GET; its response remains encrypted. */
302- static const char *noBase64uri[] = {STR_URI_SPEKE};302+ static const char *plainUri[] = {STR_URI_SPEKE, STR_URI_DEVICE_INFO};
303+ static const char *noBase64uri[] = {STR_URI_SPEKE, STR_URI_DEVICE_INFO};
303 304 
304 sess->plainUri = plainUri;305 sess->plainUri = plainUri;
305 sess->plainUriNum = ARRAY_SIZE(plainUri);306 sess->plainUriNum = ARRAY_SIZE(plainUri);
@@ -308,6 +309,7 @@ static int32_t SoftapCoapEndpointSetup(SoftapSess *sess, const SoftapSvcInitPara
308 309 
309 static const CoapResource SPEKE_RES[] = {310 static const CoapResource SPEKE_RES[] = {
310 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_SPEKE, NULL, SoftapCoapSpekeReqHandler},311 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_SPEKE, NULL, SoftapCoapSpekeReqHandler},
312+ {UTILS_BIT(COAP_METHOD_TYPE_GET), STR_URI_DEVICE_INFO, NULL, SoftapCoapDeviceInfoReqHandler},
311 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_GET_OEM_PROVISION_REQUEST, NULL,313 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_GET_OEM_PROVISION_REQUEST, NULL,
312 SoftapCoapGetOemProvisionRequestReqHandler},314 SoftapCoapGetOemProvisionRequestReqHandler},
313 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_OEM_PROVISION, NULL, SoftapCoapOemProvisionReqHandler},315 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_OEM_PROVISION, NULL, SoftapCoapOemProvisionReqHandler},
@@ -316,7 +318,8 @@ static int32_t SoftapCoapEndpointSetup(SoftapSess *sess, const SoftapSvcInitPara
316 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_PROVISION_CERT, NULL, SoftapCoapProvisionCertReqHandler},318 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_PROVISION_CERT, NULL, SoftapCoapProvisionCertReqHandler},
317 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_PROVISION_PSK, NULL, SoftapCoapProvisionPskReqHandler},319 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_PROVISION_PSK, NULL, SoftapCoapProvisionPskReqHandler},
318 };320 };
319- static const CoapResource CLOUD_SETUP_V2_RES[] = {321+ static const CoapResource NETCFG_RES[] = {
322+ {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_HUB_SETUP, NULL, SoftapCoapHubSetupReqHandler},
320 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_CLOUD_SETUP_V2, NULL, SoftapCoapSetupReqHandler},323 {UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_CLOUD_SETUP_V2, NULL, SoftapCoapSetupReqHandler},
321 };324 };
322 static const CoapResource E2E_CTL_RES[] = {325 static const CoapResource E2E_CTL_RES[] = {
@@ -330,7 +333,7 @@ static int32_t SoftapCoapEndpointSetup(SoftapSess *sess, const SoftapSvcInitPara
330 }333 }
331 334 
332 if (UTILS_IS_BIT_SET(initParam->bitMap, IOTC_WIFI_SERVICE_SOFTAP_NETCFG)) {335 if (UTILS_IS_BIT_SET(initParam->bitMap, IOTC_WIFI_SERVICE_SOFTAP_NETCFG)) {
333- ret = CoapServerAddResource(sess->coapStack.endpoint, CLOUD_SETUP_V2_RES, ARRAY_SIZE(CLOUD_SETUP_V2_RES));336+ ret = CoapServerAddResource(sess->coapStack.endpoint, NETCFG_RES, ARRAY_SIZE(NETCFG_RES));
334 if (ret != IOTC_OK) {337 if (ret != IOTC_OK) {
335 IOTC_LOGW("add cloud setup coap res error %d", ret);338 IOTC_LOGW("add cloud setup coap res error %d", ret);
336 return ret;339 return ret;
@@ -472,4 +475,8 @@ void DestroySoftapSess(SoftapSess *sess)
472 DestroyPeerSession(&sess->peerSess[i]);475 DestroyPeerSession(&sess->peerSess[i]);
473 }476 }
474 (void)memset_s(sess, sizeof(SoftapSess), 0, sizeof(SoftapSess));477 (void)memset_s(sess, sizeof(SoftapSess), 0, sizeof(SoftapSess));
475-}478+ sess->staTimer = EVENT_SOURCE_INVALID_TIMER_FD;
479+ for (uint32_t i = 0; i < IOTC_CONF_SOFTAP_MAX_PEER_SESS_NUM; ++i) {
480+ sess->peerSess[i].timer = EVENT_SOURCE_INVALID_TIMER_FD;
481+ }
482+}
Mcore/wifi/core_wifi.gni+7-0
@@ -86,6 +86,13 @@ iotc_core_wifi_src = [
86 "$wifi_core_path/infrastructure/utils/coap_sess_utils.c",86 "$wifi_core_path/infrastructure/utils/coap_sess_utils.c",
87]87]
88 88 
89+if (iot_connect_wifi_only_support) {
90+ iotc_core_wifi_inc += [ "$wifi_core_path/application/connect" ]
91+ iotc_core_wifi_src += [
92+ "$wifi_core_path/application/connect/svc_conn_online_notify.c",
93+ ]
94+}
95+ 
89# Keep 2.0's Wi-Fi LAN Search and cloud implementation in the 2.0 source96# Keep 2.0's Wi-Fi LAN Search and cloud implementation in the 2.0 source
90# set. 3.0 Wi-Fi Combo uses the Network LAN Search implementation instead.97# set. 3.0 Wi-Fi Combo uses the Network LAN Search implementation instead.
91if (iotc_one_connect_version_code < 2) {98if (iotc_one_connect_version_code < 2) {
Mcore/wifi/interfaces/core_wifi_init.c+20-5
@@ -21,9 +21,13 @@
21#include "wifi_svc_conn.h"21#include "wifi_svc_conn.h"
22#include "utils_assert.h"22#include "utils_assert.h"
23#include "iotc_errcode.h"23#include "iotc_errcode.h"
24-#include "local_ctl_svc.h"24+#include "iotc_conf.h"
25 25 
26-#if IOTC_ONECONNECT_VERSION_CODE >= 226+#if !IOTC_CONF_WIFI_ONLY_SUPPORT
27+#include "local_ctl_svc.h"
28+#endif
29+ 
30+#if IOTC_ONECONNECT_VERSION_CODE >= 2 && !IOTC_CONF_WIFI_ONLY_SUPPORT
27#include "sched_fd_watch.h"31#include "sched_fd_watch.h"
28#include "net_trans_buffer_inner.h"32#include "net_trans_buffer_inner.h"
29#include "iotc_event.h"33#include "iotc_event.h"
@@ -35,7 +39,14 @@
35#include "m2m_cloud_svc.h"39#include "m2m_cloud_svc.h"
36#endif40#endif
37 41 
38-#if IOTC_ONECONNECT_VERSION_CODE >= 242+#if IOTC_CONF_WIFI_ONLY_SUPPORT
43+static const FwkInitUnit CORE_WIFI_ONLY[] = {
44+ {FWK_INIT_LVL_COMP, "wifi_buffer", TransBufferInit, TransBufferDeinit},
45+ {FWK_INIT_LVL_FWK, "wifi_fd_watch", WifiSchedFdWatchInit, WifiSchedFdWatchDeinit},
46+ {FWK_INIT_LVL_BIZ, "softap", WifiServiceSoftapInit, WifiServiceSoftapDeinit},
47+ {FWK_INIT_LVL_BIZ, "wifi_connect", WifiServiceConnectInit, WifiServiceConnectDeinit},
48+};
49+#elif IOTC_ONECONNECT_VERSION_CODE >= 2
39 50 
40#define WIFI_NET_LAN_SEARCH_MAX_PEERS 251#define WIFI_NET_LAN_SEARCH_MAX_PEERS 2
41#define WIFI_NET_LAN_SEARCH_PEER_TIMEOUT_SEC 6052#define WIFI_NET_LAN_SEARCH_PEER_TIMEOUT_SEC 60
@@ -88,7 +99,9 @@ static const FwkInitUnit CORE_WIFI[] = {
88int32_t CoreWifiRegisterInitItem(void)99int32_t CoreWifiRegisterInitItem(void)
89{100{
90 int32_t ret;101 int32_t ret;
91-#if IOTC_ONECONNECT_VERSION_CODE >= 2102+#if IOTC_CONF_WIFI_ONLY_SUPPORT
103+ ret = FwkRegInitUnitArray(CORE_WIFI_ONLY);
104+#elif IOTC_ONECONNECT_VERSION_CODE >= 2
92 ret = FwkRegInitUnitArray(CORE_WIFI_V3);105 ret = FwkRegInitUnitArray(CORE_WIFI_V3);
93#else106#else
94 ret = FwkRegInitUnitArray(CORE_WIFI);107 ret = FwkRegInitUnitArray(CORE_WIFI);
@@ -102,7 +115,9 @@ int32_t CoreWifiRegisterInitItem(void)
102 115 
103void CoreWifiUnregisterInitItem(void)116void CoreWifiUnregisterInitItem(void)
104{117{
105-#if IOTC_ONECONNECT_VERSION_CODE >= 2118+#if IOTC_CONF_WIFI_ONLY_SUPPORT
119+ FwkUnregInitUnit(CORE_WIFI_ONLY);
120+#elif IOTC_ONECONNECT_VERSION_CODE >= 2
106 FwkUnregInitUnit(CORE_WIFI_V3);121 FwkUnregInitUnit(CORE_WIFI_V3);
107#else122#else
108 FwkUnregInitUnit(CORE_WIFI);123 FwkUnregInitUnit(CORE_WIFI);
Minterfaces/kits/common/iotc_def.h+4-1
@@ -138,10 +138,13 @@ typedef enum {
138#define IOTC_PID_LEN 5138#define IOTC_PID_LEN 5
139#define IOTC_NAME_MAX_LEN 10139#define IOTC_NAME_MAX_LEN 10
140 140 
141+/* Part 1 table 14: X is the SoftAP protocol version. */
142+#define IOTC_SOFTAP_PROTOCOL_VERSION "2"
143+ 
141#define IOTC_API_PUBLIC __attribute__ ((visibility ("default")))144#define IOTC_API_PUBLIC __attribute__ ((visibility ("default")))
142 145 
143#ifdef __cplusplus146#ifdef __cplusplus
144}147}
145#endif148#endif
146 149 
147-#endif /* IOTC_DEFINE_H */150+#endif /* IOTC_DEFINE_H */
Minterfaces/kits/common/iotc_event_params.h+2-1
@@ -34,7 +34,8 @@ extern "C" {
34#define IOTC_DEV_PSK_MAX_LEN 6434#define IOTC_DEV_PSK_MAX_LEN 64
35#define IOTC_SSID_MAX_LEN 3235#define IOTC_SSID_MAX_LEN 32
36#define IOTC_WIFI_PWD_MAX_LEN 6436#define IOTC_WIFI_PWD_MAX_LEN 64
37-#define IOTC_DATE_STR_MAX_LEN 1037+/* Part 3 5.1.1.2.10: yyyy-MM-dd HH:mm:ss. */
38+#define IOTC_DATE_STR_MAX_LEN 19
38#define IOTC_MAC_STR_MAX_LEN 1839#define IOTC_MAC_STR_MAX_LEN 18
39 40 
40/* ===== 事件参数数据结构 ===== */41/* ===== 事件参数数据结构 ===== */
Miotc.gni+28-2
@@ -23,6 +23,9 @@ declare_args() {
23 iot_connect_ble_support = true23 iot_connect_ble_support = true
24 iot_connect_sle_support = false24 iot_connect_sle_support = false
25 iot_connect_wifi_support = true25 iot_connect_wifi_support = true
26+ # Product profile switch. Keep false by default so existing products retain
27+ # their legacy transport combination unless they opt in explicitly.
28+ iot_connect_wifi_only_support = false
26 iot_connect_eth_support = false29 iot_connect_eth_support = false
27 iot_connect_bridge_support = false30 iot_connect_bridge_support = false
28 iot_connect_ailife_support = false31 iot_connect_ailife_support = false
@@ -34,6 +37,10 @@ declare_args() {
34 iotc_one_connect_version_name = "2.0"37 iotc_one_connect_version_name = "2.0"
35 iotc_one_connect_version_code = 238 iotc_one_connect_version_code = 2
36 iot_connect_speke_v2 = false39 iot_connect_speke_v2 = false
40+ # DANGEROUS diagnostic switch: prints SPEKE passcode and derived key
41+ # material in plaintext. It must only be enabled for an isolated debug
42+ # image and must remain false for normal builds and deliverables.
43+ iot_connect_speke_secret_trace = false
37 #SPEKE PBKDF2 迭代次数,取值范围 1000-100000,可根据设备性能调整44 #SPEKE PBKDF2 迭代次数,取值范围 1000-100000,可根据设备性能调整
38 iot_connect_speke_pbkdf2_iterations = 1000045 iot_connect_speke_pbkdf2_iterations = 10000
39 #TerminalPsk数量,根据设备性能调整46 #TerminalPsk数量,根据设备性能调整
@@ -44,6 +51,8 @@ declare_args() {
44 sec_acl_max_num = 451 sec_acl_max_num = 4
45 #是否支持OH靠近发现广播数据更新52 #是否支持OH靠近发现广播数据更新
46 iot_connect_ble_adv_update_support = false53 iot_connect_ble_adv_update_support = false
54+ # 资源受限产品可显式开启 lockless ExMutex;默认保持互斥锁行为。
55+ iot_connect_mutex_lockless = false
47 #是否支持iotc数据保存56 #是否支持iotc数据保存
48 iot_connect_kv_support = true57 iot_connect_kv_support = true
49 #控制发OH靠近发现广播的发出 58 #控制发OH靠近发现广播的发出
@@ -73,8 +82,25 @@ if (iotc_one_connect_version_code >= 2) {
73 iot_connect_speke_v2 = true82 iot_connect_speke_v2 = true
74}83}
75 84 
76-# Network is required by Ethernet and by the 3.0 Wi-Fi Combo route. 2.0 Wi-Fi85+if (iot_connect_wifi_only_support) {
77-# continues to use the legacy Wi-Fi LAN Search implementation.86+ # The mini product feature parser interprets values beginning with a digit
87+ # as integers, so derive the protocol version from the product profile.
88+ iotc_one_connect_version_name = "3.0"
89+ iotc_one_connect_version_code = 3
90+ assert(iot_connect_wifi_support,
91+ "WiFi Only profile requires iot_connect_wifi_support=true")
92+ assert(!iot_connect_ble_support && !iot_connect_sle_support &&
93+ !iot_connect_eth_support,
94+ "WiFi Only profile must not enable BLE, SLE, or Ethernet")
95+ assert(!iot_connect_ailife_support && !iot_connect_databus_support &&
96+ !iot_connect_oh_nearlink_support,
97+ "WiFi Only profile must not enable legacy/nearlink side channels")
98+ assert(iotc_one_connect_version_name == "3.0" &&
99+ iotc_one_connect_version_code == 3,
100+ "WiFi Only profile must use Unified Connect protocol 3.0")
101+}
102+ 
103+# Network is required by Ethernet and by the 3.0 Wi-Fi transport baseline.
78iot_connect_network_support = iot_connect_eth_support ||104iot_connect_network_support = iot_connect_eth_support ||
79 (iot_connect_wifi_support && iotc_one_connect_version_code >= 2)105 (iot_connect_wifi_support && iotc_one_connect_version_code >= 2)
80 106 
Msdk/oh_connect/api/iotc_oh_connect.c+36-1
@@ -26,7 +26,10 @@
26#include "dfx_watch_dog.h"26#include "dfx_watch_dog.h"
27#include "iotc_oh_option.h"27#include "iotc_oh_option.h"
28#include "product_adapter.h"28#include "product_adapter.h"
29+#include "config_info.h"
29#include "config_revoke_flag.h"30#include "config_revoke_flag.h"
31+#include "iotc_kv.h"
32+#include "iotc_store_key.h"
30#ifdef ENABLE_BRIDGE33#ifdef ENABLE_BRIDGE
31#include "bridge_init.h"34#include "bridge_init.h"
32#endif35#endif
@@ -34,7 +37,10 @@
34#define IOTC_OH_RESTORE_TIMEOUT UTILS_SEC_TO_MS(10)37#define IOTC_OH_RESTORE_TIMEOUT UTILS_SEC_TO_MS(10)
35 38 
36static const FwkInitUnit OH_COMM[] = {39static const FwkInitUnit OH_COMM[] = {
37- {FWK_INIT_LVL_BIZ, "config", IotcOhStoreDataInit, IotcOhStoreDataDeinit},40+ /* SecurityStore is a dependency of device, Wi-Fi provisioning and hub
41+ * business units. It must be ready before any BIZ-level unit can
42+ * register or recover persistent records. */
43+ {FWK_INIT_LVL_COMP, "config", IotcOhStoreDataInit, IotcOhStoreDataDeinit},
38};44};
39static uint32_t g_taskSize = IOTC_CONF_OH_DEFAULT_TASK_SIZE;45static uint32_t g_taskSize = IOTC_CONF_OH_DEFAULT_TASK_SIZE;
40static bool g_isCommOptionReg = false;46static bool g_isCommOptionReg = false;
@@ -180,9 +186,38 @@ static int32_t RestoreExecutorCallback(void *inData, void **outData)
180 return IOTC_OK;186 return IOTC_OK;
181}187}
182 188 
189+/*
190+ * A failed DeviceService recovery unregisters dev_data before the user can
191+ * request a factory reset. Recreate only that store registration after
192+ * deleting its stale record; the normal restore event then clears every
193+ * remaining provisioning record and reboots the device.
194+ */
195+static int32_t RecoverDeviceConfigForRestore(void)
196+{
197+ uint32_t len = 0;
198+ if (IotcKvGetLen(IOTC_STORE_KEY_CHAR_DEVICE_CONFIG, &len) == IOTC_OK) {
199+ int32_t ret = IotcKvDelValue(IOTC_STORE_KEY_CHAR_DEVICE_CONFIG);
200+ if (ret != IOTC_OK) {
201+ IOTC_LOGW("erase stale device config error %d", ret);
202+ return ret;
203+ }
204+ }
205+ 
206+ int32_t ret = ConfigInfoInit();
207+ if (ret != IOTC_OK) {
208+ IOTC_LOGW("recover device config error %d", ret);
209+ return ret;
210+ }
211+ return SetRevokeFlag();
212+}
213+ 
183int32_t IotcOhRestore(void)214int32_t IotcOhRestore(void)
184{215{
185 int32_t ret = SetRevokeFlag();216 int32_t ret = SetRevokeFlag();
217+ if (ret == IOTC_CORE_COMM_UTILS_ERR_STORE_INVALID_KEY) {
218+ IOTC_LOGW("device config unavailable, recover for restore");
219+ ret = RecoverDeviceConfigForRestore();
220+ }
186 if (ret != IOTC_OK) {221 if (ret != IOTC_OK) {
187 IOTC_LOGF("set revoke error %d", ret);222 IOTC_LOGF("set revoke error %d", ret);
188 return ret;223 return ret;
Msdk/oh_connect/api/iotc_oh_wifi.c+13-2
@@ -42,10 +42,17 @@ static bool IsNeedStartWifiSoftapSvc(void)
42 if (GetNetCfgMode() != IOTC_NET_CONFIG_MODE_SOFTAP) {42 if (GetNetCfgMode() != IOTC_NET_CONFIG_MODE_SOFTAP) {
43 return false;43 return false;
44 }44 }
45+#if IOTC_CONF_WIFI_ONLY_SUPPORT
46+ /* A partial HubSetup must not leave an offline device permanently hidden. */
47+ bool isBinded = DevSvcProxyGetBindStatus() == DEV_BIND_STATUS_BIND;
48+ bool hasWifiInfo = ConnSvcProxyIsWifiInfoExits();
49+ return !ConnSvcProxyIsNetConnected() && !(isBinded && hasWifiInfo);
50+#else
45 if (DevSvcProxyGetBindStatus() == DEV_BIND_STATUS_BIND) {51 if (DevSvcProxyGetBindStatus() == DEV_BIND_STATUS_BIND) {
46 return false;52 return false;
47 }53 }
48 return !ConnSvcProxyIsNetConnected() && !ConnSvcProxyIsWifiInfoExits();54 return !ConnSvcProxyIsNetConnected() && !ConnSvcProxyIsWifiInfoExits();
55+#endif
49}56}
50 57 
51static int32_t StartWifiSoftapSvc(void)58static int32_t StartWifiSoftapSvc(void)
@@ -75,7 +82,7 @@ static int32_t StartWifiSoftapSvc(void)
75 return IOTC_OK;82 return IOTC_OK;
76}83}
77 84 
78-#if IOTC_ONECONNECT_VERSION_CODE >= 285+#if IOTC_ONECONNECT_VERSION_CODE >= 2 && !IOTC_CONF_WIFI_ONLY_SUPPORT
79/* 3.0 uses Network LAN Search and does not start the legacy cloud service. */86/* 3.0 uses Network LAN Search and does not start the legacy cloud service. */
80static void StartLocalNetworkSvc(void)87static void StartLocalNetworkSvc(void)
81{88{
@@ -110,7 +117,9 @@ static void EventBusStartWifiSvcCallback(uint32_t event, void *param, uint32_t l
110 return;117 return;
111 }118 }
112 119 
113-#if IOTC_ONECONNECT_VERSION_CODE >= 2120+#if IOTC_CONF_WIFI_ONLY_SUPPORT
121+ /* This PR intentionally stops at the verified provisioning boundary. */
122+#elif IOTC_ONECONNECT_VERSION_CODE >= 2
114 if (GetNetCfgMode() != IOTC_NET_CONFIG_MODE_SOFTAP) {123 if (GetNetCfgMode() != IOTC_NET_CONFIG_MODE_SOFTAP) {
115 StartLocalNetworkSvc();124 StartLocalNetworkSvc();
116 }125 }
@@ -158,12 +167,14 @@ static void IotcOhWifiMainDeinit(void)
158 (void)EventBusUnsubscribe(EventBusStartWifiSvcCallback);167 (void)EventBusUnsubscribe(EventBusStartWifiSvcCallback);
159 (void)ServiceProxyStopService(IOTC_SERVICE_ID_SOFTAP, NULL);168 (void)ServiceProxyStopService(IOTC_SERVICE_ID_SOFTAP, NULL);
160 (void)ServiceProxyStopService(IOTC_SERVICE_ID_WIFI_CONNECT, NULL);169 (void)ServiceProxyStopService(IOTC_SERVICE_ID_WIFI_CONNECT, NULL);
170+#if !IOTC_CONF_WIFI_ONLY_SUPPORT
161 (void)ServiceProxyStopService(IOTC_SERVICE_ID_LOCAL_CONTROL, NULL);171 (void)ServiceProxyStopService(IOTC_SERVICE_ID_LOCAL_CONTROL, NULL);
162#if IOTC_ONECONNECT_VERSION_CODE >= 2172#if IOTC_ONECONNECT_VERSION_CODE >= 2
163 (void)ServiceProxyStopService(IOTC_SERVICE_ID_NET_LAN_SEARCH, NULL);173 (void)ServiceProxyStopService(IOTC_SERVICE_ID_NET_LAN_SEARCH, NULL);
164#else174#else
165 (void)ServiceProxyStopService(IOTC_SERVICE_ID_LAN_SEARCH, NULL);175 (void)ServiceProxyStopService(IOTC_SERVICE_ID_LAN_SEARCH, NULL);
166#endif176#endif
177+#endif
167}178}
168 179 
169static const FwkInitUnit OH_WIFI[] = {180static const FwkInitUnit OH_WIFI[] = {
Msdk/oh_connect/biz/iotc_oh_softap_name.c+25-13
@@ -16,22 +16,24 @@
16#include "iotc_errcode.h"16#include "iotc_errcode.h"
17#include "iotc_log.h"17#include "iotc_log.h"
18#include "utils_assert.h"18#include "utils_assert.h"
19-#include "security_random.h"
20#include "iotc_svc_dev.h"19#include "iotc_svc_dev.h"
21#include "dev_info.h"20#include "dev_info.h"
22#include "iotc_conf.h"21#include "iotc_conf.h"
23#include "product_adapter.h"22#include "product_adapter.h"
24 23 
25-#define SOFTAP_NAME_MOULD "Oh-AAAAAAABBBBBBB-XYYYYYRRRR"
26#define SOFTAP_UNREG_NAME_HEAD "Oh"24#define SOFTAP_UNREG_NAME_HEAD "Oh"
27#define SOFTAP_REGED_NAME_HEAD "OH"25#define SOFTAP_REGED_NAME_HEAD "OH"
28#define SOFTAP_UNREG_NAME_HEAD_OC "Oc"26#define SOFTAP_UNREG_NAME_HEAD_OC "Oc"
29#define SOFTAP_REGED_NAME_HEAD_OC "OC"27#define SOFTAP_REGED_NAME_HEAD_OC "OC"
30-#define SOFTAP_NAME_VER IOTC_ONECONNECT_VERSION_CODE28+#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)
29+#define SOFTAP_NAME_VER IOTC_SOFTAP_PROTOCOL_VERSION
30+#else
31+#define SOFTAP_NAME_VER "1"
32+#endif
31 33 
32#define CUSTOM_NAME_BUF_LEN 6434#define CUSTOM_NAME_BUF_LEN 64
33#define SOFTAP_NAME_SN_LEN 435#define SOFTAP_NAME_SN_LEN 4
34-#define SOFTAP_NETCFG_CAPCITY 0b0110100036+#define SOFTAP_NETCFG_CAPCITY 0b01111100
35#define SOFTAP_NETCFG_CAPCITY_SIZE 137#define SOFTAP_NETCFG_CAPCITY_SIZE 1
36#define SOFTAP_TERMINAL_CONF 0x0038#define SOFTAP_TERMINAL_CONF 0x00
37#define SOFTAP_TERMINAL_CONF_SIZE 139#define SOFTAP_TERMINAL_CONF_SIZE 1
@@ -60,31 +62,41 @@ int32_t IotcOhGetSoftApName(uint8_t *ssid, uint32_t *len)
60 }62 }
61 DevBindStatus bindStatus = DevSvcProxyGetBindStatus();63 DevBindStatus bindStatus = DevSvcProxyGetBindStatus();
62#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)64#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)
63- // 2.0版本广播协议
64 const char *head = bindStatus == DEV_BIND_STATUS_BIND ? SOFTAP_REGED_NAME_HEAD_OC : SOFTAP_UNREG_NAME_HEAD_OC;65 const char *head = bindStatus == DEV_BIND_STATUS_BIND ? SOFTAP_REGED_NAME_HEAD_OC : SOFTAP_UNREG_NAME_HEAD_OC;
65#else66#else
66- // 1.0版本广播协议
67 const char *head = bindStatus == DEV_BIND_STATUS_BIND ? SOFTAP_REGED_NAME_HEAD : SOFTAP_UNREG_NAME_HEAD;67 const char *head = bindStatus == DEV_BIND_STATUS_BIND ? SOFTAP_REGED_NAME_HEAD : SOFTAP_UNREG_NAME_HEAD;
68#endif68#endif
69- int ret = sprintf_s((char *)ssid, ssidSize, "%s-%.14s-%d%s%s",69+ int ret = sprintf_s((char *)ssid, ssidSize, "%s-%.14s-%s%s%s",
70 head, customName, SOFTAP_NAME_VER, devInfo->prodId,70 head, customName, SOFTAP_NAME_VER, devInfo->prodId,
71 &devInfo->sn[snLen - SOFTAP_NAME_SN_LEN]);71 &devInfo->sn[snLen - SOFTAP_NAME_SN_LEN]);
72 if (ret <= 0) {72 if (ret <= 0) {
73 IOTC_LOGE("sprintf_s ret=%d", ret);73 IOTC_LOGE("sprintf_s ret=%d", ret);
74 return IOTC_ERROR;74 return IOTC_ERROR;
75 }75 }
76- if (ret + SOFTAP_NETCFG_CAPCITY_SIZE + SOFTAP_TERMINAL_CONF_SIZE > ssidSize) {76+#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)
77+ const uint32_t suffixSize = SOFTAP_NETCFG_CAPCITY_SIZE + SOFTAP_TERMINAL_CONF_SIZE;
78+#else
79+ const uint32_t suffixSize = SOFTAP_NETCFG_CAPCITY_SIZE;
80+#endif
81+ if ((uint32_t)ret + suffixSize > ssidSize) {
77 IOTC_LOGE("buffer not enough, %d/%u", ret, ssidSize);82 IOTC_LOGE("buffer not enough, %d/%u", ret, ssidSize);
78 return IOTC_ERR_NOT_ENOUGH;83 return IOTC_ERR_NOT_ENOUGH;
79 }84 }
80- ssid[ret] = SOFTAP_NETCFG_CAPCITY;85+ uint8_t capacity = SOFTAP_NETCFG_CAPCITY;
86+ ssid[ret] = capacity;
81 ret += SOFTAP_NETCFG_CAPCITY_SIZE;87 ret += SOFTAP_NETCFG_CAPCITY_SIZE;
82- uint8_t terminalConf = SOFTAP_TERMINAL_CONF;88+#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)
83- if (ProductIsConfigOemProvisionCallback()) {89+ uint8_t terminalConf = ModelGetDataConfig();
84- terminalConf |= 0x01;90+ /* Temporary interop diagnostic: make a trailing binary NUL visible to string-only configurators. */
91+ if (terminalConf == 0x00) {
92+ terminalConf = (uint8_t)'0';
85 }93 }
86 ssid[ret] = terminalConf;94 ssid[ret] = terminalConf;
87 ret += SOFTAP_TERMINAL_CONF_SIZE;95 ret += SOFTAP_TERMINAL_CONF_SIZE;
88- *len = ret;96+ IOTC_LOGI("softap ssid=%.*s len=%d z=0x%02x p=0x%02x", ret, ssid, ret, capacity, terminalConf);
97+#else
98+ IOTC_LOGI("softap ssid=%.*s len=%d z=0x%02x", ret, ssid, ret, capacity);
99+#endif
100+ *len = (uint32_t)ret;
89 return IOTC_OK;101 return IOTC_OK;
90}102}
Mtest/unit/CMakeLists.txt+15-1
@@ -128,12 +128,14 @@ set(IOTC_TLS_TEST_SOURCES
128 "${IOTC_ROOT}/adapter/os/posix/iotc_os.c"128 "${IOTC_ROOT}/adapter/os/posix/iotc_os.c"
129 "${IOTC_ROOT}/adapter/log/iotc_log_inner.c"129 "${IOTC_ROOT}/adapter/log/iotc_log_inner.c"
130 "${IOTC_ROOT}/adapter/log/printf/iotc_log.c"130 "${IOTC_ROOT}/adapter/log/printf/iotc_log.c"
131+ "${IOTC_ROOT}/core/infrastructure/utils/utils_common.c"
131)132)
132add_executable(iotc_tls_unit_test ${IOTC_TLS_TEST_SOURCES})133add_executable(iotc_tls_unit_test ${IOTC_TLS_TEST_SOURCES})
133target_include_directories(iotc_tls_unit_test PRIVATE134target_include_directories(iotc_tls_unit_test PRIVATE
134 "${IOTC_ROOT}/adapter/include"135 "${IOTC_ROOT}/adapter/include"
135 "${IOTC_ROOT}/interfaces/kits/common"136 "${IOTC_ROOT}/interfaces/kits/common"
136 "${IOTC_ROOT}/interfaces/kits/oh_connect"137 "${IOTC_ROOT}/interfaces/kits/oh_connect"
138+ "${IOTC_ROOT}/core/infrastructure/utils/include"
137 "${CMAKE_CURRENT_LIST_DIR}/include"139 "${CMAKE_CURRENT_LIST_DIR}/include"
138)140)
139if (NOT DEFINED IOTC_TLS_TEST_LOG_LEVEL)141if (NOT DEFINED IOTC_TLS_TEST_LOG_LEVEL)
@@ -233,6 +235,14 @@ target_compile_definitions(iotc_speke_api_unit_test PRIVATE
233endif()235endif()
234 236 
235if (NOT IOTC_CJSON_ROOT STREQUAL "")237if (NOT IOTC_CJSON_ROOT STREQUAL "")
238+ # 与根 CMakeLists.txt 及 iotc.gni 默认一致;GN 构建注入该宏,这里补齐。
239+ if (NOT DEFINED SPEKE_PBKDF2_ITERATIONS)
240+ set(SPEKE_PBKDF2_ITERATIONS 10000)
241+ endif()
242+ set(IOTC_SPEKE_V2_TEST_SECRET_TRACE 0 CACHE STRING
243+ "Enable plaintext SPEKE v2 key trace in this host-test binary")
244+ set(IOTC_SPEKE_V2_TEST_LOG_LEVEL 0 CACHE STRING
245+ "SPEKE v2 host-test compile-time log level")
236 set(IOTC_SPEKE_V2_API_TEST_SOURCES246 set(IOTC_SPEKE_V2_API_TEST_SOURCES
237 iotc_speke_v2_api_unit_test.c247 iotc_speke_v2_api_unit_test.c
238 "${IOTC_ROOT}/core/infrastructure/security/speke_v2/security_speke_session.c"248 "${IOTC_ROOT}/core/infrastructure/security/speke_v2/security_speke_session.c"
@@ -244,6 +254,8 @@ if (NOT IOTC_CJSON_ROOT STREQUAL "")
244 "${IOTC_ROOT}/core/infrastructure/utils/utils_json.c"254 "${IOTC_ROOT}/core/infrastructure/utils/utils_json.c"
245 "${IOTC_ROOT}/adapter/json/cjson/iotc_json.c"255 "${IOTC_ROOT}/adapter/json/cjson/iotc_json.c"
246 "${IOTC_CJSON_ROOT}/cJSON.c"256 "${IOTC_CJSON_ROOT}/cJSON.c"
257+ "${IOTC_ROOT}/adapter/log/iotc_log_inner.c"
258+ "${IOTC_ROOT}/adapter/log/printf/iotc_log.c"
247 "${IOTC_ROOT}/adapter/os/posix/iotc_os.c"259 "${IOTC_ROOT}/adapter/os/posix/iotc_os.c"
248 ${IOTC_CRYPTO_SOURCES}260 ${IOTC_CRYPTO_SOURCES}
249 )261 )
@@ -262,8 +274,10 @@ if (NOT IOTC_CJSON_ROOT STREQUAL "")
262 )274 )
263 iotc_add_securec(iotc_speke_v2_api_unit_test)275 iotc_add_securec(iotc_speke_v2_api_unit_test)
264 target_compile_definitions(iotc_speke_v2_api_unit_test PRIVATE276 target_compile_definitions(iotc_speke_v2_api_unit_test PRIVATE
265- IOTC_CONF_LOG_BUILD_LEVEL=0277+ IOTC_CONF_LOG_BUILD_LEVEL=${IOTC_SPEKE_V2_TEST_LOG_LEVEL}
278+ IOTC_CONF_SPEKE_SECRET_TRACE=${IOTC_SPEKE_V2_TEST_SECRET_TRACE}
266 IOTC_CONF_SPEKE_V2279 IOTC_CONF_SPEKE_V2
280+ SPEKE_PBKDF2_ITERATIONS=${SPEKE_PBKDF2_ITERATIONS}
267 )281 )
268 if (IOTC_SSL_BACKEND STREQUAL "mbedtls")282 if (IOTC_SSL_BACKEND STREQUAL "mbedtls")
269 target_include_directories(iotc_speke_v2_api_unit_test BEFORE PRIVATE "${IOTC_MBEDTLS_ROOT}/include")283 target_include_directories(iotc_speke_v2_api_unit_test BEFORE PRIVATE "${IOTC_MBEDTLS_ROOT}/include")
Atest/unit/connect_boot_host/run_connect_boot_tests.sh+46-0
@@ -0,0 +1,46 @@
1+#!/bin/bash
2+# Build and run the WiFi cold-boot deferred-connect host test outside the tree.
3+set -uo pipefail
4+ 
5+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
6+IOTC_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
7+OUT_DIR=$(mktemp -d /tmp/iotc_connect_boot_host_test.XXXXXX)
8+trap 'rm -rf "$OUT_DIR"' EXIT
9+ 
10+CFLAGS=(
11+ -std=c99 -Wall -Wextra -Werror -g -O0
12+ -ffunction-sections -fdata-sections
13+ -DIOTC_CONF_WIFI_ONLY_SUPPORT=1
14+ -I"$SCRIPT_DIR/stubs"
15+ -I"$IOTC_ROOT/test/unit/device_config_host"
16+ -I"$IOTC_ROOT/test/unit/device_config_host/stubs"
17+ -I"$IOTC_ROOT/adapter/include"
18+ -I"$IOTC_ROOT/interfaces/kits/common"
19+ -I"$IOTC_ROOT/core/infrastructure/define"
20+ -I"$IOTC_ROOT/core/infrastructure/service/include"
21+ -I"$IOTC_ROOT/core/infrastructure/utils/include"
22+ -I"$IOTC_ROOT/core/infrastructure/schedule/include"
23+ -I"$IOTC_ROOT/core/infrastructure/frameworks/event_loop/include"
24+ -I"$IOTC_ROOT/core/infrastructure/frameworks/event_bus/include"
25+ -I"$IOTC_ROOT/core/infrastructure/frameworks/main/include"
26+ -I"$IOTC_ROOT/core/infrastructure/frameworks/service/include"
27+ -I"$IOTC_ROOT/core/device/config/include"
28+ -I"$IOTC_ROOT/core/wifi/application/connect"
29+ -I"$IOTC_ROOT/core/wifi/infrastructure/utils/include"
30+)
31+ 
32+SOURCES=(
33+ "$SCRIPT_DIR/test_connect_boot.c"
34+ "$IOTC_ROOT/core/wifi/application/connect/svc_conn_action.c"
35+)
36+ 
37+gcc "${CFLAGS[@]}" "${SOURCES[@]}" -Wl,--gc-sections -o "$OUT_DIR/test_connect_boot" || exit 1
38+"$OUT_DIR/test_connect_boot" || exit 1
39+ 
40+NET_INFO_SOURCES=(
41+ "$SCRIPT_DIR/test_net_info_transaction.c"
42+ "$IOTC_ROOT/core/wifi/application/connect/svc_conn_net_info.c"
43+)
44+ 
45+gcc "${CFLAGS[@]}" "${NET_INFO_SOURCES[@]}" -Wl,--gc-sections -o "$OUT_DIR/test_net_info_transaction" || exit 1
46+"$OUT_DIR/test_net_info_transaction"
Atest/unit/connect_boot_host/stubs/iotc_log.h+11-0
@@ -0,0 +1,11 @@
1+#ifndef IOTC_CONNECT_BOOT_TEST_LOG_H
2+#define IOTC_CONNECT_BOOT_TEST_LOG_H
3+ 
4+#define IOTC_LOGF(...) ((void)0)
5+#define IOTC_LOGE(...) ((void)0)
6+#define IOTC_LOGW(...) ((void)0)
7+#define IOTC_LOGN(...) ((void)0)
8+#define IOTC_LOGI(...) ((void)0)
9+#define IOTC_LOGD(...) ((void)0)
10+ 
11+#endif /* IOTC_CONNECT_BOOT_TEST_LOG_H */
Atest/unit/connect_boot_host/test_connect_boot.c+237-0
@@ -0,0 +1,237 @@
1+#include <stdbool.h>
2+#include <stdint.h>
3+#include <stdio.h>
4+#include <string.h>
5+ 
6+#include "event_bus_pub.h"
7+#include "iotc_errcode.h"
8+#include "sched_timer.h"
9+#include "svc_conn_action.h"
10+#include "svc_conn_ctx.h"
11+#include "test_framework.h"
12+ 
13+int g_testChecks;
14+int g_testFailures;
15+ 
16+typedef struct {
17+ EventSourceTimerType type;
18+ EventSourceTimerCallback callback;
19+ uint32_t intervalMs;
20+ void *userData;
21+ int32_t id;
22+} TimerRecord;
23+ 
24+static ConnectServiceContext g_ctx;
25+static TimerRecord g_timers[8];
26+static int32_t g_timerCount;
27+static int32_t g_timerAddFailure;
28+static int32_t g_timerRemoveCount;
29+static int32_t g_connectResult;
30+static int32_t g_connectCalls;
31+static bool g_needVerify;
32+static bool g_checked;
33+static int32_t g_disconnectCalls;
34+static int32_t g_deleteCalls;
35+static int32_t g_clearCalls;
36+static int32_t g_restoreCalls;
37+static int32_t g_eventCalls;
38+ 
39+static void ResetState(void)
40+{
41+ (void)memset(&g_ctx, 0, sizeof(g_ctx));
42+ g_ctx.verifyTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
43+ g_ctx.netCheckTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
44+ g_ctx.reconnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
45+ g_ctx.initialConnectTimerFd = EVENT_SOURCE_INVALID_TIMER_FD;
46+ (void)memset(g_timers, 0, sizeof(g_timers));
47+ g_timerCount = 0;
48+ g_timerAddFailure = IOTC_OK;
49+ g_timerRemoveCount = 0;
50+ g_connectResult = IOTC_OK;
51+ g_connectCalls = 0;
52+ g_needVerify = false;
53+ g_checked = false;
54+ g_disconnectCalls = 0;
55+ g_deleteCalls = 0;
56+ g_clearCalls = 0;
57+ g_restoreCalls = 0;
58+ g_eventCalls = 0;
59+}
60+ 
61+ConnectServiceContext *GetConnSvcCtx(void)
62+{
63+ return &g_ctx;
64+}
65+ 
66+int32_t SchedTimerAdd(EventSourceTimerType type, EventSourceTimerCallback callback,
67+ uint32_t ms, void *userData)
68+{
69+ if (g_timerAddFailure != IOTC_OK) {
70+ return g_timerAddFailure;
71+ }
72+ int32_t index = g_timerCount++;
73+ g_timers[index].type = type;
74+ g_timers[index].callback = callback;
75+ g_timers[index].intervalMs = ms;
76+ g_timers[index].userData = userData;
77+ g_timers[index].id = 100 + index;
78+ return g_timers[index].id;
79+}
80+ 
81+void SchedTimerRemove(int32_t id)
82+{
83+ (void)id;
84+ ++g_timerRemoveCount;
85+}
86+ 
87+int32_t IotcConnectWifi(void)
88+{
89+ ++g_connectCalls;
90+ return g_connectResult;
91+}
92+ 
93+bool IsNetworkConnected(void)
94+{
95+ return false;
96+}
97+ 
98+int32_t IotcDisconnectWifi(void)
99+{
100+ ++g_disconnectCalls;
101+ return IOTC_OK;
102+}
103+ 
104+int32_t IotcDeleteWifiInfo(void)
105+{
106+ ++g_deleteCalls;
107+ return IOTC_OK;
108+}
109+ 
110+bool ConfigIsNetInfoNeedVerify(void)
111+{
112+ return g_needVerify;
113+}
114+ 
115+bool ConfigIsNetInfoChecked(void)
116+{
117+ return g_checked;
118+}
119+ 
120+int32_t ConfigClearNetInfoFlag(void)
121+{
122+ ++g_clearCalls;
123+ g_needVerify = false;
124+ g_checked = false;
125+ return IOTC_OK;
126+}
127+ 
128+int32_t IotcFwkRestore(void)
129+{
130+ ++g_restoreCalls;
131+ return IOTC_OK;
132+}
133+ 
134+void EventBusPublishSyncInner(uint32_t event, const char *name, void *param, uint32_t len)
135+{
136+ (void)event;
137+ (void)name;
138+ (void)param;
139+ (void)len;
140+ ++g_eventCalls;
141+}
142+ 
143+static void FireTimer(int32_t index)
144+{
145+ CHECK(index >= 0 && index < g_timerCount);
146+ CHECK(g_timers[index].callback != NULL);
147+ if (index >= 0 && index < g_timerCount && g_timers[index].callback != NULL) {
148+ g_timers[index].callback(g_timers[index].id, g_timers[index].userData);
149+ }
150+}
151+ 
152+static void TestCheckedNetworkTransientFailure(void)
153+{
154+ ResetState();
155+ g_checked = true;
156+ g_connectResult = -123;
157+ 
158+ CHECK_EQ_INT(IOTC_OK, ConnSvcScheduleInitialConnect());
159+ CHECK_EQ_INT(1, g_timerCount);
160+ CHECK_EQ_INT(EVENT_SOURCE_TIMER_TYPE_ONCE, g_timers[0].type);
161+ CHECK_EQ_INT(1000, g_timers[0].intervalMs);
162+ CHECK_EQ_INT(g_timers[0].id, g_ctx.initialConnectTimerFd);
163+ 
164+ CHECK_EQ_INT(IOTC_OK, ConnSvcScheduleInitialConnect());
165+ CHECK_EQ_INT(1, g_timerCount);
166+ 
167+ FireTimer(0);
168+ CHECK_EQ_INT(1, g_connectCalls);
169+ CHECK_EQ_INT(EVENT_SOURCE_INVALID_TIMER_FD, g_ctx.initialConnectTimerFd);
170+ CHECK_EQ_INT(1, g_timerRemoveCount);
171+ CHECK_EQ_INT(2, g_timerCount);
172+ CHECK_EQ_INT(EVENT_SOURCE_TIMER_TYPE_REPEAT, g_timers[1].type);
173+ CHECK_EQ_INT(20000, g_timers[1].intervalMs);
174+ CHECK_EQ_INT(g_timers[1].id, g_ctx.reconnectTimerFd);
175+ CHECK_EQ_INT(0, g_disconnectCalls);
176+ CHECK_EQ_INT(0, g_deleteCalls);
177+ 
178+ FireTimer(1);
179+ CHECK_EQ_INT(2, g_connectCalls);
180+}
181+ 
182+static void TestSuccessfulDeferredConnect(void)
183+{
184+ ResetState();
185+ g_checked = true;
186+ CHECK_EQ_INT(IOTC_OK, ConnSvcScheduleInitialConnect());
187+ FireTimer(0);
188+ CHECK_EQ_INT(1, g_connectCalls);
189+ CHECK_EQ_INT(1, g_timerCount);
190+ CHECK_EQ_INT(EVENT_SOURCE_INVALID_TIMER_FD, g_ctx.initialConnectTimerFd);
191+ CHECK_EQ_INT(EVENT_SOURCE_INVALID_TIMER_FD, g_ctx.reconnectTimerFd);
192+}
193+ 
194+static void TestPendingFailureUsesExistingRecovery(void)
195+{
196+ ResetState();
197+ g_needVerify = true;
198+ g_connectResult = -45;
199+ CHECK_EQ_INT(IOTC_OK, ConnSvcScheduleInitialConnect());
200+ FireTimer(0);
201+ CHECK_EQ_INT(1, g_connectCalls);
202+ CHECK_EQ_INT(1, g_disconnectCalls);
203+ CHECK_EQ_INT(1, g_deleteCalls);
204+ CHECK_EQ_INT(1, g_clearCalls);
205+ CHECK_EQ_INT(1, g_restoreCalls);
206+ CHECK_EQ_INT(1, g_timerCount);
207+ CHECK_EQ_INT(EVENT_SOURCE_INVALID_TIMER_FD, g_ctx.reconnectTimerFd);
208+}
209+ 
210+static void TestTimerFailureAndExistingReconnect(void)
211+{
212+ ResetState();
213+ g_timerAddFailure = -77;
214+ CHECK_EQ_INT(-77, ConnSvcScheduleInitialConnect());
215+ CHECK_EQ_INT(-77, g_ctx.initialConnectTimerFd);
216+ CHECK_EQ_INT(0, g_timerCount);
217+ 
218+ ResetState();
219+ g_checked = true;
220+ g_connectResult = -88;
221+ g_ctx.reconnectTimerFd = 55;
222+ CHECK_EQ_INT(IOTC_OK, ConnSvcScheduleInitialConnect());
223+ FireTimer(0);
224+ CHECK_EQ_INT(1, g_timerCount);
225+ CHECK_EQ_INT(55, g_ctx.reconnectTimerFd);
226+}
227+ 
228+int main(void)
229+{
230+ setbuf(stdout, NULL);
231+ TestCheckedNetworkTransientFailure();
232+ TestSuccessfulDeferredConnect();
233+ TestPendingFailureUsesExistingRecovery();
234+ TestTimerFailureAndExistingReconnect();
235+ printf("test_connect_boot: %d checks, %d failures\n", g_testChecks, g_testFailures);
236+ return (g_testFailures == 0) ? 0 : 1;
237+}
Atest/unit/connect_boot_host/test_net_info_transaction.c+141-0
@@ -0,0 +1,141 @@
1+/* HubSetup Wi-Fi transaction tests for the device-hosted SoftAP path. */
2+ 
3+#include <stdbool.h>
4+#include <stdint.h>
5+#include <stdio.h>
6+#include <string.h>
7+ 
8+#include "comm_def.h"
9+#include "event_bus_sub.h"
10+#include "iotc_errcode.h"
11+#include "iotc_wifi.h"
12+#include "sched_timer.h"
13+#include "svc_conn_net_info.h"
14+#include "test_framework.h"
15+#include "utils_json.h"
16+ 
17+int g_testChecks;
18+int g_testFailures;
19+ 
20+static bool g_wifiExists;
21+static bool g_needVerify;
22+static bool g_checked;
23+static char g_savedSsid[IOTC_WIFI_SSID_MAX_LEN + 1];
24+static char g_savedPassword[IOTC_WIFI_PWD_MAX_LEN + 1];
25+static int32_t g_connectCalls;
26+static int32_t g_timerId;
27+static EventSourceTimerCallback g_timerCallback;
28+static void *g_timerUserData;
29+static IotcWifiMode g_wifiMode;
30+static EventBusCallback g_softapStopCallback;
31+ 
32+static void Reset(void)
33+{
34+ g_wifiExists = true;
35+ g_needVerify = false;
36+ g_checked = true;
37+ (void)strcpy(g_savedSsid, "old-home");
38+ (void)strcpy(g_savedPassword, "old-password");
39+ g_connectCalls = 0;
40+ g_timerId = EVENT_SOURCE_INVALID_TIMER_FD;
41+ g_timerCallback = NULL;
42+ g_timerUserData = NULL;
43+ g_wifiMode = IOTC_WIFI_MODE_STATION;
44+ g_softapStopCallback = NULL;
45+}
46+ 
47+int32_t UtilsJsonGetString(const IotcJson *json, const char *key, char *buf, uint32_t len)
48+{
49+ (void)json;
50+ const char *value = strcmp(key, STR_NETINFO_SSID) == 0 ? "new-home" : "new-password";
51+ size_t valueLen = strlen(value);
52+ if (valueLen + 1U > len) {
53+ return IOTC_ERR_NOT_ENOUGH;
54+ }
55+ (void)memcpy(buf, value, valueLen + 1U);
56+ return IOTC_OK;
57+}
58+ 
59+int32_t IotcGetWifiInfo(uint8_t *ssid, uint32_t *ssidLen, uint8_t *pwd, uint32_t *pwdLen)
60+{
61+ if (!g_wifiExists) {
62+ return IOTC_ADAPTER_WIFI_ERR_GET_INFO;
63+ }
64+ size_t sl = strlen(g_savedSsid);
65+ size_t pl = strlen(g_savedPassword);
66+ if (*ssidLen < sl || *pwdLen < pl) {
67+ return IOTC_ERR_NOT_ENOUGH;
68+ }
69+ (void)memcpy(ssid, g_savedSsid, sl);
70+ (void)memcpy(pwd, g_savedPassword, pl);
71+ *ssidLen = (uint32_t)sl;
72+ *pwdLen = (uint32_t)pl;
73+ return IOTC_OK;
74+}
75+ 
76+int32_t IotcSetWifiInfo(const uint8_t *ssid, uint32_t ssidLen, const uint8_t *pwd, uint32_t pwdLen)
77+{
78+ (void)memset(g_savedSsid, 0, sizeof(g_savedSsid));
79+ (void)memset(g_savedPassword, 0, sizeof(g_savedPassword));
80+ (void)memcpy(g_savedSsid, ssid, ssidLen);
81+ (void)memcpy(g_savedPassword, pwd, pwdLen);
82+ g_wifiExists = true;
83+ return IOTC_OK;
84+}
85+ 
86+int32_t IotcDeleteWifiInfo(void) { g_wifiExists = false; return IOTC_OK; }
87+int32_t IotcDisconnectWifi(void) { return IOTC_OK; }
88+int32_t IotcGetWifiMode(void) { return g_wifiMode; }
89+int32_t ConnSvcActionConnectWifi(void) { ++g_connectCalls; return IOTC_OK; }
90+bool ConfigIsNetInfoNeedVerify(void) { return g_needVerify; }
91+bool ConfigIsNetInfoChecked(void) { return g_checked; }
92+int32_t ConfigSetNetInfoNeedVerify(void) { g_needVerify = true; g_checked = false; return IOTC_OK; }
93+int32_t ConfigSetNetInfoChecked(void) { g_needVerify = false; g_checked = true; return IOTC_OK; }
94+int32_t ConfigClearNetInfoFlag(void) { g_needVerify = false; g_checked = false; return IOTC_OK; }
95+ 
96+int32_t SchedTimerAdd(EventSourceTimerType type, EventSourceTimerCallback callback, uint32_t ms, void *userData)
97+{
98+ CHECK_EQ_INT(EVENT_SOURCE_TIMER_TYPE_ONCE, type);
99+ CHECK_EQ_INT(600, ms);
100+ g_timerId = 101;
101+ g_timerCallback = callback;
102+ g_timerUserData = userData;
103+ return g_timerId;
104+}
105+ 
106+void SchedTimerRemove(int32_t id) { CHECK_EQ_INT(g_timerId, id); }
107+int32_t EventBusSubscribe(EventBusCallback cb, uint32_t event) { (void)event; g_softapStopCallback = cb; return IOTC_OK; }
108+int32_t EventBusUnsubscribe(EventBusCallback cb) { CHECK(cb == g_softapStopCallback); return IOTC_OK; }
109+ 
110+static void TestCommit(void)
111+{
112+ Reset();
113+ const IotcJson *json = (const IotcJson *)(uintptr_t)1;
114+ CHECK_EQ_INT(IOTC_OK, SvcConnPrepareNetInfo(json));
115+ CHECK(strcmp(g_savedSsid, "new-home") == 0);
116+ CHECK(g_needVerify);
117+ CHECK_EQ_INT(IOTC_OK, SvcConnCommitNetInfo());
118+ CHECK(g_timerCallback != NULL);
119+ g_timerCallback(g_timerId, g_timerUserData);
120+ CHECK_EQ_INT(1, g_connectCalls);
121+}
122+ 
123+static void TestRollback(void)
124+{
125+ Reset();
126+ const IotcJson *json = (const IotcJson *)(uintptr_t)1;
127+ CHECK_EQ_INT(IOTC_OK, SvcConnPrepareNetInfo(json));
128+ CHECK_EQ_INT(IOTC_OK, SvcConnRollbackNetInfo());
129+ CHECK(strcmp(g_savedSsid, "old-home") == 0);
130+ CHECK(strcmp(g_savedPassword, "old-password") == 0);
131+ CHECK(g_checked);
132+}
133+ 
134+int main(void)
135+{
136+ setbuf(stdout, NULL);
137+ TestCommit();
138+ TestRollback();
139+ printf("test_net_info_transaction: %d checks, %d failures\n", g_testChecks, g_testFailures);
140+ return g_testFailures == 0 ? 0 : 1;
141+}
Atest/unit/device_config_host/run_device_config_tests.sh+60-0
@@ -0,0 +1,60 @@
1+#!/bin/bash
2+# WiFi Only 配网状态版本化存储与恢复出厂 host 单元测试。
3+# 只用本机 gcc,不依赖 OpenHarmony 构建系统,不在源码树内留下生成物。
4+set -uo pipefail
5+ 
6+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
7+IOTC_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
8+OUT_DIR=$(mktemp -d /tmp/iotc_device_config_host_test.XXXXXX)
9+trap 'rm -rf "$OUT_DIR"' EXIT
10+ 
11+CFLAGS=(
12+ -std=c99 -Wall -Wextra -Werror -g -O0
13+ -I"$SCRIPT_DIR"
14+ -I"$SCRIPT_DIR/stubs"
15+ -I"$IOTC_ROOT/adapter/include"
16+ -I"$IOTC_ROOT/interfaces/kits/common"
17+ -I"$IOTC_ROOT/core/infrastructure/utils/include"
18+ -I"$IOTC_ROOT/core/infrastructure/define"
19+ -I"$IOTC_ROOT/core/device/config/include"
20+)
21+ 
22+echo "[1/3] build test_store"
23+gcc "${CFLAGS[@]}" "$SCRIPT_DIR/test_versioned_store.c" \
24+ "$IOTC_ROOT/core/device/config/versioned_store.c" \
25+ "$SCRIPT_DIR/stubs/iotc_kv_fake.c" \
26+ -o "$OUT_DIR/test_store" || exit 1
27+ 
28+echo "[2/3] build test_dss"
29+gcc "${CFLAGS[@]}" "$SCRIPT_DIR/test_device_state_store.c" \
30+ "$IOTC_ROOT/core/device/config/versioned_store.c" \
31+ "$IOTC_ROOT/core/device/config/device_state_store.c" \
32+ "$SCRIPT_DIR/stubs/iotc_kv_fake.c" \
33+ -o "$OUT_DIR/test_dss" || exit 1
34+ 
35+echo "[3/3] build test_config_revoke"
36+gcc -I"$SCRIPT_DIR/stubs/revoke" "${CFLAGS[@]}" -DIOTC_CONF_WIFI_ONLY_SUPPORT=1 \
37+ "$SCRIPT_DIR/test_config_revoke.c" \
38+ "$IOTC_ROOT/core/device/config/config_revoke.c" \
39+ "$SCRIPT_DIR/stubs/event_bus_stub.c" \
40+ -o "$OUT_DIR/test_config_revoke" || exit 1
41+ 
42+overall=0
43+echo "run test_store"
44+"$OUT_DIR/test_store"
45+[ $? -ne 0 ] && overall=1
46+ 
47+echo "run test_dss"
48+"$OUT_DIR/test_dss"
49+[ $? -ne 0 ] && overall=1
50+ 
51+echo "run test_config_revoke"
52+"$OUT_DIR/test_config_revoke"
53+[ $? -ne 0 ] && overall=1
54+ 
55+if [ "$overall" -eq 0 ]; then
56+ echo "DEVICE CONFIG HOST TESTS PASSED"
57+else
58+ echo "DEVICE CONFIG HOST TESTS FAILED"
59+fi
60+exit "$overall"
Atest/unit/device_config_host/stubs/event_bus.h+26-0
@@ -0,0 +1,26 @@
1+/*
2+ * Host-test stub of EventBus public API. Mirrors the real signatures and the
3+ * EventBusPublishSync macro; publishes invoke the subscriber synchronously.
4+ */
5+ 
6+#ifndef IOTC_TEST_STUB_EVENT_BUS_H
7+#define IOTC_TEST_STUB_EVENT_BUS_H
8+ 
9+#include <stdint.h>
10+ 
11+typedef void (*EventBusCallback)(uint32_t event, void *param, uint32_t len);
12+ 
13+int32_t EventBusSubscribe(EventBusCallback listener, uint32_t event);
14+int32_t EventBusUnsubscribe(EventBusCallback listener);
15+void EventBusPublishSyncInner(uint32_t event, const char *name, void *param, uint32_t len);
16+ 
17+#define EventBusPublishSync(event, param, len) EventBusPublishSyncInner(event, #event, param, len)
18+ 
19+/* Test knobs. */
20+extern EventBusCallback g_eventBusStubCallback;
21+extern uint32_t g_eventBusStubEvent;
22+extern int g_eventBusStubPublishCalls;
23+extern uint32_t g_eventBusStubLastEvent;
24+extern int32_t g_eventBusStubSubscribeResult;
25+ 
26+#endif /* IOTC_TEST_STUB_EVENT_BUS_H */
Atest/unit/device_config_host/stubs/event_bus_stub.c+41-0
@@ -0,0 +1,41 @@
1+/*
2+ * Host-test stub of EventBus: single subscriber slot, synchronous publish.
3+ */
4+ 
5+#include "event_bus.h"
6+ 
7+#include <string.h>
8+ 
9+EventBusCallback g_eventBusStubCallback;
10+uint32_t g_eventBusStubEvent;
11+int g_eventBusStubPublishCalls;
12+uint32_t g_eventBusStubLastEvent;
13+int32_t g_eventBusStubSubscribeResult;
14+ 
15+int32_t EventBusSubscribe(EventBusCallback listener, uint32_t event)
16+{
17+ if (g_eventBusStubSubscribeResult != 0) {
18+ return g_eventBusStubSubscribeResult;
19+ }
20+ g_eventBusStubCallback = listener;
21+ g_eventBusStubEvent = event;
22+ return 0;
23+}
24+ 
25+int32_t EventBusUnsubscribe(EventBusCallback listener)
26+{
27+ if (g_eventBusStubCallback == listener) {
28+ g_eventBusStubCallback = 0;
29+ }
30+ return 0;
31+}
32+ 
33+void EventBusPublishSyncInner(uint32_t event, const char *name, void *param, uint32_t len)
34+{
35+ (void)name;
36+ ++g_eventBusStubPublishCalls;
37+ g_eventBusStubLastEvent = event;
38+ if (g_eventBusStubCallback != 0 && event == g_eventBusStubEvent) {
39+ g_eventBusStubCallback(event, param, len);
40+ }
41+}
Atest/unit/device_config_host/stubs/iotc_kv_fake.c+199-0
@@ -0,0 +1,199 @@
1+/*
2+ * Fault-injecting fake of adapter IotcKv for host power-cut tests.
3+ * RAM 镜像在“断电”后保留;写/删操作计数用于在指定操作点注入故障。
4+ */
5+ 
6+#include "iotc_kv_fake.h"
7+ 
8+#include <stddef.h>
9+#include <string.h>
10+ 
11+#include "iotc_kv.h"
12+ 
13+#define KV_FAKE_MAX_ENTRIES 16
14+#define KV_FAKE_MAX_KEY_LEN 40
15+#define KV_FAKE_MAX_VALUE_LEN 560
16+ 
17+typedef struct {
18+ char key[KV_FAKE_MAX_KEY_LEN];
19+ uint8_t value[KV_FAKE_MAX_VALUE_LEN];
20+ uint32_t valueLen;
21+ int present;
22+} KvFakeEntry;
23+ 
24+static KvFakeEntry g_entries[KV_FAKE_MAX_ENTRIES];
25+static int g_crashed;
26+static int g_opCount;
27+static int g_armOp = -1;
28+static int g_armFault = KV_FAULT_NONE;
29+ 
30+static KvFakeEntry *FindEntry(const char *key)
31+{
32+ for (int i = 0; i < KV_FAKE_MAX_ENTRIES; ++i) {
33+ if (g_entries[i].present && strncmp(g_entries[i].key, key, KV_FAKE_MAX_KEY_LEN) == 0) {
34+ return &g_entries[i];
35+ }
36+ }
37+ return NULL;
38+}
39+ 
40+void KvFakeReset(void)
41+{
42+ (void)memset(g_entries, 0, sizeof(g_entries));
43+ g_crashed = 0;
44+ g_opCount = 0;
45+ g_armOp = -1;
46+ g_armFault = KV_FAULT_NONE;
47+}
48+ 
49+void KvFakeArm(int opIndex, int fault)
50+{
51+ g_armOp = opIndex;
52+ g_armFault = fault;
53+}
54+ 
55+void KvFakeReboot(void)
56+{
57+ g_crashed = 0;
58+ g_opCount = 0;
59+ g_armOp = -1;
60+ g_armFault = KV_FAULT_NONE;
61+}
62+ 
63+int32_t KvFakePresentCount(void)
64+{
65+ int32_t count = 0;
66+ for (int i = 0; i < KV_FAKE_MAX_ENTRIES; ++i) {
67+ count += g_entries[i].present;
68+ }
69+ return count;
70+}
71+ 
72+int32_t KvFakeContainsBytes(const uint8_t *needle, uint32_t needleLen)
73+{
74+ if (needle == NULL || needleLen == 0) {
75+ return 0;
76+ }
77+ for (int i = 0; i < KV_FAKE_MAX_ENTRIES; ++i) {
78+ if (!g_entries[i].present || needleLen > g_entries[i].valueLen) {
79+ continue;
80+ }
81+ for (uint32_t offset = 0; offset <= g_entries[i].valueLen - needleLen; ++offset) {
82+ if (memcmp(g_entries[i].value + offset, needle, needleLen) == 0) {
83+ return 1;
84+ }
85+ }
86+ }
87+ return 0;
88+}
89+ 
90+int32_t KvFakeCorrupt(const char *key)
91+{
92+ KvFakeEntry *entry = FindEntry(key);
93+ if (entry == NULL) {
94+ return -1;
95+ }
96+ for (uint32_t i = 0; i < entry->valueLen; i += 2) {
97+ entry->value[i] ^= 0xA5;
98+ }
99+ return 0;
100+}
101+ 
102+static int32_t StoreValue(const char *key, const uint8_t *value, uint32_t len, int torn)
103+{
104+ KvFakeEntry *entry = FindEntry(key);
105+ if (entry == NULL) {
106+ for (int i = 0; i < KV_FAKE_MAX_ENTRIES; ++i) {
107+ if (!g_entries[i].present) {
108+ entry = &g_entries[i];
109+ break;
110+ }
111+ }
112+ }
113+ if (entry == NULL || len > KV_FAKE_MAX_VALUE_LEN) {
114+ return -1;
115+ }
116+ uint32_t storedLen = torn ? (len / 2) : len;
117+ (void)memset(entry, 0, sizeof(*entry));
118+ (void)strncpy(entry->key, key, KV_FAKE_MAX_KEY_LEN - 1);
119+ (void)memcpy(entry->value, value, storedLen);
120+ entry->valueLen = storedLen;
121+ entry->present = 1;
122+ return 0;
123+}
124+ 
125+int32_t IotcKvInit(const char *tag)
126+{
127+ (void)tag;
128+ return 0;
129+}
130+ 
131+int32_t IotcKvDeInit(void)
132+{
133+ return 0;
134+}
135+ 
136+int32_t IotcKvSetValue(const char *key, const uint8_t *value, uint32_t len)
137+{
138+ if (key == NULL || value == NULL || len == 0 || g_crashed) {
139+ return -1;
140+ }
141+ int opIndex = g_opCount++;
142+ if (opIndex == g_armOp) {
143+ g_crashed = 1;
144+ if (g_armFault == KV_FAULT_TORN_WRITE) {
145+ (void)StoreValue(key, value, len, 1);
146+ }
147+ return -1;
148+ }
149+ return StoreValue(key, value, len, 0);
150+}
151+ 
152+int32_t IotcKvGetValue(const char *key, uint8_t *buf, uint32_t *len)
153+{
154+ if (key == NULL || buf == NULL || len == NULL || g_crashed) {
155+ return -1;
156+ }
157+ KvFakeEntry *entry = FindEntry(key);
158+ if (entry == NULL || *len < entry->valueLen) {
159+ return -1;
160+ }
161+ (void)memcpy(buf, entry->value, entry->valueLen);
162+ *len = entry->valueLen;
163+ return 0;
164+}
165+ 
166+int32_t IotcKvGetLen(const char *key, uint32_t *len)
167+{
168+ if (key == NULL || len == NULL || g_crashed) {
169+ return -1;
170+ }
171+ KvFakeEntry *entry = FindEntry(key);
172+ if (entry == NULL) {
173+ return -1;
174+ }
175+ *len = entry->valueLen;
176+ return 0;
177+}
178+ 
179+int32_t IotcKvDelValue(const char *key)
180+{
181+ if (key == NULL || g_crashed) {
182+ return -1;
183+ }
184+ int opIndex = g_opCount++;
185+ if (opIndex == g_armOp) {
186+ g_crashed = 1;
187+ if (g_armFault == KV_FAULT_TORN_ERASE) {
188+ (void)KvFakeCorrupt(key);
189+ return 0;
190+ }
191+ return -1;
192+ }
193+ KvFakeEntry *entry = FindEntry(key);
194+ if (entry == NULL) {
195+ return -1;
196+ }
197+ (void)memset(entry, 0, sizeof(*entry));
198+ return 0;
199+}
Atest/unit/device_config_host/stubs/iotc_kv_fake.h+29-0
@@ -0,0 +1,29 @@
1+/*
2+ * Fault-injecting fake of adapter IotcKv for host power-cut tests.
3+ */
4+ 
5+#ifndef IOTC_TEST_KV_FAKE_H
6+#define IOTC_TEST_KV_FAKE_H
7+ 
8+#include <stdint.h>
9+ 
10+/* 故障类型:在 arm 的第 opIndex 个写/删操作上注入。 */
11+enum {
12+ KV_FAULT_NONE = 0,
13+ KV_FAULT_FAIL, /* 操作失败并断电(后续操作全部失败,直到 KvFakeReboot) */
14+ KV_FAULT_TORN_WRITE, /* 撕裂写:只落盘前半截记录,然后断电 */
15+ KV_FAULT_TORN_ERASE /* 撕裂擦除:数据被破坏但残留存在,然后断电 */
16+};
17+ 
18+void KvFakeReset(void);
19+void KvFakeArm(int opIndex, int fault);
20+/* 模拟重新上电:不清 RAM 镜像,只恢复“已断电”状态并复位操作计数。 */
21+void KvFakeReboot(void);
22+int32_t KvFakePresentCount(void);
23+ 
24+/* 返回 RAM KV 镜像中是否出现指定连续字节,用于验证秘密未落入通用状态槽。 */
25+int32_t KvFakeContainsBytes(const uint8_t *needle, uint32_t needleLen);
26+/* 测试辅助:直接破坏指定 key 的数据但保留 key(模拟槽位损坏)。 */
27+int32_t KvFakeCorrupt(const char *key);
28+ 
29+#endif /* IOTC_TEST_KV_FAKE_H */
Atest/unit/device_config_host/stubs/iotc_log.h+15-0
@@ -0,0 +1,15 @@
1+/*
2+ * Host-test stub of adapter log header. Logs are discarded.
3+ */
4+ 
5+#ifndef IOTC_TEST_STUB_IOTC_LOG_H
6+#define IOTC_TEST_STUB_IOTC_LOG_H
7+ 
8+#define IOTC_LOGD(...) ((void)0)
9+#define IOTC_LOGI(...) ((void)0)
10+#define IOTC_LOGN(...) ((void)0)
11+#define IOTC_LOGW(...) ((void)0)
12+#define IOTC_LOGE(...) ((void)0)
13+#define IOTC_LOGF(...) ((void)0)
14+ 
15+#endif /* IOTC_TEST_STUB_IOTC_LOG_H */
Atest/unit/device_config_host/stubs/revoke/config_authinfo.h+8-0
@@ -0,0 +1,8 @@
1+#ifndef IOTC_TEST_REVOKE_CONFIG_AUTHINFO_H
2+#define IOTC_TEST_REVOKE_CONFIG_AUTHINFO_H
3+ 
4+#include <stdint.h>
5+ 
6+int32_t ConfigClearAuthInfo(void);
7+ 
8+#endif /* IOTC_TEST_REVOKE_CONFIG_AUTHINFO_H */
Atest/unit/device_config_host/stubs/revoke/config_login_info.h+8-0
@@ -0,0 +1,8 @@
1+#ifndef IOTC_TEST_REVOKE_CONFIG_LOGIN_INFO_H
2+#define IOTC_TEST_REVOKE_CONFIG_LOGIN_INFO_H
3+ 
4+#include <stdint.h>
5+ 
6+int32_t ConfigClearLoginInfo(void);
7+ 
8+#endif /* IOTC_TEST_REVOKE_CONFIG_LOGIN_INFO_H */
Atest/unit/device_config_host/stubs/revoke/config_register_info.h+8-0
@@ -0,0 +1,8 @@
1+#ifndef IOTC_TEST_REVOKE_CONFIG_REGISTER_INFO_H
2+#define IOTC_TEST_REVOKE_CONFIG_REGISTER_INFO_H
3+ 
4+#include <stdint.h>
5+ 
6+int32_t ConfigClearRegisterInfo(void);
7+ 
8+#endif /* IOTC_TEST_REVOKE_CONFIG_REGISTER_INFO_H */
Atest/unit/device_config_host/stubs/revoke/config_revoke_flag.h+3-0
@@ -0,0 +1,3 @@
1+#ifndef IOTC_TEST_REVOKE_CONFIG_REVOKE_FLAG_H
2+#define IOTC_TEST_REVOKE_CONFIG_REVOKE_FLAG_H
3+#endif /* IOTC_TEST_REVOKE_CONFIG_REVOKE_FLAG_H */
Atest/unit/device_config_host/stubs/revoke/device_state_store.h+8-0
@@ -0,0 +1,8 @@
1+#ifndef IOTC_TEST_REVOKE_DEVICE_STATE_STORE_H
2+#define IOTC_TEST_REVOKE_DEVICE_STATE_STORE_H
3+ 
4+#include <stdint.h>
5+ 
6+int32_t IotcDssFactoryResetRun(void);
7+ 
8+#endif /* IOTC_TEST_REVOKE_DEVICE_STATE_STORE_H */
Atest/unit/device_config_host/stubs/revoke/event_bus_sub.h+6-0
@@ -0,0 +1,6 @@
1+#ifndef IOTC_TEST_REVOKE_EVENT_BUS_SUB_H
2+#define IOTC_TEST_REVOKE_EVENT_BUS_SUB_H
3+ 
4+#include "event_bus.h"
5+ 
6+#endif /* IOTC_TEST_REVOKE_EVENT_BUS_SUB_H */
Atest/unit/device_config_host/stubs/revoke/sec_conf_info.h+8-0
@@ -0,0 +1,8 @@
1+#ifndef IOTC_TEST_REVOKE_SEC_CONF_INFO_H
2+#define IOTC_TEST_REVOKE_SEC_CONF_INFO_H
3+ 
4+#include <stdint.h>
5+ 
6+int32_t SecConfInfoFactoryReset(void);
7+ 
8+#endif /* IOTC_TEST_REVOKE_SEC_CONF_INFO_H */
Atest/unit/device_config_host/stubs/revoke/sec_conf_key_cert.h+8-0
@@ -0,0 +1,8 @@
1+#ifndef IOTC_TEST_REVOKE_SEC_CONF_KEY_CERT_H
2+#define IOTC_TEST_REVOKE_SEC_CONF_KEY_CERT_H
3+ 
4+#include <stdint.h>
5+ 
6+int32_t SecConfKeyCertFactoryReset(void);
7+ 
8+#endif /* IOTC_TEST_REVOKE_SEC_CONF_KEY_CERT_H */
Atest/unit/device_config_host/stubs/revoke/utils_combo.h+14-0
@@ -0,0 +1,14 @@
1+#ifndef IOTC_TEST_REVOKE_UTILS_COMBO_H
2+#define IOTC_TEST_REVOKE_UTILS_COMBO_H
3+ 
4+typedef enum {
5+ COMBO_TYPE_INVALID = -1,
6+ COMBO_TYPE_COMBO = 0,
7+ COMBO_TYPE_WIFI_ONLY,
8+ COMBO_TYPE_BLE_ONLY,
9+ COMBO_TYPE_SLE_ONLY,
10+} ComboType;
11+ 
12+ComboType UtilsGetComboType(void);
13+ 
14+#endif /* IOTC_TEST_REVOKE_UTILS_COMBO_H */
Atest/unit/device_config_host/stubs/securec.h+131-0
@@ -0,0 +1,131 @@
1+/*
2+ * Host-test stub of third_party bounds_checking_function securec.h.
3+ * Provides the _s functions used by device config sources.
4+ */
5+ 
6+#ifndef IOTC_TEST_STUB_SECUREC_H
7+#define IOTC_TEST_STUB_SECUREC_H
8+ 
9+#include <stdarg.h>
10+#include <stddef.h>
11+#include <stdio.h>
12+#include <string.h>
13+ 
14+typedef int errno_t;
15+typedef size_t rsize_t;
16+#define EOK 0
17+#define EINVAL (-22)
18+ 
19+static inline errno_t memset_s(void *dest, rsize_t destMax, int c, rsize_t count)
20+{
21+ if (dest == NULL || count > destMax) {
22+ return EINVAL;
23+ }
24+ (void)memset(dest, c, count);
25+ return EOK;
26+}
27+ 
28+static inline errno_t memcpy_s(void *dest, rsize_t destMax, const void *src, rsize_t count)
29+{
30+ if (dest == NULL || src == NULL || count > destMax) {
31+ return EINVAL;
32+ }
33+ (void)memcpy(dest, src, count);
34+ return EOK;
35+}
36+ 
37+static inline errno_t memmove_s(void *dest, rsize_t destMax, const void *src, rsize_t count)
38+{
39+ if (dest == NULL || src == NULL || count > destMax) {
40+ return EINVAL;
41+ }
42+ (void)memmove(dest, src, count);
43+ return EOK;
44+}
45+ 
46+static inline errno_t strcpy_s(char *dest, rsize_t destMax, const char *src)
47+{
48+ if (dest == NULL || src == NULL || destMax == 0) {
49+ return EINVAL;
50+ }
51+ size_t len = strlen(src);
52+ if (len >= destMax) {
53+ dest[0] = '\0';
54+ return EINVAL;
55+ }
56+ (void)memcpy(dest, src, len + 1);
57+ return EOK;
58+}
59+ 
60+static inline errno_t strncpy_s(char *dest, rsize_t destMax, const char *src, rsize_t count)
61+{
62+ if (dest == NULL || src == NULL || destMax == 0 || count >= destMax) {
63+ if (dest != NULL && destMax > 0) {
64+ dest[0] = '\0';
65+ }
66+ return EINVAL;
67+ }
68+ rsize_t srcLen = strlen(src);
69+ rsize_t copyLen = srcLen < count ? srcLen : count;
70+ (void)memcpy(dest, src, copyLen);
71+ dest[copyLen] = '\0';
72+ return EOK;
73+}
74+ 
75+static inline int snprintf_s(char *dest, rsize_t destMax, rsize_t count, const char *format, ...)
76+{
77+ if (dest == NULL || destMax == 0 || format == NULL) {
78+ return -1;
79+ }
80+ va_list ap;
81+ va_start(ap, format);
82+ int written = vsnprintf(dest, destMax, format, ap);
83+ va_end(ap);
84+ if (written < 0 || (rsize_t)written >= count) {
85+ dest[0] = '\0';
86+ return -1;
87+ }
88+ return written;
89+}
90+ 
91+static inline int sprintf_s(char *dest, rsize_t destMax, const char *format, ...)
92+{
93+ if (dest == NULL || destMax == 0 || format == NULL) {
94+ return -1;
95+ }
96+ va_list ap;
97+ va_start(ap, format);
98+ int written = vsnprintf(dest, destMax, format, ap);
99+ va_end(ap);
100+ if (written < 0 || (rsize_t)written >= destMax) {
101+ dest[0] = '\0';
102+ return -1;
103+ }
104+ return written;
105+}
106+ 
107+static inline int sscanf_s(const char *buffer, const char *format, ...)
108+{
109+ if (buffer == NULL || format == NULL) {
110+ return -1;
111+ }
112+ va_list ap;
113+ va_start(ap, format);
114+ int matched = vsscanf(buffer, format, ap);
115+ va_end(ap);
116+ return matched;
117+}
118+ 
119+static inline rsize_t strnlen_s(const char *s, rsize_t maxSize)
120+{
121+ if (s == NULL) {
122+ return 0;
123+ }
124+ rsize_t len = 0;
125+ while (len < maxSize && s[len] != '\0') {
126+ ++len;
127+ }
128+ return len;
129+}
130+ 
131+#endif /* IOTC_TEST_STUB_SECUREC_H */
Atest/unit/device_config_host/test_config_revoke.c+157-0
@@ -0,0 +1,157 @@
1+/*
2+ * Host regression for the synchronous WiFi Only factory-reset subscriber.
3+ */
4+ 
5+#include <stdint.h>
6+#include <stdio.h>
7+#include <string.h>
8+ 
9+#include "config_revoke.h"
10+#include "event_bus.h"
11+#include "iotc_event.h"
12+#include "test_framework.h"
13+#include "utils_combo.h"
14+ 
15+int g_testChecks;
16+int g_testFailures;
17+ 
18+static int32_t g_dssResult;
19+static int32_t g_securityResult;
20+static int32_t g_keyCertResult;
21+static int32_t g_loginResult;
22+static int32_t g_registerResult;
23+static int g_dssCalls;
24+static int g_securityCalls;
25+static int g_keyCertCalls;
26+static int g_loginCalls;
27+static int g_registerCalls;
28+static char g_callOrder[12];
29+static size_t g_callOrderLen;
30+ 
31+static void RecordCall(char call)
32+{
33+ if (g_callOrderLen + 1U < sizeof(g_callOrder)) {
34+ g_callOrder[g_callOrderLen++] = call;
35+ g_callOrder[g_callOrderLen] = '\0';
36+ }
37+}
38+ 
39+int32_t IotcDssFactoryResetRun(void)
40+{
41+ ++g_dssCalls;
42+ RecordCall('D');
43+ return g_dssResult;
44+}
45+ 
46+int32_t ConfigClearRegisterInfo(void)
47+{
48+ ++g_registerCalls;
49+ RecordCall('R');
50+ return g_registerResult;
51+}
52+ 
53+int32_t SecConfInfoFactoryReset(void)
54+{
55+ ++g_securityCalls;
56+ RecordCall('S');
57+ return g_securityResult;
58+}
59+ 
60+int32_t SecConfKeyCertFactoryReset(void)
61+{
62+ ++g_keyCertCalls;
63+ RecordCall('K');
64+ return g_keyCertResult;
65+}
66+ 
67+int32_t ConfigClearLoginInfo(void)
68+{
69+ ++g_loginCalls;
70+ RecordCall('L');
71+ return g_loginResult;
72+}
73+ 
74+int32_t ConfigClearAuthInfo(void)
75+{
76+ return 0;
77+}
78+ 
79+ComboType UtilsGetComboType(void)
80+{
81+ return COMBO_TYPE_WIFI_ONLY;
82+}
83+ 
84+static void ResetState(void)
85+{
86+ g_eventBusStubCallback = NULL;
87+ g_eventBusStubEvent = 0;
88+ g_eventBusStubPublishCalls = 0;
89+ g_eventBusStubLastEvent = 0;
90+ g_eventBusStubSubscribeResult = 0;
91+ g_dssResult = 0;
92+ g_securityResult = 0;
93+ g_keyCertResult = 0;
94+ g_loginResult = 0;
95+ g_registerResult = 0;
96+ g_dssCalls = 0;
97+ g_securityCalls = 0;
98+ g_keyCertCalls = 0;
99+ g_loginCalls = 0;
100+ g_registerCalls = 0;
101+ g_callOrderLen = 0;
102+ (void)memset(g_callOrder, 0, sizeof(g_callOrder));
103+}
104+ 
105+static void TestSynchronousResetOrder(void)
106+{
107+ ResetState();
108+ CHECK_EQ_INT(0, ConfigRevokeInit());
109+ CHECK(g_eventBusStubCallback != NULL);
110+ CHECK_EQ_INT(IOTC_CORE_COMM_EVENT_MAIN_RESTORE, g_eventBusStubEvent);
111+ 
112+ EventBusPublishSync(IOTC_CORE_COMM_EVENT_MAIN_RESTORE, NULL, 0);
113+ CHECK_EQ_INT(1, g_dssCalls);
114+ CHECK_EQ_INT(1, g_securityCalls);
115+ CHECK_EQ_INT(1, g_keyCertCalls);
116+ CHECK_EQ_INT(1, g_loginCalls);
117+ CHECK_EQ_INT(1, g_registerCalls);
118+ CHECK(strcmp(g_callOrder, "DSKLR") == 0);
119+ 
120+ ConfigRevokeDeinit();
121+ CHECK(g_eventBusStubCallback == NULL);
122+}
123+ 
124+static void TestDssFailureStillRunsLegacyCleanup(void)
125+{
126+ ResetState();
127+ g_dssResult = -17;
128+ g_registerResult = -19;
129+ CHECK_EQ_INT(0, ConfigRevokeInit());
130+ EventBusPublishSync(IOTC_CORE_COMM_EVENT_MAIN_RESTORE, NULL, 0);
131+ CHECK_EQ_INT(1, g_dssCalls);
132+ CHECK_EQ_INT(1, g_securityCalls);
133+ CHECK_EQ_INT(1, g_keyCertCalls);
134+ CHECK_EQ_INT(1, g_loginCalls);
135+ CHECK_EQ_INT(1, g_registerCalls);
136+ CHECK(strcmp(g_callOrder, "DSKLR") == 0);
137+ ConfigRevokeDeinit();
138+}
139+ 
140+static void TestSubscribeFailure(void)
141+{
142+ ResetState();
143+ g_eventBusStubSubscribeResult = -23;
144+ CHECK_EQ_INT(-23, ConfigRevokeInit());
145+ CHECK(g_eventBusStubCallback == NULL);
146+ CHECK_EQ_INT(0, g_dssCalls);
147+ CHECK_EQ_INT(0, g_registerCalls);
148+}
149+ 
150+int main(void)
151+{
152+ TestSynchronousResetOrder();
153+ TestDssFailureStillRunsLegacyCleanup();
154+ TestSubscribeFailure();
155+ printf("test_config_revoke: %d checks, %d failures\n", g_testChecks, g_testFailures);
156+ return (g_testFailures == 0) ? 0 : 1;
157+}
Atest/unit/device_config_host/test_device_state_store.c+101-0
@@ -0,0 +1,101 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device 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 <stdio.h>
17+#include <string.h>
18+ 
19+#include "device_state_store.h"
20+#include "iotc_errcode.h"
21+#include "iotc_kv_fake.h"
22+#include "test_framework.h"
23+ 
24+int g_testChecks;
25+int g_testFailures;
26+ 
27+static IotcDssProvisionPending MakePending(void)
28+{
29+ IotcDssProvisionPending pending = {0};
30+ for (uint8_t i = 0; i < 8; ++i) {
31+ pending.mdid[i] = (uint8_t)(0x30 + i);
32+ }
33+ pending.mdidLen = 8;
34+ (void)strcpy(pending.tempDevId, "temp-dev-0001");
35+ (void)strcpy(pending.registerCode, "RC-TEST-ONLY-NOT-SECRET");
36+ pending.state = IOTC_DSS_PENDING_STATE_WAIT_ACTIVATE;
37+ return pending;
38+}
39+ 
40+static void TestRecordRoundtrip(void)
41+{
42+ KvFakeReset();
43+ IotcDssProvisionPending pending = {0};
44+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND, IotcDssLoadProvisionPending(&pending));
45+ IotcDssProvisionPending saved = MakePending();
46+ CHECK_EQ_INT(IOTC_OK, IotcDssSaveProvisionPending(&saved));
47+ CHECK_EQ_INT(IOTC_OK, IotcDssLoadProvisionPending(&pending));
48+ CHECK(memcmp(&pending, &saved, sizeof(pending)) == 0);
49+}
50+ 
51+static void TestValidation(void)
52+{
53+ KvFakeReset();
54+ IotcDssProvisionPending pending = MakePending();
55+ pending.mdidLen = IOTC_DSS_MDID_MAX_LEN + 1;
56+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcDssSaveProvisionPending(&pending));
57+ pending = MakePending();
58+ pending.state = 99;
59+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcDssSaveProvisionPending(&pending));
60+ pending = MakePending();
61+ pending.onlineNotifyDone = 2;
62+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcDssSaveProvisionPending(&pending));
63+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcDssSaveProvisionPending(NULL));
64+}
65+ 
66+static void TestOnlineNotifyDone(void)
67+{
68+ KvFakeReset();
69+ IotcDssProvisionPending pending = MakePending();
70+ CHECK_EQ_INT(IOTC_OK, IotcDssSaveProvisionPending(&pending));
71+ CHECK_EQ_INT(IOTC_OK, IotcDssMarkOnlineNotifyDone());
72+ CHECK_EQ_INT(IOTC_OK, IotcDssLoadProvisionPending(&pending));
73+ CHECK_EQ_INT(1, pending.onlineNotifyDone);
74+ uint32_t generation = 0;
75+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(IOTC_DSS_DOMAIN_PROVISION_PENDING, &generation));
76+ CHECK_EQ_INT(2, generation);
77+ CHECK_EQ_INT(IOTC_OK, IotcDssMarkOnlineNotifyDone());
78+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(IOTC_DSS_DOMAIN_PROVISION_PENDING, &generation));
79+ CHECK_EQ_INT(2, generation);
80+}
81+ 
82+static void TestFactoryReset(void)
83+{
84+ KvFakeReset();
85+ IotcDssProvisionPending pending = MakePending();
86+ CHECK_EQ_INT(IOTC_OK, IotcDssSaveProvisionPending(&pending));
87+ CHECK_EQ_INT(IOTC_OK, IotcDssFactoryResetRun());
88+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND, IotcDssLoadProvisionPending(&pending));
89+ CHECK_EQ_INT(0, KvFakePresentCount());
90+}
91+ 
92+int main(void)
93+{
94+ setbuf(stdout, NULL);
95+ TestRecordRoundtrip();
96+ TestValidation();
97+ TestOnlineNotifyDone();
98+ TestFactoryReset();
99+ printf("test_dss: %d checks, %d failures\n", g_testChecks, g_testFailures);
100+ return (g_testFailures == 0) ? 0 : 1;
101+}
Atest/unit/device_config_host/test_framework.h+33-0
@@ -0,0 +1,33 @@
1+/*
2+ * Host unit test check macros for iot_connect device config tests.
3+ */
4+ 
5+#ifndef IOTC_TEST_FRAMEWORK_H
6+#define IOTC_TEST_FRAMEWORK_H
7+ 
8+#include <stdio.h>
9+ 
10+extern int g_testChecks;
11+extern int g_testFailures;
12+ 
13+#define CHECK(cond) \
14+ do { \
15+ ++g_testChecks; \
16+ if (!(cond)) { \
17+ ++g_testFailures; \
18+ printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
19+ } \
20+ } while (0)
21+ 
22+#define CHECK_EQ_INT(expected, actual) \
23+ do { \
24+ ++g_testChecks; \
25+ long expected_ = (long)(expected); \
26+ long actual_ = (long)(actual); \
27+ if (expected_ != actual_) { \
28+ ++g_testFailures; \
29+ printf("FAIL %s:%d: %s expected %ld got %ld\n", __FILE__, __LINE__, #actual, expected_, actual_); \
30+ } \
31+ } while (0)
32+ 
33+#endif /* IOTC_TEST_FRAMEWORK_H */
Atest/unit/device_config_host/test_versioned_store.c+298-0
@@ -0,0 +1,298 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device 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+/* 双槽版本化存储测试:基本语义 + 断电/撕裂注入矩阵(W4 验证:任一断电点
17+ * 重启后状态只能是旧有效或新有效,不能半有效)。 */
18+ 
19+#include <stdio.h>
20+#include <string.h>
21+ 
22+#include "iotc_errcode.h"
23+#include "iotc_kv_fake.h"
24+#include "test_framework.h"
25+#include "versioned_store.h"
26+ 
27+int g_testChecks;
28+int g_testFailures;
29+ 
30+#define DEMO_DOMAIN "demo"
31+#define SCHEMA_V1 1
32+ 
33+static const char *OLD_VALUE = "OLD-VALUE-0123456789";
34+static const char *NEW_VALUE = "NEW-VALUE-9876543210";
35+ 
36+static void SaveCommitted(const char *domain, const char *value)
37+{
38+ CHECK_EQ_INT(IOTC_OK, IotcVsBeginWrite(domain, SCHEMA_V1, (const uint8_t *)value,
39+ (uint32_t)strlen(value) + 1));
40+ CHECK_EQ_INT(IOTC_OK, IotcVsCommit(domain));
41+}
42+ 
43+/* 加载并断言内容等于 expected。 */
44+static void LoadExpect(const char *domain, const char *expected)
45+{
46+ uint8_t buf[64] = {0};
47+ uint32_t len = sizeof(buf);
48+ CHECK_EQ_INT(IOTC_OK, IotcVsLoad(domain, SCHEMA_V1, buf, &len));
49+ CHECK_EQ_INT((uint32_t)strlen(expected) + 1, len);
50+ CHECK(memcmp(buf, expected, len) == 0);
51+}
52+ 
53+static void TestEmptyLoad(void)
54+{
55+ KvFakeReset();
56+ uint8_t buf[16] = {0};
57+ uint32_t len = sizeof(buf);
58+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND, IotcVsLoad(DEMO_DOMAIN, SCHEMA_V1, buf, &len));
59+ uint32_t generation = 0;
60+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND, IotcVsGetGeneration(DEMO_DOMAIN, &generation));
61+}
62+ 
63+static void TestRoundtripAndGeneration(void)
64+{
65+ KvFakeReset();
66+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
67+ LoadExpect(DEMO_DOMAIN, OLD_VALUE);
68+ uint32_t generation = 0;
69+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(DEMO_DOMAIN, &generation));
70+ CHECK_EQ_INT(1, generation);
71+ CHECK_EQ_INT(1, KvFakePresentCount());
72+ 
73+ SaveCommitted(DEMO_DOMAIN, NEW_VALUE);
74+ LoadExpect(DEMO_DOMAIN, NEW_VALUE);
75+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(DEMO_DOMAIN, &generation));
76+ CHECK_EQ_INT(2, generation);
77+ 
78+ /* 第三次写入应回到第一个槽位,但对外只表现为 generation 递增 */
79+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
80+ LoadExpect(DEMO_DOMAIN, OLD_VALUE);
81+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(DEMO_DOMAIN, &generation));
82+ CHECK_EQ_INT(3, generation);
83+ CHECK_EQ_INT(1, KvFakePresentCount());
84+}
85+ 
86+static void TestSchemaAndBuffer(void)
87+{
88+ KvFakeReset();
89+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
90+ 
91+ uint8_t buf[64] = {0};
92+ uint32_t len = sizeof(buf);
93+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_SCHEMA, IotcVsLoad(DEMO_DOMAIN, 2, buf, &len));
94+ /* schema 不匹配不破坏原数据 */
95+ LoadExpect(DEMO_DOMAIN, OLD_VALUE);
96+ 
97+ uint8_t tiny[4] = {0};
98+ len = sizeof(tiny);
99+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcVsLoad(DEMO_DOMAIN, SCHEMA_V1, tiny, &len));
100+}
101+ 
102+static void TestParamValidation(void)
103+{
104+ KvFakeReset();
105+ uint8_t buf[16] = {0};
106+ uint32_t len = sizeof(buf);
107+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcVsLoad(NULL, SCHEMA_V1, buf, &len));
108+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcVsLoad("BAD-CHAR", SCHEMA_V1, buf, &len));
109+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcVsLoad("", SCHEMA_V1, buf, &len));
110+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID,
111+ IotcVsLoad("domain_name_longer_than_24chars", SCHEMA_V1, buf, &len));
112+ char unterminated[IOTC_VS_MAX_DOMAIN_LEN + 1];
113+ (void)memset(unterminated, 'a', sizeof(unterminated));
114+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcVsLoad(unterminated, SCHEMA_V1, buf, &len));
115+ 
116+ uint8_t payload[IOTC_VS_MAX_PAYLOAD_LEN + 1] = {0};
117+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID,
118+ IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, payload, 0));
119+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID,
120+ IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, payload, IOTC_VS_MAX_PAYLOAD_LEN + 1));
121+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, NULL, 4));
122+ 
123+ CHECK_EQ_INT(IOTC_OK,
124+ IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, payload, IOTC_VS_MAX_PAYLOAD_LEN));
125+ CHECK_EQ_INT(IOTC_OK, IotcVsCommit(DEMO_DOMAIN));
126+ uint8_t bigBuf[IOTC_VS_MAX_PAYLOAD_LEN] = {0};
127+ len = sizeof(bigBuf);
128+ CHECK_EQ_INT(IOTC_OK, IotcVsLoad(DEMO_DOMAIN, SCHEMA_V1, bigBuf, &len));
129+ CHECK_EQ_INT(IOTC_VS_MAX_PAYLOAD_LEN, len);
130+}
131+ 
132+static void TestAbortFlow(void)
133+{
134+ KvFakeReset();
135+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
136+ 
137+ /* BeginWrite 后 Abort:擦新槽,旧值保留 */
138+ CHECK_EQ_INT(IOTC_OK, IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, (const uint8_t *)NEW_VALUE,
139+ (uint32_t)strlen(NEW_VALUE) + 1));
140+ CHECK_EQ_INT(IOTC_OK, IotcVsAbort(DEMO_DOMAIN));
141+ LoadExpect(DEMO_DOMAIN, OLD_VALUE);
142+ CHECK_EQ_INT(1, KvFakePresentCount());
143+ uint32_t generation = 0;
144+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(DEMO_DOMAIN, &generation));
145+ CHECK_EQ_INT(1, generation);
146+ 
147+ /* 无待回滚记录时 Abort 幂等 */
148+ CHECK_EQ_INT(IOTC_OK, IotcVsAbort(DEMO_DOMAIN));
149+ LoadExpect(DEMO_DOMAIN, OLD_VALUE);
150+}
151+ 
152+static void TestCommitFlow(void)
153+{
154+ KvFakeReset();
155+ CHECK_EQ_INT(IOTC_OK, IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, (const uint8_t *)OLD_VALUE,
156+ (uint32_t)strlen(OLD_VALUE) + 1));
157+ /* 首次写在 Commit 前也不可见。 */
158+ uint8_t emptyBuf[64] = {0};
159+ uint32_t emptyLen = sizeof(emptyBuf);
160+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND,
161+ IotcVsLoad(DEMO_DOMAIN, SCHEMA_V1, emptyBuf, &emptyLen));
162+ CHECK_EQ_INT(IOTC_OK, IotcVsCommit(DEMO_DOMAIN));
163+ LoadExpect(DEMO_DOMAIN, OLD_VALUE);
164+ 
165+ KvFakeReset();
166+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
167+ CHECK_EQ_INT(IOTC_OK, IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, (const uint8_t *)NEW_VALUE,
168+ (uint32_t)strlen(NEW_VALUE) + 1));
169+ /* 提交前:pending 新值不可见,旧值仍是 active。 */
170+ LoadExpect(DEMO_DOMAIN, OLD_VALUE);
171+ CHECK_EQ_INT(2, KvFakePresentCount());
172+ CHECK_EQ_INT(IOTC_OK, IotcVsCommit(DEMO_DOMAIN));
173+ LoadExpect(DEMO_DOMAIN, NEW_VALUE);
174+ CHECK_EQ_INT(1, KvFakePresentCount());
175+ /* 重复提交幂等 */
176+ CHECK_EQ_INT(IOTC_OK, IotcVsCommit(DEMO_DOMAIN));
177+ LoadExpect(DEMO_DOMAIN, NEW_VALUE);
178+}
179+ 
180+static void TestCorruptSlotSelfHeal(void)
181+{
182+ KvFakeReset();
183+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
184+ /* 直接破坏唯一有效槽:加载失败;随后写入自愈 */
185+ CHECK_EQ_INT(0, KvFakeCorrupt("vs_" DEMO_DOMAIN "_a"));
186+ uint8_t buf[32] = {0};
187+ uint32_t len = sizeof(buf);
188+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND, IotcVsLoad(DEMO_DOMAIN, SCHEMA_V1, buf, &len));
189+ 
190+ CHECK_EQ_INT(IOTC_OK, IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, (const uint8_t *)NEW_VALUE,
191+ (uint32_t)strlen(NEW_VALUE) + 1));
192+ CHECK_EQ_INT(IOTC_OK, IotcVsCommit(DEMO_DOMAIN));
193+ LoadExpect(DEMO_DOMAIN, NEW_VALUE);
194+}
195+ 
196+static void TestErase(void)
197+{
198+ KvFakeReset();
199+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
200+ CHECK_EQ_INT(IOTC_OK, IotcVsErase(DEMO_DOMAIN));
201+ CHECK_EQ_INT(0, KvFakePresentCount());
202+ /* 空域擦除幂等 */
203+ CHECK_EQ_INT(IOTC_OK, IotcVsErase(DEMO_DOMAIN));
204+}
205+ 
206+/* 断电不变式:加载结果必须是 old 或 new 之一,内容逐字节一致。 */
207+static int32_t LoadEither(const char *domain, const char *oldValue, const char *newValue)
208+{
209+ uint8_t buf[64] = {0};
210+ uint32_t len = sizeof(buf);
211+ int32_t ret = IotcVsLoad(domain, SCHEMA_V1, buf, &len);
212+ CHECK_EQ_INT(IOTC_OK, ret);
213+ if (ret != IOTC_OK) {
214+ return -1;
215+ }
216+ if (len == (uint32_t)strlen(oldValue) + 1 && memcmp(buf, oldValue, len) == 0) {
217+ return 0;
218+ }
219+ if (len == (uint32_t)strlen(newValue) + 1 && memcmp(buf, newValue, len) == 0) {
220+ return 1;
221+ }
222+ CHECK(!"loaded record is neither intact old nor intact new");
223+ return -1;
224+}
225+ 
226+static void PowerCutCase(int armOp, int fault, const char *caseName)
227+{
228+ KvFakeReset();
229+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
230+ KvFakeReboot(); /* 只复位操作计数,保留已提交的旧值 */
231+ KvFakeArm(armOp, fault);
232+ 
233+ (void)IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, (const uint8_t *)NEW_VALUE,
234+ (uint32_t)strlen(NEW_VALUE) + 1);
235+ (void)IotcVsCommit(DEMO_DOMAIN);
236+ /* 断电恢复后:只能是旧有效或新有效 */
237+ KvFakeReboot();
238+ int32_t which = LoadEither(DEMO_DOMAIN, OLD_VALUE, NEW_VALUE);
239+ printf(" powercut %s: survived with %s\n", caseName, (which == 0) ? "old" : "new");
240+ /* 恢复后继续写入必须可用且 generation 继续递增 */
241+ SaveCommitted(DEMO_DOMAIN, NEW_VALUE);
242+ LoadExpect(DEMO_DOMAIN, NEW_VALUE);
243+ uint32_t generation = 0;
244+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(DEMO_DOMAIN, &generation));
245+ CHECK(generation >= 2);
246+}
247+ 
248+static void TestPowerCutMatrix(void)
249+{
250+ /* 写前断电:新记录未落盘,旧值有效 */
251+ PowerCutCase(0, KV_FAULT_FAIL, "fail_before_write");
252+ /* 写中撕裂:半截记录 CRC 不过,旧值有效 */
253+ PowerCutCase(0, KV_FAULT_TORN_WRITE, "torn_write");
254+ /* 提交标记写入时断电/撕裂:旧 committed 值仍有效。 */
255+ PowerCutCase(1, KV_FAULT_FAIL, "fail_at_commit_marker");
256+ PowerCutCase(1, KV_FAULT_TORN_WRITE, "torn_commit_marker");
257+ /* 标记成功、擦旧槽时断电:两条 committed 并存,加载 generation 更高的新值。 */
258+ PowerCutCase(2, KV_FAULT_FAIL, "fail_at_commit_erase");
259+ PowerCutCase(2, KV_FAULT_TORN_ERASE, "torn_erase_at_commit");
260+ 
261+ /* 断电点“回读后/提交前”:pending 新值不生效;Abort 后仍是旧值。 */
262+ KvFakeReset();
263+ SaveCommitted(DEMO_DOMAIN, OLD_VALUE);
264+ CHECK_EQ_INT(IOTC_OK, IotcVsBeginWrite(DEMO_DOMAIN, SCHEMA_V1, (const uint8_t *)NEW_VALUE,
265+ (uint32_t)strlen(NEW_VALUE) + 1));
266+ KvFakeReboot();
267+ CHECK_EQ_INT(0, LoadEither(DEMO_DOMAIN, OLD_VALUE, NEW_VALUE));
268+ CHECK_EQ_INT(IOTC_OK, IotcVsAbort(DEMO_DOMAIN));
269+ LoadExpect(DEMO_DOMAIN, OLD_VALUE);
270+}
271+ 
272+static void TestPowerCutLoop(void)
273+{
274+ /* 每个断电/撕裂点 x 每个故障类型循环 20 轮(host 侧稳定性放大) */
275+ for (int round = 0; round < 20; ++round) {
276+ PowerCutCase(round % 3, KV_FAULT_FAIL, "loop_fail");
277+ PowerCutCase(round % 3, KV_FAULT_TORN_WRITE, "loop_torn_write");
278+ PowerCutCase(round % 3, KV_FAULT_TORN_ERASE, "loop_torn_erase");
279+ }
280+}
281+ 
282+int main(void)
283+{
284+ setbuf(stdout, NULL);
285+ TestEmptyLoad();
286+ TestRoundtripAndGeneration();
287+ TestSchemaAndBuffer();
288+ TestParamValidation();
289+ TestAbortFlow();
290+ TestCommitFlow();
291+ TestCorruptSlotSelfHeal();
292+ TestErase();
293+ TestPowerCutMatrix();
294+ TestPowerCutLoop();
295+ 
296+ printf("test_store: %d checks, %d failures\n", g_testChecks, g_testFailures);
297+ return (g_testFailures == 0) ? 0 : 1;
298+}
Atest/unit/hub_setup_host/run_hub_setup_tests.sh+47-0
@@ -0,0 +1,47 @@
1+#!/bin/bash
2+# Build and run the WiFi Only HubSetup transaction test outside the source tree.
3+set -uo pipefail
4+ 
5+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
6+IOTC_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
7+if [ -n "${IOTC_TEST_OHOS_ROOT:-}" ]; then
8+ OHOS_ROOT=$(cd "$IOTC_TEST_OHOS_ROOT" && pwd)
9+else
10+ OHOS_ROOT=$(cd "$IOTC_ROOT/../../.." && pwd)
11+fi
12+OUT_DIR=$(mktemp -d /tmp/iotc_hub_setup_host_test.XXXXXX)
13+trap 'rm -rf "$OUT_DIR"' EXIT
14+ 
15+CFLAGS=(
16+ -std=c99 -Wall -Wextra -Werror -g -O0
17+ -DIOTC_CONF_WIFI_ONLY_SUPPORT=1
18+ -DIOTC_CONF_WIFI_SUPPORT=1
19+ -DIOTC_ONECONNECT_VERSION_CODE=3
20+ -I"$SCRIPT_DIR"
21+ -I"$SCRIPT_DIR/stubs"
22+ -I"$IOTC_ROOT/test/unit/device_config_host"
23+ -I"$IOTC_ROOT/test/unit/device_config_host/stubs"
24+ -I"$IOTC_ROOT/adapter/include"
25+ -I"$IOTC_ROOT/interfaces/kits/common"
26+ -I"$IOTC_ROOT/core/infrastructure/define"
27+ -I"$IOTC_ROOT/core/infrastructure/service/include"
28+ -I"$IOTC_ROOT/core/infrastructure/utils/include"
29+ -I"$IOTC_ROOT/core/device/config/include"
30+ -I"$IOTC_ROOT/core/device/service"
31+ -I"$OHOS_ROOT/third_party/cJSON"
32+)
33+ 
34+SOURCES=(
35+ "$SCRIPT_DIR/test_hub_setup.c"
36+ "$SCRIPT_DIR/stubs/hub_setup_deps_stub.c"
37+ "$IOTC_ROOT/core/device/service/svc_hub_setup.c"
38+ "$IOTC_ROOT/core/device/config/versioned_store.c"
39+ "$IOTC_ROOT/core/device/config/device_state_store.c"
40+ "$IOTC_ROOT/adapter/json/cjson/iotc_json.c"
41+ "$IOTC_ROOT/test/unit/device_config_host/stubs/iotc_kv_fake.c"
42+ "$IOTC_ROOT/test/unit/device_config_host/stubs/event_bus_stub.c"
43+ "$OHOS_ROOT/third_party/cJSON/cJSON.c"
44+)
45+ 
46+gcc "${CFLAGS[@]}" "${SOURCES[@]}" -lm -o "$OUT_DIR/test_hub_setup" || exit 1
47+"$OUT_DIR/test_hub_setup"
Atest/unit/hub_setup_host/stubs/hub_setup_deps_stub.c+122-0
@@ -0,0 +1,122 @@
1+/* Minimal real-behaviour dependencies for the HubSetup host test. */
2+ 
3+#include "hub_setup_deps_stub.h"
4+ 
5+#include <limits.h>
6+#include <string.h>
7+ 
8+#include "iotc_errcode.h"
9+#include "iotc_json.h"
10+#include "iotc_md.h"
11+#include "securec.h"
12+#include "utils_json.h"
13+ 
14+static DevHubInfo g_hubInfo;
15+static char g_domainId[DOMAIN_ID_MAX_LEN + 1];
16+static char g_devId[DEVICE_ID_MAX_STR_LEN + 1];
17+static int32_t g_saveCount;
18+ 
19+void HubSetupDepsReset(void)
20+{
21+ (void)memset(&g_hubInfo, 0, sizeof(g_hubInfo));
22+ (void)memset(g_domainId, 0, sizeof(g_domainId));
23+ (void)memset(g_devId, 0, sizeof(g_devId));
24+ g_saveCount = 0;
25+}
26+ 
27+const DevHubInfo *HubSetupDepsGetHubInfo(void)
28+{
29+ return &g_hubInfo;
30+}
31+ 
32+const char *HubSetupDepsGetDomainId(void)
33+{
34+ return g_domainId;
35+}
36+ 
37+const char *HubSetupDepsGetDevId(void)
38+{
39+ return g_devId;
40+}
41+ 
42+int32_t HubSetupDepsGetSaveCount(void)
43+{
44+ return g_saveCount;
45+}
46+ 
47+int32_t ConfigSaveHubInfo(const DevHubInfo *info)
48+{
49+ if (info == NULL || memcpy_s(&g_hubInfo, sizeof(g_hubInfo), info, sizeof(*info)) != EOK) {
50+ return IOTC_ERR_PARAM_INVALID;
51+ }
52+ ++g_saveCount;
53+ return IOTC_OK;
54+}
55+ 
56+int32_t ConfigSaveDomainId(const char *domainId, uint32_t domainIdLen)
57+{
58+ if (domainId == NULL || domainIdLen >= sizeof(g_domainId) ||
59+ memcpy_s(g_domainId, sizeof(g_domainId), domainId, domainIdLen) != EOK) {
60+ return IOTC_ERR_PARAM_INVALID;
61+ }
62+ g_domainId[domainIdLen] = '\0';
63+ return IOTC_OK;
64+}
65+ 
66+int32_t ConfigSaveDevId(const char *devId, uint32_t devIdLen)
67+{
68+ if (devId == NULL || devIdLen >= sizeof(g_devId) ||
69+ memcpy_s(g_devId, sizeof(g_devId), devId, devIdLen) != EOK) {
70+ return IOTC_ERR_PARAM_INVALID;
71+ }
72+ g_devId[devIdLen] = '\0';
73+ return IOTC_OK;
74+}
75+ 
76+int32_t UtilsJsonGetString(const IotcJson *json, const char *key, char *buf, uint32_t len)
77+{
78+ if (json == NULL || key == NULL || buf == NULL || len == 0) {
79+ return IOTC_ERR_PARAM_INVALID;
80+ }
81+ const char *value = IotcJsonGetStr(IotcJsonGetObj(json, key));
82+ if (value == NULL) {
83+ return IOTC_ADAPTER_JSON_ERR_GET_STRING;
84+ }
85+ return (strcpy_s(buf, len, value) == EOK) ? IOTC_OK : IOTC_ERR_SECUREC_STRCPY;
86+}
87+ 
88+int32_t UtilsJsonGetNum(const IotcJson *json, const char *name, int32_t *num)
89+{
90+ if (json == NULL || name == NULL || num == NULL) {
91+ return IOTC_ERR_PARAM_INVALID;
92+ }
93+ int64_t value = 0;
94+ int32_t ret = IotcJsonGetNum(IotcJsonGetObj(json, name), &value);
95+ if (ret != IOTC_OK) {
96+ return ret;
97+ }
98+ if (value < INT32_MIN || value > INT32_MAX) {
99+ return IOTC_CORE_COMM_UTILS_ERR_JSON_INVALID_ERRCODE;
100+ }
101+ *num = (int32_t)value;
102+ return IOTC_OK;
103+}
104+ 
105+int32_t IotcMdCalc(IotcMdType type, const uint8_t *inData, uint32_t inLen, uint8_t *md, uint32_t mdLen)
106+{
107+ if (type != IOTC_MD_SHA256 || inData == NULL || inLen == 0 || md == NULL ||
108+ mdLen != IOTC_MD_SHA256_BYTE_LEN) {
109+ return IOTC_ERR_PARAM_INVALID;
110+ }
111+ uint32_t hash = 2166136261U;
112+ for (uint32_t i = 0; i < inLen; ++i) {
113+ hash = (hash ^ inData[i]) * 16777619U;
114+ }
115+ for (uint32_t i = 0; i < mdLen; ++i) {
116+ hash ^= hash << 13;
117+ hash ^= hash >> 17;
118+ hash ^= hash << 5;
119+ md[i] = (uint8_t)(hash >> ((i % 4U) * 8U));
120+ }
121+ return IOTC_OK;
122+}
Atest/unit/hub_setup_host/stubs/hub_setup_deps_stub.h+16-0
@@ -0,0 +1,16 @@
1+/* Host-test controls for HubSetup dependencies. */
2+ 
3+#ifndef IOTC_TEST_HUB_SETUP_DEPS_STUB_H
4+#define IOTC_TEST_HUB_SETUP_DEPS_STUB_H
5+ 
6+#include <stdint.h>
7+ 
8+#include "iotc_svc_dev.h"
9+ 
10+void HubSetupDepsReset(void);
11+const DevHubInfo *HubSetupDepsGetHubInfo(void);
12+const char *HubSetupDepsGetDomainId(void);
13+const char *HubSetupDepsGetDevId(void);
14+int32_t HubSetupDepsGetSaveCount(void);
15+ 
16+#endif /* IOTC_TEST_HUB_SETUP_DEPS_STUB_H */
Atest/unit/hub_setup_host/test_hub_setup.c+167-0
@@ -0,0 +1,167 @@
1+/* HubSetup strict parsing, idempotency, persistence and recovery tests. */
2+ 
3+#include <stdio.h>
4+#include <string.h>
5+ 
6+#include "device_state_store.h"
7+#include "hub_setup_deps_stub.h"
8+#include "iotc_errcode.h"
9+#include "iotc_json.h"
10+#include "iotc_kv_fake.h"
11+#include "svc_hub_setup.h"
12+#include "test_framework.h"
13+#include "versioned_store.h"
14+ 
15+int g_testChecks;
16+int g_testFailures;
17+ 
18+static const char *VALID_REQUEST =
19+ "{\"domainId\":\"mdid-0001\",\"devId\":\"temp-dev-0001\",\"code\":\"reg-code-0001\","
20+ "\"ssid\":\"lab-wifi\",\"password\":\"wifi-secret-123\",\"channel\":6,"
21+ "\"bssid\":\"AA:bb:02:03:04:05\"}";
22+ 
23+static void ResetFixture(void)
24+{
25+ (void)DeviceServiceAbortHubBindInfo();
26+ KvFakeReset();
27+ HubSetupDepsReset();
28+}
29+ 
30+static int32_t PrepareText(const char *text, bool *alreadyCommitted)
31+{
32+ IotcJson *json = IotcJsonParse(text);
33+ CHECK(json != NULL);
34+ if (json == NULL) {
35+ return IOTC_ADAPTER_JSON_ERR_PARSE;
36+ }
37+ int32_t ret = DeviceServicePrepareHubBindInfo(json, alreadyCommitted);
38+ IotcJsonDelete(json);
39+ return ret;
40+}
41+ 
42+static void CheckRejected(const char *text)
43+{
44+ bool alreadyCommitted = false;
45+ CHECK(PrepareText(text, &alreadyCommitted) != IOTC_OK);
46+ CHECK(!alreadyCommitted);
47+ CHECK_EQ_INT(IOTC_OK, DeviceServiceAbortHubBindInfo());
48+}
49+ 
50+static void TestStrictFields(void)
51+{
52+ ResetFixture();
53+ static const char *invalid[] = {
54+ "{\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\"}",
55+ "{\"domainId\":\"m\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\"}",
56+ "{\"domainId\":\"m\",\"devId\":\"d\",\"ssid\":\"s\",\"password\":\"p\"}",
57+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"password\":\"p\"}",
58+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\"}",
59+ "{\"domainId\":\"\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\"}",
60+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\",\"channel\":0}",
61+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\",\"channel\":197}",
62+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\",\"channel\":\"6\"}",
63+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\",\"channel\":-2147483649}",
64+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\",\"bssid\":\"AA:BB:CC:DD:EE\"}",
65+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\",\"bssid\":\"GG:BB:CC:DD:EE:FF\"}",
66+ };
67+ for (uint32_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); ++i) {
68+ CheckRejected(invalid[i]);
69+ }
70+ 
71+ char longDomain[42] = {0};
72+ (void)memset(longDomain, 'd', sizeof(longDomain) - 1);
73+ char request[256] = {0};
74+ (void)snprintf(request, sizeof(request),
75+ "{\"domainId\":\"%s\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\"}",
76+ longDomain);
77+ CheckRejected(request);
78+ 
79+ bool alreadyCommitted = true;
80+ CHECK_EQ_INT(IOTC_OK, PrepareText(
81+ "{\"domainId\":\"m\",\"devId\":\"d\",\"code\":\"c\",\"ssid\":\"s\",\"password\":\"p\"}",
82+ &alreadyCommitted));
83+ CHECK(!alreadyCommitted);
84+ CHECK_EQ_INT(IOTC_OK, DeviceServiceAbortHubBindInfo());
85+}
86+ 
87+static void TestCommitAndNoPasswordInStateStore(void)
88+{
89+ ResetFixture();
90+ bool alreadyCommitted = true;
91+ CHECK_EQ_INT(IOTC_OK, PrepareText(VALID_REQUEST, &alreadyCommitted));
92+ CHECK(!alreadyCommitted);
93+ 
94+ IotcDssProvisionPending pending = {0};
95+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND, IotcDssLoadProvisionPending(&pending));
96+ CHECK_EQ_INT(IOTC_OK, DeviceServiceCommitHubBindInfo());
97+ CHECK_EQ_INT(IOTC_OK, IotcDssLoadProvisionPending(&pending));
98+ CHECK_EQ_INT(9, pending.mdidLen);
99+ CHECK(memcmp(pending.mdid, "mdid-0001", pending.mdidLen) == 0);
100+ CHECK(strcmp(pending.tempDevId, "temp-dev-0001") == 0);
101+ CHECK(strcmp(pending.registerCode, "reg-code-0001") == 0);
102+ CHECK_EQ_INT(0, KvFakeContainsBytes((const uint8_t *)"wifi-secret-123", 15));
103+ 
104+ uint32_t generation = 0;
105+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(IOTC_DSS_DOMAIN_PROVISION_PENDING, &generation));
106+ CHECK_EQ_INT(1, generation);
107+ CHECK(strcmp(HubSetupDepsGetDomainId(), "mdid-0001") == 0);
108+ CHECK(strcmp(HubSetupDepsGetDevId(), "temp-dev-0001") == 0);
109+ CHECK(strcmp(HubSetupDepsGetHubInfo()->ssid, "lab-wifi") == 0);
110+ CHECK(strcmp(HubSetupDepsGetHubInfo()->password, "wifi-secret-123") == 0);
111+}
112+ 
113+static void TestIdempotencyAndConflict(void)
114+{
115+ ResetFixture();
116+ bool alreadyCommitted = false;
117+ CHECK_EQ_INT(IOTC_OK, PrepareText(VALID_REQUEST, &alreadyCommitted));
118+ CHECK_EQ_INT(IOTC_OK, DeviceServiceCommitHubBindInfo());
119+ 
120+ int32_t saveCount = HubSetupDepsGetSaveCount();
121+ CHECK_EQ_INT(IOTC_OK, PrepareText(VALID_REQUEST, &alreadyCommitted));
122+ CHECK(alreadyCommitted);
123+ CHECK_EQ_INT(saveCount + 1, HubSetupDepsGetSaveCount());
124+ uint32_t generation = 0;
125+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(IOTC_DSS_DOMAIN_PROVISION_PENDING, &generation));
126+ CHECK_EQ_INT(1, generation);
127+ 
128+ const char *conflict =
129+ "{\"domainId\":\"mdid-0001\",\"devId\":\"temp-dev-0001\",\"code\":\"reg-code-0001\","
130+ "\"ssid\":\"lab-wifi\",\"password\":\"different-secret\",\"channel\":6,"
131+ "\"bssid\":\"AA:bb:02:03:04:05\"}";
132+ alreadyCommitted = false;
133+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_STATE, PrepareText(conflict, &alreadyCommitted));
134+ CHECK(!alreadyCommitted);
135+ CHECK_EQ_INT(IOTC_OK, IotcVsGetGeneration(IOTC_DSS_DOMAIN_PROVISION_PENDING, &generation));
136+ CHECK_EQ_INT(1, generation);
137+}
138+ 
139+static void TestAbortAndRecovery(void)
140+{
141+ ResetFixture();
142+ bool alreadyCommitted = false;
143+ CHECK_EQ_INT(IOTC_OK, PrepareText(VALID_REQUEST, &alreadyCommitted));
144+ CHECK_EQ_INT(IOTC_OK, DeviceServiceAbortHubBindInfo());
145+ IotcDssProvisionPending pending = {0};
146+ CHECK_EQ_INT(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND, IotcDssLoadProvisionPending(&pending));
147+ 
148+ CHECK_EQ_INT(IOTC_OK, PrepareText(VALID_REQUEST, &alreadyCommitted));
149+ CHECK_EQ_INT(IOTC_OK, DeviceServiceCommitHubBindInfo());
150+ HubSetupDepsReset();
151+ CHECK_EQ_INT(IOTC_OK, DeviceServiceRecoverHubBindInfo());
152+ CHECK(strcmp(HubSetupDepsGetDomainId(), "mdid-0001") == 0);
153+ CHECK(strcmp(HubSetupDepsGetDevId(), "temp-dev-0001") == 0);
154+ CHECK(strcmp(HubSetupDepsGetHubInfo()->code, "reg-code-0001") == 0);
155+ CHECK(HubSetupDepsGetHubInfo()->ssid[0] == '\0');
156+ CHECK(HubSetupDepsGetHubInfo()->password[0] == '\0');
157+}
158+ 
159+int main(void)
160+{
161+ TestStrictFields();
162+ TestCommitAndNoPasswordInStateStore();
163+ TestIdempotencyAndConflict();
164+ TestAbortAndRecovery();
165+ printf("HubSetup host checks=%d failures=%d\n", g_testChecks, g_testFailures);
166+ return (g_testFailures == 0) ? 0 : 1;
167+}
Mtest/unit/iotc_speke_v2_api_unit_test.c+572-9
@@ -23,6 +23,7 @@
23#include "iotc_mem.h"23#include "iotc_mem.h"
24#include "security_random.h"24#include "security_random.h"
25#include "security_speke.h"25#include "security_speke.h"
26+#include "security_speke_defs.h"
26 27 
27static int g_failures;28static int g_failures;
28 29 
@@ -87,19 +88,161 @@ uint32_t SecurityRandomUint32(void)
87 return value;88 return value;
88}89}
89 90 
90-/* Cross-backend reference vectors: produced by the deterministic LCG run on91+/* Cross-backend reference vectors for both supported base encodings. The
91- * the mbedTLS backend and independently reproduced byte-for-byte by the92+ * little-endian variant follows the RFC 7748 X25519 wire encoding; the
92- * openHiTLS backend. Each ciphertext covers the negotiated key and the GCM93+ * default big-endian variant preserves the deployed configurator contract.
93- * path end-to-end (version byte + IV + ciphertext + tag). */94+ * Both variants otherwise use the 2026-03-11 Appendix B algorithm:
95+ * PBKDF2-HMAC-SHA512 96B -> two ELL2 maps -> pointAdd -> clear_cofactor(8)
96+ * -> X25519 -> HKDF-SHA256 -> AES-128-GCM. Each ciphertext covers the
97+ * negotiated key and GCM path end-to-end. */
98+#if IOTC_CONF_SPEKE_BASE_LITTLE_ENDIAN
94static const char *VEC_ENC_C2S =99static const char *VEC_ENC_C2S =
95- "004d4314c0b59687cb950f8f9ed435b4c346420ef2fdcc03ac9c6ef296e0"100+ "004d4314c0b59687cb950f8f9e15981d65b5520475d6185ebbb4d7d827e999d0"
96- "2bce908cba0e152fdbf4e0f079e87be45e34015f0f9ee0ecf2d6c32b8b4571";101+ "59470a888d563f4a16a97defb81dd0c935b467a5ba73e9fa1b6425b0f9";
97static const char *VEC_ENC_S2C =102static const char *VEC_ENC_S2C =
98- "0079e2ffc8fd789cfd0afbc08067adfcbefeedbadfc76cf7c9b896b4e6c7"103+ "0079e2ffc8fd789cfd0afbc080ffc1be38c0915c03bec23453f28b16f611cd00"
99- "f894934c571d379b8df554ad5272b0857df8738e252e2c707962b7b5d83c18";104+ "65e455cc44fac077ca39e15700a238d76728293191eaa3f064f23dced7";
105+#else
106+static const char *VEC_ENC_C2S =
107+ "004d4314c0b59687cb950f8f9e14cfe940fff4fabbe0b196d247f0280e522a07"
108+ "0690676ae8da1693d0495d6dcbcda3d61bd6a65eeb0bd2dec35a506c5d";
109+static const char *VEC_ENC_S2C =
110+ "0079e2ffc8fd789cfd0afbc08096e9879c7fbec2164d7d1d18192e4c6b3b816"
111+ "cd9d52bd6527a21c6a7e3653b8b49e1cb5d3551e7591689b7627313a135";
112+#endif
113+ 
114+/* Deterministic message vectors: same LCG run as above, expected byte-for-byte
115+ * message1..4 JSON payloads. Field values (salt/challenge/pk/kcfData, upper
116+ * case hex per the 2026-03-11 spec message tables) come from the same
117+ * independent reference run, not from the implementation under test. */
118+static const char *VEC_MSG1 =
119+ "{\"version\":\"2.0\",\"sessionId\":\"414d180a3571de14f241770bea0537b5\",\"msgId\":\"spekeMsg1\"}";
120+#if IOTC_CONF_SPEKE_BASE_LITTLE_ENDIAN
121+static const char *VEC_MSG2 =
122+ "{\"challenge1\":\"6819A8E6CAA4E23B69BD418941DA1EDC\","
123+ "\"salt\":\"71471D94EC8993C744BCD8CFCB3CC5A6\","
124+ "\"pk1\":\"72148856A25C87E3B185A49D681A7D77FB58B0FAB6A6BF6FC12C18E8C9EB2068\","
125+ "\"iterations\":10000,"
126+ "\"sessionId\":\"414d180a3571de14f241770bea0537b5\",\"msgId\":\"spekeMsg2\"}";
127+static const char *VEC_MSG3 =
128+ "{\"challenge2\":\"DD78A93A16592A906BB244A8F2E6CB5A\","
129+ "\"pk2\":\"DA7DF5FCF7FB948A9875E4B731A5C61D11C35A296D6B3D3BC99A020C6A11A324\","
130+ "\"kcfData2\":\"B44B4AAA550A64CB26034A10590C5CA728CEC2914AF709E8FB3F610398294884\","
131+ "\"sessionId\":\"414d180a3571de14f241770bea0537b5\",\"msgId\":\"spekeMsg3\"}";
132+static const char *VEC_MSG4 =
133+ "{\"kcfData1\":\"BA010DF161C31BD4FA9D645728B08B96D464EC23D1A534C284889B940EA97C90\","
134+ "\"sessionId\":\"414d180a3571de14f241770bea0537b5\",\"msgId\":\"spekeMsg4\"}";
135+#else
136+static const char *VEC_MSG2 =
137+ "{\"challenge1\":\"6819A8E6CAA4E23B69BD418941DA1EDC\","
138+ "\"salt\":\"71471D94EC8993C744BCD8CFCB3CC5A6\","
139+ "\"pk1\":\"3CB7F9E5D1818A493BBA5F9F566106048E53FAE824E62A53F4F722EF54D95F16\","
140+ "\"iterations\":10000,"
141+ "\"sessionId\":\"414d180a3571de14f241770bea0537b5\",\"msgId\":\"spekeMsg2\"}";
142+static const char *VEC_MSG3 =
143+ "{\"challenge2\":\"DD78A93A16592A906BB244A8F2E6CB5A\","
144+ "\"pk2\":\"B001DF1F9964499479A6BFDDA10F58488C11414DB01E0D78D4A6B5ACA3484672\","
145+ "\"kcfData2\":\"E5EDFCC0828A6344A0A254730364531C2C38C18E1919C439DBCCAA8B67A21DAB\","
146+ "\"sessionId\":\"414d180a3571de14f241770bea0537b5\",\"msgId\":\"spekeMsg3\"}";
147+static const char *VEC_MSG4 =
148+ "{\"kcfData1\":\"276BD735EEEB76F66D6EC194AFC486A36C6940E714F70554A3D85B8A33FB78ED\","
149+ "\"sessionId\":\"414d180a3571de14f241770bea0537b5\",\"msgId\":\"spekeMsg4\"}";
150+#endif
100 151 
101#define HEX_CHARS_PER_BYTE 2152#define HEX_CHARS_PER_BYTE 2
102#define HEX_BYTE_BUF_SIZE 3153#define HEX_BYTE_BUF_SIZE 3
154+#define TEST_GCM_TAG_LEN 16
155+ 
156+static void ExpectStrEq(const char *actual, const char *expected, const char *label)
157+{
158+ if ((actual == NULL) || (expected == NULL) || (strcmp(actual, expected) != 0)) {
159+ g_failures++;
160+ (void)fprintf(stderr, "%s mismatch:\n got %s\n expected %s\n", label,
161+ (actual != NULL) ? actual : "(null)", (expected != NULL) ? expected : "(null)");
162+ }
163+}
164+ 
165+/* Flip one hex digit inside a JSON string field value (always changes the
166+ * value, always stays a valid hex character). */
167+static void FlipHexCharInField(char *msg, const char *field)
168+{
169+ char anchor[40];
170+ (void)snprintf_s(anchor, sizeof(anchor), sizeof(anchor) - 1, "\"%s\":\"", field);
171+ char *pos = strstr(msg, anchor);
172+ if (pos == NULL) {
173+ g_failures++;
174+ (void)fprintf(stderr, "field %s not found in message\n", field);
175+ return;
176+ }
177+ pos += strlen(anchor);
178+ *pos = *pos ^ 0x01U;
179+}
180+ 
181+static char *DupMsg(const uint8_t *msg, uint32_t len)
182+{
183+ if ((msg == NULL) || (len == 0)) {
184+ return NULL;
185+ }
186+ char *copy = (char *)IotcMalloc(len + 1);
187+ if (copy == NULL) {
188+ g_failures++;
189+ return NULL;
190+ }
191+ if (memcpy_s(copy, len + 1, msg, len) != EOK) {
192+ IotcFree(copy);
193+ g_failures++;
194+ return NULL;
195+ }
196+ copy[len] = '\0';
197+ return copy;
198+}
199+ 
200+static char *ReplaceSubstring(const char *msg, const char *from, const char *to)
201+{
202+ const char *pos = strstr(msg, from);
203+ if (pos == NULL) {
204+ g_failures++;
205+ return NULL;
206+ }
207+ size_t prefixLen = (size_t)(pos - msg);
208+ size_t newLen = strlen(msg) - strlen(from) + strlen(to);
209+ char *copy = (char *)IotcMalloc(newLen + 1);
210+ if (copy == NULL) {
211+ g_failures++;
212+ return NULL;
213+ }
214+ if (memcpy_s(copy, newLen + 1, msg, prefixLen) != EOK ||
215+ memcpy_s(copy + prefixLen, newLen + 1 - prefixLen, to, strlen(to)) != EOK ||
216+ strcpy_s(copy + prefixLen + strlen(to), newLen + 1 - prefixLen - strlen(to),
217+ pos + strlen(from)) != EOK) {
218+ IotcFree(copy);
219+ g_failures++;
220+ return NULL;
221+ }
222+ return copy;
223+}
224+ 
225+/* Negotiation must not produce a usable session before SPEKE_STATE_DONE:
226+ * both encrypt and decrypt have to be rejected. */
227+static void ExpectCryptoRejected(SpekeSession *session, const char *label)
228+{
229+ static const uint8_t dummyPlain[] = "speke v2 state gate probe";
230+ static const uint8_t dummyBlob[40] = { 0 };
231+ uint8_t *out = NULL;
232+ uint32_t outLen = 0;
233+ if (SpekeEncryptData(session, dummyPlain, sizeof(dummyPlain) - 1, &out, &outLen) == IOTC_OK) {
234+ g_failures++;
235+ (void)fprintf(stderr, "%s: encrypt unexpectedly succeeded before nego done\n", label);
236+ IotcFree(out);
237+ }
238+ out = NULL;
239+ outLen = 0;
240+ if (SpekeDecryptData(session, dummyBlob, sizeof(dummyBlob), &out, &outLen) == IOTC_OK) {
241+ g_failures++;
242+ (void)fprintf(stderr, "%s: decrypt unexpectedly succeeded before nego done\n", label);
243+ IotcFree(out);
244+ }
245+}
103 246 
104static void ExpectHexEq(const uint8_t *buf, uint32_t len, const char *expectedHex)247static void ExpectHexEq(const uint8_t *buf, uint32_t len, const char *expectedHex)
105{248{
@@ -274,12 +417,27 @@ static void CheckDataRoundtrip(SpekeSession *sender, SpekeSession *receiver, con
274 DumpHex(tc->dumpLabel, enc, encLen);417 DumpHex(tc->dumpLabel, enc, encLen);
275 }418 }
276 419 
277- /* GCM tag corruption must be rejected */420+ /* GCM tag corruption must be rejected and no (partial) plaintext delivered */
278 enc[encLen - 1] ^= 0x01U;421 enc[encLen - 1] ^= 0x01U;
279 uint8_t *bad = NULL;422 uint8_t *bad = NULL;
280 uint32_t badLen = 0;423 uint32_t badLen = 0;
281 ExpectTrue(SpekeDecryptData(receiver, enc, encLen, &bad, &badLen) != IOTC_OK);424 ExpectTrue(SpekeDecryptData(receiver, enc, encLen, &bad, &badLen) != IOTC_OK);
282 ExpectTrue(bad == NULL);425 ExpectTrue(bad == NULL);
426+ enc[encLen - 1] ^= 0x01U; /* restore tag */
427+ 
428+ /* GCM ciphertext corruption must equally be rejected (tag check fails) */
429+ enc[encLen - TEST_GCM_TAG_LEN - 1] ^= 0x01U;
430+ ExpectTrue(SpekeDecryptData(receiver, enc, encLen, &bad, &badLen) != IOTC_OK);
431+ ExpectTrue(bad == NULL);
432+ enc[encLen - TEST_GCM_TAG_LEN - 1] ^= 0x01U; /* restore */
433+ 
434+ /* 版本字节不参与 GCM tag,必须单独校验,不能接受未知封装版本。 */
435+ enc[0] = 1;
436+ ExpectEqInt(SpekeDecryptData(receiver, enc, encLen, &bad, &badLen),
437+ IOTC_CORE_COMM_SEC_ERR_SPEKE_GCM_VERSION);
438+ ExpectTrue(bad == NULL);
439+ ExpectEqInt(badLen, 0);
440+ enc[0] = 0;
283 441 
284 IotcFree(enc);442 IotcFree(enc);
285}443}
@@ -334,11 +492,408 @@ static void TestSpekeApiWrongPin(void)
334 ExpectEqInt(serverUser.finishCount, 1);492 ExpectEqInt(serverUser.finishCount, 1);
335 ExpectTrue(serverUser.finishErr != IOTC_OK);493 ExpectTrue(serverUser.finishErr != IOTC_OK);
336 ExpectEqInt(clientUser.finishCount, 0);494 ExpectEqInt(clientUser.finishCount, 0);
495+ /* failed negotiation must not leave a usable session on either end */
496+ ExpectCryptoRejected(server, "wrongpin server");
497+ ExpectCryptoRejected(client, "wrongpin client");
337 498 
338 SpekeFreeSession(client);499 SpekeFreeSession(client);
339 SpekeFreeSession(server);500 SpekeFreeSession(server);
340}501}
341 502 
503+/* Both ends hold different (i.e. mutually wrong) PINs: negotiation must fail
504+ * and neither side may end up with a usable session. */
505+static void TestSpekeApiBothWrongPin(void)
506+{
507+ static const uint8_t PIN_WRONG_SERVER[] = "999999";
508+ SpekeTestUser serverUser = { PIN_WRONG_SERVER, sizeof(PIN_WRONG_SERVER) - 1, 0, 0, -1 };
509+ SpekeTestUser clientUser = { PIN_WRONG, sizeof(PIN_WRONG) - 1, 0, 0, -1 };
510+ 
511+ SpekeSession *server = SpekeInitSession(SPEKE_TYPE_SERVER, &g_testCb, &serverUser);
512+ SpekeSession *client = SpekeInitSession(SPEKE_TYPE_CLIENT, &g_testCb, &clientUser);
513+ ExpectTrue(server != NULL && client != NULL);
514+ if (server == NULL || client == NULL) {
515+ return;
516+ }
517+ 
518+ int32_t serverCfmRet = -1;
519+ bool ok = RunNegotiation(client, server, &clientUser, &serverUser, &serverCfmRet);
520+ ExpectTrue(!ok);
521+ ExpectTrue(serverCfmRet != IOTC_OK);
522+ ExpectEqInt(serverUser.finishCount, 1);
523+ ExpectTrue(serverUser.finishErr != IOTC_OK);
524+ ExpectEqInt(clientUser.finishCount, 0);
525+ ExpectCryptoRejected(server, "bothwrong server");
526+ ExpectCryptoRejected(client, "bothwrong client");
527+ 
528+ SpekeFreeSession(client);
529+ SpekeFreeSession(server);
530+}
531+ 
532+/* Deterministic vectors: fixed LCG run, message1..4 must match the reference
533+ * payloads byte-for-byte (field values independently computed from the spec). */
534+static void TestSpekeApiDeterministicMessages(void)
535+{
536+ g_rngState = 0x12345678U;
537+ SpekeTestUser serverUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
538+ SpekeTestUser clientUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
539+ 
540+ SpekeSession *server = SpekeInitSession(SPEKE_TYPE_SERVER, &g_testCb, &serverUser);
541+ SpekeSession *client = SpekeInitSession(SPEKE_TYPE_CLIENT, &g_testCb, &clientUser);
542+ ExpectTrue(server != NULL && client != NULL);
543+ if (server == NULL || client == NULL) {
544+ return;
545+ }
546+ 
547+ uint8_t *msg1 = NULL;
548+ uint32_t msg1Len = 0;
549+ ExpectEqInt(SpekeStartSession(client, &msg1, &msg1Len), IOTC_OK);
550+ ExpectStrEq((const char *)msg1, VEC_MSG1, "msg1");
551+ 
552+ uint8_t *msg2 = NULL;
553+ uint32_t msg2Len = 0;
554+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg1, &msg2, &msg2Len), IOTC_OK);
555+ ExpectStrEq((const char *)msg2, VEC_MSG2, "msg2");
556+ IotcFree(msg1);
557+ 
558+ uint8_t *msg3 = NULL;
559+ uint32_t msg3Len = 0;
560+ ExpectEqInt(SpekeProcessPacket(client, (const char *)msg2, &msg3, &msg3Len), IOTC_OK);
561+ ExpectStrEq((const char *)msg3, VEC_MSG3, "msg3");
562+ IotcFree(msg2);
563+ 
564+ uint8_t *msg4 = NULL;
565+ uint32_t msg4Len = 0;
566+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg3, &msg4, &msg4Len), IOTC_OK);
567+ ExpectStrEq((const char *)msg4, VEC_MSG4, "msg4");
568+ IotcFree(msg3);
569+ IotcFree(msg4);
570+ 
571+ SpekeFreeSession(client);
572+ SpekeFreeSession(server);
573+}
574+ 
575+/* Single-byte tampering of protected message fields must fail negotiation. */
576+static void TestSpekeApiMsgTamper(void)
577+{
578+ SpekeTestUser serverUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
579+ SpekeTestUser clientUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
580+ SpekeSession *server = SpekeInitSession(SPEKE_TYPE_SERVER, &g_testCb, &serverUser);
581+ SpekeSession *client = SpekeInitSession(SPEKE_TYPE_CLIENT, &g_testCb, &clientUser);
582+ ExpectTrue(server != NULL && client != NULL);
583+ if (server == NULL || client == NULL) {
584+ return;
585+ }
586+ 
587+ uint8_t *msg1 = NULL;
588+ uint32_t msg1Len = 0;
589+ ExpectEqInt(SpekeStartSession(client, &msg1, &msg1Len), IOTC_OK);
590+ uint8_t *msg2 = NULL;
591+ uint32_t msg2Len = 0;
592+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg1, &msg2, &msg2Len), IOTC_OK);
593+ IotcFree(msg1);
594+ 
595+ /* msg2: tamper one pk1 hex digit. The client cannot detect it, but the
596+ * derived keys diverge, so the server must reject the resulting msg3. */
597+ char *badMsg2 = DupMsg(msg2, msg2Len);
598+ FlipHexCharInField(badMsg2, SPEKE_PK1_JSON);
599+ uint8_t *cfmBadPk1 = NULL;
600+ uint32_t cfmBadPk1Len = 0;
601+ ExpectEqInt(SpekeProcessPacket(client, badMsg2, &cfmBadPk1, &cfmBadPk1Len), IOTC_OK);
602+ IotcFree(badMsg2);
603+ ExpectTrue(cfmBadPk1 != NULL);
604+ if (cfmBadPk1 != NULL) {
605+ uint8_t *rej = NULL;
606+ uint32_t rejLen = 0;
607+ ExpectTrue(SpekeProcessPacket(server, (const char *)cfmBadPk1, &rej, &rejLen) != IOTC_OK);
608+ ExpectTrue(rej == NULL);
609+ ExpectEqInt(serverUser.finishCount, 1);
610+ ExpectTrue(serverUser.finishErr != IOTC_OK);
611+ ExpectEqInt(clientUser.finishCount, 0);
612+ IotcFree(cfmBadPk1);
613+ }
614+ ExpectCryptoRejected(server, "tamper-pk1 server");
615+ ExpectCryptoRejected(client, "tamper-pk1 client");
616+ SpekeFreeSession(client);
617+ SpekeFreeSession(server);
618+ 
619+ /* msg3: tamper kcfData2 / pk2, server must reject immediately. */
620+ static const char *msg3Fields[] = { SPEKE_KCF_DATA2_JSON, SPEKE_PK2_JSON, SPEKE_CHALLENGE2_JSON };
621+ for (uint32_t i = 0; i < sizeof(msg3Fields) / sizeof(msg3Fields[0]); i++) {
622+ serverUser = (SpekeTestUser){ PIN, sizeof(PIN) - 1, 0, 0, -1 };
623+ clientUser = (SpekeTestUser){ PIN, sizeof(PIN) - 1, 0, 0, -1 };
624+ server = SpekeInitSession(SPEKE_TYPE_SERVER, &g_testCb, &serverUser);
625+ client = SpekeInitSession(SPEKE_TYPE_CLIENT, &g_testCb, &clientUser);
626+ ExpectTrue(server != NULL && client != NULL);
627+ if (server == NULL || client == NULL) {
628+ return;
629+ }
630+ msg1 = NULL;
631+ msg1Len = 0;
632+ ExpectEqInt(SpekeStartSession(client, &msg1, &msg1Len), IOTC_OK);
633+ msg2 = NULL;
634+ msg2Len = 0;
635+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg1, &msg2, &msg2Len), IOTC_OK);
636+ IotcFree(msg1);
637+ uint8_t *msg3 = NULL;
638+ uint32_t msg3Len = 0;
639+ ExpectEqInt(SpekeProcessPacket(client, (const char *)msg2, &msg3, &msg3Len), IOTC_OK);
640+ IotcFree(msg2);
641+ char *badMsg3 = DupMsg(msg3, msg3Len);
642+ IotcFree(msg3);
643+ FlipHexCharInField(badMsg3, msg3Fields[i]);
644+ uint8_t *rej = NULL;
645+ uint32_t rejLen = 0;
646+ ExpectTrue(SpekeProcessPacket(server, badMsg3, &rej, &rejLen) != IOTC_OK);
647+ ExpectTrue(rej == NULL);
648+ ExpectEqInt(serverUser.finishCount, 1);
649+ ExpectTrue(serverUser.finishErr != IOTC_OK);
650+ ExpectEqInt(clientUser.finishCount, 0);
651+ IotcFree(badMsg3);
652+ ExpectCryptoRejected(server, "tamper-msg3 server");
653+ SpekeFreeSession(client);
654+ SpekeFreeSession(server);
655+ }
656+ 
657+ /* msg4: tamper kcfData1, the client must reject and fail negotiation. */
658+ serverUser = (SpekeTestUser){ PIN, sizeof(PIN) - 1, 0, 0, -1 };
659+ clientUser = (SpekeTestUser){ PIN, sizeof(PIN) - 1, 0, 0, -1 };
660+ server = SpekeInitSession(SPEKE_TYPE_SERVER, &g_testCb, &serverUser);
661+ client = SpekeInitSession(SPEKE_TYPE_CLIENT, &g_testCb, &clientUser);
662+ ExpectTrue(server != NULL && client != NULL);
663+ if (server == NULL || client == NULL) {
664+ return;
665+ }
666+ msg1 = NULL;
667+ msg1Len = 0;
668+ ExpectEqInt(SpekeStartSession(client, &msg1, &msg1Len), IOTC_OK);
669+ msg2 = NULL;
670+ msg2Len = 0;
671+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg1, &msg2, &msg2Len), IOTC_OK);
672+ IotcFree(msg1);
673+ uint8_t *msg3ok = NULL;
674+ uint32_t msg3okLen = 0;
675+ ExpectEqInt(SpekeProcessPacket(client, (const char *)msg2, &msg3ok, &msg3okLen), IOTC_OK);
676+ IotcFree(msg2);
677+ uint8_t *msg4ok = NULL;
678+ uint32_t msg4okLen = 0;
679+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg3ok, &msg4ok, &msg4okLen), IOTC_OK);
680+ IotcFree(msg3ok);
681+ char *badMsg4 = DupMsg(msg4ok, msg4okLen);
682+ IotcFree(msg4ok);
683+ FlipHexCharInField(badMsg4, SPEKE_KCF_DATA1_JSON);
684+ uint8_t *last = NULL;
685+ uint32_t lastLen = 0;
686+ ExpectTrue(SpekeProcessPacket(client, badMsg4, &last, &lastLen) != IOTC_OK);
687+ ExpectTrue(last == NULL);
688+ ExpectEqInt(clientUser.finishCount, 1);
689+ ExpectTrue(clientUser.finishErr != IOTC_OK);
690+ IotcFree(badMsg4);
691+ ExpectCryptoRejected(client, "tamper-msg4 client");
692+ /* server already completed; a tampered msg4 must not tear its session down */
693+ SpekeFreeSession(client);
694+ SpekeFreeSession(server);
695+}
696+ 
697+/* Replayed negotiation messages must be rejected by the state machine. */
698+static void TestSpekeApiReplay(void)
699+{
700+ SpekeTestUser serverUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
701+ SpekeTestUser clientUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
702+ SpekeSession *server = SpekeInitSession(SPEKE_TYPE_SERVER, &g_testCb, &serverUser);
703+ SpekeSession *client = SpekeInitSession(SPEKE_TYPE_CLIENT, &g_testCb, &clientUser);
704+ ExpectTrue(server != NULL && client != NULL);
705+ if (server == NULL || client == NULL) {
706+ return;
707+ }
708+ 
709+ uint8_t *msg1 = NULL;
710+ uint32_t msg1Len = 0;
711+ ExpectEqInt(SpekeStartSession(client, &msg1, &msg1Len), IOTC_OK);
712+ uint8_t *msg2 = NULL;
713+ uint32_t msg2Len = 0;
714+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg1, &msg2, &msg2Len), IOTC_OK);
715+ 
716+ /* duplicate spekeMsg1 while the server waits for spekeMsg3 must be rejected */
717+ uint8_t *rej = NULL;
718+ uint32_t rejLen = 0;
719+ ExpectTrue(SpekeProcessPacket(server, (const char *)msg1, &rej, &rejLen) != IOTC_OK);
720+ ExpectTrue(rej == NULL);
721+ 
722+ /* duplicate spekeMsg2 while the client waits for spekeMsg4 must be rejected */
723+ char *msg2Copy = DupMsg(msg2, msg2Len);
724+ uint8_t *msg3 = NULL;
725+ uint32_t msg3Len = 0;
726+ ExpectEqInt(SpekeProcessPacket(client, (const char *)msg2, &msg3, &msg3Len), IOTC_OK);
727+ rej = NULL;
728+ rejLen = 0;
729+ ExpectTrue(SpekeProcessPacket(client, msg2Copy, &rej, &rejLen) != IOTC_OK);
730+ ExpectTrue(rej == NULL);
731+ IotcFree(msg2Copy);
732+ 
733+ char *msg3Copy = DupMsg(msg3, msg3Len);
734+ uint8_t *msg4 = NULL;
735+ uint32_t msg4Len = 0;
736+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg3, &msg4, &msg4Len), IOTC_OK);
737+ IotcFree(msg3);
738+ 
739+ /* replayed spekeMsg3 after success must be rejected */
740+ rej = NULL;
741+ rejLen = 0;
742+ ExpectTrue(SpekeProcessPacket(server, msg3Copy, &rej, &rejLen) != IOTC_OK);
743+ ExpectTrue(rej == NULL);
744+ IotcFree(msg3Copy);
745+ 
746+ /* replayed spekeMsg1 after success must be rejected */
747+ rej = NULL;
748+ rejLen = 0;
749+ ExpectTrue(SpekeProcessPacket(server, (const char *)msg1, &rej, &rejLen) != IOTC_OK);
750+ ExpectTrue(rej == NULL);
751+ IotcFree(msg1);
752+ 
753+ /* client completes, then a replayed spekeMsg4 must be rejected */
754+ char *msg4Copy = DupMsg(msg4, msg4Len);
755+ uint8_t *last = NULL;
756+ uint32_t lastLen = 0;
757+ ExpectEqInt(SpekeProcessPacket(client, (const char *)msg4, &last, &lastLen), IOTC_OK);
758+ ExpectTrue(last == NULL);
759+ IotcFree(msg4);
760+ rej = NULL;
761+ rejLen = 0;
762+ ExpectTrue(SpekeProcessPacket(client, msg4Copy, &rej, &rejLen) != IOTC_OK);
763+ ExpectTrue(rej == NULL);
764+ IotcFree(msg4Copy);
765+ 
766+ ExpectEqInt(clientUser.finishCount, 1);
767+ ExpectEqInt(clientUser.finishErr, IOTC_OK);
768+ /* the completed session must still work after all the replay attempts */
769+ static const uint8_t probe[] = "post replay probe";
770+ uint8_t *enc = NULL;
771+ uint32_t encLen = 0;
772+ ExpectEqInt(SpekeEncryptData(client, probe, sizeof(probe) - 1, &enc, &encLen), IOTC_OK);
773+ uint8_t *dec = NULL;
774+ uint32_t decLen = 0;
775+ ExpectEqInt(SpekeDecryptData(server, enc, encLen, &dec, &decLen), IOTC_OK);
776+ if (dec != NULL) {
777+ ExpectEqInt(decLen, sizeof(probe) - 1);
778+ ExpectTrue(memcmp(dec, probe, sizeof(probe) - 1) == 0);
779+ IotcFree(dec);
780+ } else {
781+ g_failures++;
782+ }
783+ IotcFree(enc);
784+ 
785+ SpekeFreeSession(client);
786+ SpekeFreeSession(server);
787+}
788+ 
789+static void TestSpekeApiSessionBinding(void)
790+{
791+ SpekeTestUser serverUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
792+ SpekeTestUser clientUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
793+ SpekeSession *server = SpekeInitSession(SPEKE_TYPE_SERVER, &g_testCb, &serverUser);
794+ SpekeSession *client = SpekeInitSession(SPEKE_TYPE_CLIENT, &g_testCb, &clientUser);
795+ ExpectTrue(server != NULL && client != NULL);
796+ if (server == NULL || client == NULL) {
797+ return;
798+ }
799+ 
800+ uint8_t *msg1 = NULL;
801+ uint32_t msg1Len = 0;
802+ uint8_t *msg2 = NULL;
803+ uint32_t msg2Len = 0;
804+ ExpectEqInt(SpekeStartSession(client, &msg1, &msg1Len), IOTC_OK);
805+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg1, &msg2, &msg2Len), IOTC_OK);
806+ IotcFree(msg1);
807+ 
808+ char *wrong = DupMsg(msg2, msg2Len);
809+ FlipHexCharInField(wrong, SPEKE_SESSION_ID_JSON);
810+ uint8_t *out = (uint8_t *)(uintptr_t)1;
811+ uint32_t outLen = 123;
812+ ExpectEqInt(SpekeProcessPacket(client, wrong, &out, &outLen), IOTC_CORE_COMM_SEC_ERR_SPEKE_SESSION_ID);
813+ ExpectTrue(out == NULL);
814+ ExpectEqInt(outLen, 0);
815+ IotcFree(wrong);
816+ 
817+ uint8_t *msg3 = NULL;
818+ uint32_t msg3Len = 0;
819+ ExpectEqInt(SpekeProcessPacket(client, (const char *)msg2, &msg3, &msg3Len), IOTC_OK);
820+ IotcFree(msg2);
821+ wrong = DupMsg(msg3, msg3Len);
822+ FlipHexCharInField(wrong, SPEKE_SESSION_ID_JSON);
823+ out = (uint8_t *)(uintptr_t)1;
824+ outLen = 123;
825+ ExpectEqInt(SpekeProcessPacket(server, wrong, &out, &outLen), IOTC_CORE_COMM_SEC_ERR_SPEKE_SESSION_ID);
826+ ExpectTrue(out == NULL);
827+ ExpectEqInt(serverUser.finishCount, 0);
828+ IotcFree(wrong);
829+ 
830+ uint8_t *msg4 = NULL;
831+ uint32_t msg4Len = 0;
832+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg3, &msg4, &msg4Len), IOTC_OK);
833+ IotcFree(msg3);
834+ wrong = DupMsg(msg4, msg4Len);
835+ FlipHexCharInField(wrong, SPEKE_SESSION_ID_JSON);
836+ out = (uint8_t *)(uintptr_t)1;
837+ outLen = 123;
838+ ExpectEqInt(SpekeProcessPacket(client, wrong, &out, &outLen), IOTC_CORE_COMM_SEC_ERR_SPEKE_SESSION_ID);
839+ ExpectTrue(out == NULL);
840+ ExpectEqInt(clientUser.finishCount, 0);
841+ IotcFree(wrong);
842+ 
843+ out = (uint8_t *)(uintptr_t)1;
844+ outLen = 123;
845+ ExpectEqInt(SpekeProcessPacket(client, (const char *)msg4, &out, &outLen), IOTC_OK);
846+ ExpectTrue(out == NULL);
847+ ExpectEqInt(outLen, 0);
848+ ExpectEqInt(clientUser.finishCount, 1);
849+ IotcFree(msg4);
850+ SpekeFreeSession(client);
851+ SpekeFreeSession(server);
852+}
853+ 
854+static void TestSpekeApiIterationsStrict(void)
855+{
856+ SpekeTestUser serverUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
857+ SpekeTestUser clientUser = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
858+ SpekeSession *server = SpekeInitSession(SPEKE_TYPE_SERVER, &g_testCb, &serverUser);
859+ SpekeSession *client = SpekeInitSession(SPEKE_TYPE_CLIENT, &g_testCb, &clientUser);
860+ ExpectTrue(server != NULL && client != NULL);
861+ if (server == NULL || client == NULL) {
862+ return;
863+ }
864+ uint8_t *msg1 = NULL;
865+ uint32_t msg1Len = 0;
866+ uint8_t *msg2 = NULL;
867+ uint32_t msg2Len = 0;
868+ ExpectEqInt(SpekeStartSession(client, &msg1, &msg1Len), IOTC_OK);
869+ ExpectEqInt(SpekeProcessPacket(server, (const char *)msg1, &msg2, &msg2Len), IOTC_OK);
870+ IotcFree(msg1);
871+ 
872+ static const char *invalidFields[] = {
873+ "\"iterations\":1000000,",
874+ "\"iterations\":\"10000\",",
875+ ""
876+ };
877+ for (uint32_t i = 0; i < sizeof(invalidFields) / sizeof(invalidFields[0]); ++i) {
878+ char *bad = ReplaceSubstring((const char *)msg2, "\"iterations\":10000,", invalidFields[i]);
879+ uint8_t *out = (uint8_t *)(uintptr_t)1;
880+ uint32_t outLen = 123;
881+ ExpectEqInt(SpekeProcessPacket(client, bad, &out, &outLen), IOTC_CORE_COMM_SEC_ERR_SPEKE_ITERATIONS);
882+ ExpectTrue(out == NULL);
883+ ExpectEqInt(outLen, 0);
884+ IotcFree(bad);
885+ }
886+ 
887+ /* 严格拒绝不得破坏状态;原始合法 msg2 仍可继续处理。 */
888+ uint8_t *msg3 = NULL;
889+ uint32_t msg3Len = 0;
890+ ExpectEqInt(SpekeProcessPacket(client, (const char *)msg2, &msg3, &msg3Len), IOTC_OK);
891+ IotcFree(msg2);
892+ IotcFree(msg3);
893+ SpekeFreeSession(client);
894+ SpekeFreeSession(server);
895+}
896+ 
342static void TestSpekeApiInitNegative(void)897static void TestSpekeApiInitNegative(void)
343{898{
344 SpekeTestUser user = { PIN, sizeof(PIN) - 1, 0, 0, -1 };899 SpekeTestUser user = { PIN, sizeof(PIN) - 1, 0, 0, -1 };
@@ -358,6 +913,8 @@ static void TestSpekeApiInitNegative(void)
358 if (server != NULL) {913 if (server != NULL) {
359 ExpectTrue(SpekeStartSession(server, &msg, &len) != IOTC_OK);914 ExpectTrue(SpekeStartSession(server, &msg, &len) != IOTC_OK);
360 ExpectTrue(SpekeProcessPacket(server, "not json", &msg, &len) != IOTC_OK);915 ExpectTrue(SpekeProcessPacket(server, "not json", &msg, &len) != IOTC_OK);
916+ /* no negotiation happened: crypto must stay locked */
917+ ExpectCryptoRejected(server, "init server");
361 SpekeFreeSession(server);918 SpekeFreeSession(server);
362 }919 }
363}920}
@@ -366,7 +923,13 @@ int main(int argc, char **argv)
366{923{
367 bool dump = (argc > 1) && (strcmp(argv[1], "dump") == 0);924 bool dump = (argc > 1) && (strcmp(argv[1], "dump") == 0);
368 TestSpekeApiLoopback(dump);925 TestSpekeApiLoopback(dump);
926+ TestSpekeApiDeterministicMessages();
369 TestSpekeApiWrongPin();927 TestSpekeApiWrongPin();
928+ TestSpekeApiBothWrongPin();
929+ TestSpekeApiMsgTamper();
930+ TestSpekeApiReplay();
931+ TestSpekeApiSessionBinding();
932+ TestSpekeApiIterationsStrict();
370 TestSpekeApiInitNegative();933 TestSpekeApiInitNegative();
371 934 
372 if (g_failures != 0) {935 if (g_failures != 0) {
Atest/unit/kv_oh_lite_host/run_kv_oh_lite_tests.sh+16-0
@@ -0,0 +1,16 @@
1+#!/bin/bash
2+set -uo pipefail
3+ 
4+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
5+IOTC_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
6+OUT_DIR=$(mktemp -d /tmp/iotc_kv_oh_lite_test.XXXXXX)
7+ 
8+gcc -std=c99 -Wall -Wextra -Werror \
9+ -I"$SCRIPT_DIR/stubs" \
10+ -I"$IOTC_ROOT/adapter/include" \
11+ -I"$IOTC_ROOT/interfaces/kits/common" \
12+ "$SCRIPT_DIR/test_kv_oh_lite.c" \
13+ "$IOTC_ROOT/adapter/kv/oh_lite/iotc_kv.c" \
14+ -o "$OUT_DIR/test_kv_oh_lite" || exit 1
15+ 
16+"$OUT_DIR/test_kv_oh_lite"
Atest/unit/kv_oh_lite_host/stubs/iotc_log.h+9-0
@@ -0,0 +1,9 @@
1+#ifndef IOTC_KV_TEST_LOG_H
2+#define IOTC_KV_TEST_LOG_H
3+ 
4+#define IOTC_LOGD(...) ((void)0)
5+#define IOTC_LOGI(...) ((void)0)
6+#define IOTC_LOGW(...) ((void)0)
7+#define IOTC_LOGE(...) ((void)0)
8+ 
9+#endif
Atest/unit/kv_oh_lite_host/stubs/securec.h+39-0
@@ -0,0 +1,39 @@
1+#ifndef IOTC_KV_TEST_SECUREC_H
2+#define IOTC_KV_TEST_SECUREC_H
3+ 
4+#include <stdarg.h>
5+#include <stddef.h>
6+#include <stdio.h>
7+#include <string.h>
8+ 
9+#define EOK 0
10+ 
11+static inline int strcpy_s(char *dest, size_t destMax, const char *src)
12+{
13+ size_t len = (src == NULL) ? 0 : strlen(src);
14+ if (dest == NULL || src == NULL || len + 1 > destMax) {
15+ return -1;
16+ }
17+ (void)memcpy(dest, src, len + 1);
18+ return EOK;
19+}
20+ 
21+static inline int sprintf_s(char *dest, size_t destMax, const char *format, ...)
22+{
23+ va_list ap;
24+ va_start(ap, format);
25+ int ret = vsnprintf(dest, destMax, format, ap);
26+ va_end(ap);
27+ return (ret < 0 || (size_t)ret >= destMax) ? -1 : ret;
28+}
29+ 
30+static inline int memset_s(void *dest, size_t destMax, int c, size_t count)
31+{
32+ if (dest == NULL || count > destMax) {
33+ return -1;
34+ }
35+ (void)memset(dest, c, count);
36+ return EOK;
37+}
38+ 
39+#endif
Atest/unit/kv_oh_lite_host/stubs/utils_file.h+16-0
@@ -0,0 +1,16 @@
1+#ifndef IOTC_KV_TEST_UTILS_FILE_H
2+#define IOTC_KV_TEST_UTILS_FILE_H
3+ 
4+#define O_RDONLY_FS 0x01
5+#define O_RDWR_FS 0x02
6+#define O_CREAT_FS 0x04
7+#define O_TRUNC_FS 0x08
8+ 
9+int UtilsFileOpen(const char *path, int oflag, int mode);
10+int UtilsFileClose(int fd);
11+int UtilsFileRead(int fd, char *buf, unsigned int len);
12+int UtilsFileWrite(int fd, const char *buf, unsigned int len);
13+int UtilsFileDelete(const char *path);
14+int UtilsFileStat(const char *path, unsigned int *fileSize);
15+ 
16+#endif
Atest/unit/kv_oh_lite_host/test_kv_oh_lite.c+143-0
@@ -0,0 +1,143 @@
1+#include <stdint.h>
2+#include <stdio.h>
3+#include <string.h>
4+ 
5+#include "iotc_errcode.h"
6+#include "iotc_kv.h"
7+#include "utils_file.h"
8+ 
9+#define TEST_FILE_MAX 8
10+#define TEST_PATH_MAX 80
11+#define TEST_DATA_MAX 128
12+ 
13+typedef struct {
14+ char path[TEST_PATH_MAX];
15+ uint8_t data[TEST_DATA_MAX];
16+ uint32_t len;
17+ int present;
18+} TestFile;
19+ 
20+static TestFile g_files[TEST_FILE_MAX];
21+static int g_checks;
22+static int g_failures;
23+ 
24+#define CHECK(cond) do { \
25+ ++g_checks; \
26+ if (!(cond)) { \
27+ ++g_failures; \
28+ (void)printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
29+ } \
30+} while (0)
31+ 
32+static TestFile *FindFile(const char *path)
33+{
34+ for (int i = 0; i < TEST_FILE_MAX; ++i) {
35+ if (g_files[i].present && strcmp(g_files[i].path, path) == 0) {
36+ return &g_files[i];
37+ }
38+ }
39+ return NULL;
40+}
41+ 
42+static TestFile *CreateFile(const char *path)
43+{
44+ for (int i = 0; i < TEST_FILE_MAX; ++i) {
45+ if (!g_files[i].present) {
46+ g_files[i].present = 1;
47+ (void)snprintf(g_files[i].path, sizeof(g_files[i].path), "%s", path);
48+ return &g_files[i];
49+ }
50+ }
51+ return NULL;
52+}
53+ 
54+int UtilsFileOpen(const char *path, int oflag, int mode)
55+{
56+ (void)mode;
57+ TestFile *file = FindFile(path);
58+ if (file == NULL && (oflag & O_CREAT_FS) != 0) {
59+ file = CreateFile(path);
60+ }
61+ if (file == NULL) {
62+ return -1;
63+ }
64+ if ((oflag & O_TRUNC_FS) != 0) {
65+ file->len = 0;
66+ }
67+ return (int)(file - g_files) + 1;
68+}
69+ 
70+int UtilsFileClose(int fd)
71+{
72+ return (fd > 0 && fd <= TEST_FILE_MAX) ? 0 : -1;
73+}
74+ 
75+int UtilsFileRead(int fd, char *buf, unsigned int len)
76+{
77+ if (fd <= 0 || fd > TEST_FILE_MAX || !g_files[fd - 1].present || len > g_files[fd - 1].len) {
78+ return -1;
79+ }
80+ (void)memcpy(buf, g_files[fd - 1].data, len);
81+ return (int)len;
82+}
83+ 
84+int UtilsFileWrite(int fd, const char *buf, unsigned int len)
85+{
86+ if (fd <= 0 || fd > TEST_FILE_MAX || !g_files[fd - 1].present || len > TEST_DATA_MAX) {
87+ return -1;
88+ }
89+ (void)memcpy(g_files[fd - 1].data, buf, len);
90+ g_files[fd - 1].len = len;
91+ return (int)len;
92+}
93+ 
94+int UtilsFileDelete(const char *path)
95+{
96+ TestFile *file = FindFile(path);
97+ if (file == NULL) {
98+ return -1;
99+ }
100+ (void)memset(file, 0, sizeof(*file));
101+ return 0;
102+}
103+ 
104+int UtilsFileStat(const char *path, unsigned int *fileSize)
105+{
106+ TestFile *file = FindFile(path);
107+ if (file == NULL || fileSize == NULL) {
108+ return -1;
109+ }
110+ *fileSize = file->len;
111+ return 0;
112+}
113+ 
114+int main(void)
115+{
116+ CHECK(IotcKvInit("/kv") == IOTC_OK);
117+ static const uint8_t longValue[] = "12345678";
118+ static const uint8_t shortValue[] = "abc";
119+ CHECK(IotcKvSetValue("item", longValue, sizeof(longValue)) == IOTC_OK);
120+ CHECK(IotcKvSetValue("item", shortValue, sizeof(shortValue)) == IOTC_OK);
121+ 
122+ uint32_t len = 0;
123+ CHECK(IotcKvGetLen("item", &len) == IOTC_OK);
124+ CHECK(len == sizeof(shortValue));
125+ uint8_t value[16] = {0};
126+ len = sizeof(value);
127+ CHECK(IotcKvGetValue("item", value, &len) == IOTC_OK);
128+ CHECK(len == sizeof(shortValue));
129+ CHECK(memcmp(value, shortValue, sizeof(shortValue)) == 0);
130+ 
131+ len = 99;
132+ CHECK(IotcKvGetLen("missing", &len) == IOTC_OK);
133+ CHECK(len == 0);
134+ CHECK(FindFile("/kv/missing") == NULL);
135+ len = sizeof(value);
136+ CHECK(IotcKvGetValue("missing", value, &len) != IOTC_OK);
137+ CHECK(FindFile("/kv/missing") == NULL);
138+ CHECK(IotcKvGetLen("item", NULL) == IOTC_ERR_PARAM_INVALID);
139+ CHECK(IotcKvDeInit() == IOTC_OK);
140+ 
141+ (void)printf("test_kv_oh_lite: %d checks, %d failures\n", g_checks, g_failures);
142+ return (g_failures == 0) ? 0 : 1;
143+}
Atest/unit/online_notify_host/run_online_notify_tests.sh+49-0
@@ -0,0 +1,49 @@
1+#!/bin/bash
2+# Build and run WiFi Only /onlinenotify host tests outside the source tree.
3+set -uo pipefail
4+ 
5+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
6+IOTC_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
7+if [ -n "${IOTC_TEST_OHOS_ROOT:-}" ]; then
8+ OHOS_ROOT=$(cd "$IOTC_TEST_OHOS_ROOT" && pwd)
9+else
10+ OHOS_ROOT=$(cd "$IOTC_ROOT/../../.." && pwd)
11+fi
12+OUT_DIR=$(mktemp -d /tmp/iotc_online_notify_host_test.XXXXXX)
13+trap 'rm -rf "$OUT_DIR"' EXIT
14+ 
15+CFLAGS=(
16+ -std=c99 -Wall -Wextra -Werror -Wno-sign-compare -g -O0
17+ -DIOTC_CONF_WIFI_ONLY_SUPPORT=1
18+ -DIOTC_CONF_WIFI_SUPPORT=1
19+ -DIOTC_ONECONNECT_VERSION_CODE=3
20+ -I"$SCRIPT_DIR"
21+ -I"$SCRIPT_DIR/stubs"
22+ -I"$IOTC_ROOT/test/unit/device_config_host"
23+ -I"$IOTC_ROOT/test/unit/device_config_host/stubs"
24+ -I"$IOTC_ROOT/adapter/include"
25+ -I"$IOTC_ROOT/interfaces/kits/common"
26+ -I"$IOTC_ROOT/core/infrastructure/define"
27+ -I"$IOTC_ROOT/core/infrastructure/utils/include"
28+ -I"$IOTC_ROOT/core/infrastructure/security/include"
29+ -I"$IOTC_ROOT/core/infrastructure/schedule/include"
30+ -I"$IOTC_ROOT/core/infrastructure/frameworks/event_loop/include"
31+ -I"$IOTC_ROOT/core/device/config/include"
32+ -I"$IOTC_ROOT/core/wifi/application/connect"
33+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/coap/codec"
34+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/coap/codec/include"
35+ -I"$OHOS_ROOT/third_party/cJSON"
36+)
37+ 
38+SOURCES=(
39+ "$SCRIPT_DIR/test_online_notify.c"
40+ "$SCRIPT_DIR/stubs/online_notify_deps_stub.c"
41+ "$IOTC_ROOT/core/wifi/application/connect/svc_conn_online_notify.c"
42+ "$IOTC_ROOT/core/wifi/infrastructure/transport/coap/codec/coap_codec_udp.c"
43+ "$IOTC_ROOT/core/wifi/infrastructure/transport/coap/codec/coap_codec_comm.c"
44+ "$IOTC_ROOT/adapter/json/cjson/iotc_json.c"
45+ "$OHOS_ROOT/third_party/cJSON/cJSON.c"
46+)
47+ 
48+gcc "${CFLAGS[@]}" "${SOURCES[@]}" -lm -o "$OUT_DIR/test_online_notify" || exit 1
49+"$OUT_DIR/test_online_notify"
Atest/unit/online_notify_host/stubs/iotc_log.h+11-0
@@ -0,0 +1,11 @@
1+#ifndef IOTC_ONLINE_NOTIFY_TEST_LOG_H
2+#define IOTC_ONLINE_NOTIFY_TEST_LOG_H
3+ 
4+#define IOTC_LOGF(...) ((void)0)
5+#define IOTC_LOGE(...) ((void)0)
6+#define IOTC_LOGW(...) ((void)0)
7+#define IOTC_LOGN(...) ((void)0)
8+#define IOTC_LOGI(...) ((void)0)
9+#define IOTC_LOGD(...) ((void)0)
10+ 
11+#endif /* IOTC_ONLINE_NOTIFY_TEST_LOG_H */
Atest/unit/online_notify_host/stubs/online_notify_deps_stub.c+239-0
@@ -0,0 +1,239 @@
1+#include "online_notify_deps_stub.h"
2+ 
3+#include <stdbool.h>
4+#include <string.h>
5+ 
6+#include "event_source_timer.h"
7+#include "iotc_errcode.h"
8+#include "iotc_md.h"
9+#include "iotc_socket.h"
10+#include "securec.h"
11+#include "utils_common.h"
12+ 
13+#define STUB_PACKET_MAX_LEN 256
14+ 
15+static IotcDssProvisionPending g_pending;
16+static int32_t g_loadResult;
17+static int32_t g_markResult;
18+static int32_t g_randomResult;
19+static int32_t g_socketResult;
20+static int32_t g_socketOptResult;
21+static int32_t g_timerResult;
22+static uint32_t g_sendFailures;
23+ 
24+static uint32_t g_socketCalls;
25+static uint32_t g_sendCalls;
26+static uint32_t g_markCalls;
27+static uint32_t g_closeCalls;
28+static uint32_t g_timerAddCalls;
29+static uint32_t g_timerRemoveCalls;
30+static uint32_t g_timerInterval;
31+static uint16_t g_targetPort;
32+static uint32_t g_targetAddr;
33+static uint8_t g_lastPacket[STUB_PACKET_MAX_LEN];
34+static uint32_t g_lastPacketLen;
35+static EventSourceTimerCallback g_timerCallback;
36+static void *g_timerUserData;
37+ 
38+void OnlineNotifyStubReset(void)
39+{
40+ (void)memset_s(&g_pending, sizeof(g_pending), 0, sizeof(g_pending));
41+ (void)strcpy_s(g_pending.tempDevId, sizeof(g_pending.tempDevId), "temp-dev-0001");
42+ g_pending.mdidLen = 1;
43+ g_pending.state = IOTC_DSS_PENDING_STATE_WAIT_ACTIVATE;
44+ g_loadResult = IOTC_OK;
45+ g_markResult = IOTC_OK;
46+ g_randomResult = IOTC_OK;
47+ g_socketResult = 27;
48+ g_socketOptResult = IOTC_OK;
49+ g_timerResult = 91;
50+ g_sendFailures = 0;
51+ g_socketCalls = 0;
52+ g_sendCalls = 0;
53+ g_markCalls = 0;
54+ g_closeCalls = 0;
55+ g_timerAddCalls = 0;
56+ g_timerRemoveCalls = 0;
57+ g_timerInterval = 0;
58+ g_targetPort = 0;
59+ g_targetAddr = 0;
60+ g_lastPacketLen = 0;
61+ g_timerCallback = NULL;
62+ g_timerUserData = NULL;
63+}
64+ 
65+void OnlineNotifyStubFireTimer(void)
66+{
67+ if (g_timerCallback != NULL) {
68+ g_timerCallback(g_timerResult, g_timerUserData);
69+ }
70+}
71+ 
72+void OnlineNotifyStubSetLoadResult(int32_t ret) { g_loadResult = ret; }
73+void OnlineNotifyStubSetPendingDone(uint8_t done) { g_pending.onlineNotifyDone = done; }
74+void OnlineNotifyStubSetSendFailures(uint32_t count) { g_sendFailures = count; }
75+void OnlineNotifyStubSetRandomResult(int32_t ret) { g_randomResult = ret; }
76+void OnlineNotifyStubSetSocketResult(int32_t ret) { g_socketResult = ret; }
77+void OnlineNotifyStubSetSocketOptResult(int32_t ret) { g_socketOptResult = ret; }
78+void OnlineNotifyStubSetTimerResult(int32_t ret) { g_timerResult = ret; }
79+void OnlineNotifyStubSetMarkResult(int32_t ret) { g_markResult = ret; }
80+ 
81+uint32_t OnlineNotifyStubGetSocketCalls(void) { return g_socketCalls; }
82+uint32_t OnlineNotifyStubGetSendCalls(void) { return g_sendCalls; }
83+uint32_t OnlineNotifyStubGetMarkCalls(void) { return g_markCalls; }
84+uint32_t OnlineNotifyStubGetCloseCalls(void) { return g_closeCalls; }
85+uint32_t OnlineNotifyStubGetTimerAddCalls(void) { return g_timerAddCalls; }
86+uint32_t OnlineNotifyStubGetTimerRemoveCalls(void) { return g_timerRemoveCalls; }
87+uint32_t OnlineNotifyStubGetTimerInterval(void) { return g_timerInterval; }
88+uint16_t OnlineNotifyStubGetTargetPort(void) { return g_targetPort; }
89+uint32_t OnlineNotifyStubGetTargetAddr(void) { return g_targetAddr; }
90+uint8_t OnlineNotifyStubGetPendingDone(void) { return g_pending.onlineNotifyDone; }
91+ 
92+const uint8_t *OnlineNotifyStubGetLastPacket(uint32_t *len)
93+{
94+ if (len != NULL) {
95+ *len = g_lastPacketLen;
96+ }
97+ return g_lastPacket;
98+}
99+ 
100+int32_t IotcDssLoadProvisionPending(IotcDssProvisionPending *pending)
101+{
102+ if (g_loadResult == IOTC_OK && pending != NULL) {
103+ (void)memcpy_s(pending, sizeof(*pending), &g_pending, sizeof(g_pending));
104+ }
105+ return g_loadResult;
106+}
107+ 
108+int32_t IotcDssMarkOnlineNotifyDone(void)
109+{
110+ ++g_markCalls;
111+ if (g_markResult == IOTC_OK) {
112+ g_pending.onlineNotifyDone = 1;
113+ }
114+ return g_markResult;
115+}
116+ 
117+int32_t IotcMdCalc(IotcMdType type, const uint8_t *inData, uint32_t inLen, uint8_t *md, uint32_t mdLen)
118+{
119+ if (type != IOTC_MD_SHA256 || inData == NULL || inLen == 0 || md == NULL ||
120+ mdLen != IOTC_MD_SHA256_BYTE_LEN) {
121+ return IOTC_ERR_PARAM_INVALID;
122+ }
123+ for (uint32_t i = 0; i < mdLen; ++i) {
124+ md[i] = (uint8_t)i;
125+ }
126+ return IOTC_OK;
127+}
128+ 
129+bool UtilsHexify(const uint8_t *inBuf, uint32_t inBufLen, char *outBuf, uint32_t outBufLen)
130+{
131+ static const char HEX[] = "0123456789abcdef";
132+ if (inBuf == NULL || outBuf == NULL || outBufLen < HEXIFY_LEN(inBufLen)) {
133+ return false;
134+ }
135+ for (uint32_t i = 0; i < inBufLen; ++i) {
136+ outBuf[i * 2] = HEX[inBuf[i] >> 4];
137+ outBuf[i * 2 + 1] = HEX[inBuf[i] & 0x0F];
138+ }
139+ return true;
140+}
141+ 
142+int32_t SecurityRandom(uint8_t *buf, uint32_t bufLen)
143+{
144+ if (g_randomResult != IOTC_OK) {
145+ return g_randomResult;
146+ }
147+ if (buf == NULL || bufLen != sizeof(uint16_t)) {
148+ return IOTC_ERR_PARAM_INVALID;
149+ }
150+ uint16_t msgId = 0x1234;
151+ (void)memcpy_s(buf, bufLen, &msgId, sizeof(msgId));
152+ return IOTC_OK;
153+}
154+ 
155+int32_t IotcSocket(IotcSocketDomain domain, IotcSocketType type, IotcSocketProto proto)
156+{
157+ ++g_socketCalls;
158+ if (domain != IOTC_SOCKET_DOMAIN_AF_INET || type != IOTC_SOCKET_TYPE_DGRAM ||
159+ proto != IOTC_SOCKET_PROTO_UDP) {
160+ return IOTC_ERR_PARAM_INVALID;
161+ }
162+ return g_socketResult;
163+}
164+ 
165+void IotcClose(int32_t fd)
166+{
167+ if (fd >= 0) {
168+ ++g_closeCalls;
169+ }
170+}
171+ 
172+int32_t IotcSetSocketOpt(int32_t fd, IotcSocketOption option, const void *value, uint32_t len)
173+{
174+ (void)value;
175+ if (fd != g_socketResult || option != IOTC_SOCKET_OPTION_ENABLE_BROADCAST || len != 0) {
176+ return IOTC_ERR_PARAM_INVALID;
177+ }
178+ return g_socketOptResult;
179+}
180+ 
181+int32_t IotcSendTo(int32_t fd, const uint8_t *buf, uint32_t len, const IotcSockaddr *to, uint32_t toLen)
182+{
183+ ++g_sendCalls;
184+ if (fd != g_socketResult || buf == NULL || len > sizeof(g_lastPacket) || to == NULL ||
185+ toLen != sizeof(IotcSockaddrIn)) {
186+ return IOTC_ERR_PARAM_INVALID;
187+ }
188+ const IotcSockaddrIn *target = (const IotcSockaddrIn *)to;
189+ g_targetPort = target->sinPort;
190+ g_targetAddr = target->sinAddr;
191+ (void)memcpy_s(g_lastPacket, sizeof(g_lastPacket), buf, len);
192+ g_lastPacketLen = len;
193+ if (g_sendCalls <= g_sendFailures) {
194+ return -77;
195+ }
196+ return (int32_t)len;
197+}
198+ 
199+uint16_t IotcHtons(uint16_t value)
200+{
201+ return (uint16_t)((value << 8) | (value >> 8));
202+}
203+ 
204+uint16_t IotcNtohs(uint16_t value)
205+{
206+ return IotcHtons(value);
207+}
208+ 
209+uint32_t IotcNtohl(uint32_t value)
210+{
211+ return ((value & 0x000000FFU) << 24) | ((value & 0x0000FF00U) << 8) |
212+ ((value & 0x00FF0000U) >> 8) | ((value & 0xFF000000U) >> 24);
213+}
214+ 
215+uint32_t IotcInetAddr(const char *ip)
216+{
217+ return (ip != NULL && strcmp(ip, "255.255.255.255") == 0) ? UINT32_MAX : 0;
218+}
219+ 
220+int32_t SchedTimerAdd(EventSourceTimerType type, EventSourceTimerCallback cb, uint32_t ms, void *userData)
221+{
222+ ++g_timerAddCalls;
223+ if (type != EVENT_SOURCE_TIMER_TYPE_REPEAT || cb == NULL) {
224+ return IOTC_ERR_PARAM_INVALID;
225+ }
226+ g_timerCallback = cb;
227+ g_timerUserData = userData;
228+ g_timerInterval = ms;
229+ return g_timerResult;
230+}
231+ 
232+void SchedTimerRemove(int32_t id)
233+{
234+ if (id >= 0) {
235+ ++g_timerRemoveCalls;
236+ }
237+ g_timerCallback = NULL;
238+ g_timerUserData = NULL;
239+}
Atest/unit/online_notify_host/stubs/online_notify_deps_stub.h+31-0
@@ -0,0 +1,31 @@
1+#ifndef IOTC_ONLINE_NOTIFY_DEPS_STUB_H
2+#define IOTC_ONLINE_NOTIFY_DEPS_STUB_H
3+ 
4+#include <stdint.h>
5+ 
6+#include "device_state_store.h"
7+ 
8+void OnlineNotifyStubReset(void);
9+void OnlineNotifyStubFireTimer(void);
10+void OnlineNotifyStubSetLoadResult(int32_t ret);
11+void OnlineNotifyStubSetPendingDone(uint8_t done);
12+void OnlineNotifyStubSetSendFailures(uint32_t count);
13+void OnlineNotifyStubSetRandomResult(int32_t ret);
14+void OnlineNotifyStubSetSocketResult(int32_t ret);
15+void OnlineNotifyStubSetSocketOptResult(int32_t ret);
16+void OnlineNotifyStubSetTimerResult(int32_t ret);
17+void OnlineNotifyStubSetMarkResult(int32_t ret);
18+ 
19+uint32_t OnlineNotifyStubGetSocketCalls(void);
20+uint32_t OnlineNotifyStubGetSendCalls(void);
21+uint32_t OnlineNotifyStubGetMarkCalls(void);
22+uint32_t OnlineNotifyStubGetCloseCalls(void);
23+uint32_t OnlineNotifyStubGetTimerAddCalls(void);
24+uint32_t OnlineNotifyStubGetTimerRemoveCalls(void);
25+uint32_t OnlineNotifyStubGetTimerInterval(void);
26+const uint8_t *OnlineNotifyStubGetLastPacket(uint32_t *len);
27+uint16_t OnlineNotifyStubGetTargetPort(void);
28+uint32_t OnlineNotifyStubGetTargetAddr(void);
29+uint8_t OnlineNotifyStubGetPendingDone(void);
30+ 
31+#endif /* IOTC_ONLINE_NOTIFY_DEPS_STUB_H */
Atest/unit/online_notify_host/test_online_notify.c+180-0
@@ -0,0 +1,180 @@
1+#include <stdio.h>
2+#include <string.h>
3+ 
4+#include "coap_codec_def.h"
5+#include "coap_codec_udp.h"
6+#include "iotc_errcode.h"
7+#include "iotc_json.h"
8+#include "iotc_socket.h"
9+#include "online_notify_deps_stub.h"
10+#include "svc_conn_online_notify.h"
11+#include "test_framework.h"
12+ 
13+int g_testChecks;
14+int g_testFailures;
15+ 
16+static const char *EXPECTED_HASH =
17+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f";
18+ 
19+static void TestPacket(void)
20+{
21+ uint8_t packet[160] = {0};
22+ uint32_t packetLen = sizeof(packet);
23+ CHECK_EQ_INT(IOTC_OK, SvcConnBuildOnlineNotifyPacket("temp-dev-0001", 0x1234, packet, &packetLen));
24+ CHECK(packetLen > 4);
25+ CHECK_EQ_INT(0x40, packet[0]); /* CoAP v1, CON, TKL=0 */
26+ CHECK_EQ_INT(COAP_METHOD_TYPE_POST, packet[1]);
27+ CHECK_EQ_INT(0x12, packet[2]);
28+ CHECK_EQ_INT(0x34, packet[3]);
29+ 
30+ CoapPacket decoded = {0};
31+ CoapData raw = { packet, packetLen };
32+ CHECK_EQ_INT(IOTC_OK, CoapUdpDecode(&decoded, &raw));
33+ CHECK_EQ_INT(COAP_MSG_TYPE_CON, decoded.header.type);
34+ CHECK_EQ_INT(COAP_METHOD_TYPE_POST, decoded.header.code);
35+ CHECK_EQ_INT(0x1234, decoded.header.msgId);
36+ CHECK_EQ_INT(2, decoded.opNum);
37+ CHECK_EQ_INT(COAP_OPTION_TYPE_URI_PATH, decoded.options[0].option);
38+ CHECK_EQ_INT(strlen("onlinenotify"), decoded.options[0].value.len);
39+ CHECK(memcmp(decoded.options[0].value.data, "onlinenotify", strlen("onlinenotify")) == 0);
40+ CHECK_EQ_INT(COAP_OPTION_TYPE_CONTENT_FORMAT, decoded.options[1].option);
41+ CHECK_EQ_INT(1, decoded.options[1].value.len);
42+ CHECK_EQ_INT(50, decoded.options[1].value.data[0]);
43+ 
44+ IotcJson *json = IotcJsonParseWithLen((const char *)decoded.payload.data, decoded.payload.len);
45+ CHECK(json != NULL);
46+ IotcJson *devId = IotcJsonGetObj(json, "devId");
47+ CHECK(IotcJsonIsStr(devId));
48+ CHECK(strcmp(IotcJsonGetStr(devId), EXPECTED_HASH) == 0);
49+ IotcJsonDelete(json);
50+ 
51+ uint32_t shortLen = 4;
52+ CHECK_EQ_INT(IOTC_CORE_WIFI_TRANS_ERR_COAP_CODEC_BUFFER_SHORT,
53+ SvcConnBuildOnlineNotifyPacket("temp-dev-0001", 1, packet, &shortLen));
54+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, SvcConnBuildOnlineNotifyPacket(NULL, 1, packet, &packetLen));
55+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, SvcConnBuildOnlineNotifyPacket("", 1, packet, &packetLen));
56+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, SvcConnBuildOnlineNotifyPacket("dev", 1, NULL, &packetLen));
57+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, SvcConnBuildOnlineNotifyPacket("dev", 1, packet, NULL));
58+}
59+ 
60+static void TestFiveSuccessfulSends(void)
61+{
62+ OnlineNotifyStubReset();
63+ CHECK_EQ_INT(IOTC_OK, SvcConnOnlineNotifyStart());
64+ CHECK_EQ_INT(1, OnlineNotifyStubGetSocketCalls());
65+ CHECK_EQ_INT(1, OnlineNotifyStubGetSendCalls());
66+ CHECK_EQ_INT(1, OnlineNotifyStubGetTimerAddCalls());
67+ CHECK_EQ_INT(100, OnlineNotifyStubGetTimerInterval());
68+ CHECK_EQ_INT(IotcHtons(5683), OnlineNotifyStubGetTargetPort());
69+ CHECK_EQ_INT(UINT32_MAX, OnlineNotifyStubGetTargetAddr());
70+ 
71+ /* active 期间重复 Start 幂等,不新建 socket/timer,也不多发。 */
72+ CHECK_EQ_INT(IOTC_OK, SvcConnOnlineNotifyStart());
73+ CHECK_EQ_INT(1, OnlineNotifyStubGetSocketCalls());
74+ CHECK_EQ_INT(1, OnlineNotifyStubGetSendCalls());
75+ 
76+ for (int i = 0; i < 4; ++i) {
77+ OnlineNotifyStubFireTimer();
78+ }
79+ CHECK_EQ_INT(5, OnlineNotifyStubGetSendCalls());
80+ CHECK_EQ_INT(1, OnlineNotifyStubGetMarkCalls());
81+ CHECK_EQ_INT(1, OnlineNotifyStubGetPendingDone());
82+ CHECK_EQ_INT(1, OnlineNotifyStubGetTimerRemoveCalls());
83+ CHECK_EQ_INT(1, OnlineNotifyStubGetCloseCalls());
84+ 
85+ uint32_t packetLen = 0;
86+ const uint8_t *packet = OnlineNotifyStubGetLastPacket(&packetLen);
87+ CHECK(packet != NULL && packetLen > 4);
88+ CHECK_EQ_INT(0x40, packet[0]);
89+}
90+ 
91+static void TestSkipAndRetryLimit(void)
92+{
93+ OnlineNotifyStubReset();
94+ OnlineNotifyStubSetPendingDone(1);
95+ CHECK_EQ_INT(IOTC_OK, SvcConnOnlineNotifyStart());
96+ CHECK_EQ_INT(0, OnlineNotifyStubGetSocketCalls());
97+ 
98+ OnlineNotifyStubReset();
99+ OnlineNotifyStubSetLoadResult(IOTC_CORE_DEVICE_ERR_STORE_NOT_FOUND);
100+ CHECK_EQ_INT(IOTC_OK, SvcConnOnlineNotifyStart());
101+ CHECK_EQ_INT(0, OnlineNotifyStubGetSocketCalls());
102+ 
103+ OnlineNotifyStubReset();
104+ OnlineNotifyStubSetSendFailures(10);
105+ CHECK_EQ_INT(IOTC_OK, SvcConnOnlineNotifyStart());
106+ for (int i = 0; i < 9; ++i) {
107+ OnlineNotifyStubFireTimer();
108+ }
109+ CHECK_EQ_INT(10, OnlineNotifyStubGetSendCalls());
110+ CHECK_EQ_INT(0, OnlineNotifyStubGetMarkCalls());
111+ CHECK_EQ_INT(1, OnlineNotifyStubGetTimerRemoveCalls());
112+ CHECK_EQ_INT(1, OnlineNotifyStubGetCloseCalls());
113+}
114+ 
115+static void TestFailureCleanupAndStop(void)
116+{
117+ OnlineNotifyStubReset();
118+ OnlineNotifyStubSetRandomResult(-31);
119+ CHECK_EQ_INT(-31, SvcConnOnlineNotifyStart());
120+ CHECK_EQ_INT(0, OnlineNotifyStubGetSocketCalls());
121+ 
122+ OnlineNotifyStubReset();
123+ OnlineNotifyStubSetSocketResult(-32);
124+ CHECK_EQ_INT(-32, SvcConnOnlineNotifyStart());
125+ CHECK_EQ_INT(1, OnlineNotifyStubGetSocketCalls());
126+ CHECK_EQ_INT(0, OnlineNotifyStubGetCloseCalls());
127+ 
128+ OnlineNotifyStubReset();
129+ OnlineNotifyStubSetSocketOptResult(-33);
130+ CHECK_EQ_INT(-33, SvcConnOnlineNotifyStart());
131+ CHECK_EQ_INT(1, OnlineNotifyStubGetCloseCalls());
132+ 
133+ OnlineNotifyStubReset();
134+ OnlineNotifyStubSetTimerResult(-34);
135+ CHECK_EQ_INT(-34, SvcConnOnlineNotifyStart());
136+ CHECK_EQ_INT(1, OnlineNotifyStubGetSendCalls());
137+ CHECK_EQ_INT(1, OnlineNotifyStubGetCloseCalls());
138+ 
139+ OnlineNotifyStubReset();
140+ CHECK_EQ_INT(IOTC_OK, SvcConnOnlineNotifyStart());
141+ SvcConnOnlineNotifyStop();
142+ CHECK_EQ_INT(1, OnlineNotifyStubGetTimerRemoveCalls());
143+ CHECK_EQ_INT(1, OnlineNotifyStubGetCloseCalls());
144+ SvcConnOnlineNotifyStop();
145+ CHECK_EQ_INT(1, OnlineNotifyStubGetTimerRemoveCalls());
146+ CHECK_EQ_INT(1, OnlineNotifyStubGetCloseCalls());
147+}
148+ 
149+static void TestMarkFailureAllowsRestart(void)
150+{
151+ OnlineNotifyStubReset();
152+ OnlineNotifyStubSetMarkResult(-41);
153+ CHECK_EQ_INT(IOTC_OK, SvcConnOnlineNotifyStart());
154+ for (int i = 0; i < 4; ++i) {
155+ OnlineNotifyStubFireTimer();
156+ }
157+ CHECK_EQ_INT(5, OnlineNotifyStubGetSendCalls());
158+ CHECK_EQ_INT(1, OnlineNotifyStubGetMarkCalls());
159+ CHECK_EQ_INT(0, OnlineNotifyStubGetPendingDone());
160+ CHECK_EQ_INT(1, OnlineNotifyStubGetCloseCalls());
161+ 
162+ /* 持久化失败后保持未完成,下一次 IP ready 可重新执行完整五次。 */
163+ OnlineNotifyStubSetMarkResult(IOTC_OK);
164+ CHECK_EQ_INT(IOTC_OK, SvcConnOnlineNotifyStart());
165+ CHECK_EQ_INT(2, OnlineNotifyStubGetSocketCalls());
166+}
167+ 
168+int main(void)
169+{
170+ setbuf(stdout, NULL);
171+ OnlineNotifyStubReset();
172+ TestPacket();
173+ TestFiveSuccessfulSends();
174+ TestSkipAndRetryLimit();
175+ TestFailureCleanupAndStop();
176+ TestMarkFailureAllowsRestart();
177+ SvcConnOnlineNotifyStop();
178+ printf("test_online_notify: %d checks, %d failures\n", g_testChecks, g_testFailures);
179+ return (g_testFailures == 0) ? 0 : 1;
180+}
Atest/unit/security_config_host/run_security_config_tests.sh+25-0
@@ -0,0 +1,25 @@
1+#!/bin/bash
2+# Build and run the security-config factory-reset regression outside the source tree.
3+set -uo pipefail
4+ 
5+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
6+IOTC_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
7+OUT_DIR=$(mktemp -d /tmp/iotc_security_config_host_test.XXXXXX)
8+trap 'rm -rf "$OUT_DIR"' EXIT
9+ 
10+gcc \
11+ -std=c99 -Wall -Wextra -Werror -g -O0 -ffunction-sections -fdata-sections \
12+ -I"$IOTC_ROOT/test/unit/device_config_host/stubs" \
13+ -I"$IOTC_ROOT/adapter/include" \
14+ -I"$IOTC_ROOT/interfaces/kits/common" \
15+ -I"$IOTC_ROOT/interfaces/kits/oh_connect" \
16+ -I"$IOTC_ROOT/core/infrastructure/define" \
17+ -I"$IOTC_ROOT/core/infrastructure/security/include" \
18+ -I"$IOTC_ROOT/core/infrastructure/service/include" \
19+ -I"$IOTC_ROOT/core/infrastructure/utils/include" \
20+ -I"$IOTC_ROOT/core/security/config/include" \
21+ "$SCRIPT_DIR/test_security_config_reset.c" \
22+ "$IOTC_ROOT/core/security/config/sec_conf_info.c" \
23+ -Wl,--gc-sections -o "$OUT_DIR/test_security_config_reset" || exit 1
24+ 
25+"$OUT_DIR/test_security_config_reset"
Atest/unit/security_config_host/test_security_config_reset.c+115-0
@@ -0,0 +1,115 @@
1+#include <stdint.h>
2+#include <stdio.h>
3+#include <string.h>
4+ 
5+#include "iotc_errcode.h"
6+#include "iotc_store_key.h"
7+#include "sec_conf_info.h"
8+#include "security_store.h"
9+ 
10+static int g_checks;
11+static int g_failures;
12+static int g_deleteCalls;
13+static int32_t g_deleteResult;
14+static int32_t g_deletedKey;
15+ 
16+#define CHECK(expr) do { \
17+ ++g_checks; \
18+ if (!(expr)) { \
19+ ++g_failures; \
20+ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #expr); \
21+ } \
22+} while (0)
23+ 
24+#define CHECK_EQ(expected, actual) do { \
25+ int32_t expected_ = (expected); \
26+ int32_t actual_ = (actual); \
27+ ++g_checks; \
28+ if (expected_ != actual_) { \
29+ ++g_failures; \
30+ fprintf(stderr, "FAIL %s:%d expected=%d actual=%d\n", __FILE__, __LINE__, expected_, actual_); \
31+ } \
32+} while (0)
33+ 
34+int32_t SecurityStoreRegStoreList(SecurityStoreItem *list, uint32_t num)
35+{
36+ return (list != NULL && num == 1U) ? IOTC_OK : IOTC_ERR_PARAM_INVALID;
37+}
38+ 
39+void SecurityStoreUnregStoreList(SecurityStoreItem *list)
40+{
41+ (void)list;
42+}
43+ 
44+int32_t SecurityStorePull(int32_t key)
45+{
46+ return key == IOTC_STORE_KEY_INT_SECURITY_CONFIG ? IOTC_OK : IOTC_ERR_PARAM_INVALID;
47+}
48+ 
49+int32_t SecurityStoreDel(int32_t key)
50+{
51+ ++g_deleteCalls;
52+ g_deletedKey = key;
53+ return g_deleteResult;
54+}
55+ 
56+static void Reset(void)
57+{
58+ g_deleteCalls = 0;
59+ g_deleteResult = IOTC_OK;
60+ g_deletedKey = -1;
61+}
62+ 
63+static void TestFactoryResetClearsMemoryAndPersistentRecord(void)
64+{
65+ Reset();
66+ CHECK_EQ(IOTC_OK, SecConfInfoInit());
67+ 
68+ static const char domain[] = "domain-1";
69+ uint16_t pskNum = 1;
70+ SecPsk psk = {0};
71+ (void)strcpy(psk.pskId, "A001");
72+ psk.psk[0] = 0xA5;
73+ CHECK_EQ(IOTC_OK, SecConfInfoSet(SEC_CONF_INFO_KEY_DOMAIN_ID, (const uint8_t *)domain, sizeof(domain)));
74+ CHECK_EQ(IOTC_OK, SecConfInfoSet(SEC_CONF_INFO_KEY_TERMINAL_PSK_NUM, (const uint8_t *)&pskNum, sizeof(pskNum)));
75+ CHECK_EQ(IOTC_OK, SecConfInfoSet(SEC_CONF_INFO_KEY_TERMINAL_PSKS, (const uint8_t *)&psk, sizeof(psk)));
76+ 
77+ CHECK_EQ(IOTC_OK, SecConfInfoFactoryReset());
78+ CHECK_EQ(1, g_deleteCalls);
79+ CHECK_EQ(IOTC_STORE_KEY_INT_SECURITY_CONFIG, g_deletedKey);
80+ 
81+ char readDomain[DOMAIN_ID_MAX_LEN + 1] = {0};
82+ uint32_t domainLen = sizeof(readDomain);
83+ CHECK_EQ(IOTC_OK, SecConfInfoGet(SEC_CONF_INFO_KEY_DOMAIN_ID, (uint8_t *)readDomain, &domainLen));
84+ CHECK(readDomain[0] == '\0');
85+ pskNum = 1;
86+ uint32_t pskNumLen = sizeof(pskNum);
87+ CHECK_EQ(IOTC_OK, SecConfInfoGet(SEC_CONF_INFO_KEY_TERMINAL_PSK_NUM, (uint8_t *)&pskNum, &pskNumLen));
88+ CHECK_EQ(0, pskNum);
89+ 
90+ SecConfInfoDeinit();
91+}
92+ 
93+static void TestFactoryResetWipesMemoryWhenPersistentDeleteFails(void)
94+{
95+ Reset();
96+ CHECK_EQ(IOTC_OK, SecConfInfoInit());
97+ static const char domain[] = "domain-2";
98+ CHECK_EQ(IOTC_OK, SecConfInfoSet(SEC_CONF_INFO_KEY_DOMAIN_ID, (const uint8_t *)domain, sizeof(domain)));
99+ g_deleteResult = -79;
100+ CHECK_EQ(-79, SecConfInfoFactoryReset());
101+ 
102+ char readDomain[DOMAIN_ID_MAX_LEN + 1] = {0};
103+ uint32_t domainLen = sizeof(readDomain);
104+ CHECK_EQ(IOTC_OK, SecConfInfoGet(SEC_CONF_INFO_KEY_DOMAIN_ID, (uint8_t *)readDomain, &domainLen));
105+ CHECK(readDomain[0] == '\0');
106+ SecConfInfoDeinit();
107+}
108+ 
109+int main(void)
110+{
111+ TestFactoryResetClearsMemoryAndPersistentRecord();
112+ TestFactoryResetWipesMemoryWhenPersistentDeleteFails();
113+ printf("security config reset host: %d checks, %d failures\n", g_checks, g_failures);
114+ return g_failures == 0 ? 0 : 1;
115+}
Atest/unit/softap_host/run_softap_tests.sh+124-0
@@ -0,0 +1,124 @@
1+#!/bin/bash
2+# Unified Connect 3.0 设备 SoftAP 配网 host 测试。
3+# 只用本机 gcc,不依赖 OpenHarmony 构建系统,不在源码树内留下生成物。
4+set -uo pipefail
5+ 
6+SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
7+IOTC_ROOT=$(cd "$SCRIPT_DIR/../../.." && pwd)
8+if [ -n "${IOTC_TEST_OHOS_ROOT:-}" ]; then
9+ OHOS_ROOT=$(cd "$IOTC_TEST_OHOS_ROOT" && pwd)
10+else
11+ OHOS_ROOT=$(cd "$IOTC_ROOT/../../.." && pwd)
12+fi
13+SHARED_STUBS="$IOTC_ROOT/test/unit/device_config_host/stubs"
14+OUT_DIR=$(mktemp -d /tmp/iotc_softap_host_test.XXXXXX)
15+trap 'rm -rf "$OUT_DIR"' EXIT
16+ 
17+CFLAGS=(
18+ -std=c99 -Wall -Wextra -Werror -g -O0
19+ -I"$SCRIPT_DIR/stubs"
20+ -I"$SHARED_STUBS"
21+ -I"$IOTC_ROOT/test/unit/device_config_host"
22+ -I"$IOTC_ROOT/adapter/include"
23+ -I"$IOTC_ROOT/interfaces/kits/common"
24+ -I"$IOTC_ROOT/interfaces/kits/oh_connect"
25+ -I"$IOTC_ROOT/core/infrastructure/utils/include"
26+ -I"$IOTC_ROOT/core/infrastructure/define"
27+ -I"$IOTC_ROOT/core/infrastructure/product/include"
28+ -I"$IOTC_ROOT/core/infrastructure/service/include"
29+ -I"$IOTC_ROOT/core/infrastructure/security/include"
30+ -I"$IOTC_ROOT/core/infrastructure/schedule/include"
31+ -I"$IOTC_ROOT/core/device/model/include"
32+ -I"$IOTC_ROOT/core/device/config/include"
33+ -I"$IOTC_ROOT/core/infrastructure/frameworks/event_loop/include"
34+ -I"$IOTC_ROOT/core/infrastructure/frameworks/service/include"
35+ -I"$IOTC_ROOT/core/wifi/application/softap"
36+ -I"$IOTC_ROOT/core/wifi/application/connect"
37+ -I"$IOTC_ROOT/core/wifi/application/softap/include"
38+ -I"$IOTC_ROOT/core/wifi/interfaces/include"
39+ -I"$IOTC_ROOT/core/wifi/domain/e2e_ctl/include"
40+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/coap/codec/include"
41+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/coap/endpoint/include"
42+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/coap/stack/include"
43+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/session/include"
44+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/buffer/include"
45+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/buffer/include"
46+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/link/include"
47+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/socket/include"
48+ -I"$IOTC_ROOT/core/wifi/infrastructure/utils/include"
49+ -I"$IOTC_ROOT/core/wifi/infrastructure/transport/coap/endpoint"
50+ -I"$IOTC_ROOT/core/network/transport/buffer/include"
51+ -I"$IOTC_ROOT/core/network/transport/coap/codec/include"
52+ -I"$IOTC_ROOT/core/network/transport/coap/endpoint/include"
53+ -I"$IOTC_ROOT/core/network/transport/coap/endpoint"
54+ -I"$IOTC_ROOT/core/network/transport/link/include"
55+ -I"$IOTC_ROOT/core/network/transport/session/include"
56+ -I"$IOTC_ROOT/core/network/transport/socket/include"
57+ -I"$OHOS_ROOT/third_party/cJSON"
58+)
59+ 
60+overall=0
61+ 
62+for version in 3 1; do
63+ echo "build test_softap_name (IOTC_ONECONNECT_VERSION_CODE=$version)"
64+ gcc "${CFLAGS[@]}" -DIOTC_ONECONNECT_VERSION_CODE=$version \
65+ "$SCRIPT_DIR/test_softap_name.c" \
66+ "$IOTC_ROOT/sdk/oh_connect/biz/iotc_oh_softap_name.c" \
67+ "$SCRIPT_DIR/stubs/softap_deps_stub.c" \
68+ -o "$OUT_DIR/test_softap_name_v$version" || exit 1
69+ "$OUT_DIR/test_softap_name_v$version"
70+ [ $? -ne 0 ] && overall=1
71+done
72+ 
73+echo "build test_coap_client_runtime"
74+gcc "${CFLAGS[@]}" -ffunction-sections -fdata-sections \
75+ "$SCRIPT_DIR/test_coap_client_runtime.c" \
76+ "$IOTC_ROOT/core/wifi/infrastructure/transport/coap/endpoint/coap_endpoint_client.c" \
77+ "$IOTC_ROOT/core/network/transport/coap/endpoint/net_coap_endpoint_client.c" \
78+ -Wl,--gc-sections \
79+ -o "$OUT_DIR/test_coap_client_runtime" || exit 1
80+"$OUT_DIR/test_coap_client_runtime"
81+[ $? -ne 0 ] && overall=1
82+ 
83+echo "build test_softap_device_info"
84+gcc "${CFLAGS[@]}" -ffunction-sections -fdata-sections \
85+ -Wno-unused-parameter \
86+ -DIOTC_ONECONNECT_VERSION_NAME='"3.0"' \
87+ "$SCRIPT_DIR/test_softap_device_info.c" \
88+ "$IOTC_ROOT/core/wifi/application/softap/svc_softap_coap_provision.c" \
89+ "$IOTC_ROOT/core/device/model/dev_info_mdl.c" \
90+ "$IOTC_ROOT/core/infrastructure/utils/utils_json.c" \
91+ "$IOTC_ROOT/adapter/json/cjson/iotc_json.c" \
92+ "$OHOS_ROOT/third_party/cJSON/cJSON.c" \
93+ -Wl,--gc-sections \
94+ -o "$OUT_DIR/test_softap_device_info" || exit 1
95+"$OUT_DIR/test_softap_device_info"
96+[ $? -ne 0 ] && overall=1
97+ 
98+echo "build test_softap_security_chain"
99+gcc "${CFLAGS[@]}" -ffunction-sections -fdata-sections \
100+ "$SCRIPT_DIR/test_softap_security_chain.c" \
101+ "$IOTC_ROOT/core/wifi/application/softap/svc_softap_coap.c" \
102+ -Wl,--gc-sections \
103+ -o "$OUT_DIR/test_softap_security_chain" || exit 1
104+"$OUT_DIR/test_softap_security_chain"
105+[ $? -ne 0 ] && overall=1
106+ 
107+echo "check getProvisionRequest method"
108+if ! grep -Fq '{UTILS_BIT(COAP_METHOD_TYPE_POST), STR_URI_GET_PROVISION_REQUEST, NULL,' \
109+ "$IOTC_ROOT/core/wifi/application/softap/svc_softap_sess.c"; then
110+ echo "getProvisionRequest must use POST" >&2
111+ overall=1
112+fi
113+if grep -Fq '{UTILS_BIT(COAP_METHOD_TYPE_GET), STR_URI_GET_PROVISION_REQUEST, NULL,' \
114+ "$IOTC_ROOT/core/wifi/application/softap/svc_softap_sess.c"; then
115+ echo "getProvisionRequest must not use GET" >&2
116+ overall=1
117+fi
118+ 
119+if [ "$overall" -eq 0 ]; then
120+ echo "SOFTAP HOST TESTS PASSED"
121+else
122+ echo "SOFTAP HOST TESTS FAILED"
123+fi
124+exit "$overall"
Atest/unit/softap_host/stubs/softap_deps_stub.c+44-0
@@ -0,0 +1,44 @@
1+/*
2+ * Stub implementations of SoftAP SSID/channel dependencies for host tests.
3+ */
4+ 
5+#include "softap_deps_stub.h"
6+ 
7+#include <string.h>
8+ 
9+const IotcDeviceInfo *g_testDevInfo;
10+DevBindStatus g_testBindStatus;
11+uint8_t g_testDataConfig;
12+int32_t g_testSecurityRandomRet;
13+uint32_t g_testSecurityRandomValue;
14+ 
15+const IotcDeviceInfo *ModelGetDevInfo(void)
16+{
17+ return g_testDevInfo;
18+}
19+ 
20+DevBindStatus DevSvcProxyGetBindStatus(void)
21+{
22+ return g_testBindStatus;
23+}
24+ 
25+uint8_t ModelGetDataConfig(void)
26+{
27+ return g_testDataConfig;
28+}
29+ 
30+int32_t SecurityRandom(uint8_t *buf, uint32_t bufLen)
31+{
32+ if (g_testSecurityRandomRet != 0) {
33+ return g_testSecurityRandomRet;
34+ }
35+ (void)memset(buf, 0, bufLen);
36+ (void)memcpy(buf, &g_testSecurityRandomValue,
37+ (bufLen < sizeof(g_testSecurityRandomValue)) ? bufLen : sizeof(g_testSecurityRandomValue));
38+ return 0;
39+}
40+ 
41+uint32_t SecurityRandomUint32(void)
42+{
43+ return g_testSecurityRandomValue;
44+}
Atest/unit/softap_host/stubs/softap_deps_stub.h+20-0
@@ -0,0 +1,20 @@
1+/*
2+ * Test knobs for SoftAP host tests (fixtures controlled per test case).
3+ */
4+ 
5+#ifndef SOFTAP_DEPS_STUB_H
6+#define SOFTAP_DEPS_STUB_H
7+ 
8+#include <stdbool.h>
9+#include <stdint.h>
10+ 
11+#include "iotc_prof_def.h"
12+#include "iotc_svc_dev.h"
13+ 
14+extern const IotcDeviceInfo *g_testDevInfo;
15+extern DevBindStatus g_testBindStatus;
16+extern uint8_t g_testDataConfig;
17+extern int32_t g_testSecurityRandomRet;
18+extern uint32_t g_testSecurityRandomValue;
19+ 
20+#endif /* SOFTAP_DEPS_STUB_H */
Atest/unit/softap_host/test_coap_client_runtime.c+389-0
@@ -0,0 +1,389 @@
1+/* CoAP client timeout scheduling and request-registration regression tests. */
2+ 
3+#include <stdbool.h>
4+#include <stdint.h>
5+#include <stdio.h>
6+#include <stdlib.h>
7+#include <string.h>
8+ 
9+#include "coap_endpoint_priv.h"
10+#include "iotc_errcode.h"
11+#include "net_coap_endpoint_priv.h"
12+#include "test_framework.h"
13+ 
14+int g_testChecks;
15+int g_testFailures;
16+ 
17+struct UtilsExMutex {
18+ uint32_t unused;
19+};
20+ 
21+static uint32_t g_now;
22+static int32_t g_lockDepth;
23+static bool g_failAlloc;
24+static uint32_t g_wifiTimeoutCallbacks;
25+static uint32_t g_netTimeoutCallbacks;
26+static uint32_t g_netContextCallbacks;
27+static uint32_t g_netContextCleanups;
28+ 
29+typedef struct {
30+ uint32_t marker;
31+ bool expectTimeout;
32+} NetRequestContext;
33+ 
34+uint32_t IotcGetSysTimeMs(void)
35+{
36+ return g_now;
37+}
38+ 
39+uint32_t UtilsDeltaTime(uint32_t timeNew, uint32_t timeOld)
40+{
41+ return timeNew - timeOld;
42+}
43+ 
44+bool UtilsExMutexLockInner(UtilsExMutex *mutex, uint32_t timeout, const char *func)
45+{
46+ (void)mutex;
47+ (void)timeout;
48+ (void)func;
49+ ++g_lockDepth;
50+ return true;
51+}
52+ 
53+void UtilsExMutexUnlockInner(UtilsExMutex *mutex, const char *func)
54+{
55+ (void)mutex;
56+ (void)func;
57+ --g_lockDepth;
58+}
59+ 
60+void *IotcMalloc(uint32_t size)
61+{
62+ return g_failAlloc ? NULL : malloc(size);
63+}
64+ 
65+void IotcFree(void *ptr)
66+{
67+ free(ptr);
68+}
69+ 
70+int32_t SecurityRandom(uint8_t *buf, uint32_t len)
71+{
72+ (void)memset(buf, 0x5A, len);
73+ return IOTC_OK;
74+}
75+ 
76+int32_t CoapEndpointSendPacket(CoapEndpoint *endpoint, const CoapBuildPacket *build,
77+ CoapPacket *pkt, uint32_t preSize, const SocketAddr *addr)
78+{
79+ (void)endpoint;
80+ (void)preSize;
81+ (void)addr;
82+ pkt->header = build->header;
83+ (void)memcpy(pkt->token, build->token, build->header.tkl);
84+ return IOTC_OK;
85+}
86+ 
87+int32_t NetCoapEndpointSendPacket(NetCoapEndpoint *endpoint, const NetCoapBuildPacket *build,
88+ NetCoapPacket *pkt, uint32_t preSize, const NetSocketAddr *addr)
89+{
90+ (void)endpoint;
91+ (void)preSize;
92+ (void)addr;
93+ pkt->header = build->header;
94+ (void)memcpy(pkt->token, build->token, build->header.tkl);
95+ return IOTC_OK;
96+}
97+ 
98+static void WifiTimeoutHandler(const CoapPacket *resp, const SocketAddr *addr, void *userData, bool timeout)
99+{
100+ (void)resp;
101+ (void)addr;
102+ (void)userData;
103+ CHECK(timeout);
104+ ++g_wifiTimeoutCallbacks;
105+}
106+ 
107+static void NetTimeoutHandler(const NetCoapPacket *resp, const NetSocketAddr *addr,
108+ NetCoapUserData *userData, bool timeout)
109+{
110+ (void)resp;
111+ (void)addr;
112+ (void)userData;
113+ CHECK(timeout);
114+ ++g_netTimeoutCallbacks;
115+}
116+ 
117+static void NetContextHandler(const NetCoapPacket *resp,
118+ const NetSocketAddr *addr, NetCoapUserData *userData, bool timeout)
119+{
120+ NetRequestContext *context = userData != NULL ? userData->ptr : NULL;
121+ CHECK(context != NULL);
122+ if (context != NULL) {
123+ CHECK_EQ_INT(0xC0A5U, context->marker);
124+ CHECK_EQ_INT(context->expectTimeout, timeout);
125+ }
126+ CHECK((timeout && resp == NULL && addr == NULL) ||
127+ (!timeout && resp != NULL && addr != NULL));
128+ ++g_netContextCallbacks;
129+}
130+ 
131+static void NetContextCleanup(NetCoapUserData *userData)
132+{
133+ CHECK(userData != NULL && userData->ptr != NULL);
134+ if (userData != NULL && userData->ptr != NULL) {
135+ NetRequestContext *context = userData->ptr;
136+ (void)memset(context, 0, sizeof(*context));
137+ free(context);
138+ userData->ptr = NULL;
139+ }
140+ ++g_netContextCleanups;
141+}
142+ 
143+static CoapReqNode *AddWifiReq(CoapEndpoint *endpoint, uint32_t sentAt)
144+{
145+ CoapReqNode *node = (CoapReqNode *)calloc(1, sizeof(*node));
146+ CHECK(node != NULL);
147+ if (node != NULL) {
148+ node->time = sentAt;
149+ node->respHandler = WifiTimeoutHandler;
150+ LIST_INSERT_BEFORE(&node->node, &endpoint->client.reqList);
151+ ++endpoint->client.reqCnt;
152+ }
153+ return node;
154+}
155+ 
156+static NetCoapReqNode *AddNetReq(NetCoapEndpoint *endpoint, uint32_t sentAt)
157+{
158+ NetCoapReqNode *node = (NetCoapReqNode *)calloc(1, sizeof(*node));
159+ CHECK(node != NULL);
160+ if (node != NULL) {
161+ node->time = sentAt;
162+ node->respHandler = NetTimeoutHandler;
163+ LIST_INSERT_BEFORE(&node->node, &endpoint->client.reqList);
164+ ++endpoint->client.reqCnt;
165+ }
166+ return node;
167+}
168+ 
169+static void TestWifiTimeoutScheduling(void)
170+{
171+ struct UtilsExMutex mutex = {0};
172+ CoapEndpoint endpoint = {0};
173+ endpoint.mutex = &mutex;
174+ endpoint.client.timeout = 5000U;
175+ LIST_INIT(&endpoint.client.reqList);
176+ (void)AddWifiReq(&endpoint, 1000U);
177+ 
178+ g_now = 1001U;
179+ uint32_t wait = UINT32_MAX;
180+ CoapClientReqPrepare(&endpoint, &wait);
181+ CHECK_EQ_INT(4999, wait);
182+ CHECK(!CoapEndpointReqCheck(&endpoint, g_now));
183+ 
184+ g_now = 5999U;
185+ wait = 10000U;
186+ CoapClientReqPrepare(&endpoint, &wait);
187+ CHECK_EQ_INT(1, wait);
188+ CHECK(!CoapEndpointReqCheck(&endpoint, g_now));
189+ 
190+ g_now = 6000U;
191+ wait = UINT32_MAX;
192+ CoapClientReqPrepare(&endpoint, &wait);
193+ CHECK_EQ_INT(0, wait);
194+ CHECK(CoapEndpointReqCheck(&endpoint, g_now));
195+ CoapClientRespTimeoutCheck(&endpoint);
196+ CHECK_EQ_INT(1, g_wifiTimeoutCallbacks);
197+ CHECK_EQ_INT(0, endpoint.client.reqCnt);
198+ CHECK(LIST_EMPTY(&endpoint.client.reqList));
199+ CHECK_EQ_INT(0, g_lockDepth);
200+}
201+ 
202+static void TestWifiMultipleTimeoutCallbacks(void)
203+{
204+ struct UtilsExMutex mutex = {0};
205+ CoapEndpoint endpoint = {0};
206+ endpoint.mutex = &mutex;
207+ endpoint.client.timeout = 100U;
208+ LIST_INIT(&endpoint.client.reqList);
209+ (void)AddWifiReq(&endpoint, 10U);
210+ (void)AddWifiReq(&endpoint, 20U);
211+ g_now = 120U;
212+ g_wifiTimeoutCallbacks = 0U;
213+ CoapClientRespTimeoutCheck(&endpoint);
214+ CHECK_EQ_INT(2, g_wifiTimeoutCallbacks);
215+ CHECK_EQ_INT(0, endpoint.client.reqCnt);
216+ CHECK(LIST_EMPTY(&endpoint.client.reqList));
217+ CHECK_EQ_INT(0, g_lockDepth);
218+}
219+ 
220+static void TestWifiRegistrationErrorsPropagate(void)
221+{
222+ struct UtilsExMutex mutex = {0};
223+ CoapEndpoint endpoint = {0};
224+ endpoint.mutex = &mutex;
225+ endpoint.client.tkl = COAP_TOKEN_MAX_LEN;
226+ endpoint.client.timeout = 5000U;
227+ LIST_INIT(&endpoint.client.reqList);
228+ CoapClientReqParam param = {
229+ .type = COAP_MSG_TYPE_CON,
230+ .code = COAP_METHOD_TYPE_POST,
231+ .respHandler = WifiTimeoutHandler,
232+ };
233+ SocketAddr addr = {5683U, 0xC0A804FFU};
234+ CoapPacket packet;
235+ 
236+ endpoint.client.reqCnt = UINT8_MAX;
237+ CHECK(CoapClientSendReq(&endpoint, &param, &addr, &packet) != IOTC_OK);
238+ CHECK_EQ_INT(0, g_lockDepth);
239+ 
240+ endpoint.client.reqCnt = 0U;
241+ g_failAlloc = true;
242+ CHECK_EQ_INT(IOTC_ADAPTER_MEM_ERR_MALLOC, CoapClientSendReq(&endpoint, &param, &addr, &packet));
243+ g_failAlloc = false;
244+ CHECK_EQ_INT(0, g_lockDepth);
245+}
246+ 
247+static void TestNetTimeoutScheduling(void)
248+{
249+ struct UtilsExMutex mutex = {0};
250+ NetCoapEndpoint endpoint = {0};
251+ endpoint.mutex = &mutex;
252+ endpoint.client.timeout = 5000U;
253+ LIST_INIT(&endpoint.client.reqList);
254+ (void)AddNetReq(&endpoint, UINT32_MAX - 99U);
255+ 
256+ g_now = 50U;
257+ uint32_t wait = UINT32_MAX;
258+ NetCoapClientReqPrepare(&endpoint, &wait);
259+ CHECK_EQ_INT(4850, wait);
260+ CHECK(!NetCoapEndpointReqCheck(&endpoint, g_now));
261+ 
262+ g_now = 4900U;
263+ wait = UINT32_MAX;
264+ NetCoapClientReqPrepare(&endpoint, &wait);
265+ CHECK_EQ_INT(0, wait);
266+ CHECK(NetCoapEndpointReqCheck(&endpoint, g_now));
267+ NetCoapClientRespTimeoutCheck(&endpoint);
268+ CHECK_EQ_INT(1, g_netTimeoutCallbacks);
269+ CHECK_EQ_INT(0, endpoint.client.reqCnt);
270+ CHECK(LIST_EMPTY(&endpoint.client.reqList));
271+ CHECK_EQ_INT(0, g_lockDepth);
272+}
273+ 
274+static void TestNetRegistrationErrorsPropagate(void)
275+{
276+ struct UtilsExMutex mutex = {0};
277+ NetCoapEndpoint endpoint = {0};
278+ endpoint.mutex = &mutex;
279+ endpoint.client.tkl = NET_COAP_TOKEN_MAX_LEN;
280+ endpoint.client.timeout = 5000U;
281+ LIST_INIT(&endpoint.client.reqList);
282+ NetCoapClientReqParam param = {
283+ .type = NET_COAP_MSG_TYPE_CON,
284+ .code = NET_COAP_METHOD_TYPE_POST,
285+ .respHandler = NetTimeoutHandler,
286+ };
287+ NetSocketAddr addr = {5683U, 0xC0A804FFU};
288+ NetCoapPacket packet;
289+ 
290+ endpoint.client.reqCnt = UINT8_MAX;
291+ CHECK(NetCoapClientSendReq(&endpoint, &param, &addr, &packet) != IOTC_OK);
292+ CHECK_EQ_INT(0, g_lockDepth);
293+ 
294+ endpoint.client.reqCnt = 0U;
295+ g_failAlloc = true;
296+ CHECK_EQ_INT(IOTC_ADAPTER_MEM_ERR_MALLOC, NetCoapClientSendReq(&endpoint, &param, &addr, &packet));
297+ g_failAlloc = false;
298+ CHECK_EQ_INT(0, g_lockDepth);
299+}
300+ 
301+static NetRequestContext *NewNetContext(bool expectTimeout)
302+{
303+ NetRequestContext *context = calloc(1, sizeof(*context));
304+ CHECK(context != NULL);
305+ if (context != NULL) {
306+ context->marker = 0xC0A5U;
307+ context->expectTimeout = expectTimeout;
308+ }
309+ return context;
310+}
311+ 
312+static void InitNetEndpoint(NetCoapEndpoint *endpoint,
313+ struct UtilsExMutex *mutex)
314+{
315+ (void)memset(endpoint, 0, sizeof(*endpoint));
316+ endpoint->mutex = mutex;
317+ endpoint->client.tkl = NET_COAP_TOKEN_MAX_LEN;
318+ endpoint->client.timeout = 50U;
319+ LIST_INIT(&endpoint->client.reqList);
320+}
321+ 
322+static int32_t SendContextRequest(NetCoapEndpoint *endpoint,
323+ NetRequestContext *context)
324+{
325+ NetCoapClientReqParam param = {
326+ .type = NET_COAP_MSG_TYPE_CON,
327+ .code = NET_COAP_METHOD_TYPE_POST,
328+ .respHandler = NetContextHandler,
329+ .respUserData = { .ptr = context },
330+ .useRespUserData = true,
331+ .respUserDataCleanup = NetContextCleanup,
332+ };
333+ NetSocketAddr addr = {5683U, 0xC0A804FFU};
334+ NetCoapPacket packet = {0};
335+ return NetCoapClientSendReq(endpoint, &param, &addr, &packet);
336+}
337+ 
338+static void TestNetPerRequestContext(void)
339+{
340+ struct UtilsExMutex mutex = {0};
341+ NetCoapEndpoint endpoint = {0};
342+ InitNetEndpoint(&endpoint, &mutex);
343+ 
344+ NetRequestContext *normal = NewNetContext(false);
345+ CHECK_EQ_INT(IOTC_OK, SendContextRequest(&endpoint, normal));
346+ NetCoapReqNode *node = CONTAINER_OF(LIST_HEAD(&endpoint.client.reqList),
347+ NetCoapReqNode, node);
348+ NetCoapPacket response = {0};
349+ response.header.msgId = node->msgId;
350+ response.header.tkl = endpoint.client.tkl;
351+ (void)memcpy(response.token, node->token, response.header.tkl);
352+ NetSocketAddr peer = {5683U, 0xC0A80402U};
353+ CHECK_EQ_INT(IOTC_OK, NetCoapEndpointClientRecvRespPacket(&endpoint,
354+ &response, &peer));
355+ CHECK_EQ_INT(1, g_netContextCallbacks);
356+ CHECK_EQ_INT(1, g_netContextCleanups);
357+ CHECK_EQ_INT(0, endpoint.client.reqCnt);
358+ 
359+ NetRequestContext *expired = NewNetContext(true);
360+ g_now = 100U;
361+ CHECK_EQ_INT(IOTC_OK, SendContextRequest(&endpoint, expired));
362+ g_now = 150U;
363+ NetCoapClientRespTimeoutCheck(&endpoint);
364+ CHECK_EQ_INT(2, g_netContextCallbacks);
365+ CHECK_EQ_INT(2, g_netContextCleanups);
366+ CHECK_EQ_INT(0, endpoint.client.reqCnt);
367+ 
368+ NetRequestContext *teardown = NewNetContext(false);
369+ CHECK_EQ_INT(IOTC_OK, SendContextRequest(&endpoint, teardown));
370+ NetCoapEndpointClientDeinit(&endpoint.client);
371+ CHECK_EQ_INT(2, g_netContextCallbacks);
372+ CHECK_EQ_INT(3, g_netContextCleanups);
373+ CHECK_EQ_INT(0, endpoint.client.reqCnt);
374+ CHECK(LIST_EMPTY(&endpoint.client.reqList));
375+ CHECK_EQ_INT(0, g_lockDepth);
376+}
377+ 
378+int main(void)
379+{
380+ setbuf(stdout, NULL);
381+ TestWifiTimeoutScheduling();
382+ TestWifiMultipleTimeoutCallbacks();
383+ TestWifiRegistrationErrorsPropagate();
384+ TestNetTimeoutScheduling();
385+ TestNetRegistrationErrorsPropagate();
386+ TestNetPerRequestContext();
387+ printf("test_coap_client_runtime: %d checks, %d failures\n", g_testChecks, g_testFailures);
388+ return g_testFailures == 0 ? 0 : 1;
389+}
Atest/unit/softap_host/test_softap_device_info.c+166-0
@@ -0,0 +1,166 @@
1+/* SoftAP /DeviceInfo wire-contract regression tests. */
2+ 
3+#include <stdbool.h>
4+#include <stdint.h>
5+#include <stdio.h>
6+#include <string.h>
7+ 
8+#include "dev_info_mdl.h"
9+#include "coap_endpoint_priv.h"
10+#include "iotc_errcode.h"
11+#include "iotc_svc_dev.h"
12+#include "svc_softap_coap_provision.h"
13+#include "test_framework.h"
14+#include "utils_json.h"
15+ 
16+int g_testChecks;
17+int g_testFailures;
18+ 
19+static const IotcDeviceInfo g_deviceInfo = {
20+ .sn = "12345678",
21+ .prodId = "00414",
22+ .subProdId = "",
23+ .model = "DISP-01",
24+ .devTypeId = "1414",
25+ .devTypeName = "Disp",
26+ .manuId = "116",
27+ .manuName = "SwanLink",
28+ .devName = "OneConnectName",
29+ .fwv = "1.0.0",
30+ .hwv = "1.0.0",
31+ .swv = "1.0.0",
32+ .protType = IOTC_PROT_TYPE_WIFI,
33+};
34+ 
35+static int g_buildCalls;
36+static int g_sendCalls;
37+static uint8_t g_responseType;
38+static uint8_t g_responseCode;
39+static char g_responseJson[1024];
40+ 
41+bool UtilsIsEmptyStr(const char *str)
42+{
43+ return str == NULL || str[0] == '\0';
44+}
45+ 
46+const IotcDeviceInfo *ModelGetDevInfo(void)
47+{
48+ return &g_deviceInfo;
49+}
50+ 
51+int32_t DevSvcProxyGetAuthInfo(bool *isExist, DevAuthInfo *authInfo)
52+{
53+ if (isExist == NULL || authInfo == NULL) {
54+ return IOTC_ERR_PARAM_INVALID;
55+ }
56+ *isExist = false;
57+ (void)memset(authInfo, 0, sizeof(*authInfo));
58+ return IOTC_OK;
59+}
60+ 
61+int32_t GetWifiMacAddrStr(char *macStr, uint32_t len)
62+{
63+ if (macStr == NULL || len < sizeof("50:65:13:B1:6E:52")) {
64+ return IOTC_ERR_PARAM_INVALID;
65+ }
66+ (void)snprintf(macStr, len, "%s", "50:65:13:B1:6E:52");
67+ return IOTC_OK;
68+}
69+ 
70+int32_t CoapServerBuildDefaultRespParam(CoapServerRespParam *param,
71+ const CoapPacket *reqPkt, IotcJson *respJson)
72+{
73+ if (param == NULL || reqPkt == NULL || respJson == NULL) {
74+ return IOTC_ERR_PARAM_INVALID;
75+ }
76+ ++g_buildCalls;
77+ (void)memset(param, 0, sizeof(*param));
78+ param->req = reqPkt;
79+ param->type = COAP_MSG_TYPE_ACK;
80+ param->code = COAP_RESPONSE_CODE_CONTENT;
81+ param->payloadUserData = respJson;
82+ char *json = IotcJsonPrint(respJson);
83+ if (json == NULL) {
84+ return IOTC_ADAPTER_JSON_ERR_PRINT;
85+ }
86+ (void)snprintf(g_responseJson, sizeof(g_responseJson), "%s", json);
87+ IotcJsonFreePrint(json);
88+ return IOTC_OK;
89+}
90+ 
91+int32_t CoapServerSendResp(CoapEndpoint *endpoint, const CoapServerRespParam *param,
92+ const SocketAddr *addr, CoapPacket *packetBuf)
93+{
94+ if (endpoint == NULL || param == NULL || addr == NULL || packetBuf == NULL) {
95+ return IOTC_ERR_PARAM_INVALID;
96+ }
97+ ++g_sendCalls;
98+ g_responseType = param->type;
99+ g_responseCode = param->code;
100+ return IOTC_OK;
101+}
102+ 
103+static void Reset(void)
104+{
105+ g_buildCalls = 0;
106+ g_sendCalls = 0;
107+ g_responseType = UINT8_MAX;
108+ g_responseCode = UINT8_MAX;
109+ (void)memset(g_responseJson, 0, sizeof(g_responseJson));
110+}
111+ 
112+static void TestEmptyGetReturnsHiv20(void)
113+{
114+ Reset();
115+ CoapEndpoint endpoint = {0};
116+ CoapPacket request = {0};
117+ SocketAddr addr = {0};
118+ request.header.type = COAP_MSG_TYPE_CON;
119+ request.header.code = COAP_METHOD_TYPE_GET;
120+ request.payload.data = NULL;
121+ request.payload.len = 0;
122+ 
123+ SoftapCoapDeviceInfoReqHandler(&endpoint, &request, &addr, NULL);
124+ 
125+ CHECK_EQ_INT(1, g_buildCalls);
126+ CHECK_EQ_INT(1, g_sendCalls);
127+ CHECK_EQ_INT(COAP_MSG_TYPE_ACK, g_responseType);
128+ CHECK_EQ_INT(COAP_RESPONSE_CODE_CONTENT, g_responseCode);
129+ CHECK(strstr(g_responseJson, "\"hiv\":\"2.0\"") != NULL);
130+ CHECK(strstr(g_responseJson, "\"hiv\":\"3.0\"") == NULL);
131+ CHECK(strstr(g_responseJson, "\"devId\":\"\"") != NULL);
132+ CHECK(strstr(g_responseJson, "\"devInfo\":{") != NULL);
133+ IotcJson *response = IotcJsonParse(g_responseJson);
134+ int32_t errCode = -1;
135+ CHECK(response != NULL);
136+ CHECK_EQ_INT(IOTC_OK, UtilsJsonGetNum(response, "errCode", &errCode));
137+ CHECK_EQ_INT(IOTC_OK, errCode);
138+ IotcJsonDelete(response);
139+}
140+ 
141+static void TestNonEmptyGetIsRejected(void)
142+{
143+ Reset();
144+ CoapEndpoint endpoint = {0};
145+ uint8_t payload[] = {'{', '}'};
146+ CoapPacket request = {0};
147+ SocketAddr addr = {0};
148+ request.header.type = COAP_MSG_TYPE_CON;
149+ request.header.code = COAP_METHOD_TYPE_GET;
150+ request.payload.data = payload;
151+ request.payload.len = sizeof(payload);
152+ 
153+ SoftapCoapDeviceInfoReqHandler(&endpoint, &request, &addr, NULL);
154+ 
155+ CHECK_EQ_INT(0, g_buildCalls);
156+ CHECK_EQ_INT(0, g_sendCalls);
157+ CHECK(g_responseJson[0] == '\0');
158+}
159+ 
160+int main(void)
161+{
162+ TestEmptyGetReturnsHiv20();
163+ TestNonEmptyGetIsRejected();
164+ printf("softap DeviceInfo host: %d checks, %d failures\n", g_testChecks, g_testFailures);
165+ return g_testFailures == 0 ? 0 : 1;
166+}
Atest/unit/softap_host/test_softap_name.c+230-0
@@ -0,0 +1,230 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device 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+ * SoftAP SSID 生成测试:version>=2 带 z+p,version<2 只带 z。
18+ */
19+ 
20+#include <stdio.h>
21+#include <string.h>
22+ 
23+#include "iotc_errcode.h"
24+#include "softap_deps_stub.h"
25+#include "test_framework.h"
26+ 
27+int g_testChecks;
28+int g_testFailures;
29+ 
30+int32_t IotcOhGetSoftApName(uint8_t *ssid, uint32_t *len);
31+ 
32+#define EXPECTED_CAPACITY 0x7C
33+#define EXPECTED_ZERO_CONF 0x30 /* Temporary ASCII diagnostic for binary p=0x00. */
34+ 
35+#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)
36+#define EXPECTED_UNREG_HEAD "Oc"
37+#define EXPECTED_REG_HEAD "OC"
38+#define EXPECTED_VER_CHAR_STR IOTC_SOFTAP_PROTOCOL_VERSION
39+#define EXPECTED_SUFFIX_LEN 2
40+#else
41+#define EXPECTED_UNREG_HEAD "Oh"
42+#define EXPECTED_REG_HEAD "OH"
43+#define EXPECTED_VER_CHAR_STR "1"
44+#define EXPECTED_SUFFIX_LEN 1
45+#endif
46+ 
47+static IotcDeviceInfo MakeDevInfo(void)
48+{
49+ IotcDeviceInfo info = {0};
50+ info.manuName = "TestManu";
51+ info.devTypeName = "Lamp";
52+ info.prodId = "12345";
53+ info.sn = "SN00001234CDEF";
54+ return info;
55+}
56+ 
57+static IotcDeviceInfo g_devInfo;
58+ 
59+static void ResetFixture(void)
60+{
61+ g_devInfo = MakeDevInfo();
62+ g_testDevInfo = &g_devInfo;
63+ g_testBindStatus = DEV_BIND_STATUS_NOT_BIND;
64+ g_testDataConfig = 0;
65+}
66+ 
67+/* 期望布局:head + name + -X + PID + SN4 + z [+ p]。 */
68+static void ExpectSsid(const char *expectedText, uint8_t expectedCap, uint8_t expectedConf)
69+{
70+ uint8_t ssid[64] = {0};
71+ uint32_t len = sizeof(ssid);
72+ CHECK_EQ_INT(IOTC_OK, IotcOhGetSoftApName(ssid, &len));
73+ uint32_t textLen = (uint32_t)strlen(expectedText);
74+ CHECK_EQ_INT(textLen + EXPECTED_SUFFIX_LEN, len);
75+ CHECK(memcmp(ssid, expectedText, textLen) == 0);
76+ CHECK_EQ_INT(expectedCap, ssid[textLen]);
77+#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)
78+ CHECK_EQ_INT(expectedConf, ssid[textLen + 1]);
79+#else
80+ (void)expectedConf;
81+#endif
82+}
83+ 
84+static void TestUnregisteredHappyPath(void)
85+{
86+ ResetFixture();
87+ ExpectSsid(EXPECTED_UNREG_HEAD "-TestManuLamp-" EXPECTED_VER_CHAR_STR "12345CDEF",
88+ EXPECTED_CAPACITY, EXPECTED_ZERO_CONF);
89+}
90+ 
91+static void TestRegisteredHead(void)
92+{
93+ ResetFixture();
94+ g_testBindStatus = DEV_BIND_STATUS_BIND;
95+ ExpectSsid(EXPECTED_REG_HEAD "-TestManuLamp-" EXPECTED_VER_CHAR_STR "12345CDEF",
96+ EXPECTED_CAPACITY, EXPECTED_ZERO_CONF);
97+}
98+ 
99+static void TestOemProvisionConfByte(void)
100+{
101+ ResetFixture();
102+#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)
103+ g_testDataConfig = 0x01;
104+ ExpectSsid(EXPECTED_UNREG_HEAD "-TestManuLamp-" EXPECTED_VER_CHAR_STR "12345CDEF",
105+ EXPECTED_CAPACITY, 0x01);
106+#endif
107+}
108+ 
109+static void TestTerminalConfigBinaryLengthContract(void)
110+{
111+ ResetFixture();
112+ uint8_t ssid[64] = {0};
113+ uint32_t len = sizeof(ssid);
114+ CHECK_EQ_INT(IOTC_OK, IotcOhGetSoftApName(ssid, &len));
115+#if defined(IOTC_ONECONNECT_VERSION_CODE) && (IOTC_ONECONNECT_VERSION_CODE >= 2)
116+ /* Diagnostic build maps binary p=0x00 to visible ASCII '0' (0x30). */
117+ CHECK_EQ_INT(EXPECTED_ZERO_CONF, ssid[len - 1]);
118+ CHECK_EQ_INT(len, strlen((const char *)ssid));
119+ 
120+ ResetFixture();
121+ g_testDataConfig = 0x01;
122+ len = sizeof(ssid);
123+ CHECK_EQ_INT(IOTC_OK, IotcOhGetSoftApName(ssid, &len));
124+ CHECK_EQ_INT(0x01, ssid[len - 1]);
125+ CHECK_EQ_INT(len, strlen((const char *)ssid));
126+#else
127+ CHECK_EQ_INT(EXPECTED_CAPACITY, ssid[len - 1]);
128+ CHECK_EQ_INT(len, strlen((const char *)ssid));
129+#endif
130+}
131+ 
132+static void TestWifiOnlyCapabilityBits(void)
133+{
134+ ResetFixture();
135+ uint8_t ssid[64] = {0};
136+ uint32_t len = sizeof(ssid);
137+ CHECK_EQ_INT(IOTC_OK, IotcOhGetSoftApName(ssid, &len));
138+ uint32_t textLen = (uint32_t)strlen(EXPECTED_UNREG_HEAD "-TestManuLamp-" EXPECTED_VER_CHAR_STR "12345CDEF");
139+ uint8_t capacity = ssid[textLen];
140+ CHECK_EQ_INT(0, capacity & 0x80);
141+ CHECK_EQ_INT(0x60, capacity & 0x60);
142+ CHECK((capacity & 0x10) != 0); /* 专用 SSID */
143+ CHECK((capacity & 0x08) != 0); /* SoftAP */
144+ CHECK((capacity & 0x04) != 0); /* CoAP */
145+ CHECK_EQ_INT(0, capacity & 0x03); /* WiFi Only不声明BLE/SLE能力 */
146+}
147+ 
148+static void TestLongNameTruncatedTo14(void)
149+{
150+ ResetFixture();
151+ g_devInfo.manuName = "ABCDEFGHIJ";
152+ g_devInfo.devTypeName = "KLMNOPQRST";
153+ ExpectSsid(EXPECTED_UNREG_HEAD "-ABCDEFGHIJKLMN-" EXPECTED_VER_CHAR_STR "12345CDEF",
154+ EXPECTED_CAPACITY, EXPECTED_ZERO_CONF);
155+}
156+ 
157+static void TestRepresentativeWifiOnlyIdentity(void)
158+{
159+ ResetFixture();
160+ g_devInfo.manuName = "SwanLink";
161+ g_devInfo.devTypeName = "Disp";
162+ g_devInfo.prodId = "00414";
163+ g_devInfo.sn = "12345678";
164+ ExpectSsid(EXPECTED_UNREG_HEAD "-SwanLinkDisp-" EXPECTED_VER_CHAR_STR "004145678",
165+ EXPECTED_CAPACITY, EXPECTED_ZERO_CONF);
166+}
167+ 
168+static void TestNullFieldsRejected(void)
169+{
170+ ResetFixture();
171+ g_testDevInfo = NULL;
172+ uint8_t ssid[64] = {0};
173+ uint32_t len = sizeof(ssid);
174+ CHECK_EQ_INT(IOTC_CORE_PROF_MDL_ERR_DEV_INFO_INVALID, IotcOhGetSoftApName(ssid, &len));
175+ 
176+ ResetFixture();
177+ g_devInfo.manuName = NULL;
178+ len = sizeof(ssid);
179+ CHECK_EQ_INT(IOTC_CORE_PROF_MDL_ERR_DEV_INFO_INVALID, IotcOhGetSoftApName(ssid, &len));
180+ 
181+ ResetFixture();
182+ g_devInfo.sn = NULL;
183+ len = sizeof(ssid);
184+ CHECK_EQ_INT(IOTC_CORE_PROF_MDL_ERR_DEV_INFO_INVALID, IotcOhGetSoftApName(ssid, &len));
185+ 
186+ ResetFixture();
187+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcOhGetSoftApName(NULL, &len));
188+ CHECK_EQ_INT(IOTC_ERR_PARAM_INVALID, IotcOhGetSoftApName(ssid, NULL));
189+}
190+ 
191+static void TestShortSnRejected(void)
192+{
193+ ResetFixture();
194+ g_devInfo.sn = "abc";
195+ uint8_t ssid[64] = {0};
196+ uint32_t len = sizeof(ssid);
197+ CHECK(IotcOhGetSoftApName(ssid, &len) != IOTC_OK);
198+}
199+ 
200+static void TestBufferBoundary(void)
201+{
202+ ResetFixture();
203+ uint8_t ssid[64] = {0};
204+ /* 文本部分 26 字节:缓冲区 26 放不下(含 NUL 需 27),必须失败 */
205+ uint32_t len = 26;
206+ CHECK(IotcOhGetSoftApName(ssid, &len) != IOTC_OK);
207+ /* 恰好装下 26 文本 + z [+ p]。 */
208+ len = 26 + EXPECTED_SUFFIX_LEN;
209+ CHECK_EQ_INT(IOTC_OK, IotcOhGetSoftApName(ssid, &len));
210+ CHECK_EQ_INT(26 + EXPECTED_SUFFIX_LEN, len);
211+}
212+ 
213+int main(void)
214+{
215+ setbuf(stdout, NULL);
216+ TestUnregisteredHappyPath();
217+ TestRegisteredHead();
218+ TestOemProvisionConfByte();
219+ TestTerminalConfigBinaryLengthContract();
220+ TestWifiOnlyCapabilityBits();
221+ TestLongNameTruncatedTo14();
222+ TestRepresentativeWifiOnlyIdentity();
223+ TestNullFieldsRejected();
224+ TestShortSnRejected();
225+ TestBufferBoundary();
226+ 
227+ printf("test_softap_name(v%d): %d checks, %d failures\n", IOTC_ONECONNECT_VERSION_CODE,
228+ g_testChecks, g_testFailures);
229+ return (g_testFailures == 0) ? 0 : 1;
230+}
Atest/unit/softap_host/test_softap_security_chain.c+92-0
@@ -0,0 +1,92 @@
1+/* Ensure business responses cannot bypass SPEKE encryption before negotiation. */
2+ 
3+#include <stdint.h>
4+#include <stdio.h>
5+#include <stdlib.h>
6+#include <string.h>
7+ 
8+#include "svc_softap_coap.h"
9+#include "svc_softap_ctx.h"
10+#include "svc_softap_sess.h"
11+#include "test_framework.h"
12+#include "utils_bit_map.h"
13+ 
14+int g_testChecks;
15+int g_testFailures;
16+ 
17+static int g_encryptCalls;
18+static int g_replaceCalls;
19+ 
20+SoftapPeerSess *SoftapGetPeerSess(const SocketAddr *addrInfo, SoftapSess *sess)
21+{
22+ (void)addrInfo;
23+ return &sess->peerSess[0];
24+}
25+ 
26+int32_t SpekeEncryptData(SpekeSession *session, const uint8_t *data, uint32_t dataLen,
27+ uint8_t **encData, uint32_t *encDataLen)
28+{
29+ (void)session;
30+ (void)data;
31+ ++g_encryptCalls;
32+ *encDataLen = dataLen + 1;
33+ *encData = malloc(*encDataLen);
34+ if (*encData == NULL) {
35+ return -1;
36+ }
37+ (void)memset(*encData, 0xA5, *encDataLen);
38+ return 0;
39+}
40+ 
41+int32_t CoapUtilsReplacePayload(CoapPacket *pkt, CoapBuffer *buf, const CoapData *payload)
42+{
43+ (void)buf;
44+ ++g_replaceCalls;
45+ pkt->payload = *payload;
46+ return 0;
47+}
48+ 
49+void IotcFree(void *ptr)
50+{
51+ free(ptr);
52+}
53+ 
54+static SessCode RunEncrypt(SoftapSess *sess)
55+{
56+ uint8_t storage[64] = {0};
57+ CoapPacket packet = {0};
58+ packet.payload.data = storage;
59+ packet.payload.len = 4;
60+ UtilsBuffer buffer = {.buffer = storage, .size = sizeof(storage), .len = 16};
61+ SocketAddr addr = {0};
62+ SessAddtlInfo info = {.addr = &addr, .userData = sess};
63+ return SoftapCoapMsgSendEncryptProcess((SessMsg *)&packet, &buffer, &info);
64+}
65+ 
66+static void TestSpekeRequired(void)
67+{
68+ SoftapSess sess = {0};
69+ g_encryptCalls = 0;
70+ g_replaceCalls = 0;
71+ 
72+ CHECK_EQ_INT(SESS_CODE_ERR, RunEncrypt(&sess));
73+ CHECK_EQ_INT(0, g_encryptCalls);
74+ CHECK_EQ_INT(0, g_replaceCalls);
75+ 
76+ sess.peerSess[0].speke = (SpekeSession *)(uintptr_t)1;
77+ CHECK_EQ_INT(SESS_CODE_ERR, RunEncrypt(&sess));
78+ CHECK_EQ_INT(0, g_encryptCalls);
79+ CHECK_EQ_INT(0, g_replaceCalls);
80+ 
81+ UTILS_BIT_SET(sess.peerSess[0].bitMap, SOFTAP_PEER_SESS_BIT_MAP_SPEKE_SESS_CREATED);
82+ CHECK_EQ_INT(SESS_CODE_CONTINUE, RunEncrypt(&sess));
83+ CHECK_EQ_INT(1, g_encryptCalls);
84+ CHECK_EQ_INT(1, g_replaceCalls);
85+}
86+ 
87+int main(void)
88+{
89+ TestSpekeRequired();
90+ printf("softap security chain host: %d checks, %d failures\n", g_testChecks, g_testFailures);
91+ return g_testFailures == 0 ? 0 : 1;
92+}
Mtools/check_backend_config.py+14-1
@@ -14,7 +14,20 @@ ROOT = Path(__file__).resolve().parents[1]
14 14 
15 15 
16def read_text(path):16def read_text(path):
17- return (ROOT / path).read_text(encoding="utf-8")17+ data = (ROOT / path).read_bytes()
18+ try:
19+ return data.decode("utf-8")
20+ except UnicodeDecodeError:
21+ # The 5.1 baseline still contains a few legacy GBK/GB18030 build
22+ # files. Backend checks only inspect ASCII GN/CMake tokens, so a
23+ # deterministic GB18030 fallback preserves their contents and avoids
24+ # silently skipping the check.
25+ try:
26+ return data.decode("gb18030")
27+ except UnicodeDecodeError:
28+ # Some inherited files mix legacy encodings. Latin-1 is a
29+ # byte-preserving last resort for the ASCII-only token checks.
30+ return data.decode("latin-1")
18 31 
19 32 
20def fail(message):33def fail(message):