| // Copyright 2023 The Pigweed Authors |
| // |
| // 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 |
| // |
| // https://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. |
| |
| #include "pw_bluetooth_sapphire/internal/host/testing/fake_controller.h" |
| |
| #include <cpp-string/string_printf.h> |
| #include <endian.h> |
| #include <pw_bluetooth/hci_vendor.emb.h> |
| |
| #include <cstddef> |
| |
| #include "pw_bluetooth_sapphire/internal/host/common/log.h" |
| #include "pw_bluetooth_sapphire/internal/host/common/packet_view.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/constants.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/defaults.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/protocol.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/util.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci-spec/vendor_protocol.h" |
| #include "pw_bluetooth_sapphire/internal/host/hci/util.h" |
| |
| namespace bt::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)); |
| } |
| |
| } // namespace |
| |
| namespace hci_android = hci_spec::vendor::android; |
| |
| void FakeController::Settings::ApplyDualModeDefaults() { |
| le_connection_delay = std::chrono::seconds(0); |
| hci_version = pw::bluetooth::emboss::CoreSpecificationVersion::V5_0; |
| num_hci_command_packets = 250; |
| event_mask = 0; |
| le_event_mask = 0; |
| bd_addr = DeviceAddress(); |
| lmp_features_page0 = 0; |
| SetBit(&lmp_features_page0, hci_spec::LMPFeature::kLESupportedHost); |
| SetBit(&lmp_features_page0, hci_spec::LMPFeature::kSimultaneousLEAndBREDR); |
| SetBit(&lmp_features_page0, hci_spec::LMPFeature::kExtendedFeatures); |
| SetBit(&lmp_features_page0, hci_spec::LMPFeature::kRSSIwithInquiryResults); |
| SetBit(&lmp_features_page0, hci_spec::LMPFeature::kExtendedInquiryResponse); |
| lmp_features_page1 = 0; |
| lmp_features_page2 = 0; |
| le_features = 0; |
| le_supported_states = 0; |
| std::memset(supported_commands, 0, sizeof(supported_commands)); |
| AddBREDRSupportedCommands(); |
| AddLESupportedCommands(); |
| acl_data_packet_length = 512; |
| total_num_acl_data_packets = 1; |
| le_acl_data_packet_length = 512; |
| le_total_num_acl_data_packets = 1; |
| synchronous_data_packet_length = 0; |
| total_num_synchronous_data_packets = 0; |
| iso_data_packet_length = 512; |
| total_num_iso_data_packets = 1; |
| android_extension_settings.SetToZeros(); |
| } |
| |
| void FakeController::Settings::ApplyLEOnlyDefaults() { |
| ApplyDualModeDefaults(); |
| |
| UnsetBit(&lmp_features_page0, hci_spec::LMPFeature::kSimultaneousLEAndBREDR); |
| SetBit(&lmp_features_page0, hci_spec::LMPFeature::kBREDRNotSupported); |
| std::memset(supported_commands, 0, sizeof(supported_commands)); |
| |
| AddLESupportedCommands(); |
| } |
| |
| void FakeController::Settings::AddBREDRSupportedCommands() { |
| SetBit(supported_commands + 0, hci_spec::SupportedCommand::kCreateConnection); |
| SetBit(supported_commands + 0, |
| hci_spec::SupportedCommand::kCreateConnectionCancel); |
| SetBit(supported_commands + 0, hci_spec::SupportedCommand::kDisconnect); |
| SetBit(supported_commands + 7, hci_spec::SupportedCommand::kWriteLocalName); |
| SetBit(supported_commands + 7, hci_spec::SupportedCommand::kReadLocalName); |
| SetBit(supported_commands + 7, hci_spec::SupportedCommand::kReadScanEnable); |
| SetBit(supported_commands + 7, hci_spec::SupportedCommand::kWriteScanEnable); |
| SetBit(supported_commands + 8, |
| hci_spec::SupportedCommand::kReadPageScanActivity); |
| SetBit(supported_commands + 8, |
| hci_spec::SupportedCommand::kWritePageScanActivity); |
| SetBit(supported_commands + 9, |
| hci_spec::SupportedCommand::kWriteClassOfDevice); |
| SetBit(supported_commands + 10, |
| hci_spec::SupportedCommand::kWriteSynchronousFlowControlEnable); |
| SetBit(supported_commands + 12, hci_spec::SupportedCommand::kReadInquiryMode); |
| SetBit(supported_commands + 12, |
| hci_spec::SupportedCommand::kWriteInquiryMode); |
| SetBit(supported_commands + 13, |
| hci_spec::SupportedCommand::kReadPageScanType); |
| SetBit(supported_commands + 13, |
| hci_spec::SupportedCommand::kWritePageScanType); |
| SetBit(supported_commands + 14, hci_spec::SupportedCommand::kReadBufferSize); |
| SetBit(supported_commands + 17, |
| hci_spec::SupportedCommand::kReadSimplePairingMode); |
| SetBit(supported_commands + 17, |
| hci_spec::SupportedCommand::kWriteSimplePairingMode); |
| SetBit(supported_commands + 17, |
| hci_spec::SupportedCommand::kWriteExtendedInquiryResponse); |
| SetBit(supported_commands + 32, |
| hci_spec::SupportedCommand::kWriteSecureConnectionsHostSupport); |
| } |
| |
| void FakeController::Settings::AddLESupportedCommands() { |
| SetBit(supported_commands + 0, hci_spec::SupportedCommand::kDisconnect); |
| SetBit(supported_commands + 5, hci_spec::SupportedCommand::kSetEventMask); |
| SetBit(supported_commands + 5, hci_spec::SupportedCommand::kReset); |
| SetBit(supported_commands + 14, |
| hci_spec::SupportedCommand::kReadLocalVersionInformation); |
| SetBit(supported_commands + 14, |
| hci_spec::SupportedCommand::kReadLocalSupportedFeatures); |
| SetBit(supported_commands + 14, |
| hci_spec::SupportedCommand::kReadLocalExtendedFeatures); |
| SetBit(supported_commands + 24, |
| hci_spec::SupportedCommand::kWriteLEHostSupport); |
| SetBit(supported_commands + 25, hci_spec::SupportedCommand::kLESetEventMask); |
| SetBit(supported_commands + 25, |
| hci_spec::SupportedCommand::kLEReadBufferSizeV1); |
| SetBit(supported_commands + 25, |
| hci_spec::SupportedCommand::kLEReadLocalSupportedFeatures); |
| SetBit(supported_commands + 25, |
| hci_spec::SupportedCommand::kLESetRandomAddress); |
| SetBit(supported_commands + 25, |
| hci_spec::SupportedCommand::kLESetAdvertisingParameters); |
| SetBit(supported_commands + 25, |
| hci_spec::SupportedCommand::kLESetAdvertisingData); |
| SetBit(supported_commands + 26, |
| hci_spec::SupportedCommand::kLESetScanResponseData); |
| SetBit(supported_commands + 26, |
| hci_spec::SupportedCommand::kLESetAdvertisingEnable); |
| SetBit(supported_commands + 26, |
| hci_spec::SupportedCommand::kLECreateConnection); |
| SetBit(supported_commands + 26, |
| hci_spec::SupportedCommand::kLECreateConnectionCancel); |
| SetBit(supported_commands + 27, |
| hci_spec::SupportedCommand::kLEConnectionUpdate); |
| SetBit(supported_commands + 27, |
| hci_spec::SupportedCommand::kLEReadRemoteFeatures); |
| SetBit(supported_commands + 28, |
| hci_spec::SupportedCommand::kLEStartEncryption); |
| SetBit(supported_commands + 41, |
| hci_spec::SupportedCommand::kLEReadBufferSizeV2); |
| } |
| |
| void FakeController::Settings::ApplyLegacyLEConfig() { |
| ApplyLEOnlyDefaults(); |
| |
| hci_version = pw::bluetooth::emboss::CoreSpecificationVersion::V4_2; |
| |
| SetBit(supported_commands + 26, |
| hci_spec::SupportedCommand::kLESetScanParameters); |
| SetBit(supported_commands + 26, hci_spec::SupportedCommand::kLESetScanEnable); |
| } |
| |
| void FakeController::Settings::ApplyExtendedLEConfig() { |
| ApplyLEOnlyDefaults(); |
| |
| SetBit(&le_features, hci_spec::LESupportedFeature::kLEExtendedAdvertising); |
| SetBit(supported_commands + 36, |
| hci_spec::SupportedCommand::kLESetAdvertisingSetRandomAddress); |
| SetBit(supported_commands + 36, |
| hci_spec::SupportedCommand::kLESetExtendedAdvertisingParameters); |
| SetBit(supported_commands + 36, |
| hci_spec::SupportedCommand::kLESetExtendedAdvertisingData); |
| SetBit(supported_commands + 36, |
| hci_spec::SupportedCommand::kLESetExtendedScanResponseData); |
| SetBit(supported_commands + 36, |
| hci_spec::SupportedCommand::kLESetExtendedAdvertisingEnable); |
| SetBit(supported_commands + 36, |
| hci_spec::SupportedCommand::kLEReadMaximumAdvertisingDataLength); |
| SetBit(supported_commands + 36, |
| hci_spec::SupportedCommand::kLEReadNumberOfSupportedAdvertisingSets); |
| SetBit(supported_commands + 37, |
| hci_spec::SupportedCommand::kLERemoveAdvertisingSet); |
| SetBit(supported_commands + 37, |
| hci_spec::SupportedCommand::kLEClearAdvertisingSets); |
| } |
| |
| void FakeController::Settings::ApplyAndroidVendorExtensionDefaults() { |
| // Settings for the android vendor extensions component within the Fake |
| // Controller. These settings correspond to the vendor capabilities returned |
| // by the controller. See |
| // pw_bluetooth_sapphire/internal/host/hci-spec/vendor_protocol.h and |
| // LEGetVendorCapabilities for more information. |
| android_extension_settings.view().status().Write( |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| android_extension_settings.view().max_advt_instances().Write(3); |
| android_extension_settings.view().total_scan_results_storage().Write(1024); |
| } |
| |
| void FakeController::SetDefaultCommandStatus( |
| hci_spec::OpCode opcode, pw::bluetooth::emboss::StatusCode status) { |
| default_command_status_map_[opcode] = status; |
| } |
| |
| void FakeController::ClearDefaultCommandStatus(hci_spec::OpCode opcode) { |
| default_command_status_map_.erase(opcode); |
| } |
| |
| void FakeController::SetDefaultResponseStatus( |
| hci_spec::OpCode opcode, pw::bluetooth::emboss::StatusCode status) { |
| BT_DEBUG_ASSERT(status != pw::bluetooth::emboss::StatusCode::SUCCESS); |
| default_status_map_[opcode] = status; |
| } |
| |
| void FakeController::ClearDefaultResponseStatus(hci_spec::OpCode opcode) { |
| default_status_map_.erase(opcode); |
| } |
| |
| bool FakeController::AddPeer(std::unique_ptr<FakePeer> peer) { |
| BT_DEBUG_ASSERT(peer); |
| |
| if (peers_.count(peer->address()) != 0u) { |
| return false; |
| } |
| |
| peer->set_controller(this); |
| |
| // If a scan is enabled then send an advertising report for the peer that just |
| // got registered if it supports advertising. |
| SendAdvertisingReport(*peer); |
| |
| peers_[peer->address()] = std::move(peer); |
| return true; |
| } |
| |
| void FakeController::RemovePeer(const DeviceAddress& address) { |
| peers_.erase(address); |
| } |
| |
| FakePeer* FakeController::FindPeer(const DeviceAddress& address) { |
| auto iter = peers_.find(address); |
| return (iter == peers_.end()) ? nullptr : iter->second.get(); |
| } |
| |
| void FakeController::SendCommand(pw::span<const std::byte> command) { |
| BT_ASSERT(command.size() >= sizeof(hci_spec::CommandHeader)); |
| |
| // Post the packet to simulate async HCI behavior. |
| (void)heap_dispatcher().Post( |
| [self = GetWeakPtr(), command = DynamicByteBuffer(BufferView(command))]( |
| pw::async::Context /*ctx*/, pw::Status status) { |
| if (!self.is_alive() || !status.ok()) { |
| return; |
| } |
| const size_t payload_size = |
| command.size() - sizeof(hci_spec::CommandHeader); |
| PacketView<hci_spec::CommandHeader> packet_view(&command, payload_size); |
| self->OnCommandPacketReceived(packet_view); |
| }); |
| } |
| |
| FakePeer* FakeController::FindByConnHandle(hci_spec::ConnectionHandle handle) { |
| for (auto& [addr, peer] : peers_) { |
| if (peer->HasLink(handle)) { |
| return peer.get(); |
| } |
| } |
| return nullptr; |
| } |
| |
| uint8_t FakeController::NextL2CAPCommandId() { |
| // TODO(armansito): Guard against overflow? |
| return next_le_sig_id_++; |
| } |
| |
| void FakeController::RespondWithCommandComplete( |
| hci_spec::OpCode opcode, pw::bluetooth::emboss::StatusCode status) { |
| hci_spec::SimpleReturnParams params; |
| params.status = status; |
| RespondWithCommandComplete(opcode, BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::RespondWithCommandComplete(hci_spec::OpCode opcode, |
| const ByteBuffer& params) { |
| DynamicByteBuffer buffer(sizeof(hci_spec::CommandCompleteEventParams) + |
| params.size()); |
| MutablePacketView<hci_spec::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_spec::kCommandCompleteEventCode, buffer); |
| } |
| |
| void FakeController::RespondWithCommandComplete( |
| hci_spec::OpCode opcode, hci::EmbossEventPacket* packet) { |
| auto header = |
| packet |
| ->template view<pw::bluetooth::emboss::CommandCompleteEventWriter>(); |
| |
| header.num_hci_command_packets().Write(settings_.num_hci_command_packets); |
| header.command_opcode().BackingStorage().WriteUInt(opcode); |
| |
| SendEvent(hci_spec::kCommandCompleteEventCode, packet); |
| } |
| |
| void FakeController::RespondWithCommandStatus( |
| hci_spec::OpCode opcode, pw::bluetooth::emboss::StatusCode status) { |
| StaticByteBuffer<sizeof(hci_spec::CommandStatusEventParams)> buffer; |
| MutablePacketView<hci_spec::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_spec::kCommandStatusEventCode, buffer); |
| } |
| |
| void FakeController::SendEvent(hci_spec::EventCode event_code, |
| const ByteBuffer& payload) { |
| DynamicByteBuffer buffer(sizeof(hci_spec::EventHeader) + payload.size()); |
| MutablePacketView<hci_spec::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::SendEvent(hci_spec::EventCode event_code, |
| hci::EmbossEventPacket* packet) { |
| auto header = |
| packet->template view<pw::bluetooth::emboss::EventHeaderWriter>(); |
| uint8_t parameter_total_size = |
| packet->size() - |
| pw::bluetooth::emboss::EventHeader::IntrinsicSizeInBytes(); |
| |
| header.event_code().Write(event_code); |
| header.parameter_total_size().Write(parameter_total_size); |
| |
| SendCommandChannelPacket(packet->data()); |
| } |
| |
| void FakeController::SendLEMetaEvent(hci_spec::EventCode subevent_code, |
| const ByteBuffer& payload) { |
| DynamicByteBuffer buffer(sizeof(hci_spec::LEMetaEventParams) + |
| payload.size()); |
| buffer[0] = subevent_code; |
| buffer.Write(payload, 1); |
| SendEvent(hci_spec::kLEMetaEventCode, buffer); |
| } |
| |
| void FakeController::SendACLPacket(hci_spec::ConnectionHandle handle, |
| const ByteBuffer& payload) { |
| BT_DEBUG_ASSERT(payload.size() <= hci_spec::kMaxACLPayloadSize); |
| |
| DynamicByteBuffer buffer(sizeof(hci_spec::ACLDataHeader) + payload.size()); |
| MutablePacketView<hci_spec::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_spec::ConnectionHandle handle, |
| l2cap::ChannelId channel_id, |
| const ByteBuffer& payload) { |
| BT_DEBUG_ASSERT(payload.size() <= |
| hci_spec::kMaxACLPayloadSize - sizeof(l2cap::BasicHeader)); |
| |
| DynamicByteBuffer buffer(sizeof(l2cap::BasicHeader) + payload.size()); |
| 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_spec::ConnectionHandle handle, |
| bool is_le, |
| l2cap::CommandCode code, |
| uint8_t id, |
| const ByteBuffer& payload) { |
| DynamicByteBuffer buffer(sizeof(l2cap::CommandHeader) + payload.size()); |
| 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_spec::ConnectionHandle handle, uint16_t num) { |
| StaticByteBuffer<sizeof(hci_spec::NumberOfCompletedPacketsEventParams) + |
| sizeof(hci_spec::NumberOfCompletedPacketsEventData)> |
| buffer; |
| |
| auto* params = |
| reinterpret_cast<hci_spec::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_spec::kNumberOfCompletedPacketsEventCode, buffer); |
| } |
| |
| void FakeController::ConnectLowEnergy( |
| const DeviceAddress& addr, pw::bluetooth::emboss::ConnectionRole role) { |
| (void)heap_dispatcher().Post( |
| [addr, role, this](pw::async::Context /*ctx*/, pw::Status status) { |
| if (!status.ok()) { |
| return; |
| } |
| FakePeer* peer = FindPeer(addr); |
| if (!peer) { |
| bt_log(WARN, |
| "fake-hci", |
| "no peer found with address: %s", |
| addr.ToString().c_str()); |
| return; |
| } |
| |
| // TODO(armansito): Don't worry about managing multiple links per peer |
| // until this supports Bluetooth classic. |
| if (peer->connected()) { |
| bt_log(WARN, "fake-hci", "peer already connected"); |
| return; |
| } |
| |
| hci_spec::ConnectionHandle handle = ++next_conn_handle_; |
| peer->AddLink(handle); |
| |
| NotifyConnectionState(addr, handle, /*connected=*/true); |
| |
| auto interval_min = hci_spec::defaults::kLEConnectionIntervalMin; |
| auto interval_max = hci_spec::defaults::kLEConnectionIntervalMax; |
| |
| hci_spec::LEConnectionParameters conn_params( |
| interval_min + ((interval_max - interval_min) / 2), |
| 0, |
| hci_spec::defaults::kLESupervisionTimeout); |
| peer->set_le_params(conn_params); |
| |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEConnectionCompleteSubeventWriter>( |
| hci_spec::kLEMetaEventCode); |
| auto view = packet.view_t(); |
| view.le_meta_event().subevent_code().Write( |
| hci_spec::kLEConnectionCompleteSubeventCode); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.peer_address().CopyFrom(addr.value().view()); |
| view.peer_address_type().Write( |
| DeviceAddress::DeviceAddrToLePeerAddr(addr.type())); |
| view.peripheral_latency().Write(conn_params.latency()); |
| view.connection_interval().Write(conn_params.interval()); |
| view.supervision_timeout().Write(conn_params.supervision_timeout()); |
| view.role().Write(role); |
| view.connection_handle().Write(handle); |
| SendCommandChannelPacket(packet.data()); |
| }); |
| } |
| |
| void FakeController::SendConnectionRequest( |
| const DeviceAddress& addr, pw::bluetooth::emboss::LinkType link_type) { |
| FakePeer* peer = FindPeer(addr); |
| BT_ASSERT(peer); |
| peer->set_last_connection_request_link_type(link_type); |
| |
| bt_log(DEBUG, |
| "fake-hci", |
| "sending connection request (addr: %s, link: %s)", |
| bt_str(addr), |
| hci_spec::LinkTypeToString(link_type)); |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ConnectionRequestEventWriter>( |
| hci_spec::kConnectionRequestEventCode); |
| packet.view_t().bd_addr().CopyFrom(addr.value().view()); |
| packet.view_t().link_type().Write(link_type); |
| SendCommandChannelPacket(packet.data()); |
| } |
| |
| void FakeController::L2CAPConnectionParameterUpdate( |
| const DeviceAddress& addr, |
| const hci_spec::LEPreferredConnectionParameters& params) { |
| (void)heap_dispatcher().Post( |
| [addr, params, this](pw::async::Context /*ctx*/, pw::Status status) { |
| if (!status.ok()) { |
| return; |
| } |
| FakePeer* peer = FindPeer(addr); |
| if (!peer) { |
| bt_log(WARN, |
| "fake-hci", |
| "no peer found with address: %s", |
| addr.ToString().c_str()); |
| return; |
| } |
| |
| if (!peer->connected()) { |
| bt_log(WARN, "fake-hci", "peer not connected"); |
| return; |
| } |
| |
| BT_DEBUG_ASSERT(!peer->logical_links().empty()); |
| |
| l2cap::ConnectionParameterUpdateRequestPayload payload; |
| payload.interval_min = htole16(params.min_interval()); |
| payload.interval_max = htole16(params.max_interval()); |
| payload.peripheral_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(*peer->logical_links().begin(), |
| /*is_le=*/true, |
| l2cap::kConnectionParameterUpdateRequest, |
| NextL2CAPCommandId(), |
| BufferView(&payload, sizeof(payload))); |
| }); |
| } |
| |
| void FakeController::SendLEConnectionUpdateCompleteSubevent( |
| hci_spec::ConnectionHandle handle, |
| const hci_spec::LEConnectionParameters& params, |
| pw::bluetooth::emboss::StatusCode status) { |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEConnectionUpdateCompleteSubeventWriter>( |
| hci_spec::kLEMetaEventCode); |
| auto view = packet.view_t(); |
| view.le_meta_event().subevent_code().Write( |
| hci_spec::kLEConnectionUpdateCompleteSubeventCode); |
| view.status().Write(status); |
| view.connection_handle().Write(handle); |
| view.connection_interval().UncheckedWrite(params.interval()); |
| view.peripheral_latency().Write(params.latency()); |
| view.supervision_timeout().UncheckedWrite(params.supervision_timeout()); |
| SendCommandChannelPacket(packet.data()); |
| } |
| |
| void FakeController::Disconnect(const DeviceAddress& addr, |
| pw::bluetooth::emboss::StatusCode reason) { |
| (void)heap_dispatcher().Post( |
| [this, addr, reason](pw::async::Context /*ctx*/, pw::Status status) { |
| if (!status.ok()) { |
| return; |
| } |
| FakePeer* peer = FindPeer(addr); |
| if (!peer || !peer->connected()) { |
| bt_log(WARN, |
| "fake-hci", |
| "no connected peer found with address: %s", |
| addr.ToString().c_str()); |
| return; |
| } |
| |
| auto links = peer->Disconnect(); |
| BT_DEBUG_ASSERT(!peer->connected()); |
| BT_DEBUG_ASSERT(!links.empty()); |
| |
| for (auto link : links) { |
| NotifyConnectionState(addr, link, /*connected=*/false); |
| SendDisconnectionCompleteEvent(link, reason); |
| } |
| }); |
| } |
| |
| void FakeController::SendDisconnectionCompleteEvent( |
| hci_spec::ConnectionHandle handle, |
| pw::bluetooth::emboss::StatusCode reason) { |
| auto event = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::DisconnectionCompleteEventWriter>( |
| hci_spec::kDisconnectionCompleteEventCode); |
| event.view_t().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| event.view_t().connection_handle().Write(handle); |
| event.view_t().reason().Write(reason); |
| SendCommandChannelPacket(event.data()); |
| } |
| |
| void FakeController::SendEncryptionChangeEvent( |
| hci_spec::ConnectionHandle handle, |
| pw::bluetooth::emboss::StatusCode status, |
| pw::bluetooth::emboss::EncryptionStatus encryption_enabled) { |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::EncryptionChangeEventV1Writer>( |
| hci_spec::kEncryptionChangeEventCode); |
| response.view_t().status().Write(status); |
| response.view_t().connection_handle().Write(handle); |
| response.view_t().encryption_enabled().Write(encryption_enabled); |
| SendCommandChannelPacket(response.data()); |
| } |
| |
| bool FakeController::MaybeRespondWithDefaultCommandStatus( |
| hci_spec::OpCode opcode) { |
| auto iter = default_command_status_map_.find(opcode); |
| if (iter == default_command_status_map_.end()) { |
| return false; |
| } |
| |
| RespondWithCommandStatus(opcode, iter->second); |
| return true; |
| } |
| |
| bool FakeController::MaybeRespondWithDefaultStatus(hci_spec::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: %#.2hhx)", |
| opcode, |
| static_cast<unsigned char>(iter->second)); |
| RespondWithCommandComplete(opcode, iter->second); |
| return true; |
| } |
| |
| void FakeController::SendInquiryResponses() { |
| // TODO(jamuraa): combine some of these into a single response event |
| for (const auto& [addr, peer] : peers_) { |
| if (!peer->supports_bredr()) { |
| continue; |
| } |
| |
| SendCommandChannelPacket(peer->CreateInquiryResponseEvent(inquiry_mode_)); |
| inquiry_num_responses_left_--; |
| if (inquiry_num_responses_left_ == 0) { |
| break; |
| } |
| } |
| } |
| |
| static void WriteScanResponseReport(const FakePeer& peer, |
| hci_spec::LEAdvertisingReportData* report) { |
| BT_DEBUG_ASSERT(peer.scannable()); |
| report->event_type = hci_spec::LEAdvertisingEventType::kScanRsp; |
| |
| report->address = peer.address().value(); |
| report->address_type = hci_spec::LEAddressType::kPublic; |
| if (peer.address().type() == DeviceAddress::Type::kLERandom) { |
| report->address_type = hci_spec::LEAddressType::kRandom; |
| } |
| |
| report->length_data = peer.scan_response().size(); |
| std::memcpy( |
| report->data, peer.scan_response().data(), peer.scan_response().size()); |
| |
| report->data[report->length_data] = peer.rssi(); |
| } |
| |
| DynamicByteBuffer FakeController::BuildLegacyAdvertisingReportEvent( |
| const FakePeer& peer, bool include_scan_rsp) { |
| BT_DEBUG_ASSERT(peer.advertising_data().size() <= |
| hci_spec::kMaxLEAdvertisingDataLength); |
| size_t param_size = sizeof(hci_spec::LEMetaEventParams) + |
| sizeof(hci_spec::LEAdvertisingReportSubeventParams) + |
| sizeof(hci_spec::LEAdvertisingReportData) + |
| peer.advertising_data().size() + sizeof(int8_t); |
| |
| if (include_scan_rsp) { |
| BT_DEBUG_ASSERT(peer.scannable()); |
| BT_DEBUG_ASSERT(peer.scan_response().size() <= |
| hci_spec::kMaxLEAdvertisingDataLength); |
| param_size += sizeof(hci_spec::LEAdvertisingReportData) + |
| peer.scan_response().size() + sizeof(int8_t); |
| } |
| |
| DynamicByteBuffer buffer(sizeof(hci_spec::EventHeader) + param_size); |
| MutablePacketView<hci_spec::EventHeader> event(&buffer, param_size); |
| event.mutable_header()->event_code = hci_spec::kLEMetaEventCode; |
| event.mutable_header()->parameter_total_size = param_size; |
| |
| auto payload = event.mutable_payload<hci_spec::LEMetaEventParams>(); |
| payload->subevent_code = hci_spec::kLEAdvertisingReportSubeventCode; |
| |
| auto subevent_payload = |
| reinterpret_cast<hci_spec::LEAdvertisingReportSubeventParams*>( |
| payload->subevent_parameters); |
| subevent_payload->num_reports = 1; |
| if (include_scan_rsp) { |
| subevent_payload->num_reports++; |
| } |
| auto report = reinterpret_cast<hci_spec::LEAdvertisingReportData*>( |
| subevent_payload->reports); |
| if (peer.directed_advertising_enabled()) { |
| report->event_type = hci_spec::LEAdvertisingEventType::kAdvDirectInd; |
| } else if (peer.connectable()) { |
| report->event_type = hci_spec::LEAdvertisingEventType::kAdvInd; |
| } else if (peer.scannable()) { |
| report->event_type = hci_spec::LEAdvertisingEventType::kAdvScanInd; |
| } else { |
| report->event_type = hci_spec::LEAdvertisingEventType::kAdvNonConnInd; |
| } |
| |
| if (peer.address().type() == DeviceAddress::Type::kLERandom) { |
| report->address_type = hci_spec::LEAddressType::kRandom; |
| if (peer.address_resolved()) { |
| report->address_type = hci_spec::LEAddressType::kRandomIdentity; |
| } |
| } else { |
| report->address_type = hci_spec::LEAddressType::kPublic; |
| if (peer.address_resolved()) { |
| report->address_type = hci_spec::LEAddressType::kPublicIdentity; |
| } |
| } |
| |
| report->address = peer.address().value(); |
| report->length_data = peer.advertising_data().size(); |
| std::memcpy(report->data, |
| peer.advertising_data().data(), |
| peer.advertising_data().size()); |
| report->data[report->length_data] = peer.rssi(); |
| |
| if (include_scan_rsp) { |
| auto* scan_response_report = |
| reinterpret_cast<hci_spec::LEAdvertisingReportData*>( |
| report->data + report->length_data + sizeof(int8_t)); |
| WriteScanResponseReport(peer, scan_response_report); |
| } |
| |
| return buffer; |
| } |
| |
| DynamicByteBuffer FakeController::BuildLegacyScanResponseReportEvent( |
| const FakePeer& peer) const { |
| BT_DEBUG_ASSERT(peer.scannable()); |
| BT_DEBUG_ASSERT(peer.scan_response().size() <= |
| hci_spec::kMaxLEAdvertisingDataLength); |
| size_t param_size = sizeof(hci_spec::LEMetaEventParams) + |
| sizeof(hci_spec::LEAdvertisingReportSubeventParams) + |
| sizeof(hci_spec::LEAdvertisingReportData) + |
| peer.scan_response().size() + sizeof(int8_t); |
| |
| DynamicByteBuffer buffer(sizeof(hci_spec::EventHeader) + param_size); |
| MutablePacketView<hci_spec::EventHeader> event(&buffer, param_size); |
| event.mutable_header()->event_code = hci_spec::kLEMetaEventCode; |
| event.mutable_header()->parameter_total_size = param_size; |
| |
| auto payload = event.mutable_payload<hci_spec::LEMetaEventParams>(); |
| payload->subevent_code = hci_spec::kLEAdvertisingReportSubeventCode; |
| |
| auto subevent_payload = |
| reinterpret_cast<hci_spec::LEAdvertisingReportSubeventParams*>( |
| payload->subevent_parameters); |
| subevent_payload->num_reports = 1; |
| |
| auto report = reinterpret_cast<hci_spec::LEAdvertisingReportData*>( |
| subevent_payload->reports); |
| WriteScanResponseReport(peer, report); |
| |
| return buffer; |
| } |
| |
| void FakeController::FillExtendedAdvertisingReport( |
| const FakePeer& peer, |
| pw::bluetooth::emboss::LEExtendedAdvertisingReportDataWriter report, |
| const ByteBuffer& data, |
| bool is_fragmented, |
| bool is_scan_response) const { |
| if (peer.use_extended_advertising_pdus()) { |
| report.event_type().directed().Write(peer.directed_advertising_enabled()); |
| report.event_type().connectable().Write(peer.connectable()); |
| report.event_type().scannable().Write(peer.scannable()); |
| report.event_type().scan_response().Write(is_scan_response); |
| |
| if (is_fragmented) { |
| report.event_type().data_status().Write( |
| pw::bluetooth::emboss::LEAdvertisingDataStatus::INCOMPLETE); |
| } else { |
| report.event_type().data_status().Write( |
| pw::bluetooth::emboss::LEAdvertisingDataStatus::COMPLETE); |
| } |
| } else { |
| report.event_type().legacy().Write(true); |
| if (is_scan_response) { |
| report.event_type().scan_response().Write(true); |
| } |
| |
| if (peer.directed_advertising_enabled()) { // ADV_DIRECT_IND |
| report.event_type().directed().Write(true); |
| report.event_type().connectable().Write(true); |
| } else if (peer.connectable()) { // ADV_IND |
| report.event_type().connectable().Write(true); |
| report.event_type().scannable().Write(true); |
| } else if (peer.scannable()) { // ADV_SCAN_IND |
| report.event_type().scannable().Write(true); |
| } |
| // else ADV_NONCONN_IND |
| } |
| |
| if (peer.address().type() == DeviceAddress::Type::kLERandom) { |
| if (peer.address_resolved()) { |
| report.address_type().Write( |
| pw::bluetooth::emboss::LEExtendedAddressType::RANDOM_IDENTITY); |
| } else { |
| report.address_type().Write( |
| pw::bluetooth::emboss::LEExtendedAddressType::RANDOM); |
| } |
| } else { |
| if (peer.address_resolved()) { |
| report.address_type().Write( |
| pw::bluetooth::emboss::LEExtendedAddressType::PUBLIC_IDENTITY); |
| } else { |
| report.address_type().Write( |
| pw::bluetooth::emboss::LEExtendedAddressType::PUBLIC); |
| } |
| } |
| |
| report.address().bd_addr().CopyFrom(peer.address().value().view().bd_addr()); |
| report.primary_phy().Write( |
| pw::bluetooth::emboss::LEPrimaryAdvertisingPHY::LE_1M); |
| report.secondary_phy().Write( |
| pw::bluetooth::emboss::LESecondaryAdvertisingPHY::NONE); |
| report.advertising_sid().Write(0); |
| report.tx_power().Write(peer.tx_power()); |
| report.rssi().Write(peer.rssi()); |
| report.periodic_advertising_interval().Write(0); |
| |
| // skip direct_address_type and direct_address for now since we don't use it |
| |
| report.data_length().Write(data.size()); |
| std::memcpy(report.data().BackingStorage().begin(), data.data(), data.size()); |
| } |
| |
| DynamicByteBuffer FakeController::BuildExtendedAdvertisingReports( |
| const FakePeer& peer, const ByteBuffer& data, bool is_scan_response) const { |
| using pw::bluetooth::emboss::LEExtendedAdvertisingReportDataWriter; |
| using pw::bluetooth::emboss::LEExtendedAdvertisingReportSubeventWriter; |
| |
| size_t num_full_reports = |
| data.size() / hci_spec::kMaxPduLEExtendedAdvertisingDataLength; |
| size_t full_report_size = |
| pw::bluetooth::emboss::LEExtendedAdvertisingReportData::MinSizeInBytes() + |
| hci_spec::kMaxPduLEExtendedAdvertisingDataLength; |
| size_t last_report_size = |
| pw::bluetooth::emboss::LEExtendedAdvertisingReportData::MinSizeInBytes() + |
| (data.size() % hci_spec::kMaxPduLEExtendedAdvertisingDataLength); |
| |
| size_t reports_size = num_full_reports * full_report_size + last_report_size; |
| size_t packet_size = |
| pw::bluetooth::emboss::LEExtendedAdvertisingReportSubevent:: |
| MinSizeInBytes() + |
| reports_size; |
| |
| auto event = |
| hci::EmbossEventPacket::New<LEExtendedAdvertisingReportSubeventWriter>( |
| hci_spec::kLEMetaEventCode, packet_size); |
| auto packet = |
| event.view<LEExtendedAdvertisingReportSubeventWriter>(reports_size); |
| packet.le_meta_event().subevent_code().Write( |
| hci_spec::kLEExtendedAdvertisingReportSubeventCode); |
| |
| uint8_t num_reports = num_full_reports + 1; |
| packet.num_reports().Write(num_reports); |
| |
| for (size_t i = 0; i < num_full_reports; i++) { |
| bool is_fragmented = false; |
| if (num_reports > 1) { |
| is_fragmented = true; |
| } |
| |
| LEExtendedAdvertisingReportDataWriter report( |
| packet.reports().BackingStorage().begin() + full_report_size * i, |
| full_report_size); |
| FillExtendedAdvertisingReport( |
| peer, report, data, is_fragmented, is_scan_response); |
| } |
| |
| LEExtendedAdvertisingReportDataWriter report( |
| packet.reports().BackingStorage().begin() + |
| full_report_size * num_full_reports, |
| last_report_size); |
| FillExtendedAdvertisingReport(peer, report, data, false, is_scan_response); |
| |
| return event.release(); |
| } |
| |
| DynamicByteBuffer FakeController::BuildExtendedAdvertisingReportEvent( |
| const FakePeer& peer) const { |
| BT_DEBUG_ASSERT(peer.advertising_data().size() <= |
| hci_spec::kMaxLEExtendedAdvertisingDataLength); |
| return BuildExtendedAdvertisingReports(peer, peer.advertising_data(), false); |
| } |
| |
| DynamicByteBuffer FakeController::BuildExtendedScanResponseEvent( |
| const FakePeer& peer) const { |
| BT_DEBUG_ASSERT(peer.scannable()); |
| BT_DEBUG_ASSERT(peer.scan_response().size() <= |
| hci_spec::kMaxLEExtendedAdvertisingDataLength); |
| return BuildExtendedAdvertisingReports(peer, peer.scan_response(), true); |
| } |
| |
| void FakeController::SendAdvertisingReports() { |
| if (!le_scan_state_.enabled || peers_.empty()) { |
| return; |
| } |
| |
| for (const auto& iter : peers_) { |
| SendAdvertisingReport(*iter.second); |
| } |
| |
| // We'll send new reports for the same peers if duplicate filtering is |
| // disabled. |
| if (!le_scan_state_.filter_duplicates) { |
| (void)heap_dispatcher().Post( |
| [this](pw::async::Context /*ctx*/, pw::Status status) { |
| if (status.ok()) { |
| SendAdvertisingReports(); |
| } |
| }); |
| } |
| } |
| |
| void FakeController::SendAdvertisingReport(const FakePeer& peer) { |
| if (!le_scan_state_.enabled || !peer.supports_le() || |
| !peer.advertising_enabled()) { |
| return; |
| } |
| |
| // We want to send scan response packets only during an active scan and if the |
| // peer is scannable. |
| bool is_active_scan = |
| (le_scan_state_.scan_type == pw::bluetooth::emboss::LEScanType::ACTIVE); |
| bool need_scan_rsp = (is_active_scan && peer.scannable()); |
| |
| if (received_extended_operations_) { |
| SendCommandChannelPacket(BuildExtendedAdvertisingReportEvent(peer)); |
| |
| if (need_scan_rsp) { |
| SendCommandChannelPacket(BuildExtendedScanResponseEvent(peer)); |
| } |
| } else { |
| bool include_scan_rsp = (need_scan_rsp && peer.should_batch_reports()); |
| SendCommandChannelPacket( |
| BuildLegacyAdvertisingReportEvent(peer, include_scan_rsp)); |
| |
| // If the original report did not include a scan response then we send it as |
| // a separate event. |
| if (need_scan_rsp && !peer.should_batch_reports()) { |
| SendCommandChannelPacket(BuildLegacyScanResponseReportEvent(peer)); |
| } |
| } |
| } |
| |
| void FakeController::NotifyControllerParametersChanged() { |
| if (controller_parameters_cb_) { |
| controller_parameters_cb_(); |
| } |
| } |
| |
| void FakeController::NotifyAdvertisingState() { |
| if (advertising_state_cb_) { |
| advertising_state_cb_(); |
| } |
| } |
| |
| void FakeController::NotifyConnectionState(const DeviceAddress& addr, |
| hci_spec::ConnectionHandle handle, |
| bool connected, |
| bool canceled) { |
| if (conn_state_cb_) { |
| conn_state_cb_(addr, handle, connected, canceled); |
| } |
| } |
| |
| void FakeController::NotifyLEConnectionParameters( |
| const DeviceAddress& addr, const hci_spec::LEConnectionParameters& params) { |
| if (le_conn_params_cb_) { |
| le_conn_params_cb_(addr, params); |
| } |
| } |
| |
| void FakeController::OnCreateConnectionCommandReceived( |
| const pw::bluetooth::emboss::CreateConnectionCommandView& params) { |
| acl_create_connection_command_count_++; |
| |
| // Cannot issue this command while a request is already pending. |
| if (bredr_connect_pending_) { |
| RespondWithCommandStatus( |
| hci_spec::kCreateConnection, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| const DeviceAddress peer_address(DeviceAddress::Type::kBREDR, |
| DeviceAddressBytes(params.bd_addr())); |
| pw::bluetooth::emboss::StatusCode status = |
| pw::bluetooth::emboss::StatusCode::SUCCESS; |
| |
| // Find the peer that matches the requested address. |
| FakePeer* peer = FindPeer(peer_address); |
| if (peer) { |
| if (peer->connected()) |
| status = pw::bluetooth::emboss::StatusCode::CONNECTION_ALREADY_EXISTS; |
| else |
| status = peer->connect_status(); |
| } |
| |
| // First send the Command Status response. |
| RespondWithCommandStatus(hci_spec::kCreateConnection, status); |
| |
| // If we just sent back an error status then the operation is complete. |
| if (status != pw::bluetooth::emboss::StatusCode::SUCCESS) |
| return; |
| |
| bredr_connect_pending_ = true; |
| pending_bredr_connect_addr_ = peer_address; |
| |
| // The procedure was initiated successfully but the peer cannot be connected |
| // because it either doesn't exist or isn't connectable. |
| if (!peer || !peer->connectable()) { |
| bt_log(INFO, |
| "fake-hci", |
| "requested peer %s cannot be connected; request will time out", |
| peer_address.ToString().c_str()); |
| |
| bredr_connect_rsp_task_.Cancel(); |
| bredr_connect_rsp_task_.set_function( |
| [this, peer_address](pw::async::Context /*ctx*/, pw::Status status) { |
| if (!status.ok()) { |
| return; |
| } |
| bredr_connect_pending_ = false; |
| |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ConnectionCompleteEventWriter>( |
| hci_spec::kConnectionCompleteEventCode); |
| response.view_t().status().Write( |
| pw::bluetooth::emboss::StatusCode::PAGE_TIMEOUT); |
| response.view_t().bd_addr().CopyFrom(peer_address.value().view()); |
| SendCommandChannelPacket(response.data()); |
| }); |
| |
| // Default page timeout of 5.12s |
| // See Core Spec v5.0 Vol 2, Part E, Section 6.6 |
| constexpr pw::chrono::SystemClock::duration default_page_timeout = |
| std::chrono::microseconds(static_cast<int64_t>(625) * 0x2000); |
| bredr_connect_rsp_task_.PostAfter(default_page_timeout); |
| return; |
| } |
| |
| if (next_conn_handle_ == 0x0FFF) { |
| // Ran out of handles |
| status = pw::bluetooth::emboss::StatusCode::CONNECTION_LIMIT_EXCEEDED; |
| } else { |
| status = peer->connect_response(); |
| } |
| |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ConnectionCompleteEventWriter>( |
| hci_spec::kConnectionCompleteEventCode); |
| response.view_t().status().Write(status); |
| response.view_t().bd_addr().CopyFrom(params.bd_addr()); |
| response.view_t().link_type().Write(pw::bluetooth::emboss::LinkType::ACL); |
| response.view_t().encryption_enabled().Write( |
| pw::bluetooth::emboss::GenericEnableParam::DISABLE); |
| |
| if (status == pw::bluetooth::emboss::StatusCode::SUCCESS) { |
| hci_spec::ConnectionHandle handle = ++next_conn_handle_; |
| response.view_t().connection_handle().Write(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 (peer->force_pending_connect()) |
| return; |
| |
| bredr_connect_rsp_task_.Cancel(); |
| bredr_connect_rsp_task_.set_function( |
| [response, peer, this](pw::async::Context /*ctx*/, |
| pw::Status status) mutable { |
| if (!status.ok()) { |
| return; |
| } |
| bredr_connect_pending_ = false; |
| |
| if (response.view_t().status().Read() == |
| pw::bluetooth::emboss::StatusCode::SUCCESS) { |
| bool notify = !peer->connected(); |
| hci_spec::ConnectionHandle handle = |
| response.view_t().connection_handle().Read(); |
| peer->AddLink(handle); |
| if (notify && peer->connected()) { |
| NotifyConnectionState(peer->address(), handle, /*connected=*/true); |
| } |
| } |
| |
| SendCommandChannelPacket(response.data()); |
| }); |
| bredr_connect_rsp_task_.Post(); |
| } |
| |
| void FakeController::OnLECreateConnectionCommandReceived( |
| const pw::bluetooth::emboss::LECreateConnectionCommandView& params) { |
| le_create_connection_command_count_++; |
| if (le_create_connection_cb_) { |
| le_create_connection_cb_(params); |
| } |
| |
| // Cannot issue this command while a request is already pending. |
| if (le_connect_pending_) { |
| RespondWithCommandStatus( |
| hci_spec::kLECreateConnection, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| DeviceAddress::Type addr_type = |
| DeviceAddress::LeAddrToDeviceAddr(params.peer_address_type().Read()); |
| BT_DEBUG_ASSERT(addr_type != DeviceAddress::Type::kBREDR); |
| |
| const DeviceAddress peer_address(addr_type, |
| DeviceAddressBytes(params.peer_address())); |
| pw::bluetooth::emboss::StatusCode status = |
| pw::bluetooth::emboss::StatusCode::SUCCESS; |
| |
| // Find the peer that matches the requested address. |
| FakePeer* peer = FindPeer(peer_address); |
| if (peer) { |
| if (peer->connected()) |
| status = pw::bluetooth::emboss::StatusCode::CONNECTION_ALREADY_EXISTS; |
| else |
| status = peer->connect_status(); |
| } |
| |
| // First send the Command Status response. |
| RespondWithCommandStatus(hci_spec::kLECreateConnection, status); |
| |
| // If we just sent back an error status then the operation is complete. |
| if (status != pw::bluetooth::emboss::StatusCode::SUCCESS) |
| return; |
| |
| le_connect_pending_ = true; |
| if (!le_connect_params_) { |
| le_connect_params_ = LEConnectParams(); |
| } |
| |
| le_connect_params_->own_address_type = params.own_address_type().Read(); |
| le_connect_params_->peer_address = peer_address; |
| |
| // The procedure was initiated successfully but the peer cannot be connected |
| // because it either doesn't exist or isn't connectable. |
| if (!peer || !peer->connectable()) { |
| bt_log(INFO, |
| "fake-hci", |
| "requested fake peer cannot be connected; request will time out"); |
| return; |
| } |
| |
| if (next_conn_handle_ == 0x0FFF) { |
| // Ran out of handles |
| status = pw::bluetooth::emboss::StatusCode::CONNECTION_LIMIT_EXCEEDED; |
| } else { |
| status = peer->connect_response(); |
| } |
| |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEConnectionCompleteSubeventWriter>( |
| hci_spec::kLEMetaEventCode); |
| auto view = packet.view_t(); |
| view.le_meta_event().subevent_code().Write( |
| hci_spec::kLEConnectionCompleteSubeventCode); |
| view.status().Write(status); |
| view.peer_address().CopyFrom(params.peer_address()); |
| view.peer_address_type().Write( |
| DeviceAddress::DeviceAddrToLePeerAddr(addr_type)); |
| |
| if (status == pw::bluetooth::emboss::StatusCode::SUCCESS) { |
| uint16_t interval_min = params.connection_interval_min().UncheckedRead(); |
| uint16_t interval_max = params.connection_interval_max().UncheckedRead(); |
| uint16_t interval = interval_min + ((interval_max - interval_min) / 2); |
| |
| hci_spec::LEConnectionParameters conn_params( |
| interval, |
| params.max_latency().UncheckedRead(), |
| params.supervision_timeout().UncheckedRead()); |
| peer->set_le_params(conn_params); |
| |
| view.peripheral_latency().UncheckedCopyFrom(params.max_latency()); |
| view.connection_interval().UncheckedWrite(interval); |
| view.supervision_timeout().UncheckedCopyFrom(params.supervision_timeout()); |
| view.role().Write(pw::bluetooth::emboss::ConnectionRole::CENTRAL); |
| view.connection_handle().Write(++next_conn_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 (peer->force_pending_connect()) |
| return; |
| |
| le_connect_rsp_task_.Cancel(); |
| le_connect_rsp_task_.set_function([packet, address = peer_address, this]( |
| pw::async::Context /*ctx*/, |
| pw::Status status) { |
| auto peer = FindPeer(address); |
| if (!peer || !status.ok()) { |
| // The peer has been removed or dispatcher shut down; Ignore this response |
| return; |
| } |
| |
| le_connect_pending_ = false; |
| |
| auto view = |
| packet.view<pw::bluetooth::emboss::LEConnectionCompleteSubeventView>(); |
| if (view.status().Read() == pw::bluetooth::emboss::StatusCode::SUCCESS) { |
| bool not_previously_connected = !peer->connected(); |
| hci_spec::ConnectionHandle handle = view.connection_handle().Read(); |
| peer->AddLink(handle); |
| if (not_previously_connected && peer->connected()) { |
| NotifyConnectionState(peer->address(), handle, /*connected=*/true); |
| } |
| } |
| |
| SendCommandChannelPacket(packet.data()); |
| }); |
| le_connect_rsp_task_.PostAfter(settings_.le_connection_delay); |
| } |
| |
| void FakeController::OnLEConnectionUpdateCommandReceived( |
| const pw::bluetooth::emboss::LEConnectionUpdateCommandView& params) { |
| hci_spec::ConnectionHandle handle = params.connection_handle().Read(); |
| FakePeer* peer = FindByConnHandle(handle); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kLEConnectionUpdate, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| BT_DEBUG_ASSERT(peer->connected()); |
| |
| uint16_t min_interval = params.connection_interval_min().UncheckedRead(); |
| uint16_t max_interval = params.connection_interval_max().UncheckedRead(); |
| uint16_t max_latency = params.max_latency().UncheckedRead(); |
| uint16_t supv_timeout = params.supervision_timeout().UncheckedRead(); |
| |
| if (min_interval > max_interval) { |
| RespondWithCommandStatus( |
| hci_spec::kLEConnectionUpdate, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kLEConnectionUpdate, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| hci_spec::LEConnectionParameters conn_params( |
| min_interval + ((max_interval - min_interval) / 2), |
| max_latency, |
| supv_timeout); |
| peer->set_le_params(conn_params); |
| |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEConnectionUpdateCompleteSubeventWriter>( |
| hci_spec::kLEMetaEventCode); |
| auto view = packet.view_t(); |
| view.le_meta_event().subevent_code().Write( |
| hci_spec::kLEConnectionUpdateCompleteSubeventCode); |
| view.connection_handle().CopyFrom(params.connection_handle()); |
| if (peer->supports_ll_conn_update_procedure()) { |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.connection_interval().UncheckedWrite(conn_params.interval()); |
| view.peripheral_latency().CopyFrom(params.max_latency()); |
| view.supervision_timeout().UncheckedCopyFrom(params.supervision_timeout()); |
| } else { |
| view.status().Write( |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_REMOTE_FEATURE); |
| } |
| SendCommandChannelPacket(packet.data()); |
| |
| NotifyLEConnectionParameters(peer->address(), conn_params); |
| } |
| |
| void FakeController::OnDisconnectCommandReceived( |
| const pw::bluetooth::emboss::DisconnectCommandView& params) { |
| hci_spec::ConnectionHandle handle = params.connection_handle().Read(); |
| |
| // Find the peer that matches the disconnected handle. |
| FakePeer* peer = FindByConnHandle(handle); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kDisconnect, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| BT_DEBUG_ASSERT(peer->connected()); |
| |
| RespondWithCommandStatus(hci_spec::kDisconnect, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| bool notify = peer->connected(); |
| peer->RemoveLink(handle); |
| if (notify && !peer->connected()) { |
| NotifyConnectionState(peer->address(), handle, /*connected=*/false); |
| } |
| |
| if (auto_disconnection_complete_event_enabled_) { |
| SendDisconnectionCompleteEvent(handle); |
| } |
| } |
| |
| void FakeController::OnWriteLEHostSupportCommandReceived( |
| const pw::bluetooth::emboss::WriteLEHostSupportCommandView& params) { |
| if (params.le_supported_host().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE) { |
| SetBit(&settings_.lmp_features_page1, |
| hci_spec::LMPFeature::kLESupportedHost); |
| } else { |
| UnsetBit(&settings_.lmp_features_page1, |
| hci_spec::LMPFeature::kLESupportedHost); |
| } |
| |
| RespondWithCommandComplete(hci_spec::kWriteLEHostSupport, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnWriteSecureConnectionsHostSupport( |
| const pw::bluetooth::emboss::WriteSecureConnectionsHostSupportCommandView& |
| params) { |
| // Core Spec Volume 4, Part E, Section 7.3.92: If the Host issues this command |
| // while the Controller is paging, has page scanning enabled, or has an ACL |
| // connection, the Controller shall return the error code Command Disallowed |
| // (0x0C). |
| bool has_acl_connection = false; |
| for (auto& [_, peer] : peers_) { |
| if (peer->connected()) { |
| has_acl_connection = true; |
| break; |
| } |
| } |
| if (bredr_connect_pending_ || isBREDRPageScanEnabled() || |
| has_acl_connection) { |
| RespondWithCommandComplete( |
| hci_spec::kWriteSecureConnectionsHostSupport, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| if (params.secure_connections_host_support().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE) { |
| SetBit(&settings_.lmp_features_page1, |
| hci_spec::LMPFeature::kSecureConnectionsHostSupport); |
| } else { |
| UnsetBit(&settings_.lmp_features_page1, |
| hci_spec::LMPFeature::kSecureConnectionsHostSupport); |
| } |
| |
| RespondWithCommandComplete(hci_spec::kWriteSecureConnectionsHostSupport, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnReset() { |
| // TODO(fxbug.dev/42159137): actually do some resetting of stuff here |
| RespondWithCommandComplete(hci_spec::kReset, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnInquiry( |
| const pw::bluetooth::emboss::InquiryCommandView& params) { |
| // Confirm that LAP array is equal to either kGIAC or kLIAC. |
| if (params.lap().Read() != pw::bluetooth::emboss::InquiryAccessCode::GIAC && |
| params.lap().Read() != pw::bluetooth::emboss::InquiryAccessCode::LIAC) { |
| RespondWithCommandStatus( |
| hci_spec::kInquiry, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| if (params.inquiry_length().Read() == 0x00 || |
| params.inquiry_length().Read() > hci_spec::kInquiryLengthMax) { |
| RespondWithCommandStatus( |
| hci_spec::kInquiry, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| inquiry_num_responses_left_ = params.num_responses().Read(); |
| if (params.num_responses().Read() == 0) { |
| inquiry_num_responses_left_ = -1; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kInquiry, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| bt_log(INFO, "fake-hci", "sending inquiry responses.."); |
| SendInquiryResponses(); |
| |
| (void)heap_dispatcher().PostAfter( |
| [this](pw::async::Context /*ctx*/, pw::Status status) { |
| if (!status.ok()) { |
| return; |
| } |
| auto output = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::InquiryCompleteEventWriter>( |
| hci_spec::kInquiryCompleteEventCode); |
| output.view_t().status().Write( |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| SendCommandChannelPacket(output.data()); |
| }, |
| std::chrono::milliseconds(params.inquiry_length().Read()) * 1280); |
| } |
| |
| void FakeController::OnLESetScanEnable( |
| const pw::bluetooth::emboss::LESetScanEnableCommandView& params) { |
| le_scan_state_.enabled = false; |
| if (params.le_scan_enable().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE) { |
| le_scan_state_.enabled = true; |
| } |
| |
| le_scan_state_.filter_duplicates = false; |
| if (params.filter_duplicates().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE) { |
| le_scan_state_.filter_duplicates = true; |
| } |
| |
| // 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_) { |
| scan_state_cb_(le_scan_state_.enabled); |
| } |
| |
| RespondWithCommandComplete(hci_spec::kLESetScanEnable, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| if (le_scan_state_.enabled) { |
| SendAdvertisingReports(); |
| } |
| } |
| |
| void FakeController::OnLESetExtendedScanEnable( |
| const pw::bluetooth::emboss::LESetExtendedScanEnableCommandView& params) { |
| received_extended_operations_ = true; |
| |
| le_scan_state_.enabled = false; |
| if (params.scanning_enabled().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE) { |
| le_scan_state_.enabled = true; |
| } |
| |
| le_scan_state_.filter_duplicates = true; |
| if (params.filter_duplicates().Read() == |
| pw::bluetooth::emboss::LEExtendedDuplicateFilteringOption::DISABLED) { |
| le_scan_state_.filter_duplicates = false; |
| } |
| |
| le_scan_state_.duration = params.duration().Read(); |
| le_scan_state_.period = params.period().Read(); |
| |
| // 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_) { |
| scan_state_cb_(le_scan_state_.enabled); |
| } |
| |
| RespondWithCommandComplete(hci_spec::kLESetExtendedScanEnable, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| if (le_scan_state_.enabled) { |
| SendAdvertisingReports(); |
| } |
| } |
| |
| void FakeController::OnLESetScanParameters( |
| const pw::bluetooth::emboss::LESetScanParametersCommandView& params) { |
| if (le_scan_state_.enabled) { |
| RespondWithCommandComplete( |
| hci_spec::kLESetScanParameters, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| le_scan_state_.own_address_type = params.own_address_type().Read(); |
| le_scan_state_.filter_policy = params.scanning_filter_policy().Read(); |
| le_scan_state_.scan_type = params.le_scan_type().Read(); |
| le_scan_state_.scan_interval = params.le_scan_interval().Read(); |
| le_scan_state_.scan_window = params.le_scan_window().Read(); |
| |
| RespondWithCommandComplete(hci_spec::kLESetScanParameters, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnLESetExtendedScanParameters( |
| const pw::bluetooth::emboss::LESetExtendedScanParametersCommandView& |
| params) { |
| received_extended_operations_ = true; |
| |
| if (le_scan_state_.enabled) { |
| RespondWithCommandComplete( |
| hci_spec::kLESetScanParameters, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| if (params.num_entries().Read() == 0) { |
| RespondWithCommandComplete( |
| hci_spec::kLESetScanParameters, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| le_scan_state_.own_address_type = params.own_address_type().Read(); |
| le_scan_state_.filter_policy = params.scanning_filter_policy().Read(); |
| |
| // ExtendedLowEnergyScanner sets the same parameters for both the LE 1M and LE |
| // Coded PHYs. We just take the parameters from the LE 1M PHY for now since we |
| // don't support using different parameters for different PHYs. |
| if (!params.scanning_phys().le_1m().Read()) { |
| RespondWithCommandComplete( |
| hci_spec::kLESetScanParameters, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| le_scan_state_.scan_type = params.data()[0].scan_type().Read(); |
| le_scan_state_.scan_interval = params.data()[0].scan_interval().Read(); |
| le_scan_state_.scan_window = params.data()[0].scan_window().Read(); |
| RespondWithCommandComplete(hci_spec::kLESetExtendedScanParameters, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnReadLocalExtendedFeatures( |
| const pw::bluetooth::emboss::ReadLocalExtendedFeaturesCommandView& params) { |
| hci_spec::ReadLocalExtendedFeaturesReturnParams out_params; |
| out_params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| out_params.page_number = params.page_number().Read(); |
| out_params.maximum_page_number = 2; |
| out_params.extended_lmp_features = 0; |
| |
| switch (params.page_number().Read()) { |
| 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; |
| default: |
| out_params.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| } |
| |
| RespondWithCommandComplete(hci_spec::kReadLocalExtendedFeatures, |
| BufferView(&out_params, sizeof(out_params))); |
| } |
| |
| void FakeController::OnSetEventMask( |
| const pw::bluetooth::emboss::SetEventMaskCommandView& params) { |
| settings_.event_mask = params.event_mask().Read(); |
| RespondWithCommandComplete(hci_spec::kSetEventMask, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnLESetEventMask( |
| const pw::bluetooth::emboss::LESetEventMaskCommandView& params) { |
| settings_.le_event_mask = params.le_event_mask().BackingStorage().ReadUInt(); |
| RespondWithCommandComplete(hci_spec::kLESetEventMask, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnLEReadBufferSizeV1() { |
| hci_spec::LEReadBufferSizeV1ReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| 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_spec::kLEReadBufferSizeV1, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnLEReadBufferSizeV2() { |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEReadBufferSizeV2CommandCompleteEventWriter>( |
| hci_spec::kCommandCompleteEventCode); |
| auto view = packet.view_t(); |
| |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.le_acl_data_packet_length().Write(settings_.le_acl_data_packet_length); |
| view.total_num_le_acl_data_packets().Write( |
| settings_.le_total_num_acl_data_packets); |
| view.iso_data_packet_length().Write(settings_.iso_data_packet_length); |
| view.total_num_iso_data_packets().Write(settings_.total_num_iso_data_packets); |
| |
| RespondWithCommandComplete(hci_spec::kLEReadBufferSizeV2, &packet); |
| } |
| |
| void FakeController::OnLEReadSupportedStates() { |
| hci_spec::LEReadSupportedStatesReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.le_states = htole64(settings_.le_supported_states); |
| RespondWithCommandComplete(hci_spec::kLEReadSupportedStates, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnLEReadLocalSupportedFeatures() { |
| hci_spec::LEReadLocalSupportedFeaturesReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.le_features = htole64(settings_.le_features); |
| RespondWithCommandComplete(hci_spec::kLEReadLocalSupportedFeatures, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnLECreateConnectionCancel() { |
| if (!le_connect_pending_) { |
| // No request is currently pending. |
| RespondWithCommandComplete( |
| hci_spec::kLECreateConnectionCancel, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| le_connect_pending_ = false; |
| le_connect_rsp_task_.Cancel(); |
| BT_DEBUG_ASSERT(le_connect_params_); |
| |
| NotifyConnectionState(le_connect_params_->peer_address, |
| 0, |
| /*connected=*/false, |
| /*canceled=*/true); |
| |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEConnectionCompleteSubeventWriter>( |
| hci_spec::kLEMetaEventCode); |
| auto view = packet.view_t(); |
| view.le_meta_event().subevent_code().Write( |
| hci_spec::kLEConnectionCompleteSubeventCode); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| view.peer_address().CopyFrom(le_connect_params_->peer_address.value().view()); |
| view.peer_address_type().Write(DeviceAddress::DeviceAddrToLePeerAddr( |
| le_connect_params_->peer_address.type())); |
| |
| RespondWithCommandComplete(hci_spec::kLECreateConnectionCancel, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| SendCommandChannelPacket(packet.data()); |
| } |
| |
| void FakeController::OnWriteExtendedInquiryResponse( |
| const pw::bluetooth::emboss::WriteExtendedInquiryResponseCommandView& |
| params) { |
| // As of now, we don't support FEC encoding enabled. |
| if (params.fec_required().Read() != 0x00) { |
| RespondWithCommandStatus( |
| hci_spec::kWriteExtendedInquiryResponse, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| } |
| |
| RespondWithCommandComplete(hci_spec::kWriteExtendedInquiryResponse, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnWriteSimplePairingMode( |
| const pw::bluetooth::emboss::WriteSimplePairingModeCommandView& params) { |
| // "A host shall not set the Simple Pairing Mode to 'disabled'" |
| // Spec 5.0 Vol 2 Part E Sec 7.3.59 |
| if (params.simple_pairing_mode().Read() != |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE) { |
| RespondWithCommandComplete( |
| hci_spec::kWriteSimplePairingMode, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| SetBit(&settings_.lmp_features_page1, |
| hci_spec::LMPFeature::kSecureSimplePairingHostSupport); |
| RespondWithCommandComplete(hci_spec::kWriteSimplePairingMode, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnReadSimplePairingMode() { |
| hci_spec::ReadSimplePairingModeReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| if (CheckBit(settings_.lmp_features_page1, |
| hci_spec::LMPFeature::kSecureSimplePairingHostSupport)) { |
| params.simple_pairing_mode = |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE; |
| } else { |
| params.simple_pairing_mode = |
| pw::bluetooth::emboss::GenericEnableParam::DISABLE; |
| } |
| |
| RespondWithCommandComplete(hci_spec::kReadSimplePairingMode, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnWritePageScanType( |
| const pw::bluetooth::emboss::WritePageScanTypeCommandView& params) { |
| page_scan_type_ = params.page_scan_type().Read(); |
| RespondWithCommandComplete(hci_spec::kWritePageScanType, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnReadPageScanType() { |
| hci_spec::ReadPageScanTypeReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.page_scan_type = page_scan_type_; |
| RespondWithCommandComplete(hci_spec::kReadPageScanType, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnWriteInquiryMode( |
| const pw::bluetooth::emboss::WriteInquiryModeCommandView& params) { |
| inquiry_mode_ = params.inquiry_mode().Read(); |
| RespondWithCommandComplete(hci_spec::kWriteInquiryMode, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnReadInquiryMode() { |
| hci_spec::ReadInquiryModeReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.inquiry_mode = inquiry_mode_; |
| RespondWithCommandComplete(hci_spec::kReadInquiryMode, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnWriteClassOfDevice( |
| const pw::bluetooth::emboss::WriteClassOfDeviceCommandView& params) { |
| device_class_ = |
| DeviceClass(params.class_of_device().BackingStorage().ReadUInt()); |
| NotifyControllerParametersChanged(); |
| RespondWithCommandComplete(hci_spec::kWriteClassOfDevice, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnWritePageScanActivity( |
| const pw::bluetooth::emboss::WritePageScanActivityCommandView& params) { |
| page_scan_interval_ = params.page_scan_interval().Read(); |
| page_scan_window_ = params.page_scan_window().Read(); |
| RespondWithCommandComplete(hci_spec::kWritePageScanActivity, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnReadPageScanActivity() { |
| hci_spec::ReadPageScanActivityReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.page_scan_interval = htole16(page_scan_interval_); |
| params.page_scan_window = htole16(page_scan_window_); |
| RespondWithCommandComplete(hci_spec::kReadPageScanActivity, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnWriteScanEnable( |
| const pw::bluetooth::emboss::WriteScanEnableCommandView& params) { |
| bredr_scan_state_ = params.scan_enable().BackingStorage().ReadUInt(); |
| RespondWithCommandComplete(hci_spec::kWriteScanEnable, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnReadScanEnable() { |
| hci_spec::ReadScanEnableReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.scan_enable = bredr_scan_state_; |
| RespondWithCommandComplete(hci_spec::kReadScanEnable, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnReadLocalName() { |
| hci_spec::ReadLocalNameReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| auto mut_view = |
| MutableBufferView(params.local_name, hci_spec::kMaxNameLength); |
| mut_view.Write((const uint8_t*)(local_name_.c_str()), |
| std::min(local_name_.length() + 1, hci_spec::kMaxNameLength)); |
| RespondWithCommandComplete(hci_spec::kReadLocalName, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnWriteLocalName( |
| const pw::bluetooth::emboss::WriteLocalNameCommandView& params) { |
| std::size_t name_len = 0; |
| |
| auto local_name = params.local_name().BackingStorage().data(); |
| |
| for (; name_len < hci_spec::kMaxNameLength; ++name_len) { |
| if (local_name[name_len] == '\0') { |
| break; |
| } |
| } |
| local_name_ = std::string(local_name, local_name + name_len); |
| NotifyControllerParametersChanged(); |
| RespondWithCommandComplete(hci_spec::kWriteLocalName, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnCreateConnectionCancel() { |
| hci_spec::CreateConnectionCancelReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.bd_addr = pending_bredr_connect_addr_.value(); |
| |
| if (!bredr_connect_pending_) { |
| // No request is currently pending. |
| params.status = pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID; |
| RespondWithCommandComplete(hci_spec::kCreateConnectionCancel, |
| BufferView(¶ms, sizeof(params))); |
| return; |
| } |
| |
| bredr_connect_pending_ = false; |
| bredr_connect_rsp_task_.Cancel(); |
| |
| NotifyConnectionState( |
| pending_bredr_connect_addr_, 0, /*connected=*/false, /*canceled=*/true); |
| |
| RespondWithCommandComplete(hci_spec::kCreateConnectionCancel, |
| BufferView(¶ms, sizeof(params))); |
| |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ConnectionCompleteEventWriter>( |
| hci_spec::kConnectionCompleteEventCode); |
| response.view_t().status().Write( |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| response.view_t().bd_addr().CopyFrom( |
| pending_bredr_connect_addr_.value().view()); |
| SendCommandChannelPacket(response.data()); |
| } |
| |
| void FakeController::OnReadBufferSize() { |
| hci_spec::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; |
| params.hc_synchronous_data_packet_length = |
| htole16(settings_.synchronous_data_packet_length); |
| params.hc_total_num_synchronous_data_packets = |
| settings_.total_num_synchronous_data_packets; |
| RespondWithCommandComplete(hci_spec::kReadBufferSize, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnReadBRADDR() { |
| hci_spec::ReadBDADDRReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.bd_addr = settings_.bd_addr.value(); |
| RespondWithCommandComplete(hci_spec::kReadBDADDR, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnLESetAdvertisingEnable( |
| const pw::bluetooth::emboss::LESetAdvertisingEnableCommandView& params) { |
| // TODO(fxbug.dev/42161900): if own address type is random, check that a |
| // random address is set |
| |
| legacy_advertising_state_.enabled = |
| params.advertising_enable().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE; |
| RespondWithCommandComplete(hci_spec::kLESetAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLESetScanResponseData( |
| const pw::bluetooth::emboss::LESetScanResponseDataCommandView& params) { |
| legacy_advertising_state_.scan_rsp_length = |
| params.scan_response_data_length().Read(); |
| |
| if (params.scan_response_data_length().Read() == 0) { |
| std::memset(legacy_advertising_state_.scan_rsp_data, |
| 0, |
| sizeof(legacy_advertising_state_.scan_rsp_data)); |
| } else { |
| std::memcpy(legacy_advertising_state_.scan_rsp_data, |
| params.scan_response_data().BackingStorage().data(), |
| params.scan_response_data_length().Read()); |
| } |
| |
| RespondWithCommandComplete(hci_spec::kLESetScanResponseData, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLESetAdvertisingData( |
| const pw::bluetooth::emboss::LESetAdvertisingDataCommandView& params) { |
| legacy_advertising_state_.data_length = |
| params.advertising_data_length().Read(); |
| |
| if (params.advertising_data_length().Read() == 0) { |
| std::memset(legacy_advertising_state_.data, |
| 0, |
| sizeof(legacy_advertising_state_.data)); |
| } else { |
| std::memcpy(legacy_advertising_state_.data, |
| params.advertising_data().BackingStorage().data(), |
| params.advertising_data_length().Read()); |
| } |
| |
| RespondWithCommandComplete(hci_spec::kLESetAdvertisingData, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLESetAdvertisingParameters( |
| const pw::bluetooth::emboss::LESetAdvertisingParametersCommandView& |
| params) { |
| if (legacy_advertising_state_.enabled) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot set advertising parameters while advertising enabled"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| uint16_t interval_min = params.advertising_interval_min().UncheckedRead(); |
| uint16_t interval_max = params.advertising_interval_max().UncheckedRead(); |
| |
| // Core Spec Volume 4, Part E, Section 7.8.5: For high duty cycle directed |
| // advertising, the Advertising_Interval_Min and Advertising_Interval_Max |
| // parameters are not used and shall be ignored. |
| if (params.adv_type().Read() != pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_HIGH_DUTY_CYCLE_DIRECTED) { |
| if (interval_min >= interval_max) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval min (%d) not strictly less than max (%d)", |
| interval_min, |
| interval_max); |
| RespondWithCommandComplete( |
| hci_spec::kLESetAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER); |
| return; |
| } |
| |
| if (interval_min < hci_spec::kLEAdvertisingIntervalMin) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval min (%d) less than spec min (%d)", |
| interval_min, |
| hci_spec::kLEAdvertisingIntervalMin); |
| RespondWithCommandComplete( |
| hci_spec::kLESetAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER); |
| return; |
| } |
| |
| if (interval_max > hci_spec::kLEAdvertisingIntervalMax) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval max (%d) greater than spec max (%d)", |
| interval_max, |
| hci_spec::kLEAdvertisingIntervalMax); |
| RespondWithCommandComplete( |
| hci_spec::kLESetAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER); |
| return; |
| } |
| } |
| |
| legacy_advertising_state_.interval_min = interval_min; |
| legacy_advertising_state_.interval_max = interval_max; |
| legacy_advertising_state_.adv_type = |
| static_cast<pw::bluetooth::emboss::LEAdvertisingType>( |
| params.adv_type().Read()); |
| legacy_advertising_state_.own_address_type = params.own_address_type().Read(); |
| |
| bt_log(INFO, |
| "fake-hci", |
| "start advertising using address type: %hhd", |
| static_cast<char>(legacy_advertising_state_.own_address_type)); |
| |
| RespondWithCommandComplete(hci_spec::kLESetAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLESetRandomAddress( |
| const pw::bluetooth::emboss::LESetRandomAddressCommandView& params) { |
| if (legacy_advertising_state().enabled || le_scan_state().enabled) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot set LE random address while scanning or advertising"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetRandomAddress, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| legacy_advertising_state_.random_address = |
| DeviceAddress(DeviceAddress::Type::kLERandom, |
| DeviceAddressBytes(params.random_address())); |
| RespondWithCommandComplete(hci_spec::kLESetRandomAddress, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnReadLocalSupportedFeatures() { |
| hci_spec::ReadLocalSupportedFeaturesReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.lmp_features = htole64(settings_.lmp_features_page0); |
| RespondWithCommandComplete(hci_spec::kReadLocalSupportedFeatures, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnReadLocalSupportedCommands() { |
| hci_spec::ReadLocalSupportedCommandsReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| std::memcpy(params.supported_commands, |
| settings_.supported_commands, |
| sizeof(params.supported_commands)); |
| RespondWithCommandComplete(hci_spec::kReadLocalSupportedCommands, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnReadLocalVersionInfo() { |
| hci_spec::ReadLocalVersionInfoReturnParams params; |
| std::memset(¶ms, 0, sizeof(params)); |
| params.hci_version = settings_.hci_version; |
| RespondWithCommandComplete(hci_spec::kReadLocalVersionInfo, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnReadRemoteNameRequestCommandReceived( |
| const pw::bluetooth::emboss::RemoteNameRequestCommandView& params) { |
| const DeviceAddress peer_address(DeviceAddress::Type::kBREDR, |
| DeviceAddressBytes(params.bd_addr())); |
| |
| // Find the peer that matches the requested address. |
| FakePeer* peer = FindPeer(peer_address); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kRemoteNameRequest, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kRemoteNameRequest, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| struct RemoteNameRequestCompleteEventParams { |
| pw::bluetooth::emboss::StatusCode status; |
| DeviceAddressBytes bd_addr; |
| uint8_t remote_name[hci_spec::kMaxNameLength]; |
| } __attribute__((packed)); |
| RemoteNameRequestCompleteEventParams response = {}; |
| response.bd_addr = DeviceAddressBytes(params.bd_addr()); |
| std::strncpy((char*)response.remote_name, |
| peer->name().c_str(), |
| hci_spec::kMaxNameLength); |
| response.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| SendEvent(hci_spec::kRemoteNameRequestCompleteEventCode, |
| BufferView(&response, sizeof(response))); |
| } |
| |
| void FakeController::OnReadRemoteSupportedFeaturesCommandReceived( |
| const pw::bluetooth::emboss::ReadRemoteSupportedFeaturesCommandView& |
| params) { |
| RespondWithCommandStatus(hci_spec::kReadRemoteSupportedFeatures, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| hci_spec::ReadRemoteSupportedFeaturesCompleteEventParams response = {}; |
| response.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| response.connection_handle = htole16(params.connection_handle().Read()); |
| response.lmp_features = settings_.lmp_features_page0; |
| SendEvent(hci_spec::kReadRemoteSupportedFeaturesCompleteEventCode, |
| BufferView(&response, sizeof(response))); |
| } |
| |
| void FakeController::OnReadRemoteVersionInfoCommandReceived( |
| const pw::bluetooth::emboss::ReadRemoteVersionInfoCommandView& params) { |
| RespondWithCommandStatus(hci_spec::kReadRemoteVersionInfo, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ReadRemoteVersionInfoCompleteEventWriter>( |
| hci_spec::kReadRemoteVersionInfoCompleteEventCode); |
| auto view = response.view_t(); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.connection_handle().CopyFrom(params.connection_handle()); |
| view.version().Write(pw::bluetooth::emboss::CoreSpecificationVersion::V4_2); |
| view.company_identifier().Write(0xFFFF); // anything |
| view.subversion().Write(0xADDE); // anything |
| SendCommandChannelPacket(response.data()); |
| } |
| |
| void FakeController::OnReadRemoteExtendedFeaturesCommandReceived( |
| const pw::bluetooth::emboss::ReadRemoteExtendedFeaturesCommandView& |
| params) { |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::ReadRemoteExtendedFeaturesCompleteEventWriter>( |
| hci_spec::kReadRemoteExtendedFeaturesCompleteEventCode); |
| auto view = response.view_t(); |
| |
| switch (params.page_number().Read()) { |
| case 1: { |
| view.lmp_features().BackingStorage().WriteUInt( |
| settings_.lmp_features_page1); |
| break; |
| } |
| case 2: { |
| view.lmp_features().BackingStorage().WriteUInt( |
| settings_.lmp_features_page2); |
| break; |
| } |
| default: { |
| RespondWithCommandStatus( |
| hci_spec::kReadRemoteExtendedFeatures, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| } |
| |
| RespondWithCommandStatus(hci_spec::kReadRemoteExtendedFeatures, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.page_number().CopyFrom(params.page_number()); |
| view.max_page_number().Write(3); |
| view.connection_handle().CopyFrom(params.connection_handle()); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| SendCommandChannelPacket(response.data()); |
| } |
| |
| void FakeController::OnAuthenticationRequestedCommandReceived( |
| const pw::bluetooth::emboss::AuthenticationRequestedCommandView& params) { |
| hci_spec::ConnectionHandle handle = params.connection_handle().Read(); |
| FakePeer* peer = FindByConnHandle(handle); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kAuthenticationRequested, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kAuthenticationRequested, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| auto event = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LinkKeyRequestEventWriter>( |
| hci_spec::kLinkKeyRequestEventCode); |
| event.view_t().bd_addr().CopyFrom(peer->address_.value().view()); |
| SendCommandChannelPacket(event.data()); |
| } |
| |
| void FakeController::OnLinkKeyRequestReplyCommandReceived( |
| const pw::bluetooth::emboss::LinkKeyRequestReplyCommandView& params) { |
| DeviceAddress peer_address(DeviceAddress::Type::kBREDR, |
| DeviceAddressBytes(params.bd_addr())); |
| FakePeer* peer = FindPeer(peer_address); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kLinkKeyRequestReply, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kLinkKeyRequestReply, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| RespondWithCommandComplete(hci_spec::kLinkKeyRequestReply, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| BT_ASSERT(!peer->logical_links().empty()); |
| for (auto& conn_handle : peer->logical_links()) { |
| auto event = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::AuthenticationCompleteEventWriter>( |
| hci_spec::kAuthenticationCompleteEventCode); |
| event.view_t().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| event.view_t().connection_handle().Write(conn_handle); |
| SendCommandChannelPacket(event.data()); |
| } |
| } |
| |
| void FakeController::OnLinkKeyRequestNegativeReplyCommandReceived( |
| const pw::bluetooth::emboss::LinkKeyRequestNegativeReplyCommandView& |
| params) { |
| FakePeer* peer = FindPeer(DeviceAddress( |
| DeviceAddress::Type::kBREDR, DeviceAddressBytes(params.bd_addr()))); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kLinkKeyRequestNegativeReply, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| RespondWithCommandStatus(hci_spec::kLinkKeyRequestNegativeReply, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| auto event = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::IoCapabilityRequestEventWriter>( |
| hci_spec::kIOCapabilityRequestEventCode); |
| event.view_t().bd_addr().CopyFrom(params.bd_addr()); |
| SendCommandChannelPacket(event.data()); |
| } |
| |
| void FakeController::OnIOCapabilityRequestReplyCommand( |
| const pw::bluetooth::emboss::IoCapabilityRequestReplyCommandView& params) { |
| RespondWithCommandStatus(hci_spec::kIOCapabilityRequestReply, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| auto io_response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::IoCapabilityResponseEventWriter>( |
| hci_spec::kIOCapabilityResponseEventCode); |
| io_response.view_t().bd_addr().CopyFrom(params.bd_addr()); |
| io_response.view_t().io_capability().Write( |
| pw::bluetooth::emboss::IoCapability::NO_INPUT_NO_OUTPUT); |
| io_response.view_t().oob_data_present().Write( |
| pw::bluetooth::emboss::GenericPresenceParam::NOT_PRESENT); |
| io_response.view_t().authentication_requirements().Write( |
| pw::bluetooth::emboss::AuthenticationRequirements::GENERAL_BONDING); |
| SendCommandChannelPacket(io_response.data()); |
| |
| // Event type based on |params.io_capability| and |io_response.io_capability|. |
| hci_spec::UserConfirmationRequestEventParams request = {}; |
| request.bd_addr = DeviceAddressBytes(params.bd_addr()); |
| request.numeric_value = 0; |
| SendEvent(hci_spec::kUserConfirmationRequestEventCode, |
| BufferView(&request, sizeof(request))); |
| } |
| |
| void FakeController::OnUserConfirmationRequestReplyCommand( |
| const pw::bluetooth::emboss::UserConfirmationRequestReplyCommandView& |
| params) { |
| FakePeer* peer = FindPeer(DeviceAddress( |
| DeviceAddress::Type::kBREDR, DeviceAddressBytes(params.bd_addr()))); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kUserConfirmationRequestReply, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kUserConfirmationRequestReply, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| hci_spec::SimplePairingCompleteEventParams pairing_event; |
| pairing_event.bd_addr = DeviceAddressBytes(params.bd_addr()); |
| pairing_event.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| SendEvent(hci_spec::kSimplePairingCompleteEventCode, |
| BufferView(&pairing_event, sizeof(pairing_event))); |
| |
| auto link_key_event = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LinkKeyNotificationEventWriter>( |
| hci_spec::kLinkKeyNotificationEventCode); |
| auto link_key_view = link_key_event.view_t(); |
| link_key_view.bd_addr().CopyFrom(params.bd_addr()); |
| uint8_t key[] = {0xc0, |
| 0xde, |
| 0xfa, |
| 0x57, |
| 0x4b, |
| 0xad, |
| 0xf0, |
| 0x0d, |
| 0xa7, |
| 0x60, |
| 0x06, |
| 0x1e, |
| 0xca, |
| 0x1e, |
| 0xca, |
| 0xfe}; |
| link_key_view.link_key().value().BackingStorage().CopyFrom( |
| ::emboss::support::ReadOnlyContiguousBuffer(key, sizeof(key)), |
| sizeof(key)); |
| link_key_view.key_type().Write( |
| pw::bluetooth::emboss::KeyType::UNAUTHENTICATED_COMBINATION_FROM_P192); |
| SendCommandChannelPacket(link_key_event.data()); |
| |
| BT_ASSERT(!peer->logical_links().empty()); |
| for (auto& conn_handle : peer->logical_links()) { |
| auto event = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::AuthenticationCompleteEventWriter>( |
| hci_spec::kAuthenticationCompleteEventCode); |
| event.view_t().status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| event.view_t().connection_handle().Write(conn_handle); |
| SendCommandChannelPacket(event.data()); |
| } |
| } |
| |
| void FakeController::OnUserConfirmationRequestNegativeReplyCommand( |
| const pw::bluetooth::emboss:: |
| UserConfirmationRequestNegativeReplyCommandView& params) { |
| FakePeer* peer = FindPeer(DeviceAddress( |
| DeviceAddress::Type::kBREDR, DeviceAddressBytes(params.bd_addr()))); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kUserConfirmationRequestNegativeReply, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kUserConfirmationRequestNegativeReply, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| RespondWithCommandComplete(hci_spec::kUserConfirmationRequestNegativeReply, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| hci_spec::SimplePairingCompleteEventParams pairing_event; |
| pairing_event.bd_addr = DeviceAddressBytes(params.bd_addr()); |
| pairing_event.status = |
| pw::bluetooth::emboss::StatusCode::AUTHENTICATION_FAILURE; |
| SendEvent(hci_spec::kSimplePairingCompleteEventCode, |
| BufferView(&pairing_event, sizeof(pairing_event))); |
| } |
| |
| void FakeController::OnSetConnectionEncryptionCommand( |
| const pw::bluetooth::emboss::SetConnectionEncryptionCommandView& params) { |
| RespondWithCommandStatus(hci_spec::kSetConnectionEncryption, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| SendEncryptionChangeEvent(params.connection_handle().Read(), |
| pw::bluetooth::emboss::StatusCode::SUCCESS, |
| pw::bluetooth::emboss::EncryptionStatus:: |
| ON_WITH_E0_FOR_BREDR_OR_AES_FOR_LE); |
| } |
| |
| void FakeController::OnReadEncryptionKeySizeCommand( |
| const pw::bluetooth::emboss::ReadEncryptionKeySizeCommandView& params) { |
| hci_spec::ReadEncryptionKeySizeReturnParams response; |
| response.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| response.connection_handle = params.connection_handle().Read(); |
| response.key_size = 16; |
| RespondWithCommandComplete(hci_spec::kReadEncryptionKeySize, |
| BufferView(&response, sizeof(response))); |
| } |
| |
| void FakeController::OnEnhancedAcceptSynchronousConnectionRequestCommand( |
| const pw::bluetooth::emboss:: |
| EnhancedAcceptSynchronousConnectionRequestCommandView& params) { |
| DeviceAddress peer_address(DeviceAddress::Type::kBREDR, |
| DeviceAddressBytes(params.bd_addr())); |
| FakePeer* peer = FindPeer(peer_address); |
| if (!peer || !peer->last_connection_request_link_type().has_value()) { |
| RespondWithCommandStatus( |
| hci_spec::kEnhancedAcceptSynchronousConnectionRequest, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| RespondWithCommandStatus( |
| hci_spec::kEnhancedAcceptSynchronousConnectionRequest, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| hci_spec::ConnectionHandle sco_handle = ++next_conn_handle_; |
| peer->AddLink(sco_handle); |
| |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::SynchronousConnectionCompleteEventWriter>( |
| hci_spec::kSynchronousConnectionCompleteEventCode); |
| auto view = packet.view_t(); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.connection_handle().Write(sco_handle); |
| view.bd_addr().CopyFrom(peer->address().value().view()); |
| view.link_type().Write(peer->last_connection_request_link_type().value()); |
| view.transmission_interval().Write(1); |
| view.retransmission_window().Write(2); |
| view.rx_packet_length().Write(3); |
| view.tx_packet_length().Write(4); |
| view.air_mode().Write(params.connection_parameters() |
| .transmit_coding_format() |
| .coding_format() |
| .Read()); |
| SendCommandChannelPacket(packet.data()); |
| } |
| |
| void FakeController::OnEnhancedSetupSynchronousConnectionCommand( |
| const pw::bluetooth::emboss::EnhancedSetupSynchronousConnectionCommandView& |
| params) { |
| const hci_spec::ConnectionHandle acl_handle = |
| params.connection_handle().Read(); |
| FakePeer* peer = FindByConnHandle(acl_handle); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kEnhancedSetupSynchronousConnection, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kEnhancedSetupSynchronousConnection, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| hci_spec::ConnectionHandle sco_handle = ++next_conn_handle_; |
| peer->AddLink(sco_handle); |
| |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::SynchronousConnectionCompleteEventWriter>( |
| hci_spec::kSynchronousConnectionCompleteEventCode); |
| auto view = packet.view_t(); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.connection_handle().Write(sco_handle); |
| view.bd_addr().CopyFrom(peer->address().value().view()); |
| view.link_type().Write(pw::bluetooth::emboss::LinkType::ESCO); |
| view.transmission_interval().Write(1); |
| view.retransmission_window().Write(2); |
| view.rx_packet_length().Write(3); |
| view.tx_packet_length().Write(4); |
| view.air_mode().Write(params.connection_parameters() |
| .transmit_coding_format() |
| .coding_format() |
| .Read()); |
| SendCommandChannelPacket(packet.data()); |
| } |
| |
| void FakeController::OnLEReadRemoteFeaturesCommand( |
| const hci_spec::LEReadRemoteFeaturesCommandParams& params) { |
| if (le_read_remote_features_cb_) { |
| le_read_remote_features_cb_(); |
| } |
| |
| const hci_spec::ConnectionHandle handle = le16toh(params.connection_handle); |
| FakePeer* peer = FindByConnHandle(handle); |
| if (!peer) { |
| RespondWithCommandStatus( |
| hci_spec::kLEReadRemoteFeatures, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_CONNECTION_ID); |
| return; |
| } |
| |
| RespondWithCommandStatus(hci_spec::kLEReadRemoteFeatures, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| |
| auto response = hci::EmbossEventPacket::New< |
| pw::bluetooth::emboss::LEReadRemoteFeaturesCompleteSubeventWriter>( |
| hci_spec::kLEMetaEventCode); |
| auto view = response.view_t(); |
| view.le_meta_event().subevent_code().Write( |
| hci_spec::kLEReadRemoteFeaturesCompleteSubeventCode); |
| view.connection_handle().Write(params.connection_handle); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.le_features().BackingStorage().WriteUInt( |
| peer->le_features().le_features); |
| SendCommandChannelPacket(response.data()); |
| } |
| |
| void FakeController::OnLEStartEncryptionCommand( |
| const pw::bluetooth::emboss::LEEnableEncryptionCommandView& params) { |
| RespondWithCommandStatus(hci_spec::kLEStartEncryption, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| SendEncryptionChangeEvent(params.connection_handle().Read(), |
| pw::bluetooth::emboss::StatusCode::SUCCESS, |
| pw::bluetooth::emboss::EncryptionStatus:: |
| ON_WITH_E0_FOR_BREDR_OR_AES_FOR_LE); |
| } |
| |
| void FakeController::OnWriteSynchronousFlowControlEnableCommand( |
| const pw::bluetooth::emboss::WriteSynchronousFlowControlEnableCommandView& |
| params) { |
| constexpr size_t flow_control_enable_octet = 10; |
| bool supported = |
| settings_.supported_commands[flow_control_enable_octet] & |
| static_cast<uint8_t>( |
| hci_spec::SupportedCommand::kWriteSynchronousFlowControlEnable); |
| if (!supported) { |
| RespondWithCommandComplete( |
| hci_spec::kWriteSynchronousFlowControlEnable, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_COMMAND); |
| return; |
| } |
| RespondWithCommandComplete(hci_spec::kWriteSynchronousFlowControlEnable, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnLESetAdvertisingSetRandomAddress( |
| const pw::bluetooth::emboss::LESetAdvertisingSetRandomAddressCommandView& |
| params) { |
| hci_spec::AdvertisingHandle handle = params.advertising_handle().Read(); |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetAdvertisingSetRandomAddress, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| if (extended_advertising_states_.count(handle) == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "unknown advertising handle (%d), " |
| "use HCI_LE_Set_Extended_Advertising_Parameters to create one first", |
| handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetAdvertisingSetRandomAddress, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| LEAdvertisingState& state = extended_advertising_states_[handle]; |
| if (state.IsConnectableAdvertising() && state.enabled) { |
| bt_log( |
| INFO, |
| "fake-hci", |
| "cannot set LE random address while connectable advertising enabled"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetAdvertisingSetRandomAddress, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| state.random_address = |
| DeviceAddress(DeviceAddress::Type::kLERandom, |
| DeviceAddressBytes(params.random_address())); |
| RespondWithCommandComplete(hci_spec::kLESetAdvertisingSetRandomAddress, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| } |
| |
| void FakeController::OnLESetExtendedAdvertisingParameters( |
| const pw::bluetooth::emboss:: |
| LESetExtendedAdvertisingParametersV1CommandView& params) { |
| hci_spec::AdvertisingHandle handle = params.advertising_handle().Read(); |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| // ensure we can allocate memory for this advertising set if not already |
| // present |
| if (extended_advertising_states_.count(handle) == 0 && |
| extended_advertising_states_.size() >= num_supported_advertising_sets()) { |
| bt_log(INFO, |
| "fake-hci", |
| "no available memory for new advertising set, handle: %d", |
| handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::MEMORY_CAPACITY_EXCEEDED); |
| return; |
| } |
| |
| // for backwards compatibility, we only support legacy pdus |
| if (!params.advertising_event_properties().use_legacy_pdus().Read()) { |
| bt_log( |
| INFO, |
| "fake-hci", |
| "only legacy PDUs are supported, extended PDUs are not supported yet"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| // ensure we have a valid bit combination in the advertising event properties |
| constexpr uint16_t legacy_pdu = hci_spec::kLEAdvEventPropBitUseLegacyPDUs; |
| constexpr uint16_t prop_bits_adv_ind = |
| legacy_pdu | hci_spec::kLEAdvEventPropBitConnectable | |
| hci_spec::kLEAdvEventPropBitScannable; |
| constexpr uint16_t prop_bits_adv_direct_ind_low_duty_cycle = |
| legacy_pdu | hci_spec::kLEAdvEventPropBitConnectable | |
| hci_spec::kLEAdvEventPropBitDirected; |
| constexpr uint16_t prop_bits_adv_direct_ind_high_duty_cycle = |
| prop_bits_adv_direct_ind_low_duty_cycle | |
| hci_spec::kLEAdvEventPropBitHighDutyCycleDirectedConnectable; |
| constexpr uint16_t prop_bits_adv_scan_ind = |
| legacy_pdu | hci_spec::kLEAdvEventPropBitScannable; |
| constexpr uint16_t prop_bits_adv_nonconn_ind = legacy_pdu; |
| |
| pw::bluetooth::emboss::LEAdvertisingType adv_type; |
| uint16_t advertising_event_properties = |
| params.advertising_event_properties().BackingStorage().ReadUInt(); |
| switch (advertising_event_properties) { |
| case prop_bits_adv_ind: |
| adv_type = pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_AND_SCANNABLE_UNDIRECTED; |
| break; |
| case prop_bits_adv_direct_ind_high_duty_cycle: |
| adv_type = pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_HIGH_DUTY_CYCLE_DIRECTED; |
| break; |
| case prop_bits_adv_direct_ind_low_duty_cycle: |
| adv_type = pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_LOW_DUTY_CYCLE_DIRECTED; |
| break; |
| case prop_bits_adv_scan_ind: |
| adv_type = pw::bluetooth::emboss::LEAdvertisingType::SCANNABLE_UNDIRECTED; |
| break; |
| case prop_bits_adv_nonconn_ind: |
| adv_type = |
| pw::bluetooth::emboss::LEAdvertisingType::NOT_CONNECTABLE_UNDIRECTED; |
| break; |
| default: |
| bt_log(INFO, |
| "fake-hci", |
| "invalid bit combination: %d", |
| advertising_event_properties); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| // In case there is an error below, we want to reject all parameters instead |
| // of storing a dead state and taking up an advertising handle. Avoid creating |
| // the LEAdvertisingState directly in the map and add it in only once we have |
| // made sure all is good. |
| LEAdvertisingState state; |
| if (extended_advertising_states_.count(handle) != 0) { |
| state = extended_advertising_states_[handle]; |
| } |
| |
| uint32_t interval_min = params.primary_advertising_interval_min().Read(); |
| uint32_t interval_max = params.primary_advertising_interval_max().Read(); |
| |
| if (interval_min >= interval_max) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval min (%d) not strictly less than max (%d)", |
| interval_min, |
| interval_max); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER); |
| return; |
| } |
| |
| if (interval_min < hci_spec::kLEExtendedAdvertisingIntervalMin) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval min (%d) less than spec min (%d)", |
| interval_min, |
| hci_spec::kLEAdvertisingIntervalMin); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER); |
| return; |
| } |
| |
| if (interval_max > hci_spec::kLEExtendedAdvertisingIntervalMax) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval max (%d) greater than spec max (%d)", |
| interval_max, |
| hci_spec::kLEAdvertisingIntervalMax); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER); |
| return; |
| } |
| |
| uint8_t advertising_channels = |
| params.primary_advertising_channel_map().BackingStorage().ReadUInt(); |
| if (!advertising_channels) { |
| bt_log(INFO, |
| "fake-hci", |
| "at least one bit must be set in primary advertising channel map"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| int8_t advertising_tx_power = params.advertising_tx_power().Read(); |
| if (advertising_tx_power != |
| hci_spec::kLEExtendedAdvertisingTxPowerNoPreference && |
| (advertising_tx_power < hci_spec::kLEAdvertisingTxPowerMin || |
| advertising_tx_power > hci_spec::kLEAdvertisingTxPowerMax)) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising tx power out of range: %d", |
| advertising_tx_power); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| // TODO(fxbug.dev/42160350): Core spec Volume 4, Part E, Section 7.8.53: if |
| // legacy advertising PDUs are being used, the Primary_Advertising_PHY shall |
| // indicate the LE 1M PHY. |
| if (params.primary_advertising_phy().Read() != |
| pw::bluetooth::emboss::LEPrimaryAdvertisingPHY::LE_1M) { |
| bt_log(INFO, |
| "fake-hci", |
| "only legacy pdus are supported, requires advertising on 1M PHY"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER); |
| return; |
| } |
| |
| if (params.secondary_advertising_phy().Read() != |
| pw::bluetooth::emboss::LESecondaryAdvertisingPHY::LE_1M) { |
| bt_log(INFO, "fake-hci", "secondary advertising PHY must be selected"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER); |
| return; |
| } |
| |
| if (state.enabled) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot set parameters while advertising set is enabled"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingParameters, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| // all errors checked, set parameters that we care about |
| state.adv_type = adv_type; |
| state.own_address_type = params.own_address_type().Read(); |
| state.interval_min = interval_min; |
| state.interval_max = interval_max; |
| |
| // write full state back only at the end (we don't have a reference because we |
| // only want to write if there are no errors) |
| extended_advertising_states_[handle] = state; |
| |
| hci_spec::LESetExtendedAdvertisingParametersReturnParams return_params; |
| return_params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| return_params.selected_tx_power = hci_spec::kLEAdvertisingTxPowerMax; |
| RespondWithCommandComplete(hci_spec::kLESetExtendedAdvertisingParameters, |
| BufferView(&return_params, sizeof(return_params))); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLESetExtendedAdvertisingData( |
| const pw::bluetooth::emboss::LESetExtendedAdvertisingDataCommandView& |
| params) { |
| // controller currently doesn't support fragmented advertising, assert so we |
| // fail if we ever use it in host code without updating the controller for |
| // tests |
| BT_ASSERT(params.operation().Read() == |
| pw::bluetooth::emboss::LESetExtendedAdvDataOp::COMPLETE); |
| BT_ASSERT(params.fragment_preference().Read() == |
| pw::bluetooth::emboss::LEExtendedAdvFragmentPreference:: |
| SHOULD_NOT_FRAGMENT); |
| |
| hci_spec::AdvertisingHandle handle = params.advertising_handle().Read(); |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingData, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| if (extended_advertising_states_.count(handle) == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising handle (%d) maps to an unknown advertising set", |
| handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingData, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_ADVERTISING_IDENTIFIER); |
| return; |
| } |
| |
| LEAdvertisingState& state = extended_advertising_states_[handle]; |
| |
| // removing advertising data entirely doesn't require us to check for error |
| // conditions |
| size_t advertising_data_length = params.advertising_data_length().Read(); |
| if (advertising_data_length == 0) { |
| state.data_length = 0; |
| std::memset(state.data, 0, sizeof(state.data)); |
| RespondWithCommandComplete(hci_spec::kLESetExtendedAdvertisingData, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| return; |
| } |
| |
| // directed advertising doesn't support advertising data |
| if (state.IsDirectedAdvertising()) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot provide advertising data when using directed advertising"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingData, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| // For backwards compatibility with older devices, the host currently uses |
| // legacy advertising PDUs. The advertising data cannot exceed the legacy |
| // advertising PDU limit. |
| if (advertising_data_length > hci_spec::kMaxLEAdvertisingDataLength) { |
| bt_log(INFO, |
| "fake-hci", |
| "data length (%zu bytes) larger than legacy PDU size limit", |
| advertising_data_length); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingData, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| state.data_length = advertising_data_length; |
| std::memcpy(state.data, |
| params.advertising_data().BackingStorage().data(), |
| advertising_data_length); |
| RespondWithCommandComplete(hci_spec::kLESetExtendedAdvertisingData, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLESetExtendedScanResponseData( |
| const pw::bluetooth::emboss::LESetExtendedScanResponseDataCommandView& |
| params) { |
| // controller currently doesn't support fragmented advertising, assert so we |
| // fail if we ever use it in host code without updating the controller for |
| // tests |
| BT_ASSERT(params.operation().Read() == |
| pw::bluetooth::emboss::LESetExtendedAdvDataOp::COMPLETE); |
| BT_ASSERT(params.fragment_preference().Read() == |
| pw::bluetooth::emboss::LEExtendedAdvFragmentPreference:: |
| SHOULD_NOT_FRAGMENT); |
| |
| hci_spec::AdvertisingHandle handle = params.advertising_handle().Read(); |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedScanResponseData, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| if (extended_advertising_states_.count(handle) == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising handle (%d) maps to an unknown advertising set", |
| handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedScanResponseData, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_ADVERTISING_IDENTIFIER); |
| return; |
| } |
| |
| LEAdvertisingState& state = extended_advertising_states_[handle]; |
| |
| // removing scan response data entirely doesn't require us to check for error |
| // conditions |
| if (params.scan_response_data_length().Read() == 0) { |
| state.scan_rsp_length = 0; |
| std::memset(state.scan_rsp_data, 0, sizeof(state.scan_rsp_data)); |
| RespondWithCommandComplete(hci_spec::kLESetExtendedScanResponseData, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| return; |
| } |
| |
| // adding or changing scan response data, check for error conditions |
| if (!state.IsScannableAdvertising()) { |
| bt_log( |
| INFO, |
| "fake-hci", |
| "cannot provide scan response data for unscannable advertising types"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedScanResponseData, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| // For backwards compatibility with older devices, the host currently uses |
| // legacy advertising PDUs. The scan response data cannot exceed the legacy |
| // advertising PDU limit. |
| if (params.scan_response_data_length().Read() > |
| hci_spec::kMaxLEAdvertisingDataLength) { |
| bt_log(INFO, |
| "fake-hci", |
| "data length (%d bytes) larger than legacy PDU size limit", |
| params.scan_response_data_length().Read()); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedScanResponseData, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| state.scan_rsp_length = params.scan_response_data_length().Read(); |
| std::memcpy(state.scan_rsp_data, |
| params.scan_response_data().BackingStorage().data(), |
| params.scan_response_data_length().Read()); |
| |
| RespondWithCommandComplete(hci_spec::kLESetExtendedScanResponseData, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLESetExtendedAdvertisingEnable( |
| const pw::bluetooth::emboss::LESetExtendedAdvertisingEnableCommandView& |
| params) { |
| uint8_t num_sets = params.num_sets().Read(); |
| |
| // do some preliminary checks before making any state changes |
| if (num_sets != 0) { |
| std::unordered_set<hci_spec::AdvertisingHandle> handles; |
| |
| for (uint8_t i = 0; i < num_sets; i++) { |
| hci_spec::AdvertisingHandle handle = |
| params.data()[i].advertising_handle().Read(); |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log( |
| ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| // cannot have two array entries for the same advertising handle |
| if (handles.count(handle) != 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot refer to handle more than once (handle: %d)", |
| handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| handles.insert(handle); |
| |
| // cannot have instructions for an advertising handle we don't know about |
| if (extended_advertising_states_.count(handle) == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot enable/disable an unknown handle (handle: %d)", |
| handle); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_ADVERTISING_IDENTIFIER); |
| return; |
| } |
| } |
| } |
| |
| if (params.enable().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::DISABLE) { |
| if (num_sets == 0) { |
| // if params.enable == kDisable and params.num_sets == 0, spec asks we |
| // disable all |
| for (auto& element : extended_advertising_states_) { |
| element.second.enabled = false; |
| } |
| } else { |
| for (int i = 0; i < num_sets; i++) { |
| hci_spec::AdvertisingHandle handle = |
| params.data()[i].advertising_handle().Read(); |
| extended_advertising_states_[handle].enabled = false; |
| } |
| } |
| |
| RespondWithCommandComplete(hci_spec::kLESetExtendedAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| return; |
| } |
| |
| // rest of the function deals with enabling advertising for a given set of |
| // advertising sets |
| BT_ASSERT(params.enable().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE); |
| |
| if (num_sets == 0) { |
| bt_log( |
| INFO, "fake-hci", "cannot enable with an empty advertising set list"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| for (uint8_t i = 0; i < num_sets; i++) { |
| // FakeController currently doesn't support testing with duration and max |
| // events. When those are used in the host, these checks will fail and |
| // remind us to add the necessary code to FakeController. |
| BT_ASSERT(params.data()[i].duration().Read() == 0); |
| BT_ASSERT(params.data()[i].max_extended_advertising_events().Read() == 0); |
| |
| hci_spec::AdvertisingHandle handle = |
| params.data()[i].advertising_handle().Read(); |
| LEAdvertisingState& state = extended_advertising_states_[handle]; |
| |
| if (state.IsDirectedAdvertising() && state.data_length == 0) { |
| bt_log( |
| INFO, |
| "fake-hci", |
| "cannot enable type requiring advertising data without setting it"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| if (state.IsScannableAdvertising() && state.scan_rsp_length == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot enable, requires scan response data but hasn't been set"); |
| RespondWithCommandComplete( |
| hci_spec::kLESetExtendedAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| // TODO(fxbug.dev/42161900): if own address type is random, check that a |
| // random address is set |
| state.enabled = true; |
| } |
| |
| RespondWithCommandComplete(hci_spec::kLESetExtendedAdvertisingEnable, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLEReadMaximumAdvertisingDataLength() { |
| hci_spec::LEReadMaxAdvertisingDataLengthReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| |
| // TODO(fxbug.dev/42157495): Extended advertising supports sending larger |
| // amounts of data, but they have to be fragmented across multiple commands to |
| // the controller. This is not yet supported in this implementation. We should |
| // support larger than kMaxPduLEExtendedAdvertisingDataLength advertising data |
| // with fragmentation. |
| params.max_adv_data_length = htole16(hci_spec::kMaxLEAdvertisingDataLength); |
| RespondWithCommandComplete(hci_spec::kLEReadMaxAdvertisingDataLength, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnLEReadNumberOfSupportedAdvertisingSets() { |
| hci_spec::LEReadNumSupportedAdvertisingSetsReturnParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.num_supported_adv_sets = htole16(num_supported_advertising_sets_); |
| RespondWithCommandComplete(hci_spec::kLEReadNumSupportedAdvertisingSets, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::OnLERemoveAdvertisingSet( |
| const hci_spec::LERemoveAdvertisingSetCommandParams& params) { |
| hci_spec::AdvertisingHandle handle = params.adv_handle; |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| RespondWithCommandComplete( |
| hci_spec::kLERemoveAdvertisingSet, |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS); |
| return; |
| } |
| |
| if (extended_advertising_states_.count(handle) == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising handle (%d) maps to an unknown advertising set", |
| handle); |
| RespondWithCommandComplete( |
| hci_spec::kLERemoveAdvertisingSet, |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_ADVERTISING_IDENTIFIER); |
| return; |
| } |
| |
| if (extended_advertising_states_[handle].enabled) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot remove enabled advertising set (handle: %d)", |
| handle); |
| RespondWithCommandComplete( |
| hci_spec::kLERemoveAdvertisingSet, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| |
| extended_advertising_states_.erase(handle); |
| RespondWithCommandComplete(hci_spec::kLERemoveAdvertisingSet, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLEClearAdvertisingSets() { |
| for (const auto& element : extended_advertising_states_) { |
| if (element.second.enabled) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot remove currently enabled advertising set (handle: %d)", |
| element.second.enabled); |
| RespondWithCommandComplete( |
| hci_spec::kLEClearAdvertisingSets, |
| pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED); |
| return; |
| } |
| } |
| |
| extended_advertising_states_.clear(); |
| RespondWithCommandComplete(hci_spec::kLEClearAdvertisingSets, |
| pw::bluetooth::emboss::StatusCode::SUCCESS); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnLEReadAdvertisingChannelTxPower() { |
| if (!respond_to_tx_power_read_) { |
| return; |
| } |
| |
| hci_spec::LEReadAdvertisingChannelTxPowerReturnParams params; |
| // Send back arbitrary tx power. |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.tx_power = 9; |
| RespondWithCommandComplete(hci_spec::kLEReadAdvertisingChannelTxPower, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::SendLEAdvertisingSetTerminatedEvent( |
| hci_spec::ConnectionHandle conn_handle, |
| hci_spec::AdvertisingHandle adv_handle) { |
| hci_spec::LEAdvertisingSetTerminatedSubeventParams params; |
| params.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| params.connection_handle = conn_handle; |
| params.adv_handle = adv_handle; |
| SendLEMetaEvent(hci_spec::kLEAdvertisingSetTerminatedSubeventCode, |
| BufferView(¶ms, sizeof(params))); |
| } |
| |
| void FakeController::SendAndroidLEMultipleAdvertisingStateChangeSubevent( |
| hci_spec::ConnectionHandle conn_handle, |
| hci_spec::AdvertisingHandle adv_handle) { |
| auto packet = hci::EmbossEventPacket::New< |
| pw::bluetooth::vendor::android_hci::LEMultiAdvtStateChangeSubeventWriter>( |
| hci_spec::kVendorDebugEventCode); |
| auto view = packet.view_t(); |
| view.vendor_event().subevent_code().Write( |
| hci_android::kLEMultiAdvtStateChangeSubeventCode); |
| view.advertising_handle().Write(adv_handle); |
| view.status().Write(pw::bluetooth::emboss::StatusCode::SUCCESS); |
| view.connection_handle().Write(conn_handle); |
| SendCommandChannelPacket(packet.data()); |
| } |
| |
| void FakeController::OnCommandPacketReceived( |
| const PacketView<hci_spec::CommandHeader>& command_packet) { |
| hci_spec::OpCode opcode = le16toh(command_packet.header().opcode); |
| |
| bt_log( |
| TRACE, "fake-hci", "received command packet with opcode: %#.4x", opcode); |
| // We handle commands immediately unless a client has explicitly set a |
| // listener for `opcode`. |
| if (paused_opcode_listeners_.find(opcode) == paused_opcode_listeners_.end()) { |
| HandleReceivedCommandPacket(command_packet); |
| return; |
| } |
| |
| bt_log(DEBUG, "fake-hci", "pausing response for opcode: %#.4x", opcode); |
| paused_opcode_listeners_[opcode]( |
| [this, packet_data = DynamicByteBuffer(command_packet.data())]() { |
| PacketView<hci_spec::CommandHeader> command_packet( |
| &packet_data, packet_data.size() - sizeof(hci_spec::CommandHeader)); |
| HandleReceivedCommandPacket(command_packet); |
| }); |
| } |
| |
| void FakeController::OnAndroidLEGetVendorCapabilities() { |
| RespondWithCommandComplete(hci_android::kLEGetVendorCapabilities, |
| settings_.android_extension_settings.data()); |
| } |
| |
| void FakeController::OnAndroidStartA2dpOffload( |
| const pw::bluetooth::vendor::android_hci::StartA2dpOffloadCommandView& |
| params) { |
| hci_android::StartA2dpOffloadCommandReturnParams ret; |
| ret.opcode = hci_android::kStartA2dpOffloadCommandSubopcode; |
| |
| // return in case A2DP offload already started |
| if (offloaded_a2dp_channel_state_) { |
| ret.status = pw::bluetooth::emboss::StatusCode::CONNECTION_ALREADY_EXISTS; |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| // SCMS-T is not currently supported |
| hci_android::A2dpScmsTEnable scms_t_enable; |
| scms_t_enable.enabled = params.scms_t_enable().enabled().Read(); |
| scms_t_enable.header = params.scms_t_enable().header().Read(); |
| if (scms_t_enable.enabled == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE) { |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER; |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| // return in case any parameter has an invalid value |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| |
| hci_android::A2dpCodecType const codec_type = |
| static_cast<hci_android::A2dpCodecType>( |
| le32toh(static_cast<uint32_t>(params.codec_type().Read()))); |
| switch (codec_type) { |
| case hci_android::A2dpCodecType::kSbc: |
| case hci_android::A2dpCodecType::kAac: |
| case hci_android::A2dpCodecType::kAptx: |
| case hci_android::A2dpCodecType::kAptxhd: |
| case hci_android::A2dpCodecType::kLdac: |
| break; |
| default: |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| hci_android::A2dpSamplingFrequency const sampling_frequency = |
| static_cast<hci_android::A2dpSamplingFrequency>( |
| le32toh(static_cast<uint32_t>(params.sampling_frequency().Read()))); |
| switch (sampling_frequency) { |
| case hci_android::A2dpSamplingFrequency::k44100Hz: |
| case hci_android::A2dpSamplingFrequency::k48000Hz: |
| case hci_android::A2dpSamplingFrequency::k88200Hz: |
| case hci_android::A2dpSamplingFrequency::k96000Hz: |
| break; |
| default: |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| hci_android::A2dpBitsPerSample const bits_per_sample = |
| static_cast<hci_android::A2dpBitsPerSample>( |
| params.bits_per_sample().Read()); |
| switch (bits_per_sample) { |
| case hci_android::A2dpBitsPerSample::k16BitsPerSample: |
| case hci_android::A2dpBitsPerSample::k24BitsPerSample: |
| case hci_android::A2dpBitsPerSample::k32BitsPerSample: |
| break; |
| default: |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| hci_android::A2dpChannelMode const channel_mode = |
| static_cast<hci_android::A2dpChannelMode>(params.channel_mode().Read()); |
| switch (channel_mode) { |
| case hci_android::A2dpChannelMode::kMono: |
| case hci_android::A2dpChannelMode::kStereo: |
| break; |
| default: |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| uint32_t const encoded_audio_bitrate = |
| le32toh(params.encoded_audio_bitrate().Read()); |
| // Bits 0x01000000 to 0xFFFFFFFF are reserved |
| if (encoded_audio_bitrate >= 0x01000000) { |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| OffloadedA2dpChannel state; |
| state.codec_type = codec_type; |
| state.max_latency = le16toh(params.max_latency().Read()); |
| state.scms_t_enable = scms_t_enable; |
| state.sampling_frequency = sampling_frequency; |
| state.bits_per_sample = bits_per_sample; |
| state.channel_mode = channel_mode; |
| state.encoded_audio_bitrate = encoded_audio_bitrate; |
| state.connection_handle = le16toh(params.connection_handle().Read()); |
| state.l2cap_channel_id = le16toh(params.l2cap_channel_id().Read()); |
| state.l2cap_mtu_size = le16toh(params.l2cap_mtu_size().Read()); |
| offloaded_a2dp_channel_state_ = state; |
| |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| } |
| |
| void FakeController::OnAndroidStopA2dpOffload() { |
| hci_android::StartA2dpOffloadCommandReturnParams ret; |
| ret.opcode = hci_android::kStopA2dpOffloadCommandSubopcode; |
| |
| if (!offloaded_a2dp_channel_state_) { |
| ret.status = pw::bluetooth::emboss::StatusCode::REPEATED_ATTEMPTS; |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| offloaded_a2dp_channel_state_ = std::nullopt; |
| |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| RespondWithCommandComplete(hci_android::kA2dpOffloadCommand, |
| BufferView(&ret, sizeof(ret))); |
| } |
| |
| void FakeController::OnAndroidA2dpOffloadCommand( |
| const PacketView<hci_spec::CommandHeader>& command_packet) { |
| const auto& payload = command_packet.payload_data(); |
| |
| uint8_t subopcode = payload.To<uint8_t>(); |
| switch (subopcode) { |
| case hci_android::kStartA2dpOffloadCommandSubopcode: { |
| auto view = |
| pw::bluetooth::vendor::android_hci::MakeStartA2dpOffloadCommandView( |
| command_packet.data().data(), |
| pw::bluetooth::vendor::android_hci::StartA2dpOffloadCommand:: |
| MaxSizeInBytes()); |
| OnAndroidStartA2dpOffload(view); |
| break; |
| } |
| case hci_android::kStopA2dpOffloadCommandSubopcode: |
| OnAndroidStopA2dpOffload(); |
| break; |
| default: |
| bt_log(WARN, |
| "fake-hci", |
| "unhandled android A2DP offload command, subopcode: %#.4x", |
| subopcode); |
| RespondWithCommandComplete( |
| subopcode, pw::bluetooth::emboss::StatusCode::UNKNOWN_COMMAND); |
| break; |
| } |
| } |
| |
| void FakeController::OnAndroidLEMultiAdvtSetAdvtParam( |
| const hci_android::LEMultiAdvtSetAdvtParamCommandParams& params) { |
| hci_spec::AdvertisingHandle handle = params.adv_handle; |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtParamSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| // ensure we can allocate memory for this advertising set if not already |
| // present |
| if (extended_advertising_states_.count(handle) == 0 && |
| extended_advertising_states_.size() >= num_supported_advertising_sets()) { |
| bt_log(INFO, |
| "fake-hci", |
| "no available memory for new advertising set, handle: %d", |
| handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::MEMORY_CAPACITY_EXCEEDED; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtParamSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| // In case there is an error below, we want to reject all parameters instead |
| // of storing a dead state and taking up an advertising handle. Avoid creating |
| // the LEAdvertisingState directly in the map and add it in only once we have |
| // made sure all is good. |
| LEAdvertisingState state; |
| if (extended_advertising_states_.count(handle) != 0) { |
| state = extended_advertising_states_[handle]; |
| } |
| |
| uint16_t interval_min = le16toh(params.adv_interval_min); |
| uint16_t interval_max = le16toh(params.adv_interval_max); |
| |
| if (interval_min >= interval_max) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval min (%d) not strictly less than max (%d)", |
| interval_min, |
| interval_max); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtParamSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| if (interval_min < hci_spec::kLEAdvertisingIntervalMin) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval min (%d) less than spec min (%d)", |
| interval_min, |
| hci_spec::kLEAdvertisingIntervalMin); |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtParamSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| if (interval_max > hci_spec::kLEAdvertisingIntervalMax) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising interval max (%d) greater than spec max (%d)", |
| interval_max, |
| hci_spec::kLEAdvertisingIntervalMax); |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::UNSUPPORTED_FEATURE_OR_PARAMETER; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtParamSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| state.interval_min = interval_min; |
| state.interval_max = interval_max; |
| state.adv_type = params.adv_type; |
| state.own_address_type = params.own_address_type; |
| |
| // write full state back only at the end (we don't have a reference because we |
| // only want to write if there are no errors) |
| extended_advertising_states_[handle] = state; |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtParamSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnAndroidLEMultiAdvtSetAdvtData( |
| const hci_android::LEMultiAdvtSetAdvtDataCommandParams& params) { |
| hci_spec::AdvertisingHandle handle = params.adv_handle; |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtDataSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| if (extended_advertising_states_.count(handle) == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising handle (%d) maps to an unknown advertising set", |
| handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_ADVERTISING_IDENTIFIER; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtDataSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| LEAdvertisingState& state = extended_advertising_states_[handle]; |
| |
| // removing advertising data entirely doesn't require us to check for error |
| // conditions |
| if (params.adv_data_length == 0) { |
| state.data_length = 0; |
| std::memset(state.data, 0, sizeof(state.data)); |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtDataSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| NotifyAdvertisingState(); |
| return; |
| } |
| |
| // directed advertising doesn't support advertising data |
| if (state.IsDirectedAdvertising()) { |
| bt_log(INFO, |
| "fake-hci", |
| "cannot provide advertising data when using directed advertising"); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtDataSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| if (params.adv_data_length > hci_spec::kMaxLEAdvertisingDataLength) { |
| bt_log(INFO, |
| "fake-hci", |
| "data length (%d bytes) larger than legacy PDU size limit", |
| params.adv_data_length); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtDataSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| state.data_length = params.adv_data_length; |
| std::memcpy(state.data, params.adv_data, params.adv_data_length); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| ret.opcode = hci_android::kLEMultiAdvtSetAdvtDataSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnAndroidLEMultiAdvtSetScanResp( |
| const hci_android::LEMultiAdvtSetScanRespCommandParams& params) { |
| hci_spec::AdvertisingHandle handle = params.adv_handle; |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetScanRespSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| if (extended_advertising_states_.count(handle) == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising handle (%d) maps to an unknown advertising set", |
| handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_ADVERTISING_IDENTIFIER; |
| ret.opcode = hci_android::kLEMultiAdvtSetScanRespSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| LEAdvertisingState& state = extended_advertising_states_[handle]; |
| |
| // removing scan response data entirely doesn't require us to check for error |
| // conditions |
| if (params.scan_rsp_data_length == 0) { |
| state.scan_rsp_length = 0; |
| std::memset(state.scan_rsp_data, 0, sizeof(state.scan_rsp_data)); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| ret.opcode = hci_android::kLEMultiAdvtSetScanRespSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| NotifyAdvertisingState(); |
| return; |
| } |
| |
| // adding or changing scan response data, check for error conditions |
| if (!state.IsScannableAdvertising()) { |
| bt_log( |
| INFO, |
| "fake-hci", |
| "cannot provide scan response data for unscannable advertising types"); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetScanRespSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| if (params.scan_rsp_data_length > hci_spec::kMaxLEAdvertisingDataLength) { |
| bt_log(INFO, |
| "fake-hci", |
| "data length (%d bytes) larger than legacy PDU size limit", |
| params.scan_rsp_data_length); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetScanRespSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| state.scan_rsp_length = params.scan_rsp_data_length; |
| std::memcpy( |
| state.scan_rsp_data, params.scan_rsp_data, params.scan_rsp_data_length); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| ret.opcode = hci_android::kLEMultiAdvtSetScanRespSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnAndroidLEMultiAdvtSetRandomAddr( |
| const hci_android::LEMultiAdvtSetRandomAddrCommandParams& params) { |
| hci_spec::AdvertisingHandle handle = params.adv_handle; |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::INVALID_HCI_COMMAND_PARAMETERS; |
| ret.opcode = hci_android::kLEMultiAdvtSetRandomAddrSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| if (extended_advertising_states_.count(handle) == 0) { |
| bt_log(INFO, |
| "fake-hci", |
| "advertising handle (%d) maps to an unknown advertising set", |
| handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_ADVERTISING_IDENTIFIER; |
| ret.opcode = hci_android::kLEMultiAdvtSetRandomAddrSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| LEAdvertisingState& state = extended_advertising_states_[handle]; |
| if (state.IsConnectableAdvertising() && state.enabled) { |
| bt_log( |
| INFO, |
| "fake-hci", |
| "cannot set LE random address while connectable advertising enabled"); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::COMMAND_DISALLOWED; |
| ret.opcode = hci_android::kLEMultiAdvtSetRandomAddrSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| state.random_address = |
| DeviceAddress(DeviceAddress::Type::kLERandom, params.random_address); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| ret.opcode = hci_android::kLEMultiAdvtSetRandomAddrSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| } |
| |
| void FakeController::OnAndroidLEMultiAdvtEnable( |
| const pw::bluetooth::vendor::android_hci::LEMultiAdvtEnableCommandView& |
| params) { |
| hci_spec::AdvertisingHandle handle = params.advertising_handle().Read(); |
| |
| if (!IsValidAdvertisingHandle(handle)) { |
| bt_log(ERROR, "fake-hci", "advertising handle outside range: %d", handle); |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = |
| pw::bluetooth::emboss::StatusCode::UNKNOWN_ADVERTISING_IDENTIFIER; |
| ret.opcode = hci_android::kLEMultiAdvtEnableSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| return; |
| } |
| |
| bool enabled = false; |
| if (params.enable().Read() == |
| pw::bluetooth::emboss::GenericEnableParam::ENABLE) { |
| enabled = true; |
| } |
| |
| extended_advertising_states_[handle].enabled = enabled; |
| |
| hci_android::LEMultiAdvtSetAdvtParamReturnParams ret; |
| ret.status = pw::bluetooth::emboss::StatusCode::SUCCESS; |
| ret.opcode = hci_android::kLEMultiAdvtEnableSubopcode; |
| RespondWithCommandComplete(hci_android::kLEMultiAdvt, |
| BufferView(&ret, sizeof(ret))); |
| NotifyAdvertisingState(); |
| } |
| |
| void FakeController::OnAndroidLEMultiAdvt( |
| const PacketView<hci_spec::CommandHeader>& command_packet) { |
| const auto& payload = command_packet.payload_data(); |
| |
| uint8_t subopcode = payload.To<uint8_t>(); |
| switch (subopcode) { |
| case hci_android::kLEMultiAdvtSetAdvtParamSubopcode: { |
| auto params = |
| payload.To<hci_android::LEMultiAdvtSetAdvtParamCommandParams>(); |
| OnAndroidLEMultiAdvtSetAdvtParam(params); |
| break; |
| } |
| case hci_android::kLEMultiAdvtSetAdvtDataSubopcode: { |
| auto params = |
| payload.To<hci_android::LEMultiAdvtSetAdvtDataCommandParams>(); |
| OnAndroidLEMultiAdvtSetAdvtData(params); |
| break; |
| } |
| case hci_android::kLEMultiAdvtSetScanRespSubopcode: { |
| auto params = |
| payload.To<hci_android::LEMultiAdvtSetScanRespCommandParams>(); |
| OnAndroidLEMultiAdvtSetScanResp(params); |
| break; |
| } |
| case hci_android::kLEMultiAdvtSetRandomAddrSubopcode: { |
| auto params = |
| payload.To<hci_android::LEMultiAdvtSetRandomAddrCommandParams>(); |
| OnAndroidLEMultiAdvtSetRandomAddr(params); |
| break; |
| } |
| case hci_android::kLEMultiAdvtEnableSubopcode: { |
| auto view = |
| pw::bluetooth::vendor::android_hci::MakeLEMultiAdvtEnableCommandView( |
| command_packet.data().data(), |
| pw::bluetooth::vendor::android_hci::LEMultiAdvtEnableCommand:: |
| MaxSizeInBytes()); |
| OnAndroidLEMultiAdvtEnable(view); |
| break; |
| } |
| default: { |
| bt_log(WARN, |
| "fake-hci", |
| "unhandled android multiple advertising command, subopcode: %#.4x", |
| subopcode); |
| RespondWithCommandComplete( |
| subopcode, pw::bluetooth::emboss::StatusCode::UNKNOWN_COMMAND); |
| break; |
| } |
| } |
| } |
| |
| void FakeController::OnVendorCommand( |
| const PacketView<hci_spec::CommandHeader>& command_packet) { |
| auto opcode = le16toh(command_packet.header().opcode); |
| |
| switch (opcode) { |
| case hci_android::kLEGetVendorCapabilities: |
| OnAndroidLEGetVendorCapabilities(); |
| break; |
| case hci_android::kA2dpOffloadCommand: |
| OnAndroidA2dpOffloadCommand(command_packet); |
| break; |
| case hci_android::kLEMultiAdvt: |
| OnAndroidLEMultiAdvt(command_packet); |
| break; |
| default: |
| bt_log(WARN, |
| "fake-hci", |
| "received unhandled vendor command with opcode: %#.4x", |
| opcode); |
| RespondWithCommandComplete( |
| opcode, pw::bluetooth::emboss::StatusCode::UNKNOWN_COMMAND); |
| break; |
| } |
| } |
| |
| void FakeController::OnACLDataPacketReceived( |
| const ByteBuffer& acl_data_packet) { |
| if (acl_data_callback_) { |
| BT_DEBUG_ASSERT(data_dispatcher_); |
| DynamicByteBuffer packet_copy(acl_data_packet); |
| (void)data_dispatcher_->Post( |
| [packet_copy = std::move(packet_copy), cb = acl_data_callback_.share()]( |
| pw::async::Context /*ctx*/, pw::Status status) mutable { |
| if (status.ok()) { |
| cb(packet_copy); |
| } |
| }); |
| } |
| |
| if (acl_data_packet.size() < sizeof(hci_spec::ACLDataHeader)) { |
| bt_log(WARN, "fake-hci", "malformed ACL packet!"); |
| return; |
| } |
| |
| const auto& header = acl_data_packet.To<hci_spec::ACLDataHeader>(); |
| hci_spec::ConnectionHandle handle = |
| le16toh(header.handle_and_flags) & 0x0FFFF; |
| FakePeer* peer = FindByConnHandle(handle); |
| if (!peer) { |
| bt_log(WARN, "fake-hci", "ACL data received for unknown handle!"); |
| return; |
| } |
| |
| if (auto_completed_packets_event_enabled_) { |
| SendNumberOfCompletedPacketsEvent(handle, 1); |
| } |
| peer->OnRxL2CAP(handle, |
| acl_data_packet.view(sizeof(hci_spec::ACLDataHeader))); |
| } |
| |
| void FakeController::OnScoDataPacketReceived( |
| const ByteBuffer& sco_data_packet) { |
| if (sco_data_callback_) { |
| sco_data_callback_(sco_data_packet); |
| } |
| |
| if (sco_data_packet.size() < sizeof(hci_spec::SynchronousDataHeader)) { |
| bt_log(WARN, "fake-hci", "malformed SCO packet!"); |
| return; |
| } |
| |
| const auto& header = sco_data_packet.To<hci_spec::SynchronousDataHeader>(); |
| hci_spec::ConnectionHandle handle = |
| le16toh(header.handle_and_flags) & 0x0FFFF; |
| FakePeer* peer = FindByConnHandle(handle); |
| if (!peer) { |
| bt_log(WARN, "fake-hci", "SCO data received for unknown handle!"); |
| return; |
| } |
| |
| if (auto_completed_packets_event_enabled_) { |
| SendNumberOfCompletedPacketsEvent(handle, 1); |
| } |
| } |
| |
| void FakeController::SetDataCallback(DataCallback callback, |
| pw::async::Dispatcher& pw_dispatcher) { |
| BT_DEBUG_ASSERT(callback); |
| BT_DEBUG_ASSERT(!acl_data_callback_); |
| BT_DEBUG_ASSERT(!data_dispatcher_); |
| |
| acl_data_callback_ = std::move(callback); |
| data_dispatcher_.emplace(pw_dispatcher); |
| } |
| |
| void FakeController::ClearDataCallback() { |
| // Leave dispatcher set (if already set) to preserve its write-once-ness (this |
| // catches bugs with setting multiple data callbacks in class hierarchies). |
| acl_data_callback_ = nullptr; |
| } |
| |
| bool FakeController::LEAdvertisingState::IsDirectedAdvertising() const { |
| return adv_type == pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_HIGH_DUTY_CYCLE_DIRECTED || |
| adv_type == pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_LOW_DUTY_CYCLE_DIRECTED; |
| } |
| |
| bool FakeController::LEAdvertisingState::IsScannableAdvertising() const { |
| return adv_type == pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_AND_SCANNABLE_UNDIRECTED || |
| adv_type == |
| pw::bluetooth::emboss::LEAdvertisingType::SCANNABLE_UNDIRECTED; |
| } |
| |
| bool FakeController::LEAdvertisingState::IsConnectableAdvertising() const { |
| return adv_type == pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_AND_SCANNABLE_UNDIRECTED || |
| adv_type == pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_HIGH_DUTY_CYCLE_DIRECTED || |
| adv_type == pw::bluetooth::emboss::LEAdvertisingType:: |
| CONNECTABLE_LOW_DUTY_CYCLE_DIRECTED; |
| } |
| |
| void FakeController::HandleReceivedCommandPacket( |
| const PacketView<hci_spec::CommandHeader>& command_packet) { |
| hci_spec::OpCode opcode = le16toh(command_packet.header().opcode); |
| |
| if (MaybeRespondWithDefaultCommandStatus(opcode)) { |
| return; |
| } |
| |
| if (MaybeRespondWithDefaultStatus(opcode)) { |
| return; |
| } |
| |
| auto ogf = hci_spec::GetOGF(opcode); |
| if (ogf == hci_spec::kVendorOGF) { |
| OnVendorCommand(command_packet); |
| return; |
| } |
| |
| // TODO(fxbug.dev/42175513): Validate size of payload to be the correct length |
| // below. |
| switch (opcode) { |
| case hci_spec::kReadLocalVersionInfo: { |
| OnReadLocalVersionInfo(); |
| break; |
| } |
| case hci_spec::kReadLocalSupportedCommands: { |
| OnReadLocalSupportedCommands(); |
| break; |
| } |
| case hci_spec::kReadLocalSupportedFeatures: { |
| OnReadLocalSupportedFeatures(); |
| break; |
| } |
| case hci_spec::kLERemoveAdvertisingSet: { |
| const auto& params = |
| command_packet |
| .payload<hci_spec::LERemoveAdvertisingSetCommandParams>(); |
| OnLERemoveAdvertisingSet(params); |
| break; |
| } |
| case hci_spec::kReadBDADDR: { |
| OnReadBRADDR(); |
| break; |
| } |
| case hci_spec::kReadBufferSize: { |
| OnReadBufferSize(); |
| break; |
| } |
| case hci_spec::kCreateConnectionCancel: { |
| OnCreateConnectionCancel(); |
| break; |
| } |
| case hci_spec::kReadLocalName: { |
| OnReadLocalName(); |
| break; |
| } |
| case hci_spec::kReadScanEnable: { |
| OnReadScanEnable(); |
| break; |
| } |
| case hci_spec::kReadPageScanActivity: { |
| OnReadPageScanActivity(); |
| break; |
| } |
| case hci_spec::kReadInquiryMode: { |
| OnReadInquiryMode(); |
| break; |
| } |
| case hci_spec::kReadPageScanType: { |
| OnReadPageScanType(); |
| break; |
| } |
| case hci_spec::kReadSimplePairingMode: { |
| OnReadSimplePairingMode(); |
| break; |
| } |
| case hci_spec::kLECreateConnectionCancel: { |
| OnLECreateConnectionCancel(); |
| break; |
| } |
| case hci_spec::kLEReadLocalSupportedFeatures: { |
| OnLEReadLocalSupportedFeatures(); |
| break; |
| } |
| case hci_spec::kLEReadSupportedStates: { |
| OnLEReadSupportedStates(); |
| break; |
| } |
| case hci_spec::kLEReadBufferSizeV1: { |
| OnLEReadBufferSizeV1(); |
| break; |
| } |
| case hci_spec::kLEReadBufferSizeV2: { |
| OnLEReadBufferSizeV2(); |
| break; |
| } |
| case hci_spec::kReset: { |
| OnReset(); |
| break; |
| } |
| case hci_spec::kLinkKeyRequestReply: { |
| const auto& params = |
| command_packet |
| .payload<pw::bluetooth::emboss::LinkKeyRequestReplyCommandView>(); |
| OnLinkKeyRequestReplyCommandReceived(params); |
| break; |
| } |
| case hci_spec::kLEReadRemoteFeatures: { |
| const auto& params = |
| command_packet.payload<hci_spec::LEReadRemoteFeaturesCommandParams>(); |
| OnLEReadRemoteFeaturesCommand(params); |
| break; |
| } |
| case hci_spec::kLEReadAdvertisingChannelTxPower: { |
| OnLEReadAdvertisingChannelTxPower(); |
| break; |
| } |
| case hci_spec::kAuthenticationRequested: |
| case hci_spec::kCreateConnection: |
| case hci_spec::kDisconnect: |
| case hci_spec::kEnhancedAcceptSynchronousConnectionRequest: |
| case hci_spec::kEnhancedSetupSynchronousConnection: |
| case hci_spec::kIOCapabilityRequestReply: |
| case hci_spec::kInquiry: |
| case hci_spec::kLEClearAdvertisingSets: |
| case hci_spec::kLEConnectionUpdate: |
| case hci_spec::kLECreateConnection: |
| case hci_spec::kLEReadMaxAdvertisingDataLength: |
| case hci_spec::kLEReadNumSupportedAdvertisingSets: |
| case hci_spec::kLESetAdvertisingData: |
| case hci_spec::kLESetAdvertisingEnable: |
| case hci_spec::kLESetAdvertisingParameters: |
| case hci_spec::kLESetAdvertisingSetRandomAddress: |
| case hci_spec::kLESetEventMask: |
| case hci_spec::kLESetExtendedAdvertisingData: |
| case hci_spec::kLESetExtendedAdvertisingEnable: |
| case hci_spec::kLESetExtendedAdvertisingParameters: |
| case hci_spec::kLESetExtendedScanEnable: |
| case hci_spec::kLESetExtendedScanParameters: |
| case hci_spec::kLESetExtendedScanResponseData: |
| case hci_spec::kLESetRandomAddress: |
| case hci_spec::kLESetScanEnable: |
| case hci_spec::kLESetScanParameters: |
| case hci_spec::kLESetScanResponseData: |
| case hci_spec::kLEStartEncryption: |
| case hci_spec::kLinkKeyRequestNegativeReply: |
| case hci_spec::kReadEncryptionKeySize: |
| case hci_spec::kReadLocalExtendedFeatures: |
| case hci_spec::kReadRemoteExtendedFeatures: |
| case hci_spec::kReadRemoteSupportedFeatures: |
| case hci_spec::kReadRemoteVersionInfo: |
| case hci_spec::kRemoteNameRequest: |
| case hci_spec::kSetConnectionEncryption: |
| case hci_spec::kSetEventMask: |
| case hci_spec::kUserConfirmationRequestNegativeReply: |
| case hci_spec::kUserConfirmationRequestReply: |
| case hci_spec::kWriteClassOfDevice: |
| case hci_spec::kWriteExtendedInquiryResponse: |
| case hci_spec::kWriteInquiryMode: |
| case hci_spec::kWriteLEHostSupport: |
| case hci_spec::kWriteLocalName: |
| case hci_spec::kWritePageScanActivity: |
| case hci_spec::kWritePageScanType: |
| case hci_spec::kWriteScanEnable: |
| case hci_spec::kWriteSecureConnectionsHostSupport: |
| case hci_spec::kWriteSimplePairingMode: |
| case hci_spec::kWriteSynchronousFlowControlEnable: { |
| // This case is for packet types that have been migrated to the new Emboss |
| // architecture. Their old version can be still be assembled from the |
| // HciEmulator channel, so here we repackage and forward them as Emboss |
| // packets. |
| auto emboss_packet = bt::hci::EmbossCommandPacket::New< |
| pw::bluetooth::emboss::CommandHeaderView>(opcode, |
| command_packet.size()); |
| bt::MutableBufferView dest = emboss_packet.mutable_data(); |
| command_packet.data().view().Copy(&dest); |
| HandleReceivedCommandPacket(emboss_packet); |
| break; |
| } |
| default: { |
| bt_log(WARN, |
| "fake-hci", |
| "received unhandled command with opcode: %#.4x", |
| opcode); |
| RespondWithCommandComplete( |
| opcode, pw::bluetooth::emboss::StatusCode::UNKNOWN_COMMAND); |
| break; |
| } |
| } |
| } |
| |
| void FakeController::HandleReceivedCommandPacket( |
| const hci::EmbossCommandPacket& command_packet) { |
| hci_spec::OpCode opcode = command_packet.opcode(); |
| |
| if (MaybeRespondWithDefaultCommandStatus(opcode)) { |
| return; |
| } |
| |
| if (MaybeRespondWithDefaultStatus(opcode)) { |
| return; |
| } |
| |
| auto ogf = command_packet.ogf(); |
| if (ogf == hci_spec::kVendorOGF) { |
| bt_log(WARN, |
| "fake-hci", |
| "vendor commands not yet migrated to Emboss; received Emboss vendor " |
| "command with " |
| "opcode: %#.4x", |
| opcode); |
| RespondWithCommandComplete( |
| opcode, pw::bluetooth::emboss::StatusCode::UNKNOWN_COMMAND); |
| return; |
| } |
| |
| switch (opcode) { |
| case hci_spec::kInquiry: { |
| auto params = |
| command_packet.view<pw::bluetooth::emboss::InquiryCommandView>(); |
| OnInquiry(params); |
| break; |
| } |
| case hci_spec::kEnhancedAcceptSynchronousConnectionRequest: { |
| auto params = command_packet.view< |
| pw::bluetooth::emboss:: |
| EnhancedAcceptSynchronousConnectionRequestCommandView>(); |
| OnEnhancedAcceptSynchronousConnectionRequestCommand(params); |
| break; |
| } |
| case hci_spec::kEnhancedSetupSynchronousConnection: { |
| auto params = |
| command_packet |
| .view<pw::bluetooth::emboss:: |
| EnhancedSetupSynchronousConnectionCommandView>(); |
| OnEnhancedSetupSynchronousConnectionCommand(params); |
| break; |
| } |
| case hci_spec::kCreateConnection: { |
| const auto params = |
| command_packet |
| .view<pw::bluetooth::emboss::CreateConnectionCommandView>(); |
| OnCreateConnectionCommandReceived(params); |
| break; |
| } |
| case hci_spec::kDisconnect: { |
| const auto params = |
| command_packet.view<pw::bluetooth::emboss::DisconnectCommandView>(); |
| OnDisconnectCommandReceived(params); |
| break; |
| } |
| case hci_spec::kLESetAdvertisingEnable: { |
| const auto params = |
| command_packet |
| .view<pw::bluetooth::emboss::LESetAdvertisingEnableCommandView>(); |
| OnLESetAdvertisingEnable(params); |
| break; |
| } |
| case hci_spec::kLESetExtendedAdvertisingEnable: { |
| const auto params = command_packet.view< |
| pw::bluetooth::emboss::LESetExtendedAdvertisingEnableCommandView>(); |
| OnLESetExtendedAdvertisingEnable(params); |
| break; |
| } |
| case hci_spec::kLinkKeyRequestNegativeReply: { |
| const auto params = command_packet.view< |
| pw::bluetooth::emboss::LinkKeyRequestNegativeReplyCommandView>(); |
| OnLinkKeyRequestNegativeReplyCommandReceived(params); |
| break; |
| } |
| case hci_spec::kAuthenticationRequested: { |
| const auto params = command_packet.view< |
| pw::bluetooth::emboss::AuthenticationRequestedCommandView>(); |
| OnAuthenticationRequestedCommandReceived(params); |
| break; |
| } |
| case hci_spec::kSetConnectionEncryption: { |
| const auto params = command_packet.view< |
| pw::bluetooth::emboss::SetConnectionEncryptionCommandView>(); |
| OnSetConnectionEncryptionCommand(params); |
| break; |
| } |
| case hci_spec::kRemoteNameRequest: { |
| const auto params = |
| command_packet |
| .view<pw::bluetooth::emboss::RemoteNameRequestCommandView>(); |
| OnReadRemoteNameRequestCommandReceived(params); |
| break; |
| } |
| case hci_spec::kReadRemoteSupportedFeatures: { |
| const auto params = command_packet.view< |
| pw::bluetooth::emboss::ReadRemoteSupportedFeaturesCommandView>(); |
| OnReadRemoteSupportedFeaturesCommandReceived(params); |
| break; |
| } |
| case hci_spec::kReadRemoteExtendedFeatures: { |
| const auto params = command_packet.view< |
| pw::bluetooth::emboss::ReadRemoteExtendedFeaturesCommandView>(); |
| OnReadRemoteExtendedFeaturesCommandReceived(params); |
| break; |
| } |
| case hci_spec::kReadRemoteVersionInfo: { |
| const auto params = |
| command_packet |
| .view<pw::bluetooth::emboss::ReadRemoteVersionInfoCommandView>(); |
| OnReadRemoteVersionInfoCommandReceived(params); |
| break; |
| } |
| case hci_spec::kIOCapabilityRequestReply: { |
| const auto params = command_packet.view< |
| pw::bluetooth::emboss::IoCapabilityRequestReplyCommandView>(); |
| OnIOCapabilityRequestReplyCommand(params); |
| break; |
| } |
| case hci_spec::kSetEventMask: { |
| const auto params = |
| command_packet.view<pw::bluetooth::emboss::SetEventMaskCommandView>(); |
| OnSetEventMask(params); |
| break; |
| } |
| case hci_spec::kWriteLocalName: { |
| const auto params = |
| command_packet |
| .view<pw::bluetooth::emboss::WriteLocalNameCommandView>(); |
| OnWriteLocalName(params); |
| break; |
| } |
| case hci_spec::kWriteScanEnable: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::WriteScanEnableCommandView>(); |
| OnWriteScanEnable(params); |
| break; |
| } |
| case hci_spec::kWritePageScanActivity: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::WritePageScanActivityCommandView>(); |
| OnWritePageScanActivity(params); |
| break; |
| } |
| case hci_spec::kUserConfirmationRequestReply: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::UserConfirmationRequestReplyCommandView>(); |
| OnUserConfirmationRequestReplyCommand(params); |
| break; |
| } |
| case hci_spec::kUserConfirmationRequestNegativeReply: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss:: |
| UserConfirmationRequestNegativeReplyCommandView>(); |
| OnUserConfirmationRequestNegativeReplyCommand(params); |
| break; |
| } |
| case hci_spec::kWriteSynchronousFlowControlEnable: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss:: |
| WriteSynchronousFlowControlEnableCommandView>(); |
| OnWriteSynchronousFlowControlEnableCommand(params); |
| break; |
| } |
| case hci_spec::kWriteExtendedInquiryResponse: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::WriteExtendedInquiryResponseCommandView>(); |
| OnWriteExtendedInquiryResponse(params); |
| break; |
| } |
| case hci_spec::kWriteSimplePairingMode: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::WriteSimplePairingModeCommandView>(); |
| OnWriteSimplePairingMode(params); |
| break; |
| } |
| case hci_spec::kWriteClassOfDevice: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::WriteClassOfDeviceCommandView>(); |
| OnWriteClassOfDevice(params); |
| break; |
| } |
| case hci_spec::kWriteInquiryMode: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::WriteInquiryModeCommandView>(); |
| OnWriteInquiryMode(params); |
| break; |
| }; |
| case hci_spec::kWritePageScanType: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::WritePageScanTypeCommandView>(); |
| OnWritePageScanType(params); |
| break; |
| } |
| case hci_spec::kWriteLEHostSupport: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::WriteLEHostSupportCommandView>(); |
| OnWriteLEHostSupportCommandReceived(params); |
| break; |
| } |
| case hci_spec::kWriteSecureConnectionsHostSupport: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss:: |
| WriteSecureConnectionsHostSupportCommandView>(); |
| OnWriteSecureConnectionsHostSupport(params); |
| break; |
| } |
| case hci_spec::kReadEncryptionKeySize: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::ReadEncryptionKeySizeCommandView>(); |
| OnReadEncryptionKeySizeCommand(params); |
| break; |
| } |
| case hci_spec::kLESetEventMask: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LESetEventMaskCommandView>(); |
| OnLESetEventMask(params); |
| break; |
| } |
| case hci_spec::kLESetRandomAddress: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LESetRandomAddressCommandView>(); |
| OnLESetRandomAddress(params); |
| break; |
| } |
| case hci_spec::kLESetAdvertisingData: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LESetAdvertisingDataCommandView>(); |
| OnLESetAdvertisingData(params); |
| break; |
| } |
| case hci_spec::kLESetScanResponseData: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LESetScanResponseDataCommandView>(); |
| OnLESetScanResponseData(params); |
| break; |
| } |
| case hci_spec::kLESetScanParameters: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LESetScanParametersCommandView>(); |
| OnLESetScanParameters(params); |
| break; |
| } |
| case hci_spec::kLESetExtendedScanParameters: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::LESetExtendedScanParametersCommandView>(); |
| OnLESetExtendedScanParameters(params); |
| break; |
| } |
| case hci_spec::kLESetScanEnable: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LESetScanEnableCommandView>(); |
| OnLESetScanEnable(params); |
| break; |
| } |
| case hci_spec::kLESetExtendedScanEnable: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::LESetExtendedScanEnableCommandView>(); |
| OnLESetExtendedScanEnable(params); |
| break; |
| } |
| case hci_spec::kLECreateConnection: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LECreateConnectionCommandView>(); |
| OnLECreateConnectionCommandReceived(params); |
| break; |
| } |
| case hci_spec::kLEConnectionUpdate: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LEConnectionUpdateCommandView>(); |
| OnLEConnectionUpdateCommandReceived(params); |
| break; |
| } |
| case hci_spec::kLEStartEncryption: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss::LEEnableEncryptionCommandView>(); |
| OnLEStartEncryptionCommand(params); |
| break; |
| } |
| case hci_spec::kReadLocalExtendedFeatures: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::ReadLocalExtendedFeaturesCommandView>(); |
| OnReadLocalExtendedFeatures(params); |
| break; |
| } |
| case hci_spec::kLESetAdvertisingParameters: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::LESetAdvertisingParametersCommandView>(); |
| OnLESetAdvertisingParameters(params); |
| break; |
| } |
| case hci_spec::kLESetExtendedAdvertisingData: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::LESetExtendedAdvertisingDataCommandView>(); |
| OnLESetExtendedAdvertisingData(params); |
| break; |
| } |
| case hci_spec::kLESetExtendedScanResponseData: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::LESetExtendedScanResponseDataCommandView>(); |
| OnLESetExtendedScanResponseData(params); |
| break; |
| } |
| case hci_spec::kLEReadMaxAdvertisingDataLength: { |
| OnLEReadMaximumAdvertisingDataLength(); |
| break; |
| } |
| case hci_spec::kLEReadNumSupportedAdvertisingSets: { |
| OnLEReadNumberOfSupportedAdvertisingSets(); |
| break; |
| } |
| case hci_spec::kLEClearAdvertisingSets: { |
| OnLEClearAdvertisingSets(); |
| break; |
| } |
| case hci_spec::kLESetAdvertisingSetRandomAddress: { |
| const auto& params = command_packet.view< |
| pw::bluetooth::emboss::LESetAdvertisingSetRandomAddressCommandView>(); |
| OnLESetAdvertisingSetRandomAddress(params); |
| break; |
| } |
| case hci_spec::kLESetExtendedAdvertisingParameters: { |
| const auto& params = |
| command_packet |
| .view<pw::bluetooth::emboss:: |
| LESetExtendedAdvertisingParametersV1CommandView>(); |
| OnLESetExtendedAdvertisingParameters(params); |
| break; |
| } |
| default: { |
| bt_log(WARN, "fake-hci", "opcode: %#.4x", opcode); |
| break; |
| } |
| } |
| } |
| } // namespace bt::testing |