nearby sdk refactor

PiperOrigin-RevId: 425434260
This commit is contained in:
hais
2022-02-02 11:56:39 -08:00
committed by hai007
parent 287f0d7174
commit f5fcd35ced
1879 changed files with 2485 additions and 2332 deletions
-135
View File
@@ -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",
],
)
-113
View File
@@ -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",
],
)
-361
View File
@@ -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
-188
View File
@@ -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_
-264
View File
@@ -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
-71
View File
@@ -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
-33
View File
@@ -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
-73
View File
@@ -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_
-94
View File
@@ -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
-42
View File
@@ -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_
-87
View File
@@ -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
-57
View File
@@ -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_
-71
View File
@@ -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
-744
View File
@@ -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
-265
View File
@@ -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_
-97
View File
@@ -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
@@ -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.
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_IMPL_H_
#define CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_IMPL_H_
#include <memory>
#include "core/listeners.h"
#include "platform/base/input_stream.h"
#include "platform/base/output_stream.h"
#include "platform/base/socket.h"
#include "platform/public/atomic_boolean.h"
#include "platform/public/condition_variable.h"
#include "platform/public/mutex.h"
#include "platform/public/pipe.h"
#include "platform/public/single_thread_executor.h"
#include "webrtc/api/data_channel_interface.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// Maximum data size: 1 MB
constexpr int kMaxDataSize = 1 * 1024 * 1024;
// Defines the Socket implementation specific to WebRTC, which uses the WebRTC
// data channel to send and receive messages.
//
// Messages are buffered here to prevent the data channel from overflowing,
// which could lead to data loss.
class WebRtcSocket : public Socket, public webrtc::DataChannelObserver {
public:
WebRtcSocket(const std::string& name,
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
~WebRtcSocket() override;
WebRtcSocket(const WebRtcSocket& other) = delete;
WebRtcSocket& operator=(const WebRtcSocket& other) = delete;
// Overrides for location::nearby::Socket:
InputStream& GetInputStream() override;
OutputStream& GetOutputStream() override;
void Close() override;
// webrtc::DataChannelObserver:
void OnStateChange() override;
void OnMessage(const webrtc::DataBuffer& buffer) override;
void OnBufferedAmountChange(uint64_t sent_data_size) override;
// Listener class the gets called when the socket is ready or closed
struct SocketListener {
std::function<void(WebRtcSocket*)> socket_ready_cb =
DefaultCallback<WebRtcSocket*>();
std::function<void(WebRtcSocket*)> socket_closed_cb =
DefaultCallback<WebRtcSocket*>();
};
void SetSocketListener(SocketListener&& listener);
private:
class OutputStreamImpl : public OutputStream {
public:
explicit OutputStreamImpl(WebRtcSocket* const socket) : socket_(socket) {}
~OutputStreamImpl() override = default;
OutputStreamImpl(const OutputStreamImpl& other) = delete;
OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete;
// OutputStream:
Exception Write(const ByteArray& data) override;
Exception Flush() override;
Exception Close() override;
private:
// |this| OutputStreamImpl is owned by |socket_|.
WebRtcSocket* const socket_;
};
void WakeUpWriter();
bool IsClosed();
void ClosePipe();
bool SendMessage(const ByteArray& data);
void BlockUntilSufficientSpaceInBuffer(int length);
void OffloadFromSignalingThread(Runnable runnable);
std::string name_;
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
Pipe pipe_;
OutputStreamImpl output_stream_{this};
AtomicBoolean closed_{false};
SocketListener socket_listener_;
mutable Mutex backpressure_mutex_;
ConditionVariable buffer_variable_{&backpressure_mutex_};
// This should be destroyed first to ensure any remaining tasks flushed on
// shutdown get run while the other members are still alive.
SingleThreadExecutor single_thread_executor_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_IMPL_H_
@@ -1,235 +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 <memory>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "platform/base/byte_array.h"
#include "webrtc/api/data_channel_interface.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
// using TestPlatform = platform::ImplementationPlatform;
const char kSocketName[] = "TestSocket";
class MockDataChannel
: public rtc::RefCountedObject<webrtc::DataChannelInterface> {
public:
MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*));
MOCK_METHOD(void, UnregisterObserver, ());
MOCK_METHOD(std::string, label, (), (const));
MOCK_METHOD(bool, reliable, (), (const));
MOCK_METHOD(int, id, (), (const));
MOCK_METHOD(DataState, state, (), (const));
MOCK_METHOD(uint32_t, messages_sent, (), (const));
MOCK_METHOD(uint64_t, bytes_sent, (), (const));
MOCK_METHOD(uint32_t, messages_received, (), (const));
MOCK_METHOD(uint64_t, bytes_received, (), (const));
MOCK_METHOD(uint64_t, buffered_amount, (), (const));
MOCK_METHOD(void, Close, ());
MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&));
};
} // namespace
TEST(WebRtcSocketTest, ReadFromSocket) {
const char* message = "message";
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
webrtc_socket.OnMessage(webrtc::DataBuffer{message});
ExceptionOr<ByteArray> result = webrtc_socket.GetInputStream().Read(7);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.result(), ByteArray{message});
}
TEST(WebRtcSocketTest, ReadMultipleMessages) {
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
webrtc_socket.OnMessage(webrtc::DataBuffer{"Me"});
webrtc_socket.OnMessage(webrtc::DataBuffer{"ssa"});
webrtc_socket.OnMessage(webrtc::DataBuffer{"ge"});
ExceptionOr<ByteArray> result;
// This behaviour is different from the Java code
result = webrtc_socket.GetInputStream().Read(7);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.result(), ByteArray{"Me"});
result = webrtc_socket.GetInputStream().Read(7);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.result(), ByteArray{"ssa"});
result = webrtc_socket.GetInputStream().Read(7);
EXPECT_TRUE(result.ok());
EXPECT_EQ(result.result(), ByteArray{"ge"});
}
TEST(WebRtcSocketTest, WriteToSocket) {
const ByteArray kMessage{"Message"};
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
EXPECT_CALL(*mock_data_channel, Send(testing::_))
.WillRepeatedly(testing::Return(true));
EXPECT_TRUE(webrtc_socket.GetOutputStream().Write(kMessage).Ok());
}
TEST(WebRtcSocketTest, SendDataBiggerThanMax) {
const ByteArray kMessage{kMaxDataSize + 1};
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0);
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
Exception{Exception::kIo});
}
TEST(WebRtcSocketTest, WriteToDataChannelFails) {
ByteArray kMessage{"Message"};
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
ON_CALL(*mock_data_channel, Send(testing::_))
.WillByDefault(testing::Return(false));
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
Exception{Exception::kIo});
}
TEST(WebRtcSocketTest, Close) {
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
EXPECT_CALL(*mock_data_channel, Close());
int socket_closed_cb_called = 0;
webrtc_socket.SetSocketListener(
{.socket_closed_cb = [&](WebRtcSocket* socket) {
socket_closed_cb_called++;
}});
webrtc_socket.Close();
// We have to fake the close event to get the callback to run.
ON_CALL(*mock_data_channel, state())
.WillByDefault(
testing::Return(webrtc::DataChannelInterface::DataState::kClosed));
webrtc_socket.OnStateChange();
EXPECT_EQ(socket_closed_cb_called, 1);
}
TEST(WebRtcSocketTest, WriteOnClosedChannel) {
ByteArray kMessage{"Message"};
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
webrtc_socket.Close();
EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0);
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
Exception{Exception::kIo});
}
TEST(WebRtcSocketTest, ReadFromClosedChannel) {
ByteArray kMessage{"Message"};
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
ON_CALL(*mock_data_channel, Send(testing::_))
.WillByDefault(testing::Return(true));
webrtc_socket.GetOutputStream().Write(kMessage);
webrtc_socket.Close();
EXPECT_EQ(webrtc_socket.GetInputStream().Read(7).exception(), Exception::kIo);
}
TEST(WebRtcSocketTest, DataChannelCloseEventCleansUp) {
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
ON_CALL(*mock_data_channel, state())
.WillByDefault(
testing::Return(webrtc::DataChannelInterface::DataState::kClosed));
webrtc_socket.OnStateChange();
EXPECT_EQ(webrtc_socket.GetInputStream().Read(7).exception(), Exception::kIo);
// Calling Close again should be safe even if the channel is already shut
// down.
webrtc_socket.Close();
}
TEST(WebRtcSocketTest, OpenStateTriggersCallback) {
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
int socket_ready_cb_called = 0;
webrtc_socket.SetSocketListener(
{.socket_ready_cb = [&](WebRtcSocket* socket) {
socket_ready_cb_called++;
}});
ON_CALL(*mock_data_channel, state())
.WillByDefault(
testing::Return(webrtc::DataChannelInterface::DataState::kOpen));
webrtc_socket.OnStateChange();
EXPECT_EQ(socket_ready_cb_called, 1);
}
TEST(WebRtcSocketTest, CloseStateTriggersCallback) {
rtc::scoped_refptr<MockDataChannel> mock_data_channel(new MockDataChannel());
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
int socket_closed_cb_called = 0;
webrtc_socket.SetSocketListener(
{.socket_closed_cb = [&](WebRtcSocket* socket) {
socket_closed_cb_called++;
}});
ON_CALL(*mock_data_channel, state())
.WillByDefault(
testing::Return(webrtc::DataChannelInterface::DataState::kClosed));
webrtc_socket.OnStateChange();
EXPECT_EQ(socket_closed_cb_called, 1);
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -1,54 +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_peer_id.h"
#include <sstream>
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "core/internal/mediums/utils.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
constexpr int kPeerIdLength = 64;
std::string BytesToStringUppercase(const ByteArray& bytes) {
std::string hex_string(
absl::BytesToHexString(std::string(bytes.data(), bytes.size())));
absl::AsciiStrToUpper(&hex_string);
return hex_string;
}
} // namespace
WebrtcPeerId WebrtcPeerId::FromRandom() {
return FromSeed(Utils::GenerateRandomBytes(kPeerIdLength));
}
WebrtcPeerId WebrtcPeerId::FromSeed(const ByteArray& seed) {
ByteArray full_hash(Utils::Sha256Hash(seed, kPeerIdLength));
ByteArray hashed_seed(full_hash.data(), kPeerIdLength / 2);
return WebrtcPeerId(BytesToStringUppercase(hashed_seed));
}
bool WebrtcPeerId::IsValid() const { return !id_.empty(); }
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
@@ -1,52 +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_PEER_ID_H_
#define CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
#include <memory>
#include <string>
#include "platform/base/byte_array.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
// WebrtcPeerId is used as an identifier to exchange SDP messages to establish
// WebRTC p2p connection. An empty WebrtcPeerId is considered to be invalid.
class WebrtcPeerId {
public:
WebrtcPeerId() = default;
explicit WebrtcPeerId(const std::string& id) : id_(id) {}
~WebrtcPeerId() = default;
static WebrtcPeerId FromRandom();
static WebrtcPeerId FromSeed(const ByteArray& seed);
bool IsValid() const;
const std::string& GetId() const { return id_; }
private:
std::string id_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
@@ -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.
#include "core/internal/mediums/webrtc_peer_id.h"
#include <memory>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "platform/base/byte_array.h"
#include "platform/public/crypto.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
TEST(WebrtcPeerIdTest, GenerateRandomPeerId) {
WebrtcPeerId peer_id = WebrtcPeerId::FromRandom();
EXPECT_EQ(64, peer_id.GetId().size());
}
TEST(WebrtcPeerIdTest, GenerateFromSeed) {
// Values calculated by running actual SHA-256 hash on |seed|.
std::string seed = "seed";
std::string expected_peer_id =
"19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B";
ByteArray seed_bytes(seed);
WebrtcPeerId peer_id = WebrtcPeerId::FromSeed(seed_bytes);
EXPECT_EQ(64, peer_id.GetId().size());
EXPECT_EQ(expected_peer_id, peer_id.GetId());
}
TEST(WebrtcPeerIdTest, GetId) {
const std::string id = "this_is_a_test";
WebrtcPeerId peer_id(id);
EXPECT_EQ(id, peer_id.GetId());
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
-55
View File
@@ -1,55 +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_SOCKET_H_
#define CORE_INTERNAL_MEDIUMS_WEBRTC_SOCKET_H_
#include <memory>
#include "core/internal/mediums/webrtc/webrtc_socket_impl.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
class WebRtcSocketWrapper final {
public:
WebRtcSocketWrapper() = default;
WebRtcSocketWrapper(const WebRtcSocketWrapper&) = default;
WebRtcSocketWrapper& operator=(const WebRtcSocketWrapper&) = default;
explicit WebRtcSocketWrapper(std::unique_ptr<WebRtcSocket> socket)
: impl_(socket.release()) {}
~WebRtcSocketWrapper() = default;
InputStream& GetInputStream() { return impl_->GetInputStream(); }
OutputStream& GetOutputStream() { return impl_->GetOutputStream(); }
void Close() { return impl_->Close(); }
bool IsValid() const { return impl_ != nullptr; }
WebRtcSocket& GetImpl() { return *impl_; }
private:
std::shared_ptr<WebRtcSocket> impl_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
@@ -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_MEDIUMS_WEBRTC_SOCKET_STUB_H_
#define CORE_INTERNAL_MEDIUMS_WEBRTC_SOCKET_STUB_H_
#include <memory>
#include "platform/base/input_stream.h"
#include "platform/base/output_stream.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
class FakeInputStream : public InputStream {
public:
ExceptionOr<ByteArray> Read(std::int64_t size) {
return {Exception::kSuccess};
}
Exception Close() { return {Exception::kSuccess}; }
};
class FakeOutputStream : public OutputStream {
public:
Exception Write(const ByteArray& data) override {
return {Exception::kSuccess};
}
Exception Flush() override { return {Exception::kSuccess}; }
Exception Close() override { return {Exception::kSuccess}; }
};
class WebRtcSocketWrapper final {
public:
WebRtcSocketWrapper() = default;
WebRtcSocketWrapper(const WebRtcSocketWrapper&) = default;
WebRtcSocketWrapper& operator=(const WebRtcSocketWrapper&) = default;
~WebRtcSocketWrapper() = default;
InputStream& GetInputStream() { return fake_input_stream_; }
OutputStream& GetOutputStream() { return fake_output_stream_; }
void Close() {}
bool IsValid() const { return false; }
private:
FakeInputStream fake_input_stream_;
FakeOutputStream fake_output_stream_;
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_SOCKET_STUB_H_
-61
View File
@@ -1,61 +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_stub.h"
#include <functional>
#include <memory>
#include "core/internal/mediums/webrtc_socket_stub.h"
#include "platform/base/listeners.h"
#include "platform/public/cancelable_alarm.h"
#include "platform/public/future.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
WebRtc::WebRtc() = default;
WebRtc::~WebRtc() {}
const std::string WebRtc::GetDefaultCountryCode() { return "US"; }
bool WebRtc::IsAvailable() { return false; }
bool WebRtc::IsAcceptingConnections(const std::string& service_id) {
return false;
}
bool WebRtc::StartAcceptingConnections(const std::string& service_id,
const WebrtcPeerId& self_peer_id,
const LocationHint& location_hint,
AcceptedConnectionCallback callback) {
return false;
}
void WebRtc::StopAcceptingConnections(const std::string& service_id) {}
WebRtcSocketWrapper WebRtc::Connect(const std::string& service_id,
const WebrtcPeerId& remote_peer_id,
const LocationHint& location_hint,
CancellationFlag* cancellation_flag) {
return WebRtcSocketWrapper();
}
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
-82
View File
@@ -1,82 +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_STUB_H_
#define CORE_INTERNAL_MEDIUMS_WEBRTC_STUB_H_
#include <cstddef>
#include <functional>
#include <memory>
#include <string>
#include "connections/implementation/proto/offline_wire_formats.pb.h"
#include "core/internal/mediums/webrtc_peer_id.h"
#include "core/internal/mediums/webrtc_socket_stub.h"
#include "platform/base/cancellation_flag.h"
#include "platform/base/listeners.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);
// 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);
// Try to stop (accepting) the specific connection with provided service id.
// Runs on @MainThread
void StopAcceptingConnections(const std::string& service_id);
// 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);
};
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_STUB_H_
-406
View File
@@ -1,406 +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 "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "core/internal/mediums/webrtc_socket.h"
#include "platform/base/listeners.h"
#include "platform/base/medium_environment.h"
#include "platform/public/mutex_lock.h"
namespace location {
namespace nearby {
namespace connections {
namespace mediums {
namespace {
using FeatureFlags = FeatureFlags::Flags;
constexpr FeatureFlags kTestCases[] = {
FeatureFlags{
.enable_cancellation_flag = true,
},
FeatureFlags{
.enable_cancellation_flag = false,
},
};
class WebRtcTest : public ::testing::TestWithParam<FeatureFlags> {
protected:
using MockAcceptedCallback =
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
WebRtcTest() { env_.Stop(); }
MediumEnvironment& env_{MediumEnvironment::Instance()};
};
// Tests the flow when the two devices exchange SDP messages and connect to each
// other but the signaling channel is closed before sending the data.
TEST_P(WebRtcTest, ConnectBothDevices_ShutdownSignaling_SendData) {
env_.Start({.webrtc_enabled = true});
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
WebRtc receiver, sender;
WebRtcSocketWrapper receiver_socket, sender_socket;
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message xyz");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
}});
CancellationFlag flag;
sender_socket = sender.Connect(service_id, self_id, location_hint, &flag);
EXPECT_TRUE(sender_socket.IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
// Only shuts down signaling channel.
receiver.StopAcceptingConnections(service_id);
sender_socket.GetOutputStream().Write(message);
ExceptionOr<ByteArray> received_msg =
receiver_socket.GetInputStream().Read(/*size=*/32);
ASSERT_TRUE(received_msg.ok());
EXPECT_EQ(message, received_msg.result());
env_.Stop();
}
TEST_P(WebRtcTest, CanCancelConnect) {
env_.Start({.webrtc_enabled = true});
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
WebRtc receiver, sender;
WebRtcSocketWrapper receiver_socket, sender_socket;
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
}});
CancellationFlag flag(true);
sender_socket = sender.Connect(service_id, self_id, location_hint, &flag);
// If FeatureFlag is disabled, Cancelled is false as no-op.
if (!feature_flags.enable_cancellation_flag) {
EXPECT_TRUE(sender_socket.IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
sender_socket.GetOutputStream().Write(message);
ExceptionOr<ByteArray> received_msg =
receiver_socket.GetInputStream().Read(/*size=*/32);
ASSERT_TRUE(received_msg.ok());
EXPECT_EQ(message, received_msg.result());
receiver_socket.Close();
} else {
EXPECT_FALSE(sender_socket.IsValid());
}
env_.Stop();
}
INSTANTIATE_TEST_SUITE_P(ParametrisedWebRtcTest, WebRtcTest,
::testing::ValuesIn(kTestCases));
// Basic test to check that device is accepting connections when initialized.
TEST_F(WebRtcTest, NotAcceptingConnections) {
env_.Start({.webrtc_enabled = true});
WebRtc webrtc;
ASSERT_TRUE(webrtc.IsAvailable());
EXPECT_FALSE(webrtc.IsAcceptingConnections(std::string{}));
env_.Stop();
}
// Tests the flow when the device tries to accept connections twice. In this
// case, only the first call is successful and subsequent calls fail.
TEST_F(WebRtcTest, StartAcceptingConnectionTwice) {
env_.Start({.webrtc_enabled = true});
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
WebRtc webrtc;
WebrtcPeerId self_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint{};
ASSERT_TRUE(webrtc.IsAvailable());
ASSERT_TRUE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
{mock_accepted_callback_.AsStdFunction()}));
EXPECT_FALSE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
{mock_accepted_callback_.AsStdFunction()}));
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
EXPECT_FALSE(webrtc.IsAcceptingConnections(std::string{}));
env_.Stop();
}
// Tests the flow when the device tries to connect but there is no peer
// accepting connections at the given peer ID.
TEST_F(WebRtcTest, Connect_NoPeer) {
env_.Start({.webrtc_enabled = true});
WebRtc webrtc;
WebrtcPeerId peer_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
CancellationFlag flag;
WebRtcSocketWrapper wrapper_1 =
webrtc.Connect(service_id, peer_id, location_hint, &flag);
EXPECT_FALSE(wrapper_1.IsValid());
EXPECT_TRUE(webrtc.StartAcceptingConnections(
service_id, peer_id, location_hint, AcceptedConnectionCallback()));
env_.Stop();
}
// Tests the flow when the device calls Connect() after calling
// StartAcceptingConnections() without StopAcceptingConnections().
TEST_F(WebRtcTest, StartAcceptingConnection_ThenConnect) {
env_.Start({.webrtc_enabled = true});
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
WebRtc webrtc;
WebrtcPeerId self_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
ASSERT_TRUE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
{mock_accepted_callback_.AsStdFunction()}));
CancellationFlag flag;
WebRtcSocketWrapper wrapper = webrtc.Connect(
service_id, WebrtcPeerId("random_peer_id"), location_hint, &flag);
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
EXPECT_FALSE(wrapper.IsValid());
EXPECT_FALSE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
{mock_accepted_callback_.AsStdFunction()}));
env_.Stop();
}
// Tests the flow when the device calls StartAcceptingConnections but the medium
// is closed before a peer device can connect to it.
TEST_F(WebRtcTest, StartAndStopAcceptingConnections) {
env_.Start({.webrtc_enabled = true});
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
WebRtc webrtc;
WebrtcPeerId self_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
ASSERT_TRUE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
{mock_accepted_callback_.AsStdFunction()}));
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
webrtc.StopAcceptingConnections(service_id);
EXPECT_FALSE(webrtc.IsAcceptingConnections(service_id));
env_.Stop();
}
// Tests the flow when the device tries to connect to two different peers
// without disconnecting in between.
TEST_F(WebRtcTest, ConnectTwice) {
env_.Start({.webrtc_enabled = true});
WebRtc receiver, sender, device_c;
WebRtcSocketWrapper receiver_socket, sender_socket;
const WebrtcPeerId self_id("self_id"), other_id("other_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message xyz");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
}});
device_c.StartAcceptingConnections(service_id, other_id, location_hint,
{[](WebRtcSocketWrapper wrapper) {}});
CancellationFlag flag;
sender_socket = sender.Connect(service_id, self_id, location_hint, &flag);
EXPECT_TRUE(sender_socket.IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
WebRtcSocketWrapper socket =
sender.Connect(service_id, other_id, location_hint, &flag);
EXPECT_TRUE(socket.IsValid());
socket.Close();
EXPECT_TRUE(receiver_socket.IsValid());
EXPECT_TRUE(sender_socket.IsValid());
sender_socket.GetOutputStream().Write(message);
ExceptionOr<ByteArray> received_msg =
receiver_socket.GetInputStream().Read(/*size=*/32);
ASSERT_TRUE(received_msg.ok());
EXPECT_EQ(message, received_msg.result());
receiver_socket.Close();
env_.Stop();
}
// Tests the flow when the two devices exchange SDP messages and connect to each
// other but disconnect before being able to send/receive the actual data.
TEST_F(WebRtcTest, ConnectBothDevicesAndAbort) {
env_.Start({.webrtc_enabled = true});
WebRtc receiver, sender;
WebRtcSocketWrapper receiver_socket, sender_socket;
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message xyz");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
}});
CancellationFlag flag;
sender_socket = sender.Connect(service_id, self_id, location_hint, &flag);
EXPECT_TRUE(sender_socket.IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
receiver_socket.Close();
env_.Stop();
}
// Tests the flow when the two devices exchange SDP messages and connect to each
// other and the actual data is exchanged successfully between the devices.
TEST_F(WebRtcTest, ConnectBothDevicesAndSendData) {
env_.Start({.webrtc_enabled = true});
WebRtc receiver, sender;
WebRtcSocketWrapper receiver_socket, sender_socket;
const WebrtcPeerId self_id("self_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
Future<bool> connected;
ByteArray message("message");
receiver.StartAcceptingConnections(
service_id, self_id, location_hint,
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
receiver_socket = wrapper;
connected.Set(receiver_socket.IsValid());
}});
CancellationFlag flag;
sender_socket = sender.Connect(service_id, self_id, location_hint, &flag);
EXPECT_TRUE(sender_socket.IsValid());
ExceptionOr<bool> devices_connected = connected.Get();
ASSERT_TRUE(devices_connected.ok());
EXPECT_TRUE(devices_connected.result());
sender_socket.GetOutputStream().Write(message);
ExceptionOr<ByteArray> received_msg =
receiver_socket.GetInputStream().Read(/*size=*/32);
ASSERT_TRUE(received_msg.ok());
EXPECT_EQ(message, received_msg.result());
receiver_socket.Close();
env_.Stop();
}
TEST_F(WebRtcTest, Connect_NullPeerConnection) {
env_.Start({.webrtc_enabled = true});
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
env_.SetUseValidPeerConnection(
/*use_valid_peer_connection=*/false);
WebRtc webrtc;
const std::string service_id("NearbySharing");
WebrtcPeerId self_id("peer_id");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
CancellationFlag flag;
WebRtcSocketWrapper wrapper = webrtc.Connect(
service_id, WebrtcPeerId("random_peer_id"), location_hint, &flag);
EXPECT_FALSE(wrapper.IsValid());
env_.Stop();
}
// Tests the flow when the device calls StartAcceptingConnections and the
// receive messages stream fails.
TEST_F(WebRtcTest, ContinueAcceptingConnectionsOnComplete) {
env_.Start({.webrtc_enabled = true});
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
WebRtc webrtc;
WebrtcPeerId self_id("peer_id");
const std::string service_id("NearbySharing");
LocationHint location_hint;
ASSERT_TRUE(webrtc.IsAvailable());
ASSERT_TRUE(webrtc.StartAcceptingConnections(
service_id, self_id, location_hint,
{mock_accepted_callback_.AsStdFunction()}));
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
// Simulate a failure in receiving messages stream, WebRtc should restart
// accepting connections.
env_.SendWebRtcSignalingComplete(self_id.GetId(),
/*success=*/false);
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
// And a "success" message should not cause accepting connections to stop.
env_.SendWebRtcSignalingComplete(self_id.GetId(),
/*success=*/true);
EXPECT_TRUE(webrtc.IsAcceptingConnections(service_id));
webrtc.StopAcceptingConnections(service_id);
EXPECT_FALSE(webrtc.IsAcceptingConnections(service_id));
env_.Stop();
}
} // namespace
} // namespace mediums
} // namespace connections
} // namespace nearby
} // namespace location
-429
View File
@@ -1,429 +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/wifi_lan.h"
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/str_format.h"
#include "core/internal/mediums/utils.h"
#include "platform/public/logging.h"
#include "platform/public/mutex_lock.h"
namespace location {
namespace nearby {
namespace connections {
WifiLan::~WifiLan() {
// Destructor is not taking locks, but methods it is calling are.
while (!discovering_info_.service_ids.empty()) {
StopDiscovery(*discovering_info_.service_ids.begin());
}
while (!server_sockets_.empty()) {
StopAcceptingConnections(server_sockets_.begin()->first);
}
while (!advertising_info_.nsd_service_infos.empty()) {
StopAdvertising(advertising_info_.nsd_service_infos.begin()->first);
}
// 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 WifiLan::IsAvailable() const {
MutexLock lock(&mutex_);
return IsAvailableLocked();
}
bool WifiLan::IsAvailableLocked() const { return medium_.IsValid(); }
bool WifiLan::StartAdvertising(const std::string& service_id,
NsdServiceInfo& nsd_service_info) {
MutexLock lock(&mutex_);
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO)
<< "Can't turn on WifiLan advertising. WifiLan is not available.";
return false;
}
if (!nsd_service_info.IsValid()) {
NEARBY_LOGS(INFO)
<< "Refusing to turn on WifiLan advertising. nsd_service_info is not "
"valid.";
return false;
}
if (IsAdvertisingLocked(service_id)) {
NEARBY_LOGS(INFO)
<< "Failed to WifiLan advertise because we're already advertising.";
return false;
}
if (!IsAcceptingConnectionsLocked(service_id)) {
NEARBY_LOGS(INFO)
<< "Failed to turn on WifiLan advertising with nsd_service_info="
<< &nsd_service_info
<< ", service_name=" << nsd_service_info.GetServiceName()
<< ", service_id=" << service_id
<< ". Should accept connections before advertising.";
return false;
}
nsd_service_info.SetServiceType(GenerateServiceType(service_id));
const auto& it = server_sockets_.find(service_id);
if (it != server_sockets_.end()) {
nsd_service_info.SetIPAddress(it->second.GetIPAddress());
nsd_service_info.SetPort(it->second.GetPort());
}
if (!medium_.StartAdvertising(nsd_service_info)) {
NEARBY_LOGS(INFO)
<< "Failed to turn on WifiLan advertising with nsd_service_info="
<< &nsd_service_info
<< ", service_name=" << nsd_service_info.GetServiceName()
<< ", service_id=" << service_id;
return false;
}
NEARBY_LOGS(INFO) << "Turned on WifiLan advertising with nsd_service_info="
<< &nsd_service_info
<< ", service_name=" << nsd_service_info.GetServiceName()
<< ", service_id=" << service_id;
advertising_info_.Add(service_id, std::move(nsd_service_info));
return true;
}
bool WifiLan::StopAdvertising(const std::string& service_id) {
MutexLock lock(&mutex_);
if (!IsAdvertisingLocked(service_id)) {
NEARBY_LOGS(INFO)
<< "Can't turn off WifiLan advertising; it is already off";
return false;
}
NEARBY_LOGS(INFO) << "Turned off WifiLan advertising with service_id="
<< service_id;
bool ret =
medium_.StopAdvertising(*advertising_info_.GetServiceInfo(service_id));
// Reset our bundle of advertising state to mark that we're no longer
// advertising for specific service_id.
advertising_info_.Remove(service_id);
return ret;
}
bool WifiLan::IsAdvertising(const std::string& service_id) {
MutexLock lock(&mutex_);
return IsAdvertisingLocked(service_id);
}
bool WifiLan::IsAdvertisingLocked(const std::string& service_id) {
return advertising_info_.Existed(service_id);
}
bool WifiLan::StartDiscovery(const std::string& service_id,
DiscoveredServiceCallback callback) {
MutexLock lock(&mutex_);
if (service_id.empty()) {
NEARBY_LOGS(INFO)
<< "Refusing to start WifiLan discovering with empty service_id.";
return false;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO)
<< "Can't discover WifiLan services because WifiLan isn't available.";
return false;
}
if (IsDiscoveringLocked(service_id)) {
NEARBY_LOGS(INFO)
<< "Refusing to start discovery of WifiLan services because another "
"discovery is already in-progress.";
return false;
}
std::string service_type = GenerateServiceType(service_id);
bool ret = medium_.StartDiscovery(service_id, service_type, callback);
if (!ret) {
NEARBY_LOGS(INFO) << "Failed to start discovery of WifiLan services.";
return false;
}
NEARBY_LOGS(INFO) << "Turned on WifiLan discovering with service_id="
<< service_id;
// Mark the fact that we're currently performing a WifiLan discovering.
discovering_info_.Add(service_id);
return true;
}
bool WifiLan::StopDiscovery(const std::string& service_id) {
MutexLock lock(&mutex_);
if (!IsDiscoveringLocked(service_id)) {
NEARBY_LOGS(INFO)
<< "Can't turn off WifiLan discovering because we never started "
"discovering.";
return false;
}
std::string service_type = GenerateServiceType(service_id);
NEARBY_LOGS(INFO) << "Turned off WifiLan discovering with service_id="
<< service_id << ", service_type=" << service_type;
bool ret = medium_.StopDiscovery(service_type);
discovering_info_.Remove(service_id);
return ret;
}
bool WifiLan::IsDiscovering(const std::string& service_id) {
MutexLock lock(&mutex_);
return IsDiscoveringLocked(service_id);
}
bool WifiLan::IsDiscoveringLocked(const std::string& service_id) {
return discovering_info_.Existed(service_id);
}
bool WifiLan::StartAcceptingConnections(const std::string& service_id,
AcceptedConnectionCallback callback) {
MutexLock lock(&mutex_);
if (service_id.empty()) {
NEARBY_LOGS(INFO) << "Refusing to start accepting WifiLan connections; "
"service_id is empty.";
return false;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO)
<< "Can't start accepting WifiLan connections [service_id="
<< service_id << "]; WifiLan not available.";
return false;
}
if (IsAcceptingConnectionsLocked(service_id)) {
NEARBY_LOGS(INFO)
<< "Refusing to start accepting WifiLan connections [service="
<< service_id
<< "]; WifiLan server is already in-progress with the same name.";
return false;
}
auto port_range = medium_.GetDynamicPortRange();
// Generate an exact port here on server socket; if platform doesn't provide
// range of port then assign 0 to let platform decide it.
int port = 0;
if (port_range.has_value() &&
(port_range->first > 0 && port_range->first <= 65535 &&
port_range->second > 0 && port_range->second <= 65535 &&
port_range->first <= port_range->second)) {
port = GeneratePort(service_id, port_range.value());
}
WifiLanServerSocket server_socket = medium_.ListenForService(port);
if (!server_socket.IsValid()) {
NEARBY_LOGS(INFO)
<< "Failed to start accepting WifiLan connections for service_id="
<< service_id;
return false;
}
// Mark the fact that there's an in-progress WifiLan server accepting
// connections.
auto owned_server_socket =
server_sockets_.insert({service_id, std::move(server_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(
"wifi-lan-accept",
[callback = std::move(callback),
server_socket = std::move(owned_server_socket)]() mutable {
while (true) {
WifiLanSocket client_socket = server_socket.Accept();
if (!client_socket.IsValid()) {
server_socket.Close();
break;
}
callback.accepted_cb(std::move(client_socket));
}
});
return true;
}
bool WifiLan::StopAcceptingConnections(const std::string& service_id) {
MutexLock lock(&mutex_);
if (service_id.empty()) {
NEARBY_LOGS(INFO) << "Unable to stop accepting WifiLan connections because "
"the service_id is empty.";
return false;
}
const auto& it = server_sockets_.find(service_id);
if (it == server_sockets_.end()) {
NEARBY_LOGS(INFO) << "Can't stop accepting WifiLan connections for "
<< service_id << " because it was never started.";
return false;
}
// Closing the WifiLanServerSocket will kick off the suicide of the thread
// in accept_loops_thread_pool_ that blocks on WifiLanServerSocket.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 WifiLanServerSocket, 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.
WifiLanServerSocket& listening_socket = item.mapped();
// Regardless of whether or not we fail to close the existing
// WifiLanServerSocket, remove it from server_sockets_ so that it
// frees up this service for another round.
// Finally, close the WifiLanServerSocket.
if (!listening_socket.Close().Ok()) {
NEARBY_LOGS(INFO) << "Failed to close WifiLan server socket for service_id="
<< service_id;
return false;
}
return true;
}
bool WifiLan::IsAcceptingConnections(const std::string& service_id) {
MutexLock lock(&mutex_);
return IsAcceptingConnectionsLocked(service_id);
}
bool WifiLan::IsAcceptingConnectionsLocked(const std::string& service_id) {
return server_sockets_.find(service_id) != server_sockets_.end();
}
WifiLanSocket WifiLan::Connect(const std::string& service_id,
const NsdServiceInfo& service_info,
CancellationFlag* cancellation_flag) {
MutexLock lock(&mutex_);
// Socket to return. To allow for NRVO to work, it has to be a single object.
WifiLanSocket socket;
if (service_id.empty()) {
NEARBY_LOGS(INFO) << "Refusing to create client WifiLan socket because "
"service_id is empty.";
return socket;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO) << "Can't create client WifiLan socket [service_id="
<< service_id << "]; WifiLan isn't available.";
return socket;
}
if (cancellation_flag->Cancelled()) {
NEARBY_LOGS(INFO) << "Can't create client WifiLan socket due to cancel.";
return socket;
}
socket = medium_.ConnectToService(service_info, cancellation_flag);
if (!socket.IsValid()) {
NEARBY_LOGS(INFO) << "Failed to Connect via WifiLan [service_id="
<< service_id << "]";
}
return socket;
}
WifiLanSocket WifiLan::Connect(const std::string& service_id,
const std::string& ip_address, int port,
CancellationFlag* cancellation_flag) {
MutexLock lock(&mutex_);
// Socket to return. To allow for NRVO to work, it has to be a single object.
WifiLanSocket socket;
if (service_id.empty()) {
NEARBY_LOGS(INFO) << "Refusing to create client WifiLan socket because "
"service_id is empty.";
return socket;
}
if (!IsAvailableLocked()) {
NEARBY_LOGS(INFO) << "Can't create client WifiLan socket [service_id="
<< service_id << "]; WifiLan isn't available.";
return socket;
}
if (cancellation_flag->Cancelled()) {
NEARBY_LOGS(INFO) << "Can't create client WifiLan socket due to cancel.";
return socket;
}
socket = medium_.ConnectToService(ip_address, port, cancellation_flag);
if (!socket.IsValid()) {
NEARBY_LOGS(INFO) << "Failed to Connect via WifiLan [service_id="
<< service_id << "]";
}
return socket;
}
std::pair<std::string, int> WifiLan::GetCredentials(
const std::string& service_id) {
MutexLock lock(&mutex_);
const auto& it = server_sockets_.find(service_id);
if (it == server_sockets_.end()) {
return std::pair<std::string, int>();
}
return std::pair<std::string, int>(it->second.GetIPAddress(),
it->second.GetPort());
}
std::string WifiLan::GenerateServiceType(const std::string& service_id) {
std::string service_id_hash_string;
const ByteArray service_id_hash = Utils::Sha256Hash(
service_id, NsdServiceInfo::kTypeFromServiceIdHashLength);
for (auto byte : std::string(service_id_hash)) {
absl::StrAppend(&service_id_hash_string, absl::StrFormat("%02X", byte));
}
return absl::StrFormat(NsdServiceInfo::kNsdTypeFormat,
service_id_hash_string);
}
int WifiLan::GeneratePort(const std::string& service_id,
std::pair<std::int32_t, std::int32_t> port_range) {
const std::string service_id_hash =
std::string(Utils::Sha256Hash(service_id, 4));
std::uint32_t uint_of_service_id_hash =
service_id_hash[0] << 24 | service_id_hash[1] << 16 |
service_id_hash[2] << 8 | service_id_hash[3];
return port_range.first +
(uint_of_service_id_hash % (port_range.second - port_range.first));
}
} // namespace connections
} // namespace nearby
} // namespace location
-198
View File
@@ -1,198 +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_WIFI_LAN_H_
#define CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_
#include <cstdint>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "platform/base/byte_array.h"
#include "platform/base/cancellation_flag.h"
#include "platform/base/nsd_service_info.h"
#include "platform/public/multi_thread_executor.h"
#include "platform/public/mutex.h"
#include "platform/public/wifi_lan.h"
namespace location {
namespace nearby {
namespace connections {
class WifiLan {
public:
using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback;
// Callback that is invoked when a new connection is accepted.
struct AcceptedConnectionCallback {
std::function<void(WifiLanSocket socket)> accepted_cb =
DefaultCallback<WifiLanSocket>();
};
WifiLan() = default;
~WifiLan();
// Returns true, if WifiLan communications are supported by a platform.
bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);
// Sets custom service info name, endpoint info name in NsdServiceInfo and
// then enables WifiLan advertising.
// Returns true, if NsdServiceInfo is successfully set, and false otherwise.
bool StartAdvertising(const std::string& service_id,
NsdServiceInfo& nsd_service_info)
ABSL_LOCKS_EXCLUDED(mutex_);
// Disables WifiLan advertising.
// Returns false if no successful call StartAdvertising() was previously
// made, otherwise returns true.
bool StopAdvertising(const std::string& service_id)
ABSL_LOCKS_EXCLUDED(mutex_);
bool IsAdvertising(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
// Enables WifiLan discovery. Will report any discoverable services
// through a callback.
// Returns true, if discovery was enabled, false otherwise.
bool StartDiscovery(const std::string& service_id,
DiscoveredServiceCallback callback)
ABSL_LOCKS_EXCLUDED(mutex_);
// Disables WifiLan discovery.
bool StopDiscovery(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
bool IsDiscovering(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
// Starts a worker thread, creates a WifiLan 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_);
// Establishes connection to WifiLan service that was might be started on
// another service with StartAcceptingConnections() using the same service_id.
// Blocks until connection is established, or server-side is terminated.
// Returns socket instance. On success, WifiLanSocket.IsValid() return true.
WifiLanSocket Connect(const std::string& service_id,
const NsdServiceInfo& service_info,
CancellationFlag* cancellation_flag)
ABSL_LOCKS_EXCLUDED(mutex_);
// Establishes connection to WifiLan service by ip address and port for
// bandwidth upgradation.
// Returns socket instance. On success, WifiLanSocket.IsValid() return true.
WifiLanSocket Connect(const std::string& service_id,
const std::string& ip_address, int port,
CancellationFlag* cancellation_flag)
ABSL_LOCKS_EXCLUDED(mutex_);
// Gets ip address + port for remote services on the network to identify and
// connect to this service.
//
// Credential is for the currently-hosted Wifi ServerSocket (if any).
std::pair<std::string, int> GetCredentials(const std::string& service_id)
ABSL_LOCKS_EXCLUDED(mutex_);
private:
struct AdvertisingInfo {
bool Empty() const { return nsd_service_infos.empty(); }
void Clear() { nsd_service_infos.clear(); }
void Add(const std::string& service_id,
const NsdServiceInfo& nsd_service_info) {
nsd_service_infos.insert({service_id, nsd_service_info});
}
void Remove(const std::string& service_id) {
nsd_service_infos.erase(service_id);
}
bool Existed(const std::string& service_id) const {
return nsd_service_infos.contains(service_id);
}
NsdServiceInfo* GetServiceInfo(const std::string& service_id) {
const auto& it = nsd_service_infos.find(service_id);
if (it == nsd_service_infos.end()) {
return nullptr;
}
return &it->second;
}
absl::flat_hash_map<std::string, NsdServiceInfo> nsd_service_infos;
};
struct DiscoveringInfo {
bool Empty() const { return service_ids.empty(); }
void Clear() { service_ids.clear(); }
void Add(const std::string& service_id) { service_ids.insert(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 kMaxConcurrentAcceptLoops = 5;
// 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 IsDiscoveringLocked(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_);
// Generates mDNS type.
std::string GenerateServiceType(const std::string& service_id);
// Generates port number based on port_range_.
int GeneratePort(const std::string& service_id,
std::pair<std::int32_t, std::int32_t> port_range);
mutable Mutex mutex_;
WifiLanMedium medium_ ABSL_GUARDED_BY(mutex_);
AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_);
DiscoveringInfo discovering_info_ 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_id -> ServerSocket. If map is non-empty, we
// are currently listening for incoming connections.
// WifiLanServerSocket instances are used from accept_loops_runner_,
// and thus require pointer stability.
absl::flat_hash_map<std::string, WifiLanServerSocket> server_sockets_
ABSL_GUARDED_BY(mutex_);
};
} // namespace connections
} // namespace nearby
} // namespace location
#endif // CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_
-348
View File
@@ -1,348 +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/wifi_lan.h"
#include <string>
#include "gmock/gmock.h"
#include "protobuf-matchers/protocol-buffer-matchers.h"
#include "gtest/gtest.h"
#include "absl/strings/string_view.h"
#include "platform/base/medium_environment.h"
#include "platform/base/nsd_service_info.h"
#include "platform/public/count_down_latch.h"
#include "platform/public/logging.h"
#include "platform/public/wifi_lan.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 kServiceInfoName{"ServiceInfoName"};
constexpr absl::string_view kEndpointName{"EndpointName"};
constexpr absl::string_view kEndpointInfoKey{"n"};
class WifiLanTest : public ::testing::TestWithParam<FeatureFlags> {
protected:
using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback;
WifiLanTest() { env_.Stop(); }
MediumEnvironment& env_{MediumEnvironment::Instance()};
};
TEST_P(WifiLanTest, CanConnect) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
WifiLan wifi_lan_client;
WifiLan wifi_lan_server;
std::string service_id(kServiceID);
std::string service_info_name(kServiceInfoName);
std::string endpoint_info_name(kEndpointName);
CountDownLatch discovered_latch(1);
CountDownLatch accept_latch(1);
WifiLanSocket socket_for_server;
EXPECT_TRUE(wifi_lan_server.StartAcceptingConnections(
service_id,
{
.accepted_cb =
[&socket_for_server, &accept_latch](WifiLanSocket socket) {
socket_for_server = std::move(socket);
accept_latch.CountDown();
},
}));
NsdServiceInfo nsd_service_info;
nsd_service_info.SetServiceName(service_info_name);
nsd_service_info.SetTxtRecord(std::string(kEndpointInfoKey),
endpoint_info_name);
wifi_lan_server.StartAdvertising(service_id, nsd_service_info);
NsdServiceInfo discovered_service_info;
wifi_lan_client.StartDiscovery(
service_id,
{
.service_discovered_cb =
[&discovered_latch, &discovered_service_info](
NsdServiceInfo service_info, const std::string& service_id) {
NEARBY_LOGS(INFO)
<< "Discovered service_info=" << &service_info;
discovered_service_info = service_info;
discovered_latch.CountDown();
},
});
discovered_latch.Await(kWaitDuration).result();
ASSERT_TRUE(discovered_service_info.IsValid());
CancellationFlag flag;
WifiLanSocket socket_for_client =
wifi_lan_client.Connect(service_id, discovered_service_info, &flag);
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
EXPECT_TRUE(wifi_lan_server.StopAcceptingConnections(service_id));
EXPECT_TRUE(wifi_lan_server.StopAdvertising(service_id));
EXPECT_TRUE(socket_for_server.IsValid());
EXPECT_TRUE(socket_for_client.IsValid());
env_.Stop();
}
TEST_P(WifiLanTest, CanCancelConnect) {
FeatureFlags feature_flags = GetParam();
env_.SetFeatureFlags(feature_flags);
env_.Start();
WifiLan wifi_lan_client;
WifiLan wifi_lan_server;
std::string service_id(kServiceID);
std::string service_info_name(kServiceInfoName);
std::string endpoint_info_name(kEndpointName);
CountDownLatch discovered_latch(1);
CountDownLatch accept_latch(1);
WifiLanSocket socket_for_server;
EXPECT_TRUE(wifi_lan_server.StartAcceptingConnections(
service_id,
{
.accepted_cb =
[&socket_for_server, &accept_latch](WifiLanSocket socket) {
socket_for_server = std::move(socket);
accept_latch.CountDown();
},
}));
NsdServiceInfo nsd_service_info;
nsd_service_info.SetServiceName(service_info_name);
nsd_service_info.SetTxtRecord(std::string(kEndpointInfoKey),
endpoint_info_name);
wifi_lan_server.StartAdvertising(service_id, nsd_service_info);
NsdServiceInfo discovered_service_info;
wifi_lan_client.StartDiscovery(
service_id,
{
.service_discovered_cb =
[&discovered_latch, &discovered_service_info](
NsdServiceInfo service_info, const std::string& service_id) {
NEARBY_LOGS(INFO)
<< "Discovered service_info=" << &service_info;
discovered_service_info = service_info;
discovered_latch.CountDown();
},
});
EXPECT_TRUE(discovered_latch.Await(kWaitDuration).result());
ASSERT_TRUE(discovered_service_info.IsValid());
CancellationFlag flag(true);
WifiLanSocket socket_for_client =
wifi_lan_client.Connect(service_id, discovered_service_info, &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(wifi_lan_server.StopAcceptingConnections(service_id));
EXPECT_TRUE(wifi_lan_server.StopAdvertising(service_id));
EXPECT_TRUE(socket_for_server.IsValid());
EXPECT_TRUE(socket_for_client.IsValid());
} else {
EXPECT_FALSE(accept_latch.Await(kWaitDuration).result());
EXPECT_TRUE(wifi_lan_server.StopAcceptingConnections(service_id));
EXPECT_TRUE(wifi_lan_server.StopAdvertising(service_id));
EXPECT_FALSE(socket_for_server.IsValid());
EXPECT_FALSE(socket_for_client.IsValid());
}
env_.Stop();
}
INSTANTIATE_TEST_SUITE_P(ParametrisedWifiLanTest, WifiLanTest,
::testing::ValuesIn(kTestCases));
TEST_F(WifiLanTest, CanConstructValidObject) {
env_.Start();
WifiLan wifi_lan_a;
WifiLan wifi_lan_b;
std::string service_id(kServiceID);
EXPECT_TRUE(wifi_lan_a.IsAvailable());
EXPECT_TRUE(wifi_lan_b.IsAvailable());
env_.Stop();
}
TEST_F(WifiLanTest, CanStartAdvertising) {
env_.Start();
WifiLan wifi_lan_a;
std::string service_id(kServiceID);
std::string service_info_name(kServiceInfoName);
std::string endpoint_info_name(kEndpointName);
EXPECT_TRUE(wifi_lan_a.StartAcceptingConnections(service_id, {}));
NsdServiceInfo nsd_service_info;
nsd_service_info.SetServiceName(service_info_name);
nsd_service_info.SetTxtRecord(std::string(kEndpointInfoKey),
endpoint_info_name);
EXPECT_TRUE(wifi_lan_a.StartAdvertising(service_id, nsd_service_info));
EXPECT_TRUE(wifi_lan_a.StopAdvertising(service_id));
env_.Stop();
}
TEST_F(WifiLanTest, CanStartMultipleAdvertising) {
env_.Start();
WifiLan wifi_lan_a;
std::string service_id_1(kServiceID);
std::string service_id_2("com.google.location.nearby.apps.test_1");
std::string service_info_name_1(kServiceInfoName);
std::string service_info_name_2("ServiceInfoName_1");
std::string endpoint_info_name(kEndpointName);
EXPECT_TRUE(wifi_lan_a.StartAcceptingConnections(service_id_1, {}));
EXPECT_TRUE(wifi_lan_a.StartAcceptingConnections(service_id_2, {}));
NsdServiceInfo nsd_service_info_1;
nsd_service_info_1.SetServiceName(service_info_name_1);
nsd_service_info_1.SetTxtRecord(std::string(kEndpointInfoKey),
endpoint_info_name);
NsdServiceInfo nsd_service_info_2;
nsd_service_info_2.SetServiceName(service_info_name_2);
nsd_service_info_2.SetTxtRecord(std::string(kEndpointInfoKey),
endpoint_info_name);
EXPECT_TRUE(wifi_lan_a.StartAdvertising(service_id_1, nsd_service_info_1));
EXPECT_TRUE(wifi_lan_a.StartAdvertising(service_id_2, nsd_service_info_2));
EXPECT_TRUE(wifi_lan_a.StopAdvertising(service_id_1));
EXPECT_TRUE(wifi_lan_a.StopAdvertising(service_id_2));
EXPECT_TRUE(wifi_lan_a.StopAcceptingConnections(service_id_1));
EXPECT_TRUE(wifi_lan_a.StopAcceptingConnections(service_id_2));
env_.Stop();
}
TEST_F(WifiLanTest, CanStartDiscovery) {
env_.Start();
WifiLan wifi_lan_a;
std::string service_id(kServiceID);
EXPECT_TRUE(
wifi_lan_a.StartDiscovery(service_id, DiscoveredServiceCallback{}));
EXPECT_TRUE(wifi_lan_a.StopDiscovery(service_id));
env_.Stop();
}
TEST_F(WifiLanTest, CanStartMultipleDiscovery) {
env_.Start();
WifiLan wifi_lan_a;
std::string service_id_1(kServiceID);
std::string service_id_2("com.google.location.nearby.apps.test_1");
EXPECT_TRUE(
wifi_lan_a.StartDiscovery(service_id_1, DiscoveredServiceCallback{}));
EXPECT_TRUE(
wifi_lan_a.StartDiscovery(service_id_2, DiscoveredServiceCallback{}));
EXPECT_TRUE(wifi_lan_a.StopDiscovery(service_id_1));
EXPECT_TRUE(wifi_lan_a.StopDiscovery(service_id_2));
env_.Stop();
}
TEST_F(WifiLanTest, CanAdvertiseThatOtherMediumDiscover) {
env_.Start();
WifiLan wifi_lan_a;
WifiLan wifi_lan_b;
std::string service_id(kServiceID);
std::string service_info_name(kServiceInfoName);
std::string endpoint_info_name(kEndpointName);
CountDownLatch discovered_latch(1);
CountDownLatch lost_latch(1);
wifi_lan_b.StartDiscovery(
service_id, DiscoveredServiceCallback{
.service_discovered_cb =
[&discovered_latch](NsdServiceInfo service_info,
const std::string& service_id) {
discovered_latch.CountDown();
},
.service_lost_cb =
[&lost_latch](NsdServiceInfo service_info,
const std::string& service_id) {
lost_latch.CountDown();
},
});
EXPECT_TRUE(wifi_lan_a.StartAcceptingConnections(service_id, {}));
NsdServiceInfo nsd_service_info;
nsd_service_info.SetServiceName(service_info_name);
nsd_service_info.SetTxtRecord(std::string(kEndpointInfoKey),
endpoint_info_name);
EXPECT_TRUE(wifi_lan_a.StartAdvertising(service_id, nsd_service_info));
EXPECT_TRUE(discovered_latch.Await(kWaitDuration).result());
EXPECT_TRUE(wifi_lan_a.StopAdvertising(service_id));
EXPECT_TRUE(lost_latch.Await(kWaitDuration).result());
EXPECT_TRUE(wifi_lan_b.StopDiscovery(service_id));
env_.Stop();
}
TEST_F(WifiLanTest, CanDiscoverThatOtherMediumAdvertise) {
env_.Start();
WifiLan wifi_lan_a;
WifiLan wifi_lan_b;
std::string service_id(kServiceID);
std::string service_info_name(kServiceInfoName);
std::string endpoint_info_name(kEndpointName);
CountDownLatch discovered_latch(1);
CountDownLatch lost_latch(1);
EXPECT_TRUE(wifi_lan_b.StartAcceptingConnections(service_id, {}));
NsdServiceInfo nsd_service_info;
nsd_service_info.SetServiceName(service_info_name);
nsd_service_info.SetTxtRecord(std::string(kEndpointInfoKey),
endpoint_info_name);
wifi_lan_b.StartAdvertising(service_id, nsd_service_info);
EXPECT_TRUE(wifi_lan_a.StartDiscovery(
service_id, DiscoveredServiceCallback{
.service_discovered_cb =
[&discovered_latch](NsdServiceInfo service_info,
const std::string& service_id) {
discovered_latch.CountDown();
},
.service_lost_cb =
[&lost_latch](NsdServiceInfo service_info,
const std::string& service_id) {
lost_latch.CountDown();
},
}));
EXPECT_TRUE(discovered_latch.Await(kWaitDuration).result());
EXPECT_TRUE(wifi_lan_b.StopAdvertising(service_id));
EXPECT_TRUE(lost_latch.Await(kWaitDuration).result());
EXPECT_TRUE(wifi_lan_a.StopDiscovery(service_id));
env_.Stop();
}
} // namespace
} // namespace connections
} // namespace nearby
} // namespace location