mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-16 15:36:12 -04:00
Merge branch 'master' into release
Change-Id: I56ea2217899e92bdd9d6cb56797ac9895e194fff
This commit is contained in:
@@ -24,6 +24,8 @@ cc_library(
|
||||
"bluetooth_radio.cc",
|
||||
"mediums.cc",
|
||||
"uuid.cc",
|
||||
"webrtc.cc",
|
||||
"wifi_lan.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"advertisement_read_result.h",
|
||||
@@ -37,22 +39,29 @@ cc_library(
|
||||
"lost_entity_tracker.h",
|
||||
"mediums.h",
|
||||
"uuid.h",
|
||||
"webrtc.h",
|
||||
"wifi_lan.h",
|
||||
],
|
||||
visibility = [
|
||||
"//core_v2/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//core_v2:core_types",
|
||||
"//core_v2/internal/mediums/webrtc",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/base:util",
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:logging",
|
||||
"//platform_v2/public:types",
|
||||
"//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto",
|
||||
"//absl/container:flat_hash_map",
|
||||
"//absl/container:flat_hash_set",
|
||||
"//absl/numeric:int128",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
"//smhasher:libmurmur3",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
"//webrtc/api:scoped_refptr",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -84,10 +93,13 @@ cc_test(
|
||||
"bluetooth_radio_test.cc",
|
||||
"lost_entity_tracker_test.cc",
|
||||
"uuid_test.cc",
|
||||
"webrtc_test.cc",
|
||||
"wifi_lan_test.cc",
|
||||
],
|
||||
shard_count = 16,
|
||||
deps = [
|
||||
":mediums",
|
||||
"//core_v2/internal/mediums/webrtc",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/base:test_util",
|
||||
"//platform_v2/impl/g3", # build_cleaner: keep
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "platform_v2/base/base_input_stream.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -56,11 +58,15 @@ BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now, time to read the bytes!
|
||||
const auto *read_ptr = ble_advertisement_bytes.data();
|
||||
ByteArray advertisement_bytes{ble_advertisement_bytes};
|
||||
BaseInputStream base_input_stream{advertisement_bytes};
|
||||
// The first 1 byte is supposed to be the version and socket version.
|
||||
auto version_and_socket_version_byte =
|
||||
static_cast<char>(base_input_stream.ReadUint8());
|
||||
|
||||
// 1. Version.
|
||||
version_ = static_cast<Version>((*read_ptr & kVersionBitmask) >> 5);
|
||||
// Version.
|
||||
version_ = static_cast<Version>(
|
||||
(version_and_socket_version_byte & kVersionBitmask) >> 5);
|
||||
if (!IsSupportedVersion(version_)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: unsupported Version %u",
|
||||
@@ -68,49 +74,42 @@ BleAdvertisement::BleAdvertisement(const ByteArray &ble_advertisement_bytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Socket Version.
|
||||
socket_version_ =
|
||||
static_cast<SocketVersion>((*read_ptr & kSocketVersionBitmask) >> 2);
|
||||
// Socket version.
|
||||
socket_version_ = static_cast<SocketVersion>(
|
||||
(version_and_socket_version_byte & kSocketVersionBitmask) >> 2);
|
||||
if (!IsSupportedSocketVersion(socket_version_)) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Cannot deserialize BLEAdvertisement: unsupported SocketVersion %u",
|
||||
"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;
|
||||
// The next 3 bytes are supposed to be the service_id_hash.
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
|
||||
// 4.1. Data size.
|
||||
size_t expected_data_size = DeserializeDataSize(read_ptr);
|
||||
// The next 4 bytes are supposed to be the length of the data.
|
||||
std::uint32_t expected_data_size = base_input_stream.ReadUint32();
|
||||
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);
|
||||
"Cannot deserialize BleAdvertisement: negative data size %d",
|
||||
expected_data_size);
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// 4.2. Data.
|
||||
data_ = ByteArray(read_ptr, expected_data_size);
|
||||
read_ptr += expected_data_size;
|
||||
// The rest bytes are supposed to be the data.
|
||||
// Check that the stated data size is the same as what we received.
|
||||
data_ = base_input_stream.ReadBytes(expected_data_size);
|
||||
if (data_.size() != expected_data_size) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Cannot deserialize BleAdvertisement: expected data to be %u "
|
||||
"bytes, got %" PRIu64 " bytes ",
|
||||
expected_data_size, data_.size());
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BleAdvertisement::operator ByteArray() const {
|
||||
@@ -118,8 +117,6 @@ BleAdvertisement::operator ByteArray() const {
|
||||
return ByteArray{};
|
||||
}
|
||||
|
||||
std::string out;
|
||||
|
||||
// The first 3 bits are the Version.
|
||||
char version_and_socket_version_byte =
|
||||
(static_cast<char>(version_) << 5) & kVersionBitmask;
|
||||
@@ -131,11 +128,13 @@ BleAdvertisement::operator ByteArray() const {
|
||||
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_));
|
||||
// clang-format off
|
||||
std::string out =
|
||||
absl::StrCat(std::string(1, version_and_socket_version_byte),
|
||||
std::string(service_id_hash_),
|
||||
std::string(data_size_bytes),
|
||||
std::string(data_));
|
||||
// clang-format on
|
||||
|
||||
return ByteArray{std::move(out)};
|
||||
}
|
||||
@@ -182,33 +181,6 @@ void BleAdvertisement::SerializeDataSize(char *data_size_bytes_write_ptr,
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -81,9 +81,6 @@ class BleAdvertisement {
|
||||
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
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "platform_v2/base/base64_utils.h"
|
||||
#include "platform_v2/base/base_input_stream.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -27,8 +29,7 @@ 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 ||
|
||||
if (version != Version::kV2 || num_slots <= 0 ||
|
||||
service_id_bloom_filter.size() != kServiceIdBloomFilterLength ||
|
||||
advertisement_hash.size() != kAdvertisementHashLength) {
|
||||
return;
|
||||
@@ -61,13 +62,12 @@ BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
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);
|
||||
BaseInputStream base_input_stream{ble_advertisement_header_bytes};
|
||||
// The first 1 byte is supposed to be the version and number of slots.
|
||||
auto version_and_pcp_byte = static_cast<char>(base_input_stream.ReadUint8());
|
||||
// The upper 3 bits are supposed to be the version.
|
||||
version_ =
|
||||
static_cast<Version>((version_and_pcp_byte & kVersionBitmask) >> 5);
|
||||
if (version_ != Version::kV2) {
|
||||
NEARBY_LOG(
|
||||
ERROR,
|
||||
@@ -75,20 +75,19 @@ BleAdvertisementHeader::BleAdvertisementHeader(
|
||||
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++;
|
||||
// The lower 5 bits are supposed to be the number of slots.
|
||||
num_slots_ = static_cast<int>(version_and_pcp_byte & kNumSlotsBitmask);
|
||||
if (num_slots_ <= 0) {
|
||||
version_ = Version::kUndefined;
|
||||
return;
|
||||
}
|
||||
|
||||
// Service ID bloom filter.
|
||||
// The next 10 bytes are supposed to be the service_id_bloom_filter.
|
||||
service_id_bloom_filter_ =
|
||||
ByteArray(ble_advertisement_header_read_ptr, kServiceIdBloomFilterLength);
|
||||
ble_advertisement_header_read_ptr += kServiceIdBloomFilterLength;
|
||||
base_input_stream.ReadBytes(kServiceIdBloomFilterLength);
|
||||
|
||||
// Advertisement hash.
|
||||
advertisement_hash_ =
|
||||
ByteArray(ble_advertisement_header_read_ptr, kAdvertisementHashLength);
|
||||
ble_advertisement_header_read_ptr += kAdvertisementHashLength;
|
||||
// The next 4 bytes are supposed to be the advertisement_hash.
|
||||
advertisement_hash_ = base_input_stream.ReadBytes(kAdvertisementHashLength);
|
||||
}
|
||||
|
||||
BleAdvertisementHeader::operator std::string() const {
|
||||
@@ -96,18 +95,18 @@ BleAdvertisementHeader::operator std::string() const {
|
||||
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_));
|
||||
|
||||
// clang-format off
|
||||
std::string out = absl::StrCat(std::string(1, version_and_num_slots_byte),
|
||||
std::string(service_id_bloom_filter_),
|
||||
std::string(advertisement_hash_));
|
||||
// clang-format on
|
||||
|
||||
return Base64Utils::Encode(ByteArray(std::move(out)));
|
||||
}
|
||||
|
||||
@@ -22,16 +22,17 @@ 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";
|
||||
constexpr absl::string_view kServiceIDBloomFilter{
|
||||
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a"};
|
||||
constexpr absl::string_view kAdvertisementHash{"\x0a\x0b\x0c\x0d"};
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionWorks) {
|
||||
ByteArray service_id_bloom_filter{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{kAdvertisementHash};
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
@@ -48,8 +49,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionWorks) {
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) {
|
||||
auto bad_version = static_cast<BleAdvertisementHeader::Version>(666);
|
||||
|
||||
ByteArray service_id_bloom_filter{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{kAdvertisementHash};
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
bad_version, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
@@ -57,12 +58,24 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithBadVersion) {
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWitZeroNumSlot) {
|
||||
int num_slot = 0;
|
||||
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, num_slot, service_id_bloom_filter, advertisement_hash};
|
||||
|
||||
EXPECT_FALSE(ble_advertisement_header.IsValid());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest,
|
||||
ConstructionFailsWithShortServiceIdBloomFilter) {
|
||||
char short_service_id_bloom_filter[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09";
|
||||
|
||||
ByteArray short_service_id_bloom_filter_bytes{short_service_id_bloom_filter};
|
||||
ByteArray advertisement_hash{kAdvertisementHash};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, short_service_id_bloom_filter_bytes,
|
||||
@@ -77,7 +90,7 @@ TEST(BleAdvertisementHeaderTest,
|
||||
"\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};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
@@ -88,7 +101,7 @@ TEST(BleAdvertisementHeaderTest,
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) {
|
||||
char short_advertisement_hash[] = "\x0a\x0b\x0c";
|
||||
|
||||
ByteArray service_id_bloom_filter{kServiceIDBloomFilter};
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{short_advertisement_hash};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
@@ -100,7 +113,7 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithShortAdvertisementHash) {
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) {
|
||||
char long_advertisement_hash[] = "\x0a\x0b\x0c\x0d\x0e";
|
||||
|
||||
ByteArray service_id_bloom_filter{kServiceIDBloomFilter};
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{long_advertisement_hash};
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
@@ -109,8 +122,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFailsWithLongAdvertisementHash) {
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) {
|
||||
ByteArray service_id_bloom_filter{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{kAdvertisementHash};
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader org_ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
@@ -130,8 +143,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromSerializedStringWorks) {
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) {
|
||||
ByteArray service_id_bloom_filter{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{kAdvertisementHash};
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
@@ -159,8 +172,8 @@ TEST(BleAdvertisementHeaderTest, ConstructionFromExtraBytesWorks) {
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementHeaderTest, ConstructionFromShortLengthFails) {
|
||||
ByteArray service_id_bloom_filter{kServiceIDBloomFilter};
|
||||
ByteArray advertisement_hash{kAdvertisementHash};
|
||||
ByteArray service_id_bloom_filter{std::string(kServiceIDBloomFilter)};
|
||||
ByteArray advertisement_hash{std::string(kAdvertisementHash)};
|
||||
|
||||
BleAdvertisementHeader ble_advertisement_header{
|
||||
kVersion, kNumSlots, service_id_bloom_filter, advertisement_hash};
|
||||
|
||||
@@ -24,20 +24,20 @@ namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
const BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2;
|
||||
const BleAdvertisement::SocketVersion kSocketVersion =
|
||||
constexpr BleAdvertisement::Version kVersion = BleAdvertisement::Version::kV2;
|
||||
constexpr 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?";
|
||||
constexpr absl::string_view kServiceIDHashBytes{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kData{
|
||||
"How much wood can a woodchuck chuck if a wood chuck would chuck wood?"};
|
||||
// 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;
|
||||
constexpr size_t kAdvertisementLength = 77;
|
||||
constexpr size_t kLongAdvertisementLength = kAdvertisementLength + 1000;
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionWorksV1) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement ble_advertisement{BleAdvertisement::Version::kV1,
|
||||
BleAdvertisement::SocketVersion::kV1,
|
||||
@@ -56,8 +56,8 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadVersion) {
|
||||
BleAdvertisement::Version bad_version =
|
||||
static_cast<BleAdvertisement::Version>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement ble_advertisement{bad_version, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
@@ -69,8 +69,8 @@ TEST(BleAdvertisementTest, ConstructionFailsWithBadSocketVersion) {
|
||||
BleAdvertisement::SocketVersion bad_socket_version =
|
||||
static_cast<BleAdvertisement::SocketVersion>(666);
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, bad_socket_version,
|
||||
service_id_hash, data};
|
||||
@@ -82,7 +82,7 @@ TEST(BleAdvertisementTest, ConstructionFailsWithShortServiceIdHash) {
|
||||
char short_service_id_hash_bytes[] = "\x0a\x0b";
|
||||
|
||||
ByteArray bad_service_id_hash{short_service_id_hash_bytes};
|
||||
ByteArray data{kData};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion,
|
||||
bad_service_id_hash, data};
|
||||
@@ -94,7 +94,7 @@ 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};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion,
|
||||
bad_service_id_hash, data};
|
||||
@@ -107,7 +107,7 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongData) {
|
||||
// attribute length because it needs some room for the preceding fields.
|
||||
char long_data[512]{};
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray bad_data{long_data, 512};
|
||||
|
||||
BleAdvertisement ble_advertisement{kVersion, kSocketVersion, service_id_hash,
|
||||
@@ -117,8 +117,8 @@ TEST(BleAdvertisementTest, ConstructionFailsWithLongData) {
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
@@ -134,13 +134,10 @@ TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWorks) {
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) {
|
||||
char empty_data[0]{};
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{empty_data};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
service_id_hash, ByteArray()};
|
||||
ByteArray ble_advertisement_bytes{org_ble_advertisement};
|
||||
BleAdvertisement ble_advertisement{ble_advertisement_bytes};
|
||||
|
||||
@@ -148,13 +145,12 @@ TEST(BleAdvertisementTest, ConstructionFromSerializedBytesWithEmptyDataWorks) {
|
||||
EXPECT_EQ(kVersion, ble_advertisement.GetVersion());
|
||||
EXPECT_EQ(kSocketVersion, ble_advertisement.GetSocketVersion());
|
||||
EXPECT_EQ(service_id_hash, ble_advertisement.GetServiceIdHash());
|
||||
EXPECT_EQ(data.size(), ble_advertisement.GetData().size());
|
||||
EXPECT_EQ(data, ble_advertisement.GetData());
|
||||
EXPECT_TRUE(ble_advertisement.GetData().Empty());
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromExtraSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
@@ -187,8 +183,8 @@ TEST(BleAdvertisementTest, ConstructionFromNullBytesFails) {
|
||||
}
|
||||
|
||||
TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
@@ -204,8 +200,8 @@ TEST(BleAdvertisementTest, ConstructionFromShortLengthSerializedBytesFails) {
|
||||
|
||||
TEST(BleAdvertisementTest,
|
||||
ConstructionFromSerializedBytesWithInvalidDataLengthFails) {
|
||||
ByteArray service_id_hash{kServiceIDHashBytes};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHashBytes)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BleAdvertisement org_ble_advertisement{kVersion, kSocketVersion,
|
||||
service_id_hash, data};
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
|
||||
#include "core_v2/internal/mediums/ble_packet.h"
|
||||
|
||||
#include "platform_v2/base/base_input_stream.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -44,13 +46,14 @@ BlePacket::BlePacket(const ByteArray& ble_packet_bytes) {
|
||||
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;
|
||||
ByteArray packet_bytes{ble_packet_bytes};
|
||||
BaseInputStream base_input_stream{packet_bytes};
|
||||
// The first 3 bytes are supposed to be the service_id_hash.
|
||||
service_id_hash_ = base_input_stream.ReadBytes(kServiceIdHashLength);
|
||||
|
||||
data_ = ByteArray(ble_packet_bytes_read_ptr,
|
||||
ble_packet_bytes.size() - kServiceIdHashLength);
|
||||
// The rest bytes are supposed to be the data.
|
||||
data_ = base_input_stream.ReadBytes(ble_packet_bytes.size() -
|
||||
kServiceIdHashLength);
|
||||
}
|
||||
|
||||
BlePacket::operator ByteArray() const {
|
||||
@@ -58,11 +61,8 @@ BlePacket::operator ByteArray() const {
|
||||
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_));
|
||||
std::string out =
|
||||
absl::StrCat(std::string(service_id_hash_), std::string(data_));
|
||||
|
||||
return ByteArray(std::move(out));
|
||||
}
|
||||
|
||||
@@ -21,12 +21,12 @@ namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
constexpr char kServiceIDHash[] = "\x0a\x0b\x0c";
|
||||
constexpr char kData[] = "\x01\x02\x03\x04\x05";
|
||||
constexpr absl::string_view kServiceIDHash{"\x0a\x0b\x0c"};
|
||||
constexpr absl::string_view kData{"\x01\x02\x03\x04\x05"};
|
||||
|
||||
TEST(BlePacketTest, ConstructionWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHash};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHash)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket ble_packet{service_id_hash, data};
|
||||
|
||||
@@ -38,7 +38,7 @@ TEST(BlePacketTest, ConstructionWorks) {
|
||||
TEST(BlePacketTest, ConstructionWorksWithEmptyData) {
|
||||
char empty_data[] = "";
|
||||
|
||||
ByteArray service_id_hash{kServiceIDHash};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHash)};
|
||||
ByteArray data{empty_data};
|
||||
|
||||
BlePacket ble_packet{service_id_hash, data};
|
||||
@@ -52,7 +52,7 @@ TEST(BlePacketTest, ConstructionFailsWithShortServiceIdHash) {
|
||||
char short_service_id_hash[] = "\x0a\x0b";
|
||||
|
||||
ByteArray service_id_hash{short_service_id_hash};
|
||||
ByteArray data{kData};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket ble_packet(service_id_hash, data);
|
||||
|
||||
@@ -63,7 +63,7 @@ TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) {
|
||||
char long_service_id_hash[] = "\x0a\x0b\x0c\x0d";
|
||||
|
||||
ByteArray service_id_hash{long_service_id_hash};
|
||||
ByteArray data{kData};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket ble_packet{service_id_hash, data};
|
||||
|
||||
@@ -71,8 +71,8 @@ TEST(BlePacketTest, ConstructionFailsWithLongServiceIdHash) {
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionFromSerializedBytesWorks) {
|
||||
ByteArray service_id_hash{kServiceIDHash};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHash)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket org_ble_packet{service_id_hash, data};
|
||||
ByteArray ble_packet_bytes{org_ble_packet};
|
||||
@@ -91,8 +91,8 @@ TEST(BlePacketTest, ConstructionFromNullBytesFails) {
|
||||
}
|
||||
|
||||
TEST(BlePacketTest, ConstructionFromShortLengthDataFails) {
|
||||
ByteArray service_id_hash{kServiceIDHash};
|
||||
ByteArray data{kData};
|
||||
ByteArray service_id_hash{std::string(kServiceIDHash)};
|
||||
ByteArray data{std::string(kData)};
|
||||
|
||||
BlePacket org_ble_packet{service_id_hash, data};
|
||||
ByteArray org_ble_packet_bytes{org_ble_packet};
|
||||
|
||||
@@ -22,10 +22,10 @@ namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
const char kId[] = "AB12";
|
||||
constexpr absl::string_view kId{"AB12"};
|
||||
|
||||
TEST(BlePeripheralTest, ConstructionWorks) {
|
||||
ByteArray id{kId};
|
||||
ByteArray id{std::string(kId)};
|
||||
|
||||
BlePeripheral ble_peripheral{id};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
const size_t kByteArrayLength = 100;
|
||||
constexpr size_t kByteArrayLength = 100;
|
||||
|
||||
TEST(BloomFilterTest, EmptyFilterReturnsEmptyArray) {
|
||||
BloomFilter<kByteArrayLength> bloom_filter;
|
||||
|
||||
@@ -38,6 +38,7 @@ class BluetoothClassicTest : public ::testing::Test {
|
||||
using DiscoveryCallback = BluetoothClassicMedium::DiscoveryCallback;
|
||||
|
||||
BluetoothClassicTest() {
|
||||
env_.Start();
|
||||
env_.Reset();
|
||||
radio_a_ = std::make_unique<BluetoothRadio>();
|
||||
radio_b_ = std::make_unique<BluetoothRadio>();
|
||||
@@ -60,6 +61,7 @@ class BluetoothClassicTest : public ::testing::Test {
|
||||
radio_a_.reset();
|
||||
radio_b_.reset();
|
||||
env_.Reset();
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
|
||||
@@ -26,6 +26,10 @@ BluetoothClassic& Mediums::GetBluetoothClassic() {
|
||||
return bluetooth_classic_;
|
||||
}
|
||||
|
||||
WifiLan& Mediums::GetWifiLan() {
|
||||
return wifi_lan_;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
#include "core_v2/internal/mediums/bluetooth_classic.h"
|
||||
#include "core_v2/internal/mediums/bluetooth_radio.h"
|
||||
#include "core_v2/internal/mediums/wifi_lan.h"
|
||||
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -34,6 +36,9 @@ class Mediums {
|
||||
// Returns a handle to the Bluetooth Classic medium.
|
||||
BluetoothClassic& GetBluetoothClassic();
|
||||
|
||||
// Returns a handle to the Wifi-Lan medium.
|
||||
WifiLan& GetWifiLan();
|
||||
|
||||
private:
|
||||
// The order of declaration is critical for both construction and
|
||||
// destruction.
|
||||
@@ -45,6 +50,7 @@ class Mediums {
|
||||
// corresponding radio.
|
||||
BluetoothRadio bluetooth_radio_;
|
||||
BluetoothClassic bluetooth_classic_{bluetooth_radio_};
|
||||
WifiLan wifi_lan_;
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr char kString[] = "some string";
|
||||
constexpr absl::string_view kString{"some string"};
|
||||
constexpr std::uint64_t kNum1 = 0x123456789abcdef0;
|
||||
constexpr std::uint64_t kNum2 = 0x21436587a9cbed0f;
|
||||
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
#include "core_v2/internal/mediums/webrtc.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "core_v2/internal/mediums/webrtc/signaling_frames.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/listeners.h"
|
||||
#include "platform_v2/public/future.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
// The maximum amount of time to wait to connect to a data channel via WebRTC.
|
||||
// TODO(himanshujaju): Should this be configurable per platform?
|
||||
constexpr absl::Duration kDataChannelTimeout = absl::Milliseconds(5000);
|
||||
|
||||
} // namespace
|
||||
|
||||
WebRtc::WebRtc() = default;
|
||||
|
||||
WebRtc::~WebRtc() {
|
||||
single_thread_executor_.Shutdown();
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
bool WebRtc::IsAvailable() { return medium_.IsValid(); }
|
||||
|
||||
bool WebRtc::IsAcceptingConnections() {
|
||||
MutexLock lock(&mutex_);
|
||||
return role_ == Role::kOfferer;
|
||||
}
|
||||
|
||||
bool WebRtc::StartAcceptingConnections(const PeerId& self_id,
|
||||
AcceptedConnectionCallback callback) {
|
||||
if (!IsAvailable()) {
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
LogAndDisconnect("WebRTC is not available for data transfer.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnections()) {
|
||||
NEARBY_LOG(WARNING, "Already accepting WebRTC connections.");
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
if (role_ != Role::kNone) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"Cannot start accepting WebRTC connections, current role %d",
|
||||
role_);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitWebRtcFlow(Role::kOfferer, self_id)) return false;
|
||||
|
||||
SessionDescriptionWrapper offer = connection_flow_->CreateOffer();
|
||||
pending_local_offer_ = webrtc_frames::EncodeOffer(self_id, offer.GetSdp());
|
||||
if (!SetLocalSessionDescription(std::move(offer))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// There is no timeout set for the future returned since we do not know how
|
||||
// much time it will take for the two devices to discover each other before
|
||||
// the actual transport can begin.
|
||||
ListenForWebRtcSocketFuture(connection_flow_->GetDataChannel(),
|
||||
std::move(callback));
|
||||
NEARBY_LOG(INFO, "Started listening for WebRtc connections as %s",
|
||||
self_id.GetId().c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WebRtcSocketWrapper WebRtc::Connect(const PeerId& peer_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAvailable()) {
|
||||
Disconnect();
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
if (role_ != Role::kNone) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"Cannot connect with WebRtc because we are already acting as %d",
|
||||
role_);
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
peer_id_ = peer_id;
|
||||
if (!InitWebRtcFlow(Role::kAnswerer, PeerId::FromRandom())) {
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Attempting to make a WebRTC connection to %s.",
|
||||
peer_id.GetId().c_str());
|
||||
|
||||
std::shared_ptr<Future<WebRtcSocketWrapper>> socket_future =
|
||||
ListenForWebRtcSocketFuture(connection_flow_->GetDataChannel(),
|
||||
AcceptedConnectionCallback());
|
||||
|
||||
// The two devices have discovered each other, hence we have a timeout for
|
||||
// establishing the transport channel.
|
||||
ExceptionOr<WebRtcSocketWrapper> result =
|
||||
socket_future->Get(kDataChannelTimeout);
|
||||
if (result.ok()) return result.result();
|
||||
|
||||
Disconnect();
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
bool WebRtc::SetLocalSessionDescription(SessionDescriptionWrapper sdp) {
|
||||
if (!connection_flow_->SetLocalSessionDescription(std::move(sdp))) {
|
||||
LogAndDisconnect("Unable to set local session description");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebRtc::StopAcceptingConnections() {
|
||||
if (!IsAcceptingConnections()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Skipped StopAcceptingConnections since we are not currently "
|
||||
"accepting WebRTC connections");
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
ShutdownSignaling();
|
||||
}
|
||||
NEARBY_LOG(INFO, "Stopped accepting WebRTC connections");
|
||||
}
|
||||
|
||||
std::shared_ptr<Future<WebRtcSocketWrapper>>
|
||||
WebRtc::ListenForWebRtcSocketFuture(
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>>*
|
||||
data_channel_future,
|
||||
AcceptedConnectionCallback callback) {
|
||||
auto socket_future = std::make_shared<Future<WebRtcSocketWrapper>>();
|
||||
auto data_channel_runnable = [this, socket_future, data_channel_future,
|
||||
callback{std::move(callback)}]() {
|
||||
// The overall timeout of creating the socket and data channel is controlled
|
||||
// by the caller of this function.
|
||||
ExceptionOr<rtc::scoped_refptr<webrtc::DataChannelInterface>> res =
|
||||
data_channel_future->Get();
|
||||
if (res.ok()) {
|
||||
WebRtcSocketWrapper wrapper = CreateWebRtcSocketWrapper(res.result());
|
||||
callback.accepted_cb(wrapper);
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
socket_ = wrapper;
|
||||
}
|
||||
socket_future->Set(wrapper);
|
||||
} else {
|
||||
NEARBY_LOG(WARNING, "Failed to get WebRtcSocket.");
|
||||
socket_future->Set(WebRtcSocketWrapper());
|
||||
}
|
||||
};
|
||||
|
||||
data_channel_future->AddListener(std::move(data_channel_runnable),
|
||||
&single_thread_executor_);
|
||||
|
||||
return socket_future;
|
||||
}
|
||||
|
||||
WebRtcSocketWrapper WebRtc::CreateWebRtcSocketWrapper(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
if (data_channel == nullptr) {
|
||||
return WebRtcSocketWrapper();
|
||||
}
|
||||
|
||||
auto socket = std::make_unique<WebRtcSocket>("WebRtcSocket", data_channel);
|
||||
socket->SetOnSocketClosedListener({std::bind(&WebRtc::Disconnect, this)});
|
||||
return WebRtcSocketWrapper(std::move(socket));
|
||||
}
|
||||
|
||||
bool WebRtc::InitWebRtcFlow(Role role, const PeerId& self_id) {
|
||||
role_ = role;
|
||||
self_id_ = self_id;
|
||||
|
||||
if (connection_flow_) {
|
||||
LogAndShutdownSignaling(
|
||||
"Tried to initialize WebRTC without shutting down the previous "
|
||||
"connection");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signaling_messenger_) {
|
||||
LogAndShutdownSignaling(
|
||||
"Tried to initialize WebRTC without shutting down signaling messenger");
|
||||
return false;
|
||||
}
|
||||
|
||||
signaling_messenger_ = medium_.GetSignalingMessenger(self_id_.GetId());
|
||||
auto signaling_message_callback = [this](ByteArray message) {
|
||||
OffloadFromSignalingThread([this, message{std::move(message)}]() {
|
||||
ProcessSignalingMessage(message);
|
||||
});
|
||||
};
|
||||
|
||||
if (!signaling_messenger_->IsValid() ||
|
||||
!signaling_messenger_->StartReceivingMessages(
|
||||
signaling_message_callback)) {
|
||||
Disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role_ == Role::kAnswerer &&
|
||||
!signaling_messenger_->SendMessage(
|
||||
peer_id_.GetId(),
|
||||
webrtc_frames::EncodeReadyForSignalingPoke(self_id))) {
|
||||
LogAndDisconnect(absl::StrCat("Could not send signaling poke to peer ",
|
||||
peer_id_.GetId()));
|
||||
return false;
|
||||
}
|
||||
|
||||
connection_flow_ = ConnectionFlow::Create(GetLocalIceCandidateListener(),
|
||||
GetDataChannelListener(), medium_);
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebRtc::OnLocalIceCandidate(
|
||||
const webrtc::IceCandidateInterface* local_ice_candidate) {
|
||||
::location::nearby::mediums::IceCandidate ice_candidate =
|
||||
webrtc_frames::EncodeIceCandidate(*local_ice_candidate);
|
||||
|
||||
OffloadFromSignalingThread([this, ice_candidate{std::move(ice_candidate)}]() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (IsSignaling()) {
|
||||
signaling_messenger_->SendMessage(
|
||||
peer_id_.GetId(), webrtc_frames::EncodeIceCandidates(
|
||||
self_id_, {std::move(ice_candidate)}));
|
||||
} else {
|
||||
pending_local_ice_candidates_.push_back(std::move(ice_candidate));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LocalIceCandidateListener WebRtc::GetLocalIceCandidateListener() {
|
||||
return {std::bind(&WebRtc::OnLocalIceCandidate, this, std::placeholders::_1)};
|
||||
}
|
||||
|
||||
void WebRtc::OnDataChannelClosed() {
|
||||
OffloadFromSignalingThread([this]() {
|
||||
MutexLock lock(&mutex_);
|
||||
LogAndDisconnect("WebRTC data channel closed");
|
||||
});
|
||||
}
|
||||
|
||||
void WebRtc::OnDataChannelMessageReceived(const ByteArray& message) {
|
||||
OffloadFromSignalingThread([this, message]() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (!socket_.IsValid()) {
|
||||
LogAndDisconnect("Received a data channel message without a socket");
|
||||
return;
|
||||
}
|
||||
|
||||
socket_.NotifyDataChannelMsgReceived(message);
|
||||
});
|
||||
}
|
||||
|
||||
void WebRtc::OnDataChannelBufferedAmountChanged() {
|
||||
OffloadFromSignalingThread([this]() {
|
||||
MutexLock lock(&mutex_);
|
||||
if (!socket_.IsValid()) {
|
||||
LogAndDisconnect("Data channel buffer changed without a socket");
|
||||
return;
|
||||
}
|
||||
|
||||
socket_.NotifyDataChannelBufferedAmountChanged();
|
||||
});
|
||||
}
|
||||
|
||||
DataChannelListener WebRtc::GetDataChannelListener() {
|
||||
return {
|
||||
.data_channel_closed_cb = std::bind(&WebRtc::OnDataChannelClosed, this),
|
||||
.data_channel_message_received_cb = std::bind(
|
||||
&WebRtc::OnDataChannelMessageReceived, this, std::placeholders::_1),
|
||||
.data_channel_buffered_amount_changed_cb =
|
||||
std::bind(&WebRtc::OnDataChannelBufferedAmountChanged, this),
|
||||
};
|
||||
}
|
||||
|
||||
bool WebRtc::IsSignaling() {
|
||||
return (role_ != Role::kNone && self_id_.IsValid() && peer_id_.IsValid());
|
||||
}
|
||||
|
||||
void WebRtc::ProcessSignalingMessage(const ByteArray& message) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!connection_flow_) {
|
||||
LogAndDisconnect("Received WebRTC frame before signaling was started");
|
||||
return;
|
||||
}
|
||||
|
||||
location::nearby::mediums::WebRtcSignalingFrame frame;
|
||||
if (!frame.ParseFromString(std::string(message))) {
|
||||
LogAndDisconnect("Failed to parse signaling message");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frame.has_sender_id()) {
|
||||
LogAndDisconnect("Invalid WebRTC frame: Sender ID is missing");
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.has_ready_for_signaling_poke() && !peer_id_.IsValid()) {
|
||||
peer_id_ = PeerId(frame.sender_id().id());
|
||||
NEARBY_LOG(INFO, "Peer %s is ready for signaling",
|
||||
peer_id_.GetId().c_str());
|
||||
}
|
||||
|
||||
if (!IsSignaling()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Ignoring WebRTC frame: we are not currently listening for "
|
||||
"signaling messages");
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.sender_id().id() != peer_id_.GetId()) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Ignoring WebRTC frame: we are only listening for another peer.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.has_ready_for_signaling_poke()) {
|
||||
SendOfferAndIceCandidatesToPeer();
|
||||
} else if (frame.has_offer()) {
|
||||
connection_flow_->OnOfferReceived(
|
||||
SessionDescriptionWrapper(webrtc_frames::DecodeOffer(frame).release()));
|
||||
SendAnswerToPeer();
|
||||
} else if (frame.has_answer()) {
|
||||
connection_flow_->OnAnswerReceived(SessionDescriptionWrapper(
|
||||
webrtc_frames::DecodeAnswer(frame).release()));
|
||||
} else if (frame.has_ice_candidates()) {
|
||||
if (!connection_flow_->OnRemoteIceCandidatesReceived(
|
||||
webrtc_frames::DecodeIceCandidates(frame))) {
|
||||
LogAndDisconnect("Could not add remote ice candidates.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::SendOfferAndIceCandidatesToPeer() {
|
||||
if (pending_local_offer_.Empty()) {
|
||||
LogAndDisconnect(
|
||||
"Unable to send pending offer to remote peer: local offer not set");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!signaling_messenger_->SendMessage(peer_id_.GetId(),
|
||||
pending_local_offer_)) {
|
||||
LogAndDisconnect("Failed to send local offer via signaling messenger");
|
||||
return;
|
||||
}
|
||||
pending_local_offer_ = ByteArray();
|
||||
|
||||
if (!pending_local_ice_candidates_.empty()) {
|
||||
signaling_messenger_->SendMessage(
|
||||
peer_id_.GetId(),
|
||||
webrtc_frames::EncodeIceCandidates(
|
||||
self_id_, std::move(pending_local_ice_candidates_)));
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::SendAnswerToPeer() {
|
||||
SessionDescriptionWrapper answer = connection_flow_->CreateAnswer();
|
||||
ByteArray answer_message(
|
||||
webrtc_frames::EncodeAnswer(self_id_, answer.GetSdp()));
|
||||
|
||||
if (!SetLocalSessionDescription(std::move(answer))) return;
|
||||
|
||||
if (!signaling_messenger_->SendMessage(peer_id_.GetId(), answer_message)) {
|
||||
LogAndDisconnect("Failed to send local answer via signaling messenger");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::LogAndDisconnect(const std::string& error_message) {
|
||||
NEARBY_LOG(WARNING, "Disconnecting WebRTC : %s", error_message.c_str());
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
void WebRtc::LogAndShutdownSignaling(const std::string& error_message) {
|
||||
NEARBY_LOG(WARNING, "Stopping WebRTC signaling : %s", error_message.c_str());
|
||||
ShutdownSignaling();
|
||||
}
|
||||
|
||||
void WebRtc::ShutdownSignaling() {
|
||||
role_ = Role::kNone;
|
||||
self_id_ = PeerId();
|
||||
peer_id_ = PeerId();
|
||||
pending_local_offer_ = ByteArray();
|
||||
pending_local_ice_candidates_.clear();
|
||||
|
||||
if (signaling_messenger_) {
|
||||
signaling_messenger_->StopReceivingMessages();
|
||||
signaling_messenger_.reset();
|
||||
}
|
||||
|
||||
if (!socket_.IsValid()) ShutdownIceCandidateCollection();
|
||||
}
|
||||
|
||||
void WebRtc::Disconnect() {
|
||||
ShutdownSignaling();
|
||||
ShutdownWebRtcSocket();
|
||||
ShutdownIceCandidateCollection();
|
||||
}
|
||||
|
||||
void WebRtc::ShutdownWebRtcSocket() {
|
||||
if (socket_.IsValid()) {
|
||||
socket_.Close();
|
||||
socket_ = WebRtcSocketWrapper();
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::ShutdownIceCandidateCollection() {
|
||||
if (connection_flow_) {
|
||||
connection_flow_->Close();
|
||||
connection_flow_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void WebRtc::OffloadFromSignalingThread(Runnable runnable) {
|
||||
single_thread_executor_.Execute(std::move(runnable));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,155 @@
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/connection_flow.h"
|
||||
#include "core_v2/internal/mediums/webrtc/data_channel_listener.h"
|
||||
#include "core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h"
|
||||
#include "core_v2/internal/mediums/webrtc/peer_id.h"
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket.h"
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/listeners.h"
|
||||
#include "platform_v2/base/runnable.h"
|
||||
#include "platform_v2/public/future.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "platform_v2/public/single_thread_executor.h"
|
||||
#include "platform_v2/public/webrtc.h"
|
||||
#include "location/nearby/mediums/proto/web_rtc_signaling_frames.pb.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
#include "webrtc/api/scoped_refptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// Callback that is invoked when a new connection is accepted.
|
||||
struct AcceptedConnectionCallback {
|
||||
std::function<void(WebRtcSocketWrapper socket)> accepted_cb =
|
||||
DefaultCallback<WebRtcSocketWrapper>();
|
||||
};
|
||||
|
||||
// Entry point for connecting a data channel between two devices via WebRtc.
|
||||
class WebRtc {
|
||||
public:
|
||||
WebRtc();
|
||||
~WebRtc();
|
||||
|
||||
// Returns if WebRtc is available as a medium for nearby to transport data.
|
||||
// Runs on @MainThread.
|
||||
bool IsAvailable();
|
||||
|
||||
// Returns if the device is ready to accept connections from remote devices.
|
||||
// Runs on @MainThread.
|
||||
bool IsAcceptingConnections() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Prepares the device to accept incoming WebRtc connections. Returns a
|
||||
// boolean value indicating if the device has started accepting connections.
|
||||
// Runs on @MainThread.
|
||||
bool StartAcceptingConnections(const PeerId& self_id,
|
||||
AcceptedConnectionCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Prevents device from accepting future connections until
|
||||
// StartAcceptingConnections() is called.
|
||||
// Runs on @MainThread.
|
||||
void StopAcceptingConnections() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Initiates a WebRtc connection with peer device identified by |peer_id|.
|
||||
// Runs on @MainThread.
|
||||
WebRtcSocketWrapper Connect(const PeerId& peer_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
enum class Role {
|
||||
kNone = 0,
|
||||
kOfferer = 1,
|
||||
kAnswerer = 2,
|
||||
};
|
||||
|
||||
bool InitWebRtcFlow(Role role, const PeerId& self_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
std::shared_ptr<Future<WebRtcSocketWrapper>> ListenForWebRtcSocketFuture(
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>>*
|
||||
data_channel_future,
|
||||
AcceptedConnectionCallback callback);
|
||||
|
||||
WebRtcSocketWrapper CreateWebRtcSocketWrapper(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
|
||||
LocalIceCandidateListener GetLocalIceCandidateListener();
|
||||
void OnLocalIceCandidate(
|
||||
const webrtc::IceCandidateInterface* local_ice_candidate);
|
||||
|
||||
DataChannelListener GetDataChannelListener();
|
||||
void OnDataChannelClosed();
|
||||
void OnDataChannelMessageReceived(const ByteArray& message);
|
||||
void OnDataChannelBufferedAmountChanged();
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
bool SetLocalSessionDescription(SessionDescriptionWrapper sdp)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
bool IsSignaling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void ProcessSignalingMessage(const ByteArray& message)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void SendOfferAndIceCandidatesToPeer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on |single_thread_executor_|.
|
||||
void SendAnswerToPeer() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void LogAndDisconnect(const std::string& error_message)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void Disconnect() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void LogAndShutdownSignaling(const std::string& error_message)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownSignaling() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownWebRtcSocket() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Runs on @MainThread and |single_thread_executor_|.
|
||||
void ShutdownIceCandidateCollection();
|
||||
|
||||
void OffloadFromSignalingThread(Runnable runnable);
|
||||
|
||||
Mutex mutex_;
|
||||
|
||||
Role role_ ABSL_GUARDED_BY(mutex_) = Role::kNone;
|
||||
PeerId self_id_ ABSL_GUARDED_BY(mutex_);
|
||||
PeerId peer_id_ ABSL_GUARDED_BY(mutex_);
|
||||
ByteArray pending_local_offer_ ABSL_GUARDED_BY(mutex_);
|
||||
std::vector<::location::nearby::mediums::IceCandidate>
|
||||
pending_local_ice_candidates_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
std::unique_ptr<ConnectionFlow> connection_flow_;
|
||||
std::unique_ptr<WebRtcSignalingMessenger> signaling_messenger_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
WebRtcSocketWrapper socket_ ABSL_GUARDED_BY(mutex_);
|
||||
WebRtcMedium medium_;
|
||||
|
||||
SingleThreadExecutor single_thread_executor_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_H_
|
||||
@@ -16,23 +16,38 @@ cc_library(
|
||||
name = "webrtc",
|
||||
srcs = [
|
||||
"connection_flow.cc",
|
||||
"data_channel_observer_impl.cc",
|
||||
"peer_connection_observer_impl.cc",
|
||||
"peer_id.cc",
|
||||
"signaling_frames.cc",
|
||||
"webrtc_socket.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"connection_flow.h",
|
||||
"data_channel_listener.h",
|
||||
"data_channel_observer_impl.h",
|
||||
"local_ice_candidate_listener.h",
|
||||
"peer_connection_observer_impl.h",
|
||||
"peer_id.h",
|
||||
"session_description_wrapper.h",
|
||||
"signaling_frames.h",
|
||||
"webrtc_socket.h",
|
||||
"webrtc_socket_wrapper.h",
|
||||
],
|
||||
visibility = [
|
||||
"//core_v2/internal:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//core_v2:core_types",
|
||||
"//core_v2/internal/mediums:utils",
|
||||
"//platform_v2/base",
|
||||
"//platform_v2/public:comm",
|
||||
"//platform_v2/public:logging",
|
||||
"//platform_v2/public:types",
|
||||
"//location/nearby/mediums/proto:web_rtc_signaling_frames_cc_proto",
|
||||
"//absl/memory",
|
||||
"//absl/strings",
|
||||
"//absl/time",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
],
|
||||
)
|
||||
@@ -41,6 +56,8 @@ cc_test(
|
||||
name = "webrtc_test",
|
||||
srcs = [
|
||||
"connection_flow_test.cc",
|
||||
"peer_id_test.cc",
|
||||
"signaling_frames_test.cc",
|
||||
"webrtc_socket_test.cc",
|
||||
],
|
||||
deps = [
|
||||
@@ -48,56 +65,12 @@ cc_test(
|
||||
"//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",
|
||||
"//absl/time",
|
||||
"//webrtc/api:libjingle_peerconnection_api",
|
||||
"//webrtc/api:rtc_error",
|
||||
"//webrtc/api:scoped_refptr",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -14,25 +14,79 @@
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/connection_flow.h"
|
||||
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
#include "platform_v2/public/webrtc.h"
|
||||
#include "absl/memory/memory.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
// This is the same as the nearby data channel name.
|
||||
const char kDataChannelName[] = "dataChannel";
|
||||
|
||||
class CreateSessionDescriptionObserverImpl
|
||||
: public webrtc::CreateSessionDescriptionObserver {
|
||||
public:
|
||||
explicit CreateSessionDescriptionObserverImpl(
|
||||
Future<SessionDescriptionWrapper>* settable_future)
|
||||
: settable_future_(settable_future) {}
|
||||
~CreateSessionDescriptionObserverImpl() override = default;
|
||||
|
||||
// webrtc::CreateSessionDescriptionObserver
|
||||
void OnSuccess(webrtc::SessionDescriptionInterface* desc) override {
|
||||
settable_future_->Set(SessionDescriptionWrapper{desc});
|
||||
}
|
||||
|
||||
void OnFailure(webrtc::RTCError error) override {
|
||||
NEARBY_LOG(ERROR, "Error when creating session description: %s",
|
||||
error.message());
|
||||
settable_future_->SetException({Exception::kFailed});
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Future<SessionDescriptionWrapper>> settable_future_;
|
||||
};
|
||||
|
||||
class SetSessionDescriptionObserverImpl
|
||||
: public webrtc::SetSessionDescriptionObserver {
|
||||
public:
|
||||
explicit SetSessionDescriptionObserverImpl(Future<bool>* settable_future)
|
||||
: settable_future_(settable_future) {}
|
||||
|
||||
void OnSuccess() override { settable_future_->Set(true); }
|
||||
|
||||
void OnFailure(webrtc::RTCError error) override {
|
||||
NEARBY_LOG(ERROR, "Error when setting session description: %s",
|
||||
error.message());
|
||||
settable_future_->SetException({Exception::kFailed});
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Future<bool>> settable_future_;
|
||||
};
|
||||
|
||||
using PeerConnectionState =
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState;
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<ConnectionFlow> ConnectionFlow::Create(
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener,
|
||||
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));
|
||||
DataChannelListener data_channel_listener, WebRtcMedium& webrtc_medium) {
|
||||
auto connection_flow = absl::WrapUnique(
|
||||
new ConnectionFlow(std::move(local_ice_candidate_listener),
|
||||
std::move(data_channel_listener)));
|
||||
if (connection_flow->InitPeerConnection(webrtc_medium)) {
|
||||
return connection_flow;
|
||||
}
|
||||
@@ -42,75 +96,149 @@ std::unique_ptr<ConnectionFlow> ConnectionFlow::Create(
|
||||
|
||||
ConnectionFlow::ConnectionFlow(
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener,
|
||||
SingleThreadExecutor* single_threaded_executor)
|
||||
DataChannelListener data_channel_listener)
|
||||
: 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>();
|
||||
peer_connection_observer_(this, std::move(local_ice_candidate_listener)) {
|
||||
}
|
||||
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface>
|
||||
ConnectionFlow::CreateAnswer() {
|
||||
ConnectionFlow::~ConnectionFlow() { Close(); }
|
||||
|
||||
SessionDescriptionWrapper ConnectionFlow::CreateOffer() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
if (!TransitionState(State::kInitialized, State::kCreatingOffer)) {
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
return std::unique_ptr<webrtc::SessionDescriptionInterface>();
|
||||
webrtc::DataChannelInit data_channel_init;
|
||||
data_channel_init.reliable = true;
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel =
|
||||
peer_connection_->CreateDataChannel(kDataChannelName, &data_channel_init);
|
||||
data_channel->RegisterObserver(CreateDataChannelObserver(data_channel));
|
||||
|
||||
auto success_future = new Future<SessionDescriptionWrapper>();
|
||||
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
|
||||
rtc::scoped_refptr<CreateSessionDescriptionObserverImpl> observer =
|
||||
new rtc::RefCountedObject<CreateSessionDescriptionObserverImpl>(
|
||||
success_future);
|
||||
peer_connection_->CreateOffer(observer, options);
|
||||
|
||||
ExceptionOr<SessionDescriptionWrapper> result = success_future->Get(kTimeout);
|
||||
if (result.ok() &&
|
||||
TransitionState(State::kCreatingOffer, State::kWaitingForAnswer)) {
|
||||
return std::move(result.result());
|
||||
}
|
||||
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
bool ConnectionFlow::SetLocalSessionDescription(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> sdp) {
|
||||
SessionDescriptionWrapper ConnectionFlow::CreateAnswer() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
if (!TransitionState(State::kReceivedOffer, State::kCreatingAnswer)) {
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
return false;
|
||||
auto success_future = new Future<SessionDescriptionWrapper>();
|
||||
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions options;
|
||||
rtc::scoped_refptr<CreateSessionDescriptionObserverImpl> observer =
|
||||
new rtc::RefCountedObject<CreateSessionDescriptionObserverImpl>(
|
||||
success_future);
|
||||
peer_connection_->CreateAnswer(observer, options);
|
||||
|
||||
ExceptionOr<SessionDescriptionWrapper> result = success_future->Get(kTimeout);
|
||||
if (result.ok() &&
|
||||
TransitionState(State::kCreatingAnswer, State::kWaitingToConnect)) {
|
||||
return std::move(result.result());
|
||||
}
|
||||
|
||||
return SessionDescriptionWrapper();
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnOfferReceived(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> offer) {
|
||||
bool ConnectionFlow::SetLocalSessionDescription(SessionDescriptionWrapper sdp) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
if (!sdp.IsValid()) return false;
|
||||
|
||||
auto success_future = new Future<bool>();
|
||||
rtc::scoped_refptr<SetSessionDescriptionObserverImpl> observer =
|
||||
new rtc::RefCountedObject<SetSessionDescriptionObserverImpl>(
|
||||
success_future);
|
||||
|
||||
peer_connection_->SetLocalDescription(observer, sdp.Release());
|
||||
|
||||
ExceptionOr<bool> result = success_future->Get(kTimeout);
|
||||
return result.ok() && result.result();
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnAnswerReceived(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> answer) {
|
||||
bool ConnectionFlow::SetRemoteSessionDescription(
|
||||
SessionDescriptionWrapper sdp) {
|
||||
if (!sdp.IsValid()) return false;
|
||||
|
||||
auto success_future = new Future<bool>();
|
||||
rtc::scoped_refptr<SetSessionDescriptionObserverImpl> observer =
|
||||
new rtc::RefCountedObject<SetSessionDescriptionObserverImpl>(
|
||||
success_future);
|
||||
|
||||
peer_connection_->SetRemoteDescription(observer, sdp.Release());
|
||||
|
||||
ExceptionOr<bool> result = success_future->Get(kTimeout);
|
||||
return result.ok() && result.result();
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnOfferReceived(SessionDescriptionWrapper offer) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
if (!TransitionState(State::kInitialized, State::kReceivedOffer)) {
|
||||
return false;
|
||||
}
|
||||
return SetRemoteSessionDescription(std::move(offer));
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnAnswerReceived(SessionDescriptionWrapper answer) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!TransitionState(State::kWaitingForAnswer, State::kWaitingToConnect)) {
|
||||
return false;
|
||||
}
|
||||
return SetRemoteSessionDescription(std::move(answer));
|
||||
}
|
||||
|
||||
bool ConnectionFlow::OnRemoteIceCandidatesReceived(
|
||||
std::vector<webrtc::IceCandidateInterface*> ice_candidates) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
if (state_ == State::kEnded) {
|
||||
NEARBY_LOG(WARNING,
|
||||
"You cannot add ice candidates to a disconnected session.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (state_ != State::kWaitingToConnect && state_ != State::kConnected) {
|
||||
cached_remote_ice_candidates_.insert(
|
||||
cached_remote_ice_candidates_.end(),
|
||||
std::make_move_iterator(ice_candidates.begin()),
|
||||
std::make_move_iterator(ice_candidates.end()));
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto&& ice_candidate : ice_candidates) {
|
||||
if (!peer_connection_->AddIceCandidate(ice_candidate.get())) {
|
||||
NEARBY_LOG(WARNING, "Unable to add remote ice candidate.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
api::ListenableFuture<rtc::scoped_refptr<webrtc::DataChannelInterface>>*
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>>*
|
||||
ConnectionFlow::GetDataChannel() {
|
||||
return static_cast<
|
||||
api::ListenableFuture<rtc::scoped_refptr<webrtc::DataChannelInterface>>*>(
|
||||
&data_channel_future_);
|
||||
return &data_channel_future_;
|
||||
}
|
||||
|
||||
bool ConnectionFlow::Close() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// TODO(bfranz): Implement
|
||||
|
||||
return false;
|
||||
return CloseLocked();
|
||||
}
|
||||
|
||||
bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) {
|
||||
@@ -128,20 +256,96 @@ bool ConnectionFlow::InitPeerConnection(WebRtcMedium& webrtc_medium) {
|
||||
}
|
||||
|
||||
void ConnectionFlow::OnSignalingStable() {
|
||||
// TODO(bfranz): Implement
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (state_ != State::kWaitingToConnect && state_ != State::kConnected) return;
|
||||
|
||||
for (auto&& ice_candidate : cached_remote_ice_candidates_) {
|
||||
if (!peer_connection_->AddIceCandidate(ice_candidate.get())) {
|
||||
NEARBY_LOG(WARNING, "Unable to add remote ice candidate.");
|
||||
}
|
||||
}
|
||||
cached_remote_ice_candidates_.clear();
|
||||
}
|
||||
|
||||
void ConnectionFlow::ProcessOnPeerConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state) {
|
||||
// TODO(bfranz): Implement
|
||||
if (new_state == PeerConnectionState::kClosed ||
|
||||
new_state == PeerConnectionState::kFailed ||
|
||||
new_state == PeerConnectionState::kDisconnected) {
|
||||
MutexLock lock(&mutex_);
|
||||
CloseAndNotifyLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionFlow::ProcessDataChannelConnected() {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOG(INFO, "Data channel state changed to connected.");
|
||||
if (!TransitionState(State::kWaitingToConnect, State::kConnected))
|
||||
CloseAndNotifyLocked();
|
||||
}
|
||||
|
||||
webrtc::DataChannelObserver* ConnectionFlow::CreateDataChannelObserver(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel) {
|
||||
// TODO(bfranz): Implement
|
||||
if (!data_channel_observer_) {
|
||||
auto state_change_callback = [this,
|
||||
data_channel{std::move(data_channel)}]() {
|
||||
if (data_channel->state() ==
|
||||
webrtc::DataChannelInterface::DataState::kOpen) {
|
||||
data_channel_future_.Set(std::move(data_channel));
|
||||
OffloadFromSignalingThread([this]() { ProcessDataChannelConnected(); });
|
||||
} else if (data_channel->state() ==
|
||||
webrtc::DataChannelInterface::DataState::kClosed) {
|
||||
data_channel->UnregisterObserver();
|
||||
OffloadFromSignalingThread([this]() {
|
||||
MutexLock lock(&mutex_);
|
||||
CloseAndNotifyLocked();
|
||||
});
|
||||
}
|
||||
};
|
||||
data_channel_observer_ = absl::make_unique<DataChannelObserverImpl>(
|
||||
&data_channel_listener_, std::move(state_change_callback));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return reinterpret_cast<webrtc::DataChannelObserver*>(
|
||||
data_channel_observer_.get());
|
||||
}
|
||||
|
||||
bool ConnectionFlow::TransitionState(State current_state, State new_state) {
|
||||
if (current_state != state_) {
|
||||
NEARBY_LOG(
|
||||
WARNING,
|
||||
"Invalid state transition to %d: current state is %d but expected %d.",
|
||||
new_state, state_, current_state);
|
||||
return false;
|
||||
}
|
||||
state_ = new_state;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionFlow::CloseAndNotifyLocked() {
|
||||
if (CloseLocked()) {
|
||||
data_channel_listener_.data_channel_closed_cb();
|
||||
}
|
||||
}
|
||||
|
||||
bool ConnectionFlow::CloseLocked() {
|
||||
if (state_ == State::kEnded) {
|
||||
return false;
|
||||
}
|
||||
state_ = State::kEnded;
|
||||
|
||||
data_channel_future_.SetException({Exception::kInterrupted});
|
||||
peer_connection_->Close();
|
||||
data_channel_observer_.reset();
|
||||
NEARBY_LOG(INFO, "Closed WebRTC connection.");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionFlow::OffloadFromSignalingThread(Runnable runnable) {
|
||||
single_threaded_signaling_offloader_.Execute(std::move(runnable));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/data_channel_listener.h"
|
||||
#include "core_v2/internal/mediums/webrtc/data_channel_observer_impl.h"
|
||||
#include "core_v2/internal/mediums/webrtc/local_ice_candidate_listener.h"
|
||||
#include "core_v2/internal/mediums/webrtc/peer_connection_observer_impl.h"
|
||||
#include "core_v2/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "platform_v2/base/runnable.h"
|
||||
#include "platform_v2/public/future.h"
|
||||
#include "platform_v2/public/single_thread_executor.h"
|
||||
@@ -70,73 +72,98 @@ class ConnectionFlow {
|
||||
// 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;
|
||||
DataChannelListener data_channel_listener, WebRtcMedium& webrtc_medium);
|
||||
~ConnectionFlow();
|
||||
|
||||
// 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_);
|
||||
SessionDescriptionWrapper 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_);
|
||||
SessionDescriptionWrapper 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)
|
||||
bool SetLocalSessionDescription(SessionDescriptionWrapper sdp)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Invoked when an offer was received from a remote; this will set the remote
|
||||
// session description on the peer connection.
|
||||
void OnOfferReceived(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> offer)
|
||||
// session description on the peer connection. Returns true if the offer was
|
||||
// successfully set as remote session description.
|
||||
bool OnOfferReceived(SessionDescriptionWrapper offer)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Invoked when an answer was received from a remote; this will set the remote
|
||||
// session description on the peer connection.
|
||||
void OnAnswerReceived(
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> answer)
|
||||
// session description on the peer connection. Returns true if the offer was
|
||||
// successfully set as remote session description.
|
||||
bool OnAnswerReceived(SessionDescriptionWrapper answer)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Invoked when an ice candidate was received from a remote; this will add the
|
||||
// ice candidate to the peer connection if ready or cache it otherwise.
|
||||
bool OnRemoteIceCandidatesReceived(
|
||||
std::vector<webrtc::IceCandidateInterface*> ice_candidates)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
ice_candidates) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
// Get a future for the data channel.
|
||||
api::ListenableFuture<rtc::scoped_refptr<webrtc::DataChannelInterface>>*
|
||||
GetDataChannel();
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>>* GetDataChannel();
|
||||
// Close the peer connection and data channel.
|
||||
bool Close() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Invoked when the peer connection indicates that signaling is stable.
|
||||
void OnSignalingStable();
|
||||
void OnSignalingStable() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
webrtc::DataChannelObserver* CreateDataChannelObserver(
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
|
||||
// Invoked upon changes in the state of peer connection, e.g. react to
|
||||
// disconnect.
|
||||
void ProcessOnPeerConnectionChange(
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state);
|
||||
webrtc::PeerConnectionInterface::PeerConnectionState new_state)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
kInitialized,
|
||||
kCreatingOffer,
|
||||
kWaitingForAnswer,
|
||||
kReceivedOffer,
|
||||
kCreatingAnswer,
|
||||
kWaitingToConnect,
|
||||
kConnected,
|
||||
kEnded,
|
||||
};
|
||||
|
||||
ConnectionFlow(LocalIceCandidateListener local_ice_candidate_listener,
|
||||
DataChannelListener data_channel_listener,
|
||||
SingleThreadExecutor* single_threaded_executor);
|
||||
DataChannelListener data_channel_listener);
|
||||
|
||||
// TODO(bfranz): Consider whether this needs to be configurable per platform
|
||||
static constexpr absl::Duration kTimeout = absl::Milliseconds(250);
|
||||
|
||||
bool InitPeerConnection(WebRtcMedium& webrtc_medium);
|
||||
|
||||
bool TransitionState(State current_state, State new_state)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
bool SetRemoteSessionDescription(SessionDescriptionWrapper sdp);
|
||||
|
||||
void ProcessDataChannelConnected() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
void CloseAndNotifyLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
bool CloseLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
void OffloadFromSignalingThread(Runnable runnable);
|
||||
|
||||
Mutex mutex_;
|
||||
|
||||
State state_ ABSL_GUARDED_BY(mutex_) = State::kInitialized;
|
||||
DataChannelListener data_channel_listener_;
|
||||
|
||||
std::unique_ptr<DataChannelObserverImpl> data_channel_observer_;
|
||||
|
||||
Future<rtc::scoped_refptr<webrtc::DataChannelInterface>> data_channel_future_;
|
||||
|
||||
PeerConnectionObserverImpl peer_connection_observer_;
|
||||
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection_;
|
||||
|
||||
Mutex mutex_;
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>>
|
||||
cached_remote_ice_candidates_ ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
SingleThreadExecutor single_threaded_signaling_offloader_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -15,10 +15,18 @@
|
||||
#include "core_v2/internal/mediums/webrtc/connection_flow.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/session_description_wrapper.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/webrtc.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/time/time.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
#include "webrtc/api/jsep.h"
|
||||
#include "webrtc/api/rtc_error.h"
|
||||
#include "webrtc/api/scoped_refptr.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -26,17 +34,159 @@ namespace connections {
|
||||
namespace mediums {
|
||||
namespace {
|
||||
|
||||
TEST(ConnectionFlowTest, Create) {
|
||||
LocalIceCandidateListener local_ice_candidate_listener;
|
||||
DataChannelListener data_channel_listener;
|
||||
SingleThreadExecutor executor;
|
||||
std::unique_ptr<webrtc::IceCandidateInterface> CopyCandidate(
|
||||
const webrtc::IceCandidateInterface* candidate) {
|
||||
return webrtc::CreateIceCandidate(candidate->sdp_mid(),
|
||||
candidate->sdp_mline_index(),
|
||||
candidate->candidate());
|
||||
}
|
||||
|
||||
// TODO(bfranz) - Add test that deterministically sends answerer_ice_candidates
|
||||
// before answer is sent.
|
||||
TEST(ConnectionFlowTest, SuccessfulOfferAnswerFlow) {
|
||||
WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer;
|
||||
|
||||
Future<ByteArray> message_received_future;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer, answerer;
|
||||
|
||||
// Send Ice Candidates immediately when you retrieve them
|
||||
offerer = ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
[&answerer](const webrtc::IceCandidateInterface* candidate) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> vec;
|
||||
vec.push_back(CopyCandidate(candidate));
|
||||
// The callback might be alive while the objects in test are
|
||||
// destroyed.
|
||||
if (answerer)
|
||||
answerer->OnRemoteIceCandidatesReceived(std::move(vec));
|
||||
}},
|
||||
DataChannelListener(), webrtc_medium_offerer);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
answerer = ConnectionFlow::Create(
|
||||
{.local_ice_candidate_found_cb =
|
||||
[&offerer](const webrtc::IceCandidateInterface* candidate) {
|
||||
std::vector<std::unique_ptr<webrtc::IceCandidateInterface>> vec;
|
||||
vec.push_back(CopyCandidate(candidate));
|
||||
// The callback might be alive while the objects in test are
|
||||
// destroyed.
|
||||
if (offerer)
|
||||
offerer->OnRemoteIceCandidatesReceived(std::move(vec));
|
||||
}},
|
||||
{.data_channel_message_received_cb =
|
||||
[&message_received_future](ByteArray bytes) {
|
||||
message_received_future.Set(std::move(bytes));
|
||||
}},
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
// Create and send offer
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
EXPECT_TRUE(answerer->OnOfferReceived(offer));
|
||||
EXPECT_TRUE(offerer->SetLocalSessionDescription(std::move(offer)));
|
||||
|
||||
// Create and send answer
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer);
|
||||
EXPECT_TRUE(offerer->OnAnswerReceived(answer));
|
||||
EXPECT_TRUE(answerer->SetLocalSessionDescription(std::move(answer)));
|
||||
|
||||
// Retrieve Data Channels
|
||||
ExceptionOr<rtc::scoped_refptr<webrtc::DataChannelInterface>>
|
||||
offerer_channel = offerer->GetDataChannel()->Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(offerer_channel.ok());
|
||||
ExceptionOr<rtc::scoped_refptr<webrtc::DataChannelInterface>>
|
||||
answerer_channel = answerer->GetDataChannel()->Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(answerer_channel.ok());
|
||||
|
||||
// Send message on data channel
|
||||
const char message[] = "Test";
|
||||
offerer_channel.result()->Send(webrtc::DataBuffer(message));
|
||||
ExceptionOr<ByteArray> received_message =
|
||||
message_received_future.Get(absl::Seconds(1));
|
||||
EXPECT_TRUE(received_message.ok());
|
||||
EXPECT_EQ(received_message.result(), ByteArray{message});
|
||||
}
|
||||
|
||||
TEST(ConnectionFlowTest, CreateAnswerBeforeOfferReceived) {
|
||||
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);
|
||||
std::unique_ptr<ConnectionFlow> answerer = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), webrtc_medium);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
EXPECT_NE(connection_flow, nullptr);
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
EXPECT_FALSE(answer.IsValid());
|
||||
}
|
||||
|
||||
TEST(ConnectionFlowTest, SetAnswerBeforeOffer) {
|
||||
WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer =
|
||||
ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(),
|
||||
webrtc_medium_offerer);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
std::unique_ptr<ConnectionFlow> answerer =
|
||||
ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(),
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
// Did not set offer as local session description
|
||||
EXPECT_TRUE(answerer->OnOfferReceived(offer));
|
||||
|
||||
SessionDescriptionWrapper answer = answerer->CreateAnswer();
|
||||
EXPECT_EQ(answer.GetType(), webrtc::SdpType::kAnswer);
|
||||
EXPECT_FALSE(offerer->OnAnswerReceived(answer));
|
||||
}
|
||||
|
||||
TEST(ConnectionFlowTest, CannotCreateOfferAfterClose) {
|
||||
WebRtcMedium webrtc_medium;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), webrtc_medium);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
|
||||
EXPECT_TRUE(offerer->Close());
|
||||
|
||||
EXPECT_FALSE(offerer->CreateOffer().IsValid());
|
||||
}
|
||||
|
||||
TEST(ConnectionFlowTest, CannotSetSessionDescriptionAfterClose) {
|
||||
WebRtcMedium webrtc_medium;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer = ConnectionFlow::Create(
|
||||
LocalIceCandidateListener(), DataChannelListener(), webrtc_medium);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
|
||||
EXPECT_TRUE(offerer->Close());
|
||||
|
||||
EXPECT_FALSE(offerer->SetLocalSessionDescription(offer));
|
||||
}
|
||||
|
||||
TEST(ConnectionFlowTest, CannotReceiveOfferAfterClose) {
|
||||
WebRtcMedium webrtc_medium_offerer, webrtc_medium_answerer;
|
||||
|
||||
std::unique_ptr<ConnectionFlow> offerer =
|
||||
ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(),
|
||||
webrtc_medium_offerer);
|
||||
ASSERT_NE(offerer, nullptr);
|
||||
std::unique_ptr<ConnectionFlow> answerer =
|
||||
ConnectionFlow::Create(LocalIceCandidateListener(), DataChannelListener(),
|
||||
webrtc_medium_answerer);
|
||||
ASSERT_NE(answerer, nullptr);
|
||||
|
||||
EXPECT_TRUE(answerer->Close());
|
||||
|
||||
SessionDescriptionWrapper offer = offerer->CreateOffer();
|
||||
EXPECT_EQ(offer.GetType(), webrtc::SdpType::kOffer);
|
||||
|
||||
EXPECT_FALSE(answerer->OnOfferReceived(offer));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -28,8 +28,8 @@ 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>();
|
||||
std::function<void(const ByteArray&)> data_channel_message_received_cb =
|
||||
DefaultCallback<const ByteArray&>();
|
||||
|
||||
// Called when the data channel indicates that the buffered amount has
|
||||
// changed.
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "core_v2/internal/mediums/webrtc/data_channel_observer_impl.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
DataChannelObserverImpl::DataChannelObserverImpl(
|
||||
DataChannelListener* data_channel_listener,
|
||||
DataChannelStateChangeCallback callback)
|
||||
: data_channel_listener_(data_channel_listener),
|
||||
state_change_callback_(std::move(callback)) {}
|
||||
|
||||
void DataChannelObserverImpl::OnStateChange() { state_change_callback_(); }
|
||||
|
||||
void DataChannelObserverImpl::OnMessage(const webrtc::DataBuffer& buffer) {
|
||||
data_channel_listener_->data_channel_message_received_cb(
|
||||
ByteArray(buffer.data.data<char>(), buffer.size()));
|
||||
}
|
||||
|
||||
void DataChannelObserverImpl::OnBufferedAmountChange(uint64_t sent_data_size) {
|
||||
data_channel_listener_->data_channel_buffered_amount_changed_cb();
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,35 @@
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/data_channel_listener.h"
|
||||
#include "webrtc/api/data_channel_interface.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class DataChannelObserverImpl : public webrtc::DataChannelObserver {
|
||||
public:
|
||||
using DataChannelStateChangeCallback = std::function<void()>;
|
||||
|
||||
~DataChannelObserverImpl() override = default;
|
||||
DataChannelObserverImpl(DataChannelListener* data_channel_listener,
|
||||
DataChannelStateChangeCallback callback);
|
||||
|
||||
// webrtc::DataChannelObserver:
|
||||
void OnStateChange() override;
|
||||
void OnMessage(const webrtc::DataBuffer& buffer) override;
|
||||
void OnBufferedAmountChange(uint64_t sent_data_size) override;
|
||||
|
||||
private:
|
||||
DataChannelListener* data_channel_listener_;
|
||||
DataChannelStateChangeCallback state_change_callback_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_DATA_CHANNEL_OBSERVER_IMPL_H_
|
||||
@@ -24,11 +24,9 @@ namespace mediums {
|
||||
|
||||
PeerConnectionObserverImpl::PeerConnectionObserverImpl(
|
||||
ConnectionFlow* connection_flow,
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
SingleThreadExecutor* executor)
|
||||
LocalIceCandidateListener local_ice_candidate_listener)
|
||||
: connection_flow_(connection_flow),
|
||||
local_ice_candidate_listener_(std::move(local_ice_candidate_listener)),
|
||||
single_threaded_signaling_offloader_(executor) {}
|
||||
local_ice_candidate_listener_(std::move(local_ice_candidate_listener)) {}
|
||||
|
||||
void PeerConnectionObserverImpl::OnIceCandidate(
|
||||
const webrtc::IceCandidateInterface* candidate) {
|
||||
@@ -73,7 +71,7 @@ void PeerConnectionObserverImpl ::OnRenegotiationNeeded() {
|
||||
}
|
||||
|
||||
void PeerConnectionObserverImpl::OffloadFromSignalingThread(Runnable runnable) {
|
||||
single_threaded_signaling_offloader_->Execute(std::move(runnable));
|
||||
single_threaded_signaling_offloader_.Execute(std::move(runnable));
|
||||
}
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -31,8 +31,7 @@ class PeerConnectionObserverImpl : public webrtc::PeerConnectionObserver {
|
||||
~PeerConnectionObserverImpl() override = default;
|
||||
PeerConnectionObserverImpl(
|
||||
ConnectionFlow* connection_flow,
|
||||
LocalIceCandidateListener local_ice_candidate_listener,
|
||||
SingleThreadExecutor* executor);
|
||||
LocalIceCandidateListener local_ice_candidate_listener);
|
||||
|
||||
// webrtc::PeerConnectionObserver:
|
||||
void OnIceCandidate(const webrtc::IceCandidateInterface* candidate) override;
|
||||
@@ -51,7 +50,7 @@ class PeerConnectionObserverImpl : public webrtc::PeerConnectionObserver {
|
||||
|
||||
ConnectionFlow* connection_flow_;
|
||||
LocalIceCandidateListener local_ice_candidate_listener_;
|
||||
SingleThreadExecutor* single_threaded_signaling_offloader_;
|
||||
SingleThreadExecutor single_threaded_signaling_offloader_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -46,6 +46,8 @@ PeerId PeerId::FromSeed(const ByteArray& seed) {
|
||||
return PeerId(BytesToStringUppercase(hashed_seed));
|
||||
}
|
||||
|
||||
bool PeerId::IsValid() const { return !id_.empty(); }
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
|
||||
@@ -26,19 +26,22 @@ namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
// PeerId is used as an identifier to exchange SDP messages to establish WebRTC
|
||||
// p2p connection.
|
||||
// p2p connection. An empty PeerId is considered to be invalid.
|
||||
class PeerId {
|
||||
public:
|
||||
explicit PeerId(const string& id) : id_(id) {}
|
||||
PeerId() = default;
|
||||
explicit PeerId(const std::string& id) : id_(id) {}
|
||||
~PeerId() = default;
|
||||
|
||||
static PeerId FromRandom();
|
||||
static PeerId FromSeed(const ByteArray& seed);
|
||||
|
||||
const string& GetId() const { return id_; }
|
||||
bool IsValid() const;
|
||||
|
||||
const std::string& GetId() const { return id_; }
|
||||
|
||||
private:
|
||||
const string id_;
|
||||
std::string id_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_
|
||||
|
||||
#include "webrtc/api/peer_connection_interface.h"
|
||||
|
||||
// Wrapper object around SessionDescriptionInterface*.
|
||||
// This object owns the SessionDescriptionInterface* unless Release() has been
|
||||
// called.
|
||||
class SessionDescriptionWrapper {
|
||||
public:
|
||||
SessionDescriptionWrapper() = default;
|
||||
explicit SessionDescriptionWrapper(webrtc::SessionDescriptionInterface* sdp)
|
||||
: impl_(sdp) {}
|
||||
|
||||
// Copy constructor that performs a deep copy, i.e. creates a new
|
||||
// SessionDescriptionInterface.
|
||||
SessionDescriptionWrapper(const SessionDescriptionWrapper& sdp) {
|
||||
if (sdp.IsValid()) {
|
||||
impl_ = webrtc::CreateSessionDescription(sdp.GetType(), sdp.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
SessionDescriptionWrapper(SessionDescriptionWrapper&&) = default;
|
||||
SessionDescriptionWrapper& operator=(SessionDescriptionWrapper&&) = default;
|
||||
|
||||
// Release the ownership of the SessionDescriptionInterface*.
|
||||
webrtc::SessionDescriptionInterface* Release() { return impl_.release(); }
|
||||
|
||||
// Returns a string representation of the sdp. Only call this, if IsValid() is
|
||||
// true.
|
||||
std::string ToString() const {
|
||||
std::string str;
|
||||
impl_->ToString(&str);
|
||||
return str;
|
||||
}
|
||||
|
||||
// Returns the SdpType of the SessionDescriptionInterface. Only call this, if
|
||||
// IsValid() is true.
|
||||
webrtc::SdpType GetType() const { return impl_->GetType(); }
|
||||
|
||||
const webrtc::SessionDescriptionInterface& GetSdp() { return *impl_; }
|
||||
|
||||
// Return whether this object currently holds a SessionDescriptionInterface.
|
||||
bool IsValid() const { return impl_ != nullptr; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<webrtc::SessionDescriptionInterface> impl_;
|
||||
};
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_SESSION_DESCRIPTION_WRAPPER_H_
|
||||
@@ -54,7 +54,7 @@ Exception WebRtcSocket::OutputStreamImpl::Close() {
|
||||
|
||||
// WebRtcSocket
|
||||
WebRtcSocket::WebRtcSocket(
|
||||
const string& name,
|
||||
const std::string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel)
|
||||
: name_(name), data_channel_(std::move(data_channel)) {}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ constexpr int kMaxDataSize = 1 * 1024 * 1024;
|
||||
// which could lead to data loss.
|
||||
class WebRtcSocket : public Socket {
|
||||
public:
|
||||
WebRtcSocket(const string& name,
|
||||
WebRtcSocket(const std::string& name,
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel);
|
||||
~WebRtcSocket() override = default;
|
||||
|
||||
@@ -92,7 +92,7 @@ class WebRtcSocket : public Socket {
|
||||
bool SendMessage(const ByteArray& data);
|
||||
void BlockUntilSufficientSpaceInBuffer(int length);
|
||||
|
||||
string name_;
|
||||
std::string name_;
|
||||
rtc::scoped_refptr<webrtc::DataChannelInterface> data_channel_;
|
||||
|
||||
Pipe pipe_;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
class WebRtcSocketWrapper final {
|
||||
public:
|
||||
WebRtcSocketWrapper() = default;
|
||||
WebRtcSocketWrapper(const WebRtcSocketWrapper&) = default;
|
||||
WebRtcSocketWrapper& operator=(const WebRtcSocketWrapper&) = default;
|
||||
explicit WebRtcSocketWrapper(std::unique_ptr<WebRtcSocket> socket)
|
||||
: impl_(socket.release()) {}
|
||||
~WebRtcSocketWrapper() = default;
|
||||
|
||||
InputStream& GetInputStream() { return impl_->GetInputStream(); }
|
||||
|
||||
OutputStream& GetOutputStream() { return impl_->GetOutputStream(); }
|
||||
|
||||
void NotifyDataChannelMsgReceived(const ByteArray& message) {
|
||||
impl_->NotifyDataChannelMsgReceived(message);
|
||||
}
|
||||
|
||||
void NotifyDataChannelBufferedAmountChanged() {
|
||||
impl_->NotifyDataChannelBufferedAmountChanged();
|
||||
}
|
||||
|
||||
void Close() { return impl_->Close(); }
|
||||
|
||||
bool IsValid() const { return impl_ != nullptr; }
|
||||
|
||||
WebRtcSocket& GetImpl() { return *impl_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<WebRtcSocket> impl_;
|
||||
};
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WEBRTC_WEBRTC_SOCKET_WRAPPER_H_
|
||||
@@ -0,0 +1,121 @@
|
||||
#include "core_v2/internal/mediums/webrtc.h"
|
||||
|
||||
#include "core_v2/internal/mediums/webrtc/webrtc_socket_wrapper.h"
|
||||
#include "platform_v2/base/listeners.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace mediums {
|
||||
|
||||
namespace {
|
||||
|
||||
// Basic test to check that device is accepting connections when initialized.
|
||||
TEST(WebRtcTest, NotAcceptingConnections) {
|
||||
WebRtc webrtc;
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
EXPECT_FALSE(webrtc.IsAcceptingConnections());
|
||||
}
|
||||
|
||||
// Tests the flow when the device tries to accept connections twice. In this
|
||||
// case, only the first call is successful and subsequent calls fail.
|
||||
TEST(WebRtcTest, StartAcceptingConnectionTwice) {
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
|
||||
WebRtc webrtc;
|
||||
PeerId self_id("peer_id");
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
ASSERT_TRUE(webrtc.StartAcceptingConnections(
|
||||
self_id, {mock_accepted_callback_.AsStdFunction()}));
|
||||
EXPECT_FALSE(webrtc.StartAcceptingConnections(
|
||||
self_id, {mock_accepted_callback_.AsStdFunction()}));
|
||||
EXPECT_TRUE(webrtc.IsAcceptingConnections());
|
||||
}
|
||||
|
||||
// Tests the flow when the device tries to connect but the data channel times
|
||||
// out.
|
||||
TEST(WebRtcTest, Connect_DataChannelTimeOut) {
|
||||
WebRtc webrtc;
|
||||
PeerId peer_id("peer_id");
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
WebRtcSocketWrapper wrapper_1 = webrtc.Connect(peer_id);
|
||||
EXPECT_FALSE(wrapper_1.IsValid());
|
||||
|
||||
EXPECT_TRUE(
|
||||
webrtc.StartAcceptingConnections(peer_id, AcceptedConnectionCallback()));
|
||||
}
|
||||
|
||||
// Tests the flow when the device calls Connect() after calling
|
||||
// StartAcceptingConnections() without StopAcceptingConnections().
|
||||
TEST(WebRtcTest, StartAcceptingConnection_ThenConnect) {
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
|
||||
WebRtc webrtc;
|
||||
PeerId self_id("peer_id");
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
ASSERT_TRUE(webrtc.StartAcceptingConnections(
|
||||
self_id, {mock_accepted_callback_.AsStdFunction()}));
|
||||
WebRtcSocketWrapper wrapper = webrtc.Connect(PeerId("random_peer_id"));
|
||||
EXPECT_TRUE(webrtc.IsAcceptingConnections());
|
||||
EXPECT_FALSE(wrapper.IsValid());
|
||||
EXPECT_FALSE(webrtc.StartAcceptingConnections(
|
||||
self_id, {mock_accepted_callback_.AsStdFunction()}));
|
||||
}
|
||||
|
||||
// Tests the flow when the device calls StartAcceptingConnections but the medium
|
||||
// is closed before a peer device can connect to it.
|
||||
TEST(WebRtcTest, StartAndStopAcceptingConnections) {
|
||||
using MockAcceptedCallback =
|
||||
testing::MockFunction<void(WebRtcSocketWrapper socket)>;
|
||||
testing::StrictMock<MockAcceptedCallback> mock_accepted_callback_;
|
||||
|
||||
WebRtc webrtc;
|
||||
PeerId self_id("peer_id");
|
||||
|
||||
ASSERT_TRUE(webrtc.IsAvailable());
|
||||
ASSERT_TRUE(webrtc.StartAcceptingConnections(
|
||||
self_id, {mock_accepted_callback_.AsStdFunction()}));
|
||||
webrtc.StopAcceptingConnections();
|
||||
EXPECT_FALSE(webrtc.IsAcceptingConnections());
|
||||
}
|
||||
|
||||
// Tests the flow when the device calls StartAcceptingConnections() after
|
||||
// calling Connect() without disconnecting in between.
|
||||
TEST(WebRtcTest, Connect_ThenStartAcceptingConnections) {
|
||||
// TODO(himanshujaju) - Complete the test.
|
||||
}
|
||||
|
||||
// Tests the flow when the device tries to connect to two different peers
|
||||
// without disconnecting in between.
|
||||
TEST(WebRtcTest, ConnectTwice) {
|
||||
// TODO(himanshujaju) - Complete the test.
|
||||
}
|
||||
|
||||
// Tests the flow when the two devices exchange SDP messages and connect to each
|
||||
// other but disconnect before being able to send/receive the actual data.
|
||||
TEST(WebRtcTest, ConnectBothDevicesAndAbort) {
|
||||
// TODO(himanshujaju) - Complete the test.
|
||||
}
|
||||
|
||||
// Tests the flow when the two devices exchange SDP messages and connect to each
|
||||
// other and the actual data is exchanged successfully between the devices.
|
||||
TEST(WebRtcTest, ConnectBothDevicesAndSendData) {
|
||||
// TODO(himanshujaju) - Complete the test.
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace mediums
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,230 @@
|
||||
#include "core_v2/internal/mediums/wifi_lan.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
bool WifiLan::IsAvailable() const {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAvailableLocked();
|
||||
}
|
||||
|
||||
bool WifiLan::IsAvailableLocked() const { return medium_.IsValid(); }
|
||||
|
||||
bool WifiLan::StartAdvertising(const std::string& service_id,
|
||||
const std::string& wifi_lan_service_info_name) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (wifi_lan_service_info_name.empty()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to turn on WifiLan advertising. Empty service info name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't turn on WifiLan advertising. WifiLan is not available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartAdvertising(service_id, wifi_lan_service_info_name)) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Failed to turn on WifiLan advertising with service info name=%s",
|
||||
wifi_lan_service_info_name.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "Turned on WifiLan advertising with service info name=%s",
|
||||
wifi_lan_service_info_name.c_str());
|
||||
advertising_info_.service_id = service_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WifiLan::StopAdvertising(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAdvertisingLocked()) {
|
||||
NEARBY_LOG(INFO, "Can't turn off WifiLan advertising; it is already off");
|
||||
return;
|
||||
}
|
||||
|
||||
medium_.StopAdvertising(advertising_info_.service_id);
|
||||
// Reset our bundle of advertising state to mark that we're no longer
|
||||
// advertising.
|
||||
advertising_info_.Clear();
|
||||
}
|
||||
|
||||
bool WifiLan::IsAdvertising() {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAdvertisingLocked();
|
||||
}
|
||||
|
||||
bool WifiLan::IsAdvertisingLocked() {
|
||||
return !advertising_info_.Empty();
|
||||
}
|
||||
|
||||
bool WifiLan::StartDiscovery(const std::string& service_id,
|
||||
DiscoveredServiceCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_id.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start WifiLan discovering with empty service id.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Can't discover WifiLan services because WifiLan isn't available.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsDiscoveringLocked(service_id)) {
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Refusing to start discovery of WifiLan services because another "
|
||||
"discovery is already in-progress.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartDiscovery(service_id, callback)) {
|
||||
NEARBY_LOG(INFO, "Failed to start discovery of WifiLan services.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark the fact that we're currently performing a WifiLan discovering.
|
||||
discovering_info_.service_id = service_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WifiLan::StopDiscovery(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsDiscoveringLocked(service_id)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't turn off WifiLan discovering because we never started "
|
||||
"discovering.");
|
||||
return;
|
||||
}
|
||||
|
||||
medium_.StopDiscovery(service_id);
|
||||
discovering_info_.Clear();
|
||||
}
|
||||
|
||||
bool WifiLan::IsDiscovering(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsDiscoveringLocked(service_id);
|
||||
}
|
||||
|
||||
bool WifiLan::IsDiscoveringLocked(const std::string& service_id) {
|
||||
return !discovering_info_.Empty();
|
||||
}
|
||||
|
||||
bool WifiLan::StartAcceptingConnections(const std::string& service_id,
|
||||
AcceptedConnectionCallback callback) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (service_id.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start accepting WifiLan connections with empty "
|
||||
"service id.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't start accepting WifiLan connections for %s because "
|
||||
"WifiLan isn't available.",
|
||||
service_id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to start accepting WifiLan connections for %s because "
|
||||
"another WifiLan service socket is already in-progress.",
|
||||
service_id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!medium_.StartAcceptingConnections(service_id, callback)) {
|
||||
NEARBY_LOG(INFO, "Failed to accept connections callback for %s.",
|
||||
service_id.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
accepting_connections_info_.service_id = service_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WifiLan::StopAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
if (!IsAcceptingConnectionsLocked(service_id)) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't stop accepting WifiLan connections because it was never "
|
||||
"started.");
|
||||
return;
|
||||
}
|
||||
|
||||
medium_.StopAcceptingConnections(accepting_connections_info_.service_id);
|
||||
// Reset our bundle of accepting connections state to mark that we're no
|
||||
// longer accepting connections.
|
||||
accepting_connections_info_.Clear();
|
||||
}
|
||||
|
||||
bool WifiLan::IsAcceptingConnections(const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
return IsAcceptingConnectionsLocked(service_id);
|
||||
}
|
||||
|
||||
bool WifiLan::IsAcceptingConnectionsLocked(const std::string& service_id) {
|
||||
return !accepting_connections_info_.Empty();
|
||||
}
|
||||
|
||||
WifiLanSocket WifiLan::Connect(WifiLanService& wifi_lan_service,
|
||||
const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOG(INFO, "WifiLan::Connect: service=%p", &wifi_lan_service);
|
||||
// Socket to return. To allow for NRVO to work, it has to be a single object.
|
||||
WifiLanSocket socket;
|
||||
|
||||
if (service_id.empty()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Refusing to create WifiLan socket with empty service_id.");
|
||||
return socket;
|
||||
}
|
||||
|
||||
if (!IsAvailableLocked()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Can't create client WifiLan socket [service_id=%s]; WifiLan "
|
||||
"isn't available.",
|
||||
service_id.c_str());
|
||||
return socket;
|
||||
}
|
||||
|
||||
socket = medium_.Connect(wifi_lan_service, service_id);
|
||||
if (!socket.IsValid()) {
|
||||
NEARBY_LOG(INFO, "Failed to Connect via WifiLan [service=%s]",
|
||||
service_id.c_str());
|
||||
}
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,118 @@
|
||||
#ifndef CORE_V2_INTERNAL_MEDIUMS_WIFI_LAN_H_
|
||||
#define CORE_V2_INTERNAL_MEDIUMS_WIFI_LAN_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/public/multi_thread_executor.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "platform_v2/public/wifi_lan.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
|
||||
class WifiLan {
|
||||
public:
|
||||
using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback;
|
||||
using AcceptedConnectionCallback = WifiLanMedium::AcceptedConnectionCallback;
|
||||
|
||||
// Returns true, if WifiLan communications are supported by a platform.
|
||||
bool IsAvailable() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Sets custom service info name, and then enables WifiLan advertising.
|
||||
// Returns true, if name is successfully set, and false otherwise.
|
||||
bool StartAdvertising(const std::string& service_id,
|
||||
const std::string& wifi_lan_service_info_name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Disables WifiLan advertising, and restores service info name to
|
||||
// what they were before the call to StartAdvertising().
|
||||
void StopAdvertising(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool IsAdvertising() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Enables WifiLan discovery mode. Will report any discoverable services in
|
||||
// range through a callback. Returns true, if discovery mode was enabled,
|
||||
// false otherwise.
|
||||
bool StartDiscovery(const std::string& service_id,
|
||||
DiscoveredServiceCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Disables WifiLan discovery mode.
|
||||
void StopDiscovery(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool IsDiscovering(const std::string& service_id) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Starts a worker thread, creates a WifiLan socket, associates it with a
|
||||
// service id.
|
||||
bool StartAcceptingConnections(const std::string& service_id,
|
||||
AcceptedConnectionCallback callback)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Closes socket corresponding to a service id.
|
||||
void StopAcceptingConnections(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
bool IsAcceptingConnections(const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Establishes connection to WifiLan service that was might be started on
|
||||
// another service with StartAcceptingConnections() using the same service_id.
|
||||
// Blocks until connection is established, or server-side is terminated.
|
||||
// Returns socket instance. On success, WifiLanSocket.IsValid() return true.
|
||||
WifiLanSocket Connect(WifiLanService& wifi_lan_service,
|
||||
const std::string& service_id)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
struct AdvertisingInfo {
|
||||
bool Empty() const { return service_id.empty(); }
|
||||
void Clear() { service_id.clear(); }
|
||||
|
||||
std::string service_id;
|
||||
};
|
||||
|
||||
struct DiscoveringInfo {
|
||||
bool Empty() const { return service_id.empty(); }
|
||||
void Clear() { service_id.clear(); }
|
||||
|
||||
std::string service_id;
|
||||
};
|
||||
|
||||
struct AcceptingConnectionsInfo {
|
||||
bool Empty() const { return service_id.empty(); }
|
||||
void Clear() { service_id.clear(); }
|
||||
|
||||
std::string service_id;
|
||||
};
|
||||
|
||||
// Same as IsAvailable(), but must be called with mutex_ held.
|
||||
bool IsAvailableLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Same as IsAdvertising(), but must be called with mutex_ held.
|
||||
bool IsAdvertisingLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Same as IsDiscovering(), but must be called with mutex_ held.
|
||||
bool IsDiscoveringLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Same as IsAcceptingConnections(), but must be called with mutex_ held.
|
||||
bool IsAcceptingConnectionsLocked(const std::string& service_id)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
mutable Mutex mutex_;
|
||||
WifiLanMedium medium_ ABSL_GUARDED_BY(mutex_);
|
||||
AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_);
|
||||
DiscoveringInfo discovering_info_ ABSL_GUARDED_BY(mutex_);
|
||||
AcceptingConnectionsInfo accepting_connections_info_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // CORE_V2_INTERNAL_MEDIUMS_WIFI_LAN_H_
|
||||
@@ -0,0 +1,50 @@
|
||||
#include "core_v2/internal/mediums/wifi_lan.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/public/wifi_lan.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace connections {
|
||||
namespace {
|
||||
|
||||
constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"};
|
||||
constexpr absl::string_view kServiceInfoName{
|
||||
"Simulated WifiLan service encrypted string #1"};
|
||||
|
||||
// TODO(edwinwu): Continue writing more tests after medium_environment is done.
|
||||
class WifiLanTest : public ::testing::Test {
|
||||
protected:
|
||||
using DiscoveredServiceCallback = WifiLanMedium::DiscoveredServiceCallback;
|
||||
|
||||
WifiLanTest() { env_.Stop(); }
|
||||
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
};
|
||||
|
||||
TEST_F(WifiLanTest, CanConstructValidObject) {
|
||||
env_.Start();
|
||||
WifiLan wifi_lan_a;
|
||||
WifiLan wifi_lan_b;
|
||||
|
||||
EXPECT_TRUE(wifi_lan_a.IsAvailable());
|
||||
EXPECT_TRUE(wifi_lan_b.IsAvailable());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(WifiLanTest, CanStartAdvertising) {
|
||||
env_.Start();
|
||||
WifiLan wifi_lan;
|
||||
EXPECT_TRUE(wifi_lan.StartAdvertising(std::string(kServiceID),
|
||||
std::string(kServiceInfoName)));
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace connections
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
Reference in New Issue
Block a user