From b10957d7129f1e302e2b1efe86a0a018f6495f14 Mon Sep 17 00:00:00 2001 From: Edwin Wu Date: Fri, 11 Apr 2025 00:00:17 -0700 Subject: [PATCH] Add OpenL2capServerSocket and skeleton BleL2capServerSocket/BleL2capSocket PiperOrigin-RevId: 746334743 --- connections/implementation/mediums/ble_v2.cc | 25 ++-- connections/implementation/mediums/ble_v2.h | 6 +- .../implementation/p2p_cluster_pcp_handler.cc | 7 + internal/platform/ble_v2.cc | 3 +- internal/platform/ble_v2.h | 14 +- internal/platform/implementation/apple/BUILD | 4 + .../apple/Mediums/BLEv2/GNCBLEL2CAPServer.h | 1 + .../apple/Mediums/BLEv2/GNCBLEL2CAPServer.m | 9 +- .../apple/Mediums/BLEv2/GNCBLEL2CAPStream.h | 5 +- .../apple/Mediums/BLEv2/GNCBLEL2CAPStream.m | 16 ++- .../apple/ble_l2cap_server_socket.h | 63 +++++++++ .../apple/ble_l2cap_server_socket.mm | 46 +++++++ .../implementation/apple/ble_l2cap_socket.h | 122 ++++++++++++++++++ .../implementation/apple/ble_l2cap_socket.mm | 78 +++++++++++ .../implementation/apple/ble_medium.h | 6 + .../implementation/apple/ble_medium.mm | 25 ++++ internal/platform/implementation/ble_v2.h | 7 +- 17 files changed, 401 insertions(+), 36 deletions(-) create mode 100644 internal/platform/implementation/apple/ble_l2cap_server_socket.h create mode 100644 internal/platform/implementation/apple/ble_l2cap_server_socket.mm create mode 100644 internal/platform/implementation/apple/ble_l2cap_socket.h create mode 100644 internal/platform/implementation/apple/ble_l2cap_socket.mm diff --git a/connections/implementation/mediums/ble_v2.cc b/connections/implementation/mediums/ble_v2.cc index 5d750e22..29074c1b 100644 --- a/connections/implementation/mediums/ble_v2.cc +++ b/connections/implementation/mediums/ble_v2.cc @@ -97,6 +97,9 @@ BleV2::~BleV2() { while (!server_sockets_.empty()) { StopAcceptingConnections(server_sockets_.begin()->first); } + while (!l2cap_server_sockets_.empty()) { + StopAcceptingL2capConnections(l2cap_server_sockets_.begin()->first); + } serial_executor_.Shutdown(); alarm_executor_.Shutdown(); @@ -175,7 +178,11 @@ 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); - int psm = medium_.GetPSM(); + int psm = mediums::BleAdvertisementHeader::kDefaultPsmValue; + const auto it = l2cap_server_sockets_.find(service_id); + if (it != l2cap_server_sockets_.end()) { + psm = it->second.GetPSM(); + } mediums::BleAdvertisement medium_advertisement = { mediums::BleAdvertisement::Version::kV2, mediums::BleAdvertisement::SocketVersion::kV2, @@ -677,7 +684,7 @@ ErrorOr BleV2::StartAcceptingL2capConnections( // 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)}) + l2cap_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 StopAcceptingConnections() @@ -701,7 +708,7 @@ ErrorOr BleV2::StartAcceptingL2capConnections( MutexLock lock(&mutex_); incoming_sockets_.erase(service_id); }); - l2cap_incoming_service_id_to_socket_map_.insert( + l2cap_incoming_service_id_to_sockets_.insert( {service_id, client_socket}); } if (callback) { @@ -757,8 +764,8 @@ bool BleV2::StopAcceptingConnections(const std::string& service_id) { 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()) { + const auto it = l2cap_server_sockets_.find(service_id); + if (it == l2cap_server_sockets_.end()) { LOG(INFO) << "Can't stop accepting Ble L2CAP connections because it was " "never started."; return false; @@ -768,15 +775,15 @@ bool BleV2::StopAcceptingL2capConnections(const std::string& service_id) { // 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); + auto item = l2cap_server_sockets_.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 + // removing the entry from l2cap_server_sockets_; 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 + // BleL2capServerSocket, remove it from l2cap_server_sockets_ so that it // frees up this service for another round. // Finally, close the BleL2capServerSocket. @@ -898,7 +905,7 @@ bool BleV2::IsAcceptingConnectionsLocked(const std::string& service_id) { } bool BleV2::IsAcceptingL2capConnectionsLocked(const std::string& service_id) { - return l2cap_server_socket_map_.contains(service_id); + return l2cap_server_sockets_.contains(service_id); } bool BleV2::IsAdvertisementGattServerRunningLocked() { diff --git a/connections/implementation/mediums/ble_v2.h b/connections/implementation/mediums/ble_v2.h index 959634fd..2585f343 100644 --- a/connections/implementation/mediums/ble_v2.h +++ b/connections/implementation/mediums/ble_v2.h @@ -333,14 +333,14 @@ class BleV2 final { // 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_); + absl::flat_hash_map l2cap_server_sockets_ + 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_); + l2cap_incoming_service_id_to_sockets_ 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 91f142a5..a58732f2 100644 --- a/connections/implementation/p2p_cluster_pcp_handler.cc +++ b/connections/implementation/p2p_cluster_pcp_handler.cc @@ -369,6 +369,12 @@ Status P2pClusterPcpHandler::StopAdvertisingImpl(ClientProxy* client) { config_package_nearby::nearby_connections_feature::kEnableBleV2)) { ble_v2_medium_.StopAdvertising(client->GetAdvertisingServiceId()); ble_v2_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); + if (NearbyFlags::GetInstance().GetBoolFlag( + config_package_nearby::nearby_connections_feature:: + kEnableBleL2cap)) { + ble_v2_medium_.StopAcceptingL2capConnections( + client->GetAdvertisingServiceId()); + } } else { ble_medium_.StopAdvertising(client->GetAdvertisingServiceId()); ble_medium_.StopAcceptingConnections(client->GetAdvertisingServiceId()); @@ -1606,6 +1612,7 @@ P2pClusterPcpHandler::StartListeningForIncomingConnectionsImpl( } } } + // wifi lan if (options.enable_wlan_listening && !wifi_lan_medium_.IsAcceptingConnections(std::string(service_id))) { ErrorOr wifi_lan_result = wifi_lan_medium_.StartAcceptingConnections( diff --git a/internal/platform/ble_v2.cc b/internal/platform/ble_v2.cc index 684bf8ce..856e09f8 100644 --- a/internal/platform/ble_v2.cc +++ b/internal/platform/ble_v2.cc @@ -252,8 +252,7 @@ BleV2ServerSocket BleV2Medium::OpenServerSocket(const std::string& service_id) { BleL2capServerSocket BleV2Medium::OpenL2capServerSocket( const std::string& service_id) { - // TODO(mingshiouwu): Replace with a real implementation listening flow. - return BleL2capServerSocket(*this, nullptr); + return BleL2capServerSocket(*this, impl_->OpenL2capServerSocket(service_id)); } BleV2Socket BleV2Medium::Connect(const std::string& service_id, diff --git a/internal/platform/ble_v2.h b/internal/platform/ble_v2.h index 26d7bf12..0fead541 100644 --- a/internal/platform/ble_v2.h +++ b/internal/platform/ble_v2.h @@ -191,8 +191,7 @@ class BleV2ServerSocket final { std::unique_ptr socket = impl_->Accept(); BleV2Peripheral peripheral; if (!socket) { - NEARBY_LOGS(INFO) << "BleServerSocket Accept() failed on server socket: " - << this; + LOG(INFO) << "BleServerSocket Accept() failed on server socket: " << this; } else { auto* platform_peripheral = socket->GetRemotePeripheral(); if (platform_peripheral != nullptr) { @@ -204,7 +203,7 @@ class BleV2ServerSocket final { // Returns Exception::kIo on error, Exception::kSuccess otherwise. Exception Close() { - NEARBY_LOGS(INFO) << "BleServerSocket Closing:: " << this; + LOG(INFO) << "BleServerSocket Closing:: " << this; return impl_->Close(); } @@ -381,6 +380,9 @@ class BleL2capServerSocket final { std::unique_ptr socket) : impl_(std::move(socket)) {} + // Gets PSM value has been published by the server. + int GetPSM() { return impl_->GetPSM(); } + // Accepts an incoming connection. BleL2capSocket Accept() { std::unique_ptr socket = impl_->Accept(); @@ -545,12 +547,6 @@ 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/apple/BUILD b/internal/platform/implementation/apple/BUILD index fc912471..2dde2362 100644 --- a/internal/platform/implementation/apple/BUILD +++ b/internal/platform/implementation/apple/BUILD @@ -87,6 +87,10 @@ objc_library( "ble_gatt_client.mm", "ble_gatt_server.h", "ble_gatt_server.mm", + "ble_l2cap_server_socket.h", + "ble_l2cap_server_socket.mm", + "ble_l2cap_socket.h", + "ble_l2cap_socket.mm", "ble_medium.h", "ble_medium.mm", "ble_peripheral.h", diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h index e1fcc6b1..36d37167 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h @@ -15,6 +15,7 @@ #import #import +@class GNCBLEL2CAPStream; @protocol GNCPeripheralManager; NS_ASSUME_NONNULL_BEGIN diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.m b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.m index 4eee6e56..666e94a3 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.m +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.m @@ -18,6 +18,7 @@ #import #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEError.h" +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h" #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheralManager.h" #import "GoogleToolboxForMac/GTMLogger.h" @@ -36,7 +37,7 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP GNCStartListeningL2CAPChannelCompletionHandler _startListeningL2CAPChannelcompletionHandler; CBL2CAPChannel *_l2CAPChannel; - + GNCBLEL2CAPStream *_l2CAPStream; /// Whether start call has been performed when the peripheral was off. BOOL _alreadyStartedWhenPeripheralPoweredOff; } @@ -141,6 +142,8 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP if (error) { GTMLoggerError(@"[NEARBY] Failed to unpublish L2CAP channel: %@", error); } + [_l2CAPStream tearDown]; + _l2CAPStream = nil; _PSM = 0; } @@ -155,7 +158,7 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP } // Cleanup older references. - if (_l2CAPChannel) { + if (_l2CAPStream || _l2CAPChannel) { // The device may establish a new L2CAP socket connection while the old socket is still // connected if sysproxy stopped for a reason other than Bluetooth disconnection. Closing the // channel here ensures the server and the client state is reset between the two @@ -202,6 +205,8 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP } - (void)closeL2CAPChannel { + [_l2CAPStream tearDown]; + _l2CAPStream = nil; _l2CAPChannel = nil; } diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h index f3683f82..e4bc35cd 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h @@ -16,8 +16,6 @@ NS_ASSUME_NONNULL_BEGIN -@class GNCBLEL2CAPStream; - /// Block invoked when the stream is closed. typedef void (^GNCBLEL2CAPStreamClosedBlock)(void); @@ -52,6 +50,9 @@ typedef void (^GNCBLEL2CAPControllerReceivedDataBlock)(NSData *data); /// stream is torn down. - (void)sendData:(NSData *)data completionBlock:(void (^)(BOOL))completionBlock; +/// Closes the stream. +- (void)close; + /// Tears down the stream. - (void)tearDown; diff --git a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.m b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.m index d1b40256..8eecabd8 100644 --- a/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.m +++ b/internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.m @@ -39,12 +39,12 @@ @interface GNCBLEL2CAPStream () -/// Input stream from the watch. Operations to this stream are synchronized by |_streamQueue| +/// Input stream from the device. Operations to this stream are synchronized by |_streamQueue| /// dispatch queue. @property(nonatomic, nullable) NSInputStream *inputStream; -/// Output stream to the watch. Operations to this stream are synchronized by synchronized access on -/// |_writeBufferArray|. +/// Output stream to the device. Operations to this stream are synchronized by synchronized access +/// on |_writeBufferArray|. @property(nonatomic, nullable) NSOutputStream *outputStream; @end @@ -56,7 +56,7 @@ /// Serial queue used when invoking |_receivedDataBlock|. dispatch_queue_t _receivedDataQueue; - /// Queue used exclusively from events on |toWatchStream| and |fromWatchStream|. + /// Queue used exclusively from events on |inputStream| and |outputStream|. dispatch_queue_t _streamQueue; /// Pending data to be written to the remote device, synchronized access on itself. @@ -99,6 +99,10 @@ return self; } +- (void)close { + _closedBlock(); +} + - (void)tearDown { dispatch_async(_streamQueue, ^{ GTMLoggerDebug(@"[NEARBY] Closing inputStream %@ by tearDown", self.inputStream); @@ -300,7 +304,7 @@ } } -/// Receives data from watch and invokes |_receivedDataBlock|. +/// Receives data from device and invokes |_receivedDataBlock|. - (void)receiveStreamData { dispatch_assert_queue_debug(_streamQueue); @@ -312,7 +316,7 @@ [data appendBytes:readBuffer length:(NSUInteger)bytesRead]; if (_verboseLoggingEnabled) { - GTMLoggerDebug(@"[NEARBY] Stream data from watch of length %@", @(data.length)); + GTMLoggerDebug(@"[NEARBY] Stream data from device of length %@", @(data.length)); } dispatch_async(_receivedDataQueue, ^{ diff --git a/internal/platform/implementation/apple/ble_l2cap_server_socket.h b/internal/platform/implementation/apple/ble_l2cap_server_socket.h new file mode 100644 index 00000000..8d965d91 --- /dev/null +++ b/internal/platform/implementation/apple/ble_l2cap_server_socket.h @@ -0,0 +1,63 @@ +// 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. + +// Note: File language is detected using heuristics. Many Objective-C++ headers +// are incorrectly classified as C++ resulting in invalid linter errors. The use +// of "NSArray" and other Foundation classes like "NSData", "NSDictionary" and +// "NSUUID" are highly weighted for Objective-C and Objective-C++ scores. Oddly, +// "#import " does not contribute any points. This +// comment alone should be enough to trick the IDE in to believing this is +// actually some sort of Objective-C file. See: +// cs/google3/devtools/search/lang/recognize_language_classifiers_data + +#include + +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h" +#import "internal/platform/implementation/apple/ble_l2cap_socket.h" + +namespace nearby { +namespace apple { + +// A BLE L2CAP server socket for listening incoming L2CAP socket. +class BleL2capServerSocket : public api::ble_v2::BleL2capServerSocket { + public: + // Creates a BLE L2CAP server socket. + // + // @param l2cap_server The L2CAP server to use. + explicit BleL2capServerSocket(GNCBLEL2CAPServer* l2cap_server); + ~BleL2capServerSocket() override = default; + + // Gets PSM value has been published by the server. + int GetPSM() const override; + + // 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. + std::unique_ptr Accept() override; + + // Closes the L2CAP server socket. + Exception Close() override; + + private: + // The L2CAP server to use for listening incoming L2CAP socket and publishing + // PSM value. + GNCBLEL2CAPServer* l2cap_server_; +}; + +} // namespace apple +} // namespace nearby \ No newline at end of file diff --git a/internal/platform/implementation/apple/ble_l2cap_server_socket.mm b/internal/platform/implementation/apple/ble_l2cap_server_socket.mm new file mode 100644 index 00000000..145890da --- /dev/null +++ b/internal/platform/implementation/apple/ble_l2cap_server_socket.mm @@ -0,0 +1,46 @@ +// 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. + +#import "internal/platform/implementation/apple/ble_l2cap_server_socket.h" + +#import + +#include +#include + +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h" +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h" +#import "internal/platform/implementation/apple/ble_l2cap_socket.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +namespace nearby { +namespace apple { + +BleL2capServerSocket::BleL2capServerSocket(GNCBLEL2CAPServer* l2cap_server) + : l2cap_server_(l2cap_server) {} + +int BleL2capServerSocket::GetPSM() const { return [l2cap_server_ PSM]; } + +std::unique_ptr BleL2capServerSocket::Accept() { + // TODO: edwinwu - Implement to wrap up l2cap channel with |GNCBLEL2CAPStream|. + return nullptr; +} + +Exception BleL2capServerSocket::Close() { + [l2cap_server_ close]; + return {Exception::kSuccess}; +} + +} // namespace apple +} // namespace nearby \ No newline at end of file diff --git a/internal/platform/implementation/apple/ble_l2cap_socket.h b/internal/platform/implementation/apple/ble_l2cap_socket.h new file mode 100644 index 00000000..b520e3e4 --- /dev/null +++ b/internal/platform/implementation/apple/ble_l2cap_socket.h @@ -0,0 +1,122 @@ +// 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 + +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h" +#import "internal/platform/implementation/apple/ble_peripheral.h" +#include "internal/platform/implementation/ble_v2.h" + +namespace nearby { +namespace apple { + +/** A readable stream of bytes. */ +class BleL2capInputStream : public InputStream { + public: + // Creates a BleL2capInputStream. + // + // @param stream The underlying stream to use for reading and writing. + explicit BleL2capInputStream(GNCBLEL2CAPStream* stream); + ~BleL2capInputStream() override = default; + + // Reads at most `size` bytes from the input stream. + // + // Returns an empty byte array on end of file, or Exception::kIo on error. + ExceptionOr Read(std::int64_t size) override; + + // Closes the stream preventing further reads. + // + // Returns Exception::kIo on error, otherwise Exception::kSuccess. + Exception Close() override; + + private: + GNCBLEL2CAPStream* stream_; +}; + +/** A writable stream of bytes. */ +class BleL2capOutputStream : public OutputStream { + public: + // Creates a BleL2capOutputStream. + // + // @param stream The underlying stream to use for reading and writing. + explicit BleL2capOutputStream(GNCBLEL2CAPStream* stream); + ~BleL2capOutputStream() override = default; + + // Write the provided bytes to the output stream. + // + // Returns Exception::kIo on error, otherwise Exception::kSuccess. + Exception Write(const ByteArray& data) override; + + // no-op + // + // Always returns Exception::kSuccess. + Exception Flush() override; + + // Closes the stream preventing further writes. + // + // Returns Exception::kIo on error, otherwise Exception::kSuccess. + Exception Close() override; + + private: + GNCBLEL2CAPStream* stream_; +}; + +/** + * Concrete BleL2capSocket implementation. + */ +class BleL2capSocket : public api::ble_v2::BleL2capSocket { + public: + explicit BleL2capSocket(GNCBLEL2CAPStream* stream); + + // The peripheral used to create the socket must outlive the socket or + // undefined behavior will occur. + BleL2capSocket(GNCBLEL2CAPStream* stream, + api::ble_v2::BlePeripheral* peripheral); + ~BleL2capSocket() override = 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. + InputStream& GetInputStream() override; + + // 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. + OutputStream& GetOutputStream() override; + + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() override; + + // Sets the close notifier by client side. + void SetCloseNotifier(absl::AnyInvocable notifier) override; + + // Returns valid BlePeripheral pointer if there is a connection, and + // nullptr otherwise. + api::ble_v2::BlePeripheral* GetRemotePeripheral() override { + return peripheral_; + } + + private: + GNCBLEL2CAPStream* stream_; + std::unique_ptr input_stream_; + std::unique_ptr output_stream_; + api::ble_v2::BlePeripheral* peripheral_; +}; + +} // namespace apple +} // namespace nearby \ No newline at end of file diff --git a/internal/platform/implementation/apple/ble_l2cap_socket.mm b/internal/platform/implementation/apple/ble_l2cap_socket.mm new file mode 100644 index 00000000..e09e5947 --- /dev/null +++ b/internal/platform/implementation/apple/ble_l2cap_socket.mm @@ -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. + +#import "internal/platform/implementation/apple/ble_l2cap_socket.h" + +#import + +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPStream.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +namespace nearby { +namespace apple { + +#pragma mark - BleL2capInputStream + +BleL2capInputStream::BleL2capInputStream(GNCBLEL2CAPStream* stream) : stream_(stream) { + GTMLoggerInfo(@"BleL2capInputStream::BleL2capInputStream"); +} + +ExceptionOr BleL2capInputStream::Read(std::int64_t size) { + // TODO: edwinwu - Implement to read data from l2cap channel. + return {Exception::kIo}; +} + +Exception BleL2capInputStream::Close() { + // The input stream reads directly from the connection. It can not be closed without closing the + // connection itself. A call to `BleL2capSocket::Close` will close the connection. + return {Exception::kSuccess}; +} + +#pragma mark - BleL2capOutputStream + +BleL2capOutputStream::BleL2capOutputStream(GNCBLEL2CAPStream* stream) : stream_(stream) {} + +Exception BleL2capOutputStream::Write(const ByteArray& data) { + // TODO: edwinwu - Implement to write data to l2cap channel. + return {Exception::kIo}; +} + +Exception BleL2capOutputStream::Flush() { return {Exception::kSuccess}; } + +Exception BleL2capOutputStream::Close() { return {Exception::kSuccess}; } + +#pragma mark - BleL2capSocket + +BleL2capSocket::BleL2capSocket(GNCBLEL2CAPStream* stream) + : BleL2capSocket(stream, new EmptyBlePeripheral()) {} + +BleL2capSocket::BleL2capSocket(GNCBLEL2CAPStream* stream, api::ble_v2::BlePeripheral* peripheral) + : stream_(stream), + input_stream_(std::make_unique(stream)), + output_stream_(std::make_unique(stream)), + peripheral_(peripheral) {} + +InputStream& BleL2capSocket::GetInputStream() { return *input_stream_; } + +OutputStream& BleL2capSocket::GetOutputStream() { return *output_stream_; } + +Exception BleL2capSocket::Close() { + [stream_ close]; + return {Exception::kSuccess}; +} + +void BleL2capSocket::SetCloseNotifier(absl::AnyInvocable notifier) {} + +} // namespace apple +} // namespace nearby diff --git a/internal/platform/implementation/apple/ble_medium.h b/internal/platform/implementation/apple/ble_medium.h index fbfffa8b..a8d5a611 100644 --- a/internal/platform/implementation/apple/ble_medium.h +++ b/internal/platform/implementation/apple/ble_medium.h @@ -118,6 +118,12 @@ class BleMedium : public api::ble_v2::BleMedium { std::unique_ptr OpenServerSocket( const std::string &service_id) override; + // Opens a L2CAP server socket based on service ID. + // + // On success, returns a new BleL2capServerSocket. On error, returns nullptr. + std::unique_ptr OpenL2capServerSocket( + const std::string &service_id) override; + // TODO(b/290385712): cancellation_flag support is not yet implemented. // // Connects to a BLE peripheral. diff --git a/internal/platform/implementation/apple/ble_medium.mm b/internal/platform/implementation/apple/ble_medium.mm index c54601b1..5e154e52 100644 --- a/internal/platform/implementation/apple/ble_medium.mm +++ b/internal/platform/implementation/apple/ble_medium.mm @@ -35,6 +35,7 @@ #import "internal/platform/implementation/apple/Mediums/BLEv2/GNCPeripheral.h" #import "internal/platform/implementation/apple/ble_gatt_client.h" #import "internal/platform/implementation/apple/ble_gatt_server.h" +#import "internal/platform/implementation/apple/ble_l2cap_server_socket.h" #import "internal/platform/implementation/apple/ble_peripheral.h" #import "internal/platform/implementation/apple/ble_server_socket.h" #import "internal/platform/implementation/apple/ble_socket.h" @@ -42,6 +43,7 @@ #import "GoogleToolboxForMac/GTMLogger.h" // TODO(b/293336684): Old Weave imports that need to be deleted once shared Weave is complete. +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEL2CAPServer.h" #import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleConnection.h" #import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleUtils.h" #import "internal/platform/implementation/apple/Mediums/Ble/Sockets/Source/Central/GNSCentralManager.h" @@ -362,6 +364,29 @@ std::unique_ptr BleMedium::OpenServerSocket( return std::move(server_socket); } +std::unique_ptr BleMedium::OpenL2capServerSocket( + const std::string &service_id) { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block GNCBLEL2CAPServer *block_l2cap_server = nil; + [medium_ + openL2CAPServerWithCompletionHandler:^(GNCBLEL2CAPServer *server, NSError *error) { + if (error != nil) { + GTMLoggerError(@"Error opening L2CAP server: %@", error); + } + block_l2cap_server = server; + dispatch_semaphore_signal(semaphore); + } + peripheralManager:nil]; + if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC)) != 0) { + GTMLoggerError(@"Opening L2CAP server timed out."); + return nullptr; + } + if (!block_l2cap_server) { + return nullptr; + } + return std::make_unique(block_l2cap_server); +} + // TODO(b/290385712): Add support for @c cancellation_flag. // TODO(b/293336684): Old Weave code that need to be deleted once shared Weave is complete. std::unique_ptr BleMedium::Connect(const std::string &service_id, diff --git a/internal/platform/implementation/ble_v2.h b/internal/platform/implementation/ble_v2.h index 17e5cd1f..fb6417f8 100644 --- a/internal/platform/implementation/ble_v2.h +++ b/internal/platform/implementation/ble_v2.h @@ -17,10 +17,7 @@ #include #include -#include -#include #include -#include #include #include @@ -411,6 +408,9 @@ class BleL2capServerSocket { public: virtual ~BleL2capServerSocket() = default; + // Gets PSM value has been published by the server. + virtual int GetPSM() const = 0; + // Blocks until either: // - at least one incoming connection request is available, or // - ServerSocket is closed. @@ -567,6 +567,7 @@ class BleMedium { // // On success, returns a new BleL2capServerSocket. // On error, returns nullptr. + // Platform implementation should override this method if it supports L2CAP. virtual std::unique_ptr OpenL2capServerSocket( const std::string& service_id) { return nullptr;