协议规范
bleRPC 在 BLE GATT 之上使用分层协议栈。命令层使用 RPC 元数据包装 Protocol Buffers 载荷,可选的加密层加密序列化的命令,容器层将它们分片为 MTU 大小的数据包进行 BLE 传输。
graph TD
A["Application"] --> B["Command Layer<br/>Protobuf encode/decode + command metadata"]
B --> E["Encryption Layer (optional)<br/>AES-128-GCM encrypt/decrypt"]
E --> C["Container Layer<br/>MTU-based split/reassemble + sequencing"]
C --> D["BLE GATT<br/>(single characteristic)"]
BLE 传输
所有通信使用单个 GATT Characteristic:
- 请求:Central 通过 Write Without Response 写入
- 响应:Peripheral 通过 Notify 发送
MTU 通过 BLE ATT MTU Exchange 过程自动协商。应用程序使用协商后的 MTU(减去 3 字节 ATT 开销)来确定最大容器载荷大小
默认超时为 100ms,可通过超时控制容器配置
容器层
容器层将大载荷分割为 MTU 大小的数据包,并在接收端重新组装。每个容器携带一个事务 ID 和序列号,用于跟踪和排序。
容器格式
所有多字节字段为小端序。
首个容器 (type=0b00)
事务中的第一个容器包含总载荷长度:
| 字段 | 位 | 描述 |
|---|---|---|
transaction_id | 8 | 此请求/响应对的唯一 ID。每次事务重置。 |
sequence_number | 8 | 每发送一个容器递增。每次事务重置。 |
type | 2 | 0b00 = 首个 |
control_cmd | 4 | 0x0(数据容器不使用) |
reserved | 2 | 零填充 |
total_length | 16 | 所有容器的总载荷大小(字节,小端序) |
payload_len | 8 | 此容器中的载荷大小(字节) |
payload | 可变 | 命令数据片段 |
头部大小:6 字节(transaction_id + sequence_number + flags + total_length + payload_len)
后续容器 (type=0b01)
续传容器省略 total_length:
头部大小:4 字节(无 total_length 字段)
字节 2 位域详情
每个容器的第三个字节编码类型、控制命令和保留位:
type:00= 首个,01= 后续,11= 控制control_cmd:0x0–0x6(仅在 type=11时有效)reserved:零填充
容器分割示例
500 字节命令载荷,MTU=247(ATT 开销后可用 244 字节):
| 容器 | 类型 | 头部 | 载荷 | 总计 |
|---|---|---|---|---|
| 1 | FIRST | 6 字节 | 238 字节 | 244 字节 |
| 2 | SUBSEQUENT | 4 字节 | 240 字节 | 244 字节 |
| 3 | SUBSEQUENT | 4 字节 | 22 字节 | 26 字节 |
总载荷:500 字节(238 + 240 + 22)
最大载荷
sequence_number 为 8 位,将单个事务限制为约 255 个容器。在典型 MTU 为 247(每个后续容器 240 字节)的情况下,每个事务的实际最大载荷约为 60 KB。对于更大的传输,请使用多次请求/响应循环。
控制容器
控制容器(type=0b11)携带协议级命令而非应用数据。
超时共享 (control_cmd=0x1)
允许 peripheral 向 central 通告其处理超时时间。
请求(Central → Peripheral)
| 字段 | 值 |
|---|---|
| type | 0b11(控制) |
| control_cmd | 0x1 |
| payload_len | 0x00 |
响应(Peripheral → Central)
| 字段 | 值 |
|---|---|
| type | 0b11(控制) |
| control_cmd | 0x1 |
| payload_len | 0x02 |
| payload | timeout_ms(16 位小端序,毫秒) |
能力共享 (control_cmd=0x4)
允许 peripheral 向 central 通告其缓冲区约束和功能标志。
请求(Central → Peripheral)
| 字段 | 值 |
|---|---|
| type | 0b11(控制) |
| control_cmd | 0x4 |
| payload_len | 0x06 |
| payload[0:2] | max_request_payload_size(16 位小端序,通常为 0) |
| payload[2:4] | max_response_payload_size(16 位小端序,通常为 0) |
| payload[4:6] | flags(16 位小端序,通常为 0) |
响应(Peripheral → Central)
| 字段 | 值 |
|---|---|
| type | 0b11(控制) |
| control_cmd | 0x4 |
| payload_len | 0x06 |
| payload[0:2] | max_request_payload_size(16 位小端序) |
| payload[2:4] | max_response_payload_size(16 位小端序) |
| payload[4:6] | flags(16 位小端序)— 位 0:支持加密 |
当 flags 不存在时(4 字节载荷,用于向后兼容),没有可选功能可用
流结束 (control_cmd=0x2, 0x3)
表示流式序列结束。任一端都可以终止流。
| control_cmd | 方向 | 描述 |
|---|---|---|
0x2 | Central → Peripheral | Central 结束上传流 |
0x3 | Peripheral → Central | Peripheral 结束下载流 |
两者均为 payload_len=0x00(无载荷)
错误通知 (control_cmd=0x5)
当发生错误时(例如响应超过缓冲区限制)由 peripheral 发送。
| 字段 | 值 |
|---|---|
| type | 0b11(控制) |
| control_cmd | 0x5 |
| payload_len | 0x01 |
| payload[0] | 错误代码 |
错误代码:
0x01— RESPONSE_TOO_LARGE:响应载荷超过max_response_payload_size
密钥交换 (control_cmd=0x6)
携带密钥交换握手载荷,用于建立加密会话。当 peripheral 通过能力 flags 字段广播加密支持时使用。
| 字段 | 值 |
|---|---|
| type | 0b11(控制) |
| control_cmd | 0x6 |
| payload_len | 可变 |
| payload | 密钥交换步骤数据(参见加密) |
交换 4 个容器(步骤 1–4)完成握手。完成后,所有后续数据容器携带加密载荷
加密数据容器
当加密启用时,数据容器(FIRST / SUBSEQUENT)的载荷替换为序列化命令的加密形式:
| 字段 | 大小 | 描述 |
|---|---|---|
counter | 4 字节 | 单调递增计数器(小端序)。用作 AES-GCM nonce 和重放检测。 |
ciphertext | N 字节 | AES-128-GCM 加密的序列化命令 |
tag | 16 字节 | AES-GCM 认证标签 |
总开销:20 字节(4 计数器 + 16 标签)。密文随后按常规方式分割到容器中。
命令层
命令层使用 RPC 元数据包装 Protocol Buffers 编码的数据。命令通过名称(ASCII 字符串)标识,实现灵活、人类可读的调度。
命令格式
| 字段 | 位 | 描述 |
|---|---|---|
type | 1 | 0 = 请求,1 = 响应 |
reserved | 7 | 零填充 |
cmd_name_len | 8 | 命令名称的字节长度 |
cmd_name | 可变 | ASCII 命令名称(例如 echo、flash_read) |
data_len | 16 | protobuf 数据的长度(字节,小端序) |
data | 可变 | Protocol Buffers 编码的载荷 |
命令发现
命令从 .proto 文件定义中自动生成。匹配的 *Request / *Response 消息对会生成一个命令,其名称是前缀转换为 snake_case 的形式:
| 消息对 | 命令名称 |
|---|---|
EchoRequest + EchoResponse | echo |
FlashReadRequest + FlashReadResponse | flash_read |
DataWriteRequest + DataWriteResponse | data_write |
请求/响应流程
完整的 RPC 调用涉及命令层编码、容器层分割、BLE 传输,以及接收端的反向操作:
sequenceDiagram
participant C as Central
participant P as Peripheral
Note over C: 1. Protobuf encode request
Note over C: 2. Wrap in command header
Note over C: 3. Split into containers
C->>P: Write Without Response (FIRST)
C->>P: Write Without Response (SUBSEQUENT...)
Note over P: 4. Reassemble containers
Note over P: 5. Decode command + Protobuf
Note over P: 6. Execute handler
Note over P: 7. Encode response + split
P->>C: Notify (FIRST)
P->>C: Notify (SUBSEQUENT...)
Note over C: 8. Reassemble + decode response
流式传输
bleRPC 支持两种流式传输模式,超越简单的请求/响应:
Peripheral → Central 流(服务端流式)
一个请求触发多个响应。流以 STREAM_END_P2C 控制容器结束。
sequenceDiagram
participant C as Central
participant P as Peripheral
C->>P: Request
P->>C: Response 1 (Notify)
P->>C: Response 2 (Notify)
P->>C: Response 3 (Notify)
P-->>C: STREAM_END_P2C (control)
Central → Peripheral 流(客户端流式)
发送多个请求,然后发送 STREAM_END_C2P 控制容器。Peripheral 返回最终消息。
sequenceDiagram
participant C as Central
participant P as Peripheral
C->>P: Request 1
C->>P: Request 2
C->>P: Request 3
C-->>P: STREAM_END_C2P (control)
P->>C: Final Response (Notify)
连接建立序列
当 central 连接到 peripheral 时,会执行以下初始化步骤:
sequenceDiagram
participant C as Central
participant P as Peripheral
C->>P: BLE Connect
C->>P: MTU Exchange
C->>P: Discover Services
C->>P: Enable Notifications
C->>P: Timeout Request (ctrl)
P->>C: Timeout Response (e.g. 100ms)
C->>P: Capability Request (ctrl)
P->>C: Capability Response (max_req, max_resp, flags)
opt Encryption (flags & 0x01)
C->>P: Key Exchange Step 1 (ctrl 0x6)
P->>C: Key Exchange Step 2 (ctrl 0x6)
C->>P: Key Exchange Step 3 (ctrl 0x6)
P->>C: Key Exchange Step 4 (ctrl 0x6)
Note over C,P: AES-128-GCM session established
end
Note over C,P: Ready for RPC calls