【Bluetooth-SIG】【CoreV6.2】【Vol3 Part F】【十三】【Error Handling——ATT_ERROR_RSP】

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_RSP PDU 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_RSPrequest-response transaction 中的错误响应。

1.2 它不是给 Command 用的

规范特别说明:

Commands,例如 ATT_WRITE_CMDATT_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();
}

这个太粗糙。

正确做法是:

  1. 解析 Request Opcode In Error
  2. 检查它是否匹配当前 pending request;
  3. 再把错误传给对应 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_RSPAttribute 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 Error0x0000


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 Error0x0000

注意:

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 Error0x0000


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_CMDATT_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_RSPATT_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 AuthenticationInsufficient AuthorizationEncryption Key Size Too ShortDatabase 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_CMDATT_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

核心结论如下:

  1. ATT_ERROR_RSP 用于说明某个 request 不能被执行,并给出原因。
  2. ATT_ERROR_RSP 不由 command 触发。
  3. ATT_WRITE_CMDATT_SIGNED_WRITE_CMD 不生成 ATT_ERROR_RSP
  4. ATT_ERROR_RSP 的 opcode 是 0x01
  5. ATT_ERROR_RSP 固定长度 5 octets。
  6. Request Opcode In Error 表示哪个 request 触发错误。
  7. Attribute Handle In Error 表示哪个 handle 触发错误。
  8. 如果原始 request 没有 handle,或者 request 不支持,则 Attribute Handle In Error 使用 0x0000
  9. Error Code 表示 request 失败原因。
  10. Invalid Handle 表示 handle 在 Server 上无效。
  11. Read Not Permitted / Write Not Permitted 表示 attribute 本身不允许读写。
  12. Invalid PDU 表示 PDU 格式错误。
  13. Insufficient AuthenticationInsufficient AuthorizationInsufficient EncryptionEncryption Key Size Too Short 都属于安全访问失败。
  14. Request Not Supported 表示 Server 不支持该 request。
  15. Invalid Offset 常见于 long read/write offset 错误。
  16. Prepare Queue Full 常见于 queued writes。
  17. Attribute Not Found 在 discovery 中经常表示当前范围无更多结果。
  18. Database Out Of Sync 表示 Client 应重新发现数据库。
  19. Application Error 和 Common Profile/Service Error Codes 由上层规范定义。
  20. 多个错误同时适用时,发送哪个 error code 是 vendor-specific。
  21. Client 收到未知 error code 时,也应把它视为 request 因未知原因无法执行。
  22. 发送 ATT_ERROR_RSP 通常不应导致 Server 断开连接。
  23. Client 收到安全错误后,可以升级安全并重试。
  24. ATT_ERROR_RSP 是一个 response,收到后当前 request transaction 已经结束。
  25. 抓包分析 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 不足而失败。
相关推荐
HiDev_10 小时前
HCI 功能规范【5.1. Correctness】
蓝牙·ble·ble 蓝牙广播·蓝牙hci command·蓝牙广播扫描
HiDev_1 天前
HCI 功能规范【4.2. Controller to Host data flow control】
蓝牙·ble·ble 蓝牙广播·蓝牙hci command
HiDev_1 天前
HCI 功能规范【4.4. Command flow control】
蓝牙·ble·ble 蓝牙广播·蓝牙hci command
桑榆肖物1 个月前
ImprovWifi 跨平台传输层设计:把协议层做薄,把宿主层做稳
嵌入式硬件·wifi·.net·ble
一苇以航322 个月前
LE Audio低功耗蓝牙音频详解 (三)
音视频·蓝牙·ble·le audio
一渊之隔2 个月前
uniapp蓝牙搜索连接展示蓝牙设备包含信号显示
前端·网络·uni-app·bluetooth
yanlaifan2 个月前
BLE中PHY 1M和 2M
bluetooth
HiDev_2 个月前
iOS 蓝牙开发进阶:彻底理解 CBManager(状态、权限与正确使用方式)
ios·objective-c·蓝牙·ble
墨染倾城殇2 个月前
物联网智能家居灯控与全屋互联:无线技术要点与模组能力边界
蓝牙mesh·ble·低功耗模块·wifi蓝牙模块