diff --git a/Package.swift b/Package.swift index e9ceacdc..e4c984bd 100644 --- a/Package.swift +++ b/Package.swift @@ -570,6 +570,11 @@ let package = Package( "connections/implementation/mediums/webrtc", // This breaks the build, but seems to work fine without it? "internal/platform/medium_environment.cc", + // Temporarily ignore BLEv2 source files. + // TODO(b/293283024): Stop ignoring these files when BLEv2 migration is complete. + "internal/platform/implementation/apple/ble_peripheral.mm", + "internal/platform/implementation/apple/ble_server_socket.mm", + "internal/platform/implementation/apple/ble_socket.mm", ], sources: [ "compiled_proto", diff --git a/internal/platform/implementation/apple/BUILD b/internal/platform/implementation/apple/BUILD index b2dafb1f..af77fa20 100644 --- a/internal/platform/implementation/apple/BUILD +++ b/internal/platform/implementation/apple/BUILD @@ -104,6 +104,36 @@ objc_library( ], ) +objc_library( + name = "ble_v2", + srcs = [ + "ble_peripheral.mm", + "ble_server_socket.mm", + "ble_socket.mm", + "ble_utils.mm", + "utils.mm", + ], + hdrs = [ + "ble_peripheral.h", + "ble_server_socket.h", + "ble_socket.h", + "ble_utils.h", + "utils.h", + ], + # Prevent Objective-C++ headers from being pulled into swift. + aspect_hints = ["//tools/build_defs/swift:no_module"], + deps = [ + "//internal/platform:base", + "//internal/platform/implementation:comm", + "//internal/platform/implementation/apple/Mediums", + "//third_party/apple_frameworks:CoreBluetooth", + "//third_party/apple_frameworks:Foundation", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/functional:any_invocable", + "@com_google_absl//absl/strings", + ], +) + objc_library( name = "Shared", srcs = [ diff --git a/internal/platform/implementation/apple/ble_peripheral.h b/internal/platform/implementation/apple/ble_peripheral.h new file mode 100644 index 00000000..b31b918e --- /dev/null +++ b/internal/platform/implementation/apple/ble_peripheral.h @@ -0,0 +1,54 @@ +// Copyright 2023 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 + +#import +#import + +#include + +#include "internal/platform/implementation/ble_v2.h" + +namespace nearby { +namespace apple { + +// Opaque wrapper over a CoreBluetooth peripheral. This can be used to uniquely +// identify a peripheral and connect to its GATT server. +class BlePeripheral : public api::ble_v2::BlePeripheral { + public: + explicit BlePeripheral(CBPeripheral *peripheral); + ~BlePeripheral() override = default; + + // Returns the hardware address of this peripheral. + // + // For example, "00:11:22:AA:BB:CC". + std::string GetAddress() const override; + + // Returns an immutable unique identifier. The identifier does not change when + // the peripheral's address is rotated. + api::ble_v2::BlePeripheral::UniqueId GetUniqueId() const override; + + private: + CBPeripheral *peripheral_; + api::ble_v2::BlePeripheral::UniqueId unique_id_; +}; + +} // namespace apple +} // namespace nearby diff --git a/internal/platform/implementation/apple/ble_peripheral.mm b/internal/platform/implementation/apple/ble_peripheral.mm new file mode 100644 index 00000000..8dedbd4e --- /dev/null +++ b/internal/platform/implementation/apple/ble_peripheral.mm @@ -0,0 +1,38 @@ +// Copyright 2023 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_peripheral.h" + +#import +#import + +#include + +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/prng.h" + +namespace nearby { +namespace apple { + +BlePeripheral::BlePeripheral(CBPeripheral *peripheral) + : peripheral_(peripheral), unique_id_(Prng().NextInt64()) {} + +std::string BlePeripheral::GetAddress() const { + return peripheral_.identifier.UUIDString.UTF8String; +} + +api::ble_v2::BlePeripheral::UniqueId BlePeripheral::GetUniqueId() const { return unique_id_; } + +} // namespace apple +} // namespace nearby diff --git a/internal/platform/implementation/apple/ble_server_socket.h b/internal/platform/implementation/apple/ble_server_socket.h new file mode 100644 index 00000000..f4758e0c --- /dev/null +++ b/internal/platform/implementation/apple/ble_server_socket.h @@ -0,0 +1,76 @@ +// Copyright 2023 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 + +// TODO(b/293336684): Remove this file when shared Weave is complete. + +#import + +#include + +#include "absl/functional/any_invocable.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/ble_v2.h" + +#import "internal/platform/implementation/apple/ble_socket.h" + +namespace nearby { +namespace apple { + +// A BLE server socket for listening for incoming Weave sockets. +class BleServerSocket : public api::ble_v2::BleServerSocket { + public: + BleServerSocket() = default; + ~BleServerSocket() override; + + // Wait for an available socket. + // + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // + // On success, returns connected socket, ready to exchange data or nullptr on + // error. Once error is reported, it is permanent, and ServerSocket must be + // closed. + std::unique_ptr Accept() override + ABSL_LOCKS_EXCLUDED(mutex_); + + // Close the server socket. + // + // Returns Exception::kIo on error, otherwise Exception::kSuccess. + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + bool Connect(std::unique_ptr socket) ABSL_LOCKS_EXCLUDED(mutex_); + void SetCloseNotifier(absl::AnyInvocable notifier) + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable absl::Mutex mutex_; + absl::CondVar cond_; + absl::flat_hash_set> pending_sockets_ + ABSL_GUARDED_BY(mutex_); + absl::AnyInvocable close_notifier_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +} // namespace apple +} // namespace nearby diff --git a/internal/platform/implementation/apple/ble_server_socket.mm b/internal/platform/implementation/apple/ble_server_socket.mm new file mode 100644 index 00000000..42c2d1ef --- /dev/null +++ b/internal/platform/implementation/apple/ble_server_socket.mm @@ -0,0 +1,82 @@ +// Copyright 2023 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. + +// TODO(b/293336684): Remove this file when shared Weave is complete. + +#import "internal/platform/implementation/apple/ble_server_socket.h" + +#import + +#include +#include + +namespace nearby { +namespace apple { + +BleServerSocket::~BleServerSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +std::unique_ptr BleServerSocket::Accept() { + absl::MutexLock lock(&mutex_); + while (!closed_ && pending_sockets_.empty()) { + cond_.Wait(&mutex_); + } + if (closed_) return {}; + + std::unique_ptr remote_socket = + std::move(pending_sockets_.extract(pending_sockets_.begin()).value()); + return std::move(remote_socket); +} + +bool BleServerSocket::Connect(std::unique_ptr socket) { + absl::MutexLock lock(&mutex_); + if (closed_) { + return false; + } + pending_sockets_.insert(std::move(socket)); + cond_.SignalAll(); + return !closed_; +} + +void BleServerSocket::SetCloseNotifier(absl::AnyInvocable notifier) { + absl::MutexLock lock(&mutex_); + close_notifier_ = std::move(notifier); +} + +Exception BleServerSocket::Close() { + absl::MutexLock lock(&mutex_); + return DoClose(); +} + +Exception BleServerSocket::DoClose() { + bool should_notify = !closed_; + closed_ = true; + if (should_notify) { + cond_.SignalAll(); + if (close_notifier_) { + auto notifier = std::move(close_notifier_); + mutex_.Unlock(); + // Notifier may contain calls to public API, and may cause deadlock, if + // mutex_ is held during the call. + notifier(); + mutex_.Lock(); + } + } + return {Exception::kSuccess}; +} + +} // namespace apple +} // namespace nearby diff --git a/internal/platform/implementation/apple/ble_socket.h b/internal/platform/implementation/apple/ble_socket.h new file mode 100644 index 00000000..0e651719 --- /dev/null +++ b/internal/platform/implementation/apple/ble_socket.h @@ -0,0 +1,130 @@ +// Copyright 2023 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 + +// TODO(b/293336684): Remove this file when shared Weave is complete. + +#import + +#include + +#include "internal/platform/implementation/ble_v2.h" + +#import "internal/platform/implementation/apple/ble_peripheral.h" + +@class GNCMConnectionHandlers; +@protocol GNCMConnection; + +namespace nearby { +namespace apple { + +// A readable stream of bytes. +class BleInputStream : public InputStream { + public: + BleInputStream(); + ~BleInputStream() override; + + // 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; + + GNCMConnectionHandlers *GetConnectionHandlers() { return connectionHandlers_; } + + private: + GNCMConnectionHandlers *connectionHandlers_; + NSMutableArray *newDataPackets_; + NSMutableData *accumulatedData_; + NSCondition *condition_; +}; + +// A writable stream of bytes. +class BleOutputStream : public OutputStream { + public: + explicit BleOutputStream(id connection) + : connection_(connection), condition_([[NSCondition alloc] init]) {} + ~BleOutputStream() override; + + // 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: + id connection_; + NSCondition *condition_; +}; + +// A BLE Weave socket. +class BleSocket : public api::ble_v2::BleSocket { + public: + BleSocket(id connection, BlePeripheral *peripheral); + ~BleSocket() override; + + // Returns the InputStream of the BleSocket. + // 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 BleSocket object is destroyed. + InputStream &GetInputStream() override { return *input_stream_; } + + // Returns the OutputStream of the BleSocket. + // 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 BleSocket object is destroyed. + OutputStream &GetOutputStream() override { return *output_stream_; } + + // Returns Exception::kIo on error, otherwise Exception::kSuccess. + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + // Returns valid BlePeripheral pointer if there is a connection, and + // nullptr otherwise. + BlePeripheral *GetRemotePeripheral() override { return peripheral_; } + + bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_); + + private: + void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable absl::Mutex mutex_; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; + std::unique_ptr input_stream_; + std::unique_ptr output_stream_; + BlePeripheral *peripheral_; +}; + +} // namespace apple +} // namespace nearby diff --git a/internal/platform/implementation/apple/ble_socket.mm b/internal/platform/implementation/apple/ble_socket.mm new file mode 100644 index 00000000..a868ff0b --- /dev/null +++ b/internal/platform/implementation/apple/ble_socket.mm @@ -0,0 +1,210 @@ +// Copyright 2023 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_socket.h" + +#include "internal/platform/implementation/ble_v2.h" + +#import "internal/platform/implementation/apple/Mediums/Ble/GNCMBleConnection.h" +#import "internal/platform/implementation/apple/ble_peripheral.h" +#import "internal/platform/implementation/apple/ble_utils.h" +#import "internal/platform/implementation/apple/utils.h" + +// TODO(b/293336684): Remove this file when shared Weave is complete. + +namespace nearby { +namespace apple { + +#pragma mark - BleInputStream + +BleInputStream::BleInputStream() + : newDataPackets_([NSMutableArray array]), + accumulatedData_([NSMutableData data]), + condition_([[NSCondition alloc] init]) { + // Create the handlers of incoming data from the remote endpoint. + connectionHandlers_ = [GNCMConnectionHandlers + payloadHandler:^(NSData *data) { + [condition_ lock]; + // Add the incoming data to the data packet array to be processed in read() below. + [newDataPackets_ addObject:data]; + [condition_ broadcast]; + [condition_ unlock]; + } + disconnectedHandler:^{ + [condition_ lock]; + // Release the data packet array, meaning the stream has been closed or severed. + newDataPackets_ = nil; + [condition_ broadcast]; + [condition_ unlock]; + }]; +} + +BleInputStream::~BleInputStream() { + NSCAssert(!newDataPackets_, @"BleInputStream not closed before destruction"); +} + +ExceptionOr BleInputStream::Read(std::int64_t size) { + // Block until either (a) the connection has been closed, (b) we have enough data to return. + NSData *dataToReturn; + [condition_ lock]; + while (true) { + // Check if the stream has been closed or severed. + if (!newDataPackets_) break; + + if (newDataPackets_.count > 0) { + // Add the packet data to the accumulated data. + for (NSData *data in newDataPackets_) { + if (data.length > 0) { + [accumulatedData_ appendData:data]; + } + } + [newDataPackets_ removeAllObjects]; + } + + if ((size == -1) && (accumulatedData_.length > 0)) { + // Return all of the data. + dataToReturn = accumulatedData_; + accumulatedData_ = [NSMutableData data]; + break; + } else if (accumulatedData_.length > 0) { + // Return up to |size| bytes of the data. + std::int64_t sizeToReturn = (accumulatedData_.length < size) ? accumulatedData_.length : size; + NSRange range = NSMakeRange(0, (NSUInteger)sizeToReturn); + dataToReturn = [accumulatedData_ subdataWithRange:range]; + [accumulatedData_ replaceBytesInRange:range withBytes:nil length:0]; + break; + } + + [condition_ wait]; + } + [condition_ unlock]; + + if (dataToReturn) { + NSLog(@"[NEARBY] Input stream: Received data of size: %lu", (unsigned long)dataToReturn.length); + return ExceptionOr(ByteArrayFromNSData(dataToReturn)); + } else { + return ExceptionOr{Exception::kIo}; + } +} + +Exception BleInputStream::Close() { + // Unblock pending read operation. + [condition_ lock]; + newDataPackets_ = nil; + [condition_ broadcast]; + [condition_ unlock]; + return {Exception::kSuccess}; +} + +#pragma mark - BleOutputStream + +BleOutputStream::~BleOutputStream() { + NSCAssert(!connection_, @"BleOutputStream not closed before destruction"); +} + +Exception BleOutputStream::Write(const ByteArray &data) { + [condition_ lock]; + NSLog(@"[NEARBY] Sending data of size: %lu", NSDataFromByteArray(data).length); + + if (!connection_) { + [condition_ unlock]; + return {Exception::kIo}; + } + + NSMutableData *packet = [NSMutableData dataWithData:NSDataFromByteArray(data)]; + + // Send the data, blocking until the completion handler is called. + __block bool isComplete = NO; + __block GNCMPayloadResult sendResult = GNCMPayloadResultFailure; + NSCondition *condition = condition_; // don't capture |this| in completion + + [connection_ sendData:packet + progressHandler:^(size_t count) { + } + completion:^(GNCMPayloadResult result) { + [condition lock]; + // Make sure we haven't already reported completion before. This prevents a crash + // where we try leaving a dispatch group more times than we entered it. + // b/79095653. + if (isComplete) { + [condition unlock]; + return; + } + isComplete = YES; + sendResult = result; + [condition broadcast]; + [condition unlock]; + }]; + + while (connection_ && !isComplete) { + [condition_ wait]; + } + + if (sendResult == GNCMPayloadResultSuccess) { + [condition_ unlock]; + return {Exception::kSuccess}; + } else { + [condition_ unlock]; + return {Exception::kIo}; + } +} + +Exception BleOutputStream::Flush() { + // The write() function blocks until the data is received by the remote endpoint, so there's + // nothing to do here. + return {Exception::kSuccess}; +} + +Exception BleOutputStream::Close() { + // Unblock pending write operation. + [condition_ lock]; + connection_ = nil; + [condition_ broadcast]; + [condition_ unlock]; + return {Exception::kSuccess}; +} + +#pragma mark - BleSocket + +BleSocket::BleSocket(id connection, BlePeripheral *peripheral) + : input_stream_(new BleInputStream()), + output_stream_(new BleOutputStream(connection)), + peripheral_(peripheral) {} + +BleSocket::~BleSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +bool BleSocket::IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; +} + +Exception BleSocket::Close() { + absl::MutexLock lock(&mutex_); + DoClose(); + return {Exception::kSuccess}; +} + +void BleSocket::DoClose() { + if (!closed_) { + input_stream_->Close(); + output_stream_->Close(); + closed_ = true; + } +} + +} // namespace apple +} // namespace nearby