Understanding Maximum BT GATT notification size

I'm working with a BLE peripheral application that must transfer large amounts of data to a phone central. The application uses the MTU negotiation to determine the max notification size it can send. I ran into a problem where the BLE stack reported a TX MTU or 517 when connected to a Pixel 7 Pro, but if I try and send a gatt notification of size 517 (513 + 4 byte header), I get an error from the ATT stack:

BLE: Updated MTU: TX: 517 RX: 247 bytes

...

bt_att: No ATT channel for MTU 517

bt_gatt: No buffer available to send notification

If I clamp the max size of the message fragments to CONFIG_BT_L2CAP_TX_MTU=498 then the notification goes through. My impression was that the ATT layer is allowed to send any data equal or under the size of the negotiated TX MTU but it seems like this isn't the case. I also thought that the L2CAP layer is responsible to fragmentation and reassembly of incoming and outgoing messages in which case the L2CAP MTU should be abstracted away from the ATT layer. Is CONFIG_BT_L2CAP_TX_MTU actually just the MTU between the ATT and L2CAP layer and not the L2CAP <-> HCI?

One other weird thing I noticed was that in the ble gatt tester in btp_gatt.c, the max notification size is set like so:

/* TODO there should be better way of determining max supported MTU */
#define MAX_NOTIF_DATA (MIN(BT_L2CAP_RX_MTU, BT_L2CAP_TX_MTU) - 3)
I don't see why the L2CAP_RX_MTU would affect the maximum gatt notification size, the TODO would also indicate that this isn't the optimal way of determining the max notification size. Please help clarify which variables actually determine the maximum allowable gatt notification size.

 

Parents
  • It seems I may have misunderstood the params of the att callback, in att.c, att_mtu_req takes the requested MTU from the client and the local MTU from the configured L2CAP buffer sizes and sets the MTU for the channel to the minimum of the two. So the maximum notification size should be the min of the client-requested and server-local MTU. From the BLE Core Spec 5.1:

    When using an L2CAP channel with a fixed CID, the client and server may optionally exchange the maximum size of a packet that can be received using the ATT_EXCHANGE_MTU_REQ and ATT_EXCHANGE_MTU_RSP PDUs. Both devices then use the minimum of these exchanged values for all further communication

    If that is the case then the documentation on the att_mtu_updated callback certainly seems misleading:

    /** @brief The maximum ATT MTU on a connection has changed.
    *
    * This callback notifies the application that the maximum TX or RX
    * ATT MTU has increased.
    *
    * @param conn Connection object.
    * @param tx Updated TX ATT MTU.
    * @param rx Updated RX ATT MTU.
    */
    void (*att_mtu_updated)(struct bt_conn *conn, uint16_t tx, uint16_t rx);
    It should be that tx is the MTU the client tx'd to the server and rx is the MTU the client rx'd from the server. Please correct me if I'm wrong.
Reply
  • It seems I may have misunderstood the params of the att callback, in att.c, att_mtu_req takes the requested MTU from the client and the local MTU from the configured L2CAP buffer sizes and sets the MTU for the channel to the minimum of the two. So the maximum notification size should be the min of the client-requested and server-local MTU. From the BLE Core Spec 5.1:

    When using an L2CAP channel with a fixed CID, the client and server may optionally exchange the maximum size of a packet that can be received using the ATT_EXCHANGE_MTU_REQ and ATT_EXCHANGE_MTU_RSP PDUs. Both devices then use the minimum of these exchanged values for all further communication

    If that is the case then the documentation on the att_mtu_updated callback certainly seems misleading:

    /** @brief The maximum ATT MTU on a connection has changed.
    *
    * This callback notifies the application that the maximum TX or RX
    * ATT MTU has increased.
    *
    * @param conn Connection object.
    * @param tx Updated TX ATT MTU.
    * @param rx Updated RX ATT MTU.
    */
    void (*att_mtu_updated)(struct bt_conn *conn, uint16_t tx, uint16_t rx);
    It should be that tx is the MTU the client tx'd to the server and rx is the MTU the client rx'd from the server. Please correct me if I'm wrong.
Children
No Data
Related