ATT Write Request/Response Packet流程&源码分析

一、概述

ATT(Attribute Protocol)协议是蓝牙低功耗(BLE)技术中用于发现、读取和写入对端设备属性的重要协议。在ATT协议中,Write Request/Response Packet是客户端向服务端发送写请求并接收响应的一种数据包格式。

1.1. Write Request Packet

Write Request Packet是由客户端(Client)发送给服务端(Server)的,用于请求修改服务端上的某个属性值。这个请求是“必达”的,即服务端在收到请求后必须回复一个响应,以确保通信的可靠性。

Write Request Packet的主要特点包括

  • Opcode:Write Request的Opcode用于标识这是一个写请求操作。
  • Attribute Handle:用于指定要修改的属性的句柄(Handle),这是一个由服务端分配的唯一标识符。
  • Attribute Value:包含要写入的新属性值。

1.2. Write Response Packet

Write Response Packet是由服务端发送给客户端的,用于确认Write Request Packet的接收和处理结果。

Write Response Packet的主要特点包括

  • Opcode:Write Response的Opcode用于标识这是一个写响应操作。
  • Status:表示写操作的成功或失败状态。如果写操作成功,Status字段将包含成功代码;如果失败,则包含相应的错误代码。

1.3. Write Request/Response的交互流程

  1. 客户端发送Write Request:客户端构建Write Request Packet,包含要修改的属性的句柄和新的属性值,并通过BLE连接发送给服务端。
  2. 服务端处理Write Request:服务端收到Write Request Packet后,根据请求中的句柄和属性值进行相应处理。如果处理成功,服务端将准备Write Response Packet;如果失败,则准备包含错误代码的Write Response Packet。
  3. 服务端发送Write Response:服务端将Write Response Packet发送给客户端,以确认写操作的接收和处理结果。
  4. 客户端接收Write Response:客户端收到Write Response Packet后,根据Status字段判断写操作是否成功。如果成功,则可以继续后续操作;如果失败,则根据错误代码进行相应的错误处理。

1.4. 注意事项

  • 在BLE通信中,每个ATT命令都是“必达”的,即发送方会等待接收方的确认(ACK)或响应(Response)。如果未收到确认或响应,发送方将重传该命令直到超时或连接断开。
  • Write Request/Response Packet的交互遵循蓝牙核心规范中定义的ATT协议规则,包括PDU(Protocol Data Unit)格式、Opcode定义、错误处理等。
  • 在实际应用中,我们需要确保客户端和服务端之间的通信协议一致,并正确处理各种异常情况,以确保通信的可靠性和稳定性。

二、客户端发送请求:ATT Write Request Packet (0x12)

ATT Write Request 是BLE设备间数据交换的基础之一,它允许客户端向服务器发送数据,从而实现将数据写入设备属性。

1.1  ATT Write Request Packet 格式

ATT Write Request 数据包是一个PDU(Protocol Data Unit),根据spec,ATT Write Request 的基本结构通常包括以下几个部分:

1. Opcode(操作码):

  • ATT Write Request 的操作码是 0x12。告诉接收方这是一个写请求。

2. Handle(句柄):

  • 紧随操作码之后的是要写入数据的属性的句柄。句柄是一个唯一的标识符,用于指定要操作的属性。

3. Value(值):

  • 句柄之后是实际要写入的数据值。这个值的长度和格式取决于要写入的数据类型以及该属性的定义。

1.2. 示例

我们有一个ATT Write Request数据包,它要将数据写入句柄为0x5D的属性,并且要写入的数据是0x00 0x01

Opcode: 0x12 (ATT Write Request)  
Handle: 0x005D  
Value:  0x00 0x01

1.3. 注意事项

  • 权限:在写入数据之前,必须确保客户端有足够的权限来写入该属性。GATT服务器可以配置属性的安全设置,例如要求加密连接或签名。

  • 大小限制:ATT Write Request 有一个最大负载大小限制,通常等于BLE的MTU减去ATT头部和句柄的大小。如果要写入的数据超过了这个限制,客户端需要使用ATT协议中的“长特征值写入”过程来分块发送数据。

三、服务端响应读请求

