| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fake_controller.h" |
| |
| #include <endian.h> |
| #include <lib/async/cpp/task.h> |
| |
| #include "garnet/drivers/bluetooth/lib/common/log.h" |
| #include "garnet/drivers/bluetooth/lib/common/packet_view.h" |
| #include "garnet/drivers/bluetooth/lib/hci/defaults.h" |
| #include "garnet/drivers/bluetooth/lib/hci/hci.h" |
| #include "garnet/drivers/bluetooth/lib/hci/util.h" |
| #include "garnet/drivers/bluetooth/lib/testing/fake_device.h" |
| #include "lib/fxl/strings/string_printf.h" |
| |
| namespace btlib { |
| |
| using common::BufferView; |
| using common::ByteBuffer; |
| using common::StaticByteBuffer; |
| |
| namespace testing { |
| namespace { |
| |
| template <typename NUM_TYPE, typename ENUM_TYPE> |
| void SetBit(NUM_TYPE* num_type, ENUM_TYPE bit) { |
| *num_type |= static_cast<NUM_TYPE>(bit); |
| } |
| |
| template <typename NUM_TYPE, typename ENUM_TYPE> |
| void UnsetBit(NUM_TYPE* num_type, ENUM_TYPE bit) { |
| *num_type &= ~static_cast<NUM_TYPE>(bit); |
| } |
| |
| template <typename NUM_TYPE, typename ENUM_TYPE> |
| bool CheckBit(NUM_TYPE num_type, ENUM_TYPE bit) { |
| return (num_type & static_cast<NUM_TYPE>(bit)); |
| } |
| |
| hci::LEPeerAddressType ToPeerAddrType(common::DeviceAddress::Type type) { |
| hci::LEPeerAddressType result = hci::LEPeerAddressType::kAnonymous; |
| |
| switch (type) { |
| case common::DeviceAddress::Type::kLEPublic: |
| result = hci::LEPeerAddressType::kPublic; |
| break; |
| case common::DeviceAddress::Type::kLERandom: |
| result = hci::LEPeerAddressType::kRandom; |
| break; |
| default: |
| break; |
| } |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| FakeController::Settings::Settings() { |
| std::memset(this, 0, sizeof(*this)); |
| hci_version = hci::HCIVersion::k5_0; |
| num_hci_command_packets = 250; |
| } |
| |
| void FakeController::Settings::ApplyDualModeDefaults() { |
| std::memset(this, 0, sizeof(*this)); |
| hci_version = hci::HCIVersion::k5_0; |
| num_hci_command_packets = 250; |
| |
| acl_data_packet_length = 512; |
| total_num_acl_data_packets = 1; |
| le_acl_data_packet_length = 512; |
| le_total_num_acl_data_packets = 1; |
| |
| SetBit(&lmp_features_page0, hci::LMPFeature::kLESupported); |
| SetBit(&lmp_features_page0, hci::LMPFeature::kSimultaneousLEAndBREDR); |
| SetBit(&lmp_features_page0, hci::LMPFeature::kExtendedFeatures); |
| SetBit(&lmp_features_page0, hci::LMPFeature::kRSSIwithInquiryResults); |
| SetBit(&lmp_features_page0, hci::LMPFeature::kExtendedInquiryResponse); |
| |
| AddBREDRSupportedCommands(); |
| AddLESupportedCommands(); |
| } |
| |
| void FakeController::Settings::ApplyLEOnlyDefaults() { |
| ApplyDualModeDefaults(); |
| |
| UnsetBit(&lmp_features_page0, hci::LMPFeature::kSimultaneousLEAndBREDR); |
| SetBit(&lmp_features_page0, hci::LMPFeature::kBREDRNotSupported); |
| |
| std::memset(supported_commands, 0, sizeof(supported_commands)); |
| AddLESupportedCommands(); |
| } |
| |
| void FakeController::Settings::AddBREDRSupportedCommands() { |
| SetBit(supported_commands + 7, hci::SupportedCommand::kWriteLocalName); |
| SetBit(supported_commands + 7, hci::SupportedCommand::kReadLocalName); |
| SetBit(supported_commands + 7, hci::SupportedCommand::kReadScanEnable); |
| SetBit(supported_commands + 7, hci::SupportedCommand::kWriteScanEnable); |
| SetBit(supported_commands + 8, hci::SupportedCommand::kReadPageScanActivity); |
| SetBit(supported_commands + 8, hci::SupportedCommand::kWritePageScanActivity); |
| SetBit(supported_commands + 12, hci::SupportedCommand::kReadInquiryMode); |
| SetBit(supported_commands + 12, hci::SupportedCommand::kWriteInquiryMode); |
| SetBit(supported_commands + 13, hci::SupportedCommand::kReadPageScanType); |
| SetBit(supported_commands + 13, hci::SupportedCommand::kWritePageScanType); |
| SetBit(supported_commands + 14, hci::SupportedCommand::kReadBufferSize); |
| SetBit(supported_commands + 17, |
| hci::SupportedCommand::kReadSimplePairingMode); |
| SetBit(supported_commands + 17, |
| hci::SupportedCommand::kWriteSimplePairingMode); |
| } |
| |
| void FakeController::Settings::AddLESupportedCommands() { |
| SetBit(supported_commands, hci::SupportedCommand::kDisconnect); |
| SetBit(supported_commands + 5, hci::SupportedCommand::kSetEventMask); |
| SetBit(supported_commands + 5, hci::SupportedCommand::kReset); |
| SetBit(supported_commands + 14, |
| hci::SupportedCommand::kReadLocalVersionInformation); |
| SetBit(supported_commands + 14, |
| hci::SupportedCommand::kReadLocalSupportedFeatures); |
| SetBit(supported_commands + 14, |
| hci::SupportedCommand::kReadLocalExtendedFeatures); |
| SetBit(supported_commands + 24, hci::SupportedCommand::kWriteLEHostSupport); |
| SetBit(supported_commands + 25, hci::SupportedCommand::kLESetEventMask); |
| SetBit(supported_commands + 25, hci::SupportedCommand::kLEReadBufferSize); |
| SetBit(supported_commands + 25, |
| hci::SupportedCommand::kLEReadLocalSupportedFeatures); |
| SetBit(supported_commands + 25, hci::SupportedCommand::kLESetRandomAddress); |
| SetBit(supported_commands + 25, |
| hci::SupportedCommand::kLESetAdvertisingParameters); |
| SetBit(supported_commands + 25, hci::SupportedCommand::kLESetAdvertisingData); |
| SetBit(supported_commands + 26, |
| hci::SupportedCommand::kLESetScanResponseData); |
| SetBit(supported_commands + 26, |
| hci::SupportedCommand::kLESetAdvertisingEnable); |
| SetBit(supported_commands + 26, hci::SupportedCommand::kLECreateConnection); |
| SetBit(supported_commands + 26, |
| hci::SupportedCommand::kLECreateConnectionCancel); |
| SetBit(supported_commands + 27, hci::SupportedCommand::kLEConnectionUpdate); |
| } |
| |
| void FakeController::Settings::ApplyLegacyLEConfig() { |
| ApplyLEOnlyDefaults(); |
| |
| hci_version = hci::HCIVersion::k4_2; |
| |
| SetBit(supported_commands + 26, hci::SupportedCommand::kLESetScanParameters); |
| SetBit(supported_commands + 26, hci::SupportedCommand::kLESetScanEnable); |
| } |
| |
| void FakeController::Settings::ApplyLEConfig() { |
| ApplyLEOnlyDefaults(); |
| |
| SetBit(&le_features, hci::LESupportedFeature::kLEExtendedAdvertising); |
| } |
| |
| FakeController::LEScanState::LEScanState() |
| : enabled(false), |
| scan_type(hci::LEScanType::kPassive), |
| scan_interval(0), |
| scan_window(0), |
| filter_duplicates(false), |
| filter_policy(hci::LEScanFilterPolicy::kNoWhiteList) {} |
| |
| FakeController::LEAdvertisingState::LEAdvertisingState() |
| : enabled(false), interval(0), data_length(0), scan_rsp_length(0) { |
| std::memset(data, 0, sizeof(data)); |
| std::memset(scan_rsp_data, 0, sizeof(scan_rsp_data)); |
| } |
| |
| FakeController::FakeController() |
| : page_scan_type_(hci::PageScanType::kStandardScan), |
| page_scan_interval_(0x0800), |
| page_scan_window_(0x0012), |
| next_conn_handle_(0u), |
| le_connect_pending_(false), |
| next_le_sig_id_(1u), |
| scan_state_cb_dispatcher_(nullptr), |
| advertising_state_cb_dispatcher_(nullptr), |
| conn_state_cb_dispatcher_(nullptr), |
| le_conn_params_cb_dispatcher_(nullptr) {} |
| |
| FakeController::~FakeController() { Stop(); } |
| |
| void FakeController::SetDefaultResponseStatus(hci::OpCode opcode, |
| hci::StatusCode status) { |
| ZX_DEBUG_ASSERT(status != hci::StatusCode::kSuccess); |
| default_status_map_[opcode] = status; |
| } |
| |
| void FakeController::ClearDefaultResponseStatus(hci::OpCode opcode) { |
| default_status_map_.erase(opcode); |
| } |
| |
| void FakeController::AddDevice(std::unique_ptr<FakeDevice> device) { |
| device->set_ctrl(this); |
| devices_.push_back(std::move(device)); |
| } |
| |
| void FakeController::SetScanStateCallback(ScanStateCallback callback, |
| async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT(callback); |
| ZX_DEBUG_ASSERT(dispatcher); |
| |
| scan_state_cb_ = std::move(callback); |
| scan_state_cb_dispatcher_ = dispatcher; |
| } |
| |
| void FakeController::SetAdvertisingStateCallback( |
| fit::closure callback, async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT(callback); |
| ZX_DEBUG_ASSERT(dispatcher); |
| |
| advertising_state_cb_ = std::move(callback); |
| advertising_state_cb_dispatcher_ = dispatcher; |
| } |
| |
| void FakeController::SetConnectionStateCallback( |
| ConnectionStateCallback callback, async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT(callback); |
| ZX_DEBUG_ASSERT(dispatcher); |
| |
| conn_state_cb_ = std::move(callback); |
| conn_state_cb_dispatcher_ = dispatcher; |
| } |
| |
| void FakeController::SetLEConnectionParametersCallback( |
| LEConnectionParametersCallback callback, async_dispatcher_t* dispatcher) { |
| ZX_DEBUG_ASSERT(callback); |
| ZX_DEBUG_ASSERT(dispatcher); |
| |
| le_conn_params_cb_ = std::move(callback); |
| le_conn_params_cb_dispatcher_ = dispatcher; |
| } |
| |
| FakeDevice* FakeController::FindDeviceByAddress( |
| const common::DeviceAddress& addr) { |
| for (auto& dev : devices_) { |
| if (dev->address() == addr) |
| return dev.get(); |
| } |
| return nullptr; |
| } |
| |
| FakeDevice* FakeController::FindDeviceByConnHandle( |
| hci::ConnectionHandle handle) { |
| for (auto& dev : devices_) { |
| if (dev->HasLink(handle)) |
| return dev.get(); |
| } |
| return nullptr; |
| } |
| |
| uint8_t FakeController::NextL2CAPCommandId() { |
| // TODO(armansito): Guard against overflow? |
| return next_le_sig_id_++; |
| } |
| |
| void FakeController::RespondWithCommandComplete(hci::OpCode opcode, |
| const ByteBuffer& params) { |
| common::DynamicByteBuffer buffer(sizeof(hci::CommandCompleteEventParams) + |
| params.size()); |
| common::MutablePacketView<hci::CommandCompleteEventParams> event( |
| &buffer, params.size()); |
| |
| event.mutable_header()->num_hci_command_packets = |
| settings_.num_hci_command_packets; |
| event.mutable_header()->command_opcode = htole16(opcode); |
| event.mutable_payload_data().Write(params); |
| |
| SendEvent(hci::kCommandCompleteEventCode, buffer); |
| } |
| |
| void FakeController::RespondWithSuccess(hci::OpCode opcode) { |
| hci::SimpleReturnParams out_params; |
| out_params.status = hci::StatusCode::kSuccess; |
| RespondWithCommandComplete(opcode, |
| BufferView(&out_params, sizeof(out_params))); |
| } |
| |
| void FakeController::RespondWithCommandStatus(hci::OpCode opcode, |
| hci::StatusCode status) { |
| common::StaticByteBuffer<sizeof(hci::CommandStatusEventParams)> buffer; |
| common::MutablePacketView<hci::CommandStatusEventParams> event(&buffer); |
| |
| event.mutable_header()->status = status; |
| event.mutable_header()->num_hci_command_packets = |
| settings_.num_hci_command_packets; |
| event.mutable_header()->command_opcode = htole16(opcode); |
| |
| SendEvent(hci::kCommandStatusEventCode, buffer); |
| } |
| |
| void FakeController::SendEvent(hci::EventCode event_code, |
| const ByteBuffer& payload) { |
| common::DynamicByteBuffer buffer(sizeof(hci::EventHeader) + payload.size()); |
| common::MutablePacketView<hci::EventHeader> event(&buffer, payload.size()); |
| |
| event.mutable_header()->event_code = event_code; |
| event.mutable_header()->parameter_total_size = payload.size(); |
| event.mutable_payload_data().Write(payload); |
| |
| SendCommandChannelPacket(buffer); |
| } |
| |
| void FakeController::SendLEMetaEvent(hci::EventCode subevent_code, |
| const ByteBuffer& payload) { |
| common::DynamicByteBuffer buffer(sizeof(hci::LEMetaEventParams) + |
| payload.size()); |
| buffer[0] = subevent_code; |
| buffer.Write(payload, 1); |
| |
| SendEvent(hci::kLEMetaEventCode, buffer); |
| } |
| |
| void FakeController::SendACLPacket(hci::ConnectionHandle handle, |
| const ByteBuffer& payload) { |
| ZX_DEBUG_ASSERT(payload.size() <= hci::kMaxACLPayloadSize); |
| |
| common::DynamicByteBuffer buffer(sizeof(hci::ACLDataHeader) + payload.size()); |
| common::MutablePacketView<hci::ACLDataHeader> acl(&buffer, payload.size()); |
| |
| acl.mutable_header()->handle_and_flags = htole16(handle); |
| acl.mutable_header()->data_total_length = |
| htole16(static_cast<uint16_t>(payload.size())); |
| acl.mutable_payload_data().Write(payload); |
| |
| SendACLDataChannelPacket(buffer); |
| } |
| |
| void FakeController::SendL2CAPBFrame(hci::ConnectionHandle handle, |
| l2cap::ChannelId channel_id, |
| const ByteBuffer& payload) { |
| ZX_DEBUG_ASSERT(payload.size() <= |
| hci::kMaxACLPayloadSize - sizeof(l2cap::BasicHeader)); |
| |
| common::DynamicByteBuffer buffer(sizeof(l2cap::BasicHeader) + payload.size()); |
| common::MutablePacketView<l2cap::BasicHeader> bframe(&buffer, payload.size()); |
| |
| bframe.mutable_header()->length = htole16(payload.size()); |
| bframe.mutable_header()->channel_id = htole16(channel_id); |
| bframe.mutable_payload_data().Write(payload); |
| |
| SendACLPacket(handle, buffer); |
| } |
| |
| void FakeController::SendL2CAPCFrame(hci::ConnectionHandle handle, bool is_le, |
| l2cap::CommandCode code, uint8_t id, |
| const ByteBuffer& payload) { |
| common::DynamicByteBuffer buffer(sizeof(l2cap::CommandHeader) + |
| payload.size()); |
| common::MutablePacketView<l2cap::CommandHeader> cframe(&buffer, |
| payload.size()); |
| |
| cframe.mutable_header()->code = code; |
| cframe.mutable_header()->id = id; |
| cframe.mutable_header()->length = payload.size(); |
| cframe.mutable_payload_data().Write(payload); |
| |
| SendL2CAPBFrame( |
| handle, is_le ? l2cap::kLESignalingChannelId : l2cap::kSignalingChannelId, |
| buffer); |
| } |
| |
| void FakeController::SendNumberOfCompletedPacketsEvent( |
| hci::ConnectionHandle handle, uint16_t num) { |
| StaticByteBuffer<sizeof(hci::NumberOfCompletedPacketsEventParams) + |
| sizeof(hci::NumberOfCompletedPacketsEventData)> |
| buffer; |
| |
| auto* params = reinterpret_cast<hci::NumberOfCompletedPacketsEventParams*>( |
| buffer.mutable_data()); |
| params->number_of_handles = 1; |
| params->data->connection_handle = htole16(handle); |
| params->data->hc_num_of_completed_packets = htole16(num); |
| |
| SendEvent(hci::kNumberOfCompletedPacketsEventCode, buffer); |
| } |
| |
| void FakeController::ConnectLowEnergy(const common::DeviceAddress& addr, |
| hci::ConnectionRole role) { |
| async::PostTask(dispatcher(), [addr, role, this] { |
| FakeDevice* dev = FindDeviceByAddress(addr); |
| if (!dev) { |
| bt_log(WARN, "fake-hci", "no device found with address: %s", |
| addr.ToString().c_str()); |
| return; |
| } |
| |
| // TODO(armansito): Don't worry about managing multiple links per device |
| // until this supports Bluetooth classic. |
| if (dev->connected()) { |
| bt_log(WARN, "fake-hci", "device already connected"); |
| return; |
| } |
| |
| dev->set_connected(true); |
| hci::ConnectionHandle handle = ++next_conn_handle_; |
| dev->AddLink(handle); |
| |
| NotifyConnectionState(addr, true); |
| |
| auto interval_min = hci::defaults::kLEConnectionIntervalMin; |
| auto interval_max = hci::defaults::kLEConnectionIntervalMax; |
| |
| hci::LEConnectionParameters conn_params( |
| interval_min + ((interval_max - interval_min) / 2), 0, |
| hci::defaults::kLESupervisionTimeout); |
| dev->set_le_params(conn_params); |
| |
| hci::LEConnectionCompleteSubeventParams params; |
| std::memset(¶ms, 0, sizeof(params)); |
| |
| params.status = hci::StatusCode::kSuccess; |
| params.peer_address = addr.value(); |
| params.peer_address_type = ToPeerAddrType(addr.type()); |
| params.conn_latency = htole16(conn_params.latency()); |
| params.conn_interval = htole16(conn_params.interval()); |
| params.supervision_timeout = htole16(conn_params.supervision_timeout()); |
| params.role = role; |
| params.connection_handle = htole16(handle); |
| |
| SendLEMetaEvent(hci::kLEConnectionCompleteSubeventCode, |
| BufferView(¶ms, sizeof(params))); |
| }); |
| } |
| |
| void FakeController::L2CAPConnectionParameterUpdate( |
| const common::DeviceAddress& addr, |
| const hci::LEPreferredConnectionParameters& params) { |
| async::PostTask(dispatcher(), [addr, params, this] { |
| FakeDevice* dev = FindDeviceByAddress(addr); |
| if (!dev) { |
| bt_log(WARN, "fake-hci", "no device found with address: %s", |
| addr.ToString().c_str()); |
| return; |
| } |
| |
| if (!dev->connected()) { |
| bt_log(WARN, "fake-hci", "device not connected"); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(!dev->logical_links().empty()); |
| |
| l2cap::ConnectionParameterUpdateRequestPayload payload; |
| payload.interval_min = htole16(params.min_interval()); |
| payload.interval_max = htole16(params.max_interval()); |
| payload.slave_latency = htole16(params.max_latency()); |
| payload.timeout_multiplier = htole16(params.supervision_timeout()); |
| |
| // TODO(armansito): Instead of picking the first handle we should pick |
| // the handle that matches the current LE-U link. |
| SendL2CAPCFrame(*dev->logical_links().begin(), true, |
| l2cap::kConnectionParameterUpdateRequest, |
| NextL2CAPCommandId(), |
| BufferView(&payload, sizeof(payload))); |
| }); |
| } |
| |
| void FakeController::Disconnect(const common::DeviceAddress& addr) { |
| async::PostTask(dispatcher(), [addr, this] { |
| FakeDevice* dev = FindDeviceByAddress(addr); |
| if (!dev || !dev->connected()) { |
| bt_log(WARN, "fake-hci", "no connected device found with address: %s", |
| addr.ToString().c_str()); |
| return; |
| } |
| |
| auto links = dev->Disconnect(); |
| ZX_DEBUG_ASSERT(!dev->connected()); |
| ZX_DEBUG_ASSERT(!links.empty()); |
| |
| NotifyConnectionState(addr, false); |
| |
| for (auto link : links) { |
| hci::DisconnectionCompleteEventParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.connection_handle = htole16(link); |
| params.reason = hci::StatusCode::kRemoteUserTerminatedConnection; |
| SendEvent(hci::kDisconnectionCompleteEventCode, |
| BufferView(¶ms, sizeof(params))); |
| } |
| }); |
| } |
| |
| bool FakeController::MaybeRespondWithDefaultStatus(hci::OpCode opcode) { |
| auto iter = default_status_map_.find(opcode); |
| if (iter == default_status_map_.end()) |
| return false; |
| |
| bt_log(INFO, "fake-hci", |
| "responding with error (command: %#.4x, status: %#.2x)", opcode, |
| iter->second); |
| |
| hci::SimpleReturnParams params; |
| params.status = iter->second; |
| RespondWithCommandComplete(opcode, BufferView(¶ms, sizeof(params))); |
| return true; |
| } |
| |
| void FakeController::SendInquiryResponses() { |
| // TODO(jamuraa): combine some of these into a single response event |
| for (const auto& device : devices_) { |
| if (!device->has_inquiry_response()) { |
| continue; |
| } |
| |
| SendCommandChannelPacket(device->CreateInquiryResponseEvent(inquiry_mode_)); |
| inquiry_num_responses_left_--; |
| if (inquiry_num_responses_left_ == 0) { |
| break; |
| } |
| } |
| } |
| |
| void FakeController::SendAdvertisingReports() { |
| if (!le_scan_state_.enabled || devices_.empty()) |
| return; |
| |
| for (const auto& device : devices_) { |
| if (!device->has_advertising_reports()) { |
| continue; |
| } |
| // We want to send scan response packets only during an active scan and if |
| // the device is scannable. |
| bool need_scan_rsp = |
| (le_scan_state().scan_type == hci::LEScanType::kActive) && |
| device->scannable(); |
| SendCommandChannelPacket(device->CreateAdvertisingReportEvent( |
| need_scan_rsp && device->should_batch_reports())); |
| |
| // If the original report did not include a scan response then we send it as |
| // a separate event. |
| if (need_scan_rsp && !device->should_batch_reports()) { |
| SendCommandChannelPacket(device->CreateScanResponseReportEvent()); |
| } |
| } |
| |
| // We'll send new reports for the same devices if duplicate filtering is |
| // disabled. |
| if (!le_scan_state_.filter_duplicates) { |
| async::PostTask(dispatcher(), [this] { SendAdvertisingReports(); }); |
| } |
| } |
| |
| void FakeController::NotifyAdvertisingState() { |
| if (!advertising_state_cb_) { |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(advertising_state_cb_dispatcher_); |
| async::PostTask(advertising_state_cb_dispatcher_, |
| advertising_state_cb_.share()); |
| } |
| |
| void FakeController::NotifyConnectionState(const common::DeviceAddress& addr, |
| bool connected, bool canceled) { |
| if (!conn_state_cb_) |
| return; |
| |
| ZX_DEBUG_ASSERT(conn_state_cb_dispatcher_); |
| async::PostTask(conn_state_cb_dispatcher_, |
| [addr, connected, canceled, cb = conn_state_cb_.share()] { |
| cb(addr, connected, canceled); |
| }); |
| } |
| |
| void FakeController::NotifyLEConnectionParameters( |
| const common::DeviceAddress& addr, |
| const hci::LEConnectionParameters& params) { |
| if (!le_conn_params_cb_) |
| return; |
| |
| ZX_DEBUG_ASSERT(le_conn_params_cb_dispatcher_); |
| async::PostTask( |
| le_conn_params_cb_dispatcher_, |
| [addr, params, cb = le_conn_params_cb_.share()] { cb(addr, params); }); |
| } |
| |
| void FakeController::OnLECreateConnectionCommandReceived( |
| const hci::LECreateConnectionCommandParams& params) { |
| // Cannot issue this command while a request is already pending. |
| if (le_connect_pending_) { |
| RespondWithCommandStatus(hci::kLECreateConnection, |
| hci::StatusCode::kCommandDisallowed); |
| return; |
| } |
| |
| common::DeviceAddress::Type addr_type = |
| hci::AddressTypeFromHCI(params.peer_address_type); |
| ZX_DEBUG_ASSERT(addr_type != common::DeviceAddress::Type::kBREDR); |
| |
| const common::DeviceAddress peer_address(addr_type, params.peer_address); |
| hci::StatusCode status = hci::StatusCode::kSuccess; |
| |
| // Find the device that matches the requested address. |
| FakeDevice* device = FindDeviceByAddress(peer_address); |
| if (device) { |
| if (device->connected()) |
| status = hci::StatusCode::kConnectionAlreadyExists; |
| else |
| status = device->connect_status(); |
| } |
| |
| // First send the Command Status response. |
| RespondWithCommandStatus(hci::kLECreateConnection, status); |
| |
| // If we just sent back an error status then the operation is complete. |
| if (status != hci::StatusCode::kSuccess) |
| return; |
| |
| le_connect_pending_ = true; |
| pending_le_connect_addr_ = peer_address; |
| |
| // The procedure was initiated successfully but the device cannot be connected |
| // because it either doesn't exist or isn't connectable. |
| if (!device || !device->connectable()) { |
| bt_log(INFO, "fake-hci", |
| "requested fake device cannot be connected; request will time out"); |
| return; |
| } |
| |
| if (next_conn_handle_ == 0x0FFF) { |
| // Ran out of handles |
| status = hci::StatusCode::kConnectionLimitExceeded; |
| } else { |
| status = device->connect_response(); |
| } |
| |
| hci::LEConnectionCompleteSubeventParams response; |
| std::memset(&response, 0, sizeof(response)); |
| |
| response.status = status; |
| response.peer_address = params.peer_address; |
| response.peer_address_type = ToPeerAddrType(addr_type); |
| |
| if (status == hci::StatusCode::kSuccess) { |
| uint16_t interval_min = le16toh(params.conn_interval_min); |
| uint16_t interval_max = le16toh(params.conn_interval_max); |
| uint16_t interval = interval_min + ((interval_max - interval_min) / 2); |
| |
| hci::LEConnectionParameters conn_params( |
| interval, le16toh(params.conn_latency), |
| le16toh(params.supervision_timeout)); |
| device->set_le_params(conn_params); |
| |
| response.conn_latency = params.conn_latency; |
| response.conn_interval = le16toh(interval); |
| response.supervision_timeout = params.supervision_timeout; |
| |
| response.role = hci::ConnectionRole::kMaster; |
| |
| hci::ConnectionHandle handle = ++next_conn_handle_; |
| response.connection_handle = htole16(handle); |
| } |
| |
| // Don't send a connection event if we were asked to force the request to |
| // remain pending. This is used by test cases that operate during the pending |
| // state. |
| if (device->force_pending_connect()) |
| return; |
| |
| pending_le_connect_rsp_.Reset([response, device, this] { |
| le_connect_pending_ = false; |
| |
| if (response.status == hci::StatusCode::kSuccess) { |
| bool notify = !device->connected(); |
| device->AddLink(le16toh(response.connection_handle)); |
| if (notify && device->connected()) |
| NotifyConnectionState(device->address(), true); |
| } |
| |
| SendLEMetaEvent(hci::kLEConnectionCompleteSubeventCode, |
| BufferView(&response, sizeof(response))); |
| }); |
| async::PostTask(dispatcher(), |
| [cb = pending_le_connect_rsp_.callback()] { cb(); }); |
| } |
| |
| void FakeController::OnLEConnectionUpdateCommandReceived( |
| const hci::LEConnectionUpdateCommandParams& params) { |
| hci::ConnectionHandle handle = le16toh(params.connection_handle); |
| FakeDevice* device = FindDeviceByConnHandle(handle); |
| if (!device) { |
| RespondWithCommandStatus(hci::kLEConnectionUpdate, |
| hci::StatusCode::kUnknownConnectionId); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(device->connected()); |
| |
| uint16_t min_interval = le16toh(params.conn_interval_min); |
| uint16_t max_interval = le16toh(params.conn_interval_max); |
| uint16_t max_latency = le16toh(params.conn_latency); |
| uint16_t supv_timeout = le16toh(params.supervision_timeout); |
| |
| if (min_interval > max_interval) { |
| RespondWithCommandStatus(hci::kLEConnectionUpdate, |
| hci::StatusCode::kInvalidHCICommandParameters); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci::kLEConnectionUpdate, hci::StatusCode::kSuccess); |
| |
| hci::LEConnectionParameters conn_params( |
| min_interval + ((max_interval - min_interval) / 2), max_latency, |
| supv_timeout); |
| device->set_le_params(conn_params); |
| |
| hci::LEConnectionUpdateCompleteSubeventParams reply; |
| reply.status = hci::StatusCode::kSuccess; |
| reply.connection_handle = params.connection_handle; |
| reply.conn_interval = htole16(conn_params.interval()); |
| reply.conn_latency = params.conn_latency; |
| reply.supervision_timeout = params.supervision_timeout; |
| |
| SendLEMetaEvent(hci::kLEConnectionUpdateCompleteSubeventCode, |
| BufferView(&reply, sizeof(reply))); |
| |
| NotifyLEConnectionParameters(device->address(), conn_params); |
| } |
| |
| void FakeController::OnDisconnectCommandReceived( |
| const hci::DisconnectCommandParams& params) { |
| hci::ConnectionHandle handle = le16toh(params.connection_handle); |
| |
| // Find the device that matches the disconnected handle. |
| FakeDevice* device = FindDeviceByConnHandle(handle); |
| if (!device) { |
| RespondWithCommandStatus(hci::kDisconnect, |
| hci::StatusCode::kUnknownConnectionId); |
| return; |
| } |
| |
| ZX_DEBUG_ASSERT(device->connected()); |
| |
| RespondWithCommandStatus(hci::kDisconnect, hci::StatusCode::kSuccess); |
| |
| bool notify = device->connected(); |
| device->RemoveLink(handle); |
| if (notify && !device->connected()) |
| NotifyConnectionState(device->address(), false); |
| |
| hci::DisconnectionCompleteEventParams reply; |
| reply.status = hci::StatusCode::kSuccess; |
| reply.connection_handle = params.connection_handle; |
| reply.reason = hci::StatusCode::kConnectionTerminatedByLocalHost; |
| SendEvent(hci::kDisconnectionCompleteEventCode, |
| BufferView(&reply, sizeof(reply))); |
| } |
| |
| void FakeController::OnCommandPacketReceived( |
| const common::PacketView<hci::CommandHeader>& command_packet) { |
| hci::OpCode opcode = le16toh(command_packet.header().opcode); |
| if (MaybeRespondWithDefaultStatus(opcode)) |
| return; |
| |
| // TODO(NET-825): Validate size of payload to be the correct length below. |
| switch (opcode) { |
| case hci::kReadLocalVersionInfo: { |
| hci::ReadLocalVersionInfoReturnParams params; |
| std::memset(¶ms, 0, sizeof(params)); |
| params.hci_version = settings_.hci_version; |
| RespondWithCommandComplete(hci::kReadLocalVersionInfo, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kReadLocalSupportedCommands: { |
| hci::ReadLocalSupportedCommandsReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| std::memcpy(params.supported_commands, settings_.supported_commands, |
| sizeof(params.supported_commands)); |
| RespondWithCommandComplete(hci::kReadLocalSupportedCommands, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kReadLocalSupportedFeatures: { |
| hci::ReadLocalSupportedFeaturesReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.lmp_features = htole64(settings_.lmp_features_page0); |
| RespondWithCommandComplete(hci::kReadLocalSupportedFeatures, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kLESetRandomAddress: { |
| const auto& in_params = |
| command_packet.payload<hci::LESetRandomAddressCommandParams>(); |
| le_random_address_ = common::DeviceAddress( |
| common::DeviceAddress::Type::kLERandom, in_params.random_address); |
| |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kLESetAdvertisingParameters: { |
| const auto& in_params = |
| command_packet |
| .payload<hci::LESetAdvertisingParametersCommandParams>(); |
| // TODO(jamuraa): when we parse advertising params, return Invalid HCI |
| // Command Parameters when apporopriate (Vol 2, Part E, 7.8.9 p1259) |
| if (le_adv_state_.enabled) { |
| hci::SimpleReturnParams out_params; |
| out_params.status = hci::StatusCode::kCommandDisallowed; |
| RespondWithCommandComplete(opcode, |
| BufferView(&out_params, sizeof(out_params))); |
| return; |
| } |
| |
| uint32_t interval_min = le16toh(in_params.adv_interval_min); |
| uint32_t interval_max = le16toh(in_params.adv_interval_max); |
| |
| // Just assign the average for the interval. |
| le_adv_state_.interval = (interval_min + interval_max) / 2; |
| le_adv_state_.adv_type = in_params.adv_type; |
| |
| RespondWithSuccess(opcode); |
| NotifyAdvertisingState(); |
| break; |
| } |
| case hci::kLESetAdvertisingData: { |
| const auto& in_params = |
| command_packet.payload<hci::LESetAdvertisingDataCommandParams>(); |
| le_adv_state_.data_length = in_params.adv_data_length; |
| std::memcpy(le_adv_state_.data, in_params.adv_data, |
| le_adv_state_.data_length); |
| |
| RespondWithSuccess(opcode); |
| NotifyAdvertisingState(); |
| break; |
| } |
| case hci::kLESetScanResponseData: { |
| const auto& in_params = |
| command_packet.payload<hci::LESetScanResponseDataCommandParams>(); |
| le_adv_state_.scan_rsp_length = in_params.scan_rsp_data_length; |
| std::memcpy(le_adv_state_.scan_rsp_data, in_params.scan_rsp_data, |
| le_adv_state_.scan_rsp_length); |
| |
| RespondWithSuccess(opcode); |
| NotifyAdvertisingState(); |
| break; |
| } |
| case hci::kLESetAdvertisingEnable: { |
| const auto& in_params = |
| command_packet.payload<hci::LESetAdvertisingEnableCommandParams>(); |
| le_adv_state_.enabled = |
| (in_params.advertising_enable == hci::GenericEnableParam::kEnable); |
| |
| RespondWithSuccess(opcode); |
| NotifyAdvertisingState(); |
| break; |
| } |
| case hci::kReadBDADDR: { |
| hci::ReadBDADDRReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.bd_addr = settings_.bd_addr.value(); |
| RespondWithCommandComplete(hci::kReadBDADDR, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kReadBufferSize: { |
| hci::ReadBufferSizeReturnParams params; |
| std::memset(¶ms, 0, sizeof(params)); |
| params.hc_acl_data_packet_length = |
| htole16(settings_.acl_data_packet_length); |
| params.hc_total_num_acl_data_packets = |
| settings_.total_num_acl_data_packets; |
| RespondWithCommandComplete(hci::kReadBufferSize, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kDisconnect: { |
| OnDisconnectCommandReceived( |
| command_packet.payload<hci::DisconnectCommandParams>()); |
| break; |
| } |
| case hci::kWriteLocalName: { |
| const auto& in_params = |
| command_packet.payload<hci::WriteLocalNameCommandParams>(); |
| local_name_ = std::string(in_params.local_name, |
| in_params.local_name + hci::kMaxNameLength); |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kReadLocalName: { |
| hci::ReadLocalNameReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| auto mut_view = |
| common::MutableBufferView(params.local_name, hci::kMaxNameLength); |
| mut_view.Write((uint8_t*)(local_name_.c_str()), hci::kMaxNameLength); |
| RespondWithCommandComplete(hci::kReadLocalName, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kReadScanEnable: { |
| hci::ReadScanEnableReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.scan_enable = bredr_scan_state_; |
| |
| RespondWithCommandComplete(hci::kReadScanEnable, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kWriteScanEnable: { |
| const auto& in_params = |
| command_packet.payload<hci::WriteScanEnableCommandParams>(); |
| bredr_scan_state_ = in_params.scan_enable; |
| |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kReadPageScanActivity: { |
| hci::ReadPageScanActivityReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.page_scan_interval = htole16(page_scan_interval_); |
| params.page_scan_window = htole16(page_scan_window_); |
| |
| RespondWithCommandComplete(hci::kReadPageScanActivity, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kWritePageScanActivity: { |
| const auto& in_params = |
| command_packet.payload<hci::WritePageScanActivityCommandParams>(); |
| page_scan_interval_ = letoh16(in_params.page_scan_interval); |
| page_scan_window_ = letoh16(in_params.page_scan_window); |
| |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kReadInquiryMode: { |
| hci::ReadInquiryModeReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.inquiry_mode = inquiry_mode_; |
| RespondWithCommandComplete(hci::kReadInquiryMode, |
| common::BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kWriteInquiryMode: { |
| const auto& in_params = |
| command_packet.payload<hci::WriteInquiryModeCommandParams>(); |
| inquiry_mode_ = in_params.inquiry_mode; |
| RespondWithSuccess(opcode); |
| break; |
| }; |
| case hci::kReadPageScanType: { |
| hci::ReadPageScanTypeReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.page_scan_type = page_scan_type_; |
| |
| RespondWithCommandComplete(hci::kReadPageScanType, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kWritePageScanType: { |
| const auto& in_params = |
| command_packet.payload<hci::WritePageScanTypeCommandParams>(); |
| page_scan_type_ = in_params.page_scan_type; |
| |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kReadSimplePairingMode: { |
| hci::ReadSimplePairingModeReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| if (CheckBit(settings_.lmp_features_page1, |
| hci::LMPFeature::kSecureSimplePairingHostSupport)) { |
| params.simple_pairing_mode = hci::GenericEnableParam::kEnable; |
| } else { |
| params.simple_pairing_mode = hci::GenericEnableParam::kDisable; |
| } |
| |
| RespondWithCommandComplete(hci::kReadSimplePairingMode, |
| common::BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kWriteSimplePairingMode: { |
| const auto& in_params = |
| command_packet.payload<hci::WriteSimplePairingModeCommandParams>(); |
| // "A host shall not set the Simple Pairing Mode to 'disabled'" |
| // Spec 5.0 Vol 2 Part E Sec 7.3.59 |
| if (in_params.simple_pairing_mode != hci::GenericEnableParam::kEnable) { |
| hci::SimpleReturnParams params; |
| params.status = hci::StatusCode::kInvalidHCICommandParameters; |
| RespondWithCommandComplete(hci::kWriteSimplePairingMode, |
| common::BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| |
| SetBit(&settings_.lmp_features_page1, |
| hci::LMPFeature::kSecureSimplePairingHostSupport); |
| |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kLEConnectionUpdate: { |
| OnLEConnectionUpdateCommandReceived( |
| command_packet.payload<hci::LEConnectionUpdateCommandParams>()); |
| break; |
| } |
| case hci::kLECreateConnection: { |
| OnLECreateConnectionCommandReceived( |
| command_packet.payload<hci::LECreateConnectionCommandParams>()); |
| break; |
| } |
| case hci::kLECreateConnectionCancel: { |
| hci::SimpleReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| |
| if (!le_connect_pending_) { |
| // No request is currently pending. |
| params.status = hci::StatusCode::kCommandDisallowed; |
| RespondWithCommandComplete(hci::kLECreateConnectionCancel, |
| BufferView(¶ms, sizeof(params))); |
| return; |
| } |
| |
| le_connect_pending_ = false; |
| pending_le_connect_rsp_.Cancel(); |
| |
| NotifyConnectionState(pending_le_connect_addr_, false, true); |
| |
| hci::LEConnectionCompleteSubeventParams response; |
| std::memset(&response, 0, sizeof(response)); |
| |
| response.status = hci::StatusCode::kUnknownConnectionId; |
| response.peer_address = pending_le_connect_addr_.value(); |
| response.peer_address_type = |
| ToPeerAddrType(pending_le_connect_addr_.type()); |
| |
| RespondWithCommandComplete(hci::kLECreateConnectionCancel, |
| BufferView(¶ms, sizeof(params))); |
| SendLEMetaEvent(hci::kLEConnectionCompleteSubeventCode, |
| BufferView(&response, sizeof(response))); |
| break; |
| } |
| case hci::kLEReadLocalSupportedFeatures: { |
| hci::LEReadLocalSupportedFeaturesReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.le_features = htole64(settings_.le_features); |
| RespondWithCommandComplete(hci::kLEReadLocalSupportedFeatures, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kLEReadSupportedStates: { |
| hci::LEReadSupportedStatesReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.le_states = htole64(settings_.le_supported_states); |
| RespondWithCommandComplete(hci::kLEReadSupportedStates, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kLEReadBufferSize: { |
| hci::LEReadBufferSizeReturnParams params; |
| params.status = hci::StatusCode::kSuccess; |
| params.hc_le_acl_data_packet_length = |
| htole16(settings_.le_acl_data_packet_length); |
| params.hc_total_num_le_acl_data_packets = |
| settings_.le_total_num_acl_data_packets; |
| RespondWithCommandComplete(hci::kLEReadBufferSize, |
| BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| case hci::kSetEventMask: { |
| const auto& in_params = |
| command_packet.payload<hci::SetEventMaskCommandParams>(); |
| settings_.event_mask = le64toh(in_params.event_mask); |
| |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kLESetEventMask: { |
| const auto& in_params = |
| command_packet.payload<hci::LESetEventMaskCommandParams>(); |
| settings_.le_event_mask = le64toh(in_params.le_event_mask); |
| |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kReadLocalExtendedFeatures: { |
| const auto& in_params = |
| command_packet.payload<hci::ReadLocalExtendedFeaturesCommandParams>(); |
| |
| hci::ReadLocalExtendedFeaturesReturnParams out_params; |
| out_params.page_number = in_params.page_number; |
| out_params.maximum_page_number = 2; |
| |
| if (in_params.page_number > 2) { |
| out_params.status = hci::StatusCode::kInvalidHCICommandParameters; |
| } else { |
| out_params.status = hci::StatusCode::kSuccess; |
| |
| switch (in_params.page_number) { |
| case 0: |
| out_params.extended_lmp_features = |
| htole64(settings_.lmp_features_page0); |
| break; |
| case 1: |
| out_params.extended_lmp_features = |
| htole64(settings_.lmp_features_page1); |
| break; |
| case 2: |
| out_params.extended_lmp_features = |
| htole64(settings_.lmp_features_page2); |
| break; |
| } |
| } |
| RespondWithCommandComplete(hci::kReadLocalExtendedFeatures, |
| BufferView(&out_params, sizeof(out_params))); |
| break; |
| } |
| case hci::kLESetScanParameters: { |
| const auto& in_params = |
| command_packet.payload<hci::LESetScanParametersCommandParams>(); |
| |
| hci::SimpleReturnParams out_params; |
| if (le_scan_state_.enabled) { |
| out_params.status = hci::StatusCode::kCommandDisallowed; |
| } else { |
| out_params.status = hci::StatusCode::kSuccess; |
| le_scan_state_.scan_type = in_params.scan_type; |
| le_scan_state_.scan_interval = le16toh(in_params.scan_interval); |
| le_scan_state_.scan_window = le16toh(in_params.scan_window); |
| le_scan_state_.own_address_type = in_params.own_address_type; |
| le_scan_state_.filter_policy = in_params.filter_policy; |
| } |
| |
| RespondWithCommandComplete(opcode, |
| BufferView(&out_params, sizeof(out_params))); |
| break; |
| } |
| case hci::kLESetScanEnable: { |
| const auto& in_params = |
| command_packet.payload<hci::LESetScanEnableCommandParams>(); |
| |
| le_scan_state_.enabled = |
| (in_params.scanning_enabled == hci::GenericEnableParam::kEnable); |
| le_scan_state_.filter_duplicates = |
| (in_params.filter_duplicates == hci::GenericEnableParam::kEnable); |
| |
| // Post the scan state update before scheduling the HCI Command Complete |
| // event. This guarantees that single-threaded unit tests receive the scan |
| // state update BEFORE the HCI command sequence terminates. |
| if (scan_state_cb_) { |
| ZX_DEBUG_ASSERT(scan_state_cb_dispatcher_); |
| async::PostTask(scan_state_cb_dispatcher_, |
| [cb = scan_state_cb_.share(), |
| enabled = le_scan_state_.enabled] { cb(enabled); }); |
| } |
| |
| RespondWithSuccess(opcode); |
| |
| if (le_scan_state_.enabled) |
| SendAdvertisingReports(); |
| break; |
| } |
| |
| case hci::kInquiry: { |
| const auto& in_params = |
| command_packet.payload<hci::InquiryCommandParams>(); |
| |
| if (in_params.lap != hci::kGIAC && in_params.lap != hci::kLIAC) { |
| RespondWithCommandStatus(opcode, hci::kInvalidHCICommandParameters); |
| break; |
| } |
| |
| if (in_params.inquiry_length == 0x00 || |
| in_params.inquiry_length > hci::kInquiryLengthMax) { |
| RespondWithCommandStatus(opcode, hci::kInvalidHCICommandParameters); |
| break; |
| } |
| |
| inquiry_num_responses_left_ = in_params.num_responses; |
| if (in_params.num_responses == 0) { |
| inquiry_num_responses_left_ = -1; |
| } |
| |
| RespondWithCommandStatus(opcode, hci::kSuccess); |
| |
| bt_log(INFO, "fake-hci", "sending inquiry responses.."); |
| SendInquiryResponses(); |
| |
| async::PostDelayedTask( |
| dispatcher(), |
| [this] { |
| hci::InquiryCompleteEventParams params; |
| params.status = hci::kSuccess; |
| SendEvent(hci::kInquiryCompleteEventCode, |
| common::BufferView(¶ms, sizeof(params))); |
| }, |
| zx::msec(in_params.inquiry_length * 1280)); |
| break; |
| } |
| case hci::kReset: { |
| // TODO(jamuraa): actually do some resetting of stuff here |
| RespondWithSuccess(opcode); |
| break; |
| } |
| case hci::kWriteLEHostSupport: { |
| const auto& in_params = |
| command_packet.payload<hci::WriteLEHostSupportCommandParams>(); |
| |
| if (in_params.le_supported_host == hci::GenericEnableParam::kEnable) { |
| SetBit(&settings_.lmp_features_page1, |
| hci::LMPFeature::kLESupportedHost); |
| } else { |
| UnsetBit(&settings_.lmp_features_page1, |
| hci::LMPFeature::kLESupportedHost); |
| } |
| RespondWithSuccess(opcode); |
| break; |
| } |
| default: { |
| hci::SimpleReturnParams params; |
| params.status = hci::StatusCode::kUnknownCommand; |
| RespondWithCommandComplete(opcode, BufferView(¶ms, sizeof(params))); |
| break; |
| } |
| } |
| } |
| |
| void FakeController::OnACLDataPacketReceived( |
| const ByteBuffer& acl_data_packet) { |
| if (acl_data_packet.size() < sizeof(hci::ACLDataHeader)) { |
| bt_log(WARN, "fake-hci", "malformed ACL packet!"); |
| return; |
| } |
| |
| const auto& header = acl_data_packet.As<hci::ACLDataHeader>(); |
| hci::ConnectionHandle handle = le16toh(header.handle_and_flags) & 0x0FFFF; |
| FakeDevice* dev = FindDeviceByConnHandle(handle); |
| if (!dev) { |
| bt_log(WARN, "fake-hci", "ACL data received for unknown handle!"); |
| return; |
| } |
| |
| SendNumberOfCompletedPacketsEvent(handle, 1); |
| dev->OnRxL2CAP(handle, acl_data_packet.view(sizeof(hci::ACLDataHeader))); |
| } |
| |
| } // namespace testing |
| } // namespace btlib |