| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fake_signaling_server.h" |
| |
| #include <endian.h> |
| |
| #include "fake_l2cap.h" |
| #include "src/connectivity/bluetooth/core/bt-host/common/log.h" |
| |
| namespace bt::testing { |
| |
| void FakeSignalingServer::RegisterWithL2cap(FakeL2cap* l2cap_) { |
| auto cb = [&](auto conn, auto& sdu) { return HandleSdu(conn, sdu); }; |
| l2cap_->RegisterHandler(l2cap::kSignalingChannelId, cb); |
| fake_l2cap_ = l2cap_; |
| return; |
| } |
| |
| void FakeSignalingServer::HandleSdu(hci::ConnectionHandle conn, const ByteBuffer& sdu) { |
| ZX_ASSERT_MSG(sdu.size() >= sizeof(l2cap::CommandHeader), "SDU has only %zu bytes", sdu.size()); |
| ZX_ASSERT(sdu.As<l2cap::CommandHeader>().length == (sdu.size() - sizeof(l2cap::CommandHeader))); |
| // Extract CommandCode and strip signaling packet header from sdu. |
| l2cap::CommandHeader packet_header = sdu.As<l2cap::CommandHeader>(); |
| l2cap::CommandCode packet_code = packet_header.code; |
| l2cap::CommandId packet_id = packet_header.id; |
| auto payload = sdu.view(sizeof(l2cap::CommandHeader)); |
| switch (packet_code) { |
| case l2cap::kInformationRequest: |
| ProcessInformationRequest(conn, packet_id, payload); |
| break; |
| case l2cap::kConnectionRequest: |
| ProcessConnectionRequest(conn, packet_id, payload); |
| break; |
| case l2cap::kConfigurationRequest: |
| ProcessConfigurationRequest(conn, packet_id, payload); |
| break; |
| case l2cap::kConfigurationResponse: |
| ProcessConfigurationResponse(conn, packet_id, payload); |
| break; |
| case l2cap::kDisconnectionRequest: |
| ProcessDisconnectionRequest(conn, packet_id, payload); |
| break; |
| default: |
| bt_log(ERROR, "hci-emulator", "Does not support request code: %#.4hhx", packet_code); |
| break; |
| } |
| } |
| |
| void FakeSignalingServer::ProcessInformationRequest(hci::ConnectionHandle conn, l2cap::CommandId id, |
| const ByteBuffer& info_req) { |
| auto info_type = info_req.As<l2cap::InformationType>(); |
| if (info_type == l2cap::InformationType::kExtendedFeaturesSupported) { |
| SendInformationResponseExtendedFeatures(conn, id); |
| } else if (info_type == l2cap::InformationType::kFixedChannelsSupported) { |
| SendInformationResponseFixedChannels(conn, id); |
| } else { |
| SendCommandReject(conn, id, l2cap::RejectReason::kNotUnderstood); |
| } |
| } |
| |
| void FakeSignalingServer::ProcessConnectionRequest(hci::ConnectionHandle conn, l2cap::CommandId id, |
| const ByteBuffer& connection_req) { |
| const auto& conn_req = connection_req.As<l2cap::ConnectionRequestPayload>(); |
| const l2cap::PSM psm = letoh16(conn_req.psm); |
| const l2cap::ChannelId remote_cid = letoh16(conn_req.src_cid); |
| |
| // Validate the remote channel ID prior to assigning it a local ID. |
| if (remote_cid == l2cap::kInvalidChannelId) { |
| bt_log(ERROR, "hci-emulator", |
| "Invalid source CID; rejecting connection for PSM %#.4x from channel %#.4x", psm, |
| remote_cid); |
| SendConnectionResponse(conn, id, l2cap::kInvalidChannelId, remote_cid, |
| l2cap::ConnectionResult::kInvalidSourceCID, |
| l2cap::ConnectionStatus::kNoInfoAvailable); |
| return; |
| } |
| if (fake_l2cap_->FindDynamicChannelByRemoteId(conn, remote_cid)) { |
| bt_log(ERROR, "l2cap-bredr", |
| "Remote CID already in use; rejecting connection for PSM %#.4x from channel %#.4x", psm, |
| remote_cid); |
| SendConnectionResponse(conn, id, l2cap::kInvalidChannelId, remote_cid, |
| l2cap::ConnectionResult::kSourceCIDAlreadyAllocated, |
| l2cap::ConnectionStatus::kNoInfoAvailable); |
| return; |
| } |
| if (fake_l2cap_->ServiceRegisteredForPsm(psm) == false) { |
| bt_log(ERROR, "l2cap-bredr", "No service registered for PSM %#.4x", psm); |
| SendConnectionResponse(conn, id, l2cap::kInvalidChannelId, remote_cid, |
| l2cap::ConnectionResult::kPSMNotSupported, |
| l2cap::ConnectionStatus::kNoInfoAvailable); |
| return; |
| } |
| |
| // Assign the new channel a local channel ID. |
| l2cap::ChannelId local_cid = fake_l2cap_->FindAvailableDynamicChannelId(conn); |
| if (local_cid == l2cap::kInvalidChannelId) { |
| bt_log(DEBUG, "l2cap-bredr", |
| "Out of IDs; rejecting connection for PSM %#.4x from channel %#.4x", psm, remote_cid); |
| SendConnectionResponse(conn, id, local_cid, remote_cid, l2cap::ConnectionResult::kNoResources, |
| l2cap::ConnectionStatus::kNoInfoAvailable); |
| return; |
| } |
| |
| // Send a ConnectionResponse and ConfigurationRequest, and then register the channel. |
| SendConnectionResponse(conn, id, local_cid, remote_cid, l2cap::ConnectionResult::kSuccess, |
| l2cap::ConnectionStatus::kNoInfoAvailable); |
| SendConfigurationRequest(conn, id, remote_cid); |
| fake_l2cap_->RegisterDynamicChannel(conn, psm, local_cid, remote_cid); |
| } |
| |
| void FakeSignalingServer::ProcessConfigurationRequest(hci::ConnectionHandle conn, |
| l2cap::CommandId id, |
| const ByteBuffer& configuration_req) { |
| // Ignore the data/flags associated with the request for the purpose of the emulator |
| const auto& conf_req = configuration_req.As<l2cap::ConfigurationRequestPayload>(); |
| const l2cap::ChannelId local_cid = letoh16(conf_req.dst_cid); |
| auto channel = fake_l2cap_->FindDynamicChannelByLocalId(conn, local_cid); |
| if (channel) { |
| channel->set_configuration_request_received(); |
| SendConfigurationResponse(conn, id, local_cid, l2cap::ConfigurationResult::kSuccess); |
| if (channel->configuration_response_received() && channel->configuration_request_received()) { |
| channel->set_opened(); |
| fake_l2cap_->RegisterDynamicChannelWithPsm(conn, local_cid); |
| } |
| } else { |
| bt_log(ERROR, "fake-hci", "No local channel at channel ID %#.4x", local_cid); |
| SendConfigurationResponse(conn, id, local_cid, l2cap::ConfigurationResult::kRejected); |
| } |
| return; |
| } |
| |
| void FakeSignalingServer::ProcessConfigurationResponse(hci::ConnectionHandle conn, |
| l2cap::CommandId id, |
| const ByteBuffer& configuration_res) { |
| const auto& conf_res = configuration_res.As<l2cap::ConfigurationResponsePayload>(); |
| const l2cap::ChannelId local_cid = letoh16(conf_res.src_cid); |
| const l2cap::ConfigurationResult result = conf_res.result; |
| if (result != l2cap::ConfigurationResult::kSuccess) { |
| bt_log(ERROR, "fake-hci", "Failed to create local channel at channel ID %#.4x", local_cid); |
| } |
| auto channel = fake_l2cap_->FindDynamicChannelByLocalId(conn, local_cid); |
| if (channel) { |
| channel->set_configuration_response_received(); |
| if (channel->configuration_response_received() && channel->configuration_request_received()) { |
| channel->set_opened(); |
| fake_l2cap_->RegisterDynamicChannelWithPsm(conn, local_cid); |
| } |
| } |
| } |
| |
| void FakeSignalingServer::ProcessDisconnectionRequest(hci::ConnectionHandle conn, |
| l2cap::CommandId id, |
| const ByteBuffer& disconnection_req) { |
| const auto& disconn_req = disconnection_req.As<l2cap::DisconnectionRequestPayload>(); |
| const l2cap::ChannelId local_cid = letoh16(disconn_req.dst_cid); |
| const l2cap::ChannelId remote_cid = letoh16(disconn_req.src_cid); |
| auto channel = fake_l2cap_->FindDynamicChannelByLocalId(conn, local_cid); |
| if (channel) { |
| fake_l2cap_->DeleteDynamicChannelByLocalId(conn, local_cid); |
| return SendDisconnectionResponse(conn, id, local_cid, remote_cid); |
| } else { |
| bt_log(ERROR, "fake-hci", |
| "Received disconnection request for non-existent local channel at %#.4x", local_cid); |
| } |
| } |
| |
| void FakeSignalingServer::SendCFrame(hci::ConnectionHandle conn, l2cap::CommandCode code, |
| l2cap::CommandId id, DynamicByteBuffer& payload_buffer) { |
| size_t kResponseSize = sizeof(l2cap::CommandHeader) + payload_buffer.size(); |
| DynamicByteBuffer response_buffer(kResponseSize); |
| MutablePacketView<l2cap::CommandHeader> command_packet(&response_buffer, payload_buffer.size()); |
| command_packet.mutable_header()->code = code; |
| command_packet.mutable_header()->id = id; |
| command_packet.mutable_header()->length = payload_buffer.size(); |
| command_packet.mutable_payload_data().Write(payload_buffer); |
| auto& callback = fake_l2cap_->send_frame_callback(); |
| return callback(conn, l2cap::kSignalingChannelId, response_buffer); |
| } |
| |
| void FakeSignalingServer::SendCommandReject(hci::ConnectionHandle conn, l2cap::CommandId id, |
| l2cap::RejectReason reason) { |
| DynamicByteBuffer payload_buffer(sizeof(l2cap::CommandRejectPayload)); |
| MutablePacketView<l2cap::CommandRejectPayload> payload_view(&payload_buffer); |
| payload_view.mutable_header()->reason = |
| static_cast<uint16_t>(l2cap::RejectReason::kNotUnderstood); |
| SendCFrame(conn, l2cap::kCommandRejectCode, id, payload_buffer); |
| } |
| |
| void FakeSignalingServer::SendInformationResponseExtendedFeatures(hci::ConnectionHandle conn, |
| l2cap::CommandId id) { |
| l2cap::ExtendedFeatures extended_features = |
| l2cap::kExtendedFeaturesBitFixedChannels | l2cap::kExtendedFeaturesBitEnhancedRetransmission; |
| constexpr size_t kPayloadSize = |
| sizeof(l2cap::InformationResponsePayload) + sizeof(l2cap::ExtendedFeatures); |
| DynamicByteBuffer payload_buffer(kPayloadSize); |
| MutablePacketView<l2cap::InformationResponsePayload> payload_view( |
| &payload_buffer, sizeof(l2cap::ExtendedFeatures)); |
| payload_view.mutable_header()->type = static_cast<l2cap::InformationType>( |
| htole16(l2cap::InformationType::kExtendedFeaturesSupported)); |
| payload_view.mutable_header()->result = |
| static_cast<l2cap::InformationResult>(htole16(l2cap::InformationResult::kSuccess)); |
| payload_view.mutable_payload_data().WriteObj(htole32(extended_features)); |
| SendCFrame(conn, l2cap::kInformationResponse, id, payload_buffer); |
| } |
| |
| void FakeSignalingServer::SendInformationResponseFixedChannels(hci::ConnectionHandle conn, |
| l2cap::CommandId id) { |
| l2cap::FixedChannelsSupported fixed_channels = l2cap::kFixedChannelsSupportedBitSignaling; |
| constexpr size_t kPayloadSize = |
| sizeof(l2cap::InformationResponsePayload) + sizeof(l2cap::FixedChannelsSupported); |
| DynamicByteBuffer payload_buffer(kPayloadSize); |
| MutablePacketView<l2cap::InformationResponsePayload> payload_view( |
| &payload_buffer, sizeof(l2cap::FixedChannelsSupported)); |
| payload_view.mutable_header()->type = |
| static_cast<l2cap::InformationType>(htole16(l2cap::InformationType::kFixedChannelsSupported)); |
| payload_view.mutable_header()->result = |
| static_cast<l2cap::InformationResult>(htole16(l2cap::InformationResult::kSuccess)); |
| payload_view.mutable_payload_data().WriteObj(htole64(fixed_channels)); |
| SendCFrame(conn, l2cap::kInformationResponse, id, payload_buffer); |
| } |
| |
| void FakeSignalingServer::SendConnectionResponse(hci::ConnectionHandle conn, l2cap::CommandId id, |
| l2cap::ChannelId local_cid, |
| l2cap::ChannelId remote_cid, |
| l2cap::ConnectionResult result, |
| l2cap::ConnectionStatus status) { |
| DynamicByteBuffer payload_buffer(sizeof(l2cap::ConnectionResponsePayload)); |
| MutablePacketView<l2cap::ConnectionResponsePayload> payload_view(&payload_buffer); |
| // Destination cid is the endpoint on the device sending the response packet. |
| payload_view.mutable_header()->dst_cid = local_cid; |
| // Source cid is the endpoint on the device receiving the response packet. |
| payload_view.mutable_header()->src_cid = remote_cid; |
| payload_view.mutable_header()->result = result; |
| payload_view.mutable_header()->status = status; |
| SendCFrame(conn, l2cap::kConnectionResponse, id, payload_buffer); |
| } |
| |
| void FakeSignalingServer::SendConfigurationRequest(hci::ConnectionHandle conn, l2cap::CommandId id, |
| l2cap::ChannelId remote_cid) { |
| DynamicByteBuffer payload_buffer(sizeof(l2cap::ConfigurationRequestPayload)); |
| MutablePacketView<l2cap::ConfigurationRequestPayload> payload_view(&payload_buffer); |
| // Config request dest_cid contains channel endpoint on the device receiving the request. |
| payload_view.mutable_header()->dst_cid = remote_cid; |
| // No continuation flag or additional data associated with this request. |
| payload_view.mutable_header()->flags = 0x0000; |
| SendCFrame(conn, l2cap::kConfigurationRequest, id, payload_buffer); |
| } |
| |
| void FakeSignalingServer::SendConfigurationResponse(hci::ConnectionHandle conn, l2cap::CommandId id, |
| l2cap::ChannelId local_cid, |
| l2cap::ConfigurationResult result) { |
| DynamicByteBuffer payload_buffer(sizeof(l2cap::ConfigurationResponsePayload)); |
| MutablePacketView<l2cap::ConfigurationResponsePayload> payload_view(&payload_buffer); |
| // Config response src_cid contains the channel endpoint on the device sending the request. |
| payload_view.mutable_header()->src_cid = local_cid; |
| payload_view.mutable_header()->result = result; |
| // No continuation flag or additional data associated with this request. |
| payload_view.mutable_header()->flags = 0x0000; |
| SendCFrame(conn, l2cap::kConfigurationResponse, id, payload_buffer); |
| } |
| |
| void FakeSignalingServer::SendDisconnectionResponse(hci::ConnectionHandle conn, l2cap::CommandId id, |
| l2cap::ChannelId local_cid, |
| l2cap::ChannelId remote_cid) { |
| DynamicByteBuffer payload_buffer(sizeof(l2cap::DisconnectionResponsePayload)); |
| MutablePacketView<l2cap::DisconnectionResponsePayload> payload_view(&payload_buffer); |
| // Endpoint on the device receiving the response. |
| payload_view.mutable_header()->src_cid = remote_cid; |
| // Endpoint on device sending the response. |
| payload_view.mutable_header()->dst_cid = local_cid; |
| SendCFrame(conn, l2cap::kDisconnectionResponse, id, payload_buffer); |
| } |
| |
| } // namespace bt::testing |