mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
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:
+25
-1
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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",
|
||||
|
||||
@@ -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__",
|
||||
|
||||
@@ -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
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
+6
@@ -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
|
||||
|
||||
|
||||
+15
-28
@@ -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 {
|
||||
|
||||
+4
-1
@@ -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
|
||||
|
||||
+15
-3
@@ -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.
|
||||
|
||||
+34
-1
@@ -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
|
||||
|
||||
+31
-11
@@ -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];
|
||||
|
||||
+27
@@ -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
|
||||
+18
@@ -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
|
||||
+28
@@ -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 {
|
||||
|
||||
-2
@@ -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,
|
||||
|
||||
+681
-265
File diff suppressed because it is too large
Load Diff
+26
@@ -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
|
||||
+18
@@ -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
|
||||
+36
@@ -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
|
||||
+83
@@ -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
|
||||
+27
@@ -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
|
||||
+18
@@ -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
|
||||
+27
@@ -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
|
||||
+41
@@ -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
|
||||
+56
@@ -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
|
||||
+114
@@ -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
|
||||
+29
@@ -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
|
||||
+80
@@ -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
|
||||
+496
-650
File diff suppressed because it is too large
Load Diff
+810
-549
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
+12
-3
@@ -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
|
||||
|
||||
+179
-18
@@ -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
|
||||
|
||||
+10
-1
@@ -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;
|
||||
|
||||
|
||||
+30
-20
@@ -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__",
|
||||
])
|
||||
|
||||
|
||||
+58
@@ -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"],
|
||||
)
|
||||
+21
@@ -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
|
||||
+25
@@ -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
|
||||
+40
@@ -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
|
||||
+66
@@ -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
Reference in New Issue
Block a user