mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
nearby sdk refactor
PiperOrigin-RevId: 425434260
This commit is contained in:
@@ -1,211 +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.
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "internal",
|
||||
srcs = [
|
||||
"base_endpoint_channel.cc",
|
||||
"base_pcp_handler.cc",
|
||||
"ble_advertisement.cc",
|
||||
"ble_endpoint_channel.cc",
|
||||
"bluetooth_bwu_handler.cc",
|
||||
"bluetooth_device_name.cc",
|
||||
"bluetooth_endpoint_channel.cc",
|
||||
"bwu_manager.cc",
|
||||
"client_proxy.cc",
|
||||
"encryption_runner.cc",
|
||||
"endpoint_channel_manager.cc",
|
||||
"endpoint_manager.cc",
|
||||
"injected_bluetooth_device_store.cc",
|
||||
"internal_payload.cc",
|
||||
"internal_payload_factory.cc",
|
||||
"offline_frames.cc",
|
||||
"offline_frames_validator.cc",
|
||||
"offline_service_controller.cc",
|
||||
"p2p_cluster_pcp_handler.cc",
|
||||
"p2p_point_to_point_pcp_handler.cc",
|
||||
"p2p_star_pcp_handler.cc",
|
||||
"payload_manager.cc",
|
||||
"pcp_manager.cc",
|
||||
"service_controller_router.cc",
|
||||
"webrtc_bwu_handler.cc",
|
||||
"webrtc_endpoint_channel.cc",
|
||||
"wifi_lan_bwu_handler.cc",
|
||||
"wifi_lan_endpoint_channel.cc",
|
||||
"wifi_lan_service_info.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"base_bwu_handler.h",
|
||||
"base_endpoint_channel.h",
|
||||
"base_pcp_handler.h",
|
||||
"ble_advertisement.h",
|
||||
"ble_endpoint_channel.h",
|
||||
"bluetooth_bwu_handler.h",
|
||||
"bluetooth_device_name.h",
|
||||
"bluetooth_endpoint_channel.h",
|
||||
"bwu_handler.h",
|
||||
"bwu_manager.h",
|
||||
"client_proxy.h",
|
||||
"encryption_runner.h",
|
||||
"endpoint_channel.h",
|
||||
"endpoint_channel_manager.h",
|
||||
"endpoint_manager.h",
|
||||
"injected_bluetooth_device_store.h",
|
||||
"internal_payload.h",
|
||||
"internal_payload_factory.h",
|
||||
"offline_frames.h",
|
||||
"offline_frames_validator.h",
|
||||
"offline_service_controller.h",
|
||||
"p2p_cluster_pcp_handler.h",
|
||||
"p2p_point_to_point_pcp_handler.h",
|
||||
"p2p_star_pcp_handler.h",
|
||||
"payload_manager.h",
|
||||
"pcp.h",
|
||||
"pcp_handler.h",
|
||||
"pcp_manager.h",
|
||||
"service_controller.h",
|
||||
"service_controller_router.h",
|
||||
"webrtc_bwu_handler.h",
|
||||
"webrtc_endpoint_channel.h",
|
||||
"wifi_lan_bwu_handler.h",
|
||||
"wifi_lan_endpoint_channel.h",
|
||||
"wifi_lan_service_info.h",
|
||||
],
|
||||
copts = ["-DCORE_ADAPTER_DLL"],
|
||||
visibility = [
|
||||
"//cpp/core:__pkg__",
|
||||
"//cpp/core/internal/fuzzers:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
":message_lite",
|
||||
"//connections/implementation/proto:offline_wire_formats_portable_proto",
|
||||
"//cpp/core:core_types",
|
||||
"//cpp/core/internal/mediums",
|
||||
"//cpp/core/internal/mediums:utils",
|
||||
"//cpp/core/internal/mediums/webrtc",
|
||||
"//cpp/platform/api:comm",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/base:cancellation_flag",
|
||||
"//cpp/platform/base:error_code_recorder",
|
||||
"//cpp/platform/base:util",
|
||||
"//cpp/platform/public:comm",
|
||||
"//cpp/platform/public:logging",
|
||||
"//cpp/platform/public:types",
|
||||
"//internal/analytics",
|
||||
"//proto:connections_enums_portable_proto",
|
||||
"@com_google_absl//absl/base:core_headers",
|
||||
"@com_google_absl//absl/container:btree",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/functional:bind_front",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_absl//absl/types:span",
|
||||
":ukey2",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "message_lite",
|
||||
hdrs = [
|
||||
"message_lite.h",
|
||||
],
|
||||
visibility = [
|
||||
"//cpp/core:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"@com_google_protobuf//:protobuf_lite",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "internal_test",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"offline_simulation_user.cc",
|
||||
"simulation_user.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"mock_service_controller.h",
|
||||
"mock_service_controller_router.h",
|
||||
"offline_simulation_user.h",
|
||||
"simulation_user.h",
|
||||
],
|
||||
visibility = [
|
||||
"//cpp/core:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":internal",
|
||||
"//cpp/core:core_types",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/base:test_util",
|
||||
"//cpp/platform/public:types",
|
||||
"@com_google_googletest//:gtest_for_library_testonly",
|
||||
"@com_google_absl//absl/functional:bind_front",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "core_internal_test",
|
||||
size = "small",
|
||||
timeout = "moderate",
|
||||
srcs = [
|
||||
"base_endpoint_channel_test.cc",
|
||||
"base_pcp_handler_test.cc",
|
||||
"ble_advertisement_test.cc",
|
||||
"bluetooth_device_name_test.cc",
|
||||
"bwu_manager_test.cc",
|
||||
"client_proxy_test.cc",
|
||||
"encryption_runner_test.cc",
|
||||
"endpoint_channel_manager_test.cc",
|
||||
"endpoint_manager_test.cc",
|
||||
"injected_bluetooth_device_store_test.cc",
|
||||
"internal_payload_factory_test.cc",
|
||||
"offline_frames_test.cc",
|
||||
"offline_frames_validator_test.cc",
|
||||
"offline_service_controller_test.cc",
|
||||
"p2p_cluster_pcp_handler_test.cc",
|
||||
"payload_manager_test.cc",
|
||||
"pcp_manager_test.cc",
|
||||
"service_controller_router_test.cc",
|
||||
"wifi_lan_service_info_test.cc",
|
||||
],
|
||||
shard_count = 16,
|
||||
deps = [
|
||||
":internal",
|
||||
":internal_test",
|
||||
"//connections/implementation/proto:offline_wire_formats_portable_proto",
|
||||
"//cpp/core:core_types",
|
||||
"//cpp/core/internal/mediums",
|
||||
"//cpp/core/internal/mediums:utils",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/base:test_util",
|
||||
"//cpp/platform/impl/g3", # build_cleaner: keep
|
||||
"//cpp/platform/public:comm",
|
||||
"//cpp/platform/public:logging",
|
||||
"//cpp/platform/public:types",
|
||||
"//proto:connections_enums_portable_proto",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main","@com_github_protobuf_matchers//protobuf-matchers:protobuf-matchers",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
"@com_google_absl//absl/time",
|
||||
"@com_google_absl//absl/types:span",
|
||||
":ukey2",
|
||||
],
|
||||
)
|
||||
@@ -1,216 +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.
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "internal",
|
||||
srcs = [
|
||||
"base_endpoint_channel.cc",
|
||||
"base_pcp_handler.cc",
|
||||
"ble_advertisement.cc",
|
||||
"ble_endpoint_channel.cc",
|
||||
"bluetooth_bwu_handler.cc",
|
||||
"bluetooth_device_name.cc",
|
||||
"bluetooth_endpoint_channel.cc",
|
||||
"bwu_manager.cc",
|
||||
"client_proxy.cc",
|
||||
"encryption_runner.cc",
|
||||
"endpoint_channel_manager.cc",
|
||||
"endpoint_manager.cc",
|
||||
"injected_bluetooth_device_store.cc",
|
||||
"internal_payload.cc",
|
||||
"internal_payload_factory.cc",
|
||||
"offline_frames.cc",
|
||||
"offline_frames_validator.cc",
|
||||
"offline_service_controller.cc",
|
||||
"p2p_cluster_pcp_handler.cc",
|
||||
"p2p_point_to_point_pcp_handler.cc",
|
||||
"p2p_star_pcp_handler.cc",
|
||||
"payload_manager.cc",
|
||||
"pcp_manager.cc",
|
||||
"service_controller_router.cc",
|
||||
"webrtc_bwu_handler.cc",
|
||||
"webrtc_endpoint_channel.cc",
|
||||
"wifi_lan_bwu_handler.cc",
|
||||
"wifi_lan_endpoint_channel.cc",
|
||||
"wifi_lan_service_info.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"base_bwu_handler.h",
|
||||
"base_endpoint_channel.h",
|
||||
"base_pcp_handler.h",
|
||||
"ble_advertisement.h",
|
||||
"ble_endpoint_channel.h",
|
||||
"bluetooth_bwu_handler.h",
|
||||
"bluetooth_device_name.h",
|
||||
"bluetooth_endpoint_channel.h",
|
||||
"bwu_handler.h",
|
||||
"bwu_manager.h",
|
||||
"client_proxy.h",
|
||||
"encryption_runner.h",
|
||||
"endpoint_channel.h",
|
||||
"endpoint_channel_manager.h",
|
||||
"endpoint_manager.h",
|
||||
"injected_bluetooth_device_store.h",
|
||||
"internal_payload.h",
|
||||
"internal_payload_factory.h",
|
||||
"offline_frames.h",
|
||||
"offline_frames_validator.h",
|
||||
"offline_service_controller.h",
|
||||
"p2p_cluster_pcp_handler.h",
|
||||
"p2p_point_to_point_pcp_handler.h",
|
||||
"p2p_star_pcp_handler.h",
|
||||
"payload_manager.h",
|
||||
"pcp.h",
|
||||
"pcp_handler.h",
|
||||
"pcp_manager.h",
|
||||
"service_controller.h",
|
||||
"service_controller_router.h",
|
||||
"webrtc_bwu_handler.h",
|
||||
"webrtc_endpoint_channel.h",
|
||||
"wifi_lan_bwu_handler.h",
|
||||
"wifi_lan_endpoint_channel.h",
|
||||
"wifi_lan_service_info.h",
|
||||
],
|
||||
compatible_with = ["//buildenv/target:non_prod"],
|
||||
copts = ["-DCORE_ADAPTER_DLL"],
|
||||
defines = ["NO_WEBRTC"],
|
||||
visibility = [
|
||||
"//third_party/nearby/cpp/core:__pkg__",
|
||||
"//third_party/nearby/cpp/core/internal/fuzzers:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
":message_lite",
|
||||
"//third_party/absl/base:core_headers",
|
||||
"//third_party/absl/container:btree",
|
||||
"//third_party/absl/container:flat_hash_map",
|
||||
"//third_party/absl/container:flat_hash_set",
|
||||
"//third_party/absl/functional:bind_front",
|
||||
"//third_party/absl/memory",
|
||||
"//third_party/absl/strings",
|
||||
"//third_party/absl/time",
|
||||
"//third_party/absl/types:span",
|
||||
"//third_party/nearby/connections/implementation/proto:offline_wire_formats_portable_proto",
|
||||
"//third_party/nearby/cpp/core:core_types",
|
||||
"//third_party/nearby/cpp/core/internal/mediums",
|
||||
"//third_party/nearby/cpp/core/internal/mediums:utils",
|
||||
"//third_party/nearby/cpp/platform/api:comm",
|
||||
"//third_party/nearby/cpp/platform/base",
|
||||
"//third_party/nearby/cpp/platform/base:cancellation_flag",
|
||||
"//third_party/nearby/cpp/platform/base:error_code_recorder",
|
||||
"//third_party/nearby/cpp/platform/base:util",
|
||||
"//third_party/nearby/cpp/platform/public:comm",
|
||||
"//third_party/nearby/cpp/platform/public:logging",
|
||||
"//third_party/nearby/cpp/platform/public:types",
|
||||
"//third_party/nearby/internal/analytics",
|
||||
"//third_party/nearby/proto:connections_enums_portable_proto",
|
||||
"//third_party/ukey2",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "message_lite",
|
||||
hdrs = [
|
||||
"message_lite.h",
|
||||
],
|
||||
compatible_with = ["//buildenv/target:non_prod"],
|
||||
visibility = [
|
||||
"//third_party/nearby/cpp/core:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//net/proto2/public:proto2_lite",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "internal_test",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"offline_simulation_user.cc",
|
||||
"simulation_user.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"mock_service_controller.h",
|
||||
"mock_service_controller_router.h",
|
||||
"offline_simulation_user.h",
|
||||
"simulation_user.h",
|
||||
],
|
||||
defines = ["NO_WEBRTC"],
|
||||
visibility = [
|
||||
"//third_party/nearby/cpp/core:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":internal",
|
||||
"//testing/base/public:gunit_for_library_testonly",
|
||||
"//third_party/absl/functional:bind_front",
|
||||
"//third_party/absl/strings",
|
||||
"//third_party/nearby/cpp/core:core_types",
|
||||
"//third_party/nearby/cpp/platform/base",
|
||||
"//third_party/nearby/cpp/platform/base:test_util",
|
||||
"//third_party/nearby/cpp/platform/public:types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "core_internal_test",
|
||||
size = "small",
|
||||
timeout = "moderate",
|
||||
srcs = [
|
||||
"base_endpoint_channel_test.cc",
|
||||
"base_pcp_handler_test.cc",
|
||||
"ble_advertisement_test.cc",
|
||||
"bluetooth_device_name_test.cc",
|
||||
"bwu_manager_test.cc",
|
||||
"client_proxy_test.cc",
|
||||
"encryption_runner_test.cc",
|
||||
"endpoint_channel_manager_test.cc",
|
||||
"endpoint_manager_test.cc",
|
||||
"injected_bluetooth_device_store_test.cc",
|
||||
"internal_payload_factory_test.cc",
|
||||
"offline_frames_test.cc",
|
||||
"offline_frames_validator_test.cc",
|
||||
"offline_service_controller_test.cc",
|
||||
"p2p_cluster_pcp_handler_test.cc",
|
||||
"payload_manager_test.cc",
|
||||
"pcp_manager_test.cc",
|
||||
"service_controller_router_test.cc",
|
||||
"wifi_lan_service_info_test.cc",
|
||||
],
|
||||
defines = ["NO_WEBRTC"],
|
||||
shard_count = 16,
|
||||
deps = [
|
||||
":internal",
|
||||
":internal_test",
|
||||
"//testing/base/public:gunit",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//third_party/absl/container:flat_hash_set",
|
||||
"//third_party/absl/strings",
|
||||
"//third_party/absl/synchronization",
|
||||
"//third_party/absl/time",
|
||||
"//third_party/absl/types:span",
|
||||
"//third_party/nearby/connections/implementation/proto:offline_wire_formats_portable_proto",
|
||||
"//third_party/nearby/cpp/core:core_types",
|
||||
"//third_party/nearby/cpp/core/internal/mediums",
|
||||
"//third_party/nearby/cpp/core/internal/mediums:utils",
|
||||
"//third_party/nearby/cpp/platform/base",
|
||||
"//third_party/nearby/cpp/platform/base:test_util",
|
||||
"//third_party/nearby/cpp/platform/impl/g3", # build_cleaner: keep
|
||||
"//third_party/nearby/cpp/platform/public:comm",
|
||||
"//third_party/nearby/cpp/platform/public:logging",
|
||||
"//third_party/nearby/cpp/platform/public:types",
|
||||
"//third_party/nearby/internal/analytics",
|
||||
"//third_party/nearby/proto:connections_enums_portable_proto",
|
||||
"//third_party/ukey2",
|
||||
],
|
||||
)
|
||||
@@ -1,58 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_BASE_BWU_HANDLER_H_
|
||||
#define CORE_INTERNAL_BASE_BWU_HANDLER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "core/internal/bwu_handler.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_channel_manager.h"
|
||||
#include "platform/public/cancelable_alarm.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/scheduled_executor.h"
|
||||
#include "platform/public/single_thread_executor.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class BaseBwuHandler : public BwuHandler {
|
||||
public:
|
||||
using ClientIntroduction = BwuNegotiationFrame::ClientIntroduction;
|
||||
|
||||
BaseBwuHandler(EndpointChannelManager& channel_manager,
|
||||
BwuNotifications bwu_notifications)
|
||||
: channel_manager_(&channel_manager),
|
||||
bwu_notifications_(std::move(bwu_notifications)) {}
|
||||
~BaseBwuHandler() override = default;
|
||||
|
||||
protected:
|
||||
// Represents the incoming Socket the Initiator has gotten after initializing
|
||||
// its upgraded bandwidth medium.
|
||||
EndpointChannelManager* GetEndpointChannelManager();
|
||||
EndpointChannelManager* channel_manager_;
|
||||
BwuNotifications bwu_notifications_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BASE_BWU_HANDLER_H_
|
||||
@@ -1,401 +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 "core/internal/base_endpoint_channel.h"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
|
||||
std::int32_t 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);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ByteArray 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);
|
||||
|
||||
return ByteArray(int_bytes, sizeof(int_bytes));
|
||||
}
|
||||
|
||||
ExceptionOr<ByteArray> ReadExactly(InputStream* reader, std::int64_t size) {
|
||||
ByteArray buffer(size);
|
||||
std::int64_t current_pos = 0;
|
||||
|
||||
while (current_pos < size) {
|
||||
ExceptionOr<ByteArray> read_bytes = reader->Read(size - current_pos);
|
||||
if (!read_bytes.ok()) {
|
||||
return read_bytes;
|
||||
}
|
||||
ByteArray result = read_bytes.result();
|
||||
|
||||
if (result.Empty()) {
|
||||
NEARBY_LOGS(WARNING) << __func__ << ": Empty result when reading bytes.";
|
||||
return ExceptionOr<ByteArray>(Exception::kIo);
|
||||
}
|
||||
|
||||
buffer.CopyAt(current_pos, result);
|
||||
current_pos += result.size();
|
||||
}
|
||||
|
||||
return ExceptionOr<ByteArray>(std::move(buffer));
|
||||
}
|
||||
|
||||
ExceptionOr<std::int32_t> ReadInt(InputStream* reader) {
|
||||
ExceptionOr<ByteArray> read_bytes = ReadExactly(reader, sizeof(std::int32_t));
|
||||
if (!read_bytes.ok()) {
|
||||
return ExceptionOr<std::int32_t>(read_bytes.exception());
|
||||
}
|
||||
return ExceptionOr<std::int32_t>(BytesToInt(std::move(read_bytes.result())));
|
||||
}
|
||||
|
||||
Exception WriteInt(OutputStream* writer, std::int32_t value) {
|
||||
return writer->Write(IntToBytes(value));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BaseEndpointChannel::BaseEndpointChannel(const std::string& channel_name,
|
||||
InputStream* reader,
|
||||
OutputStream* writer)
|
||||
: BaseEndpointChannel(
|
||||
channel_name, reader, writer,
|
||||
// TODO(edwinwu): Below values should be retrieved from a base socket,
|
||||
// the #MediumSocket in Android counterpart, from which all the
|
||||
// derived medium sockets should dervied, and implement the supported
|
||||
// values and leave the default values in base #MediumSocket.
|
||||
/*ConnectionTechnology*/
|
||||
proto::connections::CONNECTION_TECHNOLOGY_UNKNOWN_TECHNOLOGY,
|
||||
/*ConnectionBand*/ proto::connections::CONNECTION_BAND_UNKNOWN_BAND,
|
||||
/*frequency*/ -1,
|
||||
/*try_count*/ 0) {}
|
||||
|
||||
BaseEndpointChannel::BaseEndpointChannel(
|
||||
const std::string& channel_name, InputStream* reader, OutputStream* writer,
|
||||
proto::connections::ConnectionTechnology technology,
|
||||
proto::connections::ConnectionBand band, int frequency, int try_count)
|
||||
: channel_name_(channel_name),
|
||||
reader_(reader),
|
||||
writer_(writer),
|
||||
technology_(technology),
|
||||
band_(band),
|
||||
frequency_(frequency),
|
||||
try_count_(try_count) {}
|
||||
|
||||
ExceptionOr<ByteArray> BaseEndpointChannel::Read() {
|
||||
ByteArray result;
|
||||
{
|
||||
MutexLock lock(&reader_mutex_);
|
||||
|
||||
ExceptionOr<std::int32_t> read_int = ReadInt(reader_);
|
||||
if (!read_int.ok()) {
|
||||
return ExceptionOr<ByteArray>(read_int.exception());
|
||||
}
|
||||
|
||||
if (read_int.result() < 0 || read_int.result() > kMaxAllowedReadBytes) {
|
||||
NEARBY_LOGS(WARNING) << __func__ << ": Read an invalid number of bytes: "
|
||||
<< read_int.result();
|
||||
return ExceptionOr<ByteArray>(Exception::kIo);
|
||||
}
|
||||
|
||||
ExceptionOr<ByteArray> read_bytes = ReadExactly(reader_, read_int.result());
|
||||
if (!read_bytes.ok()) {
|
||||
return read_bytes;
|
||||
}
|
||||
result = std::move(read_bytes.result());
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
if (IsEncryptionEnabledLocked()) {
|
||||
// If encryption is enabled, decode the message.
|
||||
std::string input(std::move(result));
|
||||
std::unique_ptr<std::string> decrypted_data =
|
||||
crypto_context_->DecodeMessageFromPeer(input);
|
||||
if (decrypted_data) {
|
||||
result = ByteArray(std::move(*decrypted_data));
|
||||
} else {
|
||||
// It could be a protocol race, where remote party sends a KEEP_ALIVE
|
||||
// before encryption is setup on their side, and we receive it after
|
||||
// we switched to encryption mode.
|
||||
// In this case, we verify that message is indeed a valid KEEP_ALIVE,
|
||||
// and let it through if it is, otherwise message is erased.
|
||||
// TODO(apolyudov): verify this happens at most once per session.
|
||||
result = {};
|
||||
auto parsed = parser::FromBytes(ByteArray(input));
|
||||
if (parsed.ok()) {
|
||||
if (parser::GetFrameType(parsed.result()) == V1Frame::KEEP_ALIVE) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< __func__
|
||||
<< ": Read unencrypted KEEP_ALIVE on encrypted channel.";
|
||||
result = ByteArray(input);
|
||||
} else {
|
||||
NEARBY_LOGS(WARNING)
|
||||
<< __func__ << ": Read unexpected unencrypted frame of type "
|
||||
<< parser::GetFrameType(parsed.result());
|
||||
}
|
||||
} else {
|
||||
NEARBY_LOGS(WARNING)
|
||||
<< __func__ << ": Unable to parse data as unencrypted message.";
|
||||
}
|
||||
}
|
||||
if (result.Empty()) {
|
||||
NEARBY_LOGS(WARNING) << __func__ << ": Unable to parse read result.";
|
||||
return ExceptionOr<ByteArray>(Exception::kInvalidProtocolBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(&last_read_mutex_);
|
||||
last_read_timestamp_ = SystemClock::ElapsedRealtime();
|
||||
}
|
||||
return ExceptionOr<ByteArray>(result);
|
||||
}
|
||||
|
||||
Exception BaseEndpointChannel::Write(const ByteArray& data) {
|
||||
{
|
||||
MutexLock pause_lock(&is_paused_mutex_);
|
||||
if (is_paused_) {
|
||||
BlockUntilUnpaused();
|
||||
}
|
||||
}
|
||||
|
||||
ByteArray encrypted_data;
|
||||
const ByteArray* data_to_write = &data;
|
||||
{
|
||||
// Holding both mutexes is necessary to prevent the keep alive and payload
|
||||
// threads from writing encrypted messages out of order which causes a
|
||||
// failure to decrypt on the reader side. However we need to release the
|
||||
// crypto lock after encrypting to ensure read decryption is not blocked.
|
||||
MutexLock lock(&writer_mutex_);
|
||||
{
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
if (IsEncryptionEnabledLocked()) {
|
||||
// If encryption is enabled, encode the message.
|
||||
std::unique_ptr<std::string> encrypted =
|
||||
crypto_context_->EncodeMessageToPeer(std::string(data));
|
||||
if (!encrypted) {
|
||||
NEARBY_LOGS(WARNING) << __func__ << ": Failed to encrypt data.";
|
||||
return {Exception::kIo};
|
||||
}
|
||||
encrypted_data = ByteArray(std::move(*encrypted));
|
||||
data_to_write = &encrypted_data;
|
||||
}
|
||||
}
|
||||
|
||||
Exception write_exception =
|
||||
WriteInt(writer_, static_cast<std::int32_t>(data_to_write->size()));
|
||||
if (write_exception.Raised()) {
|
||||
NEARBY_LOGS(WARNING) << __func__ << ": Failed to write header: "
|
||||
<< write_exception.value;
|
||||
return write_exception;
|
||||
}
|
||||
write_exception = writer_->Write(*data_to_write);
|
||||
if (write_exception.Raised()) {
|
||||
NEARBY_LOGS(WARNING) << __func__ << ": Failed to write data: "
|
||||
<< write_exception.value;
|
||||
return write_exception;
|
||||
}
|
||||
Exception flush_exception = writer_->Flush();
|
||||
if (flush_exception.Raised()) {
|
||||
NEARBY_LOGS(WARNING) << __func__ << ": Failed to flush writer: "
|
||||
<< flush_exception.value;
|
||||
return flush_exception;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(&last_write_mutex_);
|
||||
last_write_timestamp_ = SystemClock::ElapsedRealtime();
|
||||
}
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::Close() {
|
||||
{
|
||||
// In case channel is paused, resume it first thing.
|
||||
MutexLock lock(&is_paused_mutex_);
|
||||
UnblockPausedWriter();
|
||||
}
|
||||
CloseIo();
|
||||
CloseImpl();
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::CloseIo() {
|
||||
// Keep this method dedicated to reader and writer handling an nothing else.
|
||||
{
|
||||
// Do not take reader_mutex_ here: read may be in progress, and it will
|
||||
// deadlock. Calling Close() with Read() in progress will terminate the
|
||||
// IO and Read() will proceed normally (with Exception::kIo).
|
||||
Exception exception = reader_->Close();
|
||||
if (!exception.Ok()) {
|
||||
NEARBY_LOGS(WARNING) << __func__
|
||||
<< ": Exception closing reader: " << exception.value;
|
||||
}
|
||||
}
|
||||
{
|
||||
// Do not take writer_mutex_ here: write may be in progress, and it will
|
||||
// deadlock. Calling Close() with Write() in progress will terminate the
|
||||
// IO and Write() will proceed normally (with Exception::kIo).
|
||||
Exception exception = writer_->Close();
|
||||
if (!exception.Ok()) {
|
||||
NEARBY_LOGS(WARNING) << __func__
|
||||
<< ": Exception closing writer: " << exception.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::SetAnalyticsRecorder(
|
||||
analytics::AnalyticsRecorder* analytics_recorder,
|
||||
const std::string& endpoint_id) {
|
||||
analytics_recorder_ = analytics_recorder;
|
||||
endpoint_id_ = endpoint_id;
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::Close(
|
||||
proto::connections::DisconnectionReason reason) {
|
||||
NEARBY_LOGS(INFO) << __func__
|
||||
<< ": Closing endpoint channel, reason: " << reason;
|
||||
Close();
|
||||
|
||||
if (analytics_recorder_ != nullptr && !endpoint_id_.empty()) {
|
||||
analytics_recorder_->OnConnectionClosed(endpoint_id_, GetMedium(), reason);
|
||||
}
|
||||
}
|
||||
|
||||
std::string BaseEndpointChannel::GetType() const {
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
std::string subtype = IsEncryptionEnabledLocked() ? "ENCRYPTED_" : "";
|
||||
std::string medium = proto::connections::Medium_Name(
|
||||
proto::connections::Medium::UNKNOWN_MEDIUM);
|
||||
|
||||
if (GetMedium() != proto::connections::Medium::UNKNOWN_MEDIUM) {
|
||||
medium =
|
||||
absl::StrCat(subtype, proto::connections::Medium_Name(GetMedium()));
|
||||
}
|
||||
return medium;
|
||||
}
|
||||
|
||||
std::string BaseEndpointChannel::GetName() const { return channel_name_; }
|
||||
|
||||
int BaseEndpointChannel::GetMaxTransmitPacketSize() const {
|
||||
// Return default value if the medium never define it's chunk size.
|
||||
return kDefaultMaxTransmitPacketSize;
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::EnableEncryption(
|
||||
std::shared_ptr<EncryptionContext> context) {
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
crypto_context_ = context;
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::DisableEncryption() {
|
||||
MutexLock crypto_lock(&crypto_mutex_);
|
||||
crypto_context_.reset();
|
||||
}
|
||||
|
||||
bool BaseEndpointChannel::IsPaused() const {
|
||||
MutexLock lock(&is_paused_mutex_);
|
||||
return is_paused_;
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::Pause() {
|
||||
MutexLock lock(&is_paused_mutex_);
|
||||
is_paused_ = true;
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::Resume() {
|
||||
MutexLock lock(&is_paused_mutex_);
|
||||
is_paused_ = false;
|
||||
is_paused_cond_.Notify();
|
||||
}
|
||||
|
||||
absl::Time BaseEndpointChannel::GetLastReadTimestamp() const {
|
||||
MutexLock lock(&last_read_mutex_);
|
||||
return last_read_timestamp_;
|
||||
}
|
||||
|
||||
absl::Time BaseEndpointChannel::GetLastWriteTimestamp() const {
|
||||
MutexLock lock(&last_write_mutex_);
|
||||
return last_write_timestamp_;
|
||||
}
|
||||
|
||||
proto::connections::ConnectionTechnology BaseEndpointChannel::GetTechnology()
|
||||
const {
|
||||
return technology_;
|
||||
}
|
||||
|
||||
// Returns the used wifi band of this EndpointChannel.
|
||||
proto::connections::ConnectionBand BaseEndpointChannel::GetBand() const {
|
||||
return band_;
|
||||
}
|
||||
|
||||
// Returns the used wifi frequency of this EndpointChannel.
|
||||
int BaseEndpointChannel::GetFrequency() const { return frequency_; }
|
||||
|
||||
// Returns the try count of this EndpointChannel.
|
||||
int BaseEndpointChannel::GetTryCount() const { return try_count_; }
|
||||
|
||||
bool BaseEndpointChannel::IsEncryptionEnabledLocked() const {
|
||||
return crypto_context_ != nullptr;
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::BlockUntilUnpaused() {
|
||||
// For more on how this works, see
|
||||
// https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
|
||||
while (is_paused_) {
|
||||
Exception wait_succeeded = is_paused_cond_.Wait();
|
||||
if (!wait_succeeded.Ok()) {
|
||||
NEARBY_LOGS(WARNING) << __func__ << ": Failure waiting to unpause: "
|
||||
<< wait_succeeded.value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BaseEndpointChannel::UnblockPausedWriter() {
|
||||
// For more on how this works, see
|
||||
// https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
|
||||
is_paused_ = false;
|
||||
is_paused_cond_.Notify();
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,179 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_
|
||||
#define CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "securegcm/d2d_connection_context_v1.h"
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "core/internal/endpoint_channel.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/input_stream.h"
|
||||
#include "platform/base/output_stream.h"
|
||||
#include "platform/public/atomic_reference.h"
|
||||
#include "platform/public/condition_variable.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
#include "internal/analytics/analytics_recorder.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class BaseEndpointChannel : public EndpointChannel {
|
||||
public:
|
||||
BaseEndpointChannel(const std::string& channel_name, InputStream* reader,
|
||||
OutputStream* writer);
|
||||
BaseEndpointChannel(const std::string& channel_name, InputStream* reader,
|
||||
OutputStream* writer,
|
||||
proto::connections::ConnectionTechnology,
|
||||
proto::connections::ConnectionBand band, int frequency,
|
||||
int try_count);
|
||||
~BaseEndpointChannel() override = default;
|
||||
|
||||
ExceptionOr<ByteArray> Read()
|
||||
ABSL_LOCKS_EXCLUDED(reader_mutex_, crypto_mutex_,
|
||||
last_read_mutex_) override;
|
||||
|
||||
Exception Write(const ByteArray& data)
|
||||
ABSL_LOCKS_EXCLUDED(writer_mutex_, crypto_mutex_) override;
|
||||
|
||||
// Closes this EndpointChannel, without tracking the closure in analytics.
|
||||
void Close() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override;
|
||||
|
||||
// Closes this EndpointChannel and records the closure with the given reason.
|
||||
void Close(proto::connections::DisconnectionReason reason) override;
|
||||
|
||||
// Returns a one-word type descriptor for the concrete EndpointChannel
|
||||
// implementation that can be used in log messages; eg: BLUETOOTH, BLE,
|
||||
// WIFI.
|
||||
std::string GetType() const override;
|
||||
|
||||
// Returns the name of the EndpointChannel.
|
||||
std::string GetName() const override;
|
||||
|
||||
// Returns the maximum supported transmit packet size(MTU) for the underlying
|
||||
// transport.
|
||||
int GetMaxTransmitPacketSize() const override;
|
||||
|
||||
// Enables encryption on the EndpointChannel.
|
||||
// Should be called after connection is accepted by both parties, and
|
||||
// before entering data phase, where Payloads may be exchanged.
|
||||
void EnableEncryption(std::shared_ptr<EncryptionContext> context) override;
|
||||
|
||||
// Disables encryption on the EndpointChannel.
|
||||
void DisableEncryption() override;
|
||||
|
||||
// True if the EndpointChannel is currently pausing all writes.
|
||||
bool IsPaused() const ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override;
|
||||
|
||||
// Pauses all writes on this EndpointChannel until resume() is called.
|
||||
void Pause() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override;
|
||||
|
||||
// Resumes any writes on this EndpointChannel that were suspended when pause()
|
||||
// was called.
|
||||
void Resume() ABSL_LOCKS_EXCLUDED(is_paused_mutex_) override;
|
||||
|
||||
// Returns the timestamp (returned by ElapsedRealtime) of the last read from
|
||||
// this endpoint, or -1 if no reads have occurred.
|
||||
absl::Time GetLastReadTimestamp() const
|
||||
ABSL_LOCKS_EXCLUDED(last_read_mutex_) override;
|
||||
|
||||
// Returns the timestamp (returned by ElapsedRealtime) of the last write to
|
||||
// this endpoint, or -1 if no writes have occurred.
|
||||
absl::Time GetLastWriteTimestamp() const
|
||||
ABSL_LOCKS_EXCLUDED(last_write_mutex_) override;
|
||||
|
||||
// Returns the used technology of this EndpointChannel.
|
||||
proto::connections::ConnectionTechnology GetTechnology() const override;
|
||||
|
||||
// Returns the used wifi band of this EndpointChannel.
|
||||
proto::connections::ConnectionBand GetBand() const override;
|
||||
|
||||
// Returns the used wifi frequency of this EndpointChannel.
|
||||
int GetFrequency() const override;
|
||||
|
||||
// Returns the try count of this EndpointChannel.
|
||||
int GetTryCount() const override;
|
||||
|
||||
void SetAnalyticsRecorder(analytics::AnalyticsRecorder* analytics_recorder,
|
||||
const std::string& endpoint_id) override;
|
||||
|
||||
protected:
|
||||
virtual void CloseImpl() = 0;
|
||||
|
||||
private:
|
||||
// Used to sanity check that our frame sizes are reasonable.
|
||||
static constexpr std::int32_t kMaxAllowedReadBytes = 1048576; // 1MB
|
||||
|
||||
// The default maximum transmit unit/packet size.
|
||||
static constexpr int kDefaultMaxTransmitPacketSize = 65536; // 64 KB
|
||||
|
||||
bool IsEncryptionEnabledLocked() const
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(crypto_mutex_);
|
||||
void UnblockPausedWriter() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_);
|
||||
void BlockUntilUnpaused() ABSL_EXCLUSIVE_LOCKS_REQUIRED(is_paused_mutex_);
|
||||
void CloseIo() ABSL_NO_THREAD_SAFETY_ANALYSIS;
|
||||
|
||||
// We need a separate mutex to protect read timestamp, because if a read
|
||||
// blocks on IO, we don't want timestamp read access to block too.
|
||||
mutable Mutex last_read_mutex_;
|
||||
absl::Time last_read_timestamp_ ABSL_GUARDED_BY(last_read_mutex_) =
|
||||
absl::InfinitePast();
|
||||
|
||||
// We need a separate mutex to protect write timestamp, because if a write
|
||||
// blocks on IO, we don't want timestamp write access to block too.
|
||||
mutable Mutex last_write_mutex_;
|
||||
absl::Time last_write_timestamp_ ABSL_GUARDED_BY(last_write_mutex_) =
|
||||
absl::InfinitePast();
|
||||
|
||||
const std::string channel_name_;
|
||||
|
||||
// The reader and writer are synchronized independently since we can't have
|
||||
// writes waiting on reads that might potentially block forever.
|
||||
Mutex reader_mutex_;
|
||||
InputStream* reader_ ABSL_PT_GUARDED_BY(reader_mutex_);
|
||||
|
||||
Mutex writer_mutex_;
|
||||
OutputStream* writer_ ABSL_PT_GUARDED_BY(writer_mutex_);
|
||||
|
||||
// An encryptor/decryptor. May be null.
|
||||
mutable Mutex crypto_mutex_;
|
||||
std::shared_ptr<EncryptionContext> crypto_context_
|
||||
ABSL_GUARDED_BY(crypto_mutex_) ABSL_PT_GUARDED_BY(crypto_mutex_);
|
||||
|
||||
mutable Mutex is_paused_mutex_;
|
||||
ConditionVariable is_paused_cond_{&is_paused_mutex_};
|
||||
// If true, writes should block until this has been set to false.
|
||||
bool is_paused_ ABSL_GUARDED_BY(is_paused_mutex_) = false;
|
||||
|
||||
// The medium technology information of this endpoint channel.
|
||||
proto::connections::ConnectionTechnology technology_;
|
||||
proto::connections::ConnectionBand band_;
|
||||
int frequency_;
|
||||
int try_count_;
|
||||
|
||||
analytics::AnalyticsRecorder* analytics_recorder_ = nullptr;
|
||||
std::string endpoint_id_ = "";
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BASE_ENDPOINT_CHANNEL_H_
|
||||
@@ -1,410 +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 "core/internal/base_endpoint_channel.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "securegcm/d2d_connection_context_v1.h"
|
||||
#include "securegcm/ukey2_handshake.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/encryption_runner.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/base/input_stream.h"
|
||||
#include "platform/base/output_stream.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/multi_thread_executor.h"
|
||||
#include "platform/public/pipe.h"
|
||||
#include "platform/public/single_thread_executor.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
using ::location::nearby::proto::connections::DisconnectionReason;
|
||||
using ::location::nearby::proto::connections::Medium;
|
||||
using EncryptionContext = BaseEndpointChannel::EncryptionContext;
|
||||
|
||||
class TestEndpointChannel : public BaseEndpointChannel {
|
||||
public:
|
||||
explicit TestEndpointChannel(InputStream* input, OutputStream* output)
|
||||
: BaseEndpointChannel("channel", input, output) {}
|
||||
|
||||
MOCK_METHOD(Medium, GetMedium, (), (const override));
|
||||
MOCK_METHOD(void, CloseImpl, (), (override));
|
||||
};
|
||||
|
||||
std::function<void()> MakeDataPump(
|
||||
std::string label, InputStream* input, OutputStream* output,
|
||||
std::function<void(const ByteArray&)> monitor = nullptr) {
|
||||
return [label, input, output, monitor]() {
|
||||
NEARBY_LOGS(INFO) << "streaming data through '" << label << "'";
|
||||
while (true) {
|
||||
auto read_response = input->Read(Pipe::kChunkSize);
|
||||
if (!read_response.ok()) {
|
||||
NEARBY_LOGS(INFO) << "Peer reader closed on '" << label << "'";
|
||||
output->Close();
|
||||
break;
|
||||
}
|
||||
if (monitor) {
|
||||
monitor(read_response.result());
|
||||
}
|
||||
auto write_response = output->Write(read_response.result());
|
||||
if (write_response.Raised()) {
|
||||
NEARBY_LOGS(INFO) << "Peer writer closed on '" << label << "'";
|
||||
input->Close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
NEARBY_LOGS(INFO) << "streaming terminated on '" << label << "'";
|
||||
};
|
||||
}
|
||||
|
||||
std::function<void(const ByteArray&)> MakeDataMonitor(const std::string& label,
|
||||
std::string* capture,
|
||||
absl::Mutex* mutex) {
|
||||
return [label, capture, mutex](const ByteArray& input) mutable {
|
||||
std::string s = std::string(input);
|
||||
{
|
||||
absl::MutexLock lock(mutex);
|
||||
*capture += s;
|
||||
}
|
||||
NEARBY_LOGS(INFO) << "source='" << label << "'"
|
||||
<< "; message='" << s << "'";
|
||||
};
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<EncryptionContext>,
|
||||
std::shared_ptr<EncryptionContext>>
|
||||
DoDhKeyExchange(BaseEndpointChannel* channel_a,
|
||||
BaseEndpointChannel* channel_b) {
|
||||
std::shared_ptr<EncryptionContext> context_a;
|
||||
std::shared_ptr<EncryptionContext> context_b;
|
||||
EncryptionRunner crypto_a;
|
||||
EncryptionRunner crypto_b;
|
||||
ClientProxy proxy_a;
|
||||
ClientProxy proxy_b;
|
||||
CountDownLatch latch(2);
|
||||
crypto_a.StartClient(
|
||||
&proxy_a, "endpoint_id", channel_a,
|
||||
{
|
||||
.on_success_cb =
|
||||
[&latch, &context_a](
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
|
||||
const std::string& auth_token,
|
||||
const ByteArray& raw_auth_token) {
|
||||
NEARBY_LOGS(INFO) << "client-A side key negotiation done";
|
||||
EXPECT_TRUE(ukey2->VerifyHandshake());
|
||||
auto context = ukey2->ToConnectionContext();
|
||||
EXPECT_NE(context, nullptr);
|
||||
context_a = std::move(context);
|
||||
latch.CountDown();
|
||||
},
|
||||
.on_failure_cb =
|
||||
[&latch](const std::string& endpoint_id,
|
||||
EndpointChannel* channel) {
|
||||
NEARBY_LOGS(INFO) << "client-A side key negotiation failed";
|
||||
latch.CountDown();
|
||||
},
|
||||
});
|
||||
crypto_b.StartServer(
|
||||
&proxy_b, "endpoint_id", channel_b,
|
||||
{
|
||||
.on_success_cb =
|
||||
[&latch, &context_b](
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
|
||||
const std::string& auth_token,
|
||||
const ByteArray& raw_auth_token) {
|
||||
NEARBY_LOGS(INFO) << "client-B side key negotiation done";
|
||||
EXPECT_TRUE(ukey2->VerifyHandshake());
|
||||
auto context = ukey2->ToConnectionContext();
|
||||
EXPECT_NE(context, nullptr);
|
||||
context_b = std::move(context);
|
||||
latch.CountDown();
|
||||
},
|
||||
.on_failure_cb =
|
||||
[&latch](const std::string& endpoint_id,
|
||||
EndpointChannel* channel) {
|
||||
NEARBY_LOGS(INFO) << "client-B side key negotiation failed";
|
||||
latch.CountDown();
|
||||
},
|
||||
});
|
||||
EXPECT_TRUE(latch.Await(absl::Milliseconds(5000)).result());
|
||||
return std::make_pair(std::move(context_a), std::move(context_b));
|
||||
}
|
||||
|
||||
TEST(BaseEndpointChannelTest, ConstructorDestructorWorks) {
|
||||
Pipe pipe;
|
||||
InputStream& input_stream = pipe.GetInputStream();
|
||||
OutputStream& output_stream = pipe.GetOutputStream();
|
||||
|
||||
TestEndpointChannel test_channel(&input_stream, &output_stream);
|
||||
}
|
||||
|
||||
TEST(BaseEndpointChannelTest, ReadWrite) {
|
||||
// Direct not-encrypted IO.
|
||||
Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b.
|
||||
Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a.
|
||||
TestEndpointChannel channel_a(&pipe_b.GetInputStream(),
|
||||
&pipe_a.GetOutputStream());
|
||||
TestEndpointChannel channel_b(&pipe_a.GetInputStream(),
|
||||
&pipe_b.GetOutputStream());
|
||||
ByteArray tx_message{"data message"};
|
||||
channel_a.Write(tx_message);
|
||||
ByteArray rx_message = std::move(channel_b.Read().result());
|
||||
EXPECT_EQ(rx_message, tx_message);
|
||||
}
|
||||
|
||||
TEST(BaseEndpointChannelTest, NotEncryptedReadWriteCanBeIntercepted) {
|
||||
// Not encrypted IO; MITM scenario.
|
||||
|
||||
// Setup test communication environment.
|
||||
absl::Mutex mutex;
|
||||
std::string capture_a;
|
||||
std::string capture_b;
|
||||
Pipe client_a; // Channel "a" writes to client "a", reads from server "a".
|
||||
Pipe client_b; // Channel "b" writes to client "b", reads from server "b".
|
||||
Pipe server_a; // Data pump "a" reads from client "a", writes to server "b".
|
||||
Pipe server_b; // Data pump "b" reads from client "b", writes to server "a".
|
||||
TestEndpointChannel channel_a(&server_a.GetInputStream(),
|
||||
&client_a.GetOutputStream());
|
||||
TestEndpointChannel channel_b(&server_b.GetInputStream(),
|
||||
&client_b.GetOutputStream());
|
||||
|
||||
ON_CALL(channel_a, GetMedium).WillByDefault([]() { return Medium::BLE; });
|
||||
ON_CALL(channel_b, GetMedium).WillByDefault([]() { return Medium::BLE; });
|
||||
|
||||
MultiThreadExecutor executor(2);
|
||||
executor.Execute(MakeDataPump(
|
||||
"pump_a", &client_a.GetInputStream(), &server_b.GetOutputStream(),
|
||||
MakeDataMonitor("monitor_a", &capture_a, &mutex)));
|
||||
executor.Execute(MakeDataPump(
|
||||
"pump_b", &client_b.GetInputStream(), &server_a.GetOutputStream(),
|
||||
MakeDataMonitor("monitor_b", &capture_b, &mutex)));
|
||||
|
||||
EXPECT_EQ(channel_a.GetType(), "BLE");
|
||||
EXPECT_EQ(channel_b.GetType(), "BLE");
|
||||
|
||||
// Start data transfer
|
||||
ByteArray tx_message{"data message"};
|
||||
channel_a.Write(tx_message);
|
||||
ByteArray rx_message = std::move(channel_b.Read().result());
|
||||
|
||||
// Verify expectations.
|
||||
EXPECT_EQ(rx_message, tx_message);
|
||||
{
|
||||
absl::MutexLock lock(&mutex);
|
||||
std::string message{tx_message};
|
||||
EXPECT_TRUE(capture_a.find(message) != std::string::npos ||
|
||||
capture_b.find(message) != std::string::npos);
|
||||
}
|
||||
|
||||
// Shutdown test environment.
|
||||
channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION);
|
||||
channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION);
|
||||
}
|
||||
|
||||
TEST(BaseEndpointChannelTest, EncryptedReadWriteCanNotBeIntercepted) {
|
||||
// Encrypted IO; MITM scenario.
|
||||
|
||||
// Setup test communication environment.
|
||||
absl::Mutex mutex;
|
||||
std::string capture_a;
|
||||
std::string capture_b;
|
||||
Pipe client_a; // Channel "a" writes to client "a", reads from server "a".
|
||||
Pipe client_b; // Channel "b" writes to client "b", reads from server "b".
|
||||
Pipe server_a; // Data pump "a" reads from client "a", writes to server "b".
|
||||
Pipe server_b; // Data pump "b" reads from client "b", writes to server "a".
|
||||
TestEndpointChannel channel_a(&server_a.GetInputStream(),
|
||||
&client_a.GetOutputStream());
|
||||
TestEndpointChannel channel_b(&server_b.GetInputStream(),
|
||||
&client_b.GetOutputStream());
|
||||
|
||||
ON_CALL(channel_a, GetMedium).WillByDefault([]() {
|
||||
return Medium::BLUETOOTH;
|
||||
});
|
||||
ON_CALL(channel_b, GetMedium).WillByDefault([]() {
|
||||
return Medium::BLUETOOTH;
|
||||
});
|
||||
|
||||
MultiThreadExecutor executor(2);
|
||||
executor.Execute(MakeDataPump(
|
||||
"pump_a", &client_a.GetInputStream(), &server_b.GetOutputStream(),
|
||||
MakeDataMonitor("monitor_a", &capture_a, &mutex)));
|
||||
executor.Execute(MakeDataPump(
|
||||
"pump_b", &client_b.GetInputStream(), &server_a.GetOutputStream(),
|
||||
MakeDataMonitor("monitor_b", &capture_b, &mutex)));
|
||||
|
||||
// Run DH key exchange; setup encryption contexts for channels.
|
||||
auto [context_a, context_b] = DoDhKeyExchange(&channel_a, &channel_b);
|
||||
ASSERT_NE(context_a, nullptr);
|
||||
ASSERT_NE(context_b, nullptr);
|
||||
channel_a.EnableEncryption(context_a);
|
||||
channel_b.EnableEncryption(context_b);
|
||||
|
||||
EXPECT_EQ(channel_a.GetType(), "ENCRYPTED_BLUETOOTH");
|
||||
EXPECT_EQ(channel_b.GetType(), "ENCRYPTED_BLUETOOTH");
|
||||
|
||||
// Start data transfer
|
||||
ByteArray tx_message{"data message"};
|
||||
channel_a.Write(tx_message);
|
||||
ByteArray rx_message = std::move(channel_b.Read().result());
|
||||
|
||||
// Verify expectations.
|
||||
EXPECT_EQ(rx_message, tx_message);
|
||||
{
|
||||
absl::MutexLock lock(&mutex);
|
||||
std::string message{tx_message};
|
||||
EXPECT_TRUE(capture_a.find(message) == std::string::npos &&
|
||||
capture_b.find(message) == std::string::npos);
|
||||
}
|
||||
|
||||
// Shutdown test environment.
|
||||
channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION);
|
||||
channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION);
|
||||
}
|
||||
|
||||
TEST(BaseEndpointChannelTest, CanBesuspendedAndResumed) {
|
||||
// Setup test communication environment.
|
||||
Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b.
|
||||
Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a.
|
||||
TestEndpointChannel channel_a(&pipe_b.GetInputStream(),
|
||||
&pipe_a.GetOutputStream());
|
||||
TestEndpointChannel channel_b(&pipe_a.GetInputStream(),
|
||||
&pipe_b.GetOutputStream());
|
||||
|
||||
ON_CALL(channel_a, GetMedium).WillByDefault([]() {
|
||||
return Medium::WIFI_LAN;
|
||||
});
|
||||
ON_CALL(channel_b, GetMedium).WillByDefault([]() {
|
||||
return Medium::WIFI_LAN;
|
||||
});
|
||||
|
||||
EXPECT_EQ(channel_a.GetType(), "WIFI_LAN");
|
||||
EXPECT_EQ(channel_b.GetType(), "WIFI_LAN");
|
||||
|
||||
// Start data transfer
|
||||
ByteArray tx_message{"data message"};
|
||||
ByteArray more_message{"more data"};
|
||||
channel_a.Write(tx_message);
|
||||
ByteArray rx_message = std::move(channel_b.Read().result());
|
||||
|
||||
// Pause and make sure reader blocks.
|
||||
MultiThreadExecutor pause_resume_executor(2);
|
||||
channel_a.Pause();
|
||||
pause_resume_executor.Execute([&channel_a, &more_message]() {
|
||||
// Write will block until channel is resumed, or closed.
|
||||
EXPECT_TRUE(channel_a.Write(more_message).Ok());
|
||||
});
|
||||
CountDownLatch latch(1);
|
||||
ByteArray read_more;
|
||||
pause_resume_executor.Execute([&channel_b, &read_more, &latch]() {
|
||||
// Read will block until channel is resumed, or closed.
|
||||
auto response = channel_b.Read();
|
||||
EXPECT_TRUE(response.ok());
|
||||
read_more = std::move(response.result());
|
||||
latch.CountDown();
|
||||
});
|
||||
absl::SleepFor(absl::Milliseconds(500));
|
||||
EXPECT_TRUE(read_more.Empty());
|
||||
|
||||
// Resume; verify that data transfer comepleted.
|
||||
channel_a.Resume();
|
||||
EXPECT_TRUE(latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_EQ(read_more, more_message);
|
||||
|
||||
// Shutdown test environment.
|
||||
channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION);
|
||||
channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION);
|
||||
}
|
||||
|
||||
TEST(BaseEndpointChannelTest, ReadAfterInputStreamClosed) {
|
||||
Pipe pipe;
|
||||
InputStream& input_stream = pipe.GetInputStream();
|
||||
OutputStream& output_stream = pipe.GetOutputStream();
|
||||
|
||||
TestEndpointChannel test_channel(&input_stream, &output_stream);
|
||||
|
||||
// Close the output stream before trying to read from the input.
|
||||
output_stream.Close();
|
||||
|
||||
// Trying to read should fail gracefully with an IO error.
|
||||
ExceptionOr<ByteArray> read_data = test_channel.Read();
|
||||
|
||||
ASSERT_FALSE(read_data.ok());
|
||||
ASSERT_TRUE(read_data.GetException().Raised(Exception::kIo));
|
||||
}
|
||||
|
||||
TEST(BaseEndpointChannelTest, ReadUnencryptedFrameOnEncryptedChannel) {
|
||||
// Setup test communication environment.
|
||||
Pipe pipe_a; // channel_a writes to pipe_a, reads from pipe_b.
|
||||
Pipe pipe_b; // channel_b writes to pipe_b, reads from pipe_a.
|
||||
TestEndpointChannel channel_a(&pipe_b.GetInputStream(),
|
||||
&pipe_a.GetOutputStream());
|
||||
TestEndpointChannel channel_b(&pipe_a.GetInputStream(),
|
||||
&pipe_b.GetOutputStream());
|
||||
|
||||
ON_CALL(channel_a, GetMedium).WillByDefault([]() {
|
||||
return Medium::BLUETOOTH;
|
||||
});
|
||||
ON_CALL(channel_b, GetMedium).WillByDefault([]() {
|
||||
return Medium::BLUETOOTH;
|
||||
});
|
||||
|
||||
// Run DH key exchange; setup encryption contexts for channels. But only
|
||||
// encrypt |channel_b|.
|
||||
auto [context_a, context_b] = DoDhKeyExchange(&channel_a, &channel_b);
|
||||
ASSERT_NE(context_a, nullptr);
|
||||
ASSERT_NE(context_b, nullptr);
|
||||
channel_b.EnableEncryption(context_b);
|
||||
|
||||
EXPECT_EQ(channel_a.GetType(), "BLUETOOTH");
|
||||
EXPECT_EQ(channel_b.GetType(), "ENCRYPTED_BLUETOOTH");
|
||||
|
||||
// An unencrypted KeepAlive should succeed.
|
||||
ByteArray keep_alive_message = parser::ForKeepAlive();
|
||||
channel_a.Write(keep_alive_message);
|
||||
ExceptionOr<ByteArray> result = channel_b.Read();
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result(), keep_alive_message);
|
||||
|
||||
// An unencrypted data frame should fail.
|
||||
ByteArray tx_message{"data message"};
|
||||
channel_a.Write(tx_message);
|
||||
result = channel_b.Read();
|
||||
EXPECT_FALSE(result.ok());
|
||||
EXPECT_EQ(result.exception(), Exception::kInvalidProtocolBuffer);
|
||||
|
||||
// Shutdown test environment.
|
||||
channel_a.Close(DisconnectionReason::LOCAL_DISCONNECTION);
|
||||
channel_b.Close(DisconnectionReason::REMOTE_DISCONNECTION);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,538 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_INTERNAL_BASE_PCP_HANDLER_H_
|
||||
#define CORE_INTERNAL_BASE_PCP_HANDLER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "securegcm/d2d_connection_context_v1.h"
|
||||
#include "securegcm/ukey2_handshake.h"
|
||||
#include "absl/container/btree_map.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/bwu_manager.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/encryption_runner.h"
|
||||
#include "core/internal/endpoint_channel_manager.h"
|
||||
#include "core/internal/endpoint_manager.h"
|
||||
#include "core/internal/mediums/mediums.h"
|
||||
#ifdef NO_WEBRTC
|
||||
#include "core/internal/mediums/webrtc_stub.h"
|
||||
#else
|
||||
#include "core/internal/mediums/webrtc.h"
|
||||
#endif
|
||||
#include "core/internal/pcp.h"
|
||||
#include "core/internal/pcp_handler.h"
|
||||
#include "core/listeners.h"
|
||||
#include "core/status.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/prng.h"
|
||||
#include "platform/public/atomic_boolean.h"
|
||||
#include "platform/public/atomic_reference.h"
|
||||
#include "platform/public/cancelable_alarm.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/future.h"
|
||||
#include "platform/public/scheduled_executor.h"
|
||||
#include "platform/public/single_thread_executor.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Represents the WebRtc state that mediums are connectable or not.
|
||||
enum class WebRtcState {
|
||||
kUndefined = 0,
|
||||
kConnectable = 1,
|
||||
kUnconnectable = 2,
|
||||
};
|
||||
|
||||
// Annotations for methods that need to run on PCP handler thread.
|
||||
// Use only in BasePcpHandler and derived classes.
|
||||
#define RUN_ON_PCP_HANDLER_THREAD() \
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(GetPcpHandlerThread())
|
||||
|
||||
// A base implementation of the PcpHandler interface that takes care of all
|
||||
// bookkeeping and handshake protocols that are common across all PcpHandler
|
||||
// implementations -- thus, every concrete PcpHandler implementation must extend
|
||||
// this class, so that they can focus exclusively on the medium-specific
|
||||
// operations.
|
||||
class BasePcpHandler : public PcpHandler,
|
||||
public EndpointManager::FrameProcessor {
|
||||
public:
|
||||
using FrameProcessor = EndpointManager::FrameProcessor;
|
||||
|
||||
// TODO(apolyudov): Add SecureRandom.
|
||||
BasePcpHandler(Mediums* mediums, EndpointManager* endpoint_manager,
|
||||
EndpointChannelManager* channel_manager,
|
||||
BwuManager* bwu_manager, Pcp pcp);
|
||||
~BasePcpHandler() override;
|
||||
BasePcpHandler(BasePcpHandler&&) = delete;
|
||||
BasePcpHandler& operator=(BasePcpHandler&&) = delete;
|
||||
|
||||
// Starts advertising. Once successfully started, changes ClientProxy's state.
|
||||
// Notifies ConnectionListener (info.listener) in case of any event.
|
||||
// See
|
||||
// cpp/core/listeners.h
|
||||
Status StartAdvertising(ClientProxy* client, const std::string& service_id,
|
||||
const AdvertisingOptions& advertising_options,
|
||||
const ConnectionRequestInfo& info) override;
|
||||
|
||||
// Stops Advertising is active, and changes CLientProxy state,
|
||||
// otherwise does nothing.
|
||||
void StopAdvertising(ClientProxy* client) override;
|
||||
|
||||
// Starts discovery of endpoints that may be advertising.
|
||||
// Updates ClientProxy state once discovery started.
|
||||
// DiscoveryListener will get called in case of any event.
|
||||
Status StartDiscovery(ClientProxy* client, const std::string& service_id,
|
||||
const DiscoveryOptions& discovery_options,
|
||||
const DiscoveryListener& listener) override;
|
||||
|
||||
// Stops Discovery if it is active, and changes CLientProxy state,
|
||||
// otherwise does nothing.
|
||||
void StopDiscovery(ClientProxy* client) override;
|
||||
|
||||
void InjectEndpoint(ClientProxy* client, const std::string& service_id,
|
||||
const OutOfBandConnectionMetadata& metadata) override;
|
||||
|
||||
// Requests a newly discovered remote endpoint it to form a connection.
|
||||
// Updates state on ClientProxy.
|
||||
Status RequestConnection(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const ConnectionRequestInfo& info,
|
||||
const ConnectionOptions& connection_options) override;
|
||||
|
||||
// Called by either party to accept connection on their part.
|
||||
// Until both parties call it, connection will not reach a data phase.
|
||||
// Updates state in ClientProxy.
|
||||
Status AcceptConnection(ClientProxy* client, const std::string& endpoint_id,
|
||||
const PayloadListener& payload_listener) override;
|
||||
|
||||
// Called by either party to reject connection on their part.
|
||||
// If either party does call it, connection will terminate.
|
||||
// Updates state in ClientProxy.
|
||||
Status RejectConnection(ClientProxy* client,
|
||||
const std::string& endpoint_id) override;
|
||||
|
||||
// @EndpointManagerReaderThread
|
||||
void OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id,
|
||||
ClientProxy* client,
|
||||
proto::connections::Medium medium) override;
|
||||
|
||||
// Called when an endpoint disconnects while we're waiting for both sides to
|
||||
// approve/reject the connection.
|
||||
// @EndpointManagerThread
|
||||
void OnEndpointDisconnect(ClientProxy* client, const std::string& endpoint_id,
|
||||
CountDownLatch barrier) override;
|
||||
|
||||
Pcp GetPcp() const override { return pcp_; }
|
||||
Strategy GetStrategy() const override { return strategy_; }
|
||||
void DisconnectFromEndpointManager();
|
||||
|
||||
protected:
|
||||
// The result of a call to startAdvertisingImpl() or startDiscoveryImpl().
|
||||
struct StartOperationResult {
|
||||
Status status;
|
||||
// If success, the mediums on which we are now advertising/discovering, for
|
||||
// analytics.
|
||||
std::vector<proto::connections::Medium> mediums;
|
||||
};
|
||||
|
||||
// Represents an endpoint that we've discovered. Typically, the implementation
|
||||
// will know how to connect to this endpoint if asked. (eg. It holds on to a
|
||||
// BluetoothDevice)
|
||||
//
|
||||
// NOTE(DiscoveredEndpoint):
|
||||
// Specific protocol is expected to derive from it, as follows:
|
||||
// struct ProtocolEndpoint : public DiscoveredEndpoint {
|
||||
// ProtocolContext context;
|
||||
// };
|
||||
// Protocol then allocates instance with std::make_shared<ProtocolEndpoint>(),
|
||||
// and passes this instance to OnEndpointFound() method.
|
||||
// When calling OnEndpointLost(), protocol does not need to pass the same
|
||||
// instance (but it can if implementation desires to do so).
|
||||
// BasePcpHandler will hold on to the shared_ptr<DiscoveredEndpoint>.
|
||||
struct DiscoveredEndpoint {
|
||||
DiscoveredEndpoint(std::string endpoint_id, ByteArray endpoint_info,
|
||||
std::string service_id,
|
||||
proto::connections::Medium medium,
|
||||
WebRtcState web_rtc_state)
|
||||
: endpoint_id(std::move(endpoint_id)),
|
||||
endpoint_info(std::move(endpoint_info)),
|
||||
service_id(std::move(service_id)),
|
||||
medium(medium),
|
||||
web_rtc_state(web_rtc_state) {}
|
||||
virtual ~DiscoveredEndpoint() = default;
|
||||
|
||||
std::string endpoint_id;
|
||||
ByteArray endpoint_info;
|
||||
std::string service_id;
|
||||
proto::connections::Medium medium;
|
||||
WebRtcState web_rtc_state;
|
||||
};
|
||||
|
||||
struct BluetoothEndpoint : public DiscoveredEndpoint {
|
||||
BluetoothEndpoint(DiscoveredEndpoint endpoint, BluetoothDevice device)
|
||||
: DiscoveredEndpoint(std::move(endpoint)),
|
||||
bluetooth_device(std::move(device)) {}
|
||||
|
||||
BluetoothDevice bluetooth_device;
|
||||
};
|
||||
|
||||
struct BleEndpoint : public BasePcpHandler::DiscoveredEndpoint {
|
||||
BleEndpoint(DiscoveredEndpoint endpoint, BlePeripheral peripheral)
|
||||
: DiscoveredEndpoint(std::move(endpoint)),
|
||||
ble_peripheral(std::move(peripheral)) {}
|
||||
BlePeripheral ble_peripheral;
|
||||
};
|
||||
|
||||
struct WifiLanEndpoint : public DiscoveredEndpoint {
|
||||
WifiLanEndpoint(DiscoveredEndpoint endpoint,
|
||||
const NsdServiceInfo& service_info)
|
||||
: DiscoveredEndpoint(std::move(endpoint)), service_info(service_info) {}
|
||||
|
||||
NsdServiceInfo service_info;
|
||||
};
|
||||
|
||||
struct WebRtcEndpoint : public DiscoveredEndpoint {
|
||||
WebRtcEndpoint(DiscoveredEndpoint endpoint, mediums::WebrtcPeerId peer_id)
|
||||
: DiscoveredEndpoint(std::move(endpoint)),
|
||||
peer_id(std::move(peer_id)) {}
|
||||
|
||||
mediums::WebrtcPeerId peer_id;
|
||||
};
|
||||
|
||||
struct ConnectImplResult {
|
||||
proto::connections::Medium medium =
|
||||
proto::connections::Medium::UNKNOWN_MEDIUM;
|
||||
Status status = {Status::kError};
|
||||
std::unique_ptr<EndpointChannel> endpoint_channel;
|
||||
};
|
||||
|
||||
void RunOnPcpHandlerThread(const std::string& name, Runnable runnable);
|
||||
|
||||
BluetoothDevice GetRemoteBluetoothDevice(
|
||||
const std::string& remote_bluetooth_mac_address);
|
||||
|
||||
void OnEndpointFound(ClientProxy* client,
|
||||
std::shared_ptr<DiscoveredEndpoint> endpoint)
|
||||
RUN_ON_PCP_HANDLER_THREAD();
|
||||
|
||||
void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint)
|
||||
RUN_ON_PCP_HANDLER_THREAD();
|
||||
|
||||
Exception OnIncomingConnection(
|
||||
ClientProxy* client, const ByteArray& remote_endpoint_info,
|
||||
std::unique_ptr<EndpointChannel> endpoint_channel,
|
||||
proto::connections::Medium medium); // throws Exception::IO
|
||||
|
||||
virtual bool HasOutgoingConnections(ClientProxy* client) const;
|
||||
virtual bool HasIncomingConnections(ClientProxy* client) const;
|
||||
|
||||
virtual bool CanSendOutgoingConnection(ClientProxy* client) const;
|
||||
virtual bool CanReceiveIncomingConnection(ClientProxy* client) const;
|
||||
|
||||
virtual StartOperationResult StartAdvertisingImpl(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& local_endpoint_id,
|
||||
const ByteArray& local_endpoint_info,
|
||||
const AdvertisingOptions& advertising_options)
|
||||
RUN_ON_PCP_HANDLER_THREAD() = 0;
|
||||
|
||||
virtual Status StopAdvertisingImpl(ClientProxy* client)
|
||||
RUN_ON_PCP_HANDLER_THREAD() = 0;
|
||||
|
||||
virtual StartOperationResult StartDiscoveryImpl(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const DiscoveryOptions& discovery_options)
|
||||
RUN_ON_PCP_HANDLER_THREAD() = 0;
|
||||
|
||||
virtual Status StopDiscoveryImpl(ClientProxy* client)
|
||||
RUN_ON_PCP_HANDLER_THREAD() = 0;
|
||||
|
||||
virtual Status InjectEndpointImpl(ClientProxy* client,
|
||||
const std::string& service_id,
|
||||
const OutOfBandConnectionMetadata& metadata)
|
||||
RUN_ON_PCP_HANDLER_THREAD() = 0;
|
||||
|
||||
virtual ConnectImplResult ConnectImpl(ClientProxy* client,
|
||||
DiscoveredEndpoint* endpoint)
|
||||
RUN_ON_PCP_HANDLER_THREAD() = 0;
|
||||
|
||||
virtual std::vector<proto::connections::Medium>
|
||||
GetConnectionMediumsByPriority() = 0;
|
||||
virtual proto::connections::Medium GetDefaultUpgradeMedium() = 0;
|
||||
|
||||
// Returns the first discovered endpoint for the given endpoint_id.
|
||||
DiscoveredEndpoint* GetDiscoveredEndpoint(const std::string& endpoint_id);
|
||||
|
||||
// Returns a vector of discovered endpoints, sorted in order of decreasing
|
||||
// preference.
|
||||
std::vector<BasePcpHandler::DiscoveredEndpoint*> GetDiscoveredEndpoints(
|
||||
const std::string& endpoint_id);
|
||||
|
||||
// Returns a vector of discovered endpoints that share a given Medium.
|
||||
std::vector<BasePcpHandler::DiscoveredEndpoint*> GetDiscoveredEndpoints(
|
||||
const proto::connections::Medium medium);
|
||||
|
||||
mediums::WebrtcPeerId CreatePeerIdFromAdvertisement(
|
||||
const string& service_id, const string& endpoint_id,
|
||||
const ByteArray& endpoint_info);
|
||||
|
||||
SingleThreadExecutor* GetPcpHandlerThread()
|
||||
ABSL_LOCK_RETURNED(serial_executor_) {
|
||||
return &serial_executor_;
|
||||
}
|
||||
|
||||
Mediums* mediums_;
|
||||
EndpointManager* endpoint_manager_;
|
||||
EndpointChannelManager* channel_manager_;
|
||||
|
||||
private:
|
||||
struct PendingConnectionInfo {
|
||||
PendingConnectionInfo() = default;
|
||||
PendingConnectionInfo(PendingConnectionInfo&& other) = default;
|
||||
PendingConnectionInfo& operator=(PendingConnectionInfo&&) = default;
|
||||
~PendingConnectionInfo();
|
||||
|
||||
// Passes crypto context that we acquired in DH session for temporary
|
||||
// ownership here.
|
||||
void SetCryptoContext(std::unique_ptr<securegcm::UKey2Handshake> ukey2);
|
||||
|
||||
// Pass Accept notification to client.
|
||||
void LocalEndpointAcceptedConnection(
|
||||
const std::string& endpoint_id,
|
||||
const PayloadListener& payload_listener);
|
||||
|
||||
// Pass Reject notification to client.
|
||||
void LocalEndpointRejectedConnection(const std::string& endpoint_id);
|
||||
|
||||
// Client state tracker to report events to. Never changes. Always valid.
|
||||
ClientProxy* client = nullptr;
|
||||
// Peer endpoint info, or empty, if not discovered yet. May change.
|
||||
ByteArray remote_endpoint_info;
|
||||
std::int32_t nonce = 0;
|
||||
bool is_incoming = false;
|
||||
absl::Time start_time{absl::InfinitePast()};
|
||||
// Client callbacks. Always valid.
|
||||
ConnectionListener listener;
|
||||
ConnectionOptions connection_options;
|
||||
|
||||
// Only set for outgoing connections. If set, we must call
|
||||
// result->Set() when connection is established, or rejected.
|
||||
std::weak_ptr<Future<Status>> result;
|
||||
|
||||
// Only (possibly) vector for incoming connections.
|
||||
std::vector<proto::connections::Medium> supported_mediums;
|
||||
|
||||
// Keep track of a channel before we pass it to EndpointChannelManager.
|
||||
std::unique_ptr<EndpointChannel> channel;
|
||||
|
||||
// Crypto context; initially empty; established first thing after channel
|
||||
// creation by running UKey2 session. While it is in progress, we keep track
|
||||
// of channel ourselves. Once it is done, we pass channel over to
|
||||
// EndpointChannelManager. We keep crypto context until connection is
|
||||
// accepted. Crypto context is passed over to channel_manager_ before
|
||||
// switching to connected state, where Payload may be exchanged.
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2;
|
||||
|
||||
// Used in AnalyticsRecorder for devices connection tracking.
|
||||
std::string connection_token;
|
||||
};
|
||||
|
||||
// @EncryptionRunnerThread
|
||||
// Called internally when DH session has negotiated a key successfully.
|
||||
void OnEncryptionSuccessImpl(const std::string& endpoint_id,
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
|
||||
const std::string& auth_token,
|
||||
const ByteArray& raw_auth_token);
|
||||
|
||||
// @EncryptionRunnerThread
|
||||
// Called internally when DH session was not able to negotiate a key.
|
||||
void OnEncryptionFailureImpl(const std::string& endpoint_id,
|
||||
EndpointChannel* channel);
|
||||
|
||||
EncryptionRunner::ResultListener GetResultListener();
|
||||
|
||||
void OnEncryptionSuccessRunnable(
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
|
||||
const std::string& auth_token, const ByteArray& raw_auth_token);
|
||||
void OnEncryptionFailureRunnable(const std::string& endpoint_id,
|
||||
EndpointChannel* endpoint_channel);
|
||||
|
||||
static Exception WriteConnectionRequestFrame(
|
||||
EndpointChannel* endpoint_channel, const std::string& local_endpoint_id,
|
||||
const ByteArray& local_endpoint_info, std::int32_t nonce,
|
||||
const std::vector<proto::connections::Medium>& supported_mediums,
|
||||
std::int32_t keep_alive_interval_millis,
|
||||
std::int32_t keep_alive_timeout_millis);
|
||||
|
||||
static constexpr absl::Duration kConnectionRequestReadTimeout =
|
||||
absl::Seconds(2);
|
||||
static constexpr absl::Duration kRejectedConnectionCloseDelay =
|
||||
absl::Seconds(2);
|
||||
static constexpr int kConnectionTokenLength = 8;
|
||||
|
||||
// Returns true if the new endpoint is preferred over the old endpoint.
|
||||
bool IsPreferred(const BasePcpHandler::DiscoveredEndpoint& new_endpoint,
|
||||
const BasePcpHandler::DiscoveredEndpoint& old_endpoint);
|
||||
|
||||
// Returns true, if connection party should respect the specified topology.
|
||||
bool ShouldEnforceTopologyConstraints(
|
||||
const AdvertisingOptions& local_advertising_options) const;
|
||||
|
||||
// Returns true, if connection party should attempt to upgrade itself to
|
||||
// use a higher bandwidth medium, if it is available.
|
||||
bool AutoUpgradeBandwidth(
|
||||
const AdvertisingOptions& local_advertising_options) const;
|
||||
|
||||
// Returns true if the incoming connection should be killed. This only
|
||||
// happens when an incoming connection arrives while we have an outgoing
|
||||
// connection to the same endpoint and we need to stop one connection.
|
||||
bool BreakTie(ClientProxy* client, const std::string& endpoint_id,
|
||||
std::int32_t incoming_nonce, EndpointChannel* channel);
|
||||
// We're not sure how far our outgoing connection has gotten. We may (or may
|
||||
// not) have called ClientProxy::OnConnectionInitiated. Therefore, we'll
|
||||
// call both preInit and preResult failures.
|
||||
void ProcessTieBreakLoss(ClientProxy* client, const std::string& endpoint_id,
|
||||
PendingConnectionInfo* info);
|
||||
|
||||
// Returns true if the bluetooth endpoint based on remote bluetooth mac
|
||||
// address is created and appended into discovered_endpoints_ with key
|
||||
// endpoint_id.
|
||||
bool AppendRemoteBluetoothMacAddressEndpoint(
|
||||
const std::string& endpoint_id,
|
||||
const std::string& remote_bluetooth_mac_address,
|
||||
const DiscoveryOptions& local_discovery_options);
|
||||
|
||||
// Returns true if the webrtc endpoint is created and appended into
|
||||
// discovered_endpoints_ with key endpoint_id.
|
||||
bool AppendWebRTCEndpoint(const std::string& endpoint_id,
|
||||
const DiscoveryOptions& local_discovery_options);
|
||||
|
||||
void ProcessPreConnectionInitiationFailure(
|
||||
ClientProxy* client, Medium medium, const std::string& endpoint_id,
|
||||
EndpointChannel* channel, bool is_incoming, absl::Time start_time,
|
||||
Status status, Future<Status>* result);
|
||||
void ProcessPreConnectionResultFailure(ClientProxy* client,
|
||||
const std::string& endpoint_id);
|
||||
|
||||
// Called when either side accepts/rejects the connection, but only takes
|
||||
// effect after both have accepted or one side has rejected.
|
||||
//
|
||||
// NOTE: We also take in a 'can_close_immediately' variable. This is because
|
||||
// any writes in transit are dropped when we close. To avoid having a reject
|
||||
// write being dropped (which causes the other side to report
|
||||
// onResult(DISCONNECTED) instead of onResult(REJECTED)), we delay our
|
||||
// close. If the other side behaves properly, we shouldn't even see the
|
||||
// delay (because they will also close the connection).
|
||||
void EvaluateConnectionResult(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
bool can_close_immediately);
|
||||
|
||||
ExceptionOr<OfflineFrame> ReadConnectionRequestFrame(
|
||||
EndpointChannel* channel);
|
||||
|
||||
// Returns an 8 characters length hashed string generated via a token byte
|
||||
// array.
|
||||
std::string GetHashedConnectionToken(const ByteArray& token_bytes);
|
||||
|
||||
static void LogConnectionAttemptFailure(ClientProxy* client, Medium medium,
|
||||
const std::string& endpoint_id,
|
||||
bool is_incoming,
|
||||
absl::Time start_time,
|
||||
EndpointChannel* endpoint_channel);
|
||||
|
||||
static void LogConnectionAttemptSuccess(
|
||||
const std::string& endpoint_id,
|
||||
const PendingConnectionInfo& connection_info);
|
||||
|
||||
// Returns true if the client cancels the operation in progress through the
|
||||
// endpoint id. This is done by CancellationFlag.
|
||||
static bool Cancelled(ClientProxy* client, const std::string& endpoint_id);
|
||||
|
||||
void WaitForLatch(const std::string& method_name, CountDownLatch* latch);
|
||||
Status WaitForResult(const std::string& method_name, std::int64_t client_id,
|
||||
Future<Status>* future);
|
||||
bool MediumSupportedByClientOptions(
|
||||
const proto::connections::Medium& medium,
|
||||
const ConnectionOptions& connection_options) const;
|
||||
std::vector<proto::connections::Medium>
|
||||
GetSupportedConnectionMediumsByPriority(
|
||||
const ConnectionOptions& local_option);
|
||||
std::string GetStringValueOfSupportedMediums(
|
||||
const ConnectionOptions& connection_options) const;
|
||||
std::string GetStringValueOfSupportedMediums(
|
||||
const AdvertisingOptions& advertising_options) const;
|
||||
std::string GetStringValueOfSupportedMediums(
|
||||
const DiscoveryOptions& discovery_options) const;
|
||||
|
||||
// The endpoint id in high visibility mode is stable for 30 seconds, while in
|
||||
// low visibility mode it always rotates. We assume a client is trying to
|
||||
// rotate endpoint id when the advertising options is "low power" (3P) or
|
||||
// "disable Bluetooth classic" (1P).
|
||||
bool ShouldEnterHighVisibilityMode(
|
||||
const AdvertisingOptions& advertising_options);
|
||||
|
||||
// Returns the intersection of supported mediums based on the mediums reported
|
||||
// by the remote client and the local client's advertising options.
|
||||
BooleanMediumSelector ComputeIntersectionOfSupportedMediums(
|
||||
const PendingConnectionInfo& connection_info);
|
||||
|
||||
void OptionsAllowed(const BooleanMediumSelector& allowed,
|
||||
std::ostringstream& result) const;
|
||||
|
||||
ScheduledExecutor alarm_executor_;
|
||||
SingleThreadExecutor serial_executor_;
|
||||
|
||||
// A map of endpoint id -> PendingConnectionInfo. Entries in this map imply
|
||||
// that there is an active connection to the endpoint and we're waiting for
|
||||
// both sides to accept before allowing payloads through. Once the fate of
|
||||
// the connection is decided (either accepted or rejected), it should be
|
||||
// removed from this map.
|
||||
absl::flat_hash_map<std::string, PendingConnectionInfo> pending_connections_;
|
||||
// A map of endpoint id -> DiscoveredEndpoint.
|
||||
absl::btree_multimap<std::string, std::shared_ptr<DiscoveredEndpoint>>
|
||||
discovered_endpoints_;
|
||||
// A map of endpoint id -> alarm. These alarms delay closing the
|
||||
// EndpointChannel to give the other side enough time to read the rejection
|
||||
// message. It's expected that the other side will close the connection
|
||||
// after reading the message (in which case, this alarm should be cancelled
|
||||
// as it's no longer needed), but this alarm is the fallback in case that
|
||||
// doesn't happen.
|
||||
absl::flat_hash_map<std::string, CancelableAlarm> pending_alarms_;
|
||||
|
||||
// The active ClientProxy's connection lifecycle listener. Non-null while
|
||||
// advertising.
|
||||
ConnectionListener advertising_listener_;
|
||||
|
||||
AtomicBoolean stop_{false};
|
||||
Pcp pcp_;
|
||||
Strategy strategy_{PcpToStrategy(pcp_)};
|
||||
Prng prng_;
|
||||
EncryptionRunner encryption_runner_;
|
||||
BwuManager* bwu_manager_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BASE_PCP_HANDLER_H_
|
||||
@@ -1,796 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/base_pcp_handler.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "connections/implementation/proto/offline_wire_formats.pb.h"
|
||||
#include "core/internal/base_endpoint_channel.h"
|
||||
#include "core/internal/bwu_manager.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/encryption_runner.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "core/listeners.h"
|
||||
#include "core/params.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/base/medium_environment.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/pipe.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
using ::location::nearby::proto::connections::Medium;
|
||||
using ::testing::_;
|
||||
using ::testing::AtLeast;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::MockFunction;
|
||||
using ::testing::Return;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
constexpr std::array<char, 6> kFakeMacAddress = {'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
constexpr BooleanMediumSelector kTestCases[] = {
|
||||
BooleanMediumSelector{},
|
||||
BooleanMediumSelector{
|
||||
.bluetooth = true,
|
||||
},
|
||||
BooleanMediumSelector{
|
||||
.wifi_lan = true,
|
||||
},
|
||||
BooleanMediumSelector{
|
||||
.bluetooth = true,
|
||||
.wifi_lan = true,
|
||||
},
|
||||
};
|
||||
|
||||
class MockEndpointChannel : public BaseEndpointChannel {
|
||||
public:
|
||||
explicit MockEndpointChannel(Pipe* reader, Pipe* writer)
|
||||
: BaseEndpointChannel("channel", &reader->GetInputStream(),
|
||||
&writer->GetOutputStream()) {}
|
||||
|
||||
ExceptionOr<ByteArray> DoRead() { return BaseEndpointChannel::Read(); }
|
||||
Exception DoWrite(const ByteArray& data) {
|
||||
if (broken_write_) {
|
||||
return {Exception::kFailed};
|
||||
}
|
||||
return BaseEndpointChannel::Write(data);
|
||||
}
|
||||
absl::Time DoGetLastReadTimestamp() {
|
||||
return BaseEndpointChannel::GetLastReadTimestamp();
|
||||
}
|
||||
|
||||
MOCK_METHOD(ExceptionOr<ByteArray>, Read, (), (override));
|
||||
MOCK_METHOD(Exception, Write, (const ByteArray& data), (override));
|
||||
MOCK_METHOD(void, CloseImpl, (), (override));
|
||||
MOCK_METHOD(proto::connections::Medium, GetMedium, (), (const override));
|
||||
MOCK_METHOD(std::string, GetType, (), (const override));
|
||||
MOCK_METHOD(std::string, GetName, (), (const override));
|
||||
MOCK_METHOD(bool, IsPaused, (), (const override));
|
||||
MOCK_METHOD(void, Pause, (), (override));
|
||||
MOCK_METHOD(void, Resume, (), (override));
|
||||
MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override));
|
||||
|
||||
bool broken_write_{false};
|
||||
};
|
||||
|
||||
class MockPcpHandler : public BasePcpHandler {
|
||||
public:
|
||||
using DiscoveredEndpoint = BasePcpHandler::DiscoveredEndpoint;
|
||||
|
||||
MockPcpHandler(Mediums* m, EndpointManager* em, EndpointChannelManager* ecm,
|
||||
BwuManager* bwu)
|
||||
: BasePcpHandler(m, em, ecm, bwu, Pcp::kP2pCluster) {}
|
||||
|
||||
// Expose protected inner types of a base type for mocking.
|
||||
using BasePcpHandler::ConnectImplResult;
|
||||
using BasePcpHandler::DiscoveredEndpoint;
|
||||
using BasePcpHandler::StartOperationResult;
|
||||
|
||||
MOCK_METHOD(Strategy, GetStrategy, (), (const override));
|
||||
MOCK_METHOD(Pcp, GetPcp, (), (const override));
|
||||
|
||||
MOCK_METHOD(bool, HasOutgoingConnections, (ClientProxy * client),
|
||||
(const, override));
|
||||
MOCK_METHOD(bool, HasIncomingConnections, (ClientProxy * client),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(bool, CanSendOutgoingConnection, (ClientProxy * client),
|
||||
(const, override));
|
||||
MOCK_METHOD(bool, CanReceiveIncomingConnection, (ClientProxy * client),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(StartOperationResult, StartAdvertisingImpl,
|
||||
(ClientProxy * client, const std::string& service_id,
|
||||
const std::string& local_endpoint_id,
|
||||
const ByteArray& local_endpoint_info,
|
||||
const AdvertisingOptions& advertising_options),
|
||||
(override));
|
||||
MOCK_METHOD(Status, StopAdvertisingImpl, (ClientProxy * client), (override));
|
||||
MOCK_METHOD(StartOperationResult, StartDiscoveryImpl,
|
||||
(ClientProxy * client, const std::string& service_id,
|
||||
const DiscoveryOptions& discovery_options),
|
||||
(override));
|
||||
MOCK_METHOD(Status, StopDiscoveryImpl, (ClientProxy * client), (override));
|
||||
MOCK_METHOD(Status, InjectEndpointImpl,
|
||||
(ClientProxy * client, const std::string& service_id,
|
||||
const OutOfBandConnectionMetadata& metadata),
|
||||
(override));
|
||||
MOCK_METHOD(ConnectImplResult, ConnectImpl,
|
||||
(ClientProxy * client, DiscoveredEndpoint* endpoint), (override));
|
||||
MOCK_METHOD(proto::connections::Medium, GetDefaultUpgradeMedium, (),
|
||||
(override));
|
||||
|
||||
std::vector<proto::connections::Medium> GetConnectionMediumsByPriority()
|
||||
override {
|
||||
return std::vector<proto::connections::Medium>{
|
||||
proto::connections::WIFI_LAN, proto::connections::WEB_RTC,
|
||||
proto::connections::BLUETOOTH, proto::connections::BLE};
|
||||
}
|
||||
|
||||
// Mock adapters for protected non-virtual methods of a base class.
|
||||
void OnEndpointFound(ClientProxy* client,
|
||||
std::shared_ptr<DiscoveredEndpoint> endpoint)
|
||||
ABSL_NO_THREAD_SAFETY_ANALYSIS {
|
||||
BasePcpHandler::OnEndpointFound(client, std::move(endpoint));
|
||||
}
|
||||
void OnEndpointLost(ClientProxy* client, const DiscoveredEndpoint& endpoint)
|
||||
ABSL_NO_THREAD_SAFETY_ANALYSIS {
|
||||
BasePcpHandler::OnEndpointLost(client, endpoint);
|
||||
}
|
||||
std::vector<BasePcpHandler::DiscoveredEndpoint*> GetDiscoveredEndpoints(
|
||||
const std::string& endpoint_id) {
|
||||
return BasePcpHandler::GetDiscoveredEndpoints(endpoint_id);
|
||||
}
|
||||
|
||||
std::vector<proto::connections::Medium> GetDiscoveryMediums(
|
||||
ClientProxy* client) {
|
||||
auto allowed = client->GetDiscoveryOptions().CompatibleOptions().allowed;
|
||||
return GetMediumsFromSelector(allowed);
|
||||
}
|
||||
|
||||
std::vector<proto::connections::Medium> GetMediumsFromSelector(
|
||||
BooleanMediumSelector allowed) {
|
||||
return allowed.GetMediums(true);
|
||||
}
|
||||
};
|
||||
|
||||
class MockContext {
|
||||
public:
|
||||
explicit MockContext(std::atomic_int* destroyed = nullptr)
|
||||
: destroyed_{destroyed} {}
|
||||
MockContext(MockContext&& other) { *this = std::move(other); }
|
||||
MockContext& operator=(MockContext&& other) {
|
||||
destroyed_ = other.destroyed_;
|
||||
other.destroyed_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
~MockContext() {
|
||||
if (destroyed_) (*destroyed_)++;
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic_int* destroyed_;
|
||||
};
|
||||
|
||||
struct MockDiscoveredEndpoint : public MockPcpHandler::DiscoveredEndpoint {
|
||||
MockDiscoveredEndpoint(DiscoveredEndpoint endpoint, MockContext context)
|
||||
: DiscoveredEndpoint(std::move(endpoint)), context(std::move(context)) {}
|
||||
|
||||
MockContext context;
|
||||
};
|
||||
|
||||
class BasePcpHandlerTest
|
||||
: public ::testing::TestWithParam<BooleanMediumSelector> {
|
||||
protected:
|
||||
struct MockConnectionListener {
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info)>>
|
||||
initiated_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id)>> accepted_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const Status& status)>>
|
||||
rejected_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id)>>
|
||||
disconnected_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
std::int32_t quality)>>
|
||||
bandwidth_changed_cb;
|
||||
};
|
||||
struct MockDiscoveryListener {
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
const std::string& service_id)>>
|
||||
endpoint_found_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id)>>
|
||||
endpoint_lost_cb;
|
||||
StrictMock<
|
||||
MockFunction<void(const std::string& endpoint_id, DistanceInfo info)>>
|
||||
endpoint_distance_changed_cb;
|
||||
};
|
||||
|
||||
void StartAdvertising(ClientProxy* client, MockPcpHandler* pcp_handler,
|
||||
BooleanMediumSelector allowed = GetParam()) {
|
||||
std::string service_id{"service"};
|
||||
AdvertisingOptions advertising_options{
|
||||
{
|
||||
Strategy::kP2pCluster,
|
||||
allowed,
|
||||
},
|
||||
true, // auto_upgrade_bandwidth
|
||||
true, // enforce_topology_constraints
|
||||
};
|
||||
ConnectionRequestInfo info{
|
||||
.endpoint_info = ByteArray{"remote_endpoint_name"},
|
||||
.listener = connection_listener_,
|
||||
};
|
||||
EXPECT_CALL(*pcp_handler, StartAdvertisingImpl(client, service_id, _,
|
||||
info.endpoint_info, _))
|
||||
.WillOnce(Return(MockPcpHandler::StartOperationResult{
|
||||
.status = {Status::kSuccess},
|
||||
.mediums = pcp_handler->GetMediumsFromSelector(allowed),
|
||||
}));
|
||||
EXPECT_EQ(pcp_handler->StartAdvertising(client, service_id,
|
||||
advertising_options, info),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_TRUE(client->IsAdvertising());
|
||||
}
|
||||
|
||||
void StartDiscovery(ClientProxy* client, MockPcpHandler* pcp_handler,
|
||||
BooleanMediumSelector allowed = GetParam()) {
|
||||
std::string service_id{"service"};
|
||||
DiscoveryOptions discovery_options{
|
||||
{
|
||||
Strategy::kP2pCluster,
|
||||
allowed,
|
||||
},
|
||||
true, // auto_upgrade_bandwidth
|
||||
true, // enforce_topology_constraints
|
||||
5000, // keep_alive_interval_millis
|
||||
3000, // keep_alive_timeout_millis
|
||||
};
|
||||
EXPECT_CALL(*pcp_handler, StartDiscoveryImpl(client, service_id, _))
|
||||
.WillOnce(Return(MockPcpHandler::StartOperationResult{
|
||||
.status = {Status::kSuccess},
|
||||
.mediums = pcp_handler->GetMediumsFromSelector(allowed),
|
||||
}));
|
||||
EXPECT_EQ(pcp_handler->StartDiscovery(client, service_id, discovery_options,
|
||||
discovery_listener_),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_TRUE(client->IsDiscovering());
|
||||
}
|
||||
|
||||
std::pair<std::unique_ptr<MockEndpointChannel>,
|
||||
std::unique_ptr<MockEndpointChannel>>
|
||||
SetupConnection(Pipe& pipe_a, Pipe& pipe_b,
|
||||
proto::connections::Medium medium) { // NOLINT
|
||||
auto channel_a = std::make_unique<MockEndpointChannel>(&pipe_b, &pipe_a);
|
||||
auto channel_b = std::make_unique<MockEndpointChannel>(&pipe_a, &pipe_b);
|
||||
// On initiator (A) side, we drop the first write, since this is a
|
||||
// connection establishment packet, and we don't have the peer entity, just
|
||||
// the peer channel. The rest of the exchange must happen for the benefit of
|
||||
// DH key exchange.
|
||||
EXPECT_CALL(*channel_a, Read())
|
||||
.WillRepeatedly(Invoke(
|
||||
[channel = channel_a.get()]() { return channel->DoRead(); }));
|
||||
EXPECT_CALL(*channel_a, Write(_))
|
||||
.WillOnce(Return(Exception{Exception::kSuccess}))
|
||||
.WillRepeatedly(
|
||||
Invoke([channel = channel_a.get()](const ByteArray& data) {
|
||||
return channel->DoWrite(data);
|
||||
}));
|
||||
EXPECT_CALL(*channel_a, GetMedium).WillRepeatedly(Return(medium));
|
||||
EXPECT_CALL(*channel_a, GetLastReadTimestamp)
|
||||
.WillRepeatedly(Return(absl::Now()));
|
||||
EXPECT_CALL(*channel_a, IsPaused).WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(*channel_b, Read())
|
||||
.WillRepeatedly(Invoke(
|
||||
[channel = channel_b.get()]() { return channel->DoRead(); }));
|
||||
EXPECT_CALL(*channel_b, Write(_))
|
||||
.WillRepeatedly(
|
||||
Invoke([channel = channel_b.get()](const ByteArray& data) {
|
||||
return channel->DoWrite(data);
|
||||
}));
|
||||
EXPECT_CALL(*channel_b, GetMedium).WillRepeatedly(Return(medium));
|
||||
EXPECT_CALL(*channel_b, GetLastReadTimestamp)
|
||||
.WillRepeatedly(Return(absl::Now()));
|
||||
EXPECT_CALL(*channel_b, IsPaused).WillRepeatedly(Return(false));
|
||||
return std::make_pair(std::move(channel_a), std::move(channel_b));
|
||||
}
|
||||
|
||||
void RequestConnection(const std::string& endpoint_id,
|
||||
std::unique_ptr<MockEndpointChannel> channel_a,
|
||||
MockEndpointChannel* channel_b, ClientProxy* client,
|
||||
MockPcpHandler* pcp_handler,
|
||||
proto::connections::Medium connect_medium,
|
||||
std::atomic_int* flag = nullptr,
|
||||
Status expected_result = {Status::kSuccess}) {
|
||||
ConnectionRequestInfo info{
|
||||
.endpoint_info = ByteArray{"ABCD"},
|
||||
.listener = connection_listener_,
|
||||
};
|
||||
ConnectionOptions connection_options{
|
||||
.remote_bluetooth_mac_address =
|
||||
ByteArray{std::string("\x12\x34\x56\x78\x9a\xbc")},
|
||||
.keep_alive_interval_millis =
|
||||
FeatureFlags::GetInstance().GetFlags().keep_alive_interval_millis,
|
||||
.keep_alive_timeout_millis =
|
||||
FeatureFlags::GetInstance().GetFlags().keep_alive_timeout_millis,
|
||||
};
|
||||
EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call);
|
||||
EXPECT_CALL(*pcp_handler, CanSendOutgoingConnection)
|
||||
.WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(*pcp_handler, GetStrategy)
|
||||
.WillRepeatedly(Return(Strategy::kP2pCluster));
|
||||
if (expected_result == Status{Status::kSuccess}) {
|
||||
EXPECT_CALL(mock_connection_listener_.initiated_cb, Call).Times(1);
|
||||
}
|
||||
// Simulate successful discovery.
|
||||
auto encryption_runner = std::make_unique<EncryptionRunner>();
|
||||
auto allowed_mediums = pcp_handler->GetDiscoveryMediums(client);
|
||||
|
||||
EXPECT_CALL(*pcp_handler, ConnectImpl)
|
||||
.WillOnce(Invoke([&channel_a, connect_medium](
|
||||
ClientProxy* client,
|
||||
MockPcpHandler::DiscoveredEndpoint* endpoint) {
|
||||
return MockPcpHandler::ConnectImplResult{
|
||||
.medium = connect_medium,
|
||||
.status = {Status::kSuccess},
|
||||
.endpoint_channel = std::move(channel_a),
|
||||
};
|
||||
}));
|
||||
|
||||
for (const auto& discovered_medium : allowed_mediums) {
|
||||
pcp_handler->OnEndpointFound(
|
||||
client,
|
||||
std::make_shared<MockDiscoveredEndpoint>(MockDiscoveredEndpoint{
|
||||
{
|
||||
endpoint_id,
|
||||
info.endpoint_info,
|
||||
"service",
|
||||
discovered_medium,
|
||||
WebRtcState::kUndefined,
|
||||
},
|
||||
MockContext{flag},
|
||||
}));
|
||||
}
|
||||
auto other_client = std::make_unique<ClientProxy>();
|
||||
|
||||
// Run peer crypto in advance, if channel_b is provided.
|
||||
// Otherwise stay in not-encrypted state.
|
||||
if (channel_b != nullptr) {
|
||||
encryption_runner->StartServer(other_client.get(), endpoint_id, channel_b,
|
||||
{});
|
||||
}
|
||||
EXPECT_EQ(pcp_handler->RequestConnection(client, endpoint_id, info,
|
||||
connection_options),
|
||||
expected_result);
|
||||
NEARBY_LOG(INFO, "Stopping Encryption Runner");
|
||||
}
|
||||
|
||||
Pipe pipe_a_;
|
||||
Pipe pipe_b_;
|
||||
MockConnectionListener mock_connection_listener_;
|
||||
MockDiscoveryListener mock_discovery_listener_;
|
||||
ConnectionListener connection_listener_{
|
||||
.initiated_cb = mock_connection_listener_.initiated_cb.AsStdFunction(),
|
||||
.accepted_cb = mock_connection_listener_.accepted_cb.AsStdFunction(),
|
||||
.rejected_cb = mock_connection_listener_.rejected_cb.AsStdFunction(),
|
||||
.disconnected_cb =
|
||||
mock_connection_listener_.disconnected_cb.AsStdFunction(),
|
||||
.bandwidth_changed_cb =
|
||||
mock_connection_listener_.bandwidth_changed_cb.AsStdFunction(),
|
||||
};
|
||||
DiscoveryListener discovery_listener_{
|
||||
.endpoint_found_cb =
|
||||
mock_discovery_listener_.endpoint_found_cb.AsStdFunction(),
|
||||
.endpoint_lost_cb =
|
||||
mock_discovery_listener_.endpoint_lost_cb.AsStdFunction(),
|
||||
.endpoint_distance_changed_cb =
|
||||
mock_discovery_listener_.endpoint_distance_changed_cb.AsStdFunction(),
|
||||
};
|
||||
MediumEnvironment& env_ = MediumEnvironment::Instance();
|
||||
};
|
||||
|
||||
TEST_P(BasePcpHandlerTest, ConstructorDestructorWorks) {
|
||||
env_.Start();
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
SUCCEED();
|
||||
bwu.Shutdown();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, StartAdvertisingChangesState) {
|
||||
env_.Start();
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartAdvertising(&client, &pcp_handler);
|
||||
bwu.Shutdown();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, StopAdvertisingChangesState) {
|
||||
env_.Start();
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartAdvertising(&client, &pcp_handler);
|
||||
EXPECT_CALL(pcp_handler, StopAdvertisingImpl(&client)).Times(1);
|
||||
EXPECT_TRUE(client.IsAdvertising());
|
||||
pcp_handler.StopAdvertising(&client);
|
||||
EXPECT_FALSE(client.IsAdvertising());
|
||||
bwu.Shutdown();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, StartDiscoveryChangesState) {
|
||||
env_.Start();
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
bwu.Shutdown();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, StopDiscoveryChangesState) {
|
||||
env_.Start();
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
EXPECT_CALL(pcp_handler, StopDiscoveryImpl(&client)).Times(1);
|
||||
EXPECT_TRUE(client.IsDiscovering());
|
||||
pcp_handler.StopDiscovery(&client);
|
||||
EXPECT_FALSE(client.IsDiscovering());
|
||||
bwu.Shutdown();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, RequestConnectionChangesState) {
|
||||
env_.Start();
|
||||
std::string endpoint_id{"1234"};
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
auto mediums = pcp_handler.GetDiscoveryMediums(&client);
|
||||
auto connect_medium = mediums[mediums.size() - 1];
|
||||
auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium);
|
||||
auto& channel_a = channel_pair.first;
|
||||
auto& channel_b = channel_pair.second;
|
||||
EXPECT_CALL(*channel_a, CloseImpl).Times(1);
|
||||
EXPECT_CALL(*channel_b, CloseImpl).Times(1);
|
||||
EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0));
|
||||
RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client,
|
||||
&pcp_handler, connect_medium);
|
||||
NEARBY_LOG(INFO, "RequestConnection complete");
|
||||
channel_b->Close();
|
||||
bwu.Shutdown();
|
||||
pcp_handler.DisconnectFromEndpointManager();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, IoError_RequestConnectionFails) {
|
||||
env_.Start();
|
||||
std::string endpoint_id{"1234"};
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
auto mediums = pcp_handler.GetDiscoveryMediums(&client);
|
||||
auto connect_medium = mediums[mediums.size() - 1];
|
||||
auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium);
|
||||
auto& channel_a = channel_pair.first;
|
||||
auto& channel_b = channel_pair.second;
|
||||
EXPECT_CALL(*channel_a, CloseImpl).Times(AtLeast(1));
|
||||
EXPECT_CALL(*channel_b, CloseImpl).Times(AtLeast(1));
|
||||
channel_b->broken_write_ = true;
|
||||
EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0));
|
||||
RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client,
|
||||
&pcp_handler, connect_medium, nullptr,
|
||||
{Status::kEndpointIoError});
|
||||
NEARBY_LOG(INFO, "RequestConnection complete");
|
||||
channel_b->Close();
|
||||
bwu.Shutdown();
|
||||
pcp_handler.DisconnectFromEndpointManager();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, AcceptConnectionChangesState) {
|
||||
env_.Start();
|
||||
std::string endpoint_id{"1234"};
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
auto mediums = pcp_handler.GetDiscoveryMediums(&client);
|
||||
auto connect_medium = mediums[mediums.size() - 1];
|
||||
auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium);
|
||||
auto& channel_a = channel_pair.first;
|
||||
auto& channel_b = channel_pair.second;
|
||||
EXPECT_CALL(*channel_a, CloseImpl).Times(1);
|
||||
EXPECT_CALL(*channel_b, CloseImpl).Times(1);
|
||||
RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client,
|
||||
&pcp_handler, connect_medium);
|
||||
NEARBY_LOG(INFO, "Attempting to accept connection: id=%s",
|
||||
endpoint_id.c_str());
|
||||
EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0));
|
||||
NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id;
|
||||
channel_b->Close();
|
||||
bwu.Shutdown();
|
||||
pcp_handler.DisconnectFromEndpointManager();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, RejectConnectionChangesState) {
|
||||
env_.Start();
|
||||
std::string endpoint_id{"1234"};
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
auto mediums = pcp_handler.GetDiscoveryMediums(&client);
|
||||
auto connect_medium = mediums[mediums.size() - 1];
|
||||
auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium);
|
||||
auto& channel_b = channel_pair.second;
|
||||
EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(1);
|
||||
RequestConnection(endpoint_id, std::move(channel_pair.first), channel_b.get(),
|
||||
&client, &pcp_handler, connect_medium);
|
||||
NEARBY_LOGS(INFO) << "Attempting to reject connection: id=" << endpoint_id;
|
||||
EXPECT_EQ(pcp_handler.RejectConnection(&client, endpoint_id),
|
||||
Status{Status::kSuccess});
|
||||
NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id;
|
||||
channel_b->Close();
|
||||
bwu.Shutdown();
|
||||
pcp_handler.DisconnectFromEndpointManager();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, OnIncomingFrameChangesState) {
|
||||
env_.Start();
|
||||
std::string endpoint_id{"1234"};
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
auto mediums = pcp_handler.GetDiscoveryMediums(&client);
|
||||
auto connect_medium = mediums[mediums.size() - 1];
|
||||
auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium);
|
||||
auto& channel_a = channel_pair.first;
|
||||
auto& channel_b = channel_pair.second;
|
||||
EXPECT_CALL(*channel_a, CloseImpl).Times(1);
|
||||
EXPECT_CALL(*channel_b, CloseImpl).Times(1);
|
||||
RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(), &client,
|
||||
&pcp_handler, connect_medium);
|
||||
NEARBY_LOGS(INFO) << "Attempting to accept connection: id=" << endpoint_id;
|
||||
EXPECT_CALL(mock_connection_listener_.accepted_cb, Call).Times(1);
|
||||
EXPECT_CALL(mock_connection_listener_.disconnected_cb, Call)
|
||||
.Times(AtLeast(0));
|
||||
EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}),
|
||||
Status{Status::kSuccess});
|
||||
NEARBY_LOG(INFO, "Simulating remote accept: id=%s", endpoint_id.c_str());
|
||||
auto frame =
|
||||
parser::FromBytes(parser::ForConnectionResponse(Status::kSuccess));
|
||||
pcp_handler.OnIncomingFrame(frame.result(), endpoint_id, &client,
|
||||
connect_medium);
|
||||
NEARBY_LOGS(INFO) << "Closing connection: id=" << endpoint_id;
|
||||
channel_b->Close();
|
||||
bwu.Shutdown();
|
||||
pcp_handler.DisconnectFromEndpointManager();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, DestructorIsCalledOnProtocolEndpoint) {
|
||||
env_.Start();
|
||||
std::atomic_int destroyed_flag = 0;
|
||||
int mediums_count = 0;
|
||||
{
|
||||
std::string endpoint_id{"1234"};
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
auto mediums = pcp_handler.GetDiscoveryMediums(&client);
|
||||
auto connect_medium = mediums[mediums.size() - 1];
|
||||
auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium);
|
||||
auto& channel_a = channel_pair.first;
|
||||
auto& channel_b = channel_pair.second;
|
||||
EXPECT_CALL(*channel_a, CloseImpl).Times(1);
|
||||
EXPECT_CALL(*channel_b, CloseImpl).Times(1);
|
||||
RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(),
|
||||
&client, &pcp_handler, connect_medium, &destroyed_flag);
|
||||
mediums_count = mediums.size();
|
||||
NEARBY_LOG(INFO, "Attempting to accept connection: id=%s",
|
||||
endpoint_id.c_str());
|
||||
EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0));
|
||||
NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str());
|
||||
channel_b->Close();
|
||||
bwu.Shutdown();
|
||||
pcp_handler.DisconnectFromEndpointManager();
|
||||
}
|
||||
EXPECT_EQ(destroyed_flag.load(), mediums_count);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BasePcpHandlerTest, MultipleMediumsProduceSingleEndpointLostEvent) {
|
||||
env_.Start();
|
||||
BooleanMediumSelector allowed = GetParam();
|
||||
if (allowed.Count(true) < 2) {
|
||||
// Ignore single-medium test cases, and implicit "all mediums" case.
|
||||
SUCCEED();
|
||||
return;
|
||||
}
|
||||
std::atomic_int destroyed_flag = 0;
|
||||
int mediums_count = 0;
|
||||
{
|
||||
std::string endpoint_id{"1234"};
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
StartDiscovery(&client, &pcp_handler);
|
||||
auto mediums = pcp_handler.GetDiscoveryMediums(&client);
|
||||
auto connect_medium = mediums[mediums.size() - 1];
|
||||
auto channel_pair = SetupConnection(pipe_a_, pipe_b_, connect_medium);
|
||||
auto& channel_a = channel_pair.first;
|
||||
auto& channel_b = channel_pair.second;
|
||||
EXPECT_CALL(*channel_a, CloseImpl).Times(1);
|
||||
EXPECT_CALL(*channel_b, CloseImpl).Times(1);
|
||||
EXPECT_CALL(mock_discovery_listener_.endpoint_lost_cb, Call).Times(1);
|
||||
RequestConnection(endpoint_id, std::move(channel_a), channel_b.get(),
|
||||
&client, &pcp_handler, connect_medium, &destroyed_flag);
|
||||
auto allowed_mediums = pcp_handler.GetDiscoveryMediums(&client);
|
||||
mediums_count = allowed_mediums.size();
|
||||
NEARBY_LOG(INFO, "Attempting to accept connection: id=%s",
|
||||
endpoint_id.c_str());
|
||||
EXPECT_EQ(pcp_handler.AcceptConnection(&client, endpoint_id, {}),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_CALL(mock_connection_listener_.rejected_cb, Call).Times(AtLeast(0));
|
||||
for (const auto* endpoint :
|
||||
pcp_handler.GetDiscoveredEndpoints(endpoint_id)) {
|
||||
pcp_handler.OnEndpointLost(&client, *endpoint);
|
||||
}
|
||||
NEARBY_LOG(INFO, "Closing connection: id=%s", endpoint_id.c_str());
|
||||
channel_b->Close();
|
||||
bwu.Shutdown();
|
||||
pcp_handler.DisconnectFromEndpointManager();
|
||||
}
|
||||
EXPECT_EQ(destroyed_flag.load(), mediums_count);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ParameterizedBasePcpHandlerTest, BasePcpHandlerTest,
|
||||
::testing::ValuesIn(kTestCases));
|
||||
|
||||
TEST_F(BasePcpHandlerTest, InjectEndpoint) {
|
||||
env_.Start();
|
||||
std::string service_id{"service"};
|
||||
std::string endpoint_id{"ABCD"};
|
||||
ClientProxy client;
|
||||
Mediums m;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em(&ecm);
|
||||
BwuManager bwu(m, em, ecm, {}, {});
|
||||
MockPcpHandler pcp_handler(&m, &em, &ecm, &bwu);
|
||||
BooleanMediumSelector allowed{
|
||||
.bluetooth = true,
|
||||
};
|
||||
DiscoveryOptions discovery_options{
|
||||
{
|
||||
Strategy::kP2pPointToPoint,
|
||||
allowed,
|
||||
},
|
||||
false, // auto_upgrade_bandwidth;
|
||||
false, // enforce_topology_constraints;
|
||||
0, // keep_alive_interval_millis;
|
||||
0, // keep_alive_timeout_millis;
|
||||
};
|
||||
EXPECT_CALL(mock_discovery_listener_.endpoint_found_cb, Call);
|
||||
EXPECT_CALL(pcp_handler, StartDiscoveryImpl(&client, service_id, _))
|
||||
.WillOnce(Return(MockPcpHandler::StartOperationResult{
|
||||
.status = {Status::kSuccess},
|
||||
.mediums = allowed.GetMediums(true),
|
||||
}));
|
||||
EXPECT_EQ(pcp_handler.StartDiscovery(&client, service_id, discovery_options,
|
||||
discovery_listener_),
|
||||
Status{Status::kSuccess});
|
||||
EXPECT_TRUE(client.IsDiscovering());
|
||||
|
||||
EXPECT_CALL(pcp_handler, InjectEndpointImpl(&client, service_id, _))
|
||||
.WillOnce(Invoke([&pcp_handler, &endpoint_id](
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const OutOfBandConnectionMetadata& metadata) {
|
||||
pcp_handler.OnEndpointFound(
|
||||
client,
|
||||
std::make_shared<MockDiscoveredEndpoint>(MockDiscoveredEndpoint{
|
||||
{
|
||||
endpoint_id,
|
||||
/*endpoint_info=*/ByteArray{"ABCD"},
|
||||
service_id,
|
||||
Medium::BLUETOOTH,
|
||||
WebRtcState::kUndefined,
|
||||
},
|
||||
MockContext{nullptr},
|
||||
}));
|
||||
return Status{Status::kSuccess};
|
||||
}));
|
||||
pcp_handler.InjectEndpoint(
|
||||
&client, service_id,
|
||||
OutOfBandConnectionMetadata{
|
||||
.medium = Medium::BLUETOOTH,
|
||||
.remote_bluetooth_mac_address = ByteArray(kFakeMacAddress),
|
||||
});
|
||||
bwu.Shutdown();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,278 +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 "core/internal/ble_advertisement.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "core/internal/base_pcp_handler.h"
|
||||
#include "platform/base/base_input_stream.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
BleAdvertisement::BleAdvertisement(Version version, Pcp pcp,
|
||||
const ByteArray& service_id_hash,
|
||||
const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
const std::string& bluetooth_mac_address,
|
||||
const ByteArray& uwb_address,
|
||||
WebRtcState web_rtc_state) {
|
||||
DoInitialize(/*fast_advertisement=*/false, version, pcp, service_id_hash,
|
||||
endpoint_id, endpoint_info, bluetooth_mac_address, uwb_address,
|
||||
web_rtc_state);
|
||||
}
|
||||
|
||||
BleAdvertisement::BleAdvertisement(Version version, Pcp pcp,
|
||||
const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
const ByteArray& uwb_address) {
|
||||
DoInitialize(/*fast_advertisement=*/true, version, pcp, {}, endpoint_id,
|
||||
endpoint_info, {}, uwb_address, WebRtcState::kUndefined);
|
||||
}
|
||||
|
||||
void BleAdvertisement::DoInitialize(bool fast_advertisement, Version version,
|
||||
Pcp pcp, const ByteArray& service_id_hash,
|
||||
const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
const std::string& bluetooth_mac_address,
|
||||
const ByteArray& uwb_address,
|
||||
WebRtcState web_rtc_state) {
|
||||
fast_advertisement_ = fast_advertisement;
|
||||
if (!fast_advertisement_) {
|
||||
if (service_id_hash.size() != kServiceIdHashLength) return;
|
||||
}
|
||||
int max_endpoint_info_length =
|
||||
fast_advertisement_ ? kMaxFastEndpointInfoLength : kMaxEndpointInfoLength;
|
||||
if (version != Version::kV1 || endpoint_id.empty() ||
|
||||
endpoint_id.length() != kEndpointIdLength ||
|
||||
endpoint_info.size() > max_endpoint_info_length) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (pcp) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
version_ = version;
|
||||
pcp_ = pcp;
|
||||
service_id_hash_ = service_id_hash;
|
||||
endpoint_id_ = endpoint_id;
|
||||
endpoint_info_ = endpoint_info;
|
||||
uwb_address_ = uwb_address;
|
||||
if (!fast_advertisement_) {
|
||||
if (!BluetoothUtils::FromString(bluetooth_mac_address).Empty()) {
|
||||
bluetooth_mac_address_ = bluetooth_mac_address;
|
||||
}
|
||||
|
||||
web_rtc_state_ = web_rtc_state;
|
||||
}
|
||||
}
|
||||
|
||||
BleAdvertisement::BleAdvertisement(bool fast_advertisement,
|
||||
const ByteArray& ble_advertisement_bytes) {
|
||||
fast_advertisement_ = fast_advertisement;
|
||||
|
||||
if (ble_advertisement_bytes.Empty()) {
|
||||
NEARBY_LOG(ERROR,
|
||||
"Cannot deserialize BleAdvertisement: null bytes passed in.");
|
||||
return;
|
||||
}
|
||||
|
||||
int min_advertisement_length = fast_advertisement_
|
||||
? kMinFastAdvertisementLength
|
||||
: kMinAdvertisementLength;
|
||||
|
||||
if (ble_advertisement_bytes.size() < min_advertisement_length) {
|
||||
NEARBY_LOG(ERROR,
|
||||
"Cannot deserialize BleAdvertisement: expecting min %d raw "
|
||||
"bytes, got %" PRIu64,
|
||||
kMinAdvertisementLength, ble_advertisement_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArray advertisement_bytes{ble_advertisement_bytes};
|
||||
BaseInputStream base_input_stream{advertisement_bytes};
|
||||
// The first 1 byte is supposed to be the version and pcp.
|
||||
auto version_and_pcp_byte = static_cast<char>(base_input_stream.ReadUint8());
|
||||
// The upper 3 bits are supposed to be the version.
|
||||
version_ =
|
||||
static_cast<Version>((version_and_pcp_byte & kVersionBitmask) >> 5);
|
||||
if (version_ != Version::kV1) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: unsupported Version %d",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
// The lower 5 bits are supposed to be the Pcp.
|
||||
pcp_ = static_cast<Pcp>(version_and_pcp_byte & kPcpBitmask);
|
||||
switch (pcp_) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint:
|
||||
break;
|
||||
default:
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: uunsupported V1 PCP %d",
|
||||
pcp_);
|
||||
}
|
||||
|
||||
// The next 3 bytes are supposed to be the service_id_hash if not fast
|
||||
// advertisment.
|
||||
if (!fast_advertisement_)
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
|
||||
// The next 4 bytes are supposed to be the endpoint_id.
|
||||
endpoint_id_ = std::string{base_input_stream.ReadBytes(kEndpointIdLength)};
|
||||
|
||||
// The next 1 byte is supposed to be the length of the endpoint_info.
|
||||
std::uint32_t expected_endpoint_info_length = base_input_stream.ReadUint8();
|
||||
|
||||
// The next x bytes are the endpoint info. (Max length is 131 bytes or 17
|
||||
// bytes as fast_advertisement being true).
|
||||
endpoint_info_ = base_input_stream.ReadBytes(expected_endpoint_info_length);
|
||||
const int max_endpoint_info_length =
|
||||
fast_advertisement_ ? kMaxFastEndpointInfoLength : kMaxEndpointInfoLength;
|
||||
if (endpoint_info_.Empty() ||
|
||||
endpoint_info_.size() != expected_endpoint_info_length ||
|
||||
endpoint_info_.size() > max_endpoint_info_length) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement(fast advertisement=%d): "
|
||||
"expected endpointInfo to be %d bytes, got %" PRIu64,
|
||||
fast_advertisement_, expected_endpoint_info_length,
|
||||
endpoint_info_.size());
|
||||
|
||||
// Clear enpoint_id for validity.
|
||||
endpoint_id_.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// The next 6 bytes are the bluetooth mac address if not fast advertisment.
|
||||
if (!fast_advertisement_) {
|
||||
auto bluetooth_mac_address_bytes =
|
||||
base_input_stream.ReadBytes(BluetoothUtils::kBluetoothMacAddressLength);
|
||||
bluetooth_mac_address_ =
|
||||
BluetoothUtils::ToString(bluetooth_mac_address_bytes);
|
||||
}
|
||||
|
||||
// The next 1 byte is supposed to be the length of the uwb_address. If the
|
||||
// next byte is not available then it should be a fast advertisement and skip
|
||||
// it for remaining bytes.
|
||||
if (base_input_stream.IsAvailable(1)) {
|
||||
std::uint32_t expected_uwb_address_length = base_input_stream.ReadUint8();
|
||||
// If the length of uwb_address is not zero, then retrieve it.
|
||||
if (expected_uwb_address_length != 0) {
|
||||
uwb_address_ = base_input_stream.ReadBytes(expected_uwb_address_length);
|
||||
if (uwb_address_.Empty() ||
|
||||
uwb_address_.size() != expected_uwb_address_length) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: "
|
||||
"expected uwbAddress size to be %d bytes, got %" PRIu64,
|
||||
expected_uwb_address_length, uwb_address_.size());
|
||||
|
||||
// Clear enpoint_id for validity.
|
||||
endpoint_id_.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The next 1 byte is extra field.
|
||||
if (!fast_advertisement_) {
|
||||
if (base_input_stream.IsAvailable(kExtraFieldLength)) {
|
||||
auto extra_field = static_cast<char>(base_input_stream.ReadUint8());
|
||||
web_rtc_state_ = (extra_field & kWebRtcConnectableFlagBitmask) == 1
|
||||
? WebRtcState::kConnectable
|
||||
: WebRtcState::kUnconnectable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base_input_stream.Close();
|
||||
}
|
||||
|
||||
BleAdvertisement::operator ByteArray() const {
|
||||
if (!IsValid()) {
|
||||
return ByteArray();
|
||||
}
|
||||
|
||||
// The first 3 bits are the Version.
|
||||
char version_and_pcp_byte =
|
||||
(static_cast<char>(version_) << 5) & kVersionBitmask;
|
||||
// The next 5 bits are the Pcp.
|
||||
version_and_pcp_byte |= static_cast<char>(pcp_) & kPcpBitmask;
|
||||
|
||||
std::string out;
|
||||
if (fast_advertisement_) {
|
||||
// clang-format off
|
||||
out = absl::StrCat(std::string(1, version_and_pcp_byte),
|
||||
endpoint_id_,
|
||||
std::string(1, endpoint_info_.size()),
|
||||
std::string(endpoint_info_));
|
||||
// clang-format on
|
||||
} else {
|
||||
// clang-format off
|
||||
out = absl::StrCat(std::string(1, version_and_pcp_byte),
|
||||
std::string(service_id_hash_),
|
||||
endpoint_id_,
|
||||
std::string(1, endpoint_info_.size()),
|
||||
std::string(endpoint_info_));
|
||||
// clang-format on
|
||||
|
||||
// The next 6 bytes are the bluetooth mac address. If bluetooth_mac_address
|
||||
// is invalid or empty, we get back an empty byte array.
|
||||
auto bluetooth_mac_address_bytes{
|
||||
BluetoothUtils::FromString(bluetooth_mac_address_)};
|
||||
if (!bluetooth_mac_address_bytes.Empty()) {
|
||||
absl::StrAppend(&out, std::string(bluetooth_mac_address_bytes));
|
||||
} else {
|
||||
// If bluetooth MAC address is invalid, then reserve the bytes.
|
||||
auto fake_bt_mac_address_bytes =
|
||||
ByteArray(BluetoothUtils::kBluetoothMacAddressLength);
|
||||
absl::StrAppend(&out, std::string(fake_bt_mac_address_bytes));
|
||||
}
|
||||
}
|
||||
|
||||
// The next bytes are UWB address field.
|
||||
if (!uwb_address_.Empty()) {
|
||||
absl::StrAppend(&out, std::string(1, uwb_address_.size()));
|
||||
absl::StrAppend(&out, std::string(uwb_address_));
|
||||
} else if (!fast_advertisement_) {
|
||||
// Write UWB address with length 0 to be able to read the next field when
|
||||
// decode.
|
||||
absl::StrAppend(&out, std::string(1, uwb_address_.size()));
|
||||
}
|
||||
|
||||
// The next 1 byte is extra field.
|
||||
if (!fast_advertisement_) {
|
||||
int web_rtc_connectable_flag =
|
||||
(web_rtc_state_ == WebRtcState::kConnectable) ? 1 : 0;
|
||||
char extra_field_byte = static_cast<char>(web_rtc_connectable_flag) &
|
||||
kWebRtcConnectableFlagBitmask;
|
||||
absl::StrAppend(&out, std::string(1, extra_field_byte));
|
||||
}
|
||||
|
||||
return ByteArray(std::move(out));
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,126 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_BLE_ADVERTISEMENT_H_
|
||||
#define CORE_INTERNAL_BLE_ADVERTISEMENT_H_
|
||||
|
||||
#include "core/internal/base_pcp_handler.h"
|
||||
#include "core/internal/pcp.h"
|
||||
#include "platform/base/bluetooth_utils.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Represents the format of the Connections Ble Advertisement used in
|
||||
// Advertising + Discovery.
|
||||
//
|
||||
// <p>[VERSION][PCP][SERVICE_ID_HASH][ENDPOINT_ID][ENDPOINT_INFO_SIZE]
|
||||
// [ENDPOINT_INFO][BLUETOOTH_MAC][UWB_ADDRESS_SIZE][UWB_ADDRESS][EXTRA_FIELD]
|
||||
//
|
||||
// <p>The fast version of this advertisement simply omits SERVICE_ID_HASH and
|
||||
// the Bluetooth MAC address.
|
||||
//
|
||||
// <p>See go/connections-ble-advertisement for more information.
|
||||
class BleAdvertisement {
|
||||
public:
|
||||
// Versions of the BleAdvertisement.
|
||||
enum class Version {
|
||||
kUndefined = 0,
|
||||
kV1 = 1,
|
||||
// Version is only allocated 3 bits in the BleAdvertisement, so this
|
||||
// can never go beyond V7.
|
||||
};
|
||||
|
||||
static constexpr int kVersionAndPcpLength = 1;
|
||||
static constexpr int kVersionBitmask = 0x0E0;
|
||||
static constexpr int kPcpBitmask = 0x01F;
|
||||
static constexpr int kServiceIdHashLength = 3;
|
||||
static constexpr int kEndpointIdLength = 4;
|
||||
static constexpr int kEndpointInfoSizeLength = 1;
|
||||
static constexpr int kBluetoothMacAddressLength =
|
||||
BluetoothUtils::kBluetoothMacAddressLength;
|
||||
static constexpr int kUwbAddressSizeLength = 1;
|
||||
static constexpr int kExtraFieldLength = 1;
|
||||
static constexpr int kEndpointInfoLengthBitmask = 0x0FF;
|
||||
static constexpr int kWebRtcConnectableFlagBitmask = 0x01;
|
||||
static constexpr int kMinAdvertisementLength =
|
||||
kVersionAndPcpLength + kServiceIdHashLength + kEndpointIdLength +
|
||||
kEndpointInfoSizeLength + kBluetoothMacAddressLength;
|
||||
|
||||
// The difference between normal and fast advertisements is that the fast one
|
||||
// omits the SERVICE_ID_HASH and Bluetooth MAC address. This is done to save
|
||||
// space.
|
||||
static constexpr int kMinFastAdvertisementLength = kMinAdvertisementLength -
|
||||
kServiceIdHashLength -
|
||||
kBluetoothMacAddressLength;
|
||||
static constexpr int kMaxEndpointInfoLength = 131;
|
||||
static constexpr int kMaxFastEndpointInfoLength = 17;
|
||||
|
||||
BleAdvertisement() = default;
|
||||
BleAdvertisement(Version version, Pcp pcp, const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
const ByteArray& uwb_address);
|
||||
BleAdvertisement(Version version, Pcp pcp, const ByteArray& service_id_hash,
|
||||
const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
const std::string& bluetooth_mac_address,
|
||||
const ByteArray& uwb_address, WebRtcState web_rtc_state);
|
||||
BleAdvertisement(bool fast_advertisement,
|
||||
const ByteArray& ble_advertisement_bytes);
|
||||
BleAdvertisement(const BleAdvertisement&) = default;
|
||||
BleAdvertisement& operator=(const BleAdvertisement&) = default;
|
||||
BleAdvertisement(BleAdvertisement&&) = default;
|
||||
BleAdvertisement& operator=(BleAdvertisement&&) = default;
|
||||
~BleAdvertisement() = default;
|
||||
|
||||
explicit operator ByteArray() const;
|
||||
|
||||
bool IsValid() const { return !endpoint_id_.empty(); }
|
||||
bool IsFastAdvertisement() const { return fast_advertisement_; }
|
||||
Version GetVersion() const { return version_; }
|
||||
Pcp GetPcp() const { return pcp_; }
|
||||
ByteArray GetServiceIdHash() const { return service_id_hash_; }
|
||||
std::string GetEndpointId() const { return endpoint_id_; }
|
||||
ByteArray GetEndpointInfo() const { return endpoint_info_; }
|
||||
std::string GetBluetoothMacAddress() const { return bluetooth_mac_address_; }
|
||||
ByteArray GetUwbAddress() const { return uwb_address_; }
|
||||
WebRtcState GetWebRtcState() const { return web_rtc_state_; }
|
||||
|
||||
private:
|
||||
void DoInitialize(bool fast_advertisement, Version version, Pcp pcp,
|
||||
const ByteArray& service_id_hash,
|
||||
const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
const std::string& bluetooth_mac_address,
|
||||
const ByteArray& uwb_address, WebRtcState web_rtc_state);
|
||||
|
||||
bool fast_advertisement_ = false;
|
||||
Version version_{Version::kUndefined};
|
||||
Pcp pcp_{Pcp::kUnknown};
|
||||
ByteArray service_id_hash_;
|
||||
std::string endpoint_id_;
|
||||
ByteArray endpoint_info_;
|
||||
std::string bluetooth_mac_address_;
|
||||
// TODO(b/169550050): Define UWB address field.
|
||||
ByteArray uwb_address_;
|
||||
WebRtcState web_rtc_state_{WebRtcState::kUndefined};
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BLE_ADVERTISEMENT_H_
|
||||
@@ -1,436 +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 "core/internal/ble_advertisement.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "core/internal/base_pcp_handler.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV1;
|
||||
constexpr Pcp kPcp = Pcp::kP2pCluster;
|
||||
constexpr absl::string_view kServiceIdHashBytes{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kEndpointId{"AB12"};
|
||||
constexpr absl::string_view kEndpointName{
|
||||
"How much wood can a woodchuck chuck if a wood chuck would chuck wood?"};
|
||||
constexpr absl::string_view kFastAdvertisementEndpointName{"Fast Advertise"};
|
||||
constexpr absl::string_view kBluetoothMacAddress{"00:00:E6:88:64:13"};
|
||||
constexpr WebRtcState kWebRtcState = WebRtcState::kConnectable;
|
||||
|
||||
// TODO(b/169550050): Implement UWBAddress.
|
||||
TEST(BleAdvertisementTest, ConstructionWorks) {
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement ble_advertisement{
|
||||
kVersion, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, std::string(kBluetoothMacAddress),
|
||||
ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress());
|
||||
EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionWorksForFastAdvertisement) {
|
||||
ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId),
|
||||
fast_endpoint_info, ByteArray{}};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(fast_endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(WebRtcState::kUndefined, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionWorksWithEmptyEndpointInfo) {
|
||||
ByteArray empty_endpoint_info;
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndpointId),
|
||||
empty_endpoint_info,
|
||||
std::string(kBluetoothMacAddress),
|
||||
ByteArray{},
|
||||
kWebRtcState};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(empty_endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress());
|
||||
EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionWorksWithEmptyEndpointInfoForFastAdvertisement) {
|
||||
ByteArray empty_endpoint_info;
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId),
|
||||
empty_endpoint_info, ByteArray{}};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(empty_endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(WebRtcState::kUndefined, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionWorksWithEmojiEndpointInfo) {
|
||||
ByteArray emoji_endpoint_info{std::string("\u0001F450 \u0001F450")};
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
BleAdvertisement ble_advertisement{kVersion,
|
||||
kPcp,
|
||||
service_id_hash,
|
||||
std::string(kEndpointId),
|
||||
emoji_endpoint_info,
|
||||
std::string(kBluetoothMacAddress),
|
||||
ByteArray{},
|
||||
kWebRtcState};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(emoji_endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress());
|
||||
EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionWorksWithEmojiEndpointInfoForFastAdvertisement) {
|
||||
ByteArray emoji_endpoint_info{std::string("\u0001F450 \u0001F450")};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId),
|
||||
emoji_endpoint_info, ByteArray{}};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(emoji_endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(WebRtcState::kUndefined, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithLongEndpointInfo) {
|
||||
std::string long_endpoint_name(BleAdvertisement::kMaxEndpointInfoLength + 1,
|
||||
'x');
|
||||
ByteArray long_endpoint_info{long_endpoint_name};
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
BleAdvertisement ble_advertisement{
|
||||
kVersion, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
long_endpoint_info, std::string(kBluetoothMacAddress),
|
||||
ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFailsWithLongEndpointInfoForFastAdvertisement) {
|
||||
std::string long_endpoint_name(
|
||||
BleAdvertisement::kMaxFastEndpointInfoLength + 1, 'x');
|
||||
ByteArray long_endpoint_info{long_endpoint_name};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId),
|
||||
long_endpoint_info, ByteArray{}};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) {
|
||||
auto bad_version = static_cast<BleAdvertisement::Version>(666);
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement ble_advertisement{
|
||||
bad_version, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, std::string(kBluetoothMacAddress),
|
||||
ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFailsWithBadVersionForFastAdvertisement) {
|
||||
auto bad_version = static_cast<BleAdvertisement::Version>(666);
|
||||
|
||||
ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)};
|
||||
BleAdvertisement ble_advertisement{bad_version, kPcp,
|
||||
std::string(kEndpointId),
|
||||
fast_endpoint_info, ByteArray{}};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadPCP) {
|
||||
auto bad_pcp = static_cast<Pcp>(666);
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement ble_advertisement{
|
||||
kVersion, bad_pcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, std::string(kBluetoothMacAddress),
|
||||
ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadPCPForFastAdvertisement) {
|
||||
auto bad_pcp = static_cast<Pcp>(666);
|
||||
|
||||
ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)};
|
||||
BleAdvertisement ble_advertisement{kVersion, bad_pcp,
|
||||
std::string(kEndpointId),
|
||||
fast_endpoint_info, ByteArray{}};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionSucceedsWithEmptyBluetoothMacAddress) {
|
||||
std::string empty_bluetooth_mac_address = "";
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement ble_advertisement{
|
||||
kVersion, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, empty_bluetooth_mac_address,
|
||||
ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionSucceedsWithInvalidBluetoothMacAddress) {
|
||||
std::string bad_bluetooth_mac_address = "022:00";
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, bad_bluetooth_mac_address,
|
||||
ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_TRUE(ble_advertisement.GetBluetoothMacAddress().empty());
|
||||
EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromBytesWorks) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement org_ble_advertisement{
|
||||
kVersion, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, std::string(kBluetoothMacAddress),
|
||||
ByteArray{}, kWebRtcState};
|
||||
ByteArray ble_advertisement_bytes(org_ble_advertisement);
|
||||
|
||||
BleAdvertisement ble_advertisement{false, ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(kBluetoothMacAddress, ble_advertisement.GetBluetoothMacAddress());
|
||||
EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromBytesWorksForFastAdvertisement) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)};
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kPcp,
|
||||
std::string(kEndpointId),
|
||||
fast_endpoint_info, ByteArray{}};
|
||||
ByteArray ble_advertisement_bytes(org_ble_advertisement);
|
||||
|
||||
BleAdvertisement ble_advertisement{true, ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(kEndpointId, ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(fast_endpoint_info, ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(WebRtcState::kUndefined, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
// Bytes at the end should be ignored so that they can be used as reserve bytes
|
||||
// in the future.
|
||||
TEST(BleAdvertisementTest, ConstructionFromLongLengthBytesWorks) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement ble_advertisement{
|
||||
kVersion, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, std::string(kBluetoothMacAddress),
|
||||
ByteArray{}, kWebRtcState};
|
||||
ByteArray ble_advertisement_bytes(ble_advertisement);
|
||||
|
||||
// Add bytes to the end of the valid Ble advertisement.
|
||||
ByteArray long_ble_advertisement_bytes(
|
||||
BleAdvertisement::kMinAdvertisementLength + 1000);
|
||||
ASSERT_LE(ble_advertisement_bytes.size(),
|
||||
long_ble_advertisement_bytes.size());
|
||||
memcpy(long_ble_advertisement_bytes.data(), ble_advertisement_bytes.data(),
|
||||
ble_advertisement_bytes.size());
|
||||
|
||||
BleAdvertisement long_ble_advertisement{false, long_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(long_ble_advertisement.IsValid());
|
||||
EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kPcp, long_ble_advertisement.GetPcp());
|
||||
EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(kEndpointId, long_ble_advertisement.GetEndpointId());
|
||||
EXPECT_EQ(endpoint_info, long_ble_advertisement.GetEndpointInfo());
|
||||
EXPECT_EQ(kBluetoothMacAddress,
|
||||
long_ble_advertisement.GetBluetoothMacAddress());
|
||||
EXPECT_EQ(kWebRtcState, ble_advertisement.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) {
|
||||
BleAdvertisement ble_advertisement{false, ByteArray{}};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromNullBytesFailsForFastAdvertisement) {
|
||||
BleAdvertisement ble_advertisement{true, ByteArray{}};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromShortLengthBytesFails) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement ble_advertisement{
|
||||
kVersion, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, std::string(kBluetoothMacAddress),
|
||||
ByteArray{}, kWebRtcState};
|
||||
ByteArray ble_advertisement_bytes(ble_advertisement);
|
||||
|
||||
// Shorten the valid Ble Advertisement.
|
||||
ByteArray short_ble_advertisement_bytes{
|
||||
ble_advertisement_bytes.data(),
|
||||
BleAdvertisement::kMinAdvertisementLength - 1};
|
||||
|
||||
BleAdvertisement short_ble_advertisement{false,
|
||||
short_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_FALSE(short_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromShortLengthBytesFailsForFastAdvertisement) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId),
|
||||
fast_endpoint_info, ByteArray{}};
|
||||
ByteArray ble_advertisement_bytes(ble_advertisement);
|
||||
|
||||
// Shorten the valid Ble Advertisement.
|
||||
ByteArray short_ble_advertisement_bytes{
|
||||
ble_advertisement_bytes.data(),
|
||||
BleAdvertisement::kMinAdvertisementLength - 1};
|
||||
|
||||
BleAdvertisement short_ble_advertisement{true, short_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_FALSE(short_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromByesWithWrongEndpointInfoLengthFails) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray service_id_hash{std::string(kServiceIdHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndpointName)};
|
||||
BleAdvertisement ble_advertisement{
|
||||
kVersion, kPcp,
|
||||
service_id_hash, std::string(kEndpointId),
|
||||
endpoint_info, std::string(kBluetoothMacAddress),
|
||||
ByteArray{}, kWebRtcState};
|
||||
ByteArray ble_advertisement_bytes(ble_advertisement);
|
||||
|
||||
// Corrupt the EndpointNameLength bits.
|
||||
std::string corrupt_ble_advertisement_string(ble_advertisement_bytes);
|
||||
corrupt_ble_advertisement_string[8] ^= 0x0FF;
|
||||
ByteArray corrupt_ble_advertisement_bytes(corrupt_ble_advertisement_string);
|
||||
|
||||
BleAdvertisement corrupt_ble_advertisement{false,
|
||||
corrupt_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_FALSE(corrupt_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromByesWithWrongEndpointInfoLengthFailsForFastAdvertisement) {
|
||||
// Serialize good data into a good Ble Advertisement.
|
||||
ByteArray fast_endpoint_info{std::string(kFastAdvertisementEndpointName)};
|
||||
BleAdvertisement ble_advertisement{kVersion, kPcp, std::string(kEndpointId),
|
||||
fast_endpoint_info, ByteArray{}};
|
||||
ByteArray ble_advertisement_bytes = ByteArray(ble_advertisement);
|
||||
|
||||
// Corrupt the EndpointInfoLength bits.
|
||||
std::string corrupt_ble_advertisement_string(ble_advertisement_bytes);
|
||||
corrupt_ble_advertisement_string[5] ^= 0x0FF;
|
||||
ByteArray corrupt_ble_advertisement_bytes(corrupt_ble_advertisement_string);
|
||||
|
||||
BleAdvertisement corrupt_ble_advertisement{true,
|
||||
corrupt_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_FALSE(corrupt_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,65 +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 "core/internal/ble_endpoint_channel.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform/public/ble.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
|
||||
OutputStream* GetOutputStreamOrNull(BleSocket& socket) {
|
||||
if (socket.GetRemotePeripheral().IsValid()) return &socket.GetOutputStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
InputStream* GetInputStreamOrNull(BleSocket& socket) {
|
||||
if (socket.GetRemotePeripheral().IsValid()) return &socket.GetInputStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BleEndpointChannel::BleEndpointChannel(const std::string& channel_name,
|
||||
BleSocket socket)
|
||||
: BaseEndpointChannel(channel_name, GetInputStreamOrNull(socket),
|
||||
GetOutputStreamOrNull(socket)),
|
||||
ble_socket_(std::move(socket)) {}
|
||||
|
||||
proto::connections::Medium BleEndpointChannel::GetMedium() const {
|
||||
return proto::connections::Medium::BLE;
|
||||
}
|
||||
|
||||
int BleEndpointChannel::GetMaxTransmitPacketSize() const {
|
||||
return kDefaultBleMaxTransmitPacketSize;
|
||||
}
|
||||
|
||||
void BleEndpointChannel::CloseImpl() {
|
||||
auto status = ble_socket_.Close();
|
||||
if (!status.Ok()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Failed to close underlying socket for BleEndpointChannel "
|
||||
<< GetName() << ": exception=" << status.value;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,46 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_
|
||||
#define CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_
|
||||
|
||||
#include "core/internal/base_endpoint_channel.h"
|
||||
#include "platform/public/ble.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class BleEndpointChannel final : public BaseEndpointChannel {
|
||||
public:
|
||||
// Creates both outgoing and incoming Ble channels.
|
||||
BleEndpointChannel(const std::string& channel_name, BleSocket socket);
|
||||
|
||||
proto::connections::Medium GetMedium() const override;
|
||||
|
||||
int GetMaxTransmitPacketSize() const override;
|
||||
|
||||
private:
|
||||
static constexpr int kDefaultBleMaxTransmitPacketSize = 512; // 512 bytes
|
||||
|
||||
void CloseImpl() override;
|
||||
|
||||
BleSocket ble_socket_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BLE_ENDPOINT_CHANNEL_H_
|
||||
@@ -1,165 +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 "core/internal/bluetooth_bwu_handler.h"
|
||||
|
||||
#include "absl/functional/bind_front.h"
|
||||
#include "core/internal/bluetooth_endpoint_channel.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
|
||||
// Manages the Bluetooth-specific methods needed to upgrade an {@link
|
||||
// EndpointChannel}.
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
BluetoothBwuHandler::BluetoothBwuHandler(
|
||||
Mediums& mediums, EndpointChannelManager& channel_manager,
|
||||
BwuNotifications notifications)
|
||||
: BaseBwuHandler(channel_manager, std::move(notifications)),
|
||||
mediums_(mediums) {}
|
||||
|
||||
void BluetoothBwuHandler::Revert() {
|
||||
for (const std::string& service_id : active_service_ids_) {
|
||||
bluetooth_medium_.StopAcceptingConnections(service_id);
|
||||
}
|
||||
active_service_ids_.clear();
|
||||
NEARBY_LOG(INFO,
|
||||
"BluetoothBwuHandler successfully reverted all Bluetooth state.");
|
||||
}
|
||||
|
||||
// Accept Connection Callback.
|
||||
// Notifies that the remote party called BluetoothClassic::Connect()
|
||||
// for this socket.
|
||||
void BluetoothBwuHandler::OnIncomingBluetoothConnection(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
BluetoothSocket socket) {
|
||||
auto channel =
|
||||
absl::make_unique<BluetoothEndpointChannel>(service_id, socket);
|
||||
std::unique_ptr<IncomingSocketConnection> connection{
|
||||
new IncomingSocketConnection{
|
||||
.socket =
|
||||
std::make_unique<BluetoothIncomingSocket>(service_id, socket),
|
||||
.channel = std::move(channel),
|
||||
}};
|
||||
bwu_notifications_.incoming_connection_cb(client, std::move(connection));
|
||||
}
|
||||
|
||||
// Called by BWU initiator. BT Medium is set up, and BWU request is prepared,
|
||||
// with necessary info (service_id, MAC address) for remote party to perform
|
||||
// discovery.
|
||||
ByteArray BluetoothBwuHandler::InitializeUpgradedMediumForEndpoint(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& endpoint_id) {
|
||||
std::string upgrade_service_id = Utils::WrapUpgradeServiceId(service_id);
|
||||
|
||||
std::string mac_address = bluetooth_medium_.GetMacAddress();
|
||||
if (mac_address.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!bluetooth_medium_.IsAcceptingConnections(upgrade_service_id)) {
|
||||
if (!bluetooth_medium_.StartAcceptingConnections(
|
||||
upgrade_service_id,
|
||||
{
|
||||
.accepted_cb = absl::bind_front(
|
||||
&BluetoothBwuHandler::OnIncomingBluetoothConnection, this,
|
||||
client, service_id),
|
||||
})) {
|
||||
NEARBY_LOGS(ERROR) << "BluetoothBwuHandler couldn't initiate the "
|
||||
"BLUETOOTH upgrade for endpoint "
|
||||
<< endpoint_id
|
||||
<< " because it failed to start listening for "
|
||||
"incoming Bluetooth connections.";
|
||||
|
||||
return {};
|
||||
}
|
||||
NEARBY_LOGS(VERBOSE)
|
||||
<< "BluetoothBwuHandler successfully started listening for incoming "
|
||||
"Bluetooth connections on serviceid="
|
||||
<< upgrade_service_id << " while upgrading endpoint " << endpoint_id;
|
||||
}
|
||||
// cache service ID to revert
|
||||
active_service_ids_.emplace(upgrade_service_id);
|
||||
|
||||
return parser::ForBwuBluetoothPathAvailable(upgrade_service_id, mac_address);
|
||||
}
|
||||
|
||||
// Called by BWU target. Retrieves a new medium info from incoming message,
|
||||
// and establishes connection over BT using this info.
|
||||
// Returns a channel ready to exchange data or nullptr on error.
|
||||
std::unique_ptr<EndpointChannel>
|
||||
BluetoothBwuHandler::CreateUpgradedEndpointChannel(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& endpoint_id, const UpgradePathInfo& upgrade_path_info) {
|
||||
const UpgradePathInfo::BluetoothCredentials& bluetooth_credentials =
|
||||
upgrade_path_info.bluetooth_credentials();
|
||||
if (!bluetooth_credentials.has_service_name() ||
|
||||
!bluetooth_credentials.has_mac_address()) {
|
||||
NEARBY_LOG(ERROR, "BluetoothBwuHandler failed to parse UpgradePathInfo.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string& service_name = bluetooth_credentials.service_name();
|
||||
const std::string& mac_address = bluetooth_credentials.mac_address();
|
||||
|
||||
NEARBY_LOGS(VERBOSE) << "BluetoothBwuHandler is attempting to connect to "
|
||||
"available Bluetooth device "
|
||||
<< service_name << ", " << mac_address
|
||||
<< ") for endpoint " << endpoint_id;
|
||||
|
||||
BluetoothDevice device = bluetooth_medium_.GetRemoteDevice(mac_address);
|
||||
if (!device.IsValid()) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< "BluetoothBwuHandler failed to derive a valid Bluetooth device "
|
||||
"from the MAC address ("
|
||||
<< mac_address << ") for endpoint " << endpoint_id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BluetoothSocket socket = bluetooth_medium_.Connect(
|
||||
device, service_name, client->GetCancellationFlag(endpoint_id));
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< "BluetoothBwuHandler failed to connect to the Bluetooth device ("
|
||||
<< service_name << ", " << mac_address << ") for endpoint "
|
||||
<< endpoint_id;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(VERBOSE)
|
||||
<< "BluetoothBwuHandler successfully connected to Bluetooth device ("
|
||||
<< service_name << ", " << mac_address << ") while upgrading endpoint "
|
||||
<< endpoint_id;
|
||||
|
||||
auto channel =
|
||||
std::make_unique<BluetoothEndpointChannel>(service_name, socket);
|
||||
if (channel == nullptr) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< "BluetoothBwuHandler failed to create Bluetooth endpoint "
|
||||
"channel to the Bluetooth device ("
|
||||
<< service_name << ", " << mac_address << ") for endpoint "
|
||||
<< endpoint_id;
|
||||
socket.Close();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,95 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_BLUETOOTH_BWU_HANDLER_H_
|
||||
#define CORE_INTERNAL_BLUETOOTH_BWU_HANDLER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/internal/base_bwu_handler.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/mediums/mediums.h"
|
||||
#include "core/internal/mediums/utils.h"
|
||||
#include "platform/public/bluetooth_classic.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Defines the set of methods that need to be implemented to handle the
|
||||
// per-Medium-specific operations needed to upgrade an EndpointChannel.
|
||||
class BluetoothBwuHandler : public BaseBwuHandler {
|
||||
public:
|
||||
BluetoothBwuHandler(Mediums& mediums, EndpointChannelManager& channel_manager,
|
||||
BwuNotifications notifications);
|
||||
~BluetoothBwuHandler() override = default;
|
||||
|
||||
private:
|
||||
constexpr static const int kServiceIdLength = 10;
|
||||
|
||||
// Implements BaseBwuHandler:
|
||||
// Reverts any changes made to the device in the process of upgrading
|
||||
// endpoints.
|
||||
void Revert() override;
|
||||
|
||||
// Cleans up in-progress upgrades after endpoint disconnection.
|
||||
void OnEndpointDisconnect(ClientProxy* client,
|
||||
const std::string& endpoint_id) override {}
|
||||
|
||||
void OnIncomingBluetoothConnection(ClientProxy* client,
|
||||
const std::string& service_id,
|
||||
BluetoothSocket socket);
|
||||
|
||||
class BluetoothIncomingSocket : public IncomingSocket {
|
||||
public:
|
||||
explicit BluetoothIncomingSocket(const std::string& name,
|
||||
BluetoothSocket socket)
|
||||
: name_(name), socket_(socket) {}
|
||||
~BluetoothIncomingSocket() override = default;
|
||||
std::string ToString() override { return name_; }
|
||||
void Close() override { socket_.Close(); }
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
BluetoothSocket socket_;
|
||||
};
|
||||
|
||||
// First part of InitiateBwuForEndpoint implementation;
|
||||
// returns a BWU request to remote party as byte array.
|
||||
ByteArray InitializeUpgradedMediumForEndpoint(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& endpoint_id) override;
|
||||
|
||||
// Invoked from OnBwuNegotiationFrame.
|
||||
std::unique_ptr<EndpointChannel> CreateUpgradedEndpointChannel(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& endpoint_id,
|
||||
const UpgradePathInfo& upgrade_path_info) override;
|
||||
|
||||
// Returns the upgrade medium of the BwuHandler.
|
||||
// @BwuHandlerThread
|
||||
Medium GetUpgradeMedium() const override { return Medium::BLUETOOTH; }
|
||||
|
||||
Mediums& mediums_;
|
||||
absl::flat_hash_set<std::string> active_service_ids_;
|
||||
BluetoothRadio& bluetooth_radio_{mediums_.GetBluetoothRadio()};
|
||||
BluetoothClassic& bluetooth_medium_{mediums_.GetBluetoothClassic()};
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BLUETOOTH_BWU_HANDLER_H_
|
||||
@@ -1,212 +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 "core/internal/bluetooth_device_name.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "platform/base/base64_utils.h"
|
||||
#include "platform/base/base_input_stream.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
BluetoothDeviceName::BluetoothDeviceName(Version version, Pcp pcp,
|
||||
absl::string_view endpoint_id,
|
||||
const ByteArray& service_id_hash,
|
||||
const ByteArray& endpoint_info,
|
||||
const ByteArray& uwb_address,
|
||||
WebRtcState web_rtc_state) {
|
||||
if (version != Version::kV1 || endpoint_id.empty() ||
|
||||
endpoint_id.length() != kEndpointIdLength ||
|
||||
service_id_hash.size() != kServiceIdHashLength) {
|
||||
return;
|
||||
}
|
||||
switch (pcp) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint:
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
version_ = version;
|
||||
pcp_ = pcp;
|
||||
endpoint_id_ = std::string(endpoint_id);
|
||||
service_id_hash_ = service_id_hash;
|
||||
endpoint_info_ = endpoint_info;
|
||||
uwb_address_ = uwb_address;
|
||||
web_rtc_state_ = web_rtc_state;
|
||||
}
|
||||
|
||||
BluetoothDeviceName::BluetoothDeviceName(
|
||||
absl::string_view bluetooth_device_name_string) {
|
||||
ByteArray bluetooth_device_name_bytes =
|
||||
Base64Utils::Decode(bluetooth_device_name_string);
|
||||
if (bluetooth_device_name_bytes.Empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bluetooth_device_name_bytes.size() < kMinBluetoothDeviceNameLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: expecting min %d raw "
|
||||
"bytes, got %" PRIu64,
|
||||
kMinBluetoothDeviceNameLength,
|
||||
bluetooth_device_name_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
BaseInputStream base_input_stream{bluetooth_device_name_bytes};
|
||||
// The first 1 byte is supposed to be the version and pcp.
|
||||
auto version_and_pcp_byte = static_cast<char>(base_input_stream.ReadUint8());
|
||||
// The upper 3 bits are supposed to be the version.
|
||||
version_ =
|
||||
static_cast<Version>((version_and_pcp_byte & kVersionBitmask) >> 5);
|
||||
if (version_ != Version::kV1) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: unsupported version=%d",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
// The lower 5 bits are supposed to be the Pcp.
|
||||
pcp_ = static_cast<Pcp>(version_and_pcp_byte & kPcpBitmask);
|
||||
switch (pcp_) {
|
||||
case Pcp::kP2pCluster: // Fall through
|
||||
case Pcp::kP2pStar: // Fall through
|
||||
case Pcp::kP2pPointToPoint:
|
||||
break;
|
||||
default:
|
||||
NEARBY_LOG(
|
||||
INFO, "Cannot deserialize BluetoothDeviceName: unsupported V1 PCP %d",
|
||||
pcp_);
|
||||
return;
|
||||
}
|
||||
|
||||
// The next 4 bytes are supposed to be the endpoint_id.
|
||||
endpoint_id_ = std::string{base_input_stream.ReadBytes(kEndpointIdLength)};
|
||||
|
||||
// The next 3 bytes are supposed to be the service_id_hash.
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
|
||||
// The next 1 byte is field containning WebRtc state.
|
||||
auto field_byte = static_cast<char>(base_input_stream.ReadUint8());
|
||||
web_rtc_state_ = (field_byte & kWebRtcConnectableFlagBitmask) == 1
|
||||
? WebRtcState::kConnectable
|
||||
: WebRtcState::kUnconnectable;
|
||||
|
||||
// The next 6 bytes are supposed to be reserved, and can be left
|
||||
// untouched.
|
||||
base_input_stream.ReadBytes(kReservedLength);
|
||||
|
||||
// The next 1 byte is supposed to be the length of the endpoint_info.
|
||||
std::uint32_t expected_endpoint_info_length = base_input_stream.ReadUint8();
|
||||
|
||||
// The rest bytes are supposed to be the endpoint_info
|
||||
endpoint_info_ = base_input_stream.ReadBytes(expected_endpoint_info_length);
|
||||
if (endpoint_info_.Empty() ||
|
||||
endpoint_info_.size() != expected_endpoint_info_length) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: expected "
|
||||
"endpoint info to be %d bytes, got %" PRIu64,
|
||||
expected_endpoint_info_length, endpoint_info_.size());
|
||||
|
||||
// Clear enpoint_id for validadity.
|
||||
endpoint_id_.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// If the input stream has extra bytes, it's for UWB address. The first byte
|
||||
// is the address length. It can be 2-byte short address or 8-byte extended
|
||||
// address.
|
||||
if (base_input_stream.IsAvailable(1)) {
|
||||
// The next 1 byte is supposed to be the length of the uwb_address.
|
||||
std::uint32_t expected_uwb_address_length = base_input_stream.ReadUint8();
|
||||
// If the length of usb_address is not zero, then retrieve it.
|
||||
if (expected_uwb_address_length != 0) {
|
||||
uwb_address_ = base_input_stream.ReadBytes(expected_uwb_address_length);
|
||||
if (uwb_address_.Empty() ||
|
||||
uwb_address_.size() != expected_uwb_address_length) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BluetoothDeviceName: "
|
||||
"expected uwbAddress size to be %d bytes, got %" PRIu64,
|
||||
expected_uwb_address_length, uwb_address_.size());
|
||||
|
||||
// Clear enpoint_id for validadity.
|
||||
endpoint_id_.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothDeviceName::operator std::string() const {
|
||||
if (!IsValid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// The upper 3 bits are the Version.
|
||||
auto version_and_pcp_byte = static_cast<char>(
|
||||
(static_cast<uint32_t>(Version::kV1) << 5) & kVersionBitmask);
|
||||
// The lower 5 bits are the PCP.
|
||||
version_and_pcp_byte |=
|
||||
static_cast<char>(static_cast<uint32_t>(pcp_) & kPcpBitmask);
|
||||
|
||||
// A byte contains WebRtcState state.
|
||||
int web_rtc_connectable_flag =
|
||||
(web_rtc_state_ == WebRtcState::kConnectable) ? 1 : 0;
|
||||
char field_byte = static_cast<char>(web_rtc_connectable_flag) &
|
||||
kWebRtcConnectableFlagBitmask;
|
||||
|
||||
ByteArray reserved_bytes{kReservedLength};
|
||||
|
||||
ByteArray usable_endpoint_info(endpoint_info_);
|
||||
if (endpoint_info_.size() > kMaxEndpointInfoLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"While serializing Advertisement, truncating Endpoint Name %s "
|
||||
"(%lu bytes) down to %d bytes",
|
||||
absl::BytesToHexString(endpoint_info_.data()).c_str(),
|
||||
endpoint_info_.size(), kMaxEndpointInfoLength);
|
||||
usable_endpoint_info.SetData(endpoint_info_.data(), kMaxEndpointInfoLength);
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
std::string out = absl::StrCat(std::string(1, version_and_pcp_byte),
|
||||
endpoint_id_,
|
||||
std::string(service_id_hash_),
|
||||
std::string(1, field_byte),
|
||||
std::string(reserved_bytes),
|
||||
std::string(1, usable_endpoint_info.size()),
|
||||
std::string(usable_endpoint_info));
|
||||
// clang-format on
|
||||
|
||||
// If UWB address is available, attach it at the end.
|
||||
if (!uwb_address_.Empty()) {
|
||||
absl::StrAppend(&out, std::string(1, uwb_address_.size()));
|
||||
absl::StrAppend(&out, std::string(uwb_address_));
|
||||
}
|
||||
|
||||
return Base64Utils::Encode(ByteArray{std::move(out)});
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,93 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_
|
||||
#define CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "core/internal/base_pcp_handler.h"
|
||||
#include "core/internal/pcp.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Represents the format of the Bluetooth device name used in Advertising +
|
||||
// Discovery.
|
||||
//
|
||||
// <p>See go/nearby-offline-data-interchange-formats for the specification.
|
||||
class BluetoothDeviceName {
|
||||
public:
|
||||
// Versions of the BluetoothDeviceName.
|
||||
enum class Version {
|
||||
kUndefined = 0,
|
||||
kV1 = 1,
|
||||
// Version is only allocated 3 bits in the BluetoothDeviceName, so this
|
||||
// can never go beyond V7.
|
||||
};
|
||||
|
||||
static constexpr int kServiceIdHashLength = 3;
|
||||
|
||||
BluetoothDeviceName() = default;
|
||||
BluetoothDeviceName(Version version, Pcp pcp, absl::string_view endpoint_id,
|
||||
const ByteArray& service_id_hash,
|
||||
const ByteArray& endpoint_info,
|
||||
const ByteArray& uwb_address, WebRtcState web_rtc_state);
|
||||
explicit BluetoothDeviceName(absl::string_view bluetooth_device_name_string);
|
||||
BluetoothDeviceName(const BluetoothDeviceName&) = default;
|
||||
BluetoothDeviceName& operator=(const BluetoothDeviceName&) = default;
|
||||
BluetoothDeviceName(BluetoothDeviceName&&) = default;
|
||||
BluetoothDeviceName& operator=(BluetoothDeviceName&&) = default;
|
||||
~BluetoothDeviceName() = default;
|
||||
|
||||
explicit operator std::string() const;
|
||||
|
||||
bool IsValid() const { return !endpoint_id_.empty(); }
|
||||
Version GetVersion() const { return version_; }
|
||||
Pcp GetPcp() const { return pcp_; }
|
||||
std::string GetEndpointId() const { return endpoint_id_; }
|
||||
ByteArray GetServiceIdHash() const { return service_id_hash_; }
|
||||
ByteArray GetEndpointInfo() const { return endpoint_info_; }
|
||||
ByteArray GetUwbAddress() const { return uwb_address_; }
|
||||
WebRtcState GetWebRtcState() const { return web_rtc_state_; }
|
||||
|
||||
private:
|
||||
static constexpr int kEndpointIdLength = 4;
|
||||
static constexpr int kReservedLength = 6;
|
||||
static constexpr int kMaxEndpointInfoLength = 131;
|
||||
static constexpr int kMinBluetoothDeviceNameLength = 16;
|
||||
|
||||
static constexpr int kVersionBitmask = 0x0E0;
|
||||
static constexpr int kPcpBitmask = 0x01F;
|
||||
static constexpr int kEndpointNameLengthBitmask = 0x0FF;
|
||||
static constexpr int kWebRtcConnectableFlagBitmask = 0x01;
|
||||
|
||||
Version version_{Version::kUndefined};
|
||||
Pcp pcp_{Pcp::kUnknown};
|
||||
std::string endpoint_id_;
|
||||
ByteArray service_id_hash_;
|
||||
ByteArray endpoint_info_;
|
||||
// TODO(b/169550050): Define UWB address field.
|
||||
ByteArray uwb_address_;
|
||||
WebRtcState web_rtc_state_{WebRtcState::kUndefined};
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BLUETOOTH_DEVICE_NAME_H_
|
||||
@@ -1,207 +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 "core/internal/bluetooth_device_name.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "platform/base/base64_utils.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr BluetoothDeviceName::Version kVersion =
|
||||
BluetoothDeviceName::Version::kV1;
|
||||
constexpr Pcp kPcp = Pcp::kP2pCluster;
|
||||
constexpr absl::string_view kEndPointID{"AB12"};
|
||||
constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kEndPointName{"RAWK + ROWL!"};
|
||||
constexpr WebRtcState kWebRtcState = WebRtcState::kConnectable;
|
||||
|
||||
// TODO(b/169550050): Implement UWBAddress.
|
||||
TEST(BluetoothDeviceNameTest, ConstructionWorks) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
kVersion, kPcp, kEndPointID, service_id_hash,
|
||||
endpoint_info, ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_TRUE(bluetooth_device_name.IsValid());
|
||||
EXPECT_EQ(kVersion, bluetooth_device_name.GetVersion());
|
||||
EXPECT_EQ(kPcp, bluetooth_device_name.GetPcp());
|
||||
EXPECT_EQ(kEndPointID, bluetooth_device_name.GetEndpointId());
|
||||
EXPECT_EQ(service_id_hash, bluetooth_device_name.GetServiceIdHash());
|
||||
EXPECT_EQ(endpoint_info, bluetooth_device_name.GetEndpointInfo());
|
||||
EXPECT_EQ(kWebRtcState, bluetooth_device_name.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionWorksWithEmptyEndpointName) {
|
||||
ByteArray empty_endpoint_info;
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
BluetoothDeviceName bluetooth_device_name{kVersion,
|
||||
kPcp,
|
||||
kEndPointID,
|
||||
service_id_hash,
|
||||
empty_endpoint_info,
|
||||
ByteArray{},
|
||||
kWebRtcState};
|
||||
|
||||
EXPECT_TRUE(bluetooth_device_name.IsValid());
|
||||
EXPECT_EQ(kVersion, bluetooth_device_name.GetVersion());
|
||||
EXPECT_EQ(kPcp, bluetooth_device_name.GetPcp());
|
||||
EXPECT_EQ(kEndPointID, bluetooth_device_name.GetEndpointId());
|
||||
EXPECT_EQ(service_id_hash, bluetooth_device_name.GetServiceIdHash());
|
||||
EXPECT_EQ(empty_endpoint_info, bluetooth_device_name.GetEndpointInfo());
|
||||
EXPECT_EQ(kWebRtcState, bluetooth_device_name.GetWebRtcState());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadVersion) {
|
||||
auto bad_version = static_cast<BluetoothDeviceName::Version>(666);
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
bad_version, kPcp, kEndPointID, service_id_hash,
|
||||
endpoint_info, ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithBadPcp) {
|
||||
auto bad_pcp = static_cast<Pcp>(666);
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
kVersion, bad_pcp, kEndPointID, service_id_hash,
|
||||
endpoint_info, ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortEndpointId) {
|
||||
std::string short_endpoint_id("AB1");
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
kVersion, kPcp, short_endpoint_id, service_id_hash,
|
||||
endpoint_info, ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongEndpointId) {
|
||||
std::string long_endpoint_id("AB12X");
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
kVersion, kPcp, long_endpoint_id, service_id_hash,
|
||||
endpoint_info, ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortServiceIdHash) {
|
||||
char short_service_id_hash_bytes[] = "\x0a\x0b";
|
||||
|
||||
ByteArray short_service_id_hash{short_service_id_hash_bytes};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
kVersion, kPcp, kEndPointID, short_service_id_hash,
|
||||
endpoint_info, ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithLongServiceIdHash) {
|
||||
char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d";
|
||||
|
||||
ByteArray long_service_id_hash{long_service_id_hash_bytes};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
kVersion, kPcp, kEndPointID, long_service_id_hash,
|
||||
endpoint_info, ByteArray{}, kWebRtcState};
|
||||
|
||||
EXPECT_FALSE(bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithShortStringLength) {
|
||||
char bluetooth_device_name_string[] = "X";
|
||||
|
||||
ByteArray bluetooth_device_name_bytes{bluetooth_device_name_string};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
Base64Utils::Encode(bluetooth_device_name_bytes)};
|
||||
|
||||
EXPECT_FALSE(bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, ConstructionFailsWithWrongEndpointNameLength) {
|
||||
// Serialize good data into a good Bluetooth Device Name.
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
BluetoothDeviceName bluetooth_device_name{
|
||||
kVersion, kPcp, kEndPointID, service_id_hash,
|
||||
endpoint_info, ByteArray{}, kWebRtcState};
|
||||
auto bluetooth_device_name_string = std::string(bluetooth_device_name);
|
||||
|
||||
// Base64-decode the good Bluetooth Device Name.
|
||||
ByteArray bluetooth_device_name_bytes =
|
||||
Base64Utils::Decode(bluetooth_device_name_string);
|
||||
// Corrupt the EndpointNameLength bits (120-127) by reversing all of them.
|
||||
std::string corrupt_string(bluetooth_device_name_bytes.data(),
|
||||
bluetooth_device_name_bytes.size());
|
||||
corrupt_string[15] ^= 0x0FF;
|
||||
// Base64-encode the corrupted bytes into a corrupt Bluetooth Device Name.
|
||||
ByteArray corrupt_bluetooth_device_name_bytes{corrupt_string.data(),
|
||||
corrupt_string.size()};
|
||||
std::string corrupt_bluetooth_device_name_string(
|
||||
Base64Utils::Encode(corrupt_bluetooth_device_name_bytes));
|
||||
|
||||
// And deserialize the corrupt Bluetooth Device Name.
|
||||
BluetoothDeviceName corrupt_bluetooth_device_name(
|
||||
corrupt_bluetooth_device_name_string);
|
||||
|
||||
EXPECT_FALSE(corrupt_bluetooth_device_name.IsValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothDeviceNameTest, CanParseGeneratedName) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray endpoint_info{std::string(kEndPointName)};
|
||||
// Build name1 from scratch.
|
||||
BluetoothDeviceName name1{kVersion, kPcp, kEndPointID,
|
||||
service_id_hash, endpoint_info, ByteArray{},
|
||||
kWebRtcState};
|
||||
// Build name2 from string composed from name1.
|
||||
BluetoothDeviceName name2{std::string(name1)};
|
||||
EXPECT_TRUE(name1.IsValid());
|
||||
EXPECT_TRUE(name2.IsValid());
|
||||
EXPECT_EQ(name1.GetVersion(), name2.GetVersion());
|
||||
EXPECT_EQ(name1.GetPcp(), name2.GetPcp());
|
||||
EXPECT_EQ(name1.GetEndpointId(), name2.GetEndpointId());
|
||||
EXPECT_EQ(name1.GetServiceIdHash(), name2.GetServiceIdHash());
|
||||
EXPECT_EQ(name1.GetEndpointInfo(), name2.GetEndpointInfo());
|
||||
EXPECT_EQ(name1.GetWebRtcState(), name2.GetWebRtcState());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,65 +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 "core/internal/bluetooth_endpoint_channel.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform/public/bluetooth_classic.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
|
||||
OutputStream* GetOutputStreamOrNull(BluetoothSocket& socket) {
|
||||
if (socket.GetRemoteDevice().IsValid()) return &socket.GetOutputStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
InputStream* GetInputStreamOrNull(BluetoothSocket& socket) {
|
||||
if (socket.GetRemoteDevice().IsValid()) return &socket.GetInputStream();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BluetoothEndpointChannel::BluetoothEndpointChannel(
|
||||
const std::string& channel_name, BluetoothSocket socket)
|
||||
: BaseEndpointChannel(channel_name, GetInputStreamOrNull(socket),
|
||||
GetOutputStreamOrNull(socket)),
|
||||
bluetooth_socket_(std::move(socket)) {}
|
||||
|
||||
proto::connections::Medium BluetoothEndpointChannel::GetMedium() const {
|
||||
return proto::connections::Medium::BLUETOOTH;
|
||||
}
|
||||
|
||||
int BluetoothEndpointChannel::GetMaxTransmitPacketSize() const {
|
||||
return kDefaultBTMaxTransmitPacketSize;
|
||||
}
|
||||
|
||||
void BluetoothEndpointChannel::CloseImpl() {
|
||||
auto status = bluetooth_socket_.Close();
|
||||
if (!status.Ok()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Failed to close underlying socket for BluetoothEndpointChannel "
|
||||
<< GetName() << ": exception=" << status.value;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,49 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
|
||||
#define CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/internal/base_endpoint_channel.h"
|
||||
#include "platform/public/bluetooth_classic.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class BluetoothEndpointChannel final : public BaseEndpointChannel {
|
||||
public:
|
||||
// Creates both outgoing and incoming BT channels.
|
||||
BluetoothEndpointChannel(const std::string& channel_name,
|
||||
BluetoothSocket bluetooth_socket);
|
||||
|
||||
proto::connections::Medium GetMedium() const override;
|
||||
|
||||
int GetMaxTransmitPacketSize() const override;
|
||||
|
||||
private:
|
||||
static constexpr int kDefaultBTMaxTransmitPacketSize = 1980; // 990 * 2 Bytes
|
||||
|
||||
void CloseImpl() override;
|
||||
|
||||
BluetoothSocket bluetooth_socket_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BLUETOOTH_ENDPOINT_CHANNEL_H_
|
||||
@@ -1,89 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_BWU_HANDLER_H_
|
||||
#define CORE_INTERNAL_BWU_HANDLER_H_
|
||||
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_channel.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
using BwuNegotiationFrame = BandwidthUpgradeNegotiationFrame;
|
||||
|
||||
// Defines the set of methods that need to be implemented to handle the
|
||||
// per-Medium-specific operations needed to upgrade an EndpointChannel.
|
||||
class BwuHandler {
|
||||
public:
|
||||
using UpgradePathInfo = parser::UpgradePathInfo;
|
||||
|
||||
virtual ~BwuHandler() = default;
|
||||
|
||||
// Called by the Initiator to setup the upgraded medium for this endpoint (if
|
||||
// that hasn't already been done), and returns a serialized UpgradePathInfo
|
||||
// that can be sent to the Responder.
|
||||
// @BwuHandlerThread
|
||||
virtual ByteArray InitializeUpgradedMediumForEndpoint(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& endpoint_id) = 0;
|
||||
|
||||
// Called to revert any state changed by the Initiator to setup the upgraded
|
||||
// medium for an endpoint.
|
||||
// @BwuHandlerThread
|
||||
virtual void Revert() = 0;
|
||||
|
||||
// Called by the Responder to setup the upgraded medium for this endpoint (if
|
||||
// that hasn't already been done) using the UpgradePathInfo sent by the
|
||||
// Initiator, and returns a new EndpointChannel for the upgraded medium.
|
||||
// @BwuHandlerThread
|
||||
virtual std::unique_ptr<EndpointChannel> CreateUpgradedEndpointChannel(
|
||||
ClientProxy* client, const std::string& service_id,
|
||||
const std::string& endpoint_id,
|
||||
const UpgradePathInfo& upgrade_path_info) = 0;
|
||||
|
||||
// Returns the upgrade medium of the BwuHandler.
|
||||
// @BwuHandlerThread
|
||||
virtual Medium GetUpgradeMedium() const = 0;
|
||||
virtual void OnEndpointDisconnect(ClientProxy* client,
|
||||
const std::string& endpoint_id) = 0;
|
||||
|
||||
class IncomingSocket {
|
||||
public:
|
||||
virtual ~IncomingSocket() = default;
|
||||
|
||||
virtual std::string ToString() = 0;
|
||||
virtual void Close() = 0;
|
||||
};
|
||||
|
||||
struct IncomingSocketConnection {
|
||||
std::unique_ptr<IncomingSocket> socket;
|
||||
std::unique_ptr<EndpointChannel> channel;
|
||||
};
|
||||
|
||||
struct BwuNotifications {
|
||||
std::function<void(ClientProxy* client,
|
||||
std::unique_ptr<IncomingSocketConnection> connection)>
|
||||
incoming_connection_cb;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BWU_HANDLER_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,202 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_INTERNAL_BWU_MANAGER_H_
|
||||
#define CORE_INTERNAL_BWU_MANAGER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/bwu_handler.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_manager.h"
|
||||
#include "core/internal/mediums/mediums.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/scheduled_executor.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Base class for managing the upgrade of endpoints to a different medium for
|
||||
// communication (from whatever they were previously using).
|
||||
//
|
||||
// The sequencing of the upgrade protocol is as follows:
|
||||
// - Initiator sets up an upgrade path, sends
|
||||
// BANDWIDTH_UPGRADE_NEGOTIATION.UPGRADE_PATH_AVAILABLE to Responder over
|
||||
// the prior EndpointChannel.
|
||||
// - Responder joins the upgrade path, sends (possibly without encryption)
|
||||
// BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION over the new
|
||||
// EndpointChannel, and sends
|
||||
// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL over the
|
||||
// prior EndpointChannel.
|
||||
// - Initiator receives BANDWIDTH_UPGRADE_NEGOTIATION.CLIENT_INTRODUCTION
|
||||
// over the newly-established EndpointChannel, and sends
|
||||
// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL over the
|
||||
// prior EndpointChannel.
|
||||
// - Both wait to receive
|
||||
// BANDWIDTH_UPGRADE_NEGOTIATION.LAST_WRITE_TO_PRIOR_CHANNEL from the
|
||||
// other, and upon doing so, send
|
||||
// BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL to each other
|
||||
// - Both then wait to receive
|
||||
// BANDWIDTH_UPGRADE_NEGOTIATION.SAFE_TO_CLOSE_PRIOR_CHANNEL from the
|
||||
// other, and upon doing so, close the prior EndpointChannel.
|
||||
class BwuManager : public EndpointManager::FrameProcessor {
|
||||
public:
|
||||
using UpgradePathInfo = BwuHandler::UpgradePathInfo;
|
||||
|
||||
struct Config {
|
||||
BooleanMediumSelector allow_upgrade_to;
|
||||
absl::Duration bandwidth_upgrade_retry_delay;
|
||||
absl::Duration bandwidth_upgrade_retry_max_delay;
|
||||
};
|
||||
|
||||
BwuManager(Mediums& mediums, EndpointManager& endpoint_manager,
|
||||
EndpointChannelManager& channel_manager,
|
||||
absl::flat_hash_map<Medium, std::unique_ptr<BwuHandler>> handlers,
|
||||
Config config);
|
||||
|
||||
~BwuManager() override;
|
||||
|
||||
// This is the point on the outbound BWU protocol where the handler_ is set.
|
||||
// Function initiates the bandwidth upgrade and sends an
|
||||
// UPGRADE_PATH_AVAILABLE OfflineFrame.
|
||||
void InitiateBwuForEndpoint(ClientProxy* client_proxy,
|
||||
const std::string& endpoint_id,
|
||||
Medium new_medium = Medium::UNKNOWN_MEDIUM);
|
||||
|
||||
// == EndpointManager::FrameProcessor interface ==.
|
||||
// This is the point on the inbound BWU protocol where the handler_ is set.
|
||||
// This is also an entry point for handling messages for both outbound and
|
||||
// inbound BWU protocol.
|
||||
// @EndpointManagerReaderThread
|
||||
void OnIncomingFrame(OfflineFrame& frame, const std::string& endpoint_id,
|
||||
ClientProxy* client, Medium medium) override;
|
||||
|
||||
// Cleans up in-progress upgrades after endpoint disconnection.
|
||||
// @EndpointManagerReaderThread
|
||||
void OnEndpointDisconnect(ClientProxy* client_proxy,
|
||||
const std::string& endpoint_id,
|
||||
CountDownLatch barrier) override;
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
static constexpr absl::Duration kReadClientIntroductionFrameTimeout =
|
||||
absl::Seconds(5);
|
||||
BwuHandler* SetCurrentBwuHandler(Medium medium);
|
||||
void InitBwuHandlers();
|
||||
void RunOnBwuManagerThread(const std::string& name,
|
||||
std::function<void()> runnable);
|
||||
std::vector<Medium> StripOutUnavailableMediums(
|
||||
const std::vector<Medium>& mediums);
|
||||
Medium ChooseBestUpgradeMedium(const std::vector<Medium>& mediums);
|
||||
|
||||
// BaseBwuHandler
|
||||
using ClientIntroduction = BwuNegotiationFrame::ClientIntroduction;
|
||||
|
||||
// Processes the BwuNegotiationFrames that come over the
|
||||
// EndpointChannel on both initiator and responder side of the upgrade.
|
||||
void OnBwuNegotiationFrame(ClientProxy* client,
|
||||
const BwuNegotiationFrame frame,
|
||||
const string& endpoint_id);
|
||||
|
||||
// Called to revert any state changed by the Initiator or Responder in the
|
||||
// course of setting up the upgraded medium for an endpoint.
|
||||
void Revert();
|
||||
|
||||
// Common functionality to take an incoming connection and go through the
|
||||
// upgrade process. This is a callback, invoked by concrete handlers, once
|
||||
// connection is available.
|
||||
void OnIncomingConnection(
|
||||
ClientProxy* client,
|
||||
std::unique_ptr<BwuHandler::IncomingSocketConnection> mutable_connection);
|
||||
|
||||
void RunUpgradeProtocol(ClientProxy* client, const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> new_channel);
|
||||
void RunUpgradeFailedProtocol(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
const UpgradePathInfo& upgrade_path_info);
|
||||
void ProcessBwuPathAvailableEvent(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
const UpgradePathInfo& upgrade_path_info);
|
||||
std::unique_ptr<EndpointChannel> ProcessBwuPathAvailableEventInternal(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const UpgradePathInfo& upgrade_path_info);
|
||||
void ProcessLastWriteToPriorChannelEvent(ClientProxy* client,
|
||||
const std::string& endpoint_id);
|
||||
void ProcessSafeToClosePriorChannelEvent(ClientProxy* client,
|
||||
const std::string& endpoint_id);
|
||||
bool ReadClientIntroductionFrame(EndpointChannel* endpoint_channel,
|
||||
ClientIntroduction& introduction);
|
||||
bool ReadClientIntroductionAckFrame(EndpointChannel* endpoint_channel);
|
||||
bool WriteClientIntroductionAckFrame(EndpointChannel* endpoint_channel);
|
||||
void ProcessEndpointDisconnection(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
CountDownLatch* barrier);
|
||||
void ProcessUpgradeFailureEvent(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
const UpgradePathInfo& upgrade_info);
|
||||
void CancelRetryUpgradeAlarm(const std::string& endpoint_id);
|
||||
void CancelAllRetryUpgradeAlarms();
|
||||
void RetryUpgradeMediums(ClientProxy* client, const std::string& endpoint_id,
|
||||
std::vector<Medium> upgrade_mediums);
|
||||
Medium GetEndpointMedium(const std::string& endpoint_id);
|
||||
absl::Duration CalculateNextRetryDelay(const std::string& endpoint_id);
|
||||
void RetryUpgradesAfterDelay(ClientProxy* client,
|
||||
const std::string& endpoint_id);
|
||||
void AttemptToRecordBandwidthUpgradeErrorForUnknownEndpoint(
|
||||
proto::connections::BandwidthUpgradeResult result,
|
||||
proto::connections::BandwidthUpgradeErrorStage error_stage);
|
||||
|
||||
Config config_;
|
||||
|
||||
Medium medium_ = Medium::UNKNOWN_MEDIUM;
|
||||
BwuHandler* handler_ = nullptr;
|
||||
Mediums* mediums_;
|
||||
absl::flat_hash_map<Medium, std::unique_ptr<BwuHandler>> handlers_;
|
||||
|
||||
EndpointManager* endpoint_manager_;
|
||||
EndpointChannelManager* channel_manager_;
|
||||
ScheduledExecutor alarm_executor_;
|
||||
SingleThreadExecutor serial_executor_;
|
||||
// Stores each upgraded endpoint's previous EndpointChannel (that was
|
||||
// displaced in favor of a new EndpointChannel) temporarily, until it can
|
||||
// safely be shut down for good in processLastWriteToPriorChannelEvent().
|
||||
absl::flat_hash_map<std::string, std::shared_ptr<EndpointChannel>>
|
||||
previous_endpoint_channels_;
|
||||
absl::flat_hash_set<std::string> successfully_upgraded_endpoints_;
|
||||
// Maps endpointId -> ClientProxy for which
|
||||
// initiateBwuForEndpoint() has been called but which have not
|
||||
// yet completed the upgrade via onIncomingConnection().
|
||||
absl::flat_hash_map<std::string, ClientProxy*> in_progress_upgrades_;
|
||||
// Maps endpointId -> timestamp of when the SAFE_TO_CLOSE message was written.
|
||||
absl::flat_hash_map<std::string, absl::Time> safe_to_close_write_timestamps_;
|
||||
absl::flat_hash_map<std::string, std::pair<CancelableAlarm, absl::Duration>>
|
||||
retry_upgrade_alarms_;
|
||||
// Maps endpointId -> duration of delay before bwu retry.
|
||||
// When bwu failed, retry_upgrade_alarms_ will clear the entry before the
|
||||
// retry happen, then we can not find the last delay used in the alarm. Thus
|
||||
// using a different map to keep track of the delays per endpoint.
|
||||
absl::flat_hash_map<std::string, absl::Duration> retry_delays_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_BWU_MANAGER_H_
|
||||
@@ -1,111 +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 "core/internal/bwu_manager.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_channel_manager.h"
|
||||
#include "core/internal/endpoint_manager.h"
|
||||
#include "core/internal/mediums/mediums.h"
|
||||
#include "core/internal/mediums/utils.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
TEST(BwuManagerTest, CanCreateInstance) {
|
||||
Mediums mediums;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em{&ecm};
|
||||
BwuManager bwu_manager{mediums, em, ecm, {}, {}};
|
||||
|
||||
SystemClock::Sleep(absl::Seconds(3));
|
||||
|
||||
bwu_manager.Shutdown();
|
||||
}
|
||||
|
||||
TEST(BwuManagerTest, CanInitiateBwu) {
|
||||
ClientProxy client;
|
||||
std::string endpoint_id("EP_A");
|
||||
Mediums mediums;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em{&ecm};
|
||||
BwuManager bwu_manager{mediums, em, ecm, {}, {}};
|
||||
|
||||
// Method returns void, so we just verify we did not SEGFAULT while calling.
|
||||
bwu_manager.InitiateBwuForEndpoint(&client, endpoint_id);
|
||||
SystemClock::Sleep(absl::Seconds(3));
|
||||
|
||||
bwu_manager.Shutdown();
|
||||
}
|
||||
|
||||
TEST(BwuManagerTest, CanProcessBandwidthUpgradeFrames) {
|
||||
ClientProxy client;
|
||||
std::string endpoint_id("EP_A");
|
||||
LocationHint location_hint = Utils::BuildLocationHint("US");
|
||||
Mediums mediums;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em{&ecm};
|
||||
BwuManager bwu_manager{mediums, em, ecm, {}, {}};
|
||||
|
||||
ExceptionOr<OfflineFrame> path_available_frame = parser::FromBytes(
|
||||
parser::ForBwuWebrtcPathAvailable("my_id", location_hint));
|
||||
bwu_manager.OnIncomingFrame(path_available_frame.result(), endpoint_id,
|
||||
&client, Medium::WEB_RTC);
|
||||
|
||||
ExceptionOr<OfflineFrame> last_write_frame =
|
||||
parser::FromBytes(parser::ForBwuLastWrite());
|
||||
bwu_manager.OnIncomingFrame(last_write_frame.result(), endpoint_id, &client,
|
||||
Medium::WEB_RTC);
|
||||
|
||||
ExceptionOr<OfflineFrame> safe_to_close_frame =
|
||||
parser::FromBytes(parser::ForBwuSafeToClose());
|
||||
bwu_manager.OnIncomingFrame(safe_to_close_frame.result(), endpoint_id,
|
||||
&client, Medium::WEB_RTC);
|
||||
|
||||
bwu_manager.Shutdown();
|
||||
}
|
||||
|
||||
TEST(BwuManagerTest, InitiateBwu_UpgradeFails_NoCrash) {
|
||||
ClientProxy client;
|
||||
std::string endpoint_id("EP_A");
|
||||
Mediums mediums;
|
||||
EndpointChannelManager ecm;
|
||||
EndpointManager em{&ecm};
|
||||
BwuManager bwu_manager{mediums, em, ecm, {}, {}};
|
||||
parser::UpgradePathInfo upgrade_path_info;
|
||||
upgrade_path_info.set_medium(parser::UpgradePathInfo::WEB_RTC);
|
||||
|
||||
bwu_manager.InitiateBwuForEndpoint(&client, endpoint_id);
|
||||
ExceptionOr<OfflineFrame> bwu_failed_frame =
|
||||
parser::FromBytes(parser::ForBwuFailure(upgrade_path_info));
|
||||
bwu_manager.OnIncomingFrame(bwu_failed_frame.result(), endpoint_id, &client,
|
||||
Medium::WEB_RTC);
|
||||
|
||||
bwu_manager.Shutdown();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,773 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/client_proxy.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "platform/base/error_code_recorder.h"
|
||||
#include "platform/base/feature_flags.h"
|
||||
#include "platform/base/prng.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// The definition is necessary before C++17.
|
||||
constexpr absl::Duration
|
||||
ClientProxy::kHighPowerAdvertisementEndpointIdCacheTimeout;
|
||||
|
||||
constexpr char kEndpointIdChars[] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
|
||||
|
||||
ClientProxy::ClientProxy(analytics::EventLogger* event_logger)
|
||||
: client_id_(Prng().NextInt64()) {
|
||||
NEARBY_LOGS(INFO) << "ClientProxy ctor event_logger=" << event_logger;
|
||||
analytics_recorder_ =
|
||||
std::make_unique<analytics::AnalyticsRecorder>(event_logger);
|
||||
error_code_recorder_ = std::make_unique<ErrorCodeRecorder>(
|
||||
[this](const ErrorCodeParams& params) {
|
||||
analytics_recorder_->OnErrorCode(params);
|
||||
});
|
||||
}
|
||||
|
||||
ClientProxy::~ClientProxy() { Reset(); }
|
||||
|
||||
std::int64_t ClientProxy::GetClientId() const { return client_id_; }
|
||||
|
||||
std::string ClientProxy::GetLocalEndpointId() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (local_endpoint_id_.empty()) {
|
||||
local_endpoint_id_ = GenerateLocalEndpointId();
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [Local Endpoint Generated]: client="
|
||||
<< GetClientId()
|
||||
<< "; endpoint_id=" << local_endpoint_id_;
|
||||
}
|
||||
return local_endpoint_id_;
|
||||
}
|
||||
|
||||
std::string ClientProxy::GetConnectionToken(const std::string& endpoint_id) {
|
||||
Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
return item->connection_token;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string ClientProxy::GenerateLocalEndpointId() {
|
||||
if (high_vis_mode_) {
|
||||
if (!local_high_vis_mode_cache_endpoint_id_.empty()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [Local Endpoint Re-using cached endpoint id]: client="
|
||||
<< GetClientId() << "; local_high_vis_mode_cache_endpoint_id_="
|
||||
<< local_high_vis_mode_cache_endpoint_id_;
|
||||
return local_high_vis_mode_cache_endpoint_id_;
|
||||
}
|
||||
}
|
||||
std::string id;
|
||||
for (int i = 0; i < kEndpointIdLength; i++) {
|
||||
id += kEndpointIdChars[prng_.NextUint32() % sizeof(kEndpointIdChars)];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void ClientProxy::Reset() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
StoppedAdvertising();
|
||||
StoppedDiscovery();
|
||||
RemoveAllEndpoints();
|
||||
ExitHighVisibilityMode();
|
||||
analytics_recorder_->LogSession();
|
||||
}
|
||||
|
||||
void ClientProxy::StartedAdvertising(
|
||||
const std::string& service_id, Strategy strategy,
|
||||
const ConnectionListener& listener,
|
||||
absl::Span<proto::connections::Medium> mediums,
|
||||
const AdvertisingOptions& advertising_options) {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [StartedAdvertising]: client="
|
||||
<< GetClientId();
|
||||
|
||||
if (high_vis_mode_) {
|
||||
local_high_vis_mode_cache_endpoint_id_ = local_endpoint_id_;
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [High Visibility Mode Adv, Cache EndpointId]: client="
|
||||
<< GetClientId() << "; local_high_vis_mode_cache_endpoint_id_="
|
||||
<< local_high_vis_mode_cache_endpoint_id_;
|
||||
CancelClearLocalHighVisModeCacheEndpointIdAlarm();
|
||||
}
|
||||
|
||||
advertising_info_ = {service_id, listener};
|
||||
advertising_options_ = advertising_options;
|
||||
|
||||
const std::vector<proto::connections::Medium> medium_vector(mediums.begin(),
|
||||
mediums.end());
|
||||
analytics_recorder_->OnStartAdvertising(strategy, medium_vector, false, 0);
|
||||
}
|
||||
|
||||
void ClientProxy::StoppedAdvertising() {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [StoppedAdvertising]: client="
|
||||
<< GetClientId();
|
||||
|
||||
if (IsAdvertising()) {
|
||||
advertising_info_.Clear();
|
||||
analytics_recorder_->OnStopAdvertising();
|
||||
}
|
||||
// advertising_options_ is purposefully not cleared here.
|
||||
ResetLocalEndpointIdIfNeeded();
|
||||
|
||||
ExitHighVisibilityMode();
|
||||
}
|
||||
|
||||
bool ClientProxy::IsAdvertising() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return !advertising_info_.IsEmpty();
|
||||
}
|
||||
|
||||
std::string ClientProxy::GetAdvertisingServiceId() const {
|
||||
MutexLock lock(&mutex_);
|
||||
return advertising_info_.service_id;
|
||||
}
|
||||
|
||||
std::string ClientProxy::GetServiceId() const {
|
||||
MutexLock lock(&mutex_);
|
||||
if (IsAdvertising()) return advertising_info_.service_id;
|
||||
if (IsDiscovering()) return discovery_info_.service_id;
|
||||
return "idle_service_id";
|
||||
}
|
||||
|
||||
void ClientProxy::StartedDiscovery(
|
||||
const std::string& service_id, Strategy strategy,
|
||||
const DiscoveryListener& listener,
|
||||
absl::Span<proto::connections::Medium> mediums,
|
||||
const DiscoveryOptions& discovery_options) {
|
||||
MutexLock lock(&mutex_);
|
||||
discovery_info_ = DiscoveryInfo{service_id, listener};
|
||||
discovery_options_ = discovery_options;
|
||||
|
||||
const std::vector<proto::connections::Medium> medium_vector(mediums.begin(),
|
||||
mediums.end());
|
||||
analytics_recorder_->OnStartDiscovery(strategy, medium_vector, false, 0);
|
||||
}
|
||||
|
||||
void ClientProxy::StoppedDiscovery() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (IsDiscovering()) {
|
||||
discovered_endpoint_ids_.clear();
|
||||
discovery_info_.Clear();
|
||||
analytics_recorder_->OnStopDiscovery();
|
||||
}
|
||||
// discovery_options_ is purposefully not cleared here.
|
||||
ResetLocalEndpointIdIfNeeded();
|
||||
}
|
||||
|
||||
bool ClientProxy::IsDiscoveringServiceId(const std::string& service_id) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsDiscovering() && service_id == discovery_info_.service_id;
|
||||
}
|
||||
|
||||
bool ClientProxy::IsDiscovering() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return !discovery_info_.IsEmpty();
|
||||
}
|
||||
|
||||
std::string ClientProxy::GetDiscoveryServiceId() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return discovery_info_.service_id;
|
||||
}
|
||||
|
||||
void ClientProxy::OnEndpointFound(const std::string& service_id,
|
||||
const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
proto::connections::Medium medium) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [Endpoint Found]: [enter] id="
|
||||
<< endpoint_id << "; service=" << service_id << "; info="
|
||||
<< absl::BytesToHexString(endpoint_info.data());
|
||||
if (!IsDiscoveringServiceId(service_id)) {
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [Endpoint Found]: Ignoring event for id="
|
||||
<< endpoint_id
|
||||
<< " because this client is not discovering.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (discovered_endpoint_ids_.count(endpoint_id)) {
|
||||
NEARBY_LOGS(WARNING)
|
||||
<< "ClientProxy [Endpoint Found]: Ignoring event for id=" << endpoint_id
|
||||
<< " because this client has already reported this endpoint as found.";
|
||||
return;
|
||||
}
|
||||
|
||||
discovered_endpoint_ids_.insert(endpoint_id);
|
||||
discovery_info_.listener.endpoint_found_cb(endpoint_id, endpoint_info,
|
||||
service_id);
|
||||
analytics_recorder_->OnEndpointFound(medium);
|
||||
}
|
||||
|
||||
void ClientProxy::OnEndpointLost(const std::string& service_id,
|
||||
const std::string& endpoint_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [Endpoint Lost]: [enter] id=" << endpoint_id
|
||||
<< "; service=" << service_id;
|
||||
if (!IsDiscoveringServiceId(service_id)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"ClientProxy [Endpoint Lost]: Ignoring event for id=%s because "
|
||||
"this client is not discovering",
|
||||
endpoint_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto it = discovered_endpoint_ids_.find(endpoint_id);
|
||||
if (it == discovered_endpoint_ids_.end()) {
|
||||
NEARBY_LOGS(WARNING)
|
||||
<< "ClientProxy [Endpoint Lost]: Ignoring event for id=" << endpoint_id
|
||||
<< " because this client has not yet reported this endpoint as found";
|
||||
return;
|
||||
}
|
||||
|
||||
discovered_endpoint_ids_.erase(it);
|
||||
discovery_info_.listener.endpoint_lost_cb(endpoint_id);
|
||||
}
|
||||
|
||||
void ClientProxy::OnConnectionInitiated(
|
||||
const std::string& endpoint_id, const ConnectionResponseInfo& info,
|
||||
const ConnectionOptions& connection_options,
|
||||
const ConnectionListener& listener, const std::string& connection_token) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Whether this is incoming or outgoing, the local and remote endpoints both
|
||||
// still need to accept this connection, so set its establishment status to
|
||||
// PENDING.
|
||||
auto result = connections_.emplace(
|
||||
endpoint_id, Connection{
|
||||
.is_incoming = info.is_incoming_connection,
|
||||
.connection_listener = listener,
|
||||
.connection_options = connection_options,
|
||||
.connection_token = connection_token,
|
||||
});
|
||||
// Instead of using structured binding which is nice, but banned
|
||||
// (can not use c++17 features, until chromium does) we unpack manually.
|
||||
auto& pair_iter = result.first;
|
||||
bool inserted = result.second;
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [Connection Initiated]: add Connection: client="
|
||||
<< GetClientId() << "; endpoint_id=" << endpoint_id
|
||||
<< "; inserted=" << inserted;
|
||||
DCHECK(inserted);
|
||||
const Connection& item = pair_iter->second;
|
||||
// Notify the client.
|
||||
//
|
||||
// Note: we allow devices to connect to an advertiser even after it stops
|
||||
// advertising, so no need to check IsAdvertising() here.
|
||||
item.connection_listener.initiated_cb(endpoint_id, info);
|
||||
|
||||
if (info.is_incoming_connection) {
|
||||
// Add CancellationFlag for advertisers once encryption succeeds.
|
||||
AddCancellationFlag(endpoint_id);
|
||||
analytics_recorder_->OnConnectionRequestReceived(endpoint_id);
|
||||
} else {
|
||||
analytics_recorder_->OnConnectionRequestSent(endpoint_id);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientProxy::OnConnectionAccepted(const std::string& endpoint_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!HasPendingConnectionToEndpoint(endpoint_id)) {
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [Connection Accepted]: no pending "
|
||||
"connection; endpoint_id="
|
||||
<< endpoint_id;
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the client.
|
||||
Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
item->connection_listener.accepted_cb(endpoint_id);
|
||||
item->status = Connection::kConnected;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientProxy::OnConnectionRejected(const std::string& endpoint_id,
|
||||
const Status& status) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!HasPendingConnectionToEndpoint(endpoint_id)) {
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [Connection Rejected]: no pending "
|
||||
"connection; endpoint_id="
|
||||
<< endpoint_id;
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the client.
|
||||
const Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
item->connection_listener.rejected_cb(endpoint_id, status);
|
||||
OnDisconnected(endpoint_id, false /* notify */);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientProxy::OnBandwidthChanged(const std::string& endpoint_id,
|
||||
Medium new_medium) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
const Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
item->connection_listener.bandwidth_changed_cb(endpoint_id, new_medium);
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [reporting onBandwidthChanged]: client="
|
||||
<< GetClientId() << "; endpoint_id=" << endpoint_id;
|
||||
}
|
||||
}
|
||||
|
||||
void ClientProxy::OnDisconnected(const std::string& endpoint_id, bool notify) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
const Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
if (notify) {
|
||||
item->connection_listener.disconnected_cb({endpoint_id});
|
||||
}
|
||||
connections_.erase(endpoint_id);
|
||||
ResetLocalEndpointIdIfNeeded();
|
||||
}
|
||||
|
||||
CancelEndpoint(endpoint_id);
|
||||
}
|
||||
|
||||
bool ClientProxy::ConnectionStatusMatches(const std::string& endpoint_id,
|
||||
Connection::Status status) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
const Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
return item->status == status;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BooleanMediumSelector ClientProxy::GetUpgradeMediums(
|
||||
const std::string& endpoint_id) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
const Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
return item->connection_options.allowed;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ClientProxy::IsConnectedToEndpoint(const std::string& endpoint_id) const {
|
||||
return ConnectionStatusMatches(endpoint_id, Connection::kConnected);
|
||||
}
|
||||
|
||||
std::vector<std::string> ClientProxy::GetMatchingEndpoints(
|
||||
std::function<bool(const Connection&)> pred) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
std::vector<std::string> connected_endpoints;
|
||||
|
||||
for (const auto& pair : connections_) {
|
||||
const auto& endpoint_id = pair.first;
|
||||
const auto& connection = pair.second;
|
||||
if (pred(connection)) {
|
||||
connected_endpoints.push_back(endpoint_id);
|
||||
}
|
||||
}
|
||||
return connected_endpoints;
|
||||
}
|
||||
|
||||
std::vector<std::string> ClientProxy::GetPendingConnectedEndpoints() const {
|
||||
return GetMatchingEndpoints([](const Connection& connection) {
|
||||
return connection.status != Connection::kConnected;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<std::string> ClientProxy::GetConnectedEndpoints() const {
|
||||
return GetMatchingEndpoints([](const Connection& connection) {
|
||||
return connection.status == Connection::kConnected;
|
||||
});
|
||||
}
|
||||
|
||||
std::int32_t ClientProxy::GetNumOutgoingConnections() const {
|
||||
return GetMatchingEndpoints([](const Connection& connection) {
|
||||
return connection.status == Connection::kConnected &&
|
||||
!connection.is_incoming;
|
||||
})
|
||||
.size();
|
||||
}
|
||||
|
||||
std::int32_t ClientProxy::GetNumIncomingConnections() const {
|
||||
return GetMatchingEndpoints([](const Connection& connection) {
|
||||
return connection.status == Connection::kConnected &&
|
||||
connection.is_incoming;
|
||||
})
|
||||
.size();
|
||||
}
|
||||
|
||||
bool ClientProxy::HasPendingConnectionToEndpoint(
|
||||
const std::string& endpoint_id) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
const Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
return item->status != Connection::kConnected;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ClientProxy::HasLocalEndpointResponded(
|
||||
const std::string& endpoint_id) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return ConnectionStatusesContains(
|
||||
endpoint_id,
|
||||
static_cast<Connection::Status>(Connection::kLocalEndpointAccepted |
|
||||
Connection::kLocalEndpointRejected));
|
||||
}
|
||||
|
||||
bool ClientProxy::HasRemoteEndpointResponded(
|
||||
const std::string& endpoint_id) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return ConnectionStatusesContains(
|
||||
endpoint_id,
|
||||
static_cast<Connection::Status>(Connection::kRemoteEndpointAccepted |
|
||||
Connection::kRemoteEndpointRejected));
|
||||
}
|
||||
|
||||
void ClientProxy::LocalEndpointAcceptedConnection(
|
||||
const std::string& endpoint_id, const PayloadListener& listener) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (HasLocalEndpointResponded(endpoint_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [Local Accepted]: local endpoint has responded; id="
|
||||
<< endpoint_id;
|
||||
return;
|
||||
}
|
||||
|
||||
AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointAccepted);
|
||||
Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
item->payload_listener = listener;
|
||||
}
|
||||
analytics_recorder_->OnLocalEndpointAccepted(endpoint_id);
|
||||
}
|
||||
|
||||
void ClientProxy::LocalEndpointRejectedConnection(
|
||||
const std::string& endpoint_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (HasLocalEndpointResponded(endpoint_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [Local Rejected]: local endpoint has responded; id="
|
||||
<< endpoint_id;
|
||||
return;
|
||||
}
|
||||
|
||||
AppendConnectionStatus(endpoint_id, Connection::kLocalEndpointRejected);
|
||||
analytics_recorder_->OnLocalEndpointRejected(endpoint_id);
|
||||
}
|
||||
|
||||
void ClientProxy::RemoteEndpointAcceptedConnection(
|
||||
const std::string& endpoint_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (HasRemoteEndpointResponded(endpoint_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [Remote Accepted]: remote endpoint has responded; id="
|
||||
<< endpoint_id;
|
||||
return;
|
||||
}
|
||||
|
||||
AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointAccepted);
|
||||
analytics_recorder_->OnRemoteEndpointAccepted(endpoint_id);
|
||||
}
|
||||
|
||||
void ClientProxy::RemoteEndpointRejectedConnection(
|
||||
const std::string& endpoint_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (HasRemoteEndpointResponded(endpoint_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [Remote Rejected]: remote endpoint has responded; id="
|
||||
<< endpoint_id;
|
||||
return;
|
||||
}
|
||||
|
||||
AppendConnectionStatus(endpoint_id, Connection::kRemoteEndpointRejected);
|
||||
analytics_recorder_->OnRemoteEndpointRejected(endpoint_id);
|
||||
}
|
||||
|
||||
bool ClientProxy::IsConnectionAccepted(const std::string& endpoint_id) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return ConnectionStatusesContains(endpoint_id,
|
||||
Connection::kLocalEndpointAccepted) &&
|
||||
ConnectionStatusesContains(endpoint_id,
|
||||
Connection::kRemoteEndpointAccepted);
|
||||
}
|
||||
|
||||
bool ClientProxy::IsConnectionRejected(const std::string& endpoint_id) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return ConnectionStatusesContains(
|
||||
endpoint_id,
|
||||
static_cast<Connection::Status>(Connection::kLocalEndpointRejected |
|
||||
Connection::kRemoteEndpointRejected));
|
||||
}
|
||||
|
||||
bool ClientProxy::LocalConnectionIsAccepted(std::string endpoint_id) const {
|
||||
return ConnectionStatusesContains(
|
||||
endpoint_id, ClientProxy::Connection::kLocalEndpointAccepted);
|
||||
}
|
||||
|
||||
bool ClientProxy::RemoteConnectionIsAccepted(std::string endpoint_id) const {
|
||||
return ConnectionStatusesContains(
|
||||
endpoint_id, ClientProxy::Connection::kRemoteEndpointAccepted);
|
||||
}
|
||||
|
||||
void ClientProxy::AddCancellationFlag(const std::string& endpoint_id) {
|
||||
// Don't insert the CancellationFlag to the map if feature flag is disabled.
|
||||
if (!FeatureFlags::GetInstance().GetFlags().enable_cancellation_flag) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto item = cancellation_flags_.find(endpoint_id);
|
||||
if (item != cancellation_flags_.end()) {
|
||||
return;
|
||||
}
|
||||
cancellation_flags_.emplace(endpoint_id,
|
||||
std::make_unique<CancellationFlag>());
|
||||
}
|
||||
|
||||
CancellationFlag* ClientProxy::GetCancellationFlag(
|
||||
const std::string& endpoint_id) {
|
||||
const auto item = cancellation_flags_.find(endpoint_id);
|
||||
if (item == cancellation_flags_.end()) {
|
||||
return default_cancellation_flag_.get();
|
||||
}
|
||||
return item->second.get();
|
||||
}
|
||||
|
||||
void ClientProxy::CancelEndpoint(const std::string& endpoint_id) {
|
||||
const auto item = cancellation_flags_.find(endpoint_id);
|
||||
if (item == cancellation_flags_.end()) return;
|
||||
item->second->Cancel();
|
||||
cancellation_flags_.erase(item);
|
||||
}
|
||||
|
||||
void ClientProxy::CancelAllEndpoints() {
|
||||
for (const auto& item : cancellation_flags_) {
|
||||
CancellationFlag* cancellation_flag = item.second.get();
|
||||
if (cancellation_flag->Cancelled()) {
|
||||
continue;
|
||||
}
|
||||
cancellation_flag->Cancel();
|
||||
}
|
||||
cancellation_flags_.clear();
|
||||
}
|
||||
|
||||
void ClientProxy::OnPayload(const std::string& endpoint_id, Payload payload) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (IsConnectedToEndpoint(endpoint_id)) {
|
||||
const Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [reporting onPayloadReceived]: client="
|
||||
<< GetClientId() << "; endpoint_id=" << endpoint_id
|
||||
<< " ; payload_id=" << payload.GetId();
|
||||
item->payload_listener.payload_cb(endpoint_id, std::move(payload));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ClientProxy::Connection* ClientProxy::LookupConnection(
|
||||
const std::string& endpoint_id) const {
|
||||
auto item = connections_.find(endpoint_id);
|
||||
return item != connections_.end() ? &item->second : nullptr;
|
||||
}
|
||||
|
||||
ClientProxy::Connection* ClientProxy::LookupConnection(
|
||||
const std::string& endpoint_id) {
|
||||
auto item = connections_.find(endpoint_id);
|
||||
return item != connections_.end() ? &item->second : nullptr;
|
||||
}
|
||||
|
||||
void ClientProxy::OnPayloadProgress(const std::string& endpoint_id,
|
||||
const PayloadProgressInfo& info) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (IsConnectedToEndpoint(endpoint_id)) {
|
||||
Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
item->payload_listener.payload_progress_cb(endpoint_id, info);
|
||||
|
||||
if (info.status == PayloadProgressInfo::Status::kInProgress) {
|
||||
NEARBY_LOGS(VERBOSE)
|
||||
<< "ClientProxy [reporting onPayloadProgress]: client="
|
||||
<< GetClientId() << "; endpoint_id=" << endpoint_id
|
||||
<< "; payload_id=" << info.payload_id
|
||||
<< ", payload_status=" << ToString(info.status);
|
||||
} else {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [reporting onPayloadProgress]: client="
|
||||
<< GetClientId() << "; endpoint_id=" << endpoint_id
|
||||
<< "; payload_id=" << info.payload_id
|
||||
<< ", payload_status=" << ToString(info.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientProxy::RemoveAllEndpoints() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Note: we may want to notify the client of onDisconnected() for each
|
||||
// endpoint, in the case when this is called from stopAllEndpoints(). For now,
|
||||
// just remove without notifying.
|
||||
connections_.clear();
|
||||
cancellation_flags_.clear();
|
||||
local_endpoint_id_.clear();
|
||||
}
|
||||
|
||||
void ClientProxy::ResetLocalEndpointIdIfNeeded() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (connections_.empty() && !IsAdvertising() && !IsDiscovering()) {
|
||||
local_endpoint_id_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientProxy::ConnectionStatusesContains(
|
||||
const std::string& endpoint_id, Connection::Status status_to_match) const {
|
||||
const Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
return (item->status & status_to_match) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClientProxy::AppendConnectionStatus(const std::string& endpoint_id,
|
||||
Connection::Status status_to_append) {
|
||||
Connection* item = LookupConnection(endpoint_id);
|
||||
if (item != nullptr) {
|
||||
item->status =
|
||||
static_cast<Connection::Status>(item->status | status_to_append);
|
||||
}
|
||||
}
|
||||
|
||||
AdvertisingOptions ClientProxy::GetAdvertisingOptions() const {
|
||||
return advertising_options_;
|
||||
}
|
||||
|
||||
DiscoveryOptions ClientProxy::GetDiscoveryOptions() const {
|
||||
return discovery_options_;
|
||||
}
|
||||
|
||||
void ClientProxy::EnterHighVisibilityMode() {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [EnterHighVisibilityMode]: client="
|
||||
<< GetClientId();
|
||||
|
||||
high_vis_mode_ = true;
|
||||
}
|
||||
|
||||
void ClientProxy::ExitHighVisibilityMode() {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [ExitHighVisibilityMode]: client="
|
||||
<< GetClientId();
|
||||
|
||||
high_vis_mode_ = false;
|
||||
ScheduleClearLocalHighVisModeCacheEndpointIdAlarm();
|
||||
}
|
||||
|
||||
void ClientProxy::ScheduleClearLocalHighVisModeCacheEndpointIdAlarm() {
|
||||
CancelClearLocalHighVisModeCacheEndpointIdAlarm();
|
||||
|
||||
if (local_high_vis_mode_cache_endpoint_id_.empty()) {
|
||||
NEARBY_LOGS(VERBOSE) << "ClientProxy [There is no cached local high power "
|
||||
"advertising endpoint Id]: client="
|
||||
<< GetClientId();
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule to clear cache high visibility mode advertisement endpoint id in
|
||||
// 30s.
|
||||
NEARBY_LOGS(INFO) << "ClientProxy [High Visibility Mode Adv, Schedule to "
|
||||
"Clear Cache EndpointId]: client="
|
||||
<< GetClientId()
|
||||
<< "; local_high_vis_mode_cache_endpoint_id_="
|
||||
<< local_high_vis_mode_cache_endpoint_id_;
|
||||
clear_local_high_vis_mode_cache_endpoint_id_alarm_ =
|
||||
CancelableAlarm(
|
||||
"clear_high_power_endpoint_id_cache",
|
||||
[this]() {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "ClientProxy [Cleared cached local high power advertising "
|
||||
"endpoint Id.]: client="
|
||||
<< GetClientId() << "; local_high_vis_mode_cache_endpoint_id_="
|
||||
<< local_high_vis_mode_cache_endpoint_id_;
|
||||
local_high_vis_mode_cache_endpoint_id_.clear();
|
||||
},
|
||||
kHighPowerAdvertisementEndpointIdCacheTimeout,
|
||||
&single_thread_executor_);
|
||||
}
|
||||
|
||||
void ClientProxy::CancelClearLocalHighVisModeCacheEndpointIdAlarm() {
|
||||
if (clear_local_high_vis_mode_cache_endpoint_id_alarm_.IsValid()) {
|
||||
clear_local_high_vis_mode_cache_endpoint_id_alarm_.Cancel();
|
||||
clear_local_high_vis_mode_cache_endpoint_id_alarm_ = CancelableAlarm();
|
||||
}
|
||||
}
|
||||
|
||||
std::string ClientProxy::ToString(PayloadProgressInfo::Status status) const {
|
||||
switch (status) {
|
||||
case PayloadProgressInfo::Status::kSuccess:
|
||||
return std::string("Success");
|
||||
case PayloadProgressInfo::Status::kFailure:
|
||||
return std::string("Failure");
|
||||
case PayloadProgressInfo::Status::kInProgress:
|
||||
return std::string("In Progress");
|
||||
case PayloadProgressInfo::Status::kCanceled:
|
||||
return std::string("Cancelled");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,326 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_INTERNAL_CLIENT_PROXY_H_
|
||||
#define CORE_INTERNAL_CLIENT_PROXY_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/advertising_options.h"
|
||||
#include "core/discovery_options.h"
|
||||
#include "core/listeners.h"
|
||||
#include "core/status.h"
|
||||
#include "core/strategy.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/cancellation_flag.h"
|
||||
#include "platform/base/error_code_recorder.h"
|
||||
#include "platform/base/prng.h"
|
||||
#include "platform/public/cancelable_alarm.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "internal/analytics/analytics_recorder.h"
|
||||
// Prefer using absl:: versions of a set and a map; they tend to be more
|
||||
// efficient: implementation is using open-addressing hash tables.
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/types/span.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// ClientProxy is tracking state of client's connection, and serves as
|
||||
// a proxy for notifications sent to this client.
|
||||
class ClientProxy final {
|
||||
public:
|
||||
static constexpr int kEndpointIdLength = 4;
|
||||
static constexpr absl::Duration
|
||||
kHighPowerAdvertisementEndpointIdCacheTimeout = absl::Seconds(30);
|
||||
|
||||
explicit ClientProxy(analytics::EventLogger* event_logger = nullptr);
|
||||
~ClientProxy();
|
||||
ClientProxy(ClientProxy&&) = default;
|
||||
ClientProxy& operator=(ClientProxy&&) = default;
|
||||
|
||||
std::int64_t GetClientId() const;
|
||||
|
||||
std::string GetLocalEndpointId();
|
||||
|
||||
analytics::AnalyticsRecorder& GetAnalyticsRecorder() const {
|
||||
return *analytics_recorder_;
|
||||
}
|
||||
|
||||
std::string GetConnectionToken(const std::string& endpoint_id);
|
||||
|
||||
// Clears all the runtime state of this client.
|
||||
void Reset();
|
||||
|
||||
// Marks this client as advertising with the given callbacks.
|
||||
void StartedAdvertising(
|
||||
const std::string& service_id, Strategy strategy,
|
||||
const ConnectionListener& connection_lifecycle_listener,
|
||||
absl::Span<proto::connections::Medium> mediums,
|
||||
const AdvertisingOptions& advertising_options = AdvertisingOptions{});
|
||||
// Marks this client as not advertising.
|
||||
void StoppedAdvertising();
|
||||
bool IsAdvertising() const;
|
||||
std::string GetAdvertisingServiceId() const;
|
||||
|
||||
// Get service ID of a surrently active link (either advertising, or
|
||||
// discovering).
|
||||
std::string GetServiceId() const;
|
||||
|
||||
// Marks this client as discovering with the given callback.
|
||||
void StartedDiscovery(
|
||||
const std::string& service_id, Strategy strategy,
|
||||
const DiscoveryListener& discovery_listener,
|
||||
absl::Span<proto::connections::Medium> mediums,
|
||||
const DiscoveryOptions& discovery_options = DiscoveryOptions{});
|
||||
// Marks this client as not discovering at all.
|
||||
void StoppedDiscovery();
|
||||
bool IsDiscoveringServiceId(const std::string& service_id) const;
|
||||
bool IsDiscovering() const;
|
||||
std::string GetDiscoveryServiceId() const;
|
||||
|
||||
// Proxies to the client's DiscoveryListener::OnEndpointFound() callback.
|
||||
void OnEndpointFound(const std::string& service_id,
|
||||
const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
proto::connections::Medium medium);
|
||||
// Proxies to the client's DiscoveryListener::OnEndpointLost() callback.
|
||||
void OnEndpointLost(const std::string& service_id,
|
||||
const std::string& endpoint_id);
|
||||
|
||||
// Proxies to the client's ConnectionListener::OnInitiated() callback.
|
||||
void OnConnectionInitiated(const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info,
|
||||
const ConnectionOptions& connection_options,
|
||||
const ConnectionListener& listener,
|
||||
const std::string& connection_token);
|
||||
|
||||
// Proxies to the client's ConnectionListener::OnAccepted() callback.
|
||||
void OnConnectionAccepted(const std::string& endpoint_id);
|
||||
// Proxies to the client's ConnectionListener::OnRejected() callback.
|
||||
void OnConnectionRejected(const std::string& endpoint_id,
|
||||
const Status& status);
|
||||
|
||||
void OnBandwidthChanged(const std::string& endpoint_id, Medium new_medium);
|
||||
|
||||
// Removes the endpoint from this client's list of connected endpoints. If
|
||||
// notify is true, also calls the client's
|
||||
// ConnectionListener.disconnected_cb() callback.
|
||||
void OnDisconnected(const std::string& endpoint_id, bool notify);
|
||||
|
||||
// Returns all mediums eligible for upgrade.
|
||||
BooleanMediumSelector GetUpgradeMediums(const std::string& endpoint_id) const;
|
||||
// Returns true if it's safe to send payloads to this endpoint.
|
||||
bool IsConnectedToEndpoint(const std::string& endpoint_id) const;
|
||||
// Returns all endpoints that can safely be sent payloads.
|
||||
std::vector<std::string> GetConnectedEndpoints() const;
|
||||
// Returns all endpoints that are still awaiting acceptance.
|
||||
std::vector<std::string> GetPendingConnectedEndpoints() const;
|
||||
// Returns the number of endpoints that are connected and outgoing.
|
||||
std::int32_t GetNumOutgoingConnections() const;
|
||||
// Returns the number of endpoints that are connected and incoming.
|
||||
std::int32_t GetNumIncomingConnections() const;
|
||||
// If true, then we're in the process of approving (or rejecting) a
|
||||
// connection. No payloads should be sent until isConnectedToEndpoint()
|
||||
// returns true.
|
||||
bool HasPendingConnectionToEndpoint(const std::string& endpoint_id) const;
|
||||
// Returns true if the local endpoint has already marked itself as
|
||||
// accepted/rejected.
|
||||
bool HasLocalEndpointResponded(const std::string& endpoint_id) const;
|
||||
// Returns true if the remote endpoint has already marked themselves as
|
||||
// accepted/rejected.
|
||||
bool HasRemoteEndpointResponded(const std::string& endpoint_id) const;
|
||||
// Marks the local endpoint as having accepted the connection.
|
||||
void LocalEndpointAcceptedConnection(const std::string& endpoint_id,
|
||||
const PayloadListener& listener);
|
||||
// Marks the local endpoint as having rejected the connection.
|
||||
void LocalEndpointRejectedConnection(const std::string& endpoint_id);
|
||||
// Marks the remote endpoint as having accepted the connection.
|
||||
void RemoteEndpointAcceptedConnection(const std::string& endpoint_id);
|
||||
// Marks the remote endpoint as having rejected the connection.
|
||||
void RemoteEndpointRejectedConnection(const std::string& endpoint_id);
|
||||
// Returns true if both the local endpoint and the remote endpoint have
|
||||
// accepted the connection.
|
||||
bool IsConnectionAccepted(const std::string& endpoint_id) const;
|
||||
// Returns true if either the local endpoint or the remote endpoint has
|
||||
// rejected the connection.
|
||||
bool IsConnectionRejected(const std::string& endpoint_id) const;
|
||||
|
||||
// Proxies to the client's PayloadListener::OnPayload() callback.
|
||||
void OnPayload(const std::string& endpoint_id, Payload payload);
|
||||
// Proxies to the client's PayloadListener::OnPayloadProgress() callback.
|
||||
void OnPayloadProgress(const std::string& endpoint_id,
|
||||
const PayloadProgressInfo& info);
|
||||
bool LocalConnectionIsAccepted(std::string endpoint_id) const;
|
||||
bool RemoteConnectionIsAccepted(std::string endpoint_id) const;
|
||||
|
||||
// Adds a CancellationFlag for endpoint id.
|
||||
void AddCancellationFlag(const std::string& endpoint_id);
|
||||
// Returns the CancellationFlag for endpoint id,
|
||||
CancellationFlag* GetCancellationFlag(const std::string& endpoint_id);
|
||||
// Sets the CancellationFlag to true for endpoint id.
|
||||
void CancelEndpoint(const std::string& endpoint_id);
|
||||
// Cancels all CancellationFlags.
|
||||
void CancelAllEndpoints();
|
||||
AdvertisingOptions GetAdvertisingOptions() const;
|
||||
DiscoveryOptions GetDiscoveryOptions() const;
|
||||
|
||||
// The endpoint id will be stable for 30 seconds after high visibility mode
|
||||
// (high power and Bluetooth Classic) advertisement stops.
|
||||
// If client re-enters high visibility mode within 30 seconds, he is going to
|
||||
// have the same endpoint id.
|
||||
void EnterHighVisibilityMode();
|
||||
// Cleans up any modifications in high visibility mode. The endpoint id always
|
||||
// rotates.
|
||||
void ExitHighVisibilityMode();
|
||||
|
||||
private:
|
||||
struct Connection {
|
||||
// Status: may be either:
|
||||
// Connection::PENDING, or combination of
|
||||
// Connection::LOCAL_ENDPOINT_ACCEPTED:
|
||||
// Connection::LOCAL_ENDPOINT_REJECTED and
|
||||
// Connection::REMOTE_ENDPOINT_ACCEPTED:
|
||||
// Connection::REMOTE_ENDPOINT_REJECTED, or
|
||||
// Connection::CONNECTED.
|
||||
// Only when this is set to CONNECTED should you allow payload transfers.
|
||||
//
|
||||
// We want this enum to be implicitly convertible to int, because
|
||||
// we perform bit operations on it.
|
||||
enum Status : uint8_t {
|
||||
kPending = 0,
|
||||
kLocalEndpointAccepted = 1 << 0,
|
||||
kLocalEndpointRejected = 1 << 1,
|
||||
kRemoteEndpointAccepted = 1 << 2,
|
||||
kRemoteEndpointRejected = 1 << 3,
|
||||
kConnected = 1 << 4,
|
||||
};
|
||||
bool is_incoming{false};
|
||||
Status status{kPending};
|
||||
ConnectionListener connection_listener;
|
||||
PayloadListener payload_listener;
|
||||
ConnectionOptions connection_options;
|
||||
DiscoveryOptions discovery_options;
|
||||
AdvertisingOptions advertising_options;
|
||||
std::string connection_token;
|
||||
};
|
||||
|
||||
struct AdvertisingInfo {
|
||||
std::string service_id;
|
||||
ConnectionListener listener;
|
||||
void Clear() { service_id.clear(); }
|
||||
bool IsEmpty() const { return service_id.empty(); }
|
||||
};
|
||||
|
||||
struct DiscoveryInfo {
|
||||
std::string service_id;
|
||||
DiscoveryListener listener;
|
||||
void Clear() { service_id.clear(); }
|
||||
bool IsEmpty() const { return service_id.empty(); }
|
||||
};
|
||||
|
||||
void RemoveAllEndpoints();
|
||||
void ResetLocalEndpointIdIfNeeded();
|
||||
bool ConnectionStatusesContains(const std::string& endpoint_id,
|
||||
Connection::Status status_to_match) const;
|
||||
void AppendConnectionStatus(const std::string& endpoint_id,
|
||||
Connection::Status status_to_append);
|
||||
|
||||
const Connection* LookupConnection(const std::string& endpoint_id) const;
|
||||
Connection* LookupConnection(const std::string& endpoint_id);
|
||||
bool ConnectionStatusMatches(const std::string& endpoint_id,
|
||||
Connection::Status status) const;
|
||||
std::vector<std::string> GetMatchingEndpoints(
|
||||
std::function<bool(const Connection&)> pred) const;
|
||||
std::string GenerateLocalEndpointId();
|
||||
|
||||
void ScheduleClearLocalHighVisModeCacheEndpointIdAlarm();
|
||||
void CancelClearLocalHighVisModeCacheEndpointIdAlarm();
|
||||
|
||||
std::string ToString(PayloadProgressInfo::Status status) const;
|
||||
|
||||
mutable RecursiveMutex mutex_;
|
||||
Prng prng_;
|
||||
std::int64_t client_id_;
|
||||
std::string local_endpoint_id_;
|
||||
// If currently is advertising in high visibility mode is true: high power and
|
||||
// Bluetooth Classic enabled. When high_visibility_mode_ is true, the endpoint
|
||||
// id is stable for 30s. When high_visibility_mode_ is false, the endpoint id
|
||||
// always rotates.
|
||||
bool high_vis_mode_{false};
|
||||
// Caches the endpoint id when it is in high visibility mode advertisement for
|
||||
// 30s. Currently, Nearby Connections keeps rotating endpoint id. The client
|
||||
// (Nearby Share) treats different endpoints as different receivers, duplicate
|
||||
// share targets for same devices occur on share sheet in this case.
|
||||
// Therefore, we remember the high visibility mode advertisement endpoint id
|
||||
// here. empty if 1) There is no high power advertisement before 2) The
|
||||
// endpoint id cached here in previous high visibility mode advertisement
|
||||
// expires.
|
||||
std::string local_high_vis_mode_cache_endpoint_id_;
|
||||
ScheduledExecutor single_thread_executor_;
|
||||
CancelableAlarm clear_local_high_vis_mode_cache_endpoint_id_alarm_;
|
||||
|
||||
// If not empty, we are currently advertising and accepting connection
|
||||
// requests for the given service_id.
|
||||
AdvertisingInfo advertising_info_;
|
||||
|
||||
// If not empty, we are currently discovering for the given service_id.
|
||||
DiscoveryInfo discovery_info_;
|
||||
|
||||
// The active ClientProxy's advertising constraints. Empty()
|
||||
// returns true if the client hasn't started advertising false otherwise.
|
||||
// Note: this is not cleared when the client stops advertising because it
|
||||
// might still be useful downstream of advertising (eg: establishing
|
||||
// connections, performing bandwidth upgrades, etc.)
|
||||
AdvertisingOptions advertising_options_;
|
||||
|
||||
// The active ClientProxy's discovery constraints. Null if the client
|
||||
// hasn't started discovering. Note: this is not cleared when the client
|
||||
// stops discovering because it might still be useful downstream of
|
||||
// discovery (eg: connection speed, etc.)
|
||||
DiscoveryOptions discovery_options_;
|
||||
|
||||
// Maps endpoint_id to endpoint connection state.
|
||||
absl::flat_hash_map<std::string, Connection> connections_;
|
||||
|
||||
// A cache of endpoint ids that we've already notified the discoverer of. We
|
||||
// check this cache before calling onEndpointFound() so that we don't notify
|
||||
// the client multiple times for the same endpoint. This would otherwise
|
||||
// happen because some mediums (like Bluetooth) repeatedly give us the same
|
||||
// endpoints after each scan.
|
||||
absl::flat_hash_set<std::string> discovered_endpoint_ids_;
|
||||
|
||||
// Maps endpoint_id to CancellationFlag.
|
||||
absl::flat_hash_map<std::string, std::unique_ptr<CancellationFlag>>
|
||||
cancellation_flags_;
|
||||
// A default cancellation flag with isCancelled set be true.
|
||||
std::unique_ptr<CancellationFlag> default_cancellation_flag_ =
|
||||
std::make_unique<CancellationFlag>(true);
|
||||
|
||||
// An analytics logger with |EventLogger| provided by client, which is default
|
||||
// nullptr as no-op.
|
||||
std::unique_ptr<analytics::AnalyticsRecorder> analytics_recorder_;
|
||||
std::unique_ptr<ErrorCodeRecorder> error_code_recorder_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_CLIENT_PROXY_H_
|
||||
@@ -1,673 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/client_proxy.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "absl/types/span.h"
|
||||
#include "core/listeners.h"
|
||||
#include "core/strategy.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/feature_flags.h"
|
||||
#include "platform/base/medium_environment.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
using ::testing::MockFunction;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
constexpr FeatureFlags::Flags kTestCases[] = {
|
||||
FeatureFlags::Flags{
|
||||
.enable_cancellation_flag = true,
|
||||
},
|
||||
FeatureFlags::Flags{
|
||||
.enable_cancellation_flag = false,
|
||||
},
|
||||
};
|
||||
|
||||
class ClientProxyTest : public ::testing::TestWithParam<FeatureFlags::Flags> {
|
||||
protected:
|
||||
struct MockDiscoveryListener {
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const ByteArray& endpoint_info,
|
||||
const std::string& service_id)>>
|
||||
endpoint_found_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id)>>
|
||||
endpoint_lost_cb;
|
||||
};
|
||||
struct MockConnectionListener {
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info)>>
|
||||
initiated_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id)>> accepted_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const Status& status)>>
|
||||
rejected_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id)>>
|
||||
disconnected_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
std::int32_t quality)>>
|
||||
bandwidth_changed_cb;
|
||||
};
|
||||
struct MockPayloadListener {
|
||||
StrictMock<
|
||||
MockFunction<void(const std::string& endpoint_id, Payload payload)>>
|
||||
payload_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const PayloadProgressInfo& info)>>
|
||||
payload_progress_cb;
|
||||
};
|
||||
|
||||
struct Endpoint {
|
||||
ByteArray info;
|
||||
std::string id;
|
||||
};
|
||||
|
||||
bool ShouldEnterHighVisibilityMode(
|
||||
const AdvertisingOptions& advertising_options) {
|
||||
return !advertising_options.low_power &&
|
||||
advertising_options.allowed.bluetooth;
|
||||
}
|
||||
|
||||
Endpoint StartAdvertising(
|
||||
ClientProxy* client, ConnectionListener listener,
|
||||
AdvertisingOptions advertising_options = AdvertisingOptions{}) {
|
||||
if (ShouldEnterHighVisibilityMode(advertising_options)) {
|
||||
client->EnterHighVisibilityMode();
|
||||
}
|
||||
Endpoint endpoint{
|
||||
.info = ByteArray{"advertising endpoint name"},
|
||||
.id = client->GetLocalEndpointId(),
|
||||
};
|
||||
client->StartedAdvertising(service_id_, strategy_, listener,
|
||||
absl::MakeSpan(mediums_), advertising_options);
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
void StopAdvertising(ClientProxy* client) { client->StoppedAdvertising(); }
|
||||
|
||||
Endpoint StartDiscovery(ClientProxy* client, DiscoveryListener listener) {
|
||||
Endpoint endpoint{
|
||||
.info = ByteArray{"discovery endpoint name"},
|
||||
.id = client->GetLocalEndpointId(),
|
||||
};
|
||||
client->StartedDiscovery(service_id_, strategy_, listener,
|
||||
absl::MakeSpan(mediums_));
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
void OnDiscoveryEndpointFound(ClientProxy* client, const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_.endpoint_found_cb, Call).Times(1);
|
||||
client->OnEndpointFound(service_id_, endpoint.id, endpoint.info, medium_);
|
||||
}
|
||||
|
||||
void OnDiscoveryEndpointLost(ClientProxy* client, const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_.endpoint_lost_cb, Call).Times(1);
|
||||
client->OnEndpointLost(service_id_, endpoint.id);
|
||||
}
|
||||
|
||||
void OnDiscoveryConnectionInitiated(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_connection_.initiated_cb, Call).Times(1);
|
||||
const std::string auth_token{"auth_token"};
|
||||
const ByteArray raw_auth_token{auth_token};
|
||||
const std::string connection_token{"conntokn"};
|
||||
advertising_connection_info_.remote_endpoint_info = endpoint.info;
|
||||
client->OnConnectionInitiated(
|
||||
endpoint.id, advertising_connection_info_, connection_options_,
|
||||
discovery_connection_listener_, connection_token);
|
||||
EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id));
|
||||
}
|
||||
|
||||
void OnDiscoveryConnectionLocalAccepted(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id));
|
||||
EXPECT_FALSE(client->HasLocalEndpointResponded(endpoint.id));
|
||||
client->LocalEndpointAcceptedConnection(endpoint.id, payload_listener_);
|
||||
EXPECT_TRUE(client->HasLocalEndpointResponded(endpoint.id));
|
||||
EXPECT_TRUE(client->LocalConnectionIsAccepted(endpoint.id));
|
||||
}
|
||||
|
||||
void OnDiscoveryConnectionRemoteAccepted(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id));
|
||||
EXPECT_FALSE(client->HasRemoteEndpointResponded(endpoint.id));
|
||||
client->RemoteEndpointAcceptedConnection(endpoint.id);
|
||||
EXPECT_TRUE(client->HasRemoteEndpointResponded(endpoint.id));
|
||||
EXPECT_TRUE(client->RemoteConnectionIsAccepted(endpoint.id));
|
||||
}
|
||||
|
||||
void OnDiscoveryConnectionLocalRejected(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id));
|
||||
EXPECT_FALSE(client->HasLocalEndpointResponded(endpoint.id));
|
||||
client->LocalEndpointRejectedConnection(endpoint.id);
|
||||
EXPECT_TRUE(client->HasLocalEndpointResponded(endpoint.id));
|
||||
EXPECT_FALSE(client->LocalConnectionIsAccepted(endpoint.id));
|
||||
}
|
||||
|
||||
void OnDiscoveryConnectionRemoteRejected(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_TRUE(client->HasPendingConnectionToEndpoint(endpoint.id));
|
||||
EXPECT_FALSE(client->HasRemoteEndpointResponded(endpoint.id));
|
||||
client->RemoteEndpointRejectedConnection(endpoint.id);
|
||||
EXPECT_TRUE(client->HasRemoteEndpointResponded(endpoint.id));
|
||||
EXPECT_FALSE(client->RemoteConnectionIsAccepted(endpoint.id));
|
||||
}
|
||||
|
||||
void OnDiscoveryConnectionAccepted(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_connection_.accepted_cb, Call).Times(1);
|
||||
EXPECT_TRUE(client->IsConnectionAccepted(endpoint.id));
|
||||
client->OnConnectionAccepted(endpoint.id);
|
||||
}
|
||||
|
||||
void OnDiscoveryConnectionRejected(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_connection_.rejected_cb, Call).Times(1);
|
||||
EXPECT_TRUE(client->IsConnectionRejected(endpoint.id));
|
||||
client->OnConnectionRejected(endpoint.id, {Status::kConnectionRejected});
|
||||
}
|
||||
|
||||
void OnDiscoveryBandwidthChanged(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_connection_.bandwidth_changed_cb, Call).Times(1);
|
||||
client->OnBandwidthChanged(endpoint.id, Medium::WIFI_LAN);
|
||||
}
|
||||
|
||||
void OnDiscoveryConnectionDisconnected(ClientProxy* client,
|
||||
const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_connection_.disconnected_cb, Call).Times(1);
|
||||
client->OnDisconnected(endpoint.id, true);
|
||||
}
|
||||
|
||||
void OnPayload(ClientProxy* client, const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_payload_.payload_cb, Call).Times(1);
|
||||
client->OnPayload(endpoint.id, Payload(payload_bytes_));
|
||||
}
|
||||
|
||||
void OnPayloadProgress(ClientProxy* client, const Endpoint& endpoint) {
|
||||
EXPECT_CALL(mock_discovery_payload_.payload_progress_cb, Call).Times(1);
|
||||
client->OnPayloadProgress(endpoint.id, {});
|
||||
}
|
||||
|
||||
MockDiscoveryListener mock_discovery_;
|
||||
MockConnectionListener mock_discovery_connection_;
|
||||
MockPayloadListener mock_discovery_payload_;
|
||||
|
||||
proto::connections::Medium medium_{proto::connections::Medium::BLUETOOTH};
|
||||
std::vector<proto::connections::Medium> mediums_{
|
||||
proto::connections::Medium::BLUETOOTH,
|
||||
};
|
||||
Strategy strategy_{Strategy::kP2pPointToPoint};
|
||||
const std::string service_id_{"service"};
|
||||
ClientProxy client1_;
|
||||
ClientProxy client2_;
|
||||
std::string auth_token_ = "auth_token";
|
||||
ByteArray raw_auth_token_ = ByteArray(auth_token_);
|
||||
ByteArray payload_bytes_{"bytes"};
|
||||
ConnectionResponseInfo advertising_connection_info_{
|
||||
.authentication_token = auth_token_,
|
||||
.raw_authentication_token = raw_auth_token_,
|
||||
.is_incoming_connection = true,
|
||||
};
|
||||
ConnectionListener advertising_connection_listener_;
|
||||
ConnectionListener discovery_connection_listener_{
|
||||
.initiated_cb = mock_discovery_connection_.initiated_cb.AsStdFunction(),
|
||||
.accepted_cb = mock_discovery_connection_.accepted_cb.AsStdFunction(),
|
||||
.rejected_cb = mock_discovery_connection_.rejected_cb.AsStdFunction(),
|
||||
.disconnected_cb =
|
||||
mock_discovery_connection_.disconnected_cb.AsStdFunction(),
|
||||
.bandwidth_changed_cb =
|
||||
mock_discovery_connection_.bandwidth_changed_cb.AsStdFunction(),
|
||||
};
|
||||
DiscoveryListener discovery_listener_{
|
||||
.endpoint_found_cb = mock_discovery_.endpoint_found_cb.AsStdFunction(),
|
||||
.endpoint_lost_cb = mock_discovery_.endpoint_lost_cb.AsStdFunction(),
|
||||
};
|
||||
PayloadListener payload_listener_{
|
||||
.payload_cb = mock_discovery_payload_.payload_cb.AsStdFunction(),
|
||||
.payload_progress_cb =
|
||||
mock_discovery_payload_.payload_progress_cb.AsStdFunction(),
|
||||
};
|
||||
ConnectionOptions connection_options_;
|
||||
AdvertisingOptions advertising_options_;
|
||||
DiscoveryOptions discovery_options_;
|
||||
};
|
||||
|
||||
TEST_P(ClientProxyTest, CanCancelEndpoint) {
|
||||
FeatureFlags::Flags feature_flags = GetParam();
|
||||
MediumEnvironment::Instance().SetFeatureFlags(feature_flags);
|
||||
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
|
||||
EXPECT_FALSE(
|
||||
client2_.GetCancellationFlag(advertising_endpoint.id)->Cancelled());
|
||||
|
||||
client2_.CancelEndpoint(advertising_endpoint.id);
|
||||
|
||||
// If FeatureFlag is disabled, Cancelled is false as no-op.
|
||||
if (!feature_flags.enable_cancellation_flag) {
|
||||
EXPECT_FALSE(
|
||||
client2_.GetCancellationFlag(advertising_endpoint.id)->Cancelled());
|
||||
} else {
|
||||
// The Cancelled is always true as the default flag being returned.
|
||||
EXPECT_TRUE(
|
||||
client2_.GetCancellationFlag(advertising_endpoint.id)->Cancelled());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ClientProxyTest, CanCancelAllEndpoints) {
|
||||
FeatureFlags::Flags feature_flags = GetParam();
|
||||
MediumEnvironment::Instance().SetFeatureFlags(feature_flags);
|
||||
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
|
||||
EXPECT_FALSE(
|
||||
client2_.GetCancellationFlag(advertising_endpoint.id)->Cancelled());
|
||||
|
||||
client2_.CancelAllEndpoints();
|
||||
|
||||
// If FeatureFlag is disabled, Cancelled is false as no-op.
|
||||
if (!feature_flags.enable_cancellation_flag) {
|
||||
EXPECT_FALSE(
|
||||
client2_.GetCancellationFlag(advertising_endpoint.id)->Cancelled());
|
||||
} else {
|
||||
// The Cancelled is always true as the default flag being returned.
|
||||
EXPECT_TRUE(
|
||||
client2_.GetCancellationFlag(advertising_endpoint.id)->Cancelled());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(ClientProxyTest, CanCancelAllEndpointsWithDifferentEndpoint) {
|
||||
FeatureFlags::Flags feature_flags = GetParam();
|
||||
MediumEnvironment::Instance().SetFeatureFlags(feature_flags);
|
||||
|
||||
ConnectionListener advertising_connection_listener_2;
|
||||
ConnectionListener advertising_connection_listener_3;
|
||||
ClientProxy client3;
|
||||
|
||||
StartDiscovery(&client1_, discovery_listener_);
|
||||
Endpoint advertising_endpoint_2 =
|
||||
StartAdvertising(&client2_, advertising_connection_listener_2);
|
||||
Endpoint advertising_endpoint_3 =
|
||||
StartAdvertising(&client3, advertising_connection_listener_3);
|
||||
OnDiscoveryEndpointFound(&client1_, advertising_endpoint_2);
|
||||
OnDiscoveryConnectionInitiated(&client1_, advertising_endpoint_2);
|
||||
OnDiscoveryEndpointFound(&client1_, advertising_endpoint_3);
|
||||
OnDiscoveryConnectionInitiated(&client1_, advertising_endpoint_3);
|
||||
|
||||
// The CancellationFlag of endpoint_2 and endpoint_3 have been added. Default
|
||||
// Cancelled is false.
|
||||
EXPECT_FALSE(
|
||||
client1_.GetCancellationFlag(advertising_endpoint_2.id)->Cancelled());
|
||||
EXPECT_FALSE(
|
||||
client1_.GetCancellationFlag(advertising_endpoint_3.id)->Cancelled());
|
||||
|
||||
client1_.CancelAllEndpoints();
|
||||
|
||||
if (!feature_flags.enable_cancellation_flag) {
|
||||
// The CancellationFlag of endpoint_2 and endpoint_3 will not be removed
|
||||
// since it is not added. The default flag returned as Cancelled being true,
|
||||
// but Cancelled requested is false since the FeatureFlag is off.
|
||||
EXPECT_FALSE(
|
||||
client1_.GetCancellationFlag(advertising_endpoint_2.id)->Cancelled());
|
||||
EXPECT_FALSE(
|
||||
client1_.GetCancellationFlag(advertising_endpoint_3.id)->Cancelled());
|
||||
} else {
|
||||
// Expect the CancellationFlag of endpoint_2 and endpoint_3 has been
|
||||
// removed. The Cancelled is always true as the default flag being returned.
|
||||
EXPECT_TRUE(
|
||||
client1_.GetCancellationFlag(advertising_endpoint_2.id)->Cancelled());
|
||||
EXPECT_TRUE(
|
||||
client1_.GetCancellationFlag(advertising_endpoint_3.id)->Cancelled());
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ParametrisedClientProxyTest, ClientProxyTest,
|
||||
::testing::ValuesIn(kTestCases));
|
||||
|
||||
TEST_F(ClientProxyTest, ConstructorDestructorWorks) { SUCCEED(); }
|
||||
|
||||
TEST_F(ClientProxyTest, ClientIdIsUnique) {
|
||||
EXPECT_NE(client1_.GetClientId(), client2_.GetClientId());
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, GeneratedEndpointIdIsUnique) {
|
||||
EXPECT_NE(client1_.GetLocalEndpointId(), client2_.GetLocalEndpointId());
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, ResetClearsState) {
|
||||
client1_.Reset();
|
||||
EXPECT_FALSE(client1_.IsAdvertising());
|
||||
EXPECT_FALSE(client1_.IsDiscovering());
|
||||
EXPECT_TRUE(client1_.GetAdvertisingServiceId().empty());
|
||||
EXPECT_TRUE(client1_.GetDiscoveryServiceId().empty());
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, StartedAdvertisingChangesStateFromIdle) {
|
||||
client1_.StartedAdvertising(service_id_, strategy_, {}, {});
|
||||
|
||||
EXPECT_TRUE(client1_.IsAdvertising());
|
||||
EXPECT_FALSE(client1_.IsDiscovering());
|
||||
EXPECT_EQ(client1_.GetAdvertisingServiceId(), service_id_);
|
||||
EXPECT_TRUE(client1_.GetDiscoveryServiceId().empty());
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, StartedDiscoveryChangesStateFromIdle) {
|
||||
client1_.StartedDiscovery(service_id_, strategy_, {}, {});
|
||||
|
||||
EXPECT_FALSE(client1_.IsAdvertising());
|
||||
EXPECT_TRUE(client1_.IsDiscovering());
|
||||
EXPECT_TRUE(client1_.GetAdvertisingServiceId().empty());
|
||||
EXPECT_EQ(client1_.GetDiscoveryServiceId(), service_id_);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, OnEndpointFoundFiresNotificationInDiscovery) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, OnEndpointLostFiresNotificationInDiscovery) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryEndpointLost(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, OnConnectionInitiatedFiresNotificationInDiscovery) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, OnBandwidthChangedFiresNotificationInDiscovery) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint);
|
||||
OnDiscoveryBandwidthChanged(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, OnDisconnectedFiresNotificationInDiscovery) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionDisconnected(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, LocalEndpointAcceptedConnectionChangesState) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, LocalEndpointRejectedConnectionChangesState) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionLocalRejected(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, RemoteEndpointAcceptedConnectionChangesState) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, RemoteEndpointRejectedConnectionChangesState) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionRemoteRejected(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, OnPayloadChangesState) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint);
|
||||
OnPayload(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest, OnPayloadProgressChangesState) {
|
||||
Endpoint advertising_endpoint =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_);
|
||||
StartDiscovery(&client2_, discovery_listener_);
|
||||
OnDiscoveryEndpointFound(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionInitiated(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionLocalAccepted(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionRemoteAccepted(&client2_, advertising_endpoint);
|
||||
OnDiscoveryConnectionAccepted(&client2_, advertising_endpoint);
|
||||
OnPayloadProgress(&client2_, advertising_endpoint);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest,
|
||||
EndpointIdCacheWhenHighVizAdvertisementAgainImmediately) {
|
||||
BooleanMediumSelector booleanMediumSelector;
|
||||
booleanMediumSelector.bluetooth = true;
|
||||
|
||||
AdvertisingOptions advertising_options{
|
||||
{
|
||||
strategy_,
|
||||
booleanMediumSelector,
|
||||
},
|
||||
false, // auto_upgrade_bandwidth
|
||||
false, // enforce_topology_constraints
|
||||
false, // low_power
|
||||
};
|
||||
|
||||
Endpoint advertising_endpoint_1 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
StopAdvertising(&client1_);
|
||||
|
||||
// Advertise immediately.
|
||||
Endpoint advertising_endpoint_2 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
EXPECT_EQ(advertising_endpoint_1.id, advertising_endpoint_2.id);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest,
|
||||
EndpointIdRotateWhenHighVizAdvertisementAgainForAWhile) {
|
||||
BooleanMediumSelector booleanMediumSelector;
|
||||
booleanMediumSelector.bluetooth = true;
|
||||
|
||||
AdvertisingOptions advertising_options{
|
||||
{
|
||||
strategy_,
|
||||
booleanMediumSelector,
|
||||
},
|
||||
false, // auto_upgrade_bandwidth
|
||||
false, // enforce_topology_constraints
|
||||
false, // low_power
|
||||
};
|
||||
|
||||
Endpoint advertising_endpoint_1 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
StopAdvertising(&client1_);
|
||||
|
||||
// Wait to expire and then advertise.
|
||||
absl::SleepFor(ClientProxy::kHighPowerAdvertisementEndpointIdCacheTimeout +
|
||||
absl::Milliseconds(100));
|
||||
Endpoint advertising_endpoint_2 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
EXPECT_NE(advertising_endpoint_1.id, advertising_endpoint_2.id);
|
||||
}
|
||||
|
||||
TEST_F(ClientProxyTest,
|
||||
EndpointIdRotateWhenLowVizAdvertisementAfterHighVizAdvertisement) {
|
||||
BooleanMediumSelector booleanMediumSelector;
|
||||
booleanMediumSelector.bluetooth = true;
|
||||
|
||||
AdvertisingOptions high_viz_advertising_options{
|
||||
{
|
||||
strategy_,
|
||||
booleanMediumSelector,
|
||||
},
|
||||
false, // auto_upgrade_bandwidth
|
||||
false, // enforce_topology_constraints
|
||||
false, // low_power
|
||||
};
|
||||
Endpoint advertising_endpoint_1 =
|
||||
StartAdvertising(&client1_, advertising_connection_listener_,
|
||||
high_viz_advertising_options);
|
||||
|
||||
StopAdvertising(&client1_);
|
||||
|
||||
AdvertisingOptions low_viz_advertising_options{
|
||||
{
|
||||
strategy_,
|
||||
booleanMediumSelector,
|
||||
},
|
||||
false, // auto_upgrade_bandwidth
|
||||
false, // enforce_topology_constraints
|
||||
true, // low_power
|
||||
};
|
||||
|
||||
Endpoint advertising_endpoint_2 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, low_viz_advertising_options);
|
||||
|
||||
EXPECT_NE(advertising_endpoint_1.id, advertising_endpoint_2.id);
|
||||
}
|
||||
|
||||
// Tests endpoint_id rotates when discover.
|
||||
TEST_F(ClientProxyTest, EndpointIdRotateWhenStartDiscovery) {
|
||||
BooleanMediumSelector booleanMediumSelector;
|
||||
booleanMediumSelector.bluetooth = true;
|
||||
|
||||
AdvertisingOptions advertising_options{
|
||||
{
|
||||
strategy_,
|
||||
booleanMediumSelector,
|
||||
},
|
||||
false, // auto_upgrade_bandwidth
|
||||
false, // enforce_topology_constraints
|
||||
false, // low_power
|
||||
};
|
||||
|
||||
Endpoint advertising_endpoint_1 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
StopAdvertising(&client1_);
|
||||
StartDiscovery(&client1_, discovery_listener_);
|
||||
|
||||
Endpoint advertising_endpoint_2 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
EXPECT_NE(advertising_endpoint_1.id, advertising_endpoint_2.id);
|
||||
}
|
||||
|
||||
// Tests the low visibility mode with bluetooth disabled advertisment.
|
||||
TEST_F(ClientProxyTest,
|
||||
EndpointIdRotateWhenLowVizAdvertisementWithBluetoothDisabled) {
|
||||
BooleanMediumSelector booleanMediumSelector;
|
||||
booleanMediumSelector.bluetooth = false;
|
||||
|
||||
AdvertisingOptions advertising_options{
|
||||
{
|
||||
strategy_,
|
||||
booleanMediumSelector,
|
||||
},
|
||||
false, // auto_upgrade_bandwidth
|
||||
false, // enforce_topology_constraints
|
||||
false, // low_power
|
||||
};
|
||||
|
||||
Endpoint advertising_endpoint_1 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
StopAdvertising(&client1_);
|
||||
|
||||
Endpoint advertising_endpoint_2 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
EXPECT_NE(advertising_endpoint_1.id, advertising_endpoint_2.id);
|
||||
}
|
||||
|
||||
// Tests the low visibility mode with low power advertisment.
|
||||
TEST_F(ClientProxyTest, EndpointIdRotateWhenLowVizAdvertisementWithLowPower) {
|
||||
BooleanMediumSelector booleanMediumSelector;
|
||||
booleanMediumSelector.bluetooth = false;
|
||||
|
||||
AdvertisingOptions advertising_options{
|
||||
{
|
||||
strategy_,
|
||||
booleanMediumSelector,
|
||||
},
|
||||
false, // auto_upgrade_bandwidth
|
||||
false, // enforce_topology_constraints
|
||||
true, // low_power
|
||||
};
|
||||
Endpoint advertising_endpoint_1 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
StopAdvertising(&client1_);
|
||||
|
||||
Endpoint advertising_endpoint_2 = StartAdvertising(
|
||||
&client1_, advertising_connection_listener_, advertising_options);
|
||||
|
||||
EXPECT_NE(advertising_endpoint_1.id, advertising_endpoint_2.id);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,389 +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 "core/internal/encryption_runner.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "securegcm/ukey2_handshake.h"
|
||||
#include "absl/strings/ascii.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "platform/base/base64_utils.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/public/cancelable_alarm.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr absl::Duration kTimeout = absl::Seconds(15);
|
||||
constexpr std::int32_t kMaxUkey2VerificationStringLength = 32;
|
||||
constexpr std::int32_t kTokenLength = 5;
|
||||
constexpr securegcm::UKey2Handshake::HandshakeCipher kCipher =
|
||||
securegcm::UKey2Handshake::HandshakeCipher::P256_SHA512;
|
||||
|
||||
// Transforms a raw UKEY2 token (which is a random ByteArray that's
|
||||
// kMaxUkey2VerificationStringLength long) into a kTokenLength string that only
|
||||
// uses [A-Z], [0-9], '_', '-' for each character.
|
||||
std::string ToHumanReadableString(const ByteArray& token) {
|
||||
std::string result = Base64Utils::Encode(token).substr(0, kTokenLength);
|
||||
absl::AsciiStrToUpper(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HandleEncryptionSuccess(const std::string& endpoint_id,
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
|
||||
const EncryptionRunner::ResultListener& listener) {
|
||||
std::unique_ptr<std::string> verification_string =
|
||||
ukey2->GetVerificationString(kMaxUkey2VerificationStringLength);
|
||||
if (verification_string == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ByteArray raw_authentication_token(*verification_string);
|
||||
|
||||
listener.on_success_cb(endpoint_id, std::move(ukey2),
|
||||
ToHumanReadableString(raw_authentication_token),
|
||||
raw_authentication_token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CancelableAlarmRunnable(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
EndpointChannel* endpoint_channel) {
|
||||
NEARBY_LOGS(INFO) << "Timing out encryption for client "
|
||||
<< client->GetClientId()
|
||||
<< " to endpoint_id=" << endpoint_id << " after "
|
||||
<< absl::FormatDuration(kTimeout);
|
||||
endpoint_channel->Close();
|
||||
}
|
||||
|
||||
class ServerRunnable final {
|
||||
public:
|
||||
ServerRunnable(ClientProxy* client, ScheduledExecutor* alarm_executor,
|
||||
const std::string& endpoint_id, EndpointChannel* channel,
|
||||
EncryptionRunner::ResultListener&& listener)
|
||||
: client_(client),
|
||||
alarm_executor_(alarm_executor),
|
||||
endpoint_id_(endpoint_id),
|
||||
channel_(channel),
|
||||
listener_(std::move(listener)) {}
|
||||
|
||||
void operator()() const {
|
||||
CancelableAlarm timeout_alarm(
|
||||
"EncryptionRunner.StartServer() timeout",
|
||||
[this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); },
|
||||
kTimeout, alarm_executor_);
|
||||
|
||||
std::unique_ptr<securegcm::UKey2Handshake> server =
|
||||
securegcm::UKey2Handshake::ForResponder(kCipher);
|
||||
if (server == nullptr) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
// Message 1 (Client Init)
|
||||
ExceptionOr<ByteArray> client_init = channel_->Read();
|
||||
if (!client_init.ok()) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
securegcm::UKey2Handshake::ParseResult parse_result =
|
||||
server->ParseHandshakeMessage(std::string(client_init.result()));
|
||||
|
||||
// Java code throws a HandshakeException / AlertException.
|
||||
if (!parse_result.success) {
|
||||
LogException();
|
||||
if (parse_result.alert_to_send != nullptr) {
|
||||
HandleAlertException(parse_result);
|
||||
}
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "In StartServer(), read UKEY2 Message 1 from endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
|
||||
// Message 2 (Server Init)
|
||||
std::unique_ptr<std::string> server_init =
|
||||
server->GetNextHandshakeMessage();
|
||||
|
||||
// Java code throws a HandshakeException.
|
||||
if (server_init == nullptr) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
Exception write_exception =
|
||||
channel_->Write(ByteArray(std::move(*server_init)));
|
||||
if (!write_exception.Ok()) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "In StartServer(), wrote UKEY2 Message 2 to endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
|
||||
// Message 3 (Client Finish)
|
||||
ExceptionOr<ByteArray> client_finish = channel_->Read();
|
||||
|
||||
if (!client_finish.ok()) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
parse_result =
|
||||
server->ParseHandshakeMessage(std::string(client_finish.result()));
|
||||
|
||||
// Java code throws an AlertException or a HandshakeException.
|
||||
if (!parse_result.success) {
|
||||
LogException();
|
||||
if (parse_result.alert_to_send != nullptr) {
|
||||
HandleAlertException(parse_result);
|
||||
}
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "In StartServer(), read UKEY2 Message 3 from endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
|
||||
timeout_alarm.Cancel();
|
||||
|
||||
if (!HandleEncryptionSuccess(endpoint_id_, std::move(server), listener_)) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void LogException() const {
|
||||
NEARBY_LOGS(ERROR) << "In StartServer(), UKEY2 failed with endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
}
|
||||
|
||||
void HandleHandshakeOrIoException(CancelableAlarm* timeout_alarm) const {
|
||||
timeout_alarm->Cancel();
|
||||
listener_.on_failure_cb(endpoint_id_, channel_);
|
||||
}
|
||||
|
||||
void HandleAlertException(
|
||||
const securegcm::UKey2Handshake::ParseResult& parse_result) const {
|
||||
Exception write_exception =
|
||||
channel_->Write(ByteArray(*parse_result.alert_to_send));
|
||||
if (!write_exception.Ok()) {
|
||||
NEARBY_LOGS(WARNING)
|
||||
<< "In StartServer(), client " << client_->GetClientId()
|
||||
<< " failed to pass the alert error message to endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
}
|
||||
}
|
||||
|
||||
ClientProxy* client_;
|
||||
ScheduledExecutor* alarm_executor_;
|
||||
const std::string endpoint_id_;
|
||||
EndpointChannel* channel_;
|
||||
EncryptionRunner::ResultListener listener_;
|
||||
};
|
||||
|
||||
class ClientRunnable final {
|
||||
public:
|
||||
ClientRunnable(ClientProxy* client, ScheduledExecutor* alarm_executor,
|
||||
const std::string& endpoint_id, EndpointChannel* channel,
|
||||
EncryptionRunner::ResultListener&& listener)
|
||||
: client_(client),
|
||||
alarm_executor_(alarm_executor),
|
||||
endpoint_id_(endpoint_id),
|
||||
channel_(channel),
|
||||
listener_(std::move(listener)) {}
|
||||
|
||||
void operator()() const {
|
||||
CancelableAlarm timeout_alarm(
|
||||
"EncryptionRunner.StartClient() timeout",
|
||||
[this]() { CancelableAlarmRunnable(client_, endpoint_id_, channel_); },
|
||||
kTimeout, alarm_executor_);
|
||||
|
||||
std::unique_ptr<securegcm::UKey2Handshake> crypto =
|
||||
securegcm::UKey2Handshake::ForInitiator(kCipher);
|
||||
|
||||
// Java code throws a HandshakeException.
|
||||
if (crypto == nullptr) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
// Message 1 (Client Init)
|
||||
std::unique_ptr<std::string> client_init =
|
||||
crypto->GetNextHandshakeMessage();
|
||||
|
||||
// Java code throws a HandshakeException.
|
||||
if (client_init == nullptr) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
Exception write_init_exception = channel_->Write(ByteArray(*client_init));
|
||||
if (!write_init_exception.Ok()) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "In StartClient(), wrote UKEY2 Message 1 to endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
|
||||
// Message 2 (Server Init)
|
||||
ExceptionOr<ByteArray> server_init = channel_->Read();
|
||||
|
||||
if (!server_init.ok()) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
securegcm::UKey2Handshake::ParseResult parse_result =
|
||||
crypto->ParseHandshakeMessage(std::string(server_init.result()));
|
||||
|
||||
// Java code throws an AlertException or a HandshakeException.
|
||||
if (!parse_result.success) {
|
||||
LogException();
|
||||
if (parse_result.alert_to_send != nullptr) {
|
||||
HandleAlertException(parse_result);
|
||||
}
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "In StartClient(), read UKEY2 Message 2 from endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
|
||||
// Message 3 (Client Finish)
|
||||
std::unique_ptr<std::string> client_finish =
|
||||
crypto->GetNextHandshakeMessage();
|
||||
|
||||
// Java code throws a HandshakeException.
|
||||
if (client_finish == nullptr) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
Exception write_finish_exception =
|
||||
channel_->Write(ByteArray(*client_finish));
|
||||
if (!write_finish_exception.Ok()) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "In StartClient(), wrote UKEY2 Message 3 to endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
|
||||
timeout_alarm.Cancel();
|
||||
|
||||
if (!HandleEncryptionSuccess(endpoint_id_, std::move(crypto), listener_)) {
|
||||
LogException();
|
||||
HandleHandshakeOrIoException(&timeout_alarm);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void LogException() const {
|
||||
NEARBY_LOGS(ERROR) << "In StartClient(), UKEY2 failed with endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
}
|
||||
|
||||
void HandleHandshakeOrIoException(CancelableAlarm* timeout_alarm) const {
|
||||
timeout_alarm->Cancel();
|
||||
listener_.on_failure_cb(endpoint_id_, channel_);
|
||||
}
|
||||
|
||||
void HandleAlertException(
|
||||
const securegcm::UKey2Handshake::ParseResult& parse_result) const {
|
||||
Exception write_exception =
|
||||
channel_->Write(ByteArray(*parse_result.alert_to_send));
|
||||
if (!write_exception.Ok()) {
|
||||
NEARBY_LOGS(WARNING)
|
||||
<< "In StartClient(), client " << client_->GetClientId()
|
||||
<< " failed to pass the alert error message to endpoint(id="
|
||||
<< endpoint_id_ << ").";
|
||||
}
|
||||
}
|
||||
|
||||
ClientProxy* client_;
|
||||
ScheduledExecutor* alarm_executor_;
|
||||
const std::string endpoint_id_;
|
||||
EndpointChannel* channel_;
|
||||
EncryptionRunner::ResultListener listener_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
EncryptionRunner::~EncryptionRunner() {
|
||||
// Stop all the ongoing Runnables (as gracefully as possible).
|
||||
client_executor_.Shutdown();
|
||||
server_executor_.Shutdown();
|
||||
alarm_executor_.Shutdown();
|
||||
}
|
||||
|
||||
void EncryptionRunner::StartServer(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
EndpointChannel* endpoint_channel,
|
||||
EncryptionRunner::ResultListener&& listener) {
|
||||
server_executor_.Execute(
|
||||
"encryption-server",
|
||||
[runnable{ServerRunnable(client, &alarm_executor_, endpoint_id,
|
||||
endpoint_channel, std::move(listener))}]() {
|
||||
runnable();
|
||||
});
|
||||
}
|
||||
|
||||
void EncryptionRunner::StartClient(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
EndpointChannel* endpoint_channel,
|
||||
EncryptionRunner::ResultListener&& listener) {
|
||||
client_executor_.Execute(
|
||||
"encryption-client",
|
||||
[runnable{ClientRunnable(client, &alarm_executor_, endpoint_id,
|
||||
endpoint_channel, std::move(listener))}]() {
|
||||
runnable();
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,86 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_ENCRYPTION_RUNNER_H_
|
||||
#define CORE_INTERNAL_ENCRYPTION_RUNNER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "securegcm/ukey2_handshake.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_channel.h"
|
||||
#include "core/listeners.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/scheduled_executor.h"
|
||||
#include "platform/public/single_thread_executor.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Encrypts a connection over UKEY2.
|
||||
//
|
||||
// NOTE: Stalled EndpointChannels will be disconnected after kTimeout.
|
||||
// This is to prevent unverified endpoints from maintaining an
|
||||
// indefinite connection to us.
|
||||
class EncryptionRunner {
|
||||
public:
|
||||
EncryptionRunner() = default;
|
||||
~EncryptionRunner();
|
||||
|
||||
struct ResultListener {
|
||||
// @EncryptionRunnerThread
|
||||
std::function<void(const std::string& endpoint_id,
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
|
||||
const std::string& auth_token,
|
||||
const ByteArray& raw_auth_token)>
|
||||
on_success_cb =
|
||||
DefaultCallback<const std::string&,
|
||||
std::unique_ptr<securegcm::UKey2Handshake>,
|
||||
const std::string&, const ByteArray&>();
|
||||
|
||||
// Encryption has failed. The remote_endpoint_id and channel are given so
|
||||
// that any pending state can be cleaned up.
|
||||
//
|
||||
// We return the EndpointChannel because, at this stage, simultaneous
|
||||
// connections are a possibility. Use this channel to verify that the state
|
||||
// you're cleaning up is for this EndpointChannel, and not state for another
|
||||
// channel to the same endpoint.
|
||||
//
|
||||
// @EncryptionRunnerThread
|
||||
std::function<void(const std::string& endpoint_id,
|
||||
EndpointChannel* channel)>
|
||||
on_failure_cb = DefaultCallback<const std::string&, EndpointChannel*>();
|
||||
};
|
||||
|
||||
// @AnyThread
|
||||
void StartServer(ClientProxy* client, const std::string& endpoint_id,
|
||||
EndpointChannel* endpoint_channel,
|
||||
ResultListener&& result_listener);
|
||||
// @AnyThread
|
||||
void StartClient(ClientProxy* client, const std::string& endpoint_id,
|
||||
EndpointChannel* endpoint_channel,
|
||||
ResultListener&& result_listener);
|
||||
|
||||
private:
|
||||
ScheduledExecutor alarm_executor_;
|
||||
SingleThreadExecutor server_executor_;
|
||||
SingleThreadExecutor client_executor_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_ENCRYPTION_RUNNER_H_
|
||||
@@ -1,160 +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 "core/internal/encryption_runner.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_channel.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/pipe.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
using ::location::nearby::proto::connections::Medium;
|
||||
|
||||
class FakeEndpointChannel : public EndpointChannel {
|
||||
public:
|
||||
FakeEndpointChannel(InputStream* in, OutputStream* out)
|
||||
: in_(in), out_(out) {}
|
||||
ExceptionOr<ByteArray> Read() override {
|
||||
read_timestamp_ = SystemClock::ElapsedRealtime();
|
||||
return in_ ? in_->Read(Pipe::kChunkSize)
|
||||
: ExceptionOr<ByteArray>{Exception::kIo};
|
||||
}
|
||||
Exception Write(const ByteArray& data) override {
|
||||
write_timestamp_ = SystemClock::ElapsedRealtime();
|
||||
return out_ ? out_->Write(data) : Exception{Exception::kIo};
|
||||
}
|
||||
void Close() override {
|
||||
if (in_) in_->Close();
|
||||
if (out_) out_->Close();
|
||||
}
|
||||
void Close(proto::connections::DisconnectionReason reason) override {
|
||||
Close();
|
||||
}
|
||||
proto::connections::ConnectionTechnology GetTechnology() const override {
|
||||
return proto::connections::ConnectionTechnology::
|
||||
CONNECTION_TECHNOLOGY_BLE_GATT;
|
||||
}
|
||||
proto::connections::ConnectionBand GetBand() const override {
|
||||
return proto::connections::ConnectionBand::CONNECTION_BAND_CELLULAR_BAND_2G;
|
||||
}
|
||||
int GetFrequency() const override { return 0; }
|
||||
int GetTryCount() const override { return 0; }
|
||||
std::string GetType() const override { return "fake-channel-type"; }
|
||||
std::string GetName() const override { return "fake-channel"; }
|
||||
Medium GetMedium() const override { return Medium::BLE; }
|
||||
int GetMaxTransmitPacketSize() const override { return 512; }
|
||||
void EnableEncryption(std::shared_ptr<EncryptionContext> context) override {}
|
||||
void DisableEncryption() override {}
|
||||
bool IsPaused() const override { return false; }
|
||||
void Pause() override {}
|
||||
void Resume() override {}
|
||||
absl::Time GetLastReadTimestamp() const override { return read_timestamp_; }
|
||||
absl::Time GetLastWriteTimestamp() const override { return write_timestamp_; }
|
||||
void SetAnalyticsRecorder(analytics::AnalyticsRecorder* analytics_recorder,
|
||||
const std::string& endpoint_id) override {}
|
||||
|
||||
private:
|
||||
InputStream* in_ = nullptr;
|
||||
OutputStream* out_ = nullptr;
|
||||
absl::Time read_timestamp_ = absl::InfinitePast();
|
||||
absl::Time write_timestamp_ = absl::InfinitePast();
|
||||
};
|
||||
|
||||
struct User {
|
||||
User(Pipe* reader, Pipe* writer)
|
||||
: channel(&reader->GetInputStream(), &writer->GetOutputStream()) {}
|
||||
|
||||
FakeEndpointChannel channel;
|
||||
EncryptionRunner crypto;
|
||||
ClientProxy client;
|
||||
};
|
||||
|
||||
struct Response {
|
||||
enum class Status {
|
||||
kUnknown = 0,
|
||||
kDone = 1,
|
||||
kFailed = 2,
|
||||
};
|
||||
|
||||
CountDownLatch latch{2};
|
||||
Status server_status = Status::kUnknown;
|
||||
Status client_status = Status::kUnknown;
|
||||
};
|
||||
|
||||
TEST(EncryptionRunnerTest, ConstructorDestructorWorks) { EncryptionRunner enc; }
|
||||
|
||||
TEST(EncryptionRunnerTest, ReadWrite) {
|
||||
Pipe from_a_to_b;
|
||||
Pipe from_b_to_a;
|
||||
User user_a(/*reader=*/&from_b_to_a, /*writer=*/&from_a_to_b);
|
||||
User user_b(/*reader=*/&from_a_to_b, /*writer=*/&from_b_to_a);
|
||||
Response response;
|
||||
|
||||
user_a.crypto.StartServer(
|
||||
&user_a.client, "endpoint_id", &user_a.channel,
|
||||
{
|
||||
.on_success_cb =
|
||||
[&response](const std::string& endpoint_id,
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
|
||||
const std::string& auth_token,
|
||||
const ByteArray& raw_auth_token) {
|
||||
response.server_status = Response::Status::kDone;
|
||||
response.latch.CountDown();
|
||||
},
|
||||
.on_failure_cb =
|
||||
[&response](const std::string& endpoint_id,
|
||||
EndpointChannel* channel) {
|
||||
response.server_status = Response::Status::kFailed;
|
||||
response.latch.CountDown();
|
||||
},
|
||||
});
|
||||
user_b.crypto.StartClient(
|
||||
&user_b.client, "endpoint_id", &user_b.channel,
|
||||
{
|
||||
.on_success_cb =
|
||||
[&response](const std::string& endpoint_id,
|
||||
std::unique_ptr<securegcm::UKey2Handshake> ukey2,
|
||||
const std::string& auth_token,
|
||||
const ByteArray& raw_auth_token) {
|
||||
response.client_status = Response::Status::kDone;
|
||||
response.latch.CountDown();
|
||||
},
|
||||
.on_failure_cb =
|
||||
[&response](const std::string& endpoint_id,
|
||||
EndpointChannel* channel) {
|
||||
response.client_status = Response::Status::kFailed;
|
||||
response.latch.CountDown();
|
||||
},
|
||||
});
|
||||
EXPECT_TRUE(response.latch.Await(absl::Milliseconds(5000)).result());
|
||||
EXPECT_EQ(response.server_status, Response::Status::kDone);
|
||||
EXPECT_EQ(response.client_status, Response::Status::kDone);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,119 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_ENDPOINT_CHANNEL_H_
|
||||
#define CORE_INTERNAL_ENDPOINT_CHANNEL_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "securegcm/d2d_connection_context_v1.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "internal/analytics/analytics_recorder.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class EndpointChannel {
|
||||
public:
|
||||
virtual ~EndpointChannel() = default;
|
||||
|
||||
using EncryptionContext = ::securegcm::D2DConnectionContextV1;
|
||||
|
||||
virtual ExceptionOr<ByteArray>
|
||||
Read() = 0; // throws Exception::IO, Exception::INTERRUPTED
|
||||
|
||||
virtual Exception Write(const ByteArray& data) = 0; // throws Exception::IO
|
||||
|
||||
// Closes this EndpointChannel, without tracking the closure in analytics.
|
||||
virtual void Close() = 0;
|
||||
|
||||
// Closes this EndpointChannel and records the closure with the given reason.
|
||||
virtual void Close(proto::connections::DisconnectionReason reason) = 0;
|
||||
|
||||
// Returns a one-word type descriptor for the concrete EndpointChannel
|
||||
// implementation that can be used in log messages; eg: BLUETOOTH, BLE, WIFI.
|
||||
virtual std::string GetType() const = 0;
|
||||
|
||||
// Returns the name of the EndpointChannel.
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
// Returns the analytics enum representing the medium of this EndpointChannel.
|
||||
virtual proto::connections::Medium GetMedium() const = 0;
|
||||
|
||||
// Returns the used BLE or WiFi technology of this EndpointChannel.
|
||||
virtual proto::connections::ConnectionTechnology GetTechnology() const = 0;
|
||||
|
||||
// Returns the used wifi band of this EndpointChannel.
|
||||
virtual proto::connections::ConnectionBand GetBand() const = 0;
|
||||
|
||||
// Returns the used wifi frequency of this EndpointChannel.
|
||||
virtual int GetFrequency() const = 0;
|
||||
|
||||
// Returns the try counts of this EndpointChannel.
|
||||
virtual int GetTryCount() const = 0;
|
||||
|
||||
// Returns the maximum supported transmit packet size(MTU) for the underlying
|
||||
// transport.
|
||||
virtual int GetMaxTransmitPacketSize() const = 0;
|
||||
|
||||
// Enables encryption on the EndpointChannel.
|
||||
virtual void EnableEncryption(std::shared_ptr<EncryptionContext> context) = 0;
|
||||
|
||||
// Disables encryption on the EndpointChannel.
|
||||
virtual void DisableEncryption() = 0;
|
||||
|
||||
// True if the EndpointChannel is currently pausing all writes.
|
||||
virtual bool IsPaused() const = 0;
|
||||
|
||||
// Pauses all writes on this EndpointChannel until resume() is called.
|
||||
virtual void Pause() = 0;
|
||||
|
||||
// Resumes any writes on this EndpointChannel that were suspended when pause()
|
||||
// was called.
|
||||
virtual void Resume() = 0;
|
||||
|
||||
// Returns the timestamp of the last read from this endpoint, or -1 if no
|
||||
// reads have occurred.
|
||||
virtual absl::Time GetLastReadTimestamp() const = 0;
|
||||
|
||||
// Returns the timestamp of the last write to this endpoint, or -1 if no
|
||||
// writes have occurred.
|
||||
virtual absl::Time GetLastWriteTimestamp() const = 0;
|
||||
|
||||
// Sets the AnalyticsRecorder instance for analytics.
|
||||
virtual void SetAnalyticsRecorder(
|
||||
analytics::AnalyticsRecorder* analytics_recorder,
|
||||
const std::string& endpoint_id) = 0;
|
||||
};
|
||||
|
||||
inline bool operator==(const EndpointChannel& lhs, const EndpointChannel& rhs) {
|
||||
return (lhs.GetType() == rhs.GetType()) && (lhs.GetName() == rhs.GetName()) &&
|
||||
(lhs.GetMedium() == rhs.GetMedium());
|
||||
}
|
||||
|
||||
inline bool operator!=(const EndpointChannel& lhs, const EndpointChannel& rhs) {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_ENDPOINT_CHANNEL_H_
|
||||
@@ -1,187 +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 "core/internal/endpoint_channel_manager.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/time/time.h"
|
||||
#include "connections/implementation/proto/offline_wire_formats.pb.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "platform/base/feature_flags.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
const absl::Duration kDataTransferDelay = absl::Milliseconds(500);
|
||||
}
|
||||
|
||||
EndpointChannelManager::~EndpointChannelManager() {
|
||||
NEARBY_LOG(INFO, "Initiating shutdown of EndpointChannelManager.");
|
||||
MutexLock lock(&mutex_);
|
||||
channel_state_.DestroyAll();
|
||||
NEARBY_LOG(INFO, "EndpointChannelManager has shut down.");
|
||||
}
|
||||
|
||||
void EndpointChannelManager::RegisterChannelForEndpoint(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> channel) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
NEARBY_LOGS(INFO) << "EndpointChannelManager registered channel of type "
|
||||
<< channel->GetType() << " to endpoint " << endpoint_id;
|
||||
SetActiveEndpointChannel(client, endpoint_id, std::move(channel));
|
||||
|
||||
NEARBY_LOG(INFO, "Registered channel: id=%s", endpoint_id.c_str());
|
||||
}
|
||||
|
||||
void EndpointChannelManager::ReplaceChannelForEndpoint(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> channel) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
auto* endpoint = channel_state_.LookupEndpointData(endpoint_id);
|
||||
if (endpoint != nullptr && endpoint->channel == nullptr) {
|
||||
NEARBY_LOGS(INFO) << "EndpointChannelManager is missing channel while "
|
||||
"trying to update: endpoint "
|
||||
<< endpoint_id;
|
||||
}
|
||||
|
||||
SetActiveEndpointChannel(client, endpoint_id, std::move(channel));
|
||||
}
|
||||
|
||||
bool EndpointChannelManager::EncryptChannelForEndpoint(
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<EncryptionContext> context) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
channel_state_.UpdateEncryptionContextForEndpoint(endpoint_id,
|
||||
std::move(context));
|
||||
auto* endpoint = channel_state_.LookupEndpointData(endpoint_id);
|
||||
return channel_state_.EncryptChannel(endpoint);
|
||||
}
|
||||
|
||||
std::shared_ptr<EndpointChannel> EndpointChannelManager::GetChannelForEndpoint(
|
||||
const std::string& endpoint_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
auto* endpoint = channel_state_.LookupEndpointData(endpoint_id);
|
||||
if (endpoint == nullptr) {
|
||||
NEARBY_LOGS(INFO) << "No channel info for endpoint " << endpoint_id;
|
||||
return {};
|
||||
}
|
||||
|
||||
return endpoint->channel;
|
||||
}
|
||||
|
||||
void EndpointChannelManager::SetActiveEndpointChannel(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> channel) {
|
||||
// Update the channel first, then encrypt this new channel, if
|
||||
// crypto context is present.
|
||||
channel->SetAnalyticsRecorder(&client->GetAnalyticsRecorder(), endpoint_id);
|
||||
channel_state_.UpdateChannelForEndpoint(endpoint_id, std::move(channel));
|
||||
|
||||
auto* endpoint = channel_state_.LookupEndpointData(endpoint_id);
|
||||
if (endpoint->IsEncrypted()) channel_state_.EncryptChannel(endpoint);
|
||||
}
|
||||
|
||||
int EndpointChannelManager::GetConnectedEndpointsCount() const {
|
||||
MutexLock lock(&mutex_);
|
||||
return channel_state_.GetConnectedEndpointsCount();
|
||||
}
|
||||
|
||||
///////////////////////////////// ChannelState /////////////////////////////////
|
||||
|
||||
// endpoint - channel endpoint to encrypt
|
||||
bool EndpointChannelManager::ChannelState::EncryptChannel(
|
||||
EndpointChannelManager::ChannelState::EndpointData* endpoint) {
|
||||
if (endpoint != nullptr && endpoint->channel != nullptr &&
|
||||
endpoint->context != nullptr) {
|
||||
endpoint->channel->EnableEncryption(endpoint->context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
EndpointChannelManager::ChannelState::EndpointData*
|
||||
EndpointChannelManager::ChannelState::LookupEndpointData(
|
||||
const std::string& endpoint_id) {
|
||||
auto item = endpoints_.find(endpoint_id);
|
||||
return item != endpoints_.end() ? &item->second : nullptr;
|
||||
}
|
||||
|
||||
void EndpointChannelManager::ChannelState::UpdateChannelForEndpoint(
|
||||
const std::string& endpoint_id, std::unique_ptr<EndpointChannel> channel) {
|
||||
// Create EndpointData instance, if necessary, and populate channel.
|
||||
endpoints_[endpoint_id].channel = std::move(channel);
|
||||
}
|
||||
|
||||
void EndpointChannelManager::ChannelState::UpdateEncryptionContextForEndpoint(
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<EncryptionContext> context) {
|
||||
// Create EndpointData instance, if necessary, and populate crypto context.
|
||||
endpoints_[endpoint_id].context = std::move(context);
|
||||
}
|
||||
|
||||
bool EndpointChannelManager::ChannelState::RemoveEndpoint(
|
||||
const std::string& endpoint_id,
|
||||
proto::connections::DisconnectionReason reason) {
|
||||
auto item = endpoints_.find(endpoint_id);
|
||||
if (item == endpoints_.end()) return false;
|
||||
item->second.disconnect_reason = reason;
|
||||
auto channel = item->second.channel;
|
||||
if (channel) {
|
||||
// If the channel was paused (i.e. during a bandwidth upgrade negotiation)
|
||||
// we resume to ensure the thread won't hang when trying to write to it.
|
||||
channel->Resume();
|
||||
|
||||
channel->Write(parser::ForDisconnection());
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "EndpointChannelManager reported the disconnection to endpoint "
|
||||
<< endpoint_id;
|
||||
SystemClock::Sleep(kDataTransferDelay);
|
||||
}
|
||||
endpoints_.erase(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EndpointChannelManager::UnregisterChannelForEndpoint(
|
||||
const std::string& endpoint_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!channel_state_.RemoveEndpoint(
|
||||
endpoint_id,
|
||||
proto::connections::DisconnectionReason::LOCAL_DISCONNECTION)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "EndpointChannelManager unregistered channel for endpoint "
|
||||
<< endpoint_id;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,172 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_
|
||||
#define CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "securegcm/d2d_connection_context_v1.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_channel.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// NOTE(std::string):
|
||||
// All the strings in internal class public interfaces should be exchanged as
|
||||
// const std::string& if they are immutable, and as std::string if they are
|
||||
// mutable.
|
||||
// This is to keep all the internal classes compatible with each other,
|
||||
// and minimize resources spent on the type conversion.
|
||||
// Project-wide, strings are either passed around as reference (which has
|
||||
// zero maintenance costs, and sizeof(void*) memory usage => passed around in a
|
||||
// CPU register), and whenever lifetime etension is required, it must be copied
|
||||
// to std::string instance (which will again propagate as a const reference
|
||||
// within it's lifetime domain).
|
||||
|
||||
// Manages the communication channels to all the remote endpoints with which we
|
||||
// are interacting.
|
||||
class EndpointChannelManager final {
|
||||
public:
|
||||
using EncryptionContext = EndpointChannel::EncryptionContext;
|
||||
|
||||
~EndpointChannelManager();
|
||||
|
||||
// Registers the initial EndpointChannel to be associated with an endpoint;
|
||||
// if there already exists a previously-associated EndpointChannel, that will
|
||||
// be closed before continuing the registration.
|
||||
void RegisterChannelForEndpoint(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> channel)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Replaces the EndpointChannel to be associated with an endpoint from here on
|
||||
// in, transferring the encryption context from the previous EndpointChannel
|
||||
// to the newly-provided EndpointChannel.
|
||||
void ReplaceChannelForEndpoint(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> channel)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool EncryptChannelForEndpoint(const std::string& endpoint_id,
|
||||
std::unique_ptr<EncryptionContext> context)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// NOTE(shared_ptr<> usage):
|
||||
//
|
||||
// EndpointChannelManager is holding an EndpointChannel instance;
|
||||
// GetChannelForEndpoint() is passing ownership over to a worker thread.
|
||||
// It is not a pointer passing but an ownership passing, to guarantee that
|
||||
// channel instance will not disappear underneath the feet of a worker thread
|
||||
// inside EndpointManager [ EndpointManager::EndpointChannelLoopRunnable() ].
|
||||
// If it is just a pointer, Channel will get destroyed while in use by a
|
||||
// worker thread. shared_ptr is a simple and reliable tool to avoid that.
|
||||
//
|
||||
// The reason why it can not be std::unique_ptr<> is: there are other code
|
||||
// paths that expect to be able to read the pointer value multiple times, from
|
||||
// multiple places (each of them needs "ownership" for the duration of their
|
||||
// use). EndpointManager::SendTransferFrameBytes() is another such place.
|
||||
// If EndpointChannelManager replaces the current channel, and any (or both)
|
||||
// EndpointManager methods that use a channel are running, it is better to
|
||||
// have a shared ownership.
|
||||
std::shared_ptr<EndpointChannel> GetChannelForEndpoint(
|
||||
const std::string& endpoint_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if 'endpoint_id' actually had a registered EndpointChannel.
|
||||
// IOW, a return of false signifies a no-op.
|
||||
bool UnregisterChannelForEndpoint(const std::string& endpoint_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
int GetConnectedEndpointsCount() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
// Tracks channel state for all endpoints. This includes what EndpointChannel
|
||||
// the endpoint is currently using and whether or not the EndpointChannel has
|
||||
// been encrypted yet.
|
||||
class ChannelState {
|
||||
public:
|
||||
struct EndpointData {
|
||||
EndpointData() = default;
|
||||
EndpointData(EndpointData&&) = default;
|
||||
EndpointData& operator=(EndpointData&&) = default;
|
||||
~EndpointData() {
|
||||
if (channel != nullptr) {
|
||||
channel->Close(disconnect_reason);
|
||||
}
|
||||
}
|
||||
|
||||
// True if we have a 'context' for the endpoint.
|
||||
bool IsEncrypted() const { return context != nullptr; }
|
||||
|
||||
std::shared_ptr<EndpointChannel> channel;
|
||||
std::shared_ptr<EncryptionContext> context;
|
||||
proto::connections::DisconnectionReason disconnect_reason =
|
||||
proto::connections::DisconnectionReason::UNKNOWN_DISCONNECTION_REASON;
|
||||
};
|
||||
|
||||
ChannelState() = default;
|
||||
~ChannelState() { DestroyAll(); }
|
||||
ChannelState(ChannelState&&) = default;
|
||||
ChannelState& operator=(ChannelState&&) = default;
|
||||
|
||||
// Provides a way to destroy contents of a container, while holding a lock.
|
||||
void DestroyAll() { endpoints_.clear(); }
|
||||
// Return pointer to endpoint data, or nullptr, it not found.
|
||||
EndpointData* LookupEndpointData(const std::string& endpoint_id);
|
||||
|
||||
// Stores a new EndpointChannel for the endpoint.
|
||||
// Prevoius one is destroyed, if it existed.
|
||||
void UpdateChannelForEndpoint(const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> channel);
|
||||
|
||||
// Stores a new EncryptionContext for the endpoint.
|
||||
// Prevoius one is destroyed, if it existed.
|
||||
void UpdateEncryptionContextForEndpoint(
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<EncryptionContext> context);
|
||||
|
||||
// Removes all knowledge of this endpoint, cleaning up as necessary.
|
||||
// Returns false if the endpoint was not found.
|
||||
bool RemoveEndpoint(const std::string& endpoint_id,
|
||||
proto::connections::DisconnectionReason reason);
|
||||
|
||||
bool EncryptChannel(EndpointData* endpoint);
|
||||
int GetConnectedEndpointsCount() const { return endpoints_.size(); }
|
||||
|
||||
private:
|
||||
// Endpoint ID -> EndpointData. Contains everything we know about the
|
||||
// endpoint.
|
||||
absl::flat_hash_map<std::string, EndpointData> endpoints_;
|
||||
};
|
||||
|
||||
void SetActiveEndpointChannel(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
std::unique_ptr<EndpointChannel> channel)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable Mutex mutex_;
|
||||
ChannelState channel_state_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_ENDPOINT_CHANNEL_MANAGER_H_
|
||||
@@ -1,32 +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 "core/internal/endpoint_channel_manager.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
TEST(EndpointChannelManagerTest, ConstructorDestructorWorks) {
|
||||
EndpointChannelManager mgr;
|
||||
SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,674 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/endpoint_manager.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "connections/implementation/proto/offline_wire_formats.pb.h"
|
||||
#include "core/internal/endpoint_channel.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
using ::location::nearby::proto::connections::Medium;
|
||||
|
||||
constexpr absl::Duration EndpointManager::kProcessEndpointDisconnectionTimeout;
|
||||
constexpr absl::Time EndpointManager::kInvalidTimestamp;
|
||||
|
||||
class EndpointManager::LockedFrameProcessor {
|
||||
public:
|
||||
explicit LockedFrameProcessor(FrameProcessorWithMutex* fp)
|
||||
: lock_{std::make_unique<MutexLock>(&fp->mutex_)},
|
||||
frame_processor_with_mutex_{fp} {}
|
||||
|
||||
// Constructor of a no-op object.
|
||||
LockedFrameProcessor() {}
|
||||
|
||||
explicit operator bool() const { return get() != nullptr; }
|
||||
|
||||
FrameProcessor* operator->() const { return get(); }
|
||||
|
||||
void set(FrameProcessor* frame_processor) {
|
||||
if (frame_processor_with_mutex_)
|
||||
frame_processor_with_mutex_->frame_processor_ = frame_processor;
|
||||
}
|
||||
|
||||
FrameProcessor* get() const {
|
||||
return frame_processor_with_mutex_
|
||||
? frame_processor_with_mutex_->frame_processor_
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
if (frame_processor_with_mutex_)
|
||||
frame_processor_with_mutex_->frame_processor_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<MutexLock> lock_;
|
||||
FrameProcessorWithMutex* frame_processor_with_mutex_ = nullptr;
|
||||
};
|
||||
|
||||
// A Runnable that continuously grabs the most recent EndpointChannel available
|
||||
// for an endpoint.
|
||||
//
|
||||
// handler - Called whenever an EndpointChannel is available for endpointId.
|
||||
// Implementations are expected to read/write freely to the
|
||||
// EndpointChannel until an Exception::IO is thrown. Once an
|
||||
// Exception::IO occurs, a check will be performed to see if another
|
||||
// EndpointChannel is available for the given endpoint and, if so,
|
||||
// handler(EndpointChannel) will be called again.
|
||||
void EndpointManager::EndpointChannelLoopRunnable(
|
||||
const std::string& runnable_name, ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
std::function<ExceptionOr<bool>(EndpointChannel*)> handler) {
|
||||
// EndpointChannelManager will not let multiple channels exist simultaneously
|
||||
// for the same endpoint_id; it will be closing "old" channels as new ones
|
||||
// come.
|
||||
// Closed channel will return Exception::kIo for any Read, and loop (below)
|
||||
// will retry and attempt to pick another channel.
|
||||
// If channel is deleted (no mapping), or it is still the same channel
|
||||
// (same Medium) on which we got the Exception::kIo, we terminate the loop.
|
||||
NEARBY_LOG(INFO, "Started worker loop name=%s, endpoint=%s",
|
||||
runnable_name.c_str(), endpoint_id.c_str());
|
||||
Medium last_failed_medium = Medium::UNKNOWN_MEDIUM;
|
||||
while (true) {
|
||||
// It's important to keep re-fetching the EndpointChannel for an endpoint
|
||||
// because it can be changed out from under us (for example, when we
|
||||
// upgrade from Bluetooth to Wifi).
|
||||
std::shared_ptr<EndpointChannel> channel =
|
||||
channel_manager_->GetChannelForEndpoint(endpoint_id);
|
||||
if (channel == nullptr) {
|
||||
NEARBY_LOG(INFO, "Endpoint channel is nullptr, bail out.");
|
||||
break;
|
||||
}
|
||||
|
||||
// If we're looping back around after a failure, and there's not a new
|
||||
// EndpointChannel for this endpoint, there's nothing more to do here.
|
||||
if ((last_failed_medium != Medium::UNKNOWN_MEDIUM) &&
|
||||
(channel->GetMedium() == last_failed_medium)) {
|
||||
NEARBY_LOG(
|
||||
INFO, "No new endpoint channel is found after a failure, exit loop.");
|
||||
break;
|
||||
}
|
||||
|
||||
ExceptionOr<bool> keep_using_channel = handler(channel.get());
|
||||
|
||||
if (!keep_using_channel.ok()) {
|
||||
Exception exception = keep_using_channel.GetException();
|
||||
// An "invalid proto" may be a final payload on a channel we're about to
|
||||
// close, so we'll loop back around once. We set |last_failed_medium| to
|
||||
// ensure we don't loop indefinitely. See crbug.com/1182031 for more
|
||||
// detail.
|
||||
if (exception.Raised(Exception::kInvalidProtocolBuffer)) {
|
||||
last_failed_medium = channel->GetMedium();
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Received invalid protobuf message, re-fetching endpoint "
|
||||
"channel; last_failed_medium="
|
||||
<< proto::connections::Medium_Name(last_failed_medium);
|
||||
continue;
|
||||
}
|
||||
if (exception.Raised(Exception::kIo)) {
|
||||
last_failed_medium = channel->GetMedium();
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Endpoint channel IO exception; last_failed_medium="
|
||||
<< proto::connections::Medium_Name(last_failed_medium);
|
||||
continue;
|
||||
}
|
||||
if (exception.Raised(Exception::kInterrupted)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keep_using_channel.result()) {
|
||||
NEARBY_LOGS(INFO) << "Dropping current channel: last medium="
|
||||
<< proto::connections::Medium_Name(last_failed_medium);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Indicate we're out of the loop and it is ok to schedule another instance
|
||||
// if needed.
|
||||
NEARBY_LOGS(INFO) << "Worker going down; worker name=" << runnable_name
|
||||
<< "; endpoint_id=" << endpoint_id;
|
||||
// Always clear out all state related to this endpoint before terminating
|
||||
// this thread.
|
||||
DiscardEndpoint(client, endpoint_id);
|
||||
NEARBY_LOGS(INFO) << "Worker done; worker name=" << runnable_name
|
||||
<< "; endpoint_id=" << endpoint_id;
|
||||
}
|
||||
|
||||
ExceptionOr<bool> EndpointManager::HandleData(
|
||||
const std::string& endpoint_id, ClientProxy* client,
|
||||
EndpointChannel* endpoint_channel) {
|
||||
// Read as much as we can from the healthy EndpointChannel - when it is no
|
||||
// longer in good shape (i.e. our read from it throws an Exception), our
|
||||
// super class will loop back around and try our luck in case there's been
|
||||
// a replacement for this endpoint since we last checked with the
|
||||
// EndpointChannelManager.
|
||||
while (true) {
|
||||
ExceptionOr<ByteArray> bytes = endpoint_channel->Read();
|
||||
if (!bytes.ok()) {
|
||||
NEARBY_LOG(INFO, "Stop reading on read-time exception: %d",
|
||||
bytes.exception());
|
||||
return ExceptionOr<bool>(bytes.exception());
|
||||
}
|
||||
ExceptionOr<OfflineFrame> wrapped_frame = parser::FromBytes(bytes.result());
|
||||
if (!wrapped_frame.ok()) {
|
||||
if (wrapped_frame.GetException().Raised(
|
||||
Exception::kInvalidProtocolBuffer)) {
|
||||
NEARBY_LOG(INFO, "Failed to decode; endpoint=%s; channel=%s; skip",
|
||||
endpoint_id.c_str(), endpoint_channel->GetType().c_str());
|
||||
continue;
|
||||
} else {
|
||||
NEARBY_LOG(INFO, "Stop reading on parse-time exception: %d",
|
||||
wrapped_frame.exception());
|
||||
return ExceptionOr<bool>(wrapped_frame.exception());
|
||||
}
|
||||
}
|
||||
OfflineFrame& frame = wrapped_frame.result();
|
||||
|
||||
// Route the incoming offlineFrame to its registered processor.
|
||||
V1Frame::FrameType frame_type = parser::GetFrameType(frame);
|
||||
LockedFrameProcessor frame_processor = GetFrameProcessor(frame_type);
|
||||
if (!frame_processor) {
|
||||
// report messages without handlers, except KEEP_ALIVE, which has
|
||||
// no explicit handler.
|
||||
if (frame_type == V1Frame::KEEP_ALIVE) {
|
||||
NEARBY_LOG(INFO, "KeepAlive message for endpoint %s",
|
||||
endpoint_id.c_str());
|
||||
} else if (frame_type == V1Frame::DISCONNECTION) {
|
||||
NEARBY_LOG(INFO, "Disconnect message for endpoint %s",
|
||||
endpoint_id.c_str());
|
||||
endpoint_channel->Close();
|
||||
} else {
|
||||
NEARBY_LOGS(ERROR) << "Unhandled message: endpoint_id=" << endpoint_id
|
||||
<< ", frame type="
|
||||
<< V1Frame::FrameType_Name(frame_type);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
frame_processor->OnIncomingFrame(frame, endpoint_id, client,
|
||||
endpoint_channel->GetMedium());
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionOr<bool> EndpointManager::HandleKeepAlive(
|
||||
EndpointChannel* endpoint_channel, absl::Duration keep_alive_interval,
|
||||
absl::Duration keep_alive_timeout, Mutex* keep_alive_waiter_mutex,
|
||||
ConditionVariable* keep_alive_waiter) {
|
||||
// Check if it has been too long since we received a frame from our endpoint.
|
||||
absl::Time last_read_time = endpoint_channel->GetLastReadTimestamp();
|
||||
absl::Duration duration_until_timeout =
|
||||
last_read_time == kInvalidTimestamp
|
||||
? keep_alive_timeout
|
||||
: last_read_time + keep_alive_timeout -
|
||||
SystemClock::ElapsedRealtime();
|
||||
if (duration_until_timeout <= absl::ZeroDuration()) {
|
||||
return ExceptionOr<bool>(false);
|
||||
}
|
||||
|
||||
// If we haven't written anything to the endpoint for a while, attempt to send
|
||||
// the KeepAlive frame over the endpoint channel. If the write fails, our
|
||||
// super class will loop back around and try our luck again in case there's
|
||||
// been a replacement for this endpoint.
|
||||
absl::Time last_write_time = endpoint_channel->GetLastWriteTimestamp();
|
||||
absl::Duration duration_until_write_keep_alive =
|
||||
last_write_time == kInvalidTimestamp
|
||||
? keep_alive_interval
|
||||
: last_write_time + keep_alive_interval -
|
||||
SystemClock::ElapsedRealtime();
|
||||
if (duration_until_write_keep_alive <= absl::ZeroDuration()) {
|
||||
Exception write_exception = endpoint_channel->Write(parser::ForKeepAlive());
|
||||
if (!write_exception.Ok()) {
|
||||
return ExceptionOr<bool>(write_exception);
|
||||
}
|
||||
duration_until_write_keep_alive = keep_alive_interval;
|
||||
}
|
||||
|
||||
absl::Duration wait_for =
|
||||
std::min(duration_until_timeout, duration_until_write_keep_alive);
|
||||
{
|
||||
MutexLock lock(keep_alive_waiter_mutex);
|
||||
Exception wait_exception = keep_alive_waiter->Wait(wait_for);
|
||||
if (!wait_exception.Ok()) {
|
||||
return ExceptionOr<bool>(wait_exception);
|
||||
}
|
||||
}
|
||||
|
||||
return ExceptionOr<bool>(true);
|
||||
}
|
||||
|
||||
bool operator==(const EndpointManager::FrameProcessor& lhs,
|
||||
const EndpointManager::FrameProcessor& rhs) {
|
||||
// We're comparing addresses because these objects are callbacks which need to
|
||||
// be matched by exact instances.
|
||||
return &lhs == &rhs;
|
||||
}
|
||||
|
||||
bool operator<(const EndpointManager::FrameProcessor& lhs,
|
||||
const EndpointManager::FrameProcessor& rhs) {
|
||||
// We're comparing addresses because these objects are callbacks which need to
|
||||
// be matched by exact instances.
|
||||
return &lhs < &rhs;
|
||||
}
|
||||
|
||||
EndpointManager::EndpointManager(EndpointChannelManager* manager)
|
||||
: channel_manager_(manager) {}
|
||||
|
||||
EndpointManager::~EndpointManager() {
|
||||
NEARBY_LOG(INFO, "Initiating shutdown of EndpointManager.");
|
||||
CountDownLatch latch(1);
|
||||
RunOnEndpointManagerThread("bring-down-endpoints", [this, &latch]() {
|
||||
NEARBY_LOG(INFO, "Bringing down endpoints");
|
||||
endpoints_.clear();
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
|
||||
NEARBY_LOG(INFO, "Bringing down control thread");
|
||||
serial_executor_.Shutdown();
|
||||
NEARBY_LOG(INFO, "EndpointManager is down");
|
||||
}
|
||||
|
||||
void EndpointManager::RegisterFrameProcessor(
|
||||
V1Frame::FrameType frame_type, EndpointManager::FrameProcessor* processor) {
|
||||
if (auto frame_processor = GetFrameProcessor(frame_type)) {
|
||||
NEARBY_LOGS(INFO) << "EndpointManager received request to update "
|
||||
"registration of frame processor "
|
||||
<< processor << " for frame type "
|
||||
<< V1Frame::FrameType_Name(frame_type) << ", self"
|
||||
<< this;
|
||||
frame_processor.set(processor);
|
||||
} else {
|
||||
MutexLock lock(&frame_processors_lock_);
|
||||
NEARBY_LOGS(INFO) << "EndpointManager received request to add registration "
|
||||
"of frame processor "
|
||||
<< processor << " for frame type "
|
||||
<< V1Frame::FrameType_Name(frame_type)
|
||||
<< ", self=" << this;
|
||||
frame_processors_.emplace(frame_type, processor);
|
||||
}
|
||||
}
|
||||
|
||||
void EndpointManager::UnregisterFrameProcessor(
|
||||
V1Frame::FrameType frame_type,
|
||||
const EndpointManager::FrameProcessor* processor) {
|
||||
NEARBY_LOGS(INFO) << "UnregisterFrameProcessor [enter]: processor ="
|
||||
<< processor;
|
||||
if (processor == nullptr) return;
|
||||
if (auto frame_processor = GetFrameProcessor(frame_type)) {
|
||||
if (frame_processor.get() == processor) {
|
||||
frame_processor.reset();
|
||||
NEARBY_LOGS(INFO) << "EndpointManager unregister frame processor "
|
||||
<< processor << " for frame type "
|
||||
<< V1Frame::FrameType_Name(frame_type)
|
||||
<< ", self=" << this;
|
||||
} else {
|
||||
NEARBY_LOGS(INFO) << "EndpointManager cannot unregister frame processor "
|
||||
<< processor
|
||||
<< " because it is not registered for frame type "
|
||||
<< V1Frame::FrameType_Name(frame_type)
|
||||
<< ", expected=" << frame_processor.get();
|
||||
}
|
||||
} else {
|
||||
NEARBY_LOGS(INFO) << "UnregisterFrameProcessor [not found]: processor="
|
||||
<< processor;
|
||||
}
|
||||
}
|
||||
|
||||
EndpointManager::LockedFrameProcessor EndpointManager::GetFrameProcessor(
|
||||
V1Frame::FrameType frame_type) {
|
||||
MutexLock lock(&frame_processors_lock_);
|
||||
auto it = frame_processors_.find(frame_type);
|
||||
if (it != frame_processors_.end()) {
|
||||
return LockedFrameProcessor(&it->second);
|
||||
}
|
||||
return LockedFrameProcessor();
|
||||
}
|
||||
|
||||
void EndpointManager::RemoveEndpointState(const std::string& endpoint_id) {
|
||||
NEARBY_LOGS(VERBOSE) << "EnsureWorkersTerminated for endpoint "
|
||||
<< endpoint_id;
|
||||
auto item = endpoints_.find(endpoint_id);
|
||||
if (item != endpoints_.end()) {
|
||||
NEARBY_LOGS(INFO) << "EndpointState found for endpoint " << endpoint_id;
|
||||
// If another instance of data and keep-alive handlers is running, it will
|
||||
// terminate soon. Removing EndpointState waits for workers to complete.
|
||||
endpoints_.erase(item);
|
||||
NEARBY_LOGS(VERBOSE) << "Workers terminated for endpoint " << endpoint_id;
|
||||
} else {
|
||||
NEARBY_LOGS(INFO) << "EndpointState not found for endpoint " << endpoint_id;
|
||||
}
|
||||
}
|
||||
|
||||
void EndpointManager::RegisterEndpoint(
|
||||
ClientProxy* client, const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info,
|
||||
const ConnectionOptions& connection_options,
|
||||
std::unique_ptr<EndpointChannel> channel,
|
||||
const ConnectionListener& listener, const std::string& connection_token) {
|
||||
CountDownLatch latch(1);
|
||||
|
||||
// NOTE (unique_ptr<> capture):
|
||||
// std::unique_ptr<> is not copyable, so we can not pass it to
|
||||
// lambda capture, because lambda eventually is converted to std::function<>.
|
||||
// Instead, we release() a pointer, and pass a raw pointer, which is copyalbe.
|
||||
// We ignore the risk of job not scheduled (and an associated risk of memory
|
||||
// leak), because this may only happen during service shutdown.
|
||||
RunOnEndpointManagerThread("register-endpoint", [this, client,
|
||||
channel = channel.release(),
|
||||
&endpoint_id, &info,
|
||||
&connection_options,
|
||||
&listener, &connection_token,
|
||||
&latch]() {
|
||||
if (endpoints_.contains(endpoint_id)) {
|
||||
NEARBY_LOGS(WARNING) << "Registering duplicate endpoint " << endpoint_id;
|
||||
// We must remove old endpoint state before registering a new one for the
|
||||
// same endpoint_id.
|
||||
RemoveEndpointState(endpoint_id);
|
||||
}
|
||||
|
||||
absl::Duration keep_alive_interval =
|
||||
absl::Milliseconds(connection_options.keep_alive_interval_millis);
|
||||
absl::Duration keep_alive_timeout =
|
||||
absl::Milliseconds(connection_options.keep_alive_timeout_millis);
|
||||
NEARBY_LOGS(INFO) << "Registering endpoint " << endpoint_id
|
||||
<< " for client " << client->GetClientId()
|
||||
<< " with keep-alive frame as interval="
|
||||
<< absl::FormatDuration(keep_alive_interval)
|
||||
<< ", timeout="
|
||||
<< absl::FormatDuration(keep_alive_timeout);
|
||||
|
||||
// Pass ownership of channel to EndpointChannelManager
|
||||
NEARBY_LOGS(INFO) << "Registering endpoint with channel manager: endpoint "
|
||||
<< endpoint_id;
|
||||
channel_manager_->RegisterChannelForEndpoint(
|
||||
client, endpoint_id, std::unique_ptr<EndpointChannel>(channel));
|
||||
|
||||
EndpointState& endpoint_state =
|
||||
endpoints_
|
||||
.emplace(endpoint_id, EndpointState(endpoint_id, channel_manager_))
|
||||
.first->second;
|
||||
|
||||
NEARBY_LOGS(INFO) << "Starting workers: endpoint " << endpoint_id;
|
||||
// For every endpoint, there's normally only one Read handler instance
|
||||
// running on a dedicated thread. This instance reads data from the
|
||||
// endpoint and delegates incoming frames to various FrameProcessors.
|
||||
// Once the frame has been properly handled, it starts reading again for
|
||||
// the next frame. If the handler fails its read and no other
|
||||
// EndpointChannels are available for this endpoint, a disconnection
|
||||
// will be initiated.
|
||||
endpoint_state.StartEndpointReader([this, client, endpoint_id]() {
|
||||
EndpointChannelLoopRunnable(
|
||||
"Read", client, endpoint_id,
|
||||
[this, client, endpoint_id](EndpointChannel* channel) {
|
||||
return HandleData(endpoint_id, client, channel);
|
||||
});
|
||||
});
|
||||
|
||||
// For every endpoint, there's only one KeepAliveManager instance running on
|
||||
// a dedicated thread. This instance will periodically send out a ping* to
|
||||
// the endpoint while listening for an incoming pong**. If it fails to send
|
||||
// the ping, or if no pong is heard within keep_alive_timeout, it initiates
|
||||
// a disconnection.
|
||||
//
|
||||
// (*) Bluetooth requires a constant outgoing stream of messages. If
|
||||
// there's silence, Android will break the socket. This is why we ping.
|
||||
// (**) Wifi Hotspots can fail to notice a connection has been lost, and
|
||||
// they will happily keep writing to /dev/null. This is why we listen
|
||||
// for the pong.
|
||||
NEARBY_LOGS(VERBOSE) << "EndpointManager enabling KeepAlive for endpoint "
|
||||
<< endpoint_id;
|
||||
endpoint_state.StartEndpointKeepAliveManager(
|
||||
[this, client, endpoint_id, keep_alive_interval, keep_alive_timeout](
|
||||
Mutex* keep_alive_waiter_mutex,
|
||||
ConditionVariable* keep_alive_waiter) {
|
||||
EndpointChannelLoopRunnable(
|
||||
"KeepAliveManager", client, endpoint_id,
|
||||
[this, keep_alive_interval, keep_alive_timeout,
|
||||
keep_alive_waiter_mutex,
|
||||
keep_alive_waiter](EndpointChannel* channel) {
|
||||
return HandleKeepAlive(
|
||||
channel, keep_alive_interval, keep_alive_timeout,
|
||||
keep_alive_waiter_mutex, keep_alive_waiter);
|
||||
});
|
||||
});
|
||||
NEARBY_LOGS(INFO) << "Registering endpoint " << endpoint_id
|
||||
<< ", workers started and notifying client.";
|
||||
|
||||
// It's now time to let the client know of this new connection so that
|
||||
// they can accept or reject it.
|
||||
client->OnConnectionInitiated(endpoint_id, info, connection_options,
|
||||
listener, connection_token);
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
}
|
||||
|
||||
void EndpointManager::UnregisterEndpoint(ClientProxy* client,
|
||||
const std::string& endpoint_id) {
|
||||
NEARBY_LOGS(INFO) << "UnregisterEndpoint for endpoint " << endpoint_id;
|
||||
CountDownLatch latch(1);
|
||||
RunOnEndpointManagerThread(
|
||||
"unregister-endpoint", [this, client, endpoint_id, &latch]() {
|
||||
RemoveEndpoint(client, endpoint_id,
|
||||
/*notify=*/client->IsConnectedToEndpoint(endpoint_id));
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
}
|
||||
|
||||
int EndpointManager::GetMaxTransmitPacketSize(const std::string& endpoint_id) {
|
||||
std::shared_ptr<EndpointChannel> channel =
|
||||
channel_manager_->GetChannelForEndpoint(endpoint_id);
|
||||
if (channel == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return channel->GetMaxTransmitPacketSize();
|
||||
}
|
||||
|
||||
std::vector<std::string> EndpointManager::SendPayloadChunk(
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
const PayloadTransferFrame::PayloadChunk& payload_chunk,
|
||||
const std::vector<std::string>& endpoint_ids) {
|
||||
ByteArray bytes =
|
||||
parser::ForDataPayloadTransfer(payload_header, payload_chunk);
|
||||
|
||||
return SendTransferFrameBytes(
|
||||
endpoint_ids, bytes, payload_header.id(),
|
||||
/*offset=*/payload_chunk.offset(),
|
||||
/*packet_type=*/
|
||||
PayloadTransferFrame::PacketType_Name(PayloadTransferFrame::DATA));
|
||||
}
|
||||
|
||||
// Designed to run asynchronously. It is called from IO thread pools, and
|
||||
// jobs in these pools may be waited for from the EndpointManager thread. If we
|
||||
// allow synchronous behavior here it will cause a live lock.
|
||||
void EndpointManager::DiscardEndpoint(ClientProxy* client,
|
||||
const std::string& endpoint_id) {
|
||||
NEARBY_LOGS(VERBOSE) << "DiscardEndpoint for endpoint " << endpoint_id;
|
||||
RunOnEndpointManagerThread("discard-endpoint", [this, client, endpoint_id]() {
|
||||
RemoveEndpoint(client, endpoint_id,
|
||||
/*notify=*/client->IsConnectedToEndpoint(endpoint_id));
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<std::string> EndpointManager::SendControlMessage(
|
||||
const PayloadTransferFrame::PayloadHeader& header,
|
||||
const PayloadTransferFrame::ControlMessage& control,
|
||||
const std::vector<std::string>& endpoint_ids) {
|
||||
ByteArray bytes = parser::ForControlPayloadTransfer(header, control);
|
||||
|
||||
return SendTransferFrameBytes(
|
||||
endpoint_ids, bytes, header.id(),
|
||||
/*offset=*/control.offset(),
|
||||
/*packet_type=*/
|
||||
PayloadTransferFrame::PacketType_Name(PayloadTransferFrame::CONTROL));
|
||||
}
|
||||
|
||||
// @EndpointManagerThread
|
||||
void EndpointManager::RemoveEndpoint(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
bool notify) {
|
||||
NEARBY_LOGS(INFO) << "RemoveEndpoint for endpoint " << endpoint_id;
|
||||
// Unregistering from channel_manager_ will also serve to terminate
|
||||
// the dedicated handler and KeepAlive threads we started when we registered
|
||||
// this endpoint.
|
||||
if (channel_manager_->UnregisterChannelForEndpoint(endpoint_id)) {
|
||||
// Notify all frame processors of the disconnection immediately and wait
|
||||
// for them to clean up state. Only once all processors are done cleaning
|
||||
// up, we can remove the endpoint from ClientProxy after which there
|
||||
// should be no further interactions with the endpoint.
|
||||
// (See b/37352254 for history)
|
||||
WaitForEndpointDisconnectionProcessing(client, endpoint_id);
|
||||
|
||||
client->OnDisconnected(endpoint_id, notify);
|
||||
NEARBY_LOGS(INFO) << "Removed endpoint for endpoint " << endpoint_id;
|
||||
}
|
||||
RemoveEndpointState(endpoint_id);
|
||||
}
|
||||
|
||||
// @EndpointManagerThread
|
||||
void EndpointManager::WaitForEndpointDisconnectionProcessing(
|
||||
ClientProxy* client, const std::string& endpoint_id) {
|
||||
NEARBY_LOGS(INFO) << "Wait: client=" << client
|
||||
<< "; endpoint_id=" << endpoint_id;
|
||||
CountDownLatch barrier =
|
||||
NotifyFrameProcessorsOnEndpointDisconnect(client, endpoint_id);
|
||||
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Waiting for frame processors to disconnect from endpoint "
|
||||
<< endpoint_id;
|
||||
if (!barrier.Await(kProcessEndpointDisconnectionTimeout).result()) {
|
||||
NEARBY_LOGS(INFO) << "Failed to disconnect frame processors from endpoint "
|
||||
<< endpoint_id;
|
||||
} else {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Finished waiting for frame processors to disconnect from endpoint "
|
||||
<< endpoint_id;
|
||||
}
|
||||
}
|
||||
|
||||
CountDownLatch EndpointManager::NotifyFrameProcessorsOnEndpointDisconnect(
|
||||
ClientProxy* client, const std::string& endpoint_id) {
|
||||
NEARBY_LOGS(INFO) << "NotifyFrameProcessorsOnEndpointDisconnect: client="
|
||||
<< client << "; endpoint_id=" << endpoint_id;
|
||||
MutexLock lock(&frame_processors_lock_);
|
||||
auto total_size = frame_processors_.size();
|
||||
NEARBY_LOGS(INFO) << "Total frame processors: " << total_size;
|
||||
CountDownLatch barrier(total_size);
|
||||
|
||||
int valid = 0;
|
||||
for (auto& item : frame_processors_) {
|
||||
LockedFrameProcessor processor(&item.second);
|
||||
NEARBY_LOGS(INFO) << "processor=" << processor.get()
|
||||
<< "; frame type=" << V1Frame::FrameType_Name(item.first);
|
||||
if (processor) {
|
||||
valid++;
|
||||
processor->OnEndpointDisconnect(client, endpoint_id, barrier);
|
||||
} else {
|
||||
barrier.CountDown();
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
NEARBY_LOGS(INFO) << "No valid frame processors.";
|
||||
} else {
|
||||
NEARBY_LOGS(INFO) << "Valid frame processors: " << valid;
|
||||
}
|
||||
return barrier;
|
||||
}
|
||||
|
||||
std::vector<std::string> EndpointManager::SendTransferFrameBytes(
|
||||
const std::vector<std::string>& endpoint_ids, const ByteArray& bytes,
|
||||
std::int64_t payload_id, std::int64_t offset,
|
||||
const std::string& packet_type) {
|
||||
std::vector<std::string> failed_endpoint_ids;
|
||||
for (const std::string& endpoint_id : endpoint_ids) {
|
||||
std::shared_ptr<EndpointChannel> channel =
|
||||
channel_manager_->GetChannelForEndpoint(endpoint_id);
|
||||
|
||||
if (channel == nullptr) {
|
||||
// We no longer know about this endpoint (it was either explicitly
|
||||
// unregistered, or a read/write error made us unregister it internally).
|
||||
NEARBY_LOGS(ERROR) << "EndpointManager failed to find EndpointChannel "
|
||||
"over which to write "
|
||||
<< packet_type << " at offset " << offset
|
||||
<< " of Payload " << payload_id << " to endpoint "
|
||||
<< endpoint_id;
|
||||
|
||||
failed_endpoint_ids.push_back(endpoint_id);
|
||||
continue;
|
||||
}
|
||||
|
||||
Exception write_exception = channel->Write(bytes);
|
||||
if (!write_exception.Ok()) {
|
||||
failed_endpoint_ids.push_back(endpoint_id);
|
||||
NEARBY_LOGS(INFO) << "Failed to send packet; endpoint_id=" << endpoint_id;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return failed_endpoint_ids;
|
||||
}
|
||||
|
||||
EndpointManager::EndpointState::~EndpointState() {
|
||||
// We must unregister the endpoint first to signal the runnables that they
|
||||
// should exit their loops. SingleThreadExecutor destructors will wait for the
|
||||
// workers to finish. |channel_manager_| is null after moved from this object
|
||||
// (in move constructor) which prevents unregistering the channel prematurely.
|
||||
if (channel_manager_) {
|
||||
NEARBY_LOG(VERBOSE, "EndpointState destructor %s", endpoint_id_.c_str());
|
||||
channel_manager_->UnregisterChannelForEndpoint(endpoint_id_);
|
||||
}
|
||||
|
||||
// Make sure the KeepAlive thread isn't blocking shutdown.
|
||||
if (keep_alive_waiter_mutex_ && keep_alive_waiter_) {
|
||||
MutexLock lock(keep_alive_waiter_mutex_.get());
|
||||
keep_alive_waiter_->Notify();
|
||||
}
|
||||
}
|
||||
|
||||
void EndpointManager::EndpointState::StartEndpointReader(Runnable&& runnable) {
|
||||
reader_thread_.Execute("reader", std::move(runnable));
|
||||
}
|
||||
|
||||
void EndpointManager::EndpointState::StartEndpointKeepAliveManager(
|
||||
std::function<void(Mutex*, ConditionVariable*)> runnable) {
|
||||
keep_alive_thread_.Execute(
|
||||
"keep-alive",
|
||||
[runnable, keep_alive_waiter_mutex = keep_alive_waiter_mutex_.get(),
|
||||
keep_alive_waiter = keep_alive_waiter_.get()]() {
|
||||
runnable(keep_alive_waiter_mutex, keep_alive_waiter);
|
||||
});
|
||||
}
|
||||
|
||||
void EndpointManager::RunOnEndpointManagerThread(const std::string& name,
|
||||
Runnable runnable) {
|
||||
serial_executor_.Execute(name, std::move(runnable));
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,284 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_INTERNAL_ENDPOINT_MANAGER_H_
|
||||
#define CORE_INTERNAL_ENDPOINT_MANAGER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_channel.h"
|
||||
#include "core/internal/endpoint_channel_manager.h"
|
||||
#include "core/listeners.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/runnable.h"
|
||||
#include "platform/public/condition_variable.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/multi_thread_executor.h"
|
||||
#include "platform/public/single_thread_executor.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Manages all operations related to the remote endpoints with which we are
|
||||
// interacting.
|
||||
//
|
||||
// All processing of incoming and outgoing payloads is spread across this and
|
||||
// the PayloadManager as described below.
|
||||
//
|
||||
// The sending of outgoing payloads originates in
|
||||
// PayloadManager::SendPayload() before control is transferred over to
|
||||
// EndpointManager::SendPayloadChunk(). This work happens on one of three
|
||||
// dedicated writer threads belonging to the PayloadManager. The writer thread
|
||||
// that is used depends on the Payload::Type.
|
||||
//
|
||||
// The EndpointManager has one dedicated reader thread for each registered
|
||||
// endpoint, and the receiving of every incoming payload (and its subsequent
|
||||
// chunks) originates on one of those threads before control is transferred over
|
||||
// to PayloadManager::ProcessFrame() (still running on that
|
||||
// same dedicated reader thread).
|
||||
|
||||
class EndpointManager {
|
||||
public:
|
||||
class FrameProcessor {
|
||||
public:
|
||||
virtual ~FrameProcessor() = default;
|
||||
|
||||
// @EndpointManagerReaderThread
|
||||
// Called for every incoming frame of registered type.
|
||||
// NOTE(OfflineFrame& frame):
|
||||
// For large payload in data phase, resources may be saved if data is moved,
|
||||
// rather than copied (if passing data by reference is not an option).
|
||||
// To achieve that, OfflineFrame needs to be either mutabe lvalue reference,
|
||||
// or rvalue reference. Rvalue references are discouraged by go/cstyle,
|
||||
// and that leaves us with mutable lvalue reference.
|
||||
virtual void OnIncomingFrame(OfflineFrame& offline_frame,
|
||||
const std::string& from_endpoint_id,
|
||||
ClientProxy* to_client,
|
||||
proto::connections::Medium current_medium) = 0;
|
||||
|
||||
// Implementations must call barrier.CountDown() once
|
||||
// they're done. This parallelizes the disconnection event across all frame
|
||||
// processors.
|
||||
//
|
||||
// @EndpointManagerThread
|
||||
virtual void OnEndpointDisconnect(ClientProxy* client,
|
||||
const std::string& endpoint_id,
|
||||
CountDownLatch barrier) = 0;
|
||||
};
|
||||
|
||||
explicit EndpointManager(EndpointChannelManager* manager);
|
||||
~EndpointManager();
|
||||
|
||||
// Invoked from the constructors of the various *Manager components that make
|
||||
// up the OfflineServiceController implementation.
|
||||
// FrameProcessor* instances are of dynamic duration and survive all sessions.
|
||||
// Blocks until registration is complete.
|
||||
void RegisterFrameProcessor(V1Frame::FrameType frame_type,
|
||||
FrameProcessor* processor);
|
||||
void UnregisterFrameProcessor(V1Frame::FrameType frame_type,
|
||||
const FrameProcessor* processor);
|
||||
|
||||
// Invoked from the different PcpHandler implementations (of which there can
|
||||
// be only one at a time).
|
||||
// Blocks until registration is complete.
|
||||
void RegisterEndpoint(ClientProxy* client, const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info,
|
||||
const ConnectionOptions& connection_options,
|
||||
std::unique_ptr<EndpointChannel> channel,
|
||||
const ConnectionListener& listener,
|
||||
const std::string& connection_token);
|
||||
// Called when a client explicitly asks to disconnect from this endpoint. In
|
||||
// this case, we do not notify the client of onDisconnected().
|
||||
void UnregisterEndpoint(ClientProxy* client, const std::string& endpoint_id);
|
||||
|
||||
// Returns the maximum supported transmit packet size(MTU) for the underlying
|
||||
// transport.
|
||||
int GetMaxTransmitPacketSize(const std::string& endpoint_id);
|
||||
|
||||
// Returns the list of endpoints to which sending this chunk failed.
|
||||
//
|
||||
// Invoked from the PayloadManager's sendPayload() method.
|
||||
std::vector<std::string> SendPayloadChunk(
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
const PayloadTransferFrame::PayloadChunk& payload_chunk,
|
||||
const std::vector<std::string>& endpoint_ids);
|
||||
std::vector<std::string> SendControlMessage(
|
||||
const PayloadTransferFrame::PayloadHeader& payload_header,
|
||||
const PayloadTransferFrame::ControlMessage& control_message,
|
||||
const std::vector<std::string>& endpoint_ids);
|
||||
|
||||
// Called when we internally want to get rid of the endpoint, without the
|
||||
// client directly telling us to. For example...
|
||||
// a) We failed to read from the endpoint in its dedicated reader thread.
|
||||
// b) We failed to write to the endpoint in PayloadManager.
|
||||
// c) The connection was rejected in PCPHandler.
|
||||
// d) The dedicated KeepAlive thread exceeded its period of inactivity.
|
||||
// Or in the numerous other cases where a failure occurred and we no longer
|
||||
// believe the endpoint is in a healthy state.
|
||||
//
|
||||
// Note: This must not block. Otherwise we can get into a deadlock where we
|
||||
// ask everyone who's registered an FrameProcessor to
|
||||
// processEndpointDisconnection() while the caller of DiscardEndpoint() is
|
||||
// blocked here.
|
||||
void DiscardEndpoint(ClientProxy* client, const std::string& endpoint_id);
|
||||
|
||||
private:
|
||||
class EndpointState {
|
||||
public:
|
||||
EndpointState(const std::string& endpoint_id,
|
||||
EndpointChannelManager* channel_manager)
|
||||
: endpoint_id_{endpoint_id},
|
||||
channel_manager_{channel_manager},
|
||||
keep_alive_waiter_mutex_{std::make_unique<Mutex>()},
|
||||
keep_alive_waiter_{std::make_unique<ConditionVariable>(
|
||||
keep_alive_waiter_mutex_.get())} {}
|
||||
|
||||
EndpointState(const EndpointState&) = delete;
|
||||
// The default move constructor would not reset |channel_manager_|, for
|
||||
// example. This needs to be nullified so the destructor shutdown logic is
|
||||
// bypassed when objects are moved.
|
||||
EndpointState(EndpointState&& other)
|
||||
: endpoint_id_{std::move(other.endpoint_id_)},
|
||||
channel_manager_{std::exchange(other.channel_manager_, nullptr)},
|
||||
reader_thread_{std::move(other.reader_thread_)},
|
||||
keep_alive_waiter_mutex_{
|
||||
std::exchange(other.keep_alive_waiter_mutex_, nullptr)},
|
||||
keep_alive_waiter_{std::exchange(other.keep_alive_waiter_, nullptr)},
|
||||
keep_alive_thread_{std::move(other.keep_alive_thread_)} {}
|
||||
EndpointState& operator=(const EndpointState&) = delete;
|
||||
EndpointState&& operator=(EndpointState&&) = delete;
|
||||
~EndpointState();
|
||||
|
||||
void StartEndpointReader(Runnable&& runnable);
|
||||
void StartEndpointKeepAliveManager(
|
||||
std::function<void(Mutex*, ConditionVariable*)> runnable);
|
||||
|
||||
private:
|
||||
const std::string endpoint_id_;
|
||||
EndpointChannelManager* channel_manager_;
|
||||
SingleThreadExecutor reader_thread_;
|
||||
|
||||
// Use a condition variable so we can wait on the thread but still be able
|
||||
// to wake it up before shutting down. We don't want to just sleep and risk
|
||||
// blocking shutdown. Note: Create the mutex/condition variable on the heap
|
||||
// so raw pointers sent to HandleKeepAlive() aren't invalidated during
|
||||
// std::move operations.
|
||||
mutable std::unique_ptr<Mutex> keep_alive_waiter_mutex_;
|
||||
std::unique_ptr<ConditionVariable> keep_alive_waiter_;
|
||||
SingleThreadExecutor keep_alive_thread_;
|
||||
};
|
||||
|
||||
// RAII accessor for FrameProcessor
|
||||
class LockedFrameProcessor;
|
||||
|
||||
// Provides a mutex per FrameProcessor to prevent unregistering (and
|
||||
// destroying) a FrameProcessor when it's in use.
|
||||
class FrameProcessorWithMutex {
|
||||
public:
|
||||
explicit FrameProcessorWithMutex(FrameProcessor* frame_processor = nullptr)
|
||||
: frame_processor_{frame_processor} {}
|
||||
|
||||
private:
|
||||
FrameProcessor* frame_processor_;
|
||||
Mutex mutex_;
|
||||
friend class LockedFrameProcessor;
|
||||
};
|
||||
|
||||
LockedFrameProcessor GetFrameProcessor(V1Frame::FrameType frame_type);
|
||||
|
||||
ExceptionOr<bool> HandleData(const std::string& endpoint_id,
|
||||
ClientProxy* client_proxy,
|
||||
EndpointChannel* endpoint_channel);
|
||||
|
||||
ExceptionOr<bool> HandleKeepAlive(EndpointChannel* endpoint_channel,
|
||||
absl::Duration keep_alive_interval,
|
||||
absl::Duration keep_alive_timeout,
|
||||
Mutex* keep_alive_waiter_mutex,
|
||||
ConditionVariable* keep_alive_waiter);
|
||||
|
||||
// Waits for a given endpoint EndpointChannelLoopRunnable() workers to
|
||||
// terminate.
|
||||
// Is called from RegisterEndpoint to avoid races; also called from
|
||||
// RemoveEndpoint as part of proper endpoint shutdown sequence.
|
||||
// @EndpointManagerThread
|
||||
void RemoveEndpointState(const std::string& endpoint_id);
|
||||
|
||||
void EndpointChannelLoopRunnable(
|
||||
const std::string& runnable_name, ClientProxy* client_proxy,
|
||||
const std::string& endpoint_id,
|
||||
std::function<ExceptionOr<bool>(EndpointChannel*)> handler);
|
||||
|
||||
static void WaitForLatch(const std::string& method_name,
|
||||
CountDownLatch* latch);
|
||||
static void WaitForLatch(const std::string& method_name,
|
||||
CountDownLatch* latch, std::int32_t timeout_millis);
|
||||
|
||||
static constexpr absl::Duration kProcessEndpointDisconnectionTimeout =
|
||||
absl::Milliseconds(2000);
|
||||
static constexpr absl::Time kInvalidTimestamp = absl::InfinitePast();
|
||||
|
||||
// It should be noted that this method may be called multiple times (because
|
||||
// invoking this method closes the endpoint channel, which causes the
|
||||
// dedicated reader and KeepAlive threads to terminate, which in turn leads to
|
||||
// this method being called), but that's alright because the implementation of
|
||||
// this method is idempotent.
|
||||
// @EndpointManagerThread
|
||||
void RemoveEndpoint(ClientProxy* client, const std::string& endpoint_id,
|
||||
bool notify);
|
||||
|
||||
void WaitForEndpointDisconnectionProcessing(ClientProxy* client,
|
||||
const std::string& endpoint_id);
|
||||
|
||||
CountDownLatch NotifyFrameProcessorsOnEndpointDisconnect(
|
||||
ClientProxy* client, const std::string& endpoint_id);
|
||||
|
||||
std::vector<std::string> SendTransferFrameBytes(
|
||||
const std::vector<std::string>& endpoint_ids,
|
||||
const ByteArray& payload_transfer_frame_bytes, std::int64_t payload_id,
|
||||
std::int64_t offset, const std::string& packet_type);
|
||||
|
||||
// Executes all jobs sequentially, on a serial_executor_.
|
||||
void RunOnEndpointManagerThread(const std::string& name, Runnable runnable);
|
||||
|
||||
EndpointChannelManager* channel_manager_;
|
||||
|
||||
RecursiveMutex frame_processors_lock_;
|
||||
absl::flat_hash_map<V1Frame::FrameType, FrameProcessorWithMutex>
|
||||
frame_processors_ ABSL_GUARDED_BY(frame_processors_lock_);
|
||||
|
||||
// We keep track of all registered channel endpoints here.
|
||||
absl::flat_hash_map<std::string, EndpointState> endpoints_;
|
||||
|
||||
SingleThreadExecutor serial_executor_;
|
||||
};
|
||||
|
||||
// Operator overloads when comparing FrameProcessor*.
|
||||
bool operator==(const EndpointManager::FrameProcessor& lhs,
|
||||
const EndpointManager::FrameProcessor& rhs);
|
||||
bool operator<(const EndpointManager::FrameProcessor& lhs,
|
||||
const EndpointManager::FrameProcessor& rhs);
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_ENDPOINT_MANAGER_H_
|
||||
@@ -1,289 +0,0 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core/internal/endpoint_manager.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/connection_options.h"
|
||||
#include "core/internal/client_proxy.h"
|
||||
#include "core/internal/endpoint_channel_manager.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/pipe.h"
|
||||
#include "proto/connections_enums.pb.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
using ::location::nearby::proto::connections::DisconnectionReason;
|
||||
using ::location::nearby::proto::connections::Medium;
|
||||
using ::testing::_;
|
||||
using ::testing::MockFunction;
|
||||
using ::testing::Return;
|
||||
using ::testing::StrictMock;
|
||||
|
||||
class MockEndpointChannel : public EndpointChannel {
|
||||
public:
|
||||
MOCK_METHOD(ExceptionOr<ByteArray>, Read, (), (override));
|
||||
MOCK_METHOD(Exception, Write, (const ByteArray& data), (override));
|
||||
MOCK_METHOD(void, Close, (), (override));
|
||||
MOCK_METHOD(void, Close, (DisconnectionReason reason), (override));
|
||||
MOCK_METHOD(proto::connections::ConnectionTechnology, GetTechnology, (),
|
||||
(const override));
|
||||
MOCK_METHOD(proto::connections::ConnectionBand, GetBand, (),
|
||||
(const override));
|
||||
MOCK_METHOD(int, GetFrequency, (), (const override));
|
||||
MOCK_METHOD(int, GetTryCount, (), (const override));
|
||||
MOCK_METHOD(std::string, GetType, (), (const override));
|
||||
MOCK_METHOD(std::string, GetName, (), (const override));
|
||||
MOCK_METHOD(Medium, GetMedium, (), (const override));
|
||||
MOCK_METHOD(int, GetMaxTransmitPacketSize, (), (const override));
|
||||
MOCK_METHOD(void, EnableEncryption,
|
||||
(std::shared_ptr<EncryptionContext> context), (override));
|
||||
MOCK_METHOD(void, DisableEncryption, (), (override));
|
||||
MOCK_METHOD(bool, IsPaused, (), (const override));
|
||||
MOCK_METHOD(void, Pause, (), (override));
|
||||
MOCK_METHOD(void, Resume, (), (override));
|
||||
MOCK_METHOD(absl::Time, GetLastReadTimestamp, (), (const override));
|
||||
MOCK_METHOD(absl::Time, GetLastWriteTimestamp, (), (const override));
|
||||
MOCK_METHOD(void, SetAnalyticsRecorder,
|
||||
(analytics::AnalyticsRecorder*, const std::string&), (override));
|
||||
|
||||
bool IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
void DoClose() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
closed_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable absl::Mutex mutex_;
|
||||
bool closed_ = false;
|
||||
};
|
||||
|
||||
class MockFrameProcessor : public EndpointManager::FrameProcessor {
|
||||
public:
|
||||
MOCK_METHOD(void, OnIncomingFrame,
|
||||
(OfflineFrame & offline_frame,
|
||||
const std::string& from_endpoint_id, ClientProxy* to_client,
|
||||
Medium current_medium),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(void, OnEndpointDisconnect,
|
||||
(ClientProxy * client, const std::string& endpoint_id,
|
||||
CountDownLatch barrier),
|
||||
(override));
|
||||
};
|
||||
|
||||
class EndpointManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
void RegisterEndpoint(std::unique_ptr<MockEndpointChannel> channel,
|
||||
bool should_close = true) {
|
||||
CountDownLatch done(1);
|
||||
if (should_close) {
|
||||
ON_CALL(*channel, Close(_))
|
||||
.WillByDefault(
|
||||
[&done](DisconnectionReason reason) { done.CountDown(); });
|
||||
}
|
||||
EXPECT_CALL(*channel, GetMedium()).WillRepeatedly(Return(Medium::BLE));
|
||||
EXPECT_CALL(*channel, GetLastReadTimestamp())
|
||||
.WillRepeatedly(Return(start_time_));
|
||||
EXPECT_CALL(*channel, GetLastWriteTimestamp())
|
||||
.WillRepeatedly(Return(start_time_));
|
||||
EXPECT_CALL(mock_listener_.initiated_cb, Call).Times(1);
|
||||
em_.RegisterEndpoint(&client_, endpoint_id_, info_, connection_options_,
|
||||
std::move(channel), listener_, connection_token);
|
||||
if (should_close) {
|
||||
EXPECT_TRUE(done.Await(absl::Milliseconds(1000)).result());
|
||||
}
|
||||
}
|
||||
|
||||
ClientProxy client_;
|
||||
ConnectionOptions connection_options_{
|
||||
.keep_alive_interval_millis = 5000,
|
||||
.keep_alive_timeout_millis = 30000,
|
||||
};
|
||||
std::vector<std::unique_ptr<EndpointManager::FrameProcessor>> processors_;
|
||||
EndpointChannelManager ecm_;
|
||||
EndpointManager em_{&ecm_};
|
||||
std::string endpoint_id_ = "endpoint_id";
|
||||
ConnectionResponseInfo info_ = {
|
||||
.remote_endpoint_info = ByteArray{"info"},
|
||||
.authentication_token = "auth_token",
|
||||
.raw_authentication_token = ByteArray{"auth_token"},
|
||||
.is_incoming_connection = true,
|
||||
};
|
||||
struct MockConnectionListener {
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const ConnectionResponseInfo& info)>>
|
||||
initiated_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id)>> accepted_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
const Status& status)>>
|
||||
rejected_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id)>>
|
||||
disconnected_cb;
|
||||
StrictMock<MockFunction<void(const std::string& endpoint_id,
|
||||
std::int32_t quality)>>
|
||||
bandwidth_changed_cb;
|
||||
} mock_listener_;
|
||||
ConnectionListener listener_{
|
||||
.initiated_cb = mock_listener_.initiated_cb.AsStdFunction(),
|
||||
.accepted_cb = mock_listener_.accepted_cb.AsStdFunction(),
|
||||
.rejected_cb = mock_listener_.rejected_cb.AsStdFunction(),
|
||||
.disconnected_cb = mock_listener_.disconnected_cb.AsStdFunction(),
|
||||
.bandwidth_changed_cb =
|
||||
mock_listener_.bandwidth_changed_cb.AsStdFunction(),
|
||||
};
|
||||
std::string connection_token = "conntokn";
|
||||
absl::Time start_time_{absl::Now()};
|
||||
};
|
||||
|
||||
TEST_F(EndpointManagerTest, ConstructorDestructorWorks) { SUCCEED(); }
|
||||
|
||||
TEST_F(EndpointManagerTest, RegisterEndpointCallsOnConnectionInitiated) {
|
||||
auto endpoint_channel = std::make_unique<MockEndpointChannel>();
|
||||
EXPECT_CALL(*endpoint_channel, Read())
|
||||
.WillRepeatedly(Return(ExceptionOr<ByteArray>(Exception::kIo)));
|
||||
EXPECT_CALL(*endpoint_channel, Close(_)).Times(1);
|
||||
RegisterEndpoint(std::move(endpoint_channel));
|
||||
}
|
||||
|
||||
TEST_F(EndpointManagerTest, UnregisterEndpointCallsOnDisconnected) {
|
||||
auto endpoint_channel = std::make_unique<MockEndpointChannel>();
|
||||
EXPECT_CALL(*endpoint_channel, Read())
|
||||
.WillRepeatedly(Return(ExceptionOr<ByteArray>(Exception::kIo)));
|
||||
RegisterEndpoint(std::make_unique<MockEndpointChannel>());
|
||||
// NOTE: disconnect_cb is not called, because we did not reach fully connected
|
||||
// state. On top of that, UnregisterEndpoint is suppressing this notification.
|
||||
// (IMO, it should be called as long as any connection callback was called
|
||||
// before. (in this case initiated_cb is called)).
|
||||
// Test captures current protocol behavior.
|
||||
em_.UnregisterEndpoint(&client_, endpoint_id_);
|
||||
}
|
||||
|
||||
TEST_F(EndpointManagerTest, RegisterFrameProcessorWorks) {
|
||||
auto endpoint_channel = std::make_unique<MockEndpointChannel>();
|
||||
auto connect_request = std::make_unique<MockFrameProcessor>();
|
||||
ByteArray endpoint_info{"endpoint_name"};
|
||||
auto read_data =
|
||||
parser::ForConnectionRequest("endpoint_id", endpoint_info, 1234, false,
|
||||
"", std::vector{Medium::BLE}, 0, 0);
|
||||
EXPECT_CALL(*connect_request, OnIncomingFrame);
|
||||
EXPECT_CALL(*connect_request, OnEndpointDisconnect);
|
||||
EXPECT_CALL(*endpoint_channel, Read())
|
||||
.WillOnce(Return(ExceptionOr<ByteArray>(read_data)))
|
||||
.WillRepeatedly(Return(ExceptionOr<ByteArray>(Exception::kIo)));
|
||||
EXPECT_CALL(*endpoint_channel, Write(_))
|
||||
.WillRepeatedly(Return(Exception{Exception::kSuccess}));
|
||||
// Register frame processor, then register endpoint.
|
||||
// Endpoint will read one frame, then fail to read more and terminate.
|
||||
// On disconnection, it will notify frame processor and we verify that.
|
||||
em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST,
|
||||
connect_request.get());
|
||||
processors_.emplace_back(std::move(connect_request));
|
||||
RegisterEndpoint(std::move(endpoint_channel));
|
||||
}
|
||||
|
||||
TEST_F(EndpointManagerTest, UnregisterFrameProcessorWorks) {
|
||||
auto endpoint_channel = std::make_unique<MockEndpointChannel>();
|
||||
EXPECT_CALL(*endpoint_channel, Read())
|
||||
.WillRepeatedly(Return(ExceptionOr<ByteArray>(Exception::kIo)));
|
||||
EXPECT_CALL(*endpoint_channel, Write(_))
|
||||
.WillRepeatedly(Return(Exception{Exception::kSuccess}));
|
||||
|
||||
// We should not receive any notifications to frame processor.
|
||||
auto connect_request = std::make_unique<StrictMock<MockFrameProcessor>>();
|
||||
|
||||
// Register frame processor and immediately unregister it.
|
||||
em_.RegisterFrameProcessor(V1Frame::CONNECTION_REQUEST,
|
||||
connect_request.get());
|
||||
em_.UnregisterFrameProcessor(V1Frame::CONNECTION_REQUEST,
|
||||
connect_request.get());
|
||||
|
||||
processors_.emplace_back(std::move(connect_request));
|
||||
// Endpoint will not send OnDisconnect notification to frame processor.
|
||||
RegisterEndpoint(std::move(endpoint_channel), false);
|
||||
em_.UnregisterEndpoint(&client_, endpoint_id_);
|
||||
}
|
||||
|
||||
TEST_F(EndpointManagerTest, SendControlMessageWorks) {
|
||||
auto endpoint_channel = std::make_unique<MockEndpointChannel>();
|
||||
PayloadTransferFrame::PayloadHeader header;
|
||||
PayloadTransferFrame::ControlMessage control;
|
||||
header.set_id(12345);
|
||||
header.set_type(PayloadTransferFrame::PayloadHeader::BYTES);
|
||||
header.set_total_size(1024);
|
||||
control.set_offset(150);
|
||||
control.set_event(PayloadTransferFrame::ControlMessage::PAYLOAD_CANCELED);
|
||||
|
||||
ON_CALL(*endpoint_channel, Read())
|
||||
.WillByDefault([channel = endpoint_channel.get()]() {
|
||||
if (channel->IsClosed()) return ExceptionOr<ByteArray>(Exception::kIo);
|
||||
NEARBY_LOG(INFO, "Simulate read delay: wait");
|
||||
absl::SleepFor(absl::Milliseconds(100));
|
||||
NEARBY_LOG(INFO, "Simulate read delay: done");
|
||||
if (channel->IsClosed()) return ExceptionOr<ByteArray>(Exception::kIo);
|
||||
return ExceptionOr<ByteArray>(ByteArray{});
|
||||
});
|
||||
ON_CALL(*endpoint_channel, Close(_))
|
||||
.WillByDefault(
|
||||
[channel = endpoint_channel.get()](DisconnectionReason reason) {
|
||||
channel->DoClose();
|
||||
NEARBY_LOG(INFO, "Channel closed");
|
||||
});
|
||||
EXPECT_CALL(*endpoint_channel, Write(_))
|
||||
.WillRepeatedly(Return(Exception{Exception::kSuccess}));
|
||||
|
||||
RegisterEndpoint(std::move(endpoint_channel), false);
|
||||
auto failed_ids =
|
||||
em_.SendControlMessage(header, control, std::vector{endpoint_id_});
|
||||
EXPECT_EQ(failed_ids, std::vector<std::string>{});
|
||||
NEARBY_LOG(INFO, "Will unregister endpoint now");
|
||||
em_.UnregisterEndpoint(&client_, endpoint_id_);
|
||||
NEARBY_LOG(INFO, "Will call destructors now");
|
||||
}
|
||||
|
||||
TEST_F(EndpointManagerTest, SingleReadOnInvalidPayload) {
|
||||
auto endpoint_channel = std::make_unique<MockEndpointChannel>();
|
||||
EXPECT_CALL(*endpoint_channel, Read())
|
||||
.WillOnce(
|
||||
Return(ExceptionOr<ByteArray>(Exception::kInvalidProtocolBuffer)));
|
||||
EXPECT_CALL(*endpoint_channel, Write(_))
|
||||
.WillRepeatedly(Return(Exception{Exception::kSuccess}));
|
||||
EXPECT_CALL(*endpoint_channel, Close(_)).Times(1);
|
||||
RegisterEndpoint(std::move(endpoint_channel));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,28 +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.
|
||||
load("//security/fuzzing/blaze:cc_fuzz_target.bzl", "cc_fuzz_target")
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
cc_fuzz_target(
|
||||
name = "offline_frames_fuzzer",
|
||||
srcs = ["offline_frames_fuzzer.cc"],
|
||||
componentid = 148515,
|
||||
copts = ["-DCORE_ADAPTER_DLL"],
|
||||
deps = [
|
||||
"//cpp/core/internal",
|
||||
"//cpp/platform/base",
|
||||
"//security/fuzzing/blaze:default_init_google_for_cc_fuzz_target",
|
||||
],
|
||||
)
|
||||
@@ -1,25 +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 "core/internal/offline_frames.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
||||
location::nearby::ByteArray byte_array;
|
||||
byte_array.SetData(reinterpret_cast<const char*>(data), size);
|
||||
|
||||
location::nearby::connections::parser::FromBytes(byte_array);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,90 +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 "core/internal/injected_bluetooth_device_store.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core/internal/bluetooth_device_name.h"
|
||||
#include "platform/api/bluetooth_classic.h"
|
||||
#include "platform/base/bluetooth_utils.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
// api::BluetoothDevice implementation which stores a name and address passed to
|
||||
// its constructor and trivially returns them to implement virtual functions.
|
||||
class InjectedBluetoothDevice : public api::BluetoothDevice {
|
||||
public:
|
||||
InjectedBluetoothDevice(const std::string& name,
|
||||
const std::string& mac_address)
|
||||
: name_(name), mac_address_(mac_address) {}
|
||||
|
||||
~InjectedBluetoothDevice() override = default;
|
||||
|
||||
// api::BluetoothDevice:
|
||||
std::string GetName() const override { return name_; }
|
||||
|
||||
std::string GetMacAddress() const override { return mac_address_; }
|
||||
|
||||
private:
|
||||
const std::string name_;
|
||||
const std::string mac_address_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
InjectedBluetoothDeviceStore::InjectedBluetoothDeviceStore() = default;
|
||||
|
||||
InjectedBluetoothDeviceStore::~InjectedBluetoothDeviceStore() = default;
|
||||
|
||||
BluetoothDevice InjectedBluetoothDeviceStore::CreateInjectedBluetoothDevice(
|
||||
const ByteArray& remote_bluetooth_mac_address,
|
||||
const std::string& endpoint_id, const ByteArray& endpoint_info,
|
||||
const ByteArray& service_id_hash, Pcp pcp) {
|
||||
std::string remote_bluetooth_mac_address_str =
|
||||
BluetoothUtils::ToString(remote_bluetooth_mac_address);
|
||||
|
||||
// Valid MAC address is required.
|
||||
if (remote_bluetooth_mac_address_str.empty())
|
||||
return BluetoothDevice(/*device=*/nullptr);
|
||||
|
||||
// Non-empty endpoint info is required.
|
||||
if (endpoint_info.Empty()) return BluetoothDevice(/*device=*/nullptr);
|
||||
|
||||
BluetoothDeviceName name(BluetoothDeviceName::Version::kV1, pcp, endpoint_id,
|
||||
service_id_hash, endpoint_info,
|
||||
/*uwb_address=*/ByteArray(),
|
||||
WebRtcState::kConnectable);
|
||||
|
||||
// Note: BluetoothDeviceName internally verifies that |endpoint_id| and
|
||||
// |service_id_hash| are valid; the check below will fail if they are
|
||||
// malformed.
|
||||
if (!name.IsValid()) return BluetoothDevice(/*device=*/nullptr);
|
||||
|
||||
auto injected_device = std::make_unique<InjectedBluetoothDevice>(
|
||||
static_cast<std::string>(name), remote_bluetooth_mac_address_str);
|
||||
BluetoothDevice device_to_return(injected_device.get());
|
||||
|
||||
// Store underlying device to ensure that it is kept alive for future use.
|
||||
devices_.emplace_back(std::move(injected_device));
|
||||
|
||||
return device_to_return;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,69 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_INJECTED_BLUETOOTH_DEVICE_STORE_H_
|
||||
#define CORE_INTERNAL_INJECTED_BLUETOOTH_DEVICE_STORE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "core/internal/pcp.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/bluetooth_adapter.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Creates and stores BluetoothDevice objects which have been "injected" (i.e.,
|
||||
// passed to Nearby Connections manually by the client instead of through the
|
||||
// normal discovery flow).
|
||||
class InjectedBluetoothDeviceStore {
|
||||
public:
|
||||
InjectedBluetoothDeviceStore();
|
||||
~InjectedBluetoothDeviceStore();
|
||||
|
||||
// Creates an injected BluetoothDevice given the provided parameters:
|
||||
// |remote_bluetooth_mac_address|: A 6-byte MAC address.
|
||||
// |endpoint_id|: A string of length 4.
|
||||
// |endpoint_info|: A non-empty ByteArray whose length is <=131 bytes.
|
||||
// |service_id_hash|: A ByteArray whose length is 3.
|
||||
// |pcp|: PCP value to be used for the connection to this device.
|
||||
//
|
||||
// If the provided parameters are malformed or of incorrect length, this
|
||||
// function returns an invalid BluetoothDevice. Clients should use
|
||||
// BluetoothDevice::IsValid() with the returned device to verify that the
|
||||
// parameters were successfully processed.
|
||||
//
|
||||
// Note that successfully-injected devices stay valid for the lifetime of the
|
||||
// InjectedBluetoothDeviceStore and are not cleared until this object is
|
||||
// deleted.
|
||||
BluetoothDevice CreateInjectedBluetoothDevice(
|
||||
const ByteArray& remote_bluetooth_mac_address,
|
||||
const std::string& endpoint_id, const ByteArray& endpoint_info,
|
||||
const ByteArray& service_id_hash, Pcp pcp);
|
||||
|
||||
private:
|
||||
// Devices created by this class. BluetoothDevice objects returned by
|
||||
// CreateInjectedBluetoothDevice() store pointers to underlying
|
||||
// api::BluetoothDevice objects, so this maintains these underlying devices
|
||||
// to ensure that they are not deleted before they are referenced.
|
||||
std::vector<std::unique_ptr<api::BluetoothDevice>> devices_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_INJECTED_BLUETOOTH_DEVICE_STORE_H_
|
||||
@@ -1,123 +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 "core/internal/injected_bluetooth_device_store.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "core/internal/bluetooth_device_name.h"
|
||||
#include "platform/base/bluetooth_utils.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/bluetooth_adapter.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
// Need to wrap with static_cast<char> to silence -Wc++11-narrowing issue.
|
||||
constexpr std::array<char, 6> kTestRemoteBluetoothMacAddress{
|
||||
0x01, 0x23, 0x45, 0x67, static_cast<char>(0x89), static_cast<char>(0xab)};
|
||||
constexpr std::array<char, 2> kTestEndpointInfo{static_cast<char>(0xcd),
|
||||
static_cast<char>(0xef)};
|
||||
constexpr std::array<char, 3> kTestServiceIdHash{0x01, 0x23, 0x45};
|
||||
|
||||
const char kTestEndpointId[] = "abcd";
|
||||
|
||||
class InjectedBluetoothDeviceStoreTest : public testing::Test {
|
||||
protected:
|
||||
InjectedBluetoothDeviceStore store_;
|
||||
};
|
||||
|
||||
TEST_F(InjectedBluetoothDeviceStoreTest, Success) {
|
||||
ByteArray remote_bluetooth_mac_address(kTestRemoteBluetoothMacAddress);
|
||||
ByteArray endpoint_info(kTestEndpointInfo);
|
||||
ByteArray service_id_hash(kTestServiceIdHash);
|
||||
|
||||
BluetoothDevice device = store_.CreateInjectedBluetoothDevice(
|
||||
remote_bluetooth_mac_address, kTestEndpointId, endpoint_info,
|
||||
service_id_hash, Pcp::kP2pPointToPoint);
|
||||
EXPECT_TRUE(device.IsValid());
|
||||
|
||||
EXPECT_EQ(BluetoothUtils::ToString(remote_bluetooth_mac_address),
|
||||
device.GetMacAddress());
|
||||
|
||||
BluetoothDeviceName name(device.GetName());
|
||||
EXPECT_TRUE(name.IsValid());
|
||||
EXPECT_EQ(kTestEndpointId, name.GetEndpointId());
|
||||
EXPECT_EQ(endpoint_info, name.GetEndpointInfo());
|
||||
EXPECT_EQ(service_id_hash, name.GetServiceIdHash());
|
||||
EXPECT_EQ(Pcp::kP2pPointToPoint, name.GetPcp());
|
||||
}
|
||||
|
||||
TEST_F(InjectedBluetoothDeviceStoreTest, Fail_InvalidBluetoothMac) {
|
||||
// Use address with only 1 byte.
|
||||
ByteArray remote_bluetooth_mac_address(std::array<char, 1>{0x00});
|
||||
ByteArray endpoint_info(kTestEndpointInfo);
|
||||
ByteArray service_id_hash(kTestServiceIdHash);
|
||||
|
||||
BluetoothDevice device = store_.CreateInjectedBluetoothDevice(
|
||||
remote_bluetooth_mac_address, kTestEndpointId, endpoint_info,
|
||||
service_id_hash, Pcp::kP2pPointToPoint);
|
||||
EXPECT_FALSE(device.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(InjectedBluetoothDeviceStoreTest, Fail_InvalidEndpointId) {
|
||||
ByteArray remote_bluetooth_mac_address(kTestRemoteBluetoothMacAddress);
|
||||
ByteArray endpoint_info(kTestEndpointInfo);
|
||||
ByteArray service_id_hash(kTestServiceIdHash);
|
||||
|
||||
// Use empty endpoint ID.
|
||||
BluetoothDevice device1 = store_.CreateInjectedBluetoothDevice(
|
||||
remote_bluetooth_mac_address, /*endpoint_id=*/std::string(),
|
||||
endpoint_info, service_id_hash, Pcp::kP2pPointToPoint);
|
||||
EXPECT_FALSE(device1.IsValid());
|
||||
|
||||
// Use endpoint ID of wrong length.
|
||||
const std::string too_long_endpoint_id = "abcde";
|
||||
BluetoothDevice device2 = store_.CreateInjectedBluetoothDevice(
|
||||
remote_bluetooth_mac_address, too_long_endpoint_id, endpoint_info,
|
||||
service_id_hash, Pcp::kP2pPointToPoint);
|
||||
EXPECT_FALSE(device2.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(InjectedBluetoothDeviceStoreTest, Fail_EmptyEndpointInfo) {
|
||||
ByteArray remote_bluetooth_mac_address(kTestRemoteBluetoothMacAddress);
|
||||
// Use empty endpoint info.
|
||||
ByteArray endpoint_info;
|
||||
ByteArray service_id_hash(kTestServiceIdHash);
|
||||
|
||||
BluetoothDevice device = store_.CreateInjectedBluetoothDevice(
|
||||
remote_bluetooth_mac_address, kTestEndpointId, endpoint_info,
|
||||
service_id_hash, Pcp::kP2pPointToPoint);
|
||||
EXPECT_FALSE(device.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(InjectedBluetoothDeviceStoreTest, Fail_InvalidServiceIdHash) {
|
||||
ByteArray remote_bluetooth_mac_address(kTestRemoteBluetoothMacAddress);
|
||||
ByteArray endpoint_info(kTestEndpointInfo);
|
||||
// Use address with only 1 byte.
|
||||
ByteArray service_id_hash(std::array<char, 1>{0x00});
|
||||
|
||||
BluetoothDevice device = store_.CreateInjectedBluetoothDevice(
|
||||
remote_bluetooth_mac_address, kTestEndpointId, endpoint_info,
|
||||
service_id_hash, Pcp::kP2pPointToPoint);
|
||||
EXPECT_FALSE(device.IsValid());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,33 +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 "core/internal/internal_payload.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// The definition is necessary before C++17.
|
||||
constexpr int InternalPayload::kIndeterminateSize;
|
||||
|
||||
InternalPayload::InternalPayload(Payload payload)
|
||||
: payload_(std::move(payload)), payload_id_(payload_.GetId()) {}
|
||||
|
||||
Payload InternalPayload::ReleasePayload() { return std::move(payload_); }
|
||||
|
||||
Payload::Id InternalPayload::GetId() const { return payload_id_; }
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,106 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_INTERNAL_PAYLOAD_H_
|
||||
#define CORE_INTERNAL_INTERNAL_PAYLOAD_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/payload.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/exception.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Defines the operations layered atop a Payload, for use inside the
|
||||
// OfflineServiceController.
|
||||
//
|
||||
// <p>There will be an extension of this abstract base class per type of
|
||||
// Payload.
|
||||
class InternalPayload {
|
||||
public:
|
||||
static constexpr int kIndeterminateSize = -1;
|
||||
|
||||
explicit InternalPayload(Payload payload);
|
||||
virtual ~InternalPayload() = default;
|
||||
|
||||
Payload ReleasePayload();
|
||||
|
||||
Payload::Id GetId() const;
|
||||
|
||||
// Returns the PayloadType of the Payload to which this object is bound.
|
||||
//
|
||||
// <p>Note that this is supposed to return the type from the OfflineFrame
|
||||
// proto rather than what is already available via
|
||||
// Payload::getType().
|
||||
//
|
||||
// @return The PayloadType.
|
||||
virtual PayloadTransferFrame::PayloadHeader::PayloadType GetType() const = 0;
|
||||
|
||||
// Deduces the total size of the Payload to which this object is bound.
|
||||
//
|
||||
// @return The total size, or -1 if it cannot be deduced (for example, when
|
||||
// dealing with streaming data).
|
||||
virtual std::int64_t GetTotalSize() const = 0;
|
||||
|
||||
// Breaks off the next chunk from the Payload to which this object is bound.
|
||||
//
|
||||
// <p>Used when we have a complete Payload that we want to break into smaller
|
||||
// byte blobs for sending across a hard boundary (like the other side of
|
||||
// a Binder, or another device altogether).
|
||||
//
|
||||
// @param chunk_size The preferred size of the next chunk. Depending on
|
||||
// payload type, the provided size may be ignored.
|
||||
// @return The next chunk from the Payload, or null if we've reached the end.
|
||||
virtual ByteArray DetachNextChunk(int chunk_size) = 0;
|
||||
|
||||
// Adds the next chunk that comprises the Payload to which this object is
|
||||
// bound.
|
||||
//
|
||||
// <p>Used when we are trying to reconstruct a Payload that lives on the
|
||||
// other side of a hard boundary (like the other side of a Binder, or another
|
||||
// device altogether), one byte blob at a time.
|
||||
//
|
||||
// @param chunk The next chunk; this being null signals that this is the last
|
||||
// chunk, which will typically be used as a trigger to perform whatever state
|
||||
// cleanup may be required by the concrete implementation.
|
||||
virtual Exception AttachNextChunk(const ByteArray& chunk) = 0;
|
||||
|
||||
// Skips current stream pointer to the offset.
|
||||
//
|
||||
// Used when this is a resume outgoing transfer, so we want to skip
|
||||
// some data until the offset position.
|
||||
//
|
||||
// @return the offset really skipped
|
||||
virtual ExceptionOr<size_t> SkipToOffset(size_t offset) = 0;
|
||||
|
||||
// Cleans up any resources used by this Payload. Called when we're stopping
|
||||
// early, e.g. after being cancelled or having no more recipients left.
|
||||
virtual void Close() {}
|
||||
|
||||
protected:
|
||||
Payload payload_;
|
||||
// We're caching the payload ID here because the backing payload will be
|
||||
// released to another owner during the lifetime of an incoming
|
||||
// InternalPayload.
|
||||
Payload::Id payload_id_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_INTERNAL_PAYLOAD_H_
|
||||
@@ -1,350 +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 "core/internal/internal_payload_factory.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "core/payload.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/public/condition_variable.h"
|
||||
#include "platform/public/file.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/pipe.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
|
||||
class BytesInternalPayload : public InternalPayload {
|
||||
public:
|
||||
explicit BytesInternalPayload(Payload payload)
|
||||
: InternalPayload(std::move(payload)),
|
||||
total_size_(payload_.AsBytes().size()),
|
||||
detached_only_chunk_(false) {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::BYTES;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return total_size_; }
|
||||
|
||||
// Relinquishes ownership of the payload_; retrieves and returns the stored
|
||||
// ByteArray.
|
||||
ByteArray DetachNextChunk(int chunk_size) override {
|
||||
if (detached_only_chunk_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
detached_only_chunk_ = true;
|
||||
return std::move(payload_).AsBytes();
|
||||
}
|
||||
|
||||
// Does nothing.
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
ExceptionOr<size_t> SkipToOffset(size_t offset) override {
|
||||
NEARBY_LOGS(WARNING) << "Bytes payload does not support offsets";
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
private:
|
||||
// We're caching the total size here because the backing payload will be
|
||||
// moved to another owner during the lifetime of an incoming
|
||||
// InternalPayload.
|
||||
const std::int64_t total_size_;
|
||||
bool detached_only_chunk_;
|
||||
};
|
||||
|
||||
class OutgoingStreamInternalPayload : public InternalPayload {
|
||||
public:
|
||||
explicit OutgoingStreamInternalPayload(Payload payload)
|
||||
: InternalPayload(std::move(payload)) {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::STREAM;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return -1; }
|
||||
|
||||
ByteArray DetachNextChunk(int chunk_size) override {
|
||||
InputStream* input_stream = payload_.AsStream();
|
||||
if (!input_stream) return {};
|
||||
|
||||
ExceptionOr<ByteArray> bytes_read = input_stream->Read(chunk_size);
|
||||
if (!bytes_read.ok()) {
|
||||
input_stream->Close();
|
||||
return {};
|
||||
}
|
||||
|
||||
ByteArray scoped_bytes_read = std::move(bytes_read.result());
|
||||
|
||||
if (scoped_bytes_read.Empty()) {
|
||||
NEARBY_LOGS(INFO) << "No more data for outgoing payload " << this
|
||||
<< ", closing InputStream.";
|
||||
|
||||
input_stream->Close();
|
||||
return {};
|
||||
}
|
||||
|
||||
return scoped_bytes_read;
|
||||
}
|
||||
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
ExceptionOr<size_t> SkipToOffset(size_t offset) override {
|
||||
InputStream* stream = payload_.AsStream();
|
||||
if (stream == nullptr) return {Exception::kIo};
|
||||
|
||||
ExceptionOr<size_t> real_offset = stream->Skip(offset);
|
||||
if (real_offset.ok() && real_offset.GetResult() == offset) {
|
||||
return real_offset;
|
||||
}
|
||||
// Close the outgoing stream on any error
|
||||
stream->Close();
|
||||
if (!real_offset.ok()) {
|
||||
return real_offset;
|
||||
}
|
||||
NEARBY_LOGS(WARNING) << "Skip offset: " << real_offset.GetResult()
|
||||
<< ", expected offset: " << offset << " for payload "
|
||||
<< this;
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
void Close() override {
|
||||
// Ignore the potential Exception returned by close(), as a counterpart
|
||||
// to Java's closeQuietly().
|
||||
InputStream* stream = payload_.AsStream();
|
||||
if (stream) stream->Close();
|
||||
}
|
||||
};
|
||||
|
||||
class IncomingStreamInternalPayload : public InternalPayload {
|
||||
public:
|
||||
IncomingStreamInternalPayload(Payload payload, OutputStream& output_stream)
|
||||
: InternalPayload(std::move(payload)), output_stream_(&output_stream) {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::STREAM;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return -1; }
|
||||
|
||||
ByteArray DetachNextChunk(int chunk_size) override { return {}; }
|
||||
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
if (chunk.Empty()) {
|
||||
NEARBY_LOGS(INFO) << "Received null last chunk for incoming payload "
|
||||
<< this << ", closing OutputStream.";
|
||||
output_stream_->Close();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
return output_stream_->Write(chunk);
|
||||
}
|
||||
|
||||
ExceptionOr<size_t> SkipToOffset(size_t offset) override {
|
||||
NEARBY_LOGS(WARNING) << "Cannot skip offset for an incoming Payload "
|
||||
<< this;
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
void Close() override { output_stream_->Close(); }
|
||||
|
||||
private:
|
||||
OutputStream* output_stream_;
|
||||
};
|
||||
|
||||
class OutgoingFileInternalPayload : public InternalPayload {
|
||||
public:
|
||||
explicit OutgoingFileInternalPayload(Payload payload)
|
||||
: InternalPayload(std::move(payload)),
|
||||
total_size_{payload_.AsFile()->GetTotalSize()} {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::FILE;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return total_size_; }
|
||||
|
||||
ByteArray DetachNextChunk(int chunk_size) override {
|
||||
InputFile* file = payload_.AsFile();
|
||||
if (!file) return {};
|
||||
|
||||
ExceptionOr<ByteArray> bytes_read = file->Read(chunk_size);
|
||||
if (!bytes_read.ok()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ByteArray bytes = std::move(bytes_read.result());
|
||||
|
||||
if (bytes.Empty()) {
|
||||
// No more data for outgoing payload.
|
||||
|
||||
file->Close();
|
||||
return {};
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
ExceptionOr<size_t> SkipToOffset(size_t offset) override {
|
||||
NEARBY_LOGS(INFO) << "SkipToOffset " << offset;
|
||||
InputFile* file = payload_.AsFile();
|
||||
if (!file) {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
ExceptionOr<size_t> real_offset = file->Skip(offset);
|
||||
if (real_offset.ok() && real_offset.GetResult() == offset) {
|
||||
return real_offset;
|
||||
}
|
||||
// Close the outgoing file on any error
|
||||
file->Close();
|
||||
if (!real_offset.ok()) {
|
||||
return real_offset;
|
||||
}
|
||||
NEARBY_LOGS(WARNING) << "Skip offset: " << real_offset.GetResult()
|
||||
<< ", expected offset: " << offset
|
||||
<< " for file payload " << this;
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
void Close() override {
|
||||
InputFile* file = payload_.AsFile();
|
||||
if (file) file->Close();
|
||||
}
|
||||
|
||||
private:
|
||||
std::int64_t total_size_;
|
||||
};
|
||||
|
||||
class IncomingFileInternalPayload : public InternalPayload {
|
||||
public:
|
||||
IncomingFileInternalPayload(Payload payload, OutputFile output_file,
|
||||
std::int64_t total_size)
|
||||
: InternalPayload(std::move(payload)),
|
||||
output_file_(std::move(output_file)),
|
||||
total_size_(total_size) {}
|
||||
|
||||
PayloadTransferFrame::PayloadHeader::PayloadType GetType() const override {
|
||||
return PayloadTransferFrame::PayloadHeader::FILE;
|
||||
}
|
||||
|
||||
std::int64_t GetTotalSize() const override { return total_size_; }
|
||||
|
||||
ByteArray DetachNextChunk(int chunk_size) override { return {}; }
|
||||
|
||||
Exception AttachNextChunk(const ByteArray& chunk) override {
|
||||
if (chunk.Empty()) {
|
||||
// Received null last chunk for incoming payload.
|
||||
output_file_.Close();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
return output_file_.Write(chunk);
|
||||
}
|
||||
|
||||
ExceptionOr<size_t> SkipToOffset(size_t offset) override {
|
||||
NEARBY_LOGS(WARNING) << "Cannot skip offset for an incoming file Payload "
|
||||
<< this;
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
void Close() override { output_file_.Close(); }
|
||||
|
||||
private:
|
||||
OutputFile output_file_;
|
||||
const std::int64_t total_size_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<InternalPayload> CreateOutgoingInternalPayload(
|
||||
Payload payload) {
|
||||
switch (payload.GetType()) {
|
||||
case Payload::Type::kBytes:
|
||||
return absl::make_unique<BytesInternalPayload>(std::move(payload));
|
||||
|
||||
case Payload::Type::kFile: {
|
||||
InputFile* file = payload.AsFile();
|
||||
const PayloadId file_payload_id = file ? file->GetPayloadId() : 0;
|
||||
const PayloadId payload_id = payload.GetId();
|
||||
CHECK(payload_id == file_payload_id);
|
||||
return absl::make_unique<OutgoingFileInternalPayload>(std::move(payload));
|
||||
}
|
||||
|
||||
case Payload::Type::kStream:
|
||||
return absl::make_unique<OutgoingStreamInternalPayload>(
|
||||
std::move(payload));
|
||||
|
||||
default:
|
||||
DCHECK(false); // This should never happen.
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalPayload> CreateIncomingInternalPayload(
|
||||
const PayloadTransferFrame& frame) {
|
||||
if (frame.packet_type() != PayloadTransferFrame::DATA) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const Payload::Id payload_id = frame.payload_header().id();
|
||||
switch (frame.payload_header().type()) {
|
||||
case PayloadTransferFrame::PayloadHeader::BYTES: {
|
||||
return absl::make_unique<BytesInternalPayload>(
|
||||
Payload(payload_id, ByteArray(frame.payload_chunk().body())));
|
||||
}
|
||||
|
||||
case PayloadTransferFrame::PayloadHeader::STREAM: {
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
|
||||
return absl::make_unique<IncomingStreamInternalPayload>(
|
||||
Payload(payload_id,
|
||||
[pipe]() -> InputStream& {
|
||||
return pipe->GetInputStream(); // NOLINT
|
||||
}),
|
||||
pipe->GetOutputStream());
|
||||
}
|
||||
|
||||
case PayloadTransferFrame::PayloadHeader::FILE: {
|
||||
std::int64_t total_size = frame.payload_header().total_size();
|
||||
return absl::make_unique<IncomingFileInternalPayload>(
|
||||
Payload(payload_id, InputFile(payload_id, total_size)),
|
||||
OutputFile(payload_id), total_size);
|
||||
}
|
||||
default:
|
||||
DCHECK(false); // This should never happen.
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,37 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
|
||||
#define CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
|
||||
|
||||
#include "core/internal/internal_payload.h"
|
||||
#include "core/payload.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Creates an InternalPayload representing an outgoing Payload.
|
||||
std::unique_ptr<InternalPayload> CreateOutgoingInternalPayload(Payload payload);
|
||||
|
||||
// Creates an InternalPayload representing an incoming Payload from a remote
|
||||
// endpoint.
|
||||
std::unique_ptr<InternalPayload> CreateIncomingInternalPayload(
|
||||
const PayloadTransferFrame& frame);
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_INTERNAL_PAYLOAD_FACTORY_H_
|
||||
@@ -1,183 +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 "core/internal/internal_payload_factory.h"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "connections/implementation/proto/offline_wire_formats.pb.h"
|
||||
#include "core/internal/offline_frames.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/pipe.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr char kText[] = "data chunk";
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromBytePayload) {
|
||||
ByteArray data(kText);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateOutgoingInternalPayload(Payload{data});
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray(kText));
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromStreamPayload) {
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateOutgoingInternalPayload(Payload{[pipe]() -> InputStream& {
|
||||
return pipe->GetInputStream(); // NOLINT
|
||||
}});
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_NE(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray());
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromFilePayload) {
|
||||
Payload::Id payload_id = Payload::GenerateId();
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateOutgoingInternalPayload(
|
||||
Payload{payload_id, InputFile(payload_id, 512)});
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_NE(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray());
|
||||
EXPECT_EQ(payload.GetId(), payload_id);
|
||||
EXPECT_EQ(payload.AsFile()->GetPayloadId(), payload_id);
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromByteMessage) {
|
||||
PayloadTransferFrame frame;
|
||||
frame.set_packet_type(PayloadTransferFrame::DATA);
|
||||
std::int64_t payload_chunk_offset = 0;
|
||||
ByteArray data(kText);
|
||||
PayloadTransferFrame::PayloadChunk payload_chunk;
|
||||
payload_chunk.set_offset(payload_chunk_offset);
|
||||
payload_chunk.set_body(std::string(std::move(data)));
|
||||
payload_chunk.set_flags(0);
|
||||
auto& header = *frame.mutable_payload_header();
|
||||
header.set_type(PayloadTransferFrame::PayloadHeader::BYTES);
|
||||
header.set_id(12345);
|
||||
header.set_total_size(512);
|
||||
*frame.mutable_payload_chunk() = std::move(payload_chunk);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateIncomingInternalPayload(frame);
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray(kText));
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromStreamMessage) {
|
||||
PayloadTransferFrame frame;
|
||||
frame.set_packet_type(PayloadTransferFrame::DATA);
|
||||
auto& header = *frame.mutable_payload_header();
|
||||
header.set_type(PayloadTransferFrame::PayloadHeader::STREAM);
|
||||
header.set_id(12345);
|
||||
header.set_total_size(0);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateIncomingInternalPayload(frame);
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_EQ(payload.AsFile(), nullptr);
|
||||
EXPECT_NE(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray());
|
||||
EXPECT_EQ(payload.GetType(), Payload::Type::kStream);
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest, CanCreateIternalPayloadFromFileMessage) {
|
||||
PayloadTransferFrame frame;
|
||||
frame.set_packet_type(PayloadTransferFrame::DATA);
|
||||
auto& header = *frame.mutable_payload_header();
|
||||
header.set_type(PayloadTransferFrame::PayloadHeader::FILE);
|
||||
header.set_id(12345);
|
||||
header.set_total_size(512);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateIncomingInternalPayload(frame);
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
Payload payload = internal_payload->ReleasePayload();
|
||||
EXPECT_NE(payload.AsFile(), nullptr);
|
||||
EXPECT_EQ(payload.AsStream(), nullptr);
|
||||
EXPECT_EQ(payload.AsBytes(), ByteArray());
|
||||
EXPECT_EQ(payload.GetType(), Payload::Type::kFile);
|
||||
EXPECT_EQ(payload.GetId(), payload.AsFile()->GetPayloadId());
|
||||
}
|
||||
|
||||
void CreateFileWithContents(Payload::Id payload_id, const ByteArray& contents) {
|
||||
OutputFile file(payload_id);
|
||||
EXPECT_TRUE(file.Write(contents).Ok());
|
||||
EXPECT_TRUE(file.Close().Ok());
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest,
|
||||
SkipToOffset_FilePayloadValidOffset_SkipsOffset) {
|
||||
ByteArray contents("0123456789");
|
||||
constexpr size_t kOffset = 4;
|
||||
size_t size_after_skip = contents.size() - kOffset;
|
||||
Payload::Id payload_id = Payload::GenerateId();
|
||||
CreateFileWithContents(payload_id, contents);
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateOutgoingInternalPayload(
|
||||
Payload{payload_id, InputFile(payload_id, contents.size())});
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
|
||||
ExceptionOr<size_t> result = internal_payload->SkipToOffset(kOffset);
|
||||
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.GetResult(), kOffset);
|
||||
EXPECT_EQ(internal_payload->GetTotalSize(), contents.size());
|
||||
ByteArray contents_after_skip =
|
||||
internal_payload->DetachNextChunk(size_after_skip);
|
||||
EXPECT_EQ(contents_after_skip, ByteArray("456789"));
|
||||
}
|
||||
|
||||
TEST(InternalPayloadFActoryTest,
|
||||
SkipToOffset_StreamPayloadValidOffset_SkipsOffset) {
|
||||
ByteArray contents("0123456789");
|
||||
constexpr size_t kOffset = 6;
|
||||
auto pipe = std::make_shared<Pipe>();
|
||||
std::unique_ptr<InternalPayload> internal_payload =
|
||||
CreateOutgoingInternalPayload(Payload{[pipe]() -> InputStream& {
|
||||
return pipe->GetInputStream(); // NOLINT
|
||||
}});
|
||||
EXPECT_NE(internal_payload, nullptr);
|
||||
pipe->GetOutputStream().Write(contents);
|
||||
|
||||
ExceptionOr<size_t> result = internal_payload->SkipToOffset(kOffset);
|
||||
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.GetResult(), kOffset);
|
||||
EXPECT_EQ(internal_payload->GetTotalSize(), -1);
|
||||
ByteArray contents_after_skip = internal_payload->DetachNextChunk(512);
|
||||
EXPECT_EQ(contents_after_skip, ByteArray("6789"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,135 +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.
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "mediums",
|
||||
srcs = [
|
||||
"ble.cc",
|
||||
"bluetooth_classic.cc",
|
||||
"bluetooth_radio.cc",
|
||||
"mediums.cc",
|
||||
"uuid.cc",
|
||||
"webrtc.cc",
|
||||
"wifi_lan.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"ble.h",
|
||||
"bluetooth_classic.h",
|
||||
"bluetooth_radio.h",
|
||||
"lost_entity_tracker.h",
|
||||
"mediums.h",
|
||||
"uuid.h",
|
||||
"webrtc.h",
|
||||
"wifi_lan.h",
|
||||
],
|
||||
visibility = [
|
||||
"//cpp/core/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":utils",
|
||||
"//connections/implementation/proto:offline_wire_formats_portable_proto",
|
||||
"//cpp/core:core_types",
|
||||
"//cpp/core/internal/mediums/ble_v2",
|
||||
"//cpp/core/internal/mediums/webrtc",
|
||||
"//cpp/core/internal/mediums/webrtc:data_types",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/base:cancellation_flag",
|
||||
"//cpp/platform/public:comm",
|
||||
"//cpp/platform/public:logging",
|
||||
"//cpp/platform/public:types",
|
||||
"//proto/mediums:web_rtc_signaling_frames_cc_proto",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/functional:bind_front",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_absl//absl/time",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "utils",
|
||||
srcs = [
|
||||
"utils.cc",
|
||||
"webrtc_peer_id.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"utils.h",
|
||||
"webrtc_peer_id.h",
|
||||
"webrtc_socket.h",
|
||||
],
|
||||
visibility = [
|
||||
"//cpp/core/internal:__pkg__",
|
||||
"//cpp/core/internal/mediums:__pkg__",
|
||||
"//cpp/core/internal/mediums/webrtc:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//connections/implementation/proto:offline_wire_formats_portable_proto",
|
||||
"//cpp/core/internal/mediums/webrtc:data_types",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/public:types",
|
||||
"@com_google_absl//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "core_internal_mediums_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"ble_test.cc",
|
||||
"bluetooth_classic_test.cc",
|
||||
"bluetooth_radio_test.cc",
|
||||
"lost_entity_tracker_test.cc",
|
||||
"uuid_test.cc",
|
||||
"wifi_lan_test.cc",
|
||||
],
|
||||
shard_count = 16,
|
||||
deps = [
|
||||
":mediums",
|
||||
":utils",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/base:test_util",
|
||||
"//cpp/platform/impl/g3", # build_cleaner: keep
|
||||
"//cpp/platform/public:comm",
|
||||
"//cpp/platform/public:types",
|
||||
"@com_google_googletest//:gtest_main","@com_github_protobuf_matchers//protobuf-matchers:protobuf-matchers",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "core_internal_mediums_webrtc_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"webrtc_peer_id_test.cc",
|
||||
"webrtc_test.cc",
|
||||
],
|
||||
shard_count = 16,
|
||||
tags = [
|
||||
"notsan", # NOTE(b/139734036): known data race in usrsctplib.
|
||||
"requires-net:external",
|
||||
],
|
||||
deps = [
|
||||
":mediums",
|
||||
":utils",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/base:test_util",
|
||||
"//cpp/platform/impl/g3", # build_cleaner: keep
|
||||
"//cpp/platform/public:types",
|
||||
"@com_google_googletest//:gtest_main","@com_github_protobuf_matchers//protobuf-matchers:protobuf-matchers",
|
||||
],
|
||||
)
|
||||
@@ -1,113 +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.
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "mediums",
|
||||
srcs = [
|
||||
"ble.cc",
|
||||
"bluetooth_classic.cc",
|
||||
"bluetooth_radio.cc",
|
||||
"mediums.cc",
|
||||
"uuid.cc",
|
||||
"webrtc_stub.cc",
|
||||
"wifi_lan.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"ble.h",
|
||||
"bluetooth_classic.h",
|
||||
"bluetooth_radio.h",
|
||||
"lost_entity_tracker.h",
|
||||
"mediums.h",
|
||||
"uuid.h",
|
||||
"webrtc_stub.h",
|
||||
"wifi_lan.h",
|
||||
],
|
||||
compatible_with = ["//buildenv/target:non_prod"],
|
||||
defines = ["NO_WEBRTC"],
|
||||
visibility = [
|
||||
"//third_party/nearby/cpp/core/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":utils",
|
||||
"//third_party/absl/container:flat_hash_map",
|
||||
"//third_party/absl/container:flat_hash_set",
|
||||
"//third_party/absl/functional:bind_front",
|
||||
"//third_party/absl/strings",
|
||||
"//third_party/absl/strings:str_format",
|
||||
"//third_party/absl/time",
|
||||
"//third_party/nearby/connections/implementation/proto:offline_wire_formats_portable_proto",
|
||||
"//third_party/nearby/cpp/core:core_types",
|
||||
"//third_party/nearby/cpp/core/internal/mediums/ble_v2",
|
||||
"//third_party/nearby/cpp/platform/base",
|
||||
"//third_party/nearby/cpp/platform/base:cancellation_flag",
|
||||
"//third_party/nearby/cpp/platform/public:comm",
|
||||
"//third_party/nearby/cpp/platform/public:logging",
|
||||
"//third_party/nearby/cpp/platform/public:types",
|
||||
"//third_party/nearby/proto/mediums:web_rtc_signaling_frames_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "utils",
|
||||
srcs = [
|
||||
"utils.cc",
|
||||
"webrtc_peer_id.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"utils.h",
|
||||
"webrtc_peer_id.h",
|
||||
"webrtc_socket_stub.h",
|
||||
],
|
||||
compatible_with = ["//buildenv/target:non_prod"],
|
||||
defines = ["NO_WEBRTC"],
|
||||
visibility = [
|
||||
"//third_party/nearby/cpp/core/internal:__pkg__",
|
||||
"//third_party/nearby/cpp/core/internal/mediums:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//third_party/absl/strings",
|
||||
"//third_party/nearby/connections/implementation/proto:offline_wire_formats_portable_proto",
|
||||
"//third_party/nearby/cpp/platform/base",
|
||||
"//third_party/nearby/cpp/platform/public:types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "core_internal_mediums_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"ble_test.cc",
|
||||
"bluetooth_classic_test.cc",
|
||||
"bluetooth_radio_test.cc",
|
||||
"lost_entity_tracker_test.cc",
|
||||
"uuid_test.cc",
|
||||
"webrtc_peer_id_test.cc",
|
||||
"wifi_lan_test.cc",
|
||||
],
|
||||
defines = ["NO_WEBRTC"],
|
||||
shard_count = 16,
|
||||
deps = [
|
||||
":mediums",
|
||||
":utils",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//third_party/absl/strings",
|
||||
"//third_party/absl/time",
|
||||
"//third_party/nearby/cpp/platform/base",
|
||||
"//third_party/nearby/cpp/platform/base:test_util",
|
||||
"//third_party/nearby/cpp/platform/impl/g3", # build_cleaner: keep
|
||||
"//third_party/nearby/cpp/platform/public:comm",
|
||||
"//third_party/nearby/cpp/platform/public:types",
|
||||
],
|
||||
)
|
||||
@@ -1,361 +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 "core/internal/mediums/ble.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "core/internal/mediums/ble_v2/ble_advertisement.h"
|
||||
#include "core/internal/mediums/utils.h"
|
||||
#include "platform/base/prng.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
ByteArray Ble::GenerateHash(const std::string& source, size_t size) {
|
||||
return Utils::Sha256Hash(source, size);
|
||||
}
|
||||
|
||||
ByteArray Ble::GenerateDeviceToken() {
|
||||
return Utils::Sha256Hash(std::to_string(Prng().NextUint32()),
|
||||
mediums::BleAdvertisement::kDeviceTokenLength);
|
||||
}
|
||||
|
||||
Ble::Ble(BluetoothRadio& radio) : radio_(radio) {}
|
||||
|
||||
bool Ble::IsAvailable() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAvailableLocked();
|
||||
}
|
||||
|
||||
bool Ble::IsAvailableLocked() const { return medium_.IsValid(); }
|
||||
|
||||
bool Ble::StartAdvertising(const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes,
|
||||
const std::string& fast_advertisement_service_uuid) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (advertisement_bytes.Empty()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Refusing to turn on BLE advertising. Empty advertisement data.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (advertisement_bytes.size() > kMaxAdvertisementLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start BLE advertising because the advertisement "
|
||||
"was too long. Expected at most %d bytes but received %d.",
|
||||
kMaxAdvertisementLength, advertisement_bytes.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAdvertisingLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Failed to BLE advertise because we're already advertising.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Can't start BLE scanning because Bluetooth was never turned on";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOGS(INFO) << "Can't turn on BLE advertising. BLE is not available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO) << "Turning on BLE advertising (advertisement size="
|
||||
<< advertisement_bytes.size() << ")"
|
||||
<< ", service id=" << service_id
|
||||
<< ", fast advertisement service uuid="
|
||||
<< fast_advertisement_service_uuid;
|
||||
|
||||
// Wrap the connections advertisement to the medium advertisement.
|
||||
const bool fast_advertisement = !fast_advertisement_service_uuid.empty();
|
||||
ByteArray service_id_hash{GenerateHash(
|
||||
service_id, mediums::BleAdvertisement::kServiceIdHashLength)};
|
||||
ByteArray medium_advertisement_bytes{mediums::BleAdvertisement{
|
||||
mediums::BleAdvertisement::Version::kV2,
|
||||
mediums::BleAdvertisement::SocketVersion::kV2,
|
||||
fast_advertisement ? ByteArray{} : service_id_hash, advertisement_bytes,
|
||||
GenerateDeviceToken()}};
|
||||
if (medium_advertisement_bytes.Empty()) {
|
||||
NEARBY_LOGS(INFO) << "Failed to BLE advertise because we could not "
|
||||
"create a medium advertisement.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartAdvertising(service_id, medium_advertisement_bytes,
|
||||
fast_advertisement_service_uuid)) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< "Failed to turn on BLE advertising with advertisement bytes="
|
||||
<< absl::BytesToHexString(advertisement_bytes.data())
|
||||
<< ", size=" << advertisement_bytes.size()
|
||||
<< ", fast advertisement service uuid="
|
||||
<< fast_advertisement_service_uuid;
|
||||
return false;
|
||||
}
|
||||
|
||||
advertising_info_.Add(service_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ble::StopAdvertising(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAdvertisingLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO) << "Can't turn off BLE advertising; it is already off";
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO) << "Turned off BLE advertising with service id="
|
||||
<< service_id;
|
||||
bool ret = medium_.StopAdvertising(service_id);
|
||||
// Reset our bundle of advertising state to mark that we're no longer
|
||||
// advertising.
|
||||
advertising_info_.Remove(service_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Ble::IsAdvertising(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAdvertisingLocked(service_id);
|
||||
}
|
||||
|
||||
bool Ble::IsAdvertisingLocked(const std::string& service_id) {
|
||||
return advertising_info_.Existed(service_id);
|
||||
}
|
||||
|
||||
bool Ble::StartScanning(const std::string& service_id,
|
||||
const std::string& fast_advertisement_service_uuid,
|
||||
DiscoveredPeripheralCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
discovered_peripheral_callback_ = std::move(callback);
|
||||
|
||||
if (service_id.empty()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Refusing to start BLE scanning with empty service id.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsScanningLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO) << "Refusing to start scan of BLE peripherals because "
|
||||
"another scanning is already in-progress.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Can't start BLE scanning because Bluetooth was never turned on";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Can't scan BLE peripherals because BLE isn't available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartScanning(
|
||||
service_id, fast_advertisement_service_uuid,
|
||||
{
|
||||
.peripheral_discovered_cb =
|
||||
[this](BlePeripheral& peripheral,
|
||||
const std::string& service_id,
|
||||
const ByteArray& medium_advertisement_bytes,
|
||||
bool fast_advertisement) {
|
||||
// Don't bother trying to parse zero byte advertisements.
|
||||
if (medium_advertisement_bytes.size() == 0) {
|
||||
NEARBY_LOGS(INFO) << "Skipping zero byte advertisement "
|
||||
<< "with service_id: " << service_id;
|
||||
return;
|
||||
}
|
||||
// Unwrap connection BleAdvertisement from medium
|
||||
// BleAdvertisement.
|
||||
auto connection_advertisement_bytes =
|
||||
UnwrapAdvertisementBytes(medium_advertisement_bytes);
|
||||
discovered_peripheral_callback_.peripheral_discovered_cb(
|
||||
peripheral, service_id, connection_advertisement_bytes,
|
||||
fast_advertisement);
|
||||
},
|
||||
.peripheral_lost_cb =
|
||||
[this](BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
discovered_peripheral_callback_.peripheral_lost_cb(
|
||||
peripheral, service_id);
|
||||
},
|
||||
})) {
|
||||
NEARBY_LOGS(INFO) << "Failed to start scan of BLE services.";
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO) << "Turned on BLE scanning with service id=" << service_id;
|
||||
// Mark the fact that we're currently performing a BLE discovering.
|
||||
scanning_info_.Add(service_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ble::StopScanning(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsScanningLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO) << "Can't turn off BLE sacanning because we never "
|
||||
"started scanning.";
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Turned off BLE scanning with service id=%s",
|
||||
service_id.c_str());
|
||||
bool ret = medium_.StopScanning(service_id);
|
||||
scanning_info_.Clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Ble::IsScanning(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsScanningLocked(service_id);
|
||||
}
|
||||
|
||||
bool Ble::IsScanningLocked(const std::string& service_id) {
|
||||
return scanning_info_.Existed(service_id);
|
||||
}
|
||||
|
||||
bool Ble::StartAcceptingConnections(const std::string& service_id,
|
||||
AcceptedConnectionCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_id.empty()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Refusing to start accepting BLE connections with empty service id.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Refusing to start accepting BLE connections for " << service_id
|
||||
<< " because another BLE peripheral socket is already in-progress.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for "
|
||||
<< service_id << " because Bluetooth isn't enabled.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for "
|
||||
<< service_id << " because BLE isn't available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartAcceptingConnections(service_id, callback)) {
|
||||
NEARBY_LOGS(INFO) << "Failed to accept connections callback for "
|
||||
<< service_id << " .";
|
||||
return false;
|
||||
}
|
||||
|
||||
accepting_connections_info_.Add(service_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ble::StopAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Can't stop accepting BLE connections because it was never started.";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ret = medium_.StopAcceptingConnections(service_id);
|
||||
// Reset our bundle of accepting connections state to mark that we're no
|
||||
// longer accepting connections.
|
||||
accepting_connections_info_.Remove(service_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool Ble::IsAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAcceptingConnectionsLocked(service_id);
|
||||
}
|
||||
|
||||
bool Ble::IsAcceptingConnectionsLocked(const std::string& service_id) {
|
||||
return accepting_connections_info_.Existed(service_id);
|
||||
}
|
||||
|
||||
BleSocket Ble::Connect(BlePeripheral& peripheral, const std::string& service_id,
|
||||
CancellationFlag* cancellation_flag) {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOGS(INFO) << "BLE::Connect: service=" << &peripheral;
|
||||
// Socket to return. To allow for NRVO to work, it has to be a single object.
|
||||
BleSocket socket;
|
||||
|
||||
if (service_id.empty()) {
|
||||
NEARBY_LOGS(INFO) << "Refusing to create BLE socket with empty service_id.";
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOGS(INFO) << "Can't create client BLE socket to " << &peripheral
|
||||
<< " because Bluetooth isn't enabled.";
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOGS(INFO) << "Can't create client BLE socket [service_id="
|
||||
<< service_id << "]; BLE isn't available.";
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (cancellation_flag->Cancelled()) {
|
||||
NEARBY_LOGS(INFO) << "Can't create client BLE socket due to cancel.";
|
||||
return socket;
|
||||
}
|
||||
|
||||
socket = medium_.Connect(peripheral, service_id, cancellation_flag);
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOGS(INFO) << "Failed to Connect via BLE [service=" << service_id
|
||||
<< "]";
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
ByteArray Ble::UnwrapAdvertisementBytes(
|
||||
const ByteArray& medium_advertisement_data) {
|
||||
mediums::BleAdvertisement medium_ble_advertisement{medium_advertisement_data};
|
||||
if (!medium_ble_advertisement.IsValid()) {
|
||||
return ByteArray{};
|
||||
}
|
||||
|
||||
return medium_ble_advertisement.GetData();
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,188 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "core/listeners.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/cancellation_flag.h"
|
||||
#include "platform/public/ble.h"
|
||||
#include "platform/public/multi_thread_executor.h"
|
||||
#include "platform/public/mutex.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class Ble {
|
||||
public:
|
||||
using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback;
|
||||
using AcceptedConnectionCallback = BleMedium::AcceptedConnectionCallback;
|
||||
|
||||
explicit Ble(BluetoothRadio& bluetooth_radio);
|
||||
~Ble() = default;
|
||||
|
||||
// Returns true, if Ble communications are supported by a platform.
|
||||
bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Sets custom advertisement data, and then enables Ble advertising.
|
||||
// Returns true, if data is successfully set, and false otherwise.
|
||||
bool StartAdvertising(const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes,
|
||||
const std::string& fast_advertisement_service_uuid)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Disables Ble advertising.
|
||||
bool StopAdvertising(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool IsAdvertising(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Enables Ble scanning mode. Will report any discoverable peripherals in
|
||||
// range through a callback. Returns true, if scanning mode was enabled,
|
||||
// false otherwise.
|
||||
bool StartScanning(const std::string& service_id,
|
||||
const std::string& fast_advertisement_service_uuid,
|
||||
DiscoveredPeripheralCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Disables Ble discovery mode.
|
||||
bool StopScanning(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool IsScanning(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Starts a worker thread, creates a Ble socket, associates it with a
|
||||
// service id.
|
||||
bool StartAcceptingConnections(const std::string& service_id,
|
||||
AcceptedConnectionCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Closes socket corresponding to a service id.
|
||||
bool StopAcceptingConnections(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool IsAcceptingConnections(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if this object owns a valid platform implementation.
|
||||
bool IsMediumValid() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
MutexLock lock(&mutex_);
|
||||
return medium_.IsValid();
|
||||
}
|
||||
|
||||
// Returns true if this object has a valid BluetoothAdapter reference.
|
||||
bool IsAdapterValid() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
MutexLock lock(&mutex_);
|
||||
return adapter_.IsValid();
|
||||
}
|
||||
|
||||
// Establishes connection to Ble peripheral that was might be started on
|
||||
// another peripheral with StartAcceptingConnections() using the same
|
||||
// service_id. Blocks until connection is established, or server-side is
|
||||
// terminated. Returns socket instance. On success, BleSocket.IsValid() return
|
||||
// true.
|
||||
BleSocket Connect(BlePeripheral& peripheral, const std::string& service_id,
|
||||
CancellationFlag* cancellation_flag)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
struct AdvertisingInfo {
|
||||
bool Empty() const { return service_ids.empty(); }
|
||||
void Clear() { service_ids.clear(); }
|
||||
void Add(const std::string& service_id) { service_ids.emplace(service_id); }
|
||||
void Remove(const std::string& service_id) {
|
||||
service_ids.erase(service_id);
|
||||
}
|
||||
bool Existed(const std::string& service_id) const {
|
||||
return service_ids.contains(service_id);
|
||||
}
|
||||
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
struct ScanningInfo {
|
||||
bool Empty() const { return service_ids.empty(); }
|
||||
void Clear() { service_ids.clear(); }
|
||||
void Add(const std::string& service_id) { service_ids.emplace(service_id); }
|
||||
void Remove(const std::string& service_id) {
|
||||
service_ids.erase(service_id);
|
||||
}
|
||||
bool Existed(const std::string& service_id) const {
|
||||
return service_ids.contains(service_id);
|
||||
}
|
||||
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
struct AcceptingConnectionsInfo {
|
||||
bool Empty() const { return service_ids.empty(); }
|
||||
void Clear() { service_ids.clear(); }
|
||||
void Add(const std::string& service_id) { service_ids.emplace(service_id); }
|
||||
void Remove(const std::string& service_id) {
|
||||
service_ids.erase(service_id);
|
||||
}
|
||||
bool Existed(const std::string& service_id) const {
|
||||
return service_ids.contains(service_id);
|
||||
}
|
||||
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
static constexpr int kMaxAdvertisementLength = 512;
|
||||
|
||||
static ByteArray GenerateHash(const std::string& source, size_t size);
|
||||
static ByteArray GenerateDeviceToken();
|
||||
|
||||
// Same as IsAvailable(), but must be called with mutex_ held.
|
||||
bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Same as IsAdvertising(), but must be called with mutex_ held.
|
||||
bool IsAdvertisingLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Same as IsDiscovering(), but must be called with mutex_ held.
|
||||
bool IsScanningLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Same as IsAcceptingConnections(), but must be called with mutex_ held.
|
||||
bool IsAcceptingConnectionsLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Extract connection advertisement from medium advertisement.
|
||||
ByteArray UnwrapAdvertisementBytes(
|
||||
const ByteArray& medium_advertisement_data);
|
||||
|
||||
mutable Mutex mutex_;
|
||||
BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_);
|
||||
BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_){
|
||||
radio_.GetBluetoothAdapter()};
|
||||
BleMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_};
|
||||
AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_);
|
||||
ScanningInfo scanning_info_ ABSL_GUARDED_BY(mutex_);
|
||||
DiscoveredPeripheralCallback discovered_peripheral_callback_;
|
||||
AcceptingConnectionsInfo accepting_connections_info_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_H_
|
||||
@@ -1,264 +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 "core/internal/mediums/ble.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "platform/base/medium_environment.h"
|
||||
#include "platform/public/ble.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
using FeatureFlags = FeatureFlags::Flags;
|
||||
|
||||
constexpr FeatureFlags kTestCases[] = {
|
||||
FeatureFlags{
|
||||
.enable_cancellation_flag = true,
|
||||
},
|
||||
FeatureFlags{
|
||||
.enable_cancellation_flag = false,
|
||||
},
|
||||
};
|
||||
|
||||
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"};
|
||||
|
||||
class BleTest : public ::testing::TestWithParam<FeatureFlags> {
|
||||
protected:
|
||||
using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback;
|
||||
|
||||
BleTest() { env_.Stop(); }
|
||||
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
};
|
||||
|
||||
TEST_P(BleTest, CanStartAcceptingConnectionsAndConnect) {
|
||||
FeatureFlags feature_flags = GetParam();
|
||||
env_.SetFeatureFlags(feature_flags);
|
||||
env_.Start();
|
||||
BluetoothRadio radio_a;
|
||||
BluetoothRadio radio_b;
|
||||
Ble ble_a{radio_a};
|
||||
Ble ble_b{radio_b};
|
||||
radio_a.Enable();
|
||||
radio_b.Enable();
|
||||
std::string service_id(kServiceID);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
|
||||
CountDownLatch found_latch(1);
|
||||
CountDownLatch accept_latch(1);
|
||||
|
||||
ble_a.StartAdvertising(service_id, advertisement_bytes,
|
||||
fast_advertisement_service_uuid);
|
||||
ble_a.StartAcceptingConnections(
|
||||
service_id,
|
||||
{
|
||||
.accepted_cb = [&accept_latch](
|
||||
BleSocket socket,
|
||||
const std::string&) { accept_latch.CountDown(); },
|
||||
});
|
||||
BlePeripheral discovered_peripheral;
|
||||
ble_b.StartScanning(
|
||||
service_id, fast_advertisement_service_uuid,
|
||||
{
|
||||
.peripheral_discovered_cb =
|
||||
[&found_latch, &discovered_peripheral](
|
||||
BlePeripheral& peripheral, const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes,
|
||||
bool fast_advertisement) {
|
||||
discovered_peripheral = peripheral;
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Discovered peripheral=%p [impl=%p], fast advertisement=%d",
|
||||
&peripheral, &peripheral.GetImpl(), fast_advertisement);
|
||||
found_latch.CountDown();
|
||||
},
|
||||
});
|
||||
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
ASSERT_TRUE(discovered_peripheral.IsValid());
|
||||
CancellationFlag flag;
|
||||
BleSocket socket = ble_b.Connect(discovered_peripheral, service_id, &flag);
|
||||
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(socket.IsValid());
|
||||
ble_b.StopScanning(service_id);
|
||||
ble_a.StopAdvertising(service_id);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_P(BleTest, CanCancelConnect) {
|
||||
FeatureFlags feature_flags = GetParam();
|
||||
env_.SetFeatureFlags(feature_flags);
|
||||
env_.Start();
|
||||
BluetoothRadio radio_a;
|
||||
BluetoothRadio radio_b;
|
||||
Ble ble_a{radio_a};
|
||||
Ble ble_b{radio_b};
|
||||
radio_a.Enable();
|
||||
radio_b.Enable();
|
||||
std::string service_id(kServiceID);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
|
||||
CountDownLatch found_latch(1);
|
||||
CountDownLatch accept_latch(1);
|
||||
|
||||
ble_a.StartAdvertising(service_id, advertisement_bytes,
|
||||
fast_advertisement_service_uuid);
|
||||
ble_a.StartAcceptingConnections(
|
||||
service_id,
|
||||
{
|
||||
.accepted_cb = [&accept_latch](
|
||||
BleSocket socket,
|
||||
const std::string&) { accept_latch.CountDown(); },
|
||||
});
|
||||
BlePeripheral discovered_peripheral;
|
||||
ble_b.StartScanning(
|
||||
service_id, fast_advertisement_service_uuid,
|
||||
{
|
||||
.peripheral_discovered_cb =
|
||||
[&found_latch, &discovered_peripheral](
|
||||
BlePeripheral& peripheral, const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes,
|
||||
bool fast_advertisement) {
|
||||
discovered_peripheral = peripheral;
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Discovered peripheral=%p [impl=%p], fast advertisement=%d",
|
||||
&peripheral, &peripheral.GetImpl(), fast_advertisement);
|
||||
found_latch.CountDown();
|
||||
},
|
||||
});
|
||||
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
ASSERT_TRUE(discovered_peripheral.IsValid());
|
||||
CancellationFlag flag(true);
|
||||
BleSocket socket = ble_b.Connect(discovered_peripheral, service_id, &flag);
|
||||
// If FeatureFlag is disabled, Cancelled is false as no-op.
|
||||
if (!feature_flags.enable_cancellation_flag) {
|
||||
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(socket.IsValid());
|
||||
} else {
|
||||
EXPECT_FALSE(accept_latch.Await(kWaitDuration).result());
|
||||
EXPECT_FALSE(socket.IsValid());
|
||||
}
|
||||
ble_b.StopScanning(service_id);
|
||||
ble_a.StopAdvertising(service_id);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ParametrisedBleTest, BleTest,
|
||||
::testing::ValuesIn(kTestCases));
|
||||
|
||||
TEST_F(BleTest, CanConstructValidObject) {
|
||||
env_.Start();
|
||||
BluetoothRadio radio_a;
|
||||
BluetoothRadio radio_b;
|
||||
Ble ble_a{radio_a};
|
||||
Ble ble_b{radio_b};
|
||||
|
||||
EXPECT_TRUE(ble_a.IsMediumValid());
|
||||
EXPECT_TRUE(ble_a.IsAdapterValid());
|
||||
EXPECT_TRUE(ble_a.IsAvailable());
|
||||
EXPECT_TRUE(ble_b.IsMediumValid());
|
||||
EXPECT_TRUE(ble_b.IsAdapterValid());
|
||||
EXPECT_TRUE(ble_b.IsAvailable());
|
||||
EXPECT_NE(&radio_a.GetBluetoothAdapter(), &radio_b.GetBluetoothAdapter());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(BleTest, CanStartAdvertising) {
|
||||
env_.Start();
|
||||
BluetoothRadio radio_a;
|
||||
BluetoothRadio radio_b;
|
||||
Ble ble_a{radio_a};
|
||||
Ble ble_b{radio_b};
|
||||
radio_a.Enable();
|
||||
radio_b.Enable();
|
||||
std::string service_id(kServiceID);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
|
||||
CountDownLatch found_latch(1);
|
||||
|
||||
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.StartAdvertising(service_id, advertisement_bytes,
|
||||
fast_advertisement_service_uuid));
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(ble_a.StopAdvertising(service_id));
|
||||
EXPECT_TRUE(ble_b.StopScanning(service_id));
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(BleTest, CanStartDiscovery) {
|
||||
env_.Start();
|
||||
BluetoothRadio radio_a;
|
||||
BluetoothRadio radio_b;
|
||||
Ble ble_a{radio_a};
|
||||
Ble ble_b{radio_b};
|
||||
radio_a.Enable();
|
||||
radio_b.Enable();
|
||||
std::string service_id(kServiceID);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
std::string fast_advertisement_service_uuid(kFastAdvertisementServiceUuid);
|
||||
CountDownLatch accept_latch(1);
|
||||
CountDownLatch lost_latch(1);
|
||||
|
||||
ble_b.StartAdvertising(service_id, advertisement_bytes,
|
||||
fast_advertisement_service_uuid);
|
||||
|
||||
EXPECT_TRUE(ble_a.StartScanning(
|
||||
service_id, fast_advertisement_service_uuid,
|
||||
DiscoveredPeripheralCallback{
|
||||
.peripheral_discovered_cb =
|
||||
[&accept_latch](
|
||||
BlePeripheral& peripheral, const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes,
|
||||
bool fast_advertisement) { accept_latch.CountDown(); },
|
||||
.peripheral_lost_cb =
|
||||
[&lost_latch](BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
lost_latch.CountDown();
|
||||
},
|
||||
}));
|
||||
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
|
||||
ble_b.StopAdvertising(service_id);
|
||||
EXPECT_TRUE(lost_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(ble_a.StopScanning(service_id));
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,71 +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.
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "ble_v2",
|
||||
srcs = [
|
||||
"advertisement_read_result.cc",
|
||||
"ble_advertisement.cc",
|
||||
"ble_advertisement_header.cc",
|
||||
"ble_packet.cc",
|
||||
"bloom_filter.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"advertisement_read_result.h",
|
||||
"ble_advertisement.h",
|
||||
"ble_advertisement_header.h",
|
||||
"ble_packet.h",
|
||||
"ble_peripheral.h",
|
||||
"bloom_filter.h",
|
||||
"discovered_peripheral_callback.h",
|
||||
],
|
||||
copts = ["-DCORE_ADAPTER_DLL"],
|
||||
visibility = [
|
||||
"//cpp/core/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//cpp/core:core_types",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/base:util",
|
||||
"//cpp/platform/public:logging",
|
||||
"//cpp/platform/public:types",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
"@com_google_absl//absl/container:flat_hash_set",
|
||||
"@com_google_absl//absl/numeric:int128",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/time",
|
||||
"@aappleby_smhasher//:libmurmur3",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ble_v2_test",
|
||||
srcs = [
|
||||
"advertisement_read_result_test.cc",
|
||||
"ble_advertisement_header_test.cc",
|
||||
"ble_advertisement_test.cc",
|
||||
"ble_packet_test.cc",
|
||||
"ble_peripheral_test.cc",
|
||||
"bloom_filter_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":ble_v2",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/impl/g3", # buildcleaner: keep
|
||||
"//cpp/platform/public:comm",
|
||||
"@com_google_googletest//:gtest_main","@com_github_protobuf_matchers//protobuf-matchers:protobuf-matchers",
|
||||
"@com_google_absl//absl/time",
|
||||
],
|
||||
)
|
||||
@@ -1,139 +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 "core/internal/mediums/ble_v2/advertisement_read_result.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
const AdvertisementReadResult::Config AdvertisementReadResult::kDefaultConfig{
|
||||
.backoff_multiplier = 2.0,
|
||||
.base_backoff_duration = absl::Seconds(1),
|
||||
.max_backoff_duration = absl::Minutes(5),
|
||||
};
|
||||
|
||||
// Adds a successfully read advertisement for the specified slot to this read
|
||||
// result. This is fundamentally different from RecordLastReadStatus() because
|
||||
// we can report a read failure, but still manage to read some advertisements.
|
||||
void AdvertisementReadResult::AddAdvertisement(std::int32_t slot,
|
||||
const ByteArray& advertisement) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Blindly remove from the advertisements map to make sure any existing
|
||||
// key-value pair is destroyed.
|
||||
advertisements_.emplace(slot, advertisement);
|
||||
}
|
||||
|
||||
// Determines whether or not an advertisement was successfully read at the
|
||||
// specified slot.
|
||||
bool AdvertisementReadResult::HasAdvertisement(std::int32_t slot) const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return advertisements_.contains(slot);
|
||||
}
|
||||
|
||||
// Retrieves all raw advertisements that were successfully read.
|
||||
std::vector<const ByteArray*> AdvertisementReadResult::GetAdvertisements()
|
||||
const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
std::vector<const ByteArray*> all_advertisements;
|
||||
all_advertisements.reserve(advertisements_.size());
|
||||
for (const auto& item : advertisements_) {
|
||||
all_advertisements.emplace_back(&item.second);
|
||||
}
|
||||
|
||||
return all_advertisements;
|
||||
}
|
||||
|
||||
// Determines what stage we're in for retrying a read from an advertisement
|
||||
// GATT server.
|
||||
AdvertisementReadResult::RetryStatus
|
||||
AdvertisementReadResult::EvaluateRetryStatus() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Check if we have already succeeded reading this advertisement.
|
||||
if (status_ == Status::kSuccess) {
|
||||
return RetryStatus::kPreviouslySucceeded;
|
||||
}
|
||||
|
||||
// Check if we have recently failed to read this advertisement.
|
||||
if (GetDurationSinceReadLocked() < backoff_duration_) {
|
||||
return RetryStatus::kTooSoon;
|
||||
}
|
||||
|
||||
return RetryStatus::kRetry;
|
||||
}
|
||||
|
||||
// Records the status of the latest read, and updates the next backoff
|
||||
// duration for subsequent reads. Be sure to also call
|
||||
// AddAdvertisement() if any advertisements were read.
|
||||
void AdvertisementReadResult::RecordLastReadStatus(bool is_success) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Update the last read timestamp.
|
||||
last_read_timestamp_ = SystemClock::ElapsedRealtime();
|
||||
|
||||
// Update the backoff duration.
|
||||
if (is_success) {
|
||||
// Reset the backoff duration now that we had a successful read.
|
||||
backoff_duration_ = config_.base_backoff_duration;
|
||||
} else {
|
||||
// Determine whether or not we were already failing before. If we were, we
|
||||
// should increase the backoff duration.
|
||||
if (status_ == Status::kFailure) {
|
||||
// Use exponential backoff to determine the next backoff duration. This
|
||||
// simply involves multiplying our current backoff duration by some
|
||||
// multiplier.
|
||||
absl::Duration next_backoff_duration =
|
||||
config_.backoff_multiplier * backoff_duration_;
|
||||
// Update the backoff duration, making sure not to blow past the
|
||||
// ceiling.
|
||||
backoff_duration_ =
|
||||
std::min(next_backoff_duration, config_.max_backoff_duration);
|
||||
} else {
|
||||
// This is our first time failing, so we should only backoff for the
|
||||
// initial duration.
|
||||
backoff_duration_ = config_.base_backoff_duration;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the internal result.
|
||||
status_ = is_success ? Status::kSuccess : Status::kFailure;
|
||||
}
|
||||
|
||||
// Returns how much time has passed since we last tried reading from an
|
||||
// advertisement GATT server.
|
||||
absl::Duration AdvertisementReadResult::GetDurationSinceRead() const {
|
||||
MutexLock lock(&mutex_);
|
||||
return GetDurationSinceReadLocked();
|
||||
}
|
||||
|
||||
absl::Duration AdvertisementReadResult::GetDurationSinceReadLocked() const {
|
||||
return SystemClock::ElapsedRealtime() - last_read_timestamp_;
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,104 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_ADVERTISEMENT_READ_RESULT_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_ADVERTISEMENT_READ_RESULT_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Representation of a GATT advertisement read result. This object helps us
|
||||
// determine whether or not we need to retry GATT reads.
|
||||
class AdvertisementReadResult {
|
||||
public:
|
||||
// We need a long enough duration such that we always trigger a read
|
||||
// retry AND we always connect to it without delay. The former case
|
||||
// helps us initialize an AdvertisementReadResult so that we
|
||||
// unconditionally try reading on the first sighting. And the latter
|
||||
// case helps us connect immediately when we initialize a dummy read
|
||||
// result for fast advertisements (which don't use the GATT server).
|
||||
|
||||
struct Config {
|
||||
// How much to multiply the backoff duration by with every failure to read
|
||||
// from the advertisement GATT server. This should never be below 1!
|
||||
float backoff_multiplier;
|
||||
// The initial backoff duration when we fail to read from an advertisement
|
||||
// GATT server.
|
||||
absl::Duration base_backoff_duration;
|
||||
// The maximum backoff duration allowed between advertisement GATT server
|
||||
// reads.
|
||||
absl::Duration max_backoff_duration;
|
||||
};
|
||||
|
||||
static const Config kDefaultConfig;
|
||||
explicit AdvertisementReadResult(const Config& config = kDefaultConfig)
|
||||
: config_(config) {}
|
||||
~AdvertisementReadResult() = default;
|
||||
|
||||
enum class RetryStatus {
|
||||
kUnknown = 0,
|
||||
kRetry = 1,
|
||||
kPreviouslySucceeded = 2,
|
||||
kTooSoon = 3,
|
||||
};
|
||||
|
||||
void AddAdvertisement(std::int32_t slot, const ByteArray& advertisement)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
bool HasAdvertisement(std::int32_t slot) const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
std::vector<const ByteArray*> GetAdvertisements() const
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
RetryStatus EvaluateRetryStatus() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
void RecordLastReadStatus(bool is_success) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
absl::Duration GetDurationSinceRead() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
enum class Status {
|
||||
kUnknown = 0,
|
||||
kSuccess = 1,
|
||||
kFailure = 2,
|
||||
};
|
||||
|
||||
absl::Duration GetDurationSinceReadLocked() const
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable Mutex mutex_;
|
||||
|
||||
// Maps slot numbers to the GATT advertisement found in that slot.
|
||||
absl::flat_hash_map<std::int32_t, ByteArray> advertisements_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
Config config_;
|
||||
absl::Duration backoff_duration_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::Time last_read_timestamp_ ABSL_GUARDED_BY(mutex_);
|
||||
Status status_ ABSL_GUARDED_BY(mutex_) = Status::kUnknown;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_ADVERTISEMENT_READ_RESULT_H_
|
||||
@@ -1,143 +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 "core/internal/mediums/ble_v2/advertisement_read_result.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
constexpr char kAdvertisementBytes[] = "\x0A\x0B\x0C";
|
||||
|
||||
// Default values may be too big and impractical to wait for in the test.
|
||||
// For the test platform, we redefine them to some reasonable values.
|
||||
const absl::Duration kAdvertisementBaseBackoffDuration = absl::Seconds(1);
|
||||
const absl::Duration kAdvertisementMaxBackoffDuration = absl::Seconds(6);
|
||||
|
||||
const AdvertisementReadResult::Config test_config{
|
||||
.backoff_multiplier =
|
||||
AdvertisementReadResult::kDefaultConfig.backoff_multiplier,
|
||||
.base_backoff_duration = kAdvertisementBaseBackoffDuration,
|
||||
.max_backoff_duration = kAdvertisementMaxBackoffDuration,
|
||||
};
|
||||
|
||||
TEST(AdvertisementReadResultTest, AdvertisementExists) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int32_t slot = 6;
|
||||
advertisement_read_result.AddAdvertisement(slot,
|
||||
ByteArray(kAdvertisementBytes));
|
||||
|
||||
EXPECT_TRUE(advertisement_read_result.HasAdvertisement(slot));
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, AdvertisementNonExistent) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int32_t slot = 6;
|
||||
|
||||
EXPECT_FALSE(advertisement_read_result.HasAdvertisement(slot));
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
|
||||
EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(),
|
||||
AdvertisementReadResult::RetryStatus::kRetry);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(),
|
||||
AdvertisementReadResult::RetryStatus::kPreviouslySucceeded);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Sleep for some time, but not long enough to warrant a retry.
|
||||
absl::SleepFor(kAdvertisementBaseBackoffDuration / 2);
|
||||
|
||||
EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(),
|
||||
AdvertisementReadResult::RetryStatus::kTooSoon);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Sleep long enough to warrant a retry.
|
||||
absl::SleepFor(kAdvertisementBaseBackoffDuration);
|
||||
|
||||
EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(),
|
||||
AdvertisementReadResult::RetryStatus::kRetry);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Record an additional failure so our backoff duration increases.
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Sleep for the backoff duration. We shouldn't trigger a retry because the
|
||||
// backoff should have increased from failing a second time.
|
||||
absl::SleepFor(kAdvertisementBaseBackoffDuration);
|
||||
|
||||
EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(),
|
||||
AdvertisementReadResult::RetryStatus::kTooSoon);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Record an absurd amount of failures so we hit the maximum backoff duration.
|
||||
for (std::int32_t i = 0; i < 1000; i++) {
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ false);
|
||||
}
|
||||
|
||||
// Sleep for the maximum backoff duration. This should be enough to warrant a
|
||||
// retry.
|
||||
absl::SleepFor(kAdvertisementMaxBackoffDuration);
|
||||
|
||||
EXPECT_EQ(advertisement_read_result.EvaluateRetryStatus(),
|
||||
AdvertisementReadResult::RetryStatus::kRetry);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, GetDurationSinceRead) {
|
||||
AdvertisementReadResult advertisement_read_result(test_config);
|
||||
advertisement_read_result.RecordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
absl::Duration sleepTime = absl::Milliseconds(420);
|
||||
absl::SleepFor(sleepTime);
|
||||
|
||||
EXPECT_GE(advertisement_read_result.GetDurationSinceRead(), sleepTime);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,245 +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 "core/internal/mediums/ble_v2/ble_advertisement.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "platform/base/base_input_stream.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BleAdvertisement::BleAdvertisement(Version version,
|
||||
SocketVersion socket_version,
|
||||
const ByteArray &service_id_hash,
|
||||
const ByteArray &data,
|
||||
const ByteArray &device_token) {
|
||||
DoInitialize(/*fast_advertisement=*/service_id_hash.Empty(), version,
|
||||
socket_version, service_id_hash, data, device_token);
|
||||
}
|
||||
|
||||
void BleAdvertisement::DoInitialize(bool fast_advertisement, Version version,
|
||||
SocketVersion socket_version,
|
||||
const ByteArray &service_id_hash,
|
||||
const ByteArray &data,
|
||||
const ByteArray &device_token) {
|
||||
// Check that the given input is valid.
|
||||
fast_advertisement_ = fast_advertisement;
|
||||
if (!fast_advertisement_) {
|
||||
if (service_id_hash.size() != kServiceIdHashLength) return;
|
||||
}
|
||||
if (!IsSupportedVersion(version) ||
|
||||
!IsSupportedSocketVersion(socket_version) ||
|
||||
(!device_token.Empty() && device_token.size() != kDeviceTokenLength)) {
|
||||
return;
|
||||
}
|
||||
|
||||
int advertisement_Length = ComputeAdvertisementLength(
|
||||
data.size(), device_token.size(), fast_advertisement_);
|
||||
int max_advertisement_length = fast_advertisement
|
||||
? kMaxFastAdvertisementLength
|
||||
: kMaxAdvertisementLength;
|
||||
if (advertisement_Length > max_advertisement_length) {
|
||||
return;
|
||||
}
|
||||
|
||||
version_ = version;
|
||||
socket_version_ = socket_version;
|
||||
if (!fast_advertisement_) service_id_hash_ = service_id_hash;
|
||||
data_ = data;
|
||||
device_token_ = device_token;
|
||||
}
|
||||
|
||||
BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) {
|
||||
if (ble_advertisement_bytes.Empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: null bytes passed in.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ble_advertisement_bytes.size() < kVersionLength) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BleAdvertisement: expecting min %d raw bytes to "
|
||||
"parse the version, got %" PRIu64,
|
||||
kVersionLength, ble_advertisement_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArray advertisement_bytes{ble_advertisement_bytes};
|
||||
BaseInputStream base_input_stream{advertisement_bytes};
|
||||
// The first 1 byte is supposed to be the version, socket version and the fast
|
||||
// advertisement flag.
|
||||
auto version_byte = static_cast<char>(base_input_stream.ReadUint8());
|
||||
|
||||
// Version.
|
||||
version_ = static_cast<Version>((version_byte & kVersionBitmask) >> 5);
|
||||
if (!IsSupportedVersion(version_)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: unsupported Version %u",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
|
||||
// Socket version.
|
||||
socket_version_ =
|
||||
static_cast<SocketVersion>((version_byte & kSocketVersionBitmask) >> 2);
|
||||
if (!IsSupportedSocketVersion(socket_version_)) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BleAdvertisement: unsupported SocketVersion %u",
|
||||
socket_version_);
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// Fast advertisement flag.
|
||||
fast_advertisement_ =
|
||||
static_cast<bool>((version_byte & kFastAdvertisementFlagBitmask) >> 1);
|
||||
|
||||
// The next 3 bytes are supposed to be the service_id_hash if not fast
|
||||
// advertisement.
|
||||
if (!fast_advertisement_) {
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
}
|
||||
|
||||
// Data length.
|
||||
int expected_data_size =
|
||||
fast_advertisement_
|
||||
? static_cast<int>(
|
||||
base_input_stream.ReadBytes(kFastDataSizeLength).data()[0])
|
||||
: static_cast<int>(base_input_stream.ReadUint32());
|
||||
if (expected_data_size < 0) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: negative data size %d",
|
||||
expected_data_size);
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// Data.
|
||||
// Check that the stated data size is the same as what we received.
|
||||
data_ = base_input_stream.ReadBytes(expected_data_size);
|
||||
if (data_.size() != expected_data_size) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: expected data to be %u "
|
||||
"bytes, got %" PRIu64 " bytes ",
|
||||
expected_data_size, data_.size());
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// Device token. If the number of remaining bytes are valid for device token,
|
||||
// then read it.
|
||||
if (base_input_stream.IsAvailable(kDeviceTokenLength)) {
|
||||
device_token_ = base_input_stream.ReadBytes(kDeviceTokenLength);
|
||||
}
|
||||
}
|
||||
|
||||
BleAdvertisement::operator ByteArray() const {
|
||||
if (!IsValid()) {
|
||||
return ByteArray{};
|
||||
}
|
||||
|
||||
// The first 3 bits are the Version.
|
||||
char version_byte = (static_cast<char>(version_) << 5) & kVersionBitmask;
|
||||
// The next 3 bits are the Socket version. 2 bits left are reserved.
|
||||
version_byte |=
|
||||
(static_cast<char>(socket_version_) << 2) & kSocketVersionBitmask;
|
||||
// The next 1 bit is the fast advertisement flag. 1 bit left is reserved.
|
||||
version_byte |= (static_cast<char>(fast_advertisement_ ? 1 : 0) << 1) &
|
||||
kFastAdvertisementFlagBitmask;
|
||||
|
||||
// Serialize Data size bytes
|
||||
ByteArray data_size_bytes{static_cast<size_t>(
|
||||
fast_advertisement_ ? kFastDataSizeLength : kDataSizeLength)};
|
||||
auto *data_size_bytes_write_ptr = data_size_bytes.data();
|
||||
SerializeDataSize(fast_advertisement_, data_size_bytes_write_ptr,
|
||||
data_.size());
|
||||
|
||||
// clang-format on
|
||||
if (fast_advertisement_) {
|
||||
std::string out =
|
||||
absl::StrCat(std::string(1, version_byte), std::string(data_size_bytes),
|
||||
std::string(data_), std::string(device_token_));
|
||||
return ByteArray{std::move(out)};
|
||||
} else {
|
||||
std::string out = absl::StrCat(
|
||||
std::string(1, version_byte), std::string(service_id_hash_),
|
||||
std::string(data_size_bytes), std::string(data_),
|
||||
std::string(device_token_));
|
||||
return ByteArray{std::move(out)};
|
||||
}
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
bool BleAdvertisement::operator==(const BleAdvertisement &rhs) const {
|
||||
return this->GetVersion() == rhs.GetVersion() &&
|
||||
this->GetSocketVersion() == rhs.GetSocketVersion() &&
|
||||
this->GetServiceIdHash() == rhs.GetServiceIdHash() &&
|
||||
this->GetData() == rhs.GetData() &&
|
||||
this->GetDeviceToken() == rhs.GetDeviceToken();
|
||||
}
|
||||
|
||||
bool BleAdvertisement::operator<(const BleAdvertisement &rhs) const {
|
||||
if (this->GetVersion() != rhs.GetVersion()) {
|
||||
return this->GetVersion() < rhs.GetVersion();
|
||||
}
|
||||
if (this->GetSocketVersion() != rhs.GetSocketVersion()) {
|
||||
return this->GetSocketVersion() < rhs.GetSocketVersion();
|
||||
}
|
||||
if (this->GetServiceIdHash() != rhs.GetServiceIdHash()) {
|
||||
return this->GetServiceIdHash() < rhs.GetServiceIdHash();
|
||||
}
|
||||
if (this->GetDeviceToken() != rhs.GetDeviceToken()) {
|
||||
return this->GetDeviceToken() < rhs.GetDeviceToken();
|
||||
}
|
||||
return this->GetData() < rhs.GetData();
|
||||
}
|
||||
|
||||
bool BleAdvertisement::IsSupportedVersion(Version version) const {
|
||||
return version >= Version::kV1 && version <= Version::kV2;
|
||||
}
|
||||
|
||||
bool BleAdvertisement::IsSupportedSocketVersion(
|
||||
SocketVersion socket_version) const {
|
||||
return socket_version >= SocketVersion::kV1 &&
|
||||
socket_version <= SocketVersion::kV2;
|
||||
}
|
||||
|
||||
void BleAdvertisement::SerializeDataSize(bool fast_advertisement,
|
||||
char *data_size_bytes_write_ptr,
|
||||
size_t data_size) const {
|
||||
// Get a raw representation of the data size bytes in memory.
|
||||
char *data_size_bytes = reinterpret_cast<char *>(&data_size);
|
||||
|
||||
const int data_size_length =
|
||||
fast_advertisement ? kFastDataSizeLength : kDataSizeLength;
|
||||
|
||||
// Append these raw bytes to advertisement bytes, keeping in mind that we need
|
||||
// to convert from Little Endian to Big Endian in the process.
|
||||
for (int i = 0; i < data_size_length; ++i) {
|
||||
data_size_bytes_write_ptr[i] = data_size_bytes[data_size_length - i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,139 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Represents the format of the Mediums BLE Advertisement used in Advertising +
|
||||
// Discovery.
|
||||
//
|
||||
// [VERSION][SOCKET_VERSION][FAST_ADVERTISEMENT_FLAG][1_RESERVED_BIT][SERVICE_ID_HASH][DATA_SIZE][DATA][DEVICE_TOKEN]
|
||||
//
|
||||
// For fast advertisement, we remove SERVICE_ID_HASH since we already have one
|
||||
// copy in Nearby Connections(b/138447288)
|
||||
// [VERSION][SOCKET_VERSION][FAST_ADVERTISEMENT_FLAG][1_RESERVED_BIT][DATA_SIZE][DATA][DEVICE_TOKEN]
|
||||
//
|
||||
// See go/nearby-ble-design for more information.
|
||||
class BleAdvertisement {
|
||||
public:
|
||||
// Versions of the BleAdvertisement.
|
||||
enum class Version {
|
||||
kUndefined = 0,
|
||||
kV1 = 1,
|
||||
kV2 = 2,
|
||||
// Version is only allocated 3 bits in the BleAdvertisement, so this can
|
||||
// never go beyond V7.
|
||||
};
|
||||
|
||||
// Versions of the BLESocket.
|
||||
enum class SocketVersion {
|
||||
kUndefined = 0,
|
||||
kV1 = 1,
|
||||
kV2 = 2,
|
||||
// SocketVersion is only allocated 3 bits in the BleAdvertisement, so this
|
||||
// can never go beyond V7.
|
||||
};
|
||||
|
||||
static constexpr int kServiceIdHashLength = 3;
|
||||
static constexpr int kDeviceTokenLength = 2;
|
||||
|
||||
BleAdvertisement() = default;
|
||||
BleAdvertisement(Version version, SocketVersion socket_version,
|
||||
const ByteArray &service_id_hash, const ByteArray &data,
|
||||
const ByteArray &device_token);
|
||||
explicit BleAdvertisement(const ByteArray &ble_advertisement_bytes);
|
||||
BleAdvertisement(const BleAdvertisement &) = default;
|
||||
BleAdvertisement &operator=(const BleAdvertisement &) = default;
|
||||
BleAdvertisement(BleAdvertisement &&) = default;
|
||||
BleAdvertisement &operator=(BleAdvertisement &&) = default;
|
||||
~BleAdvertisement() = default;
|
||||
|
||||
explicit operator ByteArray() const;
|
||||
// Operator overloads when comparing BleAdvertisement.
|
||||
bool operator==(const BleAdvertisement &rhs) const;
|
||||
bool operator<(const BleAdvertisement &rhs) const;
|
||||
|
||||
bool IsValid() const { return IsSupportedVersion(version_); }
|
||||
Version GetVersion() const { return version_; }
|
||||
SocketVersion GetSocketVersion() const { return socket_version_; }
|
||||
bool IsFastAdvertisement() const { return fast_advertisement_; }
|
||||
ByteArray GetServiceIdHash() const { return service_id_hash_; }
|
||||
ByteArray &GetData() & { return data_; }
|
||||
const ByteArray &GetData() const & { return data_; }
|
||||
ByteArray &&GetData() && { return std::move(data_); }
|
||||
const ByteArray &&GetData() const && { return std::move(data_); }
|
||||
ByteArray GetDeviceToken() const { return device_token_; }
|
||||
|
||||
private:
|
||||
void DoInitialize(bool fast_advertisement, Version version,
|
||||
SocketVersion socket_version,
|
||||
const ByteArray &service_id_hash, const ByteArray &data,
|
||||
const ByteArray &device_token);
|
||||
bool IsSupportedVersion(Version version) const;
|
||||
bool IsSupportedSocketVersion(SocketVersion socket_version) const;
|
||||
void SerializeDataSize(bool fast_advertisement,
|
||||
char *data_size_bytes_write_ptr,
|
||||
size_t data_size) const;
|
||||
int ComputeAdvertisementLength(int data_length, int total_optional_length,
|
||||
bool fast_advertisement) const {
|
||||
// The advertisement length is the minimum length + the length of the data +
|
||||
// the length of in-use optional fields.
|
||||
return fast_advertisement ? (kMinFastAdvertisementLegth + data_length +
|
||||
total_optional_length)
|
||||
: (kMinAdvertisementLength + data_length +
|
||||
total_optional_length);
|
||||
}
|
||||
|
||||
static constexpr int kVersionLength = 1;
|
||||
static constexpr int kVersionBitmask = 0x0E0;
|
||||
static constexpr int kSocketVersionBitmask = 0x01C;
|
||||
static constexpr int kFastAdvertisementFlagBitmask = 0x002;
|
||||
static constexpr int kDataSizeLength = 4; // Length of one int.
|
||||
static constexpr int kFastDataSizeLength = 1; // Length of one byte.
|
||||
static constexpr int kMinAdvertisementLength =
|
||||
kVersionLength + kServiceIdHashLength + kDataSizeLength;
|
||||
// The maximum length for a Gatt characteristic value is 512 bytes, so make
|
||||
// sure the entire advertisement is less than that. The data can take up
|
||||
// whatever space is remaining after the bytes preceding it.
|
||||
static constexpr int kMaxAdvertisementLength = 512;
|
||||
static constexpr int kMinFastAdvertisementLegth =
|
||||
kVersionLength + kFastDataSizeLength;
|
||||
// The maximum length for the scan response is 31 bytes. However, with the
|
||||
// required header that comes before the service data, this leaves the
|
||||
// advertiser with 27 leftover bytes.
|
||||
static constexpr int kMaxFastAdvertisementLength = 27;
|
||||
|
||||
Version version_{Version::kUndefined};
|
||||
SocketVersion socket_version_{SocketVersion::kUndefined};
|
||||
bool fast_advertisement_ = false;
|
||||
ByteArray service_id_hash_;
|
||||
ByteArray data_;
|
||||
ByteArray device_token_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_H_
|
||||
@@ -1,138 +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 "core/internal/mediums/ble_v2/ble_advertisement_header.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "platform/base/base64_utils.h"
|
||||
#include "platform/base/base_input_stream.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
Version version, bool extended_advertisement, int num_slots,
|
||||
const ByteArray &service_id_bloom_filter,
|
||||
const ByteArray &advertisement_hash, int psm) {
|
||||
if (version != Version::kV2 || num_slots <= 0 ||
|
||||
service_id_bloom_filter.size() != kServiceIdBloomFilterLength ||
|
||||
advertisement_hash.size() != kAdvertisementHashLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
version_ = version;
|
||||
extended_advertisement_ = extended_advertisement;
|
||||
num_slots_ = num_slots;
|
||||
service_id_bloom_filter_ = service_id_bloom_filter;
|
||||
advertisement_hash_ = advertisement_hash;
|
||||
psm_ = psm;
|
||||
}
|
||||
|
||||
BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
const ByteArray &ble_advertisement_header_bytes) {
|
||||
if (ble_advertisement_header_bytes.Empty()) {
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
"Cannot deserialize BLEAdvertisementHeader: failed Base64 decoding");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ble_advertisement_header_bytes.size() < kMinAdvertisementHeaderLength) {
|
||||
NEARBY_LOG(ERROR,
|
||||
"Cannot deserialize BleAdvertisementHeader: expecting min %u "
|
||||
"raw bytes, got %" PRIu64 " instead",
|
||||
kMinAdvertisementHeaderLength,
|
||||
ble_advertisement_header_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArray advertisement_header_bytes{ble_advertisement_header_bytes};
|
||||
BaseInputStream base_input_stream{advertisement_header_bytes};
|
||||
// The first 1 byte is supposed to be the version and number of slots.
|
||||
auto version_and_num_slots_byte =
|
||||
static_cast<char>(base_input_stream.ReadUint8());
|
||||
// The upper 3 bits are supposed to be the version.
|
||||
version_ =
|
||||
static_cast<Version>((version_and_num_slots_byte & kVersionBitmask) >> 5);
|
||||
if (version_ != Version::kV2) {
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
"Cannot deserialize BleAdvertisementHeader: unsupported Version %d",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
// The next 1 bit is supposed to be the extended advertisement flag.
|
||||
extended_advertisement_ =
|
||||
((version_and_num_slots_byte & kExtendedAdvertismentBitMask) >> 4) == 1;
|
||||
// The lower 4 bits are supposed to be the number of slots.
|
||||
num_slots_ = static_cast<int>(version_and_num_slots_byte & kNumSlotsBitmask);
|
||||
if (num_slots_ <= 0) {
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// The next 10 bytes are supposed to be the service_id_bloom_filter.
|
||||
service_id_bloom_filter_ =
|
||||
base_input_stream.ReadBytes(kServiceIdBloomFilterLength);
|
||||
|
||||
// The next 4 bytes are supposed to be the advertisement_hash.
|
||||
advertisement_hash_ = base_input_stream.ReadBytes(kAdvertisementHashLength);
|
||||
|
||||
// The next 2 bytes are PSM value.
|
||||
if (base_input_stream.IsAvailable(sizeof(std::uint16_t))) {
|
||||
psm_ = static_cast<int>(base_input_stream.ReadUint16());
|
||||
}
|
||||
}
|
||||
|
||||
BleAdvertisementHeader::operator ByteArray() const {
|
||||
if (!IsValid()) {
|
||||
return ByteArray();
|
||||
}
|
||||
|
||||
// The first 3 bits are the Version.
|
||||
char version_and_num_slots_byte =
|
||||
(static_cast<char>(version_) << 5) & kVersionBitmask;
|
||||
// The next 1 bit is extended advertisement flag.
|
||||
version_and_num_slots_byte |=
|
||||
(static_cast<char>(extended_advertisement_) << 4) &
|
||||
kExtendedAdvertismentBitMask;
|
||||
// The next 5 bits are the number of slots.
|
||||
version_and_num_slots_byte |=
|
||||
static_cast<char>(num_slots_) & kNumSlotsBitmask;
|
||||
|
||||
// Convert psm_ value to 2-bytes.
|
||||
ByteArray psm_byte{sizeof(std::uint16_t)};
|
||||
char *data = psm_byte.data();
|
||||
data[0] = psm_ & 0xFF00;
|
||||
data[1] = psm_ & 0x00FF;
|
||||
|
||||
// clang-format off
|
||||
std::string out = absl::StrCat(std::string(1, version_and_num_slots_byte),
|
||||
std::string(service_id_bloom_filter_),
|
||||
std::string(advertisement_hash_),
|
||||
std::string(psm_byte));
|
||||
// clang-format on
|
||||
|
||||
return ByteArray(std::move(out));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,101 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Represents the format of the Mediums BLE Advertisement Header used in
|
||||
// Advertising + Discovery.
|
||||
//
|
||||
// [VERSION][NUM_SLOTS][SERVICE_ID_BLOOM_FILTER][ADVERTISEMENT_HASH][L2_CAP_PSM]
|
||||
//
|
||||
// See go/nearby-ble-design for more information.
|
||||
//
|
||||
// Note. The object constructed by default constructor or the parameterized
|
||||
// constructor with invalid value(s) is treated as invalid instance. Caller
|
||||
// should be responsible to call IsValid() to check the instance is invalid in
|
||||
// advance before continue on.
|
||||
class BleAdvertisementHeader {
|
||||
public:
|
||||
// Versions of the BleAdvertisementHeader.
|
||||
enum class Version {
|
||||
kUndefined = 0,
|
||||
kV1 = 1,
|
||||
kV2 = 2,
|
||||
// Version is only allocated 3 bits in the BleAdvertisementHeader, so this
|
||||
// can never go beyond V7.
|
||||
//
|
||||
// V1 is not present because it's an old format used in Nearby Connections
|
||||
// before this logic was pushed down into Nearby Mediums. V1 put
|
||||
// everything in the service data, while V2 puts the data inside a GATT
|
||||
// characteristic so the two are not compatible.
|
||||
};
|
||||
|
||||
BleAdvertisementHeader() = default;
|
||||
BleAdvertisementHeader(Version version, bool extended_advertisement,
|
||||
int num_slots,
|
||||
const ByteArray &service_id_bloom_filter,
|
||||
const ByteArray &advertisement_hash, int psm);
|
||||
explicit BleAdvertisementHeader(
|
||||
const ByteArray &ble_advertisement_header_bytes);
|
||||
BleAdvertisementHeader(const BleAdvertisementHeader &) = default;
|
||||
BleAdvertisementHeader &operator=(const BleAdvertisementHeader &) = default;
|
||||
BleAdvertisementHeader(BleAdvertisementHeader &&) = default;
|
||||
BleAdvertisementHeader &operator=(BleAdvertisementHeader &&) = default;
|
||||
~BleAdvertisementHeader() = default;
|
||||
|
||||
explicit operator ByteArray() const;
|
||||
|
||||
bool IsValid() const { return version_ == Version::kV2; }
|
||||
Version GetVersion() const { return version_; }
|
||||
bool IsExtendedAdvertisement() const { return extended_advertisement_; }
|
||||
int GetNumSlots() const { return num_slots_; }
|
||||
ByteArray GetServiceIdBloomFilter() const { return service_id_bloom_filter_; }
|
||||
ByteArray GetAdvertisementHash() const { return advertisement_hash_; }
|
||||
int GetPsmValue() const { return psm_; }
|
||||
|
||||
private:
|
||||
static constexpr int kVersionAndNumSlotsLength = 1;
|
||||
static constexpr int kServiceIdBloomFilterLength = 10;
|
||||
static constexpr int kAdvertisementHashLength = 4;
|
||||
static constexpr int kMinAdvertisementHeaderLength =
|
||||
kVersionAndNumSlotsLength + kServiceIdBloomFilterLength +
|
||||
kAdvertisementHashLength;
|
||||
static constexpr int kVersionBitmask = 0x0E0;
|
||||
static constexpr int kExtendedAdvertismentBitMask = 0x010;
|
||||
static constexpr int kNumSlotsBitmask = 0x00F;
|
||||
|
||||
Version version_ = Version::kUndefined;
|
||||
bool extended_advertisement_ = false;
|
||||
int num_slots_ = 0;
|
||||
ByteArray service_id_bloom_filter_;
|
||||
ByteArray advertisement_hash_;
|
||||
int psm_ = 0;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_
|
||||
@@ -1,208 +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 "core/internal/mediums/ble_v2/ble_advertisement_header.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "platform/base/base64_utils.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
constexpr BleAdvertisementHeader::Version kVersion =
|
||||
BleAdvertisementHeader::Version::kV2;
|
||||
constexpr int kNumSlots = 2;
|
||||
constexpr std::int16_t kPsmValue = 1;
|
||||
constexpr absl::string_view kServiceIDBloomFilter{
|
||||
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"};
|
||||
constexpr absl::string_view kAdvertisementHash{"\x0a\x0b\x0c\x0d"};
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionWorks) {
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion());
|
||||
EXPECT_FALSE(ble_advertisement_header.IsExtendedAdvertisement());
|
||||
EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots());
|
||||
EXPECT_EQ(service_id_bloom_filter,
|
||||
ble_advertisement_header.GetServiceIdBloomFilter());
|
||||
EXPECT_EQ(advertisement_hash,
|
||||
ble_advertisement_header.GetAdvertisementHash());
|
||||
EXPECT_EQ(kPsmValue, ble_advertisement_header.GetPsmValue());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) {
|
||||
auto bad_version = static_cast<BleAdvertisementHeader::Version>(666);
|
||||
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
bad_version, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWitZeroNumSlot) {
|
||||
int num_slot = 0;
|
||||
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, false, num_slot, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest,
|
||||
ConstructionFailsWithShortServiceIdBloomFilter) {
|
||||
char short_service_id_bloom_filter[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09";
|
||||
|
||||
ByteArray short_service_id_bloom_filter_bytes{short_service_id_bloom_filter};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, false,
|
||||
kNumSlots, short_service_id_bloom_filter_bytes,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest,
|
||||
ConstructionFailsWithLongServiceIdBloomFilter) {
|
||||
char long_service_id_bloom_filter[] =
|
||||
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b";
|
||||
|
||||
ByteArray service_id_bloom_filter{long_service_id_bloom_filter};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) {
|
||||
char short_advertisement_hash[] = "\x0a\x0b\x0c";
|
||||
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{short_advertisement_hash};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) {
|
||||
char long_advertisement_hash[] = "\x0a\x0b\x0c\x0d\x0e";
|
||||
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{long_advertisement_hash};
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) {
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader org_ble_advertisement_header{
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
auto ble_advertisement_header_bytes = ByteArray(org_ble_advertisement_header);
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
ble_advertisement_header_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion());
|
||||
EXPECT_FALSE(ble_advertisement_header.IsExtendedAdvertisement());
|
||||
EXPECT_EQ(kNumSlots, ble_advertisement_header.GetNumSlots());
|
||||
EXPECT_EQ(service_id_bloom_filter,
|
||||
ble_advertisement_header.GetServiceIdBloomFilter());
|
||||
EXPECT_EQ(advertisement_hash,
|
||||
ble_advertisement_header.GetAdvertisementHash());
|
||||
EXPECT_EQ(kPsmValue, ble_advertisement_header.GetPsmValue());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) {
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
auto ble_advertisement_header_bytes = ByteArray(ble_advertisement_header);
|
||||
|
||||
ByteArray long_ble_advertisement_header_bytes{
|
||||
ble_advertisement_header_bytes.size() + 1};
|
||||
long_ble_advertisement_header_bytes.CopyAt(0, ble_advertisement_header_bytes);
|
||||
|
||||
BleAdvertisementHeader long_ble_advertisement_header{
|
||||
long_ble_advertisement_header_bytes};
|
||||
|
||||
EXPECT_TRUE(long_ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, long_ble_advertisement_header.GetVersion());
|
||||
EXPECT_FALSE(ble_advertisement_header.IsExtendedAdvertisement());
|
||||
EXPECT_EQ(kNumSlots, long_ble_advertisement_header.GetNumSlots());
|
||||
EXPECT_EQ(service_id_bloom_filter,
|
||||
long_ble_advertisement_header.GetServiceIdBloomFilter());
|
||||
EXPECT_EQ(advertisement_hash,
|
||||
long_ble_advertisement_header.GetAdvertisementHash());
|
||||
EXPECT_EQ(kPsmValue, long_ble_advertisement_header.GetPsmValue());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) {
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, false, kNumSlots, service_id_bloom_filter,
|
||||
advertisement_hash, kPsmValue};
|
||||
auto ble_advertisement_header_bytes = ByteArray(ble_advertisement_header);
|
||||
|
||||
ByteArray short_ble_advertisement_header_bytes{
|
||||
ble_advertisement_header_bytes.size() - 3};
|
||||
short_ble_advertisement_header_bytes.CopyAt(0,
|
||||
ble_advertisement_header_bytes);
|
||||
|
||||
BleAdvertisementHeader short_ble_advertisement_header{
|
||||
short_ble_advertisement_header_bytes};
|
||||
|
||||
EXPECT_FALSE(short_ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,455 +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 "core/internal/mediums/ble_v2/ble_advertisement.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
constexpr BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2;
|
||||
constexpr BleAdvertisement::SocketVersion kSocketVersion =
|
||||
BleAdvertisement::SocketVersion::kV2;
|
||||
constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kData{
|
||||
"How much wood can a woodchuck chuck if a wood chuck would chuck wood?"};
|
||||
constexpr absl::string_view kFastData{"Fast Advertise"};
|
||||
constexpr absl::string_view kDeviceToken{"\x04\x20"};
|
||||
// kAdvertisementLength/kFastAdvertisementLength corresponds to the length of a
|
||||
// specific BleAdvertisement packed with the kData/kFastData given above. Be
|
||||
// sure to update this if kData/kFastData ever changes.
|
||||
constexpr size_t kAdvertisementLength = 77;
|
||||
constexpr size_t kFastAdvertisementLength = 16;
|
||||
constexpr size_t kLongAdvertisementLength = kAdvertisementLength + 1000;
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionWorksV1) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1,
|
||||
BleAdvertisement::SocketVersion::kV1,
|
||||
service_id_hash, data, device_token};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(BleAdvertisement::Version::kV1, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(BleAdvertisement::SocketVersion::kV1,
|
||||
ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(data.size(), ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(data, ble_advertisement.GetData());
|
||||
EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionWorksV1ForFastAdvertisement) {
|
||||
ByteArray fast_data{std::string(kFastData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1,
|
||||
BleAdvertisement::SocketVersion::kV1,
|
||||
ByteArray{}, fast_data, device_token};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(BleAdvertisement::Version::kV1, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(BleAdvertisement::SocketVersion::kV1,
|
||||
ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(fast_data.size(), ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(fast_data, ble_advertisement.GetData());
|
||||
EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) {
|
||||
BleAdvertisement::Version bad_version =
|
||||
static_cast<BleAdvertisement::Version>(666);
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement ble_advertisement{bad_version, kSocketVersion,
|
||||
service_id_hash, data, device_token};
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
|
||||
BleAdvertisement fast_ble_advertisement{bad_version, kSocketVersion,
|
||||
ByteArray{}, data, device_token};
|
||||
EXPECT_FALSE(fast_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) {
|
||||
BleAdvertisement::SocketVersion bad_socket_version =
|
||||
static_cast<BleAdvertisement::SocketVersion>(666);
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, bad_socket_version,
|
||||
service_id_hash, data, device_token};
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
|
||||
BleAdvertisement fast_ble_advertisement{kVersion, bad_socket_version,
|
||||
ByteArray{}, data, device_token};
|
||||
EXPECT_FALSE(fast_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithShortServiceIdHash) {
|
||||
char short_service_id_hash_bytes[] = "\x0a\x0b";
|
||||
|
||||
ByteArray bad_service_id_hash{short_service_id_hash_bytes};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion,
|
||||
bad_service_id_hash, data, device_token};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithLongServiceIdHash) {
|
||||
char long_service_id_hash_bytes[] = "\x0a\x0b\x0c\x0d";
|
||||
|
||||
ByteArray bad_service_id_hash{long_service_id_hash_bytes};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion,
|
||||
bad_service_id_hash, data, device_token};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithLongData) {
|
||||
// BleAdvertisement shouldn't be able to support data with the max GATT
|
||||
// attribute length because it needs some room for the preceding fields.
|
||||
char long_data[512]{};
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray bad_data{long_data, 512};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash,
|
||||
bad_data, device_token};
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
|
||||
BleAdvertisement fast_ble_advertisement{kVersion, kSocketVersion, ByteArray{},
|
||||
bad_data, device_token};
|
||||
EXPECT_FALSE(fast_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionWorksWithEmptyDeviceToken) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash,
|
||||
data, ByteArray{}};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(data.size(), ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(data, ble_advertisement.GetData());
|
||||
EXPECT_TRUE(ble_advertisement.GetDeviceToken().Empty());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionWorksWithEmptyDeviceTokenForFastAdvertisement) {
|
||||
ByteArray fast_data{std::string(kFastData)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion, ByteArray{},
|
||||
fast_data, ByteArray{}};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(fast_data.size(), ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(fast_data, ble_advertisement.GetData());
|
||||
EXPECT_TRUE(ble_advertisement.GetDeviceToken().Empty());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithWrongSizeofDeviceToken) {
|
||||
char wrong_device_token_bytes_1[] = "\x04\x2\x10"; // over 2 bytes
|
||||
char wrong_device_token_bytes_2[] = "\x04"; // 1 byte
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray bad_device_token_1{wrong_device_token_bytes_1};
|
||||
ByteArray bad_device_token_2{wrong_device_token_bytes_2};
|
||||
|
||||
BleAdvertisement ble_advertisement_1{
|
||||
kVersion, kSocketVersion, service_id_hash, data, bad_device_token_1};
|
||||
EXPECT_FALSE(ble_advertisement_1.IsValid());
|
||||
|
||||
BleAdvertisement ble_advertisement_2{
|
||||
kVersion, kSocketVersion, service_id_hash, data, bad_device_token_2};
|
||||
EXPECT_FALSE(ble_advertisement_2.IsValid());
|
||||
|
||||
BleAdvertisement fast_ble_advertisement_1{
|
||||
kVersion, kSocketVersion, ByteArray{}, data, bad_device_token_1};
|
||||
EXPECT_FALSE(fast_ble_advertisement_1.IsValid());
|
||||
|
||||
BleAdvertisement fast_ble_advertisement_2{
|
||||
kVersion, kSocketVersion, ByteArray{}, data, bad_device_token_2};
|
||||
EXPECT_FALSE(fast_ble_advertisement_2.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data, device_token};
|
||||
|
||||
ByteArray ble_advertisement_bytes{org_ble_advertisement};
|
||||
BleAdvertisement ble_advertisement{ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(data.size(), ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(data, ble_advertisement.GetData());
|
||||
EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromSerializedBytesWorksForAdvertisement) {
|
||||
ByteArray fast_data{std::string(kFastData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, ByteArray{},
|
||||
fast_data, device_token};
|
||||
|
||||
ByteArray ble_advertisement_bytes{org_ble_advertisement};
|
||||
BleAdvertisement ble_advertisement{ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(fast_data.size(), ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(fast_data, ble_advertisement.GetData());
|
||||
EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{
|
||||
kVersion, kSocketVersion, service_id_hash, ByteArray(), device_token};
|
||||
ByteArray ble_advertisement_bytes{org_ble_advertisement};
|
||||
BleAdvertisement ble_advertisement{ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_TRUE(ble_advertisement.GetData().Empty());
|
||||
EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromSerializedBytesWithEmptyDataWorksForFastAdvertisement) {
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, ByteArray{},
|
||||
ByteArray(), device_token};
|
||||
ByteArray ble_advertisement_bytes{org_ble_advertisement};
|
||||
BleAdvertisement ble_advertisement{ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion());
|
||||
EXPECT_TRUE(ble_advertisement.GetData().Empty());
|
||||
EXPECT_EQ(device_token, ble_advertisement.GetDeviceToken());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data, device_token};
|
||||
ByteArray org_ble_advertisement_bytes{org_ble_advertisement};
|
||||
|
||||
// Copy the bytes into a new array with extra bytes. We must explicitly
|
||||
// define how long our array is because we can't use variable length arrays.
|
||||
char raw_ble_advertisement_bytes[kLongAdvertisementLength]{};
|
||||
memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(),
|
||||
std::min(sizeof(raw_ble_advertisement_bytes),
|
||||
org_ble_advertisement_bytes.size()));
|
||||
|
||||
// Re-parse the Ble advertisement using our extra long advertisement bytes.
|
||||
ByteArray long_ble_advertisement_bytes{raw_ble_advertisement_bytes,
|
||||
kLongAdvertisementLength};
|
||||
BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(long_ble_advertisement.IsValid());
|
||||
EXPECT_FALSE(long_ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, long_ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(service_id_hash, long_ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(data.size(), long_ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(data, long_ble_advertisement.GetData());
|
||||
EXPECT_EQ(device_token, long_ble_advertisement.GetDeviceToken());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromExtraSerializedBytesWorksForFastAdvertisement) {
|
||||
ByteArray fast_data{std::string(kFastData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, ByteArray{},
|
||||
fast_data, device_token};
|
||||
ByteArray org_ble_advertisement_bytes{org_ble_advertisement};
|
||||
|
||||
// Copy the bytes into a new array with extra bytes. We must explicitly
|
||||
// define how long our array is because we can't use variable length arrays.
|
||||
char raw_ble_advertisement_bytes[kLongAdvertisementLength]{};
|
||||
memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(),
|
||||
std::min(sizeof(raw_ble_advertisement_bytes),
|
||||
org_ble_advertisement_bytes.size()));
|
||||
|
||||
// Re-parse the Ble advertisement using our extra long advertisement bytes.
|
||||
ByteArray long_ble_advertisement_bytes{raw_ble_advertisement_bytes,
|
||||
kLongAdvertisementLength};
|
||||
BleAdvertisement long_ble_advertisement{long_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(long_ble_advertisement.IsValid());
|
||||
EXPECT_TRUE(long_ble_advertisement.IsFastAdvertisement());
|
||||
EXPECT_EQ(kVersion, long_ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, long_ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(fast_data.size(), long_ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(fast_data, long_ble_advertisement.GetData());
|
||||
EXPECT_EQ(device_token, long_ble_advertisement.GetDeviceToken());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) {
|
||||
BleAdvertisement ble_advertisement{ByteArray{}};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data, device_token};
|
||||
ByteArray org_ble_advertisement_bytes{org_ble_advertisement};
|
||||
|
||||
// Cut off the advertisement so that it's too short.
|
||||
ByteArray short_ble_advertisement_bytes{org_ble_advertisement_bytes.data(),
|
||||
7};
|
||||
BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_FALSE(short_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromShortLengthSerializedBytesFailsForFastAdvertisement) {
|
||||
ByteArray fast_data{std::string(kFastData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, ByteArray{},
|
||||
fast_data, device_token};
|
||||
ByteArray org_ble_advertisement_bytes{org_ble_advertisement};
|
||||
|
||||
// Cut off the advertisement so that it's too short.
|
||||
ByteArray short_ble_advertisement_bytes{org_ble_advertisement_bytes.data(),
|
||||
2};
|
||||
BleAdvertisement short_ble_advertisement{short_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_FALSE(short_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromSerializedBytesWithInvalidDataLengthFails) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data, device_token};
|
||||
ByteArray org_ble_advertisement_bytes{org_ble_advertisement};
|
||||
|
||||
// Corrupt the DATA_SIZE bits. Start by making a raw copy of the Ble
|
||||
// advertisement bytes so we can modify it. We must explicitly define how
|
||||
// long our array is because we can't use variable length arrays.
|
||||
char raw_ble_advertisement_bytes[kAdvertisementLength];
|
||||
memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(),
|
||||
kAdvertisementLength);
|
||||
|
||||
// The data size field lives in indices 4-7. Corrupt it.
|
||||
memset(raw_ble_advertisement_bytes + 4, 0xFF, 4);
|
||||
|
||||
// Try to parse the Ble advertisement using our corrupted advertisement bytes.
|
||||
ByteArray corrupted_ble_advertisement_bytes{raw_ble_advertisement_bytes,
|
||||
kAdvertisementLength};
|
||||
BleAdvertisement corrupted_ble_advertisement{
|
||||
corrupted_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_FALSE(corrupted_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromSerializedBytesWithInvalidDataLengthFails2) {
|
||||
ByteArray fast_data{std::string(kFastData)};
|
||||
ByteArray device_token{std::string(kDeviceToken)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion, ByteArray{},
|
||||
fast_data, device_token};
|
||||
ByteArray org_ble_advertisement_bytes{org_ble_advertisement};
|
||||
|
||||
// Corrupt the DATA_SIZE bits. Start by making a raw copy of the Ble
|
||||
// advertisement bytes so we can modify it. We must explicitly define how
|
||||
// long our array is because we can't use variable length arrays.
|
||||
char raw_ble_advertisement_bytes[kFastAdvertisementLength];
|
||||
memcpy(raw_ble_advertisement_bytes, org_ble_advertisement_bytes.data(),
|
||||
kFastAdvertisementLength);
|
||||
|
||||
// The data size field lives in index 1. Corrupt it.
|
||||
memset(raw_ble_advertisement_bytes + 1, 0xFF, 1);
|
||||
|
||||
// Try to parse the Ble advertisement using our corrupted advertisement bytes.
|
||||
ByteArray corrupted_ble_advertisement_bytes{raw_ble_advertisement_bytes,
|
||||
kFastAdvertisementLength};
|
||||
BleAdvertisement corrupted_ble_advertisement{
|
||||
corrupted_ble_advertisement_bytes};
|
||||
|
||||
EXPECT_FALSE(corrupted_ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,73 +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 "core/internal/mediums/ble_v2/ble_packet.h"
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "platform/base/base_input_stream.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BlePacket::BlePacket(const ByteArray& service_id_hash, const ByteArray& data) {
|
||||
if (service_id_hash.size() != kServiceIdHashLength ||
|
||||
data.size() > kMaxDataSize) {
|
||||
return;
|
||||
}
|
||||
service_id_hash_ = service_id_hash;
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
BlePacket::BlePacket(const ByteArray& ble_packet_bytes) {
|
||||
if (ble_packet_bytes.Empty()) {
|
||||
NEARBY_LOG(ERROR, "Cannot deserialize BlePacket: null bytes passed in");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ble_packet_bytes.size() < kServiceIdHashLength) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BlePacket: expecting min %u raw bytes, got %zu",
|
||||
kServiceIdHashLength, ble_packet_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArray packet_bytes{ble_packet_bytes};
|
||||
BaseInputStream base_input_stream{packet_bytes};
|
||||
// The first 3 bytes are supposed to be the service_id_hash.
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
|
||||
// The rest bytes are supposed to be the data.
|
||||
data_ = base_input_stream.ReadBytes(ble_packet_bytes.size() -
|
||||
kServiceIdHashLength);
|
||||
}
|
||||
|
||||
BlePacket::operator ByteArray() const {
|
||||
if (!IsValid()) {
|
||||
return ByteArray();
|
||||
}
|
||||
|
||||
std::string out =
|
||||
absl::StrCat(std::string(service_id_hash_), std::string(data_));
|
||||
|
||||
return ByteArray(std::move(out));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,64 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_PACKET_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_PACKET_H_
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Represents the format of data sent over Ble sockets.
|
||||
//
|
||||
// [SERVICE_ID_HASH][DATA]
|
||||
//
|
||||
// See go/nearby-ble-design for more information.
|
||||
class BlePacket {
|
||||
public:
|
||||
static const std::uint32_t kServiceIdHashLength = 3;
|
||||
|
||||
BlePacket() = default;
|
||||
BlePacket(const ByteArray& service_id_hash, const ByteArray& data);
|
||||
explicit BlePacket(const ByteArray& ble_packet_byte);
|
||||
BlePacket(const BlePacket&) = default;
|
||||
BlePacket& operator=(const BlePacket&) = default;
|
||||
BlePacket(BlePacket&&) = default;
|
||||
BlePacket& operator=(BlePacket&&) = default;
|
||||
~BlePacket() = default;
|
||||
|
||||
explicit operator ByteArray() const;
|
||||
|
||||
bool IsValid() const { return !service_id_hash_.Empty(); }
|
||||
ByteArray GetServiceIdHash() const { return service_id_hash_; }
|
||||
ByteArray GetData() const { return data_; }
|
||||
|
||||
private:
|
||||
static const std::uint32_t kMaxDataSize =
|
||||
std::numeric_limits<int32_t>::max() - kServiceIdHashLength;
|
||||
|
||||
ByteArray service_id_hash_;
|
||||
ByteArray data_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_PACKET_H_
|
||||
@@ -1,111 +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 "core/internal/mediums/ble_v2/ble_packet.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
constexpr absl::string_view kServiceIDHash{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kData{"\x01\x02\x03\x04\x05"};
|
||||
|
||||
TEST(BlePacketTest, ConstructionWorks) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHash)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket ble_packet{service_id_hash, data};
|
||||
|
||||
EXPECT_TRUE(ble_packet.IsValid());
|
||||
EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash());
|
||||
EXPECT_EQ(data, ble_packet.GetData());
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionWorksWithEmptyData) {
|
||||
char empty_data[] = "";
|
||||
|
||||
ByteArray service_id_hash{std::string(kServiceIDHash)};
|
||||
ByteArray data{empty_data};
|
||||
|
||||
BlePacket ble_packet{service_id_hash, data};
|
||||
|
||||
EXPECT_TRUE(ble_packet.IsValid());
|
||||
EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash());
|
||||
EXPECT_EQ(data, ble_packet.GetData());
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) {
|
||||
char short_service_id_hash[] = "\x0a\x0b";
|
||||
|
||||
ByteArray service_id_hash{short_service_id_hash};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket ble_packet(service_id_hash, data);
|
||||
|
||||
EXPECT_FALSE(ble_packet.IsValid());
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) {
|
||||
char long_service_id_hash[] = "\x0a\x0b\x0c\x0d";
|
||||
|
||||
ByteArray service_id_hash{long_service_id_hash};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket ble_packet{service_id_hash, data};
|
||||
|
||||
EXPECT_FALSE(ble_packet.IsValid());
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHash)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket org_ble_packet{service_id_hash, data};
|
||||
ByteArray ble_packet_bytes{org_ble_packet};
|
||||
|
||||
BlePacket ble_packet{ble_packet_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_packet.IsValid());
|
||||
EXPECT_EQ(service_id_hash, ble_packet.GetServiceIdHash());
|
||||
EXPECT_EQ(data, ble_packet.GetData());
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionFromNullBytesFails) {
|
||||
BlePacket ble_packet{ByteArray{}};
|
||||
|
||||
EXPECT_FALSE(ble_packet.IsValid());
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionFromShortLengthDataFails) {
|
||||
ByteArray service_id_hash{std::string(kServiceIDHash)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket org_ble_packet{service_id_hash, data};
|
||||
ByteArray org_ble_packet_bytes{org_ble_packet};
|
||||
|
||||
// Cut off the packet so that it's too short
|
||||
ByteArray short_ble_packet_bytes{ByteArray{org_ble_packet_bytes.data(), 2}};
|
||||
|
||||
BlePacket short_ble_packet{short_ble_packet_bytes};
|
||||
|
||||
EXPECT_FALSE(short_ble_packet.IsValid());
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,49 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_PERIPHERAL_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_PERIPHERAL_H_
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class BlePeripheral {
|
||||
public:
|
||||
BlePeripheral() = default;
|
||||
explicit BlePeripheral(const ByteArray& id) : id_(id) {}
|
||||
BlePeripheral(const BlePeripheral&) = default;
|
||||
BlePeripheral& operator=(const BlePeripheral&) = default;
|
||||
BlePeripheral(BlePeripheral&&) = default;
|
||||
BlePeripheral& operator=(BlePeripheral&&) = default;
|
||||
~BlePeripheral() = default;
|
||||
|
||||
bool IsValid() const { return !id_.Empty(); }
|
||||
ByteArray GetId() const { return id_; }
|
||||
|
||||
private:
|
||||
// A unique identifier for this peripheral. It can be the BLE advertisement it
|
||||
// was found on, or even simply the BLE MAC address.
|
||||
ByteArray id_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_PERIPHERAL_H_
|
||||
@@ -1,47 +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 "core/internal/mediums/ble_v2/ble_peripheral.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
constexpr absl::string_view kId{"AB12"};
|
||||
|
||||
TEST(BlePeripheralTest, ConstructionWorks) {
|
||||
ByteArray id{std::string(kId)};
|
||||
|
||||
BlePeripheral ble_peripheral{id};
|
||||
|
||||
EXPECT_TRUE(ble_peripheral.IsValid());
|
||||
EXPECT_EQ(id, ble_peripheral.GetId());
|
||||
}
|
||||
|
||||
TEST(BlePeripheralTest, ConstructionEmptyFails) {
|
||||
BlePeripheral ble_peripheral;
|
||||
|
||||
EXPECT_FALSE(ble_peripheral.IsValid());
|
||||
EXPECT_TRUE(ble_peripheral.GetId().Empty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,105 +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 "core/internal/mediums/ble_v2/bloom_filter.h"
|
||||
|
||||
#include "absl/numeric/int128.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "src/MurmurHash3.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BloomFilterBase::BloomFilterBase(const ByteArray& bytes, BitSet* bit_set)
|
||||
: bits_(bit_set) {
|
||||
const char* bytes_read_ptr = bytes.data();
|
||||
for (size_t byte_index = 0; byte_index < bytes.size(); byte_index++) {
|
||||
for (size_t bit_index = 0; bit_index < 8; bit_index++) {
|
||||
bits_->Set((byte_index * 8) + bit_index,
|
||||
(*bytes_read_ptr >> bit_index) & 0x01);
|
||||
}
|
||||
bytes_read_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
BloomFilterBase::operator ByteArray() const {
|
||||
// Gets a binary string representation of the bitset where the leftmost
|
||||
// character corresponds to bitset position (total size) - 1.
|
||||
//
|
||||
// If the bitset's internal representation is:
|
||||
// [position 0] 0 0 1 1 0 0 0 1 0 1 0 1 [position 11]
|
||||
// The string representation will be outputted like this:
|
||||
// "1 0 1 0 1 0 0 0 1 1 0 0"
|
||||
std::string bitset_binary_string = bits_->ToString();
|
||||
|
||||
ByteArray result_bytes(GetMinBytesForBits());
|
||||
char* result_bytes_write_ptr = result_bytes.data();
|
||||
// We go through the string backwards because the rightmost character
|
||||
// corresponds to position 0 in the bitset.
|
||||
for (size_t i = bits_->Size(); i > 0; i -= 8) {
|
||||
std::string byte_binary_string = bitset_binary_string.substr(i - 8, 8);
|
||||
std::uint32_t byte_value;
|
||||
absl::numbers_internal::safe_strtou32_base(byte_binary_string, &byte_value,
|
||||
/* base= */ 2);
|
||||
*result_bytes_write_ptr = static_cast<char>(byte_value & 0x000000FF);
|
||||
result_bytes_write_ptr++;
|
||||
}
|
||||
return result_bytes;
|
||||
}
|
||||
|
||||
void BloomFilterBase::Add(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes = GetHashes(s);
|
||||
for (int32_t hash : hashes) {
|
||||
size_t position = static_cast<size_t>(hash) % bits_->Size();
|
||||
bits_->Set(position, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool BloomFilterBase::PossiblyContains(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes = GetHashes(s);
|
||||
for (int32_t hash : hashes) {
|
||||
size_t position = static_cast<size_t>(hash) % bits_->Size();
|
||||
if (!bits_->Test(position)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::int32_t> BloomFilterBase::GetHashes(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes(kHasherNumberOfRepetitions, 0);
|
||||
|
||||
absl::uint128 hash128;
|
||||
MurmurHash3_x64_128(s.data(), s.size(), 0, &hash128);
|
||||
std::uint64_t hash64 =
|
||||
absl::Uint128Low64(hash128); // the lower 64 bits of the 128-bit hash
|
||||
std::int32_t hash1 = static_cast<std::int32_t>(
|
||||
hash64 & 0x00000000FFFFFFFF); // the lower 32 bits of the 64-bit hash
|
||||
std::int32_t hash2 = static_cast<std::int32_t>(
|
||||
(hash64 >> 32) & 0x0FFFFFFFF); // the upper 32 bits of the 64-bit hash
|
||||
for (size_t i = 1; i <= kHasherNumberOfRepetitions; i++) {
|
||||
std::int32_t combinedHash = static_cast<std::int32_t>(hash1 + (i * hash2));
|
||||
// Flip all the bits if it's negative (guaranteed positive number)
|
||||
if (combinedHash < 0) combinedHash = ~combinedHash;
|
||||
hashes[i - 1] = combinedHash;
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,101 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_BLOOM_FILTER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_BLOOM_FILTER_H_
|
||||
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
/**
|
||||
* A bloom filter that gives access to the underlying BitSet. The implementation
|
||||
* is copied from our Java version of Bloom filter, which in turn copies from
|
||||
* Guava's BloomFilter.
|
||||
*
|
||||
* BloomFilter is templatized on the size of the byte array and not the size of
|
||||
* the bit set to ensure the bit set's length is a multiple of 8 (and can
|
||||
* neatly be returned as a ByteArray).
|
||||
*/
|
||||
class BloomFilterBase {
|
||||
public:
|
||||
explicit operator ByteArray() const;
|
||||
|
||||
void Add(const std::string& s);
|
||||
bool PossiblyContains(const std::string& s);
|
||||
|
||||
protected:
|
||||
class BitSet {
|
||||
public:
|
||||
virtual ~BitSet() = default;
|
||||
virtual std::string ToString() const = 0;
|
||||
virtual void Set(size_t pos, bool value) = 0;
|
||||
virtual bool Test(size_t pos) const = 0;
|
||||
virtual size_t Size() const = 0;
|
||||
};
|
||||
|
||||
BloomFilterBase(const ByteArray& bytes, BitSet* bit_set);
|
||||
virtual ~BloomFilterBase() = default;
|
||||
|
||||
constexpr static int kHasherNumberOfRepetitions = 5;
|
||||
std::vector<std::int32_t> GetHashes(const std::string& s);
|
||||
|
||||
private:
|
||||
int GetMinBytesForBits() const { return (bits_->Size() + 7) >> 3; }
|
||||
|
||||
BitSet* bits_;
|
||||
};
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
class BloomFilter final : public BloomFilterBase {
|
||||
public:
|
||||
BloomFilter() : BloomFilterBase(ByteArray{}, &bits_) {}
|
||||
explicit BloomFilter(const ByteArray& bytes)
|
||||
: BloomFilterBase(bytes, &bits_) {}
|
||||
BloomFilter(const BloomFilter&) = default;
|
||||
BloomFilter& operator=(const BloomFilter&) = default;
|
||||
BloomFilter(BloomFilter&& other) : BloomFilterBase(ByteArray{}, &bits_) {
|
||||
*this = std::move(other);
|
||||
}
|
||||
BloomFilter& operator=(BloomFilter&& other) {
|
||||
std::swap((*this).bits_, other.bits_);
|
||||
return *this;
|
||||
}
|
||||
~BloomFilter() override = default;
|
||||
|
||||
private:
|
||||
class BitSetImpl final : public BitSet {
|
||||
public:
|
||||
std::string ToString() const override { return bits_.to_string(); }
|
||||
void Set(size_t pos, bool value) override { bits_.set(pos, value); }
|
||||
bool Test(size_t pos) const override { return bits_.test(pos); }
|
||||
size_t Size() const override { return bits_.size(); }
|
||||
|
||||
private:
|
||||
std::bitset<CapacityInBytes * 8> bits_;
|
||||
} bits_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLOOM_FILTER_H_
|
||||
@@ -1,207 +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 "core/internal/mediums/ble_v2/bloom_filter.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
constexpr size_t kByteArrayLength = 100;
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
std::string empty_string(kByteArrayLength, '\0');
|
||||
|
||||
EXPECT_EQ(empty_string, std::string(bloom_filter_bytes));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterNeverContains) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddOnlyGivenArg) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddMultipleArgs) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddMultipleArgsReturnsNonemptyArray) {
|
||||
BloomFilter<10> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
bloom_filter.Add("ELEMENT_3");
|
||||
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
std::string empty_string(kByteArrayLength, '\0');
|
||||
|
||||
EXPECT_NE(std::string(bloom_filter_bytes), empty_string);
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, CopyConstructorAndAssignmentSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_copy_1{bloom_filter};
|
||||
BloomFilter<kByteArrayLength> bloom_filter_copy_2 = bloom_filter;
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter_copy_1.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter_copy_2.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, MoveConstructorSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_move{std::move(bloom_filter)};
|
||||
|
||||
EXPECT_TRUE(bloom_filter_move.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, MoveAssignmentSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_move = std::move(bloom_filter);
|
||||
|
||||
EXPECT_TRUE(bloom_filter_move.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test was added because of a bug where the BloomFilter doesn't utilize
|
||||
* all bits given. Functionally, the filter still works, but we just have a much
|
||||
* higher false positive rate. The bug was caused by confusing bit length and
|
||||
* byte length, which made our BloomFilter only set bits on the first byteLength
|
||||
* (bitLength / 8) bits rather than the whole bitLength bits.
|
||||
*
|
||||
* <p>Here, we're verifying that the bits set are somewhat scattered. So instead
|
||||
* of something like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting
|
||||
* something like [ 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, ..., 1, 0].
|
||||
*/
|
||||
TEST(BloomFilterTest, RandomnessNoEndBias) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
// Add one element to our BloomFilter.
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
std::int32_t non_zero_count = 0;
|
||||
std::int32_t longest_zero_streak = 0;
|
||||
std::int32_t current_zero_streak = 0;
|
||||
|
||||
// Record the amount of non-zero bytes and the longest streak of zero bytes in
|
||||
// the resulting BloomFilter. This is an approximation of reasonable
|
||||
// distribution since we're recording by bytes instead of bits.
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
const char* bloom_filter_bytes_read_ptr = bloom_filter_bytes.data();
|
||||
for (int i = 0; i < bloom_filter_bytes.size(); i++) {
|
||||
if (*bloom_filter_bytes_read_ptr == '\0') {
|
||||
current_zero_streak++;
|
||||
} else {
|
||||
// Increment the number of non-zero bytes we've seen, update the longest
|
||||
// zero streak, and then reset the current zero streak.
|
||||
non_zero_count++;
|
||||
longest_zero_streak = std::max(longest_zero_streak, current_zero_streak);
|
||||
current_zero_streak = 0;
|
||||
}
|
||||
bloom_filter_bytes_read_ptr++;
|
||||
}
|
||||
// Update the longest zero streak again for the tail case.
|
||||
longest_zero_streak = std::min(longest_zero_streak, current_zero_streak);
|
||||
|
||||
// Since randomness is hard to measure within one unit test, we instead do a
|
||||
// sanity check. All non-zero bytes should not be packed into one end of the
|
||||
// array.
|
||||
//
|
||||
// In this case, the size of one end is approximated to be:
|
||||
// kByteArrayLength / nonZeroCount.
|
||||
// Therefore, the longest zero streak should be less than:
|
||||
// kByteArrayLength - one end of the array.
|
||||
std::int32_t longest_acceptable_zero_streak =
|
||||
kByteArrayLength - (kByteArrayLength / non_zero_count);
|
||||
|
||||
EXPECT_TRUE(longest_zero_streak <= longest_acceptable_zero_streak);
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, RandomnessFalsePositiveRate) {
|
||||
BloomFilter<10> bloom_filter;
|
||||
|
||||
// Add 5 distinct elements to the BloomFilter.
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
bloom_filter.Add("ELEMENT_3");
|
||||
bloom_filter.Add("ELEMENT_4");
|
||||
bloom_filter.Add("ELEMENT_5");
|
||||
|
||||
std::int32_t false_positives = 0;
|
||||
// Now test 100 other elements and record the number of false positives.
|
||||
for (int i = 5; i < 105; i++) {
|
||||
false_positives +=
|
||||
bloom_filter.PossiblyContains("ELEMENT_" + std::to_string(i)) ? 1 : 0;
|
||||
}
|
||||
|
||||
// We expect the false positive rate to be 3% with 5 elements in a 10 byte
|
||||
// filter. Thus, we give a little leeway and verify that the false positive
|
||||
// rate is no more than 5%.
|
||||
EXPECT_LE(false_positives, 5);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,45 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_CALLBACK_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_CALLBACK_H_
|
||||
|
||||
#include "core/internal/mediums/ble_v2/ble_peripheral.h"
|
||||
#include "core/listeners.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
/** Callback that is invoked when a {@link BlePeripheral} is discovered. */
|
||||
struct DiscoveredPeripheralCallback {
|
||||
std::function<void(BlePeripheral& peripheral, const std::string& service_id,
|
||||
const ByteArray& advertisement_byts,
|
||||
bool fast_advertisement)>
|
||||
peripheral_discovered_cb =
|
||||
DefaultCallback<BlePeripheral&, const std::string&, const ByteArray&,
|
||||
bool>();
|
||||
std::function<void(BlePeripheral& peripheral, const std::string& service_id)>
|
||||
peripheral_lost_cb =
|
||||
DefaultCallback<BlePeripheral&, const std::string&>();
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_DISCOVERED_PERIPHERAL_CALLBACK_H_
|
||||
@@ -1,434 +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 "core/internal/mediums/bluetooth_classic.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "core/internal/mediums/uuid.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
BluetoothClassic::BluetoothClassic(BluetoothRadio& radio) : radio_(radio) {}
|
||||
|
||||
BluetoothClassic::~BluetoothClassic() {
|
||||
// Destructor is not taking locks, but methods it is calling are.
|
||||
StopDiscovery();
|
||||
while (!server_sockets_.empty()) {
|
||||
StopAcceptingConnections(server_sockets_.begin()->first);
|
||||
}
|
||||
TurnOffDiscoverability();
|
||||
|
||||
// All the AcceptLoopRunnable objects in here should already have gotten an
|
||||
// opportunity to shut themselves down cleanly in the calls to
|
||||
// StopAcceptingConnections() above.
|
||||
accept_loops_runner_.Shutdown();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsAvailable() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAvailableLocked();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsAvailableLocked() const {
|
||||
return medium_.IsValid() && adapter_.IsValid();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::TurnOnDiscoverability(const std::string& device_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (device_name.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to turn on BT discoverability. Empty device name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is off.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsDiscoverable()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to turn on BT discoverability; new name='%s'; "
|
||||
"current name='%s'",
|
||||
device_name.c_str(), adapter_.GetName().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ModifyDeviceName(device_name)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Failed to turn on BT discoverability; "
|
||||
"failed to set name to %s",
|
||||
device_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ModifyScanMode(ScanMode::kConnectableDiscoverable)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Failed to turn on BT discoverability; "
|
||||
"failed to set scan_mode to %d",
|
||||
ScanMode::kConnectableDiscoverable);
|
||||
|
||||
// Don't forget to perform this rollback of the partial state changes we've
|
||||
// made til now.
|
||||
RestoreDeviceName();
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Turned on BT discoverability with device_name=%s",
|
||||
device_name.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::TurnOffDiscoverability() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsDiscoverable()) {
|
||||
NEARBY_LOG(INFO, "Can't turn off BT discoverability; it is already off");
|
||||
return false;
|
||||
}
|
||||
|
||||
RestoreScanMode();
|
||||
RestoreDeviceName();
|
||||
|
||||
NEARBY_LOG(INFO, "Turned Bluetooth discoverability off");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsDiscoverable() const {
|
||||
return (!original_device_name_.empty() &&
|
||||
(adapter_.GetScanMode() == ScanMode::kConnectableDiscoverable));
|
||||
}
|
||||
|
||||
bool BluetoothClassic::ModifyDeviceName(const std::string& device_name) {
|
||||
if (original_device_name_.empty()) {
|
||||
original_device_name_ = adapter_.GetName();
|
||||
}
|
||||
|
||||
return adapter_.SetName(device_name);
|
||||
}
|
||||
|
||||
bool BluetoothClassic::ModifyScanMode(ScanMode scan_mode) {
|
||||
if (original_scan_mode_ == ScanMode::kUnknown) {
|
||||
original_scan_mode_ = adapter_.GetScanMode();
|
||||
}
|
||||
|
||||
if (!adapter_.SetScanMode(scan_mode)) {
|
||||
original_scan_mode_ = ScanMode::kUnknown;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::RestoreScanMode() {
|
||||
if (original_scan_mode_ == ScanMode::kUnknown ||
|
||||
!adapter_.SetScanMode(original_scan_mode_)) {
|
||||
NEARBY_LOG(INFO, "Failed to restore original Bluetooth scan mode to %d",
|
||||
original_scan_mode_);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Regardless of whether or not we could actually restore the Bluetooth scan
|
||||
// mode, reset our relevant state.
|
||||
original_scan_mode_ = ScanMode::kUnknown;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::RestoreDeviceName() {
|
||||
if (original_device_name_.empty() ||
|
||||
!adapter_.SetName(original_device_name_)) {
|
||||
NEARBY_LOG(INFO, "Failed to restore original Bluetooth device name to %s",
|
||||
original_device_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
original_device_name_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::StartDiscovery(DiscoveredDeviceCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't enabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsDiscovering()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start discovery of BT devices because another "
|
||||
"discovery is already in-progress.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartDiscovery(callback)) {
|
||||
NEARBY_LOG(INFO, "Failed to start discovery of BT devices.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark the fact that we're currently performing a Bluetooth scan.
|
||||
scan_info_.valid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::StopDiscovery() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsDiscovering()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't stop discovery of BT devices because it never started.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StopDiscovery()) {
|
||||
NEARBY_LOG(INFO, "Failed to stop discovery of Bluetooth devices.");
|
||||
return false;
|
||||
}
|
||||
|
||||
scan_info_.valid = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsDiscovering() const { return scan_info_.valid; }
|
||||
|
||||
bool BluetoothClassic::StartAcceptingConnections(
|
||||
const std::string& service_name, AcceptedConnectionCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to start accepting BT connections; service name is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't create BT server socket [service=%s]; BT is disabled.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Can't start accepting BT connections [service=%s]; BT not available.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnectionsLocked(service_name)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start accepting BT connections [service=%s]; BT "
|
||||
"server is already in-progress with the same name.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
BluetoothServerSocket socket = medium_.ListenForService(
|
||||
service_name, GenerateUuidFromString(service_name));
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOG(INFO, "Failed to start accepting Bluetooth connections for %s.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark the fact that there's an in-progress Bluetooth server accepting
|
||||
// connections.
|
||||
auto owned_socket =
|
||||
server_sockets_.emplace(service_name, std::move(socket)).first->second;
|
||||
|
||||
// Start the accept loop on a dedicated thread - this stays alive and
|
||||
// listening for new incoming connections until StopAcceptingConnections() is
|
||||
// invoked.
|
||||
accept_loops_runner_.Execute(
|
||||
"bt-accept",
|
||||
[callback = std::move(callback), server_socket = std::move(owned_socket),
|
||||
service_name]() mutable {
|
||||
while (true) {
|
||||
BluetoothSocket client_socket = server_socket.Accept();
|
||||
if (!client_socket.IsValid()) {
|
||||
server_socket.Close();
|
||||
break;
|
||||
}
|
||||
|
||||
callback.accepted_cb(std::move(client_socket));
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsAcceptingConnections(const std::string& service_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAcceptingConnectionsLocked(service_name);
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsAcceptingConnectionsLocked(
|
||||
const std::string& service_name) {
|
||||
return server_sockets_.find(service_name) != server_sockets_.end();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::StopAcceptingConnections(
|
||||
const std::string& service_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to stop accepting BT connections because the "
|
||||
"service_name is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& it = server_sockets_.find(service_name);
|
||||
if (it == server_sockets_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't stop accepting BT connections for %s because it was "
|
||||
"never started.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Closing the BluetoothServerSocket will kick off the suicide of the thread
|
||||
// in accept_loops_thread_pool_ that blocks on BluetoothServerSocket.accept().
|
||||
// That may take some time to complete, but there's no particular reason to
|
||||
// wait around for it.
|
||||
auto item = server_sockets_.extract(it);
|
||||
|
||||
// Store a handle to the BluetoothServerSocket, so we can use it after
|
||||
// removing the entry from server_sockets_; making it scoped
|
||||
// is a bonus that takes care of deallocation before we leave this method.
|
||||
BluetoothServerSocket& listening_socket = item.mapped();
|
||||
|
||||
// Regardless of whether or not we fail to close the existing
|
||||
// BluetoothServerSocket, remove it from server_sockets_ so that it
|
||||
// frees up this service for another round.
|
||||
|
||||
// Finally, close the BluetoothServerSocket.
|
||||
if (!listening_socket.Close().Ok()) {
|
||||
NEARBY_LOG(INFO, "Failed to close BT server socket for %s.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device,
|
||||
const std::string& service_name,
|
||||
CancellationFlag* cancellation_flag) {
|
||||
for (int attempts_count = 0; attempts_count < kConnectAttemptsLimit;
|
||||
attempts_count++) {
|
||||
auto wrapper_result =
|
||||
AttemptToConnect(bluetooth_device, service_name, cancellation_flag);
|
||||
if (wrapper_result.IsValid()) {
|
||||
return wrapper_result;
|
||||
}
|
||||
}
|
||||
return BluetoothSocket();
|
||||
}
|
||||
|
||||
BluetoothSocket BluetoothClassic::AttemptToConnect(
|
||||
BluetoothDevice& bluetooth_device, const std::string& service_name,
|
||||
CancellationFlag* cancellation_flag) {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOG(INFO, "BluetoothClassic::Connect: device=%p", &bluetooth_device);
|
||||
// Socket to return. To allow for NRVO to work, it has to be a single object.
|
||||
BluetoothSocket socket;
|
||||
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to create client BT socket because service_name is empty.");
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't create client BT socket [service=%s]: BT isn't enabled.",
|
||||
service_name.c_str());
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Can't create client BT socket [service=%s]; BT isn't available.",
|
||||
service_name.c_str());
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (cancellation_flag->Cancelled()) {
|
||||
NEARBY_LOGS(INFO) << "Can't create client BT socket due to cancel.";
|
||||
return socket;
|
||||
}
|
||||
|
||||
socket = medium_.ConnectToService(bluetooth_device,
|
||||
GenerateUuidFromString(service_name),
|
||||
cancellation_flag);
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOG(INFO, "Failed to Connect via BT [service=%s]",
|
||||
service_name.c_str());
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
BluetoothDevice BluetoothClassic::GetRemoteDevice(
|
||||
const std::string& mac_address) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return medium_.GetRemoteDevice(mac_address);
|
||||
}
|
||||
|
||||
std::string BluetoothClassic::GetMacAddress() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return medium_.GetMacAddress();
|
||||
}
|
||||
|
||||
std::string BluetoothClassic::GenerateUuidFromString(const std::string& data) {
|
||||
return std::string(Uuid(data));
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,210 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "core/listeners.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/cancellation_flag.h"
|
||||
#include "platform/public/bluetooth_adapter.h"
|
||||
#include "platform/public/bluetooth_classic.h"
|
||||
#include "platform/public/multi_thread_executor.h"
|
||||
#include "platform/public/mutex.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class BluetoothClassic {
|
||||
public:
|
||||
using DiscoveredDeviceCallback = BluetoothClassicMedium::DiscoveryCallback;
|
||||
using ScanMode = BluetoothAdapter::ScanMode;
|
||||
|
||||
// Callback that is invoked when a new connection is accepted.
|
||||
struct AcceptedConnectionCallback {
|
||||
std::function<void(BluetoothSocket socket)> accepted_cb =
|
||||
DefaultCallback<BluetoothSocket>();
|
||||
};
|
||||
|
||||
explicit BluetoothClassic(BluetoothRadio& bluetooth_radio);
|
||||
~BluetoothClassic();
|
||||
|
||||
// Returns true, if BT communications are supported by a platform.
|
||||
bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Sets custom device name, and then enables BT discoverable mode.
|
||||
// Returns true, if name and scan mode are successfully set, and false
|
||||
// otherwise.
|
||||
// Called by server.
|
||||
bool TurnOnDiscoverability(const std::string& device_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Disables BT discoverability, and restores scan mode and device name to
|
||||
// what they were before the call to TurnOnDiscoverability().
|
||||
// Returns false if no successful call TurnOnDiscoverability() was previously
|
||||
// made, otherwise returns true.
|
||||
// Called by server.
|
||||
bool TurnOffDiscoverability() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Enables BT discovery mode. Will report any discoverable devices in range
|
||||
// through a callback.
|
||||
// Returns true, if discovery mode was enabled, false otherwise.
|
||||
// Called by client.
|
||||
bool StartDiscovery(DiscoveredDeviceCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Disables BT discovery mode.
|
||||
// Returns true, if discovery mode was previously enabled, false otherwise.
|
||||
// Called by client.
|
||||
bool StopDiscovery() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Starts a worker thread, creates a BT server socket, associates it with a
|
||||
// service name; in a worker thread repeatedly calls ServerSocket::Accept().
|
||||
// Any connected sockets returned from Accept() are passed to a callback.
|
||||
// Returns true, if server socket was successfully created, false otherwise.
|
||||
// Called by server.
|
||||
bool StartAcceptingConnections(const std::string& service_name,
|
||||
AcceptedConnectionCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true, if object is currently running a Accept() loop.
|
||||
bool IsAcceptingConnections(const std::string& service_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Closes server socket corresponding to a service name. This automatically
|
||||
// terminates Accept() loop, if it were running.
|
||||
// Called by server.
|
||||
bool StopAcceptingConnections(const std::string& service_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if this object owns a valid platform implementation.
|
||||
bool IsMediumValid() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
MutexLock lock(&mutex_);
|
||||
return medium_.IsValid();
|
||||
}
|
||||
|
||||
// Returns true if this object has a valid BluetoothAdapter reference.
|
||||
bool IsAdapterValid() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
MutexLock lock(&mutex_);
|
||||
return adapter_.IsValid();
|
||||
}
|
||||
|
||||
// Establishes connection to BT service with internal retry for maximum
|
||||
// attempts of kConnectAttemptsLimit.
|
||||
// Blocks until connection is established, or server-side is terminated.
|
||||
// Returns socket instance. On success, BluetoothSocket.IsValid() return true.
|
||||
// Called by client.
|
||||
BluetoothSocket Connect(BluetoothDevice& bluetooth_device,
|
||||
const std::string& service_name,
|
||||
CancellationFlag* cancellation_flag)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
std::string GetMacAddress() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
BluetoothDevice GetRemoteDevice(const std::string& mac_address)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
struct ScanInfo {
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static constexpr int kMaxConcurrentAcceptLoops = 5;
|
||||
|
||||
static constexpr int kConnectAttemptsLimit = 3;
|
||||
|
||||
// Constructs UUID object from arbitrary string, using MD5 hash, and then
|
||||
// converts UUID to a readable UUID string and returns it.
|
||||
static std::string GenerateUuidFromString(const std::string& data);
|
||||
|
||||
// Same as IsAvailable(), but must be called with mutex_ held.
|
||||
bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Same as IsAcceptingConnections(), but must be called with mutex_ held.
|
||||
bool IsAcceptingConnectionsLocked(const std::string& service_name)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true, if discoverability is enabled with TurnOnDiscoverability().
|
||||
bool IsDiscoverable() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Assignes a different name to BT adapter.
|
||||
// Returns true if successful. Stores original device name.
|
||||
bool ModifyDeviceName(const std::string& device_name)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Changes current scan mode. This is an implementation of
|
||||
// Turn<On/Off>Discoveradility() method. Stores original scan mode.
|
||||
bool ModifyScanMode(ScanMode scan_mode) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Restores original device name (the one before the very first call to
|
||||
// ModifyDeviceName()). Returns true if successful.
|
||||
bool RestoreScanMode() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Restores original device scan mode (the one before the very first call to
|
||||
// ModifyScanMode()). Returns true if successful.
|
||||
bool RestoreDeviceName() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if device is currently in discovery mode.
|
||||
bool IsDiscovering() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Establishes connection to BT service that was might be started on another
|
||||
// device with StartAcceptingConnections() using the same service_name.
|
||||
// Blocks until connection is established, or server-side is terminated.
|
||||
// Returns socket instance. On success, BluetoothSocket.IsValid() return true.
|
||||
// Called by client.
|
||||
BluetoothSocket AttemptToConnect(BluetoothDevice& bluetooth_device,
|
||||
const std::string& service_name,
|
||||
CancellationFlag* cancellation_flag);
|
||||
|
||||
mutable Mutex mutex_;
|
||||
BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_);
|
||||
BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_){
|
||||
radio_.GetBluetoothAdapter()};
|
||||
BluetoothClassicMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_};
|
||||
|
||||
// A bundle of state required to do a Bluetooth Classic scan. When non-null,
|
||||
// we are currently performing a Bluetooth scan.
|
||||
ScanInfo scan_info_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// The original scan mode (that controls visibility to scanners) of the device
|
||||
// before we modified it. Restored when we stop advertising.
|
||||
ScanMode original_scan_mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kUnknown;
|
||||
|
||||
// The original Bluetooth device name, before we modified it. If non-empty, we
|
||||
// are currently Bluetooth discoverable. Restored when we stop advertising.
|
||||
std::string original_device_name_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// A thread pool dedicated to running all the accept loops from
|
||||
// StartAcceptingConnections().
|
||||
MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops};
|
||||
|
||||
// A map of service Name -> ServerSocket. If map is non-empty, we
|
||||
// are currently listening for incoming connections.
|
||||
// BluetoothServerSocket instances are used from accept_loops_runner_,
|
||||
// and thus require pointer stability.
|
||||
absl::flat_hash_map<std::string, BluetoothServerSocket> server_sockets_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
|
||||
@@ -1,291 +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 "core/internal/mediums/bluetooth_classic.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "platform/base/medium_environment.h"
|
||||
#include "platform/public/bluetooth_classic.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
using FeatureFlags = FeatureFlags::Flags;
|
||||
|
||||
constexpr FeatureFlags kTestCases[] = {
|
||||
FeatureFlags{
|
||||
.enable_cancellation_flag = true,
|
||||
},
|
||||
FeatureFlags{
|
||||
.enable_cancellation_flag = false,
|
||||
},
|
||||
};
|
||||
|
||||
constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000);
|
||||
|
||||
class BluetoothClassicTest : public ::testing::TestWithParam<FeatureFlags> {
|
||||
protected:
|
||||
using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback;
|
||||
|
||||
BluetoothClassicTest() {
|
||||
env_.Start();
|
||||
env_.Reset();
|
||||
radio_a_ = std::make_unique<BluetoothRadio>();
|
||||
radio_b_ = std::make_unique<BluetoothRadio>();
|
||||
bt_a_ = std::make_unique<BluetoothClassic>(*radio_a_);
|
||||
bt_b_ = std::make_unique<BluetoothClassic>(*radio_b_);
|
||||
radio_a_->GetBluetoothAdapter().SetName("Device-A");
|
||||
radio_b_->GetBluetoothAdapter().SetName("Device-B");
|
||||
radio_a_->Enable();
|
||||
radio_b_->Enable();
|
||||
env_.Sync();
|
||||
}
|
||||
|
||||
~BluetoothClassicTest() override {
|
||||
env_.Sync(false);
|
||||
radio_a_->Disable();
|
||||
radio_b_->Disable();
|
||||
bt_a_.reset();
|
||||
bt_b_.reset();
|
||||
env_.Sync(false);
|
||||
radio_a_.reset();
|
||||
radio_b_.reset();
|
||||
env_.Reset();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
|
||||
std::unique_ptr<BluetoothRadio> radio_a_;
|
||||
std::unique_ptr<BluetoothRadio> radio_b_;
|
||||
std::unique_ptr<BluetoothClassic> bt_a_;
|
||||
std::unique_ptr<BluetoothClassic> bt_b_;
|
||||
};
|
||||
|
||||
TEST_P(BluetoothClassicTest, CanConnect) {
|
||||
FeatureFlags feature_flags = GetParam();
|
||||
env_.SetFeatureFlags(feature_flags);
|
||||
|
||||
constexpr absl::string_view kDeviceName{"Simulated BT device #1"};
|
||||
constexpr absl::string_view kServiceName{"service name"};
|
||||
|
||||
BluetoothRadio& radio_for_client = *radio_a_;
|
||||
BluetoothRadio& radio_for_server = *radio_b_;
|
||||
BluetoothClassic& bt_client = *bt_a_;
|
||||
BluetoothClassic& bt_server = *bt_b_;
|
||||
|
||||
EXPECT_TRUE(radio_for_client.IsEnabled());
|
||||
EXPECT_TRUE(radio_for_server.IsEnabled());
|
||||
|
||||
EXPECT_TRUE(bt_server.TurnOnDiscoverability(std::string(kDeviceName)));
|
||||
EXPECT_EQ(radio_for_server.GetBluetoothAdapter().GetName(),
|
||||
std::string(kDeviceName));
|
||||
CountDownLatch latch(1);
|
||||
BluetoothDevice discovered_device;
|
||||
EXPECT_TRUE(bt_client.StartDiscovery({
|
||||
.device_discovered_cb =
|
||||
[&latch, &discovered_device](BluetoothDevice& device) {
|
||||
discovered_device = device;
|
||||
NEARBY_LOG(INFO, "Discovered device=%p [impl=%p]", &device,
|
||||
&device.GetImpl());
|
||||
latch.CountDown();
|
||||
},
|
||||
}));
|
||||
EXPECT_TRUE(latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(bt_server.TurnOffDiscoverability());
|
||||
ASSERT_TRUE(discovered_device.IsValid());
|
||||
BluetoothSocket socket_for_server;
|
||||
CountDownLatch accept_latch(1);
|
||||
EXPECT_TRUE(bt_server.StartAcceptingConnections(
|
||||
std::string(kServiceName),
|
||||
{
|
||||
.accepted_cb =
|
||||
[&socket_for_server, &accept_latch](BluetoothSocket socket) {
|
||||
socket_for_server = std::move(socket);
|
||||
accept_latch.CountDown();
|
||||
},
|
||||
}));
|
||||
CancellationFlag flag;
|
||||
BluetoothSocket socket_for_client =
|
||||
bt_client.Connect(discovered_device, std::string(kServiceName), &flag);
|
||||
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(bt_server.StopAcceptingConnections(std::string(kServiceName)));
|
||||
EXPECT_TRUE(socket_for_server.IsValid());
|
||||
EXPECT_TRUE(socket_for_client.IsValid());
|
||||
EXPECT_TRUE(socket_for_server.GetRemoteDevice().IsValid());
|
||||
EXPECT_TRUE(socket_for_client.GetRemoteDevice().IsValid());
|
||||
}
|
||||
|
||||
TEST_P(BluetoothClassicTest, CanCancelConnect) {
|
||||
FeatureFlags feature_flags = GetParam();
|
||||
env_.SetFeatureFlags(feature_flags);
|
||||
|
||||
constexpr absl::string_view kDeviceName{"Simulated BT device #1"};
|
||||
constexpr absl::string_view kServiceName{"service name"};
|
||||
|
||||
BluetoothRadio& radio_for_client = *radio_a_;
|
||||
BluetoothRadio& radio_for_server = *radio_b_;
|
||||
BluetoothClassic& bt_client = *bt_a_;
|
||||
BluetoothClassic& bt_server = *bt_b_;
|
||||
|
||||
EXPECT_TRUE(radio_for_client.IsEnabled());
|
||||
EXPECT_TRUE(radio_for_server.IsEnabled());
|
||||
|
||||
EXPECT_TRUE(bt_server.TurnOnDiscoverability(std::string(kDeviceName)));
|
||||
EXPECT_EQ(radio_for_server.GetBluetoothAdapter().GetName(),
|
||||
std::string(kDeviceName));
|
||||
CountDownLatch latch(1);
|
||||
BluetoothDevice discovered_device;
|
||||
EXPECT_TRUE(bt_client.StartDiscovery({
|
||||
.device_discovered_cb =
|
||||
[&latch, &discovered_device](BluetoothDevice& device) {
|
||||
discovered_device = device;
|
||||
NEARBY_LOG(INFO, "Discovered device=%p [impl=%p]", &device,
|
||||
&device.GetImpl());
|
||||
latch.CountDown();
|
||||
},
|
||||
}));
|
||||
EXPECT_TRUE(latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(bt_server.TurnOffDiscoverability());
|
||||
ASSERT_TRUE(discovered_device.IsValid());
|
||||
BluetoothSocket socket_for_server;
|
||||
CountDownLatch accept_latch(1);
|
||||
EXPECT_TRUE(bt_server.StartAcceptingConnections(
|
||||
std::string(kServiceName),
|
||||
{
|
||||
.accepted_cb =
|
||||
[&socket_for_server, &accept_latch](BluetoothSocket socket) {
|
||||
socket_for_server = std::move(socket);
|
||||
accept_latch.CountDown();
|
||||
},
|
||||
}));
|
||||
CancellationFlag flag(true);
|
||||
BluetoothSocket socket_for_client =
|
||||
bt_client.Connect(discovered_device, std::string(kServiceName), &flag);
|
||||
// If FeatureFlag is disabled, Cancelled is false as no-op.
|
||||
if (!feature_flags.enable_cancellation_flag) {
|
||||
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(bt_server.StopAcceptingConnections(std::string(kServiceName)));
|
||||
EXPECT_TRUE(socket_for_server.IsValid());
|
||||
EXPECT_TRUE(socket_for_client.IsValid());
|
||||
EXPECT_TRUE(socket_for_server.GetRemoteDevice().IsValid());
|
||||
EXPECT_TRUE(socket_for_client.GetRemoteDevice().IsValid());
|
||||
} else {
|
||||
EXPECT_FALSE(accept_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(bt_server.StopAcceptingConnections(std::string(kServiceName)));
|
||||
EXPECT_FALSE(socket_for_server.IsValid());
|
||||
EXPECT_FALSE(socket_for_client.IsValid());
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(ParametrisedBluetoothClassicTest, BluetoothClassicTest,
|
||||
::testing::ValuesIn(kTestCases));
|
||||
|
||||
TEST_F(BluetoothClassicTest, CanConstructValidObject) {
|
||||
EXPECT_TRUE(bt_a_->IsMediumValid());
|
||||
EXPECT_TRUE(bt_a_->IsAdapterValid());
|
||||
EXPECT_TRUE(bt_a_->IsAvailable());
|
||||
EXPECT_TRUE(bt_b_->IsMediumValid());
|
||||
EXPECT_TRUE(bt_b_->IsAdapterValid());
|
||||
EXPECT_TRUE(bt_b_->IsAvailable());
|
||||
EXPECT_NE(&radio_a_->GetBluetoothAdapter(), &radio_b_->GetBluetoothAdapter());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothClassicTest, CanStartAdvertising) {
|
||||
constexpr absl::string_view kDeviceName{"Simulated BT device #1"};
|
||||
EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName)));
|
||||
EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName);
|
||||
}
|
||||
|
||||
TEST_F(BluetoothClassicTest, CanStopAdvertising) {
|
||||
constexpr absl::string_view kDeviceName{"Simulated BT device #1"};
|
||||
EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName)));
|
||||
EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName);
|
||||
EXPECT_TRUE(bt_a_->TurnOffDiscoverability());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothClassicTest, CanStartDiscovery) {
|
||||
constexpr absl::string_view kDeviceName{"Simulated BT device #1"};
|
||||
EXPECT_TRUE(bt_a_->TurnOnDiscoverability(std::string(kDeviceName)));
|
||||
EXPECT_EQ(radio_a_->GetBluetoothAdapter().GetName(), kDeviceName);
|
||||
CountDownLatch latch(1);
|
||||
EXPECT_TRUE(bt_b_->StartDiscovery({
|
||||
.device_discovered_cb =
|
||||
[&latch](BluetoothDevice& device) { latch.CountDown(); },
|
||||
}));
|
||||
EXPECT_TRUE(latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(bt_a_->TurnOffDiscoverability());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothClassicTest, CanStopDiscovery) {
|
||||
CountDownLatch latch(1);
|
||||
EXPECT_TRUE(bt_a_->StartDiscovery({
|
||||
.device_discovered_cb =
|
||||
[&latch](BluetoothDevice& device) { latch.CountDown(); },
|
||||
}));
|
||||
EXPECT_FALSE(latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(bt_a_->StopDiscovery());
|
||||
}
|
||||
|
||||
TEST_F(BluetoothClassicTest, CanStartAcceptingConnections) {
|
||||
constexpr absl::string_view kDeviceName{"Simulated BT device #1"};
|
||||
constexpr absl::string_view kServiceName{"service name"};
|
||||
|
||||
BluetoothRadio& radio_for_client = *radio_a_;
|
||||
BluetoothRadio& radio_for_server = *radio_b_;
|
||||
BluetoothClassic& bt_client = *bt_a_;
|
||||
BluetoothClassic& bt_server = *bt_b_;
|
||||
|
||||
EXPECT_TRUE(radio_for_client.IsEnabled());
|
||||
EXPECT_TRUE(radio_for_server.IsEnabled());
|
||||
|
||||
EXPECT_TRUE(bt_server.TurnOnDiscoverability(std::string(kDeviceName)));
|
||||
EXPECT_EQ(radio_for_server.GetBluetoothAdapter().GetName(), kDeviceName);
|
||||
CountDownLatch latch(1);
|
||||
BluetoothDevice discovered_device;
|
||||
EXPECT_TRUE(bt_client.StartDiscovery({
|
||||
.device_discovered_cb =
|
||||
[&latch, &discovered_device](BluetoothDevice& device) {
|
||||
discovered_device = device;
|
||||
NEARBY_LOG(INFO, "Discovered device=%p [impl=%p]", &device,
|
||||
&device.GetImpl());
|
||||
latch.CountDown();
|
||||
},
|
||||
}));
|
||||
EXPECT_TRUE(latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(bt_server.TurnOffDiscoverability());
|
||||
EXPECT_TRUE(discovered_device.IsValid());
|
||||
EXPECT_TRUE(
|
||||
bt_server.StartAcceptingConnections(std::string(kServiceName), {}));
|
||||
// Allow StartAcceptingConnections do something, before stopping it.
|
||||
// This is best effort, because no callbacks are invoked in this scenario.
|
||||
SystemClock::Sleep(kWaitDuration);
|
||||
EXPECT_TRUE(bt_server.StopAcceptingConnections(std::string(kServiceName)));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,120 +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 "core/internal/mediums/bluetooth_radio.h"
|
||||
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
constexpr absl::Duration BluetoothRadio::kPauseBetweenToggle;
|
||||
|
||||
BluetoothRadio::BluetoothRadio() {
|
||||
if (!IsAdapterValid()) {
|
||||
NEARBY_LOG(ERROR, "Bluetooth adapter is not valid: BT is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothRadio::~BluetoothRadio() {
|
||||
// We never enabled Bluetooth, nothing to do.
|
||||
if (!ever_saved_state_.Get()) {
|
||||
NEARBY_LOG(INFO, "BT adapter was not used. Not touching HW.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle Bluetooth regardless of our original state. Some devices/chips can
|
||||
// start to freak out after some time (e.g. b/37775337), and this helps to
|
||||
// ensure BT resets properly.
|
||||
NEARBY_LOG(INFO, "Toggle BT adapter state before releasing adapter.");
|
||||
Toggle();
|
||||
|
||||
NEARBY_LOG(INFO, "Bring BT adapter to original state");
|
||||
if (!SetBluetoothState(originally_enabled_.Get())) {
|
||||
NEARBY_LOG(INFO, "Failed to restore BT adapter original state.");
|
||||
}
|
||||
}
|
||||
|
||||
bool BluetoothRadio::Enable() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SetBluetoothState(true);
|
||||
}
|
||||
|
||||
bool BluetoothRadio::Disable() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SetBluetoothState(false);
|
||||
}
|
||||
|
||||
bool BluetoothRadio::IsEnabled() const {
|
||||
return IsAdapterValid() && IsInDesiredState(true);
|
||||
}
|
||||
|
||||
bool BluetoothRadio::Toggle() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetBluetoothState(false)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT off.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SystemClock::Sleep(kPauseBetweenToggle).Raised(Exception::kInterrupted)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: interrupted before turing on.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetBluetoothState(true)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT on.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothRadio::SetBluetoothState(bool enable) {
|
||||
return bluetooth_adapter_.SetStatus(
|
||||
enable ? BluetoothAdapter::Status::kEnabled
|
||||
: BluetoothAdapter::Status::kDisabled);
|
||||
}
|
||||
|
||||
bool BluetoothRadio::IsInDesiredState(bool should_be_enabled) const {
|
||||
return bluetooth_adapter_.IsEnabled() == should_be_enabled;
|
||||
}
|
||||
|
||||
bool BluetoothRadio::SaveOriginalState() {
|
||||
if (!IsAdapterValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we haven't saved the original state of the radio, save it.
|
||||
if (!ever_saved_state_.Set(true)) {
|
||||
originally_enabled_.Set(bluetooth_adapter_.IsEnabled());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,90 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "absl/time/clock.h"
|
||||
#include "platform/public/atomic_boolean.h"
|
||||
#include "platform/public/bluetooth_adapter.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Provides the operations that can be performed on the Bluetooth radio.
|
||||
class BluetoothRadio {
|
||||
public:
|
||||
BluetoothRadio();
|
||||
BluetoothRadio(BluetoothRadio&&) = default;
|
||||
BluetoothRadio& operator=(BluetoothRadio&&) = default;
|
||||
|
||||
// Reverts the Bluetooth radio to its original state.
|
||||
~BluetoothRadio();
|
||||
|
||||
// Enables Bluetooth.
|
||||
//
|
||||
// This must be called before attempting to invoke any other methods of
|
||||
// this class.
|
||||
//
|
||||
// Returns true if enabled successfully.
|
||||
bool Enable();
|
||||
|
||||
// Disables Bluetooth.
|
||||
//
|
||||
// Returns true if disabled successfully.
|
||||
bool Disable();
|
||||
|
||||
// Returns true if the Bluetooth radio is currently enabled.
|
||||
bool IsEnabled() const;
|
||||
|
||||
// Turn BT radio Off, delay for kPauseBetweenToggle and then turn it On.
|
||||
// This will block calling thread for at least kPauseBetweenToggle duration.
|
||||
bool Toggle();
|
||||
|
||||
// Returns result of BluetoothAdapter::IsValid() for private adapter instance.
|
||||
bool IsAdapterValid() const { return bluetooth_adapter_.IsValid(); }
|
||||
|
||||
BluetoothAdapter& GetBluetoothAdapter() { return bluetooth_adapter_; }
|
||||
|
||||
private:
|
||||
static constexpr absl::Duration kPauseBetweenToggle = absl::Seconds(3);
|
||||
|
||||
bool SetBluetoothState(bool enable);
|
||||
bool IsInDesiredState(bool should_be_enabled) const;
|
||||
// To be called in enable(), disable(), and toggle(). This will remember the
|
||||
// original state of the radio before any radio state has been modified.
|
||||
// Returns false if Bluetooth doesn't exist on the device and the state cannot
|
||||
// be obtained.
|
||||
bool SaveOriginalState();
|
||||
|
||||
// BluetoothAdapter::IsValid() will return false if BT is not supported.
|
||||
BluetoothAdapter bluetooth_adapter_;
|
||||
|
||||
// The Bluetooth radio's original state, before we modified it. True if
|
||||
// originally enabled, false if originally disabled.
|
||||
// We restore the radio to its original state in the destructor.
|
||||
|
||||
AtomicBoolean originally_enabled_{false};
|
||||
// false if we never modified the radio state, true otherwise.
|
||||
AtomicBoolean ever_saved_state_{false};
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
|
||||
@@ -1,60 +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 "core/internal/mediums/bluetooth_radio.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
TEST(BluetoothRadioTest, ConstructorDestructorWorks) {
|
||||
BluetoothRadio radio;
|
||||
EXPECT_TRUE(radio.IsAdapterValid());
|
||||
}
|
||||
|
||||
TEST(BluetoothRadioTest, CanEnable) {
|
||||
BluetoothRadio radio;
|
||||
EXPECT_TRUE(radio.IsAdapterValid());
|
||||
EXPECT_FALSE(radio.IsEnabled());
|
||||
EXPECT_TRUE(radio.Enable());
|
||||
EXPECT_TRUE(radio.IsEnabled());
|
||||
}
|
||||
|
||||
TEST(BluetoothRadioTest, CanDisable) {
|
||||
BluetoothRadio radio;
|
||||
EXPECT_TRUE(radio.IsAdapterValid());
|
||||
EXPECT_FALSE(radio.IsEnabled());
|
||||
EXPECT_TRUE(radio.Enable());
|
||||
EXPECT_TRUE(radio.IsEnabled());
|
||||
EXPECT_TRUE(radio.Disable());
|
||||
EXPECT_FALSE(radio.IsEnabled());
|
||||
}
|
||||
|
||||
TEST(BluetoothRadioTest, CanToggle) {
|
||||
BluetoothRadio radio;
|
||||
EXPECT_TRUE(radio.IsAdapterValid());
|
||||
EXPECT_FALSE(radio.IsEnabled());
|
||||
EXPECT_TRUE(radio.Toggle());
|
||||
EXPECT_TRUE(radio.IsEnabled());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,94 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Tracks "lost" entities based on a manual update/compute model. Used by
|
||||
// mediums that only report found devices. Lost entities are computed based off
|
||||
// of whether a specific entity was rediscovered since the last call to
|
||||
// ComputeLostEntities.
|
||||
//
|
||||
// Note: Entity must overload the < and == operators.
|
||||
template <typename Entity>
|
||||
class LostEntityTracker {
|
||||
public:
|
||||
using EntitySet = absl::flat_hash_set<Entity>;
|
||||
|
||||
LostEntityTracker();
|
||||
~LostEntityTracker();
|
||||
|
||||
// Records the given entity as being recently found, whether or not this is
|
||||
// our first time discovering the entity.
|
||||
void RecordFoundEntity(const Entity& entity) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Computes and returns the set of entities considered lost since the last
|
||||
// time this method was called.
|
||||
EntitySet ComputeLostEntities() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
Mutex mutex_;
|
||||
EntitySet current_entities_ ABSL_GUARDED_BY(mutex_);
|
||||
EntitySet previously_found_entities_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
template <typename Entity>
|
||||
LostEntityTracker<Entity>::LostEntityTracker()
|
||||
: current_entities_{}, previously_found_entities_{} {}
|
||||
|
||||
template <typename Entity>
|
||||
LostEntityTracker<Entity>::~LostEntityTracker() {
|
||||
previously_found_entities_.clear();
|
||||
current_entities_.clear();
|
||||
}
|
||||
|
||||
template <typename Entity>
|
||||
void LostEntityTracker<Entity>::RecordFoundEntity(const Entity& entity) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
current_entities_.insert(entity);
|
||||
}
|
||||
|
||||
template <typename Entity>
|
||||
typename LostEntityTracker<Entity>::EntitySet
|
||||
LostEntityTracker<Entity>::ComputeLostEntities() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// The set of lost entities is the previously found set MINUS the currently
|
||||
// found set.
|
||||
for (const auto& item : current_entities_) {
|
||||
previously_found_entities_.erase(item);
|
||||
}
|
||||
auto lost_entities = std::move(previously_found_entities_);
|
||||
previously_found_entities_ = std::move(current_entities_);
|
||||
current_entities_ = {};
|
||||
|
||||
return lost_entities;
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
@@ -1,137 +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 "core/internal/mediums/lost_entity_tracker.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
struct TestEntity {
|
||||
int id;
|
||||
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const TestEntity& test_entity) {
|
||||
return H::combine(std::move(h), test_entity.id);
|
||||
}
|
||||
|
||||
bool operator==(const TestEntity& other) const { return id == other.id; }
|
||||
bool operator<(const TestEntity& other) const { return id < other.id; }
|
||||
};
|
||||
|
||||
TEST(LostEntityTrackerTest, NoEntitiesLost) {
|
||||
LostEntityTracker<TestEntity> lost_entity_tracker;
|
||||
TestEntity entity_1{1};
|
||||
TestEntity entity_2{2};
|
||||
TestEntity entity_3{3};
|
||||
|
||||
// Discover some entities.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_3);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
ASSERT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Rediscover the same entities.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_3);
|
||||
|
||||
// Make sure we still didn't lose any entities.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
}
|
||||
|
||||
TEST(LostEntityTrackerTest, AllEntitiesLost) {
|
||||
LostEntityTracker<TestEntity> lost_entity_tracker;
|
||||
TestEntity entity_1{1};
|
||||
TestEntity entity_2{2};
|
||||
TestEntity entity_3{3};
|
||||
|
||||
// Discover some entities.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_3);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Go through a round without rediscovering any entities.
|
||||
typename LostEntityTracker<TestEntity>::EntitySet lost_entities =
|
||||
lost_entity_tracker.ComputeLostEntities();
|
||||
EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_3) != lost_entities.end());
|
||||
}
|
||||
|
||||
TEST(LostEntityTrackerTest, SomeEntitiesLost) {
|
||||
LostEntityTracker<TestEntity> lost_entity_tracker;
|
||||
TestEntity entity_1{1};
|
||||
TestEntity entity_2{2};
|
||||
TestEntity entity_3{3};
|
||||
|
||||
// Discover some entities.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Go through the next round only rediscovering one of our entities and
|
||||
// discovering an additional entity as well. Then, verify that only one entity
|
||||
// was lost after the check.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_3);
|
||||
typename LostEntityTracker<TestEntity>::EntitySet lost_entities =
|
||||
lost_entity_tracker.ComputeLostEntities();
|
||||
EXPECT_TRUE(lost_entities.find(entity_1) == lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_3) == lost_entities.end());
|
||||
}
|
||||
|
||||
TEST(LostEntityTrackerTest, SameEntityMultipleCopies) {
|
||||
LostEntityTracker<TestEntity> lost_entity_tracker;
|
||||
TestEntity entity_1{1};
|
||||
TestEntity entity_1_copy{1};
|
||||
|
||||
// Discover an entity.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Rediscover the same entity, but through a copy of it.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1_copy);
|
||||
|
||||
// Make sure none are lost on the second round.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Go through a round without rediscovering any entities and verify that we
|
||||
// lost an entity equivalent to both copies of it.
|
||||
typename LostEntityTracker<TestEntity>::EntitySet lost_entities =
|
||||
lost_entity_tracker.ComputeLostEntities();
|
||||
EXPECT_EQ(lost_entities.size(), 1);
|
||||
EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_1_copy) != lost_entities.end());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,33 +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 "core/internal/mediums/mediums.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
BluetoothRadio& Mediums::GetBluetoothRadio() { return bluetooth_radio_; }
|
||||
|
||||
BluetoothClassic& Mediums::GetBluetoothClassic() { return bluetooth_classic_; }
|
||||
|
||||
Ble& Mediums::GetBle() { return ble_; }
|
||||
|
||||
WifiLan& Mediums::GetWifiLan() { return wifi_lan_; }
|
||||
|
||||
mediums::WebRtc& Mediums::GetWebRtc() { return webrtc_; }
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,73 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_MEDIUMS_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_MEDIUMS_H_
|
||||
|
||||
#include "core/internal/mediums/ble.h"
|
||||
#include "core/internal/mediums/bluetooth_classic.h"
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#ifdef NO_WEBRTC
|
||||
#include "core/internal/mediums/webrtc_stub.h"
|
||||
#else
|
||||
#include "core/internal/mediums/webrtc.h"
|
||||
#endif
|
||||
#include "core/internal/mediums/wifi_lan.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Facilitates convenient and reliable usage of various wireless mediums.
|
||||
class Mediums {
|
||||
public:
|
||||
Mediums() = default;
|
||||
~Mediums() = default;
|
||||
|
||||
// Returns a handle to the Bluetooth radio.
|
||||
BluetoothRadio& GetBluetoothRadio();
|
||||
|
||||
// Returns a handle to the Bluetooth Classic medium.
|
||||
BluetoothClassic& GetBluetoothClassic();
|
||||
|
||||
// Returns a handle to the Ble medium.
|
||||
Ble& GetBle();
|
||||
|
||||
// Returns a handle to the Wifi-Lan medium.
|
||||
WifiLan& GetWifiLan();
|
||||
|
||||
// Returns a handle to the WebRtc medium.
|
||||
mediums::WebRtc& GetWebRtc();
|
||||
|
||||
private:
|
||||
// The order of declaration is critical for both construction and
|
||||
// destruction.
|
||||
//
|
||||
// 1) Construction: The individual mediums have a dependency on the
|
||||
// corresponding radio, so the radio must be initialized first.
|
||||
//
|
||||
// 2) Destruction: The individual mediums should be shut down before the
|
||||
// corresponding radio.
|
||||
BluetoothRadio bluetooth_radio_;
|
||||
BluetoothClassic bluetooth_classic_{bluetooth_radio_};
|
||||
Ble ble_{bluetooth_radio_};
|
||||
WifiLan wifi_lan_;
|
||||
mediums::WebRtc webrtc_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_MEDIUMS_H_
|
||||
@@ -1,94 +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 "core/internal/mediums/utils.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "platform/base/prng.h"
|
||||
#include "platform/public/crypto.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
namespace {
|
||||
constexpr absl::string_view kUpgradeServiceIdPostfix = "_UPGRADE";
|
||||
}
|
||||
|
||||
ByteArray Utils::GenerateRandomBytes(size_t length) {
|
||||
Prng rng;
|
||||
std::string data;
|
||||
data.reserve(length);
|
||||
|
||||
// Adds 4 random bytes per iteration.
|
||||
while (length > 0) {
|
||||
std::uint32_t val = rng.NextUint32();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
data += val & 0xFF;
|
||||
val >>= 8;
|
||||
length--;
|
||||
|
||||
if (!length) break;
|
||||
}
|
||||
}
|
||||
|
||||
return ByteArray(data);
|
||||
}
|
||||
|
||||
ByteArray Utils::Sha256Hash(const ByteArray& source, size_t length) {
|
||||
return Utils::Sha256Hash(std::string(source), length);
|
||||
}
|
||||
|
||||
ByteArray Utils::Sha256Hash(const std::string& source, size_t length) {
|
||||
ByteArray full_hash(length);
|
||||
full_hash.CopyAt(0, Crypto::Sha256(source));
|
||||
return full_hash;
|
||||
}
|
||||
|
||||
std::string Utils::WrapUpgradeServiceId(const std::string& service_id) {
|
||||
if (service_id.empty()) {
|
||||
return {};
|
||||
}
|
||||
return service_id + std::string(kUpgradeServiceIdPostfix);
|
||||
}
|
||||
|
||||
std::string Utils::UnwrapUpgradeServiceId(
|
||||
const std::string& upgrade_service_id) {
|
||||
auto pos = upgrade_service_id.find(std::string(kUpgradeServiceIdPostfix));
|
||||
if (pos != std::string::npos) {
|
||||
return std::string(upgrade_service_id, 0, pos);
|
||||
}
|
||||
return upgrade_service_id;
|
||||
}
|
||||
|
||||
LocationHint Utils::BuildLocationHint(const std::string& location) {
|
||||
LocationHint location_hint;
|
||||
location_hint.set_format(LocationStandard::UNKNOWN);
|
||||
|
||||
if (!location.empty()) {
|
||||
location_hint.set_location(location);
|
||||
if (location.at(0) == '+') {
|
||||
location_hint.set_format(LocationStandard::E164_CALLING);
|
||||
} else {
|
||||
location_hint.set_format(LocationStandard::ISO_3166_1_ALPHA_2);
|
||||
}
|
||||
}
|
||||
return location_hint;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,42 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_UTILS_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_UTILS_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "connections/implementation/proto/offline_wire_formats.pb.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
static ByteArray GenerateRandomBytes(size_t length);
|
||||
static ByteArray Sha256Hash(const ByteArray& source, size_t length);
|
||||
static ByteArray Sha256Hash(const std::string& source, size_t length);
|
||||
static std::string WrapUpgradeServiceId(const std::string& service_id);
|
||||
static std::string UnwrapUpgradeServiceId(const std::string& service_id);
|
||||
static LocationHint BuildLocationHint(const std::string& location);
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_UTILS_H_
|
||||
@@ -1,87 +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 "core/internal/mediums/uuid.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "platform/public/crypto.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
std::ostream& write_hex(std::ostream& os, absl::string_view data) {
|
||||
for (const auto b : data) {
|
||||
os << std::setfill('0') << std::setw(2) << std::hex
|
||||
<< (static_cast<unsigned int>(b) & 0x0ff);
|
||||
}
|
||||
return os;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Uuid::Uuid(absl::string_view data) : data_(Crypto::Md5(data)) {
|
||||
// Based on the Java counterpart at
|
||||
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#162.
|
||||
data_[6] &= 0x0f; // Clear version.
|
||||
data_[6] |= 0x30; // Set to version 3.
|
||||
data_[8] &= 0x3f; // Clear variant.
|
||||
data_[8] |= 0x80; // Set to IETF variant.
|
||||
}
|
||||
|
||||
Uuid::Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits) {
|
||||
// Base on the Java counterpart at
|
||||
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#104.
|
||||
data_.reserve(sizeof(most_sig_bits) + sizeof(least_sig_bits));
|
||||
|
||||
data_[0] = static_cast<char>((most_sig_bits >> 56) & 0x0ff);
|
||||
data_[1] = static_cast<char>((most_sig_bits >> 48) & 0x0ff);
|
||||
data_[2] = static_cast<char>((most_sig_bits >> 40) & 0x0ff);
|
||||
data_[3] = static_cast<char>((most_sig_bits >> 32) & 0x0ff);
|
||||
data_[4] = static_cast<char>((most_sig_bits >> 24) & 0x0ff);
|
||||
data_[5] = static_cast<char>((most_sig_bits >> 16) & 0x0ff);
|
||||
data_[6] = static_cast<char>((most_sig_bits >> 8) & 0x0ff);
|
||||
data_[7] = static_cast<char>((most_sig_bits >> 0) & 0x0ff);
|
||||
|
||||
data_[8] = static_cast<char>((least_sig_bits >> 56) & 0x0ff);
|
||||
data_[9] = static_cast<char>((least_sig_bits >> 48) & 0x0ff);
|
||||
data_[10] = static_cast<char>((least_sig_bits >> 40) & 0x0ff);
|
||||
data_[11] = static_cast<char>((least_sig_bits >> 32) & 0x0ff);
|
||||
data_[12] = static_cast<char>((least_sig_bits >> 24) & 0x0ff);
|
||||
data_[13] = static_cast<char>((least_sig_bits >> 16) & 0x0ff);
|
||||
data_[14] = static_cast<char>((least_sig_bits >> 8) & 0x0ff);
|
||||
data_[15] = static_cast<char>((least_sig_bits >> 0) & 0x0ff);
|
||||
}
|
||||
|
||||
Uuid::operator std::string() const {
|
||||
// Based on the Java counterpart at
|
||||
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#375.
|
||||
std::ostringstream md5_hex;
|
||||
write_hex(md5_hex, absl::string_view(&data_[0], 4));
|
||||
md5_hex << "-";
|
||||
write_hex(md5_hex, absl::string_view(&data_[4], 2));
|
||||
md5_hex << "-";
|
||||
write_hex(md5_hex, absl::string_view(&data_[6], 2));
|
||||
md5_hex << "-";
|
||||
write_hex(md5_hex, absl::string_view(&data_[8], 2));
|
||||
md5_hex << "-";
|
||||
write_hex(md5_hex, absl::string_view(&data_[10], 6));
|
||||
|
||||
return md5_hex.str();
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,57 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_UUID_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_UUID_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// A type 3 name-based
|
||||
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based))
|
||||
// UUID.
|
||||
//
|
||||
// https://developer.android.com/reference/java/util/UUID.html
|
||||
class Uuid final {
|
||||
public:
|
||||
Uuid() : Uuid("uuid") {}
|
||||
explicit Uuid(absl::string_view data);
|
||||
Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits);
|
||||
Uuid(const Uuid&) = default;
|
||||
Uuid& operator=(const Uuid&) = default;
|
||||
Uuid(Uuid&&) = default;
|
||||
Uuid& operator=(Uuid&&) = default;
|
||||
~Uuid() = default;
|
||||
|
||||
// Returns the canonical textual representation
|
||||
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the
|
||||
// UUID.
|
||||
explicit operator std::string() const;
|
||||
std::string data() const { return data_; }
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_UUID_H_
|
||||
@@ -1,71 +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 "core/internal/mediums/uuid.h"
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "platform/public/crypto.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr absl::string_view kString{"some string"};
|
||||
constexpr std::uint64_t kNum1 = 0x123456789abcdef0;
|
||||
constexpr std::uint64_t kNum2 = 0x21436587a9cbed0f;
|
||||
|
||||
TEST(UuidTest, CreateFromStringWithMd5) {
|
||||
Uuid uuid(kString);
|
||||
std::string uuid_str(uuid);
|
||||
std::string uuid_data(uuid.data());
|
||||
std::string md5_data(Crypto::Md5(kString));
|
||||
NEARBY_LOG(INFO, "MD5-based UUID: '%s'", uuid_str.c_str());
|
||||
uuid_data[6] = 0;
|
||||
uuid_data[8] = 0;
|
||||
md5_data[6] = 0;
|
||||
md5_data[8] = 0;
|
||||
EXPECT_EQ(md5_data, uuid_data);
|
||||
}
|
||||
|
||||
TEST(UuidTest, CreateFromBinary) {
|
||||
Uuid uuid(kNum1, kNum2);
|
||||
std::string uuid_data(uuid.data());
|
||||
std::string uuid_str(uuid);
|
||||
NEARBY_LOG(INFO, "UUID: '%s'", uuid_str.c_str());
|
||||
EXPECT_EQ(uuid_data[0], (kNum1 >> 56) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[1], (kNum1 >> 48) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[2], (kNum1 >> 40) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[3], (kNum1 >> 32) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[4], (kNum1 >> 24) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[5], (kNum1 >> 16) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[6], (kNum1 >> 8) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[7], (kNum1 >> 0) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[8], (kNum2 >> 56) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[9], (kNum2 >> 48) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[10], (kNum2 >> 40) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[11], (kNum2 >> 32) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[12], (kNum2 >> 24) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[13], (kNum2 >> 16) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[14], (kNum2 >> 8) & 0xFF);
|
||||
EXPECT_EQ(uuid_data[15], (kNum2 >> 0) & 0xFF);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,744 +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 "core/internal/mediums/webrtc.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/functional/bind_front.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "core/internal/mediums/webrtc/signaling_frames.h"
|
||||
#include "core/internal/mediums/webrtc_socket.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/listeners.h"
|
||||
#include "platform/public/cancelable_alarm.h"
|
||||
#include "platform/public/future.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
#include "proto/mediums/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
// The maximum amount of time to wait to connect to a data channel via WebRTC.
|
||||
constexpr absl::Duration kDataChannelTimeout = absl::Seconds(10);
|
||||
|
||||
// Delay between restarting signaling messenger to receive messages.
|
||||
constexpr absl::Duration kRestartReceiveMessagesDuration = absl::Seconds(60);
|
||||
|
||||
} // namespace
|
||||
|
||||
WebRtc::WebRtc() = default;
|
||||
|
||||
WebRtc::~WebRtc() {
|
||||
// This ensures that all pending callbacks are run before we reset the medium
|
||||
// and we are not accepting new runnables.
|
||||
single_thread_executor_.Shutdown();
|
||||
|
||||
// Stop accepting all connections
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
for (auto& item : accepting_connections_info_) {
|
||||
service_ids.emplace(item.first);
|
||||
}
|
||||
for (const auto& service_id : service_ids) {
|
||||
StopAcceptingConnections(service_id);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string WebRtc::GetDefaultCountryCode() {
|
||||
return medium_.GetDefaultCountryCode();
|
||||
}
|
||||
|
||||
bool WebRtc::IsAvailable() { return medium_.IsValid(); }
|
||||
|
||||
bool WebRtc::IsAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
return IsAcceptingConnectionsLocked(service_id);
|
||||
}
|
||||
|
||||
bool WebRtc::IsAcceptingConnectionsLocked(const std::string& service_id) {
|
||||
return accepting_connections_info_.contains(service_id);
|
||||
}
|
||||
|
||||
bool WebRtc::StartAcceptingConnections(const std::string& service_id,
|
||||
const WebrtcPeerId& self_peer_id,
|
||||
const LocationHint& location_hint,
|
||||
AcceptedConnectionCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (!IsAvailable()) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"Cannot start accepting WebRTC connections because WebRTC is "
|
||||
"not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"Cannot start accepting WebRTC connections because service %s "
|
||||
"is already accepting WebRTC connections.",
|
||||
service_id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// We'll track our state here, so that we're separated from the other services
|
||||
// who may be also using WebRTC.
|
||||
AcceptingConnectionsInfo info = AcceptingConnectionsInfo();
|
||||
info.self_peer_id = self_peer_id;
|
||||
info.accepted_connection_callback = callback;
|
||||
|
||||
// Create a new SignalingMessenger so that we can communicate w/ Tachyon.
|
||||
info.signaling_messenger =
|
||||
medium_.GetSignalingMessenger(self_peer_id.GetId(), location_hint);
|
||||
if (!info.signaling_messenger->IsValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This registers ourselves w/ Tachyon, creating a room from the PeerId.
|
||||
// This allows a remote device to message us over Tachyon.
|
||||
if (!info.signaling_messenger->StartReceivingMessages(
|
||||
absl::bind_front(&WebRtc::OnSignalingMessage, this, service_id),
|
||||
absl::bind_front(&WebRtc::OnSignalingComplete, this, service_id))) {
|
||||
info.signaling_messenger.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// We'll automatically disconnect from Tachyon after 60sec. When this alarm
|
||||
// fires, we'll recreate our room so we continue to receive messages.
|
||||
info.restart_tachyon_receive_messages_alarm = CancelableAlarm(
|
||||
"restart_receiving_messages_webrtc",
|
||||
std::bind(&WebRtc::ProcessRestartTachyonReceiveMessages, this,
|
||||
service_id),
|
||||
kRestartReceiveMessagesDuration, &single_thread_executor_);
|
||||
|
||||
// Now that we're set up to receive messages, we'll save our state and return
|
||||
// a successful result.
|
||||
accepting_connections_info_.emplace(service_id, std::move(info));
|
||||
NEARBY_LOG(INFO,
|
||||
"Started listening for WebRTC connections as %s on service %s",
|
||||
self_peer_id.GetId().c_str(), service_id.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebRtc::StopAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (!IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"Cannot stop accepting WebRTC connections because service %s "
|
||||
"is not accepting WebRTC connections.",
|
||||
service_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab our info from the map.
|
||||
auto& info = accepting_connections_info_.find(service_id)->second;
|
||||
|
||||
// Stop receiving messages from Tachyon.
|
||||
info.signaling_messenger->StopReceivingMessages();
|
||||
info.signaling_messenger.reset();
|
||||
|
||||
// Cancel the scheduled alarm.
|
||||
if (info.restart_tachyon_receive_messages_alarm.IsValid()) {
|
||||
info.restart_tachyon_receive_messages_alarm.Cancel();
|
||||
info.restart_tachyon_receive_messages_alarm = CancelableAlarm();
|
||||
}
|
||||
|
||||
// If we had any in-progress connections that haven't materialized into full
|
||||
// DataChannels yet, it's time to shut them down since they can't reach us
|
||||
// anymore.
|
||||
absl::flat_hash_set<std::string> peer_ids;
|
||||
for (auto& item : connection_flows_) {
|
||||
peer_ids.emplace(item.first);
|
||||
}
|
||||
for (const auto& peer_id : peer_ids) {
|
||||
const auto& entry = connection_flows_.find(peer_id);
|
||||
// Skip outgoing connections in this step. Start/StopAcceptingConnections
|
||||
// only deals with incoming connections.
|
||||
if (requesting_connections_info_.contains(peer_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip fully connected connections in this step. If the connection was
|
||||
// formed while we were accepting connections, then it will stay alive until
|
||||
// it's explicitly closed.
|
||||
if (!entry->second->CloseIfNotConnected()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
connection_flows_.erase(peer_id);
|
||||
}
|
||||
|
||||
// Clean up our state. We're now no longer listening for connections.
|
||||
accepting_connections_info_.erase(service_id);
|
||||
NEARBY_LOG(INFO, "Stopped listening for WebRTC connections for service %s",
|
||||
service_id.c_str());
|
||||
}
|
||||
|
||||
WebRtcSocketWrapper WebRtc::Connect(const std::string& service_id,
|
||||
const WebrtcPeerId& remote_peer_id,
|
||||
const LocationHint& location_hint,
|
||||
CancellationFlag* cancellation_flag) {
|
||||
for (int attempts_count = 0; attempts_count < kConnectAttemptsLimit;
|
||||
attempts_count++) {
|
||||
auto wrapper_result = AttemptToConnect(service_id, remote_peer_id,
|
||||
location_hint, cancellation_flag);
|
||||
if (wrapper_result.IsValid()) {
|
||||
return wrapper_result;
|
||||
}
|
||||
}
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
WebRtcSocketWrapper WebRtc::AttemptToConnect(
|
||||
const std::string& service_id, const WebrtcPeerId& remote_peer_id,
|
||||
const LocationHint& location_hint, CancellationFlag* cancellation_flag) {
|
||||
ConnectionRequestInfo info = ConnectionRequestInfo();
|
||||
info.self_peer_id = WebrtcPeerId::FromRandom();
|
||||
Future<WebRtcSocketWrapper> socket_future = info.socket_future;
|
||||
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
if (!IsAvailable()) {
|
||||
NEARBY_LOG(
|
||||
WARNING,
|
||||
"Cannot connect to WebRTC peer %s because WebRTC is not available.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
if (cancellation_flag->Cancelled()) {
|
||||
NEARBY_LOGS(INFO) << "Cannot connect with WebRtc due to cancel.";
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
// Create a new ConnectionFlow for this connection attempt.
|
||||
std::unique_ptr<ConnectionFlow> connection_flow =
|
||||
CreateConnectionFlow(service_id, remote_peer_id);
|
||||
if (!connection_flow) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot connect to WebRTC peer %s because we failed to create a "
|
||||
"ConnectionFlow.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
// Create a new SignalingMessenger so that we can communicate over Tachyon.
|
||||
info.signaling_messenger =
|
||||
medium_.GetSignalingMessenger(info.self_peer_id.GetId(), location_hint);
|
||||
if (!info.signaling_messenger->IsValid()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot connect to WebRTC peer %s because we failed to create a "
|
||||
"SignalingMessenger.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
// This registers ourselves w/ Tachyon, creating a room from the PeerId.
|
||||
// This allows a remote device to message us over Tachyon.
|
||||
auto signaling_complete_callback = [socket_future](bool success) mutable {
|
||||
if (!success) {
|
||||
socket_future.SetException({Exception::kFailed});
|
||||
}
|
||||
};
|
||||
if (!info.signaling_messenger->StartReceivingMessages(
|
||||
absl::bind_front(&WebRtc::OnSignalingMessage, this, service_id),
|
||||
signaling_complete_callback)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot connect to WebRTC peer %s because we failed to start "
|
||||
"receiving messages over Tachyon.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
info.signaling_messenger.reset();
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
// Poke the remote device. This will cause them to send us an Offer.
|
||||
if (!info.signaling_messenger->SendMessage(
|
||||
remote_peer_id.GetId(),
|
||||
webrtc_frames::EncodeReadyForSignalingPoke(info.self_peer_id))) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot connect to WebRTC peer %s because we failed to poke "
|
||||
"the peer over Tachyon.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
info.signaling_messenger.reset();
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
// Create a new ConnectionRequest entry. This map will be used later to look
|
||||
// up state as we negotiate the connection over Tachyon.
|
||||
requesting_connections_info_.emplace(remote_peer_id.GetId(),
|
||||
std::move(info));
|
||||
connection_flows_.emplace(remote_peer_id.GetId(),
|
||||
std::move(connection_flow));
|
||||
}
|
||||
|
||||
// Wait for the connection to go through. Don't hold the mutex here so that
|
||||
// we're not blocking necessary operations.
|
||||
ExceptionOr<WebRtcSocketWrapper> socket_result =
|
||||
socket_future.Get(kDataChannelTimeout);
|
||||
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Reclaim our info, since we had released ownership while talking to
|
||||
// Tachyon.
|
||||
auto& info =
|
||||
requesting_connections_info_.find(remote_peer_id.GetId())->second;
|
||||
|
||||
// Verify that the connection went through.
|
||||
if (!socket_result.ok()) {
|
||||
NEARBY_LOG(INFO, "Failed to connect to WebRTC peer %s.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
info.signaling_messenger.reset();
|
||||
requesting_connections_info_.erase(remote_peer_id.GetId());
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
// Clean up our ConnectionRequest.
|
||||
info.signaling_messenger.reset();
|
||||
requesting_connections_info_.erase(remote_peer_id.GetId());
|
||||
|
||||
// Return the result.
|
||||
return socket_result.GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::ProcessLocalIceCandidate(
|
||||
const std::string& service_id, const WebrtcPeerId& remote_peer_id,
|
||||
const ::location::nearby::mediums::IceCandidate ice_candidate) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Check first if we have an outgoing request w/ this peer. As this request is
|
||||
// tied to a specific peer, it takes precedence.
|
||||
const auto& connection_request_entry =
|
||||
requesting_connections_info_.find(remote_peer_id.GetId());
|
||||
if (connection_request_entry != requesting_connections_info_.end()) {
|
||||
// Pass the ice candidate to the remote side.
|
||||
if (!connection_request_entry->second.signaling_messenger->SendMessage(
|
||||
remote_peer_id.GetId(),
|
||||
webrtc_frames::EncodeIceCandidates(
|
||||
connection_request_entry->second.self_peer_id,
|
||||
{ice_candidate}))) {
|
||||
NEARBY_LOG(INFO, "Failed to send ice candidate to %s.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Sent ice candidate to %s.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check next if we're expecting incoming connection requests.
|
||||
const auto& accepting_connection_entry =
|
||||
accepting_connections_info_.find(service_id);
|
||||
if (accepting_connection_entry != accepting_connections_info_.end()) {
|
||||
// Pass the ice candidate to the remote side.
|
||||
// TODO(xlythe) Consider not blocking here, since this can eat into the
|
||||
// connection time
|
||||
if (!accepting_connection_entry->second.signaling_messenger->SendMessage(
|
||||
remote_peer_id.GetId(),
|
||||
webrtc_frames::EncodeIceCandidates(
|
||||
accepting_connection_entry->second.self_peer_id,
|
||||
{ice_candidate}))) {
|
||||
NEARBY_LOG(INFO, "Failed to send ice candidate to %s.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Sent ice candidate to %s.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO,
|
||||
"Skipping restart listening for tachyon inbox messages since we "
|
||||
"are not accepting connections for service %s.",
|
||||
service_id.c_str());
|
||||
}
|
||||
|
||||
void WebRtc::OnSignalingMessage(const std::string& service_id,
|
||||
const ByteArray& message) {
|
||||
OffloadFromThread("rtc-on-signaling-message", [this, service_id, message]() {
|
||||
ProcessTachyonInboxMessage(service_id, message);
|
||||
});
|
||||
}
|
||||
|
||||
void WebRtc::OnSignalingComplete(const std::string& service_id, bool success) {
|
||||
NEARBY_LOG(INFO, "Signaling completed with status: %d.", success);
|
||||
if (success) {
|
||||
return;
|
||||
}
|
||||
|
||||
OffloadFromThread("rtc-on-signaling-complete", [this, service_id]() {
|
||||
MutexLock lock(&mutex_);
|
||||
const auto& info_entry = accepting_connections_info_.find(service_id);
|
||||
if (info_entry == accepting_connections_info_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (info_entry->second.restart_accept_connections_count <
|
||||
kRestartAcceptConnectionsLimit) {
|
||||
++info_entry->second.restart_accept_connections_count;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
RestartTachyonReceiveMessages(service_id);
|
||||
});
|
||||
}
|
||||
|
||||
void WebRtc::ProcessTachyonInboxMessage(const std::string& service_id,
|
||||
const ByteArray& message) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Attempt to parse the incoming message as a WebRtcSignalingFrame.
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
if (!frame.ParseFromString(std::string(message))) {
|
||||
NEARBY_LOG(WARNING, "Failed to parse signaling message.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the frame is valid (no missing fields).
|
||||
if (!frame.has_sender_id()) {
|
||||
NEARBY_LOG(WARNING, "Invalid WebRTC frame: Sender ID is missing.");
|
||||
return;
|
||||
}
|
||||
WebrtcPeerId remote_peer_id = WebrtcPeerId(frame.sender_id().id());
|
||||
|
||||
// Depending on the message type, we'll respond as appropriate.
|
||||
if (requesting_connections_info_.contains(remote_peer_id.GetId())) {
|
||||
// This is from a peer we have an outgoing connection request with, so we'll
|
||||
// only process the Answer path.
|
||||
if (frame.has_offer()) {
|
||||
ReceiveOffer(remote_peer_id,
|
||||
SessionDescriptionWrapper(
|
||||
webrtc_frames::DecodeOffer(frame).release()));
|
||||
SendAnswer(remote_peer_id);
|
||||
} else if (frame.has_ice_candidates()) {
|
||||
ReceiveIceCandidates(remote_peer_id,
|
||||
webrtc_frames::DecodeIceCandidates(frame));
|
||||
} else {
|
||||
NEARBY_LOG(INFO, "Received unknown WebRTC frame: ignoring.");
|
||||
}
|
||||
} else if (IsAcceptingConnectionsLocked(service_id)) {
|
||||
// We don't have an outgoing connection request with this peer, but we are
|
||||
// accepting incoming requests so we'll only process the Offer path.
|
||||
if (frame.has_ready_for_signaling_poke()) {
|
||||
SendOffer(service_id, remote_peer_id);
|
||||
} else if (frame.has_answer()) {
|
||||
ReceiveAnswer(remote_peer_id,
|
||||
SessionDescriptionWrapper(
|
||||
webrtc_frames::DecodeAnswer(frame).release()));
|
||||
} else if (frame.has_ice_candidates()) {
|
||||
ReceiveIceCandidates(remote_peer_id,
|
||||
webrtc_frames::DecodeIceCandidates(frame));
|
||||
} else {
|
||||
NEARBY_LOG(INFO, "Received unknown WebRTC frame: ignoring.");
|
||||
}
|
||||
} else {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Ignoring Tachyon message since we are not accepting connections.");
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::SendOffer(const std::string& service_id,
|
||||
const WebrtcPeerId& remote_peer_id) {
|
||||
std::unique_ptr<ConnectionFlow> connection_flow =
|
||||
CreateConnectionFlow(service_id, remote_peer_id);
|
||||
if (!connection_flow) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to send offer. Failed to create a ConnectionFlow.");
|
||||
return;
|
||||
}
|
||||
|
||||
SessionDescriptionWrapper offer = connection_flow->CreateOffer();
|
||||
if (!offer.IsValid()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to send offer. Failed to create our offer locally.");
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const webrtc::SessionDescriptionInterface& sdp = offer.GetSdp();
|
||||
if (!connection_flow->SetLocalSessionDescription(offer)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to send offer. Failed to register our offer locally.");
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab our info from the map.
|
||||
auto& info = accepting_connections_info_.find(service_id)->second;
|
||||
|
||||
// Pass the offer to the remote side.
|
||||
if (!info.signaling_messenger->SendMessage(
|
||||
remote_peer_id.GetId(),
|
||||
webrtc_frames::EncodeOffer(info.self_peer_id, sdp))) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to send offer. Failed to write the offer to the remote "
|
||||
"peer %s.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the ConnectionFlow so that other methods can use it later.
|
||||
connection_flows_.emplace(remote_peer_id.GetId(), std::move(connection_flow));
|
||||
NEARBY_LOG(INFO, "Sent offer to %s.", remote_peer_id.GetId().c_str());
|
||||
}
|
||||
|
||||
void WebRtc::ReceiveOffer(const WebrtcPeerId& remote_peer_id,
|
||||
SessionDescriptionWrapper offer) {
|
||||
const auto& entry = connection_flows_.find(remote_peer_id.GetId());
|
||||
if (entry == connection_flows_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to receive offer. Failed to create a ConnectionFlow.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry->second->OnOfferReceived(offer)) {
|
||||
NEARBY_LOG(INFO, "Unable to receive offer. Failed to process the offer.");
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::SendAnswer(const WebrtcPeerId& remote_peer_id) {
|
||||
const auto& entry = connection_flows_.find(remote_peer_id.GetId());
|
||||
if (entry == connection_flows_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to send answer. Failed to create a ConnectionFlow.");
|
||||
return;
|
||||
}
|
||||
|
||||
SessionDescriptionWrapper answer = entry->second->CreateAnswer();
|
||||
if (!answer.IsValid()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to send answer. Failed to create our answer locally.");
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
const webrtc::SessionDescriptionInterface& sdp = answer.GetSdp();
|
||||
if (!entry->second->SetLocalSessionDescription(answer)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to send answer. Failed to register our answer locally.");
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab our info from the map.
|
||||
const auto& connection_request_entry =
|
||||
requesting_connections_info_.find(remote_peer_id.GetId());
|
||||
if (connection_request_entry == requesting_connections_info_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to send answer. Failed to find an outgoing connection "
|
||||
"request.");
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the answer to the remote side.
|
||||
if (!connection_request_entry->second.signaling_messenger->SendMessage(
|
||||
remote_peer_id.GetId(),
|
||||
webrtc_frames::EncodeAnswer(
|
||||
connection_request_entry->second.self_peer_id, sdp))) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Unable to send answer. Failed to write the answer to the remote "
|
||||
"peer %s.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Sent answer to %s.", remote_peer_id.GetId().c_str());
|
||||
}
|
||||
|
||||
void WebRtc::ReceiveAnswer(const WebrtcPeerId& remote_peer_id,
|
||||
SessionDescriptionWrapper answer) {
|
||||
const auto& entry = connection_flows_.find(remote_peer_id.GetId());
|
||||
if (entry == connection_flows_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to receive answer. Failed to create a ConnectionFlow.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!entry->second->OnAnswerReceived(answer)) {
|
||||
NEARBY_LOG(INFO, "Unable to receive answer. Failed to process the answer.");
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::ReceiveIceCandidates(
|
||||
const WebrtcPeerId& remote_peer_id,
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) {
|
||||
const auto& entry = connection_flows_.find(remote_peer_id.GetId());
|
||||
if (entry == connection_flows_.end()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Unable to receive ice candidates. Failed to create a ConnectionFlow.");
|
||||
return;
|
||||
}
|
||||
|
||||
entry->second->OnRemoteIceCandidatesReceived(std::move(ice_candidates));
|
||||
}
|
||||
|
||||
void WebRtc::ProcessRestartTachyonReceiveMessages(
|
||||
const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
RestartTachyonReceiveMessages(service_id);
|
||||
}
|
||||
|
||||
void WebRtc::RestartTachyonReceiveMessages(const std::string& service_id) {
|
||||
if (!IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Skipping restart listening for tachyon inbox messages since we "
|
||||
"are not accepting connections for service %s.",
|
||||
service_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab our info from the map.
|
||||
auto& info = accepting_connections_info_.find(service_id)->second;
|
||||
|
||||
// Ensure we've disconnected from Tachyon.
|
||||
info.signaling_messenger->StopReceivingMessages();
|
||||
|
||||
// Attempt to re-register.
|
||||
if (!info.signaling_messenger->StartReceivingMessages(
|
||||
absl::bind_front(&WebRtc::OnSignalingMessage, this, service_id),
|
||||
absl::bind_front(&WebRtc::OnSignalingComplete, this, service_id))) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"Failed to restart listening for tachyon inbox messages for "
|
||||
"service %s since we failed to reach Tachyon.",
|
||||
service_id.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO,
|
||||
"Successfully restarted listening for tachyon inbox messages on "
|
||||
"service %s.",
|
||||
service_id.c_str());
|
||||
}
|
||||
|
||||
void WebRtc::ProcessDataChannelOpen(const std::string& service_id,
|
||||
const WebrtcPeerId& remote_peer_id,
|
||||
WebRtcSocketWrapper socket_wrapper) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Notify the client of the newly formed socket.
|
||||
const auto& connection_request_entry =
|
||||
requesting_connections_info_.find(remote_peer_id.GetId());
|
||||
if (connection_request_entry != requesting_connections_info_.end()) {
|
||||
connection_request_entry->second.socket_future.Set(socket_wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& accepting_connection_entry =
|
||||
accepting_connections_info_.find(service_id);
|
||||
if (accepting_connection_entry != accepting_connections_info_.end()) {
|
||||
accepting_connection_entry->second.accepted_connection_callback.accepted_cb(
|
||||
socket_wrapper);
|
||||
return;
|
||||
}
|
||||
|
||||
// No one to handle the newly created DataChannel, so we'll just close it.
|
||||
socket_wrapper.Close();
|
||||
NEARBY_LOG(INFO,
|
||||
"Ignoring new DataChannel because we "
|
||||
"are not accepting connections for service %s.",
|
||||
service_id.c_str());
|
||||
}
|
||||
|
||||
void WebRtc::ProcessDataChannelClosed(const WebrtcPeerId& remote_peer_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOG(INFO,
|
||||
"Data channel has closed, removing connection flow for peer %s.",
|
||||
remote_peer_id.GetId().c_str());
|
||||
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
}
|
||||
|
||||
std::unique_ptr<ConnectionFlow> WebRtc::CreateConnectionFlow(
|
||||
const std::string& service_id, const WebrtcPeerId& remote_peer_id) {
|
||||
RemoveConnectionFlow(remote_peer_id);
|
||||
|
||||
return ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
{[this, service_id, remote_peer_id](
|
||||
const webrtc::IceCandidateInterface* ice_candidate) {
|
||||
// Note: We need to encode the ice candidate here, before we jump
|
||||
// off the thread. Otherwise, it gets destroyed and we can't read
|
||||
// it later.
|
||||
::location::nearby::mediums::IceCandidate encoded_ice_candidate =
|
||||
webrtc_frames::EncodeIceCandidate(*ice_candidate);
|
||||
OffloadFromThread(
|
||||
"rtc-ice-candidates",
|
||||
[this, service_id, remote_peer_id, encoded_ice_candidate]() {
|
||||
ProcessLocalIceCandidate(service_id, remote_peer_id,
|
||||
encoded_ice_candidate);
|
||||
});
|
||||
}}},
|
||||
{
|
||||
.data_channel_open_cb = {[this, service_id, remote_peer_id](
|
||||
WebRtcSocketWrapper socket_wrapper) {
|
||||
OffloadFromThread(
|
||||
"rtc-channel-created",
|
||||
[this, service_id, remote_peer_id, socket_wrapper]() {
|
||||
ProcessDataChannelOpen(service_id, remote_peer_id,
|
||||
socket_wrapper);
|
||||
});
|
||||
}},
|
||||
.data_channel_closed_cb = {[this, remote_peer_id]() {
|
||||
OffloadFromThread("rtc-channel-closed", [this, remote_peer_id]() {
|
||||
ProcessDataChannelClosed(remote_peer_id);
|
||||
});
|
||||
}},
|
||||
},
|
||||
medium_);
|
||||
}
|
||||
|
||||
void WebRtc::RemoveConnectionFlow(const WebrtcPeerId& remote_peer_id) {
|
||||
if (!connection_flows_.erase(remote_peer_id.GetId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we had an outgoing connection request w/ this peer, report the failure
|
||||
// to the future that's being waited on.
|
||||
const auto& connection_request_entry =
|
||||
requesting_connections_info_.find(remote_peer_id.GetId());
|
||||
if (connection_request_entry != requesting_connections_info_.end()) {
|
||||
connection_request_entry->second.socket_future.SetException(
|
||||
{Exception::kFailed});
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::OffloadFromThread(const std::string& name, Runnable runnable) {
|
||||
single_thread_executor_.Execute(name, std::move(runnable));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,265 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "connections/implementation/proto/offline_wire_formats.pb.h"
|
||||
#include "core/internal/mediums/webrtc/connection_flow.h"
|
||||
#include "core/internal/mediums/webrtc/data_channel_listener.h"
|
||||
#include "core/internal/mediums/webrtc/local_ice_candidate_listener.h"
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket_impl.h"
|
||||
#include "core/internal/mediums/webrtc_peer_id.h"
|
||||
#include "core/internal/mediums/webrtc_socket.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/cancellation_flag.h"
|
||||
#include "platform/base/listeners.h"
|
||||
#include "platform/base/runnable.h"
|
||||
#include "platform/public/atomic_boolean.h"
|
||||
#include "platform/public/cancelable_alarm.h"
|
||||
#include "platform/public/future.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/scheduled_executor.h"
|
||||
#include "platform/public/single_thread_executor.h"
|
||||
#include "platform/public/webrtc.h"
|
||||
#include "proto/mediums/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Callback that is invoked when a new connection is accepted.
|
||||
struct AcceptedConnectionCallback {
|
||||
std::function<void(WebRtcSocketWrapper socket)> accepted_cb =
|
||||
DefaultCallback<WebRtcSocketWrapper>();
|
||||
};
|
||||
|
||||
// Entry point for connecting a data channel between two devices via WebRtc.
|
||||
class WebRtc {
|
||||
public:
|
||||
WebRtc();
|
||||
~WebRtc();
|
||||
|
||||
// Gets the default two-letter country code associated with current locale.
|
||||
// For example, en_US locale resolves to "US".
|
||||
const std::string GetDefaultCountryCode();
|
||||
|
||||
// Returns if WebRtc is available as a medium for nearby to transport data.
|
||||
// Runs on @MainThread.
|
||||
bool IsAvailable();
|
||||
|
||||
// Returns if the device is accepting connection with specific service id.
|
||||
// Runs on @MainThread.
|
||||
bool IsAcceptingConnections(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Prepares the device to accept incoming WebRtc connections. Returns a
|
||||
// boolean value indicating if the device has started accepting connections.
|
||||
// Runs on @MainThread.
|
||||
bool StartAcceptingConnections(const std::string& service_id,
|
||||
const WebrtcPeerId& self_peer_id,
|
||||
const LocationHint& location_hint,
|
||||
AcceptedConnectionCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Try to stop (accepting) the specific connection with provided service id.
|
||||
// Runs on @MainThread
|
||||
void StopAcceptingConnections(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Initiates a WebRtc connection with peer device identified by |peer_id|
|
||||
// with internal retry for maximum attempts of kConnectAttemptsLimit.
|
||||
// Runs on @MainThread.
|
||||
WebRtcSocketWrapper Connect(const std::string& service_id,
|
||||
const WebrtcPeerId& peer_id,
|
||||
const LocationHint& location_hint,
|
||||
CancellationFlag* cancellation_flag)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
static constexpr int kConnectAttemptsLimit = 3;
|
||||
static constexpr int kRestartAcceptConnectionsLimit = 3;
|
||||
|
||||
enum class Role {
|
||||
kNone = 0,
|
||||
kOfferer = 1,
|
||||
kAnswerer = 2,
|
||||
};
|
||||
|
||||
struct AcceptingConnectionsInfo {
|
||||
// The self_peer_id is generated from the BT/WiFi advertisements and allows
|
||||
// the scanner to message us over Tachyon.
|
||||
WebrtcPeerId self_peer_id;
|
||||
|
||||
// The registered callback. When there's an incoming connection, this
|
||||
// callback is notified.
|
||||
AcceptedConnectionCallback accepted_connection_callback;
|
||||
|
||||
// Allows us to communicate with the Tachyon web server.
|
||||
std::unique_ptr<WebRtcSignalingMessenger> signaling_messenger;
|
||||
|
||||
// Restarts the tachyon inbox receives messages streaming rpc if the
|
||||
// streaming rpc times out. The streaming rpc times out after 60s while
|
||||
// advertising. Non-null when listening for WebRTC connections as an
|
||||
// offerer.
|
||||
CancelableAlarm restart_tachyon_receive_messages_alarm;
|
||||
|
||||
// Tracks the number of times we've restarted receiving messages after a
|
||||
// failure. We limit the number to prevent endless restarts if we are
|
||||
// repeatedly unable to communicate with Tachyon.
|
||||
int restart_accept_connections_count = 0;
|
||||
};
|
||||
|
||||
struct ConnectionRequestInfo {
|
||||
// The self_peer_id is randomly generated and allows the advertiser to
|
||||
// message us over Tachyon.
|
||||
WebrtcPeerId self_peer_id;
|
||||
|
||||
// Allows us to communicate with the Tachyon web server.
|
||||
std::unique_ptr<WebRtcSignalingMessenger> signaling_messenger;
|
||||
|
||||
// The pending DataChannel future. Our client will be blocked on this while
|
||||
// they wait for us to set up the channel over Tachyon.
|
||||
Future<WebRtcSocketWrapper> socket_future;
|
||||
};
|
||||
|
||||
// Attempt to initiates a WebRtc connection with peer device identified by
|
||||
// |peer_id|.
|
||||
// Runs on @MainThread.
|
||||
WebRtcSocketWrapper AttemptToConnect(const std::string& service_id,
|
||||
const WebrtcPeerId& peer_id,
|
||||
const LocationHint& location_hint,
|
||||
CancellationFlag* cancellation_flag)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns if the device is accepting connection with specific service id.
|
||||
// Runs on @MainThread.
|
||||
bool IsAcceptingConnectionsLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Receives a message from the signaling messenger.
|
||||
void OnSignalingMessage(const std::string& service_id,
|
||||
const ByteArray& message);
|
||||
|
||||
// Decides whether to restart receiving messages.
|
||||
void OnSignalingComplete(const std::string& service_id, bool success);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ProcessTachyonInboxMessage(const std::string& service_id,
|
||||
const ByteArray& message)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void SendOffer(const std::string& service_id,
|
||||
const WebrtcPeerId& remote_peer_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ReceiveOffer(const WebrtcPeerId& remote_peer_id,
|
||||
SessionDescriptionWrapper offer)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void SendAnswer(const WebrtcPeerId& remote_peer_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ReceiveAnswer(const WebrtcPeerId& remote_peer_id,
|
||||
SessionDescriptionWrapper answer)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ReceiveIceCandidates(
|
||||
const WebrtcPeerId& remote_peer_id,
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
std::unique_ptr<ConnectionFlow> CreateConnectionFlow(
|
||||
const std::string& service_id, const WebrtcPeerId& remote_peer_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
std::unique_ptr<ConnectionFlow> GetConnectionFlow(
|
||||
const WebrtcPeerId& remote_peer_id) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void RemoveConnectionFlow(const WebrtcPeerId& remote_peer_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ProcessDataChannelOpen(const std::string& service_id,
|
||||
const WebrtcPeerId& remote_peer_id,
|
||||
WebRtcSocketWrapper socket_wrapper)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ProcessDataChannelClosed(const WebrtcPeerId& remote_peer_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ProcessLocalIceCandidate(
|
||||
const std::string& service_id, const WebrtcPeerId& remote_peer_id,
|
||||
const ::location::nearby::mediums::IceCandidate ice_candidate)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ProcessRestartTachyonReceiveMessages(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void RestartTachyonReceiveMessages(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void OffloadFromThread(const std::string& name, Runnable runnable);
|
||||
|
||||
Mutex mutex_;
|
||||
|
||||
WebRtcMedium medium_;
|
||||
|
||||
// The single thread we throw the potentially blocking work on to.
|
||||
ScheduledExecutor single_thread_executor_;
|
||||
|
||||
// A map of ServiceID -> State for all services that are listening for
|
||||
// incoming connections.
|
||||
absl::flat_hash_map<std::string, AcceptingConnectionsInfo>
|
||||
accepting_connections_info_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// A map of a remote PeerId -> State for pending connection requests. As
|
||||
// messages from Tachyon come in, this lets us look up the connection request
|
||||
// info to handle the interaction.
|
||||
absl::flat_hash_map<std::string, ConnectionRequestInfo>
|
||||
requesting_connections_info_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// A map of a remote PeerId -> ConnectionFlow. For each connection, we create
|
||||
// a unique ConnectionFlow.
|
||||
absl::flat_hash_map<std::string, std::unique_ptr<ConnectionFlow>>
|
||||
connection_flows_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
@@ -1,97 +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.
|
||||
licenses(["notice"])
|
||||
|
||||
cc_library(
|
||||
name = "webrtc",
|
||||
srcs = [
|
||||
"connection_flow.cc",
|
||||
"signaling_frames.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"connection_flow.h",
|
||||
"data_channel_listener.h",
|
||||
"local_ice_candidate_listener.h",
|
||||
"session_description_wrapper.h",
|
||||
"signaling_frames.h",
|
||||
],
|
||||
copts = ["-DCORE_ADAPTER_DLL"],
|
||||
visibility = [
|
||||
"//cpp/core/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":data_types",
|
||||
"//cpp/core:core_types",
|
||||
"//cpp/core/internal/mediums:utils",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/public:comm",
|
||||
"//cpp/platform/public:logging",
|
||||
"//cpp/platform/public:types",
|
||||
"//proto/mediums:web_rtc_signaling_frames_cc_proto",
|
||||
"@com_google_absl//absl/memory",
|
||||
"@com_google_absl//absl/time",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
"//third_party/webrtc/files/stable/webrtc/pc:peerconnection",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "data_types",
|
||||
srcs = [
|
||||
"webrtc_socket_impl.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"webrtc_socket_impl.h",
|
||||
],
|
||||
copts = ["-DCORE_ADAPTER_DLL"],
|
||||
visibility = [
|
||||
"//cpp/core/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//cpp/core:core_types",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/public:types",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "webrtc_test",
|
||||
timeout = "short",
|
||||
srcs = [
|
||||
"connection_flow_test.cc",
|
||||
"signaling_frames_test.cc",
|
||||
"webrtc_socket_impl_test.cc",
|
||||
],
|
||||
tags = [
|
||||
"notsan", # NOTE(b/139734036): known data race in usrsctplib.
|
||||
"requires-net:external",
|
||||
],
|
||||
deps = [
|
||||
":data_types",
|
||||
":webrtc",
|
||||
"//cpp/core/internal/mediums:utils",
|
||||
"//cpp/platform/base",
|
||||
"//cpp/platform/base:test_util",
|
||||
"//cpp/platform/impl/g3", # buildcleaner: keep
|
||||
"//cpp/platform/public:comm",
|
||||
"//cpp/platform/public:types",
|
||||
"@com_google_googletest//:gtest_main","@com_github_protobuf_matchers//protobuf-matchers:protobuf-matchers",
|
||||
"@com_google_absl//absl/time",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
"//webrtc/api:rtc_error",
|
||||
"//webrtc/api:scoped_refptr",
|
||||
"@com_google_protobuf//:protobuf",
|
||||
],
|
||||
)
|
||||
@@ -1,556 +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 "core/internal/mediums/webrtc/connection_flow.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket_impl.h"
|
||||
#include "core/internal/mediums/webrtc_socket.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
#include "platform/public/webrtc.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
constexpr absl::Duration ConnectionFlow::kTimeout;
|
||||
constexpr absl::Duration ConnectionFlow::kPeerConnectionTimeout;
|
||||
|
||||
// This is the same as the nearby data channel name.
|
||||
constexpr char kDataChannelName[] = "dataChannel";
|
||||
|
||||
class CreateSessionDescriptionObserverImpl
|
||||
: public webrtc::CreateSessionDescriptionObserver {
|
||||
public:
|
||||
CreateSessionDescriptionObserverImpl(
|
||||
ConnectionFlow* connection_flow,
|
||||
Future<SessionDescriptionWrapper> settable_future,
|
||||
ConnectionFlow::State expected_entry_state,
|
||||
ConnectionFlow::State exit_state)
|
||||
: connection_flow_{connection_flow},
|
||||
settable_future_{settable_future},
|
||||
expected_entry_state_{expected_entry_state},
|
||||
exit_state_{exit_state} {}
|
||||
|
||||
// webrtc::CreateSessionDescriptionObserver
|
||||
void OnSuccess(webrtc::SessionDescriptionInterface* desc) override {
|
||||
if (connection_flow_->TransitionState(expected_entry_state_, exit_state_)) {
|
||||
settable_future_.Set(SessionDescriptionWrapper{desc});
|
||||
} else {
|
||||
settable_future_.SetException({Exception::kFailed});
|
||||
}
|
||||
}
|
||||
|
||||
void OnFailure(webrtc::RTCError error) override {
|
||||
NEARBY_LOG(ERROR, "Error when creating session description: %s",
|
||||
error.message());
|
||||
settable_future_.SetException({Exception::kFailed});
|
||||
}
|
||||
|
||||
private:
|
||||
ConnectionFlow* connection_flow_;
|
||||
Future<SessionDescriptionWrapper> settable_future_;
|
||||
ConnectionFlow::State expected_entry_state_;
|
||||
ConnectionFlow::State exit_state_;
|
||||
};
|
||||
|
||||
class SetDescriptionObserverBase {
|
||||
public:
|
||||
ExceptionOr<bool> GetResult(absl::Duration timeout) {
|
||||
return settable_future_.Get(timeout);
|
||||
}
|
||||
|
||||
protected:
|
||||
void OnSetDescriptionComplete(webrtc::RTCError error) {
|
||||
// On success, |error.ok()| is true.
|
||||
if (error.ok()) {
|
||||
settable_future_.Set(true);
|
||||
return;
|
||||
}
|
||||
settable_future_.SetException({Exception::kFailed});
|
||||
}
|
||||
|
||||
private:
|
||||
Future<bool> settable_future_;
|
||||
};
|
||||
|
||||
class SetLocalDescriptionObserver
|
||||
: public webrtc::SetLocalDescriptionObserverInterface,
|
||||
public SetDescriptionObserverBase {
|
||||
public:
|
||||
void OnSetLocalDescriptionComplete(webrtc::RTCError error) override {
|
||||
OnSetDescriptionComplete(error);
|
||||
}
|
||||
};
|
||||
|
||||
class SetRemoteDescriptionObserver
|
||||
: public webrtc::SetRemoteDescriptionObserverInterface,
|
||||
public SetDescriptionObserverBase {
|
||||
public:
|
||||
void OnSetRemoteDescriptionComplete(webrtc::RTCError error) override {
|
||||
OnSetDescriptionComplete(error);
|
||||
}
|
||||
};
|
||||
|
||||
using PeerConnectionState =
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> ConnectionFlow::Create(
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener, WebRtcMedium& webrtc_medium) {
|
||||
auto connection_flow = absl::WrapUnique(
|
||||
new ConnectionFlow(std::move(local_ice_candidate_listener),
|
||||
std::move(data_channel_listener)));
|
||||
if (connection_flow->InitPeerConnection(webrtc_medium)) {
|
||||
return connection_flow;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ConnectionFlow::ConnectionFlow(
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener)
|
||||
: data_channel_listener_(std::move(data_channel_listener)),
|
||||
local_ice_candidate_listener_(std::move(local_ice_candidate_listener)) {}
|
||||
|
||||
ConnectionFlow::~ConnectionFlow() {
|
||||
NEARBY_LOG(INFO, "~ConnectionFlow");
|
||||
RunOnSignalingThread([this] { CloseOnSignalingThread(); });
|
||||
shutdown_latch_.Await();
|
||||
NEARBY_LOG(INFO, "~ConnectionFlow done");
|
||||
}
|
||||
|
||||
SessionDescriptionWrapper ConnectionFlow::CreateOffer() {
|
||||
CHECK(!IsRunningOnSignalingThread());
|
||||
Future<SessionDescriptionWrapper> success_future;
|
||||
if (!RunOnSignalingThread([this, success_future] {
|
||||
CreateOfferOnSignalingThread(success_future);
|
||||
})) {
|
||||
NEARBY_LOG(ERROR, "Failed to create offer");
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
ExceptionOr<SessionDescriptionWrapper> result = success_future.Get(kTimeout);
|
||||
if (result.ok()) {
|
||||
return std::move(result.result());
|
||||
}
|
||||
NEARBY_LOG(ERROR, "Failed to create offer: %d", result.exception());
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
void ConnectionFlow::CreateOfferOnSignalingThread(
|
||||
Future<SessionDescriptionWrapper> success_future) {
|
||||
if (!TransitionState(State::kInitialized, State::kCreatingOffer)) {
|
||||
success_future.SetException({Exception::kFailed});
|
||||
return;
|
||||
}
|
||||
webrtc::DataChannelInit data_channel_init;
|
||||
data_channel_init.reliable = true;
|
||||
auto pc = GetPeerConnection();
|
||||
CreateSocketFromDataChannel(
|
||||
pc->CreateDataChannel(kDataChannelName, &data_channel_init));
|
||||
|
||||
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
|
||||
rtc::scoped_refptr<CreateSessionDescriptionObserverImpl> observer(
|
||||
new rtc::RefCountedObject<CreateSessionDescriptionObserverImpl>(
|
||||
this, success_future, State::kCreatingOffer,
|
||||
State::kWaitingForAnswer));
|
||||
pc->CreateOffer(observer, options);
|
||||
}
|
||||
|
||||
SessionDescriptionWrapper ConnectionFlow::CreateAnswer() {
|
||||
CHECK(!IsRunningOnSignalingThread());
|
||||
Future<SessionDescriptionWrapper> success_future;
|
||||
if (!RunOnSignalingThread([this, success_future] {
|
||||
CreateAnswerOnSignalingThread(success_future);
|
||||
})) {
|
||||
NEARBY_LOG(ERROR, "Failed to create answer");
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
ExceptionOr<SessionDescriptionWrapper> result = success_future.Get(kTimeout);
|
||||
if (result.ok()) {
|
||||
return std::move(result.result());
|
||||
}
|
||||
NEARBY_LOG(ERROR, "Failed to create answer: %d", result.exception());
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
void ConnectionFlow::CreateAnswerOnSignalingThread(
|
||||
Future<SessionDescriptionWrapper> success_future) {
|
||||
if (!TransitionState(State::kReceivedOffer, State::kCreatingAnswer)) {
|
||||
success_future.SetException({Exception::kFailed});
|
||||
return;
|
||||
}
|
||||
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
|
||||
rtc::scoped_refptr<CreateSessionDescriptionObserverImpl> observer(
|
||||
new rtc::RefCountedObject<CreateSessionDescriptionObserverImpl>(
|
||||
this, success_future, State::kCreatingAnswer,
|
||||
State::kWaitingToConnect));
|
||||
auto pc = GetPeerConnection();
|
||||
pc->CreateAnswer(observer, options);
|
||||
}
|
||||
|
||||
bool ConnectionFlow::SetLocalSessionDescription(SessionDescriptionWrapper sdp) {
|
||||
CHECK(!IsRunningOnSignalingThread());
|
||||
if (!sdp.IsValid()) return false;
|
||||
|
||||
rtc::scoped_refptr<SetLocalDescriptionObserver> observer(
|
||||
new rtc::RefCountedObject<SetLocalDescriptionObserver>());
|
||||
|
||||
if (!RunOnSignalingThread([this, observer, sdp = std::move(sdp)]() mutable {
|
||||
if (state_ == State::kEnded) {
|
||||
observer->OnSetLocalDescriptionComplete(
|
||||
webrtc::RTCError(webrtc::RTCErrorType::INVALID_STATE));
|
||||
return;
|
||||
}
|
||||
auto pc = GetPeerConnection();
|
||||
|
||||
pc->SetLocalDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>(sdp.Release()),
|
||||
observer);
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ExceptionOr<bool> result = observer->GetResult(kTimeout);
|
||||
bool success = result.ok() && result.result();
|
||||
if (!success) {
|
||||
NEARBY_LOG(ERROR, "Failed to set local session description: %d",
|
||||
result.exception());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::SetRemoteSessionDescription(SessionDescriptionWrapper sdp,
|
||||
State expected_entry_state,
|
||||
State exit_state) {
|
||||
if (!sdp.IsValid()) return false;
|
||||
|
||||
rtc::scoped_refptr<SetRemoteDescriptionObserver> observer(
|
||||
new rtc::RefCountedObject<SetRemoteDescriptionObserver>());
|
||||
|
||||
if (!RunOnSignalingThread([this, observer, sdp = std::move(sdp),
|
||||
expected_entry_state, exit_state]() mutable {
|
||||
if (!TransitionState(expected_entry_state, exit_state)) {
|
||||
observer->OnSetRemoteDescriptionComplete(
|
||||
webrtc::RTCError(webrtc::RTCErrorType::INVALID_STATE));
|
||||
return;
|
||||
}
|
||||
auto pc = GetPeerConnection();
|
||||
|
||||
pc->SetRemoteDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>(sdp.Release()),
|
||||
observer);
|
||||
})) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ExceptionOr<bool> result = observer->GetResult(kTimeout);
|
||||
bool success = result.ok() && result.result();
|
||||
if (!success) {
|
||||
NEARBY_LOG(ERROR, "Failed to set remote description: %d",
|
||||
result.exception());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnOfferReceived(SessionDescriptionWrapper offer) {
|
||||
CHECK(!IsRunningOnSignalingThread());
|
||||
return SetRemoteSessionDescription(std::move(offer), State::kInitialized,
|
||||
State::kReceivedOffer);
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnAnswerReceived(SessionDescriptionWrapper answer) {
|
||||
CHECK(!IsRunningOnSignalingThread());
|
||||
return SetRemoteSessionDescription(
|
||||
std::move(answer), State::kWaitingForAnswer, State::kWaitingToConnect);
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnRemoteIceCandidatesReceived(
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) {
|
||||
CHECK(!IsRunningOnSignalingThread());
|
||||
// We can't call RunOnSignalingThread because C++ wants to copy ice_candidates
|
||||
// if we try. unique_ptr is not CopyConstructible and compilation fails.
|
||||
auto pc = GetPeerConnection();
|
||||
|
||||
if (!pc) {
|
||||
return false;
|
||||
}
|
||||
pc->signaling_thread()->PostTask(
|
||||
[this, can_run_tasks = std::weak_ptr<void>(can_run_tasks_),
|
||||
candidates = std::move(ice_candidates)]() mutable {
|
||||
// don't run the task if the weak_ptr is no longer valid.
|
||||
if (!can_run_tasks.lock()) {
|
||||
return;
|
||||
}
|
||||
AddIceCandidatesOnSignalingThread(std::move(candidates));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionFlow::AddIceCandidatesOnSignalingThread(
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) {
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
if (state_ == State::kEnded) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"You cannot add ice candidates to a disconnected session.");
|
||||
return;
|
||||
}
|
||||
if (state_ != State::kWaitingToConnect && state_ != State::kConnected) {
|
||||
cached_remote_ice_candidates_.insert(
|
||||
cached_remote_ice_candidates_.end(),
|
||||
std::make_move_iterator(ice_candidates.begin()),
|
||||
std::make_move_iterator(ice_candidates.end()));
|
||||
return;
|
||||
}
|
||||
auto pc = GetPeerConnection();
|
||||
for (auto&& ice_candidate : ice_candidates) {
|
||||
if (!pc->AddIceCandidate(ice_candidate.get())) {
|
||||
NEARBY_LOG(WARNING, "Unable to add remote ice candidate.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectionFlow::CloseIfNotConnected() {
|
||||
CHECK(!IsRunningOnSignalingThread());
|
||||
Future<bool> closed;
|
||||
if (RunOnSignalingThread([this, closed]() mutable {
|
||||
if (state_ == State::kConnected) {
|
||||
closed.Set(false);
|
||||
} else {
|
||||
CloseOnSignalingThread();
|
||||
closed.Set(true);
|
||||
}
|
||||
})) {
|
||||
auto result = closed.Get();
|
||||
return result.ok() && result.result();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) {
|
||||
Future<bool> success_future;
|
||||
// CreatePeerConnection callback may be invoked after ConnectionFlow lifetime
|
||||
// has ended, in case of a timeout. Future is captured by value, and is safe
|
||||
// to access, but it is not safe to access ConnectionFlow member variables
|
||||
// unless the Future::Set() returns true.
|
||||
webrtc_medium.CreatePeerConnection(
|
||||
this,
|
||||
[this, success_future](rtc::scoped_refptr<webrtc::PeerConnectionInterface>
|
||||
peer_connection) mutable {
|
||||
if (!peer_connection) {
|
||||
success_future.Set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this fails, means we have already assigned something to
|
||||
// success_future; it is either:
|
||||
// 1) this is the 2nd call of this callback (and this is a bug), or
|
||||
// 2) Get(timeout) has set the future value as exception already.
|
||||
if (success_future.IsSet()) return;
|
||||
MutexLock lock(&mutex_);
|
||||
peer_connection_ = peer_connection;
|
||||
signaling_thread_for_dcheck_only_ =
|
||||
peer_connection_->signaling_thread();
|
||||
success_future.Set(true);
|
||||
});
|
||||
|
||||
ExceptionOr<bool> result = success_future.Get(kPeerConnectionTimeout);
|
||||
bool success = result.ok() && result.result();
|
||||
if (!success) {
|
||||
shutdown_latch_.CountDown();
|
||||
NEARBY_LOG(ERROR, "Failed to create peer connection: %d",
|
||||
result.exception());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnSignalingStable() {
|
||||
if (state_ != State::kWaitingToConnect && state_ != State::kConnected) return;
|
||||
auto pc = GetPeerConnection();
|
||||
for (auto&& ice_candidate : cached_remote_ice_candidates_) {
|
||||
if (!pc->AddIceCandidate(ice_candidate.get())) {
|
||||
NEARBY_LOG(WARNING, "Unable to add remote ice candidate.");
|
||||
}
|
||||
}
|
||||
cached_remote_ice_candidates_.clear();
|
||||
}
|
||||
|
||||
void ConnectionFlow::CreateSocketFromDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
NEARBY_LOG(INFO, "Creating data channel socket");
|
||||
auto socket =
|
||||
std::make_unique<WebRtcSocket>("WebRtcSocket", std::move(data_channel));
|
||||
socket->SetSocketListener({
|
||||
.socket_ready_cb = {[this](WebRtcSocket* socket) {
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
if (!TransitionState(State::kWaitingToConnect, State::kConnected)) {
|
||||
NEARBY_LOG(ERROR,
|
||||
"Data channel socket is open but connection flow was not "
|
||||
"in the required state");
|
||||
socket->Close();
|
||||
return;
|
||||
}
|
||||
// Pass socket wrapper by copy on purpose
|
||||
data_channel_listener_.data_channel_open_cb(socket_wrapper_);
|
||||
}},
|
||||
.socket_closed_cb = [callback =
|
||||
data_channel_listener_.data_channel_closed_cb](
|
||||
WebRtcSocket*) { callback(); },
|
||||
});
|
||||
socket_wrapper_ = WebRtcSocketWrapper(std::move(socket));
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnIceCandidate(
|
||||
const webrtc::IceCandidateInterface* candidate) {
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
local_ice_candidate_listener_.local_ice_candidate_found_cb(candidate);
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnSignalingChange(
|
||||
webrtc::PeerConnectionInterface::SignalingState new_state) {
|
||||
NEARBY_LOG(INFO, "OnSignalingChange: %d", new_state);
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
if (new_state == webrtc::PeerConnectionInterface::SignalingState::kStable) {
|
||||
OnSignalingStable();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
NEARBY_LOG(INFO, "OnDataChannel");
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
CreateSocketFromDataChannel(std::move(data_channel));
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnIceGatheringChange(
|
||||
webrtc::PeerConnectionInterface::IceGatheringState new_state) {
|
||||
NEARBY_LOG(INFO, "OnIceGatheringChange: %d", new_state);
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
|
||||
NEARBY_LOG(INFO, "OnConnectionChange: %d", new_state);
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
if (new_state == PeerConnectionState::kClosed ||
|
||||
new_state == PeerConnectionState::kFailed ||
|
||||
new_state == PeerConnectionState::kDisconnected) {
|
||||
NEARBY_LOG(INFO, "Closing due to peer connection state change: %d",
|
||||
new_state);
|
||||
CloseOnSignalingThread();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnRenegotiationNeeded() {
|
||||
NEARBY_LOG(INFO, "OnRenegotiationNeeded");
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
}
|
||||
|
||||
bool ConnectionFlow::TransitionState(State current_state, State new_state) {
|
||||
CHECK(IsRunningOnSignalingThread());
|
||||
if (current_state != state_) {
|
||||
NEARBY_LOG(
|
||||
WARNING,
|
||||
"Invalid state transition to %d: current state is %d but expected %d.",
|
||||
new_state, state_, current_state);
|
||||
return false;
|
||||
}
|
||||
NEARBY_LOG(INFO, "Transition: %d -> %d", state_, new_state);
|
||||
state_ = new_state;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::CloseOnSignalingThread() {
|
||||
if (state_ == State::kEnded) {
|
||||
return false;
|
||||
}
|
||||
state_ = State::kEnded;
|
||||
// This prevents other tasks from queuing on the signaling thread for this
|
||||
// object.
|
||||
auto pc = GetAndResetPeerConnection();
|
||||
|
||||
NEARBY_LOG(INFO, "Closing WebRTC peer connection.");
|
||||
// NOTE: Closing the peer conection will close the data channel and thus the
|
||||
// socket implicitly.
|
||||
if (pc) pc->Close();
|
||||
NEARBY_LOG(INFO, "Closed WebRTC peer connection.");
|
||||
// Prevent any already queued tasks from running on the signaling thread
|
||||
can_run_tasks_.reset();
|
||||
// If anyone was waiting for shutdown to be done let them know.
|
||||
shutdown_latch_.CountDown();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::RunOnSignalingThread(Runnable&& runnable) {
|
||||
CHECK(!IsRunningOnSignalingThread());
|
||||
auto pc = GetPeerConnection();
|
||||
if (!pc) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"Peer connection not available. Cannot schedule tasks.");
|
||||
return false;
|
||||
}
|
||||
// We are off signaling thread, so we can't use peer connection's methods
|
||||
// but we can access the signaling thread handle.
|
||||
pc->signaling_thread()->PostTask(
|
||||
[can_run_tasks = std::weak_ptr<void>(can_run_tasks_),
|
||||
task = std::move(runnable)] {
|
||||
// don't run the task if the weak_ptr is no longer valid.
|
||||
// shared_ptr |can_run_tasks_| is destroyed on the same thread
|
||||
// (signaling thread). This guarantees that if the weak_ptr is valid
|
||||
// when this task starts, it will stay valid until the task ends.
|
||||
if (!can_run_tasks.lock()) {
|
||||
NEARBY_LOG(INFO, "Peer connection already closed. Cannot run tasks.");
|
||||
return;
|
||||
}
|
||||
task();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::IsRunningOnSignalingThread() {
|
||||
return signaling_thread_for_dcheck_only_ != nullptr &&
|
||||
signaling_thread_for_dcheck_only_ == rtc::Thread::Current();
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface>
|
||||
ConnectionFlow::GetPeerConnection() {
|
||||
// We must use a mutex to ensure that peer connection is
|
||||
// fully initialized.
|
||||
// We increase the peer_connection_'s refcount to keep it
|
||||
// alive while we use it.
|
||||
MutexLock lock(&mutex_);
|
||||
return peer_connection_;
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface>
|
||||
ConnectionFlow::GetAndResetPeerConnection() {
|
||||
MutexLock lock(&mutex_);
|
||||
return std::move(peer_connection_);
|
||||
}
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,233 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/internal/mediums/webrtc/data_channel_listener.h"
|
||||
#include "core/internal/mediums/webrtc/local_ice_candidate_listener.h"
|
||||
#include "core/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "core/internal/mediums/webrtc_socket.h"
|
||||
#include "platform/base/runnable.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/single_thread_executor.h"
|
||||
#include "platform/public/webrtc.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
/**
|
||||
* Flow for an offerer:
|
||||
*
|
||||
* <ul>
|
||||
* <li>INITIALIZED: After construction.
|
||||
* <li>CREATING_OFFER: After CreateOffer(). Local ice candidate collection
|
||||
* begins.
|
||||
* <li>WAITING_FOR_ANSWER: Until the remote peer sends their answer.
|
||||
* <li>WAITING_TO_CONNECT: Until the data channel actually connects. Remote
|
||||
* ice candidates should be added with OnRemoteIceCandidatesReceived as they are
|
||||
* gathered.
|
||||
* <li>CONNECTED: We successfully connected to the remote data
|
||||
* channel.
|
||||
* <li>ENDED: The final state that can occur from any of the previous
|
||||
* states if we disconnect at any point in the flow.
|
||||
* </ul>
|
||||
*
|
||||
* <p>Flow for an answerer:
|
||||
*
|
||||
* <ul>
|
||||
* <li>INITIALIZED: After construction.
|
||||
* <li>RECEIVED_OFFER: After onOfferReceived().
|
||||
* <li>CREATING_ANSWER: After CreateAnswer(). Local ice candidate collection
|
||||
* begins.
|
||||
* <li>WAITING_TO_CONNECT: Until the data channel actually connects.
|
||||
* Remote ice candidates should be added with OnRemoteIceCandidatesReceived as
|
||||
* they are gathered.
|
||||
* <li>CONNECTED: We successfully connected to the remote
|
||||
* data channel.
|
||||
* <li>ENDED: The final state that can occur from any of the
|
||||
* previous states if we disconnect at any point in the flow.
|
||||
* </ul>
|
||||
*/
|
||||
class ConnectionFlow : public webrtc::PeerConnectionObserver {
|
||||
public:
|
||||
enum class State {
|
||||
kInitialized,
|
||||
kCreatingOffer,
|
||||
kWaitingForAnswer,
|
||||
kReceivedOffer,
|
||||
kCreatingAnswer,
|
||||
kWaitingToConnect,
|
||||
kConnected,
|
||||
kEnded,
|
||||
};
|
||||
|
||||
// This method blocks on the creation of the peer connection object.
|
||||
// Can be called on any thread but never called on signaling thread.
|
||||
static std::unique_ptr<ConnectionFlow> Create(
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener, WebRtcMedium& webrtc_medium);
|
||||
~ConnectionFlow() override;
|
||||
|
||||
// Create the offer that will be sent to the remote. Mirrors the behaviour of
|
||||
// PeerConnectionInterface::CreateOffer.
|
||||
// Can be called on any thread but never called on signaling thread.
|
||||
SessionDescriptionWrapper CreateOffer() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Create the answer that will be sent to the remote. Mirrors the behaviour of
|
||||
// PeerConnectionInterface::CreateAnswer.
|
||||
// Can be called on any thread but never called on signaling thread.
|
||||
SessionDescriptionWrapper CreateAnswer() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Set the local session description. |sdp| was created via CreateOffer()
|
||||
// or CreateAnswer().
|
||||
// Can be called on any thread but never called on signaling thread.
|
||||
bool SetLocalSessionDescription(SessionDescriptionWrapper sdp)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Invoked when an offer was received from a remote; this will set the remote
|
||||
// session description on the peer connection. Returns true if the offer was
|
||||
// successfully set as remote session description.
|
||||
// Can be called on any thread but never called on signaling thread.
|
||||
bool OnOfferReceived(SessionDescriptionWrapper offer)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Invoked when an answer was received from a remote; this will set the remote
|
||||
// session description on the peer connection. Returns true if the offer was
|
||||
// successfully set as remote session description.
|
||||
// Can be called on any thread but never called on signaling thread.
|
||||
bool OnAnswerReceived(SessionDescriptionWrapper answer)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Invoked when an ice candidate was received from a remote; this will add the
|
||||
// ice candidate to the peer connection if ready or cache it otherwise.
|
||||
// Can be called on any thread but never called on signaling thread.
|
||||
bool OnRemoteIceCandidatesReceived(
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Close the peer connection and data channel if not connected.
|
||||
// Can be called on any thread but never called on signaling thread.
|
||||
bool CloseIfNotConnected() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// webrtc::PeerConnectionObserver:
|
||||
// All methods called only on signaling thread.
|
||||
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
|
||||
void OnSignalingChange(
|
||||
webrtc::PeerConnectionInterface::SignalingState new_state) override;
|
||||
void OnDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) override;
|
||||
void OnIceGatheringChange(
|
||||
webrtc::PeerConnectionInterface::IceGatheringState new_state) override;
|
||||
void OnConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) override;
|
||||
void OnRenegotiationNeeded() override;
|
||||
|
||||
// Public because it's used in tests too.
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> GetPeerConnection();
|
||||
|
||||
private:
|
||||
ConnectionFlow(LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener);
|
||||
|
||||
// Resets peer connection reference. Returns old value.
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface>
|
||||
GetAndResetPeerConnection();
|
||||
void CreateOfferOnSignalingThread(
|
||||
Future<SessionDescriptionWrapper> success_future);
|
||||
void CreateAnswerOnSignalingThread(
|
||||
Future<SessionDescriptionWrapper> success_future);
|
||||
void AddIceCandidatesOnSignalingThread(
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates);
|
||||
// Invoked when the peer connection indicates that signaling is stable.
|
||||
void OnSignalingStable() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
void CreateSocketFromDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
|
||||
// TODO(bfranz): Consider whether this needs to be configurable per platform
|
||||
static constexpr absl::Duration kTimeout = absl::Milliseconds(250);
|
||||
static constexpr absl::Duration kPeerConnectionTimeout =
|
||||
absl::Milliseconds(2500);
|
||||
|
||||
bool InitPeerConnection(WebRtcMedium& webrtc_medium);
|
||||
|
||||
bool TransitionState(State current_state, State new_state);
|
||||
|
||||
bool SetRemoteSessionDescription(SessionDescriptionWrapper sdp,
|
||||
State expected_entry_state,
|
||||
State exit_state);
|
||||
|
||||
bool CloseOnSignalingThread() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool RunOnSignalingThread(Runnable&& runnable);
|
||||
bool IsRunningOnSignalingThread();
|
||||
|
||||
Mutex mutex_;
|
||||
// Used to prevent the destructor from returning while the signaling thread is
|
||||
// still running CloseOnSignalingThread()
|
||||
CountDownLatch shutdown_latch_{1};
|
||||
|
||||
// State is used on signaling thread only.
|
||||
State state_ = State::kInitialized;
|
||||
// Used to communicate data channel events back to the caller of Create()
|
||||
DataChannelListener data_channel_listener_;
|
||||
|
||||
LocalIceCandidateListener local_ice_candidate_listener_;
|
||||
// Peer connection can be used only on signaling thread. The only exception
|
||||
// is accessing the signaling thread handle. Tasks posted on the
|
||||
// signaling thread may outlive both |peer_connection_| and |this| objects.
|
||||
// A mutex is required to access peer connection reference because peer
|
||||
// connection object and the reference can be initialized on different
|
||||
// threads - the reference could be initialized before peer connection's
|
||||
// constructor has finished.
|
||||
// |peer_connection_| is actually implemented by PeerConnectionProxy, which
|
||||
// runs the real PeerConnection's methods on the correct thread (signaling or
|
||||
// worker). If a proxy method is called on the correct thread, then the real
|
||||
// method is called directly. Otherwise, a task is posted on the correct
|
||||
// thread and the current thread is blocked until that task finishes. We
|
||||
// choose to explicitly use |peer_connection_| on the signaling thread,
|
||||
// because it allows us to do state management on the signaling thread too,
|
||||
// simplifies locking, and we don't have to block the current thread for every
|
||||
// peer connection call.
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// Used to hold a reference to the WebRtcSocket while the data channel is
|
||||
// connecting.
|
||||
WebRtcSocketWrapper socket_wrapper_;
|
||||
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
cached_remote_ice_candidates_;
|
||||
// This pointer is only for DCHECK() assertions.
|
||||
// It allows us to check if we are running on signaling thread even
|
||||
// after destroying |peer_connection_|.
|
||||
const void* signaling_thread_for_dcheck_only_ = nullptr;
|
||||
// This shared_ptr is reset on the signaling thread when ConnectionFlow is
|
||||
// closed. This prevents us from running tasks on the signaling thread when
|
||||
// peer connection is closed. The value stored in |can_run_tasks_| is not
|
||||
// used. We are using std::shared_ptr instead of rtc::WeakPtrFactory because
|
||||
// the former is thread-safe.
|
||||
std::shared_ptr<void> can_run_tasks_ = std::make_shared<int>();
|
||||
|
||||
friend class CreateSessionDescriptionObserverImpl;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_
|
||||
@@ -1,408 +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 "core/internal/mediums/webrtc/connection_flow.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "core/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "core/internal/mediums/webrtc_socket.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/medium_environment.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/webrtc.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
#include "webrtc/api/rtc_error.h"
|
||||
#include "webrtc/api/scoped_refptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
class ConnectionFlowTest : public ::testing::Test {
|
||||
protected:
|
||||
ConnectionFlowTest() {
|
||||
MediumEnvironment::Instance().Stop();
|
||||
MediumEnvironment::Instance().Start({.webrtc_enabled = true});
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> CopyCandidate(
|
||||
const webrtc::IceCandidateInterface* candidate) {
|
||||
return webrtc::CreateIceCandidate(candidate->sdp_mid(),
|
||||
candidate->sdp_mline_index(),
|
||||
candidate->candidate());
|
||||
}
|
||||
|
||||
// TODO(bfranz) - Add test that deterministically sends answerer_ice_candidates
|
||||
// before answer is sent.
|
||||
TEST_F(ConnectionFlowTest, SuccessfulOfferAnswerFlow) {
|
||||
WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer;
|
||||
|
||||
Future<ByteArray> message_received_future;
|
||||
|
||||
Future<WebRtcSocketWrapper> offerer_socket_future, answerer_socket_future;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer, answerer;
|
||||
|
||||
// Send Ice Candidates immediately when you retrieve them
|
||||
offerer = ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
[&answerer](const webrtc::IceCandidateInterface* candidate) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> vec;
|
||||
vec.push_back(CopyCandidate(candidate));
|
||||
// The callback might be alive while the objects in test are
|
||||
// destroyed.
|
||||
if (answerer)
|
||||
answerer->OnRemoteIceCandidatesReceived(std::move(vec));
|
||||
}},
|
||||
{.data_channel_open_cb =
|
||||
[&offerer_socket_future](WebRtcSocketWrapper socket) {
|
||||
offerer_socket_future.Set(std::move(socket));
|
||||
}},
|
||||
webrtc_medium_offerer);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
answerer = ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
[&offerer](const webrtc::IceCandidateInterface* candidate) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> vec;
|
||||
vec.push_back(CopyCandidate(candidate));
|
||||
// The callback might be alive while the objects in test are
|
||||
// destroyed.
|
||||
if (offerer)
|
||||
offerer->OnRemoteIceCandidatesReceived(std::move(vec));
|
||||
}},
|
||||
{.data_channel_open_cb =
|
||||
[&answerer_socket_future](WebRtcSocketWrapper socket) {
|
||||
answerer_socket_future.Set(std::move(socket));
|
||||
}},
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
// Create and send offer
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
ASSERT_TRUE(offer.IsValid());
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
EXPECT_TRUE(answerer->OnOfferReceived(offer));
|
||||
EXPECT_TRUE(offerer->SetLocalSessionDescription(std::move(offer)));
|
||||
|
||||
// Create and send answer
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
ASSERT_TRUE(answer.IsValid());
|
||||
EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer);
|
||||
EXPECT_TRUE(offerer->OnAnswerReceived(answer));
|
||||
EXPECT_TRUE(answerer->SetLocalSessionDescription(std::move(answer)));
|
||||
|
||||
// Retrieve Data Channels
|
||||
ExceptionOr<WebRtcSocketWrapper> offerer_socket =
|
||||
offerer_socket_future.Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(offerer_socket.ok());
|
||||
ExceptionOr<WebRtcSocketWrapper> answerer_socket =
|
||||
answerer_socket_future.Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(answerer_socket.ok());
|
||||
|
||||
// Send message on data channel
|
||||
const char message[] = "Test";
|
||||
offerer_socket.result().GetImpl().GetOutputStream().Write(
|
||||
ByteArray(message, 4));
|
||||
ExceptionOr<ByteArray> received_message =
|
||||
answerer_socket.result().GetImpl().GetInputStream().Read(4);
|
||||
EXPECT_TRUE(received_message.ok());
|
||||
EXPECT_EQ(received_message.result(), ByteArray{message});
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, CreateAnswerBeforeOfferReceived) {
|
||||
WebRtcMedium webrtc_medium;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> answerer = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), webrtc_medium);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
EXPECT_FALSE(answer.IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, SetAnswerBeforeOffer) {
|
||||
WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer =
|
||||
ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(),
|
||||
webrtc_medium_offerer);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
std::unique_ptr<ConnectionFlow> answerer =
|
||||
ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(),
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
ASSERT_TRUE(offer.IsValid());
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
// Did not set offer as local session description
|
||||
EXPECT_TRUE(answerer->OnOfferReceived(offer));
|
||||
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
ASSERT_TRUE(answer.IsValid());
|
||||
EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer);
|
||||
EXPECT_FALSE(offerer->OnAnswerReceived(answer));
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, CannotCreateOfferAfterClose) {
|
||||
WebRtcMedium webrtc_medium;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), webrtc_medium);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
|
||||
EXPECT_TRUE(offerer->CloseIfNotConnected());
|
||||
|
||||
EXPECT_FALSE(offerer->CreateOffer().IsValid());
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, CannotSetSessionDescriptionAfterClose) {
|
||||
WebRtcMedium webrtc_medium;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), webrtc_medium);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
ASSERT_TRUE(offer.IsValid());
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
|
||||
EXPECT_TRUE(offerer->CloseIfNotConnected());
|
||||
|
||||
EXPECT_FALSE(offerer->SetLocalSessionDescription(offer));
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, CannotReceiveOfferAfterClose) {
|
||||
WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer =
|
||||
ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(),
|
||||
webrtc_medium_offerer);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
std::unique_ptr<ConnectionFlow> answerer =
|
||||
ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(),
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
EXPECT_TRUE(answerer->CloseIfNotConnected());
|
||||
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
ASSERT_TRUE(offer.IsValid());
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
|
||||
EXPECT_FALSE(answerer->OnOfferReceived(offer));
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, NullPeerConnection) {
|
||||
MediumEnvironment::Instance().SetUseValidPeerConnection(
|
||||
/*use_valid_peer_connection=*/false);
|
||||
|
||||
WebRtcMedium medium;
|
||||
std::unique_ptr<ConnectionFlow> answerer = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), medium);
|
||||
EXPECT_EQ(answerer, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, PeerConnectionTimeout) {
|
||||
MediumEnvironment::Instance().SetUseValidPeerConnection(
|
||||
/*use_valid_peer_connection=*/true);
|
||||
WebRtcMedium medium1;
|
||||
std::unique_ptr<ConnectionFlow> flow1 = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), medium1);
|
||||
EXPECT_NE(flow1, nullptr);
|
||||
|
||||
// Attempt to trigger the 2.5s peer connection timeout.
|
||||
MediumEnvironment::Instance().SetPeerConnectionLatency(absl::Seconds(5));
|
||||
WebRtcMedium medium2;
|
||||
std::unique_ptr<ConnectionFlow> flow2 = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), medium2);
|
||||
EXPECT_EQ(flow2, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, TerminateAnswerer) {
|
||||
WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer;
|
||||
|
||||
Future<ByteArray> message_received_future;
|
||||
|
||||
Future<WebRtcSocketWrapper> offerer_socket_future, answerer_socket_future;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer, answerer;
|
||||
|
||||
// Send Ice Candidates immediately when you retrieve them
|
||||
offerer = ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
[&answerer](const webrtc::IceCandidateInterface* candidate) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> vec;
|
||||
vec.push_back(CopyCandidate(candidate));
|
||||
// The callback might be alive while the objects in test are
|
||||
// destroyed.
|
||||
if (answerer)
|
||||
answerer->OnRemoteIceCandidatesReceived(std::move(vec));
|
||||
}},
|
||||
{.data_channel_open_cb =
|
||||
[&offerer_socket_future](WebRtcSocketWrapper socket) {
|
||||
offerer_socket_future.Set(std::move(socket));
|
||||
}},
|
||||
webrtc_medium_offerer);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
answerer = ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
[&offerer](const webrtc::IceCandidateInterface* candidate) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> vec;
|
||||
vec.push_back(CopyCandidate(candidate));
|
||||
// The callback might be alive while the objects in test are
|
||||
// destroyed.
|
||||
if (offerer)
|
||||
offerer->OnRemoteIceCandidatesReceived(std::move(vec));
|
||||
}},
|
||||
{.data_channel_open_cb =
|
||||
[&answerer_socket_future](WebRtcSocketWrapper wrapper) {
|
||||
answerer_socket_future.Set(std::move(wrapper));
|
||||
}},
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
// Create and send offer
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
ASSERT_TRUE(offer.IsValid());
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
EXPECT_TRUE(answerer->OnOfferReceived(offer));
|
||||
EXPECT_TRUE(offerer->SetLocalSessionDescription(std::move(offer)));
|
||||
|
||||
// Create and send answer
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
ASSERT_TRUE(answer.IsValid());
|
||||
EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer);
|
||||
EXPECT_TRUE(offerer->OnAnswerReceived(answer));
|
||||
EXPECT_TRUE(answerer->SetLocalSessionDescription(std::move(answer)));
|
||||
|
||||
// Retrieve Data Channels
|
||||
ExceptionOr<WebRtcSocketWrapper> offerer_socket =
|
||||
offerer_socket_future.Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(offerer_socket.ok());
|
||||
ExceptionOr<WebRtcSocketWrapper> answerer_socket =
|
||||
answerer_socket_future.Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(offerer_socket.ok());
|
||||
|
||||
CountDownLatch latch(1);
|
||||
auto pc = answerer->GetPeerConnection();
|
||||
pc->signaling_thread()->PostTask([pc, latch]() mutable {
|
||||
pc->Close();
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
|
||||
// Send message on data channel
|
||||
std::string message = "Test";
|
||||
offerer_socket.result().GetOutputStream().Write(ByteArray{message});
|
||||
ExceptionOr<ByteArray> received_message =
|
||||
answerer_socket.result().GetInputStream().Read(4);
|
||||
EXPECT_FALSE(received_message.ok());
|
||||
}
|
||||
|
||||
TEST_F(ConnectionFlowTest, TerminateOfferer) {
|
||||
WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer;
|
||||
|
||||
Future<ByteArray> message_received_future;
|
||||
|
||||
Future<WebRtcSocketWrapper> offerer_socket_future, answerer_socket_future;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer, answerer;
|
||||
|
||||
// Send Ice Candidates immediately when you retrieve them
|
||||
offerer = ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
[&answerer](const webrtc::IceCandidateInterface* candidate) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> vec;
|
||||
vec.push_back(CopyCandidate(candidate));
|
||||
// The callback might be alive while the objects in test are
|
||||
// destroyed.
|
||||
if (answerer)
|
||||
answerer->OnRemoteIceCandidatesReceived(std::move(vec));
|
||||
}},
|
||||
{.data_channel_open_cb =
|
||||
[&offerer_socket_future](WebRtcSocketWrapper socket) {
|
||||
offerer_socket_future.Set(std::move(socket));
|
||||
}},
|
||||
webrtc_medium_offerer);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
answerer = ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
[&offerer](const webrtc::IceCandidateInterface* candidate) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> vec;
|
||||
vec.push_back(CopyCandidate(candidate));
|
||||
// The callback might be alive while the objects in test are
|
||||
// destroyed.
|
||||
if (offerer)
|
||||
offerer->OnRemoteIceCandidatesReceived(std::move(vec));
|
||||
}},
|
||||
{.data_channel_open_cb =
|
||||
[&answerer_socket_future](WebRtcSocketWrapper wrapper) {
|
||||
answerer_socket_future.Set(std::move(wrapper));
|
||||
}},
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
// Create and send offer
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
ASSERT_TRUE(offer.IsValid());
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
EXPECT_TRUE(answerer->OnOfferReceived(offer));
|
||||
EXPECT_TRUE(offerer->SetLocalSessionDescription(std::move(offer)));
|
||||
|
||||
// Create and send answer
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
ASSERT_TRUE(answer.IsValid());
|
||||
EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer);
|
||||
EXPECT_TRUE(offerer->OnAnswerReceived(answer));
|
||||
EXPECT_TRUE(answerer->SetLocalSessionDescription(std::move(answer)));
|
||||
|
||||
// Retrieve Data Channels
|
||||
ExceptionOr<WebRtcSocketWrapper> offerer_socket =
|
||||
offerer_socket_future.Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(offerer_socket.ok());
|
||||
ExceptionOr<WebRtcSocketWrapper> answerer_socket =
|
||||
answerer_socket_future.Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(offerer_socket.ok());
|
||||
|
||||
CountDownLatch latch(1);
|
||||
auto pc = offerer->GetPeerConnection();
|
||||
pc->signaling_thread()->PostTask([pc, latch]() mutable {
|
||||
pc->Close();
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
|
||||
// Send message on data channel
|
||||
std::string message = "Test";
|
||||
offerer_socket.result().GetOutputStream().Write(ByteArray{message});
|
||||
ExceptionOr<ByteArray> received_message =
|
||||
answerer_socket.result().GetInputStream().Read(4);
|
||||
EXPECT_FALSE(received_message.ok());
|
||||
}
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,43 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_
|
||||
|
||||
#include "core/internal/mediums/webrtc_socket.h"
|
||||
#include "core/listeners.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Callbacks from the data channel.
|
||||
struct DataChannelListener {
|
||||
// Called when the data channel is open and the socket wraper is ready to
|
||||
// read and write.
|
||||
std::function<void(WebRtcSocketWrapper)> data_channel_open_cb =
|
||||
DefaultCallback<WebRtcSocketWrapper>();
|
||||
|
||||
// Called when the data channel is closed.
|
||||
std::function<void()> data_channel_closed_cb = DefaultCallback<>();
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_
|
||||
@@ -1,39 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_
|
||||
|
||||
#include "core/listeners.h"
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Callbacks from local ice candidate collection.
|
||||
struct LocalIceCandidateListener {
|
||||
// Called when a new local ice candidate has been found.
|
||||
std::function<void(const webrtc::IceCandidateInterface*)>
|
||||
local_ice_candidate_found_cb = location::nearby::DefaultCallback<
|
||||
const webrtc::IceCandidateInterface*>();
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_
|
||||
@@ -1,64 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_
|
||||
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
// Wrapper object around SessionDescriptionInterface*.
|
||||
// This object owns the SessionDescriptionInterface* unless Release() has been
|
||||
// called.
|
||||
class SessionDescriptionWrapper {
|
||||
public:
|
||||
SessionDescriptionWrapper() = default;
|
||||
explicit SessionDescriptionWrapper(webrtc::SessionDescriptionInterface* sdp)
|
||||
: impl_(sdp) {}
|
||||
|
||||
// Copy constructor that performs a deep copy, i.e. creates a new
|
||||
// SessionDescriptionInterface.
|
||||
SessionDescriptionWrapper(const SessionDescriptionWrapper& sdp) {
|
||||
if (sdp.IsValid()) {
|
||||
impl_ = webrtc::CreateSessionDescription(sdp.GetType(), sdp.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
SessionDescriptionWrapper(SessionDescriptionWrapper&&) = default;
|
||||
SessionDescriptionWrapper& operator=(SessionDescriptionWrapper&&) = default;
|
||||
|
||||
// Release the ownership of the SessionDescriptionInterface*.
|
||||
webrtc::SessionDescriptionInterface* Release() { return impl_.release(); }
|
||||
|
||||
// Returns a string representation of the sdp. Only call this, if IsValid() is
|
||||
// true.
|
||||
std::string ToString() const {
|
||||
std::string str;
|
||||
impl_->ToString(&str);
|
||||
return str;
|
||||
}
|
||||
|
||||
// Returns the SdpType of the SessionDescriptionInterface. Only call this, if
|
||||
// IsValid() is true.
|
||||
webrtc::SdpType GetType() const { return impl_->GetType(); }
|
||||
|
||||
const webrtc::SessionDescriptionInterface& GetSdp() { return *impl_; }
|
||||
|
||||
// Return whether this object currently holds a SessionDescriptionInterface.
|
||||
bool IsValid() const { return impl_ != nullptr; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> impl_;
|
||||
};
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_
|
||||
@@ -1,134 +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 "core/internal/mediums/webrtc/signaling_frames.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace webrtc_frames {
|
||||
using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame;
|
||||
|
||||
namespace {
|
||||
|
||||
ByteArray FrameToByteArray(const WebRtcSignalingFrame& signaling_frame) {
|
||||
std::string message;
|
||||
signaling_frame.SerializeToString(&message);
|
||||
return ByteArray(message.c_str(), message.size());
|
||||
}
|
||||
|
||||
void SetSenderId(const WebrtcPeerId& sender_id, WebRtcSignalingFrame& frame) {
|
||||
frame.mutable_sender_id()->set_id(sender_id.GetId());
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> DecodeIceCandidate(
|
||||
location::nearby::mediums::IceCandidate ice_candidate_proto) {
|
||||
webrtc::SdpParseError error;
|
||||
return std::unique_ptr<webrtc::IceCandidateInterface>(
|
||||
webrtc::CreateIceCandidate(ice_candidate_proto.sdp_mid(),
|
||||
ice_candidate_proto.sdp_m_line_index(),
|
||||
ice_candidate_proto.sdp(), &error));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ByteArray EncodeReadyForSignalingPoke(const WebrtcPeerId& sender_id) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
signaling_frame.set_allocated_ready_for_signaling_poke(
|
||||
new location::nearby::mediums::ReadyForSignalingPoke());
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ByteArray EncodeOffer(const WebrtcPeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
std::string offer_str;
|
||||
offer.ToString(&offer_str);
|
||||
signaling_frame.mutable_offer()
|
||||
->mutable_session_description()
|
||||
->set_description(offer_str);
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ByteArray EncodeAnswer(const WebrtcPeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
std::string answer_str;
|
||||
answer.ToString(&answer_str);
|
||||
signaling_frame.mutable_answer()
|
||||
->mutable_session_description()
|
||||
->set_description(answer_str);
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ByteArray EncodeIceCandidates(
|
||||
const WebrtcPeerId& sender_id,
|
||||
const std::vector<location::nearby::mediums::IceCandidate>&
|
||||
ice_candidates) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
for (const auto& ice_candidate : ice_candidates) {
|
||||
*signaling_frame.mutable_ice_candidates()->add_ice_candidates() =
|
||||
ice_candidate;
|
||||
}
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
return webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kOffer,
|
||||
frame.offer().session_description().description());
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
return webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kAnswer,
|
||||
frame.answer().session_description().description());
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
for (const auto& candidate : frame.ice_candidates().ice_candidates()) {
|
||||
ice_candidates.push_back(DecodeIceCandidate(candidate));
|
||||
}
|
||||
return ice_candidates;
|
||||
}
|
||||
|
||||
location::nearby::mediums::IceCandidate EncodeIceCandidate(
|
||||
const webrtc::IceCandidateInterface& ice_candidate) {
|
||||
std::string sdp;
|
||||
ice_candidate.ToString(&sdp);
|
||||
location::nearby::mediums::IceCandidate ice_candidate_proto;
|
||||
ice_candidate_proto.set_sdp(sdp);
|
||||
ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid());
|
||||
ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index());
|
||||
return ice_candidate_proto;
|
||||
}
|
||||
|
||||
} // namespace webrtc_frames
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,58 +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.
|
||||
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "core/internal/mediums/webrtc_peer_id.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "proto/mediums/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace webrtc_frames {
|
||||
|
||||
ByteArray EncodeReadyForSignalingPoke(const WebrtcPeerId& sender_id);
|
||||
|
||||
ByteArray EncodeOffer(const WebrtcPeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer);
|
||||
ByteArray EncodeAnswer(const WebrtcPeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer);
|
||||
|
||||
ByteArray EncodeIceCandidates(
|
||||
const WebrtcPeerId& sender_id,
|
||||
const std::vector<location::nearby::mediums::IceCandidate>& ice_candidates);
|
||||
location::nearby::mediums::IceCandidate EncodeIceCandidate(
|
||||
const webrtc::IceCandidateInterface& ice_candidate);
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
|
||||
} // namespace webrtc_frames
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
@@ -1,197 +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 "core/internal/mediums/webrtc/signaling_frames.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "google/protobuf/text_format.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "protobuf-matchers/protocol-buffer-matchers.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "core/internal/mediums/webrtc_peer_id.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace webrtc_frames {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kSampleSdp[] =
|
||||
"v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 "
|
||||
"0\r\na=msid-semantic: WMS\r\n";
|
||||
|
||||
const char kIceCandidateSdp1[] =
|
||||
"a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host";
|
||||
const char kIceCandidateSdp2[] =
|
||||
"a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr";
|
||||
|
||||
const char kIceSdpMid[] = "data";
|
||||
const int kIceSdpMLineIndex = 0;
|
||||
|
||||
const char kOfferProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: OFFER_TYPE
|
||||
offer {
|
||||
session_description {
|
||||
description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char kAnswerProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: ANSWER_TYPE
|
||||
answer {
|
||||
session_description {
|
||||
description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char kIceCandidatesProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: ICE_CANDIDATES_TYPE
|
||||
ice_candidates {
|
||||
ice_candidates {
|
||||
sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0"
|
||||
sdp_mid: "data"
|
||||
sdp_m_line_index: 0
|
||||
}
|
||||
ice_candidates {
|
||||
sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0"
|
||||
sdp_mid: "data"
|
||||
sdp_m_line_index: 0
|
||||
}
|
||||
}
|
||||
)";
|
||||
} // namespace
|
||||
|
||||
TEST(SignalingFramesTest, SignalingPoke) {
|
||||
WebrtcPeerId sender_id("abc");
|
||||
ByteArray encoded_poke = EncodeReadyForSignalingPoke(sender_id);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(std::string(encoded_poke.data(), encoded_poke.size()));
|
||||
|
||||
EXPECT_THAT(frame, protobuf_matchers::EqualsProto(R"pb(
|
||||
sender_id { id: "abc" }
|
||||
type: READY_FOR_SIGNALING_POKE_TYPE
|
||||
ready_for_signaling_poke {}
|
||||
)pb"));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidOffer) {
|
||||
WebrtcPeerId sender_id("abc");
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> offer =
|
||||
webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp);
|
||||
ByteArray encoded_offer = EncodeOffer(sender_id, *offer);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_offer.data(), encoded_offer.size()));
|
||||
|
||||
EXPECT_THAT(frame, protobuf_matchers::EqualsProto(kOfferProto));
|
||||
}
|
||||
|
||||
TEST(SignaingFramesTest, DecodeValidOffer) {
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kOfferProto, &frame);
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> decoded_offer =
|
||||
DecodeOffer(frame);
|
||||
|
||||
EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType());
|
||||
std::string description;
|
||||
decoded_offer->ToString(&description);
|
||||
EXPECT_EQ(kSampleSdp, description);
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidAnswer) {
|
||||
WebrtcPeerId sender_id("abc");
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> answer(
|
||||
webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp));
|
||||
ByteArray encoded_answer = EncodeAnswer(sender_id, *answer);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_answer.data(), encoded_answer.size()));
|
||||
|
||||
EXPECT_THAT(frame, protobuf_matchers::EqualsProto(kAnswerProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidAnswer) {
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kAnswerProto, &frame);
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> decoded_answer =
|
||||
DecodeAnswer(frame);
|
||||
|
||||
EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType());
|
||||
std::string description;
|
||||
decoded_answer->ToString(&description);
|
||||
EXPECT_EQ(kSampleSdp, description);
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidIceCandidates) {
|
||||
WebrtcPeerId sender_id("abc");
|
||||
webrtc::SdpParseError error;
|
||||
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error));
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error));
|
||||
std::vector<location::nearby::mediums::IceCandidate> encoded_candidates_vec;
|
||||
for (const auto& ice_candidate : ice_candidates) {
|
||||
encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate));
|
||||
}
|
||||
ByteArray encoded_candidates =
|
||||
EncodeIceCandidates(sender_id, encoded_candidates_vec);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_candidates.data(), encoded_candidates.size()));
|
||||
|
||||
EXPECT_THAT(frame, protobuf_matchers::EqualsProto(kIceCandidatesProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidIceCandidates) {
|
||||
webrtc::SdpParseError error;
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error));
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error));
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame);
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
decoded_candidates = DecodeIceCandidates(frame);
|
||||
|
||||
ASSERT_EQ(2u, decoded_candidates.size());
|
||||
for (int i = 0; i < static_cast<int>(decoded_candidates.size()); i++) {
|
||||
EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent(
|
||||
decoded_candidates[i]->candidate()));
|
||||
EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid());
|
||||
EXPECT_EQ(ice_candidates[i]->sdp_mline_index(),
|
||||
decoded_candidates[i]->sdp_mline_index());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc_frames
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,190 +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 "core/internal/mediums/webrtc/webrtc_socket_impl.h"
|
||||
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// OutputStreamImpl
|
||||
Exception WebRtcSocket::OutputStreamImpl::Write(const ByteArray& data) {
|
||||
if (data.size() > kMaxDataSize) {
|
||||
NEARBY_LOG(WARNING, "Sending data larger than 1MB");
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
socket_->BlockUntilSufficientSpaceInBuffer(data.size());
|
||||
|
||||
if (socket_->IsClosed()) {
|
||||
NEARBY_LOG(WARNING, "Tried sending message while socket is closed");
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
if (!socket_->SendMessage(data)) {
|
||||
NEARBY_LOG(INFO, "Unable to write data to socket.");
|
||||
return {Exception::kIo};
|
||||
}
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
Exception WebRtcSocket::OutputStreamImpl::Flush() {
|
||||
// Java implementation is empty.
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
Exception WebRtcSocket::OutputStreamImpl::Close() {
|
||||
socket_->Close();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
// WebRtcSocket
|
||||
WebRtcSocket::WebRtcSocket(
|
||||
const std::string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)
|
||||
: name_(name), data_channel_(std::move(data_channel)) {
|
||||
NEARBY_LOGS(INFO) << "WebRtcSocket::WebRtcSocket(" << name_
|
||||
<< ") this: " << this;
|
||||
data_channel_->RegisterObserver(this);
|
||||
}
|
||||
|
||||
WebRtcSocket::~WebRtcSocket() {
|
||||
NEARBY_LOGS(INFO) << "WebRtcSocket::~WebRtcSocket(" << name_
|
||||
<< ") this: " << this;
|
||||
Close();
|
||||
NEARBY_LOGS(INFO) << "WebRtcSocket::~WebRtcSocket(" << name_
|
||||
<< ") this: " << this << " done";
|
||||
}
|
||||
|
||||
InputStream& WebRtcSocket::GetInputStream() { return pipe_.GetInputStream(); }
|
||||
|
||||
OutputStream& WebRtcSocket::GetOutputStream() { return output_stream_; }
|
||||
|
||||
void WebRtcSocket::Close() {
|
||||
NEARBY_LOGS(INFO) << "WebRtcSocket::Close(" << name_ << ") this: " << this;
|
||||
if (closed_.Set(true)) return;
|
||||
|
||||
ClosePipe();
|
||||
// NOTE: This call blocks and triggers a state change on the siginaling thread
|
||||
// to 'closing' but does not block until 'closed' is sent so the data channel
|
||||
// is not fully closed when this call is done.
|
||||
data_channel_->Close();
|
||||
NEARBY_LOGS(INFO) << "WebRtcSocket::Close(" << name_ << ") this: " << this
|
||||
<< " done";
|
||||
}
|
||||
|
||||
void WebRtcSocket::OnStateChange() {
|
||||
// Running on the signaling thread right now.
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< "WebRtcSocket::OnStateChange() webrtc data channel state: "
|
||||
<< webrtc::DataChannelInterface::DataStateString(data_channel_->state());
|
||||
switch (data_channel_->state()) {
|
||||
case webrtc::DataChannelInterface::DataState::kConnecting:
|
||||
break;
|
||||
case webrtc::DataChannelInterface::DataState::kOpen:
|
||||
// We implicitly depend on the |socket_listener_| to offload from
|
||||
// the signaling thread so it does not get blocked.
|
||||
socket_listener_.socket_ready_cb(this);
|
||||
break;
|
||||
case webrtc::DataChannelInterface::DataState::kClosing:
|
||||
break;
|
||||
case webrtc::DataChannelInterface::DataState::kClosed:
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
"WebRtcSocket::OnStateChange() unregistering data channel observer.");
|
||||
data_channel_->UnregisterObserver();
|
||||
// This will trigger a destruction of the owning connection flow
|
||||
// We implicitly depend on the |socket_listener_| to offload from
|
||||
// the signaling thread so it does not get blocked.
|
||||
socket_listener_.socket_closed_cb(this);
|
||||
|
||||
if (!closed_.Set(true)) {
|
||||
OffloadFromSignalingThread([this] { ClosePipe(); });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
void WebRtcSocket::OnMessage(const webrtc::DataBuffer& buffer) {
|
||||
// This is a data channel callback on the signaling thread, lets off load so
|
||||
// we don't block signaling.
|
||||
OffloadFromSignalingThread(
|
||||
[this, buffer = ByteArray(buffer.data.data<char>(), buffer.size())] {
|
||||
if (!pipe_.GetOutputStream().Write(buffer).Ok()) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pipe_.GetOutputStream().Flush().Ok()) {
|
||||
Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WebRtcSocket::OnBufferedAmountChange(uint64_t sent_data_size) {
|
||||
// This is a data channel callback on the signaling thread, lets off load so
|
||||
// we don't block signaling.
|
||||
OffloadFromSignalingThread([this] { WakeUpWriter(); });
|
||||
}
|
||||
|
||||
bool WebRtcSocket::SendMessage(const ByteArray& data) {
|
||||
return data_channel_->Send(
|
||||
webrtc::DataBuffer(std::string(data.data(), data.size())));
|
||||
}
|
||||
|
||||
bool WebRtcSocket::IsClosed() { return closed_.Get(); }
|
||||
|
||||
void WebRtcSocket::ClosePipe() {
|
||||
NEARBY_LOGS(INFO) << "WebRtcSocket::ClosePipe(" << name_
|
||||
<< ") this: " << this;
|
||||
// This is thread-safe to close these sockets even if a read or write is in
|
||||
// process on another thread, Close will wait for the exclusive mutex before
|
||||
// setting state.
|
||||
pipe_.GetInputStream().Close();
|
||||
pipe_.GetOutputStream().Close();
|
||||
WakeUpWriter();
|
||||
NEARBY_LOGS(INFO) << "WebRtcSocket::ClosePipe(" << name_ << ") this: " << this
|
||||
<< " done";
|
||||
}
|
||||
|
||||
// Must not be called on signalling thread.
|
||||
void WebRtcSocket::WakeUpWriter() {
|
||||
MutexLock lock(&backpressure_mutex_);
|
||||
buffer_variable_.Notify();
|
||||
}
|
||||
|
||||
void WebRtcSocket::SetSocketListener(SocketListener&& listener) {
|
||||
socket_listener_ = std::move(listener);
|
||||
}
|
||||
|
||||
void WebRtcSocket::BlockUntilSufficientSpaceInBuffer(int length) {
|
||||
MutexLock lock(&backpressure_mutex_);
|
||||
while (!IsClosed() &&
|
||||
(data_channel_->buffered_amount() + length > kMaxDataSize)) {
|
||||
// TODO(himanshujaju): Add wait with timeout.
|
||||
buffer_variable_.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtcSocket::OffloadFromSignalingThread(Runnable runnable) {
|
||||
single_thread_executor_.Execute(std::move(runnable));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user