--- /dev/null
+/**
+ * @file
+ * HTTPS client implementation
+ */
+
+/*
+ * Copyright (c) 2023 DoubaoAssistant
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ */
+
+#include "lwip/apps/https_client.h"
+#include "lwip/altcp_tcp.h"
+#include "lwip/altcp_tls.h"
+#include "lwip/debug.h"
+#include "lwip/dns.h"
+#include "lwip/init.h"
+#include "lwip/mem.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if LWIP_TCP && LWIP_CALLBACK_API && LWIP_SSL
+
+/**
+ * HTTPSC_DEBUG: Enable debugging for HTTPS client.
+ */
+#ifndef HTTPSC_DEBUG
+#define HTTPSC_DEBUG LWIP_DBG_OFF
+#endif
+
+/** Set this to 1 to keep server name and uri in request state */
+#ifndef HTTPSC_DEBUG_REQUEST
+#define HTTPSC_DEBUG_REQUEST 0
+#endif
+
+/** This string is passed in the HTTP header as "User-Agent: " */
+#ifndef HTTPSC_CLIENT_AGENT
+#define HTTPSC_CLIENT_AGENT \
+ "lwIP/" LWIP_VERSION_STRING " (https://savannah.nongnu.org/projects/lwip)"
+#endif
+
+/* the various debug levels for this file */
+#define HTTPSC_DEBUG_TRACE (HTTPSC_DEBUG | LWIP_DBG_TRACE)
+#define HTTPSC_DEBUG_STATE (HTTPSC_DEBUG | LWIP_DBG_STATE)
+#define HTTPSC_DEBUG_WARN (HTTPSC_DEBUG | LWIP_DBG_LEVEL_WARNING)
+#define HTTPSC_DEBUG_WARN_STATE \
+ (HTTPSC_DEBUG | LWIP_DBG_LEVEL_WARNING | LWIP_DBG_STATE)
+#define HTTPSC_DEBUG_SERIOUS (HTTPSC_DEBUG | LWIP_DBG_LEVEL_SERIOUS)
+
+#define HTTPSC_POLL_INTERVAL 1
+#define HTTPSC_POLL_TIMEOUT 80 /* 15 seconds */
+
+#define HTTPSC_CONTENT_LEN_INVALID 0xFFFFFFFF
+
+/* 内部状态定义 */
+typedef enum httpsc_conn_state {
+ HTTPSC_STATE_INIT = 0,
+ HTTPSC_STATE_CONNECTING,
+ HTTPSC_STATE_SSL_HANDSHAKING,
+ HTTPSC_STATE_SENDING,
+ HTTPSC_STATE_RECEIVING,
+ HTTPSC_STATE_CLOSING,
+ HTTPSC_STATE_ERROR
+} httpsc_conn_state_t;
+
+/* 构建HTTP请求字符串 */
+static int httpsc_create_request_string(const httpsc_connection_t *settings,
+ const char *server_name,
+ int server_port, const char *uri,
+ int use_host, char *buffer,
+ size_t buffer_size) {
+ if (settings->use_proxy) {
+ LWIP_ASSERT("server_name != NULL", server_name != NULL);
+ if (server_port != HTTPS_DEFAULT_PORT) {
+ return snprintf(buffer, buffer_size,
+ "GET https://%s:%d%s HTTP/1.1\r\n"
+ "User-Agent: %s\r\n"
+ "Accept: */*\r\n"
+ "Host: %s\r\n"
+ "Connection: Close\r\n"
+ "\r\n",
+ server_name, server_port, uri, HTTPSC_CLIENT_AGENT,
+ server_name);
+ } else {
+ return snprintf(buffer, buffer_size,
+ "GET https://%s%s HTTP/1.1\r\n"
+ "User-Agent: %s\r\n"
+ "Accept: */*\r\n"
+ "Host: %s\r\n"
+ "Connection: Close\r\n"
+ "\r\n",
+ server_name, uri, HTTPSC_CLIENT_AGENT, server_name);
+ }
+ } else if (use_host) {
+ LWIP_ASSERT("server_name != NULL", server_name != NULL);
+ return snprintf(buffer, buffer_size,
+ "GET %s HTTP/1.1\r\n"
+ "User-Agent: %s\r\n"
+ "Accept: */*\r\n"
+ "Host: %s\r\n"
+ "Connection: Close\r\n"
+ "\r\n",
+ uri, HTTPSC_CLIENT_AGENT, server_name);
+ } else {
+ return snprintf(buffer, buffer_size,
+ "GET %s HTTP/1.1\r\n"
+ "User-Agent: %s\r\n"
+ "Accept: */*\r\n"
+ "Connection: Close\r\n"
+ "\r\n",
+ uri, HTTPSC_CLIENT_AGENT);
+ }
+}
+
+/** 初始化SSL/TLS上下文 */
+static err_t httpsc_init_ssl_context(httpsc_state_t *req,
+ const char *server_name) {
+ err_t err = ERR_OK;
+ const httpsc_ssl_config_t *ssl_config = req->conn_settings->ssl_config;
+
+ /* 初始化SSL上下文 */
+ err = altcp_tls_init(&req->ssl_ctx, ssl_config->altcp_allocator);
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(HTTPSC_DEBUG_SERIOUS,
+ ("httpsc_init_ssl_context: altcp_tls_init failed: %d\n", err));
+ return err;
+ }
+
+ /* 设置SSL/TLS版本 */
+ if (ssl_config->tls_versions) {
+ altcp_tls_set_versions(&req->ssl_ctx, ssl_config->tls_versions);
+ }
+
+ /* 设置密码套件 */
+ if (ssl_config->ciphers) {
+ altcp_tls_set_ciphers(&req->ssl_ctx, ssl_config->ciphers);
+ }
+
+ /* 设置证书验证回调 */
+ if (ssl_config->verify_cert) {
+ altcp_tls_set_verify(&req->ssl_ctx, ssl_config->verify_cert, req);
+ }
+
+ /* 设置信任的CA证书 */
+ if (ssl_config->trust_anchor && ssl_config->trust_anchor_len > 0) {
+ err = altcp_tls_set_trust_anchor(&req->ssl_ctx, ssl_config->trust_anchor,
+ ssl_config->trust_anchor_len);
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(
+ HTTPSC_DEBUG_SERIOUS,
+ ("httpsc_init_ssl_context: set trust anchor failed: %d\n", err));
+ altcp_tls_free(&req->ssl_ctx);
+ return err;
+ }
+ }
+
+ /* 设置是否验证证书 */
+ altcp_tls_set_verify_mode(
+ &req->ssl_ctx, ssl_config->verify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE);
+
+ /* 设置SNI */
+ if (ssl_config->sni_hostname) {
+ altcp_tls_set_sni(&req->ssl_ctx, ssl_config->sni_hostname);
+ } else if (server_name) {
+ altcp_tls_set_sni(&req->ssl_ctx, server_name);
+ }
+
+ return ERR_OK;
+}
+
+/** 释放HTTPS客户端状态和资源 */
+static err_t httpsc_free_state(httpsc_state_t *req) {
+ if (req == NULL) {
+ return ERR_OK;
+ }
+
+ /* 释放SSL上下文 */
+ if (req->ssl_ctx.tcp_pcb != NULL) {
+ altcp_tls_free(&req->ssl_ctx);
+ }
+
+ /* 释放请求缓冲区 */
+ if (req->request != NULL) {
+ pbuf_free(req->request);
+ req->request = NULL;
+ }
+
+ /* 释放接收的头部 */
+ if (req->rx_hdrs != NULL) {
+ pbuf_free(req->rx_hdrs);
+ req->rx_hdrs = NULL;
+ }
+
+ /* 关闭TCP连接 */
+ if (req->pcb != NULL) {
+ err_t err;
+ altcp_arg(req->pcb, NULL);
+ altcp_recv(req->pcb, NULL);
+ altcp_err(req->pcb, NULL);
+ altcp_poll(req->pcb, NULL, 0);
+ altcp_sent(req->pcb, NULL);
+
+ err = altcp_close(req->pcb);
+ if (err != ERR_OK) {
+ altcp_abort(req->pcb);
+ err = ERR_ABRT;
+ }
+ req->pcb = NULL;
+ return err;
+ }
+
+ /* 释放状态内存 */
+ mem_free(req);
+ return ERR_OK;
+}
+
+/** 关闭连接并调用回调 */
+static err_t httpsc_close(httpsc_state_t *req, httpsc_result_t result,
+ u32_t server_response, err_t err) {
+ if (req != NULL) {
+ /* 调用结果回调 */
+ if (req->conn_settings != NULL && req->conn_settings->result_fn != NULL) {
+ req->conn_settings->result_fn(req->callback_arg, result,
+ req->rx_content_len, server_response, err);
+ }
+
+ /* 释放资源 */
+ return httpsc_free_state(req);
+ }
+ return ERR_OK;
+}
+
+/** 解析HTTP响应状态行 */
+static err_t httpsc_parse_response_status(struct pbuf *p, u16_t *http_version,
+ u16_t *http_status,
+ u16_t *status_str_offset) {
+ u16_t end1 = pbuf_memfind(p, "\r\n", 2, 0);
+ if (end1 == 0xFFFF) {
+ return ERR_VAL;
+ }
+
+ /* 查找第一个空格 */
+ u16_t space1 = pbuf_memfind(p, " ", 1, 0);
+ if (space1 == 0xFFFF) {
+ return ERR_VAL;
+ }
+
+ /* 验证是否是HTTP响应 */
+ if (pbuf_memcmp(p, 0, "HTTP/", 5) != 0 || pbuf_get_at(p, 6) != '.') {
+ return ERR_VAL;
+ }
+
+ /* 解析HTTP版本 */
+ *http_version = ((pbuf_get_at(p, 5) - '0') << 8) | (pbuf_get_at(p, 7) - '0');
+
+ /* 查找第二个空格 */
+ u16_t space2 = pbuf_memfind(p, " ", 1, space1 + 1);
+ if (space2 == 0xFFFF) {
+ return ERR_VAL;
+ }
+
+ /* 解析状态码 */
+ char status_code[4] = {0};
+ u16_t len = space2 - space1 - 1;
+ if (len > 3) {
+ len = 3;
+ }
+
+ if (pbuf_copy_partial(p, status_code, len, space1 + 1) != len) {
+ return ERR_VAL;
+ }
+
+ *http_status = atoi(status_code);
+ *status_str_offset = space2 + 1;
+
+ return ERR_OK;
+}
+
+/** 等待所有HTTP头部接收完成 */
+static err_t httpsc_wait_headers(struct pbuf *p, u32_t *content_length,
+ u16_t *total_header_len) {
+ u16_t end = pbuf_memfind(p, "\r\n\r\n", 4, 0);
+ if (end == 0xFFFF) {
+ return ERR_VAL;
+ }
+
+ *total_header_len = end + 4;
+ *content_length = HTTPSC_CONTENT_LEN_INVALID;
+
+ /* 查找Content-Length头部 */
+ u16_t cl_pos = pbuf_memfind(p, "Content-Length: ", 16, 0);
+ if (cl_pos != 0xFFFF) {
+ u16_t cl_end = pbuf_memfind(p, "\r\n", 2, cl_pos);
+ if (cl_end != 0xFFFF) {
+ char cl_str[20] = {0};
+ u16_t len = cl_end - cl_pos - 16;
+ if (len < sizeof(cl_str) - 1) {
+ if (pbuf_copy_partial(p, cl_str, len, cl_pos + 16) == len) {
+ *content_length = atoi(cl_str);
+ }
+ }
+ }
+ }
+
+ return ERR_OK;
+}
+
+/** SSL/TLS握手完成回调 */
+static void httpsc_ssl_handshake_done(void *arg, err_t err) {
+ httpsc_state_t *req = (httpsc_state_t *)arg;
+
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(HTTPSC_DEBUG_WARN,
+ ("httpsc_ssl_handshake_done: failed: %d\n", err));
+ httpsc_close(req, HTTPSC_RESULT_ERR_SSL_HANDSHAKE, 0, err);
+ return;
+ }
+
+ LWIP_DEBUGF(HTTPSC_DEBUG_TRACE, ("httpsc_ssl_handshake_done: success\n"));
+
+ /* 发送HTTP请求 */
+ err = altcp_tls_write(&req->ssl_ctx, req->request->payload,
+ req->request->len - 1, 0);
+ if (err != ERR_OK) {
+ LWIP_DEBUGF(HTTPSC_DEBUG_WARN,
+ ("httpsc_ssl_handshake_done: write failed: %d\n", err));
+ httpsc_close(req, HTTPSC_RESULT_ERR_CONNECT, 0, err);
+ return;
+ }
+
+ /* 释放请求缓冲区 */
+ pbuf_free(req->request);
+ req->request = NULL;
+
+ /* 更新状态 */
+ req->parse_state = HTTPSC_PARSE_WAIT_FIRST_LINE;
+}
+
+/** TCP接收回调 */
+static err_t httpsc_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p,
+ err_t err) {
+ httpsc_state_t *req = (httpsc_state_t *)arg;
+
+ if (p == NULL) {
+ /* 连接关闭 */
+ httpsc_result_t result;
+
+ if (req->parse_state != HTTPSC_PARSE_RX_DATA) {
+ result = HTTPSC_RESULT_ERR_CLOSED;
+ } else if (req->hdr_content_len != HTTPSC_CONTENT_LEN_INVALID &&
+ req->hdr_content_len != req->rx_content_len) {
+ result = HTTPSC_RESULT_ERR_CONTENT_LEN;
+ } else {
+ result = HTTPSC_RESULT_OK;
+ }
+
+ return httpsc_close(req, result, req->rx_status, err);
+ }
+
+ /* 重置超时计数器 */
+ req->timeout_ticks = HTTPSC_POLL_TIMEOUT;
+
+ /* 处理接收到的数据 */
+ if (req->parse_state != HTTPSC_PARSE_RX_DATA) {
+ /* 处理头部 */
+ if (req->rx_hdrs == NULL) {
+ req->rx_hdrs = p;
+ } else {
+ pbuf_cat(req->rx_hdrs, p);
+ }
+
+ if (req->parse_state == HTTPSC_PARSE_WAIT_FIRST_LINE) {
+ /* 解析状态行 */
+ u16_t status_str_offset;
+ err_t res =
+ httpsc_parse_response_status(req->rx_hdrs, &req->rx_http_version,
+ &req->rx_status, &status_str_offset);
+ if (res == ERR_OK) {
+ req->parse_state = HTTPSC_PARSE_WAIT_HEADERS;
+ }
+ }
+
+ if (req->parse_state == HTTPSC_PARSE_WAIT_HEADERS) {
+ /* 等待所有头部接收完成 */
+ u16_t header_len;
+ err_t res =
+ httpsc_wait_headers(req->rx_hdrs, &req->hdr_content_len, &header_len);
+ if (res == ERR_OK) {
+ /* 调用头部处理回调 */
+ if (req->conn_settings != NULL &&
+ req->conn_settings->headers_done_fn != NULL) {
+ res = req->conn_settings->headers_done_fn(req, req->callback_arg,
+ req->rx_hdrs, header_len,
+ req->hdr_content_len);
+ if (res != ERR_OK) {
+ return httpsc_close(req, HTTPSC_RESULT_LOCAL_ABORT, req->rx_status,
+ res);
+ }
+ }
+
+ /* 从pbuf中移除头部,只保留数据部分 */
+ struct pbuf *data = pbuf_free_header(req->rx_hdrs, header_len);
+ pbuf_free(req->rx_hdrs);
+ req->rx_hdrs = NULL;
+
+ /* 更新状态并处理数据 */
+ req->parse_state = HTTPSC_PARSE_RX_DATA;
+ p = data;
+ }
+ }
+ }
+
+ /* 处理数据部分 */
+ if (p != NULL && req->parse_state == HTTPSC_PARSE_RX_DATA) {
+ req->rx_content_len += p->tot_len;
+
+ /* 调用数据接收回调 */
+ if (req->recv_fn != NULL) {
+ err = req->recv_fn(req->callback_arg, pcb, p, err);
+ } else {
+ altcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+ err = ERR_OK;
+ }
+ }
+
+ return err;
+}
+
+/** TCP错误回调 */
+static void httpsc_tcp_err(void *arg, err_t err) {
+ httpsc_state_t *req = (httpsc_state_t *)arg;
+
+ LWIP_DEBUGF(HTTPSC_DEBUG_WARN, ("httpsc_tcp_err: error %d\n", err));
+
+ if (req != NULL) {
+ httpsc_close(req, HTTPSC_RESULT_ERR_CONNECT, 0, err);
+ }
+}
+
+/** TCP轮询回调 */
+static err_t httpsc_tcp_poll(void *arg, struct altcp_pcb *pcb) {
+ httpsc_state_t *req = (httpsc_state_t *)arg;
+
+ LWIP_UNUSED_ARG(pcb);
+
+ if (req != NULL) {
+ if (req->timeout_ticks > 0) {
+ req->timeout_ticks--;
+ }
+
+ if (req->timeout_ticks == 0) {
+ LWIP_DEBUGF(HTTPSC_DEBUG_WARN, ("httpsc_tcp_poll: timeout\n"));
+ return httpsc_close(req, HTTPSC_RESULT_ERR_TIMEOUT, 0, ERR_OK);
+ }
+ }
+
+ return ERR_OK;
+}
+
+/** 初始化HTTPS连接 */
+static err_t httpsc_init_connection_common(httpsc_state_t **connection,
+ const httpsc_connection_t *settings,
+ const char *server_name,
+ u16_t server_port, const char *uri,
+ altcp_recv_fn recv_fn,
+ void *callback_arg, int use_host) {
+ err_t err;
+ httpsc_state_t *req;
+ char request_buf[1024];
+ int request_len;
+
+ LWIP_ASSERT("uri != NULL", uri != NULL);
+
+ /* 构建HTTP请求字符串 */
+ request_len =
+ httpsc_create_request_string(settings, server_name, server_port, uri,
+ use_host, request_buf, sizeof(request_buf));
+ if (request_len <= 0 || request_len >= sizeof(request_buf)) {
+ return ERR_VAL;
+ }
+
+ /* 分配状态结构 */
+ req = (httpsc_state_t *)mem_malloc(sizeof(httpsc_state_t));
+ if (req == NULL) {
+ return ERR_MEM;
+ }
+ memset(req, 0, sizeof(httpsc_state_t));
+
+ /* 初始化状态 */
+ req->remote_port = settings->use_proxy ? settings->proxy_port : server_port;
+ req->timeout_ticks = HTTPSC_POLL_TIMEOUT;
+ req->parse_state = HTTPSC_PARSE_WAIT_FIRST_LINE;
+ req->conn_settings = settings;
+ req->recv_fn = recv_fn;
+ req->callback_arg = callback_arg;
+
+ /* 分配请求缓冲区 */
+ req->request = pbuf_alloc(PBUF_RAW, request_len + 1, PBUF_RAM);
+ if (req->request == NULL) {
+ mem_free(req);
+ return ERR_MEM;
+ }
+
+ /* 复制请求数据 */
+ memcpy(req->request->payload, request_buf, request_len);
+ ((char *)req->request->payload)[request_len] =
+ '\0'; /* 确保字符串以NULL结尾 */
+
+ /* 创建TCP控制块 */
+ req->pcb = altcp_new(settings->altcp_allocator);
+ if (req->pcb == NULL) {
+ pbuf_free(req->request);
+ mem_free(req);
+ return ERR_MEM;
+ }
+
+ /* 设置TCP回调 */
+ altcp_arg(req->pcb, req);
+ altcp_recv(req->pcb, httpsc_tcp_recv);
+ altcp_err(req->pcb, httpsc_tcp_err);
+ altcp_poll(req->pcb, httpsc_tcp_poll, HTTPSC_POLL_INTERVAL);
+
+ /* 初始化SSL上下文 */
+ err = httpsc_init_ssl_context(req, server_name);
+ if (err != ERR_OK) {
+ altcp_abort(req->pcb);
+ pbuf_free(req->request);
+ mem_free(req);
+ return err;
+ }
+
+ /* 绑定SSL上下文到TCP控制块 */
+ err = altcp_tls_bind(req->pcb, &req->ssl_ctx, httpsc_ssl_handshake_done);
+ if (err != ERR_OK) {
+ altcp_abort(req->pcb);
+ pbuf_free(req->request);
+ mem_free(req);
+ return err;
+ }
+
+ *connection = req;
+ return ERR_OK;
+}
+
+/** 通过IP地址获取文件 */
+err_t httpsc_get_file(const ip_addr_t *server_addr, u16_t port, const char *uri,
+ const httpsc_connection_t *settings,
+ altcp_recv_fn recv_fn, void *callback_arg,
+ httpsc_state_t **connection) {
+ err_t err;
+ httpsc_state_t *req;
+ char *server_name_str = NULL;
+
+ LWIP_ASSERT("server_addr != NULL", server_addr != NULL);
+ LWIP_ASSERT("uri != NULL", uri != NULL);
+
+ /* 将IP地址转换为字符串 */
+ server_name_str = ipaddr_ntoa(server_addr);
+ if (server_name_str == NULL) {
+ return ERR_VAL;
+ }
+
+ /* 初始化连接 */
+ err = httpsc_init_connection_common(&req, settings, server_name_str, port,
+ uri, recv_fn, callback_arg, 1);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ /* 连接到服务器 */
+ if (settings->use_proxy) {
+ err = altcp_connect(req->pcb, &settings->proxy_addr, settings->proxy_port,
+ NULL);
+ } else {
+ err = altcp_connect(req->pcb, server_addr, port, NULL);
+ }
+
+ if (err != ERR_OK) {
+ httpsc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+
+ return ERR_OK;
+}
+
+/** 通过域名获取文件 */
+err_t httpsc_get_file_dns(const char *server_name, u16_t port, const char *uri,
+ const httpsc_connection_t *settings,
+ altcp_recv_fn recv_fn, void *callback_arg,
+ httpsc_state_t **connection) {
+ err_t err;
+ httpsc_state_t *req;
+
+ LWIP_ASSERT("server_name != NULL", server_name != NULL);
+ LWIP_ASSERT("uri != NULL", uri != NULL);
+
+ /* 初始化连接 */
+ err = httpsc_init_connection_common(&req, settings, server_name, port, uri,
+ recv_fn, callback_arg, 1);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ /* 解析域名 */
+ if (settings->use_proxy) {
+ err = altcp_connect(req->pcb, &settings->proxy_addr, settings->proxy_port,
+ NULL);
+ } else {
+ err = dns_gethostbyname(server_name, &req->remote_addr,
+ (dns_found_callback)httpsc_get_file, req);
+ if (err == ERR_INPROGRESS) {
+ /* 域名解析正在进行中 */
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+ } else if (err != ERR_OK) {
+ /* 域名解析失败 */
+ httpsc_free_state(req);
+ return err;
+ }
+
+ /* 域名已缓存或直接是IP地址字符串 */
+ err = altcp_connect(req->pcb, &req->remote_addr, port, NULL);
+ }
+
+ if (err != ERR_OK) {
+ httpsc_free_state(req);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+
+ return ERR_OK;
+}
+
+#if LWIP_HTTPSC_HAVE_FILE_IO
+/* 文件下载功能实现 */
+
+typedef struct _httpsc_filestate {
+ const char *local_file_name;
+ FILE *file;
+ httpsc_connection_t settings;
+ const httpsc_connection_t *client_settings;
+ void *callback_arg;
+} httpsc_filestate_t;
+
+/** 文件接收回调 */
+static err_t httpsc_fs_tcp_recv(void *arg, struct altcp_pcb *pcb,
+ struct pbuf *p, err_t err) {
+ httpsc_filestate_t *fs = (httpsc_filestate_t *)arg;
+ struct pbuf *q;
+
+ if (p == NULL) {
+ /* 连接关闭 */
+ return err;
+ }
+
+ /* 将数据写入文件 */
+ for (q = p; q != NULL; q = q->next) {
+ if (fs->file) {
+ fwrite(q->payload, 1, q->len, fs->file);
+ }
+ }
+
+ altcp_recved(pcb, p->tot_len);
+ pbuf_free(p);
+
+ return ERR_OK;
+}
+
+/** 文件下载完成回调 */
+static void httpsc_fs_result(void *arg, httpsc_result_t result,
+ u32_t rx_content_len, u32_t srv_res, err_t err) {
+ httpsc_filestate_t *fs = (httpsc_filestate_t *)arg;
+
+ /* 关闭文件 */
+ if (fs->file) {
+ fclose(fs->file);
+ fs->file = NULL;
+ }
+
+ /* 调用用户回调 */
+ if (fs->client_settings->result_fn) {
+ fs->client_settings->result_fn(fs->callback_arg, result, rx_content_len,
+ srv_res, err);
+ }
+
+ /* 释放内存 */
+ mem_free(fs);
+}
+
+/** 初始化文件下载状态 */
+static err_t httpsc_fs_init(httpsc_filestate_t **filestate_out,
+ const char *local_file_name,
+ const httpsc_connection_t *settings,
+ void *callback_arg) {
+ httpsc_filestate_t *fs;
+ size_t name_len;
+
+ name_len = strlen(local_file_name);
+ fs = (httpsc_filestate_t *)mem_malloc(sizeof(httpsc_filestate_t) + name_len +
+ 1);
+ if (fs == NULL) {
+ return ERR_MEM;
+ }
+
+ memset(fs, 0, sizeof(httpsc_filestate_t));
+ fs->local_file_name = (const char *)(fs + 1);
+ memcpy((char *)fs->local_file_name, local_file_name, name_len + 1);
+ fs->client_settings = settings;
+ fs->callback_arg = callback_arg;
+
+ /* 复制设置并替换回调函数 */
+ memcpy(&fs->settings, settings, sizeof(httpsc_connection_t));
+ fs->settings.result_fn = httpsc_fs_result;
+
+ /* 打开文件 */
+ fs->file = fopen(local_file_name, "wb");
+ if (fs->file == NULL) {
+ mem_free(fs);
+ return ERR_VAL;
+ }
+
+ *filestate_out = fs;
+ return ERR_OK;
+}
+
+/** 通过IP地址下载文件到磁盘 */
+err_t httpsc_get_file_to_disk(const ip_addr_t *server_addr, u16_t port,
+ const char *uri,
+ const httpsc_connection_t *settings,
+ void *callback_arg, const char *local_file_name,
+ httpsc_state_t **connection) {
+ err_t err;
+ httpsc_filestate_t *fs;
+ httpsc_state_t *req;
+
+ LWIP_ASSERT("server_addr != NULL", server_addr != NULL);
+ LWIP_ASSERT("uri != NULL", uri != NULL);
+ LWIP_ASSERT("local_file_name != NULL", local_file_name != NULL);
+
+ /* 初始化文件状态 */
+ err = httpsc_fs_init(&fs, local_file_name, settings, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ /* 初始化连接 */
+ char *server_name_str = ipaddr_ntoa(server_addr);
+ if (server_name_str == NULL) {
+ mem_free(fs);
+ return ERR_VAL;
+ }
+
+ err = httpsc_init_connection_common(&req, &fs->settings, server_name_str,
+ port, uri, httpsc_fs_tcp_recv, fs, 1);
+ if (err != ERR_OK) {
+ mem_free(fs);
+ return err;
+ }
+
+ /* 连接到服务器 */
+ if (settings->use_proxy) {
+ err = altcp_connect(req->pcb, &settings->proxy_addr, settings->proxy_port,
+ NULL);
+ } else {
+ err = altcp_connect(req->pcb, server_addr, port, NULL);
+ }
+
+ if (err != ERR_OK) {
+ httpsc_free_state(req);
+ mem_free(fs);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+
+ return ERR_OK;
+}
+
+/** 通过域名下载文件到磁盘 */
+err_t httpsc_get_file_dns_to_disk(const char *server_name, u16_t port,
+ const char *uri,
+ const httpsc_connection_t *settings,
+ void *callback_arg,
+ const char *local_file_name,
+ httpsc_state_t **connection) {
+ err_t err;
+ httpsc_filestate_t *fs;
+ httpsc_state_t *req;
+
+ LWIP_ASSERT("server_name != NULL", server_name != NULL);
+ LWIP_ASSERT("uri != NULL", uri != NULL);
+ LWIP_ASSERT("local_file_name != NULL", local_file_name != NULL);
+
+ /* 初始化文件状态 */
+ err = httpsc_fs_init(&fs, local_file_name, settings, callback_arg);
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ /* 初始化连接 */
+ err = httpsc_init_connection_common(&req, &fs->settings, server_name, port,
+ uri, httpsc_fs_tcp_recv, fs, 1);
+ if (err != ERR_OK) {
+ mem_free(fs);
+ return err;
+ }
+
+ /* 解析域名 */
+ if (settings->use_proxy) {
+ err = altcp_connect(req->pcb, &settings->proxy_addr, settings->proxy_port,
+ NULL);
+ } else {
+ err = dns_gethostbyname(server_name, &req->remote_addr,
+ (dns_found_callback)httpsc_get_file, req);
+ if (err == ERR_INPROGRESS) {
+ /* 域名解析正在进行中 */
+ if (connection != NULL) {
+ *connection = req;
+ }
+ return ERR_OK;
+ } else if (err != ERR_OK) {
+ /* 域名解析失败 */
+ httpsc_free_state(req);
+ mem_free(fs);
+ return err;
+ }
+
+ /* 域名已缓存或直接是IP地址字符串 */
+ err = altcp_connect(req->pcb, &req->remote_addr, port, NULL);
+ }
+
+ if (err != ERR_OK) {
+ httpsc_free_state(req);
+ mem_free(fs);
+ return err;
+ }
+
+ if (connection != NULL) {
+ *connection = req;
+ }
+
+ return ERR_OK;
+}
+#endif /* LWIP_HTTPSC_HAVE_FILE_IO */
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API && LWIP_SSL */
\ No newline at end of file
--- /dev/null
+/**
+ * @file
+ * WebSocket 服务器实现(支持 HTTP/HTTPS 双协议)
+ */
+
+#include "lwip/apps/websocket.h"
+#include "lwip/altcp.h"
+#include "lwip/altcp_tcp.h"
+#include "lwip/def.h"
+#include "lwip/mem.h"
+#include "lwip/sys.h"
+#include <string.h>
+#include <stdlib.h>
+#include "lwip/apps/https_client.h" // 引入 HTTPS 相关头文件
+
+#if LWIP_TCP && LWIP_CALLBACK_API
+
+#define WS_MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" // WebSocket 魔法密钥
+#define WS_HANDSHAKE_RESPONSE_HTTP \ // HTTP 握手响应(非加密)
+ "HTTP/1.1 101 Switching Protocols\r\n" \
+ "Upgrade: websocket\r\n" \
+ "Connection: Upgrade\r\n" \
+ "Sec-WebSocket-Accept: %s\r\n\r\n"
+#define WS_HANDSHAKE_RESPONSE_HTTPS WS_HANDSHAKE_RESPONSE_HTTP // HTTPS 握手响应(加密通道发送)
+
+/* WebSocket 连接状态(扩展:区分 HTTP/HTTPS 连接) */
+struct _websocket_conn {
+ struct altcp_pcb *pcb; // TCP 控制块指针
+ websocket_settings_t settings; // 连接配置参数
+ struct pbuf *rx_buf; // 接收缓冲区
+ size_t rx_offset; // 接收偏移量
+ u8_t is_websocket:1; // 是否已完成 WebSocket 握手
+ u8_t is_secure:1; // 是否为 HTTPS/WSS 连接
+ u8_t is_closing:1; // 连接关闭标志
+ struct altcp_tls_context ssl_ctx; // SSL/TLS 上下文(仅 HTTPS 连接使用)
+};
+
+static err_t websocket_handshake(websocket_conn_t *conn, struct pbuf *p);
+static err_t websocket_parse_frame(websocket_conn_t *conn, struct pbuf *p);
+static err_t websocket_send_frame(websocket_conn_t *conn, u8_t opcode,
+ const void *data, size_t len, u8_t is_fin);
+static void websocket_close_conn(websocket_conn_t *conn, u16_t reason,
+ const char *reason_msg, size_t reason_msg_len);
+
+/* 检查连接是否为 HTTPS/WSS */
+u8_t websocket_is_secure(websocket_conn_t *conn) {
+ return conn ? conn->is_secure : 0;
+}
+
+/* TCP 连接接受回调(区分 HTTP/HTTPS 连接) */
+static err_t websocket_accept(void *arg, struct altcp_pcb *pcb, err_t err) {
+ websocket_settings_t *settings = (websocket_settings_t *)arg;
+ websocket_conn_t *conn;
+ LWIP_UNUSED_ARG(err);
+
+ if (pcb == NULL || settings == NULL) {
+ return ERR_ARG;
+ }
+
+ conn = (websocket_conn_t *)mem_calloc(1, sizeof(websocket_conn_t));
+ if (conn == NULL) {
+ altcp_abort(pcb);
+ return ERR_MEM;
+ }
+
+ conn->pcb = pcb;
+ memcpy(&conn->settings, settings, sizeof(websocket_settings_t));
+ conn->rx_buf = NULL;
+ conn->rx_offset = 0;
+ conn->is_websocket = 0;
+ conn->is_closing = 0;
+
+ // 判断是否为 HTTPS 连接(根据端口或 SSL 配置)
+ conn->is_secure = (settings->ssl_config != NULL);
+ if (conn->is_secure) {
+ // 初始化 SSL/TLS 上下文
+ if (altcp_tls_init(&conn->ssl_ctx, settings->altcp_allocator) != ERR_OK) {
+ mem_free(conn);
+ altcp_abort(pcb);
+ return ERR_MEM;
+ }
+
+ // 配置 SSL/TLS 参数
+ if (settings->ssl_config) {
+ altcp_tls_set_config(&conn->ssl_ctx, settings->ssl_config);
+ }
+ }
+
+ altcp_arg(pcb, conn);
+ altcp_recv(pcb, websocket_tcp_recv);
+ altcp_err(pcb, websocket_tcp_err);
+ altcp_poll(pcb, websocket_tcp_poll, 2);
+
+ return ERR_OK;
+}
+
+/* TCP 接收回调(处理 HTTP/HTTPS 数据) */
+static err_t websocket_tcp_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err) {
+ websocket_conn_t *conn = (websocket_conn_t *)arg;
+ err_t ret = ERR_OK;
+
+ if (err != ERR_OK || p == NULL) {
+ // 连接关闭或错误
+ if (conn != NULL) {
+ if (conn->settings.close_fn) {
+ conn->settings.close_fn(conn, conn->settings.arg);
+ }
+ if (conn->is_secure) {
+ altcp_tls_free(&conn->ssl_ctx);
+ }
+ if (conn->rx_buf != NULL) {
+ pbuf_free(conn->rx_buf);
+ }
+ mem_free(conn);
+ }
+ return ERR_OK;
+ }
+
+ if (conn == NULL || conn->is_closing) {
+ pbuf_free(p);
+ return ERR_ARG;
+ }
+
+ // 未完成 WebSocket 握手,处理 HTTP/HTTPS 请求
+ if (!conn->is_websocket) {
+ if (conn->is_secure) {
+ // HTTPS 连接:先进行 TLS 解密(简化处理,实际需调用 altcp_tls_read)
+ struct pbuf *decrypted = NULL;
+ err = altcp_tls_read(&conn->ssl_ctx, &decrypted, 0);
+ if (err == ERR_OK && decrypted) {
+ ret = websocket_handshake(conn, decrypted);
+ pbuf_free(decrypted);
+ } else {
+ ret = err;
+ }
+ } else {
+ // HTTP 连接:直接处理明文
+ ret = websocket_handshake(conn, p);
+ }
+ pbuf_free(p);
+ return ret;
+ }
+
+ // 已完成握手,处理 WebSocket 帧
+ ret = websocket_parse_frame(conn, p);
+ pbuf_free(p);
+ return ret;
+}
+
+/* TCP 错误回调 */
+static void websocket_tcp_err(void *arg, err_t err) {
+ websocket_conn_t *conn = (websocket_conn_t *)arg;
+ LWIP_UNUSED_ARG(err);
+
+ if (conn != NULL) {
+ if (conn->settings.close_fn) {
+ conn->settings.close_fn(conn, conn->settings.arg);
+ }
+ if (conn->is_secure) {
+ altcp_tls_free(&conn->ssl_ctx);
+ }
+ if (conn->rx_buf != NULL) {
+ pbuf_free(conn->rx_buf);
+ }
+ mem_free(conn);
+ }
+}
+
+/* TCP 轮询回调 */
+static err_t websocket_tcp_poll(void *arg, struct altcp_pcb *pcb) {
+ websocket_conn_t *conn = (websocket_conn_t *)arg;
+ LWIP_UNUSED_ARG(pcb);
+
+ if (conn == NULL) {
+ return ERR_ARG;
+ }
+
+ // 启用心跳机制,定期发送 PING 帧
+ if (conn->settings.enable_ping_pong && conn->is_websocket && !conn->is_closing) {
+ return websocket_send_frame(conn, WS_OPCODE_PING, NULL, 0, 1);
+ }
+
+ return ERR_OK;
+}
+
+/* 初始化 WebSocket 服务器 */
+err_t websocket_server_init(u16_t port, const websocket_settings_t *settings) {
+ struct altcp_pcb *pcb;
+ err_t err;
+
+ if (settings == NULL || settings->recv_fn == NULL) {
+ return ERR_ARG;
+ }
+
+ // 创建 TCP 控制块
+ pcb = altcp_new(settings->altcp_allocator ? settings->altcp_allocator() : altcp_tcp_alloc());
+ if (pcb == NULL) {
+ return ERR_MEM;
+ }
+
+ // 绑定端口
+ err = altcp_bind(pcb, IP_ADDR_ANY, port);
+ if (err != ERR_OK) {
+ altcp_close(pcb);
+ return err;
+ }
+
+ // 开始监听
+ pcb = altcp_listen(pcb);
+ if (pcb == NULL) {
+ return ERR_MEM;
+ }
+
+ // 设置回调函数
+ altcp_arg(pcb, (void *)settings);
+ altcp_accept(pcb, websocket_accept);
+
+ return ERR_OK;
+}
+
+/* 通过 WebSocket 发送数据 */
+err_t websocket_send(websocket_conn_t *conn, u8_t opcode,
+ const void *data, size_t len, u8_t is_fin) {
+ if (conn == NULL || !conn->is_websocket || conn->is_closing) {
+ return ERR_ARG;
+ }
+
+ return websocket_send_frame(conn, opcode, data, len, is_fin);
+}
+
+/* 关闭 WebSocket 连接 */
+err_t websocket_close(websocket_conn_t *conn, u16_t reason,
+ const char *reason_msg, size_t reason_msg_len) {
+ if (conn == NULL || !conn->is_websocket || conn->is_closing) {
+ return ERR_ARG;
+ }
+
+ websocket_close_conn(conn, reason, reason_msg, reason_msg_len);
+ return ERR_OK;
+}
+
+/* 处理 WebSocket 握手(HTTP 升级请求) */
+static err_t websocket_handshake(websocket_conn_t *conn, struct pbuf *p) {
+ char *data, *key, *response;
+ char accept_key[29];
+ size_t data_len, response_len;
+ err_t err;
+
+ // 检查是否接收到完整的 HTTP 请求(包含 \r\n\r\n)
+ if (pbuf_memfind(p, "\r\n\r\n", 4, 0) == 0xFFFF) {
+ return ERR_INPROGRESS; // 数据不完整
+ }
+
+ // 分配内存并复制请求数据
+ data_len = p->tot_len + 1;
+ data = (char *)mem_malloc(data_len);
+ if (data == NULL) {
+ return ERR_MEM;
+ }
+
+ if (pbuf_copy_partial(p, data, p->tot_len, 0) != p->tot_len) {
+ mem_free(data);
+ return ERR_VAL;
+ }
+ data[p->tot_len] = '\0';
+
+ // 验证是否为 WebSocket 升级请求
+ if (strstr(data, "Upgrade: websocket") == NULL ||
+ strstr(data, "Connection: Upgrade") == NULL) {
+ mem_free(data);
+ return ERR_VAL; // 非 WebSocket 请求
+ }
+
+ // 提取 Sec-WebSocket-Key
+ key = strstr(data, "Sec-WebSocket-Key: ");
+ if (key == NULL) {
+ mem_free(data);
+ return ERR_VAL;
+ }
+ key += 19; // 跳过 "Sec-WebSocket-Key: " 前缀
+
+ // 计算 Sec-WebSocket-Accept
+ {
+ unsigned char sha1[20];
+ char combined[60];
+ size_t key_len = strcspn(key, "\r\n");
+
+ if (key_len != 24) {
+ mem_free(data);
+ return ERR_VAL;
+ }
+
+ snprintf(combined, sizeof(combined), "%.*s%s", (int)key_len, key, WS_MAGIC_KEY);
+ mbedtls_sha1((unsigned char *)combined, strlen(combined), sha1);
+ base64_encode(sha1, sizeof(sha1), accept_key, sizeof(accept_key));
+ }
+
+ // 生成握手响应
+ response_len = strlen(WS_HANDSHAKE_RESPONSE_HTTP) + strlen(accept_key);
+ response = (char *)mem_malloc(response_len);
+ if (response == NULL) {
+ mem_free(data);
+ return ERR_MEM;
+ }
+
+ snprintf(response, response_len, WS_HANDSHAKE_RESPONSE_HTTP, accept_key);
+
+ // 发送握手响应(根据连接类型选择普通或加密通道)
+ if (conn->is_secure) {
+ err = altcp_tls_write(&conn->ssl_ctx, response, strlen(response), 0);
+ } else {
+ err = altcp_write(conn->pcb, response, strlen(response), TCP_WRITE_FLAG_COPY);
+ }
+
+ mem_free(response);
+ mem_free(data);
+
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ conn->is_websocket = 1; // 标记为已完成 WebSocket 握手
+ return ERR_OK;
+}
+
+/* 解析 WebSocket 帧 */
+static err_t websocket_parse_frame(websocket_conn_t *conn, struct pbuf *p) {
+ websocket_frame_t frame;
+ u8_t *payload;
+ size_t payload_len, i;
+ u16_t offset = 0;
+ err_t err;
+
+ // 读取帧头前两个字节
+ if (pbuf_copy_partial(p, &frame, 2, offset) != 2) {
+ return ERR_VAL;
+ }
+ offset += 2;
+
+ // 解析帧头字段
+ u8_t opcode = frame.fin_opcode & 0x0F;
+ u8_t is_fin = (frame.fin_opcode & 0x80) ? 1 : 0;
+ u8_t is_masked = (frame.mask_len & 0x80) ? 1 : 0;
+ u64_t payload_length = frame.mask_len & 0x7F;
+
+ // 处理扩展长度字段
+ if (payload_length == 126) {
+ if (pbuf_copy_partial(p, &frame.len.len16, 2, offset) != 2) {
+ return ERR_VAL;
+ }
+ payload_length = ntohs(frame.len.len16);
+ offset += 2;
+ } else if (payload_length == 127) {
+ if (pbuf_copy_partial(p, &frame.len.len64, 8, offset) != 8) {
+ return ERR_VAL;
+ }
+ payload_length = be64toh(frame.len.len64);
+ offset += 8;
+ }
+
+ // 检查帧大小是否超过限制
+ if (conn->settings.max_frame_size > 0 && payload_length > conn->settings.max_frame_size) {
+ websocket_close_conn(conn, WS_CLOSE_TOO_LARGE, "Frame too large", 15);
+ return ERR_VAL;
+ }
+
+ // 读取掩码密钥(如果有)
+ if (is_masked) {
+ if (pbuf_copy_partial(p, frame.mask_key, 4, offset) != 4) {
+ return ERR_VAL;
+ }
+ offset += 4;
+ }
+
+ // 分配内存并读取有效载荷
+ payload = (u8_t *)mem_malloc(payload_length);
+ if (payload == NULL) {
+ return ERR_MEM;
+ }
+
+ if (pbuf_copy_partial(p, payload, payload_length, offset) != payload_length) {
+ mem_free(payload);
+ return ERR_VAL;
+ }
+
+ // 应用掩码(如果有)
+ if (is_masked) {
+ for (i = 0; i < payload_length; i++) {
+ payload[i] ^= frame.mask_key[i % 4];
+ }
+ }
+
+ // 处理控制帧
+ switch (opcode) {
+ case WS_OPCODE_CLOSE:
+ websocket_close_conn(conn, WS_CLOSE_NORMAL, NULL, 0);
+ mem_free(payload);
+ return ERR_OK;
+
+ case WS_OPCODE_PING:
+ err = websocket_send_frame(conn, WS_OPCODE_PONG, payload, payload_length, 1);
+ mem_free(payload);
+ return err;
+
+ case WS_OPCODE_PONG:
+ mem_free(payload);
+ return ERR_OK;
+ }
+
+ // 调用用户回调处理数据帧
+ err = conn->settings.recv_fn(conn, opcode, payload, payload_length, is_fin, conn->settings.arg);
+ mem_free(payload);
+ return err;
+}
+
+/* 发送 WebSocket 帧 */
+static err_t websocket_send_frame(websocket_conn_t *conn, u8_t opcode,
+ const void *data, size_t len, u8_t is_fin) {
+ u8_t *buffer;
+ size_t frame_len, offset = 0;
+ err_t err;
+
+ // 计算帧长度
+ frame_len = 2; // 基础帧头长度
+ if (len > 65535) {
+ frame_len += 8; // 64位长度字段
+ } else if (len > 125) {
+ frame_len += 2; // 16位长度字段
+ }
+ frame_len += len; // 有效载荷长度
+
+ // 分配帧缓冲区
+ buffer = (u8_t *)mem_malloc(frame_len);
+ if (buffer == NULL) {
+ return ERR_MEM;
+ }
+
+ // 构建帧头
+ buffer[offset++] = (is_fin ? 0x80 : 0x00) | (opcode & 0x0F);
+
+ if (len > 65535) {
+ buffer[offset++] = 127;
+ u64_t len_be = htobe64(len);
+ memcpy(&buffer[offset], &len_be, 8);
+ offset += 8;
+ } else if (len > 125) {
+ buffer[offset++] = 126;
+ u16_t len_be = htons(len);
+ memcpy(&buffer[offset], &len_be, 2);
+ offset += 2;
+ } else {
+ buffer[offset++] = len;
+ }
+
+ // 复制有效载荷
+ if (len > 0 && data != NULL) {
+ memcpy(&buffer[offset], data, len);
+ }
+
+ // 发送帧(根据连接类型选择普通或加密通道)
+ if (conn->is_secure) {
+ err = altcp_tls_write(&conn->ssl_ctx, buffer, frame_len, 0);
+ } else {
+ err = altcp_write(conn->pcb, buffer, frame_len, TCP_WRITE_FLAG_COPY);
+ }
+
+ mem_free(buffer);
+
+ if (err != ERR_OK) {
+ return err;
+ }
+
+ return ERR_OK;
+}
+
+/* 关闭 WebSocket 连接 */
+static void websocket_close_conn(websocket_conn_t *conn, u16_t reason,
+ const char *reason_msg, size_t reason_msg_len) {
+ if (conn->is_closing) {
+ return;
+ }
+
+ conn->is_closing = 1;
+
+ // 构建关闭帧
+ u8_t close_frame[128];
+ size_t close_frame_len = 0;
+
+ if (reason != WS_CLOSE_NO_STATUS) {
+ close_frame[0] = (reason >> 8) & 0xFF;
+ close_frame[1] = reason & 0xFF;
+ close_frame_len = 2;
+
+ if (reason_msg != NULL && reason_msg_len > 0) {
+ if (reason_msg_len > sizeof(close_frame) - 2) {
+ reason_msg_len = sizeof(close_frame) - 2;
+ }
+ memcpy(&close_frame[2], reason_msg, reason_msg_len);
+ close_frame_len += reason_msg_len;
+ }
+ }
+
+ // 发送关闭帧
+ websocket_send_frame(conn, WS_OPCODE_CLOSE, close_frame, close_frame_len, 1);
+
+ // 关闭连接
+ altcp_arg(conn->pcb, NULL);
+ altcp_recv(conn->pcb, NULL);
+ altcp_err(conn->pcb, NULL);
+ altcp_poll(conn->pcb, NULL, 0);
+
+ if (conn->is_secure) {
+ altcp_tls_free(&conn->ssl_ctx);
+ }
+
+ if (conn->rx_buf != NULL) {
+ pbuf_free(conn->rx_buf);
+ }
+
+ if (conn->settings.close_fn) {
+ conn->settings.close_fn(conn, conn->settings.arg);
+ }
+
+ mem_free(conn);
+}
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API */
\ No newline at end of file
--- /dev/null
+/**
+ * @file
+ * HTTPS client
+ */
+
+/*
+ * Copyright (c) 2023 DoubaoAssistant
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ */
+
+#ifndef LWIP_HDR_APPS_HTTPS_CLIENT_H
+#define LWIP_HDR_APPS_HTTPS_CLIENT_H
+
+#include "lwip/opt.h"
+#include "lwip/ip_addr.h"
+#include "lwip/err.h"
+#include "lwip/altcp.h"
+#include "lwip/prot/iana.h"
+#include "lwip/pbuf.h"
+
+/* 确保SSL/TLS支持已启用 */
+#if LWIP_TCP && LWIP_CALLBACK_API && LWIP_SSL
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @ingroup httpc
+ * HTTPSC_HAVE_FILE_IO: 定义为1以启用直接下载到磁盘的功能
+ * 通过fopen/fwrite实现。这些函数仅是接口的示例实现。
+ */
+#ifndef LWIP_HTTPSC_HAVE_FILE_IO
+#define LWIP_HTTPSC_HAVE_FILE_IO 0
+#endif
+
+
+/**
+ * @ingroup httpc
+ * HTTPS使用的默认TCP端口
+ */
+#define HTTPS_DEFAULT_PORT LWIP_IANA_PORT_HTTPS
+
+/**
+ * @ingroup httpc
+ * HTTPS客户端结果码
+ */
+typedef enum ehttpsc_result {
+ /** 文件成功接收 */
+ HTTPSC_RESULT_OK = 0,
+ /** 未知错误 */
+ HTTPSC_RESULT_ERR_UNKNOWN = 1,
+ /** 连接服务器失败 */
+ HTTPSC_RESULT_ERR_CONNECT = 2,
+ /** 解析服务器主机名失败 */
+ HTTPSC_RESULT_ERR_HOSTNAME = 3,
+ /** 远程服务器意外关闭连接 */
+ HTTPSC_RESULT_ERR_CLOSED = 4,
+ /** 连接超时(服务器未及时响应) */
+ HTTPSC_RESULT_ERR_TIMEOUT = 5,
+ /** 服务器返回错误码 */
+ HTTPSC_RESULT_ERR_SVR_RESP = 6,
+ /** 本地内存错误 */
+ HTTPSC_RESULT_ERR_MEM = 7,
+ /** 本地主动中止 */
+ HTTPSC_RESULT_LOCAL_ABORT = 8,
+ /** 内容长度不匹配 */
+ HTTPSC_RESULT_ERR_CONTENT_LEN = 9,
+ /** SSL/TLS握手失败 */
+ HTTPSC_RESULT_ERR_SSL_HANDSHAKE = 10,
+ /** SSL/TLS证书验证失败 */
+ HTTPSC_RESULT_ERR_SSL_CERT = 11,
+ /** SSL/TLS协议错误 */
+ HTTPSC_RESULT_ERR_SSL_PROTOCOL = 12
+} httpsc_result_t;
+
+typedef struct _httpsc_state httpsc_state_t;
+
+/**
+ * @ingroup httpc
+ * HTTPS客户端回调函数原型
+ *
+ * @param arg 发起请求时指定的参数
+ * @param httpsc_result HTTPS传输结果(见enum httpsc_result_t)
+ * @param rx_content_len 接收的字节数(不含头部)
+ * @param srv_res 包含接收到的HTTP状态码(如果有)
+ * @param err 内部lwip函数返回的错误,可帮助确定错误源
+ * 但不一定必须!=ERR_OK
+ */
+typedef void (*httpsc_result_fn)(void *arg, httpsc_result_t httpsc_result, u32_t rx_content_len, u32_t srv_res, err_t err);
+
+/**
+ * @ingroup httpc
+ * HTTPS客户端回调原型:接收到头部时调用
+ *
+ * @param connection HTTPS客户端连接
+ * @param arg 发起请求时指定的参数
+ * @param hdr 头部pbuf(s)(可能也包含数据)
+ * @param hdr_len 'hdr'中头部的长度
+ * @param content_len 头部中接收到的内容长度(-1表示未收到)
+ * @return 如果返回值!=ERR_OK,则中止连接
+ */
+typedef err_t (*httpsc_headers_done_fn)(httpsc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len);
+
+/**
+ * @ingroup httpc
+ * SSL证书验证回调原型
+ *
+ * @param connection HTTPS客户端连接
+ * @param cert 服务器证书
+ * @param cert_len 证书长度
+ * @param depth 证书链深度
+ * @param is_valid 当前验证状态
+ * @return 验证结果(0表示验证失败,非0表示验证成功)
+ */
+typedef int (*httpsc_verify_cert_fn)(httpsc_state_t *connection, const void *cert, size_t cert_len, int depth, int is_valid);
+
+/**
+ * SSL/TLS配置参数
+ */
+typedef struct _httpsc_ssl_config {
+ /** 证书验证回调函数 */
+ httpsc_verify_cert_fn verify_cert;
+ /** 信任的CA证书(如果为NULL,则使用系统默认CA) */
+ const void *trust_anchor;
+ /** 信任的CA证书长度 */
+ size_t trust_anchor_len;
+ /** 启用/禁用证书验证(不建议禁用) */
+ int verify;
+ /** 支持的TLS版本掩码(如SSL_VERSION_TLSv1_2 | SSL_VERSION_TLSv1_3) */
+ int tls_versions;
+ /** 密码套件列表(如果为NULL,则使用默认列表) */
+ const char *ciphers;
+ /** SNI(服务器名称指示)主机名(如果为NULL,则使用连接的主机名) */
+ const char *sni_hostname;
+} httpsc_ssl_config_t;
+
+typedef struct _httpsc_connection {
+ ip_addr_t proxy_addr;
+ u16_t proxy_port;
+ u8_t use_proxy;
+ /* @todo: 添加用户名:密码? */
+
+ /* SSL/TLS配置 */
+ httpsc_ssl_config_t *ssl_config;
+
+#if LWIP_ALTCP
+ altcp_allocator_t *altcp_allocator;
+#endif
+
+ /* 传输完成(或中止)时调用的回调 */
+ httpsc_result_fn result_fn;
+ /* 接收到HTTP头部后调用的回调
+ 可以通过返回!=ERR_OK来中止连接 */
+ httpsc_headers_done_fn headers_done_fn;
+} httpsc_connection_t;
+
+err_t httpsc_get_file(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpsc_connection_t *settings,
+ altcp_recv_fn recv_fn, void* callback_arg, httpsc_state_t **connection);
+err_t httpsc_get_file_dns(const char* server_name, u16_t port, const char* uri, const httpsc_connection_t *settings,
+ altcp_recv_fn recv_fn, void* callback_arg, httpsc_state_t **connection);
+
+#if LWIP_HTTPSC_HAVE_FILE_IO
+err_t httpsc_get_file_to_disk(const ip_addr_t* server_addr, u16_t port, const char* uri, const httpsc_connection_t *settings,
+ void* callback_arg, const char* local_file_name, httpsc_state_t **connection);
+err_t httpsc_get_file_dns_to_disk(const char* server_name, u16_t port, const char* uri, const httpsc_connection_t *settings,
+ void* callback_arg, const char* local_file_name, httpsc_state_t **connection);
+#endif /* LWIP_HTTPSC_HAVE_FILE_IO */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LWIP_TCP && LWIP_CALLBACK_API && LWIP_SSL */
+
+#endif /* LWIP_HDR_APPS_HTTPS_CLIENT_H */
\ No newline at end of file