int l2cap_send_prepared_connectionless(uint16_t handle, uint16_t cid, uint16_t len){ if (!hci_can_send_packet_now(HCI_ACL_DATA_PACKET)){ log_info("l2cap_send_prepared_to_handle cid %u, cannot send\n", cid); return BTSTACK_ACL_BUFFERS_FULL; } log_debug("l2cap_send_prepared_to_handle cid %u, handle %u\n", cid, handle); uint8_t *acl_buffer = hci_get_outgoing_acl_packet_buffer(); // 0 - Connection handle : PB=10 : BC=00 bt_store_16(acl_buffer, 0, handle | (2 << 12) | (0 << 14)); // 2 - ACL length bt_store_16(acl_buffer, 2, len + 4); // 4 - L2CAP packet length bt_store_16(acl_buffer, 4, len + 0); // 6 - L2CAP channel DEST bt_store_16(acl_buffer, 6, cid); // send int err = hci_send_acl_packet(acl_buffer, len+8); l2cap_hand_out_credits(); return err; }
// // MARK: ATT_FIND_BY_TYPE_VALUE // // "Only attributes with attribute handles between and including the Starting Handle parameter // and the Ending Handle parameter that match the requested attri- bute type and the attribute // value that have sufficient permissions to allow reading will be returned" -> (1) // // TODO: handle other types then GATT_PRIMARY_SERVICE_UUID and GATT_SECONDARY_SERVICE_UUID // // NOTE: doesn't handle DYNAMIC values // NOTE: only supports 16 bit UUIDs // static uint16_t handle_find_by_type_value_request2(att_connection_t * att_connection, uint8_t * response_buffer, uint16_t response_buffer_size, uint16_t start_handle, uint16_t end_handle, uint16_t attribute_type, uint16_t attribute_len, uint8_t* attribute_value){ log_info("ATT_FIND_BY_TYPE_VALUE_REQUEST: from %04X to %04X, type %04X, value: ", start_handle, end_handle, attribute_type); hexdump(attribute_value, attribute_len); uint8_t request_type = ATT_FIND_BY_TYPE_VALUE_REQUEST; if (start_handle > end_handle || start_handle == 0){ return setup_error_invalid_handle(response_buffer, request_type, start_handle); } uint16_t offset = 1; uint16_t in_group = 0; uint16_t prev_handle = 0; att_iterator_t it; att_iterator_init(&it); while (att_iterator_has_next(&it)){ att_iterator_fetch_next(&it); if (it.handle && it.handle < start_handle) continue; if (it.handle > end_handle) break; // (1) // close current tag, if within a group and a new service definition starts or we reach end of att db if (in_group && (it.handle == 0 || att_iterator_match_uuid16(&it, GATT_PRIMARY_SERVICE_UUID) || att_iterator_match_uuid16(&it, GATT_SECONDARY_SERVICE_UUID))){ log_info("End of group, handle 0x%04x", prev_handle); bt_store_16(response_buffer, offset, prev_handle); offset += 2; in_group = 0; // check if space for another handle pair available if (offset + 4 > response_buffer_size){ break; } } // keep track of previous handle prev_handle = it.handle; // does current attribute match if (it.handle && att_iterator_match_uuid16(&it, attribute_type) && attribute_len == it.value_len && memcmp(attribute_value, it.value, it.value_len) == 0){ log_info("Begin of group, handle 0x%04x", it.handle); bt_store_16(response_buffer, offset, it.handle); offset += 2; in_group = 1; } } if (offset == 1){ return setup_error_atribute_not_found(response_buffer, request_type, start_handle); } response_buffer[0] = ATT_FIND_BY_TYPE_VALUE_RESPONSE; return offset; }
void l2cap_emit_connection_request(l2cap_channel_t *channel) { uint8_t event[16]; event[0] = L2CAP_EVENT_INCOMING_CONNECTION; event[1] = sizeof(event) - 2; bt_flip_addr(&event[2], channel->address); bt_store_16(event, 8, channel->handle); bt_store_16(event, 10, channel->psm); bt_store_16(event, 12, channel->local_cid); bt_store_16(event, 14, channel->remote_cid); hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); l2cap_dispatch(channel, HCI_EVENT_PACKET, event, sizeof(event)); }
static void att_emit_mtu_event(uint16_t handle, uint16_t mtu){ if (!att_client_packet_handler) return; uint8_t event[6]; int pos = 0; event[pos++] = ATT_MTU_EXCHANGE_COMPLETE; event[pos++] = sizeof(event) - 2; bt_store_16(event, pos, handle); pos += 2; bt_store_16(event, pos, mtu); pos += 2; (*att_client_packet_handler)(HCI_EVENT_PACKET, 0, &event[0], sizeof(event)); }
/** * send HCI packet to single connection */ void socket_connection_send_packet(connection_t *conn, uint16_t type, uint16_t channel, uint8_t *packet, uint16_t size){ uint8_t header[sizeof(packet_header_t)]; bt_store_16(header, 0, type); bt_store_16(header, 2, channel); bt_store_16(header, 4, size); #ifdef HAVE_SO_NOSIGPIPE // BSD Variants like Darwin and iOS write(conn->ds.fd, header, 6); write(conn->ds.fd, packet, size); #else // Linux send(conn->ds.fd, header, 6, MSG_NOSIGNAL); send(conn->ds.fd, packet, size, MSG_NOSIGNAL); #endif }
static void att_handle_value_indication_notify_client(uint8_t status, uint16_t client_handle, uint16_t attribute_handle){ if (!att_client_packet_handler) return; uint8_t event[7]; int pos = 0; event[pos++] = ATT_HANDLE_VALUE_INDICATION_COMPLETE; event[pos++] = sizeof(event) - 2; event[pos++] = status; bt_store_16(event, pos, client_handle); pos += 2; bt_store_16(event, pos, attribute_handle); pos += 2; (*att_client_packet_handler)(HCI_EVENT_PACKET, 0, &event[0], sizeof(event)); }
static uint16_t setup_error(uint8_t * response_buffer, uint16_t request, uint16_t handle, uint8_t error_code){ response_buffer[0] = ATT_ERROR_RESPONSE; response_buffer[1] = request; bt_store_16(response_buffer, 2, handle); response_buffer[4] = error_code; return 5; }
// // MARK: ATT_READ_BY_TYPE_REQUEST // static uint16_t handle_read_by_type_request2(uint8_t * response_buffer, uint16_t response_buffer_size, uint16_t start_handle, uint16_t end_handle, uint16_t attribute_type_len, uint8_t * attribute_type){ printf("ATT_READ_BY_TYPE_REQUEST: from %04X to %04X, type: ", start_handle, end_handle); hexdump2(attribute_type, attribute_type_len); uint16_t offset = 1; uint16_t pair_len = 0; att_iterator_t it; att_iterator_init(&it); while (att_iterator_has_next(&it)){ att_iterator_fetch_next(&it); if (!it.handle) break; if (it.handle < start_handle) continue; if (it.handle > end_handle) break; // (1) // does current attribute match if (!att_iterator_match_uuid(&it, attribute_type, attribute_type_len)) continue; att_update_value_len(&it); // check if value has same len as last one uint16_t this_pair_len = 2 + it.value_len; if (offset > 1){ if (pair_len != this_pair_len) { break; } } // first if (offset == 1) { pair_len = this_pair_len; response_buffer[offset] = pair_len; offset++; } // space? if (offset + pair_len > response_buffer_size) { if (offset > 2) break; it.value_len = response_buffer_size - 4; } // store bt_store_16(response_buffer, offset, it.handle); offset += 2; uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len); offset += bytes_copied; } if (offset == 1){ return setup_error_atribute_not_found(response_buffer, ATT_READ_BY_TYPE_REQUEST, start_handle); } response_buffer[0] = ATT_READ_BY_TYPE_RESPONSE; return offset; }
// // MARK: ATT_FIND_INFORMATION_REQUEST // // TODO: handle other types then GATT_PRIMARY_SERVICE_UUID and GATT_SECONDARY_SERVICE_UUID // static uint16_t handle_find_information_request2(uint8_t * response_buffer, uint16_t response_buffer_size, uint16_t start_handle, uint16_t end_handle){ printf("ATT_FIND_INFORMATION_REQUEST: from %04X to %04X\n", start_handle, end_handle); uint16_t offset = 1; uint16_t pair_len = 0; att_iterator_t it; att_iterator_init(&it); while (att_iterator_has_next(&it)){ att_iterator_fetch_next(&it); if (!it.handle) break; if (it.handle > end_handle) break; if (it.handle < start_handle) continue; att_update_value_len(&it); // printf("Handle 0x%04x\n", it.handle); // check if value has same len as last one uint16_t this_pair_len = 2 + it.value_len; if (offset > 1){ if (pair_len != this_pair_len) { break; } } // first if (offset == 1) { pair_len = this_pair_len; if (it.value_len == 2) { response_buffer[offset] = 0x01; // format } else { response_buffer[offset] = 0x02; } offset++; } // space? if (offset + pair_len > response_buffer_size) { if (offset > 2) break; it.value_len = response_buffer_size - 4; } // store bt_store_16(response_buffer, offset, it.handle); offset += 2; uint16_t bytes_copied = att_copy_value(&it, 0, response_buffer + offset, it.value_len); offset += bytes_copied; } if (offset == 1){ return setup_error_atribute_not_found(response_buffer, ATT_FIND_INFORMATION_REQUEST, start_handle); } response_buffer[0] = ATT_FIND_INFORMATION_REPLY; return offset; }
void hci_emit_l2cap_check_timeout(hci_connection_t *conn){ uint8_t event[4]; event[0] = L2CAP_EVENT_TIMEOUT_CHECK; event[1] = sizeof(event) - 2; bt_store_16(event, 2, conn->con_handle); hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); hci_stack.packet_handler(HCI_EVENT_PACKET, event, sizeof(event)); }
void l2cap_emit_channel_closed(l2cap_channel_t *channel) { uint8_t event[4]; event[0] = L2CAP_EVENT_CHANNEL_CLOSED; event[1] = sizeof(event) - 2; bt_store_16(event, 2, channel->local_cid); hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); l2cap_dispatch(channel, HCI_EVENT_PACKET, event, sizeof(event)); }
// // MARK: ATT_FIND_INFORMATION_REQUEST // // TODO: handle other types then GATT_PRIMARY_SERVICE_UUID and GATT_SECONDARY_SERVICE_UUID // static uint16_t handle_find_information_request2(att_connection_t * att_connection, uint8_t * response_buffer, uint16_t response_buffer_size, uint16_t start_handle, uint16_t end_handle){ log_info("ATT_FIND_INFORMATION_REQUEST: from %04X to %04X", start_handle, end_handle); uint8_t request_type = ATT_FIND_INFORMATION_REQUEST; if (start_handle > end_handle || start_handle == 0){ return setup_error_invalid_handle(response_buffer, request_type, start_handle); } uint16_t offset = 1; uint16_t uuid_len = 0; att_iterator_t it; att_iterator_init(&it); while (att_iterator_has_next(&it)){ att_iterator_fetch_next(&it); if (!it.handle) break; if (it.handle > end_handle) break; if (it.handle < start_handle) continue; // log_info("Handle 0x%04x", it.handle); uint16_t this_uuid_len = (it.flags & ATT_PROPERTY_UUID128) ? 16 : 2; // check if value has same len as last one if not first result if (offset > 1){ if (this_uuid_len != uuid_len) { break; } } // first if (offset == 1) { uuid_len = this_uuid_len; // set format field response_buffer[offset] = (it.flags & ATT_PROPERTY_UUID128) ? 0x02 : 0x01; offset++; } // space? if (offset + 2 + uuid_len > response_buffer_size) break; // store bt_store_16(response_buffer, offset, it.handle); offset += 2; memcpy(response_buffer + offset, it.uuid, uuid_len); offset += uuid_len; } if (offset == 1){ return setup_error_atribute_not_found(response_buffer, request_type, start_handle); } response_buffer[0] = ATT_FIND_INFORMATION_REPLY; return offset; }
uint16_t l2cap_create_signaling_internal(uint8_t * acl_buffer, hci_con_handle_t handle, uint16_t cid, L2CAP_SIGNALING_COMMANDS cmd, uint8_t identifier, va_list argptr){ const char *format = l2cap_signaling_commands_format[cmd-1]; uint16_t word; uint8_t * ptr; int pb = hci_non_flushable_packet_boundary_flag_supported() ? 0x00 : 0x02; // 0 - Connection handle : PB=pb : BC=00 bt_store_16(acl_buffer, 0, handle | (pb << 12) | (0 << 14)); // 6 - L2CAP channel = 1 bt_store_16(acl_buffer, 6, cid); // 8 - Code acl_buffer[8] = cmd; // 9 - id (!= 0 sequentially) acl_buffer[9] = identifier; // 12 - L2CAP signaling parameters uint16_t pos = 12; // skip AMP commands if (cmd >= CONNECTION_PARAMETER_UPDATE_REQUEST){ cmd -= 6; } while (*format) { switch(*format) { case '1': // 8 bit value case '2': // 16 bit value word = va_arg(argptr, int); // minimal va_arg is int: 2 bytes on 8+16 bit CPUs acl_buffer[pos++] = word & 0xff; if (*format == '2') { acl_buffer[pos++] = word >> 8; } break; case 'D': // variable data. passed: len, ptr word = va_arg(argptr, int); ptr = va_arg(argptr, uint8_t *); memcpy(&acl_buffer[pos], ptr, word); pos += word; break; default: break; } format++; };
static void l2cap_emit_service_registered(void *connection, uint8_t status, uint16_t psm){ uint8_t event[5]; event[0] = L2CAP_EVENT_SERVICE_REGISTERED; event[1] = sizeof(event) - 2; event[2] = status; bt_store_16(event, 3, psm); hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); (*packet_handler)(connection, HCI_EVENT_PACKET, 0, event, sizeof(event)); }
static void hfp_emit_audio_connection_established_event(hfp_callback_t callback, uint8_t value, uint16_t sco_handle){ if (!callback) return; uint8_t event[6]; event[0] = HCI_EVENT_HFP_META; event[1] = sizeof(event) - 2; event[2] = HFP_SUBEVENT_AUDIO_CONNECTION_ESTABLISHED; event[3] = value; // status 0 == OK bt_store_16(event, 4, sco_handle); (*callback)(event, sizeof(event)); }
static void emit_event_audio_connected(uint8_t status, uint16_t handle){ if (!hsp_hs_callback) return; uint8_t event[6]; event[0] = HCI_EVENT_HSP_META; event[1] = sizeof(event) - 2; event[2] = HSP_SUBEVENT_AUDIO_CONNECTION_COMPLETE; event[3] = status; bt_store_16(event, 4, handle); (*hsp_hs_callback)(event, sizeof(event)); }
void hci_emit_btstack_version() { uint8_t event[6]; event[0] = BTSTACK_EVENT_VERSION; event[1] = sizeof(event) - 2; event[2] = BTSTACK_MAJOR; event[3] = BTSTACK_MINOR; bt_store_16(event, 4, BTSTACK_REVISION); hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); hci_stack.packet_handler(HCI_EVENT_PACKET, event, sizeof(event)); }
void hci_emit_disconnection_complete(uint16_t handle, uint8_t reason){ uint8_t event[6]; event[0] = HCI_EVENT_DISCONNECTION_COMPLETE; event[1] = sizeof(event) - 2; event[2] = 0; // status = OK bt_store_16(event, 3, handle); event[5] = reason; hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); hci_stack.packet_handler(HCI_EVENT_PACKET, event, sizeof(event)); }
void send_ucd_packet(void){ l2cap_reserve_packet_buffer(); int ucd_size = 50; uint8_t * ucd_buffer = l2cap_get_outgoing_buffer(); bt_store_16(ucd_buffer, 0, 0x2211); int i; for (i=2; i< ucd_size ; i++){ ucd_buffer[i] = i; } l2cap_send_prepared_connectionless(handle, L2CAP_CID_CONNECTIONLESS_CHANNEL, ucd_size); }
void hci_emit_connection_complete(hci_connection_t *conn, uint8_t status){ uint8_t event[13]; event[0] = HCI_EVENT_CONNECTION_COMPLETE; event[1] = sizeof(event) - 2; event[2] = status; bt_store_16(event, 3, conn->con_handle); bt_flip_addr(&event[5], conn->address); event[11] = 1; // ACL connection event[12] = 0; // encryption disabled hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); hci_stack.packet_handler(HCI_EVENT_PACKET, event, sizeof(event)); }
// MARK: helper for ATT_HANDLE_VALUE_NOTIFICATION and ATT_HANDLE_VALUE_INDICATION static uint16_t prepare_handle_value(att_connection_t * att_connection, uint16_t handle, uint8_t *value, uint16_t value_len, uint8_t * response_buffer){ bt_store_16(response_buffer, 1, handle); if (value_len > att_connection->mtu - 3){ value_len = att_connection->mtu - 3; } memcpy(&response_buffer[3], value, value_len); return value_len + 3; }
void mock_simulate_sm_data_packet(uint8_t * packet, uint16_t len) { uint16_t handle = 0x40; uint16_t cid = 0x06; uint8_t acl_buffer[len + 8]; // 0 - Connection handle : PB=10 : BC=00 bt_store_16(acl_buffer, 0, handle | (0 << 12) | (0 << 14)); // 2 - ACL length bt_store_16(acl_buffer, 2, len + 4); // 4 - L2CAP packet length bt_store_16(acl_buffer, 4, len + 0); // 6 - L2CAP channel DEST bt_store_16(acl_buffer, 6, cid); memcpy(&acl_buffer[8], packet, len); hci_dump_packet(HCI_ACL_DATA_PACKET, 1, &acl_buffer[0], len + 8); le_data_handler(SM_DATA_PACKET, handle, packet, len); }
// // MARK: ATT_EXCHANGE_MTU_REQUEST // static uint16_t handle_exchange_mtu_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, uint8_t * response_buffer){ uint16_t client_rx_mtu = READ_BT_16(request_buffer, 1); if (client_rx_mtu < att_connection->mtu){ att_connection->mtu = client_rx_mtu; } response_buffer[0] = ATT_EXCHANGE_MTU_RESPONSE; bt_store_16(response_buffer, 1, att_connection->mtu); return 3; }
int l2cap_send_prepared(uint16_t local_cid, uint16_t len){ if (!hci_can_send_packet_now(HCI_ACL_DATA_PACKET)){ log_info("l2cap_send_internal cid %u, cannot send\n", local_cid); return BTSTACK_ACL_BUFFERS_FULL; } l2cap_channel_t * channel = l2cap_get_channel_for_local_cid(local_cid); if (!channel) { log_error("l2cap_send_internal no channel for cid %u\n", local_cid); return -1; // TODO: define error } if (channel->packets_granted == 0){ log_error("l2cap_send_internal cid %u, no credits!\n", local_cid); return -1; // TODO: define error } --channel->packets_granted; log_debug("l2cap_send_internal cid %u, handle %u, 1 credit used, credits left %u;\n", local_cid, channel->handle, channel->packets_granted); uint8_t *acl_buffer = hci_get_outgoing_acl_packet_buffer(); // 0 - Connection handle : PB=10 : BC=00 bt_store_16(acl_buffer, 0, channel->handle | (2 << 12) | (0 << 14)); // 2 - ACL length bt_store_16(acl_buffer, 2, len + 4); // 4 - L2CAP packet length bt_store_16(acl_buffer, 4, len + 0); // 6 - L2CAP channel DEST bt_store_16(acl_buffer, 6, channel->remote_cid); // send int err = hci_send_acl_packet(acl_buffer, len+8); l2cap_hand_out_credits(); return err; }
int l2cap_send_connectionless(uint16_t handle, uint16_t cid, uint8_t * buffer, uint16_t len) { // printf("l2cap_send_connectionless\n"); int pb = hci_non_flushable_packet_boundary_flag_supported() ? 0x00 : 0x02; // 0 - Connection handle : PB=pb : BC=00 bt_store_16(packet_buffer, 0, handle | (pb << 12) | (0 << 14)); // 2 - ACL length bt_store_16(packet_buffer, 2, len + 4); // 4 - L2CAP packet length bt_store_16(packet_buffer, 4, len + 0); // 6 - L2CAP channel DEST bt_store_16(packet_buffer, 6, cid); memcpy(&packet_buffer[8], buffer, len); hci_dump_packet(HCI_ACL_DATA_PACKET, 0, &packet_buffer[0], len + 8); dump_packet(HCI_ACL_DATA_PACKET, packet_buffer, len + 8); packet_buffer_len = len + 8; return 0; }
static void try_send_sco(void){ if (!sco_handle) return; if (!hci_can_send_sco_packet_now(sco_handle)) { // printf("try_send_sco, cannot send now\n"); return; } printf("try send handle %x\n", sco_handle); const int frames_per_packet = 24; hci_reserve_packet_buffer(); uint8_t * sco_packet = hci_get_outgoing_packet_buffer(); // set handle + flags bt_store_16(sco_packet, 0, sco_handle); // set len sco_packet[2] = frames_per_packet * 2; // 16 bit PCM int i; for (i=0;i<frames_per_packet;i++){ bt_store_16(sco_packet, 3 + 2*i, sine[phase]); phase++; if (phase >= TABLE_SIZE) phase = 0; } hci_send_sco_packet_buffer(3 + frames_per_packet * 2); }
void l2cap_emit_credits(l2cap_channel_t *channel, uint8_t credits) { // track credits channel->packets_granted += credits; // log_info("l2cap_emit_credits for cid %u, credits given: %u (+%u)\n", channel->local_cid, channel->packets_granted, credits); uint8_t event[5]; event[0] = L2CAP_EVENT_CREDITS; event[1] = sizeof(event) - 2; bt_store_16(event, 2, channel->local_cid); event[4] = credits; hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); l2cap_dispatch(channel, HCI_EVENT_PACKET, event, sizeof(event)); }
uint16_t l2cap_create_signaling_internal(uint8_t * acl_buffer, hci_con_handle_t handle, L2CAP_SIGNALING_COMMANDS cmd, uint8_t identifier, va_list argptr){ // 0 - Connection handle : PB=10 : BC=00 bt_store_16(acl_buffer, 0, handle | (2 << 12) | (0 << 14)); // 6 - L2CAP channel = 1 bt_store_16(acl_buffer, 6, 1); // 8 - Code acl_buffer[8] = cmd; // 9 - id (!= 0 sequentially) acl_buffer[9] = identifier; // 12 - L2CAP signaling parameters uint16_t pos = 12; const char *format = l2cap_signaling_commands_format[cmd-1]; uint16_t word; uint8_t * ptr; while (*format) { switch(*format) { case '1': // 8 bit value case '2': // 16 bit value word = va_arg(argptr, int); // minimal va_arg is int: 2 bytes on 8+16 bit CPUs acl_buffer[pos++] = word & 0xff; if (*format == '2') { acl_buffer[pos++] = word >> 8; } break; case 'D': // variable data. passed: len, ptr word = va_arg(argptr, int); ptr = va_arg(argptr, uint8_t *); memcpy(&acl_buffer[pos], ptr, word); pos += word; break; default: break; } format++; };
// // MARK: ATT_EXCHANGE_MTU_REQUEST // static uint16_t handle_exchange_mtu_request(att_connection_t * att_connection, uint8_t * request_buffer, uint16_t request_len, uint8_t * response_buffer){ uint16_t client_rx_mtu = READ_BT_16(request_buffer, 1); // find min(local max mtu, remote mtu) and use as mtu for this connection if (client_rx_mtu < att_connection->max_mtu){ att_connection->mtu = client_rx_mtu; } else { att_connection->mtu = att_connection->max_mtu; } response_buffer[0] = ATT_EXCHANGE_MTU_RESPONSE; bt_store_16(response_buffer, 1, att_connection->mtu); return 3; }
void l2cap_emit_channel_opened(l2cap_channel_t *channel, uint8_t status) { uint8_t event[21]; event[0] = L2CAP_EVENT_CHANNEL_OPENED; event[1] = sizeof(event) - 2; event[2] = status; bt_flip_addr(&event[3], channel->address); bt_store_16(event, 9, channel->handle); bt_store_16(event, 11, channel->psm); bt_store_16(event, 13, channel->local_cid); bt_store_16(event, 15, channel->remote_cid); bt_store_16(event, 17, channel->local_mtu); bt_store_16(event, 19, channel->remote_mtu); hci_dump_packet( HCI_EVENT_PACKET, 0, event, sizeof(event)); l2cap_dispatch(channel, HCI_EVENT_PACKET, event, sizeof(event)); }