Core_v6.2 Vol 3 Part F 学习笔记(十三):Error Handling------ATT_ERROR_RSP
0. 本篇说明
本文继续学习 Bluetooth Core Specification v6.2:
Vol 3, Part F:Attribute Protocol,简称 ATT
本篇对应章节:
3.4.1 Error handling
3.4.1.1 ATT_ERROR_RSP
上一篇我们学习了:
- Attribute PDU format;
- Attribute Opcode;
- Command Flag;
- Authentication Signature Flag;
- Method;
- Sequential protocol;
- Transaction;
- 30 秒 transaction timeout;
- timeout 后该 ATT bearer 不能继续发送 ATT requests、commands、indications 或 notifications。
这一篇正式进入 ATT Protocol PDUs 的第一个具体 PDU:
ATT_ERROR_RSP
一句话先打底:
ATT_ERROR_RSP 是 ATT Server 对 Client Request 的"拒绝信",告诉 Client:你这个请求我不能执行,原因是某某错误码。
通俗一点:
Client 发 request 像去柜台办业务。
Server 能办,就回正常 response;
办不了,就回
ATT_ERROR_RSP。这不是 Server 崩了,而是柜台小姐姐认真告诉你:"材料不全,回去补。"
1. ATT_ERROR_RSP 是什么?
1.1 规范核心意思
规范说:
ATT_ERROR_RSPPDU is used to state that a given request cannot be performed, and to provide the reason.
翻译:
ATT_ERROR_RSP用于说明某个 request 不能被执行,并提供原因。
注意两个关键词:
| 关键词 | 说明 |
|---|---|
| request | ATT_ERROR_RSP 是针对 request 的 |
| reason | 通过 Error Code 表示失败原因 |
也就是说,ATT_ERROR_RSP 是 request-response transaction 中的错误响应。
1.2 它不是给 Command 用的
规范特别说明:
Commands,例如
ATT_WRITE_CMD和ATT_SIGNED_WRITE_CMD,不会生成这个 response。
所以:
| Client 发出的 PDU | 失败时是否可能有 ATT_ERROR_RSP |
|---|---|
ATT_READ_REQ |
可能 |
ATT_WRITE_REQ |
可能 |
ATT_FIND_INFORMATION_REQ |
可能 |
ATT_READ_BLOB_REQ |
可能 |
ATT_PREPARE_WRITE_REQ |
可能 |
ATT_WRITE_CMD |
不会 |
ATT_SIGNED_WRITE_CMD |
不会 |
1.3 图示
ATT Server ATT Client ATT Server ATT Client #mermaid-svg-s1sjx4JNKwYezhcr{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s1sjx4JNKwYezhcr .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s1sjx4JNKwYezhcr .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s1sjx4JNKwYezhcr .error-icon{fill:#552222;}#mermaid-svg-s1sjx4JNKwYezhcr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s1sjx4JNKwYezhcr .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s1sjx4JNKwYezhcr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s1sjx4JNKwYezhcr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s1sjx4JNKwYezhcr .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s1sjx4JNKwYezhcr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s1sjx4JNKwYezhcr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s1sjx4JNKwYezhcr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s1sjx4JNKwYezhcr .marker.cross{stroke:#333333;}#mermaid-svg-s1sjx4JNKwYezhcr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s1sjx4JNKwYezhcr p{margin:0;}#mermaid-svg-s1sjx4JNKwYezhcr .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s1sjx4JNKwYezhcr text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-s1sjx4JNKwYezhcr .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-s1sjx4JNKwYezhcr .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-s1sjx4JNKwYezhcr .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-s1sjx4JNKwYezhcr .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-s1sjx4JNKwYezhcr #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-s1sjx4JNKwYezhcr .sequenceNumber{fill:white;}#mermaid-svg-s1sjx4JNKwYezhcr #sequencenumber{fill:#333;}#mermaid-svg-s1sjx4JNKwYezhcr #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-s1sjx4JNKwYezhcr .messageText{fill:#333;stroke:none;}#mermaid-svg-s1sjx4JNKwYezhcr .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s1sjx4JNKwYezhcr .labelText,#mermaid-svg-s1sjx4JNKwYezhcr .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-s1sjx4JNKwYezhcr .loopText,#mermaid-svg-s1sjx4JNKwYezhcr .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-s1sjx4JNKwYezhcr .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-s1sjx4JNKwYezhcr .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-s1sjx4JNKwYezhcr .noteText,#mermaid-svg-s1sjx4JNKwYezhcr .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-s1sjx4JNKwYezhcr .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s1sjx4JNKwYezhcr .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s1sjx4JNKwYezhcr .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-s1sjx4JNKwYezhcr .actorPopupMenu{position:absolute;}#mermaid-svg-s1sjx4JNKwYezhcr .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-s1sjx4JNKwYezhcr .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-s1sjx4JNKwYezhcr .actor-man circle,#mermaid-svg-s1sjx4JNKwYezhcr line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-s1sjx4JNKwYezhcr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt成功失败 ATT_READ_REQ(handle)ATT_READ_RSP(value)ATT_ERROR_RSP(request opcode, handle, error code)
1.4 工程理解
ATT_ERROR_RSP 不是异常崩溃,而是协议定义的正常结果。
比如:
text
ATT_READ_REQ(handle = 0x9999)
Server 上没有这个 handle,于是返回:
text
ATT_ERROR_RSP
Request Opcode In Error = ATT_READ_REQ
Attribute Handle In Error = 0x9999
Error Code = Invalid Handle
这不是"蓝牙坏了"。
这是 Server 在说:
"哥们,0x9999 这个门牌号我这栋楼没有。"
2. ATT_ERROR_RSP PDU 格式
2.1 Markdown 表格
根据规范 Table 3.3,ATT_ERROR_RSP 格式如下:
| 参数 | 大小 | 说明 |
|---|---|---|
| Attribute Opcode | 1 octet | 0x01 = ATT_ERROR_RSP |
| Request Opcode In Error | 1 octet | 触发这个 error response 的 request opcode |
| Attribute Handle In Error | 2 octets | 触发错误的 attribute handle |
| Error Code | 1 octet | 失败原因 |
总长度:
text
1 + 1 + 2 + 1 = 5 octets
所以 ATT_ERROR_RSP 是一个固定长度 5 字节的 PDU。
2.2 PDU 结构图
text
+------------------+-------------------------+----------------------------+------------+
| Attribute Opcode | Request Opcode In Error | Attribute Handle In Error | Error Code |
| 1 octet | 1 octet | 2 octets | 1 octet |
+------------------+-------------------------+----------------------------+------------+
2.3 Mermaid 图
#mermaid-svg-jyiii59fU1LL0a8o{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jyiii59fU1LL0a8o .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jyiii59fU1LL0a8o .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jyiii59fU1LL0a8o .error-icon{fill:#552222;}#mermaid-svg-jyiii59fU1LL0a8o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jyiii59fU1LL0a8o .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jyiii59fU1LL0a8o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jyiii59fU1LL0a8o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jyiii59fU1LL0a8o .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jyiii59fU1LL0a8o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jyiii59fU1LL0a8o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jyiii59fU1LL0a8o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jyiii59fU1LL0a8o .marker.cross{stroke:#333333;}#mermaid-svg-jyiii59fU1LL0a8o svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jyiii59fU1LL0a8o p{margin:0;}#mermaid-svg-jyiii59fU1LL0a8o .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jyiii59fU1LL0a8o .cluster-label text{fill:#333;}#mermaid-svg-jyiii59fU1LL0a8o .cluster-label span{color:#333;}#mermaid-svg-jyiii59fU1LL0a8o .cluster-label span p{background-color:transparent;}#mermaid-svg-jyiii59fU1LL0a8o .label text,#mermaid-svg-jyiii59fU1LL0a8o span{fill:#333;color:#333;}#mermaid-svg-jyiii59fU1LL0a8o .node rect,#mermaid-svg-jyiii59fU1LL0a8o .node circle,#mermaid-svg-jyiii59fU1LL0a8o .node ellipse,#mermaid-svg-jyiii59fU1LL0a8o .node polygon,#mermaid-svg-jyiii59fU1LL0a8o .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jyiii59fU1LL0a8o .rough-node .label text,#mermaid-svg-jyiii59fU1LL0a8o .node .label text,#mermaid-svg-jyiii59fU1LL0a8o .image-shape .label,#mermaid-svg-jyiii59fU1LL0a8o .icon-shape .label{text-anchor:middle;}#mermaid-svg-jyiii59fU1LL0a8o .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jyiii59fU1LL0a8o .rough-node .label,#mermaid-svg-jyiii59fU1LL0a8o .node .label,#mermaid-svg-jyiii59fU1LL0a8o .image-shape .label,#mermaid-svg-jyiii59fU1LL0a8o .icon-shape .label{text-align:center;}#mermaid-svg-jyiii59fU1LL0a8o .node.clickable{cursor:pointer;}#mermaid-svg-jyiii59fU1LL0a8o .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jyiii59fU1LL0a8o .arrowheadPath{fill:#333333;}#mermaid-svg-jyiii59fU1LL0a8o .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jyiii59fU1LL0a8o .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jyiii59fU1LL0a8o .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jyiii59fU1LL0a8o .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jyiii59fU1LL0a8o .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jyiii59fU1LL0a8o .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jyiii59fU1LL0a8o .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jyiii59fU1LL0a8o .cluster text{fill:#333;}#mermaid-svg-jyiii59fU1LL0a8o .cluster span{color:#333;}#mermaid-svg-jyiii59fU1LL0a8o div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jyiii59fU1LL0a8o .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jyiii59fU1LL0a8o rect.text{fill:none;stroke-width:0;}#mermaid-svg-jyiii59fU1LL0a8o .icon-shape,#mermaid-svg-jyiii59fU1LL0a8o .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jyiii59fU1LL0a8o .icon-shape p,#mermaid-svg-jyiii59fU1LL0a8o .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jyiii59fU1LL0a8o .icon-shape .label rect,#mermaid-svg-jyiii59fU1LL0a8o .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jyiii59fU1LL0a8o .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jyiii59fU1LL0a8o .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jyiii59fU1LL0a8o :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT_ERROR_RSP
Attribute Opcode
0x01
Request Opcode In Error
Attribute Handle In Error
Error Code
2.4 逐字段解释
| 字段 | 通俗理解 |
|---|---|
| Attribute Opcode | "这是一封错误响应" |
| Request Opcode In Error | "你刚才哪个请求出错了" |
| Attribute Handle In Error | "哪个 handle 出问题了" |
| Error Code | "具体错因是什么" |
3. Request Opcode In Error
3.1 规范要求
Request Opcode In Error 应设置为:
触发此错误的 request 的 Attribute Opcode。
例如 Client 发:
text
ATT_READ_REQ
Server 回:
text
ATT_ERROR_RSP
Request Opcode In Error = ATT_READ_REQ
3.2 示例
Client:
text
0A 99 99
解释:
| 字节 | 含义 |
|---|---|
0A |
ATT_READ_REQ |
99 99 |
handle 0x9999 |
Server:
text
01 0A 99 99 01
解释:
| 字节 | 含义 |
|---|---|
01 |
ATT_ERROR_RSP |
0A |
Request Opcode In Error = ATT_READ_REQ |
99 99 |
Attribute Handle In Error = 0x9999 |
01 |
Error Code = Invalid Handle |
3.3 工程意义
Client 收到 ATT_ERROR_RSP 后,要根据 Request Opcode In Error 找到正在等待的 transaction。
伪代码:
c
void att_client_handle_error_rsp(att_bearer_t *bearer,
const uint8_t *pdu,
uint16_t len)
{
uint8_t req_opcode;
uint16_t handle;
uint8_t err;
if (len != 5) {
return;
}
req_opcode = pdu[1];
handle = read_le16(&pdu[2]);
err = pdu[4];
if (!bearer->request_pending ||
bearer->pending_req_opcode != req_opcode) {
att_handle_unexpected_error_rsp(bearer, req_opcode, handle, err);
return;
}
bearer->request_pending = false;
gatt_procedure_failed(req_opcode, handle, err);
}
3.4 常见坑
错误代码:
c
if (opcode == ATT_ERROR_RSP) {
current_request_failed();
}
这个太粗糙。
正确做法是:
- 解析
Request Opcode In Error; - 检查它是否匹配当前 pending request;
- 再把错误传给对应 GATT procedure。
否则多 bearer、EATT 或复杂 GATT procedure 下,很容易把错误甩给错误的业务流程。
4. Attribute Handle In Error
4.1 规范要求
Attribute Handle In Error 应设置为:
原始 request 中导致错误的 attribute handle。
如果原始 request 中没有 attribute handle,或者 request 不被支持,则使用
0x0000。
4.2 什么时候填真实 handle?
比如:
text
ATT_READ_REQ(handle = 0x9999)
Server 回:
text
Attribute Handle In Error = 0x9999
再比如:
text
ATT_WRITE_REQ(handle = 0x0012, value = ...)
如果 0x0012 不允许写,Server 回:
text
Attribute Handle In Error = 0x0012
Error Code = Write Not Permitted
4.3 什么时候填 0x0000?
常见情况:
| 场景 | Handle In Error |
|---|---|
| Request 不被支持 | 0x0000 |
| 原始 request 没有 handle 字段 | 0x0000 |
| PDU 格式错误,不能可靠解析 handle | 0x0000 |
| Server 资源不足且不指向具体 attribute | 0x0000 |
| 处理过程发生内部错误且无法定位 handle | 0x0000 |
比如 Server 不支持某个 request:
text
ATT_ERROR_RSP
Request Opcode In Error = unsupported request
Attribute Handle In Error = 0x0000
Error Code = Request Not Supported
4.4 多 handle request 怎么填?
对于多个 handle 的请求,如果某个 handle 出错,通常应填第一个导致错误的 handle。
例如:
text
ATT_READ_MULTIPLE_REQ(handles = 0x0003, 0x9999, 0x0007)
如果 0x9999 无效,则:
text
Attribute Handle In Error = 0x9999
Error Code = Invalid Handle
规范在 multiple variable read 等场景中也要求:如果发送 ATT_ERROR_RSP,Attribute Handle In Error 应设置为第一个导致错误的 attribute handle。
4.5 工程建议
Server 处理请求时,要尽量保留"哪个 handle 出错"的上下文。
c
static void att_send_error_for_handle(att_bearer_t *bearer,
uint8_t req_opcode,
uint16_t handle,
uint8_t error_code)
{
uint8_t pdu[5];
pdu[0] = ATT_ERROR_RSP;
pdu[1] = req_opcode;
write_le16(&pdu[2], handle);
pdu[4] = error_code;
l2cap_send_att_pdu(bearer->cid, pdu, sizeof(pdu));
}
5. Error Code 总览
规范 Table 3.4 定义了 ATT error codes。
下面整理成 Markdown 表格。
| Error Code | 名称 | 含义 |
|---|---|---|
0x01 |
Invalid Handle | 给出的 attribute handle 在此 Server 上无效 |
0x02 |
Read Not Permitted | attribute 不能被读取 |
0x03 |
Write Not Permitted | attribute 不能被写入 |
0x04 |
Invalid PDU | Attribute PDU 无效 |
0x05 |
Insufficient Authentication | attribute 需要 authentication 后才能读写 |
0x06 |
Request Not Supported | ATT Server 不支持收到的 request |
0x07 |
Invalid Offset | 指定 offset 超过 attribute 末尾 |
0x08 |
Insufficient Authorization | attribute 需要 authorization 后才能读写 |
0x09 |
Prepare Queue Full | prepare write 队列已满 |
0x0A |
Attribute Not Found | 给定 handle range 内没有找到 attribute |
0x0B |
Attribute Not Long | attribute 不能用 ATT_READ_BLOB_REQ 读取 |
0x0C |
Encryption Key Size Too Short | 当前加密密钥长度太短 |
0x0D |
Invalid Attribute Value Length | 本次操作中的 attribute value 长度无效 |
0x0E |
Unlikely Error | 请求遇到了不太可能发生的错误,无法完成 |
0x0F |
Insufficient Encryption | attribute 需要 encryption 后才能读写 |
0x10 |
Unsupported Group Type | attribute type 不是上层规范支持的 grouping attribute |
0x11 |
Insufficient Resources | 资源不足,无法完成请求 |
0x12 |
Database Out Of Sync | Server 要求 Client 重新发现数据库 |
0x13 |
Value Not Allowed | attribute 参数值不被允许 |
0x80--0x9F |
Application Error | 上层规范定义的应用错误码 |
0xE0--0xFF |
Common Profile and Service Error Codes | 通用 Profile / Service 错误码 |
| 其他值 | Reserved for future use | 保留 |
6. 错误码分类理解
为了方便工程调试,可以把 error codes 分成几类。
6.1 Handle / Range 类
| Error Code | 名称 | 常见场景 |
|---|---|---|
0x01 |
Invalid Handle | handle 为 0、handle 不存在、start > end |
0x0A |
Attribute Not Found | handle range 里找不到符合条件的 attribute |
0x10 |
Unsupported Group Type | group type 不是支持的 grouping attribute |
这类错误通常说明:
Client 找错门牌号,或者搜索范围里没有想找的东西。
6.2 权限类
| Error Code | 名称 | 常见场景 |
|---|---|---|
0x02 |
Read Not Permitted | attribute 不允许读 |
0x03 |
Write Not Permitted | attribute 不允许写 |
0x08 |
Insufficient Authorization | 需要应用或用户授权 |
0x05 |
Insufficient Authentication | 需要认证安全关系 |
0x0F |
Insufficient Encryption | 需要链路加密 |
0x0C |
Encryption Key Size Too Short | 加密密钥长度不够 |
这类错误通常说明:
不是没有这个门,而是你没权限进。
6.3 PDU / 参数类
| Error Code | 名称 | 常见场景 |
|---|---|---|
0x04 |
Invalid PDU | PDU 长度错误、格式错误 |
0x07 |
Invalid Offset | offset 超过 value 长度 |
0x0D |
Invalid Attribute Value Length | 写入长度不符合要求 |
0x13 |
Value Not Allowed | 参数值不允许 |
这类错误通常说明:
请求包格式或参数不对。不是门禁问题,是你表格填错了。
6.4 功能支持类
| Error Code | 名称 | 常见场景 |
|---|---|---|
0x06 |
Request Not Supported | Server 不支持该 request |
0x0B |
Attribute Not Long | attribute 不适合用 Blob 读 |
这类错误说明:
你要办的业务,这个窗口不支持。
6.5 资源 / 内部错误类
| Error Code | 名称 | 常见场景 |
|---|---|---|
0x09 |
Prepare Queue Full | queued writes 太多 |
0x11 |
Insufficient Resources | 内存、buffer、queue 等资源不足 |
0x0E |
Unlikely Error | 内部处理出现意料之外的问题 |
这类错误说明:
柜台不是不想办,是系统或资源扛不住。
6.6 数据库 / 上层错误类
| Error Code | 名称 | 常见场景 |
|---|---|---|
0x12 |
Database Out Of Sync | GATT cache 失效,需要重新发现 |
0x80--0x9F |
Application Error | Service/Profile 定义的应用错误 |
0xE0--0xFF |
Common Profile and Service Error Codes | 通用 Profile/Service 错误 |
这类错误说明:
ATT 层还能沟通,但上层数据库或业务语义出问题了。
7. Error Code 逐个讲解
7.1 0x01 Invalid Handle
含义:
Client 给出的 handle 在 Server 上无效。
常见触发:
| 场景 | 示例 |
|---|---|
handle 为 0x0000 |
ATT_READ_REQ(0x0000) |
| handle 不存在 | ATT_READ_REQ(0x9999) |
| handle range 非法 | start handle > end handle |
| GATT cache 失效 | Client 用了旧 handle |
示例:
text
ATT_READ_REQ(handle = 0x9999)
ATT_ERROR_RSP(req=ATT_READ_REQ, handle=0x9999, error=Invalid Handle)
工程建议:
- Client 收到后应检查是否使用了旧 cache;
- Server 应准确设置
Attribute Handle In Error; - 如果是 discovery range 错误,通常填 starting handle。
7.2 0x02 Read Not Permitted
含义:
attribute 不能被读取。
常见触发:
| 场景 | 示例 |
|---|---|
| Control Point 不允许读 | OTA Control Point |
| Write-only characteristic | 只写控制属性 |
| Descriptor 不可读 | 私有 descriptor |
| 属性权限没有 read bit | Server 权限表限制 |
示例:
text
ATT_READ_REQ(handle = 0x0101)
ATT_ERROR_RSP(error = Read Not Permitted)
注意区分:
| 错误 | 含义 |
|---|---|
| Read Not Permitted | 这个属性本身不允许读 |
| Insufficient Encryption | 可以读,但需要先加密 |
| Insufficient Authentication | 可以读,但需要更高认证等级 |
别把所有"读失败"都归为一个锅。
蓝牙错误码不是摆设,它在帮你定位门到底是不存在、锁着,还是不让你读。
7.3 0x03 Write Not Permitted
含义:
attribute 不能被写入。
常见触发:
| 场景 | 示例 |
|---|---|
| Battery Level 通常不能由 Client 写 | Client 写电量 |
| 只读 characteristic | Device Information |
| Server 状态不允许写 | 只在特定状态可写 |
| Descriptor 不允许写 | 某些只读 descriptor |
示例:
text
ATT_WRITE_REQ(handle = 0x0012, value = 0x64)
ATT_ERROR_RSP(error = Write Not Permitted)
7.4 0x04 Invalid PDU
含义:
Attribute PDU 格式无效。
常见触发:
| 场景 | 示例 |
|---|---|
| PDU 长度错误 | ATT_READ_REQ 少了 handle |
| 参数长度非法 | UUID 长度不是 2 或 16 |
| PDU 超过 ATT_MTU | 收到异常大包 |
| 格式无法解析 | 固定字段不完整 |
前面 3.3 已经讲过:如果 Server 收到 invalid request,例如 PDU 长度错误,应返回 ATT_ERROR_RSP,Error Code 为 Invalid PDU (0x04),Attribute Handle In Error 为 0x0000。
7.5 0x05 Insufficient Authentication
含义:
attribute 需要 authentication 后才能读写。
这里的 authentication 通常意味着安全关系需要达到认证级别,例如防 MITM 的配对方式。
常见场景:
| 场景 | 说明 |
|---|---|
| 需要 MITM protection | Just Works 不够 |
| 需要 authenticated pairing | Passkey / Numeric Comparison |
| 当前链路安全等级太低 | 需要重新配对或升级安全 |
注意:
Insufficient Authentication 不等于没加密。
它更偏"身份/认证等级不够"。
7.6 0x06 Request Not Supported
含义:
ATT Server 不支持收到的 request。
比如 Server 不支持某个可选 PDU。
规范要求:不支持的 request 要返回 ATT_ERROR_RSP,Error Code 为 Request Not Supported (0x06),Attribute Handle In Error 为 0x0000。
注意:
| PDU 类型 | 不支持时怎么处理 |
|---|---|
| Request | 返回 ATT_ERROR_RSP |
| Command | 忽略 |
这点别搞反。
7.7 0x07 Invalid Offset
含义:
指定 offset 超过 attribute 末尾。
常见场景:
| 场景 | 示例 |
|---|---|
ATT_READ_BLOB_REQ offset 过大 |
offset > value length |
ATT_EXECUTE_WRITE_REQ 时 prepare write offset 非法 |
queued writes 不合法 |
| long write offset 超范围 | 写入超过 max length |
ATT_READ_BLOB_REQ 场景中,如果 value offset 大于 attribute value 长度,Server 应返回 Invalid Offset (0x07);如果 offset 等于 value length,则响应中的 part attribute value 长度为 0。
7.8 0x08 Insufficient Authorization
含义:
attribute 需要 authorization 后才能读写。
Authorization 通常是应用级或用户级授权。
例如:
| 场景 | 说明 |
|---|---|
| 用户未授权 App 访问健康数据 | 不允许读 |
| 设备处于锁定状态 | 不允许写配置 |
| Profile 要求用户确认 | 未确认前拒绝 |
| 手机系统权限未授予 | 上层拒绝访问 |
注意和 Authentication 区分:
| 错误 | 重点 |
|---|---|
| Insufficient Authentication | 安全认证等级不够 |
| Insufficient Authorization | 用户/应用授权不够 |
一个是"你是谁没证明够",一个是"你是谁我知道,但你没权限干这事"。
7.9 0x09 Prepare Queue Full
含义:
太多 prepare writes 已经排队。
常见场景:
| 场景 | 说明 |
|---|---|
| Long Write 分片太多 | Prepare Queue 不够 |
| Reliable Write 多属性排队 | 队列资源不足 |
| Client 没有及时 Execute / Cancel | 队列一直占用 |
| Server RAM 限制 | 嵌入式小内存设备常见 |
ATT_PREPARE_WRITE_REQ 场景下,如果 Server 没有足够空间排队该请求,应返回 Prepare Queue Full (0x09)。
7.10 0x0A Attribute Not Found
含义:
给定 handle range 内没有找到 attribute。
常见场景:
| 场景 | 示例 |
|---|---|
| Find Information 指定范围内无属性 | 0x0100--0x0200 没有 attribute |
| Read By Type 找不到指定 UUID | 没有 Battery Level |
| Find By Type Value 找不到服务 | 没有指定 Primary Service |
| Discovery 到结尾 | 用于表示发现完成 |
在 ATT_FIND_INFORMATION_REQ 中,如果指定范围内没有 attribute,Server 应返回 Attribute Not Found (0x0A),并把 Attribute Handle In Error 设置为 Starting Handle。
这类错误在 discovery 里经常不是"异常",而是"找完了"。
7.11 0x0B Attribute Not Long
含义:
attribute 不能用
ATT_READ_BLOB_REQ读取。
典型场景:
| 场景 | 说明 |
|---|---|
fixed length 且长度小于等于 ATT_MTU - 1 |
不需要 blob read |
| 属性不支持 long read | 上层不允许 |
| Client 对短属性发 Read Blob | Server 可返回此错误 |
在 ATT_READ_BLOB_REQ 中,如果 attribute value 是 fixed length 且长度小于等于 (ATT_MTU - 1),Server 可以返回 Attribute Not Long (0x0B)。
7.12 0x0C Encryption Key Size Too Short
含义:
当前用于加密链路的 encryption key size 太短。
常见场景:
| 场景 | 说明 |
|---|---|
| Attribute 要求更高密钥长度 | 当前 key size 不够 |
| 旧设备配对密钥太短 | 安全等级不满足 |
| Profile 要求最小 key size | Server 拒绝访问 |
注意它和 Insufficient Encryption 的区别:
| 错误 | 含义 |
|---|---|
| Insufficient Encryption | 没加密 |
| Encryption Key Size Too Short | 加密了,但密钥长度太短 |
就像门禁:
- 没加密:你没走保密通道;
- key size 太短:你走了保密通道,但安保等级不够。
7.13 0x0D Invalid Attribute Value Length
含义:
本次操作中的 attribute value 长度非法。
常见场景:
| 场景 | 说明 |
|---|---|
| 写固定长度属性时给太长 value | 例如写 4 bytes 到 2-byte CCCD |
| 写变长属性超过最大长度 | 超过 max_len |
| Execute Write 后总长度不合法 | prepare queue 合并后长度错误 |
| Profile 定义 value 长度固定 | Client 写错长度 |
ATT_WRITE_REQ 中,如果变长 value 超过最大有效长度,或者固定长度 attribute 的写入参数长度大于 attribute value 长度,应返回 Invalid Attribute Value Length (0x0D)。
7.14 0x0E Unlikely Error
含义:
请求遇到了不太可能发生的错误,无法完成。
它是一个兜底错误,不应滥用。
适合:
| 场景 | 说明 |
|---|---|
| 内部存储读取失败 | NVM/Flash 异常 |
| 上层 callback 失败 | Service 层返回内部错误 |
| DB 状态异常 | 理论上不该发生 |
| 运行时不可恢复错误 | 但还没到断链程度 |
不适合:
| 场景 | 应用更具体错误 |
|---|---|
| handle 无效 | Invalid Handle |
| 权限不足 | Read/Write Not Permitted |
| 未加密 | Insufficient Encryption |
| offset 错误 | Invalid Offset |
| 长度错误 | Invalid Attribute Value Length |
Unlikely Error 是"兜底保险",不是"垃圾桶"。
啥都扔 0x0E,调试时大家只能一起猜谜。
7.15 0x0F Insufficient Encryption
含义:
attribute 需要 encryption 后才能读写。
常见场景:
| 场景 | 说明 |
|---|---|
| 未加密读安全属性 | Server 拒绝 |
| 未加密写安全配置 | Server 拒绝 |
| Client 需要先发起 pairing/encryption | 然后重试 request |
规范在 security considerations 中也说明:如果 request 发出时 physical link 未认证/未加密,Server 要返回对应错误,Client 可以升级安全后再次发送请求。
7.16 0x10 Unsupported Group Type
含义:
Attribute type 不是上层规范支持的 grouping attribute。
典型场景:
| 场景 | 说明 |
|---|---|
ATT_READ_BY_GROUP_TYPE_REQ 使用了不支持的 group type |
不是 Primary/Secondary Service 等支持类型 |
| Client 把普通 characteristic UUID 当 group type 用 | Server 拒绝 |
| 上层规范没有定义该 grouping | 不支持 |
例如:
text
ATT_READ_BY_GROUP_TYPE_REQ(group type = 0x2A19)
0x2A19 是 Battery Level,不是 grouping attribute。
Server 可以返回:
text
Unsupported Group Type
7.17 0x11 Insufficient Resources
含义:
Server 资源不足,无法完成请求。
常见场景:
| 场景 | 说明 |
|---|---|
| 无 response buffer | 无法构造响应 |
| 内存不足 | malloc / pool 失败 |
| 临时 snapshot 创建失败 | long read |
| 队列资源不足 | 但 prepare queue 有专门错误码 |
| 多 bearer 并发太多 | 状态资源用尽 |
规范在 3.3 中也说:如果 Server 没有足够资源处理 request,应返回 Insufficient Resources (0x11),Attribute Handle In Error 为 0x0000。
7.18 0x12 Database Out Of Sync
含义:
Server 请求 Client 重新发现数据库。
典型与 GATT caching 相关。
常见场景:
| 场景 | 说明 |
|---|---|
| Client 使用了旧 GATT cache | Server 数据库已变 |
| Robust Caching 机制要求重发现 | Client change-unaware |
| Attribute Database Hash 变化 | 旧 handle 不可信 |
| Server 要求 Client rediscover | 返回此错误 |
收到这个错误后,Client 不应该继续死磕旧 handle。
正确方向是重新 discovery。
通俗讲:
你拿着旧地图找 301,结果楼已经改造了。物业说:别找了,重新拿地图。
7.19 0x13 Value Not Allowed
含义:
attribute 参数值不被允许。
常见场景:
| 场景 | 说明 |
|---|---|
| 写入的枚举值非法 | 只允许 0/1,Client 写 3 |
| 配置参数超范围 | interval、mode 不允许 |
| 控制点参数不符合状态 | 当前状态下不允许 |
| 上层规范约束 value | value 格式合法但值非法 |
它和 Invalid Attribute Value Length 区别:
| 错误 | 重点 |
|---|---|
| Invalid Attribute Value Length | 长度不对 |
| Value Not Allowed | 长度可以,但值不允许 |
7.20 0x80--0x9F Application Error
含义:
上层规范定义的应用错误码。
例如某个 Profile 或 Service 定义:
| Error Code | 业务含义 |
|---|---|
0x80 |
Op Code Not Supported |
0x81 |
Invalid Parameter |
0x82 |
Operation Failed |
0x83 |
Procedure Already In Progress |
这些不是 ATT 通用错误,而是上层业务错误。
7.21 0xE0--0xFF Common Profile and Service Error Codes
含义:
通用 Profile / Service 错误码,由相关通用规范定义。
它们属于更上层 Profile/Service 语义,不是 ATT 自己的基础错误。
8. 多个错误同时适用怎么办?
规范说明:
如果多个 error code 都适用,发送哪个 error code 是 vendor-specific。
8.1 举例
Client 发:
text
ATT_READ_REQ(handle = 0x0101)
假设这个 handle:
- 存在;
- 不能读;
- 同时也要求加密。
那么可能同时适用:
| 错误 | 原因 |
|---|---|
| Read Not Permitted | 不允许读 |
| Insufficient Encryption | 当前未加密 |
发哪个由厂商实现决定。
8.2 工程建议
虽然规范说 vendor-specific,但建议按清晰、安全的顺序处理。
一个常见顺序:
#mermaid-svg-s3VPh1ggciDs5hqW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s3VPh1ggciDs5hqW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s3VPh1ggciDs5hqW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s3VPh1ggciDs5hqW .error-icon{fill:#552222;}#mermaid-svg-s3VPh1ggciDs5hqW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s3VPh1ggciDs5hqW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s3VPh1ggciDs5hqW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s3VPh1ggciDs5hqW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s3VPh1ggciDs5hqW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s3VPh1ggciDs5hqW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s3VPh1ggciDs5hqW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s3VPh1ggciDs5hqW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s3VPh1ggciDs5hqW .marker.cross{stroke:#333333;}#mermaid-svg-s3VPh1ggciDs5hqW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s3VPh1ggciDs5hqW p{margin:0;}#mermaid-svg-s3VPh1ggciDs5hqW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-s3VPh1ggciDs5hqW .cluster-label text{fill:#333;}#mermaid-svg-s3VPh1ggciDs5hqW .cluster-label span{color:#333;}#mermaid-svg-s3VPh1ggciDs5hqW .cluster-label span p{background-color:transparent;}#mermaid-svg-s3VPh1ggciDs5hqW .label text,#mermaid-svg-s3VPh1ggciDs5hqW span{fill:#333;color:#333;}#mermaid-svg-s3VPh1ggciDs5hqW .node rect,#mermaid-svg-s3VPh1ggciDs5hqW .node circle,#mermaid-svg-s3VPh1ggciDs5hqW .node ellipse,#mermaid-svg-s3VPh1ggciDs5hqW .node polygon,#mermaid-svg-s3VPh1ggciDs5hqW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s3VPh1ggciDs5hqW .rough-node .label text,#mermaid-svg-s3VPh1ggciDs5hqW .node .label text,#mermaid-svg-s3VPh1ggciDs5hqW .image-shape .label,#mermaid-svg-s3VPh1ggciDs5hqW .icon-shape .label{text-anchor:middle;}#mermaid-svg-s3VPh1ggciDs5hqW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s3VPh1ggciDs5hqW .rough-node .label,#mermaid-svg-s3VPh1ggciDs5hqW .node .label,#mermaid-svg-s3VPh1ggciDs5hqW .image-shape .label,#mermaid-svg-s3VPh1ggciDs5hqW .icon-shape .label{text-align:center;}#mermaid-svg-s3VPh1ggciDs5hqW .node.clickable{cursor:pointer;}#mermaid-svg-s3VPh1ggciDs5hqW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s3VPh1ggciDs5hqW .arrowheadPath{fill:#333333;}#mermaid-svg-s3VPh1ggciDs5hqW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s3VPh1ggciDs5hqW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s3VPh1ggciDs5hqW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s3VPh1ggciDs5hqW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s3VPh1ggciDs5hqW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s3VPh1ggciDs5hqW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s3VPh1ggciDs5hqW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s3VPh1ggciDs5hqW .cluster text{fill:#333;}#mermaid-svg-s3VPh1ggciDs5hqW .cluster span{color:#333;}#mermaid-svg-s3VPh1ggciDs5hqW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s3VPh1ggciDs5hqW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s3VPh1ggciDs5hqW rect.text{fill:none;stroke-width:0;}#mermaid-svg-s3VPh1ggciDs5hqW .icon-shape,#mermaid-svg-s3VPh1ggciDs5hqW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s3VPh1ggciDs5hqW .icon-shape p,#mermaid-svg-s3VPh1ggciDs5hqW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s3VPh1ggciDs5hqW .icon-shape .label rect,#mermaid-svg-s3VPh1ggciDs5hqW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s3VPh1ggciDs5hqW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s3VPh1ggciDs5hqW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s3VPh1ggciDs5hqW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
Receive Request
PDU format valid?
Handle valid?
Operation permitted?
Authorization OK?
Authentication OK?
Encryption OK?
Key size OK?
Value length/value OK?
Success Response
ATT_ERROR_RSP
实际顺序要结合你的安全策略和规范细节。
尤其安全相关错误,不要泄露过多数据库信息。
9. 收到未知 Error Code 怎么办?
规范说明:
如果 Client 收到一个不理解的
Error Code,例如未来版本使用了以前 reserved 的值,那么这个ATT_ERROR_RSP仍应被认为表示该 request 因未知原因不能执行。
9.1 工程含义
Client 不要因为不认识 error code 就崩溃。
错误处理:
c
switch (err) {
case ATT_ERR_INVALID_HANDLE:
...
break;
default:
assert(0); // 错
}
正确方向:
c
switch (err) {
case ATT_ERR_INVALID_HANDLE:
...
break;
case ATT_ERR_READ_NOT_PERMITTED:
...
break;
default:
procedure_fail_unknown_reason(err);
break;
}
9.2 为什么?
协议会演进。
未来版本可能定义新的错误码。
旧 Client 不认识,也要能优雅失败。
这就像你看不懂外地话,也不能当场蓝屏。
10. 发送 ATT_ERROR_RSP 后是否应该断开连接?
规范 note 说:
发送
ATT_ERROR_RSP不应导致 ATT Server 与 Client 断开;Client 可能升级安全后重试,Server 应给 Client 足够时间完成安全升级。
10.1 工程意义
不要这样:
c
att_send_error_rsp(...);
disconnect();
这太暴躁。
尤其是这些错误:
| 错误 | Client 可能怎么恢复 |
|---|---|
| Insufficient Authentication | 发起配对或提升认证 |
| Insufficient Encryption | 启动加密后重试 |
| Encryption Key Size Too Short | 重新配对协商更大 key size |
| Insufficient Authorization | 用户授权后重试 |
| Database Out Of Sync | 重新 discovery 后重试 |
10.2 类比
Server 返回错误,就像前台说:
"材料不全,回去补。"
不是:
"材料不全,保安,把他叉出去。"
蓝牙要互操作,脾气不能太冲。
11. 安全相关错误怎么处理?
11.1 安全错误总表
| Error Code | 含义 | Client 后续动作 |
|---|---|---|
0x05 Insufficient Authentication |
认证等级不够 | 触发 authenticated pairing / MITM |
0x08 Insufficient Authorization |
授权不够 | 请求用户或应用授权 |
0x0C Encryption Key Size Too Short |
密钥长度不够 | 重新配对或提高 key size |
0x0F Insufficient Encryption |
未加密 | 启动加密或配对 |
11.2 流程图
Server Client Server Client #mermaid-svg-ayuxDsYJ8KxsjXuu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ayuxDsYJ8KxsjXuu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ayuxDsYJ8KxsjXuu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ayuxDsYJ8KxsjXuu .error-icon{fill:#552222;}#mermaid-svg-ayuxDsYJ8KxsjXuu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ayuxDsYJ8KxsjXuu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ayuxDsYJ8KxsjXuu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ayuxDsYJ8KxsjXuu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ayuxDsYJ8KxsjXuu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ayuxDsYJ8KxsjXuu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ayuxDsYJ8KxsjXuu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ayuxDsYJ8KxsjXuu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ayuxDsYJ8KxsjXuu .marker.cross{stroke:#333333;}#mermaid-svg-ayuxDsYJ8KxsjXuu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ayuxDsYJ8KxsjXuu p{margin:0;}#mermaid-svg-ayuxDsYJ8KxsjXuu .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ayuxDsYJ8KxsjXuu text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-ayuxDsYJ8KxsjXuu .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ayuxDsYJ8KxsjXuu .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-ayuxDsYJ8KxsjXuu .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-ayuxDsYJ8KxsjXuu .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-ayuxDsYJ8KxsjXuu #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-ayuxDsYJ8KxsjXuu .sequenceNumber{fill:white;}#mermaid-svg-ayuxDsYJ8KxsjXuu #sequencenumber{fill:#333;}#mermaid-svg-ayuxDsYJ8KxsjXuu #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-ayuxDsYJ8KxsjXuu .messageText{fill:#333;stroke:none;}#mermaid-svg-ayuxDsYJ8KxsjXuu .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ayuxDsYJ8KxsjXuu .labelText,#mermaid-svg-ayuxDsYJ8KxsjXuu .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-ayuxDsYJ8KxsjXuu .loopText,#mermaid-svg-ayuxDsYJ8KxsjXuu .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-ayuxDsYJ8KxsjXuu .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-ayuxDsYJ8KxsjXuu .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ayuxDsYJ8KxsjXuu .noteText,#mermaid-svg-ayuxDsYJ8KxsjXuu .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-ayuxDsYJ8KxsjXuu .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ayuxDsYJ8KxsjXuu .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ayuxDsYJ8KxsjXuu .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-ayuxDsYJ8KxsjXuu .actorPopupMenu{position:absolute;}#mermaid-svg-ayuxDsYJ8KxsjXuu .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-ayuxDsYJ8KxsjXuu .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-ayuxDsYJ8KxsjXuu .actor-man circle,#mermaid-svg-ayuxDsYJ8KxsjXuu line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-ayuxDsYJ8KxsjXuu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT_READ_REQ(protected handle)ATT_ERROR_RSP(Insufficient Encryption)Initiate pairing / encryptionATT_READ_REQ(protected handle)ATT_READ_RSP(value)
11.3 Server 侧不要断链
安全不足是可恢复错误。
Server 应给 Client 时间提升安全后重试。
12. ATT_ERROR_RSP 和 Command 的边界
12.1 Command 不生成 ATT_ERROR_RSP
规范明确:ATT_WRITE_CMD 和 ATT_SIGNED_WRITE_CMD 不生成 ATT_ERROR_RSP。
所以:
text
ATT_WRITE_CMD(handle = 0x9999)
即使 handle 无效,Server 也不会回:
text
ATT_ERROR_RSP(Invalid Handle)
它通常会 ignore。
12.2 Write Command 失败怎么知道?
ATT 层不知道。
如果业务需要知道失败,应该:
| 方案 | 说明 |
|---|---|
用 ATT_WRITE_REQ |
有 ATT_WRITE_RSP 或 ATT_ERROR_RSP |
| 用 Control Point + Indication | Server 业务层回结果 |
| 自定义 ACK characteristic | Client 写,Server notify ACK |
| 上层序列号/重传 | OTA 常用 |
12.3 示例
错误期待:
text
Client -> Server: ATT_WRITE_CMD
Server -> Client: ATT_ERROR_RSP
这是不该出现的。
正确理解:
text
Client -> Server: ATT_WRITE_CMD
Server: 成功处理或忽略
13. ATT_ERROR_RSP 和 GATT Procedure
GATT Procedure 基本都要处理 ATT_ERROR_RSP。GATT 映射表中,Exchange MTU、Primary Service Discovery、Characteristic Discovery、Read、Write 等 procedure 都包含 ATT_ERROR_RSP 作为可能的结果。
13.1 常见 GATT Procedure 对应错误
| GATT Procedure | 常见 ATT Error |
|---|---|
| Discover All Primary Services | Attribute Not Found |
| Discover Characteristics | Attribute Not Found / Read Not Permitted |
| Read Characteristic Value | Invalid Handle / Read Not Permitted / Security errors |
| Write Characteristic Value | Write Not Permitted / Invalid Attribute Value Length / Security errors |
| Read Long Characteristic Value | Invalid Offset / Attribute Not Long |
| Write Long Characteristic Value | Prepare Queue Full / Invalid Attribute Value Length |
| Reliable Writes | Prepare Queue Full / Application Error |
| Service Changed / Robust Caching | Database Out Of Sync |
13.2 Discovery 里的 Attribute Not Found 不一定是失败
比如 Discover All Primary Services:
text
ATT_READ_BY_GROUP_TYPE_REQ(start=next, end=0xFFFF)
ATT_ERROR_RSP(Attribute Not Found)
这通常表示:
发现流程到结尾了。
不是异常。
13.3 Read/Write 里的错误多半是真错误
比如:
text
ATT_READ_REQ(handle=0x0012)
ATT_ERROR_RSP(Read Not Permitted)
这说明 Client 做了不被允许的操作,应该调整流程或提升安全。
14. ATT_ERROR_RSP 抓包解析
14.1 示例一:Invalid Handle
raw bytes:
text
01 0A 99 99 01
解析:
| 字节 | 含义 |
|---|---|
01 |
ATT_ERROR_RSP |
0A |
Request Opcode In Error = ATT_READ_REQ |
99 99 |
Attribute Handle In Error = 0x9999 |
01 |
Error Code = Invalid Handle |
14.2 示例二:Read Not Permitted
text
01 0A 01 01 02
解释:
| 字段 | 值 |
|---|---|
| Error Response | 0x01 |
| 出错请求 | ATT_READ_REQ |
| 出错 handle | 0x0101 |
| 错误码 | 0x02 Read Not Permitted |
14.3 示例三:Request Not Supported
text
01 20 00 00 06
解释:
| 字段 | 值 |
|---|---|
| Error Response | 0x01 |
| 出错请求 | 0x20 |
| Handle In Error | 0x0000 |
| 错误码 | 0x06 Request Not Supported |
这里 handle 是 0x0000,因为 request 不支持或无法定位具体 handle。
14.4 示例四:Insufficient Encryption
text
01 0A 05 00 0F
解释:
| 字段 | 值 |
|---|---|
| 出错请求 | ATT_READ_REQ |
| 出错 handle | 0x0005 |
| 错误码 | 0x0F Insufficient Encryption |
Client 收到后,应启动加密或配对,然后重试。
15. Server 发送 ATT_ERROR_RSP 的基本流程
#mermaid-svg-65X3fmWL5BEqwKtS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-65X3fmWL5BEqwKtS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-65X3fmWL5BEqwKtS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-65X3fmWL5BEqwKtS .error-icon{fill:#552222;}#mermaid-svg-65X3fmWL5BEqwKtS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-65X3fmWL5BEqwKtS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-65X3fmWL5BEqwKtS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-65X3fmWL5BEqwKtS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-65X3fmWL5BEqwKtS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-65X3fmWL5BEqwKtS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-65X3fmWL5BEqwKtS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-65X3fmWL5BEqwKtS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-65X3fmWL5BEqwKtS .marker.cross{stroke:#333333;}#mermaid-svg-65X3fmWL5BEqwKtS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-65X3fmWL5BEqwKtS p{margin:0;}#mermaid-svg-65X3fmWL5BEqwKtS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-65X3fmWL5BEqwKtS .cluster-label text{fill:#333;}#mermaid-svg-65X3fmWL5BEqwKtS .cluster-label span{color:#333;}#mermaid-svg-65X3fmWL5BEqwKtS .cluster-label span p{background-color:transparent;}#mermaid-svg-65X3fmWL5BEqwKtS .label text,#mermaid-svg-65X3fmWL5BEqwKtS span{fill:#333;color:#333;}#mermaid-svg-65X3fmWL5BEqwKtS .node rect,#mermaid-svg-65X3fmWL5BEqwKtS .node circle,#mermaid-svg-65X3fmWL5BEqwKtS .node ellipse,#mermaid-svg-65X3fmWL5BEqwKtS .node polygon,#mermaid-svg-65X3fmWL5BEqwKtS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-65X3fmWL5BEqwKtS .rough-node .label text,#mermaid-svg-65X3fmWL5BEqwKtS .node .label text,#mermaid-svg-65X3fmWL5BEqwKtS .image-shape .label,#mermaid-svg-65X3fmWL5BEqwKtS .icon-shape .label{text-anchor:middle;}#mermaid-svg-65X3fmWL5BEqwKtS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-65X3fmWL5BEqwKtS .rough-node .label,#mermaid-svg-65X3fmWL5BEqwKtS .node .label,#mermaid-svg-65X3fmWL5BEqwKtS .image-shape .label,#mermaid-svg-65X3fmWL5BEqwKtS .icon-shape .label{text-align:center;}#mermaid-svg-65X3fmWL5BEqwKtS .node.clickable{cursor:pointer;}#mermaid-svg-65X3fmWL5BEqwKtS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-65X3fmWL5BEqwKtS .arrowheadPath{fill:#333333;}#mermaid-svg-65X3fmWL5BEqwKtS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-65X3fmWL5BEqwKtS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-65X3fmWL5BEqwKtS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-65X3fmWL5BEqwKtS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-65X3fmWL5BEqwKtS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-65X3fmWL5BEqwKtS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-65X3fmWL5BEqwKtS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-65X3fmWL5BEqwKtS .cluster text{fill:#333;}#mermaid-svg-65X3fmWL5BEqwKtS .cluster span{color:#333;}#mermaid-svg-65X3fmWL5BEqwKtS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-65X3fmWL5BEqwKtS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-65X3fmWL5BEqwKtS rect.text{fill:none;stroke-width:0;}#mermaid-svg-65X3fmWL5BEqwKtS .icon-shape,#mermaid-svg-65X3fmWL5BEqwKtS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-65X3fmWL5BEqwKtS .icon-shape p,#mermaid-svg-65X3fmWL5BEqwKtS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-65X3fmWL5BEqwKtS .icon-shape .label rect,#mermaid-svg-65X3fmWL5BEqwKtS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-65X3fmWL5BEqwKtS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-65X3fmWL5BEqwKtS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-65X3fmWL5BEqwKtS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
No
Yes
Receive ATT Request
PDU format valid?
Request supported?
Handle field exists?
Handle valid?
Permission OK?
Security OK?
Parameter/value OK?
Send Success Response
Send ATT_ERROR_RSP
15.1 发送函数
c
void att_send_error_rsp(att_bearer_t *bearer,
uint8_t request_opcode,
uint16_t handle_in_error,
uint8_t error_code)
{
uint8_t pdu[5];
pdu[0] = ATT_ERROR_RSP;
pdu[1] = request_opcode;
write_le16(&pdu[2], handle_in_error);
pdu[4] = error_code;
l2cap_send_att_pdu(bearer->cid, pdu, sizeof(pdu));
}
15.2 常用宏
c
#define ATT_ERROR_RSP 0x01
#define ATT_ERR_INVALID_HANDLE 0x01
#define ATT_ERR_READ_NOT_PERMITTED 0x02
#define ATT_ERR_WRITE_NOT_PERMITTED 0x03
#define ATT_ERR_INVALID_PDU 0x04
#define ATT_ERR_INSUFFICIENT_AUTHENTICATION 0x05
#define ATT_ERR_REQUEST_NOT_SUPPORTED 0x06
#define ATT_ERR_INVALID_OFFSET 0x07
#define ATT_ERR_INSUFFICIENT_AUTHORIZATION 0x08
#define ATT_ERR_PREPARE_QUEUE_FULL 0x09
#define ATT_ERR_ATTRIBUTE_NOT_FOUND 0x0A
#define ATT_ERR_ATTRIBUTE_NOT_LONG 0x0B
#define ATT_ERR_ENCRYPTION_KEY_SIZE_TOO_SHORT 0x0C
#define ATT_ERR_INVALID_ATTRIBUTE_VALUE_LEN 0x0D
#define ATT_ERR_UNLIKELY_ERROR 0x0E
#define ATT_ERR_INSUFFICIENT_ENCRYPTION 0x0F
#define ATT_ERR_UNSUPPORTED_GROUP_TYPE 0x10
#define ATT_ERR_INSUFFICIENT_RESOURCES 0x11
#define ATT_ERR_DATABASE_OUT_OF_SYNC 0x12
#define ATT_ERR_VALUE_NOT_ALLOWED 0x13
16. Client 处理 ATT_ERROR_RSP 的基本流程
#mermaid-svg-o3dDyfqu1fqCKZ2E{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-o3dDyfqu1fqCKZ2E .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-o3dDyfqu1fqCKZ2E .error-icon{fill:#552222;}#mermaid-svg-o3dDyfqu1fqCKZ2E .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-o3dDyfqu1fqCKZ2E .marker{fill:#333333;stroke:#333333;}#mermaid-svg-o3dDyfqu1fqCKZ2E .marker.cross{stroke:#333333;}#mermaid-svg-o3dDyfqu1fqCKZ2E svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-o3dDyfqu1fqCKZ2E p{margin:0;}#mermaid-svg-o3dDyfqu1fqCKZ2E .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-o3dDyfqu1fqCKZ2E .cluster-label text{fill:#333;}#mermaid-svg-o3dDyfqu1fqCKZ2E .cluster-label span{color:#333;}#mermaid-svg-o3dDyfqu1fqCKZ2E .cluster-label span p{background-color:transparent;}#mermaid-svg-o3dDyfqu1fqCKZ2E .label text,#mermaid-svg-o3dDyfqu1fqCKZ2E span{fill:#333;color:#333;}#mermaid-svg-o3dDyfqu1fqCKZ2E .node rect,#mermaid-svg-o3dDyfqu1fqCKZ2E .node circle,#mermaid-svg-o3dDyfqu1fqCKZ2E .node ellipse,#mermaid-svg-o3dDyfqu1fqCKZ2E .node polygon,#mermaid-svg-o3dDyfqu1fqCKZ2E .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-o3dDyfqu1fqCKZ2E .rough-node .label text,#mermaid-svg-o3dDyfqu1fqCKZ2E .node .label text,#mermaid-svg-o3dDyfqu1fqCKZ2E .image-shape .label,#mermaid-svg-o3dDyfqu1fqCKZ2E .icon-shape .label{text-anchor:middle;}#mermaid-svg-o3dDyfqu1fqCKZ2E .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-o3dDyfqu1fqCKZ2E .rough-node .label,#mermaid-svg-o3dDyfqu1fqCKZ2E .node .label,#mermaid-svg-o3dDyfqu1fqCKZ2E .image-shape .label,#mermaid-svg-o3dDyfqu1fqCKZ2E .icon-shape .label{text-align:center;}#mermaid-svg-o3dDyfqu1fqCKZ2E .node.clickable{cursor:pointer;}#mermaid-svg-o3dDyfqu1fqCKZ2E .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-o3dDyfqu1fqCKZ2E .arrowheadPath{fill:#333333;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-o3dDyfqu1fqCKZ2E .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o3dDyfqu1fqCKZ2E .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-o3dDyfqu1fqCKZ2E .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o3dDyfqu1fqCKZ2E .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-o3dDyfqu1fqCKZ2E .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-o3dDyfqu1fqCKZ2E .cluster text{fill:#333;}#mermaid-svg-o3dDyfqu1fqCKZ2E .cluster span{color:#333;}#mermaid-svg-o3dDyfqu1fqCKZ2E div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-o3dDyfqu1fqCKZ2E .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-o3dDyfqu1fqCKZ2E rect.text{fill:none;stroke-width:0;}#mermaid-svg-o3dDyfqu1fqCKZ2E .icon-shape,#mermaid-svg-o3dDyfqu1fqCKZ2E .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-o3dDyfqu1fqCKZ2E .icon-shape p,#mermaid-svg-o3dDyfqu1fqCKZ2E .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-o3dDyfqu1fqCKZ2E .icon-shape .label rect,#mermaid-svg-o3dDyfqu1fqCKZ2E .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-o3dDyfqu1fqCKZ2E .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-o3dDyfqu1fqCKZ2E .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-o3dDyfqu1fqCKZ2E :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} No
Yes
Yes
No
Receive ATT_ERROR_RSP
Parse request opcode, handle, error code
Matches pending request?
Known error code?
Dispatch to GATT procedure
Treat as unknown failure reason
Unexpected error response
16.1 Client 侧伪代码
c
void att_client_handle_error_rsp(att_connection_t *conn,
att_bearer_t *bearer,
const uint8_t *pdu,
uint16_t len)
{
uint8_t req_opcode;
uint16_t handle;
uint8_t err;
if (len != 5) {
return;
}
req_opcode = pdu[1];
handle = read_le16(&pdu[2]);
err = pdu[4];
if (!bearer->request_pending ||
bearer->pending_req_opcode != req_opcode) {
att_log_unexpected_error_rsp(bearer, req_opcode, handle, err);
return;
}
bearer->request_pending = false;
att_stop_transaction_timer(bearer);
switch (err) {
case ATT_ERR_INSUFFICIENT_ENCRYPTION:
case ATT_ERR_INSUFFICIENT_AUTHENTICATION:
case ATT_ERR_ENCRYPTION_KEY_SIZE_TOO_SHORT:
gatt_security_upgrade_and_retry(conn, bearer, req_opcode, handle, err);
break;
case ATT_ERR_DATABASE_OUT_OF_SYNC:
gatt_mark_cache_invalid(conn);
gatt_restart_discovery(conn);
break;
default:
gatt_procedure_fail(conn, bearer, req_opcode, handle, err);
break;
}
}
17. 错误码与恢复策略
| Error Code | Client 恢复策略 |
|---|---|
0x01 Invalid Handle |
检查 GATT cache,必要时重新 discovery |
0x02 Read Not Permitted |
不要再读该属性,检查 properties / permissions |
0x03 Write Not Permitted |
不要写该属性,检查 properties / permissions |
0x04 Invalid PDU |
检查本地 PDU 格式和长度 |
0x05 Insufficient Authentication |
发起更高安全等级配对 |
0x06 Request Not Supported |
降级或禁用该 optional feature |
0x07 Invalid Offset |
检查 long read/write offset |
0x08 Insufficient Authorization |
请求用户或应用授权 |
0x09 Prepare Queue Full |
减少 queued writes 或重新开始 |
0x0A Attribute Not Found |
discovery 流程中通常表示当前范围结束 |
0x0B Attribute Not Long |
不再用 Read Blob 读该 attribute |
0x0C Encryption Key Size Too Short |
重新配对,提高 key size |
0x0D Invalid Attribute Value Length |
检查写入长度 |
0x0E Unlikely Error |
记录日志,可能重试或上报失败 |
0x0F Insufficient Encryption |
启动加密后重试 |
0x10 Unsupported Group Type |
检查 group type 是否正确 |
0x11 Insufficient Resources |
延后重试或降低并发 |
0x12 Database Out Of Sync |
清 cache,重新发现 |
0x13 Value Not Allowed |
检查参数值范围 |
0x80--0x9F |
按上层 Service/Profile 解释 |
0xE0--0xFF |
按通用 Profile/Service 错误解释 |
| 未知错误码 | 当作未知原因失败处理 |
18. Error Response 与 Transaction
18.1 ATT_ERROR_RSP 也是 response
在 request-response transaction 中:
text
ATT_ERROR_RSP 是 response 的一种。
所以 Client 收到 ATT_ERROR_RSP 后,当前 request transaction 完成,只是结果是失败。
Server Client Server Client #mermaid-svg-2K8j5rlzULD2uv4A{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2K8j5rlzULD2uv4A .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2K8j5rlzULD2uv4A .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2K8j5rlzULD2uv4A .error-icon{fill:#552222;}#mermaid-svg-2K8j5rlzULD2uv4A .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2K8j5rlzULD2uv4A .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2K8j5rlzULD2uv4A .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2K8j5rlzULD2uv4A .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2K8j5rlzULD2uv4A .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2K8j5rlzULD2uv4A .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2K8j5rlzULD2uv4A .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2K8j5rlzULD2uv4A .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2K8j5rlzULD2uv4A .marker.cross{stroke:#333333;}#mermaid-svg-2K8j5rlzULD2uv4A svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2K8j5rlzULD2uv4A p{margin:0;}#mermaid-svg-2K8j5rlzULD2uv4A .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2K8j5rlzULD2uv4A text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-2K8j5rlzULD2uv4A .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2K8j5rlzULD2uv4A .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-2K8j5rlzULD2uv4A .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-2K8j5rlzULD2uv4A .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-2K8j5rlzULD2uv4A #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-2K8j5rlzULD2uv4A .sequenceNumber{fill:white;}#mermaid-svg-2K8j5rlzULD2uv4A #sequencenumber{fill:#333;}#mermaid-svg-2K8j5rlzULD2uv4A #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-2K8j5rlzULD2uv4A .messageText{fill:#333;stroke:none;}#mermaid-svg-2K8j5rlzULD2uv4A .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2K8j5rlzULD2uv4A .labelText,#mermaid-svg-2K8j5rlzULD2uv4A .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-2K8j5rlzULD2uv4A .loopText,#mermaid-svg-2K8j5rlzULD2uv4A .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-2K8j5rlzULD2uv4A .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2K8j5rlzULD2uv4A .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-2K8j5rlzULD2uv4A .noteText,#mermaid-svg-2K8j5rlzULD2uv4A .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-2K8j5rlzULD2uv4A .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2K8j5rlzULD2uv4A .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2K8j5rlzULD2uv4A .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2K8j5rlzULD2uv4A .actorPopupMenu{position:absolute;}#mermaid-svg-2K8j5rlzULD2uv4A .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-2K8j5rlzULD2uv4A .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2K8j5rlzULD2uv4A .actor-man circle,#mermaid-svg-2K8j5rlzULD2uv4A line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-2K8j5rlzULD2uv4A :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Transaction complete with error ATT_READ_REQATT_ERROR_RSP
18.2 不要等第二个 response
错误逻辑:
text
收到 ATT_ERROR_RSP 后继续等 ATT_READ_RSP
不对。
ATT_ERROR_RSP 已经结束这个 transaction。
18.3 Bearer 是否还能继续使用?
一般情况下,发送 ATT_ERROR_RSP 不应导致断开,Client 可以安全升级后重试。
但是如果发生 transaction timeout,则是另一回事:timeout 后该 bearer 不能继续用。这是 3.3.3 的 transaction timeout 规则,不是 ATT_ERROR_RSP 的规则。
19. Error Handling 与安全升级流程
19.1 未加密访问
Server Client Server Client #mermaid-svg-BfD4rmYlGvABFhAX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BfD4rmYlGvABFhAX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BfD4rmYlGvABFhAX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BfD4rmYlGvABFhAX .error-icon{fill:#552222;}#mermaid-svg-BfD4rmYlGvABFhAX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BfD4rmYlGvABFhAX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BfD4rmYlGvABFhAX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BfD4rmYlGvABFhAX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BfD4rmYlGvABFhAX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BfD4rmYlGvABFhAX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BfD4rmYlGvABFhAX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BfD4rmYlGvABFhAX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BfD4rmYlGvABFhAX .marker.cross{stroke:#333333;}#mermaid-svg-BfD4rmYlGvABFhAX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BfD4rmYlGvABFhAX p{margin:0;}#mermaid-svg-BfD4rmYlGvABFhAX .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BfD4rmYlGvABFhAX text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-BfD4rmYlGvABFhAX .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BfD4rmYlGvABFhAX .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-BfD4rmYlGvABFhAX .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-BfD4rmYlGvABFhAX .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-BfD4rmYlGvABFhAX #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-BfD4rmYlGvABFhAX .sequenceNumber{fill:white;}#mermaid-svg-BfD4rmYlGvABFhAX #sequencenumber{fill:#333;}#mermaid-svg-BfD4rmYlGvABFhAX #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-BfD4rmYlGvABFhAX .messageText{fill:#333;stroke:none;}#mermaid-svg-BfD4rmYlGvABFhAX .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BfD4rmYlGvABFhAX .labelText,#mermaid-svg-BfD4rmYlGvABFhAX .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-BfD4rmYlGvABFhAX .loopText,#mermaid-svg-BfD4rmYlGvABFhAX .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-BfD4rmYlGvABFhAX .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BfD4rmYlGvABFhAX .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-BfD4rmYlGvABFhAX .noteText,#mermaid-svg-BfD4rmYlGvABFhAX .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-BfD4rmYlGvABFhAX .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BfD4rmYlGvABFhAX .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BfD4rmYlGvABFhAX .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BfD4rmYlGvABFhAX .actorPopupMenu{position:absolute;}#mermaid-svg-BfD4rmYlGvABFhAX .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-BfD4rmYlGvABFhAX .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BfD4rmYlGvABFhAX .actor-man circle,#mermaid-svg-BfD4rmYlGvABFhAX line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-BfD4rmYlGvABFhAX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT_READ_REQ(protected handle)ATT_ERROR_RSP(Insufficient Encryption)Start encryption / pairingATT_READ_REQ(protected handle)ATT_READ_RSP(value)
19.2 认证不足
Server Client Server Client #mermaid-svg-Y4ogZXIckKyaEYTQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Y4ogZXIckKyaEYTQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Y4ogZXIckKyaEYTQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Y4ogZXIckKyaEYTQ .error-icon{fill:#552222;}#mermaid-svg-Y4ogZXIckKyaEYTQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Y4ogZXIckKyaEYTQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Y4ogZXIckKyaEYTQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Y4ogZXIckKyaEYTQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Y4ogZXIckKyaEYTQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Y4ogZXIckKyaEYTQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Y4ogZXIckKyaEYTQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Y4ogZXIckKyaEYTQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Y4ogZXIckKyaEYTQ .marker.cross{stroke:#333333;}#mermaid-svg-Y4ogZXIckKyaEYTQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Y4ogZXIckKyaEYTQ p{margin:0;}#mermaid-svg-Y4ogZXIckKyaEYTQ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Y4ogZXIckKyaEYTQ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-Y4ogZXIckKyaEYTQ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Y4ogZXIckKyaEYTQ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-Y4ogZXIckKyaEYTQ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-Y4ogZXIckKyaEYTQ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-Y4ogZXIckKyaEYTQ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-Y4ogZXIckKyaEYTQ .sequenceNumber{fill:white;}#mermaid-svg-Y4ogZXIckKyaEYTQ #sequencenumber{fill:#333;}#mermaid-svg-Y4ogZXIckKyaEYTQ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-Y4ogZXIckKyaEYTQ .messageText{fill:#333;stroke:none;}#mermaid-svg-Y4ogZXIckKyaEYTQ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Y4ogZXIckKyaEYTQ .labelText,#mermaid-svg-Y4ogZXIckKyaEYTQ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-Y4ogZXIckKyaEYTQ .loopText,#mermaid-svg-Y4ogZXIckKyaEYTQ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-Y4ogZXIckKyaEYTQ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-Y4ogZXIckKyaEYTQ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Y4ogZXIckKyaEYTQ .noteText,#mermaid-svg-Y4ogZXIckKyaEYTQ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-Y4ogZXIckKyaEYTQ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Y4ogZXIckKyaEYTQ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Y4ogZXIckKyaEYTQ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-Y4ogZXIckKyaEYTQ .actorPopupMenu{position:absolute;}#mermaid-svg-Y4ogZXIckKyaEYTQ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-Y4ogZXIckKyaEYTQ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-Y4ogZXIckKyaEYTQ .actor-man circle,#mermaid-svg-Y4ogZXIckKyaEYTQ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-Y4ogZXIckKyaEYTQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT_WRITE_REQ(authenticated handle)ATT_ERROR_RSP(Insufficient Authentication)Pair with MITM protectionATT_WRITE_REQ(authenticated handle)ATT_WRITE_RSP
19.3 授权不足
Server Client Server Client #mermaid-svg-8jI7PewQ0Nd9aFAs{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8jI7PewQ0Nd9aFAs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8jI7PewQ0Nd9aFAs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8jI7PewQ0Nd9aFAs .error-icon{fill:#552222;}#mermaid-svg-8jI7PewQ0Nd9aFAs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8jI7PewQ0Nd9aFAs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8jI7PewQ0Nd9aFAs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8jI7PewQ0Nd9aFAs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8jI7PewQ0Nd9aFAs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8jI7PewQ0Nd9aFAs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8jI7PewQ0Nd9aFAs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8jI7PewQ0Nd9aFAs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8jI7PewQ0Nd9aFAs .marker.cross{stroke:#333333;}#mermaid-svg-8jI7PewQ0Nd9aFAs svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8jI7PewQ0Nd9aFAs p{margin:0;}#mermaid-svg-8jI7PewQ0Nd9aFAs .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8jI7PewQ0Nd9aFAs text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-8jI7PewQ0Nd9aFAs .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8jI7PewQ0Nd9aFAs .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-8jI7PewQ0Nd9aFAs .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-8jI7PewQ0Nd9aFAs .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-8jI7PewQ0Nd9aFAs #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-8jI7PewQ0Nd9aFAs .sequenceNumber{fill:white;}#mermaid-svg-8jI7PewQ0Nd9aFAs #sequencenumber{fill:#333;}#mermaid-svg-8jI7PewQ0Nd9aFAs #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-8jI7PewQ0Nd9aFAs .messageText{fill:#333;stroke:none;}#mermaid-svg-8jI7PewQ0Nd9aFAs .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8jI7PewQ0Nd9aFAs .labelText,#mermaid-svg-8jI7PewQ0Nd9aFAs .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-8jI7PewQ0Nd9aFAs .loopText,#mermaid-svg-8jI7PewQ0Nd9aFAs .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-8jI7PewQ0Nd9aFAs .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-8jI7PewQ0Nd9aFAs .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-8jI7PewQ0Nd9aFAs .noteText,#mermaid-svg-8jI7PewQ0Nd9aFAs .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-8jI7PewQ0Nd9aFAs .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8jI7PewQ0Nd9aFAs .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8jI7PewQ0Nd9aFAs .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-8jI7PewQ0Nd9aFAs .actorPopupMenu{position:absolute;}#mermaid-svg-8jI7PewQ0Nd9aFAs .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-8jI7PewQ0Nd9aFAs .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-8jI7PewQ0Nd9aFAs .actor-man circle,#mermaid-svg-8jI7PewQ0Nd9aFAs line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-8jI7PewQ0Nd9aFAs :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT_READ_REQ(authorized handle)ATT_ERROR_RSP(Insufficient Authorization)Ask user / app permissionATT_READ_REQ(authorized handle)ATT_READ_RSP
20. ATT_ERROR_RSP 和 Robust Caching
Database Out Of Sync (0x12) 是 GATT caching 相关的重要错误。
20.1 含义
Server 请求 Client 重新发现数据库。
20.2 典型流程
GATT Server GATT Client GATT Server GATT Client #mermaid-svg-sutjNcSUd4EXxM9o{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sutjNcSUd4EXxM9o .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sutjNcSUd4EXxM9o .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sutjNcSUd4EXxM9o .error-icon{fill:#552222;}#mermaid-svg-sutjNcSUd4EXxM9o .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sutjNcSUd4EXxM9o .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sutjNcSUd4EXxM9o .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sutjNcSUd4EXxM9o .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sutjNcSUd4EXxM9o .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sutjNcSUd4EXxM9o .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sutjNcSUd4EXxM9o .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sutjNcSUd4EXxM9o .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sutjNcSUd4EXxM9o .marker.cross{stroke:#333333;}#mermaid-svg-sutjNcSUd4EXxM9o svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sutjNcSUd4EXxM9o p{margin:0;}#mermaid-svg-sutjNcSUd4EXxM9o .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sutjNcSUd4EXxM9o text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-sutjNcSUd4EXxM9o .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sutjNcSUd4EXxM9o .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-sutjNcSUd4EXxM9o .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-sutjNcSUd4EXxM9o .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-sutjNcSUd4EXxM9o #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-sutjNcSUd4EXxM9o .sequenceNumber{fill:white;}#mermaid-svg-sutjNcSUd4EXxM9o #sequencenumber{fill:#333;}#mermaid-svg-sutjNcSUd4EXxM9o #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-sutjNcSUd4EXxM9o .messageText{fill:#333;stroke:none;}#mermaid-svg-sutjNcSUd4EXxM9o .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sutjNcSUd4EXxM9o .labelText,#mermaid-svg-sutjNcSUd4EXxM9o .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-sutjNcSUd4EXxM9o .loopText,#mermaid-svg-sutjNcSUd4EXxM9o .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-sutjNcSUd4EXxM9o .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-sutjNcSUd4EXxM9o .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-sutjNcSUd4EXxM9o .noteText,#mermaid-svg-sutjNcSUd4EXxM9o .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-sutjNcSUd4EXxM9o .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sutjNcSUd4EXxM9o .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sutjNcSUd4EXxM9o .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-sutjNcSUd4EXxM9o .actorPopupMenu{position:absolute;}#mermaid-svg-sutjNcSUd4EXxM9o .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-sutjNcSUd4EXxM9o .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-sutjNcSUd4EXxM9o .actor-man circle,#mermaid-svg-sutjNcSUd4EXxM9o line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-sutjNcSUd4EXxM9o :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT_READ_REQ(old cached handle)ATT_ERROR_RSP(Database Out Of Sync)Invalidate GATT cacheRediscover databaseNew handle map
20.3 工程重点
收到 0x12 后不要死磕旧 handle。
错误行为:
text
收到 Database Out Of Sync 后继续用旧 cache 重试同一个 handle
正确行为:
text
清缓存 -> 重新 discovery -> 再访问
21. ATT_ERROR_RSP 不同 PDU 的允许错误码
规范特别提醒:
Section 3.4.2 到 3.4.8 中列出的 Error Code 不一定是每个 PDU 允许的完整列表,真正决定某个 method 允许哪些 error code 的是 Section 3.4.9 的 response summary。
21.1 工程含义
不要简单认为:
text
所有 request 都能返回所有 error code
某些 request 的 error code 是受限制的。
例如 ATT_FIND_INFORMATION_REQ:
- 可以返回
Invalid Handle; - 可以返回
Attribute Not Found; - 但不应返回
Insufficient Authentication、Insufficient Authorization、Encryption Key Size Too Short、Database Out Of Sync、Application Error 等。
21.2 为什么?
因为规范认为:
设备支持的 attribute 列表不被视为私密或机密信息,因此
ATT_FIND_INFORMATION_REQ应始终允许,不应因为认证或授权不足而拒绝。
这点很有意思:
"有哪些门牌号"不是秘密;
"门里面有什么、能不能读写"才可能需要权限。
22. 抓包分析套路
看到 ATT_ERROR_RSP,不要只看 Error Code,要按下面顺序拆。
22.1 四步解析
| 步骤 | 看什么 | 目的 |
|---|---|---|
| 1 | Request Opcode In Error |
哪个 request 失败 |
| 2 | Attribute Handle In Error |
哪个 handle 出问题 |
| 3 | Error Code |
为什么失败 |
| 4 | 前后文 | 是 discovery 正常结束、权限不足,还是 cache 失效 |
22.2 抓包检查表
| 看到的错误 | 先判断 |
|---|---|
Invalid Handle |
是否旧 cache、handle range 错误 |
Attribute Not Found |
是否 discovery 到结尾 |
Read Not Permitted |
是否读了 write-only / control point |
Write Not Permitted |
是否写了 read-only |
Insufficient Encryption |
是否未加密 |
Insufficient Authentication |
是否配对方式不够 |
Invalid Offset |
long read/write offset 是否正确 |
Prepare Queue Full |
queued writes 是否过多 |
Database Out Of Sync |
是否要清 cache 重发现 |
Unlikely Error |
Server 内部问题,查日志 |
23. 工程实现检查清单
23.1 Server 侧
| 检查项 | 说明 |
|---|---|
是否支持发送 ATT_ERROR_RSP |
必须 |
ATT_ERROR_RSP 长度是否固定 5 bytes |
必须 |
Request Opcode In Error 是否填原始 request opcode |
必须 |
Attribute Handle In Error 是否填出错 handle |
必须 |
无 handle / 不支持 request 时是否填 0x0000 |
必须 |
| Error Code 是否符合该 request 允许范围 | 必须 |
command 是否不返回 ATT_ERROR_RSP |
必须 |
| 安全不足是否返回对应安全错误 | 必须 |
| 多个错误同时存在时是否有稳定策略 | 推荐 |
| 发送 error 后是否不断链 | 通常应保持连接 |
23.2 Client 侧
| 检查项 | 说明 |
|---|---|
是否处理 ATT_ERROR_RSP |
必须 |
| 是否匹配 pending request opcode | 必须 |
| 是否匹配 bearer / CID | EATT 必须 |
| 是否解析 handle in error | 必须 |
| 是否识别安全错误并触发安全升级 | 推荐 |
| 是否识别 Database Out Of Sync | GATT caching 必须 |
| 是否把 unknown error 当作未知失败处理 | 必须 |
| 是否避免继续等待成功 response | 必须 |
| discovery 中是否把 Attribute Not Found 当作可能结束条件 | 必须 |
23.3 日志建议
建议打印:
text
ATT_ERROR_RSP:
bearer_cid = 0x0040
request_opcode = 0x0A ATT_READ_REQ
handle_in_error = 0x0012
error_code = 0x0F Insufficient Encryption
gatt_procedure = Read Characteristic Value
不要只打印:
text
ATT error = 15
这种日志像老板发消息只写"有问题",看完血压直接上来了。
24. 常见误区
24.1 误区一:ATT_ERROR_RSP 表示连接异常
不一定。
它通常只是 request 执行失败,是正常协议响应。
24.2 误区二:Command 失败也会返回 ATT_ERROR_RSP
不对。
ATT_WRITE_CMD 和 ATT_SIGNED_WRITE_CMD 不生成 ATT_ERROR_RSP。
24.3 误区三:收到 ATT_ERROR_RSP 后还要等成功 response
不对。
ATT_ERROR_RSP 已经完成这个 transaction。
24.4 误区四:所有 request 都能返回所有 Error Code
不对。
每个 method 允许的 error code 要看 response summary。
24.5 误区五:安全错误应该断链
不对。
Server 应给 Client 时间升级安全并重试。
24.6 误区六:Attribute Not Found 一定是异常
不一定。
在 discovery 中,它经常表示当前范围没有更多结果。
24.7 误区七:Unlikely Error 可以随便用
不建议。
有具体错误就用具体错误。
Unlikely Error 应作为兜底。
25. 本篇核心图
25.1 ATT_ERROR_RSP 格式
#mermaid-svg-0xsWSn3mkwiCn8i3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0xsWSn3mkwiCn8i3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0xsWSn3mkwiCn8i3 .error-icon{fill:#552222;}#mermaid-svg-0xsWSn3mkwiCn8i3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0xsWSn3mkwiCn8i3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0xsWSn3mkwiCn8i3 .marker.cross{stroke:#333333;}#mermaid-svg-0xsWSn3mkwiCn8i3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0xsWSn3mkwiCn8i3 p{margin:0;}#mermaid-svg-0xsWSn3mkwiCn8i3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0xsWSn3mkwiCn8i3 .cluster-label text{fill:#333;}#mermaid-svg-0xsWSn3mkwiCn8i3 .cluster-label span{color:#333;}#mermaid-svg-0xsWSn3mkwiCn8i3 .cluster-label span p{background-color:transparent;}#mermaid-svg-0xsWSn3mkwiCn8i3 .label text,#mermaid-svg-0xsWSn3mkwiCn8i3 span{fill:#333;color:#333;}#mermaid-svg-0xsWSn3mkwiCn8i3 .node rect,#mermaid-svg-0xsWSn3mkwiCn8i3 .node circle,#mermaid-svg-0xsWSn3mkwiCn8i3 .node ellipse,#mermaid-svg-0xsWSn3mkwiCn8i3 .node polygon,#mermaid-svg-0xsWSn3mkwiCn8i3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0xsWSn3mkwiCn8i3 .rough-node .label text,#mermaid-svg-0xsWSn3mkwiCn8i3 .node .label text,#mermaid-svg-0xsWSn3mkwiCn8i3 .image-shape .label,#mermaid-svg-0xsWSn3mkwiCn8i3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-0xsWSn3mkwiCn8i3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0xsWSn3mkwiCn8i3 .rough-node .label,#mermaid-svg-0xsWSn3mkwiCn8i3 .node .label,#mermaid-svg-0xsWSn3mkwiCn8i3 .image-shape .label,#mermaid-svg-0xsWSn3mkwiCn8i3 .icon-shape .label{text-align:center;}#mermaid-svg-0xsWSn3mkwiCn8i3 .node.clickable{cursor:pointer;}#mermaid-svg-0xsWSn3mkwiCn8i3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0xsWSn3mkwiCn8i3 .arrowheadPath{fill:#333333;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0xsWSn3mkwiCn8i3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0xsWSn3mkwiCn8i3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0xsWSn3mkwiCn8i3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0xsWSn3mkwiCn8i3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0xsWSn3mkwiCn8i3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0xsWSn3mkwiCn8i3 .cluster text{fill:#333;}#mermaid-svg-0xsWSn3mkwiCn8i3 .cluster span{color:#333;}#mermaid-svg-0xsWSn3mkwiCn8i3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0xsWSn3mkwiCn8i3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0xsWSn3mkwiCn8i3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-0xsWSn3mkwiCn8i3 .icon-shape,#mermaid-svg-0xsWSn3mkwiCn8i3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0xsWSn3mkwiCn8i3 .icon-shape p,#mermaid-svg-0xsWSn3mkwiCn8i3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0xsWSn3mkwiCn8i3 .icon-shape .label rect,#mermaid-svg-0xsWSn3mkwiCn8i3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0xsWSn3mkwiCn8i3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0xsWSn3mkwiCn8i3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0xsWSn3mkwiCn8i3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT_ERROR_RSP
Opcode
0x01
Request Opcode In Error
Attribute Handle In Error
Error Code
25.2 Request 成功或失败
ATT Server ATT Client ATT Server ATT Client #mermaid-svg-dVb46YfMYn2DhbC9{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dVb46YfMYn2DhbC9 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dVb46YfMYn2DhbC9 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dVb46YfMYn2DhbC9 .error-icon{fill:#552222;}#mermaid-svg-dVb46YfMYn2DhbC9 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dVb46YfMYn2DhbC9 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dVb46YfMYn2DhbC9 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dVb46YfMYn2DhbC9 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dVb46YfMYn2DhbC9 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dVb46YfMYn2DhbC9 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dVb46YfMYn2DhbC9 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dVb46YfMYn2DhbC9 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dVb46YfMYn2DhbC9 .marker.cross{stroke:#333333;}#mermaid-svg-dVb46YfMYn2DhbC9 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dVb46YfMYn2DhbC9 p{margin:0;}#mermaid-svg-dVb46YfMYn2DhbC9 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dVb46YfMYn2DhbC9 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-dVb46YfMYn2DhbC9 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dVb46YfMYn2DhbC9 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-dVb46YfMYn2DhbC9 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-dVb46YfMYn2DhbC9 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-dVb46YfMYn2DhbC9 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-dVb46YfMYn2DhbC9 .sequenceNumber{fill:white;}#mermaid-svg-dVb46YfMYn2DhbC9 #sequencenumber{fill:#333;}#mermaid-svg-dVb46YfMYn2DhbC9 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-dVb46YfMYn2DhbC9 .messageText{fill:#333;stroke:none;}#mermaid-svg-dVb46YfMYn2DhbC9 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dVb46YfMYn2DhbC9 .labelText,#mermaid-svg-dVb46YfMYn2DhbC9 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-dVb46YfMYn2DhbC9 .loopText,#mermaid-svg-dVb46YfMYn2DhbC9 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-dVb46YfMYn2DhbC9 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dVb46YfMYn2DhbC9 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-dVb46YfMYn2DhbC9 .noteText,#mermaid-svg-dVb46YfMYn2DhbC9 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-dVb46YfMYn2DhbC9 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dVb46YfMYn2DhbC9 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dVb46YfMYn2DhbC9 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dVb46YfMYn2DhbC9 .actorPopupMenu{position:absolute;}#mermaid-svg-dVb46YfMYn2DhbC9 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-dVb46YfMYn2DhbC9 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dVb46YfMYn2DhbC9 .actor-man circle,#mermaid-svg-dVb46YfMYn2DhbC9 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-dVb46YfMYn2DhbC9 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt成功失败 ATT_XXX_REQATT_XXX_RSPATT_ERROR_RSP
25.3 安全错误恢复
ATT Server ATT Client ATT Server ATT Client #mermaid-svg-aFK5vXMgxHgu9iij{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aFK5vXMgxHgu9iij .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aFK5vXMgxHgu9iij .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aFK5vXMgxHgu9iij .error-icon{fill:#552222;}#mermaid-svg-aFK5vXMgxHgu9iij .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aFK5vXMgxHgu9iij .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aFK5vXMgxHgu9iij .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aFK5vXMgxHgu9iij .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aFK5vXMgxHgu9iij .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aFK5vXMgxHgu9iij .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aFK5vXMgxHgu9iij .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aFK5vXMgxHgu9iij .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aFK5vXMgxHgu9iij .marker.cross{stroke:#333333;}#mermaid-svg-aFK5vXMgxHgu9iij svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aFK5vXMgxHgu9iij p{margin:0;}#mermaid-svg-aFK5vXMgxHgu9iij .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aFK5vXMgxHgu9iij text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-aFK5vXMgxHgu9iij .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aFK5vXMgxHgu9iij .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-aFK5vXMgxHgu9iij .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-aFK5vXMgxHgu9iij .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-aFK5vXMgxHgu9iij #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-aFK5vXMgxHgu9iij .sequenceNumber{fill:white;}#mermaid-svg-aFK5vXMgxHgu9iij #sequencenumber{fill:#333;}#mermaid-svg-aFK5vXMgxHgu9iij #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-aFK5vXMgxHgu9iij .messageText{fill:#333;stroke:none;}#mermaid-svg-aFK5vXMgxHgu9iij .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aFK5vXMgxHgu9iij .labelText,#mermaid-svg-aFK5vXMgxHgu9iij .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-aFK5vXMgxHgu9iij .loopText,#mermaid-svg-aFK5vXMgxHgu9iij .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-aFK5vXMgxHgu9iij .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aFK5vXMgxHgu9iij .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-aFK5vXMgxHgu9iij .noteText,#mermaid-svg-aFK5vXMgxHgu9iij .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-aFK5vXMgxHgu9iij .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aFK5vXMgxHgu9iij .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aFK5vXMgxHgu9iij .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aFK5vXMgxHgu9iij .actorPopupMenu{position:absolute;}#mermaid-svg-aFK5vXMgxHgu9iij .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-aFK5vXMgxHgu9iij .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aFK5vXMgxHgu9iij .actor-man circle,#mermaid-svg-aFK5vXMgxHgu9iij line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-aFK5vXMgxHgu9iij :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT_READ_REQATT_ERROR_RSP(Insufficient Encryption)Encrypt / PairATT_READ_REQATT_READ_RSP
25.4 错误码分类
#mermaid-svg-eT9xRwcaPI55Iths{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eT9xRwcaPI55Iths .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eT9xRwcaPI55Iths .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eT9xRwcaPI55Iths .error-icon{fill:#552222;}#mermaid-svg-eT9xRwcaPI55Iths .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eT9xRwcaPI55Iths .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eT9xRwcaPI55Iths .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eT9xRwcaPI55Iths .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eT9xRwcaPI55Iths .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eT9xRwcaPI55Iths .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eT9xRwcaPI55Iths .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eT9xRwcaPI55Iths .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eT9xRwcaPI55Iths .marker.cross{stroke:#333333;}#mermaid-svg-eT9xRwcaPI55Iths svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eT9xRwcaPI55Iths p{margin:0;}#mermaid-svg-eT9xRwcaPI55Iths .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eT9xRwcaPI55Iths .cluster-label text{fill:#333;}#mermaid-svg-eT9xRwcaPI55Iths .cluster-label span{color:#333;}#mermaid-svg-eT9xRwcaPI55Iths .cluster-label span p{background-color:transparent;}#mermaid-svg-eT9xRwcaPI55Iths .label text,#mermaid-svg-eT9xRwcaPI55Iths span{fill:#333;color:#333;}#mermaid-svg-eT9xRwcaPI55Iths .node rect,#mermaid-svg-eT9xRwcaPI55Iths .node circle,#mermaid-svg-eT9xRwcaPI55Iths .node ellipse,#mermaid-svg-eT9xRwcaPI55Iths .node polygon,#mermaid-svg-eT9xRwcaPI55Iths .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eT9xRwcaPI55Iths .rough-node .label text,#mermaid-svg-eT9xRwcaPI55Iths .node .label text,#mermaid-svg-eT9xRwcaPI55Iths .image-shape .label,#mermaid-svg-eT9xRwcaPI55Iths .icon-shape .label{text-anchor:middle;}#mermaid-svg-eT9xRwcaPI55Iths .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eT9xRwcaPI55Iths .rough-node .label,#mermaid-svg-eT9xRwcaPI55Iths .node .label,#mermaid-svg-eT9xRwcaPI55Iths .image-shape .label,#mermaid-svg-eT9xRwcaPI55Iths .icon-shape .label{text-align:center;}#mermaid-svg-eT9xRwcaPI55Iths .node.clickable{cursor:pointer;}#mermaid-svg-eT9xRwcaPI55Iths .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eT9xRwcaPI55Iths .arrowheadPath{fill:#333333;}#mermaid-svg-eT9xRwcaPI55Iths .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eT9xRwcaPI55Iths .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eT9xRwcaPI55Iths .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eT9xRwcaPI55Iths .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eT9xRwcaPI55Iths .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eT9xRwcaPI55Iths .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eT9xRwcaPI55Iths .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eT9xRwcaPI55Iths .cluster text{fill:#333;}#mermaid-svg-eT9xRwcaPI55Iths .cluster span{color:#333;}#mermaid-svg-eT9xRwcaPI55Iths div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eT9xRwcaPI55Iths .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eT9xRwcaPI55Iths rect.text{fill:none;stroke-width:0;}#mermaid-svg-eT9xRwcaPI55Iths .icon-shape,#mermaid-svg-eT9xRwcaPI55Iths .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eT9xRwcaPI55Iths .icon-shape p,#mermaid-svg-eT9xRwcaPI55Iths .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eT9xRwcaPI55Iths .icon-shape .label rect,#mermaid-svg-eT9xRwcaPI55Iths .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eT9xRwcaPI55Iths .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eT9xRwcaPI55Iths .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eT9xRwcaPI55Iths :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ATT Error Code
Handle / Range
Permission / Security
PDU / Parameter
Feature Support
Resource / Internal
Database / Application
26. 本篇小结
本篇学习了:
3.4.1 Error handling
3.4.1.1 ATT_ERROR_RSP
核心结论如下:
ATT_ERROR_RSP用于说明某个 request 不能被执行,并给出原因。ATT_ERROR_RSP不由 command 触发。ATT_WRITE_CMD和ATT_SIGNED_WRITE_CMD不生成ATT_ERROR_RSP。ATT_ERROR_RSP的 opcode 是0x01。ATT_ERROR_RSP固定长度 5 octets。Request Opcode In Error表示哪个 request 触发错误。Attribute Handle In Error表示哪个 handle 触发错误。- 如果原始 request 没有 handle,或者 request 不支持,则
Attribute Handle In Error使用0x0000。 Error Code表示 request 失败原因。Invalid Handle表示 handle 在 Server 上无效。Read Not Permitted/Write Not Permitted表示 attribute 本身不允许读写。Invalid PDU表示 PDU 格式错误。Insufficient Authentication、Insufficient Authorization、Insufficient Encryption、Encryption Key Size Too Short都属于安全访问失败。Request Not Supported表示 Server 不支持该 request。Invalid Offset常见于 long read/write offset 错误。Prepare Queue Full常见于 queued writes。Attribute Not Found在 discovery 中经常表示当前范围无更多结果。Database Out Of Sync表示 Client 应重新发现数据库。Application Error和 Common Profile/Service Error Codes 由上层规范定义。- 多个错误同时适用时,发送哪个 error code 是 vendor-specific。
- Client 收到未知 error code 时,也应把它视为 request 因未知原因无法执行。
- 发送
ATT_ERROR_RSP通常不应导致 Server 断开连接。 - Client 收到安全错误后,可以升级安全并重试。
ATT_ERROR_RSP是一个 response,收到后当前 request transaction 已经结束。- 抓包分析
ATT_ERROR_RSP时,要同时看 request opcode、handle、error code 和 GATT procedure 上下文。
一句话总结:
ATT_ERROR_RSP 不是"蓝牙崩了",而是 Server 的拒绝理由书:哪条 request 错了、哪个 handle 出事了、为什么不能办,全写在这 5 个字节里。会看它,调试效率直接起飞;不会看它,就只能盯着抓包祈祷蓝牙自己良心发现。
下一篇继续学习:
第 14 篇:Find Information------发现 Attribute Handle 与 Type
下一篇重点:
ATT_FIND_INFORMATION_REQ;ATT_FIND_INFORMATION_RSP;- Starting Handle / Ending Handle;
- handle 与 UUID 映射;
- 16-bit UUID format;
- 128-bit UUID format;
- 为什么一个 response 里不能混合不同 UUID size;
Invalid Handle;Attribute Not Found;- 为什么
ATT_FIND_INFORMATION_REQ不应该因为 authentication / authorization 不足而失败。