/****************************************************************************** * * 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 "stack/include/l2cap_client.h" #include #include #include "btcore/include/bdaddr.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/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(LOG_TAG, "%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)); ret->callbacks = *callbacks; ret->context = context; ret->remote_mtu = L2CAP_MTU_DEFAULT; ret->outbound_fragments = list_new(NULL); if (!ret) { LOG_ERROR(LOG_TAG, "%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(LOG_TAG, "%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(LOG_TAG, "%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)) osi_free(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(LOG_TAG, "%s unable to find L2CAP client for LCID 0x%04x.", __func__, local_channel_id); return; } if (error_code != L2CAP_CONN_OK) { LOG_ERROR(LOG_TAG, "%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; memset(&desired_parameters, 0, sizeof(desired_parameters)); if (!L2CA_ConfigReq(local_channel_id, &desired_parameters)) { LOG_ERROR(LOG_TAG, "%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; l2cap_client_t *client = find(local_channel_id); if (!client) { LOG_ERROR(LOG_TAG, "%s unable to find L2CAP client matching LCID 0x%04x.", __func__, local_channel_id); return; } memset(&response, 0, sizeof(response)); 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(LOG_TAG, "%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(LOG_TAG, "%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(LOG_TAG, "%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(LOG_TAG, "%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(LOG_TAG, "%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(LOG_TAG, "%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(LOG_TAG, "%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(LOG_TAG, "%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); osi_free(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 = osi_malloc(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 osi_free(bt_packet); break; } BT_HDR *fragment = osi_malloc(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(LOG_TAG, "%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; }