Files
banban/docs/WSS适配与性能优化完整总结.md
chenjintao 3deb51d838 Adapt WSS to new boards and use MQTT domain for eSIM connectivity
Preserve main GNSS and volume behavior while porting verified TLS, receive fixes and full-handshake optimizations. Add MQTT connection diagnostics and route MQTT through banban.api.talkingq.com:1883.

Include migration documentation and regression tools. Firmware builds and package checks passed; user confirmed new-board connectivity after switching the MQTT host to the domain.
2026-09-16 17:27:21 +08:00

1157 lines
61 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 旧板 WSS 适配、故障修复与性能优化完整总结
> 新板 main 说明:本文保留旧板历史过程、配置与实测数据。新板已迁入 WSS,保留 GNSS 与原 main 音量行为;新板构建、下载包及验收请见 [新板 WSS 迁移说明](D:/banban/project/nfc_gnss/docs/new_board_wss.md)。旧板实测耗时不代表新板实测。
> 整理日期:2026-09-16;设备日志时间按用户提供的串口时间记录。
>
> 工作区:`D:\banban\project\nfc_gnss`。分支:`legacy-board-wss`。本文覆盖的最新代码提交:`551a7bb1e657c12f45ac0a1f2ca851625723a63d`。
>
> 最新板端证据:2026-09-16 15:52 的 X25519 版本日志。本文区分代码事实、主机测试、板端日志和未来建议,不把主机基准当作设备实测。
## 1. 最终成果与当前状态
此次工作把旧板语音业务的 WebSocket 通道从明文 WS 迁移到经过服务器证书校验的 WSS,并修复了迁移过程中暴露的内核密码算法缺失、APP/内核配置不一致、证书时间来源不正确、非阻塞接收误断线等问题。
当前业务入口为:
```text
wss://banban.api.talkingq.com:443/ws
```
截至最新日志,板端已经完成以下验证:
| 项目 | 结果与依据 |
| --- | --- |
| 无 GNSS 旧板启动 | 正常绕过 GNSS 识别与定位流程 |
| 蜂窝网络与 MQTT | 最新日志中均已连接 |
| 根 CA 解析 | `WSS root CA parsed successfully` |
| TLS 1.2 完整握手 | 成功,`ret=0`,最终状态 `state=16` |
| 服务器证书与域名验证 | `WSS TLS handshake and certificate/hostname verification passed` |
| X25519 协商 | `WSS TLS negotiated ECDHE group: 9` |
| WebSocket 升级 | `WebSocket upgrade verified: HTTP 101` |
| APP 连接回调 | `websocket connected`、`ws connect okay` |
| 原业务鉴权 | 已调用原逻辑发送 `device_id`、`serial_number`;该日志片段未单独展示服务端鉴权成功响应 |
| 录音初始化 | 提示音结束后进入 VAD 录音 |
最主要的性能成果:
| 版本/阶段 | TLS 完整握手 | 说明 |
| --- | ---: | --- |
| 基础 WSS 修复后,未启用 NIST 加速 | 约 9.6 秒 | 14:20 日志,以串口 TCP 成功到 TLS 完成的时间差估算 |
| 启用 NIST 快速模约减 | 1820 ms | 14:38 日志,设备内部 tick 计时 |
| NIST 加速 + X25519 优先 | **1455 ms** | 15:52 日志,设备内部 tick 计时 |
与最初约 9.6 秒相比,最新 TLS 完整握手时间减少约 **85%**,原耗时约为当前的 **6.6 倍**。其中第二轮曲线调整使 1820 ms 进一步下降到 1455 ms,减少 **365 ms,约 20%**。
这些是具体日志样本的对比,不是多次测试的平均值、P95 或性能承诺。第一轮基线与后两轮的计时方式也不完全相同,百分比应按近似结果理解。
最新版本仍然满足用户明确的条件:**按键后建连,每次新建 TLS 连接执行完整握手,不提前建立连接,不依赖 TLS 会话恢复来获得上述加速。** 已存活的业务连接按原逻辑使用,没有新增“每次按键强制断开再连接”的行为。
## 2. 项目背景、范围与约束
### 2.1 硬件与软件背景
- 目标产品:Quectel EG800AKCN_11LG,日志平台为 ASR1605/CRANELRH。
- 操作系统与 SDK:ThreadX/OpenCPU 厂商 SDK。
- TLS 库:SDK 中的 mbedTLS 2.28.9。
- 内核编译器:ARM Compiler 5 / ARMCC。
- APP 编译器:ARM GCC 9.2.1。
- WebSocket 库:项目内的 librws,调用 SDK 导出的 mbedTLS 接口。
- 旧板没有 GNSS,已有旧板分支负责跳过 GNSS 检测;默认音量为 100。
WSS 问题不能简单归结为“旧板硬件不兼容”。后续板端完整握手、HTTP 101 和录音初始化已经证明,该板具备运行当前 WSS 方案的能力。主要障碍来自软件配置、协议状态处理和运算性能。
### 2.2 本次修改的范围
本次迁移的是 TalkingQ 语音业务 WebSocket 连接,包含 TLS、WebSocket Upgrade、连接状态处理、必要的内核算法支持与测试工具。
沿用以下原业务约定:
- 设备身份继续通过 `device_id`、`serial_number` 发送。
- 不增加 TLS 客户端证书或客户端私钥,不使用双向 TLS。
- 没有把 MQTT、HTTP 下载、升级接口等所有网络通道统一改成 HTTPS/TLS。
- 未修改服务器配置;本文提到的服务器转发只是诊断背景和建议。
- 性能优化没有修改按键逻辑、休眠策略、提示音或录音业务流程。
### 2.3 用户最终选定的性能路线
排查中讨论过提前建连、复用连接、TLS 会话恢复等方法。用户最终明确选择:
> 只优化每次完整握手,不提前建连。
因此最终落地的性能改动是 NIST 运算加速和 X25519 优先协商。会话恢复只做过主机探测,没有加入固件。
## 3. WSS 到底增加了什么
WSS 的协议关系是:
```text
蜂窝数据网络 → DNS → TCP → TLS → HTTP WebSocket Upgrade → WebSocket 业务消息
```
`wss://` 不能只当作 `ws://` 的字符串替换。设备必须能够建立 TLS 通道,验证服务端身份,然后在 TLS 通道中发送 WebSocket 握手请求。
```mermaid
sequenceDiagram
participant U as 用户按键
participant A as APP / librws
participant T as SDK TLS
participant S as 服务端
U->>A: 触发业务连接
A->>S: DNS 与 TCP 连接
A->>T: 配置根 CA、SNI、域名及 TLS 参数
T->>S: ClientHello
S-->>T: ServerHello、证书链、ECDHE 参数与签名
T->>T: 验证证书链、时间、域名和握手签名
T->>T: 生成临时密钥并计算共享密钥
T->>S: ClientKeyExchange、Finished 等
S-->>T: Finished 等
T-->>A: TLS 握手与验证通过
A->>S: GET /ws + Upgrade + 随机 Key
S-->>A: HTTP 101 + 匹配的 Accept
A->>S: 原 device_id / serial_number 鉴权消息
```
这里有三个独立的成功条件:
1. **TLS 成功**:安全通道建立,并验证服务器身份。
2. **WebSocket 成功**:HTTP Upgrade 返回并通过 101/Accept 校验。
3. **业务鉴权成功**:服务端接受设备身份,允许后续业务。
`MQTT Connected` 只能证明 MQTT 连接成功;`ws connect okay` 也不能单独证明后端已经确认设备鉴权或完成语音业务。
## 4. 域名、端口、路径与服务器排查
### 4.1 各字段的含义
| 字段 | 当前值 | 作用 |
| --- | --- | --- |
| 协议 | `wss` | WebSocket over TLS |
| 域名 | `banban.api.talkingq.com` | DNS、TLS SNI、证书域名匹配、HTTP Host |
| 端口 | `443` | 本次可用的 TLS 入口 |
| 路径 | `/ws` | HTTP/WebSocket 路由,不是磁盘路径或证书路径 |
证书通常验证 DNS 名称。即使域名解析到某个 IP,也不能随意把 URL 换成该 IP,并期待原域名证书仍通过验证。
### 4.2 后台探测结论
2026-09-16 的多轮探测结果如下,均属于当时的服务状态:
| 测试地址 | 初期情况 | 后续结果 |
| --- | --- | --- |
| `wss://banban.api.talkingq.com:443/ws` | TLS 可用,但 HTTP Upgrade 一度返回 404 | 11:32 复测返回有效 101,Accept 匹配;后续板端也成功 |
| `wss://banban.api.talkingq.com:8080/ws` | TLS 无法建立,出现协议格式错误 | 未验证到可用 WSS |
| `ws://banban.api.talkingq.com:8080/ws` | 明文 WebSocket 测试 | 返回有效 101 |
准确解释是:
- 443 上出现过 404,说明当时请求没有到达可用的 `/ws` Upgrade 路由;不能由此推断 443 没有任何 HTTPS 服务。
- 8080 的 WSS 失败,不等于 TCP 端口完全不通。明文 WS 成功说明该端口当时提供的是明文服务。
- 后台后续 443 `/ws` 已可用,固件最终选择 443 WSS。
- 这些测试没有向后台发送设备鉴权身份或语音业务数据。
### 4.3 eSIM 白名单的关系
此 WebSocket 通道至少需要运营商允许 `banban.api.talkingq.com:443` 的访问,并支持所需 DNS 解析。运营商是按域名、IP、端口还是代理规则放行,取决于其平台。
设备仍有 MQTT、下载和升级等独立通道。本次 WSS 成功不代表整个设备只需要这一个白名单地址。应按真实启用的业务逐项梳理,不能把历史 WS IP 或一次解析得到的 IP 当作永久完整清单。
## 5. 第一轮实现:把业务连接真正迁移到 WSS
对应提交:`3e463f57b69732ced0562e8e52915cc1566c4b6c`。
### 5.1 统一地址解析与默认地址
新增 `talkingq_wss.h` 与 `talkingq_wss.c`,集中定义默认入口,并把域名、端口、路径分开解析。
解析规则包括:
- 必须是 `wss://`,不自动降级成明文 WS。
- 未指定端口时使用 443,未指定路径时使用 `/ws`。
- 拒绝空域名、非法端口、超长输出、CR/LF 等非法路径字符。
- 当前不是通用浏览器 URL 解析器,不支持 IPv6 字面量、userinfo 等全部 URI 语法。
- 地址存储缓冲区容量为 64 字节,业务侧主机缓冲区 64 字节、路径缓冲区 128 字节;后两者更大并不代表持久化 URL 也能存储同样长度。
### 5.2 保存的 talkingq_url 从哪里来
实际读取链路是:
```text
talkingq_ws_connect()
→ hal_os_get_talkingq_url()
→ hal_kv_get("talkingq_url", ...)
→ talkingq_parse_wss_url()
→ rws_socket_set_url()
```
实际保存入口为 `hal_os_set_talkingq_url()`,通过 `hal_kv_set()` 写入 KV。仓库中可见的调用来自 `fota.c` 的共享属性处理:收到 `shared.talkingq_url`,当值非空且与当前读取结果不同时保存。该代码存在不等于每次启动必然走到该分支,执行时机还受原属性请求流程控制。
源码中虽然保留了 `U:/talkingq_url.txt` 的旧文件辅助函数,但当前 getter/setter 调用这些函数的语句是注释,实际使用的是 KV。
### 5.3 旧地址迁移
getter 对“没有保存值”以及下列已知历史默认地址,返回新的默认 WSS 地址:
```text
ws://101.35.224.118:8080
ws://101.35.224.118:8080/ws
ws://api.talkingq.com:8080
ws://175.24.73.253:80
```
这是**读取时迁移**,不会因为一次读取就重写 Flash。自定义地址不会被无条件替换;自定义明文地址会被后续 WSS 解析规则拒绝。
影响:既有设备不必先清空 KV 才能迁移已知默认值,但后台如继续下发自定义 `ws://` 地址,会导致明确连接失败,需要同步调整配置。
### 5.4 接入真正的 TLS 传输
- 通过统一的 `rws_tls_config.h` 启用 librws 的 mbedTLS 分支,避免公共/私有头文件宏不一致。
- 保留已工作的蜂窝 PDP profile 1 TCP 路径,连接成功后将 socket 交给 mbedTLS 的 BIO 收发函数。
- 没有直接套用标准示例中的 `mbedtls_net_connect()`,因为本 SDK 的接口存在 CID/端口等厂商 ABI 差异。
- 配置可信根 CA、`MBEDTLS_SSL_VERIFY_REQUIRED`、SNI 与预期服务器域名。
- 设置 WSS 最低 TLS 版本为 TLS 1.2;此 SDK 配置实际支持到 TLS 1.2,本次没有引入 TLS 1.3。
- TLS 建立后,HTTP Upgrade、WebSocket 文本和二进制收发均走相应 TLS 接口。
### 5.5 单向 TLS 与原业务鉴权
不做双向 TLS,仍然需要设备验证服务器证书。二者区别如下:
| 机制 | 谁验证谁 | 本项目做法 |
| --- | --- | --- |
| 服务器证书验证 | 设备验证服务端 | 必须执行 |
| TLS 客户端证书 | 服务端在 TLS 层验证设备证书 | 未启用 |
| 应用层设备鉴权 | 服务端验证设备提交的业务身份 | 保留原 `device_id`、`serial_number` |
内置根 CA 是公开证书,不是需要保密的客户端私钥。
### 5.6 WebSocket Upgrade 的完整验证
新增独立响应解析器,要求:
- HTTP 状态为 101。
- `Upgrade` 含 `websocket`。
- `Connection` 含 `upgrade`。
- `Sec-WebSocket-Accept` 与本次随机 Key 对应。
- 拒绝重复/错误 Accept,以及本客户端没有请求的扩展或子协议。
- 支持响应头分片接收,设置 8192 字节响应头上限,保留头结束后的 WebSocket 数据。
协议计算公式为:
```text
Key = Base64(16 字节随机数)
Accept = Base64(SHA-1(Key 的 ASCII 文本 + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
```
这里的 SHA-1 是 RFC 6455 的 Upgrade 匹配算法,不是本次服务器证书签名算法,也不承担 TLS 身份认证。服务器身份由 TLS 证书验证保证。
### 5.7 发送、任务和资源生命周期
改动同时补齐:
- TLS 短写:只发送一部分时继续发送剩余部分。
- `WANT_READ/WANT_WRITE`:按库语义重试,不按普通 socket 的 `-1` 处理。
- 致命错误、EOF、超时:明确退出并通知连接失败。
- 回调身份校验:只处理属于当前 APP socket 的事件。
- 正在启动/连接的 worker:由 worker 完成退出和对象释放,避免调用方提前释放活跃对象。
- 连接失败唤醒等待连接的业务线程。
- WSS worker 栈从 4 KiB 增至 16 KiB,为证书解析和密码运算提供空间。
增加的栈空间约为每个该 worker 12 KiB。尚无长期栈水位与高负载峰值报告,不能仅凭启动时剩余堆较大就认定所有内存情形都已覆盖。
## 6. 根证书解析失败 -9774:原因与修复
对应主要提交:`5c3e7eee6dc3bbf6d514af6bb93465bc81d6c952`。该提交还包含后续接收误断线修复,见第 9 节。
### 6.1 现象
早期板端已经联网、MQTT 已连接,WSS 输出:
```text
WSS trust anchor loaded: 789 bytes
WSS TLS connection failed: -9774
```
这不是“服务器证书过期”的直接证据,也不是硬件没有 GNSS 导致的 TLS 故障。
### 6.2 错误码分解
```text
-9774 = -0x262E
= MBEDTLS_ERR_X509_UNKNOWN_SIG_ALG (-0x2600)
+ MBEDTLS_ERR_OID_NOT_FOUND (-0x002E)
```
X.509 解析器通过 OID 识别签名算法。固件内置 ISRG Root X2 使用 ECDSA/SHA-384,但原内核 mini TLS profile 没有启用 ECDSA;相关映射/能力被条件编译裁剪后,解析该 CA 时无法识别所需算法。
`trust anchor loaded` 只表示证书数据交给了连接代码,不代表 X.509 解析或信任校验已经成功。
### 6.3 实际证书链
本次捕获和验证的链为:
```text
banban.api.talkingq.com
└─ Let's Encrypt YE1
└─ ISRG Root YE(由 ISRG Root X2 交叉签名)
└─ ISRG Root X2(设备信任锚)
```
| 证书 | 证书中的公钥 | 证书签名算法 | 当时有效期,UTC |
| --- | --- | --- | --- |
| 服务器叶证书 | P-256 | ECDSA with SHA-384 | 2026-08-28 14:10:29 至 2026-11-26 14:10:28 |
| YE1 | P-384 | ECDSA with SHA-384 | 2025-09-03 至 2028-09-02 |
| Root YE 交叉签名证书 | P-384 | ECDSA with SHA-384 | 2026-05-13 至 2032-09-02 |
| ISRG Root X2 | P-384 | ECDSA with SHA-384 | 2020-09-04 至 2040-09-17 |
设备内置根 CA 的 DER SHA-256:
```text
69729b8e15a86efc177a57afb7171dfc64add28c2fca8cf1507e34453ccb1470
```
以上是当次证书快照,不能当作后台永远不会变化的配置。服务器证书正常续期且仍能构建到受信任根时,通常不需要同步烧录新叶证书。
### 6.4 修复措施
新增共享配置 `ql-config/config/talkingq_mbedtls_config.h`,在保留原 mini profile 能力的基础上增加:
| 宏 | 作用 |
| --- | --- |
| `MBEDTLS_ECDSA_C` | 解析与验证 ECDSA 签名 |
| `MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED` | 支持 ECDHE 密钥交换、ECDSA 服务器认证的 TLS 套件 |
| `MBEDTLS_GCM_C` | 支持 AES-GCM 记录层加密 |
| `MBEDTLS_HAVE_TIME_DATE` | 启用证书日期验证 |
| `MBEDTLS_PLATFORM_TIME_ALT` | 使用 SDK 的时间适配 |
| `TALKINGQ_TLS_USE_RTC_TIME` | 选择本产品可用的 RTC 时间路径 |
同时把 APP 中同版本的 `mbedtls/ecdsa.h` 补入内核缺失的位置。它是 SDK 同版本头文件补齐,不是自行编写一套 ECDSA 实现。
### 6.5 结果与影响
- 实际 SDK 主机测试中,根 CA 解析从此前板端的 `-9774` 对应故障变为返回 0。
- 后续板端明确出现 CA 解析成功和证书/域名验证通过。
- 内核需要重编并烧录,不能仅修改 APP 的宏或地址。
- 原先“只编译 APP + firmware 就能完成最初 WSS 适配”的判断,在发现内核缺失算法后被纠正。只重新打包旧内核不会补出 ECDSA。
## 7. APP/内核 mbedTLS ABI 不一致的隐患
### 7.1 原因
APP 原先使用一份较完整的 mbedTLS 配置头文件,实际内核使用厂商 mini profile。mbedTLS 的很多结构体成员由宏条件控制,配置差异会改变结构体大小及字段偏移。
APP 分配的上下文由内核代码读写。若双方理解不同,可能出现越界写、指针解释错误或不可预测的崩溃。此项是在审查中发现并处理的结构性风险;不能把所有此前日志异常都反推为它已经造成了内存破坏。
### 7.2 解决方法
- APP 配置头改成共享 profile 的包装层。
- 内核两个相关配置头的 mini 分支引用同一 profile;原完整配置分支保留。
- 用 ARMCC 编译内核头、GCC 编译 APP 头,比较实际 `sizeof` 与 `offsetof`,同时比较启用的 TLS 宏。
- X25519 诊断增加对 `curve_list`、握手参数和 ECDH 上下文布局的检查。
当前校验的 12 种结构体大小如下,单位为字节:
| 类型,均带 mbedtls_ 前缀 | 大小 | 类型 | 大小 |
| --- | ---: | --- | ---: |
| ssl_context | 232 | ssl_config | 152 |
| net_context | 8 | x509_crt | 340 |
| pk_context | 8 | entropy_context | 632 |
| ctr_drbg_context | 320 | ecp_group | 124 |
| ecp_point | 36 | mpi | 12 |
| ssl_handshake_params | 952 | ecdh_context | 224 |
例如 `ssl_config.curve_list` 偏移为 128,`ssl_handshake_params.ecdh_ctx` 偏移为 32;这些是本 SDK、本编译器配置下的实测值,不应当作为其他 SDK 的通用常量。
影响:减少跨 APP/内核调用的 ABI 风险,但共享配置也意味着以后修改 TLS 功能宏时,应同时考虑 MQTT/其他调用内核 TLS 的模块,并重新执行 ABI 与相关功能检查。
## 8. 证书日期校验与 RTC 修复
只补 ECDSA 仍不完整:证书验证还需要可信且合理的当前时间。
内核厂商 X.509 路径原本调用 `GetCurrentCompTime()`;通过当前构建的符号和反汇编发现,该产品中的实现是空 shim,不能提供有效年月日。直接启用日期校验而保留此路径,可能把当前日期当成无效或错误值。
处理方式:
1. 在 `x509.c` 中通过 `TALKINGQ_TLS_USE_RTC_TIME` 选择 `mbedtls_time()` 加 UTC 转换路径。
2. 使用 SDK 实际 RTC 的时间实现,避免空 shim。
3. APP 在 TLS 初始化前检查时间是否明显未初始化;早于 2024-01-01 时拒绝连接并给出日志。
4. 内核反汇编确认 `x509_get_current_time` 调用 `mbedtls_platform_gmtime_r`,不再调用该空 shim。
“晚于 2024-01-01”只是启动前的粗筛,不等于时间准确。实际证书验证仍会检查 `notBefore/notAfter`。运营商 NITZ/RTC 同步、时区和异常时间处理仍是产品运行条件,串口显示本地时间也不代表底层时间转换可任意修改。
## 9. TLS 成功后 HTTP 0:非阻塞接收误断线
### 9.1 现象与定位
14:06 日志已出现:
```text
WSS root CA parsed successfully
WSS TLS handshake and certificate/hostname verification passed: banban.api.talkingq.com:443
recv data failed
WebSocket upgrade failed: HTTP 0 (0 means incomplete/invalid headers)
```
这次失败发生在读取 WebSocket Upgrade 响应时。`HTTP 0` 是程序内部“尚未解析出有效状态码”的值,不是服务器发送的 HTTP 状态码。
### 9.2 具体代码错误
原接收函数把 `error_number` 初始化为 `-1`。TLS 返回 `WANT_READ` 或 `WANT_WRITE` 时,代码直接 `break`,跳过后面的错误变量更新。循环结束后又检查 `error_number`,把残留的 `-1` 当成真实故障。
```c
/* 错误路径的简化表示 */
int error_number = -1;
len = mbedtls_ssl_read(...);
if (len == WANT_READ || len == WANT_WRITE)
break;
/* 正常路径才有机会更新 error_number */
/* 循环后把遗留的 -1 判成失败 */
```
修复后的关键分支:
```c
if (len == MBEDTLS_ERR_SSL_WANT_READ ||
len == MBEDTLS_ERR_SSL_WANT_WRITE) {
error_number = 0;
break;
}
```
此时“本轮没有完整数据”允许上层继续等待;真正的 EOF、TLS 错误仍然产生错误状态。WebSocket 握手循环会继续接收、累计响应头并按期限退出。
### 9.3 为什么必须做回归测试
只测 HTTP 响应解析器不能覆盖这个问题,因为错误发生在“TLS 返回值 → 接收函数 → 握手等待循环”的衔接处。
新增测试提取实际接收函数与实际握手循环,配合真实 HTTP 解析器和可控 TLS 桩:
- 修复前可重现 WANT 被错误当成失败。
- 修复后支持 WANT_READ、WANT_WRITE、分片 101、响应头后的帧数据保留。
- 仍正确处理 EOF、致命错误、超时、tick 回绕、空 TLS 上下文、错误 Accept。
后续 14:20 日志及用户反馈确认连接正常。这一修复只涉及 APP;前提是板端已经有补齐 ECDSA/RTC 的内核。
## 10. 识别“烧录包未生效”的经验
13:59 左右的一份日志仍输出旧格式 `WSS TLS connection failed: -9774`,而新 APP 已使用带失败阶段和十六进制错误码的格式,并增加了 CA 解析成功日志。
对实际 ZIP 内 `customer_app.bin` 的字符串检查显示:新包包含新日志格式,旧包包含旧格式。由此能够判断当时运行的 APP 不是预期新 APP,但不能仅凭该字符串进一步断言具体烧录操作错在哪里或内核是哪一版。
处理方式是明确新包绝对路径、核对包内镜像及 SHA-256,重新核查烧录工具实际选择的包和结果。
日志里的 `BUILD TIME:20260906093652` 是项目已有固定值,不能当作可靠构建身份。后续产品化可以增加真实 Git 提交号/构建标识,但本次未新增该机制。
## 11. 第一轮性能优化:NIST 快速模约减
对应提交:`3a0714c735f5eec3eaab19ec04f12ef26e8145c1`。
### 11.1 9.6 秒不是 socket 断线或固定延时
14:20 日志的关键时间为:
```text
14:20:39.114 TCP connect success / socket break
14:20:48.752 TLS handshake and certificate/hostname verification passed
14:20:48.894 WebSocket upgrade verified: HTTP 101
```
原 `socket break` 对应的是 C 代码退出 TCP 连接重试循环的 `break`,不是关闭 socket。已改成:
```text
TCP connected; leaving connect loop
```
从 TCP 连接成功到 TLS 完成约 9.6 秒,而 HTTP Upgrade 只需随后约 0.1 秒。代码中没有固定“等 10 秒再继续”的逻辑,20 秒握手超时也不是要求每次等待 20 秒。
### 11.2 缺失的优化开关
当时已启用 ECDSA/ECDHE,但共享 mini profile 没有定义 `MBEDTLS_ECP_NIST_OPTIM`。SDK 的 NIST 曲线算术因此没有启用其专门的快速模约减函数。
加入:
```c
#define MBEDTLS_ECP_NIST_OPTIM
```
这使 P-256、P-384、P-521 等曲线使用 SDK 已有的专用约减实现,例如 `ecp_mod_p256`、`ecp_mod_p384`、`ecp_mod_p521`。本次没有自行替换椭圆曲线公式或引入外部密码库。
### 11.3 为什么这个开关能加速
椭圆曲线点运算需要大量有限域乘法、平方和模约减。乘法会产生较大的中间数,必须把它约减到模数 `p` 范围内。
NIST 素数具有便于运算的特殊形式,例如:
```text
P-256: p = 2^256 - 2^224 + 2^192 + 2^96 - 1
P-384: p = 2^384 - 2^128 - 2^96 + 2^32 - 1
P-521: p = 2^521 - 1
```
以 P-521 为直观例子,在模 `2^521 - 1` 下有 `2^521 ≡ 1`。一个大数的高位部分可按这一关系折回低位,再做必要归一化。实际库实现会处理进位、边界和规范化,不能用一条简单表达式替代完整实现。
这种优化减少了通用大整数模运算开销,不改变曲线参数、证书身份或校验规则。当前证书链中多张 CA 证书使用 P-384,收益不仅作用于临时密钥交换,也作用于证书签名验证。
### 11.4 主机与板端验证
使用实际 SDK crypto/X.509 源码,在同一 Windows 主机验证同一证书链,各运行 20 次:
| 配置 | 平均证书链验证时间 |
| --- | ---: |
| 关闭 NIST 优化 | 36.8 ms |
| 开启 NIST 优化 | 10.2 ms |
主机算法测试约快 3.6 倍。之后板端完整 TLS 握手实测为 1820 ms,相比原约 9.6 秒减少约 81%。主机测的是证书校验,板端测的是整个 TLS 握手,两个倍率不要求相同。
开启 NIST 优化需要修改内核并重新编译。本次增加约 4 KiB 的打包内核镜像大小,具体镜像对比见第 17 节。
## 12. 握手计时方法与归因边界
原来只有“TCP 成功”和“TLS 完成”两条日志,无法区分证书校验、密钥交换与网络等待。
新增 `rws_tls_handshake_timed()`,通过 SDK 导出的 `mbedtls_ssl_handshake_step()` 驱动原有握手状态机,记录各阶段耗时。原 `mbedtls_ssl_handshake()` 内部也以 step 函数推进状态;此处没有重新实现 TLS 协议。
日志格式:
```text
WSS TLS phase server certificate (3): elapsed=935 ms, calls=935 ms, retries=0
WSS TLS handshake total=1455 ms, state=16, ret=0
```
| 字段 | 解释 | 不能直接推断的内容 |
| --- | --- | --- |
| `elapsed` | 当前握手阶段经过的时间 | 阶段名不一定代表其中只有一种工作 |
| `calls` | 调用 SDK step 函数期间累计经过的时间 | 含任务调度,不是硬件 CPU 周期或纯运算时间 |
| `retries` | 返回 WANT_READ/WANT_WRITE 的次数 | 不是 TCP 丢包次数,也不是网络往返次数 |
| `total` | 本次 TLS 状态机开始到结束的时间 | 不包含此前 DNS/TCP,也不包含后续 HTTP Upgrade |
| `state=16` | 此 SDK 的 `MBEDTLS_SSL_HANDSHAKE_OVER` | 数值依赖当前 SDK,不能跨版本套用 |
设备系统 tick 为 5 ms,统计按无符号 tick 差计算,再乘以 5 转成毫秒。无符号差值也用于覆盖 tick 回绕场景。
WANT 状态按原策略等待 10 ms,正常状态推进不增加睡眠。阶段日志与串口输出、调度会产生少量开销;总耗时不必精确等于所有打印阶段之和。
当前超时参数:
| 环节 | 设定 |
| --- | --- |
| TLS 发送重试预算 | 1000 ticks,约 5 秒 |
| TLS 握手预算 | 4000 ticks,约 20 秒 |
| HTTP Upgrade 等待预算 | 2000 ticks,约 10 秒 |
| 业务线程等待连接结果 | 45000 ms |
这些是循环或等待点上的预算,不是保证底层 DNS/TCP/密码函数可被任意时刻强制中断的实时截止期限。不能把超时值改小当作运算提速,否则可能只是让慢网络更容易失败。
## 13. 第二轮性能优化:完整握手优先 X25519
对应提交:`551a7bb1e657c12f45ac0a1f2ca851625723a63d`。
### 13.1 发现默认曲线顺序的影响
SDK 的曲线列表默认以较大的曲线排在前面,包括 P-521,然后才到 P-384、P-256,X25519 较靠后。
采用固件根 CA、TLS 1.2 和严格证书/域名验证,通过主机对后台做独立完整握手探测:
| ClientHello 曲线顺序 | 后台选择 |
| --- | --- |
| 模拟 SDK 原顺序 | P-521 |
| X25519 优先 | X25519 |
| P-256 优先 | P-256 |
| 从最终固件数组读取的新顺序 | X25519 |
这证明当前后台能够配合客户端优先级使用更适合该 MCU 的曲线。服务器未来更换 TLS 配置后是否仍按相同规则选择,需要重新验证。
### 13.2 最终配置及实现方式
新顺序优先为:
```text
X25519 → P-256 → P-384 → P-521 → 其余原已启用曲线
```
其他原已启用曲线保留作为兼容回退,不修改全局密码套件列表。
SDK 没有向 APP 导出 `mbedtls_ssl_conf_curves()` 的 setter,因此使用该函数本来执行的同等配置字段赋值:
```c
ssl->ssl_conf.curve_list = rws_tls_preferred_curves;
```
数组为静态存储,满足 TLS 配置生命周期要求;赋值发生在 `mbedtls_ssl_config_defaults()` 之后、`mbedtls_ssl_setup()` 之前。ABI 测试覆盖此字段及协商诊断所访问的结构体。
新增日志:
```text
WSS TLS full handshake: prefer X25519, P256, P384, P521
WSS TLS negotiated ECDHE group: 9 (X25519=9, P256=3, P384=4, P521=5)
```
第一条只表示配置偏好,第二条才表示实际解析到的协商结果。这里的 9/3/4/5 是 mbedTLS 内部枚举,不是 TLS 协议线上 NamedGroup 编号;例如 X25519 的 TLS 编号是 29,两者不能混淆。
### 13.3 主机新建 ECDH 基准
使用真实 SDK 算法,每轮重新生成客户端临时密钥并计算共享密钥,独立验证双方共享结果一致。各曲线 20 次:
| 曲线 | 平均客户端生成密钥 + 共享密钥运算 | 测试额外峰值堆 |
| --- | ---: | ---: |
| X25519 | 1.55 ms | 1144 字节 |
| P-256 | 1.90 ms | 3688 字节 |
| P-384 | 3.40 ms | 4808 字节 |
| P-521 | 5.15 ms | 6376 字节 |
峰值堆包含测试对端共享结果校验阶段的分配,不是板端完整 TLS 的峰值内存。测试使用确定性测试熵构造可重复输入,限于主机测试程序;固件继续使用其硬件熵与 CTR_DRBG 路径。
### 13.4 最新板端验证:完整握手 1455 ms
用户补充的 15:52 日志从冷启动、联网到首次按键连接,出现:
```text
WSS TLS negotiated ECDHE group: 9 (X25519=9, P256=3, P384=4, P521=5)
WSS TLS phase server certificate (3): elapsed=935 ms, calls=935 ms, retries=0
WSS TLS phase client key exchange (8): elapsed=135 ms, calls=135 ms, retries=0
WSS TLS handshake total=1455 ms, state=16, ret=0
WSS TLS handshake and certificate/hostname verification passed: banban.api.talkingq.com:443
WebSocket upgrade verified: HTTP 101
ws connect okay
```
可以确认:
- 新 APP 已生效,实际协商到 X25519。
- 执行了证书校验和客户端密钥交换等完整握手阶段。
- TLS 验证通过,WebSocket Upgrade 成功。
- 客户端密钥交换从 445 ms 降到 135 ms,减少 310 ms,约减少 69.7%,该阶段约快 3.3 倍。
- TLS 总耗时从 1820 ms 降到 1455 ms,减少 365 ms,约减少 20.1%。
前一版板端没有记录实际曲线名称,所以“前一版为 P-521”的依据是 SDK 默认顺序和主机对后台的复现;最新板端 X25519 则有直接协商日志证据。
### 13.5 运算窗口调整为何没有采用
还比较了 `MBEDTLS_ECP_WINDOW_SIZE` 的 4、5、6。此参数影响点乘预计算表大小,窗口增大可能减少部分运算,但预计算和分配也有成本。
| 窗口 | 同一证书链平均校验 | 测试峰值分配 |
| --- | ---: | ---: |
| 4 | 10.35 ms | 11913 字节 |
| 5 | 10.20 ms | 16009 字节 |
| 6 | 10.45 ms | 18697 字节 |
收益不稳定,内存开销增加,因此固件保留默认 window=4。参数更大并不保证一次性完整握手更快。
## 14. 各种密码算法分别负责什么
### 14.1 ECDSA:验证“是谁签的”
ECDSA 是椭圆曲线数字签名算法。本项目用它验证证书链以及服务器临时密钥参数上的签名。
简化验证关系如下,实际实现还要验证输入、曲线点、数值范围和错误条件:
```text
已知:签名 (r, s)、消息摘要 e、公钥 Q、基点 G、子群阶 n
w = s^(-1) mod n
u1 = e × w mod n
u2 = r × w mod n
R = u1 × G + u2 × Q
验证 R 的横坐标 mod n 是否等于 r
```
这个过程中有大整数运算、求逆和椭圆曲线点乘,解释了为什么低功耗 MCU 上证书验证会有明显成本。
证书中的公钥曲线、证书的签名算法、TLS 临时密钥交换曲线是三个不同概念。例如当前叶证书公钥为 P-256,但证书由上级 CA 使用 ECDSA/SHA-384 签名;TLS 临时密钥现在可以使用 X25519。
### 14.2 ECDHE:双方协商临时共享秘密
ECDHE 是临时椭圆曲线 Diffie-Hellman。简化表示为:
```text
客户端临时私钥 a,公钥 A = aG
服务器临时私钥 b,公钥 B = bG
客户端计算 aB,服务器计算 bA,得到同一共享秘密
```
共享秘密再结合 TLS 随机数和派生过程得到记录层密钥,不是直接把某个公钥当作 AES 密钥。临时密钥交换可提供前向保密性质,前提包括正确随机数、临时私钥管理和实际安全实现。
本次始终保持每次新连接的完整临时密钥交换,没有通过固定私钥或复用临时密钥来提速。
### 14.3 X25519:适合高效密钥交换的标准函数
X25519 基于 Curve25519 的 Montgomery 曲线,底层素数为:
```text
p = 2^255 - 19
曲线常数 A = 486662
```
它采用规定的标量处理和基于 Montgomery ladder 的计算方式进行密钥交换。这里描述算法背景,实际代码使用 SDK 自带实现,并非本次自行重写;也没有因此宣称完成了独立侧信道安全审计。
X25519 通常提供约 128 位安全强度。P-521 的理论强度更高、运算量也更大;本服务的叶证书 P-256 本身约为 128 位安全强度,不能根据 AES-256 或 P-521 单一名称就断言整个协议具备 256 位综合安全强度。
本次在标准广泛使用的 X25519 与当前业务身份体系之间做性能选择,保留服务器证书、域名、日期和握手签名校验。
### 14.4 AES-GCM:加密和验证业务数据完整性
后台探测实际协商的 TLS 1.2 套件为:
```text
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
OpenSSL 名称:ECDHE-ECDSA-AES256-GCM-SHA384
```
| 名称组成 | 职责 |
| --- | --- |
| ECDHE | 临时密钥交换 |
| ECDSA | 服务器身份相关签名验证 |
| AES-256-GCM | 记录层对称加密和认证标签 |
| SHA-384 | 该 TLS 1.2 套件的 PRF/握手相关哈希选择 |
GCM 结合计数器模式加密与 GHASH 认证,属于 AEAD。它主要作用于握手后大量业务数据,不能把证书校验的 935 ms 全部归因于 AES。
套件名不规定 ECDHE 必须用 P-521,也不代表每张证书都由 SHA-384 签名。实际探测中,证书链为 ECDSA/SHA-384,服务器握手参数签名显示 ECDSA/P-256/SHA-256;这些并不矛盾。
固件没有新增“只允许上述一个套件”的固定白名单。上述套件是本次后台实际协商结果,服务器配置变动后应重新观察。
### 14.5 CTR_DRBG 与随机数
固件使用 mbedTLS CTR_DRBG,并通过 SDK 硬件熵接口播种。随机性用于 TLS、临时密钥以及 WebSocket 握手 nonce 等流程。
本次优化没有关闭随机数、改成固定随机种子,也没有用主机 benchmark 的确定性测试熵替换固件随机源。
### 14.6 根信任与交叉签名
根 CA 是设备预先信任的锚;链条中的其他证书需要沿签发关系验证到它。根证书自签名的存在不是“第一次信任它”的理由,信任来自设备内置的可信配置。
Root YE 交叉签名证书使其能够连接到设备已信任的 ISRG Root X2。若未来希望缩短验证链,不能直接删掉中间证书或把任意下载到的证书当作根;需要协调可信根部署、完整链验证和证书生命周期。
## 15. 最新耗时归因:设备端还是服务端
### 15.1 两轮板端阶段对比
| 阶段 | NIST 版,14:38 | X25519 版,15:52 | 解释 |
| --- | ---: | ---: | --- |
| ClientHello | 5 ms | 5 ms | 本地构造/发送 |
| 等待 ServerHello | 50 ms | 40 ms | 网络、服务端处理与本地轮询 |
| 服务器证书校验 | 965 ms | **935 ms** | SDK 内部证书处理为主 |
| ServerKeyExchange | 160 ms | 165 ms | 验证握手签名等;最新 `calls=160 ms` |
| ClientKeyExchange | **445 ms** | **135 ms** | 本地临时密钥与共享密钥运算,主要优化目标 |
| 后续等待阶段 12 | 120 ms | 90 ms | 服务端后续消息、网络与轮询 |
| 其他阶段/记录开销 | 约 75 ms | 约 85 ms | 差额项,含其他阶段、日志、调度等 |
| **TLS 总计** | **1820 ms** | **1455 ms** | 设备内部统计 |
差额项是总计扣除表中单列项目,并不等同于独立测量出的“串口开销”。两次证书校验相差 30 ms,也不能算作 X25519 对证书验证的优化收益,因为该阶段算法与证书链未改。
当前证书阶段占 TLS 总时间:
```text
935 / 1455 ≈ 64.3%
```
证书、服务器密钥交换、客户端密钥交换三个阶段的 `calls` 合计约 1230 ms,占总时间约 84.5%。主要时间仍在 SDK 调用内部,因此当前连接瓶颈主要是设备端处理,而不是等待服务器响应。
必须保留的归因边界:`calls` 含调度时间;没有 CPU 周期/占用率采样,不能把这 1230 ms 全称为纯密码运算。网络等待阶段也不能直接拆成“运营商耗时”和“服务器 CPU 耗时”。
### 15.2 用户最关心的按键到 WebSocket 成功
| 时间点/指标 | NIST 版 | X25519 版 |
| --- | --- | --- |
| 按键 | 14:38:46.455 | 15:52:25.453 |
| 进入业务连接函数 | 14:38:46.605 | 15:52:25.645 |
| TCP 成功 | 14:38:46.731 | 15:52:25.788 |
| WebSocket 101 | 14:38:48.683 | 15:52:27.389 |
| **按键 → 101** | **2.228 秒** | **1.936 秒** |
| 连接函数入口 → 101 | 2.078 秒 | 1.744 秒 |
按键到 101 减少约 292 ms,即约 13.1%。它与 TLS 内部减少的 365 ms 不完全相同,是因为按键调度、DNS/TCP、HTTP Upgrade 以及日志批次时间也在变化。
串口接收时间可能受缓冲和分片影响,设备内部 tick 更适合比较 TLS 阶段;按键到 101 的端到端指标仍应在同一测试环境多次测量。
### 15.3 提示音不属于本轮目标耗时
最新日志在 15:52:27.389 连接成功,15:52:30.624 左右提示音结束并开始录音,另有约 3.24 秒。
这是连接后的音频业务等待,不应计入 TLS 握手。用户已明确优先优化“按键到 WebSocket 成功”,本次没有修改提示音。
### 15.4 其他日志现象的正确理解
- 14:38 的 `tts_error_set_speech1` 是上一轮业务等待触发的主动断开,与随后完整握手成功不是同一个故障阶段。缺少那一轮完整请求/响应上下文,不能据此直接归因后端慢。
- `frames:500, speech:0` 对应约 10 秒录音未识别到语音后停止,属于 VAD/录音链路判断。如用户当时有说话,应查音频采集和 VAD。
- 最新日志先有 `CFUN0`、PMIC 关机,再出现 `cold power on` 和 `long press, do poweron!!`。该片段本身不足以认定是 TLS 引起的异常重启。
- 反复出现的 SDK 启动提示、音频 `UNDERRUN` 不能仅凭字面与 TLS 因果关联;需要相应模块的上下文和功能表现。
## 16. Git 提交记录及职责划分
旧板基础分支为 `legacy-board`,WSS 开发分支为 `legacy-board-wss`。GNSS 绕过和默认音量不是 WSS 提交新增的行为,而是分支继承的基线。
| 提交 | 日期 | 内容 | 对编译/运行的主要影响 |
| --- | --- | --- | --- |
| `f283223` | 2026-09-15 | 无 GNSS 旧板适配 | 基础硬件启动/定位行为调整 |
| `4b56595` | 2026-09-15 | 默认音量 100 | 旧板 WSS 分支的基线 |
| `3e463f5` | 2026-09-16 | 首轮 WSS 接入 | 地址迁移、严格 TLS、Upgrade 验证、收发/任务处理、测试与文档 |
| `5c3e7ee` | 2026-09-16 | ECC/RTC 与接收重试修复 | 共享 profile、ECDSA/GCM、日期来源、ABI 检查、WANT 误断线修复;涉及内核 |
| `3a0714c` | 2026-09-16 | NIST 加速与 TLS 阶段计时 | 内核运算优化、APP 计时、测试基准 |
| `551a7bb` | 2026-09-16 | X25519 优先的完整握手 | APP 曲线列表、协商日志、ABI 扩展、曲线探测与算法基准 |
四个 WSS 提交的完整标识与标题:
```text
3e463f57b69732ced0562e8e52915cc1566c4b6c
Add verified WSS transport for legacy boards
5c3e7eee6dc3bbf6d514af6bb93465bc81d6c952
Fix legacy WSS certificate support and receive retries
3a0714c735f5eec3eaab19ec04f12ef26e8145c1
Accelerate legacy WSS elliptic-curve operations and time TLS phases
551a7bb1e657c12f45ac0a1f2ca851625723a63d
Prefer X25519 for legacy WSS full handshakes
```
这些提交在本会话中完成的是本地提交,没有执行远端 push。无关的 `docs/EC800_AITALK_KEY-V1.01.pdf` 未加入这些提交。
提交时间、包生成时间和串口时间并非一个统一时钟,也不是逐一对应的版本号。可靠定位应结合 Git 标识、包哈希、镜像内容和新增日志,而不是只比时间戳。
## 17. 固件包版本、镜像变化与回退
以下均为本工作区已生成并核对的包;不是根据提交号推测的包体大小。文件路径根目录均为 `D:\banban\project\nfc_gnss`。
| 包所在子目录/文件 | ZIP 字节数 | cp.bin 字节数 | customer_app.bin 字节数 | 状态 |
| --- | ---: | ---: | ---: | --- |
| `artifacts/wss/EG800AKCN_11LG-legacy-wss.zip` | 6526785 | 2539520 | 205956 | 首轮旧包,内核缺 ECDSA;仅供历史对照 |
| `artifacts/wss/ecc-fix/EG800AKCN_11LG-legacy-wss-ecc.zip` | 6537607 | 2543616 | 206180 | 已补 ECC/RTC,但仍有接收误断线;仅供历史对照 |
| `artifacts/wss/recv-fix/EG800AKCN_11LG-legacy-wss-recv.zip` | 6537490 | 2543616 | 206060 | 已验证功能,TLS 约 9.6 秒 |
| `artifacts/wss/tls-speed/EG800AKCN_11LG-legacy-wss-fast.zip` | 6544532 | 2547712 | 206580 | 板端 TLS 1820 ms |
| `artifacts/wss/full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip` | 6544840 | 2547712 | 206780 | 最新板端 TLS 1455 ms、X25519、HTTP 101 |
具体影响:
- ECC/RTC 内核镜像相对首轮 WSS 包增加 4096 字节。
- NIST 加速内核相对 ECC/RTC 包再增加 4096 字节。
- X25519 包与 NIST 包的 `cp.bin` 已逐字节核对一致,APP 增加 200 字节。
- 上述是打包后的镜像大小差,不等于某一算法源码编译后精确占用量;对齐、压缩和链接布局也会影响结果。
最新包:
[EG800AKCN_11LG-legacy-wss-x25519.zip](D:/banban/project/nfc_gnss/artifacts/wss/full-handshake/EG800AKCN_11LG-legacy-wss-x25519.zip)
标准构建输出也为相同内容:
[target/EG800AKCN_11LG/EG800AKCN_11LG.zip](D:/banban/project/nfc_gnss/target/EG800AKCN_11LG/EG800AKCN_11LG.zip)
各 ZIP 的 SHA-256:
```text
初版 WSS:
87747b0cc6c7223e5509e381fda8920d475aea5ba41016ca6d97e930e1a334ec
ECC/RTC:
f8db36b72838f869d8529715e34485509f3805899945d75fa0aeae072ae11956
接收修复:
d3fd2894dfffdb4aec915e2b53409d0454d6c7691200198efb5990b0bc489d9b
NIST 加速:
8ca26df0e5efbd2e21db7c7aef9356ac129f086dacb61a351dc82f2499c7b356
X25519 最新:
fe28313cb43f7cc3fc05ed59431b1c0239257a867e3d65d07bfd32e4945a7fb1
```
最新包内镜像 SHA-256:
```text
cp.bin:
4bfbf8a01a05c16e983fb854c057b9925f66c713cea80107be943b6e5746915f
customer_app.bin:
7b82a10cab51d8bbd75a62de4876d9b2c51b225930a43376940ab337a0c30a27
```
构建包、探测 JSON、公共证书和日志位于本地 `artifacts/wss`,该目录通过 `.gitignore` 排除绝大多数生成物,Git 保留说明文件等指定内容。代码提交不等于已经把 ZIP 或本机证据文件同步给其他人。
## 18. 构建、打包与实际烧录的区别
### 18.1 全新检出的完整构建
在本工作区根目录依次执行:
```powershell
Set-Location 'D:\banban\project\nfc_gnss'
.\build.bat kernel
.\build.bat app
.\build.bat firmware
```
内核需要可用的 ARMCC 工具链与合法可用的许可。已有构建环境中已经完成上述构建。
### 18.2 已有匹配内核时的 APP 改动
在当前已经构建 NIST 加速内核的工作区,接收逻辑或 X25519 APP 配置改动可以执行:
```powershell
.\build.bat app
.\build.bat firmware
```
前提是打包使用的内核确实与 APP 的共享配置相匹配。不能把这一结论泛化为“以后 WSS 改动永远不用编译内核”。
### 18.3 APP-only 编译不等于 APP-only 烧录
`build.bat firmware` 生成的内层 ZIP 仍是多分区完整包。最新 `download.json` 包含内核、APP、文件系统及其他固件分区,还包含 NVM/FOTA 参数等擦除指令,`factory_a` 有 `productionOnly` 条件。
因此:
- “这次只改 APP”描述源码和编译范围。
- “这次只刷 APP”必须由下载清单/工具选择明确保证。
- 使用本文提供的完整 ZIP,应按完整包理解;不能假设它只更新 `customer_app` 或一定保留所有用户配置。
- 旧板绕过 GNSS 运行路径,不代表完整包里不再包含 GNSS 分区镜像。
实际烧录结果应以工具执行记录与清单为准。本文没有执行设备烧录,板端日志由用户提供。
### 18.4 构建验证不能只看批处理退出码
SDK 外层批处理的退出状态可能不能充分反映内部失败。验证同时检查:
- APP/内核 ELF 或 AXF、BIN 是否正确生成。
- ZIP CRC。
- ZIP 中 `cp.bin`、`customer_app.bin` 与本次构建逐字节一致。
- 必需算法、时间与诊断符号。
- GNSS 绕过入口反汇编。
- 固件默认 URL 与必要日志字符串。
内核构建会改写部分跟踪的生成输入,例如 `diagDB.o` 和链接反馈文件。此前构建对这些文件先备份、生成后另存、最后恢复原字节,避免把构建副产物混入功能提交。
## 19. 测试体系、结果与可复现命令
### 19.1 测试覆盖分层
| 层级 | 工具 | 覆盖内容 | 结果 |
| --- | --- | --- | --- |
| URL/HTTP 纯逻辑 | `tools/test_wss.c` | 地址迁移、非法 URL、端口、边界、每个响应分片边界、101/Accept/头字段 | 通过 |
| TLS 写入/对象取消 | `tools/test_wss_io.py` | 实际函数提取,短写、WANT、EOF、错误、超时、tick 回绕、worker 释放 | 通过 |
| TLS 接收 + Upgrade | `tools/test_wss_receive.py` | 实际接收/等待函数和解析器组合,复现并修复误断线 | 通过 |
| 分步握手控制 | `tools/test_wss_handshake.py` | 正常推进、WANT、致命错误、截止预算、tick 回绕、完成状态、协商日志分支 | 通过 |
| ARMCC/GCC ABI | `tools/check_mbedtls_abi.py` | 实际头文件结构大小、字段偏移和功能宏 | 通过 |
| SDK 算法与证书 | `tools/test_mbedtls_cert.py` / `.c` | 根 CA、证书链、错误域名/不可信根/日期、AES-GCM 向量、ECDH 双方一致与释放 | 通过 |
| 后台 WSS 探测 | `tools/probe_talkingq_wss.ps1` | 系统信任、TLS、HTTP 101 和 Accept | 443 通过 |
| 固件根 CA 单独信任 | `tools/probe_wss_root.py` | 不依赖 OS 根库或中间证书下载,含错误域名/空信任库负例 | 通过 |
| 后台曲线选择 | `tools/probe_wss_groups.py` | 原顺序及新固件顺序的实际完整握手 | 新顺序选择 X25519 |
| 包完整性 | `tools/verify_wss_package.py` | ZIP、镜像一致性、内核能力、GNSS 绕过、关键日志 | 通过 |
| 硬件运行 | 用户板端日志 | TLS、协商曲线、HTTP 101、录音初始化和真实耗时 | X25519 版本已通过上述项目 |
主机 TLS 桩测试不等于完整蜂窝网络端到端测试。实际 SDK 算法主机测试使用真实算法源码,但分配器、时钟、日志和 CPU 架构与设备不同。所有结论应按对应证据层级使用。
### 19.2 常用执行方式
```powershell
Set-Location 'D:\banban\project\nfc_gnss'
.\tools\test_wss.cmd
.\ql-cross-tool\win32\owtoolchain\Python\python.exe tools\check_mbedtls_abi.py
.\ql-cross-tool\win32\owtoolchain\Python\python.exe tools\verify_wss_package.py
```
算法测试需在 Visual Studio 开发者命令行中执行,使 `cl` 可用:
```bat
ql-cross-tool\win32\owtoolchain\Python\python.exe tools\test_mbedtls_cert.py
ql-cross-tool\win32\owtoolchain\Python\python.exe tools\test_mbedtls_cert.py --compare-nist
ql-cross-tool\win32\owtoolchain\Python\python.exe tools\test_mbedtls_cert.py --compare-windows
```
后台探测:
```powershell
powershell.exe -NoProfile -ExecutionPolicy Bypass -File tools\probe_talkingq_wss.ps1
.\ql-cross-tool\win32\owtoolchain\Python\python.exe tools\probe_wss_root.py
.\ql-cross-tool\win32\owtoolchain\Python\python.exe tools\probe_wss_groups.py
```
`probe_wss_groups.py` 当前使用 Git for Windows 自带的 OpenSSL 路径。跨电脑使用应核对该路径。证书测试默认使用本地已捕获的证书链,未来叶证书过期后需要更新测试输入,不能通过关闭日期验证使旧样本继续“通过”。
包校验器针对当前构建的能力与符号要求,不是所有历史版本的通用校验器。当前 APP 与历史包不同,直接传入历史包出现镜像不一致属于预期,应切换到对应代码/构建再比较,或只做独立历史哈希校验。
## 20. 解决后的影响与兼容性
### 20.1 安全行为
| 方面 | 修改后的行为 | 运维影响 |
| --- | --- | --- |
| WebSocket 传输 | 通过 TLS 加密与完整性保护 | 运营商/网络须允许实际 TLS 入口 |
| 服务器身份 | 根 CA、证书链、日期、域名、握手签名验证 | 证书或时间异常会明确失败 |
| SNI | 发送业务域名 | 支持按域名选择证书的 TLS 入口 |
| 客户端身份 | 原应用层消息 | 不需要给每台设备新增 TLS 客户端证书 |
| 协议降级 | 不自动从 WSS 降回 WS | 不能用错误端口/路径“凑合连接” |
| 证书续期 | 依赖可构建到内置根的有效链 | 后台换根或改链需重新评估 |
| 吊销检查 | 未增加 OCSP/CRL 查询 | 不能宣称已实现在线证书吊销检查 |
共享 mini profile 为兼容原 SDK 保留了部分历史算法/协议宏;WSS 单独设置 TLS 1.2 下限。不能因为 WSS 达到 TLS 1.2,就宣称系统所有 TLS 用户都已收紧到同一策略。也不能把本轮修复视为对整个厂商 TLS 栈的完整安全审计。
### 20.2 功能行为
- 默认 URL 与已知旧地址的读取迁移会影响实际连接目标。
- 服务器证书或系统时间不正确时,设备会连接失败,而不是跳过验证。
- WebSocket 101/Accept 校验更严格,错误反向代理响应会被拒绝。
- 设备鉴权字段、业务消息、提示音和录音时序保持原逻辑。
- 用户无 GNSS 旧板逻辑和默认音量 100 继续保留。
- TLS 成功后的音频业务超时依然需要按业务链路分析,不能因传输层升级而认为业务超时问题已经全部解决。
### 20.3 性能、内存与功耗
- 全新 WSS 连接比明文 WS 多了 TLS 握手工作;性能优化缩短了这部分,但没有消除协议本身的成本。
- NIST 优化增加部分内核代码体积;X25519 APP 增量较小,算法基准中的密钥交换峰值分配更低。
- worker 栈增加到 16 KiB,需纳入整体内存预算。
- 运算时间减少可能减少相应 CPU 活跃时间,但本次没有电流波形或能耗积分测试,不给出续航提升数值。
- 没有引入预连接或保活策略改变来换取响应速度,因此没有通过新增常驻连接有意扩大待机功耗。
### 20.4 可维护性
- TLS 功能由共享配置集中管理,降低 APP/内核独立修改造成的风险。
- 日志区分 CA 解析、TCP、TLS 阶段、HTTP Upgrade,定位精度提高。
- 新增 SDK 内部结构读取用于曲线诊断,要求升级 SDK 时重新检查 `ssl_internal.h` 与 ABI,不能只复制业务 APP。
- 协商优先级只改变当前 WSS 的配置,不通过修改 SDK 全局默认曲线顺序影响其他连接。
- 包哈希、镜像一致性与明确的历史包路径,减少旧包混用。
## 21. 现有边界、尚未完成的验证与建议补充
以下是根据当前源码和本轮证据列出的边界,不表示它们都已经造成现场故障。
### 21.1 配置输入长度
URL 解析器有输出容量和字符检查,但原 `hal_os_set_talkingq_url()` 的 64 字节临时缓冲区仍使用 `strcpy`,共享属性处理也存在相似复制方式。**解析阶段的边界检查不能替代保存入口的边界检查。**
后续建议统一在属性接收和 KV 保存前验证类型、长度及 URL 合法性,拒绝超过存储容量的配置。此项没有在本轮 WSS 提交中额外修改,避免把已完成范围描述为覆盖了全部配置输入安全。
### 21.2 运行稳定性与业务验收
目前已有多次板端建连和用户功能反馈,最新片段确认 TLS、HTTP 101、发送鉴权消息和录音启动。仍建议保留正式验收项:
- 服务端明确记录设备鉴权成功,而不只看设备发送了身份。
- 长时间音频收发、连续多轮对话、断网重连、服务器重启、弱网。
- 多次冷启动与休眠/唤醒后的完整握手。
- 运行中堆峰值、栈水位、对象释放和长期内存趋势。
- 不同运营商/网络状态的延迟分布。
不应使用一次成功日志替代上述稳定性结论。
### 21.3 证书与时间生命周期
要监控叶证书续期、链条变化和设备时钟。相同域名不代表证书永远由同一根签发;只有一张根 CA 的信任集有利于范围明确,也意味着服务端换根时需要设备更新策略。
当前主机测试样本有明确有效期,过期后应更换样本。固件长期运行的正确做法是正常验证新证书,而不是绕过日期校验。
### 21.4 当前耗时还可优化到什么程度
在“不提前建连、不使用会话恢复、每次完整握手”的约束下,优先级建议如下:
1. **证书链处理**:当前 935 ms,占约 64%,是最大剩余项。可以评估是否有重复解析/不必要工作,以及经过基准验证的 SDK 运算优化。
2. **后台链条设计**:与后台/证书运维一起评估更短且可验证的链。前提是明确可信根分发、兼容性和轮换方案,不能只是删中间证书。
3. **网络等待策略**:当前两个主要等待阶段合计约 130 ms,空间小于证书运算。可测量 readiness 通知与轮询差异,但要同时考虑 CPU 占用和 RTOS 调度。
4. **日志开销**:当前分阶段日志便于诊断。产品版本可评估降低输出量的收益,但应保留必要总计和失败诊断,并做同条件对照。
5. **更深层编译/硬件加速**:仅在明确硬件能力、SDK 适配和实测数据后考虑,不直接修改全局频率或重写密码算法以追求数字。
不能根据 935 ms 证书阶段直接承诺“必定还能降到几百毫秒”,更不能通过关闭证书验证来满足耗时目标。
### 21.5 会话恢复探测作为补充资料
在用户明确最终策略之前,做过一次主机 TLS 1.2 会话恢复探测:启用 Session Ticket 时,后台提供 300 秒 lifetime hint,后续连接确实复用会话;禁用 Ticket 的那组测试未观察到 Session ID 复用。
主机样本中完整握手约 36.8 ms,Ticket 恢复约 14.7/17.1 ms,均完成 101。该结论仅反映当时主机与服务器组合,不能换算为设备效果,也不能推断后台所有节点永久具有同一配置。
用户选择仅优化完整握手后,没有将这条路线加入固件。当前共享配置未启用 `MBEDTLS_SSL_SESSION_TICKETS`,业务代码未增加 session 保存/恢复逻辑。该探测不是最新 1455 ms 的来源。
### 21.6 建议的下一轮性能记录方式
采用同一块板、同一固件、同一运营商环境,每个版本至少记录一组多次完整握手样本,区分冷启动首次连接与同次开机的后续新连接。
每条记录建议包含:
| 字段 | 用途 |
| --- | --- |
| Git 提交号、固件 ZIP SHA-256 | 证明版本 |
| 是否冷启动、数据网络是否已就绪 | 区分建连前置条件 |
| 按键、TCP、TLS、101 时间 | 端到端与分层耗时 |
| 实际 ECDHE group | 确认优化确实被协商使用 |
| 各阶段 elapsed/calls/retries | 归因运算、调度和等待 |
| 信号/网络信息与成功率 | 解释离群样本 |
| 堆峰值、栈水位 | 验证空间代价 |
统计中位数、P95、最大值和失败率,比只比较一次最快日志更适合作为产品验收依据。本次已有数据足以证明改动生效,但尚不足以形成上述统计分布。
## 22. 常见日志排障速查
| 现象 | 优先解释/检查 |
| --- | --- |
| `WSS trust anchor loaded` | 仅表示 CA 数据已加载,继续查解析结果 |
| `-9774` / `-0x262E` | 检查实际运行内核是否包含 ECDSA,以及是否刷到正确版本 |
| `WSS requires valid network/RTC time` | 检查 RTC/NITZ,不要关闭日期验证 |
| `getaddrinfo` 失败 | DNS、PDP、域名与白名单 |
| TCP 成功但 TLS 失败 | 端口协议、证书链、时间、算法、网络或 ABI;看具体阶段和错误码 |
| `socket break` | 旧日志表示退出连接循环;新日志已改名,不是断线 |
| TLS 验证成功但 `HTTP 404` | 当次 `/ws` 路由或反向代理配置问题 |
| `HTTP 0` | 尚未解析到有效状态码;检查前面的读错误、超时和响应完整性 |
| `HTTP 101` 但 Accept 不匹配 | Upgrade 响应不属于本次 Key,或代理/实现错误;必须拒绝 |
| `WSS TLS negotiated ECDHE group: 9` | 本次实际使用 X25519 |
| `ws connect okay` | WebSocket 连接层完成;仍应独立确认业务鉴权 |
| `tts_error_set_speech1` | 业务等待超时;需要对应请求/响应上下文 |
| `frames:500, speech:0` | 录音/VAD 未识别语音,不是 TLS 阶段耗时 |
| 日志仍为旧格式 | 核查工具选包、实际镜像、哈希及是否重启生效 |
## 23. 关键源码与证据索引
以下链接指向本机工作区或用户提供的本地日志附件,便于复核;转交文档时应同时提供需要的文件,因为绝对路径不会自动复制内容。
### 23.1 主要实现
| 文件 | 职责 |
| --- | --- |
| [talkingq_wss.h](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/inc/talkingq_wss.h) | 默认 URL 与缓冲区容量 |
| [talkingq_wss.c](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/talkingq_wss.c) | URL 解析、历史地址识别 |
| [talkingq_root_ca.c](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/talkingq_root_ca.c) | 内置信任根 |
| [hal.c](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/lib/hal/hal.c) | KV 地址读取、保存与迁移 |
| [fota.c](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/fota.c) | 原共享属性中的地址保存调用 |
| [ws_talkingq.c](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/ws_talkingq.c) | 业务连接、回调和原身份鉴权 |
| [rws_socketpriv.c](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpriv.c) | TLS 建连/收发、曲线策略、阶段计时、worker |
| [rws_socketpub.c](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/lib/librws/src/rws_socketpub.c) | 取消连接和对象生命周期 |
| [rws_handshake.c](D:/banban/project/nfc_gnss/ql-application/threadx/interface/custom_app/lib/librws/src/rws_handshake.c) | HTTP 101/Accept 与分片响应验证 |
| [talkingq_mbedtls_config.h](D:/banban/project/nfc_gnss/ql-config/config/talkingq_mbedtls_config.h) | APP/内核共享 TLS profile、NIST 开关 |
| [内核 x509.c](D:/banban/project/nfc_gnss/ql-kernel/threadx/quec_mbedtls/x509.c) | 证书当前时间来源修复 |
| [内核 ecp_curves.c](D:/banban/project/nfc_gnss/ql-kernel/threadx/quec_mbedtls/ecp_curves.c) | SDK 原有 NIST 快速模约减实现 |
| [内核 ecp.c](D:/banban/project/nfc_gnss/ql-kernel/threadx/quec_mbedtls/ecp.c) | SDK 点乘、默认曲线顺序与窗口策略 |
### 23.2 测试和包证据
| 文件 | 内容 |
| --- | --- |
| [test_wss.cmd](D:/banban/project/nfc_gnss/tools/test_wss.cmd) | 常用回归入口 |
| [check_mbedtls_abi.py](D:/banban/project/nfc_gnss/tools/check_mbedtls_abi.py) | 双编译器 ABI/宏校验 |
| [test_mbedtls_cert.c](D:/banban/project/nfc_gnss/tools/test_mbedtls_cert.c) | 算法、证书、ECDH 与内存基准 |
| [test_mbedtls_cert.py](D:/banban/project/nfc_gnss/tools/test_mbedtls_cert.py) | 主机 SDK 算法编译入口 |
| [probe_wss_groups.py](D:/banban/project/nfc_gnss/tools/probe_wss_groups.py) | 实际后台曲线协商探测 |
| [verify_wss_package.py](D:/banban/project/nfc_gnss/tools/verify_wss_package.py) | 当前构建与包一致性校验 |
| [443 复测证据](D:/banban/project/nfc_gnss/artifacts/wss/retest-443/server-probe.json) | TLS、证书信息和 HTTP 101 |
| [根 CA 单独验证证据](D:/banban/project/nfc_gnss/artifacts/wss/root-only-probe.json) | 固件根信任与负例结果 |
| [ABI 检查产物](D:/banban/project/nfc_gnss/artifacts/wss/ecc-fix/tls-abi.json) | 最新检查的实际结构大小和偏移 |
| [曲线探测证据](D:/banban/project/nfc_gnss/artifacts/wss/full-handshake/group-probe.json) | 原顺序与新固件顺序的协商结果 |
| [最新包校验记录](D:/banban/project/nfc_gnss/artifacts/wss/full-handshake/verification.json) | 包/镜像哈希、清单与静态校验 |
| [Session 探测记录](D:/banban/project/nfc_gnss/artifacts/wss/session-probe.json) | 未纳入固件的会话恢复路线探测 |
部分报告是在新板端日志之前生成,其中 `hardware_test` 字段可能仍写“待上板”。这是生成时的历史状态;15:52 的新增板端结果以本文和下面的原始附件为准,不把后来的运行结果伪装成早期报告已经知道的事实。
### 23.3 用户板端日志来源
| 日志 | 用途 |
| --- | --- |
| [11:41 原始附件](C:/Users/talkingq/.codex/attachments/353f070d-8be2-435e-9774-5fa670c3eb96/pasted-text.txt) | 根 CA 解析失败调查 |
| [13:54 原始附件](C:/Users/talkingq/.codex/attachments/7aea2c62-ff1c-4905-a6a0-d9f9d2244872/pasted-text.txt) | 旧 APP 日志格式与包生效检查 |
| [14:06 原始附件](C:/Users/talkingq/.codex/attachments/2a56182d-f92b-409d-a1b7-3212af6ed0b3/pasted-text.txt) | TLS 通过后接收误断线 |
| 本会话 14:20 直接粘贴日志 | 约 9.6 秒 TLS 基线 |
| 本会话 14:38 直接粘贴日志 | NIST 版 1820 ms 与阶段耗时 |
| [15:52 X25519 原始附件](C:/Users/talkingq/.codex/attachments/b88fd282-bae0-44d5-9028-3b9a56a96530/pasted-text.txt) | 实际 group=9、TLS 1455 ms、HTTP 101 |
为便于总结传播,本文未重复原始日志中的设备 IMEI、具体设备 ID、序列号或任何认证密钥;原始日志附件仍应按项目资料管理。
## 24. 交付状态
- 四个 WSS 功能提交已存在于本地 `legacy-board-wss`,最新为 `551a7bb`。
- 最新完整包已生成并通过镜像/ZIP/符号校验。
- 用户 15:52 日志已验证 X25519、完整 TLS 握手 1455 ms 和 HTTP 101。
- 当前最大已观测连接耗时项为设备证书链处理,约 935 ms。
- 本文是对既有实现、提交、测试和新增设备日志的总结,不包含新的固件功能改动。
- 是否将本文另行提交或推送,属于后续文档发布操作;本次整理本身不改变既有固件包内容与 SHA-256。