对于ATT Write Request,GATT服务器会根据请求的内容更新相应的属性值。之后,服务器会向客户端发送一个ATT Write Response Packet作为响应,以确认写入操作的成功或失败。

acl数据接收分析见Bluedroid协议栈L2CAP连接源码分析_bluedroid acl-CSDN博客

gatt_data_process函数解析从L2CAP层接收到的数据,并根据操作码(opcode)执行相应的处理逻辑。因为操作码Read Response(0x12)是偶数,调用gatt_server_handle_client_req函数来处理该响应。

3.1. gatt_server_handle_client_req

/packages/modules/Bluetooth/system/stack/gatt/gatt_sr.cc
/** This function is called to handle the client requests to server */
void gatt_server_handle_client_req(tGATT_TCB& tcb, uint16_t cid,
                                   uint8_t op_code, uint16_t len,
                                   uint8_t* p_data) {
  /* there is pending command, discard this one */
  if (!gatt_sr_cmd_empty(tcb, cid) && op_code != GATT_HANDLE_VALUE_CONF) return;

  /* the size of the message may not be bigger than the local max PDU size*/
  /* The message has to be smaller than the agreed MTU, len does not include op
   * code */

  uint16_t payload_size = gatt_tcb_get_payload_size(tcb, cid);
  if (len >= payload_size) {
    log::error("server receive invalid PDU size:{} pdu size:{}", len + 1,
               payload_size);
    /* for invalid request expecting response, send it now */
    if (op_code != GATT_CMD_WRITE && op_code != GATT_SIGN_CMD_WRITE &&
        op_code != GATT_HANDLE_VALUE_CONF) {
      gatt_send_error_rsp(tcb, cid, GATT_INVALID_PDU, op_code, 0, false);
    }
    /* otherwise, ignore the pkt */
  } else {
    // handle database out of sync
    if (gatts_process_db_out_of_sync(tcb, cid, op_code, len, p_data)) return;

    switch (op_code) {
      ...

      case GATT_REQ_READ: /* read char/char descriptor value */
      case GATT_REQ_READ_BLOB:
      case GATT_REQ_WRITE: /* write char/char descriptor value */
      case GATT_CMD_WRITE:
      case GATT_SIGN_CMD_WRITE:
      case GATT_REQ_PREPARE_WRITE:
        gatts_process_attribute_req(tcb, cid, op_code, len, p_data);
        break;

     ...

      default:
        break;
    }
  }
}

gatt_server_handle_client_req负责解析来自客户端的请求,然后根据请求的类型进行相应的处理。

1. 检查是否有待处理的命令:

  • 首先检查当前是否有针对这个客户端ID(cid)的待处理命令。如果有,并且这个新请求的操作码(op_code )不是GATT_HANDLE_VALUE_CONF(即句柄值确认),则直接返回,不处理这个新请求。因为BLE协议规定,在服务器处理完一个请求之前,不应接受新的请求(除了句柄值确认请求)。

  • ATT_HANDLE_VALUE_CFM

在BLE通信中,GATT协议定义了多种消息类型用于数据的读写和确认,其中ATT_HANDLE_VALUE_IND用于从服务器向客户端发送数据,而ATT_HANDLE_VALUE_CFM则是客户端对对ATT_HANDLE_VALUE_IND(指示)消息的接收和处理.

  • ATT_HANDLE_VALUE_IND与ATT_HANDLE_VALUE_CFM的关系

    • ATT_HANDLE_VALUE_IND:

      1. 这是一个从GATT服务器发送到GATT客户端的指示消息。

      2. 当服务器希望向客户端发送数据时,可以使用此消息类型。

      3. 与ATT_HANDLE_VALUE_NOTI(通知)消息类似,但指示消息需要客户端的确认。

    • ATT_HANDLE_VALUE_CFM:

      1. 这是一个从GATT客户端发送到GATT服务器的确认消息。

      2. 当客户端成功接收到ATT_HANDLE_VALUE_IND消息并处理完毕后,会发送此消息以确认。

      3. 此消息是可选的,但在需要确认数据已接收的场景中非常重要。

  • ATT_HANDLE_VALUE_CFM的发送与接收

        在BLE通信过程中,当GATT服务器使用ATT_HANDLE_VALUE_IND向客户端发送数据时,客户端需要按照以下步骤处理:

    • 接收ATT_HANDLE_VALUE_IND消息:

      1. 客户端的GATT事件处理函数会接收到这个消息。

      2. 消息中包含要发送的数据的句柄(Handle)和数据值(Value)。

    • 处理数据:

      1. 客户端根据接收到的数据执行相应的操作。

      2. 可能包括更新UI、存储数据或进行进一步的数据处理。

    • 发送ATT_HANDLE_VALUE_CFM(如果需要):

      1. 如果客户端需要向服务器确认数据的接收和处理,它将发送ATT_HANDLE_VALUE_CFM消息。

      2. 这个消息告诉服务器客户端已经成功接收到数据并进行了处理。

2. 确认的必要性:

  • 并非所有ATT_HANDLE_VALUE_IND消息都需要客户端的确认。这取决于应用程序的具体需求和GATT服务器的实现。

  • 如果应用程序不需要确认,或者服务器在发送数据后不需要等待确认,那么客户端可以选择不发送ATT_HANDLE_VALUE_CFM消息。

3. 性能考虑:

  • 发送ATT_HANDLE_VALUE_CFM消息会增加通信开销和延迟。

  • 因此,在设计BLE应用程序时,需要权衡确认的必要性和性能需求。

错误处理:

  • 如果客户端在接收或处理ATT_HANDLE_VALUE_IND消息时发生错误,可能会发送ATT Error Response消息来通知服务器错误原因。

  • 这有助于服务器了解问题所在并采取相应的措施。

  1. 检查PDU大小:

    1. 接下来,通过gatt_tcb_get_payload_size获取当前与客户端协商的MTU。然后,检查请求的数据长度是否超过了这个MTU。并且如果请求不是GATT_CMD_WRITE、GATT_SIGN_CMD_WRITE或GATT_HANDLE_VALUE_CONF(这些操作不需要响应),则向客户端发送一个错误响应。对于其他情况,则忽略这个数据包。

  2. 处理数据库不同步问题:在处理实际请求之前,函数会调用gatts_process_db_out_of_sync来检查并处理可能的数据库不同步问题。如果这个函数返回true,表示已经处理了这个问题并决定不继续处理这个请求,则函数直接返回。

  3. 处理GATT请求:本case对应GATT_REQ_WRITE,函数会调用gatts_process_attribute_req来处理与属性相关的请求。

3.2. gatts_process_attribute_req

/packages/modules/Bluetooth/system/stack/gatt/gatt_sr.cc
void gatts_process_attribute_req(tGATT_TCB& tcb, uint16_t cid, uint8_t op_code,
                                 uint16_t len, uint8_t* p_data) {
  uint16_t handle = 0;
  uint8_t* p = p_data;
  tGATT_STATUS status = GATT_INVALID_HANDLE;

  if (len < 2) {
    log::error("Illegal PDU length, discard request");
    status = GATT_INVALID_PDU;
  } else {
    STREAM_TO_UINT16(handle, p);
    len -= 2;
  }

#if (GATT_CONFORMANCE_TESTING == TRUE)
  gatt_cb.handle = handle;
  if (gatt_cb.enable_err_rsp && gatt_cb.req_op_code == op_code) {
    log::verbose("Conformance tst: forced err rsp: error status={}",
                 gatt_cb.err_status);

    gatt_send_error_rsp(tcb, cid, gatt_cb.err_status, cid, gatt_cb.req_op_code,
                        handle, false);

    return;
  }
#endif

  if (GATT_HANDLE_IS_VALID(handle)) {
    for (auto& el : *gatt_cb.srv_list_info) {
      if (el.s_hdl <= handle && el.e_hdl >= handle) {
        for (const auto& attr : el.p_db->attr_list) {
          if (attr.handle == handle) {
            switch (op_code) {
              case GATT_REQ_READ: /* read char/char descriptor value */
              case GATT_REQ_READ_BLOB:
                gatts_process_read_req(tcb, cid, el, op_code, handle, len, p);
                break;

              case GATT_REQ_WRITE: /* write char/char descriptor value */
              case GATT_CMD
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值