文章目录
-
- [1. 现象复盘](#1. 现象复盘)
- [2. 错误码 -0x7780 到底是什么意思](#2. 错误码 -0x7780 到底是什么意思)
- [3. 为什么去年的改法没用:WS63 的 mbed TLS 是"两层编译"](#3. 为什么去年的改法没用:WS63 的 mbed TLS 是"两层编译")
- [4. 根因:ECDHE 只在 HiLink 分支启用](#4. 根因:ECDHE 只在 HiLink 分支启用)
- [5. 完整根治方案(两处配置 + 强制全量重编)](#5. 完整根治方案(两处配置 + 强制全量重编))
-
- [5.1 主配置文件(决定性)](#5.1 主配置文件(决定性))
- [5.2 硬件适配配置文件(保持一致)](#5.2 硬件适配配置文件(保持一致))
- [5.3 必须强制全量重编(很容易漏!)](#5.3 必须强制全量重编(很容易漏!))
- [6. 一个差点误导我们的"乱码"假象](#6. 一个差点误导我们的"乱码"假象)
- [7. 总结与坑位清单](#7. 总结与坑位清单)
- 附录:关键文件清单
去年我写过一篇博文,记录了 WS63 访问公网 HTTPS 时 TLS 握手失败的问题,但当时只停留在"现象 + 试改配置"的层面,没能找到真正的根因,改完也不稳定。今天在做一个"WS63 + 温湿度传感器 + 蜂鸣器 + 华为云 MaaS 大模型对话"的样例时,把这个问题从编译链路层面彻底定位并稳定修复了。本文复盘整个排查过程,给出可复用的根治方案,并顺带澄清一个差点误导判断的"乱码"假象。
1. 现象复盘
在 WS63 上用 Mbed TLS 直连华为云 MaaS 的公网接口 api.modelarts-maas.com(OpenAI 兼容的 /v2/chat/completions)时,Wi-Fi 关联、DHCP 拿 IP、外设初始化都正常,但一进入 TLS 握手就失败:
APP|[LLMCHAT] connecting HTTPS to api.modelarts-maas.com
[ERR] signal NOT SUPPORT
APP|[LLMCHAT] TLS handshake with api.modelarts-maas.com
APP|[LLMCHAT] TLS handshake failed: -0x7780
日志里还会伴随一行 [ERR] signal NOT SUPPORT,先说结论:这行是 LiteOS 不支持 signal() 系统调用的桩警告 (Mbed TLS 的 net_sockets.c 里会调用 signal(SIGPIPE, SIG_IGN) 来忽略管道断开信号,而 LiteOS 的 signal() 是空实现),它与握手失败无关 ,完全可以忽略。真正的元凶是 -0x7780。
2. 错误码 -0x7780 到底是什么意思
在 Mbed TLS 头文件 include/mbedtls/ssl.h 里,错误码的高位是模块标识、低位是具体错误:
-0x7780 = MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE
注意:它不是 UNEXPECTED_MESSAGE (那是 -0x7700),而是 FATAL_ALERT_MESSAGE ------意味着服务端在握手过程中向我们发来了一个 fatal alert 。公网 TLS 服务器发 fatal alert 最常见的原因就是 handshake_failure(我们发过去的 ClientHello 它不接受)。
所以这是一个服务端拒绝 问题,而不是本地把数据解析坏了。方向立刻清晰:问题出在 ClientHello 里提供的密码套件/密钥交换方式与服务器对不上。
3. 为什么去年的改法没用:WS63 的 mbed TLS 是"两层编译"
这是本次排查最关键、也最容易被忽略的点。WS63 的 Mbed TLS 并不是单一一套配置编译出来的,它被拆成了两层静态库,各自用不同的配置文件:
| 库 | 作用 | 配置文件(编译宏) |
|---|---|---|
libmbedtls_v3.6.0.a |
核心 TLS 状态机:握手流程、密码套件选择、密钥交换 | MBEDTLS_CONFIG_FILE → drivers/chips/ws63/porting/mbedtls/config-ws-iot_v3.6.0.h |
libmbedtls_harden.a |
硬件加解密适配层(ECP/RSA 的硬件/软件 offload 副本) | MBEDTLS_USER_CONFIG_FILE → drivers/drivers/driver/security_unified/mbedtls_harden_adapt/mbedtls_platform_hardware_config.h |
在 build/cmake/open_source/mbedtls_v3.6.0.cmake 第 112 行可以确凿看到:
cmake
MBEDTLS_CONFIG_FILE="${ROOT_DIR}/drivers/chips/ws63/porting/mbedtls/config-ws-iot_v3.6.0.h"
核心库只认这一个主配置文件,根本不读取硬件适配头。 这意味着:决定"客户端能用哪些密码套件"的代码(ssl_client.c / ssl_tls.c 里的套件协商逻辑),只受主配置 config-ws-iot_v3.6.0.h 影响。硬件适配头里对 MBEDTLS_KEY_EXCHANGE_ECDHE_*、MBEDTLS_SSL_CIPHERSUITES 的 #undef/#define,碰不到核心握手机,改了也白改。
去年那篇博文(包括我早期的一次尝试)只改了硬件适配层那个头文件,所以"看起来改对了、实际握手照样失败"。
4. 根因:ECDHE 只在 HiLink 分支启用
打开主配置文件 drivers/chips/ws63/porting/mbedtls/config-ws-iot_v3.6.0.h,关于密钥交换与密码套件是这样写的(节选):
c
#if defined(CONFIG_SUPPORT_HILINK)
#define MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED
#define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED
#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#define MBEDTLS_SSL_CIPHERSUITES \
MBEDTLS_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, \
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, \
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
#elif defined(CONFIG_SAMPLE_SUPPORT_xxx) /* 仅个别官方示例开启 */
...
#endif
也就是说:ECDHE 密钥交换 + ECDHE 系列密码套件,只有在 CONFIG_SUPPORT_HILINK 或少数官方示例分支下才会被定义。 当你自己新建一个需要访问公网 HTTPS 的样例(既不是 HiLink,也不在官方示例分支里)时,这一整段会落到"空分支":
- 客户端不提供任何 ECDHE 套件,只能提供 RSA 密钥交换类套件;
- 而华为云 MaaS 这类现代公网端点只接受 ECDHE(出于前向安全等要求);
- 于是服务器在收到 ClientHello 后直接回
handshake_failure的 fatal alert; - Mbed TLS 在期待 ServerHello 时却收到了 alert → 报
MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE (-0x7780)。
硬件适配头 mbedtls_platform_hardware_config.h 里还有第二道"暗杀":
c
#if !defined(CONFIG_SUPPORT_HILINK) && !defined(CONFIG_SAMPLE_SUPPORT_xxx)
#undef MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED
#undef MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED
#undef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#endif
即使你在别处开了 ECDHE,这个 #undef 也会把它灭掉(仅对硬件适配库生效,但同样说明 SDK 默认态度是"非 HiLink 就不开 ECDHE")。
5. 完整根治方案(两处配置 + 强制全量重编)
把你的样例配置符号(下面以 CONFIG_SAMPLE_SUPPORT_LLMCHAT 为例,你换成自己的 Kconfig 开关名即可)加入两处配置:
5.1 主配置文件(决定性)
drivers/chips/ws63/porting/mbedtls/config-ws-iot_v3.6.0.h:
c
#if defined(CONFIG_SUPPORT_HILINK)
#define MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED
#define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED
#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#define MBEDTLS_SSL_CIPHERSUITES \
MBEDTLS_TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, \
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, \
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
#elif defined(CONFIG_SAMPLE_SUPPORT_LLMCHAT) /* ← 新增:你的公网 HTTPS 样例 */
/* 公网 TLS 1.2 端点(如华为云 MaaS)要求 ECDHE 密钥交换 */
#define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED
#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#define MBEDTLS_SSL_CIPHERSUITES \
MBEDTLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, \
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, \
MBEDTLS_TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
#endif
⚠️ 这一处才是决定核心握手库行为的关键。 如果你的 SDK 里这段原本还包含其它联网示例的符号,请保留原有符号、仅追加
LLMCHAT,不要误删别人分支。
5.2 硬件适配配置文件(保持一致)
drivers/drivers/driver/security_unified/mbedtls_harden_adapt/mbedtls_platform_hardware_config.h,把两处条件都补上你的符号:
c
/* (a) 不要再把 ECDHE 关掉 */
#if !defined(CONFIG_SUPPORT_HILINK) && !defined(CONFIG_SAMPLE_SUPPORT_LLMCHAT)
#undef MBEDTLS_KEY_EXCHANGE_DHE_RSA_ENABLED
#undef MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED
#undef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
#endif
/* (b) 对本样例使用软件 ECP 乘法(与官方联网示例一致) */
#if !defined(CONFIG_SAMPLE_SUPPORT_LLMCHAT)
#define MBEDTLS_ECP_MUL_ALT
#endif
5.3 必须强制全量重编(很容易漏!)
改的是 Mbed TLS 配置头,而 libmbedtls_v3.6.0.a 是预编译静态库,增量构建经常不会重编它------这正是一开始"改了配置还是报同样错误"的真凶。务必:
- 删除核心库产物,例如
output/ws63/acore/ws63-liteos-app/libmbedtls_v3.6.0.a; - 整体重建
ws63-liteos-app并烧写。
烧写成功后,串口应看到:
APP|[LLMCHAT] TLS handshake with api.modelarts-maas.com
APP|[LLMCHAT] MaaS request body sent (454 bytes)
APP|[LLMCHAT] MaaS response received (1838 bytes)
APP|[LLMCHAT] LLM: <大模型建议文本>
握手通过,公网 HTTPS 调用成功。
6. 一个差点误导我们的"乱码"假象
握手修好之后,又冒出一个新问题:大模型返回的中文里偶尔出现 (U+FFFD 替换符),而且每次乱的位置都不一样;英文和十六进制却完全正常。第一反应自然是"解析/发送把多字节 UTF-8 写坏了"。
为了不被直觉骗,我在应用里加了一段十六进制 dump ,把抽取出的回复前若干字节原样打印出来。结果证明内存里的字节 100% 是合法 UTF-8 三字节序列:
e6 b9 bf = 湿 e5 ba a6 = 度 e4 bd 8e = 低 e6 84 9f = 感 ...
字节层面毫无损坏,问题只出在显示 。最终确认:乱码是 HiSpark Studio 插件自带串口终端对多字节 UTF-8 解码不完善 导致的------同样是这批数据,换到 MobaXterm(UTF-8) 显示就完全正常。
经验法则:排查串口中文乱码,先打十六进制确认字节是否完好。只要 hex 是合法 UTF-8,那固件/协议层就没问题,纯粹是终端编码的事,千万别去乱改固件的发送逻辑。
(顺带提一句:WS63 的 print_str 走阻塞轮询的 uapi_uart_write,逐字节写 TX FIFO、关中断,绝不丢字节 ;它的单条打印缓冲上限 UART_TRANS_LEN_MAX 是 128 字节,超长会被静默截断,但那只会"少显示"不会"乱码"。所以显示类问题优先怀疑终端。)

7. 总结与坑位清单
WS63 访问公网 HTTPS 握手失败(-0x7780)的根治要点:
-0x7780= 服务端 fatal alert(通常是handshake_failure),不是本地解析错误,根因是 ClientHello 的密码套件不被接受。- ECDHE 默认只对 HiLink 开启。自建公网 HTTPS 样例必须自己把 ECDHE 密钥交换 + ECDHE 套件打开。
- Mbed TLS 是两层编译 :决定套件协商的核心库只认主配置
config-ws-iot_v3.6.0.h,硬件适配头改了也不影响握手------这是去年没修好的关键。 - 必须改两处:主配置(决定性)+ 硬件适配配置(保持一致、走软件 ECP)。
- 改配置头后必须删掉
libmbedtls_v3.6.0.a全量重编,否则增量构建不会重编核心库,错误照旧。 [ERR] signal NOT SUPPORT是 LiteOS 不支持signal()的无害警告,与握手无关。- 串口中文乱码先打 hex 验证:字节正确就是终端编码问题(如 HiSpark Studio 自带终端),换 MobaXterm 即可,别动固件。
附录:关键文件清单
| 文件 | 作用 |
|---|---|
drivers/chips/ws63/porting/mbedtls/config-ws-iot_v3.6.0.h |
主配置 :核心 TLS 库 MBEDTLS_CONFIG_FILE,决定 ECDHE/套件(改这里才有效) |
drivers/drivers/driver/security_unified/mbedtls_harden_adapt/mbedtls_platform_hardware_config.h |
硬件适配配置 MBEDTLS_USER_CONFIG_FILE,需同步保持一致 |
build/cmake/open_source/mbedtls_v3.6.0.cmake |
核心库编译脚本,确认 MBEDTLS_CONFIG_FILE 指向主配置 |
open_source/mbedtls/mbedtls_v3.6.0/include/mbedtls/ssl.h |
-0x7780 = MBEDTLS_ERR_SSL_FATAL_ALERT_MESSAGE 的定义所在 |
output/ws63/acore/ws63-liteos-app/libmbedtls_v3.6.0.a |
核心库产物,修复后需删除并全量重编 |
希望这篇能帮到同样被 WS63 + 公网 HTTPS 折磨的同学。如果重编后仍失败,建议在 MBEDTLS_DEBUG_* 打开的情况下,用 mbedtls_strerror() 把 alert 的具体类型打印出来,定位会更快。