Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 545bc4e4 authored by William Escande's avatar William Escande
Browse files

Prevent EATT teardown from affecting ACL lifetime

Normally, when the last dynamic channel closes and no ATT clients
are active, we disconnect. However, if we are an ATT server only (so no
ATT clients are open) and have unbonded with the peer only on our side,
then the peer will attempt to open EATT and then immediately disconnect
after encryption failure. This causes the LE connection to drop if we
haven't opened a GATT client ourselves.

The fix is to ignore the lifecycle of EATT L2CAP connections when
looking at the lifecycle of the ACL link, since they are managed by the
stack, not apps. Therefore, if an app opens/closes an L2CAP CoC in the
same fashion, it will cause a disconnection - it's only EATT that is
special-cased.

Test: atest avatar GattTest#test_eatt_when_not_encrypted_no_timeout
Test: Manual QA testing has been conducted
Bug: 267635511
Change-Id: I83e3cd1436c9548c0f18b13f7350787a61cec068
parent 9891512f
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -24,7 +24,7 @@ from bumble.pairing import PairingConfig
from bumble_experimental.gatt import GATTService
from mobly import base_test, signals, test_runner
from mobly.asserts import assert_equal  # type: ignore
from mobly.asserts import assert_false  # type: ignore
from mobly.asserts import assert_true  # type: ignore
from mobly.asserts import assert_in  # type: ignore
from mobly.asserts import assert_is_not_none  # type: ignore
from mobly.asserts import assert_not_in  # type: ignore
@@ -282,9 +282,7 @@ class GattTest(base_test.BaseTestClass): # type: ignore[misc]
        control_frame = await fut

        assert_equal(bytes(control_frame)[10], 0x05)  # All connections refused – insufficient authentication

        # TODO(b/289460863): change to assert_true when the test is fixed
        assert_false(await is_connected(self.ref, ref_dut), "Device is no longer connected")
        assert_true(await is_connected(self.ref, ref_dut), "Device is no longer connected")


async def is_connected(device: PandoraDevice, connection: Connection) -> bool:
+2 −1
Original line number Diff line number Diff line
@@ -838,7 +838,8 @@ std::vector<uint16_t> L2CA_ConnectCreditBasedReq(uint16_t psm,

  for (int i = 0; i < p_cfg->number_of_channels; i++) {
    /* Allocate a channel control block */
    tL2C_CCB* p_ccb = l2cu_allocate_ccb(p_lcb, 0);
    tL2C_CCB* p_ccb =
        l2cu_allocate_ccb(p_lcb, 0, psm == BT_PSM_EATT /* is_eatt */);
    if (p_ccb == NULL) {
      if (i == 0) {
        L2CAP_TRACE_WARNING("%s no CCB, PSM: 0x%04x", __func__, psm);
+4 −2
Original line number Diff line number Diff line
@@ -657,7 +657,8 @@ void l2cble_process_sig_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) {
              L2CAP_LE_RESULT_SOURCE_CID_ALREADY_ALLOCATED;
        } else {
          /* Allocate a ccb for this.*/
          temp_p_ccb = l2cu_allocate_ccb(p_lcb, 0);
          temp_p_ccb = l2cu_allocate_ccb(
              p_lcb, 0, con_info.psm == BT_PSM_EATT /* is_eatt */);
          if (temp_p_ccb == NULL) {
            LOG_ERROR("L2CAP - unable to allocate CCB");
            p_lcb->pending_ecoc_connection_cids[i] = 0;
@@ -1004,7 +1005,8 @@ void l2cble_process_sig_cmd(tL2C_LCB* p_lcb, uint8_t* p, uint16_t pkt_len) {
      }

      /* Allocate a ccb for this.*/
      p_ccb = l2cu_allocate_ccb(p_lcb, 0);
      p_ccb = l2cu_allocate_ccb(p_lcb, 0,
                                con_info.psm == BT_PSM_EATT /* is_eatt */);
      if (p_ccb == NULL) {
        L2CAP_TRACE_ERROR("L2CAP - unable to allocate CCB");
        l2cu_reject_ble_connection(p_ccb, id, L2CAP_CONN_NO_RESOURCES);
+2 −1
Original line number Diff line number Diff line
@@ -719,7 +719,8 @@ void l2cu_enqueue_ccb(tL2C_CCB* p_ccb);
void l2cu_dequeue_ccb(tL2C_CCB* p_ccb);
void l2cu_change_pri_ccb(tL2C_CCB* p_ccb, tL2CAP_CHNL_PRIORITY priority);

tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid);
tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid,
                            bool is_eatt = false);
void l2cu_release_ccb(tL2C_CCB* p_ccb);
tL2C_CCB* l2cu_find_ccb_by_cid(tL2C_LCB* p_lcb, uint16_t local_cid);
tL2C_CCB* l2cu_find_ccb_by_remote_cid(tL2C_LCB* p_lcb, uint16_t remote_cid);
+7 −2
Original line number Diff line number Diff line
@@ -1351,7 +1351,7 @@ void l2cu_change_pri_ccb(tL2C_CCB* p_ccb, tL2CAP_CHNL_PRIORITY priority) {
 * Returns          pointer to CCB, or NULL if none
 *
 ******************************************************************************/
tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid) {
tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid, bool is_eatt) {
  LOG_DEBUG("is_dynamic = %d, cid 0x%04x", p_lcb != nullptr, cid);
  if (!l2cb.p_free_ccb_first) {
    LOG_ERROR("First free ccb is null for cid 0x%04x", cid);
@@ -1471,8 +1471,13 @@ tL2C_CCB* l2cu_allocate_ccb(tL2C_LCB* p_lcb, uint16_t cid) {

  if (p_lcb != NULL) {
    // once a dynamic channel is opened, timeouts become active
    // the exception for this is EATT, since that is managed by GATT clients,
    // not by the L2CAP layer (GATT will keep the idle timeout at infinity while
    // clients are active)
    if (!is_eatt) {
      p_lcb->with_active_local_clients = true;
    }
  }

  return p_ccb;
}
Loading