559 lines
38 KiB
Markdown
559 lines
38 KiB
Markdown
# BLE-PROV-1.0 蓝牙配网与账号绑定协议
|
||||
|
|
|
|||
|
|
版本:1.0 草案|日期:2026-09-13|面向:网页前端、设备固件、SaaS 后端
|
|||
|
|
|
|||
|
|
**协议选型:手机/电脑与设备之间使用 BLE GATT;安全层采用 ESP-IDF Protocomm Security 2(patch 1);业务层使用本协议定义的 UTF-8 JSON 消息。设备联网后,通过 HTTPS + DEVREQ1 设备认证完成账号绑定。**
|
|||
|
|
|
|||
|
|
本文是一份新增协议契约,不表示功能已经实现或完成实机联调。当前工作区为 ESP32-S3 EchoEar、ESP-IDF v5.5.1;固件仍使用配置热点,当前 sdkconfig 未启用蓝牙。本文新增的 UUID、BLE 帧、命令和 `/ble-*` 云端接口均为拟定值,可据此开展实现与评审;不会自动兼容原来的热点配网页。
|
|||
|
|
|
|||
|
|
“必须 / 不得”表示互操作或安全的必要条件;“建议”表示可按产品调整的默认值。所有字段、字节序及枚举按本文执行,不允许前端和固件各自解释。
|
|||
|
|
|
|||
|
|
## 1. 范围与参与方
|
|||
|
|
|
|||
|
|
目标流程:用户登录平台,发现并识别附近设备,建立安全蓝牙会话,发送 Wi-Fi 配置与一次性绑定凭证,设备确认操作后联网,由 SaaS 提交绑定,页面显示结果。
|
|||
|
|
|
|||
|
|
| 链路 | 协议 | 职责 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 网页 ↔ SaaS | HTTPS + 当前用户登录会话 | 签发绑定凭证、查询归属结果 |
|
|||
|
|
| 手机/电脑 ↔ 设备 | BLE GATT + Security 2 + BLE-PROV JSON | 身份核对、配网参数传输、读取进度 |
|
|||
|
|
| 设备 ↔ SaaS | HTTPS + DEVREQ1 HMAC-SHA256 | 证明设备身份、提交绑定、恢复结果 |
|
|||
|
|
|
|||
|
|
网页通过用户手机/电脑的蓝牙与设备通信。SaaS 服务器不直接扫描用户身边的蓝牙设备。网页是 BLE Central / GATT Client,设备是 Peripheral / GATT Server。GATT 的 Server 在此指设备,不是云端服务器。[参考 R1、R2]
|
|||
|
|
|
|||
|
|
本版包含首次绑定,不包含 OTA、音频传输、企业 Wi-Fi、设备转让和已绑定设备重配网。已绑定设备返回 `ALREADY_BOUND`;不能通过重新配网或恢复网络设置覆盖云端归属。AI 服务激活沿用平台独立流程,不作为蓝牙配网完成条件。
|
|||
|
|
|
|||
|
|
## 2. 标识与生产数据
|
|||
|
|
|
|||
|
|
| 字段 | 格式 | 含义 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| `factory_device_id` | `KP01-` + 12 位大写十六进制 | 工厂身份,须与当前平台库存及 DEVREQ1 身份一致 |
|
|||
|
|
| `platform_device_id` | UUID | SaaS 内部设备主键,绑定结果中返回 |
|
|||
|
|
| `product_id` | uint32,1~4294967295 | 产品编号,平台维护编号与型号映射 |
|
|||
|
|
| `model` | ASCII,1~32 字节 | 型号,例如 `ECHOEAR-S3`;这是示例命名,不是已登记型号 |
|
|||
|
|
| `short_id` | 末 6 位工厂编号 | 只用于名称展示,不是认证依据,也不保证全局唯一 |
|
|||
|
|
| `setup_nonce` | 16 个随机字节的 Base64URL,无 `=`,22 字符 | 本次实体配网窗口标识 |
|
|||
|
|
| `session_id` | UUID v4 | SaaS 签发的绑定申请标识 |
|
|||
|
|
| `request_id` | UUID v4 | 一次业务操作的幂等标识 |
|
|||
|
|
| `mid` | uint16,1~65535 | 当前 BLE 连接内的一次请求/应答编号 |
|
|||
|
|
|
|||
|
|
产线必须写入工厂身份、产品编号、型号、现有的单设备云端认证密钥及版本,并生成独立的 SRP 配网凭据。不能用蓝牙地址、设备名称或用户登录密码作为密钥。
|
|||
|
|
|
|||
|
|
SRP 用户名固定为 `factory_device_id`,配网密码是每设备独立生成的 32 个随机字节的 Base64URL 字符串。这里的“用户名/密码”仅是本地安全握手凭据,与平台账号、Wi-Fi 密码无关。设备保存按官方算法生成的 16 字节 salt 和 verifier;二维码保存配网用户名和密码。二维码不得包含云端设备密钥。[参考 R3、R5]
|
|||
|
|
|
|||
|
|
二维码内容示意如下;尖括号内容必须在生产时替换,不能用于联调或生产认证:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"v": 1,
|
|||
|
|
"factory_device_id": "KP01-000000000001",
|
|||
|
|
"product_id": 1001,
|
|||
|
|
"setup_user": "KP01-000000000001",
|
|||
|
|
"setup_password": "<每设备独立的43字符Base64URL配网密码>"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
二维码采用设备本地可解析的数据,不把秘密放入网页 URL、查询参数或后台日志。前端仅在本次页面内存保存配网密码。本版要求扫码或输入该设备配网凭据;仅凭附近蓝牙名称不能完成安全认证。
|
|||
|
|
|
|||
|
|
## 3. 进入配网及设备发现
|
|||
|
|
|
|||
|
|
1. 用户在平台完成登录,选择“添加设备”。
|
|||
|
|
2. 用户长按设备指定触摸区域或按键 3 秒;未绑定设备打开 300 秒配网窗口,生成新 `setup_nonce`,显示配网提示。
|
|||
|
|
3. 设备发送可连接 BLE 广播;窗口内最多接受一个客户端连接。
|
|||
|
|
4. 网页通过本协议 Service UUID 过滤设备,用户在浏览器设备选择框中选择目标。
|
|||
|
|
5. 网页读取协议能力、建立安全会话,读取可信会话内的设备信息并核对二维码;随后可发送 `IDENTIFY` 让设备闪灯/显示识别动画。
|
|||
|
|
|
|||
|
|
广播名称为 `EchoEar-` + `short_id`,例如 `EchoEar-000001`。广播包包含 Flags 和完整的 128 位 Service UUID;完整名称放入 Scan Response,避免超过传统 31 字节广播包限制。不得要求浏览器在选择设备前读取任意厂家自定义广播字段。名称与信号强度只辅助选择;型号及身份以安全会话内信息、二维码和后台库存核对结果为准。
|
|||
|
|
|
|||
|
|
成功、窗口超时或用户退出后停止配网广播。设备普通重启不生成新的可绑定入口;需要明确的首次启用策略或上述实体操作。窗口到期后不得受理新配置,但已经受理的联网/云端确认任务继续收尾。BLE 断开不取消该任务。
|
|||
|
|
|
|||
|
|
## 4. GATT 服务与特征
|
|||
|
|
|
|||
|
|
以下为本协议拟定的固定 128 位 UUID,不是蓝牙 SIG 分配的标准配网服务。UUID 字符串按表使用;固件 SDK 若接收小端字节数组,必须按 SDK 规则转换并通过实际服务发现核对。[参考 R1]
|
|||
|
|
|
|||
|
|
| 名称 | UUID | 属性 | 数据与权限 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `PROVISION_SERVICE` | `8e7a1000-6f4b-4a2d-9c31-7d824f5e6100` | Service | 服务入口 |
|
|||
|
|
| `PROTOCOL_INFO` | `8e7a1001-6f4b-4a2d-9c31-7d824f5e6100` | Read | 明文 16 字节协议能力,不含秘密 |
|
|||
|
|
| `RX` | `8e7a1002-6f4b-4a2d-9c31-7d824f5e6100` | Write with response | 客户端发送握手或加密业务分片 |
|
|||
|
|
| `TX` | `8e7a1003-6f4b-4a2d-9c31-7d824f5e6100` | Indicate | 设备返回对应应答分片,带标准 CCCD `0x2902` |
|
|||
|
|
|
|||
|
|
前端必须先启用 TX 的 Indication,再发送 RX。Web Bluetooth 对应 `startNotifications()` 和 `writeValueWithResponse()`;方法名中的 Notifications 不代表本特征必须设置 Notify,本协议 TX 只设置 Indicate。[参考 R1、R2]
|
|||
|
|
|
|||
|
|
设备逐片发送 Indication,收到 ATT confirmation 后再发送下一片。确认只证明对端蓝牙协议栈接收,不表示网页业务已处理。GATT 写成功也不表示配网或绑定成功。
|
|||
|
|
|
|||
|
|
`PROTOCOL_INFO` 固定 16 字节,所有多字节整数为小端:
|
|||
|
|
|
|||
|
|
| 偏移 | 长度 | 字段 | v1 值 |
|
|||
|
|
|---:|---:|---|---|
|
|||
|
|
| 0 | 2 | magic | `42 50`,ASCII `BP` |
|
|||
|
|
| 2 | 1 | major | `01` |
|
|||
|
|
| 3 | 1 | minor | `00` |
|
|||
|
|
| 4 | 1 | security_version | `02` |
|
|||
|
|
| 5 | 1 | security_patch | `01` |
|
|||
|
|
| 6 | 2 | max_gatt_value | 20,即 `14 00` |
|
|||
|
|
| 8 | 2 | max_record | 4096,即 `00 10` |
|
|||
|
|
| 10 | 2 | capabilities | `01 00`:支持实体确认;其余位必须为 0 |
|
|||
|
|
| 12 | 4 | product_id | uint32,例如 1001 为 `E9 03 00 00` |
|
|||
|
|
|
|||
|
|
客户端只接受本文的 major、minor、security_version、security_patch 组合;不兼容时显示升级提示并断开。本特征未经认证,不能用于决定降级加密或证明设备型号。后续 `GET_INFO` 必须再次返回并核对这些能力。
|
|||
|
|
|
|||
|
|
## 5. 安全会话
|
|||
|
|
|
|||
|
|
### 5.1 固定安全配置
|
|||
|
|
|
|||
|
|
采用 ESP-IDF **v5.5.1 的 Protocomm Security 2,patch 1**:SRP6a 双向证明建立会话,业务使用 AES-256-GCM。握手消息严格使用该版本 `session.proto`、`sec2.proto` 及其依赖定义;算法、大整数编码和证明校验以同版本官方实现为互操作依据。[参考 R3~R6]
|
|||
|
|
|
|||
|
|
不得使用 Security 0,不得回退 Security 1,不得接受 Security 2 patch 0。BLE 链路配对可以作为附加保护,但不能替代此安全会话。设备不得把单设备云端认证密钥发给网页。
|
|||
|
|
|
|||
|
|
### 5.2 握手交换
|
|||
|
|
|
|||
|
|
双方建立 BLE 连接后,设备创建独立 protocomm transport session,并注册两个逻辑 endpoint:`prov-session` 和 `prov-data`。它们是固件内部路由名,不是 HTTP 路径或新增 GATT UUID。
|
|||
|
|
|
|||
|
|
| 次序 | 客户端请求 | 设备应答 | 本协议帧 SECURITY 位 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| 1 | `SessionData / SecScheme2 / S2Session_Command0` | `S2Session_Response0` | 1 |
|
|||
|
|
| 2 | `SessionData / SecScheme2 / S2Session_Command1` | `S2Session_Response1` | 1 |
|
|||
|
|
| 3 | 已加密的 `GET_INFO` JSON | 已加密的设备信息 JSON | 0 |
|
|||
|
|
|
|||
|
|
每个 protobuf 请求/应答作为一个完整 record,再按第 6 节分片。收到设备证明并验证成功前,前端不得发送 Wi-Fi 或绑定凭证。设备在握手完成前收到业务帧必须拒绝并断开。
|
|||
|
|
|
|||
|
|
### 5.3 业务加密及顺序
|
|||
|
|
|
|||
|
|
业务 record 是官方 Security 2 加密函数输出的 `ciphertext || 16-byte GCM tag`,不额外 Base64,不附加自定义 nonce。会话 nonce、密钥取得及计数器行为必须与 patch 1 一致:握手返回的 12 字节 nonce 由 8 字节会话值和 4 字节大端计数器组成;计数器从 1 开始,每完成一次加密或解密后递增。客户端请求、设备应答共用这条有序计数序列。[参考 R5、R6]
|
|||
|
|
|
|||
|
|
因此每个连接只能有一个未完成请求;设备不得主动插入加密进度推送。进度由客户端每 2 秒发送 `GET_STATUS` 获取,设备仍通过 TX Indication 返回。禁止同时轮询和发送配置,也不能跳过某条响应后继续发送下一条请求。
|
|||
|
|
|
|||
|
|
断线、解密失败、请求或应答传输超时后销毁安全会话,重新连接并握手。不得在原会话上自行重发整条密文或重置计数器。重新握手使用新随机数和新密钥;恢复业务通过原 `request_id` 和云端 `session_id` 实现。连续 5 次握手失败后设备暂停接受握手 60 秒。
|
|||
|
|
|
|||
|
|
## 6. BLE 分片格式与收发规则
|
|||
|
|
|
|||
|
|
v1 固定每次 GATT Value **不超过 20 字节**,即使链路协商了更大 ATT MTU 也不改变。这样网页不需要读取或控制 MTU。20 字节含 8 字节本协议头,单片净载荷最多 12 字节。
|
|||
|
|
|
|||
|
|
| 偏移 | 长度 | 字段 | 定义 |
|
|||
|
|
|---:|---:|---|---|
|
|||
|
|
| 0 | 1 | magic | 固定 `A5` |
|
|||
|
|
| 1 | 1 | flags | bit0 START;bit1 END;bit2 RESPONSE;bit3 SECURITY;bit4~7 必须为 0 |
|
|||
|
|
| 2 | 2 | mid | uint16 小端;请求从 1 递增,应答原值回显 |
|
|||
|
|
| 4 | 2 | total_len | uint16 小端,完整 record 长度,1~4096 |
|
|||
|
|
| 6 | 2 | offset | uint16 小端,本片载荷在 record 中的字节偏移 |
|
|||
|
|
| 8 | 1~12 | payload | record 的这一段原始字节;不得按字符切割 |
|
|||
|
|
|
|||
|
|
规则:
|
|||
|
|
|
|||
|
|
1. 每个请求占用一个 `mid`,握手也占用;同连接下不回绕,达到 65535 后完成应答再重连。
|
|||
|
|
2. 客户端 RESPONSE=0,设备 RESPONSE=1;双方相同交换使用相同 SECURITY 位。SECURITY=1 是握手 protobuf,不是“已经加密”;SECURITY=0 是安全会话中的加密业务 record。
|
|||
|
|
3. 首片 offset=0 且 START=1;末片必须满足 offset+payload_len=total_len 且 END=1。单片完整消息 START、END 同时为 1。
|
|||
|
|
4. 除末片外 payload 必须恰好 12 字节,offset 每次增加 12。相同 record 的 mid、total_len、方向及 SECURITY 位必须一致。接收端只保留一个在组装 record。
|
|||
|
|
5. 必须检查长度上限、越界、空载荷、保留位及次序。重复、缺失或错序片不得交给业务处理;结束连接并通过新会话恢复,不能猜测拼接。
|
|||
|
|
6. 完整重组以后才调用 protobuf 或安全解密。加密业务 record 至少 17 字节;解密前必须检查这一点,避免把不足 GCM tag 长度的输入交给 SDK。
|
|||
|
|
7. 每片按蓝牙栈能力使用 GATT 应答/确认推进。本协议不另外增加分片 ACK、CRC 或重传命令;完整业务由 GCM 校验完整性。传输失败按第 5.3 节重连。
|
|||
|
|
8. 完整业务 JSON 的 `mid` 必须等于帧头 mid;响应还须匹配原 request_id 和 cmd。帧头不作为身份依据。
|
|||
|
|
|
|||
|
|
仅用于分片算法校验的向量:record 是 ASCII `0123456789ABCDEFGHIJ`,共 20 字节;mid=1,方向为请求,SECURITY=1。它不是合法握手 protobuf,也不能用于发送真实配网数据。
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
第一片(20字节):a509010014000000303132333435363738394142
|
|||
|
|
第二片(16字节):a50a010014000c00434445464748494a
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
product_id=1001 的 PROTOCOL_INFO 向量:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
425001000201140000100100e9030000
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 7. 业务 JSON 公共结构
|
|||
|
|
|
|||
|
|
JSON 使用 UTF-8、无 BOM;禁止重复键、非有限数字、额外顶层值和未知字段。最大明文 4080 字节,嵌套最多 8 层。长度以编码后的字节数计。所有 UUID 使用标准小写带连字符格式;Base64URL 无填充。新增字段或修改含义必须升级协议,不能静默改变 v1。
|
|||
|
|
|
|||
|
|
请求统一结构:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"v": 1,
|
|||
|
|
"mid": 3,
|
|||
|
|
"request_id": "11111111-1111-4111-8111-111111111111",
|
|||
|
|
"cmd": "GET_INFO",
|
|||
|
|
"params": {}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
响应统一结构;成功时 error=null,失败时 result=null,错误结构固定为 code、message、retryable:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"v": 1,
|
|||
|
|
"mid": 3,
|
|||
|
|
"request_id": "11111111-1111-4111-8111-111111111111",
|
|||
|
|
"cmd": "GET_INFO",
|
|||
|
|
"ok": false,
|
|||
|
|
"result": null,
|
|||
|
|
"error": {
|
|||
|
|
"code": "SETUP_EXPIRED",
|
|||
|
|
"message": "配网窗口已结束",
|
|||
|
|
"retryable": false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
只有能完整解密并解析公共字段时才发送业务错误;无法认证或解析公共结构的消息直接断开,不能返回未加密业务错误。`message` 仅作展示,前端判断依据是稳定的 `code`。
|
|||
|
|
|
|||
|
|
## 8. 命令定义
|
|||
|
|
|
|||
|
|
### 8.1 GET_INFO:读取设备身份
|
|||
|
|
|
|||
|
|
params 固定为 `{}`。成功 result:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"factory_device_id": "KP01-000000000001",
|
|||
|
|
"product_id": 1001,
|
|||
|
|
"model": "ECHOEAR-S3",
|
|||
|
|
"firmware_version": "<固件实际版本>",
|
|||
|
|
"protocol": {"major": 1, "minor": 0, "security_version": 2, "security_patch": 1},
|
|||
|
|
"wifi_bands": ["2.4GHz"],
|
|||
|
|
"wifi_security": ["WPA2_PSK"],
|
|||
|
|
"binding_state": "UNBOUND",
|
|||
|
|
"setup_nonce": "AAECAwQFBgcICQoLDA0ODw",
|
|||
|
|
"setup_expires_in_s": 240,
|
|||
|
|
"key_version": 1,
|
|||
|
|
"setup_proof": "<64字符小写十六进制HMAC>"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`firmware_version` 为 ASCII 1~64 字节。`binding_state` 为 UNBOUND / BOUND / UNKNOWN;UNKNOWN 时后端仍须检查实际归属,不以设备自报未绑定作为授权。窗口剩余时间使用设备单调时钟。`setup_nonce` 在同一窗口重连时保持不变,重新进入窗口必须刷新。
|
|||
|
|
|
|||
|
|
setup_proof 用现有原始 32 字节设备认证密钥计算 HMAC-SHA256,结果小写 hex。签名正文是以下五行 UTF-8,LF 分隔,末尾无 LF;数字按无前导零十进制编码:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
BLESETUP1
|
|||
|
|
{factory_device_id}
|
|||
|
|
{key_version}
|
|||
|
|
{product_id}
|
|||
|
|
{setup_nonce}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这是本协议新增的域隔离证明,用于后台核验客户端拿到了设备生成的窗口信息。它不是绑定授权,也没有独立的可信时间;真正提交时必须再次校验当前窗口、设备请求认证和实体确认。网页核对二维码中的工厂编号及 product_id,不匹配时终止。
|
|||
|
|
|
|||
|
|
### 8.2 IDENTIFY:识别实体设备
|
|||
|
|
|
|||
|
|
params 为 `{"duration_ms":3000}`,允许 1000~5000。设备闪灯或显示识别动画,返回 `{"accepted":true}`。设备不输出账号或网络秘密;同连接最多每 5 秒执行一次。硬件没有可用指示能力时返回 `IDENTIFY_UNSUPPORTED`。
|
|||
|
|
|
|||
|
|
### 8.3 CONFIGURE:提交 Wi-Fi 与账号绑定凭证
|
|||
|
|
|
|||
|
|
params 示例;claim_token 是占位值,真实值由 SaaS 生成:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"session_id": "22222222-2222-4222-8222-222222222222",
|
|||
|
|
"factory_device_id": "KP01-000000000001",
|
|||
|
|
"setup_nonce": "AAECAwQFBgcICQoLDA0ODw",
|
|||
|
|
"claim_token": "<43字符Base64URL一次性令牌>",
|
|||
|
|
"wifi": {
|
|||
|
|
"ssid_b64": "TXlIb21l",
|
|||
|
|
"security": "WPA2_PSK",
|
|||
|
|
"password": "example-password",
|
|||
|
|
"hidden": false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 参数 | 必须 | 校验 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| session_id | 是 | UUID v4,与云端申请一致 |
|
|||
|
|
| factory_device_id | 是 | 必须等于本机身份 |
|
|||
|
|
| setup_nonce | 是 | 必须等于当前有效实体配网窗口 |
|
|||
|
|
| claim_token | 是 | Base64URL 解码为 32 字节;不包含平台登录密码或长期登录令牌 |
|
|||
|
|
| wifi.ssid_b64 | 是 | Base64URL,解码后 1~32 字节;此例解码为 `MyHome` |
|
|||
|
|
| wifi.security | 是 | v1 仅 `WPA2_PSK` |
|
|||
|
|
| wifi.password | 是 | 8~63 个可打印 ASCII 字节;v1 不支持 64 位十六进制原始 PSK |
|
|||
|
|
| wifi.hidden | 是 | boolean;true 表示按给定 SSID 主动连接隐藏网络 |
|
|||
|
|
|
|||
|
|
网页输入的常规 SSID 按 UTF-8 编码后再 Base64URL。设备最终向 Wi-Fi 驱动传原始 SSID 字节和明确长度,不把 SSID 当以零结尾的字符串。v1 不支持开放网络、WPA3-only、WPA-Enterprise 或 5GHz;WPA2/WPA3 混合网络只有允许 WPA2-PSK 时才可尝试。其他安全模式必须通过新版本扩展,不能当作 WPA2 强行连接。
|
|||
|
|
|
|||
|
|
设备完成校验并把待处理操作安全写入持久存储后,返回:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"accepted": true,
|
|||
|
|
"config_request_id": "33333333-3333-4333-8333-333333333333",
|
|||
|
|
"stage": "WAITING_CONFIRMATION"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`config_request_id` 必须等于 CONFIGURE 公共结构中的 request_id。此响应表示已受理,不表示联网成功。设备显示“确认本次配网与绑定”,用户在 60 秒内短按指定实体区域确认;固件生成 press_event_id(UUID v4),把该事件关联本次 session_id、config_request_id、setup_nonce 并持久化,才开始联网。先前进入窗口的长按不能复用作这次申请确认。
|
|||
|
|
|
|||
|
|
同一设备同时只运行一个配置任务。活动任务期间新 request_id 的 CONFIGURE 返回 BUSY;相同 request_id 且相同 params 返回原任务当前结果,不重复落盘或联网;相同 request_id 但 params 不同返回 REQUEST_CONFLICT。幂等摘要只包含 cmd 与 params 的确定性字段值,不包含会随重连变化的 mid;实现可用有序字段编码,不能依赖不同 JSON 空白/键序。
|
|||
|
|
|
|||
|
|
凭证在受保护存储中保存为待处理任务;Wi-Fi 取得 IP 后再提交为可用网络配置,失败保留旧网络配置。账号绑定以服务器事实为准。Wi-Fi 密码不上传 SaaS、不回显、不写业务日志;终态后清除临时 claim_token 和待处理 Wi-Fi 副本。
|
|||
|
|
|
|||
|
|
### 8.4 GET_STATUS:查询进度和恢复
|
|||
|
|
|
|||
|
|
params 为 `{"config_request_id":"33333333-3333-4333-8333-333333333333"}`。每次查询使用新的公共 request_id 与新的 mid;被查询的 config_request_id 保持不变。
|
|||
|
|
|
|||
|
|
成功 result 的固定字段如下;尚无结果时 binding_id 和 platform_device_id 为 null,未绑定 generation=0:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"config_request_id": "33333333-3333-4333-8333-333333333333",
|
|||
|
|
"session_id": "22222222-2222-4222-8222-222222222222",
|
|||
|
|
"stage": "BINDING",
|
|||
|
|
"wifi_state": "CONNECTED",
|
|||
|
|
"binding_state": "UNKNOWN",
|
|||
|
|
"local_saved": false,
|
|||
|
|
"platform_device_id": null,
|
|||
|
|
"binding_id": null,
|
|||
|
|
"binding_generation": 0,
|
|||
|
|
"error": null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
wifi_state 为 NOT_STARTED / CONNECTING / CONNECTED / FAILED;binding_state 为 UNBOUND / UNKNOWN / BOUND。`error` 为 null 或第 7 节统一错误对象。stage=FAILED 时必须带错误;stage=PENDING_RECONCILIATION 时可带暂时故障。查询到失败任务的外层 ok 仍为 true,因为查询本身成功。
|
|||
|
|
|
|||
|
|
无本地记录返回 STATUS_NOT_FOUND,此时页面应查询云端 session_id。断线重连后的状态查询须先完成新的安全握手,并仍在原窗口内;窗口已关闭时页面通过云端查询。云端提交成功但设备未保存时,页面应显示“已绑定,设备正在同步”,不能提示用户重新绑定。
|
|||
|
|
|
|||
|
|
### 8.5 FINISH:确认页面已收到结果
|
|||
|
|
|
|||
|
|
params 为 `{"config_request_id":"33333333-3333-4333-8333-333333333333"}`。仅 stage=SUCCESS 时允许,返回 `{"accepted":true,"close_after_ms":3000}`。设备必须先发送完整响应,再于 3 秒后结束 BLE 配网。
|
|||
|
|
|
|||
|
|
没有 FINISH 时,设备成功后保留 BLE 最多 30 秒用于读取结果,随后自行关闭。FINISH 只结束本地会话,不新增、取消或修改云端绑定。v1 不提供在途取消命令;网络异常不能被当作取消成功。
|
|||
|
|
|
|||
|
|
## 9. 云端绑定契约(新增)
|
|||
|
|
|
|||
|
|
以下四个路径均为拟新增接口,不与现有 `/api/v1/web/claims` 或 `/api/v1/device/claim-confirmations` 混用。成功响应固定包含 `ok=true`、`code="OK"`、request_id、server_time_ms(Unix 毫秒整数)及 data;失败响应固定包含 `ok=false`、code、message、request_id,不包含 data。这与当前平台封装一致,不能用 BLE 的 result/error 外壳解析 HTTP 响应。data 字段定义见下文,业务错误码见第 11 节。
|
|||
|
|
|
|||
|
|
### 9.1 创建绑定申请
|
|||
|
|
|
|||
|
|
`POST /api/v1/web/ble-setup-sessions`,使用当前客户登录 Cookie、`Content-Type: application/json`、`X-CSRF-Token` 及 X-Request-ID(UUID v4)。CSRF token 从已有 `GET /api/v1/web/account/session` 获取,服务端执行既有同源及账号 CSRF 校验。body:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"contract": "BLE-PROV-1.0",
|
|||
|
|
"factory_device_id": "KP01-000000000001",
|
|||
|
|
"product_id": 1001,
|
|||
|
|
"key_version": 1,
|
|||
|
|
"setup_nonce": "AAECAwQFBgcICQoLDA0ODw",
|
|||
|
|
"setup_proof": "<GET_INFO返回的64字符HMAC>",
|
|||
|
|
"profile_id": "44444444-4444-4444-8444-444444444444"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
profile_id 是平台使用者档案,必须属于当前账号有权管理的家庭/租户。账号主体由服务端从登录态取得,禁止请求方提交 user_id、owner_id、手机号或密码来决定归属。
|
|||
|
|
|
|||
|
|
服务端检查库存放行、型号、密钥版本、setup_proof、归属及档案权限。成功 HTTP 201 的 data 为:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"session_id": "22222222-2222-4222-8222-222222222222",
|
|||
|
|
"claim_token": "<CSPRNG生成的32字节Base64URL令牌>",
|
|||
|
|
"expires_in_s": 300,
|
|||
|
|
"binding_state": "PENDING"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
令牌绑定当前账号、租户/家庭、profile_id、设备、setup_nonce、预期 binding_generation;只有设备认证请求能兑换。普通业务记录保存令牌哈希。相同 X-Request-ID 的创建重试必须返回同一申请和原有效令牌:可以在单独的受保护短期存储保存加密响应至到期,不把令牌放进普通幂等缓存。所有令牌响应加 `Cache-Control: no-store`。
|
|||
|
|
|
|||
|
|
### 9.2 设备提交绑定
|
|||
|
|
|
|||
|
|
`POST /api/v1/device/ble-claims`,设备联网、完成实体确认及可信校时后调用,使用第 10 节 DEVREQ1。body:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"contract": "BLE-PROV-1.0",
|
|||
|
|
"session_id": "22222222-2222-4222-8222-222222222222",
|
|||
|
|
"claim_token": "<CONFIGURE收到的一次性令牌>",
|
|||
|
|
"setup_nonce": "AAECAwQFBgcICQoLDA0ODw",
|
|||
|
|
"config_request_id": "33333333-3333-4333-8333-333333333333",
|
|||
|
|
"press_event_id": "55555555-5555-4555-8555-555555555555"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
服务端必须在同一事务中锁定设备和申请,核验:认证设备与申请相同、令牌正确、申请未过期/撤销、窗口标识匹配、目标权限仍有效、实体确认事件对应本次申请、绑定代数未变化且不存在其他有效归属。完成后消费令牌、建立绑定、保存 generation 和申请终态,并安排独立 AI 激活任务。press_event_id 只能确认一个 session_id;这是可信固件上报的实体操作证据,不是服务器独立读取到了 GPIO。
|
|||
|
|
|
|||
|
|
HTTP 200 的 data:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"session_id": "22222222-2222-4222-8222-222222222222",
|
|||
|
|
"config_request_id": "33333333-3333-4333-8333-333333333333",
|
|||
|
|
"platform_device_id": "66666666-6666-4666-8666-666666666666",
|
|||
|
|
"binding_id": "77777777-7777-4777-8777-777777777777",
|
|||
|
|
"binding_generation": 1,
|
|||
|
|
"binding_state": "BOUND"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
相同申请、设备、配置请求和 press_event_id 的重试返回原提交结果,不再创建绑定;先检查是否为已提交的同一业务,再判断未提交申请的到期时间。若期间发生解绑、换绑或设备吊销,返回 STATE_CHANGED,不能把旧绑定当作当前有效结果。不得以“令牌已消费”为由让已成功的设备无限重绑。
|
|||
|
|
|
|||
|
|
### 9.3 页面查询与设备恢复
|
|||
|
|
|
|||
|
|
| 接口 | 认证 | 输入 | data |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| `GET /api/v1/web/ble-setup-sessions/{session_id}` | 原创建账号登录态 | 路径 session_id | session_id、status、binding_state、config_request_id、platform_device_id、binding_id、binding_generation、error |
|
|||
|
|
| `POST /api/v1/device/ble-setup-status` | DEVREQ1 | `{"session_id":"…","config_request_id":"…"}` | 同上,只允许申请对应设备查询 |
|
|||
|
|
|
|||
|
|
status 为 PENDING / COMMITTED / EXPIRED / REJECTED。PENDING 时 binding_state=UNKNOWN;COMMITTED 时 binding_state=BOUND;其余状态按实际事实返回,不能从“申请失败”推导设备不存在其他绑定。没有值的 config_request_id、设备及绑定 UUID 为 null;generation 未知/未绑定为 0,error 为 null 或统一错误对象。
|
|||
|
|
|
|||
|
|
页面每 2 秒查询,120 秒仍未确认则显示“等待设备确认”,后续由用户刷新或每 10 秒查询;不自动生成新申请。SaaS 未收到设备 claim 时不知道其本地 Wi-Fi 阶段,云端接口不得伪造“密码错误”等本地进度。
|
|||
|
|
|
|||
|
|
设备在提交前持久化 session_id、config_request_id、press_event_id 及 pending 标记。POST 响应丢失时查询同一申请;重启后先恢复原任务并查询云端,COMMITTED 则保存既有结果。只清理本地 Wi-Fi 不能撤销已经提交的账号绑定。
|
|||
|
|
|
|||
|
|
## 10. 设备 HTTPS 请求认证
|
|||
|
|
|
|||
|
|
复用当前 `internal/onboarding/crypto.go` 与 `auth.go` 的 DEVREQ1,不使用普通用户 Bearer token,也不使用名为 `Device-Certificate` 的自定义 Authorization 占位值。
|
|||
|
|
|
|||
|
|
必填请求头:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
Content-Type: application/json
|
|||
|
|
X-Device-ID: KP01-000000000001
|
|||
|
|
X-Key-Version: 1
|
|||
|
|
X-Timestamp-Ms: <Unix毫秒时间,十进制>
|
|||
|
|
X-Nonce: <16随机字节的22字符Base64URL>
|
|||
|
|
X-Request-ID: <UUID v4>
|
|||
|
|
Authorization: Device-HMAC-SHA256 <64字符小写hex签名>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
签名为 `hex_lower(HMAC_SHA256(raw_device_key_32bytes, canonical_UTF8))`。canonical 固定九行,LF 分隔且末尾无 LF:
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
DEVREQ1
|
|||
|
|
POST
|
|||
|
|
{请求路径,不带query}
|
|||
|
|
{X-Device-ID}
|
|||
|
|
{X-Key-Version}
|
|||
|
|
{X-Timestamp-Ms}
|
|||
|
|
{X-Nonce}
|
|||
|
|
{X-Request-ID}
|
|||
|
|
{实际HTTP原始body字节的SHA256小写hex}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
密钥是原始 32 字节,不是 hex 字符串的 UTF-8 字节。允许时间误差 ±120 秒;nonce 防重放至少保留 5 分钟。重试保持相同业务 request_id 和原 body 字节,但必须更新时间、nonce 并重新签名。body 上限 8 KiB,不能携带 Wi-Fi 密码。
|
|||
|
|
|
|||
|
|
设备必须验证 TLS 服务端证书和预置/受控配置的 SaaS 域名,禁止通过 BLE 任意替换目标服务器。没有可信时间时复用既有 time-challenges 的签名校时契约,随后严格验证 HTTPS;不得为解决时间问题关闭证书校验。现有校时细节以工作区 `openapi/onboarding.yaml` 与实现为准。[参考 R7]
|
|||
|
|
|
|||
|
|
## 11. 状态、错误与超时
|
|||
|
|
|
|||
|
|
### 11.1 本地任务状态
|
|||
|
|
|
|||
|
|
| stage | 含义 | 下一步 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| WAITING_CONFIRMATION | 已安全保存配置,等待本次实体确认 | 确认后 WIFI_CONNECTING;超时 FAILED |
|
|||
|
|
| WIFI_CONNECTING | 正在关联路由器及获取 IP | 成功 CLOUD_CONNECTING;确定失败 FAILED |
|
|||
|
|
| CLOUD_CONNECTING | 已取得 IP,准备可信校时和 HTTPS | BINDING 或网络故障 |
|
|||
|
|
| BINDING | 已开始向云端提交账号归属 | SAVING_RESULT、确定拒绝 FAILED,或结果不明 |
|
|||
|
|
| PENDING_RECONCILIATION | 请求可能已提交,暂时无法确认 | 查询同一 session,不能自动重绑 |
|
|||
|
|
| SAVING_RESULT | 已确认云端绑定,保存到设备 | SUCCESS 或 LOCAL_SAVE_FAILED |
|
|||
|
|
| SUCCESS | Wi-Fi 可用、云端 BOUND、本地保存完成 | FINISH / 自动关闭 |
|
|||
|
|
| FAILED | 本次确定失败;详见 error | 修复后重新申请或重试允许的阶段 |
|
|||
|
|
|
|||
|
|
LOCAL_SAVE_FAILED 用 stage=FAILED、binding_state=BOUND、local_saved=false 表示;只修复本地保存,不能解除/重复建立绑定。Wi-Fi 成功与账号成功分别展示,AI 激活失败不改写 SUCCESS。
|
|||
|
|
|
|||
|
|
### 11.2 错误码
|
|||
|
|
|
|||
|
|
| code | 含义 | 处理 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| UNSUPPORTED_VERSION | 版本不兼容 | 升级客户端/固件,不能降级安全机制 |
|
|||
|
|
| AUTH_FAILED | SRP 认证失败 | 检查设备二维码;握手层失败直接断开 |
|
|||
|
|
| INVALID_FRAME | 分片不合法 | 断开并新建安全会话 |
|
|||
|
|
| INVALID_PARAM | 字段、长度或格式不合法 | 修正参数 |
|
|||
|
|
| DEVICE_MISMATCH | 选择设备与目标不一致 | 重新识别设备 |
|
|||
|
|
| SETUP_EXPIRED | 配网窗口已关闭 | 再次实体进入配网 |
|
|||
|
|
| CONFIRM_TIMEOUT | 60 秒内未确认当前申请 | 重新提交新申请,旧申请不自动执行 |
|
|||
|
|
| BUSY | 已有进行中的配置任务 | 查询原任务 |
|
|||
|
|
| REQUEST_CONFLICT | 相同请求号对应不同参数 | 修正调用方幂等实现 |
|
|||
|
|
| STATUS_NOT_FOUND | 设备没有该本地任务 | 查云端申请,再决定恢复 |
|
|||
|
|
| IDENTIFY_UNSUPPORTED | 无可用识别指示器 | 按铭牌/二维码核对 |
|
|||
|
|
| WIFI_NOT_FOUND | 未发现目标网络 | 检查 SSID、频段和距离 |
|
|||
|
|
| WIFI_AUTH_FAILED | 驱动明确报告认证失败 | 检查密码及路由器认证方式 |
|
|||
|
|
| WIFI_CONNECT_FAILED | 原因无法精确归类 | 显示连接失败,不能一律报密码错误 |
|
|||
|
|
| WIFI_IP_TIMEOUT | 已尝试关联但未及时取得 IP | 检查 DHCP/路由器 |
|
|||
|
|
| WIFI_SECURITY_UNSUPPORTED | 非本版支持的网络类型 | 使用支持网络或升级协议 |
|
|||
|
|
| CLOUD_UNREACHABLE | 云端暂时不可达 | 按是否可能提交进入重试或对账 |
|
|||
|
|
| TOKEN_INVALID / TOKEN_EXPIRED | 一次性令牌非法或未提交即到期 | 登录页面重新申请 |
|
|||
|
|
| ALREADY_BOUND | 设备已有有效归属 | 进入独立解绑/转移流程 |
|
|||
|
|
| PERMISSION_DENIED | 当前账号/目标档案权限不满足 | 修正账号或档案选择 |
|
|||
|
|
| DEVICE_NOT_RELEASED | 设备未放行、停用或凭据无效 | 由平台处理库存/凭据 |
|
|||
|
|
| STATE_CHANGED | 提交期间权限或绑定代数变化 | 查询当前归属 |
|
|||
|
|
| LOCAL_SAVE_FAILED | 本地持久化失败 | 保留云端结果,修复保存 |
|
|||
|
|
| INTERNAL_ERROR | 内部处理失败 | 记录不含秘密的关联 ID,按状态恢复 |
|
|||
|
|
|
|||
|
|
AUTH_FAILED、INVALID_FRAME 可由客户端根据断开阶段归类,不要求设备在未认证通道发送错误 JSON。云端 HTTP 状态约定:400 参数/协议错误;401 未登录/设备认证失败;403 无权限/未放行;404 申请不存在或不可见;409 已绑定/请求冲突/状态变化;410 申请过期;429 限流;503 暂时不可用。业务 code 优先于 message。
|
|||
|
|
|
|||
|
|
### 11.3 默认时限
|
|||
|
|
|
|||
|
|
| 项目 | 默认值 | 超时行为 |
|
|||
|
|
|---|---:|---|
|
|||
|
|
| 新配置受理窗口 | 300 秒 | 停止受理;已开始任务继续 |
|
|||
|
|
| BLE 连接后未完成握手 | 90 秒 | 断开并销毁会话 |
|
|||
|
|
| 相邻分片无进展 | 10 秒 | 断开;不能继续使用原安全会话 |
|
|||
|
|
| 一条 record 传输 | 45 秒 | 断开 |
|
|||
|
|
| 完整请求后设备开始应答 | 5 秒 | 断开后查询/恢复;配置后台异步执行 |
|
|||
|
|
| 未挂起请求的连接空闲 | 90 秒 | 断开;不取消任务 |
|
|||
|
|
| 配置后的实体确认 | 60 秒 | 本地确认超时,不提交云端 |
|
|||
|
|
| Wi-Fi 关联 / DHCP | 45 秒 / 15 秒 | 确定失败,保留旧网络 |
|
|||
|
|
| 云端请求单次 | 15 秒 | 按同一申请重试/查询 |
|
|||
|
|
| 云端自动重试总窗口 | 90 秒 | 不确定则转 PENDING_RECONCILIATION |
|
|||
|
|
| 令牌有效期 | 服务端签发起 300 秒 | 不接受新的绑定提交;已提交结果仍可查询 |
|
|||
|
|
| 成功后等待 FINISH | 30 秒 | 关闭 BLE,保留绑定与网络 |
|
|||
|
|
|
|||
|
|
云端重试退避为 1、2、4、8、16 秒,之后最多每 16 秒一次,可增加不超过 20% 的随机抖动;确定的权限、设备归属或参数错误不自动重试。PENDING_RECONCILIATION 后设备在网络可用时每 30 秒查询原申请,得到 COMMITTED 或确定终态后停止。物理窗口结束不阻止设备恢复已有申请,但不能据此新建绑定。
|
|||
|
|
|
|||
|
|
## 12. 实现边界与现有系统衔接
|
|||
|
|
|
|||
|
|
| 端 | 需要实现的内容 |
|
|||
|
|
|---|---|
|
|||
|
|
| 前端 | 登录态复用、浏览器能力检查、扫码、GATT 连接/订阅、分片、Security 2 客户端、命令串行队列、状态与断线恢复 |
|
|||
|
|
| 设备 | BLE 广播和 GATT 服务、分片重组、自定义 protocomm transport 适配、实体确认、Wi-Fi 后台任务、DEVREQ1、受保护持久化 |
|
|||
|
|
| 后端 | 四个新增接口、BLESETUP1 校验、短期令牌、事务绑定与幂等、权限复查、状态恢复、审计 |
|
|||
|
|
| 产线 | 库存身份映射、独立 SRP 凭据/二维码、云端单设备密钥烧录、放行与秘密保护 |
|
|||
|
|
|
|||
|
|
设备 RX 回调只做有界校验、复制和投递工作队列,不能在蓝牙回调中等待 Wi-Fi/DHCP/HTTPS。完整 SECURITY record 交给 `prov-session`,完整业务密文交给 `prov-data`;通过 `protocomm_req_handle` 处理,由 protocomm 完成业务解密、handler 调用与响应加密,外层再分片返回,不能额外加密第二遍。[参考 R3]
|
|||
|
|
|
|||
|
|
本版使用**自定义 GATT 分片 transport + 官方 protocomm 安全层**,不是原生 `wifi_prov_mgr` 的 BLE 线协议。不能直接拿 ESP Provisioning 通用 App 与本文 UUID 通信;前端及调试工具需要本协议适配。官方 Security 2 Python 客户端可作为握手互操作对照,但其默认 transport 不理解本文帧头。
|
|||
|
|
|
|||
|
|
首次落地目标为支持 Web Bluetooth 的桌面/Android 浏览器。页面必须 HTTPS 且发现设备由用户点击触发;浏览器负责设备选择。Safari/iPhone 不能直接承诺网页蓝牙可用,需要小程序或原生客户端适配同一 BLE 协议,实际支持矩阵以验收为准。[参考 R2、R8]
|
|||
|
|
|
|||
|
|
当前平台已存在 ONBOARDING-1.1 的 DEVREQ1 及实体确认绑定流程;旧设计文档的“接口未实现”不能作为当前状态依据。本协议保留其原始设备密钥格式与当前归属约束,但新增 BLE 入口和令牌交换,不修改旧客户端字段或允许绕过实体确认。应新增明确的接入契约配置;当前工厂身份查询还限制 enrollment_mode,适配时必须显式允许本契约并保留设备/租户门禁,不能简单放开所有设备。
|
|||
|
|
|
|||
|
|
现有测试板的 `echoear-…` 硬件标识与 ONBOARDING 使用的 `KP01-…` 工厂身份不是同一个字段。量产/迁移时建立明确映射;不能由前端截断 MAC 临时制造已认证身份。当前 sdkconfig 中 `CONFIG_BT_ENABLED` 未开启,需要固件开发与内存/射频共存验证,这不是仅修改网页即可实现的功能。
|
|||
|
|
|
|||
|
|
## 13. 联调验收
|
|||
|
|
|
|||
|
|
以下是实施后的验收要求,不代表已经通过。正式发布前必须有浏览器和实机证据。
|
|||
|
|
|
|||
|
|
| 场景 | 必须观察到的结果 |
|
|||
|
|
|---|---|
|
|||
|
|
| 正常首次配网 | 识别正确设备,实体确认后联网;云端只有一条有效绑定;本地保存后 SUCCESS |
|
|||
|
|
| 附近两台同型号 | 按短编号/二维码选择,认证失败时绝不发送 Wi-Fi;IDENTIFY 指示所选设备 |
|
|||
|
|
| 官方安全互操作 | 前端与 v5.5.1 Security 2 patch 1 双向握手、加解密一致,错误配网密码失败 |
|
|||
|
|
| 降级及篡改 | Security 0/1、patch 0、篡改密文、非法分片全部拒绝,无配置副作用 |
|
|||
|
|
| 默认 MTU | 20 字节 Value 下完成最长合法配置;不能依赖 MTU=247 |
|
|||
|
|
| 分片边界 | 第 6 节向量重组正确;测试 1、12、13、4096 字节 record;超长、重复、越界拒绝 |
|
|||
|
|
| 串行加密 | GET_STATUS 不与 CONFIGURE 并发;超时必须重握手,不能重置 nonce 续传 |
|
|||
|
|
| Wi-Fi 错误 | 认证失败、无网络、DHCP 超时分别处理;不删除原可用配置 |
|
|||
|
|
| 重复配置 | 相同 request_id 和参数只执行一次;不同参数返回 REQUEST_CONFLICT |
|
|||
|
|
| BLE 中途断开 | 云端任务不被取消;新安全会话或云端页面可以恢复原状态 |
|
|||
|
|
| 请求已提交但回包丢失 | 查询原 session 得到原绑定,不产生第二条绑定 |
|
|||
|
|
| 绑定后断电/落盘失败 | 云端 BOUND 保留,设备恢复保存,页面不要求重新绑定 |
|
|||
|
|
| 令牌过期/跨设备 | 未提交的过期申请被拒绝,其他设备不能兑换令牌 |
|
|||
|
|
| 账号越权/并发抢绑 | 无权限 profile 被拒绝;并发申请最多一个提交成功 |
|
|||
|
|
| 未实体确认 | 不能联网后自动替用户提交新绑定;确认事件不能复用于另一申请 |
|
|||
|
|
| 已绑定/恢复出厂 | BLE 首次绑定入口不覆盖已有云端归属 |
|
|||
|
|
| 秘密检查 | 广播、日志、云端配网 body、浏览器持久存储中无 Wi-Fi 密码和配网秘密 |
|
|||
|
|
| AI 激活失败 | 页面保持“已绑定”,AI 激活单独重试 |
|
|||
|
|
|
|||
|
|
## 14. 依据与规范性引用
|
|||
|
|
|
|||
|
|
- R1:[Bluetooth SIG:GATT 规范](https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-61/out/en/host/generic-attribute-profile--gatt-.html)。GATT 读写、特征与 indication 的标准依据;本文业务 UUID、分片和 JSON 是自定义内容。
|
|||
|
|
- R2:[Chrome:Web Bluetooth 开发文档](https://developer.chrome.com/docs/capabilities/bluetooth)。浏览器访问约束及 GATT 调用方式。
|
|||
|
|
- R3:[ESP-IDF v5.5.1 / ESP32-S3:Protocomm](https://docs.espressif.com/projects/esp-idf/en/v5.5.1/esp32s3/api-reference/provisioning/protocomm.html)。安全组件与 endpoint/transport 集成方式。
|
|||
|
|
- R4:[session.proto](https://github.com/espressif/esp-idf/blob/v5.5.1/components/protocomm/proto/session.proto)、[sec2.proto](https://github.com/espressif/esp-idf/blob/v5.5.1/components/protocomm/proto/sec2.proto)、[constants.proto](https://github.com/espressif/esp-idf/blob/v5.5.1/components/protocomm/proto/constants.proto)。握手 wire schema 以该固定 tag 及导入依赖为准。
|
|||
|
|
- R5:[官方 Security 2 客户端](https://github.com/espressif/esp-idf/blob/v5.5.1/tools/esp_prov/security/security2.py)、[SRP6a 实现](https://github.com/espressif/esp-idf/blob/v5.5.1/tools/esp_prov/security/srp6a.py)。客户端握手、凭据生成与密码学编码的互操作参考。
|
|||
|
|
- R6:[官方 Security 2 固件实现](https://github.com/espressif/esp-idf/blob/v5.5.1/components/protocomm/src/security/security2.c)。patch 1、nonce 顺序、GCM 输出与安全会话行为。
|
|||
|
|
- R7:本地现有实现:[DEVREQ1](../internal/onboarding/crypto.go)、[设备认证](../internal/onboarding/auth.go)、[当前路由](../internal/onboarding/http.go)、[Onboarding API](../openapi/onboarding.yaml)、[现有认证测试向量](../test/fixtures/onboarding-devreq1.json)。这些已有契约不能与新增 BLE 接口混为一谈。
|
|||
|
|
- R8:[WebKit:未实现的相关能力说明](https://webkit.org/tracking-prevention/)。网页客户端覆盖范围的限制依据。
|
|||
|
|
- 设备基线:[EchoEar 固件说明](../firmware/echoear-platform-agent/README.md)、[当前 sdkconfig](../firmware/echoear-platform-agent/sdkconfig)。工作区读取日期为 2026-09-13,未将本协议部署到设备或服务器。
|
|||
|
|
|
|||
|
|
修订记录:1.0 草案确定 BLE GATT transport、Security 2 patch 1、JSON 配网消息、一次性账号绑定令牌及失败恢复约定;实施并完成互操作验收后再冻结为发布版本。
|