diff --git a/connections/implementation/BUILD b/connections/implementation/BUILD index c3f8fa8b..e437a4ff 100644 --- a/connections/implementation/BUILD +++ b/connections/implementation/BUILD @@ -56,6 +56,7 @@ cc_library( "base_endpoint_channel.cc", "base_pcp_handler.cc", "ble_endpoint_channel.cc", + "ble_l2cap_endpoint_channel.cc", "ble_v2_endpoint_channel.cc", "bluetooth_bwu_handler.cc", "bluetooth_device_name.cc", @@ -95,6 +96,7 @@ cc_library( "base_endpoint_channel.h", "base_pcp_handler.h", "ble_endpoint_channel.h", + "ble_l2cap_endpoint_channel.h", "ble_v2_endpoint_channel.h", "bluetooth_bwu_handler.h", "bluetooth_device_name.h", diff --git a/connections/implementation/ble_l2cap_endpoint_channel.cc b/connections/implementation/ble_l2cap_endpoint_channel.cc new file mode 100644 index 00000000..b9e09ca0 --- /dev/null +++ b/connections/implementation/ble_l2cap_endpoint_channel.cc @@ -0,0 +1,78 @@ +// Copyright 2025 Google LLC +// +// 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 "connections/implementation/ble_l2cap_endpoint_channel.h" + +#include +#include + +#include "connections/implementation/base_endpoint_channel.h" +#include "internal/platform/ble_v2.h" +#include "internal/platform/exception.h" +#include "internal/platform/input_stream.h" +#include "internal/platform/logging.h" +#include "internal/platform/output_stream.h" + +namespace nearby { +namespace connections { + +namespace { + +constexpr int kDefaultBleL2capMaxTransmitPacketSize = 1024; // 1024 bytes + +OutputStream* GetOutputStreamOrNull(BleL2capSocket& socket) { + if (socket.GetRemotePeripheral().IsValid()) { + return &socket.GetOutputStream(); + } + LOG(WARNING) << "GetOutputStreamOrNull: socket is not valid"; + return nullptr; +} + +InputStream* GetInputStreamOrNull(BleL2capSocket& socket) { + if (socket.GetRemotePeripheral().IsValid()) { + return &socket.GetInputStream(); + } + return nullptr; +} + +} // namespace + +BleL2capEndpointChannel::BleL2capEndpointChannel( + const std::string& service_id, const std::string& channel_name, + BleL2capSocket socket) + : BaseEndpointChannel(service_id, channel_name, + GetInputStreamOrNull(socket), + GetOutputStreamOrNull(socket)), + ble_l2cap_socket_(std::move(socket)) {} + +location::nearby::proto::connections::Medium +BleL2capEndpointChannel::GetMedium() const { + return location::nearby::proto::connections::Medium::BLE_L2CAP; +} + +int BleL2capEndpointChannel::GetMaxTransmitPacketSize() const { + return kDefaultBleL2capMaxTransmitPacketSize; +} + +void BleL2capEndpointChannel::CloseImpl() { + Exception status = ble_l2cap_socket_.Close(); + if (!status.Ok()) { + LOG(WARNING) + << "Failed to close underlying socket for BleL2capEndpointChannel " + << GetName() << ": exception=" << status.value; + } +} + +} // namespace connections +} // namespace nearby diff --git a/connections/implementation/ble_l2cap_endpoint_channel.h b/connections/implementation/ble_l2cap_endpoint_channel.h new file mode 100644 index 00000000..1211578c --- /dev/null +++ b/connections/implementation/ble_l2cap_endpoint_channel.h @@ -0,0 +1,48 @@ +// Copyright 2025 Google LLC +// +// 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. + +#ifndef CORE_INTERNAL_BLE_L2CAP_ENDPOINT_CHANNEL_H_ +#define CORE_INTERNAL_BLE_L2CAP_ENDPOINT_CHANNEL_H_ + +#include + +#include "connections/implementation/base_endpoint_channel.h" +#include "internal/platform/ble_v2.h" + +namespace nearby { +namespace connections { + +class BleL2capEndpointChannel final : public BaseEndpointChannel { + public: + // Creates both outgoing and incoming Ble channels. + BleL2capEndpointChannel(const std::string& service_id, + const std::string& channel_name, + BleL2capSocket socket); + + // Returns the medium of this endpoint channel. + location::nearby::proto::connections::Medium GetMedium() const override; + + // Returns the maximum transmit packet size of this endpoint channel. + int GetMaxTransmitPacketSize() const override; + + private: + void CloseImpl() override; + + BleL2capSocket ble_l2cap_socket_; +}; + +} // namespace connections +} // namespace nearby + +#endif // CORE_INTERNAL_BLE_L2CAP_ENDPOINT_CHANNEL_H_ diff --git a/connections/implementation/flags/nearby_connections_feature_flags.h b/connections/implementation/flags/nearby_connections_feature_flags.h index 8d47327f..773500db 100644 --- a/connections/implementation/flags/nearby_connections_feature_flags.h +++ b/connections/implementation/flags/nearby_connections_feature_flags.h @@ -40,6 +40,9 @@ constexpr auto kEnableAutoReconnect = // Disable/Enable BLE v2 in Nearby Connections SDK. constexpr auto kEnableBleV2 = flags::Flag(kConfigPackage, "45401515", false); +// Disable/Enable BLE L2CAP in Nearby Connections SDK. +constexpr auto kEnableBleL2cap = + flags::Flag(kConfigPackage, "45685706", false); // Disable/Enable GATT query in thread in BLE V2. // Manual edit: setting this to false for ChromeOS rollout as well. constexpr auto kEnableGattQueryInThread = diff --git a/connections/implementation/mediums/ble_v2.cc b/connections/implementation/mediums/ble_v2.cc index 877b6143..ba683703 100644 --- a/connections/implementation/mediums/ble_v2.cc +++ b/connections/implementation/mediums/ble_v2.cc @@ -175,9 +175,7 @@ ErrorOr BleV2::StartAdvertising(const std::string& service_id, // Wrap the connections advertisement to the medium advertisement. ByteArray service_id_hash = mediums::bleutils::GenerateHash( service_id, mediums::BleAdvertisement::kServiceIdHashLength); - // Get psm value from L2CAP server if L2CAP is supported. Now just use the - // default value. - int psm = mediums::BleAdvertisementHeader::kDefaultPsmValue; + int psm = medium_.GetPSM(); mediums::BleAdvertisement medium_advertisement = { mediums::BleAdvertisement::Version::kV2, mediums::BleAdvertisement::SocketVersion::kV2, @@ -570,13 +568,13 @@ ErrorOr BleV2::StartAcceptingConnections( MutexLock lock(&mutex_); if (service_id.empty()) { - LOG(INFO) + LOG(WARNING) << "Refusing to start accepting BLE connections with empty service id."; return {Error(OperationResultCode::CLIENT_BLE_DUPLICATE_DISCOVERING)}; } if (IsAcceptingConnectionsLocked(service_id)) { - LOG(INFO) + LOG(WARNING) << "Refusing to start accepting BLE connections for " << service_id << " because another BLE peripheral socket is already in-progress."; return {Error(OperationResultCode:: @@ -596,47 +594,128 @@ ErrorOr BleV2::StartAcceptingConnections( } BleV2ServerSocket server_socket = medium_.OpenServerSocket(service_id); - if (!server_socket.IsValid()) { - LOG(INFO) << "Failed to start accepting Ble connections for service_id=" - << service_id; - return {Error( - OperationResultCode::CONNECTIVITY_BLE_SERVER_SOCKET_CREATION_FAILURE)}; + if (server_socket.IsValid()) { + // Mark the fact that there's an in-progress Ble server accepting + // connections. + auto owned_server_socket = + server_sockets_.insert({service_id, std::move(server_socket)}) + .first->second; + // Start the accept loop on a dedicated thread - this stays alive and + // listening for new incoming connections until + // StopAcceptingL2capConnections() is invoked. + accept_loops_runner_.Execute( + "ble-accept", + [this, service_id, callback = std::move(callback), + server_socket = std::move(owned_server_socket)]() mutable { + while (true) { + BleV2Socket client_socket = server_socket.Accept(); + if (!client_socket.IsValid()) { + LOG(WARNING) << "The client socket to accept is invalid."; + server_socket.Close(); + break; + } else { + LOG(INFO) << "The client Ble GATT socket has been accepted."; + } + { + MutexLock lock(&mutex_); + client_socket.SetCloseNotifier([this, service_id]() { + MutexLock lock(&mutex_); + incoming_sockets_.erase(service_id); + }); + incoming_sockets_.insert({service_id, client_socket}); + } + if (callback) { + callback(std::move(client_socket), service_id); + } + } + }); + } else { + LOG(INFO) + << "Failed to start accepting Ble GATT connections for service_id=" + << service_id; + } + LOG(INFO) << "Start accepting Ble GATT connections for service_id=" + << service_id; + return {true}; +} + +// TODO(mingshiouwu): Add unit test for ble_l2cap flow +ErrorOr BleV2::StartAcceptingL2capConnections( + const std::string& service_id, AcceptedL2capConnectionCallback callback) { + MutexLock lock(&mutex_); + if (service_id.empty()) { + LOG(WARNING) + << "Refusing to start accepting Ble L2CAP connections with empty " + "service id."; + return {Error(OperationResultCode::CLIENT_BLE_DUPLICATE_DISCOVERING)}; } - // Mark the fact that there's an in-progress Ble server accepting - // connections. - auto owned_server_socket = - server_sockets_.insert({service_id, std::move(server_socket)}) - .first->second; + if (IsAcceptingL2capConnectionsLocked(service_id)) { + LOG(WARNING) + << "Refusing to start accepting Ble L2CAP connections for " + << service_id + << " because another Ble peripheral socket is already in-progress."; + return {Error(OperationResultCode:: + CLIENT_DUPLICATE_ACCEPTING_BLE_CONNECTION_REQUEST)}; + } - // Start the accept loop on a dedicated thread - this stays alive and - // listening for new incoming connections until StopAcceptingConnections() is - // invoked. - accept_loops_runner_.Execute( - "ble-accept", - [this, service_id = service_id, callback = std::move(callback), - server_socket = std::move(owned_server_socket)]() mutable { - while (true) { - BleV2Socket client_socket = server_socket.Accept(); - if (!client_socket.IsValid()) { - LOG(WARNING) << "The client socket to accept is invalid."; - server_socket.Close(); - break; - } - { - MutexLock lock(&mutex_); - client_socket.SetCloseNotifier([this, service_id]() { + if (!radio_.IsEnabled()) { + LOG(INFO) << "Can't start accepting Ble L2CAP connections for " + << service_id << " because Bluetooth isn't enabled."; + return {Error(OperationResultCode::MISCELLEANEOUS_BT_SYSTEM_SERVICE_NULL)}; + } + + if (!IsAvailableLocked()) { + LOG(INFO) << "Can't start accepting Ble L2CAP connections for " + << service_id << " because Ble isn't available."; + return {Error(OperationResultCode::MEDIUM_UNAVAILABLE_BLE_NOT_AVAILABLE)}; + } + + BleL2capServerSocket server_socket = + medium_.OpenL2capServerSocket(service_id); + if (server_socket.IsValid()) { + // Mark the fact that there's an in-progress Ble server accepting + // connections. + auto owned_server_socket = + l2cap_server_socket_map_.insert({service_id, std::move(server_socket)}) + .first->second; + // Start the accept loop on a dedicated thread - this stays alive and + // listening for new incoming connections until StopAcceptingConnections() + // is invoked. + accept_loops_runner_.Execute( + "ble-l2cap-accept", + [this, service_id, callback = std::move(callback), + server_socket = std::move(owned_server_socket)]() mutable { + while (true) { + BleL2capSocket client_socket = server_socket.Accept(); + if (!client_socket.IsValid()) { + LOG(WARNING) << "The client L2CAP socket to accept is invalid."; + server_socket.Close(); + break; + } else { + LOG(INFO) << "The client L2CAP socket has been accepted."; + } + { MutexLock lock(&mutex_); - incoming_sockets_.erase(service_id); - }); - incoming_sockets_.insert({service_id, client_socket}); + client_socket.SetCloseNotifier([this, service_id]() { + MutexLock lock(&mutex_); + incoming_sockets_.erase(service_id); + }); + l2cap_incoming_service_id_to_socket_map_.insert( + {service_id, client_socket}); + } + if (callback) { + callback(std::move(client_socket), service_id); + } } - if (callback) { - callback(std::move(client_socket), service_id); - } - } - }); - + }); + } else { + LOG(INFO) + << "Failed to start accepting Ble L2CAP connections for service_id=" + << service_id; + } + LOG(INFO) << "Start accepting Ble L2CAP connections for service_id=" + << service_id; return {true}; } @@ -675,11 +754,51 @@ bool BleV2::StopAcceptingConnections(const std::string& service_id) { return true; } +bool BleV2::StopAcceptingL2capConnections(const std::string& service_id) { + MutexLock lock(&mutex_); + + const auto it = l2cap_server_socket_map_.find(service_id); + if (it == l2cap_server_socket_map_.end()) { + LOG(INFO) << "Can't stop accepting Ble L2CAP connections because it was " + "never started."; + return false; + } + + // Closing the BleL2capServerSocket will kick off the suicide of the thread + // in accept_loops_thread_pool_ that blocks on BleL2capServerSocket.accept(). + // That may take some time to complete, but there's no particular reason to + // wait around for it. + auto item = l2cap_server_socket_map_.extract(it); + + // Store a handle to the BleL2capServerSocket, so we can use it after + // removing the entry from l2cap_server_socket_map_; making it scoped + // is a bonus that takes care of deallocation before we leave this method. + BleL2capServerSocket& listening_socket = item.mapped(); + + // Regardless of whether or not we fail to close the existing + // BleL2capServerSocket, remove it from l2cap_server_socket_map_ so that it + // frees up this service for another round. + + // Finally, close the BleL2capServerSocket. + if (!listening_socket.Close().Ok()) { + LOG(INFO) << "Failed to close Ble L2CAP server socket for service_id=" + << service_id; + return false; + } + + return true; +} + bool BleV2::IsAcceptingConnections(const std::string& service_id) { MutexLock lock(&mutex_); return IsAcceptingConnectionsLocked(service_id); } +bool BleV2::IsAcceptingL2capConnections(const std::string& service_id) { + MutexLock lock(&mutex_); + return IsAcceptingL2capConnectionsLocked(service_id); +} + ErrorOr BleV2::Connect(const std::string& service_id, const BleV2Peripheral& peripheral, CancellationFlag* cancellation_flag) { @@ -741,6 +860,10 @@ bool BleV2::IsAcceptingConnectionsLocked(const std::string& service_id) { return server_sockets_.contains(service_id); } +bool BleV2::IsAcceptingL2capConnectionsLocked(const std::string& service_id) { + return l2cap_server_socket_map_.contains(service_id); +} + bool BleV2::IsAdvertisementGattServerRunningLocked() { return gatt_server_ && gatt_server_->IsValid(); } diff --git a/connections/implementation/mediums/ble_v2.h b/connections/implementation/mediums/ble_v2.h index cc585b24..29120c94 100644 --- a/connections/implementation/mediums/ble_v2.h +++ b/connections/implementation/mediums/ble_v2.h @@ -62,6 +62,10 @@ class BleV2 final { using AcceptedConnectionCallback = absl::AnyInvocable; + // Callback that is invoked when a new l2cap connection is accepted. + using AcceptedL2capConnectionCallback = absl::AnyInvocable; + // The type of the BLE advertising. In current implementation, we don't // support multiple advertising types on a Medium instance. enum class AdvertisingType : int { @@ -108,7 +112,7 @@ class BleV2 final { ABSL_LOCKS_EXCLUDED(mutex_); // (TODO:hais) update this after ble_v2 async api refactor. - // Stop Ble advertising with dummy bytes for legagy device. + // Stop Ble advertising with dummy bytes for legacy device. bool StopLegacyAdvertising(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); @@ -151,13 +155,26 @@ class BleV2 final { AcceptedConnectionCallback callback) ABSL_LOCKS_EXCLUDED(mutex_); + // Starts a worker thread, creates a Ble L2CAP socket, associates it with a + // service id. + ErrorOr StartAcceptingL2capConnections( + const std::string& service_id, + AcceptedL2capConnectionCallback l2cap_callback) + ABSL_LOCKS_EXCLUDED(mutex_); + // Closes socket corresponding to a service id. bool StopAcceptingConnections(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + bool StopAcceptingL2capConnections(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + bool IsAcceptingConnections(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_); + bool IsAcceptingL2capConnections(const std::string& service_id) + ABSL_LOCKS_EXCLUDED(mutex_); + // Establishes connection to Ble peripheral. // Returns socket instance. On success, BleSocket.IsValid() return true. ErrorOr Connect(const std::string& service_id, @@ -205,6 +222,10 @@ class BleV2 final { // `mutex_` held. bool IsAcceptingConnectionsLocked(const std::string& service_id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + // Same as IsListeningForIncomingConnections(), but must be called with + // `mutex_` held. + bool IsAcceptingL2capConnectionsLocked(const std::string& service_id) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); bool IsAdvertisementGattServerRunningLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); @@ -222,14 +243,11 @@ class BleV2 final { ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); bool StopAdvertisementGattServerLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - ByteArray CreateAdvertisementHeader(int psm, bool extended_advertisement_advertised) ABSL_SHARED_LOCKS_REQUIRED(mutex_); - // For devices that don't have extended nor gatt adverting. api::ble_v2::BleAdvertisementData CreateAdvertisingDataForLegacyDevice(); - bool StartAdvertisingLocked(const std::string& service_id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); bool StartFastAdvertisingLocked( @@ -256,9 +274,7 @@ class BleV2 final { // Called by StartScanning when using the async methods. bool StopAsyncScanningLocked(absl::string_view service_id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - api::ble_v2::TxPowerLevel PowerLevelToTxPowerLevel(PowerLevel power_level); - void RunOnBleThread(Runnable runnable); static constexpr int kMaxConcurrentAcceptLoops = 5; @@ -307,6 +323,17 @@ class BleV2 final { ABSL_GUARDED_BY(mutex_); mediums::InstantOnLostManager instant_on_lost_manager_; + + // A map of service_id -> L2capServerSocket. If map is non-empty, we + // are currently listening for incoming connections. + absl::flat_hash_map + l2cap_server_socket_map_ ABSL_GUARDED_BY(mutex_); + + // A map of service_id -> BleL2capSocket. + // Tracks currently connected incoming sockets. This lets the device know when + // it's okay to restart L2CAP server related operations. + absl::flat_hash_map + l2cap_incoming_service_id_to_socket_map_ ABSL_GUARDED_BY(mutex_); }; } // namespace connections diff --git a/connections/implementation/p2p_cluster_pcp_handler.cc b/connections/implementation/p2p_cluster_pcp_handler.cc index d5a3f8b9..6ec6a6d0 100644 --- a/connections/implementation/p2p_cluster_pcp_handler.cc +++ b/connections/implementation/p2p_cluster_pcp_handler.cc @@ -32,6 +32,7 @@ #include "connections/implementation/base_pcp_handler.h" #include "connections/implementation/ble_advertisement.h" #include "connections/implementation/ble_endpoint_channel.h" +#include "connections/implementation/ble_l2cap_endpoint_channel.h" #include "connections/implementation/ble_v2_endpoint_channel.h" #include "connections/implementation/bluetooth_device_name.h" #include "connections/implementation/bluetooth_endpoint_channel.h" @@ -49,6 +50,7 @@ #include "connections/implementation/mediums/utils.h" #include "connections/implementation/pcp.h" #include "connections/implementation/pcp_handler.h" +#include "connections/implementation/webrtc_state.h" #include "connections/implementation/wifi_lan_endpoint_channel.h" #include "connections/implementation/wifi_lan_service_info.h" #include "connections/medium_selector.h" @@ -1386,6 +1388,26 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( if (NearbyFlags::GetInstance().GetBoolFlag( config_package_nearby::nearby_connections_feature::kEnableBleV2)) { // ble_v2 + // TODO(mingshiouwu): Add unit test for ble_l2cap flow + bool accepting_ble_connections_success = false; + if (options.enable_ble_listening && + NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableBleL2cap) && + !ble_v2_medium_.IsAcceptingL2capConnections(std::string(service_id))) { + if (!ble_v2_medium_.StartAcceptingL2capConnections( + std::string(service_id), + absl::bind_front( + &P2pClusterPcpHandler::BleL2capConnectionAcceptedHandler, + this, client_proxy, local_endpoint_id, + options.listening_endpoint_type))) { + LOG(WARNING) << "Failed to start listening for incoming L2CAP " + "connections on ble_v2"; + } else { + accepting_ble_connections_success = true; + } + } + if (options.enable_ble_listening && !ble_v2_medium_.IsAcceptingConnections(std::string(service_id))) { if (!ble_v2_medium_.StartAcceptingConnections( @@ -1397,9 +1419,12 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( LOG(WARNING) << "Failed to start listening for incoming connections on ble_v2"; } else { - started_mediums.push_back(BLE); + accepting_ble_connections_success = true; } } + if (accepting_ble_connections_success) { + started_mediums.push_back(BLE); + } } else { // ble v1 if (options.enable_ble_listening && @@ -2427,6 +2452,29 @@ void P2pClusterPcpHandler::BleV2ConnectionAcceptedHandler( }); } +void P2pClusterPcpHandler::BleL2capConnectionAcceptedHandler( + ClientProxy* client, absl::string_view local_endpoint_info, + NearbyDevice::Type device_type, BleL2capSocket socket, + const std::string& service_id) { + if (!socket.IsValid()) { + LOG(WARNING) << "Invalid socket in accept L2CAP callback(" + << absl::BytesToHexString(local_endpoint_info) + << "), client=" << client->GetClientId(); + return; + } + RunOnPcpHandlerThread( + "p2p-ble-l2cap-on-incoming-connection", + [this, client, service_id, device_type, + socket = std::move(socket)]() RUN_ON_PCP_HANDLER_THREAD() mutable { + ByteArray remote_peripheral_info = socket.GetRemotePeripheral().GetId(); + auto channel = std::make_unique( + service_id, std::string(remote_peripheral_info), socket); + + OnIncomingConnection(client, remote_peripheral_info, std::move(channel), + BLE, device_type); + }); +} + ErrorOr P2pClusterPcpHandler::StartBleV2Advertising( ClientProxy* client, const std::string& service_id, const std::string& local_endpoint_id, const ByteArray& local_endpoint_info, @@ -2450,12 +2498,24 @@ ErrorOr P2pClusterPcpHandler::StartBleV2Advertising( << service_id; return {Error(OperationResultCode::DEVICE_STATE_RADIO_ENABLING_FAILURE)}; } + ErrorOr ble_l2cap_result = true; + if (NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableBleL2cap)) { + ble_l2cap_result = ble_v2_medium_.StartAcceptingL2capConnections( + service_id, + absl::bind_front( + &P2pClusterPcpHandler::BleL2capConnectionAcceptedHandler, this, + client, local_endpoint_info.AsStringView(), + NearbyDevice::Type::kConnectionsDevice)); + } + ErrorOr ble_v2_result = ble_v2_medium_.StartAcceptingConnections( service_id, absl::bind_front(&P2pClusterPcpHandler::BleV2ConnectionAcceptedHandler, this, client, local_endpoint_info.AsStringView(), NearbyDevice::Type::kConnectionsDevice)); - if (ble_v2_result.has_error()) { + if (ble_v2_result.has_error() && ble_l2cap_result.has_error()) { LOG(WARNING) << "In StartBleV2Advertising(" << absl::BytesToHexString(local_endpoint_info.data()) diff --git a/connections/implementation/p2p_cluster_pcp_handler.h b/connections/implementation/p2p_cluster_pcp_handler.h index bcdd661e..3b63ad73 100644 --- a/connections/implementation/p2p_cluster_pcp_handler.h +++ b/connections/implementation/p2p_cluster_pcp_handler.h @@ -259,12 +259,16 @@ class P2pClusterPcpHandler : public BasePcpHandler { const ByteArray& advertisement_bytes, bool fast_advertisement); void BleV2LegacyDeviceDiscoveredHandler(); - void BleV2ConnectionAcceptedHandler(ClientProxy* client, absl::string_view local_endpoint_info, NearbyDevice::Type device_type, BleV2Socket socket, const std::string& service_id); + void BleL2capConnectionAcceptedHandler(ClientProxy* client, + absl::string_view local_endpoint_info, + NearbyDevice::Type device_type, + BleL2capSocket socket, + const std::string& service_id); ErrorOr StartBleV2Advertising( ClientProxy* client, const std::string& service_id, const std::string& local_endpoint_id, diff --git a/internal/platform/ble_v2.cc b/internal/platform/ble_v2.cc index 666a9d67..fed6f162 100644 --- a/internal/platform/ble_v2.cc +++ b/internal/platform/ble_v2.cc @@ -19,7 +19,10 @@ #include #include +#include "absl/functional/any_invocable.h" #include "absl/status/status.h" +#include "absl/types/optional.h" +#include "internal/platform/cancellation_flag.h" #include "internal/platform/implementation/ble_v2.h" #include "internal/platform/logging.h" #include "internal/platform/mutex_lock.h" @@ -247,6 +250,12 @@ BleV2ServerSocket BleV2Medium::OpenServerSocket(const std::string& service_id) { return BleV2ServerSocket(*this, impl_->OpenServerSocket(service_id)); } +BleL2capServerSocket BleV2Medium::OpenL2capServerSocket( + const std::string& service_id) { + // TODO(mingshiouwu): Replace with a real implementation listening flow. + return BleL2capServerSocket(*this, nullptr); +} + BleV2Socket BleV2Medium::Connect(const std::string& service_id, TxPowerLevel tx_power_level, const BleV2Peripheral& peripheral, diff --git a/internal/platform/ble_v2.h b/internal/platform/ble_v2.h index bd169377..82069d30 100644 --- a/internal/platform/ble_v2.h +++ b/internal/platform/ble_v2.h @@ -335,6 +335,78 @@ class GattClient final { std::unique_ptr impl_; }; +class BleL2capSocket final { + public: + BleL2capSocket(BleV2Peripheral peripheral, + std::unique_ptr socket) + : peripheral_(peripheral) {}; + BleL2capSocket(const BleL2capSocket&) = default; + BleL2capSocket& operator=(const BleL2capSocket&) = default; + ~BleL2capSocket() = default; + explicit BleL2capSocket(std::unique_ptr socket) + : impl_(std::move(socket)) {} + + // Returns InputStream of the BleL2capSocket. + InputStream& GetInputStream() { return impl_->GetInputStream(); } + // Returns OutputStream of the BleL2capSocket. + OutputStream& GetOutputStream() { return impl_->GetOutputStream(); } + // Sets the close notifier by client side. + void SetCloseNotifier(absl::AnyInvocable notifier) { + impl_->SetCloseNotifier(std::move(notifier)); + } + // Closes the BleL2capSocket. + Exception Close() { return impl_->Close(); } + // Returns BlePeripheral object which wraps a valid BlePeripheral pointer. + BleV2Peripheral& GetRemotePeripheral() { return peripheral_; } + // Returns true if a BleL2capSocket is usable. If this method returns false, + // it is not safe to call any other method. + bool IsValid() const { return impl_ != nullptr; } + api::ble_v2::BleL2capSocket& GetImpl() { return *impl_; } + + private: + std::shared_ptr impl_; + BleV2Peripheral peripheral_; +}; + +class BleL2capServerSocket final { + public: + BleL2capServerSocket( + BleV2Medium& medium, + std::unique_ptr socket) + : medium_(&medium), impl_(std::move(socket)) {} + BleL2capServerSocket(const BleL2capServerSocket&) = default; + BleL2capServerSocket& operator=(const BleL2capServerSocket&) = default; + ~BleL2capServerSocket() = default; + explicit BleL2capServerSocket( + std::unique_ptr socket) + : impl_(std::move(socket)) {} + + // Accepts an incoming connection. + BleL2capSocket Accept() { + std::unique_ptr socket = impl_->Accept(); + if (!socket) { + LOG(INFO) << "BleL2capServerSocket Accept() failed on server socket: " + << this; + } + return BleL2capSocket(std::move(socket)); + } + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() { + LOG(INFO) << "BleL2capServerSocket Closing:: " << this; + return impl_->Close(); + } + + // Returns true if a BleL2capServerSocket is usable. If this method returns + // false, it is not safe to call any other method. + bool IsValid() const { return impl_ != nullptr; } + api::ble_v2::BleL2capServerSocket& GetImpl() { return *impl_; } + + private: + BleV2Medium* medium_ = nullptr; + std::shared_ptr impl_; +}; + // Container of operations that can be performed over the BLE medium. class BleV2Medium final { public: @@ -440,6 +512,10 @@ class BleV2Medium final { // On Success, BleServerSocket::IsValid() returns true. BleV2ServerSocket OpenServerSocket(const std::string& service_id); + // Returns a new BleL2capServerSocket. + // On Success, BleL2capServerSocket::IsValid() returns true. + BleL2capServerSocket OpenL2capServerSocket(const std::string& service_id); + // Returns a new BleLanSocket. // On Success, BleLanSocket::IsValid() returns true. BleV2Socket Connect(const std::string& service_id, @@ -462,6 +538,12 @@ class BleV2Medium final { impl_->AddAlternateUuidForService(uuid, service_id); } + // Returns PSM value. + int GetPSM() { + // TODO(mingshiouwu): Replace with real implementation. + return 0; + } + private: Mutex mutex_; std::unique_ptr impl_; diff --git a/internal/platform/implementation/ble_v2.h b/internal/platform/implementation/ble_v2.h index a7397b29..0e50973a 100644 --- a/internal/platform/implementation/ble_v2.h +++ b/internal/platform/implementation/ble_v2.h @@ -376,6 +376,54 @@ class BleServerSocket { virtual Exception Close() = 0; }; +// A BLE L2CAP client socket for requesting L2CAP socket. +class BleL2capSocket { + public: + virtual ~BleL2capSocket() = default; + + // Returns the InputStream of the BleL2capSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the BleL2capSocket object is destroyed. + virtual InputStream& GetInputStream() = 0; + + // Returns the OutputStream of the BleL2capSocket. + // On error, returned stream will report Exception::kIo on any operation. + // + // The returned object is not owned by the caller, and can be invalidated once + // the BleL2capSocket object is destroyed. + virtual OutputStream& GetOutputStream() = 0; + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + virtual Exception Close() = 0; + + // Sets the close notifier by client side. + virtual void SetCloseNotifier(absl::AnyInvocable notifier) {}; + + // Returns valid BlePeripheral pointer if there is a connection, and + // nullptr otherwise. + virtual BlePeripheral* GetRemotePeripheral() = 0; +}; + +// A BLE L2CAP server socket for listening incoming L2CAP socket. +class BleL2capServerSocket { + public: + virtual ~BleL2capServerSocket() = default; + + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and L2CAP ServerSocket has to be + // closed. + virtual std::unique_ptr Accept() = 0; + + // Closes the L2CAP server socket. + virtual Exception Close() = 0; +}; + // The main BLE medium used inside of Nearby. This serves as the entry point // for all BLE and GATT related operations. class BleMedium { @@ -515,6 +563,15 @@ class BleMedium { virtual std::unique_ptr OpenServerSocket( const std::string& service_id) = 0; + // Opens a BLE l2cap server socket based on service ID. + // + // On success, returns a new BleL2capServerSocket. + // On error, returns nullptr. + virtual std::unique_ptr OpenL2capServerSocket( + const std::string& service_id) { + return nullptr; + } + // Connects to a BLE peripheral. // // On success, returns a new BleSocket. diff --git a/internal/platform/implementation/g3/ble_v2.cc b/internal/platform/implementation/g3/ble_v2.cc index 4b5196ad..300f6d4b 100644 --- a/internal/platform/implementation/g3/ble_v2.cc +++ b/internal/platform/implementation/g3/ble_v2.cc @@ -779,6 +779,12 @@ std::unique_ptr BleV2Medium::OpenServerSocket( return server_socket; } +std::unique_ptr +BleV2Medium::OpenL2capServerSocket(const std::string& service_id) { + // TODO(mingshiouwu): add more codes for g3 testing. + return nullptr; +} + std::unique_ptr BleV2Medium::Connect( const std::string& service_id, TxPowerLevel tx_power_level, api::ble_v2::BlePeripheral& remote_peripheral, diff --git a/internal/platform/implementation/g3/ble_v2.h b/internal/platform/implementation/g3/ble_v2.h index bffe812b..74d6d178 100644 --- a/internal/platform/implementation/g3/ble_v2.h +++ b/internal/platform/implementation/g3/ble_v2.h @@ -188,6 +188,9 @@ class BleV2Medium : public api::ble_v2::BleMedium { std::unique_ptr OpenServerSocket( const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); + std::unique_ptr OpenL2capServerSocket( + const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_); + // Connects to existing remote Ble peripheral. // // On success, returns a new BleSocket.