Merge remote-tracking branch 'nearby/main' into sync-upstream

This commit is contained in:
Lasan Mahaliyana
2026-06-13 21:02:50 +05:30
690 changed files with 62049 additions and 42631 deletions
-49
View File
@@ -1,49 +0,0 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
licenses(["notice"])
# Copyright 2020 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.
cc_library(
name = "event_logger",
hdrs = [
"event_logger.h",
],
visibility = [
"//connections:__subpackages__",
"//location/nearby/analytics/cpp:__subpackages__",
"//location/nearby/cpp/experiments:__subpackages__",
"//sharing:__subpackages__",
],
deps = [
"//internal/proto/analytics:connections_log_cc_proto",
"//sharing/proto/analytics:sharing_log_cc_proto",
],
)
cc_library(
name = "mock_event_logger",
testonly = True,
hdrs = [
"mock_event_logger.h",
"sharing_log_matchers.h",
],
compatible_with = ["//buildenv/target:non_prod"],
visibility = ["//visibility:public"],
deps = [
":event_logger",
"@com_google_googletest//:gtest_for_library_testonly",
"@com_google_protobuf//:protobuf_lite",
],
)
-41
View File
@@ -1,41 +0,0 @@
// Copyright 2022 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 NEARBY_ANALYTICS_EVENT_LOGGER_H_
#define NEARBY_ANALYTICS_EVENT_LOGGER_H_
#include "internal/proto/analytics/connections_log.pb.h"
#include "sharing/proto/analytics/nearby_sharing_log.pb.h"
namespace nearby {
namespace analytics {
// Allows callers to log the proto collected at the client (e.g. Nearby
// Connections, Nearby Sharing, etc). Callers need to implement the API
// if they want to collect this log.
class EventLogger {
public:
virtual ~EventLogger() = default;
// Logs the proto details. Might block to do I/O, e.g. upload
// synchronously to some metrics server.
virtual void Log(
const location::nearby::analytics::proto::ConnectionsLog& message) = 0;
virtual void Log(const sharing::analytics::proto::SharingLog& message) = 0;
};
} // namespace analytics
} // namespace nearby
#endif // NEARBY_ANALYTICS_EVENT_LOGGER_H_
-38
View File
@@ -1,38 +0,0 @@
// Copyright 2024 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 THIRD_PARTY_NEARBY_INTERNAL_ANALYTICS_MOCK_EVENT_LOGGER_H_
#define THIRD_PARTY_NEARBY_INTERNAL_ANALYTICS_MOCK_EVENT_LOGGER_H_
#include "gmock/gmock.h"
#include "internal/analytics/event_logger.h"
namespace nearby::analytics {
class MockEventLogger : public ::nearby::analytics::EventLogger {
public:
MockEventLogger() = default;
~MockEventLogger() override = default;
MOCK_METHOD(
void, Log,
(const location::nearby::analytics::proto::ConnectionsLog& message),
(override));
MOCK_METHOD(void, Log, (const sharing::analytics::proto::SharingLog& message),
(override));
};
} // namespace nearby::analytics
#endif // THIRD_PARTY_NEARBY_INTERNAL_ANALYTICS_MOCK_EVENT_LOGGER_H_
-64
View File
@@ -1,64 +0,0 @@
// Copyright 2024 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 THIRD_PARTY_NEARBY_INTERNAL_ANALYTICS_SHARING_LOG_MATCHERS_H_
#define THIRD_PARTY_NEARBY_INTERNAL_ANALYTICS_SHARING_LOG_MATCHERS_H_
#include "gmock/gmock.h"
namespace nearby::analytics {
MATCHER_P(HasCategory, category, "has category") {
return arg.event_category() == category;
}
MATCHER_P(HasEventType, event_type, "has event type") {
return arg.event_type() == event_type;
}
MATCHER_P(HasAction, action, "has action") {
return arg.action() == action;
}
MATCHER_P(HasSessionId, session_id, "has session id") {
return arg.session_id() == session_id;
}
MATCHER_P(HasDurationMillis, duration_millis, "has duration millis") {
return arg.duration_millis() == duration_millis;
}
MATCHER_P(SharingLogHasStatus, status, "has status") {
return arg.status() == status;
}
MATCHER_P(HasRpcName, rpc_name, "has rpc_name") {
return arg.rpc_name() == rpc_name;
}
MATCHER_P(HasDirection, direction, "has direction") {
return arg.direction() == direction;
}
MATCHER_P(HasErrorCode, error_code, "has error_code") {
return arg.error_code() == error_code;
}
MATCHER_P(HasLatencyMillis, latency_millis, "has latency_millis") {
return arg.latency_millis() == latency_millis;
}
} // namespace nearby::analytics
#endif // THIRD_PARTY_NEARBY_INTERNAL_ANALYTICS_SHARING_LOG_MATCHERS_H_
+1 -4
View File
@@ -19,8 +19,6 @@ licenses(["notice"])
cc_library(
name = "base",
srcs = [
],
hdrs = [
"observer_list.h",
],
@@ -28,6 +26,7 @@ cc_library(
"//internal/account:__subpackages__",
"//internal/platform:__subpackages__",
"//internal/test:__pkg__",
"//location/nearby/sharing/lib:__subpackages__",
"//sharing:__subpackages__",
],
deps = [
@@ -51,8 +50,6 @@ cc_library(
],
deps = [
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/types:optional",
"@com_google_absl//absl/types:span",
],
)
+8 -7
View File
@@ -15,18 +15,19 @@
#include "internal/base/bluetooth_address.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <optional>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "absl/types/span.h"
namespace nearby {
namespace device {
namespace {
template <int BASE, typename CHAR>
// Note that some of the methods return absl::optional instead
// of std::optional, because iOS platform is still in C++14.
absl::optional<uint8_t> CharToDigit(CHAR c) {
std::optional<uint8_t> CharToDigit(CHAR c) {
static_assert(1 <= BASE && BASE <= 36, "BASE needs to be in [1, 36]");
if (c >= '0' && c < '0' + std::min(BASE, 10)) return c - '0';
@@ -34,7 +35,7 @@ absl::optional<uint8_t> CharToDigit(CHAR c) {
if (c >= 'A' && c < 'A' + BASE - 10) return c - 'A' + 10;
return absl::nullopt;
return std::nullopt;
}
template <typename OutIter>
@@ -43,9 +44,9 @@ static bool HexStringToByteContainer(absl::string_view input, OutIter output) {
if (count == 0 || (count % 2) != 0) return false;
for (uintptr_t i = 0; i < count / 2; ++i) {
// most significant 4 bits
absl::optional<uint8_t> msb = CharToDigit<16>(input[i * 2]);
std::optional<uint8_t> msb = CharToDigit<16>(input[i * 2]);
// least significant 4 bits
absl::optional<uint8_t> lsb = CharToDigit<16>(input[i * 2 + 1]);
std::optional<uint8_t> lsb = CharToDigit<16>(input[i * 2 + 1]);
if (!msb.has_value() || !lsb.has_value()) {
return false;
}
-2
View File
@@ -44,8 +44,6 @@ cc_test(
srcs = ["ed25519_unittest.cc"],
copts = [
"-DUNIT_TEST",
"-Wno-inconsistent-missing-override",
"-Wno-non-virtual-dtor",
"-Ithird_party",
],
deps = [
-2
View File
@@ -99,8 +99,6 @@ cc_test(
],
copts = [
"-DUNIT_TEST",
"-Wno-inconsistent-missing-override",
"-Wno-non-virtual-dtor",
"-Ithird_party",
],
deps = [
+1 -1
View File
@@ -104,7 +104,7 @@ bool Aead::Seal(absl::string_view plaintext, absl::string_view nonce,
return true;
}
absl::optional<std::vector<uint8_t>> Aead::Open(
std::optional<std::vector<uint8_t>> Aead::Open(
absl::Span<const uint8_t> ciphertext, absl::Span<const uint8_t> nonce,
absl::Span<const uint8_t> additional_data) const {
const size_t max_output_length = ciphertext.size();
+1 -1
View File
@@ -64,7 +64,7 @@ TEST_P(AeadTest, SealOpenSpan) {
aead.Seal(kPlaintext, nonce, kAdditionalData);
EXPECT_LT(sizeof(kPlaintext), ciphertext.size());
absl::optional<std::vector<uint8_t>> decrypted =
std::optional<std::vector<uint8_t>> decrypted =
aead.Open(ciphertext, nonce, kAdditionalData);
ASSERT_TRUE(decrypted);
ASSERT_EQ(decrypted->size(), sizeof(kPlaintext));
+8 -8
View File
@@ -127,7 +127,7 @@ bool Encryptor::CryptString(bool do_encrypt, absl::string_view input,
uint8_t* out_ptr =
reinterpret_cast<uint8_t*>(nearbybase::WriteInto(&result, out_size + 1));
absl::optional<size_t> len =
std::optional<size_t> len =
(mode_ == CTR)
? CryptCTR(do_encrypt, nearbybase::as_bytes(absl::MakeSpan(input)),
absl::MakeSpan(out_ptr, out_size))
@@ -143,7 +143,7 @@ bool Encryptor::CryptString(bool do_encrypt, absl::string_view input,
bool Encryptor::CryptBytes(bool do_encrypt, absl::Span<const uint8_t> input,
std::vector<uint8_t>* output) {
std::vector<uint8_t> result(MaxOutput(do_encrypt, input.size()));
absl::optional<size_t> len =
std::optional<size_t> len =
(mode_ == CTR) ? CryptCTR(do_encrypt, input, absl::MakeSpan(result))
: Crypt(do_encrypt, input, absl::MakeSpan(result));
if (!len) return false;
@@ -159,9 +159,9 @@ size_t Encryptor::MaxOutput(bool do_encrypt, size_t length) {
return result;
}
absl::optional<size_t> Encryptor::Crypt(bool do_encrypt,
absl::Span<const uint8_t> input,
absl::Span<uint8_t> output) {
std::optional<size_t> Encryptor::Crypt(bool do_encrypt,
absl::Span<const uint8_t> input,
absl::Span<uint8_t> output) {
DCHECK(key_); // Must call Init() before En/De-crypt.
const EVP_CIPHER* cipher = GetCipherForKey(key_);
@@ -197,9 +197,9 @@ absl::optional<size_t> Encryptor::Crypt(bool do_encrypt,
return out_len;
}
absl::optional<size_t> Encryptor::CryptCTR(bool do_encrypt,
absl::Span<const uint8_t> input,
absl::Span<uint8_t> output) {
std::optional<size_t> Encryptor::CryptCTR(bool do_encrypt,
absl::Span<const uint8_t> input,
absl::Span<uint8_t> output) {
if (iv_.size() != AES_BLOCK_SIZE) {
LOG(ERROR) << "Counter value not set in CTR mode.";
return absl::nullopt;
+3 -3
View File
@@ -35,7 +35,7 @@ cc_library(
],
visibility = [
"//connections:__subpackages__",
"//presence:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
],
deps = [
":authentication_status",
@@ -54,8 +54,8 @@ cc_library(
],
visibility = [
"//connections:__subpackages__",
"//presence:__subpackages__",
"//sharing:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
],
)
@@ -70,7 +70,7 @@ cc_library(
],
compatible_with = ["//buildenv/target:non_prod"],
visibility = [
"//presence:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
],
deps = [
":authentication_status",
+9 -19
View File
@@ -164,7 +164,7 @@ cc_library(
visibility = [
"//connections/implementation:__subpackages__",
"//internal/platform/implementation:__subpackages__",
"//presence:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
],
deps = [
":base",
@@ -191,7 +191,7 @@ cc_library(
"//connections/implementation:__pkg__",
"//connections/v3:__pkg__",
"//internal/interop:__pkg__",
"//presence:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
],
deps = [
":logging",
@@ -210,7 +210,6 @@ cc_library(
srcs = [
"blocking_queue_stream.cc",
"clock_impl.cc",
"device_info_impl.cc",
"monitored_runnable.cc",
"pending_job_registry.cc",
"pipe.cc",
@@ -231,10 +230,7 @@ cc_library(
"condition_variable.h",
"count_down_latch.h",
"crypto.h",
"device_info.h",
"device_info_impl.h",
"direct_executor.h",
"file.h",
"future.h",
"lockable.h",
"monitored_runnable.h",
@@ -306,21 +302,18 @@ cc_library(
"bluetooth_adapter.h",
"bluetooth_classic.h",
"credential_storage_impl.h",
"webrtc.h",
"file.h",
"wifi.h",
"wifi_direct.h",
"wifi_hotspot.h",
"wifi_lan.h",
],
copts = [
"-DCORE_ADAPTER_DLL",
"-DNO_WEBRTC",
],
copts = ["-DCORE_ADAPTER_DLL"],
visibility = [
"//connections:__subpackages__",
"//internal/platform/implementation:__subpackages__",
"//internal/test:__subpackages__",
"//presence:__subpackages__",
"//sharing:__subpackages__",
],
deps = [
":base",
@@ -332,12 +325,10 @@ cc_library(
"//connections/implementation/flags:connections_flags",
"//internal/base",
"//internal/flags:nearby_flags",
"//internal/platform/implementation:account_manager",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"//internal/platform/implementation:wifi_utils",
# "//third_party/webrtc/files/stable/webrtc/api:create_peerconnection_factory", # buildcleaner: keep
# "//third_party/webrtc/files/stable/webrtc/api:peer_connection_interface",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
@@ -345,9 +336,7 @@ cc_library(
"@com_google_absl//absl/status",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:optional",
],
)
@@ -377,7 +366,7 @@ cc_library(
visibility = [
"//connections:__subpackages__",
"//internal/platform/implementation:__subpackages__",
"//presence:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
],
deps = [
":base",
@@ -387,6 +376,7 @@ cc_library(
":uuid",
"//internal/base",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:webrtc_platform",
"//internal/platform/implementation:wifi_utils",
"//internal/test",
"@com_google_absl//absl/base:core_headers",
@@ -582,6 +572,7 @@ cc_test(
shard_count = 16,
deps = [
":base",
":comm",
":connection_info",
":logging",
":mac_address",
@@ -593,7 +584,6 @@ cc_test(
"//internal/crypto_cros",
"//internal/platform/implementation:platform_impl",
"//internal/platform/implementation:types",
"//internal/test",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/status",
+2 -2
View File
@@ -16,6 +16,7 @@
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_AWDL_H_
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
@@ -23,7 +24,6 @@
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/functional/any_invocable.h"
#include "absl/types/optional.h"
#include "internal/platform/blocking_queue_stream.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
@@ -261,7 +261,7 @@ class AwdlMedium {
}
// Returns the port range as a pair of min and max port.
absl::optional<std::pair<std::int32_t, std::int32_t>> GetDynamicPortRange() {
std::optional<std::pair<std::int32_t, std::int32_t>> GetDynamicPortRange() {
return impl_->GetDynamicPortRange();
}
+3 -15
View File
@@ -27,7 +27,6 @@
#include "absl/strings/escaping.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
@@ -223,7 +222,7 @@ class GattServer final {
~GattServer() { Stop(); }
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::optional<api::ble::GattCharacteristic> CreateCharacteristic(
std::optional<api::ble::GattCharacteristic> CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid,
const api::ble::GattCharacteristic::Permission permission,
const api::ble::GattCharacteristic::Property property) {
@@ -277,13 +276,13 @@ class GattClient final {
}
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::optional<api::ble::GattCharacteristic> GetCharacteristic(
std::optional<api::ble::GattCharacteristic> GetCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid) {
return impl_->GetCharacteristic(service_uuid, characteristic_uuid);
}
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::optional<std::string> ReadCharacteristic(
std::optional<std::string> ReadCharacteristic(
const api::ble::GattCharacteristic& characteristic) {
return impl_->ReadCharacteristic(characteristic);
}
@@ -295,17 +294,6 @@ class GattClient final {
return impl_->WriteCharacteristic(characteristic, value, write_type);
}
// TODO(qinwangz): We should not need `on_characteristic_changed_cb` when
// unsubscribing.
// NOLINTNEXTLINE(google3-legacy-absl-backports)
bool SetCharacteristicSubscription(
const api::ble::GattCharacteristic& characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) {
return impl_->SetCharacteristicSubscription(
characteristic, enable, std::move(on_characteristic_changed_cb));
}
void Disconnect() { impl_->Disconnect(); }
// Returns true if a client_gatt_connection is usable. If this method
-79
View File
@@ -55,7 +55,6 @@ using ::nearby::api::ble::BleAdvertisementData;
using ::nearby::api::ble::GattCharacteristic;
using ::nearby::api::ble::TxPowerLevel;
using ::testing::Optional;
using ::testing::status::StatusIs;
constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000);
constexpr absl::string_view kAdvertisementString = "\x0a\x0b\x0c\x0d";
@@ -771,83 +770,5 @@ TEST_F(BleMediumTest, GattClientOperatiosOnCharacteristic) {
env_.Stop();
}
TEST_F(BleMediumTest, GattClientSubscribeNotificationGattServerCanNotify) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleMedium ble_a(adapter_a);
BleMedium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
Uuid characteristic_uuid(5678, 1234);
GattCharacteristic::Permission permissions =
GattCharacteristic::Permission::kRead;
GattCharacteristic::Property properties =
GattCharacteristic::Property::kRead |
GattCharacteristic::Property::kNotify;
// Start GattServer
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
// Add characteristic and its value.
// NOLINTNEXTLINE(google3-legacy-absl-backports)
std::optional<GattCharacteristic> server_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid,
permissions, properties);
EXPECT_TRUE(gatt_server->UpdateCharacteristic(server_characteristic.value(),
ByteArray("any")));
// Start GattClient
MacAddress mac_address = adapter_a.GetAddress();
std::unique_ptr<GattClient> gatt_client = ble_b.ConnectToGattServer(
BlePeripheral(ble_b, mac_address.address()), kTxPowerLevel,
/*ClientGattConnectionCallback=*/{});
ASSERT_NE(gatt_client, nullptr);
EXPECT_TRUE(gatt_client->DiscoverServiceAndCharacteristics(
service_uuid, {characteristic_uuid}));
// Subscribes notification
EXPECT_TRUE(gatt_client->SetCharacteristicSubscription(
server_characteristic.value(), true,
[](absl::string_view value) { EXPECT_EQ(value, "hello"); }));
// Sends notification
EXPECT_EQ(gatt_server->NotifyCharacteristicChanged(
server_characteristic.value(), false, ByteArray("hello")),
absl::OkStatus());
std::string notified_value;
CountDownLatch latch(1);
// Subscribes notification
EXPECT_TRUE(gatt_client->SetCharacteristicSubscription(
server_characteristic.value(), true, [&](absl::string_view value) {
notified_value = value;
latch.CountDown();
}));
// Sends indication
EXPECT_EQ(gatt_server->NotifyCharacteristicChanged(
server_characteristic.value(), true, ByteArray("any")),
absl::OkStatus());
latch.Await();
EXPECT_EQ(notified_value, "any");
// Unsubscribes notification
EXPECT_TRUE(gatt_client->SetCharacteristicSubscription(
server_characteristic.value(), false,
[&](absl::string_view value) { GTEST_FAIL(); }));
EXPECT_THAT(gatt_server->NotifyCharacteristicChanged(
server_characteristic.value(), true, ByteArray("any")),
StatusIs(absl::StatusCode::kNotFound));
gatt_client->Disconnect();
// Failed to subscribe characteristic notification as gatt is disconnected.
EXPECT_FALSE(gatt_client->SetCharacteristicSubscription(
server_characteristic.value(), true, [](absl::string_view value) {}));
gatt_server->Stop();
env_.Stop();
}
} // namespace
} // namespace nearby
+1 -3
View File
@@ -41,9 +41,7 @@ class BlockingQueueStream : public InputStream {
private:
mutable Mutex mutex_;
bool is_multiplex_enabled_ = NearbyFlags::GetInstance().GetBoolFlag(
connections::config_package_nearby::nearby_connections_feature::
kEnableMultiplex);
bool is_multiplex_enabled_ = false;
ArrayBlockingQueue<ByteArray> blocking_queue_{
FeatureFlags::GetInstance()
.GetFlags()
@@ -24,47 +24,21 @@ namespace nearby {
namespace {
TEST(BlockingQueueStreamTest, ReadSuccess) {
bool is_multiplex_enabled = NearbyFlags::GetInstance().GetBoolFlag(
connections::config_package_nearby::nearby_connections_feature::
kEnableMultiplex);
NearbyFlags::GetInstance().OverrideBoolFlagValue(
connections::config_package_nearby::nearby_connections_feature::
kEnableMultiplex, true);
BlockingQueueStream stream;
ByteArray bytes = ByteArray("test1test2test3");
stream.Write(bytes);
ExceptionOr<ByteArray> result = stream.Read(5);
EXPECT_EQ(result.result(), ByteArray("test1"));
result = stream.Read(5);
EXPECT_EQ(result.result(), ByteArray("test2"));
result = stream.Read(5);
EXPECT_EQ(result.result(), ByteArray("test3"));
stream.Close();
NearbyFlags::GetInstance().OverrideBoolFlagValue(
connections::config_package_nearby::nearby_connections_feature::
kEnableMultiplex, is_multiplex_enabled);
}
TEST(BlockingQueueStreamTest, MultiplexDisabled) {
bool is_multiplex_enabled = NearbyFlags::GetInstance().GetBoolFlag(
connections::config_package_nearby::nearby_connections_feature::
kEnableMultiplex);
NearbyFlags::GetInstance().OverrideBoolFlagValue(
connections::config_package_nearby::nearby_connections_feature::
kEnableMultiplex, false);
BlockingQueueStream stream;
ByteArray bytes = ByteArray("test1test2test3");
stream.Write(bytes);
ExceptionOr<ByteArray> result = stream.Read(5);
EXPECT_EQ(result, ExceptionOr<ByteArray>(Exception::kExecution));
stream.Close();
}
NearbyFlags::GetInstance().OverrideBoolFlagValue(
connections::config_package_nearby::nearby_connections_feature::
kEnableMultiplex, is_multiplex_enabled);
TEST(BlockingQueueStreamTest, MultiplexDisabled) {
BlockingQueueStream stream;
ByteArray bytes = ByteArray("test1test2test3");
stream.Write(bytes);
ExceptionOr<ByteArray> result = stream.Read(5);
EXPECT_EQ(result, ExceptionOr<ByteArray>(Exception::kExecution));
stream.Close();
}
} // namespace
+1 -1
View File
@@ -42,6 +42,6 @@ ConnectionInfoVariant ConnectionInfo::FromDataElementBytes(
return result.value();
}
}
return absl::monostate();
return std::monostate();
}
} // namespace nearby
+1 -1
View File
@@ -104,7 +104,7 @@ TEST(ConnectionInfoTest, TestMonostate) {
auto serialized = info->ToDataElementBytes();
auto connection_info =
ConnectionInfo::FromDataElementBytes(serialized.substr(0, 10));
EXPECT_TRUE(absl::holds_alternative<absl::monostate>(connection_info));
EXPECT_TRUE(absl::holds_alternative<std::monostate>(connection_info));
}
}
-74
View File
@@ -1,74 +0,0 @@
// Copyright 2021 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 PLATFORM_PUBLIC_DEVICE_INFO_H_
#define PLATFORM_PUBLIC_DEVICE_INFO_H_
#include <cstddef>
#include <functional>
#include <optional>
#include <string>
#include "absl/strings/string_view.h"
#include "internal/base/file_path.h"
#include "internal/platform/implementation/device_info.h"
namespace nearby {
class DeviceInfo {
public:
virtual ~DeviceInfo() = default;
// All strings are UTF-8 encoded.
virtual std::string GetOsDeviceName() const = 0;
virtual api::DeviceInfo::DeviceType GetDeviceType() const = 0;
virtual api::DeviceInfo::OsType GetOsType() const = 0;
virtual FilePath GetDownloadPath() const = 0;
virtual FilePath GetAppDataPath() const = 0;
virtual FilePath GetTemporaryPath() const = 0;
virtual FilePath GetLogPath() const = 0;
virtual std::optional<size_t> GetAvailableDiskSpaceInBytes(
const FilePath& path) const = 0;
virtual bool IsScreenLocked() const = 0;
virtual void RegisterScreenLockedListener(
absl::string_view listener_name,
std::function<void(api::DeviceInfo::ScreenStatus)> callback) = 0;
virtual void UnregisterScreenLockedListener(
absl::string_view listener_name) = 0;
virtual bool PreventSleep() = 0;
virtual bool AllowSleep() = 0;
// Returns UTF-8 encoded localized device name depending on device type.
std::string GetDeviceTypeName() const {
// TODO(b/230132370): return localized device name.
switch (GetDeviceType()) {
case api::DeviceInfo::DeviceType::kPhone:
return "Phone";
case api::DeviceInfo::DeviceType::kTablet:
return "Tablet";
case api::DeviceInfo::DeviceType::kLaptop:
return "PC";
default:
return "Unknown";
}
}
};
} // namespace nearby
#endif // PLATFORM_PUBLIC_DEVICE_INFO_H_
-63
View File
@@ -1,63 +0,0 @@
// Copyright 2021 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 PLATFORM_PUBLIC_DEVICE_INFO_IMPL_H_
#define PLATFORM_PUBLIC_DEVICE_INFO_IMPL_H_
#include <cstddef>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include "absl/strings/string_view.h"
#include "internal/base/file_path.h"
#include "internal/platform/device_info.h"
#include "internal/platform/implementation/device_info.h"
#include "internal/platform/implementation/platform.h"
namespace nearby {
class DeviceInfoImpl : public DeviceInfo {
public:
DeviceInfoImpl()
: device_info_impl_(api::ImplementationPlatform::CreateDeviceInfo()) {}
std::string GetOsDeviceName() const override;
api::DeviceInfo::DeviceType GetDeviceType() const override;
api::DeviceInfo::OsType GetOsType() const override;
FilePath GetDownloadPath() const override;
FilePath GetAppDataPath() const override;
FilePath GetTemporaryPath() const override;
FilePath GetLogPath() const override;
std::optional<size_t> GetAvailableDiskSpaceInBytes(
const FilePath& path) const override;
bool IsScreenLocked() const override;
void RegisterScreenLockedListener(
absl::string_view listener_name,
std::function<void(api::DeviceInfo::ScreenStatus)> callback) override;
void UnregisterScreenLockedListener(absl::string_view listener_name) override;
bool PreventSleep() override;
bool AllowSleep() override;
private:
std::unique_ptr<api::DeviceInfo> device_info_impl_;
};
} // namespace nearby
#endif // PLATFORM_PUBLIC_DEVICE_INFO_IMPL_H_
+2 -1
View File
@@ -15,6 +15,7 @@
#ifndef PLATFORM_BASE_EXCEPTION_H_
#define PLATFORM_BASE_EXCEPTION_H_
#include <type_traits>
#include <utility>
#include "absl/meta/type_traits.h"
@@ -82,7 +83,7 @@ class ExceptionOr {
ExceptionOr(Exception exception) : exception_{exception} {} // NOLINT
// If there exists explicit conversion from U to T,
// then allow explicit conversion from ExceptionOr<U> to ExceptionOr<T>.
template <typename U, typename = absl::void_t<decltype(T{std::declval<U>()})>>
template <typename U, typename = std::void_t<decltype(T{std::declval<U>()})>>
explicit ExceptionOr<T>(ExceptionOr<U> value) {
if (!value.ok()) {
exception_ = value.GetException();
-1
View File
@@ -22,7 +22,6 @@ cc_library(
],
visibility = [
"//connections:__subpackages__",
"//connections:partners",
"//internal:__subpackages__",
"//location/nearby/cpp:__subpackages__",
"//location/nearby/testing:__subpackages__",
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Mendel flags, auto-generated. DO NOT EDIT.
#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_FLAGS_NEARBY_PLATFORM_FEATURE_FLAGS_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_FLAGS_NEARBY_PLATFORM_FEATURE_FLAGS_H_
@@ -28,50 +29,45 @@ constexpr absl::string_view kConfigPackage = "nearby";
// The Nearby Platform features.
namespace nearby_platform_feature {
// The maximum scanning times for available hotspots.
constexpr auto kWifiHotspotScanMaxRetries =
flags::Flag<int64_t>(kConfigPackage, "45415883", 3);
// The maximum IP check times during Wi-Fi hotspot connection.
constexpr auto kWifiHotspotCheckIpMaxRetries =
flags::Flag<int64_t>(kConfigPackage, "45415884", 20);
// The interval between 2 IP check attempts.
constexpr auto kWifiHotspotCheckIpIntervalMillis =
flags::Flag<int64_t>(kConfigPackage, "45415885", 500);
// The maximum connection times to remote Wi-Fi hotspot.
constexpr auto kWifiHotspotConnectionMaxRetries =
flags::Flag<int64_t>(kConfigPackage, "45415886", 3);
// The interval between 2 connectin attempts.
constexpr auto kWifiHotspotConnectionIntervalMillis =
flags::Flag<int64_t>(kConfigPackage, "45415887", 2000);
// The connection timeout to remote Wi-Fi hotspot.
constexpr auto kWifiHotspotConnectionTimeoutMillis =
flags::Flag<int64_t>(kConfigPackage, "45415888", 10000);
// Enable/Disable use of address candidates for hotspot upgrade in Windows.
constexpr auto kEnableHotspotAddressCandidates =
flags::Flag<bool>(kConfigPackage, "45739567", false);
// Disable/Enable GATT feature in BLE v2.
constexpr auto kEnableBleV2Gatt =
flags::Flag<bool>(kConfigPackage, "45415180", true);
// Disable/Enable GATT feature on devices without BLE extended feature.
constexpr auto kEnableBleV2GattOnNonExtendedDevice =
flags::Flag<bool>(kConfigPackage, "45415267", true);
// Enable/Disable Intel PIe SDK to query/set WIFI feature.
constexpr auto kEnableIntelPieSdk =
flags::Flag<bool>(kConfigPackage, "45428547", false);
// Enable/Disable new Bluetooth refactor
constexpr auto kEnableNewBluetoothRefactor =
flags::Flag<bool>(kConfigPackage, "45615156", false);
// The send buffer size of blocking socket
// Replace std::async with platform thread
constexpr auto kEnablePlatformThreadToNetwork =
flags::Flag<bool>(kConfigPackage, "45412711", true);
// Enable/Disable task scheduler for ScheduledExecutor and timer.
constexpr auto kEnableTaskScheduler =
flags::Flag<bool>(kConfigPackage, "45643835", true);
// Enable/Disable Wi-Fi hotspot native.
constexpr auto kEnableWifiHotspotNative =
flags::Flag<bool>(kConfigPackage, "45667396", true);
// The send buffer size of blocking socket.
constexpr auto kSocketSendBufferSize =
flags::Flag<int64_t>(kConfigPackage, "45673785", 524288);
// Run scheduled executor callback on executor thread.
constexpr auto kRunScheduledExecutorCallbackOnExecutorThread =
flags::Flag<bool>(kConfigPackage, "45686494", false);
// The interval between 2 IP check attempts.
constexpr auto kWifiHotspotCheckIpIntervalMillis =
flags::Flag<int64_t>(kConfigPackage, "45415885", 500);
// The maximum IP check times during Wi-Fi hotspot connection.
constexpr auto kWifiHotspotCheckIpMaxRetries =
flags::Flag<int64_t>(kConfigPackage, "45415884", 10);
// The interval between 2 connectin attempts.
constexpr auto kWifiHotspotConnectionIntervalMillis =
flags::Flag<int64_t>(kConfigPackage, "45415887", 2000);
// The maximum connection times to remote WiFi hotspot.
constexpr auto kWifiHotspotConnectionMaxRetries =
flags::Flag<int64_t>(kConfigPackage, "45415886", 3);
// The connection timeout to remote Wi-Fi hotspot.
constexpr auto kWifiHotspotConnectionTimeoutMillis =
flags::Flag<int64_t>(kConfigPackage, "45415888", 10000);
// The max retry times to scan WiFi hotspots.
constexpr auto kWifiHotspotScanMaxRetries =
flags::Flag<int64_t>(kConfigPackage, "45415883", 3);
} // namespace nearby_platform_feature
} // namespace config_package_nearby
+43 -71
View File
@@ -17,69 +17,6 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test")
licenses(["notice"])
cc_library(
name = "auth_status",
hdrs = ["auth_status.h"],
visibility = [
"//internal/auth:__pkg__",
"//internal/platform/implementation:__subpackages__",
"//location/nearby/cpp/sharing/clients/cpp:__subpackages__",
],
)
cc_library(
name = "account_info",
hdrs = ["account_info.h"],
visibility = [
"//internal/auth:__pkg__",
"//internal/platform/implementation:__subpackages__",
"//location/nearby/cpp/sharing/clients/cpp:__subpackages__",
],
)
cc_library(
name = "account_manager",
hdrs = ["account_manager.h"],
visibility = [
"//internal/account:__pkg__",
"//internal/platform:__pkg__",
"//internal/platform/implementation:__subpackages__",
"//internal/test:__subpackages__",
"//location/nearby/cpp/sharing/clients/cpp:__subpackages__",
"//location/nearby/sharing/lib:__subpackages__",
"//location/nearby/sharing/sdk/quick_share_server:__pkg__",
"//sharing:__subpackages__",
],
deps = [
":account_info",
":signin_attempt",
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings:string_view",
],
)
cc_library(
name = "signin_attempt",
hdrs = ["signin_attempt.h"],
visibility = [
"//internal/account:__pkg__",
"//internal/auth:__pkg__",
"//internal/platform/implementation:__subpackages__",
"//internal/test:__subpackages__",
"//location/nearby/cpp/sharing/clients/cpp:__subpackages__",
"//location/nearby/sharing/sdk/quick_share_server:__pkg__",
"//sharing:__subpackages__",
],
deps = [
":account_info",
":auth_status",
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/strings:string_view",
],
)
cc_library(
name = "types",
hdrs = [
@@ -112,11 +49,12 @@ cc_library(
"//internal/test:__subpackages__",
"//location/nearby/analytics/cpp:__subpackages__",
"//location/nearby/cpp/sharing:__subpackages__",
"//presence:__subpackages__",
"//sharing:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
],
deps = [
"//internal/base:file_path",
"//internal/base:files",
"//internal/crypto_cros",
"//internal/platform:base",
"//internal/platform:mac_address",
@@ -149,6 +87,26 @@ cc_library(
],
)
cc_library(
name = "webrtc_platform",
hdrs = [
"webrtc.h",
"webrtc_platform.h",
],
compatible_with = ["//buildenv/target:non_prod"],
visibility = [
"//:__subpackages__",
],
deps = [
"//connections/implementation/proto:offline_wire_formats_cc_proto",
"//internal/platform:base",
"//third_party/webrtc/files/stable/webrtc/api:peer_connection_interface",
"//third_party/webrtc/files/stable/webrtc/api:scoped_refptr",
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/strings:string_view",
],
)
cc_library(
name = "comm",
hdrs = [
@@ -162,31 +120,31 @@ cc_library(
"http_loader.h",
"psk_info.h",
"upgrade_address_info.h",
"webrtc.h",
"wifi.h",
"wifi_direct.h",
"wifi_hotspot.h",
"wifi_lan.h",
],
copts = ["-DNO_WEBRTC"],
visibility = [
"//connections/implementation:__subpackages__",
"//internal/network:__subpackages__",
"//internal/platform:__pkg__",
"//internal/platform/implementation:__subpackages__",
"//presence:__subpackages__",
"//presence/implementation:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
"//third_party/nearby/presence/implementation:__subpackages__",
],
deps = [
"//connections/implementation/proto:offline_wire_formats_cc_proto",
"//internal/platform:base",
"//internal/platform:cancellation_flag",
"//internal/platform:mac_address",
"//internal/platform:uuid",
"//internal/proto:credential_cc_proto",
"//internal/proto:local_credential_cc_proto",
<<<<<<< HEAD
# "//third_party/webrtc/files/stable/webrtc/api:create_peerconnection_factory", # buildcleaner: keep
# "//third_party/webrtc/files/stable/webrtc/api:peer_connection_interface",
=======
>>>>>>> nearby/main
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/functional:any_invocable",
@@ -195,7 +153,6 @@ cc_library(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:optional",
],
)
@@ -204,7 +161,6 @@ cc_library(
hdrs = [
"platform.h",
],
defines = ["NO_WEBRTC"],
visibility = [
"//connections/implementation:__subpackages__",
"//internal:__subpackages__",
@@ -214,6 +170,7 @@ cc_library(
"//location/nearby/analytics/cpp:__subpackages__",
"//location/nearby/apps/better_together/plugins/preferences_native:__subpackages__",
"//location/nearby/cpp/sharing:__subpackages__",
"//sharing/internal/impl/common:__subpackages__",
],
deps = [
":comm",
@@ -230,6 +187,8 @@ cc_library(
tags = ["keep_dep"], # Prevent build_cleaner from removing the dependency.
visibility = [
"//:__subpackages__",
"//location/nearby/analytics/cpp:__subpackages__",
"//location/nearby/cpp:__subpackages__",
"//location/nearby/sharing/lib:__subpackages__",
],
deps = [
@@ -260,3 +219,16 @@ cc_test(
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "device_info_test",
size = "small",
timeout = "moderate",
srcs = ["device_info_test.cc"],
deps = [
":types",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/strings",
"@com_google_googletest//:gtest_main",
],
)
@@ -1,35 +0,0 @@
// Copyright 2024 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 THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_ACCOUNT_INFO_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_ACCOUNT_INFO_H_
#include <string>
namespace nearby {
// Describes a Nearby account. The account class will have more properties
// and methods in the future based on the new feature added.
struct AccountInfo {
std::string id; // The unique identify of the account.
std::string display_name;
std::string family_name;
std::string given_name;
std::string picture_url;
std::string email;
};
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_ACCOUNT_INFO_H_
@@ -1,89 +0,0 @@
// Copyright 2022 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 PLATFORM_API_ACCOUNT_MANAGER_H_
#define PLATFORM_API_ACCOUNT_MANAGER_H_
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "internal/platform/implementation/account_info.h"
#include "internal/platform/implementation/signin_attempt.h"
namespace nearby {
// AccountManager manages the accounts are used to access Nearby backend.
// In current design, AccountManager only support one active account.
class AccountManager {
public:
using Account = AccountInfo;
// Observes the activity of the account manager.
class Observer {
public:
virtual ~Observer() = default;
virtual void OnLoginSucceeded(absl::string_view account_id) = 0;
// |credential_error| is true if the logout is due to critical auth error.
virtual void OnLogoutSucceeded(absl::string_view account_id,
bool credential_error) = 0;
};
virtual ~AccountManager() = default;
// Gets current active account. If no login user, return std::nullopt.
virtual std::optional<Account> GetCurrentAccount() = 0;
// Initializes the login process for a Google account from an oauth client.
// |client_id| GCP client_id of the client
// |client_secret| GCP client_secret of the client
// Returns a SigninAttempt object that can be used to complete the login
// process.
virtual std::unique_ptr<SigninAttempt> Login(
absl::string_view client_id, absl::string_view client_secret) = 0;
// Logs out current active account. |logout_callback| is called when logout is
// completed.
virtual void Logout(
absl::AnyInvocable<void(absl::Status)> logout_callback) = 0;
// Gets access token for the active account.
// |callback| is called with the access token or error status.
//
// Returns false if callback is null.
virtual bool GetAccessToken(
absl::AnyInvocable<void(absl::StatusOr<std::string>)> callback) = 0;
// Returns a pair containing the client id and client secret used in the most
// recent Login request.
// If no current user is logged in, returns empty string for both.
virtual std::pair<std::string, std::string> GetOAuthClientCredential() = 0;
virtual void AddObserver(Observer* observer) = 0;
virtual void RemoveObserver(Observer* observer) = 0;
virtual void SaveAccountPrefs(absl::string_view user_id,
absl::string_view client_id,
absl::string_view client_secret) = 0;
};
} // namespace nearby
#endif // PLATFORM_API_ACCOUNT_MANAGER_H_
+17 -3
View File
@@ -51,6 +51,18 @@ objc_library(
],
)
objc_library(
name = "apple_webrtc",
srcs = [
"webrtc_platform.mm",
],
deps = [
"//connections/implementation/mediums/webrtc:webrtc_medium_impl",
"//internal/platform/implementation:webrtc_platform",
"//third_party/apple_frameworks:Foundation",
],
)
objc_library(
name = "apple",
srcs = [
@@ -61,7 +73,6 @@ objc_library(
"preferences_manager.mm",
"scheduled_executor.mm",
"timer.mm",
"webrtc.mm",
"wifi_hotspot.mm",
"wifi_lan.mm",
],
@@ -70,7 +81,6 @@ objc_library(
"device_info.h",
"preferences_manager.h",
"timer.h",
"webrtc.h",
"wifi.h",
"wifi_hotspot.h",
"wifi_lan.h",
@@ -102,6 +112,7 @@ objc_library(
"//internal/base:file_path",
"//internal/base:files",
"//internal/base:masker",
"//internal/platform/implementation/apple/Flags",
"//internal/platform/implementation/apple/Mediums/Hotspot",
"//internal/account",
"//internal/crypto_cros",
@@ -109,10 +120,13 @@ objc_library(
"//internal/platform:logging",
"//internal/platform:types",
"//internal/proto:tachyon_cc_proto",
<<<<<<< HEAD
"//third_party/webrtc/files/stable/webrtc/api/task_queue:default_task_queue_factory",
# "//third_party/webrtc/files/stable/webrtc/api:create_modular_peer_connection_factory",
# "//third_party/webrtc/files/stable/webrtc/api:peer_connection_interface",
"//third_party/webrtc/files/stable/webrtc/rtc_base:checks",
=======
>>>>>>> nearby/main
"//internal/platform:base",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
@@ -345,11 +359,11 @@ cc_test(
deps = [
":Platform_cc",
"//internal/platform/implementation/g3:crypto",
"//third_party/gloop/thread/fiber",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_googletest//:gtest_main",
"@com_google_nisaba//nisaba/port:thread_pool/fiber",
],
)
@@ -32,4 +32,13 @@
/** Checks whether shared peripheral manager is enabled in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL sharedPeripheralManagerEnabled;
<<<<<<< HEAD
=======
/** Checks whether single copy read/write is enabled in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL singleCopyEnabled;
/** Checks whether BLE server socket deadlock is fixed in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL fixBleServerSocketDeadlockEnabled;
>>>>>>> nearby/main
@end
@@ -48,4 +48,18 @@
kEnableSharedPeripheralManager);
}
<<<<<<< HEAD
=======
+ (BOOL)singleCopyEnabled {
return nearby::NearbyFlags::GetInstance().GetBoolFlag(
nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy);
}
+ (BOOL)fixBleServerSocketDeadlockEnabled {
return nearby::NearbyFlags::GetInstance().GetBoolFlag(
nearby::connections::config_package_nearby::nearby_connections_feature::
kFixBleServerSocketDeadlock);
}
>>>>>>> nearby/main
@end
@@ -39,6 +39,7 @@ objc_library(
"GNCMConnection.m",
"GNCPeripheral.m",
"GNCPeripheralManager.m",
"GNCPeripheralManagerMultiplexer.m",
"NSData+GNCBase85.mm",
"NSData+GNCWebSafeBase64.m",
],
@@ -59,6 +60,7 @@ objc_library(
"GNCMConnection.h",
"GNCPeripheral.h",
"GNCPeripheralManager.h",
"GNCPeripheralManagerMultiplexer.h",
"NSData+GNCBase85.h",
"NSData+GNCWebSafeBase64.h",
],
@@ -64,6 +64,7 @@ static const int kMaxAdvertisementLengthOnIOS = 23;
self = [super init];
if (self) {
_queue = queue ?: dispatch_queue_create(kGNCBLEGATTServerQueueLabel, DISPATCH_QUEUE_SERIAL);
<<<<<<< HEAD
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
if (!peripheralManager) {
// In shared mode, the peripheral manager must be injected.
@@ -90,6 +91,34 @@ static const int kMaxAdvertisementLengthOnIOS = 23;
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
_characteristicValues = [[NSMutableDictionary alloc] init];
_advertisementData = nil;
=======
_services = [[NSMutableDictionary alloc] init];
_pendingCharacteristics = [[NSMutableDictionary alloc] init];
_characteristicValues = [[NSMutableDictionary alloc] init];
_advertisementData = nil;
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
if (!peripheralManager) {
// In shared mode, the peripheral manager must be injected.
[NSException raise:NSInvalidArgumentException
format:@"Peripheral manager cannot be nil when shared manager is enabled."];
}
_peripheralManager = peripheralManager;
// In shared mode, do NOT set the delegate. The Multiplexer handles callbacks.
} else {
// Legacy mode: Create a new manager if one isn't provided.
if (!peripheralManager) {
peripheralManager = [[CBPeripheralManager alloc]
initWithDelegate:nil
queue:_queue
options:@{CBPeripheralManagerOptionShowPowerAlertKey : @NO}];
}
_peripheralManager = peripheralManager;
// In legacy mode, we own the manager (or use the injected one as if we own it) and set the
// delegate.
_peripheralManager.peripheralDelegate = self;
}
>>>>>>> nearby/main
}
return self;
}
@@ -107,6 +107,10 @@ static char *const kGNCBLEL2CAPServerQueueLabel = "com.google.nearby.GNCBLEL2CAP
}
}
if (_PSM > 0) {
GNCLoggerInfo((@"[NEARBY] Unpublish L2CAP channel with PSM: %@"), @(_PSM));
[_peripheralManager unpublishL2CAPChannel:_PSM];
}
if (_peripheralManager.state == CBManagerStatePoweredOn) {
// Bluetooth link is already encrypted, however encryption is not required here to avoid getting
// insufficient authentication errors due to initialization order.
@@ -106,15 +106,6 @@ typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable c
*/
- (instancetype)init;
/**
* Initializes the BLE medium with a custom central manager.
*
* @param centralManager The central manager to use for BLE operations.
* @param queue The queue to use for all internal operations.
*/
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
queue:(nullable dispatch_queue_t)queue;
/** The hardware supports BOTH advertising extensions and extended scans. */
@property(nonatomic, readonly) BOOL supportsExtendedAdvertisements;
@@ -22,10 +22,10 @@
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEError.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPClient.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPServer.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCCentralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManagerMultiplexer.h"
#import "internal/platform/implementation/apple/Mediums/BLE/NSData+GNCBase85.h"
#import "internal/platform/implementation/apple/Mediums/BLE/NSData+GNCWebSafeBase64.h"
@@ -41,11 +41,16 @@ static NSError *AlreadyScanningError() {
}
@interface GNCBLEMedium () <GNCCentralManagerDelegate, CBCentralManagerDelegate>
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
peripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(dispatch_queue_t)queue;
@end
@implementation GNCBLEMedium {
dispatch_queue_t _queue;
id<GNCCentralManager> _centralManager;
id<GNCPeripheralManager> _peripheralManager;
GNCPeripheralManagerMultiplexer *_multiplexer;
// The active GATT server, or @nil if one hasn't been started yet.
GNCBLEGATTServer *_server;
@@ -89,21 +94,31 @@ static NSError *AlreadyScanningError() {
- (instancetype)init {
dispatch_queue_t queue = dispatch_queue_create(kBLEMediumQueueLabel, DISPATCH_QUEUE_SERIAL);
CBCentralManager *centralManager =
[[CBCentralManager alloc] initWithDelegate:self
[[CBCentralManager alloc] initWithDelegate:nil
queue:queue
options:@{CBCentralManagerOptionShowPowerAlertKey : @NO}];
return [self initWithCentralManager:centralManager queue:queue];
CBPeripheralManager *peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:nil
queue:queue];
return [self initWithCentralManager:centralManager
peripheralManager:peripheralManager
queue:queue];
}
// This is private and should only be used for tests. The provided central manager must call
// delegate methods on the main queue.
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
queue:(nullable dispatch_queue_t)queue {
peripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(dispatch_queue_t)queue {
self = [super init];
if (self) {
_queue = queue ?: dispatch_get_main_queue();
_queue = queue;
_centralManager = centralManager;
_centralManager.centralDelegate = self;
_peripheralManager = peripheralManager;
if (GNCFeatureFlags.sharedPeripheralManagerEnabled && _peripheralManager) {
_multiplexer = [[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:_queue];
_peripheralManager.peripheralDelegate = _multiplexer;
}
_gattConnectionCompletionHandlers = [NSMutableDictionary dictionary];
_gattDisconnectionHandlers = [NSMutableDictionary dictionary];
_scanningServiceUUIDs = [NSMutableArray array];
@@ -150,15 +165,29 @@ static NSError *AlreadyScanningError() {
return NO;
}
- (void)dealloc {
[_centralManager stopScan];
_centralManager.centralDelegate = nil;
[_peripheralManager stopAdvertising];
_peripheralManager.peripheralDelegate = nil;
}
- (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)serviceData
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler {
dispatch_async(_queue, ^{
if (!_server) {
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
<<<<<<< HEAD
// TODO (edwinwu): Implement shared peripheral manager.
// For now, raise an exception.
[NSException raise:NSInvalidArgumentException
format:@"Not implemented for shared manager is enabled."];
=======
_server = [[GNCBLEGATTServer alloc] initWithPeripheralManager:_peripheralManager
queue:_queue];
[_multiplexer addListener:_server];
>>>>>>> nearby/main
} else {
// In legacy mode, we pass nil (or a separate manager) and do NOT add to multiplexer.
// GNCBLEGATTServer will create its own internal manager.
@@ -205,7 +234,7 @@ static NSError *AlreadyScanningError() {
[_scanningServiceUUIDs addObjectsFromArray:serviceUUIDs];
_advertisementFoundHandler = advertisementFoundHandler;
[self internalStartScanningIfPoweredOn];
[self updateScanningState];
if (completionHandler) {
completionHandler(nil);
}
@@ -226,7 +255,7 @@ static NSError *AlreadyScanningError() {
- (void)resumeMediumScanning:(nullable GNCStartScanningCompletionHandler)completionHandler {
dispatch_async(_queue, ^{
[self internalStartScanningIfPoweredOn];
[self updateScanningState];
if (completionHandler) {
completionHandler(nil);
}
@@ -238,10 +267,16 @@ static NSError *AlreadyScanningError() {
dispatch_async(_queue, ^{
if (!_server) {
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
<<<<<<< HEAD
// TODO (edwinwu): Implement shared peripheral manager.
// For now, raise an exception.
[NSException raise:NSInvalidArgumentException
format:@"Not implemented for shared manager is enabled."];
=======
_server = [[GNCBLEGATTServer alloc] initWithPeripheralManager:_peripheralManager
queue:_queue];
[_multiplexer addListener:_server];
>>>>>>> nearby/main
} else {
_server = [[GNCBLEGATTServer alloc] initWithPeripheralManager:nil queue:nil];
}
@@ -291,10 +326,22 @@ static NSError *AlreadyScanningError() {
dispatch_async(_queue, ^{
if (!_l2capServer) {
if (GNCFeatureFlags.sharedPeripheralManagerEnabled) {
<<<<<<< HEAD
// TODO (edwinwu): Implement shared peripheral manager.
// For now, raise an exception.
[NSException raise:NSInvalidArgumentException
format:@"Not implemented for shared manager is enabled."];
=======
_l2capServer = [[GNCBLEL2CAPServer alloc]
initWithPeripheralManager:peripheralManager ?: _peripheralManager
queue:peripheralManager ? dispatch_get_main_queue() : _queue];
// Only add to multiplexer if we are using the internal shared manager.
// If a specific manager was passed in (e.g. for testing?), we might still need logic here.
// But typically `peripheralManager` is nil in prod.
if (peripheralManager == nil || peripheralManager == _peripheralManager) {
[_multiplexer addListener:_l2capServer];
}
>>>>>>> nearby/main
} else {
// Legacy mode
_l2capServer = [[GNCBLEL2CAPServer alloc]
@@ -351,7 +398,7 @@ static NSError *AlreadyScanningError() {
#pragma mark - Internal
- (void)internalStartScanningIfPoweredOn {
- (void)updateScanningState {
dispatch_assert_queue(_queue);
// Scanning can only be done when powered on and must be restarted if bluetooth is turned off
// then back on. This will be called anytime the central manager's state changes, so
@@ -479,7 +526,7 @@ static NSError *AlreadyScanningError() {
return;
}
dispatch_assert_queue(_queue);
[self internalStartScanningIfPoweredOn];
[self updateScanningState];
}
- (void)gnc_centralManager:(id<GNCCentralManager>)central
@@ -496,6 +543,7 @@ static NSError *AlreadyScanningError() {
didConnectPeripheral:(id<GNCPeripheral>)peripheral {
dispatch_assert_queue(_queue);
[self cancelConnectionTimeout];
if (_l2capPSM > 0) {
[self internalOpenL2CAPChannel:peripheral];
return;
@@ -541,6 +589,7 @@ static NSError *AlreadyScanningError() {
didDisconnectPeripheral:(id<GNCPeripheral>)peripheral
error:(nullable NSError *)error {
dispatch_assert_queue(_queue);
GNCGATTDisconnectionHandler handler = _gattDisconnectionHandlers[peripheral.identifier];
_gattDisconnectionHandlers[peripheral.identifier] = nil;
if (handler) {
@@ -22,8 +22,10 @@ NS_ASSUME_NONNULL_BEGIN
@implementation CBCentralManager (GNCCentralManagerAdditions)
- (void)setCentralDelegate:(nullable id<GNCCentralManagerDelegate>)centralDelegate {
NSAssert([centralDelegate conformsToProtocol:@protocol(CBCentralManagerDelegate)],
@"centralDelegate must conform to protocol CBCentralManagerDelegate");
if (centralDelegate) {
NSAssert([centralDelegate conformsToProtocol:@protocol(CBCentralManagerDelegate)],
@"centralDelegate must conform to protocol CBCentralManagerDelegate");
}
self.delegate = (id<CBCentralManagerDelegate>)centralDelegate;
}
@@ -90,9 +90,11 @@ NSData *_Nullable GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommand command, NSData
/**
* Calls the completion handler with (a) YES if the GNSSocket connected, or (b) NO if it failed to
* connect for any reason. The completion handler is called on the main queue.
* connect for any reason. The completion handler is called on the given queue. If the queue is nil,
* the completion handler is called on the main queue.
*/
void GNCMWaitForConnection(GNSSocket *socket, GNCMBoolHandler completion);
void GNCMWaitForConnection(GNSSocket *socket, dispatch_queue_t _Nullable queue,
GNCMBoolHandler completion);
#ifdef __cplusplus
} // extern "C"
@@ -235,18 +235,20 @@ NSData *_Nullable GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommand command, NSData
@end
void GNCMWaitForConnection(GNSSocket *socket, GNCMBoolHandler completion) {
void GNCMWaitForConnection(GNSSocket *socket, dispatch_queue_t _Nullable queue,
GNCMBoolHandler completion) {
// This function passes YES to the completion when the socket has successfully connected, and
// otherwise passes NO to the completion after a timeout of several seconds. We shouldn't retain
// the completion after it's been called, so store it in a __block variable and nil it out once
// the socket has connected.
__block GNCMBoolHandler completionRef = completion;
dispatch_queue_t targetQueue = queue ?: dispatch_get_main_queue();
// The delegate listens for the socket connection callbacks. It's retained by the block passed to
// dispatch_after below, so it will live long enough to do its job.
GNCMBleSocketDelegate *delegate =
[GNCMBleSocketDelegate delegateWithConnectedHandler:^(BOOL didConnect) {
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_async(targetQueue, ^{
if (completionRef) completionRef(didConnect);
completionRef = nil;
});
@@ -254,9 +256,10 @@ void GNCMWaitForConnection(GNSSocket *socket, GNCMBoolHandler completion) {
socket.delegate = delegate;
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kBleSocketConnectionTimeout * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
targetQueue, ^{
(void)delegate; // make sure it's retained until the timeout
if (completionRef) completionRef(NO);
completionRef = nil;
});
}
@@ -0,0 +1,49 @@
// Copyright 2026 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/Mediums/BLE/GNCPeripheralManager.h"
NS_ASSUME_NONNULL_BEGIN
/**
* A multiplexer that forwards @c CBPeripheralManagerDelegate and @c GNCPeripheralManagerDelegate
*/
@interface GNCPeripheralManagerMultiplexer : NSObject <GNCPeripheralManagerDelegate>
/**
* Initializes the multiplexer.
*
* @param callbackQueue The queue to use for forwarding delegate callbacks.
*/
- (instancetype)initWithCallbackQueue:(dispatch_queue_t)callbackQueue NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
/**
* Adds a listener to the multiplexer. Listeners are held weakly.
*
* @param listener The listener to add.
*/
- (void)addListener:(id<GNCPeripheralManagerDelegate>)listener;
/**
* Removes a listener from the multiplexer.
*
* @param listener The listener to remove.
*/
- (void)removeListener:(id<GNCPeripheralManagerDelegate>)listener;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,194 @@
// Copyright 2026 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/Mediums/BLE/GNCPeripheralManagerMultiplexer.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"
NS_ASSUME_NONNULL_BEGIN
@implementation GNCPeripheralManagerMultiplexer {
NSHashTable<id<GNCPeripheralManagerDelegate>> *_listeners;
dispatch_queue_t _callbackQueue;
dispatch_queue_t _syncQueue;
}
- (instancetype)initWithCallbackQueue:(dispatch_queue_t)callbackQueue {
self = [super init];
if (self) {
_listeners = [NSHashTable weakObjectsHashTable];
_callbackQueue = callbackQueue;
_syncQueue = dispatch_queue_create("com.google.nearby.GNCPeripheralManagerMultiplexerSync",
DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (instancetype)init {
return [self initWithCallbackQueue:dispatch_get_main_queue()];
}
- (void)addListener:(id<GNCPeripheralManagerDelegate>)listener {
dispatch_async(_syncQueue, ^{
[self->_listeners addObject:listener];
});
}
- (void)removeListener:(id<GNCPeripheralManagerDelegate>)listener {
dispatch_async(_syncQueue, ^{
[self->_listeners removeObject:listener];
});
}
- (NSArray<id<GNCPeripheralManagerDelegate>> *)allListeners {
__block NSArray<id<GNCPeripheralManagerDelegate>> *listeners;
dispatch_sync(_syncQueue, ^{
listeners = [self->_listeners allObjects];
});
return listeners;
}
#pragma mark - GNCPeripheralManagerDelegate
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
dispatch_async(_callbackQueue, ^{
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
[listener gnc_peripheralManagerDidUpdateState:peripheral];
}
});
}
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
error:(nullable NSError *)error {
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
dispatch_async(_callbackQueue, ^{
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
if ([listener respondsToSelector:@selector(gnc_peripheralManagerDidStartAdvertising:error:)]) {
[listener gnc_peripheralManagerDidStartAdvertising:peripheral error:error];
}
}
});
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didAddService:(CBService *)service
error:(nullable NSError *)error {
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
dispatch_async(_callbackQueue, ^{
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didAddService:error:)]) {
[listener gnc_peripheralManager:peripheral didAddService:service error:error];
}
}
});
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
dispatch_async(_callbackQueue, ^{
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didReceiveReadRequest:)]) {
[listener gnc_peripheralManager:peripheral didReceiveReadRequest:request];
}
}
});
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
dispatch_async(_callbackQueue, ^{
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didPublishL2CAPChannel:error:)]) {
[listener gnc_peripheralManager:peripheral didPublishL2CAPChannel:PSM error:error];
}
}
});
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(NSError *)error {
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
dispatch_async(_callbackQueue, ^{
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didUnpublishL2CAPChannel:error:)]) {
[listener gnc_peripheralManager:peripheral didUnpublishL2CAPChannel:PSM error:error];
}
}
});
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
error:(nullable NSError *)error {
NSArray<id<GNCPeripheralManagerDelegate>> *listeners = [self allListeners];
dispatch_async(_callbackQueue, ^{
for (id<GNCPeripheralManagerDelegate> listener in listeners) {
if ([listener respondsToSelector:@selector(gnc_peripheralManager:didOpenL2CAPChannel:error:)]) {
[listener gnc_peripheralManager:peripheral didOpenL2CAPChannel:channel error:error];
}
}
});
}
#pragma mark - CBPeripheralManagerDelegate
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
[self gnc_peripheralManagerDidUpdateState:peripheral];
}
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(nullable NSError *)error {
[self gnc_peripheralManagerDidStartAdvertising:peripheral error:error];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(nullable NSError *)error {
[self gnc_peripheralManager:peripheral didAddService:service error:error];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
[self gnc_peripheralManager:peripheral didReceiveReadRequest:request];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
[self gnc_peripheralManager:peripheral didPublishL2CAPChannel:PSM error:error];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
[self gnc_peripheralManager:peripheral didUnpublishL2CAPChannel:PSM error:error];
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
error:(nullable NSError *)error {
[self gnc_peripheralManager:peripheral didOpenL2CAPChannel:channel error:error];
}
@end
NS_ASSUME_NONNULL_END
@@ -30,9 +30,6 @@ objc_library(
]) + [
"Source/GNSCentral.h",
],
copts = [
"-Wno-enum-compare", # TODO(b/418286948): Remove this when the error is fixed.
],
deps = [
":Shared",
"//internal/platform/implementation/apple/Log:GNCLogger",
@@ -51,9 +48,6 @@ objc_library(
]) + [
"Source/GNSPeripheral.h",
],
copts = [
"-Wno-enum-compare", # TODO(b/418286948): Remove this when the error is fixed.
],
deps = [
":Shared",
"//internal/platform/implementation/apple/Log:GNCLogger",
@@ -371,7 +371,7 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
}
- (void)updateBTCrashLoopHeuristic {
NSAssert(_cbPeripheralManager.state == CBCentralManagerStateResetting, @"Unexpected CB state %@",
NSAssert(_cbPeripheralManager.state == CBManagerStateResetting, @"Unexpected CB state %@",
CBManagerStateString(_cbPeripheralManager.state));
NSDate *now = [NSDate date];
if ([now timeIntervalSinceDate:_btCrashLastResettingDate] >
@@ -32,7 +32,7 @@ objc_library(
"GNCBLEL2CAPFakeInputOutputStream.m",
"GNCBLEL2CAPServerTest.mm",
"GNCBLEL2CAPStreamTest.m",
"GNCBLEMediumTest.m",
"GNCBLEMediumTest.mm",
"GNCFakeBLEGATTServer.m",
"GNCFakeBLEMedium.m",
"GNCFakeCBL2CAPChannel.m",
@@ -44,6 +44,7 @@ objc_library(
"GNCMBleUtilsTest.m",
"GNCMConnectionsTest.m",
"GNCMFakeConnection.mm",
"GNCPeripheralManagerMultiplexerTest.m",
"GNCPeripheralManagerTest.m",
"GNCPeripheralTest.m",
"NSData+GNCBase85Test.m",
@@ -24,16 +24,19 @@ NS_ASSUME_NONNULL_BEGIN
@interface GNCBLEMedium (Testing)
/**
* Creates a BLE Medium with a provided central manager.
* Creates a BLE Medium with a provided central manager and peripheral manager.
*
* This is only exposed for testing and can be used to inject a fake central manager.
* This is only exposed for tests and can be used to inject a fake central manager and
* peripheral manager.
*
* @param centralManager The central manager instance.
* @param peripheralManager The peripheral manager instance.
* @param queue The queue to run on, this must match the queue that the central manager's delegate
* is running on. Defaults to the main queue when @c nil.
* is running on.
*/
- (instancetype)initWithCentralManager:(id<GNCCentralManager>)centralManager
queue:(nullable dispatch_queue_t)queue;
peripheralManager:(nullable id<GNCPeripheralManager>)peripheralManager
queue:(dispatch_queue_t)queue;
- (NSDictionary<CBUUID *, NSData *> *)decodeAdvertisementData:
(NSDictionary<NSString *, id> *)advertisementData;
@@ -1,404 +0,0 @@
// 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/Mediums/BLE/GNCBLEMedium.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPClient+Testing.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEMedium+Testing.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeCentralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB";
@interface GNCBLEMediumTest : XCTestCase
@end
@implementation GNCBLEMediumTest
#pragma mark - Supports Extended Advertisements
- (void)testSupportsExtendedAdvertisements {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTAssertFalse([medium supportsExtendedAdvertisements]);
}
#pragma mark - Start Scanning
- (void)testStartScanning {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *startScanningExpectation =
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
XCTestExpectation *advertisementFoundExpectation =
[[XCTestExpectation alloc] initWithDescription:@"Advertisement found."];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
[fakeCentralManager simulateCentralManagerDidUpdateState:CBManagerStatePoweredOn];
[medium startScanningForService:serviceUUID
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
NSDictionary<CBUUID *, NSData *> *expected = @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
};
XCTAssertEqualObjects(expected, data);
[advertisementFoundExpectation fulfill];
}
completionHandler:^(NSError *error) {
XCTAssertNil(error);
[startScanningExpectation fulfill];
}];
[self waitForExpectations:@[ startScanningExpectation ] timeout:3];
XCTAssertEqualObjects(@[ serviceUUID ], fakeCentralManager.serviceUUIDs);
[fakeCentralManager
simulateCentralManagerDidDiscoverPeripheral:[[GNCFakePeripheral alloc] init]
advertisementData:@{
CBAdvertisementDataLocalNameKey : @"dGVzdA",
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
}];
[self waitForExpectations:@[ advertisementFoundExpectation ] timeout:3];
}
- (void)testAlreadyScanning {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
}
completionHandler:nil];
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
}
completionHandler:^(NSError *error) {
XCTAssertNotNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
- (void)testStartStopStartScanning {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
[medium startScanningForService:serviceUUID
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
}
completionHandler:^(NSError *error) {
XCTAssertNil(error);
[medium stopScanningWithCompletionHandler:^(NSError *error) {
XCTAssertNil(error);
[medium startScanningForService:serviceUUID
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
}
completionHandler:^(NSError *error) {
XCTAssertNil(error);
[expectation fulfill];
}];
}];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
#pragma mark - Decode Advertisement Data
- (void)testDecodeAndroidStyleAdvertisementData {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
NSDictionary<CBUUID *, NSData *> *expected = @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
};
NSDictionary<NSString *, id> *data = @{
CBAdvertisementDataServiceDataKey : @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
},
};
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
XCTAssertEqualObjects(expected, actual);
}
- (void)testDecodeAndroidStyleAdvertisementDataWithLocalName {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
NSDictionary<CBUUID *, NSData *> *expected = @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
};
NSDictionary<NSString *, id> *data = @{
CBAdvertisementDataLocalNameKey : @"Nearby", // Just happens to be base64 decodable.
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
CBAdvertisementDataServiceDataKey : @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
},
};
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
XCTAssertEqualObjects(expected, actual);
}
- (void)testDecodeAppleStyleAdvertisementData {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
NSDictionary<CBUUID *, NSData *> *expected = @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
};
NSDictionary<NSString *, id> *data = @{
CBAdvertisementDataLocalNameKey : @"dGVzdA",
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
};
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
XCTAssertEqualObjects(expected, actual);
}
- (void)testDecodeInvalidAdvertisementData {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
NSDictionary<NSString *, id> *data = @{
CBAdvertisementDataLocalNameKey : @"!@#$",
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
};
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
XCTAssertEqualObjects(@{}, actual);
}
- (void)testDecodeEmptyAdvertisementData {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:@{}];
XCTAssertEqualObjects(@{}, actual);
}
#pragma mark - Start GATT Server
- (void)testStartGATTServer {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Start GATT server."];
[medium startGATTServerWithCompletionHandler:^(GNCBLEGATTServer *server, NSError *error) {
XCTAssertNotNil(server);
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
#pragma mark - Start Advertising
- (void)testStartAdvertising {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
// Start advertising is fully covered with @c GNCBLEGATTServer tests. We are passing invalid
// advertising data here so we can test code paths relevant to @c GNCBLEMedium, but bail early
// enough to avoid making actual CoreBluetooth calls.
[medium startAdvertisingData:@{}
completionHandler:^(NSError *error) {
XCTAssertNotNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
- (void)testStopAdvertising {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Stop advertising."];
// Stop advertising is fully covered with @c GNCBLEGATTServer tests. We are only testing stopping
// without having started which tests the code paths relevant to @c GNCBLEMedium.
[medium stopAdvertisingWithCompletionHandler:^(NSError *error) {
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
#pragma mark - Open L2CAP Channel
- (void)testOpenL2CAPServerSocket {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *psmPublishedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"PSM published."];
XCTestExpectation *channelOpenedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
// Open L2CAP server is fully covered with @c GNCBLEL2CAPServer tests.
[medium
openL2CAPServerWithPSMPublishedCompletionHandler:^(uint16_t PSM, NSError *error) {
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
XCTAssertNil(error);
[psmPublishedexpectation fulfill];
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
XCTAssertNil(error);
[channelOpenedexpectation fulfill];
}
peripheralManager:fakePeripheralManager];
[self waitForExpectations:@[ psmPublishedexpectation ] timeout:0.1];
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:0.5];
}
- (void)testSuccessfulOpenL2CAPChannel {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Open L2CAP channel."];
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
GNCBLEL2CAPClient *l2capClient =
[[GNCBLEL2CAPClient alloc] initWithQueue:nil
requestDisconnectionHandler:^(id<GNCPeripheral> _Nonnull peripheral){
}];
[medium setL2CAPClient:l2capClient];
[medium openL2CAPChannelWithPSM:123
peripheral:fakePeripheral
completionHandler:^(GNCBLEL2CAPStream *_Nullable stream, NSError *_Nullable error) {
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
#pragma mark - Connect
- (void)testSuccessfulConnect {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
disconnectionHandler:nil
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
XCTAssertNotNil(client);
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
- (void)testFailedConnect {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
fakeCentralManager.didFailToConnectPeripheralError = [NSError errorWithDomain:@"fake"
code:0
userInfo:nil];
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
disconnectionHandler:nil
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
XCTAssertNil(client);
XCTAssertNotNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
- (void)testDisconnect {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTestExpectation *disconnectExpectation =
[[XCTestExpectation alloc] initWithDescription:@"Disconnect."];
GNCFakePeripheral *peripheral = [[GNCFakePeripheral alloc] init];
[medium connectToGATTServerForPeripheral:peripheral
disconnectionHandler:^() {
[disconnectExpectation fulfill];
}
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
XCTAssertNotNil(client);
XCTAssertNil(error);
[client disconnect];
}];
[self waitForExpectations:@[ disconnectExpectation ] timeout:3];
}
- (void)testRetrievePeripheralWithIdentifier_exists {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTAssertNotNil(
[medium retrievePeripheralWithIdentifier:
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111111"]]);
}
- (void)testRetrievePeripheralWithIdentifier_doesNotExist {
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager queue:nil];
XCTAssertNil(
[medium retrievePeripheralWithIdentifier:
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111112"]]);
}
@end
@@ -0,0 +1,621 @@
// 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/Mediums/BLE/GNCBLEMedium.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPClient+Testing.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEMedium+Testing.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeCentralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
#include "connections/implementation/flags/nearby_connections_feature_flags.h"
#include "internal/flags/nearby_flags.h"
static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB";
@interface GNCBLEMediumTest : XCTestCase
@end
@implementation GNCBLEMediumTest
- (void)tearDown {
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
[super tearDown];
}
- (void)testInit_allocatesMultiplexerWhenFlagEnabled {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
id multiplexer = [medium valueForKey:@"_multiplexer"];
if (enabled.boolValue) {
XCTAssertNotNil(multiplexer);
XCTAssertEqual(fakePeripheralManager.peripheralDelegate, multiplexer);
} else {
XCTAssertNil(multiplexer);
XCTAssertNil(fakePeripheralManager.peripheralDelegate);
}
}
}
#pragma mark - Supports Extended Advertisements
- (void)testSupportsExtendedAdvertisements {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTAssertFalse([medium supportsExtendedAdvertisements]);
}
}
#pragma mark - Start Scanning
- (void)testStartScanning {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *startScanningExpectation =
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
XCTestExpectation *advertisementFoundExpectation =
[[XCTestExpectation alloc] initWithDescription:@"Advertisement found."];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
[fakeCentralManager simulateCentralManagerDidUpdateState:CBManagerStatePoweredOn];
[medium startScanningForService:serviceUUID
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
NSDictionary<CBUUID *, NSData *> *expected = @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
};
XCTAssertEqualObjects(expected, data);
[advertisementFoundExpectation fulfill];
}
completionHandler:^(NSError *error) {
XCTAssertNil(error);
[startScanningExpectation fulfill];
}];
[self waitForExpectations:@[ startScanningExpectation ] timeout:3];
XCTAssertEqualObjects(@[ serviceUUID ], fakeCentralManager.serviceUUIDs);
[fakeCentralManager
simulateCentralManagerDidDiscoverPeripheral:[[GNCFakePeripheral alloc] init]
advertisementData:@{
CBAdvertisementDataLocalNameKey : @"dGVzdA",
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
}];
[self waitForExpectations:@[ advertisementFoundExpectation ] timeout:3];
}
}
- (void)testAlreadyScanning {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
}
completionHandler:nil];
[medium startScanningForService:[CBUUID UUIDWithString:kServiceUUID]
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
}
completionHandler:^(NSError *error) {
XCTAssertNotNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
}
- (void)testStartStopStartScanning {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Start scanning."];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
[medium startScanningForService:serviceUUID
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
}
completionHandler:^(NSError *error) {
XCTAssertNil(error);
[medium stopScanningWithCompletionHandler:^(NSError *error) {
XCTAssertNil(error);
[medium startScanningForService:serviceUUID
advertisementFoundHandler:^(id<GNCPeripheral> peripheral,
NSDictionary<CBUUID *, NSData *> *data) {
}
completionHandler:^(NSError *error) {
XCTAssertNil(error);
[expectation fulfill];
}];
}];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
}
#pragma mark - Decode Advertisement Data
- (void)testDecodeAndroidStyleAdvertisementData {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
NSDictionary<CBUUID *, NSData *> *expected = @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
};
NSDictionary<NSString *, id> *data = @{
CBAdvertisementDataServiceDataKey : @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
},
};
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
XCTAssertEqualObjects(expected, actual);
}
}
- (void)testDecodeAndroidStyleAdvertisementDataWithLocalName {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
NSDictionary<CBUUID *, NSData *> *expected = @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
};
NSDictionary<NSString *, id> *data = @{
CBAdvertisementDataLocalNameKey : @"Nearby", // Just happens to be base64 decodable.
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
CBAdvertisementDataServiceDataKey : @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
},
};
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
XCTAssertEqualObjects(expected, actual);
}
}
- (void)testDecodeAppleStyleAdvertisementData {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
NSDictionary<CBUUID *, NSData *> *expected = @{
serviceUUID : [@"test" dataUsingEncoding:NSUTF8StringEncoding],
};
NSDictionary<NSString *, id> *data = @{
CBAdvertisementDataLocalNameKey : @"dGVzdA",
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
};
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
XCTAssertEqualObjects(expected, actual);
}
}
- (void)testDecodeInvalidAdvertisementData {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
NSDictionary<NSString *, id> *data = @{
CBAdvertisementDataLocalNameKey : @"!@#$",
CBAdvertisementDataServiceUUIDsKey : @[ serviceUUID ],
};
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:data];
XCTAssertEqualObjects(@{}, actual);
}
}
- (void)testDecodeEmptyAdvertisementData {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
NSDictionary<CBUUID *, NSData *> *actual = [medium decodeAdvertisementData:@{}];
XCTAssertEqualObjects(@{}, actual);
}
}
#pragma mark - Start GATT Server
- (void)testStartGATTServer {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Start GATT server."];
[medium startGATTServerWithCompletionHandler:^(GNCBLEGATTServer *server, NSError *error) {
XCTAssertNotNil(server);
XCTAssertNil(error);
// Verify internal structure based on flag
id mediumManager = [medium valueForKey:@"_peripheralManager"];
id serverManager = [server valueForKey:@"_peripheralManager"];
if (enabled.boolValue) {
XCTAssertEqual(mediumManager, serverManager);
id multiplexer = [medium valueForKey:@"_multiplexer"];
XCTAssertEqual([mediumManager peripheralDelegate], multiplexer);
} else {
XCTAssertNotEqual(mediumManager, serverManager);
id manager = [server valueForKey:@"_peripheralManager"];
XCTAssertEqual([manager peripheralDelegate], server);
}
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
}
#pragma mark - Start Advertising
- (void)testStartAdvertising {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Start advertising."];
// Start advertising is fully covered with @c GNCBLEGATTServer tests. We are passing invalid
// advertising data here so we can test code paths relevant to @c GNCBLEMedium, but bail early
// enough to avoid making actual CoreBluetooth calls.
[medium startAdvertisingData:@{}
completionHandler:^(NSError *error) {
XCTAssertNotNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
}
- (void)testStopAdvertising {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Stop advertising."];
// Stop advertising is fully covered with @c GNCBLEGATTServer tests. We are only testing stopping
// without having started which tests the code paths relevant to @c GNCBLEMedium.
[medium stopAdvertisingWithCompletionHandler:^(NSError *error) {
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
}
#pragma mark - Open L2CAP Channel
- (void)testOpenL2CAPServerSocket {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *psmPublishedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"PSM published."];
XCTestExpectation *channelOpenedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
// Open L2CAP server is fully covered with @c GNCBLEL2CAPServer tests.
[medium
openL2CAPServerWithPSMPublishedCompletionHandler:^(uint16_t PSM, NSError *error) {
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
XCTAssertNil(error);
[psmPublishedexpectation fulfill];
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
XCTAssertNil(error);
[channelOpenedexpectation fulfill];
}
peripheralManager:fakePeripheralManager];
[self waitForExpectations:@[ psmPublishedexpectation ] timeout:0.1];
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:0.5];
}
}
- (void)testSuccessfulOpenL2CAPChannel {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"Open L2CAP channel."];
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
GNCBLEL2CAPClient *l2capClient =
[[GNCBLEL2CAPClient alloc] initWithQueue:nil
requestDisconnectionHandler:^(id<GNCPeripheral> _Nonnull peripheral){
}];
[medium setL2CAPClient:l2capClient];
[medium openL2CAPChannelWithPSM:123
peripheral:fakePeripheral
completionHandler:^(GNCBLEL2CAPStream *_Nullable stream, NSError *_Nullable error) {
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
}
#pragma mark - Connect
- (void)testSuccessfulConnect {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
disconnectionHandler:nil
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
XCTAssertNotNil(client);
XCTAssertNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
}
- (void)testFailedConnect {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation = [[XCTestExpectation alloc] initWithDescription:@"Connect."];
fakeCentralManager.didFailToConnectPeripheralError = [NSError errorWithDomain:@"fake"
code:0
userInfo:nil];
[medium connectToGATTServerForPeripheral:[[GNCFakePeripheral alloc] init]
disconnectionHandler:nil
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
XCTAssertNil(client);
XCTAssertNotNil(error);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:3];
}
}
- (void)testDisconnect {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *disconnectExpectation =
[[XCTestExpectation alloc] initWithDescription:@"Disconnect."];
GNCFakePeripheral *peripheral = [[GNCFakePeripheral alloc] init];
[medium connectToGATTServerForPeripheral:peripheral
disconnectionHandler:^() {
[disconnectExpectation fulfill];
}
completionHandler:^(GNCBLEGATTClient *client, NSError *error) {
XCTAssertNotNil(client);
XCTAssertNil(error);
[client disconnect];
}];
[self waitForExpectations:@[ disconnectExpectation ] timeout:3];
}
}
- (void)testRetrievePeripheralWithIdentifier_exists {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTAssertNotNil(
[medium retrievePeripheralWithIdentifier:
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111111"]]);
}
}
- (void)testRetrievePeripheralWithIdentifier_doesNotExist {
for (NSNumber *enabled in @[ @NO, @YES ]) {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
kEnableSharedPeripheralManager,
enabled.boolValue);
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEMedium *medium = [[GNCBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTAssertNil(
[medium retrievePeripheralWithIdentifier:
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111112"]]);
}
}
@end
@@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
/** The socket file descriptor for the L2CAP channel. */
@property(nonatomic, readonly) int socketFD;
/** The PSM (Protocol/Service Multiplexer) of the L2CAP channel. */
@property(nonatomic, readonly) CBL2CAPPSM PSM;
@property(nonatomic, readwrite) CBL2CAPPSM PSM;
@end
@@ -140,6 +140,7 @@ static const uint16_t kPSM = 192;
GNCFakeCBL2CAPChannel *fakeChannel = [[GNCFakeCBL2CAPChannel alloc] init];
fakeChannel.inputStream = fakeStream.inputStream;
fakeChannel.outputStream = fakeStream.outputStream;
fakeChannel.PSM = _PSM;
[_peripheralDelegate gnc_peripheralManager:self
didOpenL2CAPChannel:(CBL2CAPChannel *)fakeChannel
error:_didOpenL2CAPChannelError];
@@ -131,7 +131,7 @@ static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
XCTestExpectation *expectation = [self expectationWithDescription:@"Connection success"];
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
GNCMWaitForConnection((GNSSocket *)fakeSocket, nil, ^(BOOL flag) {
XCTAssertTrue(flag);
[expectation fulfill];
});
@@ -142,12 +142,31 @@ static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}
- (void)testWaitForConnection_CustomQueue {
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
dispatch_queue_t customQueue = dispatch_queue_create("com.google.nearby.testQueue", DISPATCH_QUEUE_SERIAL);
XCTestExpectation *expectation = [self expectationWithDescription:@"Connection success on custom queue"];
GNCMWaitForConnection((GNSSocket *)fakeSocket, customQueue, ^(BOOL flag) {
XCTAssertTrue(flag);
// Verify that we are on the custom queue
const char *label = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
XCTAssertEqual(strcmp(label, "com.google.nearby.testQueue"), 0);
[expectation fulfill];
});
// Simulate the connection callback
[fakeSocket simulateSocketDidConnect];
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}
- (void)testWaitForConnection_Failure_Disconnect {
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
XCTestExpectation *expectation =
[self expectationWithDescription:@"Connection failed on disconnect"];
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
GNCMWaitForConnection((GNSSocket *)fakeSocket, nil, ^(BOOL flag) {
XCTAssertFalse(flag);
[expectation fulfill];
});
@@ -164,7 +183,7 @@ static const NSTimeInterval kWaitForConnectionTimeout = 6.0; // Allow for the 5
XCTestExpectation *expectation =
[self expectationWithDescription:@"Connection failed on timeout"];
GNCMWaitForConnection((GNSSocket *)fakeSocket, ^(BOOL flag) {
GNCMWaitForConnection((GNSSocket *)fakeSocket, nil, ^(BOOL flag) {
XCTAssertFalse(flag);
[expectation fulfill];
});
@@ -0,0 +1,495 @@
// Copyright 2026 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/Mediums/BLE/GNCPeripheralManagerMultiplexer.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
@interface GNCPeripheralManagerMultiplexerTest : XCTestCase
@end
@interface FakePeripheralManagerDelegate : NSObject <GNCPeripheralManagerDelegate>
@property(nonatomic) XCTestExpectation *expectation;
@property(nonatomic) BOOL didUpdateStateCalled;
@property(nonatomic) CBManagerState state;
@property(nonatomic) BOOL didStartAdvertisingCalled;
@property(nonatomic) NSError *startAdvertisingError;
@property(nonatomic) BOOL didAddServiceCalled;
@property(nonatomic) CBService *addedService;
@property(nonatomic) NSError *addServiceError;
@property(nonatomic) BOOL didReceiveReadRequestCalled;
@property(nonatomic) CBATTRequest *readRequest;
@property(nonatomic) BOOL didPublishL2CAPChannelCalled;
@property(nonatomic) CBL2CAPPSM publishedPSM;
@property(nonatomic) NSError *publishL2CAPChannelError;
@property(nonatomic) BOOL didUnpublishL2CAPChannelCalled;
@property(nonatomic) CBL2CAPPSM unpublishedPSM;
@property(nonatomic) NSError *unpublishL2CAPChannelError;
@property(nonatomic) BOOL didOpenL2CAPChannelCalled;
@property(nonatomic) CBL2CAPChannel *openedChannel;
@property(nonatomic) NSError *openL2CAPChannelError;
@end
@implementation FakePeripheralManagerDelegate
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
_didUpdateStateCalled = YES;
_state = peripheral.state;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
error:(nullable NSError *)error {
_didStartAdvertisingCalled = YES;
_startAdvertisingError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didAddService:(CBService *)service
error:(nullable NSError *)error {
_didAddServiceCalled = YES;
_addedService = service;
_addServiceError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
_didReceiveReadRequestCalled = YES;
_readRequest = request;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
_didPublishL2CAPChannelCalled = YES;
_publishedPSM = PSM;
_publishL2CAPChannelError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(NSError *)error {
_didUnpublishL2CAPChannelCalled = YES;
_unpublishedPSM = PSM;
_unpublishL2CAPChannelError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
error:(nullable NSError *)error {
_didOpenL2CAPChannelCalled = YES;
_openedChannel = channel;
_openL2CAPChannelError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)peripheralManagerDidUpdateState:(nonnull CBPeripheralManager *)peripheral {
_didUpdateStateCalled = YES;
_state = peripheral.state;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral
error:(nullable NSError *)error {
_didStartAdvertisingCalled = YES;
_startAdvertisingError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didAddService:(CBService *)service
error:(nullable NSError *)error {
_didAddServiceCalled = YES;
_addedService = service;
_addServiceError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
_didReceiveReadRequestCalled = YES;
_readRequest = request;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
_didPublishL2CAPChannelCalled = YES;
_publishedPSM = PSM;
_publishL2CAPChannelError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
_didUnpublishL2CAPChannelCalled = YES;
_unpublishedPSM = PSM;
_unpublishL2CAPChannelError = error;
if (_expectation) {
[_expectation fulfill];
}
}
- (void)peripheralManager:(CBPeripheralManager *)peripheral
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
error:(nullable NSError *)error {
_didOpenL2CAPChannelCalled = YES;
_openedChannel = channel;
_openL2CAPChannelError = error;
if (_expectation) {
[_expectation fulfill];
}
}
@end
@implementation GNCPeripheralManagerMultiplexerTest
- (void)testMultiplexerForwardsCallbacks {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate1 = [[FakePeripheralManagerDelegate alloc] init];
FakePeripheralManagerDelegate *delegate2 = [[FakePeripheralManagerDelegate alloc] init];
delegate1.expectation = [self expectationWithDescription:@"Delegate 1 called"];
delegate2.expectation = [self expectationWithDescription:@"Delegate 2 called"];
[multiplexer addListener:delegate1];
[multiplexer addListener:delegate2];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
fakeManager.state = CBManagerStatePoweredOn;
[multiplexer gnc_peripheralManagerDidUpdateState:fakeManager];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate1.didUpdateStateCalled);
XCTAssertTrue(delegate2.didUpdateStateCalled);
XCTAssertEqual(delegate1.state, CBManagerStatePoweredOn);
XCTAssertEqual(delegate2.state, CBManagerStatePoweredOn);
}
- (void)testMultiplexerRemovesListener {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate1 = [[FakePeripheralManagerDelegate alloc] init];
delegate1.expectation = [self expectationWithDescription:@"Delegate 1 called"];
[multiplexer addListener:delegate1];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
fakeManager.state = CBManagerStatePoweredOn;
[multiplexer removeListener:delegate1];
[multiplexer gnc_peripheralManagerDidUpdateState:fakeManager];
// We expect delegate1 NOT to be called.
// Since removals are async, we wait a bit to ensure it had a chance (or didn't).
XCTWaiterResult result = [XCTWaiter waitForExpectations:@[ delegate1.expectation ] timeout:0.5];
XCTAssertEqual(result, XCTWaiterResultTimedOut);
XCTAssertFalse(delegate1.didUpdateStateCalled);
}
- (void)testMultiplexerForwardsDidStartAdvertising {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
NSError *error = [NSError errorWithDomain:@"test" code:1 userInfo:nil];
[multiplexer gnc_peripheralManagerDidStartAdvertising:fakeManager error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didStartAdvertisingCalled);
XCTAssertEqualObjects(delegate.startAdvertisingError, error);
}
- (void)testMultiplexerForwardsDidAddService {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
CBMutableService *service =
[[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"180D"] primary:YES];
NSError *error = [NSError errorWithDomain:@"test" code:2 userInfo:nil];
[multiplexer gnc_peripheralManager:fakeManager didAddService:service error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didAddServiceCalled);
XCTAssertEqualObjects(delegate.addedService, service);
XCTAssertEqualObjects(delegate.addServiceError, error);
}
- (void)testMultiplexerForwardsDidReceiveReadRequest {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
id request = [NSNull null]; // Use NSNull or any object as placeholder since we can't create
// CBATTRequest
[multiplexer gnc_peripheralManager:fakeManager didReceiveReadRequest:request];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didReceiveReadRequestCalled);
XCTAssertEqual(delegate.readRequest, request);
}
- (void)testMultiplexerForwardsDidPublishL2CAPChannel {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
CBL2CAPPSM psm = 42;
NSError *error = [NSError errorWithDomain:@"test" code:3 userInfo:nil];
[multiplexer gnc_peripheralManager:fakeManager didPublishL2CAPChannel:psm error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didPublishL2CAPChannelCalled);
XCTAssertEqual(delegate.publishedPSM, psm);
XCTAssertEqualObjects(delegate.publishL2CAPChannelError, error);
}
- (void)testMultiplexerForwardsDidUnpublishL2CAPChannel {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
CBL2CAPPSM psm = 42;
NSError *error = [NSError errorWithDomain:@"test" code:4 userInfo:nil];
[multiplexer gnc_peripheralManager:fakeManager didUnpublishL2CAPChannel:psm error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didUnpublishL2CAPChannelCalled);
XCTAssertEqual(delegate.unpublishedPSM, psm);
XCTAssertEqualObjects(delegate.unpublishL2CAPChannelError, error);
}
- (void)testMultiplexerForwardsDidOpenL2CAPChannel {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
id channel = [NSNull null]; // Placeholder
NSError *error = [NSError errorWithDomain:@"test" code:5 userInfo:nil];
[multiplexer gnc_peripheralManager:fakeManager didOpenL2CAPChannel:channel error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didOpenL2CAPChannelCalled);
XCTAssertEqual(delegate.openedChannel, channel);
XCTAssertEqualObjects(delegate.openL2CAPChannelError, error);
}
- (void)testCBPeripheralManagerDelegateDidUpdateState {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
fakeManager.state = CBManagerStatePoweredOn;
[multiplexer peripheralManagerDidUpdateState:(CBPeripheralManager *)fakeManager];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didUpdateStateCalled);
XCTAssertEqual(delegate.state, CBManagerStatePoweredOn);
}
- (void)testCBPeripheralManagerDelegateDidStartAdvertising {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
NSError *error = [NSError errorWithDomain:@"test" code:10 userInfo:nil];
[multiplexer peripheralManagerDidStartAdvertising:(CBPeripheralManager *)fakeManager error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didStartAdvertisingCalled);
XCTAssertEqualObjects(delegate.startAdvertisingError, error);
}
- (void)testCBPeripheralManagerDelegateDidAddService {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
CBMutableService *service =
[[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"180F"] primary:YES];
NSError *error = [NSError errorWithDomain:@"test" code:11 userInfo:nil];
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager didAddService:service error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didAddServiceCalled);
XCTAssertEqualObjects(delegate.addedService, service);
XCTAssertEqualObjects(delegate.addServiceError, error);
}
- (void)testCBPeripheralManagerDelegateDidReceiveReadRequest {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
id request = [NSNull null];
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager didReceiveReadRequest:request];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didReceiveReadRequestCalled);
XCTAssertEqual(delegate.readRequest, request);
}
- (void)testCBPeripheralManagerDelegateDidPublishL2CAPChannel {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
CBL2CAPPSM psm = 100;
NSError *error = [NSError errorWithDomain:@"test" code:13 userInfo:nil];
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager
didPublishL2CAPChannel:psm
error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didPublishL2CAPChannelCalled);
XCTAssertEqual(delegate.publishedPSM, psm);
XCTAssertEqualObjects(delegate.publishL2CAPChannelError, error);
}
- (void)testCBPeripheralManagerDelegateDidUnpublishL2CAPChannel {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
CBL2CAPPSM psm = 101;
NSError *error = [NSError errorWithDomain:@"test" code:14 userInfo:nil];
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager
didUnpublishL2CAPChannel:psm
error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didUnpublishL2CAPChannelCalled);
XCTAssertEqual(delegate.unpublishedPSM, psm);
XCTAssertEqualObjects(delegate.unpublishL2CAPChannelError, error);
}
- (void)testCBPeripheralManagerDelegateDidOpenL2CAPChannel {
GNCPeripheralManagerMultiplexer *multiplexer =
[[GNCPeripheralManagerMultiplexer alloc] initWithCallbackQueue:dispatch_get_main_queue()];
FakePeripheralManagerDelegate *delegate = [[FakePeripheralManagerDelegate alloc] init];
delegate.expectation = [self expectationWithDescription:@"Delegate called"];
[multiplexer addListener:delegate];
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
id channel = [NSNull null];
NSError *error = [NSError errorWithDomain:@"test" code:15 userInfo:nil];
[multiplexer peripheralManager:(CBPeripheralManager *)fakeManager
didOpenL2CAPChannel:channel
error:error];
[self waitForExpectationsWithTimeout:1 handler:nil];
XCTAssertTrue(delegate.didOpenL2CAPChannelCalled);
XCTAssertEqual(delegate.openedChannel, channel);
XCTAssertEqualObjects(delegate.openL2CAPChannelError, error);
}
@end
@@ -32,7 +32,7 @@ objc_library(
"GNCNWFramework.m",
"GNCNWFrameworkError.m",
"GNCNWFrameworkServerSocket.m",
"GNCNWFrameworkSocket.m",
"GNCNWFrameworkSocket.mm",
"GNCNWListenerImpl.m",
"GNCNWParameters.m",
],
@@ -26,4 +26,5 @@ typedef NS_ERROR_ENUM(GNCNWFrameworkErrorDomain, GNCNWFrameworkError){
GNCNWFrameworkErrorUnknown,
GNCNWFrameworkErrorTimedOut,
GNCNWFrameworkErrorDuplicateDiscovererForServiceType,
GNCNWFrameworkErrorNotConnected,
};
@@ -15,6 +15,12 @@
#import <Foundation/Foundation.h>
#import <Network/Network.h>
#ifdef __cplusplus
#include <optional>
#include <string>
#endif
@protocol GNCNWConnection;
@interface GNCNWFrameworkSocket : NSObject
@@ -44,6 +50,21 @@
*/
- (nullable NSData *)readMaxLength:(NSUInteger)length error:(NSError **_Nullable)error;
/**
* Reads the requested amount of bytes from the connection and converts it to a string.
*
* Blocks execution until the bytes have been read or an error occurs.
*
* @param length The number of bytes to read.
* @param[out] error Error that will be populated on failure. A read may return non-nil data along
* with an error. This normally happens if the data read is shorter than the
* requested length.
*/
#ifdef __cplusplus
- (std::optional<std::string>)readStringWithMaxLength:(NSUInteger)length
error:(NSError **_Nullable)error;
#endif
/**
* Writes the given data to the connection.
*
@@ -54,6 +75,17 @@
*/
- (BOOL)write:(NSData *)data error:(NSError **_Nullable)error;
/**
* Writes raw bytes to the connection.
*
* @param bytes The buffer to write.
* @param length The number of bytes to write.
* @param error Error that will be populated on failure.
*/
- (BOOL)writeBytes:(const void *)bytes
length:(NSUInteger)length
error:(NSError **_Nullable)error;
/**
* Gracefully closes the connection to remote endpoint.
*
@@ -17,6 +17,9 @@
#import <Foundation/Foundation.h>
#import <Network/Network.h>
#include <optional>
#include <string>
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWConnection.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkError.h"
@@ -85,6 +88,52 @@ static const NSTimeInterval kConnectionWriteTimeout = 5.0; // 5 seconds timeout
return blockResult;
}
- (std::optional<std::string>)readStringWithMaxLength:(NSUInteger)length error:(NSError **)error {
if (!self.connection) {
if (error) {
*error = [NSError errorWithDomain:GNCNWFrameworkErrorDomain
code:GNCNWFrameworkErrorNotConnected
userInfo:nil];
}
return std::nullopt;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block std::string resultString;
__block NSError *blockError = nil;
__block BOOL contentReceived = NO;
[self.connection
receiveMessageWithMinLength:(uint32_t)length
maxLength:(uint32_t)length
completionHandler:^(dispatch_data_t _Nullable content,
nw_content_context_t _Nullable context, bool isComplete,
nw_error_t _Nullable receiveError) {
if (receiveError) {
blockError = (__bridge_transfer NSError *)nw_error_copy_cf_error(receiveError);
}
if (content) {
contentReceived = YES;
// OPTIMIZATION: Copy directly from dispatch_data_t into std::string
resultString.reserve(dispatch_data_get_size(content));
dispatch_data_apply(content, ^bool(dispatch_data_t region, size_t offset,
const void *buffer, size_t size) {
resultString.append((const char *)buffer, size);
return true;
});
}
dispatch_semaphore_signal(semaphore);
}];
// Block the current thread until the network callback completes.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (error != nil) {
*error = blockError;
}
return (!contentReceived) ? std::nullopt : std::make_optional(std::move(resultString));
}
- (BOOL)write:(NSData *)data error:(NSError **)error {
if (!self.connection) {
if (error) {
@@ -136,6 +185,50 @@ static const NSTimeInterval kConnectionWriteTimeout = 5.0; // 5 seconds timeout
return signaled && blockSuccess;
}
- (BOOL)writeBytes:(const void *)bytes length:(NSUInteger)length error:(NSError **)error {
if (!self.connection) {
if (error) {
*error = [NSError errorWithDomain:GNCNWFrameworkErrorDomain
code:GNCNWFrameworkErrorNotConnected
userInfo:nil];
}
return NO;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
// OPTIMIZATION: Use DISPATCH_DATA_DESTRUCTOR_DEFAULT to perform a
// single copy into a GCD-managed buffer. No NSData required.
// TODO: edwinwu - Investigate to see if it is worth to make it zero-copy by replacing
// DISPATCH_DATA_DESTRUCTOR_DEFAULT with a custom empty destructor:
// dispatch_data_t dispatchData = dispatch_data_create(bytes, length, nil, ^{
// // Zero-copy: ownership remains with the caller.
// });
dispatch_data_t dispatchData =
dispatch_data_create(bytes, length, nil, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
[self.connection sendData:dispatchData
context:NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT
isComplete:NO
completionHandler:^(nw_error_t _Nullable sendError) {
if (sendError) {
blockError = (__bridge_transfer NSError *)nw_error_copy_cf_error(sendError);
}
dispatch_semaphore_signal(semaphore);
}];
// Wait until signaled or the 5-second timeout passes
intptr_t waitResult = dispatch_semaphore_wait(
semaphore,
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kConnectionWriteTimeout * NSEC_PER_SEC)));
if (error != nil) {
*error = blockError;
}
return (waitResult == 0) && (blockError == nil);
}
- (void)close {
[_connection cancel];
_connection = nil;
@@ -29,7 +29,7 @@ objc_library(
"GNCFakeNWConnection.m",
"GNCFakeNWFramework.m",
"GNCFakeNWFrameworkServerSocket.m",
"GNCFakeNWFrameworkSocket.m",
"GNCFakeNWFrameworkSocket.mm",
"GNCFakeNWListener.m",
],
hdrs = [
@@ -57,7 +57,7 @@ objc_library(
"GNCNWBrowserImplTest.m",
"GNCNWConnectionImplTest.m",
"GNCNWFrameworkServerSocketTest.m",
"GNCNWFrameworkSocketTest.m",
"GNCNWFrameworkSocketTest.mm",
"GNCNWFrameworkTest.m",
"GNCNWListenerImplTest.m",
"GNCNWParametersTest.m",
@@ -44,6 +44,25 @@
return [NSData data];
}
- (std::optional<std::string>)readStringWithMaxLength:(NSUInteger)length error:(NSError **)error {
if (self.readError) {
if (error) *error = self.readError;
return std::nullopt;
}
if (self.dataToRead) {
NSData *data = self.dataToRead;
self.dataToRead = nil;
NSUInteger actualLength = MIN(length, data.length);
if (data.length > actualLength) {
self.dataToRead =
[data subdataWithRange:NSMakeRange(actualLength, data.length - actualLength)];
}
NSData *returnData = [data subdataWithRange:NSMakeRange(0, actualLength)];
return std::string((const char *)returnData.bytes, returnData.length);
}
return std::string();
}
- (BOOL)write:(NSData *)data error:(NSError **)error {
if (self.writeError) {
if (error) {
@@ -55,6 +74,17 @@
return YES;
}
- (BOOL)writeBytes:(const void *)bytes length:(NSUInteger)length error:(NSError **)error {
if (self.writeError) {
if (error) {
*error = self.writeError;
}
return NO;
}
[self.writtenData appendBytes:bytes length:length];
return YES;
}
- (void)close {
self.isClosed = YES;
}
@@ -17,6 +17,9 @@
#import <Network/Network.h>
#import <XCTest/XCTest.h>
#include <optional>
#include <string>
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWConnection.h"
NS_ASSUME_NONNULL_BEGIN
@@ -75,6 +78,38 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertNil(error);
}
- (void)testReadStringWithMaxLength_Success {
NSError *error = nil;
NSString *testString = @"testData";
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
dispatch_data_t dispatchData = dispatch_data_create(testData.bytes, testData.length, dispatch_get_main_queue(), ^{});
_fakeConnection.dataToReceive = dispatchData;
std::optional<std::string> receivedString = [_socket readStringWithMaxLength:testData.length error:&error];
XCTAssertTrue(receivedString.has_value());
XCTAssertEqualObjects(@(receivedString.value().c_str()), testString);
XCTAssertNil(error);
}
- (void)testReadStringWithMaxLength_Error {
NSError *error = nil;
_fakeConnection.simulateReceiveFailure = YES;
std::optional<std::string> receivedString = [_socket readStringWithMaxLength:10 error:&error];
XCTAssertFalse(receivedString.has_value());
XCTAssertNil(error); // Fake doesn't produce an NSError
}
- (void)testReadStringWithMaxLength_Zero {
NSError *error = nil;
std::optional<std::string> receivedString = [_socket readStringWithMaxLength:0 error:&error];
XCTAssertFalse(receivedString.has_value());
XCTAssertNil(error);
}
- (void)testWrite_Success {
NSError *error = nil;
NSString *testString = @"testData";
@@ -97,6 +132,28 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertFalse(result);
}
- (void)testWriteBytes_Success {
NSError *error = nil;
NSString *testString = @"testData";
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [_socket writeBytes:testData.bytes length:testData.length error:&error];
XCTAssertTrue(result);
XCTAssertNil(error);
}
- (void)testWriteBytes_Error {
NSError *error = nil;
NSString *testString = @"testData";
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
_fakeConnection.simulateSendFailure = YES;
BOOL result = [_socket writeBytes:testData.bytes length:testData.length error:&error];
XCTAssertFalse(result);
}
- (void)testClose {
XCTAssertFalse(_fakeConnection.cancelCalled);
[_socket close];
@@ -105,6 +162,7 @@ NS_ASSUME_NONNULL_BEGIN
NSError *error = nil;
XCTAssertNil([_socket readMaxLength:10 error:&error]);
XCTAssertFalse([_socket write:[NSData data] error:&error]);
XCTAssertFalse([_socket writeBytes:"test" length:4 error:&error]);
}
@end
@@ -56,8 +56,10 @@ objc_library(
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"//internal/platform/implementation:webrtc_platform",
"//internal/platform/implementation/apple", # buildcleaner: keep
"//internal/platform/implementation/apple:Shared",
"//internal/platform/implementation/apple:apple_webrtc", # buildcleaner: keep
"//internal/platform/implementation/apple:ble_v2",
"//internal/platform/implementation/apple:network_utils",
"//internal/platform/implementation/apple/Flags",
@@ -18,6 +18,9 @@
#include <string>
#include "connections/implementation/flags/nearby_connections_feature_flags.h"
#include "internal/flags/nearby_flags.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWConnection.h"
@@ -45,6 +48,7 @@ static const int kTestPort = 1234;
- (void)tearDown {
_awdlMedium.reset();
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
[super tearDown];
}
@@ -128,6 +132,10 @@ static const int kTestPort = 1234;
}
- (void)testSocketAndStream {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
false);
// Create a server socket.
std::unique_ptr<nearby::api::AwdlServerSocket> serverSocket =
_awdlMedium->ListenForService(kTestPort);
@@ -167,6 +175,49 @@ static const int kTestPort = 1234;
XCTAssertTrue(fakeServerSocket.isClosed);
}
- (void)testSocketAndStream_SingleCopyEnabled {
// Enable the flag.
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
true);
// Create a server socket.
std::unique_ptr<nearby::api::AwdlServerSocket> serverSocket =
_awdlMedium->ListenForService(kTestPort);
XCTAssertTrue(serverSocket != nullptr);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
// Accept a client socket.
std::unique_ptr<nearby::api::AwdlSocket> clientSocket = serverSocket->Accept();
XCTAssertTrue(clientSocket != nullptr);
// Test input stream with optimized single-copy read.
nearby::InputStream& inputStream = clientSocket->GetInputStream();
fakeSocket.dataToRead = [@"optimized awdl data" dataUsingEncoding:NSUTF8StringEncoding];
// "optimized awdl data" is 19 bytes.
nearby::ExceptionOr<nearby::ByteArray> readData = inputStream.Read(19);
XCTAssertTrue(readData.ok());
XCTAssertEqual(std::string(readData.result()), "optimized awdl data");
// Test output stream.
nearby::OutputStream& outputStream = clientSocket->GetOutputStream();
absl::string_view writeData("write data");
XCTAssertTrue(outputStream.Write(writeData).Ok());
XCTAssertEqualObjects(fakeSocket.writtenData,
[@"write data" dataUsingEncoding:NSUTF8StringEncoding]);
// Clean up.
XCTAssertTrue(clientSocket->Close().Ok());
XCTAssertTrue(serverSocket->Close().Ok());
}
- (void)testServerSocketGetIPAddress {
// Create a server socket.
std::unique_ptr<nearby::api::AwdlServerSocket> serverSocket =
@@ -195,6 +246,39 @@ static const int kTestPort = 1234;
XCTAssertTrue(serverSocket->Close().Ok());
}
- (void)testOutputStreamWrite_SingleCopyEnabled {
// Enable the flag.
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
true);
// Create a server socket and accept a client.
std::unique_ptr<nearby::api::AwdlServerSocket> serverSocket =
_awdlMedium->ListenForService(kTestPort);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
std::unique_ptr<nearby::api::AwdlSocket> clientSocket = serverSocket->Accept();
XCTAssertTrue(clientSocket != nullptr);
// Test output stream.
nearby::OutputStream& outputStream = clientSocket->GetOutputStream();
absl::string_view writeData("optimized write data");
XCTAssertTrue(outputStream.Write(writeData).Ok());
XCTAssertEqualObjects(fakeSocket.writtenData,
[@"optimized write data" dataUsingEncoding:NSUTF8StringEncoding]);
// Clean up.
XCTAssertTrue(clientSocket->Close().Ok());
XCTAssertTrue(serverSocket->Close().Ok());
// Reset the flag.
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
}
- (void)testOutputStreamClose {
// Create a server socket and accept a client.
std::unique_ptr<nearby::api::AwdlServerSocket> serverSocket =
@@ -93,7 +93,7 @@ using MultiThreadExecutor = ::nearby::api::SubmittableExecutor;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
const int kRunnableCount = 1000;
const int kRunnableCount = 100;
for (int i = 0; i < kRunnableCount; i++) {
executor->Execute([self]() { self.counter++; });
}
@@ -13,6 +13,7 @@
// limitations under the License.
#include "internal/platform/implementation/platform.h"
#include "internal/platform/implementation/webrtc_platform.h"
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
@@ -320,7 +321,7 @@ void GNCEnsureFileAtPath(std::string path) {
}
- (void)testCreateWebRtcMedium {
auto webrtc_medium = nearby::api::ImplementationPlatform::CreateWebRtcMedium();
auto webrtc_medium = nearby::api::WebRtcImplementationPlatform::CreateWebRtcMedium();
XCTAssertNotEqual(webrtc_medium.get(), nullptr);
}
@@ -76,20 +76,14 @@ using SingleThreadExecutor = ::nearby::api::SubmittableExecutor;
// Tests that shutting down an existing task allows to complete.
- (void)testShutdownToAllowExistingTaskComplete {
std::unique_ptr<SingleThreadExecutor> executor([self executor]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
executor->Execute([self]() { self.counter++; });
executor->Shutdown();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), queue, ^{
XCTAssertEqual(self.counter, 1);
executor->Execute([self, expectation]() {
self.counter++;
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:0.5 handler:nil];
executor->Shutdown();
[self waitForExpectationsWithTimeout:1.0 handler:nil];
XCTAssertEqual(self.counter, 1);
}
@end
@@ -65,7 +65,7 @@
auto timer = std::make_unique<nearby::apple::Timer>();
std::atomic<int> fireCount = 0;
XCTAssertTrue(timer->Create(10, 10, [&]() {
XCTAssertTrue(timer->Create(100, 100, [&]() {
if (fireCount.fetch_add(1) == 1) {
dispatch_async(dispatch_get_main_queue(), ^{
[expectation fulfill];
@@ -73,9 +73,9 @@
}
}));
[self waitForExpectationsWithTimeout:1.0 handler:nil];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
XCTAssertTrue(timer->Stop());
XCTAssertEqual(fireCount.load(), 2);
XCTAssertGreaterThanOrEqual(fireCount.load(), 2);
}
- (void)testRestart {
@@ -19,8 +19,11 @@
#include <string>
#include "connections/implementation/flags/nearby_connections_feature_flags.h"
#include "internal/flags/nearby_flags.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h"
#import "internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h"
@@ -59,8 +62,8 @@ const char kIPAddress[] = "192.168.1.2";
_medium.locationManager = _fakeLocationManager;
_hotspotMedium = std::make_unique<nearby::apple::WifiHotspotMedium>(_medium);
_service_address = {
.address = {static_cast<char>(192), static_cast<char>(168), 1, 2},
.port = 1234,
.address = {static_cast<char>(192), static_cast<char>(168), 1, 2},
.port = 1234,
};
}
@@ -98,6 +101,10 @@ const char kIPAddress[] = "192.168.1.2";
}
- (void)testInputStreamRead {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
false);
nearby::CancellationFlag cancellationFlag;
std::unique_ptr<nearby::api::WifiHotspotSocket> socket =
_hotspotMedium->ConnectToService(_service_address, &cancellationFlag);
@@ -110,6 +117,28 @@ const char kIPAddress[] = "192.168.1.2";
XCTAssertTrue(readData.ok());
XCTAssertEqual(readData.result().size(), 4);
XCTAssertEqual(strncmp(readData.result().data(), "Test", 4), 0);
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
}
- (void)testInputStreamRead_SingleCopyEnabled {
// Enable the flag
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
true);
nearby::CancellationFlag cancellationFlag;
std::unique_ptr<nearby::api::WifiHotspotSocket> socket =
_hotspotMedium->ConnectToService(_service_address, &cancellationFlag);
GNCFakeNWFrameworkSocket *fakeSocket = _fakeNWFramework.sockets.firstObject;
fakeSocket.dataToRead = [@"HotspotOpt" dataUsingEncoding:NSUTF8StringEncoding];
nearby::ExceptionOr<nearby::ByteArray> readData = socket->GetInputStream().Read(10);
XCTAssertTrue(readData.ok());
XCTAssertEqual(std::string(readData.result()), "HotspotOpt");
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
}
- (void)testInputStreamClose {
@@ -125,6 +154,10 @@ const char kIPAddress[] = "192.168.1.2";
}
- (void)testOutputStreamWrite {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
false);
nearby::CancellationFlag cancellationFlag;
std::unique_ptr<nearby::api::WifiHotspotSocket> socket =
_hotspotMedium->ConnectToService(_service_address, &cancellationFlag);
@@ -136,6 +169,29 @@ const char kIPAddress[] = "192.168.1.2";
XCTAssertTrue(writeResult.Ok());
XCTAssertEqualObjects(fakeSocket.writtenData, data);
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
}
- (void)testOutputStreamWrite_SingleCopyEnabled {
// Enable the flag
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
true);
nearby::CancellationFlag cancellationFlag;
std::unique_ptr<nearby::api::WifiHotspotSocket> socket =
_hotspotMedium->ConnectToService(_service_address, &cancellationFlag);
GNCFakeNWFrameworkSocket *fakeSocket = _fakeNWFramework.sockets.firstObject;
NSData *data = [@"TestDataOpt" dataUsingEncoding:NSUTF8StringEncoding];
absl::string_view data_str(reinterpret_cast<const char *>(data.bytes), data.length);
nearby::Exception writeResult = socket->GetOutputStream().Write(data_str);
XCTAssertTrue(writeResult.Ok());
XCTAssertEqualObjects(fakeSocket.writtenData, data);
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
}
- (void)testSocketClose {
@@ -18,6 +18,9 @@
#include <string>
#include "connections/implementation/flags/nearby_connections_feature_flags.h"
#include "internal/flags/nearby_flags.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWConnection.h"
@@ -118,6 +121,10 @@
}
- (void)testSocketAndStream {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
false);
// Create a server socket.
std::unique_ptr<nearby::api::WifiLanServerSocket> serverSocket =
_wifiLanMedium->ListenForService(1234);
@@ -155,6 +162,38 @@
// Test closing the server socket.
XCTAssertTrue(serverSocket->Close().Ok());
XCTAssertTrue(fakeServerSocket.isClosed);
// Reset the flag.
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
}
- (void)testSocketAndStream_SingleCopyEnabled {
// Enable the flag
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
true);
// Create a server socket and accept a client.
std::unique_ptr<nearby::api::WifiLanServerSocket> serverSocket =
_wifiLanMedium->ListenForService(1234);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
std::unique_ptr<nearby::api::WifiLanSocket> clientSocket = serverSocket->Accept();
nearby::InputStream& inputStream = clientSocket->GetInputStream();
// Test optimized single-copy read.
fakeSocket.dataToRead = [@"optimized data" dataUsingEncoding:NSUTF8StringEncoding];
nearby::ExceptionOr<nearby::ByteArray> readData = inputStream.Read(14);
XCTAssertTrue(readData.ok());
XCTAssertEqual(std::string(readData.result()), "optimized data");
// Reset the flag.
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
}
- (void)testServerSocketGetIPAddress {
@@ -185,6 +224,39 @@
XCTAssertTrue(serverSocket->Close().Ok());
}
- (void)testOutputStreamWrite_SingleCopyEnabled {
// Enable the flag
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
::nearby::connections::config_package_nearby::nearby_connections_feature::kEnableSingleCopy,
true);
// Create a server socket and accept a client.
std::unique_ptr<nearby::api::WifiLanServerSocket> serverSocket =
_wifiLanMedium->ListenForService(1234);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
std::unique_ptr<nearby::api::WifiLanSocket> clientSocket = serverSocket->Accept();
nearby::OutputStream& outputStream = clientSocket->GetOutputStream();
// Test optimized single-copy write.
absl::string_view writeData("optimized data");
XCTAssertTrue(outputStream.Write(writeData).Ok());
XCTAssertEqualObjects(fakeSocket.writtenData,
[@"optimized data" dataUsingEncoding:NSUTF8StringEncoding]);
// Clean up.
XCTAssertTrue(clientSocket->Close().Ok());
XCTAssertTrue(serverSocket->Close().Ok());
// Reset the flag.
nearby::NearbyFlags::GetInstance().ResetOverridedValues();
}
- (void)testOutputStreamClose {
// Create a server socket and accept a client.
std::unique_ptr<nearby::api::WifiLanServerSocket> serverSocket =
@@ -56,8 +56,9 @@ using ::nearby::ObjCStringFromCppString;
- (void)testUUIDStringFromNSUUID {
NSString *uuidString = @"E621E1F8-C36C-495A-93FC-0C247A3E6E5F";
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
std::string expectedCppString = [uuidString UTF8String];
XCTAssertEqual(nearby::UUIDStringFromNSUUID(uuid), expectedCppString);
XCTAssert(nearby::UUIDStringFromNSUUID(uuid) ==
std::string([uuidString UTF8String],
[uuidString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]));
}
- (void)testBluetoothUUIDConversions {
@@ -210,19 +210,6 @@
XCTAssertFalse(result);
}
- (void)testSetCharacteristicSubscriptionReturnsFalse {
GNCBLEGATTCharacteristic *characteristic =
[[GNCBLEGATTCharacteristic alloc] initWithUUID:[CBUUID UUIDWithString:@"B2B4"]
serviceUUID:[CBUUID UUIDWithString:@"FEF3"]
permissions:CBAttributePermissionsReadable
properties:CBCharacteristicPropertyNotify];
nearby::api::ble::GattCharacteristic cppCharacteristic =
nearby::apple::CPPGATTCharacteristicFromObjC(characteristic);
BOOL result = _gattClient->SetCharacteristicSubscription(cppCharacteristic, true,
[](absl::string_view value) {});
XCTAssertFalse(result);
}
- (void)testDisconnectWhenFlagEnabled {
nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue(
nearby::connections::config_package_nearby::nearby_connections_feature::
@@ -50,8 +50,9 @@
- (void)testBleL2capServerSocketAccept {
XCTestExpectation *expectation = [self expectationWithDescription:@"accept"];
nearby::apple::BleL2capServerSocket *serverSocket = _serverSocket.get();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::unique_ptr<nearby::api::ble::BleL2capSocket> clientSocket = _serverSocket->Accept();
std::unique_ptr<nearby::api::ble::BleL2capSocket> clientSocket = serverSocket->Accept();
XCTAssertNotEqual(clientSocket.get(), nullptr);
[expectation fulfill];
});
@@ -76,8 +77,9 @@
- (void)testBleL2capServerSocketClose {
XCTestExpectation *expectation = [self expectationWithDescription:@"close"];
nearby::apple::BleL2capServerSocket *serverSocket = _serverSocket.get();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::unique_ptr<nearby::api::ble::BleL2capSocket> clientSocket = _serverSocket->Accept();
std::unique_ptr<nearby::api::ble::BleL2capSocket> clientSocket = serverSocket->Accept();
XCTAssertEqual(clientSocket.get(), nullptr);
[expectation fulfill];
});
@@ -23,9 +23,7 @@
#include <utility>
#include <vector>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTClient.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPClient.h"
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEMedium.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralManager.h"
@@ -33,9 +31,13 @@
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSSocket.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEMedium+Testing.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEGATTServer.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEMedium.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeCentralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeSocket.h"
#include "internal/platform/implementation/apple/ble_utils.h"
#include "internal/platform/implementation/ble.h"
#import "third_party/objective_c/ocmock/v3/Source/OCMock/OCMock.h"
@@ -45,11 +47,16 @@ namespace apple {
class BleMediumPeer {
public:
static void SetSocketCentralManager(BleMedium *ble_medium, GNSCentralManager *manager) {
ble_medium->socketCentralManager_ = manager;
static void SetPeripheralManagerFactory(BleMedium *ble_medium,
BleMedium::PeripheralManagerFactory factory) {
ble_medium->peripheral_manager_factory_ = std::move(factory);
}
static void SetSocketPeripheralManager(BleMedium *ble_medium, GNSPeripheralManager *manager) {
ble_medium->socketPeripheralManager_ = manager;
static void SetCentralManagerFactory(BleMedium *ble_medium,
BleMedium::CentralManagerFactory factory) {
ble_medium->central_manager_factory_ = std::move(factory);
}
static GNSPeripheralServiceManager *GetSocketPeripheralServiceManager(BleMedium *ble_medium) {
return ble_medium->socketPeripheralServiceManager_;
}
};
@@ -71,7 +78,11 @@ static const char *const kTestServiceID = "TestServiceID";
- (void)setUp {
[super setUp];
_fakeGNCBLEMedium = [[GNCFakeBLEMedium alloc] init];
GNCFakeCentralManager *fakeCentralManager = [[GNCFakeCentralManager alloc] init];
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
_fakeGNCBLEMedium = [[GNCFakeBLEMedium alloc] initWithCentralManager:fakeCentralManager
peripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
_medium = std::make_unique<nearby::apple::BleMedium>((GNCBLEMedium *)_fakeGNCBLEMedium);
}
@@ -79,6 +90,28 @@ static const char *const kTestServiceID = "TestServiceID";
[super tearDown];
}
- (void)testOpenServerSocket_UsesFactoryForInitialization {
__block BOOL factoryWasCalled = NO;
id mockPeripheralManager = OCMClassMock([GNSPeripheralManager class]);
OCMStub([mockPeripheralManager addPeripheralServiceManager:[OCMArg any]
bleServiceAddedCompletion:[OCMArg any]])
.andDo(^(GNSPeripheralManager *localSelf, GNSPeripheralServiceManager *manager,
void (^completion)(NSError *error)) {
completion(nil);
});
nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() {
factoryWasCalled = YES;
return mockPeripheralManager;
});
// This call should trigger the factory inside BleMedium.
auto server_socket = _medium->OpenServerSocket(kTestServiceID);
XCTAssertTrue(factoryWasCalled, @"BleMedium should have requested the manager from the factory.");
XCTAssertNotEqual(server_socket.get(), nullptr);
}
#pragma mark - Advertising Tests
- (void)testStartAdvertising_Success {
@@ -225,8 +258,13 @@ static const char *const kTestServiceID = "TestServiceID";
#pragma mark - GATT Server Tests
- (void)testStartGattServer_Success {
<<<<<<< HEAD
_fakeGNCBLEMedium.fakeGATTServer =
[[GNCFakeBLEGATTServer alloc] initWithPeripheralManager:nil queue:nil];
=======
_fakeGNCBLEMedium.fakeGATTServer = [[GNCFakeBLEGATTServer alloc] initWithPeripheralManager:nil
queue:nil];
>>>>>>> nearby/main
auto gatt_server = _medium->StartGattServer({});
XCTAssertNotEqual(gatt_server.get(), nullptr);
@@ -422,7 +460,9 @@ static const char *const kTestServiceID = "TestServiceID";
id mockCentralManager = OCMClassMock([GNSCentralManager class]);
OCMStub([mockCentralManager retrieveCentralPeerWithIdentifier:fakePeripheral.identifier])
.andReturn(nil);
nearby::apple::BleMediumPeer::SetSocketCentralManager(_medium.get(), mockCentralManager);
nearby::apple::BleMediumPeer::SetCentralManagerFactory(_medium.get(), ^(CBUUID *uuid) {
return mockCentralManager;
});
auto socket = _medium->Connect(kTestServiceID, nearby::api::ble::TxPowerLevel::kUltraLow,
fakePeripheral.identifier.hash, nullptr);
@@ -460,7 +500,9 @@ static const char *const kTestServiceID = "TestServiceID";
id mockCentralManager = OCMClassMock([GNSCentralManager class]);
OCMStub([mockCentralManager retrieveCentralPeerWithIdentifier:fakePeripheral.identifier])
.andReturn(mockCentralPeerManager);
nearby::apple::BleMediumPeer::SetSocketCentralManager(_medium.get(), mockCentralManager);
nearby::apple::BleMediumPeer::SetCentralManagerFactory(_medium.get(), ^(CBUUID *uuid) {
return mockCentralManager;
});
auto socket = _medium->Connect(kTestServiceID, nearby::api::ble::TxPowerLevel::kUltraLow,
fakePeripheral.identifier.hash, nullptr);
@@ -470,7 +512,10 @@ static const char *const kTestServiceID = "TestServiceID";
#pragma mark - Server Socket Tests
- (void)testOpenServerSocket_Success {
- (void)testOpenServerSocket_Success_LegacyPath {
id mockFeatureFlags = OCMClassMock([GNCFeatureFlags class]);
OCMStub([mockFeatureFlags fixBleServerSocketDeadlockEnabled]).andReturn(NO);
id mockPeripheralManager = OCMClassMock([GNSPeripheralManager class]);
OCMStub([mockPeripheralManager addPeripheralServiceManager:[OCMArg any]
bleServiceAddedCompletion:[OCMArg any]])
@@ -478,11 +523,97 @@ static const char *const kTestServiceID = "TestServiceID";
void (^completion)(NSError *error)) {
completion(nil);
});
nearby::apple::BleMediumPeer::SetSocketPeripheralManager(_medium.get(), mockPeripheralManager);
nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() {
return mockPeripheralManager;
});
auto server_socket = _medium->OpenServerSocket(kTestServiceID);
XCTAssertNotEqual(server_socket.get(), nullptr);
GNSPeripheralServiceManager *serviceManager =
nearby::apple::BleMediumPeer::GetSocketPeripheralServiceManager(_medium.get());
XCTAssertNotNil(serviceManager);
}
- (void)testOpenServerSocket_Success_OptimizedPath {
id mockFeatureFlags = OCMClassMock([GNCFeatureFlags class]);
OCMStub([mockFeatureFlags fixBleServerSocketDeadlockEnabled]).andReturn(YES);
id mockPeripheralManager = OCMClassMock([GNSPeripheralManager class]);
OCMStub([mockPeripheralManager addPeripheralServiceManager:[OCMArg any]
bleServiceAddedCompletion:[OCMArg any]])
.andDo(^(GNSPeripheralManager *localSelf, GNSPeripheralServiceManager *manager,
void (^completion)(NSError *error)) {
completion(nil);
});
nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() {
return mockPeripheralManager;
});
auto server_socket = _medium->OpenServerSocket(kTestServiceID);
XCTAssertNotEqual(server_socket.get(), nullptr);
GNSPeripheralServiceManager *serviceManager =
nearby::apple::BleMediumPeer::GetSocketPeripheralServiceManager(_medium.get());
XCTAssertNotNil(serviceManager);
}
- (void)testOpenServerSocket_OptimizedPath_AcceptSocketAfterClose {
id mockFeatureFlags = OCMClassMock([GNCFeatureFlags class]);
OCMStub([mockFeatureFlags fixBleServerSocketDeadlockEnabled]).andReturn(YES);
id mockPeripheralManager = OCMClassMock([GNSPeripheralManager class]);
OCMStub([mockPeripheralManager addPeripheralServiceManager:[OCMArg any]
bleServiceAddedCompletion:[OCMArg any]])
.andDo(^(GNSPeripheralManager *localSelf, GNSPeripheralServiceManager *manager,
void (^completion)(NSError *error)) {
completion(nil);
});
nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() {
return mockPeripheralManager;
});
auto server_socket = _medium->OpenServerSocket(kTestServiceID);
XCTAssertNotEqual(server_socket.get(), nullptr);
__block BOOL (^capturedHandler)(GNSSocket *) = nil;
id mockServiceManagerClass = OCMClassMock([GNSPeripheralServiceManager class]);
OCMStub([mockServiceManagerClass alloc]).andReturn(mockServiceManagerClass);
OCMStub([mockServiceManagerClass initWithBleServiceUUID:[OCMArg any]
addPairingCharacteristic:NO
shouldAcceptSocketHandler:[OCMArg any]])
.andDo(^(NSInvocation *invocation) {
BOOL (^handler)(GNSSocket *);
[invocation getArgument:&handler atIndex:4];
capturedHandler = handler;
})
.andReturn(mockServiceManagerClass);
auto server_socket_for_handler_capture = _medium->OpenServerSocket(kTestServiceID);
XCTAssertNotEqual(server_socket_for_handler_capture.get(), nullptr);
XCTAssertNotNil(capturedHandler);
// Invoke the shouldAcceptSocketHandler with a fake socket.
GNCFakeSocket *fakeSocket = [[GNCFakeSocket alloc] init];
BOOL result = capturedHandler((GNSSocket *)fakeSocket);
XCTAssertTrue(result);
// Close the server_socket. This triggers the close notifier, setting server_socket_ptr_ to null.
server_socket_for_handler_capture->Close();
// Now simulate the connection completing. It should safely ignore the connection because
// server_socket_ptr_ is null, preventing use-after-free or deadlocks.
[fakeSocket simulateSocketDidConnect];
// Since we use dispatch_async internally for connection callback, give it a small amount of time
// to process so we know it didn't crash.
XCTestExpectation *expectation2 = [self expectationWithDescription:@"Wait for async execution"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
[expectation2 fulfill];
});
[self waitForExpectations:@[ expectation2 ] timeout:1.0];
}
- (void)testOpenServerSocket_Failure {
@@ -493,7 +624,9 @@ static const char *const kTestServiceID = "TestServiceID";
void (^completion)(NSError *error)) {
completion([NSError errorWithDomain:@"test" code:0 userInfo:nil]);
});
nearby::apple::BleMediumPeer::SetSocketPeripheralManager(_medium.get(), mockPeripheralManager);
nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() {
return mockPeripheralManager;
});
auto server_socket = _medium->OpenServerSocket(kTestServiceID);
@@ -508,7 +641,9 @@ static const char *const kTestServiceID = "TestServiceID";
void (^completion)(NSError *error)){
// Do not call completion to simulate timeout.
});
nearby::apple::BleMediumPeer::SetSocketPeripheralManager(_medium.get(), mockPeripheralManager);
nearby::apple::BleMediumPeer::SetPeripheralManagerFactory(_medium.get(), ^() {
return mockPeripheralManager;
});
auto server_socket = _medium->OpenServerSocket(kTestServiceID);
@@ -646,18 +781,20 @@ static const char *const kTestServiceID = "TestServiceID";
NSDictionary<CBUUID *, NSData *> *serviceData =
@{[CBUUID UUIDWithString:kTestServiceUUIDString] : [NSData dataWithBytes:"test" length:4]};
__block XCTestExpectation *expectation1 = [self expectationWithDescription:@"Callback 1"];
XCTestExpectation *expectation1 = [self expectationWithDescription:@"Callback 1"];
XCTestExpectation *expectation2 = [self expectationWithDescription:@"Callback 2"];
expectation2.inverted = YES; // Should NOT be called.
auto callback1_fulfilled = std::make_shared<std::atomic<bool>>(false);
nearby::api::ble::BleMedium::ScanCallback callback = {
.advertisement_found_cb = std::function<void(nearby::api::ble::BlePeripheral::UniqueId,
nearby::api::ble::BleAdvertisementData)>(
^(nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
const nearby::api::ble::BleAdvertisementData &advertisement) {
if ([expectation1.description isEqualToString:@"Callback 1"]) {
[callback1_fulfilled, expectation1, expectation2](
nearby::api::ble::BlePeripheral::UniqueId peripheral_id,
const nearby::api::ble::BleAdvertisementData &advertisement) {
if (!callback1_fulfilled->exchange(true)) {
[expectation1 fulfill];
expectation1 = nil; // Prevent double fulfillment
} else {
[expectation2 fulfill];
}
@@ -42,8 +42,9 @@
- (void)testBleServerSocketAccept {
XCTestExpectation *expectation = [self expectationWithDescription:@"accept"];
nearby::apple::BleServerSocket *serverSocket = _serverSocket.get();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::unique_ptr<nearby::api::ble::BleSocket> clientSocket = _serverSocket->Accept();
std::unique_ptr<nearby::api::ble::BleSocket> clientSocket = serverSocket->Accept();
XCTAssertNotEqual(clientSocket.get(), nullptr);
[expectation fulfill];
});
@@ -57,8 +58,9 @@
- (void)testBleServerSocketClose {
XCTestExpectation *expectation = [self expectationWithDescription:@"close"];
nearby::apple::BleServerSocket *serverSocket = _serverSocket.get();
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
std::unique_ptr<nearby::api::ble::BleSocket> clientSocket = _serverSocket->Accept();
std::unique_ptr<nearby::api::ble::BleSocket> clientSocket = serverSocket->Accept();
XCTAssertEqual(clientSocket.get(), nullptr);
[expectation fulfill];
});
@@ -15,7 +15,7 @@
#include "internal/platform/implementation/apple/atomic_boolean.h"
#include "gtest/gtest.h"
#include "thread/fiber/fiber.h"
#include "third_party/gloop/thread/fiber/fiber.h"
namespace nearby {
namespace apple {
@@ -15,7 +15,7 @@
#include "internal/platform/implementation/apple/atomic_uint32.h"
#include "gtest/gtest.h"
#include "thread/fiber/fiber.h"
#include "third_party/gloop/thread/fiber/fiber.h"
namespace nearby {
namespace apple {
+25 -6
View File
@@ -19,6 +19,7 @@
#include <string>
#include <utility>
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h"
@@ -35,12 +36,22 @@ AwdlInputStream::AwdlInputStream(GNCNWFrameworkSocket* socket) : socket_(socket)
ExceptionOr<ByteArray> AwdlInputStream::Read(std::int64_t size) {
NSError* error = nil;
NSData* data = [socket_ readMaxLength:size error:&error];
if (data == nil) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
if (GNCFeatureFlags.singleCopyEnabled) {
auto result = [socket_ readStringWithMaxLength:size error:&error];
if (!result.has_value()) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
}
// OPTIMIZATION: Zero-copy transfer from std::string to ByteArray
return ExceptionOr<ByteArray>{ByteArray(std::move(result.value()))};
} else {
NSData* data = [socket_ readMaxLength:size error:&error];
if (data == nil) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
}
return ExceptionOr<ByteArray>{ByteArray((const char*)data.bytes, data.length)};
}
return ExceptionOr<ByteArray>{ByteArray((const char*)data.bytes, data.length)};
}
Exception AwdlInputStream::Close() {
@@ -55,7 +66,15 @@ AwdlOutputStream::AwdlOutputStream(GNCNWFrameworkSocket* socket) : socket_(socke
Exception AwdlOutputStream::Write(absl::string_view data) {
NSError* error = nil;
BOOL result = [socket_ write:[NSData dataWithBytes:data.data() length:data.size()] error:&error];
BOOL result = NO;
if (GNCFeatureFlags.singleCopyEnabled) {
// OPTIMIZATION: Write raw bytes directly, avoiding NSData creation.
result = [socket_ writeBytes:data.data() length:data.size() error:&error];
} else {
result = [socket_ write:[NSData dataWithBytes:data.data() length:data.size()] error:&error];
}
if (!result) {
GNCLoggerError(@"Error writing socket: %@", error);
return {Exception::kIo};
@@ -66,16 +66,6 @@ class GattClient : public api::ble::GattClient {
bool WriteCharacteristic(const api::ble::GattCharacteristic &characteristic,
absl::string_view value, api::ble::GattClient::WriteType type) override;
// Enable or disable notifications/indications for a given characteristic.
//
// Once notifications are enabled for a characteristic, on_characteristic_changed_cb will be
// triggered if the remote device indicates that the given characteristic has changed.
//
// Returns whether or not the subscription was successful.
bool SetCharacteristicSubscription(
const api::ble::GattCharacteristic &characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) override;
// Disconnects an established connection, or cancels a connection attempt currently in progress.
void Disconnect() override;
@@ -121,13 +121,6 @@ bool GattClient::WriteCharacteristic(const api::ble::GattCharacteristic &charact
return false;
}
// TODO(b/290385712): Implement.
bool GattClient::SetCharacteristicSubscription(
const api::ble::GattCharacteristic &characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)> on_characteristic_changed_cb) {
return false;
}
void GattClient::Disconnect() {
// There seems to be an issue between some iOS<>Android device pairs where the Android device will
// not connect to the iOS device if the iOS device disconnects and then attempts to reconnect.
@@ -14,9 +14,11 @@
#import "internal/platform/implementation/apple/ble_l2cap_socket.h"
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPConnection.h"
#import "internal/platform/implementation/apple/utils.h"
#include "internal/platform/implementation/ble.h"
namespace nearby {
@@ -52,40 +54,81 @@ BleL2capInputStream::~BleL2capInputStream() {
}
ExceptionOr<ByteArray> BleL2capInputStream::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 (GNCFeatureFlags.singleCopyEnabled) {
std::string dataToReturn;
bool success = false;
if (newDataPackets_.count > 0) {
// Add the packet data to the accumulated data.
for (NSData *data in newDataPackets_) {
if (data.length > 0) {
[accumulatedData_ appendData:data];
[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];
}
[newDataPackets_ removeAllObjects];
if (accumulatedData_.length > 0) {
std::int64_t sizeToReturn =
(accumulatedData_.length < size) ? accumulatedData_.length : size;
NSRange range = NSMakeRange(0, (NSUInteger)sizeToReturn);
// Copy bytes directly into std::string, avoiding [NSData subdataWithRange:]
dataToReturn.assign((const char *)accumulatedData_.bytes, sizeToReturn);
[accumulatedData_ replaceBytesInRange:range withBytes:nil length:0];
success = true;
break;
}
[condition_ wait];
}
[condition_ unlock];
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;
if (success) {
// OPTIMIZATION: Zero-copy transfer from std::string to ByteArray
return ExceptionOr<ByteArray>{ByteArray(std::move(dataToReturn))};
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
}
[condition_ wait];
}
[condition_ unlock];
if (dataToReturn) {
return ExceptionOr<ByteArray>{ByteArray((const char *)dataToReturn.bytes, dataToReturn.length)};
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
// Legacy Path
NSData *dataToReturn;
[condition_ lock];
while (true) {
if (!newDataPackets_) break;
if (newDataPackets_.count > 0) {
for (NSData *data in newDataPackets_) {
if (data.length > 0) {
[accumulatedData_ appendData:data];
}
}
[newDataPackets_ removeAllObjects];
}
if (accumulatedData_.length > 0) {
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) {
return ExceptionOr<ByteArray>{
ByteArray((const char *)dataToReturn.bytes, dataToReturn.length)};
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
}
}
}
@@ -111,7 +154,17 @@ Exception BleL2capOutputStream::Write(absl::string_view data) {
return {Exception::kIo};
}
NSMutableData *packet = [NSMutableData dataWithBytes:data.data() length:data.size()];
NSData *packet;
if (GNCFeatureFlags.singleCopyEnabled) {
// OPTIMIZATION: Use DISPATCH_DATA_DESTRUCTOR_DEFAULT to perform a
// single copy into a GCD-managed buffer. No NSData required.
dispatch_data_t dispatchData =
dispatch_data_create(data.data(), data.size(), nil, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
// dispatch_data_t is toll-free bridged to NSData
packet = (NSData *)dispatchData;
} else {
packet = [NSMutableData dataWithBytes:data.data() length:data.size()];
}
// Send the data, blocking until the completion handler is called.
__block BOOL isComplete = NO;
@@ -21,6 +21,7 @@
#import <Foundation/Foundation.h>
#include <functional>
#include <memory>
#include <string>
#include <vector>
@@ -50,6 +51,10 @@ class BleMedium : public api::ble::BleMedium {
friend class BleMediumPeer;
public:
// Define factory types for managers.
using PeripheralManagerFactory = std::function<GNSPeripheralManager *()>;
using CentralManagerFactory = std::function<GNSCentralManager *(CBUUID *)>;
BleMedium();
// For testing only.
explicit BleMedium(GNCBLEMedium *medium);
@@ -207,9 +212,20 @@ class BleMedium : public api::ble::BleMedium {
NSDictionary<CBUUID *, NSData *> *service_data);
NSDate *GetLastTimestampToCleanExpiredAdvertisementPackets();
// Opens a BLE server socket based on service ID with deadlock safety.
std::unique_ptr<api::ble::BleServerSocket> OpenServerSocketWithDeadlockSafety(
const std::string &service_id);
// Opens a BLE server socket based on service ID using the legacy implementation.
std::unique_ptr<api::ble::BleServerSocket> OpenServerSocketLegacy(const std::string &service_id);
// The executor for handling callbacks.
apple::SingleThreadExecutor callback_executor_;
// Factories for lazy initialization
PeripheralManagerFactory peripheral_manager_factory_ = nullptr;
CentralManagerFactory central_manager_factory_ = nullptr;
GNCBLEMedium *medium_;
PeripheralsMap peripherals_;
@@ -229,14 +245,22 @@ class BleMedium : public api::ble::BleMedium {
GNSPeripheralServiceManager *socketPeripheralServiceManager_;
GNSPeripheralManager *socketPeripheralManager_;
GNSCentralManager *socketCentralManager_;
absl::Mutex scanning_mutex_;
GNSCentralManager *socketCentralManager_ ABSL_GUARDED_BY(scanning_mutex_);
// Used for the blocking version of StartAdvertising and only has an advertisement found callback.
api::ble::BleMedium::ScanCallback scan_cb_;
std::shared_ptr<api::ble::BleMedium::ScanCallback> scan_cb_ ABSL_GUARDED_BY(scanning_mutex_);
// Used for the async version of StartAdvertising and has both an advertisement found and result
// callback.
api::ble::BleMedium::ScanningCallback scanning_cb_;
std::shared_ptr<api::ble::BleMedium::ScanningCallback> scanning_cb_
ABSL_GUARDED_BY(scanning_mutex_);
// Used for the BleServerSocket.
absl::Mutex server_socket_mutex_;
BleServerSocket *server_socket_ptr_ ABSL_GUARDED_BY(server_socket_mutex_) = nullptr;
// Used for the L2CAP server socket.
absl::Mutex l2cap_server_socket_mutex_;
BleL2capServerSocket *l2cap_server_socket_ptr_ = nullptr;
@@ -173,11 +173,19 @@ void BleMedium::HandleAdvertisementFound(id<GNCPeripheral> peripheral,
}
#endif
if (scanning_cb_.advertisement_found_cb) {
scanning_cb_.advertisement_found_cb(unique_id, data);
std::shared_ptr<api::ble::BleMedium::ScanningCallback> scanning_cb;
std::shared_ptr<api::ble::BleMedium::ScanCallback> scan_cb;
{
absl::MutexLock lock(&scanning_mutex_);
scanning_cb = scanning_cb_;
scan_cb = scan_cb_;
}
if (scan_cb_.advertisement_found_cb) {
scan_cb_.advertisement_found_cb(unique_id, data);
if (scanning_cb && scanning_cb->advertisement_found_cb) {
scanning_cb->advertisement_found_cb(unique_id, data);
}
if (scan_cb && scan_cb->advertisement_found_cb) {
scan_cb->advertisement_found_cb(unique_id, data);
}
}
@@ -185,7 +193,17 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
const Uuid &service_uuid, api::ble::TxPowerLevel tx_power_level,
api::ble::BleMedium::ScanningCallback callback) {
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
scanning_cb_ = std::move(callback);
{
absl::MutexLock lock(&scanning_mutex_);
scanning_cb_ = std::make_shared<api::ble::BleMedium::ScanningCallback>(std::move(callback));
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUID);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
}
}
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
@@ -193,8 +211,10 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
peripherals_.Clear();
ClearAdvertisementPacketsMap();
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
{
absl::MutexLock lock(&scanning_mutex_);
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -207,8 +227,13 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
}
completionHandler:^(NSError *error) {
blockError = error;
if (scanning_cb_.start_scanning_result) {
scanning_cb_.start_scanning_result(
std::shared_ptr<api::ble::BleMedium::ScanningCallback> scanning_cb;
{
absl::MutexLock lock(&scanning_mutex_);
scanning_cb = scanning_cb_;
}
if (scanning_cb && scanning_cb->start_scanning_result) {
scanning_cb->start_scanning_result(
error == nil ? absl::OkStatus()
: absl::InternalError(error.localizedDescription.UTF8String));
}
@@ -218,8 +243,13 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, kApiTimeoutInSeconds * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout) != 0) {
GNCLoggerError(@"Start scanning operation timed out.");
if (scanning_cb_.start_scanning_result) {
scanning_cb_.start_scanning_result(absl::DeadlineExceededError("Start scanning timed out"));
std::shared_ptr<api::ble::BleMedium::ScanningCallback> scanning_cb;
{
absl::MutexLock lock(&scanning_mutex_);
scanning_cb = scanning_cb_;
}
if (scanning_cb && scanning_cb->start_scanning_result) {
scanning_cb->start_scanning_result(absl::DeadlineExceededError("Start scanning timed out"));
}
return nullptr;
}
@@ -239,7 +269,17 @@ std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble::TxPowerLevel tx_power_level,
api::ble::BleMedium::ScanCallback callback) {
CBUUID *serviceUUID = CBUUID128FromCPP(service_uuid);
scan_cb_ = std::move(callback);
{
absl::MutexLock lock(&scanning_mutex_);
scan_cb_ = std::make_shared<api::ble::BleMedium::ScanCallback>(std::move(callback));
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUID);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
}
}
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
@@ -247,8 +287,10 @@ bool BleMedium::StartScanning(const Uuid &service_uuid, api::ble::TxPowerLevel t
peripherals_.Clear();
ClearAdvertisementPacketsMap();
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUID];
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
{
absl::MutexLock lock(&scanning_mutex_);
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUID ]];
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -286,7 +328,16 @@ bool BleMedium::StartMultipleServicesScanning(const std::vector<Uuid> &service_u
[serviceUUIDs addObject:CBUUID128FromCPP(service_uuid)];
}
scan_cb_ = std::move(callback);
{
absl::MutexLock lock(&scanning_mutex_);
scan_cb_ = std::make_shared<api::ble::BleMedium::ScanCallback>(std::move(callback));
if (central_manager_factory_) {
socketCentralManager_ = central_manager_factory_(serviceUUIDs[0]);
} else {
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUIDs[0]];
}
}
// Clear the map of discovered peripherals only when we are starting a new scan. If we cleared the
// map every time we stopped a scan, we would not be able to connect to peripherals that we
@@ -294,8 +345,10 @@ bool BleMedium::StartMultipleServicesScanning(const std::vector<Uuid> &service_u
peripherals_.Clear();
ClearAdvertisementPacketsMap();
socketCentralManager_ = [[GNSCentralManager alloc] initWithSocketServiceUUID:serviceUUIDs[0]];
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUIDs[0] ]];
{
absl::MutexLock lock(&scanning_mutex_);
[socketCentralManager_ startNoScanModeWithAdvertisedServiceUUIDs:@[ serviceUUIDs[0] ]];
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -321,7 +374,12 @@ bool BleMedium::StartMultipleServicesScanning(const std::vector<Uuid> &service_u
}
bool BleMedium::StopScanning() {
[socketCentralManager_ stopNoScanMode];
{
absl::MutexLock lock(&scanning_mutex_);
[socketCentralManager_ stopNoScanMode];
scan_cb_ = nullptr;
scanning_cb_ = nullptr;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
@@ -448,24 +506,118 @@ std::unique_ptr<api::ble::GattClient> BleMedium::ConnectToGattServer(
// TODO(b/293336684): Old Weave code that need to be deleted once shared Weave is complete.
std::unique_ptr<api::ble::BleServerSocket> BleMedium::OpenServerSocket(
const std::string &service_id) {
if (GNCFeatureFlags.fixBleServerSocketDeadlockEnabled) {
return OpenServerSocketWithDeadlockSafety(service_id);
} else {
return OpenServerSocketLegacy(service_id);
}
}
std::unique_ptr<api::ble::BleServerSocket> BleMedium::OpenServerSocketWithDeadlockSafety(
const std::string &service_id) {
auto server_socket = std::make_unique<BleServerSocket>();
__block auto server_socket_ptr = server_socket.get();
if (socketPeripheralManager_ == nil) {
socketPeripheralManager_ = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
restoreIdentifier:nil];
if (peripheral_manager_factory_) {
socketPeripheralManager_ = peripheral_manager_factory_();
} else {
socketPeripheralManager_ = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
restoreIdentifier:nil];
}
}
if (socketPeripheralManager_ == nil) {
GNCLoggerError(@"Failed to create peripheral manager.");
return nullptr;
// Fix for b/494335036 (Registry + Background Queue)
{
absl::MutexLock lock(server_socket_mutex_);
server_socket_ptr_ = server_socket.get();
}
server_socket->SetCloseNotifier([this]() {
absl::MutexLock lock(server_socket_mutex_);
server_socket_ptr_ = nullptr;
});
socketPeripheralServiceManager_ = [[GNSPeripheralServiceManager alloc]
initWithBleServiceUUID:[CBUUID UUIDWithString:kWeaveServiceUUID]
addPairingCharacteristic:NO
shouldAcceptSocketHandler:^BOOL(GNSSocket *socket) {
GNCMWaitForConnection(socket, ^(BOOL didConnect) {
// Optimized Path: Use background queue and registry validation.
GNCMWaitForConnection(socket, connection_callback_queue_, ^(BOOL didConnect) {
GNCMBleConnection *connection =
[GNCMBleConnection connectionWithSocket:socket
serviceID:nil
expectedIntroPacket:YES
callbackQueue:connection_callback_queue_];
auto socket_wrapper = std::make_unique<BleSocket>(connection);
socket_wrapper->SetCloseNotifier(
[socketPeripheralManager = socketPeripheralManager_,
serviceUUID = socketPeripheralServiceManager_.serviceUUID]() {
[socketPeripheralManager
removePeripheralServiceManagerForServiceUUID:serviceUUID
bleServiceRemovedCompletion:^(NSError *_Nullable error) {
GNCLoggerInfo(@"BleSocket is removed peripheral manager.");
}];
});
connection.connectionHandlers = socket_wrapper->GetInputStream().GetConnectionHandlers();
// Fix: Verify the BleServerSocket still exists before calling Connect().
// This prevents the use-after-free/deadlock reported in b/494335036.
absl::MutexLock lock(server_socket_mutex_);
if (server_socket_ptr_) {
server_socket_ptr_->Connect(std::move(socket_wrapper));
GNCLoggerInfo(@"BleServerSocket is created with connection");
} else {
GNCLoggerWarning(@"BleServerSocket was destroyed; ignoring connection.");
}
});
return YES;
}];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
[socketPeripheralManager_ addPeripheralServiceManager:socketPeripheralServiceManager_
bleServiceAddedCompletion:^(NSError *error) {
if (error != nil) {
GNCLoggerError(@"Failed to add Weave service: %@", error);
blockError = error;
}
dispatch_semaphore_signal(semaphore);
}];
[socketPeripheralManager_ start];
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, kApiTimeoutInSeconds * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, timeout) != 0) {
GNCLoggerError(@"OpenServerSocket operation timed out.");
return nullptr;
}
if (blockError != nil) {
return nullptr;
}
return std::move(server_socket);
}
std::unique_ptr<api::ble::BleServerSocket> BleMedium::OpenServerSocketLegacy(
const std::string &service_id) {
auto server_socket = std::make_unique<BleServerSocket>();
if (socketPeripheralManager_ == nil) {
if (peripheral_manager_factory_) {
socketPeripheralManager_ = peripheral_manager_factory_();
} else {
socketPeripheralManager_ = [[GNSPeripheralManager alloc] initWithAdvertisedName:nil
restoreIdentifier:nil];
}
}
// Raw pointer for closure capture in the legacy path (risks use-after-free).
BleServerSocket *server_socket_ptr = server_socket.get();
socketPeripheralServiceManager_ = [[GNSPeripheralServiceManager alloc]
initWithBleServiceUUID:[CBUUID UUIDWithString:kWeaveServiceUUID]
addPairingCharacteristic:NO
shouldAcceptSocketHandler:^BOOL(GNSSocket *socket) {
// Legacy Path: Verbatim copy of original code (blocks Main Thread).
GNCMWaitForConnection(socket, nil, ^(BOOL didConnect) {
GNCMBleConnection *connection =
[GNCMBleConnection connectionWithSocket:socket
// This must be nil as the advertiser even though we
@@ -588,8 +740,12 @@ std::unique_ptr<api::ble::BleSocket> BleMedium::Connect(
return nullptr;
}
GNSCentralPeerManager *updatedCentralPeerManager =
[socketCentralManager_ retrieveCentralPeerWithIdentifier:peripheral.identifier];
GNSCentralPeerManager *updatedCentralPeerManager;
{
absl::MutexLock lock(&scanning_mutex_);
updatedCentralPeerManager =
[socketCentralManager_ retrieveCentralPeerWithIdentifier:peripheral.identifier];
}
if (!updatedCentralPeerManager) {
return nullptr;
}
@@ -603,7 +759,14 @@ std::unique_ptr<api::ble::BleSocket> BleMedium::Connect(
dispatch_semaphore_signal(semaphore);
return;
}
GNCMWaitForConnection(nssocket, ^(BOOL didConnect) {
// Suggestion: Use the connection callback queue instead of nil
dispatch_queue_t targetQueue =
GNCFeatureFlags.fixBleServerSocketDeadlockEnabled
? connection_callback_queue_
: nil;
GNCMWaitForConnection(nssocket, targetQueue, ^(BOOL didConnect) {
if (!didConnect) {
dispatch_semaphore_signal(semaphore);
return;
@@ -14,13 +14,13 @@
#import "internal/platform/implementation/apple/ble_socket.h"
#include "internal/platform/implementation/ble.h"
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.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"
#include "internal/platform/implementation/ble.h"
// TODO(b/293336684): Remove this file when shared Weave is complete.
namespace nearby {
@@ -55,45 +55,94 @@ BleInputStream::~BleInputStream() {
}
ExceptionOr<ByteArray> 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 (GNCFeatureFlags.singleCopyEnabled) {
std::string dataToReturn;
bool success = false;
if (newDataPackets_.count > 0) {
// Add the packet data to the accumulated data.
for (NSData *data in newDataPackets_) {
if (data.length > 0) {
[accumulatedData_ appendData:data];
[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];
}
[newDataPackets_ removeAllObjects];
if ((size == -1) && (accumulatedData_.length > 0)) {
// Return all of the data.
dataToReturn.assign((const char *)accumulatedData_.bytes, accumulatedData_.length);
accumulatedData_ = [NSMutableData data];
success = true;
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);
// Copy bytes directly into std::string, avoiding [NSData subdataWithRange:]
dataToReturn.assign((const char *)accumulatedData_.bytes, sizeToReturn);
[accumulatedData_ replaceBytesInRange:range withBytes:nil length:0];
success = true;
break;
}
[condition_ wait];
}
[condition_ unlock];
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;
if (success) {
// OPTIMIZATION: Zero-copy transfer from std::string to ByteArray
return ExceptionOr<ByteArray>{ByteArray(std::move(dataToReturn))};
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
}
[condition_ wait];
}
[condition_ unlock];
if (dataToReturn) {
return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn));
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
// Legacy path
NSData *dataToReturn;
[condition_ lock];
while (true) {
// Check if the stream has been closed or severed.
if (!newDataPackets_) break;
if (newDataPackets_.count > 0) {
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) {
return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn));
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
}
}
}
@@ -119,7 +168,17 @@ Exception BleOutputStream::Write(absl::string_view data) {
return {Exception::kIo};
}
NSMutableData *packet = [NSMutableData dataWithBytes:data.data() length:data.size()];
NSData *packet;
if (GNCFeatureFlags.singleCopyEnabled) {
// OPTIMIZATION: Use DISPATCH_DATA_DESTRUCTOR_DEFAULT to perform a
// single copy into a GCD-managed buffer. No NSData required.
dispatch_data_t dispatchData =
dispatch_data_create(data.data(), data.size(), nil, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
// dispatch_data_t is toll-free bridged to NSData
packet = (NSData *)dispatchData;
} else {
packet = [NSMutableData dataWithBytes:data.data() length:data.size()];
}
// Send the data, blocking until the completion handler is called.
__block bool isComplete = NO;
@@ -16,8 +16,8 @@
#include "gtest/gtest.h"
#include "absl/time/clock.h"
#include "third_party/gloop/thread/fiber/fiber.h"
#include "internal/platform/implementation/apple/mutex.h"
#include "thread/fiber/fiber.h"
namespace nearby {
namespace apple {
@@ -18,7 +18,7 @@
#include "gtest/gtest.h"
#include "absl/time/time.h"
#include "thread/fiber/fiber.h"
#include "third_party/gloop/thread/fiber/fiber.h"
namespace nearby {
namespace apple {
@@ -18,7 +18,7 @@
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/notification.h"
#include "absl/time/time.h"
#include "thread/fiber/fiber.h"
#include "third_party/gloop/thread/fiber/fiber.h"
namespace nearby {
namespace apple {
@@ -45,10 +45,6 @@
#include "internal/platform/implementation/shared/file.h"
#include "internal/platform/payload_id.h"
#ifndef NO_WEBRTC
#import "internal/platform/implementation/apple/webrtc.h"
#endif
namespace nearby {
namespace api {
@@ -205,12 +201,6 @@ std::unique_ptr<WifiDirectMedium> ImplementationPlatform::CreateWifiDirectMedium
return nullptr;
}
#ifndef NO_WEBRTC
std::unique_ptr<WebRtcMedium> ImplementationPlatform::CreateWebRtcMedium() {
return std::make_unique<apple::WebRtcMedium>();
}
#endif
std::unique_ptr<AppLifecycleMonitor> ImplementationPlatform::CreateAppLifecycleMonitor(
std::function<void(AppLifecycleMonitor::AppLifecycleState)> state_updated_callback) {
#if TARGET_OS_IPHONE
@@ -27,11 +27,15 @@ bool CppBoolFromObjCBool(BOOL b) { return b ? true : false; }
char CharFromNSNumber(NSNumber* n) { return n.charValue; }
NSString* ObjCStringFromCppString(absl::string_view s) {
return [NSString stringWithUTF8String:s.data()];
return [[NSString alloc] initWithBytes:s.data() length:s.size() encoding:NSUTF8StringEncoding];
}
std::string CppStringFromObjCString(NSString* s) {
return std::string([s UTF8String], [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
if (!s) return std::string();
const char* cstr = [s UTF8String];
if (!cstr) return std::string();
NSUInteger len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
return std::string(cstr, len);
}
NSData* NSDataFromByteArray(ByteArray byteArray) {
@@ -1,62 +0,0 @@
// 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 PLATFORM_IMPL_APPLE_WEBRTC_H_
#define PLATFORM_IMPL_APPLE_WEBRTC_H_
#ifndef NO_WEBRTC
#include <memory>
#include <optional>
#include <string>
#include "absl/strings/string_view.h"
#include "internal/platform/implementation/webrtc.h"
#include "webrtc/api/peer_connection_interface.h"
namespace nearby::apple {
class WebRtcMedium : public api::WebRtcMedium {
public:
~WebRtcMedium() override = default;
// Gets the default two-letter country code associated with current locale.
// For example, en_US locale resolves to "US".
// This follows the ISO 3166-1 Alpha-2 standard.
std::string GetDefaultCountryCode() override;
// Creates and returns a new webrtc::PeerConnectionInterface object via
// |callback|.
void CreatePeerConnection(webrtc::PeerConnectionObserver* observer,
PeerConnectionCallback callback) override;
// Creates and returns a new webrtc::PeerConnectionInterface object via
// |callback| with |PeerConnectionFactoryInterface::Options|.
void CreatePeerConnection(
std::optional<webrtc::PeerConnectionFactoryInterface::Options> options,
webrtc::PeerConnectionObserver* observer,
PeerConnectionCallback callback) override;
// Returns a signaling messenger for sending WebRTC signaling messages.
std::unique_ptr<api::WebRtcSignalingMessenger> GetSignalingMessenger(
absl::string_view self_id,
const location::nearby::connections::LocationHint& location_hint)
override;
};
} // namespace nearby::apple
#endif // #ifndef NO_WEBRTC
#endif // PLATFORM_IMPL_APPLE_WEBRTC_H_
@@ -1,99 +0,0 @@
// 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 NO_WEBRTC
#include "internal/platform/implementation/apple/webrtc.h"
#import <Foundation/Foundation.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/crypto.h"
#include "internal/platform/logging.h"
#include "internal/platform/tachyon_express_signaling_messenger.h"
#include "internal/proto/tachyon.pb.h"
#include "internal/proto/tachyon_enums.proto.h"
#include "webrtc/api/create_modular_peer_connection_factory.h"
#include "webrtc/api/task_queue/default_task_queue_factory.h"
namespace nearby::apple {
std::string WebRtcMedium::GetDefaultCountryCode() {
NSString* countryCode = [NSLocale.currentLocale objectForKey:NSLocaleCountryCode];
if (countryCode) {
return std::string([countryCode UTF8String]);
}
return "US";
}
void WebRtcMedium::CreatePeerConnection(webrtc::PeerConnectionObserver* observer,
PeerConnectionCallback callback) {
CreatePeerConnection(std::nullopt, observer, std::move(callback));
}
void WebRtcMedium::CreatePeerConnection(
std::optional<webrtc::PeerConnectionFactoryInterface::Options> options,
webrtc::PeerConnectionObserver* observer, PeerConnectionCallback callback) {
webrtc::PeerConnectionInterface::RTCConfiguration rtc_config;
rtc_config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
// TODO: b/261663238 - Add the TURN servers and go beyond the default servers.
webrtc::PeerConnectionInterface::IceServer ice_server;
ice_server.urls.emplace_back("stun:stun.l.google.com:19302");
ice_server.urls.emplace_back("stun:stun1.l.google.com:19302");
ice_server.urls.emplace_back("stun:stun2.l.google.com:19302");
ice_server.urls.emplace_back("stun:stun3.l.google.com:19302");
ice_server.urls.emplace_back("stun:stun4.l.google.com:19302");
rtc_config.servers.push_back(ice_server);
std::unique_ptr<webrtc::Thread> signaling_thread = webrtc::Thread::Create();
signaling_thread->SetName("signaling_thread", nullptr);
if (!signaling_thread->Start()) {
callback(/*peer_connection=*/nullptr);
return;
}
webrtc::PeerConnectionDependencies dependencies(observer);
webrtc::PeerConnectionFactoryDependencies factory_dependencies;
factory_dependencies.signaling_thread = signaling_thread.release();
webrtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> peer_connection_factory =
webrtc::CreateModularPeerConnectionFactory(std::move(factory_dependencies));
if (options.has_value()) {
peer_connection_factory->SetOptions(options.value());
}
webrtc::RTCErrorOr<webrtc::scoped_refptr<webrtc::PeerConnectionInterface>>
peer_connection_or_error =
peer_connection_factory->CreatePeerConnectionOrError(rtc_config, std::move(dependencies));
if (peer_connection_or_error.ok()) {
callback(peer_connection_or_error.MoveValue());
} else {
callback(/*peer_connection=*/nullptr);
}
}
std::unique_ptr<api::WebRtcSignalingMessenger> WebRtcMedium::GetSignalingMessenger(
absl::string_view self_id, const location::nearby::connections::LocationHint& location_hint) {
return std::make_unique<TachyonExpressSignalingMessenger>(self_id, location_hint);
}
} // namespace nearby::apple
#endif // #ifndef NO_WEBRTC
@@ -0,0 +1,39 @@
// Copyright 2026 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 "internal/platform/implementation/webrtc_platform.h"
#import <Foundation/Foundation.h>
#include <memory>
#include "connections/implementation/mediums/webrtc/webrtc_medium_impl.h"
namespace nearby {
namespace api {
std::unique_ptr<WebRtcMedium> WebRtcImplementationPlatform::CreateWebRtcMedium() {
return std::make_unique<nearby::connections::mediums::WebRtcMediumImpl>();
}
std::string WebRtcImplementationPlatform::GetDefaultCountryCode() {
NSString* countryCode = [NSLocale.currentLocale objectForKey:NSLocaleCountryCode];
if (countryCode) {
return std::string([countryCode UTF8String]);
}
return "US";
}
} // namespace api
} // namespace nearby
@@ -23,6 +23,8 @@
#include <arpa/inet.h>
#include "internal/base/masker.h"
#include "internal/platform/cancellation_flag_listener.h"
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h"
@@ -43,12 +45,22 @@ WifiHotspotInputStream::WifiHotspotInputStream(GNCNWFrameworkSocket* socket) : s
ExceptionOr<ByteArray> WifiHotspotInputStream::Read(std::int64_t size) {
NSError* error = nil;
NSData* data = [socket_ readMaxLength:size error:&error];
if (data == nil) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
if (GNCFeatureFlags.singleCopyEnabled) {
auto result = [socket_ readStringWithMaxLength:size error:&error];
if (!result.has_value()) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
}
// OPTIMIZATION: Zero-copy transfer from std::string to ByteArray
return ExceptionOr<ByteArray>{ByteArray(std::move(result.value()))};
} else {
NSData* data = [socket_ readMaxLength:size error:&error];
if (data == nil) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
}
return ExceptionOr<ByteArray>{ByteArray((const char*)data.bytes, data.length)};
}
return ExceptionOr<ByteArray>{ByteArray((const char*)data.bytes, data.length)};
}
Exception WifiHotspotInputStream::Close() {
@@ -63,7 +75,15 @@ WifiHotspotOutputStream::WifiHotspotOutputStream(GNCNWFrameworkSocket* socket) :
Exception WifiHotspotOutputStream::Write(absl::string_view data) {
NSError* error = nil;
BOOL result = [socket_ write:[NSData dataWithBytes:data.data() length:data.size()] error:&error];
BOOL result = NO;
if (GNCFeatureFlags.singleCopyEnabled) {
// OPTIMIZATION: Write raw bytes directly, avoiding NSData creation.
result = [socket_ writeBytes:data.data() length:data.size() error:&error];
} else {
result = [socket_ write:[NSData dataWithBytes:data.data() length:data.size()] error:&error];
}
if (!result) {
GNCLoggerError(@"Error writing socket: %@", error);
return {Exception::kIo};
@@ -150,7 +170,7 @@ std::unique_ptr<api::WifiHotspotSocket> WifiHotspotMedium::ConnectToService(
}
// 4 bytes IP address format.
NSData* host_ip_address = [NSData dataWithBytes:service_address.address.data()
length:service_address.address.size()];
length:service_address.address.size()];
host = [GNCIPv4Address addressFromData:host_ip_address];
GNCLoggerInfo(@"Connect to Hotspot host server: %@", [host dottedRepresentation]);
@@ -19,6 +19,7 @@
#include <string>
#include <utility>
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h"
@@ -35,12 +36,22 @@ WifiLanInputStream::WifiLanInputStream(GNCNWFrameworkSocket* socket) : socket_(s
ExceptionOr<ByteArray> WifiLanInputStream::Read(std::int64_t size) {
NSError* error = nil;
NSData* data = [socket_ readMaxLength:size error:&error];
if (data == nil) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
if (GNCFeatureFlags.singleCopyEnabled) {
auto result = [socket_ readStringWithMaxLength:size error:&error];
if (!result.has_value()) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
}
// OPTIMIZATION: Zero-copy transfer from std::string to ByteArray
return ExceptionOr<ByteArray>{ByteArray(std::move(result.value()))};
} else {
NSData* data = [socket_ readMaxLength:size error:&error];
if (data == nil) {
GNCLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
}
return ExceptionOr<ByteArray>{ByteArray((const char*)data.bytes, data.length)};
}
return ExceptionOr<ByteArray>{ByteArray((const char*)data.bytes, data.length)};
}
Exception WifiLanInputStream::Close() {
@@ -55,7 +66,15 @@ WifiLanOutputStream::WifiLanOutputStream(GNCNWFrameworkSocket* socket) : socket_
Exception WifiLanOutputStream::Write(absl::string_view data) {
NSError* error = nil;
BOOL result = [socket_ write:[NSData dataWithBytes:data.data() length:data.size()] error:&error];
BOOL result = NO;
if (GNCFeatureFlags.singleCopyEnabled) {
// OPTIMIZATION: Write raw bytes directly, avoiding NSData creation.
result = [socket_ writeBytes:data.data() length:data.size() error:&error];
} else {
result = [socket_ write:[NSData dataWithBytes:data.data() length:data.size()] error:&error];
}
if (!result) {
GNCLoggerError(@"Error writing socket: %@", error);
return {Exception::kIo};
@@ -1,88 +0,0 @@
// Copyright 2024 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 THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_AUTH_STATUS_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_AUTH_STATUS_H_
namespace nearby {
enum AuthStatus {
AUTH_STATUS_UNSPECIFIED = 0,
// Request completed successfully, the results should be in the correct order
// up to the given count.
SUCCESS = 1,
// Request encountered a generic error.
GENERIC_ERROR = 2,
// Request as specified is not supported.
UNSUPPORTED = 3,
// Request failed and should be retried soon.
TEMPORARILY_UNAVAILABLE = 4,
// Request failed due to an unavailable resource.
UNAVAILABLE_RESOURCE = 5,
// The request failed due to an invalid argument.
INVALID_ARGUMENT = 6,
// In case the status could not be retrieved.
UNKNOWN_STATUS = 7,
// Currently used as a way to signal an ETag mismatch.
PRECONDITION_FAILED = 8,
// Exclusively used to report when user did not consent to required scopes.
// Do NOT use this for another other scenarios.
PERMISSION_DENIED = 9,
// The resource exists, but the requested attribute of it does not.
MISSING_ATTRIBUTE = 10,
// The method was interrupted and the caller should exit the current unit of
// work immediately.
INTERRUPTED = 11,
// User signed in with an unexpected account.
SIGNED_IN_WITH_WRONG_ACCOUNT = 12,
// Used when data cannot be parsed properly.
PARSE_ERROR = 13,
// Used to report that the local HTTP server for receiving the authorization
// code cannot be created.
CANT_CREATE_AUTH_SERVER = 14,
// Used to report that the system browser for authenticating the user cannot
// be open.
CANT_OPEN_BROWSER_FOR_AUTH = 15,
// Used to report that the authorization code cannot be received.
CANT_RECEIVE_AUTH_CODE = 16,
// Used to report that the account is blocked (e.g. CAA).
ACCOUNT_BLOCKED = 17,
// Receiving the authorization code failed because it took longer than the
// timeout.
AUTH_CODE_TIMEOUT_EXCEEDED = 18,
// Used to report when user presses the cancel button during login process.
USER_CANCELED = 19,
};
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_AUTH_STATUS_H_
+9 -18
View File
@@ -29,7 +29,6 @@
#include "absl/status/statusor.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/exception.h"
@@ -155,26 +154,26 @@ struct GattCharacteristic {
Property property;
// overloading operator for enum class Permission and Property
friend inline Permission operator|(Permission a, Permission b) {
friend Permission operator|(Permission a, Permission b) {
return static_cast<Permission>(static_cast<int>(a) | static_cast<int>(b));
}
friend inline Permission operator&(Permission a, Permission b) {
friend Permission operator&(Permission a, Permission b) {
return static_cast<Permission>(static_cast<int>(a) & static_cast<int>(b));
}
friend inline Permission& operator|=(Permission& a, Permission b) {
friend Permission& operator|=(Permission& a, Permission b) {
a = a | b;
return a;
}
friend inline Property operator|(Property a, Property b) {
friend Property operator|(Property a, Property b) {
return static_cast<Property>(static_cast<int>(a) | static_cast<int>(b));
}
friend inline Property operator&(Property a, Property b) {
friend Property operator&(Property a, Property b) {
return static_cast<Property>(static_cast<int>(a) & static_cast<int>(b));
}
friend inline Property& operator|=(Property& a, Property b) {
friend Property& operator|=(Property& a, Property b) {
a = a | b;
return a;
}
@@ -231,13 +230,13 @@ class GattClient {
// It is okay for duplicate services to exist, as long as the specified
// characteristic UUID is unique among all services of the same UUID.
// NOLINTNEXTLINE(google3-legacy-absl-backports)
virtual absl::optional<GattCharacteristic> GetCharacteristic(
virtual std::optional<GattCharacteristic> GetCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic)
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue()
// NOLINTNEXTLINE(google3-legacy-absl-backports)
virtual absl::optional<std::string> ReadCharacteristic(
virtual std::optional<std::string> ReadCharacteristic(
const GattCharacteristic& characteristic) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[])
@@ -248,14 +247,6 @@ class GattClient {
virtual bool WriteCharacteristic(const GattCharacteristic& characteristic,
absl::string_view value, WriteType type) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic,%20boolean)
//
// Enable or disable notifications/indications for a given characteristic.
virtual bool SetCharacteristicSubscription(
const GattCharacteristic& characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#disconnect()
virtual void Disconnect() = 0;
};
@@ -281,7 +272,7 @@ class GattServer {
// more information about this descriptor, please go to:
// https://www.bluetooth.com/specifications/Gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.Gatt.client_characteristic_configuration.xml
// NOLINTNEXTLINE(google3-legacy-absl-backports)
virtual absl::optional<GattCharacteristic> CreateCharacteristic(
virtual std::optional<GattCharacteristic> CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid,
GattCharacteristic::Permission permission,
GattCharacteristic::Property property) = 0;
@@ -15,12 +15,14 @@
#ifndef PLATFORM_API_DEVICE_INFO_H_
#define PLATFORM_API_DEVICE_INFO_H_
#include <cstddef>
#include <functional>
#include <optional>
#include <string>
#include "absl/strings/string_view.h"
#include "internal/base/file_path.h"
#include "internal/base/files.h"
namespace nearby {
namespace api {
@@ -51,6 +53,11 @@ class DeviceInfo {
virtual FilePath GetTemporaryPath() const = 0;
virtual FilePath GetLogPath() const = 0;
virtual std::optional<size_t> GetAvailableDiskSpaceInBytes(
const FilePath& path) const {
return Files::GetAvailableDiskSpaceInBytes(path);
};
// Monitor screen status
virtual bool IsScreenLocked() const = 0;
virtual void RegisterScreenLockedListener(
@@ -64,6 +71,24 @@ class DeviceInfo {
virtual bool AllowSleep() = 0;
};
template <typename Sink>
void AbslStringify(Sink& sink, DeviceInfo::DeviceType device_type) {
switch (device_type) {
case DeviceInfo::DeviceType::kUnknown:
sink.Append("Unknown");
return;
case DeviceInfo::DeviceType::kPhone:
sink.Append("Phone");
return;
case DeviceInfo::DeviceType::kTablet:
sink.Append("Tablet");
return;
case DeviceInfo::DeviceType::kLaptop:
sink.Append("PC");
return;
}
}
} // namespace api
} // namespace nearby
@@ -0,0 +1,29 @@
// Copyright 2026 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 "internal/platform/implementation/device_info.h"
#include "gtest/gtest.h"
#include "absl/strings/str_cat.h"
namespace nearby::api {
namespace {
TEST(DeviceInfoTest, DeviceTypeToStringTest) {
DeviceInfo::DeviceType type = DeviceInfo::DeviceType::kPhone;
EXPECT_EQ(absl::StrCat(type), "Phone");
}
} // namespace
} // namespace nearby::api
+13 -7
View File
@@ -85,6 +85,7 @@ cc_library(
"bluetooth_adapter.cc",
"bluetooth_classic.cc",
"credential_storage_impl.cc",
"webrtc.cc",
"wifi_direct.cc",
"wifi_hotspot.cc",
"wifi_lan.cc",
@@ -96,6 +97,7 @@ cc_library(
"bluetooth_classic.h",
"credential_storage_impl.h",
"socket_base.h",
"webrtc.h",
"wifi.h",
"wifi_direct.h",
"wifi_hotspot.h",
@@ -112,11 +114,14 @@ cc_library(
"//internal/platform:types",
"//internal/platform:uuid",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:webrtc_platform",
"//internal/platform/implementation:wifi_utils",
"//internal/proto:credential_cc_proto",
# "//third_party/webrtc/files/stable/webrtc/api:create_modular_peer_connection_factory",
# "//third_party/webrtc/files/stable/webrtc/api:peer_connection_interface",
# "//third_party/webrtc/files/stable/webrtc/api:scoped_refptr",
"//third_party/webrtc/files/stable/webrtc/api:create_modular_peer_connection_factory",
"//third_party/webrtc/files/stable/webrtc/api:peer_connection_interface",
"//third_party/webrtc/files/stable/webrtc/api:scoped_refptr",
"//third_party/webrtc/files/stable/webrtc/rtc_base:checks",
"//third_party/webrtc/files/stable/webrtc/rtc_base:threading",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
@@ -177,8 +182,8 @@ cc_library(
testonly = True,
srcs = [
"platform.cc",
"webrtc_platform.cc",
],
defines = ["NO_WEBRTC"],
visibility = [
"//connections:__subpackages__",
"//connections:partners",
@@ -191,10 +196,9 @@ cc_library(
"//internal/preferences:__subpackages__",
"//internal/proto/analytics:__subpackages__",
"//internal/weave:__subpackages__",
"//location/nearby/cpp:__subpackages__",
"//location/nearby/sharing/sdk:__subpackages__",
"//presence:__subpackages__",
"//sharing:__subpackages__",
"//third_party/nearby/presence:__subpackages__",
],
deps = [
":comm",
@@ -208,14 +212,16 @@ cc_library(
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"//internal/platform/implementation:webrtc_platform",
"//internal/platform/implementation/shared:count_down_latch",
"//internal/platform/implementation/shared:file",
"//third_party/gloop/thread",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_nisaba//nisaba/port:thread_pool",
],
alwayslink = 1,
)
cc_library(
@@ -679,33 +679,6 @@ bool BleMedium::GattClient::WriteCharacteristic(
return status.ok();
}
bool BleMedium::GattClient::SetCharacteristicSubscription(
const api::ble::GattCharacteristic& characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) {
absl::MutexLock lock(mutex_);
if (!is_connection_alive_) {
return false;
}
Borrowed<api::ble::GattServer*> borrowed = gatt_server_.Borrow();
if (!borrowed) {
return false;
}
BleMedium::GattServer* gatt_server =
static_cast<BleMedium::GattServer*>(*borrowed);
LOG(INFO) << "G3 Ble SetCharacteristicSubscription, characteristic=("
<< characteristic.service_uuid.Get16BitAsString() << ","
<< std::string(characteristic.uuid) << "), enable = " << enable;
if (enable) {
return gatt_server->AddCharacteristicSubscription(
peripheral_id_, characteristic,
std::move(on_characteristic_changed_cb));
} else {
return gatt_server->RemoveCharacteristicSubscription(peripheral_id_,
characteristic);
}
}
void BleMedium::GattClient::Disconnect() {
bool was_alive = is_connection_alive_.exchange(false);
if (!was_alive) return;
@@ -282,11 +282,6 @@ class BleMedium : public api::ble::BleMedium {
absl::string_view value,
api::ble::GattClient::WriteType write_type) override;
bool SetCharacteristicSubscription(
const api::ble::GattCharacteristic& characteristic, bool enable,
absl::AnyInvocable<void(absl::string_view value)>
on_characteristic_changed_cb) override;
void Disconnect() override;
void OnServerDisconnected();
@@ -14,7 +14,6 @@
#include "internal/platform/implementation/platform.h"
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
@@ -25,6 +24,7 @@
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "third_party/gloop/thread/thread.h"
#include "internal/base/file_path.h"
#include "internal/base/files.h"
#include "internal/platform/implementation/app_lifecycle_monitor.h"
@@ -55,11 +55,6 @@
#include "internal/platform/logging.h"
#include "internal/platform/os_name.h"
#include "internal/platform/payload_id.h"
#include "thread/thread.h"
#ifndef NO_WEBRTC
#include "internal/platform/implementation/g3/webrtc.h"
#include "internal/platform/implementation/webrtc.h"
#endif
#include "internal/platform/implementation/g3/atomic_boolean.h"
#include "internal/platform/implementation/g3/atomic_reference.h"
#include "internal/platform/implementation/g3/ble.h"
@@ -80,7 +75,6 @@
#include "internal/platform/implementation/g3/wifi_lan.h"
#include "internal/platform/implementation/shared/file.h"
#include "internal/platform/implementation/wifi.h"
#include "internal/platform/medium_environment.h"
namespace nearby {
namespace api {
@@ -219,16 +213,6 @@ ImplementationPlatform::CreateWifiDirectMedium() {
return std::make_unique<g3::WifiDirectMedium>();
}
#ifndef NO_WEBRTC
std::unique_ptr<WebRtcMedium> ImplementationPlatform::CreateWebRtcMedium() {
if (MediumEnvironment::Instance().GetEnvironmentConfig().webrtc_enabled) {
return std::make_unique<g3::WebRtcMedium>();
} else {
return nullptr;
}
}
#endif
std::unique_ptr<AppLifecycleMonitor>
ImplementationPlatform::CreateAppLifecycleMonitor(
std::function<void(AppLifecycleMonitor::AppLifecycleState)>
@@ -16,16 +16,15 @@
#include <atomic>
#include <memory>
#include <optional>
#include <utility>
#include "absl/strings/str_format.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
#include "internal/platform/implementation/cancelable.h"
#include "internal/platform/logging.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/runnable.h"
#include "internal/test/fake_clock.h"
namespace nearby {
namespace g3 {
@@ -66,20 +65,13 @@ class ScheduledCancelable : public api::Cancelable {
} // namespace
ScheduledExecutor::ScheduledExecutor() {
std::optional<FakeClock*> fake_clock =
MediumEnvironment::Instance().GetSimulatedClock();
if (fake_clock.has_value()) {
name_ = absl::StrFormat("G3 scheduled executor %p", this);
(*fake_clock)->AddObserver(name_, [this]() { RunReadyTasks(); });
}
name_ = absl::StrFormat("G3 scheduled executor %p", this);
MediumEnvironment::Instance().AddSimulatedClockObserver(
name_, [this]() { RunReadyTasks(); });
}
ScheduledExecutor::~ScheduledExecutor() {
std::optional<FakeClock*> fake_clock =
MediumEnvironment::Instance().GetSimulatedClock();
if (fake_clock.has_value()) {
(*fake_clock)->RemoveObserver(name_);
}
MediumEnvironment::Instance().RemoveSimulatedClockObserver(name_);
executor_.Shutdown();
}
@@ -96,10 +88,10 @@ std::shared_ptr<api::Cancelable> ScheduledExecutor::Schedule(
runnable();
}
};
std::optional<FakeClock*> fake_clock =
MediumEnvironment::Instance().GetSimulatedClock();
if (fake_clock.has_value()) {
absl::Time trigger_time = (*fake_clock)->Now() + delay;
if (MediumEnvironment::Instance()
.GetEnvironmentConfig()
.use_simulated_clock) {
absl::Time trigger_time = MediumEnvironment::Instance().Now() + delay;
absl::MutexLock lock(mutex_);
tasks_.insert(std::pair<absl::Time, std::unique_ptr<Runnable>>(
trigger_time, std::make_unique<Runnable>(std::move(task))));
@@ -110,15 +102,12 @@ std::shared_ptr<api::Cancelable> ScheduledExecutor::Schedule(
}
void ScheduledExecutor::RunReadyTasks() {
std::optional<FakeClock*> fake_clock =
MediumEnvironment::Instance().GetSimulatedClock();
if (executor_.InShutdown()) {
return;
}
if (!fake_clock.has_value()) {
return;
}
absl::Time current_time = (*fake_clock)->Now();
CHECK(
MediumEnvironment::Instance().GetEnvironmentConfig().use_simulated_clock);
absl::Time current_time = MediumEnvironment::Instance().Now();
absl::MutexLock lock(mutex_);
for (auto it = tasks_.begin(); it != tasks_.end();) {
if (it->first <= current_time) {
@@ -15,26 +15,21 @@
#include "internal/platform/implementation/system_clock.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "internal/platform/exception.h"
#include "internal/platform/medium_environment.h"
#include "internal/test/fake_clock.h"
namespace nearby {
absl::Time SystemClock::ElapsedRealtime() {
absl::optional<FakeClock*> fake_clock =
MediumEnvironment::Instance().GetSimulatedClock();
if (fake_clock.has_value()) {
return (*fake_clock)->Now();
}
return absl::Now();
return MediumEnvironment::Instance().Now();
}
Exception SystemClock::Sleep(absl::Duration duration) {
absl::optional<FakeClock*> fake_clock =
MediumEnvironment::Instance().GetSimulatedClock();
if (fake_clock.has_value()) {
(*fake_clock)->FastForward(duration);
if (MediumEnvironment::Instance()
.GetEnvironmentConfig()
.use_simulated_clock) {
MediumEnvironment::Instance().FastForward(duration);
} else {
absl::SleepFor(duration);
}

Some files were not shown because too many files have changed in this diff Show More