mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Roll forward to cl/314747126
Signed-off-by: Alexey Polyudov <apolyudov@google.com> Change-Id: Ie19e006429b138b3768e97dae971a43fdc5ef8bf
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cc_library(
|
||||
name = "mediums",
|
||||
srcs = [
|
||||
"advertisement_read_result.cc",
|
||||
"ble_advertisement.cc",
|
||||
"ble_advertisement_header.cc",
|
||||
"ble_packet.cc",
|
||||
"bloom_filter.cc",
|
||||
"bluetooth_classic.cc",
|
||||
"bluetooth_radio.cc",
|
||||
"mediums.cc",
|
||||
"uuid.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"advertisement_read_result.h",
|
||||
"ble_advertisement.h",
|
||||
"ble_advertisement_header.h",
|
||||
"ble_packet.h",
|
||||
"ble_peripheral.h",
|
||||
"bloom_filter.h",
|
||||
"bluetooth_classic.h",
|
||||
"bluetooth_radio.h",
|
||||
"lost_entity_tracker.h",
|
||||
"mediums.h",
|
||||
"uuid.h",
|
||||
],
|
||||
visibility = [
|
||||
"//core_v2/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//core_v2:core_types",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:logging",
|
||||
"//platform_v2/public:types",
|
||||
"//absl/container:flat_hash_map",
|
||||
"//absl/container:flat_hash_set",
|
||||
"//absl/numeric:int128",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
"//smhasher:libmurmur3",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "utils",
|
||||
srcs = ["utils.cc"],
|
||||
hdrs = ["utils.h"],
|
||||
visibility = [
|
||||
"//core_v2/internal/mediums/webrtc:__pkg__",
|
||||
],
|
||||
deps = [
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:types",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "core_v2_internal_mediums_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"advertisement_read_result_test.cc",
|
||||
"ble_advertisement_header_test.cc",
|
||||
"ble_advertisement_test.cc",
|
||||
"ble_packet_test.cc",
|
||||
"ble_peripheral_test.cc",
|
||||
"bloom_filter_test.cc",
|
||||
"bluetooth_classic_test.cc",
|
||||
"bluetooth_radio_test.cc",
|
||||
"lost_entity_tracker_test.cc",
|
||||
"uuid_test.cc",
|
||||
],
|
||||
shard_count = 16,
|
||||
deps = [
|
||||
":mediums",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/base:test_util",
|
||||
"//platform_v2/impl/g3", # build_cleaner: keep
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:logging",
|
||||
"//platform_v2/public:types",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//absl/time",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,139 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/advertisement_read_result.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#include "platform_v2/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,104 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "platform_v2/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_V2_INTERNAL_MEDIUMS_ADVERTISEMENT_READ_RESULT_H_
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/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,215 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/ble_advertisement.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "platform_v2/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BleAdvertisement::BleAdvertisement(Version version,
|
||||
SocketVersion socket_version,
|
||||
const ByteArray &service_id_hash,
|
||||
const ByteArray &data) {
|
||||
// Check that the given input is valid.
|
||||
if (!IsSupportedVersion(version) ||
|
||||
!IsSupportedSocketVersion(socket_version) ||
|
||||
service_id_hash.size() != kServiceIdHashLength ||
|
||||
data.size() > kMaxDataSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
version_ = version;
|
||||
socket_version_ = socket_version;
|
||||
service_id_hash_ = service_id_hash;
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
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() < kMinAdvertisementLength) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: expecting min %d raw "
|
||||
"bytes, got %" PRIu64,
|
||||
kMinAdvertisementLength, ble_advertisement_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, time to read the bytes!
|
||||
const auto *read_ptr = ble_advertisement_bytes.data();
|
||||
|
||||
// 1. Version.
|
||||
version_ = static_cast<Version>((*read_ptr & kVersionBitmask) >> 5);
|
||||
if (!IsSupportedVersion(version_)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: unsupported Version %u",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Socket Version.
|
||||
socket_version_ =
|
||||
static_cast<SocketVersion>((*read_ptr & kSocketVersionBitmask) >> 2);
|
||||
if (!IsSupportedSocketVersion(socket_version_)) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u",
|
||||
socket_version_);
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
read_ptr += kVersionLength;
|
||||
|
||||
// 3. Service ID hash.
|
||||
service_id_hash_ = ByteArray(read_ptr, kServiceIdHashLength);
|
||||
read_ptr += kServiceIdHashLength;
|
||||
|
||||
// 4.1. Data size.
|
||||
size_t expected_data_size = DeserializeDataSize(read_ptr);
|
||||
if (expected_data_size < 0) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BleAdvertisement: negative data size %" PRIu64,
|
||||
expected_data_size);
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
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 %" PRIu64 " bytes",
|
||||
expected_data_size, actual_data_size);
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// 4.2. Data.
|
||||
data_ = ByteArray(read_ptr, expected_data_size);
|
||||
read_ptr += expected_data_size;
|
||||
}
|
||||
|
||||
BleAdvertisement::operator ByteArray() const {
|
||||
if (!IsValid()) {
|
||||
return ByteArray{};
|
||||
}
|
||||
|
||||
std::string out;
|
||||
|
||||
// The first 3 bits are the Version.
|
||||
char version_and_socket_version_byte =
|
||||
(static_cast<char>(version_) << 5) & kVersionBitmask;
|
||||
// The next 3 bits are the Socket version. 2 bits left are reserved.
|
||||
version_and_socket_version_byte |=
|
||||
(static_cast<char>(socket_version_) << 2) & kSocketVersionBitmask;
|
||||
// Serialize Data size bytes(4).
|
||||
ByteArray data_size_bytes{kDataSizeLength};
|
||||
auto *data_size_bytes_write_ptr = data_size_bytes.data();
|
||||
SerializeDataSize(data_size_bytes_write_ptr, data_.size());
|
||||
|
||||
out.reserve(1 + service_id_hash_.size() + 1 + data_.size());
|
||||
out.append(1, version_and_socket_version_byte);
|
||||
out.append(std::string(service_id_hash_));
|
||||
out.append(std::string(data_size_bytes));
|
||||
out.append(std::string(data_));
|
||||
|
||||
return ByteArray{std::move(out)};
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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(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);
|
||||
|
||||
// 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];
|
||||
}
|
||||
}
|
||||
|
||||
size_t BleAdvertisement::DeserializeDataSize(
|
||||
const char *data_size_bytes_read_ptr) const {
|
||||
// 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(
|
||||
const ByteArray &ble_advertisement_bytes) const {
|
||||
return ble_advertisement_bytes.size() - kMinAdvertisementLength;
|
||||
}
|
||||
|
||||
size_t BleAdvertisement::ComputeAdvertisementLength(
|
||||
const ByteArray &data) const {
|
||||
// The advertisement length is the minimum length + the length of the data.
|
||||
return kMinAdvertisementLength + data.size();
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "platform_v2/base/byte_array.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.
|
||||
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;
|
||||
|
||||
BleAdvertisement() = default;
|
||||
BleAdvertisement(Version version, SocketVersion socket_version,
|
||||
const ByteArray &service_id_hash, const ByteArray &data);
|
||||
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_; }
|
||||
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_); }
|
||||
|
||||
private:
|
||||
bool IsSupportedVersion(Version version) const;
|
||||
bool IsSupportedSocketVersion(SocketVersion socket_version) const;
|
||||
void SerializeDataSize(char *data_size_bytes_write_ptr,
|
||||
size_t data_size) const;
|
||||
size_t DeserializeDataSize(const char *data_size_bytes_read_ptr) const;
|
||||
size_t ComputeDataSize(const ByteArray &ble_advertisement_bytes) const;
|
||||
size_t ComputeAdvertisementLength(const ByteArray &data) const;
|
||||
|
||||
static constexpr int kVersionLength = 1;
|
||||
// Length of one int. Be sure to re-evaluate how we compute data size in this
|
||||
// class if this constant ever changes!
|
||||
static constexpr int kDataSizeLength = 4;
|
||||
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 kMaxGattCharacteristicValueSize = 512;
|
||||
static constexpr int kMaxDataSize =
|
||||
kMaxGattCharacteristicValueSize - kMinAdvertisementLength;
|
||||
static constexpr int kVersionBitmask = 0x0E0;
|
||||
static constexpr int kSocketVersionBitmask = 0x01C;
|
||||
|
||||
Version version_{Version::kUndefined};
|
||||
SocketVersion socket_version_{SocketVersion::kUndefined};
|
||||
ByteArray service_id_hash_;
|
||||
ByteArray data_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_H_
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/ble_advertisement_header.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "platform_v2/base/base64_utils.h"
|
||||
#include "platform_v2/public/logging.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) {
|
||||
// TODO(edwinwu): Checks if num_slots needs to be >= 0
|
||||
if (version != Version::kV2 ||
|
||||
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;
|
||||
}
|
||||
|
||||
// Start reading the bytes.
|
||||
auto *ble_advertisement_header_read_ptr =
|
||||
ble_advertisement_header_bytes.data();
|
||||
|
||||
// The first 3 bits are supposed to be the version.
|
||||
version_ = static_cast<Version>(
|
||||
(*ble_advertisement_header_read_ptr & kVersionBitmask) >> 5);
|
||||
if (version_ != Version::kV2) {
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
"Cannot deserialize BleAdvertisementHeader: unsupported Version %d",
|
||||
version_);
|
||||
return;
|
||||
}
|
||||
// The last 5 bits of the first byte represent the number of slots.
|
||||
num_slots_ = static_cast<std::uint32_t>(*ble_advertisement_header_read_ptr &
|
||||
kNumSlotsBitmask);
|
||||
ble_advertisement_header_read_ptr++;
|
||||
|
||||
// Service ID bloom filter.
|
||||
service_id_bloom_filter_ =
|
||||
ByteArray(ble_advertisement_header_read_ptr, kServiceIdBloomFilterLength);
|
||||
ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength;
|
||||
|
||||
// Advertisement hash.
|
||||
advertisement_hash_ =
|
||||
ByteArray(ble_advertisement_header_read_ptr, kAdvertisementHashLength);
|
||||
ble_advertisement_header_read_ptr += kAdvertisementHashLength;
|
||||
}
|
||||
|
||||
BleAdvertisementHeader::operator std::string() const {
|
||||
if (!IsValid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string out;
|
||||
|
||||
// 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;
|
||||
out.reserve(1 + service_id_bloom_filter_.size() + advertisement_hash_.size());
|
||||
out.append(1, version_and_num_slots_byte);
|
||||
out.append(std::string(service_id_bloom_filter_));
|
||||
out.append(std::string(advertisement_hash_));
|
||||
|
||||
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,97 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/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_V2_INTERNAL_MEDIUMS_BLE_ADVERTISEMENT_HEADER_H_
|
||||
@@ -0,0 +1,189 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/ble_advertisement_header.h"
|
||||
|
||||
#include "platform_v2/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 char kServiceIDBloomFilter[] =
|
||||
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a";
|
||||
constexpr char kAdvertisementHash[] = "\x0a\x0b\x0c\x0d";
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionWorks) {
|
||||
ByteArray service_id_bloom_filter{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{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{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{kAdvertisementHash};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
bad_version, kNumSlots, 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{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{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{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{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{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{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{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{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{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{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,237 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/ble_advertisement.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2;
|
||||
const BleAdvertisement::SocketVersion kSocketVersion =
|
||||
BleAdvertisement::SocketVersion::kV2;
|
||||
const char kServiceIDHashBytes[] = "\x0a\x0b\x0c";
|
||||
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, ConstructionWorksV1) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
|
||||
BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1,
|
||||
BleAdvertisement::SocketVersion::kV1,
|
||||
service_id_hash, data};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
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());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) {
|
||||
BleAdvertisement::Version bad_version =
|
||||
static_cast<BleAdvertisement::Version>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
|
||||
BleAdvertisement ble_advertisement{bad_version, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) {
|
||||
BleAdvertisement::SocketVersion bad_socket_version =
|
||||
static_cast<BleAdvertisement::SocketVersion>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, bad_socket_version,
|
||||
service_id_hash, data};
|
||||
|
||||
EXPECT_FALSE(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{kData};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion,
|
||||
bad_service_id_hash, data};
|
||||
|
||||
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{kData};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion,
|
||||
bad_service_id_hash, data};
|
||||
|
||||
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{kServiceIDHashBytes};
|
||||
ByteArray bad_data{long_data, 512};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash,
|
||||
bad_data};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
ByteArray ble_advertisement_bytes{org_ble_advertisement};
|
||||
BleAdvertisement ble_advertisement{ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
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());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) {
|
||||
char empty_data[0]{};
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{empty_data};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
ByteArray ble_advertisement_bytes{org_ble_advertisement};
|
||||
BleAdvertisement ble_advertisement{ble_advertisement_bytes};
|
||||
|
||||
EXPECT_TRUE(ble_advertisement.IsValid());
|
||||
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());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
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_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());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) {
|
||||
BleAdvertisement ble_advertisement{ByteArray{}};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
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,
|
||||
ConstructionFromSerializedBytesWithInvalidDataLengthFails) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
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());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/ble_packet.h"
|
||||
|
||||
#include "platform_v2/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BlePacket::BlePacket(const ByteArray& service_id_hash, const ByteArray& data) {
|
||||
if (service_id_hash.size() != kServiceIdHashLength ||
|
||||
data.size() > kMaxDataSize) {
|
||||
return;
|
||||
}
|
||||
service_id_hash_ = service_id_hash;
|
||||
data_ = data;
|
||||
}
|
||||
|
||||
BlePacket::BlePacket(const ByteArray& ble_packet_bytes) {
|
||||
if (ble_packet_bytes.Empty()) {
|
||||
NEARBY_LOG(ERROR, "Cannot deserialize BlePacket: null bytes passed in");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ble_packet_bytes.size() < kServiceIdHashLength) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BlePacket: expecting min %u raw bytes, got %zu",
|
||||
kServiceIdHashLength, ble_packet_bytes.size());
|
||||
return;
|
||||
}
|
||||
|
||||
const char *ble_packet_bytes_read_ptr = ble_packet_bytes.data();
|
||||
service_id_hash_ =
|
||||
ByteArray(ble_packet_bytes_read_ptr, kServiceIdHashLength);
|
||||
ble_packet_bytes_read_ptr += kServiceIdHashLength;
|
||||
|
||||
data_ = ByteArray(ble_packet_bytes_read_ptr,
|
||||
ble_packet_bytes.size() - kServiceIdHashLength);
|
||||
}
|
||||
|
||||
BlePacket::operator ByteArray() const {
|
||||
if (!IsValid()) {
|
||||
return ByteArray();
|
||||
}
|
||||
|
||||
std::string out;
|
||||
|
||||
out.reserve(service_id_hash_.size() + data_.size());
|
||||
out.append(std::string(service_id_hash_));
|
||||
out.append(std::string(data_));
|
||||
|
||||
return ByteArray(std::move(out));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "platform_v2/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_V2_INTERNAL_MEDIUMS_BLE_PACKET_H_
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/ble_packet.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
constexpr char kServiceIDHash[] = "\x0a\x0b\x0c";
|
||||
constexpr char kData[] = "\x01\x02\x03\x04\x05";
|
||||
|
||||
TEST(BlePacketTest, ConstructionWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHash};
|
||||
ByteArray data{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{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{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{kData};
|
||||
|
||||
BlePacket ble_packet{service_id_hash, data};
|
||||
|
||||
EXPECT_FALSE(ble_packet.IsValid());
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHash};
|
||||
ByteArray data{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{kServiceIDHash};
|
||||
ByteArray data{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,49 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
|
||||
|
||||
#include "platform_v2/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_V2_INTERNAL_MEDIUMS_BLE_PERIPHERAL_H_
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/ble_peripheral.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
const char kId[] = "AB12";
|
||||
|
||||
TEST(BlePeripheralTest, ConstructionWorks) {
|
||||
ByteArray id{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,105 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/bloom_filter.h"
|
||||
|
||||
#include "absl/numeric/int128.h"
|
||||
#include "absl/strings/numbers.h"
|
||||
#include "smhasher/MurmurHash3.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
BloomFilterBase::BloomFilterBase(const ByteArray& bytes, BitSet* bit_set)
|
||||
: bits_(bit_set) {
|
||||
const char* bytes_read_ptr = bytes.data();
|
||||
for (size_t byte_index = 0; byte_index < bytes.size(); byte_index++) {
|
||||
for (size_t bit_index = 0; bit_index < 8; bit_index++) {
|
||||
bits_->Set((byte_index * 8) + bit_index,
|
||||
(*bytes_read_ptr >> bit_index) & 0x01);
|
||||
}
|
||||
bytes_read_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
BloomFilterBase::operator ByteArray() const {
|
||||
// Gets a binary string representation of the bitset where the leftmost
|
||||
// character corresponds to bitset position (total size) - 1.
|
||||
//
|
||||
// If the bitset's internal representation is:
|
||||
// [position 0] 0 0 1 1 0 0 0 1 0 1 0 1 [position 11]
|
||||
// The string representation will be outputted like this:
|
||||
// "1 0 1 0 1 0 0 0 1 1 0 0"
|
||||
std::string bitset_binary_string = bits_->ToString();
|
||||
|
||||
ByteArray result_bytes(GetMinBytesForBits());
|
||||
char* result_bytes_write_ptr = result_bytes.data();
|
||||
// We go through the string backwards because the rightmost character
|
||||
// corresponds to position 0 in the bitset.
|
||||
for (size_t i = bits_->Size(); i > 0; i -= 8) {
|
||||
std::string byte_binary_string = bitset_binary_string.substr(i - 8, 8);
|
||||
std::uint32_t byte_value;
|
||||
absl::numbers_internal::safe_strtou32_base(byte_binary_string, &byte_value,
|
||||
/* base= */ 2);
|
||||
*result_bytes_write_ptr = static_cast<char>(byte_value & 0x000000FF);
|
||||
result_bytes_write_ptr++;
|
||||
}
|
||||
return result_bytes;
|
||||
}
|
||||
|
||||
void BloomFilterBase::Add(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes = GetHashes(s);
|
||||
for (int32_t hash : hashes) {
|
||||
size_t position = static_cast<size_t>(hash) % bits_->Size();
|
||||
bits_->Set(position, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool BloomFilterBase::PossiblyContains(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes = GetHashes(s);
|
||||
for (int32_t hash : hashes) {
|
||||
size_t position = static_cast<size_t>(hash) % bits_->Size();
|
||||
if (!bits_->Test(position)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::int32_t> BloomFilterBase::GetHashes(const std::string& s) {
|
||||
std::vector<std::int32_t> hashes(kHasherNumberOfRepetitions, 0);
|
||||
|
||||
absl::uint128 hash128;
|
||||
MurmurHash3_x64_128(s.data(), s.size(), 0, &hash128);
|
||||
std::uint64_t hash64 =
|
||||
absl::Uint128Low64(hash128); // the lower 64 bits of the 128-bit hash
|
||||
std::int32_t hash1 = static_cast<std::int32_t>(
|
||||
hash64 & 0x00000000FFFFFFFF); // the lower 32 bits of the 64-bit hash
|
||||
std::int32_t hash2 = static_cast<std::int32_t>(
|
||||
(hash64 >> 32) & 0x0FFFFFFFF); // the upper 32 bits of the 64-bit hash
|
||||
for (size_t i = 1; i <= kHasherNumberOfRepetitions; i++) {
|
||||
std::int32_t combinedHash = static_cast<std::int32_t>(hash1 + (i * hash2));
|
||||
// Flip all the bits if it's negative (guaranteed positive number)
|
||||
if (combinedHash < 0) combinedHash = ~combinedHash;
|
||||
hashes[i - 1] = combinedHash;
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_BLOOM_FILTER_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_BLOOM_FILTER_H_
|
||||
|
||||
#include <bitset>
|
||||
#include <vector>
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
/**
|
||||
* A bloom filter that gives access to the underlying BitSet. The implementation
|
||||
* is copied from our Java version of Bloom filter, which in turn copies from
|
||||
* Guava's BloomFilter.
|
||||
*
|
||||
* BloomFilter is templatized on the size of the byte array and not the size of
|
||||
* the bit set to ensure the bit set's length is a multiple of 8 (and can
|
||||
* neatly be returned as a ByteArray).
|
||||
*/
|
||||
class BloomFilterBase {
|
||||
public:
|
||||
explicit operator ByteArray() const;
|
||||
|
||||
void Add(const std::string& s);
|
||||
bool PossiblyContains(const std::string& s);
|
||||
|
||||
protected:
|
||||
class BitSet {
|
||||
public:
|
||||
virtual ~BitSet() = default;
|
||||
virtual std::string ToString() const = 0;
|
||||
virtual void Set(size_t pos, bool value) = 0;
|
||||
virtual bool Test(size_t pos) const = 0;
|
||||
virtual size_t Size() const = 0;
|
||||
};
|
||||
|
||||
BloomFilterBase(const ByteArray& bytes, BitSet* bit_set);
|
||||
virtual ~BloomFilterBase() = default;
|
||||
|
||||
constexpr static int kHasherNumberOfRepetitions = 5;
|
||||
std::vector<std::int32_t> GetHashes(const std::string& s);
|
||||
|
||||
private:
|
||||
int GetMinBytesForBits() const { return (bits_->Size() + 7) >> 3; }
|
||||
|
||||
BitSet* bits_;
|
||||
};
|
||||
|
||||
template <size_t CapacityInBytes>
|
||||
class BloomFilter final : public BloomFilterBase {
|
||||
public:
|
||||
BloomFilter() : BloomFilterBase(ByteArray{}, &bits_) {}
|
||||
explicit BloomFilter(const ByteArray& bytes)
|
||||
: BloomFilterBase(bytes, &bits_) {}
|
||||
BloomFilter(const BloomFilter&) = default;
|
||||
BloomFilter& operator=(const BloomFilter&) = default;
|
||||
BloomFilter(BloomFilter&& other) : BloomFilterBase(ByteArray{}, &bits_) {
|
||||
*this = std::move(other);
|
||||
}
|
||||
BloomFilter& operator=(BloomFilter&& other) {
|
||||
std::swap((*this).bits_, other.bits_);
|
||||
return *this;
|
||||
}
|
||||
~BloomFilter() override = default;
|
||||
|
||||
private:
|
||||
class BitSetImpl final : public BitSet {
|
||||
public:
|
||||
std::string ToString() const override { return bits_.to_string(); }
|
||||
void Set(size_t pos, bool value) override { bits_.set(pos, value); }
|
||||
bool Test(size_t pos) const override { return bits_.test(pos); }
|
||||
size_t Size() const override { return bits_.size(); }
|
||||
|
||||
private:
|
||||
std::bitset<CapacityInBytes * 8> bits_;
|
||||
} bits_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_BLOOM_FILTER_H_
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/bloom_filter.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
const size_t kByteArrayLength = 100;
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
std::string empty_string(kByteArrayLength, '\0');
|
||||
|
||||
EXPECT_EQ(empty_string, std::string(bloom_filter_bytes));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterNeverContains) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddOnlyGivenArg) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddMultipleArgs) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_2"));
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_3"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, AddMultipleArgsReturnsNonemptyArray) {
|
||||
BloomFilter<10> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
bloom_filter.Add("ELEMENT_3");
|
||||
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
std::string empty_string(kByteArrayLength, '\0');
|
||||
|
||||
EXPECT_NE(std::string(bloom_filter_bytes), empty_string);
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, CopyConstructorAndAssignmentSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
EXPECT_FALSE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_copy_1{bloom_filter};
|
||||
BloomFilter<kByteArrayLength> bloom_filter_copy_2 = bloom_filter;
|
||||
|
||||
EXPECT_TRUE(bloom_filter.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter_copy_1.PossiblyContains("ELEMENT_1"));
|
||||
EXPECT_TRUE(bloom_filter_copy_2.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, MoveConstructorSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_move{std::move(bloom_filter)};
|
||||
|
||||
EXPECT_TRUE(bloom_filter_move.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, MoveAssignmentSuccess) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
BloomFilter<kByteArrayLength> bloom_filter_move = std::move(bloom_filter);
|
||||
|
||||
EXPECT_TRUE(bloom_filter_move.PossiblyContains("ELEMENT_1"));
|
||||
}
|
||||
|
||||
/**
|
||||
* This test was added because of a bug where the BloomFilter doesn't utilize
|
||||
* all bits given. Functionally, the filter still works, but we just have a much
|
||||
* higher false positive rate. The bug was caused by confusing bit length and
|
||||
* byte length, which made our BloomFilter only set bits on the first byteLength
|
||||
* (bitLength / 8) bits rather than the whole bitLength bits.
|
||||
*
|
||||
* <p>Here, we're verifying that the bits set are somewhat scattered. So instead
|
||||
* of something like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting
|
||||
* something like [ 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, ..., 1, 0].
|
||||
*/
|
||||
TEST(BloomFilterTest, RandomnessNoEndBias) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
// Add one element to our BloomFilter.
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
|
||||
std::int32_t non_zero_count = 0;
|
||||
std::int32_t longest_zero_streak = 0;
|
||||
std::int32_t current_zero_streak = 0;
|
||||
|
||||
// Record the amount of non-zero bytes and the longest streak of zero bytes in
|
||||
// the resulting BloomFilter. This is an approximation of reasonable
|
||||
// distribution since we're recording by bytes instead of bits.
|
||||
ByteArray bloom_filter_bytes(bloom_filter);
|
||||
const char* bloom_filter_bytes_read_ptr = bloom_filter_bytes.data();
|
||||
for (int i = 0; i < bloom_filter_bytes.size(); i++) {
|
||||
if (*bloom_filter_bytes_read_ptr == '\0') {
|
||||
current_zero_streak++;
|
||||
} else {
|
||||
// Increment the number of non-zero bytes we've seen, update the longest
|
||||
// zero streak, and then reset the current zero streak.
|
||||
non_zero_count++;
|
||||
longest_zero_streak = std::max(longest_zero_streak, current_zero_streak);
|
||||
current_zero_streak = 0;
|
||||
}
|
||||
bloom_filter_bytes_read_ptr++;
|
||||
}
|
||||
// Update the longest zero streak again for the tail case.
|
||||
longest_zero_streak = std::min(longest_zero_streak, current_zero_streak);
|
||||
|
||||
// Since randomness is hard to measure within one unit test, we instead do a
|
||||
// sanity check. All non-zero bytes should not be packed into one end of the
|
||||
// array.
|
||||
//
|
||||
// In this case, the size of one end is approximated to be:
|
||||
// kByteArrayLength / nonZeroCount.
|
||||
// Therefore, the longest zero streak should be less than:
|
||||
// kByteArrayLength - one end of the array.
|
||||
std::int32_t longest_acceptable_zero_streak =
|
||||
kByteArrayLength - (kByteArrayLength / non_zero_count);
|
||||
|
||||
EXPECT_TRUE(longest_zero_streak <= longest_acceptable_zero_streak);
|
||||
}
|
||||
|
||||
TEST(BloomFilterTest, RandomnessFalsePositiveRate) {
|
||||
BloomFilter<10> bloom_filter;
|
||||
|
||||
// Add 5 distinct elements to the BloomFilter.
|
||||
bloom_filter.Add("ELEMENT_1");
|
||||
bloom_filter.Add("ELEMENT_2");
|
||||
bloom_filter.Add("ELEMENT_3");
|
||||
bloom_filter.Add("ELEMENT_4");
|
||||
bloom_filter.Add("ELEMENT_5");
|
||||
|
||||
std::int32_t false_positives = 0;
|
||||
// Now test 100 other elements and record the number of false positives.
|
||||
for (int i = 5; i < 105; i++) {
|
||||
false_positives +=
|
||||
bloom_filter.PossiblyContains("ELEMENT_" + std::to_string(i)) ? 1 : 0;
|
||||
}
|
||||
|
||||
// We expect the false positive rate to be 3% with 5 elements in a 10 byte
|
||||
// filter. Thus, we give a little leeway and verify that the false positive
|
||||
// rate is no more than 5%.
|
||||
EXPECT_LE(false_positives, 5);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,391 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/bluetooth_classic.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "core_v2/internal/mediums/uuid.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
BluetoothClassic::BluetoothClassic(BluetoothRadio& radio) : radio_(radio) {}
|
||||
|
||||
BluetoothClassic::~BluetoothClassic() {
|
||||
// Destructor is not taking locks, but methods it is calling are.
|
||||
StopDiscovery();
|
||||
while (!server_sockets_.empty()) {
|
||||
StopAcceptingConnections(server_sockets_.begin()->first);
|
||||
}
|
||||
TurnOffDiscoverability();
|
||||
|
||||
// All the AcceptLoopRunnable objects in here should already have gotten an
|
||||
// opportunity to shut themselves down cleanly in the calls to
|
||||
// StopAcceptingConnections() above.
|
||||
accept_loops_runner_.Shutdown();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsAvailable() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAvailableLocked();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsAvailableLocked() const {
|
||||
return medium_.IsValid() && adapter_.IsValid();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::TurnOnDiscoverability(const std::string& device_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (device_name.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to turn on BT discoverability. Empty device name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is off.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO, "Can't turn on BT discoverability. BT is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsDiscoverable()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to turn on BT discoverability; new name='%s'; "
|
||||
"current name='%s'",
|
||||
device_name.c_str(), adapter_.GetName().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ModifyDeviceName(device_name)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Failed to turn on BT discoverability; "
|
||||
"failed to set name to %s",
|
||||
device_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ModifyScanMode(ScanMode::kConnectableDiscoverable)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Failed to turn on BT discoverability; "
|
||||
"failed to set scan_mode to %d",
|
||||
ScanMode::kConnectableDiscoverable);
|
||||
|
||||
// Don't forget to perform this rollback of the partial state changes we've
|
||||
// made til now.
|
||||
RestoreDeviceName();
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Turned on BT discoverability with device_name=%s",
|
||||
device_name.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::TurnOffDiscoverability() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsDiscoverable()) {
|
||||
NEARBY_LOG(INFO, "Can't turn off BT discoverability; it is already off");
|
||||
return false;
|
||||
}
|
||||
|
||||
RestoreScanMode();
|
||||
RestoreDeviceName();
|
||||
|
||||
NEARBY_LOG(INFO, "Turned Bluetooth discoverability off");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsDiscoverable() const {
|
||||
return (!original_device_name_.empty() &&
|
||||
(adapter_.GetScanMode() == ScanMode::kConnectableDiscoverable));
|
||||
}
|
||||
|
||||
bool BluetoothClassic::ModifyDeviceName(const std::string& device_name) {
|
||||
if (original_device_name_.empty()) {
|
||||
original_device_name_ = adapter_.GetName();
|
||||
}
|
||||
|
||||
return adapter_.SetName(device_name);
|
||||
}
|
||||
|
||||
bool BluetoothClassic::ModifyScanMode(ScanMode scan_mode) {
|
||||
if (original_scan_mode_ == ScanMode::kUnknown) {
|
||||
original_scan_mode_ = adapter_.GetScanMode();
|
||||
}
|
||||
|
||||
if (!adapter_.SetScanMode(scan_mode)) {
|
||||
original_scan_mode_ = ScanMode::kUnknown;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::RestoreScanMode() {
|
||||
if (original_scan_mode_ == ScanMode::kUnknown ||
|
||||
!adapter_.SetScanMode(original_scan_mode_)) {
|
||||
NEARBY_LOG(INFO, "Failed to restore original Bluetooth scan mode to %d",
|
||||
original_scan_mode_);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Regardless of whether or not we could actually restore the Bluetooth scan
|
||||
// mode, reset our relevant state.
|
||||
original_scan_mode_ = ScanMode::kUnknown;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::RestoreDeviceName() {
|
||||
if (original_device_name_.empty() ||
|
||||
!adapter_.SetName(original_device_name_)) {
|
||||
NEARBY_LOG(INFO, "Failed to restore original Bluetooth device name to %s",
|
||||
original_device_name_.c_str());
|
||||
return false;
|
||||
}
|
||||
original_device_name_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::StartDiscovery(DiscoveredDeviceCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't enabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO, "Can't discover BT devices because BT isn't available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsDiscovering()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start discovery of BT devices because another "
|
||||
"discovery is already in-progress.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartDiscovery(callback)) {
|
||||
NEARBY_LOG(INFO, "Failed to start discovery of BT devices.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark the fact that we're currently performing a Bluetooth scan.
|
||||
scan_info_.valid = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::StopDiscovery() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsDiscovering()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't stop discovery of BT devices because it never started.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StopDiscovery()) {
|
||||
NEARBY_LOG(INFO, "Failed to stop discovery of Bluetooth devices.");
|
||||
return false;
|
||||
}
|
||||
|
||||
scan_info_.valid = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsDiscovering() const { return scan_info_.valid; }
|
||||
|
||||
bool BluetoothClassic::StartAcceptingConnections(
|
||||
const std::string& service_name, AcceptedConnectionCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to start accepting BT connections; service name is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't create BT server socket [service=%s]; BT is disabled.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Can't start accepting BT connections [service=%s]; BT not available.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnectionsLocked(service_name)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start accepting BT connections [service=%s]; BT "
|
||||
"server is already in-progress with the same name.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
BluetoothServerSocket socket = medium_.ListenForService(
|
||||
service_name, GenerateUuidFromString(service_name));
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOG(INFO, "Failed to start accepting Bluetooth connections for %s.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark the fact that there's an in-progress Bluetooth server accepting
|
||||
// connections.
|
||||
auto owned_socket =
|
||||
server_sockets_.emplace(service_name, std::move(socket)).first->second;
|
||||
|
||||
// Start the accept loop on a dedicated thread - this stays alive and
|
||||
// listening for new incoming connections until StopAcceptingConnections() is
|
||||
// invoked.
|
||||
accept_loops_runner_.Execute([callback = std::move(callback),
|
||||
server_socket = std::move(owned_socket),
|
||||
service_name]() mutable {
|
||||
while (true) {
|
||||
BluetoothSocket client_socket = server_socket.Accept();
|
||||
if (!client_socket.IsValid()) {
|
||||
server_socket.Close();
|
||||
break;
|
||||
}
|
||||
|
||||
callback.accepted_cb(std::move(client_socket));
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsAcceptingConnections(const std::string& service_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAcceptingConnectionsLocked(service_name);
|
||||
}
|
||||
|
||||
bool BluetoothClassic::IsAcceptingConnectionsLocked(
|
||||
const std::string& service_name) {
|
||||
return server_sockets_.find(service_name) != server_sockets_.end();
|
||||
}
|
||||
|
||||
bool BluetoothClassic::StopAcceptingConnections(
|
||||
const std::string& service_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Unable to stop accepting BT connections because the "
|
||||
"service_name is empty.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& it = server_sockets_.find(service_name);
|
||||
if (it == server_sockets_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't stop accepting BT connections for %s because it was "
|
||||
"never started.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Closing the BluetoothServerSocket will kick off the suicide of the thread
|
||||
// in accept_loops_thread_pool_ that blocks on BluetoothServerSocket.accept().
|
||||
// That may take some time to complete, but there's no particular reason to
|
||||
// wait around for it.
|
||||
auto item = server_sockets_.extract(it);
|
||||
|
||||
// Store a handle to the BluetoothServerSocket, so we can use it after
|
||||
// removing the entry from server_sockets_; making it scoped
|
||||
// is a bonus that takes care of deallocation before we leave this method.
|
||||
BluetoothServerSocket& listening_socket = item.mapped();
|
||||
|
||||
// Regardless of whether or not we fail to close the existing
|
||||
// BluetoothServerSocket, remove it from server_sockets_ so that it
|
||||
// frees up this service for another round.
|
||||
|
||||
// Finally, close the BluetoothServerSocket.
|
||||
if (!listening_socket.Close().Ok()) {
|
||||
NEARBY_LOG(INFO, "Failed to close BT server socket for %s.",
|
||||
service_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BluetoothSocket BluetoothClassic::Connect(BluetoothDevice& bluetooth_device,
|
||||
const std::string& service_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOG(INFO, "BluetoothClassic::Connect: device=%p", &bluetooth_device);
|
||||
// Socket to return. To allow for NRVO to work, it has to be a single object.
|
||||
BluetoothSocket socket;
|
||||
|
||||
if (service_name.empty()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to create client BT socket because service_name is empty.");
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!radio_.IsEnabled()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't create client BT socket [service=%s]: BT isn't enabled.",
|
||||
service_name.c_str());
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Can't create client BT socket [service=%s]; BT isn't available.",
|
||||
service_name.c_str());
|
||||
return socket;
|
||||
}
|
||||
|
||||
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 socket;
|
||||
}
|
||||
|
||||
std::string BluetoothClassic::GenerateUuidFromString(const std::string& data) {
|
||||
return std::string(Uuid(data));
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/mediums/bluetooth_radio.h"
|
||||
#include "core_v2/listeners.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/bluetooth_adapter.h"
|
||||
#include "platform_v2/public/bluetooth_classic.h"
|
||||
#include "platform_v2/public/multi_thread_executor.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class BluetoothClassic {
|
||||
public:
|
||||
using DiscoveredDeviceCallback = BluetoothClassicMedium::DiscoveryCallback;
|
||||
using ScanMode = BluetoothAdapter::ScanMode;
|
||||
|
||||
// Callback that is invoked when a new connection is accepted.
|
||||
struct AcceptedConnectionCallback {
|
||||
std::function<void(BluetoothSocket socket)> accepted_cb =
|
||||
DefaultCallback<BluetoothSocket>();
|
||||
};
|
||||
|
||||
explicit BluetoothClassic(BluetoothRadio& bluetooth_radio);
|
||||
~BluetoothClassic();
|
||||
|
||||
// Returns true, if BT communications are supported by a platform.
|
||||
bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Sets custom device name, and then enables BT discoverable mode.
|
||||
// Returns true, if name and scan mode are successfully set, and false
|
||||
// otherwise.
|
||||
// Called by server.
|
||||
bool TurnOnDiscoverability(const std::string& device_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Disables BT discoverability, and restores scan mode and device name to
|
||||
// what they were before the call to TurnOnDiscoverability().
|
||||
// Returns false if no successful call TurnOnDiscoverability() was previously
|
||||
// made, otherwise returns true.
|
||||
// Called by server.
|
||||
bool TurnOffDiscoverability() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Enables BT discovery mode. Will report any discoverable devices in range
|
||||
// through a callback.
|
||||
// Returns true, if discovery mode was enabled, false otherwise.
|
||||
// Called by client.
|
||||
bool StartDiscovery(DiscoveredDeviceCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Disables BT discovery mode.
|
||||
// Returns true, if discovery mode was previously enabled, false otherwise.
|
||||
// Called by client.
|
||||
bool StopDiscovery() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Starts a worker thread, creates a BT server socket, associates it with a
|
||||
// service name; in a worker thread repeatedly calls ServerSocket::Accept().
|
||||
// Any connected sockets returned from Accept() are passed to a callback.
|
||||
// Returns true, if server socket was successfully created, false otherwise.
|
||||
// Called by server.
|
||||
bool StartAcceptingConnections(const std::string& service_name,
|
||||
AcceptedConnectionCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true, if object is currently running a Accept() loop.
|
||||
bool IsAcceptingConnections(const std::string& service_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Closes server socket corresponding to a service name. This automatically
|
||||
// terminates Accept() loop, if it were running.
|
||||
// Called by server.
|
||||
bool StopAcceptingConnections(const std::string& service_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if this object owns a valid platform implementation.
|
||||
bool IsMediumValid() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
MutexLock lock(&mutex_);
|
||||
return medium_.IsValid();
|
||||
}
|
||||
|
||||
// Returns true if this object has a valid BluetoothAdapter reference.
|
||||
bool IsAdapterValid() const ABSL_LOCKS_EXCLUDED(mutex_) {
|
||||
MutexLock lock(&mutex_);
|
||||
return adapter_.IsValid();
|
||||
}
|
||||
|
||||
// Establishes connection to BT service 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_);
|
||||
|
||||
private:
|
||||
struct ScanInfo {
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static constexpr int kMaxConcurrentAcceptLoops = 5;
|
||||
|
||||
// Constructs UUID object from arbitrary string, using MD5 hash, and then
|
||||
// converts UUID to a readable UUID string and returns it.
|
||||
static std::string GenerateUuidFromString(const std::string& data);
|
||||
|
||||
// Same as IsAvailable(), but must be called with mutex_ held.
|
||||
bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Same as IsAcceptingConnections(), but must be called with mutex_ held.
|
||||
bool IsAcceptingConnectionsLocked(const std::string& service_name)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true, if discoverability is enabled with TurnOnDiscoverability().
|
||||
bool IsDiscoverable() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Assignes a different name to BT adapter.
|
||||
// Returns true if successful. Stores original device name.
|
||||
bool ModifyDeviceName(const std::string& device_name)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Changes current scan mode. This is an implementation of
|
||||
// Turn<On/Off>Discoveradility() method. Stores original scan mode.
|
||||
bool ModifyScanMode(ScanMode scan_mode) ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Restores original device name (the one before the very first call to
|
||||
// ModifyDeviceName()). Returns true if successful.
|
||||
bool RestoreScanMode() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Restores original device scan mode (the one before the very first call to
|
||||
// ModifyScanMode()). Returns true if successful.
|
||||
bool RestoreDeviceName() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if device is currently in discovery mode.
|
||||
bool IsDiscovering() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable Mutex mutex_;
|
||||
BluetoothRadio& radio_ ABSL_GUARDED_BY(mutex_);
|
||||
BluetoothAdapter& adapter_ ABSL_GUARDED_BY(mutex_){
|
||||
radio_.GetBluetoothAdapter()};
|
||||
BluetoothClassicMedium medium_ ABSL_GUARDED_BY(mutex_){adapter_};
|
||||
|
||||
// A bundle of state required to do a Bluetooth Classic scan. When non-null,
|
||||
// we are currently performing a Bluetooth scan.
|
||||
ScanInfo scan_info_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// The original scan mode (that controls visibility to scanners) of the device
|
||||
// before we modified it. Restored when we stop advertising.
|
||||
ScanMode original_scan_mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kUnknown;
|
||||
|
||||
// The original Bluetooth device name, before we modified it. If non-empty, we
|
||||
// are currently Bluetooth discoverable. Restored when we stop advertising.
|
||||
std::string original_device_name_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// A thread pool dedicated to running all the accept loops from
|
||||
// StartAcceptingConnections().
|
||||
MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops};
|
||||
|
||||
// A map of service Name -> ServerSocket. If map is non-empty, we
|
||||
// are currently listening for incoming connections.
|
||||
// BluetoothServerSocket instances are used from accept_loops_runner_,
|
||||
// and thus require pointer stability.
|
||||
absl::flat_hash_map<std::string, BluetoothServerSocket> server_sockets_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_CLASSIC_H_
|
||||
@@ -0,0 +1,208 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/bluetooth_classic.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/mediums/bluetooth_radio.h"
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/public/bluetooth_classic.h"
|
||||
#include "platform_v2/public/count_down_latch.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/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_.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();
|
||||
}
|
||||
|
||||
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
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/bluetooth_radio.h"
|
||||
|
||||
#include "platform_v2/base/exception.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/system_clock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
constexpr absl::Duration BluetoothRadio::kPauseBetweenToggle;
|
||||
|
||||
BluetoothRadio::BluetoothRadio() {
|
||||
if (!IsAdapterValid()) {
|
||||
NEARBY_LOG(ERROR, "Bluetooth adapter is not valid: BT is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
BluetoothRadio::~BluetoothRadio() {
|
||||
// We never enabled Bluetooth, nothing to do.
|
||||
if (!ever_saved_state_.Get()) {
|
||||
NEARBY_LOG(INFO, "BT adapter was not used. Not touching HW.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle Bluetooth regardless of our original state. Some devices/chips can
|
||||
// start to freak out after some time (e.g. b/37775337), and this helps to
|
||||
// ensure BT resets properly.
|
||||
NEARBY_LOG(INFO, "Toggle BT adapter state before releasing adapter.");
|
||||
Toggle();
|
||||
|
||||
NEARBY_LOG(INFO, "Bring BT adapter to original state");
|
||||
if (!SetBluetoothState(originally_enabled_.Get())) {
|
||||
NEARBY_LOG(INFO, "Failed to restore BT adapter original state.");
|
||||
}
|
||||
}
|
||||
|
||||
bool BluetoothRadio::Enable() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SetBluetoothState(true);
|
||||
}
|
||||
|
||||
bool BluetoothRadio::Disable() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return SetBluetoothState(false);
|
||||
}
|
||||
|
||||
bool BluetoothRadio::IsEnabled() const {
|
||||
return IsAdapterValid() && IsInDesiredState(true);
|
||||
}
|
||||
|
||||
bool BluetoothRadio::Toggle() {
|
||||
if (!SaveOriginalState()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetBluetoothState(false)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT off.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SystemClock::Sleep(kPauseBetweenToggle).Raised(Exception::kInterrupted)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: interrupted before turing on.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetBluetoothState(true)) {
|
||||
NEARBY_LOG(INFO, "BT Toggle: Failed to turn BT on.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BluetoothRadio::SetBluetoothState(bool enable) {
|
||||
return bluetooth_adapter_.SetStatus(
|
||||
enable ? BluetoothAdapter::Status::kEnabled
|
||||
: BluetoothAdapter::Status::kDisabled);
|
||||
}
|
||||
|
||||
bool BluetoothRadio::IsInDesiredState(bool should_be_enabled) const {
|
||||
return bluetooth_adapter_.IsEnabled() == should_be_enabled;
|
||||
}
|
||||
|
||||
bool BluetoothRadio::SaveOriginalState() {
|
||||
if (!IsAdapterValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we haven't saved the original state of the radio, save it.
|
||||
if (!ever_saved_state_.Set(true)) {
|
||||
originally_enabled_.Set(bluetooth_adapter_.IsEnabled());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "platform_v2/public/atomic_boolean.h"
|
||||
#include "platform_v2/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.
|
||||
class BluetoothRadio {
|
||||
public:
|
||||
BluetoothRadio();
|
||||
BluetoothRadio(BluetoothRadio&&) = default;
|
||||
BluetoothRadio& operator=(BluetoothRadio&&) = default;
|
||||
|
||||
// Reverts the Bluetooth radio to its original state.
|
||||
~BluetoothRadio();
|
||||
|
||||
// Enables Bluetooth.
|
||||
//
|
||||
// This must be called before attempting to invoke any other methods of
|
||||
// this class.
|
||||
//
|
||||
// Returns true if enabled successfully.
|
||||
bool Enable();
|
||||
|
||||
// Disables Bluetooth.
|
||||
//
|
||||
// Returns true if disabled successfully.
|
||||
bool Disable();
|
||||
|
||||
// Returns true if the Bluetooth radio is currently enabled.
|
||||
bool IsEnabled() const;
|
||||
|
||||
// Turn BT radio Off, delay for kPauseBetweenToggle and then turn it On.
|
||||
// This will block calling thread for at least kPauseBetweenToggle duration.
|
||||
bool Toggle();
|
||||
|
||||
// Returns result of BluetoothAdapter::IsValid() for private adapter instance.
|
||||
bool IsAdapterValid() const {
|
||||
return bluetooth_adapter_.IsValid();
|
||||
}
|
||||
|
||||
BluetoothAdapter& GetBluetoothAdapter() {
|
||||
return bluetooth_adapter_;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr absl::Duration kPauseBetweenToggle = absl::Seconds(3);
|
||||
|
||||
bool SetBluetoothState(bool enable);
|
||||
bool IsInDesiredState(bool should_be_enabled) const;
|
||||
// To be called in enable(), disable(), and toggle(). This will remember the
|
||||
// original state of the radio before any radio state has been modified.
|
||||
// Returns false if Bluetooth doesn't exist on the device and the state cannot
|
||||
// be obtained.
|
||||
bool SaveOriginalState();
|
||||
|
||||
// BluetoothAdapter::IsValid() will return false if BT is not supported.
|
||||
BluetoothAdapter bluetooth_adapter_;
|
||||
|
||||
// The Bluetooth radio's original state, before we modified it. True if
|
||||
// originally enabled, false if originally disabled.
|
||||
// We restore the radio to its original state in the destructor.
|
||||
|
||||
AtomicBoolean originally_enabled_{false};
|
||||
// false if we never modified the radio state, true otherwise.
|
||||
AtomicBoolean ever_saved_state_{false};
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_BLUETOOTH_RADIO_H_
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/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
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Tracks "lost" entities based on a manual update/compute model. Used by
|
||||
// mediums that only report found devices. Lost entities are computed based off
|
||||
// of whether a specific entity was rediscovered since the last call to
|
||||
// ComputeLostEntities.
|
||||
//
|
||||
// Note: Entity must overload the < and == operators.
|
||||
template <typename Entity>
|
||||
class LostEntityTracker {
|
||||
public:
|
||||
using EntitySet = absl::flat_hash_set<Entity>;
|
||||
|
||||
LostEntityTracker();
|
||||
~LostEntityTracker();
|
||||
|
||||
// Records the given entity as being recently found, whether or not this is
|
||||
// our first time discovering the entity.
|
||||
void RecordFoundEntity(const Entity& entity) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Computes and returns the set of entities considered lost since the last
|
||||
// time this method was called.
|
||||
EntitySet ComputeLostEntities() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
Mutex mutex_;
|
||||
EntitySet current_entities_ ABSL_GUARDED_BY(mutex_);
|
||||
EntitySet previously_found_entities_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
template <typename Entity>
|
||||
LostEntityTracker<Entity>::LostEntityTracker()
|
||||
: current_entities_{}, previously_found_entities_{} {}
|
||||
|
||||
template <typename Entity>
|
||||
LostEntityTracker<Entity>::~LostEntityTracker() {
|
||||
previously_found_entities_.clear();
|
||||
current_entities_.clear();
|
||||
}
|
||||
|
||||
template <typename Entity>
|
||||
void LostEntityTracker<Entity>::RecordFoundEntity(const Entity& entity) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
current_entities_.insert(entity);
|
||||
}
|
||||
|
||||
template <typename Entity>
|
||||
typename LostEntityTracker<Entity>::EntitySet
|
||||
LostEntityTracker<Entity>::ComputeLostEntities() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// The set of lost entities is the previously found set MINUS the currently
|
||||
// found set.
|
||||
for (const auto& item : current_entities_) {
|
||||
previously_found_entities_.erase(item);
|
||||
}
|
||||
auto lost_entities = std::move(previously_found_entities_);
|
||||
previously_found_entities_ = std::move(current_entities_);
|
||||
current_entities_ = {};
|
||||
|
||||
return lost_entities;
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_LOST_ENTITY_TRACKER_H_
|
||||
@@ -0,0 +1,137 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/lost_entity_tracker.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
struct TestEntity {
|
||||
int id;
|
||||
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const TestEntity& test_entity) {
|
||||
return H::combine(std::move(h), test_entity.id);
|
||||
}
|
||||
|
||||
bool operator==(const TestEntity& other) const { return id == other.id; }
|
||||
bool operator<(const TestEntity& other) const { return id < other.id; }
|
||||
};
|
||||
|
||||
TEST(LostEntityTrackerTest, NoEntitiesLost) {
|
||||
LostEntityTracker<TestEntity> lost_entity_tracker;
|
||||
TestEntity entity_1{1};
|
||||
TestEntity entity_2{2};
|
||||
TestEntity entity_3{3};
|
||||
|
||||
// Discover some entities.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_3);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
ASSERT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Rediscover the same entities.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_3);
|
||||
|
||||
// Make sure we still didn't lose any entities.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
}
|
||||
|
||||
TEST(LostEntityTrackerTest, AllEntitiesLost) {
|
||||
LostEntityTracker<TestEntity> lost_entity_tracker;
|
||||
TestEntity entity_1{1};
|
||||
TestEntity entity_2{2};
|
||||
TestEntity entity_3{3};
|
||||
|
||||
// Discover some entities.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_3);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Go through a round without rediscovering any entities.
|
||||
typename LostEntityTracker<TestEntity>::EntitySet lost_entities =
|
||||
lost_entity_tracker.ComputeLostEntities();
|
||||
EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_3) != lost_entities.end());
|
||||
}
|
||||
|
||||
TEST(LostEntityTrackerTest, SomeEntitiesLost) {
|
||||
LostEntityTracker<TestEntity> lost_entity_tracker;
|
||||
TestEntity entity_1{1};
|
||||
TestEntity entity_2{2};
|
||||
TestEntity entity_3{3};
|
||||
|
||||
// Discover some entities.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_2);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Go through the next round only rediscovering one of our entities and
|
||||
// discovering an additional entity as well. Then, verify that only one entity
|
||||
// was lost after the check.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
lost_entity_tracker.RecordFoundEntity(entity_3);
|
||||
typename LostEntityTracker<TestEntity>::EntitySet lost_entities =
|
||||
lost_entity_tracker.ComputeLostEntities();
|
||||
EXPECT_TRUE(lost_entities.find(entity_1) == lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_2) != lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_3) == lost_entities.end());
|
||||
}
|
||||
|
||||
TEST(LostEntityTrackerTest, SameEntityMultipleCopies) {
|
||||
LostEntityTracker<TestEntity> lost_entity_tracker;
|
||||
TestEntity entity_1{1};
|
||||
TestEntity entity_1_copy{1};
|
||||
|
||||
// Discover an entity.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1);
|
||||
|
||||
// Make sure none are lost on the first round.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Rediscover the same entity, but through a copy of it.
|
||||
lost_entity_tracker.RecordFoundEntity(entity_1_copy);
|
||||
|
||||
// Make sure none are lost on the second round.
|
||||
EXPECT_TRUE(lost_entity_tracker.ComputeLostEntities().empty());
|
||||
|
||||
// Go through a round without rediscovering any entities and verify that we
|
||||
// lost an entity equivalent to both copies of it.
|
||||
typename LostEntityTracker<TestEntity>::EntitySet lost_entities =
|
||||
lost_entity_tracker.ComputeLostEntities();
|
||||
EXPECT_EQ(lost_entities.size(), 1);
|
||||
EXPECT_TRUE(lost_entities.find(entity_1) != lost_entities.end());
|
||||
EXPECT_TRUE(lost_entities.find(entity_1_copy) != lost_entities.end());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/mediums.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
BluetoothRadio& Mediums::GetBluetoothRadio() {
|
||||
return bluetooth_radio_;
|
||||
}
|
||||
|
||||
BluetoothClassic& Mediums::GetBluetoothClassic() {
|
||||
return bluetooth_classic_;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_
|
||||
|
||||
#include "core_v2/internal/mediums/bluetooth_classic.h"
|
||||
#include "core_v2/internal/mediums/bluetooth_radio.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// Facilitates convenient and reliable usage of various wireless mediums.
|
||||
class Mediums {
|
||||
public:
|
||||
Mediums() = default;
|
||||
~Mediums() = default;
|
||||
|
||||
// Returns a handle to the Bluetooth radio.
|
||||
BluetoothRadio& GetBluetoothRadio();
|
||||
|
||||
// Returns a handle to the Bluetooth Classic medium.
|
||||
BluetoothClassic& GetBluetoothClassic();
|
||||
|
||||
private:
|
||||
// The order of declaration is critical for both construction and
|
||||
// destruction.
|
||||
//
|
||||
// 1) Construction: The individual mediums have a dependency on the
|
||||
// corresponding radio, so the radio must be initialized first.
|
||||
//
|
||||
// 2) Destruction: The individual mediums should be shut down before the
|
||||
// corresponding radio.
|
||||
BluetoothRadio bluetooth_radio_;
|
||||
BluetoothClassic bluetooth_classic_{bluetooth_radio_};
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_MEDIUMS_H_
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/utils.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/base/prng.h"
|
||||
#include "platform_v2/public/crypto.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
ByteArray Utils::GenerateRandomBytes(size_t length) {
|
||||
Prng rng;
|
||||
std::string data;
|
||||
data.reserve(length);
|
||||
|
||||
// Adds 4 random bytes per iteration.
|
||||
while (length > 0) {
|
||||
std::uint32_t val = rng.NextUint32();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
data += val & 0xFF;
|
||||
val >>= 8;
|
||||
length--;
|
||||
|
||||
if (!length) break;
|
||||
}
|
||||
}
|
||||
|
||||
return ByteArray(data);
|
||||
}
|
||||
|
||||
ByteArray Utils::Sha256Hash(const ByteArray& source, size_t length) {
|
||||
ByteArray full_hash(length);
|
||||
full_hash.CopyAt(0, Crypto::Sha256(std::string(source)));
|
||||
return full_hash;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_UTILS_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_UTILS_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
static ByteArray GenerateRandomBytes(size_t length);
|
||||
static ByteArray Sha256Hash(const ByteArray& source, size_t length);
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_UTILS_H_
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/uuid.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "platform_v2/public/crypto.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
std::ostream& write_hex(std::ostream& os, absl::string_view data) {
|
||||
for (const auto b : data) {
|
||||
os << std::setfill('0')
|
||||
<< std::setw(2)
|
||||
<< std::hex
|
||||
<< (static_cast<unsigned int>(b) & 0x0ff);
|
||||
}
|
||||
return os;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Uuid::Uuid(absl::string_view data) : data_(Crypto::Md5(data)) {
|
||||
// Based on the Java counterpart at
|
||||
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#162.
|
||||
data_[6] &= 0x0f; // Clear version.
|
||||
data_[6] |= 0x30; // Set to version 3.
|
||||
data_[8] &= 0x3f; // Clear variant.
|
||||
data_[8] |= 0x80; // Set to IETF variant.
|
||||
}
|
||||
|
||||
Uuid::Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits) {
|
||||
// Base on the Java counterpart at
|
||||
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#104.
|
||||
data_.reserve(sizeof(most_sig_bits) + sizeof(least_sig_bits));
|
||||
|
||||
data_[0] = static_cast<char>((most_sig_bits >> 56) & 0x0ff);
|
||||
data_[1] = static_cast<char>((most_sig_bits >> 48) & 0x0ff);
|
||||
data_[2] = static_cast<char>((most_sig_bits >> 40) & 0x0ff);
|
||||
data_[3] = static_cast<char>((most_sig_bits >> 32) & 0x0ff);
|
||||
data_[4] = static_cast<char>((most_sig_bits >> 24) & 0x0ff);
|
||||
data_[5] = static_cast<char>((most_sig_bits >> 16) & 0x0ff);
|
||||
data_[6] = static_cast<char>((most_sig_bits >> 8) & 0x0ff);
|
||||
data_[7] = static_cast<char>((most_sig_bits >> 0) & 0x0ff);
|
||||
|
||||
data_[8] = static_cast<char>((least_sig_bits >> 56) & 0x0ff);
|
||||
data_[9] = static_cast<char>((least_sig_bits >> 48) & 0x0ff);
|
||||
data_[10] = static_cast<char>((least_sig_bits >> 40) & 0x0ff);
|
||||
data_[11] = static_cast<char>((least_sig_bits >> 32) & 0x0ff);
|
||||
data_[12] = static_cast<char>((least_sig_bits >> 24) & 0x0ff);
|
||||
data_[13] = static_cast<char>((least_sig_bits >> 16) & 0x0ff);
|
||||
data_[14] = static_cast<char>((least_sig_bits >> 8) & 0x0ff);
|
||||
data_[15] = static_cast<char>((least_sig_bits >> 0) & 0x0ff);
|
||||
}
|
||||
|
||||
Uuid::operator std::string() const {
|
||||
// Based on the Java counterpart at
|
||||
// http://androidxref.com/8.0.0_r4/xref/libcore/ojluni/src/main/java/java/util/UUID.java#375.
|
||||
std::ostringstream md5_hex;
|
||||
write_hex(md5_hex, absl::string_view(&data_[0], 4));
|
||||
md5_hex << "-";
|
||||
write_hex(md5_hex, absl::string_view(&data_[4], 2));
|
||||
md5_hex << "-";
|
||||
write_hex(md5_hex, absl::string_view(&data_[6], 2));
|
||||
md5_hex << "-";
|
||||
write_hex(md5_hex, absl::string_view(&data_[8], 2));
|
||||
md5_hex << "-";
|
||||
write_hex(md5_hex, absl::string_view(&data_[10], 6));
|
||||
|
||||
return md5_hex.str();
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_UUID_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_UUID_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
// A type 3 name-based
|
||||
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based))
|
||||
// UUID.
|
||||
//
|
||||
// https://developer.android.com/reference/java/util/UUID.html
|
||||
class Uuid final {
|
||||
public:
|
||||
Uuid() : Uuid("uuid") {}
|
||||
explicit Uuid(absl::string_view data);
|
||||
Uuid(std::uint64_t most_sig_bits, std::uint64_t least_sig_bits);
|
||||
Uuid(const Uuid&) = default;
|
||||
Uuid& operator=(const Uuid&) = default;
|
||||
Uuid(Uuid&&) = default;
|
||||
Uuid& operator=(Uuid&&) = default;
|
||||
~Uuid() = default;
|
||||
|
||||
// Returns the canonical textual representation
|
||||
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of the
|
||||
// UUID.
|
||||
explicit operator std::string() const;
|
||||
std::string data() const {
|
||||
return data_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_UUID_H_
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/uuid.h"
|
||||
|
||||
#include "platform_v2/public/crypto.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr char 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,103 @@
|
||||
# Copyright 2020 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
cc_library(
|
||||
name = "webrtc",
|
||||
srcs = [
|
||||
"connection_flow.cc",
|
||||
"peer_connection_observer_impl.cc",
|
||||
"webrtc_socket.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"connection_flow.h",
|
||||
"data_channel_listener.h",
|
||||
"local_ice_candidate_listener.h",
|
||||
"peer_connection_observer_impl.h",
|
||||
"webrtc_socket.h",
|
||||
],
|
||||
deps = [
|
||||
"//core_v2:core_types",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:logging",
|
||||
"//platform_v2/public:types",
|
||||
"//absl/memory",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "webrtc_test",
|
||||
srcs = [
|
||||
"connection_flow_test.cc",
|
||||
"webrtc_socket_test.cc",
|
||||
],
|
||||
deps = [
|
||||
":webrtc",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/impl/g3", # buildcleaner: keep
|
||||
"//platform_v2/public:comm",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "peer_id_test",
|
||||
srcs = ["peer_id_test.cc"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/impl/g3", #buildcleaner: keep
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:types",
|
||||
"//testing/base/public:gunit_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "signaling_frames_test",
|
||||
srcs = ["signaling_frames_test.cc"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
":signaling_frames",
|
||||
"//platform_v2/impl/g3", # buildcleaner: keep
|
||||
"//net/proto2/public:proto2",
|
||||
"//testing/base/public:gunit_main",
|
||||
"//webrtc/pc:peerconnection", # buildcleaner: keep
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "peer_id",
|
||||
srcs = ["peer_id.cc"],
|
||||
hdrs = ["peer_id.h"],
|
||||
deps = [
|
||||
"//core_v2/internal/mediums:utils",
|
||||
"//platform_v2/base",
|
||||
"//absl/strings",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "signaling_frames",
|
||||
srcs = ["signaling_frames.cc"],
|
||||
hdrs = ["signaling_frames.h"],
|
||||
deps = [
|
||||
":peer_id",
|
||||
"//platform_v2/base",
|
||||
"//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,148 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/connection_flow.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
#include "platform_v2/public/webrtc.h"
|
||||
#include "absl/memory/memory.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
std::unique_ptr<ConnectionFlow> ConnectionFlow::Create(
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener,
|
||||
SingleThreadExecutor* single_threaded_executor,
|
||||
WebRtcMedium& webrtc_medium) {
|
||||
auto connection_flow = absl::WrapUnique(new ConnectionFlow(
|
||||
std::move(local_ice_candidate_listener), std::move(data_channel_listener),
|
||||
single_threaded_executor));
|
||||
if (connection_flow->InitPeerConnection(webrtc_medium)) {
|
||||
return connection_flow;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ConnectionFlow::ConnectionFlow(
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener,
|
||||
SingleThreadExecutor* single_threaded_executor)
|
||||
: data_channel_listener_(std::move(data_channel_listener)),
|
||||
peer_connection_observer_(this, std::move(local_ice_candidate_listener),
|
||||
single_threaded_executor) {}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>
|
||||
ConnectionFlow::CreateOffer() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
|
||||
return std::unique_ptr<webrtc::SessionDescriptionInterface>();
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>
|
||||
ConnectionFlow::CreateAnswer() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
|
||||
return std::unique_ptr<webrtc::SessionDescriptionInterface>();
|
||||
}
|
||||
|
||||
bool ConnectionFlow::SetLocalSessionDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> sdp) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnOfferReceived(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> offer) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnAnswerReceived(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> answer) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnRemoteIceCandidatesReceived(
|
||||
std::vector<webrtc::IceCandidateInterface*> ice_candidates) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
api::ListenableFuture<rtc::scoped_refptr<webrtc::DataChannelInterface>>*
|
||||
ConnectionFlow::GetDataChannel() {
|
||||
return static_cast<
|
||||
api::ListenableFuture<rtc::scoped_refptr<webrtc::DataChannelInterface>>*>(
|
||||
&data_channel_future_);
|
||||
}
|
||||
|
||||
bool ConnectionFlow::Close() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) {
|
||||
Future<bool> success_future;
|
||||
webrtc_medium.CreatePeerConnection(
|
||||
&peer_connection_observer_,
|
||||
[this, &success_future](
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection) {
|
||||
peer_connection_ = peer_connection;
|
||||
success_future.Set(true);
|
||||
});
|
||||
|
||||
ExceptionOr<bool> result = success_future.Get(kTimeout);
|
||||
return result.ok() && result.result();
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnSignalingStable() {
|
||||
// TODO(bfranz): Implement
|
||||
}
|
||||
|
||||
void ConnectionFlow::ProcessOnPeerConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
|
||||
// TODO(bfranz): Implement
|
||||
}
|
||||
|
||||
webrtc::DataChannelObserver* ConnectionFlow::CreateDataChannelObserver(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
// TODO(bfranz): Implement
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,147 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#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_connection_observer_impl.h"
|
||||
#include "platform_v2/base/runnable.h"
|
||||
#include "platform_v2/public/future.h"
|
||||
#include "platform_v2/public/single_thread_executor.h"
|
||||
#include "platform_v2/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,
|
||||
SingleThreadExecutor* single_threaded_executor,
|
||||
WebRtcMedium& webrtc_medium);
|
||||
~ConnectionFlow() = default;
|
||||
|
||||
// Create the offer that will be sent to the remote. Mirrors the behaviour of
|
||||
// PeerConnectionInterface::CreateOffer.
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> CreateOffer()
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Create the answer that will be sent to the remote. Mirrors the behaviour of
|
||||
// PeerConnectionInterface::CreateAnswer.
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> CreateAnswer()
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Set the local session description. |sdp| was created via CreateOffer()
|
||||
// or CreateAnswer().
|
||||
bool SetLocalSessionDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> 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.
|
||||
void OnOfferReceived(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> 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.
|
||||
void OnAnswerReceived(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> 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<webrtc::IceCandidateInterface*> ice_candidates)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Get a future for the data channel.
|
||||
api::ListenableFuture<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();
|
||||
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);
|
||||
|
||||
private:
|
||||
ConnectionFlow(LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener,
|
||||
SingleThreadExecutor* single_threaded_executor);
|
||||
|
||||
// TODO(bfranz): Consider whether this needs to be configurable per platform
|
||||
static constexpr absl::Duration kTimeout = absl::Milliseconds(250);
|
||||
|
||||
bool InitPeerConnection(WebRtcMedium& webrtc_medium);
|
||||
|
||||
DataChannelListener data_channel_listener_;
|
||||
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>> data_channel_future_;
|
||||
|
||||
PeerConnectionObserverImpl peer_connection_observer_;
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
|
||||
|
||||
Mutex mutex_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_CONNECTION_FLOW_H_
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/connection_flow.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "platform_v2/public/webrtc.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
TEST(ConnectionFlowTest, Create) {
|
||||
LocalIceCandidateListener local_ice_candidate_listener;
|
||||
DataChannelListener data_channel_listener;
|
||||
SingleThreadExecutor executor;
|
||||
WebRtcMedium webrtc_medium;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> connection_flow = ConnectionFlow::Create(
|
||||
std::move(local_ice_candidate_listener), std::move(data_channel_listener),
|
||||
&executor, webrtc_medium);
|
||||
|
||||
EXPECT_NE(connection_flow, nullptr);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_
|
||||
|
||||
#include "core_v2/listeners.h"
|
||||
#include "platform_v2/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(ByteArray)> data_channel_message_received_cb =
|
||||
DefaultCallback<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_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_LISTENER_H_
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_
|
||||
|
||||
#include "core_v2/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_V2_INTERNAL_MEDIUMS_WEBRTC_LOCAL_ICE_CANDIDATE_LISTENER_H_
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h"
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/connection_flow.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
PeerConnectionObserverImpl::PeerConnectionObserverImpl(
|
||||
ConnectionFlow* connection_flow,
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
SingleThreadExecutor* executor)
|
||||
: connection_flow_(connection_flow),
|
||||
local_ice_candidate_listener_(std::move(local_ice_candidate_listener)),
|
||||
single_threaded_signaling_offloader_(executor) {}
|
||||
|
||||
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,62 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_H_
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h"
|
||||
#include "platform_v2/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,
|
||||
SingleThreadExecutor* executor);
|
||||
|
||||
// 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_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_CONNECTION_OBSERVER_IMPL_H_
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/peer_id.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "core_v2/internal/mediums/utils.h"
|
||||
#include "absl/strings/ascii.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
constexpr int kPeerIdLength = 64;
|
||||
|
||||
std::string BytesToStringUppercase(const ByteArray& bytes) {
|
||||
std::string hex_string(
|
||||
absl::BytesToHexString(std::string(bytes.data(), bytes.size())));
|
||||
absl::AsciiStrToUpper(&hex_string);
|
||||
return hex_string;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
PeerId PeerId::FromRandom() {
|
||||
return FromSeed(Utils::GenerateRandomBytes(kPeerIdLength));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// PeerId is used as an identifier to exchange SDP messages to establish WebRTC
|
||||
// p2p connection.
|
||||
class PeerId {
|
||||
public:
|
||||
explicit PeerId(const string& id) : id_(id) {}
|
||||
~PeerId() = default;
|
||||
|
||||
static PeerId FromRandom();
|
||||
static PeerId FromSeed(const ByteArray& seed);
|
||||
|
||||
const string& GetId() const { return id_; }
|
||||
|
||||
private:
|
||||
const string id_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_PEER_ID_H_
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/peer_id.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/crypto.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
TEST(PeerIdTest, GenerateRandomPeerId) {
|
||||
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 = "seed";
|
||||
std::string expected_peer_id =
|
||||
"19B25856E1C150CA834CFFC8B59B23ADBD0EC0389E58EB22B3B64768098D002B";
|
||||
|
||||
ByteArray seed_bytes(seed);
|
||||
PeerId peer_id = PeerId::FromSeed(seed_bytes);
|
||||
|
||||
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);
|
||||
EXPECT_EQ(id, peer_id.GetId());
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/signaling_frames.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace webrtc_frames {
|
||||
using WebRtcSignalingFrame = location::nearby::mediums::WebRtcSignalingFrame;
|
||||
|
||||
namespace {
|
||||
|
||||
ByteArray FrameToByteArray(const WebRtcSignalingFrame& signaling_frame) {
|
||||
std::string message;
|
||||
signaling_frame.SerializeToString(&message);
|
||||
return ByteArray(message.c_str(), message.size());
|
||||
}
|
||||
|
||||
void SetSenderId(const PeerId& sender_id, WebRtcSignalingFrame& frame) {
|
||||
frame.mutable_sender_id()->set_id(sender_id.GetId());
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> DecodeIceCandidate(
|
||||
location::nearby::mediums::IceCandidate ice_candidate_proto) {
|
||||
webrtc::SdpParseError error;
|
||||
return std::unique_ptr<webrtc::IceCandidateInterface>(
|
||||
webrtc::CreateIceCandidate(ice_candidate_proto.sdp_mid(),
|
||||
ice_candidate_proto.sdp_m_line_index(),
|
||||
ice_candidate_proto.sdp(), &error));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::READY_FOR_SIGNALING_POKE_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
signaling_frame.set_allocated_ready_for_signaling_poke(
|
||||
new location::nearby::mediums::ReadyForSignalingPoke());
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ByteArray EncodeOffer(const PeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::OFFER_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
std::string offer_str;
|
||||
offer.ToString(&offer_str);
|
||||
signaling_frame.mutable_offer()
|
||||
->mutable_session_description()
|
||||
->set_description(offer_str);
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ByteArray EncodeAnswer(const PeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::ANSWER_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
std::string answer_str;
|
||||
answer.ToString(&answer_str);
|
||||
signaling_frame.mutable_answer()
|
||||
->mutable_session_description()
|
||||
->set_description(answer_str);
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
ByteArray EncodeIceCandidates(
|
||||
const PeerId& sender_id,
|
||||
const std::vector<location::nearby::mediums::IceCandidate>&
|
||||
ice_candidates) {
|
||||
WebRtcSignalingFrame signaling_frame;
|
||||
signaling_frame.set_type(WebRtcSignalingFrame::ICE_CANDIDATES_TYPE);
|
||||
SetSenderId(sender_id, signaling_frame);
|
||||
for (const auto& ice_candidate : ice_candidates) {
|
||||
*signaling_frame.mutable_ice_candidates()->add_ice_candidates() =
|
||||
ice_candidate;
|
||||
}
|
||||
return FrameToByteArray(std::move(signaling_frame));
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
return webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kOffer,
|
||||
frame.offer().session_description().description());
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
return webrtc::CreateSessionDescription(
|
||||
webrtc::SdpType::kAnswer,
|
||||
frame.answer().session_description().description());
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
const WebRtcSignalingFrame& frame) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
for (const auto& candidate : frame.ice_candidates().ice_candidates()) {
|
||||
ice_candidates.push_back(DecodeIceCandidate(candidate));
|
||||
}
|
||||
return ice_candidates;
|
||||
}
|
||||
|
||||
location::nearby::mediums::IceCandidate EncodeIceCandidate(
|
||||
const webrtc::IceCandidateInterface& ice_candidate) {
|
||||
std::string sdp;
|
||||
ice_candidate.ToString(&sdp);
|
||||
location::nearby::mediums::IceCandidate ice_candidate_proto;
|
||||
ice_candidate_proto.set_sdp(sdp);
|
||||
ice_candidate_proto.set_sdp_mid(ice_candidate.sdp_mid());
|
||||
ice_candidate_proto.set_sdp_m_line_index(ice_candidate.sdp_mline_index());
|
||||
return ice_candidate_proto;
|
||||
}
|
||||
|
||||
} // namespace webrtc_frames
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/peer_id.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace webrtc_frames {
|
||||
|
||||
ByteArray EncodeReadyForSignalingPoke(const PeerId& sender_id);
|
||||
|
||||
ByteArray EncodeOffer(const PeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& offer);
|
||||
ByteArray EncodeAnswer(const PeerId& sender_id,
|
||||
const webrtc::SessionDescriptionInterface& answer);
|
||||
|
||||
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);
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeOffer(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> DecodeAnswer(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> DecodeIceCandidates(
|
||||
const location::nearby::mediums::WebRtcSignalingFrame& frame);
|
||||
|
||||
} // namespace webrtc_frames
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SIGNALING_FRAMES_H_
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/signaling_frames.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/peer_id.h"
|
||||
#include "net/proto2/public/text_format.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
namespace webrtc_frames {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kSampleSdp[] =
|
||||
"v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 "
|
||||
"0\r\na=msid-semantic: WMS\r\n";
|
||||
|
||||
const char kIceCandidateSdp1[] =
|
||||
"a=candidate:1 1 UDP 2130706431 10.0.1.1 8998 typ host";
|
||||
const char kIceCandidateSdp2[] =
|
||||
"a=candidate:2 1 UDP 1694498815 192.0.2.3 45664 typ srflx raddr";
|
||||
|
||||
const char kIceSdpMid[] = "data";
|
||||
const int kIceSdpMLineIndex = 0;
|
||||
|
||||
const char kOfferProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: OFFER_TYPE
|
||||
offer {
|
||||
session_description {
|
||||
description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char kAnswerProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: ANSWER_TYPE
|
||||
answer {
|
||||
session_description {
|
||||
description: "v=0\r\no=- 7859371131 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=msid-semantic: WMS\r\n"
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
const char kIceCandidatesProto[] = R"(
|
||||
sender_id { id: "abc" }
|
||||
type: ICE_CANDIDATES_TYPE
|
||||
ice_candidates {
|
||||
ice_candidates {
|
||||
sdp: "candidate:1 1 udp 2130706431 10.0.1.1 8998 typ host generation 0"
|
||||
sdp_mid: "data"
|
||||
sdp_m_line_index: 0
|
||||
}
|
||||
ice_candidates {
|
||||
sdp: "candidate:2 1 udp 1694498815 192.0.2.3 45664 typ srflx generation 0"
|
||||
sdp_mid: "data"
|
||||
sdp_m_line_index: 0
|
||||
}
|
||||
}
|
||||
)";
|
||||
} // namespace
|
||||
|
||||
TEST(SignalingFramesTest, SignalingPoke) {
|
||||
PeerId sender_id("abc");
|
||||
ByteArray encoded_poke = EncodeReadyForSignalingPoke(sender_id);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(std::string(encoded_poke.data(), encoded_poke.size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(R"(
|
||||
sender_id { id: "abc" }
|
||||
type: READY_FOR_SIGNALING_POKE_TYPE
|
||||
ready_for_signaling_poke {}
|
||||
)"));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidOffer) {
|
||||
PeerId sender_id("abc");
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> offer =
|
||||
webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, kSampleSdp);
|
||||
ByteArray encoded_offer = EncodeOffer(sender_id, *offer);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_offer.data(), encoded_offer.size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kOfferProto));
|
||||
}
|
||||
|
||||
TEST(SignaingFramesTest, DecodeValidOffer) {
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kOfferProto, &frame);
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> decoded_offer =
|
||||
DecodeOffer(frame);
|
||||
|
||||
EXPECT_EQ(webrtc::SdpType::kOffer, decoded_offer->GetType());
|
||||
std::string description;
|
||||
decoded_offer->ToString(&description);
|
||||
EXPECT_EQ(kSampleSdp, description);
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidAnswer) {
|
||||
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.data(), encoded_answer.size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kAnswerProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidAnswer) {
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kAnswerProto, &frame);
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> decoded_answer =
|
||||
DecodeAnswer(frame);
|
||||
|
||||
EXPECT_EQ(webrtc::SdpType::kAnswer, decoded_answer->GetType());
|
||||
std::string description;
|
||||
decoded_answer->ToString(&description);
|
||||
EXPECT_EQ(kSampleSdp, description);
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, EncodeValidIceCandidates) {
|
||||
PeerId sender_id("abc");
|
||||
webrtc::SdpParseError error;
|
||||
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error));
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error));
|
||||
std::vector<location::nearby::mediums::IceCandidate> encoded_candidates_vec;
|
||||
for (const auto& ice_candidate : ice_candidates) {
|
||||
encoded_candidates_vec.push_back(EncodeIceCandidate(*ice_candidate));
|
||||
}
|
||||
ByteArray encoded_candidates =
|
||||
EncodeIceCandidates(sender_id, encoded_candidates_vec);
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
frame.ParseFromString(
|
||||
std::string(encoded_candidates.data(), encoded_candidates.size()));
|
||||
|
||||
EXPECT_THAT(frame, testing::EqualsProto(kIceCandidatesProto));
|
||||
}
|
||||
|
||||
TEST(SignalingFramesTest, DecodeValidIceCandidates) {
|
||||
webrtc::SdpParseError error;
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> ice_candidates;
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp1, &error));
|
||||
ice_candidates.emplace_back(webrtc::CreateIceCandidate(
|
||||
kIceSdpMid, kIceSdpMLineIndex, kIceCandidateSdp2, &error));
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
proto2::TextFormat::ParseFromString(kIceCandidatesProto, &frame);
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
decoded_candidates = DecodeIceCandidates(frame);
|
||||
|
||||
ASSERT_EQ(2u, decoded_candidates.size());
|
||||
for (int i = 0; i < static_cast<int>(decoded_candidates.size()); i++) {
|
||||
EXPECT_TRUE(ice_candidates[i]->candidate().IsEquivalent(
|
||||
decoded_candidates[i]->candidate()));
|
||||
EXPECT_EQ(ice_candidates[i]->sdp_mid(), decoded_candidates[i]->sdp_mid());
|
||||
EXPECT_EQ(ice_candidates[i]->sdp_mline_index(),
|
||||
decoded_candidates[i]->sdp_mline_index());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace webrtc_frames
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket.h"
|
||||
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// OutputStreamImpl
|
||||
Exception WebRtcSocket::OutputStreamImpl::Write(const ByteArray& data) {
|
||||
if (data.size() > kMaxDataSize) {
|
||||
NEARBY_LOG(WARNING, "Sending data larger than 1MB");
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
socket_->BlockUntilSufficientSpaceInBuffer(data.size());
|
||||
|
||||
if (socket_->IsClosed()) {
|
||||
NEARBY_LOG(WARNING, "Tried sending message while socket is closed");
|
||||
return {Exception::kIo};
|
||||
}
|
||||
|
||||
if (!socket_->SendMessage(data)) {
|
||||
return {Exception::kIo};
|
||||
}
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
Exception WebRtcSocket::OutputStreamImpl::Flush() {
|
||||
// Java implementation is empty.
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
Exception WebRtcSocket::OutputStreamImpl::Close() {
|
||||
socket_->Close();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
// WebRtcSocket
|
||||
WebRtcSocket::WebRtcSocket(
|
||||
const string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)
|
||||
: name_(name), data_channel_(std::move(data_channel)) {}
|
||||
|
||||
InputStream& WebRtcSocket::GetInputStream() { return pipe_.GetInputStream(); }
|
||||
|
||||
OutputStream& WebRtcSocket::GetOutputStream() { return output_stream_; }
|
||||
|
||||
void WebRtcSocket::Close() {
|
||||
if (IsClosed()) return;
|
||||
|
||||
closed_.Set(true);
|
||||
pipe_.GetInputStream().Close();
|
||||
pipe_.GetOutputStream().Close();
|
||||
data_channel_->Close();
|
||||
WakeUpWriter();
|
||||
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();
|
||||
}
|
||||
|
||||
void WebRtcSocket::NotifyDataChannelBufferedAmountChanged() { WakeUpWriter(); }
|
||||
|
||||
bool WebRtcSocket::SendMessage(const ByteArray& data) {
|
||||
return data_channel_->Send(
|
||||
webrtc::DataBuffer(std::string(data.data(), data.size())));
|
||||
}
|
||||
|
||||
bool WebRtcSocket::IsClosed() { return closed_.Get(); }
|
||||
|
||||
void WebRtcSocket::WakeUpWriter() {
|
||||
MutexLock lock(&backpressure_mutex_);
|
||||
buffer_variable_.Notify();
|
||||
}
|
||||
|
||||
void WebRtcSocket::SetOnSocketClosedListener(SocketClosedListener&& listener) {
|
||||
socket_closed_listener_ = std::move(listener);
|
||||
}
|
||||
|
||||
void WebRtcSocket::BlockUntilSufficientSpaceInBuffer(int length) {
|
||||
MutexLock lock(&backpressure_mutex_);
|
||||
while (!IsClosed() &&
|
||||
(data_channel_->buffered_amount() + length > kMaxDataSize)) {
|
||||
// TODO(himanshujaju): Add wait with timeout.
|
||||
buffer_variable_.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,115 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/listeners.h"
|
||||
#include "platform_v2/base/input_stream.h"
|
||||
#include "platform_v2/base/output_stream.h"
|
||||
#include "platform_v2/base/socket.h"
|
||||
#include "platform_v2/public/atomic_boolean.h"
|
||||
#include "platform_v2/public/condition_variable.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "platform_v2/public/pipe.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Maximum data size: 1 MB
|
||||
constexpr int kMaxDataSize = 1 * 1024 * 1024;
|
||||
|
||||
// Defines the Socket implementation specific to WebRTC, which uses the WebRTC
|
||||
// data channel to send and receive messages.
|
||||
//
|
||||
// Messages are buffered here to prevent the data channel from overflowing,
|
||||
// which could lead to data loss.
|
||||
class WebRtcSocket : public Socket {
|
||||
public:
|
||||
WebRtcSocket(const string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
~WebRtcSocket() override = default;
|
||||
|
||||
WebRtcSocket(const WebRtcSocket& other) = delete;
|
||||
WebRtcSocket& operator=(const WebRtcSocket& other) = delete;
|
||||
|
||||
// Overrides for location::nearby::Socket:
|
||||
InputStream& GetInputStream() override;
|
||||
OutputStream& GetOutputStream() override;
|
||||
void Close() override;
|
||||
|
||||
// Callback from WebRTC data channel when new message has been received from
|
||||
// the remote.
|
||||
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.
|
||||
struct SocketClosedListener {
|
||||
std::function<void()> socket_closed_cb = DefaultCallback<>();
|
||||
};
|
||||
|
||||
void SetOnSocketClosedListener(SocketClosedListener&& listener);
|
||||
|
||||
private:
|
||||
class OutputStreamImpl : public OutputStream {
|
||||
public:
|
||||
explicit OutputStreamImpl(WebRtcSocket* const socket) : socket_(socket) {}
|
||||
~OutputStreamImpl() override = default;
|
||||
|
||||
OutputStreamImpl(const OutputStreamImpl& other) = delete;
|
||||
OutputStreamImpl& operator=(const OutputStreamImpl& other) = delete;
|
||||
|
||||
// OutputStream:
|
||||
Exception Write(const ByteArray& data) override;
|
||||
Exception Flush() override;
|
||||
Exception Close() override;
|
||||
|
||||
private:
|
||||
// |this| OutputStreamImpl is owned by |socket_|.
|
||||
WebRtcSocket* const socket_;
|
||||
};
|
||||
|
||||
void WakeUpWriter();
|
||||
bool IsClosed();
|
||||
bool SendMessage(const ByteArray& data);
|
||||
void BlockUntilSufficientSpaceInBuffer(int length);
|
||||
|
||||
string name_;
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
|
||||
|
||||
Pipe pipe_;
|
||||
|
||||
OutputStreamImpl output_stream_{this};
|
||||
|
||||
AtomicBoolean closed_{false};
|
||||
|
||||
SocketClosedListener socket_closed_listener_;
|
||||
|
||||
mutable Mutex backpressure_mutex_;
|
||||
ConditionVariable buffer_variable_{&backpressure_mutex_};
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_H_
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
// using TestPlatform = platform::ImplementationPlatform;
|
||||
|
||||
const char kSocketName[] = "TestSocket";
|
||||
|
||||
class MockDataChannel
|
||||
: public rtc::RefCountedObject<webrtc::DataChannelInterface> {
|
||||
public:
|
||||
MOCK_METHOD(void, RegisterObserver, (webrtc::DataChannelObserver*));
|
||||
MOCK_METHOD(void, UnregisterObserver, ());
|
||||
|
||||
MOCK_METHOD(std::string, label, (), (const));
|
||||
|
||||
MOCK_METHOD(bool, reliable, (), (const));
|
||||
MOCK_METHOD(int, id, (), (const));
|
||||
MOCK_METHOD(DataState, state, (), (const));
|
||||
MOCK_METHOD(uint32_t, messages_sent, (), (const));
|
||||
MOCK_METHOD(uint64_t, bytes_sent, (), (const));
|
||||
MOCK_METHOD(uint32_t, messages_received, (), (const));
|
||||
MOCK_METHOD(uint64_t, bytes_received, (), (const));
|
||||
|
||||
MOCK_METHOD(uint64_t, buffered_amount, (), (const));
|
||||
|
||||
MOCK_METHOD(void, Close, ());
|
||||
|
||||
MOCK_METHOD(bool, Send, (const webrtc::DataBuffer&));
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(WebRtcSocketTest, ReadFromSocket) {
|
||||
const ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
webrtc_socket.NotifyDataChannelMsgReceived(kMessage);
|
||||
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 webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
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(7);
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result(), ByteArray{"Me"});
|
||||
|
||||
result = webrtc_socket.GetInputStream().Read(7);
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result(), ByteArray{"ssa"});
|
||||
|
||||
result = webrtc_socket.GetInputStream().Read(7);
|
||||
EXPECT_TRUE(result.ok());
|
||||
EXPECT_EQ(result.result(), ByteArray{"ge"});
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteToSocket) {
|
||||
const ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Send(testing::_))
|
||||
.WillRepeatedly(testing::Return(true));
|
||||
EXPECT_TRUE(webrtc_socket.GetOutputStream().Write(kMessage).Ok());
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, SendDataBiggerThanMax) {
|
||||
const ByteArray kMessage{kMaxDataSize + 1};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0);
|
||||
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
|
||||
Exception{Exception::kIo});
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteToDataChannelFails) {
|
||||
ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
ON_CALL(*mock_data_channel, Send(testing::_))
|
||||
.WillByDefault(testing::Return(false));
|
||||
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
|
||||
Exception{Exception::kIo});
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, Close) {
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Close());
|
||||
|
||||
int socket_closed_cb_called = 0;
|
||||
|
||||
webrtc_socket.SetOnSocketClosedListener(
|
||||
{.socket_closed_cb = [&]() { socket_closed_cb_called++; }});
|
||||
webrtc_socket.Close();
|
||||
|
||||
EXPECT_EQ(socket_closed_cb_called, 1);
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, WriteOnClosedChannel) {
|
||||
ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
webrtc_socket.Close();
|
||||
|
||||
EXPECT_CALL(*mock_data_channel, Send(testing::_)).Times(0);
|
||||
EXPECT_EQ(webrtc_socket.GetOutputStream().Write(kMessage),
|
||||
Exception{Exception::kIo});
|
||||
}
|
||||
|
||||
TEST(WebRtcSocketTest, ReadFromClosedChannel) {
|
||||
ByteArray kMessage{"Message"};
|
||||
rtc::scoped_refptr<MockDataChannel> mock_data_channel = new MockDataChannel();
|
||||
WebRtcSocket webrtc_socket(kSocketName, mock_data_channel);
|
||||
ON_CALL(*mock_data_channel, Send(testing::_))
|
||||
.WillByDefault(testing::Return(true));
|
||||
|
||||
webrtc_socket.GetOutputStream().Write(kMessage);
|
||||
webrtc_socket.Close();
|
||||
|
||||
EXPECT_EQ(webrtc_socket.GetInputStream().Read(7).exception(), Exception::kIo);
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
Reference in New Issue
Block a user