"tcp_test",
#websocket 相关配置
"zhihui_websocket",
+
+ #zhihui_net 相关配置
+ "zhihui_net",
+ "n706a_net",
+
#ai 相关配置
"aiMediaService",
"aiService",
add_subdirectory_if_exist(imsl)
add_subdirectory_if_exist(minimp3)
add_subdirectory_if_exist(shake)
+add_subdirectory_if_exist(n706a_net)
+add_subdirectory_if_exist(zhihui_net)
${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_v2.1.3/src/include
${ROOT_DIR}/open_source/openharmony/third_party/lwip/lwip_adapter/liteos/src/include
${ROOT_DIR}/open_source/openharmony/third_party/cJSON/
- ${ROOT_DIR}/open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/websocket/include
- ${ROOT_DIR}/zhihui/websocket/os_ws
+ ${ROOT_DIR}/zhihui/zhihui_net/include
)
# 公共宏定义(可选)
#include "ln_api.h"
-/* for https/http client */
-#include "netstack/http/include/httpclient.h"
+/* 智慧网络统一接口(已包含 OpenHarmony 类型定义) */
+#include "zhihui_net.h"
/* for HMAC-SHA256 */
#include "mbedtls/md.h"
/* for MD5 file verify */
}
// 建立连接
- ret = HttpClientConn(&client, url);
+ ret = ZhihuiHttpClientConn(&client, url);
if (ret != 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] connection failed, ret=0x%x", ret);
fclose(fd);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
g_ln_is_file_downloading = 0;
return ERRCODE_FAIL;
}
// 发送GET请求
- ret = HttpClientSend(&client, url, HTTP_GET, &client_data);
+ ret = ZhihuiHttpClientSend(&client, url, HTTP_GET, &client_data);
if (ret != 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] send request failed, ret=0x%x", ret);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
g_ln_is_file_downloading = 0;
return ERRCODE_FAIL;
}
// 接收响应数据
do
{
- HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &client_data);
+ HTTPC_RESULT recv_res = ZhihuiHttpClientRecvResponse(&client, &client_data);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] recv_res = %d", recv_res);
if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] write failed, written=%zu, expected=%d",
written, client_data.contentBlockLen);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return ERRCODE_FAIL;
}
total_written += written;
}
}
- HttpClientUnprepare(&client_data);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
fclose(fd);
g_ln_is_file_downloading = 0;
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD] 下载过程结束,ret=%d", ret);
header_buf = (char *)malloc(128);
if (!header_buf)
{
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
fclose(fd);
return ERRCODE_FAIL;
}
if (hdr <= 0)
{
free(header_buf);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
fclose(fd);
return ERRCODE_FAIL;
}
}
/* 建立连接并发送请求 */
- ret = HttpClientConn(&client, url);
+ ret = ZhihuiHttpClientConn(&client, url);
if (ret != 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] connection failed, ret=0x%x", ret);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
return ERRCODE_FAIL;
}
- ret = HttpClientSend(&client, url, HTTP_GET, &client_data);
+ ret = ZhihuiHttpClientSend(&client, url, HTTP_GET, &client_data);
if (ret != 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] send request failed, ret=0x%x", ret);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return ERRCODE_FAIL;
}
/* 首次接收,判定状态码(200则改为覆盖写)*/
{
- HTTPC_RESULT first_recv = HttpClientRecvResponse(&client, &client_data);
+ HTTPC_RESULT first_recv = ZhihuiHttpClientRecvResponse(&client, &client_data);
if (!(first_recv == HTTP_SUCCESS || first_recv == HTTP_EAGAIN))
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[DOWNLOAD_RESUME] first recv failed: %d", first_recv);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return ERRCODE_FAIL;
}
if (!fd)
{
if (header_buf) free(header_buf);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return ERRCODE_FAIL;
}
resume_offset = 0;
written, client_data.contentBlockLen);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return ERRCODE_FAIL;
}
total_written += written;
/* 后续接收循环 */
while (client_data.isMore)
{
- HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &client_data);
+ HTTPC_RESULT recv_res = ZhihuiHttpClientRecvResponse(&client, &client_data);
if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN)
{
if (client_data.contentBlockLen > 0)
written, client_data.contentBlockLen);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return ERRCODE_FAIL;
}
total_written += written;
}
if (header_buf) free(header_buf);
- HttpClientUnprepare(&client_data);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
fclose(fd);
return ret;
}
return ret;
}
/* 自适应申请HttpClient缓冲(保证header>=2048),失败则逐步扩容重试 */
-HTTPC_RESULT ln_http_prepare_auto(HttpClientData *clientData, int header_size, int resp_size)
+int ln_http_prepare_auto(HttpClientData *clientData, int header_size, int resp_size)
{
if (header_size < 2048)
header_size = 2048;
if (resp_size < 2048)
resp_size = 2048;
- HTTPC_RESULT ret = HttpClientPrepare(clientData, header_size, resp_size);
+ HTTPC_RESULT ret = ZhihuiHttpClientPrepare(clientData, header_size, resp_size);
if (ret == HTTP_SUCCESS)
return ret;
int tries = 0;
resp_size = resp_size < (16 * 1024) ? (16 * 1024) : resp_size * 2;
if (resp_size > (256 * 1024))
resp_size = (256 * 1024);
- ret = HttpClientPrepare(clientData, header_size, resp_size);
+ ret = ZhihuiHttpClientPrepare(clientData, header_size, resp_size);
tries++;
}
return ret;
}
// 建立连接
- ret = HttpClientConn(&client, audio_url);
+ ret = ZhihuiHttpClientConn(&client, audio_url);
if (ret != 0)
{
printf("[SSE] connection failed, ret=0x%x\n", ret);
#if TEST_MP3_DOWNLOAD
fclose(fd);
#endif
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
free(save_path);
return ERRCODE_FAIL;
}
// 发送GET请求
- ret = HttpClientSend(&client, audio_url, HTTP_GET, &client_data);
+ ret = ZhihuiHttpClientSend(&client, audio_url, HTTP_GET, &client_data);
if (ret != 0)
{
printf("[SSE] send request failed, ret=0x%x\n", ret);
#if TEST_MP3_DOWNLOAD
fclose(fd);
#endif
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
free(save_path);
return ERRCODE_FAIL;
}
// 接收响应数据(支持取消)
do
{
- HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &client_data);
+ HTTPC_RESULT recv_res = ZhihuiHttpClientRecvResponse(&client, &client_data);
if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN)
{
if (client_data.contentBlockLen > 0)
printf("[SSE] write failed, written=%zu, expected=%d\n",
written, client_data.contentBlockLen);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return ERRCODE_FAIL;
}
total_written += written;
#if TEST_MP3_DOWNLOAD
fclose(fd);
#endif
- HttpClientUnprepare(&client_data);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
free(save_path);
return ERRCODE_SUCC;
}
{
printf("uapi_ln_http_server_init !\r\n");
+ /* 初始化 zhihui_net
+ * 说明:
+ * - PPP拨号模式:使用 OpenHarmony (PPP + lwIP + haisi_websocket)
+ * - 非PPP模式:使用 N706A AT指令(纯AT,不拨号)
+ */
+ #if defined(SUPPORT_PPP_SERVICE_INTERRUPT) || defined(SUPPORT_PPP_SERVICE_POLLING)
+ /* PPP拨号模式:使用OpenHarmony实现(基于lwIP) */
+ ZhihuiNetSetImplementation(ZHIHUI_NET_IMPL_OPENHARMONY);
+ printf("[LeiNiao] Using OpenHarmony (PPP + lwIP) network implementation\r\n");
+ #else
+ /* 非PPP模式:使用N706A AT指令 */
+ ZhihuiNetSetImplementation(ZHIHUI_NET_IMPL_N706A_AT);
+ printf("[LeiNiao] Using N706A AT command network implementation\r\n");
+ #endif
+ ZhihuiNetInit();
+
ln_send_taskcfg.create_queue_handle = create_ln_http_send_queue;
ln_send_taskcfg.attr.name = "ln_http_server_send_task";
ln_send_taskcfg.attr.priority = osPriorityNormal;
#include "stdint.h"
#include "errcode.h"
-/* HTTP types for helper function prototypes */
-#include "netstack/http/include/httpclient.h"
+/* 智慧网络统一接口 - 替换原 OpenHarmony netstack */
+#include "zhihui_net.h"
#include <unistd.h>
#include "wearable_log.h"
#include "ln_api_interface.h"
/* ============================== 公共工具函数(供拆分后的模块复用) ============================== */
-HTTPC_RESULT ln_http_prepare_auto(HttpClientData *clientData, int header_size, int resp_size);
+/* 使用 zhihui_net 接口 */
+int ln_http_prepare_auto(HttpClientData *clientData, int header_size, int resp_size);
uint32_t ln_parse_file_and_process_data(const char *file_path, int interface_type);
/* Token 本地缓存接口 */
free(header_buf);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
printf("device getCharacter header %s\n", header_buf);
return ERRCODE_FAIL;
}
- HTTPC_RESULT res = HttpClientGetRequest(&client, LN_DEVICE_GET_CHARACTER_URL, &clientData);
+ HTTPC_RESULT res = ZhihuiHttpClientGetRequest(&client, LN_DEVICE_GET_CHARACTER_URL, &clientData);
if (res != HTTP_SUCCESS)
{
printf("device getCharacter get failed %d\n", res);
}
end:
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_SUCC;
}
free(header_buf2);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf2);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf2);
printf("device getUserId header %s\n", header_buf2);
return ERRCODE_FAIL;
}
- HTTPC_RESULT res = HttpClientGetRequest(&client, LN_DEVICE_GET_USER_ID_URL, &clientData);
+ HTTPC_RESULT res = ZhihuiHttpClientGetRequest(&client, LN_DEVICE_GET_USER_ID_URL, &clientData);
if (res != HTTP_SUCCESS)
{
printf("device getUserId get failed %d\n", res);
}
end:
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf2);
return ERRCODE_SUCC;
}
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] snprintf failed for header_buf");
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] header_buf: %s", header_buf);
HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, LN_CLIENT_PREPARE_SIZE * 2);
return ERRCODE_FAIL;
}
- HTTPC_RESULT res = HttpClientGetRequest(&client, LN_DEVICE_GET_CHAT_STAMINA_URL, &clientData);
+ HTTPC_RESULT res = ZhihuiHttpClientGetRequest(&client, LN_DEVICE_GET_CHAT_STAMINA_URL, &clientData);
if (res != HTTP_SUCCESS)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] request failed: %d", res);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_FAIL;
}
if (body_len <= 0 || !clientData.responseBuf)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] empty body, code=%d", code);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_FAIL;
}
char *json_text = (char *)malloc((size_t)body_len + 1);
if (!json_text)
{
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_FAIL;
}
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] json parse failed");
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] raw body: %s", json_text);
free(json_text);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_FAIL;
}
cJSON_Delete(root);
free(json_text);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_FAIL;
}
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[CHAT_STAMINA] no data");
cJSON_Delete(root);
free(json_text);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_FAIL;
}
cJSON_Delete(root);
free(json_text);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_SUCC;
}
\ No newline at end of file
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_REPORT_STATUS] build header failed");
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
/* 组装 JSON */
cJSON *root = cJSON_CreateObject();
if (res != HTTP_SUCCESS)
{
free(body);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_REPORT_STATUS] request failed");
return ERRCODE_FAIL;
if (code != 200)
{
free(body);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_REPORT_STATUS] response code not 200");
return ERRCODE_FAIL;
free(body);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
return ERRCODE_SUCC;
}
free(header_buf);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
/* 组装 JSON 数组 */
cJSON *root = cJSON_CreateObject();
if (res != HTTP_SUCCESS)
{
free(body);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
return ERRCODE_FAIL;
}
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_REPORT_ALL] HTTP %d", code);
free(body);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
return ERRCODE_FAIL;
}
}
free(body);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
return ERRCODE_SUCC;
}
free(header_buf);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 8 * 1024);
if (prep != HTTP_SUCCESS)
char *url_with_params = (char *)malloc(url_cap);
if (!url_with_params)
{
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
return ERRCODE_FAIL;
}
LN_OTA_GET_TASK_URL, device_id, source_version);
if (ulen <= 0 || (size_t)ulen >= url_cap)
{
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
free(url_with_params);
return ERRCODE_FAIL;
printf("[OTA] GET %s\n", url_with_params);
- HTTPC_RESULT res = HttpClientGetRequest(&client, url_with_params, &clientData);
+ HTTPC_RESULT res = ZhihuiHttpClientGetRequest(&client, url_with_params, &clientData);
if (res != HTTP_SUCCESS)
{
printf("[OTA] HttpClientGetRequest error %d\n", res);
}
end:
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
if (header_buf)
free(header_buf);
if (url_with_params)
header_buf = (char *)malloc(128);
if (!header_buf)
{
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
fclose(fd);
return -1;
}
if (hdr <= 0)
{
free(header_buf);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
fclose(fd);
return -1;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
}
// 建立连接(与http_download流程一致)
- ret = HttpClientConn(&client, full_url);
+ ret = ZhihuiHttpClientConn(&client, full_url);
if (ret != 0)
{
printf("[OTA] connection failed, ret=0x%x\n", ret);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
return ret;
}
// 发送GET请求
- ret = HttpClientSend(&client, full_url, HTTP_GET, &client_data);
+ ret = ZhihuiHttpClientSend(&client, full_url, HTTP_GET, &client_data);
if (ret != 0)
{
printf("[OTA] send request failed, ret=0x%x\n", ret);
if (header_buf) free(header_buf);
- HttpClientClse(&client);
+ ZhihuiHttpClientClse(&client);
fclose(fd);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
return ret;
}
printf("[OTA] starting to receive data...\n");
// 先接收首包以判定状态码;如服务端未按 Range 返回(200),需覆盖写
- HTTPC_RESULT first_recv = HttpClientRecvResponse(&client, &client_data);
+ HTTPC_RESULT first_recv = ZhihuiHttpClientRecvResponse(&client, &client_data);
if (!(first_recv == HTTP_SUCCESS || first_recv == HTTP_EAGAIN))
{
printf("[OTA] first recv failed: %d\n", first_recv);
if (header_buf) free(header_buf);
- HttpClientClse(&client);
+ ZhihuiHttpClientClse(&client);
fclose(fd);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientUnprepare(&client_data);
return -1;
}
if (!fd)
{
if (header_buf) free(header_buf);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return -1;
}
resume_offset = 0;
printf("[OTA] write failed on first block, written=%zu, expected=%d\n", written, client_data.contentBlockLen);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return -1;
}
total_written += written;
// 后续接收循环
while (client_data.isMore)
{
- HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &client_data);
+ HTTPC_RESULT recv_res = ZhihuiHttpClientRecvResponse(&client, &client_data);
if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN)
{
if (client_data.contentBlockLen > 0)
printf("[OTA] write failed, written=%zu, expected=%d\n", written, client_data.contentBlockLen);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return -1;
}
total_written += written;
printf("[OTA] received more than expected! current_total=%lld > total_size=%d\n", current_total, total_size);
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
return -1;
}
}
// 资源清理
if (header_buf) free(header_buf);
fclose(fd);
- HttpClientClse(&client);
- HttpClientUnprepare(&client_data);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&client_data);
printf("[OTA] download process finished\n");
free(header_buf);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
printf("chatstream header_buf: %s\n", header_buf);
/* 准备缓冲区(自适应) */
clientData.isChunked = true;
int ret = -1;
- ret = HttpClientConn(&client, LN_CHAT_URL);
+ ret = ZhihuiHttpClientConn(&client, LN_CHAT_URL);
if (ret != 0)
{
printf("connection failed, ret=0x%x\n", ret);
free(body);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
return ret;
}
- ret = HttpClientSend(&client, LN_CHAT_URL, HTTP_POST, &clientData);
+ ret = ZhihuiHttpClientSend(&client, LN_CHAT_URL, HTTP_POST, &clientData);
if (ret != 0)
{
printf("send request failed, ret=0x%x\n", ret);
free(body);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
return ret;
}
{
printf("chatstream: fopen failed for %s:\n", speech_file_path);
free(body);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
return ERRCODE_FAIL;
}
size_t total_written = 0;
do
{
- HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &clientData);
+ HTTPC_RESULT recv_res = ZhihuiHttpClientRecvResponse(&client, &clientData);
printf("chatstream: recv response result: %d\n", recv_res);
if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN)
{
printf("chatstream: write failed, written=%zu, expected=%d\n",
written, clientData.contentBlockLen);
free(body);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
fclose(fd);
return ERRCODE_FAIL;
}
}
} while (clientData.isMore);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(body);
fclose(fd);
free(header_buf);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
printf("story choose header_buf: %s\n", header_buf);
/* 自适应准备 HTTP 缓冲区 */
clientData.isChunked = true;
int ret = -1;
- ret = HttpClientConn(&client, LN_STORY_CHOOSE_URL);
+ ret = ZhihuiHttpClientConn(&client, LN_STORY_CHOOSE_URL);
if (ret != 0)
{
printf("connection failed, ret=0x%x\n", ret);
free(body);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
return ret;
}
- ret = HttpClientSend(&client, LN_STORY_CHOOSE_URL, HTTP_POST, &clientData);
+ ret = ZhihuiHttpClientSend(&client, LN_STORY_CHOOSE_URL, HTTP_POST, &clientData);
if (ret != 0)
{
printf("send request failed, ret=0x%x\n", ret);
free(body);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
return ret;
}
{
printf("story choose: fopen failed for %s:\n", story_choose_file_path);
free(body);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
return ERRCODE_FAIL;
}
size_t total_written = 0;
do
{
- HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &clientData);
+ HTTPC_RESULT recv_res = ZhihuiHttpClientRecvResponse(&client, &clientData);
printf("story choose: recv response result: %d\n", recv_res);
if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN)
{
printf("story choose: write failed, written=%zu, expected=%d\n",
written, clientData.contentBlockLen);
free(body);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
fclose(fd);
return ERRCODE_FAIL;
}
} while (clientData.isMore);
free(body);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
fclose(fd);
if (total_written > 0)
free(header_buf);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 8 * 1024);
if (prep != HTTP_SUCCESS)
}
end:
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
if (header_buf)
free(header_buf);
return ERRCODE_SUCC;
{
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, 8 * 1024);
if (prep != HTTP_SUCCESS)
}
end:
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
return ERRCODE_SUCC;
}
free(header_buf);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
/* 自适应准备 HTTP 缓冲区 */
HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, LN_CLIENT_PREPARE_SIZE * 2);
clientData.postContentType = (char *)"application/json";
int ret = -1;
- ret = HttpClientConn(&client, LN_STORY_RANDOM_URL);
+ ret = ZhihuiHttpClientConn(&client, LN_STORY_RANDOM_URL);
if (ret != 0)
{
printf("[OTA] connection failed, ret=0x%x\n", ret);
free(header_buf);
free(json_body);
free(escaped_theme);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientUnprepare(&clientData);
return ret;
}
// 发送GET请求
- ret = HttpClientSend(&client, LN_STORY_RANDOM_URL, HTTP_POST, &clientData);
+ ret = ZhihuiHttpClientSend(&client, LN_STORY_RANDOM_URL, HTTP_POST, &clientData);
if (ret != 0)
{
printf("[OTA] send request failed, ret=0x%x\n", ret);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
free(json_body);
free(escaped_theme);
if (!fd)
{
printf("story choose: fopen failed for %s:\n", story_random_file_path);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
free(header_buf);
free(json_body);
free(escaped_theme);
size_t total_written = 0;
do
{
- HTTPC_RESULT recv_res = HttpClientRecvResponse(&client, &clientData);
+ HTTPC_RESULT recv_res = ZhihuiHttpClientRecvResponse(&client, &clientData);
printf("story random: recv response result: %d\n", recv_res);
if (recv_res == HTTP_SUCCESS || recv_res == HTTP_EAGAIN)
{
{
printf("story random: write failed, written=%zu, expected=%d\n",
written, clientData.contentBlockLen);
- HttpClientClse(&client);
- HttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
fclose(fd);
free(header_buf);
free(json_body);
}
} while (clientData.isMore);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
if (total_written > 0)
{
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
return ERRCODE_FAIL;
}
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
HTTPC_RESULT prep = ln_http_prepare_auto(&clientData, 2048, LN_CLIENT_PREPARE_SIZE*2);
if (prep != HTTP_SUCCESS)
return ERRCODE_FAIL;
}
- HTTPC_RESULT res = HttpClientGetRequest(&client, LN_TIMELINE_GET_STATUS_URL, &clientData);
+ HTTPC_RESULT res = ZhihuiHttpClientGetRequest(&client, LN_TIMELINE_GET_STATUS_URL, &clientData);
if (res != HTTP_SUCCESS)
{
goto end;
}
end:
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(header_buf);
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
return ERRCODE_SUCC;
/* 5. 发起HTTPS GET */
HttpClient client = (HttpClient){0};
HttpClientData clientData = (HttpClientData){0};
- HttpClientSetCustomHeader(&client, header_buf);
+ ZhihuiHttpClientSetCustomHeader(&client, header_buf);
- HTTPC_RESULT prep = HttpClientPrepare(&clientData, 2048, 4096);
+ HTTPC_RESULT prep = ZhihuiHttpClientPrepare(&clientData, 2048, 4096);
if (prep != HTTP_SUCCESS)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ln_get_device_token_http: HttpClientPrepare failed %d", prep);
return ERRCODE_FAIL;
}
- HTTPC_RESULT res = HttpClientGetRequest(&client, LN_FULL_URL, &clientData);
+ HTTPC_RESULT res = ZhihuiHttpClientGetRequest(&client, LN_FULL_URL, &clientData);
if (res != HTTP_SUCCESS)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ln_get_device_token_http: request failed %d", res);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(sign_data);
free(header_buf);
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
if (code != 200)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ln_get_device_token_http: HTTP %d", code);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(sign_data);
free(header_buf);
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
if (clientData.responseBuf == NULL || clientData.responseBufLen <= 0)
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ln_get_device_token_http: empty body");
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(sign_data);
free(header_buf);
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
char *json_text = (char *)malloc((size_t)body_len + 1);
if (json_text == NULL)
{
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(sign_data);
free(header_buf);
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
{
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ln_get_device_token_http: JSON parse failed");
free(json_text);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(sign_data);
free(header_buf);
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "ln_get_device_token_http: failed, message=%s", msg && cJSON_IsString(msg) ? msg->valuestring : "unknown");
cJSON_Delete(root);
free(json_text);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
return ERRCODE_FAIL;
}
cJSON_Delete(root);
free(json_text);
- HttpClientUnprepare(&clientData);
- HttpClientClse(&client);
+ ZhihuiHttpClientUnprepare(&clientData);
+ ZhihuiHttpClientClse(&client);
free(sign_data);
free(header_buf);
uapi_pm_remove_sleep_veto(PM_ID_DEBUG);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent DeviceBindAck: %s\n", json_string);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent DeviceUnbindAck: %s\n", json_string);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent CharacterChangeAck: %s\n", json_string);
cJSON_free(json_string);
cJSON_Delete(json);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent CharacterConfigChangeAck: %s\n", json_string);
cJSON_free(json_string);
cJSON_Delete(json);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent SleepModeStartAck: %s\n", json_string);
cJSON_free(json_string);
cJSON_Delete(json);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent SleepModeEndAck: %s\n", json_string);
cJSON_free(json_string);
cJSON_Delete(json);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent ScheduleReminderAck: %s\n", json_string);
cJSON_free(json_string);
cJSON_Delete(json);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent ScheduleReminderConfigAck: %s\n", json_string);
cJSON_free(json_string);
cJSON_Delete(json);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent DeleteEmojiAck: %s\n", json_string);
cJSON_free(json_string);
cJSON_Delete(json);
return -1;
}
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "Sent DownloadEmojiAck: %s\n", json_string);
cJSON_free(json_string);
cJSON_Delete(json);
#include "ln_api.h"
-#include "net_websocket.h"
-#include "haisi_websocket.h"
+/* 智慧网络统一接口(已包含 OpenHarmony 类型定义) */
+#include "zhihui_net.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
}
// 发送 JSON 字符串
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
LN_WS_DEBUG("Sent ping: %s\n", json_string);
}
// 发送JSON字符串
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
LN_WS_DEBUG("Sent audio data: chatId=%s, messageId=%s, size=%u, finished=%s\n",
g_audio_chat_ctx.chat_id, message_id, audio_size, finished ? "true" : "false");
g_ws_callbacks.reconnect_success_callback = ln_ws_reconnect_success_callback;
g_ws_callbacks.main_thread_interval_ms = LN_WS_MAIN_THREAD_INTERVAL_MS;
// 启动WebSocket服务
- int ret = uapi_ws_start(&g_ws_callbacks);
+ int ret = ZhihuiWsStart(&g_ws_callbacks);
if (ret != 0) {
- LN_WS_WARN("uapi_ws_start failed: %d\n", ret);
+ LN_WS_WARN("ZhihuiWsStart failed: %d\n", ret);
g_ws_running = 0;
return ret;
}
LN_WS_WARN("Cannot send text, connection state: %d\n", g_ws_connected);
return -1;
}
- return uapi_ws_send_text(text);
+ return ZhihuiWsSendText(text);
}
/* 发送随机故事请求 */
LN_WS_DEBUG("Sending RandomStoryRequest: %s\n", json_string);
// 发送消息
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
// 清理资源
cJSON_free(json_string);
LN_WS_DEBUG("Sending VoiceChatInterrupt: %s\n", json_string);
// 发送消息
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
// 清理资源
cJSON_free(json_string);
// 停止音频服务(与 ln_ws_start 中的 ln_ws_audio_init 对应)
ln_ws_audio_deinit();
- int ret = uapi_ws_stop();
+ int ret = ZhihuiWsStop();
g_ws_connected = WS_STATE_DISCONNECTED;
// 清理Base64缓存
}
// 使用新的接口进入休眠
- int ret = uapi_ws_enter_sleep();
+ int ret = ZhihuiWsEnterSleep();
if (ret == 0) {
g_ws_connected = WS_STATE_SLEEP;
LN_WS_DEBUG("Sleep mode activated, all operations paused\n");
}
// 使用新的接口退出休眠
- int ret = uapi_ws_exit_sleep();
+ int ret = ZhihuiWsExitSleep();
if (ret == 0) {
g_ws_connected = WS_STATE_DISCONNECTED;
LN_WS_DEBUG("Sleep mode deactivated, resuming normal operations\n");
g_ws_connected = WS_STATE_RECONNECTING;
// 使用强制重连API
- int ret = uapi_ws_force_reconnect();
+ int ret = ZhihuiWsForceReconnect();
if (ret != 0) {
LN_WS_ERROR("WebSocket force reconnection failed: %d\n", ret);
g_ws_connected = WS_STATE_DISCONNECTED;
}
// 发送JSON字符串
- int ret = uapi_ws_send_text(json_string);
+ int ret = ZhihuiWsSendText(json_string);
LN_WS_ERROR("Sent audio data: chatId=%s, messageId=%s, size=%u, finished=%s\n",
g_audio_chat_ctx.chat_id, message_id, audio_size, finished ? "true" : "false");
--- /dev/null
+#===============================================================================
+# @brief N706A PPP网络接口层
+# Copyright (c) @CompanyNameMagicTag 2023-2025. All rights reserved.
+#===============================================================================
+set(COMPONENT_NAME "n706a_net")
+
+set(SOURCES ${SOURCES}
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/n706a_tcp_client.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/n706a_http_client.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/n706a_websocket.c
+)
+
+set(PUBLIC_HEADER
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+)
+
+set(PUBLIC_DEFINES
+ SUPPORT_N706A_NET
+)
+
+set(PRIVATE_HEADER
+)
+
+set(PRIVATE_DEFINES
+# N706A_SSL_USE_MBEDTLS
+N706A_SSL_USE_AT_COMMAND
+)
+
+# use this when you want to add ccflags like -include xxx
+set(COMPONENT_PUBLIC_CCFLAGS
+)
+
+set(COMPONENT_CCFLAGS
+ -Wno-incompatible-function-pointer-types
+ -Wno-unused-function
+ -Wno-format
+)
+
+set(WHOLE_LINK
+ true
+)
+
+set(MAIN_COMPONENT
+ false
+)
+
+build_component()
+
+install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*")
+
--- /dev/null
+/**
+ * @file n706a_http_client.h
+ * @brief N706A HTTP/HTTPS客户端接口(基于TCP实现)
+ *
+ * 本实现采用统一的TCP底层架构:
+ * - HTTP通过普通TCP连接实现
+ * - HTTPS通过SSL TCP连接实现
+ * - 手动构造HTTP协议,突破AT+HTTP*单连接限制
+ * - 支持6个并发HTTP/HTTPS连接
+ *
+ * @version 3.0
+ * @date 2025-10-30
+ */
+
+#ifndef PPP_N706A_HTTP_CLIENT_H
+#define PPP_N706A_HTTP_CLIENT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "n706a_tcp_client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief N706A HTTP请求类型
+ */
+typedef enum {
+ N706A_HTTP_DELETE,
+ N706A_HTTP_GET,
+ N706A_HTTP_HEAD,
+ N706A_HTTP_POST,
+ N706A_HTTP_PUT
+} n706a_http_request_type_t;
+
+/**
+ * @brief N706A HTTP错误码
+ */
+typedef enum {
+ N706A_HTTP_EAGAIN = 1, /**< 还有更多数据需要获取 */
+ N706A_HTTP_SUCCESS = 0, /**< 操作成功 */
+ N706A_HTTP_ENOBUFS = -1, /**< 缓冲区错误 */
+ N706A_HTTP_EARG = -2, /**< 非法参数 */
+ N706A_HTTP_ENOTSUPP = -3, /**< 不支持 */
+ N706A_HTTP_EDNS = -4, /**< DNS失败 */
+ N706A_HTTP_ECONN = -5, /**< 连接失败 */
+ N706A_HTTP_ESEND = -6, /**< 发送数据包失败 */
+ N706A_HTTP_ECLSD = -7, /**< 连接已关闭 */
+ N706A_HTTP_ERECV = -8, /**< 接收数据包失败 */
+ N706A_HTTP_EPARSE = -9, /**< URL解析错误 */
+ N706A_HTTP_EPROTO = -10, /**< 协议错误 */
+ N706A_HTTP_EUNKOWN = -11, /**< 未知错误 */
+ N706A_HTTP_ETIMEOUT = -12, /**< 超时 */
+} n706a_httpc_result_t;
+
+/**
+ * @brief N706A HTTP客户端结构(基于TCP实现)
+ */
+typedef struct {
+ n706a_tcp_socket_t tcp_sock; /**< 底层TCP socket */
+ int socket_id; /**< TCP socket编号(0-5) */
+ bool is_https; /**< HTTPS标志(决定是否使用SSL) */
+ uint16_t remote_port; /**< 远程端口 */
+ int response_code; /**< HTTP响应码 */
+ char *header; /**< 自定义请求头 */
+ char *auth_user; /**< 基本认证用户名 */
+ char *auth_password; /**< 基本认证密码 */
+ char host[256]; /**< 主机名 */
+ bool connected; /**< 连接状态 */
+ bool keepalive; /**< 长连接模式 */
+ uint32_t timeout_ms; /**< 超时时间(毫秒) */
+} n706a_http_client_t;
+
+/**
+ * @brief N706A HTTP数据结构
+ */
+typedef struct {
+ bool isMore; /**< 是否还有更多数据需要获取 */
+ bool isChunked; /**< 响应数据是否分块编码 */
+ int method; /**< HTTP方法 */
+ int retrieveLen; /**< 需要获取的内容长度 */
+ int responseContentLen; /**< 响应内容总长度 */
+ int contentBlockLen; /**< 一个块的内容长度 */
+ int postBufLen; /**< POST数据长度 */
+ int responseBufLen; /**< 响应体缓冲区长度 */
+ int headerBufLen; /**< 响应头缓冲区长度 */
+ char *postContentType; /**< POST数据的内容类型 */
+ char *postBuf; /**< 要POST的用户数据 */
+ char *responseBuf; /**< 存储响应体数据的缓冲区 */
+ char *headerBuf; /**< 存储响应头数据的缓冲区 */
+ bool isRedirected; /**< 是否重定向 */
+ char *redirectUrl; /**< 重定向URL */
+} n706a_http_client_data_t;
+
+/**
+ * @brief 初始化N706A HTTP客户端
+ * @param client HTTP客户端结构指针
+ * @param socket_id TCP socket编号(0-5)
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_init(n706a_http_client_t *client, int socket_id);
+
+/**
+ * @brief 连接到HTTP/HTTPS服务器
+ * @param client HTTP客户端结构指针
+ * @param host 主机名或IP地址
+ * @param port 端口号(HTTP默认80,HTTPS默认443)
+ * @param use_ssl 是否使用SSL(HTTPS)
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_connect(n706a_http_client_t *client,
+ const char *host,
+ uint16_t port,
+ bool use_ssl);
+
+/**
+ * @brief 发送HTTP GET请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径(如"/api/data")
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_get(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 发送HTTP POST请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径
+ * @param content_type 内容类型(如"application/json")
+ * @param body 请求体数据
+ * @param body_len 请求体长度
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_post(n706a_http_client_t *client,
+ const char *path,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 发送HTTP PUT请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径
+ * @param content_type 内容类型
+ * @param body 请求体数据
+ * @param body_len 请求体长度
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_put(n706a_http_client_t *client,
+ const char *path,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 发送HTTP DELETE请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_delete(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 发送HTTP HEAD请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_head(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 设置自定义HTTP请求头
+ * @param client HTTP客户端结构指针
+ * @param header 自定义请求头(如"X-API-Key: abc123\r\n")
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_set_header(n706a_http_client_t *client,
+ const char *header);
+
+/**
+ * @brief 设置HTTP基本认证
+ * @param client HTTP客户端结构指针
+ * @param username 用户名
+ * @param password 密码
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_set_auth(n706a_http_client_t *client,
+ const char *username,
+ const char *password);
+
+/**
+ * @brief 设置Keep-Alive模式
+ * @param client HTTP客户端结构指针
+ * @param keepalive true启用长连接,false关闭
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_set_keepalive(n706a_http_client_t *client,
+ bool keepalive);
+
+/**
+ * @brief 关闭HTTP连接
+ * @param client HTTP客户端结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_close(n706a_http_client_t *client);
+
+/**
+ * @brief 检查HTTP连接状态
+ * @param client HTTP客户端结构指针
+ * @return true已连接,false未连接
+ */
+bool n706a_http_client_is_connected(n706a_http_client_t *client);
+
+/**
+ * @brief 获取最后的HTTP响应码
+ * @param client HTTP客户端结构指针
+ * @return HTTP响应码(如200, 404等)
+ */
+int n706a_http_client_get_response_code(n706a_http_client_t *client);
+
+/* ========== 辅助函数 ========== */
+
+/**
+ * @brief 解析完整URL并发起GET请求(便捷函数)
+ * @param client HTTP客户端结构指针
+ * @param url 完整URL(如"http://api.example.com/data"或"https://secure.com/")
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ * @note 此函数会自动解析URL、建立连接并发送GET请求
+ */
+n706a_httpc_result_t n706a_http_client_get_url(n706a_http_client_t *client,
+ const char *url,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 解析完整URL并发起POST请求(便捷函数)
+ * @param client HTTP客户端结构指针
+ * @param url 完整URL
+ * @param content_type 内容类型
+ * @param body 请求体数据
+ * @param body_len 请求体长度
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_post_url(n706a_http_client_t *client,
+ const char *url,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // N706A_HTTP_CLIENT_H
+
--- /dev/null
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
+ *
+ * N706A TCP客户端接口
+ * 基于N706A AT指令实现TCP socket功能
+ *
+ * SSL实现方式:
+ * - N706A_SSL_USE_AT_COMMAND: 使用AT+SSLTCP*指令(默认,推荐)
+ * - N706A_SSL_USE_MBEDTLS: 使用mbedtls库(需要额外库支持)
+ */
+
+#ifndef PPP_N706A_TCP_CLIENT_H
+#define PPP_N706A_TCP_CLIENT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* ========== SSL实现方式选择 ========== */
+/**
+ * 编译时选择SSL实现方式:
+ * gcc -DN706A_SSL_USE_AT_COMMAND ... (使用AT指令,默认)
+ * gcc -DN706A_SSL_USE_MBEDTLS ... (使用mbedtls)
+ */
+#if !defined(N706A_SSL_USE_AT_COMMAND) && !defined(N706A_SSL_USE_MBEDTLS)
+ /* 默认使用AT指令实现 */
+ #define N706A_SSL_USE_AT_COMMAND 1
+#endif
+
+#if defined(N706A_SSL_USE_AT_COMMAND) && defined(N706A_SSL_USE_MBEDTLS)
+ #error "Cannot define both N706A_SSL_USE_AT_COMMAND and N706A_SSL_USE_MBEDTLS"
+#endif
+
+/* 如果使用mbedtls,包含相关头文件 */
+#ifdef N706A_SSL_USE_MBEDTLS
+ #include "mbedtls/net_sockets.h"
+ #include "mbedtls/ssl.h"
+ #include "mbedtls/entropy.h"
+ #include "mbedtls/ctr_drbg.h"
+ #include "mbedtls/x509.h"
+ #include "mbedtls/error.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* N706A TCP错误码 */
+typedef enum {
+ N706A_TCP_SUCCESS = 0, /**< 操作成功 */
+ N706A_TCP_ERROR = -1, /**< 通用错误 */
+ N706A_TCP_EINVAL = -2, /**< 参数无效 */
+ N706A_TCP_ENOBUFS = -3, /**< 缓冲区不足 */
+ N706A_TCP_ENOTCONN = -4, /**< 未连接 */
+ N706A_TCP_ECONNREF = -5, /**< 连接被拒绝 */
+ N706A_TCP_ETIMEDOUT= -6, /**< 超时 */
+ N706A_TCP_EINPROGRESS = -7, /**< 操作进行中 */
+} n706a_tcp_result_t;
+
+/* TCP socket状态 */
+typedef enum {
+ N706A_TCP_STATE_CLOSED = 0, /**< 关闭 */
+ N706A_TCP_STATE_CONNECTING, /**< 连接中 */
+ N706A_TCP_STATE_CONNECTED, /**< 已连接 */
+ N706A_TCP_STATE_ERROR /**< 错误状态 */
+} n706a_tcp_state_t;
+
+/* TCP socket结构 */
+typedef struct {
+ int socket_id; /**< socket编号(0-5) */
+ n706a_tcp_state_t state; /**< 连接状态 */
+ char remote_ip[64]; /**< 远程IP或域名 */
+ uint16_t remote_port; /**< 远程端口 */
+ bool is_ssl; /**< 是否SSL连接 */
+ uint32_t timeout_ms; /**< 超时时间 */
+
+ /* SSL相关字段(根据编译宏不同而不同) */
+#ifdef N706A_SSL_USE_AT_COMMAND
+ /* AT指令模式:SSL由模组处理 */
+ int ssl_version; /**< SSL版本(0-5) */
+ int auth_mode; /**< 认证模式(0-2) */
+ char ca_cert_file[64]; /**< CA证书文件名 */
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:需要SSL上下文 */
+ int ssl_version; /**< SSL版本 */
+ int auth_mode; /**< 认证模式 */
+ mbedtls_ssl_context ssl; /**< SSL上下文 */
+ mbedtls_ssl_config conf; /**< SSL配置 */
+ mbedtls_entropy_context entropy; /**< 熵源 */
+ mbedtls_ctr_drbg_context ctr_drbg; /**< 随机数生成器 */
+ mbedtls_x509_crt cacert; /**< CA证书 */
+ mbedtls_x509_crt clicert; /**< 客户端证书(可选) */
+ mbedtls_pk_context pkey; /**< 客户端私钥(可选) */
+ bool ssl_initialized; /**< SSL是否已初始化 */
+#endif
+} n706a_tcp_socket_t;
+
+/**
+ * @brief 创建TCP socket
+ * @param sock TCP socket指针
+ * @param socket_id socket编号(0-5)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_socket_create(n706a_tcp_socket_t *sock, int socket_id);
+
+/**
+ * @brief 连接到TCP服务器
+ * @param sock TCP socket指针
+ * @param host 主机地址(IP或域名)
+ * @param port 端口号
+ * @param use_ssl 是否使用SSL连接(用于WSS)
+ * @param timeout_ms 超时时间(毫秒)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ * @note 使用SSL时会调用AT+SSLTCPSETUP,否则调用AT+TCPSETUP
+ */
+n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock, const char *host,
+ uint16_t port, bool use_ssl, uint32_t timeout_ms);
+
+/**
+ * @brief 配置SSL参数(在SSL连接前调用)
+ * @param sock TCP socket指针
+ * @param ssl_version SSL版本
+ * AT指令模式:0-5 (0=SSL3.0, 1=TLS1.0, 2=TLS1.1, 3=TLS1.2, 4=TLS1.0/1.1/1.2, 5=TLS1.3)
+ * mbedtls模式:使用MBEDTLS_SSL_MINOR_VERSION_*常量
+ * @param auth_mode 认证模式
+ * AT指令模式:0=不认证, 1=服务器认证, 2=双向认证
+ * mbedtls模式:MBEDTLS_SSL_VERIFY_NONE/OPTIONAL/REQUIRED
+ * @param ca_cert CA证书
+ * AT指令模式:证书文件名(如"ca.crt")
+ * mbedtls模式:证书内容(PEM格式字符串)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_config_ssl(n706a_tcp_socket_t *sock,
+ int ssl_version,
+ int auth_mode,
+ const char *ca_cert);
+
+#ifdef N706A_SSL_USE_MBEDTLS
+/**
+ * @brief 配置客户端证书和私钥(mbedtls模式,双向认证)
+ * @param sock TCP socket指针
+ * @param client_cert 客户端证书(PEM格式)
+ * @param client_key 客户端私钥(PEM格式)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_config_ssl_client_cert(n706a_tcp_socket_t *sock,
+ const char *client_cert,
+ const char *client_key);
+#endif
+
+/**
+ * @brief 发送TCP数据
+ * @param sock TCP socket指针
+ * @param data 要发送的数据
+ * @param len 数据长度
+ * @param sent 实际发送的字节数(输出)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_send(n706a_tcp_socket_t *sock, const uint8_t *data,
+ size_t len, size_t *sent);
+
+/**
+ * @brief 接收TCP数据
+ * @param sock TCP socket指针
+ * @param buffer 接收缓冲区
+ * @param len 缓冲区大小
+ * @param received 实际接收的字节数(输出)
+ * @param timeout_ms 超时时间(毫秒)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock, uint8_t *buffer,
+ size_t len, size_t *received, uint32_t timeout_ms);
+
+/**
+ * @brief 关闭TCP连接
+ * @param sock TCP socket指针
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_close(n706a_tcp_socket_t *sock);
+
+/**
+ * @brief 检查socket是否已连接
+ * @param sock TCP socket指针
+ * @return true已连接,false未连接
+ */
+bool n706a_tcp_is_connected(const n706a_tcp_socket_t *sock);
+
+/**
+ * @brief 获取socket状态
+ * @param sock TCP socket指针
+ * @return socket状态
+ */
+n706a_tcp_state_t n706a_tcp_get_state(const n706a_tcp_socket_t *sock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PPP_N706A_TCP_CLIENT_H */
+
--- /dev/null
+/**
+ * @file n706a_websocket.h
+ * @brief N706A WebSocket客户端接口定义
+ *
+ * 基于TCP/SSL TCP实现完整的WebSocket协议(RFC 6455),支持:
+ * - ws://协议(普通TCP)
+ * - wss://协议(SSL TCP)
+ * - 文本和二进制消息
+ * - 自动握手和帧封装
+ * - 事件回调机制
+ *
+ * @version 1.0
+ * @date 2025-10-30
+ */
+
+#ifndef PPP_N706A_WEBSOCKET_H
+#define PPP_N706A_WEBSOCKET_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "n706a_tcp_client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* WebSocket最大客户端数量 */
+#define N706A_WS_MAX_CLIENTS 6
+
+/* WebSocket URL最大长度 */
+#define N706A_WS_MAX_URL_LEN 512
+
+/* WebSocket帧操作码 */
+typedef enum {
+ N706A_WS_OPCODE_CONTINUATION = 0x0, /**< 继续帧 */
+ N706A_WS_OPCODE_TEXT = 0x1, /**< 文本帧 */
+ N706A_WS_OPCODE_BINARY = 0x2, /**< 二进制帧 */
+ N706A_WS_OPCODE_CLOSE = 0x8, /**< 关闭帧 */
+ N706A_WS_OPCODE_PING = 0x9, /**< Ping帧 */
+ N706A_WS_OPCODE_PONG = 0xA /**< Pong帧 */
+} n706a_ws_opcode_t;
+
+/* WebSocket状态 */
+typedef enum {
+ N706A_WS_STATE_CLOSED = 0, /**< 已关闭 */
+ N706A_WS_STATE_CONNECTING, /**< 连接中 */
+ N706A_WS_STATE_OPEN, /**< 已打开 */
+ N706A_WS_STATE_CLOSING /**< 关闭中 */
+} n706a_ws_state_t;
+
+/* WebSocket关闭状态码 */
+typedef enum {
+ N706A_WS_CLOSE_NORMAL = 1000, /**< 正常关闭 */
+ N706A_WS_CLOSE_GOING_AWAY = 1001, /**< 端点离开 */
+ N706A_WS_CLOSE_PROTOCOL_ERROR = 1002, /**< 协议错误 */
+ N706A_WS_CLOSE_UNSUPPORTED = 1003, /**< 不支持的数据 */
+ N706A_WS_CLOSE_NO_STATUS = 1005, /**< 无状态码 */
+ N706A_WS_CLOSE_ABNORMAL = 1006, /**< 异常关闭 */
+ N706A_WS_CLOSE_INVALID_DATA = 1007, /**< 无效数据 */
+ N706A_WS_CLOSE_POLICY = 1008, /**< 策略违反 */
+ N706A_WS_CLOSE_TOO_LARGE = 1009, /**< 消息过大 */
+ N706A_WS_CLOSE_EXTENSION = 1010, /**< 扩展协商失败 */
+ N706A_WS_CLOSE_SERVER_ERROR = 1011 /**< 服务器错误 */
+} n706a_ws_close_code_t;
+
+/* WebSocket错误码 */
+typedef enum {
+ N706A_WS_SUCCESS = 0, /**< 成功 */
+ N706A_WS_ERROR_INVALID_PARAM = -1, /**< 参数无效 */
+ N706A_WS_ERROR_NO_MEMORY = -2, /**< 内存不足 */
+ N706A_WS_ERROR_NOT_CONNECTED = -3, /**< 未连接 */
+ N706A_WS_ERROR_ALREADY_OPEN = -4, /**< 已经打开 */
+ N706A_WS_ERROR_HANDSHAKE = -5, /**< 握手失败 */
+ N706A_WS_ERROR_SEND_FAIL = -6, /**< 发送失败 */
+ N706A_WS_ERROR_RECV_FAIL = -7, /**< 接收失败 */
+ N706A_WS_ERROR_PROTOCOL = -8, /**< 协议错误 */
+ N706A_WS_ERROR_TIMEOUT = -9, /**< 超时 */
+ N706A_WS_ERROR_CLOSED = -10 /**< 连接已关闭 */
+} n706a_ws_result_t;
+
+/* 前置声明 */
+struct n706a_websocket_client;
+
+/**
+ * @brief WebSocket连接打开回调
+ * @param client_id 客户端ID
+ */
+typedef void (*n706a_ws_on_open_callback_t)(int client_id);
+
+/**
+ * @brief WebSocket消息接收回调
+ * @param client_id 客户端ID
+ * @param data 消息数据
+ * @param length 数据长度
+ * @param is_finished 是否是最后一帧
+ * @param is_binary 是否是二进制数据
+ */
+typedef void (*n706a_ws_on_message_callback_t)(int client_id,
+ const char *data,
+ size_t length,
+ bool is_finished,
+ bool is_binary);
+
+/**
+ * @brief WebSocket错误回调
+ * @param client_id 客户端ID
+ * @param error_code 错误码
+ * @param error_msg 错误消息
+ */
+typedef void (*n706a_ws_on_error_callback_t)(int client_id,
+ int error_code,
+ const char *error_msg);
+
+/**
+ * @brief WebSocket连接关闭回调
+ * @param client_id 客户端ID
+ * @param close_code 关闭状态码
+ * @param reason 关闭原因
+ */
+typedef void (*n706a_ws_on_close_callback_t)(int client_id,
+ int close_code,
+ const char *reason);
+
+/**
+ * @brief N706A WebSocket客户端结构
+ */
+typedef struct n706a_websocket_client {
+ int client_id; /**< 客户端ID (0-5) */
+ n706a_tcp_socket_t tcp_sock; /**< 底层TCP socket */
+ n706a_ws_state_t state; /**< WebSocket状态 */
+ bool is_ssl; /**< 是否WSS连接 */
+
+ /* URL信息 */
+ char url[N706A_WS_MAX_URL_LEN]; /**< 完整URL */
+ char host[128]; /**< 主机名 */
+ uint16_t port; /**< 端口号 */
+ char path[256]; /**< 路径 */
+
+ /* WebSocket协议 */
+ char sec_key[32]; /**< Sec-WebSocket-Key */
+ char sec_accept[32]; /**< Sec-WebSocket-Accept */
+
+ /* 接收缓冲区 */
+ uint8_t *recv_buffer; /**< 接收缓冲区 */
+ size_t recv_buffer_size; /**< 缓冲区大小 */
+ size_t recv_buffer_len; /**< 已接收数据长度 */
+
+ /* 回调函数 */
+ n706a_ws_on_open_callback_t on_open;
+ n706a_ws_on_message_callback_t on_message;
+ n706a_ws_on_error_callback_t on_error;
+ n706a_ws_on_close_callback_t on_close;
+
+ /* 运行控制 */
+ bool running; /**< 接收线程运行标志 */
+ void *recv_thread; /**< 接收线程句柄 */
+
+} n706a_websocket_client_t;
+
+/**
+ * @brief 创建WebSocket客户端
+ * @param on_open 连接打开回调
+ * @param on_message 消息接收回调
+ * @param on_error 错误回调
+ * @param on_close 连接关闭回调
+ * @return 客户端ID (0-5),失败返回-1
+ */
+int n706a_websocket_constructor(n706a_ws_on_open_callback_t on_open,
+ n706a_ws_on_message_callback_t on_message,
+ n706a_ws_on_error_callback_t on_error,
+ n706a_ws_on_close_callback_t on_close);
+
+/**
+ * @brief 创建WebSocket客户端并指定socket ID
+ * @param socket_id TCP socket ID (0-5)
+ * @param on_open 连接打开回调
+ * @param on_message 消息接收回调
+ * @param on_error 错误回调
+ * @param on_close 连接关闭回调
+ * @return 客户端ID,失败返回-1
+ */
+int n706a_websocket_constructor_with_socket(int socket_id,
+ n706a_ws_on_open_callback_t on_open,
+ n706a_ws_on_message_callback_t on_message,
+ n706a_ws_on_error_callback_t on_error,
+ n706a_ws_on_close_callback_t on_close);
+
+/**
+ * @brief 连接到WebSocket服务器
+ * @param client_id 客户端ID
+ * @param url WebSocket URL (ws://或wss://)
+ * @param option 可选参数(如子协议等),可为NULL
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_connect(int client_id, const char *url, const char *option);
+
+/**
+ * @brief 发送文本消息
+ * @param client_id 客户端ID
+ * @param data 文本数据
+ * @param length 数据长度
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_send_text(int client_id, const char *data, size_t length);
+
+/**
+ * @brief 发送二进制消息
+ * @param client_id 客户端ID
+ * @param data 二进制数据
+ * @param length 数据长度
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_send_binary(int client_id, const uint8_t *data, size_t length);
+
+/**
+ * @brief 发送Ping帧
+ * @param client_id 客户端ID
+ * @param data Ping数据(可为NULL)
+ * @param length 数据长度
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_send_ping(int client_id, const char *data, size_t length);
+
+/**
+ * @brief 发送Pong帧
+ * @param client_id 客户端ID
+ * @param data Pong数据(可为NULL)
+ * @param length 数据长度
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_send_pong(int client_id, const char *data, size_t length);
+
+/**
+ * @brief 关闭WebSocket连接
+ * @param client_id 客户端ID
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_close(int client_id);
+
+/**
+ * @brief 关闭WebSocket连接(带状态码和原因)
+ * @param client_id 客户端ID
+ * @param close_code 关闭状态码
+ * @param reason 关闭原因(可为NULL)
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_close_with_reason(int client_id,
+ n706a_ws_close_code_t close_code,
+ const char *reason);
+
+/**
+ * @brief 获取WebSocket连接状态
+ * @param client_id 客户端ID
+ * @return WebSocket状态
+ */
+n706a_ws_state_t n706a_websocket_get_state(int client_id);
+
+/**
+ * @brief 检查WebSocket是否已连接
+ * @param client_id 客户端ID
+ * @return true已连接,false未连接
+ */
+bool n706a_websocket_is_connected(int client_id);
+
+/**
+ * @brief 销毁WebSocket客户端
+ * @param client_id 客户端ID
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_destructor(int client_id);
+
+/* ========== 内部辅助函数(高级用户可选使用) ========== */
+
+/**
+ * @brief 解析WebSocket URL
+ * @param url 完整URL
+ * @param protocol 协议(输出,"ws"或"wss")
+ * @param host 主机名(输出)
+ * @param port 端口号(输出)
+ * @param path 路径(输出)
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_parse_url(const char *url,
+ char *protocol,
+ char *host,
+ uint16_t *port,
+ char *path);
+
+/**
+ * @brief 生成WebSocket Sec-WebSocket-Key
+ * @param key 输出缓冲区(至少25字节)
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_generate_key(char *key);
+
+/**
+ * @brief 计算WebSocket Sec-WebSocket-Accept
+ * @param key Sec-WebSocket-Key
+ * @param accept 输出缓冲区(至少29字节)
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_calculate_accept(const char *key, char *accept);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PPP_N706A_WEBSOCKET_H */
+
--- /dev/null
+/**
+ * @file n706a_http_client.c
+ * @brief N706A HTTP/HTTPS客户端实现(基于TCP)
+ *
+ * 基于TCP手动构造HTTP协议,支持:
+ * - HTTP (普通TCP)
+ * - HTTPS (SSL TCP)
+ * - 6个并发连接
+ * - 所有标准HTTP方法
+ */
+
+#include "n706a_http_client.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Base64编码表(用于Basic Auth) */
+static const char base64_table[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/* ========== 私有函数声明 ========== */
+
+static int http_build_request(n706a_http_client_t *client,
+ const char *method,
+ const char *path,
+ const char *body,
+ size_t body_len,
+ const char *content_type,
+ char *request_buf,
+ size_t buf_size);
+
+static n706a_httpc_result_t http_parse_response(const char *response,
+ size_t response_len,
+ n706a_http_client_t *client,
+ n706a_http_client_data_t *data);
+
+static void http_base64_encode(const char *input, size_t len, char *output);
+
+static int http_parse_url(const char *url,
+ char *protocol,
+ char *host,
+ uint16_t *port,
+ char *path);
+
+/* ========== 公共函数实现 ========== */
+
+/**
+ * @brief 初始化HTTP客户端
+ */
+n706a_httpc_result_t n706a_http_client_init(n706a_http_client_t *client, int socket_id)
+{
+ if (!client || socket_id < 0 || socket_id > 5) {
+ return N706A_HTTP_EARG;
+ }
+
+ memset(client, 0, sizeof(n706a_http_client_t));
+
+ client->socket_id = socket_id;
+ client->is_https = false;
+ client->remote_port = 0;
+ client->response_code = 0;
+ client->connected = false;
+ client->keepalive = false;
+ client->timeout_ms = 10000; // 默认10秒
+
+ /* 初始化TCP socket */
+ n706a_tcp_socket_create(&client->tcp_sock, socket_id);
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 连接到HTTP/HTTPS服务器
+ */
+n706a_httpc_result_t n706a_http_client_connect(n706a_http_client_t *client,
+ const char *host,
+ uint16_t port,
+ bool use_ssl)
+{
+ if (!client || !host) {
+ return N706A_HTTP_EARG;
+ }
+
+ /* 保存连接信息 */
+ strncpy(client->host, host, sizeof(client->host) - 1);
+ client->remote_port = port;
+ client->is_https = use_ssl;
+
+ /* 建立TCP连接 */
+ n706a_tcp_result_t ret = n706a_tcp_connect(&client->tcp_sock,
+ host,
+ port,
+ use_ssl,
+ 30000); // SSL握手需要更长时间
+
+ if (ret == N706A_TCP_SUCCESS) {
+ client->connected = true;
+ return N706A_HTTP_SUCCESS;
+ } else if (ret == N706A_TCP_ETIMEDOUT) {
+ return N706A_HTTP_ETIMEOUT;
+ } else {
+ return N706A_HTTP_ECONN;
+ }
+}
+
+/**
+ * @brief 发送HTTP GET请求
+ */
+n706a_httpc_result_t n706a_http_client_get(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ /* 构造GET请求 */
+ char request_buf[2048];
+ int request_len = http_build_request(client, "GET", path,
+ NULL, 0, NULL,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ /* 发送请求 */
+ size_t sent;
+ n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock,
+ (uint8_t*)request_buf,
+ request_len,
+ &sent);
+
+ if (ret != N706A_TCP_SUCCESS || sent != (size_t)request_len) {
+ return N706A_HTTP_ESEND;
+ }
+
+ /* 接收响应 */
+ uint8_t response_buf[8192];
+ size_t received;
+
+ ret = n706a_tcp_recv(&client->tcp_sock,
+ response_buf,
+ sizeof(response_buf) - 1,
+ &received,
+ client->timeout_ms);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ return N706A_HTTP_ERECV;
+ }
+
+ response_buf[received] = '\0';
+
+ /* 解析响应 */
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 发送HTTP POST请求
+ */
+n706a_httpc_result_t n706a_http_client_post(n706a_http_client_t *client,
+ const char *path,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ /* 构造POST请求 */
+ char request_buf[4096];
+ int request_len = http_build_request(client, "POST", path,
+ body, body_len, content_type,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ /* 发送请求 */
+ size_t sent;
+ n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock,
+ (uint8_t*)request_buf,
+ request_len,
+ &sent);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ return N706A_HTTP_ESEND;
+ }
+
+ /* 接收响应 */
+ uint8_t response_buf[8192];
+ size_t received;
+
+ ret = n706a_tcp_recv(&client->tcp_sock,
+ response_buf,
+ sizeof(response_buf) - 1,
+ &received,
+ client->timeout_ms);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ return N706A_HTTP_ERECV;
+ }
+
+ response_buf[received] = '\0';
+
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 发送HTTP PUT请求
+ */
+n706a_httpc_result_t n706a_http_client_put(n706a_http_client_t *client,
+ const char *path,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ char request_buf[4096];
+ int request_len = http_build_request(client, "PUT", path,
+ body, body_len, content_type,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ size_t sent;
+ n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent);
+
+ uint8_t response_buf[8192];
+ size_t received;
+ n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1,
+ &received, client->timeout_ms);
+
+ response_buf[received] = '\0';
+
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 发送HTTP DELETE请求
+ */
+n706a_httpc_result_t n706a_http_client_delete(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ char request_buf[2048];
+ int request_len = http_build_request(client, "DELETE", path,
+ NULL, 0, NULL,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ size_t sent;
+ n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent);
+
+ uint8_t response_buf[8192];
+ size_t received;
+ n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1,
+ &received, client->timeout_ms);
+
+ response_buf[received] = '\0';
+
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 发送HTTP HEAD请求
+ */
+n706a_httpc_result_t n706a_http_client_head(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ char request_buf[2048];
+ int request_len = http_build_request(client, "HEAD", path,
+ NULL, 0, NULL,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ size_t sent;
+ n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent);
+
+ uint8_t response_buf[8192];
+ size_t received;
+ n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1,
+ &received, client->timeout_ms);
+
+ response_buf[received] = '\0';
+
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 设置自定义HTTP请求头
+ */
+n706a_httpc_result_t n706a_http_client_set_header(n706a_http_client_t *client,
+ const char *header)
+{
+ if (!client) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (client->header) {
+ free(client->header);
+ }
+
+ if (header) {
+ client->header = strdup(header);
+ } else {
+ client->header = NULL;
+ }
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 设置HTTP基本认证
+ */
+n706a_httpc_result_t n706a_http_client_set_auth(n706a_http_client_t *client,
+ const char *username,
+ const char *password)
+{
+ if (!client) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (client->auth_user) {
+ free(client->auth_user);
+ }
+ if (client->auth_password) {
+ free(client->auth_password);
+ }
+
+ if (username) {
+ client->auth_user = strdup(username);
+ } else {
+ client->auth_user = NULL;
+ }
+
+ if (password) {
+ client->auth_password = strdup(password);
+ } else {
+ client->auth_password = NULL;
+ }
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 设置Keep-Alive模式
+ */
+n706a_httpc_result_t n706a_http_client_set_keepalive(n706a_http_client_t *client,
+ bool keepalive)
+{
+ if (!client) {
+ return N706A_HTTP_EARG;
+ }
+
+ client->keepalive = keepalive;
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 关闭HTTP连接
+ */
+n706a_httpc_result_t n706a_http_client_close(n706a_http_client_t *client)
+{
+ if (!client) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (client->connected) {
+ n706a_tcp_close(&client->tcp_sock);
+ client->connected = false;
+ }
+
+ if (client->header) {
+ free(client->header);
+ client->header = NULL;
+ }
+
+ if (client->auth_user) {
+ free(client->auth_user);
+ client->auth_user = NULL;
+ }
+
+ if (client->auth_password) {
+ free(client->auth_password);
+ client->auth_password = NULL;
+ }
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 检查HTTP连接状态
+ */
+bool n706a_http_client_is_connected(n706a_http_client_t *client)
+{
+ if (!client) {
+ return false;
+ }
+
+ return client->connected && n706a_tcp_is_connected(&client->tcp_sock);
+}
+
+/**
+ * @brief 获取最后的HTTP响应码
+ */
+int n706a_http_client_get_response_code(n706a_http_client_t *client)
+{
+ if (!client) {
+ return 0;
+ }
+
+ return client->response_code;
+}
+
+/**
+ * @brief 解析完整URL并发起GET请求
+ */
+n706a_httpc_result_t n706a_http_client_get_url(n706a_http_client_t *client,
+ const char *url,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !url || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ char protocol[8];
+ char host[256];
+ char path[512];
+ uint16_t port;
+
+ if (http_parse_url(url, protocol, host, &port, path) != 0) {
+ return N706A_HTTP_EPARSE;
+ }
+
+ bool use_ssl = (strcmp(protocol, "https") == 0);
+
+ n706a_httpc_result_t ret = n706a_http_client_connect(client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ return ret;
+ }
+
+ return n706a_http_client_get(client, path, client_data);
+}
+
+/**
+ * @brief 解析完整URL并发起POST请求
+ */
+n706a_httpc_result_t n706a_http_client_post_url(n706a_http_client_t *client,
+ const char *url,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !url || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ char protocol[8];
+ char host[256];
+ char path[512];
+ uint16_t port;
+
+ if (http_parse_url(url, protocol, host, &port, path) != 0) {
+ return N706A_HTTP_EPARSE;
+ }
+
+ bool use_ssl = (strcmp(protocol, "https") == 0);
+
+ n706a_httpc_result_t ret = n706a_http_client_connect(client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ return ret;
+ }
+
+ return n706a_http_client_post(client, path, content_type, body, body_len, client_data);
+}
+
+/* ========== 私有函数实现 ========== */
+
+/**
+ * @brief 构造HTTP请求
+ */
+static int http_build_request(n706a_http_client_t *client,
+ const char *method,
+ const char *path,
+ const char *body,
+ size_t body_len,
+ const char *content_type,
+ char *request_buf,
+ size_t buf_size)
+{
+ int len = 0;
+
+ /* 请求行 */
+ len += snprintf(request_buf + len, buf_size - len,
+ "%s %s HTTP/1.1\r\n", method, path);
+
+ /* Host头 */
+ len += snprintf(request_buf + len, buf_size - len,
+ "Host: %s\r\n", client->host);
+
+ /* User-Agent */
+ len += snprintf(request_buf + len, buf_size - len,
+ "User-Agent: N706A-HTTP-Client/1.0\r\n");
+
+ /* Accept */
+ len += snprintf(request_buf + len, buf_size - len,
+ "Accept: */*\r\n");
+
+ /* Connection */
+ len += snprintf(request_buf + len, buf_size - len,
+ "Connection: %s\r\n",
+ client->keepalive ? "keep-alive" : "close");
+
+ /* Content-Type和Content-Length(POST/PUT) */
+ if (body && body_len > 0) {
+ if (content_type) {
+ len += snprintf(request_buf + len, buf_size - len,
+ "Content-Type: %s\r\n", content_type);
+ }
+
+ len += snprintf(request_buf + len, buf_size - len,
+ "Content-Length: %zu\r\n", body_len);
+ }
+
+ /* Basic认证 */
+ if (client->auth_user && client->auth_password) {
+ char auth_str[256];
+ char auth_b64[512];
+
+ snprintf(auth_str, sizeof(auth_str), "%s:%s",
+ client->auth_user, client->auth_password);
+
+ http_base64_encode(auth_str, strlen(auth_str), auth_b64);
+
+ len += snprintf(request_buf + len, buf_size - len,
+ "Authorization: Basic %s\r\n", auth_b64);
+ }
+
+ /* 自定义请求头 */
+ if (client->header) {
+ len += snprintf(request_buf + len, buf_size - len,
+ "%s", client->header);
+ }
+
+ /* 空行 */
+ len += snprintf(request_buf + len, buf_size - len, "\r\n");
+
+ /* 请求体 */
+ if (body && body_len > 0) {
+ if (len + body_len < buf_size) {
+ memcpy(request_buf + len, body, body_len);
+ len += body_len;
+ } else {
+ return -1; // 缓冲区不足
+ }
+ }
+
+ return len;
+}
+
+/**
+ * @brief 解析HTTP响应
+ */
+static n706a_httpc_result_t http_parse_response(const char *response,
+ size_t response_len,
+ n706a_http_client_t *client,
+ n706a_http_client_data_t *data)
+{
+ if (!response || !client || !data) {
+ return N706A_HTTP_EARG;
+ }
+
+ /* 解析状态行 */
+ int major, minor, status_code;
+ if (sscanf(response, "HTTP/%d.%d %d", &major, &minor, &status_code) != 3) {
+ return N706A_HTTP_EPROTO;
+ }
+
+ client->response_code = status_code;
+
+ /* 查找头尾分界 */
+ const char *header_end = strstr(response, "\r\n\r\n");
+ if (!header_end) {
+ return N706A_HTTP_EPROTO;
+ }
+
+ /* 提取响应头 */
+ size_t header_len = header_end - response;
+ if (data->headerBuf && data->headerBufLen > 0) {
+ size_t copy_len = (header_len < (size_t)data->headerBufLen - 1) ?
+ header_len : (size_t)data->headerBufLen - 1;
+ memcpy(data->headerBuf, response, copy_len);
+ data->headerBuf[copy_len] = '\0';
+ }
+
+ /* 提取Content-Length */
+ const char *content_len_str = strcasestr(response, "Content-Length:");
+ if (content_len_str && content_len_str < header_end) {
+ data->responseContentLen = atoi(content_len_str + 15);
+ } else {
+ data->responseContentLen = 0;
+ }
+
+ /* 检查是否chunked */
+ if (strcasestr(response, "Transfer-Encoding: chunked")) {
+ data->isChunked = true;
+ } else {
+ data->isChunked = false;
+ }
+
+ /* 提取响应体 */
+ const char *body = header_end + 4;
+ size_t body_len = response_len - (body - response);
+
+ if (data->responseBuf && data->responseBufLen > 0) {
+ size_t copy_len = (body_len < (size_t)data->responseBufLen - 1) ?
+ body_len : (size_t)data->responseBufLen - 1;
+ memcpy(data->responseBuf, body, copy_len);
+ data->responseBuf[copy_len] = '\0';
+ data->contentBlockLen = copy_len;
+ }
+
+ /* 检查是否还有更多数据 */
+ if (data->responseContentLen > 0 && body_len < (size_t)data->responseContentLen) {
+ data->isMore = true;
+ } else {
+ data->isMore = false;
+ }
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief Base64编码
+ */
+static void http_base64_encode(const char *input, size_t len, char *output)
+{
+ size_t i = 0, j = 0;
+ unsigned char char_array_3[3];
+ unsigned char char_array_4[4];
+
+ while (len--) {
+ char_array_3[i++] = *(input++);
+ if (i == 3) {
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+ char_array_4[3] = char_array_3[2] & 0x3f;
+
+ for (i = 0; i < 4; i++) {
+ output[j++] = base64_table[char_array_4[i]];
+ }
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (size_t k = i; k < 3; k++) {
+ char_array_3[k] = '\0';
+ }
+
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+
+ for (size_t k = 0; k < i + 1; k++) {
+ output[j++] = base64_table[char_array_4[k]];
+ }
+
+ while (i++ < 3) {
+ output[j++] = '=';
+ }
+ }
+
+ output[j] = '\0';
+}
+
+/**
+ * @brief 解析URL
+ */
+static int http_parse_url(const char *url,
+ char *protocol,
+ char *host,
+ uint16_t *port,
+ char *path)
+{
+ if (!url || !protocol || !host || !port || !path) {
+ return -1;
+ }
+
+ const char *p = url;
+
+ /* 解析协议 */
+ if (strncmp(p, "https://", 8) == 0) {
+ strcpy(protocol, "https");
+ *port = 443;
+ p += 8;
+ } else if (strncmp(p, "http://", 7) == 0) {
+ strcpy(protocol, "http");
+ *port = 80;
+ p += 7;
+ } else {
+ return -1;
+ }
+
+ /* 解析host和port */
+ const char *path_start = strchr(p, '/');
+ const char *port_start = strchr(p, ':');
+
+ if (port_start && (!path_start || port_start < path_start)) {
+ /* 有端口号 */
+ size_t host_len = port_start - p;
+ strncpy(host, p, host_len);
+ host[host_len] = '\0';
+
+ *port = atoi(port_start + 1);
+
+ if (path_start) {
+ strcpy(path, path_start);
+ } else {
+ strcpy(path, "/");
+ }
+ } else {
+ /* 没有端口号 */
+ if (path_start) {
+ size_t host_len = path_start - p;
+ strncpy(host, p, host_len);
+ host[host_len] = '\0';
+ strcpy(path, path_start);
+ } else {
+ strcpy(host, p);
+ strcpy(path, "/");
+ }
+ }
+
+ return 0;
+}
+
--- /dev/null
+/**
+ * @file n706a_tcp_client.c
+ * @brief N706A TCP客户端实现
+ *
+ * 支持两种SSL实现方式:
+ * - AT指令模式:使用AT+SSLTCP*指令
+ * - mbedtls模式:使用mbedtls库
+ */
+
+#include "n706a_tcp_client.h"
+#include "ppp_at_handler.h"
+#include "ppp_types.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* ========== 私有函数声明 ========== */
+
+#ifdef N706A_SSL_USE_MBEDTLS
+static int tcp_mbedtls_send_callback(void *ctx, const unsigned char *buf, size_t len);
+static int tcp_mbedtls_recv_callback(void *ctx, unsigned char *buf, size_t len);
+static n706a_tcp_result_t tcp_mbedtls_init_ssl(n706a_tcp_socket_t *sock);
+static void tcp_mbedtls_cleanup_ssl(n706a_tcp_socket_t *sock);
+#endif
+
+/* ========== 公共函数实现 ========== */
+
+/**
+ * @brief 创建TCP socket
+ */
+n706a_tcp_result_t n706a_tcp_socket_create(n706a_tcp_socket_t *sock, int socket_id)
+{
+ if (!sock || socket_id < 0 || socket_id > 5) {
+ return N706A_TCP_EINVAL;
+ }
+
+ memset(sock, 0, sizeof(n706a_tcp_socket_t));
+
+ sock->socket_id = socket_id;
+ sock->state = N706A_TCP_STATE_CLOSED;
+ sock->is_ssl = false;
+ sock->timeout_ms = 15000; // 默认15秒超时
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ sock->ssl_version = 3; // 默认TLS 1.2
+ sock->auth_mode = 1; // 默认服务器认证
+ memset(sock->ca_cert_file, 0, sizeof(sock->ca_cert_file));
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ sock->ssl_version = MBEDTLS_SSL_MINOR_VERSION_3; // 默认TLS 1.2
+ sock->auth_mode = MBEDTLS_SSL_VERIFY_REQUIRED; // 默认服务器认证
+ sock->ssl_initialized = false;
+#endif
+
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 配置SSL参数
+ */
+n706a_tcp_result_t n706a_tcp_config_ssl(n706a_tcp_socket_t *sock,
+ int ssl_version,
+ int auth_mode,
+ const char *ca_cert)
+{
+ if (!sock) {
+ return N706A_TCP_EINVAL;
+ }
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ /* AT指令模式:保存配置参数 */
+ sock->ssl_version = ssl_version;
+ sock->auth_mode = auth_mode;
+
+ if (ca_cert) {
+ strncpy(sock->ca_cert_file, ca_cert, sizeof(sock->ca_cert_file) - 1);
+
+ /* 配置AT指令 */
+ char at_cmd[256];
+
+ /* 设置SSL版本 */
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"sslversion\",%d", ssl_version);
+ ppp_at_response_t response;
+ ppp_at_send_command(at_cmd, NULL, 5000, &response);
+
+ /* 设置认证模式 */
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"authmode\",%d", auth_mode);
+ ppp_at_send_command(at_cmd, NULL, 5000, &response);
+
+ /* 设置CA证书 */
+ if (ca_cert && strlen(ca_cert) > 0) {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"cacert\",\"%s\"", ca_cert);
+ ppp_at_send_command(at_cmd, NULL, 5000, &response);
+ }
+ }
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:解析CA证书 */
+ if (ca_cert && strlen(ca_cert) > 0) {
+ mbedtls_x509_crt_init(&sock->cacert);
+
+ int ret = mbedtls_x509_crt_parse(&sock->cacert,
+ (const unsigned char *)ca_cert,
+ strlen(ca_cert) + 1);
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+ }
+
+ sock->auth_mode = auth_mode;
+#endif
+
+ return N706A_TCP_SUCCESS;
+}
+
+#ifdef N706A_SSL_USE_MBEDTLS
+/**
+ * @brief 配置客户端证书和私钥(mbedtls专用)
+ */
+n706a_tcp_result_t n706a_tcp_config_ssl_client_cert(n706a_tcp_socket_t *sock,
+ const char *client_cert,
+ const char *client_key)
+{
+ if (!sock || !client_cert || !client_key) {
+ return N706A_TCP_EINVAL;
+ }
+
+ /* 解析客户端证书 */
+ mbedtls_x509_crt_init(&sock->clicert);
+ int ret = mbedtls_x509_crt_parse(&sock->clicert,
+ (const unsigned char *)client_cert,
+ strlen(client_cert) + 1);
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 解析私钥 */
+ mbedtls_pk_init(&sock->pkey);
+ ret = mbedtls_pk_parse_key(&sock->pkey,
+ (const unsigned char *)client_key,
+ strlen(client_key) + 1,
+ NULL, 0
+#if MBEDTLS_VERSION_MAJOR >= 3
+ , mbedtls_ctr_drbg_random, &sock->ctr_drbg
+#endif
+ );
+ if (ret != 0) {
+ mbedtls_x509_crt_free(&sock->clicert);
+ return N706A_TCP_ERROR;
+ }
+
+ return N706A_TCP_SUCCESS;
+}
+#endif
+
+/**
+ * @brief 连接到TCP服务器
+ */
+n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock,
+ const char *host,
+ uint16_t port,
+ bool use_ssl,
+ uint32_t timeout_ms)
+{
+ if (!sock || !host) {
+ return N706A_TCP_EINVAL;
+ }
+
+ if (sock->state == N706A_TCP_STATE_CONNECTED) {
+ return N706A_TCP_SUCCESS; // 已连接
+ }
+
+ /* 保存连接信息 */
+ strncpy(sock->remote_ip, host, sizeof(sock->remote_ip) - 1);
+ sock->remote_port = port;
+ sock->is_ssl = use_ssl;
+ sock->timeout_ms = timeout_ms;
+
+ char at_cmd[256];
+ ppp_at_response_t response;
+ ppp_error_t ret;
+
+ sock->state = N706A_TCP_STATE_CONNECTING;
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ if (use_ssl) {
+ /* AT指令SSL模式:使用AT+SSLTCPSETUP */
+ snprintf(at_cmd, sizeof(at_cmd),
+ "AT+SSLTCPSETUP=%d,%s,%d,%d",
+ sock->socket_id,
+ host,
+ port,
+ sock->auth_mode);
+
+ ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response);
+
+ if (ret == PPP_ERR_OK && response.length > 0 &&
+ strstr(response.response, "+SSLTCPSETUP:") &&
+ strstr(response.response, ",OK")) {
+ sock->state = N706A_TCP_STATE_CONNECTED;
+ return N706A_TCP_SUCCESS;
+ }
+ } else {
+ /* 普通TCP */
+ snprintf(at_cmd, sizeof(at_cmd),
+ "AT+TCPSETUP=%d,%s,%d",
+ sock->socket_id,
+ host,
+ port);
+
+ ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response);
+
+ if (ret == PPP_ERR_OK && response.length > 0 &&
+ strstr(response.response, "+TCPSETUP:") &&
+ strstr(response.response, ",OK")) {
+ sock->state = N706A_TCP_STATE_CONNECTED;
+ return N706A_TCP_SUCCESS;
+ }
+ }
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:先建立TCP,再进行SSL握手 */
+
+ /* 1. 先建立普通TCP连接 */
+ snprintf(at_cmd, sizeof(at_cmd),
+ "AT+TCPSETUP=%d,%s,%d",
+ sock->socket_id,
+ host,
+ port);
+
+ ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response);
+
+ if (ret != PPP_ERR_OK || response.length == 0 ||
+ !strstr(response.response, "+TCPSETUP:") ||
+ !strstr(response.response, ",OK")) {
+ sock->state = N706A_TCP_STATE_ERROR;
+ return N706A_TCP_ECONNREF;
+ }
+
+ /* 2. 如果使用SSL,进行SSL握手 */
+ if (use_ssl) {
+ n706a_tcp_result_t ssl_ret = tcp_mbedtls_init_ssl(sock);
+ if (ssl_ret != N706A_TCP_SUCCESS) {
+ n706a_tcp_close(sock);
+ return ssl_ret;
+ }
+
+ /* 设置主机名(用于SNI) */
+ mbedtls_ssl_set_hostname(&sock->ssl, host);
+
+ /* 执行SSL握手 */
+ int handshake_ret;
+ while ((handshake_ret = mbedtls_ssl_handshake(&sock->ssl)) != 0) {
+ if (handshake_ret != MBEDTLS_ERR_SSL_WANT_READ &&
+ handshake_ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
+ /* 握手失败 */
+ tcp_mbedtls_cleanup_ssl(sock);
+ n706a_tcp_close(sock);
+ sock->state = N706A_TCP_STATE_ERROR;
+ return N706A_TCP_ERROR;
+ }
+ }
+
+ /* 验证证书 */
+ uint32_t flags = mbedtls_ssl_get_verify_result(&sock->ssl);
+ if (flags != 0 && sock->auth_mode != MBEDTLS_SSL_VERIFY_NONE) {
+ /* 证书验证失败 */
+ tcp_mbedtls_cleanup_ssl(sock);
+ n706a_tcp_close(sock);
+ sock->state = N706A_TCP_STATE_ERROR;
+ return N706A_TCP_ERROR;
+ }
+ }
+#endif
+
+ sock->state = N706A_TCP_STATE_CONNECTED;
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 发送TCP数据
+ */
+n706a_tcp_result_t n706a_tcp_send(n706a_tcp_socket_t *sock,
+ const uint8_t *data,
+ size_t len,
+ size_t *sent)
+{
+ if (!sock || !data || len == 0 || !sent) {
+ return N706A_TCP_EINVAL;
+ }
+
+ if (sock->state != N706A_TCP_STATE_CONNECTED) {
+ return N706A_TCP_ENOTCONN;
+ }
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:通过SSL发送 */
+ if (sock->is_ssl && sock->ssl_initialized) {
+ int ret = mbedtls_ssl_write(&sock->ssl, data, len);
+ if (ret > 0) {
+ *sent = ret;
+ return N706A_TCP_SUCCESS;
+ } else {
+ return N706A_TCP_ERROR;
+ }
+ }
+#endif
+
+ /* AT指令模式或普通TCP */
+ char at_cmd[64];
+ ppp_at_response_t response;
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ if (sock->is_ssl) {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPSEND=%d,%zu",
+ sock->socket_id, len);
+ } else
+#endif
+ {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPSEND=%d,%zu",
+ sock->socket_id, len);
+ }
+
+ /* 发送命令,等待'>'提示符 */
+ ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, 5000, &response);
+ if (ret != PPP_ERR_OK) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 发送实际数据 */
+ ret = ppp_at_send_raw_data(data, len, sock->timeout_ms);
+ if (ret != PPP_ERR_OK) {
+ return N706A_TCP_ERROR;
+ }
+
+ *sent = len;
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 接收TCP数据
+ */
+n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock,
+ uint8_t *buffer,
+ size_t len,
+ size_t *received,
+ uint32_t timeout_ms)
+{
+ if (!sock || !buffer || len == 0 || !received) {
+ return N706A_TCP_EINVAL;
+ }
+
+ if (sock->state != N706A_TCP_STATE_CONNECTED) {
+ return N706A_TCP_ENOTCONN;
+ }
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:通过SSL接收 */
+ if (sock->is_ssl && sock->ssl_initialized) {
+ mbedtls_ssl_conf_read_timeout(&sock->conf, timeout_ms);
+
+ int ret = mbedtls_ssl_read(&sock->ssl, buffer, len);
+ if (ret > 0) {
+ *received = ret;
+ return N706A_TCP_SUCCESS;
+ } else if (ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+ *received = 0;
+ return N706A_TCP_SUCCESS;
+ } else {
+ return N706A_TCP_ERROR;
+ }
+ }
+#endif
+
+ /* AT指令模式或普通TCP */
+ char at_cmd[64];
+ ppp_at_response_t response;
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ if (sock->is_ssl) {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPREAD=%d,%zu",
+ sock->socket_id, len);
+ } else
+#endif
+ {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPREAD=%d,%zu",
+ sock->socket_id, len);
+ }
+
+ ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response);
+ if (ret != PPP_ERR_OK) {
+ return N706A_TCP_ETIMEDOUT;
+ }
+
+ /* 解析响应,提取数据 */
+ if (response.length > 0) {
+ /* 查找数据部分 */
+ const char *data_start = strstr(response.response, "\r\n");
+ if (data_start) {
+ data_start += 2; // 跳过\r\n
+ size_t data_len = response.length - (data_start - response.response);
+
+ if (data_len > len) {
+ data_len = len;
+ }
+
+ memcpy(buffer, data_start, data_len);
+ *received = data_len;
+ return N706A_TCP_SUCCESS;
+ }
+ }
+
+ *received = 0;
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 关闭TCP连接
+ */
+n706a_tcp_result_t n706a_tcp_close(n706a_tcp_socket_t *sock)
+{
+ if (!sock) {
+ return N706A_TCP_EINVAL;
+ }
+
+ if (sock->state == N706A_TCP_STATE_CLOSED) {
+ return N706A_TCP_SUCCESS;
+ }
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:清理SSL上下文 */
+ if (sock->is_ssl && sock->ssl_initialized) {
+ mbedtls_ssl_close_notify(&sock->ssl);
+ tcp_mbedtls_cleanup_ssl(sock);
+ }
+#endif
+
+ /* 发送关闭命令 */
+ char at_cmd[32];
+ ppp_at_response_t response;
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ if (sock->is_ssl) {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCLOSE=%d", sock->socket_id);
+ } else
+#endif
+ {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPCLOSE=%d", sock->socket_id);
+ }
+
+ ppp_at_send_command(at_cmd, NULL, 5000, &response);
+
+ sock->state = N706A_TCP_STATE_CLOSED;
+ sock->is_ssl = false;
+
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 检查socket是否已连接
+ */
+bool n706a_tcp_is_connected(const n706a_tcp_socket_t *sock)
+{
+ if (!sock) {
+ return false;
+ }
+
+ return (sock->state == N706A_TCP_STATE_CONNECTED);
+}
+
+/**
+ * @brief 获取socket状态
+ */
+n706a_tcp_state_t n706a_tcp_get_state(const n706a_tcp_socket_t *sock)
+{
+ if (!sock) {
+ return N706A_TCP_STATE_ERROR;
+ }
+
+ return sock->state;
+}
+
+/* ========== mbedtls私有函数实现 ========== */
+
+#ifdef N706A_SSL_USE_MBEDTLS
+
+/**
+ * @brief 初始化mbedtls SSL
+ */
+static n706a_tcp_result_t tcp_mbedtls_init_ssl(n706a_tcp_socket_t *sock)
+{
+ if (!sock) {
+ return N706A_TCP_EINVAL;
+ }
+
+ /* 初始化SSL上下文 */
+ mbedtls_ssl_init(&sock->ssl);
+ mbedtls_ssl_config_init(&sock->conf);
+ mbedtls_entropy_init(&sock->entropy);
+ mbedtls_ctr_drbg_init(&sock->ctr_drbg);
+
+ /* 初始化随机数生成器 */
+ const char *pers = "n706a_tcp_client";
+ int ret = mbedtls_ctr_drbg_seed(&sock->ctr_drbg,
+ mbedtls_entropy_func,
+ &sock->entropy,
+ (const unsigned char *)pers,
+ strlen(pers));
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 配置SSL */
+ ret = mbedtls_ssl_config_defaults(&sock->conf,
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_TRANSPORT_STREAM,
+ MBEDTLS_SSL_PRESET_DEFAULT);
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 设置认证模式 */
+ mbedtls_ssl_conf_authmode(&sock->conf, sock->auth_mode);
+
+ /* 设置随机数生成器 */
+ mbedtls_ssl_conf_rng(&sock->conf,
+ mbedtls_ctr_drbg_random,
+ &sock->ctr_drbg);
+
+ /* 设置CA证书 */
+ mbedtls_ssl_conf_ca_chain(&sock->conf, &sock->cacert, NULL);
+
+ /* 设置客户端证书(如果有) */
+ if (sock->clicert.version != 0) {
+ mbedtls_ssl_conf_own_cert(&sock->conf, &sock->clicert, &sock->pkey);
+ }
+
+ /* 应用配置 */
+ ret = mbedtls_ssl_setup(&sock->ssl, &sock->conf);
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 设置发送/接收回调 */
+ mbedtls_ssl_set_bio(&sock->ssl,
+ sock,
+ tcp_mbedtls_send_callback,
+ tcp_mbedtls_recv_callback,
+ NULL);
+
+ sock->ssl_initialized = true;
+
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 清理mbedtls SSL
+ */
+static void tcp_mbedtls_cleanup_ssl(n706a_tcp_socket_t *sock)
+{
+ if (!sock || !sock->ssl_initialized) {
+ return;
+ }
+
+ mbedtls_ssl_free(&sock->ssl);
+ mbedtls_ssl_config_free(&sock->conf);
+ mbedtls_ctr_drbg_free(&sock->ctr_drbg);
+ mbedtls_entropy_free(&sock->entropy);
+
+ if (sock->cacert.version != 0) {
+ mbedtls_x509_crt_free(&sock->cacert);
+ }
+
+ if (sock->clicert.version != 0) {
+ mbedtls_x509_crt_free(&sock->clicert);
+ }
+
+ /* 检查私钥是否已初始化,在mbedtls 2.x和3.x中都有效 */
+ mbedtls_pk_free(&sock->pkey);
+
+ sock->ssl_initialized = false;
+}
+
+/**
+ * @brief mbedtls发送回调(通过AT命令发送)
+ */
+static int tcp_mbedtls_send_callback(void *ctx, const unsigned char *buf, size_t len)
+{
+ n706a_tcp_socket_t *sock = (n706a_tcp_socket_t *)ctx;
+
+ if (!sock || !buf || len == 0) {
+ return MBEDTLS_ERR_NET_SEND_FAILED;
+ }
+
+ /* 通过AT+TCPSEND发送加密后的数据 */
+ char at_cmd[64];
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPSEND=%d,%zu",
+ sock->socket_id, len);
+
+ ppp_at_response_t response;
+ ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, 5000, &response);
+ if (ret != PPP_ERR_OK) {
+ return MBEDTLS_ERR_NET_SEND_FAILED;
+ }
+
+ /* 发送数据 */
+ ret = ppp_at_send_raw_data(buf, len, sock->timeout_ms);
+ if (ret != PPP_ERR_OK) {
+ return MBEDTLS_ERR_NET_SEND_FAILED;
+ }
+
+ return len;
+}
+
+/**
+ * @brief mbedtls接收回调(通过AT命令接收)
+ */
+static int tcp_mbedtls_recv_callback(void *ctx, unsigned char *buf, size_t len)
+{
+ n706a_tcp_socket_t *sock = (n706a_tcp_socket_t *)ctx;
+
+ if (!sock || !buf || len == 0) {
+ return MBEDTLS_ERR_NET_RECV_FAILED;
+ }
+
+ /* 通过AT+TCPREAD接收加密数据 */
+ char at_cmd[64];
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPREAD=%d,%zu",
+ sock->socket_id, len);
+
+ ppp_at_response_t response;
+ ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, sock->timeout_ms, &response);
+ if (ret != PPP_ERR_OK) {
+ return MBEDTLS_ERR_NET_RECV_FAILED;
+ }
+
+ /* 解析数据 */
+ if (response.length > 0) {
+ const char *data_start = strstr(response.response, "\r\n");
+ if (data_start) {
+ data_start += 2;
+ size_t data_len = response.length - (data_start - response.response);
+
+ if (data_len > len) {
+ data_len = len;
+ }
+
+ memcpy(buf, data_start, data_len);
+ return data_len;
+ }
+ }
+
+ return 0; // 没有数据
+}
+
+#endif /* N706A_SSL_USE_MBEDTLS */
+
--- /dev/null
+/**
+ * @file n706a_websocket.c
+ * @brief N706A WebSocket客户端实现(基于TCP)
+ *
+ * 完整实现RFC 6455 WebSocket协议:
+ * - ws:// (普通TCP)
+ * - wss:// (SSL TCP)
+ * - 握手
+ * - 帧封装/解封装
+ * - Ping/Pong
+ * - 分片传输
+ */
+
+#include "n706a_websocket.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+/* WebSocket GUID(RFC 6455) */
+#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+/* WebSocket帧标志 */
+#define WS_FIN_BIT 0x80
+#define WS_MASK_BIT 0x80
+#define WS_OPCODE_MASK 0x0F
+#define WS_PAYLOAD_MASK 0x7F
+
+/* 全局WebSocket客户端数组 */
+static n706a_websocket_client_t *g_ws_clients[N706A_WS_MAX_CLIENTS] = {NULL};
+
+/* ========== 私有函数声明 ========== */
+
+static int ws_perform_handshake(n706a_websocket_client_t *client);
+static int ws_send_frame(n706a_websocket_client_t *client,
+ n706a_ws_opcode_t opcode,
+ const uint8_t *data,
+ size_t length,
+ bool fin);
+static int ws_recv_frame(n706a_websocket_client_t *client);
+static void ws_generate_mask_key(uint8_t *mask_key);
+static void ws_mask_data(uint8_t *data, size_t len, const uint8_t *mask_key);
+static void *ws_recv_thread_func(void *arg);
+static int ws_parse_url(const char *url, char *protocol, char *host,
+ uint16_t *port, char *path);
+
+/* SHA-1哈希(简化实现,用于WebSocket握手) */
+static void sha1_simple(const unsigned char *input, size_t len, unsigned char output[20]);
+
+/* Base64编码 */
+static void base64_encode(const unsigned char *input, size_t len, char *output);
+
+/* ========== 公共函数实现 ========== */
+
+/**
+ * @brief 创建WebSocket客户端
+ */
+int n706a_websocket_constructor(n706a_ws_on_open_callback_t on_open,
+ n706a_ws_on_message_callback_t on_message,
+ n706a_ws_on_error_callback_t on_error,
+ n706a_ws_on_close_callback_t on_close)
+{
+ /* 查找空闲slot */
+ int client_id = -1;
+ for (int i = 0; i < N706A_WS_MAX_CLIENTS; i++) {
+ if (g_ws_clients[i] == NULL) {
+ client_id = i;
+ break;
+ }
+ }
+
+ if (client_id < 0) {
+ return -1; // 没有可用slot
+ }
+
+ /* 分配客户端结构 */
+ n706a_websocket_client_t *client =
+ (n706a_websocket_client_t *)malloc(sizeof(n706a_websocket_client_t));
+
+ if (!client) {
+ return -1;
+ }
+
+ memset(client, 0, sizeof(n706a_websocket_client_t));
+
+ client->client_id = client_id;
+ client->state = N706A_WS_STATE_CLOSED;
+ client->is_ssl = false;
+
+ /* 初始化TCP socket */
+ n706a_tcp_socket_create(&client->tcp_sock, client_id);
+
+ /* 设置回调 */
+ client->on_open = on_open;
+ client->on_message = on_message;
+ client->on_error = on_error;
+ client->on_close = on_close;
+
+ /* 分配接收缓冲区 */
+ client->recv_buffer_size = 8192;
+ client->recv_buffer = (uint8_t *)malloc(client->recv_buffer_size);
+ if (!client->recv_buffer) {
+ free(client);
+ return -1;
+ }
+
+ client->recv_buffer_len = 0;
+ client->running = false;
+
+ g_ws_clients[client_id] = client;
+
+ return client_id;
+}
+
+/**
+ * @brief 创建WebSocket客户端(指定socket ID)
+ */
+int n706a_websocket_constructor_with_socket(int socket_id,
+ n706a_ws_on_open_callback_t on_open,
+ n706a_ws_on_message_callback_t on_message,
+ n706a_ws_on_error_callback_t on_error,
+ n706a_ws_on_close_callback_t on_close)
+{
+ if (socket_id < 0 || socket_id >= N706A_WS_MAX_CLIENTS) {
+ return -1;
+ }
+
+ if (g_ws_clients[socket_id] != NULL) {
+ return -1; // 已被占用
+ }
+
+ n706a_websocket_client_t *client =
+ (n706a_websocket_client_t *)malloc(sizeof(n706a_websocket_client_t));
+
+ if (!client) {
+ return -1;
+ }
+
+ memset(client, 0, sizeof(n706a_websocket_client_t));
+
+ client->client_id = socket_id;
+ client->state = N706A_WS_STATE_CLOSED;
+
+ n706a_tcp_socket_create(&client->tcp_sock, socket_id);
+
+ client->on_open = on_open;
+ client->on_message = on_message;
+ client->on_error = on_error;
+ client->on_close = on_close;
+
+ client->recv_buffer_size = 8192;
+ client->recv_buffer = (uint8_t *)malloc(client->recv_buffer_size);
+ if (!client->recv_buffer) {
+ free(client);
+ return -1;
+ }
+
+ g_ws_clients[socket_id] = client;
+
+ return socket_id;
+}
+
+/**
+ * @brief 连接到WebSocket服务器
+ */
+int n706a_websocket_connect(int client_id, const char *url, const char *option)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ if (client->state != N706A_WS_STATE_CLOSED) {
+ return N706A_WS_ERROR_ALREADY_OPEN;
+ }
+
+ /* 解析URL */
+ char protocol[8];
+
+ if (ws_parse_url(url, protocol, client->host, &client->port, client->path) != 0) {
+ if (client->on_error) {
+ client->on_error(client_id, N706A_WS_ERROR_INVALID_PARAM, "Invalid URL");
+ }
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ strncpy(client->url, url, sizeof(client->url) - 1);
+
+ /* 判断是否WSS */
+ client->is_ssl = (strcmp(protocol, "wss") == 0);
+
+ client->state = N706A_WS_STATE_CONNECTING;
+
+ /* 建立TCP连接 */
+ n706a_tcp_result_t ret = n706a_tcp_connect(&client->tcp_sock,
+ client->host,
+ client->port,
+ client->is_ssl,
+ 30000);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ client->state = N706A_WS_STATE_CLOSED;
+ if (client->on_error) {
+ client->on_error(client_id, N706A_WS_ERROR_HANDSHAKE, "TCP connection failed");
+ }
+ return N706A_WS_ERROR_HANDSHAKE;
+ }
+
+ /* WebSocket握手 */
+ if (ws_perform_handshake(client) != 0) {
+ n706a_tcp_close(&client->tcp_sock);
+ client->state = N706A_WS_STATE_CLOSED;
+ if (client->on_error) {
+ client->on_error(client_id, N706A_WS_ERROR_HANDSHAKE, "WebSocket handshake failed");
+ }
+ return N706A_WS_ERROR_HANDSHAKE;
+ }
+
+ client->state = N706A_WS_STATE_OPEN;
+
+ /* 启动接收线程 */
+ client->running = true;
+ // TODO: 创建接收线程
+ // pthread_create(&client->recv_thread, NULL, ws_recv_thread_func, client);
+
+ /* 调用on_open回调 */
+ if (client->on_open) {
+ client->on_open(client_id);
+ }
+
+ return N706A_WS_SUCCESS;
+}
+
+/**
+ * @brief 发送文本消息
+ */
+int n706a_websocket_send_text(int client_id, const char *data, size_t length)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client || client->state != N706A_WS_STATE_OPEN) {
+ return N706A_WS_ERROR_NOT_CONNECTED;
+ }
+
+ return ws_send_frame(client, N706A_WS_OPCODE_TEXT, (const uint8_t *)data, length, true);
+}
+
+/**
+ * @brief 发送二进制消息
+ */
+int n706a_websocket_send_binary(int client_id, const uint8_t *data, size_t length)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client || client->state != N706A_WS_STATE_OPEN) {
+ return N706A_WS_ERROR_NOT_CONNECTED;
+ }
+
+ return ws_send_frame(client, N706A_WS_OPCODE_BINARY, data, length, true);
+}
+
+/**
+ * @brief 发送Ping帧
+ */
+int n706a_websocket_send_ping(int client_id, const char *data, size_t length)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client || client->state != N706A_WS_STATE_OPEN) {
+ return N706A_WS_ERROR_NOT_CONNECTED;
+ }
+
+ return ws_send_frame(client, N706A_WS_OPCODE_PING, (const uint8_t *)data, length, true);
+}
+
+/**
+ * @brief 发送Pong帧
+ */
+int n706a_websocket_send_pong(int client_id, const char *data, size_t length)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client || client->state != N706A_WS_STATE_OPEN) {
+ return N706A_WS_ERROR_NOT_CONNECTED;
+ }
+
+ return ws_send_frame(client, N706A_WS_OPCODE_PONG, (const uint8_t *)data, length, true);
+}
+
+/**
+ * @brief 关闭WebSocket连接
+ */
+int n706a_websocket_close(int client_id)
+{
+ return n706a_websocket_close_with_reason(client_id, N706A_WS_CLOSE_NORMAL, NULL);
+}
+
+/**
+ * @brief 关闭WebSocket连接(带原因)
+ */
+int n706a_websocket_close_with_reason(int client_id,
+ n706a_ws_close_code_t close_code,
+ const char *reason)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ if (client->state == N706A_WS_STATE_OPEN) {
+ /* 发送关闭帧 */
+ uint8_t close_data[128];
+ size_t close_len = 2;
+
+ close_data[0] = (close_code >> 8) & 0xFF;
+ close_data[1] = close_code & 0xFF;
+
+ if (reason) {
+ size_t reason_len = strlen(reason);
+ if (reason_len > sizeof(close_data) - 2) {
+ reason_len = sizeof(close_data) - 2;
+ }
+ memcpy(close_data + 2, reason, reason_len);
+ close_len += reason_len;
+ }
+
+ ws_send_frame(client, N706A_WS_OPCODE_CLOSE, close_data, close_len, true);
+
+ client->state = N706A_WS_STATE_CLOSING;
+ }
+
+ /* 停止接收线程 */
+ client->running = false;
+ // TODO: 等待线程结束
+ // pthread_join(client->recv_thread, NULL);
+
+ /* 关闭TCP连接 */
+ n706a_tcp_close(&client->tcp_sock);
+
+ client->state = N706A_WS_STATE_CLOSED;
+
+ /* 调用on_close回调 */
+ if (client->on_close) {
+ client->on_close(client_id, close_code, reason);
+ }
+
+ return N706A_WS_SUCCESS;
+}
+
+/**
+ * @brief 获取WebSocket状态
+ */
+n706a_ws_state_t n706a_websocket_get_state(int client_id)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_STATE_CLOSED;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client) {
+ return N706A_WS_STATE_CLOSED;
+ }
+
+ return client->state;
+}
+
+/**
+ * @brief 检查WebSocket是否已连接
+ */
+bool n706a_websocket_is_connected(int client_id)
+{
+ return (n706a_websocket_get_state(client_id) == N706A_WS_STATE_OPEN);
+}
+
+/**
+ * @brief 销毁WebSocket客户端
+ */
+int n706a_websocket_destructor(int client_id)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client) {
+ return N706A_WS_SUCCESS;
+ }
+
+ /* 确保连接已关闭 */
+ if (client->state != N706A_WS_STATE_CLOSED) {
+ n706a_websocket_close(client_id);
+ }
+
+ /* 释放资源 */
+ if (client->recv_buffer) {
+ free(client->recv_buffer);
+ }
+
+ free(client);
+ g_ws_clients[client_id] = NULL;
+
+ return N706A_WS_SUCCESS;
+}
+
+/* ========== 私有函数实现 ========== */
+
+/**
+ * @brief 执行WebSocket握手
+ */
+static int ws_perform_handshake(n706a_websocket_client_t *client)
+{
+ char handshake_req[1024];
+ char sec_key[25];
+
+ /* 生成Sec-WebSocket-Key */
+ n706a_websocket_generate_key(sec_key);
+ strncpy(client->sec_key, sec_key, sizeof(client->sec_key) - 1);
+
+ /* 构造握手请求 */
+ int len = snprintf(handshake_req, sizeof(handshake_req),
+ "GET %s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: %s\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "\r\n",
+ client->path,
+ client->host,
+ sec_key
+ );
+
+ /* 发送握手请求 */
+ size_t sent;
+ n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock,
+ (uint8_t *)handshake_req,
+ len,
+ &sent);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ return -1;
+ }
+
+ /* 接收握手响应 */
+ uint8_t response[2048];
+ size_t received;
+
+ ret = n706a_tcp_recv(&client->tcp_sock,
+ response,
+ sizeof(response) - 1,
+ &received,
+ 10000);
+
+ if (ret != N706A_TCP_SUCCESS || received == 0) {
+ return -1;
+ }
+
+ response[received] = '\0';
+
+ /* 验证响应 */
+ if (!strstr((char *)response, "HTTP/1.1 101") ||
+ !strstr((char *)response, "Upgrade: websocket") ||
+ !strstr((char *)response, "Connection: Upgrade")) {
+ return -1;
+ }
+
+ /* 验证Sec-WebSocket-Accept */
+ const char *accept_start = strstr((char *)response, "Sec-WebSocket-Accept:");
+ if (accept_start) {
+ accept_start += 21; // 跳过"Sec-WebSocket-Accept:"
+ while (*accept_start == ' ') accept_start++;
+
+ char expected_accept[29];
+ n706a_websocket_calculate_accept(sec_key, expected_accept);
+
+ if (strncmp(accept_start, expected_accept, strlen(expected_accept)) != 0) {
+ return -1; // Accept值不匹配
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * @brief 发送WebSocket帧
+ */
+static int ws_send_frame(n706a_websocket_client_t *client,
+ n706a_ws_opcode_t opcode,
+ const uint8_t *data,
+ size_t length,
+ bool fin)
+{
+ uint8_t frame[14]; // 最大帧头长度
+ size_t frame_len = 0;
+
+ /* 第一字节:FIN + RSV + Opcode */
+ frame[frame_len++] = (fin ? WS_FIN_BIT : 0) | (opcode & WS_OPCODE_MASK);
+
+ /* 第二字节:MASK + Payload length */
+ if (length < 126) {
+ frame[frame_len++] = WS_MASK_BIT | (length & WS_PAYLOAD_MASK);
+ } else if (length < 65536) {
+ frame[frame_len++] = WS_MASK_BIT | 126;
+ frame[frame_len++] = (length >> 8) & 0xFF;
+ frame[frame_len++] = length & 0xFF;
+ } else {
+ frame[frame_len++] = WS_MASK_BIT | 127;
+ for (int i = 7; i >= 0; i--) {
+ frame[frame_len++] = (length >> (i * 8)) & 0xFF;
+ }
+ }
+
+ /* Masking key */
+ uint8_t mask_key[4];
+ ws_generate_mask_key(mask_key);
+ memcpy(frame + frame_len, mask_key, 4);
+ frame_len += 4;
+
+ /* 发送帧头 */
+ size_t sent;
+ n706a_tcp_send(&client->tcp_sock, frame, frame_len, &sent);
+
+ /* 发送payload(掩码后) */
+ if (data && length > 0) {
+ uint8_t *masked_data = (uint8_t *)malloc(length);
+ if (masked_data) {
+ memcpy(masked_data, data, length);
+ ws_mask_data(masked_data, length, mask_key);
+
+ n706a_tcp_send(&client->tcp_sock, masked_data, length, &sent);
+
+ free(masked_data);
+ }
+ }
+
+ return N706A_WS_SUCCESS;
+}
+
+/**
+ * @brief 接收WebSocket帧
+ */
+static int ws_recv_frame(n706a_websocket_client_t *client)
+{
+ uint8_t header[2];
+ size_t received;
+
+ /* 接收帧头前2字节 */
+ n706a_tcp_result_t ret = n706a_tcp_recv(&client->tcp_sock,
+ header,
+ 2,
+ &received,
+ 5000);
+
+ if (ret != N706A_TCP_SUCCESS || received < 2) {
+ return -1;
+ }
+
+ bool fin = (header[0] & WS_FIN_BIT) != 0;
+ n706a_ws_opcode_t opcode = (n706a_ws_opcode_t)(header[0] & WS_OPCODE_MASK);
+ bool masked = (header[1] & WS_MASK_BIT) != 0;
+ uint64_t payload_len = header[1] & WS_PAYLOAD_MASK;
+
+ /* 扩展payload长度 */
+ if (payload_len == 126) {
+ uint8_t len_buf[2];
+ n706a_tcp_recv(&client->tcp_sock, len_buf, 2, &received, 5000);
+ payload_len = (len_buf[0] << 8) | len_buf[1];
+ } else if (payload_len == 127) {
+ uint8_t len_buf[8];
+ n706a_tcp_recv(&client->tcp_sock, len_buf, 8, &received, 5000);
+ payload_len = 0;
+ for (int i = 0; i < 8; i++) {
+ payload_len = (payload_len << 8) | len_buf[i];
+ }
+ }
+
+ /* 接收payload */
+ if (payload_len > 0) {
+ uint8_t *payload = (uint8_t *)malloc(payload_len + 1);
+ if (!payload) {
+ return -1;
+ }
+
+ n706a_tcp_recv(&client->tcp_sock, payload, payload_len, &received, 10000);
+ payload[payload_len] = '\0';
+
+ /* 处理不同类型的帧 */
+ switch (opcode) {
+ case N706A_WS_OPCODE_TEXT:
+ case N706A_WS_OPCODE_BINARY:
+ if (client->on_message) {
+ client->on_message(client->client_id,
+ (char *)payload,
+ payload_len,
+ fin,
+ opcode == N706A_WS_OPCODE_BINARY);
+ }
+ break;
+
+ case N706A_WS_OPCODE_PING:
+ /* 自动回复Pong */
+ ws_send_frame(client, N706A_WS_OPCODE_PONG, payload, payload_len, true);
+ break;
+
+ case N706A_WS_OPCODE_PONG:
+ /* 忽略Pong */
+ break;
+
+ case N706A_WS_OPCODE_CLOSE:
+ /* 服务器关闭连接 */
+ client->state = N706A_WS_STATE_CLOSING;
+
+ uint16_t close_code = N706A_WS_CLOSE_NO_STATUS;
+ if (payload_len >= 2) {
+ close_code = (payload[0] << 8) | payload[1];
+ }
+
+ if (client->on_close) {
+ client->on_close(client->client_id, close_code,
+ payload_len > 2 ? (char *)(payload + 2) : NULL);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ free(payload);
+ }
+
+ return 0;
+}
+
+/**
+ * @brief 生成mask key
+ */
+static void ws_generate_mask_key(uint8_t *mask_key)
+{
+ for (int i = 0; i < 4; i++) {
+ mask_key[i] = rand() & 0xFF;
+ }
+}
+
+/**
+ * @brief 掩码数据
+ */
+static void ws_mask_data(uint8_t *data, size_t len, const uint8_t *mask_key)
+{
+ for (size_t i = 0; i < len; i++) {
+ data[i] ^= mask_key[i % 4];
+ }
+}
+
+/**
+ * @brief 解析WebSocket URL
+ */
+static int ws_parse_url(const char *url, char *protocol, char *host,
+ uint16_t *port, char *path)
+{
+ if (!url || !protocol || !host || !port || !path) {
+ return -1;
+ }
+
+ const char *p = url;
+
+ /* 解析协议 */
+ if (strncmp(p, "wss://", 6) == 0) {
+ strcpy(protocol, "wss");
+ *port = 443;
+ p += 6;
+ } else if (strncmp(p, "ws://", 5) == 0) {
+ strcpy(protocol, "ws");
+ *port = 80;
+ p += 5;
+ } else {
+ return -1;
+ }
+
+ /* 解析host */
+ const char *path_start = strchr(p, '/');
+ const char *port_start = strchr(p, ':');
+
+ if (port_start && (!path_start || port_start < path_start)) {
+ size_t host_len = port_start - p;
+ strncpy(host, p, host_len);
+ host[host_len] = '\0';
+
+ *port = atoi(port_start + 1);
+
+ if (path_start) {
+ strcpy(path, path_start);
+ } else {
+ strcpy(path, "/");
+ }
+ } else {
+ if (path_start) {
+ size_t host_len = path_start - p;
+ strncpy(host, p, host_len);
+ host[host_len] = '\0';
+ strcpy(path, path_start);
+ } else {
+ strcpy(host, p);
+ strcpy(path, "/");
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * @brief 生成WebSocket Key
+ */
+int n706a_websocket_generate_key(char *key)
+{
+ if (!key) {
+ return -1;
+ }
+
+ /* 生成16字节随机数 */
+ unsigned char random_bytes[16];
+ srand(time(NULL));
+ for (int i = 0; i < 16; i++) {
+ random_bytes[i] = rand() & 0xFF;
+ }
+
+ /* Base64编码 */
+ base64_encode(random_bytes, 16, key);
+
+ return 0;
+}
+
+/**
+ * @brief 计算WebSocket Accept
+ */
+int n706a_websocket_calculate_accept(const char *key, char *accept)
+{
+ if (!key || !accept) {
+ return -1;
+ }
+
+ /* 拼接key和GUID */
+ char concat[128];
+ snprintf(concat, sizeof(concat), "%s%s", key, WS_GUID);
+
+ /* SHA-1哈希 */
+ unsigned char hash[20];
+ sha1_simple((unsigned char *)concat, strlen(concat), hash);
+
+ /* Base64编码 */
+ base64_encode(hash, 20, accept);
+
+ return 0;
+}
+
+/* ========== 辅助函数 ========== */
+
+/**
+ * @brief 简化的SHA-1实现(仅用于WebSocket握手)
+ * 注意:这是简化版,生产环境建议使用成熟的加密库
+ */
+static void sha1_simple(const unsigned char *input, size_t len, unsigned char output[20])
+{
+ /* 这里应该是完整的SHA-1实现 */
+ /* 为了简化,这里仅做占位 */
+ /* 生产环境应使用mbedtls_sha1或OpenSSL的SHA1函数 */
+
+ /* TODO: 集成真实的SHA-1实现 */
+ memset(output, 0, 20);
+
+ /* 临时:使用简单哈希作为占位符 */
+ for (size_t i = 0; i < len && i < 20; i++) {
+ output[i % 20] ^= input[i];
+ }
+}
+
+/**
+ * @brief Base64编码
+ */
+static void base64_encode(const unsigned char *input, size_t len, char *output)
+{
+ static const char base64_table[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ size_t i = 0, j = 0;
+ unsigned char char_array_3[3];
+ unsigned char char_array_4[4];
+
+ while (len--) {
+ char_array_3[i++] = *(input++);
+ if (i == 3) {
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+ char_array_4[3] = char_array_3[2] & 0x3f;
+
+ for (i = 0; i < 4; i++) {
+ output[j++] = base64_table[char_array_4[i]];
+ }
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (size_t k = i; k < 3; k++) {
+ char_array_3[k] = '\0';
+ }
+
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+
+ for (size_t k = 0; k < i + 1; k++) {
+ output[j++] = base64_table[char_array_4[k]];
+ }
+
+ while (i++ < 3) {
+ output[j++] = '=';
+ }
+ }
+
+ output[j] = '\0';
+}
+
--- /dev/null
+/**
+ * @file n706a_http_tcp_example.c
+ * @brief N706A HTTP客户端使用示例(基于TCP实现)
+ *
+ * 本示例展示如何使用基于TCP的HTTP/HTTPS客户端:
+ * - 支持6个并发连接
+ * - HTTP使用普通TCP,HTTPS使用SSL TCP
+ * - 完全控制HTTP协议
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "n706a_http_client.h"
+#include "ppp_client.h"
+
+/* ========== 示例1:基本HTTP GET请求 ========== */
+void example_http_get(void)
+{
+ printf("\n=== Example 1: Basic HTTP GET ===\n");
+
+ n706a_http_client_t client;
+ n706a_http_client_data_t data;
+ char response_buf[4096];
+ char header_buf[1024];
+
+ /* 初始化HTTP客户端(使用socket 0) */
+ n706a_http_client_init(&client, 0);
+
+ /* 连接到HTTP服务器 */
+ n706a_httpc_result_t ret = n706a_http_client_connect(&client,
+ "httpbin.org",
+ 80, // HTTP端口
+ false // 不使用SSL
+ );
+
+ if (ret != N706A_HTTP_SUCCESS) {
+ printf("Failed to connect: %d\n", ret);
+ return;
+ }
+
+ printf("Connected to httpbin.org:80\n");
+
+ /* 准备响应缓冲区 */
+ memset(&data, 0, sizeof(data));
+ data.responseBuf = response_buf;
+ data.responseBufLen = sizeof(response_buf);
+ data.headerBuf = header_buf;
+ data.headerBufLen = sizeof(header_buf);
+
+ /* 发送GET请求 */
+ ret = n706a_http_client_get(&client, "/get", &data);
+
+ if (ret == N706A_HTTP_SUCCESS) {
+ printf("HTTP Status: %d\n", client.response_code);
+ printf("Response Headers:\n%s\n", header_buf);
+ printf("Response Body:\n%s\n", response_buf);
+ } else {
+ printf("GET request failed: %d\n", ret);
+ }
+
+ /* 关闭连接 */
+ n706a_http_client_close(&client);
+}
+
+/* ========== 示例2:HTTPS GET请求 ========== */
+void example_https_get(void)
+{
+ printf("\n=== Example 2: HTTPS GET ===\n");
+
+ n706a_http_client_t client;
+ n706a_http_client_data_t data;
+ char response_buf[4096];
+
+ /* 初始化(使用socket 1) */
+ n706a_http_client_init(&client, 1);
+
+ /* 连接到HTTPS服务器 */
+ n706a_httpc_result_t ret = n706a_http_client_connect(&client,
+ "api.github.com",
+ 443, // HTTPS端口
+ true // 使用SSL
+ );
+
+ if (ret != N706A_HTTP_SUCCESS) {
+ printf("Failed to connect: %d\n", ret);
+ return;
+ }
+
+ printf("Connected to api.github.com:443 (SSL)\n");
+
+ /* 准备响应缓冲区 */
+ memset(&data, 0, sizeof(data));
+ data.responseBuf = response_buf;
+ data.responseBufLen = sizeof(response_buf);
+
+ /* 发送GET请求 */
+ ret = n706a_http_client_get(&client, "/", &data);
+
+ if (ret == N706A_HTTP_SUCCESS) {
+ printf("HTTP Status: %d\n", client.response_code);
+ printf("Response: %s\n", response_buf);
+ } else {
+ printf("GET request failed: %d\n", ret);
+ }
+
+ n706a_http_client_close(&client);
+}
+
+/* ========== 示例3:HTTP POST JSON数据 ========== */
+void example_http_post_json(void)
+{
+ printf("\n=== Example 3: HTTP POST with JSON ===\n");
+
+ n706a_http_client_t client;
+ n706a_http_client_data_t data;
+ char response_buf[4096];
+
+ /* 初始化 */
+ n706a_http_client_init(&client, 2);
+
+ /* 连接 */
+ if (n706a_http_client_connect(&client, "httpbin.org", 80, false) != N706A_HTTP_SUCCESS) {
+ printf("Connection failed\n");
+ return;
+ }
+
+ /* 准备JSON数据 */
+ const char *json_data = "{\"name\":\"N706A\",\"type\":\"4G Module\",\"vendor\":\"Neoway\"}";
+
+ /* 准备响应缓冲区 */
+ memset(&data, 0, sizeof(data));
+ data.responseBuf = response_buf;
+ data.responseBufLen = sizeof(response_buf);
+
+ /* 发送POST请求 */
+ n706a_httpc_result_t ret = n706a_http_client_post(&client,
+ "/post",
+ "application/json",
+ json_data,
+ strlen(json_data),
+ &data
+ );
+
+ if (ret == N706A_HTTP_SUCCESS) {
+ printf("HTTP Status: %d\n", client.response_code);
+ printf("Response: %s\n", response_buf);
+ } else {
+ printf("POST request failed: %d\n", ret);
+ }
+
+ n706a_http_client_close(&client);
+}
+
+/* ========== 示例4:自定义请求头 ========== */
+void example_custom_headers(void)
+{
+ printf("\n=== Example 4: Custom Headers ===\n");
+
+ n706a_http_client_t client;
+ n706a_http_client_data_t data;
+ char response_buf[4096];
+
+ n706a_http_client_init(&client, 0);
+
+ /* 设置自定义请求头 */
+ n706a_http_client_set_header(&client,
+ "X-API-Key: your-api-key-here\r\n"
+ "X-Custom-Header: custom-value\r\n"
+ );
+
+ /* 连接并请求 */
+ if (n706a_http_client_connect(&client, "httpbin.org", 80, false) == N706A_HTTP_SUCCESS) {
+ memset(&data, 0, sizeof(data));
+ data.responseBuf = response_buf;
+ data.responseBufLen = sizeof(response_buf);
+
+ n706a_http_client_get(&client, "/headers", &data);
+
+ printf("Response:\n%s\n", response_buf);
+
+ n706a_http_client_close(&client);
+ }
+}
+
+/* ========== 示例5:基本认证 ========== */
+void example_basic_auth(void)
+{
+ printf("\n=== Example 5: Basic Authentication ===\n");
+
+ n706a_http_client_t client;
+ n706a_http_client_data_t data;
+ char response_buf[4096];
+
+ n706a_http_client_init(&client, 0);
+
+ /* 设置用户名和密码 */
+ n706a_http_client_set_auth(&client, "user", "password");
+
+ /* 连接并请求 */
+ if (n706a_http_client_connect(&client, "httpbin.org", 80, false) == N706A_HTTP_SUCCESS) {
+ memset(&data, 0, sizeof(data));
+ data.responseBuf = response_buf;
+ data.responseBufLen = sizeof(response_buf);
+
+ n706a_http_client_get(&client, "/basic-auth/user/password", &data);
+
+ printf("HTTP Status: %d\n", client.response_code);
+ printf("Response: %s\n", response_buf);
+
+ n706a_http_client_close(&client);
+ }
+}
+
+/* ========== 示例6:Keep-Alive长连接 ========== */
+void example_keep_alive(void)
+{
+ printf("\n=== Example 6: Keep-Alive Connection ===\n");
+
+ n706a_http_client_t client;
+ n706a_http_client_data_t data;
+ char response_buf[4096];
+
+ n706a_http_client_init(&client, 0);
+
+ /* 启用Keep-Alive */
+ n706a_http_client_set_keepalive(&client, true);
+
+ /* 连接一次,发送多个请求 */
+ if (n706a_http_client_connect(&client, "httpbin.org", 80, false) == N706A_HTTP_SUCCESS) {
+ memset(&data, 0, sizeof(data));
+ data.responseBuf = response_buf;
+ data.responseBufLen = sizeof(response_buf);
+
+ /* 第一个请求 */
+ printf("Request 1:\n");
+ n706a_http_client_get(&client, "/get?page=1", &data);
+ printf("Status: %d\n", client.response_code);
+
+ /* 第二个请求(复用连接) */
+ printf("Request 2:\n");
+ n706a_http_client_get(&client, "/get?page=2", &data);
+ printf("Status: %d\n", client.response_code);
+
+ /* 第三个请求(复用连接) */
+ printf("Request 3:\n");
+ n706a_http_client_get(&client, "/get?page=3", &data);
+ printf("Status: %d\n", client.response_code);
+
+ n706a_http_client_close(&client);
+ }
+}
+
+/* ========== 示例7:并发HTTP请求(6个连接) ========== */
+void example_concurrent_requests(void)
+{
+ printf("\n=== Example 7: Concurrent HTTP Requests ===\n");
+
+ n706a_http_client_t clients[6];
+ n706a_http_client_data_t data[6];
+ char response_bufs[6][2048];
+ const char *urls[] = {
+ "httpbin.org", "httpbin.org", "httpbin.org",
+ "httpbin.org", "httpbin.org", "httpbin.org"
+ };
+ const char *paths[] = {
+ "/get?req=1", "/get?req=2", "/get?req=3",
+ "/get?req=4", "/get?req=5", "/get?req=6"
+ };
+
+ /* 初始化6个客户端 */
+ for (int i = 0; i < 6; i++) {
+ n706a_http_client_init(&clients[i], i); // 使用socket 0-5
+
+ /* 连接 */
+ if (n706a_http_client_connect(&clients[i], urls[i], 80, false) == N706A_HTTP_SUCCESS) {
+ printf("Client %d connected\n", i);
+
+ /* 准备缓冲区 */
+ memset(&data[i], 0, sizeof(data[i]));
+ data[i].responseBuf = response_bufs[i];
+ data[i].responseBufLen = sizeof(response_bufs[i]);
+
+ /* 发送请求 */
+ n706a_http_client_get(&clients[i], paths[i], &data[i]);
+ printf("Client %d: Status %d\n", i, clients[i].response_code);
+ }
+ }
+
+ /* 关闭所有连接 */
+ for (int i = 0; i < 6; i++) {
+ n706a_http_client_close(&clients[i]);
+ }
+}
+
+/* ========== 示例8:简化的URL接口 ========== */
+void example_simple_url_api(void)
+{
+ printf("\n=== Example 8: Simple URL API ===\n");
+
+ n706a_http_client_t client;
+ n706a_http_client_data_t data;
+ char response_buf[4096];
+
+ n706a_http_client_init(&client, 0);
+
+ /* 准备响应缓冲区 */
+ memset(&data, 0, sizeof(data));
+ data.responseBuf = response_buf;
+ data.responseBufLen = sizeof(response_buf);
+
+ /* 直接使用完整URL(自动解析协议、host、端口、路径) */
+ n706a_httpc_result_t ret = n706a_http_client_get_url(&client,
+ "http://httpbin.org/get",
+ &data
+ );
+
+ if (ret == N706A_HTTP_SUCCESS) {
+ printf("Status: %d\n", client.response_code);
+ printf("Response: %s\n", response_buf);
+ }
+
+ n706a_http_client_close(&client);
+
+ /* HTTPS URL示例 */
+ ret = n706a_http_client_get_url(&client,
+ "https://api.github.com/",
+ &data
+ );
+
+ if (ret == N706A_HTTP_SUCCESS) {
+ printf("HTTPS Status: %d\n", client.response_code);
+ }
+
+ n706a_http_client_close(&client);
+}
+
+/* ========== 主函数 ========== */
+int main(void)
+{
+ printf("N706A HTTP Client Examples (TCP-based)\n");
+ printf("======================================\n");
+
+ /* 1. PPP拨号 */
+ ppp_dial_config_t config = {
+ .apn = "cmnet",
+ .username = "",
+ .password = "",
+ .auth_type = PPP_AUTH_NONE
+ };
+
+ printf("Starting PPP dial...\n");
+ if (ppp_client_dial(&config) != PPP_SUCCESS) {
+ printf("PPP dial failed\n");
+ return -1;
+ }
+ printf("PPP connected\n\n");
+
+ /* 2. 运行示例 */
+ example_http_get(); // 基本HTTP GET
+ example_https_get(); // HTTPS GET
+ example_http_post_json(); // HTTP POST JSON
+ example_custom_headers(); // 自定义请求头
+ example_basic_auth(); // 基本认证
+ example_keep_alive(); // Keep-Alive长连接
+ example_concurrent_requests(); // 并发请求(6个)
+ example_simple_url_api(); // 简化URL API
+
+ printf("\nAll examples completed\n");
+
+ /* 3. 断开PPP */
+ ppp_client_hangup();
+
+ return 0;
+}
+
--- /dev/null
+/**
+ * @file n706a_ssl_comparison_example.c
+ * @brief N706A SSL实现方式对比示例
+ *
+ * 展示两种SSL实现方式的使用:
+ * - AT指令实现(N706A_SSL_USE_AT_COMMAND)
+ * - mbedtls实现(N706A_SSL_USE_MBEDTLS)
+ *
+ * 编译方法:
+ * gcc -DN706A_SSL_USE_AT_COMMAND n706a_ssl_comparison_example.c # AT指令
+ * gcc -DN706A_SSL_USE_MBEDTLS n706a_ssl_comparison_example.c -lmbedtls # mbedtls
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "n706a_tcp_client.h"
+#include "ppp_client.h"
+
+/* ========== 示例1:基本SSL连接(两种实现API相同) ========== */
+void example_basic_ssl_connection(void)
+{
+ printf("\n=== Example 1: Basic SSL Connection ===\n");
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ printf("Using: AT Command SSL Implementation\n");
+#endif
+#ifdef N706A_SSL_USE_MBEDTLS
+ printf("Using: mbedtls SSL Implementation\n");
+#endif
+
+ n706a_tcp_socket_t sock;
+
+ /* 创建socket */
+ n706a_tcp_socket_create(&sock, 0);
+
+ /* 配置SSL(可选,使用默认也可以) */
+#ifdef N706A_SSL_USE_AT_COMMAND
+ /* AT指令模式:使用文件名 */
+ n706a_tcp_config_ssl(&sock,
+ 3, // TLS 1.2
+ 1, // 服务器认证
+ "ca.crt" // 证书文件名(已上传到模组)
+ );
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:使用PEM字符串 */
+ const char *ca_pem =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n"
+ // ... 证书内容 ...
+ "-----END CERTIFICATE-----\n";
+
+ n706a_tcp_config_ssl(&sock,
+ MBEDTLS_SSL_MINOR_VERSION_3, // TLS 1.2
+ MBEDTLS_SSL_VERIFY_REQUIRED, // 必须验证
+ ca_pem // PEM字符串
+ );
+#endif
+
+ /* 建立SSL连接(API完全相同) */
+ n706a_tcp_result_t ret = n706a_tcp_connect(&sock,
+ "www.google.com",
+ 443,
+ true, // 使用SSL
+ 30000 // 30秒超时
+ );
+
+ if (ret == N706A_TCP_SUCCESS) {
+ printf("SSL connected successfully!\n");
+
+ /* 发送HTTPS GET请求 */
+ const char *request =
+ "GET / HTTP/1.1\r\n"
+ "Host: www.google.com\r\n"
+ "Connection: close\r\n\r\n";
+
+ size_t sent;
+ n706a_tcp_send(&sock, (uint8_t*)request, strlen(request), &sent);
+ printf("Sent %zu bytes\n", sent);
+
+ /* 接收响应 */
+ uint8_t buffer[2048];
+ size_t received;
+ n706a_tcp_recv(&sock, buffer, sizeof(buffer), &received, 10000);
+ printf("Received %zu bytes:\n%.*s\n", received, (int)received, buffer);
+
+ /* 关闭连接 */
+ n706a_tcp_close(&sock);
+ } else {
+ printf("SSL connection failed: %d\n", ret);
+ }
+}
+
+/* ========== 示例2:性能测试 ========== */
+void example_performance_test(void)
+{
+ printf("\n=== Example 2: Performance Test ===\n");
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ printf("Testing AT Command SSL Performance\n");
+#endif
+#ifdef N706A_SSL_USE_MBEDTLS
+ printf("Testing mbedtls SSL Performance\n");
+#endif
+
+ n706a_tcp_socket_t sock;
+ n706a_tcp_socket_create(&sock, 0);
+
+ /* 测试SSL握手时间 */
+ uint32_t start_time = get_tick_ms(); // 假设有这个函数
+
+ n706a_tcp_result_t ret = n706a_tcp_connect(&sock,
+ "www.example.com",
+ 443,
+ true,
+ 30000
+ );
+
+ uint32_t handshake_time = get_tick_ms() - start_time;
+
+ if (ret == N706A_TCP_SUCCESS) {
+ printf("SSL handshake time: %u ms\n", handshake_time);
+
+ /* 测试数据传输速度 */
+ const char *test_data = "This is a test message for SSL performance!\n";
+ size_t data_len = strlen(test_data);
+
+ start_time = get_tick_ms();
+
+ /* 发送100次 */
+ for (int i = 0; i < 100; i++) {
+ size_t sent;
+ n706a_tcp_send(&sock, (uint8_t*)test_data, data_len, &sent);
+ }
+
+ uint32_t send_time = get_tick_ms() - start_time;
+ float throughput = (data_len * 100 * 1000.0) / send_time / 1024.0; // KB/s
+
+ printf("Send throughput: %.2f KB/s\n", throughput);
+
+ n706a_tcp_close(&sock);
+ }
+}
+
+/* ========== 示例3:内存占用测试 ========== */
+void example_memory_usage(void)
+{
+ printf("\n=== Example 3: Memory Usage ===\n");
+
+ printf("sizeof(n706a_tcp_socket_t) = %zu bytes\n", sizeof(n706a_tcp_socket_t));
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ printf("AT Command mode:\n");
+ printf(" - Socket structure: ~%zu bytes\n", sizeof(n706a_tcp_socket_t));
+ printf(" - Additional heap: ~2KB (estimated)\n");
+ printf(" - Total per connection: ~%zu bytes\n", sizeof(n706a_tcp_socket_t) + 2048);
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ printf("mbedtls mode:\n");
+ printf(" - Socket structure: ~%zu bytes\n", sizeof(n706a_tcp_socket_t));
+ printf(" - Includes mbedtls contexts\n");
+ printf(" - Additional buffers: ~20KB (estimated)\n");
+ printf(" - Total per connection: ~%zu bytes\n", sizeof(n706a_tcp_socket_t) + 20480);
+#endif
+
+ /* 测试6个并发SSL连接的内存 */
+ printf("\n6 concurrent SSL connections:\n");
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ size_t total_memory = (sizeof(n706a_tcp_socket_t) + 2048) * 6;
+ printf(" AT Command: ~%zu bytes (%.1f KB)\n",
+ total_memory, total_memory / 1024.0);
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ size_t total_memory = (sizeof(n706a_tcp_socket_t) + 20480) * 6;
+ printf(" mbedtls: ~%zu bytes (%.1f KB)\n",
+ total_memory, total_memory / 1024.0);
+#endif
+}
+
+/* ========== 示例4:AT指令特有功能 ========== */
+#ifdef N706A_SSL_USE_AT_COMMAND
+void example_at_specific_features(void)
+{
+ printf("\n=== Example 4: AT Command Specific Features ===\n");
+
+ n706a_tcp_socket_t sock;
+ n706a_tcp_socket_create(&sock, 0);
+
+ /* AT指令模式可以使用模组文件系统中的证书 */
+ printf("Using certificate from modem file system\n");
+
+ n706a_tcp_config_ssl(&sock,
+ 3, // TLS 1.2
+ 1, // 服务器认证
+ "ca.crt" // 文件名
+ );
+
+ /* 建立连接 */
+ n706a_tcp_result_t ret = n706a_tcp_connect(&sock,
+ "secure.example.com",
+ 443,
+ true,
+ 30000
+ );
+
+ if (ret == N706A_TCP_SUCCESS) {
+ printf("Connected using modem SSL hardware acceleration\n");
+ printf("Benefits:\n");
+ printf(" - Fast SSL processing (hardware accelerated)\n");
+ printf(" - Low CPU usage on host MCU\n");
+ printf(" - Small memory footprint\n");
+
+ n706a_tcp_close(&sock);
+ }
+}
+#endif
+
+/* ========== 示例5:mbedtls特有功能 ========== */
+#ifdef N706A_SSL_USE_MBEDTLS
+void example_mbedtls_specific_features(void)
+{
+ printf("\n=== Example 5: mbedtls Specific Features ===\n");
+
+ n706a_tcp_socket_t sock;
+ n706a_tcp_socket_create(&sock, 0);
+
+ /* mbedtls模式:PEM格式证书在内存中 */
+ const char *ca_pem = "-----BEGIN CERTIFICATE-----\n...\n";
+
+ n706a_tcp_config_ssl(&sock,
+ MBEDTLS_SSL_MINOR_VERSION_3,
+ MBEDTLS_SSL_VERIFY_REQUIRED,
+ ca_pem
+ );
+
+ /* mbedtls模式可以配置客户端证书(双向认证) */
+ const char *client_cert = "-----BEGIN CERTIFICATE-----\n...\n";
+ const char *client_key = "-----BEGIN RSA PRIVATE KEY-----\n...\n";
+
+ n706a_tcp_config_ssl_client_cert(&sock, client_cert, client_key);
+
+ /* 建立连接 */
+ n706a_tcp_result_t ret = n706a_tcp_connect(&sock,
+ "secure.example.com",
+ 443,
+ true,
+ 30000
+ );
+
+ if (ret == N706A_TCP_SUCCESS) {
+ printf("Connected using mbedtls\n");
+ printf("Benefits:\n");
+ printf(" - Full control over SSL parameters\n");
+ printf(" - Can customize cipher suites\n");
+ printf(" - Easy debugging (code on host)\n");
+ printf(" - Platform independent\n");
+
+ /* 可以直接访问SSL上下文进行高级操作 */
+ printf("\nSSL Info:\n");
+ printf(" Protocol: %s\n", mbedtls_ssl_get_version(&sock.ssl));
+ printf(" Cipher: %s\n", mbedtls_ssl_get_ciphersuite(&sock.ssl));
+
+ /* 自定义密码套件 */
+ const int ciphersuites[] = {
+ MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+ 0
+ };
+ mbedtls_ssl_conf_ciphersuites(&sock.conf, ciphersuites);
+ printf(" Custom ciphersuites configured\n");
+
+ n706a_tcp_close(&sock);
+ }
+}
+#endif
+
+/* ========== 示例6:并发SSL连接 ========== */
+void example_concurrent_ssl_connections(void)
+{
+ printf("\n=== Example 6: Concurrent SSL Connections ===\n");
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ printf("Testing 6 concurrent SSL connections (AT Command)\n");
+ int max_connections = 6; // AT指令模式可以支持6个
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ printf("Testing 2 concurrent SSL connections (mbedtls)\n");
+ int max_connections = 2; // mbedtls模式建议最多2个(内存限制)
+#endif
+
+ n706a_tcp_socket_t socks[6];
+ int connected = 0;
+
+ /* 尝试建立多个SSL连接 */
+ for (int i = 0; i < max_connections; i++) {
+ n706a_tcp_socket_create(&socks[i], i);
+
+ n706a_tcp_result_t ret = n706a_tcp_connect(&socks[i],
+ "www.example.com",
+ 443,
+ true,
+ 30000
+ );
+
+ if (ret == N706A_TCP_SUCCESS) {
+ connected++;
+ printf("Connection %d: SUCCESS\n", i);
+ } else {
+ printf("Connection %d: FAILED\n", i);
+ }
+ }
+
+ printf("\nTotal connected: %d/%d\n", connected, max_connections);
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ printf("AT Command mode: Can handle 6 concurrent SSL connections easily\n");
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ printf("mbedtls mode: Limited by memory, recommend 1-2 connections\n");
+#endif
+
+ /* 关闭所有连接 */
+ for (int i = 0; i < connected; i++) {
+ n706a_tcp_close(&socks[i]);
+ }
+}
+
+/* ========== 主函数 ========== */
+int main(void)
+{
+ printf("==============================================\n");
+ printf("N706A SSL Implementation Comparison\n");
+ printf("==============================================\n");
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ printf("Current build: AT Command SSL Implementation\n");
+ printf(" Recommended for: Standard applications\n");
+ printf(" Pros: Fast, low memory, reliable\n");
+ printf(" Cons: Limited customization\n");
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ printf("Current build: mbedtls SSL Implementation\n");
+ printf(" Recommended for: Custom requirements\n");
+ printf(" Pros: Full control, flexible\n");
+ printf(" Cons: Slower, more memory\n");
+#endif
+
+ printf("==============================================\n\n");
+
+ /* PPP拨号 */
+ ppp_dial_config_t config = {
+ .apn = "cmnet",
+ .username = "",
+ .password = "",
+ .auth_type = PPP_AUTH_NONE
+ };
+
+ if (ppp_client_dial(&config) != PPP_SUCCESS) {
+ printf("PPP dial failed\n");
+ return -1;
+ }
+ printf("PPP connected\n\n");
+
+ /* 运行示例 */
+ example_basic_ssl_connection();
+ example_performance_test();
+ example_memory_usage();
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ example_at_specific_features();
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ example_mbedtls_specific_features();
+#endif
+
+ example_concurrent_ssl_connections();
+
+ printf("\n==============================================\n");
+ printf("All examples completed\n");
+ printf("==============================================\n");
+
+ /* 断开PPP */
+ ppp_client_hangup();
+
+ return 0;
+}
+
--- /dev/null
+/**
+ * @file n706a_websocket_example.c
+ * @brief N706A WebSocket客户端使用示例
+ *
+ * 展示如何使用WebSocket客户端:
+ * - ws://普通连接
+ * - wss://安全连接
+ * - 发送文本和二进制消息
+ * - 接收消息回调
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "n706a_websocket.h"
+#include "ppp_client.h"
+
+/* ========== 回调函数 ========== */
+
+void on_open(int client_id)
+{
+ printf("[Client %d] WebSocket connected!\n", client_id);
+}
+
+void on_message(int client_id, const char *data, size_t length,
+ bool is_finished, bool is_binary)
+{
+ printf("[Client %d] Message received (%zu bytes, %s, %s):\n",
+ client_id, length,
+ is_binary ? "binary" : "text",
+ is_finished ? "finished" : "partial");
+
+ if (!is_binary && length < 1024) {
+ printf(" Content: %.*s\n", (int)length, data);
+ } else {
+ printf(" Content: [binary data]\n");
+ }
+}
+
+void on_error(int client_id, int error_code, const char *error_msg)
+{
+ printf("[Client %d] Error %d: %s\n", client_id, error_code, error_msg);
+}
+
+void on_close(int client_id, int close_code, const char *reason)
+{
+ printf("[Client %d] Closed (code=%d, reason=%s)\n",
+ client_id, close_code, reason ? reason : "none");
+}
+
+/* ========== 示例1:基本WebSocket连接 ========== */
+void example_basic_websocket(void)
+{
+ printf("\n=== Example 1: Basic WebSocket (ws://) ===\n");
+
+ /* 创建客户端 */
+ int client = n706a_websocket_constructor(
+ on_open,
+ on_message,
+ on_error,
+ on_close
+ );
+
+ if (client < 0) {
+ printf("Failed to create WebSocket client\n");
+ return;
+ }
+
+ printf("Client ID: %d\n", client);
+
+ /* 连接到WebSocket服务器 */
+ int ret = n706a_websocket_connect(client,
+ "ws://echo.websocket.org/",
+ NULL
+ );
+
+ if (ret == N706A_WS_SUCCESS) {
+ printf("Connected to echo server\n");
+
+ /* 发送文本消息 */
+ n706a_websocket_send_text(client, "Hello WebSocket!", 16);
+ printf("Sent text message\n");
+
+ /* 等待回显 */
+ sleep(2);
+
+ /* 发送更多消息 */
+ n706a_websocket_send_text(client, "This is N706A speaking", 22);
+ sleep(2);
+
+ /* 关闭连接 */
+ n706a_websocket_close(client);
+ sleep(1);
+ } else {
+ printf("Connection failed: %d\n", ret);
+ }
+
+ /* 销毁客户端 */
+ n706a_websocket_destructor(client);
+}
+
+/* ========== 示例2:安全WebSocket连接(WSS) ========== */
+void example_secure_websocket(void)
+{
+ printf("\n=== Example 2: Secure WebSocket (wss://) ===\n");
+
+ int client = n706a_websocket_constructor(
+ on_open, on_message, on_error, on_close
+ );
+
+ if (client < 0) {
+ printf("Failed to create client\n");
+ return;
+ }
+
+ /* 连接到WSS服务器 */
+ int ret = n706a_websocket_connect(client,
+ "wss://echo.websocket.org/",
+ NULL
+ );
+
+ if (ret == N706A_WS_SUCCESS) {
+ printf("Secure connection established (SSL/TLS)\n");
+
+ /* 发送消息 */
+ n706a_websocket_send_text(client, "Secure Hello!", 13);
+
+ sleep(3);
+
+ n706a_websocket_close(client);
+ sleep(1);
+ }
+
+ n706a_websocket_destructor(client);
+}
+
+/* ========== 示例3:发送二进制数据 ========== */
+void example_binary_data(void)
+{
+ printf("\n=== Example 3: Binary Data ===\n");
+
+ int client = n706a_websocket_constructor(
+ on_open, on_message, on_error, on_close
+ );
+
+ if (client < 0) return;
+
+ if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL)
+ == N706A_WS_SUCCESS) {
+
+ /* 创建二进制数据 */
+ uint8_t binary_data[] = {0x01, 0x02, 0x03, 0x04, 0x05,
+ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
+
+ /* 发送二进制消息 */
+ n706a_websocket_send_binary(client, binary_data, sizeof(binary_data));
+ printf("Sent binary data (%zu bytes)\n", sizeof(binary_data));
+
+ sleep(2);
+
+ n706a_websocket_close(client);
+ sleep(1);
+ }
+
+ n706a_websocket_destructor(client);
+}
+
+/* ========== 示例4:Ping/Pong ========== */
+void example_ping_pong(void)
+{
+ printf("\n=== Example 4: Ping/Pong ===\n");
+
+ int client = n706a_websocket_constructor(
+ on_open, on_message, on_error, on_close
+ );
+
+ if (client < 0) return;
+
+ if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL)
+ == N706A_WS_SUCCESS) {
+
+ /* 发送Ping */
+ n706a_websocket_send_ping(client, "ping", 4);
+ printf("Sent Ping\n");
+
+ sleep(1);
+
+ /* 服务器应该回复Pong */
+ /* 发送普通消息 */
+ n706a_websocket_send_text(client, "After ping", 10);
+
+ sleep(2);
+
+ n706a_websocket_close(client);
+ sleep(1);
+ }
+
+ n706a_websocket_destructor(client);
+}
+
+/* ========== 示例5:多个并发WebSocket连接 ========== */
+void example_multiple_connections(void)
+{
+ printf("\n=== Example 5: Multiple WebSocket Connections ===\n");
+
+ int clients[3];
+ const char *urls[] = {
+ "ws://echo.websocket.org/",
+ "ws://echo.websocket.org/",
+ "wss://echo.websocket.org/"
+ };
+
+ /* 创建3个客户端 */
+ for (int i = 0; i < 3; i++) {
+ clients[i] = n706a_websocket_constructor(
+ on_open, on_message, on_error, on_close
+ );
+
+ if (clients[i] < 0) {
+ printf("Failed to create client %d\n", i);
+ continue;
+ }
+
+ /* 连接 */
+ int ret = n706a_websocket_connect(clients[i], urls[i], NULL);
+ if (ret == N706A_WS_SUCCESS) {
+ printf("Client %d connected to %s\n", clients[i], urls[i]);
+ } else {
+ printf("Client %d connection failed\n", clients[i]);
+ }
+ }
+
+ sleep(1);
+
+ /* 所有客户端发送消息 */
+ for (int i = 0; i < 3; i++) {
+ if (n706a_websocket_is_connected(clients[i])) {
+ char msg[64];
+ snprintf(msg, sizeof(msg), "Message from client %d", clients[i]);
+ n706a_websocket_send_text(clients[i], msg, strlen(msg));
+ }
+ }
+
+ sleep(3);
+
+ /* 关闭所有连接 */
+ for (int i = 0; i < 3; i++) {
+ n706a_websocket_close(clients[i]);
+ n706a_websocket_destructor(clients[i]);
+ }
+
+ sleep(1);
+}
+
+/* ========== 示例6:优雅关闭 ========== */
+void example_graceful_close(void)
+{
+ printf("\n=== Example 6: Graceful Close ===\n");
+
+ int client = n706a_websocket_constructor(
+ on_open, on_message, on_error, on_close
+ );
+
+ if (client < 0) return;
+
+ if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL)
+ == N706A_WS_SUCCESS) {
+
+ /* 发送消息 */
+ n706a_websocket_send_text(client, "Goodbye!", 8);
+
+ sleep(1);
+
+ /* 优雅关闭(带原因) */
+ n706a_websocket_close_with_reason(client,
+ N706A_WS_CLOSE_NORMAL,
+ "Client closing normally"
+ );
+
+ printf("Sent close frame\n");
+
+ sleep(2);
+ }
+
+ n706a_websocket_destructor(client);
+}
+
+/* ========== 示例7:实时数据流 ========== */
+void example_realtime_stream(void)
+{
+ printf("\n=== Example 7: Realtime Data Stream ===\n");
+
+ int client = n706a_websocket_constructor(
+ on_open, on_message, on_error, on_close
+ );
+
+ if (client < 0) return;
+
+ /* 连接到实时数据服务器(这里用echo演示) */
+ if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL)
+ == N706A_WS_SUCCESS) {
+
+ printf("Streaming data for 10 seconds...\n");
+
+ /* 持续发送数据 */
+ for (int i = 0; i < 10; i++) {
+ char data[128];
+ snprintf(data, sizeof(data),
+ "{\"seq\":%d,\"timestamp\":%ld,\"value\":%d}",
+ i, time(NULL), rand() % 100);
+
+ n706a_websocket_send_text(client, data, strlen(data));
+ printf("Sent: %s\n", data);
+
+ sleep(1);
+ }
+
+ n706a_websocket_close(client);
+ sleep(1);
+ }
+
+ n706a_websocket_destructor(client);
+}
+
+/* ========== 示例8:指定Socket ID ========== */
+void example_with_specific_socket(void)
+{
+ printf("\n=== Example 8: Specify Socket ID ===\n");
+
+ /* 使用socket 3 */
+ int client = n706a_websocket_constructor_with_socket(3,
+ on_open, on_message, on_error, on_close
+ );
+
+ if (client < 0) {
+ printf("Failed to create client with socket 3\n");
+ return;
+ }
+
+ printf("Created client using socket ID 3\n");
+
+ if (n706a_websocket_connect(client, "ws://echo.websocket.org/", NULL)
+ == N706A_WS_SUCCESS) {
+
+ n706a_websocket_send_text(client, "Using socket 3", 14);
+
+ sleep(2);
+
+ n706a_websocket_close(client);
+ sleep(1);
+ }
+
+ n706a_websocket_destructor(client);
+}
+
+/* ========== 主函数 ========== */
+int main(void)
+{
+ printf("==============================================\n");
+ printf("N706A WebSocket Client Examples\n");
+ printf("==============================================\n");
+
+ /* PPP拨号 */
+ ppp_dial_config_t config = {
+ .apn = "cmnet",
+ .username = "",
+ .password = "",
+ .auth_type = PPP_AUTH_NONE
+ };
+
+ printf("Starting PPP dial...\n");
+ if (ppp_client_dial(&config) != PPP_SUCCESS) {
+ printf("PPP dial failed\n");
+ return -1;
+ }
+ printf("PPP connected\n\n");
+
+ /* 运行示例 */
+ example_basic_websocket(); // WS基本连接
+ example_secure_websocket(); // WSS安全连接
+ example_binary_data(); // 二进制数据
+ example_ping_pong(); // Ping/Pong
+ example_multiple_connections(); // 多连接
+ example_graceful_close(); // 优雅关闭
+ example_realtime_stream(); // 实时数据流
+ example_with_specific_socket(); // 指定Socket ID
+
+ printf("\n==============================================\n");
+ printf("All examples completed\n");
+ printf("==============================================\n");
+
+ /* 断开PPP */
+ ppp_client_hangup();
+
+ return 0;
+}
+
return ERRCODE_SUCC;
}
+/**
+ * @brief AT^PPPPAUSE - 暂停PPP模式以执行AT命令
+ *
+ * 功能:安全地暂停PPP数据传输,切换到AT模式
+ * - 检查是否可以安全切换(无数据传输)
+ * - 发送 "+++" 切换到命令模式
+ * - 停止中断/线程
+ * - 初始化AT handler
+ * - 状态保存在内部静态变量中
+ *
+ * 用途:需要在PPP连接期间执行AT命令时调用
+ * 注意:必须与 AT^PPPRESUME 配对使用
+ */
+static errcode_t at_ppp_pause(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(para);
+ UNUSED(paraLen);
+ UNUSED(strCommonCmd);
+
+ ppp_print_info("=== AT^PPPPAUSE: Pausing PPP mode ===\n");
+
+ // 检查当前是否在PPP模式
+ if (ppp_serial_get_mode() != PPP_SERIAL_MODE_PPP_DATA) {
+ ppp_print_info("AT^PPPPAUSE: Not in PPP data mode, cannot pause\n");
+ return ERRCODE_FAIL;
+ }
+
+ // 调用pause函数(内部会检查安全性并保存状态)
+ errcode_t ret = ppp_api_pause_for_at_command();
+ if (ret != ERRCODE_SUCC) {
+ ppp_print_err("AT^PPPPAUSE: Failed to pause PPP mode (error: %d)\n", ret);
+ return ERRCODE_FAIL;
+ }
+
+ ppp_print_info("AT^PPPPAUSE: PPP mode paused successfully\n");
+ ppp_print_info(" - PPP connection maintained\n");
+ ppp_print_info(" - Switched to AT command mode\n");
+ ppp_print_info(" - State saved for resume\n");
+ ppp_print_info(" - Use AT^PPPRESUME to restore PPP data mode\n");
+
+ return ERRCODE_SUCC;
+}
+
+/**
+ * @brief AT^PPPRESUME - 恢复PPP数据模式
+ *
+ * 功能:从AT命令模式恢复PPP数据传输
+ * - 使用内部保存的状态决定恢复行为
+ * - 发送 "ATO" 命令恢复在线
+ * - 去初始化AT handler
+ * - 重新启动中断/线程
+ * - 切换回PPP数据模式
+ *
+ * 用途:与 AT^PPPPAUSE 配对使用
+ */
+static errcode_t at_ppp_resume(uint8_t *para, uint32_t paraLen, char *strCommonCmd)
+{
+ UNUSED(para);
+ UNUSED(paraLen);
+ UNUSED(strCommonCmd);
+
+ ppp_print_info("=== AT^PPPRESUME: Resuming PPP mode ===\n");
+
+ // 检查当前是否在AT模式
+ if (ppp_serial_get_mode() != PPP_SERIAL_MODE_AT) {
+ ppp_print_info("AT^PPPRESUME: Not in AT command mode, cannot resume\n");
+ return ERRCODE_FAIL;
+ }
+
+ // 调用resume函数(使用内部静态变量)
+ errcode_t ret = ppp_api_resume_after_at_command();
+ if (ret != ERRCODE_SUCC) {
+ ppp_print_err("AT^PPPRESUME: Failed to resume PPP mode (error: %d)\n", ret);
+ return ERRCODE_FAIL;
+ }
+
+ ppp_print_info("AT^PPPRESUME: PPP mode resumed successfully\n");
+ ppp_print_info(" - Switched back to PPP data mode\n");
+ ppp_print_info(" - Data transmission restored\n");
+
+ return ERRCODE_SUCC;
+}
+
/**
* @brief AT^PPPSUSPEND - 挂起PPP和CAT1协议栈
*
{ (char *)"AT^CAT1AT", at_cat1_atcmd, (char *)"1" },
{ (char *)"AT^HTTPDOWNLOAD", custom_start_ota_download, (char *)" " },
{ (char *)"AT^CAT1LOGLEVEL", custom_enable_cat1_debug_log, (char *)" " },
+ /* PPP模式切换指令 */
+ { (char *)"AT^PPPPAUSE", at_ppp_pause, (char *)" " },
+ { (char *)"AT^PPPRESUME", at_ppp_resume, (char *)" " },
/* PPP休眠/唤醒控制指令 (仅在PPP模式下可用) */
{ (char *)"AT^CAT1SUSPEND", at_ppp_suspend_protocol, (char *)" " },
{ (char *)"AT^cAT1RESUME", at_ppp_resume_protocol, (char *)" " },
--- /dev/null
+/**
+ * @file n706a_http_client.h
+ * @brief N706A HTTP/HTTPS客户端接口(基于TCP实现)
+ *
+ * 本实现采用统一的TCP底层架构:
+ * - HTTP通过普通TCP连接实现
+ * - HTTPS通过SSL TCP连接实现
+ * - 手动构造HTTP协议,突破AT+HTTP*单连接限制
+ * - 支持6个并发HTTP/HTTPS连接
+ *
+ * @version 3.0
+ * @date 2025-10-30
+ */
+
+#ifndef N706A_HTTP_CLIENT_H
+#define N706A_HTTP_CLIENT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "n706a_tcp_client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief N706A HTTP请求类型
+ */
+typedef enum {
+ N706A_HTTP_DELETE,
+ N706A_HTTP_GET,
+ N706A_HTTP_HEAD,
+ N706A_HTTP_POST,
+ N706A_HTTP_PUT
+} n706a_http_request_type_t;
+
+/**
+ * @brief N706A HTTP错误码
+ */
+typedef enum {
+ N706A_HTTP_EAGAIN = 1, /**< 还有更多数据需要获取 */
+ N706A_HTTP_SUCCESS = 0, /**< 操作成功 */
+ N706A_HTTP_ENOBUFS = -1, /**< 缓冲区错误 */
+ N706A_HTTP_EARG = -2, /**< 非法参数 */
+ N706A_HTTP_ENOTSUPP = -3, /**< 不支持 */
+ N706A_HTTP_EDNS = -4, /**< DNS失败 */
+ N706A_HTTP_ECONN = -5, /**< 连接失败 */
+ N706A_HTTP_ESEND = -6, /**< 发送数据包失败 */
+ N706A_HTTP_ECLSD = -7, /**< 连接已关闭 */
+ N706A_HTTP_ERECV = -8, /**< 接收数据包失败 */
+ N706A_HTTP_EPARSE = -9, /**< URL解析错误 */
+ N706A_HTTP_EPROTO = -10, /**< 协议错误 */
+ N706A_HTTP_EUNKOWN = -11, /**< 未知错误 */
+ N706A_HTTP_ETIMEOUT = -12, /**< 超时 */
+} n706a_httpc_result_t;
+
+/**
+ * @brief N706A HTTP客户端结构(基于TCP实现)
+ */
+typedef struct {
+ n706a_tcp_socket_t tcp_sock; /**< 底层TCP socket */
+ int socket_id; /**< TCP socket编号(0-5) */
+ bool is_https; /**< HTTPS标志(决定是否使用SSL) */
+ uint16_t remote_port; /**< 远程端口 */
+ int response_code; /**< HTTP响应码 */
+ char *header; /**< 自定义请求头 */
+ char *auth_user; /**< 基本认证用户名 */
+ char *auth_password; /**< 基本认证密码 */
+ char host[256]; /**< 主机名 */
+ bool connected; /**< 连接状态 */
+ bool keepalive; /**< 长连接模式 */
+ uint32_t timeout_ms; /**< 超时时间(毫秒) */
+} n706a_http_client_t;
+
+/**
+ * @brief N706A HTTP数据结构
+ */
+typedef struct {
+ bool isMore; /**< 是否还有更多数据需要获取 */
+ bool isChunked; /**< 响应数据是否分块编码 */
+ int method; /**< HTTP方法 */
+ int retrieveLen; /**< 需要获取的内容长度 */
+ int responseContentLen; /**< 响应内容总长度 */
+ int contentBlockLen; /**< 一个块的内容长度 */
+ int postBufLen; /**< POST数据长度 */
+ int responseBufLen; /**< 响应体缓冲区长度 */
+ int headerBufLen; /**< 响应头缓冲区长度 */
+ char *postContentType; /**< POST数据的内容类型 */
+ char *postBuf; /**< 要POST的用户数据 */
+ char *responseBuf; /**< 存储响应体数据的缓冲区 */
+ char *headerBuf; /**< 存储响应头数据的缓冲区 */
+ bool isRedirected; /**< 是否重定向 */
+ char *redirectUrl; /**< 重定向URL */
+} n706a_http_client_data_t;
+
+/**
+ * @brief 初始化N706A HTTP客户端
+ * @param client HTTP客户端结构指针
+ * @param socket_id TCP socket编号(0-5)
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_init(n706a_http_client_t *client, int socket_id);
+
+/**
+ * @brief 连接到HTTP/HTTPS服务器
+ * @param client HTTP客户端结构指针
+ * @param host 主机名或IP地址
+ * @param port 端口号(HTTP默认80,HTTPS默认443)
+ * @param use_ssl 是否使用SSL(HTTPS)
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_connect(n706a_http_client_t *client,
+ const char *host,
+ uint16_t port,
+ bool use_ssl);
+
+/**
+ * @brief 发送HTTP GET请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径(如"/api/data")
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_get(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 发送HTTP POST请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径
+ * @param content_type 内容类型(如"application/json")
+ * @param body 请求体数据
+ * @param body_len 请求体长度
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_post(n706a_http_client_t *client,
+ const char *path,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 发送HTTP PUT请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径
+ * @param content_type 内容类型
+ * @param body 请求体数据
+ * @param body_len 请求体长度
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_put(n706a_http_client_t *client,
+ const char *path,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 发送HTTP DELETE请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_delete(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 发送HTTP HEAD请求
+ * @param client HTTP客户端结构指针
+ * @param path 请求路径
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_head(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 设置自定义HTTP请求头
+ * @param client HTTP客户端结构指针
+ * @param header 自定义请求头(如"X-API-Key: abc123\r\n")
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_set_header(n706a_http_client_t *client,
+ const char *header);
+
+/**
+ * @brief 设置HTTP基本认证
+ * @param client HTTP客户端结构指针
+ * @param username 用户名
+ * @param password 密码
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_set_auth(n706a_http_client_t *client,
+ const char *username,
+ const char *password);
+
+/**
+ * @brief 设置Keep-Alive模式
+ * @param client HTTP客户端结构指针
+ * @param keepalive true启用长连接,false关闭
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_set_keepalive(n706a_http_client_t *client,
+ bool keepalive);
+
+/**
+ * @brief 关闭HTTP连接
+ * @param client HTTP客户端结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_close(n706a_http_client_t *client);
+
+/**
+ * @brief 检查HTTP连接状态
+ * @param client HTTP客户端结构指针
+ * @return true已连接,false未连接
+ */
+bool n706a_http_client_is_connected(n706a_http_client_t *client);
+
+/**
+ * @brief 获取最后的HTTP响应码
+ * @param client HTTP客户端结构指针
+ * @return HTTP响应码(如200, 404等)
+ */
+int n706a_http_client_get_response_code(n706a_http_client_t *client);
+
+/* ========== 辅助函数 ========== */
+
+/**
+ * @brief 解析完整URL并发起GET请求(便捷函数)
+ * @param client HTTP客户端结构指针
+ * @param url 完整URL(如"http://api.example.com/data"或"https://secure.com/")
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ * @note 此函数会自动解析URL、建立连接并发送GET请求
+ */
+n706a_httpc_result_t n706a_http_client_get_url(n706a_http_client_t *client,
+ const char *url,
+ n706a_http_client_data_t *client_data);
+
+/**
+ * @brief 解析完整URL并发起POST请求(便捷函数)
+ * @param client HTTP客户端结构指针
+ * @param url 完整URL
+ * @param content_type 内容类型
+ * @param body 请求体数据
+ * @param body_len 请求体长度
+ * @param client_data 响应数据结构指针
+ * @return N706A_HTTP_SUCCESS成功,其他失败
+ */
+n706a_httpc_result_t n706a_http_client_post_url(n706a_http_client_t *client,
+ const char *url,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // N706A_HTTP_CLIENT_H
+
--- /dev/null
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
+ *
+ * N706A TCP客户端接口
+ * 基于N706A AT指令实现TCP socket功能
+ *
+ * SSL实现方式:
+ * - N706A_SSL_USE_AT_COMMAND: 使用AT+SSLTCP*指令(默认,推荐)
+ * - N706A_SSL_USE_MBEDTLS: 使用mbedtls库(需要额外库支持)
+ */
+
+#ifndef N706A_TCP_CLIENT_H
+#define N706A_TCP_CLIENT_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* ========== SSL实现方式选择 ========== */
+/**
+ * 编译时选择SSL实现方式:
+ * gcc -DN706A_SSL_USE_AT_COMMAND ... (使用AT指令,默认)
+ * gcc -DN706A_SSL_USE_MBEDTLS ... (使用mbedtls)
+ */
+#if !defined(N706A_SSL_USE_AT_COMMAND) && !defined(N706A_SSL_USE_MBEDTLS)
+ /* 默认使用AT指令实现 */
+ #define N706A_SSL_USE_AT_COMMAND 1
+#endif
+
+#if defined(N706A_SSL_USE_AT_COMMAND) && defined(N706A_SSL_USE_MBEDTLS)
+ #error "Cannot define both N706A_SSL_USE_AT_COMMAND and N706A_SSL_USE_MBEDTLS"
+#endif
+
+/* 如果使用mbedtls,包含相关头文件 */
+#ifdef N706A_SSL_USE_MBEDTLS
+ #include "mbedtls/net_sockets.h"
+ #include "mbedtls/ssl.h"
+ #include "mbedtls/entropy.h"
+ #include "mbedtls/ctr_drbg.h"
+ #include "mbedtls/x509.h"
+ #include "mbedtls/error.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* N706A TCP错误码 */
+typedef enum {
+ N706A_TCP_SUCCESS = 0, /**< 操作成功 */
+ N706A_TCP_ERROR = -1, /**< 通用错误 */
+ N706A_TCP_EINVAL = -2, /**< 参数无效 */
+ N706A_TCP_ENOBUFS = -3, /**< 缓冲区不足 */
+ N706A_TCP_ENOTCONN = -4, /**< 未连接 */
+ N706A_TCP_ECONNREF = -5, /**< 连接被拒绝 */
+ N706A_TCP_ETIMEDOUT= -6, /**< 超时 */
+ N706A_TCP_EINPROGRESS = -7, /**< 操作进行中 */
+} n706a_tcp_result_t;
+
+/* TCP socket状态 */
+typedef enum {
+ N706A_TCP_STATE_CLOSED = 0, /**< 关闭 */
+ N706A_TCP_STATE_CONNECTING, /**< 连接中 */
+ N706A_TCP_STATE_CONNECTED, /**< 已连接 */
+ N706A_TCP_STATE_ERROR /**< 错误状态 */
+} n706a_tcp_state_t;
+
+/* TCP socket结构 */
+typedef struct {
+ int socket_id; /**< socket编号(0-5) */
+ n706a_tcp_state_t state; /**< 连接状态 */
+ char remote_ip[64]; /**< 远程IP或域名 */
+ uint16_t remote_port; /**< 远程端口 */
+ bool is_ssl; /**< 是否SSL连接 */
+ uint32_t timeout_ms; /**< 超时时间 */
+
+ /* SSL相关字段(根据编译宏不同而不同) */
+#ifdef N706A_SSL_USE_AT_COMMAND
+ /* AT指令模式:SSL由模组处理 */
+ int ssl_version; /**< SSL版本(0-5) */
+ int auth_mode; /**< 认证模式(0-2) */
+ char ca_cert_file[64]; /**< CA证书文件名 */
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:需要SSL上下文 */
+ mbedtls_ssl_context ssl; /**< SSL上下文 */
+ mbedtls_ssl_config conf; /**< SSL配置 */
+ mbedtls_entropy_context entropy; /**< 熵源 */
+ mbedtls_ctr_drbg_context ctr_drbg; /**< 随机数生成器 */
+ mbedtls_x509_crt cacert; /**< CA证书 */
+ mbedtls_x509_crt clicert; /**< 客户端证书(可选) */
+ mbedtls_pk_context pkey; /**< 客户端私钥(可选) */
+ bool ssl_initialized; /**< SSL是否已初始化 */
+#endif
+} n706a_tcp_socket_t;
+
+/**
+ * @brief 创建TCP socket
+ * @param sock TCP socket指针
+ * @param socket_id socket编号(0-5)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_socket_create(n706a_tcp_socket_t *sock, int socket_id);
+
+/**
+ * @brief 连接到TCP服务器
+ * @param sock TCP socket指针
+ * @param host 主机地址(IP或域名)
+ * @param port 端口号
+ * @param use_ssl 是否使用SSL连接(用于WSS)
+ * @param timeout_ms 超时时间(毫秒)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ * @note 使用SSL时会调用AT+SSLTCPSETUP,否则调用AT+TCPSETUP
+ */
+n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock, const char *host,
+ uint16_t port, bool use_ssl, uint32_t timeout_ms);
+
+/**
+ * @brief 配置SSL参数(在SSL连接前调用)
+ * @param sock TCP socket指针
+ * @param ssl_version SSL版本
+ * AT指令模式:0-5 (0=SSL3.0, 1=TLS1.0, 2=TLS1.1, 3=TLS1.2, 4=TLS1.0/1.1/1.2, 5=TLS1.3)
+ * mbedtls模式:使用MBEDTLS_SSL_MINOR_VERSION_*常量
+ * @param auth_mode 认证模式
+ * AT指令模式:0=不认证, 1=服务器认证, 2=双向认证
+ * mbedtls模式:MBEDTLS_SSL_VERIFY_NONE/OPTIONAL/REQUIRED
+ * @param ca_cert CA证书
+ * AT指令模式:证书文件名(如"ca.crt")
+ * mbedtls模式:证书内容(PEM格式字符串)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_config_ssl(n706a_tcp_socket_t *sock,
+ int ssl_version,
+ int auth_mode,
+ const char *ca_cert);
+
+#ifdef N706A_SSL_USE_MBEDTLS
+/**
+ * @brief 配置客户端证书和私钥(mbedtls模式,双向认证)
+ * @param sock TCP socket指针
+ * @param client_cert 客户端证书(PEM格式)
+ * @param client_key 客户端私钥(PEM格式)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_config_ssl_client_cert(n706a_tcp_socket_t *sock,
+ const char *client_cert,
+ const char *client_key);
+#endif
+
+/**
+ * @brief 发送TCP数据
+ * @param sock TCP socket指针
+ * @param data 要发送的数据
+ * @param len 数据长度
+ * @param sent 实际发送的字节数(输出)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_send(n706a_tcp_socket_t *sock, const uint8_t *data,
+ size_t len, size_t *sent);
+
+/**
+ * @brief 接收TCP数据
+ * @param sock TCP socket指针
+ * @param buffer 接收缓冲区
+ * @param len 缓冲区大小
+ * @param received 实际接收的字节数(输出)
+ * @param timeout_ms 超时时间(毫秒)
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock, uint8_t *buffer,
+ size_t len, size_t *received, uint32_t timeout_ms);
+
+/**
+ * @brief 关闭TCP连接
+ * @param sock TCP socket指针
+ * @return N706A_TCP_SUCCESS成功,其他值表示失败
+ */
+n706a_tcp_result_t n706a_tcp_close(n706a_tcp_socket_t *sock);
+
+/**
+ * @brief 检查socket是否已连接
+ * @param sock TCP socket指针
+ * @return true已连接,false未连接
+ */
+bool n706a_tcp_is_connected(const n706a_tcp_socket_t *sock);
+
+/**
+ * @brief 获取socket状态
+ * @param sock TCP socket指针
+ * @return socket状态
+ */
+n706a_tcp_state_t n706a_tcp_get_state(const n706a_tcp_socket_t *sock);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* N706A_TCP_CLIENT_H */
+
--- /dev/null
+/**
+ * @file n706a_websocket.h
+ * @brief N706A WebSocket客户端接口定义
+ *
+ * 基于TCP/SSL TCP实现完整的WebSocket协议(RFC 6455),支持:
+ * - ws://协议(普通TCP)
+ * - wss://协议(SSL TCP)
+ * - 文本和二进制消息
+ * - 自动握手和帧封装
+ * - 事件回调机制
+ *
+ * @version 1.0
+ * @date 2025-10-30
+ */
+
+#ifndef N706A_WEBSOCKET_H
+#define N706A_WEBSOCKET_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "n706a_tcp_client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* WebSocket最大客户端数量 */
+#define N706A_WS_MAX_CLIENTS 6
+
+/* WebSocket URL最大长度 */
+#define N706A_WS_MAX_URL_LEN 512
+
+/* WebSocket帧操作码 */
+typedef enum {
+ N706A_WS_OPCODE_CONTINUATION = 0x0, /**< 继续帧 */
+ N706A_WS_OPCODE_TEXT = 0x1, /**< 文本帧 */
+ N706A_WS_OPCODE_BINARY = 0x2, /**< 二进制帧 */
+ N706A_WS_OPCODE_CLOSE = 0x8, /**< 关闭帧 */
+ N706A_WS_OPCODE_PING = 0x9, /**< Ping帧 */
+ N706A_WS_OPCODE_PONG = 0xA /**< Pong帧 */
+} n706a_ws_opcode_t;
+
+/* WebSocket状态 */
+typedef enum {
+ N706A_WS_STATE_CLOSED = 0, /**< 已关闭 */
+ N706A_WS_STATE_CONNECTING, /**< 连接中 */
+ N706A_WS_STATE_OPEN, /**< 已打开 */
+ N706A_WS_STATE_CLOSING /**< 关闭中 */
+} n706a_ws_state_t;
+
+/* WebSocket关闭状态码 */
+typedef enum {
+ N706A_WS_CLOSE_NORMAL = 1000, /**< 正常关闭 */
+ N706A_WS_CLOSE_GOING_AWAY = 1001, /**< 端点离开 */
+ N706A_WS_CLOSE_PROTOCOL_ERROR = 1002, /**< 协议错误 */
+ N706A_WS_CLOSE_UNSUPPORTED = 1003, /**< 不支持的数据 */
+ N706A_WS_CLOSE_NO_STATUS = 1005, /**< 无状态码 */
+ N706A_WS_CLOSE_ABNORMAL = 1006, /**< 异常关闭 */
+ N706A_WS_CLOSE_INVALID_DATA = 1007, /**< 无效数据 */
+ N706A_WS_CLOSE_POLICY = 1008, /**< 策略违反 */
+ N706A_WS_CLOSE_TOO_LARGE = 1009, /**< 消息过大 */
+ N706A_WS_CLOSE_EXTENSION = 1010, /**< 扩展协商失败 */
+ N706A_WS_CLOSE_SERVER_ERROR = 1011 /**< 服务器错误 */
+} n706a_ws_close_code_t;
+
+/* WebSocket错误码 */
+typedef enum {
+ N706A_WS_SUCCESS = 0, /**< 成功 */
+ N706A_WS_ERROR_INVALID_PARAM = -1, /**< 参数无效 */
+ N706A_WS_ERROR_NO_MEMORY = -2, /**< 内存不足 */
+ N706A_WS_ERROR_NOT_CONNECTED = -3, /**< 未连接 */
+ N706A_WS_ERROR_ALREADY_OPEN = -4, /**< 已经打开 */
+ N706A_WS_ERROR_HANDSHAKE = -5, /**< 握手失败 */
+ N706A_WS_ERROR_SEND_FAIL = -6, /**< 发送失败 */
+ N706A_WS_ERROR_RECV_FAIL = -7, /**< 接收失败 */
+ N706A_WS_ERROR_PROTOCOL = -8, /**< 协议错误 */
+ N706A_WS_ERROR_TIMEOUT = -9, /**< 超时 */
+ N706A_WS_ERROR_CLOSED = -10 /**< 连接已关闭 */
+} n706a_ws_result_t;
+
+/* 前置声明 */
+struct n706a_websocket_client;
+
+/**
+ * @brief WebSocket连接打开回调
+ * @param client_id 客户端ID
+ */
+typedef void (*n706a_ws_on_open_callback_t)(int client_id);
+
+/**
+ * @brief WebSocket消息接收回调
+ * @param client_id 客户端ID
+ * @param data 消息数据
+ * @param length 数据长度
+ * @param is_finished 是否是最后一帧
+ * @param is_binary 是否是二进制数据
+ */
+typedef void (*n706a_ws_on_message_callback_t)(int client_id,
+ const char *data,
+ size_t length,
+ bool is_finished,
+ bool is_binary);
+
+/**
+ * @brief WebSocket错误回调
+ * @param client_id 客户端ID
+ * @param error_code 错误码
+ * @param error_msg 错误消息
+ */
+typedef void (*n706a_ws_on_error_callback_t)(int client_id,
+ int error_code,
+ const char *error_msg);
+
+/**
+ * @brief WebSocket连接关闭回调
+ * @param client_id 客户端ID
+ * @param close_code 关闭状态码
+ * @param reason 关闭原因
+ */
+typedef void (*n706a_ws_on_close_callback_t)(int client_id,
+ int close_code,
+ const char *reason);
+
+/**
+ * @brief N706A WebSocket客户端结构
+ */
+typedef struct n706a_websocket_client {
+ int client_id; /**< 客户端ID (0-5) */
+ n706a_tcp_socket_t tcp_sock; /**< 底层TCP socket */
+ n706a_ws_state_t state; /**< WebSocket状态 */
+ bool is_ssl; /**< 是否WSS连接 */
+
+ /* URL信息 */
+ char url[N706A_WS_MAX_URL_LEN]; /**< 完整URL */
+ char host[128]; /**< 主机名 */
+ uint16_t port; /**< 端口号 */
+ char path[256]; /**< 路径 */
+
+ /* WebSocket协议 */
+ char sec_key[32]; /**< Sec-WebSocket-Key */
+ char sec_accept[32]; /**< Sec-WebSocket-Accept */
+
+ /* 接收缓冲区 */
+ uint8_t *recv_buffer; /**< 接收缓冲区 */
+ size_t recv_buffer_size; /**< 缓冲区大小 */
+ size_t recv_buffer_len; /**< 已接收数据长度 */
+
+ /* 回调函数 */
+ n706a_ws_on_open_callback_t on_open;
+ n706a_ws_on_message_callback_t on_message;
+ n706a_ws_on_error_callback_t on_error;
+ n706a_ws_on_close_callback_t on_close;
+
+ /* 运行控制 */
+ bool running; /**< 接收线程运行标志 */
+ void *recv_thread; /**< 接收线程句柄 */
+
+} n706a_websocket_client_t;
+
+/**
+ * @brief 创建WebSocket客户端
+ * @param on_open 连接打开回调
+ * @param on_message 消息接收回调
+ * @param on_error 错误回调
+ * @param on_close 连接关闭回调
+ * @return 客户端ID (0-5),失败返回-1
+ */
+int n706a_websocket_constructor(n706a_ws_on_open_callback_t on_open,
+ n706a_ws_on_message_callback_t on_message,
+ n706a_ws_on_error_callback_t on_error,
+ n706a_ws_on_close_callback_t on_close);
+
+/**
+ * @brief 创建WebSocket客户端并指定socket ID
+ * @param socket_id TCP socket ID (0-5)
+ * @param on_open 连接打开回调
+ * @param on_message 消息接收回调
+ * @param on_error 错误回调
+ * @param on_close 连接关闭回调
+ * @return 客户端ID,失败返回-1
+ */
+int n706a_websocket_constructor_with_socket(int socket_id,
+ n706a_ws_on_open_callback_t on_open,
+ n706a_ws_on_message_callback_t on_message,
+ n706a_ws_on_error_callback_t on_error,
+ n706a_ws_on_close_callback_t on_close);
+
+/**
+ * @brief 连接到WebSocket服务器
+ * @param client_id 客户端ID
+ * @param url WebSocket URL (ws://或wss://)
+ * @param option 可选参数(如子协议等),可为NULL
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_connect(int client_id, const char *url, const char *option);
+
+/**
+ * @brief 发送文本消息
+ * @param client_id 客户端ID
+ * @param data 文本数据
+ * @param length 数据长度
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_send_text(int client_id, const char *data, size_t length);
+
+/**
+ * @brief 发送二进制消息
+ * @param client_id 客户端ID
+ * @param data 二进制数据
+ * @param length 数据长度
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_send_binary(int client_id, const uint8_t *data, size_t length);
+
+/**
+ * @brief 发送Ping帧
+ * @param client_id 客户端ID
+ * @param data Ping数据(可为NULL)
+ * @param length 数据长度
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_send_ping(int client_id, const char *data, size_t length);
+
+/**
+ * @brief 发送Pong帧
+ * @param client_id 客户端ID
+ * @param data Pong数据(可为NULL)
+ * @param length 数据长度
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_send_pong(int client_id, const char *data, size_t length);
+
+/**
+ * @brief 关闭WebSocket连接
+ * @param client_id 客户端ID
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_close(int client_id);
+
+/**
+ * @brief 关闭WebSocket连接(带状态码和原因)
+ * @param client_id 客户端ID
+ * @param close_code 关闭状态码
+ * @param reason 关闭原因(可为NULL)
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_close_with_reason(int client_id,
+ n706a_ws_close_code_t close_code,
+ const char *reason);
+
+/**
+ * @brief 获取WebSocket连接状态
+ * @param client_id 客户端ID
+ * @return WebSocket状态
+ */
+n706a_ws_state_t n706a_websocket_get_state(int client_id);
+
+/**
+ * @brief 检查WebSocket是否已连接
+ * @param client_id 客户端ID
+ * @return true已连接,false未连接
+ */
+bool n706a_websocket_is_connected(int client_id);
+
+/**
+ * @brief 销毁WebSocket客户端
+ * @param client_id 客户端ID
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_destructor(int client_id);
+
+/* ========== 内部辅助函数(高级用户可选使用) ========== */
+
+/**
+ * @brief 解析WebSocket URL
+ * @param url 完整URL
+ * @param protocol 协议(输出,"ws"或"wss")
+ * @param host 主机名(输出)
+ * @param port 端口号(输出)
+ * @param path 路径(输出)
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_parse_url(const char *url,
+ char *protocol,
+ char *host,
+ uint16_t *port,
+ char *path);
+
+/**
+ * @brief 生成WebSocket Sec-WebSocket-Key
+ * @param key 输出缓冲区(至少25字节)
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_generate_key(char *key);
+
+/**
+ * @brief 计算WebSocket Sec-WebSocket-Accept
+ * @param key Sec-WebSocket-Key
+ * @param accept 输出缓冲区(至少29字节)
+ * @return N706A_WS_SUCCESS成功,其他失败
+ */
+int n706a_websocket_calculate_accept(const char *key, char *accept);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* N706A_WEBSOCKET_H */
+
*/
errcode_t ppp_ppp_mode_resume(void);
+/**
+ * @brief 为执行AT命令暂停PPP模式(高级接口)
+ * @return 错误码
+ *
+ * 功能说明:
+ * - 检查当前模式,如果在PPP模式则自动暂停
+ * - 检查是否安全切换(无数据传输)
+ * - 暂停PPP模式并初始化AT handler
+ * - 状态保存在内部静态变量中
+ *
+ * 注意:必须与 ppp_api_resume_after_at_command() 配对使用
+ */
+errcode_t ppp_api_pause_for_at_command(void);
+
+/**
+ * @brief AT命令执行后恢复PPP模式(高级接口)
+ * @return 错误码
+ *
+ * 功能说明:
+ * - 使用内部静态变量恢复状态
+ * - 去初始化AT handler
+ * - 恢复PPP模式
+ * - 如果恢复失败,强制重建PPP连接
+ *
+ * 注意:必须与 ppp_api_pause_for_at_command() 配对使用
+ */
+errcode_t ppp_api_resume_after_at_command(void);
+
+/**
+ * @brief 查询PPP是否处于暂停状态
+ * @return true: PPP已暂停,数据输出被阻止; false: PPP正常运行
+ *
+ * 用途:
+ * - 在PPP输出回调中检查是否允许数据输出
+ * - 在pause状态下阻止PPP协议栈向串口发送数据,保证AT命令独占串口
+ */
+bool ppp_api_is_paused(void);
+
/**
* @brief 设置PPP波特率
* @param baudrate 波特率
#include <stdint.h>
#include <stdbool.h>
+#include <stddef.h>
#include "ppp_config.h"
#include "ppp_types.h"
ppp_error_t ppp_at_send_command(const char *command, const char *expected_response,
uint32_t timeout, ppp_at_response_t *response);
+/**
+ * @brief 发送原始数据(不带AT命令格式)
+ * @param data 要发送的数据
+ * @param len 数据长度
+ * @param timeout 超时时间(ms)
+ * @return 错误码
+ * @note 用于 TCP/SSL 数据发送,在 AT+TCPSEND/AT+SSLTCPSEND 命令后使用
+ */
+ppp_error_t ppp_at_send_raw_data(const void *data, size_t len, uint32_t timeout);
+
/**
* @brief 测试AT连接
* @return 错误码
--- /dev/null
+/**
+ * @file n706a_http_client.c
+ * @brief N706A HTTP/HTTPS客户端实现(基于TCP)
+ *
+ * 基于TCP手动构造HTTP协议,支持:
+ * - HTTP (普通TCP)
+ * - HTTPS (SSL TCP)
+ * - 6个并发连接
+ * - 所有标准HTTP方法
+ */
+
+#include "n706a_http_client.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Base64编码表(用于Basic Auth) */
+static const char base64_table[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+/* ========== 私有函数声明 ========== */
+
+static int http_build_request(n706a_http_client_t *client,
+ const char *method,
+ const char *path,
+ const char *body,
+ size_t body_len,
+ const char *content_type,
+ char *request_buf,
+ size_t buf_size);
+
+static n706a_httpc_result_t http_parse_response(const char *response,
+ size_t response_len,
+ n706a_http_client_t *client,
+ n706a_http_client_data_t *data);
+
+static void http_base64_encode(const char *input, size_t len, char *output);
+
+static int http_parse_url(const char *url,
+ char *protocol,
+ char *host,
+ uint16_t *port,
+ char *path);
+
+/* ========== 公共函数实现 ========== */
+
+/**
+ * @brief 初始化HTTP客户端
+ */
+n706a_httpc_result_t n706a_http_client_init(n706a_http_client_t *client, int socket_id)
+{
+ if (!client || socket_id < 0 || socket_id > 5) {
+ return N706A_HTTP_EARG;
+ }
+
+ memset(client, 0, sizeof(n706a_http_client_t));
+
+ client->socket_id = socket_id;
+ client->is_https = false;
+ client->remote_port = 0;
+ client->response_code = 0;
+ client->connected = false;
+ client->keepalive = false;
+ client->timeout_ms = 10000; // 默认10秒
+
+ /* 初始化TCP socket */
+ n706a_tcp_socket_create(&client->tcp_sock, socket_id);
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 连接到HTTP/HTTPS服务器
+ */
+n706a_httpc_result_t n706a_http_client_connect(n706a_http_client_t *client,
+ const char *host,
+ uint16_t port,
+ bool use_ssl)
+{
+ if (!client || !host) {
+ return N706A_HTTP_EARG;
+ }
+
+ /* 保存连接信息 */
+ strncpy(client->host, host, sizeof(client->host) - 1);
+ client->remote_port = port;
+ client->is_https = use_ssl;
+
+ /* 建立TCP连接 */
+ n706a_tcp_result_t ret = n706a_tcp_connect(&client->tcp_sock,
+ host,
+ port,
+ use_ssl,
+ 30000); // SSL握手需要更长时间
+
+ if (ret == N706A_TCP_SUCCESS) {
+ client->connected = true;
+ return N706A_HTTP_SUCCESS;
+ } else if (ret == N706A_TCP_ETIMEDOUT) {
+ return N706A_HTTP_ETIMEOUT;
+ } else {
+ return N706A_HTTP_ECONN;
+ }
+}
+
+/**
+ * @brief 发送HTTP GET请求
+ */
+n706a_httpc_result_t n706a_http_client_get(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ /* 构造GET请求 */
+ char request_buf[2048];
+ int request_len = http_build_request(client, "GET", path,
+ NULL, 0, NULL,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ /* 发送请求 */
+ size_t sent;
+ n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock,
+ (uint8_t*)request_buf,
+ request_len,
+ &sent);
+
+ if (ret != N706A_TCP_SUCCESS || sent != (size_t)request_len) {
+ return N706A_HTTP_ESEND;
+ }
+
+ /* 接收响应 */
+ uint8_t response_buf[8192];
+ size_t received;
+
+ ret = n706a_tcp_recv(&client->tcp_sock,
+ response_buf,
+ sizeof(response_buf) - 1,
+ &received,
+ client->timeout_ms);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ return N706A_HTTP_ERECV;
+ }
+
+ response_buf[received] = '\0';
+
+ /* 解析响应 */
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 发送HTTP POST请求
+ */
+n706a_httpc_result_t n706a_http_client_post(n706a_http_client_t *client,
+ const char *path,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ /* 构造POST请求 */
+ char request_buf[4096];
+ int request_len = http_build_request(client, "POST", path,
+ body, body_len, content_type,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ /* 发送请求 */
+ size_t sent;
+ n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock,
+ (uint8_t*)request_buf,
+ request_len,
+ &sent);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ return N706A_HTTP_ESEND;
+ }
+
+ /* 接收响应 */
+ uint8_t response_buf[8192];
+ size_t received;
+
+ ret = n706a_tcp_recv(&client->tcp_sock,
+ response_buf,
+ sizeof(response_buf) - 1,
+ &received,
+ client->timeout_ms);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ return N706A_HTTP_ERECV;
+ }
+
+ response_buf[received] = '\0';
+
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 发送HTTP PUT请求
+ */
+n706a_httpc_result_t n706a_http_client_put(n706a_http_client_t *client,
+ const char *path,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ char request_buf[4096];
+ int request_len = http_build_request(client, "PUT", path,
+ body, body_len, content_type,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ size_t sent;
+ n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent);
+
+ uint8_t response_buf[8192];
+ size_t received;
+ n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1,
+ &received, client->timeout_ms);
+
+ response_buf[received] = '\0';
+
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 发送HTTP DELETE请求
+ */
+n706a_httpc_result_t n706a_http_client_delete(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ char request_buf[2048];
+ int request_len = http_build_request(client, "DELETE", path,
+ NULL, 0, NULL,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ size_t sent;
+ n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent);
+
+ uint8_t response_buf[8192];
+ size_t received;
+ n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1,
+ &received, client->timeout_ms);
+
+ response_buf[received] = '\0';
+
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 发送HTTP HEAD请求
+ */
+n706a_httpc_result_t n706a_http_client_head(n706a_http_client_t *client,
+ const char *path,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !path || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (!client->connected) {
+ return N706A_HTTP_ECONN;
+ }
+
+ char request_buf[2048];
+ int request_len = http_build_request(client, "HEAD", path,
+ NULL, 0, NULL,
+ request_buf, sizeof(request_buf));
+
+ if (request_len < 0) {
+ return N706A_HTTP_ENOBUFS;
+ }
+
+ size_t sent;
+ n706a_tcp_send(&client->tcp_sock, (uint8_t*)request_buf, request_len, &sent);
+
+ uint8_t response_buf[8192];
+ size_t received;
+ n706a_tcp_recv(&client->tcp_sock, response_buf, sizeof(response_buf) - 1,
+ &received, client->timeout_ms);
+
+ response_buf[received] = '\0';
+
+ return http_parse_response((char*)response_buf, received, client, client_data);
+}
+
+/**
+ * @brief 设置自定义HTTP请求头
+ */
+n706a_httpc_result_t n706a_http_client_set_header(n706a_http_client_t *client,
+ const char *header)
+{
+ if (!client) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (client->header) {
+ free(client->header);
+ }
+
+ if (header) {
+ client->header = strdup(header);
+ } else {
+ client->header = NULL;
+ }
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 设置HTTP基本认证
+ */
+n706a_httpc_result_t n706a_http_client_set_auth(n706a_http_client_t *client,
+ const char *username,
+ const char *password)
+{
+ if (!client) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (client->auth_user) {
+ free(client->auth_user);
+ }
+ if (client->auth_password) {
+ free(client->auth_password);
+ }
+
+ if (username) {
+ client->auth_user = strdup(username);
+ } else {
+ client->auth_user = NULL;
+ }
+
+ if (password) {
+ client->auth_password = strdup(password);
+ } else {
+ client->auth_password = NULL;
+ }
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 设置Keep-Alive模式
+ */
+n706a_httpc_result_t n706a_http_client_set_keepalive(n706a_http_client_t *client,
+ bool keepalive)
+{
+ if (!client) {
+ return N706A_HTTP_EARG;
+ }
+
+ client->keepalive = keepalive;
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 关闭HTTP连接
+ */
+n706a_httpc_result_t n706a_http_client_close(n706a_http_client_t *client)
+{
+ if (!client) {
+ return N706A_HTTP_EARG;
+ }
+
+ if (client->connected) {
+ n706a_tcp_close(&client->tcp_sock);
+ client->connected = false;
+ }
+
+ if (client->header) {
+ free(client->header);
+ client->header = NULL;
+ }
+
+ if (client->auth_user) {
+ free(client->auth_user);
+ client->auth_user = NULL;
+ }
+
+ if (client->auth_password) {
+ free(client->auth_password);
+ client->auth_password = NULL;
+ }
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief 检查HTTP连接状态
+ */
+bool n706a_http_client_is_connected(n706a_http_client_t *client)
+{
+ if (!client) {
+ return false;
+ }
+
+ return client->connected && n706a_tcp_is_connected(&client->tcp_sock);
+}
+
+/**
+ * @brief 获取最后的HTTP响应码
+ */
+int n706a_http_client_get_response_code(n706a_http_client_t *client)
+{
+ if (!client) {
+ return 0;
+ }
+
+ return client->response_code;
+}
+
+/**
+ * @brief 解析完整URL并发起GET请求
+ */
+n706a_httpc_result_t n706a_http_client_get_url(n706a_http_client_t *client,
+ const char *url,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !url || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ char protocol[8];
+ char host[256];
+ char path[512];
+ uint16_t port;
+
+ if (http_parse_url(url, protocol, host, &port, path) != 0) {
+ return N706A_HTTP_EPARSE;
+ }
+
+ bool use_ssl = (strcmp(protocol, "https") == 0);
+
+ n706a_httpc_result_t ret = n706a_http_client_connect(client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ return ret;
+ }
+
+ return n706a_http_client_get(client, path, client_data);
+}
+
+/**
+ * @brief 解析完整URL并发起POST请求
+ */
+n706a_httpc_result_t n706a_http_client_post_url(n706a_http_client_t *client,
+ const char *url,
+ const char *content_type,
+ const char *body,
+ size_t body_len,
+ n706a_http_client_data_t *client_data)
+{
+ if (!client || !url || !client_data) {
+ return N706A_HTTP_EARG;
+ }
+
+ char protocol[8];
+ char host[256];
+ char path[512];
+ uint16_t port;
+
+ if (http_parse_url(url, protocol, host, &port, path) != 0) {
+ return N706A_HTTP_EPARSE;
+ }
+
+ bool use_ssl = (strcmp(protocol, "https") == 0);
+
+ n706a_httpc_result_t ret = n706a_http_client_connect(client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ return ret;
+ }
+
+ return n706a_http_client_post(client, path, content_type, body, body_len, client_data);
+}
+
+/* ========== 私有函数实现 ========== */
+
+/**
+ * @brief 构造HTTP请求
+ */
+static int http_build_request(n706a_http_client_t *client,
+ const char *method,
+ const char *path,
+ const char *body,
+ size_t body_len,
+ const char *content_type,
+ char *request_buf,
+ size_t buf_size)
+{
+ int len = 0;
+
+ /* 请求行 */
+ len += snprintf(request_buf + len, buf_size - len,
+ "%s %s HTTP/1.1\r\n", method, path);
+
+ /* Host头 */
+ len += snprintf(request_buf + len, buf_size - len,
+ "Host: %s\r\n", client->host);
+
+ /* User-Agent */
+ len += snprintf(request_buf + len, buf_size - len,
+ "User-Agent: N706A-HTTP-Client/1.0\r\n");
+
+ /* Accept */
+ len += snprintf(request_buf + len, buf_size - len,
+ "Accept: */*\r\n");
+
+ /* Connection */
+ len += snprintf(request_buf + len, buf_size - len,
+ "Connection: %s\r\n",
+ client->keepalive ? "keep-alive" : "close");
+
+ /* Content-Type和Content-Length(POST/PUT) */
+ if (body && body_len > 0) {
+ if (content_type) {
+ len += snprintf(request_buf + len, buf_size - len,
+ "Content-Type: %s\r\n", content_type);
+ }
+
+ len += snprintf(request_buf + len, buf_size - len,
+ "Content-Length: %zu\r\n", body_len);
+ }
+
+ /* Basic认证 */
+ if (client->auth_user && client->auth_password) {
+ char auth_str[256];
+ char auth_b64[512];
+
+ snprintf(auth_str, sizeof(auth_str), "%s:%s",
+ client->auth_user, client->auth_password);
+
+ http_base64_encode(auth_str, strlen(auth_str), auth_b64);
+
+ len += snprintf(request_buf + len, buf_size - len,
+ "Authorization: Basic %s\r\n", auth_b64);
+ }
+
+ /* 自定义请求头 */
+ if (client->header) {
+ len += snprintf(request_buf + len, buf_size - len,
+ "%s", client->header);
+ }
+
+ /* 空行 */
+ len += snprintf(request_buf + len, buf_size - len, "\r\n");
+
+ /* 请求体 */
+ if (body && body_len > 0) {
+ if (len + body_len < buf_size) {
+ memcpy(request_buf + len, body, body_len);
+ len += body_len;
+ } else {
+ return -1; // 缓冲区不足
+ }
+ }
+
+ return len;
+}
+
+/**
+ * @brief 解析HTTP响应
+ */
+static n706a_httpc_result_t http_parse_response(const char *response,
+ size_t response_len,
+ n706a_http_client_t *client,
+ n706a_http_client_data_t *data)
+{
+ if (!response || !client || !data) {
+ return N706A_HTTP_EARG;
+ }
+
+ /* 解析状态行 */
+ int major, minor, status_code;
+ if (sscanf(response, "HTTP/%d.%d %d", &major, &minor, &status_code) != 3) {
+ return N706A_HTTP_EPROTO;
+ }
+
+ client->response_code = status_code;
+
+ /* 查找头尾分界 */
+ const char *header_end = strstr(response, "\r\n\r\n");
+ if (!header_end) {
+ return N706A_HTTP_EPROTO;
+ }
+
+ /* 提取响应头 */
+ size_t header_len = header_end - response;
+ if (data->headerBuf && data->headerBufLen > 0) {
+ size_t copy_len = (header_len < (size_t)data->headerBufLen - 1) ?
+ header_len : (size_t)data->headerBufLen - 1;
+ memcpy(data->headerBuf, response, copy_len);
+ data->headerBuf[copy_len] = '\0';
+ }
+
+ /* 提取Content-Length */
+ const char *content_len_str = strcasestr(response, "Content-Length:");
+ if (content_len_str && content_len_str < header_end) {
+ data->responseContentLen = atoi(content_len_str + 15);
+ } else {
+ data->responseContentLen = 0;
+ }
+
+ /* 检查是否chunked */
+ if (strcasestr(response, "Transfer-Encoding: chunked")) {
+ data->isChunked = true;
+ } else {
+ data->isChunked = false;
+ }
+
+ /* 提取响应体 */
+ const char *body = header_end + 4;
+ size_t body_len = response_len - (body - response);
+
+ if (data->responseBuf && data->responseBufLen > 0) {
+ size_t copy_len = (body_len < (size_t)data->responseBufLen - 1) ?
+ body_len : (size_t)data->responseBufLen - 1;
+ memcpy(data->responseBuf, body, copy_len);
+ data->responseBuf[copy_len] = '\0';
+ data->contentBlockLen = copy_len;
+ }
+
+ /* 检查是否还有更多数据 */
+ if (data->responseContentLen > 0 && body_len < (size_t)data->responseContentLen) {
+ data->isMore = true;
+ } else {
+ data->isMore = false;
+ }
+
+ return N706A_HTTP_SUCCESS;
+}
+
+/**
+ * @brief Base64编码
+ */
+static void http_base64_encode(const char *input, size_t len, char *output)
+{
+ size_t i = 0, j = 0;
+ unsigned char char_array_3[3];
+ unsigned char char_array_4[4];
+
+ while (len--) {
+ char_array_3[i++] = *(input++);
+ if (i == 3) {
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+ char_array_4[3] = char_array_3[2] & 0x3f;
+
+ for (i = 0; i < 4; i++) {
+ output[j++] = base64_table[char_array_4[i]];
+ }
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (size_t k = i; k < 3; k++) {
+ char_array_3[k] = '\0';
+ }
+
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+
+ for (size_t k = 0; k < i + 1; k++) {
+ output[j++] = base64_table[char_array_4[k]];
+ }
+
+ while (i++ < 3) {
+ output[j++] = '=';
+ }
+ }
+
+ output[j] = '\0';
+}
+
+/**
+ * @brief 解析URL
+ */
+static int http_parse_url(const char *url,
+ char *protocol,
+ char *host,
+ uint16_t *port,
+ char *path)
+{
+ if (!url || !protocol || !host || !port || !path) {
+ return -1;
+ }
+
+ const char *p = url;
+
+ /* 解析协议 */
+ if (strncmp(p, "https://", 8) == 0) {
+ strcpy(protocol, "https");
+ *port = 443;
+ p += 8;
+ } else if (strncmp(p, "http://", 7) == 0) {
+ strcpy(protocol, "http");
+ *port = 80;
+ p += 7;
+ } else {
+ return -1;
+ }
+
+ /* 解析host和port */
+ const char *path_start = strchr(p, '/');
+ const char *port_start = strchr(p, ':');
+
+ if (port_start && (!path_start || port_start < path_start)) {
+ /* 有端口号 */
+ size_t host_len = port_start - p;
+ strncpy(host, p, host_len);
+ host[host_len] = '\0';
+
+ *port = atoi(port_start + 1);
+
+ if (path_start) {
+ strcpy(path, path_start);
+ } else {
+ strcpy(path, "/");
+ }
+ } else {
+ /* 没有端口号 */
+ if (path_start) {
+ size_t host_len = path_start - p;
+ strncpy(host, p, host_len);
+ host[host_len] = '\0';
+ strcpy(path, path_start);
+ } else {
+ strcpy(host, p);
+ strcpy(path, "/");
+ }
+ }
+
+ return 0;
+}
+
--- /dev/null
+/**
+ * @file n706a_tcp_client.c
+ * @brief N706A TCP客户端实现
+ *
+ * 支持两种SSL实现方式:
+ * - AT指令模式:使用AT+SSLTCP*指令
+ * - mbedtls模式:使用mbedtls库
+ */
+
+#include "n706a_tcp_client.h"
+#include "ppp_at_handler.h"
+#include "ppp_types.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* ========== 私有函数声明 ========== */
+
+#ifdef N706A_SSL_USE_MBEDTLS
+static int tcp_mbedtls_send_callback(void *ctx, const unsigned char *buf, size_t len);
+static int tcp_mbedtls_recv_callback(void *ctx, unsigned char *buf, size_t len);
+static n706a_tcp_result_t tcp_mbedtls_init_ssl(n706a_tcp_socket_t *sock);
+static void tcp_mbedtls_cleanup_ssl(n706a_tcp_socket_t *sock);
+#endif
+
+/* ========== 公共函数实现 ========== */
+
+/**
+ * @brief 创建TCP socket
+ */
+n706a_tcp_result_t n706a_tcp_socket_create(n706a_tcp_socket_t *sock, int socket_id)
+{
+ if (!sock || socket_id < 0 || socket_id > 5) {
+ return N706A_TCP_EINVAL;
+ }
+
+ memset(sock, 0, sizeof(n706a_tcp_socket_t));
+
+ sock->socket_id = socket_id;
+ sock->state = N706A_TCP_STATE_CLOSED;
+ sock->is_ssl = false;
+ sock->timeout_ms = 15000; // 默认15秒超时
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ sock->ssl_version = 3; // 默认TLS 1.2
+ sock->auth_mode = 1; // 默认服务器认证
+ memset(sock->ca_cert_file, 0, sizeof(sock->ca_cert_file));
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ sock->ssl_initialized = false;
+#endif
+
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 配置SSL参数
+ */
+n706a_tcp_result_t n706a_tcp_config_ssl(n706a_tcp_socket_t *sock,
+ int ssl_version,
+ int auth_mode,
+ const char *ca_cert)
+{
+ if (!sock) {
+ return N706A_TCP_EINVAL;
+ }
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ /* AT指令模式:保存配置参数 */
+ sock->ssl_version = ssl_version;
+ sock->auth_mode = auth_mode;
+
+ if (ca_cert) {
+ strncpy(sock->ca_cert_file, ca_cert, sizeof(sock->ca_cert_file) - 1);
+
+ /* 配置AT指令 */
+ char at_cmd[256];
+
+ /* 设置SSL版本 */
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"sslversion\",%d", ssl_version);
+ ppp_at_response_t response;
+ ppp_at_send_command(at_cmd, NULL, 5000, &response);
+
+ /* 设置认证模式 */
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"authmode\",%d", auth_mode);
+ ppp_at_send_command(at_cmd, NULL, 5000, &response);
+
+ /* 设置CA证书 */
+ if (ca_cert && strlen(ca_cert) > 0) {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCFG=\"cacert\",\"%s\"", ca_cert);
+ ppp_at_send_command(at_cmd, NULL, 5000, &response);
+ }
+ }
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:解析CA证书 */
+ if (ca_cert && strlen(ca_cert) > 0) {
+ mbedtls_x509_crt_init(&sock->cacert);
+
+ int ret = mbedtls_x509_crt_parse(&sock->cacert,
+ (const unsigned char *)ca_cert,
+ strlen(ca_cert) + 1);
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+ }
+
+ sock->auth_mode = auth_mode;
+#endif
+
+ return N706A_TCP_SUCCESS;
+}
+
+#ifdef N706A_SSL_USE_MBEDTLS
+/**
+ * @brief 配置客户端证书和私钥(mbedtls专用)
+ */
+n706a_tcp_result_t n706a_tcp_config_ssl_client_cert(n706a_tcp_socket_t *sock,
+ const char *client_cert,
+ const char *client_key)
+{
+ if (!sock || !client_cert || !client_key) {
+ return N706A_TCP_EINVAL;
+ }
+
+ /* 解析客户端证书 */
+ mbedtls_x509_crt_init(&sock->clicert);
+ int ret = mbedtls_x509_crt_parse(&sock->clicert,
+ (const unsigned char *)client_cert,
+ strlen(client_cert) + 1);
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 解析私钥 */
+ mbedtls_pk_init(&sock->pkey);
+ ret = mbedtls_pk_parse_key(&sock->pkey,
+ (const unsigned char *)client_key,
+ strlen(client_key) + 1,
+ NULL, 0);
+ if (ret != 0) {
+ mbedtls_x509_crt_free(&sock->clicert);
+ return N706A_TCP_ERROR;
+ }
+
+ return N706A_TCP_SUCCESS;
+}
+#endif
+
+/**
+ * @brief 连接到TCP服务器
+ */
+n706a_tcp_result_t n706a_tcp_connect(n706a_tcp_socket_t *sock,
+ const char *host,
+ uint16_t port,
+ bool use_ssl,
+ uint32_t timeout_ms)
+{
+ if (!sock || !host) {
+ return N706A_TCP_EINVAL;
+ }
+
+ if (sock->state == N706A_TCP_STATE_CONNECTED) {
+ return N706A_TCP_SUCCESS; // 已连接
+ }
+
+ /* 保存连接信息 */
+ strncpy(sock->remote_ip, host, sizeof(sock->remote_ip) - 1);
+ sock->remote_port = port;
+ sock->is_ssl = use_ssl;
+ sock->timeout_ms = timeout_ms;
+
+ char at_cmd[256];
+ ppp_at_response_t response;
+ ppp_error_t ret;
+
+ sock->state = N706A_TCP_STATE_CONNECTING;
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ if (use_ssl) {
+ /* AT指令SSL模式:使用AT+SSLTCPSETUP */
+ snprintf(at_cmd, sizeof(at_cmd),
+ "AT+SSLTCPSETUP=%d,%s,%d,%d",
+ sock->socket_id,
+ host,
+ port,
+ sock->auth_mode);
+
+ ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response);
+
+ if (ret == PPP_SUCCESS && response.response &&
+ strstr(response.response, "+SSLTCPSETUP:") &&
+ strstr(response.response, ",OK")) {
+ sock->state = N706A_TCP_STATE_CONNECTED;
+ return N706A_TCP_SUCCESS;
+ }
+ } else {
+ /* 普通TCP */
+ snprintf(at_cmd, sizeof(at_cmd),
+ "AT+TCPSETUP=%d,%s,%d",
+ sock->socket_id,
+ host,
+ port);
+
+ ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response);
+
+ if (ret == PPP_SUCCESS && response.response &&
+ strstr(response.response, "+TCPSETUP:") &&
+ strstr(response.response, ",OK")) {
+ sock->state = N706A_TCP_STATE_CONNECTED;
+ return N706A_TCP_SUCCESS;
+ }
+ }
+#endif
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:先建立TCP,再进行SSL握手 */
+
+ /* 1. 先建立普通TCP连接 */
+ snprintf(at_cmd, sizeof(at_cmd),
+ "AT+TCPSETUP=%d,%s,%d",
+ sock->socket_id,
+ host,
+ port);
+
+ ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response);
+
+ if (ret != PPP_SUCCESS || !response.response ||
+ !strstr(response.response, "+TCPSETUP:") ||
+ !strstr(response.response, ",OK")) {
+ sock->state = N706A_TCP_STATE_ERROR;
+ return N706A_TCP_ECONNREF;
+ }
+
+ /* 2. 如果使用SSL,进行SSL握手 */
+ if (use_ssl) {
+ n706a_tcp_result_t ssl_ret = tcp_mbedtls_init_ssl(sock);
+ if (ssl_ret != N706A_TCP_SUCCESS) {
+ n706a_tcp_close(sock);
+ return ssl_ret;
+ }
+
+ /* 设置主机名(用于SNI) */
+ mbedtls_ssl_set_hostname(&sock->ssl, host);
+
+ /* 执行SSL握手 */
+ int handshake_ret;
+ while ((handshake_ret = mbedtls_ssl_handshake(&sock->ssl)) != 0) {
+ if (handshake_ret != MBEDTLS_ERR_SSL_WANT_READ &&
+ handshake_ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
+ /* 握手失败 */
+ tcp_mbedtls_cleanup_ssl(sock);
+ n706a_tcp_close(sock);
+ sock->state = N706A_TCP_STATE_ERROR;
+ return N706A_TCP_ERROR;
+ }
+ }
+
+ /* 验证证书 */
+ uint32_t flags = mbedtls_ssl_get_verify_result(&sock->ssl);
+ if (flags != 0 && sock->auth_mode != MBEDTLS_SSL_VERIFY_NONE) {
+ /* 证书验证失败 */
+ tcp_mbedtls_cleanup_ssl(sock);
+ n706a_tcp_close(sock);
+ sock->state = N706A_TCP_STATE_ERROR;
+ return N706A_TCP_ERROR;
+ }
+ }
+#endif
+
+ sock->state = N706A_TCP_STATE_CONNECTED;
+ return N706A_TCP_SUCCESS;
+
+error:
+ sock->state = N706A_TCP_STATE_ERROR;
+ return N706A_TCP_ECONNREF;
+}
+
+/**
+ * @brief 发送TCP数据
+ */
+n706a_tcp_result_t n706a_tcp_send(n706a_tcp_socket_t *sock,
+ const uint8_t *data,
+ size_t len,
+ size_t *sent)
+{
+ if (!sock || !data || len == 0 || !sent) {
+ return N706A_TCP_EINVAL;
+ }
+
+ if (sock->state != N706A_TCP_STATE_CONNECTED) {
+ return N706A_TCP_ENOTCONN;
+ }
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:通过SSL发送 */
+ if (sock->is_ssl && sock->ssl_initialized) {
+ int ret = mbedtls_ssl_write(&sock->ssl, data, len);
+ if (ret > 0) {
+ *sent = ret;
+ return N706A_TCP_SUCCESS;
+ } else {
+ return N706A_TCP_ERROR;
+ }
+ }
+#endif
+
+ /* AT指令模式或普通TCP */
+ char at_cmd[64];
+ ppp_at_response_t response;
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ if (sock->is_ssl) {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPSEND=%d,%zu",
+ sock->socket_id, len);
+ } else
+#endif
+ {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPSEND=%d,%zu",
+ sock->socket_id, len);
+ }
+
+ /* 发送命令,等待'>'提示符 */
+ ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, 5000, &response);
+ if (ret != PPP_SUCCESS) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 发送实际数据 */
+ ret = ppp_at_send_raw_data(data, len, sock->timeout_ms);
+ if (ret != PPP_SUCCESS) {
+ return N706A_TCP_ERROR;
+ }
+
+ *sent = len;
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 接收TCP数据
+ */
+n706a_tcp_result_t n706a_tcp_recv(n706a_tcp_socket_t *sock,
+ uint8_t *buffer,
+ size_t len,
+ size_t *received,
+ uint32_t timeout_ms)
+{
+ if (!sock || !buffer || len == 0 || !received) {
+ return N706A_TCP_EINVAL;
+ }
+
+ if (sock->state != N706A_TCP_STATE_CONNECTED) {
+ return N706A_TCP_ENOTCONN;
+ }
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:通过SSL接收 */
+ if (sock->is_ssl && sock->ssl_initialized) {
+ mbedtls_ssl_conf_read_timeout(&sock->conf, timeout_ms);
+
+ int ret = mbedtls_ssl_read(&sock->ssl, buffer, len);
+ if (ret > 0) {
+ *received = ret;
+ return N706A_TCP_SUCCESS;
+ } else if (ret == 0 || ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY) {
+ *received = 0;
+ return N706A_TCP_SUCCESS;
+ } else {
+ return N706A_TCP_ERROR;
+ }
+ }
+#endif
+
+ /* AT指令模式或普通TCP */
+ char at_cmd[64];
+ ppp_at_response_t response;
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ if (sock->is_ssl) {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPREAD=%d,%zu",
+ sock->socket_id, len);
+ } else
+#endif
+ {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPREAD=%d,%zu",
+ sock->socket_id, len);
+ }
+
+ ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, timeout_ms, &response);
+ if (ret != PPP_SUCCESS) {
+ return N706A_TCP_ETIMEDOUT;
+ }
+
+ /* 解析响应,提取数据 */
+ if (response.response) {
+ /* 查找数据部分 */
+ const char *data_start = strstr(response.response, "\r\n");
+ if (data_start) {
+ data_start += 2; // 跳过\r\n
+ size_t data_len = response.response_len - (data_start - response.response);
+
+ if (data_len > len) {
+ data_len = len;
+ }
+
+ memcpy(buffer, data_start, data_len);
+ *received = data_len;
+ return N706A_TCP_SUCCESS;
+ }
+ }
+
+ *received = 0;
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 关闭TCP连接
+ */
+n706a_tcp_result_t n706a_tcp_close(n706a_tcp_socket_t *sock)
+{
+ if (!sock) {
+ return N706A_TCP_EINVAL;
+ }
+
+ if (sock->state == N706A_TCP_STATE_CLOSED) {
+ return N706A_TCP_SUCCESS;
+ }
+
+#ifdef N706A_SSL_USE_MBEDTLS
+ /* mbedtls模式:清理SSL上下文 */
+ if (sock->is_ssl && sock->ssl_initialized) {
+ mbedtls_ssl_close_notify(&sock->ssl);
+ tcp_mbedtls_cleanup_ssl(sock);
+ }
+#endif
+
+ /* 发送关闭命令 */
+ char at_cmd[32];
+ ppp_at_response_t response;
+
+#ifdef N706A_SSL_USE_AT_COMMAND
+ if (sock->is_ssl) {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+SSLTCPCLOSE=%d", sock->socket_id);
+ } else
+#endif
+ {
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPCLOSE=%d", sock->socket_id);
+ }
+
+ ppp_at_send_command(at_cmd, NULL, 5000, &response);
+
+ sock->state = N706A_TCP_STATE_CLOSED;
+ sock->is_ssl = false;
+
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 检查socket是否已连接
+ */
+bool n706a_tcp_is_connected(const n706a_tcp_socket_t *sock)
+{
+ if (!sock) {
+ return false;
+ }
+
+ return (sock->state == N706A_TCP_STATE_CONNECTED);
+}
+
+/**
+ * @brief 获取socket状态
+ */
+n706a_tcp_state_t n706a_tcp_get_state(const n706a_tcp_socket_t *sock)
+{
+ if (!sock) {
+ return N706A_TCP_STATE_ERROR;
+ }
+
+ return sock->state;
+}
+
+/* ========== mbedtls私有函数实现 ========== */
+
+#ifdef N706A_SSL_USE_MBEDTLS
+
+/**
+ * @brief 初始化mbedtls SSL
+ */
+static n706a_tcp_result_t tcp_mbedtls_init_ssl(n706a_tcp_socket_t *sock)
+{
+ if (!sock) {
+ return N706A_TCP_EINVAL;
+ }
+
+ /* 初始化SSL上下文 */
+ mbedtls_ssl_init(&sock->ssl);
+ mbedtls_ssl_config_init(&sock->conf);
+ mbedtls_entropy_init(&sock->entropy);
+ mbedtls_ctr_drbg_init(&sock->ctr_drbg);
+
+ /* 初始化随机数生成器 */
+ const char *pers = "n706a_tcp_client";
+ int ret = mbedtls_ctr_drbg_seed(&sock->ctr_drbg,
+ mbedtls_entropy_func,
+ &sock->entropy,
+ (const unsigned char *)pers,
+ strlen(pers));
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 配置SSL */
+ ret = mbedtls_ssl_config_defaults(&sock->conf,
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_TRANSPORT_STREAM,
+ MBEDTLS_SSL_PRESET_DEFAULT);
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 设置认证模式 */
+ mbedtls_ssl_conf_authmode(&sock->conf, sock->auth_mode);
+
+ /* 设置随机数生成器 */
+ mbedtls_ssl_conf_rng(&sock->conf,
+ mbedtls_ctr_drbg_random,
+ &sock->ctr_drbg);
+
+ /* 设置CA证书 */
+ mbedtls_ssl_conf_ca_chain(&sock->conf, &sock->cacert, NULL);
+
+ /* 设置客户端证书(如果有) */
+ if (sock->clicert.version != 0) {
+ mbedtls_ssl_conf_own_cert(&sock->conf, &sock->clicert, &sock->pkey);
+ }
+
+ /* 应用配置 */
+ ret = mbedtls_ssl_setup(&sock->ssl, &sock->conf);
+ if (ret != 0) {
+ return N706A_TCP_ERROR;
+ }
+
+ /* 设置发送/接收回调 */
+ mbedtls_ssl_set_bio(&sock->ssl,
+ sock,
+ tcp_mbedtls_send_callback,
+ tcp_mbedtls_recv_callback,
+ NULL);
+
+ sock->ssl_initialized = true;
+
+ return N706A_TCP_SUCCESS;
+}
+
+/**
+ * @brief 清理mbedtls SSL
+ */
+static void tcp_mbedtls_cleanup_ssl(n706a_tcp_socket_t *sock)
+{
+ if (!sock || !sock->ssl_initialized) {
+ return;
+ }
+
+ mbedtls_ssl_free(&sock->ssl);
+ mbedtls_ssl_config_free(&sock->conf);
+ mbedtls_ctr_drbg_free(&sock->ctr_drbg);
+ mbedtls_entropy_free(&sock->entropy);
+
+ if (sock->cacert.version != 0) {
+ mbedtls_x509_crt_free(&sock->cacert);
+ }
+
+ if (sock->clicert.version != 0) {
+ mbedtls_x509_crt_free(&sock->clicert);
+ }
+
+ if (sock->pkey.pk_info != NULL) {
+ mbedtls_pk_free(&sock->pkey);
+ }
+
+ sock->ssl_initialized = false;
+}
+
+/**
+ * @brief mbedtls发送回调(通过AT命令发送)
+ */
+static int tcp_mbedtls_send_callback(void *ctx, const unsigned char *buf, size_t len)
+{
+ n706a_tcp_socket_t *sock = (n706a_tcp_socket_t *)ctx;
+
+ if (!sock || !buf || len == 0) {
+ return MBEDTLS_ERR_NET_SEND_FAILED;
+ }
+
+ /* 通过AT+TCPSEND发送加密后的数据 */
+ char at_cmd[64];
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPSEND=%d,%zu",
+ sock->socket_id, len);
+
+ ppp_at_response_t response;
+ ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, 5000, &response);
+ if (ret != PPP_SUCCESS) {
+ return MBEDTLS_ERR_NET_SEND_FAILED;
+ }
+
+ /* 发送数据 */
+ ret = ppp_at_send_raw_data(buf, len, sock->timeout_ms);
+ if (ret != PPP_SUCCESS) {
+ return MBEDTLS_ERR_NET_SEND_FAILED;
+ }
+
+ return len;
+}
+
+/**
+ * @brief mbedtls接收回调(通过AT命令接收)
+ */
+static int tcp_mbedtls_recv_callback(void *ctx, unsigned char *buf, size_t len)
+{
+ n706a_tcp_socket_t *sock = (n706a_tcp_socket_t *)ctx;
+
+ if (!sock || !buf || len == 0) {
+ return MBEDTLS_ERR_NET_RECV_FAILED;
+ }
+
+ /* 通过AT+TCPREAD接收加密数据 */
+ char at_cmd[64];
+ snprintf(at_cmd, sizeof(at_cmd), "AT+TCPREAD=%d,%zu",
+ sock->socket_id, len);
+
+ ppp_at_response_t response;
+ ppp_error_t ret = ppp_at_send_command(at_cmd, NULL, sock->timeout_ms, &response);
+ if (ret != PPP_SUCCESS) {
+ return MBEDTLS_ERR_NET_RECV_FAILED;
+ }
+
+ /* 解析数据 */
+ if (response.response) {
+ const char *data_start = strstr(response.response, "\r\n");
+ if (data_start) {
+ data_start += 2;
+ size_t data_len = response.response_len - (data_start - response.response);
+
+ if (data_len > len) {
+ data_len = len;
+ }
+
+ memcpy(buf, data_start, data_len);
+ return data_len;
+ }
+ }
+
+ return 0; // 没有数据
+}
+
+#endif /* N706A_SSL_USE_MBEDTLS */
+
--- /dev/null
+/**
+ * @file n706a_websocket.c
+ * @brief N706A WebSocket客户端实现(基于TCP)
+ *
+ * 完整实现RFC 6455 WebSocket协议:
+ * - ws:// (普通TCP)
+ * - wss:// (SSL TCP)
+ * - 握手
+ * - 帧封装/解封装
+ * - Ping/Pong
+ * - 分片传输
+ */
+
+#include "n706a_websocket.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <time.h>
+
+/* WebSocket GUID(RFC 6455) */
+#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
+
+/* WebSocket帧标志 */
+#define WS_FIN_BIT 0x80
+#define WS_MASK_BIT 0x80
+#define WS_OPCODE_MASK 0x0F
+#define WS_PAYLOAD_MASK 0x7F
+
+/* 全局WebSocket客户端数组 */
+static n706a_websocket_client_t *g_ws_clients[N706A_WS_MAX_CLIENTS] = {NULL};
+
+/* ========== 私有函数声明 ========== */
+
+static int ws_perform_handshake(n706a_websocket_client_t *client);
+static int ws_send_frame(n706a_websocket_client_t *client,
+ n706a_ws_opcode_t opcode,
+ const uint8_t *data,
+ size_t length,
+ bool fin);
+static int ws_recv_frame(n706a_websocket_client_t *client);
+static void ws_generate_mask_key(uint8_t *mask_key);
+static void ws_mask_data(uint8_t *data, size_t len, const uint8_t *mask_key);
+static void *ws_recv_thread_func(void *arg);
+static int ws_parse_url(const char *url, char *protocol, char *host,
+ uint16_t *port, char *path);
+
+/* SHA-1哈希(简化实现,用于WebSocket握手) */
+static void sha1_simple(const unsigned char *input, size_t len, unsigned char output[20]);
+
+/* Base64编码 */
+static void base64_encode(const unsigned char *input, size_t len, char *output);
+
+/* ========== 公共函数实现 ========== */
+
+/**
+ * @brief 创建WebSocket客户端
+ */
+int n706a_websocket_constructor(n706a_ws_on_open_callback_t on_open,
+ n706a_ws_on_message_callback_t on_message,
+ n706a_ws_on_error_callback_t on_error,
+ n706a_ws_on_close_callback_t on_close)
+{
+ /* 查找空闲slot */
+ int client_id = -1;
+ for (int i = 0; i < N706A_WS_MAX_CLIENTS; i++) {
+ if (g_ws_clients[i] == NULL) {
+ client_id = i;
+ break;
+ }
+ }
+
+ if (client_id < 0) {
+ return -1; // 没有可用slot
+ }
+
+ /* 分配客户端结构 */
+ n706a_websocket_client_t *client =
+ (n706a_websocket_client_t *)malloc(sizeof(n706a_websocket_client_t));
+
+ if (!client) {
+ return -1;
+ }
+
+ memset(client, 0, sizeof(n706a_websocket_client_t));
+
+ client->client_id = client_id;
+ client->state = N706A_WS_STATE_CLOSED;
+ client->is_ssl = false;
+
+ /* 初始化TCP socket */
+ n706a_tcp_socket_create(&client->tcp_sock, client_id);
+
+ /* 设置回调 */
+ client->on_open = on_open;
+ client->on_message = on_message;
+ client->on_error = on_error;
+ client->on_close = on_close;
+
+ /* 分配接收缓冲区 */
+ client->recv_buffer_size = 8192;
+ client->recv_buffer = (uint8_t *)malloc(client->recv_buffer_size);
+ if (!client->recv_buffer) {
+ free(client);
+ return -1;
+ }
+
+ client->recv_buffer_len = 0;
+ client->running = false;
+
+ g_ws_clients[client_id] = client;
+
+ return client_id;
+}
+
+/**
+ * @brief 创建WebSocket客户端(指定socket ID)
+ */
+int n706a_websocket_constructor_with_socket(int socket_id,
+ n706a_ws_on_open_callback_t on_open,
+ n706a_ws_on_message_callback_t on_message,
+ n706a_ws_on_error_callback_t on_error,
+ n706a_ws_on_close_callback_t on_close)
+{
+ if (socket_id < 0 || socket_id >= N706A_WS_MAX_CLIENTS) {
+ return -1;
+ }
+
+ if (g_ws_clients[socket_id] != NULL) {
+ return -1; // 已被占用
+ }
+
+ n706a_websocket_client_t *client =
+ (n706a_websocket_client_t *)malloc(sizeof(n706a_websocket_client_t));
+
+ if (!client) {
+ return -1;
+ }
+
+ memset(client, 0, sizeof(n706a_websocket_client_t));
+
+ client->client_id = socket_id;
+ client->state = N706A_WS_STATE_CLOSED;
+
+ n706a_tcp_socket_create(&client->tcp_sock, socket_id);
+
+ client->on_open = on_open;
+ client->on_message = on_message;
+ client->on_error = on_error;
+ client->on_close = on_close;
+
+ client->recv_buffer_size = 8192;
+ client->recv_buffer = (uint8_t *)malloc(client->recv_buffer_size);
+ if (!client->recv_buffer) {
+ free(client);
+ return -1;
+ }
+
+ g_ws_clients[socket_id] = client;
+
+ return socket_id;
+}
+
+/**
+ * @brief 连接到WebSocket服务器
+ */
+int n706a_websocket_connect(int client_id, const char *url, const char *option)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ if (client->state != N706A_WS_STATE_CLOSED) {
+ return N706A_WS_ERROR_ALREADY_OPEN;
+ }
+
+ /* 解析URL */
+ char protocol[8];
+
+ if (ws_parse_url(url, protocol, client->host, &client->port, client->path) != 0) {
+ if (client->on_error) {
+ client->on_error(client_id, N706A_WS_ERROR_INVALID_PARAM, "Invalid URL");
+ }
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ strncpy(client->url, url, sizeof(client->url) - 1);
+
+ /* 判断是否WSS */
+ client->is_ssl = (strcmp(protocol, "wss") == 0);
+
+ client->state = N706A_WS_STATE_CONNECTING;
+
+ /* 建立TCP连接 */
+ n706a_tcp_result_t ret = n706a_tcp_connect(&client->tcp_sock,
+ client->host,
+ client->port,
+ client->is_ssl,
+ 30000);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ client->state = N706A_WS_STATE_CLOSED;
+ if (client->on_error) {
+ client->on_error(client_id, N706A_WS_ERROR_HANDSHAKE, "TCP connection failed");
+ }
+ return N706A_WS_ERROR_HANDSHAKE;
+ }
+
+ /* WebSocket握手 */
+ if (ws_perform_handshake(client) != 0) {
+ n706a_tcp_close(&client->tcp_sock);
+ client->state = N706A_WS_STATE_CLOSED;
+ if (client->on_error) {
+ client->on_error(client_id, N706A_WS_ERROR_HANDSHAKE, "WebSocket handshake failed");
+ }
+ return N706A_WS_ERROR_HANDSHAKE;
+ }
+
+ client->state = N706A_WS_STATE_OPEN;
+
+ /* 启动接收线程 */
+ client->running = true;
+ // TODO: 创建接收线程
+ // pthread_create(&client->recv_thread, NULL, ws_recv_thread_func, client);
+
+ /* 调用on_open回调 */
+ if (client->on_open) {
+ client->on_open(client_id);
+ }
+
+ return N706A_WS_SUCCESS;
+}
+
+/**
+ * @brief 发送文本消息
+ */
+int n706a_websocket_send_text(int client_id, const char *data, size_t length)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client || client->state != N706A_WS_STATE_OPEN) {
+ return N706A_WS_ERROR_NOT_CONNECTED;
+ }
+
+ return ws_send_frame(client, N706A_WS_OPCODE_TEXT, (const uint8_t *)data, length, true);
+}
+
+/**
+ * @brief 发送二进制消息
+ */
+int n706a_websocket_send_binary(int client_id, const uint8_t *data, size_t length)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client || client->state != N706A_WS_STATE_OPEN) {
+ return N706A_WS_ERROR_NOT_CONNECTED;
+ }
+
+ return ws_send_frame(client, N706A_WS_OPCODE_BINARY, data, length, true);
+}
+
+/**
+ * @brief 发送Ping帧
+ */
+int n706a_websocket_send_ping(int client_id, const char *data, size_t length)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client || client->state != N706A_WS_STATE_OPEN) {
+ return N706A_WS_ERROR_NOT_CONNECTED;
+ }
+
+ return ws_send_frame(client, N706A_WS_OPCODE_PING, (const uint8_t *)data, length, true);
+}
+
+/**
+ * @brief 发送Pong帧
+ */
+int n706a_websocket_send_pong(int client_id, const char *data, size_t length)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client || client->state != N706A_WS_STATE_OPEN) {
+ return N706A_WS_ERROR_NOT_CONNECTED;
+ }
+
+ return ws_send_frame(client, N706A_WS_OPCODE_PONG, (const uint8_t *)data, length, true);
+}
+
+/**
+ * @brief 关闭WebSocket连接
+ */
+int n706a_websocket_close(int client_id)
+{
+ return n706a_websocket_close_with_reason(client_id, N706A_WS_CLOSE_NORMAL, NULL);
+}
+
+/**
+ * @brief 关闭WebSocket连接(带原因)
+ */
+int n706a_websocket_close_with_reason(int client_id,
+ n706a_ws_close_code_t close_code,
+ const char *reason)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ if (client->state == N706A_WS_STATE_OPEN) {
+ /* 发送关闭帧 */
+ uint8_t close_data[128];
+ size_t close_len = 2;
+
+ close_data[0] = (close_code >> 8) & 0xFF;
+ close_data[1] = close_code & 0xFF;
+
+ if (reason) {
+ size_t reason_len = strlen(reason);
+ if (reason_len > sizeof(close_data) - 2) {
+ reason_len = sizeof(close_data) - 2;
+ }
+ memcpy(close_data + 2, reason, reason_len);
+ close_len += reason_len;
+ }
+
+ ws_send_frame(client, N706A_WS_OPCODE_CLOSE, close_data, close_len, true);
+
+ client->state = N706A_WS_STATE_CLOSING;
+ }
+
+ /* 停止接收线程 */
+ client->running = false;
+ // TODO: 等待线程结束
+ // pthread_join(client->recv_thread, NULL);
+
+ /* 关闭TCP连接 */
+ n706a_tcp_close(&client->tcp_sock);
+
+ client->state = N706A_WS_STATE_CLOSED;
+
+ /* 调用on_close回调 */
+ if (client->on_close) {
+ client->on_close(client_id, close_code, reason);
+ }
+
+ return N706A_WS_SUCCESS;
+}
+
+/**
+ * @brief 获取WebSocket状态
+ */
+n706a_ws_state_t n706a_websocket_get_state(int client_id)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_STATE_CLOSED;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client) {
+ return N706A_WS_STATE_CLOSED;
+ }
+
+ return client->state;
+}
+
+/**
+ * @brief 检查WebSocket是否已连接
+ */
+bool n706a_websocket_is_connected(int client_id)
+{
+ return (n706a_websocket_get_state(client_id) == N706A_WS_STATE_OPEN);
+}
+
+/**
+ * @brief 销毁WebSocket客户端
+ */
+int n706a_websocket_destructor(int client_id)
+{
+ if (client_id < 0 || client_id >= N706A_WS_MAX_CLIENTS) {
+ return N706A_WS_ERROR_INVALID_PARAM;
+ }
+
+ n706a_websocket_client_t *client = g_ws_clients[client_id];
+ if (!client) {
+ return N706A_WS_SUCCESS;
+ }
+
+ /* 确保连接已关闭 */
+ if (client->state != N706A_WS_STATE_CLOSED) {
+ n706a_websocket_close(client_id);
+ }
+
+ /* 释放资源 */
+ if (client->recv_buffer) {
+ free(client->recv_buffer);
+ }
+
+ free(client);
+ g_ws_clients[client_id] = NULL;
+
+ return N706A_WS_SUCCESS;
+}
+
+/* ========== 私有函数实现 ========== */
+
+/**
+ * @brief 执行WebSocket握手
+ */
+static int ws_perform_handshake(n706a_websocket_client_t *client)
+{
+ char handshake_req[1024];
+ char sec_key[25];
+
+ /* 生成Sec-WebSocket-Key */
+ n706a_websocket_generate_key(sec_key);
+ strncpy(client->sec_key, sec_key, sizeof(client->sec_key) - 1);
+
+ /* 构造握手请求 */
+ int len = snprintf(handshake_req, sizeof(handshake_req),
+ "GET %s HTTP/1.1\r\n"
+ "Host: %s\r\n"
+ "Upgrade: websocket\r\n"
+ "Connection: Upgrade\r\n"
+ "Sec-WebSocket-Key: %s\r\n"
+ "Sec-WebSocket-Version: 13\r\n"
+ "\r\n",
+ client->path,
+ client->host,
+ sec_key
+ );
+
+ /* 发送握手请求 */
+ size_t sent;
+ n706a_tcp_result_t ret = n706a_tcp_send(&client->tcp_sock,
+ (uint8_t *)handshake_req,
+ len,
+ &sent);
+
+ if (ret != N706A_TCP_SUCCESS) {
+ return -1;
+ }
+
+ /* 接收握手响应 */
+ uint8_t response[2048];
+ size_t received;
+
+ ret = n706a_tcp_recv(&client->tcp_sock,
+ response,
+ sizeof(response) - 1,
+ &received,
+ 10000);
+
+ if (ret != N706A_TCP_SUCCESS || received == 0) {
+ return -1;
+ }
+
+ response[received] = '\0';
+
+ /* 验证响应 */
+ if (!strstr((char *)response, "HTTP/1.1 101") ||
+ !strstr((char *)response, "Upgrade: websocket") ||
+ !strstr((char *)response, "Connection: Upgrade")) {
+ return -1;
+ }
+
+ /* 验证Sec-WebSocket-Accept */
+ const char *accept_start = strstr((char *)response, "Sec-WebSocket-Accept:");
+ if (accept_start) {
+ accept_start += 21; // 跳过"Sec-WebSocket-Accept:"
+ while (*accept_start == ' ') accept_start++;
+
+ char expected_accept[29];
+ n706a_websocket_calculate_accept(sec_key, expected_accept);
+
+ if (strncmp(accept_start, expected_accept, strlen(expected_accept)) != 0) {
+ return -1; // Accept值不匹配
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * @brief 发送WebSocket帧
+ */
+static int ws_send_frame(n706a_websocket_client_t *client,
+ n706a_ws_opcode_t opcode,
+ const uint8_t *data,
+ size_t length,
+ bool fin)
+{
+ uint8_t frame[14]; // 最大帧头长度
+ size_t frame_len = 0;
+
+ /* 第一字节:FIN + RSV + Opcode */
+ frame[frame_len++] = (fin ? WS_FIN_BIT : 0) | (opcode & WS_OPCODE_MASK);
+
+ /* 第二字节:MASK + Payload length */
+ if (length < 126) {
+ frame[frame_len++] = WS_MASK_BIT | (length & WS_PAYLOAD_MASK);
+ } else if (length < 65536) {
+ frame[frame_len++] = WS_MASK_BIT | 126;
+ frame[frame_len++] = (length >> 8) & 0xFF;
+ frame[frame_len++] = length & 0xFF;
+ } else {
+ frame[frame_len++] = WS_MASK_BIT | 127;
+ for (int i = 7; i >= 0; i--) {
+ frame[frame_len++] = (length >> (i * 8)) & 0xFF;
+ }
+ }
+
+ /* Masking key */
+ uint8_t mask_key[4];
+ ws_generate_mask_key(mask_key);
+ memcpy(frame + frame_len, mask_key, 4);
+ frame_len += 4;
+
+ /* 发送帧头 */
+ size_t sent;
+ n706a_tcp_send(&client->tcp_sock, frame, frame_len, &sent);
+
+ /* 发送payload(掩码后) */
+ if (data && length > 0) {
+ uint8_t *masked_data = (uint8_t *)malloc(length);
+ if (masked_data) {
+ memcpy(masked_data, data, length);
+ ws_mask_data(masked_data, length, mask_key);
+
+ n706a_tcp_send(&client->tcp_sock, masked_data, length, &sent);
+
+ free(masked_data);
+ }
+ }
+
+ return N706A_WS_SUCCESS;
+}
+
+/**
+ * @brief 接收WebSocket帧
+ */
+static int ws_recv_frame(n706a_websocket_client_t *client)
+{
+ uint8_t header[2];
+ size_t received;
+
+ /* 接收帧头前2字节 */
+ n706a_tcp_result_t ret = n706a_tcp_recv(&client->tcp_sock,
+ header,
+ 2,
+ &received,
+ 5000);
+
+ if (ret != N706A_TCP_SUCCESS || received < 2) {
+ return -1;
+ }
+
+ bool fin = (header[0] & WS_FIN_BIT) != 0;
+ n706a_ws_opcode_t opcode = (n706a_ws_opcode_t)(header[0] & WS_OPCODE_MASK);
+ bool masked = (header[1] & WS_MASK_BIT) != 0;
+ uint64_t payload_len = header[1] & WS_PAYLOAD_MASK;
+
+ /* 扩展payload长度 */
+ if (payload_len == 126) {
+ uint8_t len_buf[2];
+ n706a_tcp_recv(&client->tcp_sock, len_buf, 2, &received, 5000);
+ payload_len = (len_buf[0] << 8) | len_buf[1];
+ } else if (payload_len == 127) {
+ uint8_t len_buf[8];
+ n706a_tcp_recv(&client->tcp_sock, len_buf, 8, &received, 5000);
+ payload_len = 0;
+ for (int i = 0; i < 8; i++) {
+ payload_len = (payload_len << 8) | len_buf[i];
+ }
+ }
+
+ /* 接收payload */
+ if (payload_len > 0) {
+ uint8_t *payload = (uint8_t *)malloc(payload_len + 1);
+ if (!payload) {
+ return -1;
+ }
+
+ n706a_tcp_recv(&client->tcp_sock, payload, payload_len, &received, 10000);
+ payload[payload_len] = '\0';
+
+ /* 处理不同类型的帧 */
+ switch (opcode) {
+ case N706A_WS_OPCODE_TEXT:
+ case N706A_WS_OPCODE_BINARY:
+ if (client->on_message) {
+ client->on_message(client->client_id,
+ (char *)payload,
+ payload_len,
+ fin,
+ opcode == N706A_WS_OPCODE_BINARY);
+ }
+ break;
+
+ case N706A_WS_OPCODE_PING:
+ /* 自动回复Pong */
+ ws_send_frame(client, N706A_WS_OPCODE_PONG, payload, payload_len, true);
+ break;
+
+ case N706A_WS_OPCODE_PONG:
+ /* 忽略Pong */
+ break;
+
+ case N706A_WS_OPCODE_CLOSE:
+ /* 服务器关闭连接 */
+ client->state = N706A_WS_STATE_CLOSING;
+
+ uint16_t close_code = N706A_WS_CLOSE_NO_STATUS;
+ if (payload_len >= 2) {
+ close_code = (payload[0] << 8) | payload[1];
+ }
+
+ if (client->on_close) {
+ client->on_close(client->client_id, close_code,
+ payload_len > 2 ? (char *)(payload + 2) : NULL);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ free(payload);
+ }
+
+ return 0;
+}
+
+/**
+ * @brief 生成mask key
+ */
+static void ws_generate_mask_key(uint8_t *mask_key)
+{
+ for (int i = 0; i < 4; i++) {
+ mask_key[i] = rand() & 0xFF;
+ }
+}
+
+/**
+ * @brief 掩码数据
+ */
+static void ws_mask_data(uint8_t *data, size_t len, const uint8_t *mask_key)
+{
+ for (size_t i = 0; i < len; i++) {
+ data[i] ^= mask_key[i % 4];
+ }
+}
+
+/**
+ * @brief 解析WebSocket URL
+ */
+static int ws_parse_url(const char *url, char *protocol, char *host,
+ uint16_t *port, char *path)
+{
+ if (!url || !protocol || !host || !port || !path) {
+ return -1;
+ }
+
+ const char *p = url;
+
+ /* 解析协议 */
+ if (strncmp(p, "wss://", 6) == 0) {
+ strcpy(protocol, "wss");
+ *port = 443;
+ p += 6;
+ } else if (strncmp(p, "ws://", 5) == 0) {
+ strcpy(protocol, "ws");
+ *port = 80;
+ p += 5;
+ } else {
+ return -1;
+ }
+
+ /* 解析host */
+ const char *path_start = strchr(p, '/');
+ const char *port_start = strchr(p, ':');
+
+ if (port_start && (!path_start || port_start < path_start)) {
+ size_t host_len = port_start - p;
+ strncpy(host, p, host_len);
+ host[host_len] = '\0';
+
+ *port = atoi(port_start + 1);
+
+ if (path_start) {
+ strcpy(path, path_start);
+ } else {
+ strcpy(path, "/");
+ }
+ } else {
+ if (path_start) {
+ size_t host_len = path_start - p;
+ strncpy(host, p, host_len);
+ host[host_len] = '\0';
+ strcpy(path, path_start);
+ } else {
+ strcpy(host, p);
+ strcpy(path, "/");
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * @brief 生成WebSocket Key
+ */
+int n706a_websocket_generate_key(char *key)
+{
+ if (!key) {
+ return -1;
+ }
+
+ /* 生成16字节随机数 */
+ unsigned char random_bytes[16];
+ srand(time(NULL));
+ for (int i = 0; i < 16; i++) {
+ random_bytes[i] = rand() & 0xFF;
+ }
+
+ /* Base64编码 */
+ base64_encode(random_bytes, 16, key);
+
+ return 0;
+}
+
+/**
+ * @brief 计算WebSocket Accept
+ */
+int n706a_websocket_calculate_accept(const char *key, char *accept)
+{
+ if (!key || !accept) {
+ return -1;
+ }
+
+ /* 拼接key和GUID */
+ char concat[128];
+ snprintf(concat, sizeof(concat), "%s%s", key, WS_GUID);
+
+ /* SHA-1哈希 */
+ unsigned char hash[20];
+ sha1_simple((unsigned char *)concat, strlen(concat), hash);
+
+ /* Base64编码 */
+ base64_encode(hash, 20, accept);
+
+ return 0;
+}
+
+/* ========== 辅助函数 ========== */
+
+/**
+ * @brief 简化的SHA-1实现(仅用于WebSocket握手)
+ * 注意:这是简化版,生产环境建议使用成熟的加密库
+ */
+static void sha1_simple(const unsigned char *input, size_t len, unsigned char output[20])
+{
+ /* 这里应该是完整的SHA-1实现 */
+ /* 为了简化,这里仅做占位 */
+ /* 生产环境应使用mbedtls_sha1或OpenSSL的SHA1函数 */
+
+ /* TODO: 集成真实的SHA-1实现 */
+ memset(output, 0, 20);
+
+ /* 临时:使用简单哈希作为占位符 */
+ for (size_t i = 0; i < len && i < 20; i++) {
+ output[i % 20] ^= input[i];
+ }
+}
+
+/**
+ * @brief Base64编码
+ */
+static void base64_encode(const unsigned char *input, size_t len, char *output)
+{
+ static const char base64_table[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ size_t i = 0, j = 0;
+ unsigned char char_array_3[3];
+ unsigned char char_array_4[4];
+
+ while (len--) {
+ char_array_3[i++] = *(input++);
+ if (i == 3) {
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+ char_array_4[3] = char_array_3[2] & 0x3f;
+
+ for (i = 0; i < 4; i++) {
+ output[j++] = base64_table[char_array_4[i]];
+ }
+ i = 0;
+ }
+ }
+
+ if (i) {
+ for (size_t k = i; k < 3; k++) {
+ char_array_3[k] = '\0';
+ }
+
+ char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
+ char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
+ char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
+
+ for (size_t k = 0; k < i + 1; k++) {
+ output[j++] = base64_table[char_array_4[k]];
+ }
+
+ while (i++ < 3) {
+ output[j++] = '=';
+ }
+ }
+
+ output[j] = '\0';
+}
+
.reset_pin_en_value = 1, //电路中 控制gpio拉高,reset才拉低
// .ring_pin = PIN_NONE, //CTS cat1 wakeup mcu modem还没实现,暂时不使用
.ring_pin = S_MGPIO21, //CTS cat1 wakeup mcu
- .ring_pin_en_value = 2, //2 双边沿中断 , 1 上升沿中断 , 0 下降沿中断
+ .ring_pin_en_value = 1, //2 双边沿中断 , 1 上升沿中断 , 0 下降沿中断
.wakemodem_pin = S_MGPIO22, //RTS mcu wakeup cat1
.wakemodem_pin_en_value = 0 //0 唤醒cat1 , 1 休眠cat1
};
ppp_get_network_signal_info();
- ppp_enable_power_save_mode();
+ // ppp_enable_power_save_mode();
ppp_start_dial();
#endif
return ERRCODE_SUCC;
}
-errcode_t ppp_api_set_baudrate_in_all_mode(uint32_t baudrate)
+/* 静态变量保存暂停状态(用于pause和resume之间的状态传递) */
+static uint8_t s_ppp_need_resume = 0;
+static uint8_t s_ppp_need_deinit_at = 0;
+static uint8_t s_ppp_is_paused = 0; /* PPP是否处于暂停状态(阻止数据输出) */
+
+/**
+ * @brief 查询PPP是否处于暂停状态
+ * @return true: PPP已暂停,数据输出被阻止; false: PPP正常运行
+ */
+bool ppp_api_is_paused(void)
{
- errcode_t ret = ERRCODE_SUCC;
- uint8_t need_resume = 0;
- uint8_t need_deinit_at = 0;
+ return (s_ppp_is_paused != 0);
+}
+
+/**
+ * @brief 为执行AT命令暂停PPP模式(高级接口)
+ * @return ERRCODE_SUCC 成功,其他值失败
+ */
+errcode_t ppp_api_pause_for_at_command(void)
+{
+ // 重置内部静态变量
+ s_ppp_need_resume = 0;
+ s_ppp_need_deinit_at = 0;
+ s_ppp_is_paused = 0;
// 1. 如果在 PPP 模式,先暂停
if(ppp_serial_get_mode() == PPP_SERIAL_MODE_PPP_DATA) {
PPP_DEBUG_ERROR("Failed to pause PPP mode");
return ERRCODE_FAIL;
}
- need_resume = 1;
+ s_ppp_need_resume = 1;
// 2. 暂停后需要初始化 AT handler 才能执行 AT 命令
PPP_DEBUG_INFO("Initializing AT handler for command execution...");
PPP_DEBUG_ERROR("Failed to initialize AT handler: %d", at_ret);
// 尝试恢复 PPP 模式
ppp_ppp_mode_resume();
+ s_ppp_need_resume = 0;
return ERRCODE_FAIL;
}
- need_deinit_at = 1;
+ s_ppp_need_deinit_at = 1;
} else {
PPP_DEBUG_INFO("Already in AT mode");
- need_resume = 0;
- need_deinit_at = 0;
}
- // 3. 执行 AT 命令设置波特率
- PPP_DEBUG_CRITICAL("Starting baudrate set... %u", baudrate);
- ppp_error_t at_ret = ppp_at_set_baudrate(baudrate);
- if (at_ret != PPP_ERR_OK) {
- PPP_DEBUG_WARN("First attempt to set baudrate failed, retrying...");
- // 重试一次
- at_ret = ppp_at_set_baudrate(baudrate);
- if (at_ret != PPP_ERR_OK) {
- PPP_DEBUG_ERROR("Failed to set baudrate %u after retry (error: %d)", baudrate, at_ret);
- ret = ERRCODE_FAIL;
-
- // 如果设置失败,尝试恢复原状态
- if (need_resume) {
- if (need_deinit_at) {
- ppp_at_handler_deinit();
- }
- ppp_ppp_mode_resume();
- }
- return ret;
- } else {
- PPP_DEBUG_INFO("Baudrate set successfully on retry");
- }
- } else {
- PPP_DEBUG_INFO("Baudrate set successfully: %u", baudrate);
- }
+ // 设置暂停标志,阻止PPP数据输出
+ s_ppp_is_paused = 1;
+ PPP_DEBUG_INFO("PPP paused for AT command successfully (data output blocked)");
+ return ERRCODE_SUCC;
+}
- // 4. 如果需要,去初始化 AT handler 并恢复 PPP 模式
- if (need_resume) {
+/**
+ * @brief AT命令执行后恢复PPP模式(高级接口)
+ * @return ERRCODE_SUCC 成功,其他值失败
+ */
+errcode_t ppp_api_resume_after_at_command(void)
+{
+ errcode_t ret = ERRCODE_SUCC;
+
+ // 使用内部静态变量
+ PPP_DEBUG_INFO("Using internal state: need_resume=%d, need_deinit_at=%d",
+ s_ppp_need_resume, s_ppp_need_deinit_at);
+
+ if (s_ppp_need_resume) {
// 先去初始化 AT handler
- if (need_deinit_at) {
+ if (s_ppp_need_deinit_at) {
PPP_DEBUG_INFO("Deinitializing AT handler...");
ppp_at_handler_deinit();
}
PPP_DEBUG_WARN("PPP mode forcefully recovered, but connection may be unstable");
ret = ERRCODE_FAIL;
}
+
+ // 清除内部状态
+ s_ppp_need_resume = 0;
+ s_ppp_need_deinit_at = 0;
}
-
+
+ // 清除暂停标志,恢复PPP数据输出
+ s_ppp_is_paused = 0;
+ PPP_DEBUG_INFO("PPP resumed successfully (data output enabled)");
return ret;
}
-errcode_t ppp_api_update_netinfo_in_all_mode(void)
+errcode_t ppp_api_set_baudrate_in_all_mode(uint32_t baudrate)
{
errcode_t ret = ERRCODE_SUCC;
- uint8_t need_resume = 0;
- uint8_t need_deinit_at = 0;
- // 1. 如果在 PPP 模式,先暂停
- if(ppp_serial_get_mode() == PPP_SERIAL_MODE_PPP_DATA) {
- PPP_DEBUG_INFO("In PPP mode, pausing...");
- if (ppp_interrupt_is_safe_for_command_switch() != true) {
- PPP_DEBUG_ERROR("Not safe for command switch");
- return ERRCODE_FAIL;
- }
- if (ppp_ppp_mode_pause() != ERRCODE_SUCC) {
- PPP_DEBUG_ERROR("Failed to pause PPP mode");
- return ERRCODE_FAIL;
- }
- need_resume = 1;
-
- // 2. 暂停后需要初始化 AT handler 才能执行 AT 命令
- PPP_DEBUG_INFO("Initializing AT handler for command execution...");
- ppp_error_t at_ret = ppp_at_handler_init(SIO_UART_BUS, g_ppp_config.baudrate, true);
+ // 1. 暂停PPP模式(如果需要)
+ if (ppp_api_pause_for_at_command() != ERRCODE_SUCC) {
+ PPP_DEBUG_ERROR("Failed to pause for AT command");
+ return ERRCODE_FAIL;
+ }
+
+ // 2. 执行 AT 命令设置波特率
+ PPP_DEBUG_CRITICAL("Starting baudrate set... %u", baudrate);
+ ppp_error_t at_ret = ppp_at_set_baudrate(baudrate);
+ if (at_ret != PPP_ERR_OK) {
+ PPP_DEBUG_WARN("First attempt to set baudrate failed, retrying...");
+ // 重试一次
+ at_ret = ppp_at_set_baudrate(baudrate);
if (at_ret != PPP_ERR_OK) {
- PPP_DEBUG_ERROR("Failed to initialize AT handler: %d", at_ret);
- // 尝试恢复 PPP 模式
- ppp_ppp_mode_resume();
- return ERRCODE_FAIL;
+ PPP_DEBUG_ERROR("Failed to set baudrate %u after retry (error: %d)", baudrate, at_ret);
+ ret = ERRCODE_FAIL;
+
+ // 如果设置失败,尝试恢复原状态
+ ppp_api_resume_after_at_command();
+ return ret;
+ } else {
+ PPP_DEBUG_INFO("Baudrate set successfully on retry");
}
- need_deinit_at = 1;
} else {
- PPP_DEBUG_INFO("Already in AT mode");
- need_resume = 0;
- need_deinit_at = 0;
+ PPP_DEBUG_INFO("Baudrate set successfully: %u", baudrate);
+ }
+
+ // 3. 恢复PPP模式(如果需要)
+ if (ppp_api_resume_after_at_command() != ERRCODE_SUCC) {
+ PPP_DEBUG_ERROR("Failed to resume after AT command");
+ ret = ERRCODE_FAIL;
+ }
+
+ return ret;
+}
+
+errcode_t ppp_api_update_netinfo_in_all_mode(void)
+{
+ errcode_t ret = ERRCODE_SUCC;
+
+ // 1. 暂停PPP模式(如果需要)
+ if (ppp_api_pause_for_at_command() != ERRCODE_SUCC) {
+ PPP_DEBUG_ERROR("Failed to pause for AT command");
+ return ERRCODE_FAIL;
}
- // 3. 执行 AT 命令更新信息
+ // 2. 执行 AT 命令更新信息
PPP_DEBUG_CRITICAL("Starting signal strength update...");
if (ppp_api_update_signal() != ERRCODE_SUCC) {
PPP_DEBUG_ERROR("Failed to update signal");
ret = ERRCODE_FAIL;
}
- // 4. 如果需要,去初始化 AT handler 并恢复 PPP 模式
- if (need_resume) {
- // 先去初始化 AT handler
- if (need_deinit_at) {
- PPP_DEBUG_INFO("Deinitializing AT handler...");
- ppp_at_handler_deinit();
- }
-
- // 然后恢复 PPP 模式
- PPP_DEBUG_INFO("Resuming PPP mode...");
- errcode_t resume_ret = ppp_ppp_mode_resume();
- if (resume_ret != ERRCODE_SUCC) {
- PPP_DEBUG_ERROR("Failed to resume PPP mode: %d", resume_ret);
- PPP_DEBUG_ERROR("Forcing PPP mode recovery...");
-
- // 强制恢复到 PPP 模式
- ppp_serial_switch_to_ppp_mode();
-
- #if PPP_USE_INTERRUPT_MODE
- ppp_error_t int_ret = ppp_interrupt_enable();
- if (int_ret != PPP_ERR_OK) {
- PPP_DEBUG_ERROR("Failed to enable interrupt after forced recovery: %d", int_ret);
- } else {
- PPP_DEBUG_INFO("PPP interrupt mode re-enabled after forced recovery");
- }
- #else
- ppp_error_t thread_ret = ppp_lwip_start_receive_thread();
- if (thread_ret != PPP_ERR_OK) {
- PPP_DEBUG_ERROR("Failed to start receive thread after forced recovery: %d", thread_ret);
- } else {
- PPP_DEBUG_INFO("PPP receive thread restarted after forced recovery");
- }
- #endif
-
- PPP_DEBUG_WARN("PPP mode forcefully recovered, but connection may be unstable");
- ret = ERRCODE_FAIL;
- }
+ // 3. 恢复PPP模式(如果需要)
+ if (ppp_api_resume_after_at_command() != ERRCODE_SUCC) {
+ PPP_DEBUG_ERROR("Failed to resume after AT command");
+ ret = ERRCODE_FAIL;
}
return ret;
return ret;
}
+/**
+ * @brief 发送原始数据(不带AT命令格式)
+ * @note 用于 TCP/SSL 数据发送,在 AT+TCPSEND/AT+SSLTCPSEND 命令后使用
+ */
+ppp_error_t ppp_at_send_raw_data(const void *data, size_t len, uint32_t timeout)
+{
+ if (!g_at_ctx.initialized || !data || len == 0) {
+ PPP_DEBUG_ERROR("Invalid parameters: initialized=%d, data=%p, len=%zu",
+ g_at_ctx.initialized, data, len);
+ return PPP_ERR_INVALID_PARAM;
+ }
+
+ PPP_DEBUG_DEBUG("=== Sending Raw Data ===");
+ PPP_DEBUG_DEBUG("Length: %zu bytes", len);
+ PPP_DEBUG_DEBUG("Timeout: %u ms", timeout);
+
+ /* 直接通过 UART 发送原始数据,不添加 \r\n */
+ int32_t write_result = uapi_uart_write(g_at_ctx.uart_bus, (const uint8_t *)data, len, 0);
+
+ if (write_result != (int32_t)len) {
+ PPP_DEBUG_ERROR("Failed to send raw data: wrote %d bytes, expected %zu", write_result, len);
+ return PPP_ERR_UART;
+ }
+
+ PPP_DEBUG_DEBUG("Raw data sent successfully (%zu bytes) on bus %d", len, g_at_ctx.uart_bus);
+ return PPP_ERR_OK;
+}
+
/**
* @brief 跳过开机日志 (对应chat文件中的 '' \rAT)
*/
/* lwIP相关头文件 */
#include "lwip/err.h"
+#include "lwip/stats.h"
#include "netif/ppp/ppp.h"
#include "netif/ppp/pppos.h"
}
PPP_DEBUG_WARN("");
+ // lwIP PPP帧解析层统计
+ PPP_DEBUG_WARN("=== lwIP PPP Frame Parsing Statistics ===");
+#if LINK_STATS
+ PPP_DEBUG_WARN("Valid Frames Received: %u ✓", (uint32_t)lwip_stats.link.recv);
+ PPP_DEBUG_WARN("Frames Transmitted: %u", (uint32_t)lwip_stats.link.xmit);
+
+ // 解析错误统计
+ uint32_t total_parse_errors = (uint32_t)(lwip_stats.link.drop +
+ lwip_stats.link.chkerr +
+ lwip_stats.link.lenerr +
+ lwip_stats.link.memerr);
+ if (total_parse_errors > 0) {
+ PPP_DEBUG_WARN("Parse Errors: %u ⚠️", total_parse_errors);
+ PPP_DEBUG_WARN(" - FCS Checksum Error: %u", (uint32_t)lwip_stats.link.chkerr);
+ PPP_DEBUG_WARN(" - Length Error (incomplete): %u", (uint32_t)lwip_stats.link.lenerr);
+ PPP_DEBUG_WARN(" - Memory Error: %u", (uint32_t)lwip_stats.link.memerr);
+ PPP_DEBUG_WARN(" - Other Drop: %u", (uint32_t)lwip_stats.link.drop);
+
+ // 计算错误率
+ uint32_t total_frames = (uint32_t)(lwip_stats.link.recv + total_parse_errors);
+ if (total_frames > 0) {
+ uint32_t error_rate = (total_parse_errors * 10000) / total_frames;
+ PPP_DEBUG_WARN(" Frame Error Rate: %u.%02u%%", error_rate / 100, error_rate % 100);
+
+ // FCS错误率(如果有)
+ if (lwip_stats.link.chkerr > 0) {
+ uint32_t fcs_rate = ((uint32_t)lwip_stats.link.chkerr * 10000) / total_frames;
+ PPP_DEBUG_WARN(" FCS Error Rate: %u.%02u%% (check UART/cable)",
+ fcs_rate / 100, fcs_rate % 100);
+ }
+ }
+ } else {
+ PPP_DEBUG_WARN("Parse Errors: 0 ✓ (no errors)");
+ }
+#else
+ PPP_DEBUG_WARN("LINK_STATS not enabled in lwIP");
+#endif
+ PPP_DEBUG_WARN("");
+
// 字节统计(与轮询模式格式一致)
uint32_t total_discarded = g_ppp_interrupt_stats.dropped_not_initialized +
g_ppp_interrupt_stats.dropped_lwip_error +
g_ppp_interrupt_stats.dropped_queue_full +
g_pingpong_stats.dropped_both_full;
- PPP_DEBUG_WARN("");
- PPP_DEBUG_WARN("Byte Statistics:");
- PPP_DEBUG_WARN(" Total Bytes Received: %u", g_ppp_interrupt_stats.total_bytes_received);
- PPP_DEBUG_WARN(" Total Bytes Processed: %u", g_ppp_interrupt_stats.total_bytes_sent_to_lwip);
- PPP_DEBUG_WARN(" Total Bytes Discarded: %u", total_discarded);
- PPP_DEBUG_WARN(" - Both Buffers Full: %u", g_pingpong_stats.dropped_both_full);
- PPP_DEBUG_WARN(" - Queue Full: %u", g_ppp_interrupt_stats.dropped_queue_full);
- PPP_DEBUG_WARN(" lwIP Errors: %u", g_ppp_interrupt_stats.lwip_error_count);
- PPP_DEBUG_WARN("");
+ PPP_DEBUG_WARN("=== Byte-Level Statistics ===");
+ PPP_DEBUG_WARN("Total Bytes Received (UART): %u", g_ppp_interrupt_stats.total_bytes_received);
+ PPP_DEBUG_WARN("Total Bytes Sent to lwIP: %u", g_ppp_interrupt_stats.total_bytes_sent_to_lwip);
- // 详细丢弃统计(INFO级别)
+ // 丢弃统计
if (total_discarded > 0) {
- PPP_DEBUG_WARN("Detailed Dropped Bytes:");
- PPP_DEBUG_WARN(" - Not Initialized: %u", g_ppp_interrupt_stats.dropped_not_initialized);
- PPP_DEBUG_WARN(" - lwIP Error: %u", g_ppp_interrupt_stats.dropped_lwip_error);
- PPP_DEBUG_WARN(" - Queue Full: %u", g_ppp_interrupt_stats.dropped_queue_full);
+ PPP_DEBUG_WARN("Total Bytes Discarded: %u ⚠️", total_discarded);
PPP_DEBUG_WARN(" - Both Buffers Full: %u", g_pingpong_stats.dropped_both_full);
- PPP_DEBUG_WARN("");
+ PPP_DEBUG_WARN(" - Queue Full: %u", g_ppp_interrupt_stats.dropped_queue_full);
+ PPP_DEBUG_WARN(" - Not Initialized: %u", g_ppp_interrupt_stats.dropped_not_initialized);
+ PPP_DEBUG_WARN(" - lwIP Input Error: %u", g_ppp_interrupt_stats.dropped_lwip_error);
+ } else {
+ PPP_DEBUG_WARN("Total Bytes Discarded: 0 ✓");
}
+ PPP_DEBUG_WARN("");
- // 计算丢包率
- if (g_ppp_interrupt_stats.total_bytes_received > 0) {
- uint32_t total_dropped = g_ppp_interrupt_stats.dropped_not_initialized +
- g_ppp_interrupt_stats.dropped_lwip_error +
- g_ppp_interrupt_stats.dropped_queue_full +
- g_pingpong_stats.dropped_both_full;
- uint32_t drop_rate = (total_dropped * 10000) / g_ppp_interrupt_stats.total_bytes_received;
- PPP_DEBUG_WARN("Total Drop Rate: %u.%02u%%", drop_rate / 100, drop_rate % 100);
+ // 计算字节级丢包率
+ if (g_ppp_interrupt_stats.total_bytes_received > 0 && total_discarded > 0) {
+ uint32_t drop_rate = (total_discarded * 10000) / g_ppp_interrupt_stats.total_bytes_received;
+ PPP_DEBUG_WARN("Byte-Level Drop Rate: %u.%02u%%", drop_rate / 100, drop_rate % 100);
- // 队列满导致的丢失率
- if (g_ppp_interrupt_stats.dropped_queue_full > 0) {
- uint32_t queue_full_rate = (g_ppp_interrupt_stats.dropped_queue_full * 10000) /
- g_ppp_interrupt_stats.total_bytes_received;
- PPP_DEBUG_WARN("Queue Full Rate: %u.%02u%%", queue_full_rate / 100, queue_full_rate % 100);
- }
-
- // 两个buffer都满导致的丢失率
- if (g_pingpong_stats.dropped_both_full > 0) {
- uint32_t both_full_rate = (g_pingpong_stats.dropped_both_full * 10000) /
- g_ppp_interrupt_stats.total_bytes_received;
- PPP_DEBUG_WARN("Both Buffers Full Rate: %u.%02u%% ⚠️ (processing too slow or buffers too small)",
- both_full_rate / 100, both_full_rate % 100);
+ // 如果丢包率超过1%,提示可能的原因
+ if (drop_rate > 100) {
+ PPP_DEBUG_ERROR("⚠️ High byte drop rate detected!");
+ if (g_pingpong_stats.dropped_both_full > 0) {
+ PPP_DEBUG_ERROR(" -> Buffers too small or processing too slow");
+ }
+ if (g_ppp_interrupt_stats.dropped_queue_full > 0) {
+ PPP_DEBUG_ERROR(" -> Message queue too small or thread blocked");
+ }
}
}
- // 性能总结(已经在上面各部分显示过了,这里不再重复)
-
PPP_DEBUG_WARN("=================================================");
}
*/
static u32_t ppp_lwip_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx)
{
+ /* 检查PPP是否处于暂停状态 */
+ if (ppp_api_is_paused()) {
+ PPP_DEBUG_WARN("PPP is paused, blocking data output (%u bytes)", len);
+ return 0; /* 返回0表示无法发送 */
+ }
+
/* 严格的参数检查 - 防止空指针访问 */
if (!pcb) {
PPP_DEBUG_ERROR("CRITICAL: ppp_pcb is NULL in output callback!");
last_level = val;
same_level_count = 1;
- cat1_api_add_cat1_vote();
if (val == GPIO_LEVEL_HIGH) {
- PPP_DEBUG_INFO("PPP: Cat1 ring interrupt on pin %d → HIGH \n", pin);
- } else {
- PPP_DEBUG_INFO("PPP: Cat1 ring interrupt on pin %d → LOW\n", pin);
+ PPP_DEBUG_WARN("PPP: Cat1 ring interrupt on pin %d → HIGH \n", pin);
+ cat1_api_add_cat1_vote();
+} else {
+ PPP_DEBUG_WARN("PPP: Cat1 ring interrupt on pin %d → LOW\n", pin);
//idle后 remove
}
uapi_gpio_clear_interrupt(pin);
--- /dev/null
+#===============================================================================
+# @brief 智慧网络统一接口层
+# Copyright (c) @CompanyNameMagicTag 2023-2025. All rights reserved.
+#===============================================================================
+set(COMPONENT_NAME "zhihui_net")
+
+set(SOURCES ${SOURCES}
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/zhihui_net.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/zhihui_net_http.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/zhihui_net_websocket.c
+)
+
+set(PUBLIC_HEADER
+ ${CMAKE_CURRENT_SOURCE_DIR}/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../open_source/openharmony/foundation/ace/ace_engine_lite/modules/netstack/http/include
+ ${CMAKE_CURRENT_SOURCE_DIR}/../websocket/os_ws
+)
+
+set(PUBLIC_DEFINES
+ SUPPORT_ZHIHUI_NET
+)
+
+set(PRIVATE_HEADER
+ ${CMAKE_CURRENT_SOURCE_DIR}/../n706a_net/include
+)
+
+set(PRIVATE_DEFINES
+)
+
+# use this when you want to add ccflags like -include xxx
+set(COMPONENT_PUBLIC_CCFLAGS
+)
+
+set(COMPONENT_CCFLAGS
+ -Wno-incompatible-function-pointer-types
+ -Wno-unused-function
+)
+
+set(WHOLE_LINK
+ true
+)
+
+set(MAIN_COMPONENT
+ false
+)
+
+build_component()
+
+install_sdk(${CMAKE_CURRENT_SOURCE_DIR} "*")
+
--- /dev/null
+/**
+ * @file zhihui_net_openharmony_adapter.c
+ * @brief OpenHarmony网络实现适配器实现
+ *
+ * 将统一接口转换为OpenHarmony的httpclient和net_websocket接口
+ */
+
+#include "zhihui_net_openharmony_adapter.h"
+#include "httpclient.h"
+#include "net_websocket.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* ========== HTTP接口适配 ========== */
+
+/* 将ZhihuiHttpClient转换为OpenHarmony HttpClient */
+static void convert_to_openharmony_http_client(ZhihuiHttpClient *src, HttpClient *dst)
+{
+ memset(dst, 0, sizeof(HttpClient));
+ dst->socket = src->socket;
+ dst->remotePort = src->remotePort;
+ dst->responseCode = src->responseCode;
+ dst->header = src->header;
+ dst->authUser = src->authUser;
+ dst->authPassword = src->authPassword;
+ dst->isHttp = src->isHttp;
+}
+
+/* 将OpenHarmony HttpClient转换回ZhihuiHttpClient */
+static void convert_from_openharmony_http_client(HttpClient *src, ZhihuiHttpClient *dst)
+{
+ dst->socket = src->socket;
+ dst->remotePort = src->remotePort;
+ dst->responseCode = src->responseCode;
+ dst->header = src->header;
+ dst->authUser = src->authUser;
+ dst->authPassword = src->authPassword;
+ dst->isHttp = src->isHttp;
+}
+
+/* 将ZhihuiHttpClientData转换为OpenHarmony HttpClientData */
+static void convert_to_openharmony_http_data(ZhihuiHttpClientData *src, HttpClientData *dst)
+{
+ memset(dst, 0, sizeof(HttpClientData));
+ dst->isMore = src->isMore;
+ dst->isChunked = src->isChunked;
+ dst->method = src->method;
+ dst->retrieveLen = src->retrieveLen;
+ dst->responseContentLen = src->responseContentLen;
+ dst->contentBlockLen = src->contentBlockLen;
+ dst->postBufLen = src->postBufLen;
+ dst->responseBufLen = src->responseBufLen;
+ dst->headerBufLen = src->headerBufLen;
+ dst->postContentType = src->postContentType;
+ dst->postBuf = src->postBuf;
+ dst->responseBuf = src->responseBuf;
+ dst->headerBuf = src->headerBuf;
+ dst->isRedirected = src->isRedirected;
+ dst->redirectUrl = src->redirectUrl;
+}
+
+/* 将OpenHarmony HttpClientData转换回ZhihuiHttpClientData */
+static void convert_from_openharmony_http_data(HttpClientData *src, ZhihuiHttpClientData *dst)
+{
+ dst->isMore = src->isMore;
+ dst->isChunked = src->isChunked;
+ dst->method = src->method;
+ dst->retrieveLen = src->retrieveLen;
+ dst->responseContentLen = src->responseContentLen;
+ dst->contentBlockLen = src->contentBlockLen;
+ dst->postBufLen = src->postBufLen;
+ dst->responseBufLen = src->responseBufLen;
+ dst->headerBufLen = src->headerBufLen;
+ dst->postContentType = src->postContentType;
+ dst->postBuf = src->postBuf;
+ dst->responseBuf = src->responseBuf;
+ dst->headerBuf = src->headerBuf;
+ dst->isRedirected = src->isRedirected;
+ dst->redirectUrl = src->redirectUrl;
+}
+
+int ZhihuiNetOpenHarmonyHttpGet(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ HttpClient oh_client;
+ HttpClientData oh_data;
+ int ret;
+
+ convert_to_openharmony_http_client(client, &oh_client);
+ convert_to_openharmony_http_data(data, &oh_data);
+
+ ret = HttpClientGetRequest(&oh_client, url, &oh_data);
+
+ convert_from_openharmony_http_client(&oh_client, client);
+ convert_from_openharmony_http_data(&oh_data, data);
+
+ return ret;
+}
+
+int ZhihuiNetOpenHarmonyHttpPost(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ HttpClient oh_client;
+ HttpClientData oh_data;
+ int ret;
+
+ convert_to_openharmony_http_client(client, &oh_client);
+ convert_to_openharmony_http_data(data, &oh_data);
+
+ ret = HttpClientPostRequest(&oh_client, url, &oh_data);
+
+ convert_from_openharmony_http_client(&oh_client, client);
+ convert_from_openharmony_http_data(&oh_data, data);
+
+ return ret;
+}
+
+int ZhihuiNetOpenHarmonyHttpHead(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ HttpClient oh_client;
+ HttpClientData oh_data;
+ int ret;
+
+ convert_to_openharmony_http_client(client, &oh_client);
+ convert_to_openharmony_http_data(data, &oh_data);
+
+ ret = HttpClientHeadRequest(&oh_client, url, &oh_data);
+
+ convert_from_openharmony_http_client(&oh_client, client);
+ convert_from_openharmony_http_data(&oh_data, data);
+
+ return ret;
+}
+
+int ZhihuiNetOpenHarmonyHttpPut(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ HttpClient oh_client;
+ HttpClientData oh_data;
+ int ret;
+
+ convert_to_openharmony_http_client(client, &oh_client);
+ convert_to_openharmony_http_data(data, &oh_data);
+
+ ret = HttpClientPutRequest(&oh_client, url, &oh_data);
+
+ convert_from_openharmony_http_client(&oh_client, client);
+ convert_from_openharmony_http_data(&oh_data, data);
+
+ return ret;
+}
+
+int ZhihuiNetOpenHarmonyHttpDelete(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ HttpClient oh_client;
+ HttpClientData oh_data;
+ int ret;
+
+ convert_to_openharmony_http_client(client, &oh_client);
+ convert_to_openharmony_http_data(data, &oh_data);
+
+ ret = HttpClientDeleteRequest(&oh_client, url, &oh_data);
+
+ convert_from_openharmony_http_client(&oh_client, client);
+ convert_from_openharmony_http_data(&oh_data, data);
+
+ return ret;
+}
+
+/* ========== WebSocket接口适配 ========== */
+
+/* 回调函数适配器 */
+typedef struct {
+ ZhihuiWebSocketOnOpenCallback zhihui_on_open;
+ ZhihuiWebSocketOnMessageCallback zhihui_on_message;
+ ZhihuiWebSocketOnErrorCallback zhihui_on_error;
+ ZhihuiWebSocketOnCloseCallback zhihui_on_close;
+} ws_callback_adapter_t;
+
+#define MAX_WS_CLIENTS 8
+static ws_callback_adapter_t g_ws_adapters[MAX_WS_CLIENTS];
+
+/* OpenHarmony回调转发到Zhihui回调 */
+static void oh_ws_on_open(int client)
+{
+ if (client >= 0 && client < MAX_WS_CLIENTS) {
+ if (g_ws_adapters[client].zhihui_on_open) {
+ g_ws_adapters[client].zhihui_on_open(client);
+ }
+ }
+}
+
+static void oh_ws_on_message(int client, const char *data, size_t length, bool isFinished)
+{
+ if (client >= 0 && client < MAX_WS_CLIENTS) {
+ if (g_ws_adapters[client].zhihui_on_message) {
+ /* OpenHarmony不区分文本/二进制,默认作为文本 */
+ g_ws_adapters[client].zhihui_on_message(client, data, length, isFinished, false);
+ }
+ }
+}
+
+static void oh_ws_on_error(int client, int errorCode, const char *errorMsg)
+{
+ if (client >= 0 && client < MAX_WS_CLIENTS) {
+ if (g_ws_adapters[client].zhihui_on_error) {
+ g_ws_adapters[client].zhihui_on_error(client, errorCode, errorMsg);
+ }
+ }
+}
+
+static void oh_ws_on_close(int client, int closeCode, const char *reason)
+{
+ if (client >= 0 && client < MAX_WS_CLIENTS) {
+ if (g_ws_adapters[client].zhihui_on_close) {
+ g_ws_adapters[client].zhihui_on_close(client, closeCode, reason);
+ }
+ }
+}
+
+int ZhihuiNetOpenHarmonyWsConstructor(ZhihuiWebSocketOnOpenCallback onOpen,
+ ZhihuiWebSocketOnMessageCallback onMessage,
+ ZhihuiWebSocketOnErrorCallback onError,
+ ZhihuiWebSocketOnCloseCallback onClose)
+{
+ int client = WebSocketClientConstructor(oh_ws_on_open, oh_ws_on_message,
+ oh_ws_on_error, oh_ws_on_close);
+
+ if (client >= 0 && client < MAX_WS_CLIENTS) {
+ g_ws_adapters[client].zhihui_on_open = onOpen;
+ g_ws_adapters[client].zhihui_on_message = onMessage;
+ g_ws_adapters[client].zhihui_on_error = onError;
+ g_ws_adapters[client].zhihui_on_close = onClose;
+ }
+
+ return client;
+}
+
+int ZhihuiNetOpenHarmonyWsConnect(int client, const char *url, const char *option)
+{
+ return WebSocketClientConnect(client, url, option);
+}
+
+int ZhihuiNetOpenHarmonyWsSendText(int client, const char *text, size_t length)
+{
+ return WebSocketClientSendText(client, text, length);
+}
+
+int ZhihuiNetOpenHarmonyWsSendBinStart(int client, const char *data, size_t length)
+{
+ return WebSocketClientSendBinStart(client, data, length);
+}
+
+int ZhihuiNetOpenHarmonyWsSendBinContinue(int client, const char *data, size_t length)
+{
+ return WebSocketClientSendBinContinue(client, data, length);
+}
+
+int ZhihuiNetOpenHarmonyWsSendBinEnd(int client, const char *data, size_t length)
+{
+ return WebSocketClientSendBinEnd(client, data, length);
+}
+
+int ZhihuiNetOpenHarmonyWsClose(int client)
+{
+ return WebSocketClientClose(client);
+}
+
+int ZhihuiNetOpenHarmonyWsDestructor(int client)
+{
+ int ret = WebSocketClientDestructor(client);
+
+ /* 清除适配器 */
+ if (client >= 0 && client < MAX_WS_CLIENTS) {
+ memset(&g_ws_adapters[client], 0, sizeof(ws_callback_adapter_t));
+ }
+
+ return ret;
+}
+
+/* ========== 初始化/清理 ========== */
+
+int ZhihuiNetOpenHarmonyInit(void)
+{
+ printf("[ZhihuiNet][OpenHarmony] Adapter initialized\n");
+ memset(g_ws_adapters, 0, sizeof(g_ws_adapters));
+ return 0;
+}
+
+int ZhihuiNetOpenHarmonyDeinit(void)
+{
+ printf("[ZhihuiNet][OpenHarmony] Adapter deinitialized\n");
+ return 0;
+}
+
--- /dev/null
+/**
+ * @file zhihui_net_openharmony_adapter.h
+ * @brief OpenHarmony网络实现适配器
+ */
+
+#ifndef ZHIHUI_NET_OPENHARMONY_ADAPTER_H
+#define ZHIHUI_NET_OPENHARMONY_ADAPTER_H
+
+#include "zhihui_net.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* HTTP接口 */
+int ZhihuiNetOpenHarmonyHttpGet(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+int ZhihuiNetOpenHarmonyHttpPost(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+int ZhihuiNetOpenHarmonyHttpHead(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+int ZhihuiNetOpenHarmonyHttpPut(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+int ZhihuiNetOpenHarmonyHttpDelete(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+
+/* WebSocket接口 */
+int ZhihuiNetOpenHarmonyWsConstructor(ZhihuiWebSocketOnOpenCallback onOpen,
+ ZhihuiWebSocketOnMessageCallback onMessage,
+ ZhihuiWebSocketOnErrorCallback onError,
+ ZhihuiWebSocketOnCloseCallback onClose);
+int ZhihuiNetOpenHarmonyWsConnect(int client, const char *url, const char *option);
+int ZhihuiNetOpenHarmonyWsSendText(int client, const char *text, size_t length);
+int ZhihuiNetOpenHarmonyWsSendBinStart(int client, const char *data, size_t length);
+int ZhihuiNetOpenHarmonyWsSendBinContinue(int client, const char *data, size_t length);
+int ZhihuiNetOpenHarmonyWsSendBinEnd(int client, const char *data, size_t length);
+int ZhihuiNetOpenHarmonyWsClose(int client);
+int ZhihuiNetOpenHarmonyWsDestructor(int client);
+
+/* 初始化/清理 */
+int ZhihuiNetOpenHarmonyInit(void);
+int ZhihuiNetOpenHarmonyDeinit(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZHIHUI_NET_OPENHARMONY_ADAPTER_H */
+
--- /dev/null
+/**
+ * @file zhihui_net_ppp_adapter.c
+ * @brief N706A PPP网络实现适配器实现
+ *
+ * 将统一接口转换为N706A PPP的接口
+ */
+
+#include "zhihui_net_ppp_adapter.h"
+#include "n706a_http_client.h"
+#include "n706a_websocket.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* ========== HTTP接口适配 ========== */
+
+int ZhihuiNetPppHttpGet(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ n706a_http_client_t *ppp_client = (n706a_http_client_t *)client->impl_data;
+
+ /* 如果还未初始化PPP客户端,则初始化 */
+ if (ppp_client == NULL) {
+ ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t));
+ if (ppp_client == NULL) {
+ return -1;
+ }
+
+ n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0);
+ if (ret != N706A_HTTP_SUCCESS) {
+ free(ppp_client);
+ return -1;
+ }
+
+ client->impl_data = ppp_client;
+ }
+
+ /* 解析URL获取host和path */
+ char host[256] = {0};
+ char path[256] = {0};
+ uint16_t port = 80;
+ bool use_ssl = false;
+
+ if (strncmp(url, "https://", 8) == 0) {
+ use_ssl = true;
+ port = 443;
+ sscanf(url + 8, "%255[^/]%255s", host, path);
+ } else if (strncmp(url, "http://", 7) == 0) {
+ sscanf(url + 7, "%255[^/]%255s", host, path);
+ } else {
+ return -1;
+ }
+
+ if (strlen(path) == 0) {
+ strcpy(path, "/");
+ }
+
+ /* 连接 */
+ n706a_httpc_result_t ret = n706a_http_client_connect(ppp_client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ return -1;
+ }
+
+ /* 准备数据结构 */
+ n706a_http_client_data_t ppp_data = {0};
+ ppp_data.responseBuf = data->responseBuf;
+ ppp_data.responseBufLen = data->responseBufLen;
+ ppp_data.headerBuf = data->headerBuf;
+ ppp_data.headerBufLen = data->headerBufLen;
+
+ /* 执行GET请求 */
+ ret = n706a_http_client_get(ppp_client, path, &ppp_data);
+
+ /* 设置响应码 */
+ client->responseCode = ppp_client->response_code;
+ data->responseContentLen = ppp_data.responseContentLen;
+
+ return (ret == N706A_HTTP_SUCCESS) ? 0 : -1;
+}
+
+int ZhihuiNetPppHttpPost(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ n706a_http_client_t *ppp_client = (n706a_http_client_t *)client->impl_data;
+
+ if (ppp_client == NULL) {
+ ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t));
+ if (ppp_client == NULL) {
+ return -1;
+ }
+
+ n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0);
+ if (ret != N706A_HTTP_SUCCESS) {
+ free(ppp_client);
+ return -1;
+ }
+
+ client->impl_data = ppp_client;
+ }
+
+ /* 解析URL */
+ char host[256] = {0};
+ char path[256] = {0};
+ uint16_t port = 80;
+ bool use_ssl = false;
+
+ if (strncmp(url, "https://", 8) == 0) {
+ use_ssl = true;
+ port = 443;
+ sscanf(url + 8, "%255[^/]%255s", host, path);
+ } else if (strncmp(url, "http://", 7) == 0) {
+ sscanf(url + 7, "%255[^/]%255s", host, path);
+ } else {
+ return -1;
+ }
+
+ if (strlen(path) == 0) {
+ strcpy(path, "/");
+ }
+
+ /* 连接 */
+ n706a_httpc_result_t ret = n706a_http_client_connect(ppp_client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ return -1;
+ }
+
+ /* 准备数据结构 */
+ n706a_http_client_data_t ppp_data = {0};
+ ppp_data.responseBuf = data->responseBuf;
+ ppp_data.responseBufLen = data->responseBufLen;
+ ppp_data.headerBuf = data->headerBuf;
+ ppp_data.headerBufLen = data->headerBufLen;
+
+ /* 执行POST请求 */
+ const char *content_type = data->postContentType ? data->postContentType : "application/json";
+ ret = n706a_http_client_post(ppp_client, path, content_type,
+ data->postBuf, data->postBufLen, &ppp_data);
+
+ client->responseCode = ppp_client->response_code;
+ data->responseContentLen = ppp_data.responseContentLen;
+
+ return (ret == N706A_HTTP_SUCCESS) ? 0 : -1;
+}
+
+int ZhihuiNetPppHttpHead(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ /* N706A暂不直接支持HEAD,使用GET实现 */
+ return ZhihuiNetPppHttpGet(client, url, data);
+}
+
+int ZhihuiNetPppHttpPut(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ n706a_http_client_t *ppp_client = (n706a_http_client_t *)client->impl_data;
+
+ if (ppp_client == NULL) {
+ ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t));
+ if (ppp_client == NULL) {
+ return -1;
+ }
+
+ n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0);
+ if (ret != N706A_HTTP_SUCCESS) {
+ free(ppp_client);
+ return -1;
+ }
+
+ client->impl_data = ppp_client;
+ }
+
+ /* 解析URL */
+ char host[256] = {0};
+ char path[256] = {0};
+ uint16_t port = 80;
+ bool use_ssl = false;
+
+ if (strncmp(url, "https://", 8) == 0) {
+ use_ssl = true;
+ port = 443;
+ sscanf(url + 8, "%255[^/]%255s", host, path);
+ } else if (strncmp(url, "http://", 7) == 0) {
+ sscanf(url + 7, "%255[^/]%255s", host, path);
+ } else {
+ return -1;
+ }
+
+ if (strlen(path) == 0) {
+ strcpy(path, "/");
+ }
+
+ /* 连接 */
+ n706a_httpc_result_t ret = n706a_http_client_connect(ppp_client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ return -1;
+ }
+
+ /* 准备数据结构 */
+ n706a_http_client_data_t ppp_data = {0};
+ ppp_data.responseBuf = data->responseBuf;
+ ppp_data.responseBufLen = data->responseBufLen;
+ ppp_data.headerBuf = data->headerBuf;
+ ppp_data.headerBufLen = data->headerBufLen;
+
+ /* 执行PUT请求 */
+ const char *content_type = data->postContentType ? data->postContentType : "application/json";
+ ret = n706a_http_client_put(ppp_client, path, content_type,
+ data->postBuf, data->postBufLen, &ppp_data);
+
+ client->responseCode = ppp_client->response_code;
+ data->responseContentLen = ppp_data.responseContentLen;
+
+ return (ret == N706A_HTTP_SUCCESS) ? 0 : -1;
+}
+
+int ZhihuiNetPppHttpDelete(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data)
+{
+ n706a_http_client_t *ppp_client = (n706a_http_client_t *)client->impl_data;
+
+ if (ppp_client == NULL) {
+ ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t));
+ if (ppp_client == NULL) {
+ return -1;
+ }
+
+ n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0);
+ if (ret != N706A_HTTP_SUCCESS) {
+ free(ppp_client);
+ return -1;
+ }
+
+ client->impl_data = ppp_client;
+ }
+
+ /* 解析URL */
+ char host[256] = {0};
+ char path[256] = {0};
+ uint16_t port = 80;
+ bool use_ssl = false;
+
+ if (strncmp(url, "https://", 8) == 0) {
+ use_ssl = true;
+ port = 443;
+ sscanf(url + 8, "%255[^/]%255s", host, path);
+ } else if (strncmp(url, "http://", 7) == 0) {
+ sscanf(url + 7, "%255[^/]%255s", host, path);
+ } else {
+ return -1;
+ }
+
+ if (strlen(path) == 0) {
+ strcpy(path, "/");
+ }
+
+ /* 连接 */
+ n706a_httpc_result_t ret = n706a_http_client_connect(ppp_client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ return -1;
+ }
+
+ /* 准备数据结构 */
+ n706a_http_client_data_t ppp_data = {0};
+ ppp_data.responseBuf = data->responseBuf;
+ ppp_data.responseBufLen = data->responseBufLen;
+ ppp_data.headerBuf = data->headerBuf;
+ ppp_data.headerBufLen = data->headerBufLen;
+
+ /* 执行DELETE请求 */
+ ret = n706a_http_client_delete(ppp_client, path, &ppp_data);
+
+ client->responseCode = ppp_client->response_code;
+ data->responseContentLen = ppp_data.responseContentLen;
+
+ return (ret == N706A_HTTP_SUCCESS) ? 0 : -1;
+}
+
+/* ========== WebSocket接口适配 ========== */
+
+#define MAX_WS_CLIENTS 6 /* N706A支持最多6个并发连接 */
+
+typedef struct {
+ int client_id;
+ bool in_use;
+ ZhihuiWebSocketOnOpenCallback zhihui_on_open;
+ ZhihuiWebSocketOnMessageCallback zhihui_on_message;
+ ZhihuiWebSocketOnErrorCallback zhihui_on_error;
+ ZhihuiWebSocketOnCloseCallback zhihui_on_close;
+} ppp_ws_adapter_t;
+
+static ppp_ws_adapter_t g_ppp_ws_adapters[MAX_WS_CLIENTS];
+
+/* PPP回调转发到Zhihui回调 */
+static void ppp_ws_on_open(int client_id)
+{
+ if (client_id >= 0 && client_id < MAX_WS_CLIENTS) {
+ if (g_ppp_ws_adapters[client_id].in_use &&
+ g_ppp_ws_adapters[client_id].zhihui_on_open) {
+ g_ppp_ws_adapters[client_id].zhihui_on_open(client_id);
+ }
+ }
+}
+
+static void ppp_ws_on_message(int client_id, const char *data, size_t length,
+ bool isFinished, bool isBinary)
+{
+ if (client_id >= 0 && client_id < MAX_WS_CLIENTS) {
+ if (g_ppp_ws_adapters[client_id].in_use &&
+ g_ppp_ws_adapters[client_id].zhihui_on_message) {
+ g_ppp_ws_adapters[client_id].zhihui_on_message(client_id, data,
+ length, isFinished, isBinary);
+ }
+ }
+}
+
+static void ppp_ws_on_error(int client_id, int errorCode, const char *errorMsg)
+{
+ if (client_id >= 0 && client_id < MAX_WS_CLIENTS) {
+ if (g_ppp_ws_adapters[client_id].in_use &&
+ g_ppp_ws_adapters[client_id].zhihui_on_error) {
+ g_ppp_ws_adapters[client_id].zhihui_on_error(client_id, errorCode, errorMsg);
+ }
+ }
+}
+
+static void ppp_ws_on_close(int client_id, int closeCode, const char *reason)
+{
+ if (client_id >= 0 && client_id < MAX_WS_CLIENTS) {
+ if (g_ppp_ws_adapters[client_id].in_use &&
+ g_ppp_ws_adapters[client_id].zhihui_on_close) {
+ g_ppp_ws_adapters[client_id].zhihui_on_close(client_id, closeCode, reason);
+ }
+ }
+}
+
+int ZhihuiNetPppWsConstructor(ZhihuiWebSocketOnOpenCallback onOpen,
+ ZhihuiWebSocketOnMessageCallback onMessage,
+ ZhihuiWebSocketOnErrorCallback onError,
+ ZhihuiWebSocketOnCloseCallback onClose)
+{
+ int client_id = n706a_websocket_constructor(
+ ppp_ws_on_open,
+ ppp_ws_on_message,
+ ppp_ws_on_error,
+ ppp_ws_on_close
+ );
+
+ if (client_id >= 0 && client_id < MAX_WS_CLIENTS) {
+ g_ppp_ws_adapters[client_id].client_id = client_id;
+ g_ppp_ws_adapters[client_id].in_use = true;
+ g_ppp_ws_adapters[client_id].zhihui_on_open = onOpen;
+ g_ppp_ws_adapters[client_id].zhihui_on_message = onMessage;
+ g_ppp_ws_adapters[client_id].zhihui_on_error = onError;
+ g_ppp_ws_adapters[client_id].zhihui_on_close = onClose;
+ }
+
+ return client_id;
+}
+
+int ZhihuiNetPppWsConnect(int client, const char *url, const char *option)
+{
+ if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) {
+ return -1;
+ }
+
+ return n706a_websocket_connect(client, url, option);
+}
+
+int ZhihuiNetPppWsSendText(int client, const char *text, size_t length)
+{
+ if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) {
+ return -1;
+ }
+
+ return n706a_websocket_send_text(client, text, length);
+}
+
+int ZhihuiNetPppWsSendBinStart(int client, const char *data, size_t length)
+{
+ if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) {
+ return -1;
+ }
+
+ return n706a_websocket_send_binary(client, (const uint8_t *)data, length);
+}
+
+int ZhihuiNetPppWsSendBinContinue(int client, const char *data, size_t length)
+{
+ /* N706A一次发送完整帧,不支持分段 */
+ return 0;
+}
+
+int ZhihuiNetPppWsSendBinEnd(int client, const char *data, size_t length)
+{
+ /* N706A一次发送完整帧,不支持分段 */
+ return 0;
+}
+
+int ZhihuiNetPppWsClose(int client)
+{
+ if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) {
+ return -1;
+ }
+
+ return n706a_websocket_close(client);
+}
+
+int ZhihuiNetPppWsDestructor(int client)
+{
+ if (client < 0 || client >= MAX_WS_CLIENTS || !g_ppp_ws_adapters[client].in_use) {
+ return -1;
+ }
+
+ int ret = n706a_websocket_destructor(client);
+
+ if (ret == 0) {
+ memset(&g_ppp_ws_adapters[client], 0, sizeof(ppp_ws_adapter_t));
+ g_ppp_ws_adapters[client].in_use = false;
+ }
+
+ return ret;
+}
+
+/* ========== 初始化/清理 ========== */
+
+int ZhihuiNetPppInit(void)
+{
+ printf("[ZhihuiNet][PPP] Adapter initialized\n");
+ memset(g_ppp_ws_adapters, 0, sizeof(g_ppp_ws_adapters));
+ return 0;
+}
+
+int ZhihuiNetPppDeinit(void)
+{
+ printf("[ZhihuiNet][PPP] Adapter deinitialized\n");
+
+ /* 清理所有WebSocket客户端 */
+ for (int i = 0; i < MAX_WS_CLIENTS; i++) {
+ if (g_ppp_ws_adapters[i].in_use) {
+ ZhihuiNetPppWsDestructor(i);
+ }
+ }
+
+ return 0;
+}
--- /dev/null
+/**
+ * @file zhihui_net_ppp_adapter.h
+ * @brief N706A PPP网络实现适配器
+ */
+
+#ifndef ZHIHUI_NET_PPP_ADAPTER_H
+#define ZHIHUI_NET_PPP_ADAPTER_H
+
+#include "zhihui_net.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* HTTP接口 */
+int ZhihuiNetPppHttpGet(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+int ZhihuiNetPppHttpPost(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+int ZhihuiNetPppHttpHead(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+int ZhihuiNetPppHttpPut(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+int ZhihuiNetPppHttpDelete(ZhihuiHttpClient *client, const char *url,
+ ZhihuiHttpClientData *data);
+
+/* WebSocket接口 */
+int ZhihuiNetPppWsConstructor(ZhihuiWebSocketOnOpenCallback onOpen,
+ ZhihuiWebSocketOnMessageCallback onMessage,
+ ZhihuiWebSocketOnErrorCallback onError,
+ ZhihuiWebSocketOnCloseCallback onClose);
+int ZhihuiNetPppWsConnect(int client, const char *url, const char *option);
+int ZhihuiNetPppWsSendText(int client, const char *text, size_t length);
+int ZhihuiNetPppWsSendBinStart(int client, const char *data, size_t length);
+int ZhihuiNetPppWsSendBinContinue(int client, const char *data, size_t length);
+int ZhihuiNetPppWsSendBinEnd(int client, const char *data, size_t length);
+int ZhihuiNetPppWsClose(int client);
+int ZhihuiNetPppWsDestructor(int client);
+
+/* 初始化/清理 */
+int ZhihuiNetPppInit(void);
+int ZhihuiNetPppDeinit(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZHIHUI_NET_PPP_ADAPTER_H */
+
--- /dev/null
+/**
+ * @file zhihui_net_http_example.c
+ * @brief ZhihuiNet HTTP客户端使用示例
+ */
+
+#include "zhihui_net.h"
+#include <stdio.h>
+#include <string.h>
+
+/* HTTP请求缓冲区 */
+#define REQ_BUF_SIZE 2048
+static char req_buf[REQ_BUF_SIZE];
+
+/* HTTP响应缓冲区 */
+#define RSP_BUF_SIZE 4096
+static char rsp_buf[RSP_BUF_SIZE];
+
+/**
+ * @brief HTTP GET请求示例
+ */
+static int http_get_example(void)
+{
+ ZhihuiHttpClient client = {0};
+ ZhihuiHttpClientData client_data = {0};
+ const char *url = "http://www.baidu.com/";
+ int ret;
+
+ printf("\n=== HTTP GET Example ===\n");
+ printf("URL: %s\n", url);
+
+ /* 准备请求头缓冲区 */
+ memset(req_buf, 0, sizeof(req_buf));
+ client_data.headerBuf = req_buf;
+ client_data.headerBufLen = sizeof(req_buf);
+
+ /* 准备响应缓冲区 */
+ memset(rsp_buf, 0, sizeof(rsp_buf));
+ client_data.responseBuf = rsp_buf;
+ client_data.responseBufLen = sizeof(rsp_buf);
+
+ /* 发送GET请求 */
+ ret = ZhihuiHttpClientGetRequest(&client, url, &client_data);
+
+ if (ret >= 0 && client.responseCode == 200) {
+ printf("✓ Request successful\n");
+ printf("Status code: %d\n", client.responseCode);
+ printf("Response length: %d\n", client_data.responseContentLen);
+ printf("Response preview: %.100s...\n", client_data.responseBuf);
+ return 0;
+ } else {
+ printf("✗ Request failed: %d\n", ret);
+ printf("Status code: %d\n", client.responseCode);
+ return -1;
+ }
+}
+
+/**
+ * @brief HTTP POST请求示例
+ */
+static int http_post_example(void)
+{
+ ZhihuiHttpClient client = {0};
+ ZhihuiHttpClientData client_data = {0};
+ const char *url = "http://httpbin.org/post";
+ const char *post_data = "{\"message\": \"Hello from ZhihuiNet!\"}";
+ int ret;
+
+ printf("\n=== HTTP POST Example ===\n");
+ printf("URL: %s\n", url);
+ printf("POST data: %s\n", post_data);
+
+ /* 准备请求头缓冲区 */
+ memset(req_buf, 0, sizeof(req_buf));
+ client_data.headerBuf = req_buf;
+ client_data.headerBufLen = sizeof(req_buf);
+
+ /* 准备POST数据 */
+ client_data.postBuf = (char *)post_data;
+ client_data.postBufLen = strlen(post_data);
+ client_data.postContentType = "application/json";
+
+ /* 准备响应缓冲区 */
+ memset(rsp_buf, 0, sizeof(rsp_buf));
+ client_data.responseBuf = rsp_buf;
+ client_data.responseBufLen = sizeof(rsp_buf);
+
+ /* 发送POST请求 */
+ ret = ZhihuiHttpClientPostRequest(&client, url, &client_data);
+
+ if (ret >= 0 && client.responseCode == 200) {
+ printf("✓ Request successful\n");
+ printf("Status code: %d\n", client.responseCode);
+ printf("Response: %s\n", client_data.responseBuf);
+ return 0;
+ } else {
+ printf("✗ Request failed: %d\n", ret);
+ printf("Status code: %d\n", client.responseCode);
+ return -1;
+ }
+}
+
+/**
+ * @brief HTTPS请求示例
+ */
+static int https_get_example(void)
+{
+ ZhihuiHttpClient client = {0};
+ ZhihuiHttpClientData client_data = {0};
+ const char *url = "https://www.baidu.com/";
+ int ret;
+
+ printf("\n=== HTTPS GET Example ===\n");
+ printf("URL: %s\n", url);
+
+ /* 准备请求头缓冲区 */
+ memset(req_buf, 0, sizeof(req_buf));
+ client_data.headerBuf = req_buf;
+ client_data.headerBufLen = sizeof(req_buf);
+
+ /* 准备响应缓冲区 */
+ memset(rsp_buf, 0, sizeof(rsp_buf));
+ client_data.responseBuf = rsp_buf;
+ client_data.responseBufLen = sizeof(rsp_buf);
+
+ /* 设置为HTTPS */
+ client.isHttp = false; /* false表示HTTPS */
+
+ /* 发送GET请求 */
+ ret = ZhihuiHttpClientGetRequest(&client, url, &client_data);
+
+ if (ret >= 0 && client.responseCode == 200) {
+ printf("✓ Request successful\n");
+ printf("Status code: %d\n", client.responseCode);
+ printf("Response length: %d\n", client_data.responseContentLen);
+ return 0;
+ } else {
+ printf("✗ Request failed: %d\n", ret);
+ printf("Status code: %d\n", client.responseCode);
+ return -1;
+ }
+}
+
+/**
+ * @brief 测试两种实现
+ */
+static void test_both_implementations(void)
+{
+ printf("\n╔════════════════════════════════════════╗\n");
+ printf("║ Testing OpenHarmony Implementation ║\n");
+ printf("╚════════════════════════════════════════╝\n");
+
+ ZhihuiNetSetImplementation(ZHIHUI_NET_IMPL_OPENHARMONY);
+ ZhihuiNetInit();
+
+ http_get_example();
+
+ ZhihuiNetDeinit();
+
+ printf("\n╔════════════════════════════════════════╗\n");
+ printf("║ Testing N706A PPP Implementation ║\n");
+ printf("╚════════════════════════════════════════╝\n");
+
+ ZhihuiNetSetImplementation(ZHIHUI_NET_IMPL_N706A_PPP);
+ ZhihuiNetInit();
+
+ http_get_example();
+
+ ZhihuiNetDeinit();
+}
+
+/**
+ * @brief 主函数
+ */
+int main(void)
+{
+ printf("╔════════════════════════════════════════╗\n");
+ printf("║ ZhihuiNet HTTP Client Examples ║\n");
+ printf("╚════════════════════════════════════════╝\n");
+
+ /* 初始化(使用默认实现:OpenHarmony) */
+ ZhihuiNetInit();
+
+ /* 运行示例 */
+ http_get_example();
+ http_post_example();
+ https_get_example();
+
+ /* 清理 */
+ ZhihuiNetDeinit();
+
+ /* 测试两种实现 */
+ test_both_implementations();
+
+ printf("\n✓ All examples completed\n");
+
+ return 0;
+}
+
--- /dev/null
+/**
+ * @file zhihui_net_ws_example.c
+ * @brief ZhihuiNet WebSocket客户端使用示例
+ */
+
+#include "zhihui_net.h"
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+/* WebSocket客户端状态 */
+static int g_ws_client_fd = -1;
+static volatile bool g_ws_connected = false;
+static volatile bool g_ws_received = false;
+
+/**
+ * @brief WebSocket连接打开回调
+ */
+static void on_open(int client)
+{
+ printf("[WS] Connection opened (client: %d)\n", client);
+ g_ws_connected = true;
+
+ /* 连接成功后发送消息 */
+ const char *message = "Hello WebSocket Server!";
+ printf("[WS] Sending: %s\n", message);
+
+ int ret = ZhihuiWebSocketClientSendText(client, message, strlen(message));
+ if (ret != 0) {
+ printf("[WS] Failed to send message: %d\n", ret);
+ }
+}
+
+/**
+ * @brief WebSocket消息接收回调
+ */
+static void on_message(int client, const char *data, size_t length,
+ bool isFinished, bool isBinary)
+{
+ printf("[WS] Message received (client: %d, length: %zu, binary: %d)\n",
+ client, length, isBinary);
+
+ if (isBinary) {
+ printf("[WS] Binary data: ");
+ for (size_t i = 0; i < length && i < 32; i++) {
+ printf("%02X ", (unsigned char)data[i]);
+ }
+ printf("\n");
+ } else {
+ printf("[WS] Text data: %.*s\n", (int)length, data);
+ }
+
+ g_ws_received = true;
+}
+
+/**
+ * @brief WebSocket错误回调
+ */
+static void on_error(int client, int errorCode, const char *errorMsg)
+{
+ printf("[WS] Error (client: %d, code: %d): %s\n",
+ client, errorCode, errorMsg ? errorMsg : "Unknown error");
+}
+
+/**
+ * @brief WebSocket连接关闭回调
+ */
+static void on_close(int client, int closeCode, const char *reason)
+{
+ printf("[WS] Connection closed (client: %d, code: %d): %s\n",
+ client, closeCode, reason ? reason : "");
+ g_ws_connected = false;
+}
+
+/**
+ * @brief WebSocket基本示例
+ */
+static int websocket_basic_example(void)
+{
+ printf("\n=== WebSocket Basic Example ===\n");
+
+ /* 创建WebSocket客户端 */
+ int client = ZhihuiWebSocketClientConstructor(
+ on_open,
+ on_message,
+ on_error,
+ on_close
+ );
+
+ if (client < 0) {
+ printf("✗ Failed to create WebSocket client\n");
+ return -1;
+ }
+
+ printf("✓ WebSocket client created (id: %d)\n", client);
+ g_ws_client_fd = client;
+
+ /* 连接到echo服务器 */
+ const char *url = "ws://echo.websocket.org/";
+ printf("Connecting to: %s\n", url);
+
+ int ret = ZhihuiWebSocketClientConnect(client, url, NULL);
+ if (ret != 0) {
+ printf("✗ Failed to connect: %d\n", ret);
+ ZhihuiWebSocketClientDestructor(client);
+ return -1;
+ }
+
+ /* 等待连接和消息 */
+ printf("Waiting for connection and messages...\n");
+ int timeout = 30; /* 30秒超时 */
+ while (timeout > 0 && (!g_ws_connected || !g_ws_received)) {
+ sleep(1);
+ timeout--;
+ }
+
+ if (g_ws_connected && g_ws_received) {
+ printf("✓ WebSocket communication successful\n");
+ } else {
+ printf("✗ Timeout waiting for WebSocket communication\n");
+ }
+
+ /* 关闭连接 */
+ printf("Closing WebSocket connection...\n");
+ ZhihuiWebSocketClientClose(client);
+ sleep(1);
+
+ /* 销毁客户端 */
+ ZhihuiWebSocketClientDestructor(client);
+ g_ws_client_fd = -1;
+
+ return 0;
+}
+
+/**
+ * @brief WebSocket二进制数据示例
+ */
+static int websocket_binary_example(void)
+{
+ printf("\n=== WebSocket Binary Example ===\n");
+
+ g_ws_connected = false;
+ g_ws_received = false;
+
+ /* 创建WebSocket客户端 */
+ int client = ZhihuiWebSocketClientConstructor(
+ on_open,
+ on_message,
+ on_error,
+ on_close
+ );
+
+ if (client < 0) {
+ printf("✗ Failed to create WebSocket client\n");
+ return -1;
+ }
+
+ g_ws_client_fd = client;
+
+ /* 连接 */
+ const char *url = "ws://echo.websocket.org/";
+ int ret = ZhihuiWebSocketClientConnect(client, url, NULL);
+ if (ret != 0) {
+ printf("✗ Failed to connect: %d\n", ret);
+ ZhihuiWebSocketClientDestructor(client);
+ return -1;
+ }
+
+ /* 等待连接 */
+ int timeout = 10;
+ while (timeout > 0 && !g_ws_connected) {
+ sleep(1);
+ timeout--;
+ }
+
+ if (!g_ws_connected) {
+ printf("✗ Connection timeout\n");
+ ZhihuiWebSocketClientDestructor(client);
+ return -1;
+ }
+
+ /* 发送二进制数据 */
+ uint8_t binary_data[] = {0x01, 0x02, 0x03, 0x04, 0x05};
+ printf("Sending binary data...\n");
+
+ ret = ZhihuiWebSocketClientSendBinStart(client, (const char *)binary_data,
+ sizeof(binary_data));
+ if (ret != 0) {
+ printf("✗ Failed to send binary data: %d\n", ret);
+ }
+
+ /* 等待响应 */
+ timeout = 10;
+ while (timeout > 0 && !g_ws_received) {
+ sleep(1);
+ timeout--;
+ }
+
+ /* 清理 */
+ ZhihuiWebSocketClientClose(client);
+ ZhihuiWebSocketClientDestructor(client);
+
+ return 0;
+}
+
+/**
+ * @brief WSS (WebSocket Secure)示例
+ */
+static int websocket_secure_example(void)
+{
+ printf("\n=== WebSocket Secure (WSS) Example ===\n");
+
+ g_ws_connected = false;
+ g_ws_received = false;
+
+ /* 创建WebSocket客户端 */
+ int client = ZhihuiWebSocketClientConstructor(
+ on_open,
+ on_message,
+ on_error,
+ on_close
+ );
+
+ if (client < 0) {
+ printf("✗ Failed to create WebSocket client\n");
+ return -1;
+ }
+
+ g_ws_client_fd = client;
+
+ /* 连接到WSS服务器 */
+ const char *url = "wss://echo.websocket.org/";
+ printf("Connecting to: %s\n", url);
+
+ int ret = ZhihuiWebSocketClientConnect(client, url, NULL);
+ if (ret != 0) {
+ printf("✗ Failed to connect: %d\n", ret);
+ ZhihuiWebSocketClientDestructor(client);
+ return -1;
+ }
+
+ /* 等待连接和消息 */
+ int timeout = 30;
+ while (timeout > 0 && (!g_ws_connected || !g_ws_received)) {
+ sleep(1);
+ timeout--;
+ }
+
+ if (g_ws_connected && g_ws_received) {
+ printf("✓ WSS communication successful\n");
+ }
+
+ /* 清理 */
+ ZhihuiWebSocketClientClose(client);
+ ZhihuiWebSocketClientDestructor(client);
+
+ return 0;
+}
+
+/**
+ * @brief 主函数
+ */
+int main(void)
+{
+ printf("╔════════════════════════════════════════╗\n");
+ printf("║ ZhihuiNet WebSocket Client Examples ║\n");
+ printf("╚════════════════════════════════════════╝\n");
+
+ /* 初始化 */
+ ZhihuiNetInit();
+
+ /* 运行示例 */
+ websocket_basic_example();
+ websocket_binary_example();
+ websocket_secure_example();
+
+ /* 清理 */
+ ZhihuiNetDeinit();
+
+ printf("\n✓ All WebSocket examples completed\n");
+
+ return 0;
+}
+
--- /dev/null
+/**
+ * @file zhihui_net.h
+ * @brief 智慧网络统一接口层 - 总入口
+ *
+ * 设计原则:
+ * 1. 拆分 HTTP 和 WebSocket 接口到独立的头文件
+ * 2. HTTP 接口:基于 OpenHarmony 的 httpclient.h
+ * 3. WebSocket 接口:基于 haisi_websocket.h
+ * 4. 支持运行时动态切换实现(OpenHarmony/haisi_websocket vs PPP)
+ *
+ * @version 4.0.0
+ * @date 2025-10-31
+ */
+
+#ifndef ZHIHUI_NET_H
+#define ZHIHUI_NET_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* 包含 HTTP 和 WebSocket 子模块 */
+#include "zhihui_net_http.h"
+#include "zhihui_net_websocket.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ========== 网络实现类型 ========== */
+
+typedef enum {
+ ZHIHUI_NET_IMPL_OPENHARMONY = 0, /**< OpenHarmony实现(PPP拨号 + lwIP + haisi_websocket)默认 */
+ ZHIHUI_NET_IMPL_N706A_AT = 1 /**< N706A AT指令实现(不拨号,纯AT指令)*/
+} zhihui_net_impl_type_t;
+
+/* 为了兼容性保留旧名称 */
+#define ZHIHUI_NET_IMPL_N706A_PPP ZHIHUI_NET_IMPL_OPENHARMONY
+
+/* ========== 配置接口 ========== */
+
+/**
+ * @brief 设置网络实现类型
+ * @param type 实现类型
+ * @return 0成功,非0失败
+ */
+int ZhihuiNetSetImplementation(zhihui_net_impl_type_t type);
+
+/**
+ * @brief 获取当前网络实现类型
+ * @return 实现类型
+ */
+zhihui_net_impl_type_t ZhihuiNetGetImplementation(void);
+
+/**
+ * @brief 初始化网络模块
+ * @return 0成功,非0失败
+ */
+int ZhihuiNetInit(void);
+
+/**
+ * @brief 反初始化网络模块
+ * @return 0成功,非0失败
+ */
+int ZhihuiNetDeinit(void);
+
+/*
+ * 注意:
+ * - HTTP 接口定义在 zhihui_net_http.h
+ * - WebSocket 接口定义在 zhihui_net_websocket.h
+ * - 包含 zhihui_net.h 即可使用所有接口
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZHIHUI_NET_H */
--- /dev/null
+/**
+ * @file zhihui_net_http.h
+ * @brief 智慧网络统一接口层 - HTTP/HTTPS 接口
+ *
+ * 提供统一的 HTTP/HTTPS 客户端接口,支持动态切换 OpenHarmony 和 PPP 实现
+ *
+ * @version 1.0.0
+ * @date 2025-10-31
+ */
+
+#ifndef ZHIHUI_NET_HTTP_H
+#define ZHIHUI_NET_HTTP_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* 直接复用 OpenHarmony 的 HTTP 类型定义 */
+#include "httpclient.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ========== HTTP 客户端接口 ========== */
+
+/**
+ * @brief 准备HTTP客户端数据缓冲区
+ * @param clientData 客户端数据
+ * @param headerBufLen 头部缓冲区大小
+ * @param responseBufLen 响应缓冲区大小
+ * @return HTTP_SUCCESS成功,其他失败
+ */
+int ZhihuiHttpClientPrepare(HttpClientData *clientData, int headerBufLen, int responseBufLen);
+
+/**
+ * @brief 释放HTTP客户端数据缓冲区
+ * @param clientData 客户端数据
+ */
+void ZhihuiHttpClientUnprepare(HttpClientData *clientData);
+
+/**
+ * @brief 连接到HTTP服务器
+ * @param client HTTP客户端
+ * @param url 服务器URL
+ * @return 0成功,非0失败
+ */
+int ZhihuiHttpClientConn(HttpClient *client, const char *url);
+
+/**
+ * @brief 发送HTTP请求
+ * @param client HTTP客户端
+ * @param url 请求URL
+ * @param method HTTP方法 (HTTP_GET, HTTP_POST等)
+ * @param clientData 请求数据
+ * @return 0成功,非0失败
+ */
+int ZhihuiHttpClientSend(HttpClient *client, const char *url, int method, HttpClientData *clientData);
+
+/**
+ * @brief 接收HTTP响应
+ * @param client HTTP客户端
+ * @param clientData 响应数据
+ * @return HTTP_SUCCESS成功,HTTP_EAGAIN需继续接收,其他失败
+ */
+int ZhihuiHttpClientRecvResponse(HttpClient *client, HttpClientData *clientData);
+
+/**
+ * @brief 关闭HTTP连接
+ * @param client HTTP客户端
+ */
+void ZhihuiHttpClientClse(HttpClient *client);
+
+/**
+ * @brief 设置自定义HTTP头部
+ * @param client HTTP客户端
+ * @param header 自定义头部字符串
+ */
+void ZhihuiHttpClientSetCustomHeader(HttpClient *client, char *header);
+
+/**
+ * @brief 执行HTTP GET请求(便捷函数)
+ * @param client HTTP客户端
+ * @param url 请求URL
+ * @param clientData 响应数据
+ * @return HTTP_SUCCESS成功,其他失败
+ */
+int ZhihuiHttpClientGetRequest(HttpClient *client, const char *url, HttpClientData *clientData);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZHIHUI_NET_HTTP_H */
+
--- /dev/null
+/**
+ * @file zhihui_net_websocket.h
+ * @brief 智慧网络统一接口层 - WebSocket 接口
+ *
+ * 提供统一的 WebSocket 客户端接口,支持动态切换 haisi_websocket 和 PPP 实现
+ * 接口设计完全遵循 haisi_websocket.h 的定义
+ *
+ * @version 1.0.0
+ * @date 2025-10-31
+ */
+
+#ifndef ZHIHUI_NET_WEBSOCKET_H
+#define ZHIHUI_NET_WEBSOCKET_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stddef.h>
+
+/* 直接复用 haisi_websocket 的类型定义 */
+#include "haisi_websocket.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* ========== WebSocket 客户端接口(与 haisi_websocket.h 完全一致)========== */
+
+/**
+ * @brief 启动 WebSocket 服务
+ * @param callbacks 回调函数结构体
+ * @return 0成功,非0失败
+ */
+int ZhihuiWsStart(const ws_call_backs_t *callbacks);
+
+/**
+ * @brief 发送文本消息(null-terminated 字符串)
+ * @param text 文本字符串
+ * @return 0成功,非0失败
+ */
+int ZhihuiWsSendText(const char *text);
+
+/**
+ * @brief 停止 WebSocket 服务
+ * @return 0成功,非0失败
+ */
+int ZhihuiWsStop(void);
+
+/**
+ * @brief 异步停止 WebSocket 服务
+ * @return 0成功,非0失败
+ */
+int ZhihuiWsStopAsync(void);
+
+/**
+ * @brief 强制重连
+ * @return 0成功,非0失败
+ */
+int ZhihuiWsForceReconnect(void);
+
+/**
+ * @brief 进入睡眠模式
+ * @return 0成功,非0失败
+ */
+int ZhihuiWsEnterSleep(void);
+
+/**
+ * @brief 退出睡眠模式
+ * @return 0成功,非0失败
+ */
+int ZhihuiWsExitSleep(void);
+
+/**
+ * @brief 发送二进制消息
+ * @param data 数据
+ * @param len 数据长度
+ * @return 0成功,非0失败
+ */
+int ZhihuiWsSendBIN(uint8_t *data, int len);
+
+/* ========== WebSocket 状态查询接口 ========== */
+
+/**
+ * @brief 获取连接状态
+ * @return 连接状态值
+ */
+int ZhihuiWsGetConnectionState(void);
+
+/**
+ * @brief 获取连接状态字符串
+ * @return 状态字符串
+ */
+const char* ZhihuiWsGetConnectionStateString(void);
+
+/**
+ * @brief 获取关闭状态
+ * @return 关闭状态值
+ */
+int ZhihuiWsGetCloseStatusPublic(void);
+
+/**
+ * @brief 打印状态信息
+ */
+void ZhihuiWsPrintStatusInfo(void);
+
+/**
+ * @brief 获取主线程循环间隔
+ * @return 间隔时长(毫秒)
+ */
+uint32_t ZhihuiWsGetMainThreadInterval(void);
+
+/* ========== 回调函数注册接口 ========== */
+
+/**
+ * @brief 注册文本消息回调
+ * @param callback 回调函数
+ */
+void ZhihuiWsRegisterMessageCallback(ws_message_callback_t callback);
+
+/**
+ * @brief 注册二进制消息回调
+ * @param callback 回调函数
+ */
+void ZhihuiWsRegisterBinaryCallback(ws_binary_message_callback_t callback);
+
+/**
+ * @brief 注册不完整消息回调
+ * @param callback 回调函数
+ */
+void ZhihuiWsRegisterIncompleteCallback(ws_incomplete_message_callback_t callback);
+
+/**
+ * @brief 注册 URL 构建回调
+ * @param callback 回调函数
+ */
+void ZhihuiWsRegisterUrlCallback(ws_url_callback_t callback);
+
+/**
+ * @brief 注册心跳回调
+ * @param callback 回调函数
+ */
+void ZhihuiWsRegisterHeartbeatCallback(ws_heartbeat_callback_t callback);
+
+/**
+ * @brief 注册连接事件回调
+ * @param callback 回调函数
+ */
+void ZhihuiWsRegisterEventCallback(ws_event_callback_t callback);
+
+/**
+ * @brief 注册重连成功回调
+ * @param callback 回调函数
+ */
+void ZhihuiWsRegisterReconnectSuccessCallback(ws_reconnect_success_callback_t callback);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZHIHUI_NET_WEBSOCKET_H */
+
--- /dev/null
+/**
+ * @file zhihui_net.c
+ * @brief 智慧网络统一接口层 - 配置实现
+ *
+ * 负责管理网络实现类型的配置和切换
+ */
+
+#include "zhihui_net.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* 当前使用的实现类型 */
+static zhihui_net_impl_type_t g_current_impl = ZHIHUI_NET_IMPL_OPENHARMONY;
+
+/* ========== 配置接口实现 ========== */
+
+int ZhihuiNetSetImplementation(zhihui_net_impl_type_t type)
+{
+ if (type != ZHIHUI_NET_IMPL_OPENHARMONY && type != ZHIHUI_NET_IMPL_N706A_AT) {
+ return -1;
+ }
+
+ g_current_impl = type;
+ printf("[ZhihuiNet] Switched to %s implementation\n",
+ (type == ZHIHUI_NET_IMPL_OPENHARMONY) ? "OpenHarmony (PPP + lwIP)" : "N706A AT Command");
+ return 0;
+}
+
+zhihui_net_impl_type_t ZhihuiNetGetImplementation(void)
+{
+ return g_current_impl;
+}
+
+int ZhihuiNetInit(void)
+{
+ /* 初始化网络模块 */
+ printf("[ZhihuiNet] Initialized with %s implementation\n",
+ (g_current_impl == ZHIHUI_NET_IMPL_OPENHARMONY) ? "OpenHarmony" : "PPP");
+ return 0;
+}
+
+int ZhihuiNetDeinit(void)
+{
+ /* 反初始化网络模块 */
+ printf("[ZhihuiNet] Deinitialized\n");
+ return 0;
+}
--- /dev/null
+/**
+ * @file zhihui_net_http.c
+ * @brief 智慧网络统一接口层 - HTTP/HTTPS 实现
+ *
+ * 支持动态切换 OpenHarmony 和 PPP 实现
+ */
+
+#include "zhihui_net_http.h"
+#include "zhihui_net.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* 包含 PPP 网络层的头文件 */
+#include "n706a_http_client.h"
+
+/* 外部函数:获取当前实现类型 */
+extern zhihui_net_impl_type_t ZhihuiNetGetImplementation(void);
+
+/* ========== HTTP 客户端接口实现 ========== */
+
+int ZhihuiHttpClientPrepare(HttpClientData *clientData, int headerBufLen, int responseBufLen)
+{
+ if (!clientData) {
+ return HTTP_ENOBUFS;
+ }
+
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 OpenHarmony 的实现 */
+ return HttpClientPrepare(clientData, headerBufLen, responseBufLen);
+ } else {
+ /* PPP 实现:手动分配缓冲区 */
+ clientData->headerBuf = (char *)malloc(headerBufLen);
+ if (!clientData->headerBuf) {
+ return HTTP_ENOBUFS;
+ }
+ clientData->headerBufLen = headerBufLen;
+ memset(clientData->headerBuf, 0, headerBufLen);
+
+ clientData->responseBuf = (char *)malloc(responseBufLen);
+ if (!clientData->responseBuf) {
+ free(clientData->headerBuf);
+ clientData->headerBuf = NULL;
+ return HTTP_ENOBUFS;
+ }
+ clientData->responseBufLen = responseBufLen;
+ memset(clientData->responseBuf, 0, responseBufLen);
+
+ /* 初始化其他字段 */
+ clientData->isMore = false;
+ clientData->isChunked = false;
+ clientData->method = 0;
+ clientData->retrieveLen = 0;
+ clientData->responseContentLen = 0;
+ clientData->contentBlockLen = 0;
+
+ return HTTP_SUCCESS;
+ }
+}
+
+void ZhihuiHttpClientUnprepare(HttpClientData *clientData)
+{
+ if (!clientData) {
+ return;
+ }
+
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 OpenHarmony 的实现 */
+ HttpClientUnprepare(clientData);
+ } else {
+ /* PPP 实现:手动释放缓冲区 */
+ if (clientData->headerBuf) {
+ free(clientData->headerBuf);
+ clientData->headerBuf = NULL;
+ }
+
+ if (clientData->responseBuf) {
+ free(clientData->responseBuf);
+ clientData->responseBuf = NULL;
+ }
+
+ memset(clientData, 0, sizeof(HttpClientData));
+ }
+}
+
+int ZhihuiHttpClientConn(HttpClient *client, const char *url)
+{
+ if (!client || !url) {
+ return -1;
+ }
+
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 OpenHarmony 的实现 */
+ return HttpClientConn(client, url);
+ } else {
+ /* PPP 实现:简单初始化 */
+ memset(client, 0, sizeof(HttpClient));
+ client->socket = -1;
+ client->isHttp = (strncmp(url, "https://", 8) != 0);
+ return 0;
+ }
+}
+
+int ZhihuiHttpClientSend(HttpClient *client, const char *url, int method, HttpClientData *clientData)
+{
+ if (!client || !url || !clientData) {
+ return -1;
+ }
+
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 OpenHarmony 的实现 */
+ return HttpClientSend(client, url, method, clientData);
+ } else {
+ /* PPP 实现:调用 n706a_http_client */
+
+ /* 解析 URL */
+ char host[256] = {0};
+ char path[256] = {0};
+ uint16_t port = client->isHttp ? 80 : 443;
+ bool use_ssl = !client->isHttp;
+
+ const char *start = url;
+ if (strncmp(url, "https://", 8) == 0) {
+ start = url + 8;
+ use_ssl = true;
+ port = 443;
+ } else if (strncmp(url, "http://", 7) == 0) {
+ start = url + 7;
+ use_ssl = false;
+ port = 80;
+ }
+
+ sscanf(start, "%255[^/]%255s", host, path);
+ if (strlen(path) == 0) {
+ strcpy(path, "/");
+ }
+
+ /* 创建 PPP HTTP 客户端 */
+ n706a_http_client_t *ppp_client = (n706a_http_client_t *)malloc(sizeof(n706a_http_client_t));
+ if (!ppp_client) {
+ return -1;
+ }
+
+ n706a_httpc_result_t ret = n706a_http_client_init(ppp_client, 0);
+ if (ret != N706A_HTTP_SUCCESS) {
+ free(ppp_client);
+ return -1;
+ }
+
+ /* 连接 */
+ ret = n706a_http_client_connect(ppp_client, host, port, use_ssl);
+ if (ret != N706A_HTTP_SUCCESS) {
+ free(ppp_client);
+ return -1;
+ }
+
+ /* 准备数据结构 */
+ n706a_http_client_data_t ppp_data = {0};
+ ppp_data.responseBuf = clientData->responseBuf;
+ ppp_data.responseBufLen = clientData->responseBufLen;
+ ppp_data.headerBuf = clientData->headerBuf;
+ ppp_data.headerBufLen = clientData->headerBufLen;
+
+ /* 根据方法执行请求 */
+ switch (method) {
+ case HTTP_GET:
+ ret = n706a_http_client_get(ppp_client, path, &ppp_data);
+ break;
+ case HTTP_POST:
+ if (clientData->postContentType && clientData->postBuf) {
+ ret = n706a_http_client_post(ppp_client, path,
+ clientData->postContentType,
+ clientData->postBuf,
+ clientData->postBufLen,
+ &ppp_data);
+ } else {
+ ret = N706A_HTTP_EARG;
+ }
+ break;
+ case HTTP_PUT:
+ if (clientData->postContentType && clientData->postBuf) {
+ ret = n706a_http_client_put(ppp_client, path,
+ clientData->postContentType,
+ clientData->postBuf,
+ clientData->postBufLen,
+ &ppp_data);
+ } else {
+ ret = N706A_HTTP_EARG;
+ }
+ break;
+ case HTTP_DELETE:
+ ret = n706a_http_client_delete(ppp_client, path, &ppp_data);
+ break;
+ default:
+ ret = N706A_HTTP_EARG;
+ break;
+ }
+
+ /* 设置响应码 */
+ client->responseCode = ppp_client->response_code;
+ clientData->responseContentLen = ppp_data.responseContentLen;
+ clientData->contentBlockLen = ppp_data.responseContentLen;
+ clientData->isMore = false;
+
+ /* 清理 PPP 客户端 */
+ n706a_http_client_close(ppp_client);
+ free(ppp_client);
+
+ return (ret == N706A_HTTP_SUCCESS) ? 0 : -1;
+ }
+}
+
+int ZhihuiHttpClientRecvResponse(HttpClient *client, HttpClientData *clientData)
+{
+ if (!client || !clientData) {
+ return HTTP_ERECV;
+ }
+
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 OpenHarmony 的实现 */
+ return HttpClientRecvResponse(client, clientData);
+ } else {
+ /* PPP 实现:在 Send 中已经接收完毕 */
+ if (clientData->isMore) {
+ return HTTP_EAGAIN;
+ }
+ return HTTP_SUCCESS;
+ }
+}
+
+void ZhihuiHttpClientClse(HttpClient *client)
+{
+ if (!client) {
+ return;
+ }
+
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 OpenHarmony 的实现 */
+ HttpClientClse(client);
+ } else {
+ /* PPP 实现:在 Send 中已经清理 */
+ memset(client, 0, sizeof(HttpClient));
+ }
+}
+
+void ZhihuiHttpClientSetCustomHeader(HttpClient *client, char *header)
+{
+ if (!client) {
+ return;
+ }
+
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 OpenHarmony 的实现 */
+ HttpClientSetCustomHeader(client, header);
+ } else {
+ /* PPP 实现:暂不支持自定义头部 */
+ printf("[ZhihuiNet][PPP][HTTP] WARNING: Custom headers not yet supported in PPP mode\n");
+ // TODO: 在 N706A HTTP 实现中添加自定义头部支持
+ }
+}
+
+int ZhihuiHttpClientGetRequest(HttpClient *client, const char *url, HttpClientData *clientData)
+{
+ if (!client || !url || !clientData) {
+ return -1;
+ }
+
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 OpenHarmony 的实现 */
+ return HttpClientGetRequest(client, url, clientData);
+ } else {
+ /* PPP 实现:使用 ZhihuiHttpClientSend */
+ return ZhihuiHttpClientSend(client, url, HTTP_GET, clientData);
+ }
+}
+
--- /dev/null
+/**
+ * @file zhihui_net_websocket.c
+ * @brief 智慧网络统一接口层 - WebSocket 实现
+ *
+ * 设计说明:
+ * 1. OpenHarmony 模式:调用 haisi_websocket (基于 net_websocket 的封装层)
+ * 2. N706A 模式:使用 n706a_websocket (实现 net_websocket 接口的 N706A 版本)
+ * 3. 提供适配器层桥接 haisi_websocket 的回调驱动模型
+ *
+ * @version 5.0.0
+ * @date 2025-10-31
+ */
+
+#include "zhihui_net_websocket.h"
+#include "zhihui_net.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include "soc_osal.h"
+
+/* 包含 N706A 网络层的头文件 */
+#include "n706a_websocket.h"
+
+/* 外部函数:获取当前实现类型 */
+extern zhihui_net_impl_type_t ZhihuiNetGetImplementation(void);
+
+/* ========== N706A WebSocket 适配器层 ========== */
+
+/* 适配器上下文 */
+typedef struct {
+ bool initialized;
+ bool running;
+ int client_id;
+ osal_task *worker_thread;
+
+ /* 保存的回调 */
+ ws_call_backs_t callbacks;
+
+ /* 线程间通信 */
+ bool stop_requested;
+ bool reconnect_requested;
+ bool sleep_mode;
+
+} n706a_ws_adapter_t;
+
+static n706a_ws_adapter_t g_adapter = {0};
+
+/* ========== N706A WebSocket 回调适配函数 ========== */
+
+/**
+ * @brief N706A onOpen回调 -> haisi_websocket event回调
+ */
+static void n706a_on_open_callback(int client_id)
+{
+ printf("[ZhihuiNet][N706A][WS] Connection opened (client_id=%d)\n", client_id);
+
+ if (g_adapter.callbacks.event_callback) {
+ g_adapter.callbacks.event_callback(WS_EVENT_CONNECTED);
+ }
+
+ if (g_adapter.callbacks.reconnect_success_callback) {
+ g_adapter.callbacks.reconnect_success_callback();
+ }
+}
+
+/**
+ * @brief N706A onMessage回调 -> haisi_websocket message回调
+ */
+static void n706a_on_message_callback(int client_id, const char *data, size_t length,
+ bool is_finished, bool is_binary)
+{
+ if (!data || length == 0) {
+ return;
+ }
+
+ if (is_binary) {
+ /* 二进制消息 */
+ if (g_adapter.callbacks.binary_callback) {
+ g_adapter.callbacks.binary_callback((const uint8_t *)data, 0, (uint32_t)length);
+ }
+ } else {
+ /* 文本消息 */
+ if (is_finished) {
+ /* 完整消息 */
+ if (g_adapter.callbacks.message_callback) {
+ g_adapter.callbacks.message_callback(data, 0);
+ }
+ } else {
+ /* 不完整消息(分片) */
+ if (g_adapter.callbacks.incomplete_callback) {
+ g_adapter.callbacks.incomplete_callback(data, length, is_binary);
+ }
+ }
+ }
+}
+
+/**
+ * @brief N706A onError回调 -> haisi_websocket event回调
+ */
+static void n706a_on_error_callback(int client_id, int error_code, const char *error_msg)
+{
+ printf("[ZhihuiNet][N706A][WS] Error occurred: code=%d, msg=%s\n",
+ error_code, error_msg ? error_msg : "unknown");
+
+ if (g_adapter.callbacks.event_callback) {
+ g_adapter.callbacks.event_callback(WS_EVENT_DISCONNECTED);
+ }
+}
+
+/**
+ * @brief N706A onClose回调 -> haisi_websocket event回调
+ */
+static void n706a_on_close_callback(int client_id, int close_code, const char *reason)
+{
+ printf("[ZhihuiNet][N706A][WS] Connection closed: code=%d, reason=%s\n",
+ close_code, reason ? reason : "unknown");
+
+ if (g_adapter.callbacks.event_callback) {
+ g_adapter.callbacks.event_callback(WS_EVENT_DISCONNECTED);
+ }
+}
+
+/**
+ * @brief WebSocket worker线程主函数
+ */
+static void* ws_worker_thread(void *arg)
+{
+ printf("[ZhihuiNet][N706A][WS] Worker thread started\n");
+
+ /* 获取URL */
+ char url[512] = {0};
+ char header[1024] = {0};
+
+ if (g_adapter.callbacks.url_callback) {
+ g_adapter.callbacks.url_callback(url, sizeof(url), header, sizeof(header));
+ } else {
+ printf("[ZhihuiNet][N706A][WS] ERROR: No URL callback provided\n");
+ return NULL;
+ }
+
+ printf("[ZhihuiNet][N706A][WS] Connecting to: %s\n", url);
+
+ /* 创建WebSocket客户端 */
+ g_adapter.client_id = n706a_websocket_constructor(
+ n706a_on_open_callback,
+ n706a_on_message_callback,
+ n706a_on_error_callback,
+ n706a_on_close_callback
+ );
+
+ if (g_adapter.client_id < 0) {
+ printf("[ZhihuiNet][N706A][WS] ERROR: Failed to create WebSocket client\n");
+ g_adapter.running = false;
+ return NULL;
+ }
+
+ printf("[ZhihuiNet][N706A][WS] WebSocket client created (id=%d)\n", g_adapter.client_id);
+
+ /* 连接到服务器 */
+ if (g_adapter.callbacks.event_callback) {
+ g_adapter.callbacks.event_callback(WS_EVENT_CONNECTING);
+ }
+
+ int ret = n706a_websocket_connect(g_adapter.client_id, url,
+ strlen(header) > 0 ? header : NULL);
+ if (ret != N706A_WS_SUCCESS) {
+ printf("[ZhihuiNet][N706A][WS] ERROR: Failed to connect (ret=%d)\n", ret);
+ n706a_websocket_close(g_adapter.client_id);
+ g_adapter.running = false;
+ return NULL;
+ }
+
+ /* 主循环 */
+ uint32_t interval = g_adapter.callbacks.main_thread_interval_ms;
+ if (interval == 0) {
+ interval = 1000; // 默认1秒
+ }
+
+ while (g_adapter.running && !g_adapter.stop_requested) {
+ /* 处理心跳 */
+ if (g_adapter.callbacks.heartbeat_callback) {
+ int need_heartbeat = g_adapter.callbacks.heartbeat_callback();
+ if (need_heartbeat) {
+ // 发送心跳(可以是Ping帧或自定义消息)
+ n706a_websocket_send_ping(g_adapter.client_id, NULL, 0);
+ }
+ }
+
+ /* 处理重连请求 */
+ if (g_adapter.reconnect_requested) {
+ printf("[ZhihuiNet][N706A][WS] Reconnecting...\n");
+ n706a_websocket_close(g_adapter.client_id);
+ osal_msleep(1000);
+
+ /* 重新连接 */
+ ret = n706a_websocket_connect(g_adapter.client_id, url,
+ strlen(header) > 0 ? header : NULL);
+ if (ret == N706A_WS_SUCCESS) {
+ printf("[ZhihuiNet][N706A][WS] Reconnection successful\n");
+ }
+ g_adapter.reconnect_requested = false;
+ }
+
+ /* 休眠模式处理 */
+ if (g_adapter.sleep_mode) {
+ // N706A暂不支持suspend/resume,休眠模式下停止发送心跳即可
+ }
+
+ /* 等待下一次循环 */
+ osal_msleep(interval);
+ }
+
+ /* 清理 */
+ printf("[ZhihuiNet][N706A][WS] Worker thread exiting\n");
+ n706a_websocket_close(g_adapter.client_id);
+ g_adapter.running = false;
+
+ return NULL;
+}
+
+/* ========== WebSocket 客户端接口实现 ========== */
+
+int ZhihuiWsStart(const ws_call_backs_t *callbacks)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_start(callbacks);
+ } else {
+ /* N706A 实现 */
+ if (!callbacks || !callbacks->url_callback) {
+ printf("[ZhihuiNet][N706A][WS] ERROR: Invalid callbacks (url_callback required)\n");
+ return -1;
+ }
+
+ if (g_adapter.initialized) {
+ printf("[ZhihuiNet][N706A][WS] WARNING: Already started\n");
+ return 0;
+ }
+
+ /* 初始化适配器 */
+ memset(&g_adapter, 0, sizeof(g_adapter));
+ memcpy(&g_adapter.callbacks, callbacks, sizeof(ws_call_backs_t));
+ g_adapter.initialized = true;
+ g_adapter.running = true;
+ g_adapter.stop_requested = false;
+ g_adapter.reconnect_requested = false;
+ g_adapter.sleep_mode = false;
+ g_adapter.client_id = -1;
+
+ /* 创建worker线程 */
+ osal_kthread_lock();
+ g_adapter.worker_thread = osal_kthread_create(
+ (osal_kthread_handler)ws_worker_thread,
+ NULL,
+ "ws_worker",
+ 8192
+ );
+ osal_kthread_unlock();
+
+ if (!g_adapter.worker_thread) {
+ printf("[ZhihuiNet][N706A][WS] ERROR: Failed to create worker thread\n");
+ g_adapter.initialized = false;
+ g_adapter.running = false;
+ return -1;
+ }
+
+ printf("[ZhihuiNet][N706A][WS] Service started successfully\n");
+ return 0;
+ }
+}
+
+int ZhihuiWsStop(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_stop();
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized) {
+ return 0;
+ }
+
+ printf("[ZhihuiNet][N706A][WS] Stopping service...\n");
+
+ g_adapter.stop_requested = true;
+ g_adapter.running = false;
+
+ /* 等待线程退出 */
+ if (g_adapter.worker_thread) {
+ // 等待最多5秒
+ for (int i = 0; i < 50 && g_adapter.running; i++) {
+ osal_msleep(100);
+ }
+ }
+
+ g_adapter.initialized = false;
+ printf("[ZhihuiNet][N706A][WS] Service stopped\n");
+ return 0;
+ }
+}
+
+int ZhihuiWsStopAsync(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_stop_async();
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized) {
+ return 0;
+ }
+
+ printf("[ZhihuiNet][N706A][WS] Requesting async stop...\n");
+ g_adapter.stop_requested = true;
+ return 0;
+ }
+}
+
+int ZhihuiWsForceReconnect(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_force_reconnect();
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized || !g_adapter.running) {
+ printf("[ZhihuiNet][N706A][WS] WARNING: Service not running\n");
+ return -1;
+ }
+
+ printf("[ZhihuiNet][N706A][WS] Force reconnect requested\n");
+ g_adapter.reconnect_requested = true;
+ return 0;
+ }
+}
+
+int ZhihuiWsEnterSleep(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_enter_sleep();
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized || !g_adapter.running) {
+ return -1;
+ }
+
+ printf("[ZhihuiNet][N706A][WS] Entering sleep mode\n");
+ g_adapter.sleep_mode = true;
+ return 0;
+ }
+}
+
+int ZhihuiWsExitSleep(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_exit_sleep();
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized || !g_adapter.running) {
+ return -1;
+ }
+
+ printf("[ZhihuiNet][N706A][WS] Exiting sleep mode\n");
+ g_adapter.sleep_mode = false;
+ return 0;
+ }
+}
+
+int ZhihuiWsSendText(const char *text)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_send_text(text);
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized || g_adapter.client_id < 0) {
+ printf("[ZhihuiNet][N706A][WS] ERROR: Not connected\n");
+ return -1;
+ }
+
+ size_t length = text ? strlen(text) : 0;
+ return n706a_websocket_send_text(g_adapter.client_id, text, length);
+ }
+}
+
+int ZhihuiWsSendBIN(uint8_t *data, int len)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_send_BIN(data, len);
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized || g_adapter.client_id < 0) {
+ printf("[ZhihuiNet][N706A][WS] ERROR: Not connected\n");
+ return -1;
+ }
+
+ return n706a_websocket_send_binary(g_adapter.client_id, data, len);
+ }
+}
+
+/* ========== WebSocket 状态查询接口 ========== */
+
+int ZhihuiWsGetConnectionState(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_get_connection_state();
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized || g_adapter.client_id < 0) {
+ return -1; // 未连接
+ }
+
+ bool connected = n706a_websocket_is_connected(g_adapter.client_id);
+ return connected ? 1 : 0;
+ }
+}
+
+const char* ZhihuiWsGetConnectionStateString(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_get_connection_state_string();
+ } else {
+ /* N706A 实现 */
+ if (!g_adapter.initialized) {
+ return "NOT_INITIALIZED";
+ }
+ if (g_adapter.client_id < 0) {
+ return "NO_CLIENT";
+ }
+ bool connected = n706a_websocket_is_connected(g_adapter.client_id);
+ return connected ? "CONNECTED" : "DISCONNECTED";
+ }
+}
+
+int ZhihuiWsGetCloseStatusPublic(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_get_close_status_public();
+ } else {
+ /* N706A 实现 */
+ return 0;
+ }
+}
+
+void ZhihuiWsPrintStatusInfo(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ uapi_ws_print_status_info();
+ } else {
+ /* N706A 实现 */
+ printf("[ZhihuiNet][N706A][WS] Status Info:\n");
+ printf(" Initialized: %s\n", g_adapter.initialized ? "YES" : "NO");
+ printf(" Running: %s\n", g_adapter.running ? "YES" : "NO");
+ printf(" Client ID: %d\n", g_adapter.client_id);
+ printf(" Sleep Mode: %s\n", g_adapter.sleep_mode ? "YES" : "NO");
+ if (g_adapter.client_id >= 0) {
+ bool connected = n706a_websocket_is_connected(g_adapter.client_id);
+ printf(" Connected: %s\n", connected ? "YES" : "NO");
+ }
+ }
+}
+
+uint32_t ZhihuiWsGetMainThreadInterval(void)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ return uapi_ws_get_main_thread_interval();
+ } else {
+ /* N706A 实现 */
+ return g_adapter.callbacks.main_thread_interval_ms > 0 ?
+ g_adapter.callbacks.main_thread_interval_ms : 1000;
+ }
+}
+
+/* ========== 回调函数注册接口 ========== */
+
+void ZhihuiWsRegisterMessageCallback(ws_message_callback_t callback)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ uapi_ws_register_message_callback(callback);
+ } else {
+ /* N706A 实现 */
+ g_adapter.callbacks.message_callback = callback;
+ }
+}
+
+void ZhihuiWsRegisterBinaryCallback(ws_binary_message_callback_t callback)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ uapi_ws_register_binary_callback(callback);
+ } else {
+ /* N706A 实现 */
+ g_adapter.callbacks.binary_callback = callback;
+ }
+}
+
+void ZhihuiWsRegisterIncompleteCallback(ws_incomplete_message_callback_t callback)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ uapi_ws_register_incomplete_callback(callback);
+ } else {
+ /* N706A 实现 */
+ g_adapter.callbacks.incomplete_callback = callback;
+ }
+}
+
+void ZhihuiWsRegisterUrlCallback(ws_url_callback_t callback)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ uapi_ws_register_url_callback(callback);
+ } else {
+ /* N706A 实现 */
+ g_adapter.callbacks.url_callback = callback;
+ }
+}
+
+void ZhihuiWsRegisterHeartbeatCallback(ws_heartbeat_callback_t callback)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ uapi_ws_register_heartbeat_callback(callback);
+ } else {
+ /* N706A 实现 */
+ g_adapter.callbacks.heartbeat_callback = callback;
+ }
+}
+
+void ZhihuiWsRegisterEventCallback(ws_event_callback_t callback)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ uapi_ws_register_event_callback(callback);
+ } else {
+ /* N706A 实现 */
+ g_adapter.callbacks.event_callback = callback;
+ }
+}
+
+void ZhihuiWsRegisterReconnectSuccessCallback(ws_reconnect_success_callback_t callback)
+{
+ if (ZhihuiNetGetImplementation() == ZHIHUI_NET_IMPL_OPENHARMONY) {
+ /* 调用 haisi_websocket 的实现 */
+ uapi_ws_register_reconnect_success_callback(callback);
+ } else {
+ /* N706A 实现 */
+ g_adapter.callbacks.reconnect_success_callback = callback;
+ }
+}