mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Roll forward to cl/338482889
Signed-off-by: Alexey Polyudov <apolyudov@google.com> Change-Id: I9850950db8bd84f0904ea1a413151887f52098cf
This commit is contained in:
+64
-105
@@ -1,134 +1,93 @@
|
||||
cc_library(
|
||||
name = "utils",
|
||||
srcs = [
|
||||
"utils.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"utils.h",
|
||||
],
|
||||
visibility = [
|
||||
"//core/internal/mediums/webrtc:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//platform:types",
|
||||
"//platform:utils",
|
||||
"//platform/api",
|
||||
"//platform/port:string",
|
||||
"//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "mediums",
|
||||
srcs = [
|
||||
"ble_advertisement.cc",
|
||||
"ble_advertisement_header.cc",
|
||||
"ble_packet.cc",
|
||||
"ble_peripheral.cc",
|
||||
"ble.cc",
|
||||
"bloom_filter.cc",
|
||||
"bluetooth_classic.cc",
|
||||
"bluetooth_radio.cc",
|
||||
"mediums.cc",
|
||||
"uuid.cc",
|
||||
"webrtc.cc",
|
||||
"wifi_lan.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"advertisement_read_result.cc",
|
||||
"advertisement_read_result.h",
|
||||
"ble.cc",
|
||||
"ble.h",
|
||||
"ble_advertisement.h",
|
||||
"ble_advertisement_header.h",
|
||||
"ble_packet.h",
|
||||
"ble_peripheral.h",
|
||||
"ble_v2.cc",
|
||||
"ble_v2.h",
|
||||
"bloom_filter.cc",
|
||||
"bloom_filter.h",
|
||||
"bluetooth_classic.cc",
|
||||
"bluetooth_classic.h",
|
||||
"bluetooth_radio.cc",
|
||||
"bluetooth_radio.h",
|
||||
"discovered_peripheral_callback.h",
|
||||
"discovered_peripheral_tracker.cc",
|
||||
"discovered_peripheral_tracker.h",
|
||||
"lost_entity_tracker.cc",
|
||||
"lost_entity_tracker.h",
|
||||
"mediums.cc",
|
||||
"mediums.h",
|
||||
"uuid.cc",
|
||||
"uuid.h",
|
||||
"wifi_lan.cc",
|
||||
"webrtc.h",
|
||||
"wifi_lan.h",
|
||||
],
|
||||
visibility = ["//core/internal:__pkg__"],
|
||||
visibility = [
|
||||
"//core/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
":utils",
|
||||
"//platform:logging",
|
||||
"//platform:types",
|
||||
"//platform:utils",
|
||||
"//platform/api",
|
||||
"//platform/port:string",
|
||||
"//core:core_types",
|
||||
"//core/internal/mediums/ble_v2",
|
||||
"//core/internal/mediums/webrtc",
|
||||
"//proto/connections:offline_wire_formats_portable_proto",
|
||||
"//platform/base",
|
||||
"//platform/public:comm",
|
||||
"//platform/public:logging",
|
||||
"//platform/public:types",
|
||||
"//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto",
|
||||
"//absl/container:flat_hash_map",
|
||||
"//absl/container:flat_hash_set",
|
||||
"//absl/numeric:int128",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
"//smhasher:libmurmur3",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
"//webrtc/api:scoped_refptr",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "utils",
|
||||
srcs = ["utils.cc"],
|
||||
hdrs = ["utils.h"],
|
||||
visibility = [
|
||||
"//core/internal:__pkg__",
|
||||
"//core/internal/mediums:__pkg__",
|
||||
"//core/internal/mediums/ble_v2:__pkg__",
|
||||
"//core/internal/mediums/webrtc:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//proto/connections:offline_wire_formats_portable_proto",
|
||||
"//platform/base",
|
||||
"//platform/public:types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "advertisement_read_result_test",
|
||||
srcs = ["advertisement_read_result_test.cc"],
|
||||
name = "core_internal_mediums_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"ble_test.cc",
|
||||
"bloom_filter_test.cc",
|
||||
"bluetooth_classic_test.cc",
|
||||
"bluetooth_radio_test.cc",
|
||||
"lost_entity_tracker_test.cc",
|
||||
"uuid_test.cc",
|
||||
"webrtc_test.cc",
|
||||
"wifi_lan_test.cc",
|
||||
],
|
||||
shard_count = 16,
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3",
|
||||
"//core/internal/mediums/webrtc",
|
||||
"//platform/base",
|
||||
"//platform/base:test_util",
|
||||
"//platform/impl/g3", # build_cleaner: keep
|
||||
"//platform/public:comm",
|
||||
"//platform/public:logging",
|
||||
"//platform/public:types",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ble_advertisement_header_test",
|
||||
srcs = ["ble_advertisement_header_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform:utils",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ble_advertisement_test",
|
||||
srcs = ["ble_advertisement_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "ble_packet_test",
|
||||
srcs = ["ble_packet_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "bloom_filter_test",
|
||||
srcs = ["bloom_filter_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "lost_entity_tracker_test",
|
||||
srcs = ["lost_entity_tracker_test.cc"],
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
#include "core/internal/mediums/advertisement_read_result.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "platform/synchronized.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename K, typename V>
|
||||
void eraseOwnedPtrFromMap(std::map<K, ConstPtr<V> >& m, const K& k) {
|
||||
typename std::map<K, ConstPtr<V> >::iterator it = m.find(k);
|
||||
if (it != m.end()) {
|
||||
it->second.destroy();
|
||||
m.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// How much to multiply the backoff duration by with every failure to read
|
||||
// from the advertisement GATT server. This should never be below 1!
|
||||
template <typename Platform>
|
||||
const float AdvertisementReadResult<Platform>::kAdvertisementBackoffMultiplier =
|
||||
2.0;
|
||||
|
||||
// The initial backoff duration when we fail to read from an advertisement
|
||||
// GATT server.
|
||||
template <typename Platform>
|
||||
const std::int64_t
|
||||
AdvertisementReadResult<Platform>::kAdvertisementBaseBackoffDurationMillis =
|
||||
1 * 1000; // 1 second
|
||||
|
||||
// The maximum backoff duration allowed between advertisement GATT server
|
||||
// reads.
|
||||
template <typename Platform>
|
||||
const std::int64_t
|
||||
AdvertisementReadResult<Platform>::kAdvertisementMaxBackoffDurationMillis =
|
||||
5 * 60 * 1000; // 5 minutes
|
||||
|
||||
template <typename Platform>
|
||||
AdvertisementReadResult<Platform>::AdvertisementReadResult()
|
||||
: lock_(Platform::createLock()),
|
||||
system_clock_(Platform::createSystemClock()),
|
||||
advertisements_(),
|
||||
backoff_duration_millis_(kAdvertisementBaseBackoffDurationMillis),
|
||||
// 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).
|
||||
last_read_timestamp_millis_(system_clock_->elapsedRealtime() -
|
||||
kAdvertisementMaxBackoffDurationMillis),
|
||||
result_(Result::Value::UNKNOWN) {}
|
||||
|
||||
template <typename Platform>
|
||||
AdvertisementReadResult<Platform>::~AdvertisementReadResult() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
for (AdvertisementMap::iterator it = advertisements_.begin();
|
||||
it != advertisements_.end(); ++it) {
|
||||
it->second.destroy();
|
||||
}
|
||||
advertisements_.clear();
|
||||
}
|
||||
|
||||
// Adds a successfully read advertisement for the specified slot to this read
|
||||
// result. This is fundamentally different from
|
||||
// {@link #recordLastReadStatus(boolean)} because we can report a read
|
||||
// failure, but still manage to read some advertisements.
|
||||
// Note: advertisement should be passed in as a RefCounted Ptr. It is not the
|
||||
// responsibility of AdvertisementReadResult to make it RefCounted.
|
||||
template <typename Platform>
|
||||
void AdvertisementReadResult<Platform>::addAdvertisement(
|
||||
std::int32_t slot, /* RefCounted */ ConstPtr<ByteArray> advertisement) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_advertisement(advertisement);
|
||||
|
||||
// Blindly remove from the advertisements map to make sure any existing
|
||||
// key-value pair is destroyed.
|
||||
eraseOwnedPtrFromMap(advertisements_, slot);
|
||||
|
||||
advertisements_.insert(std::make_pair(slot, scoped_advertisement.release()));
|
||||
}
|
||||
|
||||
// Determines whether or not an advertisement was successfully read at the
|
||||
// specified slot.
|
||||
template <typename Platform>
|
||||
bool AdvertisementReadResult<Platform>::hasAdvertisement(std::int32_t slot) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return advertisements_.find(slot) != advertisements_.end();
|
||||
}
|
||||
|
||||
// Retrieves all raw advertisements that were successfully read.
|
||||
template <typename Platform>
|
||||
std::set<ConstPtr<ByteArray>>
|
||||
AdvertisementReadResult<Platform>::getAdvertisements() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
std::set<ConstPtr<ByteArray>> all_advertisements;
|
||||
for (AdvertisementMap::iterator it = advertisements_.begin();
|
||||
it != advertisements_.end(); ++it) {
|
||||
all_advertisements.insert(it->second);
|
||||
}
|
||||
|
||||
return all_advertisements;
|
||||
}
|
||||
|
||||
// Determines what stage we're in for retrying a read from an advertisement
|
||||
// GATT server.
|
||||
template <typename Platform>
|
||||
typename AdvertisementReadResult<Platform>::RetryStatus::Value
|
||||
AdvertisementReadResult<Platform>::evaluateRetryStatus() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
// Check if we have already succeeded reading this advertisement.
|
||||
if (result_ == Result::SUCCESS) {
|
||||
return RetryStatus::PREVIOUSLY_SUCCEEDED;
|
||||
}
|
||||
|
||||
// Check if we have recently failed to read this advertisement.
|
||||
if (getDurationSinceReadMillis() < backoff_duration_millis_) {
|
||||
return RetryStatus::TOO_SOON;
|
||||
}
|
||||
|
||||
return RetryStatus::RETRY;
|
||||
}
|
||||
|
||||
// Records the status of the latest read, and updates the next backoff
|
||||
// duration for subsequent reads. Be sure to also call
|
||||
// {@link #addAdvertisement(int, byte[])} if any advertisements were read.
|
||||
template <typename Platform>
|
||||
void AdvertisementReadResult<Platform>::recordLastReadStatus(bool is_success) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
// Update the last read timestamp.
|
||||
last_read_timestamp_millis_ = system_clock_->elapsedRealtime();
|
||||
|
||||
// Update the backoff duration.
|
||||
if (is_success) {
|
||||
// Reset the backoff duration now that we had a successful read.
|
||||
backoff_duration_millis_ = kAdvertisementBaseBackoffDurationMillis;
|
||||
} else {
|
||||
// Determine whether or not we were already failing before. If we were, we
|
||||
// should increase the backoff duration.
|
||||
if (result_ == Result::FAILURE) {
|
||||
// Use exponential backoff to determine the next backoff duration. This
|
||||
// simply involves multiplying our current backoff duration by some
|
||||
// multiplier.
|
||||
std::int64_t next_backoff_duration =
|
||||
kAdvertisementBackoffMultiplier * backoff_duration_millis_;
|
||||
// Update the backoff duration, making sure not to blow past the
|
||||
// ceiling.
|
||||
backoff_duration_millis_ = std::min(
|
||||
next_backoff_duration, kAdvertisementMaxBackoffDurationMillis);
|
||||
} else {
|
||||
// This is our first time failing, so we should only backoff for the
|
||||
// initial duration.
|
||||
backoff_duration_millis_ = kAdvertisementBaseBackoffDurationMillis;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the internal result.
|
||||
result_ = is_success ? Result::SUCCESS : Result::FAILURE;
|
||||
}
|
||||
|
||||
// Returns how much time has passed since we last tried reading from an
|
||||
// advertisement GATT server.
|
||||
template <typename Platform>
|
||||
std::int64_t AdvertisementReadResult<Platform>::getDurationSinceReadMillis() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return system_clock_->elapsedRealtime() - last_read_timestamp_millis_;
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,73 +0,0 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "platform/api/lock.h"
|
||||
#include "platform/api/system_clock.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.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.
|
||||
template <typename Platform>
|
||||
class AdvertisementReadResult {
|
||||
public:
|
||||
AdvertisementReadResult();
|
||||
~AdvertisementReadResult();
|
||||
|
||||
struct RetryStatus {
|
||||
enum Value {
|
||||
UNKNOWN = 0,
|
||||
RETRY = 1,
|
||||
PREVIOUSLY_SUCCEEDED = 2,
|
||||
TOO_SOON = 3,
|
||||
};
|
||||
};
|
||||
|
||||
void addAdvertisement(std::int32_t slot, ConstPtr<ByteArray> advertisement);
|
||||
bool hasAdvertisement(std::int32_t slot);
|
||||
std::set<ConstPtr<ByteArray>> getAdvertisements();
|
||||
typename RetryStatus::Value evaluateRetryStatus();
|
||||
void recordLastReadStatus(bool is_success);
|
||||
std::int64_t getDurationSinceReadMillis();
|
||||
|
||||
private:
|
||||
struct Result {
|
||||
enum Value { UNKNOWN = 0, SUCCESS = 1, FAILURE = 2 };
|
||||
};
|
||||
|
||||
static const float kAdvertisementBackoffMultiplier;
|
||||
static const std::int64_t kAdvertisementBaseBackoffDurationMillis;
|
||||
static const std::int64_t kAdvertisementMaxBackoffDurationMillis;
|
||||
|
||||
// ------------ GENERAL ------------
|
||||
ScopedPtr<Ptr<Lock>> lock_;
|
||||
ScopedPtr<Ptr<SystemClock>> system_clock_;
|
||||
|
||||
// ------ ADVERTISEMENTREADRESULT STATE ------
|
||||
// Maps slot numbers to the GATT advertisement found in that slot.
|
||||
typedef std::map<std::int32_t, /* RefCounted */ ConstPtr<ByteArray>>
|
||||
AdvertisementMap;
|
||||
AdvertisementMap advertisements_;
|
||||
|
||||
std::int64_t backoff_duration_millis_;
|
||||
std::int64_t last_read_timestamp_millis_;
|
||||
typename Result::Value result_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/advertisement_read_result.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
|
||||
@@ -1,140 +0,0 @@
|
||||
#include "core/internal/mediums/advertisement_read_result.h"
|
||||
|
||||
#include "platform/api/platform.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/clock.h"
|
||||
#include "absl/time/time.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
using TestPlatform = platform::ImplementationPlatform;
|
||||
|
||||
constexpr char kAdvertisementBytes[] = {0x0A, 0x0B, 0x0C};
|
||||
|
||||
// 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::Milliseconds(1000); // 1 second
|
||||
const absl::Duration kAdvertisementMaxBackoffDuration =
|
||||
absl::Milliseconds(6000); // 6 seconds
|
||||
|
||||
template <>
|
||||
const std::int64_t AdvertisementReadResult<
|
||||
TestPlatform>::kAdvertisementMaxBackoffDurationMillis =
|
||||
ToInt64Milliseconds(kAdvertisementMaxBackoffDuration);
|
||||
template <>
|
||||
const std::int64_t
|
||||
AdvertisementReadResult<
|
||||
TestPlatform>::kAdvertisementBaseBackoffDurationMillis =
|
||||
ToInt64Milliseconds(kAdvertisementBaseBackoffDuration);
|
||||
|
||||
TEST(AdvertisementReadResultTest, AdvertisementExists) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int32_t slot = 6;
|
||||
advertisement_read_result.addAdvertisement(
|
||||
slot,
|
||||
MakeConstPtr(new ByteArray(kAdvertisementBytes,
|
||||
sizeof(kAdvertisementBytes) / sizeof(char))));
|
||||
|
||||
ASSERT_TRUE(advertisement_read_result.hasAdvertisement(slot));
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, AdvertisementNonExistent) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int32_t slot = 6;
|
||||
|
||||
ASSERT_FALSE(advertisement_read_result.hasAdvertisement(slot));
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusInitialized) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::RETRY);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusSuccess) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<
|
||||
TestPlatform>::RetryStatus::PREVIOUSLY_SUCCEEDED);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusTooSoon) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Sleep for some time, but not long enough to warrant a retry.
|
||||
absl::SleepFor(absl::Milliseconds(
|
||||
absl::ToInt64Milliseconds(kAdvertisementBaseBackoffDuration) / 2));
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::TOO_SOON);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, EvaluateRetryStatusRetry) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ false);
|
||||
|
||||
// Sleep long enough to warrant a retry.
|
||||
absl::SleepFor(kAdvertisementBaseBackoffDuration);
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::RETRY);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoff) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
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);
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::TOO_SOON);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, ReportStatusExponentialBackoffMax) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
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);
|
||||
|
||||
ASSERT_EQ(advertisement_read_result.evaluateRetryStatus(),
|
||||
AdvertisementReadResult<TestPlatform>::RetryStatus::RETRY);
|
||||
}
|
||||
|
||||
TEST(AdvertisementReadResultTest, GetDurationSinceRead) {
|
||||
AdvertisementReadResult<TestPlatform> advertisement_read_result;
|
||||
advertisement_read_result.recordLastReadStatus(/* is_success= */ true);
|
||||
|
||||
std::int64_t sleepTime = 420;
|
||||
absl::SleepFor(absl::Milliseconds(sleepTime));
|
||||
|
||||
ASSERT_GE(advertisement_read_result.getDurationSinceReadMillis(), sleepTime);
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
+227
-170
@@ -1,279 +1,336 @@
|
||||
#include "core/internal/mediums/ble.h"
|
||||
|
||||
#include "platform/synchronized.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#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 {
|
||||
|
||||
template <typename Platform>
|
||||
const std::int32_t BLE<Platform>::kMaxAdvertisementLength = 512;
|
||||
|
||||
template <typename Platform>
|
||||
BLE<Platform>::BLE(Ptr<BluetoothRadio<Platform>> bluetooth_radio)
|
||||
: lock_(Platform::createLock()),
|
||||
bluetooth_radio_(bluetooth_radio),
|
||||
bluetooth_adapter_(Platform::createBluetoothAdapter()),
|
||||
ble_medium_(Platform::createBLEMedium()),
|
||||
scanning_info_(),
|
||||
advertising_info_(),
|
||||
accepting_connections_info_() {}
|
||||
|
||||
template <typename Platform>
|
||||
BLE<Platform>::~BLE() {
|
||||
stopAdvertising();
|
||||
stopAcceptingConnections();
|
||||
stopScanning();
|
||||
ByteArray Ble::GenerateHash(const std::string& source, size_t size) {
|
||||
return Utils::Sha256Hash(source, size);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLE<Platform>::isAvailable() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !ble_medium_.isNull() && !bluetooth_adapter_.isNull();
|
||||
ByteArray Ble::GenerateDeviceToken() {
|
||||
return Utils::Sha256Hash(std::to_string(Prng().NextUint32()),
|
||||
mediums::BleAdvertisement::kDeviceTokenLength);
|
||||
}
|
||||
|
||||
// TODO(ahlee): Add fastPairData for phase 2 of C++ implementation.
|
||||
template <typename Platform>
|
||||
bool BLE<Platform>::startAdvertising(const string& service_id,
|
||||
ConstPtr<ByteArray> advertisement) {
|
||||
Synchronized s(lock_.get());
|
||||
Ble::Ble(BluetoothRadio& radio) : radio_(radio) {}
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_advertisement(advertisement);
|
||||
if (scoped_advertisement.isNull() || service_id.empty()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Refusing to start BLE advertising
|
||||
// because a null parameter was passed in.");
|
||||
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 (scoped_advertisement->size() > kMaxAdvertisementLength) {
|
||||
// TODO(ahlee): logger.atSevere().log("Refusing to start BLE advertising
|
||||
// because the advertisement was too long. Expected at most %d bytes but
|
||||
// received %d.", kMaxAdvertisementLength, advertisement->size());
|
||||
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 (isAdvertising()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Failed to BLE advertise because we're
|
||||
// already advertising.");
|
||||
if (IsAdvertisingLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Failed to BLE advertise because we're already advertising.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Can't start BLE advertising because
|
||||
// Bluetooth isn't enabled.");
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Can't start BLE scanning because Bluetooth was never turned on";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Can't start BLE advertising because
|
||||
// BLE isn't enabled.");
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOGS(INFO) << "Can't turn on BLE advertising. BLE is not available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ble_medium_->startAdvertising(service_id,
|
||||
scoped_advertisement.release())) {
|
||||
// TODO(ahlee) logger.atSevere().log("Failed to start BLE advertising");
|
||||
NEARBY_LOGS(INFO) << "Turning on BLE advertising with advertisement bytes="
|
||||
<< advertisement_bytes.data() << "("
|
||||
<< 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;
|
||||
}
|
||||
|
||||
advertising_info_ = MakePtr(new AdvertisingInfo(service_id));
|
||||
if (!medium_.StartAdvertising(service_id, medium_advertisement_bytes,
|
||||
fast_advertisement_service_uuid)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Failed to turn on BLE advertising with advertisement bytes="
|
||||
<< advertisement_bytes.data() << "(" << advertisement_bytes.size()
|
||||
<< ")"
|
||||
<< ", fast advertisement service uuid="
|
||||
<< fast_advertisement_service_uuid;
|
||||
return false;
|
||||
}
|
||||
|
||||
advertising_info_.Add(service_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BLE<Platform>::stopAdvertising() {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::StopAdvertising(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!isAdvertising()) {
|
||||
// TODO(ahlee): logger.atDebug().log("Can't turn off BLE advertising because
|
||||
// it never started.");
|
||||
return;
|
||||
if (!IsAdvertisingLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO) << "Can't turn off BLE advertising; it is already off";
|
||||
return false;
|
||||
}
|
||||
|
||||
ble_medium_->stopAdvertising(advertising_info_->service_id);
|
||||
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_.destroy();
|
||||
|
||||
// TODO(ahlee): logger.atVerbose().log("Turned BLE advertising off");
|
||||
advertising_info_.Remove(service_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLE<Platform>::isAdvertising() {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::IsAdvertising(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return !advertising_info_.isNull();
|
||||
return IsAdvertisingLocked(service_id);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLE<Platform>::startScanning(
|
||||
const string& service_id,
|
||||
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::IsAdvertisingLocked(const std::string& service_id) {
|
||||
return advertising_info_.Existed(service_id);
|
||||
}
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<DiscoveredPeripheralCallback>>
|
||||
scoped_discovered_peripheral_callback(discovered_peripheral_callback);
|
||||
if (scoped_discovered_peripheral_callback.isNull() || service_id.empty()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Refusing to start BLE scanning
|
||||
// because a null parameter was passed in.");
|
||||
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 (isScanning()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Refusing to start BLE scanning
|
||||
// because we are already scanning.");
|
||||
if (IsScanningLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO) << "Refusing to start scan of BLE peripherals because "
|
||||
"another scanning is already in-progress.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Can't start BLE scanning because
|
||||
// Bluetooth was never turned on");
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Can't start BLE scanning because Bluetooth was never turned on";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Can't start BLE scanning because
|
||||
// BLE isn't available.");
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Can't scan BLE peripherals because BLE isn't available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<BLEDiscoveredPeripheralCallback>>
|
||||
scoped_ble_discovered_peripheral_callback(
|
||||
new BLEDiscoveredPeripheralCallback(
|
||||
scoped_discovered_peripheral_callback.release()));
|
||||
if (!ble_medium_->startScanning(
|
||||
service_id, scoped_ble_discovered_peripheral_callback.get())) {
|
||||
// TODO(ahlee): logger.atSevere().log("Failed to start BLE scanning.");
|
||||
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) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
scanning_info_ = MakePtr(new ScanningInfo(
|
||||
service_id, scoped_ble_discovered_peripheral_callback.release()));
|
||||
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;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BLE<Platform>::stopScanning() {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::StopScanning(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!isScanning()) {
|
||||
// TODO(ahlee): logger.atDebug().log("Can't turn off BLE scanning because we
|
||||
// never started scanning.");
|
||||
return;
|
||||
if (!IsScanningLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO) << "Can't turn off BLE sacanning because we never "
|
||||
"started scanning.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ble_medium_->stopScanning(scanning_info_->service_id);
|
||||
// Reset our bundle of scanning state to mark that we're no longer scanning.
|
||||
scanning_info_.destroy();
|
||||
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;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLE<Platform>::isScanning() {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::IsScanning(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return !scanning_info_.isNull();
|
||||
return IsScanningLocked(service_id);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLE<Platform>::startAcceptingConnections(
|
||||
const string& service_id,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::IsScanningLocked(const std::string& service_id) {
|
||||
return scanning_info_.Existed(service_id);
|
||||
}
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<AcceptedConnectionCallback>>
|
||||
scoped_accepted_connection_callback(accepted_connection_callback);
|
||||
if (scoped_accepted_connection_callback.isNull() || service_id.empty()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Refusing to start accepting BLE
|
||||
// connections because a null parameter was passed in.");
|
||||
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 (isAcceptingConnections()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Refusing to start accepting BLE
|
||||
// connections for %s because another BLE server socket is already
|
||||
// in-progress.", service_id);
|
||||
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 (!bluetooth_radio_->isEnabled()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Can't start accepting BLE connections
|
||||
// for %s because Bluetooth isn't enabled.", serviceId);
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for "
|
||||
<< service_id
|
||||
<< " because Bluetooth isn't enabled.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Can't start accepting BLE connections
|
||||
// for %s because BLE isn't available.", serviceId);
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOGS(INFO) << "Can't start accepting BLE connections for "
|
||||
<< service_id << " because BLE isn't available.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<BLEAcceptedConnectionCallback>>
|
||||
scoped_ble_accepted_connection_callback(new BLEAcceptedConnectionCallback(
|
||||
scoped_accepted_connection_callback.release()));
|
||||
if (!ble_medium_->startAcceptingConnections(
|
||||
service_id, scoped_ble_accepted_connection_callback.get())) {
|
||||
if (!medium_.StartAcceptingConnections(service_id, callback)) {
|
||||
NEARBY_LOGS(INFO) << "Failed to accept connections callback for "
|
||||
<< service_id << " .";
|
||||
return false;
|
||||
}
|
||||
|
||||
accepting_connections_info_ = MakePtr(new AcceptingConnectionsInfo(
|
||||
service_id, scoped_ble_accepted_connection_callback.release()));
|
||||
accepting_connections_info_.Add(service_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BLE<Platform>::stopAcceptingConnections() {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::StopAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!isAcceptingConnections()) {
|
||||
// TODO(ahlee): logger.atDebug().log("Can't stop accepting BLE connections
|
||||
// because it was never started.");
|
||||
return;
|
||||
if (!IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "Can't stop accepting BLE connections because it was never started.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ble_medium_->stopAcceptingConnections(
|
||||
accepting_connections_info_->service_id);
|
||||
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_.destroy();
|
||||
accepting_connections_info_.Remove(service_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLE<Platform>::isAcceptingConnections() {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::IsAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return !accepting_connections_info_.isNull();
|
||||
return IsAcceptingConnectionsLocked(service_id);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<BLESocket> BLE<Platform>::connect(Ptr<BLEPeripheral> ble_peripheral,
|
||||
const string& service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
bool Ble::IsAcceptingConnectionsLocked(const std::string& service_id) {
|
||||
return accepting_connections_info_.Existed(service_id);
|
||||
}
|
||||
|
||||
if (ble_peripheral.isNull() || service_id.empty()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Refusing to create client BLE socket
|
||||
// because at least one of blePeripheral or serviceId is null.");
|
||||
return Ptr<BLESocket>();
|
||||
BleSocket Ble::Connect(BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
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 (!bluetooth_radio_->isEnabled()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Can't create client BLE socket to %s
|
||||
// because Bluetooth isn't enabled.", blePeripheral);
|
||||
return Ptr<BLESocket>();
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOGS(INFO) << "Can't create client BLE socket to "
|
||||
<< &peripheral << " because Bluetooth isn't enabled.";
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Can't create client BLE socket to %s
|
||||
// because BLE isn't available.", blePeripheral);
|
||||
return Ptr<BLESocket>();
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOGS(INFO) << "Can't create client BLE socket [service_id="
|
||||
<< service_id << "]; BLE isn't available.";
|
||||
return socket;
|
||||
}
|
||||
|
||||
return ble_medium_->connect(ble_peripheral, service_id);
|
||||
socket = medium_.Connect(peripheral, service_id);
|
||||
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
|
||||
|
||||
+121
-146
@@ -2,196 +2,171 @@
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "platform/api/ble.h"
|
||||
#include "platform/api/bluetooth_adapter.h"
|
||||
#include "platform/api/lock.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "core/listeners.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/ble.h"
|
||||
#include "platform/public/multi_thread_executor.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
template <typename Platform>
|
||||
class BLE {
|
||||
class Ble {
|
||||
public:
|
||||
explicit BLE(Ptr<BluetoothRadio<Platform>> bluetooth_radio);
|
||||
~BLE();
|
||||
using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback;
|
||||
using AcceptedConnectionCallback = BleMedium::AcceptedConnectionCallback;
|
||||
|
||||
bool isAvailable();
|
||||
explicit Ble(BluetoothRadio& bluetooth_radio);
|
||||
~Ble() = default;
|
||||
|
||||
bool startAdvertising(const string& service_id,
|
||||
ConstPtr<ByteArray> advertisement);
|
||||
void stopAdvertising();
|
||||
// Returns true, if Ble communications are supported by a platform.
|
||||
bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
class DiscoveredPeripheralCallback {
|
||||
public:
|
||||
virtual ~DiscoveredPeripheralCallback() {}
|
||||
// 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_);
|
||||
|
||||
virtual void onPeripheralDiscovered(Ptr<BLEPeripheral> ble_peripheral,
|
||||
const string& service_id,
|
||||
ConstPtr<ByteArray> advertisement) = 0;
|
||||
virtual void onPeripheralLost(Ptr<BLEPeripheral> ble_peripheral,
|
||||
const string& service_id) = 0;
|
||||
};
|
||||
// Disables Ble advertising.
|
||||
bool StopAdvertising(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool startScanning(
|
||||
const string& service_id,
|
||||
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback);
|
||||
void stopScanning();
|
||||
bool IsAdvertising(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
class AcceptedConnectionCallback {
|
||||
public:
|
||||
virtual ~AcceptedConnectionCallback() {}
|
||||
// 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_);
|
||||
|
||||
virtual void onConnectionAccepted(Ptr<BLESocket> socket,
|
||||
const string& service_id) = 0;
|
||||
};
|
||||
// Disables Ble discovery mode.
|
||||
bool StopScanning(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool startAcceptingConnections(
|
||||
const string& service_id,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback);
|
||||
void stopAcceptingConnections();
|
||||
bool isAcceptingConnections();
|
||||
bool IsScanning(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
Ptr<BLESocket> connect(Ptr<BLEPeripheral> ble_peripheral,
|
||||
const string& service_id);
|
||||
// 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)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
// TODO(ahlee): Rename to DiscoveredPeripheralCallbackBridge
|
||||
class BLEDiscoveredPeripheralCallback
|
||||
: public BLEMedium::DiscoveredPeripheralCallback {
|
||||
public:
|
||||
explicit BLEDiscoveredPeripheralCallback(
|
||||
Ptr<BLE::DiscoveredPeripheralCallback> discovered_peripheral_callback)
|
||||
: discovered_peripheral_callback_(discovered_peripheral_callback) {}
|
||||
~BLEDiscoveredPeripheralCallback() override {
|
||||
// Nothing to do.
|
||||
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);
|
||||
}
|
||||
|
||||
void onPeripheralDiscovered(Ptr<BLEPeripheral> ble_peripheral,
|
||||
const string& service_id,
|
||||
ConstPtr<ByteArray> advertisement) override {
|
||||
discovered_peripheral_callback_->onPeripheralDiscovered(
|
||||
ble_peripheral, service_id, advertisement);
|
||||
}
|
||||
void onPeripheralLost(Ptr<BLEPeripheral> ble_peripheral,
|
||||
const string& service_id) override {
|
||||
discovered_peripheral_callback_->onPeripheralLost(ble_peripheral,
|
||||
service_id);
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedPtr<Ptr<BLE::DiscoveredPeripheralCallback>>
|
||||
discovered_peripheral_callback_;
|
||||
};
|
||||
|
||||
// TODO(ahlee): Rename to AcceptedConnectionCallbackBridge
|
||||
class BLEAcceptedConnectionCallback
|
||||
: public BLEMedium::AcceptedConnectionCallback {
|
||||
public:
|
||||
explicit BLEAcceptedConnectionCallback(
|
||||
Ptr<BLE::AcceptedConnectionCallback> accepted_connection_callback)
|
||||
: accepted_connection_callback_(accepted_connection_callback) {}
|
||||
~BLEAcceptedConnectionCallback() override {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
void onConnectionAccepted(Ptr<BLESocket> ble_socket,
|
||||
const string& service_id) override {
|
||||
accepted_connection_callback_->onConnectionAccepted(ble_socket,
|
||||
service_id);
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedPtr<Ptr<BLE::AcceptedConnectionCallback>>
|
||||
accepted_connection_callback_;
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
struct ScanningInfo {
|
||||
ScanningInfo(
|
||||
const string& service_id,
|
||||
Ptr<BLEDiscoveredPeripheralCallback> ble_discovered_peripheral_callback)
|
||||
: service_id(service_id),
|
||||
ble_discovered_peripheral_callback(
|
||||
ble_discovered_peripheral_callback) {}
|
||||
~ScanningInfo() {
|
||||
// Nothing to do (the ScopedPtr members take care of themselves).
|
||||
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);
|
||||
}
|
||||
|
||||
const string service_id;
|
||||
ScopedPtr<Ptr<BLEDiscoveredPeripheralCallback>>
|
||||
ble_discovered_peripheral_callback;
|
||||
};
|
||||
|
||||
struct AdvertisingInfo {
|
||||
explicit AdvertisingInfo(const string& service_id)
|
||||
: service_id(service_id) {}
|
||||
~AdvertisingInfo() {}
|
||||
|
||||
const string service_id;
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
struct AcceptingConnectionsInfo {
|
||||
AcceptingConnectionsInfo(
|
||||
const string& service_id,
|
||||
Ptr<BLEAcceptedConnectionCallback> ble_accepted_connection_callback)
|
||||
: service_id(service_id),
|
||||
ble_accepted_connection_callback(ble_accepted_connection_callback) {}
|
||||
~AcceptingConnectionsInfo() {
|
||||
// Nothing to do (the ScopedPtr members take care of themselves).
|
||||
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);
|
||||
}
|
||||
|
||||
const string service_id;
|
||||
ScopedPtr<Ptr<BLEAcceptedConnectionCallback>>
|
||||
ble_accepted_connection_callback;
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
static const std::int32_t kMaxAdvertisementLength;
|
||||
static constexpr int kMaxAdvertisementLength = 512;
|
||||
|
||||
bool isAdvertising();
|
||||
bool isScanning();
|
||||
static ByteArray GenerateHash(const std::string& source, size_t size);
|
||||
static ByteArray GenerateDeviceToken();
|
||||
|
||||
// ------------ GENERAL ------------
|
||||
// Same as IsAvailable(), but must be called with mutex_ held.
|
||||
bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
ScopedPtr<Ptr<Lock>> lock_;
|
||||
// Same as IsAdvertising(), but must be called with mutex_ held.
|
||||
bool IsAdvertisingLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// ------------ CORE BLE ------------
|
||||
// Same as IsDiscovering(), but must be called with mutex_ held.
|
||||
bool IsScanningLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
Ptr<BluetoothRadio<Platform>> bluetooth_radio_;
|
||||
ScopedPtr<Ptr<BluetoothAdapter>> bluetooth_adapter_;
|
||||
// The underlying, per-platform implementation.
|
||||
ScopedPtr<Ptr<BLEMedium>> ble_medium_;
|
||||
// Same as IsAcceptingConnections(), but must be called with mutex_ held.
|
||||
bool IsAcceptingConnectionsLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// ------------ DISCOVERY ------------
|
||||
// Extract connection advertisement from medium advertisement.
|
||||
ByteArray UnwrapAdvertisementBytes(
|
||||
const ByteArray& medium_advertisement_data);
|
||||
|
||||
// A bundle of state required to start/stop BLE scanning. When non-null,
|
||||
// we are currently performing a BLE scan.
|
||||
// In the Java code this maps to the bleListener and
|
||||
// bleScanningMediumOperation.
|
||||
Ptr<ScanningInfo> scanning_info_;
|
||||
|
||||
// ------------ ADVERTISING ------------
|
||||
|
||||
// A bundle of state required to start/stop BLE advertising. When non-null,
|
||||
// we are currently advertising over BLE.
|
||||
// In the Java code this maps to bleAdvertiser, advertiseCallback, and
|
||||
// bleAdvertisingMediumOperation.
|
||||
Ptr<AdvertisingInfo> advertising_info_;
|
||||
|
||||
// A bundle of state required to start/stop accepting BLE connections. When
|
||||
// non-null, we are currently accepting BLE connections.
|
||||
// In the Java code this maps to the bleServerSocket.
|
||||
Ptr<AcceptingConnectionsInfo> accepting_connections_info_;
|
||||
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
|
||||
|
||||
#include "core/internal/mediums/ble.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_H_
|
||||
|
||||
@@ -1,288 +0,0 @@
|
||||
#include "core/internal/mediums/ble_advertisement.h"
|
||||
|
||||
#include "platform/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
const std::uint32_t BLEAdvertisement::kServiceIdHashLength = 3;
|
||||
|
||||
const std::uint32_t BLEAdvertisement::kVersionLength = 1;
|
||||
// Length of one int. Be sure to re-evaluate how we compute data size in this
|
||||
// class if this constant ever changes!
|
||||
const std::uint32_t BLEAdvertisement::kDataSizeLength = 4;
|
||||
const std::uint32_t BLEAdvertisement::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.
|
||||
const std::uint32_t BLEAdvertisement::kMaxDataSize =
|
||||
512 - kMinAdvertisementLength;
|
||||
const std::uint16_t BLEAdvertisement::kVersionBitmask = 0x0E0;
|
||||
const std::uint16_t BLEAdvertisement::kSocketVersionBitmask = 0x01C;
|
||||
|
||||
ConstPtr<BLEAdvertisement> BLEAdvertisement::fromBytes(
|
||||
ConstPtr<ByteArray> ble_advertisement_bytes) {
|
||||
if (ble_advertisement_bytes.isNull()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BLEAdvertisement: null bytes passed in");
|
||||
return ConstPtr<BLEAdvertisement>();
|
||||
}
|
||||
|
||||
if (ble_advertisement_bytes->size() < kMinAdvertisementLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BLEAdvertisement: expecting min %u raw "
|
||||
"bytes, got %zu",
|
||||
kMinAdvertisementLength, ble_advertisement_bytes->size());
|
||||
return ConstPtr<BLEAdvertisement>();
|
||||
}
|
||||
|
||||
// Now, time to read the bytes!
|
||||
const char *ble_advertisement_bytes_read_ptr =
|
||||
ble_advertisement_bytes->getData();
|
||||
|
||||
// 1. Version.
|
||||
Version::Value version = parseVersionFromByte(
|
||||
static_cast<std::uint16_t>(*ble_advertisement_bytes_read_ptr));
|
||||
if (!isSupportedVersion(version)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BLEAdvertisement: unsupported Version %u",
|
||||
version);
|
||||
return ConstPtr<BLEAdvertisement>();
|
||||
}
|
||||
|
||||
// 2. Socket Version.
|
||||
SocketVersion::Value socket_version = parseSocketVersionFromByte(
|
||||
static_cast<std::uint16_t>(*ble_advertisement_bytes_read_ptr));
|
||||
if (!isSupportedSocketVersion(socket_version)) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u",
|
||||
socket_version);
|
||||
return ConstPtr<BLEAdvertisement>();
|
||||
}
|
||||
ble_advertisement_bytes_read_ptr += kVersionLength;
|
||||
|
||||
// 3. Service ID hash.
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
|
||||
new ByteArray(ble_advertisement_bytes_read_ptr, kServiceIdHashLength)));
|
||||
ble_advertisement_bytes_read_ptr += kServiceIdHashLength;
|
||||
|
||||
// 4.1. Data size.
|
||||
size_t expected_data_size =
|
||||
deserializeDataSize(ble_advertisement_bytes_read_ptr);
|
||||
if (expected_data_size < 0) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BLEAdvertisement: negative data size %zu",
|
||||
expected_data_size);
|
||||
return ConstPtr<BLEAdvertisement>();
|
||||
}
|
||||
ble_advertisement_bytes_read_ptr += kDataSizeLength;
|
||||
|
||||
// Check that the stated data size is the same as what we received.
|
||||
size_t actual_data_size = computeDataSize(ble_advertisement_bytes);
|
||||
if (actual_data_size < expected_data_size) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BLEAdvertisement: expected data to be %zu "
|
||||
"bytes, got %zu bytes",
|
||||
expected_data_size, actual_data_size);
|
||||
return ConstPtr<BLEAdvertisement>();
|
||||
}
|
||||
|
||||
// 4.2. Data.
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(MakeConstPtr(
|
||||
new ByteArray(ble_advertisement_bytes_read_ptr, expected_data_size)));
|
||||
ble_advertisement_bytes_read_ptr += expected_data_size;
|
||||
|
||||
return MakeRefCountedConstPtr(new BLEAdvertisement(
|
||||
version, socket_version, scoped_service_id_hash.release(),
|
||||
scoped_data.release()));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> BLEAdvertisement::toBytes(
|
||||
Version::Value version, SocketVersion::Value socket_version,
|
||||
ConstPtr<ByteArray> service_id_hash, ConstPtr<ByteArray> data) {
|
||||
// Check that the given input is valid.
|
||||
if (!isSupportedVersion(version)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot serialize BLEAdvertisement: unsupported Version %u",
|
||||
version);
|
||||
return ConstPtr<ByteArray>();
|
||||
}
|
||||
|
||||
if (!isSupportedSocketVersion(socket_version)) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Cannot serialize BLEAdvertisement: unsupported SocketVersion %u",
|
||||
socket_version);
|
||||
return ConstPtr<ByteArray>();
|
||||
}
|
||||
|
||||
if (service_id_hash->size() != kServiceIdHashLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot serialize BLEAdvertisement: expected a service_id_hash "
|
||||
"of %u bytes, but got %zu",
|
||||
kServiceIdHashLength, service_id_hash->size());
|
||||
return ConstPtr<ByteArray>();
|
||||
}
|
||||
|
||||
if (data->size() > kMaxDataSize) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot serialize BLEAdvertisement: expected data of at most %u "
|
||||
"bytes, but got %zu",
|
||||
kMaxDataSize, data->size());
|
||||
return ConstPtr<ByteArray>();
|
||||
}
|
||||
|
||||
// Initialize the bytes.
|
||||
size_t advertisement_length = computeAdvertisementLength(data);
|
||||
Ptr<ByteArray> advertisement_bytes{new ByteArray{advertisement_length}};
|
||||
char *advertisement_bytes_write_ptr = advertisement_bytes->getData();
|
||||
|
||||
// 1. Version.
|
||||
serializeVersionByte(advertisement_bytes_write_ptr, version);
|
||||
|
||||
// 2. SocketVersion.
|
||||
serializeSocketVersionByte(advertisement_bytes_write_ptr, socket_version);
|
||||
advertisement_bytes_write_ptr += kVersionLength;
|
||||
|
||||
// 3. Service ID hash.
|
||||
memcpy(advertisement_bytes_write_ptr, service_id_hash->getData(),
|
||||
kServiceIdHashLength);
|
||||
advertisement_bytes_write_ptr += kServiceIdHashLength;
|
||||
|
||||
// 4.1. Data length.
|
||||
serializeDataSize(advertisement_bytes_write_ptr, data->size());
|
||||
advertisement_bytes_write_ptr += kDataSizeLength;
|
||||
|
||||
// 4.2. Data.
|
||||
memcpy(advertisement_bytes_write_ptr, data->getData(), data->size());
|
||||
advertisement_bytes_write_ptr += data->size();
|
||||
|
||||
return ConstifyPtr(advertisement_bytes);
|
||||
}
|
||||
|
||||
bool BLEAdvertisement::isSupportedVersion(Version::Value version) {
|
||||
return version >= Version::V1 && version <= Version::V2;
|
||||
}
|
||||
|
||||
bool BLEAdvertisement::isSupportedSocketVersion(
|
||||
SocketVersion::Value socket_version) {
|
||||
return socket_version >= SocketVersion::V1 &&
|
||||
socket_version <= SocketVersion::V2;
|
||||
}
|
||||
|
||||
BLEAdvertisement::Version::Value BLEAdvertisement::parseVersionFromByte(
|
||||
std::uint16_t byte) {
|
||||
return static_cast<BLEAdvertisement::Version::Value>(
|
||||
(byte & kVersionBitmask) >> 5);
|
||||
}
|
||||
|
||||
BLEAdvertisement::SocketVersion::Value
|
||||
BLEAdvertisement::parseSocketVersionFromByte(std::uint16_t byte) {
|
||||
return static_cast<SocketVersion::Value>((byte & kSocketVersionBitmask) >> 2);
|
||||
}
|
||||
|
||||
size_t BLEAdvertisement::deserializeDataSize(
|
||||
const char *data_size_bytes_read_ptr) {
|
||||
// Allocate a chunk of memory to store our deserialized size.
|
||||
char data_size_bytes[kDataSizeLength];
|
||||
|
||||
// Assign the bits of our size from the given raw bytes, keeping in mind that
|
||||
// we need to convert from Big Endian to Little Endian in the process.
|
||||
for (int i = 0; i < kDataSizeLength; ++i) {
|
||||
data_size_bytes[i] = data_size_bytes_read_ptr[kDataSizeLength - i - 1];
|
||||
}
|
||||
|
||||
// Interpret the char array as a single int.
|
||||
return static_cast<size_t>(
|
||||
*(reinterpret_cast<std::uint32_t *>(&data_size_bytes)));
|
||||
}
|
||||
|
||||
size_t BLEAdvertisement::computeDataSize(
|
||||
ConstPtr<ByteArray> ble_advertisement_bytes) {
|
||||
return ble_advertisement_bytes->size() - kMinAdvertisementLength;
|
||||
}
|
||||
|
||||
size_t BLEAdvertisement::computeAdvertisementLength(ConstPtr<ByteArray> data) {
|
||||
// The advertisement length is the minimum length + the length of the data.
|
||||
return kMinAdvertisementLength + data->size();
|
||||
}
|
||||
|
||||
void BLEAdvertisement::serializeVersionByte(char *version_byte_write_ptr,
|
||||
Version::Value version) {
|
||||
*version_byte_write_ptr |=
|
||||
static_cast<char>((version << 5) & kVersionBitmask);
|
||||
}
|
||||
|
||||
void BLEAdvertisement::serializeSocketVersionByte(
|
||||
char *socket_version_byte_write_ptr, SocketVersion::Value socket_version) {
|
||||
*socket_version_byte_write_ptr |=
|
||||
static_cast<char>((socket_version << 2) & kSocketVersionBitmask);
|
||||
}
|
||||
|
||||
void BLEAdvertisement::serializeDataSize(char *data_size_bytes_write_ptr,
|
||||
size_t data_size) {
|
||||
// Get a raw representation of the data size bytes in memory.
|
||||
char *data_size_bytes = reinterpret_cast<char *>(&data_size);
|
||||
|
||||
// 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 < kDataSizeLength; ++i) {
|
||||
data_size_bytes_write_ptr[i] = data_size_bytes[kDataSizeLength - i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
BLEAdvertisement::BLEAdvertisement(Version::Value version,
|
||||
SocketVersion::Value socket_version,
|
||||
ConstPtr<ByteArray> service_id_hash,
|
||||
ConstPtr<ByteArray> data)
|
||||
: version_(version),
|
||||
socket_version_(socket_version),
|
||||
service_id_hash_(service_id_hash),
|
||||
data_(data) {}
|
||||
|
||||
BLEAdvertisement::~BLEAdvertisement() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
BLEAdvertisement::Version::Value BLEAdvertisement::getVersion() const {
|
||||
return version_;
|
||||
}
|
||||
|
||||
BLEAdvertisement::SocketVersion::Value BLEAdvertisement::getSocketVersion()
|
||||
const {
|
||||
return socket_version_;
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> BLEAdvertisement::getServiceIdHash() const {
|
||||
return service_id_hash_.get();
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> BLEAdvertisement::getData() const { return data_.get(); }
|
||||
|
||||
bool BLEAdvertisement::operator==(const BLEAdvertisement &rhs) const {
|
||||
return this->getVersion() == rhs.getVersion() &&
|
||||
this->getSocketVersion() == rhs.getSocketVersion() &&
|
||||
*(this->getServiceIdHash()) == *(rhs.getServiceIdHash()) &&
|
||||
*(this->getData()) == *(rhs.getData());
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
return *(this->getData()) < *(rhs.getData());
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,100 +0,0 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
|
||||
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Represents the format of the Mediums BLE Advertisement used in advertising
|
||||
// and discovery.
|
||||
//
|
||||
// [VERSION][SOCKET_VERSION][2_RESERVED_BITS][SERVICE_ID_HASH][DATA_SIZE][DATA]
|
||||
//
|
||||
// See go/nearby-ble-design for more information.
|
||||
class BLEAdvertisement {
|
||||
public:
|
||||
// Versions of the BLEAdvertisement.
|
||||
struct Version {
|
||||
enum Value {
|
||||
UNKNOWN = 0,
|
||||
V1 = 1,
|
||||
V2 = 2,
|
||||
// Version is only allocated 3 bits in the BLEAdvertisement, so this can
|
||||
// never go beyond V7.
|
||||
};
|
||||
};
|
||||
|
||||
// Versions of the BLESocket.
|
||||
struct SocketVersion {
|
||||
enum Value {
|
||||
UNKNOWN = 0,
|
||||
V1 = 1,
|
||||
V2 = 2,
|
||||
// SocketVersion is only allocated 3 bits in the BLEAdvertisement, so this
|
||||
// can never go beyond V7.
|
||||
};
|
||||
};
|
||||
|
||||
static ConstPtr<BLEAdvertisement> fromBytes(
|
||||
ConstPtr<ByteArray> ble_advertisement_bytes);
|
||||
|
||||
static ConstPtr<ByteArray> toBytes(Version::Value version,
|
||||
SocketVersion::Value socket_version,
|
||||
ConstPtr<ByteArray> service_id_hash,
|
||||
ConstPtr<ByteArray> data);
|
||||
|
||||
static const std::uint32_t kServiceIdHashLength;
|
||||
|
||||
~BLEAdvertisement();
|
||||
|
||||
Version::Value getVersion() const;
|
||||
SocketVersion::Value getSocketVersion() const;
|
||||
ConstPtr<ByteArray> getServiceIdHash() const;
|
||||
ConstPtr<ByteArray> getData() const;
|
||||
|
||||
// Operator overloads when comparing ConstPtr<BLEAdvertisement>.
|
||||
bool operator==(const BLEAdvertisement &rhs) const;
|
||||
bool operator<(const BLEAdvertisement &rhs) const;
|
||||
|
||||
private:
|
||||
static bool isSupportedVersion(Version::Value version);
|
||||
static bool isSupportedSocketVersion(SocketVersion::Value socket_version);
|
||||
static Version::Value parseVersionFromByte(std::uint16_t byte);
|
||||
static SocketVersion::Value parseSocketVersionFromByte(std::uint16_t byte);
|
||||
static size_t deserializeDataSize(const char *data_size_bytes_read_ptr);
|
||||
static size_t computeDataSize(ConstPtr<ByteArray> ble_advertisement_bytes);
|
||||
static size_t computeAdvertisementLength(ConstPtr<ByteArray> data);
|
||||
static void serializeVersionByte(char *version_byte_write_ptr,
|
||||
Version::Value version);
|
||||
static void serializeSocketVersionByte(char *socket_version_byte_write_ptr,
|
||||
SocketVersion::Value socket_version);
|
||||
static void serializeDataSize(char *data_size_bytes_write_ptr,
|
||||
size_t data_size);
|
||||
|
||||
static const std::uint32_t kVersionLength;
|
||||
static const std::uint32_t kDataSizeLength;
|
||||
static const std::uint32_t kMinAdvertisementLength;
|
||||
static const std::uint32_t kMaxDataSize;
|
||||
static const std::uint16_t kVersionBitmask;
|
||||
static const std::uint16_t kSocketVersionBitmask;
|
||||
|
||||
BLEAdvertisement(Version::Value version, SocketVersion::Value socket_version,
|
||||
ConstPtr<ByteArray> service_id_hash,
|
||||
ConstPtr<ByteArray> data);
|
||||
|
||||
const Version::Value version_;
|
||||
const SocketVersion::Value socket_version_;
|
||||
ScopedPtr<ConstPtr<ByteArray> > service_id_hash_;
|
||||
ScopedPtr<ConstPtr<ByteArray> > data_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
|
||||
@@ -1,208 +0,0 @@
|
||||
#include "core/internal/mediums/ble_advertisement_header.h"
|
||||
|
||||
#include "platform/base64_utils.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// The following IfThisThenThat is for BloomFilter length in
|
||||
// ble_v2.createAdvertisementHeader
|
||||
// LINT.IfChange
|
||||
const std::uint32_t BLEAdvertisementHeader::kServiceIdBloomFilterLength = 10;
|
||||
// LINT.ThenChange(//depot/google3/core/internal/\
|
||||
// mediums/ble_v2.h)
|
||||
const std::uint32_t BLEAdvertisementHeader::kAdvertisementHashLength = 4;
|
||||
|
||||
const std::uint32_t BLEAdvertisementHeader::kVersionAndNumSlotsLength = 1;
|
||||
const std::uint32_t BLEAdvertisementHeader::kMinAdvertisementHeaderLength =
|
||||
kVersionAndNumSlotsLength + kServiceIdBloomFilterLength +
|
||||
kAdvertisementHashLength;
|
||||
const std::uint16_t BLEAdvertisementHeader::kVersionBitmask = 0x0E0;
|
||||
const std::uint16_t BLEAdvertisementHeader::kNumSlotsBitmask = 0x01F;
|
||||
|
||||
ConstPtr<BLEAdvertisementHeader> BLEAdvertisementHeader::fromString(
|
||||
const std::string &ble_advertisement_header_string) {
|
||||
ScopedPtr<Ptr<ByteArray> > scoped_ble_advertisement_header_bytes(
|
||||
Base64Utils::decode(ble_advertisement_header_string));
|
||||
if (scoped_ble_advertisement_header_bytes.isNull()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BLEAdvertisementHeader: failed Base64 decoding");
|
||||
return ConstPtr<BLEAdvertisementHeader>();
|
||||
}
|
||||
|
||||
if (scoped_ble_advertisement_header_bytes->size() <
|
||||
kMinAdvertisementHeaderLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BLEAdvertisementHeader: expecting min %u "
|
||||
"raw bytes, got %zu instead",
|
||||
kMinAdvertisementHeaderLength,
|
||||
scoped_ble_advertisement_header_bytes->size());
|
||||
return ConstPtr<BLEAdvertisementHeader>();
|
||||
}
|
||||
|
||||
// Now, time to read the bytes!
|
||||
const char *ble_advertisement_header_read_ptr =
|
||||
scoped_ble_advertisement_header_bytes->getData();
|
||||
|
||||
// 1. Version.
|
||||
// The first 3 bits of the first byte represent the version.
|
||||
Version::Value version = parseVersionFromByte(
|
||||
static_cast<std::uint16_t>(*ble_advertisement_header_read_ptr));
|
||||
if (version != Version::V2) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BLEAdvertisementHeader, unsupported version %u",
|
||||
version);
|
||||
return ConstPtr<BLEAdvertisementHeader>();
|
||||
}
|
||||
|
||||
// 2. Number of slots.
|
||||
// The last 5 bits of the first byte represent the number of slots.
|
||||
std::uint32_t num_slots = parseNumSlotsFromByte(
|
||||
static_cast<std::uint16_t>(*ble_advertisement_header_read_ptr));
|
||||
ble_advertisement_header_read_ptr += kVersionAndNumSlotsLength;
|
||||
|
||||
// 3. Service ID bloom filter.
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(
|
||||
MakeConstPtr(new ByteArray(ble_advertisement_header_read_ptr,
|
||||
kServiceIdBloomFilterLength)));
|
||||
ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength;
|
||||
|
||||
// 4. Advertisement hash.
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
|
||||
MakeConstPtr(new ByteArray(ble_advertisement_header_read_ptr,
|
||||
kAdvertisementHashLength)));
|
||||
ble_advertisement_header_read_ptr += kAdvertisementHashLength;
|
||||
|
||||
return MakeRefCountedConstPtr(new BLEAdvertisementHeader(
|
||||
version, num_slots, scoped_service_id_bloom_filter.release(),
|
||||
scoped_advertisement_hash.release()));
|
||||
}
|
||||
|
||||
std::string BLEAdvertisementHeader::asString(
|
||||
Version::Value version, std::uint32_t num_slots,
|
||||
ConstPtr<ByteArray> service_id_bloom_filter,
|
||||
ConstPtr<ByteArray> advertisement_hash) {
|
||||
// Check that the given input is valid.
|
||||
if (version != Version::V2) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Cannot serialize BLEAdvertisementHeader: unsupported Version %u",
|
||||
version);
|
||||
return "";
|
||||
}
|
||||
|
||||
if (service_id_bloom_filter->size() != kServiceIdBloomFilterLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot serialize BLEAdvertisementHeader: expected "
|
||||
"service_id_bloom_filter of %u bytes, but got %zu",
|
||||
kServiceIdBloomFilterLength, service_id_bloom_filter->size());
|
||||
return "";
|
||||
}
|
||||
|
||||
if (advertisement_hash->size() != kAdvertisementHashLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot serialize BLEAdvertisementHeader: expected "
|
||||
"advertisement_hash of %u bytes, but got %zu",
|
||||
kAdvertisementHashLength, advertisement_hash->size());
|
||||
return "";
|
||||
}
|
||||
|
||||
// Initialize the bytes.
|
||||
ByteArray advertisement_header_bytes{kMinAdvertisementHeaderLength};
|
||||
char *advertisement_header_bytes_write_ptr =
|
||||
advertisement_header_bytes.getData();
|
||||
|
||||
// 1. Version.
|
||||
serializeVersionByte(advertisement_header_bytes_write_ptr, version);
|
||||
|
||||
// 2. Number of slots.
|
||||
serializeNumSlots(advertisement_header_bytes_write_ptr, num_slots);
|
||||
advertisement_header_bytes_write_ptr += kVersionAndNumSlotsLength;
|
||||
|
||||
// 3. Service ID bloom filter.
|
||||
memcpy(advertisement_header_bytes_write_ptr,
|
||||
service_id_bloom_filter->getData(), kServiceIdBloomFilterLength);
|
||||
advertisement_header_bytes_write_ptr += kServiceIdBloomFilterLength;
|
||||
|
||||
// 4. Advertisement hash.
|
||||
memcpy(advertisement_header_bytes_write_ptr, advertisement_hash->getData(),
|
||||
kAdvertisementHashLength);
|
||||
advertisement_header_bytes_write_ptr += kAdvertisementHashLength;
|
||||
|
||||
// Header needs to be binary safe, so apply a Base64 encoding.
|
||||
return Base64Utils::encode(advertisement_header_bytes);
|
||||
}
|
||||
|
||||
BLEAdvertisementHeader::Version::Value
|
||||
BLEAdvertisementHeader::parseVersionFromByte(std::uint16_t byte) {
|
||||
return static_cast<Version::Value>((byte & kVersionBitmask) >> 5);
|
||||
}
|
||||
|
||||
std::uint32_t BLEAdvertisementHeader::parseNumSlotsFromByte(
|
||||
std::uint16_t byte) {
|
||||
return static_cast<std::uint32_t>((byte & kNumSlotsBitmask));
|
||||
}
|
||||
|
||||
void BLEAdvertisementHeader::serializeVersionByte(char *version_byte_write_ptr,
|
||||
Version::Value version) {
|
||||
*version_byte_write_ptr |=
|
||||
static_cast<char>((version << 5) & kVersionBitmask);
|
||||
}
|
||||
|
||||
void BLEAdvertisementHeader::serializeNumSlots(char *num_slots_byte_write_ptr,
|
||||
std::uint32_t num_slots) {
|
||||
*num_slots_byte_write_ptr |= static_cast<char>(num_slots & kNumSlotsBitmask);
|
||||
}
|
||||
|
||||
BLEAdvertisementHeader::BLEAdvertisementHeader(
|
||||
BLEAdvertisementHeader::Version::Value version, std::uint32_t num_slots,
|
||||
ConstPtr<ByteArray> service_id_bloom_filter,
|
||||
ConstPtr<ByteArray> advertisement_hash)
|
||||
: version_(version),
|
||||
num_slots_(num_slots),
|
||||
service_id_bloom_filter_(service_id_bloom_filter),
|
||||
advertisement_hash_(advertisement_hash) {}
|
||||
|
||||
BLEAdvertisementHeader::~BLEAdvertisementHeader() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
BLEAdvertisementHeader::Version::Value BLEAdvertisementHeader::getVersion()
|
||||
const {
|
||||
return version_;
|
||||
}
|
||||
|
||||
std::uint32_t BLEAdvertisementHeader::getNumSlots() const { return num_slots_; }
|
||||
|
||||
ConstPtr<ByteArray> BLEAdvertisementHeader::getServiceIdBloomFilter() const {
|
||||
return service_id_bloom_filter_.get();
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> BLEAdvertisementHeader::getAdvertisementHash() const {
|
||||
return advertisement_hash_.get();
|
||||
}
|
||||
|
||||
bool BLEAdvertisementHeader::operator<(
|
||||
const BLEAdvertisementHeader &rhs) const {
|
||||
if (this->getVersion() != rhs.getVersion()) {
|
||||
return this->getVersion() < rhs.getVersion();
|
||||
}
|
||||
if (this->getNumSlots() != rhs.getNumSlots()) {
|
||||
return this->getNumSlots() < rhs.getNumSlots();
|
||||
}
|
||||
if (*(this->getServiceIdBloomFilter()) != *(rhs.getServiceIdBloomFilter())) {
|
||||
return *(this->getServiceIdBloomFilter()) <
|
||||
*(rhs.getServiceIdBloomFilter());
|
||||
}
|
||||
return *(this->getAdvertisementHash()) < *(rhs.getAdvertisementHash());
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,91 +0,0 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
|
||||
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.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]
|
||||
//
|
||||
// See go/nearby-ble-design for more information.
|
||||
class BLEAdvertisementHeader {
|
||||
public:
|
||||
// Versions of the BLEAdvertisementHeader.
|
||||
struct Version {
|
||||
enum Value {
|
||||
V2 = 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.
|
||||
};
|
||||
};
|
||||
|
||||
static ConstPtr<BLEAdvertisementHeader> fromString(
|
||||
const std::string &ble_advertisement_header_string);
|
||||
|
||||
static std::string asString(Version::Value version, std::uint32_t num_slots,
|
||||
ConstPtr<ByteArray> service_id_bloom_filter,
|
||||
ConstPtr<ByteArray> advertisement_hash);
|
||||
|
||||
static const std::uint32_t kServiceIdBloomFilterLength;
|
||||
static const std::uint32_t kAdvertisementHashLength;
|
||||
|
||||
~BLEAdvertisementHeader();
|
||||
|
||||
Version::Value getVersion() const;
|
||||
std::uint32_t getNumSlots() const;
|
||||
ConstPtr<ByteArray> getServiceIdBloomFilter() const;
|
||||
ConstPtr<ByteArray> getAdvertisementHash() const;
|
||||
|
||||
// Operator overloads when comparing ConstPtr<BLEAdvertisementHeader>.
|
||||
bool operator<(const BLEAdvertisementHeader &rhs) const;
|
||||
|
||||
private:
|
||||
// DiscoveredPeripheralTracker needs to be a friend of this class because it
|
||||
// directly calls the constructor (the Java code keeps the constructor package
|
||||
// private).
|
||||
// Calling the constuctor directly allows us to avoid the unnessary extra
|
||||
// calls to parse and decode to get the BLEAdvertisementHeader.
|
||||
template <typename>
|
||||
friend class DiscoveredPeripheralTracker;
|
||||
|
||||
static Version::Value parseVersionFromByte(std::uint16_t byte);
|
||||
static std::uint32_t parseNumSlotsFromByte(std::uint16_t byte);
|
||||
|
||||
static const std::uint32_t kVersionAndNumSlotsLength;
|
||||
static const std::uint32_t kMinAdvertisementHeaderLength;
|
||||
static const std::uint16_t kVersionBitmask;
|
||||
static const std::uint16_t kNumSlotsBitmask;
|
||||
|
||||
BLEAdvertisementHeader(Version::Value version, std::uint32_t num_slots,
|
||||
ConstPtr<ByteArray> service_id_bloom_filter,
|
||||
ConstPtr<ByteArray> advertisement_hash);
|
||||
|
||||
static void serializeVersionByte(char *version_byte_write_ptr,
|
||||
Version::Value version);
|
||||
static void serializeNumSlots(char *num_slots_byte_write_ptr,
|
||||
std::uint32_t num_slots);
|
||||
|
||||
const Version::Value version_;
|
||||
const uint32_t num_slots_;
|
||||
ScopedPtr<ConstPtr<ByteArray> > service_id_bloom_filter_;
|
||||
ScopedPtr<ConstPtr<ByteArray> > advertisement_hash_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
|
||||
@@ -1,221 +0,0 @@
|
||||
#include "core/internal/mediums/ble_advertisement_header.h"
|
||||
|
||||
#include "platform/base64_utils.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
const BLEAdvertisementHeader::Version::Value kVersion =
|
||||
BLEAdvertisementHeader::Version::V2;
|
||||
const std::uint32_t kNumSlots = 2;
|
||||
const char kServiceIDBloomFilter[] = {0x01, 0x02, 0x03, 0x04, 0x05,
|
||||
0x06, 0x07, 0x08, 0x09, 0x0A};
|
||||
const char kAdvertisementHash[] = {0x0A, 0x0B, 0x0C, 0x0D};
|
||||
const size_t kAdvertisementHeaderLength = 15;
|
||||
const size_t kLongAdvertisementHeaderLength = kAdvertisementHeaderLength + 1;
|
||||
const size_t kShortAdvertisementHeaderLength = kAdvertisementHeaderLength - 1;
|
||||
|
||||
TEST(BLEAdvertisementHeader, SerializationDeserializationWorks) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
|
||||
new ByteArray(kServiceIDBloomFilter,
|
||||
sizeof(kServiceIDBloomFilter) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
|
||||
MakeConstPtr(new ByteArray(kAdvertisementHash,
|
||||
sizeof(kAdvertisementHash) / sizeof(char))));
|
||||
|
||||
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
|
||||
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
|
||||
scoped_advertisement_hash.get()));
|
||||
ScopedPtr<ConstPtr<BLEAdvertisementHeader> > scoped_ble_advertisement_header(
|
||||
BLEAdvertisementHeader::fromString(ble_advertisement_header_string));
|
||||
|
||||
ASSERT_EQ(kVersion, scoped_ble_advertisement_header->getVersion());
|
||||
ASSERT_EQ(kNumSlots, scoped_ble_advertisement_header->getNumSlots());
|
||||
ASSERT_EQ(
|
||||
0,
|
||||
memcmp(
|
||||
kServiceIDBloomFilter,
|
||||
scoped_ble_advertisement_header->getServiceIdBloomFilter()->getData(),
|
||||
scoped_ble_advertisement_header->getServiceIdBloomFilter()->size()));
|
||||
ASSERT_EQ(
|
||||
0,
|
||||
memcmp(kAdvertisementHash,
|
||||
scoped_ble_advertisement_header->getAdvertisementHash()->getData(),
|
||||
scoped_ble_advertisement_header->getAdvertisementHash()->size()));
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementHeader, SerializationFailsWithBadVersion) {
|
||||
BLEAdvertisementHeader::Version::Value bad_version =
|
||||
static_cast<BLEAdvertisementHeader::Version::Value>(666);
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
|
||||
new ByteArray(kServiceIDBloomFilter,
|
||||
sizeof(kServiceIDBloomFilter) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
|
||||
MakeConstPtr(new ByteArray(kAdvertisementHash,
|
||||
sizeof(kAdvertisementHash) / sizeof(char))));
|
||||
|
||||
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
|
||||
bad_version, kNumSlots, scoped_service_id_bloom_filter.get(),
|
||||
scoped_advertisement_hash.get()));
|
||||
|
||||
ASSERT_EQ("", ble_advertisement_header_string);
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementHeader, SerializationFailsWithShortServiceIdBloomFilter) {
|
||||
char short_service_id_bloom_filter[] = {0x01, 0x02, 0x03, 0x04, 0x05,
|
||||
0x06, 0x07, 0x08, 0x09};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
|
||||
new ByteArray(short_service_id_bloom_filter,
|
||||
sizeof(short_service_id_bloom_filter) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
|
||||
MakeConstPtr(new ByteArray(kAdvertisementHash,
|
||||
sizeof(kAdvertisementHash) / sizeof(char))));
|
||||
|
||||
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
|
||||
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
|
||||
scoped_advertisement_hash.get()));
|
||||
|
||||
ASSERT_EQ("", ble_advertisement_header_string);
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementHeader, SerializationFailsWithLongServiceIdBloomFilter) {
|
||||
char long_service_id_bloom_filter[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
|
||||
0x07, 0x08, 0x09, 0x0A, 0x0B};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
|
||||
new ByteArray(long_service_id_bloom_filter,
|
||||
sizeof(long_service_id_bloom_filter) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
|
||||
MakeConstPtr(new ByteArray(kAdvertisementHash,
|
||||
sizeof(kAdvertisementHash) / sizeof(char))));
|
||||
|
||||
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
|
||||
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
|
||||
scoped_advertisement_hash.get()));
|
||||
|
||||
ASSERT_EQ("", ble_advertisement_header_string);
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementHeader, SerializationFailsWithShortAdvertisementHash) {
|
||||
char short_advertisement_hash[] = {0x0A, 0x0B, 0x0C};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
|
||||
new ByteArray(kServiceIDBloomFilter,
|
||||
sizeof(kServiceIDBloomFilter) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(MakeConstPtr(
|
||||
new ByteArray(short_advertisement_hash,
|
||||
sizeof(short_advertisement_hash) / sizeof(char))));
|
||||
|
||||
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
|
||||
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
|
||||
scoped_advertisement_hash.get()));
|
||||
|
||||
ASSERT_EQ("", ble_advertisement_header_string);
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementHeader, SerializationFailsWithLongAdvertisementHash) {
|
||||
char long_advertisement_hash[] = {0x0A, 0x0B, 0x0C, 0x0D, 0x0E};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
|
||||
new ByteArray(kServiceIDBloomFilter,
|
||||
sizeof(kServiceIDBloomFilter) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(MakeConstPtr(
|
||||
new ByteArray(long_advertisement_hash,
|
||||
sizeof(long_advertisement_hash) / sizeof(char))));
|
||||
|
||||
std::string ble_advertisement_header_string(BLEAdvertisementHeader::asString(
|
||||
kVersion, kNumSlots, scoped_service_id_bloom_filter.get(),
|
||||
scoped_advertisement_hash.get()));
|
||||
|
||||
ASSERT_EQ("", ble_advertisement_header_string);
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementHeader, DeserializationWorksWithExtraBytes) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
|
||||
new ByteArray(kServiceIDBloomFilter,
|
||||
sizeof(kServiceIDBloomFilter) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
|
||||
MakeConstPtr(new ByteArray(kAdvertisementHash,
|
||||
sizeof(kAdvertisementHash) / sizeof(char))));
|
||||
std::string ble_advertisement_header_string =
|
||||
BLEAdvertisementHeader::asString(kVersion, kNumSlots,
|
||||
scoped_service_id_bloom_filter.get(),
|
||||
scoped_advertisement_hash.get());
|
||||
|
||||
// Base64 decode the string, add a character, and then re-encode it. We must
|
||||
// explicitly define how long our array is because we can't use variable
|
||||
// length arrays.
|
||||
ScopedPtr<Ptr<ByteArray> > scoped_ble_advertisement_header_bytes(
|
||||
Base64Utils::decode(ble_advertisement_header_string));
|
||||
char raw_long_ble_advertisement_header_bytes[kLongAdvertisementHeaderLength];
|
||||
memcpy(raw_long_ble_advertisement_header_bytes,
|
||||
scoped_ble_advertisement_header_bytes->getData(),
|
||||
kLongAdvertisementHeaderLength);
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_long_ble_advertisement_header_bytes(
|
||||
MakeConstPtr(new ByteArray(raw_long_ble_advertisement_header_bytes,
|
||||
kLongAdvertisementHeaderLength)));
|
||||
std::string long_ble_advertisement_header_string =
|
||||
Base64Utils::encode(scoped_long_ble_advertisement_header_bytes.get());
|
||||
|
||||
ScopedPtr<ConstPtr<BLEAdvertisementHeader> > scoped_ble_advertisement_header(
|
||||
BLEAdvertisementHeader::fromString(long_ble_advertisement_header_string));
|
||||
|
||||
ASSERT_EQ(kVersion, scoped_ble_advertisement_header->getVersion());
|
||||
ASSERT_EQ(kNumSlots, scoped_ble_advertisement_header->getNumSlots());
|
||||
ASSERT_EQ(
|
||||
0,
|
||||
memcmp(
|
||||
kServiceIDBloomFilter,
|
||||
scoped_ble_advertisement_header->getServiceIdBloomFilter()->getData(),
|
||||
scoped_ble_advertisement_header->getServiceIdBloomFilter()->size()));
|
||||
ASSERT_EQ(
|
||||
0,
|
||||
memcmp(kAdvertisementHash,
|
||||
scoped_ble_advertisement_header->getAdvertisementHash()->getData(),
|
||||
scoped_ble_advertisement_header->getAdvertisementHash()->size()));
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementHeader, DeserializationFailsWithShortLength) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_bloom_filter(MakeConstPtr(
|
||||
new ByteArray(kServiceIDBloomFilter,
|
||||
sizeof(kServiceIDBloomFilter) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_advertisement_hash(
|
||||
MakeConstPtr(new ByteArray(kAdvertisementHash,
|
||||
sizeof(kAdvertisementHash) / sizeof(char))));
|
||||
std::string ble_advertisement_header_string =
|
||||
BLEAdvertisementHeader::asString(kVersion, kNumSlots,
|
||||
scoped_service_id_bloom_filter.get(),
|
||||
scoped_advertisement_hash.get());
|
||||
|
||||
// Base64 decode the string, remove a character, and then re-encode it. We
|
||||
// must explicitly define how long our array is because we can't use variable
|
||||
// length arrays.
|
||||
ScopedPtr<Ptr<ByteArray> > scoped_ble_advertisement_header_bytes(
|
||||
Base64Utils::decode(ble_advertisement_header_string));
|
||||
char
|
||||
raw_short_ble_advertisement_header_bytes[kShortAdvertisementHeaderLength];
|
||||
memcpy(raw_short_ble_advertisement_header_bytes,
|
||||
scoped_ble_advertisement_header_bytes->getData(),
|
||||
kShortAdvertisementHeaderLength);
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_short_ble_advertisement_header_bytes(
|
||||
MakeConstPtr(new ByteArray(raw_short_ble_advertisement_header_bytes,
|
||||
kShortAdvertisementHeaderLength)));
|
||||
std::string short_ble_advertisement_header_string =
|
||||
Base64Utils::encode(scoped_short_ble_advertisement_header_bytes.get());
|
||||
|
||||
ScopedPtr<ConstPtr<BLEAdvertisementHeader> > scoped_ble_advertisement_header(
|
||||
BLEAdvertisementHeader::fromString(
|
||||
short_ble_advertisement_header_string));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement_header.isNull());
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,322 +0,0 @@
|
||||
#include "core/internal/mediums/ble_advertisement.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
const BLEAdvertisement::Version::Value kVersion = BLEAdvertisement::Version::V2;
|
||||
const BLEAdvertisement::SocketVersion::Value kSocketVersion =
|
||||
BLEAdvertisement::SocketVersion::V2;
|
||||
const char kServiceIDHashBytes[] = {0x0A, 0x0B, 0x0C};
|
||||
const char kData[] =
|
||||
"How much wood can a woodchuck chuck if a wood chuck would chuck wood?";
|
||||
// This corresponds to the length of a specific BLEAdvertisement packed with the
|
||||
// kData given above. Be sure to update this if kData ever changes.
|
||||
const size_t kAdvertisementLength = 77;
|
||||
const size_t kLongAdvertisementLength = kAdvertisementLength + 1000;
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationDeserializationWorksV1) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(
|
||||
BLEAdvertisement::Version::V1, BLEAdvertisement::SocketVersion::V1,
|
||||
scoped_service_id_hash.get(), scoped_data.get()));
|
||||
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
|
||||
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
|
||||
|
||||
ASSERT_EQ(BLEAdvertisement::Version::V1,
|
||||
scoped_ble_advertisement->getVersion());
|
||||
ASSERT_EQ(BLEAdvertisement::SocketVersion::V1,
|
||||
scoped_ble_advertisement->getSocketVersion());
|
||||
ASSERT_EQ(scoped_service_id_hash->size(),
|
||||
scoped_ble_advertisement->getServiceIdHash()->size());
|
||||
ASSERT_EQ(0, memcmp(kServiceIDHashBytes,
|
||||
scoped_ble_advertisement->getServiceIdHash()->getData(),
|
||||
scoped_ble_advertisement->getServiceIdHash()->size()));
|
||||
ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size());
|
||||
ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(),
|
||||
scoped_ble_advertisement->getData()->size()));
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationDeserializationWorks) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
|
||||
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
|
||||
|
||||
ASSERT_EQ(kVersion, scoped_ble_advertisement->getVersion());
|
||||
ASSERT_EQ(kSocketVersion, scoped_ble_advertisement->getSocketVersion());
|
||||
ASSERT_EQ(scoped_service_id_hash->size(),
|
||||
scoped_ble_advertisement->getServiceIdHash()->size());
|
||||
ASSERT_EQ(0, memcmp(kServiceIDHashBytes,
|
||||
scoped_ble_advertisement->getServiceIdHash()->getData(),
|
||||
scoped_ble_advertisement->getServiceIdHash()->size()));
|
||||
ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size());
|
||||
ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(),
|
||||
scoped_ble_advertisement->getData()->size()));
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationDeserializationWorksWithEmptyData) {
|
||||
char empty_data[0];
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(MakeConstPtr(
|
||||
new ByteArray(empty_data, sizeof(empty_data) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
|
||||
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
|
||||
|
||||
ASSERT_EQ(kVersion, scoped_ble_advertisement->getVersion());
|
||||
ASSERT_EQ(kSocketVersion, scoped_ble_advertisement->getSocketVersion());
|
||||
ASSERT_EQ(scoped_service_id_hash->size(),
|
||||
scoped_ble_advertisement->getServiceIdHash()->size());
|
||||
ASSERT_EQ(0, memcmp(kServiceIDHashBytes,
|
||||
scoped_ble_advertisement->getServiceIdHash()->getData(),
|
||||
scoped_ble_advertisement->getServiceIdHash()->size()));
|
||||
ASSERT_EQ(scoped_data->size(), scoped_ble_advertisement->getData()->size());
|
||||
ASSERT_EQ(0, memcmp(kData, scoped_ble_advertisement->getData()->getData(),
|
||||
scoped_ble_advertisement->getData()->size()));
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationDeserializationFailsWithLargeData) {
|
||||
// Create data that's larger than the allowed size.
|
||||
char large_data[513];
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(MakeConstPtr(
|
||||
new ByteArray(large_data, sizeof(large_data) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
|
||||
BLEAdvertisement::fromBytes(scoped_ble_advertisement_bytes.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationFailsWithBadVersion) {
|
||||
BLEAdvertisement::Version::Value bad_version =
|
||||
static_cast<BLEAdvertisement::Version::Value>(666);
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(bad_version, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationFailsWithBadSocketVersion) {
|
||||
BLEAdvertisement::SocketVersion::Value bad_socket_version =
|
||||
static_cast<BLEAdvertisement::SocketVersion::Value>(666);
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, bad_socket_version,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationFailsWithShortServiceIdHash) {
|
||||
char short_service_id_hash_bytes[] = {0x0A, 0x0B};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
|
||||
new ByteArray(short_service_id_hash_bytes,
|
||||
sizeof(short_service_id_hash_bytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationFailsWithLongServiceIdHash) {
|
||||
char long_service_id_hash_bytes[] = {0x0A, 0x0B, 0x0C, 0x0D};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
|
||||
new ByteArray(long_service_id_hash_bytes,
|
||||
sizeof(long_service_id_hash_bytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, SerializationFailsWithLongData) {
|
||||
// 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];
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(long_data, sizeof(long_data) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement_bytes.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, DeserializationWorksWithExtraBytes) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
|
||||
// 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, scoped_ble_advertisement_bytes->getData(),
|
||||
std::min(sizeof(raw_ble_advertisement_bytes),
|
||||
scoped_ble_advertisement_bytes->size()));
|
||||
|
||||
// Re-parse the BLE advertisement using our extra long advertisement bytes.
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_long_ble_advertisement_bytes(
|
||||
MakeConstPtr(new ByteArray(raw_ble_advertisement_bytes,
|
||||
kLongAdvertisementLength)));
|
||||
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_long_ble_advertisement(
|
||||
BLEAdvertisement::fromBytes(scoped_long_ble_advertisement_bytes.get()));
|
||||
|
||||
ASSERT_EQ(kVersion, scoped_long_ble_advertisement->getVersion());
|
||||
ASSERT_EQ(kSocketVersion, scoped_long_ble_advertisement->getSocketVersion());
|
||||
ASSERT_EQ(scoped_service_id_hash->size(),
|
||||
scoped_long_ble_advertisement->getServiceIdHash()->size());
|
||||
ASSERT_EQ(0,
|
||||
memcmp(kServiceIDHashBytes,
|
||||
scoped_long_ble_advertisement->getServiceIdHash()->getData(),
|
||||
scoped_long_ble_advertisement->getServiceIdHash()->size()));
|
||||
ASSERT_EQ(scoped_data->size(),
|
||||
scoped_long_ble_advertisement->getData()->size());
|
||||
ASSERT_EQ(0,
|
||||
memcmp(kData, scoped_long_ble_advertisement->getData()->getData(),
|
||||
scoped_long_ble_advertisement->getData()->size()));
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, DeserializationFailsWithNullBytes) {
|
||||
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
|
||||
BLEAdvertisement::fromBytes(ConstPtr<ByteArray>()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, DeserializationFailsWithShortLength) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
|
||||
// Cut off the advertisement so that it's too short.
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_short_ble_advertisement_bytes(
|
||||
MakeConstPtr(
|
||||
new ByteArray(scoped_ble_advertisement_bytes->getData(), 7)));
|
||||
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
|
||||
BLEAdvertisement::fromBytes(scoped_short_ble_advertisement_bytes.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEAdvertisementTest, DeserializationFailsWithInvalidDataLength) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(kServiceIDHashBytes,
|
||||
sizeof(kServiceIDHashBytes) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(kVersion, kSocketVersion,
|
||||
scoped_service_id_hash.get(),
|
||||
scoped_data.get()));
|
||||
|
||||
// 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, scoped_ble_advertisement_bytes->getData(),
|
||||
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.
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_corrupted_ble_advertisement_bytes(
|
||||
MakeConstPtr(
|
||||
new ByteArray(raw_ble_advertisement_bytes, kAdvertisementLength)));
|
||||
ScopedPtr<ConstPtr<BLEAdvertisement> > scoped_ble_advertisement(
|
||||
BLEAdvertisement::fromBytes(
|
||||
scoped_corrupted_ble_advertisement_bytes.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_advertisement.isNull());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,112 +0,0 @@
|
||||
#include "core/internal/mediums/ble_packet.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "platform/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
const std::uint32_t BLEPacket::kServiceIdHashLength = 3;
|
||||
|
||||
const std::uint32_t BLEPacket::kMinPacketLength = kServiceIdHashLength;
|
||||
const std::uint32_t BLEPacket::kMaxDataSize =
|
||||
std::numeric_limits<int32_t>::max() - kMinPacketLength;
|
||||
|
||||
ConstPtr<BLEPacket> BLEPacket::fromBytes(ConstPtr<ByteArray> ble_packet_bytes) {
|
||||
if (ble_packet_bytes.isNull()) {
|
||||
NEARBY_LOG(INFO, "Cannot deserialize BLEPacket: null bytes passed in");
|
||||
return ConstPtr<BLEPacket>();
|
||||
}
|
||||
|
||||
if (ble_packet_bytes->size() < kMinPacketLength) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BLEPacket: expecting min %u raw bytes, got %zu",
|
||||
kMinPacketLength, ble_packet_bytes->size());
|
||||
return ConstPtr<BLEPacket>();
|
||||
}
|
||||
|
||||
// Now, time to read the bytes!
|
||||
const char *ble_packet_bytes_read_ptr = ble_packet_bytes->getData();
|
||||
|
||||
// 1. Service ID hash.
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
|
||||
new ByteArray(ble_packet_bytes_read_ptr, kServiceIdHashLength)));
|
||||
ble_packet_bytes_read_ptr += kServiceIdHashLength;
|
||||
|
||||
// 2. Data.
|
||||
size_t data_size = computeDataSize(ble_packet_bytes);
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(ble_packet_bytes_read_ptr, data_size)));
|
||||
ble_packet_bytes_read_ptr += data_size;
|
||||
|
||||
return MakeConstPtr(
|
||||
new BLEPacket(scoped_service_id_hash.release(), scoped_data.release()));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> BLEPacket::toBytes(ConstPtr<ByteArray> service_id_hash,
|
||||
ConstPtr<ByteArray> data) {
|
||||
if (service_id_hash->size() != kServiceIdHashLength) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot serialize BLEPacket: expected a service_id_hash of %u bytes, "
|
||||
"but got %zu",
|
||||
kServiceIdHashLength, service_id_hash->size());
|
||||
return ConstPtr<ByteArray>();
|
||||
}
|
||||
|
||||
if (data->size() > kMaxDataSize) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot serialize BLEPacket: expected data of at most %u bytes, "
|
||||
"but got %zu",
|
||||
kMaxDataSize, data->size());
|
||||
return ConstPtr<ByteArray>();
|
||||
}
|
||||
|
||||
// Initialize the bytes.
|
||||
size_t packet_length = computePacketLength(data);
|
||||
Ptr<ByteArray> packet_bytes{new ByteArray{packet_length}};
|
||||
char *packet_bytes_write_ptr = packet_bytes->getData();
|
||||
|
||||
// 1. Service ID hash.
|
||||
memcpy(packet_bytes_write_ptr, service_id_hash->getData(),
|
||||
kServiceIdHashLength);
|
||||
packet_bytes_write_ptr += kServiceIdHashLength;
|
||||
|
||||
// 2. Data.
|
||||
memcpy(packet_bytes_write_ptr, data->getData(), data->size());
|
||||
packet_bytes_write_ptr += data->size();
|
||||
|
||||
return ConstifyPtr(packet_bytes);
|
||||
}
|
||||
|
||||
size_t BLEPacket::computeDataSize(ConstPtr<ByteArray> ble_packet_bytes) {
|
||||
return ble_packet_bytes->size() - kMinPacketLength;
|
||||
}
|
||||
|
||||
size_t BLEPacket::computePacketLength(ConstPtr<ByteArray> data) {
|
||||
// The packet length is the minimum length + the length of the data.
|
||||
return kMinPacketLength + data->size();
|
||||
}
|
||||
|
||||
BLEPacket::BLEPacket(ConstPtr<ByteArray> service_id_hash,
|
||||
ConstPtr<ByteArray> data)
|
||||
: service_id_hash_(service_id_hash), data_(data) {}
|
||||
|
||||
BLEPacket::~BLEPacket() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> BLEPacket::getServiceIdHash() const {
|
||||
return service_id_hash_.get();
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> BLEPacket::getData() const { return data_.get(); }
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,81 +0,0 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_
|
||||
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.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 ConstPtr<BLEPacket> fromBytes(ConstPtr<ByteArray> ble_packet_bytes);
|
||||
|
||||
static ConstPtr<ByteArray> toBytes(ConstPtr<ByteArray> service_id_hash,
|
||||
ConstPtr<ByteArray> data);
|
||||
|
||||
static const std::uint32_t kServiceIdHashLength;
|
||||
|
||||
~BLEPacket();
|
||||
|
||||
ConstPtr<ByteArray> getServiceIdHash() const;
|
||||
ConstPtr<ByteArray> getData() const;
|
||||
|
||||
private:
|
||||
static size_t computeDataSize(ConstPtr<ByteArray> ble_packet_bytes);
|
||||
static size_t computePacketLength(ConstPtr<ByteArray> data);
|
||||
|
||||
static const std::uint32_t kMinPacketLength;
|
||||
static const std::uint32_t kMaxDataSize;
|
||||
|
||||
BLEPacket(ConstPtr<ByteArray> service_id_hash, ConstPtr<ByteArray> data);
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > service_id_hash_;
|
||||
ScopedPtr<ConstPtr<ByteArray> > data_;
|
||||
};
|
||||
|
||||
// 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 BlePacket FromBytes(const ByteArray& bytes);
|
||||
|
||||
static ByteArray ToBytes(const ByteArray& service_id_hash,
|
||||
const ByteArray& data);
|
||||
|
||||
static const uint32_t kServiceIdHashLength;
|
||||
|
||||
~BlePacket();
|
||||
|
||||
ByteArray GetServiceIdHash() const;
|
||||
ByteArray GetData() const;
|
||||
|
||||
private:
|
||||
static size_t ComputeDataSize(const ByteArray& ble_packet_bytes);
|
||||
static size_t ComputePacketLength(const ByteArray& data);
|
||||
|
||||
static const uint32_t kMinPacketLength;
|
||||
static const uint32_t kMaxDataSize;
|
||||
|
||||
BlePacket(const ByteArray& service_id_hash, const ByteArray& data);
|
||||
|
||||
ByteArray service_id_hash_;
|
||||
ByteArray data_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_PACKET_H_
|
||||
@@ -1,108 +0,0 @@
|
||||
#include "core/internal/mediums/ble_packet.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
const char kServiceIDHash[] = {0x0A, 0x0B, 0x0C};
|
||||
const char kData[] = {0x00, 0x01, 0x02, 0x03, 0x04};
|
||||
|
||||
TEST(BLEPacket, SerializationDeserializationWorks) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
|
||||
new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
|
||||
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
|
||||
ScopedPtr<ConstPtr<BLEPacket> > scoped_ble_packet(
|
||||
BLEPacket::fromBytes(scoped_ble_packet_bytes.get()));
|
||||
|
||||
ASSERT_EQ(0, memcmp(kServiceIDHash,
|
||||
scoped_ble_packet->getServiceIdHash()->getData(),
|
||||
scoped_ble_packet->getServiceIdHash()->size()));
|
||||
ASSERT_EQ(0, memcmp(kData, scoped_ble_packet->getData()->getData(),
|
||||
scoped_ble_packet->getData()->size()));
|
||||
}
|
||||
|
||||
TEST(BLEPacket, SerializationDeserializationWorksWithEmptyData) {
|
||||
char empty_data[] = {};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
|
||||
new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(MakeConstPtr(
|
||||
new ByteArray(empty_data, sizeof(empty_data) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
|
||||
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
|
||||
ScopedPtr<ConstPtr<BLEPacket> > scoped_ble_packet(
|
||||
BLEPacket::fromBytes(scoped_ble_packet_bytes.get()));
|
||||
|
||||
ASSERT_EQ(0, memcmp(kServiceIDHash,
|
||||
scoped_ble_packet->getServiceIdHash()->getData(),
|
||||
scoped_ble_packet->getServiceIdHash()->size()));
|
||||
ASSERT_EQ(0, memcmp(empty_data, scoped_ble_packet->getData()->getData(),
|
||||
scoped_ble_packet->getData()->size()));
|
||||
}
|
||||
|
||||
TEST(BLEPacket, SerializationFailsWithShortServiceIdHash) {
|
||||
char short_service_id_hash[] = {0x0A, 0x0B};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
|
||||
new ByteArray(short_service_id_hash,
|
||||
sizeof(short_service_id_hash) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
|
||||
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_packet_bytes.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEPacket, SerializationFailsWithLongServiceIdHash) {
|
||||
char long_service_id_hash[]{0x0A, 0x0B, 0x0C, 0x0D};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(
|
||||
MakeConstPtr(new ByteArray(long_service_id_hash,
|
||||
sizeof(long_service_id_hash) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
|
||||
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_packet_bytes.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEPacket, DeserializationFailsWithNullBytes) {
|
||||
ScopedPtr<ConstPtr<BLEPacket> > scoped_ble_packet(
|
||||
BLEPacket::fromBytes(ConstPtr<ByteArray>()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_packet.isNull());
|
||||
}
|
||||
|
||||
TEST(BLEPacket, DeserializationFailsWithShortLength) {
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_service_id_hash(MakeConstPtr(
|
||||
new ByteArray(kServiceIDHash, sizeof(kServiceIDHash) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_data(
|
||||
MakeConstPtr(new ByteArray(kData, sizeof(kData) / sizeof(char))));
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_ble_packet_bytes(
|
||||
BLEPacket::toBytes(scoped_service_id_hash.get(), scoped_data.get()));
|
||||
|
||||
// Cut off the packet so that it's too short
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_short_ble_packet_bytes(
|
||||
MakeConstPtr(new ByteArray(scoped_ble_packet_bytes->getData(), 2)));
|
||||
ScopedPtr<ConstPtr<BLEPacket> > scoped_ble_packet(
|
||||
BLEPacket::fromBytes(scoped_short_ble_packet_bytes.get()));
|
||||
|
||||
ASSERT_TRUE(scoped_ble_packet.isNull());
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,19 +0,0 @@
|
||||
#include "core/internal/mediums/ble_peripheral.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BLEPeripheral::BLEPeripheral(ConstPtr<ByteArray> id) : id_(id) {}
|
||||
|
||||
BLEPeripheral::~BLEPeripheral() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> BLEPeripheral::getId() const { return id_.get(); }
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,45 +0,0 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
|
||||
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class BLEPeripheral {
|
||||
public:
|
||||
explicit BLEPeripheral(ConstPtr<ByteArray> id);
|
||||
~BLEPeripheral();
|
||||
|
||||
ConstPtr<ByteArray> getId() const;
|
||||
|
||||
private:
|
||||
// A unique identifier for this peripheral. It can be the BLE advertisement it
|
||||
// was found on, or even simply the BLE MAC address.
|
||||
ScopedPtr<ConstPtr<ByteArray>> id_;
|
||||
};
|
||||
|
||||
|
||||
// Represents BLE peripheral for testing.
|
||||
class BlePeripheral {
|
||||
public:
|
||||
explicit BlePeripheral(const ByteArray& id) : id_(id) {}
|
||||
~BlePeripheral() = default;
|
||||
|
||||
const 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.
|
||||
const ByteArray id_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
|
||||
@@ -0,0 +1,175 @@
|
||||
#include "core/internal/mediums/ble.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#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"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
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::Test {
|
||||
protected:
|
||||
using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback;
|
||||
|
||||
BleTest() { env_.Stop(); }
|
||||
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
TEST_F(BleTest, CanStartAcceptingConnectionsAndConnect) {
|
||||
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());
|
||||
|
||||
BleSocket socket =
|
||||
ble_b.Connect(discovered_peripheral, service_id);
|
||||
|
||||
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(socket.IsValid());
|
||||
ble_b.StopScanning(service_id);
|
||||
ble_a.StopAdvertising(service_id);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,826 +0,0 @@
|
||||
#include "core/internal/mediums/ble.h"
|
||||
#include "core/internal/mediums/ble_advertisement_header.h"
|
||||
#include "core/internal/mediums/bloom_filter.h"
|
||||
#include "core/internal/mediums/utils.h"
|
||||
#include "core/internal/mediums/uuid.h"
|
||||
#include "platform/synchronized.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace ble_v2 {
|
||||
|
||||
template <typename Platform>
|
||||
class ProcessOnLostRunnable : public Runnable {
|
||||
public:
|
||||
explicit ProcessOnLostRunnable(Ptr<BLEV2<Platform>> ble_v2)
|
||||
: ble_v2_(ble_v2) {}
|
||||
|
||||
void run() override { ble_v2_->processOnLostTimeout(); }
|
||||
|
||||
private:
|
||||
Ptr<BLEV2<Platform>> ble_v2_;
|
||||
};
|
||||
|
||||
template <typename Platform>
|
||||
class OnAdvertisementFoundRunnable : public Runnable {
|
||||
public:
|
||||
OnAdvertisementFoundRunnable(
|
||||
Ptr<BLEV2<Platform>> ble_v2, Ptr<BLEPeripheralV2> peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data)
|
||||
: ble_v2_(ble_v2),
|
||||
peripheral_(peripheral),
|
||||
advertisement_data_(advertisement_data) {}
|
||||
|
||||
// This method is synchronized because it affects class state, but is called
|
||||
// from a separate thread that fires whenever a BLE advertisement is seen.
|
||||
void run() override {
|
||||
Synchronized s(ble_v2_->lock_.get());
|
||||
|
||||
ble_v2_->discovered_peripheral_tracker_->processFoundBleAdvertisement(
|
||||
peripheral_, advertisement_data_.release(),
|
||||
MakePtr(new typename BLEV2<Platform>::GATTAdvertisementFetcherFacade(
|
||||
ble_v2_)));
|
||||
}
|
||||
|
||||
private:
|
||||
Ptr<BLEV2<Platform>> ble_v2_;
|
||||
Ptr<BLEPeripheralV2> peripheral_;
|
||||
ScopedPtr<ConstPtr<BLEAdvertisementData>> advertisement_data_;
|
||||
};
|
||||
|
||||
} // namespace ble_v2
|
||||
|
||||
template <typename Platform>
|
||||
const std::int32_t BLEV2<Platform>::kNumAdvertisementSlots = 2;
|
||||
|
||||
template <typename Platform>
|
||||
const std::int32_t BLEV2<Platform>::kMaxAdvertisementLength = 512;
|
||||
|
||||
template <typename Platform>
|
||||
const std::int32_t BLEV2<Platform>::kDummyServiceIdLength = 512;
|
||||
|
||||
template <typename Platform>
|
||||
const char* BLEV2<Platform>::kCopresenceServiceUuid =
|
||||
"0000FEF3-0000-1000-8000-00805F9B34FB";
|
||||
|
||||
template <typename Platform>
|
||||
const std::int64_t BLEV2<Platform>::kOnLostTimeoutMillis = 15000;
|
||||
|
||||
template <typename Platform>
|
||||
const std::int64_t BLEV2<Platform>::kGattAdvertisementOperationTimeoutMillis =
|
||||
5000;
|
||||
|
||||
template <typename Platform>
|
||||
const std::int64_t
|
||||
BLEV2<Platform>::kMinConnectionAttemptRecoveryDurationMillis = 1000;
|
||||
|
||||
template <typename Platform>
|
||||
const std::int32_t
|
||||
BLEV2<Platform>::kMaxConnectionAttemptRecoveryFuzzDurationMillis = 10000;
|
||||
|
||||
template <typename Platform>
|
||||
const std::uint32_t BLEV2<Platform>::kDefaultMtu = 512;
|
||||
|
||||
// These two values make up the base UUID we use when advertising a slot. The
|
||||
// base is an all zero Version-3 name-based UUID. To turn this into an
|
||||
// advertisement slot UUID, we simply OR the least significant bits with the
|
||||
// slot number.
|
||||
//
|
||||
// More info about the format can be found here:
|
||||
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)
|
||||
template <typename Platform>
|
||||
const std::int64_t BLEV2<Platform>::kAdvertisementUuidMsb = 0x0000000000003000;
|
||||
|
||||
template <typename Platform>
|
||||
const std::int64_t BLEV2<Platform>::kAdvertisementUuidLsb = 0x8000000000000000;
|
||||
|
||||
template <typename Platform>
|
||||
BLEV2<Platform>::BLEV2(Ptr<BluetoothRadio<Platform>> bluetooth_radio)
|
||||
: lock_(Platform::createLock()),
|
||||
platform_thread_offloader_(Platform::createSingleThreadExecutor()),
|
||||
prng_(MakePtr(new Prng())),
|
||||
hash_utils_(Platform::createHashUtils()),
|
||||
bluetooth_radio_(bluetooth_radio),
|
||||
bluetooth_adapter_(Platform::createBluetoothAdapter()),
|
||||
ble_medium_(Platform::createBLEMediumV2()),
|
||||
scanning_info_(),
|
||||
discovered_peripheral_tracker_(
|
||||
new DiscoveredPeripheralTracker<Platform>()),
|
||||
on_lost_executor_(Platform::createScheduledExecutor()),
|
||||
advertising_info_(),
|
||||
gatt_server_info_(),
|
||||
accepting_connections_info_() {}
|
||||
|
||||
template <typename Platform>
|
||||
BLEV2<Platform>::~BLEV2() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
on_lost_executor_->shutdown();
|
||||
platform_thread_offloader_->shutdown();
|
||||
stopAdvertising();
|
||||
stopAdvertisementGattServer();
|
||||
stopAcceptingConnections();
|
||||
stopScanning();
|
||||
// discovered_peripheral_tracker is a ScopedPtr member and will take care of
|
||||
// itself.
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::isAvailable() {
|
||||
// This is purposefully left un-synchronized like its java counterpart.
|
||||
// Callers should be able to query this without waiting for other operations
|
||||
// to complete first and this should be safe to call after shutdown. We would
|
||||
// have made it static, but it relies on variables from the constructor (like
|
||||
// ble_medium_ and bluetooth_adapter_).
|
||||
return !ble_medium_.isNull() && !bluetooth_adapter_.isNull();
|
||||
}
|
||||
|
||||
// Returns true if currently scanning for BLE advertisements.
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::isAdvertising() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !advertising_info_.isNull();
|
||||
}
|
||||
|
||||
// Starts BLE advertising, delivering additional information through a GATT
|
||||
// server.
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::startAdvertising(
|
||||
const string& service_id, ConstPtr<ByteArray> advertisement_bytes,
|
||||
BLEMediumV2::PowerMode::Value power_mode,
|
||||
const string& fast_advertisement_service_uuid) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_advertisement_bytes(
|
||||
advertisement_bytes);
|
||||
|
||||
if (service_id.empty() || scoped_advertisement_bytes.isNull()) {
|
||||
// logger.atSevere().log("Refusing to start BLE advertising because a null
|
||||
// parameter was passed in.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (scoped_advertisement_bytes->size() > kMaxAdvertisementLength) {
|
||||
// logger.atSevere().log("Refusing to start BLE advertising because the
|
||||
// advertisement was too long. Expected at most %d bytes but received %d.",
|
||||
// kMaxAdvertisementLength, scoped_advertisement_bytes->size());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: We don't include logic checking/using the fast_pair_model_id because
|
||||
// that is a java-only concept for now.
|
||||
|
||||
if (isAdvertising()) {
|
||||
// logger.atSevere().log("Failed to BLE advertise because we're already
|
||||
// advertising.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// logger.atSevere().log("Can't start BLE advertising because Bluetooth
|
||||
// isn't enabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// logger.atSevere().log("Can't start BLE advertising because BLE is not
|
||||
// available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(ahlee): Remove this check here and in the java code (redundant)
|
||||
// Stop any existing advertisement GATT servers. We don't stop it in
|
||||
// stopAdvertising() to avoid GATT issues with BLE sockets.
|
||||
if (isAdvertisementGattServerRunning()) {
|
||||
stopAdvertisementGattServer();
|
||||
}
|
||||
|
||||
// Start a GATT server to deliver the full advertisement data. If we fail to
|
||||
// advertise the header, we must shut this down before the method returns.
|
||||
bool is_fast_advertisement = !fast_advertisement_service_uuid.empty();
|
||||
if (!is_fast_advertisement) {
|
||||
if (!startAdvertisementGattServer(service_id,
|
||||
scoped_advertisement_bytes.get())) {
|
||||
// logger.atSevere().log("Failed to to BLE advertise because the
|
||||
// advertisement GATT server failed to start");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray>> advertisement_header_bytes(
|
||||
createAdvertisementHeader(service_id, scoped_advertisement_bytes.get(),
|
||||
is_fast_advertisement));
|
||||
if (advertisement_header_bytes.isNull()) {
|
||||
// logger.atSevere().log("Failed to to BLE advertise because we could not
|
||||
// create an advertisement header");
|
||||
// We failed to start BLE advertising, so stop the advertisement GATT
|
||||
// server.
|
||||
stopAdvertisementGattServer();
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedPtr<Ptr<BLEAdvertisementData>> advertisement(
|
||||
new BLEAdvertisementData());
|
||||
advertisement->is_connectable = true;
|
||||
advertisement->tx_power_level =
|
||||
BLEAdvertisementData::UNSPECIFIED_TX_POWER_LEVEL;
|
||||
|
||||
ScopedPtr<Ptr<BLEAdvertisementData>> scan_response(
|
||||
new BLEAdvertisementData());
|
||||
scan_response->is_connectable = true;
|
||||
scan_response->tx_power_level =
|
||||
BLEAdvertisementData::UNSPECIFIED_TX_POWER_LEVEL;
|
||||
scan_response->service_uuids.insert(kCopresenceServiceUuid);
|
||||
scan_response->service_data.insert(std::make_pair(
|
||||
kCopresenceServiceUuid, advertisement_header_bytes.release()));
|
||||
|
||||
// Note: We don't use fast pair data because that is java-only for now.
|
||||
|
||||
// TODO(ahlee): Fix this if check in the java code.
|
||||
if (is_fast_advertisement) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> service_id_hash(
|
||||
generateServiceIdHash(BLEAdvertisement::Version::V2, service_id));
|
||||
ScopedPtr<ConstPtr<ByteArray>> fast_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(
|
||||
BLEAdvertisement::Version::V2, BLEAdvertisement::SocketVersion::V2,
|
||||
service_id_hash.get(), scoped_advertisement_bytes.get()));
|
||||
if (fast_advertisement_bytes.isNull()) {
|
||||
// logger.atSevere().log("Failed to BLE advertise because we could not
|
||||
// create a fast advertisement for service UUID %s.",
|
||||
// fast_advertisement_service_uuid);
|
||||
|
||||
// We shouldn't have started an advertisement GATT server in the first
|
||||
// place if we are using fast advertisements. However, to avoid careless
|
||||
// leaks, try shutting down the server anyway.
|
||||
stopAdvertisementGattServer();
|
||||
return false;
|
||||
}
|
||||
advertisement->service_data.insert(std::make_pair(
|
||||
fast_advertisement_service_uuid, fast_advertisement_bytes.release()));
|
||||
scan_response->service_uuids.insert(fast_advertisement_service_uuid);
|
||||
}
|
||||
|
||||
if (!ble_medium_->startAdvertising(ConstifyPtr(advertisement.release()),
|
||||
ConstifyPtr(scan_response.release()),
|
||||
power_mode)) {
|
||||
// If BLE advertising was not successful, stop the advertisement GATT
|
||||
// server.
|
||||
stopAdvertisementGattServer();
|
||||
return false;
|
||||
}
|
||||
|
||||
// logger.atVerbose().flog("Started BLE advertising with advertisement %s for
|
||||
// serviceID %s.", advertisement_header, service_id);
|
||||
advertising_info_ = MakePtr(new AdvertisingInfo(service_id));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
ConstPtr<ByteArray> BLEV2<Platform>::createAdvertisementHeader(
|
||||
const string& service_id, ConstPtr<ByteArray> advertisement_bytes,
|
||||
bool is_fast_advertisement) {
|
||||
// Create a randomized dummy service ID to anonymize our header with.
|
||||
string dummy_service_id;
|
||||
dummy_service_id.reserve(kDummyServiceIdLength);
|
||||
for (int i = 0; i < kDummyServiceIdLength; i++) {
|
||||
dummy_service_id[i] = static_cast<char>(prng_->nextInt32() & 0x000000FF);
|
||||
}
|
||||
|
||||
// Put the service ID along with the dummy service ID into our bloom filter
|
||||
// Note: BloomFilter length should always match
|
||||
// BLEAdvertisementHeader::kServiceIdBloomFilterLength
|
||||
ScopedPtr<Ptr<BloomFilter<10>>> bloom_filter(new BloomFilter<10>());
|
||||
bloom_filter->add(dummy_service_id);
|
||||
|
||||
// Only add the service ID to our bloom filter if it's not a fast
|
||||
// advertisement. Fast advertisements want discoverers to avoid reading our
|
||||
// GATT advertisement.
|
||||
if (!is_fast_advertisement) {
|
||||
bloom_filter->add(service_id);
|
||||
}
|
||||
|
||||
// Create a hash seeded from dummy_service_id + advertisementBytes
|
||||
//
|
||||
// First, populate advertisement_bodies with the dummy_service_id and
|
||||
// advertisement_bytes.
|
||||
string advertisement_bodies;
|
||||
advertisement_bodies.reserve(dummy_service_id.size() +
|
||||
advertisement_bytes->size());
|
||||
advertisement_bodies.append(dummy_service_id.data(), dummy_service_id.size());
|
||||
advertisement_bodies.append(advertisement_bytes->getData(),
|
||||
advertisement_bytes->size());
|
||||
|
||||
// Then, generate the advertisement hash from the populated
|
||||
// advertisement_bodies string.
|
||||
ScopedPtr<ConstPtr<ByteArray>> advertisement_bodies_byte_array(MakeConstPtr(
|
||||
new ByteArray(advertisement_bodies.data(), advertisement_bodies.size())));
|
||||
ScopedPtr<ConstPtr<ByteArray>> advertisement_hash(
|
||||
generateAdvertisementHash(advertisement_bodies_byte_array.get()));
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray>> bloom_filter_bytes(bloom_filter->asBytes());
|
||||
string ble_advertisement_header_string = BLEAdvertisementHeader::asString(
|
||||
BLEAdvertisementHeader::Version::V2, kNumAdvertisementSlots,
|
||||
bloom_filter_bytes.get(), advertisement_hash.get());
|
||||
|
||||
return MakeConstPtr(new ByteArray(ble_advertisement_header_string.data(),
|
||||
ble_advertisement_header_string.size()));
|
||||
}
|
||||
|
||||
// Stops BLE advertising.
|
||||
template <typename Platform>
|
||||
void BLEV2<Platform>::stopAdvertising() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!isAdvertising()) {
|
||||
// logger.atDebug().log("Can't turn off BLE advertising because it never
|
||||
// started.");
|
||||
return;
|
||||
}
|
||||
|
||||
ble_medium_->stopAdvertising();
|
||||
// Reset advertising_info_to mark that we're no longer advertising.
|
||||
advertising_info_.destroy();
|
||||
|
||||
// Do NOT stop the advertisement GATT server here. Doing so will cause any
|
||||
// other existing GATT connections to stop receiving callbacks. This affects
|
||||
// our BLE sockets. Therefore, we only stop it in shutdown() and
|
||||
// startAdvertising(), where it is safe to do so. At those two points, we
|
||||
// shouldn't expect any BLE sockets to be connected.
|
||||
|
||||
// logger.atVerbose().log("Turned BLE advertising off");
|
||||
}
|
||||
|
||||
// Returns true if currently scanning for BLE advertisements.
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::isScanning() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !scanning_info_.isNull();
|
||||
}
|
||||
|
||||
// Starts scanning for BLE advertisements (if it is possible for the device).
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::startScanning(
|
||||
const string& service_id,
|
||||
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback,
|
||||
BLEMediumV2::PowerMode::Value power_mode,
|
||||
const string& fast_advertisement_service_uuid) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<DiscoveredPeripheralCallback>>
|
||||
scoped_discovered_peripheral_callback(discovered_peripheral_callback);
|
||||
|
||||
if (service_id.empty() || scoped_discovered_peripheral_callback.isNull()) {
|
||||
// logger.atSevere().log("Refusing to start BLE scanning because at least
|
||||
// one of workSource, serviceId, or discoveredPeripheralCallback is null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isScanning()) {
|
||||
// logger.atSevere().log("Refusing to start BLE scanning because we are
|
||||
// already scanning.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// logger.atSevere().log("Can't start BLE scanning because Bluetooth was
|
||||
// never turned on");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// logger.atSevere().log("Can't start BLE scanning because BLE is not
|
||||
// available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
discovered_peripheral_tracker_->startTracking(
|
||||
service_id, scoped_discovered_peripheral_callback.release(),
|
||||
fast_advertisement_service_uuid);
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<ScanCallbackFacade>> scan_callback_facade(
|
||||
new ScanCallbackFacade(self_));
|
||||
std::set<string> service_uuids;
|
||||
service_uuids.insert(kCopresenceServiceUuid);
|
||||
if (!ble_medium_->startScanning(service_uuids, power_mode,
|
||||
scan_callback_facade.get())) {
|
||||
discovered_peripheral_tracker_->stopTracking(service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// logger.atVerbose().log("Started BLE scanning for serviceID %s.",
|
||||
// service_id);
|
||||
scanning_info_ = MakePtr(new ScanningInfo(
|
||||
service_id, scan_callback_facade.release(), createOnLostAlarm()));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BLEV2<Platform>::onAdvertisementFoundImpl(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data) {
|
||||
offloadFromPlatformThread(
|
||||
MakePtr(new ble_v2::OnAdvertisementFoundRunnable<Platform>(
|
||||
self_, ble_peripheral, advertisement_data)));
|
||||
}
|
||||
|
||||
// This method is synchronized because it affects class state, but is called
|
||||
// from a separate thread that has a recurring alarm running on it.
|
||||
template <typename Platform>
|
||||
void BLEV2<Platform>::processOnLostTimeout() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
discovered_peripheral_tracker_->processLostGattAdvertisements();
|
||||
}
|
||||
|
||||
// Stops scanning for BLE advertisements.
|
||||
template <typename Platform>
|
||||
void BLEV2<Platform>::stopScanning() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!isScanning()) {
|
||||
// logger.atDebug().log("Can't turn off BLE scanning because we never
|
||||
// started scanning.");
|
||||
return;
|
||||
}
|
||||
|
||||
scanning_info_->on_lost_alarm->cancel();
|
||||
|
||||
ble_medium_->stopScanning();
|
||||
discovered_peripheral_tracker_->stopTracking(scanning_info_->service_id);
|
||||
// Reset our bundle of scanning state to mark that we're no longer scanning.
|
||||
scanning_info_.destroy();
|
||||
}
|
||||
|
||||
// TODO(b/112199086) Change to RecurringCancelableAlarm
|
||||
template <typename Platform>
|
||||
Ptr<CancelableAlarm> BLEV2<Platform>::createOnLostAlarm() {
|
||||
return Ptr<CancelableAlarm>();
|
||||
}
|
||||
|
||||
// Returns true if the device is currently accepting incoming BLE socket
|
||||
// connections.
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::isAcceptingConnections() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !accepting_connections_info_.isNull();
|
||||
}
|
||||
|
||||
// Starts accepting incoming BLE socket connections.
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::startAcceptingConnections(
|
||||
const string& service_id,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<AcceptedConnectionCallback>>
|
||||
scoped_accepted_connection_callback(accepted_connection_callback);
|
||||
if (service_id.empty() || scoped_accepted_connection_callback.isNull()) {
|
||||
// logger.atSevere().log("Refusing to start accepting BLE connections
|
||||
// because at least one of serviceId or acceptedConnectionCallback is
|
||||
// null.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAcceptingConnections()) {
|
||||
// logger.atSevere().log("Refusing to start accepting BLE connections for %s
|
||||
// because another BLE server socket is already in-progress.", service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// logger.atSevere().log("Can't start accepting BLE connections for %s
|
||||
// because Bluetooth isn't enabled.", service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// logger.atSevere().log("Can't start accepting BLE connections for %s
|
||||
// because BLE is not available.", service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(ahlee): Implement w/ the rest of the connecting logic.
|
||||
// Default to returning true and creating accepting_connections_info_ so we
|
||||
// can test the advertising and discovery flow fully.
|
||||
accepting_connections_info_ =
|
||||
MakePtr(new AcceptingConnectionsInfo(service_id));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stops accepting incoming BLE socket connections.
|
||||
template <typename Platform>
|
||||
void BLEV2<Platform>::stopAcceptingConnections() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!isAcceptingConnections()) {
|
||||
// logger.atDebug().log("Can't stop accepting BLE connections because it was
|
||||
// never started.");
|
||||
return;
|
||||
}
|
||||
|
||||
ble_medium_->stopListeningForIncomingBLESockets();
|
||||
|
||||
// Reset our bundle of accepting connections state to mark that we're no
|
||||
// longer accepting connections.
|
||||
accepting_connections_info_.destroy();
|
||||
}
|
||||
|
||||
// Note: getGattConnectionBackoffPeriodMillis is only used in the java version
|
||||
// of reliablyConnect() for now.
|
||||
|
||||
// Returns true if the advertisement GATT server is currently running.
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::isAdvertisementGattServerRunning() {
|
||||
return !gatt_server_info_.isNull();
|
||||
}
|
||||
|
||||
// Starts a GATT server to deliver additional advertisement data. Returns true
|
||||
// if the server was started successfully.
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::startAdvertisementGattServer(
|
||||
const string& service_id, ConstPtr<ByteArray> advertisement) {
|
||||
// advertisement is not being wrapped in a ScopedPtr because ownership is not
|
||||
// passed on from startAdvertising().
|
||||
|
||||
if (isAdvertisementGattServerRunning()) {
|
||||
// logger.atSevere().log("Refusing to start an advertisement GATT server
|
||||
// because one is already running.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a BleAdvertisement to wrap over the passed in advertisement.
|
||||
ScopedPtr<ConstPtr<ByteArray>> legacy_service_id_hash(
|
||||
generateServiceIdHash(BLEAdvertisement::Version::V1, service_id));
|
||||
ScopedPtr<ConstPtr<ByteArray>> legacy_ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(BLEAdvertisement::Version::V1,
|
||||
BLEAdvertisement::SocketVersion::V1,
|
||||
legacy_service_id_hash.get(), advertisement));
|
||||
if (legacy_ble_advertisement_bytes.isNull()) {
|
||||
// logger.atSevere().log("Refusing to start an advertisement GATT server
|
||||
// because creating a legacy BleAdvertisement with service ID %s failed.",
|
||||
// service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray>> service_id_hash(
|
||||
generateServiceIdHash(BLEAdvertisement::Version::V2, service_id));
|
||||
ScopedPtr<ConstPtr<ByteArray>> ble_advertisement_bytes(
|
||||
BLEAdvertisement::toBytes(BLEAdvertisement::Version::V2,
|
||||
BLEAdvertisement::SocketVersion::V2,
|
||||
service_id_hash.get(), advertisement));
|
||||
if (ble_advertisement_bytes.isNull()) {
|
||||
// logger.atSevere().log("Refusing to start an advertisement GATT server
|
||||
// because creating a BleAdvertisement with service ID %s failed.",
|
||||
// service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
return internalStartAdvertisementGattServer(
|
||||
legacy_ble_advertisement_bytes.release(),
|
||||
ble_advertisement_bytes.release());
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::internalStartAdvertisementGattServer(
|
||||
ConstPtr<ByteArray> legacy_ble_advertisement_bytes,
|
||||
ConstPtr<ByteArray> ble_advertisement_bytes) {
|
||||
// Avoid leaks.
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_legacy_ble_advertisement_bytes(
|
||||
legacy_ble_advertisement_bytes);
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_ble_advertisement_bytes(
|
||||
ble_advertisement_bytes);
|
||||
|
||||
ScopedPtr<Ptr<ServerGATTConnectionLifecycleCallbackFacade>>
|
||||
connection_lifecycle_callback(
|
||||
new ServerGATTConnectionLifecycleCallbackFacade(self_));
|
||||
ScopedPtr<Ptr<GATTServer>> gatt_server(
|
||||
ble_medium_->startGATTServer(connection_lifecycle_callback.get()));
|
||||
if (gatt_server.isNull()) {
|
||||
// logger.atSevere().withCause(e).log("Unable to start an advertisement GATT
|
||||
// server.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!generateAdvertisementCharacteristic(
|
||||
/* slot= */ 0, scoped_legacy_ble_advertisement_bytes.release(),
|
||||
gatt_server.get())) {
|
||||
gatt_server->stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!generateAdvertisementCharacteristic(
|
||||
/* slot= */ 1, scoped_ble_advertisement_bytes.release(),
|
||||
gatt_server.get())) {
|
||||
gatt_server->stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
// GattCharacteristic is not included in GATTServerInfo because we don't need
|
||||
// it after it's been updated.
|
||||
gatt_server_info_ = MakePtr(new GATTServerInfo(
|
||||
gatt_server.release(), connection_lifecycle_callback.release()));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BLEV2<Platform>::generateAdvertisementCharacteristic(
|
||||
std::int32_t slot, ConstPtr<ByteArray> advertisement,
|
||||
Ptr<GATTServer> gatt_server) {
|
||||
// Avoid leaks.
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_advertisement(advertisement);
|
||||
|
||||
std::set<GATTCharacteristic::Permission::Value> permissions;
|
||||
permissions.insert(GATTCharacteristic::Permission::READ);
|
||||
std::set<GATTCharacteristic::Property::Value> properties;
|
||||
properties.insert(GATTCharacteristic::Property::READ);
|
||||
Ptr<GATTCharacteristic> gatt_characteristic(gatt_server->createCharacteristic(
|
||||
kCopresenceServiceUuid, generateAdvertisementUuid(slot), permissions,
|
||||
properties));
|
||||
|
||||
if (gatt_characteristic.isNull()) {
|
||||
// logger.atSevere().withCause(e).log("Unable to create and add a
|
||||
// characterstic to the gatt server for the advertisement.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gatt_server->updateCharacteristic(gatt_characteristic,
|
||||
scoped_advertisement.release())) {
|
||||
// logger.atSevere().withCause(e).log("Unable to write a value to the GATT
|
||||
// characteristic.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Note: In the java counterpart this in a utils class.
|
||||
// Generates a characteristic UUID for an advertisement at the given slot.
|
||||
template <typename Platform>
|
||||
string BLEV2<Platform>::generateAdvertisementUuid(std::int32_t slot) {
|
||||
return UUID<Platform>(kAdvertisementUuidMsb, kAdvertisementUuidLsb | slot)
|
||||
.str();
|
||||
}
|
||||
|
||||
// Stops a GATT server used for additional advertisement data.
|
||||
template <typename Platform>
|
||||
void BLEV2<Platform>::stopAdvertisementGattServer() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!isAdvertisementGattServerRunning()) {
|
||||
// logger.atSevere().log("Unable to stop the advertisement GATT server
|
||||
// because it's not running.");
|
||||
return;
|
||||
}
|
||||
|
||||
gatt_server_info_->gatt_server->stop();
|
||||
gatt_server_info_.destroy();
|
||||
}
|
||||
|
||||
// Connects to a GATT server, reads advertisement data, and then disconnects
|
||||
// from the GATT server. This method blocks until all advertisements are read,
|
||||
// or a connection error occurs.
|
||||
template <typename Platform>
|
||||
Ptr<AdvertisementReadResult<Platform>>
|
||||
BLEV2<Platform>::processFetchGattAdvertisementsRequest(
|
||||
Ptr<BLEPeripheralV2> peripheral, std::int32_t num_slots,
|
||||
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (advertisement_read_result.isNull()) {
|
||||
advertisement_read_result =
|
||||
MakeRefCountedPtr(new AdvertisementReadResult<Platform>());
|
||||
}
|
||||
|
||||
if (peripheral.isNull()) {
|
||||
// logger.atSevere().log("Can't read from an advertisement GATT server
|
||||
// because ble peripheral is null.");
|
||||
return advertisement_read_result;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// logger.atSevere().log("Can't read from an advertisement GATT server
|
||||
// because Bluetooth was never turned on.");
|
||||
return advertisement_read_result;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// logger.atSevere().log("Can't read from an advertisement GATT server
|
||||
// because BLE is not available.");
|
||||
return advertisement_read_result;
|
||||
}
|
||||
|
||||
return internalReadFromAdvertisementGattServer(peripheral, num_slots,
|
||||
advertisement_read_result);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<AdvertisementReadResult<Platform>>
|
||||
BLEV2<Platform>::internalReadFromAdvertisementGattServer(
|
||||
Ptr<BLEPeripheralV2> peripheral, std::int32_t num_slots,
|
||||
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result) {
|
||||
// Attempt to connect and read some GATT characteristics.
|
||||
bool read_success = true;
|
||||
|
||||
ScopedPtr<Ptr<ClientGATTConnectionLifecycleCallbackFacade>>
|
||||
connection_lifecycle_callback(
|
||||
new ClientGATTConnectionLifecycleCallbackFacade(self_));
|
||||
ScopedPtr<Ptr<ClientGATTConnection>> gatt_connection(
|
||||
ble_medium_->connectToGATTServer(peripheral, kDefaultMtu,
|
||||
BLEMediumV2::PowerMode::HIGH,
|
||||
connection_lifecycle_callback.get()));
|
||||
if (!gatt_connection.isNull() && gatt_connection->discoverServices()) {
|
||||
// Read all advertisements from all slots that we haven't read from yet.
|
||||
for (std::int32_t slot = 0; slot < num_slots; ++slot) {
|
||||
// Make sure we haven't already read this advertisement before.
|
||||
if (advertisement_read_result->hasAdvertisement(slot)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure the characteristic even exists for this slot number. If the
|
||||
// characteristic doesn't exist, we shouldn't count the fetch as a
|
||||
// failure because there's nothing we could've done about a non-existent
|
||||
// characteristic.
|
||||
Ptr<GATTCharacteristic> gatt_characteristic(
|
||||
gatt_connection->getCharacteristic(kCopresenceServiceUuid,
|
||||
generateAdvertisementUuid(slot)));
|
||||
if (/* !advertisementSlotExists()= */ gatt_characteristic.isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read advertisement data from the characteristic associated with this
|
||||
// slot.
|
||||
ScopedPtr<ConstPtr<ByteArray>> characteristic_value(
|
||||
gatt_connection->readCharacteristic(gatt_characteristic));
|
||||
if (!characteristic_value.isNull()) {
|
||||
advertisement_read_result->addAdvertisement(
|
||||
slot, characteristic_value.release());
|
||||
// logger.atVerbose().log("Successfully read advertisement at slot %d
|
||||
// on peripheral %s.", slot, peripheral);
|
||||
} else {
|
||||
// logger.atWarning().withCause(characteristicReadException).log("Can't
|
||||
// read advertisement for slot %d on peripheral %s.", slot,
|
||||
// peripheral);
|
||||
read_success = false;
|
||||
}
|
||||
// Whether or not we succeeded with this slot, we should try reading the
|
||||
// other slots to get as many advertisements as possible before
|
||||
// returning a success or failure.
|
||||
}
|
||||
|
||||
gatt_connection->disconnect();
|
||||
} else {
|
||||
// logger.atWarning().withCause(connectException).log("Can't connect to an
|
||||
// advertisement GATT server for peripheral %s.", peripheral);
|
||||
read_success = false;
|
||||
}
|
||||
|
||||
advertisement_read_result->recordLastReadStatus(read_success);
|
||||
return advertisement_read_result;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BLEV2<Platform>::offloadFromPlatformThread(Ptr<Runnable> runnable) {
|
||||
platform_thread_offloader_->execute(runnable);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
ConstPtr<ByteArray> BLEV2<Platform>::generateAdvertisementHash(
|
||||
ConstPtr<ByteArray> advertisement_bytes) {
|
||||
return Utils::sha256Hash(hash_utils_.get(), advertisement_bytes,
|
||||
BLEAdvertisementHeader::kAdvertisementHashLength);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
ConstPtr<ByteArray> BLEV2<Platform>::generateServiceIdHash(
|
||||
BLEAdvertisement::Version::Value version, const string& service_id) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> service_id_bytes(
|
||||
MakeConstPtr(new ByteArray(service_id.data(), service_id.size())));
|
||||
switch (version) {
|
||||
case BLEAdvertisement::Version::V1:
|
||||
return Utils::legacySha256HashOnlyForPrinting(
|
||||
hash_utils_.get(), service_id_bytes.get(),
|
||||
BLEAdvertisement::kServiceIdHashLength);
|
||||
case BLEAdvertisement::Version::V2:
|
||||
// Fall through.
|
||||
case BLEAdvertisement::Version::UNKNOWN:
|
||||
// Fall through.
|
||||
default:
|
||||
// Use the latest known hashing scheme.
|
||||
return Utils::sha256Hash(hash_utils_.get(), service_id_bytes.get(),
|
||||
BLEAdvertisement::kServiceIdHashLength);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,313 +0,0 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_BLE_V2_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_BLE_V2_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "core/internal/mediums/advertisement_read_result.h"
|
||||
#include "core/internal/mediums/ble_advertisement.h"
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "core/internal/mediums/discovered_peripheral_callback.h"
|
||||
#include "core/internal/mediums/discovered_peripheral_tracker.h"
|
||||
#include "platform/api/ble_v2.h"
|
||||
#include "platform/api/bluetooth_adapter.h"
|
||||
#include "platform/api/hash_utils.h"
|
||||
#include "platform/api/lock.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/cancelable_alarm.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/prng.h"
|
||||
#include "platform/ptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace ble_v2 {
|
||||
|
||||
template <typename>
|
||||
class ProcessOnLostRunnable;
|
||||
|
||||
template <typename>
|
||||
class OnAdvertisementFoundRunnable;
|
||||
|
||||
} // namespace ble_v2
|
||||
|
||||
template <typename Platform>
|
||||
class BLEV2 {
|
||||
public:
|
||||
explicit BLEV2(Ptr<BluetoothRadio<Platform>> bluetooth_radio);
|
||||
~BLEV2();
|
||||
|
||||
bool isAvailable();
|
||||
// While the start* functions for each action (advertising, scanning,
|
||||
// accepting connections) take in a service_id, the stop* and is* functions do
|
||||
// not. This is because the service_id isn't used. In the java code, shutdown
|
||||
// calls all the stop* functions w/ a null service_id. The service_id is just
|
||||
// passed through to the corresponding is* function, which ignores it.
|
||||
// service_id should be added back in when C++ supports multi-client.
|
||||
bool startAdvertising(const string& service_id,
|
||||
ConstPtr<ByteArray> advertisement,
|
||||
BLEMediumV2::PowerMode::Value power_mode,
|
||||
const string& fast_advertisement_service_uuid);
|
||||
void stopAdvertising();
|
||||
|
||||
bool startScanning(
|
||||
const string& service_id,
|
||||
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback,
|
||||
BLEMediumV2::PowerMode::Value power_mode,
|
||||
const string& fast_advertisement_service_uuid);
|
||||
void stopScanning();
|
||||
|
||||
class AcceptedConnectionCallback {
|
||||
public:
|
||||
virtual ~AcceptedConnectionCallback() {}
|
||||
|
||||
// TODO(ahlee): Add in connecting logic.
|
||||
};
|
||||
|
||||
bool isAcceptingConnections();
|
||||
bool startAcceptingConnections(
|
||||
const string& service_id,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback);
|
||||
void stopAcceptingConnections();
|
||||
|
||||
private:
|
||||
template <typename>
|
||||
friend class ble_v2::ProcessOnLostRunnable;
|
||||
template <typename>
|
||||
friend class ble_v2::OnAdvertisementFoundRunnable;
|
||||
|
||||
class GATTAdvertisementFetcherFacade
|
||||
: public DiscoveredPeripheralTracker<Platform>::GattAdvertisementFetcher {
|
||||
public:
|
||||
explicit GATTAdvertisementFetcherFacade(Ptr<BLEV2<Platform>> impl)
|
||||
: impl_(impl) {}
|
||||
~GATTAdvertisementFetcherFacade() override {}
|
||||
|
||||
Ptr<AdvertisementReadResult<Platform>> fetchGattAdvertisements(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral, std::int32_t num_slots,
|
||||
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result)
|
||||
override {
|
||||
return impl_->processFetchGattAdvertisementsRequest(
|
||||
ble_peripheral, num_slots, advertisement_read_result);
|
||||
}
|
||||
|
||||
private:
|
||||
Ptr<BLEV2<Platform>> impl_;
|
||||
};
|
||||
|
||||
class ScanCallbackFacade : public BLEMediumV2::ScanCallback {
|
||||
public:
|
||||
explicit ScanCallbackFacade(Ptr<BLEV2<Platform>> impl) : impl_(impl) {}
|
||||
~ScanCallbackFacade() override {}
|
||||
|
||||
void onAdvertisementFound(
|
||||
Ptr<BLEPeripheralV2> peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data) override {
|
||||
impl_->onAdvertisementFoundImpl(peripheral, advertisement_data);
|
||||
}
|
||||
|
||||
private:
|
||||
Ptr<BLEV2<Platform>> impl_;
|
||||
};
|
||||
|
||||
class ClientGATTConnectionLifecycleCallbackFacade
|
||||
: public ClientGATTConnectionLifecycleCallback {
|
||||
public:
|
||||
explicit ClientGATTConnectionLifecycleCallbackFacade(
|
||||
Ptr<BLEV2<Platform>> impl)
|
||||
: impl_(impl) {}
|
||||
~ClientGATTConnectionLifecycleCallbackFacade() override {}
|
||||
|
||||
void onDisconnected(Ptr<ClientGATTConnection> connection) override {
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<ClientGATTConnection>> scoped_connection(connection);
|
||||
|
||||
// Nothing else to do for now.
|
||||
}
|
||||
|
||||
private:
|
||||
Ptr<BLEV2<Platform>> impl_;
|
||||
};
|
||||
|
||||
class ServerGATTConnectionLifecycleCallbackFacade
|
||||
: public ServerGATTConnectionLifecycleCallback {
|
||||
public:
|
||||
explicit ServerGATTConnectionLifecycleCallbackFacade(
|
||||
Ptr<BLEV2<Platform>> impl)
|
||||
: impl_(impl) {}
|
||||
~ServerGATTConnectionLifecycleCallbackFacade() override {}
|
||||
|
||||
void onCharacteristicSubscription(
|
||||
Ptr<ServerGATTConnection> connection,
|
||||
Ptr<GATTCharacteristic> characteristic) override {
|
||||
// Avoid leaks. Do not scope the characteristic because it is ref counted
|
||||
// by the per-platform ble_v2 implementation.
|
||||
ScopedPtr<Ptr<ServerGATTConnection>> scoped_connection(connection);
|
||||
|
||||
// Nothing else to do for now.
|
||||
}
|
||||
|
||||
void onCharacteristicUnsubscription(
|
||||
Ptr<ServerGATTConnection> connection,
|
||||
Ptr<GATTCharacteristic> characteristic) override {
|
||||
// Avoid leaks. Do not scope the characteristic because it is ref counted
|
||||
// by the per-platform ble_v2 implementation.
|
||||
ScopedPtr<Ptr<ServerGATTConnection>> scoped_connection(connection);
|
||||
|
||||
// Nothing else to do for now.
|
||||
}
|
||||
|
||||
private:
|
||||
Ptr<BLEV2<Platform>> impl_;
|
||||
};
|
||||
|
||||
struct ScanningInfo {
|
||||
ScanningInfo(const string& service_id,
|
||||
Ptr<ScanCallbackFacade> scan_callback_facade,
|
||||
Ptr<CancelableAlarm> on_lost_alarm)
|
||||
: service_id(service_id),
|
||||
scan_callback_facade(scan_callback_facade),
|
||||
on_lost_alarm(on_lost_alarm) {}
|
||||
~ScanningInfo() {
|
||||
// Nothing to do (the ScopedPtr members take care of themselves).
|
||||
}
|
||||
|
||||
const string service_id;
|
||||
ScopedPtr<Ptr<ScanCallbackFacade>> scan_callback_facade;
|
||||
// TODO(ahlee): Change to recurring cancelable alarm
|
||||
ScopedPtr<Ptr<CancelableAlarm>> on_lost_alarm;
|
||||
};
|
||||
|
||||
struct AdvertisingInfo {
|
||||
explicit AdvertisingInfo(const string& service_id)
|
||||
: service_id(service_id) {}
|
||||
~AdvertisingInfo() {}
|
||||
|
||||
const string service_id;
|
||||
};
|
||||
|
||||
struct GATTServerInfo {
|
||||
GATTServerInfo(Ptr<GATTServer> gatt_server,
|
||||
Ptr<ServerGATTConnectionLifecycleCallbackFacade>
|
||||
connection_lifecycle_callback)
|
||||
: gatt_server(gatt_server),
|
||||
connection_lifecycle_callback(connection_lifecycle_callback) {}
|
||||
~GATTServerInfo() {
|
||||
// Nothing to do (the ScopedPtr members take care of themselves).
|
||||
}
|
||||
|
||||
ScopedPtr<Ptr<GATTServer>> gatt_server;
|
||||
ScopedPtr<Ptr<ServerGATTConnectionLifecycleCallbackFacade>>
|
||||
connection_lifecycle_callback;
|
||||
};
|
||||
|
||||
struct AcceptingConnectionsInfo {
|
||||
explicit AcceptingConnectionsInfo(const string& service_id)
|
||||
: service_id(service_id) {}
|
||||
~AcceptingConnectionsInfo() {
|
||||
// Nothing to do (the ScopedPtr members take care of themselves).
|
||||
}
|
||||
|
||||
const string service_id;
|
||||
// TODO(ahlee): Fill in.
|
||||
};
|
||||
|
||||
static const std::int32_t kNumAdvertisementSlots;
|
||||
static const std::int32_t kMaxAdvertisementLength;
|
||||
static const std::int32_t kDummyServiceIdLength;
|
||||
static const char* kCopresenceServiceUuid;
|
||||
static const std::int64_t kOnLostTimeoutMillis;
|
||||
static const std::int64_t kGattAdvertisementOperationTimeoutMillis;
|
||||
static const std::int64_t kMinConnectionAttemptRecoveryDurationMillis;
|
||||
static const std::int32_t kMaxConnectionAttemptRecoveryFuzzDurationMillis;
|
||||
static const std::uint32_t kDefaultMtu;
|
||||
static const std::int64_t kAdvertisementUuidMsb;
|
||||
static const std::int64_t kAdvertisementUuidLsb;
|
||||
|
||||
bool isAdvertising();
|
||||
ConstPtr<ByteArray> createAdvertisementHeader(
|
||||
const string& service_id, ConstPtr<ByteArray> advertisement_bytes,
|
||||
bool is_fast_advertisement);
|
||||
|
||||
bool isScanning();
|
||||
void onAdvertisementFoundImpl(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data);
|
||||
void processOnLostTimeout();
|
||||
Ptr<CancelableAlarm> createOnLostAlarm();
|
||||
|
||||
bool isAdvertisementGattServerRunning();
|
||||
bool startAdvertisementGattServer(const string& service_id,
|
||||
ConstPtr<ByteArray> advertisement);
|
||||
bool internalStartAdvertisementGattServer(
|
||||
ConstPtr<ByteArray> legacy_ble_advertisement_bytes,
|
||||
ConstPtr<ByteArray> ble_advertisement_bytes);
|
||||
bool generateAdvertisementCharacteristic(
|
||||
std::int32_t slot, ConstPtr<ByteArray> advertisement,
|
||||
Ptr<GATTServer> gatt_server);
|
||||
void stopAdvertisementGattServer();
|
||||
|
||||
Ptr<AdvertisementReadResult<Platform>> processFetchGattAdvertisementsRequest(
|
||||
Ptr<BLEPeripheralV2> peripheral, std::int32_t num_slots,
|
||||
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result);
|
||||
Ptr<AdvertisementReadResult<Platform>>
|
||||
internalReadFromAdvertisementGattServer(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral, std::int32_t num_slots,
|
||||
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result);
|
||||
|
||||
void offloadFromPlatformThread(Ptr<Runnable> runnable);
|
||||
// TODO(ahlee): Move these out to utils (also used by
|
||||
// DiscoveredPeripheralTracker).
|
||||
ConstPtr<ByteArray> generateAdvertisementHash(
|
||||
ConstPtr<ByteArray> advertisement_bytes);
|
||||
ConstPtr<ByteArray> generateServiceIdHash(
|
||||
BLEAdvertisement::Version::Value version, const string& service_id);
|
||||
|
||||
// This maps to a helper function found in bluetoothlowenergy/Utils.java. In
|
||||
// the C++ code we moved it because it's only used here.
|
||||
string generateAdvertisementUuid(std::int32_t slot);
|
||||
|
||||
// ------------ GENERAL ------------
|
||||
|
||||
ScopedPtr<Ptr<Lock>> lock_;
|
||||
// Where we throw potentially blocking work off of the platform thread.
|
||||
ScopedPtr<Ptr<typename Platform::SingleThreadExecutorType>>
|
||||
platform_thread_offloader_;
|
||||
ScopedPtr<Ptr<Prng>> prng_;
|
||||
ScopedPtr<Ptr<HashUtils>> hash_utils_;
|
||||
|
||||
// ------------ CORE BLE ------------
|
||||
|
||||
Ptr<BluetoothRadio<Platform>> bluetooth_radio_;
|
||||
ScopedPtr<Ptr<BluetoothAdapter>> bluetooth_adapter_;
|
||||
// The underlying, per-platform implementation.
|
||||
ScopedPtr<Ptr<BLEMediumV2>> ble_medium_;
|
||||
|
||||
// ------------ DISCOVERY ------------
|
||||
|
||||
// scanning_info_ is not scoped because it's nullable.
|
||||
Ptr<ScanningInfo> scanning_info_;
|
||||
ScopedPtr<Ptr<DiscoveredPeripheralTracker<Platform>>>
|
||||
discovered_peripheral_tracker_;
|
||||
ScopedPtr<Ptr<typename Platform::ScheduledExecutorType>> on_lost_executor_;
|
||||
|
||||
// ------------ ADVERTISING ------------
|
||||
|
||||
// advertising_info_, gatt_server_info_, and accepting_connections_info_ are
|
||||
// not scoped because they are nullable.
|
||||
Ptr<AdvertisingInfo> advertising_info_;
|
||||
Ptr<GATTServerInfo> gatt_server_info_;
|
||||
Ptr<AcceptingConnectionsInfo> accepting_connections_info_;
|
||||
std::shared_ptr<BLEV2> self_{this, [](void*){}};
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/ble_v2.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_H_
|
||||
@@ -0,0 +1,49 @@
|
||||
cc_library(
|
||||
name = "ble_v2",
|
||||
srcs = [
|
||||
"advertisement_read_result.cc",
|
||||
"ble_advertisement.cc",
|
||||
"ble_advertisement_header.cc",
|
||||
"ble_packet.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"advertisement_read_result.h",
|
||||
"ble_advertisement.h",
|
||||
"ble_advertisement_header.h",
|
||||
"ble_packet.h",
|
||||
"ble_peripheral.h",
|
||||
"discovered_peripheral_callback.h",
|
||||
],
|
||||
visibility = [
|
||||
"//core/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//core:core_types",
|
||||
"//platform/base",
|
||||
"//platform/base:util",
|
||||
"//platform/public:logging",
|
||||
"//platform/public:types",
|
||||
"//absl/container:flat_hash_map",
|
||||
"//absl/container:flat_hash_set",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
],
|
||||
)
|
||||
|
||||
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",
|
||||
],
|
||||
deps = [
|
||||
":ble_v2",
|
||||
"//platform/base",
|
||||
"//platform/impl/g3", # buildcleaner: keep
|
||||
"//testing/base/public:gunit_main",
|
||||
"//absl/time",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,125 @@
|
||||
#include "core/internal/mediums/ble_v2/advertisement_read_result.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "platform/public/mutex_lock.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/time/clock.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
|
||||
@@ -0,0 +1,90 @@
|
||||
#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 "platform/base/byte_array.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/time/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_
|
||||
@@ -0,0 +1,129 @@
|
||||
#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
|
||||
@@ -0,0 +1,236 @@
|
||||
#include "core/internal/mediums/ble_v2/ble_advertisement.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "platform/base/base_input_stream.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "absl/strings/str_cat.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
|
||||
@@ -0,0 +1,125 @@
|
||||
#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_
|
||||
@@ -0,0 +1,117 @@
|
||||
#include "core/internal/mediums/ble_v2/ble_advertisement_header.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "platform/base/base64_utils.h"
|
||||
#include "platform/base/base_input_stream.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
Version version, int num_slots, const ByteArray &service_id_bloom_filter,
|
||||
const ByteArray &advertisement_hash) {
|
||||
if (version != Version::kV2 || num_slots <= 0 ||
|
||||
service_id_bloom_filter.size() != kServiceIdBloomFilterLength ||
|
||||
advertisement_hash.size() != kAdvertisementHashLength) {
|
||||
return;
|
||||
}
|
||||
|
||||
version_ = version;
|
||||
num_slots_ = num_slots;
|
||||
service_id_bloom_filter_ = service_id_bloom_filter;
|
||||
advertisement_hash_ = advertisement_hash;
|
||||
}
|
||||
|
||||
BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
const std::string &ble_advertisement_header_string) {
|
||||
ByteArray ble_advertisement_header_bytes =
|
||||
Base64Utils::Decode(ble_advertisement_header_string);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
BaseInputStream base_input_stream{ble_advertisement_header_bytes};
|
||||
// The first 1 byte is supposed to be the version and number of slots.
|
||||
auto version_and_pcp_byte = static_cast<char>(base_input_stream.ReadUint8());
|
||||
// The upper 3 bits are supposed to be the version.
|
||||
version_ =
|
||||
static_cast<Version>((version_and_pcp_byte & kVersionBitmask) >> 5);
|
||||
if (version_ != Version::kV2) {
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
"Cannot deserialize BleAdvertisementHeader: unsupported Version %d",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
// The lower 5 bits are supposed to be the number of slots.
|
||||
num_slots_ = static_cast<int>(version_and_pcp_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);
|
||||
}
|
||||
|
||||
BleAdvertisementHeader::operator std::string() const {
|
||||
if (!IsValid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// The first 3 bits are the Version.
|
||||
char version_and_num_slots_byte =
|
||||
(static_cast<char>(version_) << 5) & kVersionBitmask;
|
||||
// The next 5 bits are the number of slots.
|
||||
version_and_num_slots_byte |=
|
||||
static_cast<char>(num_slots_) & kNumSlotsBitmask;
|
||||
|
||||
// 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_));
|
||||
// clang-format on
|
||||
|
||||
return Base64Utils::Encode(ByteArray(std::move(out)));
|
||||
}
|
||||
|
||||
bool BleAdvertisementHeader::operator<(
|
||||
const BleAdvertisementHeader &rhs) const {
|
||||
if (this->GetVersion() != rhs.GetVersion()) {
|
||||
return this->GetVersion() < rhs.GetVersion();
|
||||
}
|
||||
if (this->GetNumSlots() != rhs.GetNumSlots()) {
|
||||
return this->GetNumSlots() < rhs.GetNumSlots();
|
||||
}
|
||||
if (this->GetServiceIdBloomFilter() != rhs.GetServiceIdBloomFilter()) {
|
||||
return this->GetServiceIdBloomFilter() < rhs.GetServiceIdBloomFilter();
|
||||
}
|
||||
return this->GetAdvertisementHash() < rhs.GetAdvertisementHash();
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,83 @@
|
||||
#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]
|
||||
//
|
||||
// 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, int num_slots,
|
||||
const ByteArray &service_id_bloom_filter,
|
||||
const ByteArray &advertisement_hash);
|
||||
explicit BleAdvertisementHeader(
|
||||
const std::string &ble_advertisement_header_string);
|
||||
BleAdvertisementHeader(const BleAdvertisementHeader &) = default;
|
||||
BleAdvertisementHeader &operator=(const BleAdvertisementHeader &) = default;
|
||||
BleAdvertisementHeader(BleAdvertisementHeader &&) = default;
|
||||
BleAdvertisementHeader &operator=(BleAdvertisementHeader &&) = default;
|
||||
~BleAdvertisementHeader() = default;
|
||||
|
||||
// Produces an encoded binary string which can be decoded by the explicit
|
||||
// constructor. The returned string is empty if BleAdvertisementHeader is not
|
||||
// valid - false on IsValid().
|
||||
explicit operator std::string() const;
|
||||
bool operator<(const BleAdvertisementHeader &rhs) const;
|
||||
|
||||
bool IsValid() const { return version_ == Version::kV2; }
|
||||
Version GetVersion() const { return version_; }
|
||||
int GetNumSlots() const { return num_slots_; }
|
||||
ByteArray GetServiceIdBloomFilter() const { return service_id_bloom_filter_; }
|
||||
ByteArray GetAdvertisementHash() const { return advertisement_hash_; }
|
||||
|
||||
private:
|
||||
static constexpr int kServiceIdBloomFilterLength = 10;
|
||||
static constexpr int kAdvertisementHashLength = 4;
|
||||
static constexpr int kMinAdvertisementHeaderLength =
|
||||
1 + kServiceIdBloomFilterLength + kAdvertisementHashLength;
|
||||
static constexpr int kVersionBitmask = 0x0E0;
|
||||
static constexpr int kNumSlotsBitmask = 0x01F;
|
||||
|
||||
Version version_ = Version::kUndefined;
|
||||
int num_slots_;
|
||||
ByteArray service_id_bloom_filter_;
|
||||
ByteArray advertisement_hash_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLE_V2_BLE_ADVERTISEMENT_HEADER_H_
|
||||
@@ -0,0 +1,188 @@
|
||||
#include "core/internal/mediums/ble_v2/ble_advertisement_header.h"
|
||||
|
||||
#include "platform/base/base64_utils.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
constexpr BleAdvertisementHeader::Version kVersion =
|
||||
BleAdvertisementHeader::Version::kV2;
|
||||
constexpr int kNumSlots = 2;
|
||||
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, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion());
|
||||
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());
|
||||
}
|
||||
|
||||
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, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
|
||||
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, num_slot, service_id_bloom_filter, advertisement_hash};
|
||||
|
||||
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, kNumSlots, short_service_id_bloom_filter_bytes,
|
||||
advertisement_hash};
|
||||
|
||||
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, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
|
||||
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, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
|
||||
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, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
|
||||
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, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
auto ble_advertisement_header_string =
|
||||
std::string(org_ble_advertisement_header);
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
ble_advertisement_header_string};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, ble_advertisement_header.GetVersion());
|
||||
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());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) {
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
auto ble_advertisement_header_string = std::string(ble_advertisement_header);
|
||||
|
||||
// Base64 decode the string, add a character, and then re-encode it.
|
||||
ByteArray ble_advertisement_header_bytes =
|
||||
Base64Utils::Decode(ble_advertisement_header_string);
|
||||
ByteArray long_ble_advertisement_header_bytes{
|
||||
ble_advertisement_header_bytes.size() + 1};
|
||||
long_ble_advertisement_header_bytes.CopyAt(0, ble_advertisement_header_bytes);
|
||||
std::string long_ble_advertisement_header_string{
|
||||
Base64Utils::Encode(long_ble_advertisement_header_bytes)};
|
||||
|
||||
BleAdvertisementHeader long_ble_advertisement_header{
|
||||
long_ble_advertisement_header_string};
|
||||
|
||||
EXPECT_TRUE(long_ble_advertisement_header.IsValid());
|
||||
EXPECT_EQ(kVersion, long_ble_advertisement_header.GetVersion());
|
||||
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());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) {
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
auto ble_advertisement_header_string = std::string(ble_advertisement_header);
|
||||
|
||||
// Base64 decode the string, remove a character, and then re-encode it.
|
||||
ByteArray ble_advertisement_header_bytes =
|
||||
Base64Utils::Decode(ble_advertisement_header_string);
|
||||
ByteArray short_ble_advertisement_header_bytes{
|
||||
ble_advertisement_header_bytes.size() - 1};
|
||||
short_ble_advertisement_header_bytes.CopyAt(0,
|
||||
ble_advertisement_header_bytes);
|
||||
std::string short_ble_advertisement_header_string{
|
||||
Base64Utils::Encode(short_ble_advertisement_header_bytes)};
|
||||
|
||||
BleAdvertisementHeader short_ble_advertisement_header{
|
||||
short_ble_advertisement_header_string};
|
||||
|
||||
EXPECT_FALSE(short_ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,517 @@
|
||||
#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
|
||||
@@ -0,0 +1,59 @@
|
||||
#include "core/internal/mediums/ble_v2/ble_packet.h"
|
||||
|
||||
#include "platform/base/base_input_stream.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "absl/strings/str_cat.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
|
||||
@@ -0,0 +1,50 @@
|
||||
#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_
|
||||
@@ -0,0 +1,97 @@
|
||||
#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
|
||||
@@ -0,0 +1,35 @@
|
||||
#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_
|
||||
@@ -0,0 +1,33 @@
|
||||
#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
|
||||
@@ -0,0 +1,31 @@
|
||||
#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_
|
||||
@@ -9,31 +9,19 @@ namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
const std::int32_t BloomFilter<CapacityInBytes>::kHasherNumberOfRepetitions = 5;
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
BloomFilter<CapacityInBytes>::BloomFilter() : bits_() {}
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
BloomFilter<CapacityInBytes>::BloomFilter(ConstPtr<ByteArray> bytes) : bits_() {
|
||||
const char* bytes_read_ptr = bytes->getData();
|
||||
for (size_t byte_index = 0; byte_index < bytes->size(); byte_index++) {
|
||||
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);
|
||||
bits_->Set((byte_index * 8) + bit_index,
|
||||
(*bytes_read_ptr >> bit_index) & 0x01);
|
||||
}
|
||||
bytes_read_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
BloomFilter<CapacityInBytes>::~BloomFilter() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
ConstPtr<ByteArray> BloomFilter<CapacityInBytes>::asBytes() {
|
||||
BloomFilterBase::operator ByteArray() const {
|
||||
// Gets a binary string representation of the bitset where the leftmost
|
||||
// character corresponds to bitset position (total size) - 1.
|
||||
//
|
||||
@@ -41,13 +29,13 @@ ConstPtr<ByteArray> BloomFilter<CapacityInBytes>::asBytes() {
|
||||
// [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_.to_string();
|
||||
std::string bitset_binary_string = bits_->ToString();
|
||||
|
||||
Ptr<ByteArray> result_bytes{new ByteArray{CapacityInBytes}};
|
||||
char* result_bytes_write_ptr = result_bytes->getData();
|
||||
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) {
|
||||
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,
|
||||
@@ -55,35 +43,29 @@ ConstPtr<ByteArray> BloomFilter<CapacityInBytes>::asBytes() {
|
||||
*result_bytes_write_ptr = static_cast<char>(byte_value & 0x000000FF);
|
||||
result_bytes_write_ptr++;
|
||||
}
|
||||
return ConstifyPtr(result_bytes);
|
||||
return result_bytes;
|
||||
}
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
void BloomFilter<CapacityInBytes>::add(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes = getHashes(s);
|
||||
for (std::vector<std::int32_t>::iterator it = hashes.begin();
|
||||
it != hashes.end(); ++it) {
|
||||
size_t position = static_cast<size_t>(*it) % bits_.size();
|
||||
bits_.set(position);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
bool BloomFilter<CapacityInBytes>::possiblyContains(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes = getHashes(s);
|
||||
for (std::vector<std::int32_t>::iterator i = hashes.begin();
|
||||
i != hashes.end(); ++i) {
|
||||
size_t position = static_cast<size_t>(*i) % bits_.size();
|
||||
if (!bits_.test(position)) {
|
||||
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;
|
||||
}
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
std::vector<std::int32_t> BloomFilter<CapacityInBytes>::getHashes(
|
||||
const std::string& s) {
|
||||
std::vector<std::int32_t> BloomFilterBase::GetHashes(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes(kHasherNumberOfRepetitions, 0);
|
||||
|
||||
absl::uint128 hash128;
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
#define CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_
|
||||
|
||||
#include <bitset>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -23,25 +20,63 @@ namespace mediums {
|
||||
* the bit set to ensure the bit set's length is a multiple of 8 (and can
|
||||
* neatly be returned as a ByteArray).
|
||||
*/
|
||||
template <size_t CapacityInBytes>
|
||||
class BloomFilter {
|
||||
class BloomFilterBase {
|
||||
public:
|
||||
BloomFilter();
|
||||
explicit BloomFilter(ConstPtr<ByteArray> bytes);
|
||||
~BloomFilter();
|
||||
explicit operator ByteArray() const;
|
||||
|
||||
ConstPtr<ByteArray> asBytes();
|
||||
void Add(const std::string& s);
|
||||
bool PossiblyContains(const std::string& s);
|
||||
|
||||
void add(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;
|
||||
};
|
||||
|
||||
bool possiblyContains(const std::string& s);
|
||||
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:
|
||||
static const std::int32_t kHasherNumberOfRepetitions;
|
||||
int GetMinBytesForBits() const { return (bits_->Size() + 7) >> 3; }
|
||||
|
||||
std::vector<std::int32_t> getHashes(const std::string& s);
|
||||
BitSet* bits_;
|
||||
};
|
||||
|
||||
std::bitset<CapacityInBytes * 8> 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
|
||||
@@ -49,6 +84,4 @@ class BloomFilter {
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/bloom_filter.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLOOM_FILTER_H_
|
||||
|
||||
@@ -10,68 +10,102 @@ namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
const size_t kByteArrayLength = 100;
|
||||
constexpr size_t kByteArrayLength = 100;
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) {
|
||||
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
|
||||
new BloomFilter<kByteArrayLength>());
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_bloom_filter_bytes(
|
||||
scoped_bloom_filter->asBytes());
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
std::string empty_string(kByteArrayLength, '\0');
|
||||
ASSERT_EQ(0, memcmp(scoped_bloom_filter_bytes->getData(), empty_string.data(),
|
||||
empty_string.size()));
|
||||
|
||||
EXPECT_EQ(empty_string, std::string(bloom_filter_bytes));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterNeverContains) {
|
||||
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
|
||||
new BloomFilter<kByteArrayLength>());
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
|
||||
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_2"));
|
||||
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddSuccess) {
|
||||
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
|
||||
new BloomFilter<kByteArrayLength>());
|
||||
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
scoped_bloom_filter->add("ELEMENT_1");
|
||||
ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddOnlyGivenArg) {
|
||||
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
|
||||
new BloomFilter<kByteArrayLength>());
|
||||
scoped_bloom_filter->add("ELEMENT_1");
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
|
||||
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_2"));
|
||||
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3"));
|
||||
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) {
|
||||
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
|
||||
new BloomFilter<kByteArrayLength>());
|
||||
scoped_bloom_filter->add("ELEMENT_1");
|
||||
scoped_bloom_filter->add("ELEMENT_2");
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_1"));
|
||||
ASSERT_TRUE(scoped_bloom_filter->possiblyContains("ELEMENT_2"));
|
||||
ASSERT_FALSE(scoped_bloom_filter->possiblyContains("ELEMENT_3"));
|
||||
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) {
|
||||
ScopedPtr<Ptr<BloomFilter<10>>> scoped_bloom_filter(new BloomFilter<10>());
|
||||
scoped_bloom_filter->add("ELEMENT_1");
|
||||
scoped_bloom_filter->add("ELEMENT_2");
|
||||
scoped_bloom_filter->add("ELEMENT_3");
|
||||
BloomFilter<10> bloom_filter;
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_bloom_filter_bytes(
|
||||
scoped_bloom_filter->asBytes());
|
||||
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');
|
||||
ASSERT_NE(scoped_bloom_filter_bytes->asString(), empty_string);
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,10 +120,10 @@ TEST(BloomFilterTest, AddMultipleArgsReturnsNonemptyArray) {
|
||||
* something like [ 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, ..., 1, 0].
|
||||
*/
|
||||
TEST(BloomFilterTest, RandomnessNoEndBias) {
|
||||
ScopedPtr<Ptr<BloomFilter<kByteArrayLength>>> scoped_bloom_filter(
|
||||
new BloomFilter<kByteArrayLength>());
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
// Add one element to our BloomFilter.
|
||||
scoped_bloom_filter->add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
std::int32_t non_zero_count = 0;
|
||||
std::int32_t longest_zero_streak = 0;
|
||||
@@ -98,11 +132,9 @@ TEST(BloomFilterTest, RandomnessNoEndBias) {
|
||||
// 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.
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_bloom_filter_bytes(
|
||||
scoped_bloom_filter->asBytes());
|
||||
const char* bloom_filter_bytes_read_ptr =
|
||||
scoped_bloom_filter_bytes->getData();
|
||||
for (int i = 0; i < scoped_bloom_filter_bytes->size(); i++) {
|
||||
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 {
|
||||
@@ -127,31 +159,31 @@ TEST(BloomFilterTest, RandomnessNoEndBias) {
|
||||
// kByteArrayLength - one end of the array.
|
||||
std::int32_t longest_acceptable_zero_streak =
|
||||
kByteArrayLength - (kByteArrayLength / non_zero_count);
|
||||
ASSERT_TRUE(longest_zero_streak <= longest_acceptable_zero_streak);
|
||||
|
||||
EXPECT_TRUE(longest_zero_streak <= longest_acceptable_zero_streak);
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, RandomnessFalsePositiveRate) {
|
||||
ScopedPtr<Ptr<BloomFilter<10>>> scoped_bloom_filter(new BloomFilter<10>());
|
||||
BloomFilter<10> bloom_filter;
|
||||
|
||||
// Add 5 distinct elements to the BloomFilter.
|
||||
scoped_bloom_filter->add("ELEMENT_1");
|
||||
scoped_bloom_filter->add("ELEMENT_2");
|
||||
scoped_bloom_filter->add("ELEMENT_3");
|
||||
scoped_bloom_filter->add("ELEMENT_4");
|
||||
scoped_bloom_filter->add("ELEMENT_5");
|
||||
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 +=
|
||||
scoped_bloom_filter->possiblyContains("ELEMENT_" + std::to_string(i))
|
||||
? 1
|
||||
: 0;
|
||||
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%.
|
||||
ASSERT_LE(false_positives, 5);
|
||||
EXPECT_LE(false_positives, 5);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,466 +1,386 @@
|
||||
#include "core/internal/mediums/bluetooth_classic.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "core/internal/mediums/uuid.h"
|
||||
#include "platform/synchronized.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
template <typename Platform>
|
||||
const std::int32_t BluetoothClassic<Platform>::kMaxConcurrentAcceptLoops = 5;
|
||||
BluetoothClassic::BluetoothClassic(BluetoothRadio& radio) : radio_(radio) {}
|
||||
|
||||
template <typename Platform>
|
||||
BluetoothClassic<Platform>::BluetoothClassic(
|
||||
Ptr<BluetoothRadio<Platform>> bluetooth_radio)
|
||||
: lock_(Platform::createLock()),
|
||||
bluetooth_radio_(bluetooth_radio),
|
||||
bluetooth_adapter_(Platform::createBluetoothAdapter()),
|
||||
bluetooth_classic_medium_(Platform::createBluetoothClassicMedium()),
|
||||
scan_info_(),
|
||||
original_scan_mode_(BluetoothAdapter::ScanMode::UNKNOWN),
|
||||
original_device_name_(),
|
||||
accept_loops_thread_pool_(
|
||||
Platform::createMultiThreadExecutor(kMaxConcurrentAcceptLoops)),
|
||||
bluetooth_server_sockets_() {}
|
||||
|
||||
template <typename Platform>
|
||||
BluetoothClassic<Platform>::~BluetoothClassic() {
|
||||
stopDiscovery();
|
||||
for (BluetoothServerSocketMap::iterator it =
|
||||
bluetooth_server_sockets_.begin();
|
||||
it != bluetooth_server_sockets_.end(); ++it) {
|
||||
stopAcceptingConnections(it->first);
|
||||
BluetoothClassic::~BluetoothClassic() {
|
||||
// Destructor is not taking locks, but methods it is calling are.
|
||||
StopDiscovery();
|
||||
while (!server_sockets_.empty()) {
|
||||
StopAcceptingConnections(server_sockets_.begin()->first);
|
||||
}
|
||||
turnOffDiscoverability();
|
||||
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_thread_pool_->shutdown();
|
||||
|
||||
original_device_name_.destroy();
|
||||
scan_info_.destroy();
|
||||
// StopAcceptingConnections() above.
|
||||
accept_loops_runner_.Shutdown();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::isAvailable() {
|
||||
Synchronized s(lock_.get());
|
||||
bool BluetoothClassic::IsAvailable() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return !bluetooth_classic_medium_.isNull() && !bluetooth_adapter_.isNull();
|
||||
return IsAvailableLocked();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::turnOnDiscoverability(
|
||||
const string& device_name) {
|
||||
Synchronized s(lock_.get());
|
||||
bool BluetoothClassic::IsAvailableLocked() const {
|
||||
return medium_.IsValid() && adapter_.IsValid();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::TurnOnDiscoverability(const std::string& device_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (device_name.empty()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Refusing to turn on Bluetooth
|
||||
// discoverability because a null deviceName was passed in.");
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to turn on BT discoverability. Empty device name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// TODO(reznor): log.atSevere().log("Can't turn on Bluetooth discoverability
|
||||
// because Bluetooth isn't enabled.");
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is off.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// TODO(reznor): log.atSevere().log("Can't turn on Bluetooth discoverability
|
||||
// because Bluetooth isn't available.");
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDiscoverable()) {
|
||||
// TODO(reznor): log.atSevere().log("Refusing to turn on Bluetooth
|
||||
// discoverability with device name %s because we're already discoverable
|
||||
// with device name %s.", deviceName, bluetoothAdapter.getName());
|
||||
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)) {
|
||||
// TODO(reznor): log.atSevere().log("Failed to turn on Bluetooth
|
||||
// discoverability because we couldn't set the device name to %s",
|
||||
// deviceName);
|
||||
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(BluetoothAdapter::ScanMode::CONNECTABLE_DISCOVERABLE)) {
|
||||
// TODO(reznor): log.atSevere().log("Failed to turn on Bluetooth
|
||||
// discoverability because we couldn't set the scan mode to %d",
|
||||
// BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
|
||||
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();
|
||||
RestoreDeviceName();
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(reznor): log.atVerbose().log("Turned on Bluetooth discoverability with
|
||||
// deviceName %s", deviceName);
|
||||
NEARBY_LOG(INFO, "Turned on BT discoverability with device_name=%s",
|
||||
device_name.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BluetoothClassic<Platform>::turnOffDiscoverability() {
|
||||
Synchronized s(lock_.get());
|
||||
bool BluetoothClassic::TurnOffDiscoverability() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!isDiscoverable()) {
|
||||
// TODO(reznor): log.atDebug().log("Can't turn off Bluetooth discoverability
|
||||
// because it was never turned on.");
|
||||
return;
|
||||
if (!IsDiscoverable()) {
|
||||
NEARBY_LOG(INFO, "Can't turn off BT discoverability; it is already off");
|
||||
return false;
|
||||
}
|
||||
|
||||
restoreScanMode();
|
||||
restoreDeviceName();
|
||||
RestoreScanMode();
|
||||
RestoreDeviceName();
|
||||
|
||||
// TODO(reznor): log.atVerbose().log("Turned Bluetooth discoverability off");
|
||||
NEARBY_LOG(INFO, "Turned Bluetooth discoverability off");
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::isDiscoverable() const {
|
||||
return ((!original_device_name_.isNull()) &&
|
||||
(BluetoothAdapter::ScanMode::CONNECTABLE_DISCOVERABLE ==
|
||||
bluetooth_adapter_->getScanMode()));
|
||||
bool BluetoothClassic::IsDiscoverable() const {
|
||||
return (!original_device_name_.empty() &&
|
||||
(adapter_.GetScanMode() == ScanMode::kConnectableDiscoverable));
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::modifyDeviceName(const string& device_name) {
|
||||
original_device_name_ = bluetooth_adapter_->getName();
|
||||
bool BluetoothClassic::ModifyDeviceName(const std::string& device_name) {
|
||||
if (original_device_name_.empty()) {
|
||||
original_device_name_ = adapter_.GetName();
|
||||
}
|
||||
|
||||
if (!bluetooth_adapter_->setName(device_name)) {
|
||||
original_device_name_.destroy();
|
||||
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;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::modifyScanMode(
|
||||
BluetoothAdapter::ScanMode::Value scan_mode) {
|
||||
original_scan_mode_ = bluetooth_adapter_->getScanMode();
|
||||
|
||||
if (!bluetooth_adapter_->setScanMode(scan_mode)) {
|
||||
original_scan_mode_ = BluetoothAdapter::ScanMode::UNKNOWN;
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BluetoothClassic<Platform>::restoreScanMode() {
|
||||
if (!bluetooth_adapter_->setScanMode(original_scan_mode_)) {
|
||||
// TODO(reznor): log.atWarning().log("Failed to restore original Bluetooth
|
||||
// scan mode to %d", originalScanMode);
|
||||
}
|
||||
|
||||
// Regardless of whether or not we could actually restore the Bluetooth scan
|
||||
// mode, reset our relevant state.
|
||||
original_scan_mode_ = BluetoothAdapter::ScanMode::UNKNOWN;
|
||||
original_scan_mode_ = ScanMode::kUnknown;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BluetoothClassic<Platform>::restoreDeviceName() {
|
||||
if (!bluetooth_adapter_->setName(*original_device_name_)) {
|
||||
// TODO(reznor): log.atWarning().log("Failed to restore original Bluetooth
|
||||
// device name to %s", originalDeviceName);
|
||||
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;
|
||||
}
|
||||
|
||||
// Regardless of whether or not we could actually restore the Bluetooth device
|
||||
// name, reset the marker that opens us up for business for the next time
|
||||
// 'round.
|
||||
original_device_name_.destroy();
|
||||
original_device_name_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::startDiscovery(
|
||||
Ptr<DiscoveredDeviceCallback> discovered_device_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
bool BluetoothClassic::StartDiscovery(DiscoveredDeviceCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (discovered_device_callback.isNull()) {
|
||||
// TODO(reznor): log.atSevere().log("Refusing to start discovery of
|
||||
// Bluetooth devices because a null discoveredDeviceCallback was passed
|
||||
// in.");
|
||||
return false;
|
||||
}
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<DiscoveredDeviceCallback>> scoped_discovered_device_callback(
|
||||
discovered_device_callback);
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// TODO(reznor): log.atSevere().log("Can't discover Bluetooth devices
|
||||
// because Bluetooth isn't enabled.");
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't enabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// TODO(reznor): log.atSevere().log("Can't discover Bluetooth devices
|
||||
// because Bluetooth isn't available.");
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDiscovering()) {
|
||||
// TODO(reznor): log.atSevere().log("Refusing to start discovery of
|
||||
// Bluetooth devices because another discovery is already in-progress.");
|
||||
if (IsDiscovering()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start discovery of BT devices because another "
|
||||
"discovery is already in-progress.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<BluetoothDiscoveryCallback>>
|
||||
scoped_bluetooth_discovery_callback(new BluetoothDiscoveryCallback(
|
||||
scoped_discovered_device_callback.get()));
|
||||
|
||||
if (!bluetooth_classic_medium_->startDiscovery(
|
||||
scoped_bluetooth_discovery_callback.get())) {
|
||||
// TODO(reznor): log.atSevere().log("Failed to start discovery of Bluetooth
|
||||
// devices.");
|
||||
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_ =
|
||||
MakePtr(new ScanInfo(scoped_discovered_device_callback.release(),
|
||||
scoped_bluetooth_discovery_callback.release()));
|
||||
scan_info_.valid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BluetoothClassic<Platform>::stopDiscovery() {
|
||||
Synchronized s(lock_.get());
|
||||
bool BluetoothClassic::StopDiscovery() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!isDiscovering()) {
|
||||
// TODO(reznor): log.atDebug().log("Can't stop discovery of Bluetooth
|
||||
// devices because it never started.");
|
||||
return;
|
||||
if (!IsDiscovering()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't stop discovery of BT devices because it never started.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bluetooth_classic_medium_->stopDiscovery()) {
|
||||
// TODO(reznor): log.atWarning().log("Failed to stop discovery of Bluetooth
|
||||
// devices.");
|
||||
if (!medium_.StopDiscovery()) {
|
||||
NEARBY_LOG(INFO, "Failed to stop discovery of Bluetooth devices.");
|
||||
return false;
|
||||
}
|
||||
// Regardless of whether or not stopDiscovery() succeeded, destroy scan_info_
|
||||
// to:
|
||||
//
|
||||
// a) Avoid a leak.
|
||||
// b) Mark the fact that we're no longer performing a Bluetooth discovery.
|
||||
scan_info_.destroy();
|
||||
|
||||
scan_info_.valid = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::isDiscovering() const {
|
||||
return !scan_info_.isNull();
|
||||
}
|
||||
bool BluetoothClassic::IsDiscovering() const { return scan_info_.valid; }
|
||||
|
||||
template <typename Platform>
|
||||
class AcceptLoopRunnable : public Runnable {
|
||||
public:
|
||||
AcceptLoopRunnable(
|
||||
Ptr<typename BluetoothClassic<Platform>::AcceptedConnectionCallback>
|
||||
accepted_connection_callback,
|
||||
Ptr<BluetoothServerSocket> listening_socket, const string& service_name)
|
||||
: accepted_connection_callback_(accepted_connection_callback),
|
||||
listening_socket_(listening_socket),
|
||||
service_name_(service_name) {}
|
||||
bool BluetoothClassic::StartAcceptingConnections(
|
||||
const std::string& service_name, AcceptedConnectionCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
void run() override {
|
||||
while (true) {
|
||||
ExceptionOr<Ptr<BluetoothSocket>> bluetooth_socket =
|
||||
listening_socket_->accept();
|
||||
if (!bluetooth_socket.ok()) {
|
||||
if (Exception::IO == bluetooth_socket.exception()) {
|
||||
Utils::closeSocket(listening_socket_, "Bluetooth", service_name_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
accepted_connection_callback_->onConnectionAccepted(
|
||||
bluetooth_socket.result());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedPtr<
|
||||
Ptr<typename BluetoothClassic<Platform>::AcceptedConnectionCallback>>
|
||||
accepted_connection_callback_;
|
||||
Ptr<BluetoothServerSocket> listening_socket_;
|
||||
const string service_name_;
|
||||
};
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::startAcceptingConnections(
|
||||
const string& service_name,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<AcceptedConnectionCallback>>
|
||||
scoped_accepted_connection_callback(accepted_connection_callback);
|
||||
if (scoped_accepted_connection_callback.isNull() || service_name.empty()) {
|
||||
// TODO(reznor): log.atSevere().log("Refusing to start accepting Bluetooth
|
||||
// connections because at least one of serviceName or
|
||||
// acceptedConnectionCallback is null.");
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to start accepting BT connections; service name is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// TODO(reznor): log.atSevere().log("Can't create Bluetooth server socket
|
||||
// for %s because Bluetooth isn't enabled.", serviceName);
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't create BT server socket [service=%s]; BT is disabled.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAvailable()) {
|
||||
// TODO(reznor): log.atSevere().log("Can't start accepting BLuetooth
|
||||
// connections for %s because Bluetooth isn't available.", serviceName);
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Can't start accepting BT connections [service=%s]; BT not available.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAcceptingConnections(service_name)) {
|
||||
// TODO(reznor): log.atSevere().log("Refusing to start accepting Bluetooth
|
||||
// connections for %s because a Bluetooth server is already in-progress for
|
||||
// that service name.", serviceName);
|
||||
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;
|
||||
}
|
||||
|
||||
ExceptionOr<Ptr<BluetoothServerSocket>> listening_socket =
|
||||
bluetooth_classic_medium_->listenForService(
|
||||
service_name, generateUUIDFromString(service_name));
|
||||
if (!listening_socket.ok()) {
|
||||
if (Exception::IO == listening_socket.exception()) {
|
||||
// TODO(reznor): log.atSevere().withCause(e).log("Failed to start
|
||||
// accepting Bluetooth connections for %s.", serviceName);
|
||||
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;
|
||||
}
|
||||
|
||||
// Start the accept loop on a dedicated thread - this stays alive and
|
||||
// listening for new incoming connections until stopAcceptingConnections() is
|
||||
// invoked.
|
||||
accept_loops_thread_pool_->execute(MakePtr(new AcceptLoopRunnable<Platform>(
|
||||
scoped_accepted_connection_callback.release(), listening_socket.result(),
|
||||
service_name)));
|
||||
|
||||
// Mark the fact that there's an in-progress Bluetooth server accepting
|
||||
// connections.
|
||||
bluetooth_server_sockets_.insert(
|
||||
std::make_pair(service_name, listening_socket.result()));
|
||||
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([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;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothClassic<Platform>::isAcceptingConnections(
|
||||
const string& service_name) {
|
||||
Synchronized s(lock_.get());
|
||||
bool BluetoothClassic::IsAcceptingConnections(const std::string& service_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return bluetooth_server_sockets_.find(service_name) !=
|
||||
bluetooth_server_sockets_.end();
|
||||
return IsAcceptingConnectionsLocked(service_name);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BluetoothClassic<Platform>::stopAcceptingConnections(
|
||||
const string& service_name) {
|
||||
Synchronized s(lock_.get());
|
||||
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()) {
|
||||
// TODO(ahlee): logger.atSevere().log("Unable to stop accepting Bluetooth
|
||||
// connections because the serviceName is empty.");
|
||||
return;
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to stop accepting BT connections because the "
|
||||
"service_name is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isAcceptingConnections(service_name)) {
|
||||
// TODO(reznor): log.atDebug().log("Can't stop accepting Bluetooth
|
||||
// connections for %s because it was never started.", serviceName);
|
||||
return;
|
||||
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.
|
||||
BluetoothServerSocketMap::iterator listening_socket_iter =
|
||||
bluetooth_server_sockets_.find(service_name);
|
||||
auto item = server_sockets_.extract(it);
|
||||
|
||||
// Store a handle to the BluetoothServerSocket, so we can use it after
|
||||
// removing the entry from bluetooth_server_sockets_; making it scoped
|
||||
// removing the entry from server_sockets_; making it scoped
|
||||
// is a bonus that takes care of deallocation before we leave this method.
|
||||
ScopedPtr<Ptr<BluetoothServerSocket>> scoped_listening_socket(
|
||||
listening_socket_iter->second);
|
||||
BluetoothServerSocket& listening_socket = item.mapped();
|
||||
|
||||
// Regardless of whether or not we fail to close the existing
|
||||
// BluetoothServerSocket, remove it from bluetooth_server_sockets_ so that it
|
||||
// BluetoothServerSocket, remove it from server_sockets_ so that it
|
||||
// frees up this service for another round.
|
||||
bluetooth_server_sockets_.erase(listening_socket_iter);
|
||||
|
||||
// Finally, close the BluetoothServerSocket.
|
||||
Exception::Value e = scoped_listening_socket->close();
|
||||
if (Exception::NONE != e) {
|
||||
if (Exception::IO == e) {
|
||||
// TODO(reznor): log.atSevere().withCause(e).log("Failed to close
|
||||
// Bluetooth server socket for %s.", serviceName);
|
||||
}
|
||||
if (!listening_socket.Close().Ok()) {
|
||||
NEARBY_LOG(INFO, "Failed to close BT server socket for %s.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<BluetoothSocket> BluetoothClassic<Platform>::connect(
|
||||
Ptr<BluetoothDevice> bluetooth_device, const string& service_name) {
|
||||
Synchronized s(lock_.get());
|
||||
BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device,
|
||||
const std::string& service_name) {
|
||||
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 (bluetooth_device.isNull() || service_name.empty()) {
|
||||
// TODO(reznor): log.atSevere().log("Refusing to create client Bluetooth
|
||||
// socket because at least one of bluetoothDevice or serviceName is null.");
|
||||
return Ptr<BluetoothSocket>();
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to create client BT socket because service_name is empty.");
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!bluetooth_radio_->isEnabled()) {
|
||||
// TODO(reznor): log.atSevere().log("Can't create client Bluetooth socket to
|
||||
// %s because Bluetooth isn't enabled.", bluetoothSocketName);
|
||||
return Ptr<BluetoothSocket>();
|
||||
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 (!isAvailable()) {
|
||||
// TODO(reznor): log.atSevere().log("Can't create client Bluetooth socket to
|
||||
// %s because Bluetooth isn't available.", bluetoothSocketName);
|
||||
return Ptr<BluetoothSocket>();
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Can't create client BT socket [service=%s]; BT isn't available.",
|
||||
service_name.c_str());
|
||||
return socket;
|
||||
}
|
||||
|
||||
// WARNING WARNING WARNING
|
||||
//
|
||||
// This block deviates from the corresponding Java code.
|
||||
//
|
||||
// In Java, we pause an in-progress discovery before attempting this
|
||||
// connection, and then resume it after, but the memory management of the
|
||||
// DiscoveredDeviceCallback is complicated in C++, and would need a severe
|
||||
// deviation from the Java code, so we're choosing the lesser of 2 evils, and
|
||||
// introducing this (simplifying) deviation instead -- also, this deviation is
|
||||
// fairly inconsequential since we don't yet have a use-case that needs a
|
||||
// device that:
|
||||
//
|
||||
// a) uses the C++ code,
|
||||
// b) has Bluetooth Classic support, and
|
||||
// c) plays the role of Discoverer.
|
||||
ExceptionOr<Ptr<BluetoothSocket>> bluetooth_socket =
|
||||
bluetooth_classic_medium_->connectToService(
|
||||
bluetooth_device, generateUUIDFromString(service_name));
|
||||
if (!bluetooth_socket.ok()) {
|
||||
if (Exception::IO == bluetooth_socket.exception()) {
|
||||
// TODO(reznor): log.atSevere().log("Failed to connect via Bluetooth
|
||||
// socket to %s.", bluetoothSocketName);
|
||||
}
|
||||
return Ptr<BluetoothSocket>();
|
||||
socket = medium_.ConnectToService(bluetooth_device,
|
||||
GenerateUuidFromString(service_name));
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOG(INFO, "Failed to Connect via BT [service=%s]",
|
||||
service_name.c_str());
|
||||
}
|
||||
|
||||
return bluetooth_socket.result();
|
||||
return socket;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
string BluetoothClassic<Platform>::generateUUIDFromString(const string& data) {
|
||||
return UUID<Platform>(data).str();
|
||||
BluetoothDevice BluetoothClassic::GetRemoteDevice(
|
||||
const std::string& mac_address) {
|
||||
MutexLock lock(&mutex_);
|
||||
return medium_.GetRemoteDevice(mac_address);
|
||||
}
|
||||
|
||||
std::string BluetoothClassic::GetMacAddress() const {
|
||||
MutexLock lock(&mutex_);
|
||||
return medium_.GetMacAddress();
|
||||
}
|
||||
|
||||
std::string BluetoothClassic::GenerateUuidFromString(const std::string& data) {
|
||||
return std::string(Uuid(data));
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
|
||||
@@ -2,168 +2,182 @@
|
||||
#define CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "core/internal/mediums/utils.h"
|
||||
#include "platform/api/bluetooth_adapter.h"
|
||||
#include "platform/api/bluetooth_classic.h"
|
||||
#include "platform/api/lock.h"
|
||||
#include "platform/api/multi_thread_executor.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "platform/runnable.h"
|
||||
#include "core/listeners.h"
|
||||
#include "platform/base/byte_array.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"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
template <typename Platform>
|
||||
class BluetoothClassic {
|
||||
public:
|
||||
explicit BluetoothClassic(Ptr<BluetoothRadio<Platform>> bluetooth_radio);
|
||||
~BluetoothClassic();
|
||||
|
||||
bool isAvailable();
|
||||
|
||||
bool turnOnDiscoverability(const string& device_name);
|
||||
void turnOffDiscoverability();
|
||||
|
||||
// Callback that is invoked when a nearby Bluetooth device is discovered.
|
||||
class DiscoveredDeviceCallback {
|
||||
public:
|
||||
virtual ~DiscoveredDeviceCallback() {}
|
||||
|
||||
virtual void onDeviceDiscovered(Ptr<BluetoothDevice> device) = 0;
|
||||
virtual void onDeviceNameChanged(Ptr<BluetoothDevice> device) = 0;
|
||||
virtual void onDeviceLost(Ptr<BluetoothDevice> device) = 0;
|
||||
};
|
||||
|
||||
bool startDiscovery(Ptr<DiscoveredDeviceCallback> discovered_device_callback);
|
||||
void stopDiscovery();
|
||||
using DiscoveredDeviceCallback = BluetoothClassicMedium::DiscoveryCallback;
|
||||
using ScanMode = BluetoothAdapter::ScanMode;
|
||||
|
||||
// Callback that is invoked when a new connection is accepted.
|
||||
class AcceptedConnectionCallback {
|
||||
public:
|
||||
virtual ~AcceptedConnectionCallback() {}
|
||||
|
||||
virtual void onConnectionAccepted(Ptr<BluetoothSocket> socket) = 0;
|
||||
struct AcceptedConnectionCallback {
|
||||
std::function<void(BluetoothSocket socket)> accepted_cb =
|
||||
DefaultCallback<BluetoothSocket>();
|
||||
};
|
||||
|
||||
bool startAcceptingConnections(
|
||||
const string& service_name,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback);
|
||||
bool isAcceptingConnections(const string& service_name);
|
||||
void stopAcceptingConnections(const string& service_name);
|
||||
explicit BluetoothClassic(BluetoothRadio& bluetooth_radio);
|
||||
~BluetoothClassic();
|
||||
|
||||
Ptr<BluetoothSocket> connect(Ptr<BluetoothDevice> bluetooth_device,
|
||||
const string& service_name);
|
||||
// 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 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 Connect(BluetoothDevice& bluetooth_device,
|
||||
const std::string& service_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
std::string GetMacAddress() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
BluetoothDevice GetRemoteDevice(const std::string& mac_address)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
class BluetoothDiscoveryCallback
|
||||
: public BluetoothClassicMedium::DiscoveryCallback {
|
||||
public:
|
||||
explicit BluetoothDiscoveryCallback(
|
||||
Ptr<DiscoveredDeviceCallback> discovered_device_callback)
|
||||
: discovered_device_callback_(discovered_device_callback) {}
|
||||
~BluetoothDiscoveryCallback() override {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
void onDeviceDiscovered(Ptr<BluetoothDevice> bluetooth_device) override {
|
||||
discovered_device_callback_->onDeviceDiscovered(bluetooth_device);
|
||||
}
|
||||
void onDeviceNameChanged(Ptr<BluetoothDevice> bluetooth_device) override {
|
||||
discovered_device_callback_->onDeviceNameChanged(bluetooth_device);
|
||||
}
|
||||
void onDeviceLost(Ptr<BluetoothDevice> bluetooth_device) override {
|
||||
discovered_device_callback_->onDeviceLost(bluetooth_device);
|
||||
}
|
||||
|
||||
private:
|
||||
// This could well have been a ScopedPtr, with BluetoothDiscoveryCallback in
|
||||
// turn being owned by ScanInfo (and it would have been cleaner overall,
|
||||
// since the chain of wrapped callbacks starting from
|
||||
// BluetoothDiscoveryCallback would then destruct like a stack of dominoes
|
||||
// falling, triggered by the destruction of ScanInfo), but we instead give
|
||||
// ownership of this DiscoveredDeviceCallback *and*
|
||||
// BluetoothDiscoveryCallback to ScanInfo, to maintain compatibility with
|
||||
// the Java code.
|
||||
Ptr<DiscoveredDeviceCallback> discovered_device_callback_;
|
||||
};
|
||||
|
||||
struct ScanInfo {
|
||||
ScanInfo(Ptr<DiscoveredDeviceCallback> discovered_device_callback,
|
||||
Ptr<BluetoothDiscoveryCallback> bluetooth_discovery_callback)
|
||||
: discovered_device_callback(discovered_device_callback),
|
||||
bluetooth_discovery_callback(bluetooth_discovery_callback) {}
|
||||
~ScanInfo() {
|
||||
// Nothing to do (the ScopedPtr members take care of themselves).
|
||||
}
|
||||
|
||||
// Stores the DiscoveredDeviceCallback passed in to startDiscovery() by
|
||||
// clients so that we can internally stop and start Bluetooth scans
|
||||
// transparently as needed (for example, when a call to connect() is
|
||||
// invoked).
|
||||
ScopedPtr<Ptr<DiscoveredDeviceCallback>> discovered_device_callback;
|
||||
// The ordering of bluetooth_discovery_callback_ coming after
|
||||
// discovered_device_callback_ is very deliberate --
|
||||
// bluetooth_discovery_callback_ contains a reference to
|
||||
// discovered_device_callback_, so it should be destroyed first.
|
||||
ScopedPtr<Ptr<BluetoothDiscoveryCallback>> bluetooth_discovery_callback;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static string generateUUIDFromString(const string& data);
|
||||
static constexpr int kMaxConcurrentAcceptLoops = 5;
|
||||
|
||||
static const std::int32_t kMaxConcurrentAcceptLoops;
|
||||
// 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);
|
||||
|
||||
bool isDiscoverable() const;
|
||||
bool modifyDeviceName(const string& device_name);
|
||||
bool modifyScanMode(BluetoothAdapter::ScanMode::Value scan_mode);
|
||||
void restoreScanMode();
|
||||
void restoreDeviceName();
|
||||
bool isDiscovering() const;
|
||||
// Same as IsAvailable(), but must be called with mutex_ held.
|
||||
bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// ------------ GENERAL ------------
|
||||
// Same as IsAcceptingConnections(), but must be called with mutex_ held.
|
||||
bool IsAcceptingConnectionsLocked(const std::string& service_name)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
ScopedPtr<Ptr<Lock>> lock_;
|
||||
// Returns true, if discoverability is enabled with TurnOnDiscoverability().
|
||||
bool IsDiscoverable() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// ------------ CORE BLUETOOTH ------------
|
||||
// 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_);
|
||||
|
||||
Ptr<BluetoothRadio<Platform>> bluetooth_radio_;
|
||||
ScopedPtr<Ptr<BluetoothAdapter>> bluetooth_adapter_;
|
||||
// The underlying, per-platform implementation.
|
||||
ScopedPtr<Ptr<BluetoothClassicMedium>> bluetooth_classic_medium_;
|
||||
// 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_);
|
||||
|
||||
// ------------ DISCOVERY ------------
|
||||
// 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_);
|
||||
|
||||
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.
|
||||
Ptr<ScanInfo> scan_info_;
|
||||
|
||||
// ------------ ADVERTISING ------------
|
||||
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.
|
||||
BluetoothAdapter::ScanMode::Value original_scan_mode_;
|
||||
// The original Bluetooth device name, before we modified it. If non-null, we
|
||||
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.
|
||||
Ptr<string> original_device_name_;
|
||||
std::string original_device_name_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// A thread pool dedicated to running all the accept loops from
|
||||
// startAcceptingConnections().
|
||||
ScopedPtr<Ptr<typename Platform::MultiThreadExecutorType>>
|
||||
accept_loops_thread_pool_;
|
||||
// A map of service name -> ServerSocket. While this map is non-empty, we
|
||||
// StartAcceptingConnections().
|
||||
MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops};
|
||||
|
||||
// A map of service Name -> ServerSocket. If map is non-empty, we
|
||||
// are currently listening for incoming connections.
|
||||
typedef std::map<string, Ptr<BluetoothServerSocket>> BluetoothServerSocketMap;
|
||||
BluetoothServerSocketMap bluetooth_server_sockets_;
|
||||
// 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
|
||||
|
||||
#include "core/internal/mediums/bluetooth_classic.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
#include "core/internal/mediums/bluetooth_classic.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#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"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000);
|
||||
|
||||
class BluetoothClassicTest : public ::testing::Test {
|
||||
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_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)));
|
||||
}
|
||||
|
||||
TEST_F(BluetoothClassicTest, CanConnect) {
|
||||
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();
|
||||
},
|
||||
}));
|
||||
BluetoothSocket socket_for_client =
|
||||
bt_client.Connect(discovered_device, std::string(kServiceName));
|
||||
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());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,117 +1,101 @@
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
|
||||
#include "platform/exception.h"
|
||||
#include "platform/base/exception.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
template <typename Platform>
|
||||
std::int64_t BluetoothRadio<Platform>::kPauseBetweenToggleDurationMillis = 3000;
|
||||
constexpr absl::Duration BluetoothRadio::kPauseBetweenToggle;
|
||||
|
||||
template <typename Platform>
|
||||
BluetoothRadio<Platform>::BluetoothRadio()
|
||||
: bluetooth_adapter_(Platform::createBluetoothAdapter()),
|
||||
thread_utils_(Platform::createThreadUtils()),
|
||||
originally_enabled_() {
|
||||
if (bluetooth_adapter_.isNull()) {
|
||||
// TODO(reznor): log.atSevere().log("Failed to retrieve default
|
||||
// BluetoothAdapter, Bluetooth is unsupported.");
|
||||
BluetoothRadio::BluetoothRadio() {
|
||||
if (!IsAdapterValid()) {
|
||||
NEARBY_LOG(ERROR, "Bluetooth adapter is not valid: BT is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
BluetoothRadio<Platform>::~BluetoothRadio() {
|
||||
BluetoothRadio::~BluetoothRadio() {
|
||||
// We never enabled Bluetooth, nothing to do.
|
||||
if (originally_enabled_.isNull()) {
|
||||
if (!ever_saved_state_.Get()) {
|
||||
NEARBY_LOG(INFO, "BT adapter was not used. Not touching HW.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we cleanup the one non-ScopedPtr member before we leave the
|
||||
// destructor.
|
||||
ScopedPtr<Ptr<AtomicBoolean> > scoped_originally_enabled(originally_enabled_);
|
||||
|
||||
// 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.
|
||||
toggle();
|
||||
NEARBY_LOG(INFO, "Toggle BT adapter state before releasing adapter.");
|
||||
Toggle();
|
||||
|
||||
if (!setBluetoothState(originally_enabled_->get())) {
|
||||
// TODO(reznor): log.atWarning().log("Failed to turn Bluetooth back to its
|
||||
// original state.");
|
||||
NEARBY_LOG(INFO, "Bring BT adapter to original state");
|
||||
if (!SetBluetoothState(originally_enabled_.Get())) {
|
||||
NEARBY_LOG(INFO, "Failed to restore BT adapter original state.");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothRadio<Platform>::enable() {
|
||||
if (!saveOriginalState()) {
|
||||
bool BluetoothRadio::Enable() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return setBluetoothState(true);
|
||||
return SetBluetoothState(true);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothRadio<Platform>::disable() {
|
||||
if (!saveOriginalState()) {
|
||||
bool BluetoothRadio::Disable() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return setBluetoothState(false);
|
||||
return SetBluetoothState(false);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothRadio<Platform>::isEnabled() {
|
||||
return !bluetooth_adapter_.isNull() && isInDesiredState(true);
|
||||
bool BluetoothRadio::IsEnabled() const {
|
||||
return IsAdapterValid() && IsInDesiredState(true);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void BluetoothRadio<Platform>::toggle() {
|
||||
if (!saveOriginalState()) {
|
||||
return;
|
||||
bool BluetoothRadio::Toggle() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!setBluetoothState(false)) {
|
||||
// TODO(reznor): log.atWarning().log("Failed to turn Bluetooth off while
|
||||
// toggling state.");
|
||||
if (!SetBluetoothState(false)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT off.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Exception::INTERRUPTED ==
|
||||
thread_utils_->sleep(kPauseBetweenToggleDurationMillis)) {
|
||||
// TODO(reznor): log.atSevere().withCause(e).log("Interrupted while waiting
|
||||
// in between a Bluetooth toggle.");
|
||||
return;
|
||||
if (SystemClock::Sleep(kPauseBetweenToggle).Raised(Exception::kInterrupted)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: interrupted before turing on.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!setBluetoothState(true)) {
|
||||
// TODO(reznor): log.atWarning().log("Failed to turn Bluetooth on while
|
||||
// toggling state.");
|
||||
if (!SetBluetoothState(true)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT on.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothRadio<Platform>::setBluetoothState(bool enable) {
|
||||
return bluetooth_adapter_->setStatus(
|
||||
enable ? BluetoothAdapter::Status::ENABLED
|
||||
: BluetoothAdapter::Status::DISABLED);
|
||||
bool BluetoothRadio::SetBluetoothState(bool enable) {
|
||||
return bluetooth_adapter_.SetStatus(
|
||||
enable ? BluetoothAdapter::Status::kEnabled
|
||||
: BluetoothAdapter::Status::kDisabled);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothRadio<Platform>::isInDesiredState(bool should_be_enabled) const {
|
||||
return ((should_be_enabled && bluetooth_adapter_->isEnabled()) ||
|
||||
(!should_be_enabled && !bluetooth_adapter_->isEnabled()));
|
||||
bool BluetoothRadio::IsInDesiredState(bool should_be_enabled) const {
|
||||
return bluetooth_adapter_.IsEnabled() == should_be_enabled;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool BluetoothRadio<Platform>::saveOriginalState() {
|
||||
if (bluetooth_adapter_.isNull()) {
|
||||
bool BluetoothRadio::SaveOriginalState() {
|
||||
if (!IsAdapterValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we haven't saved the original state of the radio, save it.
|
||||
if (originally_enabled_.isNull()) {
|
||||
originally_enabled_ =
|
||||
Platform::createAtomicBoolean(bluetooth_adapter_->isEnabled());
|
||||
if (!ever_saved_state_.Set(true)) {
|
||||
originally_enabled_.Set(bluetooth_adapter_.IsEnabled());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -3,20 +3,21 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "platform/api/atomic_boolean.h"
|
||||
#include "platform/api/bluetooth_adapter.h"
|
||||
#include "platform/api/thread_utils.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "platform/public/atomic_boolean.h"
|
||||
#include "platform/public/bluetooth_adapter.h"
|
||||
#include "absl/time/clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Provides the operations that can be performed on the Bluetooth radio.
|
||||
template <typename Platform>
|
||||
class BluetoothRadio {
|
||||
public:
|
||||
BluetoothRadio();
|
||||
BluetoothRadio(BluetoothRadio&&) = default;
|
||||
BluetoothRadio& operator=(BluetoothRadio&&) = default;
|
||||
|
||||
// Reverts the Bluetooth radio to its original state.
|
||||
~BluetoothRadio();
|
||||
|
||||
@@ -26,44 +27,54 @@ class BluetoothRadio {
|
||||
// this class.
|
||||
//
|
||||
// Returns true if enabled successfully.
|
||||
bool enable();
|
||||
bool Enable();
|
||||
|
||||
// Disables Bluetooth.
|
||||
//
|
||||
// Returns true if disabled successfully.
|
||||
bool disable();
|
||||
// Returns true if the Bluetooth radio is currently enabled.
|
||||
bool isEnabled();
|
||||
bool Disable();
|
||||
|
||||
void toggle();
|
||||
// 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 std::int64_t kPauseBetweenToggleDurationMillis;
|
||||
static constexpr absl::Duration kPauseBetweenToggle = absl::Seconds(3);
|
||||
|
||||
bool setBluetoothState(bool enable);
|
||||
bool isInDesiredState(bool should_be_enabled) const;
|
||||
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();
|
||||
bool SaveOriginalState();
|
||||
|
||||
// BluetoothAdapter::IsValid() will return false if BT is not supported.
|
||||
BluetoothAdapter bluetooth_adapter_;
|
||||
|
||||
// Null if the device does not support Bluetooth.
|
||||
ScopedPtr<Ptr<BluetoothAdapter>> bluetooth_adapter_;
|
||||
ScopedPtr<Ptr<ThreadUtils>> thread_utils_;
|
||||
// The Bluetooth radio's original state, before we modified it. True if
|
||||
// originally enabled, false if originally disabled, null if we never modified
|
||||
// the radio state. We restore the radio to its original state in the
|
||||
// destructor.
|
||||
//
|
||||
// This is a Ptr instead of a ScopedPtr because it's lazily initialized
|
||||
// (and ScopedPtr doesn't support re-assignment).
|
||||
Ptr<AtomicBoolean> originally_enabled_;
|
||||
// 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
|
||||
|
||||
#include "core/internal/mediums/bluetooth_radio.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
|
||||
#include "gmock/gmock.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,32 +0,0 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_
|
||||
|
||||
#include "core/internal/mediums/ble_peripheral.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
/** Callback that is invoked when a {@link BLEPeripheral} is discovered. */
|
||||
class DiscoveredPeripheralCallback {
|
||||
public:
|
||||
virtual ~DiscoveredPeripheralCallback() {}
|
||||
|
||||
virtual void onPeripheralDiscovered(Ptr<BLEPeripheral> ble_peripheral,
|
||||
const string& service_id,
|
||||
ConstPtr<ByteArray> advertisement,
|
||||
bool is_fast_advertisement) = 0;
|
||||
virtual void onPeripheralLost(Ptr<BLEPeripheral> ble_peripheral,
|
||||
const string& service_id);
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_CALLBACK_H_
|
||||
@@ -1,744 +0,0 @@
|
||||
#include "core/internal/mediums/discovered_peripheral_tracker.h"
|
||||
|
||||
#include "core/internal/mediums/ble_packet.h"
|
||||
#include "core/internal/mediums/bloom_filter.h"
|
||||
#include "core/internal/mediums/utils.h"
|
||||
#include "platform/synchronized.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace dpt {
|
||||
|
||||
template <typename K, typename V>
|
||||
void eraseOwnedPtrFromMap(std::map<K, V>& m, const K& k) {
|
||||
typename std::map<K, V>::iterator it = m.find(k);
|
||||
if (it != m.end()) {
|
||||
it->second.destroy();
|
||||
m.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
void eraseAllOwnedPtrsFromMap(std::map<K, Ptr<V>>& m) {
|
||||
for (typename std::map<K, Ptr<V>>::iterator it = m.begin(); it != m.end();
|
||||
++it) {
|
||||
it->second.destroy();
|
||||
}
|
||||
m.clear();
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
V removeOwnedPtrFromMap(std::map<K, V>& m, const K& k) {
|
||||
V removed_ptr;
|
||||
typename std::map<K, V>::iterator it = m.find(k);
|
||||
if (it != m.end()) {
|
||||
removed_ptr = it->second;
|
||||
m.erase(it);
|
||||
}
|
||||
return removed_ptr;
|
||||
}
|
||||
|
||||
} // namespace dpt
|
||||
|
||||
// The maximum number of advertisement slots to assume if we don't know the
|
||||
// exact number.
|
||||
template <typename Platform>
|
||||
const std::int32_t DiscoveredPeripheralTracker<Platform>::kMaxSlots = 10;
|
||||
|
||||
// Amount of time to wait before attempting a connection. This is needed to
|
||||
// prevent the GATT server from operation overload if we just came from a GATT
|
||||
// discovery.
|
||||
template <typename Platform>
|
||||
const std::int64_t
|
||||
DiscoveredPeripheralTracker<Platform>::kMinConnectionDelayMillis =
|
||||
5 * 1000; // 5 seconds
|
||||
|
||||
template <typename Platform>
|
||||
const char* DiscoveredPeripheralTracker<Platform>::kCopresenceServiceUuid =
|
||||
"0000FEF3-0000-1000-8000-00805F9B34FB";
|
||||
|
||||
template <typename Platform>
|
||||
DiscoveredPeripheralTracker<Platform>::DiscoveredPeripheralTracker()
|
||||
: lock_(Platform::createLock()),
|
||||
thread_utils_(Platform::createThreadUtils()),
|
||||
system_clock_(Platform::createSystemClock()),
|
||||
hash_utils_(Platform::createHashUtils()),
|
||||
discovered_peripheral_callbacks_(),
|
||||
lost_entity_trackers_(),
|
||||
fast_advertisement_service_uuids_(),
|
||||
advertisement_read_results_(),
|
||||
gatt_advertisements_(),
|
||||
advertisement_service_ids_(),
|
||||
advertisement_headers_(),
|
||||
mac_addresses_() {}
|
||||
|
||||
template <typename Platform>
|
||||
DiscoveredPeripheralTracker<Platform>::~DiscoveredPeripheralTracker() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
mac_addresses_.clear();
|
||||
advertisement_headers_.clear();
|
||||
advertisement_service_ids_.clear();
|
||||
// gatt_advertisements_ maps a string to a Ptr to a set of ConstPtrs. We do
|
||||
// not go and iterate through every set because those values are RefCounted.
|
||||
dpt::eraseAllOwnedPtrsFromMap(gatt_advertisements_);
|
||||
dpt::eraseAllOwnedPtrsFromMap(advertisement_read_results_);
|
||||
fast_advertisement_service_uuids_.clear();
|
||||
dpt::eraseAllOwnedPtrsFromMap(lost_entity_trackers_);
|
||||
dpt::eraseAllOwnedPtrsFromMap(discovered_peripheral_callbacks_);
|
||||
}
|
||||
|
||||
// Starts tracking discoveries for a particular service ID.
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::startTracking(
|
||||
const string& service_id,
|
||||
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback,
|
||||
const string& fast_advertisement_service_uuid) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
dpt::eraseOwnedPtrFromMap(discovered_peripheral_callbacks_, service_id);
|
||||
discovered_peripheral_callbacks_.insert(
|
||||
std::make_pair(service_id, discovered_peripheral_callback));
|
||||
|
||||
// We create a new LostEntityTracker because any pre-existing ones only
|
||||
// contain stale advertisements. LostEntityTracker also doesn't provide a
|
||||
// reset method, so creating a new one is the right way to go.
|
||||
dpt::eraseOwnedPtrFromMap(lost_entity_trackers_, service_id);
|
||||
lost_entity_trackers_.insert(std::make_pair(
|
||||
service_id,
|
||||
MakePtr(new LostEntityTracker<Platform, BLEAdvertisement>())));
|
||||
|
||||
if (!fast_advertisement_service_uuid.empty()) {
|
||||
fast_advertisement_service_uuids_.erase(service_id);
|
||||
fast_advertisement_service_uuids_.insert(
|
||||
std::make_pair(service_id, fast_advertisement_service_uuid));
|
||||
}
|
||||
|
||||
// Clear all of the GATT read results. With this cleared, we will now attempt
|
||||
// to reconnect to every peripheral we see, giving us a chance to search for
|
||||
// the new service we're now tracking.
|
||||
// See the documentation of advertisementReadResults for more information.
|
||||
dpt::eraseAllOwnedPtrsFromMap(advertisement_read_results_);
|
||||
|
||||
// Remove stale data from any previous sessions.
|
||||
clearDataForServiceId(service_id);
|
||||
}
|
||||
|
||||
// Stops tracking discoveries for a particular service ID.
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::stopTracking(
|
||||
const string& service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
fast_advertisement_service_uuids_.erase(service_id);
|
||||
dpt::eraseOwnedPtrFromMap(lost_entity_trackers_, service_id);
|
||||
dpt::eraseOwnedPtrFromMap(discovered_peripheral_callbacks_, service_id);
|
||||
}
|
||||
|
||||
// Processes a found BLE advertisement.
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::processFoundBleAdvertisement(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data,
|
||||
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<ConstPtr<BLEAdvertisementData>> scoped_advertisement_data(
|
||||
advertisement_data);
|
||||
ScopedPtr<Ptr<GattAdvertisementFetcher>> scoped_gatt_advertisement_fetcher(
|
||||
gatt_advertisement_fetcher);
|
||||
|
||||
if (getTrackedServiceIds().empty()) {
|
||||
// TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header
|
||||
// because we are not tracking any service IDs.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ble_peripheral.isNull() || scoped_advertisement_data.isNull()) {
|
||||
// TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header
|
||||
// because the given BleSighting is null or incomplete.");
|
||||
return;
|
||||
}
|
||||
|
||||
handleFastAdvertisement(ble_peripheral, scoped_advertisement_data.get());
|
||||
handleAdvertisementHeader(ble_peripheral, scoped_advertisement_data.get(),
|
||||
scoped_gatt_advertisement_fetcher.get());
|
||||
}
|
||||
|
||||
// Processes the set of lost GATT advertisements and notifies the client of any
|
||||
// lost peripherals.
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::processLostGattAdvertisements() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
std::set<string> tracked_service_ids = getTrackedServiceIds();
|
||||
for (typename std::set<string>::iterator tsi_it = tracked_service_ids.begin();
|
||||
tsi_it != tracked_service_ids.end(); ++tsi_it) {
|
||||
BLEAdvertisementSet lost_gatt_advertisements =
|
||||
lost_entity_trackers_.find(*tsi_it)->second->computeLostEntities();
|
||||
|
||||
// Clear the map state for each lost GATT advertisement and report it to the
|
||||
// client.
|
||||
for (BLEAdvertisementSet::iterator lga_it =
|
||||
lost_gatt_advertisements.begin();
|
||||
lga_it != lost_gatt_advertisements.end(); ++lga_it) {
|
||||
clearGattAdvertisement(*lga_it);
|
||||
discovered_peripheral_callbacks_.find(*tsi_it)->second->onPeripheralLost(
|
||||
generateBlePeripheral(*lga_it), *tsi_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<BLEPeripheral> DiscoveredPeripheralTracker<Platform>::generateBlePeripheral(
|
||||
ConstPtr<BLEAdvertisement> gatt_advertisement) {
|
||||
// TODO(ahlee): Reminder to port over deviceToken change.
|
||||
return MakePtr(new BLEPeripheral(BLEAdvertisement::toBytes(
|
||||
gatt_advertisement->getVersion(), gatt_advertisement->getSocketVersion(),
|
||||
gatt_advertisement->getServiceIdHash(), gatt_advertisement->getData())));
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
std::set<string> DiscoveredPeripheralTracker<Platform>::getTrackedServiceIds() {
|
||||
std::set<string> tracked_service_ids;
|
||||
for (DiscoveredPeripheralCallbackMap::iterator dpc_it =
|
||||
discovered_peripheral_callbacks_.begin();
|
||||
dpc_it != discovered_peripheral_callbacks_.end(); ++dpc_it) {
|
||||
tracked_service_ids.insert(dpc_it->first);
|
||||
}
|
||||
return tracked_service_ids;
|
||||
}
|
||||
|
||||
// Note: There is no C++ equivalent for getTrackedGattAdvertisements() because
|
||||
// we make a copy of the subset of the keys in directly in
|
||||
// clearDataForServiceId().
|
||||
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::clearDataForServiceId(
|
||||
const string& service_id) {
|
||||
BLEAdvertisementSet gatt_advertisements_to_clear;
|
||||
for (AdvertisementServiceIdMap::iterator it =
|
||||
advertisement_service_ids_.begin();
|
||||
it != advertisement_service_ids_.end(); ++it) {
|
||||
if (it->second != service_id) {
|
||||
continue;
|
||||
}
|
||||
gatt_advertisements_to_clear.insert(it->first);
|
||||
}
|
||||
|
||||
for (BLEAdvertisementSet::iterator it = gatt_advertisements_to_clear.begin();
|
||||
it != gatt_advertisements_to_clear.end(); ++it) {
|
||||
clearGattAdvertisement(*it);
|
||||
}
|
||||
}
|
||||
|
||||
// Clears out all data related to the provided GATT advertisement. This
|
||||
// includes:
|
||||
// 1. Removing the GATT advertisement from GATT advertisement keyed maps. This
|
||||
// includes advertisementServiceIds, AdvertisementHeaders, and
|
||||
// macAddresses.
|
||||
// 2. Removing the corresponding advertisement header from
|
||||
// advertisementReadResults.
|
||||
// 3. Removing the corresponding advertisement header from gattAdvertisements,
|
||||
// only if there are no remaining GATT advertisements related to that
|
||||
// header.
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::clearGattAdvertisement(
|
||||
ConstPtr<BLEAdvertisement> gatt_advertisement) {
|
||||
// BLEAdvertisement is RefCounted, so it does not need to be scoped.
|
||||
advertisement_service_ids_.erase(gatt_advertisement);
|
||||
mac_addresses_.erase(gatt_advertisement);
|
||||
|
||||
ConstPtr<BLEAdvertisementHeader> advertisement_header =
|
||||
dpt::removeOwnedPtrFromMap(advertisement_headers_, gatt_advertisement);
|
||||
typename GattAdvertisementMap::iterator ga_it =
|
||||
gatt_advertisements_.find(advertisement_header);
|
||||
if (ga_it != gatt_advertisements_.end()) {
|
||||
// Remove the GATT advertisement from the advertisement header it's
|
||||
// associated with.
|
||||
Ptr<BLEAdvertisementSet> header_gatt_advertisements = ga_it->second;
|
||||
header_gatt_advertisements->erase(gatt_advertisement);
|
||||
|
||||
// Unconditionally remove the header from advertisementReadResults so we
|
||||
// can attempt to reread the GATT advertisement if they return.
|
||||
dpt::eraseOwnedPtrFromMap(advertisement_read_results_,
|
||||
advertisement_header);
|
||||
|
||||
// If there are no more tracked GATT advertisements under this header, go
|
||||
// ahead and remove it from gattAdvertisements.
|
||||
if (header_gatt_advertisements->empty()) {
|
||||
dpt::eraseOwnedPtrFromMap(gatt_advertisements_, advertisement_header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::handleFastAdvertisement(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data) {
|
||||
// Extract the fast advertisement bytes, if any.
|
||||
ScopedPtr<ConstPtr<ByteArray>> fast_advertisement_bytes(
|
||||
extractFastAdvertisementBytes(advertisement_data));
|
||||
if (fast_advertisement_bytes.isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a header tied to this fast advertisement. This helps us track the
|
||||
// advertisement when reporting it as lost or connecting.
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> fast_advertisement_header =
|
||||
createFastAdvertisementHeader(fast_advertisement_bytes.get());
|
||||
|
||||
// Process the fast advertisement like we would a GATT advertisement and
|
||||
// insert a placeholder AdvertisementReadResult.
|
||||
dpt::eraseOwnedPtrFromMap(advertisement_read_results_,
|
||||
fast_advertisement_header);
|
||||
advertisement_read_results_.insert(
|
||||
std::make_pair(fast_advertisement_header,
|
||||
MakePtr(new AdvertisementReadResult<Platform>())));
|
||||
|
||||
std::set<ConstPtr<ByteArray>> fast_advertisement_bytes_set;
|
||||
fast_advertisement_bytes_set.insert(fast_advertisement_bytes.get());
|
||||
handleRawGattAdvertisements(fast_advertisement_header,
|
||||
fast_advertisement_bytes_set,
|
||||
/* are_fast_advertisements= */ true);
|
||||
updateCommonStateForFoundBleAdvertisement(fast_advertisement_header,
|
||||
ble_peripheral->getId());
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::handleAdvertisementHeader(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data,
|
||||
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher) {
|
||||
// Attempt to parse the advertisement header.
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header =
|
||||
BLEAdvertisementHeader::fromString(
|
||||
extractAdvertisementHeaderBytes(ble_peripheral, advertisement_data));
|
||||
if (advertisement_header.isNull()) {
|
||||
// TODO(ahlee) logger.atVerbose().log("Failed to deserialize BLE
|
||||
// advertisement header %s. Ignoring.",
|
||||
// bytesToString(advertisementHeaderBytes));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the advertisement header contains a service ID we're tracking.
|
||||
if (!isInterestingAdvertisementHeader(advertisement_header)) {
|
||||
// TODO(ahlee) logger.atVerbose().log("Ignoring BLE advertisement header %s
|
||||
// because it does not contain any service IDs we're interested in.",
|
||||
// advertisementHeader);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine whether or not we need to read a fresh GATT advertisement.
|
||||
if (shouldReadFromAdvertisementGattServer(advertisement_header)) {
|
||||
// Determine whether or not we need to read a fresh GATT advertisement.
|
||||
std::set<ConstPtr<ByteArray>> raw_gatt_advertisements =
|
||||
fetchRawGattAdvertisements(ble_peripheral, advertisement_header,
|
||||
gatt_advertisement_fetcher);
|
||||
if (!raw_gatt_advertisements.empty()) {
|
||||
handleRawGattAdvertisements(advertisement_header, raw_gatt_advertisements,
|
||||
/* are_fast_advertisements= */ false);
|
||||
}
|
||||
}
|
||||
|
||||
// Regardless of whether or not we read a new GATT advertisement, the maps
|
||||
// should now be up-to-date. With this information, do some general
|
||||
// housekeeping.
|
||||
updateCommonStateForFoundBleAdvertisement(
|
||||
advertisement_header, /* mac_address= */ ble_peripheral->getId());
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
string DiscoveredPeripheralTracker<Platform>::extractAdvertisementHeaderBytes(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data) {
|
||||
ConstPtr<ByteArray> service_data;
|
||||
std::map<string, ConstPtr<ByteArray>>::const_iterator sd_it =
|
||||
advertisement_data->service_data.find(kCopresenceServiceUuid);
|
||||
if (sd_it != advertisement_data->service_data.end()) {
|
||||
service_data = sd_it->second;
|
||||
}
|
||||
const string& local_name = advertisement_data->local_name; // alias
|
||||
|
||||
// A valid advertisement header lives in either the local name (iOS) or the
|
||||
// service data (Android).
|
||||
if (!service_data.isNull()) {
|
||||
// TODO(ahlee) logger.atVerbose().log("Service data found on possible
|
||||
// Android BLE peripheral at address %s",
|
||||
// bleSighting.getDevice().getAddress());
|
||||
return string(service_data->getData(), service_data->size());
|
||||
} else if (!local_name.empty()) {
|
||||
// TODO(ahlee) logger.atVerbose().log("Local name found on possible iOS BLE
|
||||
// peripheral at address %s", bleSighting.getDevice().getAddress());
|
||||
return local_name;
|
||||
} else {
|
||||
// iOS peripherals have a bug where the local name sometimes doesn't appear.
|
||||
// In that case, we should still take a look at the advertisement in case
|
||||
// there's something valuable on the peripheral's GATT server.
|
||||
|
||||
// TODO(ahlee) logger.atVerbose().log("BLE advertisement found with no
|
||||
// service data or local name from BLE peripheral at address %s (could be a
|
||||
// buggy iOS peripheral with a missing local name).",
|
||||
// bleSighting.getDevice().getAddress());
|
||||
|
||||
// Create a phony BloomFilter that always contains the service ID we're
|
||||
// looking for.
|
||||
return createDummyAdvertisementHeaderBytes(ble_peripheral);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
ConstPtr<ByteArray>
|
||||
DiscoveredPeripheralTracker<Platform>::extractFastAdvertisementBytes(
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data) {
|
||||
ConstPtr<ByteArray> fast_advertisement_bytes;
|
||||
// Iterate through all tracked service IDs to see if any of their fast
|
||||
// advertisements are contained within this BLE advertisement.
|
||||
std::set<string> tracked_service_ids = getTrackedServiceIds();
|
||||
for (typename std::set<string>::iterator tsi_it = tracked_service_ids.begin();
|
||||
tsi_it != tracked_service_ids.end(); ++tsi_it) {
|
||||
// First, check if a service UUID is tied to this service ID.
|
||||
typename FastAdvertisementServiceUUIDMap::iterator fasu_it =
|
||||
fast_advertisement_service_uuids_.find(*tsi_it);
|
||||
if (fasu_it != fast_advertisement_service_uuids_.end()) {
|
||||
const string& fast_advertisement_service_uuid = fasu_it->second; // alias
|
||||
|
||||
// Then, check if there's service data for this fast advertisement
|
||||
// service UUID. If so, we can short-circuit since all BLE
|
||||
// advertisements can contain at most ONE fast advertisement.
|
||||
typename std::map<string, ConstPtr<ByteArray>>::const_iterator sd_it =
|
||||
advertisement_data->service_data.find(
|
||||
fast_advertisement_service_uuid);
|
||||
if (sd_it != advertisement_data->service_data.end()) {
|
||||
// TODO(b/117432693): Remove this copy once Ptr is fully RefCounted.
|
||||
fast_advertisement_bytes = MakeConstPtr(
|
||||
new ByteArray(sd_it->second->getData(), sd_it->second->size()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fast_advertisement_bytes;
|
||||
}
|
||||
|
||||
// Creates an advertisement header that's purely a hash of the fast
|
||||
// advertisement, since they come with no header.
|
||||
template <typename Platform>
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader>
|
||||
DiscoveredPeripheralTracker<Platform>::createFastAdvertisementHeader(
|
||||
ConstPtr<ByteArray> fast_advertisement_bytes) {
|
||||
// Our end goal is to have a fully zeroed-out byte array of the correct length
|
||||
// representing an empty bloom filter.
|
||||
// TODO(b/149938110): remove ScopedPtr.
|
||||
ScopedPtr<ConstPtr<ByteArray>> bloom_filter_bytes{ConstPtr<ByteArray>{
|
||||
new ByteArray{BLEAdvertisementHeader::kServiceIdBloomFilterLength}}};
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray>> advertisement_hash(
|
||||
generateAdvertisementHash(fast_advertisement_bytes));
|
||||
return MakeRefCountedConstPtr(new BLEAdvertisementHeader(
|
||||
BLEAdvertisementHeader::Version::V2, /* num_slots= */ 1,
|
||||
bloom_filter_bytes.get(), advertisement_hash.get()));
|
||||
}
|
||||
|
||||
// Creates a dummy advertisement header that possibly contains all tracked
|
||||
// service IDs.
|
||||
template <typename Platform>
|
||||
string
|
||||
DiscoveredPeripheralTracker<Platform>::createDummyAdvertisementHeaderBytes(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral) {
|
||||
// Put the service ID along with the dummy service ID into our bloom filter
|
||||
// Note: BloomFilter length should always match
|
||||
// BLEAdvertisementHeader::kServiceIdBloomFilterLength
|
||||
ScopedPtr<Ptr<BloomFilter<10>>> bloom_filter(new BloomFilter<10>());
|
||||
|
||||
std::set<string> tracked_service_ids = getTrackedServiceIds();
|
||||
for (typename std::set<string>::iterator tsi_it = tracked_service_ids.begin();
|
||||
tsi_it != tracked_service_ids.end(); ++tsi_it) {
|
||||
bloom_filter->add(*tsi_it);
|
||||
}
|
||||
|
||||
const string& ble_peripheral_id = ble_peripheral->getId(); // alias
|
||||
ScopedPtr<ConstPtr<ByteArray>> ble_peripheral_id_bytes(MakeConstPtr(
|
||||
new ByteArray(ble_peripheral_id.data(), ble_peripheral_id.size())));
|
||||
ScopedPtr<ConstPtr<ByteArray>> advertisement_hash(
|
||||
generateAdvertisementHash(ble_peripheral_id_bytes.get()));
|
||||
return BLEAdvertisementHeader::asString(BLEAdvertisementHeader::Version::V2,
|
||||
kMaxSlots, bloom_filter->asBytes(),
|
||||
advertisement_hash.get());
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool DiscoveredPeripheralTracker<Platform>::isInterestingAdvertisementHeader(
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header) {
|
||||
ScopedPtr<Ptr<BloomFilter<10>>> bloom_filter(
|
||||
new BloomFilter<10>(advertisement_header->getServiceIdBloomFilter()));
|
||||
std::set<string> tracked_service_ids = getTrackedServiceIds();
|
||||
for (typename std::set<string>::iterator tsi_it = tracked_service_ids.begin();
|
||||
tsi_it != tracked_service_ids.end(); ++tsi_it) {
|
||||
if (bloom_filter->possiblyContains(*tsi_it)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool DiscoveredPeripheralTracker<Platform>::
|
||||
shouldReadFromAdvertisementGattServer(
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader>
|
||||
advertisement_header) {
|
||||
// Check if we have never seen this header. New headers should always be read.
|
||||
typename AdvertisementReadResultMap::iterator arr_it =
|
||||
advertisement_read_results_.find(advertisement_header);
|
||||
if (arr_it == advertisement_read_results_.end()) {
|
||||
// TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but
|
||||
// we have never seen it before. Will try reading its GATT advertisement.",
|
||||
// advertisementHeader);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Extract the last read result for this particular header.
|
||||
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result =
|
||||
arr_it->second; // alias
|
||||
|
||||
// Now evaluate if we should retry reading.
|
||||
switch (advertisement_read_result->evaluateRetryStatus()) {
|
||||
case AdvertisementReadResult<Platform>::RetryStatus::RETRY:
|
||||
// TODO(ahlee) logger.atDebug().log("Received advertisement header %s.
|
||||
// Will retry reading its GATT advertisement.", advertisementHeader);
|
||||
return true;
|
||||
case AdvertisementReadResult<Platform>::RetryStatus::PREVIOUSLY_SUCCEEDED:
|
||||
// TODO(ahlee) logger.atVerbose().log("Received advertisement header %s,
|
||||
// but we have already read its GATT advertisement.",
|
||||
// advertisementHeader);
|
||||
return false;
|
||||
case AdvertisementReadResult<Platform>::RetryStatus::TOO_SOON:
|
||||
// TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but
|
||||
// we have recently failed to read its GATT advertisement.",
|
||||
// advertisementHeader);
|
||||
return false;
|
||||
case AdvertisementReadResult<Platform>::RetryStatus::UNKNOWN:
|
||||
// Fall through.
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(ahlee) logger.atDebug().log("Received advertisement header %s, but we
|
||||
// do not know whether or not to retry reading its GATT advertisement. Will
|
||||
// retry to be safe.", advertisementHeader);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
std::set<ConstPtr<ByteArray>>
|
||||
DiscoveredPeripheralTracker<Platform>::fetchRawGattAdvertisements(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
|
||||
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher) {
|
||||
Ptr<AdvertisementReadResult<Platform>> old_advertisement_read_result;
|
||||
typename AdvertisementReadResultMap::iterator arr_it =
|
||||
advertisement_read_results_.find(advertisement_header);
|
||||
if (arr_it != advertisement_read_results_.end()) {
|
||||
old_advertisement_read_result = arr_it->second; // alias
|
||||
}
|
||||
|
||||
/* RefCounted */ Ptr<AdvertisementReadResult<Platform>>
|
||||
advertisement_read_result =
|
||||
gatt_advertisement_fetcher->fetchGattAdvertisements(
|
||||
ble_peripheral, advertisement_header->getNumSlots(),
|
||||
old_advertisement_read_result);
|
||||
|
||||
dpt::eraseOwnedPtrFromMap(advertisement_read_results_, advertisement_header);
|
||||
arr_it = advertisement_read_results_
|
||||
.insert(std::make_pair(advertisement_header,
|
||||
advertisement_read_result))
|
||||
.first;
|
||||
|
||||
return arr_it->second->getAdvertisements();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::handleRawGattAdvertisements(
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
|
||||
const std::set<ConstPtr<ByteArray>>& raw_gatt_advertisements,
|
||||
bool are_fast_advertisements) {
|
||||
typedef std::map<string, ConstPtr<BLEAdvertisement>> BLEAdvertisementMap;
|
||||
// Parse the raw GATT advertisements. The output of this method is a mapping
|
||||
// of service ID -> GATT advertisement.
|
||||
BLEAdvertisementMap parsed_gatt_advertisements =
|
||||
parseRawGattAdvertisements(raw_gatt_advertisements);
|
||||
ScopedPtr<Ptr<BLEAdvertisementSet>> parsed_gatt_advertisement_values(
|
||||
new BLEAdvertisementSet());
|
||||
|
||||
// Update state for each GATT advertisement.
|
||||
for (BLEAdvertisementMap::iterator pga_it =
|
||||
parsed_gatt_advertisements.begin();
|
||||
pga_it != parsed_gatt_advertisements.end(); ++pga_it) {
|
||||
const string& service_id = pga_it->first; // alias
|
||||
ConstPtr<BLEAdvertisement> gatt_advertisement = pga_it->second; // alias
|
||||
parsed_gatt_advertisement_values->insert(gatt_advertisement);
|
||||
|
||||
// TODO(ahlee): Update the java code to create old_advertisement_header
|
||||
// within the if/else block.
|
||||
AdvertisementHeaderMap::iterator ah_it =
|
||||
advertisement_headers_.find(gatt_advertisement);
|
||||
if (ah_it == advertisement_headers_.end()) {
|
||||
discovered_peripheral_callbacks_.find(service_id)
|
||||
->second->onPeripheralDiscovered(
|
||||
generateBlePeripheral(gatt_advertisement), service_id,
|
||||
gatt_advertisement->getData(), are_fast_advertisements);
|
||||
} else {
|
||||
ConstPtr<BLEAdvertisementHeader> old_advertisement_header =
|
||||
ah_it->second; // alias
|
||||
dpt::eraseOwnedPtrFromMap(advertisement_read_results_,
|
||||
old_advertisement_header);
|
||||
dpt::eraseOwnedPtrFromMap(gatt_advertisements_, old_advertisement_header);
|
||||
}
|
||||
|
||||
dpt::eraseOwnedPtrFromMap(advertisement_headers_, gatt_advertisement);
|
||||
advertisement_headers_.insert(
|
||||
std::make_pair(gatt_advertisement, advertisement_header));
|
||||
|
||||
advertisement_service_ids_.erase(gatt_advertisement);
|
||||
advertisement_service_ids_.insert(
|
||||
std::make_pair(gatt_advertisement, service_id));
|
||||
}
|
||||
|
||||
// Insert the list of read GATT advertisements for this advertisement header.
|
||||
dpt::eraseOwnedPtrFromMap(gatt_advertisements_, advertisement_header);
|
||||
gatt_advertisements_.insert(std::make_pair(
|
||||
advertisement_header, parsed_gatt_advertisement_values.release()));
|
||||
}
|
||||
|
||||
// Returns a map of service IDs to GATT advertisements who belong to a tracked
|
||||
// service ID.
|
||||
template <typename Platform>
|
||||
std::map<string, ConstPtr<BLEAdvertisement>>
|
||||
DiscoveredPeripheralTracker<Platform>::parseRawGattAdvertisements(
|
||||
const std::set<ConstPtr<ByteArray>>& raw_gatt_advertisements) {
|
||||
std::set<string> tracked_service_ids = getTrackedServiceIds();
|
||||
typedef std::map<string, ConstPtr<BLEAdvertisement>> BLEAdvertisementMap;
|
||||
BLEAdvertisementMap parsed_gatt_advertisements;
|
||||
for (std::set<ConstPtr<ByteArray>>::iterator rga_it =
|
||||
raw_gatt_advertisements.begin();
|
||||
rga_it != raw_gatt_advertisements.end(); ++rga_it) {
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisement> gatt_advertisement =
|
||||
BLEAdvertisement::fromBytes(*rga_it);
|
||||
if (gatt_advertisement.isNull()) {
|
||||
// logger.atDebug().log("Unable to parse raw GATT advertisement %s",
|
||||
// *rga_it);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure the advertisement belongs to a service ID we're tracking.
|
||||
for (typename std::set<string>::iterator tsi_it =
|
||||
tracked_service_ids.begin();
|
||||
tsi_it != tracked_service_ids.end(); ++tsi_it) {
|
||||
// If we already found a higher version advertisement for this service ID,
|
||||
// there's no point in comparing this advertisement against it.
|
||||
BLEAdvertisementMap::iterator pga_it =
|
||||
parsed_gatt_advertisements.find(*tsi_it);
|
||||
if (pga_it != parsed_gatt_advertisements.end()) {
|
||||
if (pga_it->second->getVersion() > gatt_advertisement->getVersion()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Map the service ID to the advertisement if the service ID hashes match.
|
||||
ScopedPtr<ConstPtr<ByteArray>> service_id_hash(
|
||||
generateServiceIdHash(gatt_advertisement->getVersion(), *tsi_it));
|
||||
if (*service_id_hash == *(gatt_advertisement->getServiceIdHash())) {
|
||||
// logger.atDebug().log("Matched service ID %s to GATT advertisement
|
||||
// %s.", serviceId, gattAdvertisement);
|
||||
parsed_gatt_advertisements.insert(
|
||||
std::make_pair(*tsi_it, gatt_advertisement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parsed_gatt_advertisements;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void DiscoveredPeripheralTracker<Platform>::
|
||||
updateCommonStateForFoundBleAdvertisement(
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
|
||||
const string& mac_address) {
|
||||
typename GattAdvertisementMap::iterator ga_it =
|
||||
gatt_advertisements_.find(advertisement_header);
|
||||
if (ga_it == gatt_advertisements_.end()) {
|
||||
// logger.atDebug().log("No GATT advertisements found for advertisement
|
||||
// header %s.", advertisementHeader);
|
||||
return;
|
||||
}
|
||||
|
||||
Ptr<BLEAdvertisementSet> saved_gatt_advertisements = ga_it->second; // alias
|
||||
for (BLEAdvertisementSet::iterator sga_it =
|
||||
saved_gatt_advertisements->begin();
|
||||
sga_it != saved_gatt_advertisements->end(); ++sga_it) {
|
||||
ConstPtr<BLEAdvertisement> gatt_advertisement = *sga_it; // alias
|
||||
|
||||
AdvertisementServiceIdMap::iterator asi_it =
|
||||
advertisement_service_ids_.find(gatt_advertisement);
|
||||
if (asi_it == advertisement_service_ids_.end()) {
|
||||
continue;
|
||||
}
|
||||
const string& service_id = asi_it->second; // alias
|
||||
|
||||
// Make sure the stored GATT advertisement is still being tracked.
|
||||
std::set<string> tracked_service_ids = getTrackedServiceIds();
|
||||
if (tracked_service_ids.find(service_id) == tracked_service_ids.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The iterator returned from find() is guaranteed to be valid because it's
|
||||
// tied to discovered_peripheral_callbacks_, whose keyset is checked through
|
||||
// getTrackedServiceIds() above.
|
||||
lost_entity_trackers_.find(service_id)
|
||||
->second->recordFoundEntity(gatt_advertisement);
|
||||
|
||||
// The iterator returned from find() is guaranteed to be valid because it's
|
||||
// tied to advertisement_service_ids_ which is checked at the beginning of
|
||||
// the for loop.
|
||||
mac_addresses_.erase(gatt_advertisement);
|
||||
mac_addresses_.insert(std::make_pair(gatt_advertisement, mac_address));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
ConstPtr<ByteArray>
|
||||
DiscoveredPeripheralTracker<Platform>::generateAdvertisementHash(
|
||||
ConstPtr<ByteArray> advertisement_bytes) {
|
||||
return Utils::sha256Hash(hash_utils_.get(), advertisement_bytes,
|
||||
BLEAdvertisementHeader::kAdvertisementHashLength);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
ConstPtr<ByteArray>
|
||||
DiscoveredPeripheralTracker<Platform>::generateServiceIdHash(
|
||||
BLEAdvertisement::Version::Value version, const string& service_id) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> service_id_bytes(
|
||||
MakeConstPtr(new ByteArray(service_id.data(), service_id.size())));
|
||||
switch (version) {
|
||||
case BLEAdvertisement::Version::V1:
|
||||
return Utils::legacySha256HashOnlyForPrinting(
|
||||
hash_utils_.get(), service_id_bytes.get(),
|
||||
BLEPacket::kServiceIdHashLength);
|
||||
case BLEAdvertisement::Version::V2:
|
||||
// Fall through.
|
||||
case BLEAdvertisement::Version::UNKNOWN:
|
||||
// Fall through.
|
||||
default:
|
||||
// Use the latest known hashing scheme.
|
||||
return Utils::sha256Hash(hash_utils_.get(), service_id_bytes.get(),
|
||||
BLEPacket::kServiceIdHashLength);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,218 +0,0 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "core/internal/mediums/advertisement_read_result.h"
|
||||
#include "core/internal/mediums/ble_advertisement.h"
|
||||
#include "core/internal/mediums/ble_advertisement_header.h"
|
||||
#include "core/internal/mediums/ble_peripheral.h"
|
||||
#include "core/internal/mediums/discovered_peripheral_callback.h"
|
||||
#include "core/internal/mediums/lost_entity_tracker.h"
|
||||
#include "platform/api/ble_v2.h"
|
||||
#include "platform/api/hash_utils.h"
|
||||
#include "platform/api/lock.h"
|
||||
#include "platform/api/system_clock.h"
|
||||
#include "platform/api/thread_utils.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Manages all discovered peripheral logic for {@link BluetoothLowEnergy}. This
|
||||
// includes tracking found peripherals, lost peripherals, and MAC addresses
|
||||
// associated with those peripherals.
|
||||
//
|
||||
// See go/ble-on-lost for more information. It includes the algorithms used to
|
||||
// compute found and lost peripherals.
|
||||
template <typename Platform>
|
||||
class DiscoveredPeripheralTracker {
|
||||
public:
|
||||
DiscoveredPeripheralTracker();
|
||||
~DiscoveredPeripheralTracker();
|
||||
|
||||
void startTracking(
|
||||
const string& service_id,
|
||||
Ptr<DiscoveredPeripheralCallback> discovered_peripheral_callback,
|
||||
const string& fast_advertisement_service_uuid);
|
||||
void stopTracking(const string& service_id);
|
||||
|
||||
// GATT advertisement fetcher.
|
||||
class GattAdvertisementFetcher {
|
||||
public:
|
||||
virtual ~GattAdvertisementFetcher() {}
|
||||
|
||||
// Fetches relevant GATT advertisements for the peripheral found in {@link
|
||||
// DiscoveredPeripheralTracker#processFoundBleAdvertisement(BleSighting,
|
||||
// GattAdvertisementFetcher)}.
|
||||
virtual Ptr<AdvertisementReadResult<Platform>> fetchGattAdvertisements(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral, std::int32_t num_slots,
|
||||
Ptr<AdvertisementReadResult<Platform>> advertisement_read_result) = 0;
|
||||
};
|
||||
void processFoundBleAdvertisement(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data,
|
||||
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher);
|
||||
void processLostGattAdvertisements();
|
||||
|
||||
// TODO(ahlee): Add connecting logic.
|
||||
|
||||
private:
|
||||
static Ptr<BLEPeripheral> generateBlePeripheral(
|
||||
ConstPtr<BLEAdvertisement> gatt_advertisement);
|
||||
|
||||
static const std::int32_t kMaxSlots;
|
||||
static const std::int64_t kMinConnectionDelayMillis;
|
||||
static const char* kCopresenceServiceUuid;
|
||||
|
||||
std::set<string> getTrackedServiceIds();
|
||||
void clearDataForServiceId(const string& service_id);
|
||||
void clearGattAdvertisement(ConstPtr<BLEAdvertisement> gatt_advertisement);
|
||||
void handleFastAdvertisement(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data);
|
||||
void handleAdvertisementHeader(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data,
|
||||
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher);
|
||||
string extractAdvertisementHeaderBytes(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data);
|
||||
ConstPtr<ByteArray> extractFastAdvertisementBytes(
|
||||
ConstPtr<BLEAdvertisementData> advertisement_data);
|
||||
/*RefCounted */ ConstPtr<BLEAdvertisementHeader>
|
||||
createFastAdvertisementHeader(ConstPtr<ByteArray> fast_advertisement_bytes);
|
||||
string createDummyAdvertisementHeaderBytes(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral);
|
||||
bool isInterestingAdvertisementHeader(
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header);
|
||||
bool shouldReadFromAdvertisementGattServer(
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header);
|
||||
std::set<ConstPtr<ByteArray>> fetchRawGattAdvertisements(
|
||||
Ptr<BLEPeripheralV2> ble_peripheral,
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
|
||||
Ptr<GattAdvertisementFetcher> gatt_advertisement_fetcher);
|
||||
void handleRawGattAdvertisements(
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
|
||||
const std::set<ConstPtr<ByteArray>>& raw_gatt_advertisements,
|
||||
bool are_fast_advertisements);
|
||||
std::map<string, ConstPtr<BLEAdvertisement>> parseRawGattAdvertisements(
|
||||
const std::set<ConstPtr<ByteArray>>& raw_gatt_advertisements);
|
||||
void updateCommonStateForFoundBleAdvertisement(
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader> advertisement_header,
|
||||
const string& mac_address);
|
||||
|
||||
// TODO(ahlee): Add in connecting logic.
|
||||
|
||||
// TODO(ahlee): Move these out to utils (also used by BLE V2).
|
||||
ConstPtr<ByteArray> generateAdvertisementHash(
|
||||
ConstPtr<ByteArray> advertisement_bytes);
|
||||
ConstPtr<ByteArray> generateServiceIdHash(
|
||||
BLEAdvertisement::Version::Value version, const string& service_id);
|
||||
|
||||
// ------------ GENERAL ------------
|
||||
ScopedPtr<Ptr<Lock>> lock_;
|
||||
ScopedPtr<Ptr<ThreadUtils>> thread_utils_;
|
||||
ScopedPtr<Ptr<SystemClock>> system_clock_;
|
||||
ScopedPtr<Ptr<HashUtils>> hash_utils_;
|
||||
|
||||
// ------------ SERVICE ID MAPS ------------
|
||||
// Entries in these maps all follow the same lifecycle. Entries are added in
|
||||
// startTracking, and removed in stopTracking.
|
||||
|
||||
// Maps service IDs to DiscoveredPeripheralCallbacks. Tracks what service IDs
|
||||
// are currently active and gives us client callbacks to call.
|
||||
typedef std::map<string, Ptr<DiscoveredPeripheralCallback>>
|
||||
DiscoveredPeripheralCallbackMap;
|
||||
DiscoveredPeripheralCallbackMap discovered_peripheral_callbacks_;
|
||||
|
||||
// Maps service IDs to LostEntityTrackers. Used to periodically compute lost
|
||||
// GATT advertisements, grouped by service ID.
|
||||
typedef std::map<string, Ptr<LostEntityTracker<Platform, BLEAdvertisement>>>
|
||||
LostEntityTrackerMap;
|
||||
LostEntityTrackerMap lost_entity_trackers_;
|
||||
|
||||
// Maps service IDs to BLE service UUIDs. Used to check for fast
|
||||
// advertisements delivered through BLE advertisement service data, under the
|
||||
// given UUID.
|
||||
// UUIDs are represented as strings in this map because they are coming from
|
||||
// AdvertisingOptions and our UUID class is an internal concept that we don't
|
||||
// want to expose to clients.
|
||||
typedef std::map<string, string> FastAdvertisementServiceUUIDMap;
|
||||
FastAdvertisementServiceUUIDMap fast_advertisement_service_uuids_;
|
||||
|
||||
// ------------ ADVERTISEMENT HEADER MAPS ------------
|
||||
|
||||
// Maps advertisement headers to AdvertisementReadResults. Tells us when to
|
||||
// retry reading a GATT advertisement. If no entry exists for a particular
|
||||
// header, we should try reading a GATT advertisement. Entries are added
|
||||
// whenever a GATT advertisement read is attempted, and removed when GATT
|
||||
// advertisements are lost. Entries are also removed whenever
|
||||
// gattAdvertisements removes its entry.
|
||||
//
|
||||
// The map is also cleared whenever startTracking is called, due to client
|
||||
// changes. For example, say clients A and B start scanning and discover
|
||||
// advertisements A and B (for both clients) on advertisement header 1. Then,
|
||||
// A restarts scanning, causing us to clear stale advertisement A. However,
|
||||
// since B was still scanning, we don't remove advertisement header 1 from the
|
||||
// map. This causes us to never re-read advertisement A.
|
||||
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisementHeader>,
|
||||
Ptr<AdvertisementReadResult<Platform>>>
|
||||
AdvertisementReadResultMap;
|
||||
AdvertisementReadResultMap advertisement_read_results_;
|
||||
|
||||
// Maps advertisement headers to a set of GATT advertisements from a single
|
||||
// peripheral. Used to retrieve GATT advertisements that we need to reprocess
|
||||
// every time a header is seen. Entries are added when GATT advertisements are
|
||||
// read, removed when all associated GATT advertisements are lost or become
|
||||
// stale, and replaced when the advertisement header is updated for a single
|
||||
// remote peripheral.
|
||||
typedef std::set</* RefCounted */ ConstPtr<BLEAdvertisement>>
|
||||
BLEAdvertisementSet;
|
||||
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisementHeader>,
|
||||
Ptr<BLEAdvertisementSet>>
|
||||
GattAdvertisementMap;
|
||||
GattAdvertisementMap gatt_advertisements_;
|
||||
|
||||
// ------------ GATT ADVERTISEMENT MAPS ------------
|
||||
// Entries in these maps all follow the same lifecycle. Entries are added when
|
||||
// GATT advertisements are read, and removed when GATT advertisements are lost
|
||||
// or become stale.
|
||||
|
||||
// Maps GATT advertisements to the service ID it's associated with. Tracks
|
||||
// what GATT advertisements are currently active. Used to determine which
|
||||
// LostEntityTracker to invoke when advertisements are rediscovered.
|
||||
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisement>, string>
|
||||
AdvertisementServiceIdMap;
|
||||
AdvertisementServiceIdMap advertisement_service_ids_;
|
||||
|
||||
// Maps GATT advertisements to advertisement headers. Used to efficiently find
|
||||
// advertisement headers to delete when GATT advertisements are updated. This
|
||||
// is a reverse map of gatt_advertisements_.
|
||||
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisement>,
|
||||
/* RefCounted */ ConstPtr<BLEAdvertisementHeader>>
|
||||
AdvertisementHeaderMap;
|
||||
AdvertisementHeaderMap advertisement_headers_;
|
||||
|
||||
// Maps GATT advertisements to MAC addresses. Used when we need to make a
|
||||
// socket connection based off of the GATT advertisement alone. Entries are
|
||||
// modified every time a GATT advertisement's advertisement header is seen.
|
||||
typedef std::map</* RefCounted */ ConstPtr<BLEAdvertisement>, string>
|
||||
MacAddressMap;
|
||||
MacAddressMap mac_addresses_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/discovered_peripheral_tracker.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_DISCOVERED_PERIPHERAL_TRACKER_H_
|
||||
@@ -1,56 +0,0 @@
|
||||
#include "core/internal/mediums/lost_entity_tracker.h"
|
||||
|
||||
#include "platform/synchronized.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
template <typename Platform, typename Entity>
|
||||
LostEntityTracker<Platform, Entity>::LostEntityTracker()
|
||||
: lock_(Platform::createLock()),
|
||||
current_entities_(),
|
||||
previously_found_entities_() {}
|
||||
|
||||
template <typename Platform, typename Entity>
|
||||
LostEntityTracker<Platform, Entity>::~LostEntityTracker() {
|
||||
previously_found_entities_.clear();
|
||||
current_entities_.clear();
|
||||
}
|
||||
|
||||
template <typename Platform, typename Entity>
|
||||
void LostEntityTracker<Platform, Entity>::recordFoundEntity(
|
||||
ConstPtr<Entity> entity) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
current_entities_.insert(entity);
|
||||
}
|
||||
|
||||
template <typename Platform, typename Entity>
|
||||
typename LostEntityTracker<Platform, Entity>::EntitySet
|
||||
LostEntityTracker<Platform, Entity>::computeLostEntities() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
// The set of lost entities is the previously found set MINUS the currently
|
||||
// found set.
|
||||
for (typename EntitySet::iterator it = current_entities_.begin();
|
||||
it != current_entities_.end(); ++it) {
|
||||
previously_found_entities_.erase(*it);
|
||||
}
|
||||
EntitySet lost_entities(previously_found_entities_.begin(),
|
||||
previously_found_entities_.end());
|
||||
|
||||
// Update our previous and current sets.
|
||||
previously_found_entities_.clear();
|
||||
previously_found_entities_.insert(current_entities_.begin(),
|
||||
current_entities_.end());
|
||||
current_entities_.clear();
|
||||
|
||||
return lost_entities;
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,10 +1,9 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "platform/api/lock.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -14,36 +13,68 @@ 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.
|
||||
// ComputeLostEntities.
|
||||
//
|
||||
// Note: Entity must overload the < and == operators.
|
||||
template <typename Platform, typename Entity>
|
||||
template <typename Entity>
|
||||
class LostEntityTracker {
|
||||
public:
|
||||
typedef std::set<ConstPtr<Entity> > EntitySet;
|
||||
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(ConstPtr<Entity> 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();
|
||||
EntitySet ComputeLostEntities() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
ScopedPtr<Ptr<Lock> > lock_;
|
||||
EntitySet current_entities_;
|
||||
EntitySet previously_found_entities_;
|
||||
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
|
||||
|
||||
#include "core/internal/mediums/lost_entity_tracker.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "core/internal/mediums/lost_entity_tracker.h"
|
||||
|
||||
#include "platform/api/platform.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
@@ -9,111 +8,112 @@ namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
using TestPlatform = platform::ImplementationPlatform;
|
||||
|
||||
struct TestEntity {
|
||||
int id;
|
||||
|
||||
explicit TestEntity(int givenId) : id(givenId) {}
|
||||
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; }
|
||||
bool operator<(const TestEntity& other) const { return id < other.id; }
|
||||
};
|
||||
|
||||
TEST(LostEntityTracker, NoEntitiesLost) {
|
||||
LostEntityTracker<TestPlatform, TestEntity> lost_entity_tracker;
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
|
||||
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.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_2.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_3.get());
|
||||
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());
|
||||
ASSERT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Rediscover the same entities.
|
||||
lost_entity_tracker.recordFoundEntity(entity_1.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_2.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_3.get());
|
||||
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.
|
||||
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
}
|
||||
|
||||
TEST(LostEntityTracker, AllEntitiesLost) {
|
||||
LostEntityTracker<TestPlatform, TestEntity> lost_entity_tracker;
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
|
||||
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.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_2.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_3.get());
|
||||
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());
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Go through a round without rediscovering any entities.
|
||||
typename LostEntityTracker<TestPlatform, TestEntity>::EntitySet
|
||||
lost_entities = lost_entity_tracker.computeLostEntities();
|
||||
ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end());
|
||||
ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end());
|
||||
ASSERT_TRUE(lost_entities.find(entity_3.get()) != lost_entities.end());
|
||||
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(LostEntityTracker, SomeEntitiesLost) {
|
||||
LostEntityTracker<TestPlatform, TestEntity> lost_entity_tracker;
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_2(MakeConstPtr(new TestEntity(2)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_3(MakeConstPtr(new TestEntity(3)));
|
||||
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.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_2.get());
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
|
||||
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.get());
|
||||
lost_entity_tracker.recordFoundEntity(entity_3.get());
|
||||
typename LostEntityTracker<TestPlatform, TestEntity>::EntitySet
|
||||
lost_entities = lost_entity_tracker.computeLostEntities();
|
||||
ASSERT_TRUE(lost_entities.find(entity_1.get()) == lost_entities.end());
|
||||
ASSERT_TRUE(lost_entities.find(entity_2.get()) != lost_entities.end());
|
||||
ASSERT_TRUE(lost_entities.find(entity_3.get()) == lost_entities.end());
|
||||
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(LostEntityTracker, SameEntityMultipleCopies) {
|
||||
LostEntityTracker<TestPlatform, TestEntity> lost_entity_tracker;
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1(MakeConstPtr(new TestEntity(1)));
|
||||
ScopedPtr<ConstPtr<TestEntity> > entity_1_copy(
|
||||
MakeConstPtr(new TestEntity(1)));
|
||||
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.get());
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Rediscover the same entity, but through a copy of it.
|
||||
lost_entity_tracker.recordFoundEntity(entity_1_copy.get());
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1_copy);
|
||||
|
||||
// Make sure none are lost on the second round.
|
||||
ASSERT_TRUE(lost_entity_tracker.computeLostEntities().empty());
|
||||
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<TestPlatform, TestEntity>::EntitySet
|
||||
lost_entities = lost_entity_tracker.computeLostEntities();
|
||||
ASSERT_EQ(lost_entities.size(), 1);
|
||||
ASSERT_TRUE(lost_entities.find(entity_1.get()) != lost_entities.end());
|
||||
ASSERT_TRUE(lost_entities.find(entity_1_copy.get()) != lost_entities.end());
|
||||
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
|
||||
|
||||
@@ -4,44 +4,21 @@ namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
template <typename Platform>
|
||||
Mediums<Platform>::Mediums()
|
||||
: bluetooth_radio_(new BluetoothRadio<Platform>()),
|
||||
bluetooth_classic_(
|
||||
new BluetoothClassic<Platform>(bluetooth_radio_.get())),
|
||||
ble_(new BLE<Platform>(bluetooth_radio_.get())),
|
||||
ble_v2_(new mediums::BLEV2<Platform>(bluetooth_radio_.get())),
|
||||
wifi_lan_(new mediums::WifiLan<Platform>()) {}
|
||||
|
||||
template <typename Platform>
|
||||
Mediums<Platform>::~Mediums() {
|
||||
// Nothing to do.
|
||||
BluetoothRadio& Mediums::GetBluetoothRadio() {
|
||||
return bluetooth_radio_;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<BluetoothRadio<Platform> > Mediums<Platform>::bluetoothRadio() const {
|
||||
return bluetooth_radio_.get();
|
||||
BluetoothClassic& Mediums::GetBluetoothClassic() {
|
||||
return bluetooth_classic_;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<BluetoothClassic<Platform> > Mediums<Platform>::bluetoothClassic() const {
|
||||
return bluetooth_classic_.get();
|
||||
Ble& Mediums::GetBle() { return ble_; }
|
||||
|
||||
WifiLan& Mediums::GetWifiLan() {
|
||||
return wifi_lan_;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<BLE<Platform> > Mediums<Platform>::ble() const {
|
||||
return ble_.get();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<mediums::BLEV2<Platform> > Mediums<Platform>::bleV2() const {
|
||||
return ble_v2_.get();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<mediums::WifiLan<Platform> > Mediums<Platform>::wifi_lan() const {
|
||||
return wifi_lan_.get();
|
||||
}
|
||||
mediums::WebRtc& Mediums::GetWebRtc() { return webrtc_; }
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
|
||||
@@ -2,34 +2,35 @@
|
||||
#define CORE_INTERNAL_MEDIUMS_MEDIUMS_H_
|
||||
|
||||
#include "core/internal/mediums/ble.h"
|
||||
#include "core/internal/mediums/ble_v2.h"
|
||||
#include "core/internal/mediums/bluetooth_classic.h"
|
||||
#include "core/internal/mediums/bluetooth_radio.h"
|
||||
#include "core/internal/mediums/webrtc.h"
|
||||
#include "core/internal/mediums/wifi_lan.h"
|
||||
#include "platform/ptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Facilitates convenient and reliable usage of various wireless mediums.
|
||||
template <typename Platform>
|
||||
class Mediums {
|
||||
public:
|
||||
Mediums();
|
||||
// Reverts all the mediums to their original state.
|
||||
~Mediums();
|
||||
Mediums() = default;
|
||||
~Mediums() = default;
|
||||
|
||||
// Returns a handle to the Bluetooth radio.
|
||||
Ptr<BluetoothRadio<Platform> > bluetoothRadio() const;
|
||||
BluetoothRadio& GetBluetoothRadio();
|
||||
|
||||
// Returns a handle to the Bluetooth Classic medium.
|
||||
Ptr<BluetoothClassic<Platform> > bluetoothClassic() const;
|
||||
// Returns a handle to the Bluetooth Low Energy (BLE) medium.
|
||||
Ptr<BLE<Platform> > ble() const;
|
||||
// Returns a handle to V2 of the Bluetooth Low Energy (BLE) medium.
|
||||
Ptr<mediums::BLEV2<Platform> > bleV2() const;
|
||||
BluetoothClassic& GetBluetoothClassic();
|
||||
|
||||
// Returns a handle to the Ble medium.
|
||||
Ble& GetBle();
|
||||
|
||||
// Returns a handle to the Wifi-Lan medium.
|
||||
Ptr<mediums::WifiLan<Platform> > wifi_lan() const;
|
||||
WifiLan& GetWifiLan();
|
||||
|
||||
// Returns a handle to the WebRtc medium.
|
||||
mediums::WebRtc& GetWebRtc();
|
||||
|
||||
private:
|
||||
// The order of declaration is critical for both construction and
|
||||
@@ -40,17 +41,15 @@ class Mediums {
|
||||
//
|
||||
// 2) Destruction: The individual mediums should be shut down before the
|
||||
// corresponding radio.
|
||||
ScopedPtr<Ptr<BluetoothRadio<Platform> > > bluetooth_radio_;
|
||||
ScopedPtr<Ptr<BluetoothClassic<Platform> > > bluetooth_classic_;
|
||||
ScopedPtr<Ptr<BLE<Platform> > > ble_;
|
||||
ScopedPtr<Ptr<mediums::BLEV2<Platform> > > ble_v2_;
|
||||
ScopedPtr<Ptr<mediums::WifiLan<Platform> > > wifi_lan_;
|
||||
BluetoothRadio bluetooth_radio_;
|
||||
BluetoothClassic bluetooth_classic_{bluetooth_radio_};
|
||||
Ble ble_{bluetooth_radio_};
|
||||
WifiLan wifi_lan_;
|
||||
mediums::WebRtc webrtc_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/mediums.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_MEDIUMS_H_
|
||||
|
||||
@@ -1,63 +1,27 @@
|
||||
#include "core/internal/mediums/utils.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <sstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "platform/exception.h"
|
||||
#include "platform/prng.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "platform/base/prng.h"
|
||||
#include "platform/public/crypto.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
void Utils::closeSocket(Ptr<BluetoothServerSocket> socket,
|
||||
const std::string& type, const std::string& name) {
|
||||
if (!socket.isNull()) {
|
||||
Exception::Value e = socket->close();
|
||||
if (Exception::NONE != e) {
|
||||
if (Exception::IO == e) {
|
||||
// TODO(reznor): log.atWarning().withCause(e).log("Failed to close
|
||||
// %sSocket %s", type, name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// TODO(reznor): log.atVerbose().log("Closed %sSocket %s", type, name);
|
||||
}
|
||||
namespace {
|
||||
constexpr absl::string_view kUpgradeServiceIdPostfix = "_UPGRADE";
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> Utils::sha256Hash(Ptr<HashUtils> hash_utils,
|
||||
ConstPtr<ByteArray> source,
|
||||
size_t length) {
|
||||
if (source.isNull()) {
|
||||
return ConstPtr<ByteArray>();
|
||||
}
|
||||
|
||||
ScopedPtr<ConstPtr<ByteArray>> full_hash(
|
||||
hash_utils->sha256(std::string(source->getData(), source->size())));
|
||||
return MakeConstPtr(new ByteArray(full_hash->getData(), length));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> Utils::legacySha256HashOnlyForPrinting(
|
||||
Ptr<HashUtils> hash_utils, ConstPtr<ByteArray> source, size_t length) {
|
||||
if (source.isNull()) {
|
||||
return ConstPtr<ByteArray>();
|
||||
}
|
||||
|
||||
std::string formatted_hex_string = Utils::bytesToPrintableHexString(source);
|
||||
ScopedPtr<ConstPtr<ByteArray>> formatted_hex_byte_array(MakeConstPtr(
|
||||
new ByteArray(formatted_hex_string.data(), formatted_hex_string.size())));
|
||||
return Utils::sha256Hash(hash_utils, formatted_hex_byte_array.get(), length);
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> Utils::generateRandomBytes(size_t length) {
|
||||
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();
|
||||
std::uint32_t val = rng.NextUint32();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
data += val & 0xFF;
|
||||
val >>= 8;
|
||||
@@ -67,27 +31,46 @@ ConstPtr<ByteArray> Utils::generateRandomBytes(size_t length) {
|
||||
}
|
||||
}
|
||||
|
||||
return MakeConstPtr(new ByteArray(data));
|
||||
return ByteArray(data);
|
||||
}
|
||||
|
||||
std::string Utils::bytesToPrintableHexString(ConstPtr<ByteArray> bytes) {
|
||||
std::string hex_string(
|
||||
absl::BytesToHexString(std::string(bytes->getData(), bytes->size())));
|
||||
ByteArray Utils::Sha256Hash(const ByteArray& source, size_t length) {
|
||||
return Utils::Sha256Hash(std::string(source), length);
|
||||
}
|
||||
|
||||
// Print out the byte array as a space separated listing of hex bytes.
|
||||
std::ostringstream formatted_hex_string_stream;
|
||||
formatted_hex_string_stream << "[ ";
|
||||
for (int i = 0; i < hex_string.size(); i += 2) {
|
||||
formatted_hex_string_stream << "0x";
|
||||
// This is safe because we have the guarantee that hex_string is of even
|
||||
// length (because a hex encoding will always be double the size of its
|
||||
// input).
|
||||
formatted_hex_string_stream << hex_string[i] << hex_string[i + 1];
|
||||
formatted_hex_string_stream << " ";
|
||||
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 {};
|
||||
}
|
||||
formatted_hex_string_stream << "]";
|
||||
return service_id + std::string(kUpgradeServiceIdPostfix);
|
||||
}
|
||||
|
||||
return formatted_hex_string_stream.str();
|
||||
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;
|
||||
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
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_UTILS_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_UTILS_H_
|
||||
|
||||
#include "platform/api/bluetooth_classic.h"
|
||||
#include "platform/api/hash_utils.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
#include <memory>
|
||||
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -13,18 +13,12 @@ namespace connections {
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
static void closeSocket(Ptr<BluetoothServerSocket> socket,
|
||||
const std::string& type, const std::string& name);
|
||||
static ConstPtr<ByteArray> sha256Hash(Ptr<HashUtils> hash_utils,
|
||||
ConstPtr<ByteArray> source,
|
||||
size_t length);
|
||||
static ConstPtr<ByteArray> legacySha256HashOnlyForPrinting(
|
||||
Ptr<HashUtils> hash_utils, ConstPtr<ByteArray> source, size_t length);
|
||||
|
||||
static ConstPtr<ByteArray> generateRandomBytes(size_t length);
|
||||
|
||||
private:
|
||||
static std::string bytesToPrintableHexString(ConstPtr<ByteArray> bytes);
|
||||
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
|
||||
|
||||
@@ -3,30 +3,33 @@
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "platform/api/hash_utils.h"
|
||||
#include "platform/ptr.h"
|
||||
#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
|
||||
|
||||
template <typename Platform>
|
||||
UUID<Platform>::UUID(const string& data) {
|
||||
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.
|
||||
ScopedPtr<Ptr<HashUtils> > scoped_hash_utils(Platform::createHashUtils());
|
||||
ScopedPtr<ConstPtr<ByteArray> > scoped_md5_bytes(
|
||||
scoped_hash_utils->md5(data));
|
||||
data_.assign(scoped_md5_bytes->getData(), scoped_md5_bytes->size());
|
||||
|
||||
data_[6] &= 0x0f; // Clear version.
|
||||
data_[6] |= 0x30; // Set to version 3.
|
||||
data_[8] &= 0x3f; // Clear variant.
|
||||
data_[8] |= 0x80; // Set to IETF variant.
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
UUID<Platform>::UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits) {
|
||||
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));
|
||||
@@ -50,47 +53,19 @@ UUID<Platform>::UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits) {
|
||||
data_[15] = static_cast<char>((least_sig_bits >> 0) & 0x0ff);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
UUID<Platform>::~UUID() {}
|
||||
|
||||
template <typename Platform>
|
||||
string UUID<Platform>::str() {
|
||||
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.
|
||||
|
||||
// The masking with 0x0ff is essential because we're taking 8-bit bytes and
|
||||
// casting them to integers (which, depending on the platform, are 16- or
|
||||
// 32-bits wide); without that, we get a leading FF (16-bit) or FFFFFF
|
||||
// (32-bit) when the MSB of the 8-bit byte is 1.
|
||||
//
|
||||
// And the cast to an integer is required because std::hex only takes effect
|
||||
// on integral types (and no, uint8_t doesn't activate it).
|
||||
#define BYTE_TO_HEX(b) \
|
||||
std::setfill('0') << std::setw(2) << std::hex \
|
||||
<< (static_cast<unsigned int>(b) & 0x0ff)
|
||||
|
||||
std::ostringstream md5_hex;
|
||||
|
||||
md5_hex << BYTE_TO_HEX(data_[0]);
|
||||
md5_hex << BYTE_TO_HEX(data_[1]);
|
||||
md5_hex << BYTE_TO_HEX(data_[2]);
|
||||
md5_hex << BYTE_TO_HEX(data_[3]);
|
||||
write_hex(md5_hex, absl::string_view(&data_[0], 4));
|
||||
md5_hex << "-";
|
||||
md5_hex << BYTE_TO_HEX(data_[4]);
|
||||
md5_hex << BYTE_TO_HEX(data_[5]);
|
||||
write_hex(md5_hex, absl::string_view(&data_[4], 2));
|
||||
md5_hex << "-";
|
||||
md5_hex << BYTE_TO_HEX(data_[6]);
|
||||
md5_hex << BYTE_TO_HEX(data_[7]);
|
||||
write_hex(md5_hex, absl::string_view(&data_[6], 2));
|
||||
md5_hex << "-";
|
||||
md5_hex << BYTE_TO_HEX(data_[8]);
|
||||
md5_hex << BYTE_TO_HEX(data_[9]);
|
||||
write_hex(md5_hex, absl::string_view(&data_[8], 2));
|
||||
md5_hex << "-";
|
||||
md5_hex << BYTE_TO_HEX(data_[10]);
|
||||
md5_hex << BYTE_TO_HEX(data_[11]);
|
||||
md5_hex << BYTE_TO_HEX(data_[12]);
|
||||
md5_hex << BYTE_TO_HEX(data_[13]);
|
||||
md5_hex << BYTE_TO_HEX(data_[14]);
|
||||
md5_hex << BYTE_TO_HEX(data_[15]);
|
||||
write_hex(md5_hex, absl::string_view(&data_[10], 6));
|
||||
|
||||
return md5_hex.str();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
#define CORE_INTERNAL_MEDIUMS_UUID_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "platform/port/string.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -14,17 +15,24 @@ namespace connections {
|
||||
// UUID.
|
||||
//
|
||||
// https://developer.android.com/reference/java/util/UUID.html
|
||||
template <typename Platform>
|
||||
class UUID {
|
||||
class Uuid final {
|
||||
public:
|
||||
explicit UUID(const std::string& data);
|
||||
UUID(std::int64_t most_sig_bits, std::int64_t least_sig_bits);
|
||||
~UUID();
|
||||
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.
|
||||
std::string str();
|
||||
explicit operator std::string() const;
|
||||
std::string data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
@@ -34,6 +42,4 @@ class UUID {
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/uuid.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_UUID_H_
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "core/internal/mediums/uuid.h"
|
||||
|
||||
#include "platform/public/crypto.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.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
|
||||
@@ -0,0 +1,508 @@
|
||||
#include "core/internal/mediums/webrtc.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "core/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "core/internal/mediums/webrtc/signaling_frames.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 "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/time/time.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::Milliseconds(5000);
|
||||
|
||||
// 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.
|
||||
restart_receive_messages_executor_.Shutdown();
|
||||
single_thread_executor_.Shutdown();
|
||||
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
bool WebRtc::IsAvailable() { return medium_.IsValid(); }
|
||||
|
||||
bool WebRtc::IsAcceptingConnections() {
|
||||
MutexLock lock(&mutex_);
|
||||
return role_ == Role::kOfferer;
|
||||
}
|
||||
|
||||
bool WebRtc::StartAcceptingConnections(const PeerId& self_id,
|
||||
const LocationHint& location_hint,
|
||||
AcceptedConnectionCallback callback) {
|
||||
if (!IsAvailable()) {
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
LogAndDisconnect("WebRTC is not available for data transfer.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnections()) {
|
||||
NEARBY_LOG(WARNING, "Already accepting WebRTC connections.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
if (role_ != Role::kNone) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"Cannot start accepting WebRTC connections, current role %d",
|
||||
role_);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitWebRtcFlow(Role::kOfferer, self_id, location_hint)) return false;
|
||||
|
||||
restart_receive_messages_alarm_ = CancelableAlarm(
|
||||
"restart_receiving_messages_webrtc",
|
||||
std::bind(&WebRtc::RestartReceiveMessages, this, location_hint),
|
||||
kRestartReceiveMessagesDuration, &restart_receive_messages_executor_);
|
||||
|
||||
SessionDescriptionWrapper offer = connection_flow_->CreateOffer();
|
||||
pending_local_offer_ = webrtc_frames::EncodeOffer(self_id, offer.GetSdp());
|
||||
if (!SetLocalSessionDescription(std::move(offer))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is no timeout set for the future returned since we do not know how
|
||||
// much time it will take for the two devices to discover each other before
|
||||
// the actual transport can begin.
|
||||
ListenForWebRtcSocketFuture(connection_flow_->GetDataChannel(),
|
||||
std::move(callback));
|
||||
NEARBY_LOG(INFO, "Started listening for WebRtc connections as %s",
|
||||
self_id.GetId().c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WebRtcSocketWrapper WebRtc::Connect(const PeerId& peer_id,
|
||||
const LocationHint& location_hint) {
|
||||
if (!IsAvailable()) {
|
||||
Disconnect();
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
if (role_ != Role::kNone) {
|
||||
NEARBY_LOG(
|
||||
WARNING,
|
||||
"Cannot connect with WebRtc because we are already acting as %d",
|
||||
role_);
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
peer_id_ = peer_id;
|
||||
if (!InitWebRtcFlow(Role::kAnswerer, PeerId::FromRandom(), location_hint)) {
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Attempting to make a WebRTC connection to %s.",
|
||||
peer_id.GetId().c_str());
|
||||
|
||||
Future<WebRtcSocketWrapper> socket_future = ListenForWebRtcSocketFuture(
|
||||
connection_flow_->GetDataChannel(), AcceptedConnectionCallback());
|
||||
|
||||
// The two devices have discovered each other, hence we have a timeout for
|
||||
// establishing the transport channel.
|
||||
// NOTE - We should not hold |mutex_| while waiting for the data channel since
|
||||
// it would block incoming signaling messages from being processed, resulting
|
||||
// in a timeout in creating the socket.
|
||||
ExceptionOr<WebRtcSocketWrapper> result =
|
||||
socket_future.Get(kDataChannelTimeout);
|
||||
if (result.ok()) return result.result();
|
||||
|
||||
Disconnect();
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
bool WebRtc::SetLocalSessionDescription(SessionDescriptionWrapper sdp) {
|
||||
if (!connection_flow_->SetLocalSessionDescription(std::move(sdp))) {
|
||||
LogAndDisconnect("Unable to set local session description");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebRtc::StopAcceptingConnections() {
|
||||
if (!IsAcceptingConnections()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Skipped StopAcceptingConnections since we are not currently "
|
||||
"accepting WebRTC connections");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
ShutdownSignaling();
|
||||
}
|
||||
NEARBY_LOG(INFO, "Stopped accepting WebRTC connections");
|
||||
}
|
||||
|
||||
Future<WebRtcSocketWrapper> WebRtc::ListenForWebRtcSocketFuture(
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>>
|
||||
data_channel_future,
|
||||
AcceptedConnectionCallback callback) {
|
||||
Future<WebRtcSocketWrapper> socket_future;
|
||||
auto data_channel_runnable = [this, socket_future, data_channel_future,
|
||||
callback{std::move(callback)}]() mutable {
|
||||
// The overall timeout of creating the socket and data channel is controlled
|
||||
// by the caller of this function.
|
||||
ExceptionOr<rtc::scoped_refptr<webrtc::DataChannelInterface>> res =
|
||||
data_channel_future.Get();
|
||||
if (res.ok()) {
|
||||
WebRtcSocketWrapper wrapper = CreateWebRtcSocketWrapper(res.result());
|
||||
callback.accepted_cb(wrapper);
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
socket_ = wrapper;
|
||||
}
|
||||
socket_future.Set(wrapper);
|
||||
} else {
|
||||
NEARBY_LOG(WARNING, "Failed to get WebRtcSocket.");
|
||||
socket_future.Set(WebRtcSocketWrapper());
|
||||
}
|
||||
};
|
||||
|
||||
data_channel_future.AddListener(std::move(data_channel_runnable),
|
||||
&single_thread_executor_);
|
||||
|
||||
return socket_future;
|
||||
}
|
||||
|
||||
WebRtcSocketWrapper WebRtc::CreateWebRtcSocketWrapper(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
if (data_channel == nullptr) {
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
auto socket = std::make_unique<WebRtcSocket>("WebRtcSocket", data_channel);
|
||||
socket->SetOnSocketClosedListener(
|
||||
{[this]() { OffloadFromSignalingThread([this]() { Disconnect(); }); }});
|
||||
return WebRtcSocketWrapper(std::move(socket));
|
||||
}
|
||||
|
||||
bool WebRtc::InitWebRtcFlow(Role role, const PeerId& self_id,
|
||||
const LocationHint& location_hint) {
|
||||
role_ = role;
|
||||
self_id_ = self_id;
|
||||
|
||||
if (connection_flow_) {
|
||||
LogAndShutdownSignaling(
|
||||
"Tried to initialize WebRTC without shutting down the previous "
|
||||
"connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signaling_messenger_) {
|
||||
LogAndShutdownSignaling(
|
||||
"Tried to initialize WebRTC without shutting down signaling messenger");
|
||||
return false;
|
||||
}
|
||||
|
||||
signaling_messenger_ =
|
||||
medium_.GetSignalingMessenger(self_id_.GetId(), location_hint);
|
||||
auto signaling_message_callback = [this](ByteArray message) {
|
||||
OffloadFromSignalingThread([this, message{std::move(message)}]() {
|
||||
ProcessSignalingMessage(message);
|
||||
});
|
||||
};
|
||||
|
||||
if (!signaling_messenger_->IsValid() ||
|
||||
!signaling_messenger_->StartReceivingMessages(
|
||||
signaling_message_callback)) {
|
||||
DisconnectLocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role_ == Role::kAnswerer &&
|
||||
!signaling_messenger_->SendMessage(
|
||||
peer_id_.GetId(),
|
||||
webrtc_frames::EncodeReadyForSignalingPoke(self_id))) {
|
||||
LogAndDisconnect(absl::StrCat("Could not send signaling poke to peer ",
|
||||
peer_id_.GetId()));
|
||||
return false;
|
||||
}
|
||||
|
||||
connection_flow_ = ConnectionFlow::Create(GetLocalIceCandidateListener(),
|
||||
GetDataChannelListener(), medium_);
|
||||
if (!connection_flow_)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebRtc::OnLocalIceCandidate(
|
||||
const webrtc::IceCandidateInterface* local_ice_candidate) {
|
||||
::location::nearby::mediums::IceCandidate ice_candidate =
|
||||
webrtc_frames::EncodeIceCandidate(*local_ice_candidate);
|
||||
|
||||
OffloadFromSignalingThread([this, ice_candidate{std::move(ice_candidate)}]() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (IsSignaling()) {
|
||||
signaling_messenger_->SendMessage(
|
||||
peer_id_.GetId(), webrtc_frames::EncodeIceCandidates(
|
||||
self_id_, {std::move(ice_candidate)}));
|
||||
} else {
|
||||
pending_local_ice_candidates_.push_back(std::move(ice_candidate));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LocalIceCandidateListener WebRtc::GetLocalIceCandidateListener() {
|
||||
return {std::bind(&WebRtc::OnLocalIceCandidate, this, std::placeholders::_1)};
|
||||
}
|
||||
|
||||
void WebRtc::OnDataChannelClosed() {
|
||||
OffloadFromSignalingThread([this]() {
|
||||
MutexLock lock(&mutex_);
|
||||
LogAndDisconnect("WebRTC data channel closed");
|
||||
});
|
||||
}
|
||||
|
||||
void WebRtc::OnDataChannelMessageReceived(const ByteArray& message) {
|
||||
OffloadFromSignalingThread([this, message]() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (!socket_.IsValid()) {
|
||||
LogAndDisconnect("Received a data channel message without a socket");
|
||||
return;
|
||||
}
|
||||
|
||||
socket_.NotifyDataChannelMsgReceived(message);
|
||||
});
|
||||
}
|
||||
|
||||
void WebRtc::OnDataChannelBufferedAmountChanged() {
|
||||
OffloadFromSignalingThread([this]() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (!socket_.IsValid()) {
|
||||
LogAndDisconnect("Data channel buffer changed without a socket");
|
||||
return;
|
||||
}
|
||||
|
||||
socket_.NotifyDataChannelBufferedAmountChanged();
|
||||
});
|
||||
}
|
||||
|
||||
DataChannelListener WebRtc::GetDataChannelListener() {
|
||||
return {
|
||||
.data_channel_closed_cb = std::bind(&WebRtc::OnDataChannelClosed, this),
|
||||
.data_channel_message_received_cb = std::bind(
|
||||
&WebRtc::OnDataChannelMessageReceived, this, std::placeholders::_1),
|
||||
.data_channel_buffered_amount_changed_cb =
|
||||
std::bind(&WebRtc::OnDataChannelBufferedAmountChanged, this),
|
||||
};
|
||||
}
|
||||
|
||||
bool WebRtc::IsSignaling() {
|
||||
return (role_ != Role::kNone && self_id_.IsValid() && peer_id_.IsValid());
|
||||
}
|
||||
|
||||
void WebRtc::ProcessSignalingMessage(const ByteArray& message) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!connection_flow_) {
|
||||
LogAndDisconnect("Received WebRTC frame before signaling was started");
|
||||
return;
|
||||
}
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
if (!frame.ParseFromString(std::string(message))) {
|
||||
LogAndDisconnect("Failed to parse signaling message");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frame.has_sender_id()) {
|
||||
LogAndDisconnect("Invalid WebRTC frame: Sender ID is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.has_ready_for_signaling_poke() && !peer_id_.IsValid()) {
|
||||
peer_id_ = PeerId(frame.sender_id().id());
|
||||
NEARBY_LOG(INFO, "Peer %s is ready for signaling",
|
||||
peer_id_.GetId().c_str());
|
||||
}
|
||||
|
||||
if (!IsSignaling()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Ignoring WebRTC frame: we are not currently listening for "
|
||||
"signaling messages");
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.sender_id().id() != peer_id_.GetId()) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Ignoring WebRTC frame: we are only listening for another peer.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.has_ready_for_signaling_poke()) {
|
||||
SendOfferAndIceCandidatesToPeer();
|
||||
} else if (frame.has_offer()) {
|
||||
connection_flow_->OnOfferReceived(
|
||||
SessionDescriptionWrapper(webrtc_frames::DecodeOffer(frame).release()));
|
||||
SendAnswerToPeer();
|
||||
} else if (frame.has_answer()) {
|
||||
connection_flow_->OnAnswerReceived(SessionDescriptionWrapper(
|
||||
webrtc_frames::DecodeAnswer(frame).release()));
|
||||
} else if (frame.has_ice_candidates()) {
|
||||
if (!connection_flow_->OnRemoteIceCandidatesReceived(
|
||||
webrtc_frames::DecodeIceCandidates(frame))) {
|
||||
LogAndDisconnect("Could not add remote ice candidates.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::SendOfferAndIceCandidatesToPeer() {
|
||||
if (pending_local_offer_.Empty()) {
|
||||
LogAndDisconnect(
|
||||
"Unable to send pending offer to remote peer: local offer not set");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!signaling_messenger_->SendMessage(peer_id_.GetId(),
|
||||
pending_local_offer_)) {
|
||||
LogAndDisconnect("Failed to send local offer via signaling messenger");
|
||||
return;
|
||||
}
|
||||
pending_local_offer_ = ByteArray();
|
||||
|
||||
if (!pending_local_ice_candidates_.empty()) {
|
||||
signaling_messenger_->SendMessage(
|
||||
peer_id_.GetId(),
|
||||
webrtc_frames::EncodeIceCandidates(
|
||||
self_id_, std::move(pending_local_ice_candidates_)));
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::SendAnswerToPeer() {
|
||||
SessionDescriptionWrapper answer = connection_flow_->CreateAnswer();
|
||||
ByteArray answer_message(
|
||||
webrtc_frames::EncodeAnswer(self_id_, answer.GetSdp()));
|
||||
|
||||
if (!SetLocalSessionDescription(std::move(answer))) return;
|
||||
|
||||
if (!signaling_messenger_->SendMessage(peer_id_.GetId(), answer_message)) {
|
||||
LogAndDisconnect("Failed to send local answer via signaling messenger");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::LogAndDisconnect(const std::string& error_message) {
|
||||
NEARBY_LOG(WARNING, "Disconnecting WebRTC : %s", error_message.c_str());
|
||||
DisconnectLocked();
|
||||
}
|
||||
|
||||
void WebRtc::LogAndShutdownSignaling(const std::string& error_message) {
|
||||
NEARBY_LOG(WARNING, "Stopping WebRTC signaling : %s", error_message.c_str());
|
||||
ShutdownSignaling();
|
||||
}
|
||||
|
||||
void WebRtc::ShutdownSignaling() {
|
||||
role_ = Role::kNone;
|
||||
self_id_ = PeerId();
|
||||
peer_id_ = PeerId();
|
||||
pending_local_offer_ = ByteArray();
|
||||
pending_local_ice_candidates_.clear();
|
||||
|
||||
if (restart_receive_messages_alarm_.IsValid()) {
|
||||
restart_receive_messages_alarm_.Cancel();
|
||||
restart_receive_messages_alarm_ = CancelableAlarm();
|
||||
}
|
||||
|
||||
if (signaling_messenger_) {
|
||||
signaling_messenger_->StopReceivingMessages();
|
||||
signaling_messenger_.reset();
|
||||
}
|
||||
|
||||
if (!socket_.IsValid()) ShutdownIceCandidateCollection();
|
||||
}
|
||||
|
||||
void WebRtc::Disconnect() {
|
||||
MutexLock lock(&mutex_);
|
||||
DisconnectLocked();
|
||||
}
|
||||
|
||||
void WebRtc::DisconnectLocked() {
|
||||
ShutdownSignaling();
|
||||
ShutdownWebRtcSocket();
|
||||
ShutdownIceCandidateCollection();
|
||||
}
|
||||
|
||||
void WebRtc::ShutdownWebRtcSocket() {
|
||||
if (socket_.IsValid()) {
|
||||
socket_.Close();
|
||||
socket_ = WebRtcSocketWrapper();
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::ShutdownIceCandidateCollection() {
|
||||
if (connection_flow_) {
|
||||
connection_flow_->Close();
|
||||
connection_flow_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::OffloadFromSignalingThread(Runnable runnable) {
|
||||
single_thread_executor_.Execute(std::move(runnable));
|
||||
}
|
||||
|
||||
void WebRtc::RestartReceiveMessages(const LocationHint& location_hint) {
|
||||
if (!IsAcceptingConnections()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Skipping restart since we are not accepting connections.");
|
||||
return;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Restarting listening for receiving signaling messages.");
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
signaling_messenger_->StopReceivingMessages();
|
||||
|
||||
signaling_messenger_ =
|
||||
medium_.GetSignalingMessenger(self_id_.GetId(), location_hint);
|
||||
|
||||
auto signaling_message_callback = [this](ByteArray message) {
|
||||
OffloadFromSignalingThread([this, message{std::move(message)}]() {
|
||||
ProcessSignalingMessage(message);
|
||||
});
|
||||
};
|
||||
|
||||
if (!signaling_messenger_->IsValid() ||
|
||||
!signaling_messenger_->StartReceivingMessages(
|
||||
signaling_message_callback)) {
|
||||
DisconnectLocked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,174 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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/peer_id.h"
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket.h"
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket_wrapper.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "platform/base/byte_array.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 "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
#include "webrtc/api/scoped_refptr.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();
|
||||
|
||||
// Returns if WebRtc is available as a medium for nearby to transport data.
|
||||
// Runs on @MainThread.
|
||||
bool IsAvailable();
|
||||
|
||||
// Returns if the device is ready to accept connections from remote devices.
|
||||
// Runs on @MainThread.
|
||||
bool IsAcceptingConnections() 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 PeerId& self_id,
|
||||
const LocationHint& location_hint,
|
||||
AcceptedConnectionCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Prevents device from accepting future connections until
|
||||
// StartAcceptingConnections() is called.
|
||||
// Runs on @MainThread.
|
||||
void StopAcceptingConnections() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Initiates a WebRtc connection with peer device identified by |peer_id|.
|
||||
// Runs on @MainThread.
|
||||
WebRtcSocketWrapper Connect(const PeerId& peer_id,
|
||||
const LocationHint& location_hint)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
enum class Role {
|
||||
kNone = 0,
|
||||
kOfferer = 1,
|
||||
kAnswerer = 2,
|
||||
};
|
||||
|
||||
bool InitWebRtcFlow(Role role, const PeerId& self_id,
|
||||
const LocationHint& location_hint)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
Future<WebRtcSocketWrapper> ListenForWebRtcSocketFuture(
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>>
|
||||
data_channel_future,
|
||||
AcceptedConnectionCallback callback);
|
||||
|
||||
WebRtcSocketWrapper CreateWebRtcSocketWrapper(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
|
||||
LocalIceCandidateListener GetLocalIceCandidateListener();
|
||||
void OnLocalIceCandidate(
|
||||
const webrtc::IceCandidateInterface* local_ice_candidate);
|
||||
|
||||
DataChannelListener GetDataChannelListener();
|
||||
void OnDataChannelClosed();
|
||||
void OnDataChannelMessageReceived(const ByteArray& message);
|
||||
void OnDataChannelBufferedAmountChanged();
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
bool SetLocalSessionDescription(SessionDescriptionWrapper sdp)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
bool IsSignaling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ProcessSignalingMessage(const ByteArray& message)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void SendOfferAndIceCandidatesToPeer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void SendAnswerToPeer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void LogAndDisconnect(const std::string& error_message)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread.
|
||||
void Disconnect() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void DisconnectLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void LogAndShutdownSignaling(const std::string& error_message)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownSignaling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownWebRtcSocket() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownIceCandidateCollection();
|
||||
|
||||
void OffloadFromSignalingThread(Runnable runnable);
|
||||
|
||||
// Runs on |restart_receive_messages_executor_|.
|
||||
void RestartReceiveMessages(const LocationHint& location_hint)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
Mutex mutex_;
|
||||
|
||||
Role role_ ABSL_GUARDED_BY(mutex_) = Role::kNone;
|
||||
PeerId self_id_ ABSL_GUARDED_BY(mutex_);
|
||||
PeerId peer_id_ ABSL_GUARDED_BY(mutex_);
|
||||
ByteArray pending_local_offer_ ABSL_GUARDED_BY(mutex_);
|
||||
std::vector<::location::nearby::mediums::IceCandidate>
|
||||
pending_local_ice_candidates_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
WebRtcMedium medium_;
|
||||
std::unique_ptr<ConnectionFlow> connection_flow_;
|
||||
std::unique_ptr<WebRtcSignalingMessenger> signaling_messenger_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
WebRtcSocketWrapper socket_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
SingleThreadExecutor single_thread_executor_;
|
||||
|
||||
// Restarts the signaling messenger for receiving messages.
|
||||
ScheduledExecutor restart_receive_messages_executor_;
|
||||
CancelableAlarm restart_receive_messages_alarm_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
@@ -0,0 +1,174 @@
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/connection_flow.h"
|
||||
#include "core_v2/internal/mediums/webrtc/data_channel_listener.h"
|
||||
#include "core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h"
|
||||
#include "core_v2/internal/mediums/webrtc/peer_id.h"
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket.h"
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "proto/connections/offline_wire_formats.pb.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/listeners.h"
|
||||
#include "platform_v2/base/runnable.h"
|
||||
#include "platform_v2/public/atomic_boolean.h"
|
||||
#include "platform_v2/public/cancelable_alarm.h"
|
||||
#include "platform_v2/public/future.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "platform_v2/public/scheduled_executor.h"
|
||||
#include "platform_v2/public/single_thread_executor.h"
|
||||
#include "platform_v2/public/webrtc.h"
|
||||
#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
#include "webrtc/api/scoped_refptr.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();
|
||||
|
||||
// Returns if WebRtc is available as a medium for nearby to transport data.
|
||||
// Runs on @MainThread.
|
||||
bool IsAvailable();
|
||||
|
||||
// Returns if the device is ready to accept connections from remote devices.
|
||||
// Runs on @MainThread.
|
||||
bool IsAcceptingConnections() 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 PeerId& self_id,
|
||||
const LocationHint& location_hint,
|
||||
AcceptedConnectionCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Prevents device from accepting future connections until
|
||||
// StartAcceptingConnections() is called.
|
||||
// Runs on @MainThread.
|
||||
void StopAcceptingConnections() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Initiates a WebRtc connection with peer device identified by |peer_id|.
|
||||
// Runs on @MainThread.
|
||||
WebRtcSocketWrapper Connect(const PeerId& peer_id,
|
||||
const LocationHint& location_hint)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
enum class Role {
|
||||
kNone = 0,
|
||||
kOfferer = 1,
|
||||
kAnswerer = 2,
|
||||
};
|
||||
|
||||
bool InitWebRtcFlow(Role role, const PeerId& self_id,
|
||||
const LocationHint& location_hint)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
Future<WebRtcSocketWrapper> ListenForWebRtcSocketFuture(
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>>
|
||||
data_channel_future,
|
||||
AcceptedConnectionCallback callback);
|
||||
|
||||
WebRtcSocketWrapper CreateWebRtcSocketWrapper(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
|
||||
LocalIceCandidateListener GetLocalIceCandidateListener();
|
||||
void OnLocalIceCandidate(
|
||||
const webrtc::IceCandidateInterface* local_ice_candidate);
|
||||
|
||||
DataChannelListener GetDataChannelListener();
|
||||
void OnDataChannelClosed();
|
||||
void OnDataChannelMessageReceived(const ByteArray& message);
|
||||
void OnDataChannelBufferedAmountChanged();
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
bool SetLocalSessionDescription(SessionDescriptionWrapper sdp)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
bool IsSignaling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ProcessSignalingMessage(const ByteArray& message)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void SendOfferAndIceCandidatesToPeer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void SendAnswerToPeer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void LogAndDisconnect(const std::string& error_message)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread.
|
||||
void Disconnect() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void DisconnectLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void LogAndShutdownSignaling(const std::string& error_message)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownSignaling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownWebRtcSocket() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownIceCandidateCollection();
|
||||
|
||||
void OffloadFromSignalingThread(Runnable runnable);
|
||||
|
||||
// Runs on |restart_receive_messages_executor_|.
|
||||
void RestartReceiveMessages(const LocationHint& location_hint)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
Mutex mutex_;
|
||||
|
||||
Role role_ ABSL_GUARDED_BY(mutex_) = Role::kNone;
|
||||
PeerId self_id_ ABSL_GUARDED_BY(mutex_);
|
||||
PeerId peer_id_ ABSL_GUARDED_BY(mutex_);
|
||||
ByteArray pending_local_offer_ ABSL_GUARDED_BY(mutex_);
|
||||
std::vector<::location::nearby::mediums::IceCandidate>
|
||||
pending_local_ice_candidates_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
WebRtcMedium medium_;
|
||||
std::unique_ptr<ConnectionFlow> connection_flow_;
|
||||
std::unique_ptr<WebRtcSignalingMessenger> signaling_messenger_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
WebRtcSocketWrapper socket_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
SingleThreadExecutor single_thread_executor_;
|
||||
|
||||
// Restarts the signaling messenger for receiving messages.
|
||||
ScheduledExecutor restart_receive_messages_executor_;
|
||||
CancelableAlarm restart_receive_messages_alarm_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
@@ -1,77 +1,63 @@
|
||||
cc_library(
|
||||
name = "webrtc",
|
||||
hdrs = [
|
||||
srcs = [
|
||||
"connection_flow.cc",
|
||||
"data_channel_observer_impl.cc",
|
||||
"peer_connection_observer_impl.cc",
|
||||
"peer_id.cc",
|
||||
"signaling_frames.cc",
|
||||
"webrtc_socket.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"connection_flow.h",
|
||||
"data_channel_listener.h",
|
||||
"data_channel_observer_impl.h",
|
||||
"local_ice_candidate_listener.h",
|
||||
"peer_connection_observer_impl.h",
|
||||
"peer_id.h",
|
||||
"session_description_wrapper.h",
|
||||
"signaling_frames.h",
|
||||
"webrtc_socket.h",
|
||||
"webrtc_socket_wrapper.h",
|
||||
],
|
||||
visibility = [
|
||||
"//core/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//platform:utils",
|
||||
"//platform/api",
|
||||
"//core:core_types",
|
||||
"//core/internal/mediums:utils",
|
||||
"//platform/base",
|
||||
"//platform/public:comm",
|
||||
"//platform/public:logging",
|
||||
"//platform/public:types",
|
||||
"//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto",
|
||||
"//absl/memory",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "webrtc_test",
|
||||
srcs = ["webrtc_socket_test.cc"],
|
||||
srcs = [
|
||||
"connection_flow_test.cc",
|
||||
"peer_id_test.cc",
|
||||
"signaling_frames_test.cc",
|
||||
"webrtc_socket_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":webrtc",
|
||||
"//platform:types",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3", # buildcleaner: keep
|
||||
"//testing/base/public:gunit_main",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "peer_id",
|
||||
srcs = ["peer_id.cc"],
|
||||
hdrs = ["peer_id.h"],
|
||||
deps = [
|
||||
"//core/internal/mediums:utils",
|
||||
"//platform:types",
|
||||
"//platform/api",
|
||||
"//platform/port:string",
|
||||
"//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "signaling_frames",
|
||||
srcs = ["signaling_frames.cc"],
|
||||
hdrs = ["signaling_frames.h"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
"//platform:types",
|
||||
"//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "peer_id_test",
|
||||
srcs = ["peer_id_test.cc"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
"//platform:types",
|
||||
"//platform/api",
|
||||
"//platform/impl/g3", # buildcleaner: keep
|
||||
"//testing/base/public:gunit_main",
|
||||
"//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "signaling_frames_test",
|
||||
srcs = ["signaling_frames_test.cc"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
":signaling_frames",
|
||||
"//platform:types",
|
||||
"//platform/base",
|
||||
"//platform/base:test_util",
|
||||
"//platform/impl/g3", # buildcleaner: keep
|
||||
"//platform/public:comm",
|
||||
"//platform/public:types",
|
||||
"//net/proto2/public:proto2",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//webrtc/pc:peerconnection", # buildcleaner: keep
|
||||
"//absl/time",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
"//webrtc/api:rtc_error",
|
||||
"//webrtc/api:scoped_refptr",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
#include "core/internal/mediums/webrtc/connection_flow.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
|
||||
#include "core/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
#include "platform/public/webrtc.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/time/time.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;
|
||||
|
||||
namespace {
|
||||
// This is the same as the nearby data channel name.
|
||||
const char kDataChannelName[] = "dataChannel";
|
||||
|
||||
class CreateSessionDescriptionObserverImpl
|
||||
: public webrtc::CreateSessionDescriptionObserver {
|
||||
public:
|
||||
explicit CreateSessionDescriptionObserverImpl(
|
||||
Future<SessionDescriptionWrapper>* settable_future)
|
||||
: settable_future_(settable_future) {}
|
||||
~CreateSessionDescriptionObserverImpl() override = default;
|
||||
|
||||
// webrtc::CreateSessionDescriptionObserver
|
||||
void OnSuccess(webrtc::SessionDescriptionInterface* desc) override {
|
||||
settable_future_->Set(SessionDescriptionWrapper{desc});
|
||||
}
|
||||
|
||||
void OnFailure(webrtc::RTCError error) override {
|
||||
NEARBY_LOG(ERROR, "Error when creating session description: %s",
|
||||
error.message());
|
||||
settable_future_->SetException({Exception::kFailed});
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Future<SessionDescriptionWrapper>> settable_future_;
|
||||
};
|
||||
|
||||
class SetSessionDescriptionObserverImpl
|
||||
: public webrtc::SetSessionDescriptionObserver {
|
||||
public:
|
||||
explicit SetSessionDescriptionObserverImpl(Future<bool>* settable_future)
|
||||
: settable_future_(settable_future) {}
|
||||
|
||||
void OnSuccess() override { settable_future_->Set(true); }
|
||||
|
||||
void OnFailure(webrtc::RTCError error) override {
|
||||
NEARBY_LOG(ERROR, "Error when setting session description: %s",
|
||||
error.message());
|
||||
settable_future_->SetException({Exception::kFailed});
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Future<bool>> settable_future_;
|
||||
};
|
||||
|
||||
using PeerConnectionState =
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState;
|
||||
|
||||
} // namespace
|
||||
|
||||
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)),
|
||||
peer_connection_observer_(this, std::move(local_ice_candidate_listener)) {
|
||||
}
|
||||
|
||||
ConnectionFlow::~ConnectionFlow() { Close(); }
|
||||
|
||||
SessionDescriptionWrapper ConnectionFlow::CreateOffer() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!TransitionState(State::kInitialized, State::kCreatingOffer)) {
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
webrtc::DataChannelInit data_channel_init;
|
||||
data_channel_init.reliable = true;
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel =
|
||||
peer_connection_->CreateDataChannel(kDataChannelName, &data_channel_init);
|
||||
data_channel->RegisterObserver(CreateDataChannelObserver(data_channel));
|
||||
|
||||
auto success_future = new Future<SessionDescriptionWrapper>();
|
||||
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
|
||||
rtc::scoped_refptr<CreateSessionDescriptionObserverImpl> observer =
|
||||
new rtc::RefCountedObject<CreateSessionDescriptionObserverImpl>(
|
||||
success_future);
|
||||
peer_connection_->CreateOffer(observer, options);
|
||||
|
||||
ExceptionOr<SessionDescriptionWrapper> result = success_future->Get(kTimeout);
|
||||
if (result.ok() &&
|
||||
TransitionState(State::kCreatingOffer, State::kWaitingForAnswer)) {
|
||||
return std::move(result.result());
|
||||
}
|
||||
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
SessionDescriptionWrapper ConnectionFlow::CreateAnswer() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!TransitionState(State::kReceivedOffer, State::kCreatingAnswer)) {
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
auto success_future = new Future<SessionDescriptionWrapper>();
|
||||
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
|
||||
rtc::scoped_refptr<CreateSessionDescriptionObserverImpl> observer =
|
||||
new rtc::RefCountedObject<CreateSessionDescriptionObserverImpl>(
|
||||
success_future);
|
||||
peer_connection_->CreateAnswer(observer, options);
|
||||
|
||||
ExceptionOr<SessionDescriptionWrapper> result = success_future->Get(kTimeout);
|
||||
if (result.ok() &&
|
||||
TransitionState(State::kCreatingAnswer, State::kWaitingToConnect)) {
|
||||
return std::move(result.result());
|
||||
}
|
||||
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
bool ConnectionFlow::SetLocalSessionDescription(SessionDescriptionWrapper sdp) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!sdp.IsValid()) return false;
|
||||
|
||||
auto success_future = new Future<bool>();
|
||||
rtc::scoped_refptr<SetSessionDescriptionObserverImpl> observer =
|
||||
new rtc::RefCountedObject<SetSessionDescriptionObserverImpl>(
|
||||
success_future);
|
||||
|
||||
peer_connection_->SetLocalDescription(observer, sdp.Release());
|
||||
|
||||
ExceptionOr<bool> result = success_future->Get(kTimeout);
|
||||
return result.ok() && result.result();
|
||||
}
|
||||
|
||||
bool ConnectionFlow::SetRemoteSessionDescription(
|
||||
SessionDescriptionWrapper sdp) {
|
||||
if (!sdp.IsValid()) return false;
|
||||
|
||||
auto success_future = new Future<bool>();
|
||||
rtc::scoped_refptr<SetSessionDescriptionObserverImpl> observer =
|
||||
new rtc::RefCountedObject<SetSessionDescriptionObserverImpl>(
|
||||
success_future);
|
||||
|
||||
peer_connection_->SetRemoteDescription(observer, sdp.Release());
|
||||
|
||||
ExceptionOr<bool> result = success_future->Get(kTimeout);
|
||||
return result.ok() && result.result();
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnOfferReceived(SessionDescriptionWrapper offer) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!TransitionState(State::kInitialized, State::kReceivedOffer)) {
|
||||
return false;
|
||||
}
|
||||
return SetRemoteSessionDescription(std::move(offer));
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnAnswerReceived(SessionDescriptionWrapper answer) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!TransitionState(State::kWaitingForAnswer, State::kWaitingToConnect)) {
|
||||
return false;
|
||||
}
|
||||
return SetRemoteSessionDescription(std::move(answer));
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnRemoteIceCandidatesReceived(
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (state_ == State::kEnded) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"You cannot add ice candidates to a disconnected session.");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 true;
|
||||
}
|
||||
|
||||
for (auto&& ice_candidate : ice_candidates) {
|
||||
if (!peer_connection_->AddIceCandidate(ice_candidate.get())) {
|
||||
NEARBY_LOG(WARNING, "Unable to add remote ice candidate.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>>
|
||||
ConnectionFlow::GetDataChannel() {
|
||||
return data_channel_future_;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::Close() {
|
||||
MutexLock lock(&mutex_);
|
||||
return CloseLocked();
|
||||
}
|
||||
|
||||
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(
|
||||
&peer_connection_observer_,
|
||||
[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;
|
||||
peer_connection_ = peer_connection;
|
||||
success_future.Set(true);
|
||||
});
|
||||
|
||||
ExceptionOr<bool> result = success_future.Get(kTimeout);
|
||||
return result.ok() && result.result();
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnSignalingStable() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (state_ != State::kWaitingToConnect && state_ != State::kConnected) return;
|
||||
|
||||
for (auto&& ice_candidate : cached_remote_ice_candidates_) {
|
||||
if (!peer_connection_->AddIceCandidate(ice_candidate.get())) {
|
||||
NEARBY_LOG(WARNING, "Unable to add remote ice candidate.");
|
||||
}
|
||||
}
|
||||
cached_remote_ice_candidates_.clear();
|
||||
}
|
||||
|
||||
void ConnectionFlow::ProcessOnPeerConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
|
||||
if (new_state == PeerConnectionState::kClosed ||
|
||||
new_state == PeerConnectionState::kFailed ||
|
||||
new_state == PeerConnectionState::kDisconnected) {
|
||||
MutexLock lock(&mutex_);
|
||||
CloseAndNotifyLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionFlow::ProcessDataChannelConnected() {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOG(INFO, "Data channel state changed to connected.");
|
||||
if (!TransitionState(State::kWaitingToConnect, State::kConnected))
|
||||
CloseAndNotifyLocked();
|
||||
}
|
||||
|
||||
webrtc::DataChannelObserver* ConnectionFlow::CreateDataChannelObserver(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
if (!data_channel_observer_) {
|
||||
auto state_change_callback = [this,
|
||||
data_channel{std::move(data_channel)}]() {
|
||||
if (data_channel->state() ==
|
||||
webrtc::DataChannelInterface::DataState::kOpen) {
|
||||
data_channel_future_.Set(std::move(data_channel));
|
||||
OffloadFromSignalingThread([this]() { ProcessDataChannelConnected(); });
|
||||
} else if (data_channel->state() ==
|
||||
webrtc::DataChannelInterface::DataState::kClosed) {
|
||||
data_channel->UnregisterObserver();
|
||||
OffloadFromSignalingThread([this]() {
|
||||
MutexLock lock(&mutex_);
|
||||
CloseAndNotifyLocked();
|
||||
});
|
||||
}
|
||||
};
|
||||
data_channel_observer_ = absl::make_unique<DataChannelObserverImpl>(
|
||||
&data_channel_listener_, std::move(state_change_callback));
|
||||
}
|
||||
|
||||
return reinterpret_cast<webrtc::DataChannelObserver*>(
|
||||
data_channel_observer_.get());
|
||||
}
|
||||
|
||||
bool ConnectionFlow::TransitionState(State current_state, State new_state) {
|
||||
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;
|
||||
}
|
||||
state_ = new_state;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionFlow::CloseAndNotifyLocked() {
|
||||
if (CloseLocked()) {
|
||||
data_channel_listener_.data_channel_closed_cb();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectionFlow::CloseLocked() {
|
||||
if (state_ == State::kEnded) {
|
||||
return false;
|
||||
}
|
||||
state_ = State::kEnded;
|
||||
|
||||
data_channel_future_.SetException({Exception::kInterrupted});
|
||||
if (peer_connection_) peer_connection_->Close();
|
||||
|
||||
data_channel_observer_.reset();
|
||||
NEARBY_LOG(INFO, "Closed WebRTC connection.");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionFlow::OffloadFromSignalingThread(Runnable runnable) {
|
||||
single_threaded_signaling_offloader_.Execute(std::move(runnable));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,160 @@
|
||||
#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/data_channel_observer_impl.h"
|
||||
#include "core/internal/mediums/webrtc/local_ice_candidate_listener.h"
|
||||
#include "core/internal/mediums/webrtc/peer_connection_observer_impl.h"
|
||||
#include "core/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "platform/base/runnable.h"
|
||||
#include "platform/public/future.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:
|
||||
// This method blocks on the creation of the peer connection object.
|
||||
static std::unique_ptr<ConnectionFlow> Create(
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener, WebRtcMedium& webrtc_medium);
|
||||
~ConnectionFlow();
|
||||
|
||||
// Create the offer that will be sent to the remote. Mirrors the behaviour of
|
||||
// PeerConnectionInterface::CreateOffer.
|
||||
SessionDescriptionWrapper CreateOffer() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Create the answer that will be sent to the remote. Mirrors the behaviour of
|
||||
// PeerConnectionInterface::CreateAnswer.
|
||||
SessionDescriptionWrapper CreateAnswer() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Set the local session description. |sdp| was created via CreateOffer()
|
||||
// or CreateAnswer().
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
bool OnRemoteIceCandidatesReceived(
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Get a future for the data channel.
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>> GetDataChannel();
|
||||
// Close the peer connection and data channel.
|
||||
bool Close() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Invoked when the peer connection indicates that signaling is stable.
|
||||
void OnSignalingStable() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
webrtc::DataChannelObserver* CreateDataChannelObserver(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
|
||||
// Invoked upon changes in the state of peer connection, e.g. react to
|
||||
// disconnect.
|
||||
void ProcessOnPeerConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
kInitialized,
|
||||
kCreatingOffer,
|
||||
kWaitingForAnswer,
|
||||
kReceivedOffer,
|
||||
kCreatingAnswer,
|
||||
kWaitingToConnect,
|
||||
kConnected,
|
||||
kEnded,
|
||||
};
|
||||
|
||||
ConnectionFlow(LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener);
|
||||
|
||||
// TODO(bfranz): Consider whether this needs to be configurable per platform
|
||||
static constexpr absl::Duration kTimeout = absl::Milliseconds(250);
|
||||
|
||||
bool InitPeerConnection(WebRtcMedium& webrtc_medium);
|
||||
|
||||
bool TransitionState(State current_state, State new_state)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
bool SetRemoteSessionDescription(SessionDescriptionWrapper sdp);
|
||||
|
||||
void ProcessDataChannelConnected() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
void CloseAndNotifyLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
bool CloseLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void OffloadFromSignalingThread(Runnable runnable);
|
||||
|
||||
Mutex mutex_;
|
||||
|
||||
State state_ ABSL_GUARDED_BY(mutex_) = State::kInitialized;
|
||||
DataChannelListener data_channel_listener_;
|
||||
|
||||
std::unique_ptr<DataChannelObserverImpl> data_channel_observer_;
|
||||
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>> data_channel_future_;
|
||||
|
||||
PeerConnectionObserverImpl peer_connection_observer_;
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
|
||||
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
cached_remote_ice_candidates_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
SingleThreadExecutor single_threaded_signaling_offloader_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_
|
||||
@@ -0,0 +1,201 @@
|
||||
#include "core/internal/mediums/webrtc/connection_flow.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "core/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/base/medium_environment.h"
|
||||
#include "platform/public/webrtc.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.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;
|
||||
|
||||
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));
|
||||
}},
|
||||
DataChannelListener(), 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_message_received_cb =
|
||||
[&message_received_future](ByteArray bytes) {
|
||||
message_received_future.Set(std::move(bytes));
|
||||
}},
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
// Create and send offer
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
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();
|
||||
EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer);
|
||||
EXPECT_TRUE(offerer->OnAnswerReceived(answer));
|
||||
EXPECT_TRUE(answerer->SetLocalSessionDescription(std::move(answer)));
|
||||
|
||||
// Retrieve Data Channels
|
||||
ExceptionOr<rtc::scoped_refptr<webrtc::DataChannelInterface>>
|
||||
offerer_channel = offerer->GetDataChannel().Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(offerer_channel.ok());
|
||||
ExceptionOr<rtc::scoped_refptr<webrtc::DataChannelInterface>>
|
||||
answerer_channel = answerer->GetDataChannel().Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(answerer_channel.ok());
|
||||
|
||||
// Send message on data channel
|
||||
const char message[] = "Test";
|
||||
offerer_channel.result()->Send(webrtc::DataBuffer(message));
|
||||
ExceptionOr<ByteArray> received_message =
|
||||
message_received_future.Get(absl::Seconds(1));
|
||||
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();
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
// Did not set offer as local session description
|
||||
EXPECT_TRUE(answerer->OnOfferReceived(offer));
|
||||
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
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->Close());
|
||||
|
||||
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();
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
|
||||
EXPECT_TRUE(offerer->Close());
|
||||
|
||||
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->Close());
|
||||
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_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 {
|
||||
std::function<void()> data_channel_closed_cb = DefaultCallback<>();
|
||||
|
||||
// Called when a new message was received on the data channel.
|
||||
std::function<void(const ByteArray&)> data_channel_message_received_cb =
|
||||
DefaultCallback<const ByteArray&>();
|
||||
|
||||
// Called when the data channel indicates that the buffered amount has
|
||||
// changed.
|
||||
std::function<void()> data_channel_buffered_amount_changed_cb =
|
||||
DefaultCallback<>();
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "core/internal/mediums/webrtc/data_channel_observer_impl.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
DataChannelObserverImpl::DataChannelObserverImpl(
|
||||
DataChannelListener* data_channel_listener,
|
||||
DataChannelStateChangeCallback callback)
|
||||
: data_channel_listener_(data_channel_listener),
|
||||
state_change_callback_(std::move(callback)) {}
|
||||
|
||||
void DataChannelObserverImpl::OnStateChange() { state_change_callback_(); }
|
||||
|
||||
void DataChannelObserverImpl::OnMessage(const webrtc::DataBuffer& buffer) {
|
||||
data_channel_listener_->data_channel_message_received_cb(
|
||||
ByteArray(buffer.data.data<char>(), buffer.size()));
|
||||
}
|
||||
|
||||
void DataChannelObserverImpl::OnBufferedAmountChange(uint64_t sent_data_size) {
|
||||
data_channel_listener_->data_channel_buffered_amount_changed_cb();
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_
|
||||
|
||||
#include "core/internal/mediums/webrtc/data_channel_listener.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class DataChannelObserverImpl : public webrtc::DataChannelObserver {
|
||||
public:
|
||||
using DataChannelStateChangeCallback = std::function<void()>;
|
||||
|
||||
~DataChannelObserverImpl() override = default;
|
||||
DataChannelObserverImpl(DataChannelListener* data_channel_listener,
|
||||
DataChannelStateChangeCallback callback);
|
||||
|
||||
// webrtc::DataChannelObserver:
|
||||
void OnStateChange() override;
|
||||
void OnMessage(const webrtc::DataBuffer& buffer) override;
|
||||
void OnBufferedAmountChange(uint64_t sent_data_size) override;
|
||||
|
||||
private:
|
||||
DataChannelListener* data_channel_listener_;
|
||||
DataChannelStateChangeCallback state_change_callback_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_
|
||||
@@ -0,0 +1,25 @@
|
||||
#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_
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "core/internal/mediums/webrtc/peer_connection_observer_impl.h"
|
||||
|
||||
#include "core/internal/mediums/webrtc/connection_flow.h"
|
||||
#include "platform/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
PeerConnectionObserverImpl::PeerConnectionObserverImpl(
|
||||
ConnectionFlow* connection_flow,
|
||||
LocalIceCandidateListener local_ice_candidate_listener)
|
||||
: connection_flow_(connection_flow),
|
||||
local_ice_candidate_listener_(std::move(local_ice_candidate_listener)) {}
|
||||
|
||||
void PeerConnectionObserverImpl::OnIceCandidate(
|
||||
const webrtc::IceCandidateInterface* candidate) {
|
||||
NEARBY_LOG(INFO, "OnIceCandidate");
|
||||
local_ice_candidate_listener_.local_ice_candidate_found_cb(candidate);
|
||||
}
|
||||
|
||||
void PeerConnectionObserverImpl::OnSignalingChange(
|
||||
webrtc::PeerConnectionInterface::SignalingState new_state) {
|
||||
NEARBY_LOG(INFO, "OnSignalingChange: %d", new_state);
|
||||
|
||||
OffloadFromSignalingThread([this, new_state]() {
|
||||
if (new_state == webrtc::PeerConnectionInterface::SignalingState::kStable)
|
||||
connection_flow_->OnSignalingStable();
|
||||
});
|
||||
}
|
||||
|
||||
void PeerConnectionObserverImpl::OnDataChannel(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
NEARBY_LOG(INFO, "OnDataChannel");
|
||||
|
||||
data_channel->RegisterObserver(
|
||||
connection_flow_->CreateDataChannelObserver(data_channel));
|
||||
}
|
||||
|
||||
void PeerConnectionObserverImpl::OnIceGatheringChange(
|
||||
webrtc::PeerConnectionInterface::IceGatheringState new_state) {
|
||||
NEARBY_LOG(INFO, "OnIceGatheringChange: %d", new_state);
|
||||
}
|
||||
|
||||
void PeerConnectionObserverImpl::OnConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
|
||||
NEARBY_LOG(INFO, "OnConnectionChange: %d", new_state);
|
||||
|
||||
OffloadFromSignalingThread([this, new_state]() {
|
||||
connection_flow_->ProcessOnPeerConnectionChange(new_state);
|
||||
});
|
||||
}
|
||||
|
||||
void PeerConnectionObserverImpl ::OnRenegotiationNeeded() {
|
||||
NEARBY_LOG(INFO, "OnRenegotiationNeeded");
|
||||
}
|
||||
|
||||
void PeerConnectionObserverImpl::OffloadFromSignalingThread(Runnable runnable) {
|
||||
single_threaded_signaling_offloader_.Execute(std::move(runnable));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_H_
|
||||
|
||||
#include "core/internal/mediums/webrtc/local_ice_candidate_listener.h"
|
||||
#include "platform/public/single_thread_executor.h"
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class ConnectionFlow;
|
||||
|
||||
class PeerConnectionObserverImpl : public webrtc::PeerConnectionObserver {
|
||||
public:
|
||||
~PeerConnectionObserverImpl() override = default;
|
||||
PeerConnectionObserverImpl(
|
||||
ConnectionFlow* connection_flow,
|
||||
LocalIceCandidateListener local_ice_candidate_listener);
|
||||
|
||||
// webrtc::PeerConnectionObserver:
|
||||
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;
|
||||
|
||||
private:
|
||||
void OffloadFromSignalingThread(Runnable runnable);
|
||||
|
||||
ConnectionFlow* connection_flow_;
|
||||
LocalIceCandidateListener local_ice_candidate_listener_;
|
||||
SingleThreadExecutor single_threaded_signaling_offloader_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_H_
|
||||
@@ -14,27 +14,26 @@ namespace mediums {
|
||||
namespace {
|
||||
constexpr int kPeerIdLength = 64;
|
||||
|
||||
std::string BytesToStringUppercase(ConstPtr<ByteArray> bytes) {
|
||||
std::string BytesToStringUppercase(const ByteArray& bytes) {
|
||||
std::string hex_string(
|
||||
absl::BytesToHexString(std::string(bytes->getData(), bytes->size())));
|
||||
absl::BytesToHexString(std::string(bytes.data(), bytes.size())));
|
||||
absl::AsciiStrToUpper(&hex_string);
|
||||
return hex_string;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ConstPtr<PeerId> PeerId::FromRandom(Ptr<HashUtils> hash_utils) {
|
||||
return FromSeed(Utils::generateRandomBytes(kPeerIdLength), hash_utils);
|
||||
PeerId PeerId::FromRandom() {
|
||||
return FromSeed(Utils::GenerateRandomBytes(kPeerIdLength));
|
||||
}
|
||||
|
||||
ConstPtr<PeerId> PeerId::FromSeed(ConstPtr<ByteArray> seed,
|
||||
Ptr<HashUtils> hash_utils) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> full_hash(
|
||||
Utils::sha256Hash(hash_utils, seed, kPeerIdLength));
|
||||
ScopedPtr<ConstPtr<ByteArray>> hashedSeed(
|
||||
MakeConstPtr(new ByteArray(full_hash->getData(), kPeerIdLength / 2)));
|
||||
return MakeConstPtr(new PeerId(BytesToStringUppercase(hashedSeed.get())));
|
||||
PeerId PeerId::FromSeed(const ByteArray& seed) {
|
||||
ByteArray full_hash(Utils::Sha256Hash(seed, kPeerIdLength));
|
||||
ByteArray hashed_seed(full_hash.data(), kPeerIdLength / 2);
|
||||
return PeerId(BytesToStringUppercase(hashed_seed));
|
||||
}
|
||||
|
||||
bool PeerId::IsValid() const { return !id_.empty(); }
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
|
||||
|
||||
#include "platform/api/hash_utils.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -12,20 +12,22 @@ namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// PeerId is used as an identifier to exchange SDP messages to establish WebRTC
|
||||
// p2p connection.
|
||||
// p2p connection. An empty PeerId is considered to be invalid.
|
||||
class PeerId {
|
||||
public:
|
||||
PeerId() = default;
|
||||
explicit PeerId(const std::string& id) : id_(id) {}
|
||||
~PeerId() = default;
|
||||
|
||||
static ConstPtr<PeerId> FromRandom(Ptr<HashUtils> hash_utils);
|
||||
static ConstPtr<PeerId> FromSeed(ConstPtr<ByteArray> seed,
|
||||
Ptr<HashUtils> hash_utils);
|
||||
static PeerId FromRandom();
|
||||
static PeerId FromSeed(const ByteArray& seed);
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
const std::string& GetId() const { return id_; }
|
||||
|
||||
private:
|
||||
const std::string id_;
|
||||
std::string id_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -1,73 +1,39 @@
|
||||
#include "core/internal/mediums/webrtc/peer_id.h"
|
||||
|
||||
#include "platform/api/hash_utils.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.h"
|
||||
#include <memory>
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/crypto.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
class MockHashUtils : public HashUtils {
|
||||
public:
|
||||
MOCK_METHOD(ConstPtr<ByteArray>, md5, (const std::string& input), (override));
|
||||
MOCK_METHOD(ConstPtr<ByteArray>, sha256, (const std::string& input),
|
||||
(override));
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(PeerIdTest, GenerateRandomPeerId) {
|
||||
// These are actual SHA-256 values for |seed| = "seed".
|
||||
std::string hashed_output =
|
||||
"19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b";
|
||||
std::string expected_peer_id =
|
||||
"19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B";
|
||||
|
||||
Ptr<testing::NiceMock<MockHashUtils>> mock_hash_utils(
|
||||
MakePtr(new MockHashUtils()));
|
||||
ON_CALL(*mock_hash_utils.get(), sha256(testing::_))
|
||||
.WillByDefault(testing::Return(
|
||||
MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output)))));
|
||||
EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::_));
|
||||
|
||||
ConstPtr<PeerId> peer_id = PeerId::FromRandom(mock_hash_utils);
|
||||
ASSERT_EQ(64, peer_id->GetId().size());
|
||||
ASSERT_EQ(expected_peer_id, peer_id->GetId());
|
||||
PeerId peer_id = PeerId::FromRandom();
|
||||
EXPECT_EQ(64, peer_id.GetId().size());
|
||||
}
|
||||
|
||||
TEST(PeerIdTest, GenerateFromSeed) {
|
||||
// Values calculated by running actual SHA-256 hash on |seed|.
|
||||
std::string seed = "sesdfed";
|
||||
std::string hashed_output =
|
||||
"19b25856e1c150ca834cffc8b59b23adbd0ec0389e58eb22b3b64768098d002b";
|
||||
std::string seed = "seed";
|
||||
std::string expected_peer_id =
|
||||
"19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B";
|
||||
|
||||
Ptr<testing::NiceMock<MockHashUtils>> mock_hash_utils(
|
||||
MakePtr(new MockHashUtils()));
|
||||
ON_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed)))
|
||||
.WillByDefault(testing::Return(
|
||||
MakeConstPtr(new ByteArray(absl::HexStringToBytes(hashed_output)))));
|
||||
EXPECT_CALL(*mock_hash_utils.get(), sha256(testing::Eq(seed)));
|
||||
ByteArray seed_bytes(seed);
|
||||
PeerId peer_id = PeerId::FromSeed(seed_bytes);
|
||||
|
||||
ConstPtr<PeerId> peer_id =
|
||||
PeerId::FromSeed(MakeConstPtr(new ByteArray(seed)), mock_hash_utils);
|
||||
|
||||
ASSERT_EQ(64, peer_id->GetId().size());
|
||||
ASSERT_EQ(expected_peer_id, peer_id->GetId());
|
||||
EXPECT_EQ(64, peer_id.GetId().size());
|
||||
EXPECT_EQ(expected_peer_id, peer_id.GetId());
|
||||
}
|
||||
|
||||
TEST(PeerIdTest, GetId) {
|
||||
const std::string id = "this_is_a_test";
|
||||
PeerId peer_id(id);
|
||||
ASSERT_EQ(id, peer_id.GetId());
|
||||
EXPECT_EQ(id, peer_id.GetId());
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#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_
|
||||
@@ -4,44 +4,43 @@ namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace webrtc_frames {
|
||||
using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame;
|
||||
|
||||
namespace {
|
||||
|
||||
ConstPtr<ByteArray> FrameToByteArray(
|
||||
const WebRtcSignalingFrame& signaling_frame) {
|
||||
ByteArray FrameToByteArray(const WebRtcSignalingFrame& signaling_frame) {
|
||||
std::string message;
|
||||
signaling_frame.SerializeToString(&message);
|
||||
return MakeConstPtr(new ByteArray(message.c_str(), message.size()));
|
||||
return ByteArray(message.c_str(), message.size());
|
||||
}
|
||||
|
||||
void SetSenderId(ConstPtr<PeerId> sender_id, WebRtcSignalingFrame& frame) {
|
||||
frame.mutable_sender_id()->set_id(sender_id->GetId());
|
||||
void SetSenderId(const PeerId& sender_id, WebRtcSignalingFrame& frame) {
|
||||
frame.mutable_sender_id()->set_id(sender_id.GetId());
|
||||
}
|
||||
|
||||
ConstPtr<webrtc::IceCandidateInterface> DecodeIceCandidate(
|
||||
const location::nearby::mediums::IceCandidate& ice_candidate_proto) {
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> DecodeIceCandidate(
|
||||
location::nearby::mediums::IceCandidate ice_candidate_proto) {
|
||||
webrtc::SdpParseError error;
|
||||
return ConstPtr<webrtc::IceCandidateInterface>(webrtc::CreateIceCandidate(
|
||||
ice_candidate_proto.sdp_mid(), ice_candidate_proto.sdp_m_line_index(),
|
||||
ice_candidate_proto.sdp(), &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
|
||||
|
||||
ConstPtr<ByteArray> EncodeReadyForSignalingPoke(ConstPtr<PeerId> sender_id) {
|
||||
ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
signaling_frame.mutable_ready_for_signaling_poke();
|
||||
signaling_frame.set_allocated_ready_for_signaling_poke(
|
||||
new location::nearby::mediums::ReadyForSignalingPoke());
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> EncodeOffer(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer) {
|
||||
ByteArray EncodeOffer(const PeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
@@ -53,9 +52,8 @@ ConstPtr<ByteArray> EncodeOffer(
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> EncodeAnswer(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer) {
|
||||
ByteArray EncodeAnswer(const PeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
@@ -67,8 +65,8 @@ ConstPtr<ByteArray> EncodeAnswer(
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ConstPtr<ByteArray> EncodeIceCandidates(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
ByteArray EncodeIceCandidates(
|
||||
const PeerId& sender_id,
|
||||
const std::vector<location::nearby::mediums::IceCandidate>&
|
||||
ice_candidates) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
@@ -81,25 +79,23 @@ ConstPtr<ByteArray> EncodeIceCandidates(
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
Ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
return MakePtr(webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kOffer,
|
||||
frame.offer().session_description().description())
|
||||
.release());
|
||||
return webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kOffer,
|
||||
frame.offer().session_description().description());
|
||||
}
|
||||
|
||||
Ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
return MakePtr(webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kAnswer,
|
||||
frame.answer().session_description().description())
|
||||
.release());
|
||||
return webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kAnswer,
|
||||
frame.answer().session_description().description());
|
||||
}
|
||||
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
for (const auto& candidate : frame.ice_candidates().ice_candidates()) {
|
||||
ice_candidates.push_back(DecodeIceCandidate(candidate));
|
||||
}
|
||||
@@ -118,7 +114,6 @@ location::nearby::mediums::IceCandidate EncodeIceCandidate(
|
||||
}
|
||||
|
||||
} // namespace webrtc_frames
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "core/internal/mediums/webrtc/peer_id.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
@@ -13,34 +12,30 @@ namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace webrtc_frames {
|
||||
|
||||
ConstPtr<ByteArray> EncodeReadyForSignalingPoke(ConstPtr<PeerId> sender_id);
|
||||
ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id);
|
||||
|
||||
ConstPtr<ByteArray> EncodeOffer(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer);
|
||||
ConstPtr<ByteArray> EncodeAnswer(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer);
|
||||
ByteArray EncodeOffer(const PeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer);
|
||||
ByteArray EncodeAnswer(const PeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer);
|
||||
|
||||
ConstPtr<ByteArray> EncodeIceCandidates(
|
||||
ConstPtr<PeerId> sender_id,
|
||||
ByteArray EncodeIceCandidates(
|
||||
const PeerId& sender_id,
|
||||
const std::vector<location::nearby::mediums::IceCandidate>& ice_candidates);
|
||||
location::nearby::mediums::IceCandidate EncodeIceCandidate(
|
||||
const webrtc::IceCandidateInterface& ice_candidate);
|
||||
|
||||
Ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
Ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
|
||||
} // namespace webrtc_frames
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <memory>
|
||||
|
||||
#include "core/internal/mediums/webrtc/peer_id.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "net/proto2/public/text_format.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
@@ -67,12 +66,11 @@ const char kIceCandidatesProto[] = R"(
|
||||
} // namespace
|
||||
|
||||
TEST(SignalingFramesTest, SignalingPoke) {
|
||||
ConstPtr<PeerId> sender_id(new PeerId("abc"));
|
||||
ConstPtr<ByteArray> encoded_poke = EncodeReadyForSignalingPoke(sender_id);
|
||||
PeerId sender_id("abc");
|
||||
ByteArray encoded_poke = EncodeReadyForSignalingPoke(sender_id);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_poke->getData(), encoded_poke->size()));
|
||||
frame.ParseFromString(std::string(encoded_poke.data(), encoded_poke.size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(R"(
|
||||
sender_id { id: "abc" }
|
||||
@@ -82,22 +80,23 @@ TEST(SignalingFramesTest, SignalingPoke) {
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidOffer) {
|
||||
ConstPtr<PeerId> sender_id(new PeerId("abc"));
|
||||
PeerId sender_id("abc");
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> offer =
|
||||
webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp);
|
||||
ConstPtr<ByteArray> encoded_offer = EncodeOffer(sender_id, *offer);
|
||||
ByteArray encoded_offer = EncodeOffer(sender_id, *offer);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_offer->getData(), encoded_offer->size()));
|
||||
std::string(encoded_offer.data(), encoded_offer.size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kOfferProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidOffer) {
|
||||
TEST(SignaingFramesTest, DecodeValidOffer) {
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromStringPiece(kOfferProto, &frame);
|
||||
Ptr<webrtc::SessionDescriptionInterface> decoded_offer = DecodeOffer(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;
|
||||
@@ -106,22 +105,23 @@ TEST(SignalingFramesTest, DecodeValidOffer) {
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidAnswer) {
|
||||
ConstPtr<PeerId> sender_id(new PeerId("abc"));
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> answer =
|
||||
webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, kSampleSdp);
|
||||
ConstPtr<ByteArray> encoded_answer = EncodeAnswer(sender_id, *answer);
|
||||
PeerId 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->getData(), encoded_answer->size()));
|
||||
std::string(encoded_answer.data(), encoded_answer.size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidAnswer) {
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromStringPiece(kAnswerProto, &frame);
|
||||
Ptr<webrtc::SessionDescriptionInterface> decoded_answer = DecodeAnswer(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;
|
||||
@@ -130,42 +130,40 @@ TEST(SignalingFramesTest, DecodeValidAnswer) {
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidIceCandidates) {
|
||||
ConstPtr<PeerId> sender_id(new PeerId("abc"));
|
||||
PeerId sender_id("abc");
|
||||
webrtc::SdpParseError error;
|
||||
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
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.get()));
|
||||
encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate));
|
||||
}
|
||||
ConstPtr<ByteArray> encoded_candidates =
|
||||
ByteArray encoded_candidates =
|
||||
EncodeIceCandidates(sender_id, encoded_candidates_vec);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_candidates->getData(), encoded_candidates->size()));
|
||||
std::string(encoded_candidates.data(), encoded_candidates.size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidIceCandidates) {
|
||||
webrtc::SdpParseError error;
|
||||
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
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;
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromStringPiece(kIceCandidatesProto, &frame);
|
||||
std::vector<ConstPtr<webrtc::IceCandidateInterface>> decoded_candidates =
|
||||
DecodeIceCandidates(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++) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket.h"
|
||||
|
||||
#include "platform/synchronized.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -8,128 +9,89 @@ namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// OutputStreamImpl
|
||||
template <typename Platform>
|
||||
Exception::Value WebRtcSocket<Platform>::OutputStreamImpl::write(
|
||||
ConstPtr<ByteArray> data) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_data(data);
|
||||
|
||||
if (scoped_data->size() > kMaxDataSize) {
|
||||
Exception WebRtcSocket::OutputStreamImpl::Write(const ByteArray& data) {
|
||||
if (data.size() > kMaxDataSize) {
|
||||
NEARBY_LOG(WARNING, "Sending data larger than 1MB");
|
||||
return Exception::IO;
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
socket_->BlockUntilSufficientSpaceInBuffer(scoped_data->size());
|
||||
socket_->BlockUntilSufficientSpaceInBuffer(data.size());
|
||||
|
||||
if (socket_->IsClosed()) {
|
||||
NEARBY_LOG(WARNING, "Tried sending message while socket is closed");
|
||||
return Exception::IO;
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
if (!socket_->SendMessage(scoped_data.release())) {
|
||||
return Exception::IO;
|
||||
if (!socket_->SendMessage(data)) {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
return Exception::NONE;
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Exception::Value WebRtcSocket<Platform>::OutputStreamImpl::flush() {
|
||||
Exception WebRtcSocket::OutputStreamImpl::Flush() {
|
||||
// Java implementation is empty.
|
||||
return Exception::NONE;
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Exception::Value WebRtcSocket<Platform>::OutputStreamImpl::close() {
|
||||
socket_->close();
|
||||
return Exception::NONE;
|
||||
Exception WebRtcSocket::OutputStreamImpl::Close() {
|
||||
socket_->Close();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
// WebRtcSocket
|
||||
template <typename Platform>
|
||||
WebRtcSocket<Platform>::WebRtcSocket(
|
||||
WebRtcSocket::WebRtcSocket(
|
||||
const std::string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)
|
||||
: name_(name),
|
||||
data_channel_(std::move(data_channel)),
|
||||
pipe_(MakeRefCountedPtr(new Pipe())),
|
||||
incoming_data_piped_input_stream_(Pipe::createInputStream(pipe_)),
|
||||
incoming_data_piped_output_stream_(Pipe::createOutputStream(pipe_)),
|
||||
output_stream_(MakePtr(new OutputStreamImpl(this))),
|
||||
closed_(Platform::createAtomicBoolean(false)),
|
||||
backpressure_lock_(Platform::createLock()),
|
||||
buffer_variable_(
|
||||
Platform::createConditionVariable(backpressure_lock_.get())) {}
|
||||
: name_(name), data_channel_(std::move(data_channel)) {}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<InputStream> WebRtcSocket<Platform>::getInputStream() {
|
||||
return incoming_data_piped_input_stream_.get();
|
||||
}
|
||||
InputStream& WebRtcSocket::GetInputStream() { return pipe_.GetInputStream(); }
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<OutputStream> WebRtcSocket<Platform>::getOutputStream() {
|
||||
return output_stream_.get();
|
||||
}
|
||||
OutputStream& WebRtcSocket::GetOutputStream() { return output_stream_; }
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::close() {
|
||||
void WebRtcSocket::Close() {
|
||||
if (IsClosed()) return;
|
||||
|
||||
closed_->set(true);
|
||||
incoming_data_piped_output_stream_->close();
|
||||
incoming_data_piped_input_stream_->close();
|
||||
closed_.Set(true);
|
||||
pipe_.GetInputStream().Close();
|
||||
pipe_.GetOutputStream().Close();
|
||||
data_channel_->Close();
|
||||
WakeUpWriter();
|
||||
if (!socket_closed_listener_.isNull()) {
|
||||
socket_closed_listener_->OnSocketClosed();
|
||||
socket_closed_listener_.socket_closed_cb();
|
||||
}
|
||||
|
||||
void WebRtcSocket::NotifyDataChannelMsgReceived(const ByteArray& message) {
|
||||
if (!pipe_.GetOutputStream().Write(message).Ok()) {
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pipe_.GetOutputStream().Flush().Ok()) Close();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::NotifyDataChannelMsgReceived(
|
||||
ConstPtr<ByteArray> message) {
|
||||
Exception::Value exception =
|
||||
incoming_data_piped_output_stream_->write(message);
|
||||
if (exception != Exception::NONE) close();
|
||||
void WebRtcSocket::NotifyDataChannelBufferedAmountChanged() { WakeUpWriter(); }
|
||||
|
||||
exception = incoming_data_piped_output_stream_->flush();
|
||||
if (exception != Exception::NONE) close();
|
||||
bool WebRtcSocket::SendMessage(const ByteArray& data) {
|
||||
return data_channel_->Send(
|
||||
webrtc::DataBuffer(std::string(data.data(), data.size())));
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::NotifyDataChannelBufferedAmountChanged() {
|
||||
WakeUpWriter();
|
||||
bool WebRtcSocket::IsClosed() { return closed_.Get(); }
|
||||
|
||||
void WebRtcSocket::WakeUpWriter() {
|
||||
MutexLock lock(&backpressure_mutex_);
|
||||
buffer_variable_.Notify();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WebRtcSocket<Platform>::SendMessage(ConstPtr<ByteArray> data) {
|
||||
ScopedPtr<ConstPtr<ByteArray>> scoped_data(data);
|
||||
return data_channel_->Send(webrtc::DataBuffer(
|
||||
std::string(scoped_data->getData(), scoped_data->size())));
|
||||
void WebRtcSocket::SetOnSocketClosedListener(SocketClosedListener&& listener) {
|
||||
socket_closed_listener_ = std::move(listener);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WebRtcSocket<Platform>::IsClosed() {
|
||||
return closed_->get();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::WakeUpWriter() {
|
||||
Synchronized s(backpressure_lock_.get());
|
||||
buffer_variable_->notify();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::SetOnSocketClosedListener(
|
||||
Ptr<SocketClosedListener> listener) {
|
||||
socket_closed_listener_ = listener;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WebRtcSocket<Platform>::BlockUntilSufficientSpaceInBuffer(int length) {
|
||||
Synchronized s(backpressure_lock_.get());
|
||||
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();
|
||||
buffer_variable_.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
|
||||
#include "platform/api/atomic_boolean.h"
|
||||
#include "platform/api/input_stream.h"
|
||||
#include "platform/api/output_stream.h"
|
||||
#include "platform/api/socket.h"
|
||||
#include "platform/pipe.h"
|
||||
#include "webrtc/api/data_channel_interface.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 "webrtc/api/data_channel_interface.h"
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
@@ -21,7 +25,6 @@ constexpr int kMaxDataSize = 1 * 1024 * 1024;
|
||||
//
|
||||
// Messages are buffered here to prevent the data channel from overflowing,
|
||||
// which could lead to data loss.
|
||||
template <typename Platform>
|
||||
class WebRtcSocket : public Socket {
|
||||
public:
|
||||
WebRtcSocket(const std::string& name,
|
||||
@@ -32,66 +35,62 @@ class WebRtcSocket : public Socket {
|
||||
WebRtcSocket& operator=(const WebRtcSocket& other) = delete;
|
||||
|
||||
// Overrides for location::nearby::Socket:
|
||||
Ptr<InputStream> getInputStream() override;
|
||||
Ptr<OutputStream> getOutputStream() override;
|
||||
void close() override;
|
||||
InputStream& GetInputStream() override;
|
||||
OutputStream& GetOutputStream() override;
|
||||
void Close() override;
|
||||
|
||||
// Callback from WebRTC data channel when new message has been received from
|
||||
// the remote.
|
||||
void NotifyDataChannelMsgReceived(ConstPtr<ByteArray> message);
|
||||
void NotifyDataChannelMsgReceived(const ByteArray& message);
|
||||
|
||||
// Callback from WebRTC data channel that the buffered data amount has
|
||||
// changed.
|
||||
void NotifyDataChannelBufferedAmountChanged();
|
||||
|
||||
// Listener class the gets called when the socket is closed.
|
||||
class SocketClosedListener {
|
||||
public:
|
||||
virtual ~SocketClosedListener() = default;
|
||||
virtual void OnSocketClosed() = 0;
|
||||
struct SocketClosedListener {
|
||||
std::function<void()> socket_closed_cb = DefaultCallback<>();
|
||||
};
|
||||
void SetOnSocketClosedListener(Ptr<SocketClosedListener> listener);
|
||||
|
||||
void SetOnSocketClosedListener(SocketClosedListener&& listener);
|
||||
|
||||
private:
|
||||
class OutputStreamImpl : public OutputStream {
|
||||
public:
|
||||
explicit OutputStreamImpl(WebRtcSocket<Platform>* const socket)
|
||||
: socket_(socket) {}
|
||||
explicit OutputStreamImpl(WebRtcSocket* const socket) : socket_(socket) {}
|
||||
~OutputStreamImpl() override = default;
|
||||
|
||||
OutputStreamImpl(const OutputStreamImpl& other) = delete;
|
||||
OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete;
|
||||
|
||||
// OutputStream:
|
||||
Exception::Value write(ConstPtr<ByteArray> data) override;
|
||||
Exception::Value flush() override;
|
||||
Exception::Value close() override;
|
||||
Exception Write(const ByteArray& data) override;
|
||||
Exception Flush() override;
|
||||
Exception Close() override;
|
||||
|
||||
private:
|
||||
// |this| OutputStreamImpl is owned by |socket_|.
|
||||
WebRtcSocket<Platform>* const socket_;
|
||||
WebRtcSocket* const socket_;
|
||||
};
|
||||
|
||||
void WakeUpWriter();
|
||||
bool IsClosed();
|
||||
bool SendMessage(ConstPtr<ByteArray> data);
|
||||
bool SendMessage(const ByteArray& data);
|
||||
void BlockUntilSufficientSpaceInBuffer(int length);
|
||||
|
||||
std::string name_;
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
|
||||
|
||||
Ptr<Pipe> pipe_;
|
||||
ScopedPtr<Ptr<InputStream>> incoming_data_piped_input_stream_;
|
||||
ScopedPtr<Ptr<OutputStream>> incoming_data_piped_output_stream_;
|
||||
Pipe pipe_;
|
||||
|
||||
ScopedPtr<Ptr<OutputStream>> output_stream_;
|
||||
OutputStreamImpl output_stream_{this};
|
||||
|
||||
ScopedPtr<Ptr<AtomicBoolean>> closed_;
|
||||
AtomicBoolean closed_{false};
|
||||
|
||||
Ptr<SocketClosedListener> socket_closed_listener_;
|
||||
SocketClosedListener socket_closed_listener_;
|
||||
|
||||
ScopedPtr<Ptr<Lock>> backpressure_lock_;
|
||||
ScopedPtr<Ptr<ConditionVariable>> buffer_variable_;
|
||||
mutable Mutex backpressure_mutex_;
|
||||
ConditionVariable buffer_variable_{&backpressure_mutex_};
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
@@ -99,6 +98,4 @@ class WebRtcSocket : public Socket {
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket.h"
|
||||
|
||||
#include "platform/api/platform.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/ptr.h"
|
||||
#include <memory>
|
||||
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
@@ -14,7 +14,7 @@ namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
using TestPlatform = platform::ImplementationPlatform;
|
||||
// using TestPlatform = platform::ImplementationPlatform;
|
||||
|
||||
const char kSocketName[] = "TestSocket";
|
||||
|
||||
@@ -43,110 +43,109 @@ class MockDataChannel
|
||||
|
||||
} // namespace
|
||||
|
||||
class MockSocketClosedListener
|
||||
: public WebRtcSocket<TestPlatform>::SocketClosedListener {
|
||||
public:
|
||||
MOCK_METHOD(void, OnSocketClosed, ());
|
||||
};
|
||||
|
||||
TEST(WebRtcSocketTest, ReadFromSocket) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
const ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(kMessage);
|
||||
ExceptionOr<ConstPtr<ByteArray>> result =
|
||||
webrtc_socket.getInputStream()->read();
|
||||
ExceptionOr<ByteArray> result = webrtc_socket.GetInputStream().Read(7);
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result(), kMessage);
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, ReadMultipleMessages) {
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("Me")));
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(
|
||||
MakeConstPtr(new ByteArray("ssa")));
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(MakeConstPtr(new ByteArray("ge")));
|
||||
ExceptionOr<ConstPtr<ByteArray>> result;
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"Me"});
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"ssa"});
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(ByteArray{"ge"});
|
||||
|
||||
ExceptionOr<ByteArray> result;
|
||||
|
||||
// This behaviour is different from the Java code
|
||||
result = webrtc_socket.getInputStream()->read();
|
||||
result = webrtc_socket.GetInputStream().Read(7);
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result()->asString(), "Me");
|
||||
EXPECT_EQ(result.result(), ByteArray{"Me"});
|
||||
|
||||
result = webrtc_socket.getInputStream()->read();
|
||||
result = webrtc_socket.GetInputStream().Read(7);
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result()->asString(), "ssa");
|
||||
EXPECT_EQ(result.result(), ByteArray{"ssa"});
|
||||
|
||||
result = webrtc_socket.getInputStream()->read();
|
||||
result = webrtc_socket.GetInputStream().Read(7);
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result()->asString(), "ge");
|
||||
EXPECT_EQ(result.result(), ByteArray{"ge"});
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteToSocket) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
const ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Send(testing::_))
|
||||
.WillRepeatedly(testing::Return(true));
|
||||
EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::NONE);
|
||||
EXPECT_TRUE(webrtc_socket.GetOutputStream().Write(kMessage).Ok());
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, SendDataBiggerThanMax) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray(kMaxDataSize + 1));
|
||||
const ByteArray kMessage{kMaxDataSize + 1};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0);
|
||||
EXPECT_EQ(webrtc_socket.getOutputStream()->write(kMessage), Exception::IO);
|
||||
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
|
||||
Exception{Exception::kIo});
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteToDataChannelFails) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
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::IO);
|
||||
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
|
||||
Exception{Exception::kIo});
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, Close) {
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
ScopedPtr<Ptr<MockSocketClosedListener>> mock_listener(
|
||||
MakePtr(new MockSocketClosedListener()));
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
webrtc_socket.SetOnSocketClosedListener(mock_listener.get());
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
EXPECT_CALL(*mock_listener, OnSocketClosed());
|
||||
EXPECT_CALL(*mock_data_channel, Close());
|
||||
webrtc_socket.close();
|
||||
|
||||
int socket_closed_cb_called = 0;
|
||||
|
||||
webrtc_socket.SetOnSocketClosedListener(
|
||||
{.socket_closed_cb = [&]() { socket_closed_cb_called++; }});
|
||||
webrtc_socket.Close();
|
||||
|
||||
EXPECT_EQ(socket_closed_cb_called, 1);
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteOnClosedChannel) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
webrtc_socket.close();
|
||||
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::IO);
|
||||
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
|
||||
Exception{Exception::kIo});
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, ReadFromClosedChannel) {
|
||||
ConstPtr<ByteArray> kMessage = MakeConstPtr(new ByteArray("Message"));
|
||||
ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket<TestPlatform> webrtc_socket(kSocketName, mock_data_channel);
|
||||
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();
|
||||
webrtc_socket.GetOutputStream().Write(kMessage);
|
||||
webrtc_socket.Close();
|
||||
|
||||
EXPECT_EQ(webrtc_socket.getInputStream()->read().exception(), Exception::IO);
|
||||
EXPECT_EQ(webrtc_socket.GetInputStream().Read(7).exception(), Exception::kIo);
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_
|
||||
#define CORE_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket.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 NotifyDataChannelMsgReceived(const ByteArray& message) {
|
||||
impl_->NotifyDataChannelMsgReceived(message);
|
||||
}
|
||||
|
||||
void NotifyDataChannelBufferedAmountChanged() {
|
||||
impl_->NotifyDataChannelBufferedAmountChanged();
|
||||
}
|
||||
|
||||
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_WRAPPER_H_
|
||||
@@ -0,0 +1,285 @@
|
||||
#include "core/internal/mediums/webrtc.h"
|
||||
|
||||
#include "core/internal/mediums/webrtc/webrtc_socket_wrapper.h"
|
||||
#include "platform/base/listeners.h"
|
||||
#include "platform/base/medium_environment.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
class WebRtcTest : public ::testing::Test {
|
||||
protected:
|
||||
WebRtcTest() {
|
||||
MediumEnvironment::Instance().Stop();
|
||||
MediumEnvironment::Instance().Start({.webrtc_enabled = true});
|
||||
}
|
||||
};
|
||||
|
||||
// Basic test to check that device is accepting connections when initialized.
|
||||
TEST_F(WebRtcTest, NotAcceptingConnections) {
|
||||
WebRtc webrtc;
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
EXPECT_FALSE(webrtc.IsAcceptingConnections());
|
||||
}
|
||||
|
||||
// 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) {
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
|
||||
WebRtc webrtc;
|
||||
PeerId self_id("peer_id");
|
||||
LocationHint location_hint{};
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
ASSERT_TRUE(webrtc.StartAcceptingConnections(
|
||||
self_id, location_hint, {mock_accepted_callback_.AsStdFunction()}));
|
||||
EXPECT_FALSE(webrtc.StartAcceptingConnections(
|
||||
self_id, location_hint, {mock_accepted_callback_.AsStdFunction()}));
|
||||
EXPECT_TRUE(webrtc.IsAcceptingConnections());
|
||||
}
|
||||
|
||||
// Tests the flow when the device tries to connect but the data channel times
|
||||
// out.
|
||||
TEST_F(WebRtcTest, Connect_DataChannelTimeOut) {
|
||||
WebRtc webrtc;
|
||||
PeerId peer_id("peer_id");
|
||||
LocationHint location_hint;
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
WebRtcSocketWrapper wrapper_1 = webrtc.Connect(peer_id, location_hint);
|
||||
EXPECT_FALSE(wrapper_1.IsValid());
|
||||
|
||||
EXPECT_TRUE(webrtc.StartAcceptingConnections(peer_id, location_hint,
|
||||
AcceptedConnectionCallback()));
|
||||
}
|
||||
|
||||
// Tests the flow when the device calls Connect() after calling
|
||||
// StartAcceptingConnections() without StopAcceptingConnections().
|
||||
TEST_F(WebRtcTest, StartAcceptingConnection_ThenConnect) {
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
|
||||
WebRtc webrtc;
|
||||
PeerId self_id("peer_id");
|
||||
LocationHint location_hint;
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
ASSERT_TRUE(webrtc.StartAcceptingConnections(
|
||||
self_id, location_hint, {mock_accepted_callback_.AsStdFunction()}));
|
||||
WebRtcSocketWrapper wrapper =
|
||||
webrtc.Connect(PeerId("random_peer_id"), location_hint);
|
||||
EXPECT_TRUE(webrtc.IsAcceptingConnections());
|
||||
EXPECT_FALSE(wrapper.IsValid());
|
||||
EXPECT_FALSE(webrtc.StartAcceptingConnections(
|
||||
self_id, location_hint, {mock_accepted_callback_.AsStdFunction()}));
|
||||
}
|
||||
|
||||
// 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) {
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
|
||||
WebRtc webrtc;
|
||||
PeerId self_id("peer_id");
|
||||
LocationHint location_hint;
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
ASSERT_TRUE(webrtc.StartAcceptingConnections(
|
||||
self_id, location_hint, {mock_accepted_callback_.AsStdFunction()}));
|
||||
webrtc.StopAcceptingConnections();
|
||||
EXPECT_FALSE(webrtc.IsAcceptingConnections());
|
||||
}
|
||||
|
||||
// Tests the flow when the device tries to connect to two different peers
|
||||
// without disconnecting in between.
|
||||
TEST_F(WebRtcTest, ConnectTwice) {
|
||||
WebRtc receiver, sender, device_c;
|
||||
WebRtcSocketWrapper receiver_socket, sender_socket;
|
||||
const PeerId self_id("self_id"), other_id("other_id");
|
||||
LocationHint location_hint;
|
||||
Future<bool> connected;
|
||||
ByteArray message("message xyz");
|
||||
|
||||
receiver.StartAcceptingConnections(
|
||||
self_id, location_hint,
|
||||
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
|
||||
receiver_socket = wrapper;
|
||||
connected.Set(receiver_socket.IsValid());
|
||||
}});
|
||||
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
device_c.StartAcceptingConnections(other_id, location_hint,
|
||||
{mock_accepted_callback_.AsStdFunction()});
|
||||
|
||||
sender_socket = sender.Connect(self_id, location_hint);
|
||||
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(other_id, location_hint);
|
||||
EXPECT_FALSE(socket.IsValid());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
WebRtc receiver, sender;
|
||||
WebRtcSocketWrapper receiver_socket, sender_socket;
|
||||
const PeerId self_id("self_id");
|
||||
LocationHint location_hint;
|
||||
Future<bool> connected;
|
||||
ByteArray message("message xyz");
|
||||
|
||||
receiver.StartAcceptingConnections(
|
||||
self_id, location_hint,
|
||||
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
|
||||
receiver_socket = wrapper;
|
||||
connected.Set(receiver_socket.IsValid());
|
||||
}});
|
||||
|
||||
sender_socket = sender.Connect(self_id, location_hint);
|
||||
EXPECT_TRUE(sender_socket.IsValid());
|
||||
|
||||
ExceptionOr<bool> devices_connected = connected.Get();
|
||||
ASSERT_TRUE(devices_connected.ok());
|
||||
EXPECT_TRUE(devices_connected.result());
|
||||
|
||||
receiver_socket.Close();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
WebRtc receiver, sender;
|
||||
WebRtcSocketWrapper receiver_socket, sender_socket;
|
||||
const PeerId self_id("self_id");
|
||||
LocationHint location_hint;
|
||||
Future<bool> connected;
|
||||
ByteArray message("message");
|
||||
|
||||
receiver.StartAcceptingConnections(
|
||||
self_id, location_hint,
|
||||
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
|
||||
receiver_socket = wrapper;
|
||||
connected.Set(receiver_socket.IsValid());
|
||||
}});
|
||||
|
||||
sender_socket = sender.Connect(self_id, location_hint);
|
||||
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();
|
||||
}
|
||||
|
||||
// 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_F(WebRtcTest, ConnectBothDevices_ShutdownSignaling_SendData) {
|
||||
WebRtc receiver, sender;
|
||||
WebRtcSocketWrapper receiver_socket, sender_socket;
|
||||
const PeerId self_id("self_id");
|
||||
LocationHint location_hint;
|
||||
Future<bool> connected;
|
||||
ByteArray message("message xyz");
|
||||
|
||||
receiver.StartAcceptingConnections(
|
||||
self_id, location_hint,
|
||||
{[&receiver_socket, connected](WebRtcSocketWrapper wrapper) mutable {
|
||||
receiver_socket = wrapper;
|
||||
connected.Set(receiver_socket.IsValid());
|
||||
}});
|
||||
|
||||
sender_socket = sender.Connect(self_id, location_hint);
|
||||
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();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
TEST_F(WebRtcTest, StartAcceptingConnections_NullPeerConnection) {
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
|
||||
MediumEnvironment::Instance().SetUseValidPeerConnection(
|
||||
/*use_valid_peer_connection=*/false);
|
||||
|
||||
WebRtc webrtc;
|
||||
PeerId self_id("peer_id");
|
||||
LocationHint location_hint;
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
EXPECT_FALSE(webrtc.StartAcceptingConnections(
|
||||
self_id, location_hint, {mock_accepted_callback_.AsStdFunction()}));
|
||||
}
|
||||
|
||||
TEST_F(WebRtcTest, Connect_NullPeerConnection) {
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
|
||||
MediumEnvironment::Instance().SetUseValidPeerConnection(
|
||||
/*use_valid_peer_connection=*/false);
|
||||
|
||||
WebRtc webrtc;
|
||||
PeerId self_id("peer_id");
|
||||
LocationHint location_hint;
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
WebRtcSocketWrapper wrapper =
|
||||
webrtc.Connect(PeerId("random_peer_id"), location_hint);
|
||||
EXPECT_FALSE(wrapper.IsValid());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -1,213 +1,248 @@
|
||||
#include "core/internal/mediums/wifi_lan.h"
|
||||
|
||||
#include "platform/synchronized.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
template <typename Platform>
|
||||
WifiLan<Platform>::WifiLan()
|
||||
: lock_(Platform::createLock()),
|
||||
wifi_lan_medium_(Platform::createWifiLanMedium()) {}
|
||||
bool WifiLan::IsAvailable() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::IsAvailable() {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !wifi_lan_medium_.isNull();
|
||||
return IsAvailableLocked();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::StartAdvertising(
|
||||
absl::string_view service_id,
|
||||
absl::string_view wifi_lan_service_info_name) {
|
||||
Synchronized s(lock_.get());
|
||||
bool WifiLan::IsAvailableLocked() const { return medium_.IsValid(); }
|
||||
|
||||
if (!IsAvailable()) {
|
||||
bool WifiLan::StartAdvertising(const std::string& service_id,
|
||||
const std::string& service_info_name,
|
||||
const std::string& endpoint_info_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_info_name.empty()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to turn on WifiLan advertising. Empty service info name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// wifi_lan_medium_->StartAdvertising(service_id,
|
||||
// wifi_lan_service_info_name));
|
||||
|
||||
advertising_info_.service_id.assign(service_id.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WifiLan<Platform>::StopAdvertising(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (!IsAdvertising()) {
|
||||
return;
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't turn on WifiLan advertising. WifiLan is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// wifi_lan_medium_->StopAdvertising(advertising_info_.service_id);
|
||||
if (!medium_.StartAdvertising(service_id, service_info_name,
|
||||
endpoint_info_name)) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Failed to turn on WifiLan advertising with service info name=%s",
|
||||
service_info_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOGS(INFO) << "Turned on WifiLan advertising with service info name="
|
||||
<< service_info_name << ", service id=" << service_id;
|
||||
advertising_info_.Add(service_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WifiLan::StopAdvertising(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAdvertisingLocked(service_id)) {
|
||||
NEARBY_LOG(INFO, "Can't turn off WifiLan advertising; it is already off");
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Turned off WifiLan advertising with service id=%s",
|
||||
service_id.c_str());
|
||||
bool ret = medium_.StopAdvertising(service_id);
|
||||
// Reset our bundle of advertising state to mark that we're no longer
|
||||
// advertising.
|
||||
advertising_info_.service_id.clear();
|
||||
advertising_info_.Remove(service_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::IsAdvertising() {
|
||||
Synchronized s(lock_.get());
|
||||
bool WifiLan::IsAdvertising(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return !advertising_info_.service_id.empty();
|
||||
return IsAdvertisingLocked(service_id);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::StartDiscovery(
|
||||
absl::string_view service_id,
|
||||
Ptr<DiscoveredServiceCallback> discovered_service_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
bool WifiLan::IsAdvertisingLocked(const std::string& service_id) {
|
||||
return advertising_info_.Existed(service_id);
|
||||
}
|
||||
|
||||
if (discovered_service_callback.isNull() || service_id.empty()) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan
|
||||
// discovering because a null parameter was passed in.");
|
||||
bool WifiLan::StartDiscovery(const std::string& service_id,
|
||||
DiscoveredServiceCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_id.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start WifiLan discovering with empty service id.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsDiscovering(service_id)) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Refusing to start WifiLan
|
||||
// discovering because we are already discovering.");
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Can't discover WifiLan services because WifiLan isn't available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailable()) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Can't start WifiLan discovering
|
||||
// because WifiLan isn't available.");
|
||||
if (IsDiscoveringLocked(service_id)) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to start discovery of WifiLan services because another "
|
||||
"discovery is already in-progress.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid leaks.
|
||||
ScopedPtr<Ptr<DiscoveredServiceCallbackBridge>>
|
||||
scoped_discovered_service_callback_bridge(
|
||||
new DiscoveredServiceCallbackBridge(discovered_service_callback));
|
||||
if (!medium_.StartDiscovery(service_id, callback)) {
|
||||
NEARBY_LOG(INFO, "Failed to start discovery of WifiLan services.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// A possible implementation is:
|
||||
// wifi_lan_medium_->StartDiscovery(
|
||||
// service_id, Ptr<DiscoveredServiceCallbackBridge>(
|
||||
// discovered_service_callback_bridge.release()));
|
||||
|
||||
discovering_info_.service_id.assign(service_id.data());
|
||||
return false;
|
||||
NEARBY_LOG(INFO, "Turned on WifiLan discovering with service id=%s",
|
||||
service_id.c_str());
|
||||
// Mark the fact that we're currently performing a WifiLan discovering.
|
||||
discovering_info_.Add(service_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WifiLan<Platform>::StopDiscovery(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
bool WifiLan::StopDiscovery(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsDiscovering(service_id)) {
|
||||
// TODO(b/149806065): logger.atDebug().log("Can't turn off WifiLan
|
||||
// discovering because we never started discovering.");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// wifi_lan_medium_->StopDiscovery(discovering_info_.service_id);
|
||||
// Reset our bundle of scanning state to mark that we're no longer scanning.
|
||||
discovering_info_.service_id.clear();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::IsDiscovering(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
return !discovering_info_.service_id.empty();
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::StartAcceptingConnections(
|
||||
absl::string_view service_id,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback) {
|
||||
Synchronized s(lock_.get());
|
||||
|
||||
if (accepted_connection_callback.isNull() || service_id.empty()) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Refusing to start accepting
|
||||
// WifiLan connections because a null parameter was passed in.");
|
||||
if (!IsDiscoveringLocked(service_id)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't turn off WifiLan discovering because we never started "
|
||||
"discovering.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnections(service_id)) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Refusing to start accepting
|
||||
// WifiLan connections for %s because another WifiLan service socket is
|
||||
// already in-progress.", service_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailable()) {
|
||||
// TODO(b/149806065): logger.atSevere().log("Can't start accepting WifiLan
|
||||
// connections for %s because WifiLan isn't available.", serviceId);
|
||||
return false;
|
||||
}
|
||||
|
||||
ScopedPtr<Ptr<WifiLanAcceptedConnectionCallback>>
|
||||
scoped_wifi_lan_accepted_connection_callback(
|
||||
new WifiLanAcceptedConnectionCallback(
|
||||
accepted_connection_callback));
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// A possible implementation is:
|
||||
// wifi_lan_medium_->StartAcceptingConnections(
|
||||
// service_id, Ptr<WifiLanAcceptedConnectionCallback>(
|
||||
// wifi_lan_accepted_connection_callback.release()));
|
||||
|
||||
accepting_connections_info_.service_id.assign(service_id.data());
|
||||
return false;
|
||||
NEARBY_LOG(INFO, "Turned off WifiLan discovering with service id=%s",
|
||||
service_id.c_str());
|
||||
bool ret = medium_.StopDiscovery(service_id);
|
||||
discovering_info_.Clear();
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
void WifiLan<Platform>::StopAcceptingConnections(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
bool WifiLan::IsDiscovering(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAcceptingConnections(service_id)) {
|
||||
// TODO(b/149806065): logger.atDebug().log("Can't stop accepting WifiLan
|
||||
// connections because it was never started.");
|
||||
return;
|
||||
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_LOG(INFO,
|
||||
"Refusing to start accepting WifiLan connections with empty "
|
||||
"service id.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.);
|
||||
// A possible implementation is:
|
||||
// wifi_lan_medium_->StopAcceptingConnections(
|
||||
// accepting_connections_info_.service_id);
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't start accepting WifiLan connections for %s because "
|
||||
"WifiLan isn't available.",
|
||||
service_id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start accepting WifiLan connections for %s because "
|
||||
"another WifiLan service socket is already in-progress.",
|
||||
service_id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartAcceptingConnections(service_id, callback)) {
|
||||
NEARBY_LOG(INFO, "Failed to accept connections callback for %s.",
|
||||
service_id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
accepting_connections_info_.Add(service_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WifiLan::StopAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't stop accepting WifiLan 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_.service_id.clear();
|
||||
accepting_connections_info_.Remove(service_id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
bool WifiLan<Platform>::IsAcceptingConnections(absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
bool WifiLan::IsAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return !accepting_connections_info_.service_id.empty();
|
||||
return IsAcceptingConnectionsLocked(service_id);
|
||||
}
|
||||
|
||||
template <typename Platform>
|
||||
Ptr<WifiLanSocket> WifiLan<Platform>::Connect(
|
||||
Ptr<WifiLanService> wifi_lan_service, absl::string_view service_id) {
|
||||
Synchronized s(lock_.get());
|
||||
bool WifiLan::IsAcceptingConnectionsLocked(const std::string& service_id) {
|
||||
return accepting_connections_info_.Existed(service_id);
|
||||
}
|
||||
|
||||
if (wifi_lan_service.isNull() || service_id.empty()) {
|
||||
return Ptr<WifiLanSocket>();
|
||||
WifiLanSocket WifiLan::Connect(WifiLanService& wifi_lan_service,
|
||||
const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOG(INFO, "WifiLan::Connect: service=%p, service_info_name=%s",
|
||||
&wifi_lan_service, wifi_lan_service.GetServiceName().c_str());
|
||||
// Socket to return. To allow for NRVO to work, it has to be a single object.
|
||||
WifiLanSocket socket;
|
||||
|
||||
if (service_id.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to create WifiLan socket with empty service_id.");
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!IsAvailable()) {
|
||||
return Ptr<WifiLanSocket>();
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't create client WifiLan socket [service_id=%s]; WifiLan "
|
||||
"isn't available.",
|
||||
service_id.c_str());
|
||||
return socket;
|
||||
}
|
||||
|
||||
// TODO(b/149806065): Implements platform wifi-lan medium.
|
||||
// A possible implementation is:
|
||||
// return wifi_lan_medium_->Connect(wifi_lan_service, service_id);
|
||||
return Ptr<WifiLanSocket>();
|
||||
socket = medium_.Connect(wifi_lan_service, service_id);
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOG(INFO, "Failed to Connect via WifiLan [service_id=%s]",
|
||||
service_id.c_str());
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
WifiLanService WifiLan::GetRemoteWifiLanService(const std::string& ip_address,
|
||||
int port) {
|
||||
MutexLock lock(&mutex_);
|
||||
return medium_.FindRemoteService(ip_address, port);
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
@@ -2,159 +2,145 @@
|
||||
#define CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "platform/api/lock.h"
|
||||
#include "platform/api/wifi_lan.h"
|
||||
#include "platform/byte_array.h"
|
||||
#include "platform/port/string.h"
|
||||
#include "platform/ptr.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "platform/base/byte_array.h"
|
||||
#include "platform/public/multi_thread_executor.h"
|
||||
#include "platform/public/mutex.h"
|
||||
#include "platform/public/wifi_lan.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class DiscoveredServiceCallback {
|
||||
public:
|
||||
virtual ~DiscoveredServiceCallback() = default;
|
||||
|
||||
virtual void OnServiceDiscovered(Ptr<WifiLanService> wifi_lan_service) = 0;
|
||||
virtual void OnServiceLost(Ptr<WifiLanService> wifi_lan_service) = 0;
|
||||
};
|
||||
|
||||
template <typename Platform>
|
||||
class WifiLan {
|
||||
public:
|
||||
WifiLan();
|
||||
virtual ~WifiLan() = default;
|
||||
using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback;
|
||||
using AcceptedConnectionCallback = WifiLanMedium::AcceptedConnectionCallback;
|
||||
|
||||
bool IsAvailable();
|
||||
// Returns true, if WifiLan communications are supported by a platform.
|
||||
bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool StartAdvertising(absl::string_view service_id,
|
||||
absl::string_view wifi_lan_service_info_name);
|
||||
void StopAdvertising(absl::string_view service_id);
|
||||
bool IsAdvertising();
|
||||
// Sets custom service info name, endpoint info name and then enables WifiLan
|
||||
// advertising.
|
||||
// Returns true, if name is successfully set, and false otherwise.
|
||||
bool StartAdvertising(const std::string& service_id,
|
||||
const std::string& service_info_name,
|
||||
const std::string& endpoint_info_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool StartDiscovery(
|
||||
absl::string_view service_id,
|
||||
Ptr<DiscoveredServiceCallback> discovered_service_callback);
|
||||
void StopDiscovery(absl::string_view service_id);
|
||||
bool IsDiscovering(absl::string_view service_id);
|
||||
// Disables WifiLan advertising, and restores service info name to
|
||||
// what they were before the call to StartAdvertising().
|
||||
bool StopAdvertising(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
class AcceptedConnectionCallback {
|
||||
public:
|
||||
virtual ~AcceptedConnectionCallback() = default;
|
||||
bool IsAdvertising(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
virtual void OnConnectionAccepted(Ptr<WifiLanSocket> socket,
|
||||
absl::string_view service_id) = 0;
|
||||
};
|
||||
// Enables WifiLan discovery mode. Will report any discoverable services in
|
||||
// range through a callback. Returns true, if discovery mode was enabled,
|
||||
// false otherwise.
|
||||
bool StartDiscovery(const std::string& service_id,
|
||||
DiscoveredServiceCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool StartAcceptingConnections(
|
||||
absl::string_view service_id,
|
||||
Ptr<AcceptedConnectionCallback> accepted_connection_callback);
|
||||
void StopAcceptingConnections(absl::string_view service_id);
|
||||
bool IsAcceptingConnections(absl::string_view service_id);
|
||||
// Disables WifiLan discovery mode.
|
||||
bool StopDiscovery(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
Ptr<WifiLanSocket> Connect(Ptr<WifiLanService> wifi_lan_service,
|
||||
absl::string_view service_id);
|
||||
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(WifiLanService& wifi_lan_service,
|
||||
const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
WifiLanService GetRemoteWifiLanService(const std::string& ip_address,
|
||||
int port) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
class DiscoveredServiceCallbackBridge
|
||||
: public WifiLanMedium::DiscoveredServiceCallback {
|
||||
public:
|
||||
explicit DiscoveredServiceCallbackBridge(
|
||||
Ptr<mediums::DiscoveredServiceCallback> discovered_service_callback)
|
||||
: discovered_service_callback_(discovered_service_callback) {}
|
||||
~DiscoveredServiceCallbackBridge() override = default;
|
||||
|
||||
void OnServiceDiscovered(Ptr<WifiLanService> wifi_lan_service) override {
|
||||
discovered_service_callback_->OnServiceDiscovered(wifi_lan_service);
|
||||
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);
|
||||
}
|
||||
void OnServiceLost(Ptr<WifiLanService> wifi_lan_service) override {
|
||||
discovered_service_callback_->OnServiceLost(wifi_lan_service);
|
||||
bool Existed(const std::string& service_id) const {
|
||||
return service_ids.contains(service_id);
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedPtr<Ptr<mediums::DiscoveredServiceCallback>>
|
||||
discovered_service_callback_;
|
||||
};
|
||||
|
||||
class WifiLanAcceptedConnectionCallback
|
||||
: public WifiLanMedium::AcceptedConnectionCallback {
|
||||
public:
|
||||
explicit WifiLanAcceptedConnectionCallback(
|
||||
Ptr<WifiLan::AcceptedConnectionCallback> accepted_connection_callback)
|
||||
: accepted_connection_callback_(accepted_connection_callback) {}
|
||||
~WifiLanAcceptedConnectionCallback() override = default;
|
||||
|
||||
void OnConnectionAccepted(Ptr<WifiLanSocket> wifi_lan_socket,
|
||||
absl::string_view service_id) override {
|
||||
accepted_connection_callback_->OnConnectionAccepted(wifi_lan_socket,
|
||||
service_id);
|
||||
}
|
||||
|
||||
private:
|
||||
ScopedPtr<Ptr<WifiLan::AcceptedConnectionCallback>>
|
||||
accepted_connection_callback_;
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
struct DiscoveringInfo {
|
||||
DiscoveringInfo() = default;
|
||||
explicit DiscoveringInfo(absl::string_view service_id)
|
||||
: service_id(service_id) {}
|
||||
~DiscoveringInfo() = default;
|
||||
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);
|
||||
}
|
||||
|
||||
string service_id;
|
||||
};
|
||||
|
||||
struct AdvertisingInfo {
|
||||
AdvertisingInfo() = default;
|
||||
explicit AdvertisingInfo(absl::string_view service_id)
|
||||
: service_id(service_id) {}
|
||||
~AdvertisingInfo() = default;
|
||||
|
||||
string service_id;
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
struct AcceptingConnectionsInfo {
|
||||
AcceptingConnectionsInfo() = default;
|
||||
explicit AcceptingConnectionsInfo(absl::string_view service_id)
|
||||
: service_id(service_id) {}
|
||||
~AcceptingConnectionsInfo() = default;
|
||||
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);
|
||||
}
|
||||
|
||||
string service_id;
|
||||
absl::flat_hash_set<std::string> service_ids;
|
||||
};
|
||||
|
||||
// ------------ GENERAL ------------
|
||||
// Same as IsAvailable(), but must be called with mutex_ held.
|
||||
bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
ScopedPtr<Ptr<Lock>> lock_;
|
||||
// Same as IsAdvertising(), but must be called with mutex_ held.
|
||||
bool IsAdvertisingLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// ---------- CORE WIFILAN------------
|
||||
// Same as IsDiscovering(), but must be called with mutex_ held.
|
||||
bool IsDiscoveringLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// The underlying, per-platform implementation.
|
||||
ScopedPtr<Ptr<WifiLanMedium>> wifi_lan_medium_;
|
||||
// Same as IsAcceptingConnections(), but must be called with mutex_ held.
|
||||
bool IsAcceptingConnectionsLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// ------------ DISCOVERY ------------
|
||||
|
||||
// discovering_info_ is not scoped because it's nullable.
|
||||
DiscoveringInfo discovering_info_;
|
||||
|
||||
// ------------ ADVERTISING ------------
|
||||
|
||||
// A bundle of state required to start/stop WifiLan service publishing.
|
||||
AdvertisingInfo advertising_info_;
|
||||
|
||||
// A bundle of state required to start/stop accepting WifiLan service
|
||||
/// connections.
|
||||
AcceptingConnectionsInfo accepting_connections_info_;
|
||||
mutable Mutex mutex_;
|
||||
WifiLanMedium medium_ ABSL_GUARDED_BY(mutex_);
|
||||
AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_);
|
||||
DiscoveringInfo discovering_info_ ABSL_GUARDED_BY(mutex_);
|
||||
AcceptingConnectionsInfo accepting_connections_info_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#include "core/internal/mediums/wifi_lan.cc"
|
||||
|
||||
#endif // CORE_INTERNAL_MEDIUMS_WIFI_LAN_H_
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#include "core/internal/mediums/wifi_lan.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform/base/medium_environment.h"
|
||||
#include "platform/public/count_down_latch.h"
|
||||
#include "platform/public/logging.h"
|
||||
#include "platform/public/wifi_lan.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000);
|
||||
constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"};
|
||||
constexpr absl::string_view kServiceInfoName{
|
||||
"Simulated WifiLan service encrypted string #1"};
|
||||
constexpr absl::string_view kEndpointName{"Simulated endpoint name"};
|
||||
|
||||
class WifiLanTest : public ::testing::Test {
|
||||
protected:
|
||||
using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback;
|
||||
|
||||
WifiLanTest() { env_.Stop(); }
|
||||
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
};
|
||||
|
||||
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;
|
||||
WifiLan wifi_lan_b;
|
||||
std::string service_id(kServiceID);
|
||||
std::string service_info_name{kServiceInfoName};
|
||||
std::string endpoint_info_name{kEndpointName};
|
||||
CountDownLatch found_latch(1);
|
||||
|
||||
wifi_lan_b.StartDiscovery(
|
||||
service_id, DiscoveredServiceCallback{
|
||||
.service_discovered_cb =
|
||||
[&found_latch](WifiLanService& service,
|
||||
absl::string_view service_id) {
|
||||
found_latch.CountDown();
|
||||
},
|
||||
});
|
||||
|
||||
EXPECT_TRUE(wifi_lan_a.StartAdvertising(service_id, service_info_name,
|
||||
endpoint_info_name));
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(wifi_lan_a.StopAdvertising(service_id));
|
||||
EXPECT_TRUE(wifi_lan_b.StopDiscovery(service_id));
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(WifiLanTest, CanStartDiscovery) {
|
||||
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 accept_latch(1);
|
||||
CountDownLatch lost_latch(1);
|
||||
|
||||
wifi_lan_b.StartAdvertising(service_id, service_info_name,
|
||||
endpoint_info_name);
|
||||
|
||||
EXPECT_TRUE(wifi_lan_a.StartDiscovery(
|
||||
service_id, {
|
||||
.service_discovered_cb =
|
||||
[&accept_latch](WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
accept_latch.CountDown();
|
||||
},
|
||||
.service_lost_cb =
|
||||
[&lost_latch](WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
lost_latch.CountDown();
|
||||
},
|
||||
}));
|
||||
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
|
||||
wifi_lan_b.StopAdvertising(service_id);
|
||||
EXPECT_TRUE(lost_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(wifi_lan_a.StopDiscovery(service_id));
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(WifiLanTest, CanStartAcceptingConnectionsAndConnect) {
|
||||
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 found_latch(1);
|
||||
CountDownLatch accept_latch(1);
|
||||
|
||||
wifi_lan_a.StartAdvertising(service_id, service_info_name,
|
||||
endpoint_info_name);
|
||||
wifi_lan_a.StartAcceptingConnections(
|
||||
service_id,
|
||||
{
|
||||
.accepted_cb = [&accept_latch](
|
||||
WifiLanSocket socket,
|
||||
absl::string_view) { accept_latch.CountDown(); },
|
||||
});
|
||||
WifiLanService discovered_service;
|
||||
wifi_lan_b.StartDiscovery(
|
||||
service_id,
|
||||
{
|
||||
.service_discovered_cb =
|
||||
[&found_latch, &discovered_service](
|
||||
WifiLanService& service, absl::string_view service_id) {
|
||||
discovered_service = service;
|
||||
NEARBY_LOG(INFO, "Discovered service=%p [impl=%p]", &service,
|
||||
&service.GetImpl());
|
||||
found_latch.CountDown();
|
||||
},
|
||||
});
|
||||
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
ASSERT_TRUE(discovered_service.IsValid());
|
||||
|
||||
WifiLanSocket socket =
|
||||
wifi_lan_b.Connect(discovered_service, service_id);
|
||||
|
||||
EXPECT_TRUE(accept_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(socket.IsValid());
|
||||
wifi_lan_b.StopDiscovery(service_id);
|
||||
wifi_lan_a.StopAcceptingConnections(service_id);
|
||||
wifi_lan_a.StopAdvertising(service_id);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
Reference in New Issue
Block a user