【Bluetooth-SIG】【CoreV6.2】【Vol3 Part F】【二】【Protocol Overview——ATT 协议模型】
Core_v6.2 Vol 3 Part F 学习笔记(二):Protocol Overview——ATT 协议模型
0. 本篇说明
本文继续学习 Bluetooth Core Specification v6.2:
Vol 3, Part F:Attribute Protocol,简称 ATT
本篇对应章节:
2 Protocol overview
上一篇我们已经知道,ATT 是用来发现、读取、写入对端设备 attributes 的协议。
这一篇开始进入 ATT 的协议模型,也就是搞清楚:
-
ATT 有哪些角色;
-
Server 是什么;
-
Client 是什么;
-
Attribute 到底由哪些东西组成;
-
Attribute type、handle、permissions 分别干什么;
-
为什么一个设备可以同时是 Client 和 Server;
-
为什么一个设备上只能有一个 ATT Server 实例;
-
ATT bearer 是什么;
-
LE 和 BR/EDR 上 ATT bearer 有什么差异。
一句话概括本篇:
ATT 是一个 Client/Server 模型的属性访问协议,Server 暴露 Attribute Database,Client 通过 ATT bearer 访问这些 attributes。
更接地气一点:
Server 像“货架管理员”,货架上摆着一堆带编号的商品;
Client 像“顾客”,通过商品编号去查、读、写;
ATT bearer 像“柜台窗口”,请求和响应都从这个窗口走。
1. Protocol Overview 原文结构拆解
这一节虽然标题叫 “Protocol overview”,但它其实浓缩了 ATT 后面所有章节的核心思想。
可以拆成 8 个知识点:
| 编号 | 知识点 | 说明 |
|---|---|---|
| 1 | ATT 定义两种角色 | Server role 和 Client role |
| 2 | Server 暴露 attributes | Client 通过 ATT 访问这些 attributes |
| 3 | Attribute 有三个关联属性 | type、handle、permissions |
| 4 | Attribute type 说明属性代表什么 | 通常由 UUID 表示 |
| 5 | Attribute handle 唯一标识 attribute | Client 靠 handle 读、写、接收通知 |
| 6 | Permissions 用于限制访问 | 但不能通过 ATT 直接访问 permissions |
| 7 | 设备可以同时是 Client 和 Server | 同一设备可以双角色并发 |
| 8 | ATT 请求通过 ATT bearer 发送 | 一个 bearer 对应一个 L2CAP channel |
后面所有 PDU,本质上都围绕这几个点展开。
2. ATT 定义两种角色:Server 和 Client
2.1 原文含义
Protocol overview 开头说明:
Attribute Protocol 定义两种角色:server role 和 client role。
这里要注意,ATT 的 Client/Server 不是物理设备类型,也不是 Central/Peripheral 的同义词。
2.2 逐行解释
| 原文含义 | 中文解释 | 工程理解 |
|---|---|---|
| ATT defines two roles | ATT 定义两种角色 | 这是 ATT 自己的逻辑角色 |
| a server role | Server 角色 | 保存 attributes,响应访问 |
| a client role | Client 角色 | 访问对端 attributes |
| server exposes attributes | Server 暴露一组 attributes | Server 有 Attribute Database |
| client accesses attributes | Client 通过 ATT 访问这些 attributes | Client 发 request / command |
2.3 Server 是什么?
ATT Server 是保存 attributes 的一方。
Server 的典型职责:
| 职责 | 示例 |
|---|---|
| 保存 Attribute Database | GATT DB |
| 接收 Client 请求 | ATT_READ_REQ、ATT_WRITE_REQ |
| 返回响应 | ATT_READ_RSP、ATT_WRITE_RSP |
| 检查 handle 是否有效 | 无效则返回 Invalid Handle |
| 检查读写权限 | 不允许读写则返回错误 |
| 检查安全条件 | 是否加密、认证、授权 |
| 主动通知 Client | ATT_HANDLE_VALUE_NTF |
| 主动指示 Client | ATT_HANDLE_VALUE_IND |
2.4 Client 是什么?
ATT Client 是访问 Server attributes 的一方。
Client 的典型职责:
| 职责 | 示例 |
|---|---|
| 发现 attributes | Find Information、Read By Type |
| 读取 attribute value | Read Request |
| 写入 attribute value | Write Request、Write Command |
| 接收 notification | Handle Value Notification |
| 接收 indication | Handle Value Indication |
| 发送 confirmation | Handle Value Confirmation |
| 管理事务状态 | Request 发出后等待 Response |
2.5 Client/Server 与 Central/Peripheral 的区别
很多初学者最容易把这几个角色搅成一锅粥。
| 概念 | 所属层级 | 说明 |
|---|---|---|
| Central | GAP / Link 层角色 | 通常发起 BLE 连接 |
| Peripheral | GAP / Link 层角色 | 通常广播并被连接 |
| ATT Client | ATT 层角色 | 访问对端 attribute |
| ATT Server | ATT 层角色 | 暴露 attribute database |
| GATT Client | GATT 层角色 | 发起 GATT procedures |
| GATT Server | GATT 层角色 | 提供 GATT database |
它们不是一回事。
典型情况下,手机作为 Central 连接手环,手机通常是 GATT Client / ATT Client,手环通常是 GATT Server / ATT Server。
但这不是强制绑定。
一个设备可以是:
| 设备 | Link 层角色 | ATT 角色 |
|---|---|---|
| 手机 | Central | ATT Client,也可以是 ATT Server |
| 手环 | Peripheral | ATT Server,也可以是 ATT Client |
| 双模网关 | Central / Peripheral 都可能 | Client / Server 都可能 |
所以不要写死:
Central == Client
Peripheral == Server
这在简单 BLE demo 里可能“看起来对”,但在真实协议栈里会把人坑进沟里。
3. Attribute 是一个 discrete value
3.1 原文含义
Protocol overview 说:
An attribute is a discrete value.
这里的 discrete value 可以理解成“独立的数据项”。
ATT 不关心这个数据项在业务上代表什么。
它只关心这个数据项怎么被标识、怎么被访问、有没有权限限制。
3.2 Attribute 的三个关联属性
规范把 attribute 关联的内容列成三项:
| 属性 | 中文 | 说明 |
|---|---|---|
| attribute type | 属性类型 | 由 UUID 定义 |
| attribute handle | 属性句柄 | Server 上唯一标识一个 attribute |
| permissions | 权限集合 | 由使用该 attribute 的上层规范定义 |
可以画成这样:
注意,这里看起来少了一个东西:value。
因为原文说 attribute 本身是一个 discrete value,同时又有 type、handle、permissions 这些关联属性。
从工程建模上,我们通常会把它整理成:
Attribute = Handle + Type + Value + Permissions
也就是:
3.3 工程中的 Attribute 数据结构
一个最小 ATT/GATT 数据库项,大概会写成这样:
typedef struct {
uint16_t handle;
uint8_t uuid_type; // 16-bit / 32-bit / 128-bit
uint8_t uuid[16];
uint8_t *value;
uint16_t value_len;
uint16_t permissions;
} att_attribute_t;
这个结构不是规范原文规定的 C 结构,只是工程实现中常见的抽象。
4. Attribute Type:说明这个属性“是什么”
4.1 原文含义
Protocol overview 说明:
Attribute type specifies what the attribute represents.
也就是说,Attribute Type 负责说明这个 attribute 代表什么。
4.2 Attribute Type 与 UUID
Attribute Type 由 UUID 定义。
| UUID 类型 | 用途 |
|---|---|
| 16-bit UUID | Bluetooth SIG 分配的常用类型 |
| 32-bit UUID | 可扩展类型,需要映射到 128-bit UUID |
| 128-bit UUID | 厂商自定义类型常用 |
例如:
| Attribute Type | 说明 |
|---|---|
0x2800 |
Primary Service Declaration |
0x2803 |
Characteristic Declaration |
0x2902 |
Client Characteristic Configuration Descriptor |
0x2A00 |
Device Name |
0x2A19 |
Battery Level |
这里要注意:
UUID 说明 attribute 是什么,handle 说明 attribute 在哪里。
4.3 通俗理解
如果 Attribute Database 是一个小区:
| ATT 概念 | 类比 |
|---|---|
| Attribute Handle | 门牌号 |
| Attribute Type / UUID | 房屋用途 |
| Attribute Value | 房子里放的东西 |
| Permissions | 门禁规则 |
例如:
| Handle | UUID | Value |
|---|---|---|
0x0001 |
0x2800 Primary Service |
GAP Service UUID |
0x0002 |
0x2803 Characteristic Declaration |
Device Name 声明 |
0x0003 |
0x2A00 Device Name |
"MyDevice" |
Client 想读 Device Name 时,不是直接说:
我要读 Device Name
而是说:
ATT_READ_REQ(handle = 0x0003)
UUID 帮你发现它,handle 帮你访问它。
5. Bluetooth SIG defined attribute types 与 Non-Bluetooth SIG types
5.1 原文含义
Protocol overview 还提到两类 attribute type:
| 类型 | 说明 |
|---|---|
| Bluetooth SIG defined attribute types | 由 Bluetooth SIG 定义,记录在 Assigned Numbers 中 |
| Non-Bluetooth SIG attribute types | 非 Bluetooth SIG 定义,通常是厂商自定义 UUID |
5.2 工程解释
如果你做标准服务,比如 Battery Service,那么会使用 SIG 定义的 UUID:
| 服务 / 特征值 | UUID |
|---|---|
| Battery Service | 0x180F |
| Battery Level | 0x2A19 |
如果你做私有 OTA 服务,就可能定义 128-bit UUID:
12345678-1234-5678-1234-56789abcdef0
5.3 常见坑
厂商自定义 UUID 最容易出问题的是字节序。
很多抓包工具显示 UUID 的方式和代码数组里的存储方式不一致。
你以为自己写的是:
12345678-1234-5678-1234-56789abcdef0
结果代码里数组小端排布,看起来像被蓝牙规范放进洗衣机甩干了一遍。
工程建议:
-
统一封装 UUID 比较函数;
-
不要到处手写
memcmp; -
打日志时同时打印 canonical string 和 raw bytes;
-
抓包和代码里的 UUID 表示方式要做对照表。
6. Attribute Handle:Client 访问 attribute 的关键
6.1 原文含义
Protocol overview 说明:
Attribute handle uniquely identifies an attribute on a server.
也就是说,在同一个 ATT Server 上,handle 唯一标识一个 attribute。
6.2 handle 的作用
handle 用在下面几个场景:
| 场景 | 例子 |
|---|---|
| Read Request | Client 通过 handle 读取 value |
| Write Request | Client 通过 handle 写入 value |
| Notification | Server 告诉 Client 哪个 handle 的 value 变化 |
| Indication | Server 指示 Client 哪个 handle 的 value 变化 |
| Find / Discover | Client 发现 Server 上有哪些 handle |
6.3 handle 和读写操作
普通读:
普通写:
Notification:
6.4 handle 的工程意义
handle 是 ATT 层的核心索引。
ATT Server 收到请求后,第一步通常是查 handle:
att_attribute_t *att_db_find_by_handle(uint16_t handle)
{
for (int i = 0; i < att_db_count; i++) {
if (att_db[i].handle == handle) {
return &att_db[i];
}
}
return NULL;
}
如果找不到,后续就会返回类似:
Invalid Handle
或者在某些 discovery 场景返回:
Attribute Not Found
具体返回什么,要看对应 PDU 的规范要求。
7. Permissions:权限存在,但不能通过 ATT 直接访问
7.1 原文含义
Protocol overview 里有一句非常重要:
permissions are defined by each higher layer specification that utilizes the attribute; these permissions cannot be accessed using the Attribute Protocol.
意思是:
-
Attribute 的 permissions 由使用它的上层规范定义;
-
ATT 本身不能直接读取 permissions。
7.2 逐句解释
| 原文含义 | 中文解释 | 工程理解 |
|---|---|---|
| permissions are defined by higher layer specification | 权限由上层规范定义 | 比如 GATT、Service 规范、Profile 规范 |
| utilizes the attribute | 使用该 attribute 的规范 | 谁定义这个 attribute,谁规定它怎么访问 |
| cannot be accessed using ATT | 不能通过 ATT 直接访问 permissions | Client 不能发一个 ATT 命令直接查询权限表 |
7.3 什么叫不能通过 ATT 访问 permissions?
这点很容易误解。
Client 不能这样问 Server:
请告诉我 handle 0x0003 的权限是什么?
ATT 没有这种通用 PDU。
但 Client 可以通过访问结果间接知道:
| 操作 | 可能结果 |
|---|---|
| 读一个不可读 attribute | 返回 Read Not Permitted |
| 写一个不可写 attribute | 返回 Write Not Permitted |
| 未加密读取安全 attribute | 返回 Insufficient Encryption |
| 未认证访问 MITM 保护 attribute | 返回 Insufficient Authentication |
| 密钥长度不够 | 返回 Encryption Key Size Too Short |
也就是说,权限像门禁规则。
你不能直接问物业“这扇门权限表打印一份给我”,但你刷卡失败时,门禁会告诉你“大概哪里不行”。
7.4 工程中的权限设计
常见权限 bit 可能这样设计:
#define ATT_PERM_READ 0x0001
#define ATT_PERM_WRITE 0x0002
#define ATT_PERM_READ_ENCRYPT 0x0004
#define ATT_PERM_WRITE_ENCRYPT 0x0008
#define ATT_PERM_READ_AUTHEN 0x0010
#define ATT_PERM_WRITE_AUTHEN 0x0020
#define ATT_PERM_AUTHORIZE 0x0040
读请求处理时可能这样:
static uint8_t att_check_read_permission(att_attribute_t *attr,
att_security_t *sec)
{
if ((attr->permissions & ATT_PERM_READ) == 0) {
return ATT_ERR_READ_NOT_PERMITTED;
}
if ((attr->permissions & ATT_PERM_READ_ENCRYPT) &&
!sec->encrypted) {
return ATT_ERR_INSUFFICIENT_ENCRYPTION;
}
if ((attr->permissions & ATT_PERM_READ_AUTHEN) &&
!sec->authenticated) {
return ATT_ERR_INSUFFICIENT_AUTHENTICATION;
}
return ATT_ERR_SUCCESS;
}
7.5 常见坑
权限不在 ATT PDU 里,但 ATT Server 处理每个 PDU 时都要查权限。
这就像餐厅菜单上不一定写“厨房是否允许你进”,但你真往厨房冲,保安一定会拦你。
8. Client 发送 Request,Server 必须响应
8.1 原文含义
Protocol overview 明确说明:
A client may send Attribute Protocol requests to a server, and the server shall respond to all requests that it receives.
这句话里的 shall 很重要。
8.2 逐句解释
| 原文含义 | 中文解释 | 工程意义 |
|---|---|---|
| Client may send requests | Client 可以发送 ATT requests | Client 主动访问 Server |
| Server shall respond | Server 必须响应 | 收到 Request 不能装死 |
| all requests it receives | 所有收到的 requests | 不支持也要用 Error Response 回 |
8.3 Request 和 Response 的基本模型
比如读请求:
8.4 工程注意
只要是 Request,就必须有结果:
| Request 情况 | Server 应该做什么 |
|---|---|
| 支持且成功 | 返回对应 Response |
| 支持但失败 | 返回 ATT_ERROR_RSP |
| 不支持该 Request | 返回 ATT_ERROR_RSP,错误码一般是 Request Not Supported |
| PDU 格式非法 | 返回 ATT_ERROR_RSP 或按规范处理 |
| 权限不足 | 返回对应权限错误 |
| handle 不存在 | 返回对应 handle/attribute 错误 |
不能这样:
收到不支持的 request,直接丢弃
这会导致 Client 等超时。
协议栈里最怕这种“沉默是金”的行为,因为调试时你会看到 Client 像等对象回消息一样,越等越焦虑。
9. 一个设备可以同时是 Client 和 Server
9.1 原文含义
Protocol overview 说明:
A device can implement both client and server roles, and both roles can function concurrently in the same device and between the same devices.
意思是:
-
一个设备可以同时实现 Client 和 Server;
-
两个角色可以在同一个设备里并发工作;
-
同一对设备之间,也可以双向同时存在 Client/Server 关系。
9.2 图示
9.3 工程解释
这意味着协议栈不能假设:
我连接对方后,只会作为 Client
或者:
我是 Peripheral,所以我只会作为 Server
真实产品里可能出现:
| 场景 | 说明 |
|---|---|
| 手机读手环电量 | 手机是 Client,手环是 Server |
| 手环读手机时间服务 | 手环是 Client,手机是 Server |
| 两个设备互相同步状态 | 双方都同时是 Client 和 Server |
| EATT 多 bearer 并发 | 多个 ATT bearer 同时存在事务 |
9.4 工程设计建议
协议栈内部最好拆分:
Client 和 Server 状态机不要写成互斥。
否则一旦遇到双向 GATT 或 EATT,就会出现各种玄学 bug。
10. 每个 Bluetooth Device 只有一个 Server 实例
10.1 原文含义
Protocol overview 中有一个非常关键的约束:
There shall be only one instance of a server on each Bluetooth device.
也就是说:
每个蓝牙设备上只能有一个 ATT Server 实例。
这个点很重要,尤其是理解多 bearer 和 GATT database 时。
10.2 为什么只能有一个 Server?
因为 handle namespace 必须一致。
规范进一步说明,这意味着:
attribute handles shall be identical for all supported bearers.
也就是说,同一个设备如果有多个 ATT bearer,不管你从哪个 bearer 访问,看到的 handle 应该是一致的。
10.3 图示
错误理解:
后面这种设计容易导致:
-
同一个 handle 在不同 bearer 上含义不一样;
-
GATT cache 混乱;
-
EATT 并发访问不一致;
-
BR/EDR bearer 和 LE bearer 看到的数据库不一致;
-
对端 Client 怀疑人生。
10.4 工程意义
如果一个设备同时支持:
-
LE fixed ATT bearer;
-
LE EATT bearer;
-
BR/EDR ATT bearer;
它们看到的 handle 应该一致。
也就是说:
| Bearer | handle 0x0003 应该代表 |
|---|---|
| LE fixed ATT bearer | Device Name |
| LE EATT bearer | Device Name |
| BR/EDR ATT bearer | Device Name |
不能出现:
| Bearer | handle 0x0003 |
|---|---|
| LE fixed ATT bearer | Device Name |
| EATT bearer | Battery Level |
| BR/EDR bearer | Firmware Version |
那就不是协议栈,是“平行宇宙”。
11. 对于同一个 Client,Server 有一套 attributes
11.1 原文含义
Protocol overview 还说明:
For a given client, the server shall have one set of attributes, which shall have the same value and properties irrespective of which bearer is used.
这句话可以拆成三层理解:
| 要点 | 中文解释 |
|---|---|
| For a given client | 对于某一个特定 Client |
| one set of attributes | Server 有一套 attributes |
| same value and properties | 不管用哪个 bearer,value 和 properties 应该一致 |
11.2 举例说明
假设 Client A 通过两个 ATT bearer 访问 Server:
如果 Client A 通过 Bearer 1 读取 handle 0x0007,得到 Battery Level = 87。
那么它通过 Bearer 2 读取同一个 handle,应该看到同一套 attribute 视图。
11.3 “same value” 和 “per-client value” 不矛盾
后面原文还提到:
Server can support multiple clients. Attribute values can be the same or different for each client as defined by GATT or higher layer specification.
也就是说:
-
对同一个 Client,跨 bearer 视图一致;
-
对不同 Client,某些 attribute value 可以不同,前提是由 GATT 或上层规范定义。
11.4 举例:CCCD 就是典型 per-client value
CCCD,全称 Client Characteristic Configuration Descriptor。
它通常是每个 Client 独立保存的。
例如:
| Client | CCCD value | 含义 |
|---|---|---|
| Client A | 0x0001 |
开启 Notification |
| Client B | 0x0000 |
未开启 Notification |
| Client C | 0x0002 |
开启 Indication |
这时候不同 Client 看到的 CCCD value 可以不同。
但是对于 Client A 自己,不管它通过哪个 ATT bearer 访问,CCCD 视图应该一致。
11.5 工程数据结构建议
可以把 attribute 分成两类:
| 类型 | 说明 |
|---|---|
| Global Attribute Value | 所有 Client 共享 |
| Per-client Attribute Value | 每个 Client 独立 |
示例:
typedef struct {
uint16_t conn_id;
uint16_t handle;
uint8_t *value;
uint16_t value_len;
} att_client_value_t;
对于 CCCD 这种 per-client descriptor,建议不要简单存在全局 GATT DB 里,否则多个手机连接同一个设备时,通知配置会互相串台。
串台的后果就是:
A 手机打开通知,B 手机也收到;
B 手机关闭通知,A 手机突然没数据。
这不是蓝牙,是宫斗剧。
12. Server 可以支持多个 Clients
12.1 原文含义
Protocol overview 明确说 Server 可以支持多个 clients。
这意味着一个 ATT Server 可能同时被多个连接访问。
12.2 工程挑战
多个 Client 带来几个问题:
| 问题 | 说明 |
|---|---|
| 并发访问 | 多个 Client 同时读写 attribute |
| per-client 状态 | CCCD、MTU、安全状态可能不同 |
| Notification 目标 | 每个 Client 是否开启通知不同 |
| 安全等级 | 不同连接加密/认证状态可能不同 |
| Bearer 管理 | 每个连接可有多个 ATT bearer |
| 资源限制 | Prepare Queue、TX buffer、回调上下文 |
12.3 最小连接上下文
协议栈里通常需要维护 per-client / per-connection 上下文:
typedef struct {
uint16_t conn_handle;
uint16_t att_mtu;
bool encrypted;
bool authenticated;
uint8_t encryption_key_size;
/* per-client CCCD or other values */
att_client_value_t client_values[MAX_CLIENT_VALUES];
/* request transaction state */
bool has_pending_req;
uint8_t pending_opcode;
} att_client_context_t;
注意这里的 client 不是 ATT Client Engine,而是“连接到本 Server 的对端 Client 上下文”。
命名要谨慎,不然代码里会出现:
client->client->server_client->client_role
这种变量名一出来,后来的维护者基本只能烧香。
13. 多个 Services 可以暴露在一个 Server 上
13.1 原文 note 含义
Protocol overview 的 note 说明:
Multiple services may be exposed on a single server by allocating separate ranges of handles for each service.
意思是:
一个 ATT Server 可以暴露多个 services,每个 service 分配不同的 handle range。
13.2 图示
13.3 示例 Attribute Database
| Handle Range | Service | 示例 |
|---|---|---|
0x0001–0x0005 |
GAP Service | Device Name、Appearance |
0x0006–0x0009 |
GATT Service | Service Changed |
0x000A–0x000D |
Battery Service | Battery Level |
0x0010–0x0018 |
Vendor OTA Service | OTA Control、OTA Data |
13.4 Service Discovery 的本质
GATT 服务发现本质上就是:
在 Attribute Database 里找出哪些 handle range 对应哪些 service。
常见 ATT PDU 是:
ATT_READ_BY_GROUP_TYPE_REQ
ATT_READ_BY_GROUP_TYPE_RSP
这个我们后面会在 Read By Group Type 一篇详细讲。
14. Notification 和 Indication:不需要 Client 主动 Read
14.1 原文含义
Protocol overview 说明:
ATT 有 notification 和 indication 能力,可以高效地把 attribute values 发送给 Client,而不需要 Client 主动读取。
这点很关键。
如果没有 Notification/Indication,Client 想知道数据变化,就只能不断 read:
这很浪费。
有了 Notification:
有了 Indication:
14.2 Notification vs Indication
| 项 | Notification | Indication |
|---|---|---|
| ATT PDU | ATT_HANDLE_VALUE_NTF |
ATT_HANDLE_VALUE_IND |
| 是否需要 Client 确认 | 否 | 是 |
| 是否适合高频数据 | 是 | 不太适合 |
| 是否适合关键状态 | 一般 | 适合 |
| 典型用途 | 传感器数据、音频控制状态、日志流 | 关键配置结果、重要状态变化 |
| 性能 | 高 | 较低 |
| 可靠性语义 | ATT 层无确认 | ATT 层有确认 |
14.3 工程提醒
Notification 不等于“不可靠传输”。
底层链路可能仍然有重传和确认机制。
但从 ATT 层看:
-
Notification 发出去后,不等
ATT_HANDLE_VALUE_CFM; -
Indication 发出去后,必须等
ATT_HANDLE_VALUE_CFM。
所以 ATT 层语义不同。
15. ATT bearer:ATT PDU 不是凭空飞过去的
15.1 原文含义
Protocol overview 后半部分说明:
All Attribute Protocol requests are sent over an ATT bearer.
也就是说,ATT 请求都要走 ATT bearer。
15.2 ATT bearer 是什么?
ATT bearer 是发送 ATT PDU 的通道。
它底层使用 L2CAP channel。
15.3 多个 ATT bearer
Protocol overview 说明,两个设备之间可以建立多个 ATT bearers。
每个 ATT bearer:
-
使用单独的 L2CAP channel;
-
可以有不同配置;
-
可以支持不同的并发行为;
-
是 EATT 能力的基础。
15.4 为什么需要多个 bearer?
传统 ATT bearer 上,很多事务是顺序处理的。
一个 request 没完成,后面的 request 不能随便插队。
这就像一个银行窗口:
客户 A 正在办大额转账
后面的人只能排队
EATT / Enhanced ATT bearer 的意义之一,就是增加多个窗口:
窗口 1:读设备名
窗口 2:写 OTA 数据
窗口 3:读电池电量
这样能降低阻塞,提高并发能力。
16. LE 上的 ATT bearer
16.1 原文含义
Protocol overview 说明:
In LE, there is a single ATT bearer that uses a fixed channel that is available as soon as the ACL connection is established.
意思是:
在 LE 中,ACL 连接一建立,就有一个默认可用的 ATT bearer。
这个 bearer 使用固定 L2CAP channel。
16.2 图示
16.3 工程意义
BLE 连接建立后,通常就可以开始:
-
Exchange MTU;
-
Service Discovery;
-
Characteristic Discovery;
-
Read;
-
Write;
-
Configure CCCD;
-
Notification / Indication。
路径如下:
这也是为什么 BLE GATT 使用起来相对直接:
连接一建立,ATT/GATT 的基础通道就已经在那里了。
像进餐厅,桌子已经摆好了,你可以直接点菜。
17. LE 上的 additional ATT bearers
17.1 原文含义
Protocol overview 还说:
Additional ATT bearers can be established using L2CAP.
也就是说,除了默认 fixed ATT bearer,LE 还可以通过 L2CAP 建立额外 ATT bearers。
这就是 EATT 的基础。
17.2 图示
17.3 工程意义
默认 ATT bearer 像一个窗口。
EATT 像多开几个窗口。
| 场景 | 默认 ATT | EATT |
|---|---|---|
| 简单读写 | 足够 | 不一定需要 |
| 大量并发 GATT 操作 | 容易阻塞 | 更合适 |
| 多服务并行操作 | 事务排队 | 可并发 |
| OTA + 控制命令并行 | 可能互相影响 | 更容易优化 |
| 多客户端复杂场景 | 状态管理复杂 | 仍复杂,但能力更强 |
注意:EATT 不是“免费加速器”。
它引入更多 bearer、更多状态机、更多流控、更多资源管理。
想吃多线程红利,就要先交并发 bug 的学费。
18. BR/EDR 上的 ATT bearer
18.1 原文含义
Protocol overview 说明:
In BR/EDR, one or more ATT bearers can be established using L2CAP.
也就是说,BR/EDR 上没有 LE 那种“ACL 建立后自动可用的 fixed ATT bearer”。
它需要通过 L2CAP 建立一个或多个 ATT bearers。
18.2 图示
18.3 工程意义
ATT/GATT 不是 BLE 专属。
BR/EDR 也可以承载 ATT/GATT,但承载方式不同。
对比:
| 项 | LE | BR/EDR |
|---|---|---|
| 默认 ATT bearer | ACL 建立后 fixed channel 可用 | 无默认 fixed ATT bearer |
| 额外 bearer | 通过 L2CAP 建立 | 通过 L2CAP 建立 |
| 常见使用 | BLE GATT | GATT over BR/EDR |
| 底层链路 | LE ACL | BR/EDR ACL |
| 典型场景 | BLE 服务访问 | 双模设备、GATT over BR/EDR |
19. ATT bearer 与 Client/Server 的完整关系
把前面的内容合起来:
关键点:
-
一个设备可以同时有 ATT Client 和 ATT Server。
-
一个设备上只有一个 ATT Server 实例。
-
两个设备之间可以有多个 ATT bearer。
-
每个 ATT bearer 对应独立 L2CAP channel。
-
PDU 类型决定它应该交给本地 Client 还是 Server 处理。
20. 从抓包角度看 Protocol Overview
ATT 在 HCI 抓包里通常长这样:
HCI ACL Data
└── L2CAP
└── ATT
├── Opcode
├── Parameters
└── Optional Signature
20.1 LE 默认 ATT bearer 抓包路径
LE ACL Connection
HCI ACL Data
L2CAP CID: ATT fixed channel
ATT_READ_REQ
20.2 BR/EDR ATT bearer 抓包路径
BR/EDR ACL Connection
L2CAP Dynamic Channel Establishment
HCI ACL Data
L2CAP Dynamic CID
ATT PDU
20.3 抓包判断思路
看到一个 ATT 包,先问 5 个问题:
| 问题 | 作用 |
|---|---|
| 这个 PDU 是什么类型? | Request、Response、Command、Notification、Indication、Confirmation |
| 方向是谁到谁? | Client → Server 还是 Server → Client |
| 走哪个 L2CAP CID? | fixed channel 还是 dynamic channel |
| 操作哪个 handle? | 访问哪个 attribute |
| 是否需要后续响应? | 判断事务是否完整 |
21. Protocol Overview 的工程版状态机
21.1 ATT Server 简化状态机
21.2 ATT Client 简化状态机
21.3 Notification / Indication 状态机
22. 实现一个 ATT Server 时,这一节给出的约束
从 Protocol Overview 可以抽出几个实现约束:
| 规范要求 | 工程实现 |
|---|---|
| Server 暴露 attributes | 需要 Attribute Database |
| Attribute 有 type | DB 中要保存 UUID |
| Attribute 有 handle | DB 中要保存唯一 handle |
| Attribute 有 permissions | DB 中要保存权限和安全要求 |
| Client 通过 handle 访问 | Server 要支持 handle lookup |
| Request 必须响应 | Server 不能丢 request |
| 一个设备只有一个 Server 实例 | 多 bearer 共享同一 DB |
| 多 bearer handle 一致 | 不同 bearer 看到同一 handle namespace |
| 支持多个 clients | 需要 per-connection context |
| 不同 client value 可不同 | CCCD 等要 per-client 存储 |
| ATT 走 bearer | 需要 bearer manager |
| LE 有默认 fixed bearer | ACL 建立后初始化默认 ATT bearer |
| BR/EDR 通过 L2CAP 建 bearer | 需要 L2CAP channel establishment |
23. 最小 ATT Server 架构
23.1 核心结构建议
typedef struct {
uint16_t cid;
uint16_t mtu;
bool enhanced;
} att_bearer_t;
typedef struct {
uint16_t handle;
att_uuid_t type;
uint8_t *value;
uint16_t value_len;
uint16_t permissions;
} att_attribute_t;
typedef struct {
uint16_t conn_handle;
bool encrypted;
bool authenticated;
uint8_t key_size;
} att_security_context_t;
typedef struct {
uint16_t conn_handle;
att_bearer_t bearers[MAX_ATT_BEARERS];
att_security_context_t security;
} att_connection_t;
23.2 Request 处理框架
void att_server_handle_request(att_connection_t *conn,
att_bearer_t *bearer,
const uint8_t *pdu,
uint16_t len)
{
uint8_t opcode = pdu[0];
switch (opcode) {
case ATT_READ_REQ:
att_handle_read_req(conn, bearer, pdu, len);
break;
case ATT_WRITE_REQ:
att_handle_write_req(conn, bearer, pdu, len);
break;
case ATT_FIND_INFORMATION_REQ:
att_handle_find_information_req(conn, bearer, pdu, len);
break;
default:
att_send_error_rsp(bearer,
opcode,
0x0000,
ATT_ERR_REQUEST_NOT_SUPPORTED);
break;
}
}
24. 本篇重点术语表
| 术语 | 中文 | 工程理解 |
|---|---|---|
| Server role | 服务端角色 | 暴露 Attribute Database |
| Client role | 客户端角色 | 访问 Server 的 attributes |
| Attribute | 属性 | ATT 数据访问基本单位 |
| Attribute Type | 属性类型 | UUID,说明 attribute 是什么 |
| Attribute Handle | 属性句柄 | Server 内唯一编号 |
| Permissions | 权限 | 由上层定义,ATT 不能直接读取 |
| ATT bearer | ATT 承载 | 发送 ATT PDU 的 L2CAP channel |
| Fixed channel | 固定信道 | LE 默认 ATT bearer 使用 |
| Dynamic channel | 动态信道 | L2CAP 建立的额外 bearer |
| Notification | 通知 | Server 主动发,不要确认 |
| Indication | 指示 | Server 主动发,需要确认 |
| Confirmation | 确认 | Client 对 indication 的确认 |
25. 常见误区
25.1 误区一:Client 一定是 Central
不一定。
Central/Peripheral 是连接角色。
Client/Server 是 ATT/GATT 访问角色。
25.2 误区二:一个 bearer 对应一套 DB
不对。
同一个设备只有一个 ATT Server 实例。
多个 bearer 应该共享一致的 handle namespace 和 attribute 视图。
25.3 误区三:不同 Client 的所有 value 必须完全一样
不一定。
某些 value 可以按 Client 不同而不同,例如 CCCD。
但这必须由 GATT 或上层规范定义。
25.4 误区四:权限可以通过 ATT 查询
不能。
ATT 没有一个“读取权限表”的通用 PDU。
Client 只能通过访问结果和错误码间接知道权限约束。
25.5 误区五:Notification 和 Indication 都是 Server 主动发,所以差不多
不一样。
Notification 不需要 Confirmation。
Indication 需要 Client 回 Confirmation。
26. 本篇小结
本篇学习了 Vol 3 Part F 的 2 Protocol overview,核心结论如下:
-
ATT 定义两种角色:Client 和 Server。
-
Server 暴露 attributes,Client 访问 attributes。
-
Attribute 是独立数据项,关联 type、handle、permissions。
-
Attribute Type 由 UUID 定义,用来说明 attribute 代表什么。
-
Attribute Handle 在 Server 上唯一标识一个 attribute。
-
Permissions 由上层规范定义,不能通过 ATT 直接访问。
-
Client 可以发送 requests,Server 必须响应收到的 requests。
-
一个设备可以同时实现 Client 和 Server。
-
一个 Bluetooth Device 上只能有一个 ATT Server 实例。
-
多个 ATT bearers 应该看到一致的 attribute handle namespace。
-
Server 可以支持多个 clients。
-
某些 attribute value 可以按 Client 不同而不同,例如 CCCD。
-
多个 services 可以通过不同 handle range 暴露在同一个 Server 上。
-
Notification 和 Indication 允许 Server 主动向 Client 发送 attribute value。
-
所有 ATT 请求都通过 ATT bearer 发送。
-
一个 ATT bearer 使用一个 L2CAP channel。
-
LE 中默认有一个 fixed ATT bearer,ACL 连接建立后即可使用。
-
LE 可以通过 L2CAP 建立额外 ATT bearers。
-
BR/EDR 中可以通过 L2CAP 建立一个或多个 ATT bearers。
这一节是 ATT 的“协议地图”。
后面进入 Protocol requirements 时,就会开始详细拆 Attribute type、handle、value、permissions、MTU、bearer 等基础概念。
下一篇继续学习:
第 3 篇:Protocol Requirements 总览——ATT 规范里的强制要求
下一篇重点:
-
3 Protocol requirements;
-
3.1 Introduction;
-
Attribute 的基本访问规则;
-
shall、may、shall not的工程含义; -
为什么 ATT 的很多权限和语义都由上层规范决定。
更多推荐



所有评论(0)