From: 孙春阳 Date: Fri, 31 Oct 2025 07:21:50 +0000 (+0800) Subject: 把雷鸟的网络接口都抽取到公共接口,增加openharmon和n706a AT模式的支持。默认openharmon的lwip模式已经可以正常工作。n706a的at模式还需要重构。 X-Git-Url: http://git.snicker.com.cn/?a=commitdiff_plain;h=4f98cb8f2f8ff52c0a6ff13ca1602447cc932564;p=mirrors%2FFD230.git 把雷鸟的网络接口都抽取到公共接口,增加openharmon和n706a AT模式的支持。默认openharmon的lwip模式已经可以正常工作。n706a的at模式还需要重构。 --- diff --git a/code/sdk/build/config/target_config/brandy/config.py b/code/sdk/build/config/target_config/brandy/config.py index 28c9282bf..5b68f44e8 100644 --- a/code/sdk/build/config/target_config/brandy/config.py +++ b/code/sdk/build/config/target_config/brandy/config.py @@ -1491,6 +1491,11 @@ target = { "tcp_test", #websocket 相关配置 "zhihui_websocket", + + #zhihui_net 相关配置 + "zhihui_net", + "n706a_net", + #ai 相关配置 "aiMediaService", "aiService", diff --git a/code/sdk/zhihui/CMakeLists.txt b/code/sdk/zhihui/CMakeLists.txt index 8908022e7..8195aac07 100644 --- a/code/sdk/zhihui/CMakeLists.txt +++ b/code/sdk/zhihui/CMakeLists.txt @@ -31,3 +31,5 @@ add_subdirectory_if_exist(ai_new) 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) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt index 2db97e411..d4da6c36e 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt +++ b/code/sdk/zhihui/leiniao/imsl_ln/CMakeLists.txt @@ -30,8 +30,7 @@ set(PUBLIC_HEADER ${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 ) # 公共宏定义(可选) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c index cfd1db2fd..f559259d3 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.c @@ -1,6 +1,6 @@ #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 */ @@ -479,24 +479,24 @@ int uapi_ln_download_file(const char *url, const char *save_path) } // 建立连接 - 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; } @@ -504,7 +504,7 @@ int uapi_ln_download_file(const char *url, const char *save_path) // 接收响应数据 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) { @@ -517,8 +517,8 @@ int uapi_ln_download_file(const char *url, const char *save_path) 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; @@ -557,8 +557,8 @@ int uapi_ln_download_file(const char *url, const char *save_path) } } - 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); @@ -655,7 +655,7 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path) header_buf = (char *)malloc(128); if (!header_buf) { - HttpClientUnprepare(&client_data); + ZhihuiHttpClientUnprepare(&client_data); fclose(fd); return ERRCODE_FAIL; } @@ -663,7 +663,7 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path) if (hdr <= 0) { free(header_buf); - HttpClientUnprepare(&client_data); + ZhihuiHttpClientUnprepare(&client_data); fclose(fd); return ERRCODE_FAIL; } @@ -672,37 +672,37 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path) } /* 建立连接并发送请求 */ - 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; } @@ -716,8 +716,8 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path) if (!fd) { if (header_buf) free(header_buf); - HttpClientClse(&client); - HttpClientUnprepare(&client_data); + ZhihuiHttpClientClse(&client); + ZhihuiHttpClientUnprepare(&client_data); return ERRCODE_FAIL; } resume_offset = 0; @@ -733,8 +733,8 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path) 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; @@ -744,7 +744,7 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path) /* 后续接收循环 */ 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) @@ -756,8 +756,8 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path) 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; @@ -785,8 +785,8 @@ int uapi_ln_download_file_resume(const char *url, const char *save_path) } if (header_buf) free(header_buf); - HttpClientUnprepare(&client_data); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&client_data); + ZhihuiHttpClientClse(&client); fclose(fd); return ret; } @@ -829,13 +829,13 @@ int uapi_ln_download_file_resume_with_retry(const char *url, const char *save_pa 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; @@ -845,7 +845,7 @@ HTTPC_RESULT ln_http_prepare_auto(HttpClientData *clientData, int header_size, i 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; @@ -1112,28 +1112,28 @@ static uint32_t ln_download_mp3_file_advanced(const char *audio_url, const char } // 建立连接 - 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; } @@ -1141,7 +1141,7 @@ static uint32_t ln_download_mp3_file_advanced(const char *audio_url, const char // 接收响应数据(支持取消) 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) @@ -1155,8 +1155,8 @@ static uint32_t ln_download_mp3_file_advanced(const char *audio_url, const char 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; @@ -1196,8 +1196,8 @@ static uint32_t ln_download_mp3_file_advanced(const char *audio_url, const char #if TEST_MP3_DOWNLOAD fclose(fd); #endif - HttpClientUnprepare(&client_data); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&client_data); + ZhihuiHttpClientClse(&client); free(save_path); return ERRCODE_SUCC; } @@ -1739,6 +1739,22 @@ errcode_t uapi_ln_http_server_init(void) { 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; diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h index baf5b5505..b14b0f1f7 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api.h @@ -8,8 +8,8 @@ extern "C" #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 #include "wearable_log.h" #include "ln_api_interface.h" @@ -281,7 +281,8 @@ extern ScheduleReminderConfig g_schedule_reminder_config; /* ============================== 公共工具函数(供拆分后的模块复用) ============================== */ -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 本地缓存接口 */ diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_device_info.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_device_info.c index 50ce01483..4c7b50594 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_device_info.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_device_info.c @@ -500,7 +500,7 @@ uint32_t ln_device_get_character_http(const char *device_id, free(header_buf); return ERRCODE_FAIL; } - HttpClientSetCustomHeader(&client, header_buf); + ZhihuiHttpClientSetCustomHeader(&client, header_buf); printf("device getCharacter header %s\n", header_buf); @@ -511,7 +511,7 @@ uint32_t ln_device_get_character_http(const char *device_id, 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); @@ -531,8 +531,8 @@ uint32_t ln_device_get_character_http(const char *device_id, } end: - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); free(header_buf); return ERRCODE_SUCC; } @@ -569,7 +569,7 @@ uint32_t ln_device_get_user_id_http(const char *device_id, free(header_buf2); return ERRCODE_FAIL; } - HttpClientSetCustomHeader(&client, header_buf2); + ZhihuiHttpClientSetCustomHeader(&client, header_buf2); printf("device getUserId header %s\n", header_buf2); @@ -580,7 +580,7 @@ uint32_t ln_device_get_user_id_http(const char *device_id, 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); @@ -637,8 +637,8 @@ uint32_t ln_device_get_user_id_http(const char *device_id, } end: - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); free(header_buf2); return ERRCODE_SUCC; } @@ -680,7 +680,7 @@ uint32_t ln_device_get_chat_stamina_http(const char *device_id, const char *devi 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); @@ -691,12 +691,12 @@ uint32_t ln_device_get_chat_stamina_http(const char *device_id, const char *devi 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; } @@ -706,8 +706,8 @@ uint32_t ln_device_get_chat_stamina_http(const char *device_id, const char *devi 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; } @@ -715,8 +715,8 @@ uint32_t ln_device_get_chat_stamina_http(const char *device_id, const char *devi 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; } @@ -736,8 +736,8 @@ uint32_t ln_device_get_chat_stamina_http(const char *device_id, const char *devi 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; } @@ -754,8 +754,8 @@ uint32_t ln_device_get_chat_stamina_http(const char *device_id, const char *devi cJSON_Delete(root); free(json_text); - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); free(header_buf); return ERRCODE_FAIL; } @@ -766,8 +766,8 @@ uint32_t ln_device_get_chat_stamina_http(const char *device_id, const char *devi 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; } @@ -795,8 +795,8 @@ uint32_t ln_device_get_chat_stamina_http(const char *device_id, const char *devi 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 diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_emoji.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_emoji.c index b2f414699..abb8e1ee1 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_emoji.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_emoji.c @@ -1047,7 +1047,7 @@ static uint32_t ln_emoji_report_status_http(const char *device_id, 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(); @@ -1093,7 +1093,7 @@ static uint32_t ln_emoji_report_status_http(const char *device_id, 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; @@ -1103,7 +1103,7 @@ static uint32_t ln_emoji_report_status_http(const char *device_id, 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; @@ -1131,7 +1131,7 @@ static uint32_t ln_emoji_report_status_http(const char *device_id, free(body); - HttpClientUnprepare(&clientData); + ZhihuiHttpClientUnprepare(&clientData); free(header_buf); return ERRCODE_SUCC; } @@ -1169,7 +1169,7 @@ static uint32_t ln_emoji_report_all_http(const char *device_id, free(header_buf); return ERRCODE_FAIL; } - HttpClientSetCustomHeader(&client, header_buf); + ZhihuiHttpClientSetCustomHeader(&client, header_buf); /* 组装 JSON 数组 */ cJSON *root = cJSON_CreateObject(); @@ -1218,7 +1218,7 @@ static uint32_t ln_emoji_report_all_http(const char *device_id, if (res != HTTP_SUCCESS) { free(body); - HttpClientUnprepare(&clientData); + ZhihuiHttpClientUnprepare(&clientData); free(header_buf); return ERRCODE_FAIL; } @@ -1228,7 +1228,7 @@ static uint32_t ln_emoji_report_all_http(const char *device_id, { WEARABLE_LOGE(WEARABLE_LOG_MODULE_APP, "[EMOJI_REPORT_ALL] HTTP %d", code); free(body); - HttpClientUnprepare(&clientData); + ZhihuiHttpClientUnprepare(&clientData); free(header_buf); return ERRCODE_FAIL; } @@ -1248,7 +1248,7 @@ static uint32_t ln_emoji_report_all_http(const char *device_id, } free(body); - HttpClientUnprepare(&clientData); + ZhihuiHttpClientUnprepare(&clientData); free(header_buf); return ERRCODE_SUCC; } diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_ota.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_ota.c index eac265b6b..7d99321c0 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_ota.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_ota.c @@ -270,7 +270,7 @@ uint32_t ln_ota_get_task_http(const char *device_id, 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) @@ -284,7 +284,7 @@ uint32_t ln_ota_get_task_http(const char *device_id, char *url_with_params = (char *)malloc(url_cap); if (!url_with_params) { - HttpClientUnprepare(&clientData); + ZhihuiHttpClientUnprepare(&clientData); free(header_buf); return ERRCODE_FAIL; } @@ -293,7 +293,7 @@ uint32_t ln_ota_get_task_http(const char *device_id, 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; @@ -301,7 +301,7 @@ uint32_t ln_ota_get_task_http(const char *device_id, 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); @@ -327,8 +327,8 @@ uint32_t ln_ota_get_task_http(const char *device_id, } end: - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); if (header_buf) free(header_buf); if (url_with_params) @@ -418,7 +418,7 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path, header_buf = (char *)malloc(128); if (!header_buf) { - HttpClientUnprepare(&client_data); + ZhihuiHttpClientUnprepare(&client_data); fclose(fd); return -1; } @@ -426,46 +426,46 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path, 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; } @@ -479,8 +479,8 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path, if (!fd) { if (header_buf) free(header_buf); - HttpClientClse(&client); - HttpClientUnprepare(&client_data); + ZhihuiHttpClientClse(&client); + ZhihuiHttpClientUnprepare(&client_data); return -1; } resume_offset = 0; @@ -497,8 +497,8 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path, 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; @@ -520,7 +520,7 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path, // 后续接收循环 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) @@ -531,8 +531,8 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path, 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; @@ -554,8 +554,8 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path, 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; } } @@ -590,8 +590,8 @@ static int ln_download_file_to_path(const char *full_url, const char *save_path, // 资源清理 if (header_buf) free(header_buf); fclose(fd); - HttpClientClse(&client); - HttpClientUnprepare(&client_data); + ZhihuiHttpClientClse(&client); + ZhihuiHttpClientUnprepare(&client_data); printf("[OTA] download process finished\n"); diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_speech_chat.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_speech_chat.c index a7d9829d0..d2df59505 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_speech_chat.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_speech_chat.c @@ -172,7 +172,7 @@ uint32_t ln_speech_chat_stream_http(const char *device_id, free(header_buf); return ERRCODE_FAIL; } - HttpClientSetCustomHeader(&client, header_buf); + ZhihuiHttpClientSetCustomHeader(&client, header_buf); printf("chatstream header_buf: %s\n", header_buf); /* 准备缓冲区(自适应) */ @@ -191,22 +191,22 @@ uint32_t ln_speech_chat_stream_http(const char *device_id, 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; } @@ -215,15 +215,15 @@ uint32_t ln_speech_chat_stream_http(const char *device_id, { 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) { @@ -237,8 +237,8 @@ uint32_t ln_speech_chat_stream_http(const char *device_id, 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; } @@ -257,8 +257,8 @@ uint32_t ln_speech_chat_stream_http(const char *device_id, } } while (clientData.isMore); - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); free(body); fclose(fd); diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_choose.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_choose.c index 98c3a6b63..0127275ec 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_choose.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_choose.c @@ -170,7 +170,7 @@ uint32_t ln_story_choose_http(const char *device_id, free(header_buf); return ERRCODE_FAIL; } - HttpClientSetCustomHeader(&client, header_buf); + ZhihuiHttpClientSetCustomHeader(&client, header_buf); printf("story choose header_buf: %s\n", header_buf); /* 自适应准备 HTTP 缓冲区 */ @@ -189,22 +189,22 @@ uint32_t ln_story_choose_http(const char *device_id, 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; } @@ -213,15 +213,15 @@ uint32_t ln_story_choose_http(const char *device_id, { 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) { @@ -234,8 +234,8 @@ uint32_t ln_story_choose_http(const char *device_id, 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; } @@ -255,8 +255,8 @@ uint32_t ln_story_choose_http(const char *device_id, } while (clientData.isMore); free(body); - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); fclose(fd); if (total_written > 0) diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_exit.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_exit.c index 483e203d9..0fe192d37 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_exit.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_exit.c @@ -31,7 +31,7 @@ uint32_t ln_story_exit_inquiry_http(const char *device_id, 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) @@ -63,8 +63,8 @@ uint32_t ln_story_exit_inquiry_http(const char *device_id, } end: - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); if (header_buf) free(header_buf); return ERRCODE_SUCC; diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_finish.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_finish.c index 7c9a79c75..8dfcd5be5 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_finish.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_finish.c @@ -29,7 +29,7 @@ uint32_t ln_story_finish_http(const char *device_id, { 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) @@ -62,8 +62,8 @@ uint32_t ln_story_finish_http(const char *device_id, } end: - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); free(header_buf); return ERRCODE_SUCC; } diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_random.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_random.c index 727f93def..641e6148c 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_random.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_story_random.c @@ -55,7 +55,7 @@ uint32_t ln_story_random_http(const char *device_id, 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); @@ -111,24 +111,24 @@ uint32_t ln_story_random_http(const char *device_id, 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); @@ -139,8 +139,8 @@ uint32_t ln_story_random_http(const char *device_id, 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); @@ -150,7 +150,7 @@ uint32_t ln_story_random_http(const char *device_id, 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) { @@ -162,8 +162,8 @@ uint32_t ln_story_random_http(const char *device_id, { 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); @@ -185,8 +185,8 @@ uint32_t ln_story_random_http(const char *device_id, } } while (clientData.isMore); - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); if (total_written > 0) { diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_timeline_activities.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_timeline_activities.c index 286758fb3..42604fe66 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_timeline_activities.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_timeline_activities.c @@ -91,7 +91,7 @@ uint32_t ln_timeline_get_status_http(const char *device_id, 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) @@ -101,7 +101,7 @@ uint32_t ln_timeline_get_status_http(const char *device_id, 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; @@ -118,8 +118,8 @@ uint32_t ln_timeline_get_status_http(const char *device_id, } end: - HttpClientUnprepare(&clientData); - HttpClientClse(&client); + ZhihuiHttpClientUnprepare(&clientData); + ZhihuiHttpClientClse(&client); free(header_buf); uapi_pm_remove_sleep_veto(PM_ID_DEBUG); return ERRCODE_SUCC; diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_token.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_token.c index 898b9526d..bbfb36908 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_token.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_token.c @@ -294,9 +294,9 @@ uint32_t ln_get_device_token_http(const char *device_id, /* 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); @@ -306,12 +306,12 @@ uint32_t ln_get_device_token_http(const char *device_id, 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); @@ -322,8 +322,8 @@ uint32_t ln_get_device_token_http(const char *device_id, 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); @@ -334,8 +334,8 @@ uint32_t ln_get_device_token_http(const char *device_id, 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); @@ -349,8 +349,8 @@ uint32_t ln_get_device_token_http(const char *device_id, 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); @@ -364,8 +364,8 @@ uint32_t ln_get_device_token_http(const char *device_id, { 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); @@ -381,8 +381,8 @@ uint32_t ln_get_device_token_http(const char *device_id, 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; } @@ -425,8 +425,8 @@ uint32_t ln_get_device_token_http(const char *device_id, 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); diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_web_ack.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_web_ack.c index 5c621a0a9..d0b1d988e 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_web_ack.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_web_ack.c @@ -66,7 +66,7 @@ int ln_ws_send_device_bind_ack(const char *user_id, bool success, const char *ms 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); @@ -117,7 +117,7 @@ int ln_ws_send_device_unbind_ack(const char *user_id, bool success, const char * 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); @@ -168,7 +168,7 @@ int ln_ws_send_character_change_ack(const char *character_id, bool success, cons 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); @@ -217,7 +217,7 @@ int ln_ws_send_character_config_change_ack(const char *character_id, bool succes 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); @@ -265,7 +265,7 @@ int ln_ws_send_sleep_mode_start_ack(bool success, const char *msg) 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); @@ -313,7 +313,7 @@ int ln_ws_send_sleep_mode_end_ack(bool success, const char *msg) 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); @@ -362,7 +362,7 @@ int ln_ws_send_schedule_reminder_ack(int schedule_id, bool success, const char * 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); @@ -411,7 +411,7 @@ int ln_ws_send_schedule_reminder_config_ack(int schedule_id, bool success, const 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); @@ -462,7 +462,7 @@ int ln_ws_send_delete_emoji_ack(const char *character_id, const char *eyesColor, 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); @@ -513,7 +513,7 @@ int ln_ws_send_download_emoji_ack(const char *character_id, const char *eyesColo 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); diff --git a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c index 4f54746c5..1a0f9a735 100644 --- a/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c +++ b/code/sdk/zhihui/leiniao/imsl_ln/ln_api_websocket.c @@ -1,6 +1,6 @@ #include "ln_api.h" -#include "net_websocket.h" -#include "haisi_websocket.h" +/* 智慧网络统一接口(已包含 OpenHarmony 类型定义) */ +#include "zhihui_net.h" #include #include #include @@ -396,7 +396,7 @@ int ln_ws_send_ping(void) } // 发送 JSON 字符串 - int ret = uapi_ws_send_text(json_string); + int ret = ZhihuiWsSendText(json_string); LN_WS_DEBUG("Sent ping: %s\n", json_string); @@ -631,7 +631,7 @@ int ln_ws_send_audio_data(const uint8_t *audio_data, uint32_t audio_size, bool f } // 发送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"); @@ -1028,9 +1028,9 @@ int ln_ws_start(ws_message_callback_t_ln callback, uint32_t audio_timeout_ticks) 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; } @@ -1271,7 +1271,7 @@ int ln_ws_send_text(const char *text) LN_WS_WARN("Cannot send text, connection state: %d\n", g_ws_connected); return -1; } - return uapi_ws_send_text(text); + return ZhihuiWsSendText(text); } /* 发送随机故事请求 */ @@ -1336,7 +1336,7 @@ int ln_ws_send_random_story_request(const char *story_theme) 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); @@ -1414,7 +1414,7 @@ int ln_ws_send_voice_chat_interrupt(const char *reason) 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); @@ -1450,7 +1450,7 @@ int ln_ws_stop(void) // 停止音频服务(与 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缓存 @@ -2031,7 +2031,7 @@ int ln_ws_enter_sleep(void) } // 使用新的接口进入休眠 - 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"); @@ -2057,7 +2057,7 @@ int ln_ws_exit_sleep(void) } // 使用新的接口退出休眠 - 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"); @@ -2446,7 +2446,7 @@ static int ln_ws_reconnect(void) 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; @@ -2795,7 +2795,7 @@ static int ln_ws_send_audio_data_direct(const uint8_t *audio_data, uint32_t audi } // 发送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"); diff --git a/code/sdk/zhihui/n706a_net/CMakeLists.txt b/code/sdk/zhihui/n706a_net/CMakeLists.txt new file mode 100644 index 000000000..9d74e569b --- /dev/null +++ b/code/sdk/zhihui/n706a_net/CMakeLists.txt @@ -0,0 +1,50 @@ +#=============================================================================== +# @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} "*") + diff --git a/code/sdk/zhihui/n706a_net/include/n706a_http_client.h b/code/sdk/zhihui/n706a_net/include/n706a_http_client.h new file mode 100644 index 000000000..fe6b150ac --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/n706a_http_client.h @@ -0,0 +1,271 @@ +/** + * @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 +#include +#include +#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 + diff --git a/code/sdk/zhihui/n706a_net/include/n706a_tcp_client.h b/code/sdk/zhihui/n706a_net/include/n706a_tcp_client.h new file mode 100644 index 000000000..227cacefb --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/n706a_tcp_client.h @@ -0,0 +1,202 @@ +/* + * 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 +#include +#include + +/* ========== 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 */ + diff --git a/code/sdk/zhihui/n706a_net/include/n706a_websocket.h b/code/sdk/zhihui/n706a_net/include/n706a_websocket.h new file mode 100644 index 000000000..a710a841b --- /dev/null +++ b/code/sdk/zhihui/n706a_net/include/n706a_websocket.h @@ -0,0 +1,310 @@ +/** + * @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 +#include +#include +#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 */ + diff --git a/code/sdk/zhihui/n706a_net/src/n706a_http_client.c b/code/sdk/zhihui/n706a_net/src/n706a_http_client.c new file mode 100644 index 000000000..649e370af --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/n706a_http_client.c @@ -0,0 +1,785 @@ +/** + * @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 +#include +#include + +/* 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; +} + diff --git a/code/sdk/zhihui/n706a_net/src/n706a_tcp_client.c b/code/sdk/zhihui/n706a_net/src/n706a_tcp_client.c new file mode 100644 index 000000000..2d9639d0e --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/n706a_tcp_client.c @@ -0,0 +1,660 @@ +/** + * @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 +#include +#include + +/* ========== 私有函数声明 ========== */ + +#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 */ + diff --git a/code/sdk/zhihui/n706a_net/src/n706a_websocket.c b/code/sdk/zhihui/n706a_net/src/n706a_websocket.c new file mode 100644 index 000000000..70b86a2a6 --- /dev/null +++ b/code/sdk/zhihui/n706a_net/src/n706a_websocket.c @@ -0,0 +1,843 @@ +/** + * @file n706a_websocket.c + * @brief N706A WebSocket客户端实现(基于TCP) + * + * 完整实现RFC 6455 WebSocket协议: + * - ws:// (普通TCP) + * - wss:// (SSL TCP) + * - 握手 + * - 帧封装/解封装 + * - Ping/Pong + * - 分片传输 + */ + +#include "n706a_websocket.h" +#include +#include +#include +#include + +/* 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'; +} + diff --git a/code/sdk/zhihui/ppp/example/n706a_http_tcp_example.c b/code/sdk/zhihui/ppp/example/n706a_http_tcp_example.c new file mode 100644 index 000000000..074f6092a --- /dev/null +++ b/code/sdk/zhihui/ppp/example/n706a_http_tcp_example.c @@ -0,0 +1,375 @@ +/** + * @file n706a_http_tcp_example.c + * @brief N706A HTTP客户端使用示例(基于TCP实现) + * + * 本示例展示如何使用基于TCP的HTTP/HTTPS客户端: + * - 支持6个并发连接 + * - HTTP使用普通TCP,HTTPS使用SSL TCP + * - 完全控制HTTP协议 + */ + +#include +#include +#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; +} + diff --git a/code/sdk/zhihui/ppp/example/n706a_ssl_comparison_example.c b/code/sdk/zhihui/ppp/example/n706a_ssl_comparison_example.c new file mode 100644 index 000000000..64cb65aed --- /dev/null +++ b/code/sdk/zhihui/ppp/example/n706a_ssl_comparison_example.c @@ -0,0 +1,395 @@ +/** + * @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 +#include +#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; +} + diff --git a/code/sdk/zhihui/ppp/example/n706a_websocket_example.c b/code/sdk/zhihui/ppp/example/n706a_websocket_example.c new file mode 100644 index 000000000..5c3f7926d --- /dev/null +++ b/code/sdk/zhihui/ppp/example/n706a_websocket_example.c @@ -0,0 +1,397 @@ +/** + * @file n706a_websocket_example.c + * @brief N706A WebSocket客户端使用示例 + * + * 展示如何使用WebSocket客户端: + * - ws://普通连接 + * - wss://安全连接 + * - 发送文本和二进制消息 + * - 接收消息回调 + */ + +#include +#include +#include +#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; +} + diff --git a/code/sdk/zhihui/ppp/example/ppp_at_cmd.c b/code/sdk/zhihui/ppp/example/ppp_at_cmd.c index 711e516c7..3b2cf850f 100644 --- a/code/sdk/zhihui/ppp/example/ppp_at_cmd.c +++ b/code/sdk/zhihui/ppp/example/ppp_at_cmd.c @@ -2504,6 +2504,89 @@ static errcode_t at_ppp_get_config_baudrate(uint8_t *para, uint32_t paraLen, cha 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协议栈 * @@ -2576,6 +2659,9 @@ static const at_cmd_table_t g_ppp_cmd[] = { { (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 *)" " }, diff --git a/code/sdk/zhihui/ppp/include/n706a_http_client.h b/code/sdk/zhihui/ppp/include/n706a_http_client.h new file mode 100644 index 000000000..919ff4e00 --- /dev/null +++ b/code/sdk/zhihui/ppp/include/n706a_http_client.h @@ -0,0 +1,271 @@ +/** + * @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 +#include +#include +#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 + diff --git a/code/sdk/zhihui/ppp/include/n706a_tcp_client.h b/code/sdk/zhihui/ppp/include/n706a_tcp_client.h new file mode 100644 index 000000000..db6a065c9 --- /dev/null +++ b/code/sdk/zhihui/ppp/include/n706a_tcp_client.h @@ -0,0 +1,200 @@ +/* + * 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 +#include +#include + +/* ========== 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 */ + diff --git a/code/sdk/zhihui/ppp/include/n706a_websocket.h b/code/sdk/zhihui/ppp/include/n706a_websocket.h new file mode 100644 index 000000000..b7eb62ad4 --- /dev/null +++ b/code/sdk/zhihui/ppp/include/n706a_websocket.h @@ -0,0 +1,310 @@ +/** + * @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 +#include +#include +#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 */ + diff --git a/code/sdk/zhihui/ppp/include/ppp_api.h b/code/sdk/zhihui/ppp/include/ppp_api.h index a9f3fc239..58d39e453 100644 --- a/code/sdk/zhihui/ppp/include/ppp_api.h +++ b/code/sdk/zhihui/ppp/include/ppp_api.h @@ -62,6 +62,44 @@ errcode_t ppp_ppp_mode_pause(void); */ 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 波特率 diff --git a/code/sdk/zhihui/ppp/include/ppp_at_handler.h b/code/sdk/zhihui/ppp/include/ppp_at_handler.h index 901fbe64b..e3c35c995 100644 --- a/code/sdk/zhihui/ppp/include/ppp_at_handler.h +++ b/code/sdk/zhihui/ppp/include/ppp_at_handler.h @@ -7,6 +7,7 @@ extern "C" { #include #include +#include #include "ppp_config.h" #include "ppp_types.h" @@ -33,6 +34,16 @@ void ppp_at_handler_deinit(void); 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 错误码 diff --git a/code/sdk/zhihui/ppp/src/n706a_http_client.c b/code/sdk/zhihui/ppp/src/n706a_http_client.c new file mode 100644 index 000000000..649e370af --- /dev/null +++ b/code/sdk/zhihui/ppp/src/n706a_http_client.c @@ -0,0 +1,785 @@ +/** + * @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 +#include +#include + +/* 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; +} + diff --git a/code/sdk/zhihui/ppp/src/n706a_tcp_client.c b/code/sdk/zhihui/ppp/src/n706a_tcp_client.c new file mode 100644 index 000000000..d7e068425 --- /dev/null +++ b/code/sdk/zhihui/ppp/src/n706a_tcp_client.c @@ -0,0 +1,659 @@ +/** + * @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 +#include +#include + +/* ========== 私有函数声明 ========== */ + +#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 */ + diff --git a/code/sdk/zhihui/ppp/src/n706a_websocket.c b/code/sdk/zhihui/ppp/src/n706a_websocket.c new file mode 100644 index 000000000..70b86a2a6 --- /dev/null +++ b/code/sdk/zhihui/ppp/src/n706a_websocket.c @@ -0,0 +1,843 @@ +/** + * @file n706a_websocket.c + * @brief N706A WebSocket客户端实现(基于TCP) + * + * 完整实现RFC 6455 WebSocket协议: + * - ws:// (普通TCP) + * - wss:// (SSL TCP) + * - 握手 + * - 帧封装/解封装 + * - Ping/Pong + * - 分片传输 + */ + +#include "n706a_websocket.h" +#include +#include +#include +#include + +/* 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'; +} + diff --git a/code/sdk/zhihui/ppp/src/ppp_api.c b/code/sdk/zhihui/ppp/src/ppp_api.c index a46ffe610..4c14cceff 100644 --- a/code/sdk/zhihui/ppp/src/ppp_api.c +++ b/code/sdk/zhihui/ppp/src/ppp_api.c @@ -73,7 +73,7 @@ errcode_t ppp_api_power_init(void) .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 }; @@ -245,7 +245,7 @@ errcode_t zhihui_ppp_init(const ppp_config_t *config) ppp_get_network_signal_info(); - ppp_enable_power_save_mode(); + // ppp_enable_power_save_mode(); ppp_start_dial(); #endif @@ -1935,11 +1935,30 @@ errcode_t ppp_api_get_default_netif(char* netif_name, uint32_t buffer_size) 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) { @@ -1953,7 +1972,7 @@ errcode_t ppp_api_set_baudrate_in_all_mode(uint32_t baudrate) 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..."); @@ -1962,45 +1981,35 @@ errcode_t ppp_api_set_baudrate_in_all_mode(uint32_t baudrate) 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(); } @@ -2034,47 +2043,69 @@ errcode_t ppp_api_set_baudrate_in_all_mode(uint32_t baudrate) 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"); @@ -2094,43 +2125,10 @@ errcode_t ppp_api_update_netinfo_in_all_mode(void) 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; diff --git a/code/sdk/zhihui/ppp/src/ppp_at_handler.c b/code/sdk/zhihui/ppp/src/ppp_at_handler.c index 7c5fad1aa..cb663ade1 100644 --- a/code/sdk/zhihui/ppp/src/ppp_at_handler.c +++ b/code/sdk/zhihui/ppp/src/ppp_at_handler.c @@ -528,6 +528,34 @@ ppp_error_t ppp_at_send_command(const char *command, const char *expected_respon 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) */ diff --git a/code/sdk/zhihui/ppp/src/ppp_interrupt.c b/code/sdk/zhihui/ppp/src/ppp_interrupt.c index 38916fd4b..211c10f36 100644 --- a/code/sdk/zhihui/ppp/src/ppp_interrupt.c +++ b/code/sdk/zhihui/ppp/src/ppp_interrupt.c @@ -28,6 +28,7 @@ /* lwIP相关头文件 */ #include "lwip/err.h" +#include "lwip/stats.h" #include "netif/ppp/ppp.h" #include "netif/ppp/pppos.h" @@ -1238,59 +1239,84 @@ void ppp_interrupt_print_stats(void) } 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("================================================="); } diff --git a/code/sdk/zhihui/ppp/src/ppp_lwip.c b/code/sdk/zhihui/ppp/src/ppp_lwip.c index 6b267e93d..5996285a3 100644 --- a/code/sdk/zhihui/ppp/src/ppp_lwip.c +++ b/code/sdk/zhihui/ppp/src/ppp_lwip.c @@ -969,6 +969,12 @@ static void ppp_lwip_netif_status_callback(struct netif *netif) */ 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!"); diff --git a/code/sdk/zhihui/ppp/src/ppp_power_ctl.c b/code/sdk/zhihui/ppp/src/ppp_power_ctl.c index b80ffdde3..67169b612 100644 --- a/code/sdk/zhihui/ppp/src/ppp_power_ctl.c +++ b/code/sdk/zhihui/ppp/src/ppp_power_ctl.c @@ -437,11 +437,11 @@ void ppp_ring_isr_callback(pin_t pin, uintptr_t param) 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); diff --git a/code/sdk/zhihui/zhihui_net/CMakeLists.txt b/code/sdk/zhihui/zhihui_net/CMakeLists.txt new file mode 100644 index 000000000..201be7f05 --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/CMakeLists.txt @@ -0,0 +1,50 @@ +#=============================================================================== +# @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} "*") + diff --git a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.c b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.c new file mode 100644 index 000000000..30968312d --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.c @@ -0,0 +1,298 @@ +/** + * @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 +#include +#include + +/* ========== 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; +} + diff --git a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.h b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.h new file mode 100644 index 000000000..29b467c2b --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_openharmony_adapter.h @@ -0,0 +1,49 @@ +/** + * @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 */ + diff --git a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.c b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.c new file mode 100644 index 000000000..bbdc372a9 --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.c @@ -0,0 +1,445 @@ +/** + * @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 +#include +#include + +/* ========== 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; +} diff --git a/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.h b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.h new file mode 100644 index 000000000..08bf64f07 --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/adapter/zhihui_net_ppp_adapter.h @@ -0,0 +1,49 @@ +/** + * @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 */ + diff --git a/code/sdk/zhihui/zhihui_net/example/zhihui_net_http_example.c b/code/sdk/zhihui/zhihui_net/example/zhihui_net_http_example.c new file mode 100644 index 000000000..e9dda5ecd --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/example/zhihui_net_http_example.c @@ -0,0 +1,198 @@ +/** + * @file zhihui_net_http_example.c + * @brief ZhihuiNet HTTP客户端使用示例 + */ + +#include "zhihui_net.h" +#include +#include + +/* 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; +} + diff --git a/code/sdk/zhihui/zhihui_net/example/zhihui_net_ws_example.c b/code/sdk/zhihui/zhihui_net/example/zhihui_net_ws_example.c new file mode 100644 index 000000000..a7d1b5f89 --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/example/zhihui_net_ws_example.c @@ -0,0 +1,284 @@ +/** + * @file zhihui_net_ws_example.c + * @brief ZhihuiNet WebSocket客户端使用示例 + */ + +#include "zhihui_net.h" +#include +#include +#include + +/* 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; +} + diff --git a/code/sdk/zhihui/zhihui_net/include/zhihui_net.h b/code/sdk/zhihui/zhihui_net/include/zhihui_net.h new file mode 100644 index 000000000..c31f8712a --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/include/zhihui_net.h @@ -0,0 +1,78 @@ +/** + * @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 +#include +#include + +/* 包含 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 */ diff --git a/code/sdk/zhihui/zhihui_net/include/zhihui_net_http.h b/code/sdk/zhihui/zhihui_net/include/zhihui_net_http.h new file mode 100644 index 000000000..316078905 --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/include/zhihui_net_http.h @@ -0,0 +1,95 @@ +/** + * @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 +#include +#include + +/* 直接复用 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 */ + diff --git a/code/sdk/zhihui/zhihui_net/include/zhihui_net_websocket.h b/code/sdk/zhihui/zhihui_net/include/zhihui_net_websocket.h new file mode 100644 index 000000000..6eed03f90 --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/include/zhihui_net_websocket.h @@ -0,0 +1,160 @@ +/** + * @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 +#include +#include + +/* 直接复用 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 */ + diff --git a/code/sdk/zhihui/zhihui_net/src/zhihui_net.c b/code/sdk/zhihui/zhihui_net/src/zhihui_net.c new file mode 100644 index 000000000..4d973a9ea --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/src/zhihui_net.c @@ -0,0 +1,48 @@ +/** + * @file zhihui_net.c + * @brief 智慧网络统一接口层 - 配置实现 + * + * 负责管理网络实现类型的配置和切换 + */ + +#include "zhihui_net.h" +#include +#include +#include + +/* 当前使用的实现类型 */ +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; +} diff --git a/code/sdk/zhihui/zhihui_net/src/zhihui_net_http.c b/code/sdk/zhihui/zhihui_net/src/zhihui_net_http.c new file mode 100644 index 000000000..cab90348e --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/src/zhihui_net_http.c @@ -0,0 +1,276 @@ +/** + * @file zhihui_net_http.c + * @brief 智慧网络统一接口层 - HTTP/HTTPS 实现 + * + * 支持动态切换 OpenHarmony 和 PPP 实现 + */ + +#include "zhihui_net_http.h" +#include "zhihui_net.h" +#include +#include +#include + +/* 包含 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); + } +} + diff --git a/code/sdk/zhihui/zhihui_net/src/zhihui_net_websocket.c b/code/sdk/zhihui/zhihui_net/src/zhihui_net_websocket.c new file mode 100644 index 000000000..174c11c50 --- /dev/null +++ b/code/sdk/zhihui/zhihui_net/src/zhihui_net_websocket.c @@ -0,0 +1,559 @@ +/** + * @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 +#include +#include +#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; + } +}