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

# Conflicts:
#	MODULE.bazel
#	connections/implementation/mediums/ble_v2.cc
#	internal/platform/implementation/BUILD
This commit is contained in:
kidfromjupiter
2026-02-04 19:12:40 +00:00
1278 changed files with 930060 additions and 211365 deletions
+25 -1
View File
@@ -26,7 +26,7 @@ cc_library(
],
visibility = [
"//internal/account:__subpackages__",
"//internal/platform:__pkg__",
"//internal/platform:__subpackages__",
"//internal/test:__pkg__",
"//sharing:__subpackages__",
],
@@ -63,6 +63,16 @@ cc_library(
visibility = ["//visibility:public"],
)
cc_library(
name = "masker",
srcs = ["masker.cc"],
hdrs = ["masker.h"],
visibility = ["//visibility:public"],
deps = [
"@com_google_absl//absl/strings:string_view",
],
)
cc_library(
name = "file_path",
srcs = ["file_path.cc"],
@@ -129,3 +139,17 @@ cc_test(
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "masker_test",
size = "small",
timeout = "short",
srcs = [
"masker_test.cc",
],
deps = [
":masker",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_googletest//:gtest_main",
],
)
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/base/masker.h"
#include <string>
#include "absl/strings/string_view.h"
namespace nearby {
namespace masker {
namespace {
// The default mask character to use for masking. This is a single asterisk.
constexpr char kDefaultMaskChar = '*';
// The default start index to use for masking.
constexpr int kDefaultMaskStartIndex = 2;
} // namespace
std::string Mask(absl::string_view input) {
return Mask(input, kDefaultMaskChar, kDefaultMaskStartIndex);
}
std::string Mask(absl::string_view input, char mask, int start_index) {
if (start_index < 0) {
start_index = 0;
}
if (start_index >= input.length()) {
return std::string(input);
}
std::string result = std::string(input);
for (int i = start_index; i < input.length(); ++i) {
result[i] = mask;
}
return result;
}
} // namespace masker
} // namespace nearby
@@ -1,4 +1,4 @@
// Copyright 2023 Google LLC
// 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.
@@ -12,22 +12,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/linux/ble_medium.h"
#ifndef THIRD_PARTY_NEARBY_INTERNAL_BASE_MASKER_H_
#define THIRD_PARTY_NEARBY_INTERNAL_BASE_MASKER_H_
#include <memory>
#include <string>
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/linux/ble_v2_medium.h"
#include "internal/platform/implementation/linux/bluetooth_adapter.h"
#include "internal/platform/logging.h"
#include "internal/platform/uuid.h"
#include "absl/strings/string_view.h"
namespace nearby {
namespace linux {
namespace masker {
// Masks the input string with the default mask character '*' and start index 2.
std::string Mask(absl::string_view input);
} // namespace linux
} // namespace nearby
// Masks the input string with the given mask character and start index.
std::string Mask(absl::string_view input, char mask, int start_index);
} // namespace masker
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_BASE_MASKER_H_
+68
View File
@@ -0,0 +1,68 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/base/masker.h"
#include "gtest/gtest.h"
namespace nearby {
namespace masker {
namespace {
TEST(MaskerTest, DefaultMaskEmptyString) { EXPECT_EQ(Mask(""), ""); }
TEST(MaskerTest, DefaultMaskShortString) {
EXPECT_EQ(Mask("a"), "a");
EXPECT_EQ(Mask("ab"), "ab");
}
TEST(MaskerTest, DefaultMaskLongerString) {
EXPECT_EQ(Mask("abc"), "ab*");
EXPECT_EQ(Mask("abcd"), "ab**");
EXPECT_EQ(Mask("abcdef"), "ab****");
}
TEST(MaskerTest, CustomMaskEmptyString) {
EXPECT_EQ(Mask("", '#', 0), "");
EXPECT_EQ(Mask("", '#', 5), "");
}
TEST(MaskerTest, CustomMaskStartIndexZero) {
EXPECT_EQ(Mask("abc", '#', 0), "###");
EXPECT_EQ(Mask("hello", 'X', 0), "XXXXX");
}
TEST(MaskerTest, CustomMaskStartIndexOne) {
EXPECT_EQ(Mask("abc", '#', 1), "a##");
EXPECT_EQ(Mask("hello", 'X', 1), "hXXXX");
}
TEST(MaskerTest, CustomMaskStartIndexGreaterThanLength) {
EXPECT_EQ(Mask("abc", '#', 3), "abc");
EXPECT_EQ(Mask("abc", '#', 5), "abc");
}
TEST(MaskerTest, CustomMaskStartIndexNegative) {
EXPECT_EQ(Mask("abc", '#', -1), "###");
EXPECT_EQ(Mask("hello", 'X', -10), "XXXXX");
}
TEST(MaskerTest, CustomMaskDifferentChar) {
EXPECT_EQ(Mask("password", '$', 4), "pass$$$$");
EXPECT_EQ(Mask("secret", '?', 2), "se????");
}
} // namespace
} // namespace masker
} // namespace nearby
+1
View File
@@ -31,6 +31,7 @@ cc_library(
],
deps = [
"//internal/base:file_path",
"//internal/platform:logging",
"//internal/platform:types",
"//third_party/leveldb:db",
"//third_party/leveldb:table",
+1 -2
View File
@@ -43,10 +43,9 @@ cc_library(
copts = [
"-Ithird_party",
],
# Flags should not be visible to external users. Only for internal use.
visibility = [
"//:__subpackages__",
"//connections:partners",
"//googlemac/iPhone/Shared/Identity/SmartSetup:__subpackages__",
"//location/nearby/cpp:__subpackages__",
"//location/nearby/sharing/sdk:__subpackages__",
"//location/nearby/testing:__subpackages__",
+1 -2
View File
@@ -59,7 +59,6 @@ cc_library(
"//internal:__pkg__",
"//internal:__subpackages__",
"//location/nearby/cpp/sharing:__subpackages__",
"//sharing:__subpackages__",
],
deps = [
":url",
@@ -88,10 +87,10 @@ cc_library(
visibility = [
"//internal:__pkg__",
"//internal:__subpackages__",
"//sharing:__subpackages__",
],
deps = [
":types",
"//internal/platform:logging",
"//internal/platform:types",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
+48 -24
View File
@@ -32,6 +32,7 @@ cc_library(
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/log:globals",
"@com_google_absl//absl/log:log_sink_registry",
"@com_google_absl//absl/log:vlog_is_on",
],
)
@@ -41,6 +42,7 @@ cc_library(
"base64_utils.cc",
"input_stream.cc",
"prng.cc",
"service_address.cc",
],
hdrs = [
"base64_utils.h",
@@ -58,6 +60,7 @@ cc_library(
"payload_id.h",
"prng.h",
"runnable.h",
"service_address.h",
"socket.h",
"types.h",
"wifi_credential.h",
@@ -69,7 +72,8 @@ cc_library(
"//connections:partners",
],
deps = [
":mac_address",
"//connections/implementation/proto:offline_wire_formats_cc_proto",
"//internal/platform/implementation:wifi_utils",
"//proto:connections_enums_cc_proto",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
@@ -80,9 +84,9 @@ cc_library(
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/strings:string_view",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:span",
],
)
@@ -101,7 +105,6 @@ cc_library(
visibility = [
"//connections:__subpackages__",
"//connections:partners",
"//internal/platform/implementation:__subpackages__",
],
deps = [
":base",
@@ -233,7 +236,6 @@ cc_library(
"file.h",
"future.h",
"lockable.h",
"logging.h",
"monitored_runnable.h",
"multi_thread_executor.h",
"mutex.h",
@@ -241,7 +243,6 @@ cc_library(
"pending_job_registry.h",
"pipe.h",
"scheduled_executor.h",
"settable_future.h",
"single_thread_executor.h",
"submittable_executor.h",
"system_clock.h",
@@ -253,29 +254,15 @@ cc_library(
"timer_impl.h",
],
visibility = [
"//connections:__subpackages__",
"//internal/account:__subpackages__",
"//internal/auth:__subpackages__",
"//internal/auth/credential_store:__subpackages__",
"//internal/base:__subpackages__",
"//internal/crypto:__subpackages__",
"//internal/data:__subpackages__",
"//internal/interop:__pkg__",
"//internal/network:__subpackages__",
"//internal/platform:__subpackages__",
"//internal/preferences:__subpackages__",
"//internal/proto/analytics:__subpackages__",
"//internal/test:__subpackages__",
"//internal/weave:__subpackages__",
"//:__subpackages__",
"//location/nearby/apps:__subpackages__",
"//location/nearby/cpp:__subpackages__",
"//location/nearby/sharing/sdk:__subpackages__",
"//location/nearby/testing/nearby_native:__subpackages__",
"//presence:__subpackages__",
"//sharing:__subpackages__",
],
deps = [
":base",
":logging",
":util",
"//connections/implementation/flags:connections_flags",
"//internal/base:file_path",
@@ -304,7 +291,6 @@ cc_library(
srcs = [
"awdl.cc",
"ble.cc",
"ble_v2.cc",
"bluetooth_classic.cc",
"credential_storage_impl.cc",
"file.cc",
@@ -315,7 +301,6 @@ cc_library(
hdrs = [
"awdl.h",
"ble.h",
"ble_v2.h",
"bluetooth_adapter.h",
"bluetooth_classic.h",
"credential_storage_impl.h",
@@ -338,10 +323,14 @@ cc_library(
deps = [
":base",
":cancellation_flag",
":logging",
":mac_address",
":types",
":uuid",
"//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:wifi_utils",
@@ -353,6 +342,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",
],
@@ -388,6 +378,7 @@ cc_library(
],
deps = [
":base",
":logging",
":mac_address",
":types",
":uuid",
@@ -405,6 +396,27 @@ cc_library(
],
)
cc_library(
name = "mock_platform",
testonly = True,
hdrs = [
"mock_input_stream.h",
"mock_output_stream.h",
"mock_wifi_lan_medium.h",
"mock_wifi_lan_server_socket.h",
"mock_wifi_lan_socket.h",
],
visibility = ["//visibility:public"],
deps = [
":base",
":cancellation_flag",
"//internal/platform/implementation:comm",
"@com_google_absl//absl/strings:string_view",
"@com_google_absl//absl/types:optional",
"@com_google_googletest//:gtest_for_library_testonly",
],
)
cc_test(
name = "platform_base_test",
srcs = [
@@ -489,7 +501,6 @@ cc_test(
srcs = [
"ble_connection_info_test.cc",
"ble_test.cc",
"ble_v2_test.cc",
"blocking_queue_stream_test.cc",
"bluetooth_adapter_test.cc",
"bluetooth_classic_test.cc",
@@ -506,6 +517,7 @@ cc_test(
":cancellation_flag",
":comm",
":connection_info",
":logging",
":mac_address",
":test_util",
":types",
@@ -568,6 +580,7 @@ cc_test(
deps = [
":base",
":connection_info",
":logging",
":mac_address",
":test_util",
":types",
@@ -600,3 +613,14 @@ cc_test(
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "service_address_test",
srcs = ["service_address_test.cc"],
deps = [
":base",
"//connections/implementation/proto:offline_wire_formats_cc_proto",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_googletest//:gtest_main",
],
)
+14 -11
View File
@@ -48,20 +48,20 @@ std::int32_t Base64Utils::BytesToInt(const ByteArray& bytes) {
const char* int_bytes = bytes.data();
std::int32_t result = 0;
result |= (static_cast<std::int32_t>(int_bytes[0]) & 0x0FF) << 24;
result |= (static_cast<std::int32_t>(int_bytes[1]) & 0x0FF) << 16;
result |= (static_cast<std::int32_t>(int_bytes[2]) & 0x0FF) << 8;
result |= (static_cast<std::int32_t>(int_bytes[3]) & 0x0FF);
result |= (static_cast<std::int32_t>(int_bytes[0]) & 0xFF) << 24;
result |= (static_cast<std::int32_t>(int_bytes[1]) & 0xFF) << 16;
result |= (static_cast<std::int32_t>(int_bytes[2]) & 0xFF) << 8;
result |= (static_cast<std::int32_t>(int_bytes[3]) & 0xFF);
return result;
}
ByteArray Base64Utils::IntToBytes(std::int32_t value) {
char int_bytes[sizeof(std::int32_t)];
int_bytes[0] = static_cast<char>((value >> 24) & 0x0FF);
int_bytes[1] = static_cast<char>((value >> 16) & 0x0FF);
int_bytes[2] = static_cast<char>((value >> 8) & 0x0FF);
int_bytes[3] = static_cast<char>((value) & 0x0FF);
int_bytes[0] = static_cast<char>((value >> 24) & 0xFF);
int_bytes[1] = static_cast<char>((value >> 16) & 0xFF);
int_bytes[2] = static_cast<char>((value >> 8) & 0xFF);
int_bytes[3] = static_cast<char>((value) & 0xFF);
return ByteArray(int_bytes, sizeof(int_bytes));
}
@@ -71,12 +71,15 @@ ExceptionOr<std::int32_t> Base64Utils::ReadInt(InputStream* reader) {
if (!read_bytes.ok()) {
return ExceptionOr<std::int32_t>(read_bytes.exception());
}
return ExceptionOr<std::int32_t>(
BytesToInt(std::move(read_bytes.result())));
return ExceptionOr<std::int32_t>(BytesToInt(std::move(read_bytes.result())));
}
Exception Base64Utils::WriteInt(OutputStream* writer, std::int32_t value) {
return writer->Write(IntToBytes(value));
std::string bytes = {static_cast<char>((value >> 24) & 0xFF),
static_cast<char>((value >> 16) & 0xFF),
static_cast<char>((value >> 8) & 0xFF),
static_cast<char>((value) & 0xFF)};
return writer->Write(bytes);
}
} // namespace nearby
+256 -94
View File
@@ -1,4 +1,4 @@
// Copyright 2020 Google LLC
// 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.
@@ -15,126 +15,288 @@
#include "internal/platform/ble.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "internal/platform/bluetooth_adapter.h"
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/logging.h"
#include "internal/platform/mutex_lock.h"
#include "internal/platform/uuid.h"
namespace nearby {
namespace {
using ::nearby::api::ble::BleAdvertisementData;
using ::nearby::api::ble::GattCharacteristic;
using ::nearby::api::ble::TxPowerLevel;
using ReadValueCallback =
::nearby::api::ble::ServerGattConnectionCallback::ReadValueCallback;
using WriteValueCallback =
::nearby::api::ble::ServerGattConnectionCallback::WriteValueCallback;
} // namespace
bool BleMedium::StartAdvertising(
const std::string& service_id, const ByteArray& advertisement_bytes,
const std::string& fast_advertisement_service_uuid) {
return impl_->StartAdvertising(service_id, advertisement_bytes,
fast_advertisement_service_uuid);
const BleAdvertisementData& advertising_data,
api::ble::AdvertiseParameters advertise_parameters) {
return impl_->StartAdvertising(advertising_data, advertise_parameters);
}
bool BleMedium::StopAdvertising(const std::string& service_id) {
return impl_->StopAdvertising(service_id);
bool BleMedium::StopAdvertising() { return impl_->StopAdvertising(); }
std::unique_ptr<api::ble::BleMedium::AdvertisingSession>
BleMedium::StartAdvertising(
const api::ble::BleAdvertisementData& advertising_data,
api::ble::AdvertiseParameters advertise_set_parameters,
api::ble::BleMedium::AdvertisingCallback callback) {
return impl_->StartAdvertising(advertising_data, advertise_set_parameters,
std::move(callback));
}
bool BleMedium::StartScanning(
const std::string& service_id,
const std::string& fast_advertisement_service_uuid,
DiscoveredPeripheralCallback callback) {
{
MutexLock lock(&mutex_);
discovered_peripheral_callback_ = std::move(callback);
peripherals_.clear();
bool BleMedium::StartScanning(const Uuid& service_uuid,
TxPowerLevel tx_power_level,
ScanCallback callback) {
MutexLock lock(&mutex_);
if (scanning_enabled_) {
LOG(INFO) << "Ble Scanning already enabled";
return false;
}
bool success = impl_->StartScanning(
service_uuid, tx_power_level,
api::ble::BleMedium::ScanCallback{
.advertisement_found_cb =
[this](api::ble::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) {
MutexLock lock(&mutex_);
BlePeripheral proxy(*this, peripheral_id);
if (!scanning_enabled_) return;
scan_callback_.advertisement_found_cb(std::move(proxy),
advertisement_data);
},
});
if (success) {
scan_callback_ = std::move(callback);
scanning_enabled_ = true;
LOG(INFO) << "Ble Scanning enabled";
}
return success;
}
bool BleMedium::StartMultipleServicesScanning(
const std::vector<Uuid>& service_uuids,
api::ble::TxPowerLevel tx_power_level, ScanCallback callback) {
MutexLock lock(&mutex_);
if (scanning_enabled_) {
LOG(INFO) << "Ble Scanning already enabled";
return false;
}
bool success = impl_->StartMultipleServicesScanning(
service_uuids, tx_power_level,
api::ble::BleMedium::ScanCallback{
.advertisement_found_cb =
[this](api::ble::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) {
MutexLock lock(&mutex_);
BlePeripheral proxy(*this, peripheral_id);
if (!scanning_enabled_) return;
scan_callback_.advertisement_found_cb(std::move(proxy),
advertisement_data);
},
});
if (success) {
scan_callback_ = std::move(callback);
scanning_enabled_ = true;
LOG(INFO) << "Ble Scanning enabled";
}
return success;
}
BleMedium::~BleMedium() { StopScanning(); }
bool BleMedium::StopScanning() {
MutexLock lock(&mutex_);
if (!scanning_enabled_) {
return true;
}
scanning_enabled_ = false;
scan_callback_ = {};
LOG(INFO) << "Ble Scanning disabled";
return impl_->StopScanning();
}
bool BleMedium::PauseMediumScanning() {
MutexLock lock(&mutex_);
if (!scanning_enabled_) {
return true;
}
LOG(INFO) << "Pause Medium level BLE Scanning";
return impl_->PauseMediumScanning();
}
bool BleMedium::ResumeMediumScanning() {
MutexLock lock(&mutex_);
return impl_->ResumeMediumScanning();
}
std::unique_ptr<api::ble::BleMedium::ScanningSession> BleMedium::StartScanning(
const Uuid& service_uuid, api::ble::TxPowerLevel tx_power_level,
api::ble::BleMedium::ScanningCallback callback) {
LOG(INFO) << "platform mutex: " << &mutex_;
return impl_->StartScanning(
service_id, fast_advertisement_service_uuid,
service_uuid, tx_power_level,
api::ble::BleMedium::ScanningCallback{
.start_scanning_result =
[this, start_scanning_result =
std::move(callback.start_scanning_result)](
absl::Status status) mutable {
{
MutexLock lock(&mutex_);
if (status.ok()) {
scanning_enabled_ = true;
}
}
start_scanning_result(status);
},
.advertisement_found_cb = std::move(callback.advertisement_found_cb),
.advertisement_lost_cb = std::move(callback.advertisement_lost_cb),
});
}
std::unique_ptr<GattServer> BleMedium::StartGattServer(
ServerGattConnectionCallback callback) {
{
MutexLock lock(&mutex_);
server_gatt_connection_callback_ = std::move(callback);
}
std::unique_ptr<api::ble::GattServer> api_gatt_server =
impl_->StartGattServer({
.characteristic_subscription_cb =
[this](const GattCharacteristic& characteristic) {
MutexLock lock(&mutex_);
server_gatt_connection_callback_.characteristic_subscription_cb(
characteristic);
},
.characteristic_unsubscription_cb =
[this](const GattCharacteristic& characteristic) {
MutexLock lock(&mutex_);
server_gatt_connection_callback_
.characteristic_unsubscription_cb(characteristic);
},
.on_characteristic_read_cb =
[this](const api::ble::BlePeripheral::UniqueId remote_device_id,
const GattCharacteristic& characteristic, int offset,
ReadValueCallback callback) {
MutexLock lock(&mutex_);
if (server_gatt_connection_callback_
.on_characteristic_read_cb) {
server_gatt_connection_callback_.on_characteristic_read_cb(
remote_device_id, characteristic, offset,
std::move(callback));
} else {
callback(absl::FailedPreconditionError(
"Read characteristic callback is not set"));
}
},
.on_characteristic_write_cb =
[this](const api::ble::BlePeripheral::UniqueId remote_device_id,
const GattCharacteristic& characteristic, int offset,
absl::string_view data, WriteValueCallback callback) {
MutexLock lock(&mutex_);
if (server_gatt_connection_callback_
.on_characteristic_write_cb) {
server_gatt_connection_callback_.on_characteristic_write_cb(
remote_device_id, characteristic, offset, data,
std::move(callback));
} else {
callback(absl::FailedPreconditionError(
"Write characteristic callback is not set"));
}
},
});
return std::make_unique<GattServer>(std::move(api_gatt_server));
}
std::unique_ptr<GattClient> BleMedium::ConnectToGattServer(
BlePeripheral peripheral, TxPowerLevel tx_power_level,
ClientGattConnectionCallback callback) {
std::optional<api::ble::BlePeripheral::UniqueId> id =
peripheral.GetUniqueId();
if (!id.has_value()) {
LOG(ERROR) << "Failed to connect to GattServer, invalid peripheral";
return nullptr;
}
return std::make_unique<GattClient>(impl_->ConnectToGattServer(
*id, tx_power_level,
{
.peripheral_discovered_cb =
[this](api::BlePeripheral& peripheral,
const std::string& service_id, bool fast_advertisement) {
MutexLock lock(&mutex_);
auto pair = peripherals_.emplace(
&peripheral, absl::make_unique<ScanningInfo>());
auto& context = *pair.first->second;
context.peripheral = BlePeripheral(&peripheral);
discovered_peripheral_callback_.peripheral_discovered_cb(
context.peripheral, service_id,
context.peripheral.GetAdvertisementBytes(service_id),
fast_advertisement);
.disconnected_cb =
[callback = std::move(callback)]() mutable {
callback.disconnected_cb();
},
.peripheral_lost_cb =
[this](api::BlePeripheral& peripheral,
const std::string& service_id) {
MutexLock lock(&mutex_);
if (peripherals_.empty()) return;
auto context = peripherals_.find(&peripheral);
if (context == peripherals_.end()) return;
LOG(INFO) << "Removing peripheral="
<< context->second->peripheral.GetName()
<< ", impl=" << &peripheral;
discovered_peripheral_callback_.peripheral_lost_cb(
context->second->peripheral, service_id);
},
});
}));
}
bool BleMedium::StopScanning(const std::string& service_id) {
{
MutexLock lock(&mutex_);
discovered_peripheral_callback_ = {};
peripherals_.clear();
LOG(INFO) << "Ble Scanning disabled: impl=" << &GetImpl();
}
return impl_->StopScanning(service_id);
BleServerSocket BleMedium::OpenServerSocket(const std::string& service_id) {
return BleServerSocket(*this, impl_->OpenServerSocket(service_id));
}
bool BleMedium::StartAcceptingConnections(const std::string& service_id,
AcceptedConnectionCallback callback) {
{
MutexLock lock(&mutex_);
accepted_connection_callback_ = std::move(callback);
}
return impl_->StartAcceptingConnections(
service_id,
[this](api::BleSocket& socket, const std::string& service_id) {
MutexLock lock(&mutex_);
auto pair = sockets_.emplace(
&socket, std::make_unique<AcceptedConnectionInfo>());
auto& context = *pair.first->second;
if (!pair.second) {
LOG(INFO) << "Accepting (again) socket=" << &context.socket
<< ", impl=" << &socket;
} else {
context.socket = BleSocket(&socket);
LOG(INFO) << "Accepting socket=" << &context.socket
<< ", impl=" << &socket;
}
if (accepted_connection_callback_) {
accepted_connection_callback_(context.socket, service_id);
}
});
BleL2capServerSocket BleMedium::OpenL2capServerSocket(
const std::string& service_id) {
return BleL2capServerSocket(*this, impl_->OpenL2capServerSocket(service_id));
}
bool BleMedium::StopAcceptingConnections(const std::string& service_id) {
{
MutexLock lock(&mutex_);
accepted_connection_callback_ = nullptr;
sockets_.clear();
LOG(INFO) << "Ble accepted connection disabled: impl=" << &GetImpl();
}
return impl_->StopAcceptingConnections(service_id);
}
BleSocket BleMedium::Connect(BlePeripheral& peripheral,
const std::string& service_id,
BleSocket BleMedium::Connect(const std::string& service_id,
TxPowerLevel tx_power_level,
const BlePeripheral& peripheral,
CancellationFlag* cancellation_flag) {
{
MutexLock lock(&mutex_);
LOG(INFO) << "BleMedium::Connect: peripheral=" << peripheral.GetName()
<< ",impl=" << &peripheral.GetImpl();
std::optional<api::ble::BlePeripheral::UniqueId> id =
peripheral.GetUniqueId();
if (!id.has_value()) {
LOG(ERROR) << "Failed to connect, invalid peripheral";
return {};
}
return BleSocket(
impl_->Connect(peripheral.GetImpl(), service_id, cancellation_flag));
return BleSocket(peripheral, impl_->Connect(service_id, tx_power_level, *id,
cancellation_flag));
}
BleL2capSocket BleMedium::ConnectOverL2cap(
const std::string& service_id, TxPowerLevel tx_power_level,
const BlePeripheral& peripheral, CancellationFlag* cancellation_flag) {
std::optional<api::ble::BlePeripheral::UniqueId> id =
peripheral.GetUniqueId();
if (!id.has_value()) {
LOG(ERROR) << "Failed to connect over L2cap, invalid peripheral";
return {};
}
return BleL2capSocket(
peripheral,
impl_->ConnectOverL2cap(peripheral.GetPsm(), service_id, tx_power_level,
*id, cancellation_flag));
}
bool BleMedium::IsExtendedAdvertisementsAvailable() {
return IsValid() && impl_->IsExtendedAdvertisementsAvailable();
}
bool BlePeripheral::IsValid() const { return unique_id_.has_value(); }
std::optional<BlePeripheral> BleMedium::RetrieveBlePeripheralFromNativeId(
const std::string& ble_peripheral_native_id) {
if (!IsValid()) {
return std::nullopt;
}
std::optional<api::ble::BlePeripheral::UniqueId> id =
impl_->RetrieveBlePeripheralIdFromNativeId(ble_peripheral_native_id);
if (id.has_value()) {
return BlePeripheral(*this, id.value());
}
return std::nullopt;
}
} // namespace nearby
+493 -63
View File
@@ -1,4 +1,4 @@
// Copyright 2020 Google LLC
// 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.
@@ -15,49 +15,131 @@
#ifndef PLATFORM_PUBLIC_BLE_H_
#define PLATFORM_PUBLIC_BLE_H_
#include "absl/container/flat_hash_map.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/functional/any_invocable.h"
#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"
#include "internal/platform/exception.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/implementation/platform.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/listeners.h"
#include "internal/platform/logging.h"
#include "internal/platform/mutex.h"
#include "internal/platform/output_stream.h"
#include "internal/platform/uuid.h"
namespace nearby {
class BleMedium;
// TODO: b/399815436 - Remove all the shared_ptr in this file.
// Opaque wrapper over a BLE peripheral. Must contain enough data about a
// particular BLE peripheral to connect to its GATT server.
class BlePeripheral final {
public:
BlePeripheral() = default;
BlePeripheral(BleMedium& medium, api::ble::BlePeripheral::UniqueId unique_id)
: medium_(&medium), unique_id_(unique_id) {}
BlePeripheral(const BlePeripheral&) = default;
BlePeripheral& operator=(const BlePeripheral&) = default;
BlePeripheral(BlePeripheral&& other) = default;
BlePeripheral& operator=(BlePeripheral&& other) = default;
ByteArray GetId() const { return id_; }
void SetId(const ByteArray& id) { id_ = id; }
int GetPsm() const { return psm_; }
void SetPsm(int psm) { psm_ = psm; }
bool IsValid() const;
std::optional<api::ble::BlePeripheral::UniqueId> GetUniqueId() const {
return unique_id_;
}
api::ble::BlePeripheral* GetImpl() const;
std::string ToReadableString() const {
if (!IsValid()) {
return "BlePeripheral { invalid }";
}
return absl::StrFormat("BlePeripheral { id=%s, psm=%d}",
absl::BytesToHexString(GetId().AsStringView()),
GetPsm());
}
private:
BleMedium* medium_ = nullptr;
std::optional<api::ble::BlePeripheral::UniqueId> unique_id_;
// A unique identifier for this peripheral. It is the BLE advertisement bytes
// it was found on.
ByteArray id_ = {};
// The psm (protocol service multiplexer) value is used for create data
// connection on L2CAP socket. It only exists when remote device supports
// L2CAP socket feature.
int psm_ = 0;
};
// Container of operations that can be performed over the BLE GATT client
// socket.
// This class is copyable but not moveable.
class BleSocket final {
public:
BleSocket() = default;
BleSocket(BlePeripheral peripheral,
std::unique_ptr<api::ble::BleSocket> socket)
: peripheral_(peripheral) {
state_->socket = std::move(socket);
}
BleSocket(const BleSocket&) = default;
BleSocket& operator=(const BleSocket&) = default;
explicit BleSocket(api::BleSocket* socket) : impl_(socket) {}
explicit BleSocket(std::unique_ptr<api::BleSocket> socket)
: impl_(socket.release()) {}
~BleSocket() = default;
// Returns the InputStream of the BleSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleSocket object is destroyed.
InputStream& GetInputStream() { return impl_->GetInputStream(); }
InputStream& GetInputStream() { return state_->socket->GetInputStream(); }
// Returns the OutputStream of the BleSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleSocket object is destroyed.
OutputStream& GetOutputStream() { return impl_->GetOutputStream(); }
OutputStream& GetOutputStream() { return state_->socket->GetOutputStream(); }
// Sets the close notifier by client side.
void SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
state_->close_notifier = std::move(notifier);
}
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() { return impl_->Close(); }
BlePeripheral GetRemotePeripheral() {
return BlePeripheral(impl_->GetRemotePeripheral());
Exception Close() {
if (state_->close_notifier != nullptr) {
absl::AnyInvocable<void()> notifier = std::move(state_->close_notifier);
notifier();
}
return state_->socket->Close();
}
// Returns BlePeripheral object which wraps a valid BlePeripheral pointer.
BlePeripheral& GetRemotePeripheral() { return peripheral_; }
// Returns true if a socket is usable. If this method returns false,
// it is not safe to call any other method.
// NOTE(socket validity):
@@ -67,94 +149,442 @@ class BleSocket final {
// an object returned by BleMedium::Connect
// These methods may also return an invalid socket if connection failed for
// any reason.
bool IsValid() const { return impl_ != nullptr; }
bool IsValid() const { return state_->socket != nullptr; }
// Returns reference to platform implementation.
// This is used to communicate with platform code, and for debugging purposes.
// Returned reference will remain valid for while BleSocket object is
// itself valid. Typically BleSocket lifetime matches duration of the
// connection, and is controlled by end user, since they hold the instance.
api::BleSocket& GetImpl() { return *impl_; }
api::ble::BleSocket& GetImpl() { return *state_->socket; }
private:
std::shared_ptr<api::BleSocket> impl_;
struct SharedState {
std::unique_ptr<api::ble::BleSocket> socket;
absl::AnyInvocable<void()> close_notifier;
};
std::shared_ptr<SharedState> state_ = std::make_shared<SharedState>();
BlePeripheral peripheral_;
};
// Container of operations that can be performed over the BLE GATT server
// socket.
// This class is copyable but not moveable.
class BleServerSocket final {
public:
BleServerSocket(BleMedium& medium,
std::unique_ptr<api::ble::BleServerSocket> socket)
: medium_(&medium), impl_(std::move(socket)) {}
BleServerSocket(const BleServerSocket&) = default;
BleServerSocket& operator=(const BleServerSocket&) = default;
// Blocks until either:
// - at least one incoming connection request is available, or
// - ServerSocket is closed.
// On success, returns connected socket, ready to exchange data.
// On error, "impl_" will be nullptr and the caller will check it by calling
// member function "IsValid()"
// Once error is reported, it is permanent, and
// ServerSocket has to be closed by caller.
BleSocket Accept() {
std::unique_ptr<api::ble::BleSocket> socket = impl_->Accept();
BlePeripheral peripheral;
if (socket == nullptr) {
LOG(INFO) << "BleServerSocket Accept() failed on server socket: " << this;
} else {
peripheral = BlePeripheral(*medium_, socket->GetRemotePeripheralId());
}
return BleSocket(peripheral, std::move(socket));
}
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() {
LOG(INFO) << "BleServerSocket Closing:: " << this;
return impl_->Close();
}
bool IsValid() const { return impl_ != nullptr; }
api::ble::BleServerSocket& GetImpl() { return *impl_; }
private:
BleMedium* medium_;
std::shared_ptr<api::ble::BleServerSocket> impl_;
};
// Opaque wrapper over a GattServer.
// Move only, disallow copy.
//
// Note that some of the methods return absl::optional instead
// of std::optional, because iOS platform is still in C++14.
class GattServer final {
public:
explicit GattServer(std::unique_ptr<api::ble::GattServer> gatt_server)
: impl_(std::move(gatt_server)) {}
~GattServer() { Stop(); }
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::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) {
return impl_->CreateCharacteristic(service_uuid, characteristic_uuid,
permission, property);
}
bool UpdateCharacteristic(const api::ble::GattCharacteristic& characteristic,
const ByteArray& value) {
return impl_->UpdateCharacteristic(characteristic, value);
}
absl::Status NotifyCharacteristicChanged(
const api::ble::GattCharacteristic& characteristic, bool confirm,
const ByteArray& new_value) {
return impl_->NotifyCharacteristicChanged(characteristic, confirm,
new_value);
}
void Stop() {
if (impl_) return impl_->Stop();
}
// Returns true if a gatt_server is usable. If this method returns false,
// it is not safe to call any other method.
bool IsValid() const { return impl_ != nullptr; }
// Returns reference to platform implementation.
// This is used to communicate with platform code, and for debugging
// purposes.
api::ble::GattServer* GetImpl() { return impl_.get(); }
private:
std::unique_ptr<api::ble::GattServer> impl_;
};
// Opaque wrapper for a GattClient.
//
// Note that some of the methods return absl::optional instead
// of std::optional, because iOS platform is still in C++14.
class GattClient final {
public:
explicit GattClient(
std::unique_ptr<api::ble::GattClient> client_gatt_connection)
: impl_(std::move(client_gatt_connection)) {}
bool DiscoverServiceAndCharacteristics(
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
return impl_->DiscoverServiceAndCharacteristics(service_uuid,
characteristic_uuids);
}
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::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(
const api::ble::GattCharacteristic& characteristic) {
return impl_->ReadCharacteristic(characteristic);
}
// NOLINTNEXTLINE(google3-legacy-absl-backports)
bool WriteCharacteristic(const api::ble::GattCharacteristic& characteristic,
absl::string_view value,
api::ble::GattClient::WriteType write_type) {
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
// returns false, it is not safe to call any other method.
bool IsValid() const { return impl_ != nullptr; }
// Returns reference to platform implementation.
// This is used to communicate with platform code, and for debugging
// purposes.
api::ble::GattClient* GetImpl() { return impl_.get(); }
private:
std::unique_ptr<api::ble::GattClient> impl_;
};
class BleL2capSocket final {
public:
BleL2capSocket() = default;
BleL2capSocket(BlePeripheral peripheral,
std::unique_ptr<api::ble::BleL2capSocket> socket)
: peripheral_(peripheral) {
state_->socket = std::move(socket);
}
BleL2capSocket(const BleL2capSocket&) = default;
BleL2capSocket& operator=(const BleL2capSocket&) = default;
// Returns the InputStream of the BleL2capSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleL2capSocket object is destroyed.
InputStream& GetInputStream() { return state_->socket->GetInputStream(); }
// Returns the OutputStream of the BleL2capSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleL2capSocket object is destroyed.
OutputStream& GetOutputStream() { return state_->socket->GetOutputStream(); }
// Sets the close notifier by client side.
void SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
state_->close_notifier = std::move(notifier);
}
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() {
if (state_->close_notifier != nullptr) {
absl::AnyInvocable<void()> notifier = std::move(state_->close_notifier);
notifier();
}
return state_->socket->Close();
}
// Returns BlePeripheral object which wraps a valid BlePeripheral pointer.
BlePeripheral& GetRemotePeripheral() { return peripheral_; }
// Returns true if a socket is usable. If this method returns false,
// it is not safe to call any other method.
// NOTE(socket validity):
// Socket created by a default public constructor is not valid, because
// it is missing platform implementation.
// The only way to obtain a valid socket is through connection, such as
// an object returned by BleMedium::ConnectOverL2cap.
// These methods may also return an invalid socket if connection failed for
// any reason.
bool IsValid() const { return state_->socket != nullptr; }
// Returns reference to platform implementation.
// This is used to communicate with platform code, and for debugging purposes.
// Returned reference will remain valid for while BleSocket object is
// itself valid. Typically BleSocket lifetime matches duration of the
// connection, and is controlled by end user, since they hold the instance.
api::ble::BleL2capSocket& GetImpl() { return *state_->socket; }
private:
struct SharedState {
std::unique_ptr<api::ble::BleL2capSocket> socket;
absl::AnyInvocable<void()> close_notifier;
};
std::shared_ptr<SharedState> state_ = std::make_shared<SharedState>();
BlePeripheral peripheral_;
};
class BleL2capServerSocket final {
public:
BleL2capServerSocket(BleMedium& medium,
std::unique_ptr<api::ble::BleL2capServerSocket> socket)
: medium_(&medium), impl_(std::move(socket)) {}
BleL2capServerSocket(const BleL2capServerSocket&) = default;
BleL2capServerSocket& operator=(const BleL2capServerSocket&) = default;
// Gets PSM value has been published by the server.
int GetPSM() { return impl_->GetPSM(); }
// Blocks until either:
// - at least one incoming connection request is available, or
// - ServerSocket is closed.
// On success, returns connected socket, ready to exchange data.
// On error, "impl_" will be nullptr and the caller will check it by calling
// member function "IsValid()"
// Once error is reported, it is permanent, and
// ServerSocket has to be closed by caller.
BleL2capSocket Accept() {
std::unique_ptr<api::ble::BleL2capSocket> socket = impl_->Accept();
BlePeripheral peripheral;
if (socket == nullptr) {
LOG(INFO) << "BleL2capServerSocket Accept() failed on server socket: "
<< this;
} else {
peripheral = BlePeripheral(*medium_, socket->GetRemotePeripheralId());
}
return BleL2capSocket(peripheral, std::move(socket));
}
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() {
LOG(INFO) << "BleL2capServerSocket Closing:: " << this;
return impl_->Close();
}
// Returns true if a BleL2capServerSocket is usable. If this method returns
// false, it is not safe to call any other method.
bool IsValid() const { return impl_ != nullptr; }
api::ble::BleL2capServerSocket& GetImpl() { return *impl_; }
private:
BleMedium* medium_ = nullptr;
std::shared_ptr<api::ble::BleL2capServerSocket> impl_;
};
// Container of operations that can be performed over the BLE medium.
class BleMedium final {
public:
using Platform = api::ImplementationPlatform;
struct DiscoveredPeripheralCallback {
// A wrapper callback for BLE scan results.
//
// The peripheral is a wrapper object which stores the real impl of
// api::BlePeripheral.
// The reference will remain valid while api::BlePeripheral object is
// itself valid. Typically peripheral lifetime matches duration of the
// connection, and is controlled by primitive client, since they hold the
// instance.
struct ScanCallback {
absl::AnyInvocable<void(
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes, bool fast_advertisement)>
peripheral_discovered_cb =
DefaultCallback<BlePeripheral&, const std::string&,
const ByteArray&, bool>();
absl::AnyInvocable<void(BlePeripheral& peripheral,
const std::string& service_id)>
peripheral_lost_cb =
DefaultCallback<BlePeripheral&, const std::string&>();
};
struct ScanningInfo {
BlePeripheral peripheral;
BlePeripheral peripheral,
const api::ble::BleAdvertisementData& advertisement_data)>
advertisement_found_cb =
nearby::DefaultCallback<BlePeripheral,
const api::ble::BleAdvertisementData&>();
};
using AcceptedConnectionCallback = absl::AnyInvocable<void(
BleSocket& socket, const std::string& service_id)>;
struct AcceptedConnectionInfo {
BleSocket socket;
struct ServerGattConnectionCallback {
absl::AnyInvocable<void(const api::ble::GattCharacteristic& characteristic)>
characteristic_subscription_cb =
nearby::DefaultCallback<const api::ble::GattCharacteristic&>();
absl::AnyInvocable<void(const api::ble::GattCharacteristic& characteristic)>
characteristic_unsubscription_cb =
nearby::DefaultCallback<const api::ble::GattCharacteristic&>();
absl::AnyInvocable<void(
const api::ble::BlePeripheral::UniqueId remote_device_id,
const api::ble::GattCharacteristic& characteristic, int offset,
api::ble::ServerGattConnectionCallback::ReadValueCallback callback)>
on_characteristic_read_cb;
absl::AnyInvocable<void(
const api::ble::BlePeripheral::UniqueId remote_device_id,
const api::ble::GattCharacteristic& characteristic, int offset,
absl::string_view data,
api::ble::ServerGattConnectionCallback::WriteValueCallback callback)>
on_characteristic_write_cb;
};
// TODO(b/231318879): Remove this wrapper callback and use impl callback if
// there is only disconnect function here in the end.
struct ClientGattConnectionCallback {
absl::AnyInvocable<void()> disconnected_cb = nearby::DefaultCallback<>();
};
explicit BleMedium(BluetoothAdapter& adapter)
: impl_(Platform::CreateBleMedium(adapter.GetImpl())),
: impl_(api::ImplementationPlatform::CreateBleMedium(adapter.GetImpl())),
adapter_(adapter) {}
~BleMedium() = default;
~BleMedium();
// Returns true once the BLE advertising has been initiated.
bool StartAdvertising(const std::string& service_id,
const ByteArray& advertisement_bytes,
const std::string& fast_advertisement_service_uuid);
bool StopAdvertising(const std::string& service_id);
// This interface will be deprecated soon.
// TODO(b/271305977) remove this function.
// Use 'unique_ptr<AdvertisingSession> StartAdvertising' instead.
bool StartAdvertising(const api::ble::BleAdvertisementData& advertising_data,
api::ble::AdvertiseParameters advertise_parameters);
// This interface will be deprecated soon.
// TODO(b/271305977) remove this function.
bool StopAdvertising();
std::unique_ptr<api::ble::BleMedium::AdvertisingSession> StartAdvertising(
const api::ble::BleAdvertisementData& advertising_data,
api::ble::AdvertiseParameters advertise_set_parameters,
api::ble::BleMedium::AdvertisingCallback callback);
// Returns true once the BLE scan has been initiated.
bool StartScanning(const std::string& service_id,
const std::string& fast_advertisement_service_uuid,
DiscoveredPeripheralCallback callback);
// This interface will be deprecated soon.
bool StartScanning(const Uuid& service_uuid,
api::ble::TxPowerLevel tx_power_level,
ScanCallback callback) ABSL_LOCKS_EXCLUDED(mutex_);
// Returns true once BLE scanning for service_id is well and truly stopped;
// after this returns, there must be no more invocations of the
// DiscoveredPeripheralCallback passed in to StartScanning() for service_id.
bool StopScanning(const std::string& service_id);
// Returns true once the BLE multiple services scan has been initiated.
bool StartMultipleServicesScanning(const std::vector<Uuid>& service_uuids,
api::ble::TxPowerLevel tx_power_level,
ScanCallback callback)
ABSL_LOCKS_EXCLUDED(mutex_);
// Returns true once BLE socket connection requests to service_id can be
// accepted.
bool StartAcceptingConnections(const std::string& service_id,
AcceptedConnectionCallback callback);
bool StopAcceptingConnections(const std::string& service_id);
// This interface will be deprecated soon.
// TODO(b/271305977) remove this function.
bool StopScanning();
// Returns a new BleSocket. On Success, BleSocket::IsValid()
// returns true.
BleSocket Connect(BlePeripheral& peripheral, const std::string& service_id,
// Pause BLE scanning at platform Medium level.
bool PauseMediumScanning();
// Resume BLE scanning at platform Medium level.
bool ResumeMediumScanning();
std::unique_ptr<api::ble::BleMedium::ScanningSession> StartScanning(
const Uuid& service_uuid, api::ble::TxPowerLevel tx_power_level,
api::ble::BleMedium::ScanningCallback callback);
// Starts Gatt Server for waiting to client connection.
std::unique_ptr<GattServer> StartGattServer(
ServerGattConnectionCallback callback);
// Returns a new GattClient connection to a gatt server.
// There is only one instance of GattServer can run at a time.
std::unique_ptr<GattClient> ConnectToGattServer(
BlePeripheral peripheral, api::ble::TxPowerLevel tx_power_level,
ClientGattConnectionCallback callback);
// Returns a new BleServerSocket.
// On Success, BleServerSocket::IsValid() returns true.
BleServerSocket OpenServerSocket(const std::string& service_id);
// Returns a new BleL2capServerSocket.
// On Success, BleL2capServerSocket::IsValid() returns true.
BleL2capServerSocket OpenL2capServerSocket(const std::string& service_id);
// Returns a new BleSocket.
// On Success, BleSocket::IsValid() returns true.
BleSocket Connect(const std::string& service_id,
api::ble::TxPowerLevel tx_power_level,
const BlePeripheral& peripheral,
CancellationFlag* cancellation_flag);
// Returns a new BleL2capSocket.
// On Success, BleL2capSocket::IsValid() returns true.
BleL2capSocket ConnectOverL2cap(const std::string& service_id,
api::ble::TxPowerLevel tx_power_level,
const BlePeripheral& peripheral,
CancellationFlag* cancellation_flag);
bool IsExtendedAdvertisementsAvailable();
bool IsValid() const { return impl_ != nullptr; }
api::BleMedium& GetImpl() { return *impl_; }
api::ble::BleMedium* GetImpl() const { return impl_.get(); }
BluetoothAdapter& GetAdapter() { return adapter_; }
void AddAlternateUuidForService(uint16_t uuid,
const std::string& service_id) {
impl_->AddAlternateUuidForService(uuid, service_id);
}
// Retrieves a BlePeripheral from a native BLE peripheral ID.
// On Apple platform, the native ID is NSUUID in string format like
// "E621E1F8-C36C-495A-93FC-0C247A3E6E5F", other platform will be MAC address
// as string format like "0C:24:7A:3E:6E:5F".
std::optional<BlePeripheral> RetrieveBlePeripheralFromNativeId(
const std::string& ble_peripheral_native_id);
private:
Mutex mutex_;
std::unique_ptr<api::BleMedium> impl_;
std::unique_ptr<api::ble::BleMedium> impl_;
BluetoothAdapter& adapter_;
absl::flat_hash_map<api::BlePeripheral*, std::unique_ptr<ScanningInfo>>
peripherals_ ABSL_GUARDED_BY(mutex_);
absl::flat_hash_map<api::BleSocket*, std::unique_ptr<AcceptedConnectionInfo>>
sockets_ ABSL_GUARDED_BY(mutex_);
DiscoveredPeripheralCallback discovered_peripheral_callback_
ABSL_GUARDED_BY(mutex_);
AcceptedConnectionCallback accepted_connection_callback_
ServerGattConnectionCallback server_gatt_connection_callback_
ABSL_GUARDED_BY(mutex_);
ScanCallback scan_callback_ ABSL_GUARDED_BY(mutex_);
bool scanning_enabled_ ABSL_GUARDED_BY(mutex_) = false;
};
} // namespace nearby
+711 -154
View File
@@ -15,17 +15,27 @@
#include "internal/platform/ble.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/logging.h"
#include "internal/platform/feature_flags.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/mac_address.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/single_thread_executor.h"
#include "internal/platform/uuid.h"
namespace nearby {
namespace {
@@ -41,73 +51,211 @@ constexpr FeatureFlags kTestCases[] = {
},
};
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 kServiceID{"com.google.location.nearby.apps.test"};
constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"};
constexpr absl::string_view kFastAdvertisementServiceUuid{"\xf3\xfe"};
constexpr absl::string_view kAdvertisementString = "\x0a\x0b\x0c\x0d";
constexpr absl::string_view kAdvertisementHeaderString = "\x0x\x0y\x0z";
constexpr TxPowerLevel kTxPowerLevel(TxPowerLevel::kHigh);
constexpr absl::string_view kServiceIDA{
"com.google.location.nearby.apps.test.a"};
class BleMediumTest : public ::testing::TestWithParam<FeatureFlags> {
protected:
using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback;
using AcceptedConnectionCallback = BleMedium::AcceptedConnectionCallback;
MediumEnvironment& env_{MediumEnvironment::Instance()};
};
TEST_P(BleMediumTest, CanStartAcceptingConnectionsAndConnect) {
TEST_P(BleMediumTest, CanConnectToService) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleMedium ble_a{adapter_a_};
BleMedium ble_b{adapter_b_};
std::string service_id(kServiceID);
BleMedium ble_a(adapter_a_);
BleMedium ble_b(adapter_b_);
Uuid service_uuid(1234, 5678);
std::string service_id(kServiceIDA);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
CountDownLatch found_latch(1);
CountDownLatch accepted_latch(1);
CancellationFlag flag;
CountDownLatch lost_latch(1);
BlePeripheral* discovered_peripheral = nullptr;
BleServerSocket server_socket = ble_b.OpenServerSocket(service_id);
EXPECT_TRUE(server_socket.IsValid());
// Assemble regular advertisement data
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
(ble_b.StartAdvertising(advertising_data, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
BlePeripheral discovered_peripheral;
ble_a.StartScanning(
service_id, fast_advertisement_service_uuid,
DiscoveredPeripheralCallback{
.peripheral_discovered_cb =
service_uuid, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch, &discovered_peripheral](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
LOG(INFO) << "Discovered peripheral=" << peripheral.GetName()
<< ", impl=" << &peripheral.GetImpl()
<< ", fast advertisement=" << fast_advertisement;
discovered_peripheral = &peripheral;
BlePeripheral peripheral,
const BleAdvertisementData& advertisement_data) {
discovered_peripheral = std::move(peripheral);
found_latch.CountDown();
},
});
ble_b.StartAdvertising(service_id, advertisement_bytes,
fast_advertisement_service_uuid);
ble_b.StartAcceptingConnections(
service_id, [&](BleSocket socket, const std::string& service_id) {
LOG(INFO) << "Connection accepted: socket=" << &socket
<< ", service_id=" << service_id;
accepted_latch.CountDown();
});
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
BleSocket socket_a;
BleSocket socket_b;
EXPECT_FALSE(socket_a.IsValid());
EXPECT_FALSE(socket_b.IsValid());
{
CancellationFlag flag;
SingleThreadExecutor server_executor;
SingleThreadExecutor client_executor;
client_executor.Execute(
[&ble_a, &socket_a, discovered_peripheral, &service_id, &flag]() {
socket_a = ble_a.Connect(*discovered_peripheral, service_id, &flag);
[&ble_a, &socket_a, &service_id,
discovered_peripheral = std::move(discovered_peripheral),
&server_socket, &flag]() {
socket_a = ble_a.Connect(service_id, kTxPowerLevel,
discovered_peripheral, &flag);
if (!socket_a.IsValid()) {
server_socket.Close();
}
});
server_executor.Execute([&socket_b, &server_socket]() {
socket_b = server_socket.Accept();
if (!socket_b.IsValid()) {
server_socket.Close();
}
});
}
EXPECT_TRUE(accepted_latch.Await(kWaitDuration).result());
EXPECT_TRUE(socket_a.IsValid());
ble_b.StopAdvertising(service_id);
ble_a.StopScanning(service_id);
EXPECT_TRUE(socket_b.IsValid());
server_socket.Close();
env_.Stop();
}
TEST_P(BleMediumTest, CanConnectToServiceWithMultipleServices) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleMedium ble_a(adapter_a_);
BleMedium ble_b(adapter_b_);
Uuid service_uuid(1234, 5678);
std::string service_id(kServiceIDA);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
BleServerSocket server_socket = ble_b.OpenServerSocket(service_id);
EXPECT_TRUE(server_socket.IsValid());
// Assemble regular advertisement data
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
(ble_b.StartAdvertising(advertising_data, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
BlePeripheral discovered_peripheral;
ble_a.StartMultipleServicesScanning(
std::vector<Uuid>{service_uuid}, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch, &discovered_peripheral](
BlePeripheral peripheral,
const BleAdvertisementData& advertisement_data) {
discovered_peripheral = std::move(peripheral);
found_latch.CountDown();
},
});
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
BleSocket socket_a;
BleSocket socket_b;
EXPECT_FALSE(socket_a.IsValid());
EXPECT_FALSE(socket_b.IsValid());
{
CancellationFlag flag;
SingleThreadExecutor server_executor;
SingleThreadExecutor client_executor;
client_executor.Execute(
[&ble_a, &socket_a, &service_id,
discovered_peripheral = std::move(discovered_peripheral),
&server_socket, &flag]() {
socket_a = ble_a.Connect(service_id, kTxPowerLevel,
discovered_peripheral, &flag);
if (!socket_a.IsValid()) {
server_socket.Close();
}
});
server_executor.Execute([&socket_b, &server_socket]() {
socket_b = server_socket.Accept();
if (!socket_b.IsValid()) {
server_socket.Close();
}
});
}
EXPECT_TRUE(socket_a.IsValid());
EXPECT_TRUE(socket_b.IsValid());
server_socket.Close();
env_.Stop();
}
TEST_P(BleMediumTest, CanDiscoverMultipleServices) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BluetoothAdapter adapter_c_;
BleMedium ble_a(adapter_a_);
BleMedium ble_b(adapter_b_);
BleMedium ble_c(adapter_c_);
Uuid service_uuid_a(1234, 5678);
Uuid service_uuid_b(1234, 5679);
std::string service_id(kServiceIDA);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
CountDownLatch found_latch(1);
// Start advertising on adapter a
BleAdvertisementData advertising_data_a;
advertising_data_a.is_extended_advertisement = false;
advertising_data_a.service_data = {{service_uuid_a, advertisement_bytes}};
(ble_a.StartAdvertising(advertising_data_a, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
// Start advertising on adapter b
BleAdvertisementData advertising_data_b;
advertising_data_b.is_extended_advertisement = false;
advertising_data_b.service_data = {{service_uuid_b, advertisement_bytes}};
(ble_b.StartAdvertising(advertising_data_b, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
// Discover both services on adapter c
bool found_service_a = false;
bool found_service_b = false;
ble_c.StartMultipleServicesScanning(
std::vector<Uuid>{service_uuid_a, service_uuid_b}, kTxPowerLevel,
{.advertisement_found_cb =
[&found_latch, &found_service_a, &found_service_b, &service_uuid_a,
&service_uuid_b](BlePeripheral peripheral,
const BleAdvertisementData& advertisement_data) {
if (advertisement_data.service_data.contains(service_uuid_a)) {
found_service_a = true;
}
if (advertisement_data.service_data.contains(service_uuid_b)) {
found_service_b = true;
}
if (found_service_a && found_service_b) {
found_latch.CountDown();
}
}});
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
env_.Stop();
}
@@ -117,60 +265,71 @@ TEST_P(BleMediumTest, CanCancelConnect) {
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleMedium ble_a{adapter_a_};
BleMedium ble_b{adapter_b_};
std::string service_id(kServiceID);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
BleMedium ble_a(adapter_a_);
BleMedium ble_b(adapter_b_);
Uuid service_uuid(1234, 5678);
std::string service_id(kServiceIDA);
ByteArray advertisement_bytes((std::string(kAdvertisementString)));
CountDownLatch found_latch(1);
CountDownLatch accepted_latch(1);
CancellationFlag flag(true);
CountDownLatch lost_latch(1);
BlePeripheral* discovered_peripheral = nullptr;
BleServerSocket server_socket = ble_b.OpenServerSocket(service_id);
EXPECT_TRUE(server_socket.IsValid());
// Assemble regular advertisement data.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
(ble_b.StartAdvertising(advertising_data, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
BlePeripheral discovered_peripheral;
ble_a.StartScanning(
service_id, fast_advertisement_service_uuid,
DiscoveredPeripheralCallback{
.peripheral_discovered_cb =
service_uuid, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch, &discovered_peripheral](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) {
LOG(INFO) << "Discovered peripheral=" << peripheral.GetName()
<< ", impl=" << &peripheral.GetImpl()
<< ", fast advertisement=" << fast_advertisement;
discovered_peripheral = &peripheral;
BlePeripheral peripheral,
const BleAdvertisementData& advertisement_data) {
discovered_peripheral = std::move(peripheral);
found_latch.CountDown();
},
});
ble_b.StartAdvertising(service_id, advertisement_bytes,
fast_advertisement_service_uuid);
ble_b.StartAcceptingConnections(
service_id, [&](BleSocket socket, const std::string& service_id) {
LOG(INFO) << "Connection accepted: socket=" << &socket
<< ", service_id=" << service_id;
accepted_latch.CountDown();
});
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
BleSocket socket_a;
BleSocket socket_b;
EXPECT_FALSE(socket_a.IsValid());
EXPECT_FALSE(socket_b.IsValid());
{
CancellationFlag flag(true);
SingleThreadExecutor server_executor;
SingleThreadExecutor client_executor;
client_executor.Execute(
[&ble_a, &socket_a, discovered_peripheral, &service_id, &flag]() {
socket_a = ble_a.Connect(*discovered_peripheral, service_id, &flag);
[&ble_a, &socket_a, &service_id,
discovered_peripheral = std::move(discovered_peripheral),
&server_socket, &flag]() {
socket_a = ble_a.Connect(service_id, kTxPowerLevel,
discovered_peripheral, &flag);
if (!socket_a.IsValid()) {
server_socket.Close();
}
});
server_executor.Execute([&socket_b, &server_socket]() {
socket_b = server_socket.Accept();
if (!socket_b.IsValid()) {
server_socket.Close();
}
});
}
// If FeatureFlag is disabled, Cancelled is false as no-op.
if (!feature_flags.enable_cancellation_flag) {
EXPECT_TRUE(accepted_latch.Await(kWaitDuration).result());
EXPECT_TRUE(socket_a.IsValid());
EXPECT_TRUE(socket_b.IsValid());
} else {
EXPECT_FALSE(accepted_latch.Await(kWaitDuration).result());
EXPECT_FALSE(socket_a.IsValid());
EXPECT_FALSE(socket_b.IsValid());
}
ble_b.StopAdvertising(service_id);
ble_a.StopScanning(service_id);
server_socket.Close();
env_.Stop();
}
@@ -179,116 +338,514 @@ INSTANTIATE_TEST_SUITE_P(ParametrisedBleMediumTest, BleMediumTest,
TEST_F(BleMediumTest, ConstructorDestructorWorks) {
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleMedium ble_a{adapter_a_};
BleMedium ble_b{adapter_b_};
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleMedium ble_a(adapter_a);
BleMedium ble_b(adapter_b);
// Make sure we can create functional mediums.
ASSERT_TRUE(ble_a.IsValid());
ASSERT_TRUE(ble_b.IsValid());
// Make sure we can create 2 distinct mediums.
EXPECT_NE(&ble_a.GetImpl(), &ble_b.GetImpl());
EXPECT_NE(ble_a.GetImpl(), ble_b.GetImpl());
env_.Stop();
}
TEST_F(BleMediumTest, CanStartAdvertising) {
TEST_F(BleMediumTest, CanStartFastScanningAndFastAdvertising) {
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleMedium ble_a{adapter_a_};
BleMedium ble_b{adapter_b_};
std::string service_id(kServiceID);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleMedium ble_a(adapter_a);
BleMedium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
ByteArray advertisement_bytes((std::string(kAdvertisementString)));
CountDownLatch found_latch(1);
ble_a.StartAdvertising(service_id, advertisement_bytes,
fast_advertisement_service_uuid);
EXPECT_TRUE(ble_b.StartScanning(
service_id, fast_advertisement_service_uuid,
DiscoveredPeripheralCallback{
.peripheral_discovered_cb =
[&found_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) { found_latch.CountDown(); },
EXPECT_TRUE(ble_a.StartScanning(
service_uuid, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch](BlePeripheral peripheral,
const BleAdvertisementData& advertisement_data) {
found_latch.CountDown();
},
}));
// Fail to start extended advertisement due to g3 Ble medium does not support.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = true;
advertising_data.service_data.insert({service_uuid, advertisement_bytes});
EXPECT_FALSE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
// Succeed to start regular advertisement.
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
EXPECT_TRUE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_b.GetImpl()).value().is_advertising);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_TRUE(ble_a.StopAdvertising(service_id));
EXPECT_TRUE(ble_b.StopScanning(service_id));
EXPECT_TRUE(ble_a.StopScanning());
EXPECT_TRUE(ble_b.StopAdvertising());
EXPECT_FALSE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_FALSE(
env_.GetBleMediumStatus(*ble_b.GetImpl()).value().is_advertising);
env_.Stop();
}
TEST_F(BleMediumTest, CanStartScanning) {
TEST_F(BleMediumTest, CanStartScanningAndAdvertising) {
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleMedium ble_a{adapter_a_};
BleMedium ble_b{adapter_b_};
std::string service_id(kServiceID);
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleMedium ble_a(adapter_a);
BleMedium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
ByteArray advertisement_header_bytes{std::string(kAdvertisementHeaderString)};
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
ble_a.StartScanning(
service_id, fast_advertisement_service_uuid,
DiscoveredPeripheralCallback{
.peripheral_discovered_cb =
[&found_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) { found_latch.CountDown(); },
.peripheral_lost_cb =
[&lost_latch](BlePeripheral& peripheral,
const std::string& service_id) {
lost_latch.CountDown();
EXPECT_TRUE(ble_a.StartScanning(
service_uuid, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch](BlePeripheral peripheral,
const BleAdvertisementData& advertisement_data) {
found_latch.CountDown();
},
});
EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes,
fast_advertisement_service_uuid));
}));
// Fail to start extended advertisement due to g3 Ble medium does not support.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = true;
advertising_data.service_data.insert({service_uuid, advertisement_bytes});
EXPECT_FALSE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
// Succeed to start regular advertisement.
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_header_bytes}};
EXPECT_TRUE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_b.GetImpl()).value().is_advertising);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_TRUE(ble_b.StopAdvertising(service_id));
EXPECT_TRUE(lost_latch.Await(kWaitDuration).result());
EXPECT_TRUE(ble_a.StopScanning(service_id));
EXPECT_TRUE(ble_a.StopScanning());
EXPECT_TRUE(ble_b.StopAdvertising());
EXPECT_FALSE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_FALSE(
env_.GetBleMediumStatus(*ble_b.GetImpl()).value().is_advertising);
env_.UnregisterBleMedium(*ble_a.GetImpl());
env_.UnregisterBleMedium(*ble_b.GetImpl());
EXPECT_EQ(env_.GetBleMediumStatus(*ble_a.GetImpl()), std::nullopt);
EXPECT_EQ(env_.GetBleMediumStatus(*ble_b.GetImpl()), std::nullopt);
env_.Stop();
}
TEST_F(BleMediumTest, CanStopDiscovery) {
TEST_F(BleMediumTest, StartThenStopAsyncScanning) {
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleMedium ble_a{adapter_a_};
BleMedium ble_b{adapter_b_};
std::string service_id(kServiceID);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
BluetoothAdapter adapter_a;
BleMedium ble_a(adapter_a);
Uuid service_uuid(1234, 5678);
CountDownLatch found_latch_a(1);
ble_a.StartScanning(
service_id, fast_advertisement_service_uuid,
DiscoveredPeripheralCallback{
.peripheral_discovered_cb =
[&found_latch](
BlePeripheral& peripheral, const std::string& service_id,
const ByteArray& advertisement_bytes,
bool fast_advertisement) { found_latch.CountDown(); },
.peripheral_lost_cb =
[&lost_latch](BlePeripheral& peripheral,
const std::string& service_id) {
lost_latch.CountDown();
std::unique_ptr<api::ble::BleMedium::ScanningSession> scanning_session_a =
ble_a.StartScanning(
service_uuid, kTxPowerLevel,
api::ble::BleMedium::ScanningCallback{
.advertisement_found_cb =
[&](api::ble::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) -> void {
found_latch_a.CountDown();
},
});
EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes,
fast_advertisement_service_uuid));
});
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_OK(scanning_session_a->stop_scanning());
EXPECT_FALSE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
env_.Stop();
}
TEST_F(BleMediumTest, CanStartMultipleAsyncScanning) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleMedium ble_a(adapter_a);
BleMedium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
CountDownLatch found_latch_a(1);
CountDownLatch found_latch_b(1);
std::unique_ptr<api::ble::BleMedium::ScanningSession> scanning_session_a =
ble_a.StartScanning(
service_uuid, kTxPowerLevel,
api::ble::BleMedium::ScanningCallback{
.advertisement_found_cb =
[&](api::ble::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) -> void {
found_latch_a.CountDown();
},
});
std::unique_ptr<api::ble::BleMedium::ScanningSession> scanning_session_b =
ble_b.StartScanning(
service_uuid, kTxPowerLevel,
api::ble::BleMedium::ScanningCallback{
.advertisement_found_cb =
[&](api::ble::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) -> void {
found_latch_b.CountDown();
},
});
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_b.GetImpl()).value().is_scanning);
EXPECT_OK(scanning_session_a->stop_scanning());
EXPECT_OK(scanning_session_b->stop_scanning());
EXPECT_FALSE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_FALSE(env_.GetBleMediumStatus(*ble_b.GetImpl()).value().is_scanning);
env_.Stop();
}
TEST_F(BleMediumTest, CanStartAsyncScanningAndAdvertising) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleMedium ble_a(adapter_a);
BleMedium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
ByteArray advertisement_header_bytes{std::string(kAdvertisementHeaderString)};
CountDownLatch found_latch(1);
std::unique_ptr<api::ble::BleMedium::ScanningSession> scanning_session =
ble_a.StartScanning(
service_uuid, kTxPowerLevel,
api::ble::BleMedium::ScanningCallback{
.advertisement_found_cb =
[&](api::ble::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) -> void {
found_latch.CountDown();
},
});
// Succeed to start regular advertisement.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_header_bytes}};
EXPECT_TRUE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_TRUE(env_.GetBleMediumStatus(*ble_b.GetImpl()).value().is_advertising);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_TRUE(ble_a.StopScanning(service_id));
EXPECT_TRUE(ble_b.StopAdvertising(service_id));
EXPECT_FALSE(lost_latch.Await(kWaitDuration).result());
EXPECT_OK(scanning_session->stop_scanning());
EXPECT_TRUE(ble_b.StopAdvertising());
EXPECT_FALSE(env_.GetBleMediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_FALSE(
env_.GetBleMediumStatus(*ble_b.GetImpl()).value().is_advertising);
env_.UnregisterBleMedium(*ble_a.GetImpl());
env_.UnregisterBleMedium(*ble_b.GetImpl());
EXPECT_EQ(env_.GetBleMediumStatus(*ble_a.GetImpl()), std::nullopt);
EXPECT_EQ(env_.GetBleMediumStatus(*ble_b.GetImpl()), std::nullopt);
env_.Stop();
}
TEST_F(BleMediumTest, CanStartGattServer) {
env_.Start();
BluetoothAdapter adapter;
BleMedium ble(adapter);
Uuid service_uuid(1234, 5678);
Uuid characteristic_uuid(5678, 1234);
std::unique_ptr<GattServer> gatt_server =
ble.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
GattCharacteristic::Permission permission =
GattCharacteristic::Permission::kRead;
GattCharacteristic::Property property = GattCharacteristic::Property::kRead;
// NOLINTNEXTLINE(google3-legacy-absl-backports)
std::optional<GattCharacteristic> gatt_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid,
permission, property);
ASSERT_TRUE(gatt_characteristic.has_value());
ByteArray any_byte("any");
EXPECT_TRUE(
gatt_server->UpdateCharacteristic(gatt_characteristic.value(), any_byte));
gatt_server->Stop();
env_.Stop();
}
TEST_F(BleMediumTest, GattClientConnectToGattServerWorks) {
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);
// Start GattServer
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
GattCharacteristic::Permission permissions =
GattCharacteristic::Permission::kRead;
GattCharacteristic::Property properties = GattCharacteristic::Property::kRead;
// Add characteristic and its value.
// NOLINTNEXTLINE(google3-legacy-absl-backports)
std::optional<GattCharacteristic> server_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid,
permissions, properties);
ASSERT_TRUE(server_characteristic.has_value());
ByteArray server_value("any");
EXPECT_TRUE(
gatt_server->UpdateCharacteristic(*server_characteristic, server_value));
// Start GattClient
MacAddress mac_address = adapter_a.GetAddress();
EXPECT_TRUE(mac_address.IsSet());
std::unique_ptr<GattClient> gatt_client = ble_b.ConnectToGattServer(
BlePeripheral(ble_b, mac_address.address()), kTxPowerLevel,
/*ClientGattConnectionCallback=*/{});
ASSERT_NE(gatt_client, nullptr);
// Discover service and characteristics.
EXPECT_TRUE(gatt_client->DiscoverServiceAndCharacteristics(
service_uuid, {characteristic_uuid}));
// NOLINTNEXTLINE(google3-legacy-absl-backports)
std::optional<GattCharacteristic> client_characteristic =
gatt_client->GetCharacteristic(service_uuid, characteristic_uuid);
ASSERT_TRUE(client_characteristic.has_value());
// Can read the characteristic value.
EXPECT_THAT(gatt_client->ReadCharacteristic(*client_characteristic),
Optional(server_value.string_data()));
gatt_client->Disconnect();
gatt_server->Stop();
env_.Stop();
}
TEST_F(BleMediumTest, GattClientConnectToStoppedGattServerFails) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleMedium ble_a(adapter_a);
BleMedium ble_b(adapter_b);
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
MacAddress mac_address = adapter_a.GetAddress();
gatt_server->Stop();
std::unique_ptr<GattClient> gatt_client = ble_b.ConnectToGattServer(
BlePeripheral(ble_b, mac_address.address()), kTxPowerLevel,
/*ClientGattConnectionCallback=*/{});
ASSERT_NE(gatt_client, nullptr);
EXPECT_FALSE(gatt_client->IsValid());
env_.Stop();
}
TEST_F(BleMediumTest, GattClientNotifiedWhenServerDisconnects) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleMedium ble_a(adapter_a);
BleMedium ble_b(adapter_b);
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
CountDownLatch disconnected_latch(1);
// 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=*/{.disconnected_cb = [&]() {
disconnected_latch.CountDown();
}});
ASSERT_NE(gatt_client, nullptr);
gatt_server->Stop();
disconnected_latch.Await();
env_.Stop();
}
TEST_F(BleMediumTest, GattClientOperatiosOnCharacteristic) {
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);
std::string written_data;
// Start GattServer.
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{
.on_characteristic_write_cb =
[&](const api::ble::BlePeripheral::UniqueId remote_device_id,
const api::ble::GattCharacteristic& characteristic,
int offset, absl::string_view data,
api::ble::ServerGattConnectionCallback::WriteValueCallback
callback) {
written_data = data;
callback(absl::OkStatus());
}});
ASSERT_NE(gatt_server, nullptr);
// 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);
// Can't not discover service and characteristic.
EXPECT_FALSE(gatt_client->DiscoverServiceAndCharacteristics(
service_uuid, {characteristic_uuid}));
// Add characteristic and its value.
GattCharacteristic::Permission permissions =
GattCharacteristic::Permission::kRead;
GattCharacteristic::Property properties = GattCharacteristic::Property::kRead;
std::optional<GattCharacteristic> server_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid,
permissions, properties);
ASSERT_TRUE(server_characteristic.has_value());
ByteArray server_value("any");
EXPECT_TRUE(
gatt_server->UpdateCharacteristic(*server_characteristic, server_value));
// Can discover service and characteristics.
EXPECT_TRUE(gatt_client->DiscoverServiceAndCharacteristics(
service_uuid, {characteristic_uuid}));
// Can get Characteristic.
std::optional<GattCharacteristic> client_characteristic =
gatt_client->GetCharacteristic(service_uuid, characteristic_uuid);
ASSERT_TRUE(client_characteristic.has_value());
// Can read the characteristic value.
EXPECT_THAT(gatt_client->ReadCharacteristic(*client_characteristic),
Optional(std::string("any")));
// Can write the characteristic value.
EXPECT_TRUE(gatt_client->WriteCharacteristic(
*client_characteristic, "hello",
api::ble::GattClient::WriteType::kWithResponse));
EXPECT_EQ(written_data, "hello");
gatt_client->Disconnect();
// Failed to write/read characteristic value as gatt is disconnected.
EXPECT_FALSE(gatt_client->WriteCharacteristic(
*client_characteristic, "any",
api::ble::GattClient::WriteType::kWithResponse));
EXPECT_THAT(gatt_client->ReadCharacteristic(*client_characteristic),
std::nullopt);
gatt_server->Stop();
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();
}
-290
View File
@@ -1,290 +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.
#include "internal/platform/ble_v2.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/logging.h"
#include "internal/platform/mutex_lock.h"
#include "internal/platform/uuid.h"
namespace nearby {
namespace {
using ::nearby::api::ble_v2::BleAdvertisementData;
using ::nearby::api::ble_v2::GattCharacteristic;
using ::nearby::api::ble_v2::TxPowerLevel;
using ReadValueCallback =
::nearby::api::ble_v2::ServerGattConnectionCallback::ReadValueCallback;
using WriteValueCallback =
::nearby::api::ble_v2::ServerGattConnectionCallback::WriteValueCallback;
} // namespace
bool BleV2Medium::StartAdvertising(
const BleAdvertisementData& advertising_data,
api::ble_v2::AdvertiseParameters advertise_parameters) {
return impl_->StartAdvertising(advertising_data, advertise_parameters);
}
bool BleV2Medium::StopAdvertising() { return impl_->StopAdvertising(); }
std::unique_ptr<api::ble_v2::BleMedium::AdvertisingSession>
BleV2Medium::StartAdvertising(
const api::ble_v2::BleAdvertisementData& advertising_data,
api::ble_v2::AdvertiseParameters advertise_set_parameters,
api::ble_v2::BleMedium::AdvertisingCallback callback) {
return impl_->StartAdvertising(advertising_data, advertise_set_parameters,
std::move(callback));
}
bool BleV2Medium::StartScanning(const Uuid& service_uuid,
TxPowerLevel tx_power_level,
ScanCallback callback) {
MutexLock lock(&mutex_);
if (scanning_enabled_) {
LOG(INFO) << "Ble Scanning already enabled";
return false;
}
bool success = impl_->StartScanning(
service_uuid, tx_power_level,
api::ble_v2::BleMedium::ScanCallback{
.advertisement_found_cb =
[this](api::ble_v2::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) {
MutexLock lock(&mutex_);
BleV2Peripheral proxy(*this, peripheral_id);
if (!scanning_enabled_) return;
scan_callback_.advertisement_found_cb(std::move(proxy),
advertisement_data);
},
});
if (success) {
scan_callback_ = std::move(callback);
scanning_enabled_ = true;
LOG(INFO) << "Ble Scanning enabled";
}
return success;
}
bool BleV2Medium::StartMultipleServicesScanning(
const std::vector<Uuid>& service_uuids,
api::ble_v2::TxPowerLevel tx_power_level, ScanCallback callback) {
MutexLock lock(&mutex_);
if (scanning_enabled_) {
LOG(INFO) << "Ble Scanning already enabled";
return false;
}
bool success = impl_->StartMultipleServicesScanning(
service_uuids, tx_power_level,
api::ble_v2::BleMedium::ScanCallback{
.advertisement_found_cb =
[this](api::ble_v2::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) {
MutexLock lock(&mutex_);
BleV2Peripheral proxy(*this, peripheral_id);
if (!scanning_enabled_) return;
scan_callback_.advertisement_found_cb(std::move(proxy),
advertisement_data);
},
});
if (success) {
scan_callback_ = std::move(callback);
scanning_enabled_ = true;
LOG(INFO) << "Ble Scanning enabled";
}
return success;
}
BleV2Medium::~BleV2Medium() { StopScanning(); }
bool BleV2Medium::StopScanning() {
MutexLock lock(&mutex_);
if (!scanning_enabled_) {
return true;
}
scanning_enabled_ = false;
scan_callback_ = {};
LOG(INFO) << "Ble Scanning disabled";
return impl_->StopScanning();
}
bool BleV2Medium::PauseMediumScanning() {
MutexLock lock(&mutex_);
if (!scanning_enabled_) {
return true;
}
LOG(INFO) << "Pause Medium level BLE_V2 Scanning";
return impl_->PauseMediumScanning();
}
bool BleV2Medium::ResumeMediumScanning() {
MutexLock lock(&mutex_);
return impl_->ResumeMediumScanning();
}
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession>
BleV2Medium::StartScanning(const Uuid& service_uuid,
api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BleMedium::ScanningCallback callback) {
LOG(INFO) << "platform mutex: " << &mutex_;
return impl_->StartScanning(
service_uuid, tx_power_level,
api::ble_v2::BleMedium::ScanningCallback{
.start_scanning_result =
[this, start_scanning_result =
std::move(callback.start_scanning_result)](
absl::Status status) mutable {
{
MutexLock lock(&mutex_);
if (status.ok()) {
scanning_enabled_ = true;
}
}
start_scanning_result(status);
},
.advertisement_found_cb = std::move(callback.advertisement_found_cb),
.advertisement_lost_cb = std::move(callback.advertisement_lost_cb),
});
}
std::unique_ptr<GattServer> BleV2Medium::StartGattServer(
ServerGattConnectionCallback callback) {
{
MutexLock lock(&mutex_);
server_gatt_connection_callback_ = std::move(callback);
}
std::unique_ptr<api::ble_v2::GattServer> api_gatt_server =
impl_->StartGattServer({
.characteristic_subscription_cb =
[this](const GattCharacteristic& characteristic) {
MutexLock lock(&mutex_);
server_gatt_connection_callback_.characteristic_subscription_cb(
characteristic);
},
.characteristic_unsubscription_cb =
[this](const GattCharacteristic& characteristic) {
MutexLock lock(&mutex_);
server_gatt_connection_callback_
.characteristic_unsubscription_cb(characteristic);
},
.on_characteristic_read_cb =
[this](
const api::ble_v2::BlePeripheral::UniqueId remote_device_id,
const GattCharacteristic& characteristic, int offset,
ReadValueCallback callback) {
MutexLock lock(&mutex_);
if (server_gatt_connection_callback_
.on_characteristic_read_cb) {
server_gatt_connection_callback_.on_characteristic_read_cb(
remote_device_id, characteristic, offset,
std::move(callback));
} else {
callback(absl::FailedPreconditionError(
"Read characteristic callback is not set"));
}
},
.on_characteristic_write_cb =
[this](
const api::ble_v2::BlePeripheral::UniqueId remote_device_id,
const GattCharacteristic& characteristic, int offset,
absl::string_view data, WriteValueCallback callback) {
MutexLock lock(&mutex_);
if (server_gatt_connection_callback_
.on_characteristic_write_cb) {
server_gatt_connection_callback_.on_characteristic_write_cb(
remote_device_id, characteristic, offset, data,
std::move(callback));
} else {
callback(absl::FailedPreconditionError(
"Write characteristic callback is not set"));
}
},
});
return std::make_unique<GattServer>(std::move(api_gatt_server));
}
std::unique_ptr<GattClient> BleV2Medium::ConnectToGattServer(
BleV2Peripheral peripheral, TxPowerLevel tx_power_level,
ClientGattConnectionCallback callback) {
std::optional<api::ble_v2::BlePeripheral::UniqueId> id =
peripheral.GetUniqueId();
if (!id.has_value()) {
LOG(ERROR) << "Failed to connect to GattServer, invalid peripheral";
return nullptr;
}
return std::make_unique<GattClient>(impl_->ConnectToGattServer(
*id, tx_power_level,
{
.disconnected_cb =
[callback = std::move(callback)]() mutable {
callback.disconnected_cb();
},
}));
}
BleV2ServerSocket BleV2Medium::OpenServerSocket(const std::string& service_id) {
return BleV2ServerSocket(*this, impl_->OpenServerSocket(service_id));
}
BleL2capServerSocket BleV2Medium::OpenL2capServerSocket(
const std::string& service_id) {
return BleL2capServerSocket(*this, impl_->OpenL2capServerSocket(service_id));
}
BleV2Socket BleV2Medium::Connect(const std::string& service_id,
TxPowerLevel tx_power_level,
const BleV2Peripheral& peripheral,
CancellationFlag* cancellation_flag) {
std::optional<api::ble_v2::BlePeripheral::UniqueId> id =
peripheral.GetUniqueId();
if (!id.has_value()) {
LOG(ERROR) << "Failed to connect, invalid peripheral";
return {};
}
return BleV2Socket(peripheral, impl_->Connect(service_id, tx_power_level, *id,
cancellation_flag));
}
BleL2capSocket BleV2Medium::ConnectOverL2cap(
const std::string& service_id, TxPowerLevel tx_power_level,
const BleV2Peripheral& peripheral, CancellationFlag* cancellation_flag) {
std::optional<api::ble_v2::BlePeripheral::UniqueId> id =
peripheral.GetUniqueId();
if (!id.has_value()) {
LOG(ERROR) << "Failed to connect over L2cap, invalid peripheral";
return {};
}
return BleL2capSocket(
peripheral,
impl_->ConnectOverL2cap(peripheral.GetPsm(), service_id, tx_power_level,
*id, cancellation_flag));
}
bool BleV2Medium::IsExtendedAdvertisementsAvailable() {
return IsValid() && impl_->IsExtendedAdvertisementsAvailable();
}
bool BleV2Peripheral::IsValid() const { return unique_id_.has_value(); }
} // namespace nearby
-592
View File
@@ -1,592 +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_PUBLIC_BLE_V2_H_
#define PLATFORM_PUBLIC_BLE_V2_H_
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/functional/any_invocable.h"
#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"
#include "internal/platform/exception.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/platform.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/listeners.h"
#include "internal/platform/logging.h"
#include "internal/platform/mutex.h"
#include "internal/platform/output_stream.h"
#include "internal/platform/uuid.h"
namespace nearby {
class BleV2Medium;
// TODO: b/399815436 - Remove all the shared_ptr in this file.
// Opaque wrapper over a BLE peripheral. Must contain enough data about a
// particular BLE peripheral to connect to its GATT server.
class BleV2Peripheral final {
public:
BleV2Peripheral() = default;
BleV2Peripheral(BleV2Medium& medium,
api::ble_v2::BlePeripheral::UniqueId unique_id)
: medium_(&medium), unique_id_(unique_id) {}
BleV2Peripheral(const BleV2Peripheral&) = default;
BleV2Peripheral& operator=(const BleV2Peripheral&) = default;
BleV2Peripheral(BleV2Peripheral&& other) = default;
BleV2Peripheral& operator=(BleV2Peripheral&& other) = default;
ByteArray GetId() const { return id_; }
void SetId(const ByteArray& id) { id_ = id; }
int GetPsm() const { return psm_; }
void SetPsm(int psm) { psm_ = psm; }
bool IsValid() const;
std::optional<api::ble_v2::BlePeripheral::UniqueId> GetUniqueId() const {
return unique_id_;
}
api::ble_v2::BlePeripheral* GetImpl() const;
std::string ToReadableString() const {
if (!IsValid()) {
return "BleV2Peripheral { invalid }";
}
return absl::StrFormat("BleV2Peripheral { id=%s, psm=%d}",
absl::BytesToHexString(GetId().AsStringView()),
GetPsm());
}
private:
BleV2Medium* medium_ = nullptr;
std::optional<api::ble_v2::BlePeripheral::UniqueId> unique_id_;
// A unique identifier for this peripheral. It is the BLE advertisement bytes
// it was found on.
ByteArray id_ = {};
// The psm (protocol service multiplexer) value is used for create data
// connection on L2CAP socket. It only exists when remote device supports
// L2CAP socket feature.
int psm_ = 0;
};
// Container of operations that can be performed over the BLE GATT client
// socket.
// This class is copyable but not moveable.
class BleV2Socket final {
public:
BleV2Socket() = default;
BleV2Socket(BleV2Peripheral peripheral,
std::unique_ptr<api::ble_v2::BleSocket> socket)
: peripheral_(peripheral) {
state_->socket = std::move(socket);
}
BleV2Socket(const BleV2Socket&) = default;
BleV2Socket& operator=(const BleV2Socket&) = default;
// Returns the InputStream of the BleSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleSocket object is destroyed.
InputStream& GetInputStream() { return state_->socket->GetInputStream(); }
// Returns the OutputStream of the BleSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleSocket object is destroyed.
OutputStream& GetOutputStream() { return state_->socket->GetOutputStream(); }
// Sets the close notifier by client side.
void SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
state_->close_notifier = std::move(notifier);
}
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() {
if (state_->close_notifier != nullptr) {
absl::AnyInvocable<void()> notifier = std::move(state_->close_notifier);
notifier();
}
return state_->socket->Close();
}
// Returns BlePeripheral object which wraps a valid BlePeripheral pointer.
BleV2Peripheral& GetRemotePeripheral() { return peripheral_; }
// Returns true if a socket is usable. If this method returns false,
// it is not safe to call any other method.
// NOTE(socket validity):
// Socket created by a default public constructor is not valid, because
// it is missing platform implementation.
// The only way to obtain a valid socket is through connection, such as
// an object returned by BleMedium::Connect
// These methods may also return an invalid socket if connection failed for
// any reason.
bool IsValid() const { return state_->socket != nullptr; }
// Returns reference to platform implementation.
// This is used to communicate with platform code, and for debugging purposes.
// Returned reference will remain valid for while BleSocket object is
// itself valid. Typically BleSocket lifetime matches duration of the
// connection, and is controlled by end user, since they hold the instance.
api::ble_v2::BleSocket& GetImpl() { return *state_->socket; }
private:
struct SharedState {
std::unique_ptr<api::ble_v2::BleSocket> socket;
absl::AnyInvocable<void()> close_notifier;
};
std::shared_ptr<SharedState> state_ = std::make_shared<SharedState>();
BleV2Peripheral peripheral_;
};
// Container of operations that can be performed over the BLE GATT server
// socket.
// This class is copyable but not moveable.
class BleV2ServerSocket final {
public:
BleV2ServerSocket(BleV2Medium& medium,
std::unique_ptr<api::ble_v2::BleServerSocket> socket)
: medium_(&medium), impl_(std::move(socket)) {}
BleV2ServerSocket(const BleV2ServerSocket&) = default;
BleV2ServerSocket& operator=(const BleV2ServerSocket&) = default;
// Blocks until either:
// - at least one incoming connection request is available, or
// - ServerSocket is closed.
// On success, returns connected socket, ready to exchange data.
// On error, "impl_" will be nullptr and the caller will check it by calling
// member function "IsValid()"
// Once error is reported, it is permanent, and
// ServerSocket has to be closed by caller.
BleV2Socket Accept() {
std::unique_ptr<api::ble_v2::BleSocket> socket = impl_->Accept();
BleV2Peripheral peripheral;
if (socket == nullptr) {
LOG(INFO) << "BleServerSocket Accept() failed on server socket: " << this;
} else {
peripheral = BleV2Peripheral(*medium_, socket->GetRemotePeripheralId());
}
return BleV2Socket(peripheral, std::move(socket));
}
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() {
LOG(INFO) << "BleServerSocket Closing:: " << this;
return impl_->Close();
}
bool IsValid() const { return impl_ != nullptr; }
api::ble_v2::BleServerSocket& GetImpl() { return *impl_; }
private:
BleV2Medium* medium_;
std::shared_ptr<api::ble_v2::BleServerSocket> impl_;
};
// Opaque wrapper over a GattServer.
// Move only, disallow copy.
//
// Note that some of the methods return absl::optional instead
// of std::optional, because iOS platform is still in C++14.
class GattServer final {
public:
explicit GattServer(std::unique_ptr<api::ble_v2::GattServer> gatt_server)
: impl_(std::move(gatt_server)) {}
~GattServer() { Stop(); }
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::optional<api::ble_v2::GattCharacteristic> CreateCharacteristic(
const Uuid& service_uuid, const Uuid& characteristic_uuid,
const api::ble_v2::GattCharacteristic::Permission permission,
const api::ble_v2::GattCharacteristic::Property property) {
return impl_->CreateCharacteristic(service_uuid, characteristic_uuid,
permission, property);
}
bool UpdateCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic,
const ByteArray& value) {
return impl_->UpdateCharacteristic(characteristic, value);
}
absl::Status NotifyCharacteristicChanged(
const api::ble_v2::GattCharacteristic& characteristic, bool confirm,
const ByteArray& new_value) {
return impl_->NotifyCharacteristicChanged(characteristic, confirm,
new_value);
}
void Stop() {
if (impl_) return impl_->Stop();
}
// Returns true if a gatt_server is usable. If this method returns false,
// it is not safe to call any other method.
bool IsValid() const { return impl_ != nullptr; }
// Returns reference to platform implementation.
// This is used to communicate with platform code, and for debugging
// purposes.
api::ble_v2::GattServer* GetImpl() { return impl_.get(); }
private:
std::unique_ptr<api::ble_v2::GattServer> impl_;
};
// Opaque wrapper for a GattClient.
//
// Note that some of the methods return absl::optional instead
// of std::optional, because iOS platform is still in C++14.
class GattClient final {
public:
explicit GattClient(
std::unique_ptr<api::ble_v2::GattClient> client_gatt_connection)
: impl_(std::move(client_gatt_connection)) {}
bool DiscoverServiceAndCharacteristics(
const Uuid& service_uuid, const std::vector<Uuid>& characteristic_uuids) {
return impl_->DiscoverServiceAndCharacteristics(service_uuid,
characteristic_uuids);
}
// NOLINTNEXTLINE(google3-legacy-absl-backports)
absl::optional<api::ble_v2::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(
const api::ble_v2::GattCharacteristic& characteristic) {
return impl_->ReadCharacteristic(characteristic);
}
// NOLINTNEXTLINE(google3-legacy-absl-backports)
bool WriteCharacteristic(
const api::ble_v2::GattCharacteristic& characteristic,
absl::string_view value, api::ble_v2::GattClient::WriteType write_type) {
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_v2::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
// returns false, it is not safe to call any other method.
bool IsValid() const { return impl_ != nullptr; }
// Returns reference to platform implementation.
// This is used to communicate with platform code, and for debugging
// purposes.
api::ble_v2::GattClient* GetImpl() { return impl_.get(); }
private:
std::unique_ptr<api::ble_v2::GattClient> impl_;
};
class BleL2capSocket final {
public:
BleL2capSocket() = default;
BleL2capSocket(BleV2Peripheral peripheral,
std::unique_ptr<api::ble_v2::BleL2capSocket> socket)
: peripheral_(peripheral) {
state_->socket = std::move(socket);
}
BleL2capSocket(const BleL2capSocket&) = default;
BleL2capSocket& operator=(const BleL2capSocket&) = default;
// Returns the InputStream of the BleL2capSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleL2capSocket object is destroyed.
InputStream& GetInputStream() { return state_->socket->GetInputStream(); }
// Returns the OutputStream of the BleL2capSocket.
// On error, returned stream will report Exception::kIo on any operation.
//
// The returned object is not owned by the caller, and can be invalidated once
// the BleL2capSocket object is destroyed.
OutputStream& GetOutputStream() { return state_->socket->GetOutputStream(); }
// Sets the close notifier by client side.
void SetCloseNotifier(absl::AnyInvocable<void()> notifier) {
state_->close_notifier = std::move(notifier);
}
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() {
if (state_->close_notifier != nullptr) {
absl::AnyInvocable<void()> notifier = std::move(state_->close_notifier);
notifier();
}
return state_->socket->Close();
}
// Returns BlePeripheral object which wraps a valid BlePeripheral pointer.
BleV2Peripheral& GetRemotePeripheral() { return peripheral_; }
// Returns true if a socket is usable. If this method returns false,
// it is not safe to call any other method.
// NOTE(socket validity):
// Socket created by a default public constructor is not valid, because
// it is missing platform implementation.
// The only way to obtain a valid socket is through connection, such as
// an object returned by BleMedium::ConnectOverL2cap.
// These methods may also return an invalid socket if connection failed for
// any reason.
bool IsValid() const { return state_->socket != nullptr; }
// Returns reference to platform implementation.
// This is used to communicate with platform code, and for debugging purposes.
// Returned reference will remain valid for while BleSocket object is
// itself valid. Typically BleSocket lifetime matches duration of the
// connection, and is controlled by end user, since they hold the instance.
api::ble_v2::BleL2capSocket& GetImpl() { return *state_->socket; }
private:
struct SharedState {
std::unique_ptr<api::ble_v2::BleL2capSocket> socket;
absl::AnyInvocable<void()> close_notifier;
};
std::shared_ptr<SharedState> state_ = std::make_shared<SharedState>();
BleV2Peripheral peripheral_;
};
class BleL2capServerSocket final {
public:
BleL2capServerSocket(
BleV2Medium& medium,
std::unique_ptr<api::ble_v2::BleL2capServerSocket> socket)
: medium_(&medium), impl_(std::move(socket)) {}
BleL2capServerSocket(const BleL2capServerSocket&) = default;
BleL2capServerSocket& operator=(const BleL2capServerSocket&) = default;
// Gets PSM value has been published by the server.
int GetPSM() { return impl_->GetPSM(); }
// Blocks until either:
// - at least one incoming connection request is available, or
// - ServerSocket is closed.
// On success, returns connected socket, ready to exchange data.
// On error, "impl_" will be nullptr and the caller will check it by calling
// member function "IsValid()"
// Once error is reported, it is permanent, and
// ServerSocket has to be closed by caller.
BleL2capSocket Accept() {
std::unique_ptr<api::ble_v2::BleL2capSocket> socket = impl_->Accept();
BleV2Peripheral peripheral;
if (socket == nullptr) {
LOG(INFO) << "BleL2capServerSocket Accept() failed on server socket: "
<< this;
} else {
peripheral =
BleV2Peripheral(*medium_, socket->GetRemotePeripheralId());
}
return BleL2capSocket(peripheral, std::move(socket));
}
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() {
LOG(INFO) << "BleL2capServerSocket Closing:: " << this;
return impl_->Close();
}
// Returns true if a BleL2capServerSocket is usable. If this method returns
// false, it is not safe to call any other method.
bool IsValid() const { return impl_ != nullptr; }
api::ble_v2::BleL2capServerSocket& GetImpl() { return *impl_; }
private:
BleV2Medium* medium_ = nullptr;
std::shared_ptr<api::ble_v2::BleL2capServerSocket> impl_;
};
// Container of operations that can be performed over the BLE medium.
class BleV2Medium final {
public:
// A wrapper callback for BLE scan results.
//
// The peripheral is a wrapper object which stores the real impl of
// api::BlePeripheral.
// The reference will remain valid while api::BlePeripheral object is
// itself valid. Typically peripheral lifetime matches duration of the
// connection, and is controlled by primitive client, since they hold the
// instance.
struct ScanCallback {
absl::AnyInvocable<void(
BleV2Peripheral peripheral,
const api::ble_v2::BleAdvertisementData& advertisement_data)>
advertisement_found_cb =
nearby::DefaultCallback<BleV2Peripheral,
const api::ble_v2::BleAdvertisementData&>();
};
struct ServerGattConnectionCallback {
absl::AnyInvocable<void(
const api::ble_v2::GattCharacteristic& characteristic)>
characteristic_subscription_cb =
nearby::DefaultCallback<const api::ble_v2::GattCharacteristic&>();
absl::AnyInvocable<void(
const api::ble_v2::GattCharacteristic& characteristic)>
characteristic_unsubscription_cb =
nearby::DefaultCallback<const api::ble_v2::GattCharacteristic&>();
absl::AnyInvocable<void(
const api::ble_v2::BlePeripheral::UniqueId remote_device_id,
const api::ble_v2::GattCharacteristic& characteristic, int offset,
api::ble_v2::ServerGattConnectionCallback::ReadValueCallback callback)>
on_characteristic_read_cb;
absl::AnyInvocable<void(
const api::ble_v2::BlePeripheral::UniqueId remote_device_id,
const api::ble_v2::GattCharacteristic& characteristic, int offset,
absl::string_view data,
api::ble_v2::ServerGattConnectionCallback::WriteValueCallback callback)>
on_characteristic_write_cb;
};
// TODO(b/231318879): Remove this wrapper callback and use impl callback if
// there is only disconnect function here in the end.
struct ClientGattConnectionCallback {
absl::AnyInvocable<void()> disconnected_cb = nearby::DefaultCallback<>();
};
explicit BleV2Medium(BluetoothAdapter& adapter)
: impl_(
api::ImplementationPlatform::CreateBleV2Medium(adapter.GetImpl())),
adapter_(adapter) {}
~BleV2Medium();
// Returns true once the BLE advertising has been initiated.
// This interface will be deprecated soon.
// TODO(b/271305977) remove this function.
// Use 'unique_ptr<AdvertisingSession> StartAdvertising' instead.
bool StartAdvertising(
const api::ble_v2::BleAdvertisementData& advertising_data,
api::ble_v2::AdvertiseParameters advertise_parameters);
// This interface will be deprecated soon.
// TODO(b/271305977) remove this function.
bool StopAdvertising();
std::unique_ptr<api::ble_v2::BleMedium::AdvertisingSession> StartAdvertising(
const api::ble_v2::BleAdvertisementData& advertising_data,
api::ble_v2::AdvertiseParameters advertise_set_parameters,
api::ble_v2::BleMedium::AdvertisingCallback callback);
// Returns true once the BLE scan has been initiated.
// This interface will be deprecated soon.
bool StartScanning(const Uuid& service_uuid,
api::ble_v2::TxPowerLevel tx_power_level,
ScanCallback callback);
// Returns true once the BLE multiple services scan has been initiated.
bool StartMultipleServicesScanning(const std::vector<Uuid>& service_uuids,
api::ble_v2::TxPowerLevel tx_power_level,
ScanCallback callback);
// This interface will be deprecated soon.
// TODO(b/271305977) remove this function.
bool StopScanning();
// Pause BLE scanning at platform Medium level.
bool PauseMediumScanning();
// Resume BLE scanning at platform Medium level.
bool ResumeMediumScanning();
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession> StartScanning(
const Uuid& service_uuid, api::ble_v2::TxPowerLevel tx_power_level,
api::ble_v2::BleMedium::ScanningCallback callback);
// Starts Gatt Server for waiting to client connection.
std::unique_ptr<GattServer> StartGattServer(
ServerGattConnectionCallback callback);
// Returns a new GattClient connection to a gatt server.
// There is only one instance of GattServer can run at a time.
std::unique_ptr<GattClient> ConnectToGattServer(
BleV2Peripheral peripheral, api::ble_v2::TxPowerLevel tx_power_level,
ClientGattConnectionCallback callback);
// Returns a new BleServerSocket.
// On Success, BleServerSocket::IsValid() returns true.
BleV2ServerSocket OpenServerSocket(const std::string& service_id);
// Returns a new BleL2capServerSocket.
// On Success, BleL2capServerSocket::IsValid() returns true.
BleL2capServerSocket OpenL2capServerSocket(const std::string& service_id);
// Returns a new BleV2Socket.
// On Success, BleV2Socket::IsValid() returns true.
BleV2Socket Connect(const std::string& service_id,
api::ble_v2::TxPowerLevel tx_power_level,
const BleV2Peripheral& peripheral,
CancellationFlag* cancellation_flag);
// Returns a new BleL2capSocket.
// On Success, BleL2capSocket::IsValid() returns true.
BleL2capSocket ConnectOverL2cap(const std::string& service_id,
api::ble_v2::TxPowerLevel tx_power_level,
const BleV2Peripheral& peripheral,
CancellationFlag* cancellation_flag);
bool IsExtendedAdvertisementsAvailable();
bool IsValid() const { return impl_ != nullptr; }
api::ble_v2::BleMedium* GetImpl() const { return impl_.get(); }
BluetoothAdapter& GetAdapter() { return adapter_; }
void AddAlternateUuidForService(uint16_t uuid,
const std::string& service_id) {
impl_->AddAlternateUuidForService(uuid, service_id);
}
private:
Mutex mutex_;
std::unique_ptr<api::ble_v2::BleMedium> impl_;
BluetoothAdapter& adapter_;
ServerGattConnectionCallback server_gatt_connection_callback_
ABSL_GUARDED_BY(mutex_);
ScanCallback scan_callback_ ABSL_GUARDED_BY(mutex_);
bool scanning_enabled_ ABSL_GUARDED_BY(mutex_) = false;
};
} // namespace nearby
#endif // PLATFORM_PUBLIC_BLE_V2_H_
-856
View File
@@ -1,856 +0,0 @@
// 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.
#include "internal/platform/ble_v2.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/feature_flags.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/mac_address.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/single_thread_executor.h"
#include "internal/platform/uuid.h"
namespace nearby {
namespace {
using FeatureFlags = FeatureFlags::Flags;
constexpr FeatureFlags kTestCases[] = {
FeatureFlags{
.enable_cancellation_flag = true,
},
FeatureFlags{
.enable_cancellation_flag = false,
},
};
using ::nearby::api::ble_v2::BleAdvertisementData;
using ::nearby::api::ble_v2::GattCharacteristic;
using ::nearby::api::ble_v2::TxPowerLevel;
using ::testing::Optional;
using ::testing::status::StatusIs;
constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000);
constexpr absl::string_view kAdvertisementString = "\x0a\x0b\x0c\x0d";
constexpr absl::string_view kAdvertisementHeaderString = "\x0x\x0y\x0z";
constexpr TxPowerLevel kTxPowerLevel(TxPowerLevel::kHigh);
constexpr absl::string_view kServiceIDA{
"com.google.location.nearby.apps.test.a"};
class BleV2MediumTest : public ::testing::TestWithParam<FeatureFlags> {
protected:
MediumEnvironment& env_{MediumEnvironment::Instance()};
};
TEST_P(BleV2MediumTest, CanConnectToService) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleV2Medium ble_a(adapter_a_);
BleV2Medium ble_b(adapter_b_);
Uuid service_uuid(1234, 5678);
std::string service_id(kServiceIDA);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
BleV2ServerSocket server_socket = ble_b.OpenServerSocket(service_id);
EXPECT_TRUE(server_socket.IsValid());
// Assemble regular advertisement data
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
(ble_b.StartAdvertising(advertising_data, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
BleV2Peripheral discovered_peripheral;
ble_a.StartScanning(
service_uuid, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch, &discovered_peripheral](
BleV2Peripheral peripheral,
const BleAdvertisementData& advertisement_data) {
discovered_peripheral = std::move(peripheral);
found_latch.CountDown();
},
});
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
BleV2Socket socket_a;
BleV2Socket socket_b;
EXPECT_FALSE(socket_a.IsValid());
EXPECT_FALSE(socket_b.IsValid());
{
CancellationFlag flag;
SingleThreadExecutor server_executor;
SingleThreadExecutor client_executor;
client_executor.Execute(
[&ble_a, &socket_a, &service_id,
discovered_peripheral = std::move(discovered_peripheral),
&server_socket, &flag]() {
socket_a = ble_a.Connect(service_id, kTxPowerLevel,
discovered_peripheral, &flag);
if (!socket_a.IsValid()) {
server_socket.Close();
}
});
server_executor.Execute([&socket_b, &server_socket]() {
socket_b = server_socket.Accept();
if (!socket_b.IsValid()) {
server_socket.Close();
}
});
}
EXPECT_TRUE(socket_a.IsValid());
EXPECT_TRUE(socket_b.IsValid());
server_socket.Close();
env_.Stop();
}
TEST_P(BleV2MediumTest, CanConnectToServiceWithMultipleServices) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleV2Medium ble_a(adapter_a_);
BleV2Medium ble_b(adapter_b_);
Uuid service_uuid(1234, 5678);
std::string service_id(kServiceIDA);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
BleV2ServerSocket server_socket = ble_b.OpenServerSocket(service_id);
EXPECT_TRUE(server_socket.IsValid());
// Assemble regular advertisement data
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
(ble_b.StartAdvertising(advertising_data, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
BleV2Peripheral discovered_peripheral;
ble_a.StartMultipleServicesScanning(
std::vector<Uuid>{service_uuid}, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch, &discovered_peripheral](
BleV2Peripheral peripheral,
const BleAdvertisementData& advertisement_data) {
discovered_peripheral = std::move(peripheral);
found_latch.CountDown();
},
});
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
BleV2Socket socket_a;
BleV2Socket socket_b;
EXPECT_FALSE(socket_a.IsValid());
EXPECT_FALSE(socket_b.IsValid());
{
CancellationFlag flag;
SingleThreadExecutor server_executor;
SingleThreadExecutor client_executor;
client_executor.Execute(
[&ble_a, &socket_a, &service_id,
discovered_peripheral = std::move(discovered_peripheral),
&server_socket, &flag]() {
socket_a = ble_a.Connect(service_id, kTxPowerLevel,
discovered_peripheral, &flag);
if (!socket_a.IsValid()) {
server_socket.Close();
}
});
server_executor.Execute([&socket_b, &server_socket]() {
socket_b = server_socket.Accept();
if (!socket_b.IsValid()) {
server_socket.Close();
}
});
}
EXPECT_TRUE(socket_a.IsValid());
EXPECT_TRUE(socket_b.IsValid());
server_socket.Close();
env_.Stop();
}
TEST_P(BleV2MediumTest, CanDiscoverMultipleServices) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BluetoothAdapter adapter_c_;
BleV2Medium ble_a(adapter_a_);
BleV2Medium ble_b(adapter_b_);
BleV2Medium ble_c(adapter_c_);
Uuid service_uuid_a(1234, 5678);
Uuid service_uuid_b(1234, 5679);
std::string service_id(kServiceIDA);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
CountDownLatch found_latch(1);
// Start advertising on adapter a
BleAdvertisementData advertising_data_a;
advertising_data_a.is_extended_advertisement = false;
advertising_data_a.service_data = {{service_uuid_a, advertisement_bytes}};
(ble_a.StartAdvertising(advertising_data_a, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
// Start advertising on adapter b
BleAdvertisementData advertising_data_b;
advertising_data_b.is_extended_advertisement = false;
advertising_data_b.service_data = {{service_uuid_b, advertisement_bytes}};
(ble_b.StartAdvertising(advertising_data_b, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
// Discover both services on adapter c
bool found_service_a = false;
bool found_service_b = false;
ble_c.StartMultipleServicesScanning(
std::vector<Uuid>{service_uuid_a, service_uuid_b}, kTxPowerLevel,
{.advertisement_found_cb =
[&found_latch, &found_service_a, &found_service_b, &service_uuid_a,
&service_uuid_b](BleV2Peripheral peripheral,
const BleAdvertisementData& advertisement_data) {
if (advertisement_data.service_data.contains(service_uuid_a)) {
found_service_a = true;
}
if (advertisement_data.service_data.contains(service_uuid_b)) {
found_service_b = true;
}
if (found_service_a && found_service_b) {
found_latch.CountDown();
}
}});
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
env_.Stop();
}
TEST_P(BleV2MediumTest, CanCancelConnect) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
BluetoothAdapter adapter_a_;
BluetoothAdapter adapter_b_;
BleV2Medium ble_a(adapter_a_);
BleV2Medium ble_b(adapter_b_);
Uuid service_uuid(1234, 5678);
std::string service_id(kServiceIDA);
ByteArray advertisement_bytes((std::string(kAdvertisementString)));
CountDownLatch found_latch(1);
CountDownLatch lost_latch(1);
BleV2ServerSocket server_socket = ble_b.OpenServerSocket(service_id);
EXPECT_TRUE(server_socket.IsValid());
// Assemble regular advertisement data.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
(ble_b.StartAdvertising(advertising_data, {.tx_power_level = kTxPowerLevel,
.is_connectable = true}));
BleV2Peripheral discovered_peripheral;
ble_a.StartScanning(
service_uuid, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch, &discovered_peripheral](
BleV2Peripheral peripheral,
const BleAdvertisementData& advertisement_data) {
discovered_peripheral = std::move(peripheral);
found_latch.CountDown();
},
});
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
BleV2Socket socket_a;
BleV2Socket socket_b;
EXPECT_FALSE(socket_a.IsValid());
EXPECT_FALSE(socket_b.IsValid());
{
CancellationFlag flag(true);
SingleThreadExecutor server_executor;
SingleThreadExecutor client_executor;
client_executor.Execute(
[&ble_a, &socket_a, &service_id,
discovered_peripheral = std::move(discovered_peripheral),
&server_socket, &flag]() {
socket_a = ble_a.Connect(service_id, kTxPowerLevel,
discovered_peripheral, &flag);
if (!socket_a.IsValid()) {
server_socket.Close();
}
});
server_executor.Execute([&socket_b, &server_socket]() {
socket_b = server_socket.Accept();
if (!socket_b.IsValid()) {
server_socket.Close();
}
});
}
// If FeatureFlag is disabled, Cancelled is false as no-op.
if (!feature_flags.enable_cancellation_flag) {
EXPECT_TRUE(socket_a.IsValid());
EXPECT_TRUE(socket_b.IsValid());
} else {
EXPECT_FALSE(socket_a.IsValid());
EXPECT_FALSE(socket_b.IsValid());
}
server_socket.Close();
env_.Stop();
}
INSTANTIATE_TEST_SUITE_P(ParametrisedBleMediumTest, BleV2MediumTest,
::testing::ValuesIn(kTestCases));
TEST_F(BleV2MediumTest, ConstructorDestructorWorks) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
// Make sure we can create functional mediums.
ASSERT_TRUE(ble_a.IsValid());
ASSERT_TRUE(ble_b.IsValid());
// Make sure we can create 2 distinct mediums.
EXPECT_NE(ble_a.GetImpl(), ble_b.GetImpl());
env_.Stop();
}
TEST_F(BleV2MediumTest, CanStartFastScanningAndFastAdvertising) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
ByteArray advertisement_bytes((std::string(kAdvertisementString)));
CountDownLatch found_latch(1);
EXPECT_TRUE(ble_a.StartScanning(
service_uuid, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch](BleV2Peripheral peripheral,
const BleAdvertisementData& advertisement_data) {
found_latch.CountDown();
},
}));
// Fail to start extended advertisement due to g3 Ble medium does not support.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = true;
advertising_data.service_data.insert({service_uuid, advertisement_bytes});
EXPECT_FALSE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
// Succeed to start regular advertisement.
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_bytes}};
EXPECT_TRUE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
EXPECT_TRUE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_TRUE(
env_.GetBleV2MediumStatus(*ble_b.GetImpl()).value().is_advertising);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_TRUE(ble_a.StopScanning());
EXPECT_TRUE(ble_b.StopAdvertising());
EXPECT_FALSE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_FALSE(
env_.GetBleV2MediumStatus(*ble_b.GetImpl()).value().is_advertising);
env_.Stop();
}
TEST_F(BleV2MediumTest, CanStartScanningAndAdvertising) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
ByteArray advertisement_header_bytes{std::string(kAdvertisementHeaderString)};
CountDownLatch found_latch(1);
EXPECT_TRUE(ble_a.StartScanning(
service_uuid, kTxPowerLevel,
{
.advertisement_found_cb =
[&found_latch](BleV2Peripheral peripheral,
const BleAdvertisementData& advertisement_data) {
found_latch.CountDown();
},
}));
// Fail to start extended advertisement due to g3 Ble medium does not support.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = true;
advertising_data.service_data.insert({service_uuid, advertisement_bytes});
EXPECT_FALSE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
// Succeed to start regular advertisement.
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_header_bytes}};
EXPECT_TRUE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
EXPECT_TRUE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_TRUE(
env_.GetBleV2MediumStatus(*ble_b.GetImpl()).value().is_advertising);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_TRUE(ble_a.StopScanning());
EXPECT_TRUE(ble_b.StopAdvertising());
EXPECT_FALSE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_FALSE(
env_.GetBleV2MediumStatus(*ble_b.GetImpl()).value().is_advertising);
env_.UnregisterBleV2Medium(*ble_a.GetImpl());
env_.UnregisterBleV2Medium(*ble_b.GetImpl());
EXPECT_EQ(env_.GetBleV2MediumStatus(*ble_a.GetImpl()), std::nullopt);
EXPECT_EQ(env_.GetBleV2MediumStatus(*ble_b.GetImpl()), std::nullopt);
env_.Stop();
}
TEST_F(BleV2MediumTest, StartThenStopAsyncScanning) {
env_.Start();
BluetoothAdapter adapter_a;
BleV2Medium ble_a(adapter_a);
Uuid service_uuid(1234, 5678);
CountDownLatch found_latch_a(1);
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession> scanning_session_a =
ble_a.StartScanning(
service_uuid, kTxPowerLevel,
api::ble_v2::BleMedium::ScanningCallback{
.advertisement_found_cb =
[&](api::ble_v2::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) -> void {
found_latch_a.CountDown();
},
});
EXPECT_TRUE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_OK(scanning_session_a->stop_scanning());
EXPECT_FALSE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
env_.Stop();
}
TEST_F(BleV2MediumTest, CanStartMultipleAsyncScanning) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
CountDownLatch found_latch_a(1);
CountDownLatch found_latch_b(1);
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession> scanning_session_a =
ble_a.StartScanning(
service_uuid, kTxPowerLevel,
api::ble_v2::BleMedium::ScanningCallback{
.advertisement_found_cb =
[&](api::ble_v2::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) -> void {
found_latch_a.CountDown();
},
});
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession> scanning_session_b =
ble_b.StartScanning(
service_uuid, kTxPowerLevel,
api::ble_v2::BleMedium::ScanningCallback{
.advertisement_found_cb =
[&](api::ble_v2::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) -> void {
found_latch_b.CountDown();
},
});
EXPECT_TRUE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_TRUE(env_.GetBleV2MediumStatus(*ble_b.GetImpl()).value().is_scanning);
EXPECT_OK(scanning_session_a->stop_scanning());
EXPECT_OK(scanning_session_b->stop_scanning());
EXPECT_FALSE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_FALSE(env_.GetBleV2MediumStatus(*ble_b.GetImpl()).value().is_scanning);
env_.Stop();
}
TEST_F(BleV2MediumTest, CanStartAsyncScanningAndAdvertising) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
ByteArray advertisement_header_bytes{std::string(kAdvertisementHeaderString)};
CountDownLatch found_latch(1);
std::unique_ptr<api::ble_v2::BleMedium::ScanningSession> scanning_session =
ble_a.StartScanning(
service_uuid, kTxPowerLevel,
api::ble_v2::BleMedium::ScanningCallback{
.advertisement_found_cb =
[&](api::ble_v2::BlePeripheral::UniqueId peripheral_id,
BleAdvertisementData advertisement_data) -> void {
found_latch.CountDown();
},
});
// Succeed to start regular advertisement.
BleAdvertisementData advertising_data;
advertising_data.is_extended_advertisement = false;
advertising_data.service_data = {{service_uuid, advertisement_header_bytes}};
EXPECT_TRUE(ble_b.StartAdvertising(
advertising_data,
{.tx_power_level = kTxPowerLevel, .is_connectable = true}));
EXPECT_TRUE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_TRUE(
env_.GetBleV2MediumStatus(*ble_b.GetImpl()).value().is_advertising);
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
EXPECT_OK(scanning_session->stop_scanning());
EXPECT_TRUE(ble_b.StopAdvertising());
EXPECT_FALSE(env_.GetBleV2MediumStatus(*ble_a.GetImpl()).value().is_scanning);
EXPECT_FALSE(
env_.GetBleV2MediumStatus(*ble_b.GetImpl()).value().is_advertising);
env_.UnregisterBleV2Medium(*ble_a.GetImpl());
env_.UnregisterBleV2Medium(*ble_b.GetImpl());
EXPECT_EQ(env_.GetBleV2MediumStatus(*ble_a.GetImpl()), std::nullopt);
EXPECT_EQ(env_.GetBleV2MediumStatus(*ble_b.GetImpl()), std::nullopt);
env_.Stop();
}
TEST_F(BleV2MediumTest, CanStartGattServer) {
env_.Start();
BluetoothAdapter adapter;
BleV2Medium ble(adapter);
Uuid service_uuid(1234, 5678);
Uuid characteristic_uuid(5678, 1234);
std::unique_ptr<GattServer> gatt_server =
ble.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
GattCharacteristic::Permission permission =
GattCharacteristic::Permission::kRead;
GattCharacteristic::Property property = GattCharacteristic::Property::kRead;
// NOLINTNEXTLINE(google3-legacy-absl-backports)
std::optional<GattCharacteristic> gatt_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid,
permission, property);
ASSERT_TRUE(gatt_characteristic.has_value());
ByteArray any_byte("any");
EXPECT_TRUE(
gatt_server->UpdateCharacteristic(gatt_characteristic.value(), any_byte));
gatt_server->Stop();
env_.Stop();
}
TEST_F(BleV2MediumTest, GattClientConnectToGattServerWorks) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
Uuid characteristic_uuid(5678, 1234);
// Start GattServer
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
GattCharacteristic::Permission permissions =
GattCharacteristic::Permission::kRead;
GattCharacteristic::Property properties = GattCharacteristic::Property::kRead;
// Add characteristic and its value.
// NOLINTNEXTLINE(google3-legacy-absl-backports)
std::optional<GattCharacteristic> server_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid,
permissions, properties);
ASSERT_TRUE(server_characteristic.has_value());
ByteArray server_value("any");
EXPECT_TRUE(
gatt_server->UpdateCharacteristic(*server_characteristic, server_value));
// Start GattClient
MacAddress mac_address = adapter_a.GetAddress();
EXPECT_TRUE(mac_address.IsSet());
std::unique_ptr<GattClient> gatt_client = ble_b.ConnectToGattServer(
BleV2Peripheral(ble_b, mac_address.address()), kTxPowerLevel,
/*ClientGattConnectionCallback=*/{});
ASSERT_NE(gatt_client, nullptr);
// Discover service and characteristics.
EXPECT_TRUE(gatt_client->DiscoverServiceAndCharacteristics(
service_uuid, {characteristic_uuid}));
// NOLINTNEXTLINE(google3-legacy-absl-backports)
std::optional<GattCharacteristic> client_characteristic =
gatt_client->GetCharacteristic(service_uuid, characteristic_uuid);
ASSERT_TRUE(client_characteristic.has_value());
// Can read the characteristic value.
EXPECT_THAT(gatt_client->ReadCharacteristic(*client_characteristic),
Optional(server_value.string_data()));
gatt_client->Disconnect();
gatt_server->Stop();
env_.Stop();
}
TEST_F(BleV2MediumTest, GattClientConnectToStoppedGattServerFails) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
MacAddress mac_address = adapter_a.GetAddress();
gatt_server->Stop();
std::unique_ptr<GattClient> gatt_client = ble_b.ConnectToGattServer(
BleV2Peripheral(ble_b, mac_address.address()), kTxPowerLevel,
/*ClientGattConnectionCallback=*/{});
ASSERT_NE(gatt_client, nullptr);
EXPECT_FALSE(gatt_client->IsValid());
env_.Stop();
}
TEST_F(BleV2MediumTest, GattClientNotifiedWhenServerDisconnects) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{});
ASSERT_NE(gatt_server, nullptr);
CountDownLatch disconnected_latch(1);
// Start GattClient
MacAddress mac_address = adapter_a.GetAddress();
std::unique_ptr<GattClient> gatt_client = ble_b.ConnectToGattServer(
BleV2Peripheral(ble_b, mac_address.address()), kTxPowerLevel,
/*ClientGattConnectionCallback=*/{.disconnected_cb = [&]() {
disconnected_latch.CountDown();
}});
ASSERT_NE(gatt_client, nullptr);
gatt_server->Stop();
disconnected_latch.Await();
env_.Stop();
}
TEST_F(BleV2MediumTest, GattClientOperatiosOnCharacteristic) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium ble_b(adapter_b);
Uuid service_uuid(1234, 5678);
Uuid characteristic_uuid(5678, 1234);
std::string written_data;
// Start GattServer.
std::unique_ptr<GattServer> gatt_server =
ble_a.StartGattServer(/*ServerGattConnectionCallback=*/{
.on_characteristic_write_cb =
[&](const api::ble_v2::BlePeripheral::UniqueId remote_device_id,
const api::ble_v2::GattCharacteristic& characteristic,
int offset, absl::string_view data,
api::ble_v2::ServerGattConnectionCallback::WriteValueCallback
callback) {
written_data = data;
callback(absl::OkStatus());
}});
ASSERT_NE(gatt_server, nullptr);
// Start GattClient.
MacAddress mac_address = adapter_a.GetAddress();
std::unique_ptr<GattClient> gatt_client = ble_b.ConnectToGattServer(
BleV2Peripheral(ble_b, mac_address.address()), kTxPowerLevel,
/*ClientGattConnectionCallback=*/{});
ASSERT_NE(gatt_client, nullptr);
// Can't not discover service and characteristic.
EXPECT_FALSE(gatt_client->DiscoverServiceAndCharacteristics(
service_uuid, {characteristic_uuid}));
// Add characteristic and its value.
GattCharacteristic::Permission permissions =
GattCharacteristic::Permission::kRead;
GattCharacteristic::Property properties = GattCharacteristic::Property::kRead;
std::optional<GattCharacteristic> server_characteristic =
gatt_server->CreateCharacteristic(service_uuid, characteristic_uuid,
permissions, properties);
ASSERT_TRUE(server_characteristic.has_value());
ByteArray server_value("any");
EXPECT_TRUE(
gatt_server->UpdateCharacteristic(*server_characteristic, server_value));
// Can discover service and characteristics.
EXPECT_TRUE(gatt_client->DiscoverServiceAndCharacteristics(
service_uuid, {characteristic_uuid}));
// Can get Characteristic.
std::optional<GattCharacteristic> client_characteristic =
gatt_client->GetCharacteristic(service_uuid, characteristic_uuid);
ASSERT_TRUE(client_characteristic.has_value());
// Can read the characteristic value.
EXPECT_THAT(gatt_client->ReadCharacteristic(*client_characteristic),
Optional(std::string("any")));
// Can write the characteristic value.
EXPECT_TRUE(gatt_client->WriteCharacteristic(
*client_characteristic, "hello",
api::ble_v2::GattClient::WriteType::kWithResponse));
EXPECT_EQ(written_data, "hello");
gatt_client->Disconnect();
// Failed to write/read characteristic value as gatt is disconnected.
EXPECT_FALSE(gatt_client->WriteCharacteristic(
*client_characteristic, "any",
api::ble_v2::GattClient::WriteType::kWithResponse));
EXPECT_THAT(gatt_client->ReadCharacteristic(*client_characteristic),
std::nullopt);
gatt_server->Stop();
env_.Stop();
}
TEST_F(BleV2MediumTest, GattClientSubscribeNotificationGattServerCanNotify) {
env_.Start();
BluetoothAdapter adapter_a;
BluetoothAdapter adapter_b;
BleV2Medium ble_a(adapter_a);
BleV2Medium 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(
BleV2Peripheral(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
+2 -26
View File
@@ -19,8 +19,6 @@
#include <string>
#include "absl/strings/string_view.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
#include "internal/platform/implementation/bluetooth_classic.h"
#include "internal/platform/implementation/platform.h"
@@ -28,28 +26,6 @@
namespace nearby {
// Opaque wrapper over a BLE peripheral. Must contain enough data about a
// particular BLE peripheral to connect to its GATT server.
class BlePeripheral final {
public:
BlePeripheral() = default;
BlePeripheral(const BlePeripheral&) = default;
BlePeripheral& operator=(const BlePeripheral&) = default;
explicit BlePeripheral(api::BlePeripheral* peripheral) : impl_(peripheral) {}
std::string GetName() const { return impl_->GetName(); }
ByteArray GetAdvertisementBytes(const std::string& service_id) const {
return impl_->GetAdvertisementBytes(service_id);
}
api::BlePeripheral& GetImpl() { return *impl_; }
bool IsValid() const { return impl_ != nullptr; }
private:
api::BlePeripheral* impl_;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html.
class BluetoothDevice final {
public:
@@ -61,7 +37,7 @@ class BluetoothDevice final {
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName()
std::string GetName() const { return impl_->GetName(); }
MacAddress GetAddress() const { return impl_->GetAddress(); }
MacAddress GetAddress() const { return impl_->GetMacAddress(); }
api::BluetoothDevice& GetImpl() { return *impl_; }
bool IsValid() const { return impl_ != nullptr; }
@@ -104,7 +80,7 @@ class BluetoothAdapter final {
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName()
// Returns an empty string on error
std::string GetName() const { return impl_->GetName(); }
MacAddress GetAddress() const { return impl_->GetAddress(); }
MacAddress GetAddress() const { return impl_->GetMacAddress(); }
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String)
bool SetName(absl::string_view name) {
+7 -7
View File
@@ -125,13 +125,13 @@ bool BluetoothClassicMedium::StartDiscovery(DiscoveryCallback callback) {
.device_lost_cb =
[this](api::BluetoothDevice& device) {
VLOG(1) << "BT .device_lost_cb for "
<< device.GetAddress().ToString();
<< device.GetMacAddress().ToString();
MutexLock lock(&mutex_);
if (!discovery_enabled_) return;
auto item = devices_.extract(&device);
if (!item) {
LOG(WARNING) << "Removing unknown device: "
<< device.GetAddress().ToString();
<< device.GetMacAddress().ToString();
return;
}
auto& context = *item.mapped();
@@ -182,7 +182,7 @@ void BluetoothClassicMedium::RemoveObserver(Observer* observer) {
// api::BluetoothClassicMedium::Observer methods
void BluetoothClassicMedium::DeviceAdded(api::BluetoothDevice& device) {
VLOG(1) << "BT DeviceAdded; name=" << device.GetName()
<< ", address=" << device.GetAddress().ToString();
<< ", address=" << device.GetMacAddress().ToString();
BluetoothDevice bt_device(&device);
for (auto* observer : observer_list_.GetObservers()) {
observer->DeviceAdded(bt_device);
@@ -190,7 +190,7 @@ void BluetoothClassicMedium::DeviceAdded(api::BluetoothDevice& device) {
}
void BluetoothClassicMedium::DeviceRemoved(api::BluetoothDevice& device) {
VLOG(1) << "BT DeviceRemoved; name=" << device.GetName()
<< ", address=" << device.GetAddress().ToString();
<< ", address=" << device.GetMacAddress().ToString();
BluetoothDevice bt_device(&device);
for (auto* observer : observer_list_.GetObservers()) {
observer->DeviceRemoved(bt_device);
@@ -199,7 +199,7 @@ void BluetoothClassicMedium::DeviceRemoved(api::BluetoothDevice& device) {
void BluetoothClassicMedium::DeviceAddressChanged(
api::BluetoothDevice& device, absl::string_view old_address) {
VLOG(1) << "BT DeviceAddressChanged; name=" << device.GetName()
<< ", address=" << device.GetAddress().ToString()
<< ", address=" << device.GetMacAddress().ToString()
<< ", old_address=" << old_address;
BluetoothDevice bt_device(&device);
for (auto* observer : observer_list_.GetObservers()) {
@@ -209,7 +209,7 @@ void BluetoothClassicMedium::DeviceAddressChanged(
void BluetoothClassicMedium::DevicePairedChanged(api::BluetoothDevice& device,
bool new_paired_status) {
VLOG(1) << "BT DevicePairedChanged; name=" << device.GetName()
<< ", address=" << device.GetAddress().ToString()
<< ", address=" << device.GetMacAddress().ToString()
<< ", status=" << new_paired_status;
BluetoothDevice bt_device(&device);
for (auto* observer : observer_list_.GetObservers()) {
@@ -219,7 +219,7 @@ void BluetoothClassicMedium::DevicePairedChanged(api::BluetoothDevice& device,
void BluetoothClassicMedium::DeviceConnectedStateChanged(
api::BluetoothDevice& device, bool connected) {
VLOG(1) << "BT DeviceConnectedStateChanged: name=" << device.GetName()
<< ", address=" << device.GetAddress().ToString()
<< ", address=" << device.GetMacAddress().ToString()
<< ", connected=" << connected;
BluetoothDevice bt_device(&device);
for (auto* observer : observer_list_.GetObservers()) {
+3 -3
View File
@@ -250,7 +250,7 @@ TEST_F(BluetoothClassicMediumTest, SendData) {
bt_b_->ListenForService(service_name, service_uuid);
ASSERT_TRUE(server_socket.IsValid());
{
ByteArray data("data");
absl::string_view data("data");
CancellationFlag flag;
SingleThreadExecutor server_executor;
SingleThreadExecutor client_executor;
@@ -268,7 +268,7 @@ TEST_F(BluetoothClassicMediumTest, SendData) {
socket_b.GetInputStream().Read(data.size());
ASSERT_EQ(result.exception(), Exception::kSuccess);
ASSERT_TRUE(result.ok());
EXPECT_EQ(result.GetResult(), data);
EXPECT_EQ(result.GetResult().AsStringView(), data);
});
}
server_socket.Close();
@@ -297,7 +297,7 @@ TEST_F(BluetoothClassicMediumTest, IoOnClosedSocketReturnsEmpty) {
bt_b_->ListenForService(service_name, service_uuid);
ASSERT_TRUE(server_socket.IsValid());
{
ByteArray data("data");
absl::string_view data("data");
CancellationFlag flag;
SingleThreadExecutor server_executor;
SingleThreadExecutor client_executor;
+4 -3
View File
@@ -18,12 +18,9 @@
#include <algorithm>
#include <array>
#include <cstring>
#include <cstdint>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
namespace nearby {
@@ -33,6 +30,10 @@ class ByteArray {
using iterator = std::string::iterator;
using const_iterator = std::string::const_iterator;
static ByteArray FromStringView(absl::string_view source) {
return ByteArray(source.data(), source.size());
}
// Create an empty ByteArray
ByteArray() = default;
template <size_t N>
+33 -1
View File
@@ -14,6 +14,7 @@
#include "internal/platform/byte_utils.h"
#include <cstdint>
#include <cstdlib>
#include <string>
@@ -22,8 +23,18 @@
#include "internal/platform/stream_reader.h"
namespace nearby {
namespace byte_utils {
std::string ByteUtils::ToFourDigitString(ByteArray& bytes) {
namespace {
// The biggest prime number under 10000, used as a mod base to trim integers
// into 4 digits.
constexpr int kHashBasePrime = 9973;
// The hash multiplier.
constexpr int kHashBaseMultiplier = 31;
} // namespace
std::string ToFourDigitString(const ByteArray& bytes) {
int multiplier = 1;
int hashCode = 0;
@@ -36,4 +47,25 @@ std::string ByteUtils::ToFourDigitString(ByteArray& bytes) {
return absl::StrFormat("%04d", abs(hashCode));
}
int32_t BytesToInt(const ByteArray& bytes) {
const char* int_bytes = bytes.data();
int32_t result = 0;
for (int i = 0; i < bytes.size() && i < 4; ++i) {
result <<= 8;
result |= static_cast<int32_t>(int_bytes[i]) & 0x0FF;
}
return result;
}
ByteArray IntToBytes(int32_t value) {
ByteArray result(sizeof(int32_t));
char* buffer = result.data();
buffer[0] = static_cast<char>((value >> 24) & 0x0FF);
buffer[1] = static_cast<char>((value >> 16) & 0x0FF);
buffer[2] = static_cast<char>((value >> 8) & 0x0FF);
buffer[3] = static_cast<char>(value & 0x0FF);
return result;
}
} // namespace byte_utils
} // namespace nearby
+17 -12
View File
@@ -15,23 +15,28 @@
#ifndef PLATFORM_BASE_BYTE_UTILS_H_
#define PLATFORM_BASE_BYTE_UTILS_H_
#include <cstdint>
#include <string>
#include "internal/platform/byte_array.h"
namespace nearby {
// TODO(edwinwu): Remove this namespace and move all the functions into
// ByteArray class.
namespace nearby::byte_utils {
class ByteUtils {
public:
static std::string ToFourDigitString(ByteArray& bytes);
// Generates a four-digit numeric string representation of a byte array.
std::string ToFourDigitString(const ByteArray& bytes);
private:
// The biggest prime number under 10000, used as a mod base to trim integers
// into 4 digits.
static constexpr int kHashBasePrime = 9973;
// The hash multiplier.
static constexpr int kHashBaseMultiplier = 31;
};
// Converts a ByteArray to a 32-bit integer in big-endian format.
// It reads min(4, bytes.size()) bytes from input ByteArray; bytes[0] is
// read as MSB, bytes[1] as second byte, and so on. If bytes.size() < 4,
// bytes will be read to high order bytes of result and low order bytes
// will be 0.
int32_t BytesToInt(const ByteArray& bytes);
} // namespace nearby
// Converts a 32-bit integer to a 4-byte ByteArray in big-endian format.
ByteArray IntToBytes(int32_t value);
} // namespace nearby::byte_utils
#endif // PLATFORM_BASE_BYTE_UTILS_H_
+62 -7
View File
@@ -14,7 +14,9 @@
#include "internal/platform/byte_utils.h"
#include <cstdint>
#include <string>
#include <limits>
#include "gtest/gtest.h"
#include "absl/strings/string_view.h"
@@ -26,12 +28,12 @@ constexpr absl::string_view kFooBytes{"rawABCDE"};
constexpr absl::string_view kFooFourDigitsToken{"0392"};
constexpr absl::string_view kEmptyFourDigitsToken{"0000"};
constexpr absl::string_view kNegativeBytes{"raw\xd5\x01\xe4\x03\x81"};
constexpr absl::string_view kNegativeFourDigitsToken{"9084"};
constexpr absl::string_view kNegativeFourDigitsToken{"6251"};
TEST(ByteUtilsTest, ToFourDigitStringCorrect) {
ByteArray bytes{std::string(kFooBytes)};
auto four_digit_string = ByteUtils::ToFourDigitString(bytes);
auto four_digit_string = byte_utils::ToFourDigitString(bytes);
EXPECT_EQ(std::string(kFooFourDigitsToken), four_digit_string);
}
@@ -39,17 +41,70 @@ TEST(ByteUtilsTest, ToFourDigitStringCorrect) {
TEST(ByteUtilsTest, ToFourDigitStringNegativeCorrect) {
ByteArray bytes{std::string(kNegativeBytes)};
auto four_digit_string = ByteUtils::ToFourDigitString(bytes);
auto four_digit_string = byte_utils::ToFourDigitString(bytes);
EXPECT_EQ(std::string(kNegativeFourDigitsToken), kNegativeFourDigitsToken);
EXPECT_EQ(std::string(kNegativeFourDigitsToken), four_digit_string);
}
TEST(ByteUtilsTest, TestEmptyByteArrayCorrect) {
ByteArray bytes;
TEST(ByteUtilsTest, ToFourDigitStringHandlesEmptyInput) {
ByteArray bytes{};
auto four_digit_string = ByteUtils::ToFourDigitString(bytes);
auto four_digit_string = byte_utils::ToFourDigitString(bytes);
EXPECT_EQ(std::string(kEmptyFourDigitsToken), four_digit_string);
}
TEST(ByteUtilsTest, IntToBytesThenBytesToIntIsSymmetric) {
constexpr std::int32_t kTestValue = 123456789;
ByteArray bytes = byte_utils::IntToBytes(kTestValue);
EXPECT_EQ(kTestValue, byte_utils::BytesToInt(bytes));
}
TEST(ByteUtilsTest, IntToBytesThenBytesToIntIsSymmetricNegative) {
constexpr std::int32_t kTestValue = -123456789;
ByteArray bytes = byte_utils::IntToBytes(kTestValue);
EXPECT_EQ(kTestValue, byte_utils::BytesToInt(bytes));
}
TEST(ByteUtilsTest, BytesToIntHandlesZero) {
ByteArray bytes({'\0', '\0', '\0', '\0'});
EXPECT_EQ(0, byte_utils::BytesToInt(bytes));
}
TEST(ByteUtilsTest, BytesToIntHandlesNegative) {
ByteArray bytes({'\xff', '\xff', '\xff', '\xff'});
EXPECT_EQ(-1, byte_utils::BytesToInt(bytes));
}
TEST(ByteUtilsTest, BytesToIntHandlesNegativeLarge) {
ByteArray bytes({'\x80', '\x00', '\x00', '\x00'});
EXPECT_EQ(std::numeric_limits<std::int32_t>::min(),
byte_utils::BytesToInt(bytes));
}
TEST(ByteUtilsTest, IntToBytesHandlesZero) {
ByteArray expected_bytes({'\0', '\0', '\0', '\0'});
EXPECT_EQ(expected_bytes, byte_utils::IntToBytes(0));
}
TEST(ByteUtilsTest, HandlesInt32Max) {
constexpr std::int32_t kMaxValue = std::numeric_limits<std::int32_t>::max();
ByteArray bytes = byte_utils::IntToBytes(kMaxValue);
EXPECT_EQ(kMaxValue, byte_utils::BytesToInt(bytes));
}
TEST(ByteUtilsTest, HandlesInt32Min) {
constexpr std::int32_t kMinValue = std::numeric_limits<std::int32_t>::min();
ByteArray bytes = byte_utils::IntToBytes(kMinValue);
EXPECT_EQ(kMinValue, byte_utils::BytesToInt(bytes));
}
} // namespace nearby
+12 -4
View File
@@ -17,10 +17,10 @@
#include <utility>
#include "internal/platform/feature_flags.h"
#include "internal/platform/runnable.h"
#include "internal/platform/atomic_boolean.h"
#include "internal/platform/feature_flags.h"
#include "internal/platform/future.h"
#include "internal/platform/runnable.h"
namespace nearby {
@@ -31,7 +31,10 @@ namespace nearby {
class CancellableTask {
public:
explicit CancellableTask(Runnable&& runnable)
: runnable_{std::move(runnable)} {}
: CancellableTask(std::move(runnable), /*is_repeated=*/false) {}
explicit CancellableTask(Runnable&& runnable, bool is_repeated_)
: is_repeated_{is_repeated_}, runnable_{std::move(runnable)} {}
/**
* Try to cancel the task and wait until completion if the task is already
@@ -53,12 +56,17 @@ class CancellableTask {
void operator()() {
if (started_or_cancelled_.Set(true)) return;
finished_ = Future<bool>();
runnable_();
finished_.Set(true);
if (is_repeated_) {
started_or_cancelled_.Set(false);
}
}
private:
AtomicBoolean started_or_cancelled_;
const bool is_repeated_;
AtomicBoolean started_or_cancelled_{false};
Future<bool> finished_;
Runnable runnable_;
};
+1
View File
@@ -32,6 +32,7 @@ struct Exception {
kTimeout = 5, // Operation did not finish within specified time.
kIllegalCharacters = 6, // File name or parent path contained
// illegal chars
kNoData = 7, // No data available.
};
bool Ok() const { return value == kSuccess; }
explicit operator bool() const { return Ok(); }
+1
View File
@@ -118,6 +118,7 @@ class FeatureFlags {
std::uint32_t connection_max_frame_length = 1048576;
std::uint32_t blocking_queue_stream_queue_capacity = 10;
bool support_web_rtc_non_cellular_medium = false;
std::uint32_t wifi_direct_default_port = 63034;
};
static const FeatureFlags& GetInstance() {
+7 -6
View File
@@ -16,14 +16,15 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "internal/platform/payload_id.h"
namespace nearby {
InputFile::InputFile(PayloadId id, std::int64_t size)
: impl_(Platform::CreateInputFile(id, size)) {}
InputFile::InputFile(std::string file_path, std::int64_t size)
: impl_(Platform::CreateInputFile(file_path, size)) {}
InputFile::InputFile(PayloadId id) : impl_(Platform::CreateInputFile(id)) {}
InputFile::InputFile(std::string file_path)
: impl_(Platform::CreateInputFile(file_path)) {}
InputFile::~InputFile() = default;
InputFile::InputFile(InputFile&& other) noexcept = default;
InputFile& InputFile::operator=(InputFile&& other) = default;
@@ -70,9 +71,9 @@ OutputFile::OutputFile(OutputFile&&) noexcept = default;
OutputFile& OutputFile::operator=(OutputFile&&) = default;
bool OutputFile::IsValid() const { return impl_ != nullptr; }
// Writes all data from ByteArray object to the underlying stream.
// Writes all data from absl::string_view to the underlying stream.
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception OutputFile::Write(const ByteArray& data) {
Exception OutputFile::Write(absl::string_view data) {
return impl_->Write(data);
}
+5 -4
View File
@@ -20,6 +20,7 @@
#include <memory>
#include <string>
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
@@ -35,8 +36,8 @@ namespace nearby {
class InputFile final {
public:
using Platform = api::ImplementationPlatform;
InputFile(PayloadId payload_id, std::int64_t size);
InputFile(std::string file_path, std::int64_t size);
explicit InputFile(PayloadId payload_id);
explicit InputFile(std::string file_path);
~InputFile();
InputFile(InputFile&&) noexcept;
InputFile& operator=(InputFile&&);
@@ -84,9 +85,9 @@ class OutputFile final {
bool IsValid() const;
// Writes all data from ByteArray object to the underlying stream.
// Writes all data from string_view object to the underlying stream.
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Write(const ByteArray& data);
Exception Write(absl::string_view data);
// Disallows further writes to the file and frees system resources,
// associated with it.
+19 -19
View File
@@ -46,11 +46,11 @@ TEST_F(FileTest, ConstructorDestructorWorks) {
// Create an output file and write to it.
OutputFile output_file(file_path.ToString());
ASSERT_TRUE(output_file.IsValid());
output_file.Write(ByteArray(data));
output_file.Write(data);
output_file.Close();
// Create an input file and read from it.
InputFile input_file(file_path.ToString(), data.size());
InputFile input_file(file_path.ToString());
ExceptionOr<ByteArray> read_bytes = input_file.Read(data.size());
ASSERT_TRUE(read_bytes.ok());
EXPECT_EQ(read_bytes.result(), ByteArray(data));
@@ -64,11 +64,11 @@ TEST_F(FileTest, SimpleWriteRead) {
// Write to file.
OutputFile output_file(file_path.ToString());
ASSERT_TRUE(output_file.IsValid());
EXPECT_TRUE(output_file.Write(ByteArray(data)).Ok());
EXPECT_TRUE(output_file.Write(data).Ok());
EXPECT_TRUE(output_file.Close().Ok());
// Read from file.
InputFile input_file(file_path.ToString(), data.size());
InputFile input_file(file_path.ToString());
ExceptionOr<ByteArray> read_data = input_file.Read(data.size());
EXPECT_TRUE(read_data.ok());
EXPECT_EQ(std::string(read_data.result()), data);
@@ -82,11 +82,11 @@ TEST_F(FileTest, WriteThenCloseThenRead) {
// Write and close.
OutputFile output_file(file_path.ToString());
ASSERT_TRUE(output_file.IsValid());
EXPECT_TRUE(output_file.Write(ByteArray(data)).Ok());
EXPECT_TRUE(output_file.Write(data).Ok());
EXPECT_TRUE(output_file.Close().Ok());
// Re-open and read.
InputFile input_file(file_path.ToString(), data.size());
InputFile input_file(file_path.ToString());
ExceptionOr<ByteArray> read_data = input_file.Read(data.size());
EXPECT_TRUE(read_data.ok());
EXPECT_EQ(std::string(read_data.result()), data);
@@ -102,7 +102,7 @@ TEST_F(FileTest, ReadEmptyFile) {
EXPECT_TRUE(output_file.Close().Ok());
// Read from empty file.
InputFile input_file(file_path.ToString(), 0);
InputFile input_file(file_path.ToString());
ExceptionOr<ByteArray> read_data = input_file.Read(1024);
EXPECT_TRUE(read_data.ok());
EXPECT_TRUE(read_data.result().Empty());
@@ -115,10 +115,10 @@ TEST_F(FileTest, ReadExactly) {
OutputFile output_file(file_path.ToString());
ASSERT_TRUE(output_file.IsValid());
EXPECT_TRUE(output_file.Write(ByteArray(data)).Ok());
EXPECT_TRUE(output_file.Write(data).Ok());
EXPECT_TRUE(output_file.Close().Ok());
InputFile input_file(file_path.ToString(), data.size());
InputFile input_file(file_path.ToString());
ExceptionOr<ByteArray> read_data =
input_file.GetInputStream().ReadExactly(data.size());
EXPECT_TRUE(read_data.ok());
@@ -132,10 +132,10 @@ TEST_F(FileTest, ReadTooMuch) {
OutputFile output_file(file_path.ToString());
ASSERT_TRUE(output_file.IsValid());
EXPECT_TRUE(output_file.Write(ByteArray(data)).Ok());
EXPECT_TRUE(output_file.Write(data).Ok());
EXPECT_TRUE(output_file.Close().Ok());
InputFile input_file(file_path.ToString(), data.size());
InputFile input_file(file_path.ToString());
ExceptionOr<ByteArray> read_data = input_file.Read(data.size() * 2);
EXPECT_TRUE(read_data.ok());
EXPECT_EQ(std::string(read_data.result()), data);
@@ -150,10 +150,10 @@ TEST_F(FileTest, Skip) {
OutputFile output_file(file_path.ToString());
ASSERT_TRUE(output_file.IsValid());
EXPECT_TRUE(output_file.Write(ByteArray(full_data)).Ok());
EXPECT_TRUE(output_file.Write(full_data).Ok());
EXPECT_TRUE(output_file.Close().Ok());
InputFile input_file(file_path.ToString(), full_data.size());
InputFile input_file(file_path.ToString());
ExceptionOr<size_t> skipped_bytes = input_file.Skip(data_to_skip.size());
EXPECT_TRUE(skipped_bytes.ok());
EXPECT_EQ(skipped_bytes.result(), data_to_skip.size());
@@ -172,11 +172,11 @@ TEST_F(FileTest, MultipleWrites) {
OutputFile output_file(file_path.ToString());
ASSERT_TRUE(output_file.IsValid());
EXPECT_TRUE(output_file.Write(ByteArray(data1)).Ok());
EXPECT_TRUE(output_file.Write(ByteArray(data2)).Ok());
EXPECT_TRUE(output_file.Write(data1).Ok());
EXPECT_TRUE(output_file.Write(data2).Ok());
EXPECT_TRUE(output_file.Close().Ok());
InputFile input_file(file_path.ToString(), full_data.size());
InputFile input_file(file_path.ToString());
ExceptionOr<ByteArray> read_data = input_file.Read(full_data.size());
EXPECT_TRUE(read_data.ok());
EXPECT_EQ(std::string(read_data.result()), full_data);
@@ -190,7 +190,7 @@ TEST_F(FileTest, CloseTwice) {
EXPECT_TRUE(output_file.Close().Ok());
EXPECT_TRUE(output_file.Close().Ok());
InputFile input_file(file_path.ToString(), 0);
InputFile input_file(file_path.ToString());
EXPECT_TRUE(input_file.Close().Ok());
EXPECT_TRUE(input_file.Close().Ok());
}
@@ -205,10 +205,10 @@ TEST_F(FileTest, WriteLargeFile) {
OutputFile output_file(file_path.ToString());
ASSERT_TRUE(output_file.IsValid());
EXPECT_TRUE(output_file.Write(ByteArray(large_data)).Ok());
EXPECT_TRUE(output_file.Write(large_data).Ok());
EXPECT_TRUE(output_file.Close().Ok());
InputFile input_file(file_path.ToString(), large_data.size());
InputFile input_file(file_path.ToString());
ExceptionOr<ByteArray> read_data =
input_file.GetInputStream().ReadExactly(large_data.size());
EXPECT_TRUE(read_data.ok());
+1 -1
View File
@@ -21,10 +21,10 @@ cc_library(
"nearby_platform_feature_flags.h",
],
visibility = [
"//connections:__subpackages__",
"//connections:partners",
"//internal:__subpackages__",
"//location/nearby/cpp:__subpackages__",
"//location/nearby/sharing:__subpackages__",
"//location/nearby/testing:__subpackages__",
],
deps = [
@@ -53,6 +53,10 @@ constexpr auto kWifiHotspotConnectionIntervalMillis =
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);
// Enable/Disable Intel PIe SDK to query/set WIFI feature.
constexpr auto kEnableIntelPieSdk =
flags::Flag<bool>(kConfigPackage, "45428547", false);
@@ -61,9 +65,9 @@ constexpr auto kEnableIntelPieSdk =
constexpr auto kEnableNewBluetoothRefactor =
flags::Flag<bool>(kConfigPackage, "45615156", false);
// Enable/Disable Wi-Fi hotspot scan in native
constexpr auto kEnableWifiHotspotNativeScan =
flags::Flag<bool>(kConfigPackage, "45670001", false);
// Enable/Disable use of address candidates for WifiLan upgrade in Windows.
constexpr auto kEnableWifiLanAddressCandidates =
flags::Flag<bool>(kConfigPackage, "45739995", false);
// The send buffer size of blocking socket
constexpr auto kSocketSendBufferSize =
@@ -73,9 +77,6 @@ constexpr auto kSocketSendBufferSize =
constexpr auto kRunScheduledExecutorCallbackOnExecutorThread =
flags::Flag<bool>(kConfigPackage, "45686494", false);
constexpr auto kEnableIpAddressesNative =
flags::Flag<bool>(kConfigPackage, "45722101", false);
} // namespace nearby_platform_feature
} // namespace config_package_nearby
} // namespace platform
+71 -28
View File
@@ -18,49 +18,92 @@
#include <memory>
#include <utility>
#include "absl/base/thread_annotations.h"
#include "absl/time/time.h"
#include "internal/platform/condition_variable.h"
#include "internal/platform/exception.h"
#include "internal/platform/implementation/executor.h"
#include "internal/platform/settable_future.h"
#include "internal/platform/implementation/system_clock.h"
#include "internal/platform/mutex.h"
#include "internal/platform/mutex_lock.h"
namespace nearby {
template <typename T>
class Future final {
public:
using FutureCallback = typename SettableFuture<T>::FutureCallback;
// Default Future. Does not time out.
Future() : impl_(std::make_shared<SettableFuture<T>>()) {}
// Sets the value of the Future.
bool Set(T value) {
MutexLock lock(&state_->mutex);
if (!state_->done) {
state_->value = ExceptionOr<T>(std::move(value));
state_->done = true;
state_->completed.Notify();
return true;
}
return false;
}
// Creates a Future with a timeout.
explicit Future(absl::Duration timeout)
: impl_(std::make_shared<SettableFuture<T>>(timeout)) {}
// Sets the exception of the Future.
bool SetException(Exception exception) {
MutexLock lock(&state_->mutex);
return SetExceptionLocked(exception);
}
virtual bool Set(T value) { return impl_->Set(std::move(value)); }
virtual bool SetException(Exception exception) {
return impl_->SetException(exception);
ExceptionOr<T> Get() {
MutexLock lock(&state_->mutex);
while (!state_->done) {
state_->completed.Wait();
}
return state_->value;
}
virtual ExceptionOr<T> Get() { return impl_->Get(); }
virtual ExceptionOr<T> Get(absl::Duration timeout) {
return impl_->Get(timeout);
// Gets the value of the Future, timing out after the specified duration.
ExceptionOr<T> Get(absl::Duration timeout) {
MutexLock lock(&state_->mutex);
while (!state_->done) {
absl::Time start_time = SystemClock::ElapsedRealtime();
if (state_->completed.Wait(timeout).Raised(Exception::kInterrupted)) {
SetExceptionLocked({Exception::kInterrupted});
break;
}
absl::Duration spent = SystemClock::ElapsedRealtime() - start_time;
if (spent < timeout) {
timeout -= spent;
} else if (!state_->done) {
SetExceptionLocked({Exception::kTimeout});
break;
}
}
return state_->value;
}
void AddListener(FutureCallback callback, api::Executor* executor) {
impl_->AddListener(std::move(callback), executor);
// Returns true if the Future has been set.
bool IsSet() const {
MutexLock lock(&state_->mutex);
return state_->done;
}
bool IsSet() const { return impl_->IsSet(); }
private:
// Instance of future implementation is wrapped in shared_ptr<> to make
// it possible to pass Future by value and share the implementation.
// This allows for the following constructions:
// 1)
// Future<bool> future;
// RunOnXyzThread([future]() { future.Set(DoTheJobAndReport()); });
// if (future.Get().Ok()) { /*...*/ }
// 2)
// Future<bool> future = DoSomeAsyncWork(); // Returns future, but keeps copy.
// if (future.Get().Ok()) { /*...*/ }
std::shared_ptr<SettableFuture<T>> impl_;
struct FutureState {
mutable Mutex mutex;
ConditionVariable completed{&mutex};
bool done ABSL_GUARDED_BY(mutex) = {false};
ExceptionOr<T> value ABSL_GUARDED_BY(mutex) =
ExceptionOr<T>(Exception::kFailed);
};
bool SetExceptionLocked(Exception exception) {
if (!state_->done) {
state_->value = ExceptionOr<T>(exception.value != Exception::kSuccess
? exception
: Exception{Exception::kFailed});
state_->done = true;
state_->completed.Notify();
}
return true;
}
std::shared_ptr<FutureState> state_ = std::make_shared<FutureState>();
};
} // namespace nearby
+2 -154
View File
@@ -14,11 +14,11 @@
#include "internal/platform/future.h"
#include <cstring>
#include "gtest/gtest.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/direct_executor.h"
#include "internal/platform/exception.h"
#include "internal/platform/single_thread_executor.h"
@@ -114,156 +114,4 @@ TEST(FutureTest, GetBlocksWhenNotReady) {
EXPECT_GE(blocked_duration, absl::Milliseconds(500));
}
TEST(FutureTest, CallsListenerOnSet) {
constexpr int kValue = 1000;
Future<int> future;
int call_count = 0;
{
SingleThreadExecutor executor;
future.AddListener(
[&](ExceptionOr<int> result) {
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.GetResult(), kValue);
++call_count;
},
&executor);
future.Set(kValue);
// `executor` leaves scope, the destructor waits for tasks to complete
}
EXPECT_EQ(call_count, 1);
}
TEST(FutureTest, CallsAllListenersOnSet) {
constexpr int kValue = 1000;
Future<int> future;
int call_count_listener_1 = 0;
int call_count_listener_2 = 0;
{
SingleThreadExecutor executor;
future.AddListener(
[&](ExceptionOr<int> result) {
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.GetResult(), kValue);
++call_count_listener_1;
},
&executor);
future.AddListener(
[&](ExceptionOr<int> result) {
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.GetResult(), kValue);
++call_count_listener_2;
},
&executor);
future.Set(kValue);
// `executor` leaves scope, the destructor waits for tasks to complete
}
EXPECT_EQ(call_count_listener_1, 1);
EXPECT_EQ(call_count_listener_2, 1);
}
TEST(FutureTest, AddListenerWhenAlreadySetCallsCallback) {
constexpr int kValue = 1000;
Future<int> future;
int call_count = 0;
future.Set(kValue);
{
SingleThreadExecutor executor;
future.AddListener(
[&](ExceptionOr<int> result) {
ASSERT_TRUE(result.ok());
ASSERT_EQ(result.GetResult(), kValue);
++call_count;
},
&executor);
// `executor` leaves scope, the destructor waits for tasks to complete
}
EXPECT_EQ(call_count, 1);
}
TEST(FutureTest, CallsListenerOnSetException) {
constexpr Exception kException = {Exception::kFailed};
Future<int> future;
int call_count = 0;
{
SingleThreadExecutor executor;
future.AddListener(
[&](ExceptionOr<int> result) {
ASSERT_FALSE(result.ok());
ASSERT_EQ(result.GetException(), kException);
++call_count;
},
&executor);
future.SetException(kException);
// `executor` leaves scope, the destructor waits for tasks to complete
}
EXPECT_EQ(call_count, 1);
}
TEST(FutureTest, AddListenerWhenAlreadySetExceptionCallsCallback) {
constexpr Exception kException = {Exception::kFailed};
Future<int> future;
int call_count = 0;
future.SetException(kException);
{
SingleThreadExecutor executor;
future.AddListener(
[&](ExceptionOr<int> result) {
ASSERT_FALSE(result.ok());
ASSERT_EQ(result.GetException(), kException);
++call_count;
},
&executor);
// `executor` leaves scope, the destructor waits for tasks to complete
}
EXPECT_EQ(call_count, 1);
}
TEST(FutureTest, TimeoutSetsException) {
Future<int> future(absl::Milliseconds(10));
EXPECT_EQ(future.Get().exception(), Exception::kTimeout);
}
TEST(FutureTest, TimeoutCallsListeners) {
Future<int> future(absl::Milliseconds(10));
CountDownLatch latch(1);
future.AddListener(
[&](ExceptionOr<int> result) {
ASSERT_FALSE(result.ok());
ASSERT_EQ(result.exception(), Exception::kTimeout);
latch.CountDown();
},
&DirectExecutor::GetInstance());
EXPECT_TRUE(latch.Await().Ok());
EXPECT_EQ(future.Get().exception(), Exception::kTimeout);
}
TEST(FutureTest, SetValueBeforeTimeout) {
Future<int> future(absl::Minutes(1));
future.Set(5);
EXPECT_EQ(future.Get().result(), 5);
}
TEST(FutureTest, SetExceptionBeforeTimeout) {
Future<int> future(absl::Minutes(1));
future.SetException({Exception::kExecution});
EXPECT_EQ(future.Get().exception(), Exception::kExecution);
}
} // namespace nearby
+13 -19
View File
@@ -1,6 +1,3 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,6 +11,10 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test")
# 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.
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_cc//cc:cc_test.bzl", "cc_test")
licenses(["notice"])
cc_library(
@@ -41,6 +42,7 @@ cc_library(
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__",
@@ -89,15 +91,12 @@ cc_library(
"crypto.h",
"device_info.h",
"executor.h",
"future.h",
"input_file.h",
"listenable_future.h",
"log_message.h",
"mutex.h",
"output_file.h",
"preferences_manager.h",
"scheduled_executor.h",
"settable_future.h",
"submittable_executor.h",
"system_clock.h",
"timer.h",
@@ -151,16 +150,16 @@ cc_library(
cc_library(
name = "comm",
hdrs = [
"app_lifecycle_monitor.h",
"awdl.h",
"ble.h",
"ble_v2.h",
"bluetooth_adapter.h",
"bluetooth_classic.h",
"credential_callbacks.h",
"credential_storage.h",
"http_loader.h",
"psk_info.h",
"server_sync.h",
"upgrade_address_info.h",
"webrtc.h",
"wifi.h",
"wifi_direct.h",
@@ -226,20 +225,15 @@ cc_library(
name = "platform_impl",
testonly = True,
tags = ["keep_dep"], # Prevent build_cleaner from removing the dependency.
visibility = [
"//:__subpackages__",
"//connections/implementation:__subpackages__",
"//location/nearby/analytics/cpp:__subpackages__",
"//location/nearby/apps/better_together/plugins/preferences_native:__subpackages__",
"//location/nearby/cpp/sharing:__subpackages__",
],
deps = [] + select({
"@platforms//os:linux": [
"//internal/platform/implementation/linux:linux_platform_impl",
],
visibility = ["//:__subpackages__"],
deps = [
] + select({
"@platforms//os:windows": [
"//internal/platform/implementation/windows",
],
"@platforms//os:linux": [
"//internal/platform/implementation/linux:linux_platform_impl",
],
# Add other platforms as needed
"//conditions:default": [
"//internal/platform/implementation/g3",
@@ -0,0 +1,57 @@
// 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 THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APP_LIFECYCLE_MONITOR_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APP_LIFECYCLE_MONITOR_H_
#include <functional>
#include <utility>
namespace nearby {
namespace api {
class AppLifecycleMonitor {
public:
// The state of the app lifecycle. The state transition is kBackground ->
// kInactive -> kActive. Foreground is an umbrella term that describes the app
// when it is either Inactive or Active.
enum class AppLifecycleState {
// The app is active when it starts to receive events.
kActive,
// The app is inactive but is still not in background.
kInactive,
// The app is background.
kBackground,
};
// Registers callbacks for connection changes. The callbacks will be called
// when device is connected to a LAN network or when the device is connected
// to internet.
explicit AppLifecycleMonitor(
std::function<void(AppLifecycleState)> state_updated_callback)
: state_updated_callback_(std::move(state_updated_callback)) {}
virtual ~AppLifecycleMonitor() = default;
protected:
// The callback to be called when the app lifecycle state is updated. In
// order to make it possible to be called from different methods, use
// std::function instead of absl::AnyInvocable.
std::function<void(AppLifecycleState)> state_updated_callback_;
};
} // namespace api
} // namespace nearby
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APP_LIFECYCLE_MONITOR_H_
+56 -2
View File
@@ -21,6 +21,7 @@ licenses(["notice"])
package(default_visibility = [
"//ambient/nearby/testing/connection/mdc/ios:__subpackages__",
"//connections:__subpackages__",
"//connections:partners",
"//googlemac/iPhone/Nearby:__subpackages__",
"//internal/platform:__subpackages__",
"//internal/preferences:__subpackages__",
@@ -61,6 +62,7 @@ objc_library(
"preferences_manager.mm",
"scheduled_executor.mm",
"timer.mm",
"webrtc.mm",
"wifi_hotspot.mm",
"wifi_lan.mm",
],
@@ -69,6 +71,7 @@ objc_library(
"device_info.h",
"preferences_manager.h",
"timer.h",
"webrtc.h",
"wifi.h",
"wifi_hotspot.h",
"wifi_lan.h",
@@ -82,6 +85,7 @@ objc_library(
":Platform_cc",
":Shared",
":ble_v2",
":app_lifecycle_monitor",
":network_utils",
# Required Reason API File: third_party/nearby/internal/platform/implementation/apple/preferences_manager.mm
"//releasetools/apple/privacy/privacymanifests/requiredreasonsapi:user_defaults-user_defaults-read_write_app_data_ca92_1",
@@ -90,6 +94,7 @@ objc_library(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_absl//absl/status",
"@com_google_absl//absl/types:span",
"//third_party/apple_frameworks:CoreBluetooth",
"//third_party/apple_frameworks:Foundation",
@@ -97,6 +102,19 @@ objc_library(
"@nlohmann_json//:json",
"//internal/base:file_path",
"//internal/base:files",
"//internal/base:masker",
"//internal/platform/implementation/apple/Mediums/Hotspot",
"//internal/account",
"//internal/crypto_cros",
"//internal/platform/implementation:account_manager",
"//internal/platform:comm",
"//internal/platform:logging",
"//internal/platform:types",
"//internal/proto:tachyon_cc_proto",
"//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:libjingle_peerconnection_api",
"//third_party/webrtc/files/stable/webrtc/rtc_base:checks",
"//internal/platform:base",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
@@ -114,6 +132,38 @@ objc_library(
}),
)
objc_library(
name = "GNCNotificationCenter",
hdrs = ["GNCNotificationCenter.h"],
deps = [
"//third_party/apple_frameworks:Foundation",
],
)
objc_library(
name = "app_lifecycle_monitor",
srcs = [
"app_lifecycle_monitor.mm",
],
hdrs = [
"app_lifecycle_monitor.h",
],
# Prevent Objective-C++ headers from being pulled into swift.
aspect_hints = ["//tools/build_defs/swift:no_module"],
deps = [
":GNCNotificationCenter",
"//internal/platform:base",
"//internal/platform/implementation:comm",
"//internal/platform/implementation/apple/Log:GNCLogger",
"//third_party/apple_frameworks:Foundation",
] + select({
"@platforms//os:platform_ios": [
"//third_party/apple_frameworks:UIKit",
],
"//conditions:default": [],
}),
)
objc_library(
name = "ble_v2",
srcs = [
@@ -144,6 +194,7 @@ objc_library(
# Prevent Objective-C++ headers from being pulled into swift.
aspect_hints = ["//tools/build_defs/swift:no_module"],
deps = [
":Shared",
":bluetooth_adapter_v2",
":comm",
"//internal/platform:base",
@@ -225,7 +276,9 @@ objc_library(
# Prevent Objective-C++ headers from being pulled into swift.
aspect_hints = ["//tools/build_defs/swift:no_module"],
deps = [
"//internal/platform:logging",
"//third_party/apple_frameworks:os",
"@com_google_absl//absl/log:log_entry",
"@com_google_absl//absl/log:log_sink",
"@com_google_absl//absl/strings",
],
)
@@ -242,7 +295,7 @@ objc_library(
aspect_hints = ["//tools/build_defs/swift:no_module"],
deps = [
":os_log_sink",
"//internal/platform:logging",
"@com_google_absl//absl/log:log_sink_registry",
],
)
@@ -295,6 +348,7 @@ cc_test(
":Platform_cc",
"//internal/platform/implementation/g3:crypto",
"@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",
@@ -26,4 +26,7 @@
/** Checks whether BLE L2CAP is enabled in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL bleL2capEnabled;
/** Checks whether BLE L2CAP refactor is enabled in the Nearby Connections SDK. */
@property(nonatomic, class, readonly) BOOL refactorBleL2capEnabled;
@end
@@ -36,4 +36,10 @@
kEnableBleL2cap);
}
+ (BOOL)refactorBleL2capEnabled {
return nearby::NearbyFlags::GetInstance().GetBoolFlag(
nearby::connections::config_package_nearby::nearby_connections_feature::
kRefactorBleL2cap);
}
@end
@@ -0,0 +1,40 @@
// 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 THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_GNCNOTIFICATIONCENTER_H_
#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_GNCNOTIFICATIONCENTER_H_
#import <Foundation/Foundation.h>
@protocol GNCNotificationCenter <NSObject>
// Method for registering an observer
- (id<NSObject>)addObserverForName:(NSNotificationName)name
object:(id)obj
queue:(NSOperationQueue *)queue
usingBlock:(void (^)(NSNotification *notification))block;
// Method for unregistering an observer
- (void)removeObserver:(id)observer;
// Method for posting a notification
- (void)postNotificationName:(NSNotificationName)aName
object:(nullable id)anObject;
@end
@interface NSNotificationCenter () <GNCNotificationCenter>
@end
#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_IMPLEMENTATION_APPLE_GNCNOTIFICATIONCENTER_H_
@@ -35,6 +35,9 @@ NSData *_Nullable GNCMd5Data(NSData *data);
/// Generates an MD5 hash (16 bytes) from a string.
NSData *_Nullable GNCMd5String(NSString *string);
/// Converts NSData to a hex string with a "0x" prefix.
NSString *GNCConvertDataToHexString(NSData *_Nullable data);
#ifdef __cplusplus
} // extern "C"
#endif
@@ -41,4 +41,20 @@ NSData *GNCMd5String(NSString *string) {
return GNCMd5Data([string dataUsingEncoding:NSUTF8StringEncoding]);
}
NSString *GNCConvertDataToHexString(NSData *_Nullable data) {
NSUInteger dataLength = data.length;
if (dataLength == 0) {
return @"0x";
}
const unsigned char *dataBuffer = (const unsigned char *)data.bytes;
NSMutableString *hexString = [NSMutableString stringWithCapacity:dataLength * 2];
for (NSUInteger i = 0; i < dataLength; ++i) {
[hexString appendFormat:@"%02lx", (unsigned long)dataBuffer[i]];
}
return [NSString stringWithFormat:@"0x%@", hexString];
}
NS_ASSUME_NONNULL_END
@@ -18,10 +18,7 @@ licenses(["notice"])
package(default_visibility = [
"//connections:__subpackages__",
"//googlemac/iPhone/Nearby:__subpackages__",
"//internal/platform/implementation/apple:__subpackages__",
"//location/nearby:__subpackages__",
"//sharing:__subpackages__",
])
objc_library(
@@ -16,6 +16,7 @@
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPStream.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCLeaks.h"
@@ -98,8 +99,12 @@ static NSData *PrefixLengthData(NSData *data) {
dispatch_async(_selfQueue, ^{
NSData *packet;
// Prefix the service ID hash.
packet = PrefixLengthData(PrefixDataWithServiceIDHash(_serviceIDHash, data));
if (GNCFeatureFlags.refactorBleL2capEnabled) {
packet = PrefixLengthData(data);
} else {
// Prefix the service ID hash.
packet = PrefixLengthData(PrefixDataWithServiceIDHash(_serviceIDHash, data));
}
if (_verboseLoggingEnabled) {
GNCLoggerDebug(@"GNCBLEL2CAPConnection data to be sent: %@", [packet description]);
}
@@ -202,42 +207,49 @@ static NSData *PrefixLengthData(NSData *data) {
}
bytesProcessed = realData.length + kL2CAPPacketLength;
// TODO: b/399815436 - Refactor the validation logic to connections layer.
if ([self handleL2CAPPacketFromData:realData]) {
return bytesProcessed;
}
if (GNCFeatureFlags.refactorBleL2capEnabled) {
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler([realData copy]);
});
}
} else {
// TODO: b/399815436 - Refactor the validation logic to connections layer.
if ([self handleL2CAPPacketFromData:realData]) {
return bytesProcessed;
}
// TODO: b/399815436 - All BLE control packets should be handled here and not passed to
// upper layer. Need to refine the flow after refactoring.
if (_incomingConnection && !_handledReceivedBLEIntroPacket) {
[self handleBLEIntroPacketFromData:realData];
return bytesProcessed;
}
// TODO: b/399815436 - All BLE control packets should be handled here and not passed to
// upper layer. Need to refine the flow after refactoring.
if (_incomingConnection && !_handledReceivedBLEIntroPacket) {
[self handleBLEIntroPacketFromData:realData];
return bytesProcessed;
}
if (realData.length < _serviceIDHash.length) {
GNCLoggerError(@"Data length mismatch. Expected size: > %lu, Data: %@", _serviceIDHash.length,
realData);
return bytesProcessed;
}
if (realData.length < _serviceIDHash.length) {
GNCLoggerError(@"Data length mismatch. Expected size: > %lu, Data: %@", _serviceIDHash.length,
realData);
return bytesProcessed;
}
// Extract the service ID prefix from each data packet and validate it.
NSUInteger prefixLength = _serviceIDHash.length;
if (![[realData subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
return bytesProcessed;
}
// Extract the service ID prefix from each data packet and validate it.
NSUInteger prefixLength = _serviceIDHash.length;
if (![[realData subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
return bytesProcessed;
}
dispatch_async(_selfQueue, ^{
[_stream sendData:PrefixLengthData(GNCMGenerateBLEFramesPacketAcknowledgementPacket(
_serviceIDHash, realData.length))
completionBlock:^(BOOL result){
}];
});
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler([NSData
dataWithData:[realData subdataWithRange:NSMakeRange(prefixLength,
realData.length - prefixLength)]]);
dispatch_async(_selfQueue, ^{
[_stream sendData:PrefixLengthData(GNCMGenerateBLEFramesPacketAcknowledgementPacket(
_serviceIDHash, realData.length))
completionBlock:^(BOOL result){
}];
});
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler(
[realData subdataWithRange:NSMakeRange(prefixLength, realData.length - prefixLength)]);
});
}
}
return bytesProcessed;
}
@@ -296,10 +296,6 @@ enum { READ_BUFFER_SIZE = 409600 };
return;
}
if (result < 0) {
GNCLoggerError(@"[NEARBY] Write result should not be negative.");
return;
}
NSUInteger totalBytesWritten = (NSUInteger)result;
if (_verboseLoggingEnabled) {
@@ -243,6 +243,14 @@ typedef void (^GNCGATTConnectionCompletionHandler)(GNCBLEGATTClient *_Nullable c
*/
- (void)stop;
/**
* Retrieves the peripheral with the specified identifier.
*
* @param identifier The identifier of the peripheral to retrieve.
* @return The peripheral with the specified identifier, or @c nil if not found.
*/
- (nullable CBPeripheral *)retrievePeripheralWithIdentifier:(NSUUID *)identifier;
@end
NS_ASSUME_NONNULL_END
@@ -136,6 +136,22 @@ static GNCBLEL2CAPServer *_Nonnull CreateL2CapServer(
}
}
- (nullable CBPeripheral *)retrievePeripheralWithIdentifier:(NSUUID *)identifier {
NSAssert(_centralManager, @"CBCentralManager not created.");
NSAssert(identifier, @"Should have an identifier, self: %@", self);
NSArray<CBPeripheral *> *peripherals =
[_centralManager retrievePeripheralsWithIdentifiers:@[ identifier ]];
for (CBPeripheral *peripheral in peripherals) {
if ([peripheral.identifier isEqual:identifier]) {
return peripheral;
}
}
return nil;
}
- (BOOL)supportsExtendedAdvertisements {
// TODO(b/294736083): CoreBluetooth doesn't support actually advertising any extensions, however
// some devices can scan for them if the feature is available. If we return @c YES from this
@@ -97,6 +97,14 @@ NS_ASSUME_NONNULL_BEGIN
/** Asks the central manager to stop scanning for peripherals. */
- (void)stopScan;
/**
* Retrieves the peripherals with the given identifiers.
*
* @param identifiers The identifiers of the peripherals to retrieve.
* @return An array of peripherals with the given identifiers.
*/
- (NSArray<CBPeripheral *> *)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> *)identifiers;
@end
/**
@@ -14,6 +14,7 @@
#import "internal/platform/implementation/apple/Mediums/BLE/GNCMBleConnection.h"
#import "internal/platform/implementation/apple/Flags/GNCFeatureFlags.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCLeaks.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCMBleUtils.h"
@@ -58,14 +59,18 @@ NS_ASSUME_NONNULL_BEGIN
completion:(GNCMPayloadResultHandler)completion {
dispatch_async(_selfQueue, ^{
NSMutableData *packet;
if (data.length == 0) {
// Get the Control introduction packet if data length is 0.
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
packet = [NSMutableData dataWithData:introData];
if (GNCFeatureFlags.refactorBleL2capEnabled) {
packet = [NSMutableData dataWithData:data];
} else {
// Prefix the service ID hash.
packet = [NSMutableData dataWithData:_serviceIDHash];
[packet appendData:data];
if (data.length == 0) {
// Get the Control introduction packet if data length is 0.
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
packet = [NSMutableData dataWithData:introData];
} else {
// Prefix the service ID hash.
packet = [NSMutableData dataWithData:_serviceIDHash];
[packet appendData:data];
}
}
[_socket sendData:packet
@@ -100,46 +105,55 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)socket:(GNSSocket *)socket didReceiveData:(NSData *)data {
// Extract the service ID prefix from each data packet.
NSMutableData *packet;
NSUInteger prefixLength = _serviceIDHash.length;
if (_expectedIntroPacket && !_receivedIntroPacket) {
// Check if the first packet is intro packet.
if (!_serviceIDHash) {
// If _serviceIdHash is nil, then we need to parse the first incoming packet if it conforms to
// introducion packet and extract the serviceIdHash for coming packets.
NSData *serviceIDHash = GNCMParseBLEFramesIntroductionPacket(data);
if (serviceIDHash) {
_serviceIDHash = serviceIDHash;
_receivedIntroPacket = YES;
} else {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
if (GNCFeatureFlags.refactorBleL2capEnabled) {
dispatch_async(_selfQueue, ^{
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler([data copy]);
});
}
} else {
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
if ([data isEqual:introData]) {
_receivedIntroPacket = YES;
});
} else {
// Extract the service ID prefix from each data packet.
NSData *packet;
NSUInteger prefixLength = _serviceIDHash.length;
if (_expectedIntroPacket && !_receivedIntroPacket) {
// Check if the first packet is intro packet.
if (!_serviceIDHash) {
// If _serviceIdHash is nil, then we need to parse the first incoming packet if it conforms
// to introducion packet and extract the serviceIdHash for coming packets.
NSData *serviceIDHash = GNCMParseBLEFramesIntroductionPacket(data);
if (serviceIDHash) {
_serviceIDHash = serviceIDHash;
_receivedIntroPacket = YES;
} else {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
}
} else {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
NSData *introData = GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash);
if ([data isEqual:introData]) {
_receivedIntroPacket = YES;
} else {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong intro packet and discarded");
}
}
return;
}
return;
}
if (![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong data packet and discarded");
return;
}
packet = [NSMutableData
dataWithData:[data subdataWithRange:NSMakeRange(prefixLength, data.length - prefixLength)]];
dispatch_async(_selfQueue, ^{
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler(packet);
});
if (![[data subdataWithRange:NSMakeRange(0, prefixLength)] isEqual:_serviceIDHash]) {
GNCLoggerInfo(@"[NEARBY] Input stream: Received wrong data packet and discarded");
return;
}
});
packet = [data subdataWithRange:NSMakeRange(prefixLength, data.length - prefixLength)];
dispatch_async(_selfQueue, ^{
if (_connectionHandlers.payloadHandler) {
dispatch_async(_callbackQueue, ^{
_connectionHandlers.payloadHandler(packet);
});
}
});
}
}
@end
@@ -45,6 +45,11 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable instancetype)initWithPeripheral:(CBPeripheral *)peripheral
centralManager:(GNSCentralManager *)centralManager;
/**
* Connects the peripheral with BLE.
*/
- (void)bleConnect;
/**
* Called by the central manager when the peripheral is connected with BLE.
*/
@@ -67,6 +72,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface GNSCentralPeerManager (TestingHelpers)
- (NSTimer *)testing_connectionConfirmTimer;
- (BOOL)isBLEConnected;
@end
@@ -13,11 +13,11 @@
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralPeerManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralPeerManager+Private.h"
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralManager+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralPeerManager+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSSocket+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSSocket.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSUtils+Private.h"
@@ -115,19 +115,6 @@ static NSString *PeripheralStateString(CBPeripheralState state) {
@synthesize cbPeripheral = _cbPeripheral;
// Used for testing. A test subclass can override this method so a mock timer can be returned.
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)timeInterval
target:(id)target
selector:(SEL)selector
userInfo:(nullable id)userInfo
repeats:(BOOL)yesOrNo {
return [NSTimer scheduledTimerWithTimeInterval:timeInterval
target:target
selector:selector
userInfo:userInfo
repeats:yesOrNo];
}
- (instancetype)initWithPeripheral:(CBPeripheral *)peripheral
centralManager:(GNSCentralManager *)centralManager
queue:(dispatch_queue_t)queue {
@@ -153,6 +140,7 @@ static NSString *PeripheralStateString(CBPeripheralState state) {
- (void)dealloc {
GNCLoggerDebug(@"Dealloc CentralPeerManager with _cbPeripheral %@", _cbPeripheral);
[_connectionConfirmTimer invalidate];
_cbPeripheral.delegate = nil;
if (_cbPeripheral.state != CBPeripheralStateDisconnected) {
[_centralManager cancelPeripheralConnectionForPeer:self];
@@ -415,11 +403,7 @@ static NSString *PeripheralStateString(CBPeripheralState state) {
// If error is nil, kGNSNoConnection |error| is passed to the rssi completion.
- (void)cleanRSSICompletionAfterDisconnectionWithError:(NSError *)error {
if (_readRSSIValueCompletions) {
NSError *rssiCompletionError = error;
if (rssiCompletionError) {
rssiCompletionError = GNSErrorWithCode(GNSErrorNoConnection);
}
[self callRSSICompletionWithRSSIValue:nil error:rssiCompletionError];
[self callRSSICompletionWithRSSIValue:nil error:GNSErrorWithCode(GNSErrorNoConnection)];
}
}
@@ -589,11 +573,12 @@ static NSString *PeripheralStateString(CBPeripheralState state) {
// Note: Avoid using |characteristic.value| here as it seems it is always nil.
if (error) {
GNCLoggerInfo(@"Characteristic write failed with error: %@", error);
[self callDataWriteCompletionWithError:error];
[self disconnectingWithError:error];
} else {
GNCLoggerInfo(@"Characteristic write succeeded");
[self callDataWriteCompletionWithError:error];
}
[self callDataWriteCompletionWithError:error];
}
// This method sends |packet| fitting a single characteristic write to |socket|. All packets sent by
@@ -632,9 +617,11 @@ static NSString *PeripheralStateString(CBPeripheralState state) {
GNSCentralSocketCompletion completion = _discoveringServiceSocketCompletion;
_discoveringServiceSocketCompletion = nil;
_socket = socket;
dispatch_async(_queue, ^{
completion(socket, nil);
});
if (completion) {
dispatch_async(_queue, ^{
completion(socket, nil);
});
}
if (!_socket) {
[self disconnectingWithError:nil];
return;
@@ -650,11 +637,11 @@ static NSString *PeripheralStateString(CBPeripheralState state) {
data:_connectionRequestData];
[self sendPacket:connectionRequest];
_connectionConfirmTimer =
[[self class] scheduledTimerWithTimeInterval:kMaxConnectionConfirmWaitTimeInSeconds
target:self
selector:@selector(timeOutConnectionForTimer:)
userInfo:nil
repeats:NO];
[NSTimer scheduledTimerWithTimeInterval:kMaxConnectionConfirmWaitTimeInSeconds
target:self
selector:@selector(timeOutConnectionForTimer:)
userInfo:nil
repeats:NO];
}
- (void)peripheral:(CBPeripheral *)peripheral didReadRSSI:(NSNumber *)RSSI error:(NSError *)error {
@@ -24,11 +24,14 @@ typedef BOOL (^GNSUpdateValueHandler)();
* Private methods called by GNSPeripheralManager, GNSSocket and for tests.
* Should not be used by the Nearby Socket client.
*/
@interface GNSPeripheralManager ()<CBPeripheralManagerDelegate>
@interface GNSPeripheralManager () <CBPeripheralManagerDelegate>
@property(nonatomic, readonly) NSString *restoreIdentifier;
@property(nonatomic, readonly) CBPeripheralManager *cbPeripheralManager;
/** Test initializer. */
- (instancetype)initWithPeripheralManager:(CBPeripheralManager *)peripheralManager;
/**
* Updates the outgoing characteristic value using an handler. The handler is stored in
* a queue. If the CBPeripheralManager is ready, the handler is called right away. Otherwise the
@@ -97,6 +97,14 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
queue:dispatch_get_main_queue()];
}
- (instancetype)initWithPeripheralManager:(CBPeripheralManager *)peripheralManager {
self = [self initWithAdvertisedName:nil restoreIdentifier:nil queue:dispatch_get_main_queue()];
if (self) {
_cbPeripheralManager = peripheralManager;
}
return self;
}
- (void)dealloc {
[self stop];
}
@@ -323,11 +331,16 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
}
- (BOOL)updateOutgoingCharacteristic:(NSData *)data onSocket:(GNSSocket *)socket {
GNSPeripheralServiceManager *peripheralServiceManager = socket.owner;
GNSPeripheralServiceManager *peripheralServiceManager =
(GNSPeripheralServiceManager *)socket.owner;
NSAssert(peripheralServiceManager, @"%@ should have an owner.", socket);
CBCentral *central = socket.peerAsCentral;
if (!central) {
return NO;
}
return [_cbPeripheralManager updateValue:data
forCharacteristic:peripheralServiceManager.weaveOutgoingCharacteristic
onSubscribedCentrals:@[ socket.peerAsCentral ]];
onSubscribedCentrals:@[ central ]];
}
- (CBPeripheralManager *)cbPeripheralManagerWithDelegate:(id<CBPeripheralManagerDelegate>)delegate
@@ -445,7 +458,6 @@ static NSTimeInterval gKBTCrashLoopMaxTimeBetweenResetting = 15.f;
return;
}
NSAssert(peripheral.isAdvertising, @"Peripheral should be advertising.");
GNCLoggerInfo(@"Peripheral did start advertising %@", _advertisementData);
// Once an advertisment operation is over, check if the advertised data is up-to-date.
@@ -15,6 +15,7 @@
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSSocket+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSWeavePacket.h"
typedef NS_ENUM(NSInteger, GNSBluetoothServiceState) {
GNSBluetoothServiceStateNotAdded,
@@ -26,7 +27,7 @@ typedef NS_ENUM(NSInteger, GNSBluetoothServiceState) {
* Private methods called by GNSPeripheralManager, GNSSocket and for tests.
* Should not be used by the Nearby Socket client.
*/
@interface GNSPeripheralServiceManager ()<GNSSocketOwner>
@interface GNSPeripheralServiceManager () <GNSSocketOwner, GNSWeavePacketHandler>
@property(nonatomic, readonly) GNSPeripheralManager *peripheralManager;
@property(nonatomic, readonly) GNSBluetoothServiceState cbServiceState;
@@ -35,6 +36,12 @@ typedef NS_ENUM(NSInteger, GNSBluetoothServiceState) {
@property(nonatomic, readonly) CBMutableCharacteristic *weaveOutgoingCharacteristic;
@property(nonatomic, readonly) CBMutableCharacteristic *pairingCharacteristic;
@property(nonatomic, readonly) GNSShouldAcceptSocketHandler shouldAcceptSocketHandler;
/**
* Called when the BLE service is added.
* Warning: Be careful with retain cycles, if the completion block has a strong reference to this
* object.
*/
@property(nonatomic, readonly) GNSErrorHandler bleServiceAddedCompletion;
/**
* Informs this service manager that its CBService will start to be added.
@@ -108,6 +115,14 @@ typedef NS_ENUM(NSInteger, GNSBluetoothServiceState) {
*/
- (void)processWriteRequest:(CBATTRequest *)request;
/**
* Handles a Weave error.
*
* @param errorCode Weave error code.
* @param socket Socket associated with the error.
*/
- (void)handleWeaveError:(GNSError)errorCode socket:(GNSSocket *)socket;
/**
* Called when a central subscribes to a characteristic. If the characteristic is the outgoing
* characteristic, the desired connection latency is set to low.
@@ -129,4 +144,22 @@ typedef NS_ENUM(NSInteger, GNSBluetoothServiceState) {
- (void)central:(CBCentral *)central
didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;
/**
* Marks socket as ready.
*
* @param socket Socket to mark as ready.
*/
- (void)socketReady:(GNSSocket *)socket;
/**
* Sends a weave packet.
*
* @param packet Weave packet to send.
* @param socket Socket to send packet to.
* @param completion Completion handler.
*/
- (void)sendPacket:(GNSWeavePacket *)packet
toSocket:(GNSSocket *)socket
completion:(void (^)(void))completion;
@end
@@ -16,6 +16,8 @@
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralManager+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/CBATTRequest+GNSATTRequest.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSATTRequest.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSSocket+Private.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSUtils.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSWeavePacket.h"
@@ -340,6 +342,10 @@ static CBMutableCharacteristic *CreatePairingCharacteristic() {
socket = _sockets[request.central.identifier];
// Only an error packet can cause the socket to have been removed at this point.
if (!socket && [packet isKindOfClass:[GNSWeaveConnectionRequestPacket class]]) {
// If socket is not found for a connection request, it means connection was rejected.
return;
}
NSAssert(socket || [packet isKindOfClass:[GNSWeaveErrorPacket class]],
@"Socket missing after receiving non-error weave packet");
[socket incrementReceivePacketCounter];
@@ -506,8 +512,12 @@ static CBMutableCharacteristic *CreatePairingCharacteristic() {
- (void)handleConnectionRequestPacket:(GNSWeaveConnectionRequestPacket *)packet
context:(id)request {
NSAssert([request isKindOfClass:[CBATTRequest class]], @"The context should be a request.");
GNSSocket *socket = _sockets[((CBATTRequest *)request).central.identifier];
if (![request conformsToProtocol:@protocol(GNSATTRequest)]) {
GNCLoggerError(@"The context doesn't contain a central.");
return;
}
CBCentral *central = [(id<GNSATTRequest>)request central];
GNSSocket *socket = _sockets[central.identifier];
if (socket) {
GNCLoggerInfo(@"Receiving a connection request from an already connected socket %@.", socket);
// The peripheral considers the previous socket as being disconnected.
@@ -515,9 +525,7 @@ static CBMutableCharacteristic *CreatePairingCharacteristic() {
[self removeSocket:socket withError:error];
socket = nil;
}
socket = [[GNSSocket alloc] initWithOwner:self
centralPeer:((CBATTRequest *)request).central
queue:_queue];
socket = [[GNSSocket alloc] initWithOwner:self centralPeer:central queue:_queue];
if (packet.maxVersion < kWeaveVersionSupported || packet.minVersion > kWeaveVersionSupported) {
GNCLoggerError(@"Unsupported Weave version range: [%d, %d].", packet.minVersion,
packet.maxVersion);
@@ -553,22 +561,34 @@ static CBMutableCharacteristic *CreatePairingCharacteristic() {
- (void)handleConnectionConfirmPacket:(GNSWeaveConnectionConfirmPacket *)packet
context:(id)request {
NSAssert([request isKindOfClass:[CBATTRequest class]], @"The context should be a request.");
GNSSocket *socket = _sockets[((CBATTRequest *)request).central.identifier];
if (![request conformsToProtocol:@protocol(GNSATTRequest)]) {
GNCLoggerError(@"The context doesn't contain a central.");
return;
}
CBCentral *central = [(id<GNSATTRequest>)request central];
GNSSocket *socket = _sockets[central.identifier];
GNCLoggerError(@"Unexpected connection confirm packet received.");
[self handleWeaveError:GNSErrorUnexpectedWeaveControlPacket socket:socket];
}
- (void)handleErrorPacket:(GNSWeaveErrorPacket *)packet context:(id)request {
NSAssert([request isKindOfClass:[CBATTRequest class]], @"The context should be a request.");
GNSSocket *socket = _sockets[((CBATTRequest *)request).central.identifier];
if (![request conformsToProtocol:@protocol(GNSATTRequest)]) {
GNCLoggerError(@"The context doesn't contain a central.");
return;
}
CBCentral *central = [(id<GNSATTRequest>)request central];
GNSSocket *socket = _sockets[central.identifier];
GNCLoggerInfo(@"Error packet received.");
[self handleWeaveError:GNSErrorWeaveErrorPacketReceived socket:socket];
}
- (void)handleDataPacket:(GNSWeaveDataPacket *)packet context:(id)request {
NSAssert([request isKindOfClass:[CBATTRequest class]], @"The context should be a request.");
GNSSocket *socket = _sockets[((CBATTRequest *)request).central.identifier];
if (![request conformsToProtocol:@protocol(GNSATTRequest)]) {
GNCLoggerError(@"The context doesn't contain a central.");
return;
}
CBCentral *central = [(id<GNSATTRequest>)request central];
GNSSocket *socket = _sockets[central.identifier];
if (packet.isFirstPacket && socket.waitingForIncomingData) {
GNCLoggerError(@"There is already a receive operation in progress");
[self handleWeaveError:GNSErrorWeaveDataTransferInProgress socket:socket];
@@ -0,0 +1,27 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/GNSATTRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
* Category to make CBATTRequest conform to GNSATTRequest.
*/
@interface CBATTRequest (GNSATTRequest) <GNSATTRequest>
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,18 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Shared/CBATTRequest+GNSATTRequest.h"
@implementation CBATTRequest (GNSATTRequest)
@end
@@ -0,0 +1,28 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
NS_ASSUME_NONNULL_BEGIN
/**
* Protocol to abstract access to CBATTRequest and GNSFakeCBATTRequest.
*/
@protocol GNSATTRequest <NSObject>
@property(nonatomic, readonly) CBCentral *central;
@end
NS_ASSUME_NONNULL_END
@@ -234,7 +234,6 @@ typedef void (^GNSIncomingChunkReceivedBlock)(NSData *incomingData);
}
- (CBCentral *)peerAsCentral {
NSAssert([_peer isKindOfClass:[CBCentral class]], @"Wrong peer type %@", _peer);
if ([_peer isKindOfClass:[CBCentral class]]) {
return _peer;
} else {
@@ -276,8 +276,6 @@ static UInt8 WeaveDataPacketHeader(UInt8 packetCounter, BOOL firstPacketFlag, BO
@implementation GNSWeaveConnectionConfirmPacket
- (instancetype)initWithVersion:(UInt16)version packetSize:(UInt16)packetSize data:(NSData *)data {
NSAssert(packetSize >= kGNSMinSupportedPacketSize, @"The minimum packet size is %ld",
(long)kGNSMinSupportedPacketSize);
self = [super initWithPacketCounter:0];
if (self) {
_version = version;
@@ -25,9 +25,23 @@ objc_library(
testonly = 1,
srcs = [
"Central/GNSCentralManagerTest.m",
"Central/GNSCentralPeerManagerTest.m",
"Central/GNSFakeCBCharacteristic.m",
"Central/GNSFakeCBPeripheral.m",
"Central/GNSFakeCBService.m",
"Central/GNSFakeCentralManager.m",
],
hdrs = [
"Central/GNSFakeCBCharacteristic.h",
"Central/GNSFakeCBPeripheral.h",
"Central/GNSFakeCBService.h",
"Central/GNSFakeCentralManager.h",
],
deps = [
"//internal/platform/implementation/apple/Mediums/BLE/Sockets:Central",
"//internal/platform/implementation/apple/Mediums/BLE/Sockets:Shared",
"//third_party/apple_frameworks:CoreBluetooth",
"//third_party/apple_frameworks:Foundation",
"//third_party/apple_frameworks:XCTest",
"//third_party/objective_c/ocmock/v3:OCMock",
],
@@ -41,6 +55,37 @@ ios_unit_test(
deps = [":CentralTestsLib"],
)
objc_library(
name = "PeripheralTestsLib",
testonly = 1,
srcs = [
"Peripheral/GNSFakePeripheralManager.m",
"Peripheral/GNSFakePeripheralServiceManager.m",
"Peripheral/GNSPeripheralManagerTest.m",
"Peripheral/GNSPeripheralServiceManagerTest.m",
],
hdrs = [
"Peripheral/GNSFakePeripheralManager.h",
"Peripheral/GNSFakePeripheralServiceManager.h",
],
deps = [
"//internal/platform/implementation/apple/Mediums/BLE/Sockets:Peripheral",
"//internal/platform/implementation/apple/Mediums/BLE/Sockets:Shared",
"//third_party/apple_frameworks:CoreBluetooth",
"//third_party/apple_frameworks:Foundation",
"//third_party/apple_frameworks:XCTest",
"//third_party/objective_c/ocmock/v3:OCMock",
],
)
ios_unit_test(
name = "PeripheralTests",
google_create_srl_bindings_module = False,
minimum_os_version = IOS_MINIMUM_OS,
runner = "//testing/utp/ios:IOS_LATEST",
deps = [":PeripheralTestsLib"],
)
objc_library(
name = "SharedTestsLib",
testonly = 1,
@@ -0,0 +1,26 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface GNSFakeCBCharacteristic : NSObject
@property(nonatomic) CBUUID *UUID;
@property(nonatomic, nullable) NSData *value;
@property(nonatomic) BOOL isNotifying;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,18 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Central/GNSFakeCBCharacteristic.h"
@implementation GNSFakeCBCharacteristic
@end
@@ -0,0 +1,36 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
@class GNSFakeCBCharacteristic;
@class GNSFakeCBService;
NS_ASSUME_NONNULL_BEGIN
@interface GNSFakeCBPeripheral : NSObject
@property(nonatomic) NSUUID *identifier;
@property(nonatomic) CBPeripheralState state;
@property(nonatomic, weak, nullable) id<CBPeripheralDelegate> delegate;
@property(nonatomic, nullable) NSArray<GNSFakeCBService *> *services;
@property(nonatomic, nullable) GNSFakeCBCharacteristic *notifyingCharacteristic;
@property(nonatomic, nullable) CBUUID *discoveredServiceUUID;
@property(nonatomic, nullable) NSData *writtenData;
@property(nonatomic) CBCharacteristicWriteType writeType;
@property(nonatomic) BOOL shouldCallDidReadRSSI;
- (void)readRSSI;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,83 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Central/GNSFakeCBPeripheral.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Central/GNSFakeCBCharacteristic.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Central/GNSFakeCBService.h"
@implementation GNSFakeCBPeripheral
@synthesize identifier = _identifier;
@synthesize state = _state;
@synthesize delegate = _delegate;
@synthesize services = _services;
@synthesize notifyingCharacteristic = _notifyingCharacteristic;
@synthesize discoveredServiceUUID = _discoveredServiceUUID;
@synthesize writtenData = _writtenData;
@synthesize writeType = _writeType;
@synthesize shouldCallDidReadRSSI = _shouldCallDidReadRSSI;
- (instancetype)init {
self = [super init];
if (self) {
_shouldCallDidReadRSSI = YES;
}
return self;
}
- (void)readRSSI {
if (!_shouldCallDidReadRSSI) return;
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate peripheral:(CBPeripheral *)self didReadRSSI:@(-50) error:nil];
});
}
- (void)discoverServices:(nullable NSArray<CBUUID *> *)serviceUUIDs {
self.discoveredServiceUUID = serviceUUIDs.firstObject;
}
- (void)discoverCharacteristics:(nullable NSArray<CBUUID *> *)characteristicUUIDs
forService:(CBService *)service {
}
- (void)setNotifyValue:(BOOL)enabled forCharacteristic:(CBCharacteristic *)characteristic {
((GNSFakeCBCharacteristic *)characteristic).isNotifying = enabled;
self.notifyingCharacteristic = (GNSFakeCBCharacteristic *)characteristic;
}
- (void)writeValue:(NSData *)data
forCharacteristic:(CBCharacteristic *)characteristic
type:(CBCharacteristicWriteType)type {
self.writtenData = data;
self.writeType = type;
}
- (void)readValueForCharacteristic:(CBCharacteristic *)characteristic {
[self.delegate peripheral:(CBPeripheral *)self
didUpdateValueForCharacteristic:characteristic
error:nil];
}
- (BOOL)isKindOfClass:(Class)aClass {
if (aClass == [CBPeripheral class]) {
return YES;
}
return [super isKindOfClass:aClass];
}
- (NSUInteger)maximumWriteValueLengthForType:(CBCharacteristicWriteType)type {
return 100;
}
@end
@@ -0,0 +1,27 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
@class GNSFakeCBCharacteristic;
NS_ASSUME_NONNULL_BEGIN
@interface GNSFakeCBService : NSObject
@property(nonatomic) CBUUID *UUID;
@property(nonatomic, nullable) NSArray<GNSFakeCBCharacteristic *> *characteristics;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,18 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Central/GNSFakeCBService.h"
@implementation GNSFakeCBService
@end
@@ -0,0 +1,27 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Central/GNSCentralManager+Private.h"
@interface GNSFakeCentralManager : NSObject
@property(nonatomic) CBUUID *socketServiceUUID;
@property(nonatomic) CBCentralManagerState cbManagerState;
@property(nonatomic) BOOL connectPeripheralCalled;
@property(nonatomic) BOOL cancelConnectionCalled;
@property(nonatomic) BOOL didDisconnectCalled;
- (instancetype)init;
@end
@@ -0,0 +1,41 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Central/GNSFakeCentralManager.h"
@implementation GNSFakeCentralManager
@synthesize socketServiceUUID = _socketServiceUUID;
@synthesize cbManagerState = _cbManagerState;
@synthesize connectPeripheralCalled = _connectPeripheralCalled;
@synthesize cancelConnectionCalled = _cancelConnectionCalled;
@synthesize didDisconnectCalled = _didDisconnectCalled;
- (instancetype)init {
return self;
}
- (void)connectPeripheralForPeer:(id)peer options:(nullable id)options {
self.connectPeripheralCalled = YES;
}
- (void)cancelPeripheralConnectionForPeer:(id)peer {
self.cancelConnectionCalled = YES;
}
- (void)centralPeerManagerDidDisconnect:(id)peer {
self.didDisconnectCalled = YES;
}
@end
@@ -0,0 +1,56 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
// Fake CBPeripheralManager for testing.
@interface GNSFakePeripheralManager : NSObject
@property(nonatomic, weak) id<CBPeripheralManagerDelegate> delegate;
@property(nonatomic) NSInteger state;
@property(nonatomic, copy) NSDictionary<NSString *, id> *advertisementData;
@property(nonatomic) int removeAllServicesCount;
@property(nonatomic) int addServiceCount;
@property(nonatomic) int startAdvertisingCount;
@property(nonatomic) int stopAdvertisingCount;
@property(nonatomic) int updateValueCount;
@property(nonatomic) int setDesiredConnectionLatencyCount;
@property(nonatomic) CBMutableCharacteristic *lastCharacteristicUpdated;
@property(nonatomic) NSData *lastValueUpdated;
@property(nonatomic) NSArray<CBCentral *> *lastCentralsUpdated;
@property(nonatomic, readonly) NSMutableArray<CBMutableService *> *services;
@property(nonatomic, getter=isAdvertising) BOOL advertising;
@property(nonatomic) CBATTRequest *lastRespondRequest;
@property(nonatomic) CBATTError lastRespondResult;
- (instancetype)initWithDelegate:(id<CBPeripheralManagerDelegate>)delegate
queue:(dispatch_queue_t)queue
options:(NSDictionary<NSString *, id> *)options;
- (instancetype)init;
- (void)addService:(CBMutableService *)service;
- (void)removeService:(CBMutableService *)service;
- (void)removeAllServices;
- (void)startAdvertising:(NSDictionary<NSString *, id> *)advertisementData;
- (void)stopAdvertising;
- (BOOL)updateValue:(NSData *)value
forCharacteristic:(CBMutableCharacteristic *)characteristic
onSubscribedCentrals:(NSArray<CBCentral *> *)centrals;
- (void)setDesiredConnectionLatency:(CBPeripheralManagerConnectionLatency)latency
forCentral:(CBCentral *)central;
- (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result;
- (void)didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
central:(CBCentral *)central;
@end
@@ -0,0 +1,114 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Peripheral/GNSFakePeripheralManager.h"
@implementation GNSFakePeripheralManager
- (instancetype)initWithDelegate:(id<CBPeripheralManagerDelegate>)delegate
queue:(dispatch_queue_t)queue
options:(NSDictionary<NSString *, id> *)options {
self = [super init];
if (self) {
_delegate = delegate;
_state = CBManagerStatePoweredOn;
_services = [NSMutableArray array];
}
return self;
}
- (instancetype)init {
return [self initWithDelegate:nil queue:nil options:nil];
}
- (void)addService:(CBMutableService *)service {
_addServiceCount++;
[_services addObject:service];
dispatch_async(dispatch_get_main_queue(), ^{
id<CBPeripheralManagerDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(peripheralManagerDidUpdateState:)]) {
[delegate peripheralManagerDidUpdateState:(CBPeripheralManager *)self];
}
if ([delegate respondsToSelector:@selector(peripheralManager:didAddService:error:)]) {
[delegate peripheralManager:(CBPeripheralManager *)self didAddService:service error:nil];
}
});
}
- (void)removeService:(CBMutableService *)service {
[_services removeObject:service];
}
- (void)removeAllServices {
_removeAllServicesCount++;
[_services removeAllObjects];
}
- (void)startAdvertising:(NSDictionary<NSString *, id> *)advertisementData {
_advertisementData = advertisementData;
_startAdvertisingCount++;
_advertising = YES;
id<CBPeripheralManagerDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(peripheralManagerDidStartAdvertising:error:)]) {
[delegate peripheralManagerDidStartAdvertising:(CBPeripheralManager *)self error:nil];
}
}
- (void)stopAdvertising {
_advertisementData = nil;
_stopAdvertisingCount++;
_advertising = NO;
}
- (BOOL)updateValue:(NSData *)value
forCharacteristic:(CBMutableCharacteristic *)characteristic
onSubscribedCentrals:(NSArray<CBCentral *> *)centrals {
_updateValueCount++;
_lastValueUpdated = value;
_lastCharacteristicUpdated = characteristic;
_lastCentralsUpdated = centrals;
dispatch_async(dispatch_get_main_queue(), ^{
id<CBPeripheralManagerDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(peripheralManagerIsReadyToUpdateSubscribers:)]) {
[delegate peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)self];
}
});
return YES;
}
- (void)setDesiredConnectionLatency:(CBPeripheralManagerConnectionLatency)latency
forCentral:(CBCentral *)central {
_setDesiredConnectionLatencyCount++;
}
- (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result {
_lastRespondRequest = request;
_lastRespondResult = result;
}
- (void)didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
central:(CBCentral *)central {
id<CBPeripheralManagerDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(peripheralManager:
central:didSubscribeToCharacteristic:)]) {
[delegate peripheralManager:(CBPeripheralManager *)self
central:central
didSubscribeToCharacteristic:characteristic];
}
if ([delegate respondsToSelector:@selector(peripheralManagerIsReadyToUpdateSubscribers:)]) {
[delegate peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)self];
}
}
@end
@@ -0,0 +1,29 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager.h"
NS_ASSUME_NONNULL_BEGIN
@interface GNSFakePeripheralServiceManager : GNSPeripheralServiceManager
@property(nonatomic, assign) BOOL restored;
@property(nonatomic) BOOL failAddService;
@property(nonatomic, readonly) NSSet<CBCentral *> *subscribedCentrals;
- (instancetype)initWithServiceUUID:(CBUUID *)serviceUUID;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,80 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Tests/Peripheral/GNSFakePeripheralServiceManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Sockets/Source/Peripheral/GNSPeripheralServiceManager+Private.h"
@interface GNSFakePeripheralServiceManager () {
NSMutableSet<CBCentral *> *_subscribedCentrals;
}
@property(nonatomic, nullable) GNSErrorHandler addServiceCompletion;
@end
@implementation GNSFakePeripheralServiceManager
- (instancetype)initWithServiceUUID:(CBUUID *)serviceUUID {
self = [super initWithBleServiceUUID:serviceUUID
addPairingCharacteristic:NO
shouldAcceptSocketHandler:^BOOL(GNSSocket *socket) {
return NO;
}
queue:dispatch_get_main_queue()];
if (self) {
_subscribedCentrals = [NSMutableSet set];
}
return self;
}
- (NSSet<CBCentral *> *)subscribedCentrals {
return _subscribedCentrals;
}
- (void)restoredCBService:(CBMutableService *)service {
[super restoredCBService:service];
_restored = YES;
}
- (void)addedToPeripheralManager:(GNSPeripheralManager *)peripheralManager
bleServiceAddedCompletion:(GNSErrorHandler)completion {
self.addServiceCompletion = completion;
}
- (void)didAddCBServiceWithError:(NSError *)error {
if (_failAddService) {
if (self.addServiceCompletion) {
self.addServiceCompletion([NSError errorWithDomain:@"test" code:0 userInfo:nil]);
self.addServiceCompletion = nil;
}
} else {
if (self.addServiceCompletion) {
self.addServiceCompletion(error);
self.addServiceCompletion = nil;
}
}
}
- (void)central:(CBCentral *)central
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
[super central:central didSubscribeToCharacteristic:characteristic];
[_subscribedCentrals addObject:central];
}
- (void)central:(CBCentral *)central
didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
[super central:central didUnsubscribeFromCharacteristic:characteristic];
[_subscribedCentrals removeObject:central];
}
@end
@@ -33,6 +33,9 @@ objc_library(
"GNCBLEL2CAPServerTest.m",
"GNCBLEL2CAPStreamTest.m",
"GNCBLEMediumTest.m",
"GNCFakeBLEGATTServer.m",
"GNCFakeBLEMedium.m",
"GNCFakeCBL2CAPChannel.m",
"GNCFakeCentralManager.m",
"GNCFakePeripheral.m",
"GNCFakePeripheralManager.m",
@@ -41,6 +44,8 @@ objc_library(
"GNCMBleUtilsTest.m",
"GNCMConnectionsTest.m",
"GNCMFakeConnection.mm",
"GNCPeripheralManagerTest.m",
"GNCPeripheralTest.m",
"NSData+GNCBase85Test.m",
"NSData+GNCWebSafeBase64Test.m",
],
@@ -49,7 +54,11 @@ objc_library(
"GNCBLEGATTServer+Testing.h",
"GNCBLEL2CAPClient+Testing.h",
"GNCBLEL2CAPFakeInputOutputStream.h",
"GNCBLEL2CAPServer+Testing.h",
"GNCBLEMedium+Testing.h",
"GNCFakeBLEGATTServer.h",
"GNCFakeBLEMedium.h",
"GNCFakeCBL2CAPChannel.h",
"GNCFakeCentralManager.h",
"GNCFakePeripheral.h",
"GNCFakePeripheralManager.h",
@@ -62,6 +71,7 @@ objc_library(
"//internal/platform/implementation/apple/Mediums/BLE/Sockets:Shared",
"//third_party/apple_frameworks:CoreBluetooth",
"//third_party/apple_frameworks:Foundation",
"//third_party/apple_frameworks:ObjectiveC",
"//third_party/apple_frameworks:XCTest",
],
)
@@ -16,11 +16,13 @@
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
@protocol GNCPeripheral;
NS_ASSUME_NONNULL_BEGIN
@interface GNCBLEL2CAPClient (Testing)
@interface GNCBLEL2CAPClient (Testing) <GNCPeripheralDelegate>
/**
* Initializes the L2CAP client with a provided queue and request disconnection handler.
@@ -32,8 +34,15 @@ NS_ASSUME_NONNULL_BEGIN
* @param requestDisconnectionHandler Called on a private queue with @c peripheral when the
* connection to the peripheral should be cancelled.
*/
-(instancetype)initWithQueue:(nullable dispatch_queue_t)queue
requestDisconnectionHandler:(GNCRequestDisconnectionHandler)requestDisconnectionHandler;
- (instancetype)initWithQueue:(nullable dispatch_queue_t)queue
requestDisconnectionHandler:(GNCRequestDisconnectionHandler)requestDisconnectionHandler;
/**
* Closes the L2CAP channel and disconnects the peripheral.
*
* This is only exposed for testing.
*/
- (void)closeL2CAPChannel;
@end
@@ -20,8 +20,11 @@
#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/GNCBLEL2CAPFakeInputOutputStream.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
static const NSTimeInterval kTestTimeout = 1.0;
@interface GNCBLEL2CAPClientTest : XCTestCase
@property(nonatomic) GNCFakePeripheral *fakePeripheral;
@property(nonatomic) GNCBLEL2CAPClient *l2capClient;
@@ -58,10 +61,11 @@
peripheral:self.fakePeripheral
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
XCTAssertNil(error, @"Error should be nil");
XCTAssertNil(stream, @"Stream should be nil due to testing limitation");
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:1.0];
[self waitForExpectations:@[ expectation ] timeout:kTestTimeout];
}
- (void)testOpenL2CAPChannelWithError {
@@ -79,7 +83,7 @@
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:1.0];
[self waitForExpectations:@[ expectation ] timeout:kTestTimeout];
}
- (void)testDisconnect {
@@ -94,7 +98,96 @@
[self.l2capClient disconnect];
[self waitForExpectations:@[ self.requestDisconnectionHandlerExpectation ] timeout:1.0];
[self waitForExpectations:@[ self.requestDisconnectionHandlerExpectation ] timeout:kTestTimeout];
}
- (void)testOpenL2CAPChannelSetsPeripheralDelegate {
uint16_t psm = 123;
[self.l2capClient openL2CAPChannelWithPSM:psm
peripheral:self.fakePeripheral
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error){
}];
XCTAssertEqualObjects(self.fakePeripheral.peripheralDelegate, self.l2capClient);
}
- (void)testInitWithRequestDisconnectionHandlerAndDisconnect {
XCTestExpectation *expectation =
[self expectationWithDescription:@"Disconnection handler called"];
GNCBLEL2CAPClient *client = [[GNCBLEL2CAPClient alloc]
initWithRequestDisconnectionHandler:^(id<GNCPeripheral> _Nullable peripheral) {
XCTAssertNil(peripheral);
[expectation fulfill];
}];
[client disconnect];
[self waitForExpectations:@[ expectation ] timeout:kTestTimeout];
}
- (void)testCloseL2CAPChannel {
self.requestDisconnectionHandlerExpectation =
[self expectationWithDescription:@"Request disconnection handler should be called"];
uint16_t psm = 123;
[self.l2capClient openL2CAPChannelWithPSM:psm
peripheral:self.fakePeripheral
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error){
}];
[self.l2capClient closeL2CAPChannel];
[self waitForExpectations:@[ self.requestDisconnectionHandlerExpectation ] timeout:kTestTimeout];
}
- (void)testDidOpenL2CAPChannelWithStreams {
XCTestExpectation *expectation =
[self expectationWithDescription:@"L2CAP channel opened successfully"];
uint16_t psm = 123;
GNCBLEL2CAPFakeInputOutputStream *fakeStreams =
[[GNCBLEL2CAPFakeInputOutputStream alloc] initWithBufferSize:1];
self.fakePeripheral.channelInputStream = fakeStreams.inputStream;
self.fakePeripheral.channelOutputStream = fakeStreams.outputStream;
[self.l2capClient openL2CAPChannelWithPSM:psm
peripheral:self.fakePeripheral
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
XCTAssertNil(error);
XCTAssertNotNil(stream);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:kTestTimeout];
}
- (void)testDidOpenL2CAPChannelCalledTwice {
XCTestExpectation *expectation = [self expectationWithDescription:@"L2CAP channel opened twice"];
expectation.expectedFulfillmentCount = 2;
self.requestDisconnectionHandlerExpectation =
[self expectationWithDescription:@"Request disconnection handler should be called"];
uint16_t psm = 123;
GNCBLEL2CAPFakeInputOutputStream *fakeStreams =
[[GNCBLEL2CAPFakeInputOutputStream alloc] initWithBufferSize:1];
self.fakePeripheral.channelInputStream = fakeStreams.inputStream;
self.fakePeripheral.channelOutputStream = fakeStreams.outputStream;
[self.l2capClient openL2CAPChannelWithPSM:psm
peripheral:self.fakePeripheral
completionHandler:^(GNCBLEL2CAPStream *stream, NSError *error) {
XCTAssertNil(error);
XCTAssertNotNil(stream);
[expectation fulfill];
}];
// Calling gnc_peripheral again to simulate channel opened again.
CBL2CAPChannel *channel = [[CBL2CAPChannel alloc] init];
[channel setValue:fakeStreams.inputStream forKey:@"inputStream"];
[channel setValue:fakeStreams.outputStream forKey:@"outputStream"];
[self.l2capClient peripheral:(CBPeripheral *)self.fakePeripheral
didOpenL2CAPChannel:channel
error:nil];
[self waitForExpectations:@[ expectation, self.requestDisconnectionHandlerExpectation ]
timeout:kTestTimeout];
}
@end
@@ -18,10 +18,13 @@
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEL2CAPStream.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCMBleUtils.h"
#import "internal/platform/implementation/apple/Mediums/BLE/GNCMConnection.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPFakeInputOutputStream.h"
// TODO: b/399815436 - More tests for GNCBLEL2CAPConnection.
@interface GNCBLEL2CAPConnection (Testing)
static const NSTimeInterval kTestTimeout = 1.0;
@interface GNCBLEL2CAPConnection (Testing) <GNCBLEL2CAPStreamDelegate>
- (NSData *_Nullable)extractRealDataFromData:(NSData *)data;
@property(nonatomic) NSUInteger expectedDataLength;
@property(nonatomic) dispatch_queue_t selfQueue;
@@ -33,13 +36,14 @@
@property(nonatomic) GNCBLEL2CAPConnection *connection;
@property(nonatomic) dispatch_queue_t testCallbackQueue;
@property(nonatomic) NSString *serviceID;
@property(nonatomic) NSData *serviceIDHash;
@end
@implementation GNCBLEL2CAPConnectionTest
- (void)setUp {
[super setUp];
_fakeInputOutputStream = [[GNCBLEL2CAPFakeInputOutputStream alloc] initWithBufferSize:100];
_fakeInputOutputStream = [[GNCBLEL2CAPFakeInputOutputStream alloc] initWithBufferSize:1024];
_stream = [[GNCBLEL2CAPStream alloc]
initWithClosedBlock:^{
}
@@ -47,6 +51,7 @@
outputStream:_fakeInputOutputStream.outputStream];
_testCallbackQueue = dispatch_queue_create("com.google.test.callback", DISPATCH_QUEUE_SERIAL);
_serviceID = @"testServiceID";
_serviceIDHash = GNCMServiceIDHash(_serviceID);
}
- (void)tearDown {
@@ -71,10 +76,11 @@
return connection;
}
- (NSData *)createDataWithLength:(NSUInteger)length prefix:(NSData *)prefix {
NSMutableData *data = [NSMutableData dataWithLength:length];
if (prefix) {
[data appendData:prefix];
- (NSData *)createDataWithLength:(NSUInteger)length {
NSMutableData *data = [NSMutableData data];
for (NSUInteger i = 0; i < length; ++i) {
uint8_t byte = i % 256;
[data appendBytes:&byte length:1];
}
return data;
}
@@ -90,45 +96,200 @@
return packet;
}
- (NSData *)prefixDataWithServiceIDHash:(NSData *)serviceIDHash data:(NSData *)data {
NSMutableData *combinedData = [NSMutableData dataWithCapacity:serviceIDHash.length + data.length];
[combinedData appendData:serviceIDHash];
[combinedData appendData:data];
return combinedData;
}
- (void)waitForCallbackQueue {
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for callback queue"];
dispatch_async(_testCallbackQueue, ^{
[expectation fulfill];
});
[self waitForExpectations:@[ expectation ] timeout:1];
[self waitForExpectations:@[ expectation ] timeout:kTestTimeout];
}
- (void)waitForSelfQueueWithConnection:(GNCBLEL2CAPConnection *)connection {
XCTestExpectation *expectation = [self expectationWithDescription:@"Wait for self queue"];
dispatch_async(connection.selfQueue, ^{
[expectation fulfill];
});
[self waitForExpectations:@[ expectation ] timeout:kTestTimeout];
}
#pragma mark - Tests
- (void)testExtractRealDataFromData_validData {
_connection = [self createConnectionWithIncoming:YES];
NSData *testData = [self createDataWithLength:10 prefix:nil];
NSData *prefixData = [self prefixLengthData:[self createDataWithLength:testData.length
prefix:nil]];
_connection.expectedDataLength = testData.length;
NSData *realData = [_connection extractRealDataFromData:prefixData];
NSData *testData = [self createDataWithLength:10];
NSData *prefixData = [self prefixLengthData:testData];
NSMutableData *streamData = [NSMutableData dataWithData:prefixData];
[streamData appendData:[self createDataWithLength:5]];
_connection.expectedDataLength = 0;
NSData *realData = [_connection extractRealDataFromData:streamData];
XCTAssertEqualObjects(realData, testData);
XCTAssertEqual(_connection.expectedDataLength, testData.length);
}
- (void)testExtractRealDataFromData_notEnoughData {
_connection = [self createConnectionWithIncoming:YES];
NSData *prefixData = [self createDataWithLength:2 prefix:nil];
NSData *prefixData = [self createDataWithLength:2];
NSData *realData = [_connection extractRealDataFromData:prefixData];
XCTAssertNil(realData);
}
- (void)testExtractRealDataFromData_moreThanExpectedData {
_connection = [self createConnectionWithIncoming:YES];
NSData *testData = [self createDataWithLength:10 prefix:nil];
NSData *moreData = [self createDataWithLength:5 prefix:nil];
NSData *testData = [self createDataWithLength:10];
NSData *moreData = [self createDataWithLength:5];
NSMutableData *combinedData = [NSMutableData dataWithData:testData];
[combinedData appendData:moreData];
NSData *prefixData = [self prefixLengthData:[self createDataWithLength:combinedData.length
prefix:nil]];
NSData *prefixData = [self prefixLengthData:combinedData];
_connection.expectedDataLength = testData.length;
NSData *receivedData = [NSMutableData dataWithData:prefixData];
NSData *realData = [_connection extractRealDataFromData:receivedData];
XCTAssertEqualObjects(realData, testData);
}
- (void)testSendData {
_connection = [self createConnectionWithIncoming:YES];
NSData *payload = [@"test data" dataUsingEncoding:NSUTF8StringEncoding];
XCTestExpectation *expectation = [self expectationWithDescription:@"send data completion"];
[_connection sendData:payload
completion:^(BOOL result) {
XCTAssertTrue(result);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
NSData *expectedPacket = [self prefixLengthData:[self prefixDataWithServiceIDHash:_serviceIDHash
data:payload]];
NSData *writtenData = [_fakeInputOutputStream dataSentToWatchWithMaxBytes:expectedPacket.length];
XCTAssertEqualObjects(writtenData, expectedPacket);
}
- (void)testRequestDataConnectionSuccess {
_connection = [self createConnectionWithIncoming:NO];
XCTestExpectation *expectation =
[self expectationWithDescription:@"request data connection completion"];
__block BOOL success = NO;
[_connection requestDataConnectionWithCompletion:^(BOOL result) {
success = result;
[expectation fulfill];
}];
// Verify RequestDataConnection packet is sent.
NSData *requestPacket = [self
prefixLengthData:GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommandRequestDataConnection, nil)];
NSData *writtenReqData =
[_fakeInputOutputStream dataSentToWatchWithMaxBytes:requestPacket.length];
XCTAssertEqualObjects(writtenReqData, requestPacket);
// Simulate receiving ResponseDataConnectionReady packet.
NSData *responsePacket =
[self prefixLengthData:GNCMGenerateBLEL2CAPPacket(
GNCMBLEL2CAPCommandResponseDataConnectionReady, nil)];
[_fakeInputOutputStream writeFromDevice:responsePacket];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
XCTAssertTrue(success);
// Verify Introduction packet is sent.
NSData *introPacket =
[self prefixLengthData:GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash)];
NSData *writtenIntroData =
[_fakeInputOutputStream dataSentToWatchWithMaxBytes:introPacket.length];
XCTAssertEqualObjects(writtenIntroData, introPacket);
}
- (void)testRequestDataConnectionTimeout {
_connection = [self createConnectionWithIncoming:NO];
XCTestExpectation *expectation =
[self expectationWithDescription:@"request data connection completion"];
__block BOOL success = YES;
[_connection requestDataConnectionWithCompletion:^(BOOL result) {
success = result;
[expectation fulfill];
}];
// Verify RequestDataConnection packet is sent.
NSData *requestPacket = [self
prefixLengthData:GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommandRequestDataConnection, nil)];
NSData *writtenReqData =
[_fakeInputOutputStream dataSentToWatchWithMaxBytes:requestPacket.length];
XCTAssertEqualObjects(writtenReqData, requestPacket);
// Don't send ResponseDataConnectionReady packet to simulate timeout.
[self waitForExpectationsWithTimeout:kTestTimeout * 10 handler:nil];
XCTAssertFalse(success);
}
- (void)testIncomingConnectionReceiveRequestDataConnection {
_connection = [self createConnectionWithIncoming:YES];
// Simulate receiving RequestDataConnection packet.
NSData *requestPacket = [self
prefixLengthData:GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommandRequestDataConnection, nil)];
[_fakeInputOutputStream writeFromDevice:requestPacket];
// Verify ResponseDataConnectionReady packet is sent.
NSData *responsePacket =
[self prefixLengthData:GNCMGenerateBLEL2CAPPacket(
GNCMBLEL2CAPCommandResponseDataConnectionReady, nil)];
NSData *writtenData = [_fakeInputOutputStream dataSentToWatchWithMaxBytes:responsePacket.length];
XCTAssertEqualObjects(writtenData, responsePacket);
}
- (void)testReceivePayload {
_connection = [self createConnectionWithIncoming:YES];
NSData *payload = [@"test data" dataUsingEncoding:NSUTF8StringEncoding];
XCTestExpectation *payloadExpectation = [self expectationWithDescription:@"payload handler"];
_connection.connectionHandlers = [GNCMConnectionHandlers
payloadHandler:^(NSData *data) {
XCTAssertEqualObjects(data, payload);
[payloadExpectation fulfill];
}
disconnectedHandler:^{
}];
// For incoming connection, need to receive RequestDataConnection and Introduction packet first.
NSData *requestConnectionPacket = [self
prefixLengthData:GNCMGenerateBLEL2CAPPacket(GNCMBLEL2CAPCommandRequestDataConnection, nil)];
[_fakeInputOutputStream writeFromDevice:requestConnectionPacket];
[self waitForSelfQueueWithConnection:_connection];
NSData *introPacket =
[self prefixLengthData:GNCMGenerateBLEFramesIntroductionPacket(_serviceIDHash)];
[_fakeInputOutputStream writeFromDevice:introPacket];
[self waitForSelfQueueWithConnection:_connection];
// Send payload packet.
NSData *payloadPacket = [self prefixLengthData:[self prefixDataWithServiceIDHash:_serviceIDHash
data:payload]];
[_fakeInputOutputStream writeFromDevice:payloadPacket];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
- (void)testDidDisconnectWithError {
_connection = [self createConnectionWithIncoming:YES];
XCTestExpectation *disconnectionExpectation =
[self expectationWithDescription:@"disconnection handler"];
_connection.connectionHandlers = [GNCMConnectionHandlers
payloadHandler:^(NSData *data) {
}
disconnectedHandler:^{
[disconnectionExpectation fulfill];
}];
[_connection stream:_stream didDisconnectWithError:nil];
[self waitForExpectationsWithTimeout:kTestTimeout handler:nil];
}
@end
@@ -16,11 +16,13 @@
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"
@protocol GNCPeripheralManager;
NS_ASSUME_NONNULL_BEGIN
@interface GNCBLEL2CAPServer (Testing)
@interface GNCBLEL2CAPServer (Testing) <GNCPeripheralManagerDelegate>
/**
* Creates a L2CAP server with a provided peripheral manager.
@@ -31,6 +33,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (instancetype)initWithPeripheralManager:(id<GNCPeripheralManager>)peripheralManager;
/**
* Closes the L2CAP channel.
*
* This is only exposed for testing.
*/
- (void)closeL2CAPChannel;
@end
NS_ASSUME_NONNULL_END
@@ -18,8 +18,11 @@
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPServer+Testing.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
static const NSTimeInterval kTestTimeout = 1.0;
@interface GNCBLEL2CAPServerTest : XCTestCase
@end
@@ -27,11 +30,19 @@
#pragma mark Tests
- (void)testInit {
GNCBLEL2CAPServer *l2capServer = [[GNCBLEL2CAPServer alloc] init];
XCTAssertNotNil(l2capServer);
XCTAssertNil([l2capServer valueForKey:@"_peripheralManager"]);
}
- (void)testPublishL2CAPChannelAndOpenChannelWhenStartListeningChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *psmPublishedExpectation =
[[XCTestExpectation alloc] initWithDescription:@"PSM published."];
XCTestExpectation *channelOpenedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
@@ -41,12 +52,18 @@
NSError *_Nullable error) {
XCTAssertEqual(error, nil);
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
[psmPublishedExpectation fulfill];
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error) {
XCTAssertNil(error);
XCTAssertNotNil(stream);
[channelOpenedexpectation fulfill];
}];
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:0.5];
[self waitForExpectations:@[ psmPublishedExpectation, channelOpenedexpectation ]
timeout:kTestTimeout];
XCTAssertNotNil([l2capServer valueForKey:@"l2CAPChannel"]);
XCTAssertNotNil([l2capServer valueForKey:@"l2CAPStream"]);
}
- (void)testFailedToPublishL2CAPChannel {
@@ -57,6 +74,8 @@
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *psmPublishedExpectation =
[[XCTestExpectation alloc] initWithDescription:@"PSM published with error."];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer
@@ -64,10 +83,12 @@
NSError *_Nullable error) {
XCTAssertEqual(error, fakePeripheralManager.didPublishL2CAPChannelError);
XCTAssertEqual(PSM, 0);
[psmPublishedExpectation fulfill];
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error){
}];
[self waitForExpectations:@[ psmPublishedExpectation ] timeout:kTestTimeout];
}
- (void)testPoweredOffUnpublishesChannel {
@@ -89,14 +110,17 @@
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOff];
[self waitForExpectations:@[ fakePeripheralManager.unpublishExpectation ] timeout:0.0];
[self waitForExpectations:@[ fakePeripheralManager.unpublishExpectation ] timeout:kTestTimeout];
}
- (void)testStartPeripheralManagerInitiallyOff {
- (void)testPeripheralManagerDidUpdateStatePoweredOffUnpublishesChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
fakePeripheralManager.state = CBManagerStatePoweredOn;
[(id<CBPeripheralManagerDelegate>)l2capServer
peripheralManagerDidUpdateState:(CBPeripheralManager *)fakePeripheralManager];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
@@ -108,7 +132,38 @@
NSError *_Nullable error){
}];
fakePeripheralManager.state = CBManagerStatePoweredOff;
[(id<CBPeripheralManagerDelegate>)l2capServer
peripheralManagerDidUpdateState:(CBPeripheralManager *)fakePeripheralManager];
[self waitForExpectations:@[ fakePeripheralManager.unpublishExpectation ] timeout:kTestTimeout];
}
- (void)testStartPeripheralManagerInitiallyOff {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *psmPublishedExpectation =
[[XCTestExpectation alloc] initWithDescription:@"PSM published."];
XCTestExpectation *channelOpenedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(error, nil);
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
[psmPublishedExpectation fulfill];
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error) {
[channelOpenedexpectation fulfill];
}];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[self waitForExpectations:@[ psmPublishedExpectation, channelOpenedexpectation ]
timeout:kTestTimeout];
}
- (void)testClose {
@@ -142,7 +197,133 @@
[l2capServer close];
[self waitForExpectations:@[ fakePeripheralManager.unpublishExpectation ] timeout:1.0];
[self waitForExpectations:@[ fakePeripheralManager.unpublishExpectation ] timeout:kTestTimeout];
}
- (void)testFailedToOpenL2CAPChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
fakePeripheralManager.didOpenL2CAPChannelError = [NSError errorWithDomain:@"fake"
code:0
userInfo:nil];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *channelOpenedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(error, nil);
XCTAssertEqual(PSM, fakePeripheralManager.PSM);
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error) {
XCTAssertNil(stream);
XCTAssertEqual(error, fakePeripheralManager.didOpenL2CAPChannelError);
[channelOpenedexpectation fulfill];
}];
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:kTestTimeout];
XCTAssertNil([l2capServer valueForKey:@"l2CAPChannel"]);
XCTAssertNil([l2capServer valueForKey:@"l2CAPStream"]);
}
- (void)testPeripheralManagerDidUnpublishL2CAPChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error){
}];
[(id<CBPeripheralManagerDelegate>)l2capServer
peripheralManager:(CBPeripheralManager *)fakePeripheralManager
didUnpublishL2CAPChannel:l2capServer.PSM
error:[NSError errorWithDomain:@"fake" code:0 userInfo:nil]];
XCTAssertEqual(l2capServer.PSM, 0);
}
- (void)testCloseL2CAPChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *channelOpenedexpectation =
[[XCTestExpectation alloc] initWithDescription:@"Channel opened."];
[fakePeripheralManager simulatePeripheralManagerDidUpdateState:CBManagerStatePoweredOn];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error) {
[channelOpenedexpectation fulfill];
}];
[self waitForExpectations:@[ channelOpenedexpectation ] timeout:kTestTimeout];
[l2capServer closeL2CAPChannel];
XCTAssertNil([l2capServer valueForKey:@"l2CAPChannel"]);
XCTAssertNil([l2capServer valueForKey:@"l2CAPStream"]);
}
- (void)testPeripheralManagerDidPublishL2CAPChannel {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"completion called"];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(PSM, 1);
XCTAssertNil(error);
[expectation fulfill];
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error){
}];
[(id<CBPeripheralManagerDelegate>)l2capServer
peripheralManager:(CBPeripheralManager *)fakePeripheralManager
didPublishL2CAPChannel:1
error:nil];
[self waitForExpectations:@[ expectation ] timeout:kTestTimeout];
XCTAssertEqual(l2capServer.PSM, 1);
}
- (void)testPeripheralManagerDidPublishL2CAPChannelWithError {
GNCFakePeripheralManager *fakePeripheralManager = [[GNCFakePeripheralManager alloc] init];
GNCBLEL2CAPServer *l2capServer =
[[GNCBLEL2CAPServer alloc] initWithPeripheralManager:fakePeripheralManager
queue:dispatch_get_main_queue()];
XCTestExpectation *expectation =
[[XCTestExpectation alloc] initWithDescription:@"completion called"];
NSError *publishError = [NSError errorWithDomain:@"test" code:0 userInfo:nil];
[l2capServer
startListeningChannelWithPSMPublishedCompletionHandler:^(uint16_t PSM,
NSError *_Nullable error) {
XCTAssertEqual(PSM, 0);
XCTAssertEqualObjects(error, publishError);
[expectation fulfill];
}
channelOpenedCompletionHandler:^(GNCBLEL2CAPStream *_Nullable stream,
NSError *_Nullable error){
}];
[(id<CBPeripheralManagerDelegate>)l2capServer
peripheralManager:(CBPeripheralManager *)fakePeripheralManager
didPublishL2CAPChannel:0
error:publishError];
[self waitForExpectations:@[ expectation ] timeout:kTestTimeout];
XCTAssertEqual(l2capServer.PSM, 0);
}
@end
@@ -385,4 +385,20 @@ static NSString *const kServiceUUID = @"0000FEF3-0000-1000-8000-00805F9B34FB";
[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,29 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEGATTServer.h"
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface GNCFakeBLEGATTServer : GNCBLEGATTServer
// Add properties to control fake behavior if needed.
@property(nonatomic, nullable) NSError *stopError;
@property(nonatomic) BOOL isStopped;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,30 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEGATTServer.h"
NS_ASSUME_NONNULL_BEGIN
@implementation GNCFakeBLEGATTServer
- (void)stopWithCompletionHandler:(nullable void (^)(NSError *_Nullable))completionHandler {
self.isStopped = YES;
if (completionHandler) {
completionHandler(self.stopError);
}
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,53 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/GNCBLEMedium.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEGATTServer.h"
NS_ASSUME_NONNULL_BEGIN
@interface GNCFakeBLEMedium : GNCBLEMedium
// Properties to control fake behavior.
@property(nonatomic, nullable) NSError *startAdvertisingError;
@property(nonatomic, nullable) NSError *stopAdvertisingError;
@property(nonatomic, nullable) NSError *startScanningError;
@property(nonatomic, nullable) NSError *stopScanningError;
@property(nonatomic, nullable) NSError *resumeScanningError;
@property(nonatomic, nullable) NSError *startGATTServerError;
@property(nonatomic, nullable) NSError *connectToGATTServerError;
@property(nonatomic, nullable) NSError *openServerSocketError;
@property(nonatomic, nullable) NSError *openL2CAPServerSocketError;
@property(nonatomic, nullable) NSError *openL2CAPChannelError;
@property(nonatomic, nullable) GNCBLEGATTServer *fakeGATTServer;
@property(nonatomic, nullable) GNCBLEGATTClient *fakeGATTClient;
@property(nonatomic, nullable) GNCBLEL2CAPStream *fakeL2CAPStream;
@property(nonatomic) uint16_t fakePSM;
@property(nonatomic, nullable) id<GNCPeripheral> lastConnectedPeripheral;
@property(nonatomic, nullable) GNCGATTDisconnectionHandler lastDisconnectionHandler;
@property(nonatomic, nullable) GNCAdvertisementFoundHandler advertisementFoundHandler;
/** When NO, the completion for opening an L2CAP channel will not be invoked. Default is YES. */
@property(nonatomic) BOOL openL2CAPChannelShouldComplete;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,142 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeBLEMedium.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.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/GNCBLEMedium.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
NS_ASSUME_NONNULL_BEGIN
@implementation GNCFakeBLEMedium
- (instancetype)init {
self = [super init];
if (self) {
_openL2CAPChannelShouldComplete = YES;
}
return self;
}
- (void)startAdvertisingData:(NSDictionary<CBUUID *, NSData *> *)advertisementData
completionHandler:(nullable GNCStartAdvertisingCompletionHandler)completionHandler {
if (completionHandler) {
completionHandler(self.startAdvertisingError);
}
}
- (void)stopAdvertisingWithCompletionHandler:
(nullable GNCStopAdvertisingCompletionHandler)completionHandler {
if (completionHandler) {
completionHandler(self.stopAdvertisingError);
}
}
- (void)startScanningForService:(CBUUID *)serviceUUID
advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler
completionHandler:(nullable GNCStartScanningCompletionHandler)completionHandler {
self.advertisementFoundHandler = advertisementFoundHandler;
if (completionHandler) {
completionHandler(self.startScanningError);
}
}
- (void)startScanningForMultipleServices:(NSArray<CBUUID *> *)serviceUUIDs
advertisementFoundHandler:(GNCAdvertisementFoundHandler)advertisementFoundHandler
completionHandler:
(nullable GNCStartScanningCompletionHandler)completionHandler {
self.advertisementFoundHandler = advertisementFoundHandler;
if (completionHandler) {
completionHandler(self.startScanningError);
}
}
- (void)stopScanningWithCompletionHandler:
(nullable GNCStopScanningCompletionHandler)completionHandler {
if (completionHandler) {
completionHandler(self.stopScanningError);
}
}
- (void)resumeMediumScanning:(nullable GNCStartScanningCompletionHandler)completionHandler {
if (completionHandler) {
completionHandler(self.resumeScanningError);
}
}
- (void)startGATTServerWithCompletionHandler:
(nullable GNCGATTServerCompletionHandler)completionHandler {
if (completionHandler) {
completionHandler(self.fakeGATTServer, self.startGATTServerError);
}
}
- (void)connectToGATTServerForPeripheral:(id<GNCPeripheral>)peripheral
disconnectionHandler:(nullable GNCGATTDisconnectionHandler)disconnectionHandler
completionHandler:
(nullable GNCGATTConnectionCompletionHandler)completionHandler {
self.lastConnectedPeripheral = peripheral;
self.lastDisconnectionHandler = disconnectionHandler;
if (completionHandler) {
if (!self.connectToGATTServerError) {
if (!self.fakeGATTClient) {
// Create a default fake client if one isn't provided.
self.fakeGATTClient = [[GNCBLEGATTClient alloc] initWithPeripheral:peripheral
requestDisconnectionHandler:^(id<GNCPeripheral> p){
// Do nothing in fake.
}];
}
completionHandler(self.fakeGATTClient, nil);
} else {
completionHandler(nil, self.connectToGATTServerError);
}
}
}
- (void)openL2CAPServerWithPSMPublishedCompletionHandler:
(GNCOpenL2CAPServerPSMPublishedCompletionHandler)psmPublishedCompletionHandler
channelOpenedCompletionHandler:
(GNCOpenL2CAPServerChannelOpendCompletionHandler)
channelOpenedCompletionHandler
peripheralManager:
(nullable id<GNCPeripheralManager>)peripheralManager {
if (psmPublishedCompletionHandler) {
psmPublishedCompletionHandler(self.fakePSM, self.openL2CAPServerSocketError);
}
// In the fake, we don't have a real channel opened event.
}
- (void)openL2CAPChannelWithPSM:(CBL2CAPPSM)PSM
peripheral:(id<GNCPeripheral>)peripheral
completionHandler:(nullable GNCOpenL2CAPStreamCompletionHandler)completionHandler {
self.lastConnectedPeripheral = peripheral;
if (self.openL2CAPChannelShouldComplete && completionHandler) {
completionHandler(self.fakeL2CAPStream, self.openL2CAPChannelError);
}
}
- (BOOL)supportsExtendedAdvertisements {
return NO;
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,36 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* A fake CBL2CAPChannel for testing.
*/
@interface GNCFakeCBL2CAPChannel : NSObject
/** The input stream for the L2CAP channel. */
@property(nonatomic, nullable) NSInputStream *inputStream;
/** The output stream for the L2CAP channel. */
@property(nonatomic, nullable) NSOutputStream *outputStream;
/** 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;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,18 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeCBL2CAPChannel.h"
@implementation GNCFakeCBL2CAPChannel
@end
@@ -24,6 +24,7 @@
@implementation GNCFakeCentralManager {
CBManagerState _state;
NSArray<CBUUID *> *_serviceUUIDs;
NSDictionary<NSUUID *, GNCFakePeripheral *> *_peripherals;
}
@synthesize centralDelegate;
@@ -32,6 +33,12 @@
self = [super init];
if (self) {
_state = CBManagerStateUnknown;
// Add a fake peripheral
NSUUID *identifier =
[[NSUUID alloc] initWithUUIDString:@"11111111-1111-1111-1111-111111111111"];
_peripherals = [NSMutableDictionary
dictionaryWithObject:[[GNCFakePeripheral alloc] initWithIdentifier:identifier]
forKey:identifier];
}
return self;
}
@@ -63,6 +70,16 @@
- (void)stopScan {
}
- (NSArray<CBPeripheral *> *)retrievePeripheralsWithIdentifiers:(NSArray<NSUUID *> *)identifiers {
NSMutableArray<CBPeripheral *> *peripherals = [NSMutableArray array];
for (NSUUID *identifier in identifiers) {
if (_peripherals[identifier]) {
[peripherals addObject:(CBPeripheral *)_peripherals[identifier]];
}
}
return peripherals;
}
#pragma mark - Testing Helpers
- (NSArray<CBUUID *> *)serviceUUIDs {
@@ -22,6 +22,12 @@ NS_ASSUME_NONNULL_BEGIN
/** A fake implementation of @c GNCPeripheral to inject for testing. */
@interface GNCFakePeripheral : NSObject <GNCPeripheral>
/** Initializes the fake peripheral with the given identifier. */
- (instancetype)initWithIdentifier:(NSUUID *)identifier;
/** The peripheral's delegate. */
@property(nonatomic, nullable, readwrite) id<CBPeripheralDelegate> delegate;
/**
* Similates a @c discoverServices: error.
*
@@ -53,7 +59,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, nullable, readwrite) NSError *readValueForCharacteristicError;
/** Similates a delay in all delegate calls by the specified amount. */
@property(nonatomic, readwrite) NSTimeInterval delegateDelay;
@property(readwrite) NSTimeInterval delegateDelay;
/**
* Similates a @c openL2CAPChannelWithPSM: error.
@@ -64,6 +70,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic, nullable, readwrite) NSError *openL2CAPChannelError;
/** Fake input stream to inject into CBL2CAPChannel. */
@property(nonatomic, nullable) NSInputStream *channelInputStream;
/** Fake output stream to inject into CBL2CAPChannel. */
@property(nonatomic, nullable) NSOutputStream *channelOutputStream;
@end
NS_ASSUME_NONNULL_END
@@ -41,8 +41,6 @@ NS_ASSUME_NONNULL_BEGIN
NSUUID *_identifier;
}
@synthesize peripheralDelegate;
- (instancetype)init {
self = [super init];
if (self) {
@@ -52,6 +50,23 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
- (instancetype)initWithIdentifier:(NSUUID *)identifier {
self = [super init];
if (self) {
_services = [[NSMutableArray alloc] init];
_identifier = identifier;
}
return self;
}
- (void)setPeripheralDelegate:(nullable id<GNCPeripheralDelegate>)peripheralDelegate {
self.delegate = peripheralDelegate;
}
- (nullable id<GNCPeripheralDelegate>)peripheralDelegate {
return (id<GNCPeripheralDelegate>)self.delegate;
}
- (NSUUID *)identifier {
return _identifier;
}
@@ -68,7 +83,7 @@ NS_ASSUME_NONNULL_BEGIN
}
}
[peripheralDelegate gnc_peripheral:self didDiscoverServices:_discoverServicesError];
[self.peripheralDelegate gnc_peripheral:self didDiscoverServices:_discoverServicesError];
}];
}
@@ -90,7 +105,7 @@ NS_ASSUME_NONNULL_BEGIN
service.characteristics = characteristics;
}
[peripheralDelegate gnc_peripheral:self
[self.peripheralDelegate gnc_peripheral:self
didDiscoverCharacteristicsForService:service
error:_discoverCharacteristicsForServiceError];
}];
@@ -102,30 +117,39 @@ NS_ASSUME_NONNULL_BEGIN
characteristic.value = [NSData data];
}
[peripheralDelegate gnc_peripheral:self
didUpdateValueForCharacteristic:characteristic
error:_readValueForCharacteristicError];
[self.peripheralDelegate gnc_peripheral:self
didUpdateValueForCharacteristic:characteristic
error:_readValueForCharacteristicError];
}];
}
- (void)openL2CAPChannel:(CBL2CAPPSM)PSM {
[self delayDelegateUsingBlock:^() {
CBL2CAPChannel *channel = [[CBL2CAPChannel alloc] init];
if (self.channelInputStream) {
[channel setValue:self.channelInputStream forKey:@"inputStream"];
}
if (self.channelOutputStream) {
[channel setValue:self.channelOutputStream forKey:@"outputStream"];
}
if (_openL2CAPChannelError) {
[peripheralDelegate gnc_peripheral:self
didOpenL2CAPChannel:channel
error:_openL2CAPChannelError];
[self.peripheralDelegate peripheral:(CBPeripheral *)self
didOpenL2CAPChannel:channel
error:_openL2CAPChannelError];
} else {
[peripheralDelegate gnc_peripheral:self didOpenL2CAPChannel:channel error:nil];
[self.peripheralDelegate peripheral:(CBPeripheral *)self
didOpenL2CAPChannel:channel
error:nil];
}
}];
}
- (void)delayDelegateUsingBlock:(void (^)())block {
if (_delegateDelay <= 0) {
NSTimeInterval delegateDelay = self.delegateDelay;
if (delegateDelay <= 0) {
block();
} else {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, _delegateDelay * NSEC_PER_SEC),
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delegateDelay * NSEC_PER_SEC),
dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
block();
});
@@ -38,6 +38,9 @@ NS_ASSUME_NONNULL_BEGIN
/** The PSM of the L2CAP channel. */
@property(nonatomic) CBL2CAPPSM PSM;
/** The fake peripheral manager state. */
@property(nonatomic, readwrite) CBManagerState state;
/** Expectation fulfilled when peripheral responds to a request with success. */
@property(nonatomic, readonly) XCTestExpectation *respondToRequestSuccessExpectation;
@@ -83,6 +86,15 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic, nullable, readwrite) NSError *didUnPublishL2CAPChannelError;
/**
* Similates a @c openL2CAPChannel: error.
*
* Setting this error to a value other than @c nil will simulate a failure when calling @c
* openL2CAPChannel: and will call the @c gnc_peripheralManager:didOpenL2CAPChannel:error:
* delegate method with the provided error.
*/
@property(nonatomic, nullable, readwrite) NSError *didOpenL2CAPChannelError;
/**
* Simulates a state update event.
*
@@ -93,6 +105,15 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)simulatePeripheralManagerDidUpdateState:(CBManagerState)fakeState;
/**
* Simulates a read request event.
*
* Creates a fake read request for the given service and characteristic UUIDs and calls the
* @c gnc_peripheralManager:didReceiveReadRequest: delegate method.
*
* @param service The service UUID of the characteristic to read from.
* @param characteristic The characteristic UUID to read from.
*/
- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service
characteristic:(CBUUID *)characteristic;
@@ -19,6 +19,8 @@
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCBLEL2CAPFakeInputOutputStream.h"
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakeCBL2CAPChannel.h"
@interface CBCharacteristic ()
@@ -59,13 +61,12 @@
static const uint16_t kPSM = 192;
@implementation GNCFakePeripheralManager {
CBManagerState _state;
BOOL _isAdvertising;
NSDictionary<NSString *, id> *_advertisementData;
NSMutableArray<CBService *> *_services;
}
@synthesize peripheralDelegate;
@synthesize peripheralDelegate = _peripheralDelegate;
#pragma mark Public
@@ -87,10 +88,6 @@ static const uint16_t kPSM = 192;
return self;
}
- (CBManagerState)state {
return _state;
}
- (BOOL)isAdvertising {
return _isAdvertising;
}
@@ -99,7 +96,7 @@ static const uint16_t kPSM = 192;
if (!_didAddServiceError) {
[_services addObject:service];
}
[peripheralDelegate gnc_peripheralManager:self didAddService:service error:_didAddServiceError];
[_peripheralDelegate gnc_peripheralManager:self didAddService:service error:_didAddServiceError];
}
- (void)removeService:(CBMutableService *)service {
@@ -113,8 +110,8 @@ static const uint16_t kPSM = 192;
- (void)startAdvertising:(NSDictionary<NSString *, id> *)advertisementData {
_isAdvertising = _didStartAdvertisingError == nil;
_advertisementData = advertisementData;
[peripheralDelegate gnc_peripheralManagerDidStartAdvertising:self
error:_didStartAdvertisingError];
[_peripheralDelegate gnc_peripheralManagerDidStartAdvertising:self
error:_didStartAdvertisingError];
}
- (void)respondToRequest:(CBATTRequest *)request withResult:(CBATTError)result {
@@ -134,20 +131,25 @@ static const uint16_t kPSM = 192;
if (!_didPublishL2CAPChannelError) {
localPSM = _PSM;
}
[peripheralDelegate gnc_peripheralManager:self
didPublishL2CAPChannel:localPSM
error:_didPublishL2CAPChannelError];
[_peripheralDelegate gnc_peripheralManager:self
didPublishL2CAPChannel:localPSM
error:_didPublishL2CAPChannelError];
if (!_didPublishL2CAPChannelError) {
[peripheralDelegate gnc_peripheralManager:self
didOpenL2CAPChannel:[[CBL2CAPChannel alloc] init]
error:nil];
GNCBLEL2CAPFakeInputOutputStream *fakeStream =
[[GNCBLEL2CAPFakeInputOutputStream alloc] initWithBufferSize:1024];
GNCFakeCBL2CAPChannel *fakeChannel = [[GNCFakeCBL2CAPChannel alloc] init];
fakeChannel.inputStream = fakeStream.inputStream;
fakeChannel.outputStream = fakeStream.outputStream;
[_peripheralDelegate gnc_peripheralManager:self
didOpenL2CAPChannel:(CBL2CAPChannel *)fakeChannel
error:_didOpenL2CAPChannelError];
}
}
- (void)unpublishL2CAPChannel:(CBL2CAPPSM)PSM {
[peripheralDelegate gnc_peripheralManager:self
didUnpublishL2CAPChannel:_PSM
error:_didUnPublishL2CAPChannelError];
[_peripheralDelegate gnc_peripheralManager:self
didUnpublishL2CAPChannel:_PSM
error:_didUnPublishL2CAPChannelError];
[_unpublishExpectation fulfill];
}
@@ -163,14 +165,22 @@ static const uint16_t kPSM = 192;
- (void)simulatePeripheralManagerDidUpdateState:(CBManagerState)fakeState {
_state = fakeState;
[peripheralDelegate gnc_peripheralManagerDidUpdateState:self];
[_peripheralDelegate gnc_peripheralManagerDidUpdateState:self];
}
- (void)simulatePeripheralManagerDidReceiveReadRequestForService:(CBUUID *)service
characteristic:(CBUUID *)characteristic {
CBATTRequest *request = [[CBATTRequest alloc] initWithService:service
characteristic:characteristic];
[peripheralDelegate gnc_peripheralManager:self didReceiveReadRequest:request];
[_peripheralDelegate gnc_peripheralManager:self didReceiveReadRequest:request];
}
- (void)setDelegate:(id<CBPeripheralManagerDelegate>)delegate {
self.peripheralDelegate = (id<GNCPeripheralManagerDelegate>)delegate;
}
- (id<CBPeripheralManagerDelegate>)delegate {
return (id<CBPeripheralManagerDelegate>)self.peripheralDelegate;
}
@end
@@ -0,0 +1,105 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheralManager.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheralManager.h"
/** Fake delegate of @c GNCPeripheralManagerDelegate */
@interface GNCFakePeripheralManagerDelegate : NSObject <GNCPeripheralManagerDelegate>
@end
@implementation GNCFakePeripheralManagerDelegate
// Add dummy implementations for protocol methods
- (void)gnc_peripheralManagerDidUpdateState:(id<GNCPeripheralManager>)peripheral {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didAddService:(CBService *)service
error:(nullable NSError *)error {
}
- (void)gnc_peripheralManagerDidStartAdvertising:(id<GNCPeripheralManager>)peripheral
error:(nullable NSError *)error {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didReceiveReadRequest:(CBATTRequest *)request {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didReceiveWriteRequests:(NSArray<CBATTRequest *> *)requests {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
isReadyToSendWriteWithoutResponse:(id)sender {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didSubscribeToCharacteristic:(CBCharacteristic *)characteristic {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didPublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(nullable NSError *)error {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didUnpublishL2CAPChannel:(CBL2CAPPSM)PSM
error:(NSError *)error {
}
- (void)gnc_peripheralManager:(id<GNCPeripheralManager>)peripheral
didOpenL2CAPChannel:(nullable CBL2CAPChannel *)channel
error:(nullable NSError *)error {
}
// CBPeripheralManagerDelegate methods
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
}
@end
@interface GNCPeripheralManagerTest : XCTestCase
@end
@implementation GNCPeripheralManagerTest
- (void)testSetPeripheralDelegate_SetsDelegate {
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
id<GNCPeripheralManagerDelegate> delegate = [[GNCFakePeripheralManagerDelegate alloc] init];
fakeManager.peripheralDelegate = delegate;
XCTAssertEqual(fakeManager.peripheralDelegate, delegate);
}
- (void)testPeripheralDelegate_GetsDelegate {
GNCFakePeripheralManager *fakeManager = [[GNCFakePeripheralManager alloc] init];
id<GNCPeripheralManagerDelegate> delegate = [[GNCFakePeripheralManagerDelegate alloc] init];
// Set the underlying delegate property in the fake.
fakeManager.peripheralDelegate = delegate;
// Get IMP of the category method from CBPeripheralManager.
Method getMethod =
class_getInstanceMethod([CBPeripheralManager class], @selector(peripheralDelegate));
id (*getImp)(id, SEL) = (id (*)(id, SEL))method_getImplementation(getMethod);
// Call the implementation directly on the fake.
id result = getImp(fakeManager, @selector(peripheralDelegate));
XCTAssertEqual(result, delegate);
}
@end
@@ -0,0 +1,68 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/BLE/GNCPeripheral.h"
#import <CoreBluetooth/CoreBluetooth.h>
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import "internal/platform/implementation/apple/Mediums/BLE/Tests/GNCFakePeripheral.h"
/** Fake delegate of @c GNCPeripheralDelegate */
@interface GNCFakePeripheralDelegate : NSObject <GNCPeripheralDelegate>
@end
@implementation GNCFakePeripheralDelegate
@end
@interface GNCPeripheralTest : XCTestCase
@end
@implementation GNCPeripheralTest
- (void)testSetPeripheralDelegate_SetsDelegate {
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
id<GNCPeripheralDelegate> delegate = [[GNCFakePeripheralDelegate alloc] init];
// Get IMP of the category method.
Method setMethod =
class_getInstanceMethod([CBPeripheral class], @selector(setPeripheralDelegate:));
void (*setImp)(id, SEL, id) = (void (*)(id, SEL, id))method_getImplementation(setMethod);
// Call the implementation directly on the fake.
setImp(fakePeripheral, @selector(setPeripheralDelegate:), delegate);
// Verify that the underlying delegate setter was called.
XCTAssertEqual(fakePeripheral.delegate, delegate);
}
- (void)testPeripheralDelegate_GetsDelegate {
GNCFakePeripheral *fakePeripheral = [[GNCFakePeripheral alloc] init];
id<GNCPeripheralDelegate> delegate = [[GNCFakePeripheralDelegate alloc] init];
// Set the underlying delegate property.
fakePeripheral.delegate = delegate;
// Get IMP of the category method.
Method getMethod = class_getInstanceMethod([CBPeripheral class], @selector(peripheralDelegate));
id (*getImp)(id, SEL) = (id (*)(id, SEL))method_getImplementation(getMethod);
// Call the implementation directly and check the result.
id result = getImp(fakePeripheral, @selector(peripheralDelegate));
XCTAssertEqual(result, delegate);
}
@end
@@ -17,7 +17,6 @@ licenses(["notice"])
package(default_visibility = [
"//connections:__subpackages__",
"//googlemac/iPhone/Nearby:__subpackages__",
"//internal/platform/implementation/apple:__subpackages__",
])
@@ -0,0 +1,58 @@
# 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.
load("@rules_apple//apple:ios.bzl", "ios_unit_test")
load("@rules_cc//cc:objc_library.bzl", "objc_library")
load("//third_party/nearby:minimum_os.bzl", "IOS_MINIMUM_OS")
package(default_visibility = ["//:__subpackages__"])
objc_library(
name = "Fake",
testonly = True,
srcs = ["CLLocationManagerFake.m"],
hdrs = ["CLLocationManagerFake.h"],
deps = [
"//third_party/apple_frameworks:CoreLocation",
"//third_party/apple_frameworks:Foundation",
],
)
objc_library(
name = "CLLocationManagerDelegateFake",
testonly = True,
srcs = ["CLLocationManagerDelegateFake.m"],
hdrs = ["CLLocationManagerDelegateFake.h"],
deps = ["//third_party/apple_frameworks:CoreLocation"],
)
objc_library(
name = "TestsLib",
testonly = True,
srcs = ["CLLocationManagerFakeTests.m"],
deps = [
":CLLocationManagerDelegateFake",
":Fake",
"//third_party/apple_frameworks:CoreLocation",
"//third_party/apple_frameworks:Foundation",
"//third_party/apple_frameworks:XCTest",
],
)
ios_unit_test(
name = "Tests",
minimum_os_version = IOS_MINIMUM_OS,
runner = "//testing/utp/ios:IOS_LATEST",
deps = [":TestsLib"],
)
@@ -0,0 +1,21 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreLocation/CoreLocation.h>
@interface CLLocationManagerDelegateFake : NSObject <CLLocationManagerDelegate>
@property(nonatomic, copy, nonnull) void (^authorizationUpdateBlock)(CLAuthorizationStatus API_AVAILABLE(ios(14.0)));
@end
@@ -0,0 +1,25 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerDelegateFake.h"
@implementation CLLocationManagerDelegateFake
- (void)locationManagerDidChangeAuthorization:(CLLocationManager *)manager {
if (@available(iOS 14.0, *)) {
self.authorizationUpdateBlock(manager.authorizationStatus);
}
}
@end
@@ -0,0 +1,40 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreLocation/CLLocation.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** Fake object of CLLocationManager. */
__attribute__((objc_subclassing_restricted)) // NO_LINT
@interface CLLocationManagerFake : CLLocationManager
/// Returns the current authorization status.
- (CLAuthorizationStatus)authorizationStatus;
/// Request permission to access location data when the app is in use.
- (void)requestWhenInUseAuthorization;
/// Request permission to always access location data.
- (void)requestAlwaysAuthorization;
/// Sets the authorization status for location permission.
- (void)setAuthorizationStatus:(CLAuthorizationStatus)authorizationStatus;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,66 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h"
#import <CoreLocation/CLLocationManager.h>
#import <CoreLocation/CLLocationManagerDelegate.h>
#import <Foundation/Foundation.h>
@implementation CLLocationManagerFake {
CLAuthorizationStatus _authorizationStatus;
id<CLLocationManagerDelegate> _delegate;
}
- (instancetype)init {
self = [super init];
if (self) {
_authorizationStatus = kCLAuthorizationStatusNotDetermined;
}
return self;
}
- (void)setDelegate:(id<CLLocationManagerDelegate>)newValue {
_delegate = newValue;
}
- (id<CLLocationManagerDelegate>)delegate {
return _delegate;
}
- (CLAuthorizationStatus)authorizationStatus {
if (@available(iOS 14.0, *)) {
return _authorizationStatus;
}
return 0;
}
- (void)requestWhenInUseAuthorization {
if (@available(iOS 14.0, *)) {
[self.delegate locationManagerDidChangeAuthorization:self];
}
}
- (void)requestAlwaysAuthorization {
if (@available(iOS 14.0, *)) {
[self.delegate locationManagerDidChangeAuthorization:self];
}
}
- (void)setAuthorizationStatus:(CLAuthorizationStatus)authorizationStatus {
_authorizationStatus = authorizationStatus;
}
@end

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