Just Works 配对
使用技术:SLE 配对、绑定、加密、密钥管理
前置阅读:Hello Notify
学习目标
- 理解 SLE 配对(Pairing)的完整流程及其在安全通信中的作用
- 理解 Just Works 配对模型的原理和适用场景
- 掌握配对 API 的回调链:发起配对 →
pair_complete_cb→ MTU 交换 → 服务发现 - 理解绑定(Bonding)与配对的区别以及 NV 持久化的意义
- 能够诊断常见的配对失败原因及其错误码
规格与功能
本案例演示完整的 Just Works 配对流程——从连接建立到加密通信建立的必经之路。
| 规格项 | Server 端 | Client 端 |
|---|---|---|
| 配对发起方 | — | Client(连接成功后主动发起) |
| 配对模式 | Just Works(无需用户交互) | Just Works |
| 加密算法 | EA1/EA2 (对称加密) | EA1/EA2 |
| 密钥派生 | HA1/HA2 | HA1/HA2 |
| 绑定 | 配对成功后存储密钥到 NV | 配对成功后存储密钥到 NV |
| 完整性校验 | 启用 | 启用 |
程序运行流程:
- Server 和 Client 建立连接
- Client 在
connect_state_changed_cb中检测pair_state == SLE_PAIR_NONE - Client 调用
sle_pair_remote_device()发起配对 - 双方协议栈自动完成密钥交换和加密协商
pair_complete_cb被触发——配对成功- Client 发起 MTU 交换 → Server 响应 →
exchange_info_cb触发 - Client 发起服务发现 → 获取 Property 句柄 → 可以开始数据通信
hello-connect 告诉了你怎么连接,但连接后不配对就无法交换 MTU、无法发现服务、无法传输数据。配对是数据通信的前置条件。
基本概念
配对 vs 绑定
配对和绑定是两个不同但相关的概念:
| 配对(Pairing) | 绑定(Bonding) | |
|---|---|---|
| 含义 | 交换加密密钥,建立加密链路 | 将配对生成的密钥存储到 NV |
| 有效期 | 本次连接有效,断开后密钥丢失 | 永久有效,重启保持 |
| 目的 | 保护本次数据传输 | 下次重连时可快速恢复加密,无需重新配对 |
| API | sle_pair_remote_device() |
配对时配置 is_bond = SLE_PAIR_NEED_BOND |
sequenceDiagram
participant C as Client
participant S as Server
participant NV as NV Flash
Note over C,S: 已连接,未配对
C->>S: sle_pair_remote_device()
Note over C,S: 密钥交换(DH 或 ECDH)
Note over C,S: 加密链路建立
Note over C: pair_complete_cb
Note over S: pair_complete_cb
C->>NV: 存储密钥 (bonding)
S->>NV: 存储密钥 (bonding)
Note over C,S: 加密通信就绪
C--xS: 断开连接
Note over C,S: 密钥仍保存在 NV 中
C->>S: 重新连接
Note over C,S: 从 NV 读取密钥,<br/>自动恢复加密,无需重新配对
Just Works 与其他配对方式的对比
| 配对方式 | 用户交互 | 安全性 | 适用场景 |
|---|---|---|---|
| Just Works | 无(自动完成) | 基本——无法防御 MITM | 无屏幕/无输入能力的嵌入式设备 |
| Passkey Entry | 输入 6 位数字 | 中——需要物理接触 | 带键盘或显示屏的设备 |
| Numeric Comparison | 确认 6 位数字 | 高——双方确认匹配 | 双方都有显示屏的设备 |
| Out of Band (OOB) | 无(通过其他通道交换) | 高——带外通道保证 | NFC/BLE 辅助配对 |
WS63 目前主要支持 Just Works 模式。这是嵌入式 IoT 设备最常见的配对方式——没有屏幕也没有键盘,只能信任第一次连接。
配对回调解耦链
配对的完成是后续所有数据操作的前提。下面的回调链是标准流程:
sequenceDiagram
participant C as Client
participant S as Server
C->>S: sle_pair_remote_device()
Note over C,S: 协议栈自动完成密钥交换
S-->>C: pair_complete_cb
Note right of S: 配对完成 → 设置 MTU
C-->>C: pair_complete_cb
Note left of C: 配对完成 → 发起 MTU 交换
C->>S: ssapc_exchange_info_req()
S-->>C: exchange_info_cb
Note left of C: MTU 交换完成 → 发起服务发现
C->>S: ssapc_find_structure()
S-->>C: find_structure_cb
S-->>C: find_property_cb
Note left of C: 找到所有属性
S-->>C: find_structure_cmp_cb
Note left of C: 服务发现完成 → 可以读写/收通知
每个回调是下一个操作的触发条件。不要在
pair_complete_cb之前尝试交换 MTU——协议栈会拒绝。
涉及 API
| API | 谁调用 | 用途 |
|---|---|---|
sle_pair_remote_device() |
Client | 发起配对请求 |
sle_remove_paired_remote_device() |
任一方 | 删除指定设备的配对信息 |
sle_remove_all_pairs() |
任一方 | 删除所有配对信息 |
sle_get_paired_devices_num() |
任一方 | 查询已配对设备数量 |
sle_get_paired_devices() |
任一方 | 获取已配对设备地址列表 |
sle_get_pair_state() |
任一方 | 查询对端设备的配对状态 |
ssapc_exchange_info_req() |
Client | 发起 MTU 交换(配对完成后调用) |
ssapc_find_structure() |
Client | 发起服务发现(MTU 交换完成后调用) |
sle_connection_register_callbacks() |
双方 | 注册连接回调(含 pair_complete_cb、auth_complete_cb) |
案例说明
做什么
实现完整的配对-绑定-MTU交换-服务发现回调链,这是所有 SLE 数据通信功能的基础流程。
配对状态机
stateDiagram-v2
[*] --> IDLE: 上电初始化
IDLE --> ANNOUNCING: 启动广播/扫描
ANNOUNCING --> CONNECTED: 连接建立
CONNECTED --> PAIRING: sle_pair_remote_device()
PAIRING --> PAIRED: pair_complete_cb
PAIRING --> PAIR_FAILED: 配对失败
PAIRED --> ENCRYPTED: 加密链路建立
ENCRYPTED --> DATA_READY: MTU + 服务发现完成
PAIR_FAILED --> CONNECTED: 重试配对
DATA_READY --> DISCONNECTED: 连接断开
DISCONNECTED --> ANNOUNCING: 恢复广播/扫描
关键配置
配对与绑定配置
// 注册回调时配置配对相关回调
static sle_connection_callbacks_t g_conn_callbacks = {
.connect_state_changed_cb = conn_state_cb,
.pair_complete_cb = pair_complete_cb,
.auth_complete_cb = auth_complete_cb,
.pair_remove_cb = pair_remove_cb,
// ... 其他回调
};
认证信息事件
配对完成后的认证信息事件中包含加密算法和绑定指示:
typedef struct {
uint8_t link_key[SLE_LINK_KEY_LEN]; // 16 字节链路密钥
uint8_t crypto_algo; // 加密算法: AC1/AC2/EA1/EA2
uint8_t key_deriv_algo; // 密钥派生: HA1/HA2
uint8_t integr_chk_ind; // 完整性校验指示
uint8_t is_bond; // 是否绑定: SLE_PAIR_NO_BOND / SLE_PAIR_NEED_BOND
} sle_auth_info_evt_t;
代码详解
Client 端:连接后发起配对
static void conn_state_cb(uint16_t conn_id,
const sle_addr_t *addr,
sle_acb_state_t conn_state,
sle_pair_state_t pair_state,
sle_disc_reason_t disc_reason)
{
if (conn_state == SLE_ACB_STATE_CONNECTED) {
g_conn_id = conn_id;
memcpy(&g_remote_addr, addr, sizeof(sle_addr_t));
// 检查配对状态
if (pair_state == SLE_PAIR_NONE) {
printf("[client] connected, starting pairing...\n");
errcode_t ret = sle_pair_remote_device(addr);
if (ret != ERRCODE_SUCC) {
printf("[client] pair request failed: %d\n", ret);
}
}
}
else if (conn_state == SLE_ACB_STATE_DISCONNECTED) {
printf("[client] disconnected, reason=%d\n", disc_reason);
if (disc_reason == SLE_DISC_REASON_PAIR_FAILED ||
disc_reason == SLE_DISC_REASON_AUTH_FAILED) {
// 配对/认证失败:清理无效的配对信息
sle_remove_paired_remote_device(&g_remote_addr);
}
g_conn_id = 0;
sle_start_seek(); // 恢复扫描
}
}
配对完成回调
static void pair_complete_cb(uint16_t conn_id,
const sle_addr_t *addr,
errcode_t status)
{
if (status != ERRCODE_SUCC) {
printf("[client] pairing failed: %d\n", status);
// 可选:重试或断开
return;
}
printf("[client] paired successfully!\n");
// 配对成功 → 发起 MTU 交换
ssapc_exchange_info_req(g_client_handle, conn_id);
}
认证完成回调
static void auth_complete_cb(uint16_t conn_id,
const sle_auth_info_evt_t *info)
{
printf("[client] auth complete:\n");
printf(" crypto_algo=%d, key_deriv=%d\n",
info->crypto_algo, info->key_deriv_algo);
printf(" integrity_check=%d, bond=%d\n",
info->integr_chk_ind, info->is_bond);
if (info->is_bond == SLE_PAIR_NEED_BOND) {
printf("[client] keys saved to NV for future reconnection\n");
}
}
MTU 交换 → 服务发现回调链
// MTU 交换完成
static void exchange_info_cb(uint16_t conn_id, errcode_t status)
{
if (status != ERRCODE_SUCC) {
printf("[client] MTU exchange failed: %d\n", status);
return;
}
printf("[client] MTU exchange done\n");
// MTU 完成后 → 发起服务发现
ssapc_find_structure(g_client_handle, conn_id);
}
// 服务发现完成
static void find_structure_cmp_cb(uint16_t conn_id, errcode_t status)
{
if (status == ERRCODE_SUCC) {
printf("[client] service discovery complete, data ready!\n");
// 此时可以开始读写数据、接收通知
g_data_ready = true;
}
}
常见配对失败原因
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
pair_complete_cb 返回非 0 |
加密算法不匹配、密钥交换超时 | 检查双方协议栈版本是否一致 |
sle_pair_remote_device() 返回 BUSY |
前一次配对还在进行中 | 等待前一次完成再发起 |
| 配对成功但无法服务发现 | MTU 交换未完成 | 检查 pair_complete_cb 中是否调用了 exchange_info_req |
| 重连后需要重新配对 | 未启用绑定(is_bond=0) |
配对时设置绑定标志 |
| 重连后认证失败 | NV 中密钥已过期或被覆盖 | 调用 sle_remove_paired_remote_device() 后重新配对 |
在产品代码中,配对失败后建议等 1~2 秒再重试,不要立即重试——连续快速重试会加剧协议栈负担,反而降低成功率。