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

Commit 6af6ff71 authored by Sharvil Nanavati's avatar Sharvil Nanavati Committed by Andre Eisenbach
Browse files

Add a new l2cap_client class.

The code to negotiate an L2CAP connection is currently scattered
and duplicated throughout the codebase. Each profile that uses
L2CAP has its own explicit or implicit state machine to go establish
a connection and go through the handshake. This class is intended
to consolidate that duplicated code and provide a usable API for
L2CAP client connections.
parent 2377309a
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -150,6 +150,7 @@ LOCAL_SRC_FILES:= \
    ./l2cap/l2c_csm.c \
    ./l2cap/l2c_csm.c \
    ./l2cap/l2c_link.c \
    ./l2cap/l2c_link.c \
    ./l2cap/l2c_ble.c \
    ./l2cap/l2c_ble.c \
    ./l2cap/l2cap_client.c \
    ./gap/gap_api.c \
    ./gap/gap_api.c \
    ./gap/gap_ble.c \
    ./gap/gap_ble.c \
    ../vnd/ble/vendor_ble.c
    ../vnd/ble/vendor_ble.c
+74 −0
Original line number Original line Diff line number Diff line
/******************************************************************************
 *
 *  Copyright (C) 2014 Google, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#pragma once

#include <hardware/bluetooth.h>
#include <stdbool.h>
#include <stdint.h>

typedef struct buffer_t buffer_t;
typedef struct l2cap_client_t l2cap_client_t;

typedef struct {
  void (*connected)(l2cap_client_t *client, void *context);
  void (*disconnected)(l2cap_client_t *client, void *context);
  void (*read_ready)(l2cap_client_t *client, buffer_t *packet, void *context);
  void (*write_ready)(l2cap_client_t *client, void *context);
} l2cap_client_callbacks_t;

// Returns a new buffer with enough space for |size| bytes of L2CAP payload.
// |size| must be greater than zero. This function returns NULL if the buffer
// could not be allocated. The returned buffer must be freed with |buffer_free|
// when it is no longer needed.
buffer_t *l2cap_buffer_new(size_t size);

// Creates and returns a new L2CAP client object. |callbacks| must not be NULL and
// must specify a set of functions that should be called back when events occur
// on the L2CAP connection. |context| may be NULL and will be passed as the argument
// to all callbacks in |l2cap_client_callbacks_t|. The returned object must be freed
// with |l2cap_client_free|.
l2cap_client_t *l2cap_client_new(const l2cap_client_callbacks_t *callbacks, void *context);

// Frees the L2CAP client object allocated with |l2cap_client_new|. |client| may be NULL.
void l2cap_client_free(l2cap_client_t *client);

// Attempts to connect the |client| to a peer device specified by |remote_bdaddr|
// using the |psm| protocol specifier. This function returns true if the connect
// operation could be started and will indicate completion with either a 'connected'
// callback (success) or a 'disconnected' callback (failure).
//
// This function must not be called while a connect operation is in progress or
// while |l2cap_client_is_connected|. |client| and |remote_bdaddr| must not be NULL.
// |psm| must be greater than zero.
bool l2cap_client_connect(l2cap_client_t *client, const bt_bdaddr_t *remote_bdaddr, uint16_t psm);

// Disconnects a connected |client|. This function is asynchronous and idempotent. It
// will indicate completion with a 'disconnected' callback. |client| must not be NULL.
void l2cap_client_disconnect(l2cap_client_t *client);

// Returns true if |client| is connected and is ready to accept data written to it.
// |client| must not be NULL.
bool l2cap_client_is_connected(const l2cap_client_t *client);

// Writes data contained in |packet| to a connected |client|. This function returns
// true if the packet was successfully queued for delivery, false if the client cannot
// accept more data at this time. If this function returns false, the caller must wait
// for the 'write_ready' callback to write additional data to the client. Neither
// |client| nor |packet| may be NULL.
bool l2cap_client_write(l2cap_client_t *client, buffer_t *packet);
+434 −0
Original line number Original line Diff line number Diff line
/******************************************************************************
 *
 *  Copyright (C) 2014 Google, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/

#define LOG_TAG "bt_l2cap_client"

#include <assert.h>

#include "btcore/include/bdaddr.h"
#include "gki/common/gki.h"
#include "osi/include/allocator.h"
#include "osi/include/buffer.h"
#include "osi/include/list.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
#include "stack/include/l2cap_client.h"
#include "stack/include/l2c_api.h"

struct l2cap_client_t {
  l2cap_client_callbacks_t callbacks;
  void *context;

  uint16_t local_channel_id;
  uint16_t remote_mtu;
  bool configured_self;
  bool configured_peer;
  bool is_congested;
  list_t *outbound_fragments;
};

static void connect_completed_cb(uint16_t local_channel_id, uint16_t error_code);
static void config_request_cb(uint16_t local_channel_id, tL2CAP_CFG_INFO *requested_parameters);
static void config_completed_cb(uint16_t local_channel_id, tL2CAP_CFG_INFO *negotiated_parameters);
static void disconnect_request_cb(uint16_t local_channel_id, bool ack_required);
static void disconnect_completed_cb(uint16_t local_channel_id, uint16_t error_code);
static void congestion_cb(uint16_t local_channel_id, bool is_congested);
static void read_ready_cb(uint16_t local_channel_id, BT_HDR *packet);
static void write_completed_cb(uint16_t local_channel_id, uint16_t packets_completed);

static void fragment_packet(l2cap_client_t *client, buffer_t *packet);
static void dispatch_fragments(l2cap_client_t *client);
static l2cap_client_t *find(uint16_t local_channel_id);

// From the Bluetooth Core specification.
static const uint16_t L2CAP_MTU_DEFAULT = 672;
static const uint16_t L2CAP_MTU_MINIMUM = 48;

static const tL2CAP_APPL_INFO l2cap_callbacks = {
  .pL2CA_ConnectCfm_Cb       = connect_completed_cb,
  .pL2CA_ConfigInd_Cb        = config_request_cb,
  .pL2CA_ConfigCfm_Cb        = config_completed_cb,
  .pL2CA_DisconnectInd_Cb    = disconnect_request_cb,
  .pL2CA_DisconnectCfm_Cb    = disconnect_completed_cb,
  .pL2CA_CongestionStatus_Cb = congestion_cb,
  .pL2CA_DataInd_Cb          = read_ready_cb,
  .pL2CA_TxComplete_Cb       = write_completed_cb,
};

static list_t *l2cap_clients;  // A list of l2cap_client_t. Container does not own objects.

buffer_t *l2cap_buffer_new(size_t size) {
  buffer_t *buf = buffer_new(size + L2CAP_MIN_OFFSET);
  buffer_t *slice = NULL;
  if (buf)
    slice = buffer_new_slice(buf, size);
  buffer_free(buf);
  return slice;
}

l2cap_client_t *l2cap_client_new(const l2cap_client_callbacks_t *callbacks, void *context) {
  assert(callbacks != NULL);
  assert(callbacks->connected != NULL);
  assert(callbacks->disconnected != NULL);
  assert(callbacks->read_ready != NULL);
  assert(callbacks->write_ready != NULL);

  if (!l2cap_clients) {
    l2cap_clients = list_new(NULL);
    if (!l2cap_clients) {
      LOG_ERROR("%s unable to allocate space for L2CAP client list.", __func__);
      return NULL;
    }
  }

  l2cap_client_t *ret = (l2cap_client_t *)osi_calloc(sizeof(l2cap_client_t));
  if (!ret) {
    LOG_ERROR("%s unable to allocate L2CAP client.", __func__);
    goto error;
  }

  ret->callbacks = *callbacks;
  ret->context = context;

  ret->remote_mtu = L2CAP_MTU_DEFAULT;
  ret->outbound_fragments = list_new(NULL);
  if (!ret) {
    LOG_ERROR("%s unable to allocate outbound L2CAP fragment list.", __func__);
    goto error;
  }

  list_append(l2cap_clients, ret);

  return ret;

error:;
  osi_free(ret);
  return NULL;
}

void l2cap_client_free(l2cap_client_t *client) {
  if (!client)
    return;

  list_remove(l2cap_clients, client);
  l2cap_client_disconnect(client);
  list_free(client->outbound_fragments);
  osi_free(client);
}

bool l2cap_client_connect(l2cap_client_t *client, const bt_bdaddr_t *remote_bdaddr, uint16_t psm) {
  assert(client != NULL);
  assert(remote_bdaddr != NULL);
  assert(psm != 0);
  assert(!bdaddr_is_empty(remote_bdaddr));
  assert(client->local_channel_id == 0);
  assert(!client->configured_self);
  assert(!client->configured_peer);
  assert(!L2C_INVALID_PSM(psm));

  client->local_channel_id = L2CA_ConnectReq(psm, (uint8_t *)remote_bdaddr);
  if (!client->local_channel_id) {
    LOG_ERROR("%s unable to create L2CAP connection.", __func__);
    return false;
  }

  L2CA_SetConnectionCallbacks(client->local_channel_id, &l2cap_callbacks);
  return true;
}

void l2cap_client_disconnect(l2cap_client_t *client) {
  assert(client != NULL);

  if (client->local_channel_id && !L2CA_DisconnectReq(client->local_channel_id))
    LOG_ERROR("%s unable to send disconnect message for LCID 0x%04x.", __func__, client->local_channel_id);

  client->local_channel_id = 0;
  client->remote_mtu = L2CAP_MTU_DEFAULT;
  client->configured_self = false;
  client->configured_peer = false;
  client->is_congested = false;

  for (const list_node_t *node = list_begin(client->outbound_fragments); node != list_end(client->outbound_fragments); node = list_next(node))
    GKI_freebuf(list_node(node));

  list_clear(client->outbound_fragments);
}

bool l2cap_client_is_connected(const l2cap_client_t *client) {
  assert(client != NULL);

  return client->local_channel_id != 0 && client->configured_self && client->configured_peer;
}

bool l2cap_client_write(l2cap_client_t *client, buffer_t *packet) {
  assert(client != NULL);
  assert(packet != NULL);
  assert(l2cap_client_is_connected(client));

  if (client->is_congested)
    return false;

  fragment_packet(client, packet);
  dispatch_fragments(client);
  return true;
}

static void connect_completed_cb(uint16_t local_channel_id, uint16_t error_code) {
  assert(local_channel_id != 0);

  l2cap_client_t *client = find(local_channel_id);
  if (!client) {
    LOG_ERROR("%s unable to find L2CAP client for LCID 0x%04x.", __func__, local_channel_id);
    return;
  }

  if (error_code != L2CAP_CONN_OK) {
    LOG_ERROR("%s error connecting L2CAP channel: %d.", __func__, error_code);
    client->callbacks.disconnected(client, client->context);
    return;
  }

  // Use default L2CAP parameters.
  tL2CAP_CFG_INFO desired_parameters = { 0 };
  if (!L2CA_ConfigReq(local_channel_id, &desired_parameters)) {
    LOG_ERROR("%s error sending L2CAP config parameters.", __func__);
    client->callbacks.disconnected(client, client->context);
  }
}

static void config_request_cb(uint16_t local_channel_id, tL2CAP_CFG_INFO *requested_parameters) {
  tL2CAP_CFG_INFO response = { 0 };
  l2cap_client_t *client = find(local_channel_id);

  if (!client) {
    LOG_ERROR("%s unable to find L2CAP client matching LCID 0x%04x.", __func__, local_channel_id);
    return;
  }

  response.result = L2CAP_CFG_OK;

  if (requested_parameters->mtu_present) {
    // Make sure the peer chose an MTU at least as large as the minimum L2CAP MTU defined
    // by the Bluetooth Core spec.
    if (requested_parameters->mtu < L2CAP_MTU_MINIMUM) {
      response.mtu = L2CAP_MTU_MINIMUM;
      response.mtu_present = true;
      response.result = L2CAP_CFG_UNACCEPTABLE_PARAMS;
    } else {
      client->remote_mtu = requested_parameters->mtu;
    }
  }

  if (requested_parameters->fcr_present) {
    if (requested_parameters->fcr.mode != L2CAP_FCR_BASIC_MODE) {
      response.fcr_present = true;
      response.fcr = requested_parameters->fcr;
      response.fcr.mode = L2CAP_FCR_BASIC_MODE;
      response.result = L2CAP_CFG_UNACCEPTABLE_PARAMS;
    }
  }

  if (!L2CA_ConfigRsp(local_channel_id, &response)) {
    LOG_ERROR("%s unable to send config response for LCID 0x%04x.", __func__, local_channel_id);
    l2cap_client_disconnect(client);
    return;
  }

  // If we've configured both endpoints, let the listener know we've connected.
  client->configured_peer = true;
  if (l2cap_client_is_connected(client))
    client->callbacks.connected(client, client->context);
}

static void config_completed_cb(uint16_t local_channel_id, tL2CAP_CFG_INFO *negotiated_parameters) {
  l2cap_client_t *client = find(local_channel_id);

  if (!client) {
    LOG_ERROR("%s unable to find L2CAP client matching LCID 0x%04x.", __func__, local_channel_id);
    return;
  }

  switch (negotiated_parameters->result) {
    // We'll get another configuration response later.
    case L2CAP_CFG_PENDING:
      break;

    case L2CAP_CFG_UNACCEPTABLE_PARAMS:
      // TODO: see if we can renegotiate parameters instead of dropping the connection.
      LOG_WARN("%s dropping L2CAP connection due to unacceptable config parameters.", __func__);
      l2cap_client_disconnect(client);
      break;

    case L2CAP_CFG_OK:
      // If we've configured both endpoints, let the listener know we've connected.
      client->configured_self = true;
      if (l2cap_client_is_connected(client))
        client->callbacks.connected(client, client->context);
      break;

    // Failure, no further parameter negotiation possible.
    default:
      LOG_WARN("%s L2CAP parameter negotiation failed with error code %d.", __func__, negotiated_parameters->result);
      l2cap_client_disconnect(client);
      break;
  }
}

static void disconnect_request_cb(uint16_t local_channel_id, bool ack_required) {
  l2cap_client_t *client = find(local_channel_id);
  if (!client) {
    LOG_ERROR("%s unable to find L2CAP client with LCID 0x%04x.", __func__, local_channel_id);
    return;
  }

  if (ack_required)
    L2CA_DisconnectRsp(local_channel_id);

  // We already sent a disconnect response so this LCID is now invalid.
  client->local_channel_id = 0;
  l2cap_client_disconnect(client);

  client->callbacks.disconnected(client, client->context);
}

static void disconnect_completed_cb(uint16_t local_channel_id, UNUSED_ATTR uint16_t error_code) {
  assert(local_channel_id != 0);

  l2cap_client_t *client = find(local_channel_id);
  if (!client) {
    LOG_ERROR("%s unable to find L2CAP client with LCID 0x%04x.", __func__, local_channel_id);
    return;
  }

  client->local_channel_id = 0;
  l2cap_client_disconnect(client);

  client->callbacks.disconnected(client, client->context);
}

static void congestion_cb(uint16_t local_channel_id, bool is_congested) {
  assert(local_channel_id != 0);

  l2cap_client_t *client = find(local_channel_id);
  if (!client) {
    LOG_ERROR("%s unable to find L2CAP client matching LCID 0x%04x.", __func__, local_channel_id);
    return;
  }

  client->is_congested = is_congested;

  if (!is_congested) {
    // If we just decongested, dispatch whatever we have left over in our queue.
    // Once that's done, if we're still decongested, notify the listener so it
    // can start writing again.
    dispatch_fragments(client);
    if (!client->is_congested)
      client->callbacks.write_ready(client, client->context);
  }
}

static void read_ready_cb(uint16_t local_channel_id, BT_HDR *packet) {
  assert(local_channel_id != 0);

  l2cap_client_t *client = find(local_channel_id);
  if (!client) {
    LOG_ERROR("%s unable to find L2CAP client matching LCID 0x%04x.", __func__, local_channel_id);
    return;
  }

  // TODO(sharvil): eliminate copy from BT_HDR.
  buffer_t *buffer = buffer_new(packet->len);
  memcpy(buffer_ptr(buffer), packet->data + packet->offset, packet->len);
  GKI_freebuf(packet);

  client->callbacks.read_ready(client, buffer, client->context);
  buffer_free(buffer);
}

static void write_completed_cb(UNUSED_ATTR uint16_t local_channel_id, UNUSED_ATTR uint16_t packets_completed) {
  // Do nothing. We update congestion state based on the congestion callback
  // and we've already removed items from outbound_fragments list so we don't
  // really care how many packets were successfully dispatched.
}

static void fragment_packet(l2cap_client_t *client, buffer_t *packet) {
  assert(client != NULL);
  assert(packet != NULL);

  // TODO(sharvil): eliminate copy into BT_HDR.
  BT_HDR *bt_packet = GKI_getbuf(buffer_length(packet) + L2CAP_MIN_OFFSET);
  bt_packet->offset = L2CAP_MIN_OFFSET;
  bt_packet->len = buffer_length(packet);
  memcpy(bt_packet->data + bt_packet->offset, buffer_ptr(packet), buffer_length(packet));

  for (;;) {
    if (bt_packet->len <= client->remote_mtu) {
      if (bt_packet->len > 0)
        list_append(client->outbound_fragments, bt_packet);
      else
        GKI_freebuf(bt_packet);
      break;
    }

    BT_HDR *fragment = GKI_getbuf(client->remote_mtu + L2CAP_MIN_OFFSET);
    fragment->offset = L2CAP_MIN_OFFSET;
    fragment->len = client->remote_mtu;
    memcpy(fragment->data + fragment->offset, bt_packet->data + bt_packet->offset, client->remote_mtu);

    list_append(client->outbound_fragments, fragment);

    bt_packet->offset += client->remote_mtu;
    bt_packet->len -= client->remote_mtu;
  }
}

static void dispatch_fragments(l2cap_client_t *client) {
  assert(client != NULL);
  assert(!client->is_congested);

  while (!list_is_empty(client->outbound_fragments)) {
    BT_HDR *packet = (BT_HDR *)list_front(client->outbound_fragments);
    list_remove(client->outbound_fragments, packet);

    switch (L2CA_DataWrite(client->local_channel_id, packet)) {
      case L2CAP_DW_CONGESTED:
        client->is_congested = true;
        return;

      case L2CAP_DW_FAILED:
        LOG_ERROR("%s error writing data to L2CAP connection LCID 0x%04x; disconnecting.", __func__, client->local_channel_id);
        l2cap_client_disconnect(client);
        return;

      case L2CAP_DW_SUCCESS:
        break;
    }
  }
}

static l2cap_client_t *find(uint16_t local_channel_id) {
  assert(local_channel_id != 0);

  for (const list_node_t *node = list_begin(l2cap_clients); node != list_end(l2cap_clients); node = list_next(node)) {
    l2cap_client_t *client = (l2cap_client_t *)list_node(node);
    if (client->local_channel_id == local_channel_id)
      return client;
  }

  return NULL;
